如何减少容器镜像占用的磁盘空间?containerd高效存储配置实用技巧

之前文章讲了containerd的镜像存储原理,镜像在containerd中保存了两份,一份是镜像的原始文件(manifest、config、blob),另一部分是layer解压后的快照。而运行容器只使用了解压后layer快照,对于不需要进行镜像推送场景,原始镜像blob部分存在空间的浪费。在较小磁盘空间下,容易出现镜像占用过多磁盘的情况,那么如何节约空间呢?

本文基于containerd v1.7.15版本

对于通过cri运行容器的方式,比如containerd作为kubernetes的容器运行时、或使用crictl命令运行容器。

containerd的配置文件里的discard_unpacked_layers设置为true,这样content store里的原始镜像blob部分就会被gc清理掉。注意这个选项只对新拉取的镜像生效。

1
2
3
4
5
6
[plugins]
....
[plugins."io.containerd.grpc.v1.cri"]
.....
[plugins."io.containerd.grpc.v1.cri".containerd]
discard_unpacked_layers = true

对于ctr image import命令导入的镜像情况,使用 --discard-unpacked-layers选项

但是注意启用discard_unpacked_layers后不能导出镜像和推送镜像,需要慎重开启这个功能。

  • 镜像仓库开启定期清理,如果正在运行的容器的镜像在仓库中被删除后,无法将镜像重新推送回镜像仓库,也无法在其他节点运行这个镜像的容器。
  • 有充足的磁盘空间
  • 磁盘空间紧张
  • 不需要推送镜像

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>标签样例

1
2
3
4
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清理。

相关代码

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/images/mediatypes.go#L189-L215

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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)
}

Can I reduce the size of images in persistent storage (/var/lib/containerd)?

Allow to “discard unpacked layers” when using ctr

discard_unpacked_layers=true doesn’t work when using ctr

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/pkg/cri/server/image_pull.go#L182-L186

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/pkg/cri/sbserver/image_pull.go#L180-L184

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/cmd/ctr/commands/images/import.go#L237

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/import.go#L110-L117

https://github.com/containerd/containerd/blob/926c9586fe4a6236699318391cd44976a98e31f1/import.go#L223-L225

相关内容