Skip to content

Add shared dev environment for medik8s operators#21

Open
mpryc wants to merge 3 commits into
medik8s:mainfrom
mpryc:dev-environment
Open

Add shared dev environment for medik8s operators#21
mpryc wants to merge 3 commits into
medik8s:mainfrom
mpryc:dev-environment

Conversation

@mpryc

@mpryc mpryc commented Jul 2, 2026

Copy link
Copy Markdown

Add shared dev environment for medik8s operators

Why we need this PR

Every medik8s operator today requires its own manual cluster setup, deployment steps, and debugging workflow. There is no standard way to:

  • Stand up a local multi-node cluster that meets operator requirements (worker labels, softdog, cgroup delegation, inotify limits)
  • Deploy an operator with working webhooks (cert-manager certificates, TLS volume mounts)
  • Simulate node failures and observe the full remediation flow across NHC and any remediator (SNR, FAR, MDR)
  • Use the same workflow on both a local Kind cluster and a remote OCP cluster

This leads to inconsistent setups, wasted onboarding time, and "works on my machine" issues. New contributors have no clear path to run operators locally end-to-end.

This PR adds a shared dev/ directory in the tools repo that any operator can include with a one-line Makefile snippet. All operators get the same make dev-* targets with zero per-repo configuration.

Changes made

Files added under dev/:

File Purpose
dev.mk Makefile targets (dev-setup, dev-deploy, dev-describe, etc.) included via -include
common.sh Shared helper functions (kubectl/oc and docker/podman auto-detection)
setup.sh Creates Kind cluster (1 CP + 3 workers, or 3 CP + 3 workers with KIND_HA=true), installs cert-manager, OLM, labels workers, loads softdog
enable-certmanager.sh Creates cert-manager Issuer/Certificate for operator webhooks, patches deployment to mount TLS secret (skips if already mounted)
create-nhc.sh Creates a NodeHealthCheck CR auto-detecting the deployed remediator (SNR, FAR, or MDR)
simulate-failure.sh Failure simulations: kubelet stop, network partition, storm. On external clusters, prints oc debug / SSH commands (safe by default) or auto-executes with DEV_FORCE_SIMULATE=true
describe.sh Full cluster summary: nodes, operator pods (with node placement), NHC/SNR/FAR/NMO/SBR CRs, leader election, leases, events
summary.sh Remediation flow timeline (what happened during simulate/recover)
teardown.sh Destroys the Kind cluster
kind-config.yaml Kind cluster config (1 CP + 3 workers)
kind-config-ha.yaml Kind HA cluster config (3 CP + 3 workers, for SNR CP testing)

CI:

File Purpose
.github/workflows/shellcheck.yml Runs shellcheck on all dev/**/*.sh on PRs and pushes

Operator integration — add this snippet to any operator's Makefile:

# Shared dev environment
# Uses a local sibling checkout if available (e.g. ../tools),
# otherwise downloads the tools repo into .tools/ on first dev-* target use.
#
# IMPORTANT: Do NOT use $(shell git clone ...) here — $(shell) executes at
# Makefile parse time, so any make invocation (make build, make test, make help)
# would trigger a git clone. The dev-% fallback rule below is lazy: the clone
# only runs when a dev-* target is actually invoked.
TOOLS_DIR ?= $(shell cd .. && pwd)/tools
DEV_MK := $(TOOLS_DIR)/dev/dev.mk
ifeq ($(wildcard $(DEV_MK)),)
  TOOLS_DIR := $(shell pwd)/.tools
  DEV_MK := $(TOOLS_DIR)/dev/dev.mk
endif
-include $(DEV_MK)
ifeq ($(wildcard $(DEV_MK)),)
dev-%:
	@echo "Downloading medik8s/tools into $(TOOLS_DIR)..."
	@if [ -d $(TOOLS_DIR) ]; then echo "  Removing stale $(TOOLS_DIR)..."; rm -rf $(TOOLS_DIR); fi
	@git clone --depth 1 https://github.com/medik8s/tools.git $(TOOLS_DIR)
	@test -f $(DEV_MK) || { echo "Error: $(DEV_MK) not found after clone."; exit 1; }
	@$(MAKE) $@
endif

This uses a local sibling checkout (../tools) if available, otherwise shallow-clones the repo on first dev-* target use. The clone is lazy — it only runs when a dev target is invoked, not on every make invocation.

How it works

Kind cluster workflow:

make dev-setup              # Create Kind cluster (1 CP + 3 workers)
make dev-deploy             # Build image, load into Kind, install CRDs, deploy, configure cert-manager
make dev-describe           # Full summary of nodes, pods, CRs, events
make dev-simulate-failure   # Stop kubelet on a worker -> NotReady -> NHC triggers remediation
make dev-events             # Watch remediation-related events
make dev-summary            # Remediation flow timeline
make dev-recover            # Restart kubelet (Kind can't auto-reboot — see note below)
make dev-undeploy           # Remove operator from cluster
make dev-teardown           # Destroy Kind cluster

External cluster (OCP, etc.):

export SKIP_KIND=true
make dev-setup              # Configures namespaces + cert-manager (no Kind)
make dev-deploy             # Builds image, pushes to ttl.sh, deploys
make dev-describe           # Same observability targets work on any cluster
make dev-simulate-failure   # Prints oc debug command (safe by default)
make dev-undeploy           # Remove operator from cluster

