Development¶
Prerequisites¶
| Tool | Version | Purpose |
|---|---|---|
| Go | ≥ 1.26 | Build and test |
| Docker | any | Build container image |
| kubectl | any | Cluster interaction |
| helm | ≥ 3.10 | Chart development |
| minikube / kind / k3d | any | Local cluster |
Clone and build¶
Run tests¶
Project structure¶
k8s-sustain/
├── api/v1alpha1/ # CRD Go types and deepcopy
│ ├── policy_types.go
│ └── zz_generated.deepcopy.go
├── cmd/
│ ├── controller/ # Root cobra command + start subcommand
│ ├── webhook/ # webhook subcommand
│ └── dashboard/ # dashboard subcommand
├── internal/
│ ├── config/ # Centralized Viper config (flags, env, file)
│ ├── controller/ # Policy reconciler
│ ├── dashboard/ # Dashboard HTTP server
│ ├── logging/ # Shared zap logger setup
│ ├── prometheus/ # Prometheus HTTP API client
│ ├── recommender/ # Resource recommendation logic (pure functions)
│ ├── webhook/ # Admission webhook HTTP handler
│ └── workload/ # Pod recycler for Deployment/StatefulSet/DaemonSet
├── charts/k8s-sustain/ # Helm chart
├── docs/ # This documentation
├── Dockerfile
├── Makefile
└── main.go
Running locally against a cluster¶
Start the controller¶
# Point KUBECONFIG at your cluster
export KUBECONFIG=~/.kube/config
# Forward Prometheus if needed
kubectl port-forward -n k8s-sustain svc/k8s-sustain-prometheus-server 9090:80 &
go run main.go start \
--prometheus-address=http://localhost:9090 \ # port-forwarded from the cluster
--reconcile-interval=1m \
--zap-log-level=debug
Start the webhook (requires TLS)¶
The webhook must be reachable from the API server, which makes local development more involved. Use telepresence or develop against a local kind cluster with a self-signed cert.
Deploying on kind¶
A full local deployment with Prometheus, the controller, webhook, and dashboard:
1. Create a kind cluster¶
2. Build and load the image¶
3. Install with Helm¶
helm install k8s-sustain ./charts/k8s-sustain \
--set image.repository=k8s-sustain \
--set image.tag=dev \
--set image.pullPolicy=Never \
--set dashboard.enabled=true
image.pullPolicy=Never ensures Kubernetes uses the locally loaded image. The prometheusAddress is auto-resolved to the bundled prometheus subchart service.
4. Verify pods are running¶
5. Access the dashboard¶
Open http://localhost:8090.
6. Create a test policy¶
kubectl apply -f - <<'EOF'
apiVersion: k8s.sustain.io/v1alpha1
kind: Policy
metadata:
name: test-policy
spec:
selector:
namespaces: [default]
update:
types:
deployment: Ongoing
rightSizing:
resourcesConfigs:
cpu:
window: 168h
requests:
percentile: 95
memory:
window: 168h
requests:
percentile: 95
EOF
Rebuilding after changes¶
After modifying code, rebuild and reload:
make docker-build IMG=k8s-sustain:dev
kind load docker-image k8s-sustain:dev --name k8s-sustain
kubectl rollout restart deployment k8s-sustain
kubectl rollout restart deployment k8s-sustain-dashboard
Cleanup¶
Regenerating code¶
If you modify types in api/v1alpha1/, regenerate the deepcopy methods:
This requires controller-gen to be installed:
Building the container image¶
The Dockerfile uses a two-stage build: golang:1.26-alpine → gcr.io/distroless/static:nonroot.
Makefile targets¶
| Target | Description |
|---|---|
make build |
Build the binary |
make test |
Run unit tests |
make generate |
Regenerate deepcopy code |
make docker-build |
Build the container image |
make helm-lint |
Lint the Helm chart |
make helm-template |
Render templates to stdout |
Adding a new workload kind¶
To support a new workload kind (e.g. Rollout from Argo):
- Add
ArgoRollout *UpdateModetoUpdateTypesinapi/v1alpha1/policy_types.go(already present as a placeholder) - Add the deepcopy block to
zz_generated.deepcopy.go - Add
RecycleRolloutPodstointernal/workload/patcher.go - Add
reconcileRolloutstointernal/controller/policy_controller.go - Add the case to
modeForKindandresolveOwnerininternal/webhook/handler.go - Add RBAC markers (
+kubebuilder:rbac:...) to the controller - Add the Helm RBAC rule in
charts/k8s-sustain/templates/rbac.yaml
Contributing¶
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Commit with a clear message
- Open a pull request against
main
Please ensure go build ./... and go test ./... pass before opening a PR.