Podman 與 Docker 最大差異在於其無 Daemon 的架構設計。Docker 需要 Docker Daemon 持續執行於後台以管理容器,而 Podman 則直接與容器執行時互動,不需 Daemon 中介。這使得 Podman 更輕量、更快速,也更安全。Podman 的命令列工具與 Docker CLI 相似,易於上手。同時,Podman 也提供 REST API,方便程式化管理。Podman 的核心元件包含 libpod、OCI 執行時(如 crun 和 runc)、containers/image、containers/storage、Buildah 和 Conmon,這些元件協同運作,實作容器的完整生命週期管理。對於追求輕量、安全、高效容器管理的開發者而言,Podman 是一個值得關注的選擇。

Docker 容器引擎架構:玄貓的深度解析

Docker 容器引擎是現代軟體開發和佈署的核心根本。在深入研究容器技術多年後,玄貓認為理解 Docker 的底層架構對於充分利用其優勢至關重要。Docker 的架構主要由三個關鍵元件構成:

  • Docker Daemon(守護行程)
  • Docker REST API
  • Docker CLI(命令列介面)

![Docker 架構示意圖](Placeholder for Docker architecture diagram)

Docker Daemon:容器世界的幕後推手

Docker Daemon 是一個在後台執行的行程,負責管理容器的生命週期、映象、網路和儲存卷。它就像一個中央控制中心,監聽來自 Docker 客戶端的請求,並執行相應的操作。

  • 監聽 Docker API 請求:Daemon 持續監聽來自客戶端的 API 請求,例如建立、啟動、停止容器等。
  • 容器管理:Daemon 負責處理、管理和檢查正在執行的容器,確保它們按照預期執行。
  • 資源管理:Daemon 管理 Docker 映象、網路和儲存卷,為容器提供必要的資源。
  • 映象倉函式庫互動:Daemon 與外部的容器映象倉函式庫(例如 Docker Hub)互動,以下載或上傳映象。

玄貓認為,Docker Daemon 的穩定執行是容器環境的基礎。如果 Daemon 停止執行,容器將無法正常工作。

與 Docker Daemon 互動:多種方式

與 Docker Daemon 互動有多種方式,最常見的是透過 Unix Socket。這個 Socket 通常位於主機的檔案系統中:/var/run/docker.sock

ls -la /var/run/docker.sock
srw-rw----. 1 root docker 0 8月 25 12:48 /var/run/docker.sock

這個 Socket 允許客戶端與 Daemon 進行本地通訊。為了安全起見,建議將非 root 使用者增加到 docker 群組,以授權他們與 Docker Daemon 互動。

安全提示:預設情況下,Docker Daemon 沒有啟用額外的安全或身份驗證機制。因此,請務必小心,不要將 Daemon 公開到不受信任的網路。

Docker REST API:程式化的容器管理

Docker REST API 提供了一種程式化的方式來與 Docker Daemon 互動。透過 API,您可以執行各種容器管理操作,例如:

  • 列出容器
  • 建立容器
  • 檢查容器
  • 取得容器日誌
  • 匯出容器
  • 啟動或停止容器
  • 終止容器
  • 重新命名容器
  • 暫停容器

以下是一個使用 curl 命令呼叫 Docker API 的範例,用於取得本地映象的詳細訊息:

curl --unix-socket /var/run/docker.sock \
http://localhost/v1.41/images/json | jq
[
  {
    "Containers": -1,
    "Created": 1626187836,
    "Id": "sha256:be72532cbd81ba4adcef7d8f742abe7632e6f5b35bbd53251e5751a88813dd5f",
    "Labels": {
      "architecture": "x86_64",
      "build-date": "2021-07-13T14:50:13.836919",
      "com.redhat.build-host": "cpt-1005.osbs.prod.upshift.rdu2.redhat.com",
      "com.redhat.component": "ubi7-minimal-container",
      "com.redhat.license_terms": "https://www.redhat.com/en/about/red-hat-end-user-license-agreements#UBI",
      "description": "The Universal Base Image Minimal is a stripped down image that uses microdnf as a package manager. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.",
      "distribution-scope": "public",
      "io.k8s.description": "The Universal Base Image Minimal is a stripped down image that uses microdnf as a package manager. This base image is freely redistributable, but Red Hat only supports Red Hat technologies through subscriptions for Red Hat products. This image is maintained by Red Hat and updated regularly.",
      "io.k8s.display-name": "Red Hat Universal Base Image 7 Minimal",
      "io.openshift.tags": "minimal rhel7",
      "maintainer": "Red Hat, Inc.",
      "name": "ubi7-minimal",
      "release": "432",
      "summary": "Provides the latest release of the minimal Red Hat Universal Base Image 7.",
      "url": "https://access.redhat.com/containers/#/registry.access.redhat.com/ubi7-minimal/images/7.9-432",
      "vcs-ref": "8c60d5a9644707e7c4939980a221ec2927d9a88a",
      "vcs-type": "git",
      "vendor": "Red Hat, Inc.",
      "version": "7.9"
    },
    "ParentId": "",
    "RepoDigests": [
      "registry.access.redhat.com/ubi7/ubi-minimal@sha256:73b4f78b569d178a48494496fe306dbefc3c0434c4b872c7c9d7f23eb4feb909"
    ],
    "RepoTags": [
      "registry.access.redhat.com/ubi7/ubi-minimal:latest"
    ],
    "SharedSize": -1,
    "Size": 81497870,
    "VirtualSize": 81497870
  }
]