Images are pushed to ttl.sh — an anonymous, ephemeral registry that requires no auth. Images expire after 2h by default (configurable via TTL_SH_TTL). You can also force ttl.sh on a Kind cluster with DEV_REGISTRY=ttl.sh make dev-deploy.

Kind vs OpenShift recovery:

On Kind, dev-simulate-failure stops kubelet via docker exec, which also kills the SNR agent pod on that node. Since a Kind container "reboot" doesn't restart kubelet, the automatic recovery can't complete — dev-recover simulates what a real reboot would do. The dev environment tests the detection/decision flow (NHC detects unhealthy node → creates remediation CR), not the full reboot cycle.

On OpenShift, SNR automatically reboots the unhealthy node, kubelet restarts on boot, and the node rejoins — full self-healing, no manual dev-recover needed. On external clusters, dev-simulate-failure prints oc debug / SSH commands by default; set DEV_FORCE_SIMULATE=true to auto-execute.

Multi-operator flow (NHC + any remediator):

# From the SNR directory (or FAR, MDR)
make dev-setup && make dev-deploy

# From the NHC directory
make dev-deploy    # Auto-creates NodeHealthCheck CR linking to deployed remediator

# Simulate and observe
make dev-simulate-failure
kubectl get nodes -w                    # Watch node go NotReady
kubectl get selfnoderemediation -A -w   # Watch remediator act
make dev-events                         # Remediation events
make dev-summary                        # Timeline of what happened
make dev-recover                        # Restore all workers (Kind only)

