Creating Pods — Full Reference¶
Reference: https://kubernetes.io/docs/concepts/workloads/pods/
Exam Priority — Quickest Method First¶
CKA pod questions are often 2-4% weighted. For anything simple, one line wins:
Done. No YAML, no editor, no dry-run. This is always the first approach — imperative command.
When to stay imperative (one-liner):
- Create a pod with an image → kubectl run
- Override the command → kubectl run ... --command -- <cmd>
- Set env vars → kubectl run ... --env="KEY=VALUE"
- Set resource limits → kubectl run ... --requests=... --limits=...
- Specific namespace → kubectl run ... -n <ns>
When to drop to YAML (dry-run + edit):
- Probes (readiness/liveness) — kubectl run can't set these
- Volume mounts — not possible imperatively
- Init containers — not possible imperatively
- envFrom (inject whole secret/configmap) — not possible imperatively
- Multiple containers — not possible imperatively
- securityContext, tolerations, nodeSelector — not possible imperatively
Pattern when YAML is needed:
kubectl run nginx --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml
# edit only what you need
kubectl apply -f pod.yaml
Never start from a blank YAML in the exam. Always generate the scaffold with dry-run and only edit the specific fields the question asks for.
Two Ways to Create a Pod¶
Imperative — tell Kubernetes what to do right now via a command:
Declarative — write a YAML file describing the desired state, apply it:
For the CKA exam, imperative is faster for simple pods. For anything complex (probes, volumes, init containers), generate YAML first with --dry-run=client -o yaml, edit it, then apply.
kubectl run — Imperative Pod Creation¶
All Useful Flags¶
kubectl run nginx --image=nginx # basic pod
kubectl run nginx --image=nginx --port=80 # expose container port (metadata only — doesn't create a Service)
kubectl run nginx --image=nginx -n my-namespace # in specific namespace
kubectl run nginx --image=nginx --env="KEY=VALUE" # set env var
kubectl run nginx --image=nginx --labels="app=web,env=prod" # set labels
kubectl run nginx --image=nginx --restart=Never # create a bare Pod (not wrapped in a Deployment)
kubectl run nginx --image=nginx --restart=OnFailure # creates a Job
kubectl run nginx --image=nginx --restart=Always # default — creates a Deployment in older k8s; Pod in newer
kubectl run nginx --image=nginx --requests="cpu=100m,memory=64Mi"
kubectl run nginx --image=nginx --limits="cpu=500m,memory=128Mi"
kubectl run nginx --image=nginx --serviceaccount=my-sa # assign service account
kubectl run nginx --image=nginx --command -- /bin/sh -c "sleep 3600" # override entrypoint
--restart=Never is the key flag for CKA pods. Without it, kubectl run may create a Deployment instead of a bare Pod. For exam tasks that say "create a Pod called X", always add --restart=Never.
The Dry-Run Pattern — Your Fastest Path to YAML¶
Outputs a complete Pod YAML to stdout. No resource is created. Take that YAML, modify what you need, then apply:
kubectl run nginx --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml
# edit pod.yaml
kubectl apply -f pod.yaml
Using the exam alias: kubectl run nginx --image=nginx --restart=Never $do > pod.yaml
Minimal Pod YAML¶
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
That's the minimum. Everything else is optional.
Full Pod Spec — Every Important Field¶
apiVersion: v1
kind: Pod
metadata:
name: my-pod
namespace: default
labels:
app: my-app
env: production
annotations:
description: "example pod"
spec:
# ── RESTART POLICY ─────────────────────────────────────────────────
restartPolicy: Always # Always (default) | OnFailure | Never
# Always = restart regardless of exit code
# OnFailure = restart only if exit code != 0
# Never = never restart (use for one-shot tasks)
# ── SERVICE ACCOUNT ────────────────────────────────────────────────
serviceAccountName: my-sa # identity for API access from within the pod
# ── NODE PLACEMENT ─────────────────────────────────────────────────
nodeSelector:
disktype: ssd # schedule only on nodes with this label
# ── INIT CONTAINERS ────────────────────────────────────────────────
initContainers:
- name: init-wait
image: busybox
command: ['sh', '-c', 'until nslookup my-service; do echo waiting; sleep 2; done']
# Runs to completion BEFORE main containers start
# If init container fails, pod restarts and tries again
# All init containers must succeed before main containers start
# ── MAIN CONTAINERS ────────────────────────────────────────────────
containers:
- name: app
image: nginx:1.25
imagePullPolicy: IfNotPresent # Always | IfNotPresent | Never
# ── COMMAND / ARGS ─────────────────────────────────────────────
command: ["/bin/sh"] # overrides Docker ENTRYPOINT
args: ["-c", "echo hello && sleep 3600"] # overrides Docker CMD
# ── PORTS ──────────────────────────────────────────────────────
ports:
- containerPort: 80 # informational only — doesn't open/close anything
# ── ENV VARS ───────────────────────────────────────────────────
env:
- name: MY_VAR
value: "hello" # plain value
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret # secret name
key: password # key within the secret
- name: APP_CONFIG
valueFrom:
configMapKeyRef:
name: my-configmap
key: config-key
envFrom: # inject ALL keys from a secret/configmap
- secretRef:
name: db-secret
- configMapRef:
name: my-configmap
# ── RESOURCES ──────────────────────────────────────────────────
resources:
requests: # minimum guaranteed resources
cpu: "100m" # 100 millicores = 0.1 CPU core
memory: "128Mi"
limits: # hard ceiling — container killed if exceeded (memory) or throttled (cpu)
cpu: "500m"
memory: "256Mi"
# ── VOLUME MOUNTS ──────────────────────────────────────────────
volumeMounts:
- name: config-vol
mountPath: /etc/config # where to mount inside container
readOnly: true
- name: data-vol
mountPath: /data
# ── PROBES ─────────────────────────────────────────────────────
readinessProbe: # pod only receives traffic when this passes
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5 # wait before first check
periodSeconds: 10 # check every 10s
failureThreshold: 3 # fail 3 times before marking not-ready
livenessProbe: # restart container if this fails
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 15
periodSeconds: 20
startupProbe: # disables readiness/liveness until this passes
httpGet: # use for slow-starting apps
path: /healthz
port: 80
failureThreshold: 30
periodSeconds: 10
# ── SECURITY ───────────────────────────────────────────────────
securityContext:
runAsUser: 1000 # run container process as UID 1000
runAsNonRoot: true # refuse to start if image runs as root
readOnlyRootFilesystem: true # prevent writes to container filesystem
allowPrivilegeEscalation: false
# ── VOLUMES ──────────────────────────────────────────────────────
volumes:
- name: config-vol
configMap:
name: my-configmap # ConfigMap contents become files
- name: data-vol
persistentVolumeClaim:
claimName: my-pvc # PVC for persistent storage
- name: secret-vol
secret:
secretName: my-secret # each key becomes a file
- name: empty-dir
emptyDir: {} # temporary, lives as long as the pod
- name: host-path
hostPath:
path: /var/log # mount a path from the node filesystem
# ── TOLERATIONS ──────────────────────────────────────────────────
tolerations:
- key: "node-role"
operator: "Equal"
value: "gpu"
effect: "NoSchedule" # pod can schedule on nodes with this taint
command vs args — Docker ENTRYPOINT vs CMD¶
This trips people up constantly:
| Dockerfile | Pod spec |
|---|---|
ENTRYPOINT |
command |
CMD |
args |
# Run: /bin/sh -c "echo hello"
command: ["/bin/sh"]
args: ["-c", "echo hello"]
# Run: python app.py --port=8080
command: ["python"]
args: ["app.py", "--port=8080"]
If you only set args, it overrides CMD but keeps the original ENTRYPOINT. If you set command, it overrides both ENTRYPOINT and effectively ignores CMD unless you also set args.
imagePullPolicy¶
| Value | Behaviour |
|---|---|
Always |
Always pull from registry on pod start. Ensures you get the latest image. Slower. |
IfNotPresent |
Pull only if the image isn't already on the node. Faster. |
Never |
Never pull. Image must already be on the node. |
Default: IfNotPresent for tagged images (:1.25). Always if the tag is :latest or no tag.
Probes — readiness vs liveness vs startup¶
These are the three health check mechanisms. They're often confused:
| Probe | What it controls | On failure |
|---|---|---|
readinessProbe |
Whether the pod receives traffic from Services | Pod removed from Service endpoints (not restarted) |
livenessProbe |
Whether the container is alive | Container restarted |
startupProbe |
Whether the app has finished starting up | Disables readiness/liveness checks until it passes; restarts container if it never passes |
Analogy: - Readiness: "Is the shop open and ready for customers?" — No → don't send customers - Liveness: "Is the shop still alive?" — No → tear it down and rebuild - Startup: "Has the shop finished setting up before opening?" — Use this for apps that take 60+ seconds to start
Three probe types:
# HTTP GET — checks an HTTP endpoint
httpGet:
path: /healthz
port: 8080
# TCP Socket — checks if port is accepting connections
tcpSocket:
port: 8080
# Exec — runs a command inside the container; exit 0 = healthy
exec:
command:
- cat
- /tmp/healthy
Init Containers¶
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']
Init containers run before any main container starts. Rules: - They run sequentially (if multiple, one at a time in order) - All must exit 0 before main containers start - If one fails, the pod restarts and tries again from the beginning - They have separate images, commands, and mounts from main containers
Use cases: wait for a dependency to be ready, pre-populate a volume, fetch config from a remote source.
Multi-Container Pods¶
Multiple containers in one pod share:
- The same network namespace (they communicate via localhost)
- The same volumes (if mounted in both)
- The same lifecycle (they start and stop together)
spec:
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 8080
- name: log-collector
image: fluentd:latest
volumeMounts:
- name: log-dir
mountPath: /var/log/app
volumes:
- name: log-dir
emptyDir: {}
Sidecar pattern: One main container, one helper container that extends it (log shipper, proxy, config watcher). The most common multi-container pattern.
Ambassador pattern: Sidecar acts as a proxy — all network traffic from the main container goes through it. Common for service mesh (Envoy/Istio).
Resources — requests vs limits¶
Requests = what the scheduler uses to find a node with enough space. The node must have at least this much free. The container is guaranteed this amount.
Limits = hard ceiling. - CPU: container gets throttled if it exceeds the limit. It doesn't die. - Memory: container is killed with OOMKilled if it exceeds the limit. This is why OOMKilled happens — no throttling for memory, just kill.
No limits set = container can use all available resources on the node. A memory leak kills the whole node.
CPU units:
- 1 = one full CPU core
- 500m = 500 millicores = 0.5 cores
- 100m = 100 millicores = 0.1 cores
Useful Pod Commands¶
# Create
kubectl run nginx --image=nginx --restart=Never
kubectl apply -f pod.yaml
# Get / inspect
kubectl get pods
kubectl get pods -A # all namespaces
kubectl get pod nginx -o yaml # full spec
kubectl describe pod nginx # events, state, conditions — best for debugging
# Exec into a running pod
kubectl exec -it nginx -- /bin/sh
kubectl exec -it nginx -c sidecar -- /bin/bash # specific container in multi-container pod
# Copy files
kubectl cp nginx:/etc/nginx/nginx.conf ./nginx.conf # from pod to local
kubectl cp ./config.txt nginx:/tmp/config.txt # from local to pod
# Delete
kubectl delete pod nginx
kubectl delete pod nginx --force --grace-period=0 # instant kill (exam alias: $now)
# Generate YAML without creating
kubectl run nginx --image=nginx --restart=Never --dry-run=client -o yaml
# Port forward for testing
kubectl port-forward pod/nginx 8080:80 # localhost:8080 → pod:80
Common Exam Patterns¶
"Create a pod that runs a command and exits":
"Create a pod with env var from a secret":
kubectl run app --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml
# then edit pod.yaml to add env.valueFrom.secretKeyRef
"Create a pod in namespace X":
"Create a pod with cpu/memory limits":
kubectl run nginx --image=nginx --restart=Never \
--requests="cpu=100m,memory=64Mi" \
--limits="cpu=500m,memory=128Mi"
"Create a pod and check it's running":