<返回目录 Powered by claude/xia兄
第14课: CI/CD集成
什么是CI/CD?
- CI(持续集成):自动构建、测试代码
- CD(持续交付):自动部署到测试/生产环境
- Docker的作用:提供一致的构建和运行环境
GitHub Actions + Docker
# .github/workflows/docker.yml
name: Docker CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
myuser/myapp:latest
myuser/myapp:${{ github.sha }}
cache-from: type=registry,ref=myuser/myapp:buildcache
cache-to: type=registry,ref=myuser/myapp:buildcache,mode=max
- name: Run tests
run: |
docker run --rm myuser/myapp:${{ github.sha }} npm test
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: |
ssh user@server "docker pull myuser/myapp:latest && docker-compose up -d"
GitLab CI + Docker
# .gitlab-ci.yml
stages:
- build
- test
- deploy
variables:
DOCKER_DRIVER: overlay2
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
test:
stage: test
image: $IMAGE_TAG
script:
- npm test
- npm run lint
deploy_staging:
stage: deploy
only:
- develop
script:
- ssh user@staging-server "docker pull $IMAGE_TAG && docker-compose up -d"
deploy_production:
stage: deploy
only:
- main
when: manual
script:
- ssh user@prod-server "docker pull $IMAGE_TAG && docker-compose up -d"
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
DOCKER_IMAGE = "myapp"
DOCKER_TAG = "${env.BUILD_NUMBER}"
REGISTRY = "registry.example.com"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
docker.build("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}")
}
}
}
stage('Test') {
steps {
script {
docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").inside {
sh 'npm test'
sh 'npm run lint'
}
}
}
}
stage('Push') {
steps {
script {
docker.withRegistry("https://${REGISTRY}", 'docker-credentials') {
docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").push()
docker.image("${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG}").push('latest')
}
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
sh '''
ssh user@server "
docker pull ${REGISTRY}/${DOCKER_IMAGE}:${DOCKER_TAG} &&
docker-compose up -d
"
'''
}
}
}
post {
always {
cleanWs()
}
}
}
Docker in Docker (DinD)
# 在CI环境中使用Docker
# GitLab CI示例
build:
image: docker:latest
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
- docker build -t myapp .
- docker run --rm myapp npm test
DinD注意事项:
- 需要privileged模式,有安全风险
- 推荐使用Kaniko或Buildah作为替代
- 确保正确配置DOCKER_HOST
使用Kaniko构建镜像
# 无需Docker守护进程的镜像构建
# .gitlab-ci.yml
build:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
多环境部署策略
# docker-compose.yml (基础配置)
version: '3.8'
services:
app:
image: myapp:${TAG:-latest}
environment:
- NODE_ENV=${ENV:-production}
# docker-compose.staging.yml (覆盖配置)
version: '3.8'
services:
app:
environment:
- NODE_ENV=staging
- DEBUG=true
# docker-compose.prod.yml (生产配置)
version: '3.8'
services:
app:
deploy:
replicas: 3
resources:
limits:
cpus: '2'
memory: 2G
# 部署命令
# 测试环境
docker-compose -f docker-compose.yml -f docker-compose.staging.yml up -d
# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
蓝绿部署
# 蓝绿部署脚本
#!/bin/bash
# 当前运行的是蓝色还是绿色
CURRENT=$(docker ps --filter "name=app-blue" --format "{{.Names}}" | grep -q "app-blue" && echo "blue" || echo "green")
NEW=$([ "$CURRENT" = "blue" ] && echo "green" || echo "blue")
echo "Current: $CURRENT, Deploying: $NEW"
# 启动新版本
docker-compose -f docker-compose.$NEW.yml up -d
# 健康检查
for i in {1..30}; do
if curl -f http://localhost:8080/health; then
echo "New version is healthy"
break
fi
sleep 2
done
# 切换流量(更新Nginx配置)
sed -i "s/app-$CURRENT/app-$NEW/g" /etc/nginx/conf.d/default.conf
nginx -s reload
# 停止旧版本
docker-compose -f docker-compose.$CURRENT.yml down
滚动更新
# Docker Swarm滚动更新
docker service update \
--image myapp:v2.0 \
--update-parallelism 2 \
--update-delay 10s \
myapp
# Kubernetes滚动更新
kubectl set image deployment/myapp myapp=myapp:v2.0
kubectl rollout status deployment/myapp
# 回滚
kubectl rollout undo deployment/myapp
自动化测试集成
# docker-compose.test.yml
version: '3.8'
services:
app:
build: .
environment:
- NODE_ENV=test
depends_on:
- db
- redis
db:
image: postgres:14
environment:
POSTGRES_PASSWORD: test
redis:
image: redis:alpine
test:
build: .
command: npm test
depends_on:
- app
- db
- redis
# 运行测试
docker-compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from test
镜像版本管理
# 语义化版本标签
docker build -t myapp:1.2.3 .
docker tag myapp:1.2.3 myapp:1.2
docker tag myapp:1.2.3 myapp:1
docker tag myapp:1.2.3 myapp:latest
# 使用Git提交哈希
docker build -t myapp:$(git rev-parse --short HEAD) .
# 使用构建时间
docker build -t myapp:$(date +%Y%m%d-%H%M%S) .
# 组合标签
docker build -t myapp:v1.2.3-$(git rev-parse --short HEAD) .
监控和通知
# GitHub Actions通知
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
# 部署后健康检查
- name: Health check
run: |
for i in {1..30}; do
if curl -f https://myapp.com/health; then
echo "Deployment successful"
exit 0
fi
sleep 10
done
echo "Deployment failed"
exit 1
完整CI/CD流程
# .github/workflows/complete-pipeline.yml
name: Complete CI/CD Pipeline
on:
push:
branches: [ main, develop ]
tags: [ 'v*' ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: |
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image
run: |
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myapp:${{ github.sha }}
- name: Push image
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push myapp:${{ github.sha }}
deploy-staging:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- name: Deploy to staging
run: |
ssh user@staging "docker pull myapp:${{ github.sha }} && docker-compose up -d"
deploy-production:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: |
ssh user@prod "docker pull myapp:${{ github.sha }} && docker-compose up -d"
最佳实践
- 使用多阶段构建减小镜像体积
- 为每次构建打上唯一标签
- 在CI中运行安全扫描
- 使用缓存加速构建
- 自动化测试和部署
- 实施蓝绿或滚动部署
- 配置健康检查和回滚机制
- 使用密钥管理工具存储敏感信息
实践练习
- 配置GitHub Actions自动构建Docker镜像
- 集成自动化测试到CI流程
- 实现多环境部署(开发、测试、生产)
- 配置镜像安全扫描
- 实现蓝绿部署或滚动更新
- 添加部署后的健康检查和通知