奧推網

選單
科技

在 MacOS 上執行 Docker 太慢!

你是否也覺得,MacOS 中的 Docker 非常慢?本文作者想出瞭解決辦法,不妨來試試看。

原文連結:https://www。paolomainardi。com/posts/docker-performance-macos/

宣告:本文為 CSDN 翻譯,未經允許,禁止轉載。

作者 | PAOLO MAINARDI

譯者 | 彎月 責編 | 鄭麗媛

出品 | CSDN(ID:CSDNnews)

在撰寫本文之際,為了獲得良好的效能和開發者體驗,我的選擇包括:

利用 VirtioFS 在 Docker 桌面版、Rancher 桌面版、Colima 之間共享檔案系統,但這種方法仍然存在一些問題。

使用命名卷,如果你使用 VSCode,可以透過 DevContainers 等獲得良好的開發者體驗。

PHP 專案可以使用 DDEV + Mutagen。

如果你是 VI/Emacs 使用者,只需要在容器中使用編輯器和工具即可。

如何在 MacOS 上執行 Docker?

在 macOS 和 Windows 上,執行 Docker 引擎需要 Linux 核心。這一點 Windows 和 macOS 並沒有任何特別之處,之所以我們看不到 Linux 核心,是因為有人幫你做了這一切。相反,在任何作業系統上,Docker CLI 和 docker-compose 都是原生的二進位制可執行檔案。

關於微軟,有兩件事值得一提。第一,Windows 支援以原生的方式透過 Docker 在 Windows 上執行容器。這要歸功於 2016 年微軟和 Docker 合作,共同努力建立在 Windows 上實現 Docker 規範的容器引擎。

第二,微軟曾嘗試以原生的方式支援 Linux 程序,具體方式是透過實時轉換系統呼叫,在 Windows 核心(WSL1)上直接執行 Linux 程序,而不需要對程式進行任何修改。

我們回到在 macOS 上執行 Docker 的話題。

Docker for Mac 是 Docker 公司的官方產品,專為在 macOS 上無縫執行 Docker 容器而設計,甚至支援 Kubernetes。下面是 Docker for Mac 的一些特性:

Docker for Mac 在 LinuxKit VM 中執行,最近換成了 Virtualization Framework。

檔案系統共享是利用一種名叫 OSXFS 的專有技術實現的。但如果需要訪問大量檔案,這個系統就太慢了(對,我說的就是 Node 和 PHP),因此我更看好一個新的方案:VirtioFS。

網路基於 VPNKit。

可以執行 Kubernetes。

這是一款閉源產品,根據公司的規模可能還需要付費(“擁有超過 250 名員工或年收入超過 1000 萬美元的公司”)。

在我看來,Docker for Mac 有兩個有效的開源替代方案,二者都使用了 Lima,它們也有檔案系統的常見問題,而且剛開始使用 VirtioFS:

Rancher 桌面版

Colima

所以,總結一下:

Docker 容器仍然是 Linux 程序,需要虛擬機器才能在其他作業系統上執行。

Docker-cli 和 docker-compose 是原生二進位制檔案。

由於 Docker 在虛擬機器中執行,因此需要透過其他渠道來與虛擬機器共享主機檔案系統,我們可以在 OSXFS(專有且已棄用)、gRCP FUSE 或 VirtioFS 之間進行選擇。這些技術都有一定的問題,我個人最看好 VirtioFS。

如果想在 Mac 上執行 Docker 容器,你可以以在 Docker for Mac(閉源)、Rancher 桌面版(開源軟體) 和 Colima(開源軟體)之間進行選擇。

什麼是 Docker 的”卷“?

下面,我們來討論另一個容易引起混淆的話題:Docker 的卷。

Docker 容器不能儲存狀態,這意味著只有當容器存在,容器內的檔案才能存在,讓我們看一個例子:

➜ ~ # Run a container and keep it running for 300 seconds。

➜ ~ docker run -d ——name ephemeral busybox sh -c “sleep 300”d0b02322a9eef184ab00d6eee34cbd22466e7f7c1de209390eaaacaa32a48537

