Kubernetes Workload & Configuration Objects

Kubernetes Workload & Configuration Objects

Pods

The smallest deployable unit in Kubernetes — one or more tightly-coupled containers sharing network namespace and storage.

apiVersion: v1
kind: Pod
metadata:
  name: payment-app
  labels:
    app: payment-app
spec:
  containers:
    - name: payment-app
      image: myrepo/payment-app:1.2.0
      ports:
        - containerPort: 8080
      resources:
        requests:
          cpu: "200m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"
      livenessProbe:
        httpGet:
          path: /health/live
          port: 8080
        initialDelaySeconds: 10
        periodSeconds: 15
      readinessProbe:
        httpGet:
          path: /health/ready
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 10

Architect-level point: you almost never create bare Pods directly in production. A bare Pod has no self-healing — if its node dies, the pod is gone, permanently. Pods are managed indirectly through Deployments, StatefulSets, DaemonSets, or Jobs, which create and supervise them. Knowing the Pod spec matters because it’s embedded as the template inside every one of those higher-level objects.

ReplicaSets

Ensures a specified number of identical pod replicas are running at all times — the reconciliation-loop pattern applied to pod count.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: payment-app-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-app
  template:
    metadata:
      labels:
        app: payment-app
    spec:
      containers:
        - name: payment-app
          image: myrepo/payment-app:1.2.0

If a pod dies, the ReplicaSet controller notices the actual count (2) doesn’t match desired (3) and creates a replacement. You rarely create a ReplicaSet directly — it’s the object a Deployment manages on your behalf. Knowing it matters because rolling updates work by creating a new ReplicaSet and scaling it up while scaling the old one down — visible if you kubectl get rs during a deployment rollout.

Deployments

The standard way to run stateless applications — adds rolling updates, rollback, and revision history on top of ReplicaSets.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-app
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # extra pods allowed above desired count during rollout
      maxUnavailable: 0    # pods allowed to be unavailable during rollout
  selector:
    matchLabels:
      app: payment-app
  template:
    metadata:
      labels:
        app: payment-app
    spec:
      containers:
        - name: payment-app
          image: myrepo/payment-app:2.0.0

What a Deployment gives you that a ReplicaSet alone doesn’t:

  • kubectl rollout status deployment/payment-app — watch a rollout progress
  • kubectl rollout undo deployment/payment-app — instant rollback to the previous ReplicaSet
  • kubectl rollout history deployment/payment-app — revision history
  • Configurable rollout strategy: maxSurge/maxUnavailable control how aggressive vs cautious the rolling update is — maxUnavailable: 0 guarantees zero downtime at the cost of needing extra capacity headroom (maxSurge).

Architect note: Deployments assume pods are interchangeable and stateless — no pod has a “special identity.” That assumption is exactly what breaks for databases, which is why StatefulSets exist.

StatefulSets

For workloads that need stable network identity and stable, per-pod persistent storage — databases, Kafka brokers, Elasticsearch, ZooKeeper.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 20Gi

What makes this different from a Deployment:

  • Stable, predictable pod names: postgres-0, postgres-1, postgres-2 — not random suffixes. These names persist across restarts/rescheduling.
  • One PersistentVolumeClaim per pod, created from volumeClaimTemplates — postgres-0 always gets the same storage volume back, even if rescheduled to a different node.
  • Ordered, sequential creation/scaling/deletion: pod 0 starts before pod 1 starts before pod 2 — critical for clustered systems with leader-election or bootstrap ordering (e.g., the first ZooKeeper node).
  • Requires a headless Service (clusterIP: None) to give each pod its own stable DNS name: postgres-0.postgres-headless.namespace.svc.cluster.local.

Interview-level distinction: Deployment = “I don’t care which pod, just give me N healthy replicas.” StatefulSet = “I need pod 0, 1, 2 specifically, each with its own durable identity and storage, in a defined order.”

DaemonSets

Runs exactly one pod per node (or per matching subset of nodes) — for node-level infrastructure rather than application replicas.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-log-collector
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      containers:
        - name: fluentd
          image: fluent/fluentd:v1.16
          volumeMounts:
            - name: varlog
              mountPath: /var/log
      volumes:
        - name: varlog
          hostPath:
            path: /var/log

