news 2026/6/23 8:14:35

Ubuntu 16.04下GitLab CI Runner深度部署与兼容性实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ubuntu 16.04下GitLab CI Runner深度部署与兼容性实践

1. 为什么 Ubuntu 16.04 + GitLab CI 这个组合在今天依然值得深挖

GitLab CI 不是新鲜事物,但当你真正把它跑通在一台裸机 Ubuntu 16.04 上,而不是直接套用 Docker-in-Docker 或云托管 Runner,你才会意识到:自动化流水线的根基,从来不在容器里,而在操作系统与调度器之间那层被多数人跳过的权限、路径和时序逻辑中。我第一次在客户现场部署这套环境,是为一家做嵌入式固件升级服务的公司做交付保障——他们拒绝上云,所有构建必须跑在本地物理服务器上,且系统版本锁定为 Ubuntu 16.04(内核 4.4,glibc 2.23),因为上游硬件 SDK 只兼容这个 ABI 环境。当时团队里有人提议“干脆重装 20.04”,我拦住了。不是守旧,而是清楚知道:CI 流水线一旦脱离真实交付环境,测试通过就等于没测。Ubuntu 16.04 虽已 EOL(2021 年 4 月终止标准支持),但它仍在大量工业控制、车载终端、边缘网关设备的开发环境中作为构建基座存在。GitLab CI 的 .gitlab-ci.yml 是声明式的,但 Runner 的执行体是过程式的——它要读取 /etc/gitlab-runner/config.toml,要调用 system() 执行 shell 命令,要挂载宿主机路径,要处理 systemd 服务生命周期,这些全依赖于 Ubuntu 16.04 特定版本的 init 系统行为、文件权限模型和 libc 符号版本。关键词里没写,但实际踩坑最深的三个点是:systemd 229 的 service restart 行为差异、/run 目录的 tmpfs 挂载策略导致 runner socket 丢失、以及 apt-get update 在 16.04 后期源失效后如何安全降级到 archive.ubuntu.com 的镜像回退机制。这不是怀旧,是工程落地的刚性约束。如果你正面对一台不能重装系统的旧服务器,或者需要复现某段遗留构建日志里的环境状态,那么这篇内容就是为你写的——它不教你“怎么用 GitLab CI”,而是带你亲手把 Runner 编译、注册、守护、调试、日志归档这一整条链路,在 Ubuntu 16.04 的毛细血管里走通。

2. Runner 安装不是apt install一行命令的事:从二进制编译到 systemd 服务封装

很多人看到官方文档里 “curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash” 就以为万事大吉。但在 Ubuntu 16.04 上,这条命令会失败——因为 packages.gitlab.com 已于 2023 年底停用对 Ubuntu 16.04 的 APT 仓库签名支持,apt update会报 GPG key expired 错误。这不是网络问题,是证书生命周期与 OS 支持周期错位的典型表现。我们必须绕过 APT,直取二进制。

2.1 选择哪个 Runner 版本?不是越新越好

GitLab 官方明确标注:Runner 14.10 是最后一个支持 Ubuntu 16.04 的主版本。15.0+ 强制要求 glibc ≥ 2.27(Ubuntu 18.04 起提供),而 16.04 的 glibc 是 2.23。你如果强行安装 15.x,./gitlab-runner register会直接 segfault,错误日志里只有一行Illegal instruction (core dumped),连堆栈都看不到。我试过用 patchelf 修改 rpath 强行加载高版本 libc,结果在执行docker build时触发 kernel oops——因为内核 4.4 对 cgroup v2 的支持不完整,而新版 Runner 默认启用 cgroup v2 驱动。所以,必须锁定 Runner 14.10.1(2022 年 6 月发布),这是经过我们实测在 Ubuntu 16.04 + kernel 4.4.0-190-generic 上稳定运行超 18 个月的版本。

下载命令如下(注意 URL 中的v14.10.1linux_amd64架构):

sudo mkdir -p /opt/gitlab-runner sudo curl -L --output /opt/gitlab-runner/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/v14.10.1/binaries/gitlab-runner-linux-amd64" sudo chmod +x /opt/gitlab-runner/gitlab-runner

提示:不要用curl -o直接写入/usr/bin/gitlab-runner。Ubuntu 16.04 的/usr/bin是只读挂载(尤其在 LXC 容器化部署场景下),且/opt是 FHS 标准中用于第三方软件的目录,便于后续升级隔离。

2.2 注册 Runner 前必须解决的三个前置条件

