第 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@v4Node 缓存:
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 / pr | push.branches / pull_request.branches |
| tag | tags: ['v*.*.*'] |
| 定时 | schedule: - cron: ... |
| 手动 | workflow_dispatch |
| 操作 | 关键 step |
|---|---|
| 拉代码 | actions/checkout@v4 |
| 跑测试 | mvn test |
| 推镜像 | docker/build-push-action@v6 |
| 部署 | SSH 或 kubectl set image |
动手练习
- 给自己的项目写
.github/workflows/ci.yml,推到 main 触发自动测试 - 加 Docker Hub 推送,验证镜像能
docker pull - 配 matrix 测 Java 17 和 21 两个版本
- 加 SSH 部署 step,服务器上能看到容器跑起来
下一章:第 9 章:监控告警 →