见字如晤.
前段时间,Node.js 官方发布了 Node 8.9.3 LTS 版本,并且官网首页提示新版本有重要安全更新,"Important security releases, please update now!" ,然后我立即着手公司产品各个模块的 Node 版本升级.
发布基础镜像
我们所有项目均使用 Node.js 实现,并采用 Docker 容器交付和部署,所以要升级所有产品的线上 Node.js 版本,只需要更新一下 Docker 镜像打包时的配置文件 Dockerfile 即可.下方就是一个典型项目的 Dockerfile 配置文件.
RUN npm install --production \
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
&& rm -R ~/.npm*
脚本大意:
COPY . /app
CMD node index.js
基于 maichong/node:8.9.1 基础镜像构建
记录作者信息
运行 apt ,为镜像安装 openssh-client 软件
拷贝代码 package.js 到目标镜像中
设置镜像容器工作目录为 /app
在镜像中运行 npm 安装依赖
拷贝代码到目标镜像的 /app 目录中
设置镜像的启动命令
技巧: 这里先拷贝 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 可以在这里找到 https://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:06 Ign http://mirrors.aliyun.com jessie InRelease
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:10 adduser debconf debianutils dpkg ... (一共40个软件包)
...
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:12 Get:1 http://mirrors.aliyun.com/debian/ jessie/main sensible-utils all 0.0.9 [11.3 kB]
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.
为了限制篇幅,上边贴出的构建日志进行了精简,大量的 apt 网络请求和解压缩包日志使用 … 代替,同时 apt 显示了大量的必须或推荐安装的软件包也以 … 省略.
...
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
从日志中得到错误信息是,在 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. /
然后竟然构建成功!说明基础镜像没问题,莫非又是环境差异问题?!本地构建日志如下:
Ign http://mirrors.aliyun.com jessie InRelease
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
从日志中发现,在我本地构建时,apt 安装 openssh-client 只需要安装 3 个软件包.这怎么可能?!同样的基础镜像,同样都使用阿里云的源,难道是...
...
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.
阿里云源镜像数据问题?
阿里云的源镜像服务是搭建在 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 内核版本是 4.4.0-103-generic ,远程 Runner 宿主机内核版本是 4.4.0-98-generic .尝试升级远程 Runner 宿主机内核:
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
将宿主机内核升级到了最新版 4.4.0-103-generic,重启 Runner 后,在线 Docker 打包成功!
apt-get update
apt-get dist-upgrade
总结
其实 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 有怎样的具体影响?待来日机缘成熟再一探究竟.
来源: https://segmentfault.com/a/1190000012782093