Entry Level DevOps Engineer Interview Preparation Guide - FAANG Standard
This guide is based on general FAANG interview practices and may not reflect specific company procedures.
The Entry Level DevOps Engineer interview at FAANG companies consists of 7 rounds designed to assess technical fundamentals, problem-solving ability, practical DevOps knowledge, basic system design thinking, and cultural fit. The process emphasizes learning potential, foundational understanding of containerization and CI/CD, and collaboration skills. Expect a mix of technical assessments (coding/scripting, infrastructure challenges), conceptual questions, and behavioral discussions.
Interview Rounds
Recruiter Screening
What to Expect
This is your first interaction with the company. The recruiter will assess your background, verify you meet basic qualifications, discuss your motivation for DevOps, and gauge cultural fit and communication skills. They'll also explain the interview process and timeline. This is a conversation-based round focused on understanding your career trajectory and enthusiasm for the role.
Tips & Advice
Be genuine and enthusiastic about DevOps. Clearly articulate why you're interested in infrastructure and automation. Highlight any relevant coursework, projects, or learning you've done. Ask thoughtful questions about the team and role to show genuine interest. Keep answers concise and conversational. This round is more about personality and communication than technical depth.
Focus Topics
Relevant Background & Experience
Discuss any relevant education, projects, internships, certifications, or self-study. Focus on containerization, CI/CD tools, cloud platforms, or infrastructure automation you've explored.
Practice Interview
Study Questions
Career Motivation & DevOps Interest
Clearly articulate why you're interested in DevOps, what attracts you to infrastructure and automation, and any relevant learning or project experience. Be authentic and show curiosity about the field.
Practice Interview
Study Questions
Communication & Learning Ability
Demonstrate clear communication, ability to explain concepts simply, willingness to learn, and growth mindset. Share examples of how you've quickly learned new technologies.
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
This technical assessment is conducted over video or phone with a senior engineer or technical screener. You'll be asked to solve problems and answer questions on Linux fundamentals, shell scripting basics (bash), basic Python scripting, version control (Git), and fundamental DevOps concepts. This round filters for technical minimum competency and problem-solving approach. Expect live coding or scripting in a shared editor.
Tips & Advice
Test your environment setup before the call (terminal, text editor, internet connection). Communicate your thinking aloud while solving problems. Ask clarifying questions before diving into code. For scripting challenges, write clear, readable code. Don't memorize solutions—focus on understanding concepts and being able to work through problems. Practice basic Linux commands and bash scripting on your local machine. Be honest if you don't know something, but show how you'd figure it out.
Focus Topics
DevOps Fundamentals & Philosophy
Understanding of core DevOps concepts: what is DevOps, why it matters, differences from traditional operations, automation versus manual processes, continuous integration basics, and continuous deployment basics.
Practice Interview
Study Questions
Python for DevOps Basics
Basic Python knowledge: syntax, data structures (lists, dictionaries), file I/O, string manipulation, functions, and basic libraries. Ability to write simple utility scripts, not complex applications.
Practice Interview
Study Questions
Git & Version Control
Understanding of Git basics: repositories, commits, branches, merging, pulling and pushing to remote repositories, understanding of distributed version control concepts, and merge conflict resolution.
Practice Interview
Study Questions
Bash/Shell Scripting Basics
Ability to write simple bash scripts: variables, loops (for, while), conditionals (if/else), functions, working with strings and arrays, reading files, basic error handling, and executing commands.
Practice Interview
Study Questions
Problem-Solving Approach
Ability to understand requirements, break down problems, think through edge cases, communicate your approach, handle when you don't know something, and iterate on solutions.
Practice Interview
Study Questions
Linux/Unix Fundamentals
Solid understanding of core Linux concepts: file systems, file permissions (chmod, chown), basic shell navigation, process management (ps, kill, top), environment variables, and common CLI tools (grep, sed, awk, find, etc.).
Practice Interview
Study Questions
Technical Round 1 - Containerization & Infrastructure
What to Expect
This technical round focuses on containerization (Docker), basic Kubernetes concepts, and infrastructure fundamentals. You'll answer conceptual questions, solve container-related problems, and possibly complete a small hands-on Docker or infrastructure challenge. The interviewer will assess your understanding of containerization benefits, container basics, image building, orchestration fundamentals, and your ability to think through infrastructure problems.
Tips & Advice
Ensure you have hands-on Docker experience—build and run containers locally before the interview. Understand Dockerfile syntax and best practices. Be able to explain why containerization is important in modern DevOps. Know basic Kubernetes concepts (Pods, Services, Deployments) but don't go too deep into advanced features. Practice troubleshooting common container issues. Be prepared to live-code a Dockerfile or solve a container configuration problem.
Focus Topics
Networking Fundamentals for Containers
Understanding of how containers network together: container networking models, port mapping, service discovery, basic DNS concepts, and how networking differs between local containers and orchestrated systems.
Practice Interview
Study Questions
Cloud Basics - AWS Fundamentals
Basic AWS concepts relevant to DevOps: EC2 instances, S3 storage, IAM for access control, basic networking (VPCs, security groups), and understanding how cloud services differ from on-premises infrastructure.
Practice Interview
Study Questions
Infrastructure as Code (IaC) Basics
Understanding of IaC philosophy: defining infrastructure as code versus manual setup, benefits of IaC, basic exposure to tools like Terraform or CloudFormation, and why this matters for DevOps.
Practice Interview
Study Questions
Docker Fundamentals & Image Creation
Understanding of Docker concepts: containers versus images, Dockerfile syntax, building images, Docker registry, image layers, best practices (minimizing image size, security), and basic image management.
Practice Interview
Study Questions
Container Orchestration Basics - Kubernetes
Basic Kubernetes concepts: Pods as the smallest deployable unit, Services for networking, Deployments for managing replicas, ConfigMaps and Secrets for configuration, basic kubectl commands, and understanding of why orchestration is needed.
Practice Interview
Study Questions
Technical Round 2 - CI/CD Pipelines & Deployment
What to Expect
This technical round focuses on Continuous Integration and Continuous Deployment (CI/CD) concepts, pipeline design, and deployment strategies. You'll discuss CI/CD pipeline architecture, work with tools like Jenkins or GitHub Actions, understand deployment strategies (rolling, blue-green, canary), and solve problems related to automation workflows. The interviewer assesses your understanding of how code moves from development to production and ability to design basic automation workflows.
Tips & Advice
Understand the complete CI/CD pipeline flow: code commit, build, test, deploy. Be able to explain different deployment strategies and their trade-offs. Have hands-on experience setting up a simple CI/CD pipeline with Jenkins, GitHub Actions, or GitLab CI. Understand concepts like build artifacts, test automation, and deployment stages. Be ready to discuss how to make pipelines more efficient and reliable. Practice explaining deployment failures and how you'd troubleshoot them.
Focus Topics
Monitoring & Observability in Deployment
Basic understanding of monitoring deployments: health checks, logs aggregation, metrics collection, alerting, and detecting issues post-deployment. Understanding how to detect and respond to failed deployments.
Practice Interview
Study Questions
Build Automation & Artifact Management
Understanding build processes: compiling code, running tests, creating artifacts (Docker images, binaries), versioning artifacts, storing artifacts in repositories, and managing dependencies.
Practice Interview
Study Questions
Version Control & Code Flow
Understanding code branching strategies for CI/CD: trunk-based development versus feature branches, merge strategies, code review processes, and how these affect pipeline flow.
Practice Interview
Study Questions
CI/CD Pipeline Architecture
Understanding complete CI/CD flow: source control integration, build stage, test stage, artifact creation, deployment stages, rollback capabilities, and monitoring. Ability to design a basic pipeline and explain each stage.
Practice Interview
Study Questions
Jenkins & CI/CD Tools
Practical understanding of Jenkins: pipeline syntax, stages, agents, scripted versus declarative pipelines, integration with Git and deployment tools, basic troubleshooting, and understanding how Jenkins fits in the broader CI/CD ecosystem.
Practice Interview
Study Questions
Deployment Strategies
Understanding different deployment approaches: blue-green deployment (two environments with instant cutover), canary releases (gradual rollout to small user group), rolling updates (incremental instance updates), and recreate deployment. Knowing trade-offs of each strategy.
Practice Interview
Study Questions
System Design Round - Lightweight Infrastructure
What to Expect
This round assesses basic system design thinking appropriate for entry-level candidates. Rather than complex distributed systems, you'll design simple infrastructure setups: deploying a basic application to the cloud with containerization, designing a simple monitoring solution, or planning an infrastructure upgrade. The interviewer wants to see how you think about scaling, reliability, automation, and infrastructure decisions at a foundational level. Discussion is collaborative—the interviewer helps guide your thinking.
Tips & Advice
For entry-level, system design is lighter than senior levels—focus on basic concepts, not advanced optimization. Ask clarifying questions about requirements. Think out loud about trade-offs (cost versus reliability, complexity versus simplicity). Draw diagrams or describe architecture clearly. Don't memorize solutions—show your reasoning. Discuss automation needs and operational concerns. The interviewer expects foundational thinking, not expert-level design. It's okay to make simplifying assumptions as long as you state them.
Focus Topics
Monitoring & Observability Design
Designing basic monitoring: what metrics to collect, log aggregation approach, alerting strategy, dashboards for visibility, and how monitoring helps with operations.
Practice Interview
Study Questions
Containerization & Orchestration Trade-offs
Understanding when to use containers versus VMs, single container versus orchestrated setup, complexity versus benefits analysis, and when each approach is appropriate for different scales.
Practice Interview
Study Questions
Basic Scaling Concepts
Understanding vertical scaling (bigger servers) versus horizontal scaling (more servers), when to apply each, load balancing basics, stateless design for scaling, and recognizing scaling bottlenecks.
Practice Interview
Study Questions
High Availability & Redundancy Basics
Understanding concepts like multi-AZ deployments, failover mechanisms, replication, backup strategies, and how to keep systems running during failures at a basic level.
Practice Interview
Study Questions
Basic Cloud Architecture Design
Ability to design simple cloud applications: single server setup versus load-balanced setup, stateless versus stateful considerations, database placement, security groups and firewalls basics, and understanding of when to add components for reliability.
Practice Interview
Study Questions
Behavioral Round - Values & Collaboration
What to Expect
This round focuses on behavioral assessment aligned with FAANG company leadership principles and values. Using Amazon's leadership principles as a framework (though applicable across FAANG), the interviewer will explore: customer and user focus, learning and curiosity, frugality and efficiency, bias for action, earning trust through integrity, and collaboration. They'll ask about past experiences, how you handle challenges, teamwork, and cultural alignment. This is not about DevOps technical knowledge—it's about work style and values.
Tips & Advice
Use the STAR method (Situation, Task, Action, Result) for answers. Prepare specific examples from projects, internships, or coursework showing: learning from failure, collaborating with others, solving problems creatively, taking initiative, and admitting mistakes. Be authentic—don't try to game answers. Relate examples to entry-level experiences (school projects, internships, side projects). Show growth mindset and willingness to learn. Ask thoughtful questions about team culture and values. Align your answers to the company's stated leadership principles.
Focus Topics
Integrity & Learning from Failure
Examples of admitting mistakes, taking responsibility, learning from failures, and maintaining high standards. Ability to discuss failures constructively and what you learned.
Practice Interview
Study Questions
Bias for Action & Frugality
Examples of taking initiative and acting with incomplete information, moving forward despite uncertainty, thinking creatively about resource constraints, and making good use of time and resources.
Practice Interview
Study Questions
Customer and User Focus & Ownership
Ability to think about end-user impact, taking ownership of problems, understanding business context, and going beyond requirements to deliver value. Examples of when you cared about user experience or business outcomes.
Practice Interview
Study Questions
Learning Ability & Curiosity
Demonstrating hunger to learn, examples of picking up new technologies quickly, asking questions to understand systems deeply, and showing growth mindset when facing knowledge gaps.
Practice Interview
Study Questions
Collaboration & Communication
Examples of working effectively with others, communicating technical concepts clearly, handling disagreements respectfully, asking for help appropriately, and contributing to team goals beyond individual work.
Practice Interview
Study Questions
Hiring Manager Round
What to Expect
This is the final round with the actual hiring manager or senior team member responsible for hiring. It combines behavioral assessment, deeper dive into technical understanding, and mutual evaluation. The hiring manager will discuss role expectations, how you'd work with the team, learning opportunities, career growth potential, and overall fit. They're assessing: can you succeed in this role? Do you fit the team? Are you genuinely interested? Technical depth will be lighter—focus is on role readiness and team dynamics.
Tips & Advice
Treat this as a two-way conversation. Ask thoughtful questions about team structure, current challenges, onboarding process, and growth opportunities. Be specific about what excites you about the role. Discuss what success looks like in the first 90 days. Share what you've learned during the interview process and how it's increased your interest. Be authentic about your experience level and learning goals. This is your chance to assess if the company and team are right for you. The hiring manager is also selling the role, so balance selling yourself with evaluating fit.
Focus Topics
Enthusiasm & Authenticity
Expressing genuine excitement about DevOps, the role, and the company. Being authentic about your journey, why you chose this path, and what draws you to this opportunity specifically.
Practice Interview
Study Questions
Questions About Role & Team
Asking intelligent questions showing you've done research and care about fit: current team challenges, how they measure success, mentorship approach, tools and tech stack, on-call expectations, and company and team culture.
Practice Interview
Study Questions
Growth Aspirations & Learning Goals
Discussing your long-term career interests in DevOps, what you want to learn, technologies you're curious about, and whether this role aligns with your goals.
Practice Interview
Study Questions
Role Readiness & 90-Day Success
Discussing what you'd focus on in the first 90 days, what you expect to learn, how you'd onboard, and what success looks like early in the role. Showing realistic expectations for entry-level growth.
Practice Interview
Study Questions
Team Fit & Collaboration Style
Understanding team dynamics, how you'd work with the team, receiving feedback, pair programming and mentorship, and genuine interest in the team's work and challenges.
Practice Interview
Study Questions
Frequently Asked DevOps Engineer Interview Questions
Explain eventual consistency, read-your-writes consistency, monotonic reads, and causal consistency. For each, give a concrete requirement where it would be the right guarantee to offer, and describe how you would support it in a cloud service through your choice of caching and replication strategy.
Sample Answer
Direct answer
These four guarantees are points on a spectrum of how fresh and ordered the data a reader sees has to be. Eventual consistency only promises convergence once writes stop; read-your-writes guarantees a client sees its own writes; monotonic reads guarantees a client's view never goes backward in time; causal consistency guarantees that if one write causally depends on another (it read it, or the same actor made both), everyone sees them in that order, though unrelated concurrent writes can appear in any order. Pick the weakest guarantee that still satisfies the real requirement, because every guarantee above eventual consistency costs latency, availability, or both.
Structured elaboration
The four guarantees
| Guarantee | What it promises | What it does not promise |
|---|---|---|
| Eventual consistency | If writes stop, all replicas converge to the same value eventually | No bound on staleness while writes continue, no ordering guarantee between reads |
| Read-your-writes | A client always sees writes it made itself, on its next read | Nothing about seeing other clients' writes promptly |
| Monotonic reads | A client's successive reads never go backward, never see an older value after a newer one | Nothing about seeing the very latest write quickly |
| Causal consistency | Writes that are causally related (B read A, or the same client wrote both) are seen by everyone in that order | Concurrent, unrelated writes can be seen in different orders by different clients |
Where each is right, and how to implement it
- Eventual consistency fits a "like count" or view counter on a social post: nobody notices if it is off by a few seconds. Implement with asynchronous replication and a cache with a short time-to-live (TTL), reads hitting the nearest replica or cache with no coordination.
- Read-your-writes fits a shopping cart: a customer adds an item and must see it on the very next page load, but does not need to see someone else's cart changes instantly. Implement by routing a client's reads to the same replica or region that handled their last write (sticky routing, or a read-after-write token the client passes back), or by writing through a cache synchronously for that client's session.
- Monotonic reads fits an account balance shown across several pages in one session: a user should never see a balance regress from $100 to $80 back to $100 as they navigate, even before any new transaction happens, because replica lag could otherwise expose an older value after a newer one. Implement with a session-bound read version (a logical timestamp or vector clock, a small per-write counter set that lets a node detect which operations causally precede which), routing reads only to a replica that has caught up to at least that version.
- Causal consistency fits a comment thread or a bank transfer's downstream notifications: if a bank debits account A and credits account B, and a fraud check reads B's new balance to decide whether to hold funds, that check must see the credit; two unrelated customers' unrelated transfers can be seen in any order relative to each other. Implement by propagating a causal token (a vector clock or dependency list) with each write and having replicas withhold delivery of a write until its dependencies have already been applied locally.
Applied to a machine learning feature store and model metadata
A feature store serving a model at inference time typically only needs eventual consistency for feature values themselves: a slightly stale feature rarely changes a prediction meaningfully, and the alternative, blocking inference on cross-region replication, would blow the latency budget. Model metadata, such as which model version is currently active or what its rollback target is, needs a stronger guarantee than the features: a rollback decision made by one operator must be immediately visible to every serving node, which is read-your-writes at minimum and often causal consistency if the rollback event has dependencies, such as rolling back the model and the feature schema together. This is a case where two data classes inside the same system legitimately sit at different points on the spectrum.
Worked example
Account A has $500. A transfer service debits A by $200 and credits B by $200. If a support dashboard reads B's balance right after the transfer and the read lands on a stale replica, it might show B's pre-credit balance: an eventual-consistency gap that would be embarrassing but not incorrect, because the transfer log stays the source of truth. If instead a downstream automated fraud check reads B's balance to decide whether $200 looks anomalous for that account, and it must evaluate the post-credit state to be correct, causal consistency is required: the fraud check's read is causally dependent on the credit write, so the system must guarantee it sees that write, by routing the check to a replica the write has already reached, verified via a causal token, or by having the check read from the same node that processed the write.
Trade-offs and pitfalls
- Reaching for the strongest guarantee "to be safe" is the most common mistake: causal or read-your-writes consistency usually requires sticky routing or coordination that adds latency and reduces how freely the system can load-balance or fail over.
- Monotonic reads and read-your-writes are easy to conflate: read-your-writes is about your own writes, monotonic reads is about time never going backward regardless of who wrote it. A system can offer one without the other.
- A subtle pitfall is implementing causal consistency with a coarse dependency tracker, such as "same user ID" as a proxy for causality: it over-serializes unrelated writes from the same user and under-serializes genuinely causal writes across users, like the transfer-then-fraud-check example above, so dependency tracking has to be based on actual read-then-write chains, not identity.
Design an adaptive traffic ramp-up algorithm for a progressive rollout: it should adjust the percentage of traffic sent to a new version based on observed error rate, latency, and saturation, with a safe fallback when the signal is ambiguous.
Sample Answer
Direct answer
An adaptive traffic ramp-up algorithm treats the ramp schedule as a live decision informed by observed metrics rather than a fixed, pre-set sequence: it increases traffic to the new version when signals stay clean, holds or backs off when they don't, and defaults to the SAFEST behavior (not advancing) whenever the signal itself is ambiguous or insufficient, rather than guessing.
Structured elaboration
- Inputs per evaluation window: error rate delta (new version vs. baseline), a latency percentile delta, and a saturation signal (CPU/memory/queue depth) for the new version's instances specifically, since resource saturation can precede a user-facing regression even before error rate or latency visibly move.
- Decision logic per step: if all signals are within an acceptable band relative to baseline AND the sample size is large enough to trust the comparison, advance to the next traffic percentage; if any signal is clearly degraded, reduce traffic (not necessarily to zero, potentially to the previous safe level) and pause further advancement; if the sample size is too small to draw a confident conclusion either way, HOLD at the current percentage rather than advancing blind or panicking and rolling back on insufficient evidence.
- Step sizing as a function of confidence: rather than fixed percentage jumps, the size of each advance can itself scale with how clean and how CONFIDENT (sample-size-adjusted) recent signals have been, larger, faster jumps when everything's been clean for a while, smaller, more cautious steps when signals are borderline or the traffic volume is still building up a meaningful sample.
- Safe fallback on ambiguous signal: explicitly defined, not left implicit; "ambiguous" (insufficient sample size, or signals disagreeing with each other) should default to holding at the current, already-validated percentage, never to advancing further on a coin flip, and never to an immediate rollback on pure uncertainty either, since that would be as costly and unjustified as advancing on it.
Worked example
def next_step(current_pct, error_delta, latency_delta, saturation, sample_size, min_sample=200):
if sample_size < min_sample:
return current_pct, "hold: insufficient sample size"
if error_delta > 0.5 or latency_delta > 0.2 or saturation > 0.85:
return max(current_pct // 2, 1), "reduce: signal degraded"
# clean signal with enough sample: advance, capped at 100
return min(current_pct * 2, 100), "advance: signals clean"
Run through three cases: next_step(10, error_delta=0.1, latency_delta=0.05, saturation=0.4, sample_size=500) returns (20, 'advance: signals clean'). next_step(10, error_delta=0.1, latency_delta=0.05, saturation=0.4, sample_size=50) returns (10, 'hold: insufficient sample size'), correctly refusing to advance on too little data even though the signals LOOK clean. next_step(20, error_delta=0.8, latency_delta=0.1, saturation=0.4, sample_size=500) returns (10, 'reduce: signal degraded'), correctly stepping back rather than holding when a signal is genuinely bad.
Trade-offs and pitfalls
The doubling/halving step logic here is a simplification; a real system would likely use a more principled statistical test (as covered elsewhere in this topic) rather than fixed delta thresholds, but the STRUCTURE, explicit hold-on-ambiguity, reduce-on-clear-signal, advance-on-clean-and-confident-signal, is the important design pattern regardless of which specific statistical method backs the "is this signal clean" check. The common mistake is an algorithm that only has two states (advance or roll back), missing the third, crucial "hold, we don't have enough information yet" state that prevents both reckless advancement and unnecessary panic on noisy early data.
List how to inspect loaded kernel modules, query module parameters, load/unload modules safely with modprobe/rmmod/insmod, and persist module loading across reboots. Explain caveats when module versions don't match kernel versions and how to detect missing dependencies.
Sample Answer
Direct answer
Inspect what is loaded with lsmod (a thin formatter over /proc/modules), query a
module's parameters with modinfo before loading it and via /sys/module/<name>/parameters/*
after it is loaded, and prefer modprobe over the lower-level insmod/rmmod pair because
modprobe resolves and loads (or unloads) the module's dependency chain automatically. To
survive a reboot you declare the module in /etc/modules-load.d/*.conf, not by running
modprobe from a login script. The two failure modes worth knowing cold: a module built for a
different kernel build than the one running (vermagic mismatch), and a module whose
dependencies were never indexed (depmod never ran, or ran against the wrong kernel tree).
Structured elaboration
Inspecting loaded modules
lsmodlists what is currently loaded: module name, memory size, and a "Used by" column
showing the reference count and which other modules or drivers depend on it. A module with a
non-zero refcount cannot be unloaded until whatever is using it releases it.modinfo <module>shows a module's static metadata straight from its.ko(kernel object)
file on disk: author, license, description, parameters it accepts, its declared dependencies,
and itsvermagicstring (the exact kernel version and build flags it was compiled against).
This works whether or not the module is currently loaded.- Once loaded,
cat /sys/module/<name>/parameters/<param>reads the live value of a parameter,
and/sys/module/<name>/refcntshows the same reference countlsmodreports.
Querying and setting module parameters
- Discover accepted parameters with
modinfo -p <module>(name, type, description). - Set a parameter at load time:
modprobe <module> paramname=value. - Some parameters are writable live via sysfs:
echo value > /sys/module/<name>/parameters/<param>,
but only if the module declared that parameter with the right permission mode; most are
read-only after load and require unload/reload to change. - To make a parameter value permanent, write it to a file under
/etc/modprobe.d/, for example
/etc/modprobe.d/mymodule.confcontainingoptions mymodule paramname=value, which
modprobereads on every future load (including the automatic load at boot).
Loading and unloading safely
| Tool | What it does | When to prefer it |
|---|---|---|
modprobe <name> | Loads the module and every module it depends on, using modules.dep | Default choice for loading |
modprobe -r <name> | Unloads the module and any dependencies no longer in use by anything else | Default choice for unloading |
insmod <path>.ko | Loads exactly the one file given, with no dependency resolution | Only when testing a single freshly built .ko you are pointing at directly |
rmmod <name> | Unloads exactly the one module, and fails if its refcount is non-zero | Rarely; modprobe -r is safer since it also drops now-unused dependencies |
modprobe -r is the safe default because it will not remove a module that something else still
depends on (it checks the refcount and the dependency graph before acting), whereas rmmod
will happily fail loudly instead but gives you none of the dependency cleanup. Always check
lsmod | grep <name> before and after to confirm the refcount and the dependents you expect.
Persisting across reboots
Running modprobe foo by hand only loads it for the current boot. To have the kernel load it
automatically on every future boot on a systemd-based distribution, list the module name (one
per line, comments with #) in a file under /etc/modules-load.d/, for example
/etc/modules-load.d/foo.conf containing just foo. systemd-modules-load.service reads every
file in that directory at boot and calls the equivalent of modprobe for each entry. Options or
blacklisting for that module still go in /etc/modprobe.d/, they are two separate concerns:
whether to load it (modules-load.d) versus how to load it (modprobe.d).
Kernel parameters more broadly: sysctl
Loadable modules are one lever on kernel behavior; runtime kernel tunables (sysctl) are
another, and they have the same "change now vs. persist" split. sysctl -w net.ipv4.ip_forward=1 flips IPv4 packet forwarding on immediately (writing to
/proc/sys/net/ipv4/ip_forward under the hood) but only until reboot. To persist it, drop a
file under /etc/sysctl.d/, for example /etc/sysctl.d/99-ip-forward.conf containing
net.ipv4.ip_forward = 1, and either reboot or run sysctl --system to apply every file in
/etc/sysctl.d/ immediately without a reboot. Before changing a kernel parameter on a
production host, verify the risk in both directions: read what the parameter actually controls
(some, like forwarding, have security implications; others, like TCP buffer sizes, can affect
every connection on the box) and confirm the change is reversible without a reboot before you
ship it, not after.
Worked example
Illustrative, on a minimal container image that never had a kernel module tree installed for
its running kernel at all:
$ uname -r
6.8.0-45-generic
$ modprobe -v dummy
modprobe: FATAL: Module dummy not found in directory /lib/modules/6.8.0-45-generic
$ ls /lib/modules
ls: cannot access '/lib/modules': No such file or directory
This is the version-mismatch failure mode in the flesh: modprobe always looks under
/lib/modules/$(uname -r)/, and here that directory does not exist at all for the kernel
actually running, because the userland module tree was never built or installed for this
specific kernel build. A real server that had its kernel package upgraded but has not yet
rebooted hits the exact same modprobe: FATAL: Module X not found in directory /lib/modules/<version> error, for the same underlying reason (uname -r still reports the old,
running kernel version, and modprobe looks under that version's directory) even though
/lib/modules itself is not empty there: it still has the old kernel's subdirectory, just not
one for whatever newer kernel the package manager already unpacked modules for, so ls /lib/modules on that real server would show the old version present, not a bare "No such file
or directory" the way this minimal container does. Either way, the fix is the same: reboot into
the kernel the modules were actually built for, or install/build the modules for the kernel you
are actually running.
To detect a missing dependency instead of a missing module: depmod -a rebuilds
/lib/modules/$(uname -r)/modules.dep from every .ko present, and modprobe --show-depends <module> prints the resolved dependency chain without loading anything, letting you check "is
everything this module needs actually present" before you load for real. If dependency
resolution was skipped entirely (someone ran insmod directly on a module whose dependency was
never loaded), the kernel logs Unknown symbol errors to dmesg, which is the tell that you
bypassed modprobe's dependency handling.
Trade-offs and pitfalls
- Do not habitually reach for
insmod: it is correct for testing one just-built.kofile, but
using it in a runbook or script silently drops dependency resolution and version checking that
modprobegives you for free. - Editing
/etc/modules-load.d/does not apply the change now, only on the next boot; if you
need it now too, runmodprobeby hand as well. modprobe --force(orinsmodwith a mismatched.ko) can load a module despite a
vermagicmismatch; this is a last-resort debugging move, not something to script, since a
module compiled against different kernel internals can corrupt kernel memory rather than
merely fail to load.- Forgetting to re-run
depmod -aafter manually copying a new.kointo the modules tree is a
common cause of "it's right there but modprobe says not found":modprobetrusts the
modules.depindex, not a directory listing.
Leadership asks you to double deployment frequency in two quarters without raising the change-failure rate. What would you measure first, what organizational and technical changes would you propose, and how would you know you were improving rather than gaming the numbers?
Sample Answer
Direct answer
I would start by measuring, not changing anything: a baseline of deployment frequency, change fail rate, change lead time (commit to production) and recovery time (how long it takes to restore service after a failed deploy), plus a breakdown of where a change spends its time. Then I would shrink batch size and remove waiting, because both frequency and safety improve when changes are small. Crucially, "do not raise the change-fail rate" is a weaker promise than it sounds, so I would raise that with leadership at the start.
The catch hidden in the goal
Illustrative baseline: 60 deploys a month, 10 percent fail, so 6 failed deploys. Double the frequency to 120 deploys and hold the rate at 10 percent: 120 x 0.10 = 12 failed deploys a month. The rate is unchanged, but customers see twice as many failures. To keep the absolute number of bad deploys at 6, the rate must fall to 6 / 120 = 5 percent. I would propose a goal of doubling frequency while reducing fail rate or at least holding recovery time down, and say why.
Step 1: measure first
- The four numbers above, with agreed definitions of "deploy" and "failure".
- A timeline of a typical change: waiting for review, waiting for the build, waiting for an approval board or test environment, waiting for a release slot. The longest wait is usually the first target.
Step 2: organisational changes
- A reviewing norm: first response within a business day, so changes are not stuck in a queue.
- Replace slow change approval boards (committees that approve each release) for low-risk changes with pre-agreed rules; keep human approval for genuinely risky ones.
- Give teams ownership of their own release, including the pager (being on call, the first to be alerted when it breaks), so they feel the cost of a bad release.
- Agree that a failed deploy leads to a blameless review (a post-incident discussion about how the process allowed the failure, not who to blame), so failures are reported.
Step 3: technical changes
- Smaller changes merged to a shared main branch daily (trunk-based development), with unfinished features behind feature flags (switches that hide unfinished code until it is turned on).
- Automated tests and builds fast enough to run on every merge.
- Staged rollouts (sending a change to a small share of users first, then more) and the ability to switch a change off quickly, which keep each failure cheap. Safer releases make a higher frequency tolerable: a staged rollout limits how many users a bad change reaches, and a quick switch-off or rollback shortens how long they are affected, so each failure stays cheap even when there are more deploys.
How I would know it is real improvement, not gaming
- Count rules fixed in advance: a deploy is a change to production code; empty or configuration-only deploys and artificial splitting do not count unless stated.
- Watch the pair: frequency rising while change size distribution, lead time, and absolute failures behave sensibly.
- Cross-check against things people cannot easily fake: customer-visible incidents, support tickets, and a developer survey.
- Review the numbers with the teams, so a jump in frequency prompts the question 'what changed in how you work?'
Sequence over the two quarters
- Weeks 1 to 3: baseline and the wait-time timeline, plus agreeing the counting rules.
- Weeks 4 to 8: the first action is the review norm and replacing approval-board steps for low-risk changes, because they are cheap and remove the longest waits.
- Weeks 6 to 18: faster automated tests, trunk-based development and feature flags, team by team.
- Weeks 14 to 26: staged rollouts and ownership of release and pager, then re-read the baseline at week 26.
Trade-offs: a two-quarter horizon is tight for cultural change; I would sequence the quick wins (review latency, approvals) first and the testing investment alongside.
Design a way to record, for every trace, why it was sampled: which policy fired, what score or version was used, and what triggered the decision, integrated with your OpenTelemetry collectors. The system needs to support auditing sampling policy changes over time and let someone re-sample or replay historical data for a specific investigation. What would you store, and how would you index it?
Sample Answer
Direct answer
Emit a small, separate provenance record at the moment each sampling decision is made in the OpenTelemetry collector (which policy fired, its version, the score/trigger, a pointer to the raw trace), route it to its own indexed store rather than attaching it to the span itself, and keep the immutable policy-version history in a content-addressed snapshot store so any past decision can be tied back to the exact policy that produced it. Replay works by using the index to locate the original trace payload and re-running it through a resampling service against either the historical or current policy snapshot.
What to record per decision
{provenance_id, trace_id, span_root_id, policy_id, policy_version, score, trigger (rule id / deterministic / random seed), collector_id, timestamp, payload_pointer}. This is captured by a processor stage in the collector's sampling pipeline, which already has all of this context at decision time; nothing here requires re-deriving the decision after the fact.
Why provenance is a separate store from span data
Attaching this metadata directly onto every span as attributes would grow span size and, worse, several of these fields (policy_id, trigger, score) are exactly the kind of thing that turns into an unbounded-cardinality label if it ever gets promoted from a span attribute into a metric. Keeping provenance in its own indexed, purpose-built store avoids both problems: span size stays bounded, and the provenance store can be indexed on exactly the fields audits actually query (policy_id, time range, collector_id, trigger), independent of how spans themselves are indexed.
Storage layout
- Snapshot store: full policy definitions, content-addressed (hash of the policy config as its ID), append-only. A provenance record references a snapshot ID rather than duplicating the policy content.
- Index store: one row per decision, indexed on
policy_id,timestamp,trace_id,collector_id, pointing to both the policy snapshot and the original trace payload location. - Payload store: the actual trace data (sampled or not, if retained for replay purposes) in cheap object storage, referenced by pointer, not duplicated into the index.
Auditing and replay
- Auditing a policy's history over time is a query against the snapshot store's append-only log plus the index store filtered by
policy_id; because snapshots are content-addressed and immutable, there's no ambiguity about what a givenpolicy_versionactually did at decision time. - Replaying or resampling a historical trace: look up its provenance record by
trace_id, fetch the referenced payload and policy snapshot, and re-run it through a resampling service using either the original snapshot (to reproduce the original decision exactly) or the current policy (to ask "would this decision be different today").
Worked example
Assume a JSON-encoded provenance record is roughly 300 bytes (field names plus values; a smaller binary encoding would shrink this further, but JSON is used here for a conservative, worst-case estimate). Assume the fleet is producing 200,000 trace-eligible requests/sec and applying a 2% sampling rate, so decision records are only written for sampled traces (unsampled traffic is covered by an aggregate counter, not a per-trace record, to keep volume bounded):
sampled traces/sec=200,000×0.02=4,000/sec records/day=4,000×86,400=345,600,000 records/day storage/day=345,600,000×300 bytes=103,680,000,000 bytes≈103.7 GB/day storage/month≈103.7×30=3,111 GB≈3.11 TB/monthFor a 1-year audit-retention requirement:
storage/year≈103.7×365=37,850 GB≈37.85 TB/year (uncompressed JSON)Applying a conservative 5:1 compression ratio for this repetitive, structured JSON (typical for columnar or general-purpose compression on highly repetitive records):
storage/year (compressed)≈537.85≈7.57 TB/yearThis confirms the design instinct in the hints: 3+ TB/month of provenance metadata alone is large enough that it must be a dedicated, purpose-indexed store, not an afterthought bolted onto span storage, and the compression step matters enough to be worth the CPU cost at this volume.
flowchart LR
Collector[OTel Sampling Processor] --> DecisionEmit[Emit Provenance Record]
DecisionEmit --> Stream[Provenance Event Stream]
Stream --> IndexStore[Indexed Metadata Store]
Stream --> SnapshotStore[Policy Snapshot Store]
IndexStore --> AuditAPI[Audit Query API]
SnapshotStore --> AuditAPI
AuditAPI --> Replay[Replay / Resample Service]
Replay --> PayloadStore[Trace Payload Object Store]
Trade-offs and pitfalls
- Recording a full per-trace provenance record for every trace, sampled or not, would multiply the volume above by roughly 1/0.02=50×; capping the per-trace record to sampled traces only, and covering unsampled volume with an aggregate counter, is what keeps this tractable.
- Content-addressing policy snapshots (rather than a mutable "current policy" pointer) is what makes historical audit queries trustworthy; a mutable policy record would make it impossible to prove what a decision from six months ago was actually based on.
- Replay accuracy depends entirely on the payload store retaining the original trace data long enough to replay it; if trace payloads expire (per the operational retention/tiering policy) before the provenance audit-retention window does, replay for older decisions becomes impossible even though the provenance record itself still exists. The two retention windows need to be reconciled explicitly, not assumed to match.
- A JSON-based index store is simple to query ad hoc but the 300-byte/record estimate is generous; a tighter binary encoding could meaningfully cut both storage and index cost at this volume, and is worth revisiting once the audit query patterns are well understood.
A pod in namespace 'backend' cannot reach a Service in the same namespace though the pod is Running. Provide a step-by-step troubleshooting plan that includes checks against Endpoints/EndpointSlices, CoreDNS resolution, kube-proxy rules, iptables/ipvs entries, NetworkPolicies, and node-level routing. Explain what tools and commands you would use at each step.
Sample Answer
Since the pod and the Service are in the same namespace, the likeliest culprits sit in the low layers rather than in cross-namespace policy: the Service's selector has to match at least one Ready pod (visible through Endpoints/EndpointSlices), kube-proxy has to have programmed that mapping into the node's dataplane, and only once both of those check out does it make sense to suspect the CNI (Container Network Interface, the plugin responsible for pod networking) or an explicit deny policy.
Ordered checklist
| Step | Check | Points to |
|---|---|---|
| 1 | kubectl get endpoints/endpointslice for the Service | Selector/label mismatch or failing readiness probes if empty |
| 2 | DNS resolution of the Service name from the pod | CoreDNS chain, if this alone fails |
| 3 | curl the pod IP directly, bypassing the Service | Isolates Service-layer problems from pod/app problems |
| 4 | curl the Service ClusterIP | If step 3 works but this fails, suspect kube-proxy/iptables/ipvs |
| 5 | kubectl get networkpolicy in the namespace | Only relevant once steps 1 to 4 all look correct |
| 6 | Node routing / CNI daemon logs | Last resort, if everything above is clean |
flowchart TD
A[Endpoints/EndpointSlice populated?] -->|empty| B[Check selector labels + readiness probes]
A -->|populated| C[DNS resolves Service name?]
C --> D[curl pod IP directly]
D -->|fails| E[CNI / node routing]
D -->|works| F[curl ClusterIP]
F -->|fails| G[kube-proxy iptables/ipvs rules]
F -->|blocked, IP worked| H[NetworkPolicy]
Worked example: the label-selector typo
This is the single most common cause of "Service exists, pod is Running, nothing connects," and it is entirely visible in step 1 before touching any networking tool.
kubectl -n backend get svc mysvc -o yaml
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
kubectl -n backend get endpoints mysvc
NAME ENDPOINTS AGE
mysvc <none> 4h
Empty ENDPOINTS with a Running pod means the Service's selector is not matching that pod's labels. Confirm with:
kubectl -n backend get pods --show-labels
NAME READY STATUS LABELS
mypod-7f9c8b 1/1 Running app=bakend
The pod is labeled app=bakend (typo) while the Service selects app=backend; Kubernetes performs an exact string match on selectors, so this is a silent, total mismatch with no error or event anywhere, just an empty Endpoints object. Fixing the pod's label (or the Service's selector, whichever is actually correct) and re-checking:
kubectl -n backend get endpoints mysvc
NAME ENDPOINTS AGE
mysvc 10.244.1.7:8080 4h
confirms the fix. A readiness probe that never passes produces the exact same empty-Endpoints symptom with correct labels, so if labels match, check kubectl describe pod for failing readiness next, before assuming the Service configuration itself is wrong.
Once Endpoints are populated: DNS, kube-proxy, and below
- Confirm DNS resolves the Service name (
mysvc.backend.svc.cluster.local) to the ClusterIP; if not, this is a CoreDNS problem layered on top, not a Service-routing problem. - Test the pod's IP directly, bypassing the Service entirely, to separate "the app itself is unreachable" from "the Service layer is unreachable":
kubectl -n backend exec -it debug -- curl -sv http://10.244.1.7:8080
- If the direct pod IP works but the Service's ClusterIP does not, the fault is in kube-proxy's dataplane. Depending on mode, inspect either iptables (
iptables-save | grep -A5 KUBE-SERVICES) or IPVS (ipvsadm -Ln) on the node the pod actually runs on, and confirm the kube-proxy DaemonSet itself is healthy (kubectl -n kube-system get ds kube-proxy). - Only after DNS, Endpoints, and kube-proxy rules all check out does a NetworkPolicy or a broken CNI plugin become the likely explanation; check
kubectl -n backend get networkpolicyand the CNI daemon's pods/logs (Calico, Cilium, or similar) last.
Trade-offs and pitfalls
- Kubernetes exposes both a legacy
Endpointsobject and the newerEndpointSliceAPI (the default source kube-proxy actually consumes at scale, sinceEndpointsobjects for a Service with many backends get unwieldy); checking only the olderEndpointsobject can be misleading on a cluster where a controller manages them differently, so cross-check both if the answer looks ambiguous. - Jumping straight to
tcpdumpand NetworkPolicy review before confirming Endpoints and DNS wastes the most time in practice; the majority of same-namespace Service failures resolve at step 1 or step 2. - A readiness probe that is too strict (checking a downstream dependency the pod does not actually need for basic connectivity) can look identical to a genuine networking outage: zero Endpoints, healthy pod, no obvious error anywhere except a probe failure buried in
kubectl describe pod.
For an organization running hundreds of microservices across dozens of AWS accounts, how would you design an IAM and permissions strategy that minimizes blast radius, enforces least privilege, and still scales with CI/CD automation? Discuss role-per-service versus permission boundaries.
Sample Answer
Direct answer
Give each microservice its own narrowly-scoped AWS Identity and Access Management (IAM) role (role-per-service) so a compromised or over-permissioned service can only affect its own resources, then wrap every role in an org-wide permission boundary that caps what any role can ever do, no matter how permissive its own policy becomes over time. Cross-account continuous integration/continuous delivery (CI/CD) uses short-lived AWS Security Token Service (STS) credentials via assume-role, never long-lived keys, and continuous auditing (IAM Access Analyzer, CloudTrail, Config) closes the loop by catching drift toward over-permission before it becomes an incident.
Role-per-service vs. permission boundaries: not a choice, a combination
These solve different problems, which is why the right design uses both rather than picking one:
- Role-per-service limits blast radius by identity: if the payments service's credentials leak, the attacker has exactly what the payments service's policy grants, nothing from the other 200 services in the org. The cost is scale: thousands of roles to manage across dozens of accounts.
- Permission boundaries limit blast radius by ceiling, independent of identity: even if a role's own policy is accidentally over-permissioned (a wildcard
s3:*added during a rushed fix), the permission boundary, a separate managed policy that intersects with the role's policy, caps the effective permissions to whatever the boundary allows. A role can never exceed its boundary, regardless of what its attached policy says.
Using role-per-service without a permission boundary means a single misconfigured policy on one service is unconstrained. Using only a permission boundary without role-per-service means every service shares one identity, so you lose the blast-radius isolation between services entirely. Combined, the boundary is the org-wide backstop and the per-service role is the day-to-day working scope.
Privilege escalation risk
The specific failure mode permission boundaries are designed to catch is privilege escalation through permission combinations that look safe individually but aren't together. Two classic examples:
iam:PassRoleplus a compute-creation action (lambda:CreateFunction,ec2:RunInstances): a role that can pass an arbitrary IAM role to a new Lambda function or EC2 instance can effectively assume any permissions that role has, even if the calling role's own policy never explicitly grants those permissions.iam:*oriam:CreatePolicyVersion/iam:AttachRolePolicyscoped too broadly: a role that can modify its own or another role's policy can grant itself more access over time, silently escaping whatever the original design intended.
A permission boundary set at creation time and enforced by an IAM condition that requires all new/modified roles to include it (via Service Control Policy or a CI-side check) closes this off structurally: even a role that somehow gains iam:PassRole or self-modification permissions still can't exceed the boundary's ceiling, because the boundary applies to the effective permission set, not just the role's own policy document.
Architecture: cross-account CI/CD flow
sequenceDiagram
participant CI as CI/CD pipeline
participant Deployer as Deployer role (CI account)
participant STS as AWS STS
participant Target as Service role (target account)
CI->>Deployer: assume with short-lived pipeline identity
Deployer->>STS: AssumeRole into target account
STS-->>Deployer: short-lived credentials
Deployer->>Target: deploy, bounded by target role's permission boundary
Note over Target: role scoped to one service,<br/>capped by org-wide boundary
Cross-account deployment uses AssumeRole with conditions (require multi-factor authentication where applicable, restrict by aws:CalledVia or a build-ID tag) rather than static IAM access keys stored in the pipeline. For containerized or serverless workloads, workload identity federation (IAM Roles for Service Accounts on Amazon Elastic Kubernetes Service, EKS, or a Lambda execution role) removes the need for any long-lived credential at all.
Worked example: sizing the blast radius
An organization runs 400 microservices across 20 AWS accounts, roughly 20 services per account. Role-per-service means a credential leak from any one service exposes access scoped to 1/20 of that account's services, not the whole account. Add the org-wide permission boundary, and even that 1/20 slice is capped below whatever the account-level "reasonable maximum" is defined to be, for example, no role may ever hold iam:*, cross-account trust modification, or KMS key deletion rights, regardless of what any individual service's policy grants. This is a design-target framing of blast-radius reduction, not a measured incident-cost figure.
Trade-offs and pitfalls
- Role-per-service at 400+ services is a real management burden: centralized policy templates and automated policy generation from service manifests (validated by Access Analyzer and a policy-diff check in CI before merge) are what keep this tractable at scale, rather than hand-writing each policy.
- Automated least-privilege generation from runtime telemetry risks overfitting to current behavior: a policy generated purely from observed CloudTrail activity can be too narrow for a legitimate but infrequent code path (a monthly batch job), causing production failures the day that path runs. Staged rollout and manual review for any newly-tightened policy affecting a critical path is the mitigation.
- Permission boundaries are easy to get backwards: they cap what a role's policy can grant, they don't grant anything by themselves. A role with no attached policy and a generous boundary still has zero permissions; that's a common source of confusion when a permission boundary rollout is treated as if it were an access grant.
- Continuous auditing needs an owner and an SLA, not just tooling: Access Analyzer and Config findings that sit unreviewed for months provide no actual blast-radius reduction, since the whole design depends on drift being caught and corrected promptly.
Design a chaos engineering program that progressively increases risk across service, database, and network layers for a critical system, starting with the safest experiments and working up. For each layer, what's your hypothesis, your blast-radius control, and your rollback criteria?
Sample Answer
Direct answer
Structure the program as a pyramid of increasing blast radius: start with single-instance, single-connection experiments in a canary slice of traffic, and only widen scope once the previous step stayed green. Every experiment, regardless of layer, needs the same three things stated up front: a falsifiable hypothesis (what should happen if the system is as resilient as you believe), a blast-radius control (the mechanism that caps how much traffic or infrastructure the experiment can touch), and a rollback criterion (the automated trigger that aborts the experiment before it becomes an incident).
Program structure by layer
| Layer | Hypothesis | Blast-radius control | Rollback criteria | Safest to riskiest experiments |
|---|---|---|---|---|
| Service | Losing a fraction of worker instances doesn't breach the SLO, because retries and circuit breakers absorb it | Canary AZ, capped at 1 to 5 percent of real traffic, feature-flag kill switch | Error rate more than 2x baseline AND p95 latency over SLO for 5 minutes, or success rate drops more than 1 percentage point absolute | (1) kill one non-primary worker process, (2) terminate 5 percent of workers in one AZ, (3) inject added latency into a canary slice, (4) disable retries on a canary path to check the fallback actually engages |
| Database | Read replicas and connection pooling keep reads available; write failures retry or queue without data loss | Target one replica or one connection pool at a time, throttle at the connection level, never touch the primary directly in early stages | Replication lag over a fixed threshold (for example 30 seconds), write failure rate spikes more than 1 percentage point absolute, or any detected data divergence | (1) throttle one read replica's I/O by 10 percent, (2) pause replication on one replica briefly, (3) close 5 percent of connections from a non-critical pool, (4) simulate primary failover, first in staging, then in a production canary |
| Network | Timeouts, retries, and the service mesh absorb transient network faults without payment (or equivalent critical-path) loss | Confine faults to one AZ and a capped traffic percentage using mesh-level fault injection, not a real router or switch | End-to-end success rate on the critical path drops more than 1 percentage point, or a circuit breaker stays open across more than 2 dependent services simultaneously | (1) add 50ms latency to one client-to-service hop, (2) inject 1 percent packet loss in one AZ for 5 minutes, (3) simulate a route flap between two internal services, (4) blackhole a non-critical downstream dependency and confirm graceful degradation, not failure |
The pyramid runs left to right within a layer, and layer to layer (service before database before network) because a service-level failure is the easiest to reason about and the cheapest to roll back; database and network faults touch more of the system at once and take longer to reverse cleanly.
Execution discipline
- Pre-flight: a written runbook, on-call and stakeholders notified, an automated abort mechanism wired to the rollback criteria (not a human watching a dashboard and deciding), and the experiment coded as a reproducible, version-controlled script rather than an ad hoc manual action.
- During: watch the rollback-criteria metrics in real time; the abort has to be automatic and fast, because by the time a human notices a metric crossing threshold and manually intervenes, the blast radius has often already grown past what the control was meant to cap.
- After: a lightweight postmortem regardless of outcome (a clean pass is still evidence worth recording), and only widen scope for the next run once the current one is unambiguously green, not "green with an asterisk."
Trade-offs & pitfalls
The single most common mistake is skipping straight to a production-wide experiment because a staging environment "doesn't reproduce the failure mode," which is often true but doesn't change the fact that the first production run of any new fault type belongs in the smallest blast radius you can construct, even if that means accepting a less realistic signal initially. A second pitfall is defining rollback criteria in terms of the fault itself (for example, "abort if packet loss exceeds 2 percent") instead of user-facing impact (error rate, latency, success rate); the fault is the input you're controlling, the rollback trigger has to watch the output, or you can hit exactly your intended fault level while still causing an unacceptable customer-facing outage. Not every fault type generalizes across domains the same way either: a GPU training job's most dangerous failure mode isn't a crashed worker (checkpointing handles that cheaply) but silent numerical divergence (the training job keeps running, but silently starts computing mathematically wrong updates to the model, with no crash or error to announce it), where the job keeps running and producing wrong gradients (a gradient is the per-step adjustment the training process makes to the model's internal numbers; a wrong one nudges the model in a bad direction instead of a good one) with no immediate error signal, so the safe blast-radius control there is different in kind from an HTTP service's traffic-percentage cap: it's about capping how long a divergence can run undetected before an automated metric check, watching a loss curve (a plot of the model's error over time, which should trend down) or a gradient norm (a single number summarizing how large the model's updates are; a sudden spike signals training has gone unstable), kills the job, not about capping how many requests are affected. A resilience program that only ever tests one fault type at a time also under-tests: real incidents are frequently two failures at once (a network blip during a deploy, a slow dependency during a traffic spike), so a mature program's later stages deliberately combine fault types once single-fault experiments across all three layers are consistently passing.
Write a Bash function that loads key=value settings from a file into the environment, ignoring comments and blank lines, trimming whitespace and rejecting invalid variable names. Why is sourcing the file directly risky?
Sample Answer
Direct answer
Read the file as data, one line at a time: split at the first =, trim, check the name against a strict pattern, and set the variable with export "$key=$value". Never run the file. Sourcing (. file or source file) hands the whole file to the shell as a script, so anything in it that the shell treats as syntax gets executed: command substitution, backticks, ;, &, unquoted spaces. A config file is usually writable by more people and tools than the script is, so executing it turns config access into code execution with the script's privileges.
The artifact
Run on ubuntu:24.04 with bash 5.2. A few shell idioms in the code, read once here: local makes a variable private to the function; [[ ... ]] is Bash's test command and =~ inside it matches a regular expression; case ... in pattern|pattern) ...;; esac runs the branch whose glob pattern matches (;; ends the branch, and | separates alternatives); export "$key=$val" sets a variable and passes it to child programs; ${!v-<unset>} is indirect expansion, meaning "the value of the variable whose name is stored in v, or the text <unset> if there is none".
#!/usr/bin/env bash
set -u
# load_env FILE: export KEY=VALUE pairs from FILE without ever executing its content.
load_env() {
local file=$1 line key val n=0 bad=0
[[ -r $file ]] || { echo "load_env: cannot read $file" >&2; return 2; }
while IFS= read -r line || [[ -n $line ]]; do
n=$((n + 1))
line=${line%$'\r'} # tolerate CRLF files
line=${line#"${line%%[![:space:]]*}"} # trim leading whitespace
[[ -z $line || $line == \#* ]] && continue # blank line or comment
if [[ $line != *=* ]]; then
echo "load_env: $file:$n: no '=' found" >&2; bad=1; continue
fi
key=${line%%=*}; val=${line#*=}
key=${key%"${key##*[![:space:]]}"} # trim trailing whitespace on key
val=${val#"${val%%[![:space:]]*}"} # trim leading whitespace on value
val=${val%"${val##*[![:space:]]}"} # trim trailing whitespace on value
if [[ ! $key =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
echo "load_env: $file:$n: invalid name '$key'" >&2; bad=1; continue
fi
case $key in
PATH|IFS|ENV|SHELLOPTS|PS4|PROMPT_COMMAND|LD_*|BASH_*)
echo "load_env: $file:$n: refusing to set $key" >&2; bad=1; continue ;;
esac
if [[ $val == \"*\" || $val == \'*\' ]] && (( ${#val} >= 2 )); then
val=${val:1:${#val}-2} # strip one matching pair of quotes
fi
export "$key=$val"
done < "$file"
return "$bad"
}
printf '%s\n' \
'# service settings' \
'' \
' DB_HOST = db.internal ' \
'DB_PORT=5432' \
'GREETING="hello world"' \
'URL=https://x.test/a?b=1&c=2#frag' \
'TRICK=$(touch /tmp/pwned)' \
'TICK=`touch /tmp/pwned`' \
'1BAD=oops' \
'my-var=oops' \
'export FOO=bar' \
'PATH=/evil' \
'NOEQUALS' \
'LAST=no newline at end' > app.env
truncate -s -1 app.env
load_env app.env; echo "load_env returned $?"
for v in DB_HOST DB_PORT GREETING URL TRICK TICK LAST; do printf '%s=[%s]\n' "$v" "${!v-<unset>}"; done
echo "PATH still: $PATH"
ls /tmp/pwned 2>&1
echo "--- a file with three ordinary-looking lines, sourced"
printf '%s\n' 'NAME=hello world' 'TRICK=$(touch /tmp/pwned)' 'N=1' > risky.env
( . ./risky.env; ls /tmp/pwned; echo "N=$N" )
Output:
load_env: app.env:9: invalid name '1BAD'
load_env: app.env:10: invalid name 'my-var'
load_env: app.env:11: invalid name 'export FOO'
load_env: app.env:12: refusing to set PATH
load_env: app.env:13: no '=' found
load_env returned 1
DB_HOST=[db.internal]
DB_PORT=[5432]
GREETING=[hello world]
URL=[https://x.test/a?b=1&c=2#frag]
TRICK=[$(touch /tmp/pwned)]
TICK=[`touch /tmp/pwned`]
LAST=[no newline at end]
PATH still: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ls: cannot access '/tmp/pwned': No such file or directory
--- a file with three ordinary-looking lines, sourced
./risky.env: line 1: world: command not found
/tmp/pwned
N=1
What each rule does
IFS= read -r line || [[ -n $line ]](IFS=empties the field-separator setting so nothing is trimmed or split) keeps leading spaces and backslashes intact, and the second test still processes a last line that has no trailing newline (LASTloaded above). What goes wrong without them, on the inputkeep\this: plainread -rprints[keep\this](the spaces are trimmed),IFS= read -rprints[ keep\this ](kept exactly), andIFS= readwithout-rprints[ keepthis ](the backslash was eaten).- Trimming uses parameter expansion only, with no extra processes. The
\rstrip makes Windows (CRLF, carriage return plus line feed) files work. The nested trims read inside out.${var#pattern}removes the shortest match ofpatternfrom the start,${var%pattern}from the end, and doubling the symbol (##,%%) removes the longest match.[![:space:]]is one character that is not whitespace. Traced onDB_HOST = db.internal:${line%%[![:space:]]*}removes the longest tail that starts at the first non-space character, so what is left is just the leading blanks,[ ].${line#"..."}then removes exactly those blanks from the front, leaving[DB_HOST = db.internal ]. The inner expansion is quoted so its characters are matched literally.- For trailing blanks on the key
DB_HOSTthe pair is mirrored:${key##*[![:space:]]}removes the longest head that ends at a non-space character, leaving the trailing blanks[ ], and${key%"..."}removes them from the end, leaving[DB_HOST].
- Splitting at the first
=(%%=*and#*=) keeps=inside values. Comments are whole-line only, because#is legal in a value: theURLline keeps its#frag. - The name must match
^[A-Za-z_][A-Za-z0-9_]*$. The lines1BAD,my-varandexport FOOare rejected with the file name and line number, and the loop carries on. - The quote test
[[ $val == \"*\" || $val == \'*\' ]]reads "starts and ends with a double quote, or starts and ends with a single quote" (the backslashes keep the quote characters literal inside the pattern). The pattern already needs two quote characters, so a lone"never matches it; the extra(( ${#val} >= 2 ))is a redundant guard that keeps the${val:1:${#val}-2}slice arithmetic safe."hello world"becomeshello world;"halfand a bare"are left as they are. export "$key=$val"expands the variable once, inside double quotes, and the result is not parsed again. That is whyTRICKandTICKended up holding their text literally and/tmp/pwnedwas never created.- A valid name is not a safe name. The
caserefuses variables that change how the shell or the dynamic loader (the system component that starts a program and loads its shared libraries) behaves:PATHdecides which program a bare command name runs, soPATH=/evilcan swap in a fakels;IFSchanges how the shell splits words;ENV,SHELLOPTS,PS4andPROMPT_COMMANDmake the shell run or alter commands on its own;LD_*(such asLD_PRELOAD) makes the loader load an attacker's library into every program started;BASH_*includes names such asBASH_ENV, a file Bash runs on start-up. In the demo thePATH=/evilline was refused andPATHis unchanged. Safer still is an allowlist, for example only names starting withAPP_. - Matching quotes are stripped once (
GREETING), with no escape processing. This is a key=value reader, not a shell parser; if values need escapes or lists, use a real format such as JSON read withjq. - The return status is 1 if any line was rejected, but valid lines before and after were still exported. For a service start-up path, call it as
load_env app.env || exit 1. Call it directly, not inside a pipe or$(...), because those run in a subshell and the exports would vanish.
Why sourcing is risky, shown
In the demo, truncate -s -1 app.env shortens the file by one byte, which removes the final newline so the LAST line tests the no-trailing-newline path, and the sourcing test runs inside ( ... ), a subshell (a child shell), so the variables and side effects of . ./risky.env do not leak into the rest of the demo.
The second block of output comes from a file with three lines that look ordinary. NAME=hello world makes the shell try to run a command called world, and TRICK=$(touch /tmp/pwned) runs touch, so /tmp/pwned exists afterwards. A URL value containing & would also be cut at that point and the rest run as a separate background command. None of this needs an attacker: an unquoted space is enough to break a start-up script.
Trade-offs
Parsing by hand costs you shell features people expect in .env files (interpolation such as ${HOME}, multi-line values, export prefixes). Refusing those loudly is the safe choice; silently half-supporting them is how injection bugs return. Variables exported this way are also inherited by every child process, so secrets belong in a file read only by the process that needs them.
Design autoscaling for a fleet of background workers that process messages off a queue. What metrics would you use (queue length, processing time, CPU utilization), what scaling algorithm converts backlog into a target worker count, and what safety limits would you set to avoid over- or under-provisioning during a spike?
Sample Answer
Direct answer
Size the worker fleet off the backlog itself, converting queue length and per-message processing time directly into a target worker count, and use CPU as a secondary guard rather than the primary signal. Queue depth (or, for a partitioned stream, consumer lag) is the most direct evidence of "we're falling behind"; CPU alone can be healthy while a backlog grows if workers are stalled on I/O, or misleadingly high while the backlog is perfectly under control. Layer safety limits (step caps, cooldowns, min/max bounds) on top so a burst doesn't cause wild over- or under-shoot.
Structured elaboration
Core metrics
- Queue length (N): the direct backlog signal.
- Average processing time per message (T_proc): converts backlog into required throughput.
- CPU utilization: a secondary guard against a worker fleet that looks backlog-healthy but is actually resource-starved.
Backlog-to-workers formula. To drain a backlog of N messages within a target drain time T_drain, the fleet needs enough aggregate throughput to clear it in that window. If each worker processes messages serially, one worker's throughput is 1/T_proc messages/sec, so:
workers=⌈TdrainN×Tproc⌉Safety limits. These bound how far the formula's output is allowed to move the fleet in one step, which matters directly for whether the fleet meets its service level objective (SLO, the measurable target committed to, e.g. "drain a backlog within 60 seconds") during a fast ramp, as the worked example below shows.
min_workers/max_workers: hard floor and ceiling regardless of what the formula outputs.- Scale-step cap (for example, no more than 50% growth per scaling cycle): prevents a single noisy reading from provisioning an extreme jump in one step.
- Cooldown between scaling actions (for example, 60-120 seconds) with metric smoothing (a short rolling average) so a single spiky reading doesn't trigger a scale event.
- Rapid scale-up, slower scale-down: react quickly to a growing backlog, but shrink capacity conservatively so a brief lull doesn't strip capacity right before the next burst.
Per-tier metric selection. In a multi-tier application, the right autoscaling signal differs by tier, and using one signal everywhere is itself a common mistake: the web tier is user-facing, so latency is the metric that actually reflects what matters; the API tier is typically compute-bound backend logic, so CPU is a reasonable primary signal; the worker tier processes an explicit backlog, so queue depth is the direct signal, the way latency is for web and CPU is for the API tier. Whichever signal is used, cost controls (a hard max-worker cap, and an alert if the fleet stays above a stated size for longer than expected) are what prevent a single misbehaving upstream producer from scaling the worker fleet into a runaway cost event.
Worked example
Assume, as stated planning inputs: a backlog of N = 12,000 messages, an average processing time of T_proc = 2 seconds/message, and a target drain time of T_drain = 60 seconds. The desired worker count:
⌈6012,000×2⌉=400Now apply a safety limit: no more than 50% worker growth per one-minute scaling cycle. Starting from 100 running workers, the staged ramp toward the 400-worker target looks like:
100→150→225→337.5→506.25which crosses 400 on the fourth cycle (after the third cycle, 337.5 is still short of target). So this safety-limited policy takes 4 minutes to reach the needed capacity, even though the backlog math says 400 workers are needed immediately. That gap is the real cost of the safety cap: it protects against overreacting to a noisy or momentary spike, but it also means the actual drain time will run longer than the 60-second target during the ramp itself, since the fleet is under-provisioned relative to target for those first few minutes. Whether that trade-off is acceptable depends on how much backlog growth is tolerable during the ramp; a service with a hard service-level agreement (SLA) on processing latency may need a less conservative step cap than one where a few extra minutes of backlog is a minor issue.
Stream-consumer variant. For a spiky stream consumer (reading from a partitioned log rather than a plain queue), the same three-way comparison applies with different names: CPU-based triggers share the same weakness as for queue workers (a poor signal for I/O-bound consumption); consumer-lag-based triggers (how far behind the consumer group is from the head of its partitions) are the direct analog of queue length, the most trustworthy backlog signal; custom metrics (bytes/sec consumed, records pending) can supplement but rarely replace lag as the primary signal. The hybrid-policy recommendation is the same shape as for queue workers: consumer lag as the primary scaling trigger, with CPU as a secondary guard so a consumer group that's lag-healthy but resource-starved still gets flagged.
Trade-offs & pitfalls
- Queue length alone can't distinguish "genuinely more work arrived" from "one poison message is stuck and blocking a worker"; a growing queue with flat CPU and flat message-completion rate is a different problem than a growing queue with rising CPU, and the fix (unblock the stuck message vs. add workers) is different too.
- The backlog formula assumes a roughly uniform T_proc; if processing time has high variance (a few messages take far longer than the average), the formula under-provisions, since it's sizing to the average, not the tail.
- A tight scale-step safety cap and an aggressive target drain time are in direct tension, as the worked example shows; setting both without checking whether the ramp time itself fits inside the drain-time SLO is a common oversight.
- Extending this to a partitioned stream isn't just a metric swap: consumer count is capped by partition count the same way it is for any Kafka-style consumer group, so "add more workers" eventually requires adding partitions too, not just adding consumers.
Recommended Additional Resources
- A Cloud Guru / Linux Academy - DevOps fundamentals courses and hands-on labs for Docker, Kubernetes, and AWS
- Linux Academy - Deep Linux fundamentals and command-line mastery (essential foundation)
- Kubernetes.io official documentation and interactive tutorials
- Docker official documentation and getting started guide
- Jenkins official documentation and pipeline tutorials
- AWS Free Tier - hands-on practice with EC2, S3, CloudWatch, IAM, and other core services
- System Design Primer GitHub - basic architecture and scaling concepts simplified
- FAANG companies' engineering blogs - Amazon, Google, Meta, Netflix tech blogs for architecture insights
- The DevOps Handbook by Gene Kim, Jez Humble, Patrick Debois, and John Willis - understand DevOps philosophy and practices
- Bash scripting tutorials and practice - focusing on automation and common DevOps scripts
- Git documentation and practice - master version control workflows and troubleshooting
- Practice platforms: LeetCode (easy/medium shell scripting), HackerRank (DevOps challenges), Codewars (Bash and Python)
- YouTube channels: DevOps Simplified, That DevOps Guy, Linux Academy channels for visual learning
- Reddit communities: r/devops, r/sysadmin for advice and real-world perspectives
- AWS Certification prep (AWS Cloud Practitioner) - not required but helpful for cloud fundamentals
- Docker Hub and GitHub - practice building and deploying real projects in containers
Search Results
AWS DevOps Interview Questions: Top 100+ Questions 2025
This article provides a comprehensive guide to prepare you for AWS DevOps interviews, with top questions, scenario-based queries, and expert tips to help you ...
Top 55+ DevOps Interview Questions and Answers for 2026 - igmGuru
10. What is Automation Testing and how is it beneficial? 11. What do you understand by Git stash? 12. What is the use of SSH (Secure Shell)?. 13. What is ...
8 DevOps Interview Questions (With Sample Answers) - Indeed
1. What is DevOps? · 2. What are some advantages of DevOps? · 3. What is configuration management? · 4. What is continuous integration? · 7. What are the phases of ...
DevOps Interview Questions in 2026 - Network Kings
DevOps Interview Questions Guide · What is DevOps, and why do we need it? · How does DevOps differ from the old school IT? · What are the basic principles of ...
50+ DevSecOps Interview Questions and Answers for 2025
Important DevSecOps Interview Questions and Answers – Updated · How do you prioritize security within the DevOps workflow? · Differentiate between DevOps and ...
DevOps Interview Secrets: What They ACTUALLY Ask (Junior to ...
... basic troubleshooting. (Questions 1-5) For Mid-Level Engineers: Prove you can independently troubleshoot complex systems and design robust processes. (Questions ...
This interview preparation guide was generated using AI-powered research from the sources listed above. While we strive for accuracy, we recommend verifying critical information from official company sources.
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths