CI Runner 的 VPN:隧道会不会比构建活得更久?
共享 CI runner 会在作业之间保留路由与进程。请规划 VPN 在作业前、中、后的状态,并且只清理该作业自己拥有的连接。
一个部署作业建立连接、复制制品并报告成功。它打开的隧道在下一个作业启动时可能仍然存在,而它改动的路由属于整台主机。构建结果描述的是命令,而不是它留下的状态。请把 VPN 当作有生命周期的资源,而不是某个阶段开头的一行命令。
这台主机上还跑着什么
一个 Jenkins 节点可以同时运行多个执行器,任何改动主机路由、解析器设置或系统级连接的作业,都会替所有人改动。其他作业是否察觉,取决于路由与过滤策略,而不是关于 VPN 的一般规则。仓库访问、制品访问与控制通道是 runner 仍然需要的独立要求。选择完整隧道之前,先把这些加入部署目标列表。
如果控制通道要穿过隧道,设计就必须有意识地把这件事讲清楚。在回滚时才发现它,不算计划。把两份清单写下来:作业必须私下到达什么,runner 无论如何都必须到达什么。
这个作业到底需不需要私网访问?
在加入隧道之前先问:构建阶段是否需要它。一个私有的部署代理可以取回已批准的制品并执行部署,从而让通用构建阶段留在私网之外。这种拆分通常比让每个构建步骤都学会走隧道更划算。
从工作流而不是习惯出发来划定边界。如果答案是不需要,本页其余内容就不必要,而那已经是最好的结果。
下一个作业继承的状态
请描述作业之前、它持有私网访问期间,以及它结束之后(包括取消)应当成立的状态。结束状态最常被默认略过,而它恰恰是下一个作业继承的东西。把它写在 pipeline 旁边,而不是留在某个人的记忆里。
| Moment | What proves it | What leaks if it is skipped |
|---|---|---|
| Job start: pre-flight route check | The route table shows no tunnel from an earlier job, and the profile list is empty | A stale route sends deployment traffic into a dead tunnel |
| Job end: disconnect and shred | The profile is down and the temporary key file is gone | The next job inherits an open tunnel and a usable credential |
| Cancelled job: reboot or disposal | The host is replaced or rebooted before the next job starts | A killed process leaves the tunnel up with no owner |
专用或一次性代理让这些边界更容易落实。容器或命名空间只有在它的网络与权限安排真正隔离了改动时才有帮助。
停掉连接,而不是程序
使用 pipeline 的收尾路径来关闭本作业开启的连接,并删除它的临时凭据。在其他作业共用的主机上,不要按程序名匹配进程;请停掉那条具体的连接。下面两条命令就是全部清理工作,二者都不依赖于找到某个进程。
vpn-cli disconnect --profile "$PROFILE"
rm -f "$RUNNER_TEMP/client.key"
收尾并不等于隔离:runner 死掉会保留它打开的隧道,被强制终止的进程会完全跳过该块。这就是为什么仅靠清理命令不能构成完整设计。实际失败是这样的:一个部署作业在复制步骤被取消,收尾块从未运行,同一主机上的下一个作业带着一条活着的隧道启动,路由还指向一个它到不了的网络。故障看起来像下一次构建里的 'DNS' 问题,而距离造成它的作业结束已经过了几个小时。
把配置文件名和凭据路径记录到作业输出里。一条没有标识的裸断开命令事后很难审计。当之后的调查追问时,日志行也是证明该块运行过的依据。
给作业一台别人不用的主机
一次性基础设施是把继承问题消除,而不是管理它。每个作业用一台全新代理启动,没有路由、没有配置文件、没有别人的凭据,销毁它时就把作业留下的一切清干净。这会让每次构建更贵,而这就是不依赖清理代码的边界的老实代价。对于会碰生产网络的作业,一旦把事故算进去,它通常是更便宜的选择。
在必须保留共享 runner 的地方,请把失败与取消测试得和成功复制一样仔细。测试应当展示:如果这次作业被中途取消,下一个作业会看到什么。作业结束于它的访问与临时状态终止之时,而不是复制命令返回零的时候。