這個 API 傳回的 JSON 格式包含了映象的各種中繼資料資訊,例如名稱、大小、建立時間等。

Docker CLI:使用者友好的命令列工具

Docker CLI 是一個命令列工具,允許使用者與 Docker Daemon 互動。它提供了一系列易於使用的命令,用於管理容器、映象、網路和儲存卷。

透過 Docker CLI,開發者可以輕鬆地執行各種容器操作,而無需直接呼叫 REST API。

Docker 的架構設計使其具有高度的靈活性和可擴充套件性。無論您是開發者、維運工程師還是系統管理員,理解 Docker 的底層架構都將幫助您更好地利用容器技術,提高工作效率。

Docker 指令列客戶端:操控 Docker Daemon 的利器

Docker Daemon 就像一個默默工作的引擎,而 Docker 指令列客戶端(Command-Line Interface, CLI)就是你的遙控器。透過這個 CLI,你可以發號施令,讓 Docker Daemon 執行各種任務,管理容器和映像檔。Docker CLI 提供了超過 30 個指令,每個指令都有各自的選項,讓系統管理員和 Docker 使用者能夠精準地控制 Docker Daemon 及其容器。

以下列出一些最常用的 Docker 指令,讓大家對 Docker 的強大功能有個初步瞭解:

  • build:從 Dockerfile 建立映像檔。
  • cp:在容器和本地檔案系統之間複製檔案或資料夾。
  • exec:在執行中的容器內執行指令。
  • images:列出所有映像檔。
  • inspect:回傳 Docker 物件的底層資訊。
  • kill:強制停止一個或多個執行中的容器。
  • load:從 TAR 封存檔或標準輸入載入映像檔。
  • login:登入 Docker Registry。
  • logs:取得容器的日誌。
  • ps:列出執行中的容器。
  • pull:從 Registry 提取映像檔或儲存函式庫。
  • push:將映像檔或儲存函式庫推播到 Registry。
  • restart:重新啟動一個或多個容器。
  • rm:移除一個或多個容器。
  • rmi:移除一個或多個映像檔。
  • run:在新的容器中執行指令。
  • save:將一個或多個映像檔儲存到 TAR 封存檔(預設輸出到標準輸出)。
  • start:啟動一個或多個已停止的容器。
  • stop:停止一個或多個執行中的容器。
  • tag:建立一個指向來源映像檔的目標映像檔標籤。

這只是一小部分,Docker CLI 提供了非常豐富的指令,可以讓你輕鬆管理容器映像檔和執行中的容器,甚至可以匯出容器映像檔或建立新的映像檔。

當你使用 Docker CLI 發出指令時,CLI 會與 Docker Daemon 通訊,指示它執行所需的操作。因此,Daemon 是整個架構的關鍵,必須確保它已啟動並執行,才能使用 Docker CLI 及其 REST API。

Docker 映像檔:容器的藍圖

Docker 映像檔是一種由 Docker 引入的格式,用於管理二進位資料和元資料,作為建立容器的範本。可以將 Docker 映像檔視為一個包裹,其中包含了執行特定程式所需的所有東西,例如執行時期、函式庫等等。

在容器技術的世界中,Docker 映像檔的出現是一個重要的轉捩點。從 1.12 版本開始,Docker 採用了一種映像檔規格,並逐漸演變成現在符合 OCI 映像檔格式規範的版本。

最初的 Docker 映像檔規格包含了許多現在已成為 OCI 映像檔格式規範一部分的概念和欄位,例如:

  • 層(Layer)的列表
  • 建立日期
  • 作業系統
  • CPU 架構
  • 在容器執行時期使用的組態引數

