【Docker+K8s 实战·第八篇·收官】CI/CD 完整流水线:GitHub Actions + Docker + K8s,一次 push 到生产

更新时间:2026-05-20 | 阅读时长:约 26 分钟
系列:Docker + K8s 实战(共 8 篇)· 收官篇
环境:GitHub Actions,Docker Hub / GHCR,Kubernetes 1.30+
标签CI/CD GitHub Actions Docker Kubernetes GitOps 自动化部署 Helm ArgoCD


在这里插入图片描述

系列完整进度

篇次 主题 状态
第一篇 Docker 基础:镜像、容器、Dockerfile
第二篇 Dockerfile 进阶:多阶段构建
第三篇 Docker Compose:多容器编排
第四篇 K8s 核心概念:Pod、Service、Deployment
第五篇 K8s 配置与存储:ConfigMap、Secret、PV
第六篇 K8s 网络与服务发现:Ingress、TLS
第七篇 K8s 生产实践:HPA、滚动更新
第八篇(本篇·收官) CI/CD 完整流水线

目录


一、CI/CD 流水线总体设计

完整的 CI/CD 流水线:

代码变更(git push)
    ↓
┌───────────────────────────────────────────────────┐
│  CI:持续集成(保证代码质量)                        │
│                                                   │
│  ① 代码检查                                        │
│     lint(语法/风格)                               │
│     type-check(TypeScript/mypy)                  │
│     security scan(漏洞扫描)                       │
│                                                   │
│  ② 自动化测试                                       │
│     单元测试                                        │
│     集成测试(用 Docker Compose 启动依赖)            │
│     覆盖率检查(< 80% 失败)                         │
│                                                   │
│  ③ 构建镜像                                         │
│     多阶段构建(小镜像)                             │
│     镜像安全扫描(Trivy)                            │
│     推送到 Registry(打版本 tag)                    │
└───────────────────────────────────────────────────┘
    ↓
┌───────────────────────────────────────────────────┐
│  CD:持续部署(自动上线)                            │
│                                                   │
│  ④ 部署到 Staging                                   │
│     kubectl apply / helm upgrade                  │
│     自动化 smoke test(验证部署成功)                │
│                                                   │
│  ⑤ 部署到 Production(需要审批)                     │
│     GitHub Environment 手动审批                    │
│     滚动更新,零停机                                │
│     健康检查确认(rollout status)                   │
│     失败自动回滚                                    │
└───────────────────────────────────────────────────┘

触发条件:
  push to main       → 部署到 staging
  release tag v*.*.*  → 部署到 production(需人工审批)
  PR                  → 只跑 CI(不部署)

二、GitHub Actions 基础

2.1 关键概念

# .github/workflows/ci-cd.yml

name: CI/CD Pipeline          # 流水线名称

# ── 触发条件 ─────────────────────────────────────────────
on:
  push:
    branches:
      - main                  # push 到 main 分支
      - develop
    tags:
      - 'v*.*.*'              # 推送版本 tag(如 v1.2.3)
  pull_request:
    branches:
      - main                  # 向 main 发 PR
  workflow_dispatch:          # 支持手动触发
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
        type: choice
        options: [staging, production]

# ── 全局环境变量 ──────────────────────────────────────────
env:
  REGISTRY: ghcr.io                           # GitHub Container Registry
  IMAGE_NAME: ${{ github.repository }}        # 如 myorg/myapp
  # 或 Docker Hub
  # REGISTRY: docker.io
  # IMAGE_NAME: myusername/myapp

# ── Job 定义 ─────────────────────────────────────────────
jobs:
  test:                        # Job 名称
    name: 🧪 测试              # 显示名称
    runs-on: ubuntu-latest     # 运行环境

    # 服务容器(集成测试的依赖)
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-retries 5

    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      - name: 运行测试
        run: pytest tests/
        env:
          DATABASE_URL: postgresql://postgres:test@localhost/testdb

2.2 常用 Actions

# ── 代码检出 ──────────────────────────────────────────────
- uses: actions/checkout@v4
  with:
    fetch-depth: 0    # 获取完整历史(git log 用)

