Visitar URL original
GitHub - somaz94/kube-drift: Kubernetes operator that continuously detects configuration drift between desired manifests and live cluster state · GitHub
Skip to content

About

Kubernetes operator that continuously detects configuration drift between desired manifests and live cluster state

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

kube-drift

Kubernetes operator that continuously detects configuration drift between your desired-state manifests and the live cluster, on a schedule. The in-cluster, GitOps-grade counterpart to the kube-diff CLI.

A DriftCheck reports no drift, someone scales a Deployment and deletes a ConfigMap by hand, and the next scheduled check reports one drifted and one missing resource

Top Language Version License Go Report Card GitHub Stars

Status: early development (v0.5.1 — WIP). The DriftCheck CRD, the operator, and drift detection for ConfigMap, Git, Helm, and Kustomize sources are implemented, along with Prometheus metrics and Slack/webhook notifications: the controller loads (and, for Helm/Kustomize, renders) the desired manifests, compares them against the live cluster via the kube-diff engine, and records the result into status. Git sources can now clone private repositories via source.git.auth (Basic/Bearer/SSH), and broader read access for comparing arbitrary kinds is available opt-in via the chart's rbac.viewRole / rbac.extraRules knobs. See Roadmap before depending on this.


Overview

kube-drift runs the same manifest-vs-cluster comparison that the kube-diff CLI performs on demand, but does it continuously and in-cluster as a Kubernetes operator. You declare a DriftCheck resource that points at a set of desired-state manifests (from a ConfigMap or a Git repository) and, on a configurable interval, the controller compares them against the live objects in the cluster and reports what has drifted — resources that changed, that are missing, or that exist but were never declared.

Where kube-diff answers "does the cluster match this directory of YAML right now?" from a laptop or CI job, kube-drift answers "has the cluster drifted from its declared state since I last looked?" and keeps that answer fresh in the resource's status, making it a natural fit for GitOps and audit workflows.


Features

DriftCheck CRD ConfigMap Source Git Source Helm Source Kustomize Source Scheduled Reconcile Status Reporting Webhook Alerts Kubebuilder

  • DriftCheck CRD (drift.somaz.io/v1alpha1) — declarative drift checks, one per desired-state source
  • Pluggable sources — desired manifests come from a ConfigMap, a Git repository, or a Helm chart / Kustomize overlay rendered in-process (no helm/kustomize binary, no shell-out)
  • Scoped comparison — compare only the desired manifests matching target.namespaces and target.labelSelector
  • Scheduled re-evaluation — configurable interval (default 5m) for continuous drift detection
  • Structured status — per-resource drift entries plus a rolled-up summary (changed / new / deleted / unchanged), lastCheckedAt, observedGeneration, and standard conditions
  • Webhook notifications — Slack or generic-JSON webhooks fire when the drift state changes (detected or resolved), deduplicated so they don't repeat on every re-check; the URL can come from a Secret
  • Opt-in broader read RBAC — the controller's own role reads only configmaps and secrets; the Helm chart adds rbac.viewRole.enabled (bind the built-in view ClusterRole) and rbac.extraRules (a custom read-only ClusterRole) knobs for comparing arbitrary kinds — both off by default
  • Shared engine — reuses the comparison engine extracted from kube-diff, so CLI and operator produce consistent results

Source-backend maturity: ConfigMap, Git, Helm, and Kustomize sources are implemented. Helm/Kustomize are rendered in-process from a Git checkout. Git clones anonymously by default, and can authenticate to private repositories via source.git.auth (Basic / Bearer / SSH) when set. Helm charts are expected to be self-contained (dependencies vendored under charts/); charts with external dependencies can fetch them at render time with source.helm.dependencyBuild: true — see the Usage Guide.


How It Works

Each DriftCheck drives the following loop:

  1. Load desired state — fetch plain-YAML manifests from the configured source: ConfigMap key(s), or a Git repository cloned at ref with manifests read from path. Only manifests matching target.namespaces / target.labelSelector are kept.
  2. Read live state — fetch each desired object from the live cluster by group/kind/namespace/name.
  3. Compare — hand both sides to the kube-diff engine (engine.Run(...)), producing a []*diff.Result.
  4. Map to status — classify each result as changed, new, deleted, or unchanged, write the drifted entries + summary into .status, and stamp lastCheckedAt.
  5. Requeue — re-run after spec.interval.

Installation


Prerequisites

  • Kubernetes v1.25+ (the CRD ships CEL validation rules, which require x-kubernetes-validations)
  • kubectl configured against the target cluster
  • Helm 3 (for the chart install path)

Quick Install (kubectl apply)

A consolidated manifest — CRD, RBAC, and controller Deployment — is published at dist/install.yaml:

kubectl apply -f https://raw.githubusercontent.com/somaz94/kube-drift/main/dist/install.yaml

Install with Kustomize

make install    # Install the DriftCheck CRD into the cluster
make deploy     # Deploy the controller (config/default overlay)

