DevOpsIndex

CI/CD Pipeline Design — End to End

Full Java CI Pipeline

graph LR
    PUSH["git push / PR"] --> CHECKOUT["1. Checkout code<br/>git clone --depth 1"]
    CHECKOUT --> DEPS["2. Pull dependencies<br/>mvn dependency:resolve<br/>or cached ~/.m2"]
    DEPS --> UNIT["3. Unit Tests<br/>mvn test<br/>JUnit + Mockito"]
    UNIT --> INTEG["4. Integration Tests<br/>mvn verify<br/>testcontainers for DB/Redis"]
    INTEG --> BUILD["5. Build artifact<br/>mvn package -DskipTests<br/>--> target/app.jar"]
    BUILD --> DOCKER["6. Build Docker image<br/>multi-stage Dockerfile<br/>--> image:sha-abc1234"]
    DOCKER --> SCAN["7. Security scan<br/>trivy image / snyk<br/>fail on CRITICAL CVEs"]
    SCAN --> PUSH_IMG["8. Push to registry<br/>ECR / DockerHub<br/>tag: sha + semver"]

GitHub Actions: Full Java CI

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  ECR_REPO: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp
  JAVA_VERSION: "21"

jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - uses: actions/setup-java@v4
      with:
        java-version: ${{ env.JAVA_VERSION }}
        distribution: temurin
        cache: maven          # cache ~/.m2 by pom.xml hash

    - name: Unit Tests
      run: mvn test -B
      # -B = batch mode (no ANSI colors, better for CI logs)

    - name: Integration Tests
      run: mvn verify -B -Pintegration
      # Pintegration = Maven profile that runs testcontainers tests

    - name: Build JAR
      run: mvn package -DskipTests -B

    - name: Configure AWS credentials (OIDC — no static keys)
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::123456789:role/github-actions-ecr
        aws-region: us-east-1

    - name: Login to ECR
      uses: aws-actions/amazon-ecr-login@v2

    - name: Build and push Docker image
      run: |
        IMAGE_TAG="sha-${{ github.sha }}"
        docker build \
          --cache-from type=gha \    # use GH Actions cache for Docker layers
          --cache-to type=gha,mode=max \
          -t $ECR_REPO:$IMAGE_TAG \
          -t $ECR_REPO:latest .
        docker push $ECR_REPO:$IMAGE_TAG
        docker push $ECR_REPO:latest
        echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_OUTPUT
      id: build

    - name: Scan image for vulnerabilities
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: ${{ env.ECR_REPO }}:sha-${{ github.sha }}
        severity: CRITICAL,HIGH
        exit-code: 1    # fail CI if CRITICAL found

CD: Deploy to Two Clusters

Option A: ArgoCD (Recommended — GitOps)

sequenceDiagram
    participant CI as CI Pipeline
    participant GIT as manifests repo
    participant ARGO as ArgoCD (management cluster)
    participant C1 as Cluster 1 (staging)
    participant C2 as Cluster 2 (prod)

    CI->>GIT: update image tag in values.yaml<br/>staging/values.yaml: image.tag=sha-abc1234
    GIT->>ARGO: ArgoCD detects git change
    ARGO->>C1: sync Application staging --> deploy new image
    C1-->>ARGO: Healthy

    Note over CI,ARGO: After staging validation (manual gate or automated test)

    CI->>GIT: update prod/values.yaml: image.tag=sha-abc1234
    GIT->>ARGO: ArgoCD detects git change
    ARGO->>C2: sync Application prod --> deploy new image
    C2-->>ARGO: Healthy

Where does ArgoCD live? One ArgoCD instance in a management cluster. It registers both staging and prod clusters via argocd cluster add. Both clusters' kubeconfigs are stored as Secrets in the ArgoCD namespace.

# Register clusters with ArgoCD
argocd cluster add staging-context --name staging
argocd cluster add prod-context --name prod

# ArgoCD ApplicationSet: one definition → two applications
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
  generators:
  - list:
      elements:
      - cluster: staging
        url: https://staging-api.eks.amazonaws.com
        values_file: staging/values.yaml
      - cluster: prod
        url: https://prod-api.eks.amazonaws.com
        values_file: prod/values.yaml
  template:
    spec:
      source:
        helm:
          valueFiles: ["{{values_file}}"]
      destination:
        server: "{{url}}"

Option B: GitHub Actions / Jenkins — Pushing via kubectl/helm

graph LR
    CI["CI/CD Pipeline<br/>(GHA or Jenkins)"] -->|"kubectl --kubeconfig staging-kube"| C1["Cluster 1 (staging)"]
    CI -->|"kubectl --kubeconfig prod-kube"| C2["Cluster 2 (prod)"]

Sequential vs Parallel:

  • Sequential: deploy staging → wait for health check → deploy prod (safer — catch issues before prod)
  • Parallel: deploy both simultaneously (faster — use only if staging and prod are truly independent)
# GitHub Actions: sequential deploy
jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
    - name: Configure kubeconfig for staging
      run: |
        aws eks update-kubeconfig \
          --name staging-cluster \
          --region us-east-1 \
          --role-arn arn:aws:iam::123:role/staging-deploy
    - name: Helm upgrade staging
      run: |
        helm upgrade --install myapp ./helm/myapp \
          --namespace myapp \
          --values helm/myapp/values-staging.yaml \
          --set image.tag=${{ github.sha }} \
          --wait --timeout 5m \
          --atomic   # rollback on failure

  deploy-prod:
    needs: deploy-staging    # wait for staging to succeed
    runs-on: ubuntu-latest
    environment: production  # requires manual approval in GitHub
    steps:
    - name: Configure kubeconfig for prod
      run: |
        aws eks update-kubeconfig \
          --name prod-cluster \
          --region us-east-1 \
          --role-arn arn:aws:iam::123:role/prod-deploy
    - name: Helm upgrade prod
      run: |
        helm upgrade --install myapp ./helm/myapp \
          --namespace myapp \
          --values helm/myapp/values-prod.yaml \
          --set image.tag=${{ github.sha }} \
          --wait --timeout 5m \
          --atomic

Helm — Create, Install, Upgrade

Create a Chart

helm create myapp
# Creates:
# myapp/
#   Chart.yaml         ← name, version, appVersion
#   values.yaml        ← default values
#   templates/
#     deployment.yaml
#     service.yaml
#     ingress.yaml
#     _helpers.tpl     ← named templates ({{ include "myapp.name" . }})

Install a Chart

helm install <release-name> <chart> [flags]

helm install myapp ./myapp \
  --namespace myapp \
  --create-namespace \
  --values values-prod.yaml \
  --set image.tag=v1.2.3 \
  --wait            # wait until all pods are Running
  --timeout 5m

Upgrade a Chart

helm upgrade myapp ./myapp \
  --namespace myapp \
  --values values-prod.yaml \
  --set image.tag=v1.3.0 \
  --wait \
  --atomic          # rollback automatically if upgrade fails

Single Command: Upgrade if Exists, Install if Not

helm upgrade --install myapp ./myapp \
  --namespace myapp \
  --create-namespace \
  --values values-prod.yaml \
  --set image.tag=$IMAGE_TAG \
  --wait \
  --atomic
# --install flag: if release doesn't exist → install. If it does → upgrade.
# This is the standard CI/CD command — idempotent.

Check if Release Exists

# Check status
helm status myapp -n myapp
# If release doesn't exist: Error: release: not found

# List all releases in namespace
helm list -n myapp

# History of a release
helm history myapp -n myapp

# Rollback to previous version
helm rollback myapp -n myapp         # previous
helm rollback myapp 2 -n myapp       # specific revision

SDE-1 vs SDE-2 Classification

SDE-1 Level (expected to know cold)

Topic Why SDE-1
ECS Fargate basics — Task Definition, Service, networking Standard AWS container deployment
HPA basics — CPU-based scaling, kubectl get hpa Fundamental K8s operations
Scheduler Filter/Score at concept level Core K8s knowledge
Route53 → ALB → pod request flow Required for any AWS service
ALB vs NLB — L7 vs L4 Standard infra interview question
kubectl logs, describe, get events debugging Daily operational skill
Docker layers and why layer order matters Every Dockerfile you write
Docker image optimization (multi-stage, distroless) Production requirement
Helm install/upgrade/upgrade--install Standard deploy tooling
Namespaces and cgroups conceptually Container fundamentals
Terraform remote state (S3 + DynamoDB) Standard team Terraform setup
NAT Gateway vs Internet Gateway AWS networking basics
Round-robin load balancing Fundamental networking

SDE-2 Level (deeper knowledge expected)

Topic Why SDE-2
HPA stabilizationWindow, behavior.scaleDown/Up policies Tuning production autoscaling
VPA update modes, Recommender/Updater/Admission Controller architecture Right-sizing at scale
VPA + singleton interaction (eviction = downtime) Production incident prevention
HPA + VPA conflict and safe combinations System design decision
Scheduler: every Filter/Score plugin by name and function Platform engineering
Gang scheduling (Volcano) for distributed training AI/ML infra
containerd-shim role (why containers survive containerd restart) Container runtime internals
CRI gRPC interface (kubelet → containerd, no dockerd) K8s internals
ALB target group deregistration delay and connection draining Zero-downtime deploys
Terraform DynamoDB locking — race conditions and how lock prevents them IaC at team scale
ArgoCD ApplicationSet multi-cluster management Platform GitOps
Sequential vs parallel CD tradeoffs System design
helm upgrade --atomic auto-rollback mechanism Production reliability