AAWSLearn

AWSLearn

DevOps Interview Hub

Ace your next technical interview. Browse battle-tested questions and detailed answers covering AWS architecture, Kubernetes orchestration, Linux system calls, CI/CD, and Terraform state management.

Security Groups and Network Access Control Lists (NACLs) are the two primary layers of security in an Amazon VPC:

  1. Statefulness:

    • Security Groups are stateful. If you send a request from your instance, the response traffic is automatically allowed back in, regardless of inbound rules.
    • NACLs are stateless. Return traffic must be explicitly allowed by rules in the opposite direction.
  2. Level of Operation:

    • Security Groups operate at the instance/ENI level (Elastic Network Interface).
    • NACLs operate at the subnet level, acting as a firewall for all instances within that subnet.
  3. Rules:

    • Security Groups only support allow rules. By default, all outbound traffic is allowed, and all inbound traffic is blocked.
    • NACLs support both allow and deny rules, processed in numbered order.

Here is a summary table:

Feature Security Group Network ACL (NACL)
Scope Instance / ENI level Subnet level
State Stateful Stateless
Rules supported Allow rules only Allow & Deny rules
Order of execution All rules evaluated Processed sequentially

AWS RDS offers two primary features for database scaling and reliability:

  1. RDS Multi-AZ (High Availability):

    • Purpose: Disaster Recovery and High Availability.
    • Mechanism: Synchronous replication from a primary DB instance to a standby DB instance in a different Availability Zone (AZ).
    • Behavior: AWS automatically handles failover to the standby DB if the primary DB fails, preserving the same DNS endpoint. You cannot read from or write to the standby instance directly.
  2. RDS Read Replicas (Scalability):

    • Purpose: Read scalability and performance optimization.
    • Mechanism: Asynchronous replication from the primary instance to one or more read-only replica databases.
    • Behavior: You can query read replicas to offload read traffic (BI dashboards, reporting, static reads). Replicas can be promoted to standalone databases.

Here is how you might configure a Read Replica using Terraform:

# Primary Database
resource "aws_db_instance" "primary" {
  identifier           = "db-primary"
  allocated_storage    = 20
  engine               = "postgres"
  engine_version       = "15.4"
  instance_class       = "db.t3.micro"
  username             = "cloudadmin"
  password             = "SecurePassword123!"
  backup_retention_period = 7  # Required to enable replication
}

# Read Replica DB
resource "aws_db_instance" "replica" {
  identifier             = "db-replica"
  replicated_from_source = aws_db_instance.primary.identifier
  instance_class         = "db.t3.micro"
  skip_final_snapshot    = true
}

Kubernetes uses three types of container probes to monitor pod health:

  1. startupProbe:

    • Purpose: Checks if the application inside the container has successfully started.
    • Behavior: All other probes (liveness/readiness) are disabled until the startupProbe succeeds. Useful for slow-starting legacy apps to prevent them from being killed prematurely.
  2. livenessProbe:

    • Purpose: Checks if the container needs to be restarted.
    • Behavior: If this probe fails, the kubelet kills the container, and the pod's restart policy is triggered.
  3. readinessProbe:

    • Purpose: Checks if the container is ready to accept client traffic.
    • Behavior: If this probe fails, the pod is removed from the Endpoints of all matching Services, preventing traffic from reaching it.

Here is an example YAML snippet from a Deployment manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  template:
    spec:
      containers:
      - name: api-container
        image: node-api:v1.0
        ports:
        - containerPort: 8080
        
        # 1. Startup Probe
        startupProbe:
          httpGet:
            path: /healthz/startup
            port: 8080
          failureThreshold: 30
          periodSeconds: 10
          
        # 2. Liveness Probe
        livenessProbe:
          httpGet:
            path: /healthz/live
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 15
          
        # 3. Readiness Probe
        readinessProbe:
          httpGet:
            path: /healthz/ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
  1. DaemonSet:

    • Ensures that exactly one copy of a Pod runs on all (or a filtered selection of) Nodes in the cluster.
    • As new nodes are added to the cluster by autoscalers, the DaemonSet automatically schedules pods onto them.
    • Use Cases: System daemons like log shippers (Fluentbit), monitoring agents (Prometheus Node Exporter), or networking plugins (kube-proxy).
  2. Deployment:

    • Declares a desired number of replica Pods to run.
    • The scheduler assigns them to nodes based on resource capacity, affinity, and anti-affinity rules, without binding them to every single node.
    • Use Cases: Stateless applications, APIs, frontends, and microservices.