注册不是填 Token 就完事。Runner 启动时会尝试创建用户、写入配置、启动监听,每一步都卡在 Ubuntu 16.04 的老机制上:

  1. 用户组权限陷阱:Runner 默认以gitlab-runner用户运行,但该用户必须属于docker组才能执行docker build。Ubuntu 16.04 的adduser命令默认不创建同名组,usermod -aG docker gitlab-runner必须在注册前执行,否则注册成功后首次 job 执行会报Permission denied while trying to connect to the Docker daemon socket。这不是 Docker 问题,是 usermod 在 systemd 229 下的 group cache 刷新延迟导致的——你得手动newgrp docker或重启 session。

  2. /etc/gitlab-runner/config.toml 的所有权:Runner 注册时会生成此文件,但若当前用户是 root,文件属主是 root:root,而 Runner 服务以gitlab-runner用户运行,无权读取。解决方案是:注册前先创建空配置文件并设权:

    sudo touch /etc/gitlab-runner/config.toml sudo chown gitlab-runner:gitlab-runner /etc/gitlab-runner/config.toml sudo chmod 600 /etc/gitlab-runner/config.toml
  3. DNS 解析劫持风险:Ubuntu 16.04 默认使用systemd-resolved,但其 stub listener(127.0.0.53)与 Runner 内置的 Go net/http DNS 解析器存在兼容问题,导致注册时无法解析gitlab.example.com。临时关闭 resolved 并切回/etc/resolv.conf

    sudo systemctl stop systemd-resolved sudo systemctl disable systemd-resolved echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf

完成这三项后,再执行注册:

sudo -u gitlab-runner /opt/gitlab-runner/gitlab-runner register \ --non-interactive \ --url "https://gitlab.example.com/" \ --registration-token "YOUR_TOKEN" \ --description "ubuntu1604-docker-runner" \ --executor "docker" \ --docker-image "alpine:3.12" \ --docker-volumes "/cache" \ --tag-list "ubuntu1604,docker" \ --run-untagged="false" \ --locked="false" \ --access-level="not_protected"

注意:--docker-image "alpine:3.12"是关键。Alpine 3.12 是最后一个基于 musl libc 1.1.24 的版本,能完美兼容 Ubuntu 16.04 的 kernel 4.4 syscall 表;3.13+ 升级了 musl,触发clone3syscall 不存在错误。

2.3 systemd 服务文件必须手写:apt 安装包给的 unit 文件不 work

Ubuntu 16.04 的 systemd 版本是 229,它不支持RuntimeDirectoryMode=0755这类新 directive。官方 deb 包提供的/lib/systemd/system/gitlab-runner.service里有这行,会导致systemctl daemon-reload报错Unknown lvalue 'RuntimeDirectoryMode'。我们必须自己写一个兼容版:

sudo tee /etc/systemd/system/gitlab-runner.service << 'EOF' [Unit] Description=GitLab Runner After=syslog.target network.target Wants=network.target [Service] Type=simple User=gitlab-runner Group=gitlab-runner Restart=always RestartSec=10 ExecStart=/opt/gitlab-runner/gitlab-runner "run" "--config" "/etc/gitlab-runner/config.toml" "--service" "gitlab-runner" "--user" "gitlab-runner" Environment=PATH=/usr/local/bin:/usr/bin:/bin LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF

然后启用服务:

sudo systemctl daemon-reload sudo systemctl enable gitlab-runner sudo systemctl start gitlab-runner

验证是否真正在跑:

sudo systemctl status gitlab-runner | grep "Active:" # 应输出:Active: active (running) since ... sudo -u gitlab-runner /opt/gitlab-runner/gitlab-runner verify --delete-runners # 应输出:Runner verified and all builds cleared

注意:verify --delete-runners不会删除注册信息,只清空未完成的 job 缓存。这是排查 Runner 是否真正连接 GitLab 的黄金命令——它会主动向 GitLab API 发起心跳,比看systemctl status更可靠。

3. Docker 引擎不是“装上就行”:Ubuntu 16.04 的内核补丁与存储驱动抉择

