奇怪的Shell管道返回值


学好排除问题的方法,有助于提高生产力。

奇怪的Shell管道返回值


  • [1] 问题现象

今天发现 CI 监控发送了一条告警,显示有一个服务的 runtime 镜像打包失败了。通过查看对应自动化工具 GoCDpipeline 的打包日志,并没有发现是因为什么导致的,只是显示没有可用的 pip3 命令。随即,自己手动启动了一个 ubuntu:18.04 的基础镜像做测试,发现根据对应的 Dockerfile 执行下来,完全是没有问题的。这就很奇怪了?

奇怪的Shell管道返回值

  • [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

奇怪的Shell管道返回值

这种情况下,我们知道可以使用 $PIPESTATUS 来获取管道中每个命令的返回码。其中 PIPESTATUS 是一个数组,第一条命令的返回码存储在 ${PIPESTATUS[0]},以此类推。同时需要注意的是,每执行一条命令 PIPESTATUS 数组都会更新其值为上一条命令的返回码。

奇怪的Shell管道返回值

最终,我们为了防止同样的问题再次出现,对构建的 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 && \
    ......

文章作者: Escape
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Escape !