Containerd 的 image 儲存機制到 content, snapshot 的概念介紹
從 containerd 的 metadata 說起,再講到 image 是如何從 remote registry 拉下來,與 containerd 是如何用 content 記錄 image 的資訊,最後帶到真正運作的實體檔案 snapshot。大概介紹了 image 到預備 run container 的流程吧 (๑•̀ㅂ•́)و✧
上面那篇協助我釐清很多 containerd 資料夾中的問題,有夠讚。
containerd/docs/content-flow.md at main · containerd/containerd (github.com)
上面這篇則是官網的介紹,明確的介紹 imae 到 container 中的檔案儲存的流程!
Containerd image 的儲存機制
- key-value 資料會放在
io.containerd.metadata.v1.bolt
,內部會描述 image, container 等的所有資料。 - image 的 layer 會儲存在
io.containerd.content.v1.content
以 sha256 的數值出現 io.containerd.content.v1.content
解壓縮後的 data 或是運行的 layer 會放在io.containerd.snapshotter.v1.$type
什麼是 io.containerd.metadata.v1.bolt
?
containerd 使用 boltDB 儲存 metadata
BoltDB is an embedded ACID key/value database written in Go. It supports MVCC with shadow paging by allowing only one writer and multiple readers. All transactions execute in BoltDB under serializable isolation. BoltDB stores key/value pairs in B+tree data storage.
// keys.
// ├──version : <varint> - Latest version, see migrations
// └──v1 - Schema version bucket
// ╘══*namespace*
// ├──labels
// │ ╘══*key* : <string> - Label value
// ├──image
// │ ╘══*image name*
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──target
// │ │ ├──digest : <digest> - Descriptor digest
// │ │ ├──mediatype : <string> - Descriptor media type
// │ │ └──size : <varint> - Descriptor size
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──containers
// │ ╘══*container id*
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──spec : <binary> - Proto marshaled spec
// │ ├──image : <string> - Image name
// │ ├──snapshotter : <string> - Snapshotter name
// │ ├──snapshotKey : <string> - Snapshot key
// │ ├──runtime
// │ │ ├──name : <string> - Runtime name
// │ │ ├──extensions
// │ │ │ ╘══*name* : <binary> - Proto marshaled extension
// │ │ └──options : <binary> - Proto marshaled options
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──snapshots
// │ ╘══*snapshotter*
// │ ╘══*snapshot key*
// │ ├──name : <string> - Snapshot name in backend
// │ ├──createdat : <binary time> - Created at
// │ ├──updatedat : <binary time> - Updated at
// │ ├──parent : <string> - Parent snapshot name
// │ ├──children
// │ │ ╘══*snapshot key* : <nil> - Child snapshot reference
// │ └──labels
// │ ╘══*key* : <string> - Label value
// ├──content
// │ ├──blob
// │ │ ╘══*blob digest*
// │ │ ├──createdat : <binary time> - Created at
// │ │ ├──updatedat : <binary time> - Updated at
// │ │ ├──size : <varint> - Blob size
// │ │ └──labels
// │ │ ╘══*key* : <string> - Label value
// │ └──ingests
// │ ╘══*ingest reference*
// │ ├──ref : <string> - Ingest reference in backend
// │ ├──expireat : <binary time> - Time to expire ingest
// │ └──expected : <digest> - Expected commit digest
// └──leases
// ╘══*lease id*
// ├──createdat : <binary time> - Created at
// ├──labels
// │ ╘══*key* : <string> - Label value
// ├──snapshots
// │ ╘══*snapshotter*
// │ ╘══*snapshot key* : <nil> - Snapshot reference
// ├──content
// │ ╘══*blob digest* : <nil> - Content blob reference
// └──ingests
// ╘══*ingest reference* : <nil> - Content ingest reference
上面是 containerd 的 meta.db 的 schema。
# key
v1/$namespace/(containers,content,images,leases,snapshots)/$name
# kubernetes 中的 namespace 基本上都是 k8s.io,所以假設要讀取 nginx image 的資訊
v1/k8s.io/images/nginx
如何讀取 bolt 的檔案
因自身對 python 比較熟悉,所以用 python package — boltdb 來作範例
from boltdb import BoltDB
# 注意是 meta.db,不是 metadata.db
metadb = "containerd/io.containerd.metadata.v1.bolt/meta.db"
db = BoltDB(metadb)
with db.view() as tx:
bucket = tx.bucket(b"v1")
for item in bucket:
print(item)
根據上面的 containerd db schema,若是想要查看 image 的資訊
with db.view() as tx:
bucket = tx.bucket(b"v1").bucket(b"k8s.io").bucket(b"images")
for item in bucket:
print(item)
Image 的 content 介紹
參考網站:https://github.com/containerd/containerd/blob/main/docs/content-flow.md
image 會根據不同的 arch/os 有不同的 image
- 向 image registry (dockerhub) 取得 image 的 descriptor (JSON)
- 確定
mediaType
的格式 - 若
mediaType=index
,從 descriptor 中找到要運行 container 的平台 (architecture + OS, ex: amd64+linux),取得 image 的 manifest - 若
mediaType=manifest
,則直接拿來用 - 根據 manifest 上中 contents 的 hash 來獲取 blob content
manifest 的資訊
名詞說明:image 會有許多層的 content,像是 index, manifest, config 及 image 資料的 layer 類別
再來拿 pause image 來作範例
查看 local image 的資訊
ctr -n k8s.io content ls | grep pause | awk '{print $1}'
共有三層 image content,再下來透過 ctr -n k8s.io content get $sha
來查看內容
查看 image 的 label 含意
有三個 content,分別為 content(4873*)、content(961e*)、content(bc7a*)
containerd.io/distribution.source.$imageRegistry=$repo
: 從 10.240.245.141/pause 取得 image- layer (961e*) 裡的
containerd.io/uncompressed=$sha
: 有這個 label,代表為 image 的 layer。此數值為 layer 未壓縮前的 hash value containerd.io/gc.ref.
: 說明此 content 與 garbage collection 有關聯。是受到保護,不會被清理。- 有
containerd.io/gc.ref.snapshot.overlayfs
為 image 裡的 config,即 content (4873*)。此 label 說明 config 跟 snapshot 連動。 - 有
containerd.io/gc.ref.content.config
與containerd.io/gc.ref.content.l.<indexSequence>
為 image 的 manifest。manifest 會優先確保 config 與 layer 都會保留,不會被 garbage collection 清除
統整
- config(4873*): snapshot(961e)
- layer(961e*): layer0
- manifest(bc7a*): 與 config(4873*) / layer(961e*) 連動
查看 Content 的內容
- bc7a: manifest
- 4876: config
- 961e: layer
bc7a content — manifest
4873
content — config
961e content — layer
接下去,拿比較複雜的 image 作範例
Content 轉變成 Snapshots
範例改成 cilium 的 image
Cilium image content
- 🟥 config (526b*):snapshot (cb140*)
- 🟨 manifest (a2a2*):共 7 層 layer (5785 -> b315 -> 0f9c -> b505 -> 0262 -> b640 -> 1bc2)
- layers: 可看到 uncompressed
Snapshot 的運作
從 config content 可以查看到 snapshot 的 ID
ctr -n k8s.io snapshot ls | grep $snapshotID
- Snapshotter 建立空白的 snapshot,目前處於 active 狀態
- Diff applier 會比較 blob 的資訊,套用在 active 的 snapshot
- 提交目前的 diff,snapshot 轉換成 committed
- committed snapshot 將成為 parent,並且重複 1–3 步驟
因此 content unpack 在 snapshots 中,是以 diff 疊加上去,所以只有第一層的 hash 值是相同 (解壓縮後的 hash),後續的 snapshot hash 值跟對應層數的 content layer 是不一樣的。
Snapshot 要如何與 content 對應
首先,可透過 ctr snapshot tree
查看 snapshot 父子關聯。
- config content 有記錄最後一層 snapshot 的 hash 值。這邊的範例即是 cb140 snapshot。所有的 container 都是從這層開始往上長,成為 active snapshot,所以必須追蹤他!
- 上面提到 config content 有
containerd.io/gc.ref.snapshot.overlayfs
的 label,也是避免被 garbage collection - layer0 content 解壓縮後會與 snapshot tree 的 root 有一樣的 hash 值
- 從 manifest content 取得 layer0 的 hash 值
- 從 layer0 content 取得 layer0 解壓縮後的 hash 值
- 可對應到 snapshot tree 的 root 與其相同!
image snapshot 後的 container
Snapshot 是以疊加的方式往上長,每層 image layer 完成 diff,會轉換成 Commited。
- 沒有 container 的 image,最後一層會是處於 Commited。
- 有 container 的 image,會如同上面的圖一樣,會有 snapshot 處在 Active