A Kubernetes operator for switching cluster resource profiles — on demand, on a schedule, or for a fixed duration.
Running Kubernetes environments at full production capacity 24/7 is expensive. Most staging and development environments sit idle overnight, on weekends, and between test cycles — yet they keep consuming CPU, memory, and cloud budget.
kmorph solves this by letting you define named resource profiles and switch between them instantly. One command puts the entire environment to sleep. Another wakes it to production specs for a client demo. A cron schedule does it automatically every night.
priority wins
┌─────────────────────────────────────────────────┐
│ ProfileActivation: sleep-nights priority=20 │ ← Active 18:00-09:00
│ ProfileActivation: business-hours priority=10 │ ← Active 09:00-18:00
│ ProfileActivation: client-demo priority=100 │ ← Temporary override, expires in 4h
│ ProfileActivation: fallback priority=0 │ ← Always-on safety net
└─────────────────────────────────────────────────┘
↓ highest priority wins
ClusterProfile applied to cluster resources
- Priority-based activation — multiple activations coexist; the highest priority wins automatically
- Cron schedules with timezone support —
start/endexpressions, any IANA timezone - Time-limited activations —
duration: "4h"auto-expires and fails over to next priority - Suspend/resume — pause an activation without deleting it
- Drift enforcement —
strictmode continuously re-applies patches; manual changes are reverted within 30s - Audit and soft modes — preview what would change or detect drift without enforcing
- Any Kubernetes resource — Deployment, StatefulSet, DaemonSet, CronJob, ConfigMap, and more
- Custom CRDs — Argo Rollouts, KEDA ScaledObject, or any CRD (auto-resolves known versions)
- Three patch types —
strategic(native k8s),merge(RFC 7396),json(RFC 6902 — surgical, preservesimage) - Multi-namespace profiles — one profile controls resources across multiple namespaces
- Full canary lifecycle management — pause before patch → apply →
promoteFull→ watch → cleanup - Race condition prevention — Rollout is paused before template changes so Analysis Runs never start
- Abort on failure — reverts to
previousStableRSif Rollout goes Degraded - JSON patch for resources — patches
containers[0].resourceswithout touchingimage
- Dry-run mode —
spec.dryRun: truepreviews changes server-side without applying anything - Kubernetes Events — Activated, Preempted, Expired, DriftDetected, RolloutPromoted, RolloutFailed
- Prometheus metrics — active/pending activations, patch rates, rollout durations, drift events
- Google Managed Prometheus — optional
PodMonitoringCRD for GKE clusters - Validating webhooks — reject invalid resources at admission time (cert-manager integration)
- Finalizers — safe cleanup of Rollout annotations on activation deletion
- Immediate reconcile — ClusterProfile changes trigger instant reconciliation, no 30s wait
Put your entire staging environment to sleep at 18:00 and wake it at 09:00 on weekdays. Saves ~65% on cloud costs.
apiVersion: config.kmorph.io/v1alpha1
kind: ClusterProfile
metadata:
name: sleep
spec:
driftPolicy: strict
patches:
- target:
namespace: staging
kind: Deployment
labelSelector: { tier: app }
patch:
spec:
replicas: 0
- target:
namespace: staging
kind: CronJob
labelSelector: { tier: batch }
patchType: merge
patch:
spec:
suspend: true
---
apiVersion: config.kmorph.io/v1alpha1
kind: ProfileActivation
metadata:
name: sleep-nights
spec:
profileRef: sleep
priority: 20
schedule:
start: "0 18 * * 1-5"
end: "0 9 * * 1-5"
timezone: "Asia/Tokyo"Scale up to production replica counts and resources for a time-limited window. Automatically reverts when the timer expires.
apiVersion: config.kmorph.io/v1alpha1
kind: ProfileActivation
metadata:
name: client-demo
spec:
profileRef: production # full replica counts, bigger instances
priority: 100 # overrides sleep schedule
duration: "4h" # auto-expires, fallback takes over# When demo is over — delete to revert immediately
kubectl delete pa client-demoChange replicas and CPU/memory on a canary-enabled Rollout without running smoke tests or analysis. kmorph pauses the Rollout, patches it, then calls promoteFull.
patches:
# Merge patch: replicas + node class
- target:
namespace: prod
kind: Rollout
group: argoproj.io
name: api-service
patchType: merge
rolloutPolicy:
skipSteps: true
abortOnFailure: true
patch:
spec:
replicas: 3
template:
spec:
nodeSelector:
cloud.google.com/compute-class: Scale-Out
# JSON patch: resources only (image untouched)
- target:
namespace: prod
kind: Rollout
group: argoproj.io
name: api-service
patchType: json
patch:
- { op: replace, path: /spec/template/spec/containers/0/resources/requests/cpu, value: "2" }
- { op: replace, path: /spec/template/spec/containers/0/resources/requests/memory, value: "8Gi" }Validate what a production profile would change in your staging cluster before activating it. No real changes made.
apiVersion: config.kmorph.io/v1alpha1
kind: ProfileActivation
metadata:
name: preview-production
spec:
profileRef: production
dryRun: true # server-side dry-run, never applieskubectl get pa preview-production -o jsonpath='{.status.dryRunResult}' | jq .
# {
# "summary": "5 of 12 resource(s) would change",
# "changes": [
# { "name": "api", "changed": true,
# "diff": "~ spec.replicas: 1 → 3\n~ containers[0].resources.requests.cpu: 500m → 2" }
# ]
# }Override everything with highest priority — bypasses all schedules, demos, and fallbacks.
kubectl apply -f - <<EOF
apiVersion: config.kmorph.io/v1alpha1
kind: ProfileActivation
metadata:
name: emergency-shutdown
spec:
profileRef: sleep
priority: 999
EOF
# Undo: delete the activation
kubectl delete pa emergency-shutdownhelm upgrade --install kmorph charts/kmorph \
--namespace kmorph-system \
--create-namespace \
--set image.tag=0.5.6# 1. Define profiles
apiVersion: config.kmorph.io/v1alpha1
kind: ClusterProfile
metadata:
name: minimal
spec:
driftPolicy: strict
patches:
- target:
namespace: my-app
kind: Deployment
labelSelector: { tier: backend }
patch:
spec:
replicas: 1
template:
spec:
containers:
- name: app
resources:
requests: { cpu: 50m, memory: 64Mi }
---
# 2. Activate it
apiVersion: config.kmorph.io/v1alpha1
kind: ProfileActivation
metadata:
name: fallback
spec:
profileRef: minimal
priority: 0kubectl get cp # ClusterProfiles
kubectl get pa # ProfileActivations| Guide | Description |
|---|---|
| Getting Started | Installation, first profile, basic usage |
| Profiles | ClusterProfile spec, patch types, examples |
| Activations | Priority, schedules, duration, suspend, dry-run |
| Argo Rollouts | Full Rollout support with canary skip |
| Architecture | How kmorph works internally |
| Webhooks | Validating webhooks with cert-manager |
| Observability | Metrics, GMP, k9s integration |
| Helm Values | Full values reference |
See CONTRIBUTING.md · Security Policy · Code of Conduct
Apache 2.0