Docker 映像檔的內容(二進位檔案、函式庫、檔案系統資料)是以層(Layer)的方式組織的。一個層只是一組檔案系統變更,不包含任何環境變數或給定指令的預設引數。這些資料儲存在映像檔清單(Image Manifest)中,該清單擁有組態引數。

Docker 映像檔層疊技術:Union Filesystem 的妙用

那麼,這些層是如何建立並彙整到 Docker 映像檔中的呢?答案可能有點複雜。容器映像檔中的層是使用映像檔元資料組合在一起,並合併成單一的檔案系統檢視。雖然有很多方法可以實作這個結果,但目前最常見的方法是使用 Union Filesystem,也就是將兩個檔案系統組合在一起,並提供一個獨特的、壓縮的檢視。

最後,當執行容器時,會在映像檔的頂部建立一個新的、可讀寫的臨時層,這個層會在容器銷毀後遺失。

正如前面所說的,容器映像檔及其分發是 Docker 容器的關鍵特性。因此,接下來將探討容器分發的關鍵要素:Docker Registry。

Docker Registry:容器映像檔的儲藏室

Docker Registry 就像是一個 Docker 容器映像檔的儲藏室,它儲存了容器映像檔的元資料和層,使它們可以被多個 Docker Daemon 使用。

Docker Daemon 透過 HTTP API 作為 Docker Registry 的客戶端,根據 Docker 客戶端的指示推播和提取容器映像檔。

使用容器 Registry 可以幫助在許多獨立的機器上使用容器,這些機器可以被組態為向 Registry 請求一些容器映像檔,如果這些映像檔不存在於 Docker Daemon 的本地快取中。Docker Daemon 設定中預設的 Registry 是 Dockerhub,這是一個由 Docker 公司在雲端託管的軟體即服務(Software-as-a-Service)容器 Registry。然而,Dockerhub 並不是唯一的 Registry,近年來出現了許多其他的容器 Registry。

幾乎每個與容器合作的公司或社群都建立了他們自己的容器 Registry,並具有不同的 Web 介面。Quay.io 是 Dockerhub 的一個免費替代服務,它是由 Red Hat 公司託管的軟體即服務容器 Registry。

除了雲端服務之外,另一個不錯的選擇是在內部佈署 Docker Registry,可以透過在執行 Docker Daemon 的機器上使用一個指令來建立:

$ docker run -d -p 5000:5000 --restart=always --name registry registry:2

本章的目的不是要詳細介紹各種 Docker 選項和組態,但如果你想了解更多關於 Docker Registry 的資訊,可以參考 Docker 的官方檔案:https://docs.docker.com/registry/deploying/

總結來說,Docker 透過 Docker CLI、Docker 映像檔和 Docker Registry 這些關鍵元件,構建了一個強大與靈活的容器化平台,讓開發者可以更輕鬆地建構、佈署和管理應用程式。

當 Docker 停止呼吸:探索 Containerd 的救援任務

身為一個在分散式系統打滾多年的老手,我經常被問到一個問題:「如果 Docker Daemon 突然掛點,我們的容器會怎樣?」這個問題的答案,其實藏在 Docker 架構中一個經常被忽略的關鍵角色裡:Containerd。

Docker Daemon 的生死之交:Containerd 現身

過去,我們總是把焦點放在 Docker API、Client、Daemon 這些元素上,但卻忘了 Docker Daemon 並非永生不死。一旦它停止運作,整個容器生態系都會受到影響。這時候,Containerd 就扮演了救火隊的角色。

先讓我們看看一個健康的 Docker 環境:

[root@fedora34 ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service;
disabled; vendor preset: disabled)
Active: active (running) since Tue 2021-08-31 19:46:57
UTC; 1h 39min ago
TriggeredBy: ● docker.socket
Docs: https://docs.docker.com
Main PID: 20258 (dockerd)
Tasks: 12
Memory: 31.1M
CPU: 1.946s
CGroup: /system.slice/docker.service
└─20258 /usr/bin/dockerd -H fd:// --containerd=/
run/containerd/containerd.sock

從上面的輸出可以看到,Docker Daemon 正在執行中。但仔細一看,你會發現它並不是孤軍奮戰,還有一個好夥伴 Containerd 在背後默默支援。

Containerd:從幕後走向台前

Containerd 的出現,其實是為了將容器管理(包括核心互動)與 Docker Daemon 解耦。它遵循 OCI 標準,並使用 runc 作為容器執行時。簡單來說,Containerd 負責容器的實際執行,而 Docker Daemon 則負責提供 API 和管理介面。

