使用 Docker-in-Docker 來執行 CI 或整合測試環境?三思!
英文網址:https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
中文網址:https://www.jianshu.com/p/2e708cb9af3b
Docker-in-Docker 的主要目的是幫助 Docker 本身的發展。很多人用它來執行 CI 系統(例如 Jenkins ),這初看起來還不錯,但會遇到很多“有趣”的問題,這些問題可以通過將 Docker socket 繫結安裝進 Jenkins 容器中解決。
讓我們來看看這是什麼意思。如果你只是想要快速解決方案而不是細節,可以跳到文章末尾。
Docker-in-Docker:優點
兩年多前,我為-privileged flag in Docker貢獻了程式碼並寫了first version of dind。目的是為了幫助核心團隊更快速地進行 Docker 開發。在 Docker-in-Docker 之前,典型的開發流程是:
hackity hack
構建
停止正在執行的 Docker 程序
執行新的 Docker 程序
測試
重複
如果你想要一個良好可複用的構建(例如在一個容器中),稍微有一點複雜:
ackity hack
確認有一個可工作版本的 Docker 在執行
使用舊 Docker 構建新 Docker
停止 Docker 程序
啟動新的 Docker 程序
測試
停止新的 Docker 程序
重複
隨著 Docker-in-Docker 的到來,這個過程簡化為:
hackity hack
一步構建和執行
重複
好多了,不是麼?
Docker-in-Docker:缺點
然而,出乎人們的預料,Docker-in-Docker 不是百分之百完美和健壯。我的意思是,有幾個問題需要注意。
一是關於 LSM(Linux Security Modules)比如 AppArmor 和 SELinux:當啟動一個容器,那個“內部 Docker ”可能嘗試應用安全配置檔案並導致“外部Docker”產生混亂或者衝突。當你嘗試去合併帶有-privileged
Docker-in-Docker:醜陋
第二個問題和儲存驅動有關,當你在 Docker 中執行 Docker 時,外部的 Docker 執行在正常的檔案系統( EXT4、BTRFS 等等你有的)之上而內部 Docker 執行在寫時複製的系統(AUFS、BTRFS、Device Mapper,依賴於外層 Docker 所使用的檔案系統)之上。有許多組合不能工作。例如,你不能在 AUFS 之上執行 AUFS。如果你在 BTRFS 之上執行 BTRFS,開始時會正常執行,一旦你嵌入 subvolumes,移除父級 subvolumes 會失敗。Device Mapper 沒有名稱空間,所以如果多個 Docker 例項在一臺機器上使用它,它們會看見(和影響)其它的映象和容器備份裝置。這樣不好。
問題有很多解決方法;例如,如果你想在內部 Docker 中使用 AUFS,將/var/lib/docker升級為 volume 就會解決問題。 Docker 為 Device Mapper 目標名稱添加了一些基礎的名稱空間,所以在一臺機器上執行多個例項,就不會互相干擾。
目前為止,設定並不是完全一步到位,你可以看在 GitHub 的dind倉庫中的這幾個 issues。
Docker-in-Docker:更加糟糕
關於構建快取?這同樣相當棘手。人們經常問我,“我在執行 Docker-in-Docker,怎麼樣我才能使用主機上的映象而不是重新將所有東西拉入內部 Docker?”。
一些膽大的人試圖從主機繫結安裝/var/lib/docker到Docker-in-Docker容器中。有時候在多容器之間共享/var/lib/docker。
Docker 程序明確地被設計為獨佔/var/lib/docker的訪問。沒有什麼可以觸碰隱藏在那裡的任何檔案。
為什麼是這樣?這是從使用 dotClound 的時候開始的慘痛教訓。 dotCloud 容器引擎工作於多程序同時訪問/var/lib/dotcloud。像原子檔案替換(代替編輯)這樣的小竅門將程式碼強制鎖定,其它 safe-ish 系統的實驗就像 SQLite 和 BDB ,當我們重構我們的容器引擎(最終成為 Docker ),一個大的設計決策是收集一個單一程序的所有的容器操作和所有的併發訪問。
(不要誤會我的意思:完全有可能做一些漂亮的、可靠的和快速的涉及多個程序和先進的併發管理,但我們認為 Docker 的單一角色模型更簡單,以及更容易編寫和維護)
這意味著,如果你在多 Docker 例項之間共享/var/lib/docker目錄,你會碰到許多問題。當然,它可能工作,尤其是在早期的測試。 “看,我可以在 Ubuntu 中執行Docker!”。但是,嘗試做一些更復雜(從兩個不同的例項拉相同的映象)的操作,世界就會燃燒起來。
這意味著,如果你的 CI 系統進行構建和重建,每一次你會重新啟動 Docker-in-Docker 容器,你可能會摧毀它的快取。這真的不好。
解決方案
讓我們退一步,你真的想要 Docker-in-Docker?或者當 CI 系統本身就是一個容器的時候你只是想要在 CI 系統中執行 Docker(具體為:構建、執行,有時 push 容器和映象)。
我敢打賭,大多數人只是想要後者。你想要的一個解決方案是使得你的像 Jenkins 一樣的 CI 系統能夠啟動容器。
最簡單的方法就是通過使用-v引數幫頂安裝它來為你的 CI 系統暴露 Docker 的 socket。
簡單的來說,當你啟動你的 CI 容器( Jenkins 或者其他),而不是使用 Docker-in-Docker 來 hacking 一些東西,用這條命令啟動它:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
現在這個容器將能夠訪問 Docker Socket,然後就能啟動容器。它將啟動“兄弟”容器而不是“子”容器。
如果你的 CI 使用 Docker 二進位制指令碼,你可以將它包含在你的 CI 映象中或者從主機 bind-mount。例如:
docker run -v /var/run/docker.sock:/var/run/docker.sock \ -v $(which docker):/bin/docker \
-ti ubuntu
這看起來就像 Docker-in-Docker,感覺起來也像 Docker—in-Docker,但它不是 Docker-in-Docker :當你的CI容器要建立更多容器時,這些容器將會在頂級 Docker 中被建立。你將不會與遇到嵌入的影響,構建快取也將在多例項中共享。
作者:希雲Docker容器管理平臺
連結:https://www.jianshu.com/p/2e708cb9af3b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。