➜ ~ # Inspect a container to find the host PID。➜ ~ docker inspect ——format ‘{{ 。State。Pid }}’ d0b02322a9eef184ab00d6eee34cbd22466e7f7c1de209390eaaacaa32a48537515584

➜ ~ # Find the host path of the container filesystem (in this case is a btrfs volume)➜ ~ sudo cat /proc/515584/mountinfo | grep subvolumes906 611 0:23 /@/var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed[truncated]

➜ ~ # Read the container filesystem from the host。➜ ~ sudo ls -ltr /var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318ebtotal 4lrwxrwxrwx 1 root root 3 Nov 17 21:00 lib64 -> libdrwxr-xr-x 1 root root 270 Nov 17 21:00 libdrwxr-xr-x 1 root root 4726 Nov 17 21:00 bindrwxrwxrwt 1 root root 0 Dec 5 22:00 tmpdrwx———— 1 root root 0 Dec 5 22:00 rootdrwxr-xr-x 1 root root 16 Dec 5 22:00 vardrwxr-xr-x 1 root root 8 Dec 5 22:00 usrdrwxr-xr-x 1 nobody nobody 0 Dec 5 22:00 homedrwxr-xr-x 1 root root 0 Dec 10 18:37 procdrwxr-xr-x 1 root root 0 Dec 10 18:37 sysdrwxr-xr-x 1 root root 148 Dec 10 18:37 etcdrwxr-xr-x 1 root root 26 Dec 10 18:37 dev

➜ ~ # Now write something from the container and see if it‘s reflected on the host filesystem。➜ ~ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd0b02322a9ee busybox “sh -c ’sleep 300‘” 4 minutes ago Up 4 minutes ephemeral➜ ~ docker exec -it d0b02322a9ee touch hello-from-container➜ ~ sudo ls -ltr /var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318eb | grep hello-rw-r——r—— 1 root root 0 Dec 10 18:42 hello-from-container

➜ ~ # Now write something from the host to see reflected on the container。➜ ~ sudo touch /var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318eb/hello-from-the-host➜ ~ docker exec -it d0b02322a9ee sh -c “ls | grep hello-from-the-host”hello-from-the-host

➜ ~ # Stop the container。➜ ~ docker stop d0b02322a9ee➜ ~ sudo ls -ltr /var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318ebtotal 4lrwxrwxrwx 1 root root 3 Nov 17 21:00 lib64 -> libdrwxr-xr-x 1 root root 270 Nov 17 21:00 libdrwxr-xr-x 1 root root 4726 Nov 17 21:00 bindrwxrwxrwt 1 root root 0 Dec 5 22:00 tmpdrwx———— 1 root root 0 Dec 5 22:00 rootdrwxr-xr-x 1 root root 16 Dec 5 22:00 vardrwxr-xr-x 1 root root 8 Dec 5 22:00 usrdrwxr-xr-x 1 nobody nobody 0 Dec 5 22:00 homedrwxr-xr-x 1 root root 0 Dec 10 18:44 sysdrwxr-xr-x 1 root root 0 Dec 10 18:44 procdrwxr-xr-x 1 root root 148 Dec 10 18:44 etcdrwxr-xr-x 1 root root 26 Dec 10 18:44 dev-rw-r——r—— 1 root root 0 Dec 10 18:45 hello-from-the-host

➜ ~ # Filesystem still exists until it is just stopped, but now let’s remove it。➜ ~ docker rm ephemeralephemeral➜ ~ sudo ls -ltr /var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318ebls: cannot access ‘/var/lib/docker/btrfs/subvolumes/b237a173b0ba81eb0a60d35b59b0cc5ed7247423bed4b4dcbb5af57a1c3318eb’: No such file or directory

以上資訊說明:

1。 容器在執行和停止狀態下,宿主會為其分配一個檔案系統。

2。 刪除容器,關聯的檔案系統也會被刪除。

現在我們對 Docker 管理檔案系統的方式有了清晰瞭解,很容易看出如果沒有合適的資料持久層,使用容器就會有非常大的風險,這就是我們使用卷的原因。

繫結掛載

