Getting Started
kubectl Basics
kubectl is the CLI for Kubernetes. get lists resources, create makes new ones, expose creates services, scale changes replica count. describe gives detailed info about a resource.
# cluster info
kubectl cluster-info
# list nodes
kubectl get nodes
# list pods (all namespaces)
kubectl get pods --all-namespaces
# create a deployment
kubectl create deployment nginx --image=nginx
# expose deployment as service
kubectl expose deployment nginx --port=80 --type=LoadBalancer
# scale deployment
kubectl scale deployment nginx --replicas=3
# get detailed info
kubectl describe pod <pod-name>
# view logs
kubectl logs <pod-name>Output Formatting
The -o flag controls output. yaml/json show full resource spec. jsonpath and custom-columns extract specific fields for scripting. -w watches for live changes. Use --no-headers in scripts to drop column names.
# wide format (more columns)
kubectl get pods -o wide
# YAML output
kubectl get pod nginx -o yaml
# JSON output
kubectl get pod nginx -o json
# jsonpath (extract specific fields)
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
# custom columns
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase
# show labels
kubectl get pods --show-labels
# watch for changes
kubectl get pods -wContext & Configuration
A context bundles cluster address, user credentials, and namespace. kubeconfig files (default ~/.kube/config) store these. Multiple files can be combined via KUBECONFIG env var for managing many clusters.
# view current config
kubectl config view
# list contexts
kubectl config get-contexts
# switch context
kubectl config use-context my-cluster
# set default namespace for current context
kubectl config set-context --current --namespace=dev
# show current context
kubectl config current-context
# KUBECONFIG with multiple files
export KUBECONFIG=~/.kube/config:~/.kube/prod-configExplain & API Resources
api-resources lists what the cluster supports. explain shows the OpenAPI schema for any resource — invaluable for writing YAML without leaving the terminal. Use --recursive to dump the full nested structure.
# list all resource types
kubectl api-resources
# list namespaced vs cluster-scoped
kubectl api-resources --namespaced=true
kubectl api-resources --namespaced=false
# explain a resource's fields
kubectl explain pod
kubectl explain pod.spec.containers
kubectl explain deployment.spec.strategy
# explain with recursion (all nested fields)
kubectl explain pod --recursive
# include description
kubectl explain pod.spec.containers.resources --api-version=apps/v1Dry Run & Generate YAML
--dry-run=client -o yaml is the standard way to scaffold manifests from imperative commands. --dry-run=server validates against the real apiserver (admission webhooks, schema) without persisting. Great for bootstrapping declarative YAML.
# generate YAML without applying
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml
# pipe generated YAML straight to a file
kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > nginx.yaml
# apply the generated manifest
kubectl apply -f nginx.yaml
# dry-run against the server (validates with apiserver)
kubectl apply -f nginx.yaml --dry-run=server
# generate a service from a deployment
kubectl create service clusterip my-svc --tcp=80:80 --dry-run=client -o yamlHelp & Autocomplete
Every kubectl command supports --help with examples. Shell completion saves enormous typing and exposes subcommands. The alias k=kubectl is near-universal among practitioners — combine with completion for the best experience.
# top-level help
kubectl --help
# subcommand help
kubectl create --help
kubectl explain --help
# generate bash completion
kubectl completion bash > /etc/bash_completion.d/kubectl
# zsh completion
source <(kubectl completion zsh)
# alias k=kubectl with completion
alias k=kubectl
complete -o default -F __start_kubectl k
# shorthand: kubectl -> k
echo 'alias k=kubectl' >> ~/.bashrcPod
Pod Manifest
A Pod is the smallest deployable unit, holding one or more containers that share network and storage. Containers in a pod are always co-scheduled on the same node. You rarely create pods directly — use a Deployment or Job instead.
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
restartPolicy: AlwaysMulti-Container Pod (Sidecar)
Multi-container pods share network (same IP, port space) and volumes. Common patterns: sidecar (helper), adapter (transforms output), ambassador (proxy). The shared emptyDir volume lets the sidecar read logs the main container writes.
apiVersion: v1
kind: Pod
metadata:
name: web-with-logs
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
- name: log-shipper
image: busybox
command: ["sh", "-c", "tail -f /var/log/nginx/access.log"]
volumeMounts:
- name: shared-logs
mountPath: /var/log/nginx
readOnly: true
volumes:
- name: shared-logs
emptyDir: {}Pod Lifecycle & Phases
A pod's phase is high-level (Pending, Running, Succeeded, Failed). Container states (Waiting/Running/Terminated) give the real detail — e.g. CrashLoopBackOff means the container keeps exiting. Check restart count and events to diagnose.
# pod phases: Pending | Running | Succeeded | Failed | Unknown
kubectl get pod nginx
# show status including container states
kubectl get pod nginx -o jsonpath='{.status}'
# container states: Waiting | Running | Terminated
kubectl describe pod nginx
# common Waiting reasons: ImagePullBackOff, CrashLoopBackOff, CreateContainerConfigError
kubectl get pods --field-selector=status.phase=Pending
# restart count reveals crash loops
kubectl get pods -o jsonpath='{.items[*].status.containerStatuses[*].restartCount}'Pod Status & Events
Events are the cluster's audit log for a resource — scheduling, pulling, pulling errors, liveness failures. They expire (~1 hour by default), so capture them while debugging. describe bundles events with the resource spec for quick triage.
# full describe (includes Events at the bottom)
kubectl describe pod nginx
# list cluster events sorted by time
kubectl get events --sort-by=.lastTimestamp
# events for a specific namespace
kubectl get events -n default
# watch events live
kubectl get events -w
# filter events by type
kubectl get events --field-selector type=Warning
# events for a specific pod
kubectl get events --field-selector involvedObject.name=nginxExec & Port-Forward
exec runs commands inside a container; -it allocates a TTY for shells. port-forward tunnels a local port to a pod/service without exposing it publicly — essential for debugging databases or web UIs from your laptop.
# open an interactive shell
kubectl exec -it nginx -- sh
# run a one-off command
kubectl exec nginx -- ls /etc/nginx
# specify container in a multi-container pod
kubectl exec -it web-with-logs -c log-shipper -- sh
# forward a local port to a pod port
kubectl port-forward pod/nginx 8080:80
# forward to a service / deployment
kubectl port-forward svc/nginx 8080:80
kubectl port-forward deployment/nginx 8080:80
# forward to a random local port
kubectl port-forward pod/nginx :80Delete & Force
Deleting a pod controlled by a Deployment/StatefulSet causes an immediate replacement. --force --grace-period=0 skips the TERM signal grace period and can leave stale resources — use sparingly. Stuck terminating pods often need a finalizer removal.
# delete a pod (controller will recreate it)
kubectl delete pod nginx
# force delete immediately (bypasses graceful shutdown)
kubectl delete pod nginx --grace-period=0 --force
# delete by label
kubectl delete pods -l app=nginx
# delete all pods in a namespace
kubectl delete pods --all -n default
# delete a pod and wait for it to be gone
kubectl delete pod nginx --wait
# delete with a timeout
kubectl delete pod nginx --timeout=30sDeployment
Deployment Manifest
A Deployment manages ReplicaSets and provides declarative rolling updates and rollbacks. spec.selector must match spec.template.metadata.labels. The template defines the pod spec that gets rolled out.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80Rolling Update Strategy
RollingUpdate (default) gradually replaces pods for zero downtime. maxSurge/maxUnavailable control rollout speed and availability. Recreate kills all pods before creating new ones — used when the app cannot run two versions at once (e.g. single-volume writers).
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25% # extra pods above replicas during update
maxUnavailable: 25% # pods allowed down during update
# alternative: recreate (kills all, then starts new)
# strategy:
# type: Recreate
selector:
matchLabels:
app: nginx
template:Rollback
Each change to the pod template creates a ReplicaSet revision. undo flips traffic back to a previous ReplicaSet. pause lets you do multiple fixes between canary increments. Use --record (deprecated) or annotations to track change causes.
# view rollout history
kubectl rollout history deployment/nginx
# see details of a specific revision
kubectl rollout history deployment/nginx --revision=2
# undo to the previous revision
kubectl rollout undo deployment/nginx
# undo to a specific revision
kubectl rollout undo deployment/nginx --to-revision=2
# pause/resume a rollout (for canary pauses)
kubectl rollout pause deployment/nginx
kubectl rollout resume deployment/nginx
# check rollout status
kubectl rollout status deployment/nginxScale & HPA
HPA scales replicas based on CPU/memory or custom metrics. Requires metrics-server for CPU/mem. minReplicas and maxReplicas bound scaling. The v2 API supports multiple metrics and behaviors (scale-down stabilization).
# scale manually
kubectl scale deployment/nginx --replicas=5
# horizontal pod autoscaler (CPU target)
kubectl autoscale deployment nginx --min=2 --max=10 --cpu-percent=80
# list HPA
kubectl get hpa
# HPA manifest (v2 with custom metrics)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80Update Image & Restart
set image triggers a new ReplicaSet and rolling update. rollout restart is the cleanest way to pull the latest image or refresh config — it creates a new ReplicaSet with the same spec. edit opens your $EDITOR for ad-hoc fixes.
# update container image
kubectl set image deployment/nginx nginx=nginx:1.26
# update from a specific container
kubectl set image deployment/nginx container=nginx:1.26 --record
# trigger a rollout restart (same image, recreate pods)
kubectl rollout restart deployment/nginx
# edit a deployment in editor
kubectl edit deployment/nginx
# patch a single field
kubectl patch deployment/nginx -p '{"spec":{"replicas":5}}'
# apply a manifest change
kubectl apply -f nginx-deployment.yamlDeployment Status
A Deployment owns ReplicaSets; only one is active (replicas>0). Old ReplicaSets scale to zero for rollback. pod-template-hash labels each pod with its revision. Check status.conditions for Available, Progressing, and ReplicaFailure signals.
# deployment status
kubectl get deployment nginx
# watch rollout
kubectl rollout status deployment/nginx
# show ReplicaSets owned by the deployment
kubectl get rs -l app=nginx
# show pods across all revisions
kubectl get pods -l app=nginx --show-labels
# pod-template-hash label distinguishes revisions
# kubectl get pods -l app=nginx,pod-template-hash=xxxx
# conditions: Available, Progressing, ReplicaFailure
kubectl get deployment nginx -o jsonpath='{.status.conditions}'Service
Service Types
A Service provides a stable IP/DNS that load-balances across a set of pods. ClusterIP is the default (in-cluster only). NodePort and LoadBalancer expose externally. ExternalName is a DNS alias, not a proxy.
# four service types:
# - ClusterIP (default): reachable inside the cluster
# - NodePort: exposed on each node's IP at a static port
# - LoadBalancer: cloud-provisioned external LB
# - ExternalName: DNS CNAME to an external host
# quick create
kubectl expose deployment nginx --port=80 --type=ClusterIP
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl expose deployment nginx --port=80 --type=LoadBalancer
# external name (no proxying, pure DNS)
kubectl create service externalname my-svc --external-name=db.example.comClusterIP Service
ClusterIP gives a stable virtual IP and DNS name within the cluster. The selector matches pod labels; kube-proxy programs iptables/IPVS to load-balance across endpoints. Resolve via <svc>.<ns>.svc.cluster.local or just <svc>.
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
port: 80 # service port
targetPort: 80 # container port
protocol: TCP
# DNS: nginx.default.svc.cluster.local
# short names also work: nginx, nginx.default
# kube-dns / CoreDNS resolves the service VIPNodePort Service
NodePort opens the same static port (30000-32767) on every node. Traffic to any node:nodePort reaches a pod (kube-proxy may redirect to a pod on another node). Useful when you can't provision a cloud LB; pair with an external LB for HA.
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080 # optional, 30000-32767 range
# reachable at <any-node-ip>:30080
# also still reachable via clusterIP:80
# get assigned nodePort
kubectl get svc nginx -o jsonpath='{.spec.ports[*].nodePort}'LoadBalancer Service
LoadBalancer asks the cloud provider to provision an external load balancer pointing at the nodes. externalTrafficPolicy: Local preserves the client source IP (no SNAT) but skips cross-node load balancing. STATUS shows the assigned external IP once ready.
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
# cloud-specific annotations
# annotations:
# service.beta.kubernetes.io/aws-load-balancer-type: nlb
# service.beta.kubernetes.io/azure-load-balancer-internal: "true"
# externalTrafficPolicy: Local preserves client IP
externalTrafficPolicy: Local
# status.ingress shows the external IP/hostnameHeadless Service
A headless service (clusterIP: None) has no VIP — DNS returns the pod IPs directly. Required by StatefulSet so each pod gets a stable DNS name (pod-0.svc, pod-1.svc). Also used by clients that want to discover all backends themselves.
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None # makes it headless
selector:
app: nginx
ports:
- port: 80
targetPort: 80
# DNS returns pod IPs directly (not a VIP)
# A records: nginx-headless.default.svc.cluster.local
# -> 10.1.0.5, 10.1.0.6, 10.1.0.7
# used by StatefulSet for stable pod DNS:
# <pod-name>.<svc-name>.default.svc.cluster.localEndpoints & EndpointSlices
Endpoints/EndpointSlices list the pod IPs that actually receive traffic — only Ready pods appear. If a service has no endpoints, the pods aren't matching the selector or aren't Ready. EndpointSlices (v1) scale better for huge services and are the default.
# view endpoints backing a service
kubectl get endpoints nginx
# endpoint slices (newer, more scalable)
kubectl get endpointslices -l kubernetes.io/service-name=nginx
# endpoints track pods that match the selector
# AND pass readiness checks
kubectl describe endpoints nginx
# a service with no ready pods has no endpoints
# - check pod readiness if traffic is refused
# - EndpointSlices are grouped by address type (IPv4/IPv6)
# manually managed endpoints (selector-less service)ConfigMap
Create ConfigMap
ConfigMaps store non-sensitive configuration as key-value pairs. --from-literal for inline values, --from-file to embed whole files (the filename becomes the key). Useful for decoupling config from images without rebuilding.
# from literal values
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=debug \
--from-literal=ENABLE_FEATURE=true
# from a file (key = filename, value = contents)
kubectl create configmap app-config --from-file=app.properties
# from a directory (one key per file)
kubectl create configmap app-config --from-file=configs/
# from an env file
kubectl create configmap app-config --from-env-file=app.env
# from a file with a custom key
kubectl create configmap app-config --from-file=custom-key=app.propertiesConfigMap Manifest
data holds string values (each key becomes either an env var or a file when mounted). Multi-line strings use YAML block scalars (|). binaryData accepts base64 for binary blobs. Total size limit is 1 MiB per ConfigMap.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "debug"
ENABLE_FEATURE: "true"
application.yml: |
server:
port: 8080
logging:
level: debug
binaryData:
# base64-encoded binary values
logo.png: iVBORw0KGgoAAAANSUhEUg...Consume as Environment Variables
envFrom injects every ConfigMap key as an env var in one shot. env with configMapKeyRef picks individual keys. optional: true lets the pod start even if the ConfigMap doesn't exist. Updates to the ConfigMap do NOT refresh env vars in running pods.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
envFrom:
- configMapRef:
name: app-config # injects all keys as env vars
optional: true # pod starts even if CM missingConsume as Volume
Mounting a ConfigMap as a volume projects each key as a file. Updates to the ConfigMap are eventually reflected in mounted files (configured by kubelet sync period) — but apps must re-read them. subPath mounts pin a single key and do NOT update.
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config
mountPath: /etc/config
readOnly: true
volumes:
- name: config
configMap:
name: app-config
items: # optional: pick subset
- key: application.yml
path: app.yml # rename file
defaultMode: 0644
# each ConfigMap key becomes a file under /etc/configUpdate & Reload
Mounted ConfigMap files refresh automatically (after kubelet sync), but env vars are immutable for the pod's life — restart pods to pick up env changes. rollout restart is the cleanest way to propagate config updates to a Deployment.
# edit the configmap
kubectl edit configmap app-config
# patch a single key
kubectl patch configmap app-config \
--type merge -p '{"data":{"LOG_LEVEL":"info"}}'
# mounted files update within ~kubelet sync period (default 60s)
# but env vars do NOT update in running pods
# force pods to pick up new config by rolling them
kubectl rollout restart deployment/app
# verify the new value on a pod
kubectl exec app -- printenv LOG_LEVELImmutable ConfigMap
Setting immutable: true freezes the ConfigMap — any change requires delete + recreate. This drastically reduces kubelet-apiserver watch traffic for large, stable configs (a major scalability win at scale). Common in production for app configs that are versioned with the image.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
immutable: true
data:
LOG_LEVEL: "debug"
# once immutable, the only change allowed is delete + recreate
# benefits: kubelet does not watch it, lower apiserver load
# useful for large, stable configs in production
# make it mutable again
kubectl patch configmap app-config -p '{"immutable":false}'
# (this fails if currently immutable — must delete instead)Secret
Create Secret
Secrets store sensitive data base64-encoded (NOT encrypted at rest by default — enable encryption at rest for production). Use generic/Opaque for arbitrary data, docker-registry for image pulls, tls for Ingress TLS. Never commit Secret YAML to git.
# generic (opaque) from literals
kubectl create secret generic db-creds \
--from-literal=username=admin \
--from-literal=password='S3cur3!'
# from a file (e.g. SSH key, certificate)
kubectl create secret generic ssh-key --from-file=ssh-privatekey=~/.ssh/id_rsa
# docker registry secret (for private image registries)
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass \
[email protected]
# TLS secret from cert/key files
kubectl create secret tls my-tls \
--cert=fullchain.pem \
--key=privkey.pemSecret Manifest
data values must be base64-encoded (echo -n 'value' | base64). stringData is a convenience that accepts plaintext and is merged into data on apply. type Opaque is the default; specialized types (kubernetes.io/tls, kubernetes.io/dockerconfigjson) validate the keys.