CI/CD — Kubernetes
Kubernetes (K8s) is an open-source container orchestration platform that automates the deployment, scaling, and management of containerized applications. Originally developed by Google and now maintained by the CNCF, it has become the industry standard for running workloads in production.
This section covers the core Kubernetes concepts — pods, deployments, services, ingress, ConfigMaps, Secrets, Helm charts — and how to integrate Kubernetes into CI/CD pipelines for automated rollouts.
A Kubernetes cluster is divided into a control plane and worker nodes. The control plane manages the desired state of the cluster, while worker nodes run the actual application workloads.
| Component | Role |
|---|---|
| API Server | Frontend for the control plane; all communication goes through it |
| etcd | Distributed key-value store holding all cluster state |
| Scheduler | Assigns pods to nodes based on resource requirements and constraints |
| Controller Manager | Runs reconciliation loops to match desired state with actual state |
| Kubelet | Agent on each worker node that manages pod lifecycle |
| Kube-proxy | Handles network routing and load balancing for services |
info
A Pod is the smallest deployable unit in Kubernetes — one or more co-located containers that share network namespace and storage volumes. In practice, most pods run a single container.
| 1 | apiVersion: v1 |
| 2 | kind: Pod |
| 3 | metadata: |
| 4 | name: web-app |
| 5 | labels: |
| 6 | app: web |
| 7 | spec: |
| 8 | containers: |
| 9 | - name: nginx |
| 10 | image: nginx:1.25-alpine |
| 11 | ports: |
| 12 | - containerPort: 80 |
| 13 | resources: |
| 14 | requests: |
| 15 | memory: "64Mi" |
| 16 | cpu: "100m" |
| 17 | limits: |
| 18 | memory: "128Mi" |
| 19 | cpu: "250m" |
| 20 | livenessProbe: |
| 21 | httpGet: |
| 22 | path: /healthz |
| 23 | port: 80 |
| 24 | initialDelaySeconds: 10 |
| 25 | periodSeconds: 5 |
warning
A Deployment manages ReplicaSets and provides declarative updates for pods. When you update a Deployment, it performs a rolling update — gradually replacing old pods with new ones with zero downtime.
| 1 | apiVersion: apps/v1 |
| 2 | kind: Deployment |
| 3 | metadata: |
| 4 | name: web-app |
| 5 | labels: |
| 6 | app: web |
| 7 | spec: |
| 8 | replicas: 3 |
| 9 | selector: |
| 10 | matchLabels: |
| 11 | app: web |
| 12 | strategy: |
| 13 | type: RollingUpdate |
| 14 | rollingUpdate: |
| 15 | maxSurge: 1 |
| 16 | maxUnavailable: 0 |
| 17 | template: |
| 18 | metadata: |
| 19 | labels: |
| 20 | app: web |
| 21 | spec: |
| 22 | containers: |
| 23 | - name: app |
| 24 | image: ghcr.io/myorg/web-app:v1.2.0 |
| 25 | ports: |
| 26 | - containerPort: 3000 |
| 27 | env: |
| 28 | - name: NODE_ENV |
| 29 | value: "production" |
| 30 | readinessProbe: |
| 31 | httpGet: |
| 32 | path: /ready |
| 33 | port: 3000 |
| 34 | initialDelaySeconds: 5 |
| 35 | periodSeconds: 3 |
maxSurge: 1
During update, create at most 1 extra pod above the desired replica count.
maxUnavailable: 0
Never take more pods offline than needed — ensures full capacity during rollout.
Services provide stable networking for pods. Since pods are ephemeral (they can be created, destroyed, and rescheduled at any time), a Service gives them a fixed DNS name and IP address for internal and external communication.
| Service Type | Purpose |
|---|---|
| ClusterIP | Internal-only access within the cluster (default) |
| NodePort | Exposes service on a static port on every node (30000-32767) |
| LoadBalancer | Provisions a cloud load balancer for external traffic |
| 1 | apiVersion: v1 |
| 2 | kind: Service |
| 3 | metadata: |
| 4 | name: web-service |
| 5 | spec: |
| 6 | selector: |
| 7 | app: web |
| 8 | type: ClusterIP |
| 9 | ports: |
| 10 | - port: 80 |
| 11 | targetPort: 3000 |
| 12 | protocol: TCP |
Ingress exposes HTTP/HTTPS routes from outside the cluster to services inside. An Ingress Controller (like NGINX or Traefik) processes Ingress resources and configures the underlying load balancer accordingly.
| 1 | apiVersion: networking.k8s.io/v1 |
| 2 | kind: Ingress |
| 3 | metadata: |
| 4 | name: web-ingress |
| 5 | annotations: |
| 6 | cert-manager.io/cluster-issuer: letsencrypt-prod |
| 7 | nginx.ingress.kubernetes.io/rate-limit: "100" |
| 8 | spec: |
| 9 | ingressClassName: nginx |
| 10 | tls: |
| 11 | - hosts: |
| 12 | - app.example.com |
| 13 | secretName: app-tls |
| 14 | rules: |
| 15 | - host: app.example.com |
| 16 | http: |
| 17 | paths: |
| 18 | - path: / |
| 19 | pathType: Prefix |
| 20 | backend: |
| 21 | service: |
| 22 | name: web-service |
| 23 | port: |
| 24 | number: 80 |
best practice
ConfigMaps store non-sensitive configuration data. Secrets store sensitive data like passwords, tokens, and keys (base64-encoded, not encrypted by default — use external secrets management for production).
| 1 | apiVersion: v1 |
| 2 | kind: ConfigMap |
| 3 | metadata: |
| 4 | name: app-config |
| 5 | data: |
| 6 | DATABASE_HOST: "postgres.default.svc.cluster.local" |
| 7 | DATABASE_PORT: "5432" |
| 8 | LOG_LEVEL: "info" |
| 9 | --- |
| 10 | apiVersion: v1 |
| 11 | kind: Secret |
| 12 | metadata: |
| 13 | name: app-secrets |
| 14 | type: Opaque |
| 15 | data: |
| 16 | DATABASE_PASSWORD: cGFzc3dvcmQxMjM= # base64 encoded |
| 17 | API_KEY: c2VjcmV0LWFwaS1rZXk= |
| 1 | # Inject into a pod via env vars |
| 2 | containers: |
| 3 | - name: app |
| 4 | envFrom: |
| 5 | - configMapRef: |
| 6 | name: app-config |
| 7 | - secretRef: |
| 8 | name: app-secrets |
warning
Helm is the package manager for Kubernetes. A Helm chart is a collection of YAML templates that describe related Kubernetes resources. Helm allows you to install, upgrade, and rollback complex applications with a single command.
| 1 | # Add a chart repository |
| 2 | helm repo add bitnami https://charts.bitnami.com/bitnami |
| 3 | helm repo update |
| 4 | |
| 5 | # Search for charts |
| 6 | helm search repo nginx |
| 7 | |
| 8 | # Install a chart with custom values |
| 9 | helm install my-release bitnami/nginx \ |
| 10 | --set service.type=LoadBalancer \ |
| 11 | --set replicaCount=3 \ |
| 12 | --namespace production \ |
| 13 | --create-namespace |
| 14 | |
| 15 | # Upgrade a release |
| 16 | helm upgrade my-release bitnami/nginx \ |
| 17 | --set image.tag=1.27.0 \ |
| 18 | --namespace production |
| 19 | |
| 20 | # Rollback to previous version |
| 21 | helm rollback my-release 1 --namespace production |
| 22 | |
| 23 | # List all releases |
| 24 | helm list --all-namespaces |
| 1 | # Chart.yaml |
| 2 | apiVersion: v2 |
| 3 | name: web-app |
| 4 | description: A Helm chart for our web application |
| 5 | type: application |
| 6 | version: 0.1.0 |
| 7 | appVersion: "1.2.0" |
| 8 | |
| 9 | # values.yaml (defaults) |
| 10 | replicaCount: 2 |
| 11 | image: |
| 12 | repository: ghcr.io/myorg/web-app |
| 13 | tag: "latest" |
| 14 | pullPolicy: IfNotNotPresent |
| 15 | service: |
| 16 | type: ClusterIP |
| 17 | port: 80 |
| 18 | ingress: |
| 19 | enabled: true |
| 20 | host: app.example.com |
info
kubectl is the command-line tool for interacting with the Kubernetes API server. Master these commands for daily cluster operations.
| 1 | # Inspect cluster state |
| 2 | kubectl get nodes |
| 3 | kubectl get pods -A # all namespaces |
| 4 | kubectl get deployments -n production |
| 5 | kubectl get svc,ingress -n production |
| 6 | |
| 7 | # Detailed information |
| 8 | kubectl describe pod web-app-xyz123 -n production |
| 9 | kubectl describe deployment web-app -n production |
| 10 | |
| 11 | # View logs |
| 12 | kubectl logs web-app-xyz123 -n production --tail=100 |
| 13 | kubectl logs -f web-app-xyz123 -n production # follow |
| 14 | |
| 15 | # Execute into a pod |
| 16 | kubectl exec -it web-app-xyz123 -n production -- /bin/sh |
| 17 | |
| 18 | # Apply and delete resources |
| 19 | kubectl apply -f deployment.yaml |
| 20 | kubectl delete -f deployment.yaml |
| 21 | |
| 22 | # Scale |
| 23 | kubectl scale deployment web-app --replicas=5 -n production |
| 24 | |
| 25 | # Restart all pods (rolling restart) |
| 26 | kubectl rollout restart deployment web-app -n production |
| 27 | |
| 28 | # Check rollout status |
| 29 | kubectl rollout status deployment web-app -n production |
| 30 | |
| 31 | # View resource usage |
| 32 | kubectl top nodes |
| 33 | kubectl top pods -n production |
best practice
Integrating Kubernetes with CI/CD involves building container images, pushing them to a registry, updating manifests, and applying them to the cluster. Tools like ArgoCD, Flux, and GitHub Actions simplify this workflow.
| 1 | # GitHub Actions: Build, push, and deploy to K8s |
| 2 | name: Deploy to Kubernetes |
| 3 | on: |
| 4 | push: |
| 5 | tags: ["v*"] |
| 6 | |
| 7 | jobs: |
| 8 | deploy: |
| 9 | runs-on: ubuntu-latest |
| 10 | steps: |
| 11 | - uses: actions/checkout@v4 |
| 12 | |
| 13 | - name: Build and push image |
| 14 | run: | |
| 15 | docker build -t ghcr.io/myorg/app:${{ github.sha }} . |
| 16 | docker push ghcr.io/myorg/app:${{ github.sha }} |
| 17 | |
| 18 | - name: Update manifest and apply |
| 19 | run: | |
| 20 | kubectl set image deployment/web-app \ |
| 21 | app=ghcr.io/myorg/app:${{ github.sha }} \ |
| 22 | -n production |
| 23 | kubectl rollout status deployment/web-app -n production |
| 24 | env: |
| 25 | KUBECONFIG: ${{ secrets.KUBECONFIG }} |
| 1 | # ArgoCD: declarative GitOps approach |
| 2 | # Install ArgoCD |
| 3 | kubectl create namespace argocd |
| 4 | kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml |
| 5 | |
| 6 | # Create an application |
| 7 | argocd app create web-app \ |
| 8 | --repo https://github.com/myorg/k8s-manifests.git \ |
| 9 | --path apps/web-app \ |
| 10 | --dest-server https://kubernetes.default.svc \ |
| 11 | --dest-namespace production \ |
| 12 | --sync-policy automated \ |
| 13 | --auto-prune \ |
| 14 | --self-heal |
info
Kubernetes is powerful but complex. Depending on your scale and requirements, simpler alternatives may be more appropriate.
| Tool | Best For | Trade-off |
|---|---|---|
| Docker Compose | Single-host development and simple deployments | No multi-node scaling or self-healing |
| Nomad | Simpler orchestrator with multi-platform support | Smaller ecosystem than Kubernetes |
| ECS Fargate | Serverless containers on AWS without node management | AWS vendor lock-in |
| Railway / Fly.io | Rapid deployment with zero infrastructure management | Less control over networking and scheduling |
✓ Use namespaces for isolation
Separate staging, production, and monitoring workloads into dedicated namespaces with resource quotas.
✓ Always set resource requests and limits
Without resource constraints, a single pod can monopolize node resources. Requests ensure scheduling, limits prevent runaway usage.
✓ Use readiness and liveness probes
Readiness probes prevent traffic to unready pods. Liveness probes restart crashed containers automatically.
✓ Prefer GitOps for deployments
Store manifests in Git and use ArgoCD or Flux for automated, auditable deployments with built-in rollback and drift detection.
✓ Use Pod Disruption Budgets
PDBs ensure a minimum number of pods remain available during voluntary disruptions like node drains and cluster upgrades.