Skip to content

CI/CD Pipelines

🎯 Learning Objectives

  • Master CI/CD concepts and principles
  • Design effective and efficient pipelines
  • Learn advanced pipeline patterns
  • Understand deployment strategies in depth
  • Know pipeline optimization and best practices

CI/CD pipelines are the backbone of modern DevOps. This comprehensive chapter covers everything from basic concepts to advanced pipeline design patterns used in production.

Interview Focus

Be ready to design complete pipelines, explain each stage, discuss trade-offs, and optimize for speed and reliability.

CI/CD Fundamentals

What is CI/CD?

Continuous Integration (CI): - Automatically test code on every commit - Detect integration issues early - Provide fast feedback to developers - Ensure code quality before merging

Continuous Delivery: - Automatically deploy to staging/test environments - Manual approval for production - Always ready to deploy - Low-risk releases

Continuous Deployment: - Automatically deploy to production - No manual intervention - Requires comprehensive testing - Highest automation level

CI vs CD

  • CI: Focus on integration and testing
  • CD (Delivery): Deploy to staging automatically, production manually
  • CD (Deployment): Deploy to production automatically

Benefits of CI/CD

  1. Faster Time to Market: Automated processes reduce deployment time
  2. Higher Quality: Automated testing catches bugs early
  3. Reduced Risk: Small, frequent deployments are safer
  4. Better Collaboration: Shared responsibility between Dev and Ops
  5. Faster Feedback: Developers know immediately if code works
  6. Rollback Capability: Easy to revert problematic deployments

Pipeline Architecture

Typical Pipeline Stages

Source → Build → Test → Deploy → Monitor
  ↓       ↓       ↓       ↓        ↓
 Git    Compile  Unit   Staging  Health
Commit   Code    Tests   Env     Checks

Detailed Stage Breakdown

1. Source Stage: - Trigger on code commit - Checkout source code - Validate commit message - Check branch protection rules

2. Build Stage: - Install dependencies - Compile/build application - Create artifacts (Docker images, binaries) - Store artifacts in registry

3. Test Stage: - Unit tests - Integration tests - Code coverage - Static code analysis - Security scanning

4. Deploy Stage: - Deploy to environments (dev → staging → production) - Database migrations - Configuration updates - Health checks

5. Monitor Stage: - Monitor deployment - Check application health - Verify metrics - Alert on issues

Advanced Pipeline Patterns

Parallel Execution

Run independent stages in parallel for faster pipelines:

# GitHub Actions Example
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm run lint

  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm test

  integration-tests:
    runs-on: ubuntu-latest
    needs: [lint, unit-tests]  # Wait for these
    steps:
      - uses: actions/checkout@v3
      - run: npm run test:integration

Benefits: - Faster pipeline execution - Better resource utilization - Independent test failures don't block others

Pipeline as Code

Define pipelines in version control:

GitHub Actions (.github/workflows/ci.yml):

name: CI Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm install
      - run: npm test
      - run: npm run build

GitLab CI (.gitlab-ci.yml):

stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/

test:
  stage: test
  script:
    - npm test
  coverage: '/Coverage: \d+\.\d+%/'

deploy:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main

Multi-Environment Pipelines

Deploy to multiple environments with approval gates:

stages:
  - build
  - test
  - deploy-dev
  - deploy-staging
  - deploy-production

deploy-dev:
  stage: deploy-dev
  script:
    - ./deploy.sh dev
  only:
    - develop

deploy-staging:
  stage: deploy-staging
  script:
    - ./deploy.sh staging
  only:
    - main
  when: manual  # Requires manual approval

deploy-production:
  stage: deploy-production
  script:
    - ./deploy.sh production
  only:
    - main
  when: manual
  environment:
    name: production
    url: https://app.example.com

Pipeline Optimization

Caching Strategies

Dependency Caching:

# GitHub Actions
- name: Cache dependencies
  uses: actions/cache@v3
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

# GitLab CI
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .cache/

Docker Layer Caching:

- name: Build Docker image
  uses: docker/build-push-action@v4
  with:
    context: .
    push: true
    cache-from: type=registry,ref=myapp:buildcache
    cache-to: type=registry,ref=myapp:buildcache,mode=max

Fast Feedback Loops

Run Quick Tests First:

jobs:
  quick-checks:
    runs-on: ubuntu-latest
    steps:
      - name: Lint
        run: npm run lint  # Fast, fails early
      - name: Type check
        run: npm run type-check  # Fast

  full-tests:
    runs-on: ubuntu-latest
    needs: quick-checks  # Only if quick checks pass
    steps:
      - name: Unit tests
        run: npm test
      - name: Integration tests
        run: npm run test:integration  # Slower

Fail Fast Strategy: - Run fastest tests first - Stop pipeline on first failure - Parallelize independent tests - Cache aggressively

Conditional Execution

Skip stages based on conditions:

# Only run on main branch
deploy:
  script: ./deploy.sh
  only:
    - main

# Skip for documentation changes
test:
  script: npm test
  except:
    changes:
      - "docs/**/*"
      - "*.md"

# Run only for specific paths
backend-tests:
  script: npm run test:backend
  only:
    changes:
      - "backend/**/*"

Deployment Strategies in CI/CD

Blue-Green Deployment Pipeline

stages:
  - build
  - deploy-blue
  - test-blue
  - deploy-green
  - test-green
  - switch-traffic
  - cleanup-blue

