CI/CD流水线集成 #
CI/CD概述 #
流程图 #
text
┌─────────────────────────────────────────────────────┐
│ CI/CD 流程 │
├─────────────────────────────────────────────────────┤
│ │
│ 代码提交 ──→ 构建 ──→ 测试 ──→ 镜像构建 ──→ 部署 │
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ Git │───→│Build│──→│Test │──→│Image│──→│Deploy│ │
│ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ 持续集成(CI) 持续部署(CD) │
│ │
└─────────────────────────────────────────────────────┘
GitLab CI/CD #
.gitlab-ci.yml #
yaml
stages:
- build
- test
- package
- deploy
variables:
IMAGE_NAME: registry.example.com/myapp
IMAGE_TAG: $CI_COMMIT_SHA
# 构建阶段
build:
stage: build
image: node:18-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
# 测试阶段
test:
stage: test
image: node:18-alpine
script:
- npm ci
- npm test
coverage: '/Coverage: \d+\.\d+/'
# 安全扫描
security-scan:
stage: test
image: docker:latest
services:
- docker:dind
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy:latest image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_NAME:$IMAGE_TAG
# 镜像构建
docker-build:
stage: package
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD registry.example.com
script:
- docker build -t $IMAGE_NAME:$IMAGE_TAG .
- docker push $IMAGE_NAME:$IMAGE_TAG
- |
if [ "$CI_COMMIT_BRANCH" == "main" ]; then
docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:latest
docker push $IMAGE_NAME:latest
fi
# 部署到开发环境
deploy-dev:
stage: deploy
image: docker:latest
services:
- docker:dind
script:
- docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD registry.example.com
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:dev
- docker push $IMAGE_NAME:dev
- ssh deploy@dev.example.com "cd /app && docker-compose pull && docker-compose up -d"
environment:
name: development
url: https://dev.example.com
only:
- develop
# 部署到生产环境
deploy-prod:
stage: deploy
image: docker:latest
services:
- docker:dind
script:
- docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD registry.example.com
- docker pull $IMAGE_NAME:$IMAGE_TAG
- docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_NAME:prod
- docker push $IMAGE_NAME:prod
- ssh deploy@prod.example.com "cd /app && docker-compose pull && docker-compose up -d"
environment:
name: production
url: https://www.example.com
only:
- main
when: manual
GitHub Actions #
.github/workflows/docker.yml #
yaml
name: Docker CI/CD
on:
push:
branches: [main, develop]
tags: ['v*']
pull_request:
branches: [main]
env:
REGISTRY: registry.example.com
IMAGE_NAME: myapp
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm test
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
security-scan:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}'
format: 'table'
exit-code: '1'
severity: 'CRITICAL,HIGH'
docker-build:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:cache
deploy-dev:
runs-on: ubuntu-latest
needs: docker-build
if: github.ref == 'refs/heads/develop'
steps:
- name: Deploy to dev
run: |
ssh deploy@dev.example.com "cd /app && docker-compose pull && docker-compose up -d"
deploy-prod:
runs-on: ubuntu-latest
needs: docker-build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Deploy to prod
run: |
ssh deploy@prod.example.com "cd /app && docker-compose pull && docker-compose up -d"
Jenkins Pipeline #
Jenkinsfile #
groovy
pipeline {
agent any
environment {
IMAGE_NAME = 'registry.example.com/myapp'
IMAGE_TAG = "${env.BUILD_ID}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'npm ci'
sh 'npm run build'
}
}
stage('Test') {
steps {
sh 'npm test'
}
post {
always {
junit 'test-results/*.xml'
publishCoverage adapters: [coberturaAdapter('coverage/*.xml')]
}
}
}
stage('Security Scan') {
steps {
script {
sh 'docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .'
sh 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image --exit-code 1 --severity HIGH,CRITICAL ${IMAGE_NAME}:${IMAGE_TAG}'
}
}
}
stage('Docker Build & Push') {
steps {
script {
docker.withRegistry('https://registry.example.com', 'registry-credentials') {
def app = docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
app.push()
if (env.BRANCH_NAME == 'main') {
app.push('latest')
}
}
}
}
}
stage('Deploy to Dev') {
when {
branch 'develop'
}
steps {
sh 'ssh deploy@dev.example.com "cd /app && docker-compose pull && docker-compose up -d"'
}
}
stage('Deploy to Prod') {
when {
branch 'main'
}
steps {
input 'Deploy to production?'
sh 'ssh deploy@prod.example.com "cd /app && docker-compose pull && docker-compose up -d"'
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(color: 'good', message: "Build ${env.BUILD_ID} succeeded!")
}
failure {
slackSend(color: 'danger', message: "Build ${env.BUILD_ID} failed!")
}
}
}
部署策略 #
滚动更新 #
yaml
# docker-compose.yml
services:
app:
image: myapp:v1.0
deploy:
update_config:
parallelism: 2
delay: 10s
failure_action: rollback
replicas: 4
蓝绿部署 #
bash
# 当前运行蓝色环境
docker-compose -f docker-compose.blue.yml up -d
# 部署绿色环境
docker-compose -f docker-compose.green.yml up -d
# 切换流量
# 更新负载均衡配置
# 停止蓝色环境
docker-compose -f docker-compose.blue.yml down
金丝雀发布 #
yaml
# docker-compose.yml
services:
app-v1:
image: myapp:v1.0
deploy:
replicas: 9
app-v2:
image: myapp:v2.0
deploy:
replicas: 1
小结 #
本节学习了CI/CD流水线集成:
- GitLab CI/CD配置
- GitHub Actions配置
- Jenkins Pipeline配置
- 部署策略选择
下一步 #
接下来,让我们学习 生产环境最佳实践,了解生产环境的部署经验。