Install with Helm

The chart lives at helm/kube-drift/:

helm install kube-drift ./helm/kube-drift \
  --namespace kube-drift-system --create-namespace

Uninstall

Use the counterpart of whichever install path you took:

# kubectl
kubectl delete -f https://raw.githubusercontent.com/somaz94/kube-drift/main/dist/install.yaml

# Kustomize
make undeploy
make uninstall

# Helm
helm uninstall kube-drift --namespace kube-drift-system

Usage

Full walkthrough — install, every source type, reading results, metrics, RBAC, and troubleshooting — in the Usage Guide.

Create a DriftCheck that compares a set of desired manifests stored in a ConfigMap against the default namespace, re-checking every 5 minutes (config/samples/driftcheck_v1alpha1_sample.yaml):

apiVersion: drift.somaz.io/v1alpha1
kind: DriftCheck
metadata:
  name: driftcheck-sample
  namespace: default
spec:
  # Desired-state manifests to compare against the live cluster.
  source:
    type: ConfigMap
    configMap:
      name: desired-manifests
      # namespace: default    # defaults to the DriftCheck's namespace
      # key: manifests.yaml    # omit to concatenate every key as a YAML stream
  # Optionally compare only the desired manifests in these namespaces.
  target:
    namespaces:
      - default
  # How often to re-evaluate drift.
  interval: 5m
kubectl apply -f config/samples/driftcheck_v1alpha1_sample.yaml
kubectl get driftchecks

Or point at a Git repository instead of a ConfigMap — cloned at ref, with plain YAML read from path:

spec:
  source:
    type: Git
    git:
      url: https://github.com/somaz94/kube-drift.git   # anonymous by default; private repos via auth (see USAGE)
      ref: main                                        # branch, tag, or commit; omit for the default branch
      path: config/samples                             # sub-directory holding the manifests; omit for the root
  interval: 10m

Spec Reference

