自架服務 Docker Zot OCI Registry Harbor 容器映像

Harbor 拖著 PostgreSQL 跟 Redis 才跑得起來、Docker Registry 連刪 tag 都要重掃磁碟:Zot 用一支 Go binary 把私有 OCI Registry 該有的功能整包塞好

自架私有 registry 選項這幾年只剩兩個極端:Distribution 太陽春、Harbor 太肥。CNCF 的 Zot 用單一 Go binary 塞進 UI、cosign 驗章、Trivy 掃描、S3 後端、線上垃圾回收與跨機房同步,補上中小型自架團隊真正需要的中間層。

自架 Docker Hub 替代品這件事,繞來繞去只剩兩個選擇。要嘛跑一支 registry:2,也就是官方的 Distribution,換來的是連刪一個 tag 都得手動觸發 blob 掃描的 1990 年代體驗;要嘛把 Harbor 整套 8 個 container、外加 PostgreSQL 跟 Redis 開下去,光基礎架構就吃掉一台 4 GB 的 VPS。Zot 這個 CNCF 專案存在的意義就是把中間層補起來——一支 Go binary,功能該有的都有,資源佔用像 Distribution,功能像 Harbor。

Zot 於 2022 年進入 CNCF sandbox,2026 年目前穩定版本落在 2.1.x 系列。它跟其他 registry 最大的差異不在單一功能,而在它從第一天就決定不弄自家格式:磁碟上直接就是 OCI Image Layout,Distribution v2 API 是唯一的對外介面,沒有自訂 metadata 也沒有專屬資料庫。想搬去別的 registry,用 oras cp 或直接複製目錄就行。

Distribution 撐不起私有 registry 的三件事

registry:2 這支 image 到今天累積下載量早就破十億,但它本質上只是「把 blob 存到磁碟」這件事包成 HTTP。刪 tag 之後底下的 blob 不會被回收,得停機跑一次 registry garbage-collect;沒有內建 UI,想看倉庫有哪些 image 只能敲 curl /v2/_catalog;沒有簽章驗證、沒有 vulnerability scan、沒有多站同步。

這些缺項在只有一個開發者、一台伺服器的場景下無所謂。但當團隊變成三五個人、映像倉庫超過 200 個之後,每個月要處理的事情——「這個 image 有沒有被誰簽過」、「上禮拜推的那版哪個 CVE 沒修」、「另一機房的 mirror 有沒有同步到最新版」——Distribution 完全不管。

而傳統選擇 Harbor 補上這些洞的方式是把每個功能都拆成獨立服務。Harbor 2.11 的預設 docker-compose 會拉起 nginx、core、jobservice、portal、registry、registryctl、trivy-adapter、log、redis、postgresql 十個 container;正式環境還要再加 Notary、Chartmuseum、Exporter。就算沒開高可用,光把它跑起來 RAM 就要 3 GB 起跳,設定檔一改就得整套重啟。

一個 binary 塞得下多少東西

Zot 的組成剛好相反:核心 registry、Web UI、search、cosign/notation 驗章、Trivy 掃描、Prometheus metrics、跨 registry 同步——全部編進同一支 binary,透過 build tag 決定要不要包進去。官方預先構建的 zot-linux-amd64-minimal 只有 20 MB 出頭,全功能版本的 zot-linux-amd64 也不到 100 MB。

啟動只需要一個 JSON config 跟一個資料目錄。最小可用設定長這樣:

1
2
3
4
5
6
{
"distSpecVersion": "1.1.0",
"storage": { "rootDirectory": "/var/lib/registry" },
"http": { "address": "0.0.0.0", "port": "5000" },
"log": { "level": "info" }
}

跑起來就是一台完整的 OCI 1.1 registry,docker push 立刻可用。要開 UI、搜尋、掃描,就在 extensions 區塊裡追加設定,不用另外拉服務。UI 走的是 React 靜態檔案,直接由 registry 自己 serve,不需要另外一個 nginx。

線上垃圾回收跟去重是實際會用到的功能

Distribution 的垃圾回收必須離線執行,官方文件寫得很清楚:跑之前要先把 registry 設成 read-only 或直接關掉,不然會誤刪正在上傳的 blob。對只在半夜有窗口的營運環境已經很痛,對 CI 一天推幾百次的團隊根本沒辦法用。

Zot 內建的 GC 是線上執行的,透過 deduperetentionPolicy 兩層機制運作。前者用 hard link 把不同倉庫、同一 digest 的 blob 指到同一份實體檔案——實測在有 5 個以上服務共用同一組 base image 的環境下,磁碟用量會少掉 30% 到 50%。後者則按規則保留最近 N 個 tag、N 天內的 tag,或匹配特定 pattern 的 tag,過期的直接淘汰,不需要停機。

值得一提的細節:Zot 的 dedupe 判斷是靠 SHA-256 digest,不是靠檔名,所以即使兩個團隊命名習慣不同、把同一份 image tag 成不同名字,底下的 layer 還是共用同一份實體。這件事在 monorepo 或多環境 CI 場景會很快回本。

Cosign 驗章跟 Trivy 掃描不用另外裝

