Chapter 9 CI/CD Integration and Automation

作者
27min

Chapter 9 CI/CD Integration and Automation

Learning Objectives

  1. Understand Docker’s role in CI/CD workflows
  2. Master Docker-based continuous integration configuration
  3. Learn to implement automated image building and pushing
  4. Implement automated deployment of containerized applications
  5. Master multi-environment deployment strategies

Knowledge Point Details

9.1 CI/CD Fundamentals

9.1.1 CI/CD Workflow Overview

Traditional CI/CD Workflow:

🔄 正在渲染 Mermaid 图表...

Dockerized CI/CD Workflow:

🔄 正在渲染 Mermaid 图表...

9.1.2 Advantages of Docker in CI/CD

Environment Consistency:

# Development environment
docker run -p 3000:3000 myapp:dev

# Test environment
docker run -p 3000:3000 myapp:test

# Production environment
docker run -p 3000:3000 myapp:prod

Fast Deployment:

# Blue-green deployment
docker service update --image myapp:v2.0 web-service

# Rolling update
docker service update --update-parallelism 2 --update-delay 10s myapp

9.2 Jenkins Integration

9.2.1 Jenkins Environment Setup

Running Jenkins with Docker:

# Create Jenkins data volume
docker volume create jenkins_home

# Run Jenkins container
docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /usr/bin/docker:/usr/bin/docker \
  jenkins/jenkins:lts

Jenkins Configuration File:

# docker-compose.yml
version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - JAVA_OPTS=-Dhudson.plugins.git.GitSCM.ALLOW_LOCAL_CHECKOUT=true
    networks:
      - jenkins-network

  jenkins-agent:
    image: jenkins/inbound-agent:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
    networks:
      - jenkins-network

volumes:
  jenkins_home:

networks:
  jenkins-network:
    driver: bridge

9.2.2 Jenkins Pipeline Configuration

Jenkinsfile Example:

pipeline {
    agent any

    environment {
        DOCKER_REGISTRY = 'your-registry.com'
        IMAGE_NAME = 'myapp'
        IMAGE_TAG = "${BUILD_NUMBER}"
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-repo/myapp.git'
            }
        }

        stage('Build Docker Image') {
            steps {
                script {
                    docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
                    docker.build("${IMAGE_NAME}:latest")
                }
            }
        }

        stage('Test') {
            steps {
                script {
                    // Run test container
                    sh """
                        docker run --rm \
                        -v \$(pwd):/app \
                        ${IMAGE_NAME}:${IMAGE_TAG} \
                        npm test
                    """
                }
            }
        }

        stage('Security Scan') {
            steps {
                script {
                    // Use Trivy to scan image
                    sh """
                        docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
                        aquasec/trivy:latest image ${IMAGE_NAME}:${IMAGE_TAG}
                    """
                }
            }
        }

        stage('Push to Registry') {
            steps {
                script {
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
                        docker.image("${IMAGE_NAME}:${IMAGE_TAG}").push()
                        docker.image("${IMAGE_NAME}:latest").push()
                    }
                }
            }
        }

        stage('Deploy to Staging') {
            steps {
                script {
                    // Deploy to test environment
                    sh """
                        docker service update \
                        --image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
                        staging_web
                    """
                }
            }
        }

        stage('Integration Tests') {
            steps {
                script {
                    // Run integration tests
                    sh """
                        docker run --rm \
                        --network staging_network \
                        cypress/included:latest \
                        --spec "cypress/integration/**/*"
                    """
                }
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            steps {
                input 'Deploy to production?'
                script {
                    // Production environment deployment
                    sh """
                        docker service update \
                        --image ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \
                        --update-parallelism 2 \
                        --update-delay 30s \
                        production_web
                    """
                }
            }
        }
    }

    post {
        always {
            // Clean up build images
            sh "docker image prune -f"
        }
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
        }
    }
}

Multi-branch Build Strategy:

pipeline {
    agent any

    stages {
        stage('Build & Test') {
            steps {
                script {
                    def imageTag = env.BRANCH_NAME == 'main' ? 'latest' : env.BRANCH_NAME

                    // Build image
                    def image = docker.build("myapp:${imageTag}")

                    // Run tests
                    image.inside {
                        sh 'npm test'
                    }

                    // Decide whether to push based on branch
                    if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME.startsWith('release/')) {
                        docker.withRegistry('https://your-registry.com', 'registry-credentials') {
                            image.push()
                        }
                    }
                }
            }
        }
    }
}

9.3 GitLab CI Integration

9.3.1 GitLab CI Configuration

.gitlab-ci.yml Basic Configuration:

# .gitlab-ci.yml
stages:
  - build
  - test
  - security
  - deploy-staging
  - deploy-production

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  LATEST_TAG: $CI_REGISTRY_IMAGE:latest

services:
  - docker:dind

before_script:
  - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

build:
  stage: build
  image: docker:latest
  script:
    - docker build -t $IMAGE_TAG .
    - docker build -t $LATEST_TAG .
    - docker push $IMAGE_TAG
    - docker push $LATEST_TAG
  only:
    - main
    - develop
    - merge_requests

test:
  stage: test
  image: docker:latest
  script:
    - docker pull $IMAGE_TAG
    - docker run --rm $IMAGE_TAG npm test
    - docker run --rm $IMAGE_TAG npm run lint
  coverage: '/Coverage: \d+\.\d+/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

security_scan:
  stage: security
  image: docker:latest
  script:
    - docker pull $IMAGE_TAG
    - docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
      aquasec/trivy:latest image --format table $IMAGE_TAG
  allow_failure: true

deploy_staging:
  stage: deploy-staging
  image: docker:latest
  script:
    - echo "Deploying to staging..."
    - docker service update --image $IMAGE_TAG staging_web
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

deploy_production:
  stage: deploy-production
  image: docker:latest
  script:
    - echo "Deploying to production..."
    - docker service update --image $IMAGE_TAG production_web
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

9.3.2 Multi-Environment Deployment

Environment-Specific Configuration:

# .gitlab-ci.yml
.deploy_template: &deploy_template
  image: docker:latest
  script:
    - docker-compose -f docker-compose.$ENVIRONMENT.yml up -d
    - docker system prune -f

deploy_dev:
  <<: *deploy_template
  stage: deploy
  variables:
    ENVIRONMENT: development
  environment:
    name: development
  only:
    - develop

deploy_staging:
  <<: *deploy_template
  stage: deploy
  variables:
    ENVIRONMENT: staging
  environment:
    name: staging
  only:
    - main

deploy_production:
  <<: *deploy_template
  stage: deploy
  variables:
    ENVIRONMENT: production
  environment:
    name: production
  when: manual
  only:
    - main

Environment Configuration Files:

# docker-compose.development.yml
version: '3.8'
services:
  web:
    image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
      - DEBUG=true

# docker-compose.production.yml
version: '3.8'
services:
  web:
    image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    ports:
      - "80:3000"
    environment:
      - NODE_ENV=production
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 256M
        reservations:
          memory: 128M

9.4 GitHub Actions Integration

9.4.1 Basic Workflow

.github/workflows/ci-cd.yml:

name: CI/CD Pipeline

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

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Log in to Container Registry
      uses: docker/login-action@v2
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_branch}}

    - name: Build and push Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

    - name: Run tests
      run: |
        docker run --rm ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} npm test

  security-scan:
    runs-on: ubuntu-latest
    needs: build-and-test

    steps:
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
        format: 'sarif'
        output: 'trivy-results.sarif'

    - name: Upload Trivy scan results
      uses: github/codeql-action/upload-sarif@v2
      with:
        sarif_file: 'trivy-results.sarif'

  deploy-staging:
    runs-on: ubuntu-latest
    needs: [build-and-test, security-scan]
    if: github.ref == 'refs/heads/develop'
    environment: staging

    steps:
    - name: Deploy to staging
      uses: appleboy/ssh-action@v0.1.7
      with:
        host: ${{ secrets.STAGING_HOST }}
        username: ${{ secrets.STAGING_USER }}
        key: ${{ secrets.STAGING_SSH_KEY }}
        script: |
          docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          docker service update --image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} staging_web

  deploy-production:
    runs-on: ubuntu-latest
    needs: [build-and-test, security-scan]
    if: github.ref == 'refs/heads/main'
    environment: production

    steps:
    - name: Deploy to production
      uses: appleboy/ssh-action@v0.1.7
      with:
        host: ${{ secrets.PRODUCTION_HOST }}
        username: ${{ secrets.PRODUCTION_USER }}
        key: ${{ secrets.PRODUCTION_SSH_KEY }}
        script: |
          docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          docker service update \
            --image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            --update-parallelism 2 \
            --update-delay 30s \
            production_web

