Proactive protection for voluntary node drains: scale opted-in Deployments so spare pods are Ready before eviction, then let drain proceed and scale back.
A validating webhook on pods/eviction denies eviction of opted-in pods until spare capacity is Ready (one at-risk pod at a time). Node signals (Karpenter, cordon, …) only start scale-up earlier. eviction-guard.io/protected on the pod template is opt-in, not a lock.
| What it does | Product model and non-goals |
| Install | Helm / Kustomize |
| Configure | Policies, workloads, signals |
| How-to | Simulate disruption, gating, troubleshoot |
| Metrics | Prometheus gauges / counters |
| KEDA | Patch ScaledObject or external + metrics |
| GitOps | Argo CD / Flux ignore capacity during windows |
| All docs | Index |
helm install eviction-guard oci://ghcr.io/whitemug/charts/eviction-guard \
--version 0.2.3 \
--namespace eviction-guard-system --create-namespace
kubectl apply -f examples/policy-spot.yaml
kubectl apply -f examples/workload.yamlFrom a clone: helm install eviction-guard charts/eviction-guard -n eviction-guard-system --create-namespace.
Minimal workload opt-in (no scaler — patch Deployment replicas):
metadata:
annotations:
eviction-guard.io/scale-backend: deployment
spec:
template:
metadata:
labels:
eviction-guard.io/protected: "true"When an HPA owns capacity, prefer scale-backend: hpa (examples/workload-hpa.yaml). If you still bind Deployment alongside a scaler, set skipDownscaling: true on that catalog entry so Eviction Guard does not yank replicas on close.
Minimal policy (Spot capacity filter; built-in signals include Karpenter / cordon — add SpotInterrupted when you want Spot interruption coverage, see examples/policy-spot.yaml):
apiVersion: eviction-guard.io/v1alpha1
kind: EvictionGuardPolicy
metadata:
name: spot-workers
spec:
nodeFilter:
capacityTypes: ["spot"]
spareReplicas: 1
maxBuffer: 4
scaleBackAfter: 1m
backends:
deployment:
apiVersion: apps/v1
kind: Deployment
patches:
- path: spec.replicas
hpa:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
patches:
- path: spec.minReplicasMore examples in examples/. Signal recipes: docs/signals.md.
Node signal / cordon
│
▼
EvictionGuardPolicy (nodeFilter) → open/patch EvictionGuardWindow → scale spare
│
▼
pods/eviction webhook: deny until SpareReady → allow one at-risk pod
│
▼
scale back → Cooling → close
| Metric | Meaning |
|---|---|
evg_matched_nodes{policy} |
Nodes passing nodeFilter |
evg_vulnerable_nodes{policy} |
Filtered nodes with a disruption signal |
evg_at_risk_pods{policy,namespace,workload} |
Protected pods on vulnerable nodes |
evg_desired_replicas{policy,namespace,workload} |
Capacity target for the workload |
evg_current_spare{policy,namespace,workload} |
Extra replicas currently open |
evg_scale_actions_total{policy,direction,backend,result} |
Scale-up / scale-back (backend = catalog key) |
evg_spare_not_ready{policy,namespace,workload} |
1 while spare not Ready off dying nodes |
evg_capacity_apply_error{policy,namespace,workload} |
1 when the last capacity patch for a window failed |
evg_deferred_workloads{policy} |
Waiting for a window slot |
evg_max_window_exceeded_total{...} |
Force-cooled by maxWindow |
evg_eviction_decisions_total{decision} |
Webhook allow/deny |
Full reference: docs/metrics.md.
| Layer | What you do |
|---|---|
| CRDs | Apply EvictionGuardPolicy; watch EvictionGuardWindow |
| Workload | protected label + required scale-backend annotation |
| Go module | plugin.RegisterSignal (capacity via Policy catalog; node coverage via nodeFilter) |
See docs/extension.md. Building from source needs Go 1.27.1 (go.mod).
v1alpha1 (0.2.3). Migration: UPGRADING.md. Design: docs/design.md.
MIT — see LICENSE.