Key design decisions:

  • SKIP_KIND controls cluster lifecycle (Kind vs external), DEV_REGISTRY controls image delivery (local vs ttl.sh) — these are independent concerns
  • override CONTAINER_TOOL := ensures dev targets use the same container tool as setup.sh, regardless of operator Makefile defaults
  • cert-manager is installed by dev-setup because dev-deploy creates cert-manager Certificate resources for webhook TLS
  • cert-manager detection uses CRD presence check, not namespace check, so it correctly detects both upstream cert-manager and OCP's cert-manager operator
  • enable-certmanager.sh retries Issuer creation for up to 30s to handle cert-manager webhook startup delay, and skips the TLS volume patch if already present
  • Service names are computed from config/webhook/service.yaml + namePrefix from all kustomization locations (handles NHC's non-default namePrefix location)
  • Source files are never left modified: kustomization.yaml is backed up/restored via cp/mv + trap, imagePullPolicy sed patches are restored even on build failure
  • create-nhc.sh auto-detects deployed remediator templates (SNR → FAR → MDR), with configurable unhealthy duration (NHC_UNHEALTHY_DURATION, default 300s matching downstream docs)
  • Operator pods are found using both control-plane=controller-manager and app.kubernetes.io/component=controller-manager labels (NHC uses the latter)
  • dev-wait exits non-zero when no deployments are found, so CI/scripting can detect failures
  • The Makefile snippet uses a lazy dev-% fallback rule for git clone — eager $(shell git clone ...) would trigger on every make invocation
  • inotify limits check is skipped on non-Linux (macOS) and can be bypassed with --skip-inotify-check

Companion PRs

The following draft PRs add the Makefile snippet to individual operators:

Remaining operators that need the same change (identical snippet + .tools/ in .gitignore):

  • fence-agents-remediation
  • machine-deletion-remediation
  • node-maintenance-operator
  • customized-user-remediation
  • storage-based-remediation

Which issue(s) this PR fixes

N/A — new functionality.

Test plan

Tested manually with all 6 medik8s operators on Kind (podman, Kind v0.32.0, K8s v1.36.1) and external OCP (IBM Cloud ROKS 4.21):

Solo operator tests (Kind):

Operator Result Notes
SNR PASS Full flow including cert-manager, envsubst, imagePullPolicy restore
NHC PASS Label fallback (app.kubernetes.io/component), namePrefix from config/patches/
FAR PASS pipefail-safe grep, namePrefix detection
MDR PASS No webhooks — clean deploy
NMO PASS Cert-manager TLS patch skip (already has volume mount). Pod restarts on startup (NMO RBAC bug, stabilizes).
SBR PASS override CONTAINER_TOOL prevents mismatch. Namespace lookup via kustomization file. Leader election RBAC error (SBR bug).

Multi-operator tests (Kind):

Pair Result
NHC + SNR PASS — NHC CR auto-created linking to SNR
NHC + FAR PASS — NHC CR auto-created linking to FAR

External cluster tests (OCP 4.21):

Target Result
dev-setup (SKIP_KIND=true) PASS
dev-deploy (SNR, FAR, NHC via ttl.sh) PASS
dev-wait / dev-describe / dev-logs PASS
dev-undeploy PASS

Source file integrity: Verified all 6 operator repos have no modified source files (git diff) after deploy/undeploy cycles. The backup/restore mechanism (cp/mv + trap) correctly restores kustomization.yaml and imagePullPolicy patches even on failure.

CI: Shellcheck passes at all severity levels (including info).

@openshift-ci

openshift-ci Bot commented Jul 2, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: mpryc
Once this PR has been reviewed and has the lgtm label, please assign mshitrit for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared medik8s development environment under dev/: shell helpers, Kind cluster setup/teardown scripts, cert-manager and NodeHealthCheck bootstrapping, a Makefile with build/deploy/observe/simulate targets, failure-simulation and observability scripts, Kind config YAMLs, a ShellCheck CI workflow, and documentation.

Changes

Dev Environment Tooling

Layer / File(s) Summary
Shared shell helpers
dev/common.sh
Adds detect_kubectl/detect_container_tool helpers and default KUBECTL/CONTAINER_TOOL variables used by all dev scripts.
Cluster setup and teardown scripts
dev/setup.sh, dev/kind-config.yaml, dev/kind-config-ha.yaml, dev/teardown.sh
setup.sh bootstraps a Kind cluster or validates an external one, checks prerequisites, creates namespaces, and installs cert-manager/OLM; Kind configs define standard and HA topologies; teardown.sh deletes the named cluster.
Cert-manager wiring and NodeHealthCheck creation
dev/enable-certmanager.sh, dev/create-nhc.sh
enable-certmanager.sh provisions an Issuer/Certificate, annotates webhooks, and patches a deployment with the TLS volume; create-nhc.sh auto-detects a remediation template and applies a NodeHealthCheck CR.
Makefile build/deploy/lifecycle targets
dev/dev.mk
Defines cluster/image configuration and targets for setup, build, deploy, undeploy, redeploy, bundle operations, shell access, failure simulation, and help output.
Observability scripts and wrappers
dev/describe.sh, dev/summary.sh, dev/dev.mk
describe.sh and summary.sh report node/CR/event/pod status; Makefile wrappers expose logs, wait, events, summary, describe, node shell, and help.
Failure simulation
dev/simulate-failure.sh, dev/dev.mk
Implements kubelet-stop, network-partition, storm, and recover scenarios, wired via Makefile targets.
Documentation and CI
README.md, dev/README.md, .github/workflows/shellcheck.yml
Adds top-level dev environment docs, a comprehensive dev/README.md guide, and a ShellCheck workflow scanning dev/**/*.sh.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant devmk as dev.mk
  participant setupsh as setup.sh
  participant certsh as enable-certmanager.sh
  participant nhcsh as create-nhc.sh
  participant Kind
  participant Kubernetes as Kubernetes API

  Developer->>devmk: make dev-setup
  devmk->>setupsh: invoke setup.sh
  setupsh->>Kind: create cluster / verify existing
  setupsh->>Kubernetes: create namespaces, install cert-manager/OLM
  Developer->>devmk: make dev-deploy
  devmk->>Kubernetes: apply kustomize manifests
  devmk->>certsh: enable-certmanager.sh (if webhook config present)
  certsh->>Kubernetes: apply Issuer/Certificate, patch deployment
  devmk->>Kubernetes: wait for deployment Available
  devmk->>nhcsh: create-nhc.sh (if remediation template found)
  nhcsh->>Kubernetes: apply NodeHealthCheck CR
Loading
sequenceDiagram
  participant Developer
  participant devmk as dev.mk
  participant simsh as simulate-failure.sh
  participant Worker as Kind worker node
  participant Kubernetes as Kubernetes API

  Developer->>devmk: make dev-simulate-failure SCENARIO=kubelet-stop
  devmk->>simsh: invoke simulate-failure.sh
  simsh->>Worker: stop kubelet / drop iptables / concurrent stops
  simsh->>Kubernetes: monitor node status and remediation CRs
  Developer->>devmk: make dev-recover
  devmk->>simsh: invoke simulate-failure.sh recover
  simsh->>Worker: start kubelet / remove iptables rule
  simsh->>Kubernetes: wait for nodes Ready and CRs to clean up
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a shared dev environment for medik8s operators.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Shared Kind-based development environment for all medik8s operators.
Provides consistent cluster setup, operator deployment, failure simulation,
and observability across NHC, SNR, FAR, MDR, NMO, and SBR.

Features:
- Kind cluster with 1 CP + 3 workers, cert-manager, OLM, softdog
- Build/deploy operators with automatic cert-manager webhook configuration
- Failure simulations: kubelet stop, network partition, storm
- Observability: logs, describe, summary, events
- External cluster support (OCP, etc.) with SKIP_KIND=true
- ttl.sh ephemeral registry support (DEV_REGISTRY=ttl.sh)
- Rootless podman support with cpuset cgroup detection
- Shellcheck CI via GitHub Actions

Signed-off-by: Michal Pryc <mpryc@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@razo7 razo7 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested dev-* targets with all 6 Medik8s operators (SNR, NHC, FAR, MDR, NMO, SBR) on Kind v0.32.0 + K8s v1.34.8. Ran 6 solo cycles + retries, 1 non-NHC pairwise, and 5 NHC pairwise. Below are my results.

Solo Test Results

Operator Result Notes
SNR PASS (8/8) All dev targets work correctly
NHC PASS* Manual deploy needed (controller-gen Go 1.26 incompatibility + CRD $ref fix). Label mismatch breaks dev-wait/dev-logs
FAR PASS* Manual cert-manager workaround (dev-deploy Error 2 prevents enable-certmanager.sh)
MDR PASS (8/8) No webhooks, clean deployment
NMO PASS* Same cert-manager workaround as FAR
SBR PASS* Needs CONTAINER_TOOL=docker override

Pairwise Test Results

Pair Result Notes
SNR + MDR PASS Multi-operator deployment verified, dev-wait iterates both
NHC + SNR PASS Both run, dev-wait finds SNR only (NHC label mismatch)
NHC + FAR PASS Both run, no CRD/RBAC conflicts
NHC + MDR PASS Both run cleanly
NHC + NMO PARTIAL NHC runs, NMO CrashLoopBackOff (cert-manager timing)
NHC + SBR PASS Both run cleanly

The design is solid and works automatically for some operators, and for some we need some adjustments (see below). This is a very good first step towards shared kind cluster targets :)

