記一次Docker構建失敗

脈衝雲_樑興臣發表於2019-02-16

見字如晤。

前段時間,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可以在這裡找到 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: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有怎樣的具體影響?待來日機緣成熟再一探究竟。

相關文章