Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: shellcheck

on:
push:
branches: [main]
paths: ["dev/**/*.sh"]
pull_request:
paths: ["dev/**/*.sh"]

jobs:
shellcheck:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Run shellcheck
uses: ludeeus/action-shellcheck@2.0.0
with:
scandir: dev
severity: warning
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,28 @@
# tools
Tools etc. which are not specific for one of the medik8s operators

## Dev Environment

Shared development environment for all medik8s operators. See [dev/README.md](dev/README.md) for full documentation.

### Quick Start

```bash
# 1. Add to your operator's Makefile (one-time):
# TOOLS_DIR ?= $(shell cd .. && pwd)/tools
# -include $(TOOLS_DIR)/dev/dev.mk

# 2. Create the dev cluster:
make dev-setup

# 3. Build and deploy your operator:
make dev-deploy

# 4. Simulate failures:
make dev-simulate-failure
```

## Other Tools

- `findIndexImage/` — Find OCP index images for IIB discovery
- `scripts/` — Build and deploy scripts for NHC+SNR
244 changes: 244 additions & 0 deletions dev/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Medik8s Development Environment

A shared, consistent development environment for all medik8s operators.

## Quick Start

```bash
# One-time: add the dev environment snippet to your operator's Makefile
# (see Setup section below for options)

make dev-setup # Create Kind cluster
make dev-deploy # Build and deploy operator
make dev-describe # Verify everything is running
make dev-simulate-failure && kubectl get nodes -w # Test remediation
make dev-recover # Restore cluster
make dev-teardown # Clean up
```

### Full Walkthrough (NHC + SNR)

This walks through the complete remediation flow: deploy both operators,
simulate a node failure, and watch NHC trigger SNR to remediate it.

```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.

make dev-setup
popd

# 2. Deploy SNR
pushd self-node-remediation
make dev-deploy
popd

# 3. Deploy NHC (auto-creates NodeHealthCheck CR linking to SNR)
pushd node-healthcheck-operator
make dev-deploy
popd

# 4. Verify everything is running
make dev-wait
make dev-describe

# 5. Simulate a node failure
make dev-simulate-failure

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 README snippet uses $(shell git clone ...) inside ifeq — eager clone pattern. Any make invocation triggers git clone if .tools/ doesn't exist.

SNR PR #313 uses a better lazy clone with a dev-% fallback rule. The lazy pattern should be the only recommended approach.

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. The lazy clone pattern is in the README as well corresponding 2 PRs for the medik8s/self-node-remediation#313 and medik8s/node-healthcheck-operator#410


# 6. Watch the remediation flow (run in separate terminals)
kubectl get nodes -w
kubectl get selfnoderemediation -A -w
make dev-events

# 7. Debug if needed
make dev-logs # tail operator logs
make dev-describe # full resource summary
make dev-summary # remediation flow timeline
make dev-shell # shell into a worker node

# 8. Recover all workers
make dev-recover

# 9. Teardown
make dev-teardown
```

## Prerequisites