A multi-stage build allows you to use multiple FROM instructions in a single Dockerfile. Each FROM begins a new stage of the build using a different base image, allowing you to copy artifacts selectively from one stage to another.

Why use it?

  • Reduces Image Size: Separates build-time dependencies (compilers, SDKs, test libraries) from runtime packages.
  • Improves Security: Deploys only compiled binaries, minimizing tools that an attacker could exploit.
  • Simplifies Pipeline: Eliminates the need to maintain separate build and run scripts.

Example Multi-stage Dockerfile:

# Stage 1: Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Runtime stage
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["npm", "start"]

Both CMD and ENTRYPOINT define the command executed when running a container, but they behave differently when arguments are passed:

  1. ENTRYPOINT:

    • Sets the default executable wrapper that will always be run when the container starts.
    • It cannot be overridden by arguments passed to docker run (unless you use the explicit --entrypoint flag).
  2. CMD:

    • Defines the default arguments passed to the ENTRYPOINT.
    • If no ENTRYPOINT is defined, CMD is treated as the executable command.
    • Override: Passing any argument to docker run <image> <args> completely overrides the CMD instruction.

Best Practice Example:

Use them in combination. The ENTRYPOINT points to the binary, and CMD acts as the default argument.

ENTRYPOINT ["ping"]
CMD ["localhost"]

If run normally: docker run my-ping -> executes ping localhost. If run with arguments: docker run my-ping google.com -> executes ping google.com.

Load average represents the average system load over 1, 5, and 15 minutes. It measures CPU demand, representing the number of processes that are:

  • Running on the CPU.
  • Runable and waiting for CPU time.
  • In uninterruptible sleep state (blocked, usually waiting for disk or network I/O).

Interpreting the Values

You must know the number of CPU cores in the system (e.g. check nproc or htop).

  • Single Core CPU:

    • Load average of 0.5: CPU is idle 50% of the time.
    • Load average of 1.0: CPU is exactly at 100% capacity.
    • Load average of 2.0: The queue is full; CPU is overloaded, and processes are waiting for 50% of their execution time.
  • 4-Core CPU:

    • Load average of 2.0: CPU is at 50% overall capacity.
    • Load average of 4.0: CPU is at 100% capacity.
    • Load average of 8.0: CPU is overloaded; 4 active processes are running on the cores, and 4 others are waiting in queue.

You can inspect load average using commands like uptime, w, or reading /proc/loadavg:

$ uptime
 18:14:02 up 12 days,  3:14,  1 user,  load average: 0.85, 1.20, 2.45

The Linux boot sequence consists of the following steps:

  1. BIOS / UEFI: Runs Power-On Self Test (POST), detects hardware configurations, and executes the primary Boot Loader from the Master Boot Record (MBR) or EFI partition.
  2. GRUB Bootloader: Loads the selected kernel image (vmlinuz) and initial RAM disk (initramfs) into memory.
  3. Kernel Initialization: Mounts the temporary root file system, configures hardware drivers, and executes the initial user space process.
  4. systemd: The kernel spawns sbin/init, which is symlinked to systemd on modern distributions.

The Role of systemd

systemd is the system init manager (Process ID 1) responsible for bootstrapping user space services.

  • Targets: Group files (e.g. multi-user.target) that define system states, replacing old SysV init runlevels.
  • Parallelization: Starts multiple system services in parallel utilizing socket activation to speed up boot times.
  • Dependency Management: Defines exact relationships between services using parameters like Requires=, After=, and Wants= in unit configuration files.

For example, a basic systemd service file (/etc/systemd/system/app.service):

[Unit]
Description=AWSLearn Node Backend API
After=network.target

[Service]
Type=simple
User=node
WorkingDirectory=/opt/app
ExecStart=/usr/bin/npm start
Restart=on-failure