deploy-blue:
  stage: deploy-blue
  script:
    - docker build -t app:blue .
    - kubectl set image deployment/app app=app:blue

deploy-green:
  stage: deploy-green
  script:
    - docker build -t app:green .
    - kubectl set image deployment/app-green app=app:green

switch-traffic:
  stage: switch-traffic
  script:
    - kubectl patch service app -p '{"spec":{"selector":{"version":"green"}}}'
  when: manual

Canary Deployment Pipeline

deploy-canary:
  script:
    - kubectl set image deployment/app-canary app=app:new-version
    - kubectl scale deployment/app-canary --replicas=1

monitor-canary:
  script:
    - ./monitor-metrics.sh canary
  when: manual

promote-canary:
  script:
    - kubectl scale deployment/app-canary --replicas=5
    - kubectl scale deployment/app-stable --replicas=0
  when: manual

Security in CI/CD

Secrets Management

GitHub Actions Secrets:

- name: Deploy
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  run: ./deploy.sh

GitLab CI Variables:

deploy:
  script:
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin
  variables:
    DOCKER_DRIVER: overlay2

Best Practices: - Never commit secrets to repository - Use secret management tools (Vault, AWS Secrets Manager) - Rotate secrets regularly - Use least privilege principle - Audit secret access

Security Scanning

SAST (Static Application Security Testing):

- name: Run SAST
  uses: github/super-linter@v4
  env:
    DEFAULT_BRANCH: main
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Security scan
  run: |
    npm audit
    bandit -r .  # Python security scanner

Container Scanning:

- name: Scan Docker image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:latest
    format: 'sarif'
    output: 'trivy-results.sarif'

Dependency Scanning:

- name: Dependency check
  run: |
    npm audit
    pip-audit  # Python
    bundler-audit  # Ruby

Comprehensive Interview Questions

Q1: Design a complete CI/CD pipeline for a microservices application

Answer:

Pipeline Design:

stages:
  - validate
  - build
  - test
  - security-scan
  - package
  - deploy-dev
  - integration-test
  - deploy-staging
  - e2e-test
  - deploy-production

validate:
  stage: validate
  script:
    - npm run lint
    - npm run type-check
    - git-secrets scan

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  parallel:
    - script: npm run test:unit
    - script: npm run test:integration
  coverage: '/Coverage: \d+\.\d+%/'

security-scan:
  stage: security-scan
  script:
    - trivy image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - npm audit

package:
  stage: package
  script:
    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest

deploy-dev:
  stage: deploy-dev
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n dev
  only:
    - develop

deploy-production:
  stage: deploy-production
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n production
  only:
    - main
  when: manual

Q2: How do you handle database migrations in CI/CD?

Answer:

Strategy 1: Migration Job Before Deployment

migrate-database:
  stage: pre-deploy
  script:
    - kubectl run migration-$CI_COMMIT_SHA --image=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -- migrate
    - kubectl wait --for=condition=complete job/migration-$CI_COMMIT_SHA
  only:
    - main

deploy:
  needs: ["migrate-database"]
  script: ./deploy.sh

Strategy 2: Init Container

# Deployment includes init container
initContainers:
- name: migrate
  image: app:latest
  command: ["alembic", "upgrade", "head"]
containers:
- name: app
  image: app:latest

Best Practices: - Migrations must be backward compatible - Test migrations in staging first - Have rollback scripts ready - Use feature flags for schema changes

Q3: Explain the difference between Continuous Delivery and Continuous Deployment

Answer:

Continuous Delivery: - Code is always deployable - Automated deployment to staging - Manual approval for production - Lower risk, more control

Continuous Deployment: - Automated deployment to production - No manual intervention - Requires comprehensive testing - Higher automation, faster releases

When to use: - Delivery: When you need control over production releases - Deployment: When you have excellent test coverage and monitoring

Q4: How do you optimize pipeline execution time?

Answer:

  1. Parallel Execution:
  2. Run independent jobs in parallel
  3. Use matrix builds for multiple versions

  4. Caching:

  5. Cache dependencies
  6. Cache Docker layers
  7. Cache build artifacts

  8. Conditional Execution:

  9. Skip stages when not needed
  10. Run only changed tests

  11. Fast Feedback:

  12. Run quick tests first
  13. Fail fast on errors

  14. Resource Optimization:

  15. Use appropriate runner sizes
  16. Optimize Docker builds

Q5: How do you implement rollback in CI/CD pipelines?

Answer:

Automatic Rollback:

deploy:
  script:
    - kubectl set image deployment/app app=app:$VERSION
    - kubectl rollout status deployment/app --timeout=5m
    - if [ $? -ne 0 ]; then
        kubectl rollout undo deployment/app;
        exit 1;
      fi

Manual Rollback:

rollback:
  script:
    - kubectl rollout undo deployment/app
  when: manual

Blue-Green Rollback: - Switch service selector back to previous version - Instant rollback capability

Books

  • "Continuous Delivery" by Jez Humble, David Farley - CI/CD bible
  • "The DevOps Handbook" by Gene Kim - DevOps practices including CI/CD

Articles

Research Papers

  • "Continuous Integration" - Martin Fowler's foundational article
  • "The Deployment Production Line" - Deployment automation patterns

Previous: Container Orchestration | Next: GitHub Actions & GitLab CI