公司团队较小,没运维、没测试人员,每一次的版本发布都是一次次胆颤,生怕操作失误导致问题,后来为了不出错就将命令整理到了文档,一行一行执行,再后来将命令编写成为了bash脚本,ssh 登陆服务器手动执行,随着业务不断变化,服务器也有10来台了,这样也不是可靠的办法。

于是在2021年就借助 gitlab 生态,全服务全项目都上 gitlab CI/CD,每次打版打一个 tag 即可完成自动发版,根本上解决问题,避免失误,本文分享 Gitlab CI/CD 实现 Go 项目部署。

images

  • PHP 项目 Gitlab CI/CD 参见链接:
  • 前端 Vue3、React 项目 Gitlab CI/CD 参见链接:

1. Go 项目手动发版

要讲 gitlab ci/cd 发布之前,我们先梳理一遍手动发布 Go 应用流程,常规流程如下:

  1. 本地开发机打包出服务器二进制文件
  2. 使用 scp 或 ftp 工具上传至服务器
  3. 启动新服务替换旧服务

2. Gitlab Runner 构建二进制

使用 CI/CD 流程实现其实也就是将以上手动的操作交给 Gitlab Runner 执行,这里不再讲解有关 Runner 的安装参考官方文档 ,Glitab 的 Runner 通过编写 .gitlab-ci.yml 文件,

 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
51
52
53
54
55
56
57
58
59
stages:
  - build
  - deploy
build:
  image: golang:1.14
  only:
    refs:
      - develop
      - tags
  stage: build
  tags:
    - backend
  script:
    - GOPROXY="https://goproxy.cn" GOOS=linux go build -ldflags "-s -w -X main.build=`date -u '+%Y-%m-%d_%I:%M:%S%p'` -X main.version=$CI_COMMIT_REF_NAME" -o bi server.go
  artifacts:
    name: "$CI_COMMIT_REF_SLUG"
    when: on_success
    expire_in: 1 week
    paths:
      - ./bi

deploy:preview:
  image: itbing/rsync:2.1
  stage: deploy
  tags:
    - backend
  when: on_success
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" >> ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - rsync -rav -e "ssh -p $SSH_PORT -o StrictHostKeyChecking=no" --delete bi "$PREVIEW_SERVER_USER"@"$PREVIEW_SERVER":"$PREVIEW_PROJECT_PATH"
    - ssh -p "$SSH_PORT" -o StrictHostKeyChecking=no "$PREVIEW_SERVER_USER"@"$PREVIEW_SERVER" "systemctl restart bi"
  only:
    refs:
      - develop
  dependencies:
    - build

deploy:production:
  image: itbing/rsync:2.1
  stage: deploy
  tags:
    - backend
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" >> ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - rsync -rav -e "ssh -p $SSH_PORT -o StrictHostKeyChecking=no" --delete bi "$PRODUCTION_SERVER_USER"@"$PRODUCTION_SERVER":"$PRODUCTION_PROJECT_PATH"
    - ssh -p "$SSH_PORT" -o StrictHostKeyChecking=no "$PRODUCTION_SERVER_USER"@"$PRODUCTION_SERVER" "systemctl restart bi"
  when: on_success
  only:
    refs:
      - tags
  dependencies:
    - build
  environment:
    name: production
    url: http://bi.shenjumiaosuan.com

下面是对这个配置文件的解释:

  1. stages:定义了流水线中的不同阶段,包括 builddeploy

  2. build 阶段:

    • image:使用的 Docker 镜像,这里是 golang:1.14
    • only:指定只有在指定的 refs(引用)下触发构建,这里是 develop 分支和标签。
    • stage:任务所属的阶段,这里是 build
    • tags:指定运行任务的 runner(执行者),这里是 backend
    • script:执行的脚本命令,用于构建应用程序。
    • artifacts:指定构建成功后产生的构件,这里是将生成的二进制文件命名为 $CI_COMMIT_REF_SLUG 并保存到路径 ./bi,并设置了过期时间为一周。
  3. deploy:preview 阶段(部署测试环境):

    • image:使用的 Docker 镜像,这里是 itbing/rsync:2.1
    • stage:任务所属的阶段,这里是 deploy
    • tags:指定运行任务的 runner,这里是 backend
    • when:指定任务触发的条件,这里是在 build 阶段成功后触发。
    • script:执行的脚本命令,用于将构建好的二进制文件部署到预览环境,并重启服务。
    • only:指定只有在 develop 分支下触发部署。
    • dependencies:指定任务依赖的其他任务,这里依赖于 build 阶段。
  4. deploy:production 阶段(部署生产环境):

    • image:使用的 Docker 镜像,这里是 itbing/rsync:2.1
    • stage:任务所属的阶段,这里是 deploy
    • tags:指定运行任务的 runner,这里是 backend
    • script:执行的脚本命令,用于将构建好的二进制文件部署到生产环境,并重启服务。
    • when:指定任务触发的条件,这里是在标签推送后触发。
    • only:指定只有在标签推送时触发部署。
    • dependencies:指定任务依赖的其他任务,这里依赖于 build 阶段。
    • environment:指定部署的环境信息,包括环境名称和 URL。

3 Gitlab Runner 构建 Docker 镜像

这种方式的与上面方式不同的地方在于,Runner 将打包后二进制文件,上传到 docker hub,然后 ssh 登录到远端服务器,拉取 docker image 启动服务,以下是 yml 配置,

3.1 Gitlab YML 文件

gitlab-ci.yml 文件配置参考

  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
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
# You can copy and paste this template into a new `.gitlab-ci.yml` file.
# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
#
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml

variables:
  CI_REGISTRY: docker.io
  CI_REGISTRY_NAMESPACE: itbing
  CI_REGISTRY_BASE_URL: index.docker.io
  CI_CONTAINER_REGISTRY: app_client
  CI_DOCKER_IMAGE_TAG: $CI_COMMIT_REF_NAME  # 使用 Git 分支名称作为镜像标签


stages:
  #  - test
  - build
  - deploy

#format:
#  image: golang:latest
#  stage: test
#  tags:
#    - backend
#  only:
#    refs:
#      - dev
#      - tags
#  script:
#    - go env -w GOPROXY=https://goproxy.cn,direct
#    - go mod tidy
#    - go fmt $(go list ./... | grep -v /vendor/)
#    - go vet $(go list ./... | grep -v /vendor/)
##    - go test -race $(go list ./... | grep -v /vendor/)

compile:
  stage: build
  tags:
    - shell
  only:
    refs:
      - dev
      - tags
  script:
    - docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" $CI_REGISTRY  # 登录 Docker Hub
    - docker build --pull -t $CI_REGISTRY_NAMESPACE/$CI_CONTAINER_REGISTRY:$CI_DOCKER_IMAGE_TAG -f Dockerfile --build-arg CI_JOB_TOKEN=$PERSONAL_TOKEN .
    - docker push $CI_REGISTRY_NAMESPACE/$CI_CONTAINER_REGISTRY:$CI_DOCKER_IMAGE_TAG # 推送 Docker 镜像
    - docker image prune -f # 清除未使用的镜像

staging:deploy:
  image: itbing/rsync:2.1
  stage: deploy
  tags:
    - backend
  only:
    refs:
      - dev
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" >> ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh -o StrictHostKeyChecking=no "$PREVIEW_SERVER_USER"@"$PREVIEW_SERVER" "cd $PREVIEW_PROJECT_PATH && docker-compose down --rmi all && docker-compose up -d"
  when: on_success
  dependencies:
    - compile  # 设置依赖关系,确保 compile 作业成功后再执行 staging:deploy
  environment:
    name: test
    url: https://crm.sjmsdev.cn


sh:prod:deploy:
  image: itbing/rsync:2.1
  stage: deploy
  tags:
    - backend
  only:
    refs:
      - tags
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" >> ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh -o StrictHostKeyChecking=no "$PRODUCTION_SERVER_USER"@"$PROD_SH_HOST_202" "cd $PRODUCTION_PROJECT_PATH && docker-compose down --rmi all && docker-compose up -d"
  when: on_success
  #  dependencies:
  #    - compile  # 设置依赖关系,确保 compile 作业成功后再执行 production:deploy
  environment: production

jp:prod:deploy:
  image: itbing/rsync:2.1
  stage: deploy
  tags:
    - backend
  only:
    refs:
      - tags
  script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" >> ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh -o StrictHostKeyChecking=no "$PRODUCTION_SERVER_USER"@"$PROD_JP_HOST_94" "cd $PRODUCTION_PROJECT_PATH && docker-compose down --rmi all && docker-compose up -d"
  when: on_success
  #  dependencies:
  #    - compile  # 设置依赖关系,确保 compile 作业成功后再执行 production:deploy
  environment: production

Runner 所在机器每次构建 Docker 都会产生临时镜像,记得构建完成后及时清理无用镜像,否则磁盘很容易爆满,清理命令:docker image prune -f

3.2 Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
FROM itbing/golang:1.21 AS compiler-stage
ARG CI_JOB_TOKEN
RUN echo -e "machine gitlab.shenjumiaosuan.com\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc
WORKDIR /opt
COPY . .
RUN go env -w GOPROXY=https://goproxy.cn,direct
#RUN go fmt $(go list ./... | grep -v /docs/)
#RUN go vet $(go list ./... | grep -v /docs/)
#RUN go test -race $(go list ./... | grep -v -E '(vendor|docs|data)')
RUN go mod tidy && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o app_client cmd/main.go

FROM ubuntu:latest AS build-stage
RUN apt-get -qq update \
    && apt-get -qq install -y --no-install-recommends ca-certificates curl
COPY --from=compiler-stage /opt/app_client /opt
WORKDIR /opt

CMD [ "/opt/app_client" ]

配置解释:

早期因为这个项目引用 Gitlab 私有包,为了项目在构建时能 download 下来 gitlab 上的私有包,所以在使用了自制构建镜像 itbing/golang:1.21,目的是通过 gitlab token 拉取 gitlab 上私有包,如果没有这个需求可以使用 Go 官方 image。

Gitlab 构建 Go 项目私有化包[[go-gitlab-private-package]]。

3.3 Docker compose YML

docker-compose.yml 配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
version: '3'
services:
  go_client:
    image: itbing/app_client:dev
    container_name: go_client
    ports:
      - "8089:8089"
    volumes:
      - ./logs:/opt/storage
      - ./config.yml:/opt/config/local.yml

参考文献