如何减少容器镜像占用的磁盘空间?containerd高效存储配置实用技巧
之前文章讲了containerd的镜像存储原理,镜像在containerd中保存了两份,一份是镜像的原始文件(manifest、config、blob),另一部分是layer解压后的快照。而运行容器只使用了解压后layer快照,对于不需要进行镜像推送场景,原始镜像blob部分存在空间的浪费。在较小磁盘空间下,容易出现镜像占用过多磁盘的情况,那么如何节约空间呢?
本文基于containerd v1.7.15版本
1 如何减少镜像的占用空间
对于通过cri运行容器的方式,比如containerd作为kubernetes的容器运行时、或使用crictl命令运行容器。
containerd的配置文件里的discard_unpacked_layers
设置为true,这样content store里的原始镜像blob部分就会被gc清理掉。注意这个选项只对新拉取的镜像生效。
[plugins]
....
[plugins."io.containerd.grpc.v1.cri"]
.....
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = true
对于ctr image import
命令导入的镜像情况,使用 --discard-unpacked-layers
选项
2 注意事项与风险
但是注意启用discard_unpacked_layers
后不能导出镜像和推送镜像,需要慎重开启这个功能。
3 不建议开启的情况
- 镜像仓库开启定期清理,如果正在运行的容器的镜像在仓库中被删除后,无法将镜像重新推送回镜像仓库,也无法在其他节点运行这个镜像的容器。
- 有充足的磁盘空间
4 建议开启的情况
- 磁盘空间紧张
- 不需要推送镜像
5 底层原理
在ctr -n k8s.io content ls
输出content信息里,manifest的label上会有一个containerd.io/gc.ref.content.l.<index>
的标签,这个标签用来标记那些layer属于这个manifest,如果一个layer没有被任意一个manifes的containerd.io/gc.ref.content.l.<index>
label引用,则会被gc清理掉。
下面是manifest的containerd.io/gc.ref.content.l.<index>
标签样例
containerd.io/gc.ref.content.l.0=sha256:b6425c1785a5de9285e14b515ddb6135b93ca5dd9edb744e1d1916a7a3687405
containerd.io/gc.ref.content.l.1=sha256:d3515376e10b73f645c97cbad9258641916af76ae518be17e163d3bc3bd64312
containerd.io/gc.ref.content.l.2=sha256:1fcc5fefb39fa5244e9673a2cad2f2d4dbc89cb0fbaacdca36c05a90e02af18b
containerd.io/gc.ref.content.l.3=sha256:c2420332d0c7405cccc405b98ff4a1867bd103cbe571f03ac04b7e1cc7408e76
启用discard_unpacked_layers
后,在拉取或导入镜像的时候,manifes的label上不添加containerd.io/gc.ref.content.l.<index>
的标签,即不为镜像的blob没有与manifest产生映射关系,则镜像的blob会被gc清理。
相关代码
// IsLayerType returns true if the media type is a layer
func IsLayerType(mt string) bool {
if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") {
return true
}
// Parse Docker media types, strip off any + suffixes first
switch base, _ := parseMediaTypes(mt); base {
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
MediaTypeDockerSchema2LayerForeign, MediaTypeDockerSchema2LayerForeignGzip:
return true
}
return false
}
.....
// ChildGCLabels returns the label for a given descriptor to reference it
func ChildGCLabels(desc ocispec.Descriptor) []string {
mt := desc.MediaType
if IsKnownConfig(mt) {
return []string{"containerd.io/gc.ref.content.config"}
}
switch mt {
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
return []string{"containerd.io/gc.ref.content.m."}
}
if IsLayerType(mt) {
return []string{"containerd.io/gc.ref.content.l."}
}
return []string{"containerd.io/gc.ref.content."}
}
// ChildGCLabelsFilterLayers returns the labels for a given descriptor to
// reference it, skipping layer media types
func ChildGCLabelsFilterLayers(desc ocispec.Descriptor) []string {
if IsLayerType(desc.MediaType) {
return nil
}
return ChildGCLabels(desc)
}
6 Reference
Can I reduce the size of images in persistent storage (/var/lib/containerd)?
Allow to “discard unpacked layers” when using ctr