Findings (sorted by priority)

  1. dev-deploy Error 2 aborts cert-manager setup — The kustomize build/apply shell block returns Error 2 to make despite all resources being created and the shell exiting 0. This prevents enable-certmanager.sh from running. Affects FAR, NMO, and any operator with webhooks. Running enable-certmanager.sh manually works perfectly.

  2. NHC label mismatch breaks dev-wait/dev-logs/dev-describe/dev-redeploy — NHC uses app.kubernetes.io/component=controller-manager while all dev targets hardcode control-plane=controller-manager. NHC pods are invisible to all label-based dev targets. Suggest adding a DEV_POD_LABEL variable with fallback: try control-plane=controller-manager first, then app.kubernetes.io/component=controller-manager. Tracked in RHWA-1312.

  3. dev-wait exits 0 when no deployments found — Returns success with "No operator deployments found" message. Should exit non-zero so CI/scripting detects the problem.

  4. CONTAINER_TOOL mismatchsetup.sh auto-detects Docker, but operator Makefiles may default to podman. SBR's CONTAINER_TOOL ?= podman causes build failures. Document that CONTAINER_TOOL should be passed explicitly, or have dev.mk's detection take precedence. We could maybe include SBR fix at medik8s/storage-based-remediation#63

  5. kustomize commonLabels deprecation warnings — All operators emit "Warning: 'commonLabels' is deprecated". Not blocking but noisy.

  6. NHC CRD uses $ref rejected by K8s 1.34 — NHC's CRD has $ref constructs in validation schemas. Modern K8s rejects these. Fixed for testing by removing $ref and adding type: object + x-kubernetes-preserve-unknown-fields: true to 3 fields. This is an NHC upstream issue, not tools PR.

  7. dev-describe misses NHC pods in "Operator Pods" section. Same label mismatch as finding 2 from above. NHC events appear in the events section.

  8. enable-certmanager.sh reports "patched (no change)" for some operators — NMO's deployment already has the TLS volume mount in its default config, so the patch is a no-op. Not harmful but confusing output.

Comment thread dev/dev.mk Outdated
else \
$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -; \
fi
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This restore step (kustomize edit set image controller=$(IMG)) fails with Error 2 for FAR, NMO, and SBR. Since the entire dev-deploy recipe is a single make rule, this failure prevents enable-certmanager.sh from running.

Impact: Operators with webhooks (FAR, NMO) deploy but crash on startup with open /tmp/k8s-webhook-server/serving-certs/tls.crt: no such file or directory.

Tested on: FAR, NMO, and SBR when all hit the same error.

Suggested fix: Add || true to this line, or move cert-manager setup before the restore:

cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) || true

The restore is a courtesy to avoid dirtying the operator's git state — it should never block deployment.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Replaced kustomize edit set image controller=$(IMG) restore with cp/mv backup+trap pattern. The kustomization.yaml is now backed up before modification and restored via trap on EXIT - even if kustomize build or kubectl apply fails, cert-manager setup still runs. Also wrapped grep calls in { ... || true; } to prevent pipefail from killing the recipe when config/patches/ doesn't exist (FAR).

Comment thread dev/dev.mk
@# Files are restored after build (even on failure) via trap.
ifeq ($(DEV_REGISTRY),local)
@patched=""; \
for f in $$(find install/ -name '*.yaml' 2>/dev/null); do \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup.sh uses common.sh's detect_container_tool() which prefers Docker. But dev-build inherits $(CONTAINER_TOOL) from the operator's Makefile. SBR sets CONTAINER_TOOL ?= podman, so:

  1. setup.sh creates cluster with Docker
  2. dev-build tries KIND_EXPERIMENTAL_PROVIDER=podman kind load image-archive
  3. kind can't find the cluster's Docker-managed nodes

Tested on: SBR (cycle 06) — kind load fails with "ERROR: no nodes found for cluster".

Suggested fix: dev.mk should source CONTAINER_TOOL from common.sh instead of inheriting from the operator:

CONTAINER_TOOL := $(shell . $(DEV_DIR)/common.sh && echo $$CONTAINER_TOOL)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Changed from CONTAINER_TOOL ?= to override CONTAINER_TOOL := in dev.mk. This ensures dev targets always use the same auto-detected container tool as setup.sh, regardless of what the operator's Makefile sets. The override directive takes precedence over both ?= and command-line overrides from the operator Makefile.