9.4.2 Matrix Build Strategy

Multi-version Build:

name: Multi-version Build

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
        platform: [linux/amd64, linux/arm64]

    steps:
    - uses: actions/checkout@v3

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v2

    - name: Build Docker image
      uses: docker/build-push-action@v4
      with:
        context: .
        platforms: ${{ matrix.platform }}
        build-args: |
          NODE_VERSION=${{ matrix.node-version }}
        tags: myapp:node${{ matrix.node-version }}-${{ matrix.platform }}
        push: false

  test:
    runs-on: ubuntu-latest
    needs: build
    strategy:
      matrix:
        test-suite: [unit, integration, e2e]

    steps:
    - uses: actions/checkout@v3

    - name: Run ${{ matrix.test-suite }} tests
      run: |
        docker run --rm myapp:latest npm run test:${{ matrix.test-suite }}

9.5 Automated Deployment Strategies

9.5.1 Blue-Green Deployment

Blue-Green Deployment Script:

#!/bin/bash
# blue-green-deploy.sh

IMAGE_TAG=$1
SERVICE_NAME="web-service"
NETWORK_NAME="app-network"

# Get current active deployment
CURRENT_COLOR=$(docker service inspect --format '{{.Spec.Labels.color}}' ${SERVICE_NAME} 2>/dev/null || echo "blue")

# Determine new color
if [ "$CURRENT_COLOR" = "blue" ]; then
    NEW_COLOR="green"
else
    NEW_COLOR="blue"
fi

echo "Current deployment: $CURRENT_COLOR"
echo "New deployment: $NEW_COLOR"

# Deploy new version
docker service create \
    --name ${SERVICE_NAME}-${NEW_COLOR} \
    --network $NETWORK_NAME \
    --label color=$NEW_COLOR \
    --replicas 3 \
    $IMAGE_TAG

# Wait for new service to be ready
echo "Waiting for new service to be ready..."
while [ $(docker service ps ${SERVICE_NAME}-${NEW_COLOR} --filter "desired-state=running" --format "{{.CurrentState}}" | grep -c "Running") -ne 3 ]; do
    sleep 5
done

# Health check
echo "Performing health check..."
if curl -f http://localhost:8080/health; then
    echo "Health check passed. Switching traffic..."

    # Update load balancer configuration
    docker service update \
        --label-add color=$NEW_COLOR \
        nginx-lb

    # Remove old service
    if docker service ls --filter name=${SERVICE_NAME}-${CURRENT_COLOR} --format "{{.Name}}" | grep -q ${SERVICE_NAME}-${CURRENT_COLOR}; then
        docker service rm ${SERVICE_NAME}-${CURRENT_COLOR}
    fi

    # Rename new service
    docker service update --name ${SERVICE_NAME} ${SERVICE_NAME}-${NEW_COLOR}

    echo "Deployment completed successfully!"
else
    echo "Health check failed. Rolling back..."
    docker service rm ${SERVICE_NAME}-${NEW_COLOR}
    exit 1
fi

9.5.2 Rolling Update

Kubernetes Rolling Update:

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web
        image: myapp:latest
        ports:
        - containerPort: 3000
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

Docker Swarm Rolling Update:

# Configure rolling update parameters
docker service update \
    --image myapp:v2.0 \
    --update-parallelism 2 \
    --update-delay 30s \
    --update-failure-action rollback \
    --update-monitor 60s \
    web-service

9.5.3 Canary Release

Canary Release Configuration:

#!/bin/bash
# canary-deploy.sh

NEW_IMAGE=$1
CANARY_PERCENTAGE=${2:-10}

# Calculate canary instance count
TOTAL_REPLICAS=$(docker service inspect --format='{{.Spec.Replicas}}' production-web)
CANARY_REPLICAS=$((TOTAL_REPLICAS * CANARY_PERCENTAGE / 100))

