πŸ”¬ Deep Dive β€” What Every Component Stores, Where, and Why

Click any component below. Each section shows the real data structure, storage location, flow diagram, and what breaks without it.

πŸ”
Secret

What it stores Β· where Β· how it flows

A Secret stores sensitive data β€” passwords, tokens, TLS certs, SSH keys. It lives as an object in etcd (base64 encoded by default). Pods consume it as env vars or volume-mounted files.

What's inside a Secret

apiVersion:v1K8s API version
kind:Secretobject type
metadata.name:db-credsreference name
metadata.namespace:prodisolation scope
type:Opaquegeneric secret (vs kubernetes.io/tls)
data.password:cEBzc3cwcmQ=⚠ base64, NOT encrypted
data.username:YWRtaW4=⚠ base64 of "admin"
$ kubectl create secret generic db-creds \
  --from-literal=password=p@ssw0rd \
  --from-literal=username=admin -n prod
secret/db-creds created

$ kubectl get secret db-creds -n prod -o yaml
data:
  password: cEBzc3cwcmQ=
  username: YWRtaW4=

$ echo "cEBzc3cwcmQ=" | base64 -d
p@ssw0rd    ← decoded in 0.01 seconds!

Where it lives β€” storage journey

kubectl create
β†’
API Server
β†’
etcd
etcd storage path
etcd key-value store /registry/secrets/prod/db-creds β†’ {"data":{"password":"cEBzc3cwcmQ="},"type":"Opaque",...} With KMS encryption: same path β†’ encrypted bytes (unreadable without KMS key) etcd volume: /var/lib/etcd/member/snap/db (EKS: managed by AWS across 3 AZs)
base64 is ENCODING not ENCRYPTION. Anyone with kubectl access can decode it in one command. Fix: aws eks create-cluster --secrets-encryption-config + KMS key.

How a Pod consumes a Secret β€” Method 1: Environment Variable

# In Pod spec:
env:
  - name: DB_PASSWORD          # env var name in container
    valueFrom:
      secretKeyRef:
        name: db-creds          # Secret object name
        key: password           # key inside the Secret

$ kubectl exec -it nginx -n prod -- env | grep DB
DB_PASSWORD=p@ssw0rd  ← visible in process environment!
# Problem: visible in kubectl describe pod, process list, crash dumps
Env var method leaks the secret value into: kubectl describe pod output, ps aux on the node, application crash reports, and CI/CD logs that print all env vars.

How a Pod consumes a Secret β€” Method 2: Volume Mount (safer)

# In Pod spec:
volumes:
  - name: creds-vol
    secret:
      secretName: db-creds     # which Secret to mount
volumeMounts:
  - name: creds-vol
    mountPath: /etc/creds      # directory in container
    readOnly: true             # always set readOnly!

$ kubectl exec -it nginx -n prod -- cat /etc/creds/password
p@ssw0rd
# File is in-memory tmpfs β€” never written to disk
# Not visible in kubectl describe, process list, or logs
# Auto-updated when Secret rotates (with ASCP)
Volume mount is safer: secret not in env, not in describe output, mounted as tmpfs (memory only, never to node disk), and can auto-rotate with AWS Secrets Manager + ASCP.

Secret types β€” not all Secrets are Opaque

Opaque

Generic secrets. Arbitrary key-value pairs. What you've been using. DB passwords, API keys, tokens.

kubernetes.io/tls

TLS cert + private key. Used by Ingress controllers. Fields: tls.crt and tls.key.

kubernetes.io/dockerconfigjson

Docker registry credentials. Used as imagePullSecrets so pods can pull from private ECR/registries.

πŸ“‹
ConfigMap

What it stores Β· where Β· how it flows

A ConfigMap stores non-sensitive configuration: feature flags, database hostnames, log levels, entire config files. Same image, different ConfigMap = different environment. Stored in etcd in plaintext.

What's inside a ConfigMap

kind:ConfigMap
metadata.name:app-config
data.DB_HOST:postgres.prod.svc.cluster.local
data.LOG_LEVEL:info
data.MAX_RETRIES:3
data.nginx.conf:server { listen 80; ... }entire file as value
$ kubectl create configmap app-config \
  --from-literal=DB_HOST=postgres.prod \
  --from-literal=LOG_LEVEL=info \
  --from-file=nginx.conf=./nginx.conf -n prod
