Cloud Engineer Prep
0 / 0 ready
Chapter 0

Start here: you are closer than you think

This role is an assist / help / basic level role (read the JD verbs). They are not hiring a senior architect. They want someone who understands the building blocks, follows a calm troubleshooting method, writes clear docs, and learns fast. Your FinOps work already gave you a lot of that.

What you already know → what it maps to

From your FinOps workCloud engineer skill it proves
Rightsizing EC2 / VMs, reading CPU & memory utilisationCompute sizing, monitoring metrics, autoscaling logic
Explaining NAT Gateway & data transfer chargesYou know how traffic flows: subnets, NAT, IGW, cross-AZ, internet egress
Storage tiers, EBS / snapshot cleanup, S3 lifecycleStorage provisioning and maintenance
Tagging policies, account / subscription structureGovernance, environment setup for dev teams
Budgets, cost anomaly alertsAlerting design: thresholds, noise, who gets paged
Working with engineering teams to change their resources"Bridge between infrastructure and development teams" (literally in the JD)
Reports, dashboards, recommendationsDocumentation, runbooks, clear communication

Your positioning (say it in your own words)

Your 45-second intro

"I've spent my career in Cloud FinOps, which means I look at cloud infrastructure every day from the cost and usage side: rightsizing compute, understanding network and data-transfer paths, cleaning up storage, and working with engineering teams to change their resources safely. I passed the AWS Cloud Practitioner exam and I've been building hands-on in my own labs: Docker images, a local Kubernetes cluster with Deployments and Services, and basic troubleshooting with kubectl, dig and curl. I want to move from advising engineers to building and operating with them. And I bring something most junior engineers don't: I always think about what a change will cost."

Honesty rule: never claim production experience you don't have. Say "I haven't done that in production yet. In my lab I did X, and here's how I would approach it in production…". Interviewers for junior roles respect a clear method far more than a bluff.

The S.C.O.P.E. method: one way to answer any troubleshooting question

Most of the scenario questions in this guide are the same question wearing different clothes. Use this method out loud, and you will sound structured even for a service you have never touched.

SScope the impactWho is affected? All users or some? Since when? How bad?
CCheck what changedDeploy, config, traffic, cert expiry, AWS health event?
OOutside → inFollow the request: DNS → network → firewall → LB → app → database
PProtect users firstRoll back, scale out, fail over. Fix the root cause later
EExplain & documentUpdate status, write the postmortem, update the runbook

Magic phrase: "First I'd confirm the scope and check what changed recently, because most incidents are caused by a change. Then I'd follow the request path from the outside in…"

How to use this guide

1 · Read the foundations
Chapters 2–11. One evening each. Look at the diagrams first.
2 · Learn the playbook
Chapter 12 is the one method behind most answers.
3 · Drill the questions
Chapter 13. Start with Must ones. Mark each one "I can answer this".
4 · Grill yourself
Chapter 14 hides the answer until you try. Say the answer out loud.
Chapter 1

JD → knowledge map

Every line of the job description, turned into "what to know", with a priority. Must = expect direct questions. Should = likely follow-up. Stretch = senior-level; know the idea only.

JD responsibilityWhat you must be able to explainGo toPriority
Provision & maintain compute and storageEC2 / VM sizing, EBS / Managed Disk, S3 / Blob, AMIs, tags, expanding a diskCh 2, Q C1–C8Must
Minor networking: NSG rules, DNS records, basic troubleshootingVPC/VNet, subnets & CIDR, route tables, SG vs NACL vs NSG, DNS record types, TTL, ping/traceroute/dig/curlCh 3, Q N1–N9Must
Provision environments for dev teamsTerraform basics, separate accounts / subscriptions / resource groups, tags, budgets, auto-shutdownCh 9, Q F2Should
Write & update K8s manifests, deploy workloadsPod, Deployment, Service, ConfigMap, Secret, Ingress YAML; kubectl apply; rollout & rollbackCh 6, Q K1–K2Must
Monitor & troubleshoot K8s clusters and workloadsPending / CrashLoopBackOff / ImagePullBackOff, describe, logs, events, Service endpointsCh 12, Q K3–K8Must
Deploy & configure services for dev teamsGit branches & PRs, CI/CD pipeline stages, Helm basics, environment variables & configCh 8, Q P1–P6Should
Monitor health, respond to alerts, help with incidentsMetrics / logs / traces, golden signals, CloudWatch & Azure Monitor, Prometheus & Grafana, incident stepsCh 7, Q O1–O6Must
Document configs, runbooks, proceduresRunbook structure, Markdown, "why" + "how" + "how to verify" + "how to roll back"Ch 11, Q B3Must (easy win)
Docker: build & containerise appsDockerfile, image vs container, layers, multi-stage, registry, exit codesCh 5, Q D1–D3Must
Linux CLItop, free, df, du, systemctl, journalctl, ss; reading logsCh 4, Q L1–L3Should
Security basicsIAM roles vs users, least privilege, secrets managers, never commit secretsCh 10, Q S1–S4Should
HA / DR / multi-region designMulti-AZ, RTO/RPO, the 4 DR strategiesQ H1–H5Stretch

Your study order: Networking → Kubernetes → Monitoring → Docker → Linux → CI/CD → Terraform → Security. Networking and Kubernetes together are about half of this JD.

Chapter 2 · Foundations

The big picture: one app, every piece

Almost every interview scenario happens somewhere in this picture. Learn it like a map: when someone says "502 from the ALB" or "private subnet can't reach the internet", point to the box in your head.

Route 53 DNS · Azure DNS Users browser / mobile ① "where is app.example.com?" VPC 10.0.0.0/16 Azure: VNet · one Region IGW internet gw Availability Zone A Availability Zone B Public subnet 10.0.1.0/24 Public subnet 10.0.2.0/24 route 0.0.0.0/0 → IGW Application Load Balancer Azure: Application Gateway NAT Gateway NAT Gateway Private app subnet 10.0.11.0/24 Private app subnet 10.0.12.0/24 route 0.0.0.0/0 → NAT Security Group Security Group EC2 / EKS nodes Azure: VM / AKS EC2 / EKS nodes Auto Scaling Group ② forward outbound via NAT Private DB subnet 10.0.21.0/24 Private DB subnet 10.0.22.0/24 no internet route RDS primary Azure SQL / PG RDS standby Multi-AZ copy ③ SQL :5432 sync replication

Orange = the user's request path. Dashed red boxes = Security Groups (Azure: NSGs). If AZ A fails, AZ B keeps serving: that's "high availability".

Read the picture in 5 sentences

  1. DNS (Route 53) turns app.example.com into the load balancer's address.
  2. The request enters the VPC through the Internet Gateway and hits the load balancer in the public subnets.
  3. The load balancer forwards to healthy servers or pods in the private subnets. Security Groups only allow traffic from the load balancer.
  4. The app talks to the database in its own private subnet. A standby copy in another AZ takes over if the primary fails.
  5. Private servers reach the internet (for updates and APIs) outbound only, through the NAT Gateway. Nobody can connect in through the NAT.

AWS ↔ Azure translation table

The JD says "NSG", which is Azure language. Keep this table open while you study.

ConceptAWSAzure
Top-level billing / isolation boundaryAccount (inside Organizations)Subscription (inside Management Groups); plus Resource Groups
Private networkVPCVirtual Network (VNet)
Instance-level firewallSecurity Group (allow-only, stateful)NSG on a NIC (allow + deny, stateful, priority-ordered)
Subnet-level firewallNetwork ACL (stateless)NSG on a subnet (still stateful)
Custom routesRoute tableRoute table / User-Defined Routes (UDR)
Outbound internet for private subnetsNAT GatewayNAT Gateway
Connect two networksVPC Peering, Transit GatewayVNet Peering, Virtual WAN / hub-spoke
DNSRoute 53Azure DNS (+ Traffic Manager / Front Door for global routing)
Layer 7 (HTTP) load balancerApplication Load Balancer (ALB)Application Gateway (regional), Front Door (global)
Layer 4 (TCP/UDP) load balancerNetwork Load Balancer (NLB)Azure Load Balancer
Virtual machinesEC2Virtual Machines
Auto scaling group of VMsAuto Scaling GroupVirtual Machine Scale Sets (VMSS)
Block diskEBSManaged Disks
Object storageS3Blob Storage (in a Storage Account)
Managed KubernetesEKSAKS
Container registryECRACR
Serverless functionsLambdaAzure Functions
Managed SQL databaseRDS / AuroraAzure SQL Database, Azure Database for PostgreSQL/MySQL
Identity for a machine/app (no keys)IAM Role (instance profile, IRSA / Pod Identity)Managed Identity (Workload Identity for AKS)
PermissionsIAM policiesAzure RBAC role assignments (Entra ID for identities)
Metrics, logs, alarmsCloudWatchAzure Monitor (Log Analytics, Application Insights)
Audit log of API callsCloudTrailActivity Log
SecretsSecrets Manager / SSM Parameter StoreKey Vault
Remote shell without opening SSHSSM Session ManagerAzure Bastion
Native IaCCloudFormation / CDKARM templates / Bicep
Cost tools (your home turf)Cost Explorer, CUR, Budgets, Cost Anomaly DetectionCost Management + Billing, Budgets, Advisor
Chapter 3 · Foundations

Networking & DNS

The JD explicitly mentions NSG rules, DNS records, subnets and routing. This is the chapter most likely to get detailed questions.

3.1 IP addresses and CIDR, the only maths you need

An IPv4 address has 32 bits. In 10.0.0.0/16, the /16 means "the first 16 bits are fixed (the network part)". The remaining bits are for hosts. Smaller number after the slash = bigger network.

CIDRAddressesUsable in AWS / AzureTypical use
/1665,53665,531Whole VPC / VNet
/204,0964,091Large subnet (e.g. EKS pods)
/24256251Normal subnet
/281611Smallest subnet AWS allows
/321n/aOne single IP, e.g. "allow only my laptop: 203.0.113.7/32"

Both AWS and Azure reserve 5 addresses in every subnet (network address, router, DNS, a reserved one, broadcast). Formula: addresses = 2(32 − prefix).

10.0.1.0/24 public A 10.0.2.0/24 public B 10.0.11.0/24 app A 10.0.12.0/24 app B …room for 250 more /24 subnets

Private IP ranges (never routed on the internet): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. Two networks that you want to connect must not overlap. That's why CIDR planning matters.

3.2 What makes a subnet "public" or "private"?

Public subnet

Its route table has 0.0.0.0/0 → Internet Gateway. Resources still need a public IP to be reachable. Put load balancers, NAT gateways and bastions here.

Private subnet

No route to the IGW. Outbound internet only via 0.0.0.0/0 → NAT Gateway. Put app servers, K8s nodes and databases here.

Key insight: "public" is not a checkbox. It is the route table. When something "can't reach the internet", the first thing to check is the route table of that subnet.

3.3 Firewalls: Security Group vs NACL vs NSG

AWS Security GroupAWS Network ACLAzure NSG
Attached toENI (instance, pod, LB, RDS)SubnetSubnet and/or NIC (both are evaluated)
Rule typesAllow onlyAllow and DenyAllow and Deny
Stateful?Yes: return traffic automatically allowedNo: you must allow return traffic (ephemeral ports 1024–65535)Yes
OrderAll rules evaluated, any allow winsLowest rule number first, first match winsPriority 100–4096, lowest number first, first match wins
DefaultDeny all in, allow all outDefault NACL allows allBuilt-in rules at 65000+: allow VNet, allow Azure LB, deny all inbound from the internet
Nice trickSource can be another SG ("allow from the app tier SG")Good for blocking a bad IP rangeService tags (e.g. Internet, AzureLoadBalancer) and Application Security Groups

NSG example, the kind of rule you'll update in this job: "Allow the app subnet to reach Postgres"
Priority 200 · Direction Inbound · Source 10.1.2.0/24 · Source port * · Destination 10.1.3.0/24 · Destination port 5432 · Protocol TCP · Action Allow.
If a rule with priority 150 says "Deny 5432 from any", it wins, because 150 < 200 and it's processed first.

3.4 DNS: how a name becomes an IP

Your laptopOS cache Recursive resolverISP / 8.8.8.8 / VPC .2 Root servers "." .com TLD servers AuthoritativeRoute 53 / Azure DNS "ask .com" "ask ns-123.awsdns…" "app.example.com = 52.1.2.3, cache it for TTL 300 s" The resolver caches the answer for the TTL. Changing a record takes effect only after old caches expire.
RecordWhat it doesExample
A / AAAAName → IPv4 / IPv6api.example.com A 52.1.2.3
CNAMEName → another name (not allowed at the zone apex example.com)www CNAME my-alb-123.elb.amazonaws.com
Alias AWSLike a CNAME but works at the apex, free queries, follows AWS resource IPsexample.com ALIAS → ALB (Azure: alias record set)
MXMail serversexample.com MX 10 mail.example.com
TXTText: domain verification, SPF"v=spf1 include:…"
NSWhich servers are authoritative for the zoneMust match at your registrar!
TTLHow long resolvers cache the answer (seconds)300 = 5 minutes

"It's always DNS." When the symptom is "works for some people, not others" or "works by IP, not by name", suspect DNS caching (TTL), wrong records, or a split between private and public zones.

3.5 The troubleshooting tools: what each one proves

ToolQuestion it answersExampleGotcha
dig / nslookupDoes the name resolve? To what?dig +short api.example.com
dig @8.8.8.8 api.example.com
Compare different resolvers to spot caching
pingIs the host reachable at the IP layer (ICMP)?ping -c 4 10.0.11.5Many clouds and SGs block ICMP. No ping ≠ host down
traceroute / mtrWhere along the path does it stop?traceroute api.example.com* * * hops are often just routers not replying
nc (netcat)Is this TCP port open?nc -zv db.internal 5432"timed out" = firewall dropping. "refused" = host reached, nothing listening
curlDoes the HTTP app answer? What status, headers, timing?curl -v https://api.example.com/health
curl -o /dev/null -s -w '%{http_code} %{time_total}\n' URL
-v shows DNS, TLS handshake and headers
ss / netstatWhat is listening on this machine?ss -tulpnApp listening on 127.0.0.1 can't be reached from outside; it needs 0.0.0.0

Timeout vs refused, the most useful clue in networking:
Connection timed out → packets are being dropped: Security Group / NSG / NACL / route / wrong IP.
Connection refused → you reached the host, but nothing listens on that port: app down, wrong port, listening on localhost.

Chapter 4 · Foundations

Linux basics

A server has four resources. Every "server is slow" question is about which one is exhausted.

CPU
top / htop, uptime (load average)
Load average higher than the CPU count = CPU queue. In top, %wa high = CPU is waiting on disk, not busy computing.
Memory
free -h, vmstat 1, dmesg -T | grep -i oom
Look at "available", not "free". Linux uses spare memory for cache, which is normal.
Disk
df -h (space), df -i (inodes), du -sh /var/* | sort -h, iostat -xz 1
Full disk = write failures. %util near 100 and high await = disk is the bottleneck.
Network
ss -tulpn, ss -s, ip a, ip route
Who's listening, how many connections, what IP and route.

Services and logs (systemd)

# is it running? why did it fail?
systemctl status nginx
systemctl --failed                 # list all failed units
sudo systemctl restart nginx
sudo systemctl enable --now nginx  # start now + on boot

# logs
journalctl -u nginx --since "30 min ago"
journalctl -u nginx -f             # follow live
tail -f /var/log/nginx/error.log

File permissions in 30 seconds

-rwxr-x--- = owner can read/write/execute, group can read/execute, others nothing. chmod 400 key.pem is required for SSH keys (owner read only). chown app:app file changes the owner.

Bash script survival kit

#!/usr/bin/env bash
set -euo pipefail          # stop on error, on unset variable, on failed pipe
NAME="${1:-default}"       # first argument, with a default
if curl -fsS "$URL" >/dev/null; then echo "up"; else echo "down"; fi
for svc in nginx docker; do systemctl is-active --quiet "$svc" || echo "$svc down"; done
Chapter 5 · Foundations

Docker & containers

A container is a process on a host with its own isolated filesystem, network and limits. An image is the read-only template the container starts from.

Dockerfilethe recipe
docker build→ image (layers)
docker push→ registry (ECR / ACR / Docker Hub)
docker run / K8s→ running container
# ---- stage 1: build ----
FROM node:20-alpine AS build      # ①
WORKDIR /app
COPY package*.json ./             # ②
RUN npm ci
COPY . .
RUN npm run build

# ---- stage 2: run ----
FROM node:20-alpine               # ③
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node                         # ④
EXPOSE 3000
CMD ["node", "dist/server.js"]    # ⑤
  1. Pin the base image (a version, not latest), so builds are repeatable.
  2. Copy the dependency files first. That layer is cached until package.json changes, so builds get much faster.
  3. Multi-stage: the final image contains only runtime files, without compilers or source. It's smaller, faster to pull, and has less to attack.
  4. Run as a non-root user. If the app is hacked, the attacker isn't root.
  5. One process per container. Logs go to stdout/stderr, so docker logs and Kubernetes can see them.

Layers: why order matters

FROM node:20-alpine (shared, cached)
COPY package*.json + RUN npm ci (cached unless deps change)
COPY . . (changes every commit → only this rebuilds)
RUN npm run build

Each instruction creates a layer. When one layer changes, every layer after it rebuilds. So put rarely-changing steps at the top. A secret added in one layer and deleted in a later layer is still inside the image.

Docker commands you will use

docker build -t myapp:1.0.3 .
docker run -d --name web -p 8080:3000 -e DB_HOST=db myapp:1.0.3
docker ps -a                     # include stopped containers
docker logs --tail 100 -f web
docker exec -it web sh           # shell inside
docker inspect web --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
docker stats                     # live CPU / memory per container
docker tag myapp:1.0.3 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/myapp:1.0.3
docker push 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/myapp:1.0.3

Exit codes: the container tells you why it died

CodeMeaningUsual cause
0Exited normallyThe process finished. Fine for a job, a problem for a web server (in K8s, check the command)
1App errorException, missing config or env var. Read the logs
126 / 127Not executable / command not foundWrong CMD/ENTRYPOINT, missing binary, wrong path
137Killed with SIGKILL (128+9)OOMKilled (memory limit), or force-killed
143SIGTERM (128+15)Asked to stop: normal during deploy or scale-down

Docker networking modes

  • bridge (default): private container network with NAT. -p 8080:3000 publishes a port. On a user-defined bridge, containers find each other by name.
  • host: shares the host's network directly. Slightly faster, but no isolation and possible port clashes.
  • none: no network at all.
Chapter 6 · Foundations

Kubernetes

Kubernetes is a system that keeps your containers running the way you declared in YAML. You say "I want 3 copies of this image". It keeps checking and fixes reality to match. That loop is called reconciliation.

6.1 Architecture

Control plane(managed by EKS / AKS) API serverkubectl talks only to this etcddatabase of desired state Schedulerpicks a node for each pod Controllerskeep replicas = desired Worker node 1(EC2 / VM) kubelet: runs the pods kube-proxy / CNI: networking Pod Pod Pod = 1+ containers sharingone IP and storage Worker node 2 kubelet kube-proxy / CNI Pod CoreDNS

6.2 The objects, and how they connect

Ingresshost/path → svc Serviceselector: app=web Podlabel app=web Podlabel app=web Podlabel app=web ReplicaSetkeeps 3 running Deploymentyou edit this ConfigMap / Secretenv vars or files Traffic flows left → right. Ownership flows right → left. The Service finds pods by LABEL, not by name.
ObjectOne-line meaning
PodSmallest unit: one or more containers with one IP. Pods are disposable; they get replaced, not repaired.
Deployment"Run N copies of this pod template, and roll out changes gradually." This is what you edit.
ReplicaSetCreated by the Deployment; one per version. Keeps the pod count right. You rarely touch it.
ServiceA stable IP + DNS name in front of changing pods. Types: ClusterIP (internal), NodePort, LoadBalancer (creates a cloud LB).
IngressHTTP routing rules (host / path → Service), done by an ingress controller (NGINX, AWS Load Balancer Controller, AGIC on Azure).
ConfigMapNon-secret config (env vars or files).
SecretSensitive config. Only base64-encoded, not encrypted by default. Protect it with RBAC and encryption at rest.
NamespaceA folder for objects, e.g. dev, staging, team-a.
DaemonSet / StatefulSet / JobOne pod per node (log agents) / pods with stable identity & disk (databases) / run-to-completion tasks.

6.3 An annotated manifest: the thing they'll ask you to write

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: shop
spec:
  replicas: 3                         # ①
  selector:
    matchLabels: { app: web }         # ②
  template:
    metadata:
      labels: { app: web }            # ② must match
    spec:
      containers:
      - name: web
        image: 1234.dkr.ecr.ap-southeast-1.amazonaws.com/web:1.4.2  # ③
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef: { name: web-config }   # ④
        resources:                          # ⑤
          requests: { cpu: 250m, memory: 256Mi }
          limits:   { memory: 512Mi }
        readinessProbe:                     # ⑥
          httpGet: { path: /ready, port: 8080 }
        livenessProbe:
          httpGet: { path: /healthz, port: 8080 }
          initialDelaySeconds: 10
---
apiVersion: v1
kind: Service
metadata: { name: web, namespace: shop }
spec:
  selector: { app: web }              # ⑦
  ports:
  - port: 80                          # ⑧
    targetPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata: { name: web-config, namespace: shop }
data:
  LOG_LEVEL: info
  DB_HOST: shop-db.internal
  1. How many copies. The HPA can change this automatically.
  2. The Deployment manages pods with these labels. The selector and the template labels must match.
  3. Use a specific tag (or digest), never latest, so rollouts and rollbacks are predictable.
  4. Load every key of the ConfigMap as an environment variable.
  5. requests = what the scheduler reserves (and roughly what you pay for). limits = the ceiling. Going over the memory limit means OOMKilled; going over the CPU limit means throttling.
  6. readiness: "may I receive traffic?" (if it fails, the pod is removed from the Service). liveness: "am I stuck?" (if it fails, the container is restarted).
  7. The Service sends traffic to pods with this label. Labels wrong = empty endpoints = no traffic.
  8. port = what callers use (http://web.shop). targetPort = the container's port.

6.4 Pod lifecycle: the states you'll troubleshoot

PendingAccepted, but not placed on a node or still pulling the image
ContainerCreatingPulling the image, mounting volumes
RunningContainer started (≠ healthy! check readiness)
Succeeded / FailedExited 0 / non-zero
Pending
Scheduler can't place it: not enough CPU/memory, taints, node selector, PVC not bound.
ImagePullBackOff
Can't pull the image: wrong name/tag, no registry permission, no network to the registry.
CrashLoopBackOff
Starts, then dies, again and again: app error, missing config, OOM, bad liveness probe.

6.5 kubectl, the daily commands

kubectl config current-context            # WHICH cluster am I on? (check before every change)
kubectl get pods -n shop -o wide          # status, restarts, node, IP
kubectl describe pod web-7d9f -n shop     # read EVENTS at the bottom
kubectl logs web-7d9f -n shop --previous  # logs of the crashed container
kubectl get events -n shop --sort-by=.lastTimestamp
kubectl apply -f web.yaml                 # create / update from YAML
kubectl diff -f web.yaml                  # preview a change before applying
kubectl rollout status deploy/web -n shop
kubectl rollout history deploy/web -n shop
kubectl rollout undo deploy/web -n shop   # roll back to previous version
kubectl rollout restart deploy/web -n shop
kubectl get endpoints web -n shop         # which pod IPs is the Service sending to?
kubectl port-forward svc/web 8080:80 -n shop
kubectl top pods -n shop                  # needs metrics-server
kubectl run tmp --rm -it --image=nicolaka/netshoot -- bash   # debug pod with curl/dig

6.6 Requests, limits and autoscaling: how they chain together

1Traffic ↑CPU per pod rises above target (e.g. 70% of request)
2HPAadds pods (replicas 3 → 6)
3Pods Pendingno node has free requested CPU/memory
4Cluster Autoscaler / Karpenteradds a node
5Scheduledpods run; later, empty nodes are removed

FinOps link: node count is driven by requests, not real usage. Requests set far too high mean you pay for nodes that sit idle. That's a great point to bring up in the interview.

Chapter 7 · Foundations

Monitoring & observability

Metrics 📈
Numbers over time: CPU, requests/sec, p99 latency. Cheap. Good for alerts and dashboards. "Something is wrong."
Logs 📜
Text events: "ERROR db timeout user=42". Good for details. "What is wrong."
Traces 🧵
One request's path across services, with timing per hop. "Where is it slow."

The 4 golden signals (Google SRE): watch these for any service

SignalQuestionExample metric
LatencyHow long do requests take? (use p95/p99, not the average)ALB TargetResponseTime, http_request_duration_seconds
TrafficHow much demand?RequestCount, requests/sec
ErrorsHow many fail?HTTPCode_Target_5XX_Count, error rate %
SaturationHow full is the system?CPU, memory, disk, DB connections, queue depth

Tool landscape

NeedCloud nativeOpen source / SaaS
Metrics + alarmsCloudWatch Metrics & Alarms · Azure Monitor Metrics & AlertsPrometheus (collects by "scraping" /metrics) + Alertmanager
DashboardsCloudWatch Dashboards · Azure WorkbooksGrafana (reads Prometheus, CloudWatch, Azure Monitor, Loki…)
LogsCloudWatch Logs (Logs Insights) · Log Analytics (KQL)Loki, Elasticsearch/OpenSearch, Datadog
TracesAWS X-Ray · Application InsightsJaeger, Tempo; OpenTelemetry is the standard to send all three

Good alerting in one picture

Metricerror rate
Rule> 5% for 5 min
Severitypage vs ticket
RoutePagerDuty / Slack / Teams
Runbook link"what to do"

Alert on symptoms users feel (errors, latency), not on every cause (CPU 80%). Every page should be actionable. Your FinOps parallel: noisy cost-anomaly alerts get ignored, and so do noisy pages.

Incident response flow

1Acknowledge"I'm on it"
2Assessimpact, severity
3Communicateincident channel, status page
4Mitigaterollback / scale / failover
5Resolve & verifymetrics back to normal
6Postmortemblameless, action items
Chapter 8 · Foundations

Git & CI/CD

Git workflow you need

git clone git@github.com:org/infra.git && cd infra
git switch -c feat/open-5432-for-orders      # new branch
# ...edit files...
git status && git diff                       # check before commit
git add k8s/orders.yaml
git commit -m "orders: allow app subnet to reach postgres on 5432"
git push -u origin feat/open-5432-for-orders # then open a Pull Request
git pull --rebase origin main                # update your branch with main
git log --oneline -10                        # recent history
git revert <sha>                             # safe undo on a shared branch

A typical pipeline

Commit / PRtrigger
Buildcompile, docker build
Testunit, lint
Scandeps, image, IaC, secrets
Pushimage → ECR/ACR, tag = git sha
Deploy stagingauto
Approvalmanual gate
Deploy prodrolling / canary
# .github/workflows/deploy.yml (GitHub Actions, simplified)
name: deploy
on: { push: { branches: [main] } }
permissions: { id-token: write, contents: read }   # OIDC: no stored AWS keys
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with: { role-to-assume: arn:aws:iam::123456789012:role/gha-deploy, aws-region: ap-southeast-1 }
      - run: docker build -t $ECR/web:${{ github.sha }} . && docker push $ECR/web:${{ github.sha }}
  deploy-prod:
    needs: build
    environment: production          # required reviewers = approval gate
    runs-on: ubuntu-latest
    steps:
      - run: kubectl set image deploy/web web=$ECR/web:${{ github.sha }} -n shop && kubectl rollout status deploy/web -n shop

Deployment strategies

Rolling
v2v2v1v1
Replace pods a few at a time. K8s default. Cheap. Both versions run together for a while.
Blue / Green
blue v1 (idle)green v2 ← 100%
Two full environments; switch traffic at once. Instant rollback (switch back). Costs 2× for a while.
Canary
v1 90%v2
Send 5–10% to the new version, watch metrics, then increase. Safest; needs good monitoring.
Chapter 9 · Foundations

Terraform (Infrastructure as Code)

Instead of clicking in the console, you describe infrastructure in code, review it in a PR, and apply it. It's repeatable, reviewable, and you can roll it back with git.

write .tfdesired state
terraform initdownload providers, connect backend
terraform plandiff: code vs state vs real cloud
reviewread every -/+ (replace!)
terraform applymake it real, update state
terraform {
  backend "s3" {                       # remote state shared by the team
    bucket       = "acme-tf-state"
    key          = "network/prod.tfstate"
    region       = "ap-southeast-1"
    encrypt      = true
    use_lockfile = true                # S3-native locking (Terraform 1.10+); older setups use a DynamoDB table
  }
}

resource "aws_security_group_rule" "db_from_app" {
  type                     = "ingress"
  from_port                = 5432
  to_port                  = 5432
  protocol                 = "tcp"
  security_group_id        = aws_security_group.db.id
  source_security_group_id = aws_security_group.app.id   # allow from app SG, not 0.0.0.0/0
}
WordMeaning
StateTerraform's memory: "resource aws_instance.web = real instance i-0abc". Lose it and Terraform forgets what it owns.
Remote backendStore state in S3 / Azure Storage, not on your laptop, so the whole team uses one copy.
LockingOnly one apply at a time, so two runs don't corrupt the state.
DriftSomeone changed the real resource outside Terraform (e.g. in the console). The next plan shows it.
ModuleA reusable package, e.g. a dev-environment module the dev teams can call.
ImportBring an existing resource under Terraform management.

Read the plan symbols: + create · ~ update in place · - destroy · -/+ destroy and re-create (possible outage!). In an interview, saying "I always read the plan for -/+ before applying" sounds senior.

Chapter 10 · Foundations

Security & IAM

IAM user / access key

Long-lived credentials. If leaked, they work until someone revokes them. Avoid them for apps.

IAM role

Temporary credentials that are assumed and rotate automatically. Used by EC2 (instance profile), Lambda, pods (IRSA / EKS Pod Identity) and CI (OIDC). Always prefer roles. Azure: Managed Identity.

How AWS decides "allowed or denied"

1Explicit Deny anywhere?SCP, bucket policy, boundary, identity policy → DENY (always wins)
2Allowed by every gate?SCP ✓, permission boundary ✓, identity or resource policy ✓, KMS key policy ✓
3Otherwiseimplicit deny → DENY

So "the role has permission but still AccessDenied" almost always means another gate said no: a bucket policy Deny, a KMS key policy, an SCP, a permission boundary, or the app isn't using the role you think. See question S1.

Security rules of thumb

  • Least privilege: give only the actions and resources needed. Start small, add when needed.
  • No secrets in git, images or plain env files. Use Secrets Manager / Key Vault, injected at runtime.
  • Private by default: databases in private subnets, SG source = another SG, no 0.0.0.0/0 on SSH/RDP. Use SSM Session Manager / Azure Bastion instead of open port 22.
  • Encrypt at rest (KMS) and in transit (TLS).
  • Audit: CloudTrail / Activity Log on, and alerts on risky actions.
Chapter 11 · Foundations

Runbooks & documentation

A runbook is a step-by-step guide that someone tired at 3 a.m. can follow without thinking. The JD asks for this explicitly, and it's an easy win for you: bring a sample to the interview.

# Runbook: API 5xx error rate high

**Alert:** `api-5xx-rate > 5% for 5m` (Grafana → API Overview)
**Severity:** SEV2 · **Owner:** Platform team · **Last reviewed:** 2026-10-20

## 1. Impact check (2 min)
- Open dashboard: <link>. Is it all endpoints or one? All regions?
- Post in #incident: "Investigating API 5xx, impact: ___"

## 2. Did something change? (2 min)
- Deploys in the last hour: `kubectl rollout history deploy/api -n prod`
- Infra changes: last merged PRs in `infra` repo

## 3. Diagnose
```bash
kubectl get pods -n prod -l app=api          # restarts? not Ready?
kubectl logs -n prod -l app=api --since=15m | grep -i error | head
```
- DB connections saturated? → see runbook `db-connections`

## 4. Mitigate
- Recent deploy → `kubectl rollout undo deploy/api -n prod`
- Load spike → `kubectl scale deploy/api --replicas=10 -n prod`

## 5. Verify
- 5xx rate < 1% for 10 min on the dashboard

## 6. Rollback of this runbook's actions / escalate
- Not fixed in 15 min → page backend on-call (PagerDuty: api-backend)

## 7. After
- Create postmortem doc from template, link here
Good docs answer
What is it? Why does it exist? How do I change it? How do I verify? How do I roll back? Who owns it?
Markdown you need
# headings, - lists, **bold**, `code`, fenced code blocks with a language, [link](url), tables with |.
Chapter 12 · Practice

The troubleshooting playbook

Interviewers care less about the answer than about how you narrow it down. Climb this ladder from the top, and at every step say what you'd run and what each result means.

12.1 The outside-in ladder

1
DNSDoes the name resolve to the right thing?
dig +short app.example.com
Wrong/old IP → record or TTL issue
2
Route / reachabilityCan packets get there?
traceroute, route tables, IGW/NAT, peering
Missing route → silent drops
3
FirewallIs the port allowed both ways?
nc -zv host 443, SG / NACL / NSG, VPC Flow Logs (REJECT)
Timeout = dropped
4
Load balancer / IngressAre targets healthy? What code does the LB return?
Target health, LB access logs, 502/503/504
See the table below
5
App / processIs it running, listening, and logging errors?
systemctl status, ss -tulpn, kubectl logs
Refused = nothing listening
6
ResourcesCPU, memory, disk, connections?
top, df -h, kubectl top, CloudWatch
Saturation = slow / OOM
7
DependenciesDatabase, cache, third-party API, IAM?
DB metrics, app logs, traces, CloudTrail
Slow DB = slow app

12.2 HTTP status codes: what the error is telling you

CodeWho's speakingMeaningFirst things to check
400–404AppClient-side problem: bad request, not logged in, forbidden, wrong pathURL / path, auth headers, Ingress path rules
500AppThe app crashed while handling the requestApp logs, stack trace
502 Bad GatewayLB / proxyThe LB got an invalid or no response: the target closed the connection, crashed, or returned garbageApp crashing / restarting, keep-alive timeout shorter than the LB idle timeout, wrong target port/protocol (HTTP vs HTTPS)
503 Service UnavailableLB / proxyNo healthy target to send to (or the app is overloaded)Target group health, empty K8s endpoints, scaled to 0
504 Gateway TimeoutLB / proxyThe target didn't answer in timeSlow app / DB, LB idle timeout, SG blocking LB → target, NetworkPolicy dropping

12.3 "I can't connect": read the error message

ErrorWhat it provesLook at
Could not resolve hostDNS failedRecord exists? Private vs public zone? Resolver?
Connection timed outPackets dropped on the waySG / NSG / NACL, route table, public IP, wrong IP
Connection refusedReached the host, nothing listeningService down, wrong port, listening on 127.0.0.1
Permission denied (publickey)Network is fine. Authentication failedWrong key, wrong username, key file permissions
SSL certificate problemTLS issueExpired cert, wrong hostname, missing intermediate
AccessDenied / 403Network is fine. Identity/permissionsIAM policies, resource policies, KMS, SCP

12.4 Kubernetes pod triage flowchart

StatusRun thisLook forUsual fixes
Pendingkubectl describe pod → Events0/3 nodes are available: Insufficient cpu, untolerated taint, didn't match node selector, unbound PersistentVolumeClaimLower requests or add nodes (autoscaler), fix the selector / toleration, fix the PVC / StorageClass
ImagePullBackOffkubectl describe pod → Eventsnot found, unauthorized, i/o timeout, toomanyrequestsFix image name/tag, imagePullSecret or node IAM for ECR / AcrPull for ACR, NAT / VPC endpoints, mirror Docker Hub images
CrashLoopBackOffkubectl logs --previous + describe → Last State, Exit CodeExit 1 (app error), 137 + OOMKilled, liveness probe failures, exec format error (wrong CPU architecture)Fix config/env/secret, raise the memory limit or fix the leak, fix the probe path or delay, build for the right arch
Running, not Readydescribe → readiness probe eventsReadiness probe failed: … 503The app can't reach a dependency, or the probe path/port is wrong
Running, but no traffickubectl get endpoints svcEndpoints <none>Service selector labels ≠ pod labels, wrong targetPort

12.5 The universal incident answer template

Use this skeleton for any "production is broken" question

"First I'd confirm the impact: who's affected and since when, using dashboards and alerts. Then I'd check what changed around that time: deploys, config, infra, traffic. If a recent change lines up and users are hurting, I'd mitigate first by rolling back or scaling out, and investigate after. To find the cause, I'd go layer by layer from the outside in: DNS, network, load balancer, app, then dependencies, using metrics, then logs, then traces. Throughout, I'd keep the team updated in the incident channel. Afterwards I'd write a blameless postmortem and update the runbook and alerts so it's caught faster next time."

Chapter 13 · Practice

Question bank

All your questions, grouped by topic, plus questions I added because they come straight from this JD. Each one has a Say this box (a 30–60 second spoken answer), then the detail behind it. Mark each one when you can answer it without looking.

🐧 Linux & scripting

L1How would you troubleshoot high CPU, memory usage and disk I/O on a Linux server?List 1 · Q01Must
Say this

"I check the four resources one at a time: CPU, memory, disk and network. For CPU I use top to see the load average and which process is busy, and whether the time is user, system or I/O wait. For memory I use free -h and check dmesg for the OOM killer. For disk I check space with df -h and pressure with iostat -xz. Once I know the process, I check its logs and what changed recently. I mitigate first, for example by moving traffic away or scaling out, before I restart anything."

Step by step
  1. Confirm and scope: is it one server or all of them? Since when? Did a deploy, a cron job or a traffic spike happen at that time? (CloudWatch / Azure Monitor graphs.)
  2. CPU: uptime: load average vs number of CPUs (nproc). In top, press P to sort by CPU. High %us = the app is computing. High %sy = kernel. High %wa = waiting for disk, so the real problem is I/O. On AWS T-family instances, check CPUCreditBalance: at zero, the instance is throttled.
  3. Memory: free -h and look at "available". ps aux --sort=-%mem | head. dmesg -T | grep -i "killed process" shows OOM kills. Swap use going up = memory pressure.
  4. Disk I/O: iostat -xz 1: %util near 100% and high await mean the disk is saturated. iotop shows which process. On EBS, check VolumeQueueLength and whether you've hit the IOPS/throughput limit or burst balance of gp2.
  5. Disk space: df -h, df -i (inodes), du -xh / --max-depth=1 | sort -h. Usually it's logs.
  6. Fix: short term, kill or restart the runaway process, rotate logs, or take the instance out of the load balancer. Long term, fix the code or query, add log rotation, rightsize or autoscale, and add an alert.
uptime; nproc
top -o %CPU                       # or htop
ps aux --sort=-%cpu | head -5
free -h; vmstat 1 5
dmesg -T | grep -i -E "oom|killed process"
iostat -xz 1 5; sudo iotop -o
df -h; df -i; sudo du -xh /var --max-depth=2 | sort -h | tail

What they're testing: a structured method (the USE method: Utilisation, Saturation, Errors for each resource) and not panicking or rebooting first.

FinOps angle: "If the CPU is high all the time, the server may simply be undersized. I'd look at the utilisation trend before deciding whether to optimise the app or rightsize, because I've done rightsizing analysis before."

L2Write a Bash script to check service health, retry failures and alert on recovery.List 1 · Q02Should
Say this

"The script calls the health endpoint with curl -f, which fails on HTTP errors, and sets a timeout. If it fails, it retries a few times with a growing wait so one blip doesn't page anyone. It saves the last state in a file, so it alerts only when the state changes: once when the service goes down, and once when it recovers. Then I run it from cron or a systemd timer."

#!/usr/bin/env bash
# health-check.sh: check a URL, retry, alert on DOWN and on RECOVERY
set -uo pipefail
URL="${1:?usage: health-check.sh <url>}"
RETRIES=3
WAIT=5
STATE_FILE="/tmp/health_$(echo "$URL" | md5sum | cut -c1-8).state"
WEBHOOK="${SLACK_WEBHOOK_URL:-}"          # secret from env, not hard-coded

alert() {
  echo "$(date -Is) $1"
  if [ -n "$WEBHOOK" ]; then
    curl -s -X POST -H 'Content-type: application/json' \
         --data "{\"text\":\"$1\"}" "$WEBHOOK" >/dev/null
  fi
}

check() {
  for i in $(seq 1 "$RETRIES"); do
    if curl -fsS --max-time 5 "$URL" >/dev/null; then return 0; fi
    echo "attempt $i/$RETRIES failed, retrying in ${WAIT}s"
    sleep "$WAIT"; WAIT=$((WAIT * 2))     # exponential backoff
  done
  return 1
}

prev=$(cat "$STATE_FILE" 2>/dev/null || echo "UP")
if check; then now="UP"; else now="DOWN"; fi

if [ "$prev" != "$now" ]; then           # alert only on CHANGE
  if [ "$now" = "DOWN" ]; then alert "🔴 $URL is DOWN after $RETRIES attempts"
  else alert "✅ $URL has RECOVERED"; fi
fi
echo "$now" > "$STATE_FILE"
[ "$now" = "UP" ]                        # exit code 0 = healthy

# run every minute:
# * * * * * /opt/health-check.sh https://api.example.com/health >> /var/log/health.log 2>&1
Points to mention
  • curl -f makes HTTP 4xx/5xx count as failure. --max-time stops it hanging.
  • Retries with backoff avoid false alarms. The state file avoids sending the same alert every minute.
  • The webhook URL comes from an environment variable, so there's no secret in the script.
  • In real life, monitoring tools (CloudWatch Synthetics, Blackbox Exporter, Datadog) do this better. A script is good for small or legacy systems.

What they're testing: basic Bash (functions, loops, exit codes, variables) and alert hygiene (no spam, recovery notice).

L3Write a Bash script to identify failed services and restart them safely.List 1 · Q20Should
Say this

"I'd list failed units with systemctl --failed. 'Safely' means four things: only restart services on an allow-list, log the last error lines first so we don't lose the evidence, use a lock so two copies never run at once, and check after the restart that the service actually came back. If it's still failing, I escalate instead of restarting in a loop. I'd also say the first line of defence should be systemd's own Restart=on-failure setting."

#!/usr/bin/env bash
# restart-failed.sh: restart failed systemd services, safely
set -euo pipefail
DRY_RUN="${DRY_RUN:-false}"
ALLOW="nginx|docker|myapp"                    # only services we own

exec 9>/var/lock/restart-failed.lock          # one run at a time
flock -n 9 || { echo "another run in progress"; exit 0; }

for unit in $(systemctl list-units --type=service --state=failed --no-legend --plain | awk '{print $1}'); do
  name="${unit%.service}"
  if ! [[ "$name" =~ ^($ALLOW)$ ]]; then
    echo "skip $unit (not in allow-list)"; continue
  fi
  echo "$(date -Is) $unit failed. Last logs:"
  journalctl -u "$unit" -n 20 --no-pager      # keep the evidence
  if [ "$DRY_RUN" = "true" ]; then echo "[dry-run] would restart $unit"; continue; fi
  systemctl restart "$unit" || true
  sleep 5
  if systemctl is-active --quiet "$unit"; then
    echo "$unit recovered"
  else
    echo "$unit STILL failing: escalate, do not loop"   # send alert here
  fi
done
# the better first line of defence, in the unit file:
[Service]
Restart=on-failure
RestartSec=5
# and in [Unit]: StartLimitIntervalSec=300  StartLimitBurst=5  (stop after 5 tries in 5 min)

What they're testing: that "safe" means guardrails (allow-list, dry-run, lock, verify, escalate), not blindly restarting everything.

🌐 Networking & DNS

N1What happens when a user types app.example.com in a browser and presses Enter?Added from JD: classic warm-up questionMust
Say this

"First the browser resolves the name with DNS: it checks its cache, then asks a resolver, which goes to the authoritative server (Route 53) and gets the load balancer's IP. Then the browser opens a TCP connection to that IP on port 443 and does a TLS handshake to set up encryption and check the certificate. It sends the HTTP request. In the cloud, that request enters the VPC through the internet gateway and hits the load balancer, which forwards it to a healthy server or pod in a private subnet. Security groups must allow each hop. The app may query the database, then the response goes back the same way."

Why it's useful

Each step is a place things break. If you can name them in order, you have a troubleshooting checklist: DNS → TCP → TLS → LB → target → app → DB. See the diagram in Chapter 2.

What they're testing: whether you see the whole path. Bonus points: mention TTL caching, the certificate check, health checks and security groups.

N2Explain subnets and CIDR. What's the difference between 10.0.0.0/16 and 10.0.1.0/24? What makes a subnet public?Added from JD: "subnets, routing"Must
Say this

"CIDR describes a range of IPs. The number after the slash is how many bits are fixed, so a smaller number means a bigger range. A /16 has 65,536 addresses, which is typical for a whole VPC. A /24 has 256, which is typical for one subnet, and the cloud reserves 5 of those. A subnet is public when its route table sends 0.0.0.0/0 to an internet gateway. It's private when it doesn't; then it can only get outbound internet through a NAT gateway. I'd also make sure CIDRs don't overlap with other VPCs or on-prem, otherwise peering or VPN won't work later."

Quick maths

Addresses = 2(32 − prefix). /24 → 28 = 256. /26 → 64. /28 → 16. A subnet lives in one AZ on AWS. On Azure a subnet spans the zones in the region, which is a good detail to know.

What they're testing: that you know "public" is about the route table, and that you plan IP space (EKS pods use a lot of IPs with the AWS VPC CNI).

N3A dev team asks you to allow their app to reach a Postgres database. How do you update the NSG / Security Group safely?Added from JD: "updating NSG rules"Must
Say this

"First I'd confirm the exact need: which source, which destination, which port. Then I'd add the narrowest rule: inbound TCP 5432 on the database's NSG, with the source as the app subnet or app security group, never 'Any' or 0.0.0.0/0. In Azure NSGs, rules run by priority, lowest number first, so I'd check no higher-priority deny blocks it, and that I'm not overriding something important. I'd make the change through Terraform and a pull request so it's reviewed and recorded. Then I'd test with nc -zv db-host 5432 from the app, and document it."

Step by step
  1. Clarify: source (app subnet 10.1.2.0/24 or its ASG / SG), destination (DB subnet or NIC), port 5432/TCP, environment. Get a ticket or approval.
  2. Check existing rules: az network nsg rule list --nsg-name db-nsg -g rg-prod -o table. Watch out for an NSG on both subnet and NIC: traffic must be allowed by both.
  3. Least privilege: specific source, single port, a clear name like allow-orders-app-to-pg, and a description.
  4. Change via IaC (Terraform PR → plan → review → apply). In an emergency, change it in the console and then back-port it to code right away, so there's no drift.
  5. Verify: nc -zv from the app host or pod. Azure Network Watcher "IP flow verify", or on AWS the Reachability Analyzer.
  6. Document the rule and its owner. Remove temporary rules when they're no longer needed.
# Azure CLI
az network nsg rule create -g rg-prod --nsg-name db-nsg -n allow-orders-app-to-pg \
  --priority 200 --direction Inbound --access Allow --protocol Tcp \
  --source-address-prefixes 10.1.2.0/24 --destination-port-ranges 5432

# AWS CLI: source is another security group, not an IP range
aws ec2 authorize-security-group-ingress --group-id sg-db123 \
  --protocol tcp --port 5432 --source-group sg-app456

AWS vs Azure: an AWS Security Group has no deny rules and no priorities: if any rule allows it, it's allowed. An Azure NSG has priorities and deny rules, so order matters.

What they're testing: least privilege, change control (IaC + review), and verification. Not "I'd open the port."

N4You need to point a DNS record to a new load balancer. How do you do it with minimal disruption?Added from JD: "DNS records"Must
Say this

"The risk with DNS changes is caching, so I plan around the TTL. A day or so before, I lower the TTL, say from 3600 to 60 seconds, and wait for the old TTL to expire. I make sure the new load balancer is healthy and serving correctly by testing it directly. Then I update the record. On AWS I'd use an alias record pointing to the ALB. I verify with dig against the authoritative name server and public resolvers, and I keep the old load balancer running until its traffic drops to zero. Then I raise the TTL again. For a riskier change, I'd use weighted records to shift traffic gradually."

dig +short app.example.com                       # what most people get now
dig app.example.com @ns-123.awsdns-45.com        # the authoritative answer
dig app.example.com @8.8.8.8 +noall +answer      # shows remaining TTL
curl -v --resolve app.example.com:443:52.1.2.3 https://app.example.com/health  # test the new LB before switching

What they're testing: that you understand TTL and caching, that you test before switching, and that you keep the old endpoint as a rollback.

N5"The app is unreachable." How do you use ping, traceroute, dig, nc and curl to diagnose it?Added from JD: "basic CLI tools"Must
Say this

"I go up the layers. dig first: does the name resolve to the right IP? Then nc -zv host 443: is the port reachable? A timeout means a firewall or route is dropping the traffic. 'Connection refused' means I reached the host but nothing is listening. Then curl -v: what does the app answer, what status code, and does TLS work? I use ping and traceroute to see where the path stops, but carefully, because many cloud networks block ICMP, so a failed ping doesn't prove the host is down."

dig +short app.example.com                 # 1. DNS: right IP?
nc -zv app.example.com 443                 # 2. TCP port: open / timeout / refused?
curl -v https://app.example.com/health     # 3. HTTP: status, TLS, headers
curl -s -o /dev/null -w "dns:%{time_namelookup} connect:%{time_connect} tls:%{time_appconnect} total:%{time_total}\n" https://app.example.com
traceroute -T -p 443 app.example.com       # 4. path (TCP mode gets past ICMP blocks)
# on the server itself:
ss -tulpn | grep 443                       # 5. is anything listening? on 0.0.0.0 or 127.0.0.1?

What they're testing: that you know what each tool proves, especially timeout vs refused and "ping isn't proof".

N6An EC2 instance is running, but you cannot connect through SSH. What troubleshooting steps would you follow?List 2 · Q03Must
Say this

"I'd start with the error message, because it tells me the layer. A timeout means a network problem: I'd check the security group allows port 22 from my IP, the NACL allows 22 in and ephemeral ports out, the subnet's route table has an internet gateway route, and the instance has a public IP, or I'm coming through a VPN or bastion. Permission denied means the network is fine and it's the key or the username, like ec2-user vs ubuntu, or key file permissions. If it's the OS itself, like a full disk or sshd stopped, I'd check the instance status checks and system log, and get in another way with SSM Session Manager or the serial console. Long term, I'd use Session Manager and close port 22."

Step by step
  1. ssh -vvv -i key.pem ec2-user@IP and read where it stops.
  2. Timeout → network: SG inbound 22 from your current public IP (your IP may have changed!) · NACL inbound 22 + outbound 1024–65535 (stateless) · route 0.0.0.0/0 → igw · public IP / Elastic IP (stopping and starting changes a non-Elastic public IP) · OS firewall (ufw/firewalld).
  3. Refused: sshd not running or on a different port.
  4. Permission denied (publickey): wrong key pair, wrong user (Amazon Linux ec2-user, Ubuntu ubuntu), chmod 400 key.pem.
  5. Status checks in the console: a failed "instance status check" = an OS problem (kernel, disk full, fstab error). Look at Get system log and instance screenshot.
  6. Other ways in: SSM Session Manager (needs the SSM agent, an IAM role with AmazonSSMManagedInstanceCore, and outbound access to SSM endpoints), EC2 Instance Connect, EC2 Serial Console. Last resort: stop the instance, attach its root volume to another instance, and fix the files.

Azure version: NSG inbound 22 · public IP · "Boot diagnostics" + "Serial console" · "Reset password / SSH key" via the VM access extension · Azure Bastion instead of a public SSH port.

What they're testing: a layered approach driven by the error message, and knowing alternatives to open SSH.

N7An application in a private subnet cannot access the internet even though a NAT Gateway is configured. What would you check?List 2 · Q08Must
Say this

"I'd follow the packet. First, the private subnet's route table: does it really have 0.0.0.0/0 pointing to the NAT gateway, and is the subnet associated with that route table and not the main one? Second, the NAT gateway itself must sit in a public subnet whose route table points to the internet gateway, with an Elastic IP and status Available. Third, the firewalls: the security group's outbound rules, and NACLs allowing outbound traffic and the inbound return traffic on ephemeral ports. Then DNS: can the instance even resolve the name? I test with curl -v rather than ping, and VPC Flow Logs show me where packets are rejected."

App in private subnetSG outbound ✓
Private route table0.0.0.0/0 → nat-xxx ✓
NAT GW in PUBLIC subnethas EIP, Available ✓
Public route table0.0.0.0/0 → igw-xxx ✓
IGW attached to VPC→ internet
Most common mistakes
  • NAT gateway placed in a private subnet (so it has no route out).
  • Subnet associated with the wrong route table.
  • NACL blocking return traffic on ports 1024–65535.
  • Testing with ping when ICMP is blocked. Use curl -v https://example.com.
  • The app needs AWS services (S3, ECR): a VPC endpoint may be better, cheaper and more private.
aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-abc
aws ec2 describe-nat-gateways --nat-gateway-ids nat-123 --query 'NatGateways[0].[State,SubnetId]'

FinOps angle (use it!): "From cost work I know NAT gateways charge per GB processed. For heavy S3 or ECR traffic I'd add gateway or interface VPC endpoints, which both fixes access paths and cuts cost."

N8Your Route 53 DNS records are configured correctly, but users are intermittently unable to access the application. How would you troubleshoot?List 2 · Q16Must
Say this

"'Intermittent' usually means some answers or some backends are bad, not all of them. On the DNS side, I'd check whether the record returns several values and one of them is dead, whether health checks on failover or weighted records are flapping, whether an old IP is still cached somewhere because of a long TTL, and whether the name servers at the registrar match the hosted zone. I'd query from different resolvers with dig to compare. If DNS is consistent, the problem is behind it, like one unhealthy target or one bad AZ, so I'd check load balancer target health and 5xx metrics per AZ."

Checklist
  1. dig app.example.com @8.8.8.8, @1.1.1.1, @<authoritative NS>. Do they agree? Run it several times: does the answer rotate?
  2. Multi-value / weighted / latency records: is one target or IP bad? Are the health checks passing? (Route 53 → Health checks.)
  3. TTL / caching: was something changed recently with a long TTL? Some users still have the old IP.
  4. Delegation: dig NS example.com vs the NS records in the hosted zone. A mismatch makes some resolvers fail.
  5. AAAA (IPv6) record pointing somewhere broken: only IPv6 users fail.
  6. Private vs public hosted zone with the same name: inside the VPC people get different answers.
  7. If DNS is fine: per-AZ target health, ALB 5xx, one bad instance, SG changes, client-side issues (corporate proxy).

What they're testing: that you know DNS is distributed and cached, and that you verify DNS and then move on to the layers behind it.

N9Intermittent connectivity failures between two AWS VPCs. How would you troubleshoot VPC peering, Transit Gateway and routing?List 2 · Q04Stretch
Say this

"Intermittent between VPCs usually means it works from some subnets or AZs and not others. So first I'd find the pattern: which source subnets and which destinations fail. Then I check routes on both sides: every subnet's route table needs the route to the other VPC's CIDR via the peering connection or Transit Gateway. With Transit Gateway, I'd check the attachment has a subnet in every AZ, plus the TGW route table associations and propagations. Then security groups and NACLs on both sides. VPC Flow Logs show REJECTs, and Reachability Analyzer tests a specific path."

  • Peering is not transitive: A↔B and B↔C does not give A↔C.
  • CIDRs can't overlap. Route tables are needed on both VPCs, for every subnet route table.
  • TGW: attachment subnets in each AZ (traffic from an AZ without one fails, which looks "intermittent"), route table association + propagation, return routes, appliance mode if traffic goes through a stateful firewall (asymmetric routing).
  • SGs referencing a peer VPC's SG work only within the same region.
  • DNS: enable "DNS resolution from peer VPC" for private hostnames.

For a junior role: it's enough to know "routes on both sides, non-transitive, no overlapping CIDRs, Flow Logs + Reachability Analyzer".

🖥️ Compute, load balancers & scaling

C1An EC2 instance suddenly reaches 100% CPU during peak traffic. How would you troubleshoot and resolve it without impacting production?List 2 · Q01Must
Say this

"First I protect users: if it's behind an Auto Scaling Group, I make sure it scales out, or I raise the desired capacity manually so the load spreads. I don't reboot a busy production server. Then I find out whether it's just more traffic or something abnormal. I compare request count on the load balancer with CPU. If traffic doubled and CPU doubled, that's capacity. If traffic is flat but CPU jumped, it's a code or process problem. I check with top or through SSM which process is hot, and whether a deploy or cron job lined up with it. On T-instances I check CPU credits. Long term: autoscaling policies, rightsizing, caching or fixing the code."

  1. Mitigate without impact: scale out the ASG (aws autoscaling set-desired-capacity). If only one instance is bad, deregister it from the target group so it can be investigated rather than killed.
  2. Correlate: CloudWatch CPUUtilization vs ALB RequestCount and TargetResponseTime. Was there a deploy, a cron or batch job, or a traffic burst (a bot? a DDoS → WAF)?
  3. Look inside: SSM Session Manager → top, ps aux --sort=-%cpu. Is it the app, a stuck loop, a backup job, or a crypto miner (security!)?
  4. T2/T3/T4g: check CPUCreditBalance. At zero, the instance is capped. Unlimited mode or a non-burstable family fixes that.
  5. Fix: target-tracking scaling policy (e.g. 60% CPU), scheduled scaling for known peaks, a larger or compute-optimised instance type, caching (CloudFront, ElastiCache), fixing the hot code path.

FinOps angle: "I'd prefer scaling out over permanently oversizing, so we pay for peak capacity only during the peak."

C2Users get 502 Bad Gateway errors from the Application Load Balancer. How would you investigate?List 2 · Q02Must
Say this

"A 502 means the load balancer reached the target but got a bad response or the connection was closed. So the problem is usually the app or how it talks to the ALB, not DNS. I'd check the ALB metrics to see whether the 502s are generated by the ELB or the target. Then target health, and the ALB access logs, which show the target's status code and timings per request. Common causes are the app crashing or restarting, the app's keep-alive timeout being shorter than the ALB's idle timeout, the wrong protocol or port, for example the ALB speaking HTTP to an HTTPS port, or the response headers being too big. Then I'd read the app logs at the same timestamps."

  1. CloudWatch: HTTPCode_ELB_502_Count vs HTTPCode_Target_5XX_Count. The ELB count means the LB generated the error.
  2. Target group health: are targets flapping between healthy and unhealthy?
  3. ALB access logs (enable to S3): elb_status_code=502, and target_status_code="-" means the target gave no valid response.
  4. App side: crashes / OOM / restarts at the same times; app logs; the web server's error log.
  5. Keep-alive: the app's (e.g. Node, Gunicorn, NGINX) keep-alive timeout must be longer than the ALB idle timeout (default 60 s). Otherwise the app closes a connection the ALB is about to reuse, and you get random 502s.
  6. Target group protocol/port matches what the app listens on. For Lambda targets: a malformed response or a function error.

Pair it with: 503 = no healthy targets, 504 = the target was too slow. Knowing all three is a strong signal.

C3An Application Load Balancer marks all registered targets as unhealthy. How would you identify and fix the issue?List 2 · Q11Must
Say this

"When all targets are unhealthy at once, it's usually the health check or the network, not every server breaking at the same time. First I read the reason code on the target group, like timeout or 'health checks failed with these codes'. A timeout points to the security group: the targets must allow the health-check port from the ALB's security group. A wrong code, like 404 or 301, means the health-check path is wrong, or the app redirects HTTP to HTTPS. Then I test the path from inside the VPC with curl. I also check whether a recent deploy changed the port or the health endpoint, or whether the endpoint depends on a database that's down."

  1. Target group → Targets → Health status details (e.g. Target.Timeout, Target.ResponseCodeMismatch, Target.FailedHealthChecks).
  2. Timeout: target SG must allow inbound from the ALB's SG on the traffic port and the health-check port. NACLs. The app listening on 0.0.0.0, not 127.0.0.1.
  3. Code mismatch: health path exists (/health), returns 200 (or set the matcher to 200-399), no auth or redirect on it.
  4. Recent change: new port, new path, app crash on boot (check logs / systemctl status).
  5. Deep health checks: if /health checks the DB and the DB is down, every target goes unhealthy together. Keep the LB health check shallow ("am I alive?"), and alert on dependencies separately.
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:...:targetgroup/web/abc \
  --query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State,TargetHealth.Reason]' --output table
# from another instance in the VPC:
curl -v http://10.0.11.23:8080/health

Nice detail: when all targets are unhealthy, the ALB "fails open" and sends traffic to all of them anyway, so users may still see something working, or 502/504s.

C4An Auto Scaling Group is configured, but new EC2 instances are not launching during high traffic. How would you find the root cause?List 2 · Q05Should
Say this

"I'd split it in two: is the ASG trying to launch and failing, or is it not trying? The ASG's activity history answers that, with an error message. If it's not trying: the max size is already reached, the scaling policy or CloudWatch alarm isn't triggering because it's on the wrong metric or threshold, it's in cooldown, or processes are suspended. If it's trying and failing: insufficient capacity for that instance type in the AZ, a vCPU service quota limit, a broken launch template like a deleted AMI or a missing key pair or IAM role, no free IPs in the subnet, or a KMS key permission for encrypted volumes. Or instances launch but fail health checks and get terminated in a loop."

aws autoscaling describe-scaling-activities --auto-scaling-group-name web-asg --max-items 10
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names web-asg \
  --query 'AutoScalingGroups[0].[MinSize,MaxSize,DesiredCapacity,SuspendedProcesses]'
  • Fixes: raise max, use several instance types and AZs (mixed instances policy), request a quota increase, fix the launch template, use a bigger subnet, tune alarms, add a warm pool for slow-booting apps.

What they're testing: that you know where to look first (Activity history) and the two-branch reasoning.

C5An EBS volume is running out of space and the application has write failures. How do you expand it safely without downtime?List 2 · Q17Should
Say this

"First I'd get the app writing again by freeing space quickly, like rotating or compressing old logs. Then I'd take a snapshot as a safety net. EBS Elastic Volumes let me increase the size online, without stopping the instance. After the volume shows 'optimizing' or 'completed', I extend the partition with growpart and then the filesystem with resize2fs for ext4 or xfs_growfs for XFS, all online. Finally I add a disk-usage alarm, because CloudWatch doesn't collect disk usage by default; you need the CloudWatch agent."

sudo journalctl --vacuum-size=200M          # quick space win
aws ec2 create-snapshot --volume-id vol-0abc --description "before resize"
aws ec2 modify-volume --volume-id vol-0abc --size 200
aws ec2 describe-volumes-modifications --volume-ids vol-0abc   # wait for optimizing/completed
lsblk                                       # see the disk is bigger than the partition
sudo growpart /dev/nvme0n1 1                # grow partition 1
df -hT /                                    # check filesystem type
sudo resize2fs /dev/nvme0n1p1               # ext4
sudo xfs_growfs -d /                        # xfs (Amazon Linux default)
  • You can't shrink an EBS volume, and you must wait about 6 hours between modifications. Plan the size sensibly.
  • Find the root cause: log rotation (logrotate), shipping logs to CloudWatch or S3, cleanup jobs.

Azure: Managed Disks can also be expanded, and many configurations support doing it live. Then grow the partition and filesystem inside the OS in the same way.

C6A Lambda function works in testing but often times out in production. How would you troubleshoot memory, concurrency, VPC networking and downstream dependencies?List 2 · Q14Should
Say this

"Production has more load and different networking than test, so I'd compare those. In CloudWatch I'd look at Duration, Errors, Throttles and concurrent executions, and check whether timeouts line up with traffic peaks. Memory: Lambda gives CPU in proportion to memory, so raising memory often makes it faster. VPC: if the function is in a VPC, it needs a NAT gateway or VPC endpoints to reach the internet or AWS services, otherwise calls just hang until the timeout. That's a classic. Downstream: a slow database or API, or too many connections from many concurrent Lambdas; RDS Proxy helps. I'd turn on X-Ray tracing to see which call is slow, and set client timeouts shorter than the Lambda timeout."

  • Cold starts: check Init Duration in the logs; consider provisioned concurrency for latency-critical functions.
  • Concurrency: the account concurrency limit, or reserved concurrency set too low → throttles.
  • Retries: SDK retries can multiply wait time. Set explicit connect/read timeouts.
  • Check the configured timeout itself (default 3 s, max 15 min), and whether an API Gateway in front has its own timeout limit.
C7Your production RDS database suddenly has high CPU and increased query latency. How would you investigate?List 2 · Q07Should
Say this

"I'd check what changed first: a new deploy often brings a new or unindexed query. Then I'd open Performance Insights, which shows the top SQL statements and wait events causing the load. I'd look at CloudWatch for CPU, DatabaseConnections, Read/Write IOPS and FreeableMemory, to see if it's a connection storm or I/O. For the fix: short term, kill a runaway query, roll back the bad deploy, or scale up the instance class (with Multi-AZ that's a quick failover). Longer term: add the missing index, cache with ElastiCache, move reads to read replicas, and use RDS Proxy for connection pooling."

  • Slow query log (MySQL) / pg_stat_statements (Postgres) → EXPLAIN the top query.
  • Burstable DB classes (db.t3/t4g) can run out of CPU credits.
  • Connections spiking = app autoscaled, or connection leaks.

As a junior: you don't need to tune SQL. Saying "Performance Insights → top SQL → work with the developers on an index" is exactly right.

C8How do IAM roles, load balancers, Auto Scaling and disaster recovery work together?List 1 · Q12Should
Say this

"Think of one request and one server's life. The load balancer receives traffic and sends it only to healthy instances across several AZs. The Auto Scaling Group keeps the right number of instances: it adds them when load rises, and replaces any that fail health checks. Each instance launches from a launch template with an IAM role attached, so the app gets temporary credentials to reach S3 or Secrets Manager, with no keys stored on disk. That matters because instances are disposable. Disaster recovery is the same idea at region level: backups, AMIs and infrastructure code ready in another region, and DNS failover, so we can rebuild or switch over."

ALBhealth checks, spreads load
ASGscale + self-heal across AZs
Launch templateAMI, type, SG, IAM role
DRsnapshots, IaC, Route 53 failover

🐳 Docker & containers

D1Write a Dockerfile for a simple web app, then build, run and push it to a registry.Added from JD: "Docker for building and containerizing"Must
Say this

"I start from a small, pinned base image, copy the dependency file first and install, so that layer is cached, then copy the code. I run as a non-root user, expose the port, and set the start command. I build with a meaningful tag, usually the git commit SHA, test it locally with docker run, then log in to the registry, like ECR or ACR, tag and push. I also add a .dockerignore so .git, local env files and secrets never go into the image."

# Dockerfile (Python Flask example)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -r appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

# .dockerignore
.git
.env
__pycache__/

# build, run, test
docker build -t shop-api:$(git rev-parse --short HEAD) .
docker run -d -p 8000:8000 --name api shop-api:3f2c1ab
curl localhost:8000/health

# push to ECR
aws ecr get-login-password --region ap-southeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com
docker tag shop-api:3f2c1ab 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/shop-api:3f2c1ab
docker push 123456789012.dkr.ecr.ap-southeast-1.amazonaws.com/shop-api:3f2c1ab
# Azure: az acr login --name myregistry ; docker push myregistry.azurecr.io/shop-api:3f2c1ab

Detail that impresses: "Listen on 0.0.0.0, not 127.0.0.1, inside a container, otherwise the port mapping can't reach it."

D2How do you troubleshoot container crashes, OOMKilled events and image pull failures?List 1 · Q03Must
Say this

"For a crash, I check the exit code and the logs of the dead container: docker ps -a, docker logs, and docker inspect for the exit code. Exit 1 is usually an app error like a missing env var. 127 means the start command wasn't found. 137 means it was killed, often for memory. For OOMKilled, inspect shows OOMKilled: true: either the memory limit is too low for normal use, or the app leaks memory, which I check by watching usage over time with docker stats or metrics. For image pull failures, I try the pull by hand: is the name and tag right, am I logged in to the private registry, am I hitting Docker Hub rate limits, and can the host even reach the registry over the network?"

docker ps -a                                   # status + exit code
docker logs --tail 200 web
docker inspect web --format 'exit={{.State.ExitCode}} oom={{.State.OOMKilled}} err={{.State.Error}}'
docker stats --no-stream
docker events --since 30m --filter container=web
docker pull myrepo/web:1.4.2                   # reproduce the pull error
Pull errorCause
manifest unknown / not foundWrong tag or repository name
unauthorized / deniedNot logged in, expired token, no IAM permission (ECR) or AcrPull role (ACR)
toomanyrequestsDocker Hub rate limit → authenticate or mirror the image to ECR / ACR
i/o timeout / no such hostNetwork: no NAT, no VPC endpoint, DNS, proxy
no matching manifest for linux/amd64Image built only for arm64 (or the other way round)

In Kubernetes: the same ideas appear as CrashLoopBackOff, OOMKilled and ImagePullBackOff. See K3.

D3How do multi-stage builds, image layers and Docker networking affect performance and security?List 1 · Q04Should
Say this

"Multi-stage builds let me compile in one stage with all the build tools, then copy only the final artifact into a small runtime image. That makes it smaller, so it's faster to pull and starts faster, and it's more secure because there are no compilers or source code for an attacker to use, and fewer packages with vulnerabilities. Layers are cached, so I order the Dockerfile from least to most frequently changed to make builds fast. I also remember that deleting a file in a later layer doesn't remove it from the image, so secrets must never be copied in. For networking, the default bridge network isolates containers and uses port mapping. Host networking is a little faster but removes isolation. I'd only publish the ports that are needed."

  • Example size: golang:1.23 build image ≈ 800 MB → final distroless or alpine image ≈ 15–30 MB.
  • Security extras: pin base image versions or digests, run as non-root, scan with Trivy or Grype, read-only root filesystem, use BuildKit secrets (RUN --mount=type=secret) for build-time tokens.
  • User-defined bridge networks give DNS by container name. In Kubernetes, the CNI plugin replaces Docker networking.

☸️ Kubernetes

K1Write a Deployment and a Service for a web app that reads its config from a ConfigMap. Explain each part.Added from JD: "writing and updating manifests"Must
Say this

"The Deployment says: run three replicas of this image, with these labels. Its selector must match the pod template labels. I set a specific image tag, resource requests and limits, readiness and liveness probes, and load environment variables from a ConfigMap. The Service selects pods by the same label and gives them a stable name and port inside the cluster. I apply it with kubectl apply -f, check with kubectl rollout status, and confirm the Service has endpoints."

The full annotated YAML is in Chapter 6.3. Practise writing it from memory: apiVersion, kind, metadata, spec is the skeleton of every object.

kubectl apply -f web.yaml
kubectl rollout status deploy/web -n shop
kubectl get pods -l app=web -n shop
kubectl get endpoints web -n shop      # should list 3 pod IPs
kubectl run tmp --rm -it --image=curlimages/curl -n shop -- curl -s http://web/health

Tip: you don't need to memorise every field. kubectl create deployment web --image=nginx --dry-run=client -o yaml generates a starting manifest, and kubectl explain deployment.spec documents the fields. Saying this in the interview is a plus.

K2You updated a ConfigMap, but the application still uses the old values. Why, and how do you fix it?Added from JD: "managing ConfigMaps"Must
Say this

"Environment variables from a ConfigMap are read only when the container starts, so changing the ConfigMap doesn't affect running pods. I need to restart them, ideally with kubectl rollout restart deployment, which replaces pods gradually without downtime. If the ConfigMap is mounted as a file, Kubernetes does update the file after a short delay, but the app still has to re-read it. In Helm we often add a checksum of the config as a pod annotation, so a config change automatically triggers a rollout. I'd keep the ConfigMap in git so the change is reviewed and can be rolled back."

kubectl edit configmap web-config -n shop        # or better: change YAML in git → kubectl apply
kubectl rollout restart deploy/web -n shop
kubectl rollout status deploy/web -n shop
kubectl exec deploy/web -n shop -- printenv LOG_LEVEL   # verify
# Helm trick in the Deployment template:
annotations:
  checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
K3A Pod is stuck in Pending, CrashLoopBackOff or ImagePullBackOff. How do you debug it? (Also asked as: EKS pods stuck in Pending / CrashLoopBackOff.)List 1 · Q05 + List 2 · Q06Must
Say this

"My first two commands are always kubectl describe pod, where I read the Events at the bottom, and kubectl logs --previous. Pending means the scheduler can't place the pod. The event says why: not enough CPU or memory for the requests, a taint or node selector mismatch, or a volume that can't bind. The fix is lower requests, or more nodes through the autoscaler. ImagePullBackOff means the image can't be pulled: wrong name or tag, missing registry permission, or no network route to the registry. CrashLoopBackOff means the container starts and dies. I check the previous logs and the exit code: 1 is an app error like missing config, 137 with OOMKilled means raise the memory limit or fix a leak. I also check whether a liveness probe is killing a healthy but slow-starting app."

kubectl get pods -n shop
kubectl describe pod <pod> -n shop | sed -n '/Events/,$p'
kubectl logs <pod> -n shop --previous
kubectl get pod <pod> -n shop -o jsonpath='{.status.containerStatuses[0].lastState}'
kubectl get events -n shop --sort-by=.lastTimestamp | tail -20
kubectl describe nodes | grep -A5 "Allocated resources"   # is the cluster full?
EKS-specific causes (List 2 · Q06)
  • Pending: node group at max size / Cluster Autoscaler or Karpenter not working · pod IP exhaustion (the VPC CNI gives each pod a VPC IP, and the max pods per node depends on instance type) · EBS volume in a different AZ from the node.
  • ImagePullBackOff: the node IAM role lacks ECR read permission · private nodes with no NAT and no ECR VPC endpoints.
  • CrashLoopBackOff: the pod can't reach AWS services because IRSA / Pod Identity isn't set up · the image was built on an M-series Mac (arm64) but runs on amd64 nodes → exec format error.

AKS equivalents: the AcrPull role assignment for the kubelet identity, Azure CNI subnet IP exhaustion, the cluster autoscaler on node pools.

What they're testing: this is the most common K8s interview question. Being fluent with describe → events → logs --previous → exit code is essential.

K4How would you troubleshoot Service, Ingress, DNS and NetworkPolicy failures?List 1 · Q08Must
Say this

"I follow the path: client → Ingress → Service → pods, and test each hop from inside the cluster with a debug pod. For the Service, the number one check is kubectl get endpoints. If it's empty, the selector labels don't match the pods, or the pods aren't Ready. Then I check that targetPort matches the container port. For the Ingress, I check the host and path rules, the ingress class, and the ingress controller's logs. For DNS, I run nslookup my-svc.my-namespace from a pod and check CoreDNS is healthy. For NetworkPolicy, if there's a default deny, I need an explicit allow, and egress to DNS on port 53 is often forgotten, so everything fails to resolve."

Ingressrules, class, controller logs, TLS
Serviceselector, ports, endpoints
PodsReady? listening on targetPort?
DNSCoreDNS, FQDN
NetworkPolicyingress + egress allow
kubectl get svc,endpoints web -n shop
kubectl get pods -n shop --show-labels               # compare with the Service selector
kubectl describe ingress web -n shop
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50
kubectl run dbg --rm -it --image=nicolaka/netshoot -n shop -- bash
  nslookup web.shop.svc.cluster.local
  curl -sv http://web.shop:80/health                 # via the Service
  curl -sv http://10.0.11.57:8080/health             # straight to a pod IP
kubectl get pods -n kube-system -l k8s-app=kube-dns  # CoreDNS healthy?
kubectl get networkpolicy -n shop

Trick: if the pod IP works but the Service doesn't → Service/selector/port problem. If the Service works but the Ingress doesn't → Ingress/controller problem. Bisect.

K5The app is healthy according to kubectl get pods, but users report 504 errors. Walk me through your troubleshooting flow.List 3 · Q2Must
Say this

"'Running' only means the process is alive. It doesn't mean requests succeed. A 504 means a proxy in front, the load balancer or the ingress controller, waited for the app and gave up. So I'd find out which hop times out. First I check whether it's all requests or only some endpoints, and whether the timing lines up with a deploy. Then I test from inside: port-forward to a pod and curl it directly. If the pod answers fast, the problem is between the LB and the pod: empty Service endpoints, a NetworkPolicy, or a node security group. If the pod itself is slow, it's the app or its dependency, like a slow database or CPU throttling. I'd also compare timeouts: if the app needs 70 seconds but the ingress timeout is 60, every slow request becomes a 504."

  1. Scope: all paths or one? All pods or some? Since a deploy? Check the ingress / ALB metrics for 504 rate and latency.
  2. Ingress controller logs: upstream timed out + which upstream pod IP.
  3. Bypass layers: kubectl port-forward pod/x 8080 + curl -w '%{time_total}'. Pod fast → network/LB path. Pod slow → app.
  4. Path issues: kubectl get endpoints (empty or stale?), readiness probes too shallow (pod "Ready" but broken), NetworkPolicy, node SG allowing the LB on the NodePort range / pod IPs.
  5. App issues: DB or external API latency (traces!), CPU throttling from a low CPU limit (container_cpu_cfs_throttled_seconds_total), thread or connection pool exhausted.
  6. Timeout mismatch: client < CDN < ALB idle timeout < ingress proxy-read-timeout < app. Fix the slow request, or align the timeouts.
  7. Mitigate: roll back if a deploy caused it; scale out if it's saturation.

What they're testing: "green dashboards ≠ working". Readiness probes should test the thing that matters.

K6How do requests, limits, HPA and Cluster Autoscaler work together?List 1 · Q06Should
Say this

"Requests are what a pod reserves. The scheduler uses them to decide which node has room. Limits are the maximum: if a container exceeds its memory limit it's OOMKilled, and if it hits its CPU limit it's throttled. The HPA watches metrics like CPU as a percentage of the request, and changes the number of replicas. When new replicas can't fit on existing nodes, they go Pending, and the Cluster Autoscaler or Karpenter adds nodes. When nodes are underused, it removes them. So the chain is: traffic up → HPA adds pods → pods Pending → autoscaler adds nodes. That's why requests must be realistic: too low and nodes get overloaded, too high and you pay for idle capacity."

kubectl autoscale deploy web -n shop --cpu-percent=70 --min=3 --max=20
kubectl get hpa -n shop          # TARGETS shows current/target; "<unknown>" = no metrics-server or no requests set

FinOps angle: "Requests decide how many nodes we pay for. In FinOps I'd compare requested vs actual usage to find over-provisioned workloads. Tools like Kubecost or the VPA's recommendations help with that."

K7How do Helm releases, rollback and configuration management work?List 1 · Q07Should
Say this

"Helm is a package manager for Kubernetes. A chart is a set of templated YAML files plus a values.yaml with defaults. When I install it, I get a release, and every upgrade creates a new numbered revision, stored in the cluster. So I can see the history and roll back to any revision with helm rollback. For configuration, I keep one chart and a values file per environment, like values-dev.yaml and values-prod.yaml, in git. In CI I use helm upgrade --install with --atomic, which automatically rolls back if the upgrade fails. And I avoid editing Helm-managed resources by hand with kubectl, because the next upgrade will overwrite them."

helm upgrade --install web ./charts/web -n shop -f values-prod.yaml --atomic --timeout 5m
helm list -n shop
helm history web -n shop
helm rollback web 7 -n shop          # back to revision 7
helm diff upgrade web ./charts/web -f values-prod.yaml   # preview (helm-diff plugin)
helm template ./charts/web -f values-prod.yaml           # render YAML locally to check
K8A cluster upgrade works in staging but breaks CoreDNS in production. How do you approach patching and restoring service?List 3 · Q9Stretch
Say this

"CoreDNS down means almost every service-to-service call fails, so this is a high-severity incident. I'd declare it and communicate first. Then restore DNS as fast as possible: check the CoreDNS pods and logs and the Corefile ConfigMap. The most likely reason it worked in staging but not prod is a difference: prod often has custom DNS config, like forwarding to corporate DNS, that the upgrade overwrote or that's incompatible with the new version. The fastest fix is to restore the previous CoreDNS config or add-on version that matches the cluster version. The control plane usually can't be downgraded, so I fix forward on the add-on. Then I verify with nslookup from a test pod and watch error rates recover. Afterwards: back up configs before upgrades, keep staging closer to prod, and upgrade in smaller steps."

kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
kubectl -n kube-system get configmap coredns -o yaml        # compare with your git backup
kubectl -n kube-system rollout restart deploy/coredns
aws eks describe-addon --cluster-name prod --addon-name coredns   # version compatible with the k8s version?
aws eks update-addon --cluster-name prod --addon-name coredns --addon-version <known-good> --resolve-conflicts PRESERVE

🔁 Git & CI/CD

P1Walk me through how you make an infrastructure change with Git. How do you handle a merge conflict?Added from JD: "basic understanding of Git"Must
Say this

"I never change main directly. I create a branch, make a small, focused change, commit it with a clear message, push, and open a pull request. CI runs checks, for example terraform plan or a manifest lint, and posts the result, and a teammate reviews it. After approval it's merged and the pipeline deploys it. For a merge conflict, I update my branch from main. Git marks the conflicting lines between <<<<<<<, ======= and >>>>>>>. I decide which version is correct, or combine them, remove the markers, test, and commit. If I'm not sure about the other person's change, I ask them."

git switch main && git pull
git switch -c fix/raise-web-memory-limit
# edit k8s/web.yaml
git add k8s/web.yaml
git commit -m "web: raise memory limit 512Mi → 768Mi (OOMKilled at peak)"
git push -u origin fix/raise-web-memory-limit      # open PR
# conflict:
git fetch origin && git rebase origin/main         # or: git merge origin/main
# fix the files, then
git add k8s/web.yaml && git rebase --continue
git push --force-with-lease                        # only on YOUR branch

Good commit message = what + why. Reviewers and future you will thank you.

P2How do you debug a Jenkins, GitHub Actions or GitLab CI pipeline that suddenly fails?List 1 · Q09Must
Say this

"'Suddenly' means something changed, so I compare the last green run with the first red one. I read the log for the first error, not the last line. Then I ask what changed: our code; a dependency that isn't pinned and got a new version; the base Docker image; the runner image; or an expired credential or token. It can also be an external outage, a rate limit, or a full disk on a self-hosted runner. I re-run once to check whether it's flaky. Then I try to reproduce the failing step locally with the same versions. The fix is usually pinning versions, renewing the secret, or fixing the code, and adding a check so it fails earlier and more clearly next time."

Symptom in the logLikely cause
401 / 403 / ExpiredTokenSecret or token rotated or expired, OIDC role trust changed
Could not resolve dependency / new test failures nobody causedUnpinned dependency released a new version → use lock files
No space left on deviceSelf-hosted runner / Jenkins agent disk full → clean the Docker cache
toomanyrequestsDocker Hub pull rate limit
Passes on re-runFlaky test or a timing issue → fix it or quarantine the test
command not found after a runner updateubuntu-latest moved to a new version → pin the runner image
P3A deployment succeeded, but traffic is still going to the old version. Explain exactly where you start debugging.List 3 · Q1Should
Say this

"First I'd prove which version is answering, with a version endpoint or response header, and whether it's every user or just some. Then I'd walk the path from the user inward, because any layer can still point at the old version. Caches: the browser or the CDN serving cached content, which needs an invalidation. DNS: still pointing at the old load balancer or blue environment because of TTL. Load balancer: the new targets aren't registered, or the listener weights still send 100% to the old target group. Kubernetes: did the rollout really finish? Is the Service selector still matching old pods? Did the pipeline deploy to the wrong namespace or cluster? And a classic: the image tag didn't change, for example 'latest', so the nodes used the cached old image."

curl -sI https://app.example.com | grep -i -E "x-version|age|x-cache"   # CDN cache hit?
kubectl rollout status deploy/web -n shop
kubectl get deploy web -n shop -o jsonpath='{.spec.template.spec.containers[0].image}'
kubectl get pods -n shop -l app=web -o custom-columns=NAME:.metadata.name,IMAGE:.spec.containers[0].image
kubectl get svc web -n shop -o jsonpath='{.spec.selector}'
kubectl config current-context        # did the pipeline deploy to the right cluster?

Prevention: immutable image tags (git SHA), a /version endpoint, a post-deploy smoke test that checks the version.

P4How do you implement zero-downtime deployments, automated rollback and approval gates?List 1 · Q10Should
Say this

"Zero downtime: in Kubernetes, a rolling update with maxUnavailable: 0 and good readiness probes, so new pods only get traffic when they're ready, plus graceful shutdown so old pods finish their requests. For bigger risk, blue-green or canary. Automated rollback: the pipeline watches health after deploying. helm --atomic or kubectl rollout status failing triggers a rollback, and tools like Argo Rollouts or CodeDeploy can roll back when error-rate alarms fire. Approval gates: production deploys need a manual approval, for example GitHub Environments with required reviewers, a GitLab protected environment with a manual job, or a Jenkins input step. And database changes must be backward compatible, so old and new versions can run together."

strategy:
  type: RollingUpdate
  rollingUpdate: { maxUnavailable: 0, maxSurge: 25% }
# + readinessProbe + terminationGracePeriodSeconds + preStop sleep for draining
P5Your rollback script fails during a production outage. You have 5 minutes before an SLA breach. Walk me through your decision.List 3 · Q7Should
Say this

"I wouldn't debug the script. In 5 minutes the goal is to restore service by the fastest safe path. First I tell the incident lead and call for help, because escalating early is right. Then I pick a manual way to get back to the last known good version that skips the broken script: kubectl rollout undo or helm rollback, re-running the pipeline with the previous image tag, switching the load balancer back to the blue environment, or turning off the feature flag. If none of that is possible, I reduce the impact, by scaling out or failing over. I verify with the dashboards, and I keep giving status updates. If we breach the SLA, we communicate it honestly. Afterwards: the postmortem, and testing rollbacks regularly, because an untested rollback isn't a rollback."

0–1 minescalate, communicate, stop debugging the script
1–3 minmanual rollback / traffic switch / feature flag
3–5 minverify metrics, update status
Afterpostmortem, test rollbacks in drills

What they're testing: calm prioritisation, mitigation over perfection, communication, honesty.

P6The CI/CD pipeline takes 40+ minutes. The CTO wants it under 10 without adding hardware. What will you optimise?List 3 · Q4Stretch
Say this

"First I'd measure: get the duration of every stage and job, because you can't optimise what you don't measure, and usually two or three steps take most of the time. Then the usual big wins: caching dependencies and Docker layers; running jobs in parallel instead of one after another, and splitting tests into shards; doing less, like running only the tests affected by changed paths, shallow git clones, and building the image once and promoting it through environments instead of rebuilding; and failing fast by putting lint and unit tests first. Slow end-to-end tests can move to after merge or a nightly run, as long as there's a smoke test before prod. And I'd remove flaky retries and sleeps."

  • Docker: order layers well, --cache-from / BuildKit cache, a smaller base image, a .dockerignore.
  • Pre-built runner images with tools installed, instead of installing tools every run.
  • Track the pipeline time as a metric so it doesn't creep back up.

🧱 Terraform & IaC

T1How do you manage remote state, state locking and concurrent Terraform runs?List 1 · Q13Should
Say this

"The state file is Terraform's record of which real resources it manages, so it must be shared, safe and never edited by hand. I store it in a remote backend: on AWS an S3 bucket with versioning and encryption, on Azure a Storage Account container. Locking stops two applies at the same time from corrupting it. On S3 that's the native lock file in newer Terraform versions, or a DynamoDB table in older setups. Azure uses blob leases automatically. To avoid concurrent runs, applies only happen from the CI pipeline, not laptops, and I split state per environment and component, so a network change and an app change don't block each other. force-unlock is only for a lock I'm sure is stale."

  • Bucket versioning = you can recover a previous state version if it gets corrupted.
  • Restrict who can read the state: it can contain secrets in plain text.
  • Layout example: network/prod, eks/prod, app/prod, each with its own state key.
terraform init            # connects to the backend
terraform plan -out=tfplan
terraform apply tfplan    # apply exactly what was reviewed
terraform force-unlock <LOCK_ID>   # only if the run that held it is truly dead
T2How do you detect and resolve infrastructure drift without causing outages, especially when the infra is live and critical?List 1 · Q14 + List 3 · Q6Should
Say this

"Drift means the real infrastructure no longer matches the code, usually because someone changed it in the console. I detect it with terraform plan, ideally on a schedule in CI so we find out early. To resolve it safely, I don't just run apply. I read the plan and first ask: was the manual change intentional, like a hotfix during an incident? If it was, I update the code to match reality, so the plan becomes clean, with zero risk. If it was a mistake, I revert it with a targeted, reviewed apply in a quiet window. The danger signal is any resource marked replace (-/+), which can mean downtime. I'd stop and find another way, for example fixing the attribute that forces replacement, or using lifecycle rules. Then I prevent drift: console access read-only for prod, and changes only through PRs."

terraform plan -refresh-only        # show drift only, change nothing
terraform apply -refresh-only       # accept reality into the STATE (code still must be updated!)
terraform plan -target=aws_security_group.web   # narrow scope (use sparingly)

lifecycle {
  prevent_destroy = true            # protect critical resources
  ignore_changes  = [desired_capacity]  # field managed by the autoscaler, not Terraform
}

Key phrase: "Make the plan boring before applying: no destroys, no replacements on critical resources."

T3Terraform apply fails because an AWS resource already exists. How do you resolve it safely?List 2 · Q12Should
Say this

"That error means the resource exists in AWS but not in Terraform's state. Maybe someone created it by hand, or a previous apply half-failed, or it was removed from state. I don't delete the real resource. I import it into state, with terraform import or an import block, and adjust the code until terraform plan shows no changes. If it's a duplicate nobody needs, I confirm with the owner before removing it. If the code was just refactored and renamed, I use a moved block or terraform state mv instead of destroying and recreating."

# Terraform 1.5+ import block
import {
  to = aws_s3_bucket.logs
  id = "acme-prod-logs"
}
# then
terraform plan     # fix the code until: "No changes."

# older CLI way
terraform import aws_s3_bucket.logs acme-prod-logs

# refactor rename without destroying
moved {
  from = aws_s3_bucket.log
  to   = aws_s3_bucket.logs
}

📈 Monitoring, incidents & SRE

O1An alert fires at 2 a.m.: error rate is high on the main API. What do you do, step by step?Added from JD: "respond to alerts, assist in troubleshooting incidents"Must
Say this

"I acknowledge the alert so others know someone's on it, and open the runbook linked from the alert. I check the dashboard to understand the impact: how many errors, which endpoints, how many users. Then what changed: recent deploys, config changes, an AWS or Azure health event. If there was a recent deploy, I roll it back, because restoring service comes before finding the root cause. If it's bigger than I can handle, or past the runbook's time limit, I escalate to the on-call owner without waiting. I post short updates in the incident channel. When it's resolved, I confirm the metrics are back to normal and write up the timeline for the postmortem."

1Ack+ open runbook
2Impactdashboard
3Changes?deploys, config
4Mitigaterollback / scale
5Escalateif needed, early
6Update + documenttimeline

What they're testing: calm process, communication, knowing when to escalate. Juniors who escalate early are trusted more than those who hide problems.

O2How would you use Prometheus and Grafana to investigate a production latency spike?List 1 · Q15Must
Say this

"I start on the service's Grafana dashboard with the golden signals: latency, traffic, errors, saturation. I look at p95 and p99 latency, not the average, because averages hide slow requests. Then I narrow it down: which service, which endpoint, which pod or node, and when it started. I overlay deploy annotations to see if it lines up with a release. Then I check saturation, like CPU throttling, memory, and database connections, and the latency of dependencies. Once I know where, I jump to logs or traces for the slow requests to find exactly what's slow."

# p99 latency per service (histogram)
histogram_quantile(0.99, sum by (le, service) (rate(http_request_duration_seconds_bucket[5m])))
# error rate %
100 * sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
# CPU throttling per pod
rate(container_cpu_cfs_throttled_seconds_total{namespace="shop"}[5m])
# request rate
sum by (service) (rate(http_requests_total[5m]))

Words to use: "scrape", "PromQL", "histogram", "p99", "annotations", "drill down", "correlate with deploys".

Cloud-native equivalent: CloudWatch ALB TargetResponseTime (p99 statistic) + Container Insights · Azure Monitor + Application Insights "Performance" blade.

O3Latency and errors increase right after a deployment, even though the deployment "succeeded". How do you investigate and roll back?List 1 · Q21 + List 2 · Q18Must
Say this

"If the problem started right at the deploy and users are affected, I roll back first and investigate after. A successful deploy only means the pods started, not that the code is good. I confirm the timing matches the deploy on the dashboard, then roll back with kubectl rollout undo, helm rollback, or by redeploying the previous image through the pipeline, and watch the metrics recover. That also proves the cause. Then, without pressure, I compare the versions: code diff, new database queries, config or environment changes, resource limits, new dependencies. And I'd suggest adding a canary step and an automatic rollback on error rate, so next time it's caught with 5% of traffic, not 100%."

kubectl rollout history deploy/web -n shop
kubectl rollout undo deploy/web -n shop            # or --to-revision=N
kubectl rollout status deploy/web -n shop
# CloudWatch Logs Insights: errors per minute since the deploy
fields @timestamp, @message | filter @message like /ERROR/ | stats count() by bin(1m)
O4The SRE says infra is stable, developers say the system is slow, and monitoring is all green. Who do you believe, and what do you check first?List 3 · Q5Should
Say this

"I believe the user experience, and then I get data, rather than choosing a side. If people say it's slow and dashboards are green, the dashboards are probably measuring the wrong thing. First I'd get a concrete example: which page or API, when, for whom. Then I'd check what monitoring is missing: averages instead of p99, server-side timing that excludes the network or front end, health checks that only test 'is the process up', or one region or customer that's hidden in the totals. I'd reproduce it with curl timings or a trace, check dependencies like the database or a third-party API, then add an SLI that would have shown this, so the next time green really means good."

What they're testing: diplomacy plus data. "Green dashboards and unhappy users = we're monitoring the wrong thing."

O5How do you define SLIs, SLOs and error budgets for an SRE team?List 1 · Q17Should
Say this

"An SLI is a measurement of what users feel, usually 'good events divided by all events', for example the percentage of requests that succeed and are faster than 300 ms. An SLO is the target for that SLI over a time window, like 99.9% over 30 days. The error budget is what's left: 0.1%, which is about 43 minutes of failure a month. If we're within budget, teams can ship fast. If the budget is burning too fast, we slow down releases and focus on reliability. An SLA is the contract version with customers, with penalties, so it's set looser than the SLO."

SLOAllowed downtime / 30 days
99%7.2 hours
99.9%43.2 minutes
99.95%21.6 minutes
99.99%4.3 minutes
99.9% good requests (SLO)

The red sliver is your error budget. Spend it on releases and experiments, not on avoidable outages.

FinOps link: "Each extra nine costs much more, with more redundancy and more regions. So I'd help pick an SLO that matches business need, not '100%'."

O6How do logs, metrics and traces integrate through OpenTelemetry?List 1 · Q16Stretch
Say this

"OpenTelemetry is a vendor-neutral standard and set of SDKs for producing all three signals. Apps are instrumented once, and the data goes to an OTel Collector, which receives it, processes it, for example batching, filtering or adding tags, and exports it to whatever backend we use: Prometheus, Grafana Tempo and Loki, Datadog, CloudWatch and X-Ray, or Azure Monitor. The key to integration is context propagation: every request carries a trace ID across services, and that same trace ID is written into the logs. So I can go from a latency spike in a metric, to an example trace of a slow request, to the exact log lines for that request."

App + OTel SDKtraces, metrics, logs with trace_id
OTel Collectorreceive → process → export
BackendsPrometheus · Tempo/Jaeger · Loki · Datadog · CloudWatch · Azure Monitor

🔐 Security & IAM

S1An IAM role has the required permissions, but the application still gets AccessDenied when accessing S3. How would you troubleshoot?List 2 · Q10Must
Say this

"First I'd confirm the app is really using that role, with aws sts get-caller-identity from where the app runs, because environment variables or an old credentials file can override it. Then I'd remember that AWS needs every layer to allow the request, and any explicit deny wins. So beyond the role's policy I check: the bucket policy, for example a deny unless the request comes over HTTPS or through a VPC endpoint; the KMS key policy if the bucket uses SSE-KMS, because the role also needs kms:Decrypt; SCPs from AWS Organizations; permission boundaries; a VPC endpoint policy; and whether the ARN is right: bucket vs bucket/*. CloudTrail shows the exact denied call, and the IAM Policy Simulator helps test."

  1. Who am I really? aws sts get-caller-identity (in a pod: is IRSA / Pod Identity actually attached?).
  2. Resource ARN: s3:ListBucket works on arn:aws:s3:::bucket, s3:GetObject on arn:aws:s3:::bucket/*. A mistake here is very common.
  3. Bucket policy explicit deny (conditions: aws:SecureTransport, aws:SourceVpce, IP).
  4. KMS: the key policy must allow the role. The error may say AccessDenied with no mention of KMS.
  5. Organisation: SCP denying the region or the action; permission boundary on the role.
  6. Cross-account: both the role's policy and the bucket policy must allow. Object ownership / ACLs on objects uploaded by another account.
  7. Evidence: CloudTrail event → errorCode, errorMessage. Recent AWS error messages often name the policy type that denied it.

Fun fact: without s3:ListBucket, a GET for a missing object returns 403 instead of 404, which looks like a permissions problem but is actually a missing file.

Azure version: the Managed Identity needs a data-plane role like "Storage Blob Data Reader". "Contributor" alone doesn't allow reading blobs with Entra ID auth. Also check storage firewall / private endpoint rules.

S2A secret was accidentally committed to GitHub and has already been cloned. What are your next exact steps?List 3 · Q8Must
Say this

"I assume the secret is compromised, because once it's pushed and cloned, deleting the commit doesn't take it back. Step one, immediately: revoke or rotate the secret at its source, like deactivating the AWS access key or rotating the database password, and update the apps that use it through the secrets manager. Step two: check the audit logs, like CloudTrail, for any use of that key since the leak. Step three: remove it from the code and, if policy requires, from git history with git filter-repo or BFG, then force-push and ask everyone to re-clone. Step four: report it to security as an incident. Step five: prevent it: enable GitHub secret scanning and push protection, add a pre-commit hook like gitleaks, and use short-lived credentials like OIDC so there are no long-lived keys to leak."

1Rotate / revokeminutes, not hours
2Check usageCloudTrail, provider logs
3Clean historyfilter-repo, force-push, re-clone
4Reportsecurity incident
5Preventpush protection, gitleaks, OIDC
aws iam update-access-key --user-name ci-bot --access-key-id AKIA... --status Inactive
aws iam create-access-key --user-name ci-bot     # only if a key is still truly needed
# CloudTrail: look up events by access key ID to see what it did
git filter-repo --replace-text secrets-to-remove.txt

The #1 mistake candidates make: starting with "I'd delete the commit". Rotation comes first.

S3How do you manage secrets securely across CI/CD pipelines and Kubernetes?List 1 · Q18Should
Say this

"Secrets live in one managed store, like AWS Secrets Manager or Azure Key Vault, never in git, images or plain config. In CI/CD, I avoid long-lived cloud keys entirely: GitHub Actions uses OIDC to assume an IAM role or federate to Azure, and any remaining pipeline secrets are scoped to an environment and masked in logs. In Kubernetes, native Secrets are only base64, so I enable encryption at rest with KMS and restrict access with RBAC. Better still, I sync from the secret store with the External Secrets Operator or the Secrets Store CSI driver. Pods access cloud services with workload identity, like IRSA or EKS Pod Identity, or Azure Workload Identity, instead of keys. And I rotate secrets regularly."

# External Secrets Operator: pull from AWS Secrets Manager into a K8s Secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db-creds, namespace: shop }
spec:
  refreshInterval: 1h
  secretStoreRef: { name: aws-secrets, kind: ClusterSecretStore }
  target: { name: db-creds }
  data:
  - secretKey: password
    remoteRef: { key: prod/shop/db, property: password }
S4How would you integrate image scanning, dependency checks and IaC security scans into CI/CD?List 1 · Q19Stretch
Say this

"I'd 'shift left': scan as early as possible, so problems are cheap to fix. On every pull request: a secret scan like gitleaks, a dependency scan like Dependabot or Snyk, and an IaC scan like Checkov or Trivy config on Terraform and Kubernetes YAML. After the image is built: an image scan with Trivy, Grype, ECR scanning or Defender for Containers. The pipeline fails on critical or high findings with a known fix, and lower ones become tickets, so developers aren't blocked by noise. Optionally, sign images and have an admission controller like Kyverno only allow signed, scanned images into the cluster."

Pre-commitgitleaks
PRSCA deps + IaC scan (Checkov / Trivy)
Buildimage scan (Trivy) + SBOM
Registrycontinuous scanning, signing
Clusteradmission policy (Kyverno / OPA)
trivy image --severity CRITICAL,HIGH --exit-code 1 --ignore-unfixed myapp:3f2c1ab
trivy config ./terraform
checkov -d ./terraform

🏗️ High availability & disaster recovery

H1A production application becomes unavailable because of an Availability Zone failure. How would you restore service and improve availability?List 2 · Q15Should
Say this

"To restore: confirm it's really an AZ issue with the AWS Health Dashboard, then move capacity to the healthy AZs. If the app is in an Auto Scaling Group across several AZs, it should replace instances automatically. I'd check it's doing so, and raise desired capacity if the remaining AZs are overloaded. A Multi-AZ RDS database fails over on its own in a minute or two. If something was single-AZ, I'd restore it in another AZ from the latest snapshot or AMI. To improve: run across at least two or three AZs with enough spare capacity to lose one, Multi-AZ databases, stateless app servers, load balancer health checks, everything in IaC, and practise failure with game days."

FinOps angle: "Multi-AZ isn't free: there's standby capacity and cross-AZ data transfer. I'd match the design to how critical the workload is."

H2How would you design highly available infrastructure across AWS, Azure or GCP?List 1 · Q11Stretch
Say this

"The principle is 'no single point of failure'. In one region: DNS, then a load balancer across at least two Availability Zones, an Auto Scaling Group or Kubernetes nodes spread across those zones, stateless app servers, a Multi-AZ managed database, and S3 or Blob for files, which are already replicated. Health checks everywhere, so failed parts are removed automatically, and everything defined in code. If the business needs more, add a second region with data replication and DNS failover. Then I'd test it, because untested HA is a guess. The picture in Chapter 2 is exactly this design."

Point to the big-picture diagram. Azure version: Availability Zones + zone-redundant Application Gateway + VM Scale Sets / AKS across zones + zone-redundant Azure SQL.

H3An S3 bucket with production data, or important objects, was accidentally deleted. How would you investigate and recover?List 2 · Q13Should
Say this

"Recovery first. If versioning was on, a delete only adds a 'delete marker', so I remove the marker or copy back the previous version and the data returns. If the whole bucket was deleted, the data is gone unless we have replication to another bucket or an AWS Backup copy, so I'd restore from that. To investigate, I use CloudTrail: DeleteBucket is logged by default, but individual DeleteObject calls only appear if data events are enabled, otherwise S3 server access logs. Then I find out who or what did it and why. Prevention: versioning, MFA delete or Object Lock for critical data, a bucket policy denying deletes, least-privilege IAM, and backups in another account."

aws s3api list-object-versions --bucket prod-data --prefix reports/ \
  --query 'DeleteMarkers[?IsLatest==`true`].[Key,VersionId]' --output text
aws s3api delete-object --bucket prod-data --key reports/q3.csv --version-id <delete-marker-id>   # "undelete"
H4Your AWS environment has a regional service disruption. How would you execute disaster recovery and fail over to another region?List 2 · Q19Stretch
Say this

"It depends on the DR strategy chosen in advance, based on RTO, how long we can be down, and RPO, how much data we can lose. During the event, I follow the DR runbook: declare the disaster, then promote the database copy in the second region, like a cross-region read replica or Aurora Global Database. I scale up the infrastructure there, which already exists in IaC, and switch DNS with Route 53 failover records or Application Recovery Controller. Then I verify with smoke tests and communicate. Failback later is planned just as carefully. The key point: DR only works if it's been practised."

StrategyWhat runs in region 2RTO / RPOCost
Backup & restoreOnly backupsHours / hours$
Pilot lightData replicated, core infra offTens of minutes$$
Warm standbySmall full copy runningMinutes$$$
Multi-site active/activeFull copy serving trafficNear zero$$$$

FinOps angle: this table is a cost conversation, and you're the person who can quantify it.

H5Design a secure, autoscaling Kubernetes platform with HA, monitoring and disaster recovery.List 1 · Q22Stretch
Say this

"I'd build it in layers. Infrastructure: managed Kubernetes, EKS or AKS, created with Terraform, with nodes in private subnets across three AZs. Scaling: the HPA for pods and Karpenter or the Cluster Autoscaler for nodes, with sensible requests. Traffic: an ingress controller behind a cloud load balancer with WAF and TLS. Security: RBAC, network policies, pod identity instead of keys, secrets from a secrets manager, image scanning and admission policies. Delivery: GitOps with Argo CD or Flux, so the cluster state is in git. Observability: Prometheus and Grafana, central logs, and alerts on SLOs. DR: everything is in code, so a cluster can be rebuilt; Velero backs up cluster resources and volumes, and data is replicated to a second region."

For a junior role: you won't be expected to design this alone. Show that you know the building blocks and say "I'd validate the design with senior engineers."

💰 Cost: your home advantage

F1The AWS bill suddenly increased 200–300% overnight, and no deployments happened. What's your step-by-step response?List 2 · Q09 + List 3 · Q3Must
Say this (this is your moment to shine)

"First I'd find where, using Cost Explorer at daily granularity, grouped by service, then by usage type, account, region and tags. Cost Anomaly Detection may have flagged it already. 'No deployments' doesn't mean no changes, so I'd consider four groups. Security: leaked keys used for crypto mining, often in unusual regions with GPU instances. If I see that, it's a security incident first: I'd revoke the keys and terminate the resources. Automation: autoscaling runaway, a recursive Lambda, a scheduled job, a log-level change causing huge CloudWatch Logs ingestion. Traffic: a spike or a bot driving data transfer or NAT gateway charges. Pricing: a Savings Plan or Reserved Instance that expired, so the same usage is now billed on-demand, or a one-time upfront charge. Then I use CloudTrail to see who created what, fix it, and set budgets and alerts so we catch it within hours next time."

  1. Locate: Cost Explorer → Daily → Group by Service → filter → Group by Usage type / Region / Linked account / Tag. CUR + Athena for resource-level detail.
  2. Classify: usage went up (resources, traffic) vs rate went up (commitment expired, pricing change) vs one-off charge.
  3. Security check: unfamiliar regions, RunInstances in CloudTrail from unknown principals, GuardDuty findings → incident response.
  4. Contain: stop or scale down the runaway resource, after confirming with the owner.
  5. Prevent: AWS Budgets alerts, Cost Anomaly Detection subscriptions, SCPs restricting regions and instance types, tagging enforcement, a commitment expiry calendar.
aws ce get-cost-and-usage --time-period Start=2026-10-20,End=2026-10-25 \
  --granularity DAILY --metrics UnblendedCost --group-by Type=DIMENSION,Key=SERVICE

Say it out loud in the interview: "This is exactly the kind of investigation I've done in FinOps, so here I can bring engineering and cost together."

F2A dev team needs a new environment for testing and research. How would you provision and set it up?Added from JD: "provision and set up environments for development teams"Should
Say this

"First I'd understand what they need: which services, how long, what data, who needs access. Then I'd build it from a reusable Terraform module, not by hand, so it's consistent and easy to delete later. It goes in an isolated place: a separate account, or a subscription and resource group in Azure, or at least a separate VPC or namespace, so testing can't hurt production. It gets least-privilege access for the team, tags for owner, project and expiry date, a budget alert, and an auto-shutdown schedule for nights and weekends. Then I'd give them a short README: how to access it, how to deploy, who to ask, and when it will be removed."

  • Kubernetes option: a namespace per team with ResourceQuota, LimitRange, RBAC RoleBinding and a NetworkPolicy.
  • Data: never copy raw production data. Use masked or synthetic data.
  • Lifecycle: an expiry tag + a cleanup job; review idle environments monthly.

FinOps angle: "Dev environments are often the largest waste. Auto-shutdown alone can save around 60–70% of their compute cost."

🤝 Behavioural, documentation & your story

B1"You don't have hands-on cloud engineering experience. Why should we hire you?"Added: you will almost certainly get thisMust
Say this (adapt it to your real experience)

"That's fair. I haven't held the engineer title yet. But I've worked with cloud infrastructure every day from the cost and usage side: I've analysed EC2 and storage usage, explained network and data transfer costs, and worked with engineering teams to rightsize and clean up resources safely. So I already understand how these systems are built and why. In the last few weeks I've been closing the hands-on gap in my lab: I've containerised an app, deployed it on a local Kubernetes cluster with a Deployment, Service and ConfigMap, broken it on purpose and fixed it, and provisioned resources with Terraform. This role is described as assisting and growing, and I learn fast. On top of that, I'd bring cost awareness to every change, which saves the team money from day one."

Structure: acknowledge honestly → transferable experience with a concrete example → proof of learning (labs) → the unique value you add.

B2How would you act as a bridge between infrastructure and development teams? Give an example.Added from JD: "Collaboration"Must
Say this (STAR)

"Situation: in FinOps, I often had to ask engineering teams to change their resources, for example rightsizing or deleting idle environments, and teams were worried about breaking things. Task: reduce cost without hurting reliability. Action: instead of sending a list, I sat with them, showed the utilisation data, asked about their peak periods, and we agreed on a safe change with a rollback plan and a review date. Result: we saved [X%] with no incidents, and they started asking me for advice proactively. As a cloud engineer, I'd do the same: understand what developers need, explain infrastructure limits in simple terms, document things so they can self-serve, and follow up."

Replace the brackets with a real example and number from your work.

B3How do you write a good runbook or operational document?Added from JD: "document configurations, runbooks"Must
Say this

"I write it for someone who's tired at 3 a.m. and has never seen the system. It starts with what the alert or task is, how severe it is, and who owns it. Then numbered steps with copy-paste commands and the expected result of each, how to verify it worked, how to roll back, and when and who to escalate to. I keep it in Markdown in git next to the code, so it's reviewed like code, and I add a 'last reviewed' date. After every incident I update it with what we learned. And I test it by having someone else follow it."

See the full template in Chapter 11. Bring a sample runbook you wrote during your labs. It's powerful proof.

B4"Tell me a real scenario where YOU introduced a failure in infrastructure. What happened, what did you learn, and what changed after?"List 3 · Q10Must
How to answer (use a REAL story; here's the shape)

"What happened: [one or two sentences: what you changed, and what broke]. How I noticed and fixed it: [who alerted, how fast, what you did, and that you told people straight away]. Root cause: [the real reason, e.g. no review, no testing, an assumption]. What I learned: [one lesson]. What changed after: [a process you put in place: a checklist, a dry-run, a tag check, peer review, a runbook]."

FinOps-flavoured examples of the kind of story that fits (pick one that really happened to you)
  • A cleanup or rightsizing action that stopped or shrank a resource someone still needed, like a "dev" instance used by a nightly job. Lesson: confirm owners, use tags and a grace period, snapshot before deleting.
  • A budget or anomaly alert that was misconfigured, so a spike went unnoticed. Lesson: test alerts, then monitor the monitors.
  • A tagging or policy enforcement that blocked a team's deployments. Lesson: roll out in audit mode first, communicate, phase in.
  • From your labs: a K8s Service with the wrong selector, so there was no traffic. Lesson: always check kubectl get endpoints. (A lab story is OK if you say it's a lab.)

What they're testing: ownership (you say "I", not "we"), honesty, a calm fix, and a systemic improvement. Never say "I've never made a mistake."

Chapter 14 · Practice

Grill mode

Mock interview. You see only the question. Say your answer out loud (or type key points), then reveal the model answer and be honest with yourself.

Press "Next question" to start.

Follow-up drill: after each answer, ask yourself the interviewer's natural next questions: "And if that's not it?", "How would you prevent it?", "How do you know it's fixed?". If you can answer all three, mark it as nailed.

Chapter 15 · Plan

2-week study plan (≈ 2 hours a day)

Theory in the evening, one small lab per day. Each lab gives you a true "I tried this" story. All the tools are free: Docker Desktop, kind or minikube (local Kubernetes), Killercoda (free browser K8s labs), and an AWS free-tier account (set a $5 budget alert first; you know why 😉).

Day 1 · Big picture + Networking ICh 2, 3.1–3.3. Lab: draw the Chapter 2 diagram from memory. Calculate the size of /20, /24, /27.
Day 2 · DNS + CLI toolsCh 3.4–3.5, Q N1, N4, N5. Lab: run dig, dig +trace, curl -v, nc -zv against real sites; note timeout vs refused.
Day 3 · AWS networking hands-onQ N2, N3, N6, N7. Lab: build a VPC with a public + private subnet, launch a t3.micro, connect with SSM Session Manager, then break the SG and observe the timeout. Delete everything afterwards (no NAT GW, it costs money).
Day 4 · LinuxCh 4, Q L1–L3. Lab: on the instance or a local VM: top, df -h, journalctl, install nginx, stop it, run your L3 script.
Day 5 · DockerCh 5, Q D1–D3. Lab: write a Dockerfile for a tiny Python/Node app, build, run, check logs, make it crash (bad env var), read the exit code.
Day 6 · Kubernetes ICh 6.1–6.3, Q K1, K2. Lab: kind create cluster; deploy your image with a Deployment + Service + ConfigMap (load it with kind load docker-image).
Day 7 · Kubernetes break-and-fix day ⭐Q K3, K4. Lab: on purpose: wrong image tag (ImagePullBackOff), huge CPU request (Pending), bad env (CrashLoop), memory limit 10Mi (OOMKilled), wrong Service selector (no endpoints). Fix each one. Write a runbook about it.
Day 8 · Kubernetes IICh 6.6, Q K5–K7. Lab: install metrics-server, create an HPA, run a load test (kubectl run load --image=busybox -- sh -c "while true; do wget -q -O- http://web; done"). Install a chart with Helm, upgrade, roll back.
Day 9 · MonitoringCh 7, Q O1–O5. Lab: install kube-prometheus-stack with Helm, open Grafana, find CPU per pod, write one PromQL query.
Day 10 · Git + CI/CDCh 8, Q P1–P5. Lab: push your app to GitHub; add a GitHub Actions workflow that builds the Docker image and runs a test on each PR.
Day 11 · TerraformCh 9, Q T1–T3. Lab: Terraform an S3 bucket + security group; change something in the console, run terraform plan and see the drift; destroy.
Day 12 · Security + compute scenariosCh 10, Q S1–S3, C1–C5. Lab: create an IAM role for EC2 that can read one S3 bucket; test with aws sts get-caller-identity and aws s3 ls.
Day 13 · Your storyQ B1–B4, F1, F2. Write your intro, your failure story and your bridge story. Say them out loud 3 times. Record yourself.
Day 14 · Full mock interviewGrill mode, all Must questions, then Must + Should. Re-study everything marked "Need review". Skim the Azure columns in Ch 2.

Cloud cost safety for labs: set an AWS Budget alert at $5 before you start, avoid NAT Gateways and EKS (each costs money per hour; use kind locally), and delete resources at the end of each lab. Your FinOps self would approve.

Chapter 16 · Plan

Command cheat sheet

Network

dig +short NAME
dig NAME @8.8.8.8
dig +trace NAME
nc -zv HOST PORT
curl -v URL
curl -o /dev/null -s -w '%{http_code} %{time_total}\n' URL
traceroute -T -p 443 HOST
ss -tulpn
ip a ; ip route

Linux

top / htop ; uptime ; nproc
free -h ; vmstat 1
df -h ; df -i ; du -sh * | sort -h
iostat -xz 1
systemctl status|restart SVC
systemctl --failed
journalctl -u SVC --since "1h ago"
dmesg -T | grep -i oom

Docker

docker build -t app:TAG .
docker run -d -p 8080:80 app:TAG
docker ps -a
docker logs -f CTR
docker exec -it CTR sh
docker inspect CTR
docker stats

kubectl

kubectl get pods -A -o wide
kubectl describe pod P
kubectl logs P --previous
kubectl get events --sort-by=.lastTimestamp
kubectl apply -f f.yaml / diff -f
kubectl rollout status|undo|restart deploy/D
kubectl get endpoints SVC
kubectl port-forward svc/S 8080:80
kubectl top pods
kubectl config current-context

AWS CLI

aws sts get-caller-identity
aws ec2 describe-instances --filters Name=tag:Name,Values=web
aws ec2 describe-security-groups --group-ids sg-x
aws ec2 describe-route-tables
aws elbv2 describe-target-health --target-group-arn ARN
aws logs tail /aws/lambda/fn --follow
aws eks update-kubeconfig --name CLUSTER

Azure CLI

az account show
az vm list -o table
az network nsg rule list -g RG --nsg-name NSG -o table
az network vnet subnet list -g RG --vnet-name VNET -o table
az network dns record-set a list -g RG -z ZONE -o table
az aks get-credentials -g RG -n CLUSTER
az monitor metrics list --resource ID --metric "Percentage CPU"

Terraform

terraform init
terraform fmt ; terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform plan -refresh-only
terraform state list
terraform import ADDR ID

Git

git switch -c feat/x
git status ; git diff
git add -p ; git commit -m "what + why"
git push -u origin feat/x
git pull --rebase origin main
git log --oneline -10
git revert SHA
Chapter 17 · Plan

Official sources to go deeper

Primary documentation only. When an interviewer asks "where would you look?", naming the official docs is a good answer.