供應鏈安全這幾年被講到爛,但實際自架 registry 想開啟簽章驗證,Harbor 要另外裝 Notary 或設定 Cosigned webhook;Distribution 則是完全沒得選。Zot 把 cosign 跟 notation 兩套驗章協定都直接編進 binary,在 config 打開 imagetrust extension 就啟用了:

1
2
3
4
5
6
7
8
9
10
11
12
"extensions": {
"trust": {
"enable": true,
"cosign": true,
"notation": true
},
"scrub": { "enable": true, "interval": "24h" },
"search": {
"enable": true,
"cve": { "updateInterval": "6h" }
}
}

一開啟後所有 image 都會顯示簽章狀態,未簽或簽章不符的 image 可以透過 accessControl 直接拒絕拉取。CVE 掃描則是背景執行 Trivy,把結果快取在本地 Bolt 資料庫,UI 上直接標示每個 tag 有幾個 High/Critical。整個過程不需要另外一個服務,也不需要外接資料庫。

多機房同步比想像中省事

跨機房或多環境部署常見的需求是「開發環境的 image 推到 A 站台之後,正式環境的 B 站台自動同步過去」。Distribution 沒有這個功能,Harbor 靠 replication 規則做到但要在 UI 一條一條設。

Zot 的 sync extension 走的是宣告式設定,把來源 URL、輪詢間隔、想同步的倉庫 pattern 寫進 config 就好:

1
2
3
4
5
6
7
8
9
10
11
"sync": {
"registries": [{
"urls": ["https://dev-registry.example.tw"],
"onDemand": true,
"pollInterval": "10m",
"content": [{
"prefix": "myteam/**",
"tags": { "regex": "^v.*", "semver": true }
}]
}]
}

onDemand 打開之後,B 站台收到不存在的 image 拉取請求時會即時去 A 站台抓,抓完存在本地並回應。這個模式特別適合邊緣節點——不需要事先預熱所有 image,用到才拉,拉過就快取。

儲存後端可以直接接物件儲存

正式環境不會想把 blob 全部存在單台伺服器的本地磁碟上。Zot 透過 Rclone-like 的抽象層支援 filesystem、S3、Azure Blob 三種後端,設定改一個欄位就能切換:

1
2
3
4
5
6
7
8
9
10
11
"storage": {
"rootDirectory": "/tmp/zot",
"storageDriver": {
"name": "s3",
"region": "ap-northeast-1",
"bucket": "my-registry",
"regionendpoint": "https://s3.example.tw",
"secure": true
},
"cacheDriver": { "name": "dynamodb" }
}

搭配前面提到的 dedupe 與 GC,這一組設定就能撐起橫向擴展——多台 Zot 節點共用同一個 S3 bucket,透過 DynamoDB 或前置的 Redis 作為 dedupe 快取,前面掛 L4 load balancer 分流。對比 Harbor 要處理 PostgreSQL 主從、Redis Sentinel、shared storage NFS,運維成本天差地遠。

什麼情況不該用 Zot

Zot 不是萬用解。三個場景要考慮清楚:需要 Helm chart repository、需要多租戶 UI 級別的 project 隔離、需要 replication 有可視化的 job history——這三件事 Harbor 做得比較完整。Zot 的 UI 定位是「dev-friendly 的檢視器」,不是給 admin 做細部配額管控的工作台。

另外要注意 Zot 的效能定位。單節點在 SSD 上大約可以撐到每秒幾百次的 manifest 存取,超過這個規模就要考慮多節點加共用物件儲存。對絕大多數自架團隊而言這條上限離得很遠,但如果流量已經在 CDN 級,Zot 未必比得過商用方案。

該不該從 Distribution 或 Harbor 遷過來

從 Distribution 遷過來幾乎沒有痛點。Distribution 存的就是 OCI Image Layout,Zot 讀的也是 OCI Image Layout,把資料目錄整個複製過去、掛上 Zot 就繼續跑,唯一要重新設定的是認證方式跟 UI 想開哪些 extension。實測搬 200 個倉庫、大約 40 GB 的 blob,rsync 完之後 Zot 大概花 30 秒掃描一次目錄,UI 就把所有 image 都列出來了。

從 Harbor 遷則要用 skopeo copyoras cp 逐個倉庫搬,因為 Harbor 的 project 概念在 Zot 沒有對應。多花點時間但值得——之後不用再維護一整包 docker-compose、不用擔心 PostgreSQL 升級搞壞 metadata、config 改完 SIGHUP 一下就生效,而不是整套重啟。

私有 registry 是自架服務裡最容易被忽略的一環。一台跑得穩、能掃 CVE、能驗簽章、能跨機房同步的 registry,對整條 CI/CD pipeline 的可靠度影響遠比選哪個 orchestrator 大。

需要在臺灣本地部署低延遲的私有 registry,NCSE Network 提供高效能 NVMe SSD 的 VPS 主機與跨機房 IP Transit 服務,可以直接把 Zot 架起來,並串接南港與其他地點做多站同步。歡迎至 ncse.tw 了解 VPS 主機方案。

需要穩定的雲端主機?

NCSE Network 提供企業級 VPS,7 天免費試用,臺灣是方電訊機房,99% SLA 保證。

查看 VPS 方案 →