正如Docker文件的記載:“Docker 從早期就開始支援繫結掛載。與卷相比,繫結掛載的功能有限。我們可以透過繫結掛載,將宿主上的檔案或目錄掛載到容器。繫結掛需要指定宿主上的絕對路徑。相比之下,使用卷會在宿主的 Docker 儲存目錄中建立一個新目錄,並由 Docker 管理目錄。”

如果想在容器內繫結掛載主機的目錄,只需執行以下命令:

docker run -v :

下面,我們來看看繫結掛載實際的執行情況:

相信你已經看到問題了:繫結掛載允許在容器內掛載宿主的檔案系統,你可以想象在使用者和容器之間有一個虛擬機器,這會讓一切都變得更復雜。

繫結掛載的工作方式大致如下:

當你從 Mac 掛載某個路徑時,其實是在要求 Linux 虛擬機器掛載 Mac 上的網路共享檔案系統的路徑。

通常,你不需要手動設定這個繁瑣的配置,工具可以幫你自動完成。

你可以按照如下方式檢視掛載到 Docker 桌面版上的檔案或目錄:

Preferences -> Resources -> File sharing

如上圖所示,我們將本地路徑 /Users 等掛載到了 Linux 的虛擬機器上,因此,我們也可以像本地目錄一樣掛載 Mac 目錄:

➜ ~ docker run ——rm -it -v /Users/paolomainardi:$(pwd) alpine ash 2/ # ls -ltr 3total 60 4drwxr-xr-x 12 root root 4096 May 23 2022 var 5drwxr-xr-x 7 root root 4096 May 23 2022 usr 6drwxrwxrwt 2 root root 4096 May 23 2022 tmp 7drwxr-xr-x 2 root root 4096 May 23 2022 srv 8drwxr-xr-x 2 root root 4096 May 23 2022 run 9drwxr-xr-x 2 root root 4096 May 23 2022 opt10drwxr-xr-x 2 root root 4096 May 23 2022 mnt11drwxr-xr-x 5 root root 4096 May 23 2022 media12drwxr-xr-x 7 root root 4096 May 23 2022 lib13drwxr-xr-x 2 root root 4096 May 23 2022 home14drwxr-xr-x 2 root root 4096 May 23 2022 sbin15drwxr-xr-x 2 root root 4096 May 23 2022 bin16dr-xr-xr-x 13 root root 0 Dec 11 14:33 sys17dr-xr-xr-x 260 root root 0 Dec 11 14:33 proc18drwxr-xr-x 1 root root 4096 Dec 11 14:33 etc19drwxr-xr-x 5 root root 360 Dec 11 14:33 dev20drwxr-xr-x 3 root root 4096 Dec 11 14:33 Users21drwx———— 1 root root 4096 Dec 11 14:33 root22/ # ls -ltr /Users/paolomainardi/23total 424drwxr-xr-x 4 root root 128 Dec 18 2021 Public25drwx———— 4 root root 128 Dec 18 2021 Music26drwx———— 5 root root 160 Dec 18 2021 Pictures27drwx———— 4 root root 128 Dec 18 2021 Movies28drwxr-xr-x 4 root root 128 Dec 18 2021 bin29drwxr-xr-x 4 root root 128 Dec 31 2021 go30-rw-r——r—— 1 root root 98 Apr 1 2022 README。md31drwxr-xr-x 5 root root 160 Apr 3 2022 webapps32drwx———— 6 root root 192 Nov 12 16:50 Applications33drwxr-xr-x 3 root root 96 Nov 28 21:23 Sites34drwxr-xr-x 14 root root 448 Nov 28 22:22 temp35drwx———— 105 root root

正如 Docker 文件記載:

卷是持久化 Docker 容器生成和使用的資料的首選機制。雖然繫結掛載主要依賴宿主的目錄結構和作業系統,但卷完全由 Docker 管理。與繫結掛載相比,卷有幾個優點。

卷的幾個優點:

卷比繫結掛載更容易備份或遷移。

你可以使用 Docker CLI 命令或 Docker API 管理卷。

卷可用於 Linux 和 Windows 容器。

卷可以更安全地在多個容器之間共享。

卷驅動程式允許你將卷儲存在遠端主機或雲提供商上,以加密卷的內容或新增其他功能。

