Skip to content
第 8 章 运维 ⏱ 13 分钟阅读

第 8 章:CI/CD(GitHub Actions) ​

学习目标 ​

  • 编写 GitHub Actions 工作流
  • 自动跑测试 + 构建镜像 + 推送
  • 自动部署到服务器
  • 避开 Secret 泄漏和缓存失效坑

一、最小 workflow ​

yaml
# .github/workflows/ci.yml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: 17
          distribution: temurin
          cache: maven                    # 自动缓存 ~/.m2

      - name: Build with Maven
        run: mvn -B -ntp package -DskipTests

      - name: Run tests
        run: mvn -B -ntp test

推上 GitHub → Actions 标签页自动跑。

⚠️ 坑 1:on: pull_request 默认不会触发 push 后的合并后再次跑。如果 pr 合并 main 后还想跑测试,把 on: 加 push: branches: [main]。 ⚠️ 坑 2:自己 fork 仓库触发 pr 时,GITHUB_TOKEN 是只读权限,不能写回仓库。需要写权限配 PAT。

二、构建镜像推送到 Docker Hub ​

yaml
name: Build and Push

on:
  push:
    branches: [main]
    tags: ["v*.*.*"]                     # tag 触发

env:
  IMAGE_NAME: ${{ secrets.DOCKERHUB_USER }}/myapp

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: 17
          distribution: temurin
          cache: maven

      - name: Build with Maven
        run: mvn -B -ntp package -DskipTests

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USER }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Extract version
        id: meta
        run: |
          if [[ "${{ github.ref_type }}" == "tag" ]]; then
            echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
          else
            echo "version=sha-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
          fi

      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: |
            ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
            ${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha             # 走 GitHub 缓存
          cache-to: type=gha,mode=max

需要的 Secret(Docker Hub → GitHub 仓库 Settings → Secrets):

DOCKERHUB_USER     # Docker Hub 用户名
DOCKERHUB_TOKEN    # Docker Hub Access Token(不是密码)

⚠️ 坑 3:把密码直接写密码框 → Secret 重命名后老名字还在。改 Secret 名后必须改 workflow 引用的 key,否则读不到。

三、缓存优化(必加) ​

Maven 缓存:

yaml
- uses: actions/setup-java@v4
  with:
    java-version: 17
    distribution: temurin
    cache: maven                    # 等价手写 actions/cache@v4

Node 缓存:

yaml
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: npm

手写自定义缓存:

yaml
- uses: actions/cache@v4
  with:
    path: |
      ~/.gradle
      ./build
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
    restore-keys: |
      ${{ runner.os }}-gradle-

四、定时任务(cron) ​

yaml
on:
  schedule:
    - cron: '0 2 * * *'             # 每天 UTC 2 点 = 北京时间 10 点

夜间 build 触发器、依赖更新、清理 cron:

yaml
- name: Dependabot auto-merge
  run: gh pr merge --auto --squash "$PR_URL"
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

⚠️ 坑 4:GitHub Actions 的 cron 是 UTC。定时任务想北京时间凌晨跑 0 2 * * * 实际是上午 10 点。要么自己换算,要么用 TZ 环境变量配套写脚本逻辑。

五、矩阵测试(multiple 版本) ​

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false               # 失败不中断其他
      matrix:
        java: [17, 21]
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: temurin
      - run: mvn -B -ntp test

六、SSH 部署到服务器 ​

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.DEPLOY_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd /opt/myapp
            IMAGE=${{ secrets.DOCKERHUB_USER }}/myapp:sha-${{ github.sha }}
            docker pull $IMAGE
            docker stop myapp || true
            docker rm myapp || true
            docker run -d --name myapp \
              --restart unless-stopped \
              -p 8080:8080 \
              -e SPRING_PROFILES_ACTIVE=prod \
              $IMAGE

需要的 Secret:

DEPLOY_HOST        # 1.2.3.4
DEPLOY_USER        # deploy
DEPLOY_SSH_KEY     # 私钥整个内容(包括 BEGIN/END 行)

⚠️ 坑 5:SSH 私钥如果不包含 \n 换行,粘到 Secret 会变一行。保留原始 PEM 格式,多行原样粘。多行秘密要 Multi-line 模式。

七、自动部署到 K8s(kubeconfig) ​

yaml
- name: Set up kubectl
  uses: azure/setup-kubectl@v4

- name: Set Kubeconfig
  run: |
    mkdir -p ~/.kube
    echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config

- name: Deploy
  run: |
    kubectl set image deploy/myapp web=${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
    kubectl rollout status deploy/myapp --timeout=5m

⚠️ 坑 6:KUBECONFIG 直接当 Secret 存可以,但权限太大。生产推荐服务账号 token + RBAC 最小权限,而不是 admin kubeconfig。

八、本章小结 ​

触发方式on:
push / prpush.branches / pull_request.branches
tagtags: ['v*.*.*']
定时schedule: - cron: ...
手动workflow_dispatch
操作关键 step
拉代码actions/checkout@v4
跑测试mvn test
推镜像docker/build-push-action@v6
部署SSH 或 kubectl set image

动手练习 ​

  1. 给自己的项目写 .github/workflows/ci.yml,推到 main 触发自动测试
  2. 加 Docker Hub 推送,验证镜像能 docker pull
  3. 配 matrix 测 Java 17 和 21 两个版本
  4. 加 SSH 部署 step,服务器上能看到容器跑起来

下一章:第 9 章:监控告警 →

本站基于 VitePress 构建 · 由 StackHub 团队维护