Comment thread dev/dev.mk
echo "Could not find deployment to restart. Run 'make dev-deploy' first."; \
fi

.PHONY: dev-logs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When dev-deploy fails partway through (no deployment created), dev-wait reports "No operator deployments found" but exits 0. This masks deployment failures in scripts.

Tested on: NHC as dev-deploy failed at manifests target, no deployment created, dev-wait exited 0.

Suggested fix: Exit non-zero when no deployments are found.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. dev-wait now exits 1 when no operator deployments are found.

Comment thread dev/setup.sh Outdated
# Check inotify limits — Kind nodes inherit host limits and operators need many watchers.
INOTIFY_INSTANCES=$(cat /proc/sys/fs/inotify/max_user_instances 2>/dev/null || echo 0)
INOTIFY_WATCHES=$(cat /proc/sys/fs/inotify/max_user_watches 2>/dev/null || echo 0)
if [ "${INOTIFY_INSTANCES}" -lt 512 ] || [ "${INOTIFY_WATCHES}" -lt 524288 ]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard exit at max_user_instances < 512 blocked my machine (default: 128). Kind works fine with 128 for single-operator testing.

Suggested fix: Add a --skip-inotify-check flag or lower the threshold for single-operator use.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added --skip-inotify-check flag to setup.sh. The dev-setup target passes it through when invoked. Users can skip the check with: make dev-setup SKIP_INOTIFY_CHECK=true or directly: ./setup.sh --skip-inotify-check

The check message also now tells users about the skip flag.

Without the skip flag there is a hard exit as I want to make it explicit - normally full e2e tests is not passing with standard limits on 2 test boxes.

Comment thread dev/kind-config.yaml
@@ -0,0 +1,15 @@
# Kind cluster configuration for medik8s development

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without image: on node entries, Kind uses its compiled-in default. Kind v0.20.0 defaults to K8s v1.27.3, incompatible with cert-manager latest (selectableFields requires K8s 1.29+).

I worked around this by adding KIND_NODE_IMAGE env var support to setup.sh, but new users will hit a cryptic CRD validation error.

Suggested fix: Pin the node image in kind-config.yaml to at least v1.30.0, or pin cert-manager to a compatible version.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed differently. Instead of pinning a node image (which would need constant updating and this is just dev environment so rarely people do update), we now enforce Kind >= 0.22.0 at startup. Kind 0.22.0+ defaults to K8s 1.29+, which is compatible with cert-manager latest. The README also documents the minimum Kind version (v0.22.0+).

This will allow us to catch API changes in the latest K8s versions much quicker rather than manually updating pinned versions.

Comment thread dev/simulate-failure.sh Outdated
Comment on lines +146 to +159
echo "Waiting for SNR controller to finish remediation cleanup..."
# The SNR controller needs to observe the node is healthy, remove taints,
# and strip its finalizer before the CR can be deleted. Give it time.
REMAINING=0
WAITED=0
while [ $WAITED -lt 120 ]; do
REMAINING=$(${KUBECTL} get selfnoderemediation -A --no-headers 2>/dev/null | wc -l)
if [ "$REMAINING" -eq 0 ]; then
break
fi
echo " $REMAINING remediation CR(s) still being processed..."
sleep 5
WAITED=$((WAITED + 5))
done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be any other remediator CRs...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, see above comment.

Comment thread dev/README.md

