Kubernetes Tutorial: Build a Cluster in 13 Steps [2026]

Kubernetes turned eleven this year and the platform that began as a Google research curiosity now runs 96% of production container workloads across the Fortune 500, according to the 2025 CNCF Annual Survey. With Kubernetes 1.35 the active stable line and v1.36 cutting its release branch in early April 2026, the gap between “I read a blog about pods” and “I can ship a real cluster” has never been wider – or more profitable for engineers who close it. This Kubernetes tutorial walks you through 13 production-grade steps, from a clean Linux box to a hardened cluster running a real application with autoscaling, observability, and GitOps.

Unlike the official walkthroughs that stop at kubectl run nginx, every step here ends with a working artifact you can keep: a kind cluster, a Helm chart, an HPA policy, an Ingress with TLS, and an Argo CD app-of-apps. By the end you will have built a complete Kubernetes tutorial project – a Node.js API behind NGINX Ingress, autoscaled by CPU and memory, deployed via GitOps, and wired into Prometheus and Grafana. Total time: roughly four hours of focused work.

Google · Preferred Sources

Don't miss new tech stories on Google

Add Tech Insider once in the Google app and our stories appear in your news suggestions.

Add Now

Why Learn Kubernetes in 2026

If you skipped the container revolution, this is your last clean on-ramp. Kubernetes has graduated from “exotic infra-team toy” to the lingua franca of cloud computing. The 2025 Stack Overflow Developer Survey ranked Kubernetes the #2 most-admired developer tool with an 84/100 admiration score, and 71% of professional respondents said they use it at least weekly. The CNCF reports more than 4.2 million active Kubernetes clusters worldwide, with the kubernetes/kubernetes GitHub repository sitting at 115K+ stars, 32K+ forks, and 2,500+ contributors as of May 2026.

The job market has followed the adoption curve. The Linux Foundation reports more than 250,000 Certified Kubernetes Administrators (CKA) globally as of Q1 2026, with the average US Kubernetes engineer salary tracking 22% above a generalist DevOps salary, per Stack Overflow’s 2025 compensation data. Managed services dominate: AWS EKS holds roughly 32% of cloud Kubernetes workloads, GKE 28%, and Azure AKS 22%, with the three combined powering more than 70% of production clusters.

This Kubernetes tutorial targets developers and SREs who can read a Dockerfile, edit YAML without crying, and SSH into a server. You do not need prior Kubernetes experience. Every command is shown verbatim, every YAML manifest is complete, and every pitfall I have hit in production over the last three years is flagged inline.

Prerequisites and Versions

Pin these versions before you start. Mixing Kubernetes 1.36 manifests with a 1.30 kubectl is the #1 source of “but it works on my laptop” support tickets, and version drift between Helm and the cluster causes the second most. Everything below has been tested on Ubuntu 24.04 LTS, macOS 15.4, and WSL2 with Ubuntu.

Prerequisites and Versions
ToolVersion (April 2026)Why this version
Kubernetes1.35.4 stable (1.36 RC)1.35 is the latest GA line; 1.36 hits GA April 22, 2026
kubectl1.35.4One minor version skew rule – match the cluster
Docker Engine27.5+Required by kind for the local control plane
kind0.24.0Adds Kubernetes 1.36 node images, Go 1.26 build
Helm3.15.3OCI registry support, K8s 1.36 compatibility
Argo CD2.12.5App-of-apps, Helm 3.15 integration
containerd2.0.5Default CRI runtime in K8s 1.36
OSUbuntu 24.04 / macOS 15Both ship glibc 2.39+, required by k3s 1.30+

You also need 8 GB of free RAM, 20 GB of disk space, and a reasonably fast SSD. A 4-vCPU laptop is the practical floor – kind will technically run on 2 vCPUs but the control-plane bring-up time stretches past three minutes and Helm rollouts start to time out. If you are on a corporate VPN that intercepts TLS, generate exemptions for registry.k8s.io, quay.io, and docker.io before you start. Image pull failures behind a TLS-inspecting proxy look like generic ImagePullBackOff errors and waste hours.

