Appearance
自动化部署 - 从代码到生产的完整流程
1. 自动化部署概述
1.1 什么是自动化部署
自动化部署是指使用自动化工具和脚本,将应用程序从开发环境自动发布到生产环境的过程。它是持续交付(CD)的核心组成部分。
核心价值:
- 提高效率:消除手动部署的重复劳动
- 减少错误:避免人为失误导致的部署问题
- 快速交付:加速功能从开发到上线的速度
- 可回滚性:支持快速回滚到之前的版本
- 标准化:确保每次部署流程一致
1.2 部署策略
蓝绿部署(Blue-Green Deployment)
生产环境(蓝) ← 流量
|
|-- 切换流量 -->
|
测试环境(绿) ← 新版本优势:
- 零停机时间
- 快速回滚
- 完整的测试环境
劣势:
- 需要双倍资源
- 数据同步复杂
滚动部署(Rolling Deployment)
实例1 → 更新 → 完成
实例2 → 等待 → 更新 → 完成
实例3 → 等待 → 等待 → 更新 → 完成优势:
- 资源利用高效
- 逐步更新,风险可控
劣势:
- 部署时间较长
- 回滚较复杂
金丝雀部署(Canary Deployment)
新版本 → 10% 流量 → 监控 → 50% 流量 → 100% 流量
旧版本 ← 90% 流量 ← 监控 ← 50% 流量 ← 0% 流量优势:
- 风险最小化
- 渐进式验证
- 实时监控
劣势:
- 配置复杂
- 需要流量管理
1.3 部署流程
mermaid
graph LR
A[代码提交] --> B[CI 构建]
B --> C[运行测试]
C --> D{测试通过?}
D -->|是| E[构建产物]
D -->|否| F[通知失败]
E --> G[部署到暂存]
G --> H[冒烟测试]
H --> I{测试通过?}
I -->|是| J[部署到生产]
I -->|否| F
J --> K[健康检查]
K --> L{检查通过?}
L -->|是| M[完成]
L -->|否| N[回滚]2. GitHub Actions 自动化部署
2.1 基础部署配置
部署到生产环境
yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches:
- main
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'production'
type: choice
options:
- staging
- production
env:
NODE_VERSION: 18
jobs:
build:
name: Build Application
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build application
run: npm run build
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ github.sha }}
path: dist/
retention-days: 7
deploy:
name: Deploy to ${{ github.event.inputs.environment || 'production' }}
runs-on: ubuntu-latest
needs: build
environment:
name: ${{ github.event.inputs.environment || 'production' }}
url: ${{ steps.deploy.outputs.url }}
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build-${{ github.sha }}
path: dist/
- name: Deploy application
id: deploy
run: |
# 部署逻辑
echo "url=https://myapp.com" >> $GITHUB_OUTPUT2.2 多环境部署
yaml
# .github/workflows/deploy-multi-env.yml
name: Multi-Environment Deployment
on:
push:
branches:
- develop
- staging
- main
jobs:
determine-environment:
name: Determine Environment
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.env.outputs.environment }}
url: ${{ steps.env.outputs.url }}
steps:
- name: Set environment based on branch
id: env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=production" >> $GITHUB_OUTPUT
echo "url=https://app.example.com" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
echo "environment=staging" >> $GITHUB_OUTPUT
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
else
echo "environment=development" >> $GITHUB_OUTPUT
echo "url=https://dev.example.com" >> $GITHUB_OUTPUT
fi
build:
name: Build
runs-on: ubuntu-latest
needs: determine-environment
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm test
- name: Build for ${{ needs.determine-environment.outputs.environment }}
run: npm run build
env:
VITE_API_URL: ${{ secrets[format('{0}_API_URL', needs.determine-environment.outputs.environment)] }}
VITE_ENV: ${{ needs.determine-environment.outputs.environment }}
- uses: actions/upload-artifact@v3
with:
name: build
path: dist/
deploy:
name: Deploy to ${{ needs.determine-environment.outputs.environment }}
runs-on: ubuntu-latest
needs: [determine-environment, build]
environment:
name: ${{ needs.determine-environment.outputs.environment }}
url: ${{ needs.determine-environment.outputs.url }}
steps:
- uses: actions/download-artifact@v3
with:
name: build
path: dist/
- name: Deploy
run: |
echo "Deploying to ${{ needs.determine-environment.outputs.environment }}"
# 部署命令2.3 带审批的部署流程
yaml
# .github/workflows/deploy-with-approval.yml
name: Deploy with Approval
on:
push:
branches:
- main
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v3
with:
name: build
path: dist/
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/download-artifact@v3
with:
name: build
- name: Deploy to staging
run: |
# 部署到 staging
echo "Deployed to staging"
smoke-test:
name: Smoke Test
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- uses: actions/checkout@v4
- name: Run smoke tests
run: |
npm ci
npm run test:smoke -- --env=staging
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: smoke-test
environment:
name: production
url: https://example.com
steps:
- uses: actions/download-artifact@v3
with:
name: build
- name: Deploy to production
run: |
# 部署到生产环境
echo "Deployed to production"
- name: Create deployment
uses: actions/github-script@v7
with:
script: |
github.rest.repos.createDeployment({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.sha,
environment: 'production',
auto_merge: false
})3. 平台专属部署
3.1 Vercel 部署
使用 Vercel CLI
yaml
# .github/workflows/vercel-deploy.yml
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install Vercel CLI
run: npm install --global vercel@latest
- name: Pull Vercel Environment
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
- name: Deploy to Vercel
id: deploy
run: |
url=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})
echo "url=$url" >> $GITHUB_OUTPUT
- name: Comment PR with preview URL
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `✅ Preview deployed to: ${{ steps.deploy.outputs.url }}`
})使用 Vercel Action
yaml
# .github/workflows/vercel-action.yml
name: Vercel Deploy Action
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: ${{ github.event_name == 'push' && '--prod' || '' }}3.2 Netlify 部署
yaml
# .github/workflows/netlify-deploy.yml
name: Deploy to Netlify
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run build
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: './dist'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: 'Deploy from GitHub Actions'
enable-pull-request-comment: true
enable-commit-comment: true
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
timeout-minutes: 103.3 AWS S3 + CloudFront 部署
yaml
# .github/workflows/aws-deploy.yml
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npm run build
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync to S3
run: |
aws s3 sync dist/ s3://${{ secrets.AWS_S3_BUCKET }} \
--delete \
--cache-control "public, max-age=31536000, immutable" \
--exclude "index.html" \
--exclude "service-worker.js"
- name: Upload index.html with no-cache
run: |
aws s3 cp dist/index.html s3://${{ secrets.AWS_S3_BUCKET }}/ \
--cache-control "public, max-age=0, must-revalidate"
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
- name: Wait for invalidation
run: |
invalidation_id=$(aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*" \
--query 'Invalidation.Id' \
--output text)
aws cloudfront wait invalidation-completed \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--id $invalidation_id3.4 Azure Static Web Apps
yaml
# .github/workflows/azure-deploy.yml
name: Deploy to Azure
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened, closed]
branches: [main]
jobs:
build_and_deploy:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Build And Deploy
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
action: "upload"
app_location: "/"
api_location: ""
output_location: "dist"
close_pull_request:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Close Pull Request
uses: Azure/static-web-apps-deploy@v1
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
action: "close"4. Docker 容器化部署
4.1 构建 Docker 镜像
Dockerfile
dockerfile
# Dockerfile
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产镜像
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Nginx 配置
nginx
# nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /static {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}4.2 GitHub Actions Docker 部署
yaml
# .github/workflows/docker-deploy.yml
name: Docker Build and Deploy
on:
push:
branches: [main]
tags: ['v*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in 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=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
--restart unless-stopped \
-p 80:80 \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main4.3 Docker Compose 部署
yaml
# docker-compose.yml
version: '3.8'
services:
app:
image: ghcr.io/myorg/myapp:latest
restart: unless-stopped
ports:
- "80:80"
environment:
- NODE_ENV=production
networks:
- app-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
networks:
- app-network
networks:
app-network:
driver: bridgeyaml
# .github/workflows/docker-compose-deploy.yml
name: Docker Compose Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Copy files to server
uses: appleboy/scp-action@v0.1.4
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "docker-compose.yml,nginx.conf"
target: "/opt/myapp"
- name: Deploy with Docker Compose
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker-compose ps5. Kubernetes 部署
5.1 Kubernetes 配置
Deployment
yaml
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: ghcr.io/myorg/myapp:latest
ports:
- containerPort: 80
env:
- name: NODE_ENV
value: "production"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5Service
yaml
# k8s/service.yml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancerIngress
yaml
# k8s/ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 805.2 GitHub Actions Kubernetes 部署
yaml
# .github/workflows/k8s-deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Update deployment image
run: |
kubectl set image deployment/myapp \
myapp=ghcr.io/${{ github.repository }}:${{ github.sha }} \
--record
- name: Wait for rollout
run: |
kubectl rollout status deployment/myapp
- name: Verify deployment
run: |
kubectl get pods -l app=myapp
kubectl get services myapp-service5.3 使用 Helm
Helm Chart
yaml
# helm/Chart.yaml
apiVersion: v2
name: myapp
description: My React Application
type: application
version: 1.0.0
appVersion: "1.0.0"yaml
# helm/values.yaml
replicaCount: 3
image:
repository: ghcr.io/myorg/myapp
tag: latest
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80yaml
# .github/workflows/helm-deploy.yml
name: Helm Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: '3.12.0'
- uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Deploy with Helm
run: |
helm upgrade --install myapp ./helm \
--set image.tag=${{ github.sha }} \
--namespace production \
--create-namespace \
--wait \
--timeout 5m6. 零停机部署
6.1 健康检查配置
typescript
// src/health.ts
import express from 'express';
const app = express();
// 存活探针
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// 就绪探针
app.get('/ready', async (req, res) => {
try {
// 检查数据库连接
await database.ping();
// 检查关键服务
await Promise.all([
checkRedis(),
checkExternalAPI()
]);
res.status(200).json({ status: 'ready' });
} catch (error) {
res.status(503).json({ status: 'not ready', error });
}
});
export default app;6.2 优雅关闭
typescript
// src/server.ts
import express from 'express';
import http from 'http';
const app = express();
const server = http.createServer(app);
// 优雅关闭处理
const gracefulShutdown = async () => {
console.log('Received shutdown signal');
// 停止接受新连接
server.close(() => {
console.log('HTTP server closed');
});
// 等待现有请求完成
await new Promise(resolve => {
setTimeout(resolve, 10000); // 10秒超时
});
// 关闭数据库连接
await database.close();
// 关闭其他资源
await redis.quit();
console.log('Graceful shutdown completed');
process.exit(0);
};
// 监听终止信号
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
server.listen(3000, () => {
console.log('Server running on port 3000');
});6.3 滚动更新策略
yaml
# k8s/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 最多增加1个Pod
maxUnavailable: 0 # 保证始终可用
minReadySeconds: 30 # Pod就绪后等待30秒
progressDeadlineSeconds: 600 # 10分钟超时
template:
spec:
containers:
- name: myapp
image: myapp:latest
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
terminationGracePeriodSeconds: 307. 回滚机制
7.1 自动回滚配置
yaml
# .github/workflows/deploy-with-rollback.yml
name: Deploy with Auto Rollback
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy new version
id: deploy
run: |
# 部署新版本
./deploy.sh
echo "deployment_id=$(date +%s)" >> $GITHUB_OUTPUT
- name: Wait for deployment
run: sleep 60
- name: Health check
id: health
run: |
for i in {1..5}; do
if curl -f https://myapp.com/health; then
echo "healthy=true" >> $GITHUB_OUTPUT
exit 0
fi
sleep 10
done
echo "healthy=false" >> $GITHUB_OUTPUT
exit 1
continue-on-error: true
- name: Rollback on failure
if: steps.health.outputs.healthy != 'true'
run: |
echo "Health check failed, rolling back..."
./rollback.sh ${{ steps.deploy.outputs.deployment_id }}
exit 17.2 Kubernetes 回滚
bash
# 查看部署历史
kubectl rollout history deployment/myapp
# 回滚到上一个版本
kubectl rollout undo deployment/myapp
# 回滚到指定版本
kubectl rollout undo deployment/myapp --to-revision=3
# 检查回滚状态
kubectl rollout status deployment/myappyaml
# .github/workflows/k8s-rollback.yml
name: Kubernetes Rollback
on:
workflow_dispatch:
inputs:
revision:
description: 'Revision to rollback to (empty for previous)'
required: false
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Rollback deployment
run: |
if [ -n "${{ github.event.inputs.revision }}" ]; then
kubectl rollout undo deployment/myapp --to-revision=${{ github.event.inputs.revision }}
else
kubectl rollout undo deployment/myapp
fi
- name: Wait for rollback
run: |
kubectl rollout status deployment/myapp --timeout=5m
- name: Verify rollback
run: |
kubectl get pods -l app=myapp
kubectl describe deployment myapp8. 监控与告警
8.1 部署监控
yaml
# .github/workflows/deploy-with-monitoring.yml
name: Deploy with Monitoring
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy
id: deploy
run: ./deploy.sh
- name: Monitor deployment
run: |
# 发送部署开始事件到监控系统
curl -X POST https://monitoring.example.com/events \
-H "Content-Type: application/json" \
-d '{
"type": "deployment_started",
"app": "myapp",
"version": "${{ github.sha }}",
"timestamp": "'$(date -Iseconds)'"
}'
- name: Check metrics
run: |
sleep 120 # 等待2分钟收集指标
# 检查错误率
error_rate=$(curl -s https://monitoring.example.com/api/metrics?app=myapp&metric=error_rate)
if (( $(echo "$error_rate > 0.05" | bc -l) )); then
echo "Error rate too high: $error_rate"
exit 1
fi
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "❌ Deployment failed for myapp",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Deployment Failed*\nApp: myapp\nCommit: ${{ github.sha }}\nBranch: ${{ github.ref_name }}"
}
}
]
}8.2 告警集成
yaml
# .github/workflows/alert.yml
name: Deployment Alert
on:
workflow_run:
workflows: ["Deploy to Production"]
types:
- completed
jobs:
alert:
runs-on: ubuntu-latest
steps:
- name: Deployment Success
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: |
curl -X POST ${{ secrets.WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '{
"status": "success",
"message": "Deployment completed successfully",
"commit": "${{ github.sha }}",
"url": "${{ github.event.workflow_run.html_url }}"
}'
- name: Deployment Failure
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
run: |
curl -X POST ${{ secrets.WEBHOOK_URL }} \
-H 'Content-Type: application/json' \
-d '{
"status": "failure",
"message": "Deployment failed",
"commit": "${{ github.sha }}",
"url": "${{ github.event.workflow_run.html_url }}"
}'9. 最佳实践
9.1 部署清单
部署前检查:
- [ ] 代码已通过所有测试
- [ ] 代码已通过 code review
- [ ] 依赖版本已锁定
- [ ] 环境变量已配置
- [ ] 数据库迁移已准备
- [ ] 回滚计划已制定
部署中监控:
- [ ] 实时监控错误率
- [ ] 检查响应时间
- [ ] 观察资源使用
- [ ] 验证日志输出
部署后验证:
- [ ] 健康检查通过
- [ ] 冒烟测试通过
- [ ] 关键功能验证
- [ ] 性能指标正常
9.2 环境变量管理
yaml
# .github/workflows/env-management.yml
name: Environment Management
on:
workflow_dispatch:
inputs:
environment:
type: choice
options:
- development
- staging
- production
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Create .env file
run: |
cat > .env << EOF
NODE_ENV=${{ github.event.inputs.environment }}
API_URL=${{ secrets.API_URL }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
REDIS_URL=${{ secrets.REDIS_URL }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
EOF
- name: Build with environment
run: |
npm ci
npm run build9.3 版本标记
yaml
# .github/workflows/versioning.yml
name: Version and Deploy
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get version
id: version
run: |
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Build with version
run: |
npm ci
npm run build
env:
VITE_APP_VERSION: ${{ steps.version.outputs.version }}
- name: Create release
uses: softprops/action-gh-release@v1
with:
files: dist/**
generate_release_notes: true10. 总结
自动化部署是现代软件交付的核心,通过:
- 标准化流程:确保每次部署一致可靠
- 自动化测试:在部署前发现问题
- 零停机部署:保证服务连续性
- 快速回滚:出现问题时快速恢复
- 完善监控:实时掌握部署状态
通过合理的部署策略和工具链,可以实现快速、安全、可靠的持续交付。