学好排除问题的方法,有助于提高生产力。
- [1] 问题现象
今天发现 CI
监控发送了一条告警,显示有一个服务的 runtime
镜像打包失败了。通过查看对应自动化工具 GoCD
的 pipeline
的打包日志,并没有发现是因为什么导致的,只是显示没有可用的 pip3
命令。随即,自己手动启动了一个 ubuntu:18.04
的基础镜像做测试,发现根据对应的 Dockerfile
执行下来,完全是没有问题的。这就很奇怪了?
- [2] 问题原因
后来,在故障的那个 pipeline
对应的服务器上面,手动 build
镜像,发现还是会出现无法找到可用的 pip3
命令的报错。如果找不到 pip3
命令的话,肯定是安装 pip3
的时候发生的错了。随即,看了下对应的 Dockerfile
文件,是如下安装步骤。
RUN sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \
apt update -y && \
apt update -y && \
python3.6-dev \
python3-distutils \
......
wget -qO - http://fm.escapelife.site/python/12345...67 | python3 - && \
pip3 install cython invoke supervisor --upgrade --no-cache-dir && \
......
于是,手动测试了下 wget
看是否可以成功下载到对应的文件,发现无法解析到对应域名,导致并没有下载到想要下载的东西。又因为后面的管道命令,导致对应的报错并没有显示出来,返回码还是正确的。后面,发现是国内的 DNS
搞事情导致反解析域名到 IP
地址出了问题。将服务器的 DNS
修改之后,再次重跑 CI
,即可正常运行了。
# 发现wget命令返回4的错误码
➜ wget -qO - http://fm.escapelife.site/python/1234567
➜ echo $?
4
# 但是加了管道之后错误码没有了
➜ wget -qO - http://fm.escapelife.site/python/12345...67 | python3 -
➜ echo $?
0
# 将wget的参数去掉可以看到解析域名发生错误
➜ wget http://fm.escapelife.site/python/12345...67
--2020-02-24 13:03:23-- http://fm.escapelife.site/python/12345...67
Resolving fm.escapelife.site (fm.escapelife.site)... failed: nodename nor servname provided, or not known.
wget: unable to resolve host address ‘fm.escapelife.site’
- [3] 解决办法
上述问题的发生主要是因为国内的 DNS
抽风引起的,解决的方式就是将对应服务器上面的 DNS
统一修改为 8.8.4.4
的国外地址,即可正常使用了。当然,修改 DNS
的方式也是有多种可以选择的。我们知道在 build
镜像的时候,发现并没有 --dns
这样一个参数让我们选择的,那我们该怎么设置 DNS
呢?以下是两种设置方式,生效顺序是 --dns > daemon.json > resolv.conf
。
# [方式一] 使用本机的DNS
# 对于docker来说,它默认是映射主机的DNS地址
$ sudo cat /ect/resolv.conf
nameserver 8.8.4.4
# [方式二] 指定非主机的DNS
$ sudo cat /etc/docker/daemon.json
{
"dns" : [
"114.114.114.114",
"8.8.4.4"
]
}
# 启动容器的时候指定
$ docker run -it --rm --dns=8.8.4.4 --entrypoint=/bin/bash ubuntu:18.04
还没完,指的注意的是,为什么管道可以淹没前一个命令的错误码呢?这是一个非常值得深究的问题!通过 Google
的查找,找到如下链接,就是讲述如何获取被管道连接到另一个进程的退出状态。=> Get exit status of process that’s piped to another
这种情况下,我们知道可以使用 $PIPESTATUS
来获取管道中每个命令的返回码。其中 PIPESTATUS
是一个数组,第一条命令的返回码存储在 ${PIPESTATUS[0]}
,以此类推。同时需要注意的是,每执行一条命令 PIPESTATUS
数组都会更新其值为上一条命令的返回码。
最终,我们为了防止同样的问题再次出现,对构建的 Dockerfile
文件做了特殊处理,即在任何使用管道的命令之前,加了 pipefail
变量。又因为默认 ubuntu:18.04
镜像使用的是 sh
且不支持 set -o pipefail
的设置,所以需要在之前将其改为 bash
方可正常使用。
SHELL ["/bin/bash", "-c"]
RUN sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt/sources.list && \
apt update -y && \
apt update -y && \
python3.6-dev \
python3-distutils \
......
set -o pipefail && \
wget -qO - http://site.escapelife.me/python/12345...67 | python3 - && \
pip3 install cython invoke supervisor --upgrade --no-cache-dir && \
......