Docker自动化部署进阶:搭建GitHub Actions CI/CD流水线实现零停机发布

Docker自动化部署已成为DevOps实践的基础设施。手动SSH登录服务器拉取镜像、重启容器的操作方式存在发布不一致和人员依赖风险。本文搭建一条完整的GitHub Actions CI/CD流水线,覆盖代码提交、镜像构建、自动化部署、健康检查全流程,并通过蓝绿部署实现零停机发布

CI/CD流水线整体架构

流水线分为三个阶段:

  • CI阶段:代码lint → 单元测试 → 安全扫描 → Docker镜像构建 → 推送至镜像仓库
  • CD阶段:SSH连接目标服务器 → 拉取新镜像 → 蓝绿切换 → 健康检查
  • 回滚阶段:健康检查失败时自动回滚到上一版本

Dockerfile设计:分层缓存与安全基线

# Dockerfile - 多阶段构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
# 创建非root用户
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/main.js"]

关键设计点:构建层与运行层分离,减小最终镜像体积;运行时使用非root用户,符合服务器安全加固要求;内置HEALTHCHECK指令供Docker自动监控容器状态。

GitHub Actions工作流配置

在仓库根目录创建.github/workflows/deploy.yml

name: Build and Deploy

on:
  push:
    branches: [ main ]
  workflow_dispatch: # 支持手动触发

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

jobs:
  # ===== CI阶段 =====
  build-and-test:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run lint
        run: npm run lint

      - name: Run unit tests
        run: npm test -- --coverage

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

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # 构建缓存加速
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ===== CD阶段 =====
  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script_stop: true
          script: |
            set -euo pipefail
            
            IMAGE_TAG="${{ needs.build-and-test.outputs.image_tag }}"
            IMAGE_FULL="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}"
            
            echo "Deploying image: ${IMAGE_FULL}"
            
            # 登录镜像仓库
            echo ${{ secrets.REGISTRY_TOKEN }} | docker login ghcr.io -u ${{ secrets.REGISTRY_USER }} --password-stdin
            
            # 拉取新镜像
            docker pull ${IMAGE_FULL}
            
            # 启动绿色容器(新版本)
            docker run -d \
              --name app-green \
              --network app-net \
              -p 3001:3000 \
              --env-file /opt/app/.env \
              --restart unless-stopped \
              ${IMAGE_FULL}
            
            # 等待绿色容器健康检查通过
            echo "Waiting for green container health check..."
            for i in $(seq 1 30); do
              STATUS=$(docker inspect --format='{{.State.Health.Status}}' app-green 2>/dev/null)
              if [ "$STATUS" = "healthy" ]; then
                echo "Green container is healthy"
                break
              fi
              if [ $i -eq 30 ]; then
                echo "Health check timeout, rolling back"
                docker stop app-green && docker rm app-green
                exit 1
              fi
              sleep 2
            done
            
            # 更新Nginx上游指向绿色容器
            sudo sed -i 's/server 127.0.0.1:3000;/server 127.0.0.1:3001;/' /etc/nginx/conf.d/app-upstream.conf
            sudo nginx -t && sudo nginx -s reload
            
            # 停止并移除蓝色容器(旧版本)
            docker stop app-blue 2>/dev/null || true
            docker rm app-blue 2>/dev/null || true
            
            # 将绿色容器重命名为蓝色,端口切回3000
            docker stop app-green
            docker rm app-green
            docker run -d \
              --name app-blue \
              --network app-net \
              -p 3000:3000 \
              --env-file /opt/app/.env \
              --restart unless-stopped \
              ${IMAGE_FULL}
            
            # Nginx上游切回蓝色
            sudo sed -i 's/server 127.0.0.1:3001;/server 127.0.0.1:3000;/' /etc/nginx/conf.d/app-upstream.conf
            sudo nginx -t && sudo nginx -s reload
            
            # 清理悬空镜像
            docker image prune -f
            
            echo "Deployment completed successfully"

Nginx负载均衡配置

蓝绿部署依赖Nginx上游配置的快速切换。配置文件/etc/nginx/conf.d/app-upstream.conf

upstream app_backend {
    # 部署脚本会动态修改此行
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name app.example.com;
    
    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }
    
    location /health {
        access_log off;
        return 200 "ok\n";
        add_header Content-Type text/plain;
    }
}

监控告警体系集成

CI/CD流水线发布后,监控告警体系需要感知版本变更。在部署步骤末尾添加Prometheus指标推送:

# 在部署脚本末尾添加版本标记
# 使用Prometheus Pushgateway记录部署事件
DEPLOY_TIME=$(date +%s)
echo "app_deploy_info{version="${IMAGE_TAG}",status="success"} 1" | \
    curl --data-binary @- http://prometheus-pushgateway:9091/metrics/job/app-deploy

# 也可以通过Webhook通知告警系统
curl -X POST "${ALERT_WEBHOOK}" \
  -H "Content-Type: application/json" \
  -d "{"event":"deploy","version":"${IMAGE_TAG}","time":${DEPLOY_TIME},"status":"success"}"

