go 持续集成、持续部署之gitlab流水线+docker-compose踩坑之旅

前言

在前面,我们已经能在开发服务器上运行docker-compose.prod.yml,实现在项目代码基础上实现两阶段构建(打包成二进制文件、运行),详见:go docker-compose对比开发环境配置的生产环境配置优化-CSDN博客。接下来,我们期望打包、部署配置在gitlab pipeline中运行,实现持续集成、持续部署。

项目架构

代码结构

源码

https://gitlab.zhangluyue.asia/go-web

演示

全流程演示

orm_framework、web_framework作为包依赖

orm_framework pipeline流程

tag job
  1. 创建zly_robot账号,提供developer权限,用于构建tag
  2. tag构建使用Semver规范
  3. 自动构建tag只构建patch(小版本)tag;如果需要更新major或者minor需要手动构建

创建机器人账号参与pipeline

为了保证项目的自动化和安全性,创建了一个机器人zly_robot账号设置有限的权限(developer)参与pipeline: 自动构建tag。

操作步骤

准备机器人账号邮箱(我用的qq邮箱zly_robot@qq.com) => 创建机器人gitlab账号 => 配置令牌 => 将令牌信息作为变量配置到项目variable中 => 编写pipeline job

创建robot实现自动生成tag

演示

pipeline 构建结果
business使用演示

yml模板文件

template-cicd与business、client的.gitlab-ci.yml的关系

项目构建

什么是构建

构建包含哪些步骤

构建报错

web-framework不需要main.go使用,但构建要求必须提供main.go,不然报错

我的项目构建

构建的具体操作,这里交给了Dockerfile去做;在gitlab pipeline主要需要考虑的就是代码推送镜像仓库,部署至指定服务器。

镜像仓库搭建,详见:docker docker、swarm 全流程执行-CSDN博客

预构建

我本来想创建预构建job,实现node_modules等依赖包的缓存,加快构建速度。但我现在使用Dockerfile参与构建,就可以直接使用docker的层缓存,无需自行编写。

废弃的pre-install-frontend: [jobs/废弃]pre-build-jobs.yml · main · go-web / template-cicd · GitLab

"COPY go.mod go.sum /" 利用docker层缓存

先拷贝go.mod、go.sum,然后下载依赖,再copy代码。因为依赖项变动的小,所以依赖不变的情况就可以利用go mod download的缓存,无需重新下载依赖

持续集成、持续部署过程中的问题解决

问题描述:gitlab由于硬盘满了导致频繁挂

页面错误信息:

复制代码
500: We're sorry, something went wrong on our end
Request ID: 01K5111E0B61GWE8JF8KGZ15SE

Try refreshing the page, or going back and attempting the action again.
Please contact your GitLab administrator if this problem persists.

推测并确认原因:gitlab 服务器的硬盘满了,导致gitlab运行失败;

排查:gitlab的主要内容都在/dev/sda3上,需要把/dev/sda3扩大

复制代码
df -h /var/opt/gitlab
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        18G   18G   20K 100% /
[root@localhost ~]# df -h /var/log/gitlab
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        18G   18G     0 100% /

解决方案:

复制代码
# cloud-utils-growpart比fdisk更简单
yum install -y cloud-utils-growpart
# 扩展指定磁盘的分区号
growpart /dev/sda 3

其他:我还删除了历史日志

复制代码
# 删除7天前的日志
sudo find /var/log/gitlab -type f -mtime +7 -delete
问题描述:磁盘错误挂载导致虚拟机无法启动,进入紧急模式

问题原因:完成上面的图片中的操作,扩展硬盘容量(扩展后效果如下图)后,我不知道如何扩大/dev/sad3(也就是图下面的代码步骤)。我想着怎么把多出来的devtmpfs、tmpfs等和gitlab-data数据link起来。就是说,/dev/sda3满了,我打算把gitlab-data通过link存到其他地方。

我的操作:(按照问AI)

复制代码
mkdir /gitlab-data
mount /dev/sdb1 /gitlab-data
# 设置开机自动挂载(添加到/etc/fstab)
echo "/dev/sdb1 /gitlab-data ext4 defaults 0 0" | sudo tee -a /etc/fstab

我以为新增了一个硬盘分区(我一开始不懂,我以为上面多出来的devtmpfs啥的是增加的),我把/gitlab-data挂载到了一个不存在的分区/dev/sdb1上。// 实际上,我是扩展了已有/dev/sda的容量。

计划后面迁移数据,创建软链:

复制代码
# 迁移数据目录(/var/opt/gitlab)
rsync -av /var/opt/gitlab/ /gitlab-data/var-opt-gitlab/
# 迁移日志目录(/var/log/gitlab)
rsync -av /var/log/gitlab/ /gitlab-data/var-log-gitlab/

# 创建软链接:让 GitLab 访问新目录(无需修改配置)
ln -s /gitlab-data/var-opt-gitlab /var/opt/gitlab
ln -s /gitlab-data/var-log-gitlab /var/log/gitlab

...

但中途,我重启了虚拟机,就启动失败了进入紧急模式:

在journalctl -xb日志中没有看到错误信息,通过思考想到是上面的操作步骤引入的错误。由于gitlab配置了开机启动,gitlab启动失败就导致开机失败。

问题修复:

复制代码
# 将根分区重新挂载为可写模式(默认可能是只读)
mount -o remount,rw /

# 删除上面添加的"/dev/sdb1 /gitlab-data ext4 defaults 0 0"
vim /etc/fstab

# 删除无用目录
rm -rf /gitlab-data

# 重启虚拟机

问题描述:gitlab job中尝试http登录镜像服务器失败

报错信息:

问题原因:job中使用了docker-in-docker服务,docker默认不支持http访问

image使用docker(而非alpine)保证环境一致性;内部使用docker-indocker服务原因:因为在docker job中需要使用docker build,不然需要手动安装docker-cli(image: alpine时)。

尝试配置允许http访问镜像服务器(配置无效):

DOCKER_TLS_CERTDIR:""(禁用TLS加密):

  • docker:dind镜像默认会启动TLS加密,在/certs目录生成证书,要求客户端通过https连接并验证证书。
  • 将该变量设为"",会禁用TLS加密,允许docker客户端通过未加密的HTTP协议与DinD服务通讯

DOCKER_HOST: tcp://docker:2375(指定DinD服务地址):

  • docker客户端默认连接本地的unix:///var/run/docker.sock套接字,但在DinD模式下,Docker守护进程运行在独立的docker:dind容器下(服务名默认是docker)
  • 该配置告诉Docker客户端:不要连接本地套接字,而是通过TCP连接到docker服务的2375端口(这是DinD服务暴露的非机密端口与DOCKER_TLS_CERTDIR:""配合使用)
解决方案

使用宿主机docker,不使用docker-in-docker方式 // 隔离性差点,但好使

宿主机配置:

修改/etc/docker/daemon.json允许http访问镜像仓库

修改/etc/gitlab-runner/config.toml:启动特权模式,映射docker.sock;然后重启gitlab runner

移除dind服务,就能实现使用宿主机的docker配置,能够http访问镜像仓库。

其他解决方案:在镜像仓库中配置https证书,允许https访问。

docker-in-docker相关文章:gitlab-runner 中的 Docker-in-Docker - 莱布尼茨 - 博客园

gitlab job语法和优化

rules: [when:never]不执行不相关template-job

由于模板文件中前后端job放在了同一个文件中,那么比如在前端.gitlab-ci.yml中需要配置一下保证后端template-job虽然引入,但不执行

if ! docker info > /dev/null 2>&1;含义

if ! docker info > /dev/null 2>&1; 用于检查docker服务是否可用。

> /dev/null 标准输出重定向:docker info会输出大量信息,> /dev/null 将输出内容丢弃(不显示在终端),只关注命令是否执行成功

2>&1 标准错误重定向:2 代表错误输出流,&1代表标准输出流。2>&1表示将错误信息也重定向到/dev/null(与标准输出一起丢弃)

if !... 判断是否成功

模板job需要加.

不加 . 会单独作为一个job运行,如上图

set -e含义

set -e: 当脚本中任何一条命令执行失败(返回非0的退出码)时,立即终止整个脚本的执行,不再继续运行后续命令。

默认情况下,shell脚本的执行逻辑是:即使某条命令失败,也会继续执行后面的命令

deploy时使用ssh免密登录而不是账号密码登录

账号密码登录问题:

  • 安全性差
  • 易被锁定
  • 自动化障碍:ssh协议默认不支持脚本中直接传递密码
  • 权限粒度粗:密码是用户级别的,无法为特定操作(如部署)创建有限权限的临时凭证
dependencies和needs的区别

dependencies:指定后续Job依赖哪些前序Job,artifacts会自动带过来,可以使用

needs: 如果想跳过阶段顺序直接依赖,可使用needs(更灵活,但需注意依赖关系)

artifacts存放位置注意:

--password-stdin的含义

echo " D O C K E R R E G I S T R Y P A S S W O R D " ∣ d o c k e r l o g i n − u " DOCKER_REGISTRY_PASSWORD" | docker login -u " DOCKERREGISTRYPASSWORD"∣dockerlogin−u"DOCKER_REGISTRY_USER" --password-stdin "$DOCKER_REGISTRY_IP_PORT"中的--password-stdin的含义:从标准输入(stdin)读取密码

docker build -t优化

--cache-from [镜像地址] 指定"缓存源镜像",让docker优先使用远程仓库中已有的latest镜像作为构建缓存,避免重复构建相同的层(layer)

-t 构建指定版本,同时更新latest版本

部署服务器相关问题

appuser用户不允许sudo命令

报错信息:sudo docker pull时sudo不被允许

如果命令,不用sudo就能执行,思考把sudo拿掉:appuser用户添加docker用户组,就可以运行docker命令

复制代码
usermod -aG docker appuser

如果必须使用sudo命令,注意控制权限范围:配置visudo允许使用sudo命令不配置密码

复制代码
# 安全边际/etc/sudoers文件(避免语法错误导致权限问题)
visudo

# 只需允许特定命令(如docker、sed、docker-compose), 可限制权限范围
appuser ALL=(ALL) NOPASSWD: /user/bin/docker, /usr/bin/sed, /usr/local/bin/docker-compose

# 或允许所有命令
appuser ALL=(ALL) NOPASSWD: ALL
sudo dc pull 和dc pull 的区别

dc 命令来源:appuser根目录下存放./.bashrc

dc pull会在appuser根目录下找,sudo dc pull会在root下找

source ./.bashrc会在当前用户的当前命令行生效,所以sudo dc pull找不到dc命令

但我尝试在gitlab job中进行下图配置,dc也无法识别,所以我选择用gitlab utils/variable变量而非别名

问AI得到结论:别名的特性:

  • 只在当前shell会话有效
  • 不会被子进程集成(sudo会创建新的子进程)
  • 只在交互式shell中有效(gitlab job中ssh登录无效)

不使用gitlab utils/variable变量而尝试继续使用别名文件的解决方案(未测试,不保证生效):

将别名定义在/.bash_profile或/.profile中。因为登录shell(如ssh连接)会优先加载/.bash_profile或/.profile

数据卷volumns映射失败

报错信息:

它没用我的数据卷映射到/data/project/testdata/file

问题原因:数据卷所有者不是appuser

解决方案:如上图,调整所有者为appuser // 这部分内容应该在pre-deploy.sh中准备好

为你千千万万遍:pipeline失败次数记录

相关推荐
moxiaoran57531 天前
Go语言的范围range
golang
zfj3211 天前
go为什么设计成源码依赖,而不是二进制依赖
开发语言·后端·golang
weixin_462446231 天前
使用 Go 实现 SSE 流式推送 + 打字机效果(模拟 Coze Chat)
开发语言·后端·golang
pblh1231 天前
本地局域网部署的gitlab使用教程
gitlab
小信啊啊1 天前
Go语言切片slice
开发语言·后端·golang
Kiri霧1 天前
Range循环和切片
前端·后端·学习·golang
bing.shao1 天前
Golang 高并发秒杀系统踩坑
开发语言·后端·golang
小信啊啊1 天前
Go语言数组与切片的区别
开发语言·后端·golang
云霄IT1 天前
docker使用教程之部署第一个go项目
docker·容器·golang
Tony Bai1 天前
Go 1.26 新特性前瞻:从 Green Tea GC 到语法糖 new(expr),性能与体验的双重进化
开发语言·后端·golang