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/