镜像

容器镜像(Image)所承载的是封装了应用程序及其所有软件依赖的二进制数据。 容器镜像是可执行的软件包,可以单独运行;该软件包对所处的运行时环境具有 良定(Well Defined)的假定。

你通常会创建应用的容器镜像并将其推送到某仓库,然后在 Pod 中引用它。

本页概要介绍容器镜像的概念。

镜像名称

容器镜像通常会被赋予 pauseexample/mycontainer 或者 kube-apiserver 这类的名称。 镜像名称也可以包含所在仓库的主机名。例如:fictional.registry.example/imagename。 还可以包含仓库的端口号,例如:fictional.registry.example:10443/imagename

如果你不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。

在镜像名称之后,你可以添加一个 标签(Tag) (就像在 dockerpodman 中也在用的那样)。 使用标签能让你辨识同一镜像序列中的不同版本。

镜像标签可以包含小写字母、大写字符、数字、下划线(_)、句点(.)和连字符(-)。 关于在镜像标签中何处可以使用分隔字符(_-.)还有一些额外的规则。 如果你不指定标签,Kubernetes 认为你想使用标签 latest

注意:

你要避免在生产环境中使用 latest 标签,因为这会使得跟踪所运行的镜像版本变得 非常困难,同时也很难回滚到之前运行良好的版本。

正确的做法恰恰相反,你应该指定一个有意义的标签,如 v1.42.0

更新镜像

默认的镜像拉取策略是 IfNotPresent:在镜像已经存在的情况下, kubelet 将不再去拉取镜像。 如果希望强制总是拉取镜像,你可以执行以下操作之一:

  • 设置容器的 imagePullPolicyAlways
  • 省略 imagePullPolicy,并使用 :latest 作为要使用的镜像的标签。
  • 省略 imagePullPolicy 和要使用的镜像标签。
  • 启用 AlwaysPullImages 准入控制器(Admission Controller)。

如果 imagePullPolicy 未被定义为特定的值,也会被设置为 Always

带镜像索引的多架构镜像

除了提供二进制的镜像之外,容器仓库也可以提供 容器镜像索引。 镜像索引可以根据特定于体系结构版本的容器指向镜像的多个 镜像清单。 这背后的理念是让你可以为镜像命名(例如:pauseexample/mycontainerkube-apiserver) 的同时,允许不同的系统基于它们所使用的机器体系结构取回正确的二进制镜像。

Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)。 为了向前兼容,请在生成较老的镜像时也提供后缀。 这里的理念是为某镜像(如 pause)生成针对所有平台都适用的清单时, 生成 pause-amd64 这类镜像,以便较老的配置文件或者将镜像后缀影编码到其中的 YAML 文件也能兼容。

使用私有仓库

从私有仓库读取镜像时可能需要密钥。 凭证可以用以下方式提供:

  • 配置节点向私有仓库进行身份验证
    • 所有 Pod 均可读取任何已配置的私有仓库
    • 需要集群管理员配置节点
  • 预拉镜像
    • 所有 Pod 都可以使用节点上缓存的所有镜像
    • 需要所有节点的 root 访问权限才能进行设置
  • 在 Pod 中设置 ImagePullSecrets
    • 只有提供自己密钥的 Pod 才能访问私有仓库
  • 特定于厂商的扩展或者本地扩展
    • 如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点 向容器仓库认证的机制

下面将详细描述每一项。

配置 Node 对私有仓库认证

如果你在节点上运行的是 Docker,你可以配置 Docker 容器运行时来向私有容器仓库认证身份。

此方法适用于能够对节点进行配置的场合。

说明: Kubernetes 仅支持 Docker 配置中的 authsHttpHeaders 部分, 不支持 Docker 凭据辅助程序(credHelperscredsStore)。

Docker 将私有仓库的密钥保存在 $HOME/.dockercfg$HOME/.docker/config.json 文件中。如果你将相同的文件放在下面所列的搜索路径中,kubelet 会在拉取镜像时将其用作凭据 数据来源:

  • {--root-dir:-/var/lib/kubelet}/config.json
  • {kubelet 当前工作目录}/config.json
  • ${HOME}/.docker/config.json
  • /.docker/config.json
  • {--root-dir:-/var/lib/kubelet}/.dockercfg
  • {kubelet 当前工作目录}/.dockercfg
  • ${HOME}/.dockercfg
  • /.dockercfg

说明: 你可能不得不为 kubelet 进程显式地设置 HOME=/root 环境变量。

推荐采用如下步骤来配置节点以便访问私有仓库。以下示例中,在 PC 或笔记本电脑中操作:

  1. 针对你要使用的每组凭据,运行 docker login [服务器] 命令。这会更新 你本地环境中的 $HOME/.docker/config.json 文件。

  2. 在编辑器中打开查看 $HOME/.docker/config.json 文件,确保其中仅包含你要 使用的凭据信息。

  3. 获得节点列表;例如:

    • 如果想要节点名称:nodes=$(kubectl get nodes -o jsonpath='{range.items[*].metadata}{.name} {end}')

    • 如果想要节点 IP ,nodes=$(kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}')

  4. 将本地的 .docker/config.json 拷贝到所有节点,放入如上所列的目录之一:

    • 例如,可以试一下:for n in $nodes; do scp ~/.docker/config.json root@"$n":/var/lib/kubelet/config.json; done

说明: 对于产品环境的集群,可以使用配置管理工具来将这些设置应用到 你所期望的节点上。

