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/