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
- 创建
zly_robot
账号,提供developer权限,用于构建tag - tag构建使用Semver规范
- 自动构建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: 01K5111E0B61GWE8JF8KGZ15SETry 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中准备好