- [Go](https://go.dev/doc/install) (version matching the operator's go.mod)
- [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) (v0.20+)
- [kubectl](https://kubernetes.io/docs/tasks/tools/) or [oc](https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/)
- [Docker](https://docs.docker.com/get-docker/) or [Podman](https://podman.io/getting-started/installation)
- [operator-sdk](https://sdk.operatorframework.io/docs/installation/) (optional, for OLM bundle testing)

### System limits

The setup script checks inotify limits and exits with an error if they are too low (auto-fixes when running as root). Fix manually before running `make dev-setup`:

```bash
sudo sysctl -w fs.inotify.max_user_instances=8192
sudo sysctl -w fs.inotify.max_user_watches=524288
```

To make persistent, add to `/etc/sysctl.d/99-kind.conf`:
```
fs.inotify.max_user_instances=8192
fs.inotify.max_user_watches=524288
```

### Podman rootless setup

Rootless podman requires `cpuset` cgroup delegation for Kind worker nodes. Without it, kubelet cannot start inside the containers. The setup script detects this and prints instructions.

```bash
sudo mkdir -p /etc/systemd/system/user@.service.d
sudo tee /etc/systemd/system/user@.service.d/delegate.conf <<EOF
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload
```

**You must log out and log back in** for the changes to take effect. After that, all commands work without `sudo`.

## Setup

Add the following snippet at the end of your operator's Makefile:

```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.
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)..."
@git clone --depth 1 https://github.com/medik8s/tools.git $(TOOLS_DIR)
@$(MAKE) $@
endif
```

Add `.tools/` to your `.gitignore`:
```
echo '.tools/' >> .gitignore
```

To update the downloaded copy, delete it and re-run:
```bash
rm -rf .tools && make dev-help
```

All `dev-*` targets are now available.

## What It Creates

- **1 control-plane + 3 worker nodes** (SNR needs 2+ workers for peer health; multi-CP requires a real cluster)
- **Worker labels** (`node-role.kubernetes.io/worker`)
- **softdog** kernel module on workers (for SNR watchdog testing)
- **Namespaces**: `medik8s-system` (privileged PSA) and `medik8s-leases` for shared resources
- **cert-manager** (required for operator webhook TLS certificates)
- **inotify limits** increased automatically when running as root
- **OLM** (if operator-sdk is available)

Re-running `make dev-setup` on an existing cluster is safe — it re-applies configuration without recreating.

**Note:** Each operator deploys into its own namespace (e.g. `self-node-remediation`, `node-healthcheck-operator-system`) as defined in its kustomization files (either `config/default/kustomization.yaml` or a component/patch kustomization). The `dev-deploy` target automatically creates cert-manager certificates, patches the deployment to mount webhook TLS secrets, and waits for the deployment to become ready. If both NHC and SNR are deployed, it also creates a NodeHealthCheck CR linking them.

## Failure Simulations

| Command | What it does |
|---------|-------------|
| `make dev-simulate-failure` | Stop kubelet on a worker → node goes NotReady → NHC creates remediation CR |
| `make dev-simulate-network` | Block API server from a worker → tests SNR peer health decisions |
| `make dev-simulate-storm` | Stop kubelet on 2/3 workers → NHC detects storm, pauses remediation |
| `make dev-recover` | Restart kubelet, restore network, clean up CRs |

## All Targets

| Target | Description |
|--------|-------------|
| `dev-setup` | Create Kind cluster with all dependencies (or configure external cluster with `SKIP_KIND=true`) |
| `dev-teardown` | Destroy Kind cluster |
| `dev-build` | Build operator image and load into Kind (or push to ttl.sh) |
| `dev-deploy` | Build + install CRDs + deploy + configure cert-manager |
| `dev-redeploy` | Rebuild and restart (deletes pods to pick up new image) |
| `dev-undeploy` | Remove operator from cluster |
| `dev-create-nhc` | Create NodeHealthCheck CR referencing SNR |
| `dev-logs` | Tail operator controller-manager logs |
| `dev-describe` | Full summary (nodes, pods, CRs, leases, events) |
| `dev-summary` | Remediation flow timeline |
| `dev-events` | Show recent remediation-related events |
| `dev-wait` | Wait for all operator pods to be ready |
| `dev-shell` | Open a shell on a Kind node (`NODE=<name>`, default: first worker) |
| `dev-simulate-failure` | Stop kubelet on a worker |
| `dev-simulate-storm` | Stop kubelet on 2 workers |
| `dev-simulate-network` | Block API server from a worker |
| `dev-recover` | Recover all workers and clean up |
| `dev-help` | Show all dev targets |

## Using an Existing Cluster (OCP, etc.)

For external clusters (OCP, etc.), set `SKIP_KIND=true` to skip Kind
creation. Images are pushed to [ttl.sh](https://ttl.sh) — an anonymous,
ephemeral registry that requires no auth.

```bash
# Point kubectl at your cluster, then use the same workflow
export KUBECONFIG=~/.kube/my-ocp-cluster
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 # Verify everything is running
```

Exporting `SKIP_KIND=true` ensures all targets know this is an external cluster.
Without it, auto-detection might find a local Kind cluster and use the wrong
image delivery method.

You can also use ttl.sh with a Kind cluster:

```bash
DEV_REGISTRY=ttl.sh make dev-deploy
```

**Note:** Failure simulations (`dev-simulate-failure`, etc.) require
`docker exec`/`podman exec` access to Kind node containers. On external
clusters, trigger failures through your cluster's own mechanisms.

## Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `SKIP_KIND` | `false` | Set to `true` to skip Kind creation (external cluster) |
| `DEV_REGISTRY` | `local` (Kind) / `ttl.sh` (external) | Image delivery: `local` or `ttl.sh` |
| `DEV_IMG` | auto-generated | Override to use a custom image name |
| `TTL_SH_TTL` | `2h` | Image expiry when using ttl.sh |
| `MEDIK8S_CLUSTER_NAME` | `medik8s-dev` | Kind cluster name |
| `CONTAINER_TOOL` | auto-detected | `docker` or `podman` |
| `KUBECTL` | auto-detected | `kubectl` or `oc` |

## Operator Coverage

| Operator | Coverage | Notes |
|----------|----------|-------|
| NHC | Full | Node conditions, storm recovery, escalation, CP protection |
| NMO | Full | Cordon, drain, PDB-aware eviction |
| SNR | ~85% | Peer health, softdog watchdog, API check. No hardware watchdog. |
| FAR | Controller logic | No real fence agents — controller reconciliation is testable |
| MDR | Controller logic | No Machine API — reconciliation testable via envtest (`make test`) |
| SBR | Unit only | No shared storage |

## 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)
Comment on lines +271 to +276

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."


For full-fidelity testing, use an OpenShift cluster.
31 changes: 31 additions & 0 deletions dev/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
# Shared helpers for medik8s dev scripts.
# Source this from other scripts: source "$(dirname "${BASH_SOURCE[0]}")/common.sh"

# Detect kubectl or oc
detect_kubectl() {
if command -v kubectl &>/dev/null; then
echo kubectl
elif command -v oc &>/dev/null; then
echo oc
else
echo "Error: kubectl or oc is required but neither is installed." >&2
echo "Install kubectl from: https://kubernetes.io/docs/tasks/tools/" >&2
exit 1
fi
}

# Detect container tool (docker preferred, podman fallback)
detect_container_tool() {
if command -v docker &>/dev/null; then
echo docker
elif command -v podman &>/dev/null; then
echo podman
else
echo "Error: docker or podman is required but neither is installed." >&2
exit 1
fi
}

KUBECTL="${KUBECTL:-$(detect_kubectl)}"
CONTAINER_TOOL="${CONTAINER_TOOL:-$(detect_container_tool)}"
56 changes: 56 additions & 0 deletions dev/create-nhc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
# Creates a NodeHealthCheck CR that references SNR for 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.

We don't support it ATM but NHC testing is still using SNR only. It should be more generic or even extended to support our other remediators...

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. create-nhc.sh now auto-detects any deployed remediator template:

tries SNR first, then FAR, then MDR. The NHC CR is created referencing whichever remediator is found. dev-deploy also checks for all three remediator template types before triggering NHC CR creation.

# Usage: create-nhc.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=common.sh
source "${SCRIPT_DIR}/common.sh"

# Check if the NHC CRD exists
if ! ${KUBECTL} get crd nodehealthchecks.remediation.medik8s.io &>/dev/null; then
echo "Error: NodeHealthCheck CRD not found. Deploy NHC first (make dev-deploy from the NHC directory)."
exit 1
fi

# Find the SNR template namespace
SNR_NS=$(${KUBECTL} get selfnoderemediationtemplate -A --no-headers -o custom-columns=NS:.metadata.namespace 2>/dev/null | head -1)
SNR_TEMPLATE=$(${KUBECTL} get selfnoderemediationtemplate -A --no-headers -o custom-columns=NAME:.metadata.name 2>/dev/null | head -1)

if [ -z "${SNR_NS}" ] || [ -z "${SNR_TEMPLATE}" ]; then
echo "Error: No SelfNodeRemediationTemplate found. Deploy SNR first (make dev-deploy from the SNR directory)."
exit 1
fi

echo "Creating NodeHealthCheck CR referencing ${SNR_TEMPLATE} in ${SNR_NS}..."

${KUBECTL} apply -f - <<EOF
apiVersion: remediation.medik8s.io/v1alpha1
kind: NodeHealthCheck
metadata:
name: nhc-worker-default
spec:
selector:
matchExpressions:
- key: node-role.kubernetes.io/worker
operator: Exists
minHealthy: "51%"
unhealthyConditions:
- type: Ready
status: "False"
duration: 30s

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 would suggest defaulting it to 360/300 as we document downstream. And make it configurable

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. Default changed from 30s to 300s (NHC_UNHEALTHY_DURATION=300s), matching downstream docs. Configurable via environment variable:

NHC_UNHEALTHY_DURATION=30s make dev-create-nhc

The output message tells users how to override it for faster dev testing.

- type: Ready
status: Unknown
duration: 30s
remediationTemplate:
apiVersion: self-node-remediation.medik8s.io/v1alpha1
kind: SelfNodeRemediationTemplate
name: ${SNR_TEMPLATE}
namespace: ${SNR_NS}
EOF

echo "NodeHealthCheck 'nhc-worker-default' created."
echo ""
echo " Watches workers for Ready=False/Unknown for 30s, then triggers SNR."
echo " Production uses 300s; 30s is for faster dev testing."
Loading