创建使用私有镜像的 Pod 来验证。例如:

  1. kubectl apply -f - <<EOF
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: private-image-test-1
  6. spec:
  7. containers:
  8. - name: uses-private-image
  9. image: $PRIVATE_IMAGE_NAME
  10. imagePullPolicy: Always
  11. command: [ "echo", "SUCCESS" ]
  12. EOF

输出类似于:

  1. pod/private-image-test-1 created

如果你怀疑命令失败了,你可以运行:

  1. kubectl describe pods/private-image-test-1 | grep 'Failed'

如果命令确实失败,输出类似于:

  1. Fri, 26 Jun 2015 15:36:13 -0700 Fri, 26 Jun 2015 15:39:13 -0700 19 {kubelet node-i2hq} spec.containers{uses-private-image} failed Failed to pull image "user/privaterepo:v1": Error: image user/privaterepo:v1 not found

你必须确保集群中所有节点的 .docker/config.json 文件内容相同。 否则,Pod 会能在一些节点上正常运行而无法在另一些节点上启动。 例如,如果使用节点自动扩缩,那么每个实例模板都需要包含 .docker/config.json, 或者挂载一个包含该文件的驱动器。

.docker/config.json 中配置了私有仓库密钥后,所有 Pod 都将能读取私有仓库中的镜像。

提前拉取镜像

说明: 该方法适用于你能够控制节点配置的场合。 如果你的云供应商负责管理节点并自动置换节点,这一方案无法可靠地工作。

默认情况下,kubelet 会尝试从指定的仓库拉取每个镜像。 但是,如果容器属性 imagePullPolicy 设置为 IfNotPresent 或者 Never, 则会优先使用(对应 IfNotPresent)或者一定使用(对应 Never)本地镜像。

如果你希望使用提前拉取镜像的方法代替仓库认证,就必须保证集群中所有节点提前拉取的镜像是相同的。

这一方案可以用来提前载入指定的镜像以提高速度,或者作为向私有仓库执行身份认证的一种替代方案。

所有的 Pod 都可以使用节点上提前拉取的镜像。

在 Pod 上指定 ImagePullSecrets

说明: 运行使用私有仓库中镜像的容器时,建议使用这种方法。

Kubernetes 支持在 Pod 中设置容器镜像仓库的密钥。

使用 Docker Config 创建 Secret

运行以下命令,将大写字母代替为合适的值:

  1. kubectl create secret docker-registry <名称> \
  2. --docker-server=DOCKER_REGISTRY_SERVER \
  3. --docker-username=DOCKER_USER \
  4. --docker-password=DOCKER_PASSWORD \
  5. --docker-email=DOCKER_EMAIL

如果你已经有 Docker 凭据文件,则可以将凭据文件导入为 Kubernetes Secret, 而不是执行上面的命令。 基于已有的 Docker 凭据创建 Secret 解释了如何完成这一操作。

如果你在使用多个私有容器仓库,这种技术将特别有用。 原因是 kubectl create secret docker-registry 创建的是仅适用于某个私有仓库的 Secret。

说明: Pod 只能引用位于自身所在名字空间中的 Secret,因此需要针对每个名字空间 重复执行上述过程。

在 Pod 中引用 ImagePullSecrets

现在,在创建 Pod 时,可以在 Pod 定义中增加 imagePullSecrets 部分来引用该 Secret。

例如:

  1. cat <<EOF > pod.yaml
  2. apiVersion: v1
  3. kind: Pod
  4. metadata:
  5. name: foo
  6. namespace: awesomeapps
  7. spec:
  8. containers:
  9. - name: foo
  10. image: janedoe/awesomeapp:v1
  11. imagePullSecrets:
  12. - name: myregistrykey
  13. EOF
  14. cat <<EOF >> ./kustomization.yaml
  15. resources:
  16. - pod.yaml
  17. EOF

你需要对使用私有仓库的每个 Pod 执行以上操作。 不过,设置该字段的过程也可以通过为 服务账号 资源设置 imagePullSecrets 来自动完成。 有关详细指令可参见 将 ImagePullSecrets 添加到服务账号

你也可以将此方法与节点级别的 .docker/config.json 配置结合使用。 来自不同来源的凭据会被合并。

使用案例

配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。

  1. 集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。

    • 使用 Docker hub 上的公开镜像
      • 无需配置
      • 某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间
  2. 集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见

    • 使用托管的私有 Docker 仓库
      • 可以托管在 Docker Hub 或者其他地方
      • 按照上面的描述,在每个节点上手动配置 .docker/config.json 文件
    • 或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限
      • 不需要配置 Kubenretes
    • 使用控制镜像访问的托管容器镜像仓库服务
      • 与手动配置节点相比,这种方案能更好地处理集群自动扩缩容
    • 或者,在不方便更改节点配置的集群中,使用 imagePullSecrets
  3. 集群使用专有镜像,且有些镜像需要更严格的访问控制

    • 确保 AlwaysPullImages 准入控制器被启用。否则,所有 Pod 都可以使用所有镜像。
    • 确保将敏感数据存储在 Secret 资源中,而不是将其打包在镜像里
  4. 集群是多租户的并且每个租户需要自己的私有仓库

    • 确保 AlwaysPullImages 准入控制器。否则,所有租户的所有的 Pod 都可以使用所有镜像。
    • 为私有仓库启用鉴权
    • 为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secrert 发布到各租户的命名空间下。
    • 租户将 Secret 添加到每个名字空间中的 imagePullSecrets

如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。 kubelet 将所有 imagePullSecrets 合并为一个虚拟的 .docker/config.json 文件。

接下来