社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  docker

记一次Docker构建失败

折叠椅 • 6 年前 • 440 次点击  

见字如晤。

前段时间,Node.js 官方发布了Node 8.9.3 LTS版本,并且官网首页提示新版本有重要安全更新,“Important security releases, please update now!” ,然后我立即着手公司产品各个模块的Node版本升级。

发布基础镜像

我们所有项目均使用Node.js实现,并采用Docker容器交付和部署,所以要升级所有产品的线上Node.js版本,只需要更新一下Docker镜像打包时的配置文件Dockerfile即可。下方就是一个典型项目的Dockerfile配置文件。

FROM maichong/node:8.9.1

MAINTAINER Maichong Cloud <support@maichong.io>

RUN apt-get update \
    && apt-get install -y --no-install-recommends openssh-client \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

COPY package.json /app/

WORKDIR /app

RUN npm install --production \
 && rm -R ~/.npm*

COPY . /app

CMD node index.js

脚本大意:

  1. 基于 maichong/node:8.9.1 基础镜像构建
  2. 记录作者信息
  3. 运行 apt ,为镜像安装 openssh-client 软件
  4. 拷贝代码 package.js 到目标镜像中
  5. 设置镜像容器工作目录为 /app
  6. 在镜像中运行 npm 安装依赖
  7. 拷贝代码到目标镜像的 /app 目录中
  8. 设置镜像的启动命令

技巧: 这里先拷贝 package.js 再安装npm依赖,最后拷贝代码,这样的好处是:项目代码经常变动,而项目的npm依赖一般不会变化,这样的顺序安排可以有效利用docker build缓存,在package.js没有变化的情况下跳过漫长的npm依赖安装时间,大大提高打包速度。

从上边的Dockerfile配置文件中看到,我们的项目均依赖于基础镜像 maichong/node,这是我们自己发布的Node.js镜像,和Node.js官方镜像的主要区别是:我们的镜像使用了阿里云的 debian apt 源镜像,并采用了 registry.npm.taobao.org 做npm加速镜像。

所以,首先要做的是发布一个新版本的Node.js镜像:maichong/node:8.9.3。

发布过程很简单,编写基础镜像的Dockerfile,在本地构建镜像,最后将镜像推送到Docker官方的仓库,Done!

基础镜像的Dockerfile可以在这里找到 github.com/liangxingc.…

之所以在本地构建,而没有使用Docker仓库的 automated build,是因为,我们的镜像采用了国内阿里云的 Debian apt 源,再加上某些很奇妙的网络因素,在Docker Hub中自动构建时,apt升级总会失败。

升级项目

maichong/node:8.9.3 基础镜像已经准备就绪,接下来开始升级公司的各个项目。

我们的所有项目都基于脉冲云 maichong.io 进行管理,脉冲云包含了从代码仓库,到自动化构建,再到自动化部署等持续集成流程。

修改项目的Dockerfile,将基础镜像变更为 maichong/node:8.9.3,然后将代码提交到代码仓库,然后起身泡了一杯咖啡,悠然地等待脉冲云自动地在线编译打包Docker镜像,并自动将最新的镜像部署到服务器集群上,完成升级工作。

意外!

当我端者刚刚泡好的咖啡回到工位,意外发现,脉冲云在线编译构建失败!心里一凉,我*,何方BUG在此作祟?!赶快在线查看构建日志,导致失败的关键部分日志如下:

2017-12-12 10:04:05 Step 5/12 : RUN apt-get update && apt-get install -y --no-install-recommends openssh-client && apt-get clean && rm -rf /var/lib/apt/lists/*
2017-12-12 10:04:05 ---> Running in c3fb701ef925
2017-12-12 10:04:06 Ign http://mirrors.aliyun.com jessie InRelease
...
2017-12-12 10:04:07 Fetched 11.1 MB in 1s (6028 kB/s)
2017-12-12 10:04:09 Reading package lists...
2017-12-12 10:04:10 Building dependency tree...
2017-12-12 10:04:10 Reading state information...
2017-12-12 10:04:10 The following extra packages will be installed:
2017-12-12 10:04:10 adduser debconf debianutils dpkg ... (一共40个软件包)
2017-12-12 10:04:10 Suggested packages: ...
2017-12-12 10:04:10 Recommended packages: ...
2017-12-12 10:04:12 0 upgraded, 40 newly installed, 0 to remove and 0 not upgraded.
2017-12-12 10:04:12 Need to get 16.7 MB of archives.
2017-12-12 10:04:12 After this operation, 44.7 MB of additional disk space will be used.
2017-12-12 10:04:12 Get:1 http://mirrors.aliyun.com/debian/ jessie/main sensible-utils all 0.0.9 [11.3 kB]
...
2017-12-12 10:04:22 dpkg: error processing archive /var/cache/apt/archives/libgcc1_1%3a4.9.2-10_amd64.deb (--unpack):
2017-12-12 10:04:22 pre-dependency problem - not installing libgcc1:amd64
为了限制篇幅,上边贴出的构建日志进行了精简,大量的 apt 网络请求和解压缩包日志使用 … 代替,同时apt显示了大量的必须或推荐安装的软件包也以 … 省略。

从日志中得到错误信息是,在docker build打包过程中,运行apt安装openssh-client失败,最直接的错误是因为openssh-client依赖的libgcc1包安装失败。

直接反应是,莫非又是apt软件仓库依赖的问题?!咦,我为什么要说又呢?

apt 是Debian和Ubuntu系统使用的包管理器,类似Node.js世界的npm的作用,用来管理、安装Linux系统中的各种软件包,各种软件包又有不同版本并互相依赖。

apt安装软件时,会从网络服务器上获取所需软件包和相关依赖包,这里的“网络服务器”被称为“源”,所以上文中说到的 maichong/node 镜像用到了国内阿里云的“源”,是为了加快apt安装软件时的网络速度,阿里云的“源”服务器是对Debian官方源的加速镜像。

由于apt所管理的软件包数量众多、版本众多、互相依赖、互相依赖指定版本,所以容易造成依赖问题,比如 A依赖于B的1.0版本,C依赖于B的2.0版本,如果安装了A,安装C时就会出错,因为系统中无法共存B的1.0和2.0两个版本。

题外话,npm采用多层node_modules目录嵌套解决了一个包不同版本共存的问题。

仔细观察安装openssh-client时所安装的40个依赖包,发现竟然有dpkg这样非常基础的包,怎么可能?!dpkg是Debian系统最基础的包管理器,apt都是依赖于dpkg,所有能跑起来的Debian系统一定是存在dpkg包的,难道是...

基础镜像是“坏”的?

Docker的镜像一旦构建完成,就一定能够正确执行,我个人从未见过某个镜像自身是“坏”的,可能这次要开眼!

这里之所以说“坏”,是因为镜像中整个apt管理依赖都错乱了,可能是某些原因造成了镜像的“损坏”!

在我本地执行构建命令:

docker build -t test ./

然后竟然构建成功!说明基础镜像没问题,莫非又是环境差异问题?!本地构建日志如下:

Step 5/12 : RUN apt-get update     && apt-get install -y --no-install-recommends openssh-client       && apt-get clean       && rm -rf /var/lib/apt/lists/*
 ---> Running in 237bec98f4e6
Ign http://mirrors.aliyun.com jessie InRelease
...
Fetched 11.1 MB in 11s (972 kB/s)
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  libbsd0 libedit2
Suggested packages:
  ssh-askpass libpam-ssh keychain monkeysphere
Recommended packages:
  xauth
The following NEW packages will be installed:
  libbsd0 libedit2 openssh-client
0 upgraded, 3 newly installed, 0 to remove and 8 not upgraded.

从日志中发现,在我本地构建时,apt安装openssh-client只需要安装3个软件包。这怎么可能?!同样的基础镜像,同样都使用阿里云的源,难道是...

阿里云源镜像数据问题?

阿里云的源镜像服务是搭建在CDN上的,CDN的目的是为了让用户就近访问不同地理位置的服务器,以达到不同地区的用户访问速度都能很快,所以虽然使用了同样的源镜像地址,但是在不同位置更新apt时,所请求的阿里云服务器是不一样的。那么就有可能是阿里CDN数据不同或是从国外Debian官方服务器数据同步未完成导致的问题。

登录到远程脉冲云Builder Runner,这是专门用来执行用户自动化集成的服务器,一开始我们的在线构建就是发生在这里的。在Runner上 ping mirrors.aliyun.com 得到的源镜像IP地址和我本地的果然不一样。

然后我将在Runner上得到的IP地址,加到了我本地DNS解析中,这样,我本地再运行apt时,访问的服务器就和在Runner上是一样的了。

然而,在本地再次构建成功。说明并非是阿里云源镜像数据问题。同样的镜像,同样的源服务器,不同的apt运行结果,难道是...

远程主机上的镜像损坏?

会不会是我本地拉到的镜像是好的,但是远程Runner上拉到的镜像却是坏的?

这是不可能的,因为Docker在拉取镜像后,会对镜像进行验证,所以同一个镜像版本,多人拉取完成后可以保证每个人所得是一模一样的。不会存在某人拉取到一个损坏的镜像的情况。

似乎所有可能性都被排除了,问题仍然得不到解决,真是莫名其妙,手上的线索已经全部断掉,案件侦破进入了僵局。

我向团队说明了我遇到的情况,邻桌Mr.Li断言道:“一定是环境差异导致的BUG!”

是呀,一定是环境差异问题,但是所有环境差异都排除过了,同样的网络环境,同样的构建配置,同样的镜像...

我们知道Docker的优势就在于将软件和运行环境打包成一个镜像,一个镜像在不同外部环境下执行,能够保证镜像内的程序所在的镜像内部环境一模一样,因为Docker运行容器时,会将环境完全隔离。不对,并没有“完全”隔离,难道是...

宿主机内核差异问题?

在本地和在Ranner上分别执行uname -a,果然得到的内核版本是不一样的。

本地:

Linux local 4.4.0-103-generic #126-Ubuntu SMP Mon Dec 4 16:23:28 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Runner:

Linux runner02 4.4.0-98-generic #121-Ubuntu SMP Tue Oct 10 14:24:03 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

本地的Linux内核版本是 4.4.0-103-generic ,远程Runner宿主机内核版本是 4.4.0-98-generic 。尝试升级远程Runner宿主机内核:

apt-get update
apt-get dist-upgrade

将宿主机内核升级到了最新版4.4.0-103-generic,重启Runner后,在线Docker打包成功!

总结

其实Docker运行容器时,并非将所有环境“完全隔离”,比如宿主机内核就没有隔离。Docker并不像VMware那样,在启动虚拟机时,完全虚拟一个硬件环境,然后从头加载虚拟机操作系统的内核。Docker容器运行时,仍然依赖于宿主机内存里正在运行的内核,虽然不同容器使用不同镜像,但镜像的本质是文件系统的压缩包,是让你的容器运行时有一个自己定义的文件系统和软件群,而执行容器程序时,并没有从头为你启动一个系统内核。所以我们称Docker为轻量级虚拟化技术。

本文所遇到的问题的原因应该是,在构建 maichong/node:8.9.3 镜像时,是在 4.4.0-103-generic 版本内核环境下执行的,apt 安装的一系列软件是适用于 4.4.0-103-generic 版本的,而在Runner上执行构建时,内核又变为了 4.4.0-98-generic可能 apt 将之前基础镜像中的一些软件标识为无效,又要进行重新安装。

这种情况我之前也从未见过,所以撰文记录,可以断定,Docker Hub大部分镜像编译时的内核环境和我们本地的内核是不一样的,怎么单单这次apt会出错?apt在管理软件时,系统内核对apt有怎样的具体影响?待来日机缘成熟再一探究竟。


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/z2GvW8f8TB
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/5213
 
440 次点击