Hands-on: Tilt files, External Secrets Operator examples and a deliberately broken microservices app in sharifndlovu123/devops-directive-kubernetes-course. Resources are covered in Kubernetes Resource Types.
Kubernetes is only pleasant if the daily loop is fast, secrets are handled safely and you know how to find faults. This note covers all three.
1. The Inner Development Loop
Default flow: edit code, rebuild the image, push, redeploy, wait. Too slow. Tools that shorten it:
| Tool | Idea |
|---|---|
| Tilt | A Tiltfile watches your code; each change rebuilds and live-updates the service in the cluster, with a web dashboard |
| Skaffold | Build, push and deploy on every change; supports profiles for dev and CI |
| Telepresence | Run one service on your laptop while it behaves as if it were inside the cluster network |
| mirrord | Run local code with the environment, traffic and files of a real pod |
| kind / minikube / k3d | A local cluster that resembles production (2. Local Kubernetes Tools) |
# Tiltfile (fragment)
docker_build('api', './api', live_update=[sync('./api/src', '/app/src')])
k8s_yaml('k8s/api.yaml')
k8s_resource('api', port_forwards=8080)
Tips: keep images small and layers cache-friendly (3. Container Images and Build Tools), use hot-reload tools (air for Go, nodemon for Node) inside the dev image, and keep the dev environment as close to production as you can.
2. Secrets Management
Configuration belongs in Git. Secrets cannot, so the question is how they reach the cluster.
| Approach | How it works | Verdict |
|---|---|---|
Create by hand (kubectl create secret) |
Out-of-band, undocumented | Fine for a first try; unmanageable and unrepeatable |
| External Secrets Operator | Mirrors secrets from a cloud store (AWS Secrets Manager, Google Secret Manager, Azure Key Vault, 1Password, Vault) into Kubernetes Secrets, kept in sync | Good: the source of truth stays in the secret manager |
| Sealed Secrets | Encrypt with a cluster key; commit the encrypted object; the controller decrypts in-cluster | Good for GitOps |
| SOPS | Encrypt values inside YAML files using cloud KMS or age keys; decrypt at apply time | Good; pairs with Flux and Helm |
| Skip Kubernetes Secrets | App reads from Vault, a cloud secret manager or Doppler directly using workload identity | Strong isolation, more app code |
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db-creds }
spec:
refreshInterval: 1h
secretStoreRef: { kind: ClusterSecretStore, name: cloud-secrets }
target: { name: db-creds }
data:
- secretKey: password
remoteRef: { key: prod/app/db-password }
Rules that always hold:
- Encrypt Secrets at rest in etcd and restrict who can get secrets (RBAC)
- Use workload identity (IAM roles for service accounts, Azure workload identity, GKE Workload Identity) so pods authenticate without stored keys
- Rotate secrets; one secret per purpose and environment
- More general advice: 5. Secure Coding and Supply Chain
3. Debugging Kubernetes
Real clusters misbehave in repeatable ways. Practise on a broken app (the course uses a microservices demo with intentional faults) before an outage forces you to.
The investigation flow
flowchart TD
A[Something is wrong] --> B{Pod exists?}
B -- no --> C[Check Deployment / ReplicaSet events]
B -- yes --> D{Pod status}
D -- Pending --> E[Scheduling: resources, taints, PVC, quota]
D -- "ImagePullBackOff" --> F[Image name, tag, registry credentials]
D -- CrashLoopBackOff --> G[kubectl logs --previous; config, secrets, probes]
D -- Running but not Ready --> H[Readiness probe failing]
D -- "Running and Ready" --> I{Reachable?}
I -- no --> J[Service selector and endpoints, port, NetworkPolicy, Ingress]
I -- yes --> K[Application logic: logs, metrics, traces]
Commands in order
$ kubectl get pods -A # what is not Running?
$ kubectl describe pod <name> # events at the bottom explain most problems
$ kubectl logs <pod> [-c container] --previous # logs, including the crashed container
$ kubectl get events --sort-by=.lastTimestamp
$ kubectl get endpoints <service> # empty means the selector matches no ready pods
$ kubectl exec -it <pod> -- sh # look from the inside
$ kubectl port-forward svc/<service> 8080:80 # test a Service from your laptop
$ kubectl debug -it <pod> --image=busybox --target=<container> # ephemeral debug container
$ kubectl top pods # CPU and memory (needs metrics-server)
Common faults and where they hide
| Symptom | Usual cause |
|---|---|
Pending |
Not enough CPU or memory, unbound PVC, node selector or taint mismatch |
ImagePullBackOff |
Wrong tag, private registry without an imagePullSecret |
CrashLoopBackOff |
App exits on start: missing env var or secret, bad config, failing dependency |
OOMKilled |
Memory limit too low or a leak |
| Service has no endpoints | Label selector does not match the pod labels, or pods not Ready |
503 from Ingress |
Backend not Ready, wrong port, wrong targetPort |
| DNS failures | CoreDNS problem, wrong namespace in the name |
| Works in one namespace only | Missing ConfigMap or Secret in that namespace, RBAC |
| Slow or flapping | Probes too aggressive, CPU throttling from low limits |
Reading order that works: events, then logs, then configuration, then network. The Learnk8s "troubleshooting deployments" flow chart is a good reference to keep nearby.
Tools That Help
| Tool | Use |
|---|---|
| k9s | Terminal UI for navigating and debugging a cluster |
| stern | Tail logs from many pods at once |
| kubectx / kubens | Switch cluster and namespace quickly |
| Lens / Headlamp | Graphical cluster browsers |
| Prometheus + Grafana, OpenTelemetry | Metrics and traces for production |
Cheat sheet of commands: 3. kubectl Cheat Sheet.