在Drone CI的日常使用中,最让人头疼的往往不是写流水线,而是容器跑着跑着就退了,或者里面访问不了外网。这两种毛病看起来八竿子打不着,其实背后都连着“容器执行单元”这条线。执行单元就是每个step启动的那个容器,它继承了镜像里的环境,也在Drone规定的网络和资源池里干活。一旦容器异常退出,或者网络连不上,整个构建就卡住了。下面咱们就一件一件捋清楚。
一、先搞清楚“执行单元”是什么
在Drone里,流水线是由一个个step组成的,每个step本质上就是一个Docker容器。Drone会按照你写的配置,拉取镜像,创建容器,然后把你写的命令扔进去执行。命令执行完,容器就被销毁。所以你可以把容器想象成一个临时工:干完活就走,不带走一片云彩。但临时工也有要求——得有吃的(镜像里的命令),得有宿舍(运行环境),还得能跟外界联系(网络)。任何一个条件不满足,他就会撂挑子。
二、容器异常退出的排查套路
容器退出不一定都是坏事。正常退出就好比临时工干完活下班了,异常退出则是中途跑路。咱们要分几步排查。
2.1 第一步:看日志
日志是最直接的证据。在Drone的构建页面,点开失败的那一步,往下翻输出。如果你看到Killed、OOMKilled这类词,大概率是容器被系统杀掉了;如果看到Error、exited with code之类的,说明是命令本身出错了。日志里还会告诉你具体的报错位置,比瞎猜强一百倍。
2.2 第二步:看退出码
容器退出码有讲究。0代表一切正常;1通常是一般错误,比如命令写错了;137是被SIGKILL杀死,多半是内存超限;143是被SIGTERM终止,可能有人手动停了;127是命令不存在。Drone会把退出码显示在构建页面的失败步骤上,咱们要养成看退出码的习惯。比如你写了一个命令exit 1,那退出码就是1,构建失败也就顺理成章。
2.3 第三步:查资源限制
有时候日志里什么都看不到,只有一个Killed,这十有八九是内存爆了。Drone的docker runner本身可能设置了内存上限,比如每个容器最多256M。你的构建要是跑到一半内存不够,就会被系统直接杀掉。想验证的话,你可以在流水线里加一个step专门查看当前容器能看到的内存限制。比如用cat /sys/fs/cgroup/memory.max(新版内核)或者/sys/fs/cgroup/memory/memory.limit_in_bytes(老版)。但更省事的办法是给step设置resource_limit,告诉Drone“我这个任务需要更多内存”,前提是runner允许你设置。配置是这样的:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 内存限制演示
steps:
- name: 查看内存限制
image: alpine:3.19
commands:
- echo "现在看看当前cgroup的内存限制是多少"
- cat /sys/fs/cgroup/memory.max 2>/dev/null || cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null
resource_limit:
memory: 512M
注意,resource_limit这个字段在不同版本的Drone runner里支持程度不一样,如果你设置了不生效,就得去查runner的配置文件。
2.4 第四步:检查镜像本身
很多“异常退出”其实是镜像的问题。比如镜像拉不下来、镜像里的启动脚本挂了、或者镜像标签不存在。如果日志里出现image pull failed、pull access denied,那基本就是镜像源有问题。解决思路是换成可访问的镜像仓库,或者给镜像加一个可用的tag。另外,有些基础镜像为了瘦身,连基本的curl都不带,你写curl肯定会报command not found,这种不是容器异常,是环境配置不到位,用apk add装一下就行。
三、网络连通性问题的解决方法
网络问题比退出问题更玄学,但也是有规律可循的。
3.1 先分清是哪种“不通”
是外网访问不了?还是内网服务连不上?还是域名解析不了?不同原因对应不同解法。在Drone的docker runner里,每个step默认使用bridge网络,也就是说它有自己的虚拟网卡,跟宿主机不在同一个网络栈里。如果你想访问宿主机上跑了某个服务,直接localhost是不行的,得用宿主机的IP或者专用域名。
3.2 在容器里手动测
最快的办法是在step里加调试命令。比如用alpine镜像,先装curl,然后去访问目标地址。如果超时,说明网络不通;如果返回Could not resolve host,说明DNS解析失败;如果返回Connection refused,说明目标服务没启动或防火墙挡住了。示例:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 网络连通性测试
steps:
- name: 手动测试网络
image: alpine:3.19
commands:
- apk add --no-cache curl >/dev/null # 安装curl,输出不刷屏
- echo "===== 测试外网 ====="
- curl -I --connect-timeout 5 https://www.baidu.com || echo "外网不通"
- echo "===== 测试内网 ====="
- curl -I --connect-timeout 5 http://192.168.1.10:8080 || echo "内网不通"
这里用了--connect-timeout,防止命令一直卡住。
3.3 检查DNS
DNS是最容易忽略的坑。容器默认的DNS来自宿主机,如果宿主机设置了奇怪的DNS,或者容器内没有nslookup命令,就会解析失败。你可以在step里直接查看/etc/resolv.conf,看看是不是指向了正确的DNS。也可以临时指定一个公共DNS再做解析测试:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: DNS检查
steps:
- name: 检查DNS解析
image: alpine:3.19
commands:
- cat /etc/resolv.conf # 查看当前DNS配置
- apk add --no-cache bind-tools >/dev/null # 安装nslookup工具
- nslookup example.com 8.8.8.8 # 用公共DNS临时测试
如果公共DNS能解析,而默认DNS不能,那就说明是DNS配置的问题。可以在runner层面调整DNS,或者在step里通过dns参数指定(如果runner支持)。但在很多Drone版本里,step没有直接的dns字段,你得通过修改runner的配置文件来给所有容器设置DNS。
3.4 代理问题
在公司的CI环境里,访问外网经常需要走代理。Drone容器里默认是没有代理环境的,这会导致npm install、pip install等命令卡死或超时。解决办法是在step里设置HTTP_PROXY和HTTPS_PROXY环境变量。示例:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 代理配置演示
steps:
- name: 带代理下载
image: node:20-alpine
environment:
HTTP_PROXY: http://proxy.company.com:8080
HTTPS_PROXY: http://proxy.company.com:8080
NO_PROXY: localhost,127.0.0.1,.internal.company.com
commands:
- npm config set proxy $HTTP_PROXY
- npm config set https-proxy $HTTPS_PROXY
- npm install
这里注意,NO_PROXY很重要,否则连内网服务也被代理接管,反而出问题。
3.5 使用host网络模式
有些场景下,step需要直接访问宿主机上的服务,比如调试一个本地数据库。bridge网络下访问不到,这时可以尝试把network_mode设置为host,让容器共享宿主机的网络栈。但这样会带来安全风险,只建议在调试或确实需要的时候使用。示例:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 使用host网络
steps:
- name: 访问宿主机服务
image: alpine:3.19
network_mode: host
commands:
- apk add --no-cache curl >/dev/null
- curl -I http://localhost:3306 # 现在localhost就是宿主机
如果network_mode不被runner支持,也可以试试用extra_hosts把宿主机IP映射成一个自定义域名,比如db.local指向172.17.0.1(docker0网桥地址)。具体值跟Docker宿主机的桥接网卡有关。
四、一个完整的实战排查案例
下面用一个真实场景把所有知识串起来。假设你的项目在Drone里跑,npm ci的时候不是超时就是卡死,偶尔还会出现Killed。你的初始配置长这样:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 前端构建
steps:
- name: 安装依赖
image: node:20-alpine
commands:
- npm ci
- name: 运行测试
image: node:20-alpine
commands:
- npm test
开始排查。先加一个调试step,把环境信息打出来:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 前端构建-排查
steps:
- name: 环境诊断
image: node:20-alpine
commands:
- echo "===== 内存信息 ====="
- free -m
- echo "===== 网络测试 ====="
- apk add --no-cache curl >/dev/null
- curl -I --connect-timeout 5 https://registry.npmjs.org || echo "npm源连接失败"
- echo "===== DNS配置 ====="
- cat /etc/resolv.conf
构建后,从日志里发现内存只有256M可用,而npm ci需要更多,导致被Killed。同时发现registry.npmjs.org连接超时,因为公司要求走代理。于是修改配置:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 前端构建-修复后
steps:
- name: 安装依赖
image: node:20-alpine
environment:
NODE_OPTIONS: --max-old-space-size=512 # 限制Node最大堆内存,防止溢出
HTTP_PROXY: http://proxy.company.com:8080
HTTPS_PROXY: http://proxy.company.com:8080
NO_PROXY: localhost,127.0.0.1
commands:
- npm ci --registry=https://registry.npmmirror.com # 使用国内镜像,速度更快
- name: 运行测试
image: node:20-alpine
environment:
NODE_OPTIONS: --max-old-space-size=512
commands:
- npm test
这样之后,构建就稳定了。这里的关键是:内存不够就限制Node的内存使用,网络不通就换镜像源和加代理。
再补充一个场景:构建里需要用到数据库服务,但两个step没法直接用localhost通信。Drone支持在services字段里定义辅助容器,step可以通过服务名字来访问它。示例:
# 技术栈:Drone CI(YAML 配置)
kind: pipeline
type: docker
name: 访问服务容器
services:
- name: mydb
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: secret
steps:
- name: 测试数据库连接
image: alpine:3.19
commands:
- apk add --no-cache mysql-client >/dev/null
- mysql -h mydb -u root -psecret -e "SELECT VERSION();"
服务名mydb会被写入step容器的DNS解析,所以mysql -h mydb就能连上。这也是网络连通性里常见的“容器间通信”问题,学会了这个,以后遇到类似的就不用慌了。
五、技术优缺点与注意事项
5.1 优点
Drone这种容器化执行方式,最大的好处是干净。每个step都是独立容器,环境不会互相污染,镜像拉下来就是那套环境,本地跑不通的,CI里大概率也跑不通,反过来也一样。而且容器销毁后不留残留,清清爽爽。
5.2 缺点
缺点也很明显:排障比直接在虚拟机里费劲。容器是临时和隔离的,你可能没法在它活着的时候进去敲命令,只能靠日志和构建后的痕迹。网络配置也依赖runner的底层实现,不同版本对DNS、host网络、资源限制的支持不一样,容易踩坑。
5.3 注意事项
有几点想提醒大家。第一,不要忽略退出码,也不要只看一行日志,要结合上下文。第二,不要随便用network_mode: host,除非你清楚它意味着什么。第三,代理配置一定要加NO_PROXY,否则内网资源也会被绕到外网代理,导致访问失败。第四,记住给step加合理的超时和重试机制,比如Drone的retry字段,防止临时网络抖动把构建憋死。第五,如果频繁出现OOM,优先优化构建命令,减少并发,而不是无脑加内存。
六、总结
容器异常退出和网络不通,是Drone CI里最常见的两个事故。它们看着合成一块儿,实际上都是“资源、环境、配置”三个层面的问题。资源不够就加内存或优化命令,环境不对就换镜像或装工具,配置不对就查DNS、代理、网络模式。只要按着“看日志→查退出码→测网络→调参数”这条路走,就能一步步把问题揪出来。希望这篇文章能让你少踩几个坑,遇到问题的时候多一份从容。
评论
围绕“Drone容器执行单元的故障排查:容器异常退出与网络连通性的解决方法”参与讨论