新卷的內容可以由容器預先填充。

Docker 桌面版上的卷比 Mac 和 Windows 主機上的繫結掛載具有更高的效能。

如上所述,卷相對於繫結掛的優勢是顯而易見,尤其是最後一點,但其最大的缺點是開發者體驗。卷的設計初衷本來就不是滿足這個需求,因此使用它們進行程式設計的難度比較大。

下面,我們來演示一下如何使用卷,我將依次展示:

1。在 localhost 上啟動並執行一個用 node 實現的 API;

2。在宿主上完成整個開發過程;

3。建立一個 Docker 卷;

4。使用 Docker 卷建立容器;

5。在具有卷的容器內實施開發工作流程。

你可能已經注意到了,卷最大的缺點是容器外所做的任何更改都必須使用 docker cp 複製到容器內。

雖然在容器中安裝 VI 可以緩解這個問題,但是這樣就無法訪問我自己的配置檔案(以點開頭的檔案),也沒辦法使用我喜歡的 VSCode。

卷與繫結掛載

透過上述討論,我們可以得出兩個結論:

1。繫結掛載是在容器內移動檔案時最自然的方式,但當 Docker 引擎在虛擬機器中執行時,它們會遇到檔案系統共享引發的嚴重的效能問題。

2。卷的效能遠超繫結掛載,但開發的難度較大(例如本地編輯器無法看到容器外的檔案)。

比較基準

為了演示卷與繫結掛載對效能的影響有多大,我建立了一個簡單的測試套件:在一個空專案(create-react-app)上執行一些 npm install 測試。

環境:

上層:macOS - Docker 桌面版 - 啟用 virtioFS

底層:Linux

測試(npm 安裝):

1。主機上的原生節點;

2。沒有繫結掛載或卷的 Docker;

3。採用繫結掛載的 Docker,但只有 node_modules 採用卷;

4。採用繫結掛載的 Docker。

我們可以看到效能影響的結果:當僅使用繫結掛載時,macOS 的速度會降低 3。5 倍左右(使用 gRPC Fuse 時會降低 10 倍),“罪魁禍首”是 node_modules 目錄(只是一個什麼都沒做的 React 應用就有 3。7 萬個檔案,佔據了 328M)。將此目錄(node_modules)移動到卷中,消耗的時間就與 Linux 不相上下了,約為 7。65 秒。

上面,我們以 Node 為例,但其他開發生態系統受到的影響大致相同,而對於包含數十萬個小檔案的目錄來說,效能幾乎是災難。

解決問題

以上,我們對各個元件及其工作方式做了簡單地介紹,接下來的問題是:為了在 Mac 上使用 Docker 時獲得良好效能和開發者體驗,推薦方式是什麼?

目前來看,答案就是使用 Docker Desktop for Mac 加上 VirtioFS,這樣可以在效能和開發者體驗之間的一個很好的折衷,雖然最終呈現的速度比 Linux 慢,但在大多數情況下,二者的差異可以忽略不計。

在使用 Node 或 PHP 開發新專案時,你不會每次都從頭重新安裝 node_modules 或 vendor,這種情況很少見,如果真的遇到這種情況,就需要花費更多的時間。

不過需要記住以下幾點:

這是一款閉源產品;

你需要使用許可,具體取決於使用情況;

由於這是一項非常新的技術,因此仍然存在許多問題。

那麼,我們究竟有哪些選擇呢?

繫結掛載 + 卷

正如下面的結果所示,這是獲得接近原生效能的最佳方法,但會給開發者體驗帶來嚴重的問題。

讓我們看一個例子:

我們無法從主機上看到容器正在寫入的檔案,這是一個很大的問題,因為它會影響我們編寫軟體的方式以及 IDE 理解我們處理程式碼庫的方式。如何解決這個問題?具體視個人的開發習慣而定。

終端編輯器的使用者

你只需要一個容器,其中包含你所需要的編輯器和工具,然後掛載檔案和程式碼,就可以享受到近乎原生的體驗。

> docker run ——rm -v $HOME/。vimrc:/home/node/。vimrc -v $PWD:/usr/src/app node:lts bash

GUI 編輯器/IDE 使用者