在告警规则中配置发布后5分钟的异常检测窗口:

# Prometheus告警规则
# 部署后5分钟内错误率突增则告警
- alert: PostDeployErrorRateSpike
  expr: |
    rate(http_requests_total{status=~"5.."}[2m]) > 0.05
    and on() 
    (time() - app_deploy_info{status="success"} < 300)
  for: 1m
  labels:
    severity: critical
  annotations:
    summary: "部署后错误率异常"
    description: "最新部署版本5xx错误率超过5%"

故障应急响应与自动回滚

蓝绿部署的价值在于快速回滚能力。将回滚逻辑封装为独立脚本/opt/app/rollback.sh

#!/bin/bash
set -euo pipefail

# 回滚到上一个稳定镜像
LAST_STABLE=$(cat /opt/app/.last_stable_version 2>/dev/null)
if [ -z "$LAST_STABLE" ]; then
    echo "No previous version found, cannot rollback"
    exit 1
fi

echo "Rolling back to: ${LAST_STABLE}"

# 拉取旧镜像并启动
docker pull ${LAST_STABLE}
docker stop app-blue 2>/dev/null || true
docker rm app-blue 2>/dev/null || true
docker run -d \
    --name app-blue \
    --network app-net \
    -p 3000:3000 \
    --env-file /opt/app/.env \
    --restart unless-stopped \
    ${LAST_STABLE}

# 验证
sleep 10
HEALTH=$(curl -s http://127.0.0.1:3000/health)
if [ "$HEALTH" = "ok" ]; then
    echo "Rollback successful"
else
    echo "Rollback FAILED - manual intervention required"
    exit 1
fi

CI/CD流水线的混沌工程验证:定期在预发布环境注入故障,测试回滚机制是否可靠。Kubernetes容器编排环境下上述方案可简化为kubectl rollout undo,但Docker Compose或裸Docker环境下需要手动实现等效逻辑。

.last_stable_version文件更新步骤加入部署脚本:每次健康检查通过后,将当前镜像tag写入该文件。故障应急响应时执行rollback.sh即可在30秒内恢复服务。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/docker-zi-dong-hua-bu-shu-jin-jie-da-jian-githubactionscicd/

(0)
小编小编
上一篇 15小时前
下一篇 15小时前

相关推荐

Docker自动化部署进阶:搭建GitHub Actions CI/CD流水线实现零停机发布

Docker自动化部署已成为DevOps实践的基础设施。手动SSH登录服务器拉取镜像、重启容器的操作方式存在发布不一致和人员依赖风险。本文搭建一条完整的GitHub Actions CI/CD流水线,覆盖代码提交、镜像构建、自动化部署、健康检查全流程,并通过蓝绿部署实现零停机发布

CI/CD流水线整体架构

流水线分为三个阶段:

  • CI阶段:代码lint → 单元测试 → 安全扫描 → Docker镜像构建 → 推送至镜像仓库
  • CD阶段:SSH连接目标服务器 → 拉取新镜像 → 蓝绿切换 → 健康检查
  • 回滚阶段:健康检查失败时自动回滚到上一版本

Dockerfile设计:分层缓存与安全基线

# Dockerfile - 多阶段构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
# 创建非root用户
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/main.js"]

关键设计点:构建层与运行层分离,减小最终镜像体积;运行时使用非root用户,符合服务器安全加固要求;内置HEALTHCHECK指令供Docker自动监控容器状态。

GitHub Actions工作流配置

在仓库根目录创建.github/workflows/deploy.yml

name: Build and Deploy

on:
  push:
    branches: [ main ]
  workflow_dispatch: # 支持手动触发

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

jobs:
  # ===== CI阶段 =====
  build-and-test:
    runs-on: ubuntu-latest
    outputs:
      image_tag: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run lint
        run: npm run lint

      - name: Run unit tests
        run: npm test -- --coverage

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

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # 构建缓存加速
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ===== CD阶段 =====
  deploy:
    needs: build-and-test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script_stop: true
          script: |
            set -euo pipefail
            
            IMAGE_TAG="${{ needs.build-and-test.outputs.image_tag }}"
            IMAGE_FULL="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}"
            
            echo "Deploying image: ${IMAGE_FULL}"
            
            # 登录镜像仓库
            echo ${{ secrets.REGISTRY_TOKEN }} | docker login ghcr.io -u ${{ secrets.REGISTRY_USER }} --password-stdin
            
            # 拉取新镜像
            docker pull ${IMAGE_FULL}
            
            # 启动绿色容器(新版本)
            docker run -d \
              --name app-green \
              --network app-net \
              -p 3001:3000 \
              --env-file /opt/app/.env \
              --restart unless-stopped \
              ${IMAGE_FULL}
            
            # 等待绿色容器健康检查通过
            echo "Waiting for green container health check..."
            for i in $(seq 1 30); do
              STATUS=$(docker inspect --format='{{.State.Health.Status}}' app-green 2>/dev/null)
              if [ "$STATUS" = "healthy" ]; then
                echo "Green container is healthy"
                break
              fi
              if [ $i -eq 30 ]; then
                echo "Health check timeout, rolling back"
                docker stop app-green && docker rm app-green
                exit 1
              fi
              sleep 2
            done
            
            # 更新Nginx上游指向绿色容器
            sudo sed -i 's/server 127.0.0.1:3000;/server 127.0.0.1:3001;/' /etc/nginx/conf.d/app-upstream.conf
            sudo nginx -t && sudo nginx -s reload
            
            # 停止并移除蓝色容器(旧版本)
            docker stop app-blue 2>/dev/null || true
            docker rm app-blue 2>/dev/null || true
            
            # 将绿色容器重命名为蓝色,端口切回3000
            docker stop app-green
            docker rm app-green
            docker run -d \
              --name app-blue \
              --network app-net \
              -p 3000:3000 \
              --env-file /opt/app/.env \
              --restart unless-stopped \
              ${IMAGE_FULL}
            
            # Nginx上游切回蓝色
            sudo sed -i 's/server 127.0.0.1:3001;/server 127.0.0.1:3000;/' /etc/nginx/conf.d/app-upstream.conf
            sudo nginx -t && sudo nginx -s reload
            
            # 清理悬空镜像
            docker image prune -f
            
            echo "Deployment completed successfully"

Nginx负载均衡配置

蓝绿部署依赖Nginx上游配置的快速切换。配置文件/etc/nginx/conf.d/app-upstream.conf

upstream app_backend {
    # 部署脚本会动态修改此行
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name app.example.com;
    
    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_connect_timeout 5s;
        proxy_read_timeout 30s;
    }
    
    location /health {
        access_log off;
        return 200 "ok\n";
        add_header Content-Type text/plain;
    }
}

