CI/CD 集成
CI/CD 集成
GitOps 理念
GitOps 是现代 Kubernetes 应用交付的最佳实践:
Git Repository (单一真实来源)
↓
CI Pipeline (构建 & 测试)
↓
Container Registry (镜像仓库)
↓
CD Tool (ArgoCD/Flux)
↓
Kubernetes Cluster (自动同步)
核心原则:
- Git 是唯一的真实来源
- 声明式配置
- 自动化同步
- 不可变基础设施
GitHub Actions 完整工作流
多环境部署流水线
name: Build and Deploy
on:
push:
branches: [ main, develop ]
tags:
- 'v*'
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# 代码质量检查
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run type check
run: npm run type-check
# 单元测试
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
# 安全扫描
security:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
# 构建镜像
build:
needs: [lint, test, security]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
image-digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- 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}}-
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp }}
VCS_REF=${{ github.sha }}
VERSION=${{ steps.meta.outputs.version }}
- name: Sign the image with cosign
if: github.event_name != 'pull_request'
run: |
cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
# 部署到开发环境
deploy-dev:
needs: build
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment:
name: development
url: https://dev.example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG_DEV }}" | base64 -d > $HOME/.kube/config
- name: Update deployment
run: |
kubectl set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-n development
- name: Wait for rollout
run: |
kubectl rollout status deployment/myapp -n development --timeout=5m
- name: Verify deployment
run: |
kubectl get pods -n development -l app=myapp
kubectl get svc -n development -l app=myapp
# 部署到生产环境
deploy-prod:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.28.0'
- name: Configure kubectl
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG_PROD }}" | base64 -d > $HOME/.kube/config
- name: Create backup
run: |
kubectl get deployment myapp -n production -o yaml > backup-${{ github.sha }}.yaml
- name: Update deployment
run: |
kubectl set image deployment/myapp \
myapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} \
-n production \
--record
- name: Wait for rollout
run: |
kubectl rollout status deployment/myapp -n production --timeout=10m
- name: Run smoke tests
run: |
./scripts/smoke-test.sh https://example.com
- name: Rollback on failure
if: failure()
run: |
kubectl rollout undo deployment/myapp -n production
- name: Notify Slack
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to production ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Dockerfile 最佳实践
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production && \
npm cache clean --force
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产镜像
FROM node:18-alpine
# 安全:创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
WORKDIR /app
# 复制构建产物
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
# 切换到非 root 用户
USER nodejs
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node healthcheck.js
EXPOSE 3000
CMD ["node", "dist/main.js"]
GitLab CI/CD
.gitlab-ci.yml
stages:
- build
- test
- security
- deploy
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
KUBE_NAMESPACE_DEV: development
KUBE_NAMESPACE_PROD: production
# 构建镜像
build:
stage: build
image: docker:24
services:
- docker:24-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
only:
- main
- develop
- tags
# 单元测试
test:unit:
stage: test
image: node:18-alpine
script:
- npm ci
- npm run test:unit
coverage: '/Statements\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
# 安全扫描
security:container:
stage: security
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL $IMAGE_TAG
allow_failure: true
# 部署到开发环境
deploy:dev:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT_DEV
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n $KUBE_NAMESPACE_DEV
- kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE_DEV
environment:
name: development
url: https://dev.example.com
only:
- develop
# 部署到生产环境
deploy:prod:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT_PROD
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n $KUBE_NAMESPACE_PROD --record
- kubectl rollout status deployment/myapp -n $KUBE_NAMESPACE_PROD
environment:
name: production
url: https://example.com
when: manual
only:
- tags
ArgoCD - GitOps 工具
安装 ArgoCD
# 创建 namespace
kubectl create namespace argocd
# 安装 ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 等待部署完成
kubectl wait --for=condition=available --timeout=300s \
deployment/argocd-server -n argocd
# 获取初始密码
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# 端口转发
kubectl port-forward svc/argocd-server -n argocd 8080:443
# 访问 UI: https://localhost:8080
# 用户名: admin
# 密码: (上面获取的密码)
ArgoCD CLI
# 下载 ArgoCD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
# 登录
argocd login localhost:8080 --username admin --password <password>
# 修改密码
argocd account update-password
Application 配置
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
# Finalizer 确保级联删除
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
# 源仓库配置
source:
repoURL: https://github.com/myorg/myapp
targetRevision: HEAD
path: k8s/overlays/production
# Kustomize 配置
kustomize:
images:
- name: myapp
newTag: v1.2.3
# Helm 配置(如果使用 Helm)
# helm:
# valueFiles:
# - values-production.yaml
# parameters:
# - name: image.tag
# value: v1.2.3
# 目标集群配置
destination:
server: https://kubernetes.default.svc
namespace: production
# 同步策略
syncPolicy:
automated:
prune: true # 删除不在 Git 中的资源
selfHeal: true # 自动修复偏离
allowEmpty: false # 不允许空应用
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
# 重试策略
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# 忽略差异
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas # 忽略 HPA 修改的副本数
多环境管理
目录结构:
k8s/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
├── overlays/
│ ├── development/
│ │ ├── kustomization.yaml
│ │ ├── namespace.yaml
│ │ └── patches/
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── namespace.yaml
│ │ └── patches/
│ └── production/
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ └── patches/
创建多个 Application:
# app-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp
targetRevision: develop
path: k8s/overlays/development
destination:
server: https://kubernetes.default.svc
namespace: development
syncPolicy:
automated:
prune: true
selfHeal: true
---
# app-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-prod
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp
targetRevision: main
path: k8s/overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: false # 生产环境手动同步
ApplicationSet - 批量管理
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-set
namespace: argocd
spec:
generators:
- list:
elements:
- env: development
namespace: dev
branch: develop
replicas: 2
- env: staging
namespace: staging
branch: main
replicas: 3
- env: production
namespace: prod
branch: main
replicas: 5
template:
metadata:
name: 'myapp-{{env}}'
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp
targetRevision: '{{branch}}'
path: k8s/overlays/{{env}}
destination:
server: https://kubernetes.default.svc
namespace: '{{namespace}}'
syncPolicy:
automated:
prune: true
selfHeal: true
Helm Chart 发布
Chart 结构
myapp/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
├── secret.yaml
└── hpa.yaml
Chart.yaml
apiVersion: v2
name: myapp
description: My Application Helm Chart
type: application
version: 1.0.0
appVersion: "1.0.0"
maintainers:
- name: DevOps Team
email: devops@example.com
CI 中使用 Helm
# GitHub Actions
- name: Deploy with Helm
run: |
helm upgrade --install myapp ./helm/myapp \
--namespace production \
--create-namespace \
--values ./helm/myapp/values-prod.yaml \
--set image.tag=${{ github.sha }} \
--wait \
--timeout 5m
最佳实践
1. 镜像标签策略
# ❌ 不要使用 latest
image: myapp:latest
# ✅ 使用具体版本
image: myapp:v1.2.3
# ✅ 使用 Git SHA
image: myapp:sha-abc123
# ✅ 使用语义化版本
image: myapp:1.2.3
2. 配置管理
# 敏感信息使用 Secret
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
database-password: ${DATABASE_PASSWORD}
api-key: ${API_KEY}
3. 回滚策略
# 查看历史
kubectl rollout history deployment/myapp
# 回滚到上一版本
kubectl rollout undo deployment/myapp
# 回滚到指定版本
kubectl rollout undo deployment/myapp --to-revision=3
4. 金丝雀发布
使用 Argo Rollouts:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% 流量到新版本
- pause: {duration: 5m}
- setWeight: 30
- pause: {duration: 5m}
- setWeight: 50
- pause: {duration: 5m}
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v2
5. 通知集成
# ArgoCD 通知配置
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-sync-succeeded: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-sync-succeeded]
template.app-sync-succeeded: |
message: |
Application {{.app.metadata.name}} has been successfully synced.
slack:
attachments: |
[{
"title": "{{.app.metadata.name}}",
"color": "good",
"fields": [
{"title": "Sync Status", "value": "{{.app.status.sync.status}}", "short": true},
{"title": "Repository", "value": "{{.app.spec.source.repoURL}}", "short": true}
]
}]
常用命令
# ArgoCD
argocd app list
argocd app get myapp
argocd app sync myapp
argocd app diff myapp
argocd app rollback myapp
# Helm
helm list -n production
helm history myapp -n production
helm rollback myapp 3 -n production
# Kubectl
kubectl get deployment -n production
kubectl rollout status deployment/myapp -n production
kubectl rollout history deployment/myapp -n production
小结
CI/CD 是 Kubernetes 应用交付的关键:
核心组件:
- CI: GitHub Actions / GitLab CI / Jenkins
- CD: ArgoCD / Flux / Spinnaker
- Registry: Harbor / Docker Hub / GHCR
GitOps 优势:
- Git 作为单一真实来源
- 声明式配置
- 自动化同步
- 审计和回滚
最佳实践:
- 多阶段构建
- 镜像安全扫描
- 自动化测试
- 金丝雀发布
- 监控和告警
下一章我们将学习监控与日志,构建可观测性体系。