Common use cases: log collectors (Fluentd/Fluent Bit), node monitoring agents (Prometheus Node Exporter, Datadog agent), CNI/networking plugins, storage daemons. As nodes are added to the cluster, the DaemonSet controller automatically schedules a pod onto the new node — no manual intervention. Can be restricted to specific nodes via nodeSelector or tolerations (e.g., running only on GPU nodes).

Jobs & CronJobs

For run-to-completion workloads, not long-running services.

Job — runs a pod (or several) until successful completion, then stops:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 3            # retry up to 3 times on failure
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: myrepo/migration-tool:1.0
          command: ["./run-migration.sh"]

CronJob — schedules a Job to run on a recurring schedule, standard cron syntax:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
spec:
  schedule: "0 2 * * *"      # 2 AM daily
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report-generator
              image: myrepo/report-gen:1.0

Architect-level points:

  • backoffLimit controls retry attempts before the Job is marked failed.
  • parallelism and completions control fan-out for batch workloads (e.g., process 100 items, 10 pods at a time).
  • CronJob’s concurrencyPolicy (Allow/Forbid/Replace) matters in interviews — what happens if a scheduled run is still executing when the next one is due? Misconfiguring this is a real production bug class (duplicate report generation, overlapping migrations).

ConfigMaps

Externalizes non-sensitive configuration from container images, so the same image can run differently across environments.

apiVersion: v1
kind: ConfigMap
metadata:
  name: payment-app-config
data:
  LOG_LEVEL: "INFO"
  FEATURE_FLAG_NEW_CHECKOUT: "true"
  application.properties: |
    server.port=8080
    spring.datasource.max-pool-size=10

Consumed either as environment variables or mounted as files:

containers:
  - name: payment-app
    envFrom:
      - configMapRef:
          name: payment-app-config
    volumeMounts:
      - name: config-volume
        mountPath: /app/config
volumes:
  - name: config-volume
    configMap:
      name: payment-app-config

Key architectural point: volume-mounted ConfigMaps can update live (the file on disk changes when the ConfigMap is updated) without a pod restart — though the application must watch the file and reload; env-var-injected ConfigMaps require a pod restart to pick up changes, since env vars are fixed at container start.

Secrets

Structurally identical to ConfigMaps, but intended for sensitive data — API keys, passwords, TLS certs, tokens.

apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:
  username: cG9zdGdyZXM=    # base64-encoded, NOT encrypted
  password: c3VwZXJzZWNyZXQ=

Critical architect-level caveat: Secret values are base64-encoded, not encrypted, by default. Anyone with get secret RBAC permission (or etcd access) can trivially decode them. Real protection requires:

  • Encryption at rest enabled for etcd (EncryptionConfiguration on the API server) — not on by default in many setups.
  • RBAC restricting who can read Secrets — this is the actual security boundary, not the encoding.
  • For serious production use, integrating an external secrets manager (AWS Secrets Manager, HashiCorp Vault) via the External Secrets Operator, rather than relying on native Secrets as the source of truth.

Namespaces

A logical partition within a single cluster — scopes names, RBAC, resource quotas, and network policies.

apiVersion: v1
kind: Namespace
metadata:
  name: production
kubectl apply -f deployment.yaml -n production
kubectl get pods -n staging

What namespaces give you:

  • Name scoping — payment-app in staging and payment-app in production are entirely separate objects.
  • RBAC boundary — Roles and RoleBindings are namespace-scoped (vs ClusterRole/ClusterRoleBinding, which are cluster-wide).
  • ResourceQuota and LimitRange application — capping how much CPU/memory/object count a namespace (often = a team) can consume.
  • NetworkPolicy scoping — default-deny rules typically defined per namespace.

Architect-level caveat, frequently tested in interviews: namespaces are not a security boundary by themselves. Without RBAC and NetworkPolicy explicitly layered on top, any pod in any namespace can, by default, talk to any pod in any other namespace, and namespace isolation provides no compute/network isolation guarantee on its own — it’s organizational scoping, not a sandbox.