Skip to content

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:

kubectl run sleep-pod --image=nginx --command -- sleep 3600

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:

kubectl run nginx --image=nginx

Declarative — write a YAML file describing the desired state, apply it:

kubectl apply -f pod.yaml

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

kubectl run <pod-name> --image=<image>

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

kubectl run nginx --image=nginx --restart=Never --dry-run=client -o 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

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

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":

kubectl run job-pod --image=busybox --restart=Never -- /bin/sh -c "echo hello"

"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":

kubectl run nginx --image=nginx --restart=Never -n my-namespace

"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":

kubectl run nginx --image=nginx --restart=Never
kubectl get pod nginx                            # check STATUS = Running
kubectl describe pod nginx                       # if not Running, check Events section