2293 字
11 分钟
GitHub Actions CI/CD 完全指南 2026:自动化构建 + 测试 + 部署实战
手动 git push 之后,登到服务器 git pull 再 docker compose restart……每次发布都要重复这套动作,既低效又容易出错。GitHub Actions 让这一切自动化:代码推送 → 自动测试 → 自动构建镜像 → 自动部署上线,全程无需人工介入。
本文从语法基础到生产级实战,覆盖 GitHub Actions 所有核心场景。
一、Workflow 语法基础
name: CI/CD Pipeline # Workflow 名称(显示在 Actions 标签页)
# 触发条件on: push: branches: [main, develop] paths-ignore: # 这些文件变更不触发 - "**.md" - ".gitignore" pull_request: branches: [main] schedule: - cron: "0 2 * * *" # 每天凌晨2点定时运行 workflow_dispatch: # 允许手动触发(Actions 页面有按钮) inputs: environment: description: "部署环境" required: true default: "staging" type: choice options: [staging, production]
# 环境变量(所有 job 共享)env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # 仓库名,如 owner/repo
jobs: # ── Job 1:代码检查 ────────────────────────────────────────── lint: name: Lint & Format Check runs-on: ubuntu-24.04 # Runner 类型 steps: - name: Checkout code uses: actions/checkout@v4
- name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip" # 自动缓存 pip 依赖
- name: Install dependencies run: pip install ruff mypy
- name: Run Ruff linter run: ruff check .
- name: Run mypy type check run: mypy src/
# ── Job 2:测试(依赖 lint 通过)──────────────────────────── test: name: Run Tests runs-on: ubuntu-24.04 needs: [lint] # 等 lint job 成功后才运行 services: # 启动附加服务容器(集成测试使用) postgres: image: postgres:16 env: POSTGRES_DB: testdb POSTGRES_USER: testuser POSTGRES_PASSWORD: testpass 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 env: DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb REDIS_URL: redis://localhost:6379 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 - name: Run pytest with coverage run: | pytest tests/ \ --cov=src \ --cov-report=xml \ --cov-report=term-missing \ --junitxml=test-results.xml \ -v - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: files: coverage.xml token: ${{ secrets.CODECOV_TOKEN }}
# ── Job 3:多版本矩阵测试 ──────────────────────────────────── test-matrix: name: Test Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false # 某个矩阵失败不停止其他 matrix: os: [ubuntu-24.04, macos-14] python-version: ["3.11", "3.12"] exclude: - os: macos-14 python-version: "3.11" # 排除特定组合 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - run: pip install -e ".[dev]" - run: pytest tests/ -x二、Docker 镜像自动构建与发布
name: Build & Push Docker Image
on: push: branches: [main] tags: ["v*.*.*"] # 发布 tag 时触发
# 使用 OIDC 无密钥认证(推荐)permissions: contents: read packages: write # 允许推送到 GHCR id-token: write # OIDC token
jobs: docker: name: Build & Push runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4
# 设置 QEMU(多平台构建需要) - name: Set up QEMU uses: docker/setup-qemu-action@v3
# 设置 Docker Buildx - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3
# 登录 GitHub Container Registry(用 GITHUB_TOKEN,无需配置 Secret) - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
# 同时登录 Docker Hub(可选) - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}
# 提取元数据(生成 tag 和 label) - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5 with: images: | ghcr.io/${{ github.repository }} ${{ secrets.DOCKERHUB_USERNAME }}/myapp tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} # v1.2.3 → 1.2.3 type=semver,pattern={{major}}.{{minor}} # v1.2.3 → 1.2 type=sha,prefix=sha- # git commit sha type=raw,value=latest,enable={{is_default_branch}}
# 构建并推送(多平台:amd64 + arm64) - name: Build and push uses: docker/build-push-action@v6 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 # 使用 GitHub Actions 缓存层 cache-to: type=gha,mode=max # 最大化缓存 build-args: | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} GIT_SHA=${{ github.sha }}三、自动化部署场景
3.1 部署到 Vercel(静态站点/Next.js)
name: Deploy to Vercel
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-24.04 environment: name: production url: ${{ steps.deploy.outputs.url }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" cache: "pnpm" - run: corepack enable && pnpm install --frozen-lockfile - run: pnpm build
- name: Deploy to Vercel id: deploy uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: "--prod"3.2 部署到 VPS(SSH + Docker Compose)
name: Deploy to VPS
on: push: branches: [main]
jobs: deploy: runs-on: ubuntu-24.04 environment: production steps: - uses: actions/checkout@v4
# 构建并推送镜像(复用上面的 Docker 发布步骤) - name: Build & Push image run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} . docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
# SSH 连接服务器,拉取新镜像并重启 - name: Deploy via SSH uses: appleboy/ssh-action@v1 with: host: ${{ secrets.VPS_HOST }} username: ${{ secrets.VPS_USER }} key: ${{ secrets.VPS_SSH_KEY }} port: ${{ secrets.VPS_PORT }} script: | set -e cd /opt/myapp
# 更新镜像版本 export IMAGE_TAG=${{ github.sha }}
# 拉取新镜像 echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io \ -u ${{ github.actor }} --password-stdin docker pull ghcr.io/${{ github.repository }}:${IMAGE_TAG}
# 滚动更新(零停机) docker compose up -d --no-deps --build app
# 清理旧镜像 docker image prune -f echo "Deployed image: ${IMAGE_TAG}"3.3 部署到 Kubernetes
name: Deploy to Kubernetes
on: push: tags: ["v*"]
jobs: deploy: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4
# 使用 OIDC 认证 AWS(无需静态 Access Key) - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/github-actions-role aws-region: ap-northeast-1
- name: Login to ECR uses: aws-actions/amazon-ecr-login@v2
- name: Build & Push to ECR run: | IMAGE_URI=123456789.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:${{ github.ref_name }} docker build -t $IMAGE_URI . docker push $IMAGE_URI echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV
- name: Set up kubectl uses: azure/setup-kubectl@v4
- name: Configure kubeconfig run: aws eks update-kubeconfig --name my-cluster --region ap-northeast-1
- name: Deploy to K8s run: | kubectl set image deployment/myapp \ app=${{ env.IMAGE_URI }} \ --namespace=production kubectl rollout status deployment/myapp \ --namespace=production \ --timeout=5m四、Cache 加速策略
# 完整缓存策略示例steps: # Node.js 依赖缓存(setup-node 内置) - uses: actions/setup-node@v4 with: node-version: "20" cache: "pnpm" # 自动处理 ~/.pnpm-store
# Python 依赖缓存(setup-python 内置) - uses: actions/setup-python@v5 with: python-version: "3.12" cache: "pip"
# Gradle / Maven 缓存 - uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }} restore-keys: gradle-${{ runner.os }}-
# Rust 编译缓存 - uses: Swatinem/rust-cache@v2 with: workspaces: ". -> target"
# 自定义路径缓存(适合任何工具) - uses: actions/cache@v4 with: path: ~/.cache/custom-tool key: custom-${{ runner.os }}-${{ hashFiles('**/lockfile') }} restore-keys: custom-${{ runner.os }}- # 前缀匹配(部分缓存命中)五、Secrets 与 Environment 管理
name: Multi-Environment Deploy
on: push: branches: [main, staging]
jobs: deploy: runs-on: ubuntu-24.04 # 根据分支选择 Environment(不同 Environment 有不同 Secrets) environment: name: ${{ github.ref_name == 'main' && 'production' || 'staging' }} steps: - uses: actions/checkout@v4
- name: Deploy env: # 引用当前 Environment 的 Secrets DB_URL: ${{ secrets.DATABASE_URL }} API_KEY: ${{ secrets.API_KEY }} run: | echo "Deploying to ${{ vars.ENVIRONMENT_NAME }}" # vars 是非敏感的配置变量(公开可见),secrets 是加密的 ./scripts/deploy.sh \ --env ${{ vars.ENV_NAME }} \ --version ${{ github.sha }}
# production 环境需要手动审批(在 Environment 设置中配置 Required reviewers) notify: runs-on: ubuntu-24.04 needs: deploy if: always() # 无论成功失败都发通知 steps: - name: Notify Telegram uses: appleboy/telegram-action@master with: to: ${{ secrets.TELEGRAM_CHAT_ID }} token: ${{ secrets.TELEGRAM_BOT_TOKEN }} message: | ${{ job.status == 'success' && '✅' || '❌' }} 部署完成 仓库: ${{ github.repository }} 分支: ${{ github.ref_name }} 提交: ${{ github.sha }} 状态: ${{ needs.deploy.result }}六、可复用 Workflow(Reusable Workflow)
# .github/workflows/_build-and-test.yml(可复用,文件名以_开头为惯例)name: "[Reusable] Build and Test"
on: workflow_call: # 声明为可复用 Workflow inputs: python-version: required: false type: string default: "3.12" run-integration-tests: required: false type: boolean default: false secrets: CODECOV_TOKEN: required: false
jobs: build-test: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} cache: "pip" - run: pip install -e ".[dev]" - run: pytest tests/unit/ -v - name: Integration tests if: inputs.run-integration-tests run: pytest tests/integration/ -v# .github/workflows/ci.yml(调用可复用 Workflow)jobs: test: uses: ./.github/workflows/_build-and-test.yml with: python-version: "3.12" run-integration-tests: true secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-311: uses: ./.github/workflows/_build-and-test.yml with: python-version: "3.11"七、编写自定义 Action
name: "Notify Deployment"description: "发送部署结果通知"inputs: status: description: "部署状态 (success/failure)" required: true environment: description: "部署环境" required: true telegram-token: description: "Telegram Bot Token" required: true telegram-chat-id: description: "Telegram Chat ID" required: trueoutputs: message-id: description: "发送的消息 ID" value: ${{ steps.send.outputs.message-id }}
runs: using: "composite" steps: - name: Send Telegram notification id: send shell: bash env: STATUS: ${{ inputs.status }} ENV: ${{ inputs.environment }} TOKEN: ${{ inputs.telegram-token }} CHAT_ID: ${{ inputs.telegram-chat-id }} run: | EMOJI=$([[ "$STATUS" == "success" ]] && echo "✅" || echo "❌") MESSAGE="${EMOJI} *部署${STATUS}*%0A环境: ${ENV}%0A时间: $(date '+%Y-%m-%d %H:%M')"
RESPONSE=$(curl -s -X POST \ "https://api.telegram.org/bot${TOKEN}/sendMessage" \ -d "chat_id=${CHAT_ID}&text=${MESSAGE}&parse_mode=Markdown")
MESSAGE_ID=$(echo $RESPONSE | jq -r '.result.message_id') echo "message-id=$MESSAGE_ID" >> $GITHUB_OUTPUT# 在其他 Workflow 中使用自定义 Action- name: Notify deployment uses: ./.github/actions/notify-deploy with: status: ${{ job.status }} environment: production telegram-token: ${{ secrets.TELEGRAM_BOT_TOKEN }} telegram-chat-id: ${{ secrets.TELEGRAM_CHAT_ID }}八、安全最佳实践
# 权限最小化(默认关闭所有权限,按需开启)permissions: contents: read # 读取代码 packages: write # 推送到 GHCR(仅需要时) id-token: write # OIDC(仅需要时)
# 使用 SHA 固定 Action 版本(防止供应链攻击)# 不推荐:uses: actions/checkout@v4# 推荐:固定到具体 commit SHA- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
# 避免在 run 步骤中直接使用用户输入# 错误:run: echo "${{ github.event.pull_request.title }}" # 注入风险!# 正确:- name: Safe echo env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo "$PR_TITLE" # 通过环境变量传递,避免 shell 注入
# 为 Fork PR 限制 Secrets 访问- name: Skip for forks if: github.event.pull_request.head.repo.full_name == github.repository run: echo "Only runs for PRs from same repo"九、常用技巧速查
# ── 条件执行 ───────────────────────────────────────────────────- run: deploy.sh if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# ── 并行 Job(默认就是并行)────────────────────────────────────jobs: job-a: runs-on: ubuntu-latest steps: [...] job-b: runs-on: ubuntu-latest steps: [...] # job-a 和 job-b 并行运行
# ── Job 间传递数据(outputs)──────────────────────────────────jobs: build: outputs: version: ${{ steps.get-version.outputs.version }} steps: - id: get-version run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT deploy: needs: build steps: - run: echo "Deploy version ${{ needs.build.outputs.version }}"
# ── 获取 PR 信息 ────────────────────────────────────────────────- name: Comment on PR uses: actions/github-script@v7 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: '✅ 测试通过,可以合并!' })
# ── 上传/下载构建产物 ──────────────────────────────────────────- uses: actions/upload-artifact@v4 with: name: dist-files path: dist/ retention-days: 7- uses: actions/download-artifact@v4 with: name: dist-files path: dist/
# ── 设置动态环境变量 ────────────────────────────────────────────- name: Set version run: | VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0") echo "VERSION=$VERSION" >> $GITHUB_ENV # 后续步骤可用 ${{ env.VERSION }} echo "BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV相关文章:
- Docker 完全指南 2026:Compose 多服务编排与生产部署
- Linux 服务器监控完全指南 2026:Prometheus + Grafana + Alertmanager
- Kubernetes 入门完全指南 2026:核心概念 + kubectl + Helm
- Git 进阶实战完全指南 2026:hooks + 工作流 + 多账号
- Nginx 进阶完全指南 2026:反向代理 + HTTPS + 负载均衡
本文基于 GitHub Actions 2026年8月最新功能编写。Actions 版本(
actions/checkout@v4等)以当前最新版为准,推荐生产环境固定至 SHA 版本号以防供应链攻击。
GitHub Actions CI/CD 完全指南 2026:自动化构建 + 测试 + 部署实战
https://971918.xyz/posts/docs/github-actions-cicd-guide/