Go 项目使用 Gitlab 生态构建镜像
前言⌗
Go 项目的构建方式有很多,如何能找到一种适合自己团队的呢?
今天分享的就是我自己摸索出来的一些经验,通过 Gitlab 的强大生态,让 Go 项目从开发到部署都变得更加高效!
环境⌗
为了本次实验,我在本地使用 Docker 跑了如下服务:
- Traefik: 用于服务自动发现并反向代理
- Gitlab: 用于测试 CI/CD 的流程(需开启 Container Registry)
- Gitlab Runner: 用于执行 CI/CD 的任务
Traefik 和 Gitlab 的部署,可以参考我之前两篇文章,里面有详细的部署方案!
域名⌗
- gitlab.test
- registry.test
因为是内网测试环境,所以所有服务的 HTTPS 证书都是使用 mkcert 自签名的,这也为接下来的基础设施搭建挖了很大一个坑,但通过爬坑也掌握了更多原理!
注册 Runner⌗
在最新版的 Gitlab 中可以访问 https://gitlab.test/admin/runners
进入 Runner 的管理页面:
- 进入后点击
New instance runner
按钮进行创建 - 填写 Tags 以及描述信息,然后点击
Create runner
- 保存好 Gitlab-Runner 的注册 Token
接下来编写 Gtilab Runner 的配置文件以和 docker-compose.yaml
文件:
tree
.
├── README.md
├── config
│ ├── certs
│ │ └── ca.crt # 自签名证书的 CA 证书
│ └── config.toml # Gitlab Runner 配置文件
└── docker-compose.yml
3 directories, 4 files
config/config.toml
文件:
user = "gitlab-runner"
log_level = "error"
log_format = "text"
concurrent = 5
check_interval = 10
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
id = 1
url = "https://gitlab.test"
name = "docker-runner"
token = "glrt-fgsyprdA3oxDJzxQJxbB"
executor = "docker"
cache_dir = "/cache"
builds_dir = "/builds"
token_obtained_at = 2023-11-22T06:12:41Z
token_expires_at = 0001-01-01T00:00:00Z
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
image = "docker:latest"
volumes = ["/cache", "/builds", "/var/run/docker.sock:/var/run/docker.sock:ro"]
shm_size = 0
privileged = true
tls_verify = false
extra_hosts = ["gitlab.test:10.8.10.254", "registry.test:10.8.10.254"]
network_mtu = 0
pull_policy = ["if-not-present"]
network_mode = "traefik"
tls_cert_path = "/certs"
disable_cache = false
oom_kill_disable = false
allowed_services = ["docker:*"]
services_privileged = true
allowed_pull_policies = ["if-not-present"]
disable_entrypoint_overwrite = false
- 在 13,15 行将前面获得的 URL 和 Token
- 在 25 行将
/var/run/docker.sock:/var/run/docker.sock:ro
加入volumes
- 在 29 行设置自定义 TLD 的解析,而
10.8.10.254
这个 IP 是我在 traefik 网络中手动为 Traefik 设置的 IP
docker-compose.yaml
文件:
services:
runner:
image: gitlab/gitlab-runner:latest
restart: always
hostname: runner
container_name: runner
extra_hosts:
- gitlab.test:10.8.10.254
volumes:
- ./config:/etc/gitlab-runner
- runner-cache:/cache
- runner-builds:/builds
- /var/run/docker.sock:/var/run/docker.sock
environment:
TZ: PRC
CA_CERTIFICATES_PATH: /etc/gitlab-runner/certs/ca.crt
CI_SERVER_TLS_CA_FILE: /etc/gitlab-runner/certs/ca.crt
volumes:
runner-cache:
name: runner-cache
runner-builds:
name: runner-builds
networks:
traefik:
external: true
- 7-8 行为 Gitlab Runner 设置 HOST 解析
- 16-17 行为 Gitlab Runner 请求 https://gitlab.test 域名设置信任证书!
docker compose up -d
[+] Building 0.0s (0/0) docker:orbstack
[+] Running 1/1
✔ Container runner Started
成功后前往 Gitlab Admin 的 Runner 页面查看 Runner 是否正常:
项目适配⌗
为了模拟真实开发环境,我创建了两个项目组和两个项目,分别是:
主要的配置都在 services/sendbox
这个项目中,其目录结构如下:
tree -a -I '.git|.idea'
.
├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── LICENSE
├── README.md
├── cmd
│ ├── root.go
│ └── serve.go
├── go.mod
├── go.sum
├── go.work
├── go.work.sum
└── main.go
2 directories, 12 files
我在本地使用了 Go Workspace 来引入本地的包:
cat go.work
go 1.21.4
use .
replace gitlab.test/modules/mail => ../modules/mail
Dockerfile⌗
FROM golang:latest AS compiler-source-code
ARG VERSION=latest
ARG CI_JOB_USER=gitlab-ci-token
ARG GITLAB_HOST=gitlab.test
ARG CI_JOB_TOKEN
ARG CA_CERTIFICATE
WORKDIR /go/src/sendbox
RUN echo "machine gitlab.test login $CI_JOB_USER password $CI_JOB_TOKEN" >> ~/.netrc && \
echo $CA_CERTIFICATE >> /usr/local/share/ca-certificates/ca.crt && \
update-ca-certificates
ADD . /go/src/sendbox
RUN go env -w GOPRIVATE=gitlab.test && go mod tidy && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-s -w -X gitlab.test/services/sendbox/cmd.version=$VERSION -X gitlab.test/services/sendbox/cmd.commit=$(git rev-parse HEAD)" -o sendbox main.go
FROM ubuntu:latest AS build-docker-image
COPY --from=compiler-source-code /go/src/sendbox/sendbox /usr/local/bin/sendbox
ENTRYPOINT ["/usr/local/bin/sendbox"]
- 第 10 行: 将身份认证信息写入
~/.netrc
- 第 11 行: 将自签名 CA 证书写入系统中(非自签名证书,或者不适用 HTTPS 的可以删除这两行)
- 第 12 行: 更新系统信任 CA 证书(非自签名证书,或者不适用 HTTPS 的可以删除这两行)
- 第 14 行: 使用
-ldflags
将版本信息写入项目的变量中,后期做日志分析及版本稳定性分析可能会用到
在构建 Docker 镜像过程中,可以加入基于环境拉取不同私有包的设置。例如在测试环境,可以在执行构建前,先执行
go get gitlab.test/modules/mail@develop
,来拉取 develop 分支最新的 Commit,避免私有包打了 Tag 后,测试环境出现问题,又要多次发布私有包!
CI/CD 配置⌗
这里需要注意一下,最新版的 Gitlab 默认使用 .yml
后缀,所以如果要使用 .yaml
会导致 CI/CD 无法获取。如果一定要使用后者的话,可以在 Gitlab 中进行设置:
在上图中
Default CI/CD configuration file
下方修改默认 CI 配置文件名称即可!
.gitlab-ci.yml
配置如下:
default:
image: docker:latest
before_script:
- export # 不需要打印所有环境变量的话可以删除
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
variables:
GITLAB_IP: 10.0.6.3
CI_JOB_USER: gitlab-ci-token
GITLAB_HOST: gitlab.test
CA_CERTIFICATE: $CA_CERTIFICATE
CONTAINER_VER_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
CONTAINER_LATEST_TAG: $CI_REGISTRY_IMAGE:latest
stages:
- build
- release
build:
tags:
- docker
stage: build
script:
- docker build --add-host gitlab.test:"$GITLAB_IP" --pull --tag "$CONTAINER_VER_TAG" --build-arg VERSION="$CI_COMMIT_REF_SLUG" --build-arg CI_JOB_USER="$CI_JOB_USER" --build-arg GITLAB_HOST="$GITLAB_HOST" --build-arg CI_JOB_TOKEN="$CI_JOB_TOKEN" --build-arg CA_CERTIFICATE="$CA_CERTIFICATE" .
- docker push $CONTAINER_VER_TAG
release:
only:
refs:
- tags
tags:
- docker
stage: release
script:
- docker pull $CONTAINER_VER_TAG
- docker tag $CONTAINER_VER_TAG $CONTAINER_LATEST_TAG
- docker push $CONTAINER_LATEST_TAG
Gitlab CI 预设环境变量:
- CI_REGISTRY: Container Registry HOST,这里对应的就是
registry.test
- CI_REGISTRY_USER: gitlab-ci-token
- CI_REGISTRY_PASSWORD: 值与
CI_JOB_TOKEN
一致,如需操作其他项目的 Image,参考下面的权限问题
章节 - CI_REGISTRY_IMAGE: 根据项目的命名空间自动生成的,例如我这里是: registry.test/services/sendbox
- CI_COMMIT_REF_NAME: 提交的名称,如果是分支就是分支名称,如果是 Tag 就是对应的版本,如: master,develop 或 v1.0.0
如果直接将 CI_COMMIT_REF_NAME 用于 Image Tag 可能存在问题,例如当提交的分支是 feature/auth,那么生成的 Image Tag 就是
registry.test/services/sendbox:feature/auth
,这显然不符合 Tag 的命名规范,所以这里可以使用另一个环境变量CI_COMMIT_REF_SLUG
,他会将非字母和数字的字符替换为 Slug,例如将 feature/auth 替换为 feature-auth!
更多预设环境变量可以参考 Gitlab 官方文档 GitLab CI/CD variables。
- 第 4 行: 用于打印出 Gitlab CI 预设的所有环境变量,如果你不想去看文档的话可以执行
export
,其中敏感信息会被标记为[MASKED]
- 第 26 行:
--add-host gitlab.test:"$GITLAB_IP"
这个如果 gitlab 不是在本地的话不用配置,因为我这里使用 dnsmasq 将 *.test 这个 TLD 都解析成了 127.0.0.1,这就导致 buildx 中访问 gitlab.test 最终请求的 IP 是 127.0.0.1:443! - 第 36-37 行: 再为最新版本打上 latest 标签
权限问题⌗
如果你使用 Personal Token 作为 CI/CD 中的身份认证的话,那么需要这个 Personal Token 的所有者拥有私有包的仓库访问权限。
我这里使用的是 CI_JOB_TOKEN
,这个是 Pipeline 生命周期中有效的 Token,并且在 Gitlab 15.9
版本之后,为了安全考虑,不允许跨项目访问。也就是说,在 services/sendbox
项目的 CI/CD 中使用 CI_JOB_TOKEN
拉取 modules/mail
这个私有包的时候,会认证失败,报错提示如下面图中 233 行所示:
出现这个问题你可以选择关闭 Limit Access to the project
功能,或者将要访问的项目添加到下面的 Allow CI job tokens from the following projects to access this project
列表中:
这样再次执行就可以成功获取私有包的代码了!
总结⌗
到这里项目的构建与分发问题已经很好的解决了,这种 Overwrite DNS + Self-signed 的证书,真的是能折腾死人!
不过经历最艰难的过程后,能让我更加了解 Gitlab 生态中的服务相互间是如何交互的,焉知非福吧。
后面会再分享一下关于 CD 的最佳实践,敬请期待吧…
I hope this is helpful, Happy hacking…