六、CI/CD流水线建设
6.1 完整的CI Pipeline
# .github/workflows/ci.yml - 完整的CI流水线
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
PYTHON_VERSION: '3.11'
POETRY_VERSION: '1.5.0'
jobs:
lint:
name: Lint & Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${
{ env.PYTHON_VERSION }}
- name: Install Poetry
run: pip install poetry==${
{ env.POETRY_VERSION }}
- name: Install dependencies
run: poetry install --with dev
- name: Run black check
run: poetry run black --check src/
- name: Run isort check
run: poetry run isort --check-only src/
- name: Run ruff lint
run: poetry run ruff check src/
- name: Run mypy type check
run: poetry run mypy src/
test-unit:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${
{ env.PYTHON_VERSION }}
- name: Install Poetry
run: pip install poetry==${
{ env.POETRY_VERSION }}
- name: Install dependencies
run: poetry install
- name: Run unit tests
run: |
poetry run pytest tests/unit -v --cov=src --cov-report=xml --cov-report=term
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
test-integration:
name: Integration Tests
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpass
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${
{ env.PYTHON_VERSION }}
- name: Install Poetry
run: pip install poetry==${
{ env.POETRY_VERSION }}
- name: Install dependencies
run: poetry install
- name: Run integration tests
env:
DATABASE_URL: postgresql://postgres:testpass@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
run: |
poetry run pytest tests/integration -v --cov=src --cov-append
build:
name: Build & Package
runs-on: ubuntu-latest
needs: [test-unit, test-integration]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${
{ env.PYTHON_VERSION }}
- name: Install Poetry
run: pip install poetry==${
{ env.POETRY_VERSION }}
- name: Build package
run: poetry build
- name: Build Docker image
run: |
docker build -t myapp:${
{ github.sha }} .
docker tag myapp:${
{ github.sha }} myapp:latest
- name: Save Docker image
run: docker save myapp:${
{ github.sha }} -o myapp.tar
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: |
dist/
myapp.tar
Dockerfile
security-scan:
name: Security Scan
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts
- name: Load Docker image
run: docker load -i myapp.tar
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${
{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
6.2 CD流水线
# .github/workflows/cd.yml - CD流水线
name: CD Pipeline
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
jobs:
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event.inputs.environment == 'staging'
environment: staging
steps:
- uses: actions/checkout@v3
- name: Configure kubectl
uses: azure/setup-kubectl@v3
with:
version: 'latest'
- name: Set up Kubernetes config
run: |
mkdir -p $HOME/.kube
echo "${
{ secrets.KUBE_CONFIG_STAGING }}" | base64 --decode > $HOME/.kube/config
- name: Deploy to Kubernetes
run: |
# 更新镜像tag
kubectl set image deployment/myapp myapp=myapp:${
{ github.sha }}
# 滚动更新
kubectl rollout status deployment/myapp
- name: Run smoke tests
run: |
curl --retry 5 --retry-delay 10 --fail https://staging.myapp.com/health
- name: Notify deployment
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "✅ Deployment to STAGING completed successfully",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "✅ Deployment to *STAGING* completed\nVersion: `${
{ github.sha }}`\nEnvironment: staging"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${
{ secrets.SLACK_WEBHOOK_URL }}
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: deploy-staging
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production')
environment: production
steps:
- uses: actions/checkout@v3
- name: Manual approval gate
uses: trstringer/manual-approval@v1
with:
secret: ${
{ github.TOKEN }}
approvers: admin,lead-engineer
minimum-approvals: 2
issue-title: "Deploy to Production"
- name: Blue-Green deployment
run: |
# 蓝绿部署脚本
./scripts/blue-green-deploy.sh
- name: Verify deployment
run: |
# 验证健康检查
./scripts/verify-deployment.sh
- name: Rollback on failure
if: failure()
run: |
./scripts/rollback.sh
- name: Create deployment tag
run: |
git tag "deploy-$(date +%Y%m%d-%H%M%S)"
git push --tags
- name: Notify team
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "🚀 Deployment to PRODUCTION completed successfully",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚀 Deployment to *PRODUCTION* completed\nVersion: `${
{ github.sha }}`\nDeployed at: `$(date)`"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${
{ secrets.SLACK_WEBHOOK_URL }}
6.3 部署脚本示例
#!/bin/bash
# scripts/blue-green-deploy.sh - 蓝绿部署脚本
set -euo pipefail
# 配置
NAMESPACE="production"
APP_NAME="myapp"
NEW_VERSION="${GITHUB_SHA:-$(git rev-parse HEAD)}"
SERVICE_NAME="myapp-service"
INGRESS_NAME="myapp-ingress"
# 颜色
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
echo "🚀 Starting Blue-Green deployment"
# 检测当前激活的环境
CURRENT_ACTIVE=$(kubectl get service $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.selector.environment}')
echo "Current active environment: $CURRENT_ACTIVE"
if [ "$CURRENT_ACTIVE" = "blue" ]; then
NEW_ENV="green"
OLD_ENV="blue"
else
NEW_ENV="blue"
OLD_ENV="green"
fi
echo "Deploying to $NEW_ENV environment"
# 1. 部署新版本
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: $APP_NAME-$NEW_ENV
namespace: $NAMESPACE
spec:
replicas: 3
selector:
matchLabels:
app: $APP_NAME
environment: $NEW_ENV
template:
metadata:
labels:
app: $APP_NAME
environment: $NEW_ENV
spec:
containers:
- name: app
image: myapp:$NEW_VERSION
ports:
- containerPort: 8000
env:
- name: ENVIRONMENT
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
EOF
# 2. 等待新版本就绪
echo "Waiting for $NEW_ENV deployment to be ready..."
kubectl rollout status deployment/$APP_NAME-$NEW_ENV -n $NAMESPACE --timeout=300s
# 3. 健康检查
echo "Performing health check..."
POD_NAME=$(kubectl get pods -n $NAMESPACE -l app=$APP_NAME,environment=$NEW_ENV -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n $NAMESPACE $POD_NAME -- curl -s http://localhost:8000/health
if [ $? -ne 0 ]; then
echo -e "${RED}❌ Health check failed! Aborting deployment${NC}"
exit 1
fi
# 4. 切换到新环境
echo "Switching traffic to $NEW_ENV..."
kubectl patch service $SERVICE_NAME -n $NAMESPACE -p "{\"spec\":{\"selector\":{\"environment\":\"$NEW_ENV\"}}}"
# 5. 验证流量切换
echo "Verifying traffic switch..."
sleep 10
# 6. 如果切换成功,清理旧环境
echo "Cleaning up old environment ($OLD_ENV)..."
kubectl delete deployment $APP_NAME-$OLD_ENV -n $NAMESPACE --ignore-not-found
echo -e "${GREEN}✅ Blue-Green deployment completed successfully!${NC}"
echo "New version deployed to $NEW_ENV environment"