作者:贾校磊,中国移动云能力中心软件研发工程师,专注于云原生、微服务、算力网络等
容器技术是 Kubernetes 成功的基石之一,而容器镜像则是容器技术的核心概念之一,它将应用程序、运行时环境以及所有依赖项打包到一个独立的可执行单元中。它不仅实现了应用程序与其运行时环境的高度隔离,还为应用的构建、交付和扩展提供了前所未有的便捷性。在 Kubernetes 中,容器镜像不仅是应用的部署单位,也是实现轻量、可移植和可复制的关键。
本文将深入探讨 Kubernetes 中容器镜像的各个方面,从容器镜像的基本概念开始,一直到高级主题,如镜像拉取策略、安全性和最佳实践。
容器镜像名称通常由两部分组成:仓库名称(Repository Name)和标签(Tag)。让我们对这两个部分进行详细解析。
example/app
表示一个名为 app
的容器镜像位于 example
仓库下。Docker 配置文件v1.0
或者 sha256:abcd1234
。latest
,这可能导致不可预期的行为,因此在生产环境中最好避免使用 latest
。容器镜像名称的构成并非单纯的字符组合,它背后有着一些重要的原理和约定。
/
)来表示层级结构。例如,myrepo/myapp
。username/myapp
。latest
标签,因为它会导致不可控的版本变化,不利于环境的稳定性。容器镜像拉取策略定义了 Kubernetes 在启动容器时应该如何获取镜像。主要有以下几种拉取策略:
为了确保 Pod 总是使用相同版本的容器镜像,你可以指定镜像的摘要;将 <image-name>:<tag>
替换为 <image-name>@<digest>
,例如 image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
。
当使用镜像标签时,如果镜像仓库修改了代码所对应的镜像标签,可能会出现新旧代码混杂在 Pod 中运行的情况。镜像摘要唯一标识了镜像的特定版本,因此 Kubernetes 每次启动具有指定镜像名称和摘要的容器时,都会运行相同的代码。通过摘要指定镜像可固定你运行的代码,这样镜像仓库的变化就不会导致版本的混杂。
当你(或控制器)向 API 服务器提交一个新的 Pod 时,你的集群会在满足特定条件时设置 imagePullPolicy
字段:
imagePullPolicy
字段,并且你为容器镜像指定了摘要, 那么 imagePullPolicy
会自动设置为 IfNotPresent
。imagePullPolicy
字段,并且容器镜像的标签是 :latest
, imagePullPolicy
会自动设置为 Always
。imagePullPolicy
字段,并且没有指定容器镜像的标签, imagePullPolicy
会自动设置为 Always
。imagePullPolicy
字段,并且为容器镜像指定了非 :latest
的标签, imagePullPolicy
就会自动设置为 IfNotPresent
。拉取策略可以通过容器的 imagePullPolicy
字段进行配置。这个字段可以在 Pod 的容器描述中设置,也可以在 Deployment、StatefulSet 等控制器的模板中设置。以下是一个例子:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mycontainer
image: myregistry/myimage:latest
imagePullPolicy: IfNotPresent
在上述示例中,imagePullPolicy
被设置为 IfNotPresent
,表示使用 IfNotPresent 拉取策略。
使用容器运行时创建 Pod 时,当容器无法启动并且处于等待状态时,可能会出现 ImagePullBackOff
的状态。这表示容器无法被启动,因为 Kubernetes 无法成功拉取容器镜像,导致了一种回退的等待状态。 BackOff
部分表示 Kubernetes 将继续尝试拉取镜像,并增加回退延迟。Kubernetes 会增加每次尝试之间的延迟,直到达到编译限制,即 300 秒(5 分钟)。
出现上述问题的可能原因:
ImagePullSecrets
来访问私有仓库,确保这些密钥正确配置且可用。在 Kubernetes 中,镜像的拉取可以以串行或并行的方式进行。
在默认情况下,kubelet 以串行方式拉取容器镜像。这意味着,kubelet会一次只向镜像服务发送一个拉取请求。在处理一个镜像拉取请求时,其他请求必须等待,直到当前请求完成。
这种方式的优点是简单且稳定。每个节点独立地决定镜像拉取的顺序,即使使用串行拉取,不同节点也可以并行拉取相同的镜像。
如果你想启用并行镜像拉取,可以在 kubelet 的配置中将 serializeImagePulls
字段设置为 false
。这种情况下,kubelet 会立即向镜像服务发送多个镜像拉取请求,允许多个镜像同时被拉取。
并行拉取可以提高镜像拉取的效率,特别是在大型集群中。然而,需要确保容器运行时的镜像服务能够处理并行镜像拉取,以防止网络带宽或磁盘 I/O的过度消耗。
从 Kubernetes v1.27 版本开始,引入了 maxParallelImagePulls
这一特性,用于限制同时拉取的镜像数量。这个值可以在 kubelet 配置中设置。当设置了 maxParallelImagePulls
时,kubelet 将限制同时拉取的镜像数量,防止过多的网络带宽或磁盘 I/O的使用。
注意事项:
容器技术的普及导致了在不同体系结构的计算机上运行容器化应用的需求。例如,x86 架构的服务器和 ARM 架构的嵌入式设备可能需要不同的二进制文件。在 Kubernetes 中,带镜像索引的多架构镜像允许容器仓库提供容器镜像的多个架构版本。这样,可以根据使用的机器体系结构,选择正确的二进制镜像。
Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)
,以下是一个带有多架构支持的镜像索引示例:
{ "manifests": [ { "image": "pause-amd64", "platform": { "architecture": "amd64", "os": "linux" } }, { "image": "pause-arm", "platform": { "architecture": "arm", "os": "linux" } }, { "image": "pause-ppc64le", "platform": { "architecture": "ppc64le", "os": "linux" } } ] }
在上述示例中,pause
镜像的不同版本适用于不同的体系结构。
当从私有镜像仓库中拉取镜像时,你可能需要提供凭据以进行身份验证。在 Kubernetes 中,凭据可以以 Secret 对象的形式提供。以下是一些常见的方式来提供私有仓库的凭据:
Docker 配置文件:
kubectl create secret
命令可以将 Docker 配置文件转换为 Kubernetes Secret,如下所示:
kubectl create secret generic <secret-name> --from-file=.dockerconfigjson=<path/to/.docker/config.json> --type=kubernetes.io/dockerconfigjson
这样创建的 Secret 可以在 Pod 中通过 imagePullSecrets
字段引用。手动创建 Secret:
kubectl create secret docker-registry <secret-name> \
--docker-server=<registry-server> \
--docker-username=<username> \
--docker-password=<password> \
--docker-email=<email>
同样,这个 Secret 可以在 Pod 中通过 imagePullSecrets
字段引用。使用 Service Account:
kubectl create serviceaccount <service-account-name>
kubectl patch serviceaccount <service-account-name> -p '{"imagePullSecrets": [{"name": "<secret-name>"}]}'
最后,在 Pod 的配置中引用 Service Account:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
serviceAccountName: <service-account-name>
containers:
- name: mycontainer
image: myprivate.registry.com/myimage:latest
这些方法允许你以不同的方式将私有仓库的凭据提供给 Kubernetes Pod,以确保在拉取镜像时能够成功进行身份验证。选择最适合你部署需求的方法,并根据实际情况配置相关的 Secret 或 Service Account。
在 Kubernetes 中,容器镜像的使用涉及到一些最佳实践,以确保集群的稳定性、可维护性和安全性。以下是一些关于 Kubernetes 容器镜像的最佳实践:
使用版本标签: 始终为容器镜像使用版本标签,而不是使用 latest
。这有助于确保你的应用程序在部署时使用的是明确的版本,避免由于 latest
标签而导致的不确定性。
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mycontainer
image: myregistry/myimage:v1.0.0
最小化镜像层数: 精简容器镜像的层数有助于减小镜像大小,加速镜像的拉取和部署过程。使用轻量的基础镜像,仅包含应用程序运行所需的最小依赖。
安全审查: 定期审查容器镜像,确保其基础镜像和应用程序组件的安全性。使用容器扫描工具来检查镜像中的潜在漏洞,并及时更新镜像以修复已知的安全问题。
避免使用latest
标签: 避免在生产环境中使用 latest
标签,因为它使得难以追踪应用程序的版本。使用明确的版本标签,例如 v1.2.3
,以确保版本的一致性。
使用私有仓库: 部署时使用私有容器镜像仓库,以确保镜像的安全性和控制权。私有仓库可以实现身份验证、访问控制和镜像版本管理。
最小权限原则: 在容器中使用最小的权限原则,避免以 root 用户身份运行应用程序。定义适当的用户和权限,以减小潜在的安全风险。
使用环境变量: 将配置信息作为环境变量传递给容器,而不是硬编码在容器镜像中。这样做可以使应用程序更易于配置和管理。
健康检查和就绪检查: 在容器中实现健康检查和就绪检查,以确保容器的正常运行。Kubernetes 使用这些检查来确定何时将流量引导到容器,以及何时重新启动故障的容器。
资源限制: 明确设置容器的资源请求和限制,以确保集群资源的合理分配和预防容器资源耗尽的问题。
使用 liveness 和 readiness 探针: 定义适当的 liveness 和 readiness 探针,以确保容器的正常运行并根据其健康状态进行流量管理。
持续集成和持续部署(CI/CD): 集成容器镜像构建和发布到容器仓库的流程到 CI/CD 管道中,以实现自动化和快速部署。
合理选择基础镜像: 选择合适的基础镜像,考虑到你的应用程序的需求。避免使用不必要的组件和服务,以减小容器的攻击面和镜像大小。
这些最佳实践有助于提高 Kubernetes 中容器镜像的管理效率、安全性和可维护性。在实际部署中,根据具体需求和场景,可能还需要进一步定制和调整这些最佳实践。
1.https://kubernetes.io/docs/concepts/containers/images/
2.https://blog.51cto.com/goody/7448155