Step 1: Install kubectl, kind, and Helm

The local toolchain is three binaries. We are deliberately avoiding Docker Desktop’s bundled Kubernetes – it lags the upstream release by two minor versions and silently disables several admission plugins that production clusters enable by default. On Ubuntu and WSL2:

# kubectl — pinned to 1.35.4
curl -LO "https://dl.k8s.io/release/v1.35.4/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
kubectl version --client

# kind — Kubernetes IN Docker
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
sudo install -o root -g root -m 0755 kind /usr/local/bin/kind
kind version

# Helm 3.15.3
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version --short

On macOS, replace those three downloads with brew install [email protected] kind helm. Verify each binary is on your PATH and reports a sane version. If kubectl version --client returns “command not found”, your /usr/local/bin is not exported – fix that first or every subsequent step will fail in the same way.

Common pitfall #1: macOS users on Apple Silicon must use the arm64 binaries, not amd64. The architectures look interchangeable in the URL pattern but they are not. Mixing them produces a “bad CPU type in executable” error that is the second most common GitHub issue on the kind repo.

Step 2: Create Your First Cluster with kind

kind boots a multi-node Kubernetes cluster inside Docker containers. It is the de facto local-development substrate for the kubernetes/kubernetes project itself, which means it tracks the upstream release within hours. Save this as kind-config.yaml:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: ti-cluster
nodes:
  - role: control-plane
    image: kindest/node:v1.35.4
    kubeadmConfigPatches:
      - |
        kind: InitConfiguration
        nodeRegistration:
          kubeletExtraArgs:
            node-labels: "ingress-ready=true"
    extraPortMappings:
      - containerPort: 80
        hostPort: 80
        protocol: TCP
      - containerPort: 443
        hostPort: 443
        protocol: TCP
  - role: worker
    image: kindest/node:v1.35.4
  - role: worker
    image: kindest/node:v1.35.4

Bring it up with kind create cluster --config kind-config.yaml. The first run pulls about 1.2 GB of node images, so expect a 90-second wait. Subsequent clusters are cached and start in 25 seconds on a modern laptop. Verify with:

$ kubectl get nodes
NAME                       STATUS   ROLES           AGE   VERSION
ti-cluster-control-plane   Ready    control-plane   42s   v1.35.4
ti-cluster-worker          Ready    <none>          28s   v1.35.4
ti-cluster-worker2         Ready    <none>          28s   v1.35.4

The extraPortMappings stanza is what lets you reach localhost:80 from your host machine later, when the Ingress controller is in place. Forgetting that block is the #3 reason new users think their Ingress is broken – the controller is healthy, the Service is healthy, the cluster simply has no port forwarded. kind delete cluster --name ti-cluster tears the whole thing down in 4 seconds when you want to start over.

Step 3: Understand Pods, Deployments, and Services

Three primitives carry 80% of real workloads. A Pod is the smallest deployable unit – one or more containers that share a network namespace and a lifecycle. A Deployment is a controller that maintains N identical Pod replicas, performs rolling updates, and rolls back on failure. A Service is a stable virtual IP and DNS name in front of a set of Pods, so clients do not care that Pods come and go.

You almost never create raw Pods in production. You declare a Deployment and let the controller manage Pods for you. Create deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  labels:
    app: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: hashicorp/http-echo:1.0
          args: ["-text=hello from $(POD_NAME)", "-listen=:5678"]
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
          ports:
            - containerPort: 5678
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: 5678
            initialDelaySeconds: 2
---
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 5678

Apply it with kubectl apply -f deployment.yaml and watch kubectl get pods -w. Three Pods appear, each with a random suffix. Run kubectl port-forward svc/api 8080:80, hit http://localhost:8080 a few times, and you will see different Pod names – the Service is round-robin-load-balancing across the three replicas.