這類使用者的情況將更加複雜。桌面應用程式都繫結到了各自的作業系統,通常它們無法理解很多底層抽象,比如 Docker 容器中的檔案系統。

由於此時的問題是我們無法從 Docker 卷讀取檔案或向從 Docker 卷寫入檔案,因此我們需要一個能夠進行這種雙向同步的工具,最常見的解決方案之一是 Mutagen。

你可以利用 Mutagen 實現宿主與容器之間的雙向同步檔案。剛開始使用工具並不太容易,因為它包括一個守護程序、一個 CLI 工具和一些配置檔案。它本來支援 Composer,後來該功能成了Docker 桌面版的一個實驗特性,後被棄用,又被 Mutagen 的作者吸收成為 Docker 桌面版的擴充套件。最後值得一提的是,它也得到了 DDEV 專案的支援和整合。

但是,如果我們不想安裝額外的軟體該怎麼辦?能不能將 GUI 編輯器作為 Docker 容器執行,以便訪問檔案嗎?在 Linux 上執行 GUI 編輯器很容易,但我們沒有現成的解決方案在 macOS 上執行 Linux GUI 應用程式, 除非你安裝 XQuartz,並使用一些小技巧。

好訊息是,微軟為 Windows 實現了一個 Wayland 合成器,能夠執行原生 Linux GUI 應用程式:WSLg。然而,這並不是使用編輯器的常見方式,而且功能非常有限,與作業系統的整合度也很低。

最好的解決方案是在本地系統上執行 IDE,而且還能夠使用 Docker 容器,魚與熊掌兼得。那麼,我們需要編寫這樣的工具嗎?答案是不需要,因為我們已有現成的工具:開發容器(https://containers。dev/)。

開發容器

開發容器(Development Container)是一個開放規範,它為容器添加了一些專用於開發的內容和設定。

開發容器允許你將容器作為功能齊全的開發環境。你可以利用它執行應用程式,並與程式碼庫所需的工具、庫或執行時相分離,而且還能輔助持續整合和測試。開發容器可以在私有云或公共雲中執行,既可以在本地執行,也可以透過遠端執行。

這個專案是微軟建立的,規範在 CC4 許可下發布。完整的“支援工具”列表,請參見這裡(https://github。com/devcontainers/spec/blob/main/docs/specs/supporting-tools。md)。可以看到,幾乎所有的微軟編輯工具和雲服務都得到了支援,當然也包括 Visual Studio Code 和 GitHub Codespaces。

然而,這個專案也有一些競爭對手:

Gitpod

GCP 雲工作站

AWS Cloud9

Docker 開發環境

如何使用開發容器

你需要在專案的根目錄下建立一個檔案 。devcontainer/devcontainer。json,供 VsCode 使用。

總的來說,這個專案的整體思路非常好,並且由於它是一個開放標準,因此可以很容易地被其他供應商採用,例如:

Gitpod

Jetbrains

nVIM

總結要點:

一切都是基於容器的。

我們可以自定義 VSCode 擴充套件,定義一個可在團隊成員之間共享的開發環境。

透過開發容器,在容器內使用 Docker 和 Docker-compose。

SSH 開箱即用(必須啟動並執行 ssh-agent)。

在本地或雲中重用完全相同的配置。

總結

文字的主要目的是討論如何提高在 macOS 上執行 Docker 的效能,但我覺得首先我們需要充分理解一些基本概念,因為這些基本概念是理解和充分利用系統的基礎。

有人可能想問,為什麼不直接使用 Linux?

日常工作我確實使用 Linux,我認為 Linux 可以作為最佳開發環境。但我們必須考慮硬體生態系統,特別是蘋果晶片已上市,它們在效能、電池壽命和服務方面都有非常出色的表現。

因此,我想魚與熊掌兼得。如今的機器足夠強大,可以無縫執行它們。還有一些創造性的組合方式,比如將 macOS 和 NixOS 組合在一起。我們可以使用自己喜歡的硬體,並尋找最適合自己的工作流程。

《2022-2023 中國開發者大調查》重磅啟動,歡迎掃描下方二維碼,參與問卷調研,更有 iPad 等精美大禮等你拿!