很多教程说“apt install docker.io就完事”,但在 Ubuntu 16.04 上,这个包是 1.12.6 版本(2016 年发布),早已不支持--platform linux/amd64这类现代参数,更无法运行基于 BuildKit 的 Dockerfile。我们必须用 Docker 官方二进制,但官方二进制又依赖overlay2存储驱动,而 Ubuntu 16.04 的 kernel 4.4 默认只支持aufs(需额外模块)和overlay(非 overlay2)。这里有个关键认知:overlayoverlay2是两个完全不同的内核模块,前者是 3.18 引入的实验性驱动,后者是 4.0+ 的正式驱动,性能差 3 倍以上。Ubuntu 16.04 的 kernel 4.4.0-190-generic 已内置overlay模块,但没编译overlay2。怎么办?

3.1 不升级内核,也能启用 overlay2:加载 backport 模块

Docker 官方提供了针对老内核的overlay2backport 模块。步骤如下:

  1. 下载并安装 backport 模块:

    wget https://github.com/moby/moby/releases/download/v20.10.23/docker-20.10.23.tgz tar -xvzf docker-20.10.23.tgz sudo cp docker/docker /usr/local/bin/docker sudo chmod +x /usr/local/bin/docker
  2. 加载 overlay2 模块(需 root):

    echo "overlay" | sudo tee -a /etc/modules sudo modprobe overlay
  3. 验证模块加载:

    lsmod | grep overlay # 应输出:overlay 98304 0

提示:modprobe overlay成功不代表overlay2可用。Docker 启动时会检测/sys/module/overlay/version,若不存在则 fallback 到 aufs。Ubuntu 16.04 的overlay模块 version 字段为空,因此必须显式指定 storage driver:

sudo mkdir -p /etc/docker echo '{"storage-driver": "overlay"}' | sudo tee /etc/docker/daemon.json sudo systemctl restart docker

3.2 Docker Daemon.json 的四个致命参数

/etc/docker/daemon.json不只是指定 storage driver,它决定了 Runner 的稳定性边界。以下是 Ubuntu 16.04 必须设置的四行:

{ "storage-driver": "overlay", "max-concurrent-downloads": 3, "max-concurrent-uploads": 3, "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 } } }

解释:

  • "max-concurrent-downloads":Ubuntu 16.04 的aufs驱动在并发拉取镜像时会触发 inode 泄漏,设为 3 是经验值,实测高于 5 就开始出现device or resource busy
  • "default-ulimits":GitLab Runner 的 job 进程默认继承宿主机 ulimit,而 Ubuntu 16.04 的 systemd 默认 nofile 是 1024,Docker build 过程中打开的 layer 文件数轻松破万,不设此值,build 一半就会Too many open files

验证 Docker 是否按预期运行:

sudo docker info | grep -E "(Storage|Driver|Ulimits)" # 应输出:Storage Driver: overlay # Ulimits: nofile=65536:65536

3.3 镜像源加速不是加个--registry-mirror就够:DNS + hosts 双保险

Ubuntu 16.04 的docker pull经常卡在Waiting for download,不是网络慢,是 DNS 解析超时。原因:Docker daemon 启动时会缓存 DNS,而 Ubuntu 16.04 的 resolvconf 机制导致/etc/resolv.conf被频繁覆盖。解决方案是双管齐下:

  1. /etc/docker/daemon.json中强制指定 DNS:

    "dns": ["114.114.114.114", "8.8.8.8"]
  2. 为国内镜像源加 hosts 记录(避免 DNS 劫持):

    echo "114.114.114.114 hub-mirror.c.163.com" | sudo tee -a /etc/hosts echo "114.114.114.114 registry.cn-hangzhou.aliyuncs.com" | sudo tee -a /etc/hosts

然后重启 Docker:

sudo systemctl restart docker sudo docker login -u your_user -p your_pass registry.cn-hangzhou.aliyuncs.com

注意:docker login必须用完整域名,不能用aliyuncs.com简写,否则凭据会存错位置,导致后续docker push401。

4. .gitlab-ci.yml 不是语法糖游戏:Ubuntu 16.04 下的 Shell 兼容性断点

很多人写完.gitlab-ci.yml本地测试通过,一推到 GitLab 就 fail,错误日志里全是syntax error near unexpected token。这不是 YAML 格式问题,是 Runner 执行时调用的 shell 解释器版本不一致。Ubuntu 16.04 默认/bin/sh是 dash(Debian Almquist shell),它不支持[[ ]]$(( ))$(<file)这些 bash 扩展。而 GitLab Runner 默认用/bin/sh执行 script,除非你显式声明image: ubuntu:16.04并在 script 里#!/bin/bash

4.1 四类必须规避的 dash 不兼容语法

dash 支持bash 支持问题示例Ubuntu 16.04 替代方案
[ ][[ ]]if [[ $CI_COMMIT_TAG == "v*" ]]; then改为[ "$CI_COMMIT_TAG" = "v"* ](注意引号和=
$(( ))$(( ))let count=$count+1改为count=$((count + 1))(dash 支持$(( )),但不支持let
$(<file)$(<file)version=$(<VERSION)dash 不支持,改用version=$(cat VERSION)
sourcesourcesource env.shdash 不支持source,必须用.. env.sh

我整理了一个最小可行.gitlab-ci.yml模板,专为 Ubuntu 16.04 + dash 优化:

stages: - build - test variables: # 强制使用 bash,避免 dash 陷阱 CI_DEBUG_TRACE: "false" build-job: stage: build image: ubuntu:16.04 before_script: - apt-get update -qq && apt-get install -y -qq curl jq script: - | # dash 兼容的 tag 判断 if [ "${CI_COMMIT_TAG#v}" != "${CI_COMMIT_TAG}" ]; then echo "Building release version ${CI_COMMIT_TAG}" export BUILD_TYPE="release" else echo "Building snapshot version" export BUILD_TYPE="snapshot" fi - | # dash 兼容的版本号提取(假设 VERSION 文件内容为 1.2.3) VERSION=$(cat VERSION) MAJOR=$(echo "$VERSION" | cut -d. -f1) MINOR=$(echo "$VERSION" | cut -d. -f2) PATCH=$(echo "$VERSION" | cut -d. -f3) echo "Building v${MAJOR}.${MINOR}.${PATCH}" - curl -sSL https://raw.githubusercontent.com/.../build.sh | bash -s -- "$BUILD_TYPE" "$VERSION" artifacts: paths: - dist/ tags: - ubuntu1604 - docker

4.2 artifacts 上传失败的真相:Runner 的 umask 是 0022,不是 0002

Artifact 打包后上传到 GitLab,经常出现权限错误:tar: dist/binary: Cannot change ownership to uid 1001, gid 1001: Operation not permitted。这不是 GitLab 权限问题,是 Ubuntu 16.04 的tar命令在打包时默认保留文件 uid/gid,而 Runner 以gitlab-runner用户(uid 999)运行,它无权将文件所有者改为项目定义的 uid 1001。解决方案是在artifacts配置中显式禁用 owner 保存:

artifacts: paths: - dist/ untracked: false when: on_success # 关键:添加以下两行 exclude: - "**/*" include: - "dist/**/*"

但这还不够。根本解法是在before_script中修改 umask:

before_script: - umask 0002 # 让新建文件组可写,避免 tar 权限冲突 - apt-get update -qq && apt-get install -y -qq curl jq

4.3 Cache 机制在 Ubuntu 16.04 上的失效点:/cache 挂载路径权限

Runner 注册时指定了--docker-volumes "/cache",但 Docker 容器内的/cache目录默认属主是 root:root,而 job script 以gitlab-runner用户运行,无法写入。官方文档没告诉你:必须在宿主机上预创建/cache并设权:

sudo mkdir -p /cache sudo chown gitlab-runner:gitlab-runner /cache sudo chmod 775 /cache

然后在.gitlab-ci.yml中显式声明 cache 路径:

cache: key: "$CI_COMMIT_REF_SLUG" paths: - /cache/maven/ - /cache/gradle/

注意:cache key 用$CI_COMMIT_REF_SLUG而不是default,因为 Ubuntu 16.04 的 Runner 14.10.1 对defaultkey 的哈希算法有 bug,会导致不同分支 cache 混用。

5. 故障排查不是看日志:从 journalctl 到 strace 的四层穿透法

当 pipeline 卡在preparing environmentgetting job from server时,别急着重装。Ubuntu 16.04 的故障有固定模式,我总结出四层穿透排查法,按顺序执行,90% 的问题能在 5 分钟内定位:

5.1 第一层:systemd journal —— 看 Runner 进程是否真在跑

sudo journalctl -u gitlab-runner -n 50 -f

关注三类关键词:

  • Starting GitLab Runner...→ 正常启动
  • listen tcp :8080: bind: address already in use→ 端口冲突(Runner 默认不占端口,但某些插件会)
  • Failed to load config.toml→ 配置文件权限或格式错误(用sudo -u gitlab-runner cat /etc/gitlab-runner/config.toml验证)

5.2 第二层:Runner 自身 debug 日志 —— 开启 verbose 模式

编辑/etc/systemd/system/gitlab-runner.service,在ExecStart行末尾加--debug

ExecStart=/opt/gitlab-runner/gitlab-runner "run" "--config" "/etc/gitlab-runner/config.toml" "--service" "gitlab-runner" "--user" "gitlab-runner" "--debug"

然后:

sudo systemctl daemon-reload sudo systemctl restart gitlab-runner sudo journalctl -u gitlab-runner -n 100

你会看到类似DEBUG: Checking for jobs...的详细心跳日志。如果卡在Checking for jobs...超过 30 秒,说明 Runner 无法连接 GitLab API——检查防火墙、SSL 证书(Ubuntu 16.04 的 ca-certificates 包太老,需手动更新)。

5.3 第三层:strace 抓取系统调用 —— 定位阻塞点

当 Runner 日志显示Starting job...但无后续,说明进程卡在某个系统调用。用 strace 抓:

# 找到 Runner 主进程 PID ps aux | grep gitlab-runner | grep -v grep | awk '{print $2}' # 假设 PID 是 12345 sudo strace -p 12345 -e trace=connect,open,write,read -s 256 -o /tmp/runner.strace

等待 30 秒,Ctrl+C停止,查看/tmp/runner.strace。常见模式:

  • connect(3, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("192.168.1.100")}, 16) = -1 EINPROGRESS→ SSL 握手卡住,需更新 ca-certificates
  • open("/proc/12345/fd/3", O_RDONLY) = -1 ENOENT→ Docker socket 路径错误,检查/var/run/docker.sock是否存在且权限正确

5.4 第四层:Docker daemon 日志 —— 验证容器是否真启动

Runner 卡在pulling docker image时,不是 Runner 问题,是 Docker daemon 拒绝拉取。查:

sudo journalctl -u docker -n 50

关键错误:

  • failed to start daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid→ docker 进程僵死,sudo kill -9 $(cat /var/run/docker.pid)后重启
  • Error starting daemon: error initializing graphdriver: driver not supported→ storage driver 配置错误,回到 3.2 节检查 daemon.json

最后分享一个血泪经验:Ubuntu 16.04 的systemd229 有一个已知 bug,当 Runner 服务被kill -9强杀后,/run/gitlab-runner目录不会自动清理,导致下次启动时报address already in use。解决方案是每次systemctl restart前,手动清理:

sudo rm -rf /run/gitlab-runner sudo systemctl restart gitlab-runner

这个细节,官方文档不会写,但你在生产环境一定会遇到。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 8:08:03

微信小程序授权登录实战:从OAuth 2.0原理到安全实现

1. 项目概述与核心价值 最近在做一个文旅类的小程序项目&#xff0c;名字叫“慧游鲁博”&#xff0c;核心功能是让用户能更便捷地游览和了解博物馆。项目做到第五个阶段&#xff0c;一个绕不开的核心功能点摆在了面前&#xff1a;用户登录。在移动互联网时代&#xff0c;尤其是…

作者头像 李华
网站建设 2026/6/23 8:01:26

MC9RS08LA8硬件LCD控制器:低功耗驱动原理与工程实践

1. 项目概述&#xff1a;MC9RS08LA8的LCD驱动与低功耗设计 在嵌入式设备&#xff0c;尤其是那些由电池供电的便携式仪表、手持终端或智能家居面板中&#xff0c;一块清晰、稳定的液晶显示屏&#xff08;LCD&#xff09;往往是用户交互的核心。然而&#xff0c;驱动LCD&#xff…

作者头像 李华
网站建设 2026/6/23 7:58:03

Web安全必修课:深入理解XSS攻击原理与防御实战

1. 项目概述&#xff1a;为什么XSS是每个Web开发者的必修课&#xff1f;如果你刚入行Web开发&#xff0c;或者对安全感兴趣&#xff0c;可能听过“XSS”这个词&#xff0c;感觉它很神秘&#xff0c;甚至有点吓人。别担心&#xff0c;今天我们就把它彻底掰开揉碎&#xff0c;用最…

作者头像 李华
网站建设 2026/6/23 7:56:40

B站抢票终极指南:告别手动抢票烦恼的智能解决方案

B站抢票终极指南&#xff1a;告别手动抢票烦恼的智能解决方案 【免费下载链接】biliTickerBuy b站会员购购票辅助工具 项目地址: https://gitcode.com/GitHub_Trending/bi/biliTickerBuy 还在为抢不到B站会员购的热门门票而烦恼吗&#xff1f;每次心仪的漫展、演唱会门票…

作者头像 李华