```bash
# 1. Create the Kind cluster (from any operator directory)
pushd self-node-remediation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Comment thread dev/README.md
Comment on lines +237 to +242
## Limitations

- **FAR fence agent execution** — no IPMI/BMC or cloud APIs
- **MDR Machine API** — Kind has no Machine objects
- **SBR shared storage** — no ODF
- **Hardware watchdog** — only softdog (software)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to overcome these limitations?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check this, but IMO not within first version of this dev-env?

For now I've added guidance in the Limitations section:

"For full-fidelity testing of these scenarios, use an OpenShift cluster with real hardware (BMC/IPMI for FAR, Machine API for MDR, ODF for SBR, hardware watchdog for SNR). The SKIP_KIND=true workflow supports deploying to external clusters."

Comment thread dev/dev.mk
@echo " Image will expire after $(TTL_SH_TTL)."
endif

.PHONY: dev-deploy

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To deploy/install an operator, we can use bundle-run target to install the bundle.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added dev-bundle-run and dev-bundle-cleanup targets.

Comment thread dev/dev.mk
fi

.PHONY: dev-undeploy
dev-undeploy: ## Remove operator from dev cluster

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To undeploy/uninstall an operator, we can use bundle-cleanup target to uninstall the bundle.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, see above comment.

…obustness fixes

Key changes addressing Or Raz's review:

- Fix dev-deploy Error 2: backup/restore kustomization.yaml with cp/mv+trap
  instead of kustomize edit restore; wrap grep in { || true } for pipefail safety
- Fix CONTAINER_TOOL mismatch: use `override CONTAINER_TOOL :=` so dev targets
  always use the same tool as setup.sh regardless of operator Makefile defaults
- Fix NHC label mismatch: add `app.kubernetes.io/component=controller-manager`
  fallback in _dev_find_ns, dev-deploy, dev-wait, dev-logs, dev-redeploy, dev-describe
- Fix dev-wait exit code: exit 1 when no deployments found
- Fix inotify check: add --skip-inotify-check flag for single-operator use
- Fix Kind version: enforce >= 0.22.0 (K8s 1.29+ for cert-manager selectableFields)
- Fix cert-manager DNS names: read namePrefix from all kustomization locations
- Fix enable-certmanager.sh: skip patch when deployment already has TLS volume
- Support multiple remediators: create-nhc.sh auto-detects SNR/FAR/MDR templates
- Add NHC_UNHEALTHY_DURATION config (default: 300s, matching downstream docs)
- Add KIND_HA=true support with kind-config-ha.yaml (3 CP + 3 workers)
- Add dev-bundle-run / dev-bundle-cleanup targets for OLM bundle deployment
- Filter kustomize commonLabels deprecation warnings from output
- Merge dev-build recipe lines so trap covers container build failures
- describe.sh: show pod node placement and leader election info
- simulate-failure.sh: show monitor hints for all installed remediator CRDs
- Update README: lazy clone snippet, Kind version, OLM v0 note, SNR envsubst
  limitation, operator coverage known issues, external cluster workflow

Signed-off-by: Michal Pryc <mpryc@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mpryc

mpryc commented Jul 9, 2026

Copy link
Copy Markdown
Author

@razo7 Than you for such detailed review. I think all of the comments were addressed. Also tested the full update against:

  • Kind cluster: all 6 operators (SNR, MDR, FAR, NHC, NMO, SBR) solo and simultaneously, including failure simulation and recovery
  • OCP 4.21 (IBM Cloud ROKS): SNR, FAR, NHC with SKIP_KIND=true + ttl.sh

@mpryc
mpryc marked this pull request as ready for review July 9, 2026 16:57
@openshift-ci
openshift-ci Bot requested review from razo7 and slintes July 9, 2026 16:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (8)
dev/README.md (2)

79-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to fenced code block.

The code block at line 79 (sysctl config) lacks a language specifier, triggering markdownlint MD040. Use ini or conf:

+```ini
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=524288

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/README.md` at line 79, Add a language specifier to the fenced sysctl
configuration block in the README to satisfy markdownlint MD040; update the
markdown fence near the sysctl example to use a config language such as ini or
conf, keeping the block content unchanged.

Source: Linters/SAST tools


130-130: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to fenced code block.

The code block at line 130 (echo command) lacks a language specifier, triggering markdownlint MD040. Use bash:

+```bash
echo '.tools/' >> .gitignore

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/README.md` at line 130, The fenced code block for the echo command is
missing a language specifier, which triggers markdownlint MD040. Update the
markdown snippet in the README so the block is labeled with bash, and ensure the
fenced block around the echo command uses the proper language hint.

Source: Linters/SAST tools

dev/summary.sh (1)

54-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Only checks control-plane=controller-manager label, missing the fallback used in describe.sh.

describe.sh iterates over both control-plane=controller-manager and app.kubernetes.io/component=controller-manager labels (line 22), but summary.sh only uses the first. If an operator uses only the app.kubernetes.io/component label, its pods won't appear here. Consider matching the same label set for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/summary.sh` around lines 54 - 56, The controller-manager pod listing in
summary.sh only filters on the control-plane=controller-manager label, so it can
miss pods labeled with app.kubernetes.io/component=controller-manager. Update
the pod query in the summary script to use the same label matching logic as
describe.sh, so both label variants are covered consistently. Keep the change in
the get pods command that produces the controller-manager summary output.
.github/workflows/shellcheck.yml (1)

15-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Bump actions/checkout to v7. v6 is still a valid tag, but v7.0.0 is the current release; ludeeus/action-shellcheck@2.0.0 is already current.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/shellcheck.yml around lines 15 - 18, Update the workflow
to use the current release of actions/checkout by bumping the checkout step from
v6 to v7 in the shellcheck job; keep ludeeus/action-shellcheck@2.0.0 unchanged,
and adjust the action reference in the workflow definition accordingly.
dev/setup.sh (1)

43-46: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

--name without a value produces a cryptic error under set -e.

If --name is passed without a following argument, shift 2 fails silently under set -e and the script exits without a helpful message. Consider validating $# -ge 2 before consuming $2.

🛡️ Proposed fix
         --name)
