
大多数容器镜像都是使用Dockerfiles构建的,其中包含一些指令组合,如FROM,RUN,COPY,ENTRYPOINT 等,以构建符合OCI标准的镜像层。但有一条指令却很少被使用,那就是LABEL 。在这篇文章中,我们将探讨标签(OCI镜像规范中的 "注释")是什么,一些标准化的用途,以及一些你可以用来增强容器安全状况的做法。
在本文的其余部分,我们将把这些称为标签,而不是注释,因为这是更常用的术语。让我们开始吧!
什么是Docker镜像标签?
Docker镜像标签是一种为你的镜像本身添加键值元数据的方式。这些数据不会暴露给针对该镜像运行的容器,而是对编码有价值的东西,如镜像的源代码在哪里,谁支持该镜像,或什么CI构建创建它。
Docker / OCI镜像元数据解释
如果你以前构建过任何种类的软件包,你就知道--在大多数情况下--它们的模型包括软件、配置,有时还包括功能数据以及关于软件包本身的元数据。
例如,虽然Java.jar 文件基本上只是.zip 档案,但它们都有一个顶级的META-INF 目录,其中包含几个文件和目录,根据Java 2 Platform 规范,"......被 Java 2 Platform 识别和解释,以配置应用程序、扩展、类加载器和服务"。.jar如果我们打开一个由流行的Maven构建工具构建的.jar ,通常会有(除其他外)一个maven 目录,其中有用于构建pom.xml 和pom.properties 等有效内容。(参考 "POM "代表Maven的项目 对象 模型)
RPM、APT、NPM和大多数其他打包工具都有类似的元数据存储,这些元数据在安装或运行所含软件的过程中被工具使用,或者被软件库或运行时监控系统用于实用目的。

容器图像也有元数据存储在其层中。如果你列出一个镜像的 "历史",你会经常看到零字节大小的层,因为它们不包含文件系统的变化,而是要在运行时使用的元数据,通常由Dockerfile命令添加。
USER:在哪个用户下运行该进程ENV:要在进程环境中设置的变量(和它的值)。ARG:传递到容器中的构建参数,在构建的范围内像环境变量一样使用CMD:用于启动进程的命令和/或参数(ENTRYPOINT是类似的)。LABEL:不被运行时引擎使用的键/值对
这些项目中的大多数都是众所周知的,并在每个Docker文件中使用,但由于标签元数据并不是运行你的容器所需要的东西,所以它经常被忽视了。
为什么你应该使用容器镜像标签
为你的镜像使用标签有很多原因,比如记录版本,包括项目维护者的联系信息,甚至是运行时的使用信息。最常见的用例之一是记录镜像的构建信息,这可以作为镜像工件的软件供应链中的信息。
Docker标签/OCI图像注释的元数据类型
标准化的标签
图像创建/起源元数据是如此常用,以至于OCI团队发布了一套标准化的密钥,都以"org.opencontainers.image."为前缀,包括。
source:获取构建图像的源代码的URLrevision:打包软件的源代码控制修订标识符base.digest:镜像的摘要(哈希值),这个镜像是基于此的。base.name:该图像所基于的图像参考。version:包装软件的版本
自定义标签
由于它们是简单的键值对,你的项目/组织可以指定几乎任何他们想要的东西。一些可能的想法(所有这些想法都可以用类似 "com.mycorp.myteam."的前缀)。
ci-build:产生图像的CI项目运行的URLreleasenotes:被打包的软件的发布说明healthz:用于进行健康检查的HTTP端点docker.run:从该镜像运行的Docker命令示例k8s.deployment:使用此镜像进行Kubernetes部署的Base64 YAML
最后一个例子很有意思,因为,是的,你可以把几乎所有你能用base64编码的东西存储为标签键的值。这意味着你实际上可以拉出这个图像,然后运行类似下面的程序,得到一个YAML部署文件的样本,然后你可以将其用于Kubernetes集群。
$ docker image inspect myimage:tag | jq -r ".[].Config.Labels.\"com.mycorp.myteam.k8s.deployment\"" | base64 -d
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: snyk
name: snyk
spec:
replicas: 1
selector:
matchLabels:
app: snyk
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: snyk
spec:
containers:
- image: ericsmalling/snyklabeldemo:m
name: snyklabeldemo
resources: {}
status: {}
你甚至可以把它直接输入到kubectl ,然后直接部署,不需要Helm图表或Git repo中单独的YAML文件。
docker image inspect myimage:tag | jq -r ".[].Config.Labels.\"com.mycorp.myteam.k8s.deployment\"" | base64 -d | kubectl apply -f -
deployment.apps/snyk created
利用Docker标签/OCI注解
正如你所想象的,标签可以提供给你的CI系统所能访问的任何值,并且可以用来将这些镜像和/或正在运行的容器与它们的来源、文档等联系起来,只需检查它的标签。例如,假设你的组织发布了带有OCI标准org.opencontainers.image.source 标签的图像,该标签有该图像来源的SCM库的URL。如果你想知道在一个给定的Docker主机上运行的所有资源库的镜像是什么,你可以运行这样的东西。
$ docker inspect $(docker ps -q) --format='{{ .Id }} {{ index .Config.Labels "org.opencontainers.image.source" }}'
17f4ee967870c49d3ffeb1c49973071c99c63377b2f9bbf987f7c3e4a21d331c https://repo.mycorp.com/team-volton/redlion
c958ffc87c2bd5af500d24eff1ccb3ee21992a5cbcb429fafcc651aa182b66ba <no value>
输出显示了两个正在运行的容器的ID,其中一个容器有标签,所以它已经打印了它的值。
当在Kubernetes集群中运行时,事情变得有点复杂,你很可能无法访问集群节点上的容器引擎套接字。不幸的是,没有API可以从kubectl ,所以我们必须变得更聪明一些。下面的bash脚本将找到在当前上下文命名空间中运行的图像,然后它将查询图像注册表以获得图像元数据并返回标签信息。
$ cat labelgrep.sh
#!/bin/bash
FINDLABEL=$1
FINDVAL=$2
IMAGES=$(kubectl get pods -o json | jq -r ".items[].spec.containers[].image" | uniq)
for i in $IMAGES; do
VAL=$(regctl image inspect ${i} --format '{{ index .Config.Labels "'${FINDLABEL}'" }}')
if [[ "$VAL" != "" && ( "$FINDVAL" == "" || "$VAL" == "$FINDVAL") ]]; then
echo "[${i}] ${FINDLABEL}=${VAL}"
fi
done
请注意,我使用的是来自开源regclient项目的优秀工具regctl ,它允许我直接从注册表中获得关于图像的信息,而不是将图像拉到我的本地环境中进行检查。这也让我可以在任何地方运行这个脚本,而不需要有一个容器运行时引擎。
现在,让我们针对一个集群运行这个脚本,搜索在我的集群中运行的镜像的仓库。
$ ./labelgrep.sh org.opencontainers.image.source
[images.mycorp.com/voltron/redlion] org.opencontainers.image.source=https://repo.mycorp.com/team-volton/redlion
[images.mycorp.com/voltron/bluelion] org.opencontainers.image.source=https://repo.mycorp.com/team-volton/bluelion
正如你所看到的,我的集群目前有两个图像,它们在pod中运行,其标签为org.opencontainers.image.source label。
虽然这些是比较简单的例子,但我相信你可以在这些概念上进行扩展,建立你自己的脚本或API调用,以适应你的组织的需要。
总结
总之,经常被遗忘的图像标签/注释是一个强大的工具,可以让你在图像中直接插入元数据。使用它们和标准化的密钥可以帮助你的努力,不仅可以记录图像的来源,还可以被部署和安全工具所利用,帮助你更好地了解你的部署情况。