[Install]
WantedBy=multi-user.target

Terraform uses a state file (terraform.tfstate) to store the mappings between declared resource blocks in code and real-world resources provisioned in the cloud provider.

Why Remote State is Critical

  1. Team Collaboration: Storing state locally prevents multiple developers from working together. Remote backends (like AWS S3) act as a single source of truth.
  2. State Locking: Prevents concurrent execution of terraform apply that could cause state corruption or duplicate resources. S3 uses DynamoDB tables to acquire locks.
  3. Sensitive Information: State files contain passwords, database strings, and private keys in plain text. Storing state remotely permits restricting access using IAM policies and encrypting at rest.

Here is a standard Terraform configuration for a remote backend using S3 and DynamoDB:

terraform {
  backend "s3" {
    bucket         = "awslearn-tf-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-locking-table"
  }
}

Both commands integrate changes from one branch into another, but they differ in how they write commit history:

  1. git merge:

    • Mechanism: Creates a new "merge commit" that ties the history of both branches together.
    • Pros: Preserves the exact chronological history and context of when branches were merged. It is non-destructive.
    • Cons: Can pollute git history with frequent merge commits, making visual graphs hard to read.
  2. git rebase:

    • Mechanism: Moves the entire base of your branch to the tip of the target branch, rewriting commit hashes sequentially.
    • Pros: Produces a clean, linear git history with no merge commits.
    • Cons: Rewrites commit history. If done on shared public branches, it can cause sync chaos for other developers.

Rules of Thumb

  • Rebase local branches before opening a Pull Request to align your work with the latest changes in the main branch.
  • Merge branches when merging a Pull Request into main, preserving the integration point in the main branch.
  • Rule: Never rebase commits that have been pushed to a public repository.

CI/CD pipelines automate code validation and shipping, but there is a distinct boundary between Continuous Delivery and Continuous Deployment:

  1. Continuous Delivery:

    • Code changes are automatically built, tested, and pushed to a repository (like Docker Hub or S3).
    • Manual step: Deploying the release package to production requires manual trigger/approval (e.g. clicking a "Deploy to Prod" button in GitHub Actions or AWS CodePipeline).
  2. Continuous Deployment:

    • Every single commit that successfully passes the automated tests and quality checks is automatically deployed directly to production without any human intervention.
    • Requires mature test suites, automated rollbacks, and canary release strategies.

Here is a visual progression flow:

Continuous Integration (CI):
[Code] -> [Build] -> [Test]

Continuous Delivery (CD):
[CI Flow] -> [Release Packaged] -> [Manual Staging Review] -> (Manual Approval) -> [Deploy to Prod]

Continuous Deployment (CD):
[CI Flow] -> [Release Packaged] -> [Deploy to Prod (Automated)]

GitOps is an operational model for Kubernetes and cloud infrastructure that uses Git as the single source of truth for declarative state.

Push-based Pipelines (Traditional)

  • Flow: A CI system (like GitHub Actions, GitLab CI) runs a script that logs into the cloud environment (e.g. running kubectl apply or terraform apply) to push configuration.
  • Risk: CI runners must store admin credentials (like kubeconfig, AWS keys) locally.

Pull-based Pipelines (GitOps)

  • Flow: An operator (like ArgoCD or Flux) runs inside the Kubernetes cluster. It constantly polls the target Git repo, detects differences between code and cluster state, and pulls changes to reconcile them.
  • Security: No external access credentials need to be exposed. The cluster pulls changes internally.

Here is a side-by-side comparison:

Feature Push-based CI/CD Pull-based (GitOps)
Trigger Code change -> CI runner runs Git controller detects drift
Credentials Stored in CI variables Stored securely in cluster
Drift Correction Runs only on code push Corrects drift automatically
Source of Truth Git + deployment runner Git repository

Get Cloud Architecture Tutorials

Join 10,000+ cloud developers. Subscribe to receive our bi-weekly tutorials on AWS, Kubernetes, Terraform, and system design patterns.

We respect your privacy. Unsubscribe at any time.

Search AWSLearn

Search for AWS VPC, Karpenter, Docker builds, Linux runlevels, or Terraform states.