Field Type Description
source.type enum Git, ConfigMap, Helm, or Kustomize — where desired manifests come from
source.git.url string Clone URL of the repository (required when type: Git)
source.git.ref string Branch, tag, or commit; defaults to the repo's default branch
source.git.path string Directory within the repo holding the manifests; defaults to root
source.git.auth.type enum Basic, Bearer, or SSH — omit for an anonymous clone (see Usage Guide)
source.git.auth.secretRef.name string Secret (in the DriftCheck's namespace) holding the credentials for auth.type
source.configMap.name string ConfigMap name (required when type: ConfigMap)
source.configMap.namespace string ConfigMap namespace; defaults to the DriftCheck's namespace
source.configMap.key string Single data key; omit to concatenate every key as a YAML stream
source.helm.git object Chart location (same fields as source.git); path is the chart directory. Required when type: Helm
source.helm.releaseName string .Release.Name used for rendering; defaults to the DriftCheck's name
source.helm.namespace string .Release.Namespace used for rendering; defaults to the DriftCheck's namespace
source.helm.valuesFiles list Values files relative to the chart directory, merged in order
source.helm.values object Inline values, applied after valuesFiles
source.helm.dependencyBuild bool Fetch unvendored chart dependencies at render time (default false)
source.kustomize.git object Overlay location (same fields as source.git); path holds kustomization.yaml. Required when type: Kustomize
target.namespaces list Compare only desired manifests in these namespaces; manifests without a namespace are skipped
target.labelSelector selector Compare only desired manifests whose labels match
interval duration How often to re-evaluate drift (default 5m)
notify.webhooks[].type enum Slack or Generic (default Generic)
notify.webhooks[].url string Webhook endpoint
notify.webhooks[].urlSecretRef object name / key of a Secret entry holding the URL; takes precedence over url

Status Reference

Field Type Description
lastCheckedAt time When the drift check last completed
driftedResources[] list Resources whose live state differs from desired (apiVersion, kind, name, namespace, status)
summary object Tally across all compared resources: changed, new, deleted, unchanged
observedGeneration int .metadata.generation last reconciled
lastNotifiedHash string Fingerprint of the drift set last sent to webhooks; suppresses repeat notifications
conditions list Standard condition array for the latest observations

Per-resource status values: unchanged, changed, new, deleted.


Roadmap

  • DriftCheck CRD (drift.somaz.io/v1alpha1) — spec, status, printer columns
  • Operator skeleton — manager, RBAC, Kustomize overlays, Helm chart, CI/CD
  • Reconcile loop wired to the kube-diff engine (engine.Run) — drift recorded into status
  • ConfigMap source backend
  • Git source backend — clone a repo at ref, load plain YAML from path (anonymous clone; go-git, no shell-out)
  • Helm / Kustomize source backends — render a chart / build an overlay from a Git checkout in-process (Helm SDK + Kustomize API, no shell-out)
  • Webhook notifications — Slack / generic-JSON, deduplicated on drift-state change, URL sourced from a Secret
  • End-to-end test suite + test-e2e.yml on push/PR via kind-e2e-test-action
  • Git credential support for private repositories — source.git.auth (Basic / Bearer / SSH), also on source.helm.git.auth and source.kustomize.git.auth
  • Read-RBAC story for comparing arbitrary resource kinds — chart knobs rbac.viewRole.enabled (bind the built-in view ClusterRole) and rbac.extraRules (custom read-only ClusterRole), off by default
  • Opt-in Helm chart dependency build — source.helm.dependencyBuild fetches declared-but-unvendored dependencies at render time (default off; vendored charts/ remains the reproducible default)
  • Scoped comparison — spec.target.namespaces / spec.target.labelSelector filter the desired manifests before comparison
  • Honest fetch errors — missing read RBAC surfaces as a FetchError condition instead of false new drift, and kinds resolve through API discovery (irregular plurals, CRDs installed after startup)

Architecture

kube-drift deliberately does not reimplement manifest comparison. The comparison engine was extracted into reusable packages in kube-diff, and this operator consumes them directly:

  • github.com/somaz94/kube-diff/pkg/engine — the top-level Compare(...) entry point
  • github.com/somaz94/kube-diff/pkg/diff — the Result type and diff classification
  • github.com/somaz94/kube-diff/pkg/source — loading desired-state manifests
  • github.com/somaz94/kube-diff/pkg/cluster — reading live cluster objects

This keeps the CLI and the operator behaviorally consistent: the same comparison that kube-diff prints to a terminal is the one kube-drift records into DriftCheck status.


Project Structure

.
├── cmd/
│   └── main.go                              # Controller-runtime manager entry point
├── api/
│   └── v1alpha1/
│       ├── types.go                         # DriftCheck spec/status definitions
│       ├── groupversion_info.go             # GroupVersion registration
│       └── zz_generated.deepcopy.go         # Generated DeepCopy methods
├── internal/
│   ├── controller/
│   │   ├── driftcheck_controller.go         # Reconciler — load source, run kube-diff engine, write status
│   │   └── driftcheck_controller_test.go
│   ├── source/                              # Git desired-state source (go-git clone → FileSource)
│   ├── notify/                              # Slack / generic webhook delivery
│   └── metrics/                             # kube_drift_resources drift gauge
├── config/
│   ├── crd/bases/                           # Generated CRD YAML
│   ├── default/                             # Main Kustomize overlay
│   ├── manager/                             # Deployment manifest
│   ├── rbac/                                # ClusterRole, bindings, service account
│   └── samples/
│       └── driftcheck_v1alpha1_sample.yaml  # Example DriftCheck
├── helm/
│   └── kube-drift/                          # Helm chart (CRDs synced from config/crd/bases/)
├── hack/                                    # boilerplate header, version bump
├── scripts/                                 # PR auto-generator
├── docs/
│   ├── USAGE.md                             # Install, source types, results, metrics, RBAC
│   └── DEVELOPMENT.md
├── Dockerfile                               # Multi-stage (golang → distroless:nonroot)
├── Makefile
├── PROJECT                                  # Kubebuilder v4 project metadata
├── go.mod
└── README.md

Development

Kubebuilder v4 project. Common targets:

make manifests generate   # Regenerate CRD YAML, RBAC, and DeepCopy after editing api/v1alpha1/types.go
make build                # Build the manager binary → ./bin/manager
make test                 # Run unit tests with envtest
make lint                 # Run golangci-lint
make docker-build         # Build the container image
make install / uninstall  # Install / remove the CRD in the cluster
make deploy / undeploy    # Deploy / remove the controller
make help                 # List all targets

After changing api/v1alpha1/types.go, always run make manifests generate and keep helm/kube-drift/crds/ in sync with config/crd/bases/.

The end-to-end workflow (test-e2e.yml) runs the kind-based e2e suite on push / PR / manual dispatch via kind-e2e-test-action: it builds the image, loads it into a kind cluster, deploys the controller, and asserts a DriftCheck detects drift and exposes the kube_drift_resources metric.


Conventions

  • Commits: Conventional Commits (feat:, fix:, docs:, refactor:, test:, ci:, chore:)
  • Documentation & code comments: English only
  • CRD changes: always run make manifests generate after editing api/v1alpha1/types.go
  • Branches: feat/name, fix/name

Related Projects

  • kube-diff: Diff local manifests (plain YAML, Helm, Kustomize) against the live cluster.
  • kube-diff-action: Run kube-diff in GitHub Actions and post the drift as a pull request comment.
  • helios-lb: A bare-metal LoadBalancer for Kubernetes, a lightweight MetalLB alternative.
  • network-policy-generator: An operator that learns pod traffic and generates NetworkPolicies from it.
  • k8s-namespace-sync: An operator that syncs Secrets and ConfigMaps across namespaces.

License

See LICENSE — Apache 2.0.

About

Kubernetes operator that continuously detects configuration drift between desired manifests and live cluster state

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages