Kubernetes » Operations
Day-to-day cluster management: kubectl, Helm package management, observability wiring, troubleshooting techniques, and production best practices.
kubectl Mastery: Command-Line Kubernetes
kubectl is your primary interface for managing Kubernetes clusters. This section covers the commands you will use most often, organized by task.
Before you begin: kubectl needs to know which cluster to talk to. This is configured through contexts, which combine a cluster, user, and namespace.
Essential kubectl Commands
| Task | Command |
|---|---|
| See what is running | kubectl get pods |
| Get more details | kubectl describe pod <name> |
| View logs | kubectl logs <pod-name> |
| Execute in container | kubectl exec -it <pod> -- /bin/sh |
| Apply configuration | kubectl apply -f manifest.yaml |
| Delete resources | kubectl delete -f manifest.yaml |
Working with Multiple Clusters
# List available contexts
kubectl config get-contexts
# Switch to a different cluster
kubectl config use-context production-cluster
# Set default namespace for current context
kubectl config set-context --current --namespace=production
Viewing Resources
# List resources with extra info
kubectl get pods -o wide
kubectl get all -n production
# Watch for changes in real-time
kubectl get pods -w
# Filter by labels
kubectl get pods -l environment=production
kubectl get pods -l 'tier in (frontend, backend)'
Creating and Updating Resources
# Apply configuration (create or update)
kubectl apply -f deployment.yaml
# Quick edits via patch
kubectl patch deployment nginx -p '{"spec":{"replicas":5}}'
# Edit in your default editor
kubectl edit deployment nginx
Debugging and Troubleshooting
# View pod logs
kubectl logs mypod
kubectl logs mypod --previous # crashed container
kubectl logs -f -l app=nginx # follow all matching pods
# Execute commands in container
kubectl exec -it mypod -- /bin/sh
# Port forward for local testing
kubectl port-forward svc/myservice 8080:80
# Check resource usage
kubectl top pods --sort-by=memory
kubectl top nodes
Secrets and ConfigMaps
# Create secret from literal value
kubectl create secret generic db-creds --from-literal=password=mypass
# Decode a secret value
kubectl get secret db-creds -o jsonpath="{.data.password}" | base64 -d
# Create configmap from file
kubectl create configmap app-config --from-file=config.yaml
Power User Tips
Useful aliases (add to your shell config):
alias k='kubectl'
alias kgp='kubectl get pods'
alias kaf='kubectl apply -f'
JSONPath for extracting data:
# Get all pod names
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
Node management:
# Prepare node for maintenance
kubectl drain node1 --ignore-daemonsets --delete-emptydir-data
kubectl uncordon node1 # make schedulable again
# Mark node unschedulable (no drain)
kubectl cordon node1
kubectl Plugins with Krew
Krew is a plugin manager for kubectl. Popular plugins:
| Plugin | Purpose |
|---|---|
ctx |
Quickly switch contexts |
ns |
Quickly switch namespaces |
tree |
Show resource hierarchy |
neat |
Clean up YAML output |
Install krew and a plugin:
kubectl krew install ctx
kubectl ctx production # switch context
Best Practices: Production Checklist
Before going to production, verify your setup against these categories:
Resource Management
| Practice | Why It Matters |
|---|---|
| Set resource requests/limits | Prevents resource starvation and runaway costs |
| Use namespaces | Isolate environments and teams |
| Label everything consistently | Enables filtering, monitoring, and cost allocation |
| Implement ResourceQuotas | Prevents one team from consuming all resources |
High Availability
| Practice | Why It Matters |
|---|---|
| Run 3+ replicas | Survives node failures |
| Use pod anti-affinity | Spreads pods across nodes/zones |
| Define PodDisruptionBudgets | Controls how many pods can be down during updates |
| Implement health probes | Ensures traffic only goes to healthy pods |
Security
| Practice | Why It Matters |
|---|---|
| Enable RBAC with least privilege | Limits blast radius of compromised accounts |
| Use NetworkPolicies | Prevents lateral movement between services |
| Run as non-root | Reduces container escape impact |
| Scan images for vulnerabilities | Catches known issues before deployment |
Observability
| Practice | Why It Matters |
|---|---|
| Centralize logs | Enables debugging after pod deletion |
| Expose metrics | Enables alerting and capacity planning |
| Implement distributed tracing | Debugs latency across services |
| Set up alerts | Catches issues before users notice |
Observability: Seeing Inside the Cluster
Health probes tell Kubernetes whether to restart or route to a pod (covered in Workloads & Storage), but they answer a binary question. Operating a cluster means answering why — why is latency up, which release introduced the error, what was this pod logging the moment before it was evicted. That requires the three pillars of observability: metrics, logs, and traces.
| Pillar | Answers | Cardinality | Typical retention |
|---|---|---|---|
| Metrics | “What is happening, and how much?” (rates, percentiles, saturation) | Low — aggregated numbers | Weeks to months (cheap) |
| Logs | “What exactly happened in this request/pod?” | High — one entry per event | Days to weeks (expensive) |
| Traces | “Where did the time go across services?” | High — sampled | Days |
The pillars are complementary, not redundant. A metric tells you the p99 latency spiked; a trace shows you which downstream call caused it; the logs from that span’s service tell you the underlying error. Modern tooling (OpenTelemetry, exemplars, Grafana) is increasingly about correlating the three so you can pivot between them with one click.
Where this fits: Kubernetes is one source of telemetry, not the whole story. The platform-level concepts — metric types and PromQL, the ELK/Loki logging stacks, OpenTelemetry and sampling — are covered in depth in the Observability hub. This section is the Kubernetes-specific wiring: what to deploy in-cluster and how the pieces connect.
The Metrics Pipeline
Two distinct things both get called “metrics” in Kubernetes, and conflating them is a common source of confusion:
flowchart LR
subgraph cluster["In-cluster"]
K[kubelet / cAdvisor] -->|node & container<br/>CPU, mem| MS[metrics-server]
MS -->|resource metrics API| HPA[HPA / kubectl top]
Pods[App /metrics endpoints] -->|scrape| Prom[Prometheus]
KSM[kube-state-metrics] -->|object state:<br/>deploys, pods, jobs| Prom
NE[node-exporter] -->|host-level metrics| Prom
end
Prom -->|remote_write| LTS[(Long-term store<br/>Thanos / Mimir / Cortex)]
Prom --> Graf[Grafana]
Prom --> AM[Alertmanager]
AM --> Pager[PagerDuty / Slack]
| Component | Role | Used by |
|---|---|---|
| metrics-server | Lightweight, in-memory current CPU/memory only | kubectl top, the Horizontal Pod Autoscaler |
| kube-state-metrics | Exports the state of API objects (desired vs ready replicas, pod phase, job status) | Prometheus / alerting |
| node-exporter | Host-level metrics (disk, filesystem, network, load) | Prometheus |
| Prometheus | Scrapes and stores the time series; evaluates alert rules | Grafana, Alertmanager |
metrics-server is not a monitoring system — it keeps no history and is only there to feed the autoscaler and kubectl top. Real monitoring is Prometheus (or a hosted equivalent). The conventional way to install the whole Prometheus + Grafana + Alertmanager bundle is the kube-prometheus-stack Helm chart:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
-n monitoring --create-namespace
That chart ships the Prometheus Operator, which lets you describe scrape targets declaratively with ServiceMonitor/PodMonitor custom resources instead of editing a central Prometheus config:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: app-metrics
labels:
release: monitoring # must match the Prometheus' serviceMonitorSelector
spec:
selector:
matchLabels:
app: myapp # selects Services labelled app=myapp
endpoints:
- port: metrics # the named Service port exposing /metrics
interval: 30s
What to measure. Two complementary mental models cover most needs:
- RED (for request-driven services): Rate, Errors, Duration. Good for “is my API healthy?”
- USE (for resources): Utilization, Saturation, Errors. Good for “is this node/disk/queue a bottleneck?”
Beware cardinality: every unique combination of label values is a separate time series. Putting a user ID, request ID, or full URL in a Prometheus label can explode memory and bring the server down. Keep labels bounded (status code, route template, method) and push high-cardinality detail into logs or traces instead.
Log Aggregation
A pod’s logs live on the node only while the pod exists. The moment a pod is deleted, rescheduled, or its node is replaced, kubectl logs returns nothing — exactly when you most need the post-mortem. Centralized log aggregation ships every container’s stdout/stderr off-node into a queryable store before that happens.
The architecture is consistent across stacks: a lightweight collector runs as a DaemonSet (one per node), tails /var/log/containers/*.log, enriches each line with Kubernetes metadata (namespace, pod, labels), and forwards it to a backend.
flowchart LR
subgraph node["Each node (DaemonSet)"]
Logs[/var/log/containers/*.log] --> Agent[Fluent Bit / Vector]
end
Agent -->|enrich + parse| Backend{Backend}
Backend --> Loki[(Loki)]
Backend --> ES[(Elasticsearch)]
Loki --> Graf[Grafana]
ES --> Kib[Kibana]
| Stack | Collector | Store | UI | Trade-off |
|---|---|---|---|---|
| EFK / ELK | Fluentd or Fluent Bit | Elasticsearch | Kibana | Full-text indexing, powerful queries, but storage- and memory-hungry |
| Loki | Promtail / Fluent Bit | Loki | Grafana | Indexes only labels (not the log body) — far cheaper, “like Prometheus for logs” |
| Vector | Vector (agent + aggregator) | any of the above | depends on sink | High-throughput, vendor-neutral routing/transform layer |
Fluent Bit has largely displaced the heavier Fluentd as the node agent: it is written in C, uses a few MB of RAM, and is the default collector in most managed offerings. A minimal Fluent Bit pipeline that tails container logs, attaches Kubernetes metadata, and ships to Loki:
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser cri
Tag kube.*
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
[OUTPUT]
Name loki
Match *
Host loki.monitoring.svc
Labels job=fluentbit, $kubernetes['namespace_name']
Operational practices that matter more than the stack choice:
- Log structured JSON, not free-form text.
{"level":"error","msg":"...","order_id":123}is filterable;ERROR something broke (order 123)is not. - Propagate a correlation/request ID through every service so a single user request can be reassembled from logs across pods.
- Set retention and sampling. Logs are the most expensive pillar. Keep verbose
debuglogs for hours,errorlogs for weeks, and drop or sample the highest-volume noise at the collector. - Strip PII at the edge. Use a collector filter to redact emails, tokens, and card numbers before they land in long-term storage.
Distributed Tracing
Metrics say latency is high; logs say each service looks fine in isolation. A distributed trace stitches together the single user request as it fans out across services, so you can see where the time actually went.
The vocabulary:
- A trace is one end-to-end request, identified by a
trace_id. - A span is one unit of work within it (an HTTP handler, a DB query), with a start time, duration, and parent span.
- Context propagation carries the
trace_id/span_idbetween services, conventionally in the W3CtraceparentHTTP header, so the next hop knows it is part of the same trace.
flowchart LR
A[api-gateway<br/>span] --> B[orders-svc<br/>span]
B --> C[postgres<br/>span]
B --> D[payments-svc<br/>span]
D --> E[stripe call<br/>span]
OpenTelemetry (OTel) is the vendor-neutral standard that now underpins this space. Applications are instrumented once with the OTel SDK (or auto-instrumentation agents), emit spans over OTLP, and an OpenTelemetry Collector — typically a Deployment, plus an optional per-node DaemonSet — receives, batches, samples, and exports to whichever backend you choose. Because OTel decouples instrumentation from the backend, you can swap Jaeger for Tempo without touching application code.
| Backend | Notes |
|---|---|
| Jaeger | The CNCF reference tracing backend; rich UI, mature |
| Grafana Tempo | Cheap object-storage backend, integrates trace→log→metric pivots in Grafana |
| Zipkin | Older, lightweight, still widely supported |
A trimmed OTel Collector config showing the receive → process → export pipeline:
receivers:
otlp:
protocols:
grpc: # apps push spans here on :4317
processors:
batch: {}
tail_sampling: # keep all errors + slow traces, sample the rest
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
exporters:
otlp/jaeger:
endpoint: jaeger-collector.monitoring.svc:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [tail_sampling, batch]
exporters: [otlp/jaeger]
Sampling is the key operational lever: tracing every request at scale is prohibitively expensive, so you sample. Head sampling decides at the start (simple, but may discard the rare failing request); tail sampling (above) buffers the whole trace and keeps it only if it errored or was slow — far more useful for debugging, at the cost of collector memory.
Dashboards and Alerting
Collecting telemetry is worthless if no one looks at it. The last mile is dashboards (for humans investigating) and alerts (for machines waking humans up).
Grafana is the de-facto dashboarding layer; it queries Prometheus (metrics), Loki (logs), and Tempo/Jaeger (traces) as data sources in one pane, enabling the trace→log→metric pivots described above. Define dashboards as code (JSON or the Grafana Operator’s GrafanaDashboard CRD) so they live in version control alongside the app.
Alerting in the Prometheus world is a two-stage split:
- Prometheus evaluates
PrometheusRuleexpressions and fires an alert when a condition holds for a duration. - Alertmanager routes firing alerts — deduplicating, grouping, silencing, and dispatching to PagerDuty, Slack, email, etc.
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-slo-rules
labels:
release: monitoring
spec:
groups:
- name: availability
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
for: 10m # must hold 10 min to avoid flapping
labels:
severity: page
annotations:
summary: "5xx error rate above 5% for {{ $labels.service }}"
Alert on symptoms, not causes. Page on what users feel — error rate, latency SLO burn, request failures — not on every transient CPU spike. The most effective approach ties alerts to SLOs: define a target (e.g. 99.9% of requests succeed), then alert on the error budget burn rate, which fires fast for catastrophic outages and slowly for gradual degradation while suppressing the noise that causes alert fatigue. (SLO theory is developed further in the Observability hub.)
Helm: Kubernetes Package Manager
Managing dozens of YAML files for a single application becomes unwieldy. Helm solves this by packaging related resources into charts that can be versioned, shared, and customized.
When to use Helm:
- Deploying complex applications with many resources
- Sharing application configurations across teams
- Managing different configurations for different environments
- Installing third-party applications (databases, monitoring tools)
Core Concepts
| Concept | Description |
|---|---|
| Chart | Package of Kubernetes resources |
| Release | An installed instance of a chart |
| Values | Configuration that customizes a chart |
| Repository | Collection of charts |
Chart Structure
mychart/
├── Chart.yaml # Metadata (name, version)
├── values.yaml # Default configuration
├── templates/ # Kubernetes manifests with templating
│ ├── deployment.yaml
│ ├── service.yaml
│ └── _helpers.tpl
└── charts/ # Dependencies
Common Helm Commands
# Install a chart
helm install myrelease ./mychart
# Install with custom values
helm install myrelease ./mychart -f production-values.yaml
# Preview what would be installed
helm install myrelease ./mychart --dry-run
# Upgrade an existing release
helm upgrade myrelease ./mychart
# Rollback to previous version
helm rollback myrelease 1
# List installed releases
helm list
# Uninstall a release
helm uninstall myrelease
Using Public Charts
# Add a chart repository
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Search for charts
helm search repo postgresql
# Install from repository
helm install mydb bitnami/postgresql -f values.yaml
Helm Best Practices
| Practice | Benefit |
|---|---|
Use --dry-run before install |
Catch errors early |
| Keep values in version control | Track configuration changes |
| Use separate values files per environment | Clean separation of concerns |
Run helm lint before commits |
Validate chart syntax |
Common Patterns: Proven Architectural Approaches
These patterns appear repeatedly in successful Kubernetes deployments. Understanding when to use each helps you design better systems.
Multi-Container Pod Patterns
| Pattern | Purpose | Example Use Case |
|---|---|---|
| Sidecar | Extend/enhance main container | Log forwarding, service mesh proxy |
| Ambassador | Proxy outbound connections | Database proxy, API gateway |
| Adapter | Standardize output format | Convert logs to Prometheus metrics |
| Init Container | Run setup before main container | Database migrations, wait for dependencies |
Sidecar Pattern
A helper container runs alongside your application, sharing storage or network:
spec:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-forwarder
image: fluentbit:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}
Common sidecars: Logging agents, service mesh proxies (Envoy), security agents.
Init Containers
Init containers run to completion before the main container starts:
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
- name: migrate
image: myapp:latest
command: ['./migrate.sh']
containers:
- name: app
image: myapp:latest
Common uses: Database migrations, waiting for dependencies, fetching configuration.
Troubleshooting: When Things Go Wrong
When something breaks, a systematic approach saves time. Start broad, then narrow down. The flowchart below maps a pod’s reported status to the command that explains it and the most likely root cause:
flowchart TD
Start([Pod not healthy]) --> Status{Pod status?}
Status -->|ImagePullBackOff| IP["describe pod →<br/>wrong image / missing<br/>registry credentials"]
Status -->|CrashLoopBackOff| CL["logs --previous →<br/>app crash, OOM,<br/>or bad config"]
Status -->|Pending| PD["describe pod →<br/>no resources or<br/>PVC unbound"]
Status -->|Running but no traffic| SVC["get endpoints →<br/>selector does not<br/>match pod labels"]
Quick Diagnostic Commands
# What is unhealthy?
kubectl get pods --all-namespaces | grep -v Running
kubectl get events --sort-by='.lastTimestamp' -A
# Why is this pod unhealthy?
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous
Common Issues Quick Reference
| Issue | Symptom | First Command | Likely Cause |
|---|---|---|---|
| ImagePullBackOff | Pod stuck pulling image | kubectl describe pod <name> |
Wrong image name, missing credentials |
| CrashLoopBackOff | Pod keeps restarting | kubectl logs <pod> --previous |
App crash, OOM, bad config |
| Pending | Pod not scheduling | kubectl describe pod <name> |
Insufficient resources, no matching nodes |
| OOMKilled | Container killed | kubectl describe pod <name> |
Memory limit too low |
ImagePullBackOff
The image cannot be pulled. Check:
- Is the image name correct?
- Does the registry require authentication?
# Create registry credentials
kubectl create secret docker-registry regcred \
--docker-server=<registry> \
--docker-username=<user> \
--docker-password=<pass>
CrashLoopBackOff
The container starts but crashes. Debug with:
kubectl logs <pod-name> --previous
kubectl describe pod <pod-name>
Common fixes:
- Increase memory limits if OOMKilled
- Extend
initialDelaySecondson probes if app needs time to start - Check environment variables and config
Pending Pods
Pod cannot be scheduled. Check events in:
kubectl describe pod <pod-name>
Common causes:
- Insufficient resources: Scale down other workloads or add nodes
- Node selector/affinity: No matching nodes exist
- PVC pending: Storage class or capacity issue
Service Not Reachable
Debug network issues:
# Check if service has endpoints
kubectl get endpoints <service-name>
# Test from inside cluster
kubectl run debug --rm -it --image=busybox -- wget -O- <service>:<port>
If endpoints are empty, the service selector does not match any pod labels.
Certification Path
If you want to validate your Kubernetes skills, consider these certifications:
| Certification | Focus | Prerequisites |
|---|---|---|
| CKA | Cluster administration, troubleshooting | None |
| CKAD | Application development, configuration | None |
| CKS | Security hardening, runtime security | CKA required |
All exams are hands-on, performance-based tests where you solve real Kubernetes problems in a live environment.
Key Takeaways
- kubectl is the workhorse. Master
get,describe,logs, andexec— they answer most “what is happening?” questions. Contexts and namespaces keep you pointed at the right cluster. - Helm packages complexity. Charts turn dozens of manifests into one versioned, parameterized unit; use
--dry-runand per-environment values files. - Diagnose systematically. Start broad (
get pods,get events), then narrow withdescribeandlogs --previous. The pod status (ImagePullBackOff, CrashLoopBackOff, Pending) tells you where to look first. - Production is a checklist. Resource limits, 3+ replicas with anti-affinity, RBAC, health probes, and centralized logs are non-negotiable before going live.
The key to Kubernetes mastery is practice. Start with simple deployments, gradually add complexity, and always follow the principle of declarative configuration: describe what you want, and let Kubernetes make it happen.
See Also
- Fundamentals - Pods, Deployments, Services, and cluster architecture
- Workloads & Storage - StatefulSets, persistent volumes, RBAC, and autoscaling
- Advanced Topics - CRDs, Operators, service mesh, GitOps, and certifications
- Observability - The three pillars in depth: metrics & PromQL, logging stacks, OpenTelemetry tracing, and SLOs
- Docker Essentials - Quick container command reference
- CI/CD - Automating deployments into your cluster