上一篇 『CI/CD』使用GitHub Actions实现简单的自动化部署 中实现了简单的自动化部署,本篇来介绍一下使用 docker 的做法 (NX 整整折腾了两天呢)


基本流程

首先要明确一下最终的运行流程,一共有4个步骤

  1. 更新代码,提交至GitHub
  2. GitHub 触发 Actions,自动 build 镜像,并推送至镜像仓库
  3. 服务器拉取最新镜像
  4. 重新启动容器

因为今天时间有点紧我就不画图了(明天赶回家,要收拾东西)


本地原生运行测试

首先第一步要介绍一下我们的项目,还是上次的仓库:https://github.com/NX-Official/github-actions-test ,当然啦,现在是 v3 分支

为了方便我本次使用一个 go-zero 的 demo,因为它来生成 Dockerfile 比较方便

首先和文档一样创建一个 hello 服务

1
goctl api new hello

为了模拟可能的业务场景,我现在给这个 demo 增加两个要求:

  • 我这个项目需要使用 mysql 和 redis 等服务,但是 docker 中的 mysql 会对性能造成影响,我可能需要直接使用宿主机上的端口与服务
  • 本地测试环境和实际环境有不同,我希望到时候能从宿主机的某个目录读取配置文件,而不是打包在一起

所以我在这个 dome 中连接了 127.0.0.1 的 mysql 和 redis 服务,并从文件中读取配置,你可以在项目中看见

image-20221214161805856

现在,我在 hello 目录下原生运行本项目,没有问题

1
go run hello.go

image-20221214161946849

image-20221214162038371

本地打包&运行测试

接下来,用 docker 打包试试

使用 go-zero 的 goctl 工具可以方便地将当前项目打包(如果你不是 go-zero 你就得自己写 Dockerfile 了,有关教程烂大街,不属于本节内容)

  • hello 目录下一键生成 Dockerfile
1
goctl docker -go hello.go

image-20221214163025558

为了方便与整洁,我选择使用 docker-compose 来构建运行,而不是在命令行手动折腾一堆参数

在项目根目录新建 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.5'

services:
hello-api:
build:
context: .
dockerfile: hello/Dockerfile
environment:
- TZ=Asia/Shanghai
privileged: true
ports:
- "8888:8888"
stdin_open: true
tty: true

然后运行,你就会发现

image-20221214163247625

在容器里是不能使用 127.0.0.1 来访问宿主机的本地服务的

这个问题可以看我的另一篇 『Docker』Docker内程序如何访问宿主机的端口 ,结论就是要把 127.0.0.1 换成 host.docker.internal

但是观察自动生成的 Dockerfile ,他是直接把配置文件打包进去了

所以要改一下,把那两行注释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk update --no-cache && apk add --no-cache tzdata

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
# COPY hello/etc /app/etc
RUN go build -ldflags="-s -w" -o /app/hello hello/hello.go


FROM scratch

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
ENV TZ Asia/Shanghai

WORKDIR /app
COPY --from=builder /app/hello /app/hello
# COPY --from=builder /app/etc /app/etc

CMD ["./hello", "-f", "etc/hello-api.yaml"]

然后运行时挂一下自己的配置文件,在根目录新建一个 etc 目录,在里面也弄一个 hello-api.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Name: hello-api
Host: 0.0.0.0
Port: 8888


DBList:
Mysql:
Address: host.docker.internal:3306
Username: root
Password: "12345678"
DBName: "test"
Redis:
Address: host.docker.internal:6379
Password:

然后修改 docker-compose.yml ,加上两行,把目录映射进去

1
2
volumes:
- ./etc:/app/etc

再运行,没有问题

image-20221214164728750


打包上传&本机重新拉取测试

现在尝试一下将这个 image 上传到托管仓库,再在服务器拉取看看能不能跑

综合考虑,打算使用阿里云的容器镜像服务

前往 https://cr.console.aliyun.com/cn-hangzhou/instances 创建一个个人实例

image-20221214170130504

然后随便创个命名空间

image-20221214170209112

再在这个空间里创一个仓库

image-20221214170259201

进入仓库可以看见一些提示

image-20221214121126876

我们现在需要将镜像推送至阿里云,所以需要按照下面的步骤

1
2
3
$ docker login --username=***** registry.cn-hangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/****/github-actions-test:[镜像版本号]
$ docker push registry.cn-hangzhou.aliyuncs.com/*****/github-actions-test:[镜像版本号]

image-20221214120857147

image-20221214121036852

然后我在这个测试项目中新建一个 assume_server_dir 目录,假设这里就是服务器上的目录

1
2
3
4
5
6
nx@NXsMacBook-Pro assume_server_dir % tree .
.
├── docker-compose.yml
└── etc
└── hello-api.yaml

这里的 docker-compose.yml 就不能这样写了,因为这里不是构建,而是拉取,所以指定镜像地址即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3.5'

services:
hello-api:
image: registry.cn-hangzhou.aliyuncs.com/****/github-actions-test:latest
environment:
- TZ=Asia/Shanghai
privileged: true
volumes:
- ./etc:/app/etc
ports:
- "8888:8888"
stdin_open: true
tty: true

之后把本地的容器、镜像全删掉,再运行这个 docker-compose

发现可以正常拉取镜像并运行

image-20221214173331865


服务器拉取测试

好了,下一步就是在服务器上拉取并测试了

但是有个问题,我用的 M1 的 MacBook ,虽然 docker 是跨平台的,但是我这里构建的镜像是 arm 架构的,服务器上拿到运行会这样

image-20221214153056667

所以我需要使用 docker buildx 重新编译并推送(参考文章:跨平台构建 Docker 镜像新姿势,x86、arm 一把梭

1
2
3
4
docker buildx create --use --name mybuilder
docker buildx inspect mybuilder --bootstrap
docker buildx ls
docker buildx build -t registry.cn-hangzhou.aliyuncs.com/*****/github-actions-test:latest -f hello/Dockerfile --platform=linux/amd64 . --push

image-20221214175803681

然后再在服务器拉取运行,但是又遇到了新的问题

image-20221214181100918

然后我去谷歌,算是解决了问题

image-20221214181431705

现在终于跑起来了

image-20221214182811973

GitHub Actions打包&服务器拉取运行

好了,现在手动把环境都打通了,轮到使用 GitHub Actions 来自动化了

先来一个只推送到阿里云的版本,测试一下效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
name: Build
on:
push:
branch:
- v3

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3

- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
# list of Docker images to use as base name for tags
images: ${{ secrets.IMAGE_URL }} # registry.cn-hangzhou.aliyuncs.com/xxxxx/xxxxx
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha

- name: Set up QEMU
uses: docker/setup-qemu-action@v1

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1

- name: Login to Aliyun Registry
uses: docker/login-action@v1
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}

- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: hello/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}

image-20221216143739869

可以看见已经成功推送,就是 tag 打的不是 latest,这个随便了,我们在服务器也改成 v3 的版本就可以了

现在随便改动一下项目

1
2
3
4
5
func (l *HelloLogic) Hello(req *types.Request) (resp *types.Response, err error) {
return &types.Response{
Message: "If you can see this message, it means that the service is running successfully.",
}, nil
}

然后增加重启的命令

1
2
3
4
5
6
7
8
- name: Pull and run
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
password: ${{ secrets.DEPLOY_SECRET }}
port: ${{ secrets.SSH_PORT }}
script: sudo docker login --username=${{ secrets.ALIYUN_REGISTRY_USERNAME }} -p ${{ secrets.ALIYUN_REGISTRY_PASSWORD }} registry.cn-hangzhou.aliyuncs.com && cd /home/admin/projects/github-actions-test && docker-compose pull && docker-compose up -d

image-20221216151529383

当然啦,除了直接执行命令之外,你还可以使用 webhook 来通知服务器拉取最新镜像

至此,完成自动化部署