echo "Deploying canary with $CANARY_REPLICAS replicas (${CANARY_PERCENTAGE}%)"

# Create canary service
docker service create \
    --name production-web-canary \
    --network production-network \
    --label version=canary \
    --replicas $CANARY_REPLICAS \
    $NEW_IMAGE

# Monitor canary metrics
echo "Monitoring canary deployment..."
sleep 300

# Check error rate
ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])" | jq '.data.result[0].value[1]' | tr -d '"')

if (( $(echo "$ERROR_RATE < 0.01" | bc -l) )); then
    echo "Canary deployment successful. Promoting to full deployment..."

    # Update production service
    docker service update --image $NEW_IMAGE production-web

    # Remove canary service
    docker service rm production-web-canary

    echo "Full deployment completed!"
else
    echo "Canary deployment failed. Rolling back..."
    docker service rm production-web-canary
    exit 1
fi

9.6 Monitoring and Rollback

9.6.1 Deployment Monitoring

Prometheus Monitoring Configuration:

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'docker'
    static_configs:
      - targets: ['localhost:9323']

  - job_name: 'app'
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        port: 3000
    relabel_configs:
      - source_labels: [__meta_docker_container_label_monitoring]
        target_label: __tmp_should_be_scraped
        regex: true
      - source_labels: [__tmp_should_be_scraped]
        regex: true
        target_label: __address__
        replacement: ${1}:3000

rule_files:
  - "deployment_rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

Deployment Alert Rules:

# deployment_rules.yml
groups:
- name: deployment
  rules:
  - alert: DeploymentFailed
    expr: up{job="app"} == 0
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Application deployment failed"
      description: "Application {{ $labels.instance }} is down for more than 2 minutes"

  - alert: HighErrorRate
    expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.05
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High error rate detected"
      description: "Error rate is {{ $value }} for instance {{ $labels.instance }}"

  - alert: HighLatency
    expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High latency detected"
      description: "95th percentile latency is {{ $value }}s for instance {{ $labels.instance }}"

9.6.2 Automatic Rollback

Health Check and Rollback Script:

#!/bin/bash
# auto-rollback.sh

SERVICE_NAME=$1
NEW_IMAGE=$2
HEALTH_ENDPOINT=$3
MAX_WAIT=300
WAIT_INTERVAL=10

echo "Updating service $SERVICE_NAME to $NEW_IMAGE"

# Get current image as rollback version
PREVIOUS_IMAGE=$(docker service inspect --format='{{.Spec.TaskTemplate.ContainerSpec.Image}}' $SERVICE_NAME)

# Perform update
docker service update --image $NEW_IMAGE $SERVICE_NAME

# Wait for service update to complete
echo "Waiting for service update to complete..."
ELAPSED=0
while [ $ELAPSED -lt $MAX_WAIT ]; do
    REPLICAS_UPDATED=$(docker service ps $SERVICE_NAME --filter "desired-state=running" --format "{{.Image}}" | grep -c $NEW_IMAGE)
    TOTAL_REPLICAS=$(docker service inspect --format='{{.Spec.Replicas}}' $SERVICE_NAME)

    if [ $REPLICAS_UPDATED -eq $TOTAL_REPLICAS ]; then
        echo "All replicas updated successfully"
        break
    fi

    sleep $WAIT_INTERVAL
    ELAPSED=$((ELAPSED + WAIT_INTERVAL))
done

# Health check
echo "Performing health check..."
sleep 30  # Wait for application startup

HEALTH_CHECK_ATTEMPTS=0
MAX_HEALTH_ATTEMPTS=10

while [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_ATTEMPTS ]; do
    if curl -f -s $HEALTH_ENDPOINT > /dev/null; then
        echo "Health check passed"

        # Check error rate
        ERROR_COUNT=$(curl -s "http://prometheus:9090/api/v1/query?query=increase(http_requests_total{status=~'5..'}[5m])" | jq '.data.result[0].value[1]' | tr -d '"' | cut -d. -f1)

        if [ ${ERROR_COUNT:-0} -lt 10 ]; then
            echo "Deployment successful!"
            exit 0
        else
            echo "High error rate detected, initiating rollback..."
            break
        fi
    else
        echo "Health check failed, attempt $((HEALTH_CHECK_ATTEMPTS + 1))"
        HEALTH_CHECK_ATTEMPTS=$((HEALTH_CHECK_ATTEMPTS + 1))
        sleep 30
    fi