# ── 缓存依赖 ──────────────────────────────────────────────
- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-npm-

# ── 设置 Node.js ──────────────────────────────────────────
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'

# ── 设置 Python ───────────────────────────────────────────
- uses: actions/setup-python@v5
  with:
    python-version: '3.12'
    cache: 'pip'

# ── Docker Buildx(多平台构建)────────────────────────────
- uses: docker/setup-buildx-action@v3

# ── 登录 Docker Registry ──────────────────────────────────
- 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/myorg/myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

# ── Trivy 安全扫描 ────────────────────────────────────────
- uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/myorg/myapp:latest
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

# ── kubectl 部署 ──────────────────────────────────────────
- uses: azure/k8s-deploy@v5
  with:
    manifests: k8s/
    images: ghcr.io/myorg/myapp:${{ github.sha }}

三、阶段一:代码质量检查

# .github/workflows/ci.yml(PR 和 push 都触发)
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  # ── 代码检查 ────────────────────────────────────────────
  lint-and-type-check:
    name: 🔍 代码检查
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - name: 安装检查工具
        run: pip install ruff mypy

      - name: Ruff 代码风格检查
        run: ruff check .

      - name: Ruff 格式检查
        run: ruff format --check .

      - name: mypy 类型检查
        run: mypy app/ --ignore-missing-imports

  # ── 安全扫描 ─────────────────────────────────────────────
  security-scan:
    name: 🔒 安全扫描
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 依赖漏洞扫描
        uses: pypa/gh-action-pip-audit@v1.0.8
        with:
          inputs: requirements.txt

      - name: Dockerfile 安全检查
        uses: hadolint/hadolint-action@v3.1.0
        with:
          dockerfile: Dockerfile

  # ── 单元测试 ─────────────────────────────────────────────
  unit-tests:
    name: 🧪 单元测试
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - name: 安装依赖
        run: pip install -r requirements.txt -r requirements-dev.txt

      - name: 运行单元测试(含覆盖率)
        run: |
          pytest tests/unit \
            --cov=app \
            --cov-report=xml \
            --cov-fail-under=80 \
            -v

      - name: 上传覆盖率报告
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage.xml
          token: ${{ secrets.CODECOV_TOKEN }}

  # ── 集成测试 ─────────────────────────────────────────────
  integration-tests:
    name: 🔗 集成测试
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - name: 安装依赖
        run: pip install -r requirements.txt -r requirements-dev.txt

      - name: 执行数据库迁移
        run: python manage.py migrate
        env:
          DATABASE_URL: postgresql://postgres:testpass@localhost/testdb
          REDIS_URL: redis://localhost:6379/0

      - name: 运行集成测试
        run: pytest tests/integration -v
        env:
          DATABASE_URL: postgresql://postgres:testpass@localhost/testdb
          REDIS_URL: redis://localhost:6379/0
          SECRET_KEY: test-secret-key

四、阶段二:构建与推送镜像

# .github/workflows/build.yml
name: Build & Push Image

