通过 Github Action 发布 SpringBoot Docker 项目到云服务器

一 本文目的 记录如何通过 Github Action 实现 SpringBoot 项目的自动化发布。其中 SpringBoot 项目通过 Docker 进行管理。
二 Github Action 需要的主要步骤

  1. 设置 Java 环境并通过 Maven 进行项目构建
  2. 构建 Docker 镜像并推送到私有镜像仓库
  3. 登录远程服务器,拉取镜像并重启服务
三 设置 Java 环境并通过 Maven 进行项目构建 这里使用 JDK 11 并设置 maven 缓存加快构建时间。其中 -DskipTests=true 表示构建时跳过测试。如果需要测试可以删掉这个参数。
- name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: maven- name: Build with Maven run: mvn -B -DskipTests=true package --file pom.xml

四 构建 Docker 镜像并推送到私有镜像仓库 这里主要解决的问题是怎么选择 Docker 私有镜像,选择有下
名称 优点 缺点
Github Package 免费,和 Github 高度集成,配置方便 我国大陆云服务器连接 Github 太 TM 慢
Docker 官方 Docker 官方,值得信赖,大陆云服务器连接速度 OK 只支持一个私有镜像仓库,多了要钱
Coding.net 大陆云服务器连接速度快,免费,被腾讯收了,服务应该不会中断 配置略微复杂但可以接受
我最终的选择是 Coding.net,但如果云服务器允许,我会选择 Github Package。
4.1 Coding.net 私有镜像仓库
4.1.1 通过 Coding.net 创建私有镜像仓库如下:
  1. 详见项目,比如选择项目名叫 docker-image
  2. 在项目中创建 “制品仓库”,制品类型选择 docker,然后起名,设置权限(可以默认)就 OK
创建制品 设置制品信息
通过 Github Action 发布 SpringBoot Docker 项目到云服务器
文章图片
通过 Github Action 发布 SpringBoot Docker 项目到云服务器
文章图片
4.1.2 推送、拉取 Docker 镜像: 一般通过两种途径:命令行或者 CICD 工具。不过哪种,都需要先登录到有项目操作权限的账号。
登录 点击操作指引:
通过 Github Action 发布 SpringBoot Docker 项目到云服务器
文章图片

获取登录令牌:
通过 Github Action 发布 SpringBoot Docker 项目到云服务器
文章图片

docker login \ -u random-user-name \ -p random-passworkd \ coding-username-docker.pkg.coding.net

这里注意,虽然生成的是随机 username 和 password,但确实长期有效,所以可以放在 Gihub repo 的 secrets 中供 Github Action 使用。
推送/拉取镜像 推送:
docker push \ coding-username-docker.pkg.coding.net/docker-image/test-service/:

推送前确保已经有了这个 coding-username-docker.pkg.coding.net/docker-image/test-service/: 这个 tag,没有的话要打标签。
docker tag \ coding-username-docker.pkg.coding.net/docker-image/test-service/:

拉取:
docker pull \ coding-username-docker.pkg.coding.net/docker-image/test-service/:

4.1.3 在 Github Action 中设置 Coding.net 私有镜像仓库
# 登录 - name: Log in to the Coding docker registry uses: docker/login-action@v1 with: registry: ${{ env.REGISTRY }} # REGISTRY 为 GHA 环境变量 username: ${{ secrets.CODING_USER }} # coding.net 的 random-user-name,设置在 GHA 的 secrets 中 password: ${{ secrets.CODING_TOKEN }} # coding.net 的 random-password# 设置 image 名称 - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v3 with: images: ${{ env.IMAGE_NAME_TOTAL_NAME }}# 推送 - name: Build and push Docker image uses: docker/build-push-action@v2 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}

Docker tags 设置规则可以参考 docker/metadata-action,最基本规则如下
Event Ref Docker tags
push refs/heads/main main
push refs/heads/releases/v1 releases-v1
push tag refs/tags/v1.2.3 v1.2.3, latest
【通过 Github Action 发布 SpringBoot Docker 项目到云服务器】这里注意,如果是 Push Github tag,那么会默认生成两个 Docker tag(Perfect!)。
如果我们想要 push tag 后触发 GHA,需要在 workflow 中添加
on: push: tags: # tags 更新时触发 workflow - 'v*'

4.2 Github Package 私有镜像仓库
首先 Github Package 不一定是 Docker 镜像,可以去官网了解更多,这里只介绍利用 Github package 配合 Github 的 Container registry 创建私有 Docker 镜像仓库。
完整步骤可以参考 Container registry,流程和 Coding.net 类似:登录后然后推送、拉取。
4.2.1 登录 Container registry 推荐使用 GITHUB_TOKEN,在 Settings/Developer settings/Personal access tokens 中设置:
通过 Github Action 发布 SpringBoot Docker 项目到云服务器
文章图片

这里要记得把 Token 保存下来,不然后面就找不到了。
获取 Token 后登录到 Contianer Registry
$ export CR_PAT=YOUR_TOKEN # 存储 token 到变量 CR_PAT 中 $ echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin > Login Succeeded

4.2.2 Push/Pull 镜像
# push $ docker push ghcr.io/OWNER/IMAGE_NAME:TAG# pull docker pull ghcr.io/OWNER/IMAGE_NAME:TAG

4.2.3 GHA 中的设置 可以直接用 github 本身变量,比如 github.actor 作为 username。和 coding.net 重复部分不再赘述。
- name: Log in to the Container registry uses: docker/login-action@v1 with: registry: ${{ env.REGISTRY }} # 为 ghcr.io username: ${{ github.actor }} # 可以直接读取当前 Github 触发事件的用户 password: ${{ secrets.GITHUB_TOKEN }}- name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v3 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # IMAGE_NAME 为 ${{ github.repository }}- name: Build and push Docker image uses: docker/build-push-action@v2 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}

五 登录远程服务器,拉取镜像并重启服务 这里使用了 appleboy/ssh-action,它的有点事可以直接在 GHA workflow 中配置登录远程服务器后需要执行的脚本。
- name: executing remote ssh commands using password uses: appleboy/ssh-action@master with: host: ${{ secrets.REMOTE_HOST }} username: ${{ secrets.REMOTE_USER }} # 通过 ssh key 登录 key: ${{ secrets.REMOTE_ACCESS_TOKEN }} script: | docker login -u ${{ secrets.CODING_USER }} -p ${{ secrets.CODING_TOKEN }} ${{ env.REGISTRY }} docker pull ${{ env.IMAGE_TOTAL_NAME }}:${{ env.IMAGE_TAG }} docker stop ${{ env.CONTAINER_NAME }} docker rm ${{ env.CONTAINER_NAME }} docker run -d \ --name ${{ env.CONTAINER_NAME }} \ -p ${{ secrets.EXPOSED_PORT }}:${{ secrets.EXPOSED_PORT }} \ --link ${{ secrets.DATABASE_CONTAINER_NAME }}:${{ secrets.DATABASE_CONTAINER_NAME }} \ ${{ env.IMAGE_TOTAL_NAME }}:${{ env.IMAGE_TAG }}

这里注意几点:
  1. 使用 ssh key 登录远程服务器,主要是 key 不是 password;
  2. 建议直接通过 命令行工具 把所有 key 文件内容复制到剪贴板,然后直接复制到 Github repo 的 secrets 中(如果手动复制,记得把注释也放进去):
# mac 下操作 pbcopy < you_key_file

六 完整 Github Actions YAML 文件
name: GHA CI# Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the main branch push: # branches: [ main ] tags: # tags 更新时触发 workflow,这里仅仅使用 tag 管理发布 - 'v*' # pull_request: # branches: [ main ]# Allows you to run this workflow manually from the Actions tab workflow_dispatch:env: IMAGE_TOTAL_NAME: ${{ secrets.CODING_REGISTRY }}/docker-image/${{ github.repository }}/${{ github.repository }} IMAGE_TAG: latest CONTAINER_NAME: ${{ github.repository }}# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: build-and-deploy: runs-on: ubuntu-lateststeps: - uses: actions/checkout@v3# maven build - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn -B -DskipTests=true package --file pom.xml# coding.net docker repo - name: Log in to the Coding docker registry uses: docker/login-action@v1 with: registry: ${{ secrets.CODING_REGISTRY }} username: ${{ secrets.CODING_USER }} password: ${{ secrets.CODING_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v3 with: images: ${{ env.IMAGE_TOTAL_NAME }} - name: Build and push Docker image uses: docker/build-push-action@v2 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }}# pull new image and restart service - name: executing remote ssh commands using password uses: appleboy/ssh-action@master with: host: ${{ secrets.REMOTE_HOST }} username: ${{ secrets.REMOTE_USER }} key: ${{ secrets.REMOTE_ACCESS_TOKEN }} script: | docker login -u ${{ secrets.CODING_USER }} -p ${{ secrets.CODING_TOKEN }} ${{ secrets.CODING_REGISTRY }} docker pull ${{ env.IMAGE_TOTAL_NAME }}:${{ env.IMAGE_TAG }} docker stop ${{ env.CONTAINER_NAME }} docker rm ${{ env.CONTAINER_NAME }} docker run -d \ --name ${{ env.CONTAINER_NAME }} \ -p ${{ secrets.EXPOSED_PORT }}:${{ secrets.EXPOSED_PORT }} \ --link ${{ secrets.DATABASE_CONTAINER_NAME }}:${{ secrets.DATABASE_CONTAINER_NAME }} \ ${{ env.IMAGE_TOTAL_NAME }}:${{ env.IMAGE_TAG }}

    推荐阅读