見字如晤。
前段時間,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
指令碼大意:
- 基於 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: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有怎樣的具體影響?待來日機緣成熟再一探究竟。