on:
  push:
    branches: [main]
    tags: ['v*.*.*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    name: 🐳 构建镜像
    runs-on: ubuntu-latest
    # 输出镜像 tag,供后续 job 使用
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}
      version: ${{ steps.version.outputs.version }}

    permissions:
      contents: read
      packages: write         # 推送到 GHCR 需要

    steps:
      - name: 检出代码
        uses: actions/checkout@v4
        with:
          fetch-depth: 0      # 获取完整历史,用于生成版本号

      # 生成语义化版本号
      - name: 生成版本号
        id: version
        run: |
          if [[ "${{ github.ref }}" == refs/tags/* ]]; then
            VERSION="${GITHUB_REF#refs/tags/}"
          else
            VERSION="$(git describe --tags --always --dirty)-$(echo $GITHUB_SHA | cut -c1-7)"
          fi
          echo "version=${VERSION}" >> $GITHUB_OUTPUT
          echo "版本号:${VERSION}"

      # 生成镜像 metadata(tags 和 labels)
      - name: 生成镜像 Metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            # push 到 main:打 sha 短哈希 tag
            type=sha,prefix=,suffix=,format=short
            # push tag v*:打版本 tag(如 v1.2.3)
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            # main 分支:打 latest tag
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
          labels: |
            org.opencontainers.image.title=MyApp
            org.opencontainers.image.description=My production application
            org.opencontainers.image.vendor=MyOrg

      # 设置 Docker Buildx(支持多平台和缓存)
      - name: 设置 Docker Buildx
        uses: docker/setup-buildx-action@v3

      # 登录到 GHCR
      - name: 登录 GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # 构建并推送(使用 GitHub Actions 缓存加速)
      - name: 构建并推送镜像
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./Dockerfile
          platforms: linux/amd64,linux/arm64    # 多平台构建
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          # GitHub Actions 缓存(免费,速度快)
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
            GIT_COMMIT=${{ github.sha }}
            VERSION=${{ steps.version.outputs.version }}

      # 安全扫描(构建后扫描)
      - name: Trivy 镜像漏洞扫描
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: '1'          # 发现高危漏洞则 CI 失败

      # 上传扫描报告到 GitHub Security
      - name: 上传安全扫描报告
        uses: github/codeql-action/upload-sarif@v3
        if: always()              # 即使 trivy 失败也上传报告
        with:
          sarif_file: trivy-results.sarif

      # 输出镜像摘要(用于追踪)
      - name: 输出镜像信息
        run: |
          echo "镜像:${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
          echo "Tag:${{ steps.meta.outputs.tags }}"
          echo "Digest:${{ steps.build.outputs.digest }}"
          echo "版本:${{ steps.version.outputs.version }}"

五、阶段三:部署到 Kubernetes

5.1 配置 kubeconfig

# 在 GitHub Secrets 中添加 K8s 集群配置

# 方式1:完整 kubeconfig(适合自建集群)
# 获取 kubeconfig
kubectl config view --minify --flatten > kubeconfig.yaml
# 在 GitHub 仓库 Settings → Secrets 中添加:
# KUBE_CONFIG = (kubeconfig.yaml 的 base64 编码内容)
# base64 kubeconfig.yaml | pbcopy

# 方式2:ServiceAccount Token(更安全,推荐)
# 创建专用 ServiceAccount
kubectl create serviceaccount github-actions -n myapp
kubectl create clusterrolebinding github-actions \
  --clusterrole=cluster-admin \
  --serviceaccount=myapp:github-actions

# 获取 Token(K8s 1.24+)
kubectl create token github-actions -n myapp --duration=8760h
# 在 GitHub Secrets 中添加:
# KUBE_TOKEN = 上面输出的 token
# KUBE_SERVER = https://your-cluster-api-server
# KUBE_CA_CERT = (base64 编码的 CA 证书)

5.2 部署 Job

# .github/workflows/deploy.yml
name: Deploy to K8s

on:
  workflow_call:                  # 被其他 workflow 调用
    inputs:
      environment:
        required: true
        type: string
      image-tag:
        required: true
        type: string
    secrets:
      KUBE_CONFIG:
        required: true

jobs:
  deploy:
    name: 🚀 部署到 ${{ inputs.environment }}
    runs-on: ubuntu-latest
    environment:
      name: ${{ inputs.environment }}
      url: https://${{ inputs.environment == 'production' && 'example.com' || 'staging.example.com' }}

    steps:
      - name: 检出代码
        uses: actions/checkout@v4

      # 配置 kubectl
      - name: 配置 kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'v1.30.0'

      - name: 设置 kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
          chmod 600 ~/.kube/config

      # 验证集群连接
      - name: 验证集群连接
        run: |
          kubectl cluster-info
          kubectl get nodes

      # 更新镜像版本(触发滚动更新)
      - name: 更新镜像版本
        run: |
          NAMESPACE=${{ inputs.environment }}

          kubectl set image deployment/api \
            api=ghcr.io/${{ github.repository }}:${{ inputs.image-tag }} \
            -n ${NAMESPACE}

          kubectl set image deployment/worker \
            worker=ghcr.io/${{ github.repository }}:${{ inputs.image-tag }} \
            -n ${NAMESPACE}

          # 添加更新注解(记录在 rollout history)
          kubectl annotate deployment/api \
            kubernetes.io/change-cause="${{ inputs.image-tag }} deployed by GitHub Actions" \
            -n ${NAMESPACE} \
            --overwrite

      # 等待滚动更新完成
      - name: 等待部署完成
        run: |
          NAMESPACE=${{ inputs.environment }}

          kubectl rollout status deployment/api \
            -n ${NAMESPACE} \
            --timeout=300s

          kubectl rollout status deployment/worker \
            -n ${NAMESPACE} \
            --timeout=300s

      # Smoke Test(验证服务健康)
      - name: Smoke Test
        run: |
          NAMESPACE=${{ inputs.environment }}

          if [ "${NAMESPACE}" == "production" ]; then
            BASE_URL="https://example.com"
          else
            BASE_URL="https://staging.example.com"
          fi

          # 等待 DNS 和 Ingress 生效
          sleep 10

          # 验证健康检查接口
          HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
            "${BASE_URL}/health/live" \
            --retry 5 \
            --retry-delay 5 \
            --retry-connrefused)

          if [ "${HTTP_STATUS}" != "200" ]; then
            echo "❌ Smoke Test 失败!HTTP 状态码:${HTTP_STATUS}"
            echo "触发回滚..."
            kubectl rollout undo deployment/api -n ${NAMESPACE}
            kubectl rollout undo deployment/worker -n ${NAMESPACE}
            exit 1
          fi

          echo "✅ Smoke Test 通过!HTTP 状态码:${HTTP_STATUS}"

      # 输出部署信息
      - name: 部署成功通知
        if: success()
        run: |
          echo "✅ 部署成功!"
          echo "环境:${{ inputs.environment }}"
          echo "镜像:ghcr.io/${{ github.repository }}:${{ inputs.image-tag }}"
          echo "Pod 状态:"
          kubectl get pods -n ${{ inputs.environment }} -l app=api

      # 部署失败处理
      - name: 部署失败回滚
        if: failure()
        run: |
          echo "❌ 部署失败,执行回滚..."
          kubectl rollout undo deployment/api -n ${{ inputs.environment }}
          kubectl rollout status deployment/api -n ${{ inputs.environment }} --timeout=120s
          echo "回滚完成"

六、多环境部署:staging + production

# .github/workflows/ci-cd-complete.yml
# 完整的多环境 CI/CD 流水线
name: Complete CI/CD Pipeline

on:
  push:
    branches: [main]
    tags: ['v*.*.*']
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ══════ 阶段1:代码质量 ══════
  quality:
    name: 🔍 代码质量检查
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'
      - run: pip install ruff mypy
      - run: ruff check . && ruff format --check .
      - run: mypy app/

  # ══════ 阶段2:测试 ══════
  test:
    name: 🧪 自动化测试
    needs: quality             # 质量检查通过才测试
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-retries 5
      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-retries 3
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'
      - run: pip install -r requirements.txt -r requirements-dev.txt
      - run: |
          python manage.py migrate
          pytest --cov=app --cov-fail-under=80 -v
        env:
          DATABASE_URL: postgresql://postgres:test@localhost/testdb
          REDIS_URL: redis://localhost:6379/0
          SECRET_KEY: ci-test-key

  # ══════ 阶段3:构建镜像 ══════
  build:
    name: 🐳 构建镜像
    needs: test               # 测试通过才构建
    runs-on: ubuntu-latest
    # PR 时不 push,只 build 测试
    if: github.event_name != 'pull_request'
    outputs:
      image-tag: ${{ steps.meta.outputs.version }}
      image-full: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

    permissions:
      contents: read
      packages: write
      security-events: write  # 上传安全报告

    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=,format=short
            type=semver,pattern={{version}}
            type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        id: build
        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
      - uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
          severity: CRITICAL,HIGH
          exit-code: '1'
          format: sarif
          output: trivy.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy.sarif

  # ══════ 阶段4:部署到 Staging ══════
  deploy-staging:
    name: 🚀 部署到 Staging
    needs: build
    runs-on: ubuntu-latest
    # 只有 main 分支才部署到 staging
    if: github.ref == 'refs/heads/main'
    environment:
      name: staging
      url: https://staging.example.com

    steps:
      - uses: actions/checkout@v4
      - uses: azure/setup-kubectl@v3
      - name: 设置 kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.STAGING_KUBE_CONFIG }}" | base64 -d > ~/.kube/config
      - name: 部署到 Staging
        run: |
          kubectl set image deployment/api \
            api=${{ needs.build.outputs.image-full }} \
            -n staging
          kubectl rollout status deployment/api -n staging --timeout=300s
      - name: Smoke Test
        run: |
          sleep 15
          curl -f https://staging.example.com/health/live || \
            (kubectl rollout undo deployment/api -n staging && exit 1)
          echo "✅ Staging 部署成功"

  # ══════ 阶段5:部署到 Production(需要审批)══════
  deploy-production:
    name: 🚀 部署到 Production
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    # 只有 tag push 才部署到生产
    if: startsWith(github.ref, 'refs/tags/v')
    environment:
      name: production          # GitHub Environment,可以配置审批人
      url: https://example.com  # 部署成功后显示的链接

    steps:
      - uses: actions/checkout@v4
      - uses: azure/setup-kubectl@v3
      - name: 设置 kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "${{ secrets.PROD_KUBE_CONFIG }}" | base64 -d > ~/.kube/config
      - name: 部署到 Production
        run: |
          # 更新所有相关 Deployment
          for deploy in api worker scheduler frontend; do
            kubectl set image deployment/${deploy} \
              ${deploy}=${{ needs.build.outputs.image-full }} \
              -n production 2>/dev/null || true
          done

          # 等待所有 Deployment 完成
          kubectl rollout status deployment/api -n production --timeout=300s
          kubectl rollout status deployment/worker -n production --timeout=300s

      - name: 生产 Smoke Test
        run: |
          sleep 20
          # 多次验证确保稳定
          for i in 1 2 3; do
            sleep 5
            curl -f https://example.com/health/live || \
              (kubectl rollout undo deployment/api -n production && exit 1)
          done
          echo "✅ Production 部署成功!"

      # Slack/钉钉/企业微信 通知
      - name: 发送部署通知
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: |
            部署到 Production:${{ job.status }}
            版本:${{ needs.build.outputs.image-tag }}
            提交:${{ github.sha }}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

6.1 配置 GitHub Environment 审批

GitHub 仓库 → Settings → Environments → New environment

Environment: production
Protection rules:
  ✅ Required reviewers: [team-leads](需要指定人审批)
  ✅ Wait timer: 0 minutes
  ✅ Deployment branches: Selected branches → v*.*.*(只允许 tag)

这样每次部署到 production 时,
会自动暂停等待指定审批人点击 "Approve and deploy"。

七、GitOps:ArgoCD 自动同步

GitOps 理念:
  集群状态 = Git 仓库中的 YAML 文件
  ArgoCD 持续监听 Git 仓库变化
  自动同步 Git 中的期望状态到集群

与传统 CI/CD 的区别:
  传统:CI/CD 系统 → kubectl apply → 集群
  GitOps:更新 Git → ArgoCD 检测变化 → 自动同步集群

优势:
  集群状态有版本历史(git log)
  回滚 = git revert
  多集群管理统一
  审计追踪完整
# 安装 ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# 获取初始密码
kubectl get secret argocd-initial-admin-secret \
  -n argocd \
  -o jsonpath="{.data.password}" | base64 -d

# 访问 UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# https://localhost:8080
# argocd-app.yaml:定义 ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  project: default

  # 源:Git 仓库中的 K8s 配置
  source:
    repoURL: https://github.com/myorg/myapp-config
    targetRevision: main                # 监听 main 分支
    path: k8s/production                # 目录
    # 如果使用 Helm
    # chart: myapp
    # helm:
    #   values: |
    #     image.tag: latest

  # 目标:部署到哪个集群和 namespace
  destination:
    server: https://kubernetes.default.svc
    namespace: production

  # 同步策略
  syncPolicy:
    automated:
      prune: true           # 自动删除 Git 中不存在的资源
      selfHeal: true        # 手动修改集群状态后自动恢复到 Git 中的状态
      allowEmpty: false     # 不允许同步空目录
    syncOptions:
      - CreateNamespace=true    # 自动创建 namespace
      - PrunePropagationPolicy=foreground
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m
# CI 更新镜像 tag(GitOps 方式)
# CI 不直接 kubectl,而是更新 Git 仓库中的镜像 tag
# ArgoCD 检测到 Git 变化,自动同步到集群

# .github/workflows/update-image-tag.yml
name: Update Image Tag

jobs:
  update-tag:
    name: 更新 Git 中的镜像 Tag
    steps:
      - name: 检出配置仓库
        uses: actions/checkout@v4
        with:
          repository: myorg/myapp-config   # 独立的配置仓库(推荐)
          token: ${{ secrets.CONFIG_REPO_TOKEN }}

      - name: 更新镜像 Tag
        run: |
          # 使用 kustomize 更新镜像 tag
          cd k8s/production
          kustomize edit set image \
            ghcr.io/myorg/myapp=ghcr.io/myorg/myapp:${{ github.sha }}

          # 或者直接 sed
          sed -i "s|image: ghcr.io/myorg/myapp:.*|image: ghcr.io/myorg/myapp:${{ github.sha }}|g" \
            deployment.yaml

      - name: 提交变更
        run: |
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add .
          git commit -m "chore: update image tag to ${{ github.sha }}"
          git push
          # ArgoCD 检测到 Git 变化,自动同步到集群!

八、Helm:参数化 K8s 配置

Helm 解决的问题:
  不同环境(staging/prod)的 K8s 配置大部分相同,只有少数差异
  (副本数、资源限制、域名、镜像 tag 等)
  
  不用 Helm:维护多份几乎相同的 YAML(难以维护)
  用 Helm:一套模板 + 不同的 values.yaml(DRY 原则)
# 安装 Helm
brew install helm   # macOS

# 创建 Chart
helm create myapp
# myapp/
#   Chart.yaml         Chart 元信息
#   values.yaml        默认值
#   templates/         K8s 资源模板
#     deployment.yaml
#     service.yaml
#     ingress.yaml
#     _helpers.tpl     辅助模板函数
# myapp/values.yaml(默认值)
replicaCount: 2

image:
  repository: ghcr.io/myorg/myapp
  tag: latest
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  host: example.com
  tls:
    enabled: true
    secretName: example-com-tls

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

env:
  LOG_LEVEL: info
  PORT: "8000"

secrets:
  dbPassword: ""
  secretKey: ""
# myapp/values-staging.yaml(staging 覆盖)
replicaCount: 1

image:
  tag: sha-abc123    # CI 传入具体 tag

ingress:
  host: staging.example.com
  tls:
    secretName: staging-example-com-tls

resources:
  limits:
    cpu: 200m
    memory: 256Mi

env:
  LOG_LEVEL: debug
# myapp/values-production.yaml(production 覆盖)
replicaCount: 5

image:
  tag: v1.2.0        # 生产用版本 tag

ingress:
  host: example.com

resources:
  requests:
    cpu: 200m
    memory: 256Mi
  limits:
    cpu: 1000m
    memory: 1Gi

autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20
# Helm 常用命令

# 安装
helm install myapp ./myapp \
  -f myapp/values-staging.yaml \
  --set image.tag=sha-abc123 \
  --namespace staging \
  --create-namespace

# 升级(已存在则更新,不存在则安装)
helm upgrade --install myapp ./myapp \
  -f myapp/values-production.yaml \
  --set image.tag=v1.2.0 \
  --namespace production \
  --atomic \             # 失败则自动回滚
  --timeout 300s \
  --wait                 # 等待所有资源就绪

# 查看
helm list -A             # 所有 namespace 的 releases
helm status myapp -n production
helm history myapp -n production

# 回滚
helm rollback myapp 2 -n production   # 回到第 2 个版本

# 渲染模板(不部署,只查看生成的 YAML)
helm template myapp ./myapp \
  -f values-staging.yaml \
  --set image.tag=sha-abc123

# 删除
helm uninstall myapp -n staging
# 在 GitHub Actions 中使用 Helm
- name: Helm 部署
  uses: azure/setup-helm@v3
  with:
    version: '3.14.0'

- name: Helm upgrade
  run: |
    helm upgrade --install myapp ./helm/myapp \
      -f helm/myapp/values-${{ inputs.environment }}.yaml \
      --set image.tag=${{ inputs.image-tag }} \
      --namespace ${{ inputs.environment }} \
      --create-namespace \
      --atomic \
      --timeout 300s \
      --wait

九、系列收官:完整知识图谱

Docker + K8s 实战系列完整知识图谱:

┌─────────────────────────────────────────────────────────────┐
│  第一篇:Docker 基础                                          │
│  镜像/容器/Dockerfile/数据卷/网络                             │
│  docker run/exec/logs/inspect                               │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第二篇:Dockerfile 进阶                                      │
│  多阶段构建(1GB→50MB)                                       │
│  BuildKit 缓存挂载/跨平台构建/镜像安全扫描                     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第三篇:Docker Compose                                       │
│  多容器编排/服务依赖/网络隔离                                   │
│  多环境 override/Makefile 封装                               │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第四篇:K8s 核心概念                                         │
│  Pod/Deployment/Service/Namespace                           │
│  集群架构/kubectl 常用命令                                    │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第五篇:K8s 配置与存储                                       │
│  ConfigMap/Secret(Sealed Secrets)                         │
│  PV/PVC/StorageClass/StatefulSet                            │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第六篇:K8s 网络与服务发现                                   │
│  CoreDNS/Ingress/ingress-nginx                              │
│  cert-manager(Let's Encrypt)/NetworkPolicy                │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第七篇:K8s 生产实践                                         │
│  三探针/资源限制/HPA(KEDA)                                  │
│  滚动更新/蓝绿/PDB/ResourceQuota                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  第八篇:CI/CD 完整流水线(本篇·收官)                         │
│  GitHub Actions(质量/测试/构建/部署)                         │
│  多环境(staging/production)/GitOps(ArgoCD)               │
│  Helm 参数化配置                                             │
└─────────────────────────────────────────────────────────────┘

贯穿全系列的三个核心理念:

① 不可变基础设施(Immutable Infrastructure)
   代码变更 → 构建新镜像 → 部署新镜像 → 不改已运行的容器
   回滚 = 部署旧镜像(而非在运行中修改配置)

② 声明式配置(Declarative Configuration)
   告诉 K8s "我要什么状态"(YAML)
   K8s 负责达到并维持这个状态
   不是"执行步骤",而是"描述目标"

③ 关注点分离(Separation of Concerns)
   代码(Git)↔ 配置(ConfigMap)↔ 密钥(Secret)
   构建(CI)↔ 部署(CD)↔ 运行(K8s)
   开发环境(Compose)↔ 生产环境(K8s)

💬 八篇系列全部看完了!你们现在的 CI/CD 是什么方案?有没有在生产用 K8s? 欢迎评论区分享!

🙏 「Docker + K8s 实战」系列(八篇)完结撒花!从 Docker 基础到 K8s 生产再到 CI/CD,全链路讲完了。如果整个系列帮到你,最后一次三连(点赞👍 + 收藏⭐ + 关注)!感谢一路相伴!


本文为原创技术分享。环境:Docker 26.x,Kubernetes 1.30+,GitHub Actions。最后更新:2026-05-20
Docker + K8s 实战系列(八篇)完结 🎉

更多推荐