runner 实际在做的事
一句话总结
GitLab 只是读取配置并生成作业列表,真正执行这些作业的是另一个独立的进程——Runner。所以如果没有 Runner,流水线不会报任何错误,会永远停留在等待状态。
为什么需要它
第一次接触流水线的人最常遇到的卡点,不是语法错误,而是“作业创建了,却不开始”的情况。画面上没有红色的错误,作业只显示 pending。这种状态并不是配置错了,而是没有能领取该作业的 Runner,如果不了解服务器与执行者分离的结构,就会一直盯着配置文件看。
分离本身是有意的设计。构建既沉重又危险。它会执行任意代码,占满磁盘,连接网络。如果在与服务器相同的进程中做这些事,服务器也会一起死掉。把执行放到外面,就可以按需增加 Runner,给不同的团队提供不同的环境,即使某个 Runner 坏了,GitLab 也安然无恙。
工作原理
Runner 在注册到 GitLab 之后,会定期询问“有没有我可以领取的作业”。不是由服务器推送,而是由 Runner 拉取的结构,所以即使 Runner 在防火墙之内,也不需要开放入站端口。
领取什么,由 tag 决定。如果在作业中写了 tags,就只有带有该 tag 的 Runner 才会领取该作业。如果需要 tag 的作业没有写 tag,或者没有任何一个 Runner 带有该 tag,作业就会一直等待。这就是前面所说的 pending 的典型原因。
领取之后的执行方式由 executor 决定。shell 直接在安装了 Runner 的机器上执行,docker 为每个作业新启动一个容器,kubernetes 为每个作业创建一个 Pod。后两者每次都提供干净的环境,所以可复现性好,但相应地,作业之间什么都不会留下。前面学过的 artifacts 和 cache 之所以必要,原因就在这里——这是一种在保持隔离的同时,只挑选需要的东西传递过去的装置。
运行时,GitLab 会注入大量以 CI_ 开头的变量。像 CI_COMMIT_BRANCH、CI_COMMIT_SHA、CI_PIPELINE_ID、CI_JOB_ID 这样的变量。rules 的条件也用这些变量来写,制品名称和镜像 tag 也用这些变量来生成。把提交 SHA 放进镜像 tag 的习惯就是从这里来的。
在现场相遇的样子
在 Runner 运维中,最常争论的数字是并发数。太低,作业就会排队,太高,机器就会开始使用交换空间,所有作业一起变慢。而变慢的流水线会招致重试,重试又会再次制造负载。
另一个是共享 Runner 和专用 Runner 的边界。如果在谁都能用的共享 Runner 上运行部署作业,使用该 Runner 的其他项目就有可能看到部署凭据经过的痕迹。所以部署作业要分离到受保护分支专用的 Runner 上,这是基本做法。
另外,这个实验环境中既没有 GitLab 服务器,也没有 Runner。前两个实验通过配置和亲手编写的解析器,处理了作业是如何创建的、按什么顺序排队;此后的实验则使用 gitlab-ci-local 来运行,它按照与 GitLab 相同的规则解析配置,并通过 shell 实际执行作业。像 Runner 注册、tag 匹配、受保护变量这样需要服务器才能有的行为不会重现,并在各个实验中写明了这一局限。
接下来要看什么
接下来看流水线出事故的地方。处理机密值泄漏的路径、引用别人配置时的风险,以及让部署可以回退的最低限度的装置。