GitHub Actions自动化部署实战:CI/CD流水线搭建与蓝绿发布策略

CI/CD流水线核心概念

CI/CD(持续集成/持续部署)是将代码从提交到生产环境自动化的工程实践。手动部署容易出错、耗时且不可重复——开发者在本地构建通过,推到服务器就跑不起来,这类问题在团队协作中反复出现。CI/CD流水线通过标准化构建、测试、部署流程,让每次代码变更都经过相同的验证和发布流程。

GitHub Actions是GitHub原生提供的CI/CD平台,与代码仓库深度集成,无需额外搭建Jenkins服务器。Workflow定义文件以YAML格式存放在 .github/workflows/ 目录,跟随代码版本管理。以下实战方案覆盖从代码提交到Docker容器化部署的完整链路。

GitHub Actions Workflow配置

一个完整的CI/CD Workflow分为三个阶段:代码检查与单元测试、Docker镜像构建与推送、自动化部署。配置文件 .github/workflows/deploy.yml

name: CI/CD Pipeline

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

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

jobs:
  # 阶段1:代码检查与测试
  test:
    runs-on: ubuntu-latest
    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: Lint check
        run: npm run lint

      - name: Type check
        run: npm run typecheck

      - name: Unit tests
        run: npm test -- --coverage

      - name: Upload coverage
        if: github.event_name == 'push'
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  # 阶段2:Docker镜像构建与推送
  build:
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4

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

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

      - name: Build and push Docker image
        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
          build-args: |
            BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
            VCS_REF=${{ github.sha }}

  # 阶段3:自动化部署
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: deploy
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            # 拉取最新镜像
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            
            # 滚动更新容器
            docker-compose -f /opt/app/docker-compose.yml up -d --no-deps app
            
            # 健康检查
            for i in $(seq 1 30); do
              if curl -sf http://localhost:8000/health; then
                echo "Deploy succeeded"
                exit 0
              fi
              sleep 2
            done
            echo "Health check failed, rolling back"
            docker-compose -f /opt/app/docker-compose.yml down
            exit 1

Dockerfile优化与多阶段构建

Docker镜像大小直接影响构建速度和部署效率。多阶段构建(Multi-stage Build)将编译环境和运行环境分离,最终镜像只包含运行时必需的文件:

# 阶段1:依赖安装
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

# 阶段2:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
RUN npm run build && \
    npm prune --production

# 阶段3:运行时
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 appgroup && \
    adduser --system --uid 1001 appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
USER appuser
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD wget -qO- http://localhost:8000/health || exit 1
CMD ["node", "dist/server.js"]

优化要点:利用缓存层——package.json变更频率远低于源码,将依赖安装放在COPY . .之前,源码变更时不会重新安装依赖;精简镜像——alpine基础镜像约5MB,完整版约1GB;非root运行——容器内以普通用户运行应用,即使容器被攻破也无法访问宿主机root权限。

Docker Compose服务编排

生产环境的Docker部署通常涉及多个服务容器。docker-compose.yml定义应用、数据库、反向代理的完整栈:

version: "3.9"
services:
  app:
    image: ghcr.io/${IMAGE_NAME}:${TAG:-latest}
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://app:${DB_PASSWORD}@db:5432/appdb
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8000/health"]
      interval: 15s
      timeout: 5s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1024M

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=appdb
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app

volumes:
  pgdata:

部署回滚与蓝绿发布策略

自动化部署必须考虑失败回滚。蓝绿发布维护两套环境,切换流量实现零停机部署:

#!/bin/bash
# blue-green-deploy.sh
COMPOSE_FILE="/opt/app/docker-compose.yml"
CURRENT=$(docker-compose -f $COMPOSE_FILE ps --services --filter "status=running" | grep -o 'app_[bg]' | head -1)

if [ "$CURRENT" = "app_b" ]; then
    NEXT="app_g"
else
    NEXT="app_b"
fi

echo "Current: $CURRENT, Deploying: $NEXT"

# 拉取新镜像并启动NEXT版本
docker-compose -f $COMPOSE_FILE up -d --no-deps --scale ${NEXT}=1 ${NEXT}
sleep 5

# 健康检查NEXT版本
if curl -sf http://localhost:8001/health; then
    echo "Health check passed, switching traffic"
    # 更新Nginx upstream指向NEXT版本
    sed -i "s/server $CURRENT/server $NEXT/" /opt/app/nginx.conf
    nginx -s reload
    # 停止旧版本
    docker-compose -f $COMPOSE_FILE stop $CURRENT
    echo "Deploy completed: $NEXT is live"
else
    echo "Health check failed, keeping $CURRENT"
    docker-compose -f $COMPOSE_FILE stop $NEXT
    exit 1
fi

关键设计:两套容器并行运行,通过Nginx upstream切换流量;先启动再切换,旧版本在新版本健康检查通过前一直在线;即时回滚——如果新版本健康检查失败,只需重新reload Nginx指向旧版本,秒级恢复。

流水线安全与密钥管理

CI/CD流水线拥有代码仓库到生产部署的完整权限,其安全性直接决定生产环境安全:

GitHub Secrets管理:所有敏感信息(SSH私钥、数据库密码、API Token)必须存入GitHub Secrets,Workflow通过 ${{ secrets.XXX }} 引用,日志中自动脱敏。不要在代码仓库中存储任何密钥,包括.env文件。

最小权限原则:部署用的SSH密钥只授权特定命令——在目标服务器的sshd_config或authorized_keys中强制命令:command="/opt/scripts/deploy.sh" ssh-ed25519 AAAA...",即使密钥泄露,攻击者也无法获得完整shell。

环境保护规则:GitHub的Environment功能支持审批流程——生产环境部署需要指定审批人确认后才能执行。在Workflow中声明 environment: production,GitHub会等待审批通过后再运行deploy job。

镜像签名与验证:使用cosign对Docker镜像签名,部署时验证签名确保镜像未被篡改:cosign verify --key cosign.pub $IMAGE:$TAG。将验证步骤加入部署脚本,签名校验失败则拒绝部署。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/githubactions-zi-dong-hua-bu-shu-shi-zhan-cicd-liu-shui/

(0)
小编小编
上一篇 1天前
下一篇 1天前

相关推荐