configmap/app-config created

ConfigMap vs Secret β€” when to use each

ConfigMap stored in etcd β€” plaintext βœ“ Database hostnames βœ“ Feature flags (true/false) βœ“ Log levels (debug/info) βœ“ Config files (nginx.conf) βœ“ Environment names (prod) β†’ Safe to be seen. Not sensitive. Secret stored in etcd β€” base64 (add KMS!) βœ“ Passwords βœ“ API keys / tokens βœ“ TLS certificates βœ“ SSH private keys βœ“ Docker registry passwords β†’ Must be protected. Sensitive.
πŸ«›
Pod

What's inside a Pod Β· how containers share it

A Pod is the smallest deployable unit. It wraps one or more containers that share a network namespace (same IP, same localhost) and optional shared volumes. The Pod spec is what you write; Kubernetes creates the actual containers from it.

What's inside a Pod spec

spec.containers[]list of containers to run
.name:nginxcontainer name inside pod
.image:nginx:1.25Docker image to pull
.ports[]:containerPort: 80documentation only (not a firewall)
.resources.requests:cpu:250m mem:256Mischeduler uses this
.resources.limits:cpu:500m mem:512Miruntime enforces this
.env[]:DB_HOST=postgresenvironment variables
.volumeMounts[]:mountPath: /etc/credsmount point in container
spec.volumes[]:secret: db-credswhat to mount
spec.nodeName:node-01set by scheduler after creation
status.podIP:10.244.0.5assigned by VPC CNI on EKS
status.phase:RunningPending→Running→Succeeded/Failed

How containers share a Pod

Pod (IP: 10.244.0.5) Shared network namespace β€” same IP (10.244.0.5), same eth0, same loopback (localhost) Container A: nginx listens on localhost:80 image: nginx:1.25 cpu: 250m / 500m Container B: log-agent reads localhost:80/metrics sidecar pattern cpu: 50m / 100m localhost Shared volume: /var/log mounted in BOTH containers (emptyDir or PVC)
Analogy: A pod is a shared apartment. Each container is a roommate. They share the same front door (IP address), the same phone number (localhost), and can share storage (volumes). But each has their own CPU/memory budget (resource limits).

Pod lifecycle β€” the exact state machine

Pending waiting for node ContainerCreating pulling image, starting Running all containers live Succeeded Job completed Failed container exit≠0 Terminating graceful shutdown (30s)
πŸ”—
Service

What it stores Β· how traffic flows Β· 4 types

A Service is a stable virtual endpoint in front of dynamic pods. It stores: a virtual IP (ClusterIP), a label selector (which pods to route to), and port mappings. kube-proxy on every node reads this and writes iptables rules.

What's inside a Service

spec.type:ClusterIPor NodePort/LoadBalancer/ExternalName
spec.selector:app: nginxmatches pods with this label
spec.ports[0].port:80what callers connect to
spec.ports[0].targetPort:8080what pod actually listens on
spec.clusterIP:10.96.100.10assigned by K8s, never changes
status.loadBalancer:a1b2.elb.amazonaws.comAWS NLB DNS (LB type only)
$ kubectl apply -f svc.yaml
service/nginx-svc created

$ kubectl describe svc nginx-svc -n prod
Selector:   app=nginx
ClusterIP:  10.96.100.10
Endpoints:  10.244.0.5:80, 10.244.1.3:80
            ↑ these are the actual pod IPs behind the service

How traffic flows through a Service

Caller Pod curl nginx-svc:80 CoreDNS nginx-svc.prod β†’ 10.96.100.10 kube-proxy iptables rules on node 10.96.100.10:80 β†’ random healthy pod pod: 10.244.0.5 pod: 10.244.1.3 pod: 10.244.2.1 Pod restarts with new IP β†’ kube-proxy updates iptables β†’ Service DNS unchanged
πŸ“¦
Deployment

What it stores Β· rolling update mechanics Β· rollback

A Deployment stores the desired state for a stateless application: image, replicas, update strategy. It creates ReplicaSets which create Pods. The deployment history lets you instantly roll back to any previous version.

What's inside a Deployment

spec.replicas:6desired pod count
spec.selector:app: webappwhich pods this manages
spec.template:pod spec herewhat each pod looks like
strategy.type:RollingUpdateor Recreate
strategy.maxSurge:1extra pods during update
strategy.maxUnavailable:00 = zero downtime
status.readyReplicas:6currently healthy pods
status.conditions:Available=True

Rolling update β€” exactly what happens step by step

BEFORE: 3 pods on v1 v1-abc v1-def v1-ghi STEP 1: +1 v2 pod (maxSurge=1) v1-abc v1-def v1-ghi v2-zzz STEP 2: remove 1 v1 (maxUnavail=0 β†’ wait for v2 ready first) v1-abc term v1-def v1-ghi v2-zzz βœ“ AFTER: all on v2, zero downtime v2-aaa v2-bbb v2-ccc Service always routes to READY pods only β€” no traffic to terminating or ContainerCreating pods kubectl rollout undo deploy/webapp β†’ instantly reverts to previous ReplicaSet (v1 still exists)
πŸ—„
StatefulSet

Stable identity Β· ordered ops Β· own PVC per pod

StatefulSets give each pod a permanent name, a permanent DNS hostname, and its own PersistentVolumeClaim. mysql-0 always gets PVC data-mysql-0. Even after a crash and restart, same pod name, same storage, same DNS hostname.

spec.serviceName:mysql-headlessREQUIRED headless service for DNS
spec.replicas:2mysql-0, mysql-1
spec.podManagementPolicy:OrderedReadystart 0, then 1, then 2...
volumeClaimTemplates:data: 10Gi RWOcreates PVC per pod automatically
DNS: mysql-0.mysql-headless.prod.svc.cluster.local
$ kubectl get pvc -n prod
data-mysql-0  Bound  10Gi  node-01  ← mysql-0 OWNS this
data-mysql-1  Bound  10Gi  node-02  ← mysql-1 OWNS this
# Delete mysql-0 pod β†’ new pod named mysql-0 binds to SAME PVC
# Data survives. Identity survives. Replication config survives.
Why StatefulSet needs a Headless Service
Headless Service (clusterIP: None) Does NOT get a ClusterIP. Instead creates DNS A records pointing directly to each pod IP. mysql-0 mysql-0.mysql-headless.prod β†’ 10.244.0.10 (direct) mysql-1 mysql-1.mysql-headless.prod β†’ 10.244.1.5 (direct) mysql replica can ping "mysql-0.mysql-headless" to find primary β€” stable forever
πŸ‘
DaemonSet

One pod per node Β· automatic Β· node-level agents

A DaemonSet guarantees exactly one copy of a pod runs on every node in the cluster. When a new node joins, the DaemonSet controller automatically places a pod there. When a node leaves, the pod is garbage-collected.

spec.selector:app: fluent-bit
spec.template.spec.volumes:hostPath: /var/logreads node log files
spec.tolerations:NoSchedule tolerationcan run on tainted nodes
$ kubectl get ds -n kube-system
NAME         DESIRED  CURRENT  READY  NODE-SELECTOR
fluent-bit   2        2        2      <none>
# DESIRED = number of nodes in cluster
# Scales automatically with node count

$ kubectl get pods -n kube-system -o wide | grep fluent
fluent-bit-n1   Running   node-01
fluent-bit-n2   Running   node-02
NOT supported on AWS Fargate. Fargate has no node for the agent to run on. Use EC2 node groups for anything that needs DaemonSets: logging (Fluent Bit), metrics (node-exporter), networking (aws-node VPC CNI).

Why each agent MUST run on every node

node-01 fluent-bit DaemonSet pod reads /var/log/containers/* on THIS node app-pod-1 mysql-0 logs collected βœ“ node-02 fluent-bit DaemonSet pod reads /var/log/containers/* on THIS node webapp-v2-ccc mysql-1 logs collected βœ“

Fluent Bit reads log files from the node's own filesystem. If you ran it as a Deployment with 2 replicas, the scheduler might put both on node-01 β€” node-02's logs would be completely missed.

πŸ–₯
Node

What a node reports Β· capacity vs allocatable Β· conditions

A Node object in Kubernetes represents a worker machine (EC2 on EKS). It reports its capacity, allocatable resources, conditions (Ready/Memory/DiskPressure), and lists all pods running on it.

status.capacity.cpu:2total physical cores
status.capacity.memory:3967Mitotal physical RAM
status.allocatable.cpu:1900mavailable for pods (minus system)
status.allocatable.memory:3467Miavailable for pods (minus system)
conditions[Ready]:Truenode healthy and accepting pods
conditions[MemoryPressure]:Falsenot low on RAM
conditions[DiskPressure]:Falsenot low on disk
spec.taints:[]empty = accepts all pods
labels:topology.kubernetes.io/zone=us-east-1ascheduler uses for spreading
$ kubectl describe node node-01
Capacity:    cpu: 2       memory: 3967Mi
Allocatable: cpu: 1900m   memory: 3467Mi
Allocated resources:
  cpu: 1000m/1900m (52%)   ← sum of all pod requests
  memory: 1200Mi/3467Mi (34%)
# Scheduler uses Allocatable, not Capacity
# Remaining allocatable = how many more pods can fit

Capacity vs Allocatable β€” the 100m gap matters

Total Capacity: 2000m CPU 100m reserved: kube-system pods 50m reserved: kubelet eviction threshold Allocatable: 1900m available for YOUR pods scheduler checks this before placing pod Allocatable used: 1000m nginx: 250m requested webapp-v2-bbb: 250m webapp-v2-zzz: 250m mysql-0: 250m 900m remaining β†’ more pods can fit
🧠
Control Plane

api-server Β· etcd Β· scheduler Β· controller-manager β€” complete data flow

The control plane processes every kubectl command. Here is the exact sequence of what happens when you run kubectl apply -f deploy.yaml β€” across all four components.

Complete flow: kubectl apply β†’ running pods

β‘  kubectl sends YAML to API server β‘‘ API Server authenticates + validates YAML β‘’ etcd persists Deployment object to disk β‘£ Deployment Controller watches etcd for new Deployment β†’ creates RS β‘€ ReplicaSet Controller creates N pending Pod objects in etcd β‘₯ Scheduler watches for unscheduled pods β†’ picks node ⑦ kubelet on chosen node watches for pods assigned to it β‘§ containerd pulls image from ECR starts container ⑨ kubelet updates pod status β†’ Running in etcd β‘© Pod Running kubectl get pods shows Running βœ“ Total time: ~5 seconds for small images Β· ~60 seconds for large images Everything goes through etcd. Every state change is an etcd write. etcd = source of truth. On EKS: steps β‘ -⑨ all happen β€” only etcd and API server are AWS-managed. kubelet on your EC2.

api-server β€” the gatekeeper

# What it checks on every request:
1. Authentication
   "Are you who you say?"
   (IAM token on EKS)
2. Authorization (RBAC)
   "Can this identity do this?"
   (kubectl apply needs create)
3. Admission Control
   "Does this object meet policy?"
   (resource limits set?)
4. Validation
   "Is the YAML schema correct?"
   (image field not empty?)

etcd β€” what it actually stores

# etcd key structure:
/registry/pods/prod/nginx
β†’ full Pod spec + status JSON
/registry/services/prod/nginx-svc
β†’ Service spec + ClusterIP
/registry/deployments/prod/webapp
β†’ Deployment spec + history
/registry/secrets/prod/db-creds
β†’ base64 data (add KMS!)
/registry/nodes/node-01
β†’ node capacity + conditions

scheduler β€” watch β†’ decide β†’ write

# Scheduler algorithm:
1. Watch etcd for pods where
   spec.nodeName == ""
2. Run predicates (filter)
   - ResourceFit (cpu+mem)
   - NodeReady condition
   - Taints/tolerations
   - Node/pod affinity
3. Score survivors
   - Spread (prefer empty)
   - Least waste
   - Zone balance
4. Write nodeName to etcd
   β†’ kubelet sees it and acts