Kubernetes Install Path
Ravn watches Kubernetes clusters with the same detection-only philosophy as standalone hosts β no sidecars, no mutation, no cluster-admin. This page covers everything from a fresh k3d cluster to events flowing in the portal.
What gets deployed
| Component | Kind | Purpose |
|---|---|---|
ravn-controller |
Deployment (1 replica) | Watches cluster-wide Events API; maps CrashLoopBackOff, OOMKilled, ImagePullBackOff, FailedScheduling, probe Unhealthy, evictions to typed KubeWorkload signals |
ravn-node-agent |
DaemonSet (1 pod/node) | Watches each nodeβs .status.conditions; emits KubeNode signals on memory/disk/PID pressure and NodeNotReady |
| RBAC | ClusterRole + ClusterRoleBinding | Read-only (get/list/watch) on events, pods, namespaces (controller) and nodes (node-agent). No write verbs |
| ServiceAccounts | ServiceAccount Γ 2 | One per component with projected audience-bound tokens |
Both agents authenticate to the control plane with a projected Kubernetes
ServiceAccount token (audience ravn). The control plane validates the token
against the clusterβs OIDC JWKS, with a Kubernetes TokenReview fallback for
environments where the JWKS endpoint is not reachable externally.
Prerequisites
- Kubernetes 1.21+ (projected ServiceAccount token API stable from 1.21)
kubectlconfigured for your target clusterhelmv3.x (install via your package manager orbrew install helm)- A running Ravn control plane (see Standalone install or
docker-compose.yml) - The
ravn-k8scontainer image accessible to the cluster
Quick-start (k3d local cluster)
The fastest way to see the full flow is with the included k3d dev cluster.
The dev shell (direnv allow or devenv shell) provides k3d, kubectl,
k9s, and helm.
# 1. Start the dev cluster (writes a project-local kubeconfig at
# .devenv/state/kubeconfig β never touches your ~/.kube/config).
k3d-up
# 2. Apply the test workloads (healthy pod + crasher in CrashLoopBackOff).
kubectl apply -f k8s/test-workloads.yaml
# 3. Build and import the agent image.
nix build .#ravn-k8s-image
k3d image import ravn-k8s:latest -c ravn-dev
# 4. Install the chart.
helm install ravn deploy/helm/ravn \
--namespace ravn-system --create-namespace \
--set image.repository=ravn-k8s \
--set image.tag=latest \
--set controlPlane.ingestUrl=http://host.k3d.internal:8080/ingest \
--set cluster=ravn-dev
# 5. Confirm the pods are Running.
kubectl get pods -n ravn-system
# 6. Watch events from the crasher.
kubectl get events -n ravn-test --field-selector involvedObject.name=crasher
Within a few seconds the CrashLoopBackOff / BackOff events from the
crasher pod will appear in the portal at http://localhost:5173 as KubeWorkload
signals, severity Error.
Production install (Helm)
1. Add the image to your registry
# Build with Nix (reproducible, no Docker daemon required).
nix build .#ravn-k8s-image
skopeo copy oci-archive:result \
docker://ghcr.io/YOUR_ORG/ravn-k8s:0.2.0
Or use Docker:
docker build -t ghcr.io/YOUR_ORG/ravn-k8s:0.2.0 -f Dockerfile .
docker push ghcr.io/YOUR_ORG/ravn-k8s:0.2.0
2. Configure values
Create a values-prod.yaml (do not commit secrets into this file β use
--set or a Sealed Secret / External Secret Operator for anything sensitive):
# values-prod.yaml
image:
repository: ghcr.io/YOUR_ORG/ravn-k8s
tag: "0.2.0"
# Point at your control plane. Use an internal Service address if the
# control plane runs in the same cluster (mode: inCluster).
controlPlane:
mode: external # or: inCluster
ingestUrl: "https://ravn-cp.YOUR_DOMAIN/ingest"
tokenAudience: "ravn" # must match RAVN_INGEST_AUDIENCE on the server
cluster: "prod-eu"
logLevel: "info"
controller:
watchNamespace: "" # "" = all namespaces (recommended for production)
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 128Mi }
nodeAgent:
tolerations:
- operator: Exists # run on every node, including control-plane nodes
3. Install
helm install ravn deploy/helm/ravn \
--namespace ravn-system \
--create-namespace \
--values values-prod.yaml
Upgrade later:
helm upgrade ravn deploy/helm/ravn \
--namespace ravn-system \
--values values-prod.yaml
4. Verify
# Agents running?
kubectl get pods -n ravn-system
# Controller logging events?
kubectl logs -n ravn-system deploy/ravn-controller --tail=50
# Node agent running on every node?
kubectl get pods -n ravn-system -l app.kubernetes.io/component=node-agent -o wide
# Simulated fault: CrashLoopBackOff appears in portal within ~10 s.
kubectl apply -f k8s/test-workloads.yaml
kubectl get events -n ravn-test
Key values reference
| Value | Purpose | Default |
|---|---|---|
image.repository |
Agent image repository | ghcr.io/olafkfreund/ravn-k8s |
image.tag |
Image tag | 0.2.0 |
controlPlane.mode |
external or inCluster |
external |
controlPlane.ingestUrl |
Authenticated /ingest endpoint |
(required) |
controlPlane.tokenAudience |
Projected-token audience (must match server) | ravn |
controlPlane.tokenMount |
Mount path for the projected token | /var/run/secrets/ravn |
cluster |
Cluster identity recorded on every event | default |
logLevel |
RAVN_LOG filter (info, debug, warn) |
info |
controller.enabled |
Deploy the cluster-wide Events controller | true |
controller.watchNamespace |
Restrict the Events watch to one namespace | "" (all) |
controller.replicaCount |
Controller replicas (single-leader design) | 1 |
nodeAgent.enabled |
Deploy the per-node DaemonSet | true |
nodeAgent.tolerations |
DaemonSet tolerations | tolerate all taints |
executor.enabled |
Deploy the pod-healing executor (#146) | false |
namespace.create |
Have the chart manage the Namespace object | false |
imagePullSecrets |
Image pull secrets | [] |
Authentication: in-cluster vs external control plane
External control plane (default β design D2)
The control plane runs outside the monitored cluster. Agents authenticate
using a projected ServiceAccount token with audience ravn:
k8s cluster External network
βββββββββββββββββββββββ ββββββββββββββββββββ
β ravn-controller βββHTTPS/443ββΆβ ravn-server β
β (projected SA token)β β /ingest β
βββββββββββββββββββββββ ββββββββββββββββββββ
The server validates the token against the clusterβs OIDC JWKS endpoint:
# On the ravn-server side:
RAVN_INGEST_OIDC_ISSUER=https://kubernetes.default.svc # your cluster issuer
RAVN_INGEST_OIDC_JWKS_URL=https://CLUSTER_IP/openid/v1/jwks
RAVN_INGEST_AUDIENCE=ravn
If the JWKS endpoint is not reachable from the external control plane, enable the TokenReview fallback:
RAVN_INGEST_TOKENREVIEW_URL=https://CLUSTER_API_SERVER
RAVN_INGEST_TOKENREVIEW_TOKEN=<credential with system:auth-delegator>
RAVN_INGEST_AUDIENCE=ravn
In-cluster control plane
Set controlPlane.mode: inCluster and point controlPlane.ingestUrl at the
in-cluster Service address:
controlPlane:
mode: inCluster
ingestUrl: "http://ravn-server.ravn-system.svc:8080/ingest"
The server uses kubernetes.default.svc as the OIDC issuer automatically in
this mode (no JWKS URL required when running in-cluster).
Security posture
- Read-only RBAC β
get/list/watchonly. The controller touchesevents,pods,namespaces; the node-agent touchesnodes. No write verbs, no cluster-admin, nosecretsaccess. - Tight securityContext β
runAsNonRoot,readOnlyRootFilesystem,allowPrivilegeEscalation: false, all capabilities dropped, seccompRuntimeDefault. - No hostPath / no privileged access by default. The optional journald / container-stdout taps described in the design (#56) are intentionally absent; they would add a tightly-scoped read-only hostPath and are left for a future opt-in values flag.
- Token rotation β the projected ServiceAccount token has a 1-hour TTY and is re-read on every publish cycle, so kubelet rotation is picked up transparently.
- Audience binding β the projected token is scoped to audience
ravn; it cannot be presented to any other relying party.
Executor (supervised pod healing β future, issue #146)
The chart includes a pre-built, disabled slot for the ravn-executor component
that will land in issue #146. When enabled, it acts on pending remediation
proposals from the control plane by driving typed pod-healing operations
(eviction, force-delete, annotation updates) under the same Ed25519-signed
CommandEnvelope protocol the host actuator uses.
To activate once #146 ships:
- Create the signing-key Secret:
kubectl create secret generic ravn-executor-signing-key \ --from-file=signing.key=/path/to/verifying.key \ -n ravn-system - Enable in your values:
executor: enabled: true signingKeyFile: "/var/run/secrets/ravn-executor/signing.key" - Upgrade the release:
helm upgrade ravn deploy/helm/ravn --namespace ravn-system --values values-prod.yaml
Until #146 is merged, executor.enabled=false is the only safe value. The
RBAC and Deployment templates are validated by helm lint but produce no
Kubernetes objects while disabled.
Raw manifests (kubectl apply)
If you prefer to manage manifests without Helm:
# Edit RAVN_INGEST_URL in 20-controller.yaml and 30-node-agent.yaml first.
kubectl apply -f deploy/k8s/
The raw manifests in deploy/k8s/ are always kept in sync with the Helm chart
and validated by kubectl apply --dry-run=client in CI.
GitOps (ArgoCD / FluxCD)
Declarative templates for deploying Ravn via popular GitOps engines are provided under deploy/k8s/gitops/. These templates deploy the Helm chart directly from the source repository, keeping values versioned in your GitOps configuration.
ArgoCD Application
Deploy the pre-built ArgoCD Application manifest:
kubectl apply -f deploy/k8s/gitops/argocd-application.yaml
The manifest specifies createNamespace: true and overrides parameters like cluster and controlPlane.ingestUrl inside the helm.values block.
FluxCD HelmRelease
Deploy the FluxCD GitRepository and HelmRelease manifests:
kubectl apply -f deploy/k8s/gitops/flux-helmrelease.yaml
This configures a Flux source controller to track the main branch of ravn-agents and executes the Helm controller using the custom values block.
Troubleshooting
Agents not appearing in the portal:
- Check
kubectl logs -n ravn-system deploy/ravn-controllerfor401errors (OIDC misconfiguration) or connection errors (wrongingestUrl). - Confirm the token audience matches:
kubectl create token ravn-controller -n ravn-system --audience=ravnshould return a valid JWT.
No events from the crasher workload:
- Confirm the controller is running:
kubectl get pods -n ravn-system. - The controller filters
Normallifecycle events; onlyWarningevents and reasons ratedError+ surface. Check that the crasher is actually inCrashLoopBackOff:kubectl get pods -n ravn-test.
DaemonSet pod not scheduled on control-plane nodes:
- The default
tolerations: [{operator: Exists}]tolerates all taints. If you overrode tolerations, addeffect: NoSchedule, key: node-role.kubernetes.io/control-plane.
TokenReview 401 from control plane:
- Verify the credential used for
RAVN_INGEST_TOKENREVIEW_TOKENhassystem:auth-delegatorRBAC. Check with:kubectl auth can-i create tokenreviews --as=system:serviceaccount:ravn-system:ravn-controller.
See also
- Standalone install β NixOS module or Docker Compose for the control plane
- Air-gapped install β fully offline, no external image registry
- Architecture β the three planes explained
deploy/helm/ravn/values.yamlβ full annotated values referencedeploy/k8s/β raw Kubernetes manifests