Common pitfall #2: always set both resources.requests and resources.limits. Pods without requests are scheduled on whichever node has the least real-time load, which in a busy cluster means your important workload lands on a node that is about to evict you. Pods without limits can consume all node memory and trigger an OOMKill cascade that takes other tenants down with them.

Step 4: Build a Real Application Image

The hashicorp/http-echo image was a warm-up. Now we build a real workload – a small Node.js Express API that returns JSON and exposes a /healthz endpoint. Create a app/ directory:

Step 4: Build a Real Application Image
// app/server.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const version = process.env.APP_VERSION || 'v1';

app.get('/', (_req, res) => {
  res.json({
    message: 'hello from kubernetes tutorial',
    version,
    pod: process.env.HOSTNAME,
    node: process.env.NODE_NAME,
  });
});

app.get('/healthz', (_req, res) => res.status(200).send('ok'));

app.listen(port, () => console.log(`listening on ${port}`));
# app/Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY server.js ./
USER node
EXPOSE 3000
CMD ["node", "server.js"]

Build and load the image directly into the kind cluster – there is no need to push to a remote registry for local development, which saves both bandwidth and the headache of registry authentication:

cd app
docker build -t ti-api:1.0 .
kind load docker-image ti-api:1.0 --name ti-cluster

Now update the Deployment manifest from Step 3 to use image: ti-api:1.0, change the ports to 3000, and re-apply. The rolling update controller terminates old Pods one at a time, waits for readiness probes to pass on the new ones, and switches traffic atomically. Run kubectl rollout status deploy/api to follow it live.

Step 5: Configuration with ConfigMaps and Secrets

Hard-coding configuration into images is an anti-pattern that punishes you the first time staging and production diverge. Kubernetes splits configuration into two objects: ConfigMaps for non-sensitive key-value data, and Secrets for credentials, certs, and tokens. Both are mounted into Pods as environment variables or files at runtime.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  APP_VERSION: "v1.2.0"
  LOG_LEVEL: "info"
---
apiVersion: v1
kind: Secret
metadata:
  name: api-secret
type: Opaque
stringData:
  API_KEY: "replace-me-with-real-secret"

Reference them in the Deployment with envFrom:

spec:
  template:
    spec:
      containers:
        - name: api
          image: ti-api:1.0
          envFrom:
            - configMapRef:
                name: api-config
            - secretRef:
                name: api-secret

Common pitfall #3: Secrets in stock Kubernetes are base64-encoded, not encrypted. Anyone with get secrets RBAC permission can read them in clear. For production, enable etcd encryption-at-rest with a KMS provider, or layer External Secrets Operator on top of AWS Secrets Manager / Vault. Treating Kubernetes Secrets as if they were already encrypted is the most common security finding in CNCF audits.

Step 6: Expose the App with NGINX Ingress

A Service of type ClusterIP is reachable only inside the cluster. To accept traffic from a browser, you need an Ingress controller – an L7 reverse proxy running inside the cluster that watches Ingress resources and reconfigures itself. NGINX Ingress is the most-deployed controller in the CNCF survey at roughly 47% share. Install it directly into kind:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/kind/deploy.yaml

kubectl wait --namespace ingress-nginx 
  --for=condition=ready pod 
  --selector=app.kubernetes.io/component=controller 
  --timeout=180s

Now define an Ingress that routes localhost/api to your Service:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

Apply, then curl http://localhost/api. You should see the JSON response with a fresh Pod hostname on each request. The kind extraPortMappings from Step 2 is what makes localhost:80 reach the Ingress – without it, you would need kubectl port-forward as a workaround.

Step 7: Package the App as a Helm Chart

Plain YAML stops scaling around the third environment. Helm – the Kubernetes package manager – templates manifests, parameterizes them per-environment, and tracks installed releases. Helm 3.15.3 introduced full OCI registry support, so charts can live next to container images.

helm create ti-api
cd ti-api
# replace values.yaml and templates/ with your own

Edit values.yaml to declare image, replicas, resources, ingress, and autoscaling defaults:

image:
  repository: ti-api
  tag: "1.0"
  pullPolicy: IfNotPresent

replicaCount: 3

service:
  type: ClusterIP
  port: 80
  targetPort: 3000

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: ""
      paths:
        - path: /api
          pathType: Prefix

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

Install with helm install ti-api ./ti-api -n default. Override per-environment with -f values-prod.yaml or --set replicaCount=10. Helm tracks every release in cluster-side history; helm rollback ti-api 1 reverts to the previous version atomically – including ConfigMaps, Secrets, and Ingress changes – in under three seconds on a small chart.

Step 8: Autoscale Workloads with HPA

The Horizontal Pod Autoscaler scales replica counts based on observed metrics. HPA v2 in Kubernetes 1.35+ supports CPU, memory, and custom Prometheus-derived metrics. It depends on the Metrics Server – install it first:

Step 8: Autoscale Workloads with HPA
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# kind clusters need the --kubelet-insecure-tls flag
kubectl patch deploy metrics-server -n kube-system --type=json 
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

The HPA itself is short – Helm generated a basic one for us above. Verify it landed:

$ kubectl get hpa
NAME     REFERENCE           TARGETS         MINPODS   MAXPODS   REPLICAS
ti-api   Deployment/ti-api   3%/70%, 12Mi/128Mi   3   10        3

Now generate load with hey or wrk: hey -z 5m -c 50 http://localhost/api. Within 60 seconds you will watch kubectl get hpa -w climb past 70% CPU and the controller scale to 6, 8, then 10 Pods. Stop the load and HPA holds the high replica count for 5 minutes (the default --horizontal-pod-autoscaler-downscale-stabilization window) before draining back down – a safety mechanism that prevents flapping during traffic dips.

Common pitfall #4: HPA cannot scale a Deployment that has spec.replicas hard-coded if you also keep applying that manifest from CI. The autoscaler raises the count, your next CI run sets it back, and the loop produces sawtooth replica counts. The fix: declare replicas only in the chart’s autoscaling.minReplicas and let HPA own the field.

Step 9: Persistent Storage with PVCs

Stateless apps are easy. Real systems need databases, file uploads, and queues. Kubernetes abstracts storage behind two objects: a PersistentVolume (PV, the actual disk) and a PersistentVolumeClaim (PVC, the app’s request for storage). On managed clouds, a default StorageClass auto-provisions PVs from EBS, GCE PD, or Azure Disk. kind ships with a local-path-provisioner that does the same on your laptop.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: api-data
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
  storageClassName: standard

Mount it inside a Pod via spec.volumes and volumeMounts:

spec:
  template:
    spec:
      containers:
        - name: api
          image: ti-api:1.0
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: api-data

For databases, prefer a StatefulSet over a Deployment – it gives Pods stable network identities (db-0, db-1) and stable PVC mappings, both of which matter when one replica is the primary. The Bitnami PostgreSQL chart, for example, ships a StatefulSet with a 1-replica primary and N read replicas by default.

Step 10: Observability with Prometheus and Grafana

You cannot operate what you cannot see. The kube-prometheus-stack Helm chart bundles Prometheus, Alertmanager, Grafana, and the kube-state-metrics exporter – five tools, one install, fully wired up:

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace monitoring
helm install kps prometheus-community/kube-prometheus-stack 
  -n monitoring 
  --set grafana.adminPassword=admin 
  --set prometheus.prometheusSpec.retention=7d

Wait two minutes for Pods to settle, then port-forward Grafana with kubectl -n monitoring port-forward svc/kps-grafana 3000:80 and open http://localhost:3000 with credentials admin / admin. The chart pre-provisions 28 dashboards, including the famous “Kubernetes / Compute Resources / Cluster” view. To scrape your own app’s metrics, expose a /metrics endpoint and add a ServiceMonitor:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: ti-api
  labels:
    release: kps
spec:
  selector:
    matchLabels:
      app: ti-api
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

The release: kps label is critical – kube-prometheus-stack only watches ServiceMonitors that match the Prometheus instance’s serviceMonitorSelector, which defaults to that label. Forget it and your metrics simply never appear in Grafana, with no error message anywhere. This is by far the most-asked Stack Overflow question on the project.

Step 11: GitOps with Argo CD

Imperative kubectl apply is fine on day one. By month three, you have drift between the cluster and Git, no audit trail, and three engineers convinced their local manifests are the source of truth. GitOps reverses the flow: Git is the source of truth, an in-cluster agent reconciles continuously, and every change is a pull request. Argo CD is the dominant implementation, with 17K+ GitHub stars and roughly 8,000 organizations in production per the CNCF 2025 end-user landscape.

kubectl create namespace argocd
kubectl apply -n argocd -f 
  https://raw.githubusercontent.com/argoproj/argo-cd/v2.12.5/manifests/install.yaml

# Get the bootstrap admin password
kubectl -n argocd get secret argocd-initial-admin-secret 
  -o jsonpath="{.data.password}" | base64 -d
kubectl -n argocd port-forward svc/argocd-server 8443:443

Open https://localhost:8443 (accept the self-signed cert), log in as admin with the bootstrap password. Now define an Application CRD that points at a Git repo:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: ti-api
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/ti-api-deploy
    targetRevision: HEAD
    path: charts/ti-api
    helm:
      valueFiles: ["values-prod.yaml"]
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

From now on, the only way to change the cluster is to merge a PR to that repo. selfHeal: true auto-reverts manual kubectl edit changes within 60 seconds – a blunt but effective enforcement mechanism. The “app of apps” pattern lets you bootstrap a whole cluster from a single root Application that points at a folder of more Applications, which is how organizations like Intuit and Adobe manage thousands of services.

Step 12: Network Policies and Pod Security

By default, every Pod in a Kubernetes cluster can talk to every other Pod across all namespaces. That is convenient and catastrophically permissive – one compromised Pod equals lateral movement to the database, the secret store, and the cloud provider IMDS. NetworkPolicies are the firewall, and they are deny-by-default once at least one policy targets a Pod.

Step 12: Network Policies and Pod Security
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-ingress-only
spec:
  podSelector:
    matchLabels:
      app: ti-api
  policyTypes: ["Ingress"]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 3000

Now only the NGINX Ingress namespace can reach the API on port 3000. Pair it with the Pod Security Standards admission controller – the successor to PodSecurityPolicy, which was removed in 1.25. Label the namespace and Kubernetes enforces baseline or restricted profiles automatically:

kubectl label namespace default 
  pod-security.kubernetes.io/enforce=baseline 
  pod-security.kubernetes.io/warn=restricted

Common pitfall #5: kind ships with kindnet as the default CNI, which does not enforce NetworkPolicies. Manifests apply cleanly but traffic is not blocked, which produces a false sense of security. Switch to Calico or Cilium with kind create cluster --config kind-config.yaml after disabling the default CNI in the config – the kind documentation has a five-line snippet for it.

Step 13: Production Checklist and Cleanup

You have a complete Kubernetes tutorial project. Before you copy it to production, walk this checklist – every item came from a real outage one of my colleagues survived in 2025:

  • Liveness AND readiness probes on every container. Missing readiness means traffic hits a not-yet-warm Pod; missing liveness means a deadlocked process never restarts.
  • PodDisruptionBudgets with minAvailable set to replicas - 1. Stops voluntary disruptions (node drains, cluster autoscaler) from taking the entire Deployment down.
  • Pod anti-affinity across nodes and zones. One bad node should never cost more than one replica.
  • RBAC scoped to a ServiceAccount, never cluster-admin for app workloads. The CNCF security audit found 71% of breaches in 2025 traced to over-privileged ServiceAccounts.
  • Image scanning in CI with Trivy, Grype, or Snyk. Block the build on any HIGH or CRITICAL CVE.
  • Backup the etcd hourly with etcdctl snapshot save. Managed services do this for you; self-hosted clusters must do it themselves.
  • Centralized logs with Loki, Elasticsearch, or a managed service. kubectl logs is gone the moment a Pod is evicted.
  • Resource quotas per namespace. One runaway Job should not exhaust the cluster.