监控告警体系集成

CI/CD流水线发布后,监控告警体系需要感知版本变更。在部署步骤末尾添加Prometheus指标推送:

# 在部署脚本末尾添加版本标记
# 使用Prometheus Pushgateway记录部署事件
DEPLOY_TIME=$(date +%s)
echo "app_deploy_info{version="${IMAGE_TAG}",status="success"} 1" | \
    curl --data-binary @- http://prometheus-pushgateway:9091/metrics/job/app-deploy

# 也可以通过Webhook通知告警系统
curl -X POST "${ALERT_WEBHOOK}" \
  -H "Content-Type: application/json" \
  -d "{"event":"deploy","version":"${IMAGE_TAG}","time":${DEPLOY_TIME},"status":"success"}"

在告警规则中配置发布后5分钟的异常检测窗口:

# Prometheus告警规则
# 部署后5分钟内错误率突增则告警
- alert: PostDeployErrorRateSpike
  expr: |
    rate(http_requests_total{status=~"5.."}[2m]) > 0.05
    and on() 
    (time() - app_deploy_info{status="success"} < 300)
  for: 1m
  labels:
    severity: critical
  annotations:
    summary: "部署后错误率异常"
    description: "最新部署版本5xx错误率超过5%"

故障应急响应与自动回滚

蓝绿部署的价值在于快速回滚能力。将回滚逻辑封装为独立脚本/opt/app/rollback.sh

#!/bin/bash
set -euo pipefail

# 回滚到上一个稳定镜像
LAST_STABLE=$(cat /opt/app/.last_stable_version 2>/dev/null)
if [ -z "$LAST_STABLE" ]; then
    echo "No previous version found, cannot rollback"
    exit 1
fi

echo "Rolling back to: ${LAST_STABLE}"

# 拉取旧镜像并启动
docker pull ${LAST_STABLE}
docker stop app-blue 2>/dev/null || true
docker rm app-blue 2>/dev/null || true
docker run -d \
    --name app-blue \
    --network app-net \
    -p 3000:3000 \
    --env-file /opt/app/.env \
    --restart unless-stopped \
    ${LAST_STABLE}

# 验证
sleep 10
HEALTH=$(curl -s http://127.0.0.1:3000/health)
if [ "$HEALTH" = "ok" ]; then
    echo "Rollback successful"
else
    echo "Rollback FAILED - manual intervention required"
    exit 1
fi

CI/CD流水线的混沌工程验证:定期在预发布环境注入故障,测试回滚机制是否可靠。Kubernetes容器编排环境下上述方案可简化为kubectl rollout undo,但Docker Compose或裸Docker环境下需要手动实现等效逻辑。

.last_stable_version文件更新步骤加入部署脚本:每次健康检查通过后,将当前镜像tag写入该文件。故障应急响应时执行rollback.sh即可在30秒内恢复服务。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/docker-zi-dong-hua-bu-shu-jin-jie-da-jian-githubactionscicd/

(0)
小编小编
上一篇 15小时前
下一篇 15小时前

相关推荐