作者:贾校磊,中国移动云能力中心软件研发工程师,专注于云原生、微服务、算力网络等
01 前言
容器技术是 Kubernetes 成功的基石之一,而容器镜像则是容器技术的核心概念之一,它将应用程序、运行时环境以及所有依赖项打包到一个独立的可执行单元中。它不仅实现了应用程序与其运行时环境的高度隔离,还为应用的构建、交付和扩展提供了前所未有的便捷性。在 Kubernetes 中,容器镜像不仅是应用的部署单位,也是实现轻量、可移植和可复制的关键。
本文将深入探讨 Kubernetes 中容器镜像的各个方面,从容器镜像的基本概念开始,一直到高级主题,如镜像拉取策略、安全性和最佳实践。
02 镜像名称
容器镜像名称的构成
容器镜像名称通常由两部分组成:仓库名称(Repository Name)和标签(Tag)。让我们对这两个部分进行详细解析。
- 仓库名称(Repository Name):
- 仓库名称用于标识容器镜像所存储的地方。它类似于软件仓库的概念,可以将其视为容器镜像的存储库。
- 仓库名称通常以路径的形式组织,以表示层级关系。例如,
example/app
表示一个名为app
的容器镜像位于example
仓库下。Docker 配置文件 - 如果省略仓库名称,Kubernetes 默认使用 Docker Hub 作为容器仓库。
- 标签(Tag):
- 标签是容器镜像的版本标识。它使我们能够区分不同版本的同一容器镜像。
- 常见的标签包括软件版本号、提交哈希值或者其他表示版本关系的字符串。例如,
v1.0
或者sha256:abcd1234
。 - 如果省略标签,Kubernetes 默认使用
latest
,这可能导致不可预期的行为,因此在生产环境中最好避免使用latest
。
容器镜像名称的原理
容器镜像名称的构成并非单纯的字符组合,它背后有着一些重要的原理和约定。
- 仓库名称的约定:
- 仓库名称应该是小写的,并且可以包含斜杠(
/
)来表示层级结构。例如,myrepo/myapp
。 - 对于 Docker Hub 的公共仓库,仓库名称通常包含用户名,例如
username/myapp
。
- 仓库名称应该是小写的,并且可以包含斜杠(
- 标签的约定:
- 标签应该遵循语义化版本(Semantic Versioning)的规范,以确保版本号的可预测性和一致性。
- 避免使用
latest
标签,因为它会导致不可控的版本变化,不利于环境的稳定性。
03 镜像拉取策略
镜像拉取策略
容器镜像拉取策略定义了 Kubernetes 在启动容器时应该如何获取镜像。主要有以下几种拉取策略:
- IfNotPresent:
- 如果容器镜像在本地不存在(本地缓存中没有),则尝试从远程仓库拉取。如果本地已存在,则直接使用本地缓存的镜像。这是默认的拉取策略。
- Always:
- 每次启动容器时,都尝试从远程仓库拉取最新的镜像。即使本地已经存在相同版本的镜像,也会拉取最新的版本。
- Never:
- 容器不会尝试从远程仓库拉取镜像。只有在本地存在所需版本的镜像时,容器才会启动。适用于完全离线的环境或者需要手动预先拉取镜像的情况。
为了确保 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 拉取策略。
ImagePullBackOff
使用容器运行时创建 Pod 时,当容器无法启动并且处于等待状态时,可能会出现 ImagePullBackOff
的状态。这表示容器无法被启动,因为 Kubernetes 无法成功拉取容器镜像,导致了一种回退的等待状态。 BackOff
部分表示 Kubernetes 将继续尝试拉取镜像,并增加回退延迟。Kubernetes 会增加每次尝试之间的延迟,直到达到编译限制,即 300 秒(5 分钟)。
出现上述问题的可能原因:
- 无效的镜像名称: 检查容器的镜像名称是否正确,包括镜像名称的拼写、大小写等问题。
- 私有仓库拉取问题: 如果使用私有容器镜像仓库,可能需要提供正确的认证信息,如用户名、密码或密钥。
- 镜像不存在: 如果指定的容器镜像在仓库中不存在,Kubernetes 将无法拉取镜像。
- ImagePullSecrets 未正确配置: 如果使用了
ImagePullSecrets
来访问私有仓库,确保这些密钥正确配置且可用。
串行和并行镜像拉取
在 Kubernetes 中,镜像的拉取可以以串行或并行的方式进行。
1. 串行镜像拉取:
在默认情况下,kubelet 以串行方式拉取容器镜像。这意味着,kubelet会一次只向镜像服务发送一个拉取请求。在处理一个镜像拉取请求时,其他请求必须等待,直到当前请求完成。
这种方式的优点是简单且稳定。每个节点独立地决定镜像拉取的顺序,即使使用串行拉取,不同节点也可以并行拉取相同的镜像。
2. 并行镜像拉取:
如果你想启用并行镜像拉取,可以在 kubelet 的配置中将 serializeImagePulls
字段设置为 false
。这种情况下,kubelet 会立即向镜像服务发送多个镜像拉取请求,允许多个镜像同时被拉取。
并行拉取可以提高镜像拉取的效率,特别是在大型集群中。然而,需要确保容器运行时的镜像服务能够处理并行镜像拉取,以防止网络带宽或磁盘 I/O的过度消耗。
3. 最大并行镜像拉取数量:
从 Kubernetes v1.27 版本开始,引入了 maxParallelImagePulls
这一特性,用于限制同时拉取的镜像数量。这个值可以在 kubelet 配置中设置。当设置了 maxParallelImagePulls
时,kubelet 将限制同时拉取的镜像数量,防止过多的网络带宽或磁盘 I/O的使用。
注意事项:
- kubelet 从不代表一个 Pod 并行地拉取多个镜像。即使启用了并行拉取,对于一个 Pod,kubelet 仍然会按顺序拉取它包含的每个容器的镜像。
- 如果你有一个 Pod,它包含一个初始容器和一个应用容器,这两个容器的镜像拉取不会并行。但是,如果有两个使用不同镜像的 Pod,在启用并行拉取时,kubelet 会代表这两个 Pod 并行拉取镜像。
带镜像索引的多架构镜像
容器技术的普及导致了在不同体系结构的计算机上运行容器化应用的需求。例如,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
镜像的不同版本适用于不同的体系结构。
04 私有仓库
当从私有镜像仓库中拉取镜像时,你可能需要提供凭据以进行身份验证。在 Kubernetes 中,凭据可以以 Secret 对象的形式提供。以下是一些常见的方式来提供私有仓库的凭据:
Docker 配置文件:
- Docker 配置文件通常包含了与 Docker Hub 或其他私有仓库进行身份验证所需的凭据信息。
- Kubernetes 可以使用这些配置文件的内容创建 Secret 对象,供 Pod 使用。
- 使用
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:
- 你可以手动创建一个包含私有仓库凭据的 Secret 对象。这对于直接使用用户名和密码进行身份验证的情况很有用。
- 使用以下命令创建一个基本的 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:
- Kubernetes 中的 Service Account 可以与 Secret 结合使用,实现对私有仓库的身份验证。
- 首先,创建一个 Service Account:
kubectl create serviceaccount <service-account-name>
- 然后,将 Service Account 与 Secret 关联:
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。
05 最佳实践
在 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 中容器镜像的管理效率、安全性和可维护性。在实际部署中,根据具体需求和场景,可能还需要进一步定制和调整这些最佳实践。
06 参考文献
1.https://kubernetes.io/docs/concepts/containers/images/
2.https://blog.51cto.com/goody/7448155