讓我們也來看看 Containerd 的狀態:

[root@fedora34 ~]# systemctl status containerd
● containerd.service - containerd container runtime
Loaded: loaded (/usr/lib/systemd/system/containerd.
service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2021-08-25 12:48:17
UTC; 6 days ago
Docs: https://containerd.io
Process: 4267 ExecStartPre=/sbin/modprobe overlay
(code=exited, status=0/SUCCESS)
Main PID: 4268 (containerd)
Tasks: 43
Memory: 44.1M
CPU: 8min 36.291s
CGroup: /system.slice/containerd.service
├─ 4268 /usr/bin/containerd
├─20711 /usr/bin/containerd-
shim-runc-v2 -namespace moby -id
3901d2600732ae1f2681cde0074f290c1839b1a4b0c63ac
9aaccdba4f646e06a -address /run/containerd/containe>
├─20864 /usr/bin/containerd-
shim-runc-v2 -namespace moby -id
78dc2eeb321433fc67cf910743c0c53e54d9f45cfee8d183
19d03a622dc56666 -address /run/containerd/containe>
└─21015 /usr/bin/containerd-
shim-runc-v2 -namespace moby -id
7433c0613412349833b927efa79a4f589916b12c942003cd
616d45ed7611fc31 -address /run/containerd/containe>

可以看到,Containerd 也在執行中,並且啟動了三個 containerd-shim-runc-v2 子程式。這些 shim 行程,就是實際執行容器的執行者。

現在,讓我們確認一下系統中正在執行的容器:

[root@fedora34 ~]# docker ps
CONTAINER ID IMAGE COMMAND
CREATED STATUS PORTS NAMES
7433c0613412 centos/httpd-24-centos7:latest “container-
entrypoin...” 26 minutes ago Up 26 minutes 8080/tcp, 8443/
tcp funny_goodall
78dc2eeb3214 centos/httpd-24-centos7:latest “container-
entrypoin...” 26 minutes ago Up 26 minutes 8080/tcp, 8443/
tcp wonderful_rubin
3901d2600732 centos/httpd-24-centos7:latest “container-
entrypoin...” 26 minutes ago Up 26 minutes 8080/tcp, 8443/
tcp relaxed_heisenberg

Docker Client 顯示有三個容器正在執行,它們都透過 runc 執行時啟動,由 Containerd 管理,並透過 Docker Daemon 進行組態。

Containerd 的內部運作:模組化的設計

Containerd 的架構設計相當模組化,主要分為以下幾個子系統和模組:

  • Bundle Service:從磁碟映像檔中提取 bundles。
  • Runtime Service:執行 bundles,建立執行時容器。
  • Executor Module:實作容器執行時(即 Runtimes)。
  • Supervisor Module:監控並報告容器狀態(即 Containers)。
  • Snapshot Module:管理檔案系統快照。
  • Events Module:收集和消費事件。
  • Metrics Module:透過 metrics API 匯出各種指標。

Containerd 啟動容器的流程相當複雜,簡單來說可以分為以下幾個步驟:

  1. 透過 Distribution Controller 提取 metadata 和內容。
  2. 使用 Bundle Controller 解壓縮提取的資料,建立快照組成 bundles。
  3. 透過 Runtime Controller 執行剛剛建立的 bundle。

玄貓的觀察:Containerd 的重要性

Containerd 的出現,讓 Docker 的架構更加健壯。即使 Docker Daemon 發生故障,Containerd 仍然可以獨立執行容器,最大程度地減少服務中斷。這對於需要高用性的應用程式來說至關重要。

當然,Containerd 並非萬能。如果 Containerd 本身也發生故障,容器仍然會受到影響。因此,在生產環境中,監控 Docker Daemon 和 Containerd 的健康狀態,並建立完善的容錯機制,才是確保應用程式穩定執行的關鍵。

為何我放棄 Docker Daemon:Podman 的無 Daemon 架構解析

在容器技術的世界裡,Docker 憑藉其簡潔易用的特性,長期佔據著主導地位。然而,隨著容器技術的發展,越來越多的開發者開始尋找更輕量、更安全的替代方案。Podman 正是在這樣的背景下應運而生。

Podman 的無 Daemon 設計哲學

Podman,全名為 POD MANager,是一個無 Daemon 的容器引擎。這意味著,與 Docker 不同,Podman 在執行容器時不需要一個持續在後台執行的 Daemon 服務。對於初次使用的開發者來說,安裝 Podman 後最明顯的感受就是:沒有任何服務需要啟動!這與 Docker 的 Daemon 中心架構形成了鮮明的對比。

安裝完成後,Podman 的二進位檔案同時扮演了命令列介面(CLI)和容器引擎的角色,負責協調容器執行時的執行。接下來,我們將探討 Podman 的運作方式和核心元件。

Podman 命令列工具與 REST API

Podman CLI 提供了豐富的命令集,完整列表可參考官方檔案。以下列出一些常用的命令:

  • build: 從 Containerfile 或 Dockerfile 構建映像檔。
  • cp: 在容器和本地檔案系統之間複製檔案/資料夾。
  • exec: 在正在執行的容器中執行命令。
  • events: 顯示 Podman 事件。
  • generate: 生成結構化資料,例如 Kubernetes YAML 或 systemd 單位檔案。
  • images: 列出本地快取映像檔。
  • inspect: 檢視容器或映像檔的底層資訊。
  • kill: 終止一個或多個正在執行的容器。
  • load: 從容器 TAR 封存檔或標準輸入載入映像檔。
  • login: 登入到容器登入檔。
  • logs: 取得容器的日誌。
  • pod: 管理 Pod。
  • ps: 列出正在執行的容器。
  • pull: 從登入檔提取映像檔或儲存函式庫。
  • push: 將映像檔或儲存函式庫推播到登入檔。
  • restart: 重新啟動一個或多個容器。
  • rm: 移除一個或多個容器。
  • rmi: 移除一個或多個映像檔。
  • run: 在新容器中執行命令。
  • save: 將一個或多個映像檔儲存到 TAR 封存檔(預設輸出到標準輸出)。
  • start: 啟動一個或多個已停止的容器。
  • stop: 停止一個或多個正在執行的容器。
  • system: 管理 Podman(磁碟使用情況、容器遷移、REST API 服務、儲存管理和清理)。
  • tag: 建立一個指向 SOURCE_IMAGE 的 TARGET_IMAGE 標籤。
  • unshare: 在修改後的使用者名稱空間中執行命令。
  • volume: 管理容器卷(列表、清理、建立、檢視)。

對於熟悉 Docker 的開發者來說,Podman CLI 命令與 Docker CLI 命令非常相似,這有助於實作兩者之間的平滑過渡。

與 Docker 不同的是,Podman 不需要執行 Docker Daemon 來執行上述命令。不過,使用者仍然可以選擇執行 Podman 服務,並使其監聽 Unix Socket 以公開原生 REST API。

透過執行以下命令,Podman 將在指定路徑上建立一個 Socket 端點,並監聽 API 呼叫:

$ podman system service --time 0 unix://tmp/podman.sock

如果未提供路徑,則 rootful 服務的預設 Socket 端點為 unix://run/podman/podman.sock,rootless 容器的預設 Socket 端點為 unix://run/user/<UID>/podman/podman.sock

如此一來,使用者就可以向 Socket 端點發出 REST API 呼叫。以下範例查詢 Podman 以取得可用的本地映像檔:

curl --unix-socket /tmp/podman.sock \
     http://d/v3.0.0/libpod/images/json | jq .

上述範例中的 jq 命令用於產生更易讀的 JSON 輸出。

Podman 的核心元件

Podman 的設計目標是盡可能遵守開放標準。因此,其執行時、構建、儲存和網路元件主要依賴於社群專案和標準。以下列出 Podman 的主要元件:

  • libpod: 用於管理容器生命週期,已包含在 Podman 主儲存函式庫中。
  • OCI 執行時: 根據 OCI 規範實作的執行時,例如 crun 和 runc。
  • containers/image: 用於映像檔管理的 Go 語言函式庫,被容器引擎和容器登入檔廣泛使用。
  • containers/storage: 用於管理檔案系統層、容器映像檔和容器卷的 Go 語言函式庫。
  • Buildah: 用於構建 OCI 映像檔的工具和函式庫。
  • Conmon: 用於監控 OCI 執行時的工具,被 Podman 和 CRI-O 使用。

總結來說,Podman 透過其無 Daemon 架構和對開放標準的擁抱,為開發者提供了一種更輕量、更靈活的容器解決方案。透過瞭解 Podman 的核心元件和命令列工具,開發者可以更好地利用 Podman 來管理容器,並構建現代化的應用程式。玄貓認為,隨著容器技術的不斷發展,Podman 將在未來的容器生態系統中扮演越來越重要的角色。