done

# Perform rollback
echo "Rolling back to previous version: $PREVIOUS_IMAGE"
docker service update --image $PREVIOUS_IMAGE $SERVICE_NAME

# Wait for rollback to complete
echo "Waiting for rollback to complete..."
sleep 60

# Verify rollback
if curl -f -s $HEALTH_ENDPOINT > /dev/null; then
    echo "Rollback completed successfully"
    exit 1
else
    echo "Rollback failed, manual intervention required"
    exit 2
fi

Practical Case Study

Case 1: Microservices CI/CD Pipeline

Scenario Description: Build a complete CI/CD pipeline for a microservices architecture including frontend, API gateway, user service, and order service.

Project Structure:

microservices-app/
├── frontend/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── api-gateway/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── user-service/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── order-service/
│   ├── Dockerfile
│   ├── package.json
│   └── src/
├── docker-compose.yml
├── .gitlab-ci.yml
└── deploy/
    ├── staging.yml
    └── production.yml

GitLab CI Configuration:

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy-staging
  - integration-test
  - deploy-production

variables:
  DOCKER_DRIVER: overlay2
  SERVICES: "frontend api-gateway user-service order-service"

.build_template: &build_template
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - cd $SERVICE_DIR
    - docker build -t $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA
  only:
    changes:
      - "$SERVICE_DIR/**/*"

build-frontend:
  <<: *build_template
  variables:
    SERVICE_NAME: frontend
    SERVICE_DIR: frontend

build-api-gateway:
  <<: *build_template
  variables:
    SERVICE_NAME: api-gateway
    SERVICE_DIR: api-gateway

build-user-service:
  <<: *build_template
  variables:
    SERVICE_NAME: user-service
    SERVICE_DIR: user-service

build-order-service:
  <<: *build_template
  variables:
    SERVICE_NAME: order-service
    SERVICE_DIR: order-service

.test_template: &test_template
  stage: test
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker run --rm $CI_REGISTRY_IMAGE/$SERVICE_NAME:$CI_COMMIT_SHA npm test

test-frontend:
  <<: *test_template
  variables:
    SERVICE_NAME: frontend
  needs: ["build-frontend"]

test-api-gateway:
  <<: *test_template
  variables:
    SERVICE_NAME: api-gateway
  needs: ["build-api-gateway"]

test-user-service:
  <<: *test_template
  variables:
    SERVICE_NAME: user-service
  needs: ["build-user-service"]

test-order-service:
  <<: *test_template
  variables:
    SERVICE_NAME: order-service
  needs: ["build-order-service"]

deploy-staging:
  stage: deploy-staging
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker-compose -f deploy/staging.yml up -d
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - develop

integration-tests:
  stage: integration-test
  image: postman/newman:alpine
  script:
    - newman run tests/integration/postman_collection.json --environment tests/integration/staging.json
  needs: ["deploy-staging"]

deploy-production:
  stage: deploy-production
  image: docker:latest
  services:
    - docker:dind
  script:
    - ./scripts/blue-green-deploy.sh
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

Summary

Docker plays a core role in CI/CD workflows, achieving the following through containerization technology:

  1. Environment Consistency: Development, testing, and production environments are completely consistent
  2. Fast Deployment: Containers start quickly with high deployment efficiency
  3. Easy to Scale: Supports horizontal scaling and load balancing
  4. Version Management: Version control through image tags
  5. Rollback Capability: Quick rollback to previous versions

Best Practices Summary:

  • Use multi-stage builds to optimize image size
  • Implement image security scanning and vulnerability detection
  • Adopt semantic versioning management strategy
  • Implement automated testing and deployment
  • Establish comprehensive monitoring and alerting mechanisms
  • Develop emergency response and rollback strategies

Through proper CI/CD workflow design, development efficiency can be significantly improved, deployment risks reduced, and application quality and stability ensured.