To clean up the local environment, kind delete cluster --name ti-cluster wipes the cluster, the Docker containers, and the kubeconfig context in one command. Helm and kubectl binaries stay on PATH for the next session. The total disk reclaimed is roughly 4 GB after a full run-through.

Common Pitfalls Checklist

Five extra mistakes that cost engineers the most hours, beyond the ones flagged inline above:

  • CrashLoopBackOff with exit code 137 – that is OOMKilled. Bump resources.limits.memory or fix the leak. kubectl describe pod shows the exit reason in the Events section.
  • Stuck “Terminating” Pods – usually a finalizer that cannot complete. Investigate before you kubectl delete --force; force-delete leaks the underlying resource.
  • ImagePullBackOff on private images – you forgot imagePullSecrets on the Pod or the ServiceAccount. Create a kubernetes.io/dockerconfigjson Secret and reference it.
  • Service has endpoints but curl fails – your Service selector does not match Pod labels, or kube-proxy is broken. kubectl get endpoints svc-name tells you immediately.
  • “context deadline exceeded” on apply – kube-apiserver is overloaded. Look at controller-manager logs; usually a runaway controller flooding the API.

Troubleshooting Cheat Sheet

SymptomFirst diagnosticLikely cause
Pod stuck Pendingkubectl describe podNo node satisfies resource requests or affinity rules
ImagePullBackOffkubectl describe podWrong image name, missing pull secret, or registry rate limit
CrashLoopBackOffkubectl logs --previousApp crashes on startup; check env vars and probes
Service unreachablekubectl get endpointsEmpty endpoints = selector mismatch with Pod labels
Ingress 404kubectl describe ingressWrong path, missing ingressClassName, or controller down
HPA shows <unknown>kubectl top podsMetrics Server not installed or kubelet-tls flag missing
OOMKilledkubectl describe podMemory limit too low or app memory leak
Slow rolloutskubectl rollout statusReadiness probe too slow, image pull cold cache
kubectl auth failskubectl auth can-iRBAC missing for the ServiceAccount or user
Webhook 503kubectl get events -AAdmission webhook target Service is down

Advanced Tips for Production Clusters

Once the basics ship, three advanced patterns separate hobby clusters from production. First, Cluster Autoscaler or Karpenter for node-level autoscaling. HPA scales Pods; Cluster Autoscaler scales nodes when Pods cannot schedule. Karpenter, AWS’s open-source replacement, provisions right-sized nodes in 40 seconds versus 5+ minutes for the legacy autoscaler – a meaningful gap during a flash sale.

Advanced Tips for Production Clusters

Second, service mesh. The 2025 CNCF Survey found 68% of Kubernetes users in production run a mesh, and Istio 1.23.3 ambient mode now claims a 50%+ reduction in sidecar overhead, putting the latency cost under 1 millisecond per hop. Linkerd is the lightweight alternative – fewer features, but its Rust data plane uses a fraction of the memory.

Third, policy-as-code with OPA Gatekeeper or Kyverno. Both translate human-readable policies – “no privileged Pods”, “every namespace must have a quota”, “images must come from our registry” – into admission webhooks that reject non-compliant manifests at kubectl apply time. This is how regulated industries pass SOC 2 and PCI audits without reading every PR.

Debugging Tools Every Operator Should Know

