Docker-in-Docker 是一個虛擬化的 Docker 守護程式環境,在容器本身內執行以建置容器映像。 建立 Docker-in-Docker 的主要目的是幫助開發 Docker 本身。 很多人用它來運行 Jenkins CI。 乍一看這似乎很正常,但隨後出現的問題可以透過在 Jenkins CI 容器中安裝 Docker 來避免。 本文告訴您如何執行此操作。 如果您對沒有詳細資訊的最終解決方案感興趣,請閱讀本文的最後一部分「解決問題」。
Docker-in-Docker:“好”
兩年多前我投入了 Docker
- 駭客攻擊;
- 建造;
- 停止正在執行的 Docker 守護程式;
- 啟動新的 Docker 守護程式;
- 測試;
- 重複循環。
如果你想製作一個漂亮的、可複製的元件(即在容器中),那麼它就會變得更加複雜:
- 駭客攻擊;
- 確保 Docker 的工作版本正在運作;
- 使用舊 Docker 建置新 Docker;
- 停止 Docker 守護程式;
- 啟動一個新的 Docker 守護程式;
- 測試;
- 停止新的 Docker 守護程式;
- 重複。
隨著 Docker-in-Docker 的出現,這個過程變得更加簡單:
- 駭客攻擊;
- 組裝+發射一站式完成;
- 重複循環。
這樣不是好很多嗎?
Docker-in-Docker:“糟糕”
然而,與普遍看法相反,Docker-in-Docker 並不是 100% 的明星、小馬和獨角獸。 我的意思是,開發人員需要注意幾個問題。
其中之一涉及 LSM(Linux 安全模組),例如 AppArmor 和 SELinux:運行容器時,「內部 Docker」可能會嘗試套用與「外部 Docker」發生衝突或混淆的安全性設定檔。 這是在嘗試合併 –privileged 標誌的原始實現時最難解決的問題。 我的更改有效,所有測試都會在我的 Debian 機器和 Ubuntu 測試虛擬機上通過,但它們會在 Michael Crosby 的機器上崩潰並燒毀(我記得他有 Fedora)。 我不記得問題的確切原因,但這可能是因為 Mike 是使用 SELINUX=enforce 的聰明人(我使用了 AppArmor),而我的更改沒有考慮 SELinux 設定檔。
Docker-in-Docker:“邪惡”
第二個問題與 Docker 儲存驅動程式有關。 當您執行 Docker-in-Docker 時,外部 Docker 在常規檔案系統(EXT4、BTRFS 或任何您擁有的系統)之上運行,而內部 Docker 在寫入時複製系統(AUFS、BTRFS、Device Mapper)之上運行等等)。,取決於配置為使用外部Docker 的內容)。 這會產生許多不起作用的組合。 例如,您將無法在 AUFS 之上執行 AUFS。
如果您在 BTRFS 之上運行 BTRFS,它首先應該可以工作,但是一旦存在嵌套子卷,刪除父子卷將失敗。 Device Mapper 模組沒有命名空間,因此如果多個 Docker 實例在同一台電腦上運行它,它們都將能夠看到(並影響)彼此以及容器備份設備上的映像。 這不好。
有一些解決方法可以解決其中許多問題。 例如,如果你想在 Docker 內部使用 AUFS,只要將 /var/lib/docker 資料夾變成一個磁碟區就可以了。 Docker 已向 Device Mapper 目標名稱添加了一些基本命名空間,這樣如果多個 Docker 呼叫在同一台電腦上運行,它們就不會互相干擾。
然而,這樣的設定一點也不簡單,從這些可以看出
Docker-in-Docker:情況變得更糟
建置緩存怎麼樣? 這也可能相當困難。 人們經常問我「如果我運行 Docker-in-Docker,我如何使用主機上託管的映像,而不是將所有內容拉回我的內部 Docker 中」?
一些有進取心的人嘗試將 /var/lib/docker 從主機綁定到 Docker-in-Docker 容器。 有時他們會與多個容器共用 /var/lib/docker 。
您想破壞您的資料嗎? 因為這正是會損壞您的資料的原因!
Docker 守護程式顯然被設計為具有對 /var/lib/docker 的獨佔存取權。 其他任何東西都不應「觸摸、戳或戳」位於此資料夾中的任何 Docker 檔案。
為什麼會這樣呢? 因為這是開發 dotCloud 時學到的最慘痛教訓之一的成果。 dotCloud 容器引擎透過讓多個進程同時存取 /var/lib/dotcloud 來運作。 諸如原子檔案替換(而不是就地編輯)、使用諮詢鎖和強制鎖定填充程式碼以及 SQLite 和 BDB 等安全系統的其他實驗等狡猾的技巧並不總是有效。 當我們重新設計容器引擎(最終成為 Docker)時,重大設計決策之一是將所有容器操作整合到單一守護程序下,以消除所有並發廢話。
不要誤會我的意思:完全有可能製造出涉及多個流程和現代並行控制的優質、可靠和快速的東西。 但我們認為使用 Docker 作為唯一的參與者來編寫和維護程式碼更簡單、更容易。
這表示如果您在多個 Docker 實例之間共用 /var/lib/docker 目錄,則會出現問題。 當然,這是可行的,尤其是在測試的早期階段。 “聽著,媽媽,我可以作為 docker 運行 ubuntu!” 但嘗試一些更複雜的事情,例如從兩個不同的實例中提取相同的圖像,你會看到世界在燃燒。
這意味著,如果您的 CI 系統執行建置和重建,則每次重新啟動 Docker-in-Docker 容器時,您都有可能將核武放入其緩存中。 這一點都不酷!
解決方案
讓我們退後一步。 您真的需要 Docker-in-Docker 還是您只是希望能夠運行 Docker 並從 CI 系統建置和運行容器和映像,而 CI 系統本身位於容器中?
我敢打賭大多數人都想要後一種選擇,這意味著他們希望像 Jenkins 這樣的 CI 系統能夠運行容器。 最簡單的方法就是將 Docker 套接字插入 CI 容器中,並將其與 -v 標誌關聯起來。
簡而言之,當您執行 CI 容器(Jenkins 或其他)時,請勿與 Docker-in-Docker 一起破解某些內容,而是使用以下行啟動它:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
該容器現在可以存取 Docker 套接字,因此能夠運行容器。 除了運行“子”容器之外,它將啟動“兄弟”容器。
使用官方 docker 映像(包含 Docker 二進位)嘗試此操作:
docker run -v /var/run/docker.sock:/var/run/docker.sock
-ti docker
它的外觀和工作方式類似於 Docker-in-Docker,但它不是 Docker-in-Docker:當此容器建立其他容器時,它們將在頂級 Docker 中建立。 您不會遇到嵌套的副作用,並且程序集快取將在多個呼叫之間共用。
注意:本文的先前版本建議將 Docker 二進位檔案從主機連結到容器。 現在這已經變得不可靠了,因為 Docker 引擎不再涵蓋靜態或近靜態庫。
因此,如果您想使用 Jenkins CI 中的 Docker,您有 2 個選擇:
使用基本映像打包系統(即,如果您的映像是基於 Debian,則使用 .deb 套件)和 Docker API 安裝 Docker CLI。
一些廣告🙂
感謝您與我們在一起。 你喜歡我們的文章嗎? 想看更多有趣的內容? 通過下訂單或推薦給朋友來支持我們,
Dell R730xd 在阿姆斯特丹的 Equinix Tier IV 數據中心便宜 2 倍? 只有這裡
來源: www.habr.com