+            if [[ $# -lt 2 ]]; then
+                echo "Error: --name requires a cluster name argument."
+                exit 1
+            fi
             CLUSTER_NAME="$2"
             shift 2
             ;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/setup.sh` around lines 43 - 46, The `--name` option handling in
`setup.sh` can fail cryptically when no value follows it, so add an explicit
argument-count check before consuming `$2` in the `--name)` case. Update the
option parsing logic to validate that at least two arguments remain, emit a
clear error message if the value is missing, and only then assign `CLUSTER_NAME`
and `shift 2` in the same branch.
dev/common.sh (1)

30-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider lazy detection to avoid coupling scripts to tools they don't use.

Both detect_kubectl and detect_container_tool execute at source time, so any script sourcing common.sh will hard-exit if either tool family is missing — even scripts that only need one. For example, teardown.sh only uses KUBECTL but will fail if neither docker nor podman is installed.

This is a reasonable tradeoff for a dev environment where both tool families are expected to coexist, but if you later add scripts that only need kubectl (e.g., a pure observability script run against an external cluster without container tooling), they'll be blocked unnecessarily. Consider deferring detection to first use, or splitting into separate source lines per script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/common.sh` around lines 30 - 31, Lazy-load tool detection in common.sh so
sourcing it does not fail for scripts that only need one dependency. The current
KUBECTL and CONTAINER_TOOL assignments eagerly call detect_kubectl and
detect_container_tool at source time; refactor this so detection happens on
first use or is split behind script-specific sourcing, keeping the existing
symbols available without coupling every consumer to both tool families.
dev/enable-certmanager.sh (1)

76-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

CA-injection annotation loop uses a fragile substring match.

grep -q "namespace: ${NAMESPACE}" against the full YAML of every webhookconfiguration in the cluster can match unrelated fields (labels, annotations, ownerReferences) rather than clientConfig.service.namespace. In a shared dev cluster with multiple operators/cert-manager/OLM webhooks, this could annotate an unrelated webhook for CA injection and disrupt its TLS trust chain.

♻️ Suggested fix: query the specific field
 for wh_type in mutatingwebhookconfigurations validatingwebhookconfigurations; do
     for wh in $(${KUBECTL} get "${wh_type}" -o name 2>/dev/null); do
-        # Only annotate webhooks that reference services in our namespace
-        if ${KUBECTL} get "${wh}" -o yaml 2>/dev/null | grep -q "namespace: ${NAMESPACE}"; then
+        # Only annotate webhooks whose clientConfig actually targets our namespace
+        if ${KUBECTL} get "${wh}" -o jsonpath='{.webhooks[*].clientConfig.service.namespace}' 2>/dev/null | grep -qw "${NAMESPACE}"; then
             ${KUBECTL} annotate "${wh}" cert-manager.io/inject-ca-from="${NAMESPACE}/serving-cert" --overwrite 2>/dev/null || true
         fi
     done
 done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/enable-certmanager.sh` around lines 76 - 83, The webhook CA-injection
loop is matching on a broad YAML substring instead of the service namespace
field. Update the logic in the webhook iteration around the ${KUBECTL}
get/annotate flow to inspect only clientConfig.service.namespace for each
mutatingwebhookconfigurations/validatingwebhookconfigurations entry, and then
apply cert-manager.io/inject-ca-from only when that exact field equals
${NAMESPACE}. Keep the existing wh_type/wh iteration but replace the fragile
grep-based check with a precise field query.
dev/create-nhc.sh (1)

16-29: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

--duration without a value crashes with an unhelpful error.

If --duration is the last argument, shift 2 (line 20) errors with "shift count out of range" instead of a clear usage message, because set -u (line 6) also makes the preceding $2 reference an unbound-variable error first.

🛡️ Suggested fix
     case $1 in
         --duration)
+            if [ $# -lt 2 ]; then
+                echo "Error: --duration requires a value" >&2
+                exit 1
+            fi
             NHC_UNHEALTHY_DURATION="$2"
             shift 2
             ;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dev/create-nhc.sh` around lines 16 - 29, The --duration option handling in
create-nhc.sh should validate that a value is present before accessing $2 or
calling shift 2. Update the argument parsing in the while/case block so the
--duration branch checks for a missing next argument and prints the existing
usage message with a clean exit instead of triggering set -u or “shift count out
of range”; keep the fix localized around the --duration case and the script’s
usage output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/shellcheck.yml:
- Around line 14-15: The Checkout step in the shellcheck workflow uses
actions/checkout without disabling stored GitHub credentials. Update the
checkout configuration in the workflow to set persist-credentials to false on
the actions/checkout invocation so this job does not keep the token in
.git/config.

In `@dev/dev.mk`:
- Around line 144-197: The dev-deploy recipe is swallowing failures from
critical steps, so make it stop on errors and return non-zero when setup or
readiness checks fail. In dev-deploy, ensure the kustomize edit set image step
and enable-certmanager.sh are chained with explicit failure propagation, and
change the kubectl wait / warning path so a failed deployment readiness check
sets an error status instead of only logging. Use the dev-deploy target plus the
kustomize edit set image, enable-certmanager.sh, and kubectl wait blocks to
locate the fix.
- Around line 9-56: Move the CONTAINER_TOOL auto-detection block above the
DEV_CLUSTER_TYPE selection in dev.mk so the Kind probe has a valid provider when
it expands KIND_EXPERIMENTAL_PROVIDER. Update the ordering around
DEV_CLUSTER_TYPE, DEV_REGISTRY, and DEV_IMG so $(CONTAINER_TOOL) is defined
before it is referenced, preserving the intended kind vs external detection and
the correct image branch for podman-backed setups.

In `@dev/setup.sh`:
- Around line 106-135: The inotify validation in the setup script always runs
even on non-Linux hosts, causing macOS to fail because /proc/sys/fs/inotify does
not exist. Update the logic in the inotify check block of setup.sh to detect the
platform first (for example via uname or a Linux-only guard) and automatically
skip the inotify limits check on non-Linux systems while preserving the existing
threshold check on Linux.

In `@dev/summary.sh`:
- Line 17: The node status lookup in the kubectl command is using the last entry
in .status.conditions, which may not be the Ready condition. Update the logic in
the summary.sh node listing to explicitly select the Ready condition (or switch
to plain kubectl get nodes, which reports readiness correctly) so the
STATUS/READY columns reflect the actual node readiness rather than an arbitrary
condition.

---

Nitpick comments:
In @.github/workflows/shellcheck.yml:
- Around line 15-18: Update the workflow to use the current release of
actions/checkout by bumping the checkout step from v6 to v7 in the shellcheck
job; keep ludeeus/action-shellcheck@2.0.0 unchanged, and adjust the action
reference in the workflow definition accordingly.

In `@dev/common.sh`:
- Around line 30-31: Lazy-load tool detection in common.sh so sourcing it does
not fail for scripts that only need one dependency. The current KUBECTL and
CONTAINER_TOOL assignments eagerly call detect_kubectl and detect_container_tool
at source time; refactor this so detection happens on first use or is split
behind script-specific sourcing, keeping the existing symbols available without
coupling every consumer to both tool families.

In `@dev/create-nhc.sh`:
- Around line 16-29: The --duration option handling in create-nhc.sh should
validate that a value is present before accessing $2 or calling shift 2. Update
the argument parsing in the while/case block so the --duration branch checks for
a missing next argument and prints the existing usage message with a clean exit
instead of triggering set -u or “shift count out of range”; keep the fix
localized around the --duration case and the script’s usage output.

In `@dev/enable-certmanager.sh`:
- Around line 76-83: The webhook CA-injection loop is matching on a broad YAML
substring instead of the service namespace field. Update the logic in the
webhook iteration around the ${KUBECTL} get/annotate flow to inspect only
clientConfig.service.namespace for each
mutatingwebhookconfigurations/validatingwebhookconfigurations entry, and then
apply cert-manager.io/inject-ca-from only when that exact field equals
${NAMESPACE}. Keep the existing wh_type/wh iteration but replace the fragile
grep-based check with a precise field query.

In `@dev/README.md`:
- Line 79: Add a language specifier to the fenced sysctl configuration block in
the README to satisfy markdownlint MD040; update the markdown fence near the
sysctl example to use a config language such as ini or conf, keeping the block
content unchanged.
- Line 130: The fenced code block for the echo command is missing a language
specifier, which triggers markdownlint MD040. Update the markdown snippet in the
README so the block is labeled with bash, and ensure the fenced block around the
echo command uses the proper language hint.

In `@dev/setup.sh`:
- Around line 43-46: The `--name` option handling in `setup.sh` can fail
cryptically when no value follows it, so add an explicit argument-count check
before consuming `$2` in the `--name)` case. Update the option parsing logic to
validate that at least two arguments remain, emit a clear error message if the
value is missing, and only then assign `CLUSTER_NAME` and `shift 2` in the same
branch.

In `@dev/summary.sh`:
- Around line 54-56: The controller-manager pod listing in summary.sh only
filters on the control-plane=controller-manager label, so it can miss pods
labeled with app.kubernetes.io/component=controller-manager. Update the pod
query in the summary script to use the same label matching logic as describe.sh,
so both label variants are covered consistently. Keep the change in the get pods
command that produces the controller-manager summary output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 22c52222-9b9a-4f02-94fb-841d1ae27ac4

📥 Commits

Reviewing files that changed from the base of the PR and between 096f222 and d4b39bd.

📒 Files selected for processing (14)
  • .github/workflows/shellcheck.yml
  • README.md
  • dev/README.md
  • dev/common.sh
  • dev/create-nhc.sh
  • dev/describe.sh
  • dev/dev.mk
  • dev/enable-certmanager.sh
  • dev/kind-config-ha.yaml
  • dev/kind-config.yaml
  • dev/setup.sh
  • dev/simulate-failure.sh
  • dev/summary.sh
  • dev/teardown.sh

Comment thread .github/workflows/shellcheck.yml
Comment thread dev/dev.mk Outdated
Comment thread dev/dev.mk
Comment thread dev/setup.sh Outdated
Comment thread dev/summary.sh Outdated
…pagation

- Move CONTAINER_TOOL auto-detection above DEV_CLUSTER_TYPE in dev.mk so
  KIND_EXPERIMENTAL_PROVIDER has a valid value during Kind cluster probe
- Skip inotify limits check on non-Linux hosts (macOS has no /proc/sys/fs/inotify)
- Change dev-deploy kubectl wait from warning to error exit on readiness failure
- Add persist-credentials: false to shellcheck workflow checkout step
- Fix summary.sh node status: use kubectl get nodes -o wide instead of
  conditions[-1:] which may not be the Ready condition
- Add external cluster support to simulate-failure.sh: prints oc debug / SSH
  commands by default, DEV_FORCE_SIMULATE=true to auto-execute via oc debug
- Add Kind vs OpenShift recovery documentation to README with examples
- Make cleanup steps consistent: dev-undeploy (both) + dev-teardown (Kind only)

Signed-off-by: Michal Pryc <mpryc@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mpryc
mpryc force-pushed the dev-environment branch from 680c99f to bb0890e Compare July 9, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants