为什么GitLab post-receive脚本在while循环中被终止或卡住?
我编写了一款用于查找GitLab Runner CI生成的最新job.log文件的post-receive脚本,实际运行时发现脚本在找到目标文件前就被终止或卡住,始终无法跳出while循环,同时GitLab Runner返回4:Deadline Exceeded错误。
最小可复现示例(MWE)
该MWE可完成完整部署流程,上传仓库到GitLab服务器并运行仓库CI。目前未做通用化适配,至少满足以下运行要求:系统:Ubuntu 20.04,架构:AMD64。
git clone git@github.com:Deployment-Oneliners/Self-host-GitLab-Server-and-Runner-CI.git cd Self-host-GitLab-Server-and-Runner-CI git checkout post-receive rm -r test/libs/* chmod +x install-bats-libs.sh ./install-bats-libs.sh ./install_gitlab.sh -s -r ./test.sh
运行完成后可通过以下命令在GitLab Docker容器内查看post-receive脚本日志:
sudo docker ps -a sudo docker exec -t -i ab15330e020f /bin/bash cd /var/opt/gitlab/git-data/repositories/@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git/refs/keep-around/9514d16aafc1d741ba6a9ff47718d632fa8d435b cat post_receive_log.txt
如需彻底卸载MWE,可运行:./uninstall_gitlab.sh -y -h -r。
相关代码
为定位代码中断位置,我在post-receive脚本中添加了大量变量输出到post_receive_log.txt的逻辑,以下是查找最新任务日志的循环代码:
find_job_of_commit() { local search_path=$1 local searched_commit=$2 echo "in loop search_path=$search_path" >> "post_receive_log.txt" echo "in loop searched_commit=$searched_commit" >> "post_receive_log.txt" query_result=$(while ! find "$search_path" -name "job.log" | xargs grep "Checking out $searched_commit"; do sleep 10 ; done) echo "query_result=$query_result" >> "post_receive_log.txt" }
运行输出
脚本输出的post_receive_log.txt内容如下:
repopath_to_artifacts=/var/opt/gitlab/gitlab-rails/shared/artifacts/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 in loop search_path=/var/opt/gitlab/gitlab-rails/shared/artifacts/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 in loop searched_commit=eb052e7d
由此可推断post-receive脚本要么在执行sleep 10命令时被终止,要么始终无法找到目标文件被困在while循环中。更完善的调试代码显示脚本确实是在执行sleep 10后停止,没有找到本次运行的最新任务(本次运行最新任务编号为31)。
但我根据post_receive_log.txt的输出,在GitLab Docker容器内手动执行完全相同的等待命令却可以正常运行,结果如下:
root@127:/var/opt/gitlab/git-data/repositories/@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35.git# while ! find "/var/opt/gitlab/gitlab-rails/shared/artifacts/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35" -name "job.log" | xargs grep "Checking out eb052e7d"; do sleep 10 ; done /var/opt/gitlab/gitlab-rails/shared/artifacts/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35/2021_10_16/33/33/job.log:Checking out eb052e7d as master...
提出的假设
- 假设一:repopath_to_artifacts文件路径中的@符号在命令行和bash脚本中的处理逻辑不同,导致脚本中识别的路径无效,但在命令行中路径有效。
- 假设二:post-receive脚本运行超过一定时长后被GitLab主动终止,该猜测可被
4:Deadline Exceeded错误佐证。 - 假设三:find命令会使用目录的快照,该快照在单个shell脚本运行期间不会更新。不过该可能性较低,也无法解释脚本被终止的现象。但该猜测得到了手动测试的佐证:在Docker容器内手动运行post-receive脚本,不管任务成功还是失败都能正常运行,哪怕是在post-receive脚本刚刚因为找不到对应提交异常终止后立刻运行也没问题。
问题
请问是什么原因导致post-receive脚本意外终止或被困在while循环中?
首先排除假设一:路径中的@符号是合法的文件名字符,bash解析带@的路径时不会有差异化处理,手动执行同路径脚本可正常运行已经验证了这一点。
假设三也不成立:find命令每次执行都会实时遍历目录树,不存在缓存目录快照的机制,和运行上下文无关。
问题的根本原因就是你提出的假设二:GitLab 对服务端钩子(post-receive属于此类)的执行时长有默认55秒的硬限制。你的轮询逻辑每次休眠10秒,只要CI任务生成job.log的时间晚于钩子启动后55秒,脚本就会被GitLab进程直接杀死,返回4:Deadline Exceeded错误。
另外还有一个时序问题:post-receive钩子触发的时机是代码入库完成的瞬间,此时对应的CI任务可能还在调度队列中,还没开始执行,自然不会生成对应的job.log文件,你提前轮询只会空等直到超时。你手动执行脚本时CI任务已经完成,所以能正常匹配到日志。
修复建议
- 不要在服务端钩子中做同步等待逻辑,钩子仅做触发异步任务的操作(比如写入消息队列、后台启动独立的等待进程)后直接返回,避免触发超时。
- 更合理的方案是使用GitLab的CI Webhook功能,配置CI任务完成后主动回调你的处理逻辑,完全避免无意义的轮询。
- 如果必须保留轮询逻辑,要给循环添加最大重试次数限制,总等待时长控制在50秒以内,同时每次循环打印时间戳和扫描结果,方便后续排查。
内容的提问来源于stack exchange,提问作者a.t.