Past Step 13 the most-leveraged investment of your time is fluency with five command-line tools. Memorize their flags and you cut median incident response from 30 minutes to under 5. None of these are part of stock Kubernetes – they are community-built and ship as static binaries you can drop into any environment.

  • k9s – a terminal UI that turns kubectl into a navigable dashboard. The :pods, :svc, and :ev shortcuts cover 80% of debugging. Filter by namespace with 0, follow logs with l, exec into a Pod with s. Roughly 28K GitHub stars and the most-adopted operator tool in the 2025 CNCF survey.
  • stern – multi-Pod log tailing. stern api follows logs from every Pod whose name matches “api” across all replicas, color-coded by Pod. Indispensable when an HPA has scaled you to 12 replicas and the bug only appears on one of them.
  • kubectx and kubens – context and namespace switchers. kubectx prod jumps between clusters; kubens kube-system sets the default namespace. Eliminates the “kubectl get pods returned empty because I was on the wrong cluster” foot-gun.
  • kubectl debug – built-in since 1.25, GA in 1.36. Attaches an ephemeral debug container to a running Pod, with arbitrary tooling: kubectl debug pod/api-x --image=nicolaka/netshoot --target=api. No restart, no image rebuild.
  • k3sup and k0sctl – bootstrappers for self-hosted clusters over SSH. Spin up a 3-node cluster on bare metal in under five minutes, with TLS and a working kubeconfig at the end.

Pair these with shell aliases for the kubectl commands you type most: alias k=kubectl, alias kgp='kubectl get pods', alias kdp='kubectl describe pod'. The official kubectl bash/zsh completions (kubectl completion zsh) cover the rest. By the second week, your hands will type kgp faster than your brain forms the intent – which is exactly the point during a 3 a.m. incident.

Managed Kubernetes vs Self-Hosted

The build-versus-buy question is the first architectural decision in any real adoption. Managed services (EKS, GKE, AKS) handle the control plane, etcd backups, version upgrades, and certificate rotation. Self-hosted clusters give you total control and deeper cost optimization. The numbers from the 2025 CNCF Annual Survey suggest the market has chosen:

PlatformMarket shareControl plane costTime to first clusterBest for
AWS EKS~32%$0.10/hr ($73/mo)~15 minAWS-native shops, IAM-heavy environments
Google GKE~28%$0.10/hr (Autopilot extra)~5 minMost polished UX, fastest upgrades
Azure AKS~22%Free (paid tier $0.10/hr)~10 minEnterprise + Microsoft 365 integration
Self-hosted (kubeadm)~10%Hardware only~60 minOn-prem, regulated, edge
k3s/k0s~5%Hardware only~5 minEdge, IoT, single-node
OpenShift~3%$0.171/core/hr (Red Hat)~45 minRegulated enterprises, opinionated platform

For most teams in 2026, the calculus is simple: a $73/month managed control plane is cheaper than the four-engineer-hours per week needed to run etcd, manage kubeadm upgrades, and renew certificates. Move to self-hosted only when the answer to “what does this save us?” is at least five figures per year, or when compliance forbids a managed plane.

What’s New in Kubernetes 1.36

Kubernetes 1.36 reaches GA on April 22, 2026, twelve days after this article posts. The release branch is already cut, the RC has been published with Go 1.26.0, and the headline features are public on kubernetes.io/blog. Five changes will materially affect the manifests in this tutorial:

  • Dynamic Resource Allocation (DRA) GA – first-class scheduling of GPUs, FPGAs, and custom devices, replacing the device-plugin shim with a richer API. Critical for AI/ML workloads.
  • PodSecurity admission gets stricter defaults – the baseline profile now blocks hostPath volumes outside an allowlist by default. Audit your PVs before upgrading.
  • Sidecar containers GA – first-class lifecycle ordering so log shippers and meshes start before app containers and stop after them. Removes a class of “container terminated before sidecar” race conditions.
  • JobTrackingWithFinalizers is no longer optional – improves Job completion accounting at scale (10K+ Pods).
  • kubectl debug graduates to GA – ephemeral debug containers without restarting the workload.

None of these break the manifests in this tutorial, but the PodSecurity change in particular is one that you want to test in staging before pushing to production. The Kubernetes release team’s SIG-Release has been excellent about backwards compatibility – major regressions in the last four releases have been fewer than 0.5% of changed APIs, per the LWKD weekly newsletter.

FAQ: Kubernetes Tutorial Questions

How long does it take to learn Kubernetes?

Plan on 40-60 hours to be productive – running this tutorial, following along with the official kubernetes.io tutorials, and shipping one toy app to a managed cluster. Plan on 6-12 months of daily use to feel confident debugging production. The CKA exam, which is the industry’s competence benchmark, requires roughly 80 hours of focused study according to the Linux Foundation’s own training data.

Do I need to learn Docker before Kubernetes?

Yes, in the sense that you need to understand container images and how to write a Dockerfile. Kubernetes does not actually use Docker as its runtime anymore – containerd is the default since 1.24 – but the OCI image format and Docker’s UX are still the lingua franca of container builds. Plan on a 4-8 hour Docker primer before you start.

Is Kubernetes overkill for small projects?

Often, yes. A two-service app on a $20 VPS with Docker Compose will be more reliable and cheaper than the same workload on a $200/month EKS cluster. Kubernetes pays off above roughly 5-10 microservices, multiple environments, or any workload that needs to autoscale across more than one node. Below that threshold, k3s or even Fly.io / Railway will save you both money and time.

What’s the difference between kind, minikube, and k3s?

kind runs Kubernetes nodes as Docker containers; minikube runs them as a VM (or Docker, optionally); k3s is a lightweight Kubernetes distribution that runs as a single binary, ideal for edge and IoT. For laptop development with multi-node simulation, kind is the modern default and what every kubernetes/kubernetes contributor uses. minikube is a fine alternative on macOS where Docker Desktop’s overhead is high.

Should I use Helm or plain YAML?

Plain YAML for the first two services. Helm the moment you have more than one environment, more than three services, or any reusable component. Kustomize is the no-templating-language alternative – it overlays patches onto base manifests. Many teams use both: Kustomize for environment overlays, Helm for third-party charts.

How do I get a job working with Kubernetes?

Three things, in order: (1) ship one real app to a managed cluster end-to-end (this tutorial qualifies), (2) pass the CKA exam – it costs $395 and the certificate is valid for two years, (3) read four to six well-known incident postmortems (the Reddit, GitLab, and Cloudflare write-ups are gold). DevOps and SRE roles touching Kubernetes were the #2 fastest-growing job category in the LinkedIn 2025 Jobs on the Rise report.

What’s the difference between Kubernetes and OpenShift?

OpenShift is Red Hat’s enterprise Kubernetes distribution. It bundles a CI/CD system (Tekton), a container registry, an Ingress (HAProxy-based Routes), and stricter security defaults. The trade-off is cost – Red Hat charges around $0.171/core/hour for OpenShift, which lands at roughly $1,000 per core per year above vanilla Kubernetes. Worth it for regulated enterprises; overkill for most teams.

Can I run Kubernetes at home?

Absolutely. A three-node Raspberry Pi 5 cluster running k3s is a popular weekend project – total hardware cost roughly $300 and power draw under 25 watts. Combine it with Tailscale for remote access and Argo CD for GitOps and you have a credible homelab that mirrors a real production setup.

What’s the future of Kubernetes?

The platform is now boring infrastructure – exactly where mature projects want to be. The interesting work has moved up the stack: AI/ML scheduling (KubeFlow, KServe, the new DRA APIs), platform engineering (Backstage, Crossplane), and edge (k3s, KubeEdge). Kubernetes itself ships three releases per year with the SIG Release group, and the cadence is unlikely to change before 2030.

Related Coverage

External references for further reading: Kubernetes Releases, Official Tutorials, Helm Docs, kind Project, and Argo CD Documentation.

Nadia Dubois

Nadia Dubois

AI & Innovation Editor

Nadia Dubois is the AI & Innovation Editor at Tech Insider, where she tracks the rapid evolution of artificial intelligence, from foundation models to real-world enterprise deployment. She previously covered AI and startups for La Tribune and contributed to MIT Technology Review's European coverage. Nadia specializes in generative AI, AI regulation, and the intersection of technology and European industrial policy. She holds a dual degree in Computational Linguistics and Journalism from Sciences Po Paris.

View all articles