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 work | Cloud engineer skill it proves |
|---|---|
| Rightsizing EC2 / VMs, reading CPU & memory utilisation | Compute sizing, monitoring metrics, autoscaling logic |
| Explaining NAT Gateway & data transfer charges | You know how traffic flows: subnets, NAT, IGW, cross-AZ, internet egress |
| Storage tiers, EBS / snapshot cleanup, S3 lifecycle | Storage provisioning and maintenance |
| Tagging policies, account / subscription structure | Governance, environment setup for dev teams |
| Budgets, cost anomaly alerts | Alerting 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, recommendations | Documentation, runbooks, clear communication |
Your positioning (say it in your own words)
"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.
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
Chapters 2–11. One evening each. Look at the diagrams first.
Chapter 12 is the one method behind most answers.
Chapter 13. Start with Must ones. Mark each one "I can answer this".
Chapter 14 hides the answer until you try. Say the answer out loud.
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 responsibility | What you must be able to explain | Go to | Priority |
|---|---|---|---|
| Provision & maintain compute and storage | EC2 / VM sizing, EBS / Managed Disk, S3 / Blob, AMIs, tags, expanding a disk | Ch 2, Q C1–C8 | Must |
| Minor networking: NSG rules, DNS records, basic troubleshooting | VPC/VNet, subnets & CIDR, route tables, SG vs NACL vs NSG, DNS record types, TTL, ping/traceroute/dig/curl | Ch 3, Q N1–N9 | Must |
| Provision environments for dev teams | Terraform basics, separate accounts / subscriptions / resource groups, tags, budgets, auto-shutdown | Ch 9, Q F2 | Should |
| Write & update K8s manifests, deploy workloads | Pod, Deployment, Service, ConfigMap, Secret, Ingress YAML; kubectl apply; rollout & rollback | Ch 6, Q K1–K2 | Must |
| Monitor & troubleshoot K8s clusters and workloads | Pending / CrashLoopBackOff / ImagePullBackOff, describe, logs, events, Service endpoints | Ch 12, Q K3–K8 | Must |
| Deploy & configure services for dev teams | Git branches & PRs, CI/CD pipeline stages, Helm basics, environment variables & config | Ch 8, Q P1–P6 | Should |
| Monitor health, respond to alerts, help with incidents | Metrics / logs / traces, golden signals, CloudWatch & Azure Monitor, Prometheus & Grafana, incident steps | Ch 7, Q O1–O6 | Must |
| Document configs, runbooks, procedures | Runbook structure, Markdown, "why" + "how" + "how to verify" + "how to roll back" | Ch 11, Q B3 | Must (easy win) |
| Docker: build & containerise apps | Dockerfile, image vs container, layers, multi-stage, registry, exit codes | Ch 5, Q D1–D3 | Must |
| Linux CLI | top, free, df, du, systemctl, journalctl, ss; reading logs | Ch 4, Q L1–L3 | Should |
| Security basics | IAM roles vs users, least privilege, secrets managers, never commit secrets | Ch 10, Q S1–S4 | Should |
| HA / DR / multi-region design | Multi-AZ, RTO/RPO, the 4 DR strategies | Q H1–H5 | Stretch |
Your study order: Networking → Kubernetes → Monitoring → Docker → Linux → CI/CD → Terraform → Security. Networking and Kubernetes together are about half of this JD.
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.
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
- DNS (Route 53) turns
app.example.cominto the load balancer's address. - The request enters the VPC through the Internet Gateway and hits the load balancer in the public subnets.
- The load balancer forwards to healthy servers or pods in the private subnets. Security Groups only allow traffic from the load balancer.
- The app talks to the database in its own private subnet. A standby copy in another AZ takes over if the primary fails.
- 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.
| Concept | AWS | Azure |
|---|---|---|
| Top-level billing / isolation boundary | Account (inside Organizations) | Subscription (inside Management Groups); plus Resource Groups |
| Private network | VPC | Virtual Network (VNet) |
| Instance-level firewall | Security Group (allow-only, stateful) | NSG on a NIC (allow + deny, stateful, priority-ordered) |
| Subnet-level firewall | Network ACL (stateless) | NSG on a subnet (still stateful) |
| Custom routes | Route table | Route table / User-Defined Routes (UDR) |
| Outbound internet for private subnets | NAT Gateway | NAT Gateway |
| Connect two networks | VPC Peering, Transit Gateway | VNet Peering, Virtual WAN / hub-spoke |
| DNS | Route 53 | Azure DNS (+ Traffic Manager / Front Door for global routing) |
| Layer 7 (HTTP) load balancer | Application Load Balancer (ALB) | Application Gateway (regional), Front Door (global) |
| Layer 4 (TCP/UDP) load balancer | Network Load Balancer (NLB) | Azure Load Balancer |
| Virtual machines | EC2 | Virtual Machines |
| Auto scaling group of VMs | Auto Scaling Group | Virtual Machine Scale Sets (VMSS) |
| Block disk | EBS | Managed Disks |
| Object storage | S3 | Blob Storage (in a Storage Account) |
| Managed Kubernetes | EKS | AKS |
| Container registry | ECR | ACR |
| Serverless functions | Lambda | Azure Functions |
| Managed SQL database | RDS / Aurora | Azure 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) |
| Permissions | IAM policies | Azure RBAC role assignments (Entra ID for identities) |
| Metrics, logs, alarms | CloudWatch | Azure Monitor (Log Analytics, Application Insights) |
| Audit log of API calls | CloudTrail | Activity Log |
| Secrets | Secrets Manager / SSM Parameter Store | Key Vault |
| Remote shell without opening SSH | SSM Session Manager | Azure Bastion |
| Native IaC | CloudFormation / CDK | ARM templates / Bicep |
| Cost tools (your home turf) | Cost Explorer, CUR, Budgets, Cost Anomaly Detection | Cost Management + Billing, Budgets, Advisor |
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.
| CIDR | Addresses | Usable in AWS / Azure | Typical use |
|---|---|---|---|
/16 | 65,536 | 65,531 | Whole VPC / VNet |
/20 | 4,096 | 4,091 | Large subnet (e.g. EKS pods) |
/24 | 256 | 251 | Normal subnet |
/28 | 16 | 11 | Smallest subnet AWS allows |
/32 | 1 | n/a | One 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).
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 Group | AWS Network ACL | Azure NSG | |
|---|---|---|---|
| Attached to | ENI (instance, pod, LB, RDS) | Subnet | Subnet and/or NIC (both are evaluated) |
| Rule types | Allow only | Allow and Deny | Allow and Deny |
| Stateful? | Yes: return traffic automatically allowed | No: you must allow return traffic (ephemeral ports 1024–65535) | Yes |
| Order | All rules evaluated, any allow wins | Lowest rule number first, first match wins | Priority 100–4096, lowest number first, first match wins |
| Default | Deny all in, allow all out | Default NACL allows all | Built-in rules at 65000+: allow VNet, allow Azure LB, deny all inbound from the internet |
| Nice trick | Source can be another SG ("allow from the app tier SG") | Good for blocking a bad IP range | Service 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
| Record | What it does | Example |
|---|---|---|
| A / AAAA | Name → IPv4 / IPv6 | api.example.com A 52.1.2.3 |
| CNAME | Name → another name (not allowed at the zone apex example.com) | www CNAME my-alb-123.elb.amazonaws.com |
| Alias AWS | Like a CNAME but works at the apex, free queries, follows AWS resource IPs | example.com ALIAS → ALB (Azure: alias record set) |
| MX | Mail servers | example.com MX 10 mail.example.com |
| TXT | Text: domain verification, SPF | "v=spf1 include:…" |
| NS | Which servers are authoritative for the zone | Must match at your registrar! |
| TTL | How 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
| Tool | Question it answers | Example | Gotcha |
|---|---|---|---|
dig / nslookup | Does the name resolve? To what? | dig +short api.example.comdig @8.8.8.8 api.example.com | Compare different resolvers to spot caching |
ping | Is the host reachable at the IP layer (ICMP)? | ping -c 4 10.0.11.5 | Many clouds and SGs block ICMP. No ping ≠ host down |
traceroute / mtr | Where 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 |
curl | Does the HTTP app answer? What status, headers, timing? | curl -v https://api.example.com/healthcurl -o /dev/null -s -w '%{http_code} %{time_total}\n' URL | -v shows DNS, TLS handshake and headers |
ss / netstat | What is listening on this machine? | ss -tulpn | App 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.
Linux basics
A server has four resources. Every "server is slow" question is about which one is exhausted.
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.free -h, vmstat 1, dmesg -T | grep -i oomLook at "available", not "free". Linux uses spare memory for cache, which is normal.
df -h (space), df -i (inodes), du -sh /var/* | sort -h, iostat -xz 1Full disk = write failures.
%util near 100 and high await = disk is the bottleneck.ss -tulpn, ss -s, ip a, ip routeWho'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
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.
# ---- 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"] # ⑤
- Pin the base image (a version, not
latest), so builds are repeatable. - Copy the dependency files first. That layer is cached until
package.jsonchanges, so builds get much faster. - Multi-stage: the final image contains only runtime files, without compilers or source. It's smaller, faster to pull, and has less to attack.
- Run as a non-root user. If the app is hacked, the attacker isn't root.
- One process per container. Logs go to stdout/stderr, so
docker logsand Kubernetes can see them.
Layers: why order matters
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
| Code | Meaning | Usual cause |
|---|---|---|
0 | Exited normally | The process finished. Fine for a job, a problem for a web server (in K8s, check the command) |
1 | App error | Exception, missing config or env var. Read the logs |
126 / 127 | Not executable / command not found | Wrong CMD/ENTRYPOINT, missing binary, wrong path |
137 | Killed with SIGKILL (128+9) | OOMKilled (memory limit), or force-killed |
143 | SIGTERM (128+15) | Asked to stop: normal during deploy or scale-down |
Docker networking modes
- bridge (default): private container network with NAT.
-p 8080:3000publishes 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.
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
6.2 The objects, and how they connect
| Object | One-line meaning |
|---|---|
| Pod | Smallest 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. |
| ReplicaSet | Created by the Deployment; one per version. Keeps the pod count right. You rarely touch it. |
| Service | A stable IP + DNS name in front of changing pods. Types: ClusterIP (internal), NodePort, LoadBalancer (creates a cloud LB). |
| Ingress | HTTP routing rules (host / path → Service), done by an ingress controller (NGINX, AWS Load Balancer Controller, AGIC on Azure). |
| ConfigMap | Non-secret config (env vars or files). |
| Secret | Sensitive config. Only base64-encoded, not encrypted by default. Protect it with RBAC and encryption at rest. |
| Namespace | A folder for objects, e.g. dev, staging, team-a. |
| DaemonSet / StatefulSet / Job | One 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
- How many copies. The HPA can change this automatically.
- The Deployment manages pods with these labels. The selector and the template labels must match.
- Use a specific tag (or digest), never
latest, so rollouts and rollbacks are predictable. - Load every key of the ConfigMap as an environment variable.
- 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.
- 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).
- The Service sends traffic to pods with this label. Labels wrong = empty endpoints = no traffic.
port= what callers use (http://web.shop).targetPort= the container's port.
6.4 Pod lifecycle: the states you'll troubleshoot
Scheduler can't place it: not enough CPU/memory, taints, node selector, PVC not bound.
Can't pull the image: wrong name/tag, no registry permission, no network to the registry.
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
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.
Monitoring & observability
Numbers over time: CPU, requests/sec, p99 latency. Cheap. Good for alerts and dashboards. "Something is wrong."
Text events: "ERROR db timeout user=42". Good for details. "What is wrong."
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
| Signal | Question | Example metric |
|---|---|---|
| Latency | How long do requests take? (use p95/p99, not the average) | ALB TargetResponseTime, http_request_duration_seconds |
| Traffic | How much demand? | RequestCount, requests/sec |
| Errors | How many fail? | HTTPCode_Target_5XX_Count, error rate % |
| Saturation | How full is the system? | CPU, memory, disk, DB connections, queue depth |
Tool landscape
| Need | Cloud native | Open source / SaaS |
|---|---|---|
| Metrics + alarms | CloudWatch Metrics & Alarms · Azure Monitor Metrics & Alerts | Prometheus (collects by "scraping" /metrics) + Alertmanager |
| Dashboards | CloudWatch Dashboards · Azure Workbooks | Grafana (reads Prometheus, CloudWatch, Azure Monitor, Loki…) |
| Logs | CloudWatch Logs (Logs Insights) · Log Analytics (KQL) | Loki, Elasticsearch/OpenSearch, Datadog |
| Traces | AWS X-Ray · Application Insights | Jaeger, Tempo; OpenTelemetry is the standard to send all three |
Good alerting in one picture
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
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
# .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
Replace pods a few at a time. K8s default. Cheap. Both versions run together for a while.
Two full environments; switch traffic at once. Instant rollback (switch back). Costs 2× for a while.
Send 5–10% to the new version, watch metrics, then increase. Safest; needs good monitoring.
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.
-/+ (replace!)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
}
| Word | Meaning |
|---|---|
| State | Terraform's memory: "resource aws_instance.web = real instance i-0abc". Lose it and Terraform forgets what it owns. |
| Remote backend | Store state in S3 / Azure Storage, not on your laptop, so the whole team uses one copy. |
| Locking | Only one apply at a time, so two runs don't corrupt the state. |
| Drift | Someone changed the real resource outside Terraform (e.g. in the console). The next plan shows it. |
| Module | A reusable package, e.g. a dev-environment module the dev teams can call. |
| Import | Bring 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.
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"
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/0on 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.
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
What is it? Why does it exist? How do I change it? How do I verify? How do I roll back? Who owns it?
# headings, - lists, **bold**, `code`, fenced code blocks with a language, [link](url), tables with |.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
dig +short app.example.comWrong/old IP → record or TTL issue
traceroute, route tables, IGW/NAT, peeringMissing route → silent drops
nc -zv host 443, SG / NACL / NSG, VPC Flow Logs (REJECT)Timeout = dropped
See the table below
systemctl status, ss -tulpn, kubectl logsRefused = nothing listening
top, df -h, kubectl top, CloudWatchSaturation = slow / OOM
Slow DB = slow app
12.2 HTTP status codes: what the error is telling you
| Code | Who's speaking | Meaning | First things to check |
|---|---|---|---|
400–404 | App | Client-side problem: bad request, not logged in, forbidden, wrong path | URL / path, auth headers, Ingress path rules |
500 | App | The app crashed while handling the request | App logs, stack trace |
502 Bad Gateway | LB / proxy | The LB got an invalid or no response: the target closed the connection, crashed, or returned garbage | App crashing / restarting, keep-alive timeout shorter than the LB idle timeout, wrong target port/protocol (HTTP vs HTTPS) |
503 Service Unavailable | LB / proxy | No healthy target to send to (or the app is overloaded) | Target group health, empty K8s endpoints, scaled to 0 |
504 Gateway Timeout | LB / proxy | The target didn't answer in time | Slow app / DB, LB idle timeout, SG blocking LB → target, NetworkPolicy dropping |
12.3 "I can't connect": read the error message
| Error | What it proves | Look at |
|---|---|---|
Could not resolve host | DNS failed | Record exists? Private vs public zone? Resolver? |
Connection timed out | Packets dropped on the way | SG / NSG / NACL, route table, public IP, wrong IP |
Connection refused | Reached the host, nothing listening | Service down, wrong port, listening on 127.0.0.1 |
Permission denied (publickey) | Network is fine. Authentication failed | Wrong key, wrong username, key file permissions |
SSL certificate problem | TLS issue | Expired cert, wrong hostname, missing intermediate |
AccessDenied / 403 | Network is fine. Identity/permissions | IAM policies, resource policies, KMS, SCP |
12.4 Kubernetes pod triage flowchart
| Status | Run this | Look for | Usual fixes |
|---|---|---|---|
| Pending | kubectl describe pod → Events | 0/3 nodes are available: Insufficient cpu, untolerated taint, didn't match node selector, unbound PersistentVolumeClaim | Lower requests or add nodes (autoscaler), fix the selector / toleration, fix the PVC / StorageClass |
| ImagePullBackOff | kubectl describe pod → Events | not found, unauthorized, i/o timeout, toomanyrequests | Fix image name/tag, imagePullSecret or node IAM for ECR / AcrPull for ACR, NAT / VPC endpoints, mirror Docker Hub images |
| CrashLoopBackOff | kubectl logs --previous + describe → Last State, Exit Code | Exit 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 Ready | describe → readiness probe events | Readiness probe failed: … 503 | The app can't reach a dependency, or the probe path/port is wrong |
| Running, but no traffic | kubectl get endpoints svc | Endpoints <none> | Service selector labels ≠ pod labels, wrong targetPort |
12.5 The universal incident answer template
"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."
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
"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
- 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.)
- CPU:
uptime: load average vs number of CPUs (nproc). Intop, pressPto 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. - Memory:
free -hand look at "available".ps aux --sort=-%mem | head.dmesg -T | grep -i "killed process"shows OOM kills. Swap use going up = memory pressure. - Disk I/O:
iostat -xz 1:%utilnear 100% and highawaitmean the disk is saturated.iotopshows which process. On EBS, checkVolumeQueueLengthand whether you've hit the IOPS/throughput limit or burst balance of gp2. - Disk space:
df -h,df -i(inodes),du -xh / --max-depth=1 | sort -h. Usually it's logs. - 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
"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 -fmakes HTTP 4xx/5xx count as failure.--max-timestops 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
"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
"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
"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
"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
- Clarify: source (app subnet
10.1.2.0/24or its ASG / SG), destination (DB subnet or NIC), port5432/TCP, environment. Get a ticket or approval. - 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. - Least privilege: specific source, single port, a clear name like
allow-orders-app-to-pg, and a description. - 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.
- Verify:
nc -zvfrom the app host or pod. Azure Network Watcher "IP flow verify", or on AWS the Reachability Analyzer. - 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
"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
"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
"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
ssh -vvv -i key.pem ec2-user@IPand read where it stops.- 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). - Refused: sshd not running or on a different port.
- Permission denied (publickey): wrong key pair, wrong user (Amazon Linux
ec2-user, Ubuntuubuntu),chmod 400 key.pem. - 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.
- 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
"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."
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
pingwhen ICMP is blocked. Usecurl -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
"'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
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?- Multi-value / weighted / latency records: is one target or IP bad? Are the health checks passing? (Route 53 → Health checks.)
- TTL / caching: was something changed recently with a long TTL? Some users still have the old IP.
- Delegation:
dig NS example.comvs the NS records in the hosted zone. A mismatch makes some resolvers fail. - AAAA (IPv6) record pointing somewhere broken: only IPv6 users fail.
- Private vs public hosted zone with the same name: inside the VPC people get different answers.
- 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
"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
"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."
- 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. - Correlate: CloudWatch
CPUUtilizationvs ALBRequestCountandTargetResponseTime. Was there a deploy, a cron or batch job, or a traffic burst (a bot? a DDoS → WAF)? - 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!)? - T2/T3/T4g: check
CPUCreditBalance. At zero, the instance is capped. Unlimited mode or a non-burstable family fixes that. - 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
"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."
- CloudWatch:
HTTPCode_ELB_502_CountvsHTTPCode_Target_5XX_Count. The ELB count means the LB generated the error. - Target group health: are targets flapping between healthy and unhealthy?
- ALB access logs (enable to S3):
elb_status_code=502, andtarget_status_code="-"means the target gave no valid response. - App side: crashes / OOM / restarts at the same times; app logs; the web server's error log.
- 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.
- 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
"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."
- Target group → Targets → Health status details (e.g.
Target.Timeout,Target.ResponseCodeMismatch,Target.FailedHealthChecks). - 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, not127.0.0.1. - Code mismatch: health path exists (
/health), returns 200 (or set the matcher to200-399), no auth or redirect on it. - Recent change: new port, new path, app crash on boot (check logs /
systemctl status). - Deep health checks: if
/healthchecks 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
"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
"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
"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 Durationin 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
"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) →EXPLAINthe 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
"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."
🐳 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
"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
"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 error | Cause |
|---|---|
manifest unknown / not found | Wrong tag or repository name |
unauthorized / denied | Not logged in, expired token, no IAM permission (ECR) or AcrPull role (ACR) |
toomanyrequests | Docker Hub rate limit → authenticate or mirror the image to ECR / ACR |
i/o timeout / no such host | Network: no NAT, no VPC endpoint, DNS, proxy |
no matching manifest for linux/amd64 | Image 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
"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.23build image ≈ 800 MB → finaldistrolessoralpineimage ≈ 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
"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
"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
"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
"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."
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
"'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."
- Scope: all paths or one? All pods or some? Since a deploy? Check the ingress / ALB metrics for 504 rate and latency.
- Ingress controller logs:
upstream timed out+ which upstream pod IP. - Bypass layers:
kubectl port-forward pod/x 8080+curl -w '%{time_total}'. Pod fast → network/LB path. Pod slow → app. - 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. - 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. - Timeout mismatch: client < CDN < ALB idle timeout < ingress
proxy-read-timeout< app. Fix the slow request, or align the timeouts. - 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
"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
"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
"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
"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
"'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 log | Likely cause |
|---|---|
401 / 403 / ExpiredToken | Secret or token rotated or expired, OIDC role trust changed |
Could not resolve dependency / new test failures nobody caused | Unpinned dependency released a new version → use lock files |
No space left on device | Self-hosted runner / Jenkins agent disk full → clean the Docker cache |
toomanyrequests | Docker Hub pull rate limit |
| Passes on re-run | Flaky test or a timing issue → fix it or quarantine the test |
command not found after a runner update | ubuntu-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
"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
"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
"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."
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
"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
"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
"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
"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
"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."
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
"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
"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
"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
"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."
| SLO | Allowed downtime / 30 days |
|---|---|
| 99% | 7.2 hours |
| 99.9% | 43.2 minutes |
| 99.95% | 21.6 minutes |
| 99.99% | 4.3 minutes |
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
"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."
🔐 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
"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."
- Who am I really?
aws sts get-caller-identity(in a pod: is IRSA / Pod Identity actually attached?). - Resource ARN:
s3:ListBucketworks onarn:aws:s3:::bucket,s3:GetObjectonarn:aws:s3:::bucket/*. A mistake here is very common. - Bucket policy explicit deny (conditions:
aws:SecureTransport,aws:SourceVpce, IP). - KMS: the key policy must allow the role. The error may say AccessDenied with no mention of KMS.
- Organisation: SCP denying the region or the action; permission boundary on the role.
- Cross-account: both the role's policy and the bucket policy must allow. Object ownership / ACLs on objects uploaded by another account.
- 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
"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."
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
"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
"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."
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
"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
"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
"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
"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."
| Strategy | What runs in region 2 | RTO / RPO | Cost |
|---|---|---|---|
| Backup & restore | Only backups | Hours / hours | $ |
| Pilot light | Data replicated, core infra off | Tens of minutes | $$ |
| Warm standby | Small full copy running | Minutes | $$$ |
| Multi-site active/active | Full copy serving traffic | Near 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
"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
"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."
- Locate: Cost Explorer → Daily → Group by Service → filter → Group by Usage type / Region / Linked account / Tag. CUR + Athena for resource-level detail.
- Classify: usage went up (resources, traffic) vs rate went up (commitment expired, pricing change) vs one-off charge.
- Security check: unfamiliar regions,
RunInstancesin CloudTrail from unknown principals, GuardDuty findings → incident response. - Contain: stop or scale down the runaway resource, after confirming with the owner.
- 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
"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
"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
"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
"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
"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."
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.
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.
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 😉).
dig, dig +trace, curl -v, nc -zv against real sites; note timeout vs refused.top, df -h, journalctl, install nginx, stop it, run your L3 script.kind create cluster; deploy your image with a Deployment + Service + ConfigMap (load it with kind load docker-image).kubectl run load --image=busybox -- sh -c "while true; do wget -q -O- http://web; done"). Install a chart with Helm, upgrade, roll back.terraform plan and see the drift; destroy.aws sts get-caller-identity and aws s3 ls.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.
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 routeLinux
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
Official sources to go deeper
Primary documentation only. When an interviewer asks "where would you look?", naming the official docs is a good answer.