这是本节的多页打印视图。
点击此处打印 .
返回本页常规视图 .
文档
1 - Kubernetes 文档 Kubernetes 是一个开源的容器编排引擎,用来对容器化应用进行自动部署、 扩缩和管理。此开源项目由云原生计算基金会(CNCF)托管。
1.1 - Kubernetes 文档支持的版本 本网站包含当前版本和之前四个版本的 Kubernetes 文档。
Kubernetes 版本的文档可用性与当前是否支持该版本是分开的。
阅读支持期限 ,了解官方支持 Kubernetes 的哪些版本,以及支持多长时间。
2 - 入门 本节列出了设置和运行 Kubernetes 的不同方法。
安装 Kubernetes 时,请根据以下条件选择安装类型:易于维护、安全性、可控制性、可用资源以及操作和管理 Kubernetes 集群所需的专业知识。
你可以下载 Kubernetes ,在本地机器、云或你自己的数据中心上部署 Kubernetes 集群。
某些 Kubernetes 组件 ,
比如 kube-apiserver
或 kube-proxy
等,
可以在集群中以容器镜像 部署。
建议 尽可能将 Kubernetes 组件作为容器镜像运行,并且让 Kubernetes 管理这些组件。
但是运行容器的相关组件 —— 尤其是 kubelet,不在此列。
如果你不想自己管理 Kubernetes 集群,则可以选择托管服务,包括经过认证的平台 。
在各种云和裸机环境中,还有其他标准化和定制的解决方案。
学习环境 如果正打算学习 Kubernetes,请使用 Kubernetes 社区支持
或生态系统中的工具在本地计算机上设置 Kubernetes 集群。
请参阅安装工具 。
生产环境 在评估生产环境 的解决方案时,
请考虑要自己管理 Kubernetes 集群(或相关抽象)的哪些方面,将哪些托付给提供商。
对于你自己管理的集群,官方支持的用于部署 Kubernetes 的工具是
kubeadm 。
接下来 Kubernetes 的设计是让其控制平面 在 Linux 上运行的。
在集群中,你可以在 Linux 或其他操作系统(包括 Windows)上运行应用程序。
2.1 - 学习环境 2.2 - 生产环境 生产质量的 Kubernetes 集群需要规划和准备。
如果你的 Kubernetes 集群是用来运行关键负载的,该集群必须被配置为弹性的(Resilient)。
本页面阐述你在安装生产就绪的集群或将现有集群升级为生产用途时可以遵循的步骤。
如果你已经熟悉生产环境安装,因此只关注一些链接,则可以跳到接下来 节。
生产环境考量 通常,一个生产用 Kubernetes 集群环境与个人学习、开发或测试环境所使用的 Kubernetes 相比有更多的需求。
生产环境可能需要被很多用户安全地访问,需要提供一致的可用性,以及能够与需求变化相适配的资源。
在你决定在何处运行你的生产用 Kubernetes 环境(在本地或者在云端),
以及你希望承担或交由他人承担的管理工作量时,
需要考察以下因素如何影响你对 Kubernetes 集群的需求:
可用性 :一个单机的 Kubernetes 学习环境
具有单点失效特点。创建高可用的集群则意味着需要考虑:将控制面与工作节点分开 在多个节点上提供控制面组件的副本 为针对集群的 API 服务器
的流量提供负载均衡 随着负载的合理需要,提供足够的可用的(或者能够迅速变为可用的)工作节点 规模 :如果你预期你的生产用 Kubernetes 环境要承受固定量的请求,
你可能可以针对所需要的容量来一次性完成安装。
不过,如果你预期服务请求会随着时间增长,或者因为类似季节或者特殊事件的原因而发生剧烈变化,
你就需要规划如何处理请求上升时对控制面和工作节点的压力,或者如何缩减集群规模以减少未使用资源的消耗。安全性与访问管理 :在你自己的学习环境 Kubernetes 集群上,你拥有完全的管理员特权。
但是针对运行着重要工作负载的共享集群,用户账户不止一两个时,
就需要更细粒度的方案来确定谁或者哪些主体可以访问集群资源。
你可以使用基于角色的访问控制(RBAC )
和其他安全机制来确保用户和负载能够访问到所需要的资源,
同时确保工作负载及集群自身仍然是安全的。
你可以通过管理策略 和
容器资源
来针对用户和工作负载所可访问的资源设置约束。在自行构建 Kubernetes 生产环境之前,
请考虑将这一任务的部分或者全部交给云方案承包服务 提供商或者其他
Kubernetes 合作伙伴 。选项有:
无服务 :仅是在第三方设备上运行负载,完全不必管理集群本身。
你需要为 CPU 用量、内存和磁盘请求等付费。托管控制面 :让供应商决定集群控制面的规模和可用性,并负责打补丁和升级等操作。托管工作节点 :配置一个节点池来满足你的需要,由供应商来确保节点始终可用,并在需要的时候完成升级。集成 :有一些供应商能够将 Kubernetes 与一些你可能需要的其他服务集成,
这类服务包括存储、容器镜像仓库、身份认证方法以及开发工具等。无论你是自行构造一个生产用 Kubernetes 集群还是与合作伙伴一起协作,
请审阅下面章节以评估你的需求,因为这关系到你的集群的控制面 、工作节点 、用户访问 以及负载资源 。
生产用集群安装 在生产质量的 Kubernetes 集群中,控制面用不同的方式来管理集群和可以分布到多个计算机上的服务。
每个工作节点则代表的是一个可配置来运行 Kubernetes Pod 的实体。
生产用控制面 最简单的 Kubernetes 集群中,整个控制面和工作节点服务都运行在同一台机器上。
你可以通过添加工作节点来提升环境运算能力,正如
Kubernetes 组件 示意图所示。
如果只需要集群在很短的一段时间内可用,或者可以在某些事物出现严重问题时直接丢弃,
这种配置可能符合你的需要。
如果你需要一个更为持久的、高可用的集群,那么就需要考虑扩展控制面的方式。
根据设计,运行在一台机器上的单机控制面服务不是高可用的。
如果你认为保持集群的正常运行并需要确保它在出错时可以被修复是很重要的,
可以考虑以下步骤:
管理证书 :控制面服务之间的安全通信是通过证书来完成的。
证书是在部署期间自动生成的,或者你也可以使用自己的证书机构来生成它们。
参阅 PKI 证书和需求 了解细节。为 API 服务器配置负载均衡 :配置负载均衡器来将外部的 API 请求散布给运行在不同节点上的 API 服务实例。
参阅创建外部负载均衡器 了解细节。创建多控制面系统 :为了实现高可用性,控制面不应被限制在一台机器上。
如果控制面服务是使用某 init 服务(例如 systemd)来运行的,每个服务应该至少运行在三台机器上。
不过,将控制面作为服务运行在 Kubernetes Pod 中可以确保你所请求的个数的服务始终保持可用。
调度器应该是可容错的,但不是高可用的。
某些部署工具会安装 Raft 票选算法来对 Kubernetes 服务执行领导者选举。
如果主节点消失,另一个服务会被选中并接手相应服务。跨多个可用区 :如果保持你的集群一直可用这点非常重要,可以考虑创建一个跨多个数据中心的集群;
在云环境中,这些数据中心被视为可用区。若干个可用区在一起可构成地理区域。
通过将集群分散到同一区域中的多个可用区内,即使某个可用区不可用,整个集群能够继续工作的机会也大大增加。
更多的细节可参阅跨多个可用区运行 。管理演进中的特性 :如果你计划长时间保留你的集群,就需要执行一些维护其健康和安全的任务。
例如,如果你采用 kubeadm 安装的集群,
则有一些可以帮助你完成
证书管理
和升级 kubeadm 集群
的指令。
参见管理集群 了解一个 Kubernetes
管理任务的较长列表。如要了解运行控制面服务时可使用的选项,可参阅
kube-apiserver 、
kube-controller-manager 和
kube-scheduler
组件参考页面。
如要了解高可用控制面的例子,可参阅高可用拓扑结构选项 、
使用 kubeadm 创建高可用集群
以及为 Kubernetes 运维 etcd 集群 。
关于制定 etcd 备份计划,可参阅对 etcd 集群执行备份 。
生产用工作节点 生产质量的工作负载需要是弹性的;它们所依赖的其他组件(例如 CoreDNS)也需要是弹性的。
无论你是自行管理控制面还是让云供应商来管理,你都需要考虑如何管理工作节点
(有时也简称为节点 )。
配置节点 :节点可以是物理机或者虚拟机。如果你希望自行创建和管理节点,
你可以安装一个受支持的操作系统,之后添加并运行合适的节点服务 。考虑:在安装节点时要通过配置适当的内存、CPU 和磁盘读写速率、存储容量来满足你的负载的需求。 是否通用的计算机系统即足够,还是你有负载需要使用 GPU 处理器、Windows 节点或者 VM 隔离。 验证节点 :参阅验证节点配置 以了解如何确保节点满足加入到 Kubernetes 集群的需求。添加节点到集群中 :如果你自行管理你的集群,你可以通过安装配置你的机器,
之后或者手动加入集群,或者让它们自动注册到集群的 API 服务器。
参阅节点 节,了解如何配置 Kubernetes 以便以这些方式来添加节点。扩缩节点 :制定一个扩充集群容量的规划,你的集群最终会需要这一能力。
参阅大规模集群考察事项
以确定你所需要的节点数;
这一规模是基于你要运行的 Pod 和容器个数来确定的。
如果你自行管理集群节点,这可能意味着要购买和安装你自己的物理设备。生产级用户环境 在生产环境中,情况可能不再是你或者一小组人在访问集群,而是几十上百人需要访问集群。
在学习环境或者平台原型环境中,你可能具有一个可以执行任何操作的管理账号。
在生产环境中,你会需要对不同名字空间具有不同访问权限级别的很多账号。
建立一个生产级别的集群意味着你需要决定如何有选择地允许其他用户访问集群。
具体而言,你需要选择验证尝试访问集群的人的身份标识(身份认证),
并确定他们是否被许可执行他们所请求的操作(鉴权):
认证(Authentication) :API 服务器可以使用客户端证书、持有者令牌、
身份认证代理或者 HTTP 基本认证机制来完成身份认证操作。
你可以选择你要使用的认证方法。通过使用插件,
API 服务器可以充分利用你所在组织的现有身份认证方法,
例如 LDAP 或者 Kerberos。
关于认证 Kubernetes 用户身份的不同方法的描述,
可参阅身份认证 。鉴权(Authorization) :当你准备为一般用户执行权限判定时,
你可能会需要在 RBAC 和 ABAC 鉴权机制之间做出选择。
参阅鉴权概述 ,
了解对用户账户(以及访问你的集群的服务账户)执行鉴权的不同模式。基于角色的访问控制 (RBAC ):
让你通过为通过身份认证的用户授权特定的许可集合来控制集群访问。
访问许可可以针对某特定名字空间(Role)或者针对整个集群(ClusterRole)。
通过使用 RoleBinding 和 ClusterRoleBinding 对象,这些访问许可可以被关联到特定的用户身上。基于属性的访问控制 (ABAC ):
让你能够基于集群中资源的属性来创建访问控制策略,基于对应的属性来决定允许还是拒绝访问。
策略文件的每一行都给出版本属性(apiVersion 和 kind)以及一个规约属性的映射,
用来匹配主体(用户或组)、资源属性、非资源属性(/version 或 /apis)和只读属性。
参阅示例 以了解细节。作为在你的生产用 Kubernetes 集群中安装身份认证和鉴权机制的负责人,要考虑的事情如下:
设置鉴权模式 :当 Kubernetes API 服务器(kube-apiserver )启动时,
所支持的鉴权模式必须使用 --authorization-mode
标志配置。
例如,kube-apiserver.yaml
(位于 /etc/kubernetes/manifests
下)中对应的标志可以设置为 Node,RBAC
。
这样就会针对已完成身份认证的请求执行 Node 和 RBAC 鉴权。创建用户证书和角色绑定(RBAC) :如果你在使用 RBAC 鉴权,用户可以创建由集群 CA 签名的
CertificateSigningRequest(CSR)。接下来你就可以将 Role 和 ClusterRole 绑定到每个用户身上。
参阅证书签名请求 了解细节。创建组合属性的策略(ABAC) :如果你在使用 ABAC 鉴权,
你可以设置属性组合以构造策略对所选用户或用户组执行鉴权,
判定他们是否可访问特定的资源(例如 Pod)、名字空间或者 apiGroup。
进一步的详细信息可参阅示例 。考虑准入控制器 :针对指向 API 服务器的请求的其他鉴权形式还包括
Webhook 令牌认证 。
Webhook 和其他特殊的鉴权类型需要通过向 API
服务器添加准入控制器 来启用。为负载资源设置约束 生产环境负载的需求可能对 Kubernetes 的控制面内外造成压力。
在针对你的集群的负载执行配置时,要考虑以下条目:
创建额外的服务账户 :用户账户决定用户可以在集群上执行的操作,服务账号则定义的是在特定名字空间中
Pod 的访问权限。默认情况下,Pod 使用所在名字空间中的 default 服务账号。
参阅管理服务账号 以了解如何创建新的服务账号。
例如,你可能需要:接下来 2.2.1 - 容器运行时 你需要在集群内每个节点上安装一个
容器运行时
以使 Pod 可以运行在上面。本文概述了所涉及的内容并描述了与节点设置相关的任务。
Kubernetes 1.25
要求你使用符合容器运行时接口 (CRI)的运行时。
有关详细信息,请参阅 CRI 版本支持 。
本页简要介绍在 Kubernetes 中几个常见的容器运行时的用法。
说明: v1.24 之前的 Kubernetes 版本直接集成了 Docker Engine 的一个组件,名为 dockershim 。
这种特殊的直接整合不再是 Kubernetes 的一部分
(这次删除被作为 v1.20 发行版本的一部分宣布 )。
你可以阅读检查 Dockershim 移除是否会影响你 以了解此删除可能会如何影响你。
要了解如何使用 dockershim 进行迁移,
请参阅从 dockershim 迁移 。
如果你正在运行 v1.25 以外的 Kubernetes 版本,查看对应版本的文档。
以下步骤将通用设置应用于 Linux 上的 Kubernetes 节点。
如果你确定不需要某个特定设置,则可以跳过它。
有关更多信息,请参阅网络插件要求 或特定容器运行时的文档。
转发 IPv4 并让 iptables 看到桥接流量 通过运行 lsmod | grep br_netfilter
来验证 br_netfilter
模块是否已加载。
若要显式加载此模块,请运行 sudo modprobe br_netfilter
。
为了让 Linux 节点的 iptables 能够正确查看桥接流量,请确认 sysctl
配置中的
net.bridge.bridge-nf-call-iptables
设置为 1。例如:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置所需的 sysctl 参数,参数在重新启动后保持不变
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# 应用 sysctl 参数而不重新启动
sudo sysctl --system
cgroup 驱动 在 Linux 上,控制组(CGroup) 用于限制分配给进程的资源。
kubelet 和底层容器运行时都需要对接控制组来强制执行
为 Pod 和容器管理资源
并为诸如 CPU、内存这类资源设置请求和限制。若要对接控制组,kubelet 和容器运行时需要使用一个 cgroup 驱动 。
关键的一点是 kubelet 和容器运行时需使用相同的 cgroup 驱动并且采用相同的配置。
可用的 cgroup 驱动有两个:
cgroupfs 驱动 cgroupfs
驱动是 kubelet 中默认的 cgroup 驱动。当使用 cgroupfs
驱动时,
kubelet 和容器运行时将直接对接 cgroup 文件系统来配置 cgroup。
当 systemd 是初始化系统时,
不 推荐使用 cgroupfs
驱动,因为 systemd 期望系统上只有一个 cgroup 管理器。
此外,如果你使用 cgroup v2 ,
则应用 systemd
cgroup 驱动取代 cgroupfs
。
systemd cgroup 驱动 当某个 Linux 系统发行版使用 systemd
作为其初始化系统时,初始化进程会生成并使用一个 root 控制组(cgroup
),并充当 cgroup 管理器。
systemd 与 cgroup 集成紧密,并将为每个 systemd 单元分配一个 cgroup。
因此,如果你 systemd
用作初始化系统,同时使用 cgroupfs
驱动,则系统中会存在两个不同的 cgroup 管理器。
同时存在两个 cgroup 管理器将造成系统中针对可用的资源和使用中的资源出现两个视图。某些情况下,
将 kubelet 和容器运行时配置为使用 cgroupfs
、但为剩余的进程使用 systemd
的那些节点将在资源压力增大时变得不稳定。
当 systemd 是选定的初始化系统时,缓解这个不稳定问题的方法是针对 kubelet 和容器运行时将
systemd
用作 cgroup 驱动。
要将 systemd
设置为 cgroup 驱动,需编辑 KubeletConfiguration
的 cgroupDriver
选项,并将其设置为 systemd
。例如:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
...
cgroupDriver : systemd
如果你将 systemd
配置为 kubelet 的 cgroup 驱动,你也必须将 systemd
配置为容器运行时的 cgroup 驱动。
参阅容器运行时文档,了解指示说明。例如:
注意: 注意:更改已加入集群的节点的 cgroup 驱动是一项敏感的操作。
如果 kubelet 已经使用某 cgroup 驱动的语义创建了 Pod,更改运行时以使用别的
cgroup 驱动,当为现有 Pod 重新创建 PodSandbox 时会产生错误。
重启 kubelet 也可能无法解决此类问题。
如果你有切实可行的自动化方案,使用其他已更新配置的节点来替换该节点,
或者使用自动化方案来重新安装。
将 kubeadm 管理的集群迁移到 systemd
驱动 如果你希望将现有的由 kubeadm 管理的集群迁移到 systemd
cgroup 驱动,
请按照配置 cgroup 驱动 操作。
CRI 版本支持 你的容器运行时必须至少支持 v1alpha2 版本的容器运行时接口。
Kubernetes 1.25 默认使用 v1 版本的 CRI API。如果容器运行时不支持 v1 版本的 API,
则 kubelet 会回退到使用(已弃用的)v1alpha2 版本的 API。
容器运行时 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
containerd 本节概述了使用 containerd 作为 CRI 运行时的必要步骤。
使用以下命令在系统上安装 Containerd:
按照开始使用 containerd 的说明进行操作。
创建有效的配置文件 config.toml
后返回此步骤。
你可以在路径 /etc/containerd/config.toml
下找到此文件。
你可以在路径 C:\Program Files\containerd\config.toml
下找到此文件。
在 Linux 上,containerd 的默认 CRI 套接字是 /run/containerd/containerd.sock
。
在 Windows 上,默认 CRI 端点是 npipe://./pipe/containerd-containerd
。
配置 systemd
cgroup 驱动 结合 runc
使用 systemd
cgroup 驱动,在 /etc/containerd/config.toml
中设置:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
如果你使用 cgroup v2 ,则推荐 systemd
cgroup 驱动。
说明: 如果你从软件包(例如,RPM 或者 .deb
)中安装 containerd,你可能会发现其中默认禁止了
CRI 集成插件。
你需要启用 CRI 支持才能在 Kubernetes 集群中使用 containerd。
要确保 cri
没有出现在 /etc/containerd/config.toml
文件中 disabled_plugins
列表内。如果你更改了这个文件,也请记得要重启 containerd
。
如果你应用此更改,请确保重新启动 containerd:
sudo systemctl restart containerd
当使用 kubeadm 时,请手动配置
kubelet 的 cgroup 驱动 。
重载沙箱(pause)镜像 在你的 containerd 配置 中,
你可以通过设置以下选项重载沙箱镜像:
[plugins."io.containerd.grpc.v1.cri" ]
sandbox_image = "registry.k8s.io/pause:3.2"
一旦你更新了这个配置文件,可能就同样需要重启 containerd
:systemctl restart containerd
。
CRI-O 本节包含安装 CRI-O 作为容器运行时的必要步骤。
要安装 CRI-O,请按照 CRI-O 安装说明 执行操作。
cgroup 驱动 CRI-O 默认使用 systemd cgroup 驱动,这对你来说可能工作得很好。
要切换到 cgroupfs
cgroup 驱动,请编辑 /etc/crio/crio.conf
或在
/etc/crio/crio.conf.d/02-cgroup-manager.conf
中放置一个插入式配置,例如:
[crio.runtime]
conmon_cgroup = "pod"
cgroup_manager = "cgroupfs"
你还应该注意当使用 CRI-O 时,并且 CRI-O 的 cgroup 设置为 cgroupfs
时,必须将 conmon_cgroup
设置为值 pod
。
通常需要保持 kubelet 的 cgroup 驱动配置(通常通过 kubeadm 完成)和 CRI-O 同步。
对于 CRI-O,CRI 套接字默认为 /var/run/crio/crio.sock
。
重载沙箱(pause)镜像 在你的 CRI-O 配置 中,
你可以设置以下配置值:
[crio.image]
pause_image="registry.k8s.io/pause:3.6"
这一设置选项支持动态配置重加载来应用所做变更:systemctl reload crio
。
也可以通过向 crio
进程发送 SIGHUP
信号来实现。
Docker Engine 说明: 以下操作假设你使用 cri-dockerd
适配器来将
Docker Engine 与 Kubernetes 集成。
在你的每个节点上,遵循安装 Docker Engine
指南为你的 Linux 发行版安装 Docker。 按照源代码仓库中的说明安装 cri-dockerd
。 对于 cri-dockerd
,默认情况下,CRI 套接字是 /run/cri-dockerd.sock
。
Mirantis 容器运行时 Mirantis Container Runtime (MCR)
是一种商用容器运行时,以前称为 Docker 企业版。
你可以使用 MCR 中包含的开源 cri-dockerd
组件将 Mirantis Container Runtime 与 Kubernetes 一起使用。
要了解有关如何安装 Mirantis Container Runtime 的更多信息,
请访问 MCR 部署指南 。
检查名为 cri-docker.socket
的 systemd 单元以找出 CRI 套接字的路径。
重载沙箱(pause)镜像 cri-dockerd
适配器能够接受指定用作 Pod 的基础容器的容器镜像(“pause 镜像”)作为命令行参数。
要使用的命令行参数是 --pod-infra-container-image
。
接下来 除了容器运行时,你的集群还需要有效的网络插件 。
2.2.2 - 使用部署工具安装 Kubernetes
2.2.2.1 - 使用 kubeadm 引导集群 2.2.2.1.1 - 安装 kubeadm
本页面显示如何安装 kubeadm
工具箱。
有关在执行此安装过程后如何使用 kubeadm 创建集群的信息,
请参见使用 kubeadm 创建集群 。
准备开始 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux
发行版以及一些不提供包管理器的发行版提供通用的指令。 每台机器 2 GB 或更多的 RAM(如果少于这个数字将会影响你应用的运行内存)。 CPU 2 核心及以上。 集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)。 节点之中不可以有重复的主机名、MAC 地址或 product_uuid。请参见这里 了解更多详细信息。 开启机器上的某些端口。请参见这里 了解更多详细信息。 禁用交换分区。为了保证 kubelet 正常工作,你必须 禁用交换分区。 确保每个节点上 MAC 地址和 product_uuid 的唯一性 你可以使用命令 ip link
或 ifconfig -a
来获取网络接口的 MAC 地址 可以使用 sudo cat /sys/class/dmi/id/product_uuid
命令对 product_uuid 校验 一般来讲,硬件设备会拥有唯一的地址,但是有些虚拟机的地址可能会重复。
Kubernetes 使用这些值来唯一确定集群中的节点。
如果这些值在每个节点上不唯一,可能会导致安装失败 。
检查网络适配器 如果你有一个以上的网络适配器,同时你的 Kubernetes 组件通过默认路由不可达,我们建议你预先添加 IP 路由规则,
这样 Kubernetes 集群就可以通过对应的适配器完成连接。
检查所需端口 启用这些必要的端口 后才能使 Kubernetes 的各组件相互通信。
可以使用 netcat 之类的工具来检查端口是否启用,例如:
你使用的 Pod 网络插件 (详见后续章节) 也可能需要开启某些特定端口。
由于各个 Pod 网络插件的功能都有所不同,请参阅他们各自文档中对端口的要求。
安装容器运行时 为了在 Pod 中运行容器,Kubernetes 使用
容器运行时(Container Runtime) 。
默认情况下,Kubernetes 使用
容器运行时接口(Container Runtime Interface,CRI)
来与你所选择的容器运行时交互。
如果你不指定运行时,kubeadm 会自动尝试通过扫描已知的端点列表来检测已安装的容器运行时。
如果检测到有多个或者没有容器运行时,kubeadm 将抛出一个错误并要求你指定一个想要使用的运行时。
参阅容器运行时
以了解更多信息。
说明: Docker Engine 没有实现 CRI ,
而这是容器运行时在 Kubernetes 中工作所需要的。
为此,必须安装一个额外的服务 cri-dockerd 。
cri-dockerd 是一个基于传统的内置 Docker 引擎支持的项目,
它在 1.24 版本从 kubelet 中移除 。
下面的表格包括被支持的操作系统的已知端点。
Linux 容器运行时 运行时 Unix 域套接字 containerd unix:///var/run/containerd/containerd.sock
CRI-O unix:///var/run/crio/crio.sock
Docker Engine(使用 cri-dockerd) unix:///var/run/cri-dockerd.sock
Windows 容器运行时 运行时 Windows 命名管道路径 containerd npipe:////./pipe/containerd-containerd
Docker Engine(使用 cri-dockerd) npipe:////./pipe/cri-dockerd
安装 kubeadm、kubelet 和 kubectl 你需要在每台机器上安装以下的软件包:
kubeadm 不能 帮你安装或者管理 kubelet
或 kubectl
,
所以你需要确保它们与通过 kubeadm 安装的控制平面的版本相匹配。
如果不这样做,则存在发生版本偏差的风险,可能会导致一些预料之外的错误和问题。
然而,控制平面与 kubelet 之间可以存在一个 次要版本的偏差,但 kubelet
的版本不可以超过 API 服务器的版本。
例如,1.7.0 版本的 kubelet 可以完全兼容 1.8.0 版本的 API 服务器,反之则不可以。
有关安装 kubectl
的信息,请参阅安装和设置 kubectl 文档。
警告: 这些指南不包括系统升级时使用的所有 Kubernetes 程序包。这是因为 kubeadm 和 Kubernetes
有特殊的升级注意事项 。
关于版本偏差的更多信息,请参阅以下文档:
更新 apt
包索引并安装使用 Kubernetes apt
仓库所需要的包:
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
下载 Google Cloud 公开签名秘钥:
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
添加 Kubernetes apt
仓库:
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
更新 apt
包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
exclude=kubelet kubeadm kubectl
EOF
# 将 SELinux 设置为 permissive 模式(相当于将其禁用)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
sudo yum install -y kubelet kubeadm kubectl --disableexcludes= kubernetes
sudo systemctl enable --now kubelet
请注意:
通过运行命令 setenforce 0
和 sed ...
将 SELinux 设置为 permissive 模式可以有效地将其禁用。
这是允许容器访问主机文件系统所必需的,而这些操作是为了例如 Pod 网络工作正常。
你必须这么做,直到 kubelet 做出对 SELinux 的支持进行升级为止。
如果你知道如何配置 SELinux 则可以将其保持启用状态,但可能需要设定 kubeadm 不支持的部分配置
如果由于该 Red Hat 的发行版无法解析 basearch
导致获取 baseurl
失败,请将 \$basearch
替换为你计算机的架构。
输入 uname -m
以查看该值。
例如,x86_64
的 baseurl
URL 可以是:https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
。
安装 CNI 插件(大多数 Pod 网络都需要):
CNI_PLUGINS_VERSION = "v1.1.1"
ARCH = "amd64"
DEST = "/opt/cni/bin"
sudo mkdir -p " $DEST "
curl -L "https://github.com/containernetworking/plugins/releases/download/ ${ CNI_PLUGINS_VERSION } /cni-plugins-linux- ${ ARCH } - ${ CNI_PLUGINS_VERSION } .tgz" | sudo tar -C " $DEST " -xz
定义要下载命令文件的目录。
说明: DOWNLOAD_DIR
变量必须被设置为一个可写入的目录。
如果你在运行 Flatcar Container Linux,可设置 DOWNLOAD_DIR="/opt/bin"
。
DOWNLOAD_DIR = "/usr/local/bin"
sudo mkdir -p " $DOWNLOAD_DIR "
安装 crictl(kubeadm/kubelet 容器运行时接口(CRI)所需)
CRICTL_VERSION = "v1.25.0"
ARCH = "amd64"
curl -L "https://github.com/kubernetes-sigs/cri-tools/releases/download/ ${ CRICTL_VERSION } /crictl- ${ CRICTL_VERSION } -linux- ${ ARCH } .tar.gz" | sudo tar -C $DOWNLOAD_DIR -xz
安装 kubeadm
、kubelet
、kubectl
并添加 kubelet
系统服务:
RELEASE = " $( curl -sSL https://dl.k8s.io/release/stable.txt) "
ARCH = "amd64"
cd $DOWNLOAD_DIR
sudo curl -L --remote-name-all https://dl.k8s.io/release/${ RELEASE } /bin/linux/${ ARCH } /{ kubeadm,kubelet}
sudo chmod +x { kubeadm,kubelet}
RELEASE_VERSION = "v0.4.0"
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/ ${ RELEASE_VERSION } /cmd/kubepkg/templates/latest/deb/kubelet/lib/systemd/system/kubelet.service" | sed "s:/usr/bin: ${ DOWNLOAD_DIR } :g" | sudo tee /etc/systemd/system/kubelet.service
sudo mkdir -p /etc/systemd/system/kubelet.service.d
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/ ${ RELEASE_VERSION } /cmd/kubepkg/templates/latest/deb/kubeadm/10-kubeadm.conf" | sed "s:/usr/bin: ${ DOWNLOAD_DIR } :g" | sudo tee /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
请参照安装工具页面 的说明安装 kubelet
。
激活并启动 kubelet
:
systemctl enable --now kubelet
说明: Flatcar Container Linux 发行版会将 /usr/
目录挂载为一个只读文件系统。
在启动引导你的集群之前,你需要执行一些额外的操作来配置一个可写入的目录。
参见 kubeadm 故障排查指南
以了解如何配置一个可写入的目录。
kubelet 现在每隔几秒就会重启,因为它陷入了一个等待 kubeadm 指令的死循环。
配置 cgroup 驱动程序 容器运行时和 kubelet 都具有名字为
"cgroup driver"
的属性,该属性对于在 Linux 机器上管理 CGroups 而言非常重要。
警告: 你需要确保容器运行时和 kubelet 所使用的是相同的 cgroup 驱动,否则 kubelet
进程会失败。
相关细节可参见配置 cgroup 驱动 。
故障排查 如果你在使用 kubeadm 时遇到困难,请参阅我们的
故障排查文档 。
接下来 2.2.2.1.2 - 对 kubeadm 进行故障排查 与任何程序一样,你可能会在安装或者运行 kubeadm 时遇到错误。
本文列举了一些常见的故障场景,并提供可帮助你理解和解决这些问题的步骤。
如果你的问题未在下面列出,请执行以下步骤:
由于缺少 RBAC,无法将 v1.18 Node 加入 v1.17 集群 自从 v1.18 后,如果集群中已存在同名 Node,kubeadm 将禁止 Node 加入集群。
这需要为 bootstrap-token 用户添加 RBAC 才能 GET Node 对象。
但这会导致一个问题,v1.18 的 kubeadm join
无法加入由 kubeadm v1.17 创建的集群。
要解决此问题,你有两种选择:
使用 kubeadm v1.18 在控制平面节点上执行 kubeadm init phase bootstrap-token
。
请注意,这也会启用 bootstrap-token 的其余权限。
或者,也可以使用 kubectl apply -f ...
手动应用以下 RBAC:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
name : kubeadm:get-nodes
rules :
- apiGroups :
- ""
resources :
- nodes
verbs :
- get
---
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : kubeadm:get-nodes
roleRef :
apiGroup : rbac.authorization.k8s.io
kind : ClusterRole
name : kubeadm:get-nodes
subjects :
- apiGroup : rbac.authorization.k8s.io
kind : Group
name : system:bootstrappers:kubeadm:default-node-token
在安装过程中没有找到 ebtables
或者其他类似的可执行文件 如果在运行 kubeadm init
命令时,遇到以下的警告
[ preflight] WARNING: ebtables not found in system path
[ preflight] WARNING: ethtool not found in system path
那么或许在你的节点上缺失 ebtables
、ethtool
或者类似的可执行文件。
你可以使用以下命令安装它们:
对于 Ubuntu/Debian 用户,运行 apt install ebtables ethtool
命令。 对于 CentOS/Fedora 用户,运行 yum install ebtables ethtool
命令。 在安装过程中,kubeadm 一直等待控制平面就绪 如果你注意到 kubeadm init
在打印以下行后挂起:
[ apiclient] Created API client, waiting for the control plane to become ready
这可能是由许多问题引起的。最常见的是:
网络连接问题。在继续之前,请检查你的计算机是否具有全部联通的网络连接。 容器运行时的 cgroup 驱动不同于 kubelet 使用的 cgroup 驱动。要了解如何正确配置 cgroup 驱动,
请参阅配置 cgroup 驱动 。 控制平面上的 Docker 容器持续进入崩溃状态或(因其他原因)挂起。你可以运行 docker ps
命令来检查以及 docker logs
命令来检视每个容器的运行日志。
对于其他容器运行时,请参阅使用 crictl 对 Kubernetes 节点进行调试 。 当删除托管容器时 kubeadm 阻塞 如果容器运行时停止并且未删除 Kubernetes 所管理的容器,可能发生以下情况:
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
(block)
一个可行的解决方案是重新启动 Docker 服务,然后重新运行 kubeadm reset
:
你也可以使用 crictl
来调试容器运行时的状态。
参见使用 CRICTL 调试 Kubernetes 节点 。
Pod 处于 RunContainerError
、CrashLoopBackOff
或者 Error
状态 在 kubeadm init
命令运行后,系统中不应该有 Pod 处于这类状态。
在 kubeadm init
命令执行完后,如果有 Pod 处于这些状态之一,请在 kubeadm
仓库提起一个 issue。coredns
(或者 kube-dns
) 应该处于 Pending
状态,
直到你部署了网络插件为止。
如果在部署完网络插件之后,有 Pod 处于 RunContainerError
、CrashLoopBackOff
或 Error
状态之一,并且 coredns
(或者 kube-dns
)仍处于 Pending
状态,
那很可能是你安装的网络插件由于某种原因无法工作。你或许需要授予它更多的
RBAC 特权或使用较新的版本。请在 Pod Network 提供商的问题跟踪器中提交问题,
然后在此处分类问题。
coredns
停滞在 Pending
状态这一行为是 预期之中 的,因为系统就是这么设计的。
kubeadm 的网络供应商是中立的,因此管理员应该选择
安装 Pod 的网络插件 。
你必须完成 Pod 的网络配置,然后才能完全部署 CoreDNS。
在网络被配置好之前,DNS 组件会一直处于 Pending
状态。
HostPort
服务无法工作此 HostPort
和 HostIP
功能是否可用取决于你的 Pod 网络配置。请联系 Pod 网络插件的作者,
以确认 HostPort
和 HostIP
功能是否可用。
已验证 Calico、Canal 和 Flannel CNI 驱动程序支持 HostPort。
有关更多信息,请参考 CNI portmap 文档 .
如果你的网络提供商不支持 portmap CNI 插件,你或许需要使用
NodePort 服务的功能
或者使用 HostNetwork=true
。
无法通过其服务 IP 访问 Pod 许多网络附加组件尚未启用 hairpin 模式
该模式允许 Pod 通过其服务 IP 进行访问。这是与 CNI 有关的问题。
请与网络附加组件提供商联系,以获取他们所提供的 hairpin 模式的最新状态。
如果你正在使用 VirtualBox (直接使用或者通过 Vagrant 使用),你需要
确保 hostname -i
返回一个可路由的 IP 地址。默认情况下,第一个接口连接不能路由的仅主机网络。
解决方法是修改 /etc/hosts
,请参考示例 Vagrantfile 。
TLS 证书错误 以下错误说明证书可能不匹配。
# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
验证 $HOME/.kube/config
文件是否包含有效证书,
并在必要时重新生成证书。在 kubeconfig 文件中的证书是 base64 编码的。
该 base64 --decode
命令可以用来解码证书,openssl x509 -text -noout
命令可以用于查看证书信息。
使用如下方法取消设置 KUBECONFIG
环境变量的值:
或者将其设置为默认的 KUBECONFIG
位置:
export KUBECONFIG = /etc/kubernetes/admin.conf
另一个方法是覆盖 kubeconfig
的现有用户 "管理员":
mv $HOME /.kube $HOME /.kube.bak
mkdir $HOME /.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME /.kube/config
sudo chown $( id -u) :$( id -g) $HOME /.kube/config
Kubelet 客户端证书轮换失败 默认情况下,kubeadm 使用 /etc/kubernetes/kubelet.conf
中指定的 /var/lib/kubelet/pki/kubelet-client-current.pem
符号链接
来配置 kubelet 自动轮换客户端证书。如果此轮换过程失败,你可能会在 kube-apiserver 日志中看到诸如
x509: certificate has expired or is not yet valid
之类的错误。要解决此问题,你必须执行以下步骤:
从故障节点备份和删除 /etc/kubernetes/kubelet.conf
和 /var/lib/kubelet/pki/kubelet-client*
。 在集群中具有 /etc/kubernetes/pki/ca.key
的、正常工作的控制平面节点上
执行 kubeadm kubeconfig user --org system:nodes --client-name system:node:$NODE > kubelet.conf
。
$NODE
必须设置为集群中现有故障节点的名称。
手动修改生成的 kubelet.conf
以调整集群名称和服务器端点,
或传递 kubeconfig user --config
(此命令接受 InitConfiguration
)。
如果你的集群没有 ca.key
,你必须在外部对 kubelet.conf
中的嵌入式证书进行签名。 将得到的 kubelet.conf
文件复制到故障节点上,作为 /etc/kubernetes/kubelet.conf
。 在故障节点上重启 kubelet(systemctl restart kubelet
),等待 /var/lib/kubelet/pki/kubelet-client-current.pem
重新创建。 手动编辑 kubelet.conf
指向轮换的 kubelet 客户端证书,方法是将 client-certificate-data
和 client-key-data
替换为:
client-certificate : /var/lib/kubelet/pki/kubelet-client-current.pem
client-key : /var/lib/kubelet/pki/kubelet-client-current.pem
重新启动 kubelet。 确保节点状况变为 Ready
。 在 Vagrant 中使用 flannel 作为 Pod 网络时的默认 NIC 以下错误可能表明 Pod 网络中出现问题:
Error from server ( NotFound) : the server could not find the requested resource
如果你正在 Vagrant 中使用 flannel 作为 Pod 网络,则必须指定 flannel 的默认接口名称。
Vagrant 通常为所有 VM 分配两个接口。第一个为所有主机分配了 IP 地址 10.0.2.15
,用于获得 NATed 的外部流量。
这可能会导致 flannel 出现问题,它默认为主机上的第一个接口。这导致所有主机认为它们具有
相同的公共 IP 地址。为防止这种情况,传递 --iface eth1
标志给 flannel 以便选择第二个接口。
容器使用的非公共 IP 在某些情况下 kubectl logs
和 kubectl run
命令或许会返回以下错误,即便除此之外集群一切功能正常:
Error from server: Get https://10.19.0.41:10250/containerLogs/default/mysql-ddc65b868-glc5m/mysql: dial tcp 10.19.0.41:10250: getsockopt: no route to host
这或许是由于 Kubernetes 使用的 IP 无法与看似相同的子网上的其他 IP 进行通信的缘故,
可能是由机器提供商的政策所导致的。
DigitalOcean 既分配一个共有 IP 给 eth0
,也分配一个私有 IP 在内部用作其浮动 IP 功能的锚点,
然而 kubelet
将选择后者作为节点的 InternalIP
而不是公共 IP。
使用 ip addr show
命令代替 ifconfig
命令去检查这种情况,因为 ifconfig
命令
不会显示有问题的别名 IP 地址。或者指定的 DigitalOcean 的 API 端口允许从 droplet 中
查询 anchor IP:
curl http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address
解决方法是通知 kubelet
使用哪个 --node-ip
。当使用 DigitalOcean 时,可以是公网IP(分配给 eth0
的),
或者是私网IP(分配给 eth1
的)。私网 IP 是可选的。
kubadm NodeRegistrationOptions
结构
的 KubeletExtraArgs
部分被用来处理这种情况。
然后重启 kubelet
:
systemctl daemon-reload
systemctl restart kubelet
coredns
Pod 有 CrashLoopBackOff
或者 Error
状态如果有些节点运行的是旧版本的 Docker,同时启用了 SELinux,你或许会遇到 coredns
Pod 无法启动的情况。
要解决此问题,你可以尝试以下选项之一:
kubectl -n kube-system get deployment coredns -o yaml | \
sed 's/allowPrivilegeEscalation: false/allowPrivilegeEscalation: true/g' | \
kubectl apply -f -
CoreDNS 处于 CrashLoopBackOff
时的另一个原因是当 Kubernetes 中部署的 CoreDNS Pod 检测
到环路时。有许多解决方法
可以避免在每次 CoreDNS 监测到循环并退出时,Kubernetes 尝试重启 CoreDNS Pod 的情况。
警告: 禁用 SELinux 或设置 allowPrivilegeEscalation
为 true
可能会损害集群的安全性。
etcd Pod 持续重启 如果你遇到以下错误:
rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused \"read parent: connection reset by peer\""
如果你使用 Docker 1.13.1.84 运行 CentOS 7 就会出现这种问题。
此版本的 Docker 会阻止 kubelet 在 etcd 容器中执行。
为解决此问题,请选择以下选项之一:
kubeadm init
标志例如 --component-extra-args
允许你将自定义参数传递给像
kube-apiserver 这样的控制平面组件。然而,由于解析 (mapStringString
) 的基础类型值,此机制将受到限制。
如果你决定传递一个支持多个逗号分隔值(例如
--apiserver-extra-args "enable-admission-plugins=LimitRanger,NamespaceExists"
)参数,
将出现 flag: malformed pair, expect string=string
错误。
发生这种问题是因为参数列表 --apiserver-extra-args
预期的是 key=value
形式,
而这里的 NamespacesExists
被误认为是缺少取值的键名。
一种解决方法是尝试分离 key=value
对,像这样:
--apiserver-extra-args "enable-admission-plugins=LimitRanger,enable-admission-plugins=NamespaceExists"
但这将导致键 enable-admission-plugins
仅有值 NamespaceExists
。
已知的解决方法是使用 kubeadm
配置文件 。
在节点被云控制管理器初始化之前,kube-proxy 就被调度了 在云环境场景中,可能出现在云控制管理器完成节点地址初始化之前,kube-proxy 就被调度到新节点了。
这会导致 kube-proxy 无法正确获取节点的 IP 地址,并对管理负载平衡器的代理功能产生连锁反应。
在 kube-proxy Pod 中可以看到以下错误:
server.go:610] Failed to retrieve node IP: host IP unknown; known addresses: []
proxier.go:340] invalid nodeIP, initializing kube-proxy with 127.0.0.1 as nodeIP
一种已知的解决方案是修补 kube-proxy DaemonSet,以允许在控制平面节点上调度它,
而不管它们的条件如何,将其与其他节点保持隔离,直到它们的初始保护条件消除:
kubectl -n kube-system patch ds kube-proxy -p= '{ "spec": { "template": { "spec": { "tolerations": [ { "key": "CriticalAddonsOnly", "operator": "Exists" }, { "effect": "NoSchedule", "key": "node-role.kubernetes.io/control-plane" } ] } } } }'
此问题的跟踪在这里 。
节点上的 /usr
被以只读方式挂载 在类似 Fedora CoreOS 或者 Flatcar Container Linux 这类 Linux 发行版本中,
目录 /usr
是以只读文件系统的形式挂载的。
在支持 FlexVolume 时,
类似 kubelet 和 kube-controller-manager 这类 Kubernetes 组件使用默认路径
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/
,
而 FlexVolume 的目录 必须是可写入的 ,该功能特性才能正常工作。
(注意 :FlexVolume 在 Kubernetes v1.23 版本中已被弃用)
为了解决这个问题,你可以使用 kubeadm 的配置文件 来配置 FlexVolume 的目录。
在(使用 kubeadm init
创建的)主控制节点上,使用 --config
参数传入如下文件:
apiVersion : kubeadm.k8s.io/v1beta3
kind : InitConfiguration
nodeRegistration :
kubeletExtraArgs :
volume-plugin-dir : "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
---
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
controllerManager :
extraArgs :
flex-volume-plugin-dir : "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
在加入到集群中的节点上,使用下面的文件:
apiVersion : kubeadm.k8s.io/v1beta3
kind : JoinConfiguration
nodeRegistration :
kubeletExtraArgs :
volume-plugin-dir : "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
或者,你要可以更改 /etc/fstab
使得 /usr
目录能够以可写入的方式挂载,
不过请注意这样做本质上是在更改 Linux 发行版的某种设计原则。
kubeadm upgrade plan
输出错误信息 context deadline exceeded
在使用 kubeadm
来升级某运行外部 etcd 的 Kubernetes 集群时可能显示这一错误信息。
这并不是一个非常严重的一个缺陷,之所以出现此错误信息,原因是老的 kubeadm
版本会对外部 etcd 集群执行版本检查。你可以继续执行 kubeadm upgrade apply ...
。
这一问题已经在 1.19 版本中得到修复。
kubeadm reset
会卸载 /var/lib/kubelet
如果已经挂载了 /var/lib/kubelet
目录,执行 kubeadm reset
操作的时候会将其卸载。
要解决这一问题,可以在执行了 kubeadm reset
操作之后重新挂载
/var/lib/kubelet
目录。
这是一个在 1.15 中引入的故障,已经在 1.20 版本中修复。
无法在 kubeadm 集群中安全地使用 metrics-server 在 kubeadm 集群中可以通过为 metrics-server
设置 --kubelet-insecure-tls
来以不安全的形式使用该服务。
建议不要在生产环境集群中这样使用。
如果你需要在 metrics-server 和 kubelet 之间使用 TLS,会有一个问题,
kubeadm 为 kubelet 部署的是自签名的服务证书。这可能会导致 metrics-server
端报告下面的错误信息:
x509: certificate signed by unknown authority
x509: certificate is valid for IP-foo not IP-bar
参见为 kubelet 启用签名的服务证书
以进一步了解如何在 kubeadm 集群中配置 kubelet 使用正确签名了的服务证书。
另请参阅 How to run the metrics-server securely 。
2.2.2.1.3 - 使用 kubeadm 创建集群
使用 kubeadm
,你能创建一个符合最佳实践的最小化 Kubernetes 集群。
事实上,你可以使用 kubeadm
配置一个通过
Kubernetes 一致性测试 的集群。
kubeadm
还支持其他集群生命周期功能,
例如启动引导令牌 和集群升级。
kubeadm 工具很棒,如果你需要:
一个尝试 Kubernetes 的简单方法。 一个现有用户可以自动设置集群并测试其应用程序的途径。 其他具有更大范围的生态系统和/或安装工具中的构建模块。 你可以在各种机器上安装和使用 kubeadm
:笔记本电脑,
一组云服务器,Raspberry Pi 等。无论是部署到云还是本地,
你都可以将 kubeadm
集成到预配置系统中,例如 Ansible 或 Terraform。
准备开始 要遵循本指南,你需要:
一台或多台运行兼容 deb/rpm 的 Linux 操作系统的计算机;例如:Ubuntu 或 CentOS。 每台机器 2 GB 以上的内存,内存不足时应用会受限制。 用作控制平面节点的计算机上至少有 2 个 CPU。 集群中所有计算机之间具有完全的网络连接。你可以使用公共网络或专用网络。 你还需要使用可以在新集群中部署特定 Kubernetes 版本对应的 kubeadm
。
Kubernetes 版本及版本偏差策略 适用于 kubeadm
以及整个 Kubernetes。
查阅该策略以了解支持哪些版本的 Kubernetes 和 kubeadm
。
该页面是为 Kubernetes v1.25 编写的。
kubeadm
工具的整体功能状态为一般可用性(GA)。一些子功能仍在积极开发中。
随着工具的发展,创建集群的实现可能会略有变化,但总体实现应相当稳定。
说明: 根据定义,在 kubeadm alpha
下的所有命令均在 alpha 级别上受支持。
目标 安装单个控制平面的 Kubernetes 集群 在集群上安装 Pod 网络,以便你的 Pod 可以相互连通 操作指南 主机准备 在所有主机上安装 容器运行时 和 kubeadm。
详细说明和其他前提条件,请参见安装 kubeadm 。
说明: 如果你已经安装了kubeadm,执行 apt-get update && apt-get upgrade
或 yum update
以获取 kubeadm 的最新版本。
升级时,kubelet 每隔几秒钟重新启动一次,
在 crashloop 状态中等待 kubeadm 发布指令。crashloop 状态是正常现象。
初始化控制平面后,kubelet 将正常运行。
准备所需的容器镜像 这个步骤是可选的,只适用于你希望 kubeadm init
和 kubeadm join
不去下载存放在 registry.k8s.io
上的默认的容器镜像的情况。
当你在离线的节点上创建一个集群的时候,Kubeadm 有一些命令可以帮助你预拉取所需的镜像。
阅读离线运行 kubeadm
获取更多的详情。
Kubeadm 允许你给所需要的镜像指定一个自定义的镜像仓库。
阅读使用自定义镜像
获取更多的详情。
初始化控制平面节点 控制平面节点是运行控制平面组件的机器,
包括 etcd (集群数据库)
和 API Server
(命令行工具 kubectl 与之通信)。
(推荐)如果计划将单个控制平面 kubeadm 集群升级成高可用,
你应该指定 --control-plane-endpoint
为所有控制平面节点设置共享端点。
端点可以是负载均衡器的 DNS 名称或 IP 地址。 选择一个 Pod 网络插件,并验证是否需要为 kubeadm init
传递参数。
根据你选择的第三方网络插件,你可能需要设置 --pod-network-cidr
的值。
请参阅安装 Pod 网络附加组件 。 (可选)kubeadm
试图通过使用已知的端点列表来检测容器运行时。
使用不同的容器运行时或在预配置的节点上安装了多个容器运行时,请为 kubeadm init
指定 --cri-socket
参数。
请参阅安装运行时 。 (可选)除非另有说明,否则 kubeadm
使用与默认网关关联的网络接口来设置此控制平面节点 API server 的广播地址。
要使用其他网络接口,请为 kubeadm init
设置 --apiserver-advertise-address=<ip-address>
参数。
要部署使用 IPv6 地址的 Kubernetes 集群,
必须指定一个 IPv6 地址,例如 --apiserver-advertise-address=2001:db8::101
要初始化控制平面节点,请运行:
关于 apiserver-advertise-address 和 ControlPlaneEndpoint 的注意事项 --apiserver-advertise-address
可用于为控制平面节点的 API server 设置广播地址,
--control-plane-endpoint
可用于为所有控制平面节点设置共享端点。
--control-plane-endpoint
允许 IP 地址和可以映射到 IP 地址的 DNS 名称。
请与你的网络管理员联系,以评估有关此类映射的可能解决方案。
这是一个示例映射:
192.168.0.102 cluster-endpoint
其中 192.168.0.102
是此节点的 IP 地址,cluster-endpoint
是映射到该 IP 的自定义 DNS 名称。
这将允许你将 --control-plane-endpoint=cluster-endpoint
传递给 kubeadm init
,并将相同的 DNS 名称传递给 kubeadm join
。
稍后你可以修改 cluster-endpoint
以指向高可用性方案中的负载均衡器的地址。
kubeadm 不支持将没有 --control-plane-endpoint
参数的单个控制平面集群转换为高可用性集群。
有关 kubeadm init
参数的更多信息,请参见 kubeadm 参考指南 。
要使用配置文件配置 kubeadm init
命令,
请参见带配置文件使用 kubeadm init 。
要自定义控制平面组件,包括可选的对控制平面组件和 etcd 服务器的活动探针提供 IPv6 支持,
请参阅自定义参数 。
要重新配置一个已经创建的集群,请参见
重新配置一个 kubeadm 集群 。
要再次运行 kubeadm init
,你必须首先卸载集群 。
如果将具有不同架构的节点加入集群,
请确保已部署的 DaemonSet 对这种体系结构具有容器镜像支持。
kubeadm init
首先运行一系列预检查以确保机器
准备运行 Kubernetes。这些预检查会显示警告并在错误时退出。然后 kubeadm init
下载并安装集群控制平面组件。这可能会需要几分钟。
完成之后你应该看到:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a Pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>
要使非 root 用户可以运行 kubectl,请运行以下命令,
它们也是 kubeadm init
输出的一部分:
mkdir -p $HOME /.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME /.kube/config
sudo chown $( id -u) :$( id -g) $HOME /.kube/config
或者,如果你是 root
用户,则可以运行:
export KUBECONFIG = /etc/kubernetes/admin.conf
警告: kubeadm 对 admin.conf
中的证书进行签名时,将其配置为
Subject: O = system:masters, CN = kubernetes-admin
。
system:masters
是一个例外的、超级用户组,可以绕过鉴权层(例如 RBAC)。
不要将 admin.conf
文件与任何人共享,应该使用 kubeadm kubeconfig user
命令为其他用户生成 kubeconfig 文件,完成对他们的定制授权。
更多细节请参见为其他用户生成 kubeconfig 文件 。
记录 kubeadm init
输出的 kubeadm join
命令。
你需要此命令将节点加入集群 。
令牌用于控制平面节点和加入节点之间的相互身份验证。
这里包含的令牌是密钥。确保它的安全,
因为拥有此令牌的任何人都可以将经过身份验证的节点添加到你的集群中。
可以使用 kubeadm token
命令列出,创建和删除这些令牌。
请参阅 kubeadm 参考指南 。
安装 Pod 网络附加组件 注意: 本节包含有关网络设置和部署顺序的重要信息。
在继续之前,请仔细阅读所有建议。
你必须部署一个基于 Pod 网络插件的
容器网络接口
(CNI),以便你的 Pod 可以相互通信。
在安装网络之前,集群 DNS (CoreDNS) 将不会启动。
注意你的 Pod 网络不得与任何主机网络重叠:
如果有重叠,你很可能会遇到问题。
(如果你发现网络插件的首选 Pod 网络与某些主机网络之间存在冲突,
则应考虑使用一个合适的 CIDR 块来代替,
然后在执行 kubeadm init
时使用 --pod-network-cidr
参数并在你的网络插件的 YAML 中替换它)。 默认情况下,kubeadm
将集群设置为使用和强制使用 RBAC (基于角色的访问控制)。
确保你的 Pod 网络插件支持 RBAC,以及用于部署它的 manifests 也是如此。 如果要为集群使用 IPv6(双协议栈或仅单协议栈 IPv6 网络),
请确保你的 Pod 网络插件支持 IPv6。
IPv6 支持已在 CNI v0.6.0 版本中添加。 说明: kubeadm 应该是与 CNI 无关的,对 CNI 驱动进行验证目前不在我们的端到端测试范畴之内。
如果你发现与 CNI 插件相关的问题,应在其各自的问题跟踪器中记录而不是在 kubeadm
或 kubernetes 问题跟踪器中记录。
一些外部项目为 Kubernetes 提供使用 CNI 的 Pod 网络,其中一些还支持网络策略 。
请参阅实现 Kubernetes 网络模型 的附加组件列表。
你可以使用以下命令在控制平面节点或具有 kubeconfig 凭据的节点上安装 Pod 网络附加组件:
kubectl apply -f <add-on.yaml>
每个集群只能安装一个 Pod 网络。
安装 Pod 网络后,你可以通过在 kubectl get pods --all-namespaces
输出中检查
CoreDNS Pod 是否 Running
来确认其是否正常运行。
一旦 CoreDNS Pod 启用并运行,你就可以继续加入节点。
如果你的网络无法正常工作或 CoreDNS 不在“运行中”状态,请查看 kubeadm
的
故障排除指南 。
托管节点标签 默认情况下,kubeadm 启用 NodeRestriction
准入控制器来限制 kubelets 在节点注册时可以应用哪些标签。准入控制器文档描述 kubelet --node-labels
选项允许使用哪些标签。
其中 node-role.kubernetes.io/control-plane
标签就是这样一个受限制的标签,
kubeadm 在节点创建后使用特权客户端手动应用此标签。
你可以使用一个有特权的 kubeconfig,比如由 kubeadm 管理的 /etc/kubernetes/admin.conf
,
通过执行 kubectl label
来手动完成操作。
控制平面节点隔离 默认情况下,出于安全原因,你的集群不会在控制平面节点上调度 Pod。
如果你希望能够在控制平面节点上调度 Pod,例如单机 Kubernetes 集群,请运行:
kubectl taint nodes --all node-role.kubernetes.io/control-plane- node-role.kubernetes.io/master-
输出看起来像:
这将从任何拥有 node-role.kubernetes.io/control-plane:NoSchedule
污点的节点(包括控制平面节点)上移除该污点。
这意味着调度程序将能够在任何地方调度 Pod。
加入节点 节点是你的工作负载(容器和 Pod 等)运行的地方。要将新节点添加到集群,请对每台计算机执行以下操作:
如果没有令牌,可以通过在控制平面节点上运行以下命令来获取令牌:
输出类似于以下内容:
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
8ewj1p.9r9hcjoqgajrj4gi 23h 2018-06-12T02:51:28Z authentication, The default bootstrap system:
signing token generated by bootstrappers:
'kubeadm init'. kubeadm:
default-node-token
默认情况下,令牌会在 24 小时后过期。如果要在当前令牌过期后将节点加入集群,
则可以通过在控制平面节点上运行以下命令来创建新令牌:
输出类似于以下内容:
如果你没有 --discovery-token-ca-cert-hash
的值,则可以通过在控制平面节点上执行以下命令链来获取它:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | \
openssl dgst -sha256 -hex | sed 's/^.* //'
输出类似于以下内容:
8cb2de97839780a412b93877f8507ad6c94f73add17d5d7058e91741c9d5ec78
说明: 要为 <control-plane-host>:<control-plane-port>
指定 IPv6 元组,必须将 IPv6 地址括在方括号中,例如:[2001:db8::101]:2073
输出应类似于:
[preflight] Running pre-flight checks
... (log output of join workflow) ...
Node join complete:
* Certificate signing request sent to control-plane and response
received.
* Kubelet informed of new secure connection details.
Run 'kubectl get nodes' on control-plane to see this machine join.
几秒钟后,当你在控制平面节点上执行 kubectl get nodes
,你会注意到该节点出现在输出中。
说明: 由于集群节点通常是按顺序初始化的,CoreDNS Pod 很可能都运行在第一个控制面节点上。
为了提供更高的可用性,请在加入至少一个新节点后
使用 kubectl -n kube-system rollout restart deployment coredns
命令,重新平衡这些 CoreDNS Pod。
(可选)从控制平面节点以外的计算机控制集群 为了使 kubectl 在其他计算机(例如笔记本电脑)上与你的集群通信,
你需要将管理员 kubeconfig 文件从控制平面节点复制到工作站,如下所示:
scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf get nodes
说明: 上面的示例假定为 root 用户启用了 SSH 访问。如果不是这种情况,
你可以使用 scp
将 admin.conf
文件复制给其他允许访问的用户。
admin.conf 文件为用户提供了对集群的超级用户特权。
该文件应谨慎使用。对于普通用户,建议生成一个你为其授予特权的唯一证书。
你可以使用 kubeadm alpha kubeconfig user --client-name <CN>
命令执行此操作。
该命令会将 KubeConfig 文件打印到 STDOUT,你应该将其保存到文件并分发给用户。
之后,使用 kubectl create (cluster)rolebinding
授予特权。
(可选)将 API 服务器代理到本地主机 如果要从集群外部连接到 API 服务器,则可以使用 kubectl proxy
:
scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf proxy
你现在可以在本地访问 API 服务器 http://localhost:8001/api/v1
。
清理 如果你在集群中使用了一次性服务器进行测试,则可以关闭这些服务器,而无需进一步清理。你可以使用 kubectl config delete-cluster
删除对集群的本地引用。
但是,如果要更干净地取消配置集群,
则应首先清空节点 并确保该节点为空,
然后取消配置该节点。
删除节点 使用适当的凭证与控制平面节点通信,运行:
kubectl drain <node name> --delete-emptydir-data --force --ignore-daemonsets
在删除节点之前,请重置 kubeadm
安装的状态:
重置过程不会重置或清除 iptables 规则或 IPVS 表。如果你希望重置 iptables,则必须手动进行:
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
如果要重置 IPVS 表,则必须运行以下命令:
现在删除节点:
kubectl delete node <node name>
如果你想重新开始,只需运行 kubeadm init
或 kubeadm join
并加上适当的参数。
清理控制平面 你可以在控制平面主机上使用 kubeadm reset
来触发尽力而为的清理。
有关此子命令及其选项的更多信息,请参见 kubeadm reset
参考文档。
下一步 反馈 版本偏差策略 虽然 kubeadm 允许所管理的组件有一定程度的版本偏差,
但是建议你将 kubeadm 的版本与控制平面组件、kube-proxy 和 kubelet 的版本相匹配。
kubeadm 中的 Kubernetes 版本偏差 kubeadm 可以与 Kubernetes 组件一起使用,这些组件的版本与 kubeadm 相同,或者比它大一个版本。
Kubernetes 版本可以通过使用 --kubeadm init
的 --kubernetes-version
标志或使用 --config
时的
ClusterConfiguration.kubernetesVersion
字段指定给 kubeadm。
这个选项将控制 kube-apiserver、kube-controller-manager、kube-scheduler 和 kube-proxy 的版本。
例子:
kubeadm 的版本为 1.25。 kubernetesVersion
必须为 1.25 或者 1.24。kubeadm 中 kubelet 的版本偏差 与 Kubernetes 版本类似,kubeadm 可以使用与 kubeadm 相同版本的 kubelet,
或者比 kubeadm 老一个版本的 kubelet。
例子:
kubeadm 的版本为 1.25。 主机上的 kubelet 必须为 1.25 或者 1.24。 kubeadm 支持的 kubeadm 的版本偏差 kubeadm 命令在现有节点或由 kubeadm 管理的整个集群上的操作有一定限制。
如果新的节点加入到集群中,用于 kubeadm join
的 kubeadm 二进制文件必须与用 kubeadm init
创建集群或用 kubeadm upgrade
升级同一节点时所用的 kubeadm 版本一致。
类似的规则适用于除了 kubeadm upgrade
以外的其他 kubeadm 命令。
kubeadm join
的例子:
使用 kubeadm init
创建集群时使用版本为 1.25 的 kubeadm。 添加节点所用的 kubeadm 可执行文件为版本 。 对于正在升级的节点,所使用的的 kubeadm 必须与管理该节点的 kubeadm 具有相同的
MINOR 版本或比后者新一个 MINOR 版本。
kubeadm upgrade
的例子:
用于创建或升级节点的 kubeadm 版本为 1.24。 用于升级节点的 kubeadm 版本必须为 1.24 或 1.25。 要了解更多关于不同 Kubernetes 组件之间的版本偏差,请参见
版本偏差策略 。
局限性 集群弹性 此处创建的集群具有单个控制平面节点,运行单个 etcd 数据库。
这意味着如果控制平面节点发生故障,你的集群可能会丢失数据并且可能需要从头开始重新创建。
解决方法:
定期备份 etcd 。
kubeadm 配置的 etcd 数据目录位于控制平面节点上的 /var/lib/etcd
中。 kubeadm deb/rpm 软件包和二进制文件是为 amd64、arm (32-bit)、arm64、ppc64le 和 s390x
构建的遵循多平台提案 。
从 v1.12 开始还支持用于控制平面和附加组件的多平台容器镜像。
只有一些网络提供商为所有平台提供解决方案。
请查阅上方的网络提供商清单或每个提供商的文档以确定提供商是否支持你选择的平台。
故障排除 如果你在使用 kubeadm 时遇到困难,
请查阅我们的故障排除文档 。
2.2.2.1.4 - 使用 kubeadm API 定制组件 本页面介绍了如何自定义 kubeadm 部署的组件。
你可以使用 ClusterConfiguration
结构中定义的参数,或者在每个节点上应用补丁来定制控制平面组件。
你可以使用 KubeletConfiguration
和 KubeProxyConfiguration
结构分别定制 kubelet 和 kube-proxy 组件。
所有这些选项都可以通过 kubeadm 配置 API 实现。
有关配置中的每个字段的详细信息,你可以导航到我们的
API 参考页面 。
使用 ClusterConfiguration
中的标志自定义控制平面 kubeadm ClusterConfiguration
对象为用户提供了一种方法,
用以覆盖传递给控制平面组件(如 APIServer、ControllerManager、Scheduler 和 Etcd)的默认参数。
各组件配置使用如下字段定义:
apiServer
controllerManager
scheduler
etcd
这些结构包含一个通用的 extraArgs
字段,该字段由 key: value
组成。
要覆盖控制平面组件的参数:
将适当的字段 extraArgs
添加到配置中。 向字段 extraArgs
添加要覆盖的参数值。 用 --config <YOUR CONFIG YAML>
运行 kubeadm init
。 说明: 你可以通过运行 kubeadm config print init-defaults
并将输出保存到你所选的文件中,
以默认值形式生成 ClusterConfiguration
对象。
说明: ClusterConfiguration
对象目前在 kubeadm 集群中是全局的。
这意味着你添加的任何标志都将应用于同一组件在不同节点上的所有实例。
要在不同节点上为每个组件应用单独的配置,你可以使用补丁 。
说明: 当前不支持重复的参数(keys)或多次传递相同的参数 --foo
。
要解决此问题,你必须使用补丁 。
APIServer 参数 有关详细信息,请参阅 kube-apiserver 参考文档 。
使用示例:
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
kubernetesVersion : v1.16.0
apiServer :
extraArgs :
anonymous-auth : "false"
enable-admission-plugins : AlwaysPullImages,DefaultStorageClass
audit-log-path : /home/johndoe/audit.log
ControllerManager 参数 有关详细信息,请参阅 kube-controller-manager 参考文档 。
使用示例:
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
kubernetesVersion : v1.16.0
controllerManager :
extraArgs :
cluster-signing-key-file : /home/johndoe/keys/ca.key
deployment-controller-sync-period : "50"
Scheduler 参数 有关详细信息,请参阅 kube-scheduler 参考文档 。
使用示例:
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
kubernetesVersion : v1.16.0
scheduler :
extraArgs :
config : /etc/kubernetes/scheduler-config.yaml
extraVolumes :
- name : schedulerconfig
hostPath : /home/johndoe/schedconfig.yaml
mountPath : /etc/kubernetes/scheduler-config.yaml
readOnly : true
pathType : "File"
Etcd 参数 有关详细信息,请参阅 etcd 服务文档 .
使用示例:
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
etcd :
local :
extraArgs :
election-timeout : 1000
使用补丁定制 特性状态: Kubernetes v1.22 [beta]
Kubeadm 允许将包含补丁文件的目录传递给各个节点上的 InitConfiguration
和 JoinConfiguration
。
这些补丁可被用作组件配置写入磁盘之前的最后一个自定义步骤。
可以使用 --config <你的 YAML 格式控制文件>
将配置文件传递给 kubeadm init
:
apiVersion : kubeadm.k8s.io/v1beta3
kind : InitConfiguration
patches :
directory : /home/user/somedir
说明: 对于 kubeadm init
,你可以传递一个包含 ClusterConfiguration
和 InitConfiguration
的文件,以 ---
分隔。
你可以使用 --config <你的 YAML 格式配置文件>
将配置文件传递给 kubeadm join
:
apiVersion : kubeadm.k8s.io/v1beta3
kind : JoinConfiguration
patches :
directory : /home/user/somedir
补丁目录必须包含名为 target[suffix][+patchtype].extension
的文件。
例如,kube-apiserver0+merge.yaml
或只是 etcd.json
。
target
可以是 kube-apiserver
、kube-controller-manager
、kube-scheduler
、etcd
和 kubeletconfiguration
之一。patchtype
可以是 strategy
、merge
或 json
之一,并且这些必须匹配
kubectl 支持 的补丁格式。
默认补丁类型是 strategic
的。extension
必须是 json
或 yaml
。suffix
是一个可选字符串,可用于确定首先按字母数字应用哪些补丁。说明: 如果你使用 kubeadm upgrade
升级 kubeadm 节点,你必须再次提供相同的补丁,以便在升级后保留自定义配置。
为此,你可以使用 --patches
参数,该参数必须指向同一目录。 kubeadm upgrade
目前不支持用于相同目的的 API 结构配置。
自定义 kubelet 要自定义 kubelet,你可以在同一配置文件中的 ClusterConfiguration
或 InitConfiguration
之外添加一个 KubeletConfiguration
,用 ---
分隔。
然后可以将此文件传递给 kubeadm init
,kubeadm 会将相同的
KubeletConfiguration
配置应用于集群中的所有节点。
要在基础 KubeletConfiguration
上应用特定节点的配置,你可以使用
kubeletconfiguration
补丁定制 。
或者你可以使用 kubelet
参数进行覆盖,方法是将它们传递到 InitConfiguration
和 JoinConfiguration
支持的 nodeRegistration.kubeletExtraArgs
字段中。一些 kubelet 参数已被弃用,
因此在使用这些参数之前,请在 kubelet 参考文档 中检查它们的状态。
更多详情,请参阅使用 kubeadm 配置集群中的每个 kubelet
自定义 kube-proxy 要自定义 kube-proxy,你可以在 ClusterConfiguration
或 InitConfiguration
之外添加一个由 ---
分隔的 KubeProxyConfiguration
, 传递给 kubeadm init
。
可以导航到 API 参考页面 查看更多详情,
说明: kubeadm 将 kube-proxy 部署为 DaemonSet ,
这意味着 KubeProxyConfiguration
将应用于集群中的所有 kube-proxy 实例。
2.2.2.1.5 - 高可用拓扑选项 本页面介绍了配置高可用(HA)Kubernetes 集群拓扑的两个选项。
你可以设置 HA 集群:
使用堆叠(stacked)控制平面节点,其中 etcd 节点与控制平面节点共存 使用外部 etcd 节点,其中 etcd 在与控制平面不同的节点上运行 在设置 HA 集群之前,你应该仔细考虑每种拓扑的优缺点。
说明: kubeadm 静态引导 etcd 集群。
阅读 etcd 集群指南 以获得更多详细信息。
堆叠(Stacked)etcd 拓扑 堆叠(Stacked)HA 集群是一种这样的拓扑 ,
其中 etcd 分布式数据存储集群堆叠在 kubeadm 管理的控制平面节点上,作为控制平面的一个组件运行。
每个控制平面节点运行 kube-apiserver
、kube-scheduler
和 kube-controller-manager
实例。
kube-apiserver
使用负载均衡器暴露给工作节点。
每个控制平面节点创建一个本地 etcd 成员(member),这个 etcd 成员只与该节点的 kube-apiserver
通信。
这同样适用于本地 kube-controller-manager
和 kube-scheduler
实例。
这种拓扑将控制平面和 etcd 成员耦合在同一节点上。相对使用外部 etcd 集群,
设置起来更简单,而且更易于副本管理。
然而,堆叠集群存在耦合失败的风险。如果一个节点发生故障,则 etcd 成员和控制平面实例都将丢失,
并且冗余会受到影响。你可以通过添加更多控制平面节点来降低此风险。
因此,你应该为 HA 集群运行至少三个堆叠的控制平面节点。
这是 kubeadm 中的默认拓扑。当使用 kubeadm init
和 kubeadm join --control-plane
时,
在控制平面节点上会自动创建本地 etcd 成员。
外部 etcd 拓扑 具有外部 etcd 的 HA 集群是一种这样的拓扑 ,
其中 etcd 分布式数据存储集群在独立于控制平面节点的其他节点上运行。
就像堆叠的 etcd 拓扑一样,外部 etcd 拓扑中的每个控制平面节点都会运行
kube-apiserver
、kube-scheduler
和 kube-controller-manager
实例。
同样,kube-apiserver
使用负载均衡器暴露给工作节点。但是 etcd 成员在不同的主机上运行,
每个 etcd 主机与每个控制平面节点的 kube-apiserver
通信。
这种拓扑结构解耦了控制平面和 etcd 成员。因此它提供了一种 HA 设置,
其中失去控制平面实例或者 etcd 成员的影响较小,并且不会像堆叠的 HA 拓扑那样影响集群冗余。
但此拓扑需要两倍于堆叠 HA 拓扑的主机数量。
具有此拓扑的 HA 集群至少需要三个用于控制平面节点的主机和三个用于 etcd 节点的主机。
接下来 2.2.2.1.6 - 利用 kubeadm 创建高可用集群 本文讲述了使用 kubeadm 设置一个高可用的 Kubernetes 集群的两种不同方式:
使用具有堆叠的控制平面节点。这种方法所需基础设施较少。etcd 成员和控制平面节点位于同一位置。 使用外部 etcd 集群。这种方法所需基础设施较多。控制平面的节点和 etcd 成员是分开的。 在下一步之前,你应该仔细考虑哪种方法更好地满足你的应用程序和环境的需求。
高可用拓扑选项 讲述了每种方法的优缺点。
如果你在安装 HA 集群时遇到问题,请在 kubeadm 问题跟踪 里向我们提供反馈。
你也可以阅读升级文档 。
注意: 这篇文档没有讲述在云提供商上运行集群的问题。在云环境中,
此处记录的方法不适用于类型为 LoadBalancer 的服务对象,也不适用于具有动态 PersistentVolume 的对象。
准备开始 根据集群控制平面所选择的拓扑结构不同,准备工作也有所差异:
需要准备:
配置满足 kubeadm 的最低要求
的三台机器作为控制面节点。控制平面节点为奇数有利于机器故障或者分区故障时重新选举。 配置满足 kubeadm 的最低要求
的三台机器作为工作节点 在集群中,确保所有计算机之间存在全网络连接(公网或私网) 在所有机器上具有 sudo 权限 从某台设备通过 SSH 访问系统中所有节点的能力 所有机器上已经安装 kubeadm
和 kubelet
还需要准备:
给 etcd 集群使用的另外至少三台机器。为了分布式一致性算法达到更好的投票效果,集群必须由奇数个节点组成。机器上已经安装 kubeadm
和 kubelet
。 机器上同样需要安装好容器运行时,并能正常运行。 拓扑详情请参考外部 etcd 拓扑 。
容器镜像 每台主机需要能够从 Kubernetes 容器镜像仓库(registry.k8s.io
)读取和拉取镜像。
想要在无法拉取 Kubernetes 仓库镜像的机器上部署高可用集群也是可行的。通过其他的手段保证主机上已经有对应的容器镜像即可。
命令行 一旦集群创建成功,需要在 PC 上安装 kubectl 用于管理 Kubernetes。
为了方便故障排查,也可以在每个控制平面节点上安装 kubectl
。
这两种方法的第一步 为 kube-apiserver 创建负载均衡器 说明: 使用负载均衡器需要许多配置。你的集群搭建可能需要不同的配置。下面的例子只是其中的一方面配置。
创建一个名为 kube-apiserver 的负载均衡器解析 DNS。
在云环境中,应该将控制平面节点放置在 TCP 转发负载平衡后面。
该负载均衡器将流量分配给目标列表中所有运行状况良好的控制平面节点。
API 服务器的健康检查是在 kube-apiserver 的监听端口(默认值 :6443
)
上进行的一个 TCP 检查。
不建议在云环境中直接使用 IP 地址。
负载均衡器必须能够在 API 服务器端口上与所有控制平面节点通信。
它还必须允许其监听端口的入站流量。
确保负载均衡器的地址始终匹配 kubeadm 的 ControlPlaneEndpoint
地址。
阅读软件负载平衡选项指南
以获取更多详细信息。
添加第一个控制平面节点到负载均衡器并测试连接:
nc -v LOAD_BALANCER_IP PORT
由于 API 服务器尚未运行,预期会出现一个连接拒绝错误。
然而超时意味着负载均衡器不能和控制平面节点通信。
如果发生超时,请重新配置负载均衡器与控制平面节点进行通信。
将其余控制平面节点添加到负载均衡器目标组。
使用堆控制平面和 etcd 节点 控制平面节点的第一步 初始化控制平面:
sudo kubeadm init --control-plane-endpoint "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" --upload-certs
你可以使用 --kubernetes-version
标志来设置要使用的 Kubernetes 版本。
建议将 kubeadm、kebelet、kubectl 和 Kubernetes 的版本匹配。 这个 --control-plane-endpoint
标志应该被设置成负载均衡器的地址或 DNS 和端口。 这个 --upload-certs
标志用来将在所有控制平面实例之间的共享证书上传到集群。
如果正好相反,你更喜欢手动地通过控制平面节点或者使用自动化工具复制证书,
请删除此标志并参考如下部分证书分配手册 。 说明: 标志 kubeadm init
、--config
和 --certificate-key
不能混合使用,
因此如果你要使用
kubeadm 配置 ,你必须在相应的配置结构
(位于 InitConfiguration
和 JoinConfiguration: controlPlane
)添加 certificateKey
字段。
说明: 一些 CNI 网络插件需要更多配置,例如指定 Pod IP CIDR,而其他插件则不需要。参考
CNI 网络文档 。
通过传递 --pod-network-cidr
标志添加 Pod CIDR,或者你可以使用 kubeadm
配置文件,在 ClusterConfiguration
的 networking
对象下设置 podSubnet
字段。
...
You can now join any number of control-plane node by running the following command on each as a root:
kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use kubeadm init phase upload-certs to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
将此输出复制到文本文件。 稍后你将需要它来将控制平面节点和工作节点加入集群。
当使用 --upload-certs
调用 kubeadm init
时,主控制平面的证书被加密并上传到 kubeadm-certs
Secret 中。
要重新上传证书并生成新的解密密钥,请在已加入集群节点的控制平面上使用以下命令:
sudo kubeadm init phase upload-certs --upload-certs
你还可以在 init
期间指定自定义的 --certificate-key
,以后可以由 join
使用。
要生成这样的密钥,可以使用以下命令: kubeadm certs certificate-key
说明: kubeadm-certs
Secret 和解密密钥会在两个小时后失效。
注意: 正如命令输出中所述,证书密钥可访问集群敏感数据。请妥善保管!
应用你所选择的 CNI 插件:
请遵循以下指示
安装 CNI 驱动。如果适用,请确保配置与 kubeadm 配置文件中指定的 Pod
CIDR 相对应。
说明: 在进行下一步之前,必须选择并部署合适的网络插件。
否则集群不会正常运行。
输入以下内容,并查看控制平面组件的 Pod 启动:
kubectl get pod -n kube-system -w
其余控制平面节点的步骤 对于每个其他控制平面节点,你应该:
执行先前由第一个节点上的 kubeadm init
输出提供给你的 join 命令。
它看起来应该像这样:
sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
这个 --control-plane
标志通知 kubeadm join
创建一个新的控制平面。 --certificate-key ...
将导致从集群中的 kubeadm-certs
Secret
下载控制平面证书并使用给定的密钥进行解密。外部 etcd 节点 使用外部 etcd 节点设置集群类似于用于堆叠 etcd 的过程,
不同之处在于你应该首先设置 etcd,并在 kubeadm 配置文件中传递 etcd 信息。
设置 ectd 集群 按照这里 的指示去设置。
根据这里 的描述配置 SSH。
将以下文件从集群中的任何 etcd 节点复制到第一个控制平面节点:
export CONTROL_PLANE = "ubuntu@10.0.0.7"
scp /etc/kubernetes/pki/etcd/ca.crt " ${ CONTROL_PLANE } " :
scp /etc/kubernetes/pki/apiserver-etcd-client.crt " ${ CONTROL_PLANE } " :
scp /etc/kubernetes/pki/apiserver-etcd-client.key " ${ CONTROL_PLANE } " :
用第一台控制平面节点的 user@host
替换 CONTROL_PLANE
的值。 设置第一个控制平面节点 用以下内容创建一个名为 kubeadm-config.yaml
的文件:
---
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
kubernetesVersion : stable
controlPlaneEndpoint : "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" # change this (see below)
etcd :
external :
endpoints :
- https://ETCD_0_IP:2379 # 适当地更改 ETCD_0_IP
- https://ETCD_1_IP:2379 # 适当地更改 ETCD_1_IP
- https://ETCD_2_IP:2379 # 适当地更改 ETCD_2_IP
caFile : /etc/kubernetes/pki/etcd/ca.crt
certFile : /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile : /etc/kubernetes/pki/apiserver-etcd-client.key
说明: 这里的堆叠(stacked)etcd 和外部 etcd 之前的区别在于设置外部 etcd
需要一个 etcd
的 external
对象下带有 etcd 端点的配置文件。
如果是内部 etcd,是自动管理的。
以下的步骤与设置内置 etcd 的集群是相似的:
在节点上运行 sudo kubeadm init --config kubeadm-config.yaml --upload-certs
命令。
记下输出的 join 命令,这些命令将在以后使用。
应用你选择的 CNI 插件。
说明: 在进行下一步之前,必须选择并部署合适的网络插件。
否则集群不会正常运行。
其他控制平面节点的步骤 步骤与设置内置 etcd 相同:
确保第一个控制平面节点已完全初始化。 使用保存到文本文件的 join 命令将每个控制平面节点连接在一起。
建议一次加入一个控制平面节点。 不要忘记默认情况下,--certificate-key
中的解密秘钥会在两个小时后过期。 列举控制平面之后的常见任务 安装工作节点 你可以使用之前存储的 kubeadm init
命令的输出将工作节点加入集群中:
sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
手动证书分发 如果你选择不将 kubeadm init
与 --upload-certs
命令一起使用,
则意味着你将必须手动将证书从主控制平面节点复制到将要加入的控制平面节点上。
有许多方法可以实现这种操作。下面的例子使用了 ssh
和 scp
:
如果要在单独的一台计算机控制所有节点,则需要 SSH。
在你的主设备上启用 ssh-agent,要求该设备能访问系统中的所有其他节点:
将 SSH 身份添加到会话中:
ssh-add ~/.ssh/path_to_private_key
检查节点间的 SSH 以确保连接是正常运行的
SSH 到任何节点时,请确保添加 -A
标志。
此标志允许你通过 SSH 登录到节点后从该节点上访问你自己 PC 上的 SSH 代理。
如果你不完全信任该节点上的用户会话安全,可以考虑使用其他替代方法。 当在任何节点上使用 sudo 时,请确保保持环境变量设置,以便 SSH
转发能够正常工作: 在所有节点上配置 SSH 之后,你应该在运行过 kubeadm init
命令的第一个控制平面节点上运行以下脚本。
该脚本会将证书从第一个控制平面节点复制到另一个控制平面节点:
在以下示例中,用其他控制平面节点的 IP 地址替换 CONTROL_PLANE_IPS
。
USER = ubuntu # 可定制
CONTROL_PLANE_IPS = "10.0.0.7 10.0.0.8"
for host in ${ CONTROL_PLANE_IPS } ; do
scp /etc/kubernetes/pki/ca.crt " ${ USER } " @$host :
scp /etc/kubernetes/pki/ca.key " ${ USER } " @$host :
scp /etc/kubernetes/pki/sa.key " ${ USER } " @$host :
scp /etc/kubernetes/pki/sa.pub " ${ USER } " @$host :
scp /etc/kubernetes/pki/front-proxy-ca.crt " ${ USER } " @$host :
scp /etc/kubernetes/pki/front-proxy-ca.key " ${ USER } " @$host :
scp /etc/kubernetes/pki/etcd/ca.crt " ${ USER } " @$host :etcd-ca.crt
# 如果你正使用外部 etcd,忽略下一行
scp /etc/kubernetes/pki/etcd/ca.key " ${ USER } " @$host :etcd-ca.key
done
注意: 只需要复制上面列表中的证书。kubeadm 将负责生成其余证书以及加入控制平面实例所需的 SAN。
如果你错误地复制了所有证书,由于缺少所需的 SAN,创建其他节点可能会失败。
然后,在每个即将加入集群的控制平面节点上,你必须先运行以下脚本,然后再运行 kubeadm join
。
该脚本会将先前复制的证书从主目录移动到 /etc/kubernetes/pki
:
USER = ubuntu # 可定制
mkdir -p /etc/kubernetes/pki/etcd
mv /home/${ USER } /ca.crt /etc/kubernetes/pki/
mv /home/${ USER } /ca.key /etc/kubernetes/pki/
mv /home/${ USER } /sa.pub /etc/kubernetes/pki/
mv /home/${ USER } /sa.key /etc/kubernetes/pki/
mv /home/${ USER } /front-proxy-ca.crt /etc/kubernetes/pki/
mv /home/${ USER } /front-proxy-ca.key /etc/kubernetes/pki/
mv /home/${ USER } /etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt
# 如果你正使用外部 etcd,忽略下一行
mv /home/${ USER } /etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
2.2.2.1.7 - 使用 kubeadm 创建一个高可用 etcd 集群 说明: 在本指南中,使用 kubeadm 作为外部 etcd 节点管理工具,请注意 kubeadm 不计划支持此类节点的证书更换或升级。
对于长期规划是使用 etcdadm 增强工具来管理这些方面。
默认情况下,kubeadm 在每个控制平面节点上运行一个本地 etcd 实例。也可以使用外部的 etcd 集群,并在不同的主机上提供 etcd 实例。
这两种方法的区别在 高可用拓扑的选项 页面中阐述。
这个任务将指导你创建一个由三个成员组成的高可用外部 etcd 集群,该集群在创建过程中可被 kubeadm 使用。
准备开始 三个可以通过 2379 和 2380 端口相互通信的主机。本文档使用这些作为默认端口。不过,它们可以通过 kubeadm 的配置文件进行自定义。 每个主机都应该能够访问 Kubernetes 容器镜像仓库 (registry.k8s.io),
或者使用 kubeadm config images list/pull
列出/拉取所需的 etcd 镜像。
本指南将把 etcd 实例设置为由 kubelet 管理的静态 Pod 。 一些可以用来在主机间复制文件的基础设施。例如 ssh
和 scp
就可以满足需求。 建立集群 一般来说,是在一个节点上生成所有证书并且只分发这些必要 的文件到其它节点上。
说明: kubeadm 包含生成下述证书所需的所有必要的密码学工具;在这个例子中,不需要其他加密工具。
说明: 下面的例子使用 IPv4 地址,但是你也可以使用 IPv6 地址配置 kubeadm、kubelet 和 etcd。一些 Kubernetes 选项支持双协议栈,但是 etcd 不支持。
关于 Kubernetes 双协议栈支持的更多细节,请参见 kubeadm 的双栈支持 。
将 kubelet 配置为 etcd 的服务管理器。
说明: 你必须在要运行 etcd 的所有主机上执行此操作。
由于 etcd 是首先创建的,因此你必须通过创建具有更高优先级的新文件来覆盖
kubeadm 提供的 kubelet 单元文件。cat << EOF > /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf
[Service]
ExecStart=
# 将下面的 "systemd" 替换为你的容器运行时所使用的 cgroup 驱动。
# kubelet 的默认值为 "cgroupfs"。
# 如果需要的话,将 "--container-runtime-endpoint " 的值替换为一个不同的容器运行时。
ExecStart=/usr/bin/kubelet --address=127.0.0.1 --pod-manifest-path=/etc/kubernetes/manifests --cgroup-driver=systemd
Restart=always
EOF
systemctl daemon-reload
systemctl restart kubelet
检查 kubelet 的状态以确保其处于运行状态:
为 kubeadm 创建配置文件。
使用以下脚本为每个将要运行 etcd 成员的主机生成一个 kubeadm 配置文件。
# 使用你的主机 IP 替换 HOST0、HOST1 和 HOST2 的 IP 地址
export HOST0 = 10.0.0.6
export HOST1 = 10.0.0.7
export HOST2 = 10.0.0.8
# 使用你的主机名更新 NAME0、NAME1 和 NAME2
export NAME0 = "infra0"
export NAME1 = "infra1"
export NAME2 = "infra2"
# 创建临时目录来存储将被分发到其它主机上的文件
mkdir -p /tmp/${ HOST0 } / /tmp/${ HOST1 } / /tmp/${ HOST2 } /
HOSTS =( ${ HOST0 } ${ HOST1 } ${ HOST2 } )
NAMES =( ${ NAME0 } ${ NAME1 } ${ NAME2 } )
for i in " ${ !HOSTS[@]} " ; do
HOST = ${ HOSTS [$i ]}
NAME = ${ NAMES [$i ]}
cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
---
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: InitConfiguration
nodeRegistration:
name: ${NAME}
localAPIEndpoint:
advertiseAddress: ${HOST}
---
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: ClusterConfiguration
etcd:
local:
serverCertSANs:
- "${HOST}"
peerCertSANs:
- "${HOST}"
extraArgs:
initial-cluster: ${NAMES[0]}=https://${HOSTS[0]}:2380,${NAMES[1]}=https://${HOSTS[1]}:2380,${NAMES[2]}=https://${HOSTS[2]}:2380
initial-cluster-state: new
name: ${NAME}
listen-peer-urls: https://${HOST}:2380
listen-client-urls: https://${HOST}:2379
advertise-client-urls: https://${HOST}:2379
initial-advertise-peer-urls: https://${HOST}:2380
EOF
done
生成证书颁发机构
如果你已经拥有 CA,那么唯一的操作是复制 CA 的 crt
和 key
文件到
etc/kubernetes/pki/etcd/ca.crt
和 /etc/kubernetes/pki/etcd/ca.key
。
复制完这些文件后继续下一步,“为每个成员创建证书”。
如果你还没有 CA,则在 $HOST0
(你为 kubeadm 生成配置文件的位置)上运行此命令。
kubeadm init phase certs etcd-ca
这一操作创建如下两个文件:
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
为每个成员创建证书
kubeadm init phase certs etcd-server --config= /tmp/${ HOST2 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-peer --config= /tmp/${ HOST2 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client --config= /tmp/${ HOST2 } /kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client --config= /tmp/${ HOST2 } /kubeadmcfg.yaml
cp -R /etc/kubernetes/pki /tmp/${ HOST2 } /
# 清理不可重复使用的证书
find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
kubeadm init phase certs etcd-server --config= /tmp/${ HOST1 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-peer --config= /tmp/${ HOST1 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client --config= /tmp/${ HOST1 } /kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client --config= /tmp/${ HOST1 } /kubeadmcfg.yaml
cp -R /etc/kubernetes/pki /tmp/${ HOST1 } /
find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete
kubeadm init phase certs etcd-server --config= /tmp/${ HOST0 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-peer --config= /tmp/${ HOST0 } /kubeadmcfg.yaml
kubeadm init phase certs etcd-healthcheck-client --config= /tmp/${ HOST0 } /kubeadmcfg.yaml
kubeadm init phase certs apiserver-etcd-client --config= /tmp/${ HOST0 } /kubeadmcfg.yaml
# 不需要移动 certs 因为它们是给 HOST0 使用的
# 清理不应从此主机复制的证书
find /tmp/${ HOST2 } -name ca.key -type f -delete
find /tmp/${ HOST1 } -name ca.key -type f -delete
复制证书和 kubeadm 配置
证书已生成,现在必须将它们移动到对应的主机。
USER = ubuntu
HOST = ${ HOST1 }
scp -r /tmp/${ HOST } /* ${ USER } @${ HOST } :
ssh ${ USER } @${ HOST }
USER@HOST $ sudo -Es
root@HOST $ chown -R root:root pki
root@HOST $ mv pki /etc/kubernetes/
确保已经所有预期的文件都存在
$HOST0
所需文件的完整列表如下:
/tmp/${HOST0}
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
├── ca.crt
├── ca.key
├── healthcheck-client.crt
├── healthcheck-client.key
├── peer.crt
├── peer.key
├── server.crt
└── server.key
在 $HOST1
上:
$ HOME
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
├── ca.crt
├── healthcheck-client.crt
├── healthcheck-client.key
├── peer.crt
├── peer.key
├── server.crt
└── server.key
在 $HOST2
上:
$ HOME
└── kubeadmcfg.yaml
---
/etc/kubernetes/pki
├── apiserver-etcd-client.crt
├── apiserver-etcd-client.key
└── etcd
├── ca.crt
├── healthcheck-client.crt
├── healthcheck-client.key
├── peer.crt
├── peer.key
├── server.crt
└── server.key
创建静态 Pod 清单
既然证书和配置已经就绪,是时候去创建清单了。
在每台主机上运行 kubeadm
命令来生成 etcd 使用的静态清单。
root@HOST0 $ kubeadm init phase etcd local --config= /tmp/${ HOST0 } /kubeadmcfg.yaml
root@HOST1 $ kubeadm init phase etcd local --config= $HOME /kubeadmcfg.yaml
root@HOST2 $ kubeadm init phase etcd local --config= $HOME /kubeadmcfg.yaml
可选:检查集群运行状况
docker run --rm -it \
--net host \
-v /etc/kubernetes:/etc/kubernetes registry.k8s.io/etcd:${ ETCD_TAG } etcdctl \
--cert /etc/kubernetes/pki/etcd/peer.crt \
--key /etc/kubernetes/pki/etcd/peer.key \
--cacert /etc/kubernetes/pki/etcd/ca.crt \
--endpoints https://${ HOST0 } :2379 endpoint health --cluster
...
https://[ HOST0 IP] :2379 is healthy: successfully committed proposal: took = 16.283339ms
https://[ HOST1 IP] :2379 is healthy: successfully committed proposal: took = 19.44402ms
https://[ HOST2 IP] :2379 is healthy: successfully committed proposal: took = 35.926451ms
将 ${ETCD_TAG}
设置为你的 etcd 镜像的版本标签,例如 3.4.3-0
。
要查看 kubeadm 使用的 etcd 镜像和标签,请执行
kubeadm config images list --kubernetes-version ${K8S_VERSION}
,
例如,其中的 ${K8S_VERSION}
可以是 v1.17.0
。 将 ${HOST0}
设置为要测试的主机的 IP 地址。 接下来 一旦拥有了一个正常工作的 3 成员的 etcd 集群,你就可以基于
使用 kubeadm 外部 etcd 的方法 ,
继续部署一个高可用的控制平面。
2.2.2.1.8 - 使用 kubeadm 配置集群中的每个 kubelet 特性状态: Kubernetes v1.11 [stable]
kubeadm CLI 工具的生命周期与 kubelet
解耦;kubelet 是一个守护程序,在 Kubernetes 集群中的每个节点上运行。
当 Kubernetes 初始化或升级时,kubeadm CLI 工具由用户执行,而 kubelet 始终在后台运行。
由于kubelet是守护程序,因此需要通过某种初始化系统或服务管理器进行维护。
当使用 DEB 或 RPM 安装 kubelet 时,配置系统去管理 kubelet。
你可以改用其他服务管理器,但需要手动地配置。
集群中涉及的所有 kubelet 的一些配置细节都必须相同,
而其他配置方面则需要基于每个 kubelet 进行设置,以适应给定机器的不同特性(例如操作系统、存储和网络)。
你可以手动地管理 kubelet 的配置,但是 kubeadm 现在提供一种 KubeletConfiguration
API
类型用于集中管理 kubelet 的配置 。
Kubelet 配置模式 以下各节讲述了通过使用 kubeadm 简化 kubelet 配置模式,而不是在每个节点上手动地管理 kubelet 配置。
将集群级配置传播到每个 kubelet 中 你可以通过 kubeadm init
和 kubeadm join
命令为 kubelet 提供默认值。
有趣的示例包括使用其他容器运行时或通过服务器设置不同的默认子网。
如果你想使用子网 10.96.0.0/12
作为服务的默认网段,你可以给 kubeadm 传递 --service-cidr
参数:
kubeadm init --service-cidr 10.96.0.0/12
现在,可以从该子网分配服务的虚拟 IP。
你还需要通过 kubelet 使用 --cluster-dns
标志设置 DNS 地址。
在集群中的每个管理器和节点上的 kubelet 的设置需要相同。
kubelet 提供了一个版本化的结构化 API 对象,该对象可以配置 kubelet
中的大多数参数,并将此配置推送到集群中正在运行的每个 kubelet 上。
此对象被称为 KubeletConfiguration
。
KubeletConfiguration
允许用户指定标志,例如用骆峰值代表集群的 DNS IP 地址,如下所示:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
clusterDNS :
- 10.96.0.10
有关 KubeletConfiguration
的更多详细信息,请参阅本节 。
提供特定于某实例的配置细节 由于硬件、操作系统、网络或者其他主机特定参数的差异。某些主机需要特定的 kubelet 配置。
以下列表提供了一些示例。
由 kubelet 配置标志 --resolv-conf
指定的 DNS 解析文件的路径在操作系统之间可能有所不同,
它取决于你是否使用 systemd-resolved
。
如果此路径错误,则在其 kubelet 配置错误的节点上 DNS 解析也将失败。
除非你使用云驱动,否则默认情况下 Node API 对象的 .metadata.name
会被设置为计算机的主机名。
如果你需要指定一个与机器的主机名不同的节点名称,你可以使用 --hostname-override
标志覆盖默认值。
当前,kubelet 无法自动检测容器运行时使用的 cgroup 驱动程序,
但是值 --cgroup-driver
必须与容器运行时使用的 cgroup 驱动程序匹配,以确保 kubelet 的健康运行状况。
要指定容器运行时,你必须用 --container-runtime-endpoint=<path>
标志来指定端点。
应用此类特定于实例的配置的推荐方法是使用
KubeletConfiguration
补丁 。
如果自定义的 KubeletConfiguration
API 对象使用像 kubeadm ... --config some-config-file.yaml
这样的配置文件进行传递,则可以配置 kubeadm 启动的 kubelet。
通过调用 kubeadm config print init-defaults --component-configs KubeletConfiguration
,
你可以看到此结构中的所有默认值。
也可以在基础 KubeletConfiguration
上应用实例特定的补丁。
阅读自定义 kubelet
来获取有关各个字段的更多信息。
使用 kubeadm init
时的工作流程 当调用 kubeadm init
时,kubelet 的配置会被写入磁盘 /var/lib/kubelet/config.yaml
,
并上传到集群 kube-system
命名空间的 kubelet-config
ConfigMap。
kubelet 配置信息也被写入 /etc/kubernetes/kubelet.conf
,其中包含集群内所有 kubelet 的基线配置。
此配置文件指向允许 kubelet 与 API 服务器通信的客户端证书。
这解决了将集群级配置传播到每个 kubelet 的需求。
针对为特定实例提供配置细节 的第二种模式,
kubeadm 的解决方法是将环境文件写入 /var/lib/kubelet/kubeadm-flags.env
,其中包含了一个标志列表,
当 kubelet 启动时,该标志列表会传递给 kubelet 标志在文件中的显示方式如下:
KUBELET_KUBEADM_ARGS = "--flag1=value1 --flag2=value2 ..."
除了启动 kubelet 时所使用的标志外,该文件还包含动态参数,例如 cgroup 驱动程序以及是否使用其他容器运行时套接字(--cri-socket
)。
将这两个文件编组到磁盘后,如果使用 systemd,则 kubeadm 尝试运行以下两个命令:
systemctl daemon-reload && systemctl restart kubelet
如果重新加载和重新启动成功,则正常的 kubeadm init
工作流程将继续。
使用 kubeadm join
时的工作流程 当运行 kubeadm join
时,kubeadm 使用 Bootstrap Token 证书执行 TLS 引导,该引导会获取一份证书,
该证书需要下载 kubelet-config
ConfigMap 并把它写入 /var/lib/kubelet/config.yaml
中。
动态环境文件的生成方式恰好与 kubeadm init
完全相同。
接下来,kubeadm
运行以下两个命令将新配置加载到 kubelet 中:
systemctl daemon-reload && systemctl restart kubelet
在 kubelet 加载新配置后,kubeadm 将写入 /etc/kubernetes/bootstrap-kubelet.conf
KubeConfig 文件中,
该文件包含 CA 证书和引导程序令牌。
kubelet 使用这些证书执行 TLS 引导程序并获取唯一的凭据,该凭据被存储在 /etc/kubernetes/kubelet.conf
中。
当 /etc/kubernetes/kubelet.conf
文件被写入后,kubelet 就完成了 TLS 引导过程。
Kubeadm 在完成 TLS 引导过程后将删除 /etc/kubernetes/bootstrap-kubelet.conf
文件。
kubelet 的 systemd drop-in 文件 kubeadm
中附带了有关系统如何运行 kubelet 的 systemd 配置文件。
请注意 kubeadm CLI 命令不会修改此文件。
通过 kubeadm
DEB 包
或者 RPM 包
安装的配置文件被写入 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
并由 systemd 使用。
它对原来的 RPM 版本 kubelet.service
或者 DEB 版本 kubelet.service
作了增强:
说明: 下面的内容只是一个例子。如果你不想使用包管理器,
请遵循没有包管理器 )
章节的指南。
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# 这是 "kubeadm init" 和 "kubeadm join" 运行时生成的文件,
# 动态地填充 KUBELET_KUBEADM_ARGS 变量
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# 这是一个文件,用户在不得已下可以将其用作替代 kubelet args。
# 用户最好使用 .NodeRegistration.KubeletExtraArgs 对象在配置文件中替代。
# KUBELET_EXTRA_ARGS 应该从此文件中获取。
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
此文件指定由 kubeadm 为 kubelet 管理的所有文件的默认位置。
用于 TLS 引导程序的 KubeConfig 文件为 /etc/kubernetes/bootstrap-kubelet.conf
,
但仅当 /etc/kubernetes/kubelet.conf
不存在时才能使用。 具有唯一 kubelet 标识的 KubeConfig 文件为 /etc/kubernetes/kubelet.conf
。 包含 kubelet 的组件配置的文件为 /var/lib/kubelet/config.yaml
。 包含的动态环境的文件 KUBELET_KUBEADM_ARGS
是来源于 /var/lib/kubelet/kubeadm-flags.env
。 包含用户指定标志替代的文件 KUBELET_EXTRA_ARGS
是来源于
/etc/default/kubelet
(对于 DEB),或者 /etc/sysconfig/kubelet
(对于 RPM)。
KUBELET_EXTRA_ARGS
在标志链中排在最后,并且在设置冲突时具有最高优先级。 Kubernetes 可执行文件和软件包内容 Kubernetes 版本对应的 DEB 和 RPM 软件包是:
2.2.2.1.9 - 使用 kubeadm 支持双协议栈 特性状态: Kubernetes v1.23 [stable]
你的集群包含双协议栈 组网支持,
这意味着集群网络允许你在两种地址族间任选其一。在集群中,控制面可以为同一个
Pod 或者 Service
同时赋予 IPv4 和 IPv6 地址。
准备开始 你需要已经遵从安装 kubeadm
中所给的步骤安装了 kubeadm 工具。
针对你要作为节点 使用的每台服务器,
确保其允许 IPv6 转发。在 Linux 节点上,你可以通过以 root 用户在每台服务器上运行
sysctl -w net.ipv6.conf.all.forwarding=1
来完成设置。
你需要一个可以使用的 IPv4 和 IPv6 地址范围。集群操作人员通常为 IPv4 使用
私有地址范围。对于 IPv6,集群操作人员通常会基于分配给该操作人员的地址范围,
从 2000::/3
中选择一个全局的单播地址块。你不需要将集群的 IP 地址范围路由
到公众互联网。
所分配的 IP 地址数量应该与你计划运行的 Pod 和 Service 的数量相适应。
说明: 如果你在使用 kubeadm upgrade
命令升级现有的集群,kubeadm
不允许更改 Pod
的 IP 地址范围(“集群 CIDR”),也不允许更改集群的服务地址范围(“Service CIDR”)。
创建双协议栈集群 要使用 kubeadm init
创建一个双协议栈集群,你可以传递与下面的例子类似的命令行参数:
# 这里的地址范围仅作示例使用
kubeadm init --pod-network-cidr= 10.244.0.0/16,2001:db8:42:0::/56 --service-cidr= 10.96.0.0/16,2001:db8:42:1::/112
为了更便于理解,参看下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件 ,
该文件用于双协议栈控制面的主控制节点。
---
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
networking :
podSubnet : 10.244.0.0 /16,2001:db8:42:0::/56
serviceSubnet : 10.96.0.0 /16,2001:db8:42:1::/112
---
apiVersion : kubeadm.k8s.io/v1beta3
kind : InitConfiguration
localAPIEndpoint :
advertiseAddress : "10.100.0.1"
bindPort : 6443
nodeRegistration :
kubeletExtraArgs :
node-ip : 10.100.0.2 ,fd00:1:2:3::2
InitConfiguration 中的 advertiseAddress
给出 API 服务器将公告自身要监听的
IP 地址。advertiseAddress
的取值与 kubeadm init
的标志
--apiserver-advertise-address
的取值相同。
运行 kubeadm 来实例化双协议栈控制面节点:
kubeadm init --config= kubeadm-config.yaml
kube-controller-manager 标志 --node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6
是使用默认值来设置的。参见配置 IPv4/IPv6 双协议栈 。
说明: 标志 --apiserver-advertise-address
不支持双协议栈。
向双协议栈集群添加节点 在添加节点之前,请确保该节点具有 IPv6 可路由的网络接口并且启用了 IPv6 转发。
下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件
示例用于向集群中添加工作节点。
apiVersion : kubeadm.k8s.io/v1beta3
kind : JoinConfiguration
discovery :
bootstrapToken :
apiServerEndpoint : 10.100.0.1 :6443
token : "clvldh.vjjwg16ucnhp94qr"
caCertHashes :
- "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
# 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration :
kubeletExtraArgs :
node-ip : 10.100.0.3 ,fd00:1:2:3::3
下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件
示例用于向集群中添加另一个控制面节点。
apiVersion : kubeadm.k8s.io/v1beta3
kind : JoinConfiguration
controlPlane :
localAPIEndpoint :
advertiseAddress : "10.100.0.2"
bindPort : 6443
discovery :
bootstrapToken :
apiServerEndpoint : 10.100.0.1 :6443
token : "clvldh.vjjwg16ucnhp94qr"
caCertHashes :
- "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
# 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration :
kubeletExtraArgs :
node-ip : 10.100.0.4 ,fd00:1:2:3::4
JoinConfiguration.controlPlane 中的 advertiseAddress
设定 API 服务器将公告自身要监听的
IP 地址。advertiseAddress
的取值与 kubeadm join
的标志
--apiserver-advertise-address
的取值相同。
kubeadm join --config= kubeadm-config.yaml
创建单协议栈集群 说明: 双协议栈支持并不意味着你需要使用双协议栈来寻址。
你可以部署一个启用了双协议栈联网特性的单协议栈集群。
为了更便于理解,参看下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件 示例,
该文件用于单协议栈控制面节点。
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
networking :
podSubnet : 10.244.0.0 /16
serviceSubnet : 10.96.0.0 /16
接下来 2.2.2.2 - 使用 kOps 安装 Kubernetes 本篇快速入门介绍了如何在 AWS 上轻松安装 Kubernetes 集群。
本篇使用了一个名为 kOps
的工具。
kOps
是一个自动化的制备系统:
准备开始 创建集群 (1/5) 安装 kops 安装 从下载页面 下载 kops
(从源代码构建也很方便):
使用下面的命令下载最新发布版本:
curl -LO https://github.com/kubernetes/kops/releases/download/$( curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4) /kops-darwin-amd64
要下载特定版本,使用特定的 kops 版本替换下面命令中的部分:
$( curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)
例如,要下载 kops v1.20.0,输入:
curl -LO https://github.com/kubernetes/kops/releases/download/v1.20.0/kops-darwin-amd64
令 kops 二进制文件可执行:
chmod +x kops-darwin-amd64
将 kops 二进制文件移到你的 PATH 下:
sudo mv kops-darwin-amd64 /usr/local/bin/kops
你也可以使用 Homebrew 安装 kops:
brew update && brew install kops
使用命令下载最新发布版本:
curl -LO https://github.com/kubernetes/kops/releases/download/$( curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4) /kops-linux-amd64
要下载 kops 的特定版本,用特定的 kops 版本替换下面命令中的部分:
$( curl -s https://api.github.com/repos/kubernetes/kops/releases/latest | grep tag_name | cut -d '"' -f 4)
例如,要下载 kops v1.20 版本,输入:
curl -LO https://github.com/kubernetes/kops/releases/download/v1.20.0/kops-linux-amd64
令 kops 二进制文件可执行:
chmod +x kops-linux-amd64
将 kops 二进制文件移到 PATH 下:
sudo mv kops-linux-amd64 /usr/local/bin/kops
你也可以使用 Homebrew 来安装 kops。
brew update && brew install kops
(2/5) 为你的集群创建一个 route53 域名 kops 在集群内部和外部都使用 DNS 进行发现操作,这样你可以从客户端访问
kubernetes API 服务器。
kops 对集群名称有明显的要求:它应该是有效的 DNS 名称。这样一来,你就不会再使集群混乱,
可以与同事明确共享集群,并且无需依赖记住 IP 地址即可访问集群。
你可以,或许应该使用子域名来划分集群。作为示例,我们将使用域名 useast1.dev.example.com
。
这样,API 服务器端点域名将为 api.useast1.dev.example.com
。
Route53 托管区域可以服务子域名。你的托管区域可能是 useast1.dev.example.com
,还有 dev.example.com
甚至 example.com
。
kops 可以与以上任何一种配合使用,因此通常你出于组织原因选择不同的托管区域。
例如,允许你在 dev.example.com
下创建记录,但不能在 example.com
下创建记录。
假设你使用 dev.example.com
作为托管区域。你可以使用
正常流程
或者使用诸如 aws route53 create-hosted-zone --name dev.example.com --caller-reference 1
之类的命令来创建该托管区域。
然后,你必须在父域名中设置你的 DNS 记录,以便该域名中的记录可以被解析。
在这里,你将在 example.com
中为 dev
创建 DNS 记录。
如果它是根域名,则可以在域名注册机构配置 DNS 记录。
例如,你需要在购买 example.com
的地方配置 example.com
。
检查你的 route53 域已经被正确设置(这是导致问题的最常见原因!)。
如果你安装了 dig 工具,则可以通过运行以下步骤再次检查集群是否配置正确:
dig NS dev.example.com
你应该看到 Route53 分配了你的托管区域的 4 条 DNS 记录。
(3/5) 创建一个 S3 存储桶来存储集群状态 kops 使你即使在安装后也可以管理集群。为此,它必须跟踪已创建的集群及其配置、所使用的密钥等。
此信息存储在 S3 存储桶中。S3 权限用于控制对存储桶的访问。
多个集群可以使用同一 S3 存储桶,并且你可以在管理同一集群的同事之间共享一个
S3 存储桶 - 这比传递 kubecfg 文件容易得多。
但是有权访问 S3 存储桶的任何人都将拥有对所有集群的管理访问权限,
因此你不想在运营团队之外共享它。
因此,通常每个运维团队都有一个 S3 存储桶(而且名称通常对应于上面托管区域的名称!)
在我们的示例中,我们选择 dev.example.com
作为托管区域,因此我们选择
clusters.dev.example.com
作为 S3 存储桶名称。
导出 AWS_PROFILE
文件(如果你需要选择一个配置文件用来使 AWS CLI 正常工作) 使用 aws s3 mb s3://clusters.dev.example.com
创建 S3 存储桶 你可以进行 export KOPS_STATE_STORE=s3://clusters.dev.example.com
操作,
然后 kops 将默认使用此位置。
我们建议将其放入你的 bash profile 文件或类似文件中。 (4/5) 建立你的集群配置 运行 kops create cluster
以创建你的集群配置:
kops create cluster --zones=us-east-1c useast1.dev.example.com
kops 将为你的集群创建配置。请注意,它仅 创建配置,实际上并没有创建云资源。
你将在下一步中使用 kops update cluster
进行创建。
这使你有机会查看配置或进行更改。
它打印出可用于进一步探索的命令:
使用以下命令列出集群:kops get cluster
使用以下命令编辑该集群:kops edit cluster useast1.dev.example.com
使用以下命令编辑你的节点实例组:kops edit ig --name = useast1.dev.example.com nodes
使用以下命令编辑你的主实例组:kops edit ig --name = useast1.dev.example.com master-us-east-1c
如果这是你第一次使用 kops,请花几分钟尝试一下!实例组是一组实例,将被注册为 Kubernetes 节点。
在 AWS 上,这是通过 auto-scaling-groups 实现的。你可以有多个实例组。
例如,你可能想要混合了 Spot 实例和按需实例的节点,或者混合了 GPU 实例和非 GPU 实例的节点。
(5/5) 在 AWS 中创建集群 运行 kops update cluster
以在 AWS 中创建集群:
kops update cluster useast1.dev.example.com --yes
这需要几秒钟的时间才能运行,但实际上集群可能需要几分钟才能准备就绪。
每当更改集群配置时,都会使用 kops update cluster
工具。
它将在集群中应用你对配置进行的更改,根据需要重新配置 AWS 或者 Kubernetes。
例如,在你运行 kops edit ig nodes
之后,然后运行 kops update cluster --yes
应用你的配置,有时你还必须运行 kops rolling-update cluster
立即回滚更新配置。
如果没有 --yes
参数,kops update cluster
操作将向你显示其操作的预览效果。这对于生产集群很方便!
探索其他附加组件 请参阅附加组件列表 探索其他附加组件,
包括用于 Kubernetes 集群的日志记录、监视、网络策略、可视化和控制的工具。
清理 删除集群:kops delete cluster useast1.dev.example.com --yes
接下来 2.2.2.3 - 使用 Kubespray 安装 Kubernetes 此快速入门有助于使用 Kubespray
安装在 GCE、Azure、OpenStack、AWS、vSphere、Equinix Metal(曾用名 Packet)、Oracle Cloud
Infrastructure(实验性)或 Baremetal 上托管的 Kubernetes 集群。
Kubespray 是由若干 Ansible Playbook、
清单(inventory) 、
制备工具和通用 OS/Kubernetes 集群配置管理任务的领域知识组成的。
Kubespray 提供:
高可用性集群 可组合属性(例如可选择网络插件) 支持大多数流行的 Linux 发行版Flatcar Container Linux Debian Bullseye、Buster、Jessie、Stretch Ubuntu 16.04、18.04、20.04、22.04 CentOS/RHEL 7、8、9 Fedora 35、36 Fedora CoreOS openSUSE Leap 15.x/Tumbleweed Oracle Linux 7、8、9 Alma Linux 8、9 Rocky Linux 8、9 Kylin Linux Advanced Server V10 Amazon Linux 2 持续集成测试 要选择最适合你的用例的工具,请阅读
kubeadm 和
kops
之间的这份比较 。
创建集群 (1/5)满足下层设施要求 按以下要求 来配置服务器:
Kubernetes 的最低版本要求为 V1.22在将运行 Ansible 命令的计算机上安装 Ansible v2.11(或更高版本)、Jinja 2.11(或更高版本)和 python-netaddr 目标服务器必须能够访问 Internet 才能拉取 Docker 镜像。否则,
需要其他配置(请参见离线环境 ) 目标服务器配置为允许 IPv4 转发 如果针对 Pod 和 Service 使用 IPv6,则目标服务器配置为允许 IPv6 转发 防火墙不是由 kubespray 管理的 。你需要根据需求设置适当的规则策略。为了避免部署过程中出现问题,可以禁用防火墙。如果从非 root 用户帐户运行 kubespray,则应在目标服务器中配置正确的特权升级方法并指定
ansible_become
标志或命令参数 --become
或 -b
Kubespray 提供以下实用程序来帮助你设置环境:
(2/5)编写清单文件 设置服务器后,请创建一个
Ansible 的清单文件 。
你可以手动执行此操作,也可以通过动态清单脚本执行此操作。有关更多信息,请参阅
“建立你自己的清单 ”。
(3/5)规划集群部署 Kubespray 能够自定义部署的许多方面:
选择部署模式: kubeadm 或非 kubeadm CNI(网络)插件 DNS 配置 控制平面的选择:本机/可执行文件或容器化 组件版本 Calico 路由反射器 组件运行时选项 证书生成方式 可以修改变量文件 以进行
Kubespray 定制。
如果你刚刚开始使用 Kubespray,请考虑使用 Kubespray 默认设置来部署你的集群并探索 Kubernetes。
(4/5)部署集群 接下来,部署你的集群:
使用 ansible-playbook
进行集群部署。
ansible-playbook -i your/inventory/inventory.ini cluster.yml -b -v \
--private-key= ~/.ssh/private_key
大型部署(超过 100 个节点)
可能需要特定的调整 ,
以获得最佳效果。
(5/5)验证部署 Kubespray 提供了一种使用
Netchecker
验证 Pod 间连接和 DNS 解析的方法。
Netchecker 确保 netchecker-agents Pod 可以解析 DNS 请求,
并在默认命名空间内对每个请求执行 ping 操作。
这些 Pod 模仿其他工作负载类似的行为,并用作集群运行状况指示器。
集群操作 Kubespray 提供了其他 Playbook 来管理集群: scale 和 upgrade 。
扩展集群 你可以通过运行 scale playbook 向集群中添加工作节点。有关更多信息,
请参见 “添加节点 ”。
你可以通过运行 remove-node playbook 来从集群中删除工作节点。有关更多信息,
请参见 “删除节点 ”。
升级集群 你可以通过运行 upgrade-cluster Playbook 来升级集群。有关更多信息,请参见
“升级 ”。
清理 你可以通过 reset playbook
重置节点并清除所有与 Kubespray 一起安装的组件。
注意: 运行 reset playbook 时,请确保不要意外地将生产集群作为目标!
反馈 接下来 2.2.3 - Turnkey 云解决方案 本页列示 Kubernetes 认证解决方案供应商。
在每一个供应商分页,你可以学习如何安装和设置生产就绪的集群。
2.3 - 最佳实践 2.3.1 - 大规模集群的注意事项 集群是运行 Kubernetes 代理的、
由控制平面 管理的一组
节点 (物理机或虚拟机)。
Kubernetes v1.25 单个集群支持的最大节点数为 5000。
更具体地说,Kubernetes 旨在适应满足以下所有 标准的配置:
每个节点的 Pod 数量不超过 110 节点数不超过 5000 Pod 总数不超过 150000 容器总数不超过 300000 你可以通过添加或删除节点来扩展集群。集群扩缩的方式取决于集群的部署方式。
云供应商资源配额 为避免遇到云供应商配额问题,在创建具有大规模节点的集群时,请考虑以下事项:
请求增加云资源的配额,例如:计算实例 CPUs 存储卷 使用中的 IP 地址 数据包过滤规则集 负载均衡数量 网络子网 日志流 由于某些云供应商限制了创建新实例的速度,因此通过分批启动新节点来控制集群扩展操作,并在各批之间有一个暂停。 控制面组件 对于大型集群,你需要一个具有足够计算能力和其他资源的控制平面。
通常,你将在每个故障区域运行一个或两个控制平面实例,
先垂直缩放这些实例,然后在到达下降点(垂直)后再水平缩放。
你应该在每个故障区域至少应运行一个实例,以提供容错能力。
Kubernetes 节点不会自动将流量引向相同故障区域中的控制平面端点。
但是,你的云供应商可能有自己的机制来执行此操作。
例如,使用托管的负载均衡器时,你可以配置负载均衡器发送源自故障区域 A 中的 kubelet 和 Pod 的流量,
并将该流量仅定向到也位于区域 A 中的控制平面主机。
如果单个控制平面主机或端点故障区域 A 脱机,则意味着区域 A 中的节点的所有控制平面流量现在都在区域之间发送。
在每个区域中运行多个控制平面主机能降低出现这种结果的可能性。
etcd 存储 为了提高大规模集群的性能,你可以将事件对象存储在单独的专用 etcd 实例中。
在创建集群时,你可以(使用自定义工具):
启动并配置额外的 etcd 实例 配置 API 服务器 ,将它用于存储事件 有关为大型集群配置和管理 etcd 的详细信息,
请参阅为 Kubernetes 运行 etcd 集群
和使用 kubeadm 创建一个高可用 etcd 集群 。
插件资源 Kubernetes 资源限制
有助于最大程度地减少内存泄漏的影响以及 Pod 和容器可能对其他组件的其他方式的影响。
这些资源限制适用于插件 资源,
就像它们适用于应用程序工作负载一样。
例如,你可以对日志组件设置 CPU 和内存限制
...
containers :
- name : fluentd-cloud-logging
image : fluent/fluentd-kubernetes-daemonset:v1
resources :
limits :
cpu : 100m
memory : 200Mi
插件的默认限制通常基于从中小规模 Kubernetes 集群上运行每个插件的经验收集的数据。
插件在大规模集群上运行时,某些资源消耗常常比其默认限制更多。
如果在不调整这些值的情况下部署了大规模集群,则插件可能会不断被杀死,因为它们不断达到内存限制。
或者,插件可能会运行,但由于 CPU 时间片的限制而导致性能不佳。
为避免遇到集群插件资源问题,在创建大规模集群时,请考虑以下事项:
部分垂直扩展插件 —— 总有一个插件副本服务于整个集群或服务于整个故障区域。
对于这些附加组件,请在扩大集群时加大资源请求和资源限制。 许多水平扩展插件 —— 你可以通过运行更多的 Pod 来增加容量——但是在大规模集群下,
可能还需要稍微提高 CPU 或内存限制。
VerticalPodAutoscaler 可以在 recommender 模式下运行,
以提供有关请求和限制的建议数字。 一些插件在每个节点上运行一个副本,并由 DaemonSet 控制:
例如,节点级日志聚合器。与水平扩展插件的情况类似,
你可能还需要稍微提高 CPU 或内存限制。 接下来 VerticalPodAutoscaler
是一种自定义资源,你可以将其部署到集群中,帮助你管理资源请求和 Pod 的限制。
访问 Vertical Pod Autoscaler
以了解有关 VerticalPodAutoscaler
的更多信息,
以及如何使用它来扩展集群组件(包括对集群至关重要的插件)的信息。
集群自动扩缩器
与许多云供应商集成在一起,帮助你在你的集群中,按照资源需求级别运行正确数量的节点。
addon resizer
可帮助你在集群规模变化时自动调整插件的大小。
2.3.2 - 运行于多可用区环境 本页描述如何跨多个区(Zone)中运行集群。
背景 Kubernetes 从设计上允许同一个 Kubernetes 集群跨多个失效区来运行,
通常这些区位于某个称作 区域(region) 逻辑分组中。
主要的云提供商都将区域定义为一组失效区的集合(也称作 可用区(Availability Zones) ),
能够提供一组一致的功能特性:每个区域内,各个可用区提供相同的 API 和服务。
典型的云体系结构都会尝试降低某个区中的失效影响到其他区中服务的概率。
控制面行为 所有的控制面组件
都支持以一组可相互替换的资源池的形式来运行,每个组件都有多个副本。
当你部署集群控制面时,应将控制面组件的副本跨多个失效区来部署。
如果可用性是一个很重要的指标,应该选择至少三个失效区,并将每个
控制面组件(API 服务器、调度器、etcd、控制器管理器)复制多个副本,
跨至少三个失效区来部署。如果你在运行云控制器管理器,则也应该将
该组件跨所选的三个失效区来部署。
说明: Kubernetes 并不会为 API 服务器端点提供跨失效区的弹性。
你可以为集群 API 服务器使用多种技术来提升其可用性,包括使用
DNS 轮转、SRV 记录或者带健康检查的第三方负载均衡解决方案等等。
节点行为 Kubernetes 自动为负载资源(如Deployment
或 StatefulSet ))
跨集群中不同节点来部署其 Pods。
这种分布逻辑有助于降低失效带来的影响。
节点启动时,每个节点上的 kubelet 会向 Kubernetes API 中代表该 kubelet 的 Node 对象
添加 标签 。
这些标签可能包含区信息 。
如果你的集群跨了多个可用区或者地理区域,你可以使用节点标签,结合
Pod 拓扑分布约束
来控制如何在你的集群中多个失效域之间分布 Pods。这里的失效域可以是
地理区域、可用区甚至是特定节点。
这些提示信息使得调度器
能够更好地分布 Pods,以实现更好的可用性,降低因为某种失效给整个工作负载
带来的风险。
例如,你可以设置一种约束,确保某个 StatefulSet 中的三个副本都运行在
不同的可用区中,只要其他条件允许。你可以通过声明的方式来定义这种约束,
而不需要显式指定每个工作负载使用哪些可用区。
跨多个区分布节点 Kubernetes 的核心逻辑并不会帮你创建节点,你需要自行完成此操作,或者使用
类似 Cluster API 这类工具来替你管理节点。
使用类似 Cluster API 这类工具,你可以跨多个失效域来定义一组用做你的集群
工作节点的机器,以及当整个区的服务出现中断时如何自动治愈集群的策略。
为 Pods 手动指定区 你可以应用节点选择算符约束
到你所创建的 Pods 上,或者为 Deployment、StatefulSet 或 Job 这类工作负载资源
中的 Pod 模板设置此类约束。
跨区的存储访问 当创建持久卷时,PersistentVolumeLabel
准入控制器
会自动向那些链接到特定区的 PersistentVolume 添加区标签。
调度器 通过其
NoVolumeZoneConflict
断言确保申领给定 PersistentVolume 的 Pods 只会
被调度到该卷所在的可用区。
你可以为 PersistentVolumeClaim 指定StorageClass
以设置该类中的存储可以使用的失效域(区)。
要了解如何配置能够感知失效域或区的 StorageClass,请参阅
可用的拓扑逻辑 。
网络 Kubernetes 自身不提供与可用区相关的联网配置。
你可以使用网络插件
来配置集群的联网,该网络解决方案可能拥有一些与可用区相关的元素。
例如,如果你的云提供商支持 type=LoadBalancer
的 Service,则负载均衡器
可能仅会将请求流量发送到运行在负责处理给定连接的负载均衡器组件所在的区。
请查阅云提供商的文档了解详细信息。
对于自定义的或本地集群部署,也可以考虑这些因素
Service
Ingress 的行为,
包括处理不同失效区的方法,在很大程度上取决于你的集群是如何搭建的。
失效恢复 在搭建集群时,你可能需要考虑当某区域中的所有失效区都同时掉线时,是否以及如何
恢复服务。例如,你是否要求在某个区中至少有一个节点能够运行 Pod?
请确保任何对集群很关键的修复工作都不要指望集群中至少有一个健康节点。
例如:当所有节点都不健康时,你可能需要运行某个修复性的 Job,
该 Job 要设置特定的容忍度
以便修复操作能够至少将一个节点恢复为可用状态。
Kubernetes 对这类问题没有现成的解决方案;不过这也是要考虑的因素之一。
接下来 要了解调度器如何在集群中放置 Pods 并遵从所配置的约束,可参阅
调度与驱逐 。
2.3.3 - 校验节点设置 节点一致性测试 是一个容器化的测试框架,提供了针对节点的系统验证和功能测试。
测试验证节点是否满足 Kubernetes 的最低要求;通过测试的节点有资格加入 Kubernetes 集群。
该测试主要检测节点是否满足 Kubernetes 的最低要求,通过检测的节点有资格加入 Kubernetes 集群。
节点的前提条件 要运行节点一致性测试,节点必须满足与标准 Kubernetes 节点相同的前提条件。节点至少应安装以下守护程序:
要运行节点一致性测试,请执行以下步骤:
得出 kubelet 的 --kubeconfig
的值;例如:--kubeconfig=/var/lib/kubelet/config.yaml
。
由于测试框架启动了本地控制平面来测试 kubelet,因此使用 http://localhost:8080
作为API 服务器的 URL。
一些其他的 kubelet 命令行参数可能会被用到:--cloud-provider
:如果使用 --cloud-provider=gce
,需要移除这个参数来运行测试。 使用以下命令运行节点一致性测试:
# $CONFIG_DIR 是你 Kubelet 的 pod manifest 路径。
# $LOG_DIR 是测试的输出路径。
sudo docker run -it --rm --privileged --net= host \
-v /:/rootfs -v $CONFIG_DIR :$CONFIG_DIR -v $LOG_DIR :/var/result \
registry.k8s.io/node-test:0.2
Kubernetes 也为其他硬件体系结构的系统提供了节点一致性测试的 Docker 镜像:
架构 镜像 amd64 node-test-amd64 arm node-test-arm arm64 node-test-arm64
运行特定的测试 要运行特定测试,请使用你希望运行的测试的特定表达式覆盖环境变量 FOCUS
。
sudo docker run -it --rm --privileged --net= host \
-v /:/rootfs:ro -v $CONFIG_DIR :$CONFIG_DIR -v $LOG_DIR :/var/result \
-e FOCUS = MirrorPod \ # Only run MirrorPod test
registry.k8s.io/node-test:0.2
要跳过特定的测试,请使用你希望跳过的测试的常规表达式覆盖环境变量 SKIP
。
sudo docker run -it --rm --privileged --net= host \
-v /:/rootfs:ro -v $CONFIG_DIR :$CONFIG_DIR -v $LOG_DIR :/var/result \
-e SKIP = MirrorPod \ # 运行除 MirrorPod 测试外的所有一致性测试内容
registry.k8s.io/node-test:0.2
节点一致性测试是节点端到端测试 的容器化版本。
默认情况下,它会运行所有一致性测试。
理论上,只要合理地配置容器和挂载所需的卷,就可以运行任何的节点端到端测试用例。但是这里强烈建议只运行一致性测试 ,因为运行非一致性测试需要很多复杂的配置。
注意事项 测试会在节点上遗留一些 Docker 镜像,包括节点一致性测试本身的镜像和功能测试相关的镜像。 测试会在节点上遗留一些死的容器。这些容器是在功能测试的过程中创建的。 2.3.4 - 强制实施 Pod 安全性标准 本页提供实施 Pod 安全标准(Pod Security Standards)
时的一些最佳实践。
使用内置的 Pod 安全性准入控制器 特性状态: Kubernetes v1.25 [stable]
Pod 安全性准入控制器
尝试替换已被废弃的 PodSecurityPolicies。
完全未经配置的名字空间应该被视为集群安全模型中的重大缺陷。
我们建议花一些时间来分析在每个名字空间中执行的负载的类型,
并通过引用 Pod 安全性标准来确定每个负载的合适级别。
未设置标签的名字空间应该视为尚未被评估。
针对所有名字空间中的所有负载都具有相同的安全性需求的场景,
我们提供了一个示例
用来展示如何批量应用 Pod 安全性标签。
拥抱最小特权原则 在一个理想环境中,每个名字空间中的每个 Pod 都会满足 restricted
策略的需求。
不过,这既不可能也不现实,某些负载会因为合理的原因而需要特权上的提升。
允许 privileged
负载的名字空间需要建立并实施适当的访问控制机制。 对于运行在特权宽松的名字空间中的负载,需要维护其独特安全性需求的文档。
如果可能的话,要考虑如何进一步约束这些需求。 采用多种模式的策略 Pod 安全性标准准入控制器的 audit
和 warn
模式(mode)
能够在不影响现有负载的前提下,让该控制器更方便地收集关于 Pod 的重要的安全信息。
针对所有名字空间启用这些模式是一种好的实践,将它们设置为你最终打算 enforce
的
期望的 级别和版本。这一阶段中所生成的警告和审计注解信息可以帮助你到达这一状态。
如果你期望负载的作者能够作出变更以便适应期望的级别,可以启用 warn
模式。
如果你希望使用审计日志了监控和驱动变更,以便负载能够适应期望的级别,可以启用 audit
模式。
当你将 enforce
模式设置为期望的取值时,这些模式在不同的场合下仍然是有用的:
通过将 warn
设置为 enforce
相同的级别,客户可以在尝试创建无法通过合法检查的 Pod
(或者包含 Pod 模板的资源)时收到警告信息。这些信息会帮助于更新资源使其合规。 在将 enforce
锁定到特定的非最新版本的名字空间中,将 audit
和 warn
模式设置为 enforce
一样的级别而非 latest
版本,
这样可以方便看到之前版本所允许但当前最佳实践中被禁止的设置。 第三方替代方案 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Kubernetes 生态系统中也有一些其他强制实施安全设置的替代方案处于开发状态中:
采用 内置的 方案(例如 PodSecurity 准入控制器)还是第三方工具,
这一决策完全取决于你自己的情况。在评估任何解决方案时,对供应链的信任都是至关重要的。
最终,使用前述方案中的 任何 一种都好过放任自流。
2.3.5 - PKI 证书和要求 Kubernetes 需要 PKI 证书才能进行基于 TLS 的身份验证。如果你是使用
kubeadm 安装的 Kubernetes,
则会自动生成集群所需的证书。你还可以生成自己的证书。
例如,不将私钥存储在 API 服务器上,可以让私钥更加安全。此页面说明了集群必需的证书。
集群是如何使用证书的 Kubernetes 需要 PKI 才能执行以下操作:
Kubelet 的客户端证书,用于 API 服务器身份验证 Kubelet 服务端证书 ,
用于 API 服务器与 Kubelet 的会话 API 服务器端点的证书 集群管理员的客户端证书,用于 API 服务器身份认证 API 服务器的客户端证书,用于和 Kubelet 的会话 API 服务器的客户端证书,用于和 etcd 的会话 控制器管理器的客户端证书或 kubeconfig,用于和 API 服务器的会话 调度器的客户端证书或 kubeconfig,用于和 API 服务器的会话 前端代理 的客户端及服务端证书说明: 只有当你运行 kube-proxy
并要支持扩展 API 服务器 时,
才需要 front-proxy
证书。
etcd 还实现了双向 TLS 来对客户端和对其他对等节点进行身份验证。
证书存放的位置 假如通过 kubeadm 安装 Kubernetes,大多数证书都存储在 /etc/kubernetes/pki
。
本文档中的所有路径都是相对于该目录的,但用户账户证书除外,kubeadm 将其放在 /etc/kubernetes
中。
如果你不想通过 kubeadm 生成这些必需的证书,你可以使用一个单一的根 CA
来创建这些证书或者直接提供所有证书。
参见证书 以进一步了解创建自己的证书机构。
关于管理证书的更多信息,请参见使用 kubeadm 进行证书管理 。
单根 CA 你可以创建由管理员控制的单根 CA。该根 CA 可以创建多个中间 CA,并将所有进一步的创建委托给 Kubernetes。
需要这些 CA:
路径 默认 CN 描述 ca.crt,key kubernetes-ca Kubernetes 通用 CA etcd/ca.crt,key etcd-ca 与 etcd 相关的所有功能 front-proxy-ca.crt,key kubernetes-front-proxy-ca 用于前端代理
上面的 CA 之外,还需要获取用于服务账户管理的密钥对,也就是 sa.key
和 sa.pub
。
下面的例子说明了上表中所示的 CA 密钥和证书文件。
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-ca.key
所有的证书 如果你不想将 CA 的私钥拷贝至你的集群中,你也可以自己生成全部的证书。
需要这些证书:
默认 CN 父级 CA O (位于 Subject 中) 类型 主机 (SAN) kube-etcd etcd-ca server, client <hostname>
, <Host_IP>
, localhost
, 127.0.0.1
kube-etcd-peer etcd-ca server, client <hostname>
, <Host_IP>
, localhost
, 127.0.0.1
kube-etcd-healthcheck-client etcd-ca client kube-apiserver-etcd-client etcd-ca system:masters client kube-apiserver kubernetes-ca server <hostname>
, <Host_IP>
, <advertise_IP>
, [1]
kube-apiserver-kubelet-client kubernetes-ca system:masters client front-proxy-client kubernetes-front-proxy-ca client
[1]: 用来连接到集群的不同 IP 或 DNS 名
(就像 kubeadm 为负载均衡所使用的固定
IP 或 DNS 名:kubernetes
、kubernetes.default
、kubernetes.default.svc
、
kubernetes.default.svc.cluster
、kubernetes.default.svc.cluster.local
)。
其中,kind
对应一种或多种类型的 x509 密钥用途 :
kind 密钥用途 server 数字签名、密钥加密、服务端认证 client 数字签名、密钥加密、客户端认证
说明: 上面列出的 Hosts/SAN 是推荐的配置方式;如果需要特殊安装,则可以在所有服务器证书上添加其他 SAN。
说明: 对于 kubeadm 用户:
不使用私钥,将证书复制到集群 CA 的方案,在 kubeadm 文档中将这种方案称为外部 CA。 如果将以上列表与 kubeadm 生成的 PKI 进行比较,你会注意到,如果使用外部 etcd,则不会生成
kube-etcd
、kube-etcd-peer
和 kube-etcd-healthcheck-client
证书。 证书路径 证书应放置在建议的路径中(以便 kubeadm
使用)。无论使用什么位置,都应使用给定的参数指定路径。
默认 CN 建议的密钥路径 建议的证书路径 命令 密钥参数 证书参数 etcd-ca etcd/ca.key etcd/ca.crt kube-apiserver --etcd-cafile kube-apiserver-etcd-client apiserver-etcd-client.key apiserver-etcd-client.crt kube-apiserver --etcd-keyfile --etcd-certfile kubernetes-ca ca.key ca.crt kube-apiserver --client-ca-file kubernetes-ca ca.key ca.crt kube-controller-manager --cluster-signing-key-file --client-ca-file, --root-ca-file, --cluster-signing-cert-file kube-apiserver apiserver.key apiserver.crt kube-apiserver --tls-private-key-file --tls-cert-file kube-apiserver-kubelet-client apiserver-kubelet-client.key apiserver-kubelet-client.crt kube-apiserver --kubelet-client-key --kubelet-client-certificate front-proxy-ca front-proxy-ca.key front-proxy-ca.crt kube-apiserver --requestheader-client-ca-file front-proxy-ca front-proxy-ca.key front-proxy-ca.crt kube-controller-manager --requestheader-client-ca-file front-proxy-client front-proxy-client.key front-proxy-client.crt kube-apiserver --proxy-client-key-file --proxy-client-cert-file etcd-ca etcd/ca.key etcd/ca.crt etcd --trusted-ca-file, --peer-trusted-ca-file kube-etcd etcd/server.key etcd/server.crt etcd --key-file --cert-file kube-etcd-peer etcd/peer.key etcd/peer.crt etcd --peer-key-file --peer-cert-file etcd-ca etcd/ca.crt etcdctl --cacert kube-etcd-healthcheck-client etcd/healthcheck-client.key etcd/healthcheck-client.crt etcdctl --key --cert
注意事项同样适用于服务帐户密钥对:
私钥路径 公钥路径 命令 参数 sa.key kube-controller-manager --service-account-private-key-file sa.pub kube-apiserver --service-account-key-file
下面的例子展示了自行生成所有密钥和证书时所需要提供的文件路径。
这些路径基于前面的表格 。
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/apiserver-etcd-client.key
/etc/kubernetes/pki/apiserver-etcd-client.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/apiserver.key
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver-kubelet-client.key
/etc/kubernetes/pki/apiserver-kubelet-client.crt
/etc/kubernetes/pki/front-proxy-ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-client.key
/etc/kubernetes/pki/front-proxy-client.crt
/etc/kubernetes/pki/etcd/server.key
/etc/kubernetes/pki/etcd/server.crt
/etc/kubernetes/pki/etcd/peer.key
/etc/kubernetes/pki/etcd/peer.crt
/etc/kubernetes/pki/etcd/healthcheck-client.key
/etc/kubernetes/pki/etcd/healthcheck-client.crt
/etc/kubernetes/pki/sa.key
/etc/kubernetes/pki/sa.pub
你必须手动配置以下管理员帐户和服务帐户:
文件名 凭据名称 默认 CN O (位于 Subject 中) admin.conf default-admin kubernetes-admin system:masters kubelet.conf default-auth system:node:<nodeName>
(参阅注释) system:nodes controller-manager.conf default-controller-manager system:kube-controller-manager scheduler.conf default-scheduler system:kube-scheduler
说明: kubelet.conf
中 <nodeName>
的值 必须 与 kubelet 向 apiserver 注册时提供的节点名称的值完全匹配。
有关更多详细信息,请阅读节点授权 。
对于每个配置,请都使用给定的 CN 和 O 生成 x509 证书/密钥偶对。
为每个配置运行下面的 kubectl
命令:
KUBECONFIG = <filename> kubectl config set-cluster default-cluster --server= https://<host ip>:6443 --certificate-authority <path-to-kubernetes-ca> --embed-certs
KUBECONFIG = <filename> kubectl config set-credentials <credential-name> --client-key <path-to-key>.pem --client-certificate <path-to-cert>.pem --embed-certs
KUBECONFIG = <filename> kubectl config set-context default-system --cluster default-cluster --user <credential-name>
KUBECONFIG = <filename> kubectl config use-context default-system
这些文件用途如下:
文件名 命令 说明 admin.conf kubectl 配置集群的管理员 kubelet.conf kubelet 集群中的每个节点都需要一份 controller-manager.conf kube-controller-manager 必需添加到 manifests/kube-controller-manager.yaml
清单中 scheduler.conf kube-scheduler 必需添加到 manifests/kube-scheduler.yaml
清单中
下面是前表中所列文件的完整路径。
/etc/kubernetes/admin.conf
/etc/kubernetes/kubelet.conf
/etc/kubernetes/controller-manager.conf
/etc/kubernetes/scheduler.conf
3 - 概念 概念部分帮助你了解 Kubernetes 系统的各个部分以及
Kubernetes 用来表示集群 的抽象概念,
并帮助你更深入地理解 Kubernetes 是如何工作的。
3.1 - 概述 Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,方便进行声明式配置和自动化。Kubernetes 拥有一个庞大且快速增长的生态系统,其服务、支持和工具的使用范围广泛。
此页面是 Kubernetes 的概述。
Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。
Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。
Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。
Google 在 2014 年开源了 Kubernetes 项目。
Kubernetes 建立在 Google 大规模运行生产工作负载十几年经验 的基础上,
结合了社区中最优秀的想法和实践。
时光回溯 让我们回顾一下为何 Kubernetes 能够裨益四方。
传统部署时代:
早期,各个组织是在物理服务器上运行应用程序。
由于无法限制在物理服务器中运行的应用程序资源使用,因此会导致资源分配问题。
例如,如果在同一台物理服务器上运行多个应用程序,
则可能会出现一个应用程序占用大部分资源的情况,而导致其他应用程序的性能下降。
一种解决方案是将每个应用程序都运行在不同的物理服务器上,
但是当某个应用程式资源利用率不高时,剩余资源无法被分配给其他应用程式,
而且维护许多物理服务器的成本很高。
虚拟化部署时代:
因此,虚拟化技术被引入了。虚拟化技术允许你在单个物理服务器的 CPU 上运行多台虚拟机(VM)。
虚拟化能使应用程序在不同 VM 之间被彼此隔离,且能提供一定程度的安全性,
因为一个应用程序的信息不能被另一应用程序随意访问。
虚拟化技术能够更好地利用物理服务器的资源,并且因为可轻松地添加或更新应用程序,
而因此可以具有更高的可扩缩性,以及降低硬件成本等等的好处。
通过虚拟化,你可以将一组物理资源呈现为可丢弃的虚拟机集群。
每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。
容器部署时代:
容器类似于 VM,但是更宽松的隔离特性,使容器之间可以共享操作系统(OS)。
因此,容器比起 VM 被认为是更轻量级的。且与 VM 类似,每个容器都具有自己的文件系统、CPU、内存、进程空间等。
由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。
容器因具有许多优势而变得流行起来,例如:
敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。 持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性),
提供可靠且频繁的容器镜像构建和部署。 关注开发与运维的分离:在构建、发布时创建应用程序容器镜像,而不是在部署时,
从而将应用程序与基础架构分离。 可观察性:不仅可以显示 OS 级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。 跨开发、测试和生产的环境一致性:在笔记本计算机上也可以和在云中运行一样的应用程序。 跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、
Google Kubernetes Engine 和其他任何地方运行。 以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。 松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分,
并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。 资源隔离:可预测的应用程序性能。 资源利用:高效率和高密度。 为什么需要 Kubernetes,它能做什么? 容器是打包和运行应用程序的好方式。在生产环境中,
你需要管理运行着应用程序的容器,并确保服务不会下线。
例如,如果一个容器发生故障,则你需要启动另一个容器。
如果此行为交由给系统处理,是不是会更容易一些?
这就是 Kubernetes 要来做的事情!
Kubernetes 为你提供了一个可弹性运行分布式系统的框架。
Kubernetes 会满足你的扩展要求、故障转移你的应用、提供部署模式等。
例如,Kubernetes 可以轻松管理系统的 Canary 部署。
Kubernetes 为你提供:
Kubernetes 不是什么 Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。
由于 Kubernetes 是在容器级别运行,而非在硬件级别,它提供了 PaaS 产品共有的一些普遍适用的功能,
例如部署、扩展、负载均衡,允许用户集成他们的日志记录、监控和警报方案。
但是,Kubernetes 不是单体式(monolithic)系统,那些默认解决方案都是可选、可插拔的。
Kubernetes 为构建开发人员平台提供了基础,但是在重要的地方保留了用户选择权,能有更高的灵活性。
Kubernetes:
不限制支持的应用程序类型。
Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。
如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。 不部署源代码,也不构建你的应用程序。
持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。 不提供应用程序级别的服务作为内置服务,例如中间件(例如消息中间件)、
数据处理框架(例如 Spark)、数据库(例如 MySQL)、缓存、集群存储系统
(例如 Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在
Kubernetes 上的应用程序通过可移植机制
(例如开放服务代理 )来访问。 不是日志记录、监视或警报的解决方案。
它集成了一些功能作为概念证明,并提供了收集和导出指标的机制。 不提供也不要求配置用的语言、系统(例如 jsonnet),它提供了声明性 API,
该声明性 API 可以由任意形式的声明性规范所构成。 不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。 此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。
编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。
而 Kubernetes 包含了一组独立可组合的控制过程,可以连续地将当前状态驱动到所提供的预期状态。
你不需要在乎如何从 A 移动到 C,也不需要集中控制,这使得系统更易于使用
且功能更强大、系统更健壮,更为弹性和可扩展。 接下来 3.1.1 - Kubernetes 组件 Kubernetes 集群由控制平面的组件和一组称为节点的机器组成。
当你部署完 Kubernetes,便拥有了一个完整的集群。
一组工作机器,称为 节点 ,
会运行容器化应用程序。每个集群至少有一个工作节点。
工作节点会托管 Pod
,而 Pod 就是作为应用负载的组件。
控制平面 管理集群中的工作节点和 Pod。
在生产环境中,控制平面通常跨多台计算机运行,
一个集群通常运行多个节点,提供容错性和高可用性。
本文档概述了一个正常运行的 Kubernetes 集群所需的各种组件。
Kubernetes 集群的组件
控制平面组件(Control Plane Components) 控制平面组件会为集群做出全局决策,比如资源的调度。
以及检测和响应集群事件,例如当不满足部署的 replicas
字段时,
要启动新的 pod )。
控制平面组件可以在集群中的任何节点上运行。
然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件,
并且不会在此计算机上运行用户容器。
请参阅使用 kubeadm 构建高可用性集群
中关于跨多机器控制平面设置的示例。
kube-apiserver API 服务器是 Kubernetes 控制平面 的组件,
该组件负责公开了 Kubernetes API,负责处理接受请求的工作。
API 服务器是 Kubernetes 控制平面的前端。
Kubernetes API 服务器的主要实现是 kube-apiserver 。
kube-apiserver
设计上考虑了水平扩缩,也就是说,它可通过部署多个实例来进行扩缩。
你可以运行 kube-apiserver
的多个实例,并在这些实例之间平衡流量。
etcd 一致且高度可用的键值存储,用作 Kubernetes 的所有集群数据的后台数据库。
如果你的 Kubernetes 集群使用 etcd 作为其后台数据库,
请确保你针对这些数据有一份
备份 计划。
你可以在官方文档 中找到有关 etcd 的深入知识。
kube-scheduler kube-scheduler
是控制平面 的组件,
负责监视新创建的、未指定运行节点(node) 的 Pods ,
并选择节点来让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 及 Pods 集合的资源需求、软硬件及策略约束、
亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。
kube-controller-manager kube-controller-manager
是控制平面 的组件,
负责运行控制器 进程。
从逻辑上讲,
每个控制器 都是一个单独的进程,
但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。
这些控制器包括:
节点控制器(Node Controller):负责在节点出现故障时进行通知和响应 任务控制器(Job Controller):监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成 端点分片控制器(EndpointSlice controller):填充端点分片(EndpointSlice)对象(以提供 Service 和 Pod 之间的链接)。 服务账号控制器(ServiceAccount controller):为新的命名空间创建默认的服务账号(ServiceAccount)。 cloud-controller-manager 一个 Kubernetes
控制平面 组件,
嵌入了特定于云平台的控制逻辑。
云控制器管理器(Cloud Controller Manager)允许你将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
cloud-controller-manager
仅运行特定于云平台的控制器。
因此如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境,
所部署的集群不需要有云控制器管理器。
与 kube-controller-manager
类似,cloud-controller-manager
将若干逻辑上独立的控制回路组合到同一个可执行文件中,
供你以同一进程的方式运行。
你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。
下面的控制器都包含对云平台驱动的依赖:
节点控制器(Node Controller):用于在节点终止响应后检查云提供商以确定节点是否已被删除 路由控制器(Route Controller):用于在底层云基础架构中设置路由 服务控制器(Service Controller):用于创建、更新和删除云提供商负载均衡器 Node 组件 节点组件会在每个节点上运行,负责维护运行的 Pod 并提供 Kubernetes 运行环境。
kubelet kubelet
会在集群中每个节点(node) 上运行。
它保证容器(containers) 都运行在
Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpecs,
确保这些 PodSpecs 中描述的容器处于运行状态且健康。
kubelet 不会管理不是由 Kubernetes 创建的容器。
kube-proxy kube-proxy
是集群中每个节点(node) 所上运行的网络代理,
实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的一些网络规则,
这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。
如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。
否则,kube-proxy 仅做流量转发。
容器运行时(Container Runtime) 容器运行环境是负责运行容器的软件。
Kubernetes 支持许多容器运行环境,例如
containerd 、
CRI-O
以及 Kubernetes CRI (容器运行环境接口)
的其他任何实现。
插件(Addons) 插件使用 Kubernetes 资源(DaemonSet 、
Deployment 等)实现集群功能。
因为这些插件提供集群级别的功能,插件中命名空间域的资源属于 kube-system
命名空间。
下面描述众多插件中的几种。有关可用插件的完整列表,请参见
插件(Addons) 。
DNS 尽管其他插件都并非严格意义上的必需组件,但几乎所有 Kubernetes
集群都应该有集群 DNS ,
因为很多示例都需要 DNS 服务。
集群 DNS 是一个 DNS 服务器,和环境中的其他 DNS 服务器一起工作,它为 Kubernetes 服务提供 DNS 记录。
Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。
Web 界面(仪表盘) Dashboard
是 Kubernetes 集群的通用的、基于 Web 的用户界面。
它使用户可以管理集群中运行的应用程序以及集群本身,
并进行故障排除。
容器资源监控 容器资源监控
将关于容器的一些常见的时间序列度量值保存到一个集中的数据库中,
并提供浏览这些数据的界面。
集群层面日志 集群层面日志 机制负责将容器的日志数据保存到一个集中的日志存储中,
这种集中日志存储提供搜索和浏览接口。
接下来 3.1.2 - Kubernetes API Kubernetes API 使你可以查询和操纵 Kubernetes 中对象的状态。 Kubernetes 控制平面的核心是 API 服务器和它暴露的 HTTP API。 用户、集群的不同部分以及外部组件都通过 API 服务器相互通信。
Kubernetes 控制面 的核心是
API 服务器 。
API 服务器负责提供 HTTP API,以供用户、集群中的不同部分和集群外部组件相互通信。
Kubernetes API 使你可以查询和操纵 Kubernetes API
中对象(例如:Pod、Namespace、ConfigMap 和 Event)的状态。
大部分操作都可以通过 kubectl 命令行接口或类似
kubeadm 这类命令行工具来执行,
这些工具在背后也是调用 API。不过,你也可以使用 REST 调用来访问这些 API。
如果你正在编写程序来访问 Kubernetes API,
可以考虑使用客户端库 之一。
OpenAPI 规范 完整的 API 细节是用 OpenAPI 来表述的。
OpenAPI V2 Kubernetes API 服务器通过 /openapi/v2
端点提供聚合的 OpenAPI v2 规范。
你可以按照下表所给的请求头部,指定响应的格式:
OpenAPI v2 查询请求的合法头部值 头部 可选值 说明 Accept-Encoding
gzip
不指定此头部也是可以的 Accept
application/com.github.proto-openapi.spec.v2@v1.0+protobuf
主要用于集群内部 application/json
默认值 *
提供 application/json
Kubernetes 为 API 实现了一种基于 Protobuf 的序列化格式,主要用于集群内部通信。
关于此格式的详细信息,可参考
Kubernetes Protobuf 序列化 设计提案。
每种模式对应的接口描述语言(IDL)位于定义 API 对象的 Go 包中。
OpenAPI V3 特性状态: Kubernetes v1.24 [beta]
Kubernetes v1.25 提供将其 API 以 OpenAPI v3 形式发布的 beta 支持;
这一功能特性处于 beta 状态,默认被开启。
你可以通过为 kube-apiserver 组件关闭 OpenAPIV3
特性门控 来禁用此 beta 特性。
发现端点 /openapi/v3
被提供用来查看可用的所有组、版本列表。
此列表仅返回 JSON。这些组、版本以下面的格式提供:
{
"paths": {
...,
"api/v1": {
"serverRelativeURL": "/openapi/v3/api/v1?hash=CC0E9BFD992D8C59AEC98A1E2336F899E8318D3CF4C68944C3DEC640AF5AB52D864AC50DAA8D145B3494F75FA3CFF939FCBDDA431DAD3CA79738B297795818CF"
},
"apis/admissionregistration.k8s.io/v1": {
"serverRelativeURL": "/openapi/v3/apis/admissionregistration.k8s.io/v1?hash=E19CC93A116982CE5422FC42B590A8AFAD92CDE9AE4D59B5CAAD568F083AD07946E6CB5817531680BCE6E215C16973CD39003B0425F3477CFD854E89A9DB6597"
},
....
}
}
为了改进客户端缓存,相对的 URL 会指向不可变的 OpenAPI 描述。
为了此目的,API 服务器也会设置正确的 HTTP 缓存标头
(Expires
为未来 1 年,和 Cache-Control
为 immutable
)。
当一个过时的 URL 被使用时,API 服务器会返回一个指向最新 URL 的重定向。
Kubernetes API 服务器会在端点 /openapi/v3/apis/<group>/<version>?hash=<hash>
发布一个 Kubernetes 组版本的 OpenAPI v3 规范。
请参阅下表了解可接受的请求头部。
OpenAPI v3 查询的合法请求头部值 头部 可选值 说明 Accept-Encoding
gzip
不提供此头部也是可接受的 Accept
application/com.github.proto-openapi.spec.v3@v1.0+protobuf
主要用于集群内部使用 application/json
默认 *
以 application/json
形式返回
持久化 Kubernetes 通过将序列化状态的对象写入到 etcd 中完成存储操作。
API 组和版本控制 为了更容易消除字段或重组资源的呈现方式,Kubernetes 支持多个 API 版本,每个版本位于不同的 API 路径,
例如 /api/v1
或 /apis/rbac.authorization.k8s.io/v1alpha1
。
版本控制是在 API 级别而不是在资源或字段级别完成的,以确保 API 呈现出清晰、一致的系统资源和行为视图,
并能够控制对生命结束和/或实验性 API 的访问。
为了更容易演进和扩展其 API,Kubernetes 实现了 API 组 ,
这些 API 组可以被启用或禁用 。
API 资源通过其 API 组、资源类型、名字空间(用于名字空间作用域的资源)和名称来区分。
API 服务器透明地处理 API 版本之间的转换:所有不同的版本实际上都是相同持久化数据的呈现。
API 服务器可以通过多个 API 版本提供相同的底层数据。
例如,假设针对相同的资源有两个 API 版本:v1
和 v1beta1
。
如果你最初使用其 API 的 v1beta1
版本创建了一个对象,
你稍后可以使用 v1beta1
或 v1
API 版本来读取、更新或删除该对象,
直到 v1beta1
版本被废弃和移除为止。此后,你可以使用 v1
API 继续访问和修改该对象。
API 变更 任何成功的系统都要随着新的使用案例的出现和现有案例的变化来成长和变化。
为此,Kubernetes 已设计了 Kubernetes API 来持续变更和成长。
Kubernetes 项目的目标是 不要 给现有客户端带来兼容性问题,并在一定的时期内维持这种兼容性,
以便其他项目有机会作出适应性变更。
一般而言,新的 API 资源和新的资源字段可以被频繁地添加进来。
删除资源或者字段则要遵从
API 废弃策略 。
Kubernetes 对维护达到正式发布(GA)阶段的官方 API 的兼容性有着很强的承诺,通常这一 API 版本为 v1
。
此外,Kubernetes 保持与 Kubernetes 官方 API 的 Beta API 版本持久化数据的兼容性,
并确保在该功能特性已进入稳定期时数据可以通过 GA API 版本进行转换和访问。
如果你采用一个 Beta API 版本,一旦该 API 进阶,你将需要转换到后续的 Beta 或稳定的 API 版本。
执行此操作的最佳时间是 Beta API 处于弃用期,因为此时可以通过两个 API 版本同时访问那些对象。
一旦 Beta API 结束其弃用期并且不再提供服务,则必须使用替换的 API 版本。
说明: 尽管 Kubernetes 也努力为 Alpha API 版本维护兼容性,在有些场合兼容性是无法做到的。
如果你使用了任何 Alpha API 版本,需要在升级集群时查看 Kubernetes 发布说明,
如果 API 确实以不兼容的方式发生变更,则需要在升级之前删除所有现有的 Alpha 对象。
关于 API 版本分级的定义细节,请参阅
API 版本参考 页面。
API 扩展 有两种途径来扩展 Kubernetes API:
你可以使用自定义资源 来以声明式方式定义
API 服务器如何提供你所选择的资源 API。 你也可以选择实现自己的聚合层 来扩展
Kubernetes API。 接下来 3.1.3 - 使用 Kubernetes 对象 Kubernetes 对象是 Kubernetes 系统中的持久性实体。Kubernetes 使用这些实体表示你的集群状态。 了解 Kubernetes 对象模型以及如何使用这些对象。
3.1.3.1 - 理解 Kubernetes 对象 本页说明了在 Kubernetes API 中是如何表示 Kubernetes 对象的,
以及使用 .yaml
格式的文件表示 Kubernetes 对象。
理解 Kubernetes 对象 在 Kubernetes 系统中,Kubernetes 对象 是持久化的实体。
Kubernetes 使用这些实体去表示整个集群的状态。
比较特别地是,它们描述了如下信息:
哪些容器化应用正在运行(以及在哪些节点上运行) 可以被应用使用的资源 关于应用运行时表现的策略,比如重启策略、升级策略以及容错策略 Kubernetes 对象是“目标性记录” —— 一旦创建该对象,Kubernetes 系统将不断工作以确保该对象存在。
通过创建对象,你就是在告知 Kubernetes 系统,你想要的集群工作负载状态看起来应是什么样子的,
这就是 Kubernetes 集群所谓的 期望状态(Desired State) 。
操作 Kubernetes 对象 —— 无论是创建、修改或者删除 —— 需要使用
Kubernetes API 。
比如,当使用 kubectl
命令行接口(CLI)时,CLI 会调用必要的 Kubernetes API;
也可以在程序中使用客户端库 ,
来直接调用 Kubernetes API。
对象规约(Spec)与状态(Status) 几乎每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置:
对象 spec
(规约) 和 对象 status
(状态) 。
对于具有 spec
的对象,你必须在创建对象时设置其内容,描述你希望对象所具有的特征:
期望状态(Desired State) 。
status
描述了对象的当前状态(Current State) ,它是由 Kubernetes 系统和组件设置并更新的。
在任何时刻,Kubernetes 控制平面
都一直都在积极地管理着对象的实际状态,以使之达成期望状态。
例如,Kubernetes 中的 Deployment 对象能够表示运行在集群中的应用。
当创建 Deployment 时,可能会去设置 Deployment 的 spec
,以指定该应用要有 3 个副本运行。
Kubernetes 系统读取 Deployment 的 spec
,
并启动我们所期望的应用的 3 个实例 —— 更新状态以与规约相匹配。
如果这些实例中有的失败了(一种状态变更),Kubernetes 系统会通过执行修正操作来响应
spec
和状态间的不一致 —— 意味着它会启动一个新的实例来替换。
关于对象 spec、status 和 metadata 的更多信息,可参阅
Kubernetes API 约定 。
描述 Kubernetes 对象 创建 Kubernetes 对象时,必须提供对象的 spec
,用来描述该对象的期望状态,
以及关于对象的一些基本信息(例如名称)。
当使用 Kubernetes API 创建对象时(直接创建,或经由 kubectl
),
API 请求必须在请求本体中包含 JSON 格式的信息。
大多数情况下,你需要提供 .yaml
文件为 kubectl 提供这些信息 。
kubectl
在发起 API 请求时,将这些信息转换成 JSON 格式。
这里有一个 .yaml
示例文件,展示了 Kubernetes Deployment 的必需字段和对象 spec
:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
相较于上面使用 .yaml
文件来创建 Deployment,另一种类似的方式是使用 kubectl
命令行接口(CLI)中的
kubectl apply
命令,
将 .yaml
文件作为参数。下面是一个示例:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
输出类似下面这样:
deployment.apps/nginx-deployment created
必需字段 在想要创建的 Kubernetes 对象所对应的 .yaml
文件中,需要配置的字段如下:
apiVersion
- 创建该对象所使用的 Kubernetes API 的版本kind
- 想要创建的对象的类别metadata
- 帮助唯一标识对象的一些数据,包括一个 name
字符串、UID
和可选的 namespace
spec
- 你所期望的该对象的状态对每个 Kubernetes 对象而言,其 spec
之精确格式都是不同的,包含了特定于该对象的嵌套字段。
我们能在 Kubernetes API 参考
找到我们想要在 Kubernetes 上创建的任何对象的规约格式。
例如,参阅 Pod API 参考文档中
spec
字段 。
对于每个 Pod,其 .spec
字段设置了 Pod 及其期望状态(例如 Pod 中每个容器的容器镜像名称)。
另一个对象规约的例子是 StatefulSet API 中的
spec
字段 。
对于 StatefulSet 而言,其 .spec
字段设置了 StatefulSet 及其期望状态。
在 StatefulSet 的 .spec
内,有一个为 Pod 对象提供的模板 。
该模板描述了 StatefulSet 控制器为了满足 StatefulSet 规约而要创建的 Pod。
不同类型的对象可以由不同的 .status
信息。API 参考页面给出了 .status
字段的详细结构,
以及针对不同类型 API 对象的具体内容。
接下来 3.1.3.2 - Kubernetes 对象管理 kubectl
命令行工具支持多种不同的方式来创建和管理 Kubernetes 对象。
本文档概述了不同的方法。
阅读 Kubectl book 来了解 kubectl
管理对象的详细信息。
管理技巧 警告: 应该只使用一种技术来管理 Kubernetes 对象。混合和匹配技术作用在同一对象上将导致未定义行为。
管理技术 作用于 建议的环境 支持的写者 学习难度 指令式命令 活跃对象 开发项目 1+ 最低 指令式对象配置 单个文件 生产项目 1 中等 声明式对象配置 文件目录 生产项目 1+ 最高
指令式命令 使用指令式命令时,用户可以在集群中的活动对象上进行操作。用户将操作传给
kubectl
命令作为参数或标志。
这是开始或者在集群中运行一次性任务的推荐方法。因为这个技术直接在活跃对象
上操作,所以它不提供以前配置的历史记录。
例子 通过创建 Deployment 对象来运行 nginx 容器的实例:
kubectl create deployment nginx --image nginx
权衡 与对象配置相比的优点:
命令简单,易学且易于记忆。 命令仅需一步即可对集群进行更改。 与对象配置相比的缺点:
命令不与变更审查流程集成。 命令不提供与更改关联的审核跟踪。 除了实时内容外,命令不提供记录源。 命令不提供用于创建新对象的模板。 指令式对象配置 在指令式对象配置中,kubectl 命令指定操作(创建,替换等),可选标志和
至少一个文件名。指定的文件必须包含 YAML 或 JSON 格式的对象的完整定义。
有关对象定义的详细信息,请查看
API 参考 。
警告: replace
指令式命令将现有规范替换为新提供的规范,并放弃对配置文件中
缺少的对象的所有更改。此方法不应与对象规约被独立于配置文件进行更新的
资源类型一起使用。比如类型为 LoadBalancer
的服务,它的 externalIPs
字段就是独立于集群配置进行更新。
例子 创建配置文件中定义的对象:
kubectl create -f nginx.yaml
删除两个配置文件中定义的对象:
kubectl delete -f nginx.yaml -f redis.yaml
通过覆盖活动配置来更新配置文件中定义的对象:
kubectl replace -f nginx.yaml
权衡 与指令式命令相比的优点:
对象配置可以存储在源控制系统中,比如 Git。 对象配置可以与流程集成,例如在推送和审计之前检查更新。 对象配置提供了用于创建新对象的模板。 与指令式命令相比的缺点:
对象配置需要对对象架构有基本的了解。 对象配置需要额外的步骤来编写 YAML 文件。 与声明式对象配置相比的优点:
指令式对象配置行为更加简单易懂。 从 Kubernetes 1.5 版本开始,指令对象配置更加成熟。 与声明式对象配置相比的缺点:
指令式对象配置更适合文件,而非目录。 对活动对象的更新必须反映在配置文件中,否则会在下一次替换时丢失。 声明式对象配置 使用声明式对象配置时,用户对本地存储的对象配置文件进行操作,但是用户
未定义要对该文件执行的操作。
kubectl
会自动检测每个文件的创建、更新和删除操作。
这使得配置可以在目录上工作,根据目录中配置文件对不同的对象执行不同的操作。
说明: 声明式对象配置保留其他编写者所做的修改,即使这些更改并未合并到对象配置文件中。
可以通过使用 patch
API 操作仅写入观察到的差异,而不是使用 replace
API
操作来替换整个对象配置来实现。
例子 处理 configs
目录中的所有对象配置文件,创建并更新活跃对象。
可以首先使用 diff
子命令查看将要进行的更改,然后在进行应用:
kubectl diff -f configs/
kubectl apply -f configs/
递归处理目录:
kubectl diff -R -f configs/
kubectl apply -R -f configs/
权衡 与指令式对象配置相比的优点:
对活动对象所做的更改即使未合并到配置文件中,也会被保留下来。 声明性对象配置更好地支持对目录进行操作并自动检测每个文件的操作类型(创建,修补,删除)。 与指令式对象配置相比的缺点:
声明式对象配置难于调试并且出现异常时结果难以理解。 使用 diff 产生的部分更新会创建复杂的合并和补丁操作。 接下来 3.1.3.3 - 对象名称和 ID 集群中的每一个对象都有一个名称 来标识在同类资源中的唯一性。
每个 Kubernetes 对象也有一个 UID 来标识在整个集群中的唯一性。
比如,在同一个名字空间
中有一个名为 myapp-1234
的 Pod,但是可以命名一个 Pod 和一个 Deployment 同为 myapp-1234
。
对于用户提供的非唯一性的属性,Kubernetes 提供了
标签(Labels) 和
注解(Annotation) 机制。
名称 客户端提供的字符串,引用资源 URL 中的对象,如/api/v1/pods/some name
。
某一时刻,只能有一个给定类型的对象具有给定的名称。但是,如果删除该对象,则可以创建同名的新对象。
说明: 当对象所代表的是一个物理实体(例如代表一台物理主机的 Node)时,
如果在 Node 对象未被删除并重建的条件下,重新创建了同名的物理主机,
则 Kubernetes 会将新的主机看作是老的主机,这可能会带来某种不一致性。
以下是比较常见的四种资源命名约束。
DNS 子域名 很多资源类型需要可以用作 DNS 子域名的名称。
DNS 子域名的定义可参见 RFC 1123 。
这一要求意味着名称必须满足如下规则:
不能超过 253 个字符 只能包含小写字母、数字,以及 '-' 和 '.' 必须以字母数字开头 必须以字母数字结尾 RFC 1123 标签名 某些资源类型需要其名称遵循 RFC 1123
所定义的 DNS 标签标准。也就是命名必须满足如下规则:
最多 63 个字符 只能包含小写字母、数字,以及 '-' 必须以字母数字开头 必须以字母数字结尾 RFC 1035 标签名 某些资源类型需要其名称遵循 RFC 1035
所定义的 DNS 标签标准。也就是命名必须满足如下规则:
最多 63 个字符 只能包含小写字母、数字,以及 '-' 必须以字母开头 必须以字母数字结尾 路径分段名称 某些资源类型要求名称能被安全地用作路径中的片段。
换句话说,其名称不能是 .
、..
,也不可以包含 /
或 %
这些字符。
下面是一个名为 nginx-demo
的 Pod 的配置清单:
apiVersion : v1
kind : Pod
metadata :
name : nginx-demo
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
UID Kubernetes 系统生成的字符串,唯一标识对象。
在 Kubernetes 集群的整个生命周期中创建的每个对象都有一个不同的 UID,它旨在区分类似实体的历史事件。
Kubernetes UID 是全局唯一标识符(也叫 UUID)。
UUID 是标准化的,见 ISO/IEC 9834-8 和 ITU-T X.667。
接下来 3.1.3.4 - 标签和选择算符 标签(Labels) 是附加到 Kubernetes 对象(比如 Pod)上的键值对。
标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。
标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。
每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。
"metadata" : {
"labels" : {
"key1" : "value1" ,
"key2" : "value2"
}
}
标签能够支持高效的查询和监听操作,对于用户界面和命令行是很理想的。
应使用注解 记录非识别信息。
动机 标签使用户能够以松散耦合的方式将他们自己的组织结构映射到系统对象,而无需客户端存储这些映射。
服务部署和批处理流水线通常是多维实体(例如,多个分区或部署、多个发行序列、多个层,每层多个微服务)。
管理通常需要交叉操作,这打破了严格的层次表示的封装,特别是由基础设施而不是用户确定的严格的层次结构。
示例标签:
"release" : "stable"
, "release" : "canary"
"environment" : "dev"
, "environment" : "qa"
, "environment" : "production"
"tier" : "frontend"
, "tier" : "backend"
, "tier" : "cache"
"partition" : "customerA"
, "partition" : "customerB"
"track" : "daily"
, "track" : "weekly"
有一些常用标签 的例子;你可以任意制定自己的约定。
请记住,标签的 Key 对于给定对象必须是唯一的。
语法和字符集 标签 是键值对。有效的标签键有两个段:可选的前缀和名称,用斜杠(/
)分隔。
名称段是必需的,必须小于等于 63 个字符,以字母数字字符([a-z0-9A-Z]
)开头和结尾,
带有破折号(-
),下划线(_
),点( .
)和之间的字母数字。
前缀是可选的。如果指定,前缀必须是 DNS 子域:由点(.
)分隔的一系列 DNS 标签,总共不超过 253 个字符,
后跟斜杠(/
)。
如果省略前缀,则假定标签键对用户是私有的。
向最终用户对象添加标签的自动系统组件(例如 kube-scheduler
、kube-controller-manager
、
kube-apiserver
、kubectl
或其他第三方自动化工具)必须指定前缀。
kubernetes.io/
和 k8s.io/
前缀是为 Kubernetes 核心组件保留的 。
有效标签值:
必须为 63 个字符或更少(可以为空) 除非标签值为空,必须以字母数字字符([a-z0-9A-Z]
)开头和结尾 包含破折号(-
)、下划线(_
)、点(.
)和字母或数字 例如,这是一个有 environment: production
和 app: nginx
标签的 Pod 配置文件:
apiVersion : v1
kind : Pod
metadata :
name : label-demo
labels :
environment : production
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
标签选择算符 与名称和 UID 不同,
标签不支持唯一性。通常,我们希望许多对象携带相同的标签。
通过标签选择算符 ,客户端/用户可以识别一组对象。标签选择算符是 Kubernetes 中的核心分组原语。
API 目前支持两种类型的选择算符:基于等值的 和基于集合的 。
标签选择算符可以由逗号分隔的多个需求 组成。
在多个需求的情况下,必须满足所有要求,因此逗号分隔符充当逻辑与 (&&
)运算符。
空标签选择算符或者未指定的选择算符的语义取决于上下文,
支持使用选择算符的 API 类别应该将算符的合法性和含义用文档记录下来。
说明: 对于某些 API 类别(例如 ReplicaSet)而言,两个实例的标签选择算符不得在命名空间内重叠,
否则它们的控制器将互相冲突,无法确定应该存在的副本个数。
注意: 对于基于等值的和基于集合的条件而言,不存在逻辑或(||
)操作符。
你要确保你的过滤语句按合适的方式组织。
基于等值的 需求基于等值 或基于不等值 的需求允许按标签键和值进行过滤。
匹配对象必须满足所有指定的标签约束,尽管它们也可能具有其他标签。
可接受的运算符有 =
、==
和 !=
三种。
前两个表示相等 (并且是同义词),而后者表示不相等 。例如:
environment = production
tier != frontend
前者选择所有资源,其键名等于 environment
,值等于 production
。
后者选择所有资源,其键名等于 tier
,值不同于 frontend
,所有资源都没有带有 tier
键的标签。
可以使用逗号运算符来过滤 production
环境中的非 frontend
层资源:environment=production,tier!=frontend
。
基于等值的标签要求的一种使用场景是 Pod 要指定节点选择标准。
例如,下面的示例 Pod 选择带有标签 "accelerator=nvidia-tesla-p100
"。
apiVersion : v1
kind : Pod
metadata :
name : cuda-test
spec :
containers :
- name : cuda-test
image : "registry.k8s.io/cuda-vector-add:v0.1"
resources :
limits :
nvidia.com/gpu : 1
nodeSelector :
accelerator : nvidia-tesla-p100
基于集合 的需求基于集合 的标签需求允许你通过一组值来过滤键。
支持三种操作符:in
、notin
和 exists
(只可以用在键标识符上)。例如:
environment in (production, qa)
tier notin (frontend, backend)
partition
!partition
第一个示例选择了所有键等于 environment
并且值等于 production
或者 qa
的资源。 第二个示例选择了所有键等于 tier
并且值不等于 frontend
或者 backend
的资源,以及所有没有 tier
键标签的资源。 第三个示例选择了所有包含了有 partition
标签的资源;没有校验它的值。 第四个示例选择了所有没有 partition
标签的资源;没有校验它的值。 类似地,逗号分隔符充当与 运算符。因此,使用 partition
键(无论为何值)和
environment
不同于 qa
来过滤资源可以使用 partition, environment notin (qa)
来实现。
基于集合 的标签选择算符是相等标签选择算符的一般形式,因为 environment=production
等同于 environment in (production)
;!=
和 notin
也是类似的。
基于集合 的要求可以与基于相等 的要求混合使用。例如:partition in (customerA, customerB),environment!=qa
。
API LIST 和 WATCH 过滤 LIST 和 WATCH 操作可以使用查询参数指定标签选择算符过滤一组对象。
两种需求都是允许的。(这里显示的是它们出现在 URL 查询字符串中)
基于等值 的需求:?labelSelector=environment%3Dproduction,tier%3Dfrontend
基于集合 的需求:?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29
两种标签选择算符都可以通过 REST 客户端用于 list 或者 watch 资源。
例如,使用 kubectl
定位 apiserver
,可以使用基于等值 的标签选择算符可以这么写:
kubectl get pods -l environment = production,tier= frontend
或者使用基于集合的 需求:
kubectl get pods -l 'environment in (production),tier in (frontend)'
正如刚才提到的,基于集合 的需求更具有表达力。例如,它们可以实现值的或 操作:
kubectl get pods -l 'environment in (production, qa)'
或者通过exists 运算符限制不匹配:
kubectl get pods -l 'environment,environment notin (frontend)'
在 API 对象中设置引用 一些 Kubernetes 对象,例如 services
和 replicationcontrollers
,
也使用了标签选择算符去指定了其他资源的集合,例如
pods 。
Service 和 ReplicationController 一个 Service
指向的一组 Pod 是由标签选择算符定义的。同样,一个 ReplicationController
应该管理的 Pod 的数量也是由标签选择算符定义的。
两个对象的标签选择算符都是在 json
或者 yaml
文件中使用映射定义的,并且只支持
基于等值 需求的选择算符:
"selector" : {
"component" : "redis" ,
}
或者
selector :
component : redis
这个选择算符(分别在 json
或者 yaml
格式中)等价于 component=redis
或 component in (redis)
。
支持基于集合需求的资源 比较新的资源,例如 Job
、
Deployment
、
ReplicaSet
和
DaemonSet
,
也支持基于集合的 需求。
selector :
matchLabels :
component : redis
matchExpressions :
- {key: tier, operator: In, values : [cache]}
- {key: environment, operator: NotIn, values : [dev]}
matchLabels
是由 {key,value}
对组成的映射。
matchLabels
映射中的单个 {key,value}
等同于 matchExpressions
的元素,
其 key
字段为 "key",operator
为 "In",而 values
数组仅包含 "value"。
matchExpressions
是 Pod 选择算符需求的列表。
有效的运算符包括 In
、NotIn
、Exists
和 DoesNotExist
。
在 In
和 NotIn
的情况下,设置的值必须是非空的。
来自 matchLabels
和 matchExpressions
的所有要求都按逻辑与的关系组合到一起
-- 它们必须都满足才能匹配。
选择节点集 通过标签进行选择的一个用例是确定节点集,方便 Pod 调度。
有关更多信息,请参阅选择节点 文档。
3.1.3.5 - 名字空间 在 Kubernetes 中,名字空间(Namespace) 提供一种机制,将同一集群中的资源划分为相互隔离的组。
同一名字空间内的资源名称要唯一,但跨名字空间时没有这个要求。
名字空间作用域仅针对带有名字空间的对象,例如 Deployment、Service 等,
这种作用域对集群访问的对象不适用,例如 StorageClass、Node、PersistentVolume 等。
何时使用多个名字空间 名字空间适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。当需要名字空间提供的功能时,请开始使用它们。
名字空间为名称提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。
名字空间不能相互嵌套,每个 Kubernetes 资源只能在一个名字空间中。
名字空间是在多个用户之间划分集群资源的一种方法(通过资源配额 )。
不必使用多个名字空间来分隔仅仅轻微不同的资源,例如同一软件的不同版本:
应该使用标签
来区分同一名字空间中的不同资源。
使用名字空间 名字空间的创建和删除在名字空间的管理指南文档 描述。
说明: 避免使用前缀 kube-
创建名字空间,因为它是为 Kubernetes 系统名字空间保留的。
查看名字空间 你可以使用以下命令列出集群中现存的名字空间:
NAME STATUS AGE
default Active 1d
kube-node-lease Active 1d
kube-public Active 1d
kube-system Active 1d
Kubernetes 会创建四个初始名字空间:
default
没有指明使用其它名字空间的对象所使用的默认名字空间kube-system
Kubernetes 系统创建对象所使用的名字空间kube-public
这个名字空间是自动创建的,所有用户(包括未经过身份验证的用户)都可以读取它。
这个名字空间主要用于集群使用,以防某些资源在整个集群中应该是可见和可读的。
这个名字空间的公共方面只是一种约定,而不是要求。kube-node-lease
此名字空间用于与各个节点相关的
租约(Lease) 对象。
节点租期允许 kubelet 发送心跳 ,由此控制面能够检测到节点故障。为请求设置名字空间 要为当前请求设置名字空间,请使用 --namespace
参数。
例如:
kubectl run nginx --image= nginx --namespace= <名字空间名称>
kubectl get pods --namespace= <名字空间名称>
设置名字空间偏好 你可以永久保存名字空间,以用于对应上下文中所有后续 kubectl 命令。
kubectl config set-context --current --namespace= <名字空间名称>
# 验证
kubectl config view --minify | grep namespace:
名字空间和 DNS 当你创建一个服务 时,
Kubernetes 会创建一个相应的 DNS 条目 。
该条目的形式是 <服务名称>.<名字空间名称>.svc.cluster.local
,这意味着如果容器只使用
<服务名称>
,它将被解析到本地名字空间的服务。这对于跨多个名字空间(如开发、测试和生产)
使用相同的配置非常有用。如果你希望跨名字空间访问,则需要使用完全限定域名(FQDN)。
因此,所有的名字空间名称都必须是合法的
RFC 1123 DNS 标签 。
警告: 通过创建与公共顶级域名
同名的名字空间,这些名字空间中的服务可以拥有与公共 DNS 记录重叠的、较短的 DNS 名称。
所有名字空间中的负载在执行 DNS 查找时,
如果查找的名称没有尾部句点 ,
就会被重定向到这些服务上,因此呈现出比公共 DNS 更高的优先序。
为了缓解这类问题,需要将创建名字空间的权限授予可信的用户。
如果需要,你可以额外部署第三方的安全控制机制,
例如以准入 Webhook
的形式,阻止用户创建与公共 TLD
同名的名字空间。
并非所有对象都在名字空间中 大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某些名字空间中。
但是名字空间资源本身并不在名字空间中。而且底层资源,
例如节点 和持久化卷不属于任何名字空间。
查看哪些 Kubernetes 资源在名字空间中,哪些不在名字空间中:
# 位于名字空间中的资源
kubectl api-resources --namespaced= true
# 不在名字空间中的资源
kubectl api-resources --namespaced= false
自动打标签 特性状态: Kubernetes 1.21 [beta]
Kubernetes 控制面会为所有名字空间设置一个不可变更的
标签
kubernetes.io/metadata.name
,只要 NamespaceDefaultLabelName
这一特性门控 被启用。
标签的值是名字空间的名称。
接下来 3.1.3.6 - 注解 你可以使用 Kubernetes 注解为对象附加任意的非标识的元数据。客户端程序(例如工具和库)能够获取这些元数据信息。
为对象附加元数据 你可以使用标签或注解将元数据附加到 Kubernetes 对象。
标签可以用来选择对象和查找满足某些条件的对象集合。 相反,注解不用于标识和选择对象。
注解中的元数据,可以很小,也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。
注解和标签一样,是键/值对:
"metadata" : {
"annotations" : {
"key1" : "value1" ,
"key2" : "value2"
}
}
说明: Map 中的键和值必须是字符串。
换句话说,你不能使用数字、布尔值、列表或其他类型的键或值。
以下是一些例子,用来说明哪些信息可以使用注解来记录:
由声明性配置所管理的字段。
将这些字段附加为注解,能够将它们与客户端或服务端设置的默认值、
自动生成的字段以及通过自动调整大小或自动伸缩系统设置的字段区分开来。 构建、发布或镜像信息(如时间戳、发布 ID、Git 分支、PR 数量、镜像哈希、仓库地址)。 指向日志记录、监控、分析或审计仓库的指针。 可用于调试目的的客户端库或工具信息:例如,名称、版本和构建信息。
用户或者工具/系统的来源信息,例如来自其他生态系统组件的相关对象的 URL。
轻量级上线工具的元数据信息:例如,配置或检查点。
负责人员的电话或呼机号码,或指定在何处可以找到该信息的目录条目,如团队网站。
从用户到最终运行的指令,以修改行为或使用非标准功能。
你可以将这类信息存储在外部数据库或目录中而不使用注解,
但这样做就使得开发人员很难生成用于部署、管理、自检的客户端共享库和工具。
语法和字符集 注解(Annotations) 存储的形式是键/值对。有效的注解键分为两部分:
可选的前缀和名称,以斜杠(/
)分隔。
名称段是必需项,并且必须在 63 个字符以内,以字母数字字符([a-z0-9A-Z]
)开头和结尾,
并允许使用破折号(-
),下划线(_
),点(.
)和字母数字。
前缀是可选的。如果指定,则前缀必须是 DNS 子域:一系列由点(.
)分隔的 DNS 标签,
总计不超过 253 个字符,后跟斜杠(/
)。
如果省略前缀,则假定注解键对用户是私有的。 由系统组件添加的注解
(例如,kube-scheduler
,kube-controller-manager
,kube-apiserver
,kubectl
或其他第三方组件),必须为终端用户添加注解前缀。
kubernetes.io/
和 k8s.io/
前缀是为 Kubernetes 核心组件保留的。
例如,下面是一个 Pod 的配置文件,其注解中包含 imageregistry: https://hub.docker.com/
:
apiVersion : v1
kind : Pod
metadata :
name : annotations-demo
annotations :
imageregistry : "https://hub.docker.com/"
spec :
containers :
- name : nginx
image : nginx:1.7.9
ports :
- containerPort : 80
接下来 3.1.3.7 - 字段选择器 “字段选择器(Field selectors)”允许你根据一个或多个资源字段的值
筛选 Kubernetes 资源 。
下面是一些使用字段选择器查询的例子:
metadata.name=my-service
metadata.namespace!=default
status.phase=Pending
下面这个 kubectl
命令将筛选出 status.phase
字段值为 Running
的所有 Pod:
kubectl get pods --field-selector status.phase= Running
说明: 字段选择器本质上是资源“过滤器(Filters)”。默认情况下,字段选择器/过滤器是未被应用的,
这意味着指定类型的所有资源都会被筛选出来。
这使得以下的两个 kubectl
查询是等价的:
kubectl get pods
kubectl get pods --field-selector ""
支持的字段 不同的 Kubernetes 资源类型支持不同的字段选择器。
所有资源类型都支持 metadata.name
和 metadata.namespace
字段。
使用不被支持的字段选择器会产生错误。例如:
kubectl get ingress --field-selector foo.bar= baz
Error from server (BadRequest): Unable to find "ingresses" that match label selector "", field selector "foo.bar=baz": "foo.bar" is not a known field selector: only "metadata.name", "metadata.namespace"
支持的操作符 你可在字段选择器中使用 =
、==
和 !=
(=
和 ==
的意义是相同的)操作符。
例如,下面这个 kubectl
命令将筛选所有不属于 default
命名空间的 Kubernetes 服务:
kubectl get services --all-namespaces --field-selector metadata.namespace!= default
链式选择器 同标签 和其他选择器一样,
字段选择器可以通过使用逗号分隔的列表组成一个选择链。
下面这个 kubectl
命令将筛选 status.phase
字段不等于 Running
同时
spec.restartPolicy
字段等于 Always
的所有 Pod:
kubectl get pods --field-selector= status.phase!= Running,spec.restartPolicy= Always
多种资源类型 你能够跨多种资源类型来使用字段选择器。
下面这个 kubectl
命令将筛选出所有不在 default
命名空间中的 StatefulSet 和 Service:
kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!= default
3.1.3.8 - Finalizers Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后,
再完全删除被标记为删除的资源。
Finalizer 提醒控制器 清理被删除的对象拥有的资源。
当你告诉 Kubernetes 删除一个指定了 Finalizer 的对象时,
Kubernetes API 通过填充 .metadata.deletionTimestamp
来标记要删除的对象,
并返回 202
状态码(HTTP "已接受") 使其进入只读状态。
此时控制平面或其他组件会采取 Finalizer 所定义的行动,
而目标对象仍然处于终止中(Terminating)的状态。
这些行动完成后,控制器会删除目标对象相关的 Finalizer。
当 metadata.finalizers
字段为空时,Kubernetes 认为删除已完成并删除对象。
你可以使用 Finalizer 控制资源的垃圾收集 。
例如,你可以定义一个 Finalizer,在删除目标资源前清理相关资源或基础设施。
你可以通过使用 Finalizers 提醒控制器
在删除目标资源前执行特定的清理任务,
来控制资源的垃圾收集 。
Finalizers 通常不指定要执行的代码。
相反,它们通常是特定资源上的键的列表,类似于注解。
Kubernetes 自动指定了一些 Finalizers,但你也可以指定你自己的。
Finalizers 如何工作 当你使用清单文件创建资源时,你可以在 metadata.finalizers
字段指定 Finalizers。
当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers
字段中的值,
并进行以下操作:
修改对象,将你开始执行删除的时间添加到 metadata.deletionTimestamp
字段。 禁止对象被删除,直到其 metadata.finalizers
字段为空。 返回 202
状态码(HTTP "Accepted")。 管理 finalizer 的控制器注意到对象上发生的更新操作,对象的 metadata.deletionTimestamp
被设置,意味着已经请求删除该对象。然后,控制器会试图满足资源的 Finalizers 的条件。
每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers
字段中删除该键。
当 finalizers
字段为空时,deletionTimestamp
字段被设置的对象会被自动删除。
你也可以使用 Finalizers 来阻止删除未被管理的资源。
一个常见的 Finalizer 的例子是 kubernetes.io/pv-protection
,
它用来防止意外删除 PersistentVolume
对象。
当一个 PersistentVolume
对象被 Pod 使用时,
Kubernetes 会添加 pv-protection
Finalizer。
如果你试图删除 PersistentVolume
,它将进入 Terminating
状态,
但是控制器因为该 Finalizer 存在而无法删除该资源。
当 Pod 停止使用 PersistentVolume
时,
Kubernetes 清除 pv-protection
Finalizer,控制器就会删除该卷。
属主引用、标签和 Finalizers 与标签 类似,
属主引用
描述了 Kubernetes 中对象之间的关系,但它们作用不同。
当一个控制器
管理类似于 Pod 的对象时,它使用标签来跟踪相关对象组的变化。
例如,当 Job 创建一个或多个 Pod 时,
Job 控制器会给这些 Pod 应用上标签,并跟踪集群中的具有相同标签的 Pod 的变化。
Job 控制器还为这些 Pod 添加了“属主引用”,指向创建 Pod 的 Job。
如果你在这些 Pod 运行的时候删除了 Job,
Kubernetes 会使用属主引用(而不是标签)来确定集群中哪些 Pod 需要清理。
当 Kubernetes 识别到要删除的资源上的属主引用时,它也会处理 Finalizers。
在某些情况下,Finalizers 会阻止依赖对象的删除,
这可能导致目标属主对象被保留的时间比预期的长,而没有被完全删除。
在这些情况下,你应该检查目标属主和附属对象上的 Finalizers 和属主引用,来排查原因。
说明: 在对象卡在删除状态的情况下,要避免手动移除 Finalizers,以允许继续删除操作。
Finalizers 通常因为特殊原因被添加到资源上,所以强行删除它们会导致集群出现问题。
只有了解 finalizer 的用途时才能这样做,并且应该通过一些其他方式来完成
(例如,手动清除其余的依赖对象)。
接下来 3.1.3.9 - 属主与附属 在 Kubernetes 中,一些对象是其他对象的“属主(Owner)”。
例如,ReplicaSet 是一组 Pod 的属主。
具有属主的对象是属主的“附属(Dependent)”。
属主关系不同于一些资源使用的标签和选择算符 机制。
例如,有一个创建 EndpointSlice
对象的 Service,
该 Service 使用标签来让控制平面确定,哪些 EndpointSlice
对象属于该 Service。
除开标签,每个代表 Service 所管理的 EndpointSlice
都有一个属主引用。
属主引用避免 Kubernetes 的不同部分干扰到不受它们控制的对象。
对象规约中的属主引用 附属对象有一个 metadata.ownerReferences
字段,用于引用其属主对象。
一个有效的属主引用,包含与附属对象同在一个命名空间下的对象名称和一个 UID。
Kubernetes 自动为一些对象的附属资源设置属主引用的值,
这些对象包含 ReplicaSet、DaemonSet、Deployment、Job、CronJob、ReplicationController 等。
你也可以通过改变这个字段的值,来手动配置这些关系。
然而,通常不需要这么做,你可以让 Kubernetes 自动管理附属关系。
附属对象还有一个 ownerReferences.blockOwnerDeletion
字段,该字段使用布尔值,
用于控制特定的附属对象是否可以阻止垃圾收集删除其属主对象。
如果控制器 (例如 Deployment 控制器)
设置了 metadata.ownerReferences
字段的值,Kubernetes 会自动设置
blockOwnerDeletion
的值为 true
。
你也可以手动设置 blockOwnerDeletion
字段的值,以控制哪些附属对象会阻止垃圾收集。
Kubernetes 准入控制器根据属主的删除权限控制用户访问,以便为附属资源更改此字段。
这种控制机制可防止未经授权的用户延迟属主对象的删除。
说明: 根据设计,kubernetes 不允许跨名字空间指定属主。
名字空间范围的附属可以指定集群范围的或者名字空间范围的属主。
名字空间范围的属主必须 和该附属处于相同的名字空间。
如果名字空间范围的属主和附属不在相同的名字空间,那么该属主引用就会被认为是缺失的,
并且当附属的所有属主引用都被确认不再存在之后,该附属就会被删除。
集群范围的附属只能指定集群范围的属主。
在 v1.20+ 版本,如果一个集群范围的附属指定了一个名字空间范围类型的属主,
那么该附属就会被认为是拥有一个不可解析的属主引用,并且它不能够被垃圾回收。
在 v1.20+ 版本,如果垃圾收集器检测到无效的跨名字空间的属主引用,
或者一个集群范围的附属指定了一个名字空间范围类型的属主,
那么它就会报告一个警告事件。该事件的原因是 OwnerRefInvalidNamespace
,
involvedObject
属性中包含无效的附属。
你可以运行 kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
来获取该类型的事件。
属主关系与 Finalizer 当你告诉 Kubernetes 删除一个资源,API 服务器允许管理控制器处理该资源的任何
Finalizer 规则 。
Finalizer
防止意外删除你的集群所依赖的、用于正常运作的资源。
例如,如果你试图删除一个仍被 Pod 使用的 PersistentVolume
,该资源不会被立即删除,
因为 PersistentVolume
有 kubernetes.io/pv-protection
Finalizer。
相反,它将进入 Terminating
状态,直到 Kubernetes 清除这个 Finalizer,
而这种情况只会发生在 PersistentVolume
不再被挂载到 Pod 上时。
当你使用前台或孤立级联删除 时,
Kubernetes 也会向属主资源添加 Finalizer。
在前台删除中,会添加 foreground
Finalizer,这样控制器必须在删除了拥有
ownerReferences.blockOwnerDeletion=true
的附属资源后,才能删除属主对象。
如果你指定了孤立删除策略,Kubernetes 会添加 orphan
Finalizer,
这样控制器在删除属主对象后,会忽略附属资源。
接下来 3.1.3.10 - 推荐使用的标签 除了 kubectl 和 dashboard 之外,你还可以使用其他工具来可视化和管理 Kubernetes 对象。
一组通用的标签可以让多个工具之间相互操作,用所有工具都能理解的通用方式描述对象。
除了支持工具外,推荐的标签还以一种可以查询的方式描述了应用程序。
元数据围绕 应用(application) 的概念进行组织。Kubernetes
不是平台即服务(PaaS),没有或强制执行正式的应用程序概念。
相反,应用程序是非正式的,并使用元数据进行描述。应用程序包含的定义是松散的。
说明: 这些是推荐的标签。它们使管理应用程序变得更容易但不是任何核心工具所必需的。
共享标签和注解都使用同一个前缀:app.kubernetes.io
。没有前缀的标签是用户私有的。
共享前缀可以确保共享标签不会干扰用户自定义的标签。
标签 为了充分利用这些标签,应该在每个资源对象上都使用它们。
键 描述 示例 类型 app.kubernetes.io/name
应用程序的名称 mysql
字符串 app.kubernetes.io/instance
用于唯一确定应用实例的名称 mysql-abcxzy
字符串 app.kubernetes.io/version
应用程序的当前版本(例如语义版本、修订版哈希等) 5.7.21
字符串 app.kubernetes.io/component
架构中的组件 database
字符串 app.kubernetes.io/part-of
此级别的更高级别应用程序的名称 wordpress
字符串 app.kubernetes.io/managed-by
用于管理应用程序的工具 helm
字符串
为说明这些标签的实际使用情况,请看下面的 StatefulSet 对象:
# 这是一段节选
apiVersion : apps/v1
kind : StatefulSet
metadata :
labels :
app.kubernetes.io/name : mysql
app.kubernetes.io/instance : mysql-abcxzy
app.kubernetes.io/version : "5.7.21"
app.kubernetes.io/component : database
app.kubernetes.io/part-of : wordpress
app.kubernetes.io/managed-by : helm
应用和应用实例 应用可以在 Kubernetes 集群中安装一次或多次。在某些情况下,可以安装在同一命名空间中。
例如,可以不止一次地为不同的站点安装不同的 WordPress。
应用的名称和实例的名称是分别记录的。例如,WordPress 应用的
app.kubernetes.io/name
为 wordpress
,而其实例名称
app.kubernetes.io/instance
为 wordpress-abcxzy
。
这使得应用和应用的实例均可被识别,应用的每个实例都必须具有唯一的名称。
示例 为了说明使用这些标签的不同方式,以下示例具有不同的复杂性。
一个简单的无状态服务 考虑使用 Deployment
和 Service
对象部署的简单无状态服务的情况。
以下两个代码段表示如何以最简单的形式使用标签。
下面的 Deployment
用于监督运行应用本身的那些 Pod。
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app.kubernetes.io/name : myservice
app.kubernetes.io/instance : myservice-abcxzy
...
下面的 Service
用于暴露应用。
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : myservice
app.kubernetes.io/instance : myservice-abcxzy
...
带有一个数据库的 Web 应用程序 考虑一个稍微复杂的应用:一个使用 Helm 安装的 Web 应用(WordPress),
其中使用了数据库(MySQL)。以下代码片段说明用于部署此应用程序的对象的开始。
以下 Deployment
的开头用于 WordPress:
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app.kubernetes.io/name : wordpress
app.kubernetes.io/instance : wordpress-abcxzy
app.kubernetes.io/version : "4.9.4"
app.kubernetes.io/managed-by : helm
app.kubernetes.io/component : server
app.kubernetes.io/part-of : wordpress
...
这个 Service
用于暴露 WordPress:
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : wordpress
app.kubernetes.io/instance : wordpress-abcxzy
app.kubernetes.io/version : "4.9.4"
app.kubernetes.io/managed-by : helm
app.kubernetes.io/component : server
app.kubernetes.io/part-of : wordpress
...
MySQL 作为一个 StatefulSet
暴露,包含它和它所属的较大应用程序的元数据:
apiVersion : apps/v1
kind : StatefulSet
metadata :
labels :
app.kubernetes.io/name : mysql
app.kubernetes.io/instance : mysql-abcxzy
app.kubernetes.io/version : "5.7.21"
app.kubernetes.io/managed-by : helm
app.kubernetes.io/component : database
app.kubernetes.io/part-of : wordpress
...
Service
用于将 MySQL 作为 WordPress 的一部分暴露:
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : mysql
app.kubernetes.io/instance : mysql-abcxzy
app.kubernetes.io/version : "5.7.21"
app.kubernetes.io/managed-by : helm
app.kubernetes.io/component : database
app.kubernetes.io/part-of : wordpress
...
使用 MySQL StatefulSet
和 Service
,你会注意到有关 MySQL 和 WordPress 的信息,包括更广泛的应用程序。
3.2 - Kubernetes 架构 Kubernetes 背后的架构概念。
3.2.1 - 节点 Kubernetes 通过将容器放入在节点(Node)上运行的 Pod 中来执行你的工作负载。
节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。
每个节点包含运行 Pod 所需的服务;
这些节点由 控制面 负责管理。
通常集群中会有若干个节点;而在一个学习所用或者资源受限的环境中,你的集群中也可能只有一个节点。
节点上的组件 包括
kubelet 、
容器运行时 以及
kube-proxy 。
管理 向 API 服务器 添加节点的方式主要有两种:
节点上的 kubelet
向控制面执行自注册; 你(或者别的什么人)手动添加一个 Node 对象。 在你创建了 Node 对象 或者节点上的
kubelet
执行了自注册操作之后,控制面会检查新的 Node 对象是否合法。
例如,如果你尝试使用下面的 JSON 对象来创建 Node 对象:
{
"kind" : "Node" ,
"apiVersion" : "v1" ,
"metadata" : {
"name" : "10.240.79.157" ,
"labels" : {
"name" : "my-first-k8s-node"
}
}
}
Kubernetes 会在内部创建一个 Node 对象作为节点的表示。Kubernetes 检查 kubelet
向 API 服务器注册节点时使用的 metadata.name
字段是否匹配。
如果节点是健康的(即所有必要的服务都在运行中),则该节点可以用来运行 Pod。
否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。
说明: Kubernetes 会一直保存着非法节点对应的对象,并持续检查该节点是否已经变得健康。
你,或者某个控制器 必须显式地删除该
Node 对象以停止健康检查操作。
Node 对象的名称必须是合法的
DNS 子域名 。
节点名称唯一性 节点的名称 用来标识 Node 对象。
没有两个 Node 可以同时使用相同的名称。 Kubernetes 还假定名字相同的资源是同一个对象。
就 Node 而言,隐式假定使用相同名称的实例会具有相同的状态(例如网络配置、根磁盘内容)
和类似节点标签这类属性。这可能在节点被更改但其名称未变时导致系统状态不一致。
如果某个 Node 需要被替换或者大量变更,需要从 API 服务器移除现有的 Node 对象,
之后再在更新之后重新将其加入。
节点自注册 当 kubelet 标志 --register-node
为 true(默认)时,它会尝试向 API 服务注册自己。
这是首选模式,被绝大多数发行版选用。
对于自注册模式,kubelet 使用下列参数启动:
--kubeconfig
- 用于向 API 服务器执行身份认证所用的凭据的路径。--cloud-provider
- 与某云驱动
进行通信以读取与自身相关的元数据的方式。--register-node
- 自动向 API 服务注册。--register-with-taints
- 使用所给的污点 列表
(逗号分隔的 <key>=<value>:<effect>
)注册节点。当 register-node
为 false 时无效。--node-ip
- 节点 IP 地址。--node-labels
- 在集群中注册节点时要添加的标签 。
(参见 NodeRestriction 准入控制插件 所实施的标签限制)。--node-status-update-frequency
- 指定 kubelet 向 API 服务器发送其节点状态的频率。当 Node 鉴权模式 和
NodeRestriction 准入插件 被启用后,
仅授权 kubelet 创建/修改自己的 Node 资源。
说明: 正如节点名称唯一性 一节所述,当 Node 的配置需要被更新时,
一种好的做法是重新向 API 服务器注册该节点。例如,如果 kubelet 重启时其 --node-labels
是新的值集,但同一个 Node 名称已经被使用,则所作变更不会起作用,
因为节点标签是在 Node 注册时完成的。
如果在 kubelet 重启期间 Node 配置发生了变化,已经被调度到某 Node 上的 Pod
可能会出现行为不正常或者出现其他问题,例如,已经运行的 Pod
可能通过污点机制设置了与 Node 上新设置的标签相排斥的规则,也有一些其他 Pod,
本来与此 Pod 之间存在不兼容的问题,也会因为新的标签设置而被调到同一节点。
节点重新注册操作可以确保节点上所有 Pod 都被排空并被正确地重新调度。
手动节点管理 你可以使用 kubectl
来创建和修改 Node 对象。
如果你希望手动创建节点对象时,请设置 kubelet 标志 --register-node=false
。
你可以修改 Node 对象(忽略 --register-node
设置)。
例如,你可以修改节点上的标签或并标记其为不可调度。
你可以结合使用 Node 上的标签和 Pod 上的选择算符来控制调度。
例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。
如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该 Node 之上,
但不会影响任何已经在其上的 Pod。
这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
要标记一个 Node 为不可调度,执行以下命令:
更多细节参考安全地腾空节点 。
说明: 被 DaemonSet 控制器创建的 Pod
能够容忍节点的不可调度属性。
DaemonSet 通常提供节点本地的服务,即使节点上的负载应用已经被腾空,
这些服务也仍需运行在节点之上。
节点状态 一个节点的状态包含以下信息:
你可以使用 kubectl
来查看节点状态和其他细节信息:
kubectl describe node <节点名称>
下面对输出的每个部分进行详细描述。
地址 这些字段的用法取决于你的云服务商或者物理机配置。
HostName:由节点的内核报告。可以通过 kubelet 的 --hostname-override
参数覆盖。 ExternalIP:通常是节点的可外部路由(从集群外可访问)的 IP 地址。 InternalIP:通常是节点的仅可在集群内部路由的 IP 地址。 状况 conditions
字段描述了所有 Running
节点的状况。状况的示例包括:
节点状况及每种状况适用场景的描述 节点状况 描述 Ready
如节点是健康的并已经准备好接收 Pod 则为 True
;False
表示节点不健康而且不能接收 Pod;Unknown
表示节点控制器在最近 node-monitor-grace-period
期间(默认 40 秒)没有收到节点的消息 DiskPressure
True
表示节点存在磁盘空间压力,即磁盘可用量低, 否则为 False
MemoryPressure
True
表示节点存在内存压力,即节点内存可用量低,否则为 False
PIDPressure
True
表示节点存在进程压力,即节点上进程过多;否则为 False
NetworkUnavailable
True
表示节点网络配置不正确;否则为 False
说明: 如果使用命令行工具来打印已保护(Cordoned)节点的细节,其中的 Condition 字段可能包括
SchedulingDisabled
。SchedulingDisabled
不是 Kubernetes API 中定义的
Condition,被保护起来的节点在其规约中被标记为不可调度(Unschedulable)。
在 Kubernetes API 中,节点的状况表示节点资源中.status
的一部分。
例如,以下 JSON 结构描述了一个健康节点:
"conditions" : [
{
"type" : "Ready" ,
"status" : "True" ,
"reason" : "KubeletReady" ,
"message" : "kubelet is posting ready status" ,
"lastHeartbeatTime" : "2019-06-05T18:38:35Z" ,
"lastTransitionTime" : "2019-06-05T11:41:27Z"
}
]
如果 Ready 状况的 status
处于 Unknown
或者 False
状态的时间超过了
pod-eviction-timeout
值(一个传递给
kube-controller-manager
的参数),节点控制器 会对节点上的所有 Pod 触发
API 发起的驱逐 。
默认的逐出超时时长为 5 分钟 。
某些情况下,当节点不可达时,API 服务器不能和其上的 kubelet 通信。
删除 Pod 的决定不能传达给 kubelet,直到它重新建立和 API 服务器的连接为止。
与此同时,被计划删除的 Pod 可能会继续在游离的节点上运行。
节点控制器在确认 Pod 在集群中已经停止运行前,不会强制删除它们。
你可以看到可能在这些无法访问的节点上运行的 Pod 处于 Terminating
或者 Unknown
状态。
如果 kubernetes 不能基于下层基础设施推断出某节点是否已经永久离开了集群,
集群管理员可能需要手动删除该节点对象。
从 Kubernetes 删除节点对象将导致 API 服务器删除节点上所有运行的 Pod 对象并释放它们的名字。
当节点上出现问题时,Kubernetes 控制面会自动创建与影响节点的状况对应的
污点 。
调度器在将 Pod 指派到某 Node 时会考虑 Node 上的污点设置。
Pod 也可以设置容忍度 ,
以便能够在设置了特定污点的 Node 上运行。
进一步的细节可参阅根据状况为节点设置污点 。
容量(Capacity)与可分配(Allocatable) 这两个值描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。
capacity
块中的字段标示节点拥有的资源总量。
allocatable
块指示节点上可供普通 Pod 消耗的资源量。
可以在学习如何在节点上预留计算资源
的时候了解有关容量和可分配资源的更多信息。
信息(Info) Info 指的是节点的一般信息,如内核版本、Kubernetes 版本(kubelet
和 kube-proxy
版本)、
容器运行时详细信息,以及节点使用的操作系统。
kubelet
从节点收集这些信息并将其发布到 Kubernetes API。
心跳 Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。
对于节点,有两种形式的心跳:
更新节点的 .status
kube-node-lease
名字空间 中的
Lease(租约) 对象。
每个节点都有一个关联的 Lease 对象。与 Node 的 .status
更新相比,Lease 是一种轻量级资源。
使用 Lease 来表达心跳在大型集群中可以减少这些更新对性能的影响。
kubelet 负责创建和更新节点的 .status
,以及更新它们对应的 Lease。
当节点状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet 会更新 .status
。
.status
更新的默认间隔为 5 分钟(比节点不可达事件的 40 秒默认超时时间长很多)。 kubelet
会创建并每 10 秒(默认更新间隔时间)更新 Lease 对象。
Lease 的更新独立于 Node 的 .status
更新而发生。
如果 Lease 的更新操作失败,kubelet 会采用指数回退机制,从 200 毫秒开始重试,
最长重试间隔为 7 秒钟。节点控制器 节点控制器 是 Kubernetes 控制面组件,
管理节点的方方面面。
节点控制器在节点的生命周期中扮演多个角色。
第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。
第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。
如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。
如果不可用,节点控制器会将该节点从它的节点列表删除。
第三个是监控节点的健康状况。节点控制器负责:
在节点不可达的情况下,在 Node 的 .status
中更新 Ready
状况。
在这种情况下,节点控制器将 NodeReady 状况更新为 Unknown
。 如果节点仍然无法访问:对于不可达节点上的所有 Pod 触发
API 发起的逐出 操作。
默认情况下,节点控制器在将节点标记为 Unknown
后等待 5 分钟提交第一个驱逐请求。 默认情况下,节点控制器每 5 秒检查一次节点状态,可以使用 kube-controller-manager
组件上的 --node-monitor-period
参数来配置周期。
逐出速率限制 大部分情况下,节点控制器把逐出速率限制在每秒 --node-eviction-rate
个(默认为 0.1)。
这表示它每 10 秒钟内至多从一个节点驱逐 Pod。
当一个可用区域(Availability Zone)中的节点变为不健康时,节点的驱逐行为将发生改变。
节点控制器会同时检查可用区域中不健康(Ready
状况为 Unknown
或 False
)
的节点的百分比:
如果不健康节点的比例超过 --unhealthy-zone-threshold
(默认为 0.55),
驱逐速率将会降低。 如果集群较小(意即小于等于 --large-cluster-size-threshold
个节点 - 默认为 50),
驱逐操作将会停止。 否则驱逐速率将降为每秒 --secondary-node-eviction-rate
个(默认为 0.01)。 在逐个可用区域中实施这些策略的原因是,
当一个可用区域可能从控制面脱离时其它可用区域可能仍然保持连接。
如果你的集群没有跨越云服务商的多个可用区域,那(整个集群)就只有一个可用区域。
跨多个可用区域部署你的节点的一个关键原因是当某个可用区域整体出现故障时,
工作负载可以转移到健康的可用区域。
因此,如果一个可用区域中的所有节点都不健康时,节点控制器会以正常的速率
--node-eviction-rate
进行驱逐操作。
在所有的可用区域都不健康(也即集群中没有健康节点)的极端情况下,
节点控制器将假设控制面与节点间的连接出了某些问题,它将停止所有驱逐动作
(如果故障后部分节点重新连接,节点控制器会从剩下不健康或者不可达节点中驱逐 Pod)。
节点控制器还负责驱逐运行在拥有 NoExecute
污点的节点上的 Pod,
除非这些 Pod 能够容忍此污点。
节点控制器还负责根据节点故障(例如节点不可访问或没有就绪)
为其添加污点 。
这意味着调度器不会将 Pod 调度到不健康的节点上。
资源容量跟踪 Node 对象会跟踪节点上资源的容量(例如可用内存和 CPU 数量)。
通过自注册 机制生成的 Node 对象会在注册期间报告自身容量。
如果你手动 添加了 Node,
你就需要在添加节点时手动设置节点容量。
Kubernetes 调度器
保证节点上有足够的资源供其上的所有 Pod 使用。
它会检查节点上所有容器的请求的总和不会超过节点的容量。
总的请求包括由 kubelet 启动的所有容器,但不包括由容器运行时直接启动的容器,
也不包括不受 kubelet
控制的其他进程。
节点拓扑 特性状态: Kubernetes v1.18 [beta]
如果启用了 TopologyManager
特性门控 ,
kubelet
可以在作出资源分配决策时使用拓扑提示。
参考控制节点上拓扑管理策略 了解详细信息。
节点体面关闭 特性状态: Kubernetes v1.21 [beta]
kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的所有 Pod。
在节点终止期间,kubelet 保证 Pod 遵从常规的
Pod 终止流程 。
节点体面关闭特性依赖于 systemd,因为它要利用
systemd 抑制器锁 机制,
在给定的期限内延迟节点关闭。
节点体面关闭特性受 GracefulNodeShutdown
特性门控 控制,
在 1.21 版本中是默认启用的。
注意,默认情况下,下面描述的两个配置选项,shutdownGracePeriod
和
shutdownGracePeriodCriticalPods
都是被设置为 0 的,因此不会激活节点体面关闭功能。
要激活此功能特性,这两个 kubelet 配置选项要适当配置,并设置为非零值。
在体面关闭节点过程中,kubelet 分两个阶段来终止 Pod:
终止在节点上运行的常规 Pod。 终止在节点上运行的关键 Pod 。 节点体面关闭的特性对应两个
KubeletConfiguration
选项:
shutdownGracePeriod
:指定节点应延迟关闭的总持续时间。此时间是 Pod 体面终止的时间总和,不区分常规 Pod
还是关键 Pod 。 shutdownGracePeriodCriticalPods
:在节点关闭期间指定用于终止关键 Pod
的持续时间。该值应小于 shutdownGracePeriod
。 例如,如果设置了 shutdownGracePeriod=30s
和 shutdownGracePeriodCriticalPods=10s
,
则 kubelet 将延迟 30 秒关闭节点。
在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod,
而保留最后 10 秒用于终止关键 Pod 。
说明: 当 Pod 在正常节点关闭期间被驱逐时,它们会被标记为关闭。
运行 kubectl get pods
时,被驱逐的 Pod 的状态显示为 Terminated
。
并且 kubectl describe pod
表示 Pod 因节点关闭而被驱逐:
Reason: Terminated
Message: Pod was terminated in response to imminent node shutdown.
节点非体面关闭 特性状态: Kubernetes v1.24 [alpha]
节点关闭的操作可能无法被 kubelet 的节点关闭管理器检测到,
是因为该命令不会触发 kubelet 所使用的抑制锁定机制,或者是因为用户错误的原因,
即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。
请参考以上节点体面关闭 部分了解更多详细信息。
当某节点关闭但 kubelet 的节点关闭管理器未检测到这一事件时,
在那个已关闭节点上、属于 StatefulSet 的 Pod 将停滞于终止状态,并且不能移动到新的运行节点上。
这是因为已关闭节点上的 kubelet 已不存在,亦无法删除 Pod,
因此 StatefulSet 无法创建同名的新 Pod。
如果 Pod 使用了卷,则 VolumeAttachments 不会从原来的已关闭节点上删除,
因此这些 Pod 所使用的卷也无法挂接到新的运行节点上。
所以,那些以 StatefulSet 形式运行的应用无法正常工作。
如果原来的已关闭节点被恢复,kubelet 将删除 Pod,新的 Pod 将被在不同的运行节点上创建。
如果原来的已关闭节点没有被恢复,那些在已关闭节点上的 Pod 将永远滞留在终止状态。
为了缓解上述情况,用户可以手动将具有 NoExecute
或 NoSchedule
效果的
node.kubernetes.io/out-of-service
污点添加到节点上,标记其无法提供服务。
如果在 kube-controller-manager
上启用了 NodeOutOfServiceVolumeDetach
特性门控 ,
并且节点被通过污点标记为无法提供服务,如果节点 Pod 上没有设置对应的容忍度,
那么这样的 Pod 将被强制删除,并且该在节点上被终止的 Pod 将立即进行卷分离操作。
这样就允许那些在无法提供服务节点上的 Pod 能在其他节点上快速恢复。
在非体面关闭期间,Pod 分两个阶段终止:
强制删除没有匹配的 out-of-service
容忍度的 Pod。 立即对此类 Pod 执行分离卷操作。 说明: 在添加 node.kubernetes.io/out-of-service
污点之前,应该验证节点已经处于关闭或断电状态(而不是在重新启动中)。 将 Pod 移动到新节点后,用户需要手动移除停止服务的污点,并且用户要检查关闭节点是否已恢复,因为该用户是最初添加污点的用户。 基于 Pod 优先级的节点体面关闭 特性状态: Kubernetes v1.23 [alpha]
为了在节点体面关闭期间提供更多的灵活性,尤其是处理关闭期间的 Pod 排序问题,
节点体面关闭机制能够关注 Pod 的 PriorityClass 设置,前提是你已经在集群中启用了此功能特性。
此功能特性允许集群管理员基于 Pod
的优先级类(Priority Class)
显式地定义节点体面关闭期间 Pod 的处理顺序。
前文所述的节点体面关闭 特性能够分两个阶段关闭 Pod,
首先关闭的是非关键的 Pod,之后再处理关键 Pod。
如果需要显式地以更细粒度定义关闭期间 Pod 的处理顺序,需要一定的灵活度,
这时可以使用基于 Pod 优先级的体面关闭机制。
当节点体面关闭能够处理 Pod 优先级时,节点体面关闭的处理可以分为多个阶段,
每个阶段关闭特定优先级类的 Pod。kubelet 可以被配置为按确切的阶段处理 Pod,
且每个阶段可以独立设置关闭时间。
假设集群中存在以下自定义的 Pod
优先级类 。
Pod 优先级类名称 Pod 优先级类数值 custom-class-a
100000 custom-class-b
10000 custom-class-c
1000 regular/unset
0
在 kubelet 配置 中,
shutdownGracePeriodByPodPriority
可能看起来是这样:
Pod 优先级类数值 关闭期限 100000 10 秒 10000 180 秒 1000 120 秒 0 60 秒
对应的 kubelet 配置 YAML 将会是:
shutdownGracePeriodByPodPriority :
- priority : 100000
shutdownGracePeriodSeconds : 10
- priority : 10000
shutdownGracePeriodSeconds : 180
- priority : 1000
shutdownGracePeriodSeconds : 120
- priority : 0
shutdownGracePeriodSeconds : 60
上面的表格表明,所有 priority
值大于等于 100000 的 Pod 会得到 10 秒钟期限停止,
所有 priority
值介于 10000 和 100000 之间的 Pod 会得到 180 秒钟期限停止,
所有 priority
值介于 1000 和 10000 之间的 Pod 会得到 120 秒钟期限停止,
所有其他 Pod 将获得 60 秒的时间停止。
用户不需要为所有的优先级类都设置数值。例如,你也可以使用下面这种配置:
Pod 优先级类数值 关闭期限 100000 300 秒 1000 120 秒 0 60 秒
在上面这个场景中,优先级类为 custom-class-b
的 Pod 会与优先级类为 custom-class-c
的 Pod 在关闭时按相同期限处理。
如果在特定的范围内不存在 Pod,则 kubelet 不会等待对应优先级范围的 Pod。
kubelet 会直接跳到下一个优先级数值范围进行处理。
如果此功能特性被启用,但没有提供配置数据,则不会出现排序操作。
使用此功能特性需要启用 GracefulNodeShutdownBasedOnPodPriority
特性门控 ,
并将 kubelet 配置
中的 shutdownGracePeriodByPodPriority
设置为期望的配置,
其中包含 Pod 的优先级类数值以及对应的关闭期限。
说明: 在节点体面关闭期间考虑 Pod 优先级的能力是作为 Kubernetes v1.23 中的 Alpha 功能引入的。
在 Kubernetes 1.25 中该功能是 Beta 版,默认启用。
kubelet 子系统中会生成 graceful_shutdown_start_time_seconds
和
graceful_shutdown_end_time_seconds
度量指标以便监视节点关闭行为。
交换内存管理 特性状态: Kubernetes v1.22 [alpha]
在 Kubernetes 1.22 之前,节点不支持使用交换内存,并且默认情况下,
如果在节点上检测到交换内存配置,kubelet 将无法启动。
在 1.22 以后,可以逐个节点地启用交换内存支持。
要在节点上启用交换内存,必须启用kubelet 的 NodeSwap
特性门控,
同时使用 --fail-swap-on
命令行参数或者将 failSwapOn
配置 设置为 false。
警告: 当内存交换功能被启用后,Kubernetes 数据(如写入 tmpfs 的 Secret 对象的内容)可以被交换到磁盘。
用户还可以选择配置 memorySwap.swapBehavior
以指定节点使用交换内存的方式。例如:
memorySwap :
swapBehavior : LimitedSwap
可用的 swapBehavior
的配置选项有:
LimitedSwap
:Kubernetes 工作负载的交换内存会受限制。
不受 Kubernetes 管理的节点上的工作负载仍然可以交换。UnlimitedSwap
:Kubernetes 工作负载可以使用尽可能多的交换内存请求,
一直到达到系统限制为止。如果启用了特性门控但是未指定 memorySwap
的配置,默认情况下 kubelet 将使用
LimitedSwap
设置。
LimitedSwap
这种设置的行为取决于节点运行的是 v1 还是 v2 的控制组(也就是 cgroups
):
cgroupsv1: Kubernetes 工作负载可以使用内存和交换,上限为 Pod 的内存限制值(如果设置了的话)。cgroupsv2: Kubernetes 工作负载不能使用交换内存。如需更多信息以及协助测试和提供反馈,请参见
KEP-2400
及其设计提案 。
接下来 3.2.2 - 节点与控制面之间的通信 本文列举控制面节点(确切说是 API 服务器)和 Kubernetes 集群之间的通信路径。
目的是为了让用户能够自定义他们的安装,以实现对网络配置的加固,
使得集群能够在不可信的网络上(或者在一个云服务商完全公开的 IP 上)运行。
节点到控制面 Kubernetes 采用的是中心辐射型(Hub-and-Spoke)API 模式。
所有从节点(或运行于其上的 Pod)发出的 API 调用都终止于 API 服务器。
其它控制面组件都没有被设计为可暴露远程服务。
API 服务器被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求,
并启用一种或多种形式的客户端身份认证 机制。
一种或多种客户端鉴权机制 应该被启用,
特别是在允许使用匿名请求
或服务账户令牌 的时候。
应该使用集群的公共根证书开通节点,这样它们就能够基于有效的客户端凭据安全地连接 API 服务器。
一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。
请查看 kubelet TLS 启动引导
以了解如何自动提供 kubelet 客户端证书。
想要连接到 API 服务器的 Pod 可以使用服务账号安全地进行连接。
当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。
kubernetes
服务(位于 default
名字空间中)配置了一个虚拟 IP 地址,
用于(通过 kube-proxy)转发请求到 API 服务器的 HTTPS 末端。
控制面组件也通过安全端口与集群的 API 服务器通信。
这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的,
能够在不可信的网络或公网上运行。
控制面到节点 从控制面(API 服务器)到节点有两种主要的通信路径。
第一种是从 API 服务器到集群中每个节点上运行的 kubelet 进程。
第二种是从 API 服务器通过它的代理功能连接到任何节点、Pod 或者服务。
API 服务器到 kubelet 从 API 服务器到 kubelet 的连接用于:
获取 Pod 日志 挂接(通过 kubectl)到运行中的 Pod 提供 kubelet 的端口转发功能。 这些连接终止于 kubelet 的 HTTPS 末端。
默认情况下,API 服务器不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击,
在非受信网络或公开网络上运行也是 不安全的 。
为了对这个连接进行认证,使用 --kubelet-certificate-authority
标志给
API 服务器提供一个根证书包,用于 kubelet 的服务证书。
如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 API 服务器和
kubelet 之间使用 SSH 隧道 。
最后,应该启用
Kubelet 认证/鉴权
来保护 kubelet API。
API 服务器到节点、Pod 和服务 从 API 服务器到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。
这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https:
来运行在安全的 HTTPS 连接上。
不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。
因此,虽然连接是加密的,仍无法提供任何完整性保证。
这些连接 目前还不能安全地 在非受信网络或公共网络上运行。
SSH 隧道 Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。在这种配置下,
API 服务器建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务器)
并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。
这一隧道保证通信不会被暴露到集群节点所运行的网络之外。
Konnectivity 服务 特性状态: Kubernetes v1.18 [beta]
作为 SSH 隧道的替代方案,Konnectivity 服务提供 TCP 层的代理,以便支持从控制面到集群的通信。
Konnectivity 服务包含两个部分:Konnectivity 服务器和 Konnectivity 代理,
分别运行在控制面网络和节点网络中。
Konnectivity 代理建立并维持到 Konnectivity 服务器的网络连接。
启用 Konnectivity 服务之后,所有控制面到节点的通信都通过这些连接传输。
请浏览 Konnectivity 服务任务
在你的集群中配置 Konnectivity 服务。
3.2.3 - 控制器 在机器人技术和自动化领域,控制回路(Control Loop)是一个非终止回路,用于调节系统状态。
这是一个控制环的例子:房间里的温度自动调节器。
当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State) 。
房间的实际温度是当前状态(Current State) 。
通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态。
在 Kubernetes 中,控制器通过监控
集群
的公共状态,并致力于将当前状态转变为期望的状态。
控制器模式 一个控制器至少追踪一种类型的 Kubernetes 资源。这些
对象
有一个代表期望状态的 spec
字段。
该资源的控制器负责确保其当前状态接近期望状态。
控制器可能会自行执行操作;在 Kubernetes 中更常见的是一个控制器会发送信息给
API 服务器 ,这会有副作用。
具体可参看后文的例子。
通过 API 服务器来控制 Job 控制器是一个 Kubernetes 内置控制器的例子。
内置控制器通过和集群 API 服务器交互来管理状态。
Job 是一种 Kubernetes 资源,它运行一个或者多个 Pod ,
来执行一个任务然后停止。
(一旦被调度了 ,对 kubelet
来说 Pod
对象就会变成了期望状态的一部分)。
在集群中,当 Job 控制器拿到新任务时,它会保证一组 Node 节点上的 kubelet
可以运行正确数量的 Pod 来完成工作。
Job 控制器不会自己运行任何的 Pod 或者容器。Job 控制器是通知 API 服务器来创建或者移除 Pod。
控制面 中的其它组件
根据新的消息作出反应(调度并运行新 Pod)并且最终完成工作。
创建新 Job 后,所期望的状态就是完成这个 Job。Job 控制器会让 Job 的当前状态不断接近期望状态:创建为 Job 要完成工作所需要的 Pod,使 Job 的状态接近完成。
控制器也会更新配置对象。例如:一旦 Job 的工作完成了,Job 控制器会更新 Job 对象的状态为 Finished
。
(这有点像温度自动调节器关闭了一个灯,以此来告诉你房间的温度现在到你设定的值了)。
直接控制 相比 Job 控制器,有些控制器需要对集群外的一些东西进行修改。
例如,如果你使用一个控制回路来保证集群中有足够的
节点 ,那么控制器就需要当前集群外的
一些服务在需要时创建新节点。
和外部状态交互的控制器从 API 服务器获取到它想要的状态,然后直接和外部系统进行通信
并使当前状态更接近期望状态。
(实际上有一个控制器
可以水平地扩展集群中的节点。)
这里,很重要的一点是,控制器做出了一些变更以使得事物更接近你的期望状态,
之后将当前状态报告给集群的 API 服务器。
其他控制回路可以观测到所汇报的数据的这种变化并采取其各自的行动。
在温度计的例子中,如果房间很冷,那么某个控制器可能还会启动一个防冻加热器。
就 Kubernetes 集群而言,控制面间接地与 IP 地址管理工具、存储服务、云驱动
APIs 以及其他服务协作,通过扩展 Kubernetes
来实现这点。
期望状态与当前状态 Kubernetes 采用了系统的云原生视图,并且可以处理持续的变化。
在任务执行时,集群随时都可能被修改,并且控制回路会自动修复故障。
这意味着很可能集群永远不会达到稳定状态。
只要集群中的控制器在运行并且进行有效的修改,整体状态的稳定与否是无关紧要的。
设计 作为设计原则之一,Kubernetes 使用了很多控制器,每个控制器管理集群状态的一个特定方面。
最常见的一个特定的控制器使用一种类型的资源作为它的期望状态,
控制器管理控制另外一种类型的资源向它的期望状态演化。
例如,Job 的控制器跟踪 Job 对象(以发现新的任务)和 Pod 对象(以运行 Job,然后查看任务何时完成)。
在这种情况下,新任务会创建 Job,而 Job 控制器会创建 Pod。
使用简单的控制器而不是一组相互连接的单体控制回路是很有用的。
控制器会失败,所以 Kubernetes 的设计正是考虑到了这一点。
说明: 可以有多个控制器来创建或者更新相同类型的对象。
在后台,Kubernetes 控制器确保它们只关心与其控制资源相关联的资源。
例如,你可以创建 Deployment 和 Job;它们都可以创建 Pod。
Job 控制器不会删除 Deployment 所创建的 Pod,因为有信息
(标签 )让控制器可以区分这些 Pod。
运行控制器的方式 Kubernetes 内置一组控制器,运行在 kube-controller-manager 内。
这些内置的控制器提供了重要的核心功能。
Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。
Kubernetes 允许你运行一个稳定的控制平面,这样即使某些内置控制器失败了,
控制平面的其他部分会接替它们的工作。
你会遇到某些控制器运行在控制面之外,用以扩展 Kubernetes。
或者,如果你愿意,你也可以自己编写新控制器。
你可以以一组 Pod 来运行你的控制器,或者运行在 Kubernetes 之外。
最合适的方案取决于控制器所要执行的功能是什么。
接下来 3.2.4 - 云控制器管理器 特性状态: Kubernetes v1.11 [beta]
使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。
Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。
组件 cloud-controller-manager 是指云控制器管理器, 一个 Kubernetes 控制平面 组件,
嵌入了特定于云平台的控制逻辑。
云控制器管理器(Cloud Controller Manager)允许你将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
cloud-controller-manager
组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
cloud-controller-manager
组件是基于一种插件机制来构造的,
这种机制使得不同的云厂商都能将其平台与 Kubernetes 集成。
设计
云控制器管理器以一组多副本的进程集合的形式运行在控制面中,通常表现为 Pod
中的容器。每个 cloud-controller-manager
在同一进程中实现多个控制器 。
说明: 你也可以用 Kubernetes 插件
的形式而不是控制面中的一部分来运行云控制器管理器。
云控制器管理器的功能 云控制器管理器中的控制器包括:
节点控制器 节点控制器负责在云基础设施中创建了新服务器时为之更新节点(Node) 对象。
节点控制器从云提供商获取当前租户中主机的信息。节点控制器执行以下功能:
使用从云平台 API 获取的对应服务器的唯一标识符更新 Node 对象; 利用特定云平台的信息为 Node 对象添加注解和标签,例如节点所在的区域
(Region)和所具有的资源(CPU、内存等等); 获取节点的网络地址和主机名; 检查节点的健康状况。如果节点无响应,控制器通过云平台 API
查看该节点是否已从云中禁用、删除或终止。如果节点已从云中删除,
则控制器从 Kubernetes 集群中删除 Node 对象。 某些云驱动实现中,这些任务被划分到一个节点控制器和一个节点生命周期控制器中。
路由控制器 Route 控制器负责适当地配置云平台中的路由,以便 Kubernetes 集群中不同节点上的容器之间可以相互通信。
取决于云驱动本身,路由控制器可能也会为 Pod 网络分配 IP 地址块。
服务控制器 服务(Service) 与受控的负载均衡器、
IP 地址、网络包过滤、目标健康检查等云基础设施组件集成。
服务控制器与云驱动的 API 交互,以配置负载均衡器和其他基础设施组件。
你所创建的 Service 资源会需要这些组件服务。
鉴权 本节分别讲述云控制器管理器为了完成自身工作而产生的对各类 API 对象的访问需求。
节点控制器 节点控制器只操作 Node 对象。它需要读取和修改 Node 对象的完全访问权限。
v1/Node
:
Get List Create Update Patch Watch Delete 路由控制器 路由控制器会监听 Node 对象的创建事件,并据此配置路由设施。
它需要读取 Node 对象的 Get 权限。
v1/Node
:
服务控制器 服务控制器监测 Service 对象的 Create、Update 和 Delete 事件,并配置对应服务的 Endpoints 对象
(对于 EndpointSlices,kube-controller-manager 按需对其进行管理)。
为了访问 Service 对象,它需要 List 和 Watch 访问权限。
为了更新 Service 对象,它需要 Patch 和 Update 访问权限。
为了能够配置 Service 对应的 Endpoints 资源,
它需要 Create、List、Get、Watch 和 Update 等访问权限。
v1/Service
:
其他 在云控制器管理器的实现中,其核心部分需要创建 Event 对象的访问权限,
并创建 ServiceAccount 资源以保证操作安全性的权限。
v1/Event
:
v1/ServiceAccount
:
用于云控制器管理器 RBAC
的 ClusterRole 如下例所示:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
name : cloud-controller-manager
rules :
- apiGroups :
- ""
resources :
- events
verbs :
- create
- patch
- update
- apiGroups :
- ""
resources :
- nodes
verbs :
- '*'
- apiGroups :
- ""
resources :
- nodes/status
verbs :
- patch
- apiGroups :
- ""
resources :
- services
verbs :
- list
- patch
- update
- watch
- apiGroups :
- ""
resources :
- serviceaccounts
verbs :
- create
- apiGroups :
- ""
resources :
- persistentvolumes
verbs :
- get
- list
- update
- watch
- apiGroups :
- ""
resources :
- endpoints
verbs :
- create
- get
- list
- watch
- update
接下来 云控制器管理器的管理
给出了运行和管理云控制器管理器的指南。
要升级 HA 控制平面以使用云控制器管理器,
请参见将复制的控制平面迁移以使用云控制器管理器 。
想要了解如何实现自己的云控制器管理器,或者对现有项目进行扩展么?
云控制器管理器使用 Go 语言的接口,从而使得针对各种云平台的具体实现都可以接入。
其中使用了在 kubernetes/cloud-provider
项目中 cloud.go
文件所定义的 CloudProvider
接口。
本文中列举的共享控制器(节点控制器、路由控制器和服务控制器等)的实现以及其他一些生成具有
CloudProvider 接口的框架的代码,都是 Kubernetes 的核心代码。
特定于云驱动的实现虽不是 Kubernetes 核心成分,仍要实现 CloudProvider
接口。
关于如何开发插件的详细信息,
可参考开发云控制器管理器 文档。
3.2.5 - 关于 cgroup v2 在 Linux 上,控制组 约束分配给进程的资源。
kubelet 和底层容器运行时都需要对接 cgroup
来强制执行为 Pod 和容器管理资源 ,
这包括为容器化工作负载配置 CPU/内存请求和限制。
Linux 中有两个 cgroup 版本:cgroup v1 和 cgroup v2。cgroup v2 是新一代的 cgroup
API。
什么是 cgroup v2? 特性状态: Kubernetes v1.25 [stable]
cgroup v2 是 Linux cgroup
API 的下一个版本。cgroup v2 提供了一个具有增强资源管理能力的统一控制系统。
cgroup v2 对 cgroup v1 进行了多项改进,例如:
一些 Kubernetes 特性专门使用 cgroup v2 来增强资源管理和隔离。
例如,MemoryQoS 特性改进了内存 QoS 并依赖于 cgroup v2 原语。
使用 cgroup v2 使用 cgroup v2 的推荐方法是使用一个默认启用 cgroup v2 的 Linux 发行版。
要检查你的发行版是否使用 cgroup v2,请参阅识别 Linux 节点上的 cgroup 版本 。
要求 cgroup v2 具有以下要求:
操作系统发行版启用 cgroup v2 Linux 内核为 5.8 或更高版本 容器运行时支持 cgroup v2。例如: kubelet 和容器运行时被配置为使用
systemd cgroup 驱动 Linux 发行版 cgroup v2 支持 有关使用 cgroup v2 的 Linux 发行版的列表,
请参阅 cgroup v2 文档 。
Container-Optimized OS(从 M97 开始) Ubuntu(从 21.10 开始,推荐 22.04+) Debian GNU/Linux(从 Debian 11 Bullseye 开始) Fedora(从 31 开始) Arch Linux(从 2021 年 4 月开始) RHEL 和类似 RHEL 的发行版(从 9 开始) 要检查你的发行版是否使用 cgroup v2,
请参阅你的发行版文档或遵循识别 Linux 节点上的 cgroup 版本 中的指示说明。
你还可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。
如果你的发行版使用 GRUB,则应在 /etc/default/grub
下的 GRUB_CMDLINE_LINUX
中添加 systemd.unified_cgroup_hierarchy=1
,
然后执行 sudo update-grub
。不过,推荐的方法仍是使用一个默认已启用 cgroup v2 的发行版。
迁移到 cgroup v2 要迁移到 cgroup v2,需确保满足要求 ,然后升级到一个默认启用 cgroup v2 的内核版本。
kubelet 能够自动检测操作系统是否运行在 cgroup v2 上并相应调整其操作,无需额外配置。
切换到 cgroup v2 时,用户体验应没有任何明显差异,除非用户直接在节点上或从容器内访问 cgroup 文件系统。
cgroup v2 使用一个与 cgroup v1 不同的 API,因此如果有任何应用直接访问 cgroup 文件系统,
则需要将这些应用更新为支持 cgroup v2 的版本。例如:
一些第三方监控和安全代理可能依赖于 cgroup 文件系统。你要将这些代理更新到支持 cgroup v2 的版本。 如果以独立的 DaemonSet 的形式运行 cAdvisor 以监控 Pod 和容器,
需将其更新到 v0.43.0 或更高版本。 如果你使用 JDK,推荐使用 JDK 11.0.16 及更高版本或 JDK 15 及更高版本,
以便完全支持 cgroup v2 。 识别 Linux 节点上的 cgroup 版本 cgroup 版本取决于正在使用的 Linux 发行版和操作系统上配置的默认 cgroup 版本。
要检查你的发行版使用的是哪个 cgroup 版本,请在该节点上运行 stat -fc %T /sys/fs/cgroup/
命令:
stat -fc %T /sys/fs/cgroup/
对于 cgroup v2,输出为 cgroup2fs
。
对于 cgroup v1,输出为 tmpfs
。
接下来 3.2.6 - 容器运行时接口(CRI) CRI 是一个插件接口,它使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。
你需要在集群中的每个节点上都有一个可以正常工作的容器运行时 ,
这样 kubelet 能启动
Pod 及其容器。
容器运行时接口(CRI)是 kubelet 和容器运行时之间通信的主要协议。
Kubernetes 容器运行时接口(Container Runtime Interface;CRI)定义了主要 gRPC 协议,
用于集群组件
kubelet 和
容器运行时 。
API 特性状态: Kubernetes v1.23 [stable]
当通过 gRPC 连接到容器运行时,kubelet 将充当客户端。运行时和镜像服务端点必须在容器运行时中可用,
可以使用 --image-service-endpoint
和 --container-runtime-endpoint
命令行标志 在 kubelet 中单独配置。
对 Kubernetes v1.25,kubelet 偏向于使用 CRI v1
版本。
如果容器运行时不支持 CRI 的 v1
版本,那么 kubelet 会尝试协商任何旧的其他支持版本。
如果 kubelet 无法协商支持的 CRI 版本,则 kubelet 放弃并且不会注册为节点。
升级 升级 Kubernetes 时,kubelet 会尝试在组件重启时自动选择最新的 CRI 版本。
如果失败,则将如上所述进行回退。如果由于容器运行时已升级而需要 gRPC 重拨,
则容器运行时还必须支持最初选择的版本,否则重拨预计会失败。
这需要重新启动 kubelet。
接下来 3.2.7 - 垃圾收集 垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。
垃圾收集允许系统清理如下资源:
属主与依赖 Kubernetes 中很多对象通过属主引用
链接到彼此。属主引用(Owner Reference)可以告诉控制面哪些对象依赖于其他对象。
Kubernetes 使用属主引用来为控制面以及其他 API 客户端在删除某对象时提供一个清理关联资源的机会。
在大多数场合,Kubernetes 都是自动管理属主引用的。
属主关系与某些资源所使用的标签和选择算符 不同。
例如,考虑一个创建 EndpointSlice
对象的 Service 。
Service 使用标签 来允许控制面确定哪些 EndpointSlice
对象被该 Service 使用。
除了标签,每个被 Service 托管的 EndpointSlice
对象还有一个属主引用属性。
属主引用可以帮助 Kubernetes 中的不同组件避免干预并非由它们控制的对象。
说明: 根据设计,系统不允许出现跨名字空间的属主引用。名字空间作用域的依赖对象可以指定集群作用域或者名字空间作用域的属主。
名字空间作用域的属主必须 存在于依赖对象所在的同一名字空间。
如果属主位于不同名字空间,则属主引用被视为不存在,而当检查发现所有属主都已不存在时,依赖对象会被删除。
集群作用域的依赖对象只能指定集群作用域的属主。
在 1.20 及更高版本中,如果一个集群作用域的依赖对象指定了某个名字空间作用域的类别作为其属主,
则该对象被视为拥有一个无法解析的属主引用,因而无法被垃圾收集处理。
在 1.20 及更高版本中,如果垃圾收集器检测到非法的跨名字空间 ownerReference
,
或者某集群作用域的依赖对象的 ownerReference
引用某名字空间作用域的类别,
系统会生成一个警告事件,其原因为 OwnerRefInvalidNamespace
,involvedObject
设置为非法的依赖对象。你可以通过运行
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
来检查是否存在这类事件。
级联删除 Kubernetes 会检查并删除那些不再拥有属主引用的对象,例如在你删除了 ReplicaSet
之后留下来的 Pod。当你删除某个对象时,你可以控制 Kubernetes 是否去自动删除该对象的依赖对象,
这个过程称为 级联删除(Cascading Deletion) 。
级联删除有两种类型,分别如下:
你也可以使用 Kubernetes Finalizers
来控制垃圾收集机制如何以及何时删除包含属主引用的资源。
前台级联删除 在前台级联删除中,正在被你删除的属主对象首先进入 deletion in progress 状态。
在这种状态下,针对属主对象会发生以下事情:
Kubernetes API 服务器将某对象的 metadata.deletionTimestamp
字段设置为该对象被标记为要删除的时间点。 Kubernetes API 服务器也会将 metadata.finalizers
字段设置为 foregroundDeletion
。 在删除过程完成之前,通过 Kubernetes API 仍然可以看到该对象。 当属主对象进入删除过程中状态后,控制器删除其依赖对象。控制器在删除完所有依赖对象之后,
删除属主对象。这时,通过 Kubernetes API 就无法再看到该对象。
在前台级联删除过程中,唯一可能阻止属主对象被删除的是那些带有
ownerReference.blockOwnerDeletion=true
字段的依赖对象。
参阅使用前台级联删除
以了解进一步的细节。
后台级联删除 在后台级联删除过程中,Kubernetes 服务器立即删除属主对象,控制器在后台清理所有依赖对象。
默认情况下,Kubernetes 使用后台级联删除方案,除非你手动设置了要使用前台删除,
或者选择遗弃依赖对象。
参阅使用后台级联删除
以了解进一步的细节。
被遗弃的依赖对象 当 Kubernetes 删除某个属主对象时,被留下来的依赖对象被称作被遗弃的(Orphaned)对象。
默认情况下,Kubernetes 会删除依赖对象。要了解如何重载这种默认行为,可参阅
删除属主对象和遗弃依赖对象 。
未使用容器和镜像的垃圾收集 kubelet 会每五分钟对未使用的镜像执行一次垃圾收集,
每分钟对未使用的容器执行一次垃圾收集。
你应该避免使用外部的垃圾收集工具,因为外部工具可能会破坏 kubelet
的行为,移除应该保留的容器。
要配置对未使用容器和镜像的垃圾收集选项,可以使用一个
配置文件 ,基于
KubeletConfiguration
资源类型来调整与垃圾收集相关的 kubelet 行为。
容器镜像生命周期 Kubernetes 通过其镜像管理器(Image Manager) 来管理所有镜像的生命周期,
该管理器是 kubelet 的一部分,工作时与
cadvisor 协同。
kubelet 在作出垃圾收集决定时会考虑如下磁盘用量约束:
HighThresholdPercent
LowThresholdPercent
磁盘用量超出所配置的 HighThresholdPercent
值时会触发垃圾收集,
垃圾收集器会基于镜像上次被使用的时间来按顺序删除它们,首先删除的是最近未使用的镜像。
kubelet 会持续删除镜像,直到磁盘用量到达 LowThresholdPercent
值为止。
容器垃圾收集 kubelet 会基于如下变量对所有未使用的容器执行垃圾收集操作,这些变量都是你可以定义的:
MinAge
:kubelet 可以垃圾回收某个容器时该容器的最小年龄。设置为 0
表示禁止使用此规则。MaxPerPodContainer
:每个 Pod 可以包含的已死亡的容器个数上限。设置为小于 0
的值表示禁止使用此规则。MaxContainers
:集群中可以存在的已死亡的容器个数上限。设置为小于 0
的值意味着禁止应用此规则。除以上变量之外,kubelet 还会垃圾收集除无标识的以及已删除的容器,通常从最近未使用的容器开始。
当保持每个 Pod 的最大数量的容器(MaxPerPodContainer
)会使得全局的已死亡容器个数超出上限
(MaxContainers
)时,MaxPerPodContainers
和 MaxContainers
之间可能会出现冲突。
在这种情况下,kubelet 会调整 MaxPerPodContainer
来解决这一冲突。
最坏的情形是将 MaxPerPodContainer
降格为 1
,并驱逐最近未使用的容器。
此外,当隶属于某已被删除的 Pod 的容器的年龄超过 MinAge
时,它们也会被删除。
配置垃圾收集 你可以通过配置特定于管理资源的控制器来调整资源的垃圾收集行为。
下面的页面为你展示如何配置垃圾收集:
接下来 3.3 - 容器 打包应用及其运行依赖环境的技术。
每个运行的容器都是可重复的;
包含依赖环境在内的标准,意味着无论你在哪里运行它都会得到相同的行为。
容器将应用程序从底层的主机设施中解耦。
这使得在不同的云或 OS 环境中部署更加容易。
容器镜像 容器镜像 是一个随时可以运行的软件包,
包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。
根据设计,容器是不可变的:你不能更改已经运行的容器的代码。
如果有一个容器化的应用程序需要修改,则需要构建包含更改的新镜像,然后再基于新构建的镜像重新运行容器。
容器运行时 容器运行环境是负责运行容器的软件。
Kubernetes 支持许多容器运行环境,例如
containerd 、
CRI-O
以及 Kubernetes CRI (容器运行环境接口)
的其他任何实现。
接下来 3.3.1 - 镜像 容器镜像(Image)所承载的是封装了应用程序及其所有软件依赖的二进制数据。
容器镜像是可执行的软件包,可以单独运行;该软件包对所处的运行时环境具有良定(Well Defined)的假定。
你通常会创建应用的容器镜像并将其推送到某仓库(Registry),然后在
Pod 中引用它。
本页概要介绍容器镜像的概念。
镜像名称 容器镜像通常会被赋予 pause
、example/mycontainer
或者 kube-apiserver
这类的名称。
镜像名称也可以包含所在仓库的主机名。例如:fictional.registry.example/imagename
。
还可以包含仓库的端口号,例如:fictional.registry.example:10443/imagename
。
如果你不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。
在镜像名称之后,你可以添加一个标签(Tag)(与使用 docker
或 podman
等命令时的方式相同)。
使用标签能让你辨识同一镜像序列中的不同版本。
镜像标签可以包含小写字母、大写字母、数字、下划线(_
)、句点(.
)和连字符(-
)。
关于在镜像标签中何处可以使用分隔字符(_
、-
和 .
)还有一些额外的规则。
如果你不指定标签,Kubernetes 认为你想使用标签 latest
。
更新镜像 当你最初创建一个 Deployment 、
StatefulSet 、Pod
或者其他包含 Pod 模板的对象时,如果没有显式设定的话,
Pod 中所有容器的默认镜像拉取策略是 IfNotPresent
。这一策略会使得
kubelet
在镜像已经存在的情况下直接略过拉取镜像的操作。
镜像拉取策略 容器的 imagePullPolicy
和镜像的标签会影响
kubelet 尝试拉取(下载)指定的镜像。
以下列表包含了 imagePullPolicy
可以设置的值,以及这些值的效果:
IfNotPresent
只有当镜像在本地不存在时才会拉取。 Always
每当 kubelet 启动一个容器时,kubelet 会查询容器的镜像仓库,
将名称解析为一个镜像摘要 。
如果 kubelet 有一个容器镜像,并且对应的摘要已在本地缓存,kubelet 就会使用其缓存的镜像;
否则,kubelet 就会使用解析后的摘要拉取镜像,并使用该镜像来启动容器。 Never
Kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地,
kubelet 会尝试启动容器;否则,会启动失败。
更多细节见提前拉取镜像 。 只要能够可靠地访问镜像仓库,底层镜像提供者的缓存语义甚至可以使 imagePullPolicy: Always
高效。
你的容器运行时可以注意到节点上已经存在的镜像层,这样就不需要再次下载。
说明: 在生产环境中部署容器时,你应该避免使用 :latest
标签,因为这使得正在运行的镜像的版本难以追踪,并且难以正确地回滚。
相反,应指定一个有意义的标签,如 v1.42.0
。
为了确保 Pod 总是使用相同版本的容器镜像,你可以指定镜像的摘要;
将 <image-name>:<tag>
替换为 <image-name>@<digest>
,例如 image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
。
当使用镜像标签时,如果镜像仓库修改了代码所对应的镜像标签,可能会出现新旧代码混杂在 Pod 中运行的情况。
镜像摘要唯一标识了镜像的特定版本,因此 Kubernetes 每次启动具有指定镜像名称和摘要的容器时,都会运行相同的代码。
通过摘要指定镜像可固定你运行的代码,这样镜像仓库的变化就不会导致版本的混杂。
有一些第三方的准入控制器
在创建 Pod(和 Pod 模板)时产生变更,这样运行的工作负载就是根据镜像摘要,而不是标签来定义的。
无论镜像仓库上的标签发生什么变化,你都想确保你所有的工作负载都运行相同的代码,那么指定镜像摘要会很有用。
默认镜像拉取策略 当你(或控制器)向 API 服务器提交一个新的 Pod 时,你的集群会在满足特定条件时设置 imagePullPolicy
字段:
如果你省略了 imagePullPolicy
字段,并且容器镜像的标签是 :latest
,
imagePullPolicy
会自动设置为 Always
。 如果你省略了 imagePullPolicy
字段,并且没有指定容器镜像的标签,
imagePullPolicy
会自动设置为 Always
。 如果你省略了 imagePullPolicy
字段,并且为容器镜像指定了非 :latest
的标签,
imagePullPolicy
就会自动设置为 IfNotPresent
。 说明: 容器的 imagePullPolicy
的值总是在对象初次 创建 时设置的,如果后来镜像的标签发生变化,则不会更新。
例如,如果你用一个 非 :latest
的镜像标签创建一个 Deployment,
并在随后更新该 Deployment 的镜像标签为 :latest
,则 imagePullPolicy
字段 不会 变成 Always
。
你必须手动更改已经创建的资源的拉取策略。
必要的镜像拉取 如果你想总是强制执行拉取,你可以使用下述的一中方式:
设置容器的 imagePullPolicy
为 Always
。 省略 imagePullPolicy
,并使用 :latest
作为镜像标签;
当你提交 Pod 时,Kubernetes 会将策略设置为 Always
。 省略 imagePullPolicy
和镜像的标签;
当你提交 Pod 时,Kubernetes 会将策略设置为 Always
。 启用准入控制器 AlwaysPullImages 。 ImagePullBackOff 当 kubelet 使用容器运行时创建 Pod 时,容器可能因为 ImagePullBackOff
导致状态为
Waiting 。
ImagePullBackOff
状态意味着容器无法启动,
因为 Kubernetes 无法拉取容器镜像(原因包括无效的镜像名称,或从私有仓库拉取而没有 imagePullSecret
)。
BackOff
部分表示 Kubernetes 将继续尝试拉取镜像,并增加回退延迟。
Kubernetes 会增加每次尝试之间的延迟,直到达到编译限制,即 300 秒(5 分钟)。
带镜像索引的多架构镜像 除了提供二进制的镜像之外,
容器仓库也可以提供容器镜像索引 。
镜像索引可以指向镜像的多个镜像清单 ,
提供特定于体系结构版本的容器。
这背后的理念是让你可以为镜像命名(例如:pause
、example/mycontainer
、kube-apiserver
)
的同时,允许不同的系统基于它们所使用的机器体系结构取回正确的二进制镜像。
Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)
。
为了向前兼容,请在生成较老的镜像时也提供后缀。
这里的理念是为某镜像(如 pause
)生成针对所有平台都适用的清单时,
生成 pause-amd64
这类镜像,以便较老的配置文件或者将镜像后缀硬编码到其中的
YAML 文件也能兼容。
使用私有仓库 从私有仓库读取镜像时可能需要密钥。
凭证可以用以下方式提供:
配置节点向私有仓库进行身份验证所有 Pod 均可读取任何已配置的私有仓库 需要集群管理员配置节点 预拉镜像所有 Pod 都可以使用节点上缓存的所有镜像 需要所有节点的 root 访问权限才能进行设置 在 Pod 中设置 ImagePullSecrets 特定于厂商的扩展或者本地扩展如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点向容器仓库认证的机制 下面将详细描述每一项。
配置 Node 对私有仓库认证 {configuring-nodes-to-authenticate-to-a-private-registry} 设置凭据的具体说明取决于你选择使用的容器运行时和仓库。
你应该参考解决方案的文档来获取最准确的信息。
说明: Kubernetes 默认仅支持 Docker 配置中的 auths
和 HttpHeaders
部分,
不支持 Docker 凭据辅助程序(credHelpers
或 credsStore
)。
有关配置私有容器镜像仓库的示例,
请参阅任务从私有镜像库中拉取镜像 。
该示例使用 Docker Hub 中的私有注册表。
config.json 说明 对于 config.json
的解释在原始 Docker 实现和 Kubernetes 的解释之间有所不同。
在 Docker 中,auths
键只能指定根 URL,而 Kubernetes 允许 glob URLs 以及前缀匹配的路径。
这意味着,像这样的 config.json
是有效的:
{
"auths" : {
"*my-registry.io/images" : {
"auth" : "…"
}
}
}
使用以下语法匹配根 URL (*my-registry.io
):
pattern:
{ term }
term:
'*' 匹配任何无分隔符字符序列
'?' 匹配任意单个非分隔符
'[' [ '^' ] 字符范围
字符集(必须非空)
c 匹配字符 c (c 不为 '*', '?', '\\', '[')
'\\' c 匹配字符 c
字符范围:
c 匹配字符 c (c 不为 '\\', '?', '-', ']')
'\\' c 匹配字符 c
lo '-' hi 匹配字符范围在 lo 到 hi 之间字符
现在镜像拉取操作会将每种有效模式的凭据都传递给 CRI 容器运行时。例如下面的容器镜像名称会匹配成功:
my-registry.io/images
my-registry.io/images/my-image
my-registry.io/images/another-image
sub.my-registry.io/images/my-image
a.sub.my-registry.io/images/my-image
kubelet 为每个找到的凭证的镜像按顺序拉取。这意味着在 config.json
中可能有多项:
{
"auths" : {
"my-registry.io/images" : {
"auth" : "…"
},
"my-registry.io/images/subpath" : {
"auth" : "…"
}
}
}
如果一个容器指定了要拉取的镜像 my-registry.io/images/subpath/my-image
,
并且其中一个失败,kubelet 将尝试从另一个身份验证源下载镜像。
提前拉取镜像 说明: 该方法适用于你能够控制节点配置的场合。
如果你的云供应商负责管理节点并自动置换节点,这一方案无法可靠地工作。
默认情况下,kubelet
会尝试从指定的仓库拉取每个镜像。
但是,如果容器属性 imagePullPolicy
设置为 IfNotPresent
或者 Never
,
则会优先使用(对应 IfNotPresent
)或者一定使用(对应 Never
)本地镜像。
如果你希望使用提前拉取镜像的方法代替仓库认证,就必须保证集群中所有节点提前拉取的镜像是相同的。
这一方案可以用来提前载入指定的镜像以提高速度,或者作为向私有仓库执行身份认证的一种替代方案。
所有的 Pod 都可以使用节点上提前拉取的镜像。
在 Pod 上指定 ImagePullSecrets 说明: 运行使用私有仓库中镜像的容器时,建议使用这种方法。
Kubernetes 支持在 Pod 中设置容器镜像仓库的密钥。
imagePullSecrets
必须全部与 Pod 位于同一个名字空间中。
引用的 Secret 必须是 kubernetes.io/dockercfg
或 kubernetes.io/dockerconfigjson
类型。
使用 Docker Config 创建 Secret 你需要知道用于向仓库进行身份验证的用户名、密码和客户端电子邮件地址,以及它的主机名。
运行以下命令,注意替换适当的大写值:
kubectl create secret docker-registry <name> --docker-server= DOCKER_REGISTRY_SERVER --docker-username= DOCKER_USER --docker-password= DOCKER_PASSWORD --docker-email= DOCKER_EMAIL
如果你已经有 Docker 凭据文件,则可以将凭据文件导入为 Kubernetes
Secret ,
而不是执行上面的命令。
基于已有的 Docker 凭据创建 Secret
解释了如何完成这一操作。
如果你在使用多个私有容器仓库,这种技术将特别有用。
原因是 kubectl create secret docker-registry
创建的是仅适用于某个私有仓库的 Secret。
说明: Pod 只能引用位于自身所在名字空间中的 Secret,因此需要针对每个名字空间重复执行上述过程。
在 Pod 中引用 ImagePullSecrets {referring-to-an-imagepullsecrets-on-a-pod} 现在,在创建 Pod 时,可以在 Pod 定义中增加 imagePullSecrets
部分来引用该 Secret。
imagePullSecrets
数组中的每一项只能引用同一名字空间中的 Secret。
例如:
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
你需要对使用私有仓库的每个 Pod 执行以上操作。不过,
设置该字段的过程也可以通过为服务账号 资源设置
imagePullSecrets
来自动完成。
有关详细指令,
可参见将 ImagePullSecrets 添加到服务账号 。
你也可以将此方法与节点级别的 .docker/config.json
配置结合使用。
来自不同来源的凭据会被合并。
使用案例 配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。
集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。使用来自公共仓库的公共镜像无需配置 某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间 集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见使用托管的私有仓库 或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限 使用控制镜像访问的托管容器镜像仓库服务与手动配置节点相比,这种方案能更好地处理集群自动扩缩容 或者,在不方便更改节点配置的集群中,使用 imagePullSecrets
集群使用专有镜像,且有些镜像需要更严格的访问控制 集群是多租户的并且每个租户需要自己的私有仓库确保 AlwaysPullImages 准入控制器 。否则,所有租户的所有的 Pod 都可以使用所有镜像。 为私有仓库启用鉴权 为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secret 发布到各租户的名字空间下。 租户将 Secret 添加到每个名字空间中的 imagePullSecrets 如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。
kubelet
将所有 imagePullSecrets
合并为一个虚拟的 .docker/config.json
文件。
接下来 3.3.2 - 容器环境 本页描述了在容器环境里容器可用的资源。
容器环境 Kubernetes 的容器环境给容器提供了几个重要的资源:
文件系统,其中包含一个镜像
和一个或多个的卷 容器自身的信息 集群中其他对象的信息 容器信息 一个容器的 hostname 是该容器运行所在的 Pod 的名称。通过 hostname
命令或者调用 libc 中的
gethostname
函数可以获取该名称。
Pod 名称和命名空间可以通过
下行 API
转换为环境变量。
Pod 定义中的用户所定义的环境变量也可在容器中使用,就像在 container 镜像中静态指定的任何环境变量一样。
集群信息 创建容器时正在运行的所有服务都可用作该容器的环境变量。
这里的服务仅限于新容器的 Pod 所在的名字空间中的服务,以及 Kubernetes 控制面的服务。
对于名为 foo 的服务,当映射到名为 bar 的容器时,定义了以下变量:
FOO_SERVICE_HOST = <其上服务正运行的主机>
FOO_SERVICE_PORT = <其上服务正运行的端口>
服务具有专用的 IP 地址。如果启用了
DNS 插件 ,
可以在容器中通过 DNS 来访问服务。
接下来 3.3.3 - 容器运行时类(Runtime Class) 特性状态: Kubernetes v1.20 [stable]
本页面描述了 RuntimeClass 资源和运行时的选择机制。
RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用于运行 Pod 中的容器。
动机 你可以在不同的 Pod 设置不同的 RuntimeClass,以提供性能与安全性之间的平衡。
例如,如果你的部分工作负载需要高级别的信息安全保证,你可以决定在调度这些 Pod
时尽量使它们在使用硬件虚拟化的容器运行时中运行。
这样,你将从这些不同运行时所提供的额外隔离中获益,代价是一些额外的开销。
你还可以使用 RuntimeClass 运行具有相同容器运行时但具有不同设置的 Pod。
设置 在节点上配置 CRI 的实现(取决于所选用的运行时) 创建相应的 RuntimeClass 资源 1. 在节点上配置 CRI 实现 RuntimeClass 的配置依赖于 运行时接口(CRI)的实现。
根据你使用的 CRI 实现,查阅相关的文档(下方 )来了解如何配置。
说明: RuntimeClass 假设集群中的节点配置是同构的(换言之,所有的节点在容器运行时方面的配置是相同的)。
如果需要支持异构节点,配置方法请参阅下面的 调度 。
所有这些配置都具有相应的 handler
名,并被 RuntimeClass 引用。
handler 必须是有效的 DNS 标签名 。
2. 创建相应的 RuntimeClass 资源 在上面步骤 1 中,每个配置都需要有一个用于标识配置的 handler
。
针对每个 handler 需要创建一个 RuntimeClass 对象。
RuntimeClass 资源当前只有两个重要的字段:RuntimeClass 名 (metadata.name
) 和 handler (handler
)。
对象定义如下所示:
# RuntimeClass 定义于 node.k8s.io API 组
apiVersion : node.k8s.io/v1
kind : RuntimeClass
metadata :
# 用来引用 RuntimeClass 的名字
# RuntimeClass 是一个集群层面的资源
name : myclass
# 对应的 CRI 配置的名称
handler : myconfiguration
RuntimeClass 对象的名称必须是有效的
DNS 子域名 。
说明: 建议将 RuntimeClass 写操作(create、update、patch 和 delete)限定于集群管理员使用。
通常这是默认配置。参阅授权概述 了解更多信息。
使用说明 一旦完成集群中 RuntimeClasses 的配置,
你可以在 Pod spec 中指定 runtimeClassName
来使用它。例如:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
runtimeClassName : myclass
# ...
这一设置会告诉 kubelet 使用所指的 RuntimeClass 来运行该 pod。
如果所指的 RuntimeClass 不存在或者 CRI 无法运行相应的 handler,
那么 pod 将会进入 Failed
终止阶段 。
你可以查看相应的事件 ,
获取执行过程中的错误信息。
如果未指定 runtimeClassName
,则将使用默认的 RuntimeHandler,相当于禁用 RuntimeClass 功能特性。
CRI 配置 关于如何安装 CRI 运行时,请查阅
CRI 安装 。
通过 containerd 的 /etc/containerd/config.toml
配置文件来配置运行时 handler。
handler 需要配置在 runtimes 块中:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.${HANDLER_NAME}]
更详细信息,请查阅 containerd 的配置指南
通过 CRI-O 的 /etc/crio/crio.conf
配置文件来配置运行时 handler。
handler 需要配置在
crio.runtime 表 之下:
[crio.runtime.runtimes.${HANDLER_NAME}]
runtime_path = "${PATH_TO_BINARY}"
更详细信息,请查阅 CRI-O 配置文档 。
调度 特性状态: Kubernetes v1.16 [beta]
通过为 RuntimeClass 指定 scheduling
字段,
你可以通过设置约束,确保运行该 RuntimeClass 的 Pod 被调度到支持该 RuntimeClass 的节点上。
如果未设置 scheduling
,则假定所有节点均支持此 RuntimeClass。
为了确保 pod 会被调度到支持指定运行时的 node 上,每个 node 需要设置一个通用的 label 用于被
runtimeclass.scheduling.nodeSelector
挑选。在 admission 阶段,RuntimeClass 的 nodeSelector 将会与
pod 的 nodeSelector 合并,取二者的交集。如果有冲突,pod 将会被拒绝。
如果 node 需要阻止某些需要特定 RuntimeClass 的 pod,可以在 tolerations
中指定。
与 nodeSelector
一样,tolerations 也在 admission 阶段与 pod 的 tolerations 合并,取二者的并集。
更多有关 node selector 和 tolerations 的配置信息,请查阅
将 Pod 分派到节点 。
Pod 开销 特性状态: Kubernetes v1.24 [stable]
你可以指定与运行 Pod 相关的 开销 资源。声明开销即允许集群(包括调度器)在决策 Pod 和资源时将其考虑在内。
Pod 开销通过 RuntimeClass 的 overhead
字段定义。
通过使用这个字段,你可以指定使用该 RuntimeClass 运行 Pod 时的开销并确保 Kubernetes 将这些开销计算在内。
接下来 3.3.4 - 容器生命周期回调 这个页面描述了 kubelet 管理的容器如何使用容器生命周期回调框架,
藉由其管理生命周期中的事件触发,运行指定代码。
概述 类似于许多具有生命周期回调组件的编程语言框架,例如 Angular、Kubernetes 为容器提供了生命周期回调。
回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时运行在处理程序中实现的代码。
容器回调 有两个回调暴露给容器:
PostStart
这个回调在容器被创建之后立即被执行。
但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。
没有参数传递给处理程序。
PreStop
在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等)
而被终止之前,此回调会被调用。
如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。
在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。
Pod 的终止宽限周期在 PreStop
回调被执行之前即开始计数,
所以无论回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。
没有参数会被传递给处理程序。
有关终止行为的更详细描述,请参见
终止 Pod 。
回调处理程序的实现 容器可以通过实现和注册该回调的处理程序来访问该回调。
针对容器,有两种类型的回调处理程序可供实现:
Exec - 在容器的 cgroups 和名字空间中执行特定的命令(例如 pre-stop.sh
)。
命令所消耗的资源计入容器的资源消耗。 HTTP - 对容器上的特定端点执行 HTTP 请求。 回调处理程序执行 当调用容器生命周期管理回调时,Kubernetes 管理系统根据回调动作执行其处理程序,
httpGet
和 tcpSocket
在 kubelet 进程执行,而 exec
则由容器内执行。
回调处理程序调用在包含容器的 Pod 上下文中是同步的。
这意味着对于 PostStart
回调,容器入口点和回调异步触发。
但是,如果回调运行或挂起的时间太长,则容器无法达到 running
状态。
PreStop
回调并不会与停止容器的信号处理程序异步执行;回调必须在可以发送信号之前完成执行。
如果 PreStop
回调在执行期间停滞不前,Pod 的阶段会变成 Terminating
并且一直处于该状态,
直到其 terminationGracePeriodSeconds
耗尽为止,这时 Pod 会被杀死。
这一宽限期是针对 PreStop
回调的执行时间及容器正常停止时间的总和而言的。
例如,如果 terminationGracePeriodSeconds
是 60,回调函数花了 55 秒钟完成执行,
而容器在收到信号之后花了 10 秒钟来正常结束,那么容器会在其能够正常结束之前即被杀死,
因为 terminationGracePeriodSeconds
的值小于后面两件事情所花费的总时间(55+10)。
如果 PostStart
或 PreStop
回调失败,它会杀死容器。
用户应该使他们的回调处理程序尽可能的轻量级。
但也需要考虑长时间运行的命令也很有用的情况,比如在停止容器之前保存状态。
回调递送保证 回调的递送应该是至少一次 ,这意味着对于任何给定的事件,
例如 PostStart
或 PreStop
,回调可以被调用多次。
如何正确处理被多次调用的情况,是回调实现所要考虑的问题。
通常情况下,只会进行单次递送。
例如,如果 HTTP 回调接收器宕机,无法接收流量,则不会尝试重新发送。
然而,偶尔也会发生重复递送的可能。
例如,如果 kubelet 在发送回调的过程中重新启动,回调可能会在 kubelet 恢复后重新发送。
调试回调处理程序 回调处理程序的日志不会在 Pod 事件中公开。
如果处理程序由于某种原因失败,它将播放一个事件。
对于 PostStart
,这是 FailedPostStartHook
事件,对于 PreStop
,这是 FailedPreStopHook
事件。
要自己生成失败的 FailedPostStartHook
事件,请修改
lifecycle-events.yaml
文件将 postStart 命令更改为 “badcommand” 并应用它。
以下是通过运行 kubectl describe pod lifecycle-demo
后你看到的一些结果事件的示例输出:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 7s default-scheduler Successfully assigned default/lifecycle-demo to ip-XXX-XXX-XX-XX.us-east-2...
Normal Pulled 6s kubelet Successfully pulled image "nginx" in 229.604315ms
Normal Pulling 4s (x2 over 6s) kubelet Pulling image "nginx"
Normal Created 4s (x2 over 5s) kubelet Created container lifecycle-demo-container
Normal Started 4s (x2 over 5s) kubelet Started container lifecycle-demo-container
Warning FailedPostStartHook 4s (x2 over 5s) kubelet Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
Normal Killing 4s (x2 over 5s) kubelet FailedPostStartHook
Normal Pulled 4s kubelet Successfully pulled image "nginx" in 215.66395ms
Warning BackOff 2s (x2 over 3s) kubelet Back-off restarting failed container
接下来
3.4 - Kubernetes 中的 Windows 3.4.1 - Kubernetes 中的 Windows 容器 在许多组织中,所运行的很大一部分服务和应用是 Windows 应用。
Windows 容器 提供了一种封装进程和包依赖项的方式,
从而简化了 DevOps 实践,令 Windows 应用程序同样遵从云原生模式。
对于同时投入基于 Windows 应用和 Linux 应用的组织而言,他们不必寻找不同的编排系统来管理其工作负载,
使其跨部署的运营效率得以大幅提升,而不必关心所用的操作系统。
Kubernetes 中的 Windows 节点 若要在 Kubernetes 中启用对 Windows 容器的编排,可以在现有的 Linux 集群中包含 Windows 节点。
在 Kubernetes 上调度 Pod 中的 Windows 容器与调度基于 Linux 的容器类似。
为了运行 Windows 容器,你的 Kubernetes 集群必须包含多个操作系统。
尽管你只能在 Linux 上运行控制平面 ,
你可以部署运行 Windows 或 Linux 的工作节点。
支持 Windows 节点 的前提是操作系统为 Windows Server 2019。
本文使用术语 Windows 容器 表示具有进程隔离能力的 Windows 容器。
Kubernetes 不支持使用
Hyper-V 隔离能力 来运行
Windows 容器。
兼容性与局限性 某些节点层面的功能特性仅在使用特定容器运行时 时才可用;
另外一些特性则在 Windows 节点上不可用,包括:
巨页(HugePages):Windows 容器当前不支持。 特权容器:Windows 容器当前不支持。
HostProcess 容器 提供类似功能。 TerminationGracePeriod:需要 containerD。 Windows 节点并不支持共享命名空间的所有功能特性。
有关更多详细信息,请参考 API 兼容性 。
有关 Kubernetes 测试时所使用的 Windows 版本的详细信息,请参考 Windows 操作系统版本兼容性 。
从 API 和 kubectl 的角度来看,Windows 容器的行为与基于 Linux 的容器非常相似。
然而,在本节所概述的一些关键功能上,二者存在一些显著差异。
与 Linux 比较 Kubernetes 关键组件在 Windows 上的工作方式与在 Linux 上相同。
本节介绍几个关键的工作负载抽象及其如何映射到 Windows。
Pod、工作负载资源和 Service 是在 Kubernetes 上管理 Windows 工作负载的关键元素。
然而,它们本身还不足以在动态的云原生环境中对 Windows 工作负载进行恰当的生命周期管理。
Kubernetes 还支持:
kubelet 的命令行选项 某些 kubelet 命令行选项在 Windows 上的行为不同,如下所述:
--windows-priorityclass
允许你设置 kubelet 进程的调度优先级
(参考 CPU 资源管理 )。--kube-reserved
、--system-reserved
和 --eviction-hard
标志更新
NodeAllocatable 。未实现使用 --enforce-node-allocable
驱逐。 未实现使用 --eviction-hard
和 --eviction-soft
驱逐。 在 Windows 节点上运行时,kubelet 没有内存或 CPU 限制。
--kube-reserved
和 --system-reserved
仅从 NodeAllocatable
中减去,并且不保证为工作负载提供的资源。
有关更多信息,请参考 Windows 节点的资源管理 。 未实现 MemoryPressure
条件。 kubelet 不会执行 OOM 驱逐操作。 API 兼容性 由于操作系统和容器运行时的缘故,Kubernetes API 在 Windows 上的工作方式存在细微差异。
某些工作负载属性是为 Linux 设计的,无法在 Windows 上运行。
从较高的层面来看,以下操作系统概念是不同的:
身份 - Linux 使用 userID(UID)和 groupID(GID),表示为整数类型。
用户名和组名是不规范的,它们只是 /etc/groups
或 /etc/passwd
中的别名,
作为 UID+GID 的后备标识。
Windows 使用更大的二进制安全标识符 (SID),
存放在 Windows 安全访问管理器(Security Access Manager,SAM)数据库中。
此数据库在主机和容器之间或容器之间不共享。 文件权限 - Windows 使用基于 SID 的访问控制列表,
而像 Linux 使用基于对象权限和 UID+GID 的位掩码(POSIX 系统)以及可选的 访问控制列表。 文件路径 - Windows 上的约定是使用 \
而不是 /
。
Go IO 库通常接受两者,能让其正常工作,但当你设置要在容器内解读的路径或命令行时,
可能需要用 \
。 信号 - Windows 交互式应用处理终止的方式不同,可以实现以下一种或多种:UI 线程处理包括 WM_CLOSE
在内准确定义的消息。 控制台应用使用控制处理程序(Control Handler)处理 Ctrl-C 或 Ctrl-Break。 服务会注册可接受 SERVICE_CONTROL_STOP
控制码的服务控制处理程序(Service Control Handler)函数。 容器退出码遵循相同的约定,其中 0 表示成功,非零表示失败。
具体的错误码在 Windows 和 Linux 中可能不同。
但是,从 Kubernetes 组件(kubelet、kube-proxy)传递的退出码保持不变。
容器规范的字段兼容性 以下列表记录了 Pod 容器规范在 Windows 和 Linux 之间的工作方式差异:
巨页(Huge page)在 Windows 容器运行时中未实现,且不可用。
巨页需要不可为容器配置的用户特权生效 。 requests.cpu
和 requests.memory
-
从节点可用资源中减去请求,因此请求可用于避免一个节点过量供应。
但是,请求不能用于保证已过量供应的节点中的资源。
如果运营商想要完全避免过量供应,则应将设置请求作为最佳实践应用到所有容器。securityContext.allowPrivilegeEscalation
-
不能在 Windows 上使用;所有权能字都无法生效。securityContext.capabilities
- POSIX 权能未在 Windows 上实现。securityContext.privileged
- Windows 不支持特权容器,
可使用 HostProcess 容器 代替。securityContext.procMount
- Windows 没有 /proc
文件系统。securityContext.readOnlyRootFilesystem
-
不能在 Windows 上使用;对于容器内运行的注册表和系统进程,写入权限是必需的。securityContext.runAsGroup
- 不能在 Windows 上使用,因为不支持 GID。securityContext.runAsNonRoot
-
此设置将阻止以 ContainerAdministrator
身份运行容器,这是 Windows 上与 root 用户最接近的身份。securityContext.runAsUser
- 改用 runAsUserName
。securityContext.seLinuxOptions
- 不能在 Windows 上使用,因为 SELinux 特定于 Linux。terminationMessagePath
- 这个字段有一些限制,因为 Windows 不支持映射单个文件。
默认值为 /dev/termination-log
,因为默认情况下它在 Windows 上不存在,所以能生效。Pod 规范的字段兼容性 以下列表记录了 Pod 规范在 Windows 和 Linux 之间的工作方式差异:
hostIPC
和 hostpid
- 不能在 Windows 上共享主机命名空间。hostNetwork
- Windows 操作系统不支持共享主机网络。dnsPolicy
- Windows 不支持将 Pod dnsPolicy
设为 ClusterFirstWithHostNet
,
因为未提供主机网络。Pod 始终用容器网络运行。podSecurityContext
(参见下文)shareProcessNamespace
- 这是一个 beta 版功能特性,依赖于 Windows 上未实现的 Linux 命名空间。
Windows 无法共享进程命名空间或容器的根文件系统(root filesystem)。
只能共享网络。terminationGracePeriodSeconds
- 这在 Windows 上的 Docker 中没有完全实现,
请参考 GitHub issue 。
目前的行为是通过 CTRL_SHUTDOWN_EVENT 发送 ENTRYPOINT 进程,然后 Windows 默认等待 5 秒,
最后使用正常的 Windows 关机行为终止所有进程。
5 秒默认值实际上位于容器内 的 Windows 注册表中,
因此在构建容器时可以覆盖这个值。volumeDevices
- 这是一个 beta 版功能特性,未在 Windows 上实现。
Windows 无法将原始块设备挂接到 Pod。volumes
如果你定义一个 emptyDir
卷,则你无法将卷源设为 memory
。 你无法为卷挂载启用 mountPropagation
,因为这在 Windows 上不支持。 Pod 安全上下文的字段兼容性 Pod 的所有 securityContext
字段都无法在 Windows 上生效。
节点问题检测器 节点问题检测器(参考节点健康监测 )初步支持 Windows。
有关更多信息,请访问该项目的 GitHub 页面 。
Pause 容器 在 Kubernetes Pod 中,首先创建一个基础容器或 “pause” 容器来承载容器。
在 Linux 中,构成 Pod 的 cgroup 和命名空间维持持续存在需要一个进程;
而 pause 进程就提供了这个功能。
属于同一 Pod 的容器(包括基础容器和工作容器)共享一个公共网络端点
(相同的 IPv4 和/或 IPv6 地址,相同的网络端口空间)。
Kubernetes 使用 pause 容器以允许工作容器崩溃或重启,而不会丢失任何网络配置。
Kubernetes 维护一个多体系结构的镜像,包括对 Windows 的支持。
对于 Kubernetes v1.25,推荐的 pause 镜像为 registry.k8s.io/pause:3.6
。
可在 GitHub 上获得源代码 。
Microsoft 维护一个不同的多体系结构镜像,支持 Linux 和 Windows amd64,
你可以找到的镜像类似 mcr.microsoft.com/oss/kubernetes/pause:3.6
。
此镜像的构建与 Kubernetes 维护的镜像同源,但所有 Windows 可执行文件均由
Microsoft 进行了验证码签名 。
如果你正部署到一个需要签名可执行文件的生产或类生产环境,
Kubernetes 项目建议使用 Microsoft 维护的镜像。
容器运行时 你需要将容器运行时 安装到集群中的每个节点,
这样 Pod 才能在这些节点上运行。
以下容器运行时适用于 Windows:
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
ContainerD 特性状态: Kubernetes v1.20 [stable]
对于运行 Windows 的 Kubernetes 节点,你可以使用
ContainerD 1.4.0+ 作为容器运行时。
学习如何在 Windows 上安装 ContainerD 。
说明: 将 GMSA 和 containerd 一起用于访问 Windows
网络共享时存在
已知限制 ,
这需要一个内核补丁。
Mirantis 容器运行时 Mirantis 容器运行时 (MCR)
可作为所有 Windows Server 2019 和更高版本的容器运行时。
有关更多信息,请参考在 Windows Server 上安装 MCR 。
Windows 操作系统版本兼容性 在 Windows 节点上,如果主机操作系统版本必须与容器基础镜像操作系统版本匹配,
则会应用严格的兼容性规则。
仅 Windows Server 2019 作为容器操作系统时,才能完全支持 Windows 容器。
对于 Kubernetes v1.25,Windows 节点(和 Pod)的操作系统兼容性如下:
Windows Server LTSC release Windows Server 2019 Windows Server 2022 Windows Server SAC release Windows Server version 20H2 也适用 Kubernetes 版本偏差策略 。
获取帮助和故障排查 对 Kubernetes 集群进行故障排查的主要帮助来源应始于故障排查 页面。
本节包括了一些其他特定于 Windows 的故障排查帮助。
日志是解决 Kubernetes 中问题的重要元素。
确保在任何时候向其他贡献者寻求故障排查协助时随附了日志信息。
遵照 SIG Windows
日志收集贡献指南 中的指示说明。
报告问题和功能请求 如果你发现疑似 bug,或者你想提出功能请求,请按照
SIG Windows 贡献指南
新建一个 Issue。
你应该先搜索 issue 列表,以防之前报告过这个问题,凭你对该问题的经验添加评论,
并随附日志信息。
Kubernetes Slack 上的 SIG Windows 频道也是一个很好的途径,
可以在创建工单之前获得一些初始支持和故障排查思路。
接下来 kubeadm 工具帮助你部署 Kubernetes 集群,提供管理集群的控制平面以及运行工作负载的节点。
添加 Windows 节点 阐述了如何使用
kubeadm 将 Windows 节点部署到你的集群。
Kubernetes 集群 API 项目也提供了自动部署 Windows 节点的方式。
Windows 分发渠道 有关 Windows 分发渠道的详细阐述,请参考
Microsoft 文档 。
有关支持模型在内的不同 Windows Server 服务渠道的信息,请参考
Windows Server 服务渠道 。
3.4.2 - Kubernetes 中的 Windows 容器调度指南 在许多组织中运行的服务和应用程序中,Windows 应用程序构成了很大一部分。
本指南将引导你完成在 Kubernetes 中配置和部署 Windows 容器的步骤。
目标 配置 Deployment 样例以在 Windows 节点上运行 Windows 容器 在 Kubernetes 中突出 Windows 特定的功能 在你开始之前 创建一个 Kubernetes 集群,其中包含一个控制平面和一个运行 Windows Server 的工作节点 务必请注意,在 Kubernetes 上创建和部署服务和工作负载的行为方式与 Linux 和 Windows 容器的行为方式大致相同。
与集群交互的 kubectl 命令 是一致的。
下一小节的示例旨在帮助你快速开始使用 Windows 容器。 快速开始:部署 Windows 容器 以下示例 YAML 文件部署了一个在 Windows 容器内运行的简单 Web 服务器的应用程序。
创建一个名为 win-webserver.yaml
的 Service 规约,其内容如下:
apiVersion : v1
kind : Service
metadata :
name : win-webserver
labels :
app : win-webserver
spec :
ports :
# 此 Service 服务的端口
- port : 80
targetPort : 80
selector :
app : win-webserver
type : NodePort
---
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : win-webserver
name : win-webserver
spec :
replicas : 2
selector :
matchLabels :
app : win-webserver
template :
metadata :
labels :
app : win-webserver
name : win-webserver
spec :
containers :
- name : windowswebserver
image : mcr.microsoft.com/windows/servercore:ltsc2019
command :
- powershell.exe
- -command
- "<#code used from https://gist.github.com/19WAS85/5424431#> ; $$listener = New-Object System.Net.HttpListener ; $$listener.Prefixes.Add('http://*:80/') ; $$listener.Start() ; $$callerCounts = @{} ; Write-Host('Listening at http://*:80/') ; while ($$listener.IsListening) { ;$$context = $$listener.GetContext() ;$$requestUrl = $$context.Request.Url ;$$clientIP = $$context.Request.RemoteEndPoint.Address ;$$response = $$context.Response ;Write-Host '' ;Write-Host('> {0}' -f $$requestUrl) ; ;$$count = 1 ;$$k=$$callerCounts.Get_Item($$clientIP) ;if ($$k -ne $$null) { $$count += $$k } ;$$callerCounts.Set_Item($$clientIP, $$count) ;$$ip=(Get-NetAdapter | Get-NetIpAddress); $$header='<html><body><H1>Windows Container Web Server</H1>' ;$$callerCountsString='' ;$$callerCounts.Keys | % { $$callerCountsString+='<p>IP {0} callerCount {1} ' -f $$ip[1].IPAddress,$$callerCounts.Item($$_) } ;$$footer='</body></html>' ;$$content='{0}{1}{2}' -f $$header,$$callerCountsString,$$footer ;Write-Output $$content ;$$buffer = [System.Text.Encoding]::UTF8.GetBytes($$content) ;$$response.ContentLength64 = $$buffer.Length ;$$response.OutputStream.Write($$buffer, 0, $$buffer.Length) ;$$response.Close() ;$$responseStatus = $$response.StatusCode ;Write-Host('< {0}' -f $$responseStatus) } ; "
nodeSelector :
kubernetes.io/os : windows
说明: 端口映射也是支持的,但为简单起见,此示例将容器的端口 80 直接暴露给服务。
检查所有节点是否健康
部署 Service 并监视 Pod 更新:
kubectl apply -f win-webserver.yaml
kubectl get pods -o wide -w
当 Service 被正确部署时,两个 Pod 都被标记为就绪(Ready)。要退出 watch 命令,请按 Ctrl+C。
检查部署是否成功。请验证:
当执行 kubectl get pods
命令时,能够从 Linux 控制平面所在的节点上列出两个 Pod。 跨网络的节点到 Pod 通信,从 Linux 控制平面所在的节点上执行 curl
命令来访问
Pod IP 的 80 端口以检查 Web 服务器响应。 Pod 间通信,使用 docker exec
或 kubectl exec
命令进入容器,并在 Pod 之间(以及跨主机,如果你有多个 Windows 节点)相互进行 ping 操作。 Service 到 Pod 的通信,在 Linux 控制平面所在的节点以及独立的 Pod 中执行 curl
命令来访问虚拟的服务 IP(在 kubectl get services
命令下查看)。 服务发现,执行 curl
命令来访问带有 Kubernetes
默认 DNS 后缀 的服务名称。 入站连接,在 Linux 控制平面所在的节点上或集群外的机器上执行 curl
命令来访问 NodePort 服务。 出站连接,使用 kubectl exec
,从 Pod 内部执行 curl
访问外部 IP。 说明: 由于当前 Windows 平台的网络堆栈限制,Windows 容器主机无法访问调度到其上的 Service 的 IP。
只有 Windows Pod 能够访问 Service IP。
可观察性 捕捉来自工作负载的日志 日志是可观察性的重要元素;它们使用户能够深入了解工作负载的运行情况,并且是解决问题的关键因素。
由于 Windows 容器和 Windows 容器中的工作负载与 Linux 容器的行为不同,因此用户很难收集日志,从而限制了操作可见性。
例如,Windows 工作负载通常配置为记录到 ETW(Windows 事件跟踪)或向应用程序事件日志推送条目。
LogMonitor
是一个微软开源的工具,是监视 Windows 容器内所配置的日志源的推荐方法。
LogMonitor 支持监视事件日志、ETW 提供程序和自定义应用程序日志,将它们传送到 STDOUT 以供 kubectl logs <pod>
使用。
按照 LogMonitor GitHub 页面中的说明,将其二进制文件和配置文件复制到所有容器,
并为 LogMonitor 添加必要的入口点以将日志推送到标准输出(STDOUT)。
配置容器用户 使用可配置的容器用户名 Windows 容器可以配置为使用不同于镜像默认值的用户名来运行其入口点和进程。
在这里 了解更多信息。
使用组托管服务帐户(GMSA)管理工作负载身份 Windows 容器工作负载可以配置为使用组托管服务帐户(Group Managed Service Accounts,GMSA)。
组托管服务帐户是一种特定类型的活动目录(Active Directory)帐户,可提供自动密码管理、
简化的服务主体名称(Service Principal Name,SPN)管理,以及将管理委派给多个服务器上的其他管理员的能力。
配置了 GMSA 的容器可以携带使用 GMSA 配置的身份访问外部活动目录域资源。
在此处 了解有关为 Windows 容器配置和使用 GMSA 的更多信息。
污点和容忍度 用户需要使用某种污点(Taint)和节点选择器的组合,以便将 Linux 和 Windows 工作负载各自调度到特定操作系统的节点。
下面概述了推荐的方法,其主要目标之一是该方法不应破坏现有 Linux 工作负载的兼容性。
从 1.25 开始,你可以(并且应该)将每个 Pod 的 .spec.os.name
设置为 Pod 中的容器设计所用于的操作系统。
对于运行 Linux 容器的 Pod,将 .spec.os.name
设置为 linux
。
对于运行 Windows 容器的 Pod,将 .spec.os.name
设置为 windows
。
说明: 从 1.25 开始,IdentifyPodOS
特性处于 GA 阶段,默认启用。
调度器在将 Pod 分配到节点时并不使用 .spec.os.name
的值。
你应该使用正常的 Kubernetes 机制将 Pod 分配给节点 ,
以确保集群的控制平面将 Pod 放置到运行适当操作系统的节点上。
.spec.os.name
值对 Windows Pod 的调度没有影响,
因此仍然需要污点和容忍以及节点选择器来确保 Windows Pod 落在适当的 Windows 节点。
确保特定于操作系统的工作负载落到合适的容器主机上 用户可以使用污点(Taint)和容忍度(Toleration)确保将 Windows 容器调度至合适的主机上。
现在,所有的 Kubernetes 节点都有以下默认标签:
kubernetes.io/os = [windows|linux] kubernetes.io/arch = [amd64|arm64|...] 如果 Pod 规约没有指定像 "kubernetes.io/os": windows
这样的 nodeSelector,
则 Pod 可以被调度到任何主机上,Windows 或 Linux。
这可能会有问题,因为 Windows 容器只能在 Windows 上运行,而 Linux 容器只能在 Linux 上运行。
最佳实践是使用 nodeSelector。
但是,我们了解到,在许多情况下,用户已经预先存在大量 Linux 容器部署,
以及现成配置的生态系统,例如社区中的 Helm Chart 包和程序化的 Pod 生成案例,例如 Operator。
在这些情况下,你可能不愿更改配置来添加节点选择器。
另一种方法是使用污点。因为 kubelet 可以在注册过程中设置污点,
所以可以很容易地修改为,当只能在 Windows 上运行时,自动添加污点。
例如:--register-with-taints='os=windows:NoSchedule'
通过向所有 Windows 节点添加污点,任何负载都不会被调度到这些节点上(包括现有的 Linux Pod)。
为了在 Windows 节点上调度 Windows Pod,它需要 nodeSelector 和匹配合适的容忍度来选择 Windows。
nodeSelector :
kubernetes.io/os : windows
node.kubernetes.io/windows-build : '10.0.17763'
tolerations :
- key : "os"
operator : "Equal"
value : "windows"
effect : "NoSchedule"
处理同一集群中的多个 Windows 版本 每个 Pod 使用的 Windows Server 版本必须与节点的版本匹配。
如果要在同一个集群中使用多个 Windows Server 版本,则应设置额外的节点标签和节点选择器。
Kubernetes 1.17 自动添加了一个新标签 node.kubernetes.io/windows-build
来简化这一点。
如果你运行的是旧版本,则建议手动将此标签添加到 Windows 节点。
此标签反映了需要匹配以实现兼容性的 Windows 主要、次要和内部版本号。
以下是目前用于每个 Windows Server 版本的值。
产品名称 构建号 Windows Server 2019 10.0.17763 Windows Server, Version 20H2 10.0.19042 Windows Server 2022 10.0.20348
使用 RuntimeClass 进行简化 RuntimeClass 可用于简化使用污点和容忍度的流程。
集群管理员可以创建一个用于封装这些污点和容忍度的 RuntimeClass
对象。
将此文件保存到 runtimeClasses.yml
。它包括针对 Windows 操作系统、架构和版本的 nodeSelector
。 apiVersion : node.k8s.io/v1
kind : RuntimeClass
metadata :
name : windows-2019
handler : 'docker'
scheduling :
nodeSelector :
kubernetes.io/os : 'windows'
kubernetes.io/arch : 'amd64'
node.kubernetes.io/windows-build : '10.0.17763'
tolerations :
- effect : NoSchedule
key : os
operator : Equal
value : "windows"
以集群管理员身份运行 kubectl create -f runtimeClasses.yml
根据情况,向 Pod 规约中添加 runtimeClassName: windows-2019
例如:
apiVersion : apps/v1
kind : Deployment
metadata :
name : iis-2019
labels :
app : iis-2019
spec :
replicas : 1
template :
metadata :
name : iis-2019
labels :
app : iis-2019
spec :
runtimeClassName : windows-2019
containers :
- name : iis
image : mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
resources :
limits :
cpu : 1
memory : 800Mi
requests :
cpu : .1
memory : 300Mi
ports :
- containerPort : 80
selector :
matchLabels :
app : iis-2019
---
apiVersion : v1
kind : Service
metadata :
name : iis
spec :
type : LoadBalancer
ports :
- protocol : TCP
port : 80
selector :
app : iis-2019
3.5 - 工作负载 理解 Pods,Kubernetes 中可部署的最小计算对象,以及辅助它运行它们的高层抽象对象。
工作负载是在 Kubernetes 上运行的应用程序。
在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成,
你都可以在一组 Pod 中运行它。
在 Kubernetes 中,Pod
代表的是集群上处于运行状态的一组
容器 的集合。
Kubernetes Pod 遵循预定义的生命周期 。
例如,当在你的集群中运行了某个 Pod,但是 Pod 所在的
节点 出现致命错误时,
所有该节点上的 Pod 的状态都会变成失败。Kubernetes 将这类失败视为最终状态:
即使该节点后来恢复正常运行,你也需要创建新的 Pod
以恢复应用。
不过,为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod
。
而是使用负载资源 来替用户管理一组 Pod。
这些负载资源通过配置 控制器
来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。
Kubernetes 提供若干种内置的工作负载资源:
DaemonSet
定义提供节点本地支撑设施的 Pod
。这些 Pod 可能对于你的集群的运维是
非常重要的,例如作为网络链接的辅助工具或者作为网络
插件
的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet
的规约匹配,则控制平面会为该 DaemonSet
调度一个 Pod
到该新节点上运行。Job 和
CronJob 。
定义一些一直运行到结束并停止的任务。Job
用来执行一次性任务,而
CronJob
用来执行的根据时间规划反复运行的任务。在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方工作负载相关的资源。
通过使用定制资源定义(CRD) ,
你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。
例如,如果你希望运行一组 Pod
,但要求所有 Pod 都可用时才执行操作
(比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展,
并将其安装到集群中运行。
接下来 除了阅读了解每类资源外,你还可以了解与这些资源相关的任务:
要了解 Kubernetes 将代码与配置分离的实现机制,可参阅配置部分 。
关于 Kubernetes 如何为应用管理 Pod,还有两个支撑概念能够提供相关背景信息:
一旦你的应用处于运行状态,你就可能想要以
Service
的形式使之可在互联网上访问;或者对于 Web 应用而言,使用
Ingress
资源将其暴露到互联网上。
3.5.1 - Pod Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod (就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)
容器 ;
这些容器共享存储、网络、以及怎样运行这些容器的声明。
Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。
Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器,
这些容器相对紧密地耦合在一起。
在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的
Init 容器 。
你也可以在集群支持临时性容器 的情况下,
为调试的目的注入临时性容器。
什么是 Pod? 说明: 除了 Docker 之外,Kubernetes 支持很多其他
容器运行时 ,
Docker 是最有名的运行时,
使用 Docker 的术语来描述 Pod 会很有帮助。
Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离方面,
即用来隔离容器 的技术。
在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
Pod 类似于共享名字空间并共享文件系统卷的一组容器。
使用 Pod 下面是一个 Pod 示例,它由一个运行镜像 nginx:1.14.2
的容器组成。
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
要创建上面显示的 Pod,请运行以下命令:
kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml
Pod 通常不是直接创建的,而是使用工作负载资源创建的。
有关如何将 Pod 用于工作负载资源的更多信息,请参阅使用 Pod 。
用于管理 pod 的工作负载资源 通常你不需要直接创建 Pod,甚至单实例 Pod。
相反,你会使用诸如
Deployment 或
Job 这类工作负载资源来创建 Pod。
如果 Pod 需要跟踪状态,可以考虑
StatefulSet 资源。
Kubernetes 集群中的 Pod 主要有两种用法:
运行单个容器的 Pod 。"每个 Pod 一个容器" 模型是最常见的 Kubernetes 用例;
在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
运行多个协同工作的容器的 Pod 。
Pod 可能封装由多个紧密耦合且需要共享资源的共处容器组成的应用程序。
这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众,
而另一个单独的 “边车”(sidecar)容器则刷新或更新这些文件。
Pod 将这些容器和存储资源打包为一个可管理的实体。
说明: 将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景。
只有在一些场景中,容器之间紧密关联时你才应该使用这种模式。
每个 Pod 都旨在运行给定应用程序的单个实例。如果希望横向扩展应用程序
(例如,运行多个实例以提供更多的资源),则应该使用多个 Pod,每个实例使用一个 Pod。
在 Kubernetes 中,这通常被称为副本(Replication) 。
通常使用一种工作负载资源及其控制器 来创建和管理一组 Pod 副本。
参见 Pod 和控制器 以了解 Kubernetes
如何使用工作负载资源及其控制器以实现应用的扩缩和自动修复。
Pod 怎样管理多个容器 Pod 被设计成支持形成内聚服务单元的多个协作过程(形式为容器)。
Pod 中的容器被自动安排到集群中的同一物理机或虚拟机上,并可以一起进行调度。
容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身。
例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的
"边车 (sidercar)" 容器负责从远端更新这些文件,如下图所示:
有些 Pod 具有 Init 容器 和
应用容器 。
Init 容器会在启动应用容器之前运行并完成。
Pod 天生地为其成员容器提供了两种共享资源:网络 和存储 。
使用 Pod 你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。
这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。
当 Pod 由你或者间接地由控制器
创建时,它被调度在集群中的节点 上运行。
Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被驱逐 或者节点失效为止。
说明: 重启 Pod 中的容器不应与重启 Pod 混淆。
Pod 不是进程,而是容器运行的环境。
在被删除之前,Pod 会一直存在。
当你为 Pod 对象创建清单时,要确保所指定的 Pod 名称是合法的
DNS 子域名 。
Pod 操作系统 特性状态: Kubernetes v1.25 [stable]
你应该将 .spec.os.name
字段设置为 windows
或 linux
以表示你希望 Pod 运行在哪个操作系统之上。
这两个是 Kubernetes 目前支持的操作系统。将来,这个列表可能会被扩充。
在 Kubernetes v1.25 中,为此字段设置的值对 Pod
的调度 没有影响。
设置 . spec.os.name
有助于确定性地标识 Pod 的操作系统并用于验证。
如果你指定的 Pod 操作系统与运行 kubelet 所在节点的操作系统不同,
那么 kubelet 将会拒绝运行该 Pod。
Pod 安全标准 也使用这个字段来避免强制执行与该操作系统无关的策略。
Pod 和控制器 你可以使用工作负载资源来创建和管理多个 Pod。
资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。
例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作,
就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。
下面是一些管理一个或者多个 Pod 的工作负载资源的示例:
Pod 模板 工作负载 资源的控制器通常使用
Pod 模板(Pod Template) 来替你创建 Pod 并管理它们。
Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括
Deployment 、
Job 和
DaemonSet 等。
工作负载的控制器会使用负载对象中的 PodTemplate
来生成实际的 Pod。
PodTemplate
是你用来运行应用时指定的负载资源的目标状态的一部分。
下面的示例是一个简单的 Job 的清单,其中的 template
指示启动一个容器。
该 Pod 中的容器会打印一条消息之后暂停。
apiVersion : batch/v1
kind : Job
metadata :
name : hello
spec :
template :
# 这里是 Pod 模板
spec :
containers :
- name : hello
image : busybox:1.28
command : ['sh' , '-c' , 'echo "Hello, Kubernetes!" && sleep 3600' ]
restartPolicy : OnFailure
# 以上为 Pod 模板
修改 Pod 模板或者切换到新的 Pod 模板都不会对已经存在的 Pod 直接起作用。
如果改变工作负载资源的 Pod 模板,工作负载资源需要使用更新后的模板来创建 Pod,
并使用新创建的 Pod 替换旧的 Pod。
例如,StatefulSet 控制器针对每个 StatefulSet 对象确保运行中的 Pod 与当前的 Pod
模板匹配。如果编辑 StatefulSet 以更改其 Pod 模板,
StatefulSet 将开始基于更新后的模板创建新的 Pod。
每个工作负载资源都实现了自己的规则,用来处理对 Pod 模板的更新。
如果你想了解更多关于 StatefulSet 的具体信息,
请阅读 StatefulSet 基础教程中的更新策略 。
在节点上,kubelet 并不直接监测或管理与
Pod 模板相关的细节或模板的更新,这些细节都被抽象出来。
这种抽象和关注点分离简化了整个系统的语义,
并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为。
Pod 更新与替换 正如前面章节所述,当某工作负载的 Pod 模板被改变时,
控制器会基于更新的模板创建新的 Pod 对象而不是对现有 Pod 执行更新或者修补操作。
Kubernetes 并不禁止你直接管理 Pod。对运行中的 Pod 的某些字段执行就地更新操作还是可能的。不过,类似
patch
和
replace
这类更新操作有一些限制:
Pod 的绝大多数元数据都是不可变的。例如,你不可以改变其 namespace
、name
、
uid
或者 creationTimestamp
字段;generation
字段是比较特别的,
如果更新该字段,只能增加字段取值而不能减少。
如果 metadata.deletionTimestamp
已经被设置,则不可以向 metadata.finalizers
列表中添加新的条目。
Pod 更新不可以改变除 spec.containers[*].image
、spec.initContainers[*].image
、
spec.activeDeadlineSeconds
或 spec.tolerations
之外的字段。
对于 spec.tolerations
,你只被允许添加新的条目到其中。
在更新 spec.activeDeadlineSeconds
字段时,以下两种更新操作是被允许的:
如果该字段尚未设置,可以将其设置为一个正数; 如果该字段已经设置为一个正数,可以将其设置为一个更小的、非负的整数。 资源共享和通信 Pod 使它的成员容器间能够进行数据共享和通信。
Pod 中的存储 一个 Pod 可以设置一组共享的存储卷 。
Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。
卷还允许 Pod 中的持久数据保留下来,即使其中的容器需要重新启动。
有关 Kubernetes 如何在 Pod 中实现共享存储并将其提供给 Pod 的更多信息,
请参考存储 。
Pod 联网 每个 Pod 都在每个地址族中获得一个唯一的 IP 地址。
Pod 中的每个容器共享网络名字空间,包括 IP 地址和网络端口。
Pod 内 的容器可以使用 localhost
互相通信。
当 Pod 中的容器与 Pod 之外 的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)。
在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,并且可以通过 localhost
发现对方。
他们也能通过如 SystemV 信号量或 POSIX 共享内存这类标准的进程间通信方式互相通信。
不同 Pod 中的容器的 IP 地址互不相同,如果没有特殊配置,就无法通过 OS 级 IPC 进行通信。
如果某容器希望与运行于其他 Pod 中的容器通信,可以通过 IP 联网的方式实现。
Pod 中的容器所看到的系统主机名与为 Pod 配置的 name
属性值相同。
网络 部分提供了更多有关此内容的信息。
容器的特权模式 在 Linux 中,Pod 中的任何容器都可以使用容器规约中的
安全性上下文 中的
privileged
(Linux)参数启用特权模式。
这对于想要使用操作系统管理权能(Capabilities,如操纵网络堆栈和访问设备)的容器很有用。
如果你的集群启用了 WindowsHostProcessContainers
特性,你可以使用 Pod 规约中安全上下文的
windowsOptions.hostProcess
参数来创建
Windows HostProcess Pod 。
这些 Pod 中的所有容器都必须以 Windows HostProcess 容器方式运行。
HostProcess Pod 可以直接运行在主机上,它也能像 Linux 特权容器一样,用于执行管理任务。
说明: 你的容器运行时 必须支持特权容器的概念才能使用这一配置。
静态 Pod 静态 Pod(Static Pod) 直接由特定节点上的 kubelet
守护进程管理,
不需要 API 服务器 看到它们。
尽管大多数 Pod 都是通过控制面(例如,Deployment )
来管理的,对于静态 Pod 而言,kubelet
直接监控每个 Pod,并在其失效时重启之。
静态 Pod 通常绑定到某个节点上的 kubelet 。
其主要用途是运行自托管的控制面。
在自托管场景中,使用 kubelet
来管理各个独立的控制面组件 。
kubelet
自动尝试为每个静态 Pod 在 Kubernetes API
服务器上创建一个镜像 Pod 。
这意味着在节点上运行的 Pod 在 API 服务器上是可见的,但不可以通过 API 服务器来控制。
容器探针 Probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 可以执行三种动作:
ExecAction
(借助容器运行时执行)TCPSocketAction
(由 kubelet 直接检测)HTTPGetAction
(由 kubelet 直接检测)你可以参阅 Pod 的生命周期文档中的探针 部分。
接下来 要了解为什么 Kubernetes 会在其他资源
(如 StatefulSet
或 Deployment )
封装通用的 Pod API,相关的背景信息可以在前人的研究中找到。具体包括:
3.5.1.1 - Pod 的生命周期 本页面讲述 Pod 的生命周期。
Pod 遵循预定义的生命周期,起始于 Pending
阶段 ,
如果至少其中有一个主要容器正常启动,则进入 Running
,之后取决于 Pod
中是否有容器以失败状态结束而进入 Succeeded
或者 Failed
阶段。
在 Pod 运行期间,kubelet
能够重启容器以处理一些失效场景。
在 Pod 内部,Kubernetes 跟踪不同容器的状态 并确定使
Pod 重新变得健康所需要采取的动作。
在 Kubernetes API 中,Pod 包含规约部分和实际状态部分。
Pod 对象的状态包含了一组 Pod 状况(Conditions) 。
如果应用需要的话,你也可以向其中注入自定义的就绪态信息 。
Pod 在其生命周期中只会被调度 一次。
一旦 Pod 被调度(分派)到某个节点,Pod 会一直在该节点运行,直到 Pod
停止或者被终止 。
Pod 生命期 和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。
Pod 会被创建、赋予一个唯一的
ID(UID ),
并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。
如果一个节点 死掉了,调度到该节点的
Pod 也被计划在给定超时期限结束后删除 。
Pod 自身不具有自愈能力。如果 Pod
被调度到某节点 而该节点之后失效,
Pod 会被删除;类似地,Pod 无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。
Kubernetes 使用一种高级抽象来管理这些相对而言可随时丢弃的 Pod 实例,
称作控制器 。
任何给定的 Pod (由 UID 定义)从不会被“重新调度(rescheduled)”到不同的节点;
相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。
如果需要,新 Pod 的名字可以不变,但是其 UID 会不同。
如果某物声称其生命期与某 Pod 相同,例如存储卷 ,
这就意味着该对象在此 Pod (UID 亦相同)存在期间也一直存在。
如果 Pod 因为任何原因被删除,甚至某完全相同的替代 Pod 被创建时,
这个相关的对象(例如这里的卷)也会被删除并重建。
Pod 结构图例 一个包含多个容器的 Pod 中包含一个用来拉取文件的程序和一个 Web 服务器,
均使用持久卷作为容器间共享的存储。
Pod 阶段 Pod 的 status
字段是一个
PodStatus
对象,其中包含一个 phase
字段。
Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。
该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。
Pod 阶段的数量和含义是严格定义的。
除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase
值。
下面是 phase
可能的值:
取值 描述 Pending
(悬决)Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 Running
(运行中)Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。 Succeeded
(成功)Pod 中的所有容器都已成功终止,并且不会再重启。 Failed
(失败)Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止。 Unknown
(未知)因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。
说明: 当一个 Pod 被删除时,执行一些 kubectl 命令会展示这个 Pod 的状态为 Terminating
(终止)。
这个 Terminating
状态并不是 Pod 阶段之一。
Pod 被赋予一个可以体面终止的期限,默认为 30 秒。
你可以使用 --force
参数来强制终止 Pod 。
如果某节点死掉或者与集群中其他节点失联,Kubernetes
会实施一种策略,将失去的节点上运行的所有 Pod 的 phase
设置为 Failed
。
容器状态 Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段 一样。
你可以使用容器生命周期回调
来在容器生命周期中的特定时间点触发事件。
一旦调度器 将 Pod
分派给某个节点,kubelet
就通过容器运行时 开始为
Pod 创建容器。容器的状态有三种:Waiting
(等待)、Running
(运行中)和
Terminated
(已终止)。
要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称>
。
其输出中包含 Pod 中每个容器的状态。
每种状态都有特定的含义:
Waiting
(等待)如果容器并不处在 Running
或 Terminated
状态之一,它就处在 Waiting
状态。
处于 Waiting
状态的容器仍在运行它完成启动所需要的操作:例如,
从某个容器镜像仓库拉取容器镜像,或者向容器应用 Secret
数据等等。
当你使用 kubectl
来查询包含 Waiting
状态的容器的 Pod 时,你也会看到一个
Reason 字段,其中给出了容器处于等待状态的原因。
Running
(运行中)Running
状态表明容器正在执行状态并且没有问题发生。
如果配置了 postStart
回调,那么该回调已经执行且已完成。
如果你使用 kubectl
来查询包含 Running
状态的容器的 Pod 时,
你也会看到关于容器进入 Running
状态的信息。
Terminated
(已终止)处于 Terminated
状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。
如果你使用 kubectl
来查询包含 Terminated
状态的容器的 Pod 时,
你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。
如果容器配置了 preStop
回调,则该回调会在容器进入 Terminated
状态之前执行。
容器重启策略 Pod 的 spec
中包含一个 restartPolicy
字段,其可能取值包括
Always、OnFailure 和 Never。默认值是 Always。
restartPolicy
适用于 Pod 中的所有容器。restartPolicy
仅针对同一节点上
kubelet
的容器重启动作。当 Pod 中的容器退出时,kubelet
会按指数回退方式计算重启的延迟(10s、20s、40s、...),其最长延迟为 5 分钟。
一旦某容器执行了 10 分钟并且没有出现问题,kubelet
对该容器的重启回退计时器执行重置操作。
Pod 状况 Pod 有一个 PodStatus 对象,其中包含一个
PodConditions
数组。Pod 可能通过也可能未通过其中的一些状况测试。
Kubelet 管理以下 PodCondition:
PodScheduled
:Pod 已经被调度到某节点;PodHasNetwork
:Pod 沙箱被成功创建并且配置了网络(Alpha 特性,必须被显式启用 );ContainersReady
:Pod 中所有容器都已就绪;Initialized
:所有的 Init 容器 都已成功完成;Ready
:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。字段名称 描述 type
Pod 状况的名称 status
表明该状况是否适用,可能的取值有 "True
"、"False
" 或 "Unknown
" lastProbeTime
上次探测 Pod 状况时的时间戳 lastTransitionTime
Pod 上次从一种状态转换到另一种状态时的时间戳 reason
机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因 message
人类可读的消息,给出上次状态转换的详细信息
Pod 就绪态 特性状态: Kubernetes v1.14 [stable]
你的应用可以向 PodStatus 中注入额外的反馈或者信号:Pod Readiness(Pod 就绪态) 。
要使用这一特性,可以设置 Pod 规约中的 readinessGates
列表,为 kubelet
提供一组额外的状况供其评估 Pod 就绪态时使用。
就绪态门控基于 Pod 的 status.conditions
字段的当前值来做决定。
如果 Kubernetes 无法在 status.conditions
字段中找到某状况,
则该状况的状态值默认为 "False
"。
这里是一个例子:
kind : Pod
...
spec :
readinessGates :
- conditionType : "www.example.com/feature-1"
status :
conditions :
- type : Ready # 内置的 Pod 状况
status : "False"
lastProbeTime : null
lastTransitionTime : 2018-01-01T00:00:00Z
- type : "www.example.com/feature-1" # 额外的 Pod 状况
status : "False"
lastProbeTime : null
lastTransitionTime : 2018-01-01T00:00:00Z
containerStatuses :
- containerID : docker://abcd...
ready : true
...
你所添加的 Pod 状况名称必须满足 Kubernetes
标签键名格式 。
Pod 就绪态的状态 命令 kubectl patch
不支持修改对象的状态。
如果需要设置 Pod 的 status.conditions
,应用或者
Operators
需要使用 PATCH
操作。你可以使用
Kubernetes 客户端库 之一来编写代码,
针对 Pod 就绪态设置定制的 Pod 状况。
对于使用定制状况的 Pod 而言,只有当下面的陈述都适用时,该 Pod 才会被评估为就绪:
Pod 中所有容器都已就绪; readinessGates
中的所有状况都为 True
值。当 Pod 的容器都已就绪,但至少一个定制状况没有取值或者取值为 False
,
kubelet
将 Pod 的状况 设置为 ContainersReady
。
Pod 网络就绪 特性状态: Kubernetes v1.25 [alpha]
在 Pod 被调度到某节点后,它需要被 Kubelet 接受并且挂载所需的卷。
一旦这些阶段完成,Kubelet 将与容器运行时(使用容器运行时接口(Container Runtime Interface;CRI) )
一起为 Pod 生成运行时沙箱并配置网络。
如果启用了 PodHasNetworkCondition
特性门控 ,
kubelet 会通过 Pod 的 status.conditions
字段中的 PodHasNetwork
状况来报告
Pod 是否达到了初始化里程碑。
当 kubelet 检测到 Pod 不具备配置了网络的运行时沙箱时,PodHasNetwork
状况将被设置为 False
。
以下场景中将会发生这种状况:
在 Pod 生命周期的早期阶段,kubelet 还没有开始使用容器运行时为 Pod 设置沙箱时。 在 Pod 生命周期的末期阶段,Pod 的沙箱由于以下原因被销毁时:节点重启时 Pod 没有被驱逐 对于使用虚拟机进行隔离的容器运行时,Pod 沙箱虚拟机重启时,需要创建一个新的沙箱和全新的容器网络配置。 在运行时插件成功完成 Pod 的沙箱创建和网络配置后,
kubelet 会将 PodHasNetwork
状况设置为 True
。
当 PodHasNetwork
状况设置为 True
后,
Kubelet 可以开始拉取容器镜像和创建容器。
对于带有 Init 容器的 Pod,kubelet 会在 Init 容器成功完成后将 Initialized
状况设置为 True
(这发生在运行时成功创建沙箱和配置网络之后),
对于没有 Init 容器的 Pod,kubelet 会在创建沙箱和网络配置开始之前将
Initialized
状况设置为 True
。
容器探针 probe 是由 kubelet 对容器执行的定期诊断。
要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
检查机制 使用探针来检查容器有四种不同的方法。
每个探针都必须准确定义为这四种机制中的一种:
exec
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。 grpc
使用 gRPC 执行一个远程过程调用。
目标应该实现
gRPC健康检查 。
如果响应的状态是 "SERVING",则认为诊断成功。
gRPC 探针是一个 Alpha 特性,只有在你启用了
"GRPCContainerProbe" 特性门控 时才能使用。 httpGet
对容器的 IP 地址上指定端口和路径执行 HTTP GET
请求。如果响应的状态码大于等于 200
且小于 400,则诊断被认为是成功的。 tcpSocket
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。 探测结果 每次探测都将获得以下三种结果之一:
Success
(成功)容器通过了诊断。 Failure
(失败)容器未通过诊断。 Unknown
(未知)诊断失败,因此不会采取任何行动。 探测类型 针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器,
并且容器将根据其重启策略 决定未来。如果容器不提供存活探针,
则默认状态为 Success
。 readinessProbe
指示容器是否准备好为请求提供服务。如果就绪态探测失败,
端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。
初始延迟之前的就绪态的状态值默认为 Failure
。
如果容器不提供就绪态探针,则默认状态为 Success
。 startupProbe
指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被
禁用,直到此探针成功为止。如果启动探测失败,kubelet
将杀死容器,
而容器依其重启策略 进行重启。
如果容器没有提供启动探测,则默认状态为 Success
。 如欲了解如何设置存活态、就绪态和启动探针的进一步细节,
可以参阅配置存活态、就绪态和启动探针 。
何时该使用存活态探针? 特性状态: Kubernetes v1.0 [stable]
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针;
kubelet
将根据 Pod 的 restartPolicy
自动执行修复操作。
如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针,
并指定 restartPolicy
为 "Always
" 或 "OnFailure
"。
何时该使用就绪态探针? 特性状态: Kubernetes v1.0 [stable]
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。
在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着
Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,
检查某个特定于就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。
当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。
这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,
你可以使用启动探针 。
然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
说明: 请注意,如果你只是想在 Pod 被删除时能够排空请求,则不一定需要使用就绪态探针;
在删除 Pod 时,Pod 会自动将自身置于未就绪状态,无论就绪态探针是否存在。
等待 Pod 中的容器停止期间,Pod 会一直处于未就绪状态。
何时该使用启动探针? 特性状态: Kubernetes v1.20 [stable]
对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。
你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定,
对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds
总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。
periodSeconds
的默认值是 10 秒。你应该将其 failureThreshold
设置得足够高,
以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。
这一设置有助于减少死锁状况的发生。
Pod 的终止 由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地终止是很重要的。
一般不应武断地使用 KILL
信号终止它们,导致这些进程没有机会完成清理操作。
设计的目标是令你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除操作终将完成。
当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期,
而不是直接强制地杀死 Pod。在存在强制关闭设施的前提下,
kubelet 会尝试体面地终止
Pod。
通常情况下,容器运行时会发送一个 TERM 信号到每个容器中的主进程。
很多容器运行时都能够注意到容器镜像中 STOPSIGNAL
的值,并发送该信号而不是 TERM。
一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后
Pod 就会被从 API 服务器 上移除。
如果 kubelet
或者容器运行时的管理服务在等待进程终止期间被重启,
集群会从头开始重试,赋予 Pod 完整的体面终止限期。
下面是一个例子:
你使用 kubectl
工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期是默认值(30 秒)。
API 服务器中的 Pod 对象被更新,记录涵盖体面终止限期在内 Pod
的最终死期,超出所计算时间点则认为 Pod 已死(dead)。
如果你使用 kubectl describe
来查验你正在删除的 Pod,该 Pod 会显示为
"Terminating" (正在终止)。
在 Pod 运行所在的节点上:kubelet
一旦看到 Pod
被标记为正在终止(已经设置了体面终止限期),kubelet
即开始本地的 Pod 关闭过程。
如果 Pod 中的容器之一定义了 preStop
回调 ,
kubelet
开始在容器内运行该回调逻辑。如果超出体面终止限期时,
preStop
回调逻辑仍在运行,kubelet
会请求给予该 Pod 的宽限期一次性增加 2 秒钟。
说明: 如果 `preStop` 回调所需要的时间长于默认的体面终止限期,你必须修改
`terminationGracePeriodSeconds` 属性值来使其正常工作。
kubelet
接下来触发容器运行时发送 TERM 信号给每个容器中的进程 1。
说明: Pod 中的容器会在不同时刻收到 TERM 信号,接收顺序也是不确定的。
如果关闭的顺序很重要,可以考虑使用 `preStop` 回调逻辑来协调。
在 kubelet
启动体面关闭逻辑的同时,控制面会将关闭的 Pod 从对应的
EndpointSlice(和 Endpoints)对象中移除,过滤条件是 Pod
被对应的服务 以某
选择算符 选定。
ReplicaSet
和其他工作负载资源不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。
关闭动作很慢的 Pod 也无法继续处理请求数据,
因为负载均衡器(例如服务代理)已经在终止宽限期开始的时候将其从端点列表中移除。 超出终止宽限期限时,kubelet
会触发强制关闭过程。容器运行时会向 Pod
中所有容器内仍在运行的进程发送 SIGKILL
信号。
kubelet
也会清理隐藏的 pause
容器,如果容器运行时使用了这种容器的话。
kubelet
触发强制从 API 服务器上删除 Pod 对象的逻辑,并将体面终止限期设置为 0
(这意味着马上删除)。
API 服务器删除 Pod 的 API 对象,从任何客户端都无法再看到该对象。
强制终止 Pod 注意: 对于某些工作负载及其 Pod 而言,强制删除很可能会带来某种破坏。
默认情况下,所有的删除操作都会附有 30 秒钟的宽限期限。
kubectl delete
命令支持 --grace-period=<seconds>
选项,允许你重载默认值,
设定自己希望的期限值。
将宽限期限强制设置为 0
意味着立即从 API 服务器删除 Pod。
如果 Pod 仍然运行于某节点上,强制删除操作会触发 kubelet
立即执行清理操作。
说明: 你必须在设置 --grace-period=0
的同时额外设置 --force
参数才能发起强制删除请求。
执行强制删除操作时,API 服务器不再等待来自 kubelet
的、关于 Pod
已经在原来运行的节点上终止执行的确认消息。
API 服务器直接删除 Pod 对象,这样新的与之同名的 Pod 即可以被创建。
在节点侧,被设置为立即终止的 Pod 仍然会在被强行杀死之前获得一点点的宽限时间。
注意: 马上删除时不等待确认正在运行的资源已被终止。这些资源可能会无限期地继续在集群上运行。
如果你需要强制删除 StatefulSet 的 Pod,
请参阅从 StatefulSet 中删除 Pod 的任务文档。
已终止 Pod 的垃圾收集 对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API 服务器上,
直到用户或者控制器 进程显式地将其删除。
控制面组件会在 Pod 个数超出所配置的阈值
(根据 kube-controller-manager
的 terminated-pod-gc-threshold
设置)时删除已终止的
Pod(阶段值为 Succeeded
或 Failed
)。
这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。
接下来 3.5.1.2 - Init 容器 本页提供了 Init 容器的概览。Init 容器是一种特殊容器,在 Pod
内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
你可以在 Pod 的规约中与用来描述应用容器的 containers
数组平行的位置指定
Init 容器。
理解 Init 容器 每个 Pod 中可以包含多个容器,
应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
它们总是运行到完成。 每个都必须在下一个启动之前成功完成。 如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。
然而,如果 Pod 对应的 restartPolicy
值为 "Never",并且 Pod 的 Init 容器失败,
则 Kubernetes 会将整个 Pod 状态设置为失败。
为 Pod 设置 Init 容器需要在
Pod 规约 中添加 initContainers
字段,
该字段以 Container
类型对象数组的形式组织,和应用的 containers
数组同级相邻。
参阅 API 参考的容器 章节了解详情。
Init 容器的状态在 status.initContainerStatuses
字段中以容器状态数组的格式返回
(类似 status.containerStatuses
字段)。
与普通容器的不同之处 Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。
然而,Init 容器对资源请求和限制的处理稍有不同,在下面资源 节有说明。
同时 Init 容器不支持 lifecycle
、livenessProbe
、readinessProbe
和 startupProbe
,
因为它们必须在 Pod 就绪之前运行完成。
如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。
每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时,
Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。
使用 Init 容器 因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
与同一 Pod 中的多个应用容器相比,Init 容器能以不同的文件系统视图运行。因此,Init
容器可以被赋予访问应用容器不能访问的 Secret 的权限。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init
容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。
一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
Init 容器可以安全地运行实用程序或自定义代码,而在其他方式下运行这些实用程序或自定义代码可能会降低应用容器镜像的安全性。
通过将不必要的工具分开,你可以限制应用容器镜像的被攻击范围。
示例 下面是一些如何使用 Init 容器的想法:
使用 Init 容器的情况 下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice
启动,
第二个等待 mydb
启动。 一旦这两个 Init 容器都启动完成,Pod 将启动 spec
节中的应用容器。
apiVersion : v1
kind : Pod
metadata :
name : myapp-pod
labels :
app.kubernetes.io/name : MyApp
spec :
containers :
- name : myapp-container
image : busybox:1.28
command : ['sh' , '-c' , 'echo The app is running! && sleep 3600' ]
initContainers :
- name : init-myservice
image : busybox:1.28
command : ['sh' , '-c' , "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done" ]
- name : init-mydb
image : busybox:1.28
command : ['sh' , '-c' , "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done" ]
你通过运行下面的命令启动 Pod:
kubectl apply -f myapp.yaml
输出类似于:
pod/myapp-pod created
使用下面的命令检查其状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
或者查看更多详细信息:
kubectl describe -f myapp.yaml
输出类似于:
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container init-myservice
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container init-myservice
如需查看 Pod 内 Init 容器的日志,请执行:
kubectl logs myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb # 查看第二个 Init 容器
在这一刻,Init 容器将会等待至发现名称为 mydb
和 myservice
的 Service。
如下为创建这些 Service 的配置文件:
---
apiVersion : v1
kind : Service
metadata :
name : myservice
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
---
apiVersion : v1
kind : Service
metadata :
name : mydb
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9377
创建 mydb
和 myservice
服务的命令:
kubectl apply -f services.yaml
输出类似于:
service/myservice created
service/mydb created
这样你将能看到这些 Init 容器执行完毕,随后 my-app
的 Pod 进入 Running
状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
这个简单例子应该能为你创建自己的 Init 容器提供一些启发。
接下来 节提供了更详细例子的链接。
具体行为 在 Pod 启动过程中,每个 Init 容器会在网络和数据卷初始化之后按顺序启动。
kubelet 运行依据 Init 容器在 Pod 规约中的出现顺序依次运行之。
每个 Init 容器成功退出后才会启动下一个 Init 容器。
如果某容器因为容器运行时的原因无法启动,或以错误状态退出,kubelet 会根据
Pod 的 restartPolicy
策略进行重试。
然而,如果 Pod 的 restartPolicy
设置为 "Always",Init 容器失败时会使用
restartPolicy
的 "OnFailure" 策略。
在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready
状态。
Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending
状态,
但会将状况 Initializing
设置为 false。
如果 Pod 重启 ,所有 Init 容器必须重新执行。
对 Init 容器规约的修改仅限于容器的 image
字段。
更改 Init 容器的 image
字段,等同于重启该 Pod。
因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。
特别地,基于 emptyDirs
写文件的代码,应该对输出文件可能已经存在做好准备。
Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe
,
因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。
Kubernetes 会在校验时强制执行此检查。
在 Pod 上使用 activeDeadlineSeconds
和在容器上使用 livenessProbe
可以避免
Init 容器一直重复失败。
activeDeadlineSeconds
时间包含了 Init 容器启动的时间。
但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds
,
因为 activeDeadlineSeconds
在 Init 容器结束后仍有效果。
如果你设置了 activeDeadlineSeconds
,已经在正常运行的 Pod 会被杀死。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一;
与任何其它容器共享同一个名称,会在校验时抛出错误。
资源 在给定的 Init 容器执行顺序下,资源使用适用于如下规则:
所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为
Pod 有效初始 request/limit 。
如果任何资源没有指定资源限制,这被视为最高限制。 Pod 对资源的 有效 limit/request 是如下两者中的较大者:所有应用容器对某个资源的 limit/request 之和 对某个资源的有效初始 limit/request 基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源,
这些资源在 Pod 生命周期过程中并没有被使用。 Pod 的 有效 QoS 层 ,与 Init 容器和应用容器的一样。 配额和限制适用于有效 Pod 的请求和限制值。
Pod 级别的 cgroups 是基于有效 Pod 的请求和限制值,和调度器相同。
Pod 重启的原因 Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:
当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时,
Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。
如果你在使用较早版本的 Kubernetes,可查阅你所使用的版本对应的文档。
接下来 3.5.1.3 - 干扰(Disruptions) 本指南针对的是希望构建高可用性应用的应用所有者,他们有必要了解可能发生在 Pod 上的干扰类型。
文档同样适用于想要执行自动化集群操作(例如升级和自动扩展集群)的集群管理员。
自愿干扰和非自愿干扰 Pod 不会消失,除非有人(用户或控制器)将其销毁,或者出现了不可避免的硬件或软件系统错误。
我们把这些不可避免的情况称为应用的非自愿干扰(Involuntary Disruptions) 。例如:
节点下层物理机的硬件故障 集群管理员错误地删除虚拟机(实例) 云提供商或虚拟机管理程序中的故障导致的虚拟机消失 内核错误 节点由于集群网络隔离从集群中消失 由于节点资源不足 导致 pod 被驱逐。 除了资源不足的情况,大多数用户应该都熟悉这些情况;它们不是特定于 Kubernetes 的。
我们称其他情况为自愿干扰(Voluntary Disruptions) 。
包括由应用所有者发起的操作和由集群管理员发起的操作。
典型的应用所有者的操作包括:
删除 Deployment 或其他管理 Pod 的控制器 更新了 Deployment 的 Pod 模板导致 Pod 重启 直接删除 Pod(例如,因为误操作) 集群管理员操作包括:
这些操作可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管提供商自动执行。
咨询集群管理员或联系云提供商,或者查询发布文档,以确定是否为集群启用了任何资源干扰源。
如果没有启用,可以不用创建 Pod Disruption Budgets(Pod 干扰预算)
注意: 并非所有的自愿干扰都会受到 Pod 干扰预算的限制。
例如,删除 Deployment 或 Pod 的删除操作就会跳过 Pod 干扰预算检查。
处理干扰 以下是减轻非自愿干扰的一些方法:
确保 Pod 在请求中给出所需资源 。 如果需要更高的可用性,请复制应用。
(了解有关运行多副本的无状态
和有状态 应用的信息。) 为了在运行复制应用时获得更高的可用性,请跨机架(使用
反亲和性
或跨区域(如果使用多区域集群 )扩展应用。 自愿干扰的频率各不相同。在一个基本的 Kubernetes 集群中,没有自愿干扰(只有用户触发的干扰)。
然而,集群管理员或托管提供商可能运行一些可能导致自愿干扰的额外服务。例如,节点软
更新可能导致自愿干扰。另外,集群(节点)自动缩放的某些
实现可能导致碎片整理和紧缩节点的自愿干扰。集群
管理员或托管提供商应该已经记录了各级别的自愿干扰(如果有的话)。
有些配置选项,例如在 pod spec 中
使用 PriorityClasses
也会产生自愿(和非自愿)的干扰。
干扰预算 特性状态: Kubernetes v1.21 [stable]
即使你会经常引入自愿性干扰,Kubernetes 提供的功能也能够支持你运行高度可用的应用。
作为一个应用的所有者,你可以为每个应用创建一个 PodDisruptionBudget
(PDB)。
PDB 将限制在同一时间因自愿干扰导致的多副本应用中发生宕机的 Pod 数量。
例如,基于票选机制的应用希望确保运行中的副本数永远不会低于票选所需的数量。
Web 前端可能希望确保提供负载的副本数量永远不会低于总数的某个百分比。
集群管理员和托管提供商应该使用遵循 PodDisruptionBudgets 的接口
(通过调用Eviction API ),
而不是直接删除 Pod 或 Deployment。
例如,kubectl drain
命令可以用来标记某个节点即将停止服务。
运行 kubectl drain
命令时,工具会尝试驱逐你所停服的节点上的所有 Pod。
kubectl
代表你所提交的驱逐请求可能会暂时被拒绝,
所以该工具会周期性地重试所有失败的请求,
直到目标节点上的所有的 Pod 都被终止,或者达到配置的超时时间。
PDB 指定应用可以容忍的副本数量(相当于应该有多少副本)。
例如,具有 .spec.replicas: 5
的 Deployment 在任何时间都应该有 5 个 Pod。
如果 PDB 允许其在某一时刻有 4 个副本,那么驱逐 API 将允许同一时刻仅有一个(而不是两个)Pod 自愿干扰。
使用标签选择器来指定构成应用的一组 Pod,这与应用的控制器(Deployment,StatefulSet 等)
选择 Pod 的逻辑一样。
Pod 的“预期”数量由管理这些 Pod 的工作负载资源的 .spec.replicas
参数计算出来的。
控制平面通过检查 Pod 的
.metadata.ownerReferences
来发现关联的工作负载资源。
PDB 无法防止非自愿干扰 ;
但它们确实计入预算。
由于应用的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算,
但是工作负载资源(如 Deployment 和 StatefulSet)
在进行滚动升级时不受 PDB 的限制。
应用更新期间的故障处理方式是在对应的工作负载资源的 spec
中配置的。
当使用驱逐 API 驱逐 Pod 时,Pod 会被体面地
终止 ,期间会
参考 PodSpec
中的 terminationGracePeriodSeconds
配置值。
PodDisruptionBudget 例子 假设集群有 3 个节点,node-1
到 node-3
。集群上运行了一些应用。
其中一个应用有 3 个副本,分别是 pod-a
,pod-b
和 pod-c
。
另外,还有一个不带 PDB 的无关 pod pod-x
也同样显示出来。
最初,所有的 Pod 分布如下:
node-1 node-2 node-3 pod-a available pod-b available pod-c available pod-x available
3 个 Pod 都是 deployment 的一部分,并且共同拥有同一个 PDB,要求 3 个 Pod 中至少有 2 个 Pod 始终处于可用状态。
例如,假设集群管理员想要重启系统,升级内核版本来修复内核中的缺陷。
集群管理员首先使用 kubectl drain
命令尝试腾空 node-1
节点。
命令尝试驱逐 pod-a
和 pod-x
。操作立即就成功了。
两个 Pod 同时进入 terminating
状态。这时的集群处于下面的状态:
node-1 draining node-2 node-3 pod-a terminating pod-b available pod-c available pod-x terminating
Deployment 控制器观察到其中一个 Pod 正在终止,因此它创建了一个替代 Pod pod-d
。
由于 node-1
被封锁(cordon),pod-d
落在另一个节点上。
同样其他控制器也创建了 pod-y
作为 pod-x
的替代品。
(注意:对于 StatefulSet 来说,pod-a
(也称为 pod-0
)需要在替换 Pod 创建之前完全终止,
替代它的也称为 pod-0
,但是具有不同的 UID。除此之外,此示例也适用于 StatefulSet。)
当前集群的状态如下:
node-1 draining node-2 node-3 pod-a terminating pod-b available pod-c available pod-x terminating pod-d starting pod-y
在某一时刻,Pod 被终止,集群如下所示:
node-1 drained node-2 node-3 pod-b available pod-c available pod-d starting pod-y
此时,如果一个急躁的集群管理员试图排空(drain)node-2
或 node-3
,drain 命令将被阻塞,
因为对于 Deployment 来说只有 2 个可用的 Pod,并且它的 PDB 至少需要 2 个。
经过一段时间,pod-d
变得可用。
集群状态如下所示:
node-1 drained node-2 node-3 pod-b available pod-c available pod-d available pod-y
现在,集群管理员试图排空(drain)node-2
。
drain 命令将尝试按照某种顺序驱逐两个 Pod,假设先是 pod-b
,然后是 pod-d
。
命令成功驱逐 pod-b
,但是当它尝试驱逐 pod-d
时将被拒绝,因为对于
Deployment 来说只剩一个可用的 Pod 了。
Deployment 创建 pod-b
的替代 Pod pod-e
。
因为集群中没有足够的资源来调度 pod-e
,drain 命令再次阻塞。集群最终将是下面这种状态:
node-1 drained node-2 node-3 no node pod-b terminating pod-c available pod-e pending pod-d available pod-y
此时,集群管理员需要增加一个节点到集群中以继续升级操作。
可以看到 Kubernetes 如何改变干扰发生的速率,根据:
应用需要多少个副本 优雅关闭应用实例需要多长时间 启动应用新实例需要多长时间 控制器的类型 集群的资源能力 Pod 干扰状况 特性状态: Kubernetes v1.25 [alpha]
说明: 要使用此行为,你必须在集群中启用 PodDisruptionCondition
特性门控 。
启用后,会给 Pod 添加一个 DisruptionTarget
状况 ,
用来表明该 Pod 因为发生干扰 而被删除。
状况中的 reason
字段进一步给出 Pod 终止的原因,如下:
PreemptionByKubeScheduler
Pod 将被调度器抢占 ,
目的是接受优先级更高的新 Pod。
要了解更多的相关信息,请参阅 Pod 优先级和抢占 。 DeletionByTaintManager
由于 Pod 不能容忍 NoExecute
污点,Pod 将被
Taint Manager(kube-controller-manager
中节点生命周期控制器的一部分)删除;
请参阅基于污点 的驱逐。 EvictionByEvictionAPI
Pod 已被标记为通过 Kubernetes API 驱逐 。 DeletionByPodGC
绑定到一个不再存在的 Node 上的 Pod 将被
Pod 垃圾收集 删除。 说明: Pod 的干扰可能会被中断。控制平面可能会重新尝试继续干扰同一个 Pod,但这没办法保证。
因此,DisruptionTarget
条件可能会添被加到 Pod 上,
但该 Pod 实际上可能不会被删除。
在这种情况下,一段时间后,Pod 干扰状况将被清除。
使用 Job(或 CronJob)时,你可能希望将这些 Pod 干扰状况作为 Job
Pod 失效策略 的一部分。
分离集群所有者和应用所有者角色 通常,将集群管理者和应用所有者视为彼此了解有限的独立角色是很有用的。这种责任分离在下面这些场景下是有意义的:
当有许多应用团队共用一个 Kubernetes 集群,并且有自然的专业角色 当第三方工具或服务用于集群自动化管理 Pod 干扰预算通过在角色之间提供接口来支持这种分离。
如果你的组织中没有这样的责任分离,则可能不需要使用 Pod 干扰预算。
如何在集群上执行干扰性操作 如果你是集群管理员,并且需要对集群中的所有节点执行干扰操作,例如节点或系统软件升级,则可以使用以下选项
接受升级期间的停机时间。 故障转移到另一个完整的副本集群。没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。 编写可容忍干扰的应用和使用 PDB。不停机。 最小的资源重复。 允许更多的集群管理自动化。 编写可容忍干扰的应用是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非
自愿干扰所做工作相比,有大量的重叠 接下来 3.5.1.4 - 临时容器 特性状态: Kubernetes v1.25 [stable]
本页面概述了临时容器:一种特殊的容器,该容器在现有
Pod
中临时运行,以便完成用户发起的操作,例如故障排查。
你会使用临时容器来检查服务,而不是用它来构建应用程序。
了解临时容器 Pod 是 Kubernetes 应用程序的基本构建块。
由于 Pod 是一次性且可替换的,因此一旦 Pod 创建,就无法将容器加入到 Pod 中。
取而代之的是,通常使用 Deployment
以受控的方式来删除并替换 Pod。
有时有必要检查现有 Pod 的状态。例如,对于难以复现的故障进行排查。
在这些场景中,可以在现有 Pod 中运行临时容器来检查其状态并运行任意命令。
什么是临时容器? 临时容器与其他容器的不同之处在于,它们缺少对资源或执行的保证,并且永远不会自动重启,
因此不适用于构建应用程序。
临时容器使用与常规容器相同的 ContainerSpec
节来描述,但许多字段是不兼容和不允许的。
临时容器没有端口配置,因此像 ports
,livenessProbe
,readinessProbe
这样的字段是不允许的。 Pod 资源分配是不可变的,因此 resources
配置是不允许的。 有关允许字段的完整列表,请参见
EphemeralContainer 参考文档 。 临时容器是使用 API 中的一种特殊的 ephemeralcontainers
处理器进行创建的,
而不是直接添加到 pod.spec
段,因此无法使用 kubectl edit
来添加一个临时容器。
与常规容器一样,将临时容器添加到 Pod 后,将不能更改或删除临时容器。
临时容器的用途 当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec
无用时,
临时容器对于交互式故障排查很有用。
尤其是,Distroless 镜像
允许用户部署最小的容器镜像,从而减少攻击面并减少故障和漏洞的暴露。
由于 distroless 镜像不包含 Shell 或任何的调试工具,因此很难单独使用
kubectl exec
命令进行故障排查。
使用临时容器时,启用
进程名字空间共享
很有帮助,可以查看其他容器中的进程。
接下来
3.5.1.5 - Downward API 有两种方法可以将 Pod 和容器字段暴露给运行中的容器:环境变量和由特殊卷类型承载的文件。 这两种暴露 Pod 和容器字段的方法统称为 Downward API。
对于容器来说,在不与 Kubernetes 过度耦合的情况下,拥有关于自身的信息有时是很有用的。
Downward API 允许容器在不使用 Kubernetes 客户端或 API 服务器的情况下获得自己或集群的信息。
例如,现有应用程序假设某特定的周知的环境变量是存在的,其中包含唯一标识符。
一种方法是对应用程序进行封装,但这很繁琐且容易出错,并且违背了低耦合的目标。
更好的选择是使用 Pod 名称作为标识符,并将 Pod 名称注入到周知的环境变量中。
在 Kubernetes 中,有两种方法可以将 Pod 和容器字段暴露给运行中的容器:
这两种暴露 Pod 和容器字段的方式统称为 Downward API 。
可用字段 只有部分 Kubernetes API 字段可以通过 Downward API 使用。本节列出了你可以使用的字段。
你可以使用 fieldRef
传递来自可用的 Pod 级字段的信息。在 API 层面,一个 Pod 的
spec
总是定义了至少一个 Container 。
你可以使用 resourceFieldRef
传递来自可用的 Container 级字段的信息。
可通过 fieldRef
获得的信息 对于大多数 Pod 级别的字段,你可以将它们作为环境变量或使用 downwardAPI
卷提供给容器。
通过这两种机制可用的字段有:
metadata.name
Pod 的名称 metadata.namespace
Pod 的命名空间 metadata.uid
Pod 的唯一 ID metadata.annotations['<KEY>']
Pod 的注解 <KEY>
的值(例如:metadata.annotations['myannotation']
) metadata.labels['<KEY>']
Pod 的标签 <KEY>
的值(例如:metadata.labels['mylabel']
) spec.serviceAccountName
Pod 的服务账号 名称 spec.nodeName
Pod 运行时所处的节点 名称 status.hostIP
Pod 所在节点的主 IP 地址 status.podIP
Pod 的主 IP 地址(通常是其 IPv4 地址) 此外,以下信息可以通过 downwardAPI
卷 fieldRef
获得,但不能作为环境变量 获得:
metadata.labels
Pod 的所有标签,格式为 标签键名="转义后的标签值"
,每行一个标签 metadata.annotations
Pod 的全部注解,格式为 注解键名="转义后的注解值"
,每行一个注解 可通过 resourceFieldRef
获得的信息 resource: limits.cpu
容器的 CPU 限制值 resource: requests.cpu
容器的 CPU 请求值 resource: limits.memory
容器的内存限制值 resource: requests.memory
容器的内存请求值 resource: limits.hugepages-*
容器的巨页限制值(前提是启用了 DownwardAPIHugePages
特性门控 ) resource: requests.hugepages-*
容器的巨页请求值(前提是启用了 DownwardAPIHugePages
特性门控 ) resource: limits.ephemeral-storage
容器的临时存储的限制值 resource: requests.ephemeral-storage
容器的临时存储的请求值 如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据
节点可分配资源
计算并暴露 CPU 和内存的最大可分配值。
接下来 你可以阅读有关 downwardAPI
卷 的内容。
你可以尝试使用 Downward API 暴露容器或 Pod 级别的信息:
3.5.2 - 工作负载资源 3.5.2.1 - Deployments 一个 Deployment 为 Pod
和 ReplicaSet
提供声明式的更新能力。
你负责描述 Deployment 中的 目标状态 ,而 Deployment 控制器(Controller)
以受控速率更改实际状态,
使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment,
并通过新的 Deployment 收养其资源。
说明: 不要管理 Deployment 所拥有的 ReplicaSet 。
如果存在下面未覆盖的使用场景,请考虑在 Kubernetes 仓库中提出 Issue。
用例 以下是 Deployments 的典型用例:
创建 Deployment 以将 ReplicaSet 上线 。ReplicaSet 在后台创建 Pod。
检查 ReplicaSet 的上线状态,查看其是否成功。通过更新 Deployment 的 PodTemplateSpec,声明 Pod 的新状态 。
新的 ReplicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。
每个新的 ReplicaSet 都会更新 Deployment 的修订版本。 创建 Deployment 下面是一个 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx
Pod:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
在该例中:
创建名为 nginx-deployment
(由 .metadata.name
字段标明)的 Deployment。 该 Deployment 创建三个(由 .spec.replicas
字段标明)Pod 副本。 template
字段包含以下子字段:Pod 被使用 .metadata.labels
字段打上 app: nginx
标签。 Pod 模板规约(即 .template.spec
字段)指示 Pod 运行一个 nginx
容器,
该容器运行版本为 1.14.2 的 nginx
Docker Hub 镜像。 创建一个容器并使用 .spec.template.spec.containers[0].name
字段将其命名为 nginx
。 开始之前,请确保的 Kubernetes 集群已启动并运行。
按照以下步骤创建上述 Deployment :
通过运行以下命令创建 Deployment :
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
运行 kubectl get deployments
检查 Deployment 是否已创建。
如果仍在创建 Deployment,则输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 0/3 0 0 1s
在检查集群中的 Deployment 时,所显示的字段有:
NAME
列出了名字空间中 Deployment 的名称。READY
显示应用程序的可用的“副本”数。显示的模式是“就绪个数/期望个数”。UP-TO-DATE
显示为了达到期望状态已经更新的副本数。AVAILABLE
显示应用可供用户使用的副本数。AGE
显示应用程序运行的时间。请注意期望副本数是根据 .spec.replicas
字段设置 3。
要查看 Deployment 上线状态,运行 kubectl rollout status deployment/nginx-deployment
。
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out
几秒钟后再次运行 kubectl get deployments
。输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 18s
注意 Deployment 已创建全部三个副本,并且所有副本都是最新的(它们包含最新的 Pod 模板)
并且可用。
要查看 Deployment 创建的 ReplicaSet(rs
),运行 kubectl get rs
。
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-75675f5897 3 3 3 18s
ReplicaSet 输出中包含以下字段:
NAME
列出名字空间中 ReplicaSet 的名称;DESIRED
显示应用的期望副本个数,即在创建 Deployment 时所定义的值。
此为期望状态;CURRENT
显示当前运行状态中的副本个数;READY
显示应用中有多少副本可以为用户提供服务;AGE
显示应用已经运行的时间长度。注意 ReplicaSet 的名称始终被格式化为[Deployment名称]-[哈希]
。
其中的哈希
字符串与 ReplicaSet 上的 pod-template-hash
标签一致。
要查看每个 Pod 自动生成的标签,运行 kubectl get pods --show-labels
。
输出类似于:
NAME READY STATUS RESTARTS AGE LABELS
nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=3123191453
所创建的 ReplicaSet 确保总是存在三个 nginx
Pod。
说明: 你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: nginx
)。
标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。
Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符,
它们可能会发生冲突执行难以预料的操作。
Pod-template-hash 标签 Deployment 控制器将 pod-template-hash
标签添加到 Deployment
所创建或收留的每个 ReplicaSet 。
此标签可确保 Deployment 的子 ReplicaSets 不重叠。
标签是通过对 ReplicaSet 的 PodTemplate
进行哈希处理。
所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet
可能拥有的任何现有 Pod 中。
更新 Deployment 说明: 仅当 Deployment Pod 模板(即 .spec.template
)发生改变时,例如模板的标签或容器镜像被更新,
才会触发 Deployment 上线。其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作。
按照以下步骤更新 Deployment:
先来更新 nginx Pod 以使用 nginx:1.16.1
镜像,而不是 nginx:1.14.2
镜像。
kubectl set image deployment.v1.apps/nginx-deployment nginx = nginx:1.16.1
或者使用下面的命令:
kubectl set image deployment/nginx-deployment nginx = nginx:1.16.1
输出类似于:
deployment.apps/nginx-deployment image updated
或者,可以对 Deployment 执行 edit
操作并将 .spec.template.spec.containers[0].image
从
nginx:1.14.2
更改至 nginx:1.16.1
。
kubectl edit deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment edited
要查看上线状态,运行:
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
或者
deployment "nginx-deployment" successfully rolled out
获取关于已更新的 Deployment 的更多信息:
运行 kubectl get rs
以查看 Deployment 通过创建新的 ReplicaSet 并将其扩容到
3 个副本并将旧 ReplicaSet 缩容到 0 个副本完成了 Pod 的更新操作:
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1564180365 3 3 3 6s
nginx-deployment-2035384211 0 0 0 36s
现在运行 get pods
应仅显示新的 Pod:
输出类似于:
NAME READY STATUS RESTARTS AGE
nginx-deployment-1564180365-khku8 1/1 Running 0 14s
nginx-deployment-1564180365-nacti 1/1 Running 0 14s
nginx-deployment-1564180365-z9gth 1/1 Running 0 14s
下次要更新这些 Pod 时,只需再次更新 Deployment Pod 模板即可。
Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pod 的 75% 处于运行状态(最大不可用比例为 25%)。
Deployment 还确保仅所创建 Pod 数量只可能比期望 Pod 数高一点点。
默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 125%(最大峰值 25%)。
例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除旧的 Pod,
并创建了新的 Pod。它不会杀死旧 Pod,直到有足够数量的新 Pod 已经出现。
在足够数量的旧 Pod 被杀死前并没有创建新 Pod。它确保至少 3 个 Pod 可用,
同时最多总共 4 个 Pod 可用。
当 Deployment 设置为 4 个副本时,Pod 的个数会介于 3 和 5 之间。
获取 Deployment 的更多信息
kubectl describe deployments
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Thu, 30 Nov 2017 10:56:25 +0000
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=2
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-1564180365 (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set nginx-deployment-2035384211 to 3
Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 1
Normal ScalingReplicaSet 22s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 2
Normal ScalingReplicaSet 22s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 2
Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 1
Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 3
Normal ScalingReplicaSet 14s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 0
可以看到,当第一次创建 Deployment 时,它创建了一个 ReplicaSet(nginx-deployment-2035384211
)
并将其直接扩容至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet
(nginx-deployment-1564180365),并将其扩容为 1,等待其就绪;然后将旧 ReplicaSet 缩容到 2,
将新的 ReplicaSet 扩容到 2 以便至少有 3 个 Pod 可用且最多创建 4 个 Pod。
然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。
最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。
说明: Kubernetes 在计算 availableReplicas
数值时不考虑终止过程中的 Pod,
availableReplicas
的值一定介于 replicas - maxUnavailable
和 replicas + maxSurge
之间。
因此,你可能在上线期间看到 Pod 个数比预期的多,Deployment 所消耗的总的资源也大于
replicas + maxSurge
个 Pod 所用的资源,直到被终止的 Pod 所设置的
terminationGracePeriodSeconds
到期为止。
翻转(多 Deployment 动态更新) Deployment 控制器每次注意到新的 Deployment 时,都会创建一个 ReplicaSet 以启动所需的 Pod。
如果更新了 Deployment,则控制标签匹配 .spec.selector
但模板不匹配 .spec.template
的 Pod 的现有 ReplicaSet 被缩容。
最终,新的 ReplicaSet 缩放为 .spec.replicas
个副本,
所有旧 ReplicaSets 缩放为 0 个副本。
当 Deployment 正在上线时被更新,Deployment 会针对更新创建一个新的 ReplicaSet
并开始对其扩容,之前正在被扩容的 ReplicaSet 会被翻转,添加到旧 ReplicaSets 列表
并开始缩容。
例如,假定你在创建一个 Deployment 以生成 nginx:1.14.2
的 5 个副本,但接下来
更新 Deployment 以创建 5 个 nginx:1.16.1
的副本,而此时只有 3 个 nginx:1.14.2
副本已创建。在这种情况下,Deployment 会立即开始杀死 3 个 nginx:1.14.2
Pod,
并开始创建 nginx:1.16.1
Pod。它不会等待 nginx:1.14.2
的 5
个副本都创建完成后才开始执行变更动作。
更改标签选择算符 通常不鼓励更新标签选择算符。建议你提前规划选择算符。
在任何情况下,如果需要更新标签选择算符,请格外小心,
并确保自己了解这背后可能发生的所有事情。
说明: 在 API 版本 apps/v1
中,Deployment 标签选择算符在创建后是不可变的。
添加选择算符时要求使用新标签更新 Deployment 规约中的 Pod 模板标签,否则将返回验证错误。
此更改是非重叠的,也就是说新的选择算符不会选择使用旧选择算符所创建的 ReplicaSet 和 Pod,
这会导致创建新的 ReplicaSet 时所有旧 ReplicaSet 都会被孤立。 选择算符的更新如果更改了某个算符的键名,这会导致与添加算符时相同的行为。 删除选择算符的操作会删除从 Deployment 选择算符中删除现有算符。
此操作不需要更改 Pod 模板标签。现有 ReplicaSet 不会被孤立,也不会因此创建新的 ReplicaSet,
但请注意已删除的标签仍然存在于现有的 Pod 和 ReplicaSet 中。 回滚 Deployment 有时,你可能想要回滚 Deployment;例如,当 Deployment 不稳定时(例如进入反复崩溃状态)。
默认情况下,Deployment 的所有上线记录都保留在系统中,以便可以随时回滚
(你可以通过修改修订历史记录限制来更改这一约束)。
说明: Deployment 被触发上线时,系统就会创建 Deployment 的新的修订版本。
这意味着仅当 Deployment 的 Pod 模板(.spec.template
)发生更改时,才会创建新修订版本
-- 例如,模板的标签或容器镜像发生变化。
其他更新,如 Deployment 的扩缩容操作不会创建 Deployment 修订版本。
这是为了方便同时执行手动缩放或自动缩放。
换言之,当你回滚到较早的修订版本时,只有 Deployment 的 Pod 模板部分会被回滚。
按 Ctrl-C 停止上述上线状态观测。有关上线停滞的详细信息,参考这里 。 获取 Deployment 描述信息:
kubectl describe deployment
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Tue, 15 Mar 2016 14:48:04 -0700
Labels: app=nginx
Selector: app=nginx
Replicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.161
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
OldReplicaSets: nginx-deployment-1564180365 (3/3 replicas created)
NewReplicaSet: nginx-deployment-3066724191 (1/1 replicas created)
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-2035384211 to 3
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 1
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 2
22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 2
21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 1
21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 3
13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 0
13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-3066724191 to 1
要解决此问题,需要回滚到以前稳定的 Deployment 版本。
检查 Deployment 上线历史 按照如下步骤检查回滚历史:
首先,检查 Deployment 修订历史:
kubectl rollout history deployment/nginx-deployment
输出类似于:
deployments "nginx-deployment"
REVISION CHANGE-CAUSE
1 kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml
2 kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
3 kubectl set image deployment/nginx-deployment nginx=nginx:1.161
CHANGE-CAUSE
的内容是从 Deployment 的 kubernetes.io/change-cause
注解复制过来的。
复制动作发生在修订版本创建时。你可以通过以下方式设置 CHANGE-CAUSE
消息:
使用 kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1"
为 Deployment 添加注解。 手动编辑资源的清单。 要查看修订历史的详细信息,运行:
kubectl rollout history deployment/nginx-deployment --revision= 2
输出类似于:
deployments "nginx-deployment" revision 2
Labels: app=nginx
pod-template-hash=1159050644
Annotations: kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
QoS Tier:
cpu: BestEffort
memory: BestEffort
Environment Variables: <none>
No volumes.
回滚到之前的修订版本 按照下面给出的步骤将 Deployment 从当前版本回滚到以前的版本(即版本 2)。
假定现在你已决定撤消当前上线并回滚到以前的修订版本:
kubectl rollout undo deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment rolled back
或者,你也可以通过使用 --to-revision
来回滚到特定修订版本:
kubectl rollout undo deployment/nginx-deployment --to-revision= 2
输出类似于:
deployment.apps/nginx-deployment rolled back
与回滚相关的指令的更详细信息,请参考
kubectl rollout
。
现在,Deployment 正在回滚到以前的稳定版本。正如你所看到的,Deployment
控制器生成了回滚到修订版本 2 的 DeploymentRollback
事件。
检查回滚是否成功以及 Deployment 是否正在运行,运行:
kubectl get deployment nginx-deployment
输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 30m
获取 Deployment 描述信息:
kubectl describe deployment nginx-deployment
输出类似于:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Sun, 02 Sep 2018 18:17:55 -0500
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=4
kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
Selector: app=nginx
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.16.1
Port: 80/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-c4747d96c (3/3 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 12m deployment-controller Scaled up replica set nginx-deployment-75675f5897 to 3
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 1
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 2
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 2
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 1
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 3
Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 0
Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-595696685f to 1
Normal DeploymentRollback 15s deployment-controller Rolled back deployment "nginx-deployment" to revision 2
Normal ScalingReplicaSet 15s deployment-controller Scaled down replica set nginx-deployment-595696685f to 0
缩放 Deployment 你可以使用如下指令缩放 Deployment:
kubectl scale deployment/nginx-deployment --replicas= 10
输出类似于:
deployment.apps/nginx-deployment scaled
假设集群启用了Pod 的水平自动缩放 ,
你可以为 Deployment 设置自动缩放器,并基于现有 Pod 的 CPU 利用率选择要运行的
Pod 个数下限和上限。
kubectl autoscale deployment/nginx-deployment --min= 10 --max= 15 --cpu-percent= 80
输出类似于:
deployment.apps/nginx-deployment scaled
比例缩放 RollingUpdate 的 Deployment 支持同时运行应用程序的多个版本。
当自动缩放器缩放处于上线进程(仍在进行中或暂停)中的 RollingUpdate Deployment 时,
Deployment 控制器会平衡现有的活跃状态的 ReplicaSets(含 Pod 的 ReplicaSets)中的额外副本,
以降低风险。这称为 比例缩放(Proportional Scaling) 。
例如,你正在运行一个 10 个副本的 Deployment,其
maxSurge =3,maxUnavailable =2。
镜像更新使用 ReplicaSet nginx-deployment-1989198191
启动新的上线过程,
但由于上面提到的 maxUnavailable
要求,该进程被阻塞了。检查上线状态:
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1989198191 5 5 0 9s
nginx-deployment-618515232 8 8 8 1m
然后,出现了新的 Deployment 扩缩请求。自动缩放器将 Deployment 副本增加到 15。
Deployment 控制器需要决定在何处添加 5 个新副本。如果未使用比例缩放,所有 5 个副本
都将添加到新的 ReplicaSet 中。使用比例缩放时,可以将额外的副本分布到所有 ReplicaSet。
较大比例的副本会被添加到拥有最多副本的 ReplicaSet,而较低比例的副本会进入到
副本较少的 ReplicaSet。所有剩下的副本都会添加到副本最多的 ReplicaSet。
具有零副本的 ReplicaSets 不会被扩容。 在上面的示例中,3 个副本被添加到旧 ReplicaSet 中,2 个副本被添加到新 ReplicaSet。
假定新的副本都很健康,上线过程最终应将所有副本迁移到新的 ReplicaSet 中。
要确认这一点,请运行:
输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 15 18 7 8 7m
上线状态确认了副本是如何被添加到每个 ReplicaSet 的。
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1989198191 7 7 0 7m
nginx-deployment-618515232 11 11 11 7m
暂停、恢复 Deployment 的上线过程 在你更新一个 Deployment 的时候,或者计划更新它的时候,
你可以在触发一个或多个更新之前暂停 Deployment 的上线过程。
当你准备应用这些变更时,你可以重新恢复 Deployment 上线过程。
这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。
使用如下指令暂停上线:
kubectl rollout pause deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment paused
你可以根据需要执行很多更新操作,例如,可以要使用的资源:
kubectl set resources deployment/nginx-deployment -c= nginx --limits= cpu = 200m,memory= 512Mi
输出类似于:
deployment.apps/nginx-deployment resource requirements updated
暂停 Deployment 上线之前的初始状态将继续发挥作用,但新的更新在 Deployment
上线被暂停期间不会产生任何效果。
说明: 你不可以回滚处于暂停状态的 Deployment,除非先恢复其执行状态。
Deployment 状态 Deployment 的生命周期中会有许多状态。上线新的 ReplicaSet 期间可能处于
Progressing(进行中) ,可能是
Complete(已完成) ,也可能是
Failed(失败) 以至于无法继续进行。
进行中的 Deployment 执行下面的任务期间,Kubernetes 标记 Deployment 为进行中 (Progressing)_:
Deployment 创建新的 ReplicaSet Deployment 正在为其最新的 ReplicaSet 扩容 Deployment 正在为其旧有的 ReplicaSet(s) 缩容 新的 Pod 已经就绪或者可用(就绪至少持续了 MinReadySeconds 秒)。 当上线过程进入“Progressing”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetCreated
| reason: FoundNewReplicaSet
| reason: ReplicaSetUpdated
你可以使用 kubectl rollout status
监视 Deployment 的进度。
完成的 Deployment 当 Deployment 具有以下特征时,Kubernetes 将其标记为完成(Complete) ;
与 Deployment 关联的所有副本都已更新到指定的最新版本,这意味着之前请求的所有更新都已完成。 与 Deployment 关联的所有副本都可用。 未运行 Deployment 的旧副本。 当上线过程进入“Complete”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetAvailable
这一 Progressing
状况的状态值会持续为 "True"
,直至新的上线动作被触发。
即使副本的可用状态发生变化(进而影响 Available
状况),Progressing
状况的值也不会变化。
你可以使用 kubectl rollout status
检查 Deployment 是否已完成。
如果上线成功完成,kubectl rollout status
返回退出代码 0。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
从 kubectl rollout
命令获得的返回状态为 0(成功):
0
失败的 Deployment 你的 Deployment 可能会在尝试部署其最新的 ReplicaSet 受挫,一直处于未完成状态。
造成此情况一些可能因素如下:
配额(Quota)不足 就绪探测(Readiness Probe)失败 镜像拉取错误 权限不足 限制范围(Limit Ranges)问题 应用程序运行时的配置错误 检测此状况的一种方法是在 Deployment 规约中指定截止时间参数:
(.spec.progressDeadlineSeconds
)。
.spec.progressDeadlineSeconds
给出的是一个秒数值,Deployment 控制器在(通过 Deployment 状态)
标示 Deployment 进展停滞之前,需要等待所给的时长。
以下 kubectl
命令设置规约中的 progressDeadlineSeconds
,从而告知控制器
在 10 分钟后报告 Deployment 的上线没有进展:
kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
输出类似于:
deployment.apps/nginx-deployment patched
超过截止时间后,Deployment 控制器将添加具有以下属性的 Deployment 状况到
Deployment 的 .status.conditions
中:
type: Progressing
status: "False"
reason: ProgressDeadlineExceeded
这一状况也可能会比较早地失败,因而其状态值被设置为 "False"
,
其原因为 ReplicaSetCreateError
。
一旦 Deployment 上线完成,就不再考虑其期限。
参考
Kubernetes API Conventions
获取更多状态状况相关的信息。
说明: 除了报告 Reason=ProgressDeadlineExceeded
状态之外,Kubernetes 对已停止的
Deployment 不执行任何操作。更高级别的编排器可以利用这一设计并相应地采取行动。
例如,将 Deployment 回滚到其以前的版本。
说明: 如果你暂停了某个 Deployment 上线,Kubernetes 不再根据指定的截止时间检查 Deployment 上线的进展。
你可以在上线过程中间安全地暂停 Deployment 再恢复其执行,这样做不会导致超出最后时限的问题。
Deployment 可能会出现瞬时性的错误,可能因为设置的超时时间过短,
也可能因为其他可认为是临时性的问题。例如,假定所遇到的问题是配额不足。
如果描述 Deployment,你将会注意到以下部分:
kubectl describe deployment nginx-deployment
输出类似于:
<...>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
ReplicaFailure True FailedCreate
<...>
如果运行 kubectl get deployment nginx-deployment -o yaml
,Deployment 状态输出
将类似于这样:
status:
availableReplicas: 2
conditions:
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: Replica set "nginx-deployment-4262182780" is progressing.
reason: ReplicaSetUpdated
status: "True"
type: Progressing
- lastTransitionTime: 2016-10-04T12:25:42Z
lastUpdateTime: 2016-10-04T12:25:42Z
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
object-counts, requested: pods=1, used: pods=3, limited: pods=2'
reason: FailedCreate
status: "True"
type: ReplicaFailure
observedGeneration: 3
replicas: 2
unavailableReplicas: 2
最终,一旦超过 Deployment 进度限期,Kubernetes 将更新状态和进度状况的原因:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded
ReplicaFailure True FailedCreate
可以通过缩容 Deployment 或者缩容其他运行状态的控制器,或者直接在命名空间中增加配额
来解决配额不足的问题。如果配额条件满足,Deployment 控制器完成了 Deployment 上线操作,
Deployment 状态会更新为成功状况(Status=True
和 Reason=NewReplicaSetAvailable
)。
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
type: Available
加上 status: True
意味着 Deployment 具有最低可用性。
最低可用性由 Deployment 策略中的参数指定。
type: Progressing
加上 status: True
表示 Deployment 处于上线过程中,并且正在运行,
或者已成功完成进度,最小所需新副本处于可用。
请参阅对应状况的 Reason 了解相关细节。
在我们的案例中 reason: NewReplicaSetAvailable
表示 Deployment 已完成。
你可以使用 kubectl rollout status
检查 Deployment 是否未能取得进展。
如果 Deployment 已超过进度限期,kubectl rollout status
返回非零退出代码。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
kubectl rollout
命令的退出状态为 1(表明发生了错误):
1
对失败 Deployment 的操作 可应用于已完成的 Deployment 的所有操作也适用于失败的 Deployment。
你可以对其执行扩缩容、回滚到以前的修订版本等操作,或者在需要对 Deployment 的
Pod 模板应用多项调整时,将 Deployment 暂停。
清理策略 你可以在 Deployment 中设置 .spec.revisionHistoryLimit
字段以指定保留此
Deployment 的多少个旧有 ReplicaSet。其余的 ReplicaSet 将在后台被垃圾回收。
默认情况下,此值为 10。
说明: 显式将此字段设置为 0 将导致 Deployment 的所有历史记录被清空,因此 Deployment 将无法回滚。
金丝雀部署 如果要使用 Deployment 向用户子集或服务器子集上线版本,
则可以遵循资源管理 所描述的金丝雀模式,
创建多个 Deployment,每个版本一个。
编写 Deployment 规约 同其他 Kubernetes 配置一样, Deployment 需要 .apiVersion
,.kind
和 .metadata
字段。
有关配置文件的其他信息,请参考部署 Deployment 、
配置容器和使用 kubectl 管理资源 等相关文档。
Deployment 对象的名称必须是合法的
DNS 子域名 。
Deployment 还需要 .spec
部分 。
Pod 模板 .spec
中只有 .spec.template
和 .spec.selector
是必需的字段。
.spec.template
是一个 Pod 模板 。
它和 Pod 的语法规则完全相同。
只是这里它是嵌套的,因此不需要 apiVersion
或 kind
。
除了 Pod 的必填字段外,Deployment 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。
对于标签,请确保不要与其他控制器重叠。请参考选择算符 。
只有 .spec.template.spec.restartPolicy
等于 Always
才是被允许的,这也是在没有指定时的默认设置。
副本 .spec.replicas
是指定所需 Pod 的可选字段。它的默认值是1。
如果你对某个 Deployment 执行了手动扩缩操作(例如,通过
kubectl scale deployment deployment --replicas=X
),
之后基于清单对 Deployment 执行了更新操作(例如通过运行
kubectl apply -f deployment.yaml
),那么通过应用清单而完成的更新会覆盖之前手动扩缩所作的变更。
如果一个 HorizontalPodAutoscaler
(或者其他执行水平扩缩操作的类似 API)在管理 Deployment 的扩缩,
则不要设置 .spec.replicas
。
恰恰相反,应该允许 Kubernetes
控制面 来自动管理
.spec.replicas
字段。
选择算符 .spec.selector
是指定本 Deployment 的 Pod
标签选择算符 的必需字段。
.spec.selector
必须匹配 .spec.template.metadata.labels
,否则请求会被 API 拒绝。
在 API apps/v1
版本中,.spec.selector
和 .metadata.labels
如果没有设置的话,
不会被默认设置为 .spec.template.metadata.labels
,所以需要明确进行设置。
同时在 apps/v1
版本中,Deployment 创建后 .spec.selector
是不可变的。
当 Pod 的标签和选择算符匹配,但其模板和 .spec.template
不同时,或者此类 Pod
的总数超过 .spec.replicas
的设置时,Deployment 会终结之。
如果 Pod 总数未达到期望值,Deployment 会基于 .spec.template
创建新的 Pod。
说明: 你不应直接创建与此选择算符匹配的 Pod,也不应通过创建另一个 Deployment 或者类似于
ReplicaSet 或 ReplicationController 这类控制器来创建标签与此选择算符匹配的 Pod。
如果这样做,第一个 Deployment 会认为它创建了这些 Pod。
Kubernetes 不会阻止你这么做。
如果有多个控制器的选择算符发生重叠,则控制器之间会因冲突而无法正常工作。
策略 .spec.strategy
策略指定用于用新 Pod 替换旧 Pod 的策略。
.spec.strategy.type
可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。
重新创建 Deployment 如果 .spec.strategy.type==Recreate
,在创建新 Pod 之前,所有现有的 Pod 会被杀死。
说明: 这只会确保为了升级而创建新 Pod 之前其他 Pod 都已终止。如果你升级一个 Deployment,
所有旧版本的 Pod 都会立即被终止。控制器等待这些 Pod 被成功移除之后,
才会创建新版本的 Pod。如果你手动删除一个 Pod,其生命周期是由 ReplicaSet 来控制的,
后者会立即创建一个替换 Pod(即使旧的 Pod 仍然处于 Terminating 状态)。
如果你需要一种“最多 n 个”的 Pod 个数保证,你需要考虑使用
StatefulSet 。
滚动更新 Deployment Deployment 会在 .spec.strategy.type==RollingUpdate
时,采取
滚动更新的方式更新 Pod。你可以指定 maxUnavailable
和 maxSurge
来控制滚动更新
过程。
最大不可用 .spec.strategy.rollingUpdate.maxUnavailable
是一个可选字段,用来指定
更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是所需
Pod 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。
如果 .spec.strategy.rollingUpdate.maxSurge
为 0,则此值不能为 0。
默认值为 25%。
例如,当此值设置为 30% 时,滚动更新开始时会立即将旧 ReplicaSet 缩容到期望 Pod 个数的70%。
新 Pod 准备就绪后,可以继续缩容旧有的 ReplicaSet,然后对新的 ReplicaSet 扩容,
确保在更新期间可用的 Pod 总数在任何时候都至少为所需的 Pod 个数的 70%。
最大峰值 .spec.strategy.rollingUpdate.maxSurge
是一个可选字段,用来指定可以创建的超出期望
Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pod 的百分比(例如,10%)。
如果 MaxUnavailable
为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。
此字段的默认值为 25%。
例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod
的总数不超过所需 Pod 总数的 130%。一旦旧 Pod 被杀死,新的 ReplicaSet 可以进一步扩容,
同时确保更新期间的任何时候运行中的 Pod 总数最多为所需 Pod 总数的 130%。
进度期限秒数 .spec.progressDeadlineSeconds
是一个可选字段,用于指定系统在报告 Deployment
进展失败 之前等待 Deployment 取得进展的秒数。
这类报告会在资源状态中体现为 type: Progressing
、status: False
、
reason: ProgressDeadlineExceeded
。Deployment 控制器将在默认 600 毫秒内持续重试 Deployment。
将来,一旦实现了自动回滚,Deployment 控制器将在探测到这样的条件时立即回滚 Deployment。
如果指定,则此字段值需要大于 .spec.minReadySeconds
取值。
最短就绪时间 .spec.minReadySeconds
是一个可选字段,用于指定新创建的 Pod
在没有任意容器崩溃情况下的最小就绪时间,
只有超出这个时间 Pod 才被视为可用。默认值为 0(Pod 在准备就绪后立即将被视为可用)。
要了解何时 Pod 被视为就绪,
可参考容器探针 。
修订历史限制 Deployment 的修订历史记录存储在它所控制的 ReplicaSets 中。
.spec.revisionHistoryLimit
是一个可选字段,用来设定出于回滚目的所要保留的旧 ReplicaSet 数量。
这些旧 ReplicaSet 会消耗 etcd 中的资源,并占用 kubectl get rs
的输出。
每个 Deployment 修订版本的配置都存储在其 ReplicaSets 中;因此,一旦删除了旧的 ReplicaSet,
将失去回滚到 Deployment 的对应修订版本的能力。
默认情况下,系统保留 10 个旧 ReplicaSet,但其理想值取决于新 Deployment 的频率和稳定性。
更具体地说,将此字段设置为 0 意味着将清理所有具有 0 个副本的旧 ReplicaSet。
在这种情况下,无法撤消新的 Deployment 上线,因为它的修订历史被清除了。
paused(暂停的) .spec.paused
是用于暂停和恢复 Deployment 的可选布尔字段。
暂停的 Deployment 和未暂停的 Deployment 的唯一区别是,Deployment 处于暂停状态时,
PodTemplateSpec 的任何修改都不会触发新的上线。
Deployment 在创建时是默认不会处于暂停状态。
接下来 3.5.2.2 - ReplicaSet ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。
因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。
ReplicaSet 的工作原理 ReplicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod
的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod
以满足副本个数条件时要使用的 Pod 模板等等。
每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值,
进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。
ReplicaSet 通过 Pod 上的
metadata.ownerReferences
字段连接到附属 Pod,该字段给出当前对象的属主资源。
ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet
的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态,
并据此计划其操作行为。
ReplicaSet 使用其选择算符来辨识要获得的 Pod 集合。如果某个 Pod 没有
OwnerReference 或者其 OwnerReference 不是一个控制器 ,
且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。
何时使用 ReplicaSet ReplicaSet 确保任何时间都有指定数量的 Pod 副本在运行。
然而,Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod
提供声明式的更新以及许多其他有用的功能。
因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet,
除非你需要自定义更新业务流程或根本不需要更新。
这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是使用
Deployment,并在 spec 部分定义你的应用。
示例 apiVersion : apps/v1
kind : ReplicaSet
metadata :
name : frontend
labels :
app : guestbook
tier : frontend
spec :
# 按你的实际情况修改副本数
replicas : 3
selector :
matchLabels :
tier : frontend
template :
metadata :
labels :
tier : frontend
spec :
containers :
- name : php-redis
image : gcr.io/google_samples/gb-frontend:v3
将此清单保存到 frontend.yaml
中,并将其提交到 Kubernetes 集群,
就能创建 yaml 文件所定义的 ReplicaSet 及其管理的 Pod。
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你可以看到当前被部署的 ReplicaSet:
并看到你所创建的前端:
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 6s
你也可以查看 ReplicaSet 的状态:
kubectl describe rs/frontend
你会看到类似如下的输出:
Name: frontend
Namespace: default
Selector: tier=frontend
Labels: app=guestbook
tier=frontend
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"labels":{"app":"guestbook","tier":"frontend"},"name":"frontend",...
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: tier=frontend
Containers:
php-redis:
Image: gcr.io/google_samples/gb-frontend:v3
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 117s replicaset-controller Created pod: frontend-wtsmm
Normal SuccessfulCreate 116s replicaset-controller Created pod: frontend-b2zdv
Normal SuccessfulCreate 116s replicaset-controller Created pod: frontend-vcmts
最后可以查看启动了的 Pod 集合:
你会看到类似如下的 Pod 信息:
NAME READY STATUS RESTARTS AGE
frontend-b2zdv 1/1 Running 0 6m36s
frontend-vcmts 1/1 Running 0 6m36s
frontend-wtsmm 1/1 Running 0 6m36s
你也可以查看 Pod 的属主引用被设置为前端的 ReplicaSet。
要实现这点,可取回运行中的某个 Pod 的 YAML:
kubectl get pods frontend-b2zdv -o yaml
输出将类似这样,frontend ReplicaSet 的信息被设置在 metadata 的
ownerReferences
字段中:
apiVersion : v1
kind : Pod
metadata :
creationTimestamp : "2020-02-12T07:06:16Z"
generateName : frontend-
labels :
tier : frontend
name : frontend-b2zdv
namespace : default
ownerReferences :
- apiVersion : apps/v1
blockOwnerDeletion : true
controller : true
kind : ReplicaSet
name : frontend
uid : f391f6db-bb9b-4c09-ae74-6a1f77f3d5cf
...
非模板 Pod 的获得 尽管你完全可以直接创建裸的 Pod,强烈建议你确保这些裸的 Pod 并不包含可能与你的某个
ReplicaSet 的选择算符相匹配的标签。原因在于 ReplicaSet 并不仅限于拥有在其模板中设置的
Pod,它还可以像前面小节中所描述的那样获得其他 Pod。
以前面的 frontend ReplicaSet 为例,并在以下清单中指定这些 Pod:
apiVersion : v1
kind : Pod
metadata :
name : pod1
labels :
tier : frontend
spec :
containers :
- name : hello1
image : gcr.io/google-samples/hello-app:2.0
---
apiVersion : v1
kind : Pod
metadata :
name : pod2
labels :
tier : frontend
spec :
containers :
- name : hello2
image : gcr.io/google-samples/hello-app:1.0
由于这些 Pod 没有控制器(Controller,或其他对象)作为其属主引用,
并且其标签与 frontend ReplicaSet 的选择算符匹配,它们会立即被该 ReplicaSet 获取。
假定你在 frontend ReplicaSet 已经被部署之后创建 Pod,并且你已经在 ReplicaSet
中设置了其初始的 Pod 副本数以满足其副本计数需要:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
新的 Pod 会被该 ReplicaSet 获取,并立即被 ReplicaSet 终止,
因为它们的存在会使得 ReplicaSet 中 Pod 个数超出其期望值。
取回 Pod:
输出显示新的 Pod 或者已经被终止,或者处于终止过程中:
NAME READY STATUS RESTARTS AGE
frontend-b2zdv 1/1 Running 0 10m
frontend-vcmts 1/1 Running 0 10m
frontend-wtsmm 1/1 Running 0 10m
pod1 0/1 Terminating 0 1s
pod2 0/1 Terminating 0 1s
如果你先行创建 Pod:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
之后再创建 ReplicaSet:
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你会看到 ReplicaSet 已经获得了该 Pod,并仅根据其规约创建新的 Pod,
直到新的 Pod 和原来的 Pod 的总数达到其预期个数。
这时取回 Pod 列表:
将会生成下面的输出:
NAME READY STATUS RESTARTS AGE
frontend-hmmj2 1/1 Running 0 9s
pod1 1/1 Running 0 36s
pod2 1/1 Running 0 36s
采用这种方式,一个 ReplicaSet 中可以包含异质的 Pod 集合。
编写 ReplicaSet 的清单 与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersion
、kind
、和 metadata
字段。
对于 ReplicaSet 而言,其 kind
始终是 ReplicaSet。
ReplicaSet 对象的名称必须是合法的
DNS 子域名 。
ReplicaSet 也需要
.spec
部分。
Pod 模板 .spec.template
是一个 Pod 模板 ,
要求设置标签。在 frontend.yaml
示例中,我们指定了标签 tier: frontend
。
注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。
对于模板的重启策略
字段,.spec.template.spec.restartPolicy
,唯一允许的取值是 Always
,这也是默认值.
Pod 选择算符 .spec.selector
字段是一个标签选择算符 。
如前文中所讨论的 ,这些是用来标识要被获取的 Pod
的标签。在签名的 frontend.yaml
示例中,选择算符为:
matchLabels :
tier : frontend
在 ReplicaSet 中,.spec.template.metadata.labels
的值必须与 spec.selector
值相匹配,否则该配置会被 API 拒绝。
说明: 对于设置了相同的 .spec.selector
,但
.spec.template.metadata.labels
和 .spec.template.spec
字段不同的两个
ReplicaSet 而言,每个 ReplicaSet 都会忽略被另一个 ReplicaSet 所创建的 Pod。
Replicas 你可以通过设置 .spec.replicas
来指定要同时运行的 Pod 个数。
ReplicaSet 创建、删除 Pod 以与此值匹配。
如果你没有指定 .spec.replicas
,那么默认值为 1。
使用 ReplicaSet 删除 ReplicaSet 和它的 Pod 要删除 ReplicaSet 和它的所有 Pod,使用
kubectl delete
命令。
默认情况下,垃圾收集器
自动删除所有依赖的 Pod。
当使用 REST API 或 client-go
库时,你必须在 -d
选项中将 propagationPolicy
设置为 Background
或 Foreground
。例如:
kubectl proxy --port= 8080
curl -X DELETE 'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
-H "Content-Type: application/json"
只删除 ReplicaSet 你可以只删除 ReplicaSet 而不影响它的各个 Pod,方法是使用
kubectl delete
命令并设置 --cascade=orphan
选项。
当使用 REST API 或 client-go
库时,你必须将 propagationPolicy
设置为 Orphan
。
例如:
kubectl proxy --port= 8080
curl -X DELETE 'localhost:8080/apis/apps/v1/namespaces/default/replicasets/frontend' \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"
一旦删除了原来的 ReplicaSet,就可以创建一个新的来替换它。
由于新旧 ReplicaSet 的 .spec.selector
是相同的,新的 ReplicaSet 将接管老的 Pod。
但是,它不会努力使现有的 Pod 与新的、不同的 Pod 模板匹配。
若想要以可控的方式更新 Pod 的规约,可以使用
Deployment
资源,因为 ReplicaSet 并不直接支持滚动更新。
将 Pod 从 ReplicaSet 中隔离 可以通过改变标签来从 ReplicaSet 中移除 Pod。
这种技术可以用来从服务中去除 Pod,以便进行排错、数据恢复等。
以这种方式移除的 Pod 将被自动替换(假设副本的数量没有改变)。
扩缩 ReplicaSet 通过更新 .spec.replicas
字段,ReplicaSet 可以被轻松地进行扩缩。ReplicaSet
控制器能确保匹配标签选择器的数量的 Pod 是可用的和可操作的。
在降低集合规模时,ReplicaSet 控制器通过对可用的所有 Pod 进行排序来优先选择要被删除的那些 Pod。
其一般性算法如下:
首先选择剔除悬决(Pending,且不可调度)的各个 Pod 如果设置了 controller.kubernetes.io/pod-deletion-cost
注解,则注解值较小的优先被裁减掉 所处节点上副本个数较多的 Pod 优先于所处节点上副本较少者 如果 Pod 的创建时间不同,最近创建的 Pod 优先于早前创建的 Pod 被裁减。
(当 LogarithmicScaleDown
这一特性门控
被启用时,创建时间是按整数幂级来分组的)。 如果以上比较结果都相同,则随机选择。
Pod 删除开销 特性状态: Kubernetes v1.22 [beta]
通过使用 controller.kubernetes.io/pod-deletion-cost
注解,用户可以对 ReplicaSet 缩容时要先删除哪些 Pod 设置偏好。
此注解要设置到 Pod 上,取值范围为 [-2147483647, 2147483647]。
所代表的是删除同一 ReplicaSet 中其他 Pod 相比较而言的开销。
删除开销较小的 Pod 比删除开销较高的 Pod 更容易被删除。
Pod 如果未设置此注解,则隐含的设置值为 0。负值也是可接受的。
如果注解值非法,API 服务器会拒绝对应的 Pod。
此功能特性处于 Beta 阶段,默认被启用。你可以通过为 kube-apiserver 和
kube-controller-manager 设置特性门控
PodDeletionCost
来禁用此功能。
说明: 此机制实施时仅是尽力而为,并不能对 Pod 的删除顺序作出任何保证; 用户应避免频繁更新注解值,例如根据某观测度量值来更新此注解值是应该避免的。
这样做会在 API 服务器上产生大量的 Pod 更新操作。 使用场景示例 同一应用的不同 Pod 可能其利用率是不同的。在对应用执行缩容操作时,
可能希望移除利用率较低的 Pod。为了避免频繁更新 Pod,应用应该在执行缩容操作之前更新一次
controller.kubernetes.io/pod-deletion-cost
注解值
(将注解值设置为一个与其 Pod 利用率对应的值)。
如果应用自身控制器缩容操作时(例如 Spark 部署的驱动 Pod),这种机制是可以起作用的。
ReplicaSet 作为水平的 Pod 自动扩缩器目标 ReplicaSet 也可以作为水平的 Pod 扩缩器 (HPA)
的目标。也就是说,ReplicaSet 可以被 HPA 自动扩缩。
以下是 HPA 以我们在前一个示例中创建的副本集为目标的示例。
apiVersion : autoscaling/v1
kind : HorizontalPodAutoscaler
metadata :
name : frontend-scaler
spec :
scaleTargetRef :
kind : ReplicaSet
name : frontend
minReplicas : 3
maxReplicas : 10
targetCPUUtilizationPercentage : 50
将这个列表保存到 hpa-rs.yaml
并提交到 Kubernetes 集群,就能创建它所定义的
HPA,进而就能根据复制的 Pod 的 CPU 利用率对目标 ReplicaSet 进行自动扩缩。
kubectl apply -f https://k8s.io/examples/controllers/hpa-rs.yaml
或者,可以使用 kubectl autoscale
命令完成相同的操作(而且它更简单!)
kubectl autoscale rs frontend --max= 10 --min= 3 --cpu-percent= 50
ReplicaSet 的替代方案 Deployment(推荐) Deployment
是一个可以拥有
ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。
尽管 ReplicaSet 可以独立使用,目前它们的主要用途是提供给 Deployment 作为编排
Pod 创建、删除和更新的一种机制。当使用 Deployment 时,你不必关心如何管理它所创建的
ReplicaSet,Deployment 拥有并管理其 ReplicaSet。
因此,建议你在需要 ReplicaSet 时使用 Deployment。
裸 Pod 与用户直接创建 Pod 的情况不同,ReplicaSet 会替换那些由于某些原因被删除或被终止的
Pod,例如在节点故障或破坏性的节点维护(如内核升级)的情况下。
因为这个原因,我们建议你使用 ReplicaSet,即使应用程序只需要一个 Pod。
想像一下,ReplicaSet 类似于进程监视器,只不过它在多个节点上监视多个 Pod,
而不是在单个节点上监视单个进程。
ReplicaSet 将本地容器重启的任务委托给了节点上的某个代理(例如,Kubelet)去完成。
Job 使用Job
代替 ReplicaSet,
可以用于那些期望自行终止的 Pod。
DaemonSet 对于管理那些提供主机级别功能(如主机监控和主机日志)的容器,
就要用 DaemonSet
而不用 ReplicaSet。
这些 Pod 的寿命与主机寿命有关:这些 Pod 需要先于主机上的其他 Pod 运行,
并且在机器准备重新启动/关闭时安全地终止。
ReplicationController ReplicaSet 是 ReplicationController
的后继者。二者目的相同且行为类似,只是 ReplicationController 不支持
标签用户指南
中讨论的基于集合的选择算符需求。
因此,相比于 ReplicationController,应优先考虑 ReplicaSet。
接下来 3.5.2.3 - StatefulSet StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理某 Pod 集合的部署和扩缩,
并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似,
StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是,
StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的,
但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。
尽管 StatefulSet 中的单个 Pod 仍可能出现故障,
但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。
使用 StatefulSet StatefulSet 对于需要满足以下一个或多个需求的应用程序很有价值:
稳定的、唯一的网络标识符。 稳定的、持久的存储。 有序的、优雅的部署和扩缩。 有序的、自动的滚动更新。 在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。
如果应用程序不需要任何稳定的标识符或有序的部署、删除或扩缩,
则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如
Deployment 或者
ReplicaSet
可能更适用于你的无状态应用部署需要。
限制 给定 Pod 的存储必须由
PersistentVolume Provisioner
基于所请求的 storage class
来制备,或者由管理员预先制备。 删除或者扩缩 StatefulSet 并不会 删除它关联的存储卷。
这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。 StatefulSet 当前需要无头服务 来负责 Pod
的网络标识。你需要负责创建此服务。 当删除一个 StatefulSet 时,该 StatefulSet 不提供任何终止 Pod 的保证。
为了实现 StatefulSet 中的 Pod 可以有序且体面地终止,可以在删除之前将 StatefulSet
缩容到 0。 在默认 Pod 管理策略 (OrderedReady
) 时使用滚动更新 ,
可能进入需要人工干预 才能修复的损坏状态。 组件 下面的示例演示了 StatefulSet 的组件。
apiVersion : v1
kind : Service
metadata :
name : nginx
labels :
app : nginx
spec :
ports :
- port : 80
name : web
clusterIP : None
selector :
app : nginx
---
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : web
spec :
selector :
matchLabels :
app : nginx # 必须匹配 .spec.template.metadata.labels
serviceName : "nginx"
replicas : 3 # 默认值是 1
minReadySeconds : 10 # 默认值是 0
template :
metadata :
labels :
app : nginx # 必须匹配 .spec.selector.matchLabels
spec :
terminationGracePeriodSeconds : 10
containers :
- name : nginx
image : registry.k8s.io/nginx-slim:0.8
ports :
- containerPort : 80
name : web
volumeMounts :
- name : www
mountPath : /usr/share/nginx/html
volumeClaimTemplates :
- metadata :
name : www
spec :
accessModes : [ "ReadWriteOnce" ]
storageClassName : "my-storage-class"
resources :
requests :
storage : 1Gi
上述例子中:
名为 nginx
的 Headless Service 用来控制网络域名。 名为 web
的 StatefulSet 有一个 Spec,它表明将在独立的 3 个 Pod 副本中启动 nginx 容器。 volumeClaimTemplates
将通过 PersistentVolume 制备程序所准备的
PersistentVolumes 来提供稳定的存储。StatefulSet 的命名需要遵循
DNS 子域名 规范。
Pod 选择算符 你必须设置 StatefulSet 的 .spec.selector
字段,使之匹配其在
.spec.template.metadata.labels
中设置的标签。
未指定匹配的 Pod 选择算符将在创建 StatefulSet 期间导致验证错误。
卷申领模板 你可以设置 .spec.volumeClaimTemplates
,
它可以使用 PersistentVolume 制备程序所准备的
PersistentVolumes 来提供稳定的存储。
最短就绪秒数 特性状态: Kubernetes v1.25 [stable]
.spec.minReadySeconds
是一个可选字段。
它指定新创建的 Pod 应该在没有任何容器崩溃的情况下运行并准备就绪,才能被认为是可用的。
这用于在使用滚动更新 策略时检查滚动的进度。
该字段默认为 0(Pod 准备就绪后将被视为可用)。
要了解有关何时认为 Pod 准备就绪的更多信息,
请参阅容器探针 。
Pod 标识 StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。
该标识和 Pod 是绑定的,与该 Pod 调度到哪个节点上无关。
有序索引 对于具有 N 个副本的 StatefulSet,该 StatefulSet 中的每个 Pod 将被分配一个从 0 到 N-1
的整数序号,该序号在此 StatefulSet 上是唯一的。
稳定的网络 ID StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。
组合主机名的格式为$(StatefulSet 名称)-$(序号)
。
上例将会创建三个名称分别为 web-0、web-1、web-2
的 Pod。
StatefulSet 可以使用无头服务 控制它的
Pod 的网络域。管理域的这个服务的格式为:
$(服务名称).$(名字空间).svc.cluster.local
,其中 cluster.local
是集群域。
一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:
$(pod 名称).$(所属服务的 DNS 域名)
,其中所属服务由 StatefulSet 的 serviceName
域来设定。
取决于集群域内部 DNS 的配置,有可能无法查询一个刚刚启动的 Pod 的 DNS 命名。
当集群内其他客户端在 Pod 创建完成前发出 Pod 主机名查询时,就会发生这种情况。
负缓存 (在 DNS 中较为常见) 意味着之前失败的查询结果会被记录和重用至少若干秒钟,
即使 Pod 已经正常运行了也是如此。
如果需要在 Pod 被创建之后及时发现它们,可使用以下选项:
直接查询 Kubernetes API(比如,利用 watch 机制)而不是依赖于 DNS 查询 缩短 Kubernetes DNS 驱动的缓存时长(通常这意味着修改 CoreDNS 的 ConfigMap,目前缓存时长为 30 秒) 正如限制 中所述,
你需要负责创建无头服务 以便为 Pod 提供网络标识。
下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:
集群域名 服务(名字空间/名字) StatefulSet(名字空间/名字) StatefulSet 域名 Pod DNS Pod 主机名 cluster.local default/nginx default/web nginx.default.svc.cluster.local web-{0..N-1}.nginx.default.svc.cluster.local web-{0..N-1} cluster.local foo/nginx foo/web nginx.foo.svc.cluster.local web-{0..N-1}.nginx.foo.svc.cluster.local web-{0..N-1} kube.local foo/nginx foo/web nginx.foo.svc.kube.local web-{0..N-1}.nginx.foo.svc.kube.local web-{0..N-1}
说明: 集群域会被设置为 cluster.local
,除非有其他配置 。
稳定的存储 对于 StatefulSet 中定义的每个 VolumeClaimTemplate,每个 Pod 接收到一个 PersistentVolumeClaim。
在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class
制备的
1 Gib 的 PersistentVolume。
如果没有声明 StorageClass,就会使用默认的 StorageClass。
当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts
会挂载与其
PersistentVolumeClaims 相关联的 PersistentVolume。
请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的
PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。
Pod 名称标签 当 StatefulSet 控制器 创建 Pod 时,
它会添加一个标签 statefulset.kubernetes.io/pod-name
,该标签值设置为 Pod 名称。
这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。
部署和扩缩保证 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1
。 当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0
。 在将扩缩操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。 在一个 Pod 终止之前,所有的继任者必须完全关闭。 StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds
设置为 0。
这种做法是不安全的,要强烈阻止。
更多的解释请参考强制删除 StatefulSet Pod 。
在上面的 nginx 示例被创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。
在 web-0 进入 Running 和 Ready
状态前不会部署 web-1。在 web-1 进入 Running 和 Ready 状态前不会部署 web-2。
如果 web-1 已经处于 Running 和 Ready 状态,而 web-2 尚未部署,在此期间发生了
web-0 运行失败,那么 web-2 将不会被部署,要等到 web-0 部署完成并进入 Running 和
Ready 状态后,才会部署 web-2。
如果用户想将示例中的 StatefulSet 扩缩为 replicas=1
,首先被终止的是 web-2。
在 web-2 没有被完全停止和删除前,web-1 不会被终止。
当 web-2 已被终止和删除、web-1 尚未被终止,如果在此期间发生 web-0 运行失败,
那么就不会终止 web-1,必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。
Pod 管理策略 StatefulSet 允许你放宽其排序保证,
同时通过它的 .spec.podManagementPolicy
域保持其唯一性和身份保证。
OrderedReady Pod 管理 OrderedReady
Pod 管理是 StatefulSet 的默认设置。
它实现了上面 描述的功能。
并行 Pod 管理 Parallel
Pod 管理让 StatefulSet 控制器并行的启动或终止所有的 Pod,
启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 Ready 或者完全停止状态。
这个选项只会影响扩缩操作的行为,更新则不会被影响。
更新策略 StatefulSet 的 .spec.updateStrategy
字段让你可以配置和禁用掉自动滚动更新 Pod
的容器、标签、资源请求或限制、以及注解。有两个允许的值:
OnDelete
当 StatefulSet 的 .spec.updateStrategy.type
设置为 OnDelete
时,
它的控制器将不会自动更新 StatefulSet 中的 Pod。
用户必须手动删除 Pod 以便让控制器创建新的 Pod,以此来对 StatefulSet 的
.spec.template
的变动作出反应。 RollingUpdate
RollingUpdate
更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。这是默认的更新策略。滚动更新 当 StatefulSet 的 .spec.updateStrategy.type
被设置为 RollingUpdate
时,
StatefulSet 控制器会删除和重建 StatefulSet 中的每个 Pod。
它将按照与 Pod 终止相同的顺序(从最大序号到最小序号)进行,每次更新一个 Pod。
Kubernetes 控制平面会等到被更新的 Pod 进入 Running 和 Ready 状态,然后再更新其前身。
如果你设置了 .spec.minReadySeconds
(查看最短就绪秒数 ),
控制平面在 Pod 就绪后会额外等待一定的时间再执行下一步。
分区滚动更新 通过声明 .spec.updateStrategy.rollingUpdate.partition
的方式,RollingUpdate
更新策略可以实现分区。
如果声明了一个分区,当 StatefulSet 的 .spec.template
被更新时,
所有序号大于等于该分区序号的 Pod 都会被更新。
所有序号小于该分区序号的 Pod 都不会被更新,并且,即使它们被删除也会依据之前的版本进行重建。
如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition
大于它的
.spec.replicas
,则对它的 .spec.template
的更新将不会传递到它的 Pod。
在大多数情况下,你不需要使用分区,但如果你希望进行阶段更新、执行金丝雀或执行分阶段上线,则这些分区会非常有用。
最大不可用 Pod 特性状态: Kubernetes v1.24 [alpha]
你可以通过指定 .spec.updateStrategy.rollingUpdate.maxUnavailable
字段来控制更新期间不可用的 Pod 的最大数量。
该值可以是绝对值(例如,“5”)或者是期望 Pod 个数的百分比(例如,10%
)。
绝对值是根据百分比值四舍五入计算的。
该字段不能为 0。默认设置为 1。
该字段适用于 0
到 replicas - 1
范围内的所有 Pod。
如果在 0
到 replicas - 1
范围内存在不可用 Pod,这类 Pod 将被计入 maxUnavailable
值。
说明: maxUnavailable
字段处于 Alpha 阶段,仅当 API 服务器启用了 MaxUnavailableStatefulSet
特性门控 时才起作用。
强制回滚 在默认 Pod 管理策略 (OrderedReady
) 下使用滚动更新 ,
可能进入需要人工干预才能修复的损坏状态。
如果更新后 Pod 模板配置进入无法运行或就绪的状态(例如,
由于错误的二进制文件或应用程序级配置错误),StatefulSet 将停止回滚并等待。
在这种状态下,仅将 Pod 模板还原为正确的配置是不够的。
由于已知问题 ,StatefulSet
将继续等待损坏状态的 Pod 准备就绪(永远不会发生),然后再尝试将其恢复为正常工作配置。
恢复模板后,还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样,
StatefulSet 才会开始使用被还原的模板来重新创建 Pod。
PersistentVolumeClaim 保留 特性状态: Kubernetes v1.23 [alpha]
在 StatefulSet 的生命周期中,可选字段
.spec.persistentVolumeClaimRetentionPolicy
控制是否删除以及如何删除 PVC。
使用该字段,你必须在 API 服务器和控制器管理器启用 StatefulSetAutoDeletePVC
特性门控 。
启用后,你可以为每个 StatefulSet 配置两个策略:
whenDeleted
配置删除 StatefulSet 时应用的卷保留行为。 whenScaled
配置当 StatefulSet 的副本数减少时应用的卷保留行为;例如,缩小集合时。 对于你可以配置的每个策略,你可以将值设置为 Delete
或 Retain
。
Delete
对于受策略影响的每个 Pod,基于 StatefulSet 的 volumeClaimTemplate
字段创建的 PVC 都会被删除。
使用 whenDeleted
策略,所有来自 volumeClaimTemplate
的 PVC 在其 Pod 被删除后都会被删除。
使用 whenScaled
策略,只有与被缩减的 Pod 副本对应的 PVC 在其 Pod 被删除后才会被删除。 Retain
(默认)来自 volumeClaimTemplate
的 PVC 在 Pod 被删除时不受影响。这是此新功能之前的行为。 请记住,这些策略仅 适用于由于 StatefulSet 被删除或被缩小而被删除的 Pod。
例如,如果与 StatefulSet 关联的 Pod 由于节点故障而失败,
并且控制平面创建了替换 Pod,则 StatefulSet 保留现有的 PVC。
现有卷不受影响,集群会将其附加到新 Pod 即将启动的节点上。
策略的默认值为 Retain
,与此新功能之前的 StatefulSet 行为相匹配。
这是一个示例策略。
apiVersion : apps/v1
kind : StatefulSet
...
spec :
persistentVolumeClaimRetentionPolicy :
whenDeleted : Retain
whenScaled : Delete
...
StatefulSet 控制器 为其 PVC
添加了属主引用 ,
这些 PVC 在 Pod 终止后被垃圾回收器 删除。
这使 Pod 能够在删除 PVC 之前(以及在删除后备 PV 和卷之前,取决于保留策略)干净地卸载所有卷。
当你设置 whenDeleted
删除策略,对 StatefulSet 实例的属主引用放置在与该 StatefulSet 关联的所有 PVC 上。
whenScaled
策略必须仅在 Pod 缩减时删除 PVC,而不是在 Pod 因其他原因被删除时删除。
执行协调操作时,StatefulSet 控制器将其所需的副本数与集群上实际存在的 Pod 进行比较。
对于 StatefulSet 中的所有 Pod 而言,如果其 ID 大于副本数,则将被废弃并标记为需要删除。
如果 whenScaled
策略是 Delete
,则在删除 Pod 之前,
首先将已销毁的 Pod 设置为与 StatefulSet 模板对应的 PVC 的属主。
这会导致 PVC 仅在已废弃的 Pod 终止后被垃圾收集。
这意味着如果控制器崩溃并重新启动,在其属主引用更新到适合策略的 Pod 之前,不会删除任何 Pod。
如果在控制器关闭时强制删除了已废弃的 Pod,则属主引用可能已被设置,也可能未被设置,具体取决于控制器何时崩溃。
更新属主引用可能需要几个协调循环,因此一些已废弃的 Pod 可能已经被设置了属主引用,而其他可能没有。
出于这个原因,我们建议等待控制器恢复,控制器将在终止 Pod 之前验证属主引用。
如果这不可行,则操作员应验证 PVC 上的属主引用,以确保在强制删除 Pod 时删除预期的对象。
副本数 .spec.replicas
是一个可选字段,用于指定所需 Pod 的数量。它的默认值为 1。
如果你手动扩缩已部署的负载,例如通过 kubectl scale statefulset statefulset --replicas=X
,
然后根据清单更新 StatefulSet(例如:通过运行 kubectl apply -f statefulset.yaml
),
那么应用该清单的操作会覆盖你之前所做的手动扩缩。
如果 HorizontalPodAutoscaler
(或任何类似的水平扩缩 API)正在管理 StatefulSet 的扩缩,
请不要设置 .spec.replicas
。
相反,允许 Kubernetes 控制平面自动管理 .spec.replicas
字段。
接下来 3.5.2.4 - DaemonSet DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。
当有节点加入集群时, 也会为他们新增一个 Pod 。
当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
DaemonSet 的一些典型用法:
在每个节点上运行集群守护进程 在每个节点上运行日志收集守护进程 在每个节点上运行监控守护进程 一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。
一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志,
并且对不同硬件类型具有不同的内存、CPU 要求。
编写 DaemonSet Spec 创建 DaemonSet 你可以在 YAML 文件中描述 DaemonSet。
例如,下面的 daemonset.yaml 文件描述了一个运行 fluentd-elasticsearch Docker 镜像的 DaemonSet:
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : fluentd-elasticsearch
namespace : kube-system
labels :
k8s-app : fluentd-logging
spec :
selector :
matchLabels :
name : fluentd-elasticsearch
template :
metadata :
labels :
name : fluentd-elasticsearch
spec :
tolerations :
# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
- key : node-role.kubernetes.io/control-plane
operator : Exists
effect : NoSchedule
- key : node-role.kubernetes.io/master
operator : Exists
effect : NoSchedule
containers :
- name : fluentd-elasticsearch
image : quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources :
limits :
memory : 200Mi
requests :
cpu : 100m
memory : 200Mi
volumeMounts :
- name : varlog
mountPath : /var/log
terminationGracePeriodSeconds : 30
volumes :
- name : varlog
hostPath :
path : /var/log
基于 YAML 文件创建 DaemonSet:
kubectl apply -f https://k8s.io/examples/controllers/daemonset.yaml
必需字段 与所有其他 Kubernetes 配置一样,DaemonSet 也需要 apiVersion
、kind
和 metadata
字段。
有关使用这些配置文件的通用信息,
参见运行无状态应用 和使用 kubectl 管理对象 。
DaemonSet 对象的名称必须是一个合法的
DNS 子域名 。
DaemonSet 也需要 .spec
节区。
Pod 模板 .spec
中唯一必需的字段是 .spec.template
。
.spec.template
是一个 Pod 模板 。
除了它是嵌套的,因而不具有 apiVersion
或 kind
字段之外,它与
Pod 具有相同的 schema。
除了 Pod 必需字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 Pod 选择算符 )。
在 DaemonSet 中的 Pod 模板必须具有一个值为 Always
的
RestartPolicy
。
当该值未指定时,默认是 Always
。
Pod 选择算符 .spec.selector
字段表示 Pod 选择算符,它与
Job 的 .spec.selector
的作用是相同的。
你必须指定与 .spec.template
的标签匹配的 Pod 选择算符。
此外,一旦创建了 DaemonSet,它的 .spec.selector
就不能修改。
修改 Pod 选择算符可能导致 Pod 意外悬浮,并且这对用户来说是费解的。
spec.selector
是一个对象,如下两个字段组成:
matchLabels
- 与 ReplicationController
的 .spec.selector
的作用相同。matchExpressions
- 允许构建更加复杂的选择器,可以通过指定 key、value
列表以及将 key 和 value 列表关联起来的 Operator。当上述两个字段都指定时,结果会按逻辑与(AND)操作处理。
.spec.selector
必须与 .spec.template.metadata.labels
相匹配。
如果配置中这两个字段不匹配,则会被 API 拒绝。
仅在某些节点上运行 Pod 如果指定了 .spec.template.spec.nodeSelector
,DaemonSet 控制器将在能够与
Node 选择算符 匹配的节点上创建 Pod。
类似这种情况,可以指定 .spec.template.spec.affinity
,之后 DaemonSet
控制器将在能够与节点亲和性 匹配的节点上创建 Pod。
如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。
Daemon Pods 是如何被调度的 通过默认调度器调度 特性状态: Kubernetes 1.17 [stable]
DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。
通常,运行 Pod 的节点由 Kubernetes 调度器选择。
不过,DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:
Pod 行为的不一致性:正常 Pod 在被创建后等待调度时处于 Pending
状态,
DaemonSet Pods 创建后不会处于 Pending
状态下。这使用户感到困惑。 Pod 抢占 由默认调度器处理。
启用抢占后,DaemonSet 控制器将在不考虑 Pod 优先级和抢占的情况下制定调度决策。ScheduleDaemonSetPods
允许你使用默认调度器而不是 DaemonSet 控制器来调度这些 DaemonSet,
方法是将 NodeAffinity
条件而不是 .spec.nodeName
条件添加到这些 DaemonSet Pod。
默认调度器接下来将 Pod 绑定到目标主机。
如果 DaemonSet Pod 的节点亲和性配置已存在,则被替换
(原始的节点亲和性配置在选择目标主机之前被考虑)。
DaemonSet 控制器仅在创建或修改 DaemonSet Pod 时执行这些操作,
并且不会更改 DaemonSet 的 spec.template
。
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchFields :
- key : metadata.name
operator : In
values :
- target-host-name
此外,系统会自动添加 node.kubernetes.io/unschedulable:NoSchedule
容忍度到这些
DaemonSet Pod。在调度 DaemonSet Pod 时,默认调度器会忽略 unschedulable
节点。
污点和容忍度 尽管 Daemon Pod 遵循污点和容忍度 规则,
根据相关特性,控制器会自动将以下容忍度添加到 DaemonSet Pod:
容忍度键名 效果 版本 描述 node.kubernetes.io/not-ready
NoExecute 1.13+ 当出现类似网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。 node.kubernetes.io/unreachable
NoExecute 1.13+ 当出现类似于网络断开的情况导致节点问题时,DaemonSet Pod 不会被逐出。 node.kubernetes.io/disk-pressure
NoSchedule 1.8+ DaemonSet Pod 被默认调度器调度时能够容忍磁盘压力属性。 node.kubernetes.io/memory-pressure
NoSchedule 1.8+ DaemonSet Pod 被默认调度器调度时能够容忍内存压力属性。 node.kubernetes.io/unschedulable
NoSchedule 1.12+ DaemonSet Pod 能够容忍默认调度器所设置的 unschedulable
属性. node.kubernetes.io/network-unavailable
NoSchedule 1.12+ DaemonSet 在使用宿主网络时,能够容忍默认调度器所设置的 network-unavailable
属性。
与 Daemon Pods 通信 与 DaemonSet 中的 Pod 进行通信的几种可能模式如下:
推送(Push) :配置 DaemonSet 中的 Pod,将更新发送到另一个服务,例如统计数据库。
这些服务没有客户端。
NodeIP 和已知端口 :DaemonSet 中的 Pod 可以使用 hostPort
,从而可以通过节点 IP
访问到 Pod。客户端能通过某种方法获取节点 IP 列表,并且基于此也可以获取到相应的端口。
DNS :创建具有相同 Pod 选择算符的无头服务 ,
通过使用 endpoints
资源或从 DNS 中检索到多个 A 记录来发现 DaemonSet。
Service :创建具有相同 Pod 选择算符的服务,并使用该服务随机访问到某个节点上的守护进程(没有办法访问到特定节点)。
更新 DaemonSet 如果节点的标签被修改,DaemonSet 将立刻向新匹配上的节点添加 Pod,
同时删除不匹配的节点上的 Pod。
你可以修改 DaemonSet 创建的 Pod。不过并非 Pod 的所有字段都可更新。
下次当某节点(即使具有相同的名称)被创建时,DaemonSet 控制器还会使用最初的模板。
你可以删除一个 DaemonSet。如果使用 kubectl
并指定 --cascade=orphan
选项,
则 Pod 将被保留在节点上。接下来如果创建使用相同选择算符的新 DaemonSet,
新的 DaemonSet 会收养已有的 Pod。
如果有 Pod 需要被替换,DaemonSet 会根据其 updateStrategy
来替换。
你可以对 DaemonSet 执行滚动更新 操作。
DaemonSet 的替代方案 init 脚本 直接在节点上启动守护进程(例如使用 init
、upstartd
或 systemd
)的做法当然是可行的。
不过,基于 DaemonSet 来运行这些进程有如下一些好处:
像所运行的其他应用一样,DaemonSet 具备为守护进程提供监控和日志管理的能力。
为守护进程和应用所使用的配置语言和工具(如 Pod 模板、kubectl
)是相同的。
在资源受限的容器中运行守护进程能够增加守护进程和应用容器的隔离性。
然而,这一点也可以通过在容器中运行守护进程但却不在 Pod 中运行之来实现。
裸 Pod 直接创建 Pod并指定其运行在特定的节点上也是可以的。
然而,DaemonSet 能够替换由于任何原因(例如节点失败、例行节点维护、内核升级)
而被删除或终止的 Pod。
由于这个原因,你应该使用 DaemonSet 而不是单独创建 Pod。
静态 Pod 通过在一个指定的、受 kubelet
监视的目录下编写文件来创建 Pod 也是可行的。
这类 Pod 被称为静态 Pod 。
不像 DaemonSet,静态 Pod 不受 kubectl
和其它 Kubernetes API 客户端管理。
静态 Pod 不依赖于 API 服务器,这使得它们在启动引导新集群的情况下非常有用。
此外,静态 Pod 在将来可能会被废弃。
Deployment DaemonSet 与 Deployment 非常类似,
它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。
建议为无状态的服务使用 Deployment,比如前端服务。
对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。
当需要 Pod 副本总是运行在全部或特定主机上,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时,
应该使用 DaemonSet。
例如,网络插件 通常包含一个以 DaemonSet 运行的组件。
这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。
接下来 3.5.2.5 - Job Job 会创建一个或者多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。
随着 Pod 成功结束,Job 跟踪记录成功完成的 Pod 个数。
当数量达到指定的成功个数阈值时,任务(即 Job)结束。
删除 Job 的操作会清除所创建的全部 Pod。
挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。
当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job
对象会启动一个新的 Pod。
你也可以使用 Job 以并行的方式运行多个 Pod。
如果你想按某种排期表(Schedule)运行 Job(单个任务或多个并行任务),请参阅
CronJob 。
运行示例 Job 下面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。
此计算大约需要 10 秒钟完成。
apiVersion : batch/v1
kind : Job
metadata :
name : pi
spec :
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
backoffLimit : 4
你可以使用下面的命令来运行此示例:
kubectl apply -f https://kubernetes.io/examples/controllers/job.yaml
输出类似于:
job.batch/pi created
使用 kubectl
来检查 Job 的状态:
输出类似于:
Name: pi
Namespace: default
Selector: controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
Labels: controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
job-name=pi
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"batch/v1","kind":"Job","metadata":{"annotations":{},"name":"pi","namespace":"default"},"spec":{"backoffLimit":4,"template":...
Parallelism: 1
Completions: 1
Start Time: Mon, 02 Dec 2019 15:20:11 +0200
Completed At: Mon, 02 Dec 2019 15:21:16 +0200
Duration: 65s
Pods Statuses: 0 Running / 1 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=c9948307-e56d-4b5d-8302-ae2d7b7da67c
job-name=pi
Containers:
pi:
Image: perl:5.34.0
Port: <none>
Host Port: <none>
Command:
perl
-Mbignum=bpi
-wle
print bpi(2000)
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 14m job-controller Created pod: pi-5rwd7
要查看 Job 对应的已完成的 Pod,可以执行 kubectl get pods
。
要以机器可读的方式列举隶属于某 Job 的全部 Pod,你可以使用类似下面这条命令:
pods = $( kubectl get pods --selector= job-name= pi --output= jsonpath = '{.items[*].metadata.name}' )
echo $pods
输出类似于:
pi-5rwd7
这里,选择算符与 Job 的选择算符相同。--output=jsonpath
选项给出了一个表达式,
用来从返回的列表中提取每个 Pod 的 name 字段。
查看其中一个 Pod 的标准输出:
输出类似于:
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
编写 Job 规约 与 Kubernetes 中其他资源的配置类似,Job 也需要 apiVersion
、kind
和 metadata
字段。
Job 的名字必须是合法的 DNS 子域名 。
Job 配置还需要一个 .spec
节 。
Pod 模板 Job 的 .spec
中只有 .spec.template
是必需的字段。
字段 .spec.template
的值是一个 Pod 模板 。
其定义规范与 Pod
完全相同,只是其中不再需要 apiVersion
或 kind
字段。
除了作为 Pod 所必需的字段之外,Job 中的 Pod 模板必须设置合适的标签
(参见 Pod 选择算符 )和合适的重启策略。
Job 中 Pod 的 RestartPolicy
只能设置为 Never
或 OnFailure
之一。
Pod 选择算符 字段 .spec.selector
是可选的。在绝大多数场合,你都不需要为其赋值。
参阅设置自己的 Pod 选择算符 .
Job 的并行执行 适合以 Job 形式来运行的任务主要有三种:
非并行 Job:通常只启动一个 Pod,除非该 Pod 失败。 当 Pod 成功终止时,立即视 Job 为完成状态。 具有确定完成计数 的并行 Job:.spec.completions
字段设置为非 0 的正数值。Job 用来代表整个任务,当成功的 Pod 个数达到 .spec.completions
时,Job 被视为完成。 当使用 .spec.completionMode="Indexed"
时,每个 Pod 都会获得一个不同的
索引值,介于 0 和 .spec.completions-1
之间。 带工作队列 的并行 Job:不设置 spec.completions
,默认值为 .spec.parallelism
。 多个 Pod 之间必须相互协调,或者借助外部服务确定每个 Pod 要处理哪个工作条目。
例如,任一 Pod 都可以从工作队列中取走最多 N 个工作条目。 每个 Pod 都可以独立确定是否其它 Pod 都已完成,进而确定 Job 是否完成。 当 Job 中任何 Pod 成功终止,不再创建新 Pod。 一旦至少 1 个 Pod 成功完成,并且所有 Pod 都已终止,即可宣告 Job 成功完成。 一旦任何 Pod 成功退出,任何其它 Pod 都不应再对此任务执行任何操作或生成任何输出。
所有 Pod 都应启动退出过程。 对于非并行 的 Job,你可以不设置 spec.completions
和 spec.parallelism
。
这两个属性都不设置时,均取默认值 1。
对于确定完成计数 类型的 Job,你应该设置 .spec.completions
为所需要的完成个数。
你可以设置 .spec.parallelism
,也可以不设置。其默认值为 1。
对于一个工作队列 Job,你不可以设置 .spec.completions
,但要将.spec.parallelism
设置为一个非负整数。
关于如何利用不同类型的 Job 的更多信息,请参见 Job 模式 一节。
控制并行性 并行性请求(.spec.parallelism
)可以设置为任何非负整数。
如果未设置,则默认为 1。
如果设置为 0,则 Job 相当于启动之后便被暂停,直到此值被增加。
实际并行性(在任意时刻运行状态的 Pod 个数)可能比并行性请求略大或略小,
原因如下:
对于确定完成计数 Job,实际上并行执行的 Pod 个数不会超出剩余的完成数。
如果 .spec.parallelism
值较高,会被忽略。 对于工作队列 Job,有任何 Job 成功结束之后,不会有新的 Pod 启动。
不过,剩下的 Pod 允许执行完毕。 如果 Job 控制器 没有来得及作出响应,或者 如果 Job 控制器因为任何原因(例如,缺少 ResourceQuota
或者没有权限)无法创建 Pod。
Pod 个数可能比请求的数目小。 Job 控制器可能会因为之前同一 Job 中 Pod 失效次数过多而压制新 Pod 的创建。 当 Pod 处于体面终止进程中,需要一定时间才能停止。 完成模式 特性状态: Kubernetes v1.24 [stable]
带有确定完成计数 的 Job,即 .spec.completions
不为 null 的 Job,
都可以在其 .spec.completionMode
中设置完成模式:
NonIndexed
(默认值):当成功完成的 Pod 个数达到 .spec.completions
所
设值时认为 Job 已经完成。换言之,每个 Job 完成事件都是独立无关且同质的。
要注意的是,当 .spec.completions
取值为 null 时,Job 被隐式处理为 NonIndexed
。
Indexed
:Job 的 Pod 会获得对应的完成索引,取值为 0 到 .spec.completions-1
。
该索引可以通过三种方式获取:
Pod 注解 batch.kubernetes.io/job-completion-index
。 作为 Pod 主机名的一部分,遵循模式 $(job-name)-$(index)
。
当你同时使用带索引的 Job(Indexed Job)与 服务(Service) ,
Job 中的 Pod 可以通过 DNS 使用确切的主机名互相寻址。 对于容器化的任务,在环境变量 JOB_COMPLETION_INDEX
中。 当每个索引都对应一个成功完成的 Pod 时,Job 被认为是已完成的。
关于如何使用这种模式的更多信息,可参阅
用带索引的 Job 执行基于静态任务分配的并行处理 。
需要注意的是,对同一索引值可能被启动的 Pod 不止一个,尽管这种情况很少发生。
这时,只有一个会被记入完成计数中。
处理 Pod 和容器失效 Pod 中的容器可能因为多种不同原因失效,例如因为其中的进程退出时返回值非零,
或者容器因为超出内存约束而被杀死等等。
如果发生这类事件,并且 .spec.template.spec.restartPolicy = "OnFailure"
,
Pod 则继续留在当前节点,但容器会被重新运行。
因此,你的程序需要能够处理在本地被重启的情况,或者要设置
.spec.template.spec.restartPolicy = "Never"
。
关于 restartPolicy
的更多信息,可参阅
Pod 生命周期 。
整个 Pod 也可能会失败,且原因各不相同。
例如,当 Pod 启动时,节点失效(被升级、被重启、被删除等)或者其中的容器失败而
.spec.template.spec.restartPolicy = "Never"
。
当 Pod 失败时,Job 控制器会启动一个新的 Pod。
这意味着,你的应用需要处理在一个新 Pod 中被重启的情况。
尤其是应用需要处理之前运行所产生的临时文件、锁、不完整的输出等问题。
注意,即使你将 .spec.parallelism
设置为 1,且将 .spec.completions
设置为
1,并且 .spec.template.spec.restartPolicy
设置为 "Never",同一程序仍然有可能被启动两次。
如果你确实将 .spec.parallelism
和 .spec.completions
都设置为比 1 大的值,
那就有可能同时出现多个 Pod 运行的情况。
为此,你的 Pod 也必须能够处理并发性问题。
Pod 回退失效策略 在有些情形下,你可能希望 Job 在经历若干次重试之后直接进入失败状态,
因为这很可能意味着遇到了配置错误。
为了实现这点,可以将 .spec.backoffLimit
设置为视 Job 为失败之前的重试次数。
失效回退的限制值默认为 6。
与 Job 相关的失效的 Pod 会被 Job 控制器重建,回退重试时间将会按指数增长
(从 10 秒、20 秒到 40 秒)最多至 6 分钟。
计算重试次数有以下两种方法:
计算 .status.phase = "Failed"
的 Pod 数量。 当 Pod 的 restartPolicy = "OnFailure"
时,针对 .status.phase
等于 Pending
或
Running
的 Pod,计算其中所有容器的重试次数。 如果两种方式其中一个的值达到 .spec.backoffLimit
,则 Job 被判定为失败。
当 JobTrackingWithFinalizers
特性被禁用时,
失败的 Pod 数目仅基于 API 中仍然存在的 Pod。
说明: 如果你的 Job 的 restartPolicy
被设置为 "OnFailure",就要注意运行该 Job 的 Pod
会在 Job 到达失效回退次数上限时自动被终止。
这会使得调试 Job 中可执行文件的工作变得非常棘手。
我们建议在调试 Job 时将 restartPolicy
设置为 "Never",
或者使用日志系统来确保失效 Job 的输出不会意外遗失。
Job 终止与清理 Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常 也不会被删除。
保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告或者其它诊断性输出。
Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。
在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。
你可以使用 kubectl
来删除 Job(例如,kubectl delete jobs/pi
或者 kubectl delete -f ./job.yaml
)。
当使用 kubectl
来删除 Job 时,该 Job 所创建的 Pod 也会被删除。
默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never
)
或者某个容器出错退出(restartPolicy=OnFailure
)。
这时,Job 基于前述的 spec.backoffLimit
来决定是否以及如何重试。
一旦重试次数到达 .spec.backoffLimit
所设的上限,Job 会被标记为失败,
其中运行的 Pod 都会被终止。
终止 Job 的另一种方式是设置一个活跃期限。
你可以为 Job 的 .spec.activeDeadlineSeconds
设置一个秒数值。
该值适用于 Job 的整个生命期,无论 Job 创建了多少个 Pod。
一旦 Job 运行时间达到 activeDeadlineSeconds
秒,其所有运行中的 Pod 都会被终止,
并且 Job 的状态更新为 type: Failed
及 reason: DeadlineExceeded
。
注意 Job 的 .spec.activeDeadlineSeconds
优先级高于其 .spec.backoffLimit
设置。
因此,如果一个 Job 正在重试一个或多个失效的 Pod,该 Job 一旦到达
activeDeadlineSeconds
所设的时限即不再部署额外的 Pod,
即使其重试次数还未达到 backoffLimit
所设的限制。
例如:
apiVersion : batch/v1
kind : Job
metadata :
name : pi-with-timeout
spec :
backoffLimit : 5
activeDeadlineSeconds : 100
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
注意 Job 规约和 Job 中的
Pod 模板规约
都有 activeDeadlineSeconds
字段。
请确保你在合适的层次设置正确的字段。
还要注意的是,restartPolicy
对应的是 Pod,而不是 Job 本身:
一旦 Job 状态变为 type: Failed
,就不会再发生 Job 重启的动作。
换言之,由 .spec.activeDeadlineSeconds
和 .spec.backoffLimit
所触发的 Job
终结机制都会导致 Job 永久性的失败,而这类状态都需要手工干预才能解决。
自动清理完成的 Job 完成的 Job 通常不需要留存在系统中。在系统中一直保留它们会给 API 服务器带来额外的压力。
如果 Job 由某种更高级别的控制器来管理,例如
CronJob ,
则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。
已完成 Job 的 TTL 机制 特性状态: Kubernetes v1.23 [stable]
自动清理已完成 Job (状态为 Complete
或 Failed
)的另一种方式是使用由
TTL 控制器 所提供的 TTL 机制。
通过设置 Job 的 .spec.ttlSecondsAfterFinished
字段,可以让该控制器清理掉已结束的资源。
TTL 控制器清理 Job 时,会级联式地删除 Job 对象。
换言之,它会删除所有依赖的对象,包括 Pod 及 Job 本身。
注意,当 Job 被删除时,系统会考虑其生命周期保障,例如其 Finalizers。
例如:
apiVersion : batch/v1
kind : Job
metadata :
name : pi-with-ttl
spec :
ttlSecondsAfterFinished : 100
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
Job pi-with-ttl
在结束 100 秒之后,可以成为被自动删除的对象。
如果该字段设置为 0
,Job 在结束之后立即成为可被自动删除的对象。
如果该字段没有设置,Job 不会在结束之后被 TTL 控制器自动清除。
Job 模式 Job 对象可以用来支持多个 Pod 的可靠的并发执行。
Job 对象不是设计用来支持相互通信的并行进程的,后者一般在科学计算中应用较多。
Job 的确能够支持对一组相互独立而又有所关联的工作条目 的并行处理。
这类工作条目可能是要发送的电子邮件、要渲染的视频帧、要编解码的文件、NoSQL
数据库中要扫描的主键范围等等。
在一个复杂系统中,可能存在多个不同的工作条目集合。
这里我们仅考虑用户希望一起管理的工作条目集合之一:批处理作业 。
并行计算的模式有好多种,每种都有自己的强项和弱点。这里要权衡的因素有:
每个工作条目对应一个 Job 或者所有工作条目对应同一 Job 对象。
后者更适合处理大量工作条目的场景;
前者会给用户带来一些额外的负担,而且需要系统管理大量的 Job 对象。 创建与工作条目相等的 Pod 或者令每个 Pod 可以处理多个工作条目。
前者通常不需要对现有代码和容器做较大改动;
后者则更适合工作条目数量较大的场合,原因同上。 有几种技术都会用到工作队列。这意味着需要运行一个队列服务,
并修改现有程序或容器使之能够利用该工作队列。
与之比较,其他方案在修改现有容器化应用以适应需求方面可能更容易一些。 下面是对这些权衡的汇总,第 2 到 4 列对应上面的权衡比较。
模式的名称对应了相关示例和更详细描述的链接。
当你使用 .spec.completions
来设置完成数时,Job 控制器所创建的每个 Pod
使用完全相同的 spec
。
这意味着任务的所有 Pod 都有相同的命令行,都使用相同的镜像和数据卷,
甚至连环境变量都(几乎)相同。
这些模式是让每个 Pod 执行不同工作的几种不同形式。
下表显示的是每种模式下 .spec.parallelism
和 .spec.completions
所需要的设置。
其中,W
表示的是工作条目的个数。
高级用法 挂起 Job 特性状态: Kubernetes v1.24 [stable]
Job 被创建时,Job 控制器会马上开始执行 Pod 创建操作以满足 Job 的需求,
并持续执行此操作直到 Job 完成为止。
不过你可能想要暂时挂起 Job 执行,或启动处于挂起状态的 Job,
并拥有一个自定义控制器以后再决定什么时候开始。
要挂起一个 Job,你可以更新 .spec.suspend
字段为 true,
之后,当你希望恢复其执行时,将其更新为 false。
创建一个 .spec.suspend
被设置为 true 的 Job 本质上会将其创建为被挂起状态。
当 Job 被从挂起状态恢复执行时,其 .status.startTime
字段会被重置为当前的时间。
这意味着 .spec.activeDeadlineSeconds
计时器会在 Job 挂起时被停止,
并在 Job 恢复执行时复位。
当你挂起一个 Job 时,所有正在运行且状态不是 Completed
的 Pod
将被终止 。
Pod 的体面终止期限会被考虑,不过 Pod 自身也必须在此期限之内处理完信号。
处理逻辑可能包括保存进度以便将来恢复,或者取消已经做出的变更等等。
Pod 以这种形式终止时,不会被记入 Job 的 completions
计数。
处于被挂起状态的 Job 的定义示例可能是这样子:
kubectl get job myjob -o yaml
apiVersion : batch/v1
kind : Job
metadata :
name : myjob
spec :
suspend : true
parallelism : 1
completions : 5
template :
spec :
...
你也可以使用命令行为 Job 打补丁来切换 Job 的挂起状态。
挂起一个活跃的 Job:
kubectl patch job/myjob --type= strategic --patch '{"spec":{"suspend":true}}'
恢复一个挂起的 Job:
kubectl patch job/myjob --type= strategic --patch '{"spec":{"suspend":false}}'
Job 的 status
可以用来确定 Job 是否被挂起,或者曾经被挂起。
kubectl get jobs/myjob -o yaml
apiVersion : batch/v1
kind : Job
# .metadata 和 .spec 已省略
status :
conditions :
- lastProbeTime : "2021-02-05T13:14:33Z"
lastTransitionTime : "2021-02-05T13:14:33Z"
status : "True"
type : Suspended
startTime : "2021-02-05T13:13:48Z"
Job 的 "Suspended" 类型的状况在状态值为 "True" 时意味着 Job 正被挂起;
lastTransitionTime
字段可被用来确定 Job 被挂起的时长。
如果此状况字段的取值为 "False",则 Job 之前被挂起且现在在运行。
如果 "Suspended" 状况在 status
字段中不存在,则意味着 Job 从未被停止执行。
当 Job 被挂起和恢复执行时,也会生成事件:
kubectl describe jobs/myjob
Name: myjob
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 12m job-controller Created pod: myjob-hlrpl
Normal SuccessfulDelete 11m job-controller Deleted pod: myjob-hlrpl
Normal Suspended 11m job-controller Job suspended
Normal SuccessfulCreate 3s job-controller Created pod: myjob-jvb44
Normal Resumed 3s job-controller Job resumed
最后四个事件,特别是 "Suspended" 和 "Resumed" 事件,都是因为 .spec.suspend
字段值被改来改去造成的。在这两个事件之间,我们看到没有 Pod 被创建,不过当
Job 被恢复执行时,Pod 创建操作立即被重启执行。
可变调度指令 特性状态: Kubernetes v1.23 [beta]
说明: 为了使用此功能,你必须在 API 服务器 上启用
JobMutableNodeSchedulingDirectives
特性门控 。
默认情况下启用。
在大多数情况下,并行作业会希望 Pod 在一定约束条件下运行,
比如所有的 Pod 都在同一个区域,或者所有的 Pod 都在 GPU 型号 x 或 y 上,而不是两者的混合。
suspend 字段是实现这些语义的第一步。
suspend 允许自定义队列控制器,以决定工作何时开始;然而,一旦工作被取消暂停,
自定义队列控制器对 Job 中 Pod 的实际放置位置没有影响。
此特性允许在 Job 开始之前更新调度指令,从而为定制队列提供影响 Pod
放置的能力,同时将 Pod 与节点间的分配关系留给 kube-scheduler 决定。
这一特性仅适用于之前从未被暂停过的、已暂停的 Job。
控制器能够影响 Pod 放置,同时参考实际 pod-to-node 分配给 kube-scheduler。
这仅适用于从未暂停的 Job。
Job 的 Pod 模板中可以更新的字段是节点亲和性、节点选择器、容忍、标签和注解。
指定你自己的 Pod 选择算符 通常,当你创建一个 Job 对象时,你不会设置 .spec.selector
。
系统的默认值填充逻辑会在创建 Job 时添加此字段。
它会选择一个不会与任何其他 Job 重叠的选择算符设置。
不过,有些场合下,你可能需要重载这个自动设置的选择算符。
为了实现这点,你可以手动设置 Job 的 spec.selector
字段。
做这个操作时请务必小心。
如果你所设定的标签选择算符并不唯一针对 Job 对应的 Pod 集合,
甚或该算符还能匹配其他无关的 Pod,这些无关的 Job 的 Pod 可能会被删除。
或者当前 Job 会将另外一些 Pod 当作是完成自身工作的 Pod,
又或者两个 Job 之一或者二者同时都拒绝创建 Pod,无法运行至完成状态。
如果所设置的算符不具有唯一性,其他控制器(如 RC 副本控制器)及其所管理的 Pod
集合可能会变得行为不可预测。
Kubernetes 不会在你设置 .spec.selector
时尝试阻止你犯这类错误。
下面是一个示例场景,在这种场景下你可能会使用刚刚讲述的特性。
假定名为 old
的 Job 已经处于运行状态。
你希望已有的 Pod 继续运行,但你希望 Job 接下来要创建的其他 Pod
使用一个不同的 Pod 模板,甚至希望 Job 的名字也发生变化。
你无法更新现有的 Job,因为这些字段都是不可更新的。
因此,你会删除 old
Job,但允许该 Job 的 Pod 集合继续运行 。
这是通过 kubectl delete jobs/old --cascade=orphan
实现的。
在删除之前,我们先记下该 Job 所使用的选择算符。
kubectl get job old -o yaml
输出类似于:
kind : Job
metadata :
name : old
...
spec :
selector :
matchLabels :
controller-uid : a8f3d00d-c6d2-11e5-9f87-42010af00002
...
接下来你会创建名为 new
的新 Job,并显式地为其设置相同的选择算符。
由于现有 Pod 都具有标签 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002
,
它们也会被名为 new
的 Job 所控制。
你需要在新 Job 中设置 manualSelector: true
,
因为你并未使用系统通常自动为你生成的选择算符。
kind : Job
metadata :
name : new
...
spec :
manualSelector : true
selector :
matchLabels :
controller-uid : a8f3d00d-c6d2-11e5-9f87-42010af00002
...
新的 Job 自身会有一个不同于 a8f3d00d-c6d2-11e5-9f87-42010af00002
的唯一 ID。
设置 manualSelector: true
是在告诉系统你知道自己在干什么并要求系统允许这种不匹配的存在。
Pod 失效策略 特性状态: Kubernetes v1.25 [alpha]
说明: 只有你在集群中启用了
JobPodFailurePolicy
特性门控
你才能为某个 Job 配置 Pod 失效策略。
此外,建议启用 PodDisruptionConditions
特性门控以便在 Pod 失效策略中检测和处理 Pod 干扰状况
(参考:Pod 干扰状况 )。
这两个特性门控都是在 Kubernetes v1.25 中提供的。
Pod 失效策略使用 .spec.podFailurePolicy
字段来定义,
它能让你的集群根据容器的退出码和 Pod 状况来处理 Pod 失效事件。
在某些情况下,你可能希望更好地控制 Pod 失效的处理方式,
而不是仅限于 Pod 回退失效策略 所提供的控制能力,
后者是基于 Job 的 .spec.backoffLimit
实现的。以下是一些使用场景:
通过避免不必要的 Pod 重启来优化工作负载的运行成本,
你可以在某 Job 中一个 Pod 失效且其退出码表明存在软件错误时立即终止该 Job。 为了保证即使有干扰也能完成 Job,你可以忽略由干扰导致的 Pod 失效
(例如抢占 、
通过 API 发起的驱逐
或基于污点 的驱逐),
这样这些失效就不会被计入 .spec.backoffLimit
的重试限制中。 你可以在 .spec.podFailurePolicy
字段中配置 Pod 失效策略,以满足上述使用场景。
该策略可以根据容器退出码和 Pod 状况来处理 Pod 失效。
下面是一个定义了 podFailurePolicy
的 Job 的清单:
apiVersion : batch/v1
kind : Job
metadata :
name : job-pod-failure-policy-example
spec :
completions : 12
parallelism : 3
template :
spec :
restartPolicy : Never
containers :
- name : main
image : docker.io/library/bash:5
command : ["bash" ] # 模拟一个触发 FailJob 动作的错误的示例命令
args :
- -c
- echo "Hello world!" && sleep 5 && exit 42
backoffLimit : 6
podFailurePolicy :
rules :
- action : FailJob
onExitCodes :
containerName : main # 可选
operator : In # In 和 NotIn 二选一
values : [42 ]
- action : Ignore # Ignore、FailJob、Count 其中之一
onPodConditions :
- type : DisruptionTarget # 表示 Pod 失效
在上面的示例中,Pod 失效策略的第一条规则规定如果 main
容器失败并且退出码为 42,
Job 将被标记为失败。以下是 main
容器的具体规则:
退出码 0 代表容器成功 退出码 42 代表 整个 Job 失败 所有其他退出码都代表容器失败,同时也代表着整个 Pod 失效。
如果重启总次数低于 backoffLimit
定义的次数,则会重新启动 Pod,
如果等于 backoffLimit
所设置的次数,则代表 整个 Job 失效。 说明: 因为 Pod 模板中指定了 restartPolicy: Never
,
所以 kubelet 将不会重启 Pod 中的 main
容器。
Pod 失效策略的第二条规则,
指定对于状况为 DisruptionTarget
的失效 Pod 采取 Ignore
操作,
统计 .spec.backoffLimit
重试次数限制时不考虑 Pod 因干扰而发生的异常。
说明: 如果根据 Pod 失效策略或 Pod 回退失效策略判定 Pod 已经失效,
并且 Job 正在运行多个 Pod,Kubernetes 将终止该 Job 中仍处于 Pending 或 Running 的所有 Pod。
下面是此 API 的一些要求和语义:
如果你想在 Job 中使用 .spec.podFailurePolicy
字段,
你必须将 Job 的 Pod 模板中的 .spec.restartPolicy
设置为 Never
。 在 spec.podFailurePolicy.rules
中设定的 Pod 失效策略规则将按序评估。
一旦某个规则与 Pod 失效策略匹配,其余规则将被忽略。
当没有规则匹配 Pod 失效策略时,将会采用默认的处理方式。 你可能希望在 spec.podFailurePolicy.rules[*].containerName
中通过指定的名称将规则限制到特定容器。
如果不设置,规则将适用于所有容器。
如果指定了容器名称,它应该匹配 Pod 模板中的一个普通容器或一个初始容器(Init Container)。 你可以在 spec.podFailurePolicy.rules[*].action
指定当 Pod 失效策略发生匹配时要采取的操作。
可能的值为:FailJob
:表示 Pod 的任务应标记为 Failed,并且所有正在运行的 Pod 应被终止。Ignore
:表示 .spec.backoffLimit
的计数器不应该增加,应该创建一个替换的 Pod。Count
:表示 Pod 应该以默认方式处理。.spec.backoffLimit
的计数器应该增加。 使用 Finalizer 追踪 Job 特性状态: Kubernetes v1.23 [beta]
说明: 要使用该行为,你必须为 API 服务器
和控制器管理器
启用 JobTrackingWithFinalizers
特性门控 。
默认是启用的。
启用后,控制面基于下述行为追踪新的 Job。在启用该特性之前创建的 Job 不受影响。
作为用户,你会看到的唯一区别是控制面对 Job 完成情况的跟踪更加准确。
该功能未启用时,Job 控制器(Controller) 依靠计算集群中存在的 Pod 来跟踪作业状态。
也就是说,维持一个统计 succeeded
和 failed
的 Pod 的计数器。
然而,Pod 可以因为一些原因被移除,包括:
当一个节点宕机时,垃圾收集器会删除孤立(Orphan)Pod。 垃圾收集器在某个阈值后删除已完成的 Pod(处于 Succeeded
或 Failed
阶段)。 人工干预删除 Job 的 Pod。 一个外部控制器(不包含于 Kubernetes)来删除或取代 Pod。 如果你为你的集群启用了 JobTrackingWithFinalizers
特性,控制面会跟踪属于任何 Job 的 Pod。
并注意是否有任何这样的 Pod 被从 API 服务器上删除。
为了实现这一点,Job 控制器创建的 Pod 带有 Finalizer batch.kubernetes.io/job-tracking
。
控制器只有在 Pod 被记入 Job 状态后才会移除 Finalizer,允许 Pod 可以被其他控制器或用户删除。
Job 控制器只对新的 Job 使用新的算法。在启用该特性之前创建的 Job 不受影响。
你可以根据检查 Job 是否含有 batch.kubernetes.io/job-tracking
注解,
来确定 Job 控制器是否正在使用 Pod Finalizer 追踪 Job。
你不 应该给 Job 手动添加或删除该注解。
替代方案 裸 Pod 当 Pod 运行所在的节点重启或者失败,Pod 会被终止并且不会被重启。
Job 会重新创建新的 Pod 来替代已终止的 Pod。
因为这个原因,我们建议你使用 Job 而不是独立的裸 Pod,
即使你的应用仅需要一个 Pod。
副本控制器 Job 与副本控制器 是彼此互补的。
副本控制器管理的是那些不希望被终止的 Pod (例如,Web 服务器),
Job 管理的是那些希望被终止的 Pod(例如,批处理作业)。
正如在 Pod 生命期 中讨论的,
Job
仅适合于 restartPolicy
设置为 OnFailure
或 Never
的 Pod。
注意:如果 restartPolicy
未设置,其默认值是 Always
。
单个 Job 启动控制器 Pod 另一种模式是用唯一的 Job 来创建 Pod,而该 Pod 负责启动其他 Pod,
因此扮演了一种后启动 Pod 的控制器的角色。
这种模式的灵活性更高,但是有时候可能会把事情搞得很复杂,很难入门,
并且与 Kubernetes 的集成度很低。
这种模式的实例之一是用 Job 来启动一个运行脚本的 Pod,脚本负责启动 Spark
主控制器(参见 Spark 示例 ),
运行 Spark 驱动,之后完成清理工作。
这种方法的优点之一是整个过程得到了 Job 对象的完成保障,
同时维持了对创建哪些 Pod、如何向其分派工作的完全控制能力,
接下来 了解 Pod 。 了解运行 Job 的不同的方式: 跟随自动清理完成的 Job 文中的链接,了解你的集群如何清理完成和失败的任务。 Job
是 Kubernetes REST API 的一部分。阅读
对象定义理解关于该资源的 API。阅读 CronJob
,它允许你定义一系列定期运行的 Job,类似于 UNIX 工具 cron
。 3.5.2.6 - 已完成 Job 的自动清理 特性状态: Kubernetes v1.23 [stable]
TTL-after-finished 控制器 提供了一种 TTL 机制来限制已完成执行的资源对象的生命周期。
TTL 控制器目前只处理 Job 。
TTL-after-finished 控制器 TTL-after-finished 控制器只支持 Job。集群操作员可以通过指定 Job 的 .spec.ttlSecondsAfterFinished
字段来自动清理已结束的作业(Complete
或 Failed
),如
示例
所示。
TTL-after-finished 控制器假设作业能在执行完成后的 TTL 秒内被清理,也就是当 TTL 过期后。
当 TTL 控制器清理作业时,它将做级联删除操作,即删除资源对象的同时也删除其依赖对象。
注意,当资源被删除时,由该资源的生命周期保证其终结器(Finalizers)等被执行。
可以随时设置 TTL 秒。以下是设置 Job 的 .spec.ttlSecondsAfterFinished
字段的一些示例:
警告 更新 TTL 秒数 请注意,在创建 Job 或已经执行结束后,仍可以修改其 TTL 周期,例如 Job 的
.spec.ttlSecondsAfterFinished
字段。
但是一旦 Job 变为可被删除状态(当其 TTL 已过期时),即使你通过 API 增加其 TTL
时长得到了成功的响应,系统也不保证 Job 将被保留。
时间偏差 由于 TTL-after-finished 控制器使用存储在 Kubernetes 资源中的时间戳来确定 TTL 是否已过期,
因此该功能对集群中的时间偏差很敏感,这可能导致 TTL-after-finished 控制器在错误的时间清理资源对象。
时钟并不总是如此正确,但差异应该很小。
设置非零 TTL 时请注意避免这种风险。
接下来 3.5.2.7 - CronJob 特性状态: Kubernetes v1.21 [stable]
CronJob 创建基于时隔重复调度的 Jobs 。
一个 CronJob 对象就像 crontab (cron table) 文件中的一行。
它用 Cron 格式进行编写,
并周期性地在给定的调度时间执行 Job。
注意: 所有 CronJob 的 schedule:
时间都是基于
kube-controller-manager .
的时区。
如果你的控制平面在 Pod 或是裸容器中运行了 kube-controller-manager,
那么为该容器所设置的时区将会决定 Cron Job 的控制器所使用的时区。
注意: 如 v1 CronJob API 所述,官方并不支持设置时区。
Kubernetes 项目官方并不支持设置如 CRON_TZ
或者 TZ
等变量。
CRON_TZ
或者 TZ
是用于解析和计算下一个 Job 创建时间所使用的内部库中一个实现细节。
不建议在生产集群中使用它。
为 CronJob 资源创建清单时,请确保所提供的名称是一个合法的
DNS 子域名 。
名称不能超过 52 个字符。
这是因为 CronJob 控制器将自动在提供的 Job 名称后附加 11 个字符,并且存在一个限制,
即 Job 名称的最大长度不能超过 63 个字符。
CronJob CronJob 用于执行周期性的动作,例如备份、报告生成等。
这些任务中的每一个都应该配置为周期性重复的(例如:每天/每周/每月一次);
你可以定义任务开始执行的时间间隔。
示例 下面的 CronJob 示例清单会在每分钟打印出当前时间和问候消息:
apiVersion : batch/v1
kind : CronJob
metadata :
name : hello
spec :
schedule : "* * * * *"
jobTemplate :
spec :
template :
spec :
containers :
- name : hello
image : busybox:1.28
imagePullPolicy : IfNotPresent
command :
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy : OnFailure
使用 CronJob 运行自动化任务
一文会为你详细讲解此例。
Cron 时间表语法 # ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
输入 描述 相当于 @yearly (or @annually) 每年 1 月 1 日的午夜运行一次 0 0 1 1 * @monthly 每月第一天的午夜运行一次 0 0 1 * * @weekly 每周的周日午夜运行一次 0 0 * * 0 @daily (or @midnight) 每天午夜运行一次 0 0 * * * @hourly 每小时的开始一次 0 * * * *
例如,下面这行指出必须在每个星期五的午夜以及每个月 13 号的午夜开始任务:
0 0 13 * 5
要生成 CronJob 时间表表达式,你还可以使用 crontab.guru 之类的 Web 工具。
时区 对于没有指定时区的 CronJob,kube-controller-manager 基于本地时区解释排期表(Schedule)。
特性状态: Kubernetes v1.25 [beta]
如果启用了 CronJobTimeZone
特性门控 ,
你可以为 CronJob 指定一个时区(如果你没有启用该特性门控,或者你使用的是不支持试验性时区功能的
Kubernetes 版本,集群中所有 CronJob 的时区都是未指定的)。
启用该特性后,你可以将 spec.timeZone
设置为有效时区 名称。
例如,设置 spec.timeZone: "Etc/UTC"
指示 Kubernetes 采用 UTC 来解释排期表。
Go 标准库中的时区数据库包含在二进制文件中,并用作备用数据库,以防系统上没有可用的外部数据库。
CronJob 限制 CronJob 根据其计划编排,在每次该执行任务的时候大约会创建一个 Job。
我们之所以说 "大约",是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。
我们试图使这些情况尽量少发生,但不能完全杜绝。因此,Job 应该是 幂等的 。
如果 startingDeadlineSeconds
设置为很大的数值或未设置(默认),并且
concurrencyPolicy
设置为 Allow
,则作业将始终至少运行一次。
注意: 如果 startingDeadlineSeconds
的设置值低于 10 秒钟,CronJob 可能无法被调度。
这是因为 CronJob 控制器每 10 秒钟执行一次检查。
对于每个 CronJob,CronJob 控制器(Controller)
检查从上一次调度的时间点到现在所错过了调度次数。如果错过的调度次数超过 100 次,
那么它就不会启动这个任务,并记录这个错误:
Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.
需要注意的是,如果 startingDeadlineSeconds
字段非空,则控制器会统计从
startingDeadlineSeconds
设置的值到现在而不是从上一个计划时间到现在错过了多少次 Job。
例如,如果 startingDeadlineSeconds
是 200
,则控制器会统计在过去 200 秒中错过了多少次 Job。
如果未能在调度时间内创建 CronJob,则计为错过。
例如,如果 concurrencyPolicy
被设置为 Forbid
,并且当前有一个调度仍在运行的情况下,
试图调度的 CronJob 将被计算为错过。
例如,假设一个 CronJob 被设置为从 08:30:00
开始每隔一分钟创建一个新的 Job,
并且它的 startingDeadlineSeconds
字段未被设置。如果 CronJob 控制器从
08:29:00
到 10:21:00
终止运行,则该 Job 将不会启动,因为其错过的调度
次数超过了 100。
为了进一步阐述这个概念,假设将 CronJob 设置为从 08:30:00
开始每隔一分钟创建一个新的 Job,
并将其 startingDeadlineSeconds
字段设置为 200 秒。
如果 CronJob 控制器恰好在与上一个示例相同的时间段(08:29:00
到 10:21:00
)终止运行,
则 Job 仍将从 10:22:00
开始。
造成这种情况的原因是控制器现在检查在最近 200 秒(即 3 个错过的调度)中发生了多少次错过的
Job 调度,而不是从现在为止的最后一个调度时间开始。
CronJob 仅负责创建与其调度时间相匹配的 Job,而 Job 又负责管理其代表的 Pod。
控制器版本 从 Kubernetes v1.21 版本开始,CronJob 控制器的第二个版本被用作默认实现。
要禁用此默认 CronJob 控制器而使用原来的 CronJob 控制器,请在
kube-controller-manager
中设置特性门控
CronJobControllerV2
,将此标志设置为 false
。例如:
--feature-gates="CronJobControllerV2=false"
接下来 了解 CronJob 所依赖的 Pod 与 Job 的概念。 阅读 CronJob .spec.schedule
字段的格式 。 有关创建和使用 CronJob 的说明及示例规约文件,请参见
使用 CronJob 运行自动化任务 。 有关自动清理失败或完成作业的说明,请参阅自动清理作业 CronJob
是 Kubernetes REST API 的一部分,
阅读
CronJob
对象定义以了解关于该资源的 API。3.5.2.8 - ReplicationController ReplicationController 确保在任何时候都有特定数量的 Pod 副本处于运行状态。
换句话说,ReplicationController 确保一个 Pod 或一组同类的 Pod 总是可用的。
ReplicationController 如何工作 当 Pod 数量过多时,ReplicationController 会终止多余的 Pod。当 Pod 数量太少时,ReplicationController 将会启动新的 Pod。
与手动创建的 Pod 不同,由 ReplicationController 创建的 Pod 在失败、被删除或被终止时会被自动替换。
例如,在中断性维护(如内核升级)之后,你的 Pod 会在节点上重新创建。
因此,即使你的应用程序只需要一个 Pod,你也应该使用 ReplicationController 创建 Pod。
ReplicationController 类似于进程管理器,但是 ReplicationController 不是监控单个节点上的单个进程,而是监控跨多个节点的多个 Pod。
在讨论中,ReplicationController 通常缩写为 "rc",并作为 kubectl 命令的快捷方式。
一个简单的示例是创建一个 ReplicationController 对象来可靠地无限期地运行 Pod 的一个实例。
更复杂的用例是运行一个多副本服务(如 web 服务器)的若干相同副本。
运行一个示例 ReplicationController 这个示例 ReplicationController 配置运行 nginx Web 服务器的三个副本。
apiVersion : v1
kind : ReplicationController
metadata :
name : nginx
spec :
replicas : 3
selector :
app : nginx
template :
metadata :
name : nginx
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx
ports :
- containerPort : 80
通过下载示例文件并运行以下命令来运行示例任务:
kubectl apply -f https://k8s.io/examples/controllers/replication.yaml
输出类似于:
replicationcontroller/nginx created
使用以下命令检查 ReplicationController 的状态:
kubectl describe replicationcontrollers/nginx
输出类似于:
Name: nginx
Namespace: default
Selector: app=nginx
Labels: app=nginx
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 0 Running / 3 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- ---- ------ -------
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-qrm3m
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-3ntk0
20s 20s 1 {replication-controller } Normal SuccessfulCreate Created pod: nginx-4ok8v
在这里,创建了三个 Pod,但没有一个 Pod 正在运行,这可能是因为正在拉取镜像。
稍后,相同的命令可能会显示:
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
要以机器可读的形式列出属于 ReplicationController 的所有 Pod,可以使用如下命令:
pods = $( kubectl get pods --selector= app = nginx --output= jsonpath ={ .items..metadata.name} )
echo $pods
输出类似于:
nginx-3ntk0 nginx-4ok8v nginx-qrm3m
这里,选择算符与 ReplicationController 的选择算符相同(参见 kubectl describe
输出),并以不同的形式出现在 replication.yaml
中。
--output=jsonpath
选项指定了一个表达式,仅从返回列表中的每个 Pod 中获取名称。
编写一个 ReplicationController 规约 与所有其它 Kubernetes 配置一样,ReplicationController 需要 apiVersion
、kind
和 metadata
字段。
ReplicationController 对象的名称必须是有效的
DNS 子域名 。
有关使用配置文件的常规信息,
参考对象管理 。
ReplicationController 也需要一个 .spec
部分 。
Pod 模板 .spec.template
是 .spec
的唯一必需字段。
.spec.template
是一个 Pod 模板 。
它的模式与 Pod 完全相同,只是它是嵌套的,没有 apiVersion
或 kind
属性。
除了 Pod 所需的字段外,ReplicationController 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。
对于标签,请确保不与其他控制器重叠。参考 Pod 选择算符 。
只允许 .spec.template.spec.restartPolicy
等于 Always
,如果没有指定,这是默认值。
对于本地容器重启,ReplicationController 委托给节点上的代理,
例如 Kubelet 。
ReplicationController 上的标签 ReplicationController 本身可以有标签 (.metadata.labels
)。
通常,你可以将这些设置为 .spec.template.metadata.labels
;
如果没有指定 .metadata.labels
那么它默认为 .spec.template.metadata.labels
。
但是,Kubernetes 允许它们是不同的,.metadata.labels
不会影响 ReplicationController 的行为。
Pod 选择算符 .spec.selector
字段是一个标签选择算符 。
ReplicationController 管理标签与选择算符匹配的所有 Pod。
它不区分它创建或删除的 Pod 和其他人或进程创建或删除的 Pod。
这允许在不影响正在运行的 Pod 的情况下替换 ReplicationController。
如果指定了 .spec.template.metadata.labels
,它必须和 .spec.selector
相同,否则它将被 API 拒绝。
如果没有指定 .spec.selector
,它将默认为 .spec.template.metadata.labels
。
另外,通常不应直接使用另一个 ReplicationController 或另一个控制器(例如 Job)
来创建其标签与该选择算符匹配的任何 Pod。如果这样做,ReplicationController 会认为它创建了这些 Pod。
Kubernetes 并没有阻止你这样做。
如果你的确创建了多个控制器并且其选择算符之间存在重叠,那么你将不得不自己管理删除操作(参考后文 )。
多个副本 你可以通过设置 .spec.replicas
来指定应该同时运行多少个 Pod。
在任何时候,处于运行状态的 Pod 个数都可能高于或者低于设定值。例如,副本个数刚刚被增加或减少时,
或者一个 Pod 处于优雅终止过程中而其替代副本已经提前开始创建时。
如果你没有指定 .spec.replicas
,那么它默认是 1。
使用 ReplicationController 删除一个 ReplicationController 以及它的 Pod 要删除一个 ReplicationController 以及它的 Pod,使用
kubectl delete
。
kubectl 将 ReplicationController 缩容为 0 并等待以便在删除 ReplicationController 本身之前删除每个 Pod。
如果这个 kubectl 命令被中断,可以重新启动它。
当使用 REST API 或客户端库 时,你需要明确地执行这些步骤(缩容副本为 0、
等待 Pod 删除,之后删除 ReplicationController 资源)。
只删除 ReplicationController 你可以删除一个 ReplicationController 而不影响它的任何 Pod。
使用 kubectl,为 kubectl delete
指定 --cascade=orphan
选项。
当使用 REST API 或客户端库 时,只需删除 ReplicationController 对象。
一旦原始对象被删除,你可以创建一个新的 ReplicationController 来替换它。
只要新的和旧的 .spec.selector
相同,那么新的控制器将领养旧的 Pod。
但是,它不会做出任何努力使现有的 Pod 匹配新的、不同的 Pod 模板。
如果希望以受控方式更新 Pod 以使用新的 spec,请执行滚动更新 操作。
从 ReplicationController 中隔离 Pod 通过更改 Pod 的标签,可以从 ReplicationController 的目标中删除 Pod。
此技术可用于从服务中删除 Pod 以进行调试、数据恢复等。以这种方式删除的 Pod
将被自动替换(假设复制副本的数量也没有更改)。
常见的使用模式 重新调度 如上所述,无论你想要继续运行 1 个 Pod 还是 1000 个 Pod,一个 ReplicationController 都将确保存在指定数量的 Pod,即使在节点故障或 Pod 终止(例如,由于另一个控制代理的操作)的情况下也是如此。
扩缩容 通过设置 replicas
字段,ReplicationController 可以允许扩容或缩容副本的数量。
你可以手动或通过自动扩缩控制代理来控制 ReplicationController 执行此操作。
滚动更新 ReplicationController 的设计目的是通过逐个替换 Pod 以方便滚动更新服务。
如 #1353 PR 中所述,建议的方法是使用 1 个副本创建一个新的 ReplicationController,
逐个扩容新的(+1)和缩容旧的(-1)控制器,然后在旧的控制器达到 0 个副本后将其删除。
这一方法能够实现可控的 Pod 集合更新,即使存在意外失效的状况。
理想情况下,滚动更新控制器将考虑应用程序的就绪情况,并确保在任何给定时间都有足够数量的 Pod 有效地提供服务。
这两个 ReplicationController 将需要创建至少具有一个不同标签的 Pod,比如 Pod 主要容器的镜像标签,因为通常是镜像更新触发滚动更新。
多个版本跟踪 除了在滚动更新过程中运行应用程序的多个版本之外,通常还会使用多个版本跟踪来长时间,
甚至持续运行多个版本。这些跟踪将根据标签加以区分。
例如,一个服务可能把具有 tier in (frontend), environment in (prod)
的所有 Pod 作为目标。
现在假设你有 10 个副本的 Pod 组成了这个层。但是你希望能够 canary
(金丝雀
)发布这个组件的新版本。
你可以为大部分副本设置一个 ReplicationController,其中 replicas
设置为 9,
标签为 tier=frontend, environment=prod, track=stable
而为 canary
设置另一个 ReplicationController,其中 replicas
设置为 1,
标签为 tier=frontend, environment=prod, track=canary
。
现在这个服务覆盖了 canary
和非 canary
Pod。但你可以单独处理
ReplicationController,以测试、监控结果等。
和服务一起使用 ReplicationController 多个 ReplicationController 可以位于一个服务的后面,例如,一部分流量流向旧版本,
一部分流量流向新版本。
一个 ReplicationController 永远不会自行终止,但它不会像服务那样长时间存活。
服务可以由多个 ReplicationController 控制的 Pod 组成,并且在服务的生命周期内
(例如,为了执行 Pod 更新而运行服务),可以创建和销毁许多 ReplicationController。
服务本身和它们的客户端都应该忽略负责维护服务 Pod 的 ReplicationController 的存在。
编写多副本的应用 由 ReplicationController 创建的 Pod 是可替换的,语义上是相同的,
尽管随着时间的推移,它们的配置可能会变得异构。
这显然适合于多副本的无状态服务器,但是 ReplicationController 也可以用于维护主选、
分片和工作池应用程序的可用性。
这样的应用程序应该使用动态的工作分配机制,例如
RabbitMQ 工作队列 ,
而不是静态的或者一次性定制每个 Pod 的配置,这被认为是一种反模式。
执行的任何 Pod 定制,例如资源的垂直自动调整大小(例如,CPU 或内存),
都应该由另一个在线控制器进程执行,这与 ReplicationController 本身没什么不同。
ReplicationController 的职责 ReplicationController 仅确保所需的 Pod 数量与其标签选择算符匹配,并且是可操作的。
目前,它的计数中只排除终止的 Pod。
未来,可能会考虑系统提供的就绪状态 和其他信息,
我们可能会对替换策略添加更多控制,
我们计划发出事件,这些事件可以被外部客户端用来实现任意复杂的替换和/或缩减策略。
ReplicationController 永远被限制在这个狭隘的职责范围内。
它本身既不执行就绪态探测,也不执行活跃性探测。
它不负责执行自动扩缩,而是由外部自动扩缩器控制(如
#492 中所述),后者负责更改其 replicas
字段值。
我们不会向 ReplicationController 添加调度策略(例如,
spreading )。
它也不应该验证所控制的 Pod 是否与当前指定的模板匹配,因为这会阻碍自动调整大小和其他自动化过程。
类似地,完成期限、整理依赖关系、配置扩展和其他特性也属于其他地方。
我们甚至计划考虑批量创建 Pod 的机制(查阅 #170 )。
ReplicationController 旨在成为可组合的构建基元。
我们希望在它和其他补充原语的基础上构建更高级别的 API 或者工具,以便于将来的用户使用。
kubectl 目前支持的 "macro" 操作(运行、扩缩、滚动更新)就是这方面的概念示例。
例如,我们可以想象类似于 Asgard
的东西管理 ReplicationController、自动定标器、服务、调度策略、金丝雀发布等。
API 对象 在 Kubernetes REST API 中 Replication controller 是顶级资源。
更多关于 API 对象的详细信息可以在
ReplicationController API 对象 找到。
ReplicationController 的替代方案 ReplicaSet ReplicaSet
是下一代 ReplicationController,
支持新的基于集合的标签选择算符 。
它主要被 Deployment
用来作为一种编排 Pod 创建、删除及更新的机制。
请注意,我们推荐使用 Deployment 而不是直接使用 ReplicaSet,除非你需要自定义更新编排或根本不需要更新。
Deployment (推荐) Deployment
是一种更高级别的 API 对象,用于更新其底层 ReplicaSet 及其 Pod。
如果你想要这种滚动更新功能,那么推荐使用 Deployment,因为它们是声明式的、服务端的,并且具有其它特性。
裸 Pod 与用户直接创建 Pod 的情况不同,ReplicationController 能够替换因某些原因被删除或被终止的 Pod,
例如在节点故障或中断节点维护的情况下,例如内核升级。
因此,我们建议你使用 ReplicationController,即使你的应用程序只需要一个 Pod。
可以将其看作类似于进程管理器,它只管理跨多个节点的多个 Pod,而不是单个节点上的单个进程。
ReplicationController 将本地容器重启委托给节点上的某个代理(例如 Kubelet)。
Job 对于预期会自行终止的 Pod (即批处理任务),使用
Job
而不是 ReplicationController。
DaemonSet 对于提供机器级功能(例如机器监控或机器日志记录)的 Pod,
使用 DaemonSet
而不是
ReplicationController。
这些 Pod 的生命期与机器的生命期绑定:它们需要在其他 Pod 启动之前在机器上运行,
并且在机器准备重新启动或者关闭时安全地终止。
接下来 3.6 - 服务、负载均衡和联网 Kubernetes 网络背后的概念和资源。
Kubernetes 网络模型 集群中每一个 Pod
都会获得自己的、
独一无二的 IP 地址,
这就意味着你不需要显式地在 Pod
之间创建链接,你几乎不需要处理容器端口到主机端口之间的映射。
这将形成一个干净的、向后兼容的模型;在这个模型里,从端口分配、命名、服务发现、
负载均衡 、
应用配置和迁移的角度来看,Pod
可以被视作虚拟机或者物理主机。
Kubernetes 强制要求所有网络设施都满足以下基本要求(从而排除了有意隔离网络的策略):
Pod 能够与所有其他节点 上的 Pod 通信,
且不需要网络地址转译(NAT) 节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有 Pod 通信 说明:对于支持在主机网络中运行 Pod
的平台(比如:Linux),
当 Pod 挂接到节点的宿主网络上时,它们仍可以不通过 NAT 和所有节点上的 Pod 通信。
这个模型不仅不复杂,而且还和 Kubernetes 的实现从虚拟机向容器平滑迁移的初衷相符,
如果你的任务开始是在虚拟机中运行的,你的虚拟机有一个 IP,
可以和项目中其他虚拟机通信。这里的模型是基本相同的。
Kubernetes 的 IP 地址存在于 Pod
范围内 —— 容器共享它们的网络命名空间 ——
包括它们的 IP 地址和 MAC 地址。
这就意味着 Pod
内的容器都可以通过 localhost
到达对方端口。
这也意味着 Pod
内的容器需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同,
这也被称为“一个 Pod 一个 IP”模型。
如何实现以上需求是所使用的特定容器运行时的细节。
也可以在 Node
本身请求端口,并用这类端口转发到你的 Pod
(称之为主机端口),
但这是一个很特殊的操作。转发方式如何实现也是容器运行时的细节。
Pod
自己并不知道这些主机端口的存在。
Kubernetes 网络解决四方面的问题:
集群网络 解释了如何为集群设置网络,
还概述了所涉及的技术。
3.6.1 - 服务(Service) 将在集群中运行的应用程序暴露在单个外向端点后面,即使工作负载分散到多个后端也是如此。
将运行在一组
Pods 上的应用程序公开为网络服务的抽象方法。
使用 Kubernetes,你无需修改应用程序即可使用不熟悉的服务发现机制。
Kubernetes 为 Pod 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名,
并且可以在它们之间进行负载均衡。
动机 创建和销毁 Kubernetes Pod 以匹配集群的期望状态。
Pod 是非永久性资源。
如果你使用 Deployment
来运行你的应用程序,则它可以动态创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,
那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
进入 Services 。
Service 资源 Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略 —— 通常称为微服务。
Service 所针对的 Pod 集合通常是通过选择算符 来确定的。
要了解定义服务端点的其他方法,请参阅不带选择算符的服务 。
举个例子,考虑一个图片处理后端,它运行了 3 个副本。这些副本是可互换的 ——
前端不需要关心它们调用了哪个后端副本。
然而组成这一组后端程序的 Pod 实际上可能会发生变化,
前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。
Service 定义的抽象能够解耦这种关联。
云原生服务发现 如果你想要在应用程序中使用 Kubernetes API 进行服务发现,则可以查询
API 服务器
用于匹配 EndpointSlices。只要服务中的 Pod 集合发生更改,Kubernetes 就会为服务更新 EndpointSlices。
对于非本机应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
定义 Service Service 在 Kubernetes 中是一个 REST 对象,和 Pod 类似。
像所有的 REST 对象一样,Service 定义可以基于 POST
方式,请求 API server 创建新的实例。
Service 对象的名称必须是合法的
RFC 1035 标签名称 。
例如,假定有一组 Pod,它们对外暴露了 9376 端口,同时还被打上 app=MyApp
标签:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
上述配置创建一个名称为 "my-service" 的 Service 对象,它会将请求代理到使用
TCP 端口 9376,并且具有标签 app.kubernetes.io/name=MyApp
的 Pod 上。
Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群 IP”),该 IP 地址由服务代理使用。
(请参见下面的 VIP 和 Service 代理 ).
服务选择算符的控制器不断扫描与其选择算符匹配的 Pod,然后将所有更新发布到也称为
“my-service” 的 Endpoint 对象。
说明: 需要注意的是,Service 能够将一个接收 port
映射到任意的 targetPort
。
默认情况下,targetPort
将被设置为与 port
字段相同的值。
Pod 中的端口定义是有名字的,你可以在 Service 的 targetPort
属性中引用这些名称。
例如,我们可以通过以下方式将 Service 的 targetPort
绑定到 Pod 端口:
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
app.kubernetes.io/name : proxy
spec :
containers :
- name : nginx
image : nginx:stable
ports :
- containerPort : 80
name : http-web-svc
---
apiVersion : v1
kind : Service
metadata :
name : nginx-service
spec :
selector :
app.kubernetes.io/name : proxy
ports :
- name : name-of-service-port
protocol : TCP
port : 80
targetPort : http-web-svc
即使 Service 中使用同一配置名称混合使用多个 Pod,各 Pod 通过不同的端口号支持相同的网络协议,
此功能也可以使用。这为 Service 的部署和演化提供了很大的灵活性。
例如,你可以在新版本中更改 Pod 中后端软件公开的端口号,而不会破坏客户端。
服务的默认协议是 TCP;你还可以使用任何其他受支持的协议 。
由于许多服务需要公开多个端口,因此 Kubernetes 在服务对象上支持多个端口定义。
每个端口定义可以具有相同的 protocol
,也可以具有不同的协议。
没有选择算符的 Service 由于选择算符的存在,服务最常见的用法是为 Kubernetes Pod 的访问提供抽象,
但是当与相应的 EndpointSlices
对象一起使用且没有选择算符时,
服务也可以为其他类型的后端提供抽象,包括在集群外运行的后端。
例如:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。 希望服务指向另一个 名字空间(Namespace) 中或其它集群中的服务。 你正在将工作负载迁移到 Kubernetes。在评估该方法时,你仅在 Kubernetes 中运行一部分后端。 在任何这些场景中,都能够定义没有选择算符的 Service。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
由于此服务没有选择算符,因此不会自动创建相应的 EndpointSlice(和旧版 Endpoint)对象。
你可以通过手动添加 EndpointSlice 对象,将服务手动映射到运行该服务的网络地址和端口:
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : my-service-1 # 按惯例将服务的名称用作 EndpointSlice 名称的前缀
labels :
# 你应设置 "kubernetes.io/service-name" 标签。
# 设置其值以匹配服务的名称
kubernetes.io/service-name : my-service
addressType : IPv4
ports :
- name : '' # 留空,因为 port 9376 未被 IANA 分配为已注册端口
appProtocol : http
protocol : TCP
port : 9376
endpoints :
- addresses :
- "10.4.5.6" # 此列表中的 IP 地址可以按任何顺序显示
- "10.1.2.3"
自定义 EndpointSlices 当为服务创建 EndpointSlice 对象时,可以为 EndpointSlice 使用任何名称。
命名空间中的每个 EndpointSlice 必须有一个唯一的名称。通过在 EndpointSlice 上设置
kubernetes.io/service-name
label
可以将 EndpointSlice 链接到服务。
说明: 端点 IP 地址必须不是 :本地回路地址(IPv4 的 127.0.0.0/8、IPv6 的 ::1/128)
或链路本地地址(IPv4 的 169.254.0.0/16 和 224.0.0.0/24、IPv6 的 fe80::/64)。
端点 IP 地址不能是其他 Kubernetes 服务的集群 IP,因为
kube-proxy 不支持将虚拟 IP 作为目标。
对于你自己或在你自己代码中创建的 EndpointSlice,你还应该为
endpointslice.kubernetes.io/managed-by
标签拣选一个值。如果你创建自己的控制器代码来管理 EndpointSlice,
请考虑使用类似于 "my-domain.example/name-of-controller"
的值。
如果你使用的是第三方工具,请使用全小写的工具名称,并将空格和其他标点符号更改为短划线 (-
)。
如果人们直接使用 kubectl
之类的工具来管理 EndpointSlices,请使用描述这种手动管理的名称,
例如 "staff"
或 "cluster-admins"
。你应该避免使用保留值 "controller"
,
该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlices。
访问没有选择算符的 Service 访问没有选择算符的 Service,与有选择算符的 Service 的原理相同。
在没有选择算符的 Service 示例 中,
流量被路由到 EndpointSlice 清单中定义的两个端点之一:
通过 TCP 协议连接到 10.1.2.3 或 10.4.5.6 的端口 9376。
ExternalName Service 是 Service 的特例,它没有选择算符,但是使用 DNS 名称。
有关更多信息,请参阅本文档后面的 ExternalName 。
EndpointSlices 特性状态: Kubernetes v1.21 [stable]
EndpointSlices
这些对象表示针对服务的后备网络端点的子集(切片 )。
你的 Kubernetes 集群会跟踪每个 EndpointSlice 表示的端点数量。
如果服务的端点太多以至于达到阈值,Kubernetes 会添加另一个空的 EndpointSlice 并在其中存储新的端点信息。
默认情况下,一旦现有 EndpointSlice 都包含至少 100 个端点,Kubernetes 就会创建一个新的 EndpointSlice。
在需要添加额外的端点之前,Kubernetes 不会创建新的 EndpointSlice。
参阅 EndpointSlices
了解有关该 API 的更多信息。
Endpoints 在 Kubernetes API 中,Endpoints
(该资源类别为复数)定义了网络端点的列表,通常由 Service 引用,以定义可以将流量发送到哪些 Pod。
推荐用 EndpointSlice API 替换 Endpoints。
超出容量的端点 Kubernetes 限制单个 Endpoints 对象中可以容纳的端点数量。
当一个服务有超过 1000 个后备端点时,Kubernetes 会截断 Endpoints 对象中的数据。
由于一个服务可以链接多个 EndpointSlice,所以 1000 个后备端点的限制仅影响旧版的 Endpoints API。
这种情况下,Kubernetes 选择最多 1000 个可能的后端端点来存储到 Endpoints 对象中,并在
Endpoints: endpoints.kubernetes.io/over-capacity: truncated
上设置注解 。
如果后端 Pod 的数量低于 1000,控制平面也会移除该注解。
流量仍会发送到后端,但任何依赖旧版 Endpoints API 的负载均衡机制最多只能将流量发送到 1000 个可用的后备端点。
相同的 API 限制意味着你不能手动将 Endpoints 更新为拥有超过 1000 个端点。
应用协议 特性状态: Kubernetes v1.20 [stable]
appProtocol
字段提供了一种为每个 Service 端口指定应用协议的方式。
此字段的取值会被映射到对应的 Endpoints 和 EndpointSlices 对象。
该字段遵循标准的 Kubernetes 标签语法。
其值可以是 IANA 标准服务名称
或以域名为前缀的名称,如 mycompany.com/my-custom-protocol
。
虚拟 IP 和 Service 代理 在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy
进程。
kube-proxy
负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是
ExternalName
的形式。
为什么不使用 DNS 轮询? 时不时会有人问到为什么 Kubernetes 依赖代理将入站流量转发到后端。那其他方法呢?
例如,是否可以配置具有多个 A 值(或 IPv6 为 AAAA)的 DNS 记录,并依靠轮询名称解析?
使用服务代理有以下几个原因:
DNS 实现的历史由来已久,它不遵守记录 TTL,并且在名称查找结果到期后对其进行缓存。 有些应用程序仅执行一次 DNS 查找,并无限期地缓存结果。 即使应用和库进行了适当的重新解析,DNS 记录上的 TTL 值低或为零也可能会给
DNS 带来高负载,从而使管理变得困难。 在本页下文中,你可以了解各种 kube-proxy 实现是如何工作的。
总的来说,你应该注意当运行 kube-proxy
时,内核级别的规则可能会被修改(例如,可能会创建 iptables 规则),
在某些情况下直到你重新引导才会清理这些内核级别的规则。
因此,运行 kube-proxy 只能由了解在计算机上使用低级别、特权网络代理服务后果的管理员来完成。
尽管 kube-proxy
可执行文件支持 cleanup
功能,但此功能不是官方特性,因此只能按原样使用。
配置 请注意,kube-proxy 可以以不同的模式启动,具体取决于其配置。
kube-proxy 的配置是通过 ConfigMap 完成的,并且 kube-proxy 的 ConfigMap 有效地弃用了 kube-proxy 几乎所有标志的行为。 kube-proxy 的 ConfigMap 不支持实时重新加载配置。 kube-proxy 的 ConfigMap 参数不能在启动时被全部校验和验证。
例如,如果你的操作系统不允许你运行 iptables 命令,则标准内核 kube-proxy 实现将无法工作。
同样,如果你的操作系统不支持 netsh
,它将无法在 Windows 用户空间模式下运行。 userspace 代理模式 这种模式,kube-proxy 会监视 Kubernetes 控制平面对 Service 对象和 Endpoints 对象的添加和移除操作。
对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。
任何连接到“代理端口”的请求,都会被代理到 Service 的后端 Pods
中的某个上面(如 Endpoints
所报告的一样)。
使用哪个后端 Pod,是 kube-proxy 基于 SessionAffinity
来确定的。
最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP
(是虚拟 IP)
和 Port
的请求,并重定向到代理端口,代理端口再代理请求到后端Pod。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。
iptables 代理模式 这种模式,kube-proxy
会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。
对每个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP
和端口的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面。
对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个后端。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,
而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应,则连接失败。
这与用户空间模式不同:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接已失败,
并会自动使用其他后端 Pod 重试。
你可以使用 Pod 就绪探测器
验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。
这样做意味着你避免将流量通过 kube-proxy 发送到已知已失败的 Pod。
IPVS 代理模式 特性状态: Kubernetes v1.11 [stable]
在 ipvs
模式下,kube-proxy 监视 Kubernetes 服务和端点,调用 netlink
接口相应地创建 IPVS 规则,
并定期将 IPVS 规则与 Kubernetes 服务和端点同步。该控制循环可确保 IPVS
状态与所需状态匹配。访问服务时,IPVS 将流量定向到后端 Pod 之一。
IPVS 代理模式基于类似于 iptables 模式的 netfilter 挂钩函数,
但是使用哈希表作为基础数据结构,并且在内核空间中工作。
这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy
重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。
与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 提供了更多选项来平衡后端 Pod 的流量。这些是:
rr
:轮替(Round-Robin)lc
:最少链接(Least Connection),即打开链接数量最少者优先dh
:目标地址哈希(Destination Hashing)sh
:源地址哈希(Source Hashing)sed
:最短预期延迟(Shortest Expected Delay)nq
:从不排队(Never Queue)说明: 要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前使 IPVS 在节点上可用。
当 kube-proxy 以 IPVS 代理模式启动时,它将验证 IPVS 内核模块是否可用。
如果未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
在这些代理模型中,绑定到服务 IP 的流量:
在客户端不了解 Kubernetes 或服务或 Pod 的任何信息的情况下,将 Port 代理到适当的后端。
如果要确保每次都将来自特定客户端的连接传递到同一 Pod,
则可以通过将 service.spec.sessionAffinity
设置为 "ClientIP"
(默认值是 "None"),来基于客户端的 IP 地址选择会话亲和性。
你还可以通过适当设置 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来设置最大会话停留时间。(默认值为 10800 秒,即 3 小时)。
说明: 在 Windows 上,不支持为服务设置最大会话停留时间。
多端口 Service 对于某些服务,你需要公开多个端口。
Kubernetes 允许你在 Service 对象上配置多个端口定义。
为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- name : http
protocol : TCP
port : 80
targetPort : 9376
- name : https
protocol : TCP
port : 443
targetPort : 9377
说明: 与一般的Kubernetes名称一样,端口名称只能包含小写字母数字字符 和 -
。
端口名称还必须以字母数字字符开头和结尾。
例如,名称 123-abc
和 web
有效,但是 123_abc
和 -web
无效。
选择自己的 IP 地址 在 Service
创建的请求中,可以通过设置 spec.clusterIP
字段来指定自己的集群 IP 地址。
比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。
用户选择的 IP 地址必须合法,并且这个 IP 地址在 service-cluster-ip-range
CIDR 范围内,
这对 API 服务器来说是通过一个标识来指定的。
如果 IP 地址不合法,API 服务器会返回 HTTP 状态码 422,表示值不合法。
流量策略 外部流量策略 你可以通过设置 spec.externalTrafficPolicy
字段来控制来自于外部的流量是如何路由的。
可选值有 Cluster
和 Local
。字段设为 Cluster
会将外部流量路由到所有就绪的端点,
设为 Local
会只路由到当前节点上就绪的端点。
如果流量策略设置为 Local
,而且当前节点上没有就绪的端点,kube-proxy 不会转发请求相关服务的任何流量。
说明: 特性状态: Kubernetes v1.22 [alpha]
如果你启用了 kube-proxy 的 ProxyTerminatingEndpoints
特性门控 ,
kube-proxy 会检查节点是否有本地的端点,以及是否所有的本地端点都被标记为终止中。
如果本地有端点,而且所有端点处于终止中的状态,那么 kube-proxy 会忽略任何设为 Local
的外部流量策略。
在所有本地端点处于终止中的状态的同时,kube-proxy 将请求指定服务的流量转发到位于其它节点的状态健康的端点,
如同外部流量策略设为 Cluster
。
针对处于正被终止状态的端点这一转发行为使得外部负载均衡器可以优雅地排出由
NodePort
服务支持的连接,就算是健康检查节点端口开始失败也是如此。
否则,当节点还在负载均衡器的节点池内,在 Pod 终止过程中的流量会被丢掉,这些流量可能会丢失。
内部流量策略 特性状态: Kubernetes v1.22 [beta]
你可以设置 spec.internalTrafficPolicy
字段来控制内部来源的流量是如何转发的。可设置的值有 Cluster
和 Local
。
将字段设置为 Cluster
会将内部流量路由到所有就绪端点,设置为 Local
只会路由到当前节点上就绪的端点。
如果流量策略是 Local
,而且当前节点上没有就绪的端点,那么 kube-proxy 会丢弃流量。
服务发现 Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。
环境变量 当 Pod 运行在 Node
上,kubelet 会为每个活跃的 Service 添加一组环境变量。
kubelet 为 Pod 添加环境变量 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
。
这里 Service 的名称需大写,横线被转换成下划线。
它还支持与 Docker Engine 的 "legacy container links " 特性兼容的变量
(参阅 makeLinkVariables ) 。
举个例子,一个名称为 redis-primary
的 Service 暴露了 TCP 端口 6379,
同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
REDIS_PRIMARY_SERVICE_HOST = 10.0.0.11
REDIS_PRIMARY_SERVICE_PORT = 6379
REDIS_PRIMARY_PORT = tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP = tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO = tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT = 6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR = 10.0.0.11
说明: 当你具有需要访问服务的 Pod 时,并且你正在使用环境变量方法将端口和集群 IP 发布到客户端
Pod 时,必须在客户端 Pod 出现 之前 创建服务。
否则,这些客户端 Pod 将不会设定其环境变量。
如果仅使用 DNS 查找服务的集群 IP,则无需担心此设定问题。
DNS 你可以(几乎总是应该)使用附加组件
为 Kubernetes 集群设置 DNS 服务。
支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录。
如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。
例如,如果你在 Kubernetes 命名空间 my-ns
中有一个名为 my-service
的服务,
则控制平面和 DNS 服务共同为 my-service.my-ns
创建 DNS 记录。
my-ns
命名空间中的 Pod 应该能够通过按名检索 my-service
来找到服务
(my-service.my-ns
也可以工作)。
其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns
。
这些名称将解析为为服务分配的集群 IP。
Kubernetes 还支持命名端口的 DNS SRV(服务)记录。
如果 my-service.my-ns
服务具有名为 http
的端口,且协议设置为 TCP,
则可以对 _http._tcp.my-service.my-ns
执行 DNS SRV 查询以发现该端口号、"http"
以及 IP 地址。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName
类型的 Service 的方式。
更多关于 ExternalName
信息可以查看
DNS Pod 和 Service 。
无头服务(Headless Services) 有时不需要或不想要负载均衡,以及单独的 Service IP。
遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP
)的值为 "None"
来创建 Headless
Service。
你可以使用一个无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。
对于无头 Services
并不会分配 Cluster IP,kube-proxy 不会处理它们,
而且平台也不会为它们进行负载均衡和路由。
DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。
带选择算符的服务 对定义了选择算符的无头服务,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象,
并且修改 DNS 配置返回 A 或 AAA 条记录(IPv4 或 IPv6 地址),通过这个地址直接到达 Service
的后端 Pod 上。
无选择算符的服务 对没有定义选择算符的无头服务,控制平面不会创建 EndpointSlice 对象。
然而 DNS 系统会查找和配置以下之一:
对于 type: ExternalName
服务,查找和配置其 CNAME 记录 对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A / AAAA 条记录对于 IPv4 端点,DNS 系统创建 A 条记录。 对于 IPv6 端点,DNS 系统创建 AAAA 条记录。 发布服务(服务类型) 对一些应用的某些部分(如前端),可能希望将其暴露给 Kubernetes 集群外部的 IP 地址。
Kubernetes ServiceTypes
允许指定你所需要的 Service 类型。
Type
的取值以及行为如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。
这也是你没有为服务显式指定 type
时使用的默认值。
NodePort
:通过每个节点上的 IP 和静态端口(NodePort
)暴露服务。
为了让节点端口可用,Kubernetes 设置了集群 IP 地址,这等同于你请求 type: ClusterIP
的服务。
LoadBalancer
:使用云提供商的负载均衡器向外部暴露服务。
外部负载均衡器可以将流量路由到自动创建的 NodePort
服务和 ClusterIP
服务上。
ExternalName
:通过返回 CNAME
记录和对应值,可以将服务映射到
externalName
字段的内容(例如,foo.bar.example.com
)。
无需创建任何类型代理。
说明: 你需要使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用 ExternalName
类型。
你也可以使用 Ingress 来暴露自己的服务。
Ingress 不是一种服务类型,但它充当集群的入口点。
它可以将路由规则整合到一个资源中,因为它可以在同一 IP 地址下公开多个服务。
NodePort 类型 如果你将 type
字段设置为 NodePort
,则 Kubernetes 控制平面将在
--service-node-port-range
标志指定的范围内分配端口(默认值:30000-32767)。
每个节点将那个端口(每个节点上的相同端口号)代理到你的服务中。
你的服务在其 .spec.ports[*].nodePort
字段中报告已分配的端口。
使用 NodePort 可以让你自由设置自己的负载均衡解决方案,
配置 Kubernetes 不完全支持的环境,
甚至直接暴露一个或多个节点的 IP 地址。
对于 NodePort 服务,Kubernetes 额外分配一个端口(TCP、UDP 或 SCTP 以匹配服务的协议)。
集群中的每个节点都将自己配置为监听分配的端口并将流量转发到与该服务关联的某个就绪端点。
通过使用适当的协议(例如 TCP)和适当的端口(分配给该服务)连接到所有节点,
你将能够从集群外部使用 type: NodePort
服务。
选择你自己的端口 如果需要特定的端口号,你可以在 nodePort
字段中指定一个值。
控制平面将为你分配该端口或报告 API 事务失败。
这意味着你需要自己注意可能发生的端口冲突。
你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。
以下是 type: NodePort
服务的一个示例清单,它指定了一个 NodePort 值(在本例中为 30007)。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
type : NodePort
selector :
app.kubernetes.io/name : MyApp
ports :
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port : 80
targetPort : 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort : 30007
为 type: NodePort
服务自定义 IP 地址配置 你可以在集群中设置节点以使用特定 IP 地址来提供 NodePort 服务。
如果每个节点都连接到多个网络(例如:一个网络用于应用程序流量,另一个网络用于节点和控制平面之间的流量),
你可能需要执行此操作。
如果你要指定特定的 IP 地址来代理端口,可以将 kube-proxy 的 --nodeport-addresses
标志或
kube-proxy 配置文件 的等效
nodePortAddresses
字段设置为特定的 IP 段。
此标志采用逗号分隔的 IP 段列表(例如 10.0.0.0/8
、192.0.2.0/25
)来指定 kube-proxy 应视为该节点本地的
IP 地址范围。
例如,如果你使用 --nodeport-addresses=127.0.0.0/8
标志启动 kube-proxy,
则 kube-proxy 仅选择 NodePort 服务的环回接口。
--nodeport-addresses
的默认值是一个空列表。
这意味着 kube-proxy 应考虑 NodePort 的所有可用网络接口。
(这也与早期的 Kubernetes 版本兼容。)
请注意,此服务显示为 <NodeIP>:spec.ports[*].nodePort
和 .spec.clusterIP:spec.ports[*].port
。
如果设置了 kube-proxy 的 --nodeport-addresses
标志或 kube-proxy 配置文件中的等效字段,
则 <NodeIP>
将是过滤的节点 IP 地址(或可能的 IP 地址)。
LoadBalancer 类型 在使用支持外部负载均衡器的云提供商的服务时,设置 type
的值为 "LoadBalancer"
,
将为 Service 提供负载均衡器。
负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的
status.loadBalancer
字段发布出去。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
clusterIP : 10.0.171.239
type : LoadBalancer
status :
loadBalancer :
ingress :
- ip : 192.0.2.127
来自外部负载均衡器的流量将直接重定向到后端 Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。
某些云提供商允许设置 loadBalancerIP
。
在这些情况下,将根据用户设置的 loadBalancerIP
来创建负载均衡器。
如果没有设置 loadBalancerIP
字段,将会给负载均衡器指派一个临时 IP。
如果设置了 loadBalancerIP
,但云提供商并不支持这种特性,那么设置的
loadBalancerIP
值将会被忽略掉。
要实现 type: LoadBalancer
的服务,Kubernetes 通常首先进行与请求 type: NodePort
服务等效的更改。
cloud-controller-manager 组件然后配置外部负载均衡器以将流量转发到已分配的节点端口。
作为 Alpha 特性 ,你可以将负载均衡服务配置为忽略 分配节点端口,
前提是云提供商实现支持这点。
混合协议类型的负载均衡器 特性状态: Kubernetes v1.20 [alpha]
默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时,
所有端口必须具有相同的协议,并且该协议必须是受云提供商支持的协议。
当服务中定义了多个端口时,特性门控 MixedProtocolLBService
(在 kube-apiserver 1.24 版本默认为启用)允许
LoadBalancer 类型的服务使用不同的协议。
说明: 可用于 LoadBalancer 类型服务的协议集仍然由云提供商决定。
如果云提供商不支持混合协议,他们将只提供单一协议。
禁用负载均衡器节点端口分配 特性状态: Kubernetes v1.24 [stable]
你可以通过设置 spec.allocateLoadBalancerNodePorts
为 false
对类型为 LoadBalancer 的服务禁用节点端口分配。
这仅适用于直接将流量路由到 Pod 而不是使用节点端口的负载均衡器实现。
默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,
LoadBalancer 类型的服务继续分配节点端口。
如果现有服务已被分配节点端口,将参数 spec.allocateLoadBalancerNodePorts
设置为 false
时,这些服务上已分配置的节点端口不会 被自动释放。
你必须显式地在每个服务端口中删除 nodePorts
项以释放对应端口。
设置负载均衡器实现的类别 特性状态: Kubernetes v1.24 [stable]
spec.loadBalancerClass
允许你不使用云提供商的默认负载均衡器实现,转而使用指定的负载均衡器实现。
默认情况下,.spec.loadBalancerClass
的取值是 nil
,如果集群使用 --cloud-provider
配置了云提供商,
LoadBalancer
类型服务会使用云提供商的默认负载均衡器实现。
如果设置了 .spec.loadBalancerClass
,则假定存在某个与所指定的类相匹配的负载均衡器实现在监视服务变化。
所有默认的负载均衡器实现(例如,由云提供商所提供的)都会忽略设置了此字段的服务。.spec.loadBalancerClass
只能设置到类型为 LoadBalancer
的 Service 之上,而且一旦设置之后不可变更。
.spec.loadBalancerClass
的值必须是一个标签风格的标识符,
可以有选择地带有类似 "internal-vip
" 或 "example.com/internal-vip
" 这类前缀。
没有前缀的名字是保留给最终用户的。
内部负载均衡器 在混合环境中,有时有必要在同一(虚拟)网络地址块内路由来自服务的流量。
在水平分割 DNS 环境中,你需要两个服务才能将内部和外部流量都路由到你的端点(Endpoints)。
如要设置内部负载均衡器,请根据你所使用的云运营商,为服务添加以下注解之一。
[...]
metadata :
name : my-service
annotations :
cloud.google.com/load-balancer-type : "Internal"
[...]
[...]
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-internal : "true"
[...]
[...]
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/azure-load-balancer-internal : "true"
[...]
[...]
metadata :
name : my-service
annotations :
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type : "private"
[...]
[...]
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/openstack-internal-load-balancer : "true"
[...]
[...]
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/cce-load-balancer-internal-vpc : "true"
[...]
[...]
metadata :
annotations :
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid : subnet-xxxxx
[...]
[...]
metadata :
annotations :
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type : "intranet"
[...]
[...]
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/oci-load-balancer-internal : true
[...]
AWS TLS 支持 为了对在 AWS 上运行的集群提供 TLS/SSL 部分支持,你可以向 LoadBalancer
服务添加三个注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-ssl-cert : arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
第一个指定要使用的证书的 ARN。 它可以是已上载到 IAM 的第三方颁发者的证书,
也可以是在 AWS Certificate Manager 中创建的证书。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-backend-protocol : (https|http|ssl|tcp)
第二个注解指定 Pod 使用哪种协议。对于 HTTPS 和 SSL,ELB 希望 Pod
使用证书通过加密连接对自己进行身份验证。
HTTP 和 HTTPS 选择第7层代理:ELB 终止与用户的连接,解析标头,并在转发请求时向
X-Forwarded-For
标头注入用户的 IP 地址(Pod 仅在连接的另一端看到 ELB 的 IP 地址)。
TCP 和 SSL 选择第4层代理:ELB 转发流量而不修改报头。
在某些端口处于安全状态而其他端口未加密的混合使用环境中,可以使用以下注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-backend-protocol : http
service.beta.kubernetes.io/aws-load-balancer-ssl-ports : "443,8443"
在上例中,如果服务包含 80
、443
和 8443
三个端口, 那么 443
和 8443
将使用 SSL 证书,
而 80
端口将转发 HTTP 数据包。
从 Kubernetes v1.9 起可以使用
预定义的 AWS SSL 策略
为你的服务使用 HTTPS 或 SSL 侦听器。
要查看可以使用哪些策略,可以使用 aws
命令行工具:
aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'
然后,你可以使用 "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy
"
注解; 例如:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy : "ELBSecurityPolicy-TLS-1-2-2017-01"
AWS 上的 PROXY 协议支持 为了支持在 AWS 上运行的集群,启用
PROXY 协议 。
你可以使用以下服务注解:
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol : "*"
从 1.3.0 版开始,此注解的使用适用于 ELB 代理的所有端口,并且不能进行其他配置。
AWS 上的 ELB 访问日志 有几个注解可用于管理 AWS 上 ELB 服务的访问日志。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-enabled
控制是否启用访问日志。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval
控制发布访问日志的时间间隔(以分钟为单位)。你可以指定 5 分钟或 60 分钟的间隔。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name
控制存储负载均衡器访问日志的 Amazon S3 存储桶的名称。
注解 service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix
指定为 Amazon S3 存储桶创建的逻辑层次结构。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled : "true"
# 指定是否为负载均衡器启用访问日志
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval : "60"
# 发布访问日志的时间间隔。你可以将其设置为 5 分钟或 60 分钟。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name : "my-bucket"
# 用来存放访问日志的 Amazon S3 Bucket 名称
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix : "my-bucket-prefix/prod"
# 你为 Amazon S3 Bucket 所创建的逻辑层次结构,例如 `my-bucket-prefix/prod`
AWS 上的连接排空 可以将注解 service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled
设置为 "true"
来管理 ELB 的连接排空。
注解 service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout
也可以用于设置最大时间(以秒为单位),以保持现有连接在注销实例之前保持打开状态。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled : "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout : "60"
其他 ELB 注解 还有其他一些注解,用于管理经典弹性负载均衡器,如下所述。
metadata :
name : my-service
annotations :
# 按秒计的时间,表示负载均衡器关闭连接之前连接可以保持空闲
# (连接上无数据传输)的时间长度
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout : "60"
# 指定该负载均衡器上是否启用跨区的负载均衡能力
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled : "true"
# 逗号分隔列表值,每一项都是一个键-值耦对,会作为额外的标签记录于 ELB 中
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags : "environment=prod,owner=devops"
# 将某后端视为健康、可接收请求之前需要达到的连续成功健康检查次数。
# 默认为 2,必须介于 2 和 10 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold : ""
# 将某后端视为不健康、不可接收请求之前需要达到的连续不成功健康检查次数。
# 默认为 6,必须介于 2 和 10 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold : "3"
# 对每个实例进行健康检查时,连续两次检查之间的大致间隔秒数
# 默认为 10,必须介于 5 和 300 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval : "20"
# 时长秒数,在此期间没有响应意味着健康检查失败
# 此值必须小于 service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval
# 默认值为 5,必须介于 2 和 60 之间
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout : "5"
# 由已有的安全组所构成的列表,可以配置到所创建的 ELB 之上。
# 与注解 service.beta.kubernetes.io/aws-load-balancer-extra-security-groups 不同,
# 这一设置会替代掉之前指定给该 ELB 的所有其他安全组,也会覆盖掉为此
# ELB 所唯一创建的安全组。
# 此列表中的第一个安全组 ID 被用来作为决策源,以允许入站流量流入目标工作节点
# (包括服务流量和健康检查)。
# 如果多个 ELB 配置了相同的安全组 ID,为工作节点安全组添加的允许规则行只有一个,
# 这意味着如果你删除了这些 ELB 中的任何一个,都会导致该规则记录被删除,
# 以至于所有共享该安全组 ID 的其他 ELB 都无法访问该节点。
# 此注解如果使用不当,会导致跨服务的不可用状况。
service.beta.kubernetes.io/aws-load-balancer-security-groups : "sg-53fae93f"
# 额外的安全组列表,将被添加到所创建的 ELB 之上。
# 添加时,会保留为 ELB 所专门创建的安全组。
# 这样会确保每个 ELB 都有一个唯一的安全组 ID 和与之对应的允许规则记录,
# 允许请求(服务流量和健康检查)发送到目标工作节点。
# 这里顶一个安全组可以被多个服务共享。
service.beta.kubernetes.io/aws-load-balancer-extra-security-groups : "sg-53fae93f,sg-42efd82e"
# 用逗号分隔的一个键-值偶对列表,用来为负载均衡器选择目标节点
service.beta.kubernetes.io/aws-load-balancer-target-node-labels : "ingress-gw,gw-name=public-api"
AWS 上网络负载均衡器支持 特性状态: Kubernetes v1.15 [beta]
要在 AWS 上使用网络负载均衡器,可以使用注解
service.beta.kubernetes.io/aws-load-balancer-type
,将其取值设为 nlb
。
metadata :
name : my-service
annotations :
service.beta.kubernetes.io/aws-load-balancer-type : "nlb"
说明: NLB 仅适用于某些实例类。有关受支持的实例类型的列表,
请参见
AWS 文档
中关于所支持的实例类型的 Elastic Load Balancing 说明。
与经典弹性负载均衡器不同,网络负载均衡器(NLB)将客户端的 IP 地址转发到该节点。
如果服务的 .spec.externalTrafficPolicy
设置为 Cluster
,则客户端的IP地址不会传达到最终的 Pod。
通过将 .spec.externalTrafficPolicy
设置为 Local
,客户端IP地址将传播到最终的 Pod,
但这可能导致流量分配不均。
没有针对特定 LoadBalancer 服务的任何 Pod 的节点将无法通过自动分配的
.spec.healthCheckNodePort
进行 NLB 目标组的运行状况检查,并且不会收到任何流量。
为了获得均衡流量,请使用 DaemonSet 或指定
Pod 反亲和性
使其不在同一节点上。
你还可以将 NLB 服务与内部负载均衡器
注解一起使用。
为了使客户端流量能够到达 NLB 后面的实例,使用以下 IP 规则修改了节点安全组:
Rule Protocol Port(s) IpRange(s) IpRange Description Health Check TCP NodePort(s) (.spec.healthCheckNodePort
for .spec.externalTrafficPolicy = Local
) Subnet CIDR kubernetes.io/rule/nlb/health=<loadBalancerName> Client Traffic TCP NodePort(s) .spec.loadBalancerSourceRanges
(默认值为 0.0.0.0/0
)kubernetes.io/rule/nlb/client=<loadBalancerName> MTU Discovery ICMP 3,4 .spec.loadBalancerSourceRanges
(默认值为 0.0.0.0/0
)kubernetes.io/rule/nlb/mtu=<loadBalancerName>
为了限制哪些客户端 IP 可以访问网络负载均衡器,请指定 loadBalancerSourceRanges
。
spec :
loadBalancerSourceRanges :
- "143.231.0.0/16"
说明: 如果未设置 .spec.loadBalancerSourceRanges
,则 Kubernetes 允许从 0.0.0.0/0
到节点安全组的流量。
如果节点具有公共 IP 地址,请注意,非 NLB 流量也可以到达那些修改后的安全组中的所有实例。
有关弹性 IP 注解和更多其他常见用例,
请参阅AWS 负载均衡控制器文档 。
腾讯 Kubernetes 引擎(TKE)上的 CLB 注解 以下是在 TKE 上管理云负载均衡器的注解。
metadata :
name : my-service
annotations :
# 绑定负载均衡器到指定的节点。
service.kubernetes.io/qcloud-loadbalancer-backends-label : key in (value1, value2)
# 为已有负载均衡器添加 ID。
service.kubernetes.io/tke-existed-lbid:lb-6swtxxxx
# 负载均衡器(LB)的自定义参数尚不支持修改 LB 类型。
service.kubernetes.io/service.extensiveParameters : ""
# 自定义负载均衡监听器。
service.kubernetes.io/service.listenerParameters : ""
# 指定负载均衡类型。
# 可用参数: classic (Classic Cloud Load Balancer) 或 application (Application Cloud Load Balancer)
service.kubernetes.io/loadbalance-type : xxxxx
# 指定公用网络带宽计费方法。
# 可用参数: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic) 和 BANDWIDTH_POSTPAID_BY_HOUR (bill-by-bandwidth).
service.kubernetes.io/qcloud-loadbalancer-internet-charge-type : xxxxxx
# 指定带宽参数 (取值范围: [1,2000] Mbps).
service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out : "10"
# 当设置该注解时,负载均衡器将只注册正在运行 Pod 的节点,
# 否则所有节点将会被注册。
service.kubernetes.io/local-svc-only-bind-node-with-pod : true
ExternalName 类型 类型为 ExternalName 的服务将服务映射到 DNS 名称,而不是典型的选择算符,例如 my-service
或者 cassandra
。
你可以使用 spec.externalName
参数指定这些服务。
例如,以下 Service 定义将 prod
名称空间中的 my-service
服务映射到 my.database.example.com
:
apiVersion : v1
kind : Service
metadata :
name : my-service
namespace : prod
spec :
type : ExternalName
externalName : my.database.example.com
说明: ExternalName 服务接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。
类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。
要对 IP 地址进行硬编码,请考虑使用无头 Services 。
当查找主机 my-service.prod.svc.cluster.local
时,集群 DNS 服务返回 CNAME
记录,
其值为 my.database.example.com
。
访问 my-service
的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。
如果以后你决定将数据库移到集群中,则可以启动其 Pod,添加适当的选择算符或端点以及更改服务的 type
。
警告: 对于一些常见的协议,包括 HTTP 和 HTTPS,你使用 ExternalName 可能会遇到问题。
如果你使用 ExternalName,那么集群内客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,此差异可能会导致错误或意外响应。
HTTP 请求将具有源服务器无法识别的 Host:
标头;
TLS 服务器将无法提供与客户端连接的主机名匹配的证书。
外部 IP 如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs
。
通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量,
将会被路由到 Service 的 Endpoint 上。
externalIPs
不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
根据 Service 的规定,externalIPs
可以同任意的 ServiceType
来一起指定。
在上面的例子中,my-service
可以在 "80.11.12.10:80
"(externalIP:port
) 上被客户端访问。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- name : http
protocol : TCP
port : 80
targetPort : 9376
externalIPs :
- 80.11.12.10
不足之处 为 VIP 使用用户空间代理,将只适合小型到中型规模的集群,不能够扩展到上千 Service 的大型集群。
查看最初设计方案 获取更多细节。
使用用户空间代理,隐藏了访问 Service 的数据包的源 IP 地址。
这使得一些类型的防火墙无法起作用。
iptables 代理不会隐藏 Kubernetes 集群内部的 IP 地址,
但却要求客户端请求必须通过一个负载均衡器或 Node 端口。
Type
字段支持嵌套功能 —— 每一层需要添加到上一层里面。
不会严格要求所有云提供商(例如,GCE 就没必要为了使一个 LoadBalancer
能工作而分配一个 NodePort
,但是 AWS 需要 ),但针对服务的 Kubernetes API 设计是强制要求的。
虚拟IP实施 对很多想使用 Service 的人来说,前面的信息应该足够了。
然而,有很多内部原理性的内容,还是值去理解的。
避免冲突 Kubernetes 最主要的哲学之一,是用户不应该暴露那些能够导致他们操作失败、但又不是他们的过错的场景。
对于 Service 资源的设计,这意味着如果用户的选择有可能与他人冲突,那就不要让用户自行选择端口号。
这是一个隔离性的失败。
为了使用户能够为他们的 Service 选择一个端口号,我们必须确保不能有 2 个 Service 发生冲突。
Kubernetes 通过在为 API 服务器配置的 service-cluster-ip-range
CIDR
范围内为每个服务分配自己的 IP 地址来实现。
为了保证每个 Service 被分配到一个唯一的 IP,需要一个内部的分配器能够原子地更新
etcd 中的一个全局分配映射表,
这个更新操作要先于创建每一个 Service。
为了使 Service 能够获取到 IP,这个映射表对象必须在注册中心存在,
否则创建 Service 将会失败,指示一个 IP 不能被分配。
在控制平面中,一个后台 Controller 的职责是创建映射表
(需要支持从使用了内存锁的 Kubernetes 的旧版本迁移过来)。
同时 Kubernetes 会通过控制器检查不合理的分配(如管理员干预导致的)
以及清理已被分配但不再被任何 Service 使用的 IP 地址。
type: ClusterIP
服务的 IP 地址范围
特性状态: Kubernetes v1.25 [beta]
但是,这种
ClusterIP
分配策略存在一个问题,因为用户还可以
为服务选择自己的地址 。
如果内部分配器为另一个服务选择相同的 IP 地址,这可能会导致冲突。
ServiceIPStaticSubrange
特性门控 在
v1.25 及后续版本中默认启用,其分配策略根据配置的 service-cluster-ip-range
的大小,使用以下公式
min(max(16, cidrSize / 16), 256)
进行划分,该公式可描述为
“在不小于 16 且不大于 256 之间有一个步进量(Graduated Step)”,将
ClusterIP
范围分成两段。动态 IP 分配将优先从上半段地址中选择,
从而降低与下半段地址分配的 IP 冲突的风险。
这允许用户将 service-cluster-ip-range
的下半段地址用于他们的服务,
与所分配的静态 IP 的冲突风险非常低。
Service IP 地址 不像 Pod 的 IP 地址,它实际路由到一个固定的目的地,Service 的 IP 实际上不能通过单个主机来进行应答。
相反,我们使用 iptables
(Linux 中的数据包处理逻辑)来定义一个虚拟 IP 地址(VIP),
它可以根据需要透明地进行重定向。
当客户端连接到 VIP 时,它们的流量会自动地传输到一个合适的 Endpoint。
环境变量和 DNS,实际上会根据 Service 的 VIP 和端口来进行填充。
kube-proxy 支持三种代理模式: 用户空间、iptables 和 IPVS;它们各自的操作略有不同。
Userspace 作为一个例子,考虑前面提到的图片处理应用程序。
当创建后端 Service 时,Kubernetes master 会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy
实例观察到。
当代理看到一个新的 Service,它会打开一个新的端口,
建立一个从该 VIP 重定向到新端口的 iptables,并开始接收请求连接。
当一个客户端连接到一个 VIP,iptables 规则开始起作用,它会重定向该数据包到
"服务代理" 的端口。
"服务代理" 选择一个后端,并将客户端的流量代理到后端上。
这意味着 Service 的所有者能够选择任何他们想使用的端口,而不存在冲突的风险。
客户端可以连接到一个 IP 和端口,而不需要知道实际访问了哪些 Pod。
iptables 再次考虑前面提到的图片处理应用程序。
当创建后端 Service 时,Kubernetes 控制面板会给它指派一个虚拟 IP 地址,比如 10.0.0.1。
假设 Service 的端口是 1234,该 Service 会被集群中所有的 kube-proxy
实例观察到。
当代理看到一个新的 Service, 它会配置一系列的 iptables 规则,从 VIP 重定向到每个 Service 规则。
该特定于服务的规则连接到特定于 Endpoint 的规则,而后者会重定向(目标地址转译)到后端。
当客户端连接到一个 VIP,iptables 规则开始起作用。一个后端会被选择(或者根据会话亲和性,或者随机),
数据包被重定向到这个后端。
不像用户空间代理,数据包从来不拷贝到用户空间,kube-proxy 不是必须为该 VIP 工作而运行,
并且客户端 IP 是不可更改的。
当流量打到 Node 的端口上,或通过负载均衡器,会执行相同的基本流程,
但是在那些案例中客户端 IP 是可以更改的。
IPVS 在大规模集群(例如 10000 个服务)中,iptables 操作会显着降低速度。
IPVS 专为负载均衡而设计,并基于内核内哈希表。
因此,你可以通过基于 IPVS 的 kube-proxy 在大量服务中实现性能一致性。
同时,基于 IPVS 的 kube-proxy 具有更复杂的负载均衡算法(最小连接、局部性、加权、持久性)。
API 对象 Service 是 Kubernetes REST API 中的顶级资源。你可以在以下位置找到有关 API 对象的更多详细信息:
Service 对象 API .
受支持的协议 TCP 你可以将 TCP 用于任何类型的服务,这是默认的网络协议。
UDP 你可以将 UDP 用于大多数服务。 对于 type=LoadBalancer 服务,对 UDP 的支持取决于提供此功能的云提供商。
SCTP 特性状态: Kubernetes v1.20 [stable]
一旦你使用了支持 SCTP 流量的网络插件,你就可以使用 SCTP 于更多的服务。
对于 type = LoadBalancer 的服务,SCTP 的支持取决于提供此设施的云供应商(大多数不支持)。
警告 支持多宿主 SCTP 关联 警告: 支持多宿主SCTP关联要求 CNI 插件能够支持为一个 Pod 分配多个接口和 IP 地址。
用于多宿主 SCTP 关联的 NAT 在相应的内核模块中需要特殊的逻辑。
Windows 说明: 基于 Windows 的节点不支持 SCTP。
用户空间 kube-proxy 警告: 当 kube-proxy 处于用户空间模式时,它不支持 SCTP 关联的管理。
HTTP 如果你的云提供商支持它,则可以在 LoadBalancer 模式下使用服务来设置外部
HTTP/HTTPS 反向代理,并将其转发到该服务的 Endpoints。
说明: 你还可以使用 Ingress 代替
Service 来公开 HTTP/HTTPS 服务。
PROXY 协议 如果你的云提供商支持它,
则可以在 LoadBalancer 模式下使用 Service 在 Kubernetes 本身之外配置负载均衡器,
该负载均衡器将转发前缀为
PROXY 协议
的连接。
负载均衡器将发送一系列初始字节,描述传入的连接,类似于此示例:
PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n
上述是来自客户端的数据。
接下来 3.6.2 - 使用拓扑键实现拓扑感知的流量路由 特性状态: Kubernetes v1.21 [deprecated]
说明: 此功能特性,尤其是 Alpha 阶段的 topologyKeys
API,在 Kubernetes v1.21
版本中已被废弃。Kubernetes v1.21 版本中引入的
拓扑感知的提示 ,
提供类似的功能。
服务拓扑(Service Topology)可以让一个服务基于集群的 Node 拓扑进行流量路由。
例如,一个服务可以指定流量是被优先路由到一个和客户端在同一个 Node 或者在同一可用区域的端点。
拓扑感知的流量路由 默认情况下,发往 ClusterIP
或者 NodePort
服务的流量可能会被路由到
服务的任一后端的地址。Kubernetes 1.7 允许将“外部”流量路由到接收到流量的
节点上的 Pod。对于 ClusterIP
服务,无法完成同节点优先的路由,你也无法
配置集群优选路由到同一可用区中的端点。
通过在 Service 上配置 topologyKeys
,你可以基于来源节点和目标节点的
标签来定义流量路由策略。
通过对源和目的之间的标签匹配,作为集群操作者的你可以根据节点间彼此“较近”和“较远”
来定义节点集合。你可以基于符合自身需求的任何度量值来定义标签。
例如,在公有云上,你可能更偏向于把流量控制在同一区内,因为区间流量是有费用成本的,
而区内流量则没有。
其它常见需求还包括把流量路由到由 DaemonSet
管理的本地 Pod 上,或者
把将流量转发到连接在同一机架交换机的节点上,以获得低延时。
使用服务拓扑 如果集群启用了 ServiceTopology
特性门控 ,
你就可以在 Service 规约中设定 topologyKeys
字段,从而控制其流量路由。
此字段是 Node
标签的优先顺序字段,将用于在访问这个 Service
时对端点进行排序。
流量会被定向到第一个标签值和源 Node
标签值相匹配的 Node
。
如果这个 Service
没有匹配的后端 Node
,那么第二个标签会被使用做匹配,
以此类推,直到没有标签。
如果没有匹配到,流量会被拒绝,就如同这个 Service
根本没有后端。
换言之,系统根据可用后端的第一个拓扑键来选择端点。
如果这个字段被配置了而没有后端可以匹配客户端拓扑,那么这个 Service
对那个客户端是没有后端的,链接应该是失败的。
这个字段配置为 "*"
意味着任意拓扑。
这个通配符值如果使用了,那么只有作为配置值列表中的最后一个才有用。
如果 topologyKeys
没有指定或者为空,就没有启用这个拓扑约束。
一个集群中,其 Node
的标签被打为其主机名,区域名和地区名。
那么就可以设置 Service
的 topologyKeys
的值,像下面的做法一样定向流量了。
只定向到同一个 Node
上的端点,Node
上没有端点存在时就失败:
配置 ["kubernetes.io/hostname"]
。 偏向定向到同一个 Node
上的端点,回退同一区域的端点上,然后是同一地区,
其它情况下就失败:配置 ["kubernetes.io/hostname", "topology.kubernetes.io/zone", "topology.kubernetes.io/region"]
。
这或许很有用,例如,数据局部性很重要的情况下。 偏向于同一区域,但如果此区域中没有可用的终结点,则回退到任何可用的终结点:
配置 ["topology.kubernetes.io/zone", "*"]
。 约束条件 服务拓扑和 externalTrafficPolicy=Local
是不兼容的,所以 Service
不能同时使用这两种特性。
但是在同一个集群的不同 Service
上是可以分别使用这两种特性的,只要不在同一个
Service
上就可以。
有效的拓扑键目前只有:kubernetes.io/hostname
、topology.kubernetes.io/zone
和
topology.kubernetes.io/region
,但是未来会推广到其它的 Node
标签。
拓扑键必须是有效的标签,并且最多指定16个。
通配符:"*"
,如果要用,则必须是拓扑键值的最后一个值。
示例 以下是使用服务拓扑功能的常见示例。
仅节点本地端点 仅路由到节点本地端点的一种服务。如果节点上不存在端点,流量则被丢弃:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
首选节点本地端点 首选节点本地端点,如果节点本地端点不存在,则回退到集群范围端点的一种服务:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
- "*"
仅地域或区域端点 首选地域端点而不是区域端点的一种服务。 如果以上两种范围内均不存在端点,
流量则被丢弃。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
优先选择节点本地端点、地域端点,然后是区域端点 优先选择节点本地端点,地域端点,然后是区域端点,最后才是集群范围端点的
一种服务。
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : my-app
ports :
- protocol : TCP
port : 80
targetPort : 9376
topologyKeys :
- "kubernetes.io/hostname"
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
- "*"
接下来 3.6.3 - Ingress 使用一种能感知协议配置的机制来理解 URI、主机名称、路径和更多 Web 概念,使得 HTTP(或 HTTPS)网络服务可用。 Ingress 概念允许你通过 Kubernetes API 定义的规则将流量映射到不同的后端。
特性状态: Kubernetes v1.19 [stable]
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
术语 为了表达更加清晰,本指南定义了以下术语:
节点(Node): Kubernetes 集群中的一台工作机器,是集群的一部分。 集群(Cluster): 一组运行由 Kubernetes 管理的容器化应用程序的节点。
在此示例和在大多数常见的 Kubernetes 部署环境中,集群中的节点都不在公共网络中。 边缘路由器(Edge Router): 在集群中强制执行防火墙策略的路由器。可以是由云提供商管理的网关,也可以是物理硬件。 集群网络(Cluster Network): 一组逻辑的或物理的连接,根据 Kubernetes
网络模型 在集群内实现通信。 服务(Service):Kubernetes 服务(Service) ,
使用标签 选择器(selectors)辨认一组 Pod。
除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟 IP。 Ingress 是什么? Ingress
公开从集群外部到集群内服务 的
HTTP 和 HTTPS 路由。
流量路由由 Ingress 资源上定义的规则控制。
下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:
图. Ingress
Ingress 可为 Service 提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及基于名称的虚拟托管。
Ingress 控制器
通常负责通过负载均衡器来实现 Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。
Ingress 不会公开任意端口或协议。
将 HTTP 和 HTTPS 以外的服务公开到 Internet 时,通常使用
Service.Type=NodePort
或 Service.Type=LoadBalancer
类型的 Service。
环境准备 你必须拥有一个 Ingress 控制器 才能满足 Ingress 的要求。
仅创建 Ingress 资源本身没有任何效果。
你可能需要部署 Ingress 控制器,例如 ingress-nginx 。
你可以从许多 Ingress 控制器 中进行选择。
理想情况下,所有 Ingress 控制器都应符合参考规范。但实际上,不同的 Ingress 控制器操作略有不同。
说明: 确保你查看了 Ingress 控制器的文档,以了解选择它的注意事项。
Ingress 资源 一个最小的 Ingress 资源示例:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : minimal-ingress
annotations :
nginx.ingress.kubernetes.io/rewrite-target : /
spec :
ingressClassName : nginx-example
rules :
- http :
paths :
- path : /testpath
pathType : Prefix
backend :
service :
name : test
port :
number : 80
Ingress 需要指定 apiVersion
、kind
、 metadata
和 spec
字段。
Ingress 对象的命名必须是合法的 DNS 子域名名称 。
关于如何使用配置文件,请参见部署应用 、
配置容器 、
管理资源 。
Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress
控制器,例如重写目标注解 。
不同的 Ingress 控制器 支持不同的注解。
查看你所选的 Ingress 控制器的文档,以了解其支持哪些注解。
Ingress 规约
提供了配置负载均衡器或者代理服务器所需的所有信息。
最重要的是,其中包含与所有传入请求匹配的规则列表。
Ingress 资源仅支持用于转发 HTTP(S) 流量的规则。
如果 ingressClassName
被省略,那么你应该定义一个默认 Ingress 类 。
有一些 Ingress 控制器不需要定义默认的 IngressClass
。比如:Ingress-NGINX
控制器可以通过参数
--watch-ingress-without-class
来配置。
不过仍然推荐
按下文 所示来设置默认的 IngressClass
。
Ingress 规则 每个 HTTP 规则都包含以下信息:
可选的 host
。在此示例中,未指定 host
,因此该规则适用于通过指定 IP 地址的所有入站 HTTP 通信。
如果提供了 host
(例如 foo.bar.com),则 rules
适用于该 host
。 路径列表 paths(例如,/testpath
),每个路径都有一个由 serviceName
和 servicePort
定义的关联后端。
在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入请求的内容。 backend
(后端)是 Service 文档 中所述的服务和端口名称的组合。
与规则的 host
和 path
匹配的对 Ingress 的 HTTP(和 HTTPS )请求将发送到列出的 backend
。通常在 Ingress 控制器中会配置 defaultBackend
(默认后端),以服务于无法与规约中 path
匹配的所有请求。
默认后端 没有设置规则的 Ingress 将所有流量发送到同一个默认后端,而
.spec.defaultBackend
则是在这种情况下处理请求的那个默认后端。
defaultBackend
通常是
Ingress 控制器 的配置选项,而非在
Ingress 资源中指定。
如果未设置任何的 .spec.rules
,那么必须指定 .spec.defaultBackend
。
如果未设置 defaultBackend
,那么如何处理所有与规则不匹配的流量将交由
Ingress 控制器决定(请参考你的 Ingress 控制器的文档以了解它是如何处理那些流量的)。
如果没有 hosts
或 paths
与 Ingress 对象中的 HTTP 请求匹配,则流量将被路由到默认后端。
资源后端 Resource
后端是一个引用,指向同一命名空间中的另一个 Kubernetes 资源,将其作为 Ingress 对象。
Resource
后端与 Service 后端是互斥的,在二者均被设置时会无法通过合法性检查。
Resource
后端的一种常见用法是将所有入站数据导向带有静态资产的对象存储后端。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : ingress-resource-backend
spec :
defaultBackend :
resource :
apiGroup : k8s.example.com
kind : StorageBucket
name : static-assets
rules :
- http :
paths :
- path : /icons
pathType : ImplementationSpecific
backend :
resource :
apiGroup : k8s.example.com
kind : StorageBucket
name : icon-assets
创建了如上的 Ingress 之后,你可以使用下面的命令查看它:
kubectl describe ingress ingress-resource-backend
Name: ingress-resource-backend
Namespace: default
Address:
Default backend: APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
Host Path Backends
---- ---- --------
*
/icons APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations: <none>
Events: <none>
路径类型 Ingress 中的每个路径都需要有对应的路径类型(Path Type)。未明确设置 pathType
的路径无法通过合法性检查。当前支持的路径类型有三种:
ImplementationSpecific
:对于这种路径类型,匹配方法取决于 IngressClass。
具体实现可以将其作为单独的 pathType
处理或者与 Prefix
或 Exact
类型作相同处理。
Exact
:精确匹配 URL 路径,且区分大小写。
Prefix
:基于以 /
分隔的 URL 路径前缀匹配。匹配区分大小写,并且对路径中的元素逐个完成。
路径元素指的是由 /
分隔符分隔的路径中的标签列表。
如果每个 p 都是请求路径 p 的元素前缀,则请求与路径 p 匹配。
说明: 如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会匹配
(例如:/foo/bar
匹配 /foo/bar/baz
, 但不匹配 /foo/barbaz
)。
示例 类型 路径 请求路径 匹配与否? Prefix /
(所有路径) 是 Exact /foo
/foo
是 Exact /foo
/bar
否 Exact /foo
/foo/
否 Exact /foo/
/foo
否 Prefix /foo
/foo
, /foo/
是 Prefix /foo/
/foo
, /foo/
是 Prefix /aaa/bb
/aaa/bbb
否 Prefix /aaa/bbb
/aaa/bbb
是 Prefix /aaa/bbb/
/aaa/bbb
是,忽略尾部斜线 Prefix /aaa/bbb
/aaa/bbb/
是,匹配尾部斜线 Prefix /aaa/bbb
/aaa/bbb/ccc
是,匹配子路径 Prefix /aaa/bbb
/aaa/bbbxyz
否,字符串前缀不匹配 Prefix /
, /aaa
/aaa/ccc
是,匹配 /aaa
前缀 Prefix /
, /aaa
, /aaa/bbb
/aaa/bbb
是,匹配 /aaa/bbb
前缀 Prefix /
, /aaa
, /aaa/bbb
/ccc
是,匹配 /
前缀 Prefix /aaa
/ccc
否,使用默认后端 混合 /foo
(Prefix), /foo
(Exact)/foo
是,优选 Exact 类型
多重匹配 在某些情况下,Ingress 中的多条路径会匹配同一个请求。
这种情况下最长的匹配路径优先。
如果仍然有两条同等的匹配路径,则精确路径类型优先于前缀路径类型。
主机名通配符 主机名可以是精确匹配(例如“foo.bar.com
”)或者使用通配符来匹配
(例如“*.foo.com
”)。
精确匹配要求 HTTP host
头部字段与 host
字段值完全匹配。
通配符匹配则要求 HTTP host
头部字段与通配符规则中的后缀部分相同。
主机 host 头部 匹配与否? *.foo.com
bar.foo.com
基于相同的后缀匹配 *.foo.com
baz.bar.foo.com
不匹配,通配符仅覆盖了一个 DNS 标签 *.foo.com
foo.com
不匹配,通配符仅覆盖了一个 DNS 标签
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : ingress-wildcard-host
spec :
rules :
- host : "foo.bar.com"
http :
paths :
- pathType : Prefix
path : "/bar"
backend :
service :
name : service1
port :
number : 80
- host : "*.foo.com"
http :
paths :
- pathType : Prefix
path : "/foo"
backend :
service :
name : service2
port :
number : 80
Ingress 类 Ingress 可以由不同的控制器实现,通常使用不同的配置。
每个 Ingress 应当指定一个类,也就是一个对 IngressClass 资源的引用。
IngressClass 资源包含额外的配置,其中包括应当实现该类的控制器名称。
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb
spec :
controller : example.com/ingress-controller
parameters :
apiGroup : k8s.example.com
kind : IngressParameters
name : external-lb
IngressClass 中的 .spec.parameters
字段可用于引用其他资源以提供额外的相关配置。
参数(parameters
)的具体类型取决于你在 .spec.controller
字段中指定的 Ingress 控制器。
IngressClass 的作用域 取决于你的 Ingress 控制器,你可能可以使用集群范围设置的参数或某个名字空间范围的参数。
IngressClass 的参数默认是集群范围的。
如果你设置了 .spec.parameters
字段且未设置 .spec.parameters.scope
字段,或是将 .spec.parameters.scope
字段设为了 Cluster
,那么该
IngressClass 所指代的即是一个集群作用域的资源。
参数的 kind
(和 apiGroup
一起)指向一个集群作用域的
API(可能是一个定制资源(Custom Resource)),而它的
name
则为此 API 确定了一个具体的集群作用域的资源。
示例:
---
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb-1
spec :
controller : example.com/ingress-controller
parameters :
# 此 IngressClass 的配置定义在一个名为 “external-config-1” 的
# ClusterIngressParameter(API 组为 k8s.example.net)资源中。
# 这项定义告诉 Kubernetes 去寻找一个集群作用域的参数资源。
scope : Cluster
apiGroup : k8s.example.net
kind : ClusterIngressParameter
name : external-config-1
特性状态: Kubernetes v1.23 [stable]
如果你设置了 .spec.parameters
字段且将 .spec.parameters.scope
字段设为了 Namespace
,那么该 IngressClass 将会引用一个命名空间作用域的资源。
.spec.parameters.namespace
必须和此资源所处的命名空间相同。
参数的 kind
(和 apiGroup
一起)指向一个命名空间作用域的 API(例如:ConfigMap),而它的
name
则确定了一个位于你指定的命名空间中的具体的资源。
命名空间作用域的参数帮助集群操作者将控制细分到用于工作负载的各种配置中(比如:负载均衡设置、API
网关定义)。如果你使用集群作用域的参数,那么你必须从以下两项中选择一项执行:
每次修改配置,集群操作团队需要批准其他团队的修改。 集群操作团队定义具体的准入控制,比如 RBAC
角色与角色绑定,以使得应用程序团队可以修改集群作用域的配置参数资源。 IngressClass API 本身是集群作用域的。
这里是一个引用命名空间作用域的配置参数的 IngressClass 的示例:
---
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
name : external-lb-2
spec :
controller : example.com/ingress-controller
parameters :
# 此 IngressClass 的配置定义在一个名为 “external-config” 的
# IngressParameter(API 组为 k8s.example.com)资源中,
# 该资源位于 “external-configuration” 命名空间中。
scope : Namespace
apiGroup : k8s.example.com
kind : IngressParameter
namespace : external-configuration
name : external-config
废弃的注解 在 Kubernetes 1.18 版本引入 IngressClass 资源和 ingressClassName
字段之前,Ingress
类是通过 Ingress 中的一个 kubernetes.io/ingress.class
注解来指定的。
这个注解从未被正式定义过,但是得到了 Ingress 控制器的广泛支持。
Ingress 中新的 ingressClassName
字段是该注解的替代品,但并非完全等价。
该注解通常用于引用实现该 Ingress 的控制器的名称,而这个新的字段则是对一个包含额外
Ingress 配置的 IngressClass 资源的引用,包括 Ingress 控制器的名称。
默认 Ingress 类 你可以将一个特定的 IngressClass 标记为集群默认 Ingress 类。
将一个 IngressClass 资源的 ingressclass.kubernetes.io/is-default-class
注解设置为
true
将确保新的未指定 ingressClassName
字段的 Ingress 能够分配为这个默认的
IngressClass.
注意: 如果集群中有多个 IngressClass 被标记为默认,准入控制器将阻止创建新的未指定
ingressClassName
的 Ingress 对象。
解决这个问题只需确保集群中最多只能有一个 IngressClass 被标记为默认。
有一些 Ingress 控制器不需要定义默认的 IngressClass
。比如:Ingress-NGINX
控制器可以通过参数
--watch-ingress-without-class
来配置。
不过仍然推荐
设置默认的 IngressClass
。
apiVersion : networking.k8s.io/v1
kind : IngressClass
metadata :
labels :
app.kubernetes.io/component : controller
name : nginx-example
annotations :
ingressclass.kubernetes.io/is-default-class : "true"
spec :
controller : k8s.io/ingress-nginx
Ingress 类型 由单个 Service 来完成的 Ingress 现有的 Kubernetes 概念允许你暴露单个 Service (参见替代方案 )。
你也可以通过指定无规则的 默认后端 来对 Ingress 进行此操作。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : test-ingress
spec :
defaultBackend :
service :
name : test
port :
number : 80
如果使用 kubectl apply -f
创建此 Ingress,则应该能够查看刚刚添加的 Ingress 的状态:
kubectl get ingress test-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
test-ingress external-lb * 203.0.113.123 80 59s
其中 203.0.113.123
是由 Ingress 控制器分配以满足该 Ingress 的 IP。
说明: 入口控制器和负载平衡器可能需要一两分钟才能分配 IP 地址。
在此之前,你通常会看到地址字段的值被设定为 <pending>
。
简单扇出 一个扇出(fanout)配置根据请求的 HTTP URI 将来自同一 IP 地址的流量路由到多个 Service。
Ingress 允许你将负载均衡器的数量降至最低。例如,这样的设置:
图. Ingress 扇出
将需要一个如下所示的 Ingress:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : simple-fanout-example
spec :
rules :
- host : foo.bar.com
http :
paths :
- path : /foo
pathType : Prefix
backend :
service :
name : service1
port :
number : 4200
- path : /bar
pathType : Prefix
backend :
service :
name : service2
port :
number : 8080
当你使用 kubectl apply -f
创建 Ingress 时:
kubectl describe ingress simple-fanout-example
Name: simple-fanout-example
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:4200 (10.8.0.90:4200)
/bar service2:8080 (10.8.0.91:8080)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 22s loadbalancer-controller default/test
Ingress 控制器将提供实现特定的负载均衡器来满足 Ingress,
只要 Service (service1
,service2
) 存在。
当它这样做时,你会在 Address 字段看到负载均衡器的地址。
基于名称的虚拟托管 基于名称的虚拟主机支持将针对多个主机名的 HTTP 流量路由到同一 IP 地址上。
图. 基于名称实现虚拟托管的 Ingress
以下 Ingress 让后台负载均衡器基于host 头部字段
来路由请求。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : name-virtual-host-ingress
spec :
rules :
- host : foo.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service1
port :
number : 80
- host : bar.foo.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service2
port :
number : 80
如果你创建的 Ingress 资源没有在 rules
中定义的任何 hosts
,则可以匹配指向
Ingress 控制器 IP 地址的任何网络流量,而无需基于名称的虚拟主机。
例如,以下 Ingress 会将请求 first.bar.com
的流量路由到 service1
,将请求
second.bar.com
的流量路由到 service2
,而所有其他流量都会被路由到 service3
。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : name-virtual-host-ingress-no-third-host
spec :
rules :
- host : first.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service1
port :
number : 80
- host : second.bar.com
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service2
port :
number : 80
- http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : service3
port :
number : 80
TLS 你可以通过设定包含 TLS 私钥和证书的Secret
来保护 Ingress。
Ingress 只支持单个 TLS 端口 443,并假定 TLS 连接终止于
Ingress 节点(与 Service 及其 Pod 之间的流量都以明文传输)。
如果 Ingress 中的 TLS 配置部分指定了不同的主机,那么它们将根据通过
SNI TLS 扩展指定的主机名(如果 Ingress 控制器支持 SNI)在同一端口上进行复用。
TLS Secret 的数据中必须包含用于 TLS 的以键名 tls.crt
保存的证书和以键名 tls.key
保存的私钥。
例如:
apiVersion : v1
kind : Secret
metadata :
name : testsecret-tls
namespace : default
data :
tls.crt : base64 编码的证书
tls.key : base64 编码的私钥
type : kubernetes.io/tls
在 Ingress 中引用此 Secret 将会告诉 Ingress 控制器使用 TLS 加密从客户端到负载均衡器的通道。
你需要确保创建的 TLS Secret 创建自包含 https-example.foo.com
的公用名称(CN)的证书。
这里的公共名称也被称为全限定域名(FQDN)。
说明: 注意,默认规则上无法使用 TLS,因为需要为所有可能的子域名发放证书。
因此,tls
字段中的 hosts
的取值需要与 rules
字段中的 host
完全匹配。
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : tls-example-ingress
spec :
tls :
- hosts :
- https-example.foo.com
secretName : testsecret-tls
rules :
- host : https-example.foo.com
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : service1
port :
number : 80
说明: 各种 Ingress 控制器所支持的 TLS 功能之间存在差异。请参阅有关
nginx 、
GCE
或者任何其他平台特定的 Ingress 控制器的文档,以了解 TLS 如何在你的环境中工作。
负载均衡 Ingress 控制器启动引导时使用一些适用于所有 Ingress
的负载均衡策略设置,例如负载均衡算法、后端权重方案等。
更高级的负载均衡概念(例如持久会话、动态权重)尚未通过 Ingress 公开。
你可以通过用于服务的负载均衡器来获取这些功能。
值得注意的是,尽管健康检查不是通过 Ingress 直接暴露的,在 Kubernetes
中存在并行的概念,比如
就绪检查 ,
允许你实现相同的目的。
请检查特定控制器的说明文档(nginx 、
GCE )以了解它们是怎样处理健康检查的。
更新 Ingress 要更新现有的 Ingress 以添加新的 Host,可以通过编辑资源来对其进行更新:
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 35s loadbalancer-controller default/test
kubectl edit ingress test
这一命令将打开编辑器,允许你以 YAML 格式编辑现有配置。
修改它来增加新的主机:
spec :
rules :
- host : foo.bar.com
http :
paths :
- backend :
serviceName : service1
servicePort : 80
path : /foo
pathType : Prefix
- host : bar.baz.com
http :
paths :
- backend :
serviceName : service2
servicePort : 80
path : /foo
pathType : Prefix
..
保存更改后,kubectl 将更新 API 服务器中的资源,该资源将告诉 Ingress 控制器重新配置负载均衡器。
验证:
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
bar.baz.com
/foo service2:80 (10.8.0.91:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 45s loadbalancer-controller default/test
你也可以通过 kubectl replace -f
命令调用修改后的 Ingress yaml 文件来获得同样的结果。
跨可用区失败 不同的云厂商使用不同的技术来实现跨故障域的流量分布。详情请查阅相关 Ingress 控制器的文档。
请查看相关 Ingress 控制器 的文档以了解详细信息。
替代方案 不直接使用 Ingress 资源,也有多种方法暴露 Service:
接下来 3.6.4 - Ingress 控制器 为了让
Ingress 在你的集群中工作, 必须有一个 Ingress 控制器正在运行。你需要选择至少一个 Ingress 控制器并确保其已被部署到你的集群中。 本页列出了你可以部署的常见 Ingress 控制器。
为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。
与作为 kube-controller-manager
可执行文件的一部分运行的其他类型的控制器不同,
Ingress 控制器不是随集群自动启动的。
基于此页面,你可选择最适合你的集群的 ingress 控制器实现。
Kubernetes 作为一个项目,目前支持和维护
AWS 、
GCE
和 Nginx Ingress 控制器。
其他控制器 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
使用多个 Ingress 控制器 你可以使用
Ingress 类 在集群中部署任意数量的
Ingress 控制器。
请注意你的 Ingress 类资源的 .metadata.name
字段。
当你创建 Ingress 时,你需要用此字段的值来设置 Ingress 对象的 ingressClassName
字段(请参考
IngressSpec v1 reference )。
ingressClassName
是之前的注解 做法的替代。
如果你不为 Ingress 指定 IngressClass,并且你的集群中只有一个 IngressClass 被标记为默认,那么
Kubernetes 会将此集群的默认 IngressClass
应用 到 Ingress 上。
IngressClass。
你可以通过将
ingressclass.kubernetes.io/is-default-class
注解
的值设置为 "true"
来将一个 IngressClass 标记为集群默认。
理想情况下,所有 Ingress 控制器都应满足此规范,但各种 Ingress 控制器的操作略有不同。
说明: 确保你查看了 ingress 控制器的文档,以了解选择它的注意事项。
接下来 3.6.5 - 使用 Service 连接到应用 Kubernetes 连接容器的模型 既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。
Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。
Kubernetes 给每一个 Pod 分配一个集群私有 IP 地址,所以没必要在
Pod 与 Pod 之间创建连接或将容器的端口映射到主机端口。
这意味着同一个 Pod 内的所有容器能通过 localhost 上的端口互相连通,集群中的所有 Pod
也不需要通过 NAT 转换就能够互相看到。
本文档的剩余部分详述如何在上述网络模型之上运行可靠的服务。
本指南使用一个简单的 Nginx 服务器来演示概念验证原型。
在集群中暴露 Pod 我们在之前的示例中已经做过,然而让我们以网络连接的视角再重做一遍。
创建一个 Nginx Pod,注意其中包含一个容器端口的规约:
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
selector :
matchLabels :
run : my-nginx
replicas : 2
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- name : my-nginx
image : nginx
ports :
- containerPort : 80
这使得可以从集群中任何一个节点来访问它。检查节点,该 Pod 正在运行:
kubectl apply -f ./run-my-nginx.yaml
kubectl get pods -l run = my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m
my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd
检查 Pod 的 IP 地址:
kubectl get pods -l run = my-nginx -o custom-columns= POD_IP:.status.podIPs
POD_IP
[ map[ ip:10.244.3.4]]
[ map[ ip:10.244.2.5]]
你应该能够通过 ssh 登录到集群中的任何一个节点上,并使用诸如 curl
之类的工具向这两个 IP 地址发出查询请求。
需要注意的是,容器 不会 使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。
这意味着可以在同一个节点上运行多个 Nginx Pod,使用相同的 containerPort
,并且可以从集群中任何其他的
Pod 或节点上使用 IP 的方式访问到它们。
如果你想的话,你依然可以将宿主节点的某个端口的流量转发到 Pod 中,但是出于网络模型的原因,你不必这么做。
如果对此好奇,请参考 Kubernetes 网络模型 。
创建 Service 我们有一组在一个扁平的、集群范围的地址空间中运行 Nginx 服务的 Pod。
理论上,你可以直接连接到这些 Pod,但如果某个节点死掉了会发生什么呢?
Pod 会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。
Kubernetes Service 是集群中提供相同功能的一组 Pod 的抽象表达。
当每个 Service 创建时,会被分配一个唯一的 IP 地址(也称为 clusterIP)。
这个 IP 地址与 Service 的生命周期绑定在一起,只要 Service 存在,它就不会改变。
可以配置 Pod 使它与 Service 进行通信,Pod 知道与 Service 通信将被自动地负载均衡到该 Service 中的某些 Pod 上。
可以使用 kubectl expose
命令为 2个 Nginx 副本创建一个 Service:
kubectl expose deployment/my-nginx
service/my-nginx exposed
这等价于使用 kubectl create -f
命令及如下的 yaml 文件创建:
apiVersion : v1
kind : Service
metadata :
name : my-nginx
labels :
run : my-nginx
spec :
ports :
- port : 80
protocol : TCP
selector :
run : my-nginx
上述规约将创建一个 Service,该 Service 会将所有具有标签 run: my-nginx
的 Pod 的 TCP
80 端口暴露到一个抽象的 Service 端口上(targetPort
:容器接收流量的端口;port
:可任意取值的抽象的 Service
端口,其他 Pod 通过该端口访问 Service)。
查看 Service
API 对象以了解 Service 所能接受的字段列表。
查看你的 Service 资源:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s
正如前面所提到的,一个 Service 由一组 Pod 提供支撑。这些 Pod 通过
EndpointSlices 暴露出来。
Service Selector 将持续评估,结果被 POST
到使用标签 与该 Service 连接的一个 EndpointSlice。
当 Pod 终止后,它会自动从包含该 Pod 的 EndpointSlices 中移除。
新的能够匹配上 Service Selector 的 Pod 将自动地被为该 Service 添加到 EndpointSlice 中。
检查 Endpoint,注意到 IP 地址与在第一步创建的 Pod 是相同的。
kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP: 10.0.162.149
Port: <unset> 80/TCP
Endpoints: 10.244.2.5:80,10.244.3.4:80
Session Affinity: None
Events: <none>
kubectl get endpointslices -l kubernetes.io/service-name= my-nginx
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
my-nginx-7vzhx IPv4 80 10.244.2.5,10.244.3.4 21s
现在,你应该能够从集群中任意节点上使用 curl 命令向 <CLUSTER-IP>:<PORT>
发送请求以访问 Nginx Service。
注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇,
可以进一步阅读服务代理 的内容。
访问 Service Kubernetes支持两种查找服务的主要模式: 环境变量和 DNS。前者开箱即用,而后者则需要
CoreDNS 集群插件 。
说明: 如果不需要服务环境变量(因为可能与预期的程序冲突,可能要处理的变量太多,或者仅使用DNS等),则可以通过在
pod spec
上将 enableServiceLinks
标志设置为 false
来禁用此模式。
环境变量 当 Pod 在节点上运行时,kubelet 会针对每个活跃的 Service 为 Pod 添加一组环境变量。
这就引入了一个顺序的问题。为解释这个问题,让我们先检查正在运行的 Nginx Pod
的环境变量(你的环境中的 Pod 名称将会与下面示例命令中的不同):
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
能看到环境变量中并没有你创建的 Service 相关的值。这是因为副本的创建先于 Service。
这样做的另一个缺点是,调度器可能会将所有 Pod 部署到同一台机器上,如果该机器宕机则整个 Service 都会离线。
要改正的话,我们可以先终止这 2 个 Pod,然后等待 Deployment 去重新创建它们。
这次 Service 会 先于 副本存在。这将实现调度器级别的 Pod 按 Service
分布(假定所有的节点都具有同样的容量),并提供正确的环境变量:
kubectl scale deployment my-nginx --replicas= 0; kubectl scale deployment my-nginx --replicas= 2;
kubectl get pods -l run = my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m
你可能注意到,Pod 具有不同的名称,这是因为它们是被重新创建的。
kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443
DNS Kubernetes 提供了一个自动为其它 Service 分配 DNS 名字的 DNS 插件 Service。
你可以通过如下命令检查它是否在工作:
kubectl get services kube-dns --namespace= kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m
本段剩余的内容假设你已经有一个拥有持久 IP 地址的 Service(my-nginx),以及一个为其
IP 分配名称的 DNS 服务器。 这里我们使用 CoreDNS 集群插件(应用名为 kube-dns
),
所以在集群中的任何 Pod 中,你都可以使用标准方法(例如:gethostbyname()
)与该 Service 通信。
如果 CoreDNS 没有在运行,你可以参照
CoreDNS README
或者安装 CoreDNS 来启用它。
让我们运行另一个 curl 应用来进行测试:
kubectl run curl --image= radial/busyboxplus:curl -i --tty
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt
然后,按回车并执行命令 nslookup my-nginx
:
[ root@curl-131556218-9fnch:/ ] $ nslookup my-nginx
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: my-nginx
Address 1: 10.0.162.149
保护 Service 到现在为止,我们只在集群内部访问了 Nginx 服务器。在将 Service 暴露到因特网之前,我们希望确保通信信道是安全的。
为实现这一目的,需要:
用于 HTTPS 的自签名证书(除非已经有了一个身份证书) 使用证书配置的 Nginx 服务器 使 Pod 可以访问证书的 Secret 你可以从
Nginx https 示例 获取所有上述内容。
你需要安装 go 和 make 工具。如果你不想安装这些软件,可以按照后文所述的手动执行步骤执行操作。简要过程如下:
make keys KEY = /tmp/nginx.key CERT = /tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt
secret/nginxsecret created
NAME TYPE DATA AGE
nginxsecret kubernetes.io/tls 2 1m
以下是 configmap:
kubectl create configmap nginxconfigmap --from-file= default.conf
configmap/nginxconfigmap created
NAME DATA AGE
nginxconfigmap 1 114s
以下是你在运行 make 时遇到问题时要遵循的手动步骤(例如,在 Windows 上):
# 创建公钥和相对应的私钥
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
# 对密钥实施 base64 编码
cat /d/tmp/nginx.crt | base64
cat /d/tmp/nginx.key | base64
使用前面命令的输出来创建 yaml 文件,如下所示。 base64 编码的值应全部放在一行上。
apiVersion : "v1"
kind : "Secret"
metadata :
name : "nginxsecret"
namespace : "default"
type : kubernetes.io/tls
data :
tls.crt : "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
tls.key : "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
现在使用文件创建 Secret:
kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME TYPE DATA AGE
nginxsecret kubernetes.io/tls 2 1m
现在修改 nginx 副本以启动一个使用 Secret 中的证书的 HTTPS 服务器以及相应的用于暴露其端口(80 和 443)的 Service:
apiVersion : v1
kind : Service
metadata :
name : my-nginx
labels :
run : my-nginx
spec :
type : NodePort
ports :
- port : 8080
targetPort : 80
protocol : TCP
name : http
- port : 443
protocol : TCP
name : https
selector :
run : my-nginx
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
selector :
matchLabels :
run : my-nginx
replicas : 1
template :
metadata :
labels :
run : my-nginx
spec :
volumes :
- name : secret-volume
secret :
secretName : nginxsecret
- name : configmap-volume
configMap :
name : nginxconfigmap
containers :
- name : nginxhttps
image : bprashanth/nginxhttps:1.0
ports :
- containerPort : 443
- containerPort : 80
volumeMounts :
- mountPath : /etc/nginx/ssl
name : secret-volume
- mountPath : /etc/nginx/conf.d
name : configmap-volume
关于 nginx-secure-app 清单,值得注意的几点如下:
它将 Deployment 和 Service 的规约放在了同一个文件中。 Nginx 服务器 通过
80 端口处理 HTTP 流量,通过 443 端口处理 HTTPS 流量,而 Nginx Service 则暴露了这两个端口。每个容器能通过挂载在 /etc/nginx/ssl
的卷访问秘钥。卷和密钥需要在 Nginx 服务器启动 之前 配置好。 kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
这时,你可以从任何节点访问到 Nginx 服务器。
kubectl get pods -l run = my-nginx -o custom-columns= POD_IP:.status.podIPs
POD_IP
[ map[ ip:10.244.3.5]]
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>
注意最后一步我们是如何提供 -k
参数执行 curl 命令的,这是因为在证书生成时,
我们不知道任何关于运行 nginx 的 Pod 的信息,所以不得不在执行 curl 命令时忽略 CName 不匹配的情况。
通过创建 Service,我们连接了在证书中的 CName 与在 Service 查询时被 Pod 使用的实际 DNS 名字。
让我们从一个 Pod 来测试(为了方便,这里使用同一个 Secret,Pod 仅需要使用 nginx.crt 去访问 Service):
apiVersion : apps/v1
kind : Deployment
metadata :
name : curl-deployment
spec :
selector :
matchLabels :
app : curlpod
replicas : 1
template :
metadata :
labels :
app : curlpod
spec :
volumes :
- name : secret-volume
secret :
secretName : nginxsecret
containers :
- name : curlpod
command :
- sh
- -c
- while true; do sleep 1; done
image : radial/busyboxplus:curl
volumeMounts :
- mountPath : /etc/nginx/ssl
name : secret-volume
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app = curlpod
NAME READY STATUS RESTARTS AGE
curl-deployment-1515033274-1410r 1/1 Running 0 1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...
暴露 Service 对应用的某些部分,你可能希望将 Service 暴露在一个外部 IP 地址上。
Kubernetes 支持两种实现方式:NodePort 和 LoadBalancer。
在上一段创建的 Service 使用了 NodePort
,因此,如果你的节点有一个公网
IP,那么 Nginx HTTPS 副本已经能够处理因特网上的流量。
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
clusterIP: 10.0.162.149
ports:
- name: http
nodePort: 31704
port: 8080
protocol: TCP
targetPort: 80
- name: https
nodePort: 32453
port: 443
protocol: TCP
targetPort: 443
selector:
run: my-nginx
kubectl get nodes -o yaml | grep ExternalIP -C 1
- address: 104.197.41.11
type: ExternalIP
allocatable:
--
- address: 23.251.152.56
type: ExternalIP
allocatable:
...
$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>
让我们重新创建一个 Service 以使用云负载均衡器。
将 my-nginx
Service 的 Type
由 NodePort
改成 LoadBalancer
:
kubectl edit svc my-nginx
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>
在 EXTERNAL-IP
列中的 IP 地址能在公网上被访问到。CLUSTER-IP
只能从集群/私有云网络中访问。
注意,在 AWS 上,类型 LoadBalancer
的服务会创建一个 ELB,且 ELB 使用主机名(比较长),而不是 IP。
ELB 的主机名太长以至于不能适配标准 kubectl get svc
的输出,所以需要通过执行
kubectl describe service my-nginx
命令来查看它。
可以看到类似如下内容:
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...
接下来 3.6.6 - EndpointSlice EndpointSlice API 是 Kubernetes 用于扩缩 Service 以处理大量后端的机制,还允许集群高效更新其健康后端的列表。
特性状态: Kubernetes v1.21 [stable]
端点切片(EndpointSlices) 提供了一种简单的方法来跟踪 Kubernetes 集群中的网络端点(network endpoints)。
它们为 Endpoints 提供了一种可扩缩和可拓展的替代方案。
EndpointSlice API 在 Kubernetes 中,EndpointSlice
包含对一组网络端点的引用。
控制面会自动为设置了选择算符 的
Kubernetes Service 创建 EndpointSlice。
这些 EndpointSlice 将包含对与 Service 选择算符匹配的所有 Pod 的引用。
EndpointSlice 通过唯一的协议、端口号和 Service 名称将网络端点组织在一起。
EndpointSlice 的名称必须是合法的
DNS 子域名 。
例如,下面是 Kubernetes Service example
所拥有的 EndpointSlice 对象示例。
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : example-abc
labels :
kubernetes.io/service-name : example
addressType : IPv4
ports :
- name : http
protocol : TCP
port : 80
endpoints :
- addresses :
- "10.1.2.3"
conditions :
ready : true
hostname : pod-1
nodeName : node-1
zone : us-west2-a
默认情况下,控制面创建和管理的 EndpointSlice 将包含不超过 100 个端点。
你可以使用 kube-controller-manager
的 --max-endpoints-per-slice
标志设置此值,最大值为 1000。
当涉及如何路由内部流量时,EndpointSlice 可以充当
kube-proxy
的决策依据。
地址类型 EndpointSlice 支持三种地址类型:
每个 EndpointSlice
对象代表一个特定的 IP 地址类型。如果你有一个支持 IPv4 和 IPv6 的 Service,
那么将至少有两个 EndpointSlice
对象(一个用于 IPv4,一个用于 IPv6)。
状况 EndpointSlice API 存储了可能对使用者有用的、有关端点的状况。
这三个状况分别是 ready
、serving
和 terminating
。
Ready(就绪) ready
状况是映射 Pod 的 Ready
状况的。
对于处于运行中的 Pod,它的 Ready
状况被设置为 True
,应该将此 EndpointSlice 状况也设置为 true
。
出于兼容性原因,当 Pod 处于终止过程中,ready
永远不会为 true
。
消费者应参考 serving
状况来检查处于终止中的 Pod 的就绪情况。
该规则的唯一例外是将 spec.publishNotReadyAddresses
设置为 true
的 Service。
这些 Service 的端点将始终将 ready
状况设置为 true
。
Serving(服务中) 特性状态: Kubernetes v1.22 [beta]
serving
状况与 ready
状况相同,不同之处在于它不考虑终止状态。
如果 EndpointSlice API 的使用者关心 Pod 终止时的就绪情况,就应检查此状况。
说明: 尽管 serving
与 ready
几乎相同,但是它是为防止破坏 ready
的现有含义而增加的。
如果对于处于终止中的端点,ready
可能是 true
,那么对于现有的客户端来说可能是有些意外的,
因为从始至终,Endpoints 或 EndpointSlice API 从未包含处于终止中的端点。
出于这个原因,ready
对于处于终止中的端点 总是 false
,
并且在 v1.20 中添加了新的状况 serving
,以便客户端可以独立于 ready
的现有语义来跟踪处于终止中的 Pod 的就绪情况。
Terminating(终止中) 特性状态: Kubernetes v1.22 [beta]
Terminating
是表示端点是否处于终止中的状况。
对于 Pod 来说,这是设置了删除时间戳的 Pod。
拓扑信息 EndpointSlice 中的每个端点都可以包含一定的拓扑信息。
拓扑信息包括端点的位置,对应节点、可用区的信息。
这些信息体现为 EndpointSlices 的如下端点字段:
nodeName
- 端点所在的 Node 名称;zone
- 端点所处的可用区。说明: 在 v1 API 中,逐个端点设置的 topology
实际上被去除,
以鼓励使用专用的字段 nodeName
和 zone
。
对 EndpointSlice
对象的 endpoint
字段设置任意的拓扑结构信息这一操作已被废弃,
不再被 v1 API 所支持。取而代之的是 v1 API 所支持的 nodeName
和 zone
这些独立的字段。这些字段可以在不同的 API 版本之间自动完成转译。
例如,v1beta1 API 中 topology
字段的 topology.kubernetes.io/zone
取值可以在 v1 API 中通过 zone
字段访问。
管理 通常,控制面(尤其是端点切片的控制器 )
会创建和管理 EndpointSlice 对象。EndpointSlice 对象还有一些其他使用场景,
例如作为服务网格(Service Mesh)的实现。
这些场景都会导致有其他实体或者控制器负责管理额外的 EndpointSlice 集合。
为了确保多个实体可以管理 EndpointSlice 而且不会相互产生干扰,
Kubernetes 定义了标签
endpointslice.kubernetes.io/managed-by
,用来标明哪个实体在管理某个 EndpointSlice。
端点切片控制器会在自己所管理的所有 EndpointSlice 上将该标签值设置为
endpointslice-controller.k8s.io
。
管理 EndpointSlice 的其他实体也应该为此标签设置一个唯一值。
属主关系 在大多数场合下,EndpointSlice 都由某个 Service 所有,
(因为)该端点切片正是为该服务跟踪记录其端点。这一属主关系是通过为每个 EndpointSlice
设置一个属主(owner)引用,同时设置 kubernetes.io/service-name
标签来标明的,
目的是方便查找隶属于某 Service 的所有 EndpointSlice。
EndpointSlice 镜像 在某些场合,应用会创建定制的 Endpoints 资源。为了保证这些应用不需要并发的更改
Endpoints 和 EndpointSlice 资源,集群的控制面将大多数 Endpoints
映射到对应的 EndpointSlice 之上。
控制面对 Endpoints 资源进行映射的例外情况有:
Endpoints 资源上标签 endpointslice.kubernetes.io/skip-mirror
值为 true
。 Endpoints 资源包含标签 control-plane.alpha.kubernetes.io/leader
。 对应的 Service 资源不存在。 对应的 Service 的选择算符不为空。 每个 Endpoints 资源可能会被转译到多个 EndpointSlices 中去。
当 Endpoints 资源中包含多个子网或者包含多个 IP 协议族(IPv4 和 IPv6)的端点时,
就有可能发生这种状况。
每个子网最多有 1000 个地址会被镜像到 EndpointSlice 中。
EndpointSlices 的分布问题 每个 EndpointSlice 都有一组端口值,适用于资源内的所有端点。
当为 Service 使用命名端口时,Pod 可能会就同一命名端口获得不同的端口号,
因而需要不同的 EndpointSlice。这有点像 Endpoints 用来对子网进行分组的逻辑。
控制面尝试尽量将 EndpointSlice 填满,不过不会主动地在若干 EndpointSlice
之间执行再平衡操作。这里的逻辑也是相对直接的:
列举所有现有的 EndpointSlices,移除那些不再需要的端点并更新那些已经变化的端点。 列举所有在第一步中被更改过的 EndpointSlices,用新增加的端点将其填满。 如果还有新的端点未被添加进去,尝试将这些端点添加到之前未更改的切片中,
或者创建新切片。 这里比较重要的是,与在 EndpointSlice 之间完成最佳的分布相比,第三步中更看重限制
EndpointSlice 更新的操作次数。例如,如果有 10 个端点待添加,有两个 EndpointSlice
中各有 5 个空位,上述方法会创建一个新的 EndpointSlice 而不是将现有的两个
EndpointSlice 都填满。换言之,与执行多个 EndpointSlice 更新操作相比较,
方法会优先考虑执行一个 EndpointSlice 创建操作。
由于 kube-proxy 在每个节点上运行并监视 EndpointSlice 状态,EndpointSlice
的每次变更都变得相对代价较高,因为这些状态变化要传递到集群中每个节点上。
这一方法尝试限制要发送到所有节点上的变更消息个数,即使这样做可能会导致有多个
EndpointSlice 没有被填满。
在实践中,上面这种并非最理想的分布是很少出现的。大多数被 EndpointSlice
控制器处理的变更都是足够小的,可以添加到某已有 EndpointSlice 中去的。
并且,假使无法添加到已有的切片中,不管怎样都很快就会创建一个新的
EndpointSlice 对象。Deployment 的滚动更新为重新为 EndpointSlice
打包提供了一个自然的机会,所有 Pod 及其对应的端点在这一期间都会被替换掉。
重复的端点 由于 EndpointSlice 变化的自身特点,端点可能会同时出现在不止一个 EndpointSlice
中。鉴于不同的 EndpointSlice 对象在不同时刻到达 Kubernetes 的监视/缓存中,
这种情况的出现是很自然的。
说明: EndpointSlice API 的客户端必须能够处理特定端点地址出现在多个 EndpointSlice 中的情况。
你可以在 kube-proxy
中的 EndpointSliceCache
代码中找到有关如何执行这个端点去重的参考实现。
与 Endpoints 的比较 原来的 Endpoints API 提供了在 Kubernetes 中跟踪网络端点的一种简单而直接的方法。随着 Kubernetes
集群和服务 逐渐开始为更多的后端 Pod 处理和发送请求,
原来的 API 的局限性变得越来越明显。最明显的是那些因为要处理大量网络端点而带来的挑战。
由于任一 Service 的所有网络端点都保存在同一个 Endpoints 对象中,这些 Endpoints
对象可能变得非常巨大。对于保持稳定的服务(长时间使用同一组端点),影响不太明显;
即便如此,Kubernetes 的一些使用场景也没有得到很好的服务。
当某 Service 存在很多后端端点并且该工作负载频繁扩缩或上线新更改时,对该 Service 的单个 Endpoints
对象的每次更新都意味着(在控制平面内以及在节点和 API 服务器之间)Kubernetes 集群组件之间会出现大量流量。
这种额外的流量在 CPU 使用方面也有开销。
使用 EndpointSlices 时,添加或移除单个 Pod 对于正监视变更的客户端会触发相同数量的更新,
但这些更新消息的大小在大规模场景下要小得多。
EndpointSlices 还支持围绕双栈网络和拓扑感知路由等新功能的创新。
接下来 3.6.7 - 网络策略 如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量,
则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。
NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许
Pod 与网络上的各类网络“实体”
(我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语,
这些术语在 Kubernetes 中有特定含义)通信。
NetworkPolicies 适用于一端或两端与 Pod 的连接,与其他连接无关。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问) 被允许的名字空间 IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的,
无论 Pod 或节点的 IP 地址) 在定义基于 Pod 或名字空间的 NetworkPolicy 时,
你会使用选择算符 来设定哪些流量可以进入或离开与该算符匹配的 Pod。
另外,当创建基于 IP 的 NetworkPolicy 时,我们基于 IP 组块(CIDR 范围)来定义策略。
前置条件 网络策略通过网络插件 来实现。
要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。
创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的。
Pod 隔离的两种类型 Pod 有两种隔离: 出口的隔离和入口的隔离。它们涉及到可以建立哪些连接。
这里的“隔离”不是绝对的,而是意味着“有一些限制”。
另外的,“非隔离方向”意味着在所述方向上没有限制。这两种隔离(或不隔离)是独立声明的,
并且都与从一个 Pod 到另一个 Pod 的连接有关。
默认情况下,一个 Pod 的出口是非隔离的,即所有外向连接都是被允许的。如果有任何的 NetworkPolicy
选择该 Pod 并在其 policyTypes
中包含 “Egress”,则该 Pod 是出口隔离的,
我们称这样的策略适用于该 Pod 的出口。当一个 Pod 的出口被隔离时,
唯一允许的来自 Pod 的连接是适用于出口的 Pod 的某个 NetworkPolicy 的 egress
列表所允许的连接。
这些 egress
列表的效果是相加的。
默认情况下,一个 Pod 对入口是非隔离的,即所有入站连接都是被允许的。如果有任何的 NetworkPolicy
选择该 Pod 并在其 policyTypes
中包含 “Ingress”,则该 Pod 被隔离入口,
我们称这种策略适用于该 Pod 的入口。当一个 Pod 的入口被隔离时,唯一允许进入该 Pod
的连接是来自该 Pod 节点的连接和适用于入口的 Pod 的某个 NetworkPolicy 的 ingress
列表所允许的连接。这些 ingress
列表的效果是相加的。
网络策略是相加的,所以不会产生冲突。如果策略适用于 Pod 某一特定方向的流量,
Pod 在对应方向所允许的连接是适用的网络策略所允许的集合。
因此,评估的顺序不影响策略的结果。
要允许从源 Pod 到目的 Pod 的连接,源 Pod 的出口策略和目的 Pod 的入口策略都需要允许连接。
如果任何一方不允许连接,建立连接将会失败。
NetworkPolicy 资源 参阅 NetworkPolicy
来了解资源的完整定义。
下面是一个 NetworkPolicy 的示例:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : test-network-policy
namespace : default
spec :
podSelector :
matchLabels :
role : db
policyTypes :
- Ingress
- Egress
ingress :
- from :
- ipBlock :
cidr : 172.17.0.0 /16
except :
- 172.17.1.0 /24
- namespaceSelector :
matchLabels :
project : myproject
- podSelector :
matchLabels :
role : frontend
ports :
- protocol : TCP
port : 6379
egress :
- to :
- ipBlock :
cidr : 10.0.0.0 /24
ports :
- protocol : TCP
port : 5978
说明: 除非选择支持网络策略的网络解决方案,否则将上述示例发送到API服务器没有任何效果。
必需字段 :与所有其他的 Kubernetes 配置一样,NetworkPolicy 需要 apiVersion
、
kind
和 metadata
字段。关于配置文件操作的一般信息,
请参考配置 Pod 以使用 ConfigMap
和对象管理 。
spec :NetworkPolicy 规约
中包含了在一个名字空间中定义特定网络策略所需的所有信息。
podSelector :每个 NetworkPolicy 都包括一个 podSelector
,
它对该策略所适用的一组 Pod 进行选择。示例中的策略选择带有 "role=db" 标签的 Pod。
空的 podSelector
选择名字空间下的所有 Pod。
policyTypes :每个 NetworkPolicy 都包含一个 policyTypes
列表,其中包含
Ingress
或 Egress
或两者兼具。policyTypes
字段表示给定的策略是应用于进入所选
Pod 的入站流量还是来自所选 Pod 的出站流量,或两者兼有。
如果 NetworkPolicy 未指定 policyTypes
则默认情况下始终设置 Ingress
;
如果 NetworkPolicy 有任何出口规则的话则设置 Egress
。
ingress :每个 NetworkPolicy 可包含一个 ingress
规则的白名单列表。
每个规则都允许同时匹配 from
和 ports
部分的流量。示例策略中包含一条简单的规则:
它匹配某个特定端口,来自三个来源中的一个,第一个通过 ipBlock
指定,第二个通过 namespaceSelector
指定,第三个通过 podSelector
指定。
egress :每个 NetworkPolicy 可包含一个 egress
规则的白名单列表。
每个规则都允许匹配 to
和 port
部分的流量。该示例策略包含一条规则,
该规则将指定端口上的流量匹配到 10.0.0.0/24
中的任何目的地。
所以,该网络策略示例:
隔离 "default" 名字空间下 "role=db" 的 Pod (如果它们不是已经被隔离的话)。
(Ingress 规则)允许以下 Pod 连接到 "default" 名字空间下的带有 "role=db"
标签的所有 Pod 的 6379 TCP 端口:
"default" 名字空间下带有 "role=frontend" 标签的所有 Pod 带有 "project=myproject" 标签的所有名字空间中的 Pod IP 地址范围为 172.17.0.0–172.17.0.255 和 172.17.2.0–172.17.255.255
(即,除了 172.17.1.0/24 之外的所有 172.17.0.0/16) (Egress 规则)允许 “default” 命名空间中任何带有标签 “role=db” 的 Pod 到 CIDR
10.0.0.0/24 下 5978 TCP 端口的连接。
参阅声明网络策略 演练了解更多示例。
选择器 to
和 from
的行为 可以在 ingress
的 from
部分或 egress
的 to
部分中指定四种选择器:
podSelector :此选择器将在与 NetworkPolicy 相同的名字空间中选择特定的
Pod,应将其允许作为入站流量来源或出站流量目的地。
namespaceSelector :此选择器将选择特定的名字空间,应将所有 Pod
用作其入站流量来源或出站流量目的地。
namespaceSelector 和 podSelector :一个指定 namespaceSelector
和 podSelector
的 to
/from
条目选择特定名字空间中的特定 Pod。
注意使用正确的 YAML 语法;下面的策略:
...
ingress :
- from :
- namespaceSelector :
matchLabels :
user : alice
podSelector :
matchLabels :
role : client
...
在 from
数组中仅包含一个元素,只允许来自标有 role=client
的 Pod
且该 Pod 所在的名字空间中标有 user=alice
的连接。但是 这项 策略:
...
ingress :
- from :
- namespaceSelector :
matchLabels :
user : alice
- podSelector :
matchLabels :
role : client
...
在 from
数组中包含两个元素,允许来自本地名字空间中标有 role=client
的
Pod 的连接,或 来自任何名字空间中标有 user=alice
的任何 Pod 的连接。
如有疑问,请使用 kubectl describe
查看 Kubernetes 如何解释该策略。
ipBlock :此选择器将选择特定的 IP CIDR 范围以用作入站流量来源或出站流量目的地。
这些应该是集群外部 IP,因为 Pod IP 存在时间短暂的且随机产生。
集群的入站和出站机制通常需要重写数据包的源 IP 或目标 IP。
在发生这种情况时,不确定在 NetworkPolicy 处理之前还是之后发生,
并且对于网络插件、云提供商、Service
实现等的不同组合,其行为可能会有所不同。
对入站流量而言,这意味着在某些情况下,你可以根据实际的原始源 IP 过滤传入的数据包,
而在其他情况下,NetworkPolicy 所作用的 源IP
则可能是 LoadBalancer
或
Pod 的节点等。
对于出站流量而言,这意味着从 Pod 到被重写为集群外部 IP 的 Service
IP
的连接可能会或可能不会受到基于 ipBlock
的策略的约束。
默认策略 默认情况下,如果名字空间中不存在任何策略,则所有进出该名字空间中 Pod 的流量都被允许。
以下示例使你可以更改该名字空间中的默认行为。
默认拒绝所有入站流量 你可以通过创建选择所有容器但不允许任何进入这些容器的入站流量的 NetworkPolicy
来为名字空间创建 “default” 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-ingress
spec :
podSelector : {}
policyTypes :
- Ingress
这确保即使没有被任何其他 NetworkPolicy 选择的 Pod 仍将被隔离以进行入口。
此策略不影响任何 Pod 的出口隔离。
允许所有入站流量 如果你想允许一个命名空间中所有 Pod 的所有入站连接,你可以创建一个明确允许的策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-ingress
spec :
podSelector : {}
ingress :
- {}
policyTypes :
- Ingress
有了这个策略,任何额外的策略都不会导致到这些 Pod 的任何入站连接被拒绝。
此策略对任何 Pod 的出口隔离没有影响。
默认拒绝所有出站流量 你可以通过创建选择所有容器但不允许来自这些容器的任何出站流量的 NetworkPolicy
来为名字空间创建 “default” 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-egress
spec :
podSelector : {}
policyTypes :
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许流出流量。
此策略不会更改任何 Pod 的入站流量隔离行为。
允许所有出站流量 如果要允许来自命名空间中所有 Pod 的所有连接,
则可以创建一个明确允许来自该命名空间中 Pod 的所有出站连接的策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-egress
spec :
podSelector : {}
egress :
- {}
policyTypes :
- Egress
有了这个策略,任何额外的策略都不会导致来自这些 Pod 的任何出站连接被拒绝。
此策略对进入任何 Pod 的隔离没有影响。
默认拒绝所有入站和所有出站流量 你可以为名字空间创建“默认”策略,以通过在该名字空间中创建以下 NetworkPolicy
来阻止所有入站和出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-all
spec :
podSelector : {}
policyTypes :
- Ingress
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许入站或出站流量。
SCTP 支持 特性状态: Kubernetes v1.20 [stable]
作为一个稳定特性,SCTP 支持默认是被启用的。
要在集群层面禁用 SCTP,你(或你的集群管理员)需要为 API 服务器指定
--feature-gates=SCTPSupport=false,...
来禁用 SCTPSupport
特性门控 。
启用该特性门控后,用户可以将 NetworkPolicy 的 protocol
字段设置为 SCTP
。
说明: 你必须使用支持 SCTP 协议网络策略的 CNI 插件。
针对某个端口范围 特性状态: Kubernetes v1.25 [stable]
在编写 NetworkPolicy 时,你可以针对一个端口范围而不是某个固定端口。
这一目的可以通过使用 endPort
字段来实现,如下例所示:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : multi-port-egress
namespace : default
spec :
podSelector :
matchLabels :
role : db
policyTypes :
- Egress
egress :
- to :
- ipBlock :
cidr : 10.0.0.0 /24
ports :
- protocol : TCP
port : 32000
endPort : 32768
上面的规则允许名字空间 default
中所有带有标签 role=db
的 Pod 使用 TCP 协议与
10.0.0.0/24
范围内的 IP 通信,只要目标端口介于 32000 和 32768 之间就可以。
使用此字段时存在以下限制:
endPort
字段必须等于或者大于 port
字段的值。只有在定义了 port
时才能定义 endPort
。 两个字段的设置值都只能是数字。 说明: 你的集群所使用的 CNI 插件必须支持在
NetworkPolicy 规约中使用 endPort
字段。
如果你的网络插件 不支持
endPort
字段,而你指定了一个包含 endPort
字段的 NetworkPolicy,
策略只对单个 port
字段生效。
基于名字指向某名字空间 特性状态: Kubernetes 1.22 [stable]
只要 NamespaceDefaultLabelName
特性门控 被启用,
Kubernetes 控制面会在所有名字空间上设置一个不可变更的标签
kubernetes.io/metadata.name
。该标签的值是名字空间的名称。
如果 NetworkPolicy 无法在某些对象字段中指向某名字空间,
你可以使用标准的标签方式来指向特定名字空间。
通过网络策略(至少目前还)无法完成的工作 到 Kubernetes 1.25 为止,NetworkPolicy API 还不支持以下功能,
不过你可能可以使用操作系统组件(如 SELinux、OpenVSwitch、IPTables 等等)
或者第七层技术(Ingress 控制器、服务网格实现)或准入控制器来实现一些替代方案。
如果你对 Kubernetes 中的网络安全性还不太了解,了解使用 NetworkPolicy API
还无法实现下面的用户场景是很值得的。
强制集群内部流量经过某公用网关(这种场景最好通过服务网格或其他代理来实现); 与 TLS 相关的场景(考虑使用服务网格或者 Ingress 控制器); 特定于节点的策略(你可以使用 CIDR 来表达这一需求不过你无法使用节点在
Kubernetes 中的其他标识信息来辩识目标节点); 基于名字来选择服务(不过,你可以使用 标签
来选择目标 Pod 或名字空间,这也通常是一种可靠的替代方案); 创建或管理由第三方来实际完成的“策略请求”; 实现适用于所有名字空间或 Pods 的默认策略(某些第三方 Kubernetes 发行版本或项目可以做到这点); 高级的策略查询或者可达性相关工具; 生成网络安全事件日志的能力(例如,被阻塞或接收的连接请求); 显式地拒绝策略的能力(目前,NetworkPolicy 的模型默认采用拒绝操作,
其唯一的能力是添加允许策略); 禁止本地回路或指向宿主的网络流量(Pod 目前无法阻塞 localhost 访问,
它们也无法禁止来自所在节点的访问请求)。 接下来 参阅声明网络策略 演练了解更多示例; 有关 NetworkPolicy 资源所支持的常见场景的更多信息,
请参见此指南 。 3.6.8 - 拓扑感知提示 拓扑感知提示(Topology Aware Hints) 提供了一种机制来帮助将网络流量保持在其请求方所在的区域内。 在集群中的 Pod 之间优先选用相同区域的流量有助于提高可靠性、增强性能(网络延迟和吞吐量)或降低成本。
特性状态: Kubernetes v1.23 [beta]
拓扑感知提示 包含客户怎么使用服务端点的建议,从而实现了拓扑感知的路由功能。
这种方法添加了元数据,以启用 EndpointSlice(或 Endpoints)对象的调用者,
这样,访问这些网络端点的请求流量就可以在它的发起点附近就近路由。
例如,你可以在一个地域内路由流量,以降低通信成本,或提高网络性能。
动机 Kubernetes 集群越来越多的部署到多区域环境中。
拓扑感知提示 提供了一种把流量限制在它的发起区域之内的机制。
这个概念一般被称之为 “拓扑感知路由”。
在计算 服务(Service) 的端点时,
EndpointSlice 控制器会评估每一个端点的拓扑(地域和区域),填充提示字段,并将其分配到某个区域。
集群组件,例如kube-proxy
就可以使用这些提示信息,并用他们来影响流量的路由(倾向于拓扑上相邻的端点)。
使用拓扑感知提示 你可以通过把注解 service.kubernetes.io/topology-aware-hints
的值设置为 auto
,
来激活服务的拓扑感知提示功能。
这告诉 EndpointSlice 控制器在它认为安全的时候来设置拓扑提示。
重要的是,这并不能保证总会设置提示(hints)。
工作原理 此特性启用的功能分为两个组件:EndpointSlice 控制器和 kube-proxy。
本节概述每个组件如何实现此特性。
EndpointSlice 控制器 此特性开启后,EndpointSlice 控制器负责在 EndpointSlice 上设置提示信息。
控制器按比例给每个区域分配一定比例数量的端点。
这个比例来源于此区域中运行节点的
可分配
CPU 核心数。
例如,如果一个区域拥有 2 CPU 核心,而另一个区域只有 1 CPU 核心,
那控制器将给那个有 2 CPU 的区域分配两倍数量的端点。
以下示例展示了提供提示信息后 EndpointSlice 的样子:
apiVersion : discovery.k8s.io/v1
kind : EndpointSlice
metadata :
name : example-hints
labels :
kubernetes.io/service-name : example-svc
addressType : IPv4
ports :
- name : http
protocol : TCP
port : 80
endpoints :
- addresses :
- "10.1.2.3"
conditions :
ready : true
hostname : pod-1
zone : zone-a
hints :
forZones :
- name : "zone-a"
kube-proxy kube-proxy 组件依据 EndpointSlice 控制器设置的提示,过滤由它负责路由的端点。
在大多数场合,这意味着 kube-proxy 可以把流量路由到同一个区域的端点。
有时,控制器从某个不同的区域分配端点,以确保在多个区域之间更平均的分配端点。
这会导致部分流量被路由到其他区域。
保护措施 Kubernetes 控制平面和每个节点上的 kube-proxy,在使用拓扑感知提示功能前,会应用一些保护措施规则。
如果没有检出,kube-proxy 将无视区域限制,从集群中的任意节点上选择端点。
端点数量不足: 如果一个集群中,端点数量少于区域数量,控制器不创建任何提示。不可能实现均衡分配: 在一些场合中,不可能实现端点在区域中的平衡分配。
例如,假设 zone-a 比 zone-b 大两倍,但只有 2 个端点,
那分配到 zone-a 的端点可能收到比 zone-b 多两倍的流量。
如果控制器不能确定此“期望的过载”值低于每一个区域可接受的阈值,控制器将不指派提示信息。
重要的是,这不是基于实时反馈。所以对于单独的端点仍有可能超载。一个或多个节点信息不足: 如果任一节点没有设置标签 topology.kubernetes.io/zone
,
或没有上报可分配的 CPU 数据,控制平面将不会设置任何拓扑感知提示,
继而 kube-proxy 也就不能通过区域过滤端点。一个或多个端点没有设置区域提示: 当这类事情发生时,
kube-proxy 会假设这是正在执行一个从/到拓扑感知提示的转移。
在这种场合下过滤Service 的端点是有风险的,所以 kube-proxy 回撤为使用所有的端点。不在提示中的区域: 如果 kube-proxy 不能根据一个指示在它所在的区域中发现一个端点,
它回撤为使用所有节点的端点。当你的集群新增一个新的区域时,这种情况发生概率很高。限制 当 Service 的 externalTrafficPolicy
或 internalTrafficPolicy
设置值为 Local
时,
拓扑感知提示功能不可用。
你可以在一个集群的不同服务中使用这两个特性,但不能在同一个服务中这么做。 这种方法不适用于大部分流量来自于一部分区域的服务。
相反的,这里假设入站流量将根据每个区域中节点的服务能力按比例的分配。 EndpointSlice 控制器在计算每一个区域的容量比例时,会忽略未就绪的节点。
在大量节点未就绪的场景下,这样做会带来非预期的结果。 EndpointSlice 控制器在计算每一个区域的部署比例时,并不会考虑
容忍度 。
如果服务后台的 Pod 被限制只能运行在集群节点的一个子集上,这些信息并不会被使用。 这种方法和自动扩展机制之间不能很好的协同工作。例如,如果大量流量来源于一个区域,
那只有分配到该区域的端点才可用来处理流量。这会导致
Pod 自动水平扩展
要么不能拾取此事件,要么新增 Pod 被启动到其他区域。 接下来 3.6.9 - Windows 网络 Kubernetes 支持运行 Linux 或 Windows 节点。
你可以在统一集群内混布这两种节点。
本页提供了特定于 Windows 操作系统的网络概述。
Windows 容器网络 Windows 容器网络通过 CNI 插件 暴露。
Windows 容器网络的工作方式与虚拟机类似。
每个容器都有一个连接到 Hyper-V 虚拟交换机(vSwitch)的虚拟网络适配器(vNIC)。
主机网络服务(Host Networking Service,HNS)和主机计算服务(Host Comute Service,HCS)
协同创建容器并将容器 vNIC 挂接到网络。
HCS 负责管理容器,而 HNS 负责管理以下网络资源:
虚拟网络(包括创建 vSwitch) Endpoint / vNIC 命名空间 包括数据包封装、负载均衡规则、ACL 和 NAT 规则在内的策略。 Windows HNS 和 vSwitch 实现命名空间划分,且可以按需为 Pod 或容器创建虚拟 NIC。
然而,诸如 DNS、路由和指标等许多配置将存放在 Windows 注册表数据库中,
而不是像 Linux 将这些配置作为文件存放在 /etc
内。
针对容器的 Windows 注册表与主机的注册表是分开的,因此将 /etc/resolv.conf
从主机映射到一个容器的类似概念与 Linux 上的效果不同。
这些必须使用容器环境中运行的 Windows API 进行配置。
因此,实现 CNI 时需要调用 HNS,而不是依赖文件映射将网络详情传递到 Pod 或容器中。
网络模式 Windows 支持五种不同的网络驱动/模式:L2bridge、L2tunnel、Overlay (Beta)、Transparent 和 NAT。
在 Windows 和 Linux 工作节点组成的异构集群中,你需要选择一个同时兼容 Windows 和 Linux 的网络方案。
下表列出了 Windows 支持的树外插件,并给出了何时使用每种 CNI 的建议:
网络驱动 描述 容器数据包修改 网络插件 网络插件特点 L2bridge 容器挂接到一个外部 vSwitch。容器挂接到下层网络,但物理网络不需要了解容器的 MAC,因为这些 MAC 在入站/出站时被重写。 MAC 被重写为主机 MAC,可使用 HNS OutboundNAT 策略将 IP 重写为主机 IP。 win-bridge 、Azure-CNI 、Flannel host-gateway 使用 win-bridgewin-bridge 使用 L2bridge 网络模式,将容器连接到主机的下层,提供最佳性能。节点间连接需要用户定义的路由(UDR)。 L2Tunnel 这是 L2bridge 的一种特例,但仅用在 Azure 上。所有数据包都会被发送到应用了 SDN 策略的虚拟化主机。 MAC 被重写,IP 在下层网络上可见。 Azure-CNI Azure-CNI 允许将容器集成到 Azure vNET,允许容器充分利用 Azure 虚拟网络 所提供的能力集合。例如,安全地连接到 Azure 服务或使用 Azure NSG。参考 azure-cni 了解有关示例 。 Overlay 容器被赋予一个 vNIC,连接到外部 vSwitch。每个上层网络都有自己的 IP 子网,由自定义 IP 前缀进行定义。该上层网络驱动使用 VXLAN 封装。 用外部头进行封装。 win-overlay 、Flannel VXLAN(使用 win-overlay)当需要将虚拟容器网络与主机的下层隔离时(例如出于安全原因),应使用 win-overlay。如果你的数据中心的 IP 个数有限,可以将 IP 在不同的上层网络中重用(带有不同的 VNID 标记)。在 Windows Server 2019 上这个选项需要 KB4489899 。 Transparent(ovn-kubernetes 的特殊用例) 需要一个外部 vSwitch。容器挂接到一个外部 vSwitch,由后者通过逻辑网络(逻辑交换机和路由器)实现 Pod 内通信。 数据包通过 GENEVE 或 STT 隧道进行封装,以到达其它主机上的 Pod。 数据包基于 OVN 网络控制器提供的隧道元数据信息被转发或丢弃。 南北向通信使用 NAT。 ovn-kubernetes 通过 ansible 部署 。通过 Kubernetes 策略可以实施分布式 ACL。支持 IPAM。无需 kube-proxy 即可实现负载均衡。无需 iptables/netsh 即可进行 NAT。NAT(Kubernetes 中未使用 ) 容器被赋予一个 vNIC,连接到内部 vSwitch。DNS/DHCP 是使用一个名为 WinNAT 的内部组件 实现的 MAC 和 IP 重写为主机 MAC/IP。 nat 放在此处保持完整性。
如上所述,Windows 通过 VXLAN 网络后端 (Beta 支持 ;委派给 win-overlay)
和 host-gateway 网络后端 (稳定支持;委派给 win-bridge)
也支持 Flannel 的 CNI 插件 。
此插件支持委派给参考 CNI 插件(win-overlay、win-bridge)之一,配合使用 Windows
上的 Flannel 守护程序(Flanneld),以便自动分配节点子网租赁并创建 HNS 网络。
该插件读取自己的配置文件(cni.conf),并聚合 FlannelD 生成的 subnet.env 文件中的环境变量。
然后,委派给网络管道的参考 CNI 插件之一,并将包含节点分配子网的正确配置发送给 IPAM 插件(例如:host-local
)。
对于 Node、Pod 和 Service 对象,TCP/UDP 流量支持以下网络流:
Pod → Pod(IP) Pod → Pod(名称) Pod → Service(集群 IP) Pod → Service(PQDN,但前提是没有 ".") Pod → Service(FQDN) Pod → 外部(IP) Pod → 外部(DNS) Node → Pod Pod → Node IP 地址管理(IPAM) Windows 支持以下 IPAM 选项:
负载均衡和 Service Kubernetes Service 是一种抽象:定义了逻辑上的一组 Pod 和一种通过网络访问这些 Pod 的方式。
在包含 Windows 节点的集群中,你可以使用以下类别的 Service:
NodePort
ClusterIP
LoadBalancer
ExternalName
Windows 容器网络与 Linux 网络有着很重要的差异。
更多细节和背景信息,参考 Microsoft Windows 容器网络文档 。
在 Windows 上,你可以使用以下设置来配置 Service 和负载均衡行为:
Windows Service 设置 功能特性 描述 支持的 Windows 操作系统最低版本 启用方式 会话亲和性 确保每次都将来自特定客户端的连接传递到同一个 Pod。 Windows Server 2022 将 service.spec.sessionAffinity
设为 “ClientIP” Direct Server Return (DSR) 在负载均衡模式中 IP 地址修正和 LBNAT 直接发生在容器 vSwitch 端口;服务流量到达时源 IP 设置为原始 Pod IP。 Windows Server 2019 在 kube-proxy 中设置以下标志:--feature-gates="WinDSR=true" --enable-dsr=true
保留目标(Preserve-Destination) 跳过服务流量的 DNAT,从而在到达后端 Pod 的数据包中保留目标服务的虚拟 IP。也会禁用节点间的转发。 Windows Server,version 1903 在服务注解中设置 "preserve-destination": "true"
并在 kube-proxy 中启用 DSR。 IPv4/IPv6 双栈网络 进出集群和集群内通信都支持原生的 IPv4 间与 IPv6 间流量 Windows Server 2019 参考 IPv4/IPv6 双栈 。 客户端 IP 保留 确保入站流量的源 IP 得到保留。也会禁用节点间转发。 Windows Server 2019 将 service.spec.externalTrafficPolicy
设置为 “Local” 并在 kube-proxy 中启用 DSR。
警告: 如果目的地节点在运行 Windows Server 2022,则上层网络的 NodePort Service 存在已知问题。
要完全避免此问题,可以使用 externalTrafficPolicy: Local
配置服务。
在安装了 KB5005619 的 Windows Server 2022 或更高版本上,采用 L2bridge 网络时
Pod 间连接存在已知问题。
要解决此问题并恢复 Pod 间连接,你可以在 kube-proxy 中禁用 WinDSR 功能。
这些问题需要操作系统修复。
有关更新,请参考 https://github.com/microsoft/Windows-Containers/issues/204 。
限制 Windows 节点不支持 以下网络功能:
主机网络模式 从节点本身访问本地 NodePort(可以从其他节点或外部客户端进行访问) 为同一 Service 提供 64 个以上后端 Pod(或不同目的地址) 在连接到上层网络的 Windows Pod 之间使用 IPv6 通信 非 DSR 模式中的本地流量策略(Local Traffic Policy) 通过 win-overlay
、win-bridge
使用 ICMP 协议,或使用 Azure-CNI 插件进行出站通信。 具体而言,Windows 数据平面(VFP )不支持 ICMP 数据包转换,这意味着:指向同一网络内目的地址的 ICMP 数据包(例如 Pod 间的 ping 通信)可正常工作; TCP/UDP 数据包可正常工作; 通过远程网络指向其它地址的 ICMP 数据包(例如通过 ping 从 Pod 到外部公网的通信)无法被转换,
因此无法被路由回到这些数据包的源点; 由于 TCP/UDP 数据包仍可被转换,所以在调试与外界的连接时,
你可以将 ping <destination>
替换为 curl <destination>
。 其他限制:
由于缺少 CHECK
实现,Windows 参考网络插件 win-bridge 和 win-overlay 未实现
CNI 规约 的 v0.4.0 版本。 Flannel VXLAN CNI 插件在 Windows 上有以下限制:使用 Flannel v0.12.0(或更高版本)时,节点到 Pod 的连接仅适用于本地 Pod。 Flannel 仅限于使用 VNI 4096 和 UDP 端口 4789。
有关这些参数的更多详细信息,请参考官方的 Flannel VXLAN 后端文档。 3.6.10 - 服务内部流量策略 如果集群中的两个 Pod 想要通信,并且两个 Pod 实际上都在同一节点运行, 服务内部流量策略 可以将网络流量限制在该节点内。 通过集群网络避免流量往返有助于提高可靠性、增强性能(网络延迟和吞吐量)或降低成本。
特性状态: Kubernetes v1.23 [beta]
服务内部流量策略 开启了内部流量限制,将内部流量只路由到发起方所处节点内的服务端点。
这里的”内部“流量指当前集群中的 Pod 所发起的流量。
这种机制有助于节省开销,提升效率。
使用服务内部流量策略 ServiceInternalTrafficPolicy
特性门控 是 Beta 功能,默认启用。
启用该功能后,你就可以通过将 Service 的
.spec.internalTrafficPolicy
项设置为 Local
,
来为它指定一个内部专用的流量策略。
此设置就相当于告诉 kube-proxy 对于集群内部流量只能使用本地的服务端口。
说明: 如果某节点上的 Pod 均不提供指定 Service 的服务端点,
即使该 Service 在其他节点上有可用的服务端点,
Service 的行为看起来也像是它只有 0 个服务端点(只针对此节点上的 Pod)。
以下示例展示了把 Service 的 .spec.internalTrafficPolicy
项设为 Local
时,
Service 的样子:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
internalTrafficPolicy : Local
工作原理 kube-proxy 基于 spec.internalTrafficPolicy
的设置来过滤路由的目标服务端点。
当它的值设为 Local
时,只选择节点本地的服务端点。
当它的值设为 Cluster
或缺省时,则选择所有的服务端点。
启用特性门控
ServiceInternalTrafficPolicy
后,
spec.internalTrafficPolicy
的值默认设为 Cluster
。
接下来 3.6.11 - Service 与 Pod 的 DNS 你的工作负载可以使用 DNS 发现集群内的 Service,本页说明具体工作原理。
Kubernetes 为 Service 和 Pod 创建 DNS 记录。
你可以使用一致的 DNS 名称而非 IP 地址访问 Service。
Kubernetes DNS 除了在集群上调度 DNS Pod 和 Service,
还配置 kubelet 以告知各个容器使用 DNS Service 的 IP 来解析 DNS 名称。
集群中定义的每个 Service (包括 DNS 服务器自身)都被赋予一个 DNS 名称。
默认情况下,客户端 Pod 的 DNS 搜索列表会包含 Pod 自身的名字空间和集群的默认域。
Service 的名字空间 DNS 查询可能因为执行查询的 Pod 所在的名字空间而返回不同的结果。
不指定名字空间的 DNS 查询会被限制在 Pod 所在的名字空间内。
要访问其他名字空间中的 Service,需要在 DNS 查询中指定名字空间。
例如,假定名字空间 test
中存在一个 Pod,prod
名字空间中存在一个服务
data
。
Pod 查询 data
时没有返回结果,因为使用的是 Pod 的名字空间 test
。
Pod 查询 data.prod
时则会返回预期的结果,因为查询中指定了名字空间。
DNS 查询可以使用 Pod 中的 /etc/resolv.conf
展开。kubelet 会为每个 Pod
生成此文件。例如,对 data
的查询可能被展开为 data.test.svc.cluster.local
。
search
选项的取值会被用来展开查询。要进一步了解 DNS 查询,可参阅
resolv.conf
手册页面 。
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
概括起来,名字空间 test
中的 Pod 可以成功地解析 data.prod
或者
data.prod.svc.cluster.local
。
DNS 记录 哪些对象会获得 DNS 记录呢?
Services Pods 以下各节详细介绍已支持的 DNS 记录类型和布局。
其它布局、名称或者查询即使碰巧可以工作,也应视为实现细节,
将来很可能被更改而且不会因此发出警告。
有关最新规范请查看
Kubernetes 基于 DNS 的服务发现 。
Service A/AAAA 记录 “普通” Service(除了无头 Service)会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于 Service 的 IP 协议族。
该名称会解析成对应 Service 的集群 IP。
“无头(Headless)” Service (没有集群 IP)也会以
my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个 DNS A 或 AAAA 记录,
具体取决于 Service 的 IP 协议族。
与普通 Service 不同,这一记录会被解析成对应 Service 所选择的 Pod IP 的集合。
客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。
SRV 记录 Kubernetes 根据普通 Service 或
Headless Service
中的命名端口创建 SRV 记录。每个命名端口,
SRV 记录格式为 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
。
普通 Service,该记录会被解析成端口号和域名:my-svc.my-namespace.svc.cluster-domain.example
。
无头 Service,该记录会被解析成多个结果,及该服务的每个后端 Pod 各一个 SRV 记录,
其中包含 Pod 端口号和格式为 auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
的域名。
Pod A/AAAA 记录 一般而言,Pod 会对应如下 DNS 名字解析:
pod-ip-address.my-namespace.pod.cluster-domain.example
例如,对于一个位于 default
名字空间,IP 地址为 172.17.0.3 的 Pod,
如果集群的域名为 cluster.local
,则 Pod 会对应 DNS 名称:
172-17-0-3.default.pod.cluster.local
通过 Service 暴露出来的所有 Pod 都会有如下 DNS 解析名称可用:
pod-ip-address.service-name.my-namespace.svc.cluster-domain.example
Pod 的 hostname 和 subdomain 字段 当前,创建 Pod 时其主机名取自 Pod 的 metadata.name
值。
Pod 规约中包含一个可选的 hostname
字段,可以用来指定 Pod 的主机名。
当这个字段被设置时,它将优先于 Pod 的名字成为该 Pod 的主机名。
举个例子,给定一个 hostname
设置为 "my-host
" 的 Pod,
该 Pod 的主机名将被设置为 "my-host
"。
Pod 规约还有一个可选的 subdomain
字段,可以用来指定 Pod 的子域名。
举个例子,某 Pod 的 hostname
设置为 “foo
”,subdomain
设置为 “bar
”,
在名字空间 “my-namespace
” 中对应的完全限定域名(FQDN)为
“foo.bar.my-namespace.svc.cluster-domain.example
”。
示例:
apiVersion : v1
kind : Service
metadata :
name : default-subdomain
spec :
selector :
name : busybox
clusterIP : None
ports :
- name : foo # 实际上不需要指定端口号
port : 1234
targetPort : 1234
---
apiVersion : v1
kind : Pod
metadata :
name : busybox1
labels :
name : busybox
spec :
hostname : busybox-1
subdomain : default-subdomain
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
name : busybox
---
apiVersion : v1
kind : Pod
metadata :
name : busybox2
labels :
name : busybox
spec :
hostname : busybox-2
subdomain : default-subdomain
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
name : busybox
如果某无头 Service 与某 Pod 在同一个名字空间中,且它们具有相同的子域名,
集群的 DNS 服务器也会为该 Pod 的全限定主机名返回 A 记录或 AAAA 记录。
例如,在同一个名字空间中,给定一个主机名为 “busybox-1”、
子域名设置为 “default-subdomain” 的 Pod,和一个名称为 “default-subdomain
”
的无头 Service,Pod 将看到自己的 FQDN 为
"busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
"。
DNS 会为此名字提供一个 A 记录或 AAAA 记录,指向该 Pod 的 IP。
“busybox1
” 和 “busybox2
” 这两个 Pod 分别具有它们自己的 A 或 AAAA 记录。
EndpointSlice
对象可以为任何端点地址及其 IP 指定 hostname
。
说明: 由于不是为 Pod 名称创建 A 或 AAAA 记录的,因此 Pod 的 A 或 AAAA 需要 hostname
。
没有设置 hostname
但设置了 subdomain
的 Pod 只会为
无头 Service 创建 A 或 AAAA 记录(default-subdomain.my-namespace.svc.cluster-domain.example
)
指向 Pod 的 IP 地址。
另外,除非在服务上设置了 publishNotReadyAddresses=True
,否则只有 Pod 进入就绪状态
才会有与之对应的记录。
Pod 的 setHostnameAsFQDN 字段 特性状态: Kubernetes v1.22 [stable]
当 Pod 配置为具有全限定域名 (FQDN) 时,其主机名是短主机名。
例如,如果你有一个具有完全限定域名 busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
的 Pod,
则默认情况下,该 Pod 内的 hostname
命令返回 busybox-1
,而 hostname --fqdn
命令返回 FQDN。
当你在 Pod 规约中设置了 setHostnameAsFQDN: true
时,kubelet 会将 Pod
的全限定域名(FQDN)作为该 Pod 的主机名记录到 Pod 所在名字空间。
在这种情况下,hostname
和 hostname --fqdn
都会返回 Pod 的全限定域名。
说明: 在 Linux 中,内核的主机名字段(struct utsname
的 nodename
字段)限定最多 64 个字符。
如果 Pod 启用这一特性,而其 FQDN 超出 64 字符,Pod 的启动会失败。
Pod 会一直出于 Pending
状态(通过 kubectl
所看到的 ContainerCreating
),
并产生错误事件,例如
"Failed to construct FQDN from Pod hostname and cluster domain, FQDN
long-FQDN
is too long (64 characters is the max, 70 characters requested)."
(无法基于 Pod 主机名和集群域名构造 FQDN,FQDN long-FQDN
过长,至多 64 个字符,请求字符数为 70)。
对于这种场景而言,改善用户体验的一种方式是创建一个
准入 Webhook 控制器 ,
在用户创建顶层对象(如 Deployment)的时候控制 FQDN 的长度。
Pod 的 DNS 策略 DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。
这些策略可以在 Pod 规约中的 dnsPolicy
字段设置:
"Default
": Pod 从运行所在的节点继承名称解析配置。
参考相关讨论 获取更多信息。 "ClusterFirst
": 与配置的集群域后缀不匹配的任何 DNS 查询(例如 "www.kubernetes.io")
都将转发到从节点继承的上游名称服务器。集群管理员可能配置了额外的存根域和上游 DNS 服务器。
参阅相关讨论
了解在这些场景中如何处理 DNS 查询的信息。 "ClusterFirstWithHostNet
":对于以 hostNetwork 方式运行的 Pod,应显式设置其 DNS 策略
"ClusterFirstWithHostNet
"。注意:这在 Windows 上不支持。 有关详细信息,请参见下文 。 "None
": 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置。Pod 会使用其 dnsConfig
字段所提供的 DNS 设置。
参见 Pod 的 DNS 配置 节。 说明: "Default" 不是默认的 DNS 策略。如果未明确指定 dnsPolicy
,则使用 "ClusterFirst"。
下面的示例显示了一个 Pod,其 DNS 策略设置为 "ClusterFirstWithHostNet
",
因为它已将 hostNetwork
设置为 true
。
apiVersion : v1
kind : Pod
metadata :
name : busybox
namespace : default
spec :
containers :
- image : busybox:1.28
command :
- sleep
- "3600"
imagePullPolicy : IfNotPresent
name : busybox
restartPolicy : Always
hostNetwork : true
dnsPolicy : ClusterFirstWithHostNet
Pod 的 DNS 配置 特性状态: Kubernetes v1.14 [stable]
Pod 的 DNS 配置可让用户对 Pod 的 DNS 设置进行更多控制。
dnsConfig
字段是可选的,它可以与任何 dnsPolicy
设置一起使用。
但是,当 Pod 的 dnsPolicy
设置为 "None
" 时,必须指定 dnsConfig
字段。
用户可以在 dnsConfig
字段中指定以下属性:
nameservers
:将用作于 Pod 的 DNS 服务器的 IP 地址列表。
最多可以指定 3 个 IP 地址。当 Pod 的 dnsPolicy
设置为 "None
" 时,
列表必须至少包含一个 IP 地址,否则此属性是可选的。
所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。
searches
:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。
指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。
重复的域名将被删除。Kubernetes 最多允许 6 个搜索域。
options
:可选的对象列表,其中每个对象可能具有 name
属性(必需)和 value
属性(可选)。
此属性中的内容将合并到从指定的 DNS 策略生成的选项。
重复的条目将被删除。
以下是具有自定义 DNS 设置的 Pod 示例:
apiVersion : v1
kind : Pod
metadata :
namespace : default
name : dns-example
spec :
containers :
- name : test
image : nginx
dnsPolicy : "None"
dnsConfig :
nameservers :
- 1.2.3.4
searches :
- ns1.svc.cluster-domain.example
- my.dns.search.suffix
options :
- name : ndots
value : "2"
- name : edns0
创建上面的 Pod 后,容器 test
会在其 /etc/resolv.conf
文件中获取以下内容:
nameserver 1.2.3.4
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0
对于 IPv6 设置,搜索路径和名称服务器应按以下方式设置:
kubectl exec -it dns-example -- cat /etc/resolv.conf
输出类似于:
nameserver 2001:db8:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5
DNS 搜索域列表限制 特性状态: Kubernetes 1.26 [beta]
Kubernetes 本身不限制 DNS 配置,最多可支持 32 个搜索域列表,所有搜索域的总长度不超过 2048。
此限制分别适用于节点的解析器配置文件、Pod 的 DNS 配置和合并的 DNS 配置。
说明: 早期版本的某些容器运行时可能对 DNS 搜索域的数量有自己的限制。
根据容器运行环境,那些具有大量 DNS 搜索域的 Pod 可能会卡在 Pending 状态。
众所周知 containerd v1.5.5 或更早版本和 CRI-O v1.21 或更早版本都有这个问题。
Windows 节点上的 DNS 解析 在 Windows 节点上运行的 Pod 不支持 ClusterFirstWithHostNet。
Windows 将所有带有 .
的名称视为全限定域名(FQDN)并跳过全限定域名(FQDN)解析。 在 Windows 上,可以使用的 DNS 解析器有很多。
由于这些解析器彼此之间会有轻微的行为差别,建议使用
Resolve-DNSName
powershell cmdlet 进行名称查询解析。 在 Linux 上,有一个 DNS 后缀列表,当解析全名失败时可以使用。
在 Windows 上,你只能有一个 DNS 后缀,
即与该 Pod 的命名空间相关联的 DNS 后缀(例如:mydns.svc.cluster.local
)。
Windows 可以解析全限定域名(FQDN),和使用了该 DNS 后缀的 Services 或者网络名称。
例如,在 default
命名空间中生成一个 Pod,该 Pod 会获得的 DNS 后缀为 default.svc.cluster.local
。
在 Windows 的 Pod 中,你可以解析 kubernetes.default.svc.cluster.local
和 kubernetes
,
但是不能解析部分限定名称(kubernetes.default
和 kubernetes.default.svc
)。 接下来 有关管理 DNS 配置的指导,
请查看配置 DNS 服务
3.6.12 - IPv4/IPv6 双协议栈 Kubernetes 允许你配置单协议栈 IPv4 网络、单协议栈 IPv6 网络或同时激活这两种网络的双协议栈网络。本页说明具体配置方法。
特性状态: Kubernetes v1.23 [stable]
IPv4/IPv6 双协议栈网络能够将 IPv4 和 IPv6 地址分配给
Pod 和
Service 。
从 1.21 版本开始,Kubernetes 集群默认启用 IPv4/IPv6 双协议栈网络,
以支持同时分配 IPv4 和 IPv6 地址。
支持的功能 Kubernetes 集群的 IPv4/IPv6 双协议栈可提供下面的功能:
双协议栈 pod 网络 (每个 pod 分配一个 IPv4 和 IPv6 地址) IPv4 和 IPv6 启用的服务 Pod 的集群外出口通过 IPv4 和 IPv6 路由 先决条件 为了使用 IPv4/IPv6 双栈的 Kubernetes 集群,需要满足以下先决条件:
Kubernetes 1.20 版本或更高版本,有关更早 Kubernetes 版本的使用双栈服务的信息,
请参考对应版本的 Kubernetes 文档。 提供商支持双协议栈网络(云提供商或其他提供商必须能够为 Kubernetes
节点提供可路由的 IPv4/IPv6 网络接口) 支持双协议栈的网络插件 配置 IPv4/IPv6 双协议栈 如果配置 IPv4/IPv6 双栈,请分配双栈集群网络:
kube-apiserver:--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
kube-controller-manager:--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
--node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6
对于 IPv4 默认为 /24,
对于 IPv6 默认为 /64 kube-proxy:--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
kubelet:当没有 --cloud-provider
时,管理员可以通过 --node-ip
来传递逗号分隔的 IP 地址,
为该节点手动配置双栈 .status.addresses
。
如果 Pod 以 HostNetwork 模式在该节点上运行,则 Pod 会用 .status.podIPs
字段来报告它的 IP 地址。
一个节点中的所有 podIP
都会匹配该节点的由 .status.addresses
字段定义的 IP 组。 说明: IPv4 CIDR 的一个例子:10.244.0.0/16
(尽管你会提供你自己的地址范围)。
IPv6 CIDR 的一个例子:fdXY:IJKL:MNOP:15::/64
(这里演示的是格式而非有效地址 - 请看 RFC 4193 )。
服务 你可以使用 IPv4 或 IPv6 地址来创建
Service 。
服务的地址族默认为第一个服务集群 IP 范围的地址族(通过 kube-apiserver 的
--service-cluster-ip-range
参数配置)。
当你定义服务时,可以选择将其配置为双栈。若要指定所需的行为,你可以设置
.spec.ipFamilyPolicy
字段为以下值之一:
SingleStack
:单栈服务。控制面使用第一个配置的服务集群 IP 范围为服务分配集群 IP。PreferDualStack
:为服务分配 IPv4 和 IPv6 集群 IP 地址。 RequireDualStack
:从 IPv4 和 IPv6 的地址范围分配服务的 .spec.ClusterIPs
从基于在 .spec.ipFamilies
数组中第一个元素的地址族的 .spec.ClusterIPs
列表中选择 .spec.ClusterIP
如果你想要定义哪个 IP 族用于单栈或定义双栈 IP 族的顺序,可以通过设置
服务上的可选字段 .spec.ipFamilies
来选择地址族。
说明: .spec.ipFamilies
字段是不可变的,因为系统无法为已经存在的服务重新分配
.spec.ClusterIP
。如果你想改变 .spec.ipFamilies
,则需要删除并重新创建服务。
你可以设置 .spec.ipFamily
为以下任何数组值:
["IPv4"]
["IPv6"]
["IPv4","IPv6"]
(双栈)["IPv6","IPv4"]
(双栈)你所列出的第一个地址族用于原来的 .spec.ClusterIP
字段。
双栈服务配置场景 以下示例演示多种双栈服务配置场景下的行为。
新服务的双栈选项 此服务规约中没有显式设定 .spec.ipFamilyPolicy
。当你创建此服务时,Kubernetes
从所配置的第一个 service-cluster-ip-range
中为服务分配一个集群 IP,并设置
.spec.ipFamilyPolicy
为 SingleStack
。
(无选择算符的服务
和无头服务 的行为方式
与此相同。)
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
此服务规约显式地将 .spec.ipFamilyPolicy
设置为 PreferDualStack
。
当你在双栈集群上创建此服务时,Kubernetes 会为该服务分配 IPv4 和 IPv6 地址。
控制平面更新服务的 .spec
以记录 IP 地址分配。
字段 .spec.ClusterIPs
是主要字段,包含两个分配的 IP 地址;.spec.ClusterIP
是次要字段,
其取值从 .spec.ClusterIPs
计算而来。
对于 .spec.ClusterIP
字段,控制面记录来自第一个服务集群 IP 范围
对应的地址族的 IP 地址。 对于单协议栈的集群,.spec.ClusterIPs
和 .spec.ClusterIP
字段都
仅仅列出一个地址。 对于启用了双协议栈的集群,将 .spec.ipFamilyPolicy
设置为
RequireDualStack
时,其行为与 PreferDualStack
相同。 apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
下面的服务规约显式地在 .spec.ipFamilies
中指定 IPv6
和 IPv4
,并
将 .spec.ipFamilyPolicy
设定为 PreferDualStack
。
当 Kubernetes 为 .spec.ClusterIPs
分配一个 IPv6 和一个 IPv4 地址时,
.spec.ClusterIP
被设置成 IPv6 地址,因为它是 .spec.ClusterIPs
数组中的第一个元素,
覆盖其默认值。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
- IPv4
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
现有服务的双栈默认值 下面示例演示了在服务已经存在的集群上新启用双栈时的默认行为。
(将现有集群升级到 1.21 或者更高版本会启用双协议栈支持。)
在集群上启用双栈时,控制面会将现有服务(无论是 IPv4
还是 IPv6
)配置
.spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为服务的当前地址族。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查现有服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : MyApp
name : my-service
spec :
clusterIP : 10.0.197.123
clusterIPs :
- 10.0.197.123
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app.kubernetes.io/name : MyApp
type : ClusterIP
status :
loadBalancer : {}
在集群上启用双栈时,带有选择算符的现有
无头服务
由控制面设置 .spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为第一个服务集群 IP 范围的地址族(通过配置 kube-apiserver 的
--service-cluster-ip-range
参数),即使 .spec.ClusterIP
的设置值为 None
也如此。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查带有选择算符的现有无头服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : MyApp
name : my-service
spec :
clusterIP : None
clusterIPs :
- None
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app.kubernetes.io/name : MyApp
在单栈和双栈之间切换服务 服务可以从单栈更改为双栈,也可以从双栈更改为单栈。
要将服务从单栈更改为双栈,根据需要将 .spec.ipFamilyPolicy
从 SingleStack
改为
PreferDualStack
或 RequireDualStack
。
当你将此服务从单栈更改为双栈时,Kubernetes 将分配缺失的地址族,
以便现在该服务具有 IPv4 和 IPv6 地址。
编辑服务规约将 .spec.ipFamilyPolicy
从 SingleStack
改为 PreferDualStack
。
之前:
spec :
ipFamilyPolicy : SingleStack
之后:
spec :
ipFamilyPolicy : PreferDualStack
要将服务从双栈更改为单栈,请将 .spec.ipFamilyPolicy
从 PreferDualStack
或
RequireDualStack
改为 SingleStack
。
当你将此服务从双栈更改为单栈时,Kubernetes 只保留 .spec.ClusterIPs
数组中的第一个元素,并设置 .spec.ClusterIP
为那个 IP 地址,
并设置 .spec.ipFamilies
为 .spec.ClusterIPs
地址族。 无选择算符的无头服务 对于不带选择算符的无头服务 ,
若没有显式设置 .spec.ipFamilyPolicy
,则 .spec.ipFamilyPolicy
字段默认设置为 RequireDualStack
。
LoadBalancer 类型服务 要为你的服务提供双栈负载均衡器:
将 .spec.type
字段设置为 LoadBalancer
将 .spec.ipFamilyPolicy
字段设置为 PreferDualStack
或者 RequireDualStack
说明: 为了使用双栈的负载均衡器类型服务,你的云驱动必须支持 IPv4 和 IPv6 的负载均衡器。
出站流量 如果你要启用出站流量,以便使用非公开路由 IPv6 地址的 Pod 到达集群外地址
(例如公网),则需要通过透明代理或 IP 伪装等机制使 Pod 使用公共路由的
IPv6 地址。
ip-masq-agent 项目
支持在双栈集群上进行 IP 伪装。
Windows 支持 Windows 上的 Kubernetes 不支持单栈“仅 IPv6” 网络。 然而,
对于 Pod 和节点而言,仅支持单栈形式服务的双栈 IPv4/IPv6 网络是被支持的。
你可以使用 l2bridge
网络来实现 IPv4/IPv6 双栈联网。
说明: Windows 上的 Overlay (VXLAN) 网络不 支持双栈网络。
关于 Windows 的不同网络模式,你可以进一步阅读
Windows 上的网络 。
接下来 3.7 - 存储 为集群中的 Pods 提供长期和临时存储的方法。
3.7.1 - 卷 Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用程序带来一些问题。
问题之一是当容器崩溃时文件丢失。
kubelet 会重新启动容器,但容器会以干净的状态重启。
第二个问题会在同一 Pod
中运行多个容器并共享文件时出现。
Kubernetes 卷(Volume)
这一抽象概念能够解决这两个问题。
阅读本文前建议你熟悉一下 Pod 。
背景 Docker 也有卷(Volume) 的概念,但对它只有少量且松散的管理。
Docker 卷是磁盘上或者另外一个容器内的一个目录。
Docker 提供卷驱动程序,但是其功能非常有限。
Kubernetes 支持很多类型的卷。
Pod 可以同时使用任意数目的卷类型。
临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。
当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。
对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。
所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。
使用卷时, 在 .spec.volumes
字段中设置为 Pod 提供的卷,并在
.spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。
容器中的进程看到的文件系统视图是由它们的容器镜像
的初始内容以及挂载在容器中的卷(如果定义了的话)所组成的。
其中根文件系统同容器镜像的内容相吻合。
任何在该文件系统下的写入操作,如果被允许的话,都会影响接下来容器中进程访问文件系统时所看到的内容。
卷挂载在镜像中的指定路径 下。
Pod 配置中的每个容器必须独立指定各个卷的挂载位置。
卷不能挂载到其他卷之上(不过存在一种使用 subPath 的相关机制),也不能与其他卷有硬链接。
卷类型 Kubernetes 支持下列类型的卷:
awsElasticBlockStore (已弃用) 特性状态: Kubernetes v1.17 [deprecated]
awsElasticBlockStore
卷将 Amazon Web 服务(AWS)EBS 卷 挂载到你的
Pod 中。emptyDir
在 Pod 被删除时也会一起被删除,但 EBS 卷的内容在删除
Pod 时会被保留,卷只是被卸载掉了。
这意味着 EBS 卷可以预先填充数据,并且该数据可以在 Pod 之间共享。
说明: 你在使用 EBS 卷之前必须使用 aws ec2 create-volume
命令或者 AWS API 创建该卷。
使用 awsElasticBlockStore
卷时有一些限制:
Pod 运行所在的节点必须是 AWS EC2 实例。 这些实例需要与 EBS 卷在相同的地域(Region)和可用区(Availability-Zone)。 EBS 卷只支持被挂载到单个 EC2 实例上。 创建 AWS EBS 卷 在将 EBS 卷用到 Pod 上之前,你首先要创建它。
aws ec2 create-volume --availability-zone= eu-west-1a --size= 10 --volume-type= gp2
确保该区域与你的集群所在的区域相匹配。还要检查卷的大小和 EBS 卷类型都适合你的用途。
AWS EBS 配置示例 apiVersion : v1
kind : Pod
metadata :
name : test-ebs
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /test-ebs
name : test-volume
volumes :
- name : test-volume
# 此 AWS EBS 卷必须已经存在
awsElasticBlockStore :
volumeID : "<volume id>"
fsType : ext4
如果 EBS 卷是分区的,你可以提供可选的字段 partition: "<partition number>"
来指定要挂载到哪个分区上。
AWS EBS CSI 卷迁移 特性状态: Kubernetes v1.25 [stable]
启用 awsElasticBlockStore
的 CSIMigration
特性后,所有插件操作将从现有的树内插件重定向到
ebs.csi.aws.com
容器存储接口(CSI)驱动程序。
为了使用此特性,必须在集群中安装
AWS EBS CSI 驱动 。
AWS EBS CSI 迁移结束 特性状态: Kubernetes v1.17 [alpha]
要禁止控制器管理器和 kubelet 加载 awsElasticBlockStore
存储插件,
请将 InTreePluginAWSUnregister
标志设置为 true
。
azureDisk (已弃用) 特性状态: Kubernetes v1.19 [deprecated]
azureDisk
卷类型用来在 Pod 上挂载 Microsoft Azure
数据盘(Data Disk) 。
若需了解更多详情,请参考 azureDisk
卷插件 。
azureDisk 的 CSI 迁移 特性状态: Kubernetes v1.24 [stable]
启用 azureDisk
的 CSIMigration
特性后,所有插件操作从现有的树内插件重定向到
disk.csi.azure.com
容器存储接口(CSI)驱动程序。
为了使用此特性,必须在集群中安装
Azure 磁盘 CSI 驱动程序 。
azureDisk CSI 迁移完成 特性状态: Kubernetes v1.21 [alpha]
要禁止控制器管理器和 kubelet 加载 azureDisk
存储插件,
请将 InTreePluginAzureDiskUnregister
标志设置为 true
。
azureFile (已弃用) 特性状态: Kubernetes v1.21 [deprecated]
azureFile
卷类型用来在 Pod 上挂载 Microsoft Azure 文件卷(File Volume)(SMB 2.1 和 3.0)。
更多详情请参考 azureFile
卷插件 。
azureFile CSI 迁移 特性状态: Kubernetes v1.21 [beta]
启用 azureFile
的 CSIMigration
特性后,所有插件操作将从现有的树内插件重定向到
file.csi.azure.com
容器存储接口(CSI)驱动程序。要使用此特性,必须在集群中安装
Azure 文件 CSI 驱动程序 ,
并且 CSIMigrationAzureFile
特性门控
必须被启用。
Azure 文件 CSI 驱动尚不支持为同一卷设置不同的 fsgroup。
如果 CSIMigrationAzureFile
特性被启用,用不同的 fsgroup 来使用同一卷也是不被支持的。
azureFile CSI 迁移完成 特性状态: Kubernetes v1.21 [alpha]
要禁止控制器管理器和 kubelet 加载 azureFile
存储插件,
请将 InTreePluginAzureFileUnregister
标志设置为 true
。
cephfs cephfs
卷允许你将现存的 CephFS 卷挂载到 Pod 中。
不像 emptyDir
那样会在 Pod 被删除的同时也会被删除,cephfs
卷的内容在 Pod 被删除时会被保留,只是卷被卸载了。
这意味着 cephfs
卷可以被预先填充数据,且这些数据可以在
Pod 之间共享。同一 cephfs
卷可同时被多个写者挂载。
说明: 在使用 Ceph 卷之前,你的 Ceph 服务器必须已经运行并将要使用的 share 导出(exported)。
更多信息请参考 CephFS 示例 。
cinder (已弃用) 特性状态: Kubernetes v1.18 [deprecated]
说明: Kubernetes 必须配置了 OpenStack Cloud Provider。
cinder
卷类型用于将 OpenStack Cinder 卷挂载到 Pod 中。
Cinder 卷示例配置 apiVersion : v1
kind : Pod
metadata :
name : test-cinder
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-cinder-container
volumeMounts :
- mountPath : /test-cinder
name : test-volume
volumes :
- name : test-volume
# 此 OpenStack 卷必须已经存在
cinder :
volumeID : "<volume id>"
fsType : ext4
OpenStack CSI 迁移 特性状态: Kubernetes v1.24 [stable]
自 Kubernetes 1.21 版本起,Cinder 的 CSIMigration
特性是默认被启用的。
此特性会将插件的所有操作从现有的树内插件重定向到
cinder.csi.openstack.org
容器存储接口(CSI)驱动程序。
为了使用此特性,必须在集群中安装
OpenStack Cinder CSI 驱动程序 ,
你可以通过设置 CSIMigrationOpenStack
特性门控
为 false
来禁止 Cinder CSI 迁移。
要禁止控制器管理器和 kubelet 加载树内 Cinder 插件,你可以启用
InTreePluginOpenStackUnregister
特性门控 。
configMap configMap
卷提供了向 Pod 注入配置数据的方法。
ConfigMap 对象中存储的数据可以被 configMap
类型的卷引用,然后被 Pod 中运行的容器化应用使用。
引用 configMap 对象时,你可以在卷中通过它的名称来引用。
你可以自定义 ConfigMap 中特定条目所要使用的路径。
下面的配置显示了如何将名为 log-config
的 ConfigMap 挂载到名为 configmap-pod
的 Pod 中:
apiVersion : v1
kind : Pod
metadata :
name : configmap-pod
spec :
containers :
- name : test
image : busybox:1.28
volumeMounts :
- name : config-vol
mountPath : /etc/config
volumes :
- name : config-vol
configMap :
name : log-config
items :
- key : log_level
path : log_level
log-config
ConfigMap 以卷的形式挂载,并且存储在 log_level
条目中的所有内容都被挂载到 Pod 的 /etc/config/log_level
路径下。
请注意,这个路径来源于卷的 mountPath
和 log_level
键对应的 path
。
说明: 在使用 ConfigMap 之前你首先要创建它。 容器以 subPath 卷挂载方式使用 ConfigMap 时,将无法接收 ConfigMap 的更新。 文本数据挂载成文件时采用 UTF-8 字符编码。如果使用其他字符编码形式,可使用
binaryData
字段。 downwardAPI downwardAPI
卷用于为应用提供 downward API 数据。
在这类卷中,所公开的数据以纯文本格式的只读文件形式存在。
说明: 容器以
subPath 卷挂载方式使用 downward API 时,在字段值更改时将不能接收到它的更新。
更多详细信息请参考通过文件将 Pod 信息呈现给容器 。
emptyDir 当 Pod 分派到某个节点上时,emptyDir
卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。
就像其名称表示的那样,卷最初是空的。
尽管 Pod 中的容器挂载 emptyDir
卷的路径可能相同也可能不同,这些容器都可以读写
emptyDir
卷中相同的文件。
当 Pod 因为某些原因被从节点上删除时,emptyDir
卷中的数据也会被永久删除。
说明: 容器崩溃并不 会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir
卷中的数据是安全的。
emptyDir
的一些用途:
缓存空间,例如基于磁盘的归并排序。 为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。 emptyDir.medium
字段用来控制 emptyDir
卷的存储位置。
默认情况下,emptyDir
卷存储在该节点所使用的介质上;
此处的介质可以是磁盘、SSD 或网络存储,这取决于你的环境。
你可以将 emptyDir.medium
字段设置为 "Memory"
,
以告诉 Kubernetes 为你挂载 tmpfs(基于 RAM 的文件系统)。
虽然 tmpfs 速度非常快,但是要注意它与磁盘不同:tmpfs 在节点重启时会被清除,
并且你所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。
你可以通过为默认介质指定大小限制,来限制 emptyDir
卷的存储容量。
此存储是从节点临时存储 中分配的。
如果来自其他来源(如日志文件或镜像分层数据)的数据占满了存储,emptyDir
可能会在达到此限制之前发生存储容量不足的问题。
说明: 当启用 SizeMemoryBackedVolumes
特性门控 时,
你可以为基于内存提供的卷指定大小。
如果未指定大小,则基于内存的卷的大小为 Linux 主机上内存的 50%。
emptyDir 配置示例 apiVersion : v1
kind : Pod
metadata :
name : test-pd
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /cache
name : cache-volume
volumes :
- name : cache-volume
emptyDir :
sizeLimit : 500Mi
fc (光纤通道) fc
卷类型允许将现有的光纤通道块存储卷挂载到 Pod 中。
可以使用卷配置中的参数 targetWWNs
来指定单个或多个目标 WWN(World Wide Names)。
如果指定了多个 WWN,targetWWNs 期望这些 WWN 来自多路径连接。
说明: 你必须配置 FC SAN Zoning,以便预先向目标 WWN 分配和屏蔽这些 LUN(卷),这样
Kubernetes 主机才可以访问它们。
更多详情请参考 FC 示例 。
gcePersistentDisk(已弃用) 特性状态: Kubernetes v1.17 [deprecated]
gcePersistentDisk
卷能将谷歌计算引擎 (GCE) 持久盘(PD)
挂载到你的 Pod 中。
不像 emptyDir
那样会在 Pod 被删除的同时也会被删除,持久盘卷的内容在删除 Pod
时会被保留,卷只是被卸载了。
这意味着持久盘卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
说明: 在使用 PD 前,你必须使用 gcloud
或者 GCE API 或 UI 创建它。
使用 gcePersistentDisk
时有一些限制:
运行 Pod 的节点必须是 GCE VM 这些 VM 必须和持久盘位于相同的 GCE 项目和区域(zone) GCE PD 的一个特点是它们可以同时被多个消费者以只读方式挂载。
这意味着你可以用数据集预先填充 PD,然后根据需要并行地在尽可能多的 Pod 中提供该数据集。
不幸的是,PD 只能由单个使用者以读写模式挂载,即不允许同时写入。
在由 ReplicationController 所管理的 Pod 上使用 GCE PD 将会失败,除非 PD
是只读模式或者副本的数量是 0 或 1。
创建 GCE 持久盘(PD) 在 Pod 中使用 GCE 持久盘之前,你首先要创建它。
gcloud compute disks create --size= 500GB --zone= us-central1-a my-data-disk
GCE 持久盘配置示例 apiVersion : v1
kind : Pod
metadata :
name : test-pd
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /test-pd
name : test-volume
volumes :
- name : test-volume
# 此 GCE PD 必须已经存在
gcePersistentDisk :
pdName : my-data-disk
fsType : ext4
区域持久盘 区域持久盘 特性允许你创建能在同一区域的两个可用区中使用的持久盘。
要使用这个特性,必须以持久卷(PersistentVolume)的方式提供卷;直接从
Pod 引用这种卷是不可以的。
手动供应基于区域 PD 的 PersistentVolume 使用为 GCE PD 定义的存储类
可以实现动态供应。在创建 PersistentVolume 之前,你首先要创建 PD。
gcloud compute disks create --size= 500GB my-data-disk
--region us-central1
--replica-zones us-central1-a,us-central1-b
区域持久盘配置示例 apiVersion : v1
kind : PersistentVolume
metadata :
name : test-volume
spec :
capacity :
storage : 400Gi
accessModes :
- ReadWriteOnce
gcePersistentDisk :
pdName : my-data-disk
fsType : ext4
nodeAffinity :
required :
nodeSelectorTerms :
- matchExpressions :
# failure-domain.beta.kubernetes.io/zone 应在 1.21 之前使用
- key : topology.kubernetes.io/zone
operator : In
values :
- us-central1-a
- us-central1-b
GCE CSI 迁移 特性状态: Kubernetes v1.25 [stable]
启用 GCE PD 的 CSIMigration
特性后,所有插件操作将从现有的树内插件重定向到
pd.csi.storage.gke.io
容器存储接口(CSI)驱动程序。
为了使用此特性,必须在集群中上安装
GCE PD CSI 驱动程序 。
GCE CSI 迁移完成 特性状态: Kubernetes v1.21 [alpha]
要禁止控制器管理器和 kubelet 加载 gcePersistentDisk
存储插件,请将
InTreePluginGCEUnregister
标志设置为 true
。
gitRepo (已弃用) 警告: gitRepo
卷类型已经被废弃。如果需要在容器中提供 git 仓库,请将一个
EmptyDir 卷挂载到 InitContainer 中,使用 git
命令完成仓库的克隆操作,然后将 EmptyDir 卷挂载到 Pod 的容器中。
gitRepo
卷是一个卷插件的例子。
该查卷挂载一个空目录,并将一个 Git 代码仓库克隆到这个目录中供 Pod 使用。
下面给出一个 gitRepo
卷的示例:
apiVersion : v1
kind : Pod
metadata :
name : server
spec :
containers :
- image : nginx
name : nginx
volumeMounts :
- mountPath : /mypath
name : git-volume
volumes :
- name : git-volume
gitRepo :
repository : "git@somewhere:me/my-git-repository.git"
revision : "22f1d8406d464b0c0874075539c1f2e96c253775"
glusterfs(已弃用) 特性状态: Kubernetes v1.25 [deprecated]
glusterfs
卷能将 Glusterfs (一个开源的网络文件系统)
挂载到你的 Pod 中。不像 emptyDir
那样会在删除 Pod 的同时也会被删除,glusterfs
卷的内容在删除 Pod 时会被保存,卷只是被卸载。
这意味着 glusterfs
卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
GlusterFS 可以被多个写者同时挂载。
说明: 在使用前你必须先安装运行自己的 GlusterFS。
更多详情请参考 GlusterFS 示例 。
hostPath 警告: HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。
当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。
如果通过 AdmissionPolicy 限制 HostPath 对特定目录的访问,则必须要求
volumeMounts
使用 readOnly
挂载以使策略生效。
hostPath
卷能将主机节点文件系统上的文件或目录挂载到你的 Pod 中。
虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。
例如,hostPath
的一些用法有:
运行一个需要访问 Docker 内部机制的容器;可使用 hostPath
挂载 /var/lib/docker
路径。 在容器中运行 cAdvisor 时,以 hostPath
方式挂载 /sys
。 允许 Pod 指定给定的 hostPath
在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。 除了必需的 path
属性之外,你可以选择性地为 hostPath
卷指定 type
。
支持的 type
值如下:
取值 行为 空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查。 DirectoryOrCreate
如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet 相同的组和属主信息。 Directory
在给定路径上必须存在的目录。 FileOrCreate
如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权。 File
在给定路径上必须存在的文件。 Socket
在给定路径上必须存在的 UNIX 套接字。 CharDevice
在给定路径上必须存在的字符设备。 BlockDevice
在给定路径上必须存在的块设备。
当使用这种类型的卷时要小心,因为:
HostPath 卷可能会暴露特权系统凭据(例如 Kubelet)或特权
API(例如容器运行时套接字),可用于容器逃逸或攻击集群的其他部分。 具有相同配置(例如基于同一 PodTemplate 创建)的多个 Pod
会由于节点上文件的不同而在不同节点上有不同的行为。 下层主机上创建的文件或目录只能由 root 用户写入。
你需要在特权容器 中以
root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath
卷。 hostPath 配置示例 apiVersion : v1
kind : Pod
metadata :
name : test-pd
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /test-pd
name : test-volume
volumes :
- name : test-volume
hostPath :
# 宿主上目录位置
path : /data
# 此字段为可选
type : Directory
注意: FileOrCreate
模式不会负责创建文件的父目录。
如果欲挂载的文件的父目录不存在,Pod 启动会失败。
为了确保这种模式能够工作,可以尝试把文件和它对应的目录分开挂载,如
FileOrCreate
配置 所示。
hostPath FileOrCreate 配置示例 apiVersion : v1
kind : Pod
metadata :
name : test-webserver
spec :
containers :
- name : test-webserver
image : registry.k8s.io/test-webserver:latest
volumeMounts :
- mountPath : /var/local/aaa
name : mydir
- mountPath : /var/local/aaa/1.txt
name : myfile
volumes :
- name : mydir
hostPath :
# 确保文件所在目录成功创建。
path : /var/local/aaa
type : DirectoryOrCreate
- name : myfile
hostPath :
path : /var/local/aaa/1.txt
type : FileOrCreate
iscsi iscsi
卷能将 iSCSI (基于 IP 的 SCSI) 卷挂载到你的 Pod 中。
不像 emptyDir
那样会在删除 Pod 的同时也会被删除,iscsi
卷的内容在删除 Pod 时会被保留,卷只是被卸载。
这意味着 iscsi
卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
说明: 在使用 iSCSI 卷之前,你必须拥有自己的 iSCSI 服务器,并在上面创建卷。
iSCSI 的一个特点是它可以同时被多个用户以只读方式挂载。
这意味着你可以用数据集预先填充卷,然后根据需要在尽可能多的 Pod 上使用它。
不幸的是,iSCSI 卷只能由单个使用者以读写模式挂载。不允许同时写入。
更多详情请参考 iSCSI 示例 。
local local
卷所代表的是某个被挂载的本地存储设备,例如磁盘、分区或者目录。
local
卷只能用作静态创建的持久卷。不支持动态配置。
与 hostPath
卷相比,local
卷能够以持久和可移植的方式使用,而无需手动将 Pod
调度到节点。系统通过查看 PersistentVolume 的节点亲和性配置,就能了解卷的节点约束。
然而,local
卷仍然取决于底层节点的可用性,并不适合所有应用程序。
如果节点变得不健康,那么 local
卷也将变得不可被 Pod 访问。使用它的 Pod 将不能运行。
使用 local
卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征而带来的潜在的数据丢失风险。
下面是一个使用 local
卷和 nodeAffinity
的持久卷示例:
apiVersion : v1
kind : PersistentVolume
metadata :
name : example-pv
spec :
capacity :
storage : 100Gi
volumeMode : Filesystem
accessModes :
- ReadWriteOnce
persistentVolumeReclaimPolicy : Delete
storageClassName : local-storage
local :
path : /mnt/disks/ssd1
nodeAffinity :
required :
nodeSelectorTerms :
- matchExpressions :
- key : kubernetes.io/hostname
operator : In
values :
- example-node
使用 local
卷时,你需要设置 PersistentVolume 对象的 nodeAffinity
字段。
Kubernetes 调度器使用 PersistentVolume 的 nodeAffinity
信息来将使用 local
卷的 Pod 调度到正确的节点。
PersistentVolume 对象的 volumeMode
字段可被设置为 "Block"
(而不是默认值 "Filesystem"),以将 local
卷作为原始块设备暴露出来。
使用 local
卷时,建议创建一个 StorageClass 并将其 volumeBindingMode
设置为
WaitForFirstConsumer
。要了解更多详细信息,请参考
local StorageClass 示例 。
延迟卷绑定的操作可以确保 Kubernetes 在为 PersistentVolumeClaim 作出绑定决策时,会评估
Pod 可能具有的其他节点约束,例如:如节点资源需求、节点选择器、Pod 亲和性和 Pod 反亲和性。
你可以在 Kubernetes 之外单独运行静态驱动以改进对 local 卷的生命周期管理。
请注意,此驱动尚不支持动态配置。
有关如何运行外部 local
卷驱动,请参考
local 卷驱动用户指南 。
说明: 如果不使用外部静态驱动来管理卷的生命周期,用户需要手动清理和删除 local 类型的持久卷。
nfs nfs
卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。
不像 emptyDir
那样会在删除 Pod 的同时也会被删除,nfs
卷的内容在删除 Pod
时会被保存,卷只是被卸载。
这意味着 nfs
卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
apiVersion : v1
kind : Pod
metadata :
name : test-pd
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /my-nfs-data
name : test-volume
volumes :
- name : test-volume
nfs :
server : my-nfs-server.example.com
path : /my-nfs-volume
readOnly : true
说明: 在使用 NFS 卷之前,你必须运行自己的 NFS 服务器并将目标 share 导出备用。
还需要注意,不能在 Pod spec 中指定 NFS 挂载可选项。
可以选择设置服务端的挂载可选项,或者使用
/etc/nfsmount.conf 。
此外,还可以通过允许设置挂载可选项的持久卷挂载 NFS 卷。
如需了解用持久卷挂载 NFS 卷的示例,请参考 NFS 示例 。
persistentVolumeClaim persistentVolumeClaim
卷用来将持久卷 (PersistentVolume)挂载到 Pod 中。
持久卷申领(PersistentVolumeClaim)是用户在不知道特定云环境细节的情况下“申领”持久存储(例如
GCE PersistentDisk 或者 iSCSI 卷)的一种方法。
更多详情请参考持久卷 。
portworxVolume(已弃用) 特性状态: Kubernetes v1.25 [deprecated]
portworxVolume
是一个可伸缩的块存储层,能够以超融合(hyperconverged)的方式与 Kubernetes 一起运行。
Portworx
支持对服务器上存储的指纹处理、基于存储能力进行分层以及跨多个服务器整合存储容量。
Portworx 可以以 in-guest 方式在虚拟机中运行,也可以在裸金属 Linux 节点上运行。
portworxVolume
类型的卷可以通过 Kubernetes 动态创建,也可以预先配备并在 Pod 内引用。
下面是一个引用预先配备的 Portworx 卷的示例 Pod:
apiVersion : v1
kind : Pod
metadata :
name : test-portworx-volume-pod
spec :
containers :
- image : registry.k8s.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /mnt
name : pxvol
volumes :
- name : pxvol
# 此 Portworx 卷必须已经存在
portworxVolume :
volumeID : "pxvol"
fsType : "<fs-type>"
说明: 在 Pod 中使用 portworxVolume 之前,你要确保有一个名为 pxvol
的 PortworxVolume 存在。
更多详情可以参考 Portworx 卷 。
Portworx CSI 迁移 特性状态: Kubernetes v1.25 [beta]
已针对 Portworx 添加 CSIMigration
特性,但在 Kubernetes 1.23 中默认禁用,因为它处于 Alpha 状态。
自 v1.25 以来它已进入 Beta 阶段,但默认仍关闭。
它将所有插件操作不再指向树内插件(In-Tree Plugin),转而指向
pxd.portworx.com
容器存储接口(Container Storage Interface,CSI)驱动。
Portworx CSI 驱动程序 必须安装在集群上。
要启用此特性,需在 kube-controller-manager 和 kubelet 中设置 CSIMigrationPortworx=true
。
projected (投射) 投射卷能将若干现有的卷来源映射到同一目录上。更多详情请参考投射卷 。
rbd rbd
卷允许将 Rados 块设备 卷挂载到你的 Pod 中。
不像 emptyDir
那样会在删除 Pod 的同时也会被删除,rbd
卷的内容在删除 Pod 时会被保存,卷只是被卸载。
这意味着 rbd
卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
说明: 在使用 RBD 之前,你必须安装运行 Ceph。
RBD 的一个特性是它可以同时被多个用户以只读方式挂载。
这意味着你可以用数据集预先填充卷,然后根据需要在尽可能多的 Pod 中并行地使用卷。
不幸的是,RBD 卷只能由单个使用者以读写模式安装。不允许同时写入。
更多详情请参考
RBD 示例 。
RBD CSI 迁移 特性状态: Kubernetes v1.23 [alpha]
启用 RBD 的 CSIMigration
特性后,所有插件操作从现有的树内插件重定向到
rbd.csi.ceph.com
CSI 驱动程序。
要使用该特性,必须在集群内安装
Ceph CSI 驱动 ,并启用 csiMigrationRBD
特性门控 。
(请注意,csiMigrationRBD
标志已在 v1.24 版本中移除且替换为 CSIMigrationRBD
。)
说明: 作为一位管理存储的 Kubernetes 集群操作者,在尝试迁移到 RBD CSI 驱动前,你必须完成下列先决事项:
你必须在集群中安装 v3.5.0 或更高版本的 Ceph CSI 驱动(rbd.csi.ceph.com
)。 因为 clusterID
是 CSI 驱动程序必需的参数,而树内存储类又将 monitors
作为一个必需的参数,所以 Kubernetes 存储管理者需要根据 monitors
的哈希值(例:#echo -n '<monitors_string>' | md5sum
)来创建
clusterID
,并保持该 monitors
存在于该 clusterID
的配置中。 同时,如果树内存储类的 adminId
的值不是 admin
,那么其 adminSecretName
就需要被修改成 adminId
参数的 base64 编码值。 secret secret
卷用来给 Pod 传递敏感信息,例如密码。你可以将 Secret 存储在 Kubernetes
API 服务器上,然后以文件的形式挂载到 Pod 中,无需直接与 Kubernetes 耦合。
secret
卷由 tmpfs(基于 RAM 的文件系统)提供存储,因此它们永远不会被写入非易失性(持久化的)存储器。
说明: 使用前你必须在 Kubernetes API 中创建 Secret。
说明: 容器以 subPath
卷挂载方式挂载 Secret 时,将感知不到 Secret 的更新。
更多详情请参考配置 Secrets 。
vsphereVolume(已弃用) 说明: 建议你改用 vSphere CSI 树外驱动程序。
vsphereVolume
用来将 vSphere VMDK 卷挂载到你的 Pod 中。
在卸载卷时,卷的内容会被保留。
vSphereVolume 卷类型支持 VMFS 和 VSAN 数据仓库。
进一步信息可参考
vSphere 卷 。
vSphere CSI 迁移 特性状态: Kubernetes v1.19 [beta]
从 Kubernetes v1.25 开始,针对 vsphereVolume
的 CSIMigrationvSphere
特性默认被启用。
来自树内 vspherevolume
的所有插件操作将被重新指向到
csi.vsphere.vmware.com
CSI 驱动,
除非 CSIMigrationvSphere
特性门控被禁用。
vSphere CSI 驱动 必须安装到集群上。
你可以在 VMware 的文档页面迁移树内 vSphere 卷插件到 vSphere 容器存储插件
中找到有关如何迁移树内 vsphereVolume
的其他建议。
从 Kubernetes v1.25 开始,(已弃用)树内 vSphere 存储驱动不支持低于 7.0u2 的 vSphere 版本。
你必须运行 vSphere 7.0u2 或更高版本才能继续使用这个已弃用的驱动,或迁移到替代的 CSI 驱动。
如果你正在运行 Kubernetes v1.25,请查阅该 Kubernetes 版本的文档。
说明: vSphere CSI 驱动不支持内置 vsphereVolume
的以下 StorageClass 参数:
diskformat
hostfailurestotolerate
forceprovisioning
cachereservation
diskstripes
objectspacereservation
iopslimit
使用这些参数创建的现有卷将被迁移到 vSphere CSI 驱动,不过使用 vSphere
CSI 驱动所创建的新卷都不会理会这些参数。
vSphere CSI 迁移完成 特性状态: Kubernetes v1.19 [beta]
为了避免控制器管理器和 kubelet 加载 vsphereVolume
插件,你需要将
InTreePluginvSphereUnregister
特性设置为 true
。你还必须在所有工作节点上安装
csi.vsphere.vmware.com
CSI 驱动。
使用 subPath 有时,在单个 Pod 中共享卷以供多方使用是很有用的。
volumeMounts.subPath
属性可用于指定所引用的卷内的子路径,而不是其根路径。
下面例子展示了如何配置某包含 LAMP 堆栈(Linux Apache MySQL PHP)的 Pod 使用同一共享卷。
此示例中的 subPath
配置不建议在生产环境中使用。
PHP 应用的代码和相关数据映射到卷的 html
文件夹,MySQL 数据库存储在卷的 mysql
文件夹中:
apiVersion : v1
kind : Pod
metadata :
name : my-lamp-site
spec :
containers :
- name : mysql
image : mysql
env :
- name : MYSQL_ROOT_PASSWORD
value : "rootpasswd"
volumeMounts :
- mountPath : /var/lib/mysql
name : site-data
subPath : mysql
- name : php
image : php:7.0-apache
volumeMounts :
- mountPath : /var/www/html
name : site-data
subPath : html
volumes :
- name : site-data
persistentVolumeClaim :
claimName : my-lamp-site-data
使用带有扩展环境变量的 subPath 特性状态: Kubernetes v1.17 [stable]
使用 subPathExpr
字段可以基于 downward API 环境变量来构造 subPath
目录名。
subPath
和 subPathExpr
属性是互斥的。
在这个示例中,Pod
使用 subPathExpr
来 hostPath
卷 /var/log/pods
中创建目录 pod1
。
hostPath
卷采用来自 downwardAPI
的 Pod 名称生成目录名。
宿主目录 /var/log/pods/pod1
被挂载到容器的 /logs
中。
apiVersion : v1
kind : Pod
metadata :
name : pod1
spec :
containers :
- name : container1
env :
- name : POD_NAME
valueFrom :
fieldRef :
apiVersion : v1
fieldPath : metadata.name
image : busybox:1.28
command : [ "sh" , "-c" , "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
volumeMounts :
- name : workdir1
mountPath : /logs
# 包裹变量名的是小括号,而不是大括号
subPathExpr : $(POD_NAME)
restartPolicy : Never
volumes :
- name : workdir1
hostPath :
path : /var/log/pods
资源 emptyDir
卷的存储介质(例如磁盘、SSD 等)是由保存 kubelet
数据的根目录(通常是 /var/lib/kubelet
)的文件系统的介质确定。
Kubernetes 对 emptyDir
卷或者 hostPath
卷可以消耗的空间没有限制,容器之间或 Pod 之间也没有隔离。
要了解如何使用资源规约来请求空间,
可参考如何管理资源 。
树外(Out-of-Tree)卷插件 Out-of-Tree 卷插件包括容器存储接口(CSI) 和
FlexVolume(已弃用)。它们使存储供应商能够创建自定义存储插件,而无需将插件源码添加到
Kubernetes 代码仓库。
以前,所有卷插件(如上面列出的卷类型)都是“树内(In-Tree)”的。
“树内”插件是与 Kubernetes 的核心组件一同构建、链接、编译和交付的。
这意味着向 Kubernetes 添加新的存储系统(卷插件)需要将代码合并到 Kubernetes 核心代码库中。
CSI 和 FlexVolume 都允许独立于 Kubernetes 代码库开发卷插件,并作为扩展部署(安装)在 Kubernetes 集群上。
对于希望创建树外(Out-Of-Tree)卷插件的存储供应商,请参考
卷插件常见问题 。
CSI 容器存储接口 (CSI)
为容器编排系统(如 Kubernetes)定义标准接口,以将任意存储系统暴露给它们的容器工作负载。
更多详情请阅读 CSI 设计方案 。
说明: Kubernetes v1.13 废弃了对 CSI 规范版本 0.2 和 0.3 的支持,并将在以后的版本中删除。
说明: CSI 驱动可能并非兼容所有的 Kubernetes 版本。
请查看特定 CSI 驱动的文档,以了解各个 Kubernetes 版本所支持的部署步骤以及兼容性列表。
一旦在 Kubernetes 集群上部署了 CSI 兼容卷驱动程序,用户就可以使用
csi
卷类型来挂接、挂载 CSI 驱动所提供的卷。
csi
卷可以在 Pod 中以三种方式使用:
通过 PersistentVolumeClaim(#persistentvolumeclaim) 对象引用 使用一般性的临时卷 使用 CSI 临时卷 ,
前提是驱动支持这种用法 存储管理员可以使用以下字段来配置 CSI 持久卷:
driver
:指定要使用的卷驱动名称的字符串值。
这个值必须与 CSI 驱动程序在 GetPluginInfoResponse
中返回的值相对应;该接口定义在
CSI 规范 中。
Kubernetes 使用所给的值来标识要调用的 CSI 驱动程序;CSI
驱动程序也使用该值来辨识哪些 PV 对象属于该 CSI 驱动程序。volumeHandle
:唯一标识卷的字符串值。
该值必须与 CSI 驱动在 CreateVolumeResponse
的 volume_id
字段中返回的值相对应;接口定义在
CSI 规范 中。
在所有对 CSI 卷驱动程序的调用中,引用该 CSI 卷时都使用此值作为 volume_id
参数。readOnly
:一个可选的布尔值,指示通过 ControllerPublished
关联该卷时是否设置该卷为只读。默认值是 false。
该值通过 ControllerPublishVolumeRequest
中的 readonly
字段传递给 CSI 驱动。fsType
:如果 PV 的 VolumeMode
为 Filesystem
,那么此字段指定挂载卷时应该使用的文件系统。
如果卷尚未格式化,并且支持格式化,此值将用于格式化卷。
此值可以通过 ControllerPublishVolumeRequest
、NodeStageVolumeRequest
和
NodePublishVolumeRequest
的 VolumeCapability
字段传递给 CSI 驱动。volumeAttributes
:一个字符串到字符串的映射表,用来设置卷的静态属性。
该映射必须与 CSI 驱动程序返回的 CreateVolumeResponse
中的 volume.attributes
字段的映射相对应;
CSI 规范 中有相应的定义。
该映射通过ControllerPublishVolumeRequest
、NodeStageVolumeRequest
、和
NodePublishVolumeRequest
中的 volume_context
字段传递给 CSI 驱动。controllerPublishSecretRef
:对包含敏感信息的 Secret 对象的引用;
该敏感信息会被传递给 CSI 驱动来完成 CSI ControllerPublishVolume
和
ControllerUnpublishVolume
调用。
此字段是可选的;在不需要 Secret 时可以是空的。
如果 Secret 包含多个 Secret 条目,则所有的 Secret 条目都会被传递。nodeExpandSecretRef
:对包含敏感信息的 Secret 对象的引用,
该信息会传递给 CSI 驱动以完成 CSI NodeExpandVolume
调用。
此字段是可选的,如果不需要 Secret,则可能是空的。
如果 Secret 包含多个 Secret 条目,则传递所有 Secret 条目。
当你为节点初始化的卷扩展配置 Secret 数据时,kubelet 会通过 NodeExpandVolume()
调用将该数据传递给 CSI 驱动。
为了使用 nodeExpandSecretRef
字段,你的集群应运行 Kubernetes 1.25 或更高版本,
并且你必须为每个 kube-apiserver 和每个节点上的 kubelet 启用名为 CSINodeExpandSecret
的特性门控 。
在节点初始化的存储大小调整操作期间,你还必须使用支持或需要 Secret 数据的 CSI 驱动。nodePublishSecretRef
:对包含敏感信息的 Secret 对象的引用。
该信息传递给 CSI 驱动来完成 CSI NodePublishVolume
调用。
此字段是可选的,如果不需要 Secret,则可能是空的。
如果 Secret 对象包含多个 Secret 条目,则传递所有 Secret 条目。nodeStageSecretRef
:对包含敏感信息的 Secret 对象的引用,
该信息会传递给 CSI 驱动以完成 CSI NodeStageVolume
调用。
此字段是可选的,如果不需要 Secret,则可能是空的。
如果 Secret 包含多个 Secret 条目,则传递所有 Secret 条目。CSI 原始块卷支持 特性状态: Kubernetes v1.18 [stable]
具有外部 CSI 驱动程序的供应商能够在 Kubernetes 工作负载中实现原始块卷支持。
你可以和以前一样,
安装自己的带有原始块卷支持的 PV/PVC ,
采用 CSI 对此过程没有影响。
CSI 临时卷 特性状态: Kubernetes v1.25 [stable]
你可以直接在 Pod 规约中配置 CSI 卷。采用这种方式配置的卷都是临时卷,
无法在 Pod 重新启动后继续存在。
进一步的信息可参阅临时卷 。
有关如何开发 CSI 驱动的更多信息,请参考 kubernetes-csi 文档 。
Windows CSI 代理 特性状态: Kubernetes v1.22 [stable]
CSI 节点插件需要执行多种特权操作,例如扫描磁盘设备和挂载文件系统等。
这些操作在每个宿主操作系统上都是不同的。对于 Linux 工作节点而言,容器化的 CSI
节点插件通常部署为特权容器。对于 Windows 工作节点而言,容器化 CSI
节点插件的特权操作是通过 csi-proxy
来支持的。csi-proxy 是一个由社区管理的、独立的可执行二进制文件,
需要被预安装到每个 Windows 节点上。
要了解更多的细节,可以参考你要部署的 CSI 插件的部署指南。
从树内插件迁移到 CSI 驱动程序 特性状态: Kubernetes v1.25 [stable]
CSIMigration
特性针对现有树内插件的操作会被定向到相应的 CSI 插件(应已安装和配置)。
因此,操作员在过渡到取代树内插件的 CSI 驱动时,无需对现有存储类、PV 或 PVC(指树内插件)进行任何配置更改。
所支持的操作和特性包括:配备(Provisioning)/删除、挂接(Attach)/解挂(Detach)、
挂载(Mount)/卸载(Unmount)和调整卷大小。
上面的卷类型 节列出了支持 CSIMigration
并已实现相应 CSI
驱动程序的树内插件。
下面是支持 Windows 节点上持久性存储的树内插件:
flexVolume(已弃用) 特性状态: Kubernetes v1.23 [deprecated]
FlexVolume 是一个使用基于 exec 的模型来与驱动程序对接的树外插件接口。
用户必须在每个节点上的预定义卷插件路径中安装 FlexVolume
驱动程序可执行文件,在某些情况下,控制平面节点中也要安装。
Pod 通过 flexvolume
树内插件与 FlexVolume 驱动程序交互。
更多详情请参考 FlexVolume README 文档。
下面的 FlexVolume 插件
以 PowerShell 脚本的形式部署在宿主系统上,支持 Windows 节点:
说明: FlexVolume 已被弃用。推荐使用树外 CSI 驱动来将外部存储整合进 Kubernetes。
FlexVolume 驱动的维护者应开发一个 CSI 驱动并帮助用户从 FlexVolume 驱动迁移到 CSI。
FlexVolume 用户应迁移工作负载以使用对等的 CSI 驱动。
挂载卷的传播 挂载卷的传播能力允许将容器安装的卷共享到同一 Pod 中的其他容器,甚至共享到同一节点上的其他 Pod。
卷的挂载传播特性由 Container.volumeMounts
中的 mountPropagation
字段控制。
它的值包括:
HostToContainer
- 此卷挂载将会感知到主机后续针对此卷或其任何子目录的挂载操作。
换句话说,如果主机在此挂载卷中挂载任何内容,容器将能看到它被挂载在那里。
类似的,配置了 Bidirectional
挂载传播选项的 Pod 如果在同一卷上挂载了内容,挂载传播设置为
HostToContainer
的容器都将能看到这一变化。
该模式等同于 Linux 内核文档 中描述的
rslave
挂载传播选项。
配置 在某些部署环境中,挂载传播正常工作前,必须在 Docker 中正确配置挂载共享(mount share),如下所示。
编辑你的 Docker systemd
服务文件,按下面的方法设置 MountFlags
:
或者,如果存在 MountFlags=slave
就删除掉。然后重启 Docker 守护进程:
sudo systemctl daemon-reload
sudo systemctl restart docker
接下来 参考使用持久卷部署 WordPress 和 MySQL 示例。
3.7.2 - 持久卷 本文描述 Kubernetes 中的持久卷(Persistent Volume) 。
建议先熟悉卷(Volume) 的概念。
介绍 存储的管理是一个与计算实例的管理完全不同的问题。
PersistentVolume 子系统为用户和管理员提供了一组 API,
将存储如何制备的细节从其如何被使用中抽象出来。
为了实现这点,我们引入了两个新的 API 资源:PersistentVolume 和
PersistentVolumeClaim。
持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先制备,
或者使用存储类(Storage Class) 来动态制备。
持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样,
也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。
此 API 对象中记述了存储的实现细节,无论其背后是 NFS、iSCSI 还是特定于云平台的存储系统。
持久卷申领(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。
Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU
和内存);同样 PVC 申领也可以请求特定的大小和访问模式
(例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany
模式之一来挂载,参见访问模式 )。
尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源,
常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。
集群管理员需要能够提供不同性质的 PersistentVolume,
并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。
为了满足这类需求,就有了存储类(StorageClass) 资源。
参见基于运行示例的详细演练 。
卷和申领的生命周期 PV 卷是集群中的资源。PVC 申领是对这些资源的请求,也被用来执行对资源的申领检查。
PV 卷和 PVC 申领之间的互动遵循如下生命周期:
制备 PV 卷的制备有两种方式:静态制备或动态制备。
静态制备 集群管理员创建若干 PV 卷。这些卷对象带有真实存储的细节信息,
并且对集群用户可用(可见)。PV 卷对象存在于 Kubernetes API 中,可供用户消费(使用)。
动态制备 如果管理员所创建的所有静态 PV 卷都无法与用户的 PersistentVolumeClaim 匹配,
集群可以尝试为该 PVC 申领动态制备一个存储卷。
这一制备操作是基于 StorageClass 来实现的:PVC 申领必须请求某个
存储类 ,
同时集群管理员必须已经创建并配置了该类,这样动态制备卷的动作才会发生。
如果 PVC 申领指定存储类为 ""
,则相当于为自身禁止使用动态制备的卷。
为了基于存储类完成动态的存储制备,集群管理员需要在 API 服务器上启用
DefaultStorageClass
准入控制器 。
举例而言,可以通过保证 DefaultStorageClass
出现在 API 服务器组件的
--enable-admission-plugins
标志值中实现这点;该标志的值可以是逗号分隔的有序列表。
关于 API 服务器标志的更多信息,可以参考
kube-apiserver
文档。
绑定 用户创建一个带有特定存储容量和特定访问模式需求的 PersistentVolumeClaim 对象;
在动态制备场景下,这个 PVC 对象可能已经创建完毕。
主控节点中的控制回路监测新的 PVC 对象,寻找与之匹配的 PV 卷(如果可能的话),
并将二者绑定到一起。
如果为了新的 PVC 申领动态制备了 PV 卷,则控制回路总是将该 PV 卷绑定到这一 PVC 申领。
否则,用户总是能够获得他们所请求的资源,只是所获得的 PV 卷可能会超出所请求的配置。
一旦绑定关系建立,则 PersistentVolumeClaim 绑定就是排他性的,
无论该 PVC 申领是如何与 PV 卷建立的绑定关系。
PVC 申领与 PV 卷之间的绑定是一种一对一的映射,实现上使用 ClaimRef 来记述
PV 卷与 PVC 申领间的双向绑定关系。
如果找不到匹配的 PV 卷,PVC 申领会无限期地处于未绑定状态。
当与之匹配的 PV 卷可用时,PVC 申领会被绑定。
例如,即使某集群上制备了很多 50 Gi 大小的 PV 卷,也无法与请求
100 Gi 大小的存储的 PVC 匹配。当新的 100 Gi PV 卷被加入到集群时,
该 PVC 才有可能被绑定。
使用 Pod 将 PVC 申领当做存储卷来使用。集群会检视 PVC 申领,找到所绑定的卷,
并为 Pod 挂载该卷。对于支持多种访问模式的卷,
用户要在 Pod 中以卷的形式使用申领时指定期望的访问模式。
一旦用户有了申领对象并且该申领已经被绑定,
则所绑定的 PV 卷在用户仍然需要它期间一直属于该用户。
用户通过在 Pod 的 volumes
块中包含 persistentVolumeClaim
节区来调度 Pod,访问所申领的 PV 卷。
相关细节可参阅使用申领作为卷 。
保护使用中的存储对象 保护使用中的存储对象(Storage Object in Use Protection)
这一功能特性的目的是确保仍被 Pod 使用的 PersistentVolumeClaim(PVC)
对象及其所绑定的 PersistentVolume(PV)对象在系统中不会被删除,因为这样做可能会引起数据丢失。
说明: 当使用某 PVC 的 Pod 对象仍然存在时,认为该 PVC 仍被此 Pod 使用。
如果用户删除被某 Pod 使用的 PVC 对象,该 PVC 申领不会被立即移除。
PVC 对象的移除会被推迟,直至其不再被任何 Pod 使用。
此外,如果管理员删除已绑定到某 PVC 申领的 PV 卷,该 PV 卷也不会被立即移除。
PV 对象的移除也要推迟到该 PV 不再绑定到 PVC。
你可以看到当 PVC 的状态为 Terminating
且其 Finalizers
列表中包含
kubernetes.io/pvc-protection
时,PVC 对象是处于被保护状态的。
kubectl describe pvc hostpath
Name: hostpath
Namespace: default
StorageClass: example-hostpath
Status: Terminating
Volume:
Labels: <none>
Annotations: volume.beta.kubernetes.io/storage-class=example-hostpath
volume.beta.kubernetes.io/storage-provisioner=example.com/hostpath
Finalizers: [kubernetes.io/pvc-protection]
...
你也可以看到当 PV 对象的状态为 Terminating
且其 Finalizers
列表中包含
kubernetes.io/pv-protection
时,PV 对象是处于被保护状态的。
kubectl describe pv task-pv-volume
Name: task-pv-volume
Labels: type=local
Annotations: <none>
Finalizers: [kubernetes.io/pv-protection]
StorageClass: standard
Status: Terminating
Claim:
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 1Gi
Message:
Source:
Type: HostPath (bare host directory volume)
Path: /tmp/data
HostPathType:
Events: <none>
回收(Reclaiming) 当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除,
从而允许该资源被回收再利用。PersistentVolume 对象的回收策略告诉集群,
当其被从申领中释放时如何处理该数据卷。
目前,数据卷可以被 Retained(保留)、Recycled(回收)或 Deleted(删除)。
保留(Retain) 回收策略 Retain
使得用户可以手动回收资源。当 PersistentVolumeClaim
对象被删除时,PersistentVolume 卷仍然存在,对应的数据卷被视为"已释放(released)"。
由于卷上仍然存在这前一申领人的数据,该卷还不能用于其他申领。
管理员可以通过下面的步骤来手动回收该卷:
删除 PersistentVolume 对象。与之相关的、位于外部基础设施中的存储资产
(例如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)在 PV 删除之后仍然存在。 根据情况,手动清除所关联的存储资产上的数据。 手动删除所关联的存储资产。 如果你希望重用该存储资产,可以基于存储资产的定义创建新的 PersistentVolume 卷对象。
删除(Delete) 对于支持 Delete
回收策略的卷插件,删除动作会将 PersistentVolume 对象从
Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或
Cinder 卷)中移除所关联的存储资产。
动态制备的卷会继承其 StorageClass 中设置的回收策略 ,
该策略默认为 Delete
。管理员需要根据用户的期望来配置 StorageClass;
否则 PV 卷被创建之后必须要被编辑或者修补。
参阅更改 PV 卷的回收策略 。
回收(Recycle) 警告: 回收策略 Recycle
已被废弃。取而代之的建议方案是使用动态制备。
如果下层的卷插件支持,回收策略 Recycle
会在卷上执行一些基本的擦除
(rm -rf /thevolume/*
)操作,之后允许该卷用于新的 PVC 申领。
不过,管理员可以按
参考资料
中所述,使用 Kubernetes 控制器管理器命令行参数来配置一个定制的回收器(Recycler)
Pod 模板。此定制的回收器 Pod 模板必须包含一个 volumes
规约,如下例所示:
apiVersion : v1
kind : Pod
metadata :
name : pv-recycler
namespace : default
spec :
restartPolicy : Never
volumes :
- name : vol
hostPath :
path : /any/path/it/will/be/replaced
containers :
- name : pv-recycler
image : "registry.k8s.io/busybox"
command : ["/bin/sh" , "-c" , "test -e /scrub && rm -rf /scrub/..?* /scrub/.[!.]* /scrub/* && test -z \"$(ls -A /scrub)\" || exit 1" ]
volumeMounts :
- name : vol
mountPath : /scrub
定制回收器 Pod 模板中在 volumes
部分所指定的特定路径要替换为正被回收的卷的路径。
PersistentVolume 删除保护 finalizer 特性状态: Kubernetes v1.23 [alpha]
可以在 PersistentVolume 上添加终结器(Finalizer),
以确保只有在删除对应的存储后才删除具有 Delete
回收策略的 PersistentVolume。
新引入的 kubernetes.io/pv-controller
和 external-provisioner.volume.kubernetes.io/finalizer
终结器仅会被添加到动态制备的卷上。
终结器 kubernetes.io/pv-controller
会被添加到树内插件卷上。
下面是一个例子:
kubectl describe pv pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Name: pvc-74a498d6-3929-47e8-8c02-078c1ece4d78
Labels: <none>
Annotations: kubernetes.io/createdby: vsphere-volume-dynamic-provisioner
pv.kubernetes.io/bound-by-controller: yes
pv.kubernetes.io/provisioned-by: kubernetes.io/vsphere-volume
Finalizers: [ kubernetes.io/pv-protection kubernetes.io/pv-controller]
StorageClass: vcp-sc
Status: Bound
Claim: default/vcp-pvc-1
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 1Gi
Node Affinity: <none>
Message:
Source:
Type: vSphereVolume ( a Persistent Disk resource in vSphere)
VolumePath: [ vsanDatastore] d49c4a62-166f-ce12-c464-020077ba5d46/kubernetes-dynamic-pvc-74a498d6-3929-47e8-8c02-078c1ece4d78.vmdk
FSType: ext4
StoragePolicyName: vSAN Default Storage Policy
Events: <none>
终结器 external-provisioner.volume.kubernetes.io/finalizer
会被添加到 CSI 卷上。下面是一个例子:
Name: pvc-2f0bab97-85a8-4552-8044-eb8be45cf48d
Labels: <none>
Annotations: pv.kubernetes.io/provisioned-by: csi.vsphere.vmware.com
Finalizers: [ kubernetes.io/pv-protection external-provisioner.volume.kubernetes.io/finalizer]
StorageClass: fast
Status: Bound
Claim: demo-app/nginx-logs
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 200Mi
Node Affinity: <none>
Message:
Source:
Type: CSI ( a Container Storage Interface ( CSI) volume source )
Driver: csi.vsphere.vmware.com
FSType: ext4
VolumeHandle: 44830fa8-79b4-406b-8b58-621ba25353fd
ReadOnly: false
VolumeAttributes: storage.kubernetes.io/csiProvisionerIdentity= 1648442357185-8081-csi.vsphere.vmware.com
type = vSphere CNS Block Volume
Events: <none>
当为特定的树内卷插件启用了 CSIMigration{provider}
特性标志时,kubernetes.io/pv-controller
终结器将被替换为 external-provisioner.volume.kubernetes.io/finalizer
终结器。
预留 PersistentVolume 控制平面可以在集群中将 PersistentVolumeClaims 绑定到匹配的 PersistentVolumes 。
但是,如果你希望 PVC 绑定到特定 PV,则需要预先绑定它们。
通过在 PersistentVolumeClaim 中指定 PersistentVolume,你可以声明该特定
PV 与 PVC 之间的绑定关系。如果该 PersistentVolume 存在且未被通过其
claimRef
字段预留给 PersistentVolumeClaim,则该 PersistentVolume
会和该 PersistentVolumeClaim 绑定到一起。
绑定操作不会考虑某些卷匹配条件是否满足,包括节点亲和性等等。
控制面仍然会检查存储类 、
访问模式和所请求的存储尺寸都是合法的。
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : foo-pvc
namespace : foo
spec :
storageClassName : "" # 此处须显式设置空字符串,否则会被设置为默认的 StorageClass
volumeName : foo-pv
...
此方法无法对 PersistentVolume 的绑定特权做出任何形式的保证。
如果有其他 PersistentVolumeClaim 可以使用你所指定的 PV,
则你应该首先预留该存储卷。你可以将 PV 的 claimRef
字段设置为相关的
PersistentVolumeClaim 以确保其他 PVC 不会绑定到该 PV 卷。
apiVersion : v1
kind : PersistentVolume
metadata :
name : foo-pv
spec :
storageClassName : ""
claimRef :
name : foo-pvc
namespace : foo
...
如果你想要使用 claimPolicy
属性设置为 Retain
的 PersistentVolume 卷时,
包括你希望复用现有的 PV 卷时,这点是很有用的
扩充 PVC 申领 特性状态: Kubernetes v1.24 [stable]
现在,对扩充 PVC 申领的支持默认处于被启用状态。你可以扩充以下类型的卷:
azureDisk azureFile awsElasticBlockStore cinder (deprecated) csi flexVolume (deprecated) gcePersistentDisk glusterfs rbd portworxVolume 只有当 PVC 的存储类中将 allowVolumeExpansion
设置为 true 时,你才可以扩充该 PVC 申领。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : gluster-vol-default
provisioner : kubernetes.io/glusterfs
parameters :
resturl : "http://192.168.10.100:8080"
restuser : ""
secretNamespace : ""
secretName : ""
allowVolumeExpansion : true
如果要为某 PVC 请求较大的存储卷,可以编辑 PVC 对象,设置一个更大的尺寸值。
这一编辑操作会触发为下层 PersistentVolume 提供存储的卷的扩充。
Kubernetes 不会创建新的 PV 卷来满足此申领的请求。
与之相反,现有的卷会被调整大小。
警告: 直接编辑 PersistentVolume 的大小可以阻止该卷自动调整大小。
如果对 PersistentVolume 的容量进行编辑,然后又将其所对应的
PersistentVolumeClaim 的 .spec
进行编辑,使该 PersistentVolumeClaim
的大小匹配 PersistentVolume 的话,则不会发生存储大小的调整。
Kubernetes 控制平面将看到两个资源的所需状态匹配,
并认为其后备卷的大小已被手动增加,无需调整。
CSI 卷的扩充 特性状态: Kubernetes v1.24 [stable]
对 CSI 卷的扩充能力默认是被启用的,不过扩充 CSI 卷要求 CSI
驱动支持卷扩充操作。可参阅特定 CSI 驱动的文档了解更多信息。
重设包含文件系统的卷的大小 只有卷中包含的文件系统是 XFS、Ext3 或者 Ext4 时,你才可以重设卷的大小。
当卷中包含文件系统时,只有在 Pod 使用 ReadWrite
模式来使用 PVC
申领的情况下才能重设其文件系统的大小。文件系统扩充的操作或者是在 Pod
启动期间完成,或者在下层文件系统支持在线扩充的前提下在 Pod 运行期间完成。
如果 FlexVolumes 的驱动将 RequiresFSResize
能力设置为 true
,
则该 FlexVolume 卷(于 Kubernetes v1.23 弃用)可以在 Pod 重启期间调整大小。
重设使用中 PVC 申领的大小 特性状态: Kubernetes v1.24 [stable]
在这种情况下,你不需要删除和重建正在使用某现有 PVC 的 Pod 或 Deployment。
所有使用中的 PVC 在其文件系统被扩充之后,立即可供其 Pod 使用。
此功能特性对于没有被 Pod 或 Deployment 使用的 PVC 而言没有效果。
你必须在执行扩展操作之前创建一个使用该 PVC 的 Pod。
与其他卷类型类似,FlexVolume 卷也可以在被 Pod 使用期间执行扩充操作。
说明: FlexVolume 卷的重设大小只能在下层驱动支持重设大小的时候才可进行。
说明: 扩充 EBS 卷的操作非常耗时。同时还存在另一个配额限制:
每 6 小时只能执行一次(尺寸)修改操作。
处理扩充卷过程中的失败 如果用户指定的新大小过大,底层存储系统无法满足,PVC 的扩展将不断重试,
直到用户或集群管理员采取一些措施。这种情况是不希望发生的,因此 Kubernetes
提供了以下从此类故障中恢复的方法。
如果扩充下层存储的操作失败,集群管理员可以手动地恢复 PVC
申领的状态并取消重设大小的请求。否则,在没有管理员干预的情况下,
控制器会反复重试重设大小的操作。
将绑定到 PVC 申领的 PV 卷标记为 Retain
回收策略。 删除 PVC 对象。由于 PV 的回收策略为 Retain
,我们不会在重建 PVC 时丢失数据。 删除 PV 规约中的 claimRef
项,这样新的 PVC 可以绑定到该卷。
这一操作会使得 PV 卷变为 "可用(Available)"。 使用小于 PV 卷大小的尺寸重建 PVC,设置 PVC 的 volumeName
字段为 PV 卷的名称。
这一操作将把新的 PVC 对象绑定到现有的 PV 卷。 不要忘记恢复 PV 卷上设置的回收策略。
特性状态: Kubernetes v1.23 [alpha]
说明: Kubernetes 从 1.23 版本开始将允许用户恢复失败的 PVC 扩展这一能力作为
alpha 特性支持。
RecoverVolumeExpansionFailure
必须被启用以允许使用此特性。
可参考
特性门控
文档了解更多信息。
如果集群中的特性门控 RecoverVolumeExpansionFailure
已启用,在 PVC 的扩展发生失败时,你可以使用比先前请求的值更小的尺寸来重试扩展。
要使用一个更小的尺寸尝试请求新的扩展,请编辑该 PVC 的 .spec.resources
并选择一个比你之前所尝试的值更小的值。
如果由于容量限制而无法成功扩展至更高的值,这将很有用。
如果发生了这种情况,或者你怀疑可能发生了这种情况,
你可以通过指定一个在底层存储制备容量限制内的尺寸来重试扩展。
你可以通过查看 .status.resizeStatus
以及 PVC 上的事件来监控调整大小操作的状态。
请注意,
尽管你可以指定比之前的请求更低的存储量,新值必须仍然高于 .status.capacity
。
Kubernetes 不支持将 PVC 缩小到小于其当前的尺寸。
持久卷的类型 PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件:
cephfs
- CephFS volumecsi
- 容器存储接口 (CSI)fc
- Fibre Channel (FC) 存储hostPath
- HostPath 卷
(仅供单节点测试使用;不适用于多节点集群;请尝试使用 local
卷作为替代)iscsi
- iSCSI (SCSI over IP) 存储local
- 节点上挂载的本地存储设备nfs
- 网络文件系统 (NFS) 存储rbd
- Rados 块设备 (RBD) 卷以下的持久卷已被弃用。这意味着当前仍是支持的,但是 Kubernetes 将来的发行版会将其移除。
旧版本的 Kubernetes 仍支持这些“树内(In-Tree)”持久卷类型:
photonPersistentDisk
- Photon 控制器持久化盘。(从 v1.15 版本开始将不可用 )scaleIO
- ScaleIO 卷(v1.21 之后不可用 )flocker
- Flocker 存储
(v1.25 之后不可用 )quobyte
- Quobyte 卷
(v1.25 之后不可用 )storageos
- StorageOS 卷
(v1.25 之后不可用 )持久卷 每个 PV 对象都包含 spec
部分和 status
部分,分别对应卷的规约和状态。
PersistentVolume 对象的名称必须是合法的
DNS 子域名 .
apiVersion : v1
kind : PersistentVolume
metadata :
name : pv0003
spec :
capacity :
storage : 5Gi
volumeMode : Filesystem
accessModes :
- ReadWriteOnce
persistentVolumeReclaimPolicy : Recycle
storageClassName : slow
mountOptions :
- hard
- nfsvers=4.1
nfs :
path : /tmp
server : 172.17.0.2
说明: 在集群中使用持久卷存储通常需要一些特定于具体卷类型的辅助程序。
在这个例子中,PersistentVolume 是 NFS 类型的,因此需要辅助程序 /sbin/mount.nfs
来支持挂载 NFS 文件系统。
容量 一般而言,每个 PV 卷都有确定的存储容量。
容量属性是使用 PV 对象的 capacity
属性来设置的。
参考词汇表中的量纲(Quantity)
词条,了解 capacity
字段可以接受的单位。
目前,存储大小是可以设置和请求的唯一资源。
未来可能会包含 IOPS、吞吐量等属性。
卷模式 特性状态: Kubernetes v1.18 [stable]
针对 PV 持久卷,Kubernetes
支持两种卷模式(volumeModes
):Filesystem(文件系统)
和 Block(块)
。
volumeMode
是一个可选的 API 参数。
如果该参数被省略,默认的卷模式是 Filesystem
。
volumeMode
属性设置为 Filesystem
的卷会被 Pod 挂载(Mount) 到某个目录。
如果卷的存储来自某块设备而该设备目前为空,Kuberneretes 会在第一次挂载卷之前在设备上创建文件系统。
你可以将 volumeMode
设置为 Block
,以便将卷作为原始块设备来使用。
这类卷以块设备的方式交给 Pod 使用,其上没有任何文件系统。
这种模式对于为 Pod 提供一种使用最快可能方式来访问卷而言很有帮助,
Pod 和卷之间不存在文件系统层。另外,Pod 中运行的应用必须知道如何处理原始块设备。
关于如何在 Pod 中使用 volumeMode: Block
的卷,
可参阅原始块卷支持 。
访问模式 PersistentVolume 卷可以用资源提供者所支持的任何方式挂载到宿主系统上。
如下表所示,提供者(驱动)的能力不同,每个 PV 卷的访问模式都会设置为对应卷所支持的模式值。
例如,NFS 可以支持多个读写客户,但是某个特定的 NFS PV 卷可能在服务器上以只读的方式导出。
每个 PV 卷都会获得自身的访问模式集合,描述的是特定 PV 卷的能力。
访问模式有:
ReadWriteOnce
卷可以被一个节点以读写方式挂载。
ReadWriteOnce 访问模式也允许运行在同一节点上的多个 Pod 访问卷。 ReadOnlyMany
卷可以被多个节点以只读方式挂载。 ReadWriteMany
卷可以被多个节点以读写方式挂载。 ReadWriteOncePod
卷可以被单个 Pod 以读写方式挂载。
如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC,
请使用 ReadWriteOncePod 访问模式。这只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。 这篇博客文章 Introducing Single Pod Access Mode for PersistentVolumes
描述了更详细的内容。
在命令行接口(CLI)中,访问模式也使用以下缩写形式:
RWO - ReadWriteOnce ROX - ReadOnlyMany RWX - ReadWriteMany RWOP - ReadWriteOncePod 说明: Kubernetes 使用卷访问模式来匹配 PersistentVolumeClaim 和 PersistentVolume。
在某些场合下,卷访问模式也会限制 PersistentVolume 可以挂载的位置。
卷访问模式并不会 在存储已经被挂载的情况下为其实施写保护。
即使访问模式设置为 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany,它们也不会对卷形成限制。
例如,即使某个卷创建时设置为 ReadOnlyMany,也无法保证该卷是只读的。
如果访问模式设置为 ReadWriteOncePod,则卷会被限制起来并且只能挂载到一个 Pod 上。
重要提醒! 每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持多种访问模式。
例如,一个 GCEPersistentDisk 卷可以被某节点以 ReadWriteOnce
模式挂载,或者被多个节点以 ReadOnlyMany 模式挂载,但不可以同时以两种模式挂载。
卷插件 ReadWriteOnce ReadOnlyMany ReadWriteMany ReadWriteOncePod AWSElasticBlockStore ✓ - - - AzureFile ✓ ✓ ✓ - AzureDisk ✓ - - - CephFS ✓ ✓ ✓ - Cinder ✓ - (如果多次挂接卷可用 ) - CSI 取决于驱动 取决于驱动 取决于驱动 取决于驱动 FC ✓ ✓ - - FlexVolume ✓ ✓ 取决于驱动 - GCEPersistentDisk ✓ ✓ - - Glusterfs ✓ ✓ ✓ - HostPath ✓ - - - iSCSI ✓ ✓ - - NFS ✓ ✓ ✓ - RBD ✓ ✓ - - VsphereVolume ✓ - -(Pod 运行于同一节点上时可行) - PortworxVolume ✓ - ✓ -
类 每个 PV 可以属于某个类(Class),通过将其 storageClassName
属性设置为某个
StorageClass 的名称来指定。
特定类的 PV 卷只能绑定到请求该类存储卷的 PVC 申领。
未设置 storageClassName
的 PV 卷没有类设定,只能绑定到那些没有指定特定存储类的 PVC 申领。
早前,Kubernetes 使用注解 volume.beta.kubernetes.io/storage-class
而不是
storageClassName
属性。这一注解目前仍然起作用,不过在将来的 Kubernetes
发布版本中该注解会被彻底废弃。
回收策略 目前的回收策略有:
Retain -- 手动回收 Recycle -- 基本擦除 (rm -rf /thevolume/*
) Delete -- 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除 目前,仅 NFS 和 HostPath 支持回收(Recycle)。
AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。
挂载选项 Kubernetes 管理员可以指定持久卷被挂载到节点上时使用的附加挂载选项。
说明: 并非所有持久卷类型都支持挂载选项。
以下卷类型支持挂载选项:
awsElasticBlockStore
azureDisk
azureFile
cephfs
cinder
(于 v1.18 弃用 )gcePersistentDisk
glusterfs
iscsi
nfs
rbd
vsphereVolume
Kubernetes 不对挂载选项执行合法性检查。如果挂载选项是非法的,挂载就会失败。
早前,Kubernetes 使用注解 volume.beta.kubernetes.io/mount-options
而不是
mountOptions
属性。这一注解目前仍然起作用,不过在将来的 Kubernetes
发布版本中该注解会被彻底废弃。
节点亲和性 每个 PV 卷可以通过设置节点亲和性来定义一些约束,进而限制从哪些节点上可以访问此卷。
使用这些卷的 Pod 只会被调度到节点亲和性规则所选择的节点上执行。
要设置节点亲和性,配置 PV 卷 .spec
中的 nodeAffinity
。
持久卷
API 参考关于该字段的更多细节。
阶段 每个卷会处于以下阶段(Phase)之一:
Available(可用)-- 卷是一个空闲资源,尚未绑定到任何申领; Bound(已绑定)-- 该卷已经绑定到某申领; Released(已释放)-- 所绑定的申领已被删除,但是资源尚未被集群回收; Failed(失败)-- 卷的自动回收操作失败。 命令行接口能够显示绑定到某 PV 卷的 PVC 对象。
PersistentVolumeClaims 每个 PVC 对象都有 spec
和 status
部分,分别对应申领的规约和状态。
PersistentVolumeClaim 对象的名称必须是合法的
DNS 子域名 .
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : myclaim
spec :
accessModes :
- ReadWriteOnce
volumeMode : Filesystem
resources :
requests :
storage : 8Gi
storageClassName : slow
selector :
matchLabels :
release : "stable"
matchExpressions :
- {key: environment, operator: In, values : [dev]}
访问模式 申领在请求具有特定访问模式的存储时,使用与卷相同的访问模式约定 。
卷模式 申领使用与卷相同的约定 来表明是将卷作为文件系统还是块设备来使用。
资源 申领和 Pod 一样,也可以请求特定数量的资源。在这个上下文中,请求的资源是存储。
卷和申领都使用相同的
资源模型 。
选择算符 申领可以设置标签选择算符
来进一步过滤卷集合。只有标签与选择算符相匹配的卷能够绑定到申领上。
选择算符包含两个字段:
matchLabels
- 卷必须包含带有此值的标签matchExpressions
- 通过设定键(key)、值列表和操作符(operator)
来构造的需求。合法的操作符有 In、NotIn、Exists 和 DoesNotExist。来自 matchLabels
和 matchExpressions
的所有需求都按逻辑与的方式组合在一起。
这些需求都必须被满足才被视为匹配。
类 申领可以通过为 storageClassName
属性设置
StorageClass 的名称来请求特定的存储类。
只有所请求的类的 PV 卷,即 storageClassName
值与 PVC 设置相同的 PV 卷,
才能绑定到 PVC 申领。
PVC 申领不必一定要请求某个类。如果 PVC 的 storageClassName
属性值设置为 ""
,
则被视为要请求的是没有设置存储类的 PV 卷,因此这一 PVC 申领只能绑定到未设置存储类的
PV 卷(未设置注解或者注解值为 ""
的 PersistentVolume(PV)对象在系统中不会被删除,
因为这样做可能会引起数据丢失。未设置 storageClassName
的 PVC 与此大不相同,
也会被集群作不同处理。具体筛查方式取决于
DefaultStorageClass
准入控制器插件
是否被启用。
如果准入控制器插件被启用,则管理员可以设置一个默认的 StorageClass。
所有未设置 storageClassName
的 PVC 都只能绑定到隶属于默认存储类的 PV 卷。
设置默认 StorageClass 的工作是通过将对应 StorageClass 对象的注解
storageclass.kubernetes.io/is-default-class
赋值为 true
来完成的。
如果管理员未设置默认存储类,集群对 PVC 创建的处理方式与未启用准入控制器插件时相同。
如果设定的默认存储类不止一个,准入控制插件会禁止所有创建 PVC 操作。 如果准入控制器插件被关闭,则不存在默认 StorageClass 的说法。
所有将 storageClassName
设为 ""
的 PVC 只能被绑定到也将 storageClassName
设为 ""
的 PV。
不过,只要默认的 StorageClass 可用,就可以稍后更新缺少 storageClassName
的 PVC。
如果这个 PVC 更新了,它将不再绑定到也将 storageClassName
设为 ""
的 PV。 参阅可追溯的默认 StorageClass 赋值 了解更多详细信息。
取决于安装方法,默认的 StorageClass 可能在集群安装期间由插件管理器(Addon
Manager)部署到集群中。
当某 PVC 除了请求 StorageClass 之外还设置了 selector
,则这两种需求会按逻辑与关系处理:
只有隶属于所请求类且带有所请求标签的 PV 才能绑定到 PVC。
说明: 目前,设置了非空 selector
的 PVC 对象无法让集群为其动态制备 PV 卷。
早前,Kubernetes 使用注解 volume.beta.kubernetes.io/storage-class
而不是
storageClassName
属性。这一注解目前仍然起作用,不过在将来的 Kubernetes
发布版本中该注解会被彻底废弃。
可追溯的默认 StorageClass 赋值 特性状态: Kubernetes v1.25 [alpha]
你可以创建 PersistentVolumeClaim,而无需为新 PVC 指定 storageClassName
。
即使你的集群中不存在默认 StorageClass,你也可以这样做。
在这种情况下,新的 PVC 会按照你的定义进行创建,并且在默认值可用之前,该 PVC 的 storageClassName
保持不设置。
但是,如果你启用了 RetroactiveDefaultStorageClass
特性门控 ,
则 Kubernetes 的行为会有所不同:现有 PVC 无需更新 storageClassName
就能使用新的默认 StorageClass。
当一个默认的 StorageClass 变得可用时,控制平面会识别所有未设置 storageClassName
的现有 PVC。
对于 storageClassName
为空值或没有此主键的 PVC,
控制平面会更新这些 PVC 以设置其 storageClassName
与新的默认 StorageClass 匹配。
如果你有一个现有的 PVC,其中 storageClassName
是 ""
,
并且你配置了默认 StorageClass,则此 PVC 将不会得到更新。
为了保持绑定到 storageClassName
设为 ""
的 PV(当存在默认 StorageClass 时),
你需要将关联 PVC 的 storageClassName
设置为 ""
。
此行为可帮助管理员更改默认 StorageClass,方法是先移除旧的 PVC,然后再创建或设置另一个 PVC。
这一时间窗口内因为没有指定默认值,会导致所创建的未设置 storageClassName
的 PVC 也没有默认值设置,
但由于默认 StorageClass 赋值是可追溯的,这种更改默认值的方式是安全的。
使用申领作为卷 Pod 将申领作为卷来使用,并藉此访问存储资源。
申领必须位于使用它的 Pod 所在的同一名字空间内。
集群在 Pod 的名字空间中查找申领,并使用它来获得申领所使用的 PV 卷。
之后,卷会被挂载到宿主上并挂载到 Pod 中。
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
containers :
- name : myfrontend
image : nginx
volumeMounts :
- mountPath : "/var/www/html"
name : mypd
volumes :
- name : mypd
persistentVolumeClaim :
claimName : myclaim
关于名字空间的说明 PersistentVolume 卷的绑定是排他性的。
由于 PersistentVolumeClaim 是名字空间作用域的对象,使用
"Many" 模式(ROX
、RWX
)来挂载申领的操作只能在同一名字空间内进行。
类型为 hostpath
的 PersistentVolume hostPath
PersistentVolume 使用节点上的文件或目录来模拟网络附加(network-attached)存储。
相关细节可参阅 hostPath
卷示例 。
原始块卷支持 特性状态: Kubernetes v1.18 [stable]
以下卷插件支持原始块卷,包括其动态制备(如果支持的话)的卷:
AWSElasticBlockStore AzureDisk CSI FC (光纤通道) GCEPersistentDisk iSCSI Local 卷 OpenStack Cinder RBD (Ceph 块设备) VsphereVolume 使用原始块卷的持久卷 apiVersion : v1
kind : PersistentVolume
metadata :
name : block-pv
spec :
capacity :
storage : 10Gi
accessModes :
- ReadWriteOnce
volumeMode : Block
persistentVolumeReclaimPolicy : Retain
fc :
targetWWNs : ["50060e801049cfd1" ]
lun : 0
readOnly : false
申请原始块卷的 PVC 申领 apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : block-pvc
spec :
accessModes :
- ReadWriteOnce
volumeMode : Block
resources :
requests :
storage : 10Gi
在容器中添加原始块设备路径的 Pod 规约 apiVersion : v1
kind : Pod
metadata :
name : pod-with-block-volume
spec :
containers :
- name : fc-container
image : fedora:26
command : ["/bin/sh" , "-c" ]
args : [ "tail -f /dev/null" ]
volumeDevices :
- name : data
devicePath : /dev/xvda
volumes :
- name : data
persistentVolumeClaim :
claimName : block-pvc
说明: 向 Pod 中添加原始块设备时,你要在容器内设置设备路径而不是挂载路径。
绑定块卷 如果用户通过 PersistentVolumeClaim 规约的 volumeMode
字段来表明对原始块设备的请求,
绑定规则与之前版本中未在规约中考虑此模式的实现略有不同。
下面列举的表格是用户和管理员可以为请求原始块设备所作设置的组合。
此表格表明在不同的组合下卷是否会被绑定。
静态制备卷的卷绑定矩阵:
PV volumeMode PVC volumeMode Result 未指定 未指定 绑定 未指定 Block 不绑定 未指定 Filesystem 绑定 Block 未指定 不绑定 Block Block 绑定 Block Filesystem 不绑定 Filesystem Filesystem 绑定 Filesystem Block 不绑定 Filesystem 未指定 绑定
说明: Alpha 发行版本中仅支持静态制备的卷。
管理员需要在处理原始块设备时小心处理这些值。
对卷快照及从卷快照中恢复卷的支持 特性状态: Kubernetes v1.20 [stable]
卷快照(Volume Snapshot)仅支持树外 CSI 卷插件。
有关细节可参阅卷快照 文档。
树内卷插件被弃用。你可以查阅卷插件 FAQ
了解已弃用的卷插件。
基于卷快照创建 PVC 申领 apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : restore-pvc
spec :
storageClassName : csi-hostpath-sc
dataSource :
name : new-snapshot-test
kind : VolumeSnapshot
apiGroup : snapshot.storage.k8s.io
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 10Gi
卷克隆 卷克隆 功能特性仅适用于 CSI 卷插件。
基于现有 PVC 创建新的 PVC 申领 apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : cloned-pvc
spec :
storageClassName : my-csi-plugin
dataSource :
name : existing-src-pvc-name
kind : PersistentVolumeClaim
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 10Gi
卷填充器(Populator)与数据源 特性状态: Kubernetes v1.24 [beta]
Kubernetes 支持自定义的卷填充器。要使用自定义的卷填充器,你必须为
kube-apiserver 和 kube-controller-manager 启用 AnyVolumeDataSource
特性门控 。
卷填充器利用了 PVC 规约字段 dataSourceRef
。
不像 dataSource
字段只能包含对另一个持久卷申领或卷快照的引用,
dataSourceRef
字段可以包含对同一命名空间中任何对象的引用(不包含除 PVC 以外的核心资源)。
对于启用了特性门控的集群,使用 dataSourceRef
比 dataSource
更好。
数据源引用 dataSourceRef
字段的行为与 dataSource
字段几乎相同。
如果其中一个字段被指定而另一个字段没有被指定,API 服务器将给两个字段相同的值。
这两个字段都不能在创建后改变,如果试图为这两个字段指定不同的值,将导致验证错误。
因此,这两个字段将总是有相同的内容。
在 dataSourceRef
字段和 dataSource
字段之间有两个用户应该注意的区别:
dataSource
字段会忽略无效的值(如同是空值),
而 dataSourceRef
字段永远不会忽略值,并且若填入一个无效的值,会导致错误。
无效值指的是 PVC 之外的核心对象(没有 apiGroup 的对象)。dataSourceRef
字段可以包含不同类型的对象,而 dataSource
字段只允许 PVC 和卷快照。用户应该始终在启用了特性门控的集群上使用 dataSourceRef
,而在没有启用特性门控的集群上使用 dataSource
。
在任何情况下都没有必要查看这两个字段。
这两个字段的值看似相同但是语义稍微不一样,是为了向后兼容。
特别是混用旧版本和新版本的控制器时,它们能够互通。
使用卷填充器 卷填充器是能创建非空卷的控制器 ,
其卷的内容通过一个自定义资源决定。
用户通过使用 dataSourceRef
字段引用自定义资源来创建一个被填充的卷:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : populated-pvc
spec :
dataSourceRef :
name : example-name
kind : ExampleDataSource
apiGroup : example.storage.k8s.io
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 10Gi
因为卷填充器是外部组件,如果没有安装所有正确的组件,试图创建一个使用卷填充器的 PVC 就会失败。
外部控制器应该在 PVC 上产生事件,以提供创建状态的反馈,包括在由于缺少某些组件而无法创建 PVC 的情况下发出警告。
你可以把 alpha 版本的卷数据源验证器
控制器安装到你的集群中。
如果没有填充器处理该数据源的情况下,该控制器会在 PVC 上产生警告事件。
当一个合适的填充器被安装到 PVC 上时,该控制器的职责是上报与卷创建有关的事件,以及在该过程中发生的问题。
编写可移植的配置 如果你要编写配置模板和示例用来在很多集群上运行并且需要持久性存储,建议你使用以下模式:
将 PersistentVolumeClaim 对象包含到你的配置包(Bundle)中,和 Deployment
以及 ConfigMap 等放在一起。 不要在配置中包含 PersistentVolume 对象,因为对配置进行实例化的用户很可能
没有创建 PersistentVolume 的权限。 为用户提供在实例化模板时指定存储类名称的能力。仍按用户提供存储类名称,将该名称放到 persistentVolumeClaim.storageClassName
字段中。
这样会使得 PVC 在集群被管理员启用了存储类支持时能够匹配到正确的存储类, 如果用户未指定存储类名称,将 persistentVolumeClaim.storageClassName
留空(nil)。
这样,集群会使用默认 StorageClass
为用户自动制备一个存储卷。
很多集群环境都配置了默认的 StorageClass
,或者管理员也可以自行创建默认的
StorageClass
。 在你的工具链中,监测经过一段时间后仍未被绑定的 PVC 对象,要让用户知道这些对象,
因为这可能意味着集群不支持动态存储(因而用户必须先创建一个匹配的 PV),或者
集群没有配置存储系统(因而用户无法配置需要 PVC 的工作负载配置)。 接下来 API 参考 阅读以下页面中描述的 API:
3.7.3 - 投射卷 本文档描述 Kubernetes 中的投射卷(Projected Volumes) 。
建议先熟悉卷 概念。
介绍 一个 projected
卷可以将若干现有的卷源映射到同一个目录之上。
目前,以下类型的卷源可以被投射:
所有的卷源都要求处于 Pod 所在的同一个名字空间内。进一步的详细信息,可参考
一体化卷 设计文档。
带有 Secret、DownwardAPI 和 ConfigMap 的配置示例 apiVersion : v1
kind : Pod
metadata :
name : volume-test
spec :
containers :
- name : container-test
image : busybox:1.28
volumeMounts :
- name : all-in-one
mountPath : "/projected-volume"
readOnly : true
volumes :
- name : all-in-one
projected :
sources :
- secret :
name : mysecret
items :
- key : username
path : my-group/my-username
- downwardAPI :
items :
- path : "labels"
fieldRef :
fieldPath : metadata.labels
- path : "cpu_limit"
resourceFieldRef :
containerName : container-test
resource : limits.cpu
- configMap :
name : myconfigmap
items :
- key : config
path : my-group/my-config
带有非默认权限模式设置的 Secret 的配置示例 apiVersion : v1
kind : Pod
metadata :
name : volume-test
spec :
containers :
- name : container-test
image : busybox:1.28
volumeMounts :
- name : all-in-one
mountPath : "/projected-volume"
readOnly : true
volumes :
- name : all-in-one
projected :
sources :
- secret :
name : mysecret
items :
- key : username
path : my-group/my-username
- secret :
name : mysecret2
items :
- key : password
path : my-group/my-password
mode : 511
每个被投射的卷源都列举在规约中的 sources
下面。参数几乎相同,只有两个例外:
对于 Secret,secretName
字段被改为 name
以便于 ConfigMap 的命名一致; defaultMode
只能在投射层级设置,不能在卷源层级设置。不过,正如上面所展示的,
你可以显式地为每个投射单独设置 mode
属性。serviceAccountToken 投射卷 当 TokenRequestProjection
特性被启用时,你可以将当前
服务账号
的令牌注入到 Pod 中特定路径下。例如:
apiVersion : v1
kind : Pod
metadata :
name : sa-token-test
spec :
containers :
- name : container-test
image : busybox:1.28
volumeMounts :
- name : token-vol
mountPath : "/service-account"
readOnly : true
serviceAccountName : default
volumes :
- name : token-vol
projected :
sources :
- serviceAccountToken :
audience : api
expirationSeconds : 3600
path : token
示例 Pod 中包含一个投射卷,其中包含注入的服务账号令牌。
此 Pod 中的容器可以使用该令牌访问 Kubernetes API 服务器, 使用
Pod 的 ServiceAccount
进行身份验证。audience
字段包含令牌所针对的受众。
收到令牌的主体必须使用令牌受众中所指定的某个标识符来标识自身,否则应该拒绝该令牌。
此字段是可选的,默认值为 API 服务器的标识。
字段 expirationSeconds
是服务账号令牌预期的生命期长度。默认值为 1 小时,
必须至少为 10 分钟(600 秒)。管理员也可以通过设置 API 服务器的命令行参数
--service-account-max-token-expiration
来为其设置最大值上限。
path
字段给出与投射卷挂载点之间的相对路径。
说明: 以 subPath
形式使用投射卷源的容器无法收到对应卷源的更新。
与 SecurityContext 间的关系 关于在投射的服务账号卷中处理文件访问权限的提案
介绍了如何使得所投射的文件具有合适的属主访问权限。
Linux 在包含了投射卷并在
SecurityContext
中设置了 RunAsUser
属性的 Linux Pod 中,投射文件具有正确的属主属性设置,
其中包含了容器用户属主。
Windows 在包含了投射卷并在 SecurityContext
中设置了 RunAsUsername
的 Windows Pod 中,
由于 Windows 中用户账号的管理方式问题,文件的属主无法正确设置。
Windows 在名为安全账号管理器(Security Account Manager,SAM)
的数据库中保存本地用户和组信息。每个容器会维护其自身的 SAM 数据库实例,
宿主系统无法窥视到容器运行期间数据库内容。Windows 容器被设计用来运行操作系统的用户态部分,
与宿主系统之间隔离,因此维护了一个虚拟的 SAM 数据库。
所以,在宿主系统上运行的 kubelet 无法动态为虚拟的容器账号配置宿主文件的属主。
如果需要将宿主机器上的文件与容器共享,建议将它们放到挂载于 C:\
之外的独立卷中。
默认情况下,所投射的文件会具有如下例所示的属主属性设置:
PS C:\> Get-Acl C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt | Format-List
Path : Microsoft.PowerShell.Core\FileSystem::C:\var\run\secrets\kubernetes.io\serviceaccount\..2021_08_31_22_22_18.318230061\ca.crt
Owner : BUILTIN\Administrators
Group : NT AUTHORITY\SYSTEM
Access : NT AUTHORITY\SYSTEM Allow FullControl
BUILTIN\Administrators Allow FullControl
BUILTIN\Users Allow ReadAndExecute, Synchronize
Audit :
Sddl : O:BAG: SYD: AI(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;0x1200a9;;;BU)
这意味着,所有类似 ContainerAdministrator
的管理员用户都具有读、写和执行访问权限,
而非管理员用户将具有读和执行访问权限。
说明: 总体而言,为容器授予访问宿主系统的权限这种做法是不推荐的,因为这样做可能会打开潜在的安全性攻击之门。
在创建 Windows Pod 时,如果在其 SecurityContext
中设置了 RunAsUser
,
Pod 会一直阻塞在 ContainerCreating
状态。因此,建议不要在 Windows
节点上使用仅针对 Linux 的 RunAsUser
选项。
3.7.4 - 临时卷 本文档描述 Kubernetes 中的 临时卷(Ephemeral Volume) 。
建议先了解卷 ,特别是 PersistentVolumeClaim 和 PersistentVolume。
有些应用程序需要额外的存储,但并不关心数据在重启后是否仍然可用。
例如,缓存服务经常受限于内存大小,而且可以将不常用的数据转移到比内存慢的存储中,对总体性能的影响并不大。
另有些应用程序需要以文件形式注入的只读数据,比如配置数据或密钥。
临时卷 就是为此类用例设计的。因为卷会遵从 Pod 的生命周期,与 Pod 一起创建和删除,
所以停止和重新启动 Pod 时,不会受持久卷在何处可用的限制。
临时卷在 Pod 规约中以 内联 方式定义,这简化了应用程序的部署和管理。
临时卷的类型 Kubernetes 为了不同的用途,支持几种不同类型的临时卷:
emptyDir
、configMap
、downwardAPI
、secret
是作为
本地临时存储
提供的。它们由各个节点上的 kubelet 管理。
CSI 临时卷 必须 由第三方 CSI 存储驱动程序提供。
通用临时卷 可以 由第三方 CSI 存储驱动程序提供,也可以由支持动态制备的任何其他存储驱动程序提供。
一些专门为 CSI 临时卷编写的 CSI 驱动程序,不支持动态制备:因此这些驱动程序不能用于通用临时卷。
使用第三方驱动程序的优势在于,它们可以提供 Kubernetes 本身不支持的功能,
例如,与 kubelet 管理的磁盘具有不同性能特征的存储,或者用来注入不同的数据。
CSI 临时卷 特性状态: Kubernetes v1.25 [stable]
说明: 只有一部分 CSI 驱动程序支持 CSI 临时卷。Kubernetes CSI
驱动程序列表
显示了支持临时卷的驱动程序。
从概念上讲,CSI 临时卷类似于 configMap
、downwardAPI
和 secret
类型的卷:
在各个本地节点管理卷的存储,并在 Pod 调度到节点后与其他本地资源一起创建。
在这个阶段,Kubernetes 没有重新调度 Pod 的概念。卷创建不太可能失败,否则 Pod 启动将会受阻。
特别是,这些卷 不 支持感知存储容量的 Pod 调度 。
它们目前也没包括在 Pod 的存储资源使用限制中,因为 kubelet 只能对它自己管理的存储强制执行。
下面是使用 CSI 临时存储的 Pod 的示例清单:
kind : Pod
apiVersion : v1
metadata :
name : my-csi-app
spec :
containers :
- name : my-frontend
image : busybox:1.28
volumeMounts :
- mountPath : "/data"
name : my-csi-inline-vol
command : [ "sleep" , "1000000" ]
volumes :
- name : my-csi-inline-vol
csi :
driver : inline.storage.kubernetes.io
volumeAttributes :
foo : bar
volumeAttributes
决定驱动程序准备什么样的卷。每个驱动程序的属性不尽相同,没有实现标准化。
有关进一步的说明,请参阅每个 CSI 驱动程序的文档。
CSI 驱动程序限制 CSI 临时卷允许用户直接向 CSI 驱动程序提供 volumeAttributes
,它会作为 Pod 规约的一部分。
有些 volumeAttributes
通常仅限于管理员使用,允许这一类 volumeAttributes
的 CSI 驱动程序不适合在内联临时卷中使用。
例如,通常在 StorageClass 中定义的参数不应通过使用内联临时卷向用户公开。
如果集群管理员需要限制在 Pod 规约中作为内联卷使用的 CSI 驱动程序,可以这样做:
从 CSIDriver 规约的 volumeLifecycleModes
中删除 Ephemeral
,这可以防止驱动程序被用作内联临时卷。 使用准入 Webhook
来限制如何使用此驱动程序。 通用临时卷 特性状态: Kubernetes v1.23 [stable]
通用临时卷类似于 emptyDir
卷,因为它为每个 Pod 提供临时数据存放目录,
在最初制备完毕时一般为空。不过通用临时卷也有一些额外的功能特性:
存储可以是本地的,也可以是网络连接的。 卷可以有固定的大小,Pod 不能超量使用。 卷可能有一些初始数据,这取决于驱动程序和参数。 示例:
kind : Pod
apiVersion : v1
metadata :
name : my-app
spec :
containers :
- name : my-frontend
image : busybox:1.28
volumeMounts :
- mountPath : "/scratch"
name : scratch-volume
command : [ "sleep" , "1000000" ]
volumes :
- name : scratch-volume
ephemeral :
volumeClaimTemplate :
metadata :
labels :
type : my-frontend-volume
spec :
accessModes : [ "ReadWriteOnce" ]
storageClassName : "scratch-storage-class"
resources :
requests :
storage : 1Gi
生命周期和 PersistentVolumeClaim 关键的设计思想是在 Pod 的卷来源中允许使用
卷申领的参数 。
PersistentVolumeClaim 的标签、注解和整套字段集均被支持。
创建这样一个 Pod 后,
临时卷控制器在 Pod 所属的命名空间中创建一个实际的 PersistentVolumeClaim 对象,
并确保删除 Pod 时,同步删除 PersistentVolumeClaim。
如上设置将触发卷的绑定与/或制备,相应动作或者在
StorageClass
使用即时卷绑定时立即执行,
或者当 Pod 被暂时性调度到某节点时执行 (WaitForFirstConsumer
卷绑定模式)。
对于通用的临时卷,建议采用后者,这样调度器就可以自由地为 Pod 选择合适的节点。
对于即时绑定,调度器则必须选出一个节点,使得在卷可用时,能立即访问该卷。
就资源所有权 而言,
拥有通用临时存储的 Pod 是提供临时存储 (ephemeral storage) 的 PersistentVolumeClaim 的所有者。
当 Pod 被删除时,Kubernetes 垃圾收集器会删除 PVC,
然后 PVC 通常会触发卷的删除,因为存储类的默认回收策略是删除卷。
你可以使用带有 retain
回收策略的 StorageClass 创建准临时 (quasi-ephemeral) 本地存储:
该存储比 Pod 寿命长,在这种情况下,你需要确保单独进行卷清理。
当这些 PVC 存在时,它们可以像其他 PVC 一样使用。
特别是,它们可以被引用作为批量克隆或快照的数据源。
PVC 对象还保持着卷的当前状态。
PersistentVolumeClaim 的命名 自动创建的 PVC 采取确定性的命名机制:名称是 Pod 名称和卷名称的组合,中间由连字符(-
)连接。
在上面的示例中,PVC 将命名为 my-app-scratch-volume
。
这种确定性的命名机制使得与 PVC 交互变得更容易,因为一旦知道 Pod 名称和卷名,就不必搜索它。
这种命名机制也引入了潜在的冲突,
不同的 Pod 之间(名为 “Pod-a” 的 Pod 挂载名为 "scratch" 的卷,
和名为 "pod" 的 Pod 挂载名为 “a-scratch” 的卷,这两者均会生成名为
"pod-a-scratch" 的 PVC),或者在 Pod 和手工创建的 PVC 之间可能出现冲突。
以下冲突会被检测到:如果 PVC 是为 Pod 创建的,那么它只用于临时卷。
此检测基于所有权关系。现有的 PVC 不会被覆盖或修改。
但这并不能解决冲突,因为如果没有正确的 PVC,Pod 就无法启动。
注意: 当同一个命名空间中命名 Pod 和卷时,要小心,以防止发生此类冲突。
安全 启用 GenericEphemeralVolume 特性会有一些副作用,用户能创建 Pod 就能间接地创建 PVC,
即使他们没有权限直接创建 PVC。
集群管理员必须意识到这一点。
如果这不符合他们的安全模型,他们应该使用准入 Webhook
拒绝包含通用临时卷的对象,例如 Pod。
正常的 PVC 的名字空间配额
仍然有效,因此即使允许用户使用这种新机制,他们也不能使用它来规避其他策略。
接下来 kubelet 管理的临时卷 参阅本地临时存储 。
CSI 临时卷 通用临时卷 3.7.5 - 存储类 本文描述了 Kubernetes 中 StorageClass 的概念。
建议先熟悉卷 和持久卷 的概念。
介绍 StorageClass 为管理员提供了描述存储 "类" 的方法。
不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。
Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为 "配置文件"。
StorageClass 资源 每个 StorageClass 都包含 provisioner
、parameters
和 reclaimPolicy
字段,
这些字段会在 StorageClass 需要动态制备 PersistentVolume 时会使用到。
StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。
当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。
管理员可以为没有申请绑定到特定 StorageClass 的 PVC 指定一个默认的存储类:
更多详情请参阅
PersistentVolumeClaim 章节 。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : standard
provisioner : kubernetes.io/aws-ebs
parameters :
type : gp2
reclaimPolicy : Retain
allowVolumeExpansion : true
mountOptions :
- debug
volumeBindingMode : Immediate
存储制备器 每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件制备 PV。
该字段必须指定。
你不限于指定此处列出的 "内置" 制备器(其名称前缀为 "kubernetes.io" 并打包在 Kubernetes 中)。
你还可以运行和指定外部制备器,这些独立的程序遵循由 Kubernetes
定义的规范 。
外部供应商的作者完全可以自由决定他们的代码保存于何处、打包方式、运行方式、使用的插件(包括 Flex)等。
代码仓库 kubernetes-sigs/sig-storage-lib-external-provisioner
包含一个用于为外部制备器编写功能实现的类库。你可以访问代码仓库
kubernetes-sigs/sig-storage-lib-external-provisioner
了解外部驱动列表。
例如,NFS 没有内部制备器,但可以使用外部制备器。
也有第三方存储供应商提供自己的外部制备器。
回收策略 由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy
字段中指定回收策略,可以是
Delete
或者 Retain
。如果 StorageClass 对象被创建时没有指定 reclaimPolicy
,它将默认为 Delete
。
通过 StorageClass 手动创建并管理的 PersistentVolume 会使用它们被创建时指定的回收策略。
允许卷扩展 特性状态: Kubernetes v1.11 [beta]
PersistentVolume 可以配置为可扩展。将此功能设置为 true
时,允许用户通过编辑相应的 PVC 对象来调整卷大小。
当下层 StorageClass 的 allowVolumeExpansion
字段设置为 true 时,以下类型的卷支持卷扩展。
Table of Volume types and the version of Kubernetes they require 卷类型 Kubernetes 版本要求 gcePersistentDisk 1.11 awsElasticBlockStore 1.11 Cinder 1.11 glusterfs 1.11 rbd 1.11 Azure File 1.11 Azure Disk 1.11 Portworx 1.11 FlexVolume 1.13 CSI 1.14 (alpha), 1.16 (beta)
挂载选项 由 StorageClass 动态创建的 PersistentVolume 将使用类中 mountOptions
字段指定的挂载选项。
如果卷插件不支持挂载选项,却指定了挂载选项,则制备操作会失败。
挂载选项在 StorageClass 和 PV 上都不会做验证,如果其中一个挂载选项无效,那么这个 PV 挂载操作就会失败。
卷绑定模式 volumeBindingMode
字段控制了卷绑定和动态制备 应该发生在什么时候。
默认情况下,Immediate
模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。
对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume
会在不知道 Pod 调度要求的情况下绑定或者制备。
集群管理员可以通过指定 WaitForFirstConsumer
模式来解决此问题。
该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。
PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。
这些包括但不限于资源需求 、
节点筛选器 、
Pod 亲和性和互斥性 、
以及污点和容忍度 。
以下插件支持动态制备的 WaitForFirstConsumer
模式:
以下插件支持预创建绑定 PersistentVolume 的 WaitForFirstConsumer
模式:
特性状态: Kubernetes v1.17 [stable]
动态制备和预先创建的 PV 也支持 CSI 卷 ,
但是你需要查看特定 CSI 驱动的文档以查看其支持的拓扑键名和例子。
说明: 如果你选择使用 WaitForFirstConsumer
,请不要在 Pod 规约中使用 nodeName
来指定节点亲和性。
如果在这种情况下使用 nodeName
,Pod 将会绕过调度程序,PVC 将停留在 pending
状态。
相反,在这种情况下,你可以使用节点选择器作为主机名,如下所示
apiVersion : v1
kind : Pod
metadata :
name : task-pv-pod
spec :
nodeSelector :
kubernetes.io/hostname : kube-01
volumes :
- name : task-pv-storage
persistentVolumeClaim :
claimName : task-pv-claim
containers :
- name : task-pv-container
image : nginx
ports :
- containerPort : 80
name : "http-server"
volumeMounts :
- mountPath : "/usr/share/nginx/html"
name : task-pv-storage
允许的拓扑结构 当集群操作人员使用了 WaitForFirstConsumer
的卷绑定模式,
在大部分情况下就没有必要将制备限制为特定的拓扑结构。
然而,如果还有需要的话,可以使用 allowedTopologies
。
这个例子描述了如何将制备卷的拓扑限制在特定的区域,
在使用时应该根据插件支持情况替换 zone
和 zones
参数。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : standard
provisioner : kubernetes.io/gce-pd
parameters :
type : pd-standard
volumeBindingMode : WaitForFirstConsumer
allowedTopologies :
- matchLabelExpressions :
- key : failure-domain.beta.kubernetes.io/zone
values :
- us-central-1a
- us-central-1b
参数 Storage Classes 的参数描述了存储类的卷。取决于制备器,可以接受不同的参数。
例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。
当参数被省略时,会使用默认值。
一个 StorageClass 最多可以定义 512 个参数。这些参数对象的总长度不能超过 256 KiB, 包括参数的键和值。
AWS EBS apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : slow
provisioner : kubernetes.io/aws-ebs
parameters :
type : io1
iopsPerGB : "10"
fsType : ext4
type
:io1
,gp2
,sc1
,st1
。详细信息参见
AWS 文档 。默认值:gp2
。zone
(弃用):AWS 区域。如果没有指定 zone
和 zones
,
通常卷会在 Kubernetes 集群节点所在的活动区域中轮询调度分配。
zone
和 zones
参数不能同时使用。zones
(弃用):以逗号分隔的 AWS 区域列表。
如果没有指定 zone
和 zones
,通常卷会在 Kubernetes 集群节点所在的活动区域中轮询调度分配。
zone
和zones
参数不能同时使用。iopsPerGB
:只适用于 io1
卷。每 GiB 每秒 I/O 操作。
AWS 卷插件将其与请求卷的大小相乘以计算 IOPS 的容量,
并将其限制在 20000 IOPS(AWS 支持的最高值,请参阅
AWS 文档 )。
这里需要输入一个字符串,即 "10"
,而不是 10
。fsType
:受 Kubernetes 支持的文件类型。默认值:"ext4"
。encrypted
:指定 EBS 卷是否应该被加密。合法值为 "true"
或者 "false"
。
这里需要输入字符串,即 "true"
, 而非 true
。kmsKeyId
:可选。加密卷时使用密钥的完整 Amazon 资源名称。
如果没有提供,但 encrypted
值为 true,AWS 生成一个密钥。关于有效的 ARN 值,请参阅 AWS 文档。GCE PD apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : slow
provisioner : kubernetes.io/gce-pd
parameters :
type : pd-standard
fstype : ext4
replication-type : none
type
:pd-standard
或者 pd-ssd
。默认:pd-standard
zone
(弃用):GCE 区域。如果没有指定 zone
和 zones
,
通常卷会在 Kubernetes 集群节点所在的活动区域中轮询调度分配。
zone
和 zones
参数不能同时使用。zones
(弃用):逗号分隔的 GCE 区域列表。如果没有指定 zone
和 zones
,
通常卷会在 Kubernetes 集群节点所在的活动区域中轮询调度(round-robin)分配。
zone
和 zones
参数不能同时使用。fstype
:ext4
或 xfs
。 默认:ext4
。宿主机操作系统必须支持所定义的文件系统类型。replication-type
:none
或者 regional-pd
。默认值:none
。如果 replication-type
设置为 none
,会制备一个常规(当前区域内的)持久化磁盘。
如果 replication-type
设置为 regional-pd
,
会制备一个区域性持久化磁盘(Regional Persistent Disk) 。
强烈建议设置 volumeBindingMode: WaitForFirstConsumer
,这样设置后,
当你创建一个 Pod,它使用的 PersistentVolumeClaim 使用了这个 StorageClass,
区域性持久化磁盘会在两个区域里制备。 其中一个区域是 Pod 所在区域。
另一个区域是会在集群管理的区域中任意选择。磁盘区域可以通过 allowedTopologies
加以限制。
Glusterfs(已弃用) apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : slow
provisioner : kubernetes.io/glusterfs
parameters :
resturl : "http://127.0.0.1:8081"
clusterid : "630372ccdc720a92c681fb928f27b53f"
restauthenabled : "true"
restuser : "admin"
secretNamespace : "default"
secretName : "heketi-secret"
gidMin : "40000"
gidMax : "50000"
volumetype : "replicate:3"
resturl
:制备 gluster 卷的需求的 Gluster REST 服务/Heketi 服务 url。
通用格式应该是 IPaddress:Port
,这是 GlusterFS 动态制备器的必需参数。
如果 Heketi 服务在 OpenShift/kubernetes 中安装并暴露为可路由服务,则可以使用类似于
http://heketi-storage-project.cloudapps.mystorage.com
的格式,其中 fqdn 是可解析的 heketi 服务网址。restauthenabled
:Gluster REST 服务身份验证布尔值,用于启用对 REST 服务器的身份验证。
如果此值为 'true',则必须填写 restuser
和 restuserkey
或 secretNamespace
+ secretName
。
此选项已弃用,当在指定 restuser
、restuserkey
、secretName
或 secretNamespace
时,身份验证被启用。restuser
:在 Gluster 可信池中有权创建卷的 Gluster REST服务/Heketi 用户。restuserkey
:Gluster REST 服务/Heketi 用户的密码将被用于对 REST 服务器进行身份验证。
此参数已弃用,取而代之的是 secretNamespace
+ secretName
。secretNamespace
,secretName
:Secret 实例的标识,包含与 Gluster
REST 服务交互时使用的用户密码。
这些参数是可选的,secretNamespace
和 secretName
都省略时使用空密码。
所提供的 Secret 必须将类型设置为 "kubernetes.io/glusterfs",例如以这种方式创建:
kubectl create secret generic heketi-secret \
--type="kubernetes.io/glusterfs" --from-literal=key='opensesame' \
--namespace=default
Secret 的例子可以在
glusterfs-provisioning-secret.yaml 中找到。
clusterid
:630372ccdc720a92c681fb928f27b53f
是集群的 ID,当制备卷时,
Heketi 将会使用这个文件。它也可以是一个 clusterid 列表,例如:
"8452344e2becec931ece4e33c4674e4e,42982310de6c63381718ccfa6d8cf397"
。这个是可选参数。gidMin
,gidMax
:StorageClass GID 范围的最小值和最大值。
在此范围(gidMin-gidMax)内的唯一值(GID)将用于动态制备卷。这些是可选的值。
如果不指定,所制备的卷为一个 2000-2147483647 之间的值,这是 gidMin 和
gidMax 的默认值。volumetype
:卷的类型及其参数可以用这个可选值进行配置。如果未声明卷类型,则由制备器决定卷的类型。
例如:
'Replica volume':volumetype: replicate:3
其中 '3' 是 replica 数量。 'Disperse/EC volume':volumetype: disperse:4:2
其中 '4' 是数据,'2' 是冗余数量。 'Distribute volume':volumetype: none
有关可用的卷类型和管理选项,
请参阅管理指南 。
更多相关的参考信息,
请参阅如何配置 Heketi 。
当动态制备持久卷时,Gluster 插件自动创建名为 gluster-dynamic-<claimname>
的端点和无头服务。在 PVC 被删除时动态端点和无头服务会自动被删除。
NFS apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : example-nfs
provisioner : example.com/external-nfs
parameters :
server : nfs-server.example.com
path : /share
readOnly : "false"
server
:NFS 服务器的主机名或 IP 地址。path
:NFS 服务器导出的路径。readOnly
:是否将存储挂载为只读的标志(默认为 false)。Kubernetes 不包含内部 NFS 驱动。你需要使用外部驱动为 NFS 创建 StorageClass。
这里有些例子:
OpenStack Cinder apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : gold
provisioner : kubernetes.io/cinder
parameters :
availability : nova
availability
:可用区域。如果没有指定,通常卷会在 Kubernetes 集群节点所在的活动区域中轮转调度。vSphere vSphere 存储类有两种制备器:
树内制备器已经被
弃用 。
更多关于 CSI 制备器的详情,请参阅
Kubernetes vSphere CSI 驱动
和 vSphereVolume CSI 迁移 。
CSI 制备器 vSphere CSI StorageClass 制备器在 Tanzu Kubernetes 集群下运行。示例请参阅
vSphere CSI 仓库 。
vCP 制备器 以下示例使用 VMware Cloud Provider (vCP) StorageClass 制备器。
使用用户指定的磁盘格式创建一个 StorageClass。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : fast
provisioner : kubernetes.io/vsphere-volume
parameters :
diskformat : zeroedthick
diskformat
:thin
、zeroedthick
和 eagerzeroedthick
。默认值:"thin"
。
在用户指定的数据存储上创建磁盘格式的 StorageClass。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : fast
provisioner : kubernetes.io/vsphere-volume
parameters :
diskformat : zeroedthick
datastore : VSANDatastore
datastore
:用户也可以在 StorageClass 中指定数据存储。
卷将在 storage class 中指定的数据存储上创建,在这种情况下是 VSANDatastore
。
该字段是可选的。
如果未指定数据存储,则将在用于初始化 vSphere Cloud Provider 的 vSphere
配置文件中指定的数据存储上创建该卷。
Kubernetes 中的存储策略管理
Kubernetes 内的 Virtual SAN 策略支持
Vsphere Infrastructure(VI)管理员将能够在动态卷配置期间指定自定义 Virtual SAN
存储功能。你现在可以在动态制备卷期间以存储能力的形式定义存储需求,例如性能和可用性。
存储能力需求会转换为 Virtual SAN 策略,之后当持久卷(虚拟磁盘)被创建时,
会将其推送到 Virtual SAN 层。虚拟磁盘分布在 Virtual SAN 数据存储中以满足要求。
你可以参考基于存储策略的动态制备卷管理 ,
进一步了解有关持久卷管理的存储策略的详细信息。
有几个 vSphere 例子 供你在
Kubernetes for vSphere 中尝试进行持久卷管理。
Ceph RBD apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : fast
provisioner : kubernetes.io/rbd
parameters :
monitors : 10.16.153.105 :6789
adminId : kube
adminSecretName : ceph-secret
adminSecretNamespace : kube-system
pool : kube
userId : kube
userSecretName : ceph-secret-user
userSecretNamespace : default
fsType : ext4
imageFormat : "2"
imageFeatures : "layering"
monitors
:Ceph monitor,逗号分隔。该参数是必需的。adminId
:Ceph 客户端 ID,用于在池 ceph 池中创建映像。默认是 "admin"。adminSecret
:adminId
的 Secret 名称。该参数是必需的。
提供的 secret 必须有值为 "kubernetes.io/rbd" 的 type 参数。adminSecretNamespace
:adminSecret
的命名空间。默认是 "default"。pool
:Ceph RBD 池。默认是 "rbd"。userId
:Ceph 客户端 ID,用于映射 RBD 镜像。默认与 adminId
相同。userSecretNamespace
:userSecretName
的命名空间。fsType
:Kubernetes 支持的 fsType。默认:"ext4"
。imageFormat
:Ceph RBD 镜像格式,"1" 或者 "2"。默认值是 "1"。imageFeatures
:这个参数是可选的,只能在你将 imageFormat
设置为 "2" 才使用。
目前支持的功能只是 layering
。默认是 "",没有功能打开。Azure 磁盘 Azure Unmanaged Disk Storage Class(非托管磁盘存储类) kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : slow
provisioner : kubernetes.io/azure-disk
parameters :
skuName : Standard_LRS
location : eastus
storageAccount : azure_storage_account_name
skuName
:Azure 存储帐户 Sku 层。默认为空。location
:Azure 存储帐户位置。默认为空。storageAccount
:Azure 存储帐户名称。
如果提供存储帐户,它必须位于与集群相同的资源组中,并且 location
是被忽略的。如果未提供存储帐户,则会在与集群相同的资源组中创建新的存储帐户。Azure 磁盘 Storage Class(从 v1.7.2 开始) kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : slow
provisioner : kubernetes.io/azure-disk
parameters :
storageaccounttype : Standard_LRS
kind : managed
storageaccounttype
:Azure 存储帐户 Sku 层。默认为空。kind
:可能的值是 shared
、dedicated
和 managed
(默认)。
当 kind
的值是 shared
时,所有非托管磁盘都在集群的同一个资源组中的几个共享存储帐户中创建。
当 kind
的值是 dedicated
时,将为在集群的同一个资源组中新的非托管磁盘创建新的专用存储帐户。resourceGroup
:指定要创建 Azure 磁盘所属的资源组。必须是已存在的资源组名称。
若未指定资源组,磁盘会默认放入与当前 Kubernetes 集群相同的资源组中。Premium VM 可以同时添加 Standard_LRS 和 Premium_LRS 磁盘,而 Standard
虚拟机只能添加 Standard_LRS 磁盘。 托管虚拟机只能连接托管磁盘,非托管虚拟机只能连接非托管磁盘。 Azure 文件 kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : azurefile
provisioner : kubernetes.io/azure-file
parameters :
skuName : Standard_LRS
location : eastus
storageAccount : azure_storage_account_name
skuName
:Azure 存储帐户 Sku 层。默认为空。location
:Azure 存储帐户位置。默认为空。storageAccount
:Azure 存储帐户名称。默认为空。
如果不提供存储帐户,会搜索所有与资源相关的存储帐户,以找到一个匹配
skuName
和 location
的账号。
如果提供存储帐户,它必须存在于与集群相同的资源组中,skuName
和 location
会被忽略。secretNamespace
:包含 Azure 存储帐户名称和密钥的密钥的名字空间。
默认值与 Pod 相同。secretName
:包含 Azure 存储帐户名称和密钥的密钥的名称。
默认值为 azure-storage-account-<accountName>-secret
readOnly
:指示是否将存储安装为只读的标志。默认为 false,表示"读/写"挂载。
该设置也会影响VolumeMounts中的 ReadOnly
设置。在存储制备期间,为挂载凭证创建一个名为 secretName
的 Secret。如果集群同时启用了
RBAC
和控制器角色 ,
为 system:controller:persistent-volume-binder
的 clusterrole 添加
Secret
资源的 create
权限。
在多租户上下文中,强烈建议显式设置 secretNamespace
的值,否则其他用户可能会读取存储帐户凭据。
Portworx 卷 apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : portworx-io-priority-high
provisioner : kubernetes.io/portworx-volume
parameters :
repl : "1"
snap_interval : "70"
priority_io : "high"
fs
:选择的文件系统:none/xfs/ext4
(默认:ext4
)。block_size
:以 Kbytes 为单位的块大小(默认值:32
)。repl
:同步副本数量,以复制因子 1..3
(默认值:1
)的形式提供。
这里需要填写字符串,即,"1"
而不是 1
。io_priority
:决定是否从更高性能或者较低优先级存储创建卷
high/medium/low
(默认值:low
)。snap_interval
:触发快照的时钟/时间间隔(分钟)。
快照是基于与先前快照的增量变化,0 是禁用快照(默认:0
)。
这里需要填写字符串,即,是 "70"
而不是 70
。aggregation_level
:指定卷分配到的块数量,0 表示一个非聚合卷(默认:0
)。
这里需要填写字符串,即,是 "0"
而不是 0
。ephemeral
:指定卷在卸载后进行清理还是持久化。
emptyDir
的使用场景可以将这个值设置为 true,
persistent volumes
的使用场景可以将这个值设置为 false
(例如 Cassandra 这样的数据库)
true/false
(默认为 false
)。这里需要填写字符串,即,
是 "true"
而不是 true
。本地 特性状态: Kubernetes v1.14 [stable]
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : local-storage
provisioner : kubernetes.io/no-provisioner
volumeBindingMode : WaitForFirstConsumer
本地卷还不支持动态制备,然而还是需要创建 StorageClass 以延迟卷绑定,
直到完成 Pod 的调度。这是由 WaitForFirstConsumer
卷绑定模式指定的。
延迟卷绑定使得调度器在为 PersistentVolumeClaim 选择一个合适的
PersistentVolume 时能考虑到所有 Pod 的调度限制。
3.7.6 - 卷快照 在 Kubernetes 中,卷快照 是一个存储系统上卷的快照,本文假设你已经熟悉了 Kubernetes
的持久卷 。
介绍 与 PersistentVolume
和 PersistentVolumeClaim
这两个 API 资源用于给用户和管理员制备卷类似,
VolumeSnapshotContent
和 VolumeSnapshot
这两个 API 资源用于给用户和管理员创建卷快照。
VolumeSnapshotContent
是从一个卷获取的一种快照,该卷由管理员在集群中进行制备。
就像持久卷(PersistentVolume)是集群的资源一样,它也是集群中的资源。
VolumeSnapshot
是用户对于卷的快照的请求。它类似于持久卷声明(PersistentVolumeClaim)。
VolumeSnapshotClass
允许指定属于 VolumeSnapshot
的不同属性。在从存储系统的相同卷上获取的快照之间,
这些属性可能有所不同,因此不能通过使用与 PersistentVolumeClaim
相同的 StorageClass
来表示。
卷快照能力为 Kubernetes 用户提供了一种标准的方式来在指定时间点复制卷的内容,并且不需要创建全新的卷。
例如,这一功能使得数据库管理员能够在执行编辑或删除之类的修改之前对数据库执行备份。
当使用该功能时,用户需要注意以下几点:
API 对象 VolumeSnapshot
,VolumeSnapshotContent
和 VolumeSnapshotClass
是 CRD ,
不属于核心 API。 VolumeSnapshot
支持仅可用于 CSI 驱动。作为 VolumeSnapshot
部署过程的一部分,Kubernetes 团队提供了一个部署于控制平面的快照控制器,
并且提供了一个叫做 csi-snapshotter
的边车(Sidecar)辅助容器,和 CSI 驱动程序一起部署。
快照控制器监视 VolumeSnapshot
和 VolumeSnapshotContent
对象,
并且负责创建和删除 VolumeSnapshotContent
对象。
边车 csi-snapshotter 监视 VolumeSnapshotContent
对象,
并且触发针对 CSI 端点的 CreateSnapshot
和 DeleteSnapshot
的操作。 还有一个验证性质的 Webhook 服务器,可以对快照对象进行更严格的验证。
Kubernetes 发行版应将其与快照控制器和 CRD(而非 CSI 驱动程序)一起安装。
此服务器应该安装在所有启用了快照功能的 Kubernetes 集群中。 CSI 驱动可能实现,也可能没有实现卷快照功能。CSI 驱动可能会使用 csi-snapshotter
来提供对卷快照的支持。详见 CSI 驱动程序文档 Kubernetes 负责 CRD 和快照控制器的安装。 卷快照和卷快照内容的生命周期 VolumeSnapshotContents
是集群中的资源。VolumeSnapshots
是对于这些资源的请求。
VolumeSnapshotContents
和 VolumeSnapshots
之间的交互遵循以下生命周期:
制备卷快照 快照可以通过两种方式进行制备:预制备或动态制备。
预制备 集群管理员创建多个 VolumeSnapshotContents
。它们带有存储系统上实际卷快照的详细信息,可以供集群用户使用。
它们存在于 Kubernetes API 中,并且能够被使用。
动态制备 可以从 PersistentVolumeClaim
中动态获取快照,而不用使用已经存在的快照。
在获取快照时,卷快照类
指定要用的特定于存储提供程序的参数。
绑定 在预制备和动态制备场景下,快照控制器处理绑定 VolumeSnapshot
对象和其合适的 VolumeSnapshotContent
对象。
绑定关系是一对一的。
在预制备快照绑定场景下,VolumeSnapshotContent
对象创建之后,才会和 VolumeSnapshot
进行绑定。
快照源的持久性卷声明保护 这种保护的目的是确保在从系统中获取快照时,不会将正在使用的
PersistentVolumeClaim
API 对象从系统中删除(因为这可能会导致数据丢失)。
如果一个 PVC 正在被快照用来作为源进行快照创建,则该 PVC 是使用中的。如果用户删除正作为快照源的 PVC API 对象,
则 PVC 对象不会立即被删除掉。相反,PVC 对象的删除将推迟到任何快照不在主动使用它为止。
当快照的 Status
中的 ReadyToUse
值为 true
时,PVC 将不再用作快照源。
当从 PersistentVolumeClaim
中生成快照时,PersistentVolumeClaim
就在被使用了。
如果删除一个作为快照源的 PersistentVolumeClaim
对象,这个 PersistentVolumeClaim
对象不会立即被删除的。
相反,删除 PersistentVolumeClaim
对象的动作会被放弃,或者推迟到快照的 Status 为 ReadyToUse 时再执行。
删除 删除 VolumeSnapshot
对象触发删除 VolumeSnapshotContent
操作,并且 DeletionPolicy
会紧跟着执行。
如果 DeletionPolicy
是 Delete
,那么底层存储快照会和 VolumeSnapshotContent
一起被删除。
如果 DeletionPolicy
是 Retain
,那么底层快照和 VolumeSnapshotContent
都会被保留。
卷快照 每个 VolumeSnapshot
包含一个 spec 和一个 status。
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshot
metadata :
name : new-snapshot-test
spec :
volumeSnapshotClassName : csi-hostpath-snapclass
source :
persistentVolumeClaimName : pvc-test
persistentVolumeClaimName
是 PersistentVolumeClaim
数据源对快照的名称。
这个字段是动态制备快照中的必填字段。
卷快照可以通过指定 VolumeSnapshotClass
使用 volumeSnapshotClassName
属性来请求特定类。如果没有设置,那么使用默认类(如果有)。
如下面例子所示,对于预制备的快照,需要给快照指定 volumeSnapshotContentName
作为来源。
对于预制备的快照 source
中的volumeSnapshotContentName
字段是必填的。
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshot
metadata :
name : test-snapshot
spec :
source :
volumeSnapshotContentName : test-content
每个 VolumeSnapshotContent 对象包含 spec 和 status。
在动态制备时,快照通用控制器创建 VolumeSnapshotContent
对象。下面是例子:
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshotContent
metadata :
name : snapcontent-72d9a349-aacd-42d2-a240-d775650d2455
spec :
deletionPolicy : Delete
driver : hostpath.csi.k8s.io
source :
volumeHandle : ee0cfb94-f8d4-11e9-b2d8-0242ac110002
sourceVolumeMode : Filesystem
volumeSnapshotClassName : csi-hostpath-snapclass
volumeSnapshotRef :
name : new-snapshot-test
namespace : default
uid : 72d9a349-aacd-42d2-a240-d775650d2455
volumeHandle
是存储后端创建卷的唯一标识符,在卷创建期间由 CSI 驱动程序返回。动态设置快照需要此字段。它指出了快照的卷源。
对于预制备快照,你(作为集群管理员)要按如下命令来创建 VolumeSnapshotContent
对象。
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshotContent
metadata :
name : new-snapshot-content-test
spec :
deletionPolicy : Delete
driver : hostpath.csi.k8s.io
source :
snapshotHandle : 7bdd0de3-aaeb-11e8-9aae-0242ac110002
sourceVolumeMode : Filesystem
volumeSnapshotRef :
name : new-snapshot-test
namespace : default
snapshotHandle
是存储后端创建卷的唯一标识符。对于预设置快照,这个字段是必须的。
它指定此 VolumeSnapshotContent
表示的存储系统上的 CSI 快照 ID。
sourceVolumeMode
是创建快照的卷的模式。sourceVolumeMode
字段的值可以是
Filesystem
或 Block
。如果没有指定源卷模式,Kubernetes 会将快照视为未知的源卷模式。
volumeSnapshotRef
字段是对相应的 VolumeSnapshot
的引用。
请注意,当 VolumeSnapshotContent
被创建为预配置快照时。
volumeSnapshotRef
中引用的 VolumeSnapshot
可能还不存在。
转换快照的卷模式 如果在你的集群上安装的 VolumeSnapshots
API 支持 sourceVolumeMode
字段,则该 API 可以防止未经授权的用户转换卷的模式。
要检查你的集群是否具有此特性的能力,可以运行如下命令:
$ kubectl get crd volumesnapshotcontent -o yaml
如果你希望允许用户从现有的 VolumeSnapshot
创建 PersistentVolumeClaim
,
但是使用与源卷不同的卷模式,则需要添加注解
snapshot.storage.kubernetes.io/allowVolumeModeChange: "true"
到对应 VolumeSnapshot
的 VolumeSnapshotContent
中。
对于预制备的快照,Spec.SourceVolumeMode
需要由集群管理员填充。
启用此特性的 VolumeSnapshotContent
资源示例如下所示:
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshotContent
metadata :
name : new-snapshot-content-test
annotations :
- snapshot.storage.kubernetes.io/allowVolumeModeChange : "true"
spec :
deletionPolicy : Delete
driver : hostpath.csi.k8s.io
source :
snapshotHandle : 7bdd0de3-aaeb-11e8-9aae-0242ac110002
sourceVolumeMode : Filesystem
volumeSnapshotRef :
name : new-snapshot-test
namespace : default
从快照制备卷 你可以制备一个新卷,该卷预填充了快照中的数据,在 持久卷声明
对象中使用 dataSource 字段。
更多详细信息,请参阅
卷快照和从快照还原卷 。
3.7.7 - 卷快照类 本文档描述了 Kubernetes 中 VolumeSnapshotClass 的概念。建议熟悉
卷快照(Volume Snapshots) 和
存储类(Storage Class) 。
介绍 就像 StorageClass 为管理员提供了一种在配置卷时描述存储“类”的方法,
VolumeSnapshotClass 提供了一种在配置卷快照时描述存储“类”的方法。
VolumeSnapshotClass 资源 每个 VolumeSnapshotClass 都包含 driver
、deletionPolicy
和 parameters
字段,
在需要动态配置属于该类的 VolumeSnapshot 时使用。
VolumeSnapshotClass 对象的名称很重要,是用户可以请求特定类的方式。
管理员在首次创建 VolumeSnapshotClass 对象时设置类的名称和其他参数,
对象一旦创建就无法更新。
说明: CRD 的安装是 Kubernetes 发行版的责任。 如果不存在所需的 CRD,则 VolumeSnapshotClass 的创建将失败。
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshotClass
metadata :
name : csi-hostpath-snapclass
driver : hostpath.csi.k8s.io
deletionPolicy : Delete
parameters :
管理员可以为未请求任何特定类绑定的 VolumeSnapshots 指定默认的 VolumeSnapshotClass,
方法是设置注解 snapshot.storage.kubernetes.io/is-default-class: "true"
:
apiVersion : snapshot.storage.k8s.io/v1
kind : VolumeSnapshotClass
metadata :
name : csi-hostpath-snapclass
annotations :
snapshot.storage.kubernetes.io/is-default-class : "true"
driver : hostpath.csi.k8s.io
deletionPolicy : Delete
parameters :
驱动程序 卷快照类有一个驱动程序,用于确定配置 VolumeSnapshot 的 CSI 卷插件。
此字段必须指定。
删除策略 卷快照类具有 deletionPolicy
属性。用户可以配置当所绑定的 VolumeSnapshot
对象将被删除时,如何处理 VolumeSnapshotContent 对象。
卷快照类的这个策略可以是 Retain
或者 Delete
。这个策略字段必须指定。
如果删除策略是 Delete
,那么底层的存储快照会和 VolumeSnapshotContent 对象
一起删除。如果删除策略是 Retain
,那么底层快照和 VolumeSnapshotContent
对象都会被保留。
参数 卷快照类具有描述属于该卷快照类的卷快照的参数,可根据 driver
接受不同的参数。
3.7.8 - 动态卷制备 动态卷制备允许按需创建存储卷。
如果没有动态制备,集群管理员必须手动地联系他们的云或存储提供商来创建新的存储卷,
然后在 Kubernetes 集群创建
PersistentVolume
对象 来表示这些卷。
动态制备功能消除了集群管理员预先配置存储的需要。相反,它在用户请求时自动制备存储。
背景 动态卷制备的实现基于 storage.k8s.io
API 组中的 StorageClass
API 对象。
集群管理员可以根据需要定义多个 StorageClass
对象,每个对象指定一个卷插件 (又名 provisioner ),
卷插件向卷制备商提供在创建卷时需要的数据卷信息及相关参数。
集群管理员可以在集群中定义和公开多种存储(来自相同或不同的存储系统),每种都具有自定义参数集。
该设计也确保终端用户不必担心存储制备的复杂性和细微差别,但仍然能够从多个存储选项中进行选择。
点击这里 查阅有关存储类的更多信息。
启用动态卷制备 要启用动态制备功能,集群管理员需要为用户预先创建一个或多个 StorageClass
对象。
StorageClass
对象定义当动态制备被调用时,哪一个驱动将被使用和哪些参数将被传递给驱动。
StorageClass 对象的名字必须是一个合法的
DNS 子域名 。
以下清单创建了一个 StorageClass
存储类 "slow",它提供类似标准磁盘的永久磁盘。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : slow
provisioner : kubernetes.io/gce-pd
parameters :
type : pd-standard
以下清单创建了一个 "fast" 存储类,它提供类似 SSD 的永久磁盘。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : fast
provisioner : kubernetes.io/gce-pd
parameters :
type : pd-ssd
使用动态卷制备 用户通过在 PersistentVolumeClaim
中包含存储类来请求动态制备的存储。
在 Kubernetes v1.9 之前,这通过 volume.beta.kubernetes.io/storage-class
注解实现。
然而,这个注解自 v1.6 起就不被推荐使用了。
用户现在能够而且应该使用 PersistentVolumeClaim
对象的 storageClassName
字段。
这个字段的值必须能够匹配到集群管理员配置的 StorageClass
名称(见下面 )。
例如,要选择 “fast” 存储类,用户将创建如下的 PersistentVolumeClaim:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : claim1
spec :
accessModes :
- ReadWriteOnce
storageClassName : fast
resources :
requests :
storage : 30Gi
该声明会自动制备一块类似 SSD 的永久磁盘。
在删除该声明后,这个卷也会被销毁。
设置默认值的行为 可以在集群上启用动态卷制备,以便在未指定存储类的情况下动态设置所有声明。
集群管理员可以通过以下方式启用此行为:
管理员可以通过向其添加
storageclass.kubernetes.io/is-default-class
注解
来将特定的 StorageClass
标记为默认。
当集群中存在默认的 StorageClass
并且用户创建了一个未指定 storageClassName
的 PersistentVolumeClaim
时,
DefaultStorageClass
准入控制器会自动向其中添加指向默认存储类的 storageClassName
字段。
请注意,集群上最多只能有一个 默认 存储类,否则无法创建没有明确指定
storageClassName
的 PersistentVolumeClaim
。
拓扑感知 在多可用区 集群中,Pod 可以被分散到某个区域的多个可用区。
单可用区存储后端应该被制备到 Pod 被调度到的可用区。
这可以通过设置卷绑定模式 来实现。
3.7.9 - CSI 卷克隆 本文档介绍 Kubernetes 中克隆现有 CSI 卷的概念。阅读前建议先熟悉
卷 。
介绍 CSI 卷克隆功能增加了通过在
dataSource
字段中指定存在的
PVC ,
来表示用户想要克隆的 卷(Volume) 。
克隆(Clone),意思是为已有的 Kubernetes 卷创建副本,它可以像任何其它标准卷一样被使用。
唯一的区别就是配置后,后端设备将创建指定完全相同的副本,而不是创建一个“新的”空卷。
从 Kubernetes API 的角度看,克隆的实现只是在创建新的 PVC 时,
增加了指定一个现有 PVC 作为数据源的能力。源 PVC 必须是 bound
状态且可用的(不在使用中)。
用户在使用该功能时,需要注意以下事项:
克隆支持(VolumePVCDataSource
)仅适用于 CSI 驱动。 克隆支持仅适用于 动态供应器。 CSI 驱动可能实现,也可能未实现卷克隆功能。 仅当 PVC 与目标 PVC 存在于同一命名空间(源和目标 PVC 必须在相同的命名空间)时,才可以克隆 PVC。 支持用一个不同存储类进行克隆。目标卷和源卷可以是相同的存储类,也可以不同。 可以使用默认的存储类,也可以在 spec 中省略 storageClassName 字段。 克隆只能在两个使用相同 VolumeMode 设置的卷中进行
(如果请求克隆一个块存储模式的卷,源卷必须也是块存储模式)。 制备 克隆卷与其他任何 PVC 一样配置,除了需要增加 dataSource 来引用同一命名空间中现有的 PVC。
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : clone-of-pvc-1
namespace : myns
spec :
accessModes :
- ReadWriteOnce
storageClassName : cloning
resources :
requests :
storage : 5Gi
dataSource :
kind : PersistentVolumeClaim
name : pvc-1
说明: 你必须为 spec.resources.requests.storage
指定一个值,并且你指定的值必须大于或等于源卷的值。
结果是一个名称为 clone-of-pvc-1
的新 PVC 与指定的源 pvc-1
拥有相同的内容。
使用 一旦新的 PVC 可用,被克隆的 PVC 像其他 PVC 一样被使用。
可以预期的是,新创建的 PVC 是一个独立的对象。
可以独立使用、克隆、快照或删除它,而不需要考虑它的原始数据源 PVC。
这也意味着,源没有以任何方式链接到新创建的 PVC,它也可以被修改或删除,而不会影响到新创建的克隆。
3.7.10 - 存储容量 存储容量是有限的,并且会因为运行 Pod 的节点不同而变化:
网络存储可能并非所有节点都能够访问,或者对于某个节点存储是本地的。
特性状态: Kubernetes v1.24 [stable]
本页面描述了 Kubernetes 如何跟踪存储容量以及调度程序如何为了余下的尚未挂载的卷使用该信息将
Pod 调度 到能够访问到足够存储容量的节点上。
如果没有跟踪存储容量,调度程序可能会选择一个没有足够容量来提供卷的节点,并且需要多次调度重试。
准备开始 Kubernetes v1.25 包含了对存储容量跟踪的集群级 API 支持。
要使用它,你还必须使用支持容量跟踪的 CSI 驱动程序。请查阅你使用的 CSI 驱动程序的文档,
以了解此支持是否可用,如果可用,该如何使用它。如果你运行的不是
Kubernetes v1.25,请查看对应版本的 Kubernetes 文档。
API 这个特性有两个 API 扩展接口:
调度 如果有以下情况,存储容量信息将会被 Kubernetes 调度程序使用:
Pod 使用的卷还没有被创建, 卷使用引用了 CSI 驱动的 StorageClass ,
并且使用了 WaitForFirstConsumer
卷绑定模式 , 驱动程序的 CSIDriver
对象的 StorageCapacity
被设置为 true。 在这种情况下,调度程序仅考虑将 Pod 调度到有足够存储容量的节点上。这个检测非常简单,
仅将卷的大小与 CSIStorageCapacity
对象中列出的容量进行比较,并使用包含该节点的拓扑。
对于具有 Immediate
卷绑定模式的卷,存储驱动程序将决定在何处创建该卷,而不取决于将使用该卷的 Pod。
然后,调度程序将 Pod 调度到创建卷后可使用该卷的节点上。
对于 CSI 临时卷 ,
调度总是在不考虑存储容量的情况下进行。
这是基于这样的假设:该卷类型仅由节点本地的特殊 CSI 驱动程序使用,并且不需要大量资源。
重新调度 当为带有 WaitForFirstConsumer
的卷的 Pod 来选择节点时,该决定仍然是暂定的。
下一步是要求 CSI 存储驱动程序创建卷,并提示该卷在被选择的节点上可用。
因为 Kubernetes 可能会根据已经过时的存储容量信息来选择一个节点,因此可能无法真正创建卷。
然后就会重置节点选择,Kubernetes 调度器会再次尝试为 Pod 查找节点。
限制 存储容量跟踪增加了调度器第一次尝试即成功的机会,但是并不能保证这一点,因为调度器必须根据可能过期的信息来进行决策。
通常,与没有任何存储容量信息的调度相同的重试机制可以处理调度失败。
当 Pod 使用多个卷时,调度可能会永久失败:一个卷可能已经在拓扑段中创建,而该卷又没有足够的容量来创建另一个卷,
要想从中恢复,必须要进行手动干预,比如通过增加存储容量或者删除已经创建的卷。
接下来 3.7.11 - Windows 存储 此页面提供特定于 Windows 操作系统的存储概述。
持久存储 Windows 有一个分层文件系统驱动程序用来挂载容器层和创建基于 NTFS 的文件系统拷贝。
容器中的所有文件路径仅在该容器的上下文中解析。
使用 Docker 时,卷挂载只能是容器中的目录,而不能是单个文件。此限制不适用于 containerd。 卷挂载不能将文件或目录映射回宿主文件系统。 不支持只读文件系统,因为 Windows 注册表和 SAM 数据库始终需要写访问权限。不过,Windows 支持只读的卷。 不支持卷的用户掩码和访问许可,因为宿主与容器之间并不共享 SAM,二者之间不存在映射关系。
所有访问许可都是在容器上下文中解析的。 因此,Windows 节点不支持以下存储功能:
卷子路径挂载:只能在 Windows 容器上挂载整个卷 Secret 的子路径挂载 宿主挂载映射 只读的根文件系统(映射的卷仍然支持 readOnly
) 块设备映射 内存作为存储介质(例如 emptyDir.medium
设置为 Memory
) 类似 UID/GID、各用户不同的 Linux 文件系统访问许可等文件系统特性 使用 DefaultMode 设置 Secret 权限
(因为该特性依赖 UID/GID) 基于 NFS 的存储和卷支持 扩展已挂载卷(resizefs) 使用 Kubernetes 卷 ,
对数据持久性和 Pod 卷共享有需求的复杂应用也可以部署到 Kubernetes 上。
管理与特定存储后端或协议相关的持久卷时,相关的操作包括:对卷的制备(Provisioning)、
去配(De-provisioning)和调整大小,将卷挂接到 Kubernetes 节点或从节点上解除挂接,
将卷挂载到需要持久数据的 Pod 中的某容器上或从容器上卸载。
卷管理组件作为 Kubernetes 卷插件 发布。
Windows 支持以下类型的 Kubernetes 卷插件:
树内(In-Tree)卷插件 以下树内(In-Tree)插件支持 Windows 节点上的持久存储:
3.7.12 - 卷健康监测 特性状态: Kubernetes v1.21 [alpha]
CSI 卷健康监测支持 CSI 驱动从底层的存储系统着手,
探测异常的卷状态,并以事件的形式上报到 PVCs
或 Pods .
卷健康监测 Kubernetes 卷健康监测 是 Kubernetes 容器存储接口(CSI)实现的一部分。
卷健康监测特性由两个组件实现:外部健康监测控制器和 kubelet 。
如果 CSI 驱动器通过控制器的方式支持卷健康监测特性,那么只要在 CSI 卷上监测到异常卷状态,就会在
PersistentVolumeClaim (PVC)
中上报一个事件。
外部健康监测控制器 也会监测节点失效事件。
如果要启动节点失效监测功能,你可以设置标志 enable-node-watcher
为 true
。
当外部健康监测器检测到节点失效事件,控制器会报送一个事件,该事件会在 PVC 上继续上报,
以表明使用此 PVC 的 Pod 正位于一个失效的节点上。
如果 CSI 驱动程序支持节点测的卷健康检测,那当在 CSI 卷上检测到异常卷时,
会在使用该 PVC 的每个Pod 上触发一个事件。
此外,卷运行状况信息作为 Kubelet VolumeStats 指标公开。
添加了一个新的指标 kubelet_volume_stats_health_status_abnormal。
该指标包括两个标签:namespace
和 persistentvolumeclaim
。
计数为 1 或 0。1 表示卷不正常,0 表示卷正常。更多信息请访问KEP 。
说明: 你需要启用
CSIVolumeHealth
特性门控 ,
才能在节点上使用此特性。
接下来 参阅 CSI 驱动程序文档 ,
可以找出有哪些 CSI 驱动程序实现了此特性。
3.7.13 - 特定于节点的卷数限制 此页面描述了各个云供应商可关联至一个节点的最大卷数。
谷歌、亚马逊和微软等云供应商通常对可以关联到节点的卷数量进行限制。
Kubernetes 需要尊重这些限制。 否则,在节点上调度的 Pod 可能会卡住去等待卷的关联。
Kubernetes 的默认限制 The Kubernetes 调度器对关联于一个节点的卷数有默认限制:
自定义限制 你可以通过设置 KUBE_MAX_PD_VOLS
环境变量的值来设置这些限制,然后再启动调度器。
CSI 驱动程序可能具有不同的过程,关于如何自定义其限制请参阅相关文档。
如果设置的限制高于默认限制,请谨慎使用。请参阅云提供商的文档以确保节点可支持你设置的限制。
此限制应用于整个集群,所以它会影响所有节点。
动态卷限制 特性状态: Kubernetes v1.17 [stable]
以下卷类型支持动态卷限制。
Amazon EBS Google Persistent Disk Azure Disk CSI 对于由内建插件管理的卷,Kubernetes 会自动确定节点类型并确保节点上可关联的卷数目合规。 例如:
3.8 - 配置 Kubernetes 为配置 Pods 提供的资源。
3.8.1 - 配置最佳实践 本文档重点介绍并整合了整个用户指南、入门文档和示例中介绍的配置最佳实践。
这是一份不断改进的文件。
如果你认为某些内容缺失但可能对其他人有用,请不要犹豫,提交 Issue 或提交 PR。
一般配置提示 在推送到集群之前,配置文件应存储在版本控制中。
这允许你在必要时快速回滚配置更改。
它还有助于集群重新创建和恢复。 使用 YAML 而不是 JSON 编写配置文件。虽然这些格式几乎可以在所有场景中互换使用,但 YAML 往往更加用户友好。 另请注意,可以在目录上调用许多 kubectl
命令。
例如,你可以在配置文件的目录中调用 kubectl apply
。 除非必要,否则不指定默认值:简单的最小配置会降低错误的可能性。 “独立的“ Pod 与 ReplicaSet 、Deployment 和 Job 服务 在创建相应的后端工作负载(Deployment 或 ReplicaSet),以及在需要访问它的任何工作负载之前创建
服务 。
当 Kubernetes 启动容器时,它提供指向启动容器时正在运行的所有服务的环境变量。
例如,如果存在名为 foo
的服务,则所有容器将在其初始环境中获得以下变量。
FOO_SERVICE_HOST = <the host the Service is running on>
FOO_SERVICE_PORT = <the port the Service is running on>
这确实意味着在顺序上的要求 - 必须在 Pod
本身被创建之前创建 Pod
想要访问的任何 Service
,
否则将环境变量不会生效。DNS 没有此限制。
一个可选(尽管强烈推荐)的集群插件
是 DNS 服务器。DNS 服务器为新的 Services
监视 Kubernetes API,并为每个创建一组 DNS 记录。
如果在整个集群中启用了 DNS,则所有 Pod
应该能够自动对 Services
进行名称解析。 不要为 Pod 指定 hostPort
,除非非常有必要这样做。
当你为 Pod 绑定了 hostPort
,那么能够运行该 Pod 的节点就有限了,因为每个 <hostIP, hostPort, protocol>
组合必须是唯一的。
如果你没有明确指定 hostIP
和 protocol
,
Kubernetes 将使用 0.0.0.0
作为默认的 hostIP
,使用 TCP
作为默认的 protocol
。
如果你只需要访问端口以进行调试,则可以使用
apiserver proxy
或
kubectl port-forward
。
如果你明确需要在节点上公开 Pod 的端口,请在使用 hostPort
之前考虑使用
NodePort 服务。
避免使用 hostNetwork
,原因与 hostPort
相同。 当你不需要 kube-proxy
负载均衡时,
使用无头服务
(ClusterIP
被设置为 None
)进行服务发现。 使用标签 定义并使用标签 来识别应用程序
或 Deployment 的 语义属性 ,例如 { app.kubernetes.io/name: MyApp, tier: frontend, phase: test, deployment: v3 }
。
你可以使用这些标签为其他资源选择合适的 Pod;
例如,一个选择所有 tier: frontend
Pod 的服务,或者 app.kubernetes.io/name: MyApp
的所有 phase: test
组件。
有关此方法的示例,请参阅 guestbook 。
通过从选择器中省略特定发行版的标签,可以使服务跨越多个 Deployment。
当你需要不停机的情况下更新正在运行的服务,可以使用 Deployment 。
Deployment 描述了对象的期望状态,并且如果对该规约的更改被成功应用,则 Deployment
控制器以受控速率将实际状态改变为期望状态。
你可以操纵标签进行调试。
由于 Kubernetes 控制器(例如 ReplicaSet)和服务使用选择器标签来匹配 Pod,
从 Pod 中删除相关标签将阻止其被控制器考虑或由服务提供服务流量。
如果删除现有 Pod 的标签,其控制器将创建一个新的 Pod 来取代它。
这是在“隔离“环境中调试先前“活跃“的 Pod 的有用方法。
要以交互方式删除或添加标签,请使用 kubectl label
。 使用 kubectl 使用 kubectl apply -f <directory>
。
它在 <directory>
中的所有 .yaml
、.yml
和 .json
文件中查找 Kubernetes 配置,并将其传递给 apply
。 使用标签选择器进行 get
和 delete
操作,而不是特定的对象名称。 请参阅标签选择器 和
有效使用标签 部分。 使用 kubectl create deployment
和 kubectl expose
来快速创建单容器 Deployment 和 Service。
有关示例,请参阅使用服务访问集群中的应用程序 。 3.8.2 - ConfigMap ConfigMap 是一种 API 对象,用来将非机密性的数据保存到键值对中。使用时, Pods 可以将其用作环境变量、命令行参数或者存储卷中的配置文件。
ConfigMap 将你的环境配置信息和 容器镜像 解耦,便于应用配置的修改。
注意: ConfigMap 并不提供保密或者加密功能。
如果你想存储的数据是机密的,请使用 Secret ,
或者使用其他第三方工具来保证你的数据的私密性,而不是用 ConfigMap。
动机 使用 ConfigMap 来将你的配置数据和应用程序代码分开。
比如,假设你正在开发一个应用,它可以在你自己的电脑上(用于开发)和在云上
(用于实际流量)运行。
你的代码里有一段是用于查看环境变量 DATABASE_HOST
,在本地运行时,
你将这个变量设置为 localhost
,在云上,你将其设置为引用 Kubernetes 集群中的
公开数据库组件的 服务 。
这让你可以获取在云中运行的容器镜像,并且如果有需要的话,在本地调试完全相同的代码。
ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过
1 MiB。如果你需要保存超出此尺寸限制的数据,你可能希望考虑挂载存储卷
或者使用独立的数据库或者文件服务。
ConfigMap 对象 ConfigMap 是一个 API 对象 ,
让你可以存储其他对象所需要使用的配置。
和其他 Kubernetes 对象都有一个 spec
不同的是,ConfigMap 使用 data
和
binaryData
字段。这些字段能够接收键-值对作为其取值。data
和 binaryData
字段都是可选的。data
字段设计用来保存 UTF-8 字符串,而 binaryData
则被设计用来保存二进制数据作为 base64 编码的字串。
ConfigMap 的名字必须是一个合法的
DNS 子域名 。
data
或 binaryData
字段下面的每个键的名称都必须由字母数字字符或者
-
、_
或 .
组成。在 data
下保存的键名不可以与在 binaryData
下出现的键名有重叠。
从 v1.19 开始,你可以添加一个 immutable
字段到 ConfigMap 定义中,
创建不可变更的 ConfigMap 。
ConfigMaps 和 Pods 你可以写一个引用 ConfigMap 的 Pod 的 spec
,并根据 ConfigMap 中的数据在该
Pod 中配置容器。这个 Pod 和 ConfigMap 必须要在同一个
名字空间 中。
说明: 静态 Pod 中的
spec
字段不能引用 ConfigMap 或任何其他 API 对象。
这是一个 ConfigMap 的示例,它的一些键只有一个值,其他键的值看起来像是
配置的片段格式。
apiVersion : v1
kind : ConfigMap
metadata :
name : game-demo
data :
# 类属性键;每一个键都映射到一个简单的值
player_initial_lives : "3"
ui_properties_file_name : "user-interface.properties"
# 类文件键
game.properties : |
enemy.types=aliens,monsters
player.maximum-lives=5
user-interface.properties : |
color.good=purple
color.bad=yellow
allow.textmode=true
你可以使用四种方式来使用 ConfigMap 配置 Pod 中的容器:
在容器命令和参数内 容器的环境变量 在只读卷里面添加一个文件,让应用来读取 编写代码在 Pod 中运行,使用 Kubernetes API 来读取 ConfigMap 这些不同的方法适用于不同的数据使用方式。
对前三个方法,kubelet
使用 ConfigMap 中的数据在 Pod 中启动容器。
第四种方法意味着你必须编写代码才能读取 ConfigMap 和它的数据。然而,
由于你是直接使用 Kubernetes API,因此只要 ConfigMap 发生更改,
你的应用就能够通过订阅来获取更新,并且在这样的情况发生的时候做出反应。
通过直接进入 Kubernetes API,这个技术也可以让你能够获取到不同的名字空间里的 ConfigMap。
下面是一个 Pod 的示例,它通过使用 game-demo
中的值来配置一个 Pod:
ConfigMap 不会区分单行属性值和多行类似文件的值,重要的是 Pods
和其他对象如何使用这些值。
上面的例子定义了一个卷并将它作为 /config
文件夹挂载到 demo
容器内,
创建两个文件,/config/game.properties
和
/config/user-interface.properties
,
尽管 ConfigMap 中包含了四个键。
这是因为 Pod 定义中在 volumes
节指定了一个 items
数组。
如果你完全忽略 items
数组,则 ConfigMap 中的每个键都会变成一个与该键同名的文件,
因此你会得到四个文件。
使用 ConfigMap ConfigMap 可以作为数据卷挂载。ConfigMap 也可被系统的其他组件使用,
而不一定直接暴露给 Pod。例如,ConfigMap 可以保存系统中其他组件要使用的配置数据。
ConfigMap 最常见的用法是为同一命名空间里某 Pod 中运行的容器执行配置。
你也可以单独使用 ConfigMap。
比如,你可能会遇到基于 ConfigMap 来调整其行为的
插件 或者
operator 。
在 Pod 中将 ConfigMap 当做文件使用 要在一个 Pod 的存储卷中使用 ConfigMap:
创建一个 ConfigMap 对象或者使用现有的 ConfigMap 对象。多个 Pod 可以引用同一个
ConfigMap。 修改 Pod 定义,在 spec.volumes[]
下添加一个卷。
为该卷设置任意名称,之后将 spec.volumes[].configMap.name
字段设置为对你的
ConfigMap 对象的引用。 为每个需要该 ConfigMap 的容器添加一个 .spec.containers[].volumeMounts[]
。
设置 .spec.containers[].volumeMounts[].readOnly=true
并将
.spec.containers[].volumeMounts[].mountPath
设置为一个未使用的目录名,
ConfigMap 的内容将出现在该目录中。 更改你的镜像或者命令行,以便程序能够从该目录中查找文件。ConfigMap 中的每个
data
键会变成 mountPath
下面的一个文件名。 下面是一个将 ConfigMap 以卷的形式进行挂载的 Pod 示例:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
containers :
- name : mypod
image : redis
volumeMounts :
- name : foo
mountPath : "/etc/foo"
readOnly : true
volumes :
- name : foo
configMap :
name : myconfigmap
你希望使用的每个 ConfigMap 都需要在 spec.volumes
中被引用到。
如果 Pod 中有多个容器,则每个容器都需要自己的 volumeMounts
块,但针对每个
ConfigMap,你只需要设置一个 spec.volumes
块。
被挂载的 ConfigMap 内容会被自动更新 当卷中使用的 ConfigMap 被更新时,所投射的键最终也会被更新。
kubelet 组件会在每次周期性同步时检查所挂载的 ConfigMap 是否为最新。
不过,kubelet 使用的是其本地的高速缓存来获得 ConfigMap 的当前值。
高速缓存的类型可以通过
KubeletConfiguration 结构 .
的 ConfigMapAndSecretChangeDetectionStrategy
字段来配置。
ConfigMap 既可以通过 watch 操作实现内容传播(默认形式),也可实现基于 TTL
的缓存,还可以直接经过所有请求重定向到 API 服务器。
因此,从 ConfigMap 被更新的那一刻算起,到新的主键被投射到 Pod 中去,
这一时间跨度可能与 kubelet 的同步周期加上高速缓存的传播延迟相等。
这里的传播延迟取决于所选的高速缓存类型
(分别对应 watch 操作的传播延迟、高速缓存的 TTL 时长或者 0)。
以环境变量方式使用的 ConfigMap 数据不会被自动更新。
更新这些数据需要重新启动 Pod。
说明: 使用 ConfigMap 作为
subPath 卷挂载的容器将不会收到 ConfigMap 的更新。
不可变更的 ConfigMap 特性状态: Kubernetes v1.21 [stable]
Kubernetes 特性 Immutable Secret 和 ConfigMaps 提供了一种将各个
Secret 和 ConfigMap 设置为不可变更的选项。对于大量使用 ConfigMap 的集群
(至少有数万个各不相同的 ConfigMap 给 Pod 挂载)而言,禁止更改
ConfigMap 的数据有以下好处:
保护应用,使之免受意外(不想要的)更新所带来的负面影响。 通过大幅降低对 kube-apiserver 的压力提升集群性能,
这是因为系统会关闭对已标记为不可变更的 ConfigMap 的监视操作。 此功能特性由 ImmutableEphemeralVolumes
特性门控 来控制。
你可以通过将 immutable
字段设置为 true
创建不可变更的 ConfigMap。
例如:
apiVersion : v1
kind : ConfigMap
metadata :
...
data :
...
immutable : true
一旦某 ConfigMap 被标记为不可变更,则 无法 逆转这一变化,,也无法更改
data
或 binaryData
字段的内容。你只能删除并重建 ConfigMap。
因为现有的 Pod 会维护一个已被删除的 ConfigMap 的挂载点,建议重新创建这些 Pods。
接下来 3.8.3 - Secret Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。
这样的信息可能会被放在 Pod 规约中或者镜像中。
使用 Secret 意味着你不需要在应用程序代码中包含机密数据。
由于创建 Secret 可以独立于使用它们的 Pod,
因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。
Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施,
例如避免将机密数据写入非易失性存储。
Secret 类似于 ConfigMap
但专门用于保存机密数据。
注意: 默认情况下,Kubernetes Secret 未加密地存储在 API 服务器的底层数据存储(etcd)中。
任何拥有 API 访问权限的人都可以检索或修改 Secret,任何有权访问 etcd 的人也可以。
此外,任何有权限在命名空间中创建 Pod 的人都可以使用该访问权限读取该命名空间中的任何 Secret;
这包括间接访问,例如创建 Deployment 的能力。
为了安全地使用 Secret,请至少执行以下步骤:
为 Secret 启用静态加密 。 以最小特权访问 Secret 并启用或配置 RBAC 规则 。 限制 Secret 对特定容器的访问。 考虑使用外部 Secret 存储驱动 。有关管理和提升 Secret 安全性的指南,请参阅
Kubernetes Secret 良好实践 。
参见 Secret 的信息安全 了解详情。
Secret 的使用 Pod 可以用三种方式之一来使用 Secret:
Kubernetes 控制面也使用 Secret;
例如,引导令牌 Secret
是一种帮助自动化节点注册的机制。
Secret 的替代方案 除了使用 Secret 来保护机密数据,你也可以选择一些替代方案。
下面是一些选项:
如果你的云原生组件需要执行身份认证来访问你所知道的、在同一 Kubernetes 集群中运行的另一个应用,
你可以使用 ServiceAccount
及其令牌来标识你的客户端身份。 你可以运行的第三方工具也有很多,这些工具可以运行在集群内或集群外,提供机密数据管理。
例如,这一工具可能是 Pod 通过 HTTPS 访问的一个服务,该服务在客户端能够正确地通过身份认证
(例如,通过 ServiceAccount 令牌)时,提供机密数据内容。 就身份认证而言,你可以为 X.509 证书实现一个定制的签名者,并使用
CertificateSigningRequest
来让该签名者为需要证书的 Pod 发放证书。 你可以使用一个设备插件
来将节点本地的加密硬件暴露给特定的 Pod。例如,你可以将可信任的 Pod
调度到提供可信平台模块(Trusted Platform Module,TPM)的节点上。
这类节点是另行配置的。 你还可以将如上选项的两种或多种进行组合,包括直接使用 Secret 对象本身也是一种选项。
例如:实现(或部署)一个 operator ,
从外部服务取回生命期很短的会话令牌,之后基于这些生命期很短的会话令牌来创建 Secret。
运行在集群中的 Pod 可以使用这些会话令牌,而 Operator 则确保这些令牌是合法的。
这种责权分离意味着你可以运行那些不了解会话令牌如何发放与刷新的确切机制的 Pod。
使用 Secret 创建 Secret 对 Secret 名称与数据的约束 Secret 对象的名称必须是合法的
DNS 子域名 。
在为创建 Secret 编写配置文件时,你可以设置 data
与/或 stringData
字段。
data
和 stringData
字段都是可选的。data
字段中所有键值都必须是 base64
编码的字符串。如果不希望执行这种 base64 字符串的转换操作,你可以选择设置
stringData
字段,其中可以使用任何字符串作为其取值。
data
和 stringData
中的键名只能包含字母、数字、-
、_
或 .
字符。
stringData
字段中的所有键值对都会在内部被合并到 data
字段中。
如果某个主键同时出现在 data
和 stringData
字段中,stringData
所指定的键值具有高优先级。
尺寸限制 每个 Secret 的尺寸最多为 1MiB。施加这一限制是为了避免用户创建非常大的 Secret,
进而导致 API 服务器和 kubelet 内存耗尽。不过创建很多小的 Secret 也可能耗尽内存。
你可以使用资源配额 来约束每个名字空间中
Secret(或其他资源)的个数。
编辑 Secret 你可以使用 kubectl 来编辑一个已有的 Secret:
kubectl edit secrets mysecret
这一命令会启动你的默认编辑器,允许你更新 data
字段中存放的 base64 编码的 Secret 值;
例如:
# 请编辑以下对象。以 `#` 开头的几行将被忽略,
# 且空文件将放弃编辑。如果保存此文件时出错,
# 则重新打开此文件时也会有相关故障。
apiVersion : v1
data :
username : YWRtaW4=
password : MWYyZDFlMmU2N2Rm
kind : Secret
metadata :
annotations :
kubectl.kubernetes.io/last-applied-configuration : { ... }
creationTimestamp : 2020-01-22T18:41:56Z
name : mysecret
namespace : default
resourceVersion : "164619"
uid : cfee02d6-c137-11e5-8d73-42010af00002
type : Opaque
这一示例清单定义了一个 Secret,其 data
字段中包含两个主键:username
和 password
。
清单中的字段值是 Base64 字符串,不过,当你在 Pod 中使用 Secret 时,kubelet 为 Pod
及其中的容器提供的是解码 后的数据。
你可以在一个 Secret 中打包多个主键和数值,也可以选择使用多个 Secret,
完全取决于哪种方式最方便。
使用 Secret Secret 可以以数据卷的形式挂载,也可以作为环境变量
暴露给 Pod 中的容器使用。Secret 也可用于系统中的其他部分,而不是一定要直接暴露给 Pod。
例如,Secret 也可以包含系统中其他部分在替你与外部系统交互时要使用的凭证数据。
Kubernetes 会检查 Secret 的卷数据源,确保所指定的对象引用确实指向类型为 Secret
的对象。因此,如果 Pod 依赖于某 Secret,该 Secret 必须先于 Pod 被创建。
如果 Secret 内容无法取回(可能因为 Secret 尚不存在或者临时性地出现 API
服务器网络连接问题),kubelet 会周期性地重试 Pod 运行操作。kubelet 也会为该 Pod
报告 Event 事件,给出读取 Secret 时遇到的问题细节。
可选的 Secret 当你定义一个基于 Secret 的环境变量时,你可以将其标记为可选。
默认情况下,所引用的 Secret 都是必需的。
只有所有非可选的 Secret 都可用时,Pod 中的容器才能启动运行。
如果 Pod 引用了 Secret 中的特定主键,而虽然 Secret 本身存在,对应的主键不存在,
Pod 启动也会失败。
在 Pod 中以文件形式使用 Secret 如果你希望在 Pod 中访问 Secret 内的数据,一种方式是让 Kubernetes 将 Secret
以 Pod 中一个或多个容器的文件系统中的文件的形式呈现出来。
要配置这种行为,你需要:
创建一个 Secret 或者使用已有的 Secret。多个 Pod 可以引用同一个 Secret。 更改 Pod 定义,在 .spec.volumes[]
下添加一个卷。根据需要为卷设置其名称,
并将 .spec.volumes[].secret.secretName
字段设置为 Secret 对象的名称。 为每个需要该 Secret 的容器添加 .spec.containers[].volumeMounts[]
。
并将 .spec.containers[].volumeMounts[].readOnly
设置为 true
,
将 .spec.containers[].volumeMounts[].mountPath
设置为希望 Secret
被放置的、目前尚未被使用的路径名。 更改你的镜像或命令行,以便程序读取所设置的目录下的文件。Secret 的 data
映射中的每个主键都成为 mountPath
下面的文件名。 下面是一个通过卷来挂载名为 mysecret
的 Secret 的 Pod 示例:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
containers :
- name : mypod
image : redis
volumeMounts :
- name : foo
mountPath : "/etc/foo"
readOnly : true
volumes :
- name : foo
secret :
secretName : mysecret
optional : false # 默认设置,意味着 "mysecret" 必须已经存在
你要访问的每个 Secret 都需要通过 .spec.volumes
来引用。
如果 Pod 中包含多个容器,则每个容器需要自己的 volumeMounts
块,
不过针对每个 Secret 而言,只需要一份 .spec.volumes
设置。
说明: Kubernetes v1.22 版本之前都会自动创建用来访问 Kubernetes API 的凭证。
这一老的机制是基于创建可被挂载到 Pod 中的令牌 Secret 来实现的。
在最近的版本中,包括 Kubernetes v1.25 中,API 凭据是直接通过
TokenRequest
API 来获得的,这一凭据会使用投射卷
挂载到 Pod 中。使用这种方式获得的令牌有确定的生命期,并且在挂载它们的 Pod
被删除时自动作废。
你仍然可以手动创建
服务账号令牌。例如,当你需要一个永远都不过期的令牌时。
不过,仍然建议使用 TokenRequest
子资源来获得访问 API 服务器的令牌。
你可以使用 kubectl create token
命令调用 TokenRequest
API 获得令牌。
将 Secret 键投射到特定目录 你也可以控制 Secret 键所投射到的卷中的路径。
你可以使用 .spec.volumes[].secret.items
字段来更改每个主键的目标路径:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
containers :
- name : mypod
image : redis
volumeMounts :
- name : foo
mountPath : "/etc/foo"
readOnly : true
volumes :
- name : foo
secret :
secretName : mysecret
items :
- key : username
path : my-group/my-username
将发生的事情如下:
mysecret
中的键 username
会出现在容器中的路径为 /etc/foo/my-group/my-username
,
而不是 /etc/foo/username
。Secret 对象的 password
键不会被投射。 如果使用了 .spec.volumes[].secret.items
,则只有 items
中指定了的主键会被投射。
如果要使用 Secret 中的所有主键,则需要将它们全部枚举到 items
字段中。
如果你显式地列举了主键,则所列举的主键都必须在对应的 Secret 中存在。
否则所在的卷不会被创建。
Secret 文件的访问权限 你可以为某个 Secret 主键设置 POSIX 文件访问权限位。
如果你不指定访问权限,默认会使用 0644
。
你也可以为整个 Secret 卷设置默认的访问模式,然后再根据需要在主键层面重载。
例如,你可以像下面这样设置默认的模式:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
containers :
- name : mypod
image : redis
volumeMounts :
- name : foo
mountPath : "/etc/foo"
volumes :
- name : foo
secret :
secretName : mysecret
defaultMode : 0400
该 Secret 被挂载在 /etc/foo
下,Secret 卷挂载所创建的所有文件的访问模式都是 0400
。
说明: 如果你是使用 JSON 来定义 Pod 或 Pod 模板,需要注意 JSON 规范不支持八进制的记数方式。
你可以在 defaultMode
中设置十进制的值(例如,八进制中的 0400 在十进制中为 256)。
如果你使用 YAML 来编写定义,你可以用八进制值来设置 defaultMode
。
使用来自卷中的 Secret 值 在挂载了 Secret 卷的容器内,Secret 的主键都呈现为文件。
Secret 的取值都是 Base64 编码的,保存在这些文件中。
下面是在上例中的容器内执行命令的结果:
输出类似于:
username
password
输出类似于:
admin
输出类似于:
1f2d1e2e67df
容器中的程序要负责根据需要读取 Secret 数据。
挂载的 Secret 是被自动更新的 当卷中包含来自 Secret 的数据,而对应的 Secret 被更新,Kubernetes
会跟踪到这一操作并更新卷中的数据。更新的方式是保证最终一致性。
说明: 对于以 subPath 形式挂载 Secret 卷的容器而言,
它们无法收到自动的 Secret 更新。
Kubelet 组件会维护一个缓存,在其中保存节点上 Pod 卷中使用的 Secret 的当前主键和取值。
你可以配置 kubelet 如何检测所缓存数值的变化。
kubelet 配置 中的
configMapAndSecretChangeDetectionStrategy
字段控制 kubelet 所采用的策略。
默认的策略是 Watch
。
对 Secret 的更新操作既可以通过 API 的 watch 机制(默认)来传播,
基于设置了生命期的缓存获取,也可以通过 kubelet 的同步回路来从集群的 API
服务器上轮询获取。
因此,从 Secret 被更新到新的主键被投射到 Pod 中,中间存在一个延迟。
这一延迟的上限是 kubelet 的同步周期加上缓存的传播延迟,
其中缓存的传播延迟取决于所选择的缓存类型。
对应上一段中提到的几种传播机制,延迟时长为 watch 的传播延迟、所配置的缓存 TTL
或者对于直接轮询而言是零。
以环境变量的方式使用 Secret 如果需要在 Pod 中以环境变量
的形式使用 Secret:
创建 Secret(或者使用现有 Secret)。多个 Pod 可以引用同一个 Secret。 更改 Pod 定义,在要使用 Secret 键值的每个容器中添加与所使用的主键对应的环境变量。
读取 Secret 主键的环境变量应该在 env[].valueFrom.secretKeyRef
中填写 Secret
的名称和主键名称。 更改你的镜像或命令行,以便程序读取环境变量中保存的值。 下面是一个通过环境变量来使用 Secret 的示例 Pod:
apiVersion : v1
kind : Pod
metadata :
name : secret-env-pod
spec :
containers :
- name : mycontainer
image : redis
env :
- name : SECRET_USERNAME
valueFrom :
secretKeyRef :
name : mysecret
key : username
optional : false # 此值为默认值;意味着 "mysecret"
# 必须存在且包含名为 "username" 的主键
- name : SECRET_PASSWORD
valueFrom :
secretKeyRef :
name : mysecret
key : password
optional : false # 此值为默认值;意味着 "mysecret"
# 必须存在且包含名为 "password" 的主键
restartPolicy : Never
非法环境变量 对于通过 envFrom
字段来填充环境变量的 Secret 而言,
如果其中包含的主键不能被当做合法的环境变量名,这些主键会被忽略掉。
Pod 仍然可以启动。
如果你定义的 Pod 中包含非法的变量名称,则 Pod 可能启动失败,
会形成 reason 为 InvalidVariableNames
的事件,以及列举被略过的非法主键的消息。
下面的例子中展示了一个 Pod,引用的是名为 mysecret
的 Secret,
其中包含两个非法的主键:1badkey
和 2alsobad
。
输出类似于:
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON
0s 0s 1 dapi-test-pod Pod Warning InvalidEnvironmentVariableNames kubelet, 127.0.0.1 Keys [1badkey, 2alsobad] from the EnvFrom secret default/mysecret were skipped since they are considered invalid environment variable names.
通过环境变量使用 Secret 值 在通过环境变量来使用 Secret 的容器中,Secret 主键展现为普通的环境变量。
这些变量的取值是 Secret 数据的 Base64 解码值。
下面是在前文示例中的容器内执行命令的结果:
输出类似于:
admin
输出类似于:
1f2d1e2e67df
说明: 如果容器已经在通过环境变量来使用 Secret,Secret 更新在容器内是看不到的,
除非容器被重启。有一些第三方的解决方案,能够在 Secret 发生变化时触发容器重启。
容器镜像拉取 Secret 如果你尝试从私有仓库拉取容器镜像,你需要一种方式让每个节点上的 kubelet
能够完成与镜像库的身份认证。你可以配置 镜像拉取 Secret 来实现这点。
Secret 是在 Pod 层面来配置的。
Pod 的 imagePullSecrets
字段是一个对 Pod 所在的名字空间中的 Secret
的引用列表。你可以使用 imagePullSecrets
来将镜像仓库访问凭据传递给 kubelet。
kubelet 使用这个信息来替你的 Pod 拉取私有镜像。
参阅 Pod API 参考
中的 PodSpec
进一步了解 imagePullSecrets
字段。
使用 imagePullSecrets imagePullSecrets
字段是一个列表,包含对同一名字空间中 Secret 的引用。
你可以使用 imagePullSecrets
将包含 Docker(或其他)镜像仓库密码的 Secret
传递给 kubelet。kubelet 使用此信息来替 Pod 拉取私有镜像。
参阅 PodSpec API
进一步了解 imagePullSecrets
字段。
手动设定 imagePullSecret 你可以通过阅读容器镜像
文档了解如何设置 imagePullSecrets
。
设置 imagePullSecrets 为自动挂载 你可以手动创建 imagePullSecret
,并在一个 ServiceAccount 中引用它。
对使用该 ServiceAccount 创建的所有 Pod,或者默认使用该 ServiceAccount 创建的 Pod
而言,其 imagePullSecrets
字段都会设置为该服务账号。
请阅读向服务账号添加 ImagePullSecrets
来详细了解这一过程。
在静态 Pod 中使用 Secret 你不可以在静态 Pod
中使用 ConfigMap 或 Secret。
使用场景 使用场景:作为容器环境变量 创建 Secret:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
data :
USER_NAME : YWRtaW4=
PASSWORD : MWYyZDFlMmU2N2Rm
创建 Secret:
kubectl apply -f mysecret.yaml
使用 envFrom
来将 Secret 的所有数据定义为容器的环境变量。
来自 Secret 的主键成为 Pod 中的环境变量名称:
apiVersion : v1
kind : Pod
metadata :
name : secret-test-pod
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "/bin/sh" , "-c" , "env" ]
envFrom :
- secretRef :
name : mysecret
restartPolicy : Never
使用场景:带 SSH 密钥的 Pod 创建包含一些 SSH 密钥的 Secret:
kubectl create secret generic ssh-key-secret --from-file= ssh-privatekey= /path/to/.ssh/id_rsa --from-file= ssh-publickey= /path/to/.ssh/id_rsa.pub
输出类似于:
secret "ssh-key-secret" created
你也可以创建一个 kustomization.yaml
文件,在其 secretGenerator
字段中包含 SSH 密钥。
注意: 在提供你自己的 SSH 密钥之前要仔细思考:集群的其他用户可能有权访问该 Secret。
你也可以创建一个 SSH 私钥,代表一个你希望与你共享 Kubernetes 集群的其他用户分享的服务标识。
当凭据信息被泄露时,你可以收回该访问权限。
现在你可以创建一个 Pod,在其中访问包含 SSH 密钥的 Secret,并通过卷的方式来使用它:
apiVersion : v1
kind : Pod
metadata :
name : secret-test-pod
labels :
name : secret-test
spec :
volumes :
- name : secret-volume
secret :
secretName : ssh-key-secret
containers :
- name : ssh-test-container
image : mySshImage
volumeMounts :
- name : secret-volume
readOnly : true
mountPath : "/etc/secret-volume"
容器命令执行时,秘钥的数据可以在下面的位置访问到:
/etc/secret-volume/ssh-publickey
/etc/secret-volume/ssh-privatekey
容器就可以随便使用 Secret 数据来建立 SSH 连接。
使用场景:带有生产、测试环境凭据的 Pod 这一示例所展示的一个 Pod 会使用包含生产环境凭据的 Secret,另一个 Pod
使用包含测试环境凭据的 Secret。
你可以创建一个带有 secretGenerator
字段的 kustomization.yaml
文件或者运行
kubectl create secret
来创建 Secret。
kubectl create secret generic prod-db-secret --from-literal= username = produser --from-literal= password = Y4nys7f11
输出类似于:
secret "prod-db-secret" created
你也可以创建一个包含测试环境凭据的 Secret:
kubectl create secret generic test-db-secret --from-literal= username = testuser --from-literal= password = iluvtests
输出类似于:
secret "test-db-secret" created
说明: 特殊字符(例如 $
、\
、*
、=
和 !
)会被你的
Shell 解释,因此需要转义。
在大多数 Shell 中,对密码进行转义的最简单方式是用单引号('
)将其括起来。
例如,如果你的实际密码是 S!B\*d$zDsb
,则应通过以下方式执行命令:
kubectl create secret generic dev-db-secret --from-literal= username = devuser --from-literal= password = 'S!B\*d$zDsb='
你无需对文件中的密码(--from-file
)中的特殊字符进行转义。
现在生成 Pod:
cat <<EOF > pod.yaml
apiVersion: v1
kind: List
items:
- kind: Pod
apiVersion: v1
metadata:
name: prod-db-client-pod
labels:
name: prod-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: prod-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
- kind: Pod
apiVersion: v1
metadata:
name: test-db-client-pod
labels:
name: test-db-client
spec:
volumes:
- name: secret-volume
secret:
secretName: test-db-secret
containers:
- name: db-client-container
image: myClientImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
EOF
将 Pod 添加到同一 kustomization.yaml
文件中:
cat <<EOF >> kustomization.yaml
resources:
- pod.yaml
EOF
通过下面的命令在 API 服务器上应用所有这些对象:
两个文件都会在其文件系统中出现下面的文件,文件中内容是各个容器的环境值:
/etc/secret-volume/username
/etc/secret-volume/password
注意这两个 Pod 的规约中只有一个字段不同。
这便于基于相同的 Pod 模板生成具有不同能力的 Pod。
你可以通过使用两个服务账号来进一步简化这一基本的 Pod 规约:
prod-user
服务账号使用 prod-db-secret
test-user
服务账号使用 test-db-secret
Pod 规约简化为:
apiVersion : v1
kind : Pod
metadata :
name : prod-db-client-pod
labels :
name : prod-db-client
spec :
serviceAccount : prod-db-client
containers :
- name : db-client-container
image : myClientImage
使用场景:在 Secret 卷中带句点的文件 通过定义以句点(.
)开头的主键,你可以“隐藏”你的数据。
这些主键代表的是以句点开头的文件或“隐藏”文件。
例如,当下面的 Secret 被挂载到 secret-volume
卷中时:
apiVersion : v1
kind : Secret
metadata :
name : dotfile-secret
data :
.secret-file : dmFsdWUtMg0KDQo=
---
apiVersion : v1
kind : Pod
metadata :
name : secret-dotfiles-pod
spec :
volumes :
- name : secret-volume
secret :
secretName : dotfile-secret
containers :
- name : dotfile-test-container
image : registry.k8s.io/busybox
command :
- ls
- "-l"
- "/etc/secret-volume"
volumeMounts :
- name : secret-volume
readOnly : true
mountPath : "/etc/secret-volume"
卷中会包含一个名为 .secret-file
的文件,并且容器 dotfile-test-container
中此文件位于路径 /etc/secret-volume/.secret-file
处。
说明: 以句点开头的文件会在 ls -l
的输出中被隐藏起来;
列举目录内容时你必须使用 ls -la
才能看到它们。
使用场景:仅对 Pod 中一个容器可见的 Secret 考虑一个需要处理 HTTP 请求,执行某些复杂的业务逻辑,之后使用 HMAC
来对某些消息进行签名的程序。因为这一程序的应用逻辑很复杂,
其中可能包含未被注意到的远程服务器文件读取漏洞,
这种漏洞可能会把私钥暴露给攻击者。
这一程序可以分隔成两个容器中的两个进程:前端容器要处理用户交互和业务逻辑,
但无法看到私钥;签名容器可以看到私钥,并对来自前端的简单签名请求作出响应
(例如,通过本地主机网络)。
采用这种划分的方法,攻击者现在必须欺骗应用服务器来做一些其他操作,
而这些操作可能要比读取一个文件要复杂很多。
Secret 的类型 创建 Secret 时,你可以使用 Secret
资源的 type
字段,或者与其等价的 kubectl
命令行参数(如果有的话)为其设置类型。
Secret 类型有助于对 Secret 数据进行编程处理。
Kubernetes 提供若干种内置的类型,用于一些常见的使用场景。
针对这些类型,Kubernetes 所执行的合法性检查操作以及对其所实施的限制各不相同。
内置类型 用法 Opaque
用户定义的任意数据 kubernetes.io/service-account-token
服务账号令牌 kubernetes.io/dockercfg
~/.dockercfg
文件的序列化形式kubernetes.io/dockerconfigjson
~/.docker/config.json
文件的序列化形式kubernetes.io/basic-auth
用于基本身份认证的凭据 kubernetes.io/ssh-auth
用于 SSH 身份认证的凭据 kubernetes.io/tls
用于 TLS 客户端或者服务器端的数据 bootstrap.kubernetes.io/token
启动引导令牌数据
通过为 Secret 对象的 type
字段设置一个非空的字符串值,你也可以定义并使用自己
Secret 类型(如果 type
值为空字符串,则被视为 Opaque
类型)。
Kubernetes 并不对类型的名称作任何限制。不过,如果你要使用内置类型之一,
则你必须满足为该类型所定义的所有要求。
如果你要定义一种公开使用的 Secret 类型,请遵守 Secret 类型的约定和结构,
在类型名签名添加域名,并用 /
隔开。
例如:cloud-hosting.example.net/cloud-api-credentials
。
Opaque Secret 当 Secret 配置文件中未作显式设定时,默认的 Secret 类型是 Opaque
。
当你使用 kubectl
来创建一个 Secret 时,你会使用 generic
子命令来标明要创建的是一个 Opaque
类型 Secret。
例如,下面的命令会创建一个空的 Opaque
类型 Secret 对象:
kubectl create secret generic empty-secret
kubectl get secret empty-secret
输出类似于
NAME TYPE DATA AGE
empty-secret Opaque 0 2m6s
DATA
列显示 Secret 中保存的数据条目个数。
在这个例子种,0
意味着你刚刚创建了一个空的 Secret。
服务账号令牌 Secret 类型为 kubernetes.io/service-account-token
的 Secret
用来存放标识某服务账号 的令牌凭据。
从 v1.22 开始,这种类型的 Secret 不再被用来向 Pod 中加载凭据数据,
建议通过 TokenRequest
API 来获得令牌,而不是使用服务账号令牌 Secret 对象。
通过 TokenRequest
API 获得的令牌比保存在 Secret 对象中的令牌更加安全,
因为这些令牌有着被限定的生命期,并且不会被其他 API 客户端读取。
你可以使用 kubectl create token
命令调用 TokenRequest
API 获得令牌。
只有在你无法使用 TokenRequest
API 来获取令牌,
并且你能够接受因为将永不过期的令牌凭据写入到可读取的 API 对象而带来的安全风险时,
才应该创建服务账号令牌 Secret 对象。
使用这种 Secret 类型时,你需要确保对象的注解 kubernetes.io/service-account-name
被设置为某个已有的服务账号名称。
如果你同时负责 ServiceAccount 和 Secret 对象的创建,应该先创建 ServiceAccount 对象。
当 Secret 对象被创建之后,某个 Kubernetes控制器 会填写
Secret 的其它字段,例如 kubernetes.io/service-account.uid
注解以及 data
字段中的
token
键值,使之包含实际的令牌内容。
下面的配置实例声明了一个服务账号令牌 Secret:
apiVersion : v1
kind : Secret
metadata :
name : secret-sa-sample
annotations :
kubernetes.io/service-account.name : "sa-name"
type : kubernetes.io/service-account-token
data :
# 你可以像 Opaque Secret 一样在这里添加额外的键/值偶对
extra : YmFyCg==
创建了 Secret 之后,等待 Kubernetes 在 data
字段中填充 token
主键。
参考 ServiceAccount
文档了解服务账号的工作原理。你也可以查看
Pod
资源中的 automountServiceAccountToken
和 serviceAccountName
字段文档,
进一步了解从 Pod 中引用服务账号凭据。
Docker 配置 Secret 你可以使用下面两种 type
值之一来创建 Secret,用以存放用于访问容器镜像仓库的凭据:
kubernetes.io/dockercfg
kubernetes.io/dockerconfigjson
kubernetes.io/dockercfg
是一种保留类型,用来存放 ~/.dockercfg
文件的序列化形式。
该文件是配置 Docker 命令行的一种老旧形式。使用此 Secret 类型时,你需要确保
Secret 的 data
字段中包含名为 .dockercfg
的主键,其对应键值是用 base64
编码的某 ~/.dockercfg
文件的内容。
类型 kubernetes.io/dockerconfigjson
被设计用来保存 JSON 数据的序列化形式,
该 JSON 也遵从 ~/.docker/config.json
文件的格式规则,而后者是 ~/.dockercfg
的新版本格式。使用此 Secret 类型时,Secret 对象的 data
字段必须包含
.dockerconfigjson
键,其键值为 base64 编码的字符串包含 ~/.docker/config.json
文件的内容。
下面是一个 kubernetes.io/dockercfg
类型 Secret 的示例:
apiVersion : v1
kind : Secret
metadata :
name : secret-dockercfg
type : kubernetes.io/dockercfg
data :
.dockercfg : |
"<base64 encoded ~/.dockercfg file>"
说明: 如果你不希望执行 base64 编码转换,可以使用 stringData
字段代替。
当你使用清单文件来创建这两类 Secret 时,API 服务器会检查 data
字段中是否存在所期望的主键,
并且验证其中所提供的键值是否是合法的 JSON 数据。
不过,API 服务器不会检查 JSON 数据本身是否是一个合法的 Docker 配置文件内容。
当你没有 Docker 配置文件,或者你想使用 kubectl
创建一个 Secret
来访问容器仓库时,你可以这样做:
kubectl create secret docker-registry secret-tiger-docker \
--docker-email= tiger@acme.example \
--docker-username= tiger \
--docker-password= pass1234 \
--docker-server= my-registry.example:5000
上面的命令创建一个类型为 kubernetes.io/dockerconfigjson
的 Secret。
如果你对 .data.dockerconfigjson
内容进行转储并执行 base64 解码:
kubectl get secret secret-tiger-docker -o jsonpath = '{.data.*}' | base64 -d
那么输出等价于这个 JSON 文档(这也是一个有效的 Docker 配置文件):
{
"auths" : {
"my-registry.example:5000" : {
"username" : "tiger" ,
"password" : "pass1234" ,
"email" : "tiger@acme.example" ,
"auth" : "dGlnZXI6cGFzczEyMzQ="
}
}
}
说明: auths
值是 base64 编码的,其内容被屏蔽但未被加密。
任何能够读取该 Secret 的人都可以了解镜像库的访问令牌。
基本身份认证 Secret kubernetes.io/basic-auth
类型用来存放用于基本身份认证所需的凭据信息。
使用这种 Secret 类型时,Secret 的 data
字段必须包含以下两个键之一:
username
: 用于身份认证的用户名;password
: 用于身份认证的密码或令牌。以上两个键的键值都是 base64 编码的字符串。
当然你也可以在创建 Secret 时使用 stringData
字段来提供明文形式的内容。
以下清单是基本身份验证 Secret 的示例:
apiVersion : v1
kind : Secret
metadata :
name : secret-basic-auth
type : kubernetes.io/basic-auth
stringData :
username : admin # kubernetes.io/basic-auth 类型的必需字段
password : t0p-Secret # kubernetes.io/basic-auth 类型的必需字段
提供基本身份认证类型的 Secret 仅仅是出于方便性考虑。
你也可以使用 Opaque
类型来保存用于基本身份认证的凭据。
不过,使用预定义的、公开的 Secret 类型(kubernetes.io/basic-auth
)
有助于帮助其他用户理解 Secret 的目的,并且对其中存在的主键形成一种约定。
API 服务器会检查 Secret 配置中是否提供了所需要的主键。
SSH 身份认证 Secret Kubernetes 所提供的内置类型 kubernetes.io/ssh-auth
用来存放 SSH 身份认证中所需要的凭据。
使用这种 Secret 类型时,你就必须在其 data
(或 stringData
)
字段中提供一个 ssh-privatekey
键值对,作为要使用的 SSH 凭据。
下面的清单是一个 SSH 公钥/私钥身份认证的 Secret 示例:
apiVersion : v1
kind : Secret
metadata :
name : secret-ssh-auth
type : kubernetes.io/ssh-auth
data :
# 此例中的实际数据被截断
ssh-privatekey : |
MIIEpQIBAAKCAQEAulqb/Y ...
提供 SSH 身份认证类型的 Secret 仅仅是出于用户方便性考虑。
你也可以使用 Opaque
类型来保存用于 SSH 身份认证的凭据。
不过,使用预定义的、公开的 Secret 类型(kubernetes.io/ssh-auth
)
有助于其他人理解你的 Secret 的用途,也可以就其中包含的主键名形成约定。
API 服务器确实会检查 Secret 配置中是否提供了所需要的主键。
注意: SSH 私钥自身无法建立 SSH 客户端与服务器端之间的可信连接。
需要其它方式来建立这种信任关系,以缓解“中间人(Man In The Middle)”
攻击,例如向 ConfigMap 中添加一个 known_hosts
文件。
TLS Secret Kubernetes 提供一种内置的 kubernetes.io/tls
Secret 类型,用来存放 TLS
场合通常要使用的证书及其相关密钥。
TLS Secret 的一种典型用法是为 Ingress
资源配置传输过程中的数据加密,不过也可以用于其他资源或者直接在负载中使用。
当使用此类型的 Secret 时,Secret 配置中的 data
(或 stringData
)字段必须包含
tls.key
和 tls.crt
主键,尽管 API 服务器实际上并不会对每个键的取值作进一步的合法性检查。
下面的 YAML 包含一个 TLS Secret 的配置示例:
apiVersion : v1
kind : Secret
metadata :
name : secret-tls
type : kubernetes.io/tls
data :
# 此例中的数据被截断
tls.crt : |
MIIC2DCCAcCgAwIBAgIBATANBgkqh ...
tls.key : |
MIIEpgIBAAKCAQEA7yn3bRHQ5FHMQ ...
提供 TLS 类型的 Secret 仅仅是出于用户方便性考虑。
你也可以使用 Opaque
类型来保存用于 TLS 服务器与/或客户端的凭据。
不过,使用内置的 Secret 类型的有助于对凭据格式进行归一化处理,并且
API 服务器确实会检查 Secret 配置中是否提供了所需要的主键。
当使用 kubectl
来创建 TLS Secret 时,你可以像下面的例子一样使用 tls
子命令:
kubectl create secret tls my-tls-secret \
--cert= path/to/cert/file \
--key= path/to/key/file
这里的公钥/私钥对都必须事先已存在。用于 --cert
的公钥证书必须是
RFC 7468 中 5.1 节
中所规定的 DER 格式,且与 --key
所给定的私钥匹配。
私钥必须是 DER 格式的 PKCS #8
(参见 RFC 7468 第 11节 )。
说明: 类型为 kubernetes.io/tls
的 Secret 中包含密钥和证书的 DER 数据,以 Base64 格式编码。
如果你熟悉私钥和证书的 PEM 格式,base64 与该格式相同,只是你需要略过 PEM
数据中所包含的第一行和最后一行。
例如,对于证书而言,你 不要 包含 --------BEGIN CERTIFICATE-----
和 -------END CERTIFICATE----
这两行。
启动引导令牌 Secret 通过将 Secret 的 type
设置为 bootstrap.kubernetes.io/token
可以创建启动引导令牌类型的 Secret。这种类型的 Secret 被设计用来支持节点的启动引导过程。
其中包含用来为周知的 ConfigMap 签名的令牌。
启动引导令牌 Secret 通常创建于 kube-system
名字空间内,并以
bootstrap-token-<令牌 ID>
的形式命名;
其中 <令牌 ID>
是一个由 6 个字符组成的字符串,用作令牌的标识。
以 Kubernetes 清单文件的形式,某启动引导令牌 Secret 可能看起来像下面这样:
apiVersion : v1
kind : Secret
metadata :
name : bootstrap-token-5emitj
namespace : kube-system
type : bootstrap.kubernetes.io/token
data :
auth-extra-groups : c3lzdGVtOmJvb3RzdHJhcHBlcnM6a3ViZWFkbTpkZWZhdWx0LW5vZGUtdG9rZW4=
expiration : MjAyMC0wOS0xM1QwNDozOToxMFo=
token-id : NWVtaXRq
token-secret : a3E0Z2lodnN6emduMXAwcg==
usage-bootstrap-authentication : dHJ1ZQ==
usage-bootstrap-signing : dHJ1ZQ==
启动引导令牌类型的 Secret 会在 data
字段中包含如下主键:
token-id
:由 6 个随机字符组成的字符串,作为令牌的标识符。必需。token-secret
:由 16 个随机字符组成的字符串,包含实际的令牌机密。必需。description
:供用户阅读的字符串,描述令牌的用途。可选。expiration
:一个使用 RFC3339 来编码的 UTC 绝对时间,给出令牌要过期的时间。可选。usage-bootstrap-<usage>
:布尔类型的标志,用来标明启动引导令牌的其他用途。auth-extra-groups
:用逗号分隔的组名列表,身份认证时除被认证为
system:bootstrappers
组之外,还会被添加到所列的用户组中。上面的 YAML 文件可能看起来令人费解,因为其中的数值均为 base64 编码的字符串。
实际上,你完全可以使用下面的 YAML 来创建一个一模一样的 Secret:
apiVersion : v1
kind : Secret
metadata :
# 注意 Secret 的命名方式
name : bootstrap-token-5emitj
# 启动引导令牌 Secret 通常位于 kube-system 名字空间
namespace : kube-system
type : bootstrap.kubernetes.io/token
stringData :
auth-extra-groups : "system:bootstrappers:kubeadm:default-node-token"
expiration : "2020-09-13T04:39:10Z"
# 此令牌 ID 被用于生成 Secret 名称
token-id : "5emitj"
token-secret : "kq4gihvszzgn1p0r"
# 此令牌还可用于 authentication (身份认证)
usage-bootstrap-authentication : "true"
# 且可用于 signing (证书签名)
usage-bootstrap-signing : "true"
不可更改的 Secret 特性状态: Kubernetes v1.21 [stable]
Kubernetes 允许你将特定的 Secret(和 ConfigMap)标记为 不可更改(Immutable) 。
禁止更改现有 Secret 的数据有下列好处:
防止意外(或非预期的)更新导致应用程序中断 (对于大量使用 Secret 的集群而言,至少数万个不同的 Secret 供 Pod 挂载),
通过将 Secret 标记为不可变,可以极大降低 kube-apiserver 的负载,提升集群性能。
kubelet 不需要监视那些被标记为不可更改的 Secret。 将 Secret 标记为不可更改 你可以通过将 Secret 的 immutable
字段设置为 true
创建不可更改的 Secret。
例如:
apiVersion : v1
kind : Secret
metadata :
...
data :
...
immutable : true
你也可以更改现有的 Secret,令其不可更改。
说明: 一旦一个 Secret 或 ConfigMap 被标记为不可更改,撤销此操作或者更改 data
字段的内容都是 不 可能的。
只能删除并重新创建这个 Secret。现有的 Pod 将维持对已删除 Secret 的挂载点 --
建议重新创建这些 Pod。
尽管 ConfigMap 和 Secret 的工作方式类似,但 Kubernetes 对 Secret 有一些额外的保护。
Secret 通常保存重要性各异的数值,其中很多都可能会导致 Kubernetes 中
(例如,服务账号令牌)或对外部系统的特权提升。
即使某些个别应用能够推导它期望使用的 Secret 的能力,
同一名字空间中的其他应用可能会让这种假定不成立。
只有当某个节点上的 Pod 需要某 Secret 时,对应的 Secret 才会被发送到该节点上。
如果将 Secret 挂载到 Pod 中,kubelet 会将数据的副本保存在在 tmpfs
中,
这样机密的数据不会被写入到持久性存储中。
一旦依赖于该 Secret 的 Pod 被删除,kubelet 会删除来自于该 Secret 的机密数据的本地副本。
同一个 Pod 中可能包含多个容器。默认情况下,你所定义的容器只能访问默认 ServiceAccount
及其相关 Secret。你必须显式地定义环境变量或者将卷映射到容器中,才能为容器提供对其他
Secret 的访问。
针对同一节点上的多个 Pod 可能有多个 Secret。不过,只有某个 Pod 所请求的 Secret
才有可能对 Pod 中的容器可见。因此,一个 Pod 不会获得访问其他 Pod 的 Secret 的权限。
警告: 在一个节点上以 privileged: true
运行的所有容器可以访问该节点上使用的所有 Secret。
接下来 3.8.4 - 为 Pod 和容器管理资源 当你定义 Pod 时可以选择性地为每个
容器 设定所需要的资源数量。
最常见的可设定资源是 CPU 和内存(RAM)大小;此外还有其他类型的资源。
当你为 Pod 中的 Container 指定了资源 请求 时,
kube-scheduler
就利用该信息决定将 Pod 调度到哪个节点上。
当你还为 Container 指定了资源 限制 时,kubelet 就可以确保运行的容器不会使用超出所设限制的资源。
kubelet 还会为容器预留所 请求 数量的系统资源,供其使用。
请求和限制 如果 Pod 运行所在的节点具有足够的可用资源,容器可能(且可以)使用超出对应资源
request
属性所设置的资源量。不过,容器不可以使用超出其资源 limit
属性所设置的资源量。
例如,如果你将容器的 memory
的请求量设置为 256 MiB,而该容器所处的 Pod
被调度到一个具有 8 GiB 内存的节点上,并且该节点上没有其他 Pod
运行,那么该容器就可以尝试使用更多的内存。
如果你将某容器的 memory
限制设置为 4 GiB,kubelet
(和容器运行时 )就会确保该限制生效。
容器运行时会禁止容器使用超出所设置资源限制的资源。
例如:当容器中进程尝试使用超出所允许内存量的资源时,系统内核会将尝试申请内存的进程终止,
并引发内存不足(OOM)错误。
限制可以以被动方式来实现(系统会在发现违例时进行干预),或者通过强制生效的方式实现
(系统会避免容器用量超出限制)。不同的容器运行时采用不同方式来实现相同的限制。
说明: 如果你为某个资源指定了限制,但不指定请求,
并且没有应用准入时机制为该资源设置默认请求,
然后 Kubernetes 复制你所指定的限制值,将其用作资源的请求值。
资源类型 CPU 和 内存 都是 资源类型 。每种资源类型具有其基本单位。
CPU 表达的是计算处理能力,其单位是 Kubernetes CPU 。
内存的单位是字节。
对于 Linux 负载,则可以指定巨页(Huge Page)资源。
巨页是 Linux 特有的功能,节点内核在其中分配的内存块比默认页大小大得多。
例如,在默认页面大小为 4KiB 的系统上,你可以指定限制 hugepages-2Mi: 80Mi
。
如果容器尝试分配 40 个 2MiB 大小的巨页(总共 80 MiB ),则分配请求会失败。
说明: 你不能过量使用 hugepages- *
资源。
这与 memory
和 cpu
资源不同。
CPU 和内存统称为 计算资源 ,或简称为 资源 。
计算资源的数量是可测量的,可以被请求、被分配、被消耗。
它们与 API 资源 不同。
API 资源(如 Pod 和 Service )是可通过
Kubernetes API 服务器读取和修改的对象。
Pod 和 容器的资源请求和限制 针对每个容器,你都可以指定其资源限制和请求,包括如下选项:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.limits.hugepages-<size>
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
spec.containers[].resources.requests.hugepages-<size>
尽管你只能逐个容器地指定请求和限制值,考虑 Pod 的总体资源请求和限制也是有用的。
对特定资源而言,Pod 的资源请求/限制 是 Pod 中各容器对该类型资源的请求/限制的总和。
Kubernetes 中的资源单位 CPU 资源单位 CPU 资源的限制和请求以 “cpu” 为单位。
在 Kubernetes 中,一个 CPU 等于 1 个物理 CPU 核 或者 1 个虚拟核 ,
取决于节点是一台物理主机还是运行在某物理主机上的虚拟机。
你也可以表达带小数 CPU 的请求。
当你定义一个容器,将其 spec.containers[].resources.requests.cpu
设置为 0.5 时,
你所请求的 CPU 是你请求 1.0
CPU 时的一半。
对于 CPU 资源单位,数量
表达式 0.1
等价于表达式 100m
,可以看作 “100 millicpu”。
有些人说成是“一百毫核”,其实说的是同样的事情。
CPU 资源总是设置为资源的绝对数量而非相对数量值。
例如,无论容器运行在单核、双核或者 48-核的机器上,500m
CPU 表示的是大约相同的计算能力。
说明: Kubernetes 不允许设置精度小于 1m
的 CPU 资源。
因此,当 CPU 单位小于 1
或 1000m
时,使用毫核的形式是有用的;
例如 5m
而不是 0.005
。
内存资源单位 memory
的限制和请求以字节为单位。
你可以使用普通的整数,或者带有以下
数量 后缀
的定点数字来表示内存:E、P、T、G、M、k。
你也可以使用对应的 2 的幂数:Ei、Pi、Ti、Gi、Mi、Ki。
例如,以下表达式所代表的是大致相同的值:
128974848、129e6、129M、128974848000m、123Mi
请注意后缀的大小写。如果你请求 400m
临时存储,实际上所请求的是 0.4 字节。
如果有人这样设定资源请求或限制,可能他的实际想法是申请 400Mi 字节(400Mi
)
或者 400M 字节。
容器资源示例 以下 Pod 有两个容器。每个容器的请求为 0.25 CPU 和 64MiB(226 字节)内存,
每个容器的资源限制为 0.5 CPU 和 128MiB 内存。
你可以认为该 Pod 的资源请求为 0.5 CPU 和 128 MiB 内存,资源限制为 1 CPU 和 256MiB 内存。
apiVersion : v1
kind : Pod
metadata :
name : frontend
spec :
containers :
- name : app
image : images.my-company.example/app:v4
resources :
requests :
memory : "64Mi"
cpu : "250m"
limits :
memory : "128Mi"
cpu : "500m"
- name : log-aggregator
image : images.my-company.example/log-aggregator:v6
resources :
requests :
memory : "64Mi"
cpu : "250m"
limits :
memory : "128Mi"
cpu : "500m"
带资源请求的 Pod 如何调度 当你创建一个 Pod 时,Kubernetes 调度程序将为 Pod 选择一个节点。
每个节点对每种资源类型都有一个容量上限:可为 Pod 提供的 CPU 和内存量。
调度程序确保对于每种资源类型,所调度的容器的资源请求的总和小于节点的容量。
请注意,尽管节点上的实际内存或 CPU 资源使用量非常低,如果容量检查失败,
调度程序仍会拒绝在该节点上放置 Pod。
当稍后节点上资源用量增加,例如到达请求率的每日峰值区间时,节点上也不会出现资源不足的问题。
Kubernetes 应用资源请求与限制的方式 当 kubelet 将容器作为 Pod 的一部分启动时,它会将容器的 CPU 和内存请求与限制信息传递给容器运行时。
在 Linux 系统上,容器运行时通常会配置内核
CGroups ,负责应用并实施所定义的请求。
CPU 限制定义的是容器可使用的 CPU 时间的硬性上限。
在每个调度周期(时间片)期间,Linux 内核检查是否已经超出该限制;
内核会在允许该 cgroup 恢复执行之前会等待。 CPU 请求值定义的是一个权重值。如果若干不同的容器(CGroups)需要在一个共享的系统上竞争运行,
CPU 请求值大的负载会获得比请求值小的负载更多的 CPU 时间。 内存请求值主要用于(Kubernetes)Pod 调度期间。在一个启用了 CGroup v2 的节点上,
容器运行时可能会使用内存请求值作为设置 memory.min
和 memory.low
的提示值。 内存限制定义的是 cgroup 的内存限制。如果容器尝试分配的内存量超出限制,
则 Linux 内核的内存不足处理子系统会被激活,并停止尝试分配内存的容器中的某个进程。
如果该进程在容器中 PID 为 1,而容器被标记为可重新启动,则 Kubernetes
会重新启动该容器。 Pod 或容器的内存限制也适用于通过内存供应的卷,例如 emptyDir
卷。
kubelet 会跟踪 tmpfs
形式的 emptyDir 卷用量,将其作为容器的内存用量,
而不是临时存储用量。 如果某容器内存用量超过其内存请求值并且所在节点内存不足时,容器所处的 Pod
可能被逐出 。
每个容器可能被允许也可能不被允许使用超过其 CPU 限制的处理时间。
但是,容器运行时不会由于 CPU 使用率过高而杀死 Pod 或容器。
要确定某容器是否会由于资源限制而无法调度或被杀死,请参阅疑难解答 节。
监控计算和内存资源用量 kubelet 会将 Pod 的资源使用情况作为 Pod
status
的一部分来报告的。
如果为集群配置了可选的监控工具 ,
则可以直接从指标 API
或者监控工具获得 Pod 的资源使用情况。
本地临时存储 特性状态: Kubernetes v1.25 [stable]
节点通常还可以具有本地的临时性存储,由本地挂接的可写入设备或者有时也用 RAM
来提供支持。
“临时(Ephemeral)”意味着对所存储的数据不提供长期可用性的保证。
Pods 通常可以使用临时性本地存储来实现缓冲区、保存日志等功能。
kubelet 可以为使用本地临时存储的 Pods 提供这种存储空间,允许后者使用
emptyDir
类型的卷 将其挂载到容器中。
kubelet 也使用此类存储来保存节点层面的容器日志 、
容器镜像文件以及运行中容器的可写入层。
注意: 如果节点失效,存储在临时性存储中的数据会丢失。
你的应用不能对本地临时性存储的性能 SLA(例如磁盘 IOPS)作任何假定。
作为一种 beta 阶段功能特性,Kubernetes 允许你跟踪、预留和限制 Pod
可消耗的临时性本地存储数量。
本地临时性存储的配置 Kubernetes 有两种方式支持节点上配置本地临时性存储:
采用这种配置时,你会把所有类型的临时性本地数据(包括 emptyDir
卷、可写入容器层、容器镜像、日志等)放到同一个文件系统中。
作为最有效的 kubelet 配置方式,这意味着该文件系统是专门提供给 Kubernetes
(kubelet)来保存数据的。
kubelet 也会生成节点层面的容器日志 ,
并按临时性本地存储的方式对待之。
kubelet 会将日志写入到所配置的日志目录(默认为 /var/log
)下的文件中;
还会针对其他本地存储的数据使用同一个基础目录(默认为 /var/lib/kubelet
)。
通常,/var/lib/kubelet
和 /var/log
都是在系统的根文件系统中。kubelet
的设计也考虑到这一点。
你的集群节点当然可以包含其他的、并非用于 Kubernetes 的很多文件系统。
你使用节点上的某个文件系统来保存运行 Pods 时产生的临时性数据:日志和
emptyDir
卷等。你可以使用这个文件系统来保存其他数据(例如:与 Kubernetes
无关的其他系统日志);这个文件系统还可以是根文件系统。
kubelet 也将节点层面的容器日志
写入到第一个文件系统中,并按临时性本地存储的方式对待之。
同时你使用另一个由不同逻辑存储设备支持的文件系统。在这种配置下,你会告诉
kubelet 将容器镜像层和可写层保存到这第二个文件系统上的某个目录中。
第一个文件系统中不包含任何镜像层和可写层数据。
当然,你的集群节点上还可以有很多其他与 Kubernetes 没有关联的文件系统。
kubelet 能够度量其本地存储的用量。
实现度量机制的前提是你已使用本地临时存储所支持的配置之一对节点进行配置。
如果你的节点配置不同于以上预期,kubelet 就无法对临时性本地存储实施资源限制。
说明: kubelet 会将 tmpfs
emptyDir 卷的用量当作容器内存用量,而不是本地临时性存储来统计。
说明: kubelet 将仅跟踪临时存储的根文件系统。
挂载一个单独磁盘到 /var/lib/kubelet
或 /var/lib/containers
的操作系统布局将不会正确地报告临时存储。
为本地临时性存储设置请求和限制 你可以指定 ephemeral-storage
来管理本地临时性存储。
Pod 中的每个容器可以设置以下属性:
spec.containers[].resources.limits.ephemeral-storage
spec.containers[].resources.requests.ephemeral-storage
ephemeral-storage
的请求和限制是按量纲计量的。
你可以使用一般整数或者定点数字加上下面的后缀来表达存储量:E、P、T、G、M、k。
你也可以使用对应的 2 的幂级数来表达:Ei、Pi、Ti、Gi、Mi、Ki。
例如,下面的表达式所表达的大致是同一个值:
请注意后缀的大小写。如果你请求 400m
临时存储,实际上所请求的是 0.4 字节。
如果有人这样设定资源请求或限制,可能他的实际想法是申请 400Mi 字节(400Mi
)
或者 400M 字节。
在下面的例子中,Pod 包含两个容器。每个容器请求 2 GiB 大小的本地临时性存储。
每个容器都设置了 4 GiB 作为其本地临时性存储的限制。
因此,整个 Pod 的本地临时性存储请求是 4 GiB,且其本地临时性存储的限制为 8 GiB。
apiVersion : v1
kind : Pod
metadata :
name : frontend
spec :
containers :
- name : app
image : images.my-company.example/app:v4
resources :
requests :
ephemeral-storage : "2Gi"
limits :
ephemeral-storage : "4Gi"
volumeMounts :
- name : ephemeral
mountPath : "/tmp"
- name : log-aggregator
image : images.my-company.example/log-aggregator:v6
resources :
requests :
ephemeral-storage : "2Gi"
limits :
ephemeral-storage : "4Gi"
volumeMounts :
- name : ephemeral
mountPath : "/tmp"
volumes :
- name : ephemeral
emptyDir : {}
带临时性存储的 Pods 的调度行为 当你创建一个 Pod 时,Kubernetes 调度器会为 Pod 选择一个节点来运行之。
每个节点都有一个本地临时性存储的上限,是其可提供给 Pod 使用的总量。
欲了解更多信息,
可参考节点可分配资源 节。
调度器会确保所调度的容器的资源请求总和不会超出节点的资源容量。
临时性存储消耗的管理 如果 kubelet 将本地临时性存储作为资源来管理,则 kubelet 会度量以下各处的存储用量:
emptyDir
卷,除了 tmpfs emptyDir
卷保存节点层面日志的目录 可写入的容器镜像层 如果某 Pod 的临时存储用量超出了你所允许的范围,kubelet
会向其发出逐出(eviction)信号,触发该 Pod 被逐出所在节点。
就容器层面的隔离而言,如果某容器的可写入镜像层和日志用量超出其存储限制,
kubelet 也会将所在的 Pod 标记为逐出候选。
就 Pod 层面的隔离而言,kubelet 会将 Pod 中所有容器的限制相加,得到 Pod
存储限制的总值。如果所有容器的本地临时性存储用量总和加上 Pod 的 emptyDir
卷的用量超出 Pod 存储限制,kubelet 也会将该 Pod 标记为逐出候选。
注意: 如果 kubelet 没有度量本地临时性存储的用量,即使 Pod
的本地存储用量超出其限制也不会被逐出。
不过,如果用于可写入容器镜像层、节点层面日志或者 emptyDir
卷的文件系统中可用空间太少,
节点会为自身设置本地存储不足的污点 标签。
这一污点会触发对那些无法容忍该污点的 Pod 的逐出操作。
关于临时性本地存储的配置信息,请参考这里
kubelet 支持使用不同方式来度量 Pod 的存储用量:
kubelet 按预定周期执行扫描操作,检查 emptyDir
卷、容器日志目录以及可写入容器镜像层。
这一扫描会度量存储空间用量。
说明: 项目配额(Project Quota)是一个操作系统层的功能特性,用来管理文件系统中的存储用量。
在 Kubernetes 中,你可以启用项目配额以监视存储用量。
你需要确保节点上为 emptyDir
提供存储的文件系统支持项目配额。
例如,XFS 和 ext4fs 文件系统都支持项目配额。
说明: 项目配额可以帮你监视存储用量,但无法强制执行限制。
Kubernetes 所使用的项目 ID 始于 1048576
。
所使用的 IDs 会注册在 /etc/projects
和 /etc/projid
文件中。
如果该范围中的项目 ID 已经在系统中被用于其他目的,则已占用的项目 ID
也必须注册到 /etc/projects
和 /etc/projid
中,这样 Kubernetes
才不会使用它们。
配额方式与目录扫描方式相比速度更快,结果更精确。当某个目录被分配给某个项目时,
该目录下所创建的所有文件都属于该项目,内核只需要跟踪该项目中的文件所使用的存储块个数。
如果某文件被创建后又被删除,但对应文件描述符仍处于打开状态,
该文件会继续耗用存储空间。配额跟踪技术能够精确第记录对应存储空间的状态,
而目录扫描方式会忽略被删除文件所占用的空间。
如果你希望使用项目配额,你需要:
在 kubelet 配置 中使用
featureGates
字段或者使用 --feature-gates
命令行参数启用
LocalStorageCapacityIsolationFSQuotaMonitoring=true
特性门控 。
确保根文件系统(或者可选的运行时文件系统)启用了项目配额。所有 XFS
文件系统都支持项目配额。
对 extf 文件系统而言,你需要在文件系统尚未被挂载时启用项目配额跟踪特性:
# 对 ext4 而言,在 /dev/block-device 尚未被挂载时执行下面操作
sudo tune2fs -O project -Q prjquota /dev/block-device
确保根文件系统(或者可选的运行时文件系统)在挂载时项目配额特性是被启用了的。
对于 XFS 和 ext4fs 而言,对应的挂载选项称作 prjquota
。
扩展资源(Extended Resources) 扩展资源是 kubernetes.io
域名之外的标准资源名称。
它们使得集群管理员能够颁布非 Kubernetes 内置资源,而用户可以使用他们。
使用扩展资源需要两个步骤。首先,集群管理员必须颁布扩展资源。
其次,用户必须在 Pod 中请求扩展资源。
管理扩展资源 节点级扩展资源 节点级扩展资源绑定到节点。
设备插件管理的资源 有关如何颁布在各节点上由设备插件所管理的资源,
请参阅设备插件 。
其他资源 为了颁布新的节点级扩展资源,集群操作员可以向 API 服务器提交 PATCH
HTTP 请求,
以在集群中节点的 status.capacity
中为其配置可用数量。
完成此操作后,节点的 status.capacity
字段中将包含新资源。
kubelet 会异步地对 status.allocatable
字段执行自动更新操作,使之包含新资源。
由于调度器在评估 Pod 是否适合在某节点上执行时会使用节点的 status.allocatable
值,
调度器只会考虑异步更新之后的新值。
在更新节点容量使之包含新资源之后和请求该资源的第一个 Pod 被调度到该节点之间,
可能会有短暂的延迟。
示例:
这是一个示例,显示了如何使用 curl
构造 HTTP 请求,公告主节点为 k8s-master
的节点 k8s-node-1
上存在五个 example.com/foo
资源。
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1foo", "value": "5"}]' \
http://k8s-master:8080/api/v1/nodes/k8s-node-1/status
说明: 在前面的请求中,~1
是在 patch 路径中对字符 /
的编码。
JSON-Patch 中的操作路径的值被视为 JSON-Pointer 类型。
有关更多详细信息,请参见
IETF RFC 6901 第 3 节 。
集群层面的扩展资源 集群层面的扩展资源并不绑定到具体节点。
它们通常由调度器扩展程序(Scheduler Extenders)管理,这些程序处理资源消耗和资源配额。
你可以在调度器配置
中指定由调度器扩展程序处理的扩展资源。
示例:
下面的调度器策略配置标明集群层扩展资源 "example.com/foo" 由调度器扩展程序处理。
仅当 Pod 请求 "example.com/foo" 时,调度器才会将 Pod 发送到调度器扩展程序。 ignoredByScheduler
字段指定调度器不要在其 PodFitsResources
断言中检查
"example.com/foo" 资源。{
"kind" : "Policy" ,
"apiVersion" : "v1" ,
"extenders" : [
{
"urlPrefix" :"<extender-endpoint>" ,
"bindVerb" : "bind" ,
"managedResources" : [
{
"name" : "example.com/foo" ,
"ignoredByScheduler" : true
}
]
}
]
}
使用扩展资源 就像 CPU 和内存一样,用户可以在 Pod 的规约中使用扩展资源。
调度器负责资源的核算,确保同时分配给 Pod 的资源总量不会超过可用数量。
API 服务器将扩展资源的数量限制为整数。
有效 数量的示例是 3
、3000m
和 3Ki
。
无效 数量的示例是 0.5
和 1500m
。
说明: 扩展资源取代了非透明整数资源(Opaque Integer Resources,OIR)。
用户可以使用 kubernetes.io
(保留)以外的任何域名前缀。
要在 Pod 中使用扩展资源,请在容器规约的 spec.containers[].resources.limits
映射中包含资源名称作为键。
说明: 扩展资源不能过量使用,因此如果容器规约中同时存在请求和限制,则它们的取值必须相同。
仅当所有资源请求(包括 CPU、内存和任何扩展资源)都被满足时,Pod 才能被调度。
在资源请求无法满足时,Pod 会保持在 PENDING
状态。
示例:
下面的 Pod 请求 2 个 CPU 和 1 个 "example.com/foo"(扩展资源)。
apiVersion : v1
kind : Pod
metadata :
name : my-pod
spec :
containers :
- name : my-container
image : myimage
resources :
requests :
cpu : 2
example.com/foo : 1
limits :
example.com/foo : 1
PID 限制 进程 ID(PID)限制允许对 kubelet 进行配置,以限制给定 Pod 可以消耗的 PID 数量。
有关信息,请参见 PID 限制 。
疑难解答 我的 Pod 处于悬决状态且事件信息显示 FailedScheduling
如果调度器找不到该 Pod 可以匹配的任何节点,则该 Pod 将保持未被调度状态,
直到找到一个可以被调度到的位置。每当调度器找不到 Pod 可以调度的地方时,
会产生一个 Event 。
你可以使用 kubectl
来查看 Pod 的事件;例如:
kubectl describe pod frontend | grep -A 9999999999 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 23s default-scheduler 0/42 nodes available: insufficient cpu
在上述示例中,由于节点上的 CPU 资源不足,名为 “frontend” 的 Pod 无法被调度。
由于内存不足(PodExceedsFreeMemory)而导致失败时,也有类似的错误消息。
一般来说,如果 Pod 处于悬决状态且有这种类型的消息时,你可以尝试如下几件事情:
向集群添加更多节点。 终止不需要的 Pod,为悬决的 Pod 腾出空间。 检查 Pod 所需的资源是否超出所有节点的资源容量。例如,如果所有节点的容量都是cpu:1
,
那么一个请求为 cpu: 1.1
的 Pod 永远不会被调度。 检查节点上的污点设置。如果集群中节点上存在污点,而新的 Pod 不能容忍污点,
调度器只会考虑将 Pod 调度到不带有该污点的节点上。 你可以使用 kubectl describe nodes
命令检查节点容量和已分配的资源数量。 例如:
kubectl describe nodes e2e-test-node-pool-4lw4
Name: e2e-test-node-pool-4lw4
[ ... 这里忽略了若干行以便阅读 ...]
Capacity:
cpu: 2
memory: 7679792Ki
pods: 110
Allocatable:
cpu: 1800m
memory: 7474992Ki
pods: 110
[ ... 这里忽略了若干行以便阅读 ...]
Non-terminated Pods: (5 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
kube-system fluentd-gcp-v1.38-28bv1 100m (5%) 0 (0%) 200Mi (2%) 200Mi (2%)
kube-system kube-dns-3297075139-61lj3 260m (13%) 0 (0%) 100Mi (1%) 170Mi (2%)
kube-system kube-proxy-e2e-test-... 100m (5%) 0 (0%) 0 (0%) 0 (0%)
kube-system monitoring-influxdb-grafana-v4-z1m12 200m (10%) 200m (10%) 600Mi (8%) 600Mi (8%)
kube-system node-problem-detector-v0.1-fj7m3 20m (1%) 200m (10%) 20Mi (0%) 100Mi (1%)
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
680m (34%) 400m (20%) 920Mi (11%) 1070Mi (13%)
在上面的输出中,你可以看到如果 Pod 请求超过 1.120 CPU 或者 6.23Gi 内存,节点将无法满足。
通过查看 "Pods" 部分,你将看到哪些 Pod 占用了节点上的资源。
Pods 可用的资源量低于节点的资源总量,因为系统守护进程也会使用一部分可用资源。
在 Kubernetes API 中,每个 Node 都有一个 .status.allocatable
字段
(详情参见 NodeStatus )。
字段 .status.allocatable
描述节点上可以用于 Pod 的资源总量(例如:15 个虚拟
CPU、7538 MiB 内存)。关于 Kubernetes 中节点可分配资源的信息,
可参阅为系统守护进程预留计算资源 。
你可以配置资源配额 功能特性以限制每个名字空间可以使用的资源总量。
当某名字空间中存在 ResourceQuota 时,Kubernetes 会在该名字空间中的对象强制实施配额。
例如,如果你为不同的团队分配名字空间,你可以为这些名字空间添加 ResourceQuota。
设置资源配额有助于防止一个团队占用太多资源,以至于这种占用会影响其他团队。
你还需要考虑为这些名字空间设置授权访问:
为名字空间提供 全部 的写权限时,具有合适权限的人可能删除所有资源,
包括所配置的 ResourceQuota。
我的容器被终止了 你的容器可能因为资源紧张而被终止。要查看容器是否因为遇到资源限制而被杀死,
请针对相关的 Pod 执行 kubectl describe pod
:
kubectl describe pod simmemleak-hra99
输出类似于:
Name: simmemleak-hra99
Namespace: default
Image(s): saadali/simmemleak
Node: kubernetes-node-tf0f/10.240.216.66
Labels: name=simmemleak
Status: Running
Reason:
Message:
IP: 10.244.2.75
Containers:
simmemleak:
Image: saadali/simmemleak:latest
Limits:
cpu: 100m
memory: 50Mi
State: Running
Started: Tue, 07 Jul 2019 12:54:41 -0700
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Fri, 07 Jul 2019 12:54:30 -0700
Finished: Fri, 07 Jul 2019 12:54:33 -0700
Ready: False
Restart Count: 5
Conditions:
Type Status
Ready False
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 42s default-scheduler Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
Normal Pulled 41s kubelet Container image "saadali/simmemleak:latest" already present on machine
Normal Created 41s kubelet Created container simmemleak
Normal Started 40s kubelet Started container simmemleak
Normal Killing 32s kubelet Killing container with id ead3fb35-5cf5-44ed-9ae1-488115be66c6: Need to kill Pod
在上面的例子中,Restart Count: 5
意味着 Pod 中的 simmemleak
容器被终止并且(到目前为止)重启了五次。
原因 OOMKilled
显示容器尝试使用超出其限制的内存量。
你接下来要做的或许是检查应用代码,看看是否存在内存泄露。
如果你发现应用的行为与你所预期的相同,则可以考虑为该容器设置一个更高的内存限制
(也可能需要设置请求值)。
接下来 3.8.5 - 使用 kubeconfig 文件组织集群访问 使用 kubeconfig 文件来组织有关集群、用户、命名空间和身份认证机制的信息。
kubectl
命令行工具使用 kubeconfig 文件来查找选择集群所需的信息,并与集群的 API 服务器进行通信。
说明: 用于配置集群访问的文件称为“kubeconfig 文件”。
这是引用配置文件的通用方法,并不意味着有一个名为 kubeconfig
的文件
警告: 只使用来源可靠的 kubeconfig 文件。使用特制的 kubeconfig 文件可能会导致恶意代码执行或文件暴露。
如果必须使用不受信任的 kubeconfig 文件,请首先像检查 shell 脚本一样仔细检查它。
默认情况下,kubectl
在 $HOME/.kube
目录下查找名为 config
的文件。
你可以通过设置 KUBECONFIG
环境变量或者设置
--kubeconfig
参数来指定其他 kubeconfig 文件。
有关创建和指定 kubeconfig 文件的分步说明,请参阅
配置对多集群的访问 。
支持多集群、用户和身份认证机制 假设你有多个集群,并且你的用户和组件以多种方式进行身份认证。比如:
正在运行的 kubelet 可能使用证书在进行认证。 用户可能通过令牌进行认证。 管理员可能拥有多个证书集合提供给各用户。 使用 kubeconfig 文件,你可以组织集群、用户和命名空间。你还可以定义上下文,以便在集群和命名空间之间快速轻松地切换。
上下文(Context) 通过 kubeconfig 文件中的 context 元素,使用简便的名称来对访问参数进行分组。
每个 context 都有三个参数:cluster、namespace 和 user。
默认情况下,kubectl
命令行工具使用 当前上下文 中的参数与集群进行通信。
选择当前上下文
kubectl config use-context
KUBECONFIG 环境变量 KUBECONFIG
环境变量包含一个 kubeconfig 文件列表。
对于 Linux 和 Mac,列表以冒号分隔。对于 Windows,列表以分号分隔。
KUBECONFIG
环境变量不是必要的。
如果 KUBECONFIG
环境变量不存在,kubectl
使用默认的 kubeconfig 文件,$HOME/.kube/config
。
如果 KUBECONFIG
环境变量存在,kubectl
使用 KUBECONFIG
环境变量中列举的文件合并后的有效配置。
合并 kubeconfig 文件 要查看配置,输入以下命令:
如前所述,输出可能来自 kubeconfig 文件,也可能是合并多个 kubeconfig 文件的结果。
以下是 kubectl
在合并 kubeconfig 文件时使用的规则。
如果设置了 --kubeconfig
参数,则仅使用指定的文件。不进行合并。此参数只能使用一次。
否则,如果设置了 KUBECONFIG
环境变量,将它用作应合并的文件列表。根据以下规则合并 KUBECONFIG
环境变量中列出的文件:
忽略空文件名。 对于内容无法反序列化的文件,产生错误信息。 第一个设置特定值或者映射键的文件将生效。 永远不会更改值或者映射键。示例:保留第一个文件的上下文以设置 current-context
。示例:如果两个文件都指定了 red-user
,则仅使用第一个文件的 red-user
中的值。即使第二个文件在 red-user
下有非冲突条目,也要丢弃它们。 有关设置 KUBECONFIG
环境变量的示例,请参阅
设置 KUBECONFIG 环境变量 。
否则,使用默认的 kubeconfig 文件, $HOME/.kube/config
,不进行合并。
根据此链中的第一个匹配确定要使用的上下文。
如果存在,使用 --context
命令行参数。 使用合并的 kubeconfig 文件中的 current-context
。 这种场景下允许空上下文。
确定集群和用户。此时,可能有也可能没有上下文。根据此链中的第一个匹配确定集群和用户,这将运行两次:一次用于用户,一次用于集群。
如果存在,使用命令行参数:--user
或者 --cluster
。 如果上下文非空,从上下文中获取用户或集群。 这种场景下用户和集群可以为空。
确定要使用的实际集群信息。此时,可能有也可能没有集群信息。基于此链构建每个集群信息;第一个匹配项会被采用:
如果存在:--server
、--certificate-authority
和 --insecure-skip-tls-verify
,使用命令行参数。 如果合并的 kubeconfig 文件中存在集群信息属性,则使用它们。 如果没有 server 配置,则配置无效。 确定要使用的实际用户信息。使用与集群信息相同的规则构建用户信息,但每个用户只允许一种身份认证技术:
如果存在:--client-certificate
、--client-key
、--username
、--password
和 --token
,使用命令行参数。 使用合并的 kubeconfig 文件中的 user
字段。 如果存在两种冲突技术,则配置无效。 对于仍然缺失的任何信息,使用其对应的默认值,并可能提示输入身份认证信息。 文件引用 kubeconfig 文件中的文件和路径引用是相对于 kubeconfig 文件的位置。
命令行上的文件引用是相对于当前工作目录的。
在 $HOME/.kube/config
中,相对路径按相对路径存储,绝对路径按绝对路径存储。
代理 你可以在 kubeconfig
文件中,为每个集群配置 proxy-url
来让 kubectl
使用代理,例如:
apiVersion : v1
kind : Config
clusters :
- cluster :
proxy-url : http://proxy.example.org:3128
server : https://k8s.example.org/k8s/clusters/c-xxyyzz
name : development
users :
- name : developer
contexts :
- context :
name : development
接下来 3.8.6 - Windows 节点的资源管理 本页概述了 Linux 和 Windows 在资源管理方式上的区别。
在 Linux 节点上,cgroup 用作资源控制的 Pod 边界。
在这个边界内创建容器以便于隔离网络、进程和文件系统。
Linux cgroup API 可用于收集 CPU、I/O 和内存使用统计数据。
与此相反,Windows 中每个容器对应一个作业对象 ,
与系统命名空间过滤器一起使用,将所有进程包含在一个容器中,提供与主机的逻辑隔离。
(作业对象是一种 Windows 进程隔离机制,不同于 Kubernetes 提及的 Job )。
如果没有命名空间过滤,就无法运行 Windows 容器。
这意味着在主机环境中无法让系统特权生效,因此特权容器在 Windows 上不可用。
容器不能使用来自主机的标识,因为安全帐户管理器(Security Account Manager,SAM)是独立的。
内存管理 Windows 不像 Linux 一样提供杀手(killer)机制,杀死内存不足的进程。
Windows 始终将所有用户态内存分配视为虚拟内存,并强制使用页面文件(pagefile)。
Windows 节点不会为进程过量使用内存。
最终结果是 Windows 不会像 Linux 那样达到内存不足的情况,Windows 将进程页面放到磁盘,
不会因为内存不足(OOM)而终止进程。
如果内存配置过量且所有物理内存都已耗尽,则换页性能就会降低。
CPU 管理 Windows 可以限制为不同进程分配的 CPU 时间长度,但无法保证最小的 CPU 时间长度。
在 Windows 上,kubelet 支持使用命令行标志来设置 kubelet 进程的调度优先级 :
--windows-priorityclass
。
与 Windows 主机上运行的其他进程相比,此标志允许 kubelet 进程获取更多的 CPU 时间片。
有关允许值及其含义的更多信息,请访问 Windows 优先级类 。
为了确保运行的 Pod 不会耗尽 kubelet 的 CPU 时钟周期,
要将此标志设置为 ABOVE_NORMAL_PRIORITY_CLASS
或更高。
资源预留 为了满足操作系统、容器运行时和 kubelet 等 Kubernetes 主机进程使用的内存和 CPU,
你可以(且应该)用 --kube-reserved
和/或 --system-reserved
kubelet 标志来预留内存和 CPU 资源。
在 Windows 上,这些值仅用于计算节点的可分配 资源。
注意: 在你部署工作负载时,需对容器设置内存和 CPU 资源的限制。
这也会从 NodeAllocatable
中减去,帮助集群范围的调度器决定哪些 Pod 放到哪些节点上。
若调度 Pod 时未设置限制值,可能对 Windows 节点过量配置资源。
在极端情况下,这会让节点变得不健康。
在 Windows 上,一种好的做法是预留至少 2GiB 的内存。
要决定预留多少 CPU,需明确每个节点的最大 Pod 密度,
并监控节点上运行的系统服务的 CPU 使用率,然后选择一个满足工作负载需求的值。
3.9 - 安全 确保云原生工作负载安全的一组概念。
3.9.1 - 云原生安全概述 在云原生安全的背景下思考 Kubernetes 安全模型。
本概述定义了一个模型,用于在 Cloud Native 安全性上下文中考虑 Kubernetes 安全性。
警告: 此容器安全模型只提供建议,而不是经过验证的信息安全策略。
云原生安全的 4 个 C 你可以分层去考虑安全性,云原生安全的 4 个 C 分别是云(Cloud)、集群(Cluster)、容器(Container)和代码(Code)。
说明: 这种分层方法增强了
深度防护方法 在安全性方面的
防御能力,该方法被广泛认为是保护软件系统的最佳实践。
云原生安全的 4C 云原生安全模型的每一层都是基于下一个最外层,代码层受益于强大的基础安全层(云、集群、容器)。
你无法通过在代码层解决安全问题来为基础层中糟糕的安全标准提供保护。
云 在许多方面,云(或者位于同一位置的服务器,或者是公司数据中心)是 Kubernetes 集群中的
可信计算基 。
如果云层容易受到攻击(或者被配置成了易受攻击的方式),就不能保证在此基础之上构建的组件是安全的。
每个云提供商都会提出安全建议,以在其环境中安全地运行工作负载。
云提供商安全性 如果你是在你自己的硬件或者其他不同的云提供商上运行 Kubernetes 集群,
请查阅相关文档来获取最好的安全实践。
下面是一些比较流行的云提供商的安全性文档链接:
基础设施安全 关于在 Kubernetes 集群中保护你的基础设施的建议:
基础设施安全 Kubernetes 基础架构关注领域 建议 通过网络访问 API 服务(控制平面) 所有对 Kubernetes 控制平面的访问不允许在 Internet 上公开,同时应由网络访问控制列表控制,该列表包含管理集群所需的 IP 地址集。 通过网络访问 Node(节点) 节点应配置为 仅能 从控制平面上通过指定端口来接受(通过网络访问控制列表)连接,以及接受 NodePort 和 LoadBalancer 类型的 Kubernetes 服务连接。如果可能的话,这些节点不应完全暴露在公共互联网上。 Kubernetes 访问云提供商的 API 每个云提供商都需要向 Kubernetes 控制平面和节点授予不同的权限集。为集群提供云提供商访问权限时,最好遵循对需要管理的资源的最小特权原则 。Kops 文档 提供有关 IAM 策略和角色的信息。 访问 etcd 对 etcd(Kubernetes 的数据存储)的访问应仅限于控制平面。根据配置情况,你应该尝试通过 TLS 来使用 etcd。更多信息可以在 etcd 文档 中找到。 etcd 加密 在所有可能的情况下,最好对所有存储进行静态数据加密,并且由于 etcd 拥有整个集群的状态(包括机密信息),因此其磁盘更应该进行静态数据加密。
集群 保护 Kubernetes 有两个方面需要注意:
集群组件 如果想要保护集群免受意外或恶意的访问,采取良好的信息管理实践,请阅读并遵循有关保护集群 的建议。
集群中的组件(你的应用) 根据你的应用程序的受攻击面,你可能需要关注安全性的特定面,比如:
如果你正在运行中的一个服务(A 服务)在其他资源链中很重要,并且所运行的另一工作负载(服务 B)
容易受到资源枯竭的攻击,则如果你不限制服务 B 的资源的话,损害服务 A 的风险就会很高。
下表列出了安全性关注的领域和建议,用以保护 Kubernetes 中运行的工作负载:
容器 容器安全性不在本指南的探讨范围内。下面是一些探索此主题的建议和连接:
容器关注领域 建议 容器漏洞扫描和操作系统依赖安全性 作为镜像构建的一部分,你应该扫描你的容器里的已知漏洞。 镜像签名和执行 对容器镜像进行签名,以维护对容器内容的信任。 禁止特权用户 构建容器时,请查阅文档以了解如何在具有最低操作系统特权级别的容器内部创建用户,以实现容器的目标。 使用带有较强隔离能力的容器运行时 选择提供较强隔离能力的容器运行时类 。
代码 应用程序代码是你最能够控制的主要攻击面之一,虽然保护应用程序代码不在 Kubernetes 安全主题范围内,但以下是保护应用程序代码的建议:
代码安全性 代码安全 代码关注领域 建议 仅通过 TLS 访问 如果你的代码需要通过 TCP 通信,请提前与客户端执行 TLS 握手。除少数情况外,请加密传输中的所有内容。更进一步,加密服务之间的网络流量是一个好主意。这可以通过被称为双向 TLS 或 mTLS 的过程来完成,该过程对两个证书持有服务之间的通信执行双向验证。 限制通信端口范围 此建议可能有点不言自明,但是在任何可能的情况下,你都只应公开服务上对于通信或度量收集绝对必要的端口。 第三方依赖性安全 最好定期扫描应用程序的第三方库以了解已知的安全漏洞。每种编程语言都有一个自动执行此检查的工具。 静态代码分析 大多数语言都提供给了一种方法,来分析代码段中是否存在潜在的不安全的编码实践。只要有可能,你都应该使用自动工具执行检查,该工具可以扫描代码库以查找常见的安全错误,一些工具可以在以下连接中找到: https://owasp.org/www-community/Source_Code_Analysis_Tools 动态探测攻击 你可以对服务运行一些自动化工具,来尝试一些众所周知的服务攻击。这些攻击包括 SQL 注入、CSRF 和 XSS。OWASP Zed Attack 代理工具是最受欢迎的动态分析工具之一。
接下来 学习了解相关的 Kubernetes 安全主题:
3.9.2 - Pod 安全性标准 详细了解 Pod 安全性标准(Pod Security Standards)中所定义的不同策略级别。
Pod 安全性标准定义了三种不同的 策略(Policy) ,以广泛覆盖安全应用场景。
这些策略是 叠加式的(Cumulative) ,安全级别从高度宽松至高度受限。
本指南概述了每个策略的要求。
Profile 描述 Privileged 不受限制的策略,提供最大可能范围的权限许可。此策略允许已知的特权提升。 Baseline 限制性最弱的策略,禁止已知的策略提升。允许使用默认的(规定最少)Pod 配置。 Restricted 限制性非常强的策略,遵循当前的保护 Pod 的最佳实践。
Profile 细节 Privileged Privileged 策略是有目的地开放且完全无限制的策略。
此类策略通常针对由特权较高、受信任的用户所管理的系统级或基础设施级负载。
Privileged 策略定义中限制较少。默认允许的(Allow-by-default)实施机制(例如 gatekeeper)
可以缺省设置为 Privileged。
与此不同,对于默认拒绝(Deny-by-default)的实施机制(如 Pod 安全策略)而言,
Privileged 策略应该禁止所有限制。
Baseline Baseline 策略的目标是便于常见的容器化应用采用,同时禁止已知的特权提升。
此策略针对的是应用运维人员和非关键性应用的开发人员。
下面列举的控制应该被实施(禁止):
说明: 在下述表格中,通配符(*
)意味着一个列表中的所有元素。
例如 spec.containers[*].securityContext
表示 所定义的所有容器 的安全性上下文对象。
如果所列出的任一容器不能满足要求,整个 Pod 将无法通过校验。
Baseline 策略规范 控制(Control) 策略(Policy) HostProcess Windows Pod 提供了运行 HostProcess 容器 的能力,这使得对 Windows 节点的特权访问成为可能。Baseline 策略中禁止对宿主的特权访问。
特性状态: Kubernetes v1.23 [beta]
限制的字段
spec.securityContext.windowsOptions.hostProcess
spec.containers[*].securityContext.windowsOptions.hostProcess
spec.initContainers[*].securityContext.windowsOptions.hostProcess
spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess
准许的取值
宿主名字空间 必须禁止共享宿主上的名字空间。
限制的字段
spec.hostNetwork
spec.hostPID
spec.hostIPC
准许的取值
特权容器 特权 Pod 会使大多数安全性机制失效,必须被禁止。
限制的字段
spec.containers[*].securityContext.privileged
spec.initContainers[*].securityContext.privileged
spec.ephemeralContainers[*].securityContext.privileged
准许的取值
权能 必须禁止添加除下列字段之外的权能。
限制的字段
spec.containers[*].securityContext.capabilities.add
spec.initContainers[*].securityContext.capabilities.add
spec.ephemeralContainers[*].securityContext.capabilities.add
准许的取值
未定义、nil AUDIT_WRITE
CHOWN
DAC_OVERRIDE
FOWNER
FSETID
KILL
MKNOD
NET_BIND_SERVICE
SETFCAP
SETGID
SETPCAP
SETUID
SYS_CHROOT
HostPath 卷 必须禁止 HostPath 卷。
限制的字段
准许的取值
宿主端口 应该禁止使用宿主端口,或者至少限制只能使用某确定列表中的端口。
限制的字段
spec.containers[*].ports[*].hostPort
spec.initContainers[*].ports[*].hostPort
spec.ephemeralContainers[*].ports[*].hostPort
准许的取值
AppArmor 在受支持的主机上,默认使用 runtime/default
AppArmor 配置。Baseline 策略应避免覆盖或者禁用默认策略,以及限制覆盖一些配置集合的权限。
限制的字段
metadata.annotations["container.apparmor.security.beta.kubernetes.io/*"]
准许的取值
未定义、nil runtime/default
localhost/*
SELinux 设置 SELinux 类型的操作是被限制的,设置自定义的 SELinux 用户或角色选项是被禁止的。
限制的字段
spec.securityContext.seLinuxOptions.type
spec.containers[*].securityContext.seLinuxOptions.type
spec.initContainers[*].securityContext.seLinuxOptions.type
spec.ephemeralContainers[*].securityContext.seLinuxOptions.type
准许的取值
未定义、"" container_t
container_init_t
container_kvm_t
限制的字段
spec.securityContext.seLinuxOptions.user
spec.containers[*].securityContext.seLinuxOptions.user
spec.initContainers[*].securityContext.seLinuxOptions.user
spec.ephemeralContainers[*].securityContext.seLinuxOptions.user
spec.securityContext.seLinuxOptions.role
spec.containers[*].securityContext.seLinuxOptions.role
spec.initContainers[*].securityContext.seLinuxOptions.role
spec.ephemeralContainers[*].securityContext.seLinuxOptions.role
准许的取值
/proc
挂载类型要求使用默认的 /proc
掩码以减小攻击面。
限制的字段
spec.containers[*].securityContext.procMount
spec.initContainers[*].securityContext.procMount
spec.ephemeralContainers[*].securityContext.procMount
准许的取值
Seccomp Seccomp 配置必须不能显式设置为 Unconfined
。
限制的字段
spec.securityContext.seccompProfile.type
spec.containers[*].securityContext.seccompProfile.type
spec.initContainers[*].securityContext.seccompProfile.type
spec.ephemeralContainers[*].securityContext.seccompProfile.type
准许的取值
未定义、nil RuntimeDefault
Localhost
Sysctls Sysctls 可以禁用安全机制或影响宿主上所有容器,因此除了若干“安全”的子集之外,应该被禁止。如果某 sysctl 是受容器或 Pod 的名字空间限制,且与节点上其他 Pod 或进程相隔离,可认为是安全的。
限制的字段
spec.securityContext.sysctls[*].name
准许的取值
未定义、nil kernel.shm_rmid_forced
net.ipv4.ip_local_port_range
net.ipv4.ip_unprivileged_port_start
net.ipv4.tcp_syncookies
net.ipv4.ping_group_range
Restricted Restricted 策略旨在实施当前保护 Pod 的最佳实践,尽管这样作可能会牺牲一些兼容性。
该类策略主要针对运维人员和安全性很重要的应用的开发人员,以及不太被信任的用户。
下面列举的控制需要被实施(禁止):
说明: 在下述表格中,通配符(*
)意味着一个列表中的所有元素。
例如 spec.containers[*].securityContext
表示 所定义的所有容器 的安全性上下文对象。
如果所列出的任一容器不能满足要求,整个 Pod 将无法通过校验。
Restricted 策略规范 控制 策略 Baseline 策略的所有要求。 卷类型 除了限制 HostPath 卷之外,此类策略还限制可以通过 PersistentVolumes 定义的非核心卷类型。
限制的字段
准许的取值
spec.volumes[*]
列表中的每个条目必须将下面字段之一设置为非空值:spec.volumes[*].configMap
spec.volumes[*].csi
spec.volumes[*].downwardAPI
spec.volumes[*].emptyDir
spec.volumes[*].ephemeral
spec.volumes[*].persistentVolumeClaim
spec.volumes[*].projected
spec.volumes[*].secret
特权提升(v1.8+) 禁止(通过 SetUID 或 SetGID 文件模式)获得特权提升。这是 v1.25+ 中仅针对 Linux 的策略 (spec.os.name != windows)
限制的字段
spec.containers[*].securityContext.allowPrivilegeEscalation
spec.initContainers[*].securityContext.allowPrivilegeEscalation
spec.ephemeralContainers[*].securityContext.allowPrivilegeEscalation
允许的取值
以非 root 账号运行 容器必须以非 root 账号运行。
限制的字段
spec.securityContext.runAsNonRoot
spec.containers[*].securityContext.runAsNonRoot
spec.initContainers[*].securityContext.runAsNonRoot
spec.ephemeralContainers[*].securityContext.runAsNonRoot
准许的取值
如果 Pod 级别 spec.securityContext.runAsNonRoot
设置为 true
,则允许容器组的安全上下文字段设置为 未定义/nil
。 非 root 用户(v1.23+) 容器不可以将 runAsUser 设置为 0
限制的字段
spec.securityContext.runAsUser
spec.containers[*].securityContext.runAsUser
spec.initContainers[*].securityContext.runAsUser
spec.ephemeralContainers[*].securityContext.runAsUser
准许的取值
Seccomp (v1.19+) Seccomp Profile 必须被显式设置成一个允许的值。禁止使用 Unconfined
Profile 或者指定 不存在的 Profile。这是 v1.25+ 中仅针对 Linux 的策略 (spec.os.name != windows)
限制的字段
spec.securityContext.seccompProfile.type
spec.containers[*].securityContext.seccompProfile.type
spec.initContainers[*].securityContext.seccompProfile.type
spec.ephemeralContainers[*].securityContext.seccompProfile.type
准许的取值
如果 Pod 级别的 spec.securityContext.seccompProfile.type
已设置得当,容器级别的安全上下文字段可以为未定义/nil
。
反之如果 所有的 容器级别的安全上下文字段已设置,
则 Pod 级别的字段可为 未定义/nil
。 权能(v1.22+) 容器必须弃用 ALL
权能,并且只允许添加
NET_BIND_SERVICE
权能。这是 v1.25+ 中仅针对 Linux 的策略 (.spec.os.name != "windows")
限制的字段
spec.containers[*].securityContext.capabilities.drop
spec.initContainers[*].securityContext.capabilities.drop
spec.ephemeralContainers[*].securityContext.capabilities.drop
准许的取值
限制的字段
spec.containers[*].securityContext.capabilities.add
spec.initContainers[*].securityContext.capabilities.add
spec.ephemeralContainers[*].securityContext.capabilities.add
准许的取值
策略实例化 将策略定义从策略实例中解耦出来有助于形成跨集群的策略理解和语言陈述,
以免绑定到特定的下层实施机制。
随着相关机制的成熟,这些机制会按策略分别定义在下面。特定策略的实施方法不在这里定义。
Pod 安全性准入控制器
替代方案 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
在 Kubernetes 生态系统中还在开发一些其他的替代方案,例如:
Pod OS 字段 Kubernetes 允许你使用运行 Linux 或 Windows 的节点。你可以在一个集群中混用两种类型的节点。
Kubernetes 中的 Windows 与基于 Linux 的工作负载相比有一些限制和差异。
具体而言,许多 Pod securityContext
字段在 Windows 上不起作用 。
说明: v1.24 之前的 Kubelet 不强制处理 Pod OS 字段,如果集群中有些节点运行早于 v1.24 的版本,
则应将限制性的策略锁定到 v1.25 之前的版本。
限制性的 Pod Security Standard 变更 Kubernetes v1.25 中的另一个重要变化是 限制性的(Restricted) Pod 安全性已更新,
能够处理 pod.spec.os.name
字段。根据 OS 名称,专用于特定 OS 的某些策略对其他 OS 可以放宽限制。
OS 特定的策略控制 仅当 .spec.os.name
不是 windows
时,才需要对以下控制进行限制:
常见问题 为什么不存在介于 Privileged 和 Baseline 之间的策略类型 这里定义的三种策略框架有一个明晰的线性递进关系,从最安全(Restricted)到最不安全,
并且覆盖了很大范围的工作负载。特权要求超出 Baseline 策略者通常是特定于应用的需求,
所以我们没有在这个范围内提供标准框架。
这并不意味着在这样的情形下仍然只能使用 Privileged 框架,
只是说处于这个范围的策略需要因地制宜地定义。
SIG Auth 可能会在将来考虑这个范围的框架,前提是有对其他框架的需求。
安全配置与安全上下文的区别是什么? 安全上下文 在运行时配置 Pod
和容器。安全上下文是在 Pod 清单中作为 Pod 和容器规约的一部分来定义的,
所代表的是传递给容器运行时的参数。
安全策略则是控制面用来对安全上下文以及安全性上下文之外的参数实施某种设置的机制。
在 2020 年 7 月,
Pod 安全性策略 已被废弃,
取而代之的是内置的 Pod 安全性准入控制器 。
沙箱(Sandboxed)Pod 怎么处理? 现在还没有 API 标准来控制 Pod 是否被视作沙箱化 Pod。
沙箱 Pod 可以通过其是否使用沙箱化运行时(如 gVisor 或 Kata Container)来辨别,
不过目前还没有关于什么是沙箱化运行时的标准定义。
沙箱化负载所需要的保护可能彼此各不相同。例如,当负载与下层内核直接隔离开来时,
限制特权化操作的许可就不那么重要。这使得那些需要更多许可权限的负载仍能被有效隔离。
此外,沙箱化负载的保护高度依赖于沙箱化的实现方法。
因此,现在还没有针对所有沙箱化负载的建议配置。
3.9.3 - Pod 安全性准入 对 Pod 安全性准入控制器的概述,Pod 安全性准入控制器可以实施 Pod 安全性标准。
特性状态: Kubernetes v1.25 [stable]
Kubernetes Pod 安全性标准(Security Standards)
为 Pod 定义不同的隔离级别。这些标准能够让你以一种清晰、一致的方式定义如何限制 Pod 行为。
Kubernetes 提供了一个内置的 Pod Security
准入控制器 来执行 Pod 安全标准
(Pod Security Standard)。
创建 Pod 时在名字空间 级别应用这些 Pod 安全限制。
内置 Pod 安全准入强制执行 本页面是 Kubernetes v1.25 文档的一部分。
如果你运行的是其他版本的 Kubernetes,请查阅该版本的文档。
Pod 安全性级别 Pod 安全性准入插件对 Pod
的安全性上下文 有一定的要求,
并且依据 Pod 安全性标准 所定义的三个级别
(privileged
、baseline
和 restricted
)对其他字段也有要求。
关于这些需求的更进一步讨论,请参阅
Pod 安全性标准 页面。
为名字空间设置 Pod 安全性准入控制标签 一旦特性被启用或者安装了 Webhook,你可以配置名字空间以定义每个名字空间中
Pod 安全性准入控制模式。
Kubernetes 定义了一组标签 ,
你可以设置这些标签来定义某个名字空间上要使用的预定义的 Pod 安全性标准级别。
你所选择的标签定义了检测到潜在违例时,
控制面 要采取什么样的动作。
Pod 安全准入模式 模式 描述 enforce 策略违例会导致 Pod 被拒绝 audit 策略违例会触发审计日志 中记录新事件时添加审计注解;但是 Pod 仍是被接受的。 warn 策略违例会触发用户可见的警告信息,但是 Pod 仍是被接受的。
名字空间可以配置任何一种或者所有模式,或者甚至为不同的模式设置不同的级别。
对于每种模式,决定所使用策略的标签有两个:
# 模式的级别标签用来标示对应模式所应用的策略级别
#
# MODE 必须是 `enforce`、`audit` 或 `warn` 之一
# LEVEL 必须是 `privileged`、baseline` 或 `restricted` 之一
pod-security.kubernetes.io/<MODE> : <LEVEL>
# 可选:针对每个模式版本的版本标签可以将策略锁定到
# 给定 Kubernetes 小版本号所附带的版本(例如 v1.25)
#
# MODE 必须是 `enforce`、`audit` 或 `warn` 之一
# VERSION 必须是一个合法的 Kubernetes 小版本号或者 `latest`
pod-security.kubernetes.io/<MODE>-version : <VERSION>
关于用法示例,可参阅使用名字空间标签来强制实施 Pod 安全标准 。
负载资源和 Pod 模板 Pod 通常是通过创建 Deployment 或
Job
这类工作负载对象
来间接创建的。工作负载对象为工作负载资源定义一个 Pod 模板
和一个对应的负责基于该模板来创建 Pod 的控制器 。
为了尽早地捕获违例状况,audit
和 warn
模式都应用到负载资源。
不过,enforce
模式并 不 应用到工作负载资源,仅应用到所生成的 Pod 对象上。
豁免 你可以为 Pod 安全性的实施设置 豁免(Exemptions) 规则,
从而允许创建一些本来会被与给定名字空间相关的策略所禁止的 Pod。
豁免规则可以在准入控制器配置
中静态配置。
豁免规则可以显式枚举。满足豁免标准的请求会被准入控制器 忽略
(所有 enforce
、audit
和 warn
行为都会被略过)。
豁免的维度包括:
Username: 来自用户名已被豁免的、已认证的(或伪装的)的用户的请求会被忽略。RuntimeClassName: 指定了已豁免的运行时类名称的 Pod
和负载资源 会被忽略。Namespace: 位于被豁免的名字空间中的 Pod 和负载资源 会被忽略。注意: 大多数 Pod 是作为对工作负载资源 的响应,
由控制器所创建的,这意味着为某最终用户提供豁免时,只会当该用户直接创建 Pod
时对其实施安全策略的豁免。用户创建工作负载资源时不会被豁免。
控制器服务账号(例如:system:serviceaccount:kube-system:replicaset-controller
)
通常不应该被豁免,因为豁免这类服务账号隐含着对所有能够创建对应工作负载资源的用户豁免。
策略检查时会对以下 Pod 字段的更新操作予以豁免,这意味着如果 Pod
更新请求仅改变这些字段时,即使 Pod 违反了当前的策略级别,请求也不会被拒绝。
除了对 seccomp 或 AppArmor 注解之外的所有元数据(Metadata)更新操作:seccomp.security.alpha.kubernetes.io/pod
(已弃用)container.seccomp.security.alpha.kubernetes.io/*
(已弃用)container.apparmor.security.beta.kubernetes.io/*
对 .spec.activeDeadlineSeconds
的合法更新 对 .spec.tolerations
的合法更新 接下来 3.9.4 - Pod 安全策略 被移除的特性 PodSecurityPolicy 在 Kubernetes v1.21
中被弃用 ,
在 Kubernetes v1.25 中被移除。
作为替代,你可以使用下面任一方式执行类似的限制,或者同时使用下面这两种方式。
有关如何迁移,
参阅从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器 。
有关移除此 API 的更多信息,参阅
弃用 PodSecurityPolicy:过去、现在、未来 。
如果所运行的 Kubernetes 不是 v1.25 版本,则需要查看你所使用的 Kubernetes 版本的对应文档。
3.9.5 - Windows 节点的安全性 本篇介绍特定于 Windows 操作系统的安全注意事项和最佳实践。
保护节点上的 Secret 数据 在 Windows 上,来自 Secret 的数据以明文形式写入节点的本地存储
(与在 Linux 上使用 tmpfs / 内存中文件系统不同)。
作为集群操作员,你应该采取以下两项额外措施:
使用文件 ACL 来保护 Secret 的文件位置。 使用 BitLocker
进行卷级加密。 容器用户 可以为 Windows Pod 或容器指定 RunAsUsername
以作为特定用户执行容器进程。这大致相当于 RunAsUser 。
Windows 容器提供两个默认用户帐户,ContainerUser 和 ContainerAdministrator。
在微软的 Windows 容器安全 文档
何时使用 ContainerAdmin 和 ContainerUser 用户帐户
中介绍了这两个用户帐户之间的区别。
在容器构建过程中,可以将本地用户添加到容器镜像中。
Windows 容器还可以通过使用组管理的服务账号 作为
Active Directory 身份运行。
Pod 级安全隔离 Windows 节点不支持特定于 Linux 的 Pod 安全上下文机制(例如 SELinux、AppArmor、Seccomp 或自定义 POSIX 权能字)。
Windows 上不支持 特权容器。
然而,可以在 Windows 上使用 HostProcess 容器 来执行
Linux 上特权容器执行的许多任务。
3.9.6 - Kubernetes API 访问控制 本页面概述了对 Kubernetes API 的访问控制。
用户使用 kubectl
、客户端库或构造 REST 请求来访问 Kubernetes API 。
人类用户和 Kubernetes 服务账户 都可以被鉴权访问 API。
当请求到达 API 时,它会经历多个阶段,如下图所示:
传输安全 默认情况下,Kubernetes API 服务器在第一个非 localhost 网络接口的 6443 端口上进行监听,
受 TLS 保护。在一个典型的 Kubernetes 生产集群中,API 使用 443 端口。
该端口可以通过 --secure-port
进行变更,监听 IP 地址可以通过 --bind-address
标志进行变更。
API 服务器出示证书。该证书可以使用私有证书颁发机构(CA)签名,也可以基于链接到公认的 CA 的公钥基础架构签名。
该证书和相应的私钥可以通过使用 --tls-cert-file
和 --tls-private-key-file
标志进行设置。
如果你的集群使用私有证书颁发机构,你需要在客户端的 ~/.kube/config
文件中提供该 CA 证书的副本,
以便你可以信任该连接并确认该连接没有被拦截。
你的客户端可以在此阶段出示 TLS 客户端证书。
认证 如上图步骤 1 所示,建立 TLS 后, HTTP 请求将进入认证(Authentication)步骤。
集群创建脚本或者集群管理员配置 API 服务器,使之运行一个或多个身份认证组件。
身份认证组件在认证 节中有更详细的描述。
认证步骤的输入整个 HTTP 请求;但是,通常组件只检查头部或/和客户端证书。
认证模块包含客户端证书、密码、普通令牌、引导令牌和 JSON Web 令牌(JWT,用于服务账户)。
可以指定多个认证模块,在这种情况下,服务器依次尝试每个验证模块,直到其中一个成功。
如果请求认证不通过,服务器将以 HTTP 状态码 401 拒绝该请求。
反之,该用户被认证为特定的 username
,并且该用户名可用于后续步骤以在其决策中使用。
部分验证器还提供用户的组成员身份,其他则不提供。
鉴权 如上图的步骤 2 所示,将请求验证为来自特定的用户后,请求必须被鉴权。
请求必须包含请求者的用户名、请求的行为以及受该操作影响的对象。
如果现有策略声明用户有权完成请求的操作,那么该请求被鉴权通过。
例如,如果 Bob 有以下策略,那么他只能在 projectCaribou
名称空间中读取 Pod。
{
"apiVersion" : "abac.authorization.kubernetes.io/v1beta1" ,
"kind" : "Policy" ,
"spec" : {
"user" : "bob" ,
"namespace" : "projectCaribou" ,
"resource" : "pods" ,
"readonly" : true
}
}
如果 Bob 执行以下请求,那么请求会被鉴权,因为允许他读取 projectCaribou
名称空间中的对象。
{
"apiVersion" : "authorization.k8s.io/v1beta1" ,
"kind" : "SubjectAccessReview" ,
"spec" : {
"resourceAttributes" : {
"namespace" : "projectCaribou" ,
"verb" : "get" ,
"group" : "unicorn.example.org" ,
"resource" : "pods"
}
}
}
如果 Bob 在 projectCaribou
名字空间中请求写(create
或 update
)对象,其鉴权请求将被拒绝。
如果 Bob 在诸如 projectFish
这类其它名字空间中请求读取(get
)对象,其鉴权也会被拒绝。
Kubernetes 鉴权要求使用公共 REST 属性与现有的组织范围或云提供商范围的访问控制系统进行交互。
使用 REST 格式很重要,因为这些控制系统可能会与 Kubernetes API 之外的 API 交互。
Kubernetes 支持多种鉴权模块,例如 ABAC 模式、RBAC 模式和 Webhook 模式等。
管理员创建集群时,他们配置应在 API 服务器中使用的鉴权模块。
如果配置了多个鉴权模块,则 Kubernetes 会检查每个模块,任意一个模块鉴权该请求,请求即可继续;
如果所有模块拒绝了该请求,请求将会被拒绝(HTTP 状态码 403)。
要了解更多有关 Kubernetes 鉴权的更多信息,包括有关使用支持鉴权模块创建策略的详细信息,
请参阅鉴权 。
准入控制 准入控制模块是可以修改或拒绝请求的软件模块。
除鉴权模块可用的属性外,准入控制模块还可以访问正在创建或修改的对象的内容。
准入控制器对创建、修改、删除或(通过代理)连接对象的请求进行操作。
准入控制器不会对仅读取对象的请求起作用。
有多个准入控制器被配置时,服务器将依次调用它们。
这一操作如上图的步骤 3 所示。
与身份认证和鉴权模块不同,如果任何准入控制器模块拒绝某请求,则该请求将立即被拒绝。
除了拒绝对象之外,准入控制器还可以为字段设置复杂的默认值。
可用的准入控制模块在准入控制器 中进行了描述。
请求通过所有准入控制器后,将使用检验例程检查对应的 API 对象,然后将其写入对象存储(如步骤 4 所示)。
审计 Kubernetes 审计提供了一套与安全相关的、按时间顺序排列的记录,其中记录了集群中的操作序列。
集群对用户、使用 Kubernetes API 的应用程序以及控制平面本身产生的活动进行审计。
更多信息请参考审计 。
接下来 阅读更多有关身份认证、鉴权和 API 访问控制的文档:
你可以了解
3.9.7 - 基于角色的访问控制良好实践 为集群操作人员提供的良好的 RBAC 设计原则和实践。
Kubernetes RBAC
是一项重要的安全控制措施,用于保证集群用户和工作负载只能访问履行自身角色所需的资源。
在为集群用户设计权限时,请务必确保集群管理员知道可能发生特权提级的地方,
降低因过多权限而导致安全事件的风险。
此文档的良好实践应该与通用
RBAC 文档 一起阅读。
通用的良好实践 最小特权 理想情况下,分配给用户和服务帐户的 RBAC 权限应该是最小的。
仅应使用操作明确需要的权限,虽然每个集群会有所不同,但可以应用的一些常规规则:
尽可能在命名空间级别分配权限。授予用户在特定命名空间中的权限时使用 RoleBinding
而不是 ClusterRoleBinding。 尽可能避免通过通配符设置权限,尤其是对所有资源的权限。
由于 Kubernetes 是一个可扩展的系统,因此通过通配符来授予访问权限不仅会授予集群中当前的所有对象类型,
还包含所有未来被创建的所有对象类型。 管理员不应使用 cluster-admin
账号,除非特别需要。为低特权帐户提供
伪装权限
可以避免意外修改集群资源。 避免将用户添加到 system:masters
组。任何属于此组成员的用户都会绕过所有 RBAC 权限检查,
始终具有不受限制的超级用户访问权限,并且不能通过删除 RoleBinding
或 ClusterRoleBinding
来取消其权限。顺便说一句,如果集群使用 Webhook 鉴权,此组的成员身份也会绕过该
Webhook(来自属于该组成员的用户的请求永远不会发送到 Webhook)。 最大限度地减少特权令牌的分发 理想情况下,不应为 Pod 分配具有强大权限(例如,在特权提级的风险 中列出的任一权限)的服务帐户。
如果工作负载需要比较大的权限,请考虑以下做法:
限制运行此类 Pod 的节点数量。确保你运行的任何 DaemonSet 都是必需的,
并且以最小权限运行,以限制容器逃逸的影响范围。 避免将此类 Pod 与不可信任或公开的 Pod 在一起运行。
考虑使用污点和容忍度 、
节点亲和性 或
Pod 反亲和性 确保
Pod 不会与不可信或不太受信任的 Pod 一起运行。
特别注意可信度不高的 Pod 不符合 Restricted Pod 安全标准的情况。 加固 Kubernetes 默认提供访问权限并非是每个集群都需要的。
审查默认提供的 RBAC 权限为安全加固提供了机会。
一般来说,不应该更改 system:
帐户的某些权限,有一些方式来强化现有集群的权限:
审查 system:unauthenticated
组的绑定,并在可能的情况下将其删除,
因为这会给所有能够访问 API 服务器的人以网络级别的权限。 通过设置 automountServiceAccountToken: false
来避免服务账号令牌的默认自动挂载,
有关更多详细信息,请参阅使用默认服务账号令牌 。
此参数可覆盖 Pod 服务账号设置,而需要服务账号令牌的工作负载仍可以挂载。 定期检查 定期检查 Kubernetes RBAC 设置是否有冗余条目和提权可能性是至关重要的。
如果攻击者能够创建与已删除用户同名的用户账号,
他们可以自动继承被删除用户的所有权限,尤其是分配给该用户的权限。
Kubernetes RBAC - 权限提权的风险 在 Kubernetes RBAC 中有许多特权,如果被授予,
用户或服务帐户可以提升其在集群中的权限并可能影响集群外的系统。
本节旨在提醒集群操作员需要注意的不同领域,
以确保他们不会无意中授予超出预期的集群访问权限。
列举 Secret 大家都很清楚,若允许对 Secrets 执行 get
访问,用户就获得了访问 Secret 内容的能力。
同样需要注意的是:list
和 watch
访问也会授权用户获取 Secret 的内容。
例如,当返回 List 响应时(例如,通过
kubectl get secrets -A -o yaml
),响应包含所有 Secret 的内容。
工作负载的创建 在一个命名空间中创建工作负载(Pod 或管理 Pod 的工作负载资源 )
的权限隐式地授予了对该命名空间中许多其他资源的访问权限,例如可以挂载在
Pod 中的 Secret、ConfigMap 和 PersistentVolume。
此外,由于 Pod 可以被任何服务账号 运行,
因此授予创建工作负载的权限也会隐式地授予该命名空间中任何服务账号的 API 访问级别。
可以运行特权 Pod 的用户可以利用该访问权限获得节点访问权限,
并可能进一步提升他们的特权。如果你不完全信任某用户或其他主体,
不相信他们能够创建比较安全且相互隔离的 Pod,你应该强制实施 Baseline
或 Restricted Pod 安全标准。
你可以使用 Pod 安全性准入 或其他(第三方)机制来强制实施这些限制。
出于这些原因,命名空间应该用于隔离不同的信任级别或不同租户所需的资源。
遵循最小特权 原则并分配最小权限集仍被认为是最佳实践,
但命名空间内的边界概念应视为比较弱。
持久卷的创建 如 PodSecurityPolicy
文档中所述,创建 PersistentVolumes 的权限可以提权访问底层主机。
如果需要访问 PersistentVolume,受信任的管理员应该创建 PersistentVolume
,
受约束的用户应该使用 PersistentVolumeClaim
访问该存储。
访问 Node 的 proxy
子资源 有权访问 Node 对象的 proxy 子资源的用户有权访问 Kubelet API,
这允许在他们有权访问的节点上的所有 Pod 上执行命令。
此访问绕过审计日志记录和准入控制,因此在授予对此资源的权限前应小心。
esclate 动词 通常,RBAC 系统会阻止用户创建比他所拥有的更多权限的 ClusterRole
。
而 escalate
动词是个例外。如
RBAC 文档
中所述,拥有此权限的用户可以有效地提升他们的权限。
bind 动词 与 escalate
动作类似,授予此权限的用户可以绕过 Kubernetes
对权限提升的内置保护,用户可以创建并绑定尚不具有的权限的角色。
impersonate 动词 此动词允许用户伪装并获得集群中其他用户的权限。
授予它时应小心,以确保通过其中一个伪装账号不会获得过多的权限。
CSR 和证书颁发 CSR API 允许用户拥有 create
CSR 的权限和 update
certificatesigningrequests/approval
的权限,
其中签名者是 kubernetes.io/kube-apiserver-client
,
通过此签名创建的客户端证书允许用户向集群进行身份验证。
这些客户端证书可以包含任意的名称,包括 Kubernetes 系统组件的副本。
这将有利于特权提级。
令牌请求 拥有 serviceaccounts/token
的 create
权限的用户可以创建
TokenRequest 来发布现有服务帐户的令牌。
控制准入 Webhook 可以控制 validatingwebhookconfigurations
或 mutatingwebhookconfigurations
的用户可以控制能读取任何允许进入集群的对象的 webhook,
并且在有变更 webhook 的情况下,还可以变更准入的对象。
Kubernetes RBAC - 拒绝服务攻击的风险 对象创建拒绝服务 有权在集群中创建对象的用户根据创建对象的大小和数量可能会创建足够大的对象,
产生拒绝服务状况,如 Kubernetes 使用的 etcd 容易受到 OOM 攻击 中的讨论。
允许太不受信任或者不受信任的用户对系统进行有限的访问在多租户集群中是特别重要的。
缓解此问题的一种选择是使用资源配额 以限制可以创建的对象数量。
接下来 3.9.8 - Kubernetes Secret 良好实践 帮助集群管理员和应用开发者更好管理 Secret 的原理和实践。
在 Kubernetes 中,Secret 是这样一个对象: secret 用于存储敏感信息,如密码、OAuth 令牌和 SSH 密钥。
Secret 允许用户对如何使用敏感信息进行更多的控制,并减少信息意外暴露的风险。
默认情况下,Secret 值被编码为 base64 字符串并以非加密的形式存储,但可以配置为
静态加密(Encrypt at rest) 。
Pod 可以通过多种方式引用 Secret,
例如在卷挂载中引用或作为环境变量引用。Secret 设计用于机密数据,而
ConfigMap
设计用于非机密数据。
以下良好实践适用于集群管理员和应用开发者。遵从这些指导方针有助于提高 Secret 对象中敏感信息的安全性,
还可以更有效地管理你的 Secret。
集群管理员 本节提供了集群管理员可用于提高集群中机密信息安全性的良好实践。
默认情况下,Secret 对象以非加密的形式存储在 etcd 中。
你配置对在 etcd
中存储的 Secret 数据进行加密。相关的指导信息,
请参阅静态加密 Secret 数据 。
配置 Secret 资源的最小特权访问 当规划诸如 Kubernetes
基于角色的访问控制 (RBAC)
这类访问控制机制时,需要注意访问 Secret
对象的以下指导信息。
你还应遵从 RBAC 良好实践 中的其他指导信息。
组件 :限制仅最高特权的系统级组件可以执行 watch
或 list
访问。
仅在组件的正常行为需要时才授予对 Secret 的 get
访问权限。人员 :限制对 Secret 的 get
、watch
或 list
访问权限。仅允许集群管理员访问 etcd
。
这包括只读访问。对于更复杂的访问控制,例如使用特定注解限制对 Secret 的访问,请考虑使用第三方鉴权机制。注意: 授予对 Secret 的 list
访问权限将意味着允许对应主体获取 Secret 的内容。
如果一个用户可以创建使用某 Secret 的 Pod,则该用户也可以看到该 Secret 的值。
即使集群策略不允许用户直接读取 Secret,同一用户也可能有权限运行 Pod 进而暴露该 Secret。
你可以检测或限制具有此访问权限的用户有意或无意地暴露 Secret 数据所造成的影响。
这里有一些建议:
使用生命期短暂的 Secret 实现对特定事件发出警报的审计规则,例如同一用户并发读取多个 Secret 时发出警报 改进 etcd 管理策略 不再使用 etcd
所使用的持久存储时,考虑擦除或粉碎这些数据。
如果存在多个 etcd
实例,则在实例之间配置加密的 SSL/TLS 通信以保护传输中的 Secret 数据。
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
你可以使用第三方 Secret 存储提供商将机密数据保存在你的集群之外,然后配置 Pod 访问该信息。
Kubernetes Secret 存储 CSI 驱动 是一个 DaemonSet,
它允许 kubelet 从外部存储中检索 Secret,并将 Secret 作为卷挂载到特定的、你授权访问数据的 Pod。
有关支持的提供商列表,请参阅
Secret 存储 CSI 驱动的提供商 。
开发者 本节为开发者提供了构建和部署 Kubernetes 资源时用于改进机密数据安全性的良好实践。
限制特定容器集合才能访问 Secret 如果你在一个 Pod 中定义了多个容器,且仅其中一个容器需要访问 Secret,则可以定义卷挂载或环境变量配置,
这样其他容器就不会有访问该 Secret 的权限。
读取后保护 Secret 数据 应用程序从一个环境变量或一个卷读取机密信息的值后仍然需要保护这些值。
例如,你的应用程序必须避免以明文记录 Secret 数据,还必须避免将这些数据传输给不受信任的一方。
避免共享 Secret 清单 如果你通过清单(Manifest) 配置 Secret,
同时将该 Secret 数据编码为 base64,
那么共享此文件或将其检入一个源代码仓库就意味着有权读取该清单的所有人都能使用该 Secret。
注意: Base64 编码 不是 一种加密方法,它没有为纯文本提供额外的保密机制。
3.9.9 - 多租户 此页面概述了集群多租户的可用配置选项和最佳实践。
共享集群可以节省成本并简化管理。
然而,共享集群也带来了诸如安全性、公平性和管理嘈杂邻居 等挑战。
集群可以通过多种方式共享。在某些情况下,不同的应用可能会在同一个集群中运行。
在其他情况下,同一应用的多个实例可能在同一个集群中运行,每个实例对应一个最终用户。
所有这些类型的共享经常使用一个总括术语 多租户(Multi-Tenancy) 来表述。
虽然 Kubernetes 没有最终用户或租户的一阶概念,
它还是提供了几个特性来帮助管理不同的租户需求。下面将对此进行讨论。
用例 确定如何共享集群的第一步是理解用例,以便你可以评估可用的模式和工具。
一般来说,Kubernetes 集群中的多租户分为两大类,但也可以有许多变体和混合。
多团队 多租户的一种常见形式是在组织内的多个团队之间共享一个集群,每个团队可以操作一个或多个工作负载。
这些工作负载经常需要相互通信,并与位于相同或不同集群上的其他工作负载进行通信。
在这一场景中,团队成员通常可以通过类似 kubectl
等工具直接访问 Kubernetes 资源,
或者通过 GitOps 控制器或其他类型的自动化发布工具间接访问 Kubernetes 资源。
不同团队的成员之间通常存在某种程度的信任,
但 RBAC、配额和网络策略等 Kubernetes 策略对于安全、公平地共享集群至关重要。
多客户 多租户的另一种主要形式通常涉及为客户运行多个工作负载实例的软件即服务 (SaaS) 供应商。
这种业务模型与其部署风格之间的相关非常密切,以至于许多人称之为 “SaaS 租户”。 但是,更好的术语可能是“多客户租户(Multi-Customer Tenancy)”,因为 SaaS 供应商也可以使用其他部署模型,
并且这种部署模型也可以在 SaaS 之外使用。
在这种情况下,客户无权访问集群;
从他们的角度来看,Kubernetes 是不可见的,仅由供应商用于管理工作负载。
成本优化通常是一个关键问题,Kubernetes 策略用于确保工作负载彼此高度隔离。
术语 租户 在讨论 Kubernetes 中的多租户时,“租户”没有单一的定义。
相反,租户的定义将根据讨论的是多团队还是多客户租户而有所不同。
在多团队使用中,租户通常是一个团队,
每个团队通常部署少量工作负载,这些工作负载会随着服务的复杂性而发生规模伸缩。
然而,“团队”的定义本身可能是模糊的,
因为团队可能被组织成更高级别的部门或细分为更小的团队。
相反,如果每个团队为每个新客户部署专用的工作负载,那么他们使用的是多客户租户模型。
在这种情况下,“租户”只是共享单个工作负载的一组用户。
这种租户可能大到整个公司,也可能小到该公司的一个团队。
在许多情况下,同一组织可能在不同的上下文中使用“租户”的两种定义。
例如,一个平台团队可能向多个内部“客户”提供安全工具和数据库等共享服务,
而 SaaS 供应商也可能让多个团队共享一个开发集群。
最后,混合架构也是可能的,
例如,某 SaaS 提供商为每个客户的敏感数据提供独立的工作负载,同时提供多租户共享的服务。
展示共存租户模型的集群 隔离 使用 Kubernetes 设计和构建多租户解决方案有多种方法。
每种方法都有自己的一组权衡,这些权衡会影响隔离级别、实现工作量、操作复杂性和服务成本。
Kubernetes 集群由运行 Kubernetes 软件的控制平面和由工作节点组成的数据平面组成,
租户工作负载作为 Pod 在工作节点上执行。
租户隔离可以根据组织要求应用于控制平面和数据平面。
所提供的隔离级别有时会使用一些术语来描述,例如 “硬性(Hard)” 多租户意味着强隔离,
而 “柔性(Soft)” 多租户意味着较弱的隔离。
特别是,“硬性”多租户通常用于描述租户彼此不信任的情况,
并且大多是从安全和资源共享的角度(例如,防范数据泄露或 DoS 攻击等)。
由于数据平面通常具有更大的攻击面,“硬性”多租户通常需要额外注意隔离数据平面,
尽管控制平面隔离也很关键。
但是,“硬性”和“柔性”这两个术语常常令人困惑,因为没有一种定义能够适用于所有用户。
相反,依据“硬度(Hardness)”或“柔度(Softness)”所定义的广泛谱系则更容易理解,
根据你的需求,可以使用许多不同的技术在集群中维护不同类型的隔离。
在更极端的情况下,彻底放弃所有集群级别的共享并为每个租户分配其专用集群可能更容易或有必要,
如果认为虚拟机所提供的安全边界还不够,甚至可以在专用硬件上运行。
对于托管的 Kubernetes 集群而言,这种方案可能更容易,
其中创建和操作集群的开销至少在一定程度上由云提供商承担。
必须根据管理多个集群的成本和复杂性来评估更强的租户隔离的好处。
Multi-Cluster SIG 负责解决这些类型的用例。
本页的其余部分重点介绍用于共享 Kubernetes 集群的隔离技术。
但是,即使你正在考虑使用专用集群,查看这些建议也可能很有价值,
因为如果你的需求或功能发生变化,它可以让你在未来比较灵活地切换到共享集群。
控制面隔离 控制平面隔离确保不同租户无法访问或影响彼此的 Kubernetes API 资源。
命名空间 在 Kubernetes 中,
命名空间 提供了一种在单个集群中隔离 API 资源组的机制。
这种隔离有两个关键维度:
一个命名空间中的对象名称可以与其他命名空间中的名称重叠,类似于文件夹中的文件。
这允许租户命名他们的资源,而无需考虑其他租户在做什么。 许多 Kubernetes 安全策略的作用域是命名空间。
例如,RBAC Role 和 NetworkPolicy 是命名空间作用域的资源。
使用 RBAC,可以将用户和服务帐户限制在一个命名空间中。 在多租户环境中,命名空间有助于将租户的工作负载划分到各不相同的逻辑管理单元中。
事实上,一种常见的做法是将每个工作负载隔离在自己的命名空间中,
即使多个工作负载由同一个租户操作。
这可确保每个工作负载都有自己的身份,并且可以使用适当的安全策略进行配置。
命名空间隔离模型需要配置其他几个 Kubernetes 资源、网络插件,
并遵守安全最佳实践以正确隔离租户工作负载。
这些考虑将在下面讨论。
访问控制 控制平面最重要的隔离类型是授权。如果各个团队或其工作负载可以访问或修改彼此的 API 资源,
他们可以更改或禁用所有其他类型的策略,从而取消这些策略可能提供的任何保护。
因此,确保每个租户只对他们需要的命名空间有适当的访问权,
而不是更多,这一点至关重要。这被称为“最小特权原则(Principle of Least Privileges)”。
基于角色的访问控制 (RBAC) 通常用于在 Kubernetes 控制平面中对用户和工作负载(服务帐户)强制执行鉴权。
角色
和角色绑定 是两种
Kubernetes 对象,用来在命名空间级别对应用实施访问控制;
对集群级别的对象访问鉴权也有类似的对象,不过这些对象对于多租户集群不太有用。
在多团队环境中,必须使用 RBAC 来限制租户只能访问合适的命名空间,
并确保集群范围的资源只能由集群管理员等特权用户访问或修改。
如果一个策略最终授予用户的权限比他们所需要的还多,
这可能是一个信号,表明包含受影响资源的命名空间应该被重构为更细粒度的命名空间。
命名空间管理工具可以通过将通用 RBAC 策略应用于不同的命名空间来简化这些细粒度命名空间的管理,
同时在必要时仍允许细粒度策略。
配额 Kubernetes 工作负载消耗节点资源,例如 CPU 和内存。在多租户环境中,
你可以使用资源配额 来管理租户工作负载的资源使用情况。
对于多团队场景,各个租户可以访问 Kubernetes API,你可以使用资源配额来限制租户可以创建的 API 资源的数量
(例如:Pod 的数量,或 ConfigMap 的数量)。
对对象计数的限制确保了公平性,并有助于避免嘈杂邻居 问题影响共享控制平面的其他租户。
资源配额是命名空间作用域的对象。
通过将租户映射到命名空间,
集群管理员可以使用配额来确保租户不能垄断集群的资源或压垮控制平面。
命名空间管理工具简化了配额的管理。
此外,虽然 Kubernetes 配额仅针对单个命名空间,
但一些命名空间管理工具允许多个命名空间组共享配额,
与内置配额相比,降低了管理员的工作量,同时为其提供了更大的灵活性。
配额可防止单个租户所消耗的资源超过其被分配的份额,从而最大限度地减少嘈杂邻居 问题,
即一个租户对其他租户工作负载的性能产生负面影响。
当你对命名空间应用配额时,
Kubernetes 要求你还为每个容器指定资源请求和限制。
限制是容器可以消耗的资源量的上限。
根据资源类型,尝试使用超出配置限制的资源的容器将被限制或终止。
当资源请求设置为低于限制时,
每个容器所请求的数量都可以得到保证,但可能仍然存在跨工作负载的一些潜在影响。
配额不能针对所共享的所有资源(例如网络流量)提供保护。
节点隔离(如下所述)可能是解决此问题的更好方法。
数据平面隔离 数据平面隔离确保不同租户的 Pod 和工作负载之间被充分隔离。
网络隔离 默认情况下,Kubernetes 集群中的所有 Pod 都可以相互通信,并且所有网络流量都是未加密的。
这可能导致安全漏洞,导致流量被意外或恶意发送到非预期目的地,
或被受感染节点上的工作负载拦截。
Pod 之间的通信可以使用网络策略 来控制,
它使用命名空间标签或 IP 地址范围来限制 Pod 之间的通信。
在需要租户之间严格网络隔离的多租户环境中,
建议从拒绝 Pod 之间通信的默认策略入手,
然后添加一条允许所有 Pod 查询 DNS 服务器以进行名称解析的规则。
有了这样的默认策略之后,你就可以开始添加允许在命名空间内进行通信的更多规则。
另外建议不要在网络策略定义中对 namespaceSelector 字段使用空标签选择算符 “{}”,
以防需要允许在命名空间之间传输流量。
该方案可根据需要进一步细化。
请注意,这仅适用于单个控制平面内的 Pod;
属于不同虚拟控制平面的 Pod 不能通过 Kubernetes 网络相互通信。
命名空间管理工具可以简化默认或通用网络策略的创建。
此外,其中一些工具允许你在整个集群中强制实施一组一致的命名空间标签,
确保它们是你策略的可信基础。
警告: 网络策略需要一个支持网络策略实现的
CNI 插件 。
否则,NetworkPolicy 资源将被忽略。
服务网格可以提供更高级的网络隔离,
除了命名空间之外,它还提供基于工作负载身份的 OSI 第 7 层策略。
这些更高层次的策略可以更轻松地管理基于命名空间的多租户,
尤其是存在多个命名空间专用于某一个租户时。
服务网格还经常使用双向 TLS 提供加密能力,
即使在存在受损节点的情况下也能保护你的数据,
并且可以跨专用或虚拟集群工作。
但是,它们的管理可能要复杂得多,并且可能并不适合所有用户。
存储隔离 Kubernetes 提供了若干类型的卷,可以用作工作负载的持久存储。
为了安全和数据隔离,建议使用动态卷制备 ,
并且应避免使用节点资源的卷类型。
存储类(StorageClass) 允许你根据服务质量级别、
备份策略或由集群管理员确定的自定义策略描述集群提供的自定义存储“类”。
Pod 可以使用持久卷申领(PersistentVolumeClaim) 请求存储。
PersistentVolumeClaim 是一种命名空间作用域的资源,
它可以隔离存储系统的不同部分,并将隔离出来的存储提供给共享 Kubernetes 集群中的租户专用。
但是,重要的是要注意 PersistentVolume 是集群作用域的资源,
并且其生命周期独立于工作负载和命名空间的生命周期。
例如,你可以为每个租户配置一个单独的 StorageClass,并使用它来加强隔离。
如果一个 StorageClass 是共享的,你应该设置一个回收策略
以确保 PersistentVolume 不能在不同的命名空间中重复使用。
沙箱容器 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Kubernetes Pod 由在工作节点上执行的一个或多个容器组成。
容器利用操作系统级别的虚拟化,
因此提供的隔离边界比使用基于硬件虚拟化的虚拟机弱一些。
在共享环境中,攻击者可以利用应用和系统层中未修补的漏洞实现容器逃逸和远程代码执行,
从而允许访问主机资源。
在某些应用中,例如内容管理系统(CMS),
客户可能被授权上传和执行非受信的脚本或代码。
无论哪种情况,都需要使用强隔离进一步隔离和保护工作负载的机制。
沙箱提供了一种在共享集群中隔离运行中的工作负载的方法。
它通常涉及在单独的执行环境(例如虚拟机或用户空间内核)中运行每个 Pod。
当你运行不受信任的代码时(假定工作负载是恶意的),通常建议使用沙箱,
这种隔离是必要的,部分原因是由于容器是在共享内核上运行的进程。
它们从底层主机挂载像 /sys 和 /proc 这样的文件系统,
这使得它们不如在具有自己内核的虚拟机上运行的应用安全。
虽然 seccomp、AppArmor 和 SELinux 等控件可用于加强容器的安全性,
但很难将一套通用规则应用于在共享集群中运行的所有工作负载。
在沙箱环境中运行工作负载有助于将主机隔离开来,不受容器逃逸影响,
在容器逃逸场景中,攻击者会利用漏洞来访问主机系统以及在该主机上运行的所有进程/文件。
虚拟机和用户空间内核是两种流行的沙箱方法。
可以使用以下沙箱实现:
gVisor 拦截来自容器的系统调用,并通过用户空间内核运行它们,
用户空间内核采用 Go 编写,对底层主机的访问是受限的Kata Containers 是符合 OCI 的运行时,允许你在 VM 中运行容器。
Kata 中提供的硬件虚拟化为运行不受信任代码的容器提供了额外的安全层。节点隔离 节点隔离是另一种可用于将租户工作负载相互隔离的技术。
通过节点隔离,一组节点专用于运行来自特定租户的 Pod,并且禁止混合不同租户 Pod 集合。
这种配置减少了嘈杂的租户问题,因为在一个节点上运行的所有 Pod 都将属于一个租户。
节点隔离的信息泄露风险略低,
因为成功实现容器逃逸的攻击者也只能访问挂载在该节点上的容器和卷。
尽管来自不同租户的工作负载在不同的节点上运行,
仍然很重要的是要注意 kubelet 和
(除非使用虚拟控制平面)API 服务仍然是共享服务。
熟练的攻击者可以使用分配给 kubelet 或节点上运行的其他 Pod
的权限在集群内横向移动并获得对其他节点上运行的租户工作负载的访问权限。
如果这是一个主要问题,请考虑实施补偿控制,
例如使用 seccomp、AppArmor 或 SELinux,或者探索使用沙箱容器,或者为每个租户创建单独的集群。
从计费的角度来看,节点隔离比沙箱容器更容易理解,
因为你可以按节点而不是按 Pod 收费。
它的兼容性和性能问题也较少,而且可能比沙箱容器更容易实现。
例如,可以为每个租户的节点配置污点,
以便只有具有相应容忍度的 Pod 才能在其上运行。
然后可以使用变更性质的 Webhook 自动向部署到租户命名空间中的 Pod 添加容忍度和节点亲和性,
以便它们在为该租户指定的一组特定节点上运行。
节点隔离可以使用将 Pod 指派给节点 或
Virtual Kubelet 来实现。
额外的注意事项 本节讨论与多租户相关的其他 Kubernetes 结构和模式。
API 优先级和公平性 API 优先级和公平性 是 Kubernetes 的一个特性,
允许你为集群中运行的某些 Pod 赋予优先级。
当应用调用 Kubernetes API 时,API 服务器会评估分配给 Pod 的优先级。
来自具有较高优先级的 Pod 的调用会在具有较低优先级的 Pod 的调用之前完成。
当争用很激烈时,较低优先级的调用可以排队,直到服务器不那么忙,或者你可以拒绝请求。
使用 API 优先级和公平性在 SaaS 环境中并不常见,
除非你允许客户运行与 Kubernetes API 接口的应用,例如控制器。
服务质量 (QoS) 当你运行 SaaS 应用时,
你可能希望能够为不同的租户提供不同的服务质量 (QoS) 层级。
例如,你可能拥有具有性能保证和功能较差的免费增值服务,
以及具有一定性能保证的收费服务层。
幸运的是,有几个 Kubernetes 结构可以帮助你在共享集群中完成此任务,
包括网络 QoS、存储类以及 Pod 优先级和抢占。
这些都是为了给租户提供他们所支付的服务质量。
让我们从网络 QoS 开始。
通常,节点上的所有 Pod 共享一个网络接口。
如果没有网络 QoS,一些 Pod 可能会以牺牲其他 Pod 为代价不公平地消耗可用带宽。
Kubernetes 带宽插件 为网络创建
扩展资源 ,
以允许你使用 Kubernetes 的 resources 结构,即 requests 和 limits 设置。
通过使用 Linux tc 队列将速率限制应用于 Pod。
请注意,根据支持流量整形 文档,
该插件被认为是实验性的,在生产环境中使用之前应该进行彻底的测试。
对于存储 QoS,你可能希望创建具有不同性能特征的不同存储类或配置文件。
每个存储配置文件可以与不同的服务层相关联,该服务层针对 IO、冗余或吞吐量等不同的工作负载进行优化。
可能需要额外的逻辑来允许租户将适当的存储配置文件与其工作负载相关联。
最后,还有 Pod 优先级和抢占 ,
你可以在其中为 Pod 分配优先级值。
在调度 Pod 时,当没有足够的资源来调度分配了较高优先级的 Pod 时,
调度程序将尝试驱逐具有较低优先级的 Pod。
如果你有一个用例,其中租户在共享集群中具有不同的服务层,例如免费和付费,
你可能希望使用此功能为某些层级提供更高的优先级。
DNS Kubernetes 集群包括一个域名系统(DNS)服务,
可为所有服务和 Pod 提供从名称到 IP 地址的转换。
默认情况下,Kubernetes DNS 服务允许在集群中的所有命名空间中进行查找。
在多租户环境中,租户可以访问 Pod 和其他 Kubernetes 资源,
或者在需要更强隔离的情况下,可能需要阻止 Pod 在其他名称空间中查找服务。
你可以通过为 DNS 服务配置安全规则来限制跨命名空间的 DNS 查找。
例如,CoreDNS(Kubernetes 的默认 DNS 服务)可以利用 Kubernetes
元数据来限制对命名空间内的 Pod 和服务的查询。
有关更多信息,请阅读 CoreDNS 文档中配置此功能的
示例 。
当使用各租户独立虚拟控制面 模型时,
必须为每个租户配置 DNS 服务或必须使用多租户 DNS 服务。参见一个
CoreDNS 的定制版本 支持多租户的示例。
Operators Operator 模式 是管理应用的 Kubernetes 控制器。
Operator 可以简化应用的多个实例的管理,例如数据库服务,
这使它们成为多消费者 (SaaS) 多租户用例中的通用构建块。
在多租户环境中使用 Operators 应遵循一套更严格的准则。具体而言,Operator 应:
支持在不同的租户命名空间内创建资源,而不仅仅是在部署 Operator 的命名空间内。 确保 Pod 配置了资源请求和限制,以确保调度和公平。 支持节点隔离、沙箱容器等数据平面隔离技术的 Pod 配置。 实现 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
为多租户共享 Kubernetes 集群有两种主要方法:
使用命名空间(即每个租户独立的命名空间)
或虚拟化控制平面(即每个租户独立的虚拟控制平面)。
在这两种情况下,还建议对数据平面隔离和其他考虑事项,如 API 优先级和公平性,进行管理。
Kubernetes 很好地支持命名空间隔离,其资源开销可以忽略不计,并提供了允许租户适当交互的机制,
例如允许服务之间的通信。
但是,它可能很难配置,而且不适用于非命名空间作用域的 Kubernetes 资源,例如自定义资源定义、存储类和 Webhook 等。
控制平面虚拟化允许以更高的资源使用率和更困难的跨租户共享为代价隔离非命名空间作用域的资源。
当命名空间隔离不足但不希望使用专用集群时,这是一个不错的选择,
因为维护专用集群的成本很高(尤其是本地集群),
或者由于专用集群的额外开销较高且缺乏资源共享。
但是,即使在虚拟化控制平面中,你也可能会看到使用命名空间的好处。
以下各节将更详细地讨论这两个选项:
每个租户独立的命名空间 如前所述,你应该考虑将每个工作负载隔离在其自己的命名空间中,
即使你使用的是专用集群或虚拟化控制平面。
这可确保每个工作负载只能访问其自己的资源,例如 ConfigMap 和 Secret,
并允许你为每个工作负载定制专用的安全策略。
此外,最佳实践是为整个集群中的每个命名空间名称提供唯一的名称(即,即使它们位于单独的集群中),
因为这使你将来可以灵活地在专用集群和共享集群之间切换,
或者使用多集群工具,例如服务网格。
相反,在租户级别分配命名空间也有优势,
而不仅仅是工作负载级别,
因为通常有一些策略适用于单个租户拥有的所有工作负载。
然而,这种方案也有自己的问题。
首先,这使得为各个工作负载定制策略变得困难或不可能,
其次,确定应该赋予命名空间的单一级别的 “租户” 可能很困难。
例如,一个组织可能有部门、团队和子团队 - 哪些应该分配一个命名空间?
为了解决这个问题,Kubernetes 提供了
Hierarchical Namespace Controller (HNC) ,
它允许你将多个命名空间组织成层次结构,并在它们之间共享某些策略和资源。
它还可以帮助你管理命名空间标签、命名空间生命周期和委托管理,
并在相关命名空间之间共享资源配额。
这些功能在多团队和多客户场景中都很有用。
下面列出了提供类似功能并有助于管理命名空间资源的其他项目:
多团队租户 多客户租户 策略引擎 策略引擎提供了验证和生成租户配置的特性:
每个租户独立的虚拟控制面 控制面隔离的另一种形式是使用 Kubernetes 扩展为每个租户提供一个虚拟控制面,
以实现集群范围内 API 资源的分段。
数据平面隔离 技术可以与此模型一起使用,
以安全地跨多个租户管理工作节点。
基于虚拟控制面的多租户模型通过为每个租户提供专用控制面组件来扩展基于命名空间的多租户,
从而完全控制集群范围的资源和附加服务。
工作节点在所有租户之间共享,并由租户通常无法访问的 Kubernetes 集群管理。
该集群通常被称为 超集群(Super-Cluster) (或有时称为 host-cluster )。
由于租户的控制面不直接与底层计算资源相关联,因此它被称为虚拟控制平面 。
虚拟控制面通常由 Kubernetes API 服务器、控制器管理器和 etcd 数据存储组成。
它通过元数据同步控制器与超集群交互,
该控制器跨租户控制面和超集群控制面对变化进行协调。
通过使用每个租户单独的专用控制面,可以解决由于所有租户共享一个 API 服务器而导致的大部分隔离问题。
例如,控制平面中的嘈杂邻居、策略错误配置导致的不可控爆炸半径以及如
Webhook 和 CRD 等集群范围对象之间的冲突。
因此,虚拟控制平面模型特别适用于每个租户都需要访问
Kubernetes API 服务器并期望具有完整集群可管理性的情况。
改进的隔离是以每个租户运行和维护一个单独的虚拟控制平面为代价的。
此外,租户层面的控制面不能解决数据面的隔离问题,
例如节点级的嘈杂邻居或安全威胁。这些仍然必须单独解决。
Kubernetes Cluster API - Nested (CAPN)
项目提供了虚拟控制平面的实现。
其他实现 3.9.10 - Kubernetes API 服务器旁路风险 与 API 服务器及其他组件相关的安全架构信息
Kubernetes API 服务器是外部(用户和服务)与集群交互的主要入口。
作为此角色的一部分,API 服务器有几个关键的内置安全控制,
例如审计日志和准入控制器 。
但是,有一些方法可以绕过这些安全控制从而修改集群的配置或内容。
本页描述了绕过 Kubernetes API 服务器中内置安全控制的几种方式,
以便集群运维人员和安全架构师可以确保这些绕过方式被适当地限制。
静态 Pod 每个节点上的 kubelet
会加载并直接管理集群中存储在指定目录中或从特定 URL
获取的静态 Pod 清单。
API 服务器不管理这些静态 Pod。对该位置具有写入权限的攻击者可以修改从该位置加载的静态 Pod 的配置,或引入新的静态 Pod。
静态 Pod 被限制访问 Kubernetes API 中的其他对象。例如,你不能将静态 Pod 配置为从集群挂载 Secret。
但是,这些 Pod 可以执行其他安全敏感的操作,例如挂载来自下层节点的 hostPath
卷。
默认情况下,kubelet 会创建一个镜像 Pod(Mirror Pod) ,
以便静态 Pod 在 Kubernetes API 中可见。但是,如果攻击者在创建 Pod 时使用了无效的名字空间名称,
则该 Pod 将在 Kubernetes API 中不可见,只能通过对受影响主机有访问权限的工具发现。
如果静态 Pod 无法通过准入控制,kubelet 不会将 Pod 注册到 API 服务器。但该 Pod 仍然在节点上运行。
有关更多信息,请参阅 kubeadm issue #1541 。
缓解措施 仅在节点需要时启用 kubelet 静态 Pod 清单功能 。 如果节点使用静态 Pod 功能,请将对静态 Pod 清单目录或 URL 的文件系统的访问权限限制为需要访问的用户。 限制对 kubelet 配置参数和文件的访问,以防止攻击者设置静态 Pod 路径或 URL。 定期审计并集中报告所有对托管静态 Pod 清单和 kubelet 配置文件的目录或 Web 存储位置的访问。 kubelet API kubelet 提供了一个 HTTP API,通常暴露在集群工作节点上的 TCP 端口 10250 上。
在某些 Kubernetes 发行版中,API 也可能暴露在控制平面节点上。
对 API 的直接访问允许公开有关运行在节点上的 Pod、这些 Pod 的日志以及在节点上运行的每个容器中执行命令的信息。
当 Kubernetes 集群用户具有对 Node
对象子资源 RBAC 访问权限时,该访问权限可用作与 kubelet API 交互的授权。
实际的访问权限取决于授予了哪些子资源访问权限,详见
kubelet 鉴权 。
对 kubelet API 的直接访问不受准入控制影响,也不会被 Kubernetes 审计日志记录。
能直接访问此 API 的攻击者可能会绕过能检测或防止某些操作的控制机制。
kubelet API 可以配置为以多种方式验证请求。
默认情况下,kubelet 的配置允许匿名访问。大多数 Kubernetes 提供商将默认值更改为使用 Webhook 和证书身份认证。
这使得控制平面能够确保调用者访问 Node
API 资源或子资源是经过授权的。但控制平面不能确保默认的匿名访问也是如此。
缓解措施 etcd API Kubernetes 集群使用 etcd 作为数据存储。etcd
服务监听 TCP 端口 2379。
只有 Kubernetes API 服务器和你所使用的备份工具需要访问此存储。对该 API 的直接访问允许公开或修改集群中保存的数据。
对 etcd API 的访问通常通过客户端证书身份认证来管理。
由 etcd 信任的证书颁发机构所颁发的任何证书都可以完全访问 etcd 中存储的数据。
对 etcd 的直接访问不受 Kubernetes 准入控制的影响,也不会被 Kubernetes 审计日志记录。
具有对 API 服务器的 etcd 客户端证书私钥的读取访问权限(或可以创建一个新的受信任的客户端证书)
的攻击者可以通过访问集群 Secret 或修改访问规则来获得集群管理员权限。
即使不提升其 Kubernetes RBAC 权限,可以修改 etcd 的攻击者也可以在集群内检索所有 API 对象或创建新的工作负载。
许多 Kubernetes 提供商配置 etcd 为使用双向 TLS(客户端和服务器都验证彼此的证书以进行身份验证)。
尽管存在该特性,但目前还没有被广泛接受的 etcd API 鉴权实现。
由于缺少鉴权模型,任何具有对 etcd 的客户端访问权限的证书都可以用于获得对 etcd 的完全访问权限。
通常,仅用于健康检查的 etcd 客户端证书也可以授予完全读写访问权限。
缓解措施 确保 etcd 所信任的证书颁发机构仅用于该服务的身份认证。 控制对 etcd 服务器证书的私钥以及 API 服务器的客户端证书和密钥的访问。 考虑在网络层面限制对 etcd 端口的访问,仅允许来自特定和受信任的 IP 地址段的访问。 容器运行时套接字 在 Kubernetes 集群中的每个节点上,与容器交互的访问都由容器运行时控制。
通常,容器运行时会公开一个 kubelet 可以访问的 UNIX 套接字。
具有此套接字访问权限的攻击者可以启动新容器或与正在运行的容器进行交互。
在集群层面,这种访问造成的影响取决于在受威胁节点上运行的容器是否可以访问 Secret 或其他机密数据,
攻击者可以使用这些机密数据将权限提升到其他工作节点或控制平面组件。
缓解措施 确保严格控制对容器运行时套接字所在的文件系统访问。如果可能,限制为仅 root
用户可访问。 使用 Linux 内核命名空间等机制将 kubelet 与节点上运行的其他组件隔离。 确保限制或禁止使用包含容器运行时套接字的 hostPath
挂载 ,
无论是直接挂载还是通过挂载父目录挂载。此外,hostPath
挂载必须设置为只读,以降低攻击者绕过目录限制的风险。 限制用户对节点的访问,特别是限制超级用户对节点的访问。 3.9.11 - 安全检查清单 确保 Kubernetes 集群安全的基线检查清单。
本清单旨在提供一个基本的指导列表,其中包含链接,指向各个主题的更为全面的文档。
此清单不求详尽无遗,是预计会不断演化的。
关于如何阅读和使用本文档:
主题的顺序并不代表优先级的顺序。 在每章节的列表下面的段落中,都详细列举了一些检查清项目。 注意: 单靠检查清单是不够的 ,无法获得良好的安全态势。
实现良好的安全态势需要持续的关注和改进,实现安全上有备无患的目标道路漫长,清单可作为征程上的第一步。
对于你的特定安全需求,此清单中的某些建议可能过于严格或过于宽松。
由于 Kubernetes 的安全性并不是“一刀切”的,因此针对每一类检查清单项目都应该做价值评估。
认证和鉴权 在启动后,用户和组件都不应以 system:masters
身份向 Kubernetes API 进行身份验证。
同样,应避免将任何 kube-controller-manager 以 system:masters
运行。
事实上,system:masters
应该只用作一个例外机制,而不是管理员用户。
网络安全 许多容器网络接口(Container Network Interface,CNI)插件 提供了限制
Pod 可能与之通信的网络资源的功能。
这种限制通常通过网络策略 来完成,
网络策略提供了一种名称空间作用域的资源来定义规则。
在每个名称空间中,默认的网络策略会阻塞所有的出入站流量,并选择所有 Pod,
采用允许列表的方法很有用,可以确保不遗漏任何工作负载。
并非所有 CNI 插件都在传输过程中提供加密。
如果所选的插件缺少此功能,一种替代方案是可以使用服务网格来提供该功能。
控制平面的 etcd 数据存储应该实施访问限制控制,并且不要在互联网上公开。
此外,应使用双向 TLS(mTLS)与其进行安全通信。
用在这里的证书机构应该仅用于 etcd。
应该限制外部互联网对 Kubernetes API 服务器未公开的 API 的访问。
请小心,因为许多托管的 Kubernetes 发行版在默认情况下公开了 API 服务器。
当然,你可以使用堡垒机访问服务器。
对 kubelet API 的访问应该受到限制,
并且不公开,当没有使用 --config
参数来设置配置文件时,默认的身份验证和鉴权设置是过于宽松的。
如果使用云服务供应商托管的 Kubernetes,在没有明确需要的情况下,
也应该限制或阻止从 Pod 对云元数据 API 169.254.169.254
的访问,因为这可能泄露信息。
关于限制使用 LoadBalancer 和 ExternalIP 请参阅
CVE-2020-8554:中间人使用 LoadBalancer 或 ExternalIP
和
DenyServiceExternalIPs 准入控制器 获取更多信息。
Pod 安全 RBAC 的授权是至关重要的,
但不能在足够细的粒度上对 Pod 的资源进行授权 ,
也不足以对管理 Pod 的任何资源进行授权。
唯一的粒度是资源本身上的 API 动作,例如,对 Pod 的 create
。
在未指定额外许可的情况下,创建这些资源的权限允许直接不受限制地访问集群的可调度节点。
Pod 安全性标准 定义了三种不同的策略:
特权策略(Privileged)、基线策略(Baseline)和限制策略(Restricted),它们限制了 PodSpec
中关于安全的字段的设置。
这些标准可以通过默认启用的新的
Pod 安全性准入 或第三方准入 Webhook 在名称空间级别强制执行。
请注意,与它所取代的、已被删除的 PodSecurityPolicy 准入机制相反,
Pod 安全性准入 可以轻松地与准入 Webhook 和外部服务相结合使用。
restricted
Pod 安全准入策略是 Pod 安全性标准 集中最严格的策略,
可以在多种种模式下运行 ,
根据最佳安全实践,逐步地采用 warn
、audit
或者 enforce
模式以应用最合适的安全上下文(Security Context) 。
尽管如此,对于特定的用例,应该单独审查 Pod 的安全上下文 ,
以限制 Pod 在预定义的安全性标准之上可能具有的特权和访问权限。
有关 Pod 安全性 的实践教程,
请参阅博文 Kubernetes 1.23:Pod 安全性升级到 Beta 。
为了限制一个 Pod 可以使用的内存和 CPU 资源,
应该设置 Pod 在节点上可消费的内存和 CPU 限制 ,
从而防止来自恶意的或已被攻破的工作负载的潜在 DoS 攻击。这种策略可以由准入控制器强制执行。
请注意,CPU 限制设置可能会影响 CPU 用量,从而可能会对自动扩缩功能或效率产生意外的影响,
换言之,系统会在可用的 CPU 资源下最大限度地运行进程。
注意: 内存限制高于请求的,可能会使整个节点面临 OOM 问题。
启用 Seccomp Seccomp 通过减少容器内对 Linux 内核的系统调用(System Call)以缩小攻击面,从而提高工作负载的安全性。
Seccomp 过滤器模式借助 BPF 创建了配置文件(Profile),文件中设置对具体系统调用的允许或拒绝,
可以针对单个工作负载上启用这类 Seccomp 配置文件。你可以阅读相应的安全教程 。
此外,Kubernetes Security Profiles Operator
是一个方便在集群中管理和使用 Seccomp 的项目。
从历史背景看,请注意 Docker 自 2016 年以来一直使用默认的 Seccomp 配置文件 ,
仅允许来自 Docker Engine 1.10 的很小的一组系统调用,
但是,在默认情况下 Kubernetes 仍不限制工作负载。
默认的 Seccomp 配置文件也可以在
containerd 中找到。
幸运的是,Seccomp Default 可将默认的 Seccomp 配置文件用于所有工作负载,
这是一项新的 Alpha 功能,现在可以启用和测试了。
说明: Seccomp 仅适用于 Linux 节点。
启用 AppArmor 或 SELinux AppArmor AppArmor 是一个 Linux 内核安全模块,
可以提供一种简单的方法来实现强制访问控制(Mandatory Access Control, MAC)并通过系统日志进行更好地审计。
要在 Kubernetes 中启用 AppArmor ,至少需要 1.4 版本。
与 Seccomp 一样,AppArmor 也通过配置文件进行配置,
其中每个配置文件要么在强制(Enforcing)模式下运行,即阻止访问不允许的资源,要么在投诉(Complaining)模式下运行,只报告违规行为。
AppArmor 配置文件是通过注解的方式,以容器为粒度强制执行的,允许进程获得刚好合适的权限。
SELinux SELinux
也是一个 Linux 内核安全模块,可以提供支持访问控制安全策略的机制,包括强制访问控制(MAC)。
SELinux 标签可以通过 securityContext
节 指配给容器或 Pod。
Pod 布局 处于不同敏感级别的 Pod,例如,应用程序 Pod 和 Kubernetes API 服务器,应该部署到不同的节点上。
节点隔离的目的是防止应用程序容器的逃逸,进而直接访问敏感度更高的应用,
甚至轻松地改变集群工作机制。
这种隔离应该被强制执行,以防止 Pod 集合被意外部署到同一节点上。
可以通过以下功能实现:
节点选择器(Node Selector) 作为 Pod 规约的一部分来设置的键值对,指定 Pod 可部署到哪些节点。
通过 PodNodeSelector
准入控制器可以在名字空间和集群级别强制实施节点选择。 PodTolerationRestriction 容忍度 准入控制器,
允许管理员设置在名字空间内允许使用的容忍度。
名字空间中的 Pod 只能使用名字空间对象的注解键上所指定的容忍度,这些键提供默认和允许的容忍度集合。RuntimeClass RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用于运行 Pod 中的容器,
并以性能开销为代价提供或多或少的主机隔离能力。 Secrets Pod 所需的秘密信息应该存储在 Kubernetes Secret 中,而不是像 ConfigMap 这样的替代品中。
存储在 etcd 中的 Secret 资源应该被静态加密。
需要 Secret 的 Pod 应该通过卷自动挂载这些信息,
最好使用 emptyDir.medium
选项 存储在内存中。
该机制还可以用于从第三方存储中注入 Secret 作为卷,如 Secret Store CSI 驱动 。
与通过 RBAC 来允许 Pod 服务帐户访问 Secret 相比,应该优先使用上述机制。这种机制允许将 Secret 作为环境变量或文件添加到 Pod 中。
请注意,与带访问权限控制的文件相比,由于日志的崩溃转储,以及 Linux 的环境变量的非机密性,环境变量方法可能更容易发生泄漏。
不应该将服务账号令牌挂载到不需要它们的 Pod 中。
这可以通过在服务帐号内将
automountServiceAccountToken
设置为 false
来完成整个名字空间范围的配置,
或者也可以单独在 Pod 层面定制。
对于 Kubernetes v1.22 及更高版本,
请使用绑定服务账号 作为有时间限制的服务帐号凭证。
镜像 容器镜像应该包含运行其所打包的程序所需要的最少内容。
最好,只使用程序及其依赖项,基于最小的基础镜像来构建镜像。
尤其是,在生产中使用的镜像不应包含 Shell 或调试工具,
因为可以使用临时调试容器 进行故障排除。
构建镜像时使用 Dockerfile 中的 USER
指令直接开始使用非特权用户。
安全上下文(Security Context)
允许使用 runAsUser
和 runAsGroup
来指定使用特定的用户和组来启动容器镜像,
即使没有在镜像清单文件(Manifest)中指定这些配置信息。
不过,镜像层中的文件权限设置可能无法做到在不修改镜像的情况下,使用新的非特权用户来启动进程。
避免使用镜像标签来引用镜像,尤其是 latest
标签,因为标签对应的镜像可以在仓库中被轻松地修改。
首选使用完整的 Sha256
摘要,该摘要对特定镜像清单文件而言是唯一的。
可以通过 ImagePolicyWebhook
强制执行此策略。
镜像签名还可以在部署时由准入控制器自动验证 ,
以验证其真实性和完整性。
扫描容器镜像可以防止关键性的漏洞随着容器镜像一起被部署到集群中。
镜像扫描应在将容器镜像部署到集群之前完成,通常作为 CI/CD 流水线中的部署过程的一部分来完成。
镜像扫描的目的是获取有关容器镜像中可能存在的漏洞及其预防措施的信息,
例如使用公共漏洞评分系统 (Common Vulnerability Scoring System,CVSS) 评分。
如果镜像扫描的结果与管道合性规则匹配,则只有经过正确修补的容器镜像才会最终进入生产环境。
准入控制器 准入控制器可以帮助提高集群的安全性。
然而,由于它们是对 API 服务器的扩展,其自身可能会带来风险,
所以它们应该得到适当的保护 。
下面列出了一些准入控制器,可以考虑用这些控制器来增强集群和应用程序的安全状况。
列表中包括了可能在本文档其他部分曾提及的控制器。
第一组准入控制器包括默认启用的插件 ,
除非你知道自己在做什么,否则请考虑保持它们处于被启用的状态:
CertificateApproval
执行额外的授权检查,以确保审批用户具有审批证书请求的权限。 CertificateSigning
执行其他授权检查,以确保签名用户具有签名证书请求的权限。 CertificateSubjectRestriction
拒绝将 group
(或 organization attribute
)设置为 system:masters
的所有证书请求。 LimitRanger
强制执行 LimitRange API 约束。 MutatingAdmissionWebhook
允许通过 Webhook 使用自定义控制器,这些控制器可能会变更它所审查的请求。 PodSecurity
Pod Security Policy 的替代品,用于约束所部署 Pod 的安全上下文。 ResourceQuota
强制执行资源配额,以防止资源被过度使用。 ValidatingAdmissionWebhook
允许通过 Webhook 使用自定义控制器,这些控制器不变更它所审查的请求。 第二组包括默认情况下没有启用、但处于正式发布状态的插件,建议启用这些插件以改善你的安全状况:
DenyServiceExternalIPs
拒绝使用 Service.spec.externalIPs
字段,已有的 Service 不受影响,新增或者变更时不允许使用。
这是 CVE-2020-8554:中间人使用 LoadBalancer 或 ExternalIP 的缓解措施。 NodeRestriction
将 kubelet 的权限限制为只能修改其拥有的 Pod API 资源或代表其自身的节点 API 资源。
此插件还可以防止 kubelet 使用 node-restriction.kubernetes.io/
注解,
攻击者可以使用该注解来访问 kubelet 的凭证,从而影响所控制的节点上的 Pod 布局。 第三组包括默认情况下未启用,但可以考虑在某些场景下启用的插件:
AlwaysPullImages
强制使用最新版本标记的镜像,并确保部署者有权使用该镜像。 ImagePolicyWebhook
允许通过 Webhook 对镜像强制执行额外的控制。 接下来 3.10 - 策略 可配置的、可应用到一组资源的策略。
说明: 有关 Kubernetes 中的 NetworkPolicy 的文档,
请参阅网络策略 。
3.10.1 - 限制范围 默认情况下, Kubernetes 集群上的容器运行使用的计算资源 没有限制。
使用 Kubernetes 资源配额 ,
管理员(也称为 集群操作者 )可以在一个指定的命名空间 内限制集群资源的使用与创建。
在命名空间中,一个 Pod 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。
作为集群操作者或命名空间级的管理员,你可能也会担心如何确保一个 Pod 不会垄断命名空间内所有可用的资源。
LimitRange 是限制命名空间内可为每个适用的对象类别
(例如 Pod 或 PersistentVolumeClaim )
指定的资源分配量(限制和请求)的策略对象。
一个 LimitRange(限制范围) 对象提供的限制能够做到:
在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。 在一个命名空间中实施对每个 PersistentVolumeClaim
能申请的最小和最大的存储空间大小的限制。 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。 当某命名空间中有一个 LimitRange 对象时,将在该命名空间中实施 LimitRange 限制。
LimitRange 的名称必须是合法的
DNS 子域名 。
资源限制和请求的约束 管理员在一个命名空间内创建一个 LimitRange
对象。 用户在此命名空间内创建(或尝试创建) Pod 和 PersistentVolumeClaim 等对象。 首先,LimitRanger
准入控制器对所有没有设置计算资源需求的所有 Pod(及其容器)设置默认请求值与限制值。 其次,LimitRange
跟踪其使用量以保证没有超出命名空间中存在的任意 LimitRange
所定义的最小、最大资源使用量以及使用量比值。 若尝试创建或更新的对象(Pod 和 PersistentVolumeClaim)违反了 LimitRange
的约束,
向 API 服务器的请求会失败,并返回 HTTP 状态码 403 Forbidden
以及描述哪一项约束被违反的消息。 若你在命名空间中添加 LimitRange
启用了对 cpu
和 memory
等计算相关资源的限制,
你必须指定这些值的请求使用量与限制使用量。否则,系统将会拒绝创建 Pod。 LimitRange
的验证仅在 Pod 准入阶段进行,不对正在运行的 Pod 进行验证。
如果你添加或修改 LimitRange,命名空间中已存在的 Pod 将继续不变。如果命名空间中存在两个或更多 LimitRange
对象,应用哪个默认值是不确定的。 Pod 的 LimitRange 和准入检查 LimitRange
不 检查所应用的默认值的一致性。
这意味着 LimitRange
设置的 limit 的默认值可能小于客户端提交给 API 服务器的规约中为容器指定的 request 值。
如果发生这种情况,最终 Pod 将无法调度。
例如,你使用如下清单定义一个 LimitRange
:
apiVersion : v1
kind : LimitRange
metadata :
name : cpu-resource-constraint
spec :
limits :
- default : # 此处定义默认限制值
cpu : 500m
defaultRequest : # 此处定义默认请求值
cpu : 500m
max : # max 和 min 定义限制范围
cpu : "1"
min :
cpu : 100m
以及一个声明 CPU 资源请求为 700m
但未声明限制值的 Pod:
apiVersion : v1
kind : Pod
metadata :
name : example-conflict-with-limitrange-cpu
spec :
containers :
- name : demo
image : registry.k8s.io/pause:2.0
resources :
requests :
cpu : 700m
那么该 Pod 将不会被调度,失败并出现类似以下的错误:
Pod "example-conflict-with-limitrange-cpu" is invalid: spec.containers[0].resources.requests: Invalid value: "700m": must be less than or equal to cpu limit
如果你同时设置了 request
和 limit
,那么即使使用相同的 LimitRange
,新 Pod 也会被成功调度:
apiVersion : v1
kind : Pod
metadata :
name : example-no-conflict-with-limitrange-cpu
spec :
containers :
- name : demo
image : registry.k8s.io/pause:2.0
resources :
requests :
cpu : 700m
limits :
cpu : 700m
资源约束示例 能够使用限制范围创建的策略示例有:
在一个有两个节点,8 GiB 内存与16个核的集群中,限制一个命名空间的 Pod 申请
100m 单位,最大 500m 单位的 CPU,以及申请 200Mi,最大 600Mi 的内存。 为 spec 中没有 cpu 和内存需求值的 Container 定义默认 CPU 限制值与需求值
150m,内存默认需求值 300Mi。 在命名空间的总限制值小于 Pod 或 Container 的限制值的总和的情况下,可能会产生资源竞争。
在这种情况下,将不会创建 Container 或 Pod。
竞争和对 LimitRange 的改变都不会影响任何已经创建了的资源。
接下来 关于使用限值的例子,可参阅:
有关上下文和历史信息,请参阅 LimitRanger 设计文档 。
3.10.2 - 资源配额 当多个用户或团队共享具有固定节点数目的集群时,人们会担心有人使用超过其基于公平原则所分配到的资源量。
资源配额是帮助管理员解决这一问题的工具。
资源配额,通过 ResourceQuota
对象来定义,对每个命名空间的资源消耗总量提供限制。
它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命名空间中的 Pod 可以使用的计算资源的总上限。
资源配额的工作方式如下:
不同的团队可以在不同的命名空间下工作。这可以通过
RBAC 强制执行。
集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况,
以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN),
并在消息中给出有可能违反的约束。
如果命名空间下的计算资源 (如 cpu
和 memory
)的配额被启用,
则用户必须为这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。
提示: 可使用 LimitRanger
准入控制器来为没有设置计算资源需求的 Pod 设置默认值。
若想避免这类问题,请参考
演练 示例。
ResourceQuota 对象的名称必须是合法的
DNS 子域名 。
下面是使用命名空间和配额构建策略的示例:
在具有 32 GiB 内存和 16 核 CPU 资源的集群中,允许 A 团队使用 20 GiB 内存 和 10 核的 CPU 资源,
允许 B 团队使用 10 GiB 内存和 4 核的 CPU 资源,并且预留 2 GiB 内存和 2 核的 CPU 资源供将来分配。 限制 "testing" 命名空间使用 1 核 CPU 资源和 1GiB 内存。允许 "production" 命名空间使用任意数量。 在集群容量小于各命名空间配额总和的情况下,可能存在资源竞争。资源竞争时,Kubernetes 系统会遵循先到先得的原则。
不管是资源竞争还是配额的修改,都不会影响已经创建的资源使用对象。
启用资源配额 资源配额的支持在很多 Kubernetes 版本中是默认启用的。
当 API 服务器
的命令行标志 --enable-admission-plugins=
中包含 ResourceQuota
时,
资源配额会被启用。
当命名空间中存在一个 ResourceQuota 对象时,对于该命名空间而言,资源配额就是开启的。
计算资源配额 用户可以对给定命名空间下的可被请求的
计算资源
总量进行限制。
配额机制所支持的资源类型:
资源名称 描述 limits.cpu
所有非终止状态的 Pod,其 CPU 限额总量不能超过该值。 limits.memory
所有非终止状态的 Pod,其内存限额总量不能超过该值。 requests.cpu
所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。 requests.memory
所有非终止状态的 Pod,其内存需求总量不能超过该值。 hugepages-<size>
对于所有非终止状态的 Pod,针对指定尺寸的巨页请求总数不能超过此值。 cpu
与 requests.cpu
相同。 memory
与 requests.memory
相同。
扩展资源的资源配额 除上述资源外,在 Kubernetes 1.10 版本中,还添加了对
扩展资源
的支持。
由于扩展资源不可超量分配,因此没有必要在配额中为同一扩展资源同时指定 requests
和 limits
。
对于扩展资源而言,目前仅允许使用前缀为 requests.
的配额项。
以 GPU 拓展资源为例,如果资源名称为 nvidia.com/gpu
,并且要将命名空间中请求的 GPU
资源总数限制为 4,则可以如下定义配额:
requests.nvidia.com/gpu: 4
有关更多详细信息,请参阅查看和设置配额 。
存储资源配额 用户可以对给定命名空间下的存储资源
总量进行限制。
此外,还可以根据相关的存储类(Storage Class)来限制存储资源的消耗。
资源名称 描述 requests.storage
所有 PVC,存储资源的需求总量不能超过该值。 persistentvolumeclaims
在该命名空间中所允许的 PVC 总量。 <storage-class-name>.storageclass.storage.k8s.io/requests.storage
在所有与 <storage-class-name>
相关的持久卷申领中,存储请求的总和不能超过该值。 <storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims
在与 storage-class-name 相关的所有持久卷申领中,命名空间中可以存在的持久卷申领 总数。
例如,如果一个操作人员针对 gold
存储类型与 bronze
存储类型设置配额,
操作人员可以定义如下配额:
gold.storageclass.storage.k8s.io/requests.storage: 500Gi
bronze.storageclass.storage.k8s.io/requests.storage: 100Gi
在 Kubernetes 1.8 版本中,本地临时存储的配额支持已经是 Alpha 功能:
资源名称 描述 requests.ephemeral-storage
在命名空间的所有 Pod 中,本地临时存储请求的总和不能超过此值。 limits.ephemeral-storage
在命名空间的所有 Pod 中,本地临时存储限制值的总和不能超过此值。 ephemeral-storage
与 requests.ephemeral-storage
相同。
说明: 如果所使用的是 CRI 容器运行时,容器日志会被计入临时存储配额。
这可能会导致存储配额耗尽的 Pods 被意外地驱逐出节点。
参考日志架构
了解详细信息。
对象数量配额 你可以使用以下语法对所有标准的、命名空间域的资源类型进行配额设置:
count/<resource>.<group>
:用于非核心(core)组的资源count/<resource>
:用于核心组的资源这是用户可能希望利用对象计数配额来管理的一组资源示例。
count/persistentvolumeclaims
count/services
count/secrets
count/configmaps
count/replicationcontrollers
count/deployments.apps
count/replicasets.apps
count/statefulsets.apps
count/jobs.batch
count/cronjobs.batch
相同语法也可用于自定义资源。
例如,要对 example.com
API 组中的自定义资源 widgets
设置配额,请使用
count/widgets.example.com
。
当使用 count/*
资源配额时,如果对象存在于服务器存储中,则会根据配额管理资源。
这些类型的配额有助于防止存储资源耗尽。例如,用户可能想根据服务器的存储能力来对服务器中
Secret 的数量进行配额限制。
集群中存在过多的 Secret 实际上会导致服务器和控制器无法启动。
用户可以选择对 Job 进行配额管理,以防止配置不当的 CronJob 在某命名空间中创建太多
Job 而导致集群拒绝服务。
对有限的一组资源上实施一般性的对象数量配额也是可能的。
支持以下类型:
资源名称 描述 configmaps
在该命名空间中允许存在的 ConfigMap 总数上限。 persistentvolumeclaims
在该命名空间中允许存在的 PVC 的总数上限。 pods
在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded)
为真。 replicationcontrollers
在该命名空间中允许存在的 ReplicationController 总数上限。 resourcequotas
在该命名空间中允许存在的 ResourceQuota 总数上限。 services
在该命名空间中允许存在的 Service 总数上限。 services.loadbalancers
在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。 services.nodeports
在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。 secrets
在该命名空间中允许存在的 Secret 总数上限。
例如,pods
配额统计某个命名空间中所创建的、非终止状态的 Pod
个数并确保其不超过某上限值。
用户可能希望在某命名空间中设置 pods
配额,以避免有用户创建很多小的 Pod,
从而耗尽集群所能提供的 Pod IP 地址。
配额作用域 每个配额都有一组相关的 scope
(作用域),配额只会对作用域内的资源生效。
配额机制仅统计所列举的作用域的交集中的资源用量。
当一个作用域被添加到配额中后,它会对作用域相关的资源数量作限制。
如配额中指定了允许(作用域)集合之外的资源,会导致验证错误。
作用域 描述 Terminating
匹配所有 spec.activeDeadlineSeconds
不小于 0 的 Pod。 NotTerminating
匹配所有 spec.activeDeadlineSeconds
是 nil 的 Pod。 BestEffort
匹配所有 Qos 是 BestEffort 的 Pod。 NotBestEffort
匹配所有 Qos 不是 BestEffort 的 Pod。 PriorityClass
匹配所有引用了所指定的优先级类 的 Pods。 CrossNamespacePodAffinity
匹配那些设置了跨名字空间 (反)亲和性条件 的 Pod。
BestEffort
作用域限制配额跟踪以下资源:
Terminating
、NotTerminating
、NotBestEffort
和 PriorityClass
这些作用域限制配额跟踪以下资源:
pods
cpu
memory
requests.cpu
requests.memory
limits.cpu
limits.memory
需要注意的是,你不可以在同一个配额对象中同时设置 Terminating
和 NotTerminating
作用域,你也不可以在同一个配额中同时设置 BestEffort
和 NotBestEffort
作用域。
scopeSelector
支持在 operator
字段中使用以下值:
In
NotIn
Exists
DoesNotExist
定义 scopeSelector
时,如果使用以下值之一作为 scopeName
的值,则对应的
operator
只能是 Exists
。
Terminating
NotTerminating
BestEffort
NotBestEffort
如果 operator
是 In
或 NotIn
之一,则 values
字段必须至少包含一个值。
例如:
scopeSelector :
matchExpressions :
- scopeName : PriorityClass
operator : In
values :
- middle
如果 operator
为 Exists
或 DoesNotExist
,则不 可以设置 values
字段。
基于优先级类(PriorityClass)来设置资源配额 特性状态: Kubernetes v1.17 [stable]
Pod 可以创建为特定的优先级 。
通过使用配额规约中的 scopeSelector
字段,用户可以根据 Pod 的优先级控制其系统资源消耗。
仅当配额规范中的 scopeSelector
字段选择到某 Pod 时,配额机制才会匹配和计量 Pod 的资源消耗。
如果配额对象通过 scopeSelector
字段设置其作用域为优先级类,
则配额对象只能跟踪以下资源:
pods
cpu
memory
ephemeral-storage
limits.cpu
limits.memory
limits.ephemeral-storage
requests.cpu
requests.memory
requests.ephemeral-storage
本示例创建一个配额对象,并将其与具有特定优先级的 Pod 进行匹配。
该示例的工作方式如下:
集群中的 Pod 可取三个优先级类之一,即 "low"、"medium"、"high"。 为每个优先级创建一个配额对象。 将以下 YAML 保存到文件 quota.yml
中。
apiVersion : v1
kind : List
items :
- apiVersion : v1
kind : ResourceQuota
metadata :
name : pods-high
spec :
hard :
cpu : "1000"
memory : 200Gi
pods : "10"
scopeSelector :
matchExpressions :
- operator : In
scopeName : PriorityClass
values : ["high" ]
- apiVersion : v1
kind : ResourceQuota
metadata :
name : pods-medium
spec :
hard :
cpu : "10"
memory : 20Gi
pods : "10"
scopeSelector :
matchExpressions :
- operator : In
scopeName : PriorityClass
values : ["medium" ]
- apiVersion : v1
kind : ResourceQuota
metadata :
name : pods-low
spec :
hard :
cpu : "5"
memory : 10Gi
pods : "10"
scopeSelector :
matchExpressions :
- operator : In
scopeName : PriorityClass
values : ["low" ]
使用 kubectl create
命令运行以下操作。
kubectl create -f ./quota.yml
resourcequota/pods-high created
resourcequota/pods-medium created
resourcequota/pods-low created
使用 kubectl describe quota
操作验证配额的 Used
值为 0
。
Name: pods-high
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 1k
memory 0 200Gi
pods 0 10
Name: pods-low
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 5
memory 0 10Gi
pods 0 10
Name: pods-medium
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 10
memory 0 20Gi
pods 0 10
创建优先级为 "high" 的 Pod。
将以下 YAML 保存到文件 high-priority-pod.yml
中。
apiVersion : v1
kind : Pod
metadata :
name : high-priority
spec :
containers :
- name : high-priority
image : ubuntu
command : ["/bin/sh" ]
args : ["-c" , "while true; do echo hello; sleep 10;done" ]
resources :
requests :
memory : "10Gi"
cpu : "500m"
limits :
memory : "10Gi"
cpu : "500m"
priorityClassName : high
使用 kubectl create
运行以下操作。
kubectl create -f ./high-priority-pod.yml
确认 "high" 优先级配额 pods-high
的 "Used" 统计信息已更改,并且其他两个配额未更改。
Name: pods-high
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 500m 1k
memory 10Gi 200Gi
pods 1 10
Name: pods-low
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 5
memory 0 10Gi
pods 0 10
Name: pods-medium
Namespace: default
Resource Used Hard
-------- ---- ----
cpu 0 10
memory 0 20Gi
pods 0 10
跨名字空间的 Pod 亲和性配额 特性状态: Kubernetes v1.24 [stable]
集群运维人员可以使用 CrossNamespacePodAffinity
配额作用域来限制哪个名字空间中可以存在包含跨名字空间亲和性规则的 Pod。
更为具体一点,此作用域用来配置哪些 Pod 可以在其 Pod 亲和性规则中设置
namespaces
或 namespaceSelector
字段。
禁止用户使用跨名字空间的亲和性规则可能是一种被需要的能力,
因为带有反亲和性约束的 Pod 可能会阻止所有其他名字空间的 Pod 被调度到某失效域中。
使用此作用域操作符可以避免某些名字空间(例如下面例子中的 foo-ns
)运行特别的 Pod,
这类 Pod 使用跨名字空间的 Pod 亲和性约束,在该名字空间中创建了作用域为
CrossNamespaceAffinity
的、硬性约束为 0 的资源配额对象。
apiVersion : v1
kind : ResourceQuota
metadata :
name : disable-cross-namespace-affinity
namespace : foo-ns
spec :
hard :
pods : "0"
scopeSelector :
matchExpressions :
- scopeName : CrossNamespaceAffinity
如果集群运维人员希望默认禁止使用 namespaces
和 namespaceSelector
,
而仅仅允许在特定名字空间中这样做,他们可以将 CrossNamespaceAffinity
作为一个被约束的资源。方法是为 kube-apiserver
设置标志
--admission-control-config-file
,使之指向如下的配置文件:
apiVersion : apiserver.config.k8s.io/v1
kind : AdmissionConfiguration
plugins :
- name : "ResourceQuota"
configuration :
apiVersion : apiserver.config.k8s.io/v1
kind : ResourceQuotaConfiguration
limitedResources :
- resource : pods
matchScopes :
- scopeName : CrossNamespaceAffinity
基于上面的配置,只有名字空间中包含作用域为 CrossNamespaceAffinity
且硬性约束大于或等于使用 namespaces
和 namespaceSelector
字段的 Pod
个数时,才可以在该名字空间中继续创建在其 Pod 亲和性规则中设置 namespaces
或 namespaceSelector
的新 Pod。
请求与限制的比较 分配计算资源时,每个容器可以为 CPU 或内存指定请求和约束。
配额可以针对二者之一进行设置。
如果配额中指定了 requests.cpu
或 requests.memory
的值,则它要求每个容器都显式给出对这些资源的请求。
同理,如果配额中指定了 limits.cpu
或 limits.memory
的值,那么它要求每个容器都显式设定对应资源的限制。
查看和设置配额 Kubectl 支持创建、更新和查看配额:
kubectl create namespace myspace
cat <<EOF > compute-resources.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-resources
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
requests.nvidia.com/gpu: 4
EOF
kubectl create -f ./compute-resources.yaml --namespace= myspace
cat <<EOF > object-counts.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: object-counts
spec:
hard:
configmaps: "10"
persistentvolumeclaims: "4"
pods: "4"
replicationcontrollers: "20"
secrets: "10"
services: "10"
services.loadbalancers: "2"
EOF
kubectl create -f ./object-counts.yaml --namespace= myspace
kubectl get quota --namespace= myspace
NAME AGE
compute-resources 30s
object-counts 32s
kubectl describe quota compute-resources --namespace= myspace
Name: compute-resources
Namespace: myspace
Resource Used Hard
-------- ---- ----
limits.cpu 0 2
limits.memory 0 2Gi
requests.cpu 0 1
requests.memory 0 1Gi
requests.nvidia.com/gpu 0 4
kubectl describe quota object-counts --namespace= myspace
Name: object-counts
Namespace: myspace
Resource Used Hard
-------- ---- ----
configmaps 0 10
persistentvolumeclaims 0 4
pods 0 4
replicationcontrollers 0 20
secrets 1 10
services 0 10
services.loadbalancers 0 2
kubectl 还使用语法 count/<resource>.<group>
支持所有标准的、命名空间域的资源的对象计数配额:
kubectl create namespace myspace
kubectl create quota test --hard= count/deployments.apps= 2,count/replicasets.apps= 4,count/pods= 3,count/secrets= 4 --namespace= myspace
kubectl create deployment nginx --image= nginx --namespace= myspace --replicas= 2
kubectl describe quota --namespace= myspace
Name: test
Namespace: myspace
Resource Used Hard
-------- ---- ----
count/deployments.apps 1 2
count/pods 2 3
count/replicasets.apps 1 4
count/secrets 1 4
配额和集群容量 ResourceQuota 与集群资源总量是完全独立的。它们通过绝对的单位来配置。
所以,为集群添加节点时,资源配额不会 自动赋予每个命名空间消耗更多资源的能力。
有时可能需要资源配额支持更复杂的策略,比如:
在几个团队中按比例划分总的集群资源。 允许每个租户根据需要增加资源使用量,但要有足够的限制以防止资源意外耗尽。 探测某个命名空间的需求,添加物理节点并扩大资源配额值。 这些策略可以通过将资源配额作为一个组成模块、手动编写一个控制器来监控资源使用情况,
并结合其他信号调整命名空间上的硬性资源配额来实现。
注意:资源配额对集群资源总体进行划分,但它对节点没有限制:来自不同命名空间的 Pod 可能在同一节点上运行。
默认情况下限制特定优先级的资源消耗 有时候可能希望当且仅当某名字空间中存在匹配的配额对象时,才可以创建特定优先级
(例如 "cluster-services")的 Pod。
通过这种机制,操作人员能够限制某些高优先级类仅出现在有限数量的命名空间中,
而并非每个命名空间默认情况下都能够使用这些优先级类。
要实现此目的,应设置 kube-apiserver
的标志 --admission-control-config-file
指向如下配置文件:
apiVersion : apiserver.config.k8s.io/v1
kind : AdmissionConfiguration
plugins :
- name : "ResourceQuota"
configuration :
apiVersion : apiserver.config.k8s.io/v1
kind : ResourceQuotaConfiguration
limitedResources :
- resource : pods
matchScopes :
- scopeName : PriorityClass
operator : In
values : ["cluster-services" ]
现在在 kube-system
名字空间中创建一个资源配额对象:
apiVersion : v1
kind : ResourceQuota
metadata :
name : pods-cluster-services
spec :
scopeSelector :
matchExpressions :
- operator : In
scopeName : PriorityClass
values : ["cluster-services" ]
kubectl apply -f https://k8s.io/examples/policy/priority-class-resourcequota.yaml -n kube-system
resourcequota/pods-cluster-services created
在这里,当以下条件满足时可以创建 Pod:
Pod 未设置 priorityClassName
Pod 的 priorityClassName
设置值不是 cluster-services
Pod 的 priorityClassName
设置值为 cluster-services
,它将被创建于
kube-system
名字空间中,并且它已经通过了资源配额检查。 如果 Pod 的 priorityClassName
设置为 cluster-services
,但要被创建到
kube-system
之外的别的名字空间,则 Pod 创建请求也被拒绝。
接下来 3.10.3 - 进程 ID 约束与预留 特性状态: Kubernetes v1.20 [stable]
Kubernetes 允许你限制一个 Pod 中可以使用的
进程 ID(PID)数目。你也可以为每个 节点
预留一定数量的可分配的 PID,供操作系统和守护进程(而非 Pod)使用。
进程 ID(PID)是节点上的一种基础资源。很容易就会在尚未超出其它资源约束的时候就
已经触及任务个数上限,进而导致宿主机器不稳定。
集群管理员需要一定的机制来确保集群中运行的 Pod 不会导致 PID 资源枯竭,甚而
造成宿主机上的守护进程(例如
kubelet 或者
kube-proxy
乃至包括容器运行时本身)无法正常运行。
此外,确保 Pod 中 PID 的个数受限对于保证其不会影响到同一节点上其它负载也很重要。
说明: 在某些 Linux 安装环境中,操作系统会将 PID 约束设置为一个较低的默认值,例如
32768
。这时可以考虑提升 /proc/sys/kernel/pid_max
的设置值。
你可以配置 kubelet 限制给定 Pod 能够使用的 PID 个数。
例如,如果你的节点上的宿主操作系统被设置为最多可使用 262144
个 PID,同时预期
节点上会运行的 Pod 个数不会超过 250
,那么你可以为每个 Pod 设置 1000
个 PID
的预算,避免耗尽该节点上可用 PID 的总量。
如果管理员系统像 CPU 或内存那样允许对 PID 进行过量分配(Overcommit),他们也可以
这样做,只是会有一些额外的风险。不管怎样,任何一个 Pod 都不可以将整个机器的运行
状态破坏。这类资源限制有助于避免简单的派生炸弹(Fork
Bomb)影响到整个集群的运行。
在 Pod 级别设置 PID 限制使得管理员能够保护 Pod 之间不会互相伤害,不过无法
确保所有调度到该宿主机器上的所有 Pod 都不会影响到节点整体。
Pod 级别的限制也无法保护节点代理任务自身不会受到 PID 耗尽的影响。
你也可以预留一定量的 PID,作为节点的额外开销,与分配给 Pod 的 PID 集合独立。
这有点类似于在给操作系统和其它设施预留 CPU、内存或其它资源时所做的操作,
这些任务都在 Pod 及其所包含的容器之外运行。
PID 限制是与计算资源
请求和限制相辅相成的一种机制。不过,你需要用一种不同的方式来设置这一限制:
你需要将其设置到 kubelet 上而不是在 Pod 的 .spec
中为 Pod 设置资源限制。
目前还不支持在 Pod 级别设置 PID 限制。
注意: 这意味着,施加在 Pod 之上的限制值可能因为 Pod 运行所在的节点不同而有差别。
为了简化系统,最简单的方法是为所有节点设置相同的 PID 资源限制和预留值。
节点级别 PID 限制 Kubernetes 允许你为系统预留一定量的进程 ID。为了配置预留数量,你可以使用
kubelet 的 --system-reserved
和 --kube-reserved
命令行选项中的参数
pid=<number>
。你所设置的参数值分别用来声明为整个系统和 Kubernetes 系统
守护进程所保留的进程 ID 数目。
说明: 在 Kubernetes 1.20 版本之前,在节点级别通过 PID 资源限制预留 PID 的能力
需要启用特性门控
SupportNodePidsLimit
才行。
Pod 级别 PID 限制 Kubernetes 允许你限制 Pod 中运行的进程个数。你可以在节点级别设置这一限制,
而不是为特定的 Pod 来将其设置为资源限制。
每个节点都可以有不同的 PID 限制设置。
要设置限制值,你可以设置 kubelet 的命令行参数 --pod-max-pids
,或者
在 kubelet 的配置文件
中设置 PodPidsLimit
。
说明: 在 Kubernetes 1.20 版本之前,为 Pod 设置 PID 资源限制的能力需要启用
特性门控
SupportNodePidsLimit
才行。
基于 PID 的驱逐 你可以配置 kubelet 使之在 Pod 行为不正常或者消耗不正常数量资源的时候将其终止。
这一特性称作驱逐。你可以针对不同的驱逐信号
配置资源不足的处理 。
使用 pid.available
驱逐信号来配置 Pod 使用的 PID 个数的阈值。
你可以设置硬性的和软性的驱逐策略。不过,即使使用硬性的驱逐策略,
如果 PID 个数增长过快,节点仍然可能因为触及节点 PID 限制而进入一种不稳定状态。
驱逐信号的取值是周期性计算的,而不是一直能够强制实施约束。
Pod 级别和节点级别的 PID 限制会设置硬性限制。
一旦触及限制值,工作负载会在尝试获得新的 PID 时开始遇到问题。
这可能会也可能不会导致 Pod 被重新调度,取决于工作负载如何应对这类失败
以及 Pod 的存活性和就绪态探测是如何配置的。
可是,如果限制值被正确设置,你可以确保其它 Pod 负载和系统进程不会因为某个
Pod 行为不正常而没有 PID 可用。
接下来 3.10.4 - 节点资源管理器 Kubernetes 提供了一组资源管理器,用于支持延迟敏感的、高吞吐量的工作负载。
资源管理器的目标是协调和优化节点资源,以支持对 CPU、设备和内存(巨页)等资源有特殊需求的 Pod。
主管理器,也叫拓扑管理器(Topology Manager),是一个 Kubelet 组件,
它通过策略 ,
协调全局的资源管理过程。
各个管理器的配置方式会在专项文档中详细阐述:
3.11 - 调度、抢占和驱逐 在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod 匹配到合适的节点, 以便 kubelet 能够运行它们。抢占 (Preemption) 指的是终止低优先级的 Pod 以便高优先级的 Pod 可以调度运行的过程。驱逐 (Eviction) 是在资源匮乏的节点上,主动让一个或多个 Pod 失效的过程。
在 Kubernetes 中,调度 (scheduling) 指的是确保 Pod
匹配到合适的节点 ,
以便 kubelet 能够运行它们。
抢占 (Preemption) 指的是终止低优先级 的 Pod
以便高优先级的 Pod 可以调度运行的过程。
驱逐 (Eviction) 是在资源匮乏的节点上,主动让一个或多个 Pod 失效的过程。
调度 Pod 干扰 Pod 干扰 是指节点上的
Pod 被自愿或非自愿终止的过程。
自愿干扰是由应用程序所有者或集群管理员有意启动的。非自愿干扰是无意的,
可能由不可避免的问题触发,如节点耗尽资源或意外删除。
3.11.1 - Kubernetes 调度器 在 Kubernetes 中,调度 是指将 Pod
放置到合适的节点 上,以便对应节点上的
Kubelet 能够运行这些 Pod。
调度概览 调度器通过 Kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到节点上的 Pod。
调度器会将所发现的每一个未调度的 Pod 调度到一个合适的节点上来运行。
调度器会依据下文的调度原则来做出调度选择。
如果你想要理解 Pod 为什么会被调度到特定的节点上,
或者你想要尝试实现一个自定义的调度器,这篇文章将帮助你了解调度。
kube-scheduler kube-scheduler
是 Kubernetes 集群的默认调度器,并且是集群
控制面 的一部分。
如果你真得希望或者有这方面的需求,kube-scheduler
在设计上允许你自己编写一个调度组件并替换原有的 kube-scheduler。
对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的节点去运行这个 Pod。
然而,Pod 内的每一个容器对资源都有不同的需求,
而且 Pod 本身也有不同的需求。因此,Pod 在被调度到节点上之前,
根据这些特定的调度需求,需要对集群中的节点进行一次过滤。
在一个集群中,满足一个 Pod 调度请求的所有节点称之为 可调度节点 。
如果没有任何一个节点能满足 Pod 的资源请求,
那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的 Node。
调度器先在集群中找到一个 Pod 的所有可调度节点,然后根据一系列函数对这些可调度节点打分,
选出其中得分最高的节点来运行 Pod。之后,调度器将这个调度决定通知给
kube-apiserver,这个过程叫做 绑定 。
在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限制、
亲和以及反亲和要求、数据局部性、负载间的干扰等等。
kube-scheduler 调度流程 kube-scheduler 给一个 Pod 做调度选择时包含两个步骤:
过滤 打分 过滤阶段会将所有满足 Pod 调度需求的节点选出来。
例如,PodFitsResources 过滤函数会检查候选节点的可用资源能否满足 Pod 的资源请求。
在过滤之后,得出一个节点列表,里面包含了所有可调度节点;通常情况下,
这个节点列表包含不止一个节点。如果这个列表是空的,代表这个 Pod 不可调度。
在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的节点。
根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。
最后,kube-scheduler 会将 Pod 调度到得分最高的节点上。
如果存在多个得分最高的节点,kube-scheduler 会从中随机选取一个。
支持以下两种方式配置调度器的过滤和打分行为:
调度策略
允许你配置过滤所用的 断言(Predicates) 和打分所用的 优先级(Priorities) 。调度配置 允许你配置实现不同调度阶段的插件,
包括:QueueSort
、Filter
、Score
、Bind
、Reserve
、Permit
等等。
你也可以配置 kube-scheduler 运行不同的配置文件。接下来 3.11.2 - 将 Pod 指派给节点 你可以约束一个 Pod
以便 限制 其只能在特定的节点 上运行,
或优先在特定的节点上运行。
有几种方法可以实现这点,推荐的方法都是用
标签选择算符 来进行选择。
通常这样的约束不是必须的,因为调度器将自动进行合理的放置(比如,将 Pod 分散到节点上,
而不是将 Pod 放置在可用资源不足的节点上等等)。但在某些情况下,你可能需要进一步控制
Pod 被部署到哪个节点。例如,确保 Pod 最终落在连接了 SSD 的机器上,
或者将来自两个不同的服务且有大量通信的 Pods 被放置在同一个可用区。
你可以使用下列方法中的任何一种来选择 Kubernetes 对特定 Pod 的调度:
节点标签 与很多其他 Kubernetes 对象类似,节点也有标签 。
你可以手动地添加标签 。
Kubernetes 也会为集群中所有节点添加一些标准的标签。
参见常用的标签、注解和污点 以了解常见的节点标签。
说明: 这些标签的取值是取决于云提供商的,并且是无法在可靠性上给出承诺的。
例如,kubernetes.io/hostname
的取值在某些环境中可能与节点名称相同,
而在其他环境中会取不同的值。
节点隔离/限制 通过为节点添加标签,你可以准备让 Pod 调度到特定节点或节点组上。
你可以使用这个功能来确保特定的 Pod 只能运行在具有一定隔离性,安全性或监管属性的节点上。
如果使用标签来实现节点隔离,建议选择节点上的
kubelet
无法修改的标签键。
这可以防止受感染的节点在自身上设置这些标签,进而影响调度器将工作负载调度到受感染的节点。
NodeRestriction
准入插件 防止
kubelet 使用 node-restriction.kubernetes.io/
前缀设置或修改标签。
要使用该标签前缀进行节点隔离:
确保你在使用节点鉴权 机制并且已经启用了
NodeRestriction 准入插件 。 将带有 node-restriction.kubernetes.io/
前缀的标签添加到 Node 对象,
然后在节点选择器 中使用这些标签。
例如,example.com.node-restriction.kubernetes.io/fips=true
或
example.com.node-restriction.kubernetes.io/pci-dss=true
。 nodeSelector nodeSelector
是节点选择约束的最简单推荐形式。你可以将 nodeSelector
字段添加到
Pod 的规约中设置你希望目标节点所具有的节点标签 。
Kubernetes 只会将 Pod 调度到拥有你所指定的每个标签的节点上。
进一步的信息可参见将 Pod 指派给节点 。
亲和性与反亲和性 nodeSelector
提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。
亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:
亲和性、反亲和性语言的表达能力更强。nodeSelector
只能选择拥有所有指定标签的节点。
亲和性、反亲和性为你提供对选择逻辑的更强控制能力。 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod。 你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束,
而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。 亲和性功能由两种类型的亲和性组成:
节点亲和性 功能类似于 nodeSelector
字段,但它的表达能力更强,并且允许你指定软规则。Pod 间亲和性/反亲和性允许你根据其他 Pod 的标签来约束 Pod。 节点亲和性 节点亲和性概念上类似于 nodeSelector
,
它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。
节点亲和性有两种:
requiredDuringSchedulingIgnoredDuringExecution
:
调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelector
,
但其语法表达能力更强。preferredDuringSchedulingIgnoredDuringExecution
:
调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。说明: 在上述类型中,IgnoredDuringExecution
意味着如果节点标签在 Kubernetes
调度 Pod 后发生了变更,Pod 仍将继续运行。
你可以使用 Pod 规约中的 .spec.affinity.nodeAffinity
字段来设置节点亲和性。
例如,考虑下面的 Pod 规约:
apiVersion : v1
kind : Pod
metadata :
name : with-node-affinity
spec :
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : topology.kubernetes.io/zone
operator : In
values :
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 1
preference :
matchExpressions :
- key : another-node-label-key
operator : In
values :
- another-node-label-value
containers :
- name : with-node-affinity
image : registry.k8s.io/pause:2.0
在这一示例中,所应用的规则如下:
节点必须 包含一个键名为 topology.kubernetes.io/zone
的标签,
并且该标签的取值必须 为 antarctica-east1
或 antarctica-west1
。 节点最好 具有一个键名为 another-node-label-key
且取值为
another-node-label-value
的标签。 你可以使用 operator
字段来为 Kubernetes 设置在解释规则时要使用的逻辑操作符。
你可以使用 In
、NotIn
、Exists
、DoesNotExist
、Gt
和 Lt
之一作为操作符。
NotIn
和 DoesNotExist
可用来实现节点反亲和性行为。
你也可以使用节点污点
将 Pod 从特定节点上驱逐。
说明: 如果你同时指定了 nodeSelector
和 nodeAffinity
,两者 必须都要满足,
才能将 Pod 调度到候选节点上。
如果你指定了多个与 nodeAffinity
类型关联的 nodeSelectorTerms
,
只要其中一个 nodeSelectorTerms
满足的话,Pod 就可以被调度到节点上。
如果你指定了多个与同一 nodeSelectorTerms
关联的 matchExpressions
,
则只有当所有 matchExpressions
都满足时 Pod 才可以被调度到节点上。
参阅使用节点亲和性来为 Pod 指派节点 ,
以了解进一步的信息。
节点亲和性权重 你可以为 preferredDuringSchedulingIgnoredDuringExecution
亲和性类型的每个实例设置
weight
字段,其取值范围是 1 到 100。
当调度器找到能够满足 Pod 的其他调度请求的节点时,调度器会遍历节点满足的所有的偏好性规则,
并将对应表达式的 weight
值加和。
最终的加和值会添加到该节点的其他优先级函数的评分之上。
在调度器为 Pod 作出调度决定时,总分最高的节点的优先级也最高。
例如,考虑下面的 Pod 规约:
apiVersion : v1
kind : Pod
metadata :
name : with-affinity-anti-affinity
spec :
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : kubernetes.io/os
operator : In
values :
- linux
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 1
preference :
matchExpressions :
- key : label-1
operator : In
values :
- key-1
- weight : 50
preference :
matchExpressions :
- key : label-2
operator : In
values :
- key-2
containers :
- name : with-node-affinity
image : registry.k8s.io/pause:2.0
如果存在两个候选节点,都满足 preferredDuringSchedulingIgnoredDuringExecution
规则,
其中一个节点具有标签 label-1:key-1
,另一个节点具有标签 label-2:key-2
,
调度器会考察各个节点的 weight
取值,并将该权重值添加到节点的其他得分值之上,
说明: 如果你希望 Kubernetes 能够成功地调度此例中的 Pod,你必须拥有打了
kubernetes.io/os=linux
标签的节点。
逐个调度方案中设置节点亲和性 特性状态: Kubernetes v1.20 [beta]
在配置多个调度方案 时,
你可以将某个方案与节点亲和性关联起来,如果某个调度方案仅适用于某组特殊的节点时,
这样做是很有用的。
要实现这点,可以在调度器配置 中为
NodeAffinity
插件 的
args
字段添加 addedAffinity
。例如:
apiVersion : kubescheduler.config.k8s.io/v1beta3
kind : KubeSchedulerConfiguration
profiles :
- schedulerName : default-scheduler
- schedulerName : foo-scheduler
pluginConfig :
- name : NodeAffinity
args :
addedAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : scheduler-profile
operator : In
values :
- foo
这里的 addedAffinity
除遵从 Pod 规约中设置的节点亲和性之外,
还适用于将 .spec.schedulerName
设置为 foo-scheduler
。
换言之,为了匹配 Pod,节点需要满足 addedAffinity
和 Pod 的 .spec.NodeAffinity
。
由于 addedAffinity
对最终用户不可见,其行为可能对用户而言是出乎意料的。
应该使用与调度方案名称有明确关联的节点标签。
说明: DaemonSet 控制器为 DaemonSet 创建 Pods ,
但该控制器不理会调度方案。
DaemonSet 控制器创建 Pod 时,默认的 Kubernetes 调度器负责放置 Pod,
并遵从 DaemonSet 控制器中奢侈的 nodeAffinity
规则。
Pod 间亲和性与反亲和性 Pod 间亲和性与反亲和性使你可以基于已经在节点上运行的 Pod 的标签来约束
Pod 可以调度到的节点,而不是基于节点上的标签。
Pod 间亲和性与反亲和性的规则格式为“如果 X 上已经运行了一个或多个满足规则 Y 的 Pod,
则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 X 上”。
这里的 X 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域,
Y 则是 Kubernetes 尝试满足的规则。
你通过标签选择算符
的形式来表达规则(Y),并可根据需要指定选关联的名字空间列表。
Pod 在 Kubernetes 中是名字空间作用域的对象,因此 Pod 的标签也隐式地具有名字空间属性。
针对 Pod 标签的所有标签选择算符都要指定名字空间,Kubernetes
会在指定的名字空间内寻找标签。
你会通过 topologyKey
来表达拓扑域(X)的概念,其取值是系统用来标示域的节点标签键。
相关示例可参见常用标签、注解和污点 。
说明: Pod 间亲和性和反亲和性都需要相当的计算量,因此会在大规模集群中显著降低调度速度。
我们不建议在包含数百个节点的集群中使用这类设置。
说明: Pod 反亲和性需要节点上存在一致性的标签。换言之,
集群中每个节点都必须拥有与 topologyKey
匹配的标签。
如果某些或者所有节点上不存在所指定的 topologyKey
标签,调度行为可能与预期的不同。
Pod 间亲和性与反亲和性的类型 与节点亲和性 类似,Pod 的亲和性与反亲和性也有两种类型:
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
例如,你可以使用 requiredDuringSchedulingIgnoredDuringExecution
亲和性来告诉调度器,
将两个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁。
类似地,你可以使用 preferredDuringSchedulingIgnoredDuringExecution
反亲和性来将同一服务的多个 Pod 分布到多个云提供商可用区中。
要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity
字段。
对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity
字段。
Pod 亲和性示例 考虑下面的 Pod 规约:
apiVersion : v1
kind : Pod
metadata :
name : with-pod-affinity
spec :
affinity :
podAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
- labelSelector :
matchExpressions :
- key : security
operator : In
values :
- S1
topologyKey : topology.kubernetes.io/zone
podAntiAffinity :
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 100
podAffinityTerm :
labelSelector :
matchExpressions :
- key : security
operator : In
values :
- S2
topologyKey : topology.kubernetes.io/zone
containers :
- name : with-pod-affinity
image : registry.k8s.io/pause:2.0
本示例定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。Pod 亲和性规则配置为
requiredDuringSchedulingIgnoredDuringExecution
,而 Pod 反亲和性配置为
preferredDuringSchedulingIgnoredDuringExecution
。
亲和性规则表示,仅当节点和至少一个已运行且有 security=S1
的标签的
Pod 处于同一区域时,才可以将该 Pod 调度到节点上。
更确切的说,调度器必须将 Pod 调度到具有 topology.kubernetes.io/zone=V
标签的节点上,并且集群中至少有一个位于该可用区的节点上运行着带有
security=S1
标签的 Pod。
反亲和性规则表示,如果节点处于 Pod 所在的同一可用区且至少一个 Pod 具有
security=S2
标签,则该 Pod 不应被调度到该节点上。
更确切地说, 如果同一可用区中存在其他运行着带有 security=S2
标签的 Pod 节点,
并且节点具有标签 topology.kubernetes.io/zone=R
,Pod 不能被调度到该节点上。
查阅设计文档
以进一步熟悉 Pod 亲和性与反亲和性的示例。
你可以针对 Pod 间亲和性与反亲和性为其 operator
字段使用 In
、NotIn
、Exists
、
DoesNotExist
等值。
原则上,topologyKey
可以是任何合法的标签键。出于性能和安全原因,topologyKey
有一些限制:
对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
中,topologyKey
不允许为空。 对于 requiredDuringSchedulingIgnoredDuringExecution
要求的 Pod 反亲和性,
准入控制器 LimitPodHardAntiAffinityTopology
要求 topologyKey
只能是
kubernetes.io/hostname
。如果你希望使用其他定制拓扑逻辑,
你可以更改准入控制器或者禁用之。 除了 labelSelector
和 topologyKey
,你也可以指定 labelSelector
要匹配的命名空间列表,方法是在 labelSelector
和 topologyKey
所在层同一层次上设置 namespaces
。
如果 namespaces
被忽略或者为空,则默认为 Pod 亲和性/反亲和性的定义所在的命名空间。
名字空间选择算符 特性状态: Kubernetes v1.24 [stable]
用户也可以使用 namespaceSelector
选择匹配的名字空间,namespaceSelector
是对名字空间集合进行标签查询的机制。
亲和性条件会应用到 namespaceSelector
所选择的名字空间和 namespaces
字段中所列举的名字空间之上。
注意,空的 namespaceSelector
({}
)会匹配所有名字空间,而 null 或者空的
namespaces
列表以及 null 值 namespaceSelector
意味着“当前 Pod 的名字空间”。
更实际的用例 Pod 间亲和性与反亲和性在与更高级别的集合(例如 ReplicaSet、StatefulSet、
Deployment 等)一起使用时,它们可能更加有用。
这些规则使得你可以配置一组工作负载,使其位于所定义的同一拓扑中;
例如优先将两个相关的 Pod 置于相同的节点上。
以一个三节点的集群为例。你使用该集群运行一个带有内存缓存(例如 Redis)的 Web 应用程序。
在此例中,还假设 Web 应用程序和内存缓存之间的延迟应尽可能低。
你可以使用 Pod 间的亲和性和反亲和性来尽可能地将该 Web 服务器与缓存并置。
在下面的 Redis 缓存 Deployment 示例中,副本上设置了标签 app=store
。
podAntiAffinity
规则告诉调度器避免将多个带有 app=store
标签的副本部署到同一节点上。
因此,每个独立节点上会创建一个缓存实例。
apiVersion : apps/v1
kind : Deployment
metadata :
name : redis-cache
spec :
selector :
matchLabels :
app : store
replicas : 3
template :
metadata :
labels :
app : store
spec :
affinity :
podAntiAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
- labelSelector :
matchExpressions :
- key : app
operator : In
values :
- store
topologyKey : "kubernetes.io/hostname"
containers :
- name : redis-server
image : redis:3.2-alpine
下例的 Deployment 为 Web 服务器创建带有标签 app=web-store
的副本。
Pod 亲和性规则告诉调度器将每个副本放到存在标签为 app=store
的 Pod 的节点上。
Pod 反亲和性规则告诉调度器决不要在单个节点上放置多个 app=web-store
服务器。
apiVersion : apps/v1
kind : Deployment
metadata :
name : web-server
spec :
selector :
matchLabels :
app : web-store
replicas : 3
template :
metadata :
labels :
app : web-store
spec :
affinity :
podAntiAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
- labelSelector :
matchExpressions :
- key : app
operator : In
values :
- web-store
topologyKey : "kubernetes.io/hostname"
podAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
- labelSelector :
matchExpressions :
- key : app
operator : In
values :
- store
topologyKey : "kubernetes.io/hostname"
containers :
- name : web-app
image : nginx:1.16-alpine
创建前面两个 Deployment 会产生如下的集群布局,每个 Web 服务器与一个缓存实例并置,
并分别运行在三个独立的节点上。
node-1 node-2 node-3 webserver-1 webserver-2 webserver-3 cache-1 cache-2 cache-3
总体效果是每个缓存实例都非常可能被在同一个节点上运行的某个客户端访问。
这种方法旨在最大限度地减少偏差(负载不平衡)和延迟。
你可能还有使用 Pod 反亲和性的一些其他原因。
参阅 ZooKeeper 教程
了解一个 StatefulSet 的示例,该 StatefulSet 配置了反亲和性以实现高可用,
所使用的是与此例相同的技术。
nodeName nodeName
是比亲和性或者 nodeSelector
更为直接的形式。nodeName
是 Pod
规约中的一个字段。如果 nodeName
字段不为空,调度器会忽略该 Pod,
而指定节点上的 kubelet 会尝试将 Pod 放到该节点上。
使用 nodeName
规则的优先级会高于使用 nodeSelector
或亲和性与非亲和性的规则。
使用 nodeName
来选择节点的方式有一些局限性:
如果所指代的节点不存在,则 Pod 无法运行,而且在某些情况下可能会被自动删除。 如果所指代的节点无法提供用来运行 Pod 所需的资源,Pod 会失败,
而其失败原因中会给出是否因为内存或 CPU 不足而造成无法运行。 在云环境中的节点名称并不总是可预测的,也不总是稳定的。 下面是一个使用 nodeName
字段的 Pod 规约示例:
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
containers :
- name : nginx
image : nginx
nodeName : kube-01
上面的 Pod 只能运行在节点 kube-01
之上。
Pod 拓扑分布约束 你可以使用 拓扑分布约束(Topology Spread Constraints) 来控制
Pod 在集群内故障域之间的分布,
故障域的示例有区域(Region)、可用区(Zone)、节点和其他用户自定义的拓扑域。
这样做有助于提升性能、实现高可用或提升资源利用率。
阅读 Pod 拓扑分布约束
以进一步了解这些约束的工作方式。
接下来 3.11.3 - Pod 开销 特性状态: Kubernetes v1.24 [stable]
在节点上运行 Pod 时,Pod 本身占用大量系统资源。这些是运行 Pod 内容器所需资源之外的资源。
在 Kubernetes 中,POD 开销 是一种方法,用于计算 Pod 基础设施在容器请求和限制之上消耗的资源。
在 Kubernetes 中,Pod 的开销是根据与 Pod 的 RuntimeClass
相关联的开销在准入 时设置的。
如果启用了 Pod Overhead,在调度 Pod 时,除了考虑容器资源请求的总和外,还要考虑 Pod 开销。
类似地,kubelet 将在确定 Pod cgroups 的大小和执行 Pod 驱逐排序时也会考虑 Pod 开销。
配置 Pod 开销 你需要确保使用一个定义了 overhead
字段的 RuntimeClass
。
使用示例 要使用 Pod 开销,你需要一个定义了 overhead
字段的 RuntimeClass。
作为例子,下面的 RuntimeClass 定义中包含一个虚拟化所用的容器运行时,
RuntimeClass 如下,其中每个 Pod 大约使用 120MiB 用来运行虚拟机和寄宿操作系统:
apiVersion : node.k8s.io/v1
kind : RuntimeClass
metadata :
name : kata-fc
handler : kata-fc
overhead :
podFixed :
memory : "120Mi"
cpu : "250m"
通过指定 kata-fc
RuntimeClass 处理程序创建的工作负载会将内存和 CPU
开销计入资源配额计算、节点调度以及 Pod cgroup 尺寸确定。
假设我们运行下面给出的工作负载示例 test-pod:
apiVersion : v1
kind : Pod
metadata :
name : test-pod
spec :
runtimeClassName : kata-fc
containers :
- name : busybox-ctr
image : busybox:1.28
stdin : true
tty : true
resources :
limits :
cpu : 500m
memory : 100Mi
- name : nginx-ctr
image : nginx
resources :
limits :
cpu : 1500m
memory : 100Mi
在准入阶段 RuntimeClass 准入控制器
更新工作负载的 PodSpec 以包含
RuntimeClass 中定义的 overhead
。如果 PodSpec 中已定义该字段,该 Pod 将会被拒绝。
在这个例子中,由于只指定了 RuntimeClass 名称,所以准入控制器更新了 Pod,使之包含 overhead
。
在 RuntimeClass 准入控制器进行修改后,你可以查看更新后的 PodSpec:
kubectl get pod test-pod -o jsonpath = '{.spec.overhead}'
输出:
map[cpu:250m memory:120Mi]
如果定义了 ResourceQuata ,
则容器请求的总量以及 overhead
字段都将计算在内。
当 kube-scheduler 决定在哪一个节点调度运行新的 Pod 时,调度器会兼顾该 Pod 的
overhead
以及该 Pod 的容器请求总量。在这个示例中,调度器将资源请求和开销相加,
然后寻找具备 2.25 CPU 和 320 MiB 内存可用的节点。
一旦 Pod 被调度到了某个节点, 该节点上的 kubelet 将为该 Pod 新建一个
cgroup 。 底层容器运行时将在这个
Pod 中创建容器。
如果该资源对每一个容器都定义了一个限制(定义了限制值的 Guaranteed QoS 或者
Burstable QoS),kubelet 会为与该资源(CPU 的 cpu.cfs_quota_us
以及内存的
memory.limit_in_bytes
)
相关的 Pod cgroup 设定一个上限。该上限基于 PodSpec 中定义的容器限制总量与 overhead
之和。
对于 CPU,如果 Pod 的 QoS 是 Guaranteed 或者 Burstable,kubelet 会基于容器请求总量与
PodSpec 中定义的 overhead
之和设置 cpu.shares
。
请看这个例子,验证工作负载的容器请求:
kubectl get pod test-pod -o jsonpath = '{.spec.containers[*].resources.limits}'
容器请求总计 2000m CPU 和 200MiB 内存:
map[cpu: 500m memory:100Mi] map[cpu:1500m memory:100Mi]
对照从节点观察到的情况来检查一下:
kubectl describe node | grep test-pod -B2
该输出显示请求了 2250m CPU 以及 320MiB 内存。请求包含了 Pod 开销在内:
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
default test-pod 2250m (56%) 2250m (56%) 320Mi (1%) 320Mi (1%) 36m
验证 Pod cgroup 限制 在工作负载所运行的节点上检查 Pod 的内存 cgroups。在接下来的例子中,
将在该节点上使用具备 CRI 兼容的容器运行时命令行工具
crictl
。
这是一个显示 Pod 开销行为的高级示例, 预计用户不需要直接在节点上检查 cgroups。
首先在特定的节点上确定该 Pod 的标识符:
# 在该 Pod 被调度到的节点上执行如下命令:
POD_ID = " $( sudo crictl pods --name test-pod -q) "
可以依此判断该 Pod 的 cgroup 路径:
# 在该 Pod 被调度到的节点上执行如下命令:
sudo crictl inspectp -o= json $POD_ID | grep cgroupsPath
执行结果的 cgroup 路径中包含了该 Pod 的 pause
容器。Pod 级别的 cgroup 在即上一层目录。
"cgroupsPath": "/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/7ccf55aee35dd16aca4189c952d83487297f3cd760f1bbf09620e206e7d0c27a"
在这个例子中,该 Pod 的 cgroup 路径是 kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2
。
验证内存的 Pod 级别 cgroup 设置:
# 在该 Pod 被调度到的节点上执行这个命令。
# 另外,修改 cgroup 的名称以匹配为该 Pod 分配的 cgroup。
cat /sys/fs/cgroup/memory/kubepods/podd7f4b509-cf94-4951-9417-d1087c92a5b2/memory.limit_in_bytes
和预期的一样,这一数值为 320 MiB。
335544320
可观察性 在 kube-state-metrics 中可以通过
kube_pod_overhead_*
指标来协助确定何时使用 Pod 开销,
以及协助观察以一个既定开销运行的工作负载的稳定性。
该特性在 kube-state-metrics 的 1.9 发行版本中不可用,不过预计将在后续版本中发布。
在此之前,用户需要从源代码构建 kube-state-metrics。
接下来 3.11.4 - Pod 拓扑分布约束 你可以使用 拓扑分布约束(Topology Spread Constraints) 来控制
Pod 在集群内故障域之间的分布,
例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。
这样做有助于实现高可用并提升资源利用率。
你可以将集群级约束 设为默认值,或为个别工作负载配置拓扑分布约束。
动机 假设你有一个最多包含二十个节点的集群,你想要运行一个自动扩缩的
工作负载 ,请问要使用多少个副本?
答案可能是最少 2 个 Pod,最多 15 个 Pod。
当只有 2 个 Pod 时,你倾向于这 2 个 Pod 不要同时在同一个节点上运行:
你所遭遇的风险是如果放在同一个节点上且单节点出现故障,可能会让你的工作负载下线。
除了这个基本的用法之外,还有一些高级的使用案例,能够让你的工作负载受益于高可用性并提高集群利用率。
随着你的工作负载扩容,运行的 Pod 变多,将需要考虑另一个重要问题。
假设你有 3 个节点,每个节点运行 5 个 Pod。这些节点有足够的容量能够运行许多副本;
但与这个工作负载互动的客户端分散在三个不同的数据中心(或基础设施可用区)。
现在你可能不太关注单节点故障问题,但你会注意到延迟高于自己的预期,
在不同的可用区之间发送网络流量会产生一些网络成本。
你决定在正常运营时倾向于将类似数量的副本调度
到每个基础设施可用区,且你想要该集群在遇到问题时能够自愈。
Pod 拓扑分布约束使你能够以声明的方式进行配置。
topologySpreadConstraints
字段Pod API 包括一个 spec.topologySpreadConstraints
字段。这个字段的用法如下所示:
---
apiVersion : v1
kind : Pod
metadata :
name : example-pod
spec :
# 配置一个拓扑分布约束
topologySpreadConstraints :
- maxSkew : <integer>
minDomains : <integer> # 可选;自从 v1.25 开始成为 Beta
topologyKey : <string>
whenUnsatisfiable : <string>
labelSelector : <object>
matchLabelKeys : <list> # 可选;自从 v1.25 开始成为 Alpha
nodeAffinityPolicy : [Honor|Ignore] # 可选;自从 v1.25 开始成为 Alpha
nodeTaintsPolicy : [Honor|Ignore] # 可选;自从 v1.25 开始成为 Alpha
### 其他 Pod 字段置于此处
你可以运行 kubectl explain Pod.spec.topologySpreadConstraints
或参阅 Pod API
参考的调度 一节,
了解有关此字段的更多信息。
分布约束定义 你可以定义一个或多个 topologySpreadConstraints
条目以指导 kube-scheduler
如何将每个新来的 Pod 与跨集群的现有 Pod 相关联。这些字段包括:
topologyKey 是节点标签 的键。如果节点使用此键标记并且具有相同的标签值,
则将这些节点视为处于同一拓扑域中。我们将拓扑域中(即键值对)的每个实例称为一个域。
调度器将尝试在每个拓扑域中放置数量均衡的 Pod。
另外,我们将符合条件的域定义为其节点满足 nodeAffinityPolicy 和 nodeTaintsPolicy 要求的域。
whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:
DoNotSchedule
(默认)告诉调度器不要调度。ScheduleAnyway
告诉调度器仍然继续调度,只是根据如何能将偏差最小化来对节点进行排序。labelSelector 用于查找匹配的 Pod。匹配此标签的 Pod 将被统计,以确定相应拓扑域中 Pod 的数量。
有关详细信息,请参考标签选择算符 。
matchLabelKeys 是一个 Pod 标签键的列表,用于选择需要计算分布方式的 Pod 集合。
这些键用于从 Pod 标签中查找值,这些键值标签与 labelSelector
进行逻辑与运算,以选择一组已有的 Pod,
通过这些 Pod 计算新来 Pod 的分布方式。Pod 标签中不存在的键将被忽略。
null 或空列表意味着仅与 labelSelector
匹配。
借助 matchLabelKeys
,用户无需在变更 Pod 修订版本时更新 pod.spec
。
控制器或 Operator 只需要将不同修订版的 label
键设为不同的值。
调度器将根据 matchLabelKeys
自动确定取值。例如,如果用户使用 Deployment,
则他们可以使用由 Deployment 控制器自动添加的、以 pod-template-hash
为键的标签来区分单个
Deployment 的不同修订版。
topologySpreadConstraints :
- maxSkew : 1
topologyKey : kubernetes.io/hostname
whenUnsatisfiable : DoNotSchedule
matchLabelKeys :
- app
- pod-template-hash
说明: matchLabelKeys
字段是 1.25 中新增的一个 Alpha 字段。
你必须启用 MatchLabelKeysInPodTopologySpread
特性门控 才能使用此字段。
当 Pod 定义了不止一个 topologySpreadConstraint
,这些约束之间是逻辑与的关系。
kube-scheduler 会为新的 Pod 寻找一个能够满足所有约束的节点。
节点标签 拓扑分布约束依赖于节点标签来标识每个节点 所在的拓扑域。
例如,某节点可能具有标签:
region : us-east-1
zone : us-east-1a
说明: 为了简便,此示例未使用众所周知 的标签键
topology.kubernetes.io/zone
和 topology.kubernetes.io/region
。
但是,建议使用那些已注册的标签键,而不是此处使用的私有(不合格)标签键 region
和 zone
。
你无法对不同上下文之间的私有标签键的含义做出可靠的假设。
假设你有一个 4 节点的集群且带有以下标签:
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready <none> 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready <none> 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready <none> 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready <none> 2m43s v1.16.0 node=node4,zone=zoneB
那么,从逻辑上看集群如下:
graph TB
subgraph "zoneB"
n3(Node3)
n4(Node4)
end
subgraph "zoneA"
n1(Node1)
n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4 k8s;
class zoneA,zoneB cluster;
一致性 你应该为一个组中的所有 Pod 设置相同的 Pod 拓扑分布约束。
通常,如果你正使用一个工作负载控制器,例如 Deployment,则 Pod 模板会帮你解决这个问题。
如果你混合不同的分布约束,则 Kubernetes 会遵循该字段的 API 定义;
但是,该行为可能更令人困惑,并且故障排除也没那么简单。
你需要一种机制来确保拓扑域(例如云提供商区域)中的所有节点具有一致的标签。
为了避免你需要手动为节点打标签,大多数集群会自动填充知名的标签,
例如 topology.kubernetes.io/hostname
。检查你的集群是否支持此功能。
拓扑分布约束示例 示例:一个拓扑分布约束 假设你拥有一个 4 节点集群,其中标记为 foo: bar
的 3 个 Pod 分别位于 node1、node2 和 node3 中:
graph BT
subgraph "zoneB"
p3(Pod) --> n3(Node3)
n4(Node4)
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3 k8s;
class zoneA,zoneB cluster;
如果你希望新来的 Pod 均匀分布在现有的可用区域,则可以按如下设置其清单:
kind : Pod
apiVersion : v1
metadata :
name : mypod
labels :
foo : bar
spec :
topologySpreadConstraints :
- maxSkew : 1
topologyKey : zone
whenUnsatisfiable : DoNotSchedule
labelSelector :
matchLabels :
foo : bar
containers :
- name : pause
image : registry.k8s.io/pause:3.1
从此清单看,topologyKey: zone
意味着均匀分布将只应用于存在标签键值对为 zone: <any value>
的节点
(没有 zone
标签的节点将被跳过)。如果调度器找不到一种方式来满足此约束,
则 whenUnsatisfiable: DoNotSchedule
字段告诉该调度器将新来的 Pod 保持在 pending 状态。
如果该调度器将这个新来的 Pod 放到可用区 A
,则 Pod 的分布将成为 [3, 1]
。
这意味着实际偏差是 2(计算公式为 3 - 1
),这违反了 maxSkew: 1
的约定。
为了满足这个示例的约束和上下文,新来的 Pod 只能放到可用区 B
中的一个节点上:
graph BT
subgraph "zoneB"
p3(Pod) --> n3(Node3)
p4(mypod) --> n4(Node4)
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3 k8s;
class p4 plain;
class zoneA,zoneB cluster;
或者
graph BT
subgraph "zoneB"
p3(Pod) --> n3(Node3)
p4(mypod) --> n3
n4(Node4)
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3 k8s;
class p4 plain;
class zoneA,zoneB cluster;
你可以调整 Pod 规约以满足各种要求:
将 maxSkew
更改为更大的值,例如 2
,这样新来的 Pod 也可以放在可用区 A
中。 将 topologyKey
更改为 node
,以便将 Pod 均匀分布在节点上而不是可用区中。
在上面的例子中,如果 maxSkew
保持为 1
,则新来的 Pod 只能放到 node4
节点上。 将 whenUnsatisfiable: DoNotSchedule
更改为 whenUnsatisfiable: ScheduleAnyway
,
以确保新来的 Pod 始终可以被调度(假设满足其他的调度 API)。但是,最好将其放置在匹配 Pod 数量较少的拓扑域中。
请注意,这一优先判定会与其他内部调度优先级(如资源使用率等)排序准则一起进行标准化。 示例:多个拓扑分布约束 下面的例子建立在前面例子的基础上。假设你拥有一个 4 节点集群,
其中 3 个标记为 foo: bar
的 Pod 分别位于 node1、node2 和 node3 上:
graph BT
subgraph "zoneB"
p3(Pod) --> n3(Node3)
n4(Node4)
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3 k8s;
class p4 plain;
class zoneA,zoneB cluster;
可以组合使用 2 个拓扑分布约束来控制 Pod 在节点和可用区两个维度上的分布:
kind : Pod
apiVersion : v1
metadata :
name : mypod
labels :
foo : bar
spec :
topologySpreadConstraints :
- maxSkew : 1
topologyKey : zone
whenUnsatisfiable : DoNotSchedule
labelSelector :
matchLabels :
foo : bar
- maxSkew : 1
topologyKey : node
whenUnsatisfiable : DoNotSchedule
labelSelector :
matchLabels :
foo : bar
containers :
- name : pause
image : registry.k8s.io/pause:3.1
在这种情况下,为了匹配第一个约束,新的 Pod 只能放置在可用区 B
中;
而在第二个约束中,新来的 Pod 只能调度到节点 node4
上。
该调度器仅考虑满足所有已定义约束的选项,因此唯一可行的选择是放置在节点 node4
上。
示例:有冲突的拓扑分布约束 多个约束可能导致冲突。假设有一个跨 2 个可用区的 3 节点集群:
graph BT
subgraph "zoneB"
p4(Pod) --> n3(Node3)
p5(Pod) --> n3
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n1
p3(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3,p4,p5 k8s;
class zoneA,zoneB cluster;
如果你将 two-constraints.yaml
(来自上一个示例的清单)应用到这个 集群,你将看到 Pod mypod
保持在 Pending
状态。
出现这种情况的原因为:为了满足第一个约束,Pod mypod
只能放置在可用区 B
中;
而在第二个约束中,Pod mypod
只能调度到节点 node2
上。
两个约束的交集将返回一个空集,且调度器无法放置该 Pod。
为了应对这种情形,你可以提高 maxSkew
的值或修改其中一个约束才能使用 whenUnsatisfiable: ScheduleAnyway
。
根据实际情形,例如若你在故障排查时发现某个漏洞修复工作毫无进展,你还可能决定手动删除一个现有的 Pod。
与节点亲和性和节点选择算符的相互作用 如果 Pod 定义了 spec.nodeSelector
或 spec.affinity.nodeAffinity
,
调度器将在偏差计算中跳过不匹配的节点。
示例:带节点亲和性的拓扑分布约束 假设你有一个跨可用区 A 到 C 的 5 节点集群:
graph BT
subgraph "zoneB"
p3(Pod) --> n3(Node3)
n4(Node4)
end
subgraph "zoneA"
p1(Pod) --> n1(Node1)
p2(Pod) --> n2(Node2)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n1,n2,n3,n4,p1,p2,p3 k8s;
class p4 plain;
class zoneA,zoneB cluster;
graph BT
subgraph "zoneC"
n5(Node5)
end
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class n5 k8s;
class zoneC cluster;
而且你知道可用区 C
必须被排除在外。在这种情况下,可以按如下方式编写清单,
以便将 Pod mypod
放置在可用区 B
上,而不是可用区 C
上。
同样,Kubernetes 也会一样处理 spec.nodeSelector
。
kind : Pod
apiVersion : v1
metadata :
name : mypod
labels :
foo : bar
spec :
topologySpreadConstraints :
- maxSkew : 1
topologyKey : zone
whenUnsatisfiable : DoNotSchedule
labelSelector :
matchLabels :
foo : bar
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : zone
operator : NotIn
values :
- zoneC
containers :
- name : pause
image : registry.k8s.io/pause:3.1
隐式约定 这里有一些值得注意的隐式约定:
注意,如果新 Pod 的 topologySpreadConstraints[*].labelSelector
与自身的标签不匹配,将会发生什么。
在上面的例子中,如果移除新 Pod 的标签,则 Pod 仍然可以放置到可用区 B
中的节点上,因为这些约束仍然满足。
然而,在放置之后,集群的不平衡程度保持不变。可用区 A
仍然有 2 个 Pod 带有标签 foo: bar
,
而可用区 B
有 1 个 Pod 带有标签 foo: bar
。如果这不是你所期望的,
更新工作负载的 topologySpreadConstraints[*].labelSelector
以匹配 Pod 模板中的标签。 集群级别的默认约束 为集群设置默认的拓扑分布约束也是可能的。默认拓扑分布约束在且仅在以下条件满足时才会被应用到 Pod 上:
Pod 没有在其 .spec.topologySpreadConstraints
中定义任何约束。 Pod 隶属于某个 Service、ReplicaSet、StatefulSet 或 ReplicationController。 默认约束可以设置为调度方案 中
PodTopologySpread
插件参数的一部分。约束的设置采用如前所述的 API ,
只是 labelSelector
必须为空。
选择算符是根据 Pod 所属的 Service、ReplicaSet、StatefulSet 或 ReplicationController 来设置的。
配置的示例可能看起来像下面这个样子:
apiVersion : kubescheduler.config.k8s.io/v1beta3
kind : KubeSchedulerConfiguration
profiles :
- schedulerName : default-scheduler
pluginConfig :
- name : PodTopologySpread
args :
defaultConstraints :
- maxSkew : 1
topologyKey : topology.kubernetes.io/zone
whenUnsatisfiable : ScheduleAnyway
defaultingType : List
内置默认约束 特性状态: Kubernetes v1.24 [stable]
如果你没有为 Pod 拓扑分布配置任何集群级别的默认约束,
kube-scheduler 的行为就像你指定了以下默认拓扑约束一样:
defaultConstraints :
- maxSkew : 3
topologyKey : "kubernetes.io/hostname"
whenUnsatisfiable : ScheduleAnyway
- maxSkew : 5
topologyKey : "topology.kubernetes.io/zone"
whenUnsatisfiable : ScheduleAnyway
此外,原来用于提供等同行为的 SelectorSpread
插件默认被禁用。
说明: 对于分布约束中所指定的拓扑键而言,PodTopologySpread
插件不会为不包含这些拓扑键的节点评分。
这可能导致在使用默认拓扑约束时,其行为与原来的 SelectorSpread
插件的默认行为不同。
如果你的节点不会 同时 设置 kubernetes.io/hostname
和 topology.kubernetes.io/zone
标签,
你应该定义自己的约束而不是使用 Kubernetes 的默认约束。
如果你不想为集群使用默认的 Pod 分布约束,你可以通过设置 defaultingType
参数为 List
,
并将 PodTopologySpread
插件配置中的 defaultConstraints
参数置空来禁用默认 Pod 分布约束:
apiVersion : kubescheduler.config.k8s.io/v1beta3
kind : KubeSchedulerConfiguration
profiles :
- schedulerName : default-scheduler
pluginConfig :
- name : PodTopologySpread
args :
defaultConstraints : []
defaultingType : List
比较 podAffinity 和 podAntiAffinity 在 Kubernetes 中,
Pod 间亲和性和反亲和性 控制
Pod 彼此的调度方式(更密集或更分散)。
podAffinity
吸引 Pod;你可以尝试将任意数量的 Pod 集中到符合条件的拓扑域中。 podAntiAffinity
驱逐 Pod。如果将此设为 requiredDuringSchedulingIgnoredDuringExecution
模式,
则只有单个 Pod 可以调度到单个拓扑域;如果你选择 preferredDuringSchedulingIgnoredDuringExecution
,
则你将丢失强制执行此约束的能力。 要实现更细粒度的控制,你可以设置拓扑分布约束来将 Pod 分布到不同的拓扑域下,从而实现高可用性或节省成本。
这也有助于工作负载的滚动更新和平稳地扩展副本规模。
有关详细信息,请参阅有关 Pod 拓扑分布约束的增强倡议的
动机 一节。
已知局限性 当 Pod 被移除时,无法保证约束仍被满足。例如,缩减某 Deployment 的规模时,Pod 的分布可能不再均衡。
你可以使用 Descheduler 来重新实现 Pod 分布的均衡。
具有污点的节点上匹配的 Pod 也会被统计。
参考 Issue 80921 。
接下来 3.11.5 - 污点和容忍度 节点亲和性
是 Pod 的一种属性,它使 Pod
被吸引到一类特定的节点
(这可能出于一种偏好,也可能是硬性要求)。
污点(Taint) 则相反——它使节点能够排斥一类特定的 Pod。
容忍度(Toleration) 是应用于 Pod 上的。容忍度允许调度器调度带有对应污点的 Pod。
容忍度允许调度但并不保证调度:作为其功能的一部分,
调度器也会评估其他参数 。
污点和容忍度(Toleration)相互配合,可以用来避免 Pod 被分配到不合适的节点上。
每个节点上都可以应用一个或多个污点,这表示对于那些不能容忍这些污点的 Pod,
是不会被该节点接受的。
概念 你可以使用命令 kubectl taint 给节点增加一个污点。比如,
kubectl taint nodes node1 key1 = value1:NoSchedule
给节点 node1
增加一个污点,它的键名是 key1
,键值是 value1
,效果是 NoSchedule
。
这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1
这个节点。
若要移除上述命令所添加的污点,你可以执行:
kubectl taint nodes node1 key1 = value1:NoSchedule-
你可以在 Pod 规约中为 Pod 设置容忍度。
下面两个容忍度均与上面例子中使用 kubectl taint
命令创建的污点相匹配,
因此如果一个 Pod 拥有其中的任何一个容忍度,都能够被调度到 node1
:
tolerations :
- key : "key1"
operator : "Equal"
value : "value1"
effect : "NoSchedule"
tolerations :
- key : "key1"
operator : "Exists"
effect : "NoSchedule"
这里是一个使用了容忍度的 Pod:
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
env : test
spec :
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
tolerations :
- key : "example-key"
operator : "Exists"
effect : "NoSchedule"
operator
的默认值是 Equal
。
一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果,并且:
如果 operator
是 Exists
(此时容忍度不能指定 value
),或者 如果 operator
是 Equal
,则它们的 value
应该相等 说明: 存在两种特殊情况:
如果一个容忍度的 key
为空且 operator
为 Exists
,
表示这个容忍度与任意的 key、value 和 effect 都匹配,即这个容忍度能容忍任何污点。
如果 effect
为空,则可以与所有键名 key1
的效果相匹配。
上述例子中 effect
使用的值为 NoSchedule
,你也可以使用另外一个值 PreferNoSchedule
。
这是“优化”或“软”版本的 NoSchedule
—— 系统会 尽量 避免将 Pod 调度到存在其不能容忍污点的节点上,
但这不是强制的。effect
的值还可以设置为 NoExecute
,下文会详细描述这个值。
你可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。
Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历,
过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了
Pod 是否会被分配到该节点。需要注意以下情况:
如果未被忽略的污点中存在至少一个 effect 值为 NoSchedule
的污点,
则 Kubernetes 不会将 Pod 调度到该节点。 如果未被忽略的污点中不存在 effect 值为 NoSchedule
的污点,
但是存在至少一个 effect 值为 PreferNoSchedule
的污点,
则 Kubernetes 会 尝试 不将 Pod 调度到该节点。 如果未被忽略的污点中存在至少一个 effect 值为 NoExecute
的污点,
则 Kubernetes 不会将 Pod 调度到该节点(如果 Pod 还未在节点上运行),
或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。 例如,假设你给一个节点添加了如下污点
kubectl taint nodes node1 key1 = value1:NoSchedule
kubectl taint nodes node1 key1 = value1:NoExecute
kubectl taint nodes node1 key2 = value2:NoSchedule
假定某个 Pod 有两个容忍度:
tolerations :
- key : "key1"
operator : "Equal"
value : "value1"
effect : "NoSchedule"
- key : "key1"
operator : "Equal"
value : "value1"
effect : "NoExecute"
在这种情况下,上述 Pod 不会被调度到上述节点,因为其没有容忍度和第三个污点相匹配。
但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行,
那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
通常情况下,如果给一个节点添加了一个 effect 值为 NoExecute
的污点,
则任何不能忍受这个污点的 Pod 都会马上被驱逐,任何可以忍受这个污点的 Pod 都不会被驱逐。
但是,如果 Pod 存在一个 effect 值为 NoExecute
的容忍度指定了可选属性
tolerationSeconds
的值,则表示在给节点添加了上述污点之后,
Pod 还能继续在节点上运行的时间。例如,
tolerations :
- key : "key1"
operator : "Equal"
value : "value1"
effect : "NoExecute"
tolerationSeconds : 3600
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点,
那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。
如果在此之前上述污点被删除了,则 Pod 不会被驱逐。
使用例子 通过污点和容忍度,可以灵活地让 Pod 避开 某些节点或者将 Pod 从某些节点驱逐。
下面是几个使用例子:
专用节点 :如果想将某些节点专门分配给特定的一组用户使用,你可以给这些节点添加一个污点(即,
kubectl taint nodes nodename dedicated=groupName:NoSchedule
),
然后给这组用户的 Pod 添加一个相对应的容忍度
(通过编写一个自定义的准入控制器 ,
很容易就能做到)。
拥有上述容忍度的 Pod 就能够被调度到上述专用节点,同时也能够被调度到集群中的其它节点。
如果你希望这些 Pod 只能被调度到上述专用节点,
那么你还需要给这些专用节点另外添加一个和上述污点类似的 label (例如:dedicated=groupName
),
同时还要在上述准入控制器中给 Pod 增加节点亲和性要求,要求上述 Pod 只能被调度到添加了
dedicated=groupName
标签的节点上。配备了特殊硬件的节点 :在部分节点配备了特殊硬件(比如 GPU)的集群中,
我们希望不需要这类硬件的 Pod 不要被调度到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。
要达到这个目的,可以先给配备了特殊硬件的节点添加污点
(例如 kubectl taint nodes nodename special=true:NoSchedule
或
kubectl taint nodes nodename special=true:PreferNoSchedule
),
然后给使用了这类特殊硬件的 Pod 添加一个相匹配的容忍度。
和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义
准入控制器 。
比如,我们推荐使用扩展资源
来表示特殊硬件,给配置了特殊硬件的节点添加污点时包含扩展资源名称,
然后运行一个 ExtendedResourceToleration
准入控制器。此时,因为节点已经被设置污点了,没有对应容忍度的 Pod 不会被调度到这些节点。
但当你创建一个使用了扩展资源的 Pod 时,ExtendedResourceToleration
准入控制器会自动给
Pod 加上正确的容忍度,这样 Pod 就会被自动调度到这些配置了特殊硬件的节点上。
这种方式能够确保配置了特殊硬件的节点专门用于运行需要这些硬件的 Pod,
并且你无需手动给这些 Pod 添加容忍度。基于污点的驱逐 : 这是在每个 Pod 中配置的在节点出现问题时的驱逐行为,
接下来的章节会描述这个特性。基于污点的驱逐 特性状态: Kubernetes v1.18 [stable]
前文提到过污点的效果值 NoExecute
会影响已经在节点上运行的 Pod,如下
如果 Pod 不能忍受这类污点,Pod 会马上被驱逐。 如果 Pod 能够忍受这类污点,但是在容忍度定义中没有指定 tolerationSeconds
,
则 Pod 还会一直在这个节点上运行。 如果 Pod 能够忍受这类污点,而且指定了 tolerationSeconds
,
则 Pod 还能在这个节点上继续运行这个指定的时间长度。 当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready
:节点未准备好。这相当于节点状况 Ready
的值为 "False
"。node.kubernetes.io/unreachable
:节点控制器访问不到节点. 这相当于节点状况 Ready
的值为 "Unknown
"。node.kubernetes.io/memory-pressure
:节点存在内存压力。node.kubernetes.io/disk-pressure
:节点存在磁盘压力。node.kubernetes.io/pid-pressure
: 节点的 PID 压力。node.kubernetes.io/network-unavailable
:节点网络不可用。node.kubernetes.io/unschedulable
: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized
:如果 kubelet 启动时指定了一个“外部”云平台驱动,
它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager
的一个控制器初始化这个节点后,kubelet 将删除这个污点。在节点被驱逐时,节点控制器或者 kubelet 会添加带有 NoExecute
效果的相关污点。
如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。
说明: 控制面会限制向节点添加新污点的速率。这一速率限制可以管理多个节点同时不可达时
(例如出现网络中断的情况),可能触发的驱逐的数量。
你可以为 Pod 设置 tolerationSeconds
,以指定当节点失效或者不响应时,
Pod 维系与该节点间绑定关系的时长。
比如,你可能希望在出现网络分裂事件时,对于一个与节点本地状态有着深度绑定的应用而言,
仍然停留在当前节点上运行一段较长的时间,以等待网络恢复以避免被驱逐。
你为这种 Pod 所设置的容忍度看起来可能是这样:
tolerations :
- key : "node.kubernetes.io/unreachable"
operator : "Exists"
effect : "NoExecute"
tolerationSeconds : 6000
说明: Kubernetes 会自动给 Pod 添加针对 node.kubernetes.io/not-ready
和
node.kubernetes.io/unreachable
的容忍度,且配置 tolerationSeconds=300
,
除非用户自身或者某控制器显式设置此容忍度。
这些自动添加的容忍度意味着 Pod 可以在检测到对应的问题之一时,在 5
分钟内保持绑定在该节点上。
DaemonSet 中的 Pod 被创建时,
针对以下污点自动添加的 NoExecute
的容忍度将不会指定 tolerationSeconds
:
node.kubernetes.io/unreachable
node.kubernetes.io/not-ready
这保证了出现上述问题时 DaemonSet 中的 Pod 永远不会被驱逐。
基于节点状态添加污点 控制平面使用节点控制器 自动创建
与节点状况
对应的、效果为 NoSchedule
的污点。
调度器在进行调度时检查污点,而不是检查节点状况。这确保节点状况不会直接影响调度。
例如,如果 DiskPressure
节点状况处于活跃状态,则控制平面添加
node.kubernetes.io/disk-pressure
污点并且不会调度新的 Pod 到受影响的节点。
如果 MemoryPressure
节点状况处于活跃状态,则控制平面添加
node.kubernetes.io/memory-pressure
污点。
对于新创建的 Pod,可以通过添加相应的 Pod 容忍度来忽略节点状况。
控制平面还在具有除 BestEffort
之外的
QoS 类 的 Pod 上添加
node.kubernetes.io/memory-pressure
容忍度。
这是因为 Kubernetes 将 Guaranteed
或 Burstable
QoS 类中的 Pod(甚至没有设置内存请求的 Pod)
视为能够应对内存压力,而新创建的 BestEffort
Pod 不会被调度到受影响的节点上。
DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule
容忍度,以防 DaemonSet 崩溃:
node.kubernetes.io/memory-pressure
node.kubernetes.io/disk-pressure
node.kubernetes.io/pid-pressure
(1.14 或更高版本)node.kubernetes.io/unschedulable
(1.10 或更高版本)node.kubernetes.io/network-unavailable
(只适合主机网络配置 )添加上述容忍度确保了向后兼容,你也可以选择自由向 DaemonSet 添加容忍度。
接下来 3.11.6 - 调度框架 特性状态: Kubernetes 1.19 [stable]
调度框架是面向 Kubernetes 调度器的一种插件架构,
它为现有的调度器添加了一组新的“插件” API。插件会被编译到调度器之中。
这些 API 允许大多数调度功能以插件的形式实现,同时使调度“核心”保持简单且可维护。
请参考调度框架的设计提案
获取框架设计的更多技术信息。
框架工作流程 调度框架定义了一些扩展点。调度器插件注册后在一个或多个扩展点处被调用。
这些插件中的一些可以改变调度决策,而另一些仅用于提供信息。
每次调度一个 Pod 的尝试都分为两个阶段,即 调度周期 和 绑定周期 。
调度周期和绑定周期 调度周期为 Pod 选择一个节点,绑定周期将该决策应用于集群。
调度周期和绑定周期一起被称为“调度上下文”。
调度周期是串行运行的,而绑定周期可能是同时运行的。
如果确定 Pod 不可调度或者存在内部错误,则可以终止调度周期或绑定周期。
Pod 将返回队列并重试。
扩展点 下图显示了一个 Pod 的调度上下文以及调度框架公开的扩展点。
在此图片中,“过滤器”等同于“断言”,“评分”相当于“优先级函数”。
一个插件可以在多个扩展点处注册,以执行更复杂或有状态的任务。
调度框架扩展点 队列排序 这些插件用于对调度队列中的 Pod 进行排序。
队列排序插件本质上提供 less(Pod1, Pod2)
函数。
一次只能启动一个队列插件。
PreFilter 这些插件用于预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。
如果 PreFilter 插件返回错误,则调度周期将终止。
Filter 这些插件用于过滤出不能运行该 Pod 的节点。对于每个节点,
调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行,
则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
PostFilter 这些插件在 Filter 阶段后调用,但仅在该 Pod 没有可行的节点时调用。
插件按其配置的顺序调用。如果任何 PostFilter 插件标记节点为“Schedulable”,
则其余的插件不会调用。典型的 PostFilter 实现是抢占,试图通过抢占其他 Pod
的资源使该 Pod 可以调度。
PreScore 这些插件用于执行 “前置评分(pre-scoring)” 工作,即生成一个可共享状态供 Score 插件使用。
如果 PreScore 插件返回错误,则调度周期将终止。
Score 这些插件用于对通过过滤阶段的节点进行排序。调度器将为每个节点调用每个评分插件。
将有一个定义明确的整数范围,代表最小和最大分数。
在标准化评分 阶段之后,调度器将根据配置的插件权重
合并所有插件的节点分数。
NormalizeScore 这些插件用于在调度器计算 Node 排名之前修改分数。
在此扩展点注册的插件被调用时会使用同一插件的 Score 结果。
每个插件在每个调度周期调用一次。
例如,假设一个 BlinkingLightScorer
插件基于具有的闪烁指示灯数量来对节点进行排名。
func ScoreNode (_ * v1.pod, n * v1.Node) (int , error ) {
return getBlinkingLightCount (n)
}
然而,最大的闪烁灯个数值可能比 NodeScoreMax
小。要解决这个问题,
BlinkingLightScorer
插件还应该注册该扩展点。
func NormalizeScores (scores map [string ]int ) {
highest := 0
for _, score := range scores {
highest = max (highest, score)
}
for node, score := range scores {
scores[node] = score* NodeScoreMax/ highest
}
}
如果任何 NormalizeScore 插件返回错误,则调度阶段将终止。
说明: 希望执行“预保留”工作的插件应该使用 NormalizeScore 扩展点。
Reserve Reserve 是一个信息性的扩展点。
管理运行时状态的插件(也成为“有状态插件”)应该使用此扩展点,以便
调度器在节点给指定 Pod 预留了资源时能够通知该插件。
这是在调度器真正将 Pod 绑定到节点之前发生的,并且它存在是为了防止
在调度器等待绑定成功时发生竞争情况。
这个是调度周期的最后一步。
一旦 Pod 处于保留状态,它将在绑定周期结束时触发 Unreserve 插件
(失败时)或 PostBind 插件(成功时)。
Permit Permit 插件在每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定。
一个允许插件可以做以下三件事之一:
批准 一旦所有 Permit 插件批准 Pod 后,该 Pod 将被发送以进行绑定。拒绝 如果任何 Permit 插件拒绝 Pod,则该 Pod 将被返回到调度队列。
这将触发Unreserve 插件。等待 (带有超时) 如果一个 Permit 插件返回 “等待” 结果,则 Pod 将保持在一个内部的 “等待中”
的 Pod 列表,同时该 Pod 的绑定周期启动时即直接阻塞直到得到
批准 。如果超时发生,等待 变成 拒绝 ,并且 Pod
将返回调度队列,从而触发 Unreserve 插件。说明: 尽管任何插件可以访问 “等待中” 状态的 Pod 列表并批准它们
(查看
FrameworkHandle
)。
我们期望只有允许插件可以批准处于 “等待中” 状态的预留 Pod 的绑定。
一旦 Pod 被批准了,它将发送到
PreBind 阶段。
PreBind 这些插件用于执行 Pod 绑定前所需的所有工作。
例如,一个 PreBind 插件可能需要制备网络卷并且在允许 Pod 运行在该节点之前
将其挂载到目标节点上。
如果任何 PreBind 插件返回错误,则 Pod 将被 拒绝 并且
退回到调度队列中。
Bind Bind 插件用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。
各 Bind 插件按照配置顺序被调用。Bind 插件可以选择是否处理指定的 Pod。
如果某 Bind 插件选择处理某 Pod,剩余的 Bind 插件将被跳过 。
PostBind 这是个信息性的扩展点。
PostBind 插件在 Pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。
Unreserve 这是个信息性的扩展点。
如果 Pod 被保留,然后在后面的阶段中被拒绝,则 Unreserve 插件将被通知。
Unreserve 插件应该清楚保留 Pod 的相关状态。
使用此扩展点的插件通常也使用 Reserve 。
插件 API 插件 API 分为两个步骤。首先,插件必须完成注册并配置,然后才能使用扩展点接口。
扩展点接口具有以下形式。
type Plugin interface {
Name () string
}
type QueueSortPlugin interface {
Plugin
Less (* v1.pod, * v1.pod) bool
}
type PreFilterPlugin interface {
Plugin
PreFilter (context.Context, * framework.CycleState, * v1.pod) error
}
// ...
插件配置 你可以在调度器配置中启用或禁用插件。
如果你在使用 Kubernetes v1.18 或更高版本,大部分调度
插件
都在使用中且默认启用。
除了默认的插件,你还可以实现自己的调度插件并且将它们与默认插件一起配置。
你可以访问 scheduler-plugins
了解更多信息。
如果你正在使用 Kubernetes v1.18 或更高版本,你可以将一组插件设置为
一个调度器配置文件,然后定义不同的配置文件来满足各类工作负载。
了解更多关于多配置文件 。
3.11.7 - 调度器性能调优 特性状态: Kubernetes 1.14 [beta]
作为 kubernetes 集群的默认调度器,
kube-scheduler
主要负责将 Pod 调度到集群的 Node 上。
在一个集群中,满足一个 Pod 调度请求的所有 Node 称之为 可调度 Node。
调度器先在集群中找到一个 Pod 的可调度 Node,然后根据一系列函数对这些可调度 Node 打分,
之后选出其中得分最高的 Node 来运行 Pod。
最后,调度器将这个调度决定告知 kube-apiserver,这个过程叫做 绑定(Binding) 。
这篇文章将会介绍一些在大规模 Kubernetes 集群下调度器性能优化的方式。
在大规模集群中,你可以调节调度器的表现来平衡调度的延迟(新 Pod 快速就位)
和精度(调度器很少做出糟糕的放置决策)。
你可以通过设置 kube-scheduler 的 percentageOfNodesToScore
来配置这个调优设置。
这个 KubeSchedulerConfiguration 设置决定了调度集群中节点的阈值。
设置阈值 percentageOfNodesToScore
选项接受从 0 到 100 之间的整数值。
0 值比较特殊,表示 kube-scheduler 应该使用其编译后的默认值。
如果你设置 percentageOfNodesToScore
的值超过了 100,
kube-scheduler 的表现等价于设置值为 100。
要修改这个值,先编辑 kube-scheduler 的配置文件
然后重启调度器。
大多数情况下,这个配置文件是 /etc/kubernetes/config/kube-scheduler.yaml
。
修改完成后,你可以执行
kubectl get pods -n kube-system | grep kube-scheduler
来检查该 kube-scheduler 组件是否健康。
节点打分阈值 要提升调度性能,kube-scheduler 可以在找到足够的可调度节点之后停止查找。
在大规模集群中,比起考虑每个节点的简单方法相比可以节省时间。
你可以使用整个集群节点总数的百分比作为阈值来指定需要多少节点就足够。
kube-scheduler 会将它转换为节点数的整数值。在调度期间,如果
kube-scheduler 已确认的可调度节点数足以超过了配置的百分比数量,
kube-scheduler 将停止继续查找可调度节点并继续进行
打分阶段 。
调度器如何遍历节点 详细介绍了这个过程。
默认阈值 如果你不指定阈值,Kubernetes 使用线性公式计算出一个比例,在 100-节点集群
下取 50%,在 5000-节点的集群下取 10%。这个自动设置的参数的最低值是 5%。
这意味着,调度器至少会对集群中 5% 的节点进行打分,除非用户将该参数设置的低于 5。
如果你想让调度器对集群内所有节点进行打分,则将 percentageOfNodesToScore
设置为 100。
示例 下面就是一个将 percentageOfNodesToScore
参数设置为 50% 的例子。
apiVersion : kubescheduler.config.k8s.io/v1alpha1
kind : KubeSchedulerConfiguration
algorithmSource :
provider : DefaultProvider
...
percentageOfNodesToScore : 50
调节 percentageOfNodesToScore 参数 percentageOfNodesToScore
的值必须在 1 到 100 之间,而且其默认值是通过集群的规模计算得来的。
另外,还有一个 50 个 Node 的最小值是硬编码在程序中。
值得注意的是,该参数设置后可能会导致只有集群中少数节点被选为可调度节点,
很多节点都没有进入到打分阶段。这样就会造成一种后果,
一个本来可以在打分阶段得分很高的节点甚至都不能进入打分阶段。
由于这个原因,这个参数不应该被设置成一个很低的值。
通常的做法是不会将这个参数的值设置的低于 10。
很低的参数值一般在调度器的吞吐量很高且对节点的打分不重要的情况下才使用。
换句话说,只有当你更倾向于在可调度节点中任意选择一个节点来运行这个 Pod 时,
才使用很低的参数设置。
调度器做调度选择的时候如何覆盖所有的 Node 如果你想要理解这一个特性的内部细节,那么请仔细阅读这一章节。
在将 Pod 调度到节点上时,为了让集群中所有节点都有公平的机会去运行这些 Pod,
调度器将会以轮询的方式覆盖全部的 Node。
你可以将 Node 列表想象成一个数组。调度器从数组的头部开始筛选可调度节点,
依次向后直到可调度节点的数量达到 percentageOfNodesToScore
参数的要求。
在对下一个 Pod 进行调度的时候,前一个 Pod 调度筛选停止的 Node 列表的位置,
将会来作为这次调度筛选 Node 开始的位置。
如果集群中的 Node 在多个区域,那么调度器将从不同的区域中轮询 Node,
来确保不同区域的 Node 接受可调度性检查。如下例,考虑两个区域中的六个节点:
Zone 1: Node 1, Node 2, Node 3, Node 4
Zone 2: Node 5, Node 6
调度器将会按照如下的顺序去评估 Node 的可调度性:
Node 1, Node 5, Node 2, Node 6, Node 3, Node 4
在评估完所有 Node 后,将会返回到 Node 1,从头开始。
接下来 3.11.8 - 扩展资源的资源装箱 在 kube-scheduler 的调度插件
NodeResourcesFit
中存在两种支持资源装箱(bin packing)的策略:MostAllocated
和
RequestedToCapacityRatio
。
使用 MostAllocated 策略启用资源装箱 MostAllocated
策略基于资源的利用率来为节点计分,优选分配比率较高的节点。
针对每种资源类型,你可以设置一个权重值以改变其对节点得分的影响。
要为插件 NodeResourcesFit
设置 MostAllocated
策略,
可以使用一个类似于下面这样的调度器配置 :
apiVersion : kubescheduler.config.k8s.io/v1beta3
kind : KubeSchedulerConfiguration
profiles :
- pluginConfig :
- args :
scoringStrategy :
resources :
- name : cpu
weight : 1
- name : memory
weight : 1
- name : intel.com/foo
weight : 3
- name : intel.com/bar
weight : 3
type : MostAllocated
name : NodeResourcesFit
要进一步了解其它参数及其默认配置,请参阅
NodeResourcesFitArgs
的 API 文档。
使用 RequestedToCapacityRatio 策略来启用资源装箱 RequestedToCapacityRatio
策略允许用户基于请求值与容量的比率,针对参与节点计分的每类资源设置权重。
这一策略使得用户可以使用合适的参数来对扩展资源执行装箱操作,进而提升大规模集群中稀有资源的利用率。
此策略根据所分配资源的一个配置函数来评价节点。
NodeResourcesFit
计分函数中的 RequestedToCapacityRatio
可以通过字段
scoringStrategy
来控制。
在 scoringStrategy
字段中,你可以配置两个参数:requestedToCapacityRatio
和 resources
。requestedToCapacityRatio
参数中的 shape
设置使得用户能够调整函数的算法,基于 utilization
和 score
值计算最少请求或最多请求。
resources
参数中包含计分过程中需要考虑的资源的 name
,以及用来设置每种资源权重的 weight
。
下面是一个配置示例,使用 requestedToCapacityRatio
字段为扩展资源 intel.com/foo
和 intel.com/bar
设置装箱行为:
apiVersion : kubescheduler.config.k8s.io/v1beta3
kind : KubeSchedulerConfiguration
profiles :
- pluginConfig :
- args :
scoringStrategy :
resources :
- name : intel.com/foo
weight : 3
- name : intel.com/bar
weight : 3
requestedToCapacityRatio :
shape :
- utilization : 0
score : 0
- utilization : 100
score : 10
type : RequestedToCapacityRatio
name : NodeResourcesFit
使用 kube-scheduler 标志 --config=/path/to/config/file
引用 KubeSchedulerConfiguration
文件,可以将配置传递给调度器。
要进一步了解其它参数及其默认配置,可以参阅
NodeResourcesFitArgs
的 API 文档。
调整计分函数 shape
用于指定 RequestedToCapacityRatio
函数的行为。
shape :
- utilization : 0
score : 0
- utilization : 100
score : 10
上面的参数在 utilization
为 0% 时给节点评分为 0,在 utilization
为
100% 时给节点评分为 10,因此启用了装箱行为。
要启用最少请求(least requested)模式,必须按如下方式反转得分值。
shape :
- utilization : 0
score : 10
- utilization : 100
score : 0
resources
是一个可选参数,默认情况下设置为:
resources :
- name : cpu
weight : 1
- name : memory
weight : 1
它可以像下面这样用来添加扩展资源:
resources :
- name : intel.com/foo
weight : 5
- name : cpu
weight : 3
- name : memory
weight : 1
weight
参数是可选的,如果未指定,则设置为 1。
同时,weight
不能设置为负值。
节点容量分配的评分 本节适用于希望了解此功能的内部细节的人员。
以下是如何针对给定的一组值来计算节点得分的示例。
请求的资源:
intel.com/foo : 2
memory: 256MB
cpu: 2
资源权重:
intel.com/foo : 5
memory: 1
cpu: 3
FunctionShapePoint {{0, 0}, {100, 10}}
节点 1 配置:
可用:
intel.com/foo : 4
memory : 1 GB
cpu: 8
已用:
intel.com/foo: 1
memory: 256MB
cpu: 1
节点得分:
intel.com/foo = resourceScoringFunction((2+1),4)
= (100 - ((4-3)*100/4)
= (100 - 25)
= 75 # requested + used = 75% * available
= rawScoringFunction(75)
= 7 # floor(75/10)
memory = resourceScoringFunction((256+256),1024)
= (100 -((1024-512)*100/1024))
= 50 # requested + used = 50% * available
= rawScoringFunction(50)
= 5 # floor(50/10)
cpu = resourceScoringFunction((2+1),8)
= (100 -((8-3)*100/8))
= 37.5 # requested + used = 37.5% * available
= rawScoringFunction(37.5)
= 3 # floor(37.5/10)
NodeScore = (7 * 5) + (5 * 1) + (3 * 3) / (5 + 1 + 3)
= 5
节点 2 配置:
可用:
intel.com/foo: 8
memory: 1GB
cpu: 8
已用:
intel.com/foo: 2
memory: 512MB
cpu: 6
节点得分:
intel.com/foo = resourceScoringFunction((2+2),8)
= (100 - ((8-4)*100/8)
= (100 - 50)
= 50
= rawScoringFunction(50)
= 5
memory = resourceScoringFunction((256+512),1024)
= (100 -((1024-768)*100/1024))
= 75
= rawScoringFunction(75)
= 7
cpu = resourceScoringFunction((2+6),8)
= (100 -((8-8)*100/8))
= 100
= rawScoringFunction(100)
= 10
NodeScore = (5 * 5) + (7 * 1) + (10 * 3) / (5 + 1 + 3)
= 7
接下来 3.11.9 - Pod 优先级和抢占 特性状态: Kubernetes v1.14 [stable]
Pod 可以有优先级 。
优先级表示一个 Pod 相对于其他 Pod 的重要性。
如果一个 Pod 无法被调度,调度程序会尝试抢占(驱逐)较低优先级的 Pod,
以使悬决 Pod 可以被调度。
警告: 在一个并非所有用户都是可信的集群中,恶意用户可能以最高优先级创建 Pod,
导致其他 Pod 被驱逐或者无法被调度。
管理员可以使用 ResourceQuota 来阻止用户创建高优先级的 Pod。
参见默认限制优先级消费 。
如何使用优先级和抢占 要使用优先级和抢占:
新增一个或多个 PriorityClass 。
创建 Pod,并将其 priorityClassName
设置为新增的 PriorityClass。
当然你不需要直接创建 Pod;通常,你将会添加 priorityClassName
到集合对象(如 Deployment)
的 Pod 模板中。
继续阅读以获取有关这些步骤的更多信息。
说明: Kubernetes 已经提供了 2 个 PriorityClass:
system-cluster-critical
和 system-node-critical
。
这些是常见的类,用于确保始终优先调度关键组件 。
PriorityClass PriorityClass 是一个无命名空间对象,它定义了从优先级类名称到优先级整数值的映射。
名称在 PriorityClass 对象元数据的 name
字段中指定。
值在必填的 value
字段中指定。值越大,优先级越高。
PriorityClass 对象的名称必须是有效的
DNS 子域名 ,
并且它不能以 system-
为前缀。
PriorityClass 对象可以设置任何小于或等于 10 亿的 32 位整数值。
较大的数字是为通常不应被抢占或驱逐的关键的系统 Pod 所保留的。
集群管理员应该为这类映射分别创建独立的 PriorityClass 对象。
PriorityClass 还有两个可选字段:globalDefault
和 description
。
globalDefault
字段表示这个 PriorityClass 的值应该用于没有 priorityClassName
的 Pod。
系统中只能存在一个 globalDefault
设置为 true 的 PriorityClass。
如果不存在设置了 globalDefault
的 PriorityClass,
则没有 priorityClassName
的 Pod 的优先级为零。
description
字段是一个任意字符串。
它用来告诉集群用户何时应该使用此 PriorityClass。
关于 PodPriority 和现有集群的注意事项 如果你升级一个已经存在的但尚未使用此特性的集群,该集群中已经存在的 Pod 的优先级等效于零。
添加一个将 globalDefault
设置为 true
的 PriorityClass 不会改变现有 Pod 的优先级。
此类 PriorityClass 的值仅用于添加 PriorityClass 后创建的 Pod。
如果你删除了某个 PriorityClass 对象,则使用被删除的 PriorityClass 名称的现有 Pod 保持不变,
但是你不能再创建使用已删除的 PriorityClass 名称的 Pod。
PriorityClass 示例 apiVersion : scheduling.k8s.io/v1
kind : PriorityClass
metadata :
name : high-priority
value : 1000000
globalDefault : false
description : "此优先级类应仅用于 XYZ 服务 Pod。"
非抢占式 PriorityClass 特性状态: Kubernetes v1.24 [stable]
配置了 preemptionPolicy: Never
的 Pod 将被放置在调度队列中较低优先级 Pod 之前,
但它们不能抢占其他 Pod。等待调度的非抢占式 Pod 将留在调度队列中,直到有足够的可用资源,
它才可以被调度。非抢占式 Pod,像其他 Pod 一样,受调度程序回退的影响。
这意味着如果调度程序尝试这些 Pod 并且无法调度它们,它们将以更低的频率被重试,
从而允许其他优先级较低的 Pod 排在它们之前。
非抢占式 Pod 仍可能被其他高优先级 Pod 抢占。
preemptionPolicy
默认为 PreemptLowerPriority
,
这将允许该 PriorityClass 的 Pod 抢占较低优先级的 Pod(现有默认行为也是如此)。
如果 preemptionPolicy
设置为 Never
,则该 PriorityClass 中的 Pod 将是非抢占式的。
数据科学工作负载是一个示例用例。用户可以提交他们希望优先于其他工作负载的作业,
但不希望因为抢占运行中的 Pod 而导致现有工作被丢弃。
设置为 preemptionPolicy: Never
的高优先级作业将在其他排队的 Pod 之前被调度,
只要足够的集群资源“自然地”变得可用。
非抢占式 PriorityClass 示例 apiVersion : scheduling.k8s.io/v1
kind : PriorityClass
metadata :
name : high-priority-nonpreempting
value : 1000000
preemptionPolicy : Never
globalDefault : false
description : "This priority class will not cause other pods to be preempted."
Pod 优先级 在你拥有一个或多个 PriorityClass 对象之后,
你可以创建在其规约中指定这些 PriorityClass 名称之一的 Pod。
优先级准入控制器使用 priorityClassName
字段并填充优先级的整数值。
如果未找到所指定的优先级类,则拒绝 Pod。
以下 YAML 是 Pod 配置的示例,它使用在前面的示例中创建的 PriorityClass。
优先级准入控制器检查 Pod 规约并将其优先级解析为 1000000。
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
env : test
spec :
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
priorityClassName : high-priority
Pod 优先级对调度顺序的影响 当启用 Pod 优先级时,调度程序会按优先级对悬决 Pod 进行排序,
并且每个悬决的 Pod 会被放置在调度队列中其他优先级较低的悬决 Pod 之前。
因此,如果满足调度要求,较高优先级的 Pod 可能会比具有较低优先级的 Pod 更早调度。
如果无法调度此类 Pod,调度程序将继续并尝试调度其他较低优先级的 Pod。
抢占 Pod 被创建后会进入队列等待调度。
调度器从队列中挑选一个 Pod 并尝试将它调度到某个节点上。
如果没有找到满足 Pod 的所指定的所有要求的节点,则触发对悬决 Pod 的抢占逻辑。
让我们将悬决 Pod 称为 P。抢占逻辑试图找到一个节点,
在该节点中删除一个或多个优先级低于 P 的 Pod,则可以将 P 调度到该节点上。
如果找到这样的节点,一个或多个优先级较低的 Pod 会被从节点中驱逐。
被驱逐的 Pod 消失后,P 可以被调度到该节点上。
当 Pod P 抢占节点 N 上的一个或多个 Pod 时,
Pod P 状态的 nominatedNodeName
字段被设置为节点 N 的名称。
该字段帮助调度程序跟踪为 Pod P 保留的资源,并为用户提供有关其集群中抢占的信息。
请注意,Pod P 不一定会调度到“被提名的节点(Nominated Node)”。
调度程序总是在迭代任何其他节点之前尝试“指定节点”。
在 Pod 因抢占而牺牲时,它们将获得体面终止期。
如果调度程序正在等待牺牲者 Pod 终止时另一个节点变得可用,
则调度程序可以使用另一个节点来调度 Pod P。
因此,Pod 规约中的 nominatedNodeName
和 nodeName
并不总是相同。
此外,如果调度程序抢占节点 N 上的 Pod,但随后比 Pod P 更高优先级的 Pod 到达,
则调度程序可能会将节点 N 分配给新的更高优先级的 Pod。
在这种情况下,调度程序会清除 Pod P 的 nominatedNodeName
。
通过这样做,调度程序使 Pod P 有资格抢占另一个节点上的 Pod。
抢占的限制 被抢占牺牲者的体面终止 当 Pod 被抢占时,牺牲者会得到他们的
体面终止期 。
它们可以在体面终止期内完成工作并退出。如果它们不这样做就会被杀死。
这个体面终止期在调度程序抢占 Pod 的时间点和待处理的 Pod (P)
可以在节点 (N) 上调度的时间点之间划分出了一个时间跨度。
同时,调度器会继续调度其他待处理的 Pod。当牺牲者退出或被终止时,
调度程序会尝试在待处理队列中调度 Pod。
因此,调度器抢占牺牲者的时间点与 Pod P 被调度的时间点之间通常存在时间间隔。
为了最小化这个差距,可以将低优先级 Pod 的体面终止时间设置为零或一个小数字。
支持 PodDisruptionBudget,但不保证 PodDisruptionBudget
(PDB) 允许多副本应用程序的所有者限制因自愿性质的干扰而同时终止的 Pod 数量。
Kubernetes 在抢占 Pod 时支持 PDB,但对 PDB 的支持是基于尽力而为原则的。
调度器会尝试寻找不会因被抢占而违反 PDB 的牺牲者,但如果没有找到这样的牺牲者,
抢占仍然会发生,并且即使违反了 PDB 约束也会删除优先级较低的 Pod。
与低优先级 Pod 之间的 Pod 间亲和性 只有当这个问题的答案是肯定的时,才考虑在一个节点上执行抢占操作:
“如果从此节点上删除优先级低于悬决 Pod 的所有 Pod,悬决 Pod 是否可以在该节点上调度?”
说明: 抢占并不一定会删除所有较低优先级的 Pod。
如果悬决 Pod 可以通过删除少于所有较低优先级的 Pod 来调度,
那么只有一部分较低优先级的 Pod 会被删除。
即便如此,上述问题的答案必须是肯定的。
如果答案是否定的,则不考虑在该节点上执行抢占。
如果悬决 Pod 与节点上的一个或多个较低优先级 Pod 具有 Pod 间亲和性 ,
则在没有这些较低优先级 Pod 的情况下,无法满足 Pod 间亲和性规则。
在这种情况下,调度程序不会抢占节点上的任何 Pod。
相反,它寻找另一个节点。调度程序可能会找到合适的节点,
也可能不会。无法保证悬决 Pod 可以被调度。
我们针对此问题推荐的解决方案是仅针对同等或更高优先级的 Pod 设置 Pod 间亲和性。
跨节点抢占 假设正在考虑在一个节点 N 上执行抢占,以便可以在 N 上调度待处理的 Pod P。
只有当另一个节点上的 Pod 被抢占时,P 才可能在 N 上变得可行。
下面是一个例子:
正在考虑将 Pod P 调度到节点 N 上。 Pod Q 正在与节点 N 位于同一区域的另一个节点上运行。 Pod P 与 Pod Q 具有 Zone 维度的反亲和(topologyKey:topology.kubernetes.io/zone
)。 Pod P 与 Zone 中的其他 Pod 之间没有其他反亲和性设置。 为了在节点 N 上调度 Pod P,可以抢占 Pod Q,但调度器不会进行跨节点抢占。
因此,Pod P 将被视为在节点 N 上不可调度。 如果将 Pod Q 从所在节点中移除,则不会违反 Pod 间反亲和性约束,
并且 Pod P 可能会被调度到节点 N 上。
如果有足够的需求,并且如果我们找到性能合理的算法,
我们可能会考虑在未来版本中添加跨节点抢占。
故障排除 Pod 优先级和抢占可能会产生不必要的副作用。以下是一些潜在问题的示例以及处理这些问题的方法。
Pod 被不必要地抢占 抢占在资源压力较大时从集群中删除现有 Pod,为更高优先级的悬决 Pod 腾出空间。
如果你错误地为某些 Pod 设置了高优先级,这些无意的高优先级 Pod 可能会导致集群中出现抢占行为。
Pod 优先级是通过设置 Pod 规约中的 priorityClassName
字段来指定的。
优先级的整数值然后被解析并填充到 podSpec
的 priority
字段。
为了解决这个问题,你可以将这些 Pod 的 priorityClassName
更改为使用较低优先级的类,
或者将该字段留空。默认情况下,空的 priorityClassName
解析为零。
当 Pod 被抢占时,集群会为被抢占的 Pod 记录事件。只有当集群没有足够的资源用于 Pod 时,
才会发生抢占。在这种情况下,只有当悬决 Pod(抢占者)的优先级高于受害 Pod 时才会发生抢占。
当没有悬决 Pod,或者悬决 Pod 的优先级等于或低于牺牲者时,不得发生抢占。
如果在这种情况下发生抢占,请提出问题。
有 Pod 被抢占,但抢占者并没有被调度 当 Pod 被抢占时,它们会收到请求的体面终止期,默认为 30 秒。
如果受害 Pod 在此期限内没有终止,它们将被强制终止。
一旦所有牺牲者都离开,就可以调度抢占者 Pod。
在抢占者 Pod 等待牺牲者离开的同时,可能某个适合同一个节点的更高优先级的 Pod 被创建。
在这种情况下,调度器将调度优先级更高的 Pod 而不是抢占者。
这是预期的行为:具有较高优先级的 Pod 应该取代具有较低优先级的 Pod。
优先级较高的 Pod 在优先级较低的 Pod 之前被抢占 调度程序尝试查找可以运行悬决 Pod 的节点。如果没有找到这样的节点,
调度程序会尝试从任意节点中删除优先级较低的 Pod,以便为悬决 Pod 腾出空间。
如果具有低优先级 Pod 的节点无法运行悬决 Pod,
调度器可能会选择另一个具有更高优先级 Pod 的节点(与其他节点上的 Pod 相比)进行抢占。
牺牲者的优先级必须仍然低于抢占者 Pod。
当有多个节点可供执行抢占操作时,调度器会尝试选择具有一组优先级最低的 Pod 的节点。
但是,如果此类 Pod 具有 PodDisruptionBudget,当它们被抢占时,
则会违反 PodDisruptionBudget,那么调度程序可能会选择另一个具有更高优先级 Pod 的节点。
当存在多个节点抢占且上述场景均不适用时,调度器会选择优先级最低的节点。
Pod 优先级和服务质量之间的相互作用 Pod 优先级和 QoS 类
是两个正交特征,交互很少,并且对基于 QoS 类设置 Pod 的优先级没有默认限制。
调度器的抢占逻辑在选择抢占目标时不考虑 QoS。
抢占会考虑 Pod 优先级并尝试选择一组优先级最低的目标。
仅当移除优先级最低的 Pod 不足以让调度程序调度抢占式 Pod,
或者最低优先级的 Pod 受 PodDisruptionBudget 保护时,才会考虑优先级较高的 Pod。
kubelet 使用优先级来确定
节点压力驱逐 Pod 的顺序。
你可以使用 QoS 类来估计 Pod 最有可能被驱逐的顺序。kubelet 根据以下因素对 Pod 进行驱逐排名:
对紧俏资源的使用是否超过请求值 Pod 优先级 相对于请求的资源使用量 有关更多详细信息,请参阅
kubelet 驱逐时 Pod 的选择 。
当某 Pod 的资源用量未超过其请求时,kubelet 节点压力驱逐不会驱逐该 Pod。
如果优先级较低的 Pod 的资源使用量没有超过其请求,则不会被驱逐。
另一个优先级较高且资源使用量超过其请求的 Pod 可能会被驱逐。
接下来 3.11.10 - 节点压力驱逐 节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。
kubelet
监控集群节点的内存、磁盘空间和文件系统的 inode 等资源。
当这些资源中的一个或者多个达到特定的消耗水平,
kubelet 可以主动地使节点上一个或者多个 Pod 失效,以回收资源防止饥饿。
在节点压力驱逐期间,kubelet 将所选 Pod 的 PodPhase
设置为 Failed
。这将终止 Pod。
节点压力驱逐不同于 API 发起的驱逐 。
kubelet 并不理会你配置的 PodDisruptionBudget
或者是 Pod 的 terminationGracePeriodSeconds
。
如果你使用了软驱逐条件 ,kubelet 会考虑你所配置的
eviction-max-pod-grace-period
。
如果你使用了硬驱逐条件 ,它使用 0s
宽限期来终止 Pod。
如果 Pod 是由替换失败 Pod 的工作负载 资源
(例如 StatefulSet
或者 Deployment )管理,
则控制平面或 kube-controller-manager
会创建新的 Pod 来代替被驱逐的 Pod。
说明: kubelet 在终止最终用户 Pod 之前会尝试回收节点级资源 。
例如,它会在磁盘资源不足时删除未使用的容器镜像。
kubelet 使用各种参数来做出驱逐决定,如下所示:
驱逐信号 驱逐信号是特定资源在特定时间点的当前状态。
kubelet 使用驱逐信号,通过将信号与驱逐条件进行比较来做出驱逐决定,
驱逐条件是节点上应该可用资源的最小量。
kubelet 使用以下驱逐信号:
驱逐信号 描述 memory.available
memory.available
:= node.status.capacity[memory]
- node.stats.memory.workingSet
nodefs.available
nodefs.available
:= node.stats.fs.available
nodefs.inodesFree
nodefs.inodesFree
:= node.stats.fs.inodesFree
imagefs.available
imagefs.available
:= node.stats.runtime.imagefs.available
imagefs.inodesFree
imagefs.inodesFree
:= node.stats.runtime.imagefs.inodesFree
pid.available
pid.available
:= node.stats.rlimit.maxpid
- node.stats.rlimit.curproc
在上表中,描述
列显示了 kubelet 如何获取信号的值。每个信号支持百分比值或者是字面值。
kubelet 计算相对于与信号有关的总量的百分比值。
memory.available
的值来自 cgroupfs,而不是像 free -m
这样的工具。
这很重要,因为 free -m
在容器中不起作用,如果用户使用
节点可分配资源
这一功能特性,资源不足的判定是基于 cgroup 层次结构中的用户 Pod 所处的局部及 cgroup 根节点作出的。
这个脚本
重现了 kubelet 为计算 memory.available
而执行的相同步骤。
kubelet 在其计算中排除了 inactive_file(即非活动 LRU 列表上基于文件来虚拟的内存的字节数),
因为它假定在压力下内存是可回收的。
kubelet 支持以下文件系统分区:
nodefs
:节点的主要文件系统,用于本地磁盘卷、emptyDir、日志存储等。
例如,nodefs
包含 /var/lib/kubelet/
。imagefs
:可选文件系统,供容器运行时存储容器镜像和容器可写层。kubelet 会自动发现这些文件系统并忽略其他文件系统。kubelet 不支持其他配置。
一些 kubelet 垃圾收集功能已被弃用,以鼓励使用驱逐机制。
现有标志 新的标志 原因 --image-gc-high-threshold
--eviction-hard
或 --eviction-soft
现有的驱逐信号可以触发镜像垃圾收集 --image-gc-low-threshold
--eviction-minimum-reclaim
驱逐回收具有相同的行为 --maximum-dead-containers
- 一旦旧的日志存储在容器的上下文之外就会被弃用 --maximum-dead-containers-per-container
- 一旦旧的日志存储在容器的上下文之外就会被弃用 --minimum-container-ttl-duration
- 一旦旧的日志存储在容器的上下文之外就会被弃用
驱逐条件 你可以为 kubelet 指定自定义驱逐条件,以便在作出驱逐决定时使用。
驱逐条件的形式为 [eviction-signal][operator][quantity]
,其中:
eviction-signal
是要使用的驱逐信号 。operator
是你想要的关系运算符 ,
比如 <
(小于)。quantity
是驱逐条件数量,例如 1Gi
。
quantity
的值必须与 Kubernetes 使用的数量表示相匹配。
你可以使用文字值或百分比(%
)。例如,如果一个节点的总内存为 10Gi 并且你希望在可用内存低于 1Gi 时触发驱逐,
则可以将驱逐条件定义为 memory.available<10%
或 memory.available< 1G
。
你不能同时使用二者。
你可以配置软和硬驱逐条件。
软驱逐条件 软驱逐条件将驱逐条件与管理员所必须指定的宽限期配对。
在超过宽限期之前,kubelet 不会驱逐 Pod。
如果没有指定的宽限期,kubelet 会在启动时返回错误。
你可以既指定软驱逐条件宽限期,又指定 Pod 终止宽限期的上限,,给 kubelet 在驱逐期间使用。
如果你指定了宽限期的上限并且 Pod 满足软驱逐阈条件,则 kubelet 将使用两个宽限期中的较小者。
如果你没有指定宽限期上限,kubelet 会立即杀死被驱逐的 Pod,不允许其体面终止。
你可以使用以下标志来配置软驱逐条件:
eviction-soft
:一组驱逐条件,如 memory.available<1.5Gi
,
如果驱逐条件持续时长超过指定的宽限期,可以触发 Pod 驱逐。eviction-soft-grace-period
:一组驱逐宽限期,
如 memory.available=1m30s
,定义软驱逐条件在触发 Pod 驱逐之前必须保持多长时间。eviction-max-pod-grace-period
:在满足软驱逐条件而终止 Pod 时使用的最大允许宽限期(以秒为单位)。硬驱逐条件 硬驱逐条件没有宽限期。当达到硬驱逐条件时,
kubelet 会立即杀死 pod,而不会正常终止以回收紧缺的资源。
你可以使用 eviction-hard
标志来配置一组硬驱逐条件,
例如 memory.available<1Gi
。
kubelet 具有以下默认硬驱逐条件:
memory.available<100Mi
nodefs.available<10%
imagefs.available<15%
nodefs.inodesFree<5%
(Linux 节点)只有在没有更改任何参数的情况下,硬驱逐阈值才会被设置成这些默认值。
如果你更改了任何参数的值,则其他参数的取值不会继承其默认值设置,而将被设置为零。
为了提供自定义值,你应该分别设置所有阈值。
驱逐监测间隔 kubelet 根据其配置的 housekeeping-interval
(默认为 10s
)评估驱逐条件。
节点条件 kubelet 报告节点状况以反映节点处于压力之下,因为满足硬或软驱逐条件,与配置的宽限期无关。
kubelet 根据下表将驱逐信号映射为节点状况:
节点条件 驱逐信号 描述 MemoryPressure
memory.available
节点上的可用内存已满足驱逐条件 DiskPressure
nodefs.available
、nodefs.inodesFree
、imagefs.available
或 imagefs.inodesFree
节点的根文件系统或镜像文件系统上的可用磁盘空间和 inode 已满足驱逐条件 PIDPressure
pid.available
(Linux) 节点上的可用进程标识符已低于驱逐条件
kubelet 根据配置的 --node-status-update-frequency
更新节点条件,默认为 10s
。
节点条件振荡 在某些情况下,节点在软驱逐条件上下振荡,而没有保持定义的宽限期。
这会导致报告的节点条件在 true
和 false
之间不断切换,从而导致错误的驱逐决策。
为了防止振荡,你可以使用 eviction-pressure-transition-period
标志,
该标志控制 kubelet 在将节点条件转换为不同状态之前必须等待的时间。
过渡期的默认值为 5m
。
回收节点级资源 kubelet 在驱逐最终用户 Pod 之前会先尝试回收节点级资源。
当报告 DiskPressure
节点状况时,kubelet 会根据节点上的文件系统回收节点级资源。
有 imagefs
如果节点有一个专用的 imagefs
文件系统供容器运行时使用,kubelet 会执行以下操作:
如果 nodefs
文件系统满足驱逐条件,kubelet 垃圾收集死亡 Pod 和容器。 如果 imagefs
文件系统满足驱逐条件,kubelet 将删除所有未使用的镜像。 没有 imagefs
如果节点只有一个满足驱逐条件的 nodefs
文件系统,
kubelet 按以下顺序释放磁盘空间:
对死亡的 Pod 和容器进行垃圾收集 删除未使用的镜像 kubelet 驱逐时 Pod 的选择 如果 kubelet 回收节点级资源的尝试没有使驱逐信号低于条件,
则 kubelet 开始驱逐最终用户 Pod。
kubelet 使用以下参数来确定 Pod 驱逐顺序:
Pod 的资源使用是否超过其请求 Pod 优先级 Pod 相对于请求的资源使用情况 因此,kubelet 按以下顺序排列和驱逐 Pod:
首先考虑资源使用量超过其请求的 BestEffort
或 Burstable
Pod。
这些 Pod 会根据它们的优先级以及它们的资源使用级别超过其请求的程度被逐出。 资源使用量少于请求量的 Guaranteed
Pod 和 Burstable
Pod 根据其优先级被最后驱逐。 说明: kubelet 不使用 Pod 的 QoS 类来确定驱逐顺序。
在回收内存等资源时,你可以使用 QoS 类来估计最可能的 Pod 驱逐顺序。
QoS 不适用于临时存储(EphemeralStorage)请求,
因此如果节点在 DiskPressure
下,则上述场景将不适用。
仅当 Guaranteed
Pod 中所有容器都被指定了请求和限制并且二者相等时,才保证 Pod 不被驱逐。
这些 Pod 永远不会因为另一个 Pod 的资源消耗而被驱逐。
如果系统守护进程(例如 kubelet
和 journald
)
消耗的资源比通过 system-reserved
或 kube-reserved
分配保留的资源多,
并且该节点只有 Guaranteed
或 Burstable
Pod 使用的资源少于其上剩余的请求,
那么 kubelet 必须选择驱逐这些 Pod 中的一个以保持节点稳定性并减少资源匮乏对其他 Pod 的影响。
在这种情况下,它会选择首先驱逐最低优先级的 Pod。
当 kubelet 因 inode 或 PID 不足而驱逐 Pod 时,
它使用优先级来确定驱逐顺序,因为 inode 和 PID 没有请求。
kubelet 根据节点是否具有专用的 imagefs
文件系统对 Pod 进行不同的排序:
有 imagefs
如果 nodefs
触发驱逐,
kubelet 会根据 nodefs
使用情况(本地卷 + 所有容器的日志
)对 Pod 进行排序。
如果 imagefs
触发驱逐,kubelet 会根据所有容器的可写层使用情况对 Pod 进行排序。
没有 imagefs
如果 nodefs
触发驱逐,
kubelet 会根据磁盘总用量(本地卷 + 日志和所有容器的可写层
)对 Pod 进行排序。
最小驱逐回收 在某些情况下,驱逐 Pod 只会回收少量的紧俏资源。
这可能导致 kubelet 反复达到配置的驱逐条件并触发多次驱逐。
你可以使用 --eviction-minimum-reclaim
标志或
kubelet 配置文件
为每个资源配置最小回收量。
当 kubelet 注意到某个资源耗尽时,它会继续回收该资源,直到回收到你所指定的数量为止。
例如,以下配置设置最小回收量:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
evictionHard :
memory.available : "500Mi"
nodefs.available : "1Gi"
imagefs.available : "100Gi"
evictionMinimumReclaim :
memory.available : "0Mi"
nodefs.available : "500Mi"
imagefs.available : "2Gi"
在这个例子中,如果 nodefs.available
信号满足驱逐条件,
kubelet 会回收资源,直到信号达到 1Gi
的条件,
然后继续回收至少 500Mi
直到信号达到 1.5Gi
。
类似地,kubelet 会回收 imagefs
资源,直到 imagefs.available
信号达到 102Gi
。
对于所有资源,默认的 eviction-minimum-reclaim
为 0
。
节点内存不足行为 如果节点在 kubelet 能够回收内存之前遇到内存不足(OOM)事件,
则节点依赖 oom_killer 来响应。
kubelet 根据 Pod 的服务质量(QoS)为每个容器设置一个 oom_score_adj
值。
服务质量 oom_score_adj Guaranteed
-997 BestEffort
1000 Burstable
min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
说明: kubelet 还将具有 system-node-critical
优先级
的 Pod 中的容器 oom_score_adj
值设为 -997
。
如果 kubelet 在节点遇到 OOM 之前无法回收内存,
则 oom_killer
根据它在节点上使用的内存百分比计算 oom_score
,
然后加上 oom_score_adj
得到每个容器有效的 oom_score
。
然后它会杀死得分最高的容器。
这意味着低 QoS Pod 中相对于其调度请求消耗内存较多的容器,将首先被杀死。
与 Pod 驱逐不同,如果容器被 OOM 杀死,
kubelet
可以根据其 RestartPolicy
重新启动它。
最佳实践 以下部分描述了驱逐配置的最佳实践。
可调度的资源和驱逐策略 当你为 kubelet 配置驱逐策略时,
你应该确保调度程序不会在 Pod 触发驱逐时对其进行调度,因为这类 Pod 会立即引起内存压力。
考虑以下场景:
节点内存容量:10Gi
操作员希望为系统守护进程(内核、kubelet
等)保留 10% 的内存容量 操作员希望在节点内存利用率达到 95% 以上时驱逐 Pod,以减少系统 OOM 的概率。 为此,kubelet 启动设置如下:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
在此配置中,--system-reserved
标志为系统预留了 1.5Gi
的内存,
即 总内存的 10% + 驱逐条件量
。
如果 Pod 使用的内存超过其请求值或者系统使用的内存超过 1Gi
,
则节点可以达到驱逐条件,这使得 memory.available
信号低于 500Mi
并触发条件。
DaemonSet Pod 优先级是做出驱逐决定的主要因素。
如果你不希望 kubelet 驱逐属于 DaemonSet
的 Pod,
请在 Pod 规约中为这些 Pod 提供足够高的 priorityClass
。
你还可以使用优先级较低的 priorityClass
或默认配置,
仅在有足够资源时才运行 DaemonSet
Pod。
已知问题 以下部分描述了与资源不足处理相关的已知问题。
kubelet 可能不会立即观察到内存压力 默认情况下,kubelet 轮询 cAdvisor
以定期收集内存使用情况统计信息。
如果该轮询时间窗口内内存使用量迅速增加,kubelet 可能无法足够快地观察到 MemoryPressure
,
但是 OOMKiller
仍将被调用。
你可以使用 --kernel-memcg-notification
标志在 kubelet 上启用 memcg
通知 API,以便在超过条件时立即收到通知。
如果你不是追求极端利用率,而是要采取合理的过量使用措施,
则解决此问题的可行方法是使用 --kube-reserved
和 --system-reserved
标志为系统分配内存。
active_file 内存未被视为可用内存 在 Linux 上,内核跟踪活动 LRU 列表上的基于文件所虚拟的内存字节数作为 active_file
统计信息。
kubelet 将 active_file
内存区域视为不可回收。
对于大量使用块设备形式的本地存储(包括临时本地存储)的工作负载,
文件和块数据的内核级缓存意味着许多最近访问的缓存页面可能被计为 active_file
。
如果这些内核块缓冲区中在活动 LRU 列表上有足够多,
kubelet 很容易将其视为资源用量过量并为节点设置内存压力污点,从而触发 Pod 驱逐。
更多细节请参见 https://github.com/kubernetes/kubernetes/issues/43916 。
你可以通过为可能执行 I/O 密集型活动的容器设置相同的内存限制和内存请求来应对该行为。
你将需要估计或测量该容器的最佳内存限制值。
接下来 3.11.11 - API 发起的驱逐 API 发起的驱逐是一个先调用
Eviction API
创建 Eviction
对象,再由该对象体面地中止 Pod 的过程。
你可以通过直接调用 Eviction API 发起驱逐,也可以通过编程的方式使用
API 服务器 的客户端来发起驱逐,
比如 kubectl drain
命令。
此操作创建一个 Eviction
对象,该对象再驱动 API 服务器终止选定的 Pod。
API 发起的驱逐将遵从你的
PodDisruptionBudgets
和 terminationGracePeriodSeconds
配置。
使用 API 创建 Eviction 对象,就像对 Pod 执行策略控制的
DELETE
操作
调用 Eviction API 你可以使用 Kubernetes 语言客户端
来访问 Kubernetes API 并创建 Eviction
对象。
要执行此操作,你应该用 POST 发出要尝试的请求,类似于下面的示例:
说明: policy/v1
版本的 Eviction 在 v1.22 以及更高的版本中可用,之前的发行版本使用 policy/v1beta1
版本。
{
"apiVersion" : "policy/v1" ,
"kind" : "Eviction" ,
"metadata" : {
"name" : "quux" ,
"namespace" : "default"
}
}
说明: 在 v1.22 版本废弃以支持 policy/v1
{
"apiVersion" : "policy/v1beta1" ,
"kind" : "Eviction" ,
"metadata" : {
"name" : "quux" ,
"namespace" : "default"
}
}
或者,你可以通过使用 curl
或者 wget
来访问 API 以尝试驱逐操作,类似于以下示例:
curl -v -H 'Content-type: application/json' https://your-cluster-api-endpoint.example/api/v1/namespaces/default/pods/quux/eviction -d @eviction.json
API 发起驱逐的工作原理 当你使用 API 来请求驱逐时,API 服务器将执行准入检查,并通过以下方式之一做出响应:
200 OK
:允许驱逐,子资源 Eviction
被创建,并且 Pod 被删除,
类似于发送一个 DELETE
请求到 Pod 地址。429 Too Many Requests
:当前不允许驱逐,因为配置了 PodDisruptionBudget 。
你可以稍后再尝试驱逐。你也可能因为 API 速率限制而看到这种响应。500 Internal Server Error
:不允许驱逐,因为存在配置错误,
例如存在多个 PodDisruptionBudgets 引用同一个 Pod。如果你想驱逐的 Pod 不属于有 PodDisruptionBudget 的工作负载,
API 服务器总是返回 200 OK
并且允许驱逐。
如果 API 服务器允许驱逐,Pod 按照如下方式删除:
API 服务器中的 Pod
资源会更新上删除时间戳,之后 API 服务器会认为此 Pod
资源将被终止。
此 Pod
资源还会标记上配置的宽限期。 本地运行状态的 Pod 所处的节点上的 kubelet
注意到 Pod
资源被标记为终止,并开始优雅停止本地 Pod。 当 kubelet 停止 Pod 时,控制面从 Endpoint
和 EndpointSlice
对象中移除该 Pod。因此,控制器不再将此 Pod 视为有用对象。 Pod 的宽限期到期后,kubelet 强制终止本地 Pod。 kubelet 告诉 API 服务器删除 Pod
资源。 API 服务器删除 Pod
资源。 解决驱逐被卡住的问题 在某些情况下,你的应用可能进入中断状态,
在你干预之前,驱逐 API 总是返回 429
或 500
。
例如,如果 ReplicaSet 为你的应用程序创建了 Pod,
但新的 Pod 没有进入 Ready
状态,就会发生这种情况。
在最后一个被驱逐的 Pod 有很长的终止宽限期的情况下,你可能也会注意到这种行为。
如果你注意到驱逐被卡住,请尝试以下解决方案之一:
终止或暂停导致问题的自动化操作,重新启动操作之前,请检查被卡住的应用程序。 等待一段时间后,直接从集群控制平面删除 Pod,而不是使用 Eviction API。 接下来 3.12 - 集群管理 关于创建和管理 Kubernetes 集群的底层细节。
集群管理概述面向任何创建和管理 Kubernetes 集群的读者人群。
我们假设你大概了解一些核心的 Kubernetes 概念 。
规划集群 查阅安装 中的指导,获取如何规划、建立以及配置 Kubernetes
集群的示例。本文所列的文章称为发行版 。
说明: 并非所有发行版都是被积极维护的。
请选择使用最近 Kubernetes 版本测试过的发行版。
在选择一个指南前,有一些因素需要考虑:
你是打算在你的计算机上尝试 Kubernetes,还是要构建一个高可用的多节点集群?
请选择最适合你需求的发行版。 你正在使用类似 Google Kubernetes Engine
这样的被托管的 Kubernetes 集群 , 还是管理你自己的集群 ? 你的集群是在本地 还是云(IaaS) 上?Kubernetes 不能直接支持混合集群。
作为代替,你可以建立多个集群。 如果你在本地配置 Kubernetes ,
需要考虑哪种网络模型 最适合。你的 Kubernetes 在裸机 上还是虚拟机(VM) 上运行? 你是想运行一个集群 ,还是打算参与开发 Kubernetes 项目代码 ?
如果是后者,请选择一个处于开发状态的发行版。
某些发行版只提供二进制发布版,但提供更多的选择。 让你自己熟悉运行一个集群所需的组件 。 管理集群 保护集群 保护 kubelet 可选集群服务 DNS 集成 描述了如何将一个 DNS
名解析到一个 Kubernetes service。记录和监控集群活动 阐述了 Kubernetes
的日志如何工作以及怎样实现。3.12.1 - 证书 要了解如何为集群生成证书,参阅证书 。
3.12.2 - 管理资源 你已经部署了应用并通过服务暴露它。然后呢?
Kubernetes 提供了一些工具来帮助管理你的应用部署,包括扩缩容和更新。
我们将更深入讨论的特性包括
配置文件 和
标签 。
组织资源配置 许多应用需要创建多个资源,例如 Deployment 和 Service。
可以通过将多个资源组合在同一个文件中(在 YAML 中以 ---
分隔)
来简化对它们的管理。例如:
apiVersion : v1
kind : Service
metadata :
name : my-nginx-svc
labels :
app : nginx
spec :
type : LoadBalancer
ports :
- port : 80
selector :
app : nginx
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
labels :
app : nginx
spec :
replicas : 3
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
可以用创建单个资源相同的方式来创建多个资源:
kubectl apply -f https://k8s.io/examples/application/nginx-app.yaml
service/my-nginx-svc created
deployment.apps/my-nginx created
资源将按照它们在文件中的顺序创建。
因此,最好先指定服务,这样在控制器(例如 Deployment)创建 Pod 时能够
确保调度器可以将与服务关联的多个 Pod 分散到不同节点。
kubectl create
也接受多个 -f
参数:
kubectl apply -f https://k8s.io/examples/application/nginx/nginx-svc.yaml -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml
还可以指定目录路径,而不用添加多个单独的文件:
kubectl apply -f https://k8s.io/examples/application/nginx/
kubectl
将读取任何后缀为 .yaml
、.yml
或者 .json
的文件。
建议的做法是,将同一个微服务或同一应用层相关的资源放到同一个文件中,
将同一个应用相关的所有文件按组存放到同一个目录中。
如果应用的各层使用 DNS 相互绑定,那么你可以将堆栈的所有组件一起部署。
还可以使用 URL 作为配置源,便于直接使用已经提交到 Github 上的配置文件进行部署:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/zh-cn/examples/application/nginx/nginx-deployment.yaml
deployment.apps/my-nginx created
kubectl 中的批量操作 资源创建并不是 kubectl
可以批量执行的唯一操作。
kubectl
还可以从配置文件中提取资源名,以便执行其他操作,
特别是删除你之前创建的资源:
kubectl delete -f https://k8s.io/examples/application/nginx-app.yaml
deployment.apps "my-nginx" deleted
service "my-nginx-svc" deleted
在仅有两种资源的情况下,可以使用"资源类型/资源名"的语法在命令行中
同时指定这两个资源:
kubectl delete deployments/my-nginx services/my-nginx-svc
对于资源数目较大的情况,你会发现使用 -l
或 --selector
指定筛选器(标签查询)能很容易根据标签筛选资源:
kubectl delete deployment,services -l app = nginx
deployment.apps "my-nginx" deleted
service "my-nginx-svc" deleted
由于 kubectl
用来输出资源名称的语法与其所接受的资源名称语法相同,
你可以使用 $()
或 xargs
进行链式操作:
kubectl get $( kubectl create -f docs/concepts/cluster-administration/nginx/ -o name | grep service)
kubectl create -f docs/concepts/cluster-administration/nginx/ -o name | grep service | xargs -i kubectl get {}
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-svc LoadBalancer 10.0.0.208 <pending> 80/TCP 0s
上面的命令中,我们首先使用 examples/application/nginx/
下的配置文件创建资源,
并使用 -o name
的输出格式(以"资源/名称"的形式打印每个资源)打印所创建的资源。
然后,我们通过 grep
来过滤 "service",最后再打印 kubectl get
的内容。
如果你碰巧在某个路径下的多个子路径中组织资源,那么也可以递归地在所有子路径上
执行操作,方法是在 --filename,-f
后面指定 --recursive
或者 -R
。
例如,假设有一个目录路径为 project/k8s/development
,它保存开发环境所需的
所有清单,并按资源类型组织:
project/k8s/development
├── configmap
│ └── my-configmap.yaml
├── deployment
│ └── my-deployment.yaml
└── pvc
└── my-pvc.yaml
默认情况下,对 project/k8s/development
执行的批量操作将停止在目录的第一级,
而不是处理所有子目录。
如果我们试图使用以下命令在此目录中创建资源,则会遇到一个错误:
kubectl apply -f project/k8s/development
error: you must provide one or more resources by argument or filename (.json|.yaml|.yml|stdin)
正确的做法是,在 --filename,-f
后面标明 --recursive
或者 -R
之后:
kubectl apply -f project/k8s/development --recursive
configmap/my-config created
deployment.apps/my-deployment created
persistentvolumeclaim/my-pvc created
--recursive
可以用于接受 --filename,-f
参数的任何操作,例如:
kubectl {create,get,delete,describe,rollout}
等。
有多个 -f
参数出现的时候,--recursive
参数也能正常工作:
kubectl apply -f project/k8s/namespaces -f project/k8s/development --recursive
namespace/development created
namespace/staging created
configmap/my-config created
deployment.apps/my-deployment created
persistentvolumeclaim/my-pvc created
如果你有兴趣进一步学习关于 kubectl
的内容,请阅读
命令行工具(kubectl) 。
有效地使用标签 到目前为止我们使用的示例中的资源最多使用了一个标签。
在许多情况下,应使用多个标签来区分集合。
例如,不同的应用可能会为 app
标签设置不同的值。
但是,类似 guestbook 示例
这样的多层应用,还需要区分每一层。前端可以带以下标签:
labels :
app : guestbook
tier : frontend
Redis 的主节点和从节点会有不同的 tier
标签,甚至还有一个额外的 role
标签:
labels :
app : guestbook
tier : backend
role : master
以及
labels :
app : guestbook
tier : backend
role : slave
标签允许我们按照标签指定的任何维度对我们的资源进行切片和切块:
kubectl apply -f examples/guestbook/all-in-one/guestbook-all-in-one.yaml
kubectl get pods -Lapp -Ltier -Lrole
NAME READY STATUS RESTARTS AGE APP TIER ROLE
guestbook-fe-4nlpb 1/1 Running 0 1m guestbook frontend <none>
guestbook-fe-ght6d 1/1 Running 0 1m guestbook frontend <none>
guestbook-fe-jpy62 1/1 Running 0 1m guestbook frontend <none>
guestbook-redis-master-5pg3b 1/1 Running 0 1m guestbook backend master
guestbook-redis-slave-2q2yf 1/1 Running 0 1m guestbook backend slave
guestbook-redis-slave-qgazl 1/1 Running 0 1m guestbook backend slave
my-nginx-divi2 1/1 Running 0 29m nginx <none> <none>
my-nginx-o0ef1 1/1 Running 0 29m nginx <none> <none>
kubectl get pods -lapp= guestbook,role= slave
NAME READY STATUS RESTARTS AGE
guestbook-redis-slave-2q2yf 1/1 Running 0 3m
guestbook-redis-slave-qgazl 1/1 Running 0 3m
金丝雀部署(Canary Deployments) 另一个需要多标签的场景是用来区分同一组件的不同版本或者不同配置的多个部署。
常见的做法是部署一个使用金丝雀发布 来部署新应用版本
(在 Pod 模板中通过镜像标签指定),保持新旧版本应用同时运行。
这样,新版本在完全发布之前也可以接收实时的生产流量。
例如,你可以使用 track
标签来区分不同的版本。
主要稳定的发行版将有一个 track
标签,其值为 stable
:
name : frontend
replicas : 3
...
labels :
app : guestbook
tier : frontend
track : stable
...
image : gb-frontend:v3
然后,你可以创建 guestbook 前端的新版本,让这些版本的 track
标签带有不同的值
(即 canary
),以便两组 Pod 不会重叠:
name : frontend-canary
replicas : 1
...
labels :
app : guestbook
tier : frontend
track : canary
...
image : gb-frontend:v4
前端服务通过选择标签的公共子集(即忽略 track
标签)来覆盖两组副本,
以便流量可以转发到两个应用:
selector :
app : guestbook
tier : frontend
你可以调整 stable
和 canary
版本的副本数量,以确定每个版本将接收
实时生产流量的比例(在本例中为 3:1)。
一旦有信心,你就可以将新版本应用的 track
标签的值从
canary
替换为 stable
,并且将老版本应用删除。
想要了解更具体的示例,请查看
Ghost 部署教程 。
更新标签 有时,现有的 pod 和其它资源需要在创建新资源之前重新标记。
这可以用 kubectl label
完成。
例如,如果想要将所有 nginx pod 标记为前端层,运行:
kubectl label pods -l app = nginx tier = fe
pod/my-nginx-2035384211-j5fhi labeled
pod/my-nginx-2035384211-u2c7e labeled
pod/my-nginx-2035384211-u3t6x labeled
首先用标签 "app=nginx" 过滤所有的 Pod,然后用 "tier=fe" 标记它们。
想要查看你刚才标记的 Pod,请运行:
kubectl get pods -l app = nginx -L tier
NAME READY STATUS RESTARTS AGE TIER
my-nginx-2035384211-j5fhi 1/1 Running 0 23m fe
my-nginx-2035384211-u2c7e 1/1 Running 0 23m fe
my-nginx-2035384211-u3t6x 1/1 Running 0 23m fe
这将输出所有 "app=nginx" 的 Pod,并有一个额外的描述 Pod 的 tier 的标签列
(用参数 -L
或者 --label-columns
标明)。
想要了解更多信息,请参考
标签 和
kubectl label
命令文档。
更新注解 有时,你可能希望将注解附加到资源中。注解是 API 客户端(如工具、库等)
用于检索的任意非标识元数据。这可以通过 kubectl annotate
来完成。例如:
kubectl annotate pods my-nginx-v4-9gw19 description = 'my frontend running nginx'
kubectl get pods my-nginx-v4-9gw19 -o yaml
apiVersion: v1
kind: pod
metadata:
annotations:
description: my frontend running nginx
...
想要了解更多信息,请参考
注解 和
kubectl annotate
命令文档。
扩缩你的应用 当应用上的负载增长或收缩时,使用 kubectl
能够实现应用规模的扩缩。
例如,要将 nginx 副本的数量从 3 减少到 1,请执行以下操作:
kubectl scale deployment/my-nginx --replicas= 1
deployment.extensions/my-nginx scaled
现在,你的 Deployment 管理的 Pod 只有一个了。
kubectl get pods -l app = nginx
NAME READY STATUS RESTARTS AGE
my-nginx-2035384211-j5fhi 1/1 Running 0 30m
想要让系统自动选择需要 nginx 副本的数量,范围从 1 到 3,请执行以下操作:
kubectl autoscale deployment/my-nginx --min= 1 --max= 3
horizontalpodautoscaler.autoscaling/my-nginx autoscaled
现在,你的 nginx 副本将根据需要自动地增加或者减少。
想要了解更多信息,请参考
kubectl scale 命令文档、
kubectl autoscale 命令文档和
水平 Pod 自动伸缩 文档。
就地更新资源 有时,有必要对你所创建的资源进行小范围、无干扰地更新。
kubectl apply 建议在源代码管理中维护一组配置文件
(参见配置即代码 ),
这样,它们就可以和应用代码一样进行维护和版本管理。
然后,你可以用 kubectl apply
将配置变更应用到集群中。
这个命令将会把推送的版本与以前的版本进行比较,并应用你所做的更改,
但是不会自动覆盖任何你没有指定更改的属性。
kubectl apply -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml
deployment.apps/my-nginx configured
注意,kubectl apply
将为资源增加一个额外的注解,以确定自上次调用以来对配置的更改。
执行时,kubectl apply
会在以前的配置、提供的输入和资源的当前配置之间
找出三方差异,以确定如何修改资源。
目前,新创建的资源是没有这个注解的,所以,第一次调用 kubectl apply
时
将使用提供的输入和资源的当前配置双方之间差异进行比较。
在第一次调用期间,它无法检测资源创建时属性集的删除情况。
因此,kubectl 不会删除它们。
所有后续的 kubectl apply
操作以及其他修改配置的命令,如 kubectl replace
和 kubectl edit
,都将更新注解,并允许随后调用的 kubectl apply
使用三方差异进行检查和执行删除。
说明: 想要使用 apply,请始终使用 kubectl apply
或 kubectl create --save-config
创建资源。
kubectl edit 或者,你也可以使用 kubectl edit
更新资源:
kubectl edit deployment/my-nginx
这相当于首先 get
资源,在文本编辑器中编辑它,然后用更新的版本 apply
资源:
kubectl get deployment my-nginx -o yaml > /tmp/nginx.yaml
vi /tmp/nginx.yaml
# do some edit, and then save the file
kubectl apply -f /tmp/nginx.yaml
deployment.apps/my-nginx configured
rm /tmp/nginx.yaml
这使你可以更加容易地进行更重大的更改。
请注意,可以使用 EDITOR
或 KUBE_EDITOR
环境变量来指定编辑器。
想要了解更多信息,请参考
kubectl edit 文档。
kubectl patch 你可以使用 kubectl patch
来更新 API 对象。此命令支持 JSON patch、
JSON merge patch、以及 strategic merge patch。 请参考
使用 kubectl patch 更新 API 对象
和
kubectl patch .
破坏性的更新 在某些情况下,你可能需要更新某些初始化后无法更新的资源字段,或者你可能只想立即进行递归更改,
例如修复 Deployment 创建的不正常的 Pod。若要更改这些字段,请使用 replace --force
,
它将删除并重新创建资源。在这种情况下,你可以修改原始配置文件:
kubectl replace -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml --force
deployment.apps/my-nginx deleted
deployment.apps/my-nginx replaced
在不中断服务的情况下更新应用 在某些时候,你最终需要更新已部署的应用,通常都是通过指定新的镜像或镜像标签,
如上面的金丝雀发布的场景中所示。kubectl
支持几种更新操作,
每种更新操作都适用于不同的场景。
我们将指导你通过 Deployment 如何创建和更新应用。
假设你正运行的是 1.14.2 版本的 nginx:
kubectl create deployment my-nginx --image= nginx:1.14.2
deployment.apps/my-nginx created
要更新到 1.16.1 版本,只需使用我们前面学到的 kubectl 命令将
.spec.template.spec.containers[0].image
从 nginx:1.14.2
修改为 nginx:1.16.1
。
kubectl edit deployment/my-nginx
没错,就是这样!Deployment 将在后台逐步更新已经部署的 nginx 应用。
它确保在更新过程中,只有一定数量的旧副本被开闭,并且只有一定基于所需 Pod 数量的新副本被创建。
想要了解更多细节,请参考 Deployment 。
接下来 3.12.3 - 集群网络系统 集群网络系统是 Kubernetes 的核心部分,但是想要准确了解它的工作原理可是个不小的挑战。
下面列出的是网络系统的的四个主要问题:
高度耦合的容器间通信:这个已经被 Pod
和 localhost
通信解决了。 Pod 间通信:这是本文档讲述的重点。 Pod 与 Service 间通信:涵盖在 Service 中。 外部与 Service 间通信:也涵盖在 Service 中。 Kubernetes 的宗旨就是在应用之间共享机器。
通常来说,共享机器需要两个应用之间不能使用相同的端口,但是在多个应用开发者之间
去大规模地协调端口是件很困难的事情,尤其是还要让用户暴露在他们控制范围之外的集群级别的问题上。
动态分配端口也会给系统带来很多复杂度 - 每个应用都需要设置一个端口的参数,
而 API 服务器还需要知道如何将动态端口数值插入到配置模块中,服务也需要知道如何找到对方等等。
与其去解决这些问题,Kubernetes 选择了其他不同的方法。
要了解 Kubernetes 网络模型,请参阅此处 。
如何实现 Kubernetes 的网络模型 网络模型由每个节点上的容器运行时实现。最常见的容器运行时使用
Container Network Interface (CNI) 插件来管理其网络和安全功能。
许多不同的 CNI 插件来自于许多不同的供应商。其中一些仅提供添加和删除网络接口的基本功能,
而另一些则提供更复杂的解决方案,例如与其他容器编排系统集成、运行多个 CNI 插件、高级 IPAM 功能等。
请参阅此页面 了解
Kubernetes 支持的网络插件的非详尽列表。
接下来 网络模型的早期设计、运行原理以及未来的一些计划,
都在联网设计文档 里有更详细的描述。
3.12.4 - 日志架构 应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。
大部分现代化应用都有某种日志记录机制。同样地,容器引擎也被设计成支持日志记录。
针对容器化应用,最简单且最广泛采用的日志记录方式就是写入标准输出和标准错误流。
但是,由容器引擎或运行时提供的原生功能通常不足以构成完整的日志记录方案。
例如,如果发生容器崩溃、Pod 被逐出或节点宕机等情况,你可能想访问应用日志。
在集群中,日志应该具有独立的存储,并且其生命周期与节点、Pod 或容器的生命周期相独立。
这个概念叫集群级的日志 。
集群级日志架构需要一个独立的后端用来存储、分析和查询日志。
Kubernetes 并不为日志数据提供原生的存储解决方案。
相反,有很多现成的日志方案可以集成到 Kubernetes 中。
下面各节描述如何在节点上处理和存储日志。
Pod 和容器日志 Kubernetes 从正在运行的 Pod 中捕捉每个容器的日志。
此示例使用带有一个容器的 Pod
的清单,该容器每秒将文本写入标准输出一次。
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox:1.28
args : [/bin/sh, -c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done' ]
要运行此 Pod,请执行以下命令:
kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml
输出为:
要获取这些日志,请执行以下 kubectl logs
命令:
输出类似于:
0: Fri Apr 1 11:42:23 UTC 2022
1: Fri Apr 1 11:42:24 UTC 2022
2: Fri Apr 1 11:42:25 UTC 2022
你可以使用 kubectl logs --previous
从容器的先前实例中检索日志。
如果你的 Pod 有多个容器,请如下通过将容器名称追加到该命令并使用 -c
标志来指定要访问哪个容器的日志:
kubectl logs counter -c count
详见 kubectl logs
文档 。
节点的容器日志处理方式
容器运行时对写入到容器化应用程序的 stdout
和 stderr
流的所有输出进行处理和转发。
不同的容器运行时以不同的方式实现这一点;不过它们与 kubelet 的集成都被标准化为 CRI 日志格式 。
默认情况下,如果容器重新启动,kubelet 会保留一个终止的容器及其日志。
如果一个 Pod 被逐出节点,所对应的所有容器及其日志也会被逐出。
kubelet 通过 Kubernetes API 的特殊功能将日志提供给客户端访问。
访问这个日志的常用方法是运行 kubectl logs
。
日志轮转 特性状态: Kubernetes v1.21 [stable]
你可以配置 kubelet 令其自动轮转日志。
如果配置轮转,kubelet 负责轮转容器日志并管理日志目录结构。
kubelet(使用 CRI)将此信息发送到容器运行时,而运行时则将容器日志写到给定位置。
你可以使用 kubelet 配置文件 配置两个
kubelet 配置选项 、
containerLogMaxSize
和 containerLogMaxFiles
。
这些设置分别允许你分别配置每个日志文件大小的最大值和每个容器允许的最大文件数。
当类似于基本日志示例一样运行 kubectl logs
时,
节点上的 kubelet 会处理请求并直接从日志文件读取。kubelet 将返回该日志文件的内容。
说明: 只有最新的日志文件的内容可以通过 kubectl logs
获得。
例如,如果 Pod 写入 40 MiB 的日志,并且 kubelet 在 10 MiB 之后轮转日志,
则运行 kubectl logs
将最多返回 10 MiB 的数据。
系统组件日志 系统组件有两种类型:通常在容器中运行的组件和直接参与容器运行的组件。例如:
kubelet 和容器运行时不在容器中运行。kubelet 运行你的容器
(一起按 Pod 分组) Kubernetes 调度器、控制器管理器和 API 服务器在 Pod 中运行
(通常是静态 Pod 。
etcd 组件在控制平面中运行,最常见的也是作为静态 Pod。
如果你的集群使用 kube-proxy,则通常将其作为 DaemonSet
运行。 日志位置 kubelet 和容器运行时写入日志的方式取决于节点使用的操作系统:
在使用 systemd 的 Linux 节点上,kubelet 和容器运行时默认写入 journald。
你要使用 journalctl
来阅读 systemd 日志;例如:journalctl -u kubelet
。
如果 systemd 不存在,kubelet 和容器运行时将写入到 /var/log
目录中的 .log
文件。
如果你想将日志写入其他地方,你可以通过辅助工具 kube-log-runner
间接运行 kubelet,
并使用该工具将 kubelet 日志重定向到你所选择的目录。
你还可以使用已弃用的 kubelet 命令行参数 --log-dir
设置日志目录。
但是,kubelet 始终指示你的容器运行时将日志写入 /var/log/pods
中的目录。
有关 kube-log-runner
的更多信息,请阅读系统日志 。
默认情况下,kubelet 将日志写入目录 C:\var\logs
中的文件(注意这不是 C:\var\log
)。
尽管 C:\var\log
是这些日志的 Kubernetes 默认位置,
但一些集群部署工具会将 Windows 节点设置为将日志放到 C:\var\log\kubelet
。
如果你想将日志写入其他地方,你可以通过辅助工具 kube-log-runner
间接运行 kubelet,
并使用该工具将 kubelet 日志重定向到你所选择的目录。
但是,kubelet 总是指示你的容器运行时在目录 C:\var\log\pods
中写入日志。
有关 kube-log-runner
的更多信息,请阅读系统日志 。
对于在 Pod 中运行的 Kubernetes 集群组件,其日志会写入 /var/log
目录中的文件,
相当于绕过默认的日志机制(组件不会写入 systemd 日志)。
你可以使用 Kubernetes 的存储机制将持久存储映射到运行该组件的容器中。
有关 etcd 及其日志的详细信息,请查阅 etcd 文档 。
同样,你可以使用 Kubernetes 的存储机制将持久存储映射到运行该组件的容器中。
说明: 如果你部署 Kubernetes 集群组件(例如调度器)以将日志记录到从父节点共享的卷中,
则需要考虑并确保这些日志被轮转。 Kubernetes 不管理这种日志轮转 。
你的操作系统可能会自动实现一些日志轮转。例如,如果你将目录 /var/log
共享到一个组件的静态 Pod 中,
则节点级日志轮转会将该目录中的文件视同为 Kubernetes 之外的组件所写入的文件。
一些部署工具会考虑日志轮转并将其自动化;而其他一些工具会将此留给你来处理。
集群级日志架构 虽然 Kubernetes 没有为集群级日志记录提供原生的解决方案,但你可以考虑几种常见的方法。
以下是一些选项:
使用在每个节点上运行的节点级日志记录代理。 在应用程序的 Pod 中,包含专门记录日志的边车(Sidecar)容器。 将日志直接从应用程序中推送到日志记录后端。 使用节点级日志代理
你可以通过在每个节点上使用 节点级的日志记录代理 来实现集群级日志记录。
日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。
通常,日志记录代理程序是一个容器,它可以访问包含该节点上所有应用程序容器的日志文件的目录。
由于日志记录代理必须在每个节点上运行,推荐以 DaemonSet
的形式运行该代理。
节点级日志在每个节点上仅创建一个代理,不需要对节点上的应用做修改。
容器向标准输出和标准错误输出写出数据,但在格式上并不统一。
节点级代理收集这些日志并将其进行转发以完成汇总。
使用边车容器运行日志代理 你可以通过以下方式之一使用边车(Sidecar)容器:
边车容器将应用程序日志传送到自己的标准输出。 边车容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。 传输数据流的边车容器
利用边车容器,写入到自己的 stdout
和 stderr
传输流,
你就可以利用每个节点上的 kubelet 和日志代理来处理日志。
边车容器从文件、套接字或 journald 读取日志。
每个边车容器向自己的 stdout
和 stderr
流中输出日志。
这种方法允许你将日志流从应用程序的不同部分分离开,其中一些可能缺乏对写入
stdout
或 stderr
的支持。重定向日志背后的逻辑是最小的,因此它的开销不大。
另外,因为 stdout
和 stderr
由 kubelet 处理,所以你可以使用内置的工具 kubectl logs
。
例如,某 Pod 中运行一个容器,且该容器使用两个不同的格式写入到两个不同的日志文件。
下面是这个 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox:1.28
args :
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts :
- name : varlog
mountPath : /var/log
volumes :
- name : varlog
emptyDir : {}
不建议在同一个日志流中写入不同格式的日志条目,即使你成功地将其重定向到容器的 stdout
流。
相反,你可以创建两个边车容器。每个边车容器可以从共享卷跟踪特定的日志文件,
并将文件内容重定向到各自的 stdout
流。
下面是运行两个边车容器的 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox:1.28
args :
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts :
- name : varlog
mountPath : /var/log
- name : count-log-1
image : busybox:1.28
args : [/bin/sh, -c, 'tail -n+1 -F /var/log/1.log']
volumeMounts :
- name : varlog
mountPath : /var/log
- name : count-log-2
image : busybox:1.28
args : [/bin/sh, -c, 'tail -n+1 -F /var/log/2.log']
volumeMounts :
- name : varlog
mountPath : /var/log
volumes :
- name : varlog
emptyDir : {}
现在当你运行这个 Pod 时,你可以运行如下命令分别访问每个日志流:
kubectl logs counter count-log-1
输出类似于:
0: Fri Apr 1 11:42:26 UTC 2022
1: Fri Apr 1 11:42:27 UTC 2022
2: Fri Apr 1 11:42:28 UTC 2022
...
kubectl logs counter count-log-2
输出类似于:
Fri Apr 1 11:42:29 UTC 2022 INFO 0
Fri Apr 1 11:42:30 UTC 2022 INFO 0
Fri Apr 1 11:42:31 UTC 2022 INFO 0
...
如果你在集群中安装了节点级代理,由代理自动获取上述日志流,而无需任何进一步的配置。
如果你愿意,你可以将代理配置为根据源容器解析日志行。
即使对于 CPU 和内存使用率较低的 Pod(CPU 为几毫核,内存为几兆字节),将日志写入一个文件,
将这些日志流写到 stdout
也有可能使节点所需的存储量翻倍。
如果你有一个写入特定文件的应用程序,则建议将 /dev/stdout
设置为目标文件,而不是采用流式边车容器方法。
边车容器还可用于轮转应用程序本身无法轮转的日志文件。
这种方法的一个例子是定期运行 logrotate
的小容器。
但是,直接使用 stdout
和 stderr
更直接,而将轮转和保留策略留给 kubelet。
集群中安装的节点级代理会自动获取这些日志流,而无需进一步配置。
如果你愿意,你也可以配置代理程序来解析源容器的日志行。
注意,尽管 CPU 和内存使用率都很低(以多个 CPU 毫核指标排序或者按内存的兆字节排序),
向文件写日志然后输出到 stdout
流仍然会成倍地增加磁盘使用率。
如果你的应用向单一文件写日志,通常最好设置 /dev/stdout
作为目标路径,
而不是使用流式的边车容器方式。
如果应用程序本身不能轮转日志文件,则可以通过边车容器实现。
这种方式的一个例子是运行一个小的、定期轮转日志的容器。
然而,还是推荐直接使用 stdout
和 stderr
,将日志的轮转和保留策略交给 kubelet。
具有日志代理功能的边车容器
如果节点级日志记录代理程序对于你的场景来说不够灵活,
你可以创建一个带有单独日志记录代理的边车容器,将代理程序专门配置为与你的应用程序一起运行。
说明: 在边车容器中使用日志代理会带来严重的资源损耗。
此外,你不能使用 kubectl logs
访问日志,因为日志并没有被 kubelet 管理。
下面是两个配置文件,可以用来实现一个带日志代理的边车容器。
第一个文件包含用来配置 fluentd 的
ConfigMap 。
apiVersion : v1
kind : ConfigMap
metadata :
name : fluentd-config
data :
fluentd.conf : |
<source>
type tail
format none
path /var/log/1.log
pos_file /var/log/1.log.pos
tag count.format1
</source>
<source>
type tail
format none
path /var/log/2.log
pos_file /var/log/2.log.pos
tag count.format2
</source>
<match **>
type google_cloud
</match>
说明: 你可以将此示例配置中的 fluentd 替换为其他日志代理,从应用容器内的其他来源读取数据。
第二个清单描述了一个运行 fluentd 边车容器的 Pod。
该 Pod 挂载一个卷,flutend 可以从这个卷上拣选其配置数据。
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox:1.28
args :
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts :
- name : varlog
mountPath : /var/log
- name : count-agent
image : registry.k8s.io/fluentd-gcp:1.30
env :
- name : FLUENTD_ARGS
value : -c /etc/fluentd-config/fluentd.conf
volumeMounts :
- name : varlog
mountPath : /var/log
- name : config-volume
mountPath : /etc/fluentd-config
volumes :
- name : varlog
emptyDir : {}
- name : config-volume
configMap :
name : fluentd-config
从应用中直接暴露日志目录
从各个应用中直接暴露和推送日志数据的集群日志机制已超出 Kubernetes 的范围。
接下来 3.12.5 - Kubernetes 系统组件指标 通过系统组件指标可以更好地了解系统组个内部发生的情况。系统组件指标对于构建仪表板和告警特别有用。
Kubernetes 组件以 Prometheus 格式
生成度量值。
这种格式是结构化的纯文本,旨在使人和机器都可以阅读。
Kubernetes 中组件的指标 在大多数情况下,可以通过 HTTP 访问组件的 /metrics
端点来获取组件的度量值。
对于那些默认情况下不暴露端点的组件,可以使用 --bind-address
标志启用。
这些组件的示例:
在生产环境中,你可能需要配置 Prometheus 服务器 或
某些其他指标搜集器以定期收集这些指标,并使它们在某种时间序列数据库中可用。
请注意,kubelet 还会在 /metrics/cadvisor
,
/metrics/resource
和 /metrics/probes
端点中公开度量值。这些度量值的生命周期各不相同。
如果你的集群使用了 RBAC ,
则读取指标需要通过基于用户、组或 ServiceAccount 的鉴权,要求具有允许访问
/metrics
的 ClusterRole。
例如:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
name : prometheus
rules :
- nonResourceURLs :
- "/metrics"
verbs :
- get
指标生命周期 Alpha 指标 → 稳定的指标 → 弃用的指标 → 隐藏的指标 → 删除的指标
Alpha 指标没有稳定性保证。这些指标可以随时被修改或者删除。
稳定的指标可以保证不会改变。这意味着:
稳定的、不包含已弃用(deprecated)签名的指标不会被删除(或重命名) 稳定的指标的类型不会被更改 已弃用的指标最终将被删除,不过仍然可用。
这类指标包含注解,标明其被废弃的版本。
例如:
隐藏的指标不会再被发布以供抓取,但仍然可用。
要使用隐藏指标,请参阅显式隐藏指标 节。
删除的指标不再被发布,亦无法使用。
显示隐藏指标 如上所述,管理员可以通过设置可执行文件的命令行参数来启用隐藏指标,
如果管理员错过了上一版本中已经弃用的指标的迁移,则可以把这个用作管理员的逃生门。
show-hidden-metrics-for-version
标志接受版本号作为取值,版本号给出
你希望显示该发行版本中已弃用的指标。
版本表示为 x.y,其中 x 是主要版本,y 是次要版本。补丁程序版本不是必须的,
即使指标可能会在补丁程序发行版中弃用,原因是指标弃用策略规定仅针对次要版本。
该参数只能使用前一个次要版本。如果管理员将先前版本设置为 show-hidden-metrics-for-version
,
则先前版本中隐藏的度量值会再度生成。不允许使用过旧的版本,因为那样会违反指标弃用策略。
以指标 A
为例,此处假设 A
在 1.n 中已弃用。根据指标弃用策略,我们可以得出以下结论:
在版本 1.n
中,这个指标已经弃用,且默认情况下可以生成。 在版本 1.n+1
中,这个指标默认隐藏,可以通过命令行参数 show-hidden-metrics-for-version=1.n
来再度生成。 在版本 1.n+2
中,这个指标就将被从代码中移除,不会再有任何逃生窗口。 如果你要从版本 1.12
升级到 1.13
,但仍依赖于 1.12
中弃用的指标 A
,则应通过命令行设置隐藏指标:
--show-hidden-metrics=1.12
,并记住在升级到 1.14
版本之前删除此指标依赖项。
禁用加速器指标 kubelet 通过 cAdvisor 收集加速器指标。为了收集这些指标,对于 NVIDIA GPU 之类的加速器,
kubelet 在驱动程序上保持打开状态。这意味着为了执行基础结构更改(例如更新驱动程序),
集群管理员需要停止 kubelet 代理。
现在,收集加速器指标的责任属于供应商,而不是 kubelet。供应商必须提供一个收集指标的容器,
并将其公开给指标服务(例如 Prometheus)。
DisableAcceleratorUsageMetrics
特性门控
禁止由 kubelet 收集的指标。
关于何时会在默认情况下启用此功能也有一定规划 。
组件指标 kube-controller-manager 指标 控制器管理器指标可提供有关控制器管理器性能和运行状况的重要洞察。
这些指标包括通用的 Go 语言运行时指标(例如 go_routine 数量)和控制器特定的度量指标,
例如可用于评估集群运行状况的 etcd 请求延迟或云提供商(AWS、GCE、OpenStack)的 API 延迟等。
从 Kubernetes 1.7 版本开始,详细的云提供商指标可用于 GCE、AWS、Vsphere 和 OpenStack 的存储操作。
这些指标可用于监控持久卷操作的运行状况。
比如,对于 GCE,这些指标称为:
cloudprovider_gce_api_request_duration_seconds { request = "instance_list"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_insert"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_delete"}
cloudprovider_gce_api_request_duration_seconds { request = "attach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "detach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "list_disk"}
kube-scheduler 指标 特性状态: Kubernetes v1.21 [beta]
调度器会暴露一些可选的指标,报告所有运行中 Pods 所请求的资源和期望的约束值。
这些指标可用来构造容量规划监控面板、访问调度约束的当前或历史数据、
快速发现因为缺少资源而无法被调度的负载,或者将 Pod 的实际资源用量
与其请求值进行比较。
kube-scheduler 组件能够辩识各个 Pod 所配置的资源
请求和约束 。
在 Pod 的资源请求值或者约束值非零时,kube-scheduler 会以度量值时间序列的形式
生成报告。该时间序列值包含以下标签:
名字空间 Pod 名称 Pod 调度所处节点,或者当 Pod 未被调度时用空字符串表示 优先级 为 Pod 所指派的调度器 资源的名称(例如,cpu
) 资源的单位,如果知道的话(例如,cores
) 一旦 Pod 进入完成状态(其 restartPolicy
为 Never
或 OnFailure
,且
其处于 Succeeded
或 Failed
Pod 阶段,或者已经被删除且所有容器都具有
终止状态),该时间序列停止报告,因为调度器现在可以调度其它 Pod 来执行。
这两个指标称作 kube_pod_resource_request
和 kube_pod_resource_limit
。
指标暴露在 HTTP 端点 /metrics/resources
,与调度器上的 /metrics
端点
一样要求相同的访问授权。你必须使用
--show-hidden-metrics-for-version=1.20
标志才能暴露那些稳定性为 Alpha
的指标。
禁用指标 你可以通过命令行标志 --disabled-metrics
来关闭某指标。
在例如某指标会带来性能问题的情况下,这一操作可能是有用的。
标志的参数值是一组被禁止的指标(例如:--disabled-metrics=metric1,metric2
)。
指标顺序性保证 在 Alpha 阶段,标志只能接受一组映射值作为可以使用的指标标签。
每个映射值的格式为<指标名称>,<标签名称>=<可用标签列表>
,其中
<可用标签列表>
是一个用逗号分隔的、可接受的标签名的列表。
最终的格式看起来会是这样:
--allow-label-value <指标名称>,<标签名称>='<可用值1>,<可用值2>...', <指标名称2>,<标签名称>='<可用值1>, <可用值2>...', ...
.
下面是一个例子:
--allow-label-value number_count_metric,odd_number='1,3,5', number_count_metric,even_number='2,4,6', date_gauge_metric,weekend='Saturday,Sunday'
接下来 3.12.6 - 系统日志 系统组件的日志记录集群中发生的事件,这对于调试非常有用。
你可以配置日志的精细度,以展示更多或更少的细节。
日志可以是粗粒度的,如只显示组件内的错误,
也可以是细粒度的,如显示事件的每一个跟踪步骤(比如 HTTP 访问日志、pod 状态更新、控制器动作或调度器决策)。
Klog klog 是 Kubernetes 的日志库。
klog
为 Kubernetes 系统组件生成日志消息。
有关 klog 配置的更多信息,请参见命令行工具参考 。
Kubernetes 正在进行简化其组件日志的努力。下面的 klog 命令行参数从 Kubernetes 1.23
开始已被废弃 ,
会在未来版本中移除:
--add-dir-header
--alsologtostderr
--log-backtrace-at
--log-dir
--log-file
--log-file-max-size
--logtostderr
--one-output
--skip-headers
--skip-log-headers
--stderrthreshold
输出总会被写到标准错误输出(stderr)之上,无论输出格式如何。
对输出的重定向将由调用 Kubernetes 组件的软件来处理。
这一软件可以是 POSIX Shell 或者类似 systemd 这样的工具。
在某些场合下,例如对于无发行主体的(distroless)容器或者 Windows 系统服务,
这些替代方案都是不存在的。那么你可以使用
kube-log-runner
可执行文件来作为 Kubernetes 的封装层,完成对输出的重定向。
在很多 Kubernetes 基础镜像中,都包含一个预先构建的可执行程序。
这个程序原来称作 /go-runner
,而在服务器和节点的发行版本库中,称作 kube-log-runner
。
下表展示的是 kube-log-runner
调用与 Shell 重定向之间的对应关系:
用法 POSIX Shell(例如 Bash) kube-log-runner <options> <cmd>
合并 stderr 与 stdout,写出到 stdout 2>&1
kube-log-runner
(默认行为 )将 stderr 与 stdout 重定向到日志文件 1>>/tmp/log 2>&1
kube-log-runner -log-file=/tmp/log
输出到 stdout 并复制到日志文件中 2>&1 | tee -a /tmp/log
kube-log-runner -log-file=/tmp/log -also-stdout
仅将 stdout 重定向到日志 >/tmp/log
kube-log-runner -log-file=/tmp/log -redirect-stderr=false
klog 输出 传统的 klog 原生格式示例:
I1025 00:15:15.525108 1 httplog.go:79] GET /api/v1/namespaces/kube-system/pods/metrics-server-v0.3.1-57c75779f-9p8wg: (1.512ms) 200 [pod_nanny/v0.0.0 (linux/amd64) kubernetes/$Format 10.56.1.19:51756]
消息字符串可能包含换行符:
I1025 00:15:15.525108 1 example.go:79] This is a message
which has a line break.
结构化日志 特性状态: Kubernetes v1.23 [beta]
警告: 迁移到结构化日志消息是一个正在进行的过程。在此版本中,并非所有日志消息都是结构化的。
解析日志文件时,你也必须要处理非结构化日志消息。
日志格式和值的序列化可能会发生变化。
结构化日志记录旨在日志消息中引入统一结构,以便以编程方式提取信息。
你可以方便地用更小的开销来处理结构化日志。
生成日志消息的代码决定其使用传统的非结构化的 klog 还是结构化的日志。
默认的结构化日志消息是以文本形式呈现的,其格式与传统的 klog 保持向后兼容:
<klog header> "<message>" <key1> = "<value1>" <key2>="<value2>" ...
示例:
I1025 00:15:15.525108 1 controller_utils.go:116] "Pod status updated" pod = "kube-system/kubedns" status="ready"
字符串在输出时会被添加引号。其他数值类型都使用 %+v
来格式化,因此可能导致日志消息会延续到下一行,
具体取决于数据本身 。
I1025 00:15:15.525108 1 example.go:116] "Example" data="This is text with a line break\nand \"quotation marks\"." someInt=1 someFloat=0.1 someStruct={StringField: First line,
second line.}
上下文日志 特性状态: Kubernetes v1.24 [alpha]
上下文日志建立在结构化日志之上。
它主要是关于开发人员如何使用日志记录调用:基于该概念的代码将更加灵活,
并且支持在结构化日志 KEP
中描述的额外用例。
如果开发人员在他们的组件中使用额外的函数,比如 WithValues
或 WithName
,
那么日志条目将会包含额外的信息,这些信息会被调用者传递给函数。
目前这一特性是由 StructuredLogging
特性门控所控制的,默认关闭。
这个基础设施是在 1.24 中被添加的,并不需要修改组件。
该 component-base/logs/example
命令演示了如何使用新的日志记录调用以及组件如何支持上下文日志记录。
$ cd $GOPATH /src/k8s.io/kubernetes/staging/src/k8s.io/component-base/logs/example/cmd/
$ go run . --help
...
--feature-gates mapStringBool A set of key=value pairs that describe feature gates for alpha/experimental features. Options are:
AllAlpha=true|false (ALPHA - default=false)
AllBeta=true|false (BETA - default=false)
ContextualLogging=true|false (ALPHA - default=false)
$ go run . --feature-gates ContextualLogging = true
...
I0404 18:00:02.916429 451895 logger.go:94] "example/myname: runtime" foo="bar" duration="1m0s"
I0404 18:00:02.916447 451895 logger.go:95] "example: another runtime" foo="bar" duration="1m0s"
example
前缀和 foo="bar"
会被函数的调用者添加上,
不需修改该函数,它就会记录 runtime
消息和 duration="1m0s"
值。
禁用上下文日志后,WithValues
和 WithName
什么都不会做,
并且会通过调用全局的 klog 日志记录器记录日志。
因此,这些附加信息不再出现在日志输出中:
$ go run . --feature-gates ContextualLogging = false
...
I0404 18:03:31.171945 452150 logger.go:94] "runtime" duration="1m0s"
I0404 18:03:31.171962 452150 logger.go:95] "another runtime" duration="1m0s"
JSON 日志格式 特性状态: Kubernetes v1.19 [alpha]
警告: JSON 输出并不支持太多标准 klog 参数。对于不受支持的 klog 参数的列表,
请参见命令行工具参考 。
并不是所有日志都保证写成 JSON 格式(例如,在进程启动期间)。
如果你打算解析日志,请确保可以处理非 JSON 格式的日志行。
字段名和 JSON 序列化可能会发生变化。
--logging-format=json
参数将日志格式从 klog 原生格式改为 JSON 格式。
JSON 日志格式示例(美化输出):
{
"ts" : 1580306777.04728 ,
"v" : 4 ,
"msg" : "Pod status updated" ,
"pod" :{
"name" : "nginx-1" ,
"namespace" : "default"
},
"status" : "ready"
}
具有特殊意义的 key:
ts
- Unix 时间风格的时间戳(必选项,浮点值)v
- 精细度(仅用于 info 级别,不能用于错误信息,整数)err
- 错误字符串(可选项,字符串)msg
- 消息(必选项,字符串)当前支持JSON格式的组件列表:
日志精细度级别 参数 -v
控制日志的精细度。增大该值会增大日志事件的数量。
减小该值可以减小日志事件的数量。增大精细度会记录更多的不太严重的事件。
精细度设置为 0 时只记录关键(critical)事件。
日志位置 有两种类型的系统组件:运行在容器中的组件和不运行在容器中的组件。例如:
Kubernetes 调度器和 kube-proxy 在容器中运行。 kubelet 和容器运行时 不在容器中运行。 在使用 systemd 的系统中,kubelet 和容器运行时写入 journald。
在别的系统中,日志写入 /var/log
目录下的 .log
文件中。
容器中的系统组件总是绕过默认的日志记录机制,写入 /var/log
目录下的 .log
文件。
与容器日志类似,你应该轮转 /var/log
目录下系统组件日志。
在 kube-up.sh
脚本创建的 Kubernetes 集群中,日志轮转由 logrotate
工具配置。
logrotate
工具,每天或者当日志大于 100MB 时,轮转日志。
接下来 3.12.7 - 追踪 Kubernetes 系统组件 特性状态: Kubernetes v1.22 [alpha]
系统组件追踪功能记录各个集群操作的时延信息和这些操作之间的关系。
Kubernetes 组件基于 gRPC 导出器的
OpenTelemetry 协议
发送追踪信息,并用
OpenTelemetry Collector
收集追踪信息,再将其转交给追踪系统的后台。
追踪信息的收集 关于收集追踪信息、以及使用收集器的完整指南,可参见
Getting Started with the OpenTelemetry Collector 。
不过,还有一些特定于 Kubernetes 组件的事项值得注意。
默认情况下,Kubernetes 组件使用 gRPC 的 OTLP 导出器来导出追踪信息,将信息写到
IANA OpenTelemetry 端口 。
举例来说,如果收集器以 Kubernetes 组件的边车模式运行,以下接收器配置会收集 span 信息,并将它们写入到标准输出。
receivers :
otlp :
protocols :
grpc :
exporters :
# 用适合你后端环境的导出器替换此处的导出器
logging :
logLevel : debug
service :
pipelines :
traces :
receivers : [otlp]
exporters : [logging]
组件追踪 kube-apiserver 追踪 kube-apiserver 为传入的 HTTP 请求、传出到 webhook 和 etcd 的请求以及重入的请求生成 span。
由于 kube-apiserver 通常是一个公开的端点,所以它通过出站的请求传播
W3C 追踪上下文 ,
但不使用入站请求的追踪上下文。
在 kube-apiserver 中启用追踪 要启用追踪特性,需要启用 kube-apiserver 上的 APIServerTracing
特性门控 。
然后,使用 --tracing-config-file=<<配置文件路径>
为 kube-apiserver 提供追踪配置文件。
下面是一个示例配置,它为万分之一的请求记录 spans,并使用了默认的 OpenTelemetry 端口。
apiVersion : apiserver.config.k8s.io/v1alpha1
kind : TracingConfiguration
# default value
#endpoint: localhost:4317
samplingRatePerMillion : 100
有关 TracingConfiguration 结构体的更多信息,请参阅
API 服务器配置 API (v1alpha1) 。
kubelet 追踪 特性状态: Kubernetes v1.25 [alpha]
kubelet CRI 接口和实施身份验证的 HTTP 服务器被插桩以生成追踪 span。
与 API 服务器一样,端点和采样率是可配置的。
追踪上下文传播也是可以配置的。始终优先采用父 span 的采样决策。
用户所提供的追踪配置采样率将被应用到不带父级的 span。
如果在没有配置端点的情况下启用,将使用默认的 OpenTelemetry Collector 接收器地址 “localhost:4317”。
在 kubelet 中启用追踪 要启用 span,需在 kubelet 上启用 KubeletTracing
特性门控 。
另外,为 kubelet 提供追踪配置 。
以下是 kubelet 配置的示例代码片段,每 10000 个请求中记录一个请求的 span,并使用默认的 OpenTelemetry 端点:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
featureGates :
KubeletTracing : true
tracing :
# 默认值
#endpoint: localhost:4317
samplingRatePerMillion : 100
稳定性 追踪工具仍在积极开发中,未来它会以多种方式发生变化。
这些变化包括:span 名称、附加属性、检测端点等等。
此类特性在达到稳定版本之前,不能保证追踪工具的向后兼容性。
接下来 3.12.8 - Kubernetes 中的代理 本文讲述了 Kubernetes 中所使用的代理。
代理 用户在使用 Kubernetes 的过程中可能遇到几种不同的代理(proxy):
kubectl proxy :
运行在用户的桌面或 pod 中 从本机地址到 Kubernetes apiserver 的代理 客户端到代理使用 HTTP 协议 代理到 apiserver 使用 HTTPS 协议 指向 apiserver 添加认证头信息 apiserver proxy :
是一个建立在 apiserver 内部的“堡垒” 将集群外部的用户与集群 IP 相连接,这些IP是无法通过其他方式访问的 运行在 apiserver 进程内 客户端到代理使用 HTTPS 协议 (如果配置 apiserver 使用 HTTP 协议,则使用 HTTP 协议) 通过可用信息进行选择,代理到目的地可能使用 HTTP 或 HTTPS 协议 可以用来访问 Node、 Pod 或 Service 当用来访问 Service 时,会进行负载均衡 kube proxy :
在每个节点上运行 代理 UDP、TCP 和 SCTP 不支持 HTTP 提供负载均衡能力 只用来访问 Service apiserver 之前的代理/负载均衡器:
在不同集群中的存在形式和实现不同 (如 nginx) 位于所有客户端和一个或多个 API 服务器之间 存在多个 API 服务器时,扮演负载均衡器的角色 外部服务的云负载均衡器:
由一些云供应商提供 (如 AWS ELB、Google Cloud Load Balancer) Kubernetes 服务类型为 LoadBalancer
时自动创建 通常仅支持 UDP/TCP 协议 SCTP 支持取决于云供应商的负载均衡器实现 不同云供应商的云负载均衡器实现不同 Kubernetes 用户通常只需要关心前两种类型的代理,集群管理员通常需要确保后面几种类型的代理设置正确。
请求重定向 代理已经取代重定向功能,重定向功能已被弃用。
3.12.9 - API 优先级和公平性 特性状态: Kubernetes v1.20 [beta]
对于集群管理员来说,控制 Kubernetes API 服务器在过载情况下的行为是一项关键任务。
kube-apiserver
有一些控件(例如:命令行标志 --max-requests-inflight
和 --max-mutating-requests-inflight
),
可以限制将要接受的未处理的请求,从而防止过量请求入站,潜在导致 API 服务器崩溃。
但是这些标志不足以保证在高流量期间,最重要的请求仍能被服务器接受。
API 优先级和公平性(APF)是一种替代方案,可提升上述最大并发限制。
APF 以更细粒度的方式对请求进行分类和隔离。
它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
通过使用公平排队技术从队列中分发请求,这样,
一个行为不佳的控制器 就不会饿死其他控制器
(即使优先级相同)。
本功能特性在设计上期望其能与标准控制器一起工作得很好;
这类控制器使用通知组件(Informers)获得信息并对 API 请求的失效作出反应,
在处理失效时能够执行指数型回退。其他客户端也以类似方式工作。
注意: 属于 “长时间运行” 类型的某些请求(例如远程命令执行或日志拖尾)不受 API 优先级和公平性过滤器的约束。
如果未启用 APF 特性,即便设置 --max-requests-inflight
标志,该类请求也不受约束。
APF 适用于 watch 请求。当 APF 被禁用时,watch 请求不受 --max-requests-inflight
限制。
启用/禁用 API 优先级和公平性 API 优先级与公平性(APF)特性由特性门控控制,默认情况下启用。
有关特性门控的一般性描述以及如何启用和禁用特性门控,
请参见特性门控 。
APF 的特性门控称为 APIPriorityAndFairness
。
此特性也与某个 API 组 相关:
(a) v1alpha1
版本,默认被禁用;
(b) v1beta1
和 v1beta2
版本,默认被启用。
你可以在启动 kube-apiserver
时,添加以下命令行标志来禁用此功能门控及 API Beta 组:
kube-apiserver \
--feature-gates= APIPriorityAndFairness = false \
--runtime-config= flowcontrol.apiserver.k8s.io/v1beta1= false,flowcontrol.apiserver.k8s.io/v1beta2= false \
# ...其他配置不变
或者,你也可以通过 --runtime-config=flowcontrol.apiserver.k8s.io/v1alpha1=true
启用 API 组的 v1alpha1 版本。
命令行标志 --enable-priority-fairness=false
将彻底禁用 APF 特性,
即使其他标志启用它也是无效。
概念 APF 特性包含几个不同的功能。
传入的请求通过 FlowSchema 按照其属性分类,并分配优先级。
每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。
在同一个优先级内,公平排队算法可以防止来自不同 流(Flow) 的请求相互饿死。
该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。
优先级 如果未启用 APF,API 服务器中的整体并发量将受到 kube-apiserver
的参数
--max-requests-inflight
和 --max-mutating-requests-inflight
的限制。
启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的 优先级 中。
每个传入的请求都会分配一个优先级;每个优先级都有各自的配置,设定允许分发的并发请求数。
例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。
这表示即使异常的 Pod 向 API 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。
请求占用的席位 上述并发管理的描述是基线情况。其中,各个请求具有不同的持续时间,
但在与一个优先级的并发限制进行比较时,这些请求在任何给定时刻都以同等方式进行计数。
在这个基线场景中,每个请求占用一个并发单位。
我们用 “席位(Seat)” 一词来表示一个并发单位,其灵感来自火车或飞机上每位乘客占用一个固定座位的供应方式。
但有些请求所占用的席位不止一个。有些请求是服务器预估将返回大量对象的 list 请求。
和所需运行时间相近的其他请求相比,我们发现这类请求会给服务器带来异常沉重的负担。
出于这个原因,服务器估算将返回的对象数量,并认为请求所占用的席位数与估算得到的数量成正比。
watch 请求的执行时间调整 APF 管理 watch 请求,但这需要考量基线行为之外的一些情况。
第一个关注点是如何判定 watch 请求的席位占用时长。
取决于请求参数不同,对 watch 请求的响应可能以针对所有预先存在的对象 create 通知开头,也可能不这样。
一旦最初的突发通知(如果有)结束,APF 将认为 watch 请求已经用完其席位。
每当向服务器通知创建/更新/删除一个对象时,正常通知都会以并发突发的方式发送到所有相关的 watch 响应流。
为此,APF 认为每个写入请求都会在实际写入完成后花费一些额外的时间来占用席位。
服务器估算要发送的通知数量,并调整写入请求的席位数以及包含这些额外工作后的席位占用时间。
排队 即使在同一优先级内,也可能存在大量不同的流量源。
在过载情况下,防止一个请求流饿死其他流是非常有价值的
(尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求,
理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。
公平排队算法在处理具有相同优先级的请求时,实现了上述场景。
每个请求都被分配到某个 流(Flow) 中,该 流 由对应的 FlowSchema 的名字加上一个
流区分项(Flow Distinguisher) 来标识。
这里的流区分项可以是发出请求的用户、目标资源的名字空间或什么都不是。
系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。
要启用对不同实例的不同处理方式,多实例的控制器要分别用不同的用户名来执行身份认证。
将请求划分到流中之后,APF 功能将请求分配到队列中。
分配时使用一种称为混洗分片(Shuffle-Sharding) 的技术。
该技术可以相对有效地利用队列隔离低强度流与高强度流。
排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、
公平性(当总流量超标时,各个独立的流将都会取得进展)、
突发流量的容忍度以及排队引发的额外延迟之间进行权衡。
豁免请求 某些特别重要的请求不受制于此特性施加的任何限制。
这些豁免可防止不当的流控配置完全禁用 API 服务器。
资源 流控 API 涉及两种资源。
PriorityLevelConfiguration
定义可用的优先级和可处理的并发预算量,还可以微调排队行为。
FlowSchema
用于对每个入站请求进行分类,并与一个 PriorityLevelConfiguration 相匹配。
此外同一 API 组还有一个 v1alpha1
版本,其中包含语法和语义都相同的资源类别。
PriorityLevelConfiguration 一个 PriorityLevelConfiguration 表示单个优先级。每个 PriorityLevelConfiguration
对未完成的请求数有各自的限制,对排队中的请求数也有限制。
PriorityLevelConfiguration 的并发限制不是指定请求绝对数量,而是在“并发份额”中指定。
API 服务器的总并发量限制通过这些份额按例分配到现有 PriorityLevelConfiguration 中。
集群管理员可以更改 --max-requests-inflight
(或 --max-mutating-requests-inflight
)的值,
再重新启动 kube-apiserver
来增加或减小服务器的总流量,
然后所有的 PriorityLevelConfiguration 将看到其最大并发增加(或减少)了相同的比例。
注意: 启用 APF 特性后,服务器的总并发量限制将设置为
--max-requests-inflight
和 --max-mutating-requests-inflight
之和。
可变请求和不可变请求之间不再有任何区别;
如果对于某种资源,你需要区别对待不同请求,请创建不同的 FlowSchema 分别匹配可变请求和不可变请求。
当入站请求的数量大于分配的 PriorityLevelConfiguration 中允许的并发级别时,
type
字段将确定对额外请求的处理方式。
Reject
类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。
Queue
类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。
公平排队算法支持通过排队配置对优先级微调。
可以在增强建议 中阅读算法的详细信息,
但总之:
queues
递增能减少不同流之间的冲突概率,但代价是增加了内存使用量。
值为 1 时,会禁用公平排队逻辑,但仍允许请求排队。queueLengthLimit
递增可以在不丢弃任何请求的情况下支撑更大的突发流量,
但代价是增加了等待时间和内存使用量。下表显示了有趣的随机分片配置集合,每行显示给定的老鼠(低强度流)
被不同数量的大象挤压(高强度流)的概率。
表来源请参阅: https://play.golang.org/p/Gi0PLgVHiUg
混分切片配置示例 随机分片 队列数 1 个大象 4 个大象 16 个大象 12 32 4.428838398950118e-09 0.11431348830099144 0.9935089607656024 10 32 1.550093439632541e-08 0.0626479840223545 0.9753101519027554 10 64 6.601827268370426e-12 0.00045571320990370776 0.49999929150089345 9 64 3.6310049976037345e-11 0.00045501212304112273 0.4282314876454858 8 64 2.25929199850899e-10 0.0004886697053040446 0.35935114681123076 8 128 6.994461389026097e-13 3.4055790161620863e-06 0.02746173137155063 7 128 1.0579122850901972e-11 6.960839379258192e-06 0.02406157386340147 7 256 7.597695465552631e-14 6.728547142019406e-08 0.0006709661542533682 6 256 2.7134626662687968e-12 2.9516464018476436e-07 0.0008895654642000348 6 512 4.116062922897309e-14 4.982983350480894e-09 2.26025764343413e-05 6 1024 6.337324016514285e-16 8.09060164312957e-11 4.517408062903668e-07
FlowSchema FlowSchema 匹配一些入站请求,并将它们分配给优先级。
每个入站请求都会对所有 FlowSchema 测试是否匹配,
首先从 matchingPrecedence
数值最低的匹配开始(我们认为这是逻辑上的最高优先级),
然后依次进行,直到首个匹配出现。
注意: 对一个请求来说,只有首个匹配的 FlowSchema 才有意义。
如果一个入站请求与多个 FlowSchema 匹配,则将基于逻辑上最高优先级 matchingPrecedence
的请求进行筛选。
如果一个请求匹配多个 FlowSchema 且 matchingPrecedence
的值相同,则按 name
的字典序选择最小,
但是最好不要依赖它,而是确保不存在两个 FlowSchema 具有相同的 matchingPrecedence
值。
当给定的请求与某个 FlowSchema 的 rules
的其中一条匹配,那么就认为该请求与该 FlowSchema 匹配。
判断规则与该请求是否匹配,不仅 要求该条规则的 subjects
字段至少存在一个与该请求相匹配,
而且 要求该条规则的 resourceRules
或 nonResourceRules
(取决于传入请求是针对资源URL还是非资源URL)字段至少存在一个与该请求相匹配。
对于 subjects
中的 name
字段和资源和非资源规则的
verbs
、apiGroups
、resources
、namespaces
和 nonResourceURLs
字段,
可以指定通配符 *
来匹配任意值,从而有效地忽略该字段。
FlowSchema 的 distinguisherMethod.type
字段决定了如何把与该模式匹配的请求分散到各个流中。
可能是 ByUser
,在这种情况下,一个请求用户将无法饿死其他容量的用户;
或者是 ByNamespace
,在这种情况下,一个名字空间中的资源请求将无法饿死其它名字空间的资源请求;
或者它可以为空(或者可以完全省略 distinguisherMethod
),
在这种情况下,与此 FlowSchema 匹配的请求将被视为单个流的一部分。
资源和你的特定环境决定了如何选择正确一个 FlowSchema。
默认值 每个 kube-apiserver 会维护两种类型的 APF 配置对象:强制的(Mandatory)和建议的(Suggested)。
强制的配置对象 有四种强制的配置对象对应内置的守护行为。这里的行为是服务器在还未创建对象之前就具备的行为,
而当这些对象存在时,其规约反映了这类行为。四种强制的对象如下:
强制的 exempt
优先级用于完全不受流控限制的请求:它们总是立刻被分发。
强制的 exempt
FlowSchema 把 system:masters
组的所有请求都归入该优先级。
如果合适,你可以定义新的 FlowSchema,将其他请求定向到该优先级。 强制的 catch-all
优先级与强制的 catch-all
FlowSchema 结合使用,
以确保每个请求都分类。一般而言,你不应该依赖于 catch-all
的配置,
而应适当地创建自己的 catch-all
FlowSchema 和 PriorityLevelConfiguration
(或使用默认安装的 global-default
配置)。
因为这一优先级不是正常场景下要使用的,catch-all
优先级的并发度份额很小,
并且不会对请求进行排队。 建议的配置对象 建议的 FlowSchema 和 PriorityLevelConfiguration 包含合理的默认配置。
你可以修改这些对象或者根据需要创建新的配置对象。如果你的集群可能承受较重负载,
那么你就要考虑哪种配置最合适。
建议的配置把请求分为六个优先级:
node-high
优先级用于来自节点的健康状态更新。system
优先级用于 system:nodes
组(即 kubelet)的与健康状态更新无关的请求;
kubelets 必须能连上 API 服务器,以便工作负载能够调度到其上。leader-election
优先级用于内置控制器的领导选举的请求
(特别是来自 kube-system
名字空间中 system:kube-controller-manager
和
system:kube-scheduler
用户和服务账号,针对 endpoints
、configmaps
或 leases
的请求)。
将这些请求与其他流量相隔离非常重要,因为领导者选举失败会导致控制器发生故障并重新启动,
这反过来会导致新启动的控制器在同步信息时,流量开销更大。workload-high
优先级用于内置控制器的其他请求。workload-low
优先级用于来自所有其他服务帐户的请求,通常包括来自 Pod
中运行的控制器的所有请求。global-default
优先级可处理所有其他流量,例如:非特权用户运行的交互式
kubectl
命令。建议的 FlowSchema 用来将请求导向上述的优先级内,这里不再一一列举。
强制的与建议的配置对象的维护 每个 kube-apiserver
都独立地维护其强制的与建议的配置对象,
这一维护操作既是服务器的初始行为,也是其周期性操作的一部分。
因此,当存在不同版本的服务器时,如果各个服务器对于配置对象中的合适内容有不同意见,
就可能出现抖动。
每个 kube-apiserver
都会对强制的与建议的配置对象执行初始的维护操作,
之后(每分钟)对这些对象执行周期性的维护。
对于强制的配置对象,维护操作包括确保对象存在并且包含合适的规约(如果存在的话)。
服务器会拒绝创建或更新与其守护行为不一致的规约。
对建议的配置对象的维护操作被设计为允许其规约被重载。删除操作是不允许的,
维护操作期间会重建这类配置对象。如果你不需要某个建议的配置对象,
你需要将它放在一边,并让其规约所产生的影响最小化。
对建议的配置对象而言,其维护方面的设计也支持在上线新的 kube-apiserver
时完成自动的迁移动作,即便可能因为当前的服务器集合存在不同的版本而可能造成抖动仍是如此。
对建议的配置对象的维护操作包括基于服务器建议的规约创建对象
(如果对象不存在的话)。反之,如果对象已经存在,维护操作的行为取决于是否
kube-apiserver
或者用户在控制对象。如果 kube-apiserver
在控制对象,
则服务器确保对象的规约与服务器所给的建议匹配,如果用户在控制对象,
对象的规约保持不变。
关于谁在控制对象这个问题,首先要看对象上的 apf.kubernetes.io/autoupdate-spec
注解。如果对象上存在这个注解,并且其取值为true
,则 kube-apiserver
在控制该对象。如果存在这个注解,并且其取值为false
,则用户在控制对象。
如果这两个条件都不满足,则需要进一步查看对象的 metadata.generation
。
如果该值为 1,则 kube-apiserver 控制对象,否则用户控制对象。
这些规则是在 1.22 发行版中引入的,而对 metadata.generation
的考量是为了便于从之前较简单的行为迁移过来。希望控制建议的配置对象的用户应该将对象的
apf.kubernetes.io/autoupdate-spec
注解设置为 false
。
对强制的或建议的配置对象的维护操作也包括确保对象上存在 apf.kubernetes.io/autoupdate-spec
这一注解,并且其取值准确地反映了是否 kube-apiserver 在控制着对象。
维护操作还包括删除那些既非强制又非建议的配置,同时注解配置为
apf.kubernetes.io/autoupdate-spec=true
的对象。
健康检查并发豁免 推荐配置没有为本地 kubelet 对 kube-apiserver 执行健康检查的请求进行任何特殊处理
——它们倾向于使用安全端口,但不提供凭据。
在推荐配置中,这些请求将分配 global-default
FlowSchema 和 global-default
优先级,
这样其他流量可以排除健康检查。
如果添加以下 FlowSchema,健康检查请求不受速率限制。
注意: 进行此更改后,任何敌对方都可以发送与此 FlowSchema 匹配的任意数量的健康检查请求。
如果你有 Web 流量过滤器或类似的外部安全机制保护集群的 API 服务器免受常规网络流量的侵扰,
则可以配置规则,阻止所有来自集群外部的健康检查请求。
apiVersion : flowcontrol.apiserver.k8s.io/v1beta2
kind : FlowSchema
metadata :
name : health-for-strangers
spec :
matchingPrecedence : 1000
priorityLevelConfiguration :
name : exempt
rules :
- nonResourceRules :
- nonResourceURLs :
- "/healthz"
- "/livez"
- "/readyz"
verbs :
- "*"
subjects :
- kind : Group
group :
name : system:unauthenticated
问题诊断 启用了 APF 的 API 服务器,它每个 HTTP 响应都有两个额外的 HTTP 头:
X-Kubernetes-PF-FlowSchema-UID
和 X-Kubernetes-PF-PriorityLevel-UID
,
注意与请求匹配的 FlowSchema 和已分配的优先级。
如果请求用户没有查看这些对象的权限,则这些 HTTP 头中将不包含 API 对象的名称,
因此在调试时,你可以使用类似如下的命令:
kubectl get flowschemas -o custom-columns= "uid:{metadata.uid},name:{metadata.name}"
kubectl get prioritylevelconfigurations -o custom-columns= "uid:{metadata.uid},name:{metadata.name}"
来获取 UID 到 FlowSchema 的名称和 UID 到 PriorityLevelConfiguration 的名称的映射。
可观察性 指标 说明: 在 Kubernetes v1.20 之前的版本中,标签 flow_schema
和 priority_level
的名称有时被写作 flowSchema
和 priorityLevel
,即存在不一致的情况。
如果你在运行 Kubernetes v1.19 或者更早版本,你需要参考你所使用的集群版本对应的文档。
当你开启了 APF 后,kube-apiserver 会暴露额外指标。
监视这些指标有助于判断你的配置是否不当地限制了重要流量,
或者发现可能会损害系统健康的,行为不良的工作负载。
apiserver_flowcontrol_rejected_requests_total
是一个计数器向量,
记录被拒绝的请求数量(自服务器启动以来累计值),
由标签 flow_chema
(表示与请求匹配的 FlowSchema)、priority_evel
(表示分配给请该求的优先级)和 reason
来区分。
reason
标签将具有以下值之一:queue-full
,表明已经有太多请求排队,concurrency-limit
,表示将 PriorityLevelConfiguration 配置为
Reject
而不是 Queue
,或者time-out
,表示在其排队时间超期的请求仍在队列中。apiserver_flowcontrol_dispatched_requests_total
是一个计数器向量,
记录开始执行的请求数量(自服务器启动以来的累积值),
由标签 flow_schema
(表示与请求匹配的 FlowSchema)和
priority_level
(表示分配给该请求的优先级)来区分。apiserver_current_inqueue_requests
是一个表向量,
记录最近排队请求数量的高水位线,
由标签 request_kind
分组,标签的值为 mutating
或 readOnly
。
这些高水位线表示在最近一秒钟内看到的最大数字。
它们补充说明了老的表向量 apiserver_current_inflight_requests
(该量保存了最后一个窗口中,正在处理的请求数量的高水位线)。apiserver_flowcontrol_read_vs_write_request_count_samples
是一个直方图向量,
记录当前请求数量的观察值,
由标签 phase
(取值为 waiting
和 executing
)和 request_kind
(取值 mutating
和 readOnly
)拆分。定期以高速率观察该值。
每个观察到的值是一个介于 0 和 1 之间的比值,计算方式为请求数除以该请求数的对应限制
(等待的队列长度限制和执行所用的并发限制)。apiserver_flowcontrol_read_vs_write_request_count_watermarks
是一个直方图向量,
记录请求数量的高/低水位线,
由标签 phase
(取值为 waiting
和 executing
)和 request_kind
(取值为 mutating
和 readOnly
)拆分;标签 mark
取值为 high
和 low
。
apiserver_flowcontrol_read_vs_write_request_count_samples
向量观察到有值新增,
则该向量累积。这些水位线显示了样本值的范围。apiserver_flowcontrol_current_inqueue_requests
是一个表向量,
记录包含排队中的(未执行)请求的瞬时数量,
由标签 priorityLevel
和 flowSchema
拆分。apiserver_flowcontrol_current_executing_requests
是一个表向量,
记录包含执行中(不在队列中等待)请求的瞬时数量,
由标签 priority_level
和 flow_schema
进一步区分。apiserver_flowcontrol_request_concurrency_in_use
是一个规范向量,
包含占用座位的瞬时数量,由标签 priority_level
和 flow_schema
进一步区分。apiserver_flowcontrol_priority_level_request_count_samples
是一个直方图向量,
记录当前请求的观测值,由标签 phase
(取值为waiting
和 executing
)和
priority_level
进一步区分。
每个直方图都会定期进行观察,直到相关类别的最后活动为止。观察频率高。apiserver_flowcontrol_priority_level_request_count_watermarks
是一个直方图向量,
记录请求数的高/低水位线,由标签 phase
(取值为 waiting
和 executing
)和
priority_level
拆分;
标签 mark
取值为 high
和 low
。
apiserver_flowcontrol_priority_level_request_count_samples
向量观察到有值新增,
则该向量累积。这些水位线显示了样本值的范围。apiserver_flowcontrol_request_concurrency_limit
是一个表向量,
记录并发限制的计算值(基于 API 服务器的总并发限制和 PriorityLevelConfiguration
的并发份额),并按标签 priority_level
进一步区分。apiserver_flowcontrol_request_execution_seconds
是一个直方图向量,
记录请求实际执行需要花费的时间,
由标签 flow_schema
(表示与请求匹配的 FlowSchema)和
priority_level
(表示分配给该请求的优先级)进一步区分。调试端点 启用 APF 特性后, kube-apiserver 会在其 HTTP/HTTPS 端口提供以下路径:
/debug/api_priority_and_fairness/dump_priority_levels
——
所有优先级及其当前状态的列表。你可以这样获取:
kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels
输出类似于:
PriorityLevelName, ActiveQueues, IsIdle, IsQuiescing, WaitingRequests, ExecutingRequests,
workload-low, 0, true, false, 0, 0,
global-default, 0, true, false, 0, 0,
exempt, <none>, <none>, <none>, <none>, <none>,
catch-all, 0, true, false, 0, 0,
system, 0, true, false, 0, 0,
leader-election, 0, true, false, 0, 0,
workload-high, 0, true, false, 0, 0,
/debug/api_priority_and_fairness/dump_queues
—— 所有队列及其当前状态的列表。
你可以这样获取:
kubectl get --raw /debug/api_priority_and_fairness/dump_queues
输出类似于:
PriorityLevelName, Index, PendingRequests, ExecutingRequests, VirtualStart,
workload-high, 0, 0, 0, 0.0000,
workload-high, 1, 0, 0, 0.0000,
workload-high, 2, 0, 0, 0.0000,
...
leader-election, 14, 0, 0, 0.0000,
leader-election, 15, 0, 0, 0.0000,
/debug/api_priority_and_fairness/dump_requests
—— 当前正在队列中等待的所有请求的列表。
你可以这样获取:
kubectl get --raw /debug/api_priority_and_fairness/dump_requests
输出类似于:
PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher, ArriveTime,
exempt, <none>, <none>, <none>, <none>, <none>,
system, system-nodes, 12, 0, system:node:127.0.0.1, 2020-07-23T15:26:57.179170694Z,
针对每个优先级别,输出中还包含一条虚拟记录,对应豁免限制。
你可以使用以下命令获得更详细的清单:
kubectl get --raw '/debug/api_priority_and_fairness/dump_requests?includeRequestDetails=1'
输出类似于:
PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher, ArriveTime, UserName, Verb, APIPath, Namespace, Name, APIVersion, Resource, SubResource,
system, system-nodes, 12, 0, system:node:127.0.0.1, 2020-07-23T15:31:03.583823404Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps,
system, system-nodes, 12, 1, system:node:127.0.0.1, 2020-07-23T15:31:03.594555947Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps,
接下来 有关 API 优先级和公平性的设计细节的背景信息,
请参阅增强提案 。
你可以通过 SIG API Machinery
或特性的 Slack 频道 提出建议和特性请求。
3.12.10 - 安装扩展(Addons) 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Add-ons 扩展了 Kubernetes 的功能。
本文列举了一些可用的 add-ons 以及到它们各自安装说明的链接。该列表并不试图详尽无遗。
联网和网络策略 ACI 通过 Cisco ACI 提供集成的容器网络和安全网络。Antrea 在第 3/4 层执行操作,为 Kubernetes
提供网络连接和安全服务。Antrea 利用 Open vSwitch 作为网络的数据面。Calico 是一个联网和网络策略供应商。
Calico 支持一套灵活的网络选项,因此你可以根据自己的情况选择最有效的选项,包括非覆盖和覆盖网络,带或不带 BGP。
Calico 使用相同的引擎为主机、Pod 和(如果使用 Istio 和 Envoy)应用程序在服务网格层执行网络策略。Canal 结合 Flannel 和 Calico,提供联网和网络策略。Cilium 是一种网络、可观察性和安全解决方案,具有基于 eBPF 的数据平面。
Cilium 提供了简单的 3 层扁平网络,
能够以原生路由(routing)和覆盖/封装(overlay/encapsulation)模式跨越多个集群,
并且可以使用与网络寻址分离的基于身份的安全模型在 L3 至 L7 上实施网络策略。
Cilium 可以作为 kube-proxy 的替代品;它还提供额外的、可选的可观察性和安全功能。CNI-Genie 使 Kubernetes 无缝连接到
Calico、Canal、Flannel 或 Weave 等其中一种 CNI 插件。Contiv 为各种用例和丰富的策略框架提供可配置的网络
(带 BGP 的原生 L3、带 vxlan 的覆盖、标准 L2 和 Cisco-SDN/ACI)。
Contiv 项目完全开源 。
其安装程序 提供了基于 kubeadm 和非 kubeadm 的安装选项。Contrail 基于
Tungsten Fabric ,是一个开源的多云网络虚拟化和策略管理平台。
Contrail 和 Tungsten Fabric 与业务流程系统(例如 Kubernetes、OpenShift、OpenStack 和 Mesos)集成在一起,
为虚拟机、容器或 Pod 以及裸机工作负载提供了隔离模式。Flannel
是一个可以用于 Kubernetes 的 overlay 网络提供者。Knitter 是在一个 Kubernetes Pod 中支持多个网络接口的插件。Multus 是一个多插件,
可在 Kubernetes 中提供多种网络支持,以支持所有 CNI 插件(例如 Calico、Cilium、Contiv、Flannel),
而且包含了在 Kubernetes 中基于 SRIOV、DPDK、OVS-DPDK 和 VPP 的工作负载。OVN-Kubernetes 是一个 Kubernetes 网络驱动,
基于 OVN(Open Virtual Network) 实现,是从 Open vSwitch (OVS)
项目衍生出来的虚拟网络实现。OVN-Kubernetes 为 Kubernetes 提供基于覆盖网络的网络实现,
包括一个基于 OVS 实现的负载均衡器和网络策略。Nodus 是一个基于 OVN 的 CNI 控制器插件,
提供基于云原生的服务功能链 (SFC)。NSX-T 容器插件(NCP)
提供了 VMware NSX-T 与容器协调器(例如 Kubernetes)之间的集成,以及 NSX-T 与基于容器的
CaaS / PaaS 平台(例如关键容器服务(PKS)和 OpenShift)之间的集成。Nuage
是一个 SDN 平台,可在 Kubernetes Pods 和非 Kubernetes 环境之间提供基于策略的联网,并具有可视化和安全监控。Romana 是一个 Pod 网络的第三层解决方案,并支持
NetworkPolicy API。Weave Net
提供在网络分组两端参与工作的联网和网络策略,并且不需要额外的数据库。服务发现 CoreDNS 是一种灵活的,可扩展的 DNS 服务器,可以
安装 为集群内的 Pod 提供 DNS 服务。可视化管理 基础设施 遗留 Add-ons 还有一些其它 add-ons 归档在已废弃的 cluster/addons 路径中。
维护完善的 add-ons 应该被链接到这里。欢迎提出 PRs!
3.13 - 扩展 Kubernetes 改变你的 Kubernetes 集群的行为的若干方法。
Kubernetes 是高度可配置且可扩展的。因此,大多数情况下,
你不需要派生自己的 Kubernetes 副本或者向项目代码提交补丁。
本指南描述定制 Kubernetes 的可选方式。主要针对的读者是希望了解如何针对自身工作环境需要来调整
Kubernetes 的集群管理者 。
对于那些充当平台开发人员 的开发人员或
Kubernetes 项目的贡献者 而言,
他们也会在本指南中找到有用的介绍信息,了解系统中存在哪些扩展点和扩展模式,
以及它们所附带的各种权衡和约束等等。
定制化的方法主要可分为配置 和扩展 两种。
前者主要涉及更改命令行参数、本地配置文件或者 API 资源;
后者则需要额外运行一些程序、网络服务或两者。
本文主要关注扩展 。
配置 配置文件 和命令参数 的说明位于在线文档的参考 一节,
每个可执行文件一个页面:
在托管的 Kubernetes 服务中或者受控安装的发行版本中,命令参数和配置文件不总是可以修改的。
即使它们是可修改的,通常其修改权限也仅限于集群操作员。
此外,这些内容在将来的 Kubernetes 版本中很可能发生变化,设置新参数或配置文件可能需要重启进程。
有鉴于此,应该在没有其他替代方案时才会使用这些命令参数和配置文件。
诸如 ResourceQuota 、
NetworkPolicy
和基于角色的访问控制(RBAC )
等内置策略 API 都是以声明方式配置策略选项的内置 Kubernetes API。
即使在托管的 Kubernetes 服务和受控的 Kubernetes 安装环境中,API 通常也是可用的。
内置策略 API 遵循与 Pod 这类其他 Kubernetes 资源相同的约定。
当你使用稳定版本 的策略 API,
它们与其他 Kubernetes API 一样,采纳的是一种预定义的支持策略 。
出于以上原因,在条件允许的情况下,基于策略 API 的方案应该优先于配置文件 和命令参数 。
扩展 扩展(Extensions)是一些扩充 Kubernetes 能力并与之深度集成的软件组件。
它们调整 Kubernetes 的工作方式使之支持新的类型和新的硬件种类。
大多数集群管理员会使用一种托管的 Kubernetes 服务或者其某种发行版本。
这类集群通常都预先安装了扩展。因此,大多数 Kubernetes 用户不需要安装扩展,
至于需要自己编写新的扩展的情况就更少了。
扩展模式 Kubernetes 从设计上即支持通过编写客户端程序来将其操作自动化。
任何能够对 Kubernetes API 发出读写指令的程序都可以提供有用的自动化能力。
自动化组件 可以运行在集群上,也可以运行在集群之外。
通过遵从本文中的指南,你可以编写高度可用的、运行稳定的自动化组件。
自动化组件通常可以用于所有 Kubernetes 集群,包括托管的集群和受控的安装环境。
编写客户端程序有一种特殊的控制器(Controller) 模式,
能够与 Kubernetes 很好地协同工作。控制器通常会读取某个对象的 .spec
,或许还会执行一些操作,
之后更新对象的 .status
。
控制器是 Kubernetes API 的客户端。当 Kubernetes 充当客户端且调用某远程服务时,
Kubernetes 将此称作 Webhook 。该远程服务称作 Webhook 后端 。
与定制的控制器相似,Webhook 也会引入失效点(Point of Failure)。
说明: 在 Kubernetes 之外,“Webhook” 这个词通常是指一种异步通知机制,
其中 Webhook 调用将用作对另一个系统或组件的单向通知。
在 Kubernetes 生态系统中,甚至同步的 HTTP 调用也经常被描述为 “Webhook”。
在 Webhook 模型中,Kubernetes 向远程服务发起网络请求。
在另一种称作可执行文件插件(Binary Plugin) 模型中,Kubernetes 执行某个可执行文件(程序)。
这些可执行文件插件由 kubelet(例如,CSI 存储插件 和
CNI 网络插件 )
和 kubectl 使用。
扩展点 下图展示了 Kubernetes 集群中的这些扩展点及其访问集群的客户端。
Kubernetes 扩展点
用户通常使用 kubectl
与 Kubernetes API 交互。
插件 定制客户端的行为。
有一些通用的扩展可以应用到不同的客户端,还有一些特定的方式可以扩展 kubectl
。
API 服务器处理所有请求。API 服务器中的几种扩展点能够使用户对请求执行身份认证、
基于其内容阻止请求、编辑请求内容、处理删除操作等等。
这些扩展点在 API 访问扩展 节详述。
API 服务器能提供各种类型的资源(Resources) 服务。
诸如 pods
的内置资源类型 是由 Kubernetes 项目所定义的,无法改变。
请查阅 API 扩展 了解如何扩展 Kubernetes API。
Kubernetes 调度器负责决定
Pod 要放置到哪些节点上执行。有几种方式来扩展调度行为,这些方法将在调度器扩展 节中展开说明。
Kubernetes 中的很多行为都是通过称为控制器(Controller) 的程序来实现的,
这些程序也都是 API 服务器的客户端。控制器常常与定制资源结合使用。
进一步了解请查阅结合使用新的 API 与自动化组件 和更改内置资源 。
Kubelet 运行在各个服务器(节点)上,帮助 Pod 展现为虚拟的服务器并在集群网络中拥有自己的 IP。
网络插件 使得 Kubernetes 能够采用不同实现技术来连接 Pod 网络。
你可以使用设备插件 集成定制硬件或其他专用的节点本地设施,
使得这些设施可用于集群中运行的 Pod。Kubelet 包括了对使用设备插件的支持。
kubelet 也会为 Pod 及其容器增加或解除卷 的挂载。
你可以使用存储插件 增加对新存储类别和其他卷类型的支持。
扩展点选择流程图 如果你无法确定从何处入手,下面的流程图可能对你有些帮助。
注意,某些方案可能需要同时采用几种类型的扩展。
选择一个扩展方式的流程图指导
客户端扩展 kubectl 所用的插件是单独的二进制文件,用于添加或替换特定子命令的行为。
kubectl
工具还可以与凭据插件 集成。
这些扩展只影响单个用户的本地环境,因此不能强制执行站点范围的策略。
如果你要扩展 kubectl
工具,请阅读用插件扩展 kubectl 。
API 扩展 定制资源对象 如果你想要定义新的控制器、应用配置对象或者其他声明式 API,并且使用 Kubernetes
工具(如 kubectl
)来管理它们,可以考虑向 Kubernetes 添加定制资源 。
关于定制资源的更多信息,可参见定制资源概念指南 。
API 聚合层 你可以使用 Kubernetes 的
API 聚合层 将
Kubernetes API 与其他服务集成,例如指标 。
结合使用新 API 与自动化组件 定制资源 API 与控制回路的组合称作控制器 模式。
如果你的控制器代替人工操作员根据所需状态部署基础设施,那么控制器也可以遵循
Operator 模式 。
Operator 模式用于管理特定的应用;通常,这些应用需要维护状态并需要仔细考虑状态的管理方式。
你还可以创建自己的定制 API 和控制回路来管理其他资源(例如存储)或定义策略(例如访问控制限制)。
更改内置资源 当你通过添加定制资源来扩展 Kubernetes 时,所添加的资源总是会被放在一个新的 API 组中。
你不可以替换或更改现有的 API 组。添加新的 API 不会直接让你影响现有
API(如 Pod)的行为,不过 API 访问扩展 能够实现这点。
API 访问扩展 当请求到达 Kubernetes API 服务器时,首先要经过身份认证 ,之后是鉴权 操作,
再之后要经过若干类型的准入控制 (某些请求实际上未通过身份认证,需要特殊处理)。
参见控制 Kubernetes API 访问 以了解此流程的细节。
Kubernetes 身份认证/授权流程中的每个步骤都提供了扩展点。
身份认证 身份认证 负责将所有请求中的头部或证书映射到发出该请求的客户端的用户名。
Kubernetes 提供若干内置的身份认证方法。它也可以运行在某种身份认证代理的后面,
并且可以将来自 Authorization:
头部的令牌发送到某个远程服务
(认证 Webhook
来执行验证操作,以备内置方法无法满足你的要求。
鉴权 鉴权 操作负责确定特定的用户是否可以读、写 API
资源或对其执行其他操作。此操作仅在整个资源集合的层面进行。
换言之,它不会基于对象的特定字段作出不同的判决。
如果内置的鉴权选项无法满足你的需要,
你可以使用鉴权 Webhook 来调用用户提供的代码,
执行定制的鉴权决定。
动态准入控制 请求的鉴权操作结束之后,如果请求的是写操作,
还会经过准入控制 处理步骤。
除了内置的处理步骤,还存在一些扩展点:
镜像策略 Webhook
能够限制容器中可以运行哪些镜像。为了执行任意的准入控制决定,
可以使用一种通用的准入 Webhook
机制。这类准入 Webhook 可以拒绝创建或更新请求。
一些准入 Webhook 会先修改传入的请求数据,才会由 Kubernetes 进一步处理这些传入请求数据。 基础设施扩展 设备插件 设备插件 允许一个节点通过设备插件 发现新的
Node 资源(除了内置的类似 CPU 和内存这类资源之外)。
存储插件 容器存储接口 (CSI) 插件提供了一种扩展
Kubernetes 的方式使其支持新类别的卷。
这些卷可以由持久的外部存储提供支持,可以提供临时存储,还可以使用文件系统范型为信息提供只读接口。
Kubernetes 还包括对 FlexVolume
插件的支持,该插件自 Kubernetes v1.23 起被弃用(被 CSI 替代)。
FlexVolume 插件允许用户挂载 Kubernetes 本身不支持的卷类型。
当你运行依赖于 FlexVolume 存储的 Pod 时,kubelet 会调用一个二进制插件来挂载该卷。
归档的 FlexVolume
设计提案对此方法有更多详细说明。
Kubernetes 存储供应商的卷插件 FAQ
包含了有关存储插件的通用信息。
网络插件 你的 Kubernetes 集群需要一个网络插件 才能拥有一个正常工作的 Pod 网络,
才能支持 Kubernetes 网络模型的其他方面。
[网络插件](/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/可以让
Kubernetes 使用不同的网络拓扑和技术。
调度扩展 调度器是一种特殊的控制器,负责监视 Pod 变化并将 Pod 分派给节点。
默认的调度器可以被整体替换掉,同时继续使用其他 Kubernetes 组件。
或者也可以在同一时刻使用多个调度器 。
这是一项非同小可的任务,几乎绝大多数 Kubernetes
用户都会发现其实他们不需要修改调度器。
你可以控制哪些调度插件 处于激活状态,
或将插件集关联到名字不同的调度器配置文件 上。
你还可以编写自己的插件,与一个或多个 kube-scheduler
的扩展点 集成。
最后,内置的 kube-scheduler
组件支持
Webhook ,
从而允许远程 HTTP 后端(调度器扩展)来为 kube-scheduler 选择的 Pod 所在节点执行过滤和优先排序操作。
说明: 你只能使用调度器扩展程序 Webhook 来影响节点过滤和节点优先排序;
其他扩展点无法通过集成 Webhook 获得。
接下来 3.13.1 - Operator 模式 Operator 是 Kubernetes 的扩展软件,
它利用定制资源 管理应用及其组件。
Operator 遵循 Kubernetes 的理念,特别是在控制器 方面。
初衷 Operator 模式 旨在记述(正在管理一个或一组服务的)运维人员的关键目标。
这些运维人员负责一些特定的应用和 Service,他们需要清楚地知道系统应该如何运行、如何部署以及出现问题时如何处理。
在 Kubernetes 上运行工作负载的人们都喜欢通过自动化来处理重复的任务。
Operator 模式会封装你编写的(Kubernetes 本身提供功能以外的)任务自动化代码。
Kubernetes 上的 Operator Kubernetes 为自动化而生。无需任何修改,你即可以从 Kubernetes 核心中获得许多内置的自动化功能。
你可以使用 Kubernetes 自动化部署和运行工作负载, 甚至 可以自动化 Kubernetes 自身。
Kubernetes 的 Operator 模式 概念允许你在不修改
Kubernetes 自身代码的情况下,
通过为一个或多个自定义资源关联控制器 来扩展集群的能力。
Operator 是 Kubernetes API 的客户端,
充当自定义资源 的控制器。
Operator 示例 使用 Operator 可以自动化的事情包括:
按需部署应用 获取/还原应用状态的备份 处理应用代码的升级以及相关改动。例如,数据库 schema 或额外的配置设置 发布一个 service,要求不支持 Kubernetes API 的应用也能发现它 模拟整个或部分集群中的故障以测试其稳定性 在没有内部成员选举程序的情况下,为分布式应用选择首领角色 想要更详细的了解 Operator?下面是一个示例:
有一个名为 SampleDB 的自定义资源,你可以将其配置到集群中。 一个包含 Operator 控制器部分的 Deployment,用来确保 Pod 处于运行状态。 Operator 代码的容器镜像。 控制器代码,负责查询控制平面以找出已配置的 SampleDB 资源。 Operator 的核心是告诉 API 服务器,如何使现实与代码里配置的资源匹配。如果添加新的 SampleDB,Operator 将设置 PersistentVolumeClaims 以提供持久化的数据库存储,
设置 StatefulSet 以运行 SampleDB,并设置 Job 来处理初始配置。 如果你删除它,Operator 将建立快照,然后确保 StatefulSet 和 Volume 已被删除。 Operator 也可以管理常规数据库的备份。对于每个 SampleDB 资源,Operator
会确定何时创建(可以连接到数据库并进行备份的)Pod。这些 Pod 将依赖于
ConfigMap 和/或具有数据库连接详细信息和凭据的 Secret。 由于 Operator 旨在为其管理的资源提供强大的自动化功能,因此它还需要一些额外的支持性代码。
在这个示例中,代码将检查数据库是否正运行在旧版本上,
如果是,则创建 Job 对象为你升级数据库。 部署 Operator 部署 Operator 最常见的方法是将自定义资源及其关联的控制器添加到你的集群中。
跟运行容器化应用一样,控制器通常会运行在控制平面 之外。
例如,你可以在集群中将控制器作为 Deployment 运行。
使用 Operator 部署 Operator 后,你可以对 Operator 所使用的资源执行添加、修改或删除操作。
按照上面的示例,你将为 Operator 本身建立一个 Deployment,然后:
kubectl get SampleDB # 查找所配置的数据库
kubectl edit SampleDB/example-database # 手动修改某些配置
可以了!Operator 会负责应用所作的更改并保持现有服务处于良好的状态。
编写你自己的 Operator 如果生态系统中没可以实现你目标的 Operator,你可以自己编写代码。
你还可以使用任何支持
Kubernetes API 客户端 的语言或运行时来实现
Operator(即控制器)。
以下是一些库和工具,你可用于编写自己的云原生 Operator。
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
接下来 3.13.2 - 计算、存储和网络扩展 本节介绍不属于 Kubernetes 本身组成部分的一些集群扩展。
你可以使用这些扩展来增强集群中的节点,或者提供将 Pod 关联在一起的网络结构。
CSI 和
FlexVolume 存储插件
容器存储接口 (CSI) 插件提供了一种扩展
Kubernetes 的方式使其支持新类别的卷。
这些卷可以由持久的外部存储提供支持,可以提供临时存储,还可以使用文件系统范型为信息提供只读接口。
Kubernetes 还包括对 FlexVolume
插件的扩展支持,该插件自 Kubernetes v1.23 起被弃用(被 CSI 替代)。
FlexVolume 插件允许用户挂载 Kubernetes 本身不支持的卷类型。
当你运行依赖于 FlexVolume 存储的 Pod 时,kubelet 会调用一个二进制插件来挂载该卷。
归档的 FlexVolume
设计提案对此方法有更多详细说明。
Kubernetes 存储供应商的卷插件 FAQ
包含了有关存储插件的通用信息。
3.13.2.1 - 网络插件 Kubernetes 1.25 支持用于集群联网的容器网络接口 (CNI) 插件。
你必须使用和你的集群相兼容并且满足你的需求的 CNI 插件。
在更广泛的 Kubernetes 生态系统中你可以使用不同的插件(开源和闭源)。
要实现 Kubernetes 网络模型 ,你需要一个 CNI 插件。
你必须使用与 v0.4.0
或更高版本的 CNI 规范相符合的 CNI 插件。
Kubernetes 推荐使用一个兼容 v1.0.0
CNI 规范的插件(插件可以兼容多个规范版本)。
安装 在网络语境中,容器运行时(Container Runtime)是在节点上的守护进程,
被配置用来为 kubelet 提供 CRI 服务。具体而言,容器运行时必须配置为加载所需的
CNI 插件,从而实现 Kubernetes 网络模型。
说明: 在 Kubernetes 1.24 之前,CNI 插件也可以由 kubelet 使用命令行参数 cni-bin-dir
和 network-plugin
管理。Kubernetes 1.24 移除了这些命令行参数,
CNI 的管理不再是 kubelet 的工作。
如果你在移除 dockershim 之后遇到问题,请参阅排查 CNI 插件相关的错误 。
要了解容器运行时如何管理 CNI 插件的具体信息,可参见对应容器运行时的文档,例如:
要了解如何安装和管理 CNI 插件的具体信息,可参阅对应的插件或
网络驱动(Networking Provider)
的文档。
网络插件要求 对于插件开发人员以及时常会构建并部署 Kubernetes 的用户而言,
插件可能也需要特定的配置来支持 kube-proxy。
iptables 代理依赖于 iptables,插件可能需要确保 iptables 能够监控容器的网络通信。
例如,如果插件将容器连接到 Linux 网桥,插件必须将 net/bridge/bridge-nf-call-iptables
sysctl 参数设置为 1
,以确保 iptables 代理正常工作。
如果插件不使用 Linux 网桥,而是使用类似于 Open vSwitch 或者其它一些机制,
它应该确保为代理对容器通信执行正确的路由。
默认情况下,如果未指定 kubelet 网络插件,则使用 noop
插件,
该插件设置 net/bridge/bridge-nf-call-iptables=1
,以确保简单的配置
(如带网桥的 Docker )与 iptables 代理正常工作。
本地回路 CNI 除了安装到节点上用于实现 Kubernetes 网络模型的 CNI 插件外,Kubernetes
还需要容器运行时提供一个本地回路接口 lo
,用于各个沙箱(Pod 沙箱、虚机沙箱……)。
实现本地回路接口的工作可以通过复用
CNI 本地回路插件 来实现,
也可以通过开发自己的代码来实现
(参阅 CRI-O 中的示例 )。
支持 hostPort CNI 网络插件支持 hostPort
。 你可以使用官方
portmap
插件,它由 CNI 插件团队提供,或者使用你自己的带有 portMapping 功能的插件。
如果你想要启动 hostPort
支持,则必须在 cni-conf-dir
指定 portMappings capability
。
例如:
{
"name" : "k8s-pod-network" ,
"cniVersion" : "0.4.0" ,
"plugins" : [
{
"type" : "calico" ,
"log_level" : "info" ,
"datastore_type" : "kubernetes" ,
"nodename" : "127.0.0.1" ,
"ipam" : {
"type" : "host-local" ,
"subnet" : "usePodCidr"
},
"policy" : {
"type" : "k8s"
},
"kubernetes" : {
"kubeconfig" : "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type" : "portmap" ,
"capabilities" : {"portMappings" : true }
}
]
}
支持流量整形 实验功能
CNI 网络插件还支持 pod 入口和出口流量整形。
你可以使用 CNI 插件团队提供的
bandwidth
插件,也可以使用你自己的具有带宽控制功能的插件。
如果你想要启用流量整形支持,你必须将 bandwidth
插件添加到 CNI 配置文件
(默认是 /etc/cni/net.d
)并保证该可执行文件包含在你的 CNI 的 bin
文件夹内 (默认为 /opt/cni/bin
)。
{
"name" : "k8s-pod-network" ,
"cniVersion" : "0.4.0" ,
"plugins" : [
{
"type" : "calico" ,
"log_level" : "info" ,
"datastore_type" : "kubernetes" ,
"nodename" : "127.0.0.1" ,
"ipam" : {
"type" : "host-local" ,
"subnet" : "usePodCidr"
},
"policy" : {
"type" : "k8s"
},
"kubernetes" : {
"kubeconfig" : "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type" : "bandwidth" ,
"capabilities" : {"bandwidth" : true }
}
]
}
现在,你可以将 kubernetes.io/ingress-bandwidth
和 kubernetes.io/egress-bandwidth
注解添加到 pod 中。例如:
apiVersion : v1
kind : Pod
metadata :
annotations :
kubernetes.io/ingress-bandwidth : 1M
kubernetes.io/egress-bandwidth : 1M
...
接下来 3.13.2.2 - 设备插件 设备插件可以让你配置集群以支持需要特定于供应商设置的设备或资源,例如 GPU、NIC、FPGA 或非易失性主存储器。
特性状态: Kubernetes v1.10 [beta]
Kubernetes 提供了一个
设备插件框架 ,
你可以用它来将系统硬件资源发布到 Kubelet 。
供应商可以实现设备插件,由你手动部署或作为 DaemonSet
来部署,而不必定制 Kubernetes 本身的代码。目标设备包括 GPU、高性能 NIC、FPGA、
InfiniBand 适配器以及其他类似的、可能需要特定于供应商的初始化和设置的计算资源。
注册设备插件 kubelet
提供了一个 Registration
的 gRPC 服务:
service Registration {
rpc Register(RegisterRequest) returns (Empty) {}
}
设备插件可以通过此 gRPC 服务在 kubelet 进行注册。在注册期间,设备插件需要发送下面几样内容:
设备插件的 Unix 套接字。 设备插件的 API 版本。 ResourceName
是需要公布的。这里 ResourceName
需要遵循扩展资源命名方案 ,
类似于 vendor-domain/resourcetype
。(比如 NVIDIA GPU 就被公布为 nvidia.com/gpu
。)成功注册后,设备插件就向 kubelet 发送它所管理的设备列表,然后 kubelet
负责将这些资源发布到 API 服务器,作为 kubelet 节点状态更新的一部分。
比如,设备插件在 kubelet 中注册了 hardware-vendor.example/foo
并报告了节点上的两个运行状况良好的设备后,节点状态将更新以通告该节点已安装 2 个
"Foo" 设备并且是可用的。
然后,用户可以请求设备作为 Pod 规范的一部分,
参见 Container 。
请求扩展资源类似于管理请求和限制的方式,
其他资源,有以下区别:
扩展资源仅可作为整数资源使用,并且不能被过量使用 设备不能在容器之间共享 示例 假设 Kubernetes 集群正在运行一个设备插件,该插件在一些节点上公布的资源为 hardware-vendor.example/foo
。
下面就是一个 Pod 示例,请求此资源以运行一个工作负载的示例:
---
apiVersion : v1
kind : Pod
metadata :
name : demo-pod
spec :
containers :
- name : demo-container-1
image : registry.k8s.io/pause:2.0
resources :
limits :
hardware-vendor.example/foo : 2
#
# 这个 pod 需要两个 hardware-vendor.example/foo 设备
# 而且只能够调度到满足需求的节点上
#
# 如果该节点中有 2 个以上的设备可用,其余的可供其他 Pod 使用
设备插件的实现 设备插件的常规工作流程包括以下几个步骤:
初始化。在这个阶段,设备插件将执行供应商特定的初始化和设置,
以确保设备处于就绪状态。
插件使用主机路径 /var/lib/kubelet/device-plugins/
下的 Unix 套接字启动一个
gRPC 服务,该服务实现以下接口:
service DevicePlugin {
// GetDevicePluginOptions 返回与设备管理器沟通的选项。
rpc GetDevicePluginOptions(Empty) returns (DevicePluginOptions) {}
// ListAndWatch 返回 Device 列表构成的数据流。
// 当 Device 状态发生变化或者 Device 消失时,ListAndWatch
// 会返回新的列表。
rpc ListAndWatch(Empty) returns (stream ListAndWatchResponse) {}
// Allocate 在容器创建期间调用,这样设备插件可以运行一些特定于设备的操作,
// 并告诉 kubelet 如何令 Device 可在容器中访问的所需执行的具体步骤
rpc Allocate(AllocateRequest) returns (AllocateResponse) {}
// GetPreferredAllocation 从一组可用的设备中返回一些优选的设备用来分配,
// 所返回的优选分配结果不一定会是设备管理器的最终分配方案。
// 此接口的设计仅是为了让设备管理器能够在可能的情况下做出更有意义的决定。
rpc GetPreferredAllocation(PreferredAllocationRequest) returns (PreferredAllocationResponse) {}
// PreStartContainer 在设备插件注册阶段根据需要被调用,调用发生在容器启动之前。
// 在将设备提供给容器使用之前,设备插件可以运行一些诸如重置设备之类的特定于
// 具体设备的操作,
rpc PreStartContainer(PreStartContainerRequest) returns (PreStartContainerResponse) {}
}
说明: 插件并非必须为 GetPreferredAllocation()
或 PreStartContainer()
提供有用的实现逻辑,
调用 GetDevicePluginOptions()
时所返回的 DevicePluginOptions
消息中应该设置这些调用是否可用。kubelet
在真正调用这些函数之前,总会调用
GetDevicePluginOptions()
来查看是否存在这些可选的函数。
插件通过 Unix socket 在主机路径 /var/lib/kubelet/device-plugins/kubelet.sock
处向 kubelet 注册自身。 成功注册自身后,设备插件将以服务模式运行,在此期间,它将持续监控设备运行状况,
并在设备状态发生任何变化时向 kubelet 报告。它还负责响应 Allocate
gRPC 请求。
在 Allocate
期间,设备插件可能还会做一些设备特定的准备;例如 GPU 清理或 QRNG 初始化。
如果操作成功,则设备插件将返回 AllocateResponse
,其中包含用于访问被分配的设备容器运行时的配置。
kubelet 将此信息传递到容器运行时。 处理 kubelet 重启 设备插件应能监测到 kubelet 重启,并且向新的 kubelet 实例来重新注册自己。
在当前实现中,当 kubelet 重启的时候,新的 kubelet 实例会删除 /var/lib/kubelet/device-plugins
下所有已经存在的 Unix 套接字。
设备插件需要能够监控到它的 Unix 套接字被删除,并且当发生此类事件时重新注册自己。
设备插件部署 你可以将你的设备插件作为节点操作系统的软件包来部署、作为 DaemonSet 来部署或者手动部署。
规范目录 /var/lib/kubelet/device-plugins
是需要特权访问的,
所以设备插件必须要在被授权的安全的上下文中运行。
如果你将设备插件部署为 DaemonSet,/var/lib/kubelet/device-plugins
目录必须要在插件的
PodSpec
中声明作为 卷(Volume) 被挂载到插件中。
如果你选择 DaemonSet 方法,你可以通过 Kubernetes 进行以下操作:
将设备插件的 Pod 放置在节点上,在出现故障后重新启动守护进程 Pod,来进行自动升级。
API 兼容性 Kubernetes 设备插件支持还处于 beta 版本。所以在稳定版本出来之前 API 会以不兼容的方式进行更改。
作为一个项目,Kubernetes 建议设备插件开发者:
注意未来版本的更改 支持多个版本的设备插件 API,以实现向后/向前兼容性。 如果你启用 DevicePlugins 功能,并在需要升级到 Kubernetes 版本来获得较新的设备插件 API
版本的节点上运行设备插件,请在升级这些节点之前先升级设备插件以支持这两个版本。
采用该方法将确保升级期间设备分配的连续运行。
监控设备插件资源 特性状态: Kubernetes v1.15 [beta]
为了监控设备插件提供的资源,监控代理程序需要能够发现节点上正在使用的设备,
并获取元数据来描述哪个指标与容器相关联。
设备监控代理暴露给 Prometheus 的指标应该遵循
Kubernetes Instrumentation Guidelines ,
使用 pod
、namespace
和 container
标签来标识容器。
kubelet 提供了 gRPC 服务来使得正在使用中的设备被发现,并且还为这些设备提供了元数据:
// PodResourcesLister 是一个由 kubelet 提供的服务,用来提供供节点上
// Pod 和容器使用的节点资源的信息
service PodResourcesLister {
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
}
List
gRPC 端点这一 List
端点提供运行中 Pod 的资源信息,包括类似独占式分配的
CPU ID、设备插件所报告的设备 ID 以及这些设备分配所处的 NUMA 节点 ID。
此外,对于基于 NUMA 的机器,它还会包含为容器保留的内存和大页的信息。
// ListPodResourcesResponse 是 List 函数的响应
message ListPodResourcesResponse {
repeated PodResources pod_resources = 1;
}
// PodResources 包含关于分配给 Pod 的节点资源的信息
message PodResources {
string name = 1;
string namespace = 2;
repeated ContainerResources containers = 3;
}
// ContainerResources 包含分配给容器的资源的信息
message ContainerResources {
string name = 1;
repeated ContainerDevices devices = 2;
repeated int64 cpu_ids = 3;
repeated ContainerMemory memory = 4;
}
// ContainerMemory 包含分配给容器的内存和大页信息
message ContainerMemory {
string memory_type = 1;
uint64 size = 2;
TopologyInfo topology = 3;
}
// Topology 描述资源的硬件拓扑结构
message TopologyInfo {
repeated NUMANode nodes = 1;
}
// NUMA 代表的是 NUMA 节点
message NUMANode {
int64 ID = 1;
}
// ContainerDevices 包含分配给容器的设备信息
message ContainerDevices {
string resource_name = 1;
repeated string device_ids = 2;
TopologyInfo topology = 3;
}
说明: List
端点中的 ContainerResources
中的 cpu_ids 对应于分配给某个容器的专属 CPU。
如果要统计共享池中的 CPU,List
端点需要与 GetAllocatableResources
端点一起使用,如下所述:
调用 GetAllocatableResources
获取所有可用的 CPU。 在系统中所有的 ContainerResources
上调用 GetCpuIds
。 用 GetAllocatableResources
获取的 CPU 数减去 GetCpuIds
获取的 CPU 数。 GetAllocatableResources
gRPC 端点特性状态: Kubernetes v1.23 [beta]
端点 GetAllocatableResources
提供工作节点上原始可用的资源信息。
此端点所提供的信息比导出给 API 服务器的信息更丰富。
说明: GetAllocatableResources
应该仅被用于评估一个节点上的可分配的 资源。
如果目标是评估空闲/未分配的资源,此调用应该与 List() 端点一起使用。
除非暴露给 kubelet 的底层资源发生变化,否则 GetAllocatableResources
得到的结果将保持不变。
这种情况很少发生,但当发生时(例如:热插拔,设备健康状况改变),客户端应该调用 GetAlloctableResources
端点。
然而,调用 GetAllocatableResources
端点在 cpu、内存被更新的情况下是不够的,
Kubelet 需要重新启动以获取正确的资源容量和可分配的资源。
// AllocatableResourcesResponses 包含 kubelet 所了解到的所有设备的信息
message AllocatableResourcesResponse {
repeated ContainerDevices devices = 1;
repeated int64 cpu_ids = 2;
repeated ContainerMemory memory = 3;
}
从 Kubernetes v1.23 开始,GetAllocatableResources
被默认启用。
你可以通过关闭 KubeletPodResourcesGetAllocatable
特性门控 来禁用。
在 Kubernetes v1.23 之前,要启用这一功能,kubelet
必须用以下标志启动:
--feature-gates=KubeletPodResourcesGetAllocatable=true
ContainerDevices
会向外提供各个设备所隶属的 NUMA 单元这类拓扑信息。
NUMA 单元通过一个整数 ID 来标识,其取值与设备插件所报告的一致。
设备插件注册到 kubelet 时
会报告这类信息。
gRPC 服务通过 /var/lib/kubelet/pod-resources/kubelet.sock
的 UNIX 套接字来提供服务。
设备插件资源的监控代理程序可以部署为守护进程或者 DaemonSet。
规范的路径 /var/lib/kubelet/pod-resources
需要特权来进入,
所以监控代理程序必须要在获得授权的安全的上下文中运行。
如果设备监控代理以 DaemonSet 形式运行,必须要在插件的
PodSpec
中声明将 /var/lib/kubelet/pod-resources
目录以卷 的形式被挂载到设备监控代理中。
对 “PodResourcesLister 服务”的支持要求启用 KubeletPodResources
特性门控 。
从 Kubernetes 1.15 开始默认启用,自从 Kubernetes 1.20 开始为 v1。
设备插件与拓扑管理器的集成 特性状态: Kubernetes v1.18 [beta]
拓扑管理器是 Kubelet 的一个组件,它允许以拓扑对齐方式来调度资源。
为了做到这一点,设备插件 API 进行了扩展来包括一个 TopologyInfo
结构体。
message TopologyInfo {
repeated NUMANode nodes = 1;
}
message NUMANode {
int64 ID = 1;
}
设备插件希望拓扑管理器可以将填充的 TopologyInfo 结构体作为设备注册的一部分以及设备 ID
和设备的运行状况发送回去。然后设备管理器将使用此信息来咨询拓扑管理器并做出资源分配决策。
TopologyInfo
支持将 nodes
字段设置为 nil
或一个 NUMA 节点的列表。
这样就可以使设备插件通告跨越多个 NUMA 节点的设备。
将 TopologyInfo
设置为 nil
或为给定设备提供一个空的
NUMA 节点列表表示设备插件没有该设备的 NUMA 亲和偏好。
下面是一个由设备插件为设备填充 TopologyInfo
结构体的示例:
pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}
设备插件示例 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
下面是一些设备插件实现的示例:
接下来
3.13.3 - 扩展 Kubernetes API 3.13.3.1 - 定制资源 定制资源(Custom Resource) 是对 Kubernetes API 的扩展。
本页讨论何时向 Kubernetes 集群添加定制资源,何时使用独立的服务。
本页描述添加定制资源的两种方法以及怎样在二者之间做出抉择。
定制资源 资源(Resource) 是
Kubernetes API 中的一个端点,
其中存储的是某个类别的
API 对象 的一个集合。
例如内置的 Pod 资源包含一组 Pod 对象。
定制资源(Custom Resource) 是对 Kubernetes API 的扩展,不一定在默认的
Kubernetes 安装中就可用。
定制资源所代表的是对特定 Kubernetes 安装的一种定制。
不过,很多 Kubernetes 核心功能现在都用定制资源来实现,这使得 Kubernetes 更加模块化。
定制资源可以通过动态注册的方式在运行中的集群内或出现或消失,集群管理员可以独立于集群更新定制资源。
一旦某定制资源被安装,用户可以使用 kubectl 来创建和访问其中的对象,
就像他们为 Pod 这种内置资源所做的一样。
定制控制器 就定制资源本身而言,它只能用来存取结构化的数据。
当你将定制资源与定制控制器(Custom Controller) 结合时,
定制资源就能够提供真正的声明式 API(Declarative API) 。
Kubernetes 声明式 API 强制对职权做了一次分离操作。
你声明所用资源的期望状态,而 Kubernetes 控制器使 Kubernetes 对象的当前状态与你所声明的期望状态保持同步。
声明式 API 的这种机制与命令式 API(你指示 服务器要做什么,服务器就去做什么)形成鲜明对比。
你可以在一个运行中的集群上部署和更新定制控制器,这类操作与集群的生命周期无关。
定制控制器可以用于任何类别的资源,不过它们与定制资源结合起来时最为有效。
Operator 模式 就是将定制资源与定制控制器相结合的。
你可以使用定制控制器来将特定于某应用的领域知识组织起来,以编码的形式构造对 Kubernetes API 的扩展。
我是否应该向我的 Kubernetes 集群添加定制资源? 在创建新的 API 时,
请考虑是将你的 API 与 Kubernetes 集群 API 聚合起来 ,
还是让你的 API 独立运行。
考虑 API 聚合的情况 优选独立 API 的情况 你的 API 是声明式的 。 你的 API 不符合声明式 模型。 你希望可以是使用 kubectl
来读写你的新资源类别。 不要求 kubectl
支持。 你希望在 Kubernetes UI (如仪表板)中和其他内置类别一起查看你的新资源类别。 不需要 Kubernetes UI 支持。 你在开发新的 API。 你已经有一个提供 API 服务的程序并且工作良好。 你有意愿取接受 Kubernetes 对 REST 资源路径所作的格式限制,例如 API 组和名字空间。(参阅 API 概述 ) 你需要使用一些特殊的 REST 路径以便与已经定义的 REST API 保持兼容。 你的资源可以自然地界定为集群作用域或集群中某个名字空间作用域。 集群作用域或名字空间作用域这种二分法很不合适;你需要对资源路径的细节进行控制。 你希望复用 Kubernetes API 支持特性 。 你不需要这类特性。
声明式 API 典型地,在声明式 API 中:
你的 API 包含相对而言为数不多的、尺寸较小的对象(资源)。 对象定义了应用或者基础设施的配置信息。 对象更新操作频率较低。 通常需要人来读取或写入对象。 对象的主要操作是 CRUD 风格的(创建、读取、更新和删除)。 不需要跨对象的事务支持:API 对象代表的是期望状态而非确切实际状态。 命令式 API(Imperative API)与声明式有所不同。
以下迹象表明你的 API 可能不是声明式的:
客户端发出“做这个操作”的指令,之后在该操作结束时获得同步响应。 客户端发出“做这个操作”的指令,并获得一个操作 ID,之后需要检查一个 Operation(操作)
对象来判断请求是否成功完成。 你会将你的 API 类比为远程过程调用(Remote Procedure Call,RPC)。 直接存储大量数据;例如每个对象几 kB,或者存储上千个对象。 需要较高的访问带宽(长期保持每秒数十个请求)。 存储有应用来处理的最终用户数据(如图片、个人标识信息(PII)等)或者其他大规模数据。 在对象上执行的常规操作并非 CRUD 风格。 API 不太容易用对象来建模。 你决定使用操作 ID 或者操作对象来表现悬决的操作。 我应该使用一个 ConfigMap 还是一个定制资源? 如果满足以下条件之一,应该使用 ConfigMap:
存在一个已有的、文档完备的配置文件格式约定,例如 mysql.cnf
或 pom.xml
。 你希望将整个配置文件放到某 configMap 中的一个主键下面。 配置文件的主要用途是针对运行在集群中 Pod 内的程序,供后者依据文件数据配置自身行为。 文件的使用者期望以 Pod 内文件或者 Pod 内环境变量的形式来使用文件数据,
而不是通过 Kubernetes API。 你希望当文件被更新时通过类似 Deployment 之类的资源完成滚动更新操作。 说明: 请使用
Secret 来保存敏感数据。
Secret 类似于 configMap,但更为安全。
如果以下条件中大多数都被满足,你应该使用定制资源(CRD 或者 聚合 API):
你希望使用 Kubernetes 客户端库和 CLI 来创建和更改新的资源。 你希望 kubectl
能够直接支持你的资源;例如,kubectl get my-object object-name
。 你希望构造新的自动化机制,监测新对象上的更新事件,并对其他对象执行 CRUD
操作,或者监测后者更新前者。 你希望编写自动化组件来处理对对象的更新。 你希望使用 Kubernetes API 对诸如 .spec
、.status
和 .metadata
等字段的约定。 你希望对象是对一组受控资源的抽象,或者对其他资源的归纳提炼。 添加定制资源 Kubernetes 提供了两种方式供你向集群中添加定制资源:
CRD 相对简单,创建 CRD 可以不必编程。 API 聚合 需要编程,
但支持对 API 行为进行更多的控制,例如数据如何存储以及在不同 API 版本间如何转换等。Kubernetes 提供这两种选项以满足不同用户的需求,这样就既不会牺牲易用性也不会牺牲灵活性。
聚合 API 指的是一些下位的 API 服务器,运行在主 API 服务器后面;主 API
服务器以代理的方式工作。这种组织形式称作
API 聚合(API Aggregation,AA) 。
对用户而言,看起来仅仅是 Kubernetes API 被扩展了。
CRD 允许用户创建新的资源类别同时又不必添加新的 API 服务器。
使用 CRD 时,你并不需要理解 API 聚合。
无论以哪种方式安装定制资源,新的资源都会被当做定制资源,以便与内置的
Kubernetes 资源(如 Pods)相区分。
说明: 避免将定制资源用于存储应用、最终用户或监控数据:
将应用数据存储在 Kubernetes API 内的架构设计通常代表一种过于紧密耦合的设计。
在架构上,云原生 应用架构倾向于各组件之间的松散耦合。
如果部分工作负载需要支持服务来维持其日常运转,则这种支持服务应作为一个组件运行或作为一个外部服务来使用。
这样,工作负载的正常运转就不会依赖 Kubernetes API 了。
CustomResourceDefinitions CustomResourceDefinition
API 资源允许你定义定制资源。
定义 CRD 对象的操作会使用你所设定的名字和模式定义(Schema)创建一个新的定制资源,
Kubernetes API 负责为你的定制资源提供存储和访问服务。
CRD 对象的名称必须是合法的
DNS 子域名 。
CRD 使得你不必编写自己的 API 服务器来处理定制资源,不过其背后实现的通用性也意味着
你所获得的灵活性要比 API 服务器聚合 少很多。
关于如何注册新的定制资源、使用新资源类别的实例以及如何使用控制器来处理事件,
相关的例子可参见定制控制器示例 。
API 服务器聚合 通常,Kubernetes API 中的每个资源都需要处理 REST 请求和管理对象持久性存储的代码。
Kubernetes API 主服务器能够处理诸如 pods 和 services 这些内置资源,也可以
按通用的方式通过 CRD 来处理定制资源。
聚合层(Aggregation Layer)
使得你可以通过编写和部署你自己的 API 服务器来为定制资源提供特殊的实现。
主 API 服务器将针对你要处理的定制资源的请求全部委托给你自己的 API 服务器来处理,同时将这些资源
提供给其所有客户端。
选择添加定制资源的方法 CRD 更为易用;聚合 API 则更为灵活。请选择最符合你的需要的方法。
通常,如何存在以下情况,CRD 可能更合适:
定制资源的字段不多; 你在组织内部使用该资源或者在一个小规模的开源项目中使用该资源,而不是在商业产品中使用。 比较易用性 CRD 比聚合 API 更容易创建。
CRD 聚合 API 无需编程。用户可选择任何语言来实现 CRD 控制器。 需要编程,并构建可执行文件和镜像。 无需额外运行服务;CRD 由 API 服务器处理。 需要额外创建服务,且该服务可能失效。 一旦 CRD 被创建,不需要持续提供支持。Kubernetes 主控节点升级过程中自动会带入缺陷修复。 可能需要周期性地从上游提取缺陷修复并更新聚合 API 服务器。 无需处理 API 的多个版本;例如,当你控制资源的客户端时,你可以更新它使之与 API 同步。 你需要处理 API 的多个版本;例如,在开发打算与很多人共享的扩展时。
高级特性与灵活性 聚合 API 可提供更多的高级 API 特性,也可对其他特性实行定制;例如,对存储层进行定制。
特性 描述 CRD 聚合 API 合法性检查 帮助用户避免错误,允许你独立于客户端版本演化 API。这些特性对于由很多无法同时更新的客户端的场合。 可以。大多数验证可以使用 OpenAPI v3.0 合法性检查 来设定。其他合法性检查操作可以通过添加合法性检查 Webhook 来实现。 可以,可执行任何合法性检查。 默认值设置 同上 可以。可通过 OpenAPI v3.0 合法性检查 的 default
关键词(自 1.17 正式发布)或更改性(Mutating)Webhook 来实现(不过从 etcd 中读取老的对象时不会执行这些 Webhook)。 可以。 多版本支持 允许通过两个 API 版本同时提供同一对象。可帮助简化类似字段更名这类 API 操作。如果你能控制客户端版本,这一特性将不再重要。 可以 。可以。 定制存储 支持使用具有不同性能模式的存储(例如,要使用时间序列数据库而不是键值存储),或者因安全性原因对存储进行隔离(例如对敏感信息执行加密)。 不可以。 可以。 定制业务逻辑 在创建、读取、更新或删除对象时,执行任意的检查或操作。 可以。要使用 Webhook 。 可以。 支持 scale 子资源 允许 HorizontalPodAutoscaler 和 PodDisruptionBudget 这类子系统与你的新资源交互。 可以 。可以。 支持 status 子资源 允许在用户写入 spec 部分而控制器写入 status 部分时执行细粒度的访问控制。允许在对定制资源的数据进行更改时增加对象的代际(Generation);这需要资源对 spec 和 status 部分有明确划分。 可以 。可以。 其他子资源 添加 CRUD 之外的操作,例如 "logs" 或 "exec"。 不可以。 可以。 strategic-merge-patch 新的端点要支持标记了 Content-Type: application/strategic-merge-patch+json
的 PATCH 操作。对于更新既可在本地更改也可在服务器端更改的对象而言是有用的。要了解更多信息,可参见使用 kubectl patch
来更新 API 对象 。 不可以。 可以。 支持协议缓冲区 新的资源要支持想要使用协议缓冲区(Protocol Buffer)的客户端。 不可以。 可以。 OpenAPI Schema 是否存在新资源类别的 OpenAPI(Swagger)Schema 可供动态从服务器上读取?是否存在机制确保只能设置被允许的字段以避免用户犯字段拼写错误?是否实施了字段类型检查(换言之,不允许在 string
字段设置 int
值)? 可以,依据 OpenAPI v3.0 合法性检查 模式(1.16 中进入正式发布状态)。 可以。
公共特性 与在 Kubernetes 平台之外实现定制资源相比,
无论是通过 CRD 还是通过聚合 API 来创建定制资源,你都会获得很多 API 特性:
功能特性 具体含义 CRUD 新的端点支持通过 HTTP 和 kubectl
发起的 CRUD 基本操作 监测(Watch) 新的端点支持通过 HTTP 发起的 Kubernetes Watch 操作 发现(Discovery) 类似 kubectl
和仪表盘(Dashboard)这类客户端能够自动提供列举、显示、在字段级编辑你的资源的操作 json-patch 新的端点支持带 Content-Type: application/json-patch+json
的 PATCH 操作 merge-patch 新的端点支持带 Content-Type: application/merge-patch+json
的 PATCH 操作 HTTPS 新的端点使用 HTTPS 内置身份认证 对扩展的访问会使用核心 API 服务器(聚合层)来执行身份认证操作 内置鉴权授权 对扩展的访问可以复用核心 API 服务器所使用的鉴权授权机制;例如,RBAC Finalizers 在外部清除工作结束之前阻止扩展资源被删除 准入 Webhooks 在创建、更新和删除操作中对扩展资源设置默认值和执行合法性检查 UI/CLI 展示 kubectl
和仪表盘(Dashboard)可以显示扩展资源区分未设置值和空值 客户端能够区分哪些字段是未设置的,哪些字段的值是被显式设置为零值的。 生成客户端库 Kubernetes 提供通用的客户端库,以及用来生成特定类别客户端库的工具 标签和注解 提供涵盖所有对象的公共元数据结构,且工具知晓如何编辑核心资源和定制资源的这些元数据
准备安装定制资源 在向你的集群添加定制资源之前,有些事情需要搞清楚。
第三方代码和新的失效点的问题 尽管添加新的 CRD 不会自动带来新的失效点(Point of
Failure),例如导致第三方代码被在 API 服务器上运行,
类似 Helm Charts 这种软件包或者其他安装包通常在提供 CRD
的同时还包含带有第三方代码的 Deployment,负责实现新的定制资源的业务逻辑。
安装聚合 API 服务器时,也总会牵涉到运行一个新的 Deployment。
存储 定制资源和 ConfigMap 一样也会消耗存储空间。创建过多的定制资源可能会导致
API 服务器上的存储空间超载。
聚合 API 服务器可以使用主 API 服务器相同的存储。如果是这样,你也要注意此警告。
身份认证、鉴权授权以及审计 CRD 通常与 API 服务器上的内置资源一样使用相同的身份认证、鉴权授权和审计日志机制。
如果你使用 RBAC 来执行鉴权授权,大多数 RBAC 角色都不会授权对新资源的访问
(除了 cluster-admin 角色以及使用通配符规则创建的其他角色)。
你要显式地为新资源的访问授权。CRD 和聚合 API 通常在交付时会包含针对所添加的类别的新的角色定义。
聚合 API 服务器可能会使用主 API 服务器相同的身份认证、鉴权授权和审计机制,也可能不会。
访问定制资源 Kubernetes 客户端库 可用来访问定制资源。
并非所有客户端库都支持定制资源。Go 和 Python 客户端库是支持的。
当你添加了新的定制资源后,可以用如下方式之一访问它们:
kubectl
Kubernetes 动态客户端 你所编写的 REST 客户端 使用 Kubernetes 客户端生成工具 所生成的客户端。
生成客户端的工作有些难度,不过某些项目可能会随着 CRD 或聚合 API 一起提供一个客户端。 接下来 3.13.3.2 - Kubernetes API 聚合层 使用聚合层(Aggregation Layer),用户可以通过附加的 API 扩展 Kubernetes,
而不局限于 Kubernetes 核心 API 提供的功能。
这里的附加 API 可以是现成的解决方案,比如
metrics server ,
或者你自己开发的 API。
聚合层不同于
定制资源(Custom Resources) 。
后者的目的是让 kube-apiserver
能够识别新的对象类别(Kind)。
聚合层 聚合层在 kube-apiserver 进程内运行。在扩展资源注册之前,聚合层不做任何事情。
要注册 API,你可以添加一个 APIService 对象,用它来 “申领” Kubernetes API 中的 URL 路径。
自此以后,聚合层将把发给该 API 路径的所有内容(例如 /apis/myextension.mycompany.io/v1/…
)
转发到已注册的 APIService。
APIService 的最常见实现方式是在集群中某 Pod 内运行 扩展 API 服务器 。
如果你在使用扩展 API 服务器来管理集群中的资源,该扩展 API 服务器(也被写成“extension-apiserver”)
一般需要和一个或多个控制器 一起使用。
apiserver-builder 库同时提供构造扩展 API 服务器和控制器框架代码。
响应延迟 扩展 API 服务器与 kube-apiserver 之间需要存在低延迟的网络连接。
发现请求需要在五秒钟或更短的时间内完成到 kube-apiserver 的往返。
如果你的扩展 API 服务器无法满足这一延迟要求,应考虑如何更改配置以满足需要。
接下来 或者,学习如何使用 CustomResourceDefinition 扩展 Kubernetes API 。
4 - 任务 Kubernetes 文档这一部分包含的一些页面展示如何去完成单个任务。
每个任务页面是一般通过给出若干步骤展示如何执行完成某事。
如果你希望编写一个任务页面,参考
创建一个文档拉取请求 。
4.1 - 安装工具 在你的计算机上设置 Kubernetes 工具。
kubectl Kubernetes 命令行工具,kubectl ,
让你可以对 Kubernetes 集群运行命令。
你可以使用 kubectl 来部署应用、监测和管理集群资源以及查看日志。
有关更多信息,包括 kubectl 操作的完整列表,请参见 kubectl
参考文件 。
kubectl 可安装在各种 Linux 平台、 macOS 和 Windows 上。
在下面找到你喜欢的操作系统。
kind kind
让你能够在本地计算机上运行 Kubernetes。
kind
要求你安装并配置好 Docker 。
kind 快速入门 页面展示了开始使用
kind
所需要完成的操作。
查看 kind 的快速入门指南
minikube 与 kind
类似,minikube
是一个工具,
能让你在本地运行 Kubernetes。
minikube
在你本地的个人计算机(包括 Windows、macOS 和 Linux PC)运行一个单节点的
Kubernetes 集群,以便你来尝试 Kubernetes 或者开展每天的开发工作。
如果你关注如何安装此工具,可以按官方的
Get Started! 指南操作。
查看 minikube 快速入门指南
当你拥有了可工作的 minikube
时,
就可以用它来运行示例应用 了。
kubeadm 你可以使用 kubeadm 工具来
创建和管理 Kubernetes 集群。
该工具能够执行必要的动作并用一种用户友好的方式启动一个可用的、安全的集群。
安装 kubeadm
展示了如何安装 kubeadm 的过程。
一旦安装了 kubeadm,
你就可以使用它来创建一个集群 。
查看 kubeadm 安装指南
4.1.1 - 在 Linux 系统中安装并设置 kubectl 准备开始 kubectl 版本和集群版本之间的差异必须在一个小版本号内。
例如:v1.25 版本的客户端能与 v1.24、
v1.25 和 v1.26 版本的控制面通信。
用最新兼容版的 kubectl 有助于避免不可预见的问题。
在 Linux 系统中安装 kubectl 在 Linux 系统中安装 kubectl 有如下几种方法:
用 curl 在 Linux 系统中安装 kubectl 用以下命令下载最新发行版:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/linux/amd64/kubectl"
说明: 如需下载某个指定的版本,请用指定版本号替换该命令的这一部分:
$(curl -L -s https://dl.k8s.io/release/stable.txt)
。
例如,要在 Linux 中下载 v1.25.0 版本,请输入:
curl -LO https://dl.k8s.io/release/v1.25.0/bin/linux/amd64/kubectl
验证该可执行文件(可选步骤)
下载 kubectl 校验和文件:
curl -LO "https://dl.k8s.io/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/linux/amd64/kubectl.sha256"
基于校验和文件,验证 kubectl 的可执行文件:
echo " $( cat kubectl.sha256) kubectl" | sha256sum --check
验证通过时,输出为:
验证失败时,sha256
将以非零值退出,并打印如下输出:
kubectl: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
说明: 下载的 kubectl 与校验和文件版本必须相同。
安装 kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
说明: 即使你没有目标系统的 root 权限,仍然可以将 kubectl 安装到目录 ~/.local/bin
中:
chmod +x kubectl
mkdir -p ~/.local/bin
mv ./kubectl ~/.local/bin/kubectl
# 之后将 ~/.local/bin 附加(或前置)到 $PATH
执行测试,以保障你安装的版本是最新的:
或者使用如下命令来查看版本的详细信息:
kubectl version --client --output=yaml
用原生包管理工具安装
更新 apt
包索引,并安装使用 Kubernetes apt
仓库所需要的包:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
如果你使用 Debian 9(stretch)或更早版本,则你还需要安装 apt-transport-https
:
sudo apt-get install -y apt-transport-https
下载 Google Cloud 公开签名秘钥:
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
添加 Kubernetes apt
仓库:
echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list
更新 apt
包索引,使之包含新的仓库并安装 kubectl:
sudo apt-get update
sudo apt-get install -y kubectl
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
sudo yum install -y kubectl
用其他包管理工具安装
如果你使用的 Ubuntu 或其他 Linux 发行版,内建支持
snap 包管理工具,
则可用 snap 命令安装 kubectl。
snap install kubectl --classic
kubectl version --client
如果你使用 Linux 系统,并且装了 Homebrew
包管理工具,
则可以使用这种方式安装 kubectl。
brew install kubectl
kubectl version --client
验证 kubectl 配置 为了让 kubectl 能发现并访问 Kubernetes 集群,你需要一个
kubeconfig 文件 ,
该文件在
kube-up.sh
创建集群时,或成功部署一个 Miniube 集群时,均会自动生成。
通常,kubectl 的配置信息存放于文件 ~/.kube/config
中。
通过获取集群状态的方法,检查是否已恰当的配置了 kubectl:
如果返回一个 URL,则意味着 kubectl 成功的访问到了你的集群。
如果你看到如下所示的消息,则代表 kubectl 配置出了问题,或无法连接到 Kubernetes 集群。
The connection to the server <server-name:port> was refused - did you specify the right host or port?
(访问 <server-name:port> 被拒绝 - 你指定的主机和端口是否有误?)
例如,如果你想在自己的笔记本上(本地)运行 Kubernetes 集群,你需要先安装一个 Minikube 这样的工具,然后再重新运行上面的命令。
如果命令 kubectl cluster-info
返回了 url,但你还不能访问集群,那可以用以下命令来检查配置是否妥当:
kubectl cluster-info dump
kubectl 的可选配置和插件 启用 shell 自动补全功能 kubectl 为 Bash、Zsh、Fish 和 PowerShell 提供自动补全功能,可以为你节省大量的输入。
下面是为 Bash、Fish 和 Zsh 设置自动补全功能的操作步骤。
简介 kubectl 的 Bash 补全脚本可以用命令 kubectl completion bash
生成。
在 Shell 中导入(Sourcing)补全脚本,将启用 kubectl 自动补全功能。
然而,补全脚本依赖于工具 bash-completion ,
所以要先安装它(可以用命令 type _init_completion
检查 bash-completion 是否已安装)。
安装 bash-completion 很多包管理工具均支持 bash-completion(参见这里 )。
可以通过 apt-get install bash-completion
或 yum install bash-completion
等命令来安装它。
上述命令将创建文件 /usr/share/bash-completion/bash_completion
,它是 bash-completion 的主脚本。
依据包管理工具的实际情况,你需要在 ~/.bashrc
文件中手工导入此文件。
要查看结果,请重新加载你的 Shell,并运行命令 type _init_completion
。
如果命令执行成功,则设置完成,否则将下面内容添加到文件 ~/.bashrc
中:
source /usr/share/bash-completion/bash_completion
重新加载 Shell,再输入命令 type _init_completion
来验证 bash-completion 的安装状态。
启动 kubectl 自动补全功能 Bash 你现在需要确保一点:kubectl 补全脚本已经导入(sourced)到 Shell 会话中。
可以通过以下两种方法进行设置:
echo 'source <(kubectl completion bash)' >>~/.bashrc
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null
如果 kubectl 有关联的别名,你可以扩展 Shell 补全来适配此别名:
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc
说明: bash-completion 负责导入 /etc/bash_completion.d
目录中的所有补全脚本。
两种方式的效果相同。重新加载 Shell 后,kubectl 自动补全功能即可生效。
若要在当前 Shell 会话中启用 Bash 补全功能,需要运行 exec bash
命令:
kubectl 通过命令 kubectl completion fish
生成 Fish 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.config/fish/config.fish
中。
kubectl completion fish | source
重新加载 shell 后,kubectl 自动补全功能将立即生效。
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,kubectl 自动补全将自动使用它。
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 2: command not found: compdef
这样的错误提示,那请将下面内容添加到 ~/.zshrc
文件的开头:
autoload -Uz compinit
compinit
安装 kubectl convert
插件 一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
这对于将清单迁移到新的 Kubernetes 发行版上未被废弃的 API 版本时尤其有帮助。
更多信息请访问 迁移到非弃用 API
用以下命令下载最新发行版:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/linux/amd64/kubectl-convert"
验证该可执行文件(可选步骤)
下载 kubectl-convert 校验和文件:
curl -LO "https://dl.k8s.io/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/linux/amd64/kubectl-convert.sha256"
基于校验和,验证 kubectl-convert 的可执行文件:
echo " $( cat kubectl-convert.sha256) kubectl-convert" | sha256sum --check
验证通过时,输出为:
验证失败时,sha256
将以非零值退出,并打印输出类似于:
kubectl-convert: FAILED
sha256sum: WARNING: 1 computed checksum did NOT match
安装 kubectl-convert
sudo install -o root -g root -m 0755 kubectl-convert /usr/local/bin/kubectl-convert
验证插件是否安装成功
如果你没有看到任何错误就代表插件安装成功了。
接下来 4.1.2 - 在 macOS 系统上安装和设置 kubectl 准备开始 kubectl 版本和集群之间的差异必须在一个小版本号之内。
例如:v1.25 版本的客户端能与 v1.24、
v1.25 和 v1.26 版本的控制面通信。
用最新兼容版本的 kubectl 有助于避免不可预见的问题。
在 macOS 系统上安装 kubectl 在 macOS 系统上安装 kubectl 有如下方法:
用 curl 在 macOS 系统上安装 kubectl 下载最新的发行版:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/arm64/kubectl"
说明: 如果需要下载某个指定的版本,用该指定版本号替换掉命令的这个部分:$(curl -L -s https://dl.k8s.io/release/stable.txt)
。
例如:要为 Intel macOS 系统下载 v1.25.0 版本,则输入:
curl -LO "https://dl.k8s.io/release/v1.25.0/bin/darwin/amd64/kubectl"
对于 Apple Silicon 版本的 macOS,输入:
curl -LO "https://dl.k8s.io/release/v1.25.0/bin/darwin/arm64/kubectl"
验证可执行文件(可选操作)
下载 kubectl 的校验和文件:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/amd64/kubectl.sha256"
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/arm64/kubectl.sha256"
根据校验和文件,验证 kubectl:
echo " $( cat kubectl.sha256) kubectl" | shasum -a 256 --check
验证通过时,输出如下:
验证失败时,shasum
将以非零值退出,并打印如下输出:
kubectl: FAILED
shasum: WARNING: 1 computed checksum did NOT match
说明: 下载的 kubectl 与校验和文件版本要相同。
将 kubectl 置为可执行文件:
将可执行文件 kubectl 移动到系统可寻址路径 PATH
内的一个位置:
sudo mv ./kubectl /usr/local/bin/kubectl
sudo chown root: /usr/local/bin/kubectl
说明: 确保 /usr/local/bin
在你的 PATH 环境变量中。
测试一下,确保你安装的是最新的版本:
或者使用下面命令来查看版本的详细信息:
kubectl version --client --output=yaml
用 Homebrew 在 macOS 系统上安装 如果你是 macOS 系统,且用的是 Homebrew 包管理工具,
则可以用 Homebrew 安装 kubectl。
运行安装命令:
或
brew install kubernetes-cli
测试一下,确保你安装的是最新的版本:
用 Macports 在 macOS 上安装 如果你用的是 macOS,且用 Macports 包管理工具,则你可以用 Macports 安装kubectl。
运行安装命令:
sudo port selfupdate
sudo port install kubectl
测试一下,确保你安装的是最新的版本:
验证 kubectl 配置 为了让 kubectl 能发现并访问 Kubernetes 集群,你需要一个
kubeconfig 文件 ,
该文件在
kube-up.sh
创建集群时,或成功部署一个 Miniube 集群时,均会自动生成。
通常,kubectl 的配置信息存放于文件 ~/.kube/config
中。
通过获取集群状态的方法,检查是否已恰当的配置了 kubectl:
如果返回一个 URL,则意味着 kubectl 成功的访问到了你的集群。
如果你看到如下所示的消息,则代表 kubectl 配置出了问题,或无法连接到 Kubernetes 集群。
The connection to the server <server-name:port> was refused - did you specify the right host or port?
(访问 <server-name:port> 被拒绝 - 你指定的主机和端口是否有误?)
例如,如果你想在自己的笔记本上(本地)运行 Kubernetes 集群,你需要先安装一个 Minikube 这样的工具,然后再重新运行上面的命令。
如果命令 kubectl cluster-info
返回了 url,但你还不能访问集群,那可以用以下命令来检查配置是否妥当:
kubectl cluster-info dump
可选的 kubectl 配置和插件 启用 shell 自动补全功能 kubectl 为 Bash、Zsh、Fish 和 PowerShell 提供自动补全功能,可以为你节省大量的输入。
下面是为 Bash、Fish 和 Zsh 设置自动补全功能的操作步骤。
简介 kubectl 的 Bash 补全脚本可以通过 kubectl completion bash
命令生成。
在你的 shell 中导入(Sourcing)这个脚本即可启用补全功能。
此外,kubectl 补全脚本依赖于工具 bash-completion ,
所以你必须先安装它。
警告: bash-completion 有两个版本:v1 和 v2。v1 对应 Bash3.2(也是 macOS 的默认安装版本),v2 对应 Bash 4.1+。
kubectl 的补全脚本无法适配 bash-completion v1 和 Bash 3.2。
必须为它配备 bash-completion v2 和 Bash 4.1+ 。
有鉴于此,为了在 macOS 上使用 kubectl 补全功能,你必须要安装和使用 Bash 4.1+
(说明 )。
后续说明假定你用的是 Bash 4.1+(也就是 Bash 4.1 或更新的版本)。
升级 Bash 后续说明假定你已使用 Bash 4.1+。你可以运行以下命令检查 Bash 版本:
如果版本太旧,可以用 Homebrew 安装/升级:
重新加载 shell,并验证所需的版本已经生效:
echo $BASH_VERSION $SHELL
Homebrew 通常把它安装为 /usr/local/bin/bash
。
安装 bash-completion 说明: 如前所述,本说明假定你使用的 Bash 版本为 4.1+,这意味着你要安装 bash-completion v2
(不同于 Bash 3.2 和 bash-completion v1,kubectl 的补全功能在该场景下无法工作)。
你可以用命令 type _init_completion
测试 bash-completion v2 是否已经安装。
如未安装,用 Homebrew 来安装它:
brew install bash-completion@2
如命令的输出信息所显示的,将如下内容添加到文件 ~/.bash_profile
中:
export BASH_COMPLETION_COMPAT_DIR = "/usr/local/etc/bash_completion.d"
[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
重新加载 shell,并用命令 type _init_completion
验证 bash-completion v2 已经恰当的安装。
启用 kubectl 自动补全功能 你现在需要确保在所有的 shell 环境中均已导入(sourced) kubectl 的补全脚本,
有若干种方法可以实现这一点:
总之,重新加载 shell 之后,kubectl 补全功能将立即生效。
kubectl 通过命令 kubectl completion fish
生成 Fish 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.config/fish/config.fish
中。
kubectl completion fish | source
重新加载 shell 后,kubectl 自动补全功能将立即生效。
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,kubectl 自动补全将自动使用它。
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 2: command not found: compdef
这样的错误提示,那请将下面内容添加到 ~/.zshrc
文件的开头:
autoload -Uz compinit
compinit
安装 kubectl convert
插件 一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
这对于将清单迁移到新的 Kubernetes 发行版上未被废弃的 API 版本时尤其有帮助。
更多信息请访问 迁移到非弃用 API
用以下命令下载最新发行版:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/amd64/kubectl-convert"
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/arm64/kubectl-convert"
验证该可执行文件(可选步骤)
下载 kubectl-convert 校验和文件:
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/amd64/kubectl-convert.sha256"
curl -LO "https://dl.k8s.io/release/ $( curl -L -s https://dl.k8s.io/release/stable.txt) /bin/darwin/arm64/kubectl-convert.sha256"
基于校验和,验证 kubectl-convert 的可执行文件:
echo " $( cat kubectl-convert.sha256) kubectl-convert" | shasum -a 256 --check
验证通过时,输出为:
验证失败时,sha256
将以非零值退出,并打印输出类似于:
kubectl-convert: FAILED
shasum: WARNING: 1 computed checksum did NOT match
使 kubectl-convert 二进制文件可执行
chmod +x ./kubectl-convert
将 kubectl-convert 可执行文件移动到系统 PATH
环境变量中的一个位置。
sudo mv ./kubectl-convert /usr/local/bin/kubectl-convert
sudo chown root: /usr/local/bin/kubectl-convert
说明: 确保你的 PATH 环境变量中存在 /usr/local/bin
验证插件是否安装成功
如果你没有看到任何错误就代表插件安装成功了。
接下来 4.1.3 - 在 Windows 上安装 kubectl 准备开始 kubectl 版本和集群版本之间的差异必须在一个小版本号内。
例如:v1.25 版本的客户端能与 v1.24、
v1.25 和 v1.26 版本的控制面通信。
用最新兼容版的 kubectl 有助于避免不可预见的问题。
在 Windows 上安装 kubectl 在 Windows 系统中安装 kubectl 有如下几种方法:
用 curl 在 Windows 上安装 kubectl 下载 最新发行版 v1.25.0 。
如果你已安装了 curl
,也可以使用此命令:
curl.exe -LO "https://dl.k8s.io/release/v1.25.0/bin/windows/amd64/kubectl.exe"
验证该可执行文件(可选步骤)
下载 kubectl
校验和文件:
curl.exe -LO "https://dl.k8s.io/v1.25.0/bin/windows/amd64/kubectl.exe.sha256"
基于校验和文件,验证 kubectl
的可执行文件:
在命令行环境中,手工对比 CertUtil
命令的输出与校验和文件: CertUtil -hashfile kubectl.exe SHA256
type kubectl.exe.sha256
用 PowerShell 自动验证,用运算符 -eq
来直接取得 True
或 False
的结果: $($(CertUtil -hashfile .\kubectl.exe SHA256)[1] -replace " " , "" ) -eq $(type .\kubectl.exe.sha256)
将 kubectl
二进制文件夹追加或插入到你的 PATH
环境变量中。
测试一下,确保此 kubectl
的版本和期望版本一致:
或者使用下面命令来查看版本的详细信息:
kubectl version --client --output=yaml
说明: Windows 版的 Docker Desktop
将其自带版本的 kubectl
添加到 PATH
。
如果你之前安装过 Docker Desktop,可能需要把此 PATH
条目置于 Docker Desktop 安装的条目之前,
或者直接删掉 Docker Desktop 的 kubectl
。
要在 Windows 上安装 kubectl,你可以使用包管理器 Chocolatey 、
命令行安装器 Scoop 或包管理器 Winget 。
choco install kubernetes-cli
winget install -e --id Kubernetes.kubectl
测试一下,确保安装的是最新版本:
导航到你的 home 目录:
# 当你用 cmd.exe 时,则运行: cd %USERPROFILE%
cd ~
创建目录 .kube
:
切换到新创建的目录 .kube
:
配置 kubectl,以接入远程的 Kubernetes 集群:
New-Item config -type file
说明: 编辑配置文件,你需要先选择一个文本编辑器,比如 Notepad。
验证 kubectl 配置 为了让 kubectl 能发现并访问 Kubernetes 集群,你需要一个
kubeconfig 文件 ,
该文件在
kube-up.sh
创建集群时,或成功部署一个 Miniube 集群时,均会自动生成。
通常,kubectl 的配置信息存放于文件 ~/.kube/config
中。
通过获取集群状态的方法,检查是否已恰当的配置了 kubectl:
如果返回一个 URL,则意味着 kubectl 成功的访问到了你的集群。
如果你看到如下所示的消息,则代表 kubectl 配置出了问题,或无法连接到 Kubernetes 集群。
The connection to the server <server-name:port> was refused - did you specify the right host or port?
(访问 <server-name:port> 被拒绝 - 你指定的主机和端口是否有误?)
例如,如果你想在自己的笔记本上(本地)运行 Kubernetes 集群,你需要先安装一个 Minikube 这样的工具,然后再重新运行上面的命令。
如果命令 kubectl cluster-info
返回了 url,但你还不能访问集群,那可以用以下命令来检查配置是否妥当:
kubectl cluster-info dump
kubectl 可选配置和插件 启用 shell 自动补全功能 kubectl 为 Bash、Zsh、Fish 和 PowerShell 提供自动补全功能,可以为你节省大量的输入。
下面是设置 PowerShell 自动补全功能的操作步骤。
使用命令 kubectl completion powershell
生成 PowerShell 的 kubectl 自动补全脚本。
如果需要自动补全在所有 shell 会话中生效,请将以下命令添加到 $PROFILE
文件中:
kubectl completion powershell | Out-String | Invoke-Expression
此命令将在每次 PowerShell 启动时重新生成自动补全脚本。你还可以将生成的自动补全脚本添加到 $PROFILE
文件中。
如果需要将自动补全脚本直接添加到 $PROFILE
文件中,请在 PowerShell 终端运行以下命令:
kubectl completion powershell >> $PROFILE
完成上述操作后重启 shell,kubectl的自动补全就可以工作了。
安装 kubectl convert
插件 一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
这对于将清单迁移到新的 Kubernetes 发行版上未被废弃的 API 版本时尤其有帮助。
更多信息请访问 迁移到非弃用 API
用以下命令下载最新发行版:
curl.exe -LO "https://dl.k8s.io/release/v1.25.0/bin/windows/amd64/kubectl-convert.exe"
验证该可执行文件(可选步骤)
下载 kubectl-convert
校验和文件:
curl.exe -LO "https://dl.k8s.io/v1.25.0/bin/windows/amd64/kubectl-convert.exe.sha256"
基于校验和验证 kubectl-convert
的可执行文件:
用提示的命令对 CertUtil
的输出和下载的校验和文件进行手动比较。 CertUtil -hashfile kubectl-convert.exe SHA256
type kubectl-convert.exe.sha256
将 kubectl-convert
二进制文件夹附加或添加到你的 PATH
环境变量中。
验证插件是否安装成功。
如果你没有看到任何错误就代表插件安装成功了。
接下来 4.1.4 - 内含的工具 在页面 kubectl-installs-*.md 中包含的代码片段
4.1.4.1 - fish 自动补全 启用 fish 自动补全的可选配置。
kubectl 通过命令 kubectl completion fish
生成 Fish 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.config/fish/config.fish
中。
kubectl completion fish | source
重新加载 shell 后,kubectl 自动补全功能将立即生效。
4.1.4.2 - kubectl-convert 概述 一个 kubectl 插件,允许你将清单从一个 Kubernetes API 版本转换到不同的版本。
一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
这对于将清单迁移到新的 Kubernetes 发行版上未被废弃的 API 版本时尤其有帮助。
更多信息请访问 迁移到非弃用 API
4.1.4.3 - Linux 系统中的 Bash 自动补全功能 Linux 系统中 Bash 自动补全功能的一些可选配置。
简介 kubectl 的 Bash 补全脚本可以用命令 kubectl completion bash
生成。
在 Shell 中导入(Sourcing)补全脚本,将启用 kubectl 自动补全功能。
然而,补全脚本依赖于工具 bash-completion ,
所以要先安装它(可以用命令 type _init_completion
检查 bash-completion 是否已安装)。
安装 bash-completion 很多包管理工具均支持 bash-completion(参见这里 )。
可以通过 apt-get install bash-completion
或 yum install bash-completion
等命令来安装它。
上述命令将创建文件 /usr/share/bash-completion/bash_completion
,它是 bash-completion 的主脚本。
依据包管理工具的实际情况,你需要在 ~/.bashrc
文件中手工导入此文件。
要查看结果,请重新加载你的 Shell,并运行命令 type _init_completion
。
如果命令执行成功,则设置完成,否则将下面内容添加到文件 ~/.bashrc
中:
source /usr/share/bash-completion/bash_completion
重新加载 Shell,再输入命令 type _init_completion
来验证 bash-completion 的安装状态。
启动 kubectl 自动补全功能 Bash 你现在需要确保一点:kubectl 补全脚本已经导入(sourced)到 Shell 会话中。
可以通过以下两种方法进行设置:
echo 'source <(kubectl completion bash)' >>~/.bashrc
kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null
如果 kubectl 有关联的别名,你可以扩展 Shell 补全来适配此别名:
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -o default -F __start_kubectl k' >>~/.bashrc
说明: bash-completion 负责导入 /etc/bash_completion.d
目录中的所有补全脚本。
两种方式的效果相同。重新加载 Shell 后,kubectl 自动补全功能即可生效。
若要在当前 Shell 会话中启用 Bash 补全功能,需要运行 exec bash
命令:
4.1.4.4 - macOS 系统上的 bash 自动补全 在 macOS 上实现 Bash 自动补全的一些可选配置。
简介 kubectl 的 Bash 补全脚本可以通过 kubectl completion bash
命令生成。
在你的 shell 中导入(Sourcing)这个脚本即可启用补全功能。
此外,kubectl 补全脚本依赖于工具 bash-completion ,
所以你必须先安装它。
警告: bash-completion 有两个版本:v1 和 v2。v1 对应 Bash3.2(也是 macOS 的默认安装版本),v2 对应 Bash 4.1+。
kubectl 的补全脚本无法适配 bash-completion v1 和 Bash 3.2。
必须为它配备 bash-completion v2 和 Bash 4.1+ 。
有鉴于此,为了在 macOS 上使用 kubectl 补全功能,你必须要安装和使用 Bash 4.1+
(说明 )。
后续说明假定你用的是 Bash 4.1+(也就是 Bash 4.1 或更新的版本)。
升级 Bash 后续说明假定你已使用 Bash 4.1+。你可以运行以下命令检查 Bash 版本:
如果版本太旧,可以用 Homebrew 安装/升级:
重新加载 shell,并验证所需的版本已经生效:
echo $BASH_VERSION $SHELL
Homebrew 通常把它安装为 /usr/local/bin/bash
。
安装 bash-completion 说明: 如前所述,本说明假定你使用的 Bash 版本为 4.1+,这意味着你要安装 bash-completion v2
(不同于 Bash 3.2 和 bash-completion v1,kubectl 的补全功能在该场景下无法工作)。
你可以用命令 type _init_completion
测试 bash-completion v2 是否已经安装。
如未安装,用 Homebrew 来安装它:
brew install bash-completion@2
如命令的输出信息所显示的,将如下内容添加到文件 ~/.bash_profile
中:
export BASH_COMPLETION_COMPAT_DIR = "/usr/local/etc/bash_completion.d"
[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
重新加载 shell,并用命令 type _init_completion
验证 bash-completion v2 已经恰当的安装。
启用 kubectl 自动补全功能 你现在需要确保在所有的 shell 环境中均已导入(sourced) kubectl 的补全脚本,
有若干种方法可以实现这一点:
总之,重新加载 shell 之后,kubectl 补全功能将立即生效。
4.1.4.5 - PowerShell 自动补全 powershell 自动补全的一些可选配置。
使用命令 kubectl completion powershell
生成 PowerShell 的 kubectl 自动补全脚本。
如果需要自动补全在所有 shell 会话中生效,请将以下命令添加到 $PROFILE
文件中:
kubectl completion powershell | Out-String | Invoke-Expression
此命令将在每次 PowerShell 启动时重新生成自动补全脚本。你还可以将生成的自动补全脚本添加到 $PROFILE
文件中。
如果需要将自动补全脚本直接添加到 $PROFILE
文件中,请在 PowerShell 终端运行以下命令:
kubectl completion powershell >> $PROFILE
完成上述操作后重启 shell,kubectl的自动补全就可以工作了。
4.1.4.6 - zsh 自动补全 zsh 自动补全的一些可选配置
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,kubectl 自动补全将自动使用它。
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 2: command not found: compdef
这样的错误提示,那请将下面内容添加到 ~/.zshrc
文件的开头:
autoload -Uz compinit
compinit
4.1.4.7 - 后续内容 安装 kubectl 之后,还可以做些什么?
4.1.4.8 - 验证 kubectl 的安装效果 如何验证 kubectl。
为了让 kubectl 能发现并访问 Kubernetes 集群,你需要一个
kubeconfig 文件 ,
该文件在
kube-up.sh
创建集群时,或成功部署一个 Miniube 集群时,均会自动生成。
通常,kubectl 的配置信息存放于文件 ~/.kube/config
中。
通过获取集群状态的方法,检查是否已恰当的配置了 kubectl:
如果返回一个 URL,则意味着 kubectl 成功的访问到了你的集群。
如果你看到如下所示的消息,则代表 kubectl 配置出了问题,或无法连接到 Kubernetes 集群。
The connection to the server <server-name:port> was refused - did you specify the right host or port?
(访问 <server-name:port> 被拒绝 - 你指定的主机和端口是否有误?)
例如,如果你想在自己的笔记本上(本地)运行 Kubernetes 集群,你需要先安装一个 Minikube 这样的工具,然后再重新运行上面的命令。
如果命令 kubectl cluster-info
返回了 url,但你还不能访问集群,那可以用以下命令来检查配置是否妥当:
kubectl cluster-info dump
4.2.1 - 从 dockershim 迁移 本节提供从 dockershim 迁移到其他容器运行时的必备知识。
自从 Kubernetes 1.20 宣布
弃用 dockershim ,
各类疑问随之而来:这对各类工作负载和 Kubernetes 部署会产生什么影响。
我们的弃用 Dockershim 常见问题 可以帮助你更好地理解这个问题。
Dockershim 在 Kubernetes v1.24 版本已经被移除。
如果你集群内是通过 dockershim 使用 Docker Engine 作为容器运行时,并希望 Kubernetes 升级到 v1.24,
建议你迁移到其他容器运行时或使用其他方法以获得 Docker 引擎支持。
建议从 dockershim 迁移到其他替代的容器运行时。
请参阅容器运行时
一节以了解可用的备选项。
当在迁移过程中遇到麻烦,请上报问题 。
那么问题就可以及时修复,你的集群也可以进入移除 dockershim 前的就绪状态。
你的集群中可以有不止一种类型的节点,尽管这不是常见的情况。
下面这些任务可以帮助你完成迁移:
接下来 查看容器运行时 了解可选的容器运行时。 GitHub 问题 跟踪有关
dockershim 的弃用和删除的讨论。如果你发现与 dockershim 迁移相关的缺陷或其他技术问题,
可以在 Kubernetes 项目报告问题 。 4.2.1.1 - 将节点上的容器运行时从 Docker Engine 改为 containerd 本任务给出将容器运行时从 Docker 改为 containerd 所需的步骤。
此任务适用于运行 1.23 或更早版本 Kubernetes 的集群操作人员。
同时,此任务也涉及从 dockershim 迁移到 containerd 的示例场景。
有关其他备选的容器运行时,可查阅
此页面 进行拣选。
准备开始 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
安装 containerd。进一步的信息可参见
containerd 的安装文档 。
关于一些特定的环境准备工作,请遵循 containerd 指南 。
腾空节点 kubectl drain <node-to-drain> --ignore-daemonsets
将 <node-to-drain>
替换为你所要腾空的节点的名称。
停止 Docker 守护进程 systemctl stop kubelet
systemctl disable docker.service --now
安装 Containerd 遵循此指南
了解安装 containerd 的详细步骤。
从官方的 Docker 仓库安装 containerd.io
包。关于为你所使用的 Linux 发行版来设置
Docker 仓库,以及安装 containerd.io
包的详细说明,
可参见开始使用 containerd 。 配置 containerd:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
重启 containerd:
sudo systemctl restart containerd
启动一个 Powershell 会话,将 $Version
设置为期望的版本(例如:$Version="1.4.3"
),
之后运行下面的命令:
下载 containerd:
curl.exe -L https: //github.com/containerd/containerd/releases/download/v$Version /containerd-$Version -windows-amd64.tar.gz -o containerd-windows -amd64.tar.gz
tar.exe xvf .\containerd-windows -amd64.tar.gz
解压缩并执行配置:
Copy-Item -Path ".\bin\" -Destination "$Env:ProgramFiles\containerd" -Recurse -Force
cd $Env:ProgramFiles \containerd\
.\containerd.exe config default | Out-File config.toml -Encoding ascii
# 请审查配置信息。取决于你的安装环境,你可能需要调整:
# - sandbox_image (Kubernetes pause 镜像)
# - CNI 的 bin_dir 和 conf_dir 的位置
Get-Content config.toml
# (可选步骤,但强烈建议执行)将 containerd 排除在 Windows Defender 扫描之外
Add-MpPreference -ExclusionProcess "$Env:ProgramFiles\containerd\containerd.exe"
启动 containerd:
.\containerd.exe --register-service
Start-Service containerd
配置 kubelet 使用 containerd 作为其容器运行时 编辑文件 /var/lib/kubelet/kubeadm-flags.env
,将 containerd 运行时添加到标志中:
--container-runtime=remote
和
--container-runtime-endpoint=unix:///run/containerd/containerd.sock
。
kubeadm
工具将每个主机的 CRI 套接字保存在该主机对应的 Node 对象的注解中。
使用 kubeadm
的用户应该知道,kubeadm
工具将每个主机的 CRI 套接字保存在该主机对应的
Node 对象的注解中。
要更改这一注解信息,你可以在一台包含 kubeadm /etc/kubernetes/admin.conf
文件的机器上执行以下命令:
kubectl edit no <node-name>
这一命令会打开一个文本编辑器,供你在其中编辑 Node 对象。
要选择不同的文本编辑器,你可以设置 KUBE_EDITOR
环境变量。
更改 kubeadm.alpha.kubernetes.io/cri-socket
值,将其从
/var/run/dockershim.sock
改为你所选择的 CRI 套接字路径
(例如:unix:///run/containerd/containerd.sock
)。
注意新的 CRI 套接字路径必须带有 unix://
前缀。
保存文本编辑器中所作的修改,这会更新 Node 对象。
重启 kubelet 验证节点处于健康状态 运行 kubectl get nodes -o wide
,containerd 会显示为我们所更改的节点上的运行时。
移除 Docker Engine 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
最后,在一切顺利时删除 Docker。
sudo yum remove docker-ce docker-ce-cli
sudo apt-get purge docker-ce docker-ce-cli
sudo dnf remove docker-ce docker-ce-cli
sudo apt-get purge docker-ce docker-ce-cli
上面的命令不会移除你的主机上的镜像、容器、卷或者定制的配置文件。
要删除这些内容,参阅 Docker 的指令来卸载 Docker Engine 。
注意: Docker 所提供的卸载 Docker Engine 命令指导中,存在删除 containerd 的风险。
在执行命令时要谨慎。
4.2.1.2 - 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
本页面为你展示如何迁移你的 Docker Engine 节点,使之使用 cri-dockerd
而不是 dockershim。
在以下场景中,你可以遵从这里的步骤执行操作:
你期望不再使用 dockershim,但仍然使用 Docker Engine 来在 Kubernetes 中运行容器。 你希望升级到 Kubernetes v1.25 且你的现有集群依赖于 dockershim,
因此你必须放弃 dockershim,而 cri-dockerd
是你的一种选项。 要进一步了解 dockershim 的移除,请阅读 FAQ 页面 。
cri-dockerd 是什么? 在 Kubernetes v1.24 及更早版本中,你可以在 Kubernetes 中使用 Docker Engine,
依赖于一个称作 dockershim 的内置 Kubernetes 组件。
dockershim 组件在 Kubernetes v1.24 发行版本中已被移除;不过,一种来自第三方的替代品,
cri-dockerd
是可供使用的。cri-dockerd
适配器允许你通过
容器运行时接口(Container Runtime Interface,CRI)
来使用 Docker Engine。
如果你想要迁移到 cri-dockerd
以便继续使用 Docker Engine 作为你的容器运行时,
你需要在所有被影响的节点上执行以下操作:
安装 cri-dockerd
; 隔离(Cordon)并腾空(Drain)该节点; 配置 kubelet 使用 cri-dockerd
; 重新启动 kubelet; 验证节点处于健康状态。 首先在非关键节点上测试这一迁移过程。
你应该针对所有希望迁移到 cri-dockerd
的节点执行以下步骤。
准备开始 隔离并腾空节点 隔离节点,阻止新的 Pod 被调度到节点上:
kubectl cordon <NODE_NAME>
将 <NODE_NAME>
替换为节点名称。
腾空节点以安全地逐出所有运行中的 Pod:
kubectl drain <NODE_NAME> --ignore-daemonsets
下面的步骤适用于用 kubeadm 工具安装的集群。如果你使用不同的工具,
你需要使用针对该工具的配置指令来修改 kubelet。
在每个被影响的节点上,打开 /var/lib/kubelet/kubeadm-flags.env
文件; 将 --container-runtime-endpoint
标志,将其设置为 unix:///var/run/cri-dockerd.sock
。 kubeadm 工具将节点上的套接字存储为控制面上 Node
对象的注解。
要为每个被影响的节点更改此套接字:
编辑 Node
对象的 YAML 表示:
KUBECONFIG = /path/to/admin.conf kubectl edit no <NODE_NAME>
根据下面的说明执行替换:
/path/to/admin.conf
:指向 kubectl 配置文件 admin.conf
的路径;<NODE_NAME>
:你要修改的节点的名称。将 kubeadm.alpha.kubernetes.io/cri-socket
标志从
/var/run/dockershim.sock
更改为 unix:///var/run/cri-dockerd.sock
;
保存所作更改。保存时,Node
对象被更新。
重启 kubelet systemctl restart kubelet
验证节点处于健康状态 要检查节点是否在使用 cri-dockerd
端点,
按照找出你所使用的运行时 页面所给的指令操作。
kubelet 的 --container-runtime-endpoint
标志取值应该是 unix:///var/run/cri-dockerd.sock
。
解除节点隔离 kubectl uncordon <NODE_NAME>
接下来 4.2.1.3 - 查明节点上所使用的容器运行时 本页面描述查明集群中节点所使用的容器运行时
的步骤。
取决于你运行集群的方式,节点所使用的容器运行时可能是事先配置好的,
也可能需要你来配置。如果你在使用托管的 Kubernetes 服务,
可能存在特定于厂商的方法来检查节点上配置的容器运行时。
本页描述的方法应该在能够执行 kubectl
的场合下都可以工作。
准备开始 安装并配置 kubectl
。参见安装工具 节了解详情。
查明节点所使用的容器运行时 使用 kubectl
来读取并显示节点信息:
kubectl get nodes -o wide
输出如下面所示。CONTAINER-RUNTIME
列给出容器运行时及其版本。
对于 Docker Engine,输出类似于:
NAME STATUS VERSION CONTAINER-RUNTIME
node-1 Ready v1.16.15 docker://19.3.1
node-2 Ready v1.16.15 docker://19.3.1
node-3 Ready v1.16.15 docker://19.3.1
如果你的容器运行时显示为 Docker Engine,你仍然可能不会被 v1.24 中 dockershim 的移除所影响。
通过检查运行时端点 ,可以查看你是否在使用 dockershim。
如果你没有使用 dockershim,你就不会被影响。
对于 containerd,输出类似于这样:
# For containerd
NAME STATUS VERSION CONTAINER-RUNTIME
node-1 Ready v1.19.6 containerd://1.4.1
node-2 Ready v1.19.6 containerd://1.4.1
node-3 Ready v1.19.6 containerd://1.4.1
你可以在容器运行时
页面找到与容器运行时相关的更多信息。
检查当前使用的运行时端点 容器运行时使用 Unix Socket 与 kubelet 通信,这一通信使用基于 gRPC 框架的
CRI 协议 。kubelet 扮演客户端,运行时扮演服务器端。
在某些情况下,你可能想知道你的节点使用的是哪个 socket。
如若集群是 Kubernetes v1.24 及以后的版本,
或许你想知道当前运行时是否是使用 dockershim 的 Docker Engine。
说明: 如果你的节点在通过 cri-dockerd
使用 Docker Engine,
那么集群不会受到 Kubernetes 移除 dockershim 的影响。
可以通过检查 kubelet 的参数得知当前使用的是哪个 socket。
查看 kubelet 进程的启动命令
tr \\0 ' ' < /proc/"$(pgrep kubelet)"/cmdline
如有节点上没有 tr
或者 pgrep
,就需要手动检查 kubelet 的启动命令
在命令的输出中,查找 --container-runtime
和 --container-runtime-endpoint
标志。
如果 Kubernetes 集群版本是 v1.23 或者更早的版本,并且这两个参数不存在,
或者 container-runtime
标志值不是 remote
,则你在通过 dockershim 套接字使用
Docker Engine。
或者如果集群使用的 Docker engine 和 dockershim socket,则输出结果中 --container-runtime
不是 remote
, 如果设置了 --container-runtime-endpoint
参数,查看套接字名称即可得知当前使用的运行时。
如若套接字 unix:///run/containerd/containerd.sock
是 containerd 的端点。 如果你将节点上的容器运行时从 Docker Engine 改变为 containerd,可在
迁移到不同的运行时
找到更多信息。或者,如果你想在 Kubernetes v1.24 及以后的版本仍使用 Docker Engine,
可以安装 CRI 兼容的适配器实现,如 cri-dockerd
。
cri-dockerd
。
4.2.1.4 - 排查 CNI 插件相关的错误 为了避免 CNI 插件相关的错误,需要验证你正在使用或升级到一个经过测试的容器运行时,
该容器运行时能够在你的 Kubernetes 版本上正常工作。
关于 "Incompatible CNI versions" 和 "Failed to destroy network for sandbox" 错误 在 containerd v1.6.0-v1.6.3 中,当配置或清除 Pod CNI 网络时,如果 CNI 插件没有升级和/或
CNI 配置文件中没有声明 CNI 配置版本时,会出现服务问题。containerd 团队报告说:
“这些问题在 containerd v1.6.4 中得到了解决。”
在使用 containerd v1.6.0-v1.6.3 时,如果你不升级 CNI 插件和/或声明 CNI 配置版本,
你可能会遇到以下 "Incompatible CNI versions" 或 "Failed to destroy network for sandbox"
错误状况。
Incompatible CNI versions 错误 如果因为配置版本比插件版本新,导致你的 CNI 插件版本与配置中的插件版本无法正确匹配时,
在启动 Pod 时,containerd 日志可能会显示类似的错误信息:
incompatible CNI versions; config is \"1.0.0\", plugin supports [\"0.1.0\" \"0.2.0\" \"0.3.0\" \"0.3.1\" \"0.4.0\"]"
为了解决这个问题,需要更新你的 CNI 插件和 CNI 配置文件 。
Failed to destroy network for sandbox 错误 如果 CNI 插件配置中未给出插件的版本,
Pod 可能可以运行。但是,停止 Pod 时会产生类似于以下错误:
ERRO[2022-04-26T00:43:24.518165483Z] StopPodSandbox for "b" failed
error="failed to destroy network for sandbox \"bbc85f891eaf060c5a879e27bba9b6b06450210161dfdecfbb2732959fb6500a\": invalid version \"\": the version is empty"
此错误使 Pod 处于未就绪状态,且仍然挂接到某网络名字空间上。
为修复这一问题,编辑 CNI 配置文件 以添加缺失的版本信息。
下一次尝试停止 Pod 应该会成功。
更新你的 CNI 插件和 CNI 配置文件 如果你使用 containerd v1.6.0-v1.6.3 并遇到 "Incompatible CNI versions" 或者
"Failed to destroy network for sandbox" 错误,考虑更新你的 CNI 插件并编辑 CNI 配置文件。
以下是针对各节点要执行的典型步骤的概述:
安全地腾空并隔离节点 。停止容器运行时和 kubelet 服务后,执行以下升级操作: 如果你正在运行 CNI 插件,请将它们升级到最新版本。 如果你使用的是非 CNI 插件,请将它们替换为 CNI 插件,并使用最新版本的插件。 更新插件配置文件以指定或匹配 CNI 规范支持的插件版本,
如后文"containerd 配置文件示例" 章节所示。 对于 containerd
,请确保你已安装 CNI loopback 插件的最新版本(v1.0.0 或更高版本)。 将节点组件(例如 kubelet)升级到 Kubernetes v1.24 升级到或安装最新版本的容器运行时。 通过重新启动容器运行时和 kubelet 将节点重新加入到集群。取消节点隔离(kubectl uncordon <nodename>
)。 containerd 配置文件示例 以下示例显示了 containerd
运行时 v1.6.x 的配置,
它支持最新版本的 CNI 规范(v1.0.0)。
请参阅你的插件和网络提供商的文档,以获取有关你系统配置的进一步说明。
在 Kubernetes 中,作为其默认行为,containerd 运行时为 Pod 添加一个本地回路接口,lo
。
containerd 运行时通过 CNI 插件 loopback
配置本地回路接口。loopback
插件作为 containerd
发布包的一部分,扮演 cni
角色。
containerd
v1.6.0 及更高版本包括与 CNI v1.0.0 兼容的 loopback 插件以及其他默认 CNI 插件。
loopback 插件的配置由 containerd 内部完成, 并被设置为使用 CNI v1.0.0。
这也意味着当这个更新版本的 containerd
启动时,loopback
插件的版本必然是 v1.0.0 或更高版本。
以下 Bash 命令生成一个 CNI 配置示例。这里,cniVersion
字段被设置为配置版本值 1.0.0,
以供 containerd
调用 CNI 桥接插件时使用。
cat << EOF | tee /etc/cni/net.d/10-containerd-net.conflist
{
"cniVersion": "1.0.0",
"name": "containerd-net",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"promiscMode": true,
"ipam": {
"type": "host-local",
"ranges": [
[{
"subnet": "10.88.0.0/16"
}],
[{
"subnet": "2001:db8:4860::/64"
}]
],
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "::/0" }
]
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
EOF
基于你的用例和网络地址规划,将前面示例中的 IP 地址范围更新为合适的值。
4.2.1.5 - 检查移除 Dockershim 是否对你有影响 Kubernetes 的 dockershim
组件使得你可以把 Docker 用作 Kubernetes 的
容器运行时 。
在 Kubernetes v1.24 版本中,内建组件 dockershim
被移除。
本页讲解你的集群把 Docker 用作容器运行时的运作机制,
并提供使用 dockershim
时,它所扮演角色的详细信息,
继而展示了一组操作,可用来检查移除 dockershim
对你的工作负载是否有影响。
检查你的应用是否依赖于 Docker 即使你是通过 Docker 创建的应用容器,也不妨碍你在其他任何容器运行时上运行这些容器。
这种使用 Docker 的方式并不构成对 Docker 作为一个容器运行时的依赖。
当用了别的容器运行时之后,Docker 命令可能不工作,或者产生意外的输出。
下面是判定你是否依赖于 Docker 的方法。
确认没有特权 Pod 执行 Docker 命令(如 docker ps
)、重新启动 Docker
服务(如 systemctl restart docker.service
)或修改 Docker 配置文件
/etc/docker/daemon.json
。 检查 Docker 配置文件(如 /etc/docker/daemon.json
)中容器镜像仓库的镜像(mirror)站点设置。
这些配置通常需要针对不同容器运行时来重新设置。 检查确保在 Kubernetes 基础设施之外的节点上运行的脚本和应用程序没有执行 Docker 命令。
可能的情况有:SSH 到节点排查故障; 节点启动脚本; 直接安装在节点上的监控和安全代理。 检查执行上述特权操作的第三方工具。
详细操作请参考从 dockershim 迁移遥测和安全代理 。 确认没有对 dockershim 行为的间接依赖。这是一种极端情况,不太可能影响你的应用。
一些工具很可能被配置为使用了 Docker 特性,比如,基于特定指标发警报,
或者在故障排查指令的一个环节中搜索特定的日志信息。
如果你有此类配置的工具,需要在迁移之前,在测试集群上测试这类行为。 Docker 依赖详解 容器运行时 是一个软件,
用来运行组成 Kubernetes Pod 的容器。
Kubernetes 负责编排和调度 Pod;在每一个节点上,kubelet
使用抽象的容器运行时接口,所以你可以任意选用兼容的容器运行时。
在早期版本中,Kubernetes 提供的兼容性支持一个容器运行时:Docker。
在 Kubernetes 后来的发展历史中,集群运营人员希望采用别的容器运行时。
于是 CRI 被设计出来满足这类灵活性需求 - 而 kubelet 亦开始支持 CRI。
然而,因为 Docker 在 CRI 规范创建之前就已经存在,Kubernetes 就创建了一个适配器组件 dockershim
。
dockershim 适配器允许 kubelet 与 Docker 交互,就好像 Docker 是一个 CRI 兼容的运行时一样。
你可以阅读博文
Kubernetes 正式支持集成 Containerd 。
切换到 Containerd 容器运行时可以消除掉中间环节。
所有相同的容器都可由 Containerd 这类容器运行时来运行。
但是现在,由于直接用容器运行时调度容器,它们对 Docker 是不可见的。
因此,你以前用来检查这些容器的 Docker 工具或漂亮的 UI 都不再可用。
你不能再使用 docker ps
或 docker inspect
命令来获取容器信息。
由于你不能列出容器,因此你不能获取日志、停止容器,甚至不能通过 docker exec
在容器中执行命令。
说明: 如果你在用 Kubernetes 运行工作负载,最好通过 Kubernetes API 停止容器,
而不是通过容器运行时来停止它们(此建议适用于所有容器运行时,不仅仅是针对 Docker)。
你仍然可以下载镜像,或者用 docker build
命令创建它们。
但用 Docker 创建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。
为了在 Kubernetes 中使用,需要把镜像推送(push)到某镜像仓库。
接下来 4.2.1.6 - 从 dockershim 迁移遥测和安全代理 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Kubernetes 对与 Docker Engine 直接集成的支持已被弃用且已经被删除。
大多数应用程序不直接依赖于托管容器的运行时。但是,仍然有大量的遥测和监控代理依赖
docker 来收集容器元数据、日志和指标。
本文汇总了一些信息和链接:信息用于阐述如何探查这些依赖,链接用于解释如何迁移这些代理去使用通用的工具或其他容器运行。
遥测和安全代理 在 Kubernetes 集群中,有几种不同的方式来运行遥测或安全代理。
一些代理在以 DaemonSet 的形式运行或直接在节点上运行时,直接依赖于 Docker Engine。
为什么有些遥测代理会与 Docker Engine 通信? 从历史上看,Kubernetes 是专门为与 Docker Engine 一起工作而编写的。
Kubernetes 负责网络和调度,依靠 Docker Engine
在节点上启动并运行容器(在 Pod 内)。一些与遥测相关的信息,例如 pod 名称,
只能从 Kubernetes 组件中获得。其他数据,例如容器指标,不是容器运行时的责任。
早期遥测代理需要查询容器运行时和 Kubernetes 以报告准确的信息。
随着时间的推移,Kubernetes 获得了支持多种运行时的能力,
现在支持任何兼容容器运行时接口 的运行时。
一些代理和 Docker 工具紧密绑定。比如代理会用到
docker ps
或 docker top
这类命令来列出容器和进程,用
docker logs
订阅 Docker 的日志。
如果现有集群中的节点使用 Docker Engine,在你切换到其它容器运行时的时候,
这些命令将不再起作用。
识别依赖于 Docker Engine 的 DaemonSet 如果某 Pod 想调用运行在节点上的 dockerd
,该 Pod 必须满足以下两个条件之一:
将包含 Docker 守护进程特权套接字的文件系统挂载为一个卷 ;或 直接以卷的形式挂载 Docker 守护进程特权套接字的特定路径。 举例来说:在 COS 镜像中,Docker 通过 /var/run/docker.sock
开放其 Unix 域套接字。
这意味着 Pod 的规约中需要包含 hostPath
卷以挂载 /var/run/docker.sock
。
下面是一个 shell 示例脚本,用于查找包含直接映射 Docker 套接字的挂载点的 Pod。
你也可以删掉 grep '/var/run/docker.sock'
这一代码片段以查看其它挂载信息。
kubectl get pods --all-namespaces \
-o= jsonpath = '{range .items[*]}{"\n"}{.metadata.namespace}{":\t"}{.metadata.name}{":\t"}{range .spec.volumes[*]}{.hostPath.path}{", "}{end}{end}' \
| sort \
| grep '/var/run/docker.sock'
说明: 对于 Pod 来说,访问宿主机上的 Docker 还有其他方式。
例如,可以挂载
/var/run
的父目录而非其完整路径
(就像
这个例子 )。
上述脚本只检测最常见的使用方式。
检测节点代理对 Docker 的依赖性 在你的集群节点被定制、且在各个节点上均安装了额外的安全和遥测代理的场景下,
一定要和代理的供应商确认:该代理是否依赖于 Docker。
遥测和安全代理的供应商 本节旨在汇总有关可能依赖于容器运行时的各种遥测和安全代理的信息。
我们通过
谷歌文档
提供了为各类遥测和安全代理供应商准备的持续更新的迁移指导。
请与供应商联系,获取从 dockershim 迁移的最新说明。
从 dockershim 迁移 无需更改:在运行时变更时可以无缝切换运行。
如何迁移:
Kubernetes 中对于 Docker 的弃用
名字中包含以下字符串的 Pod 可能访问 Docker Engine:
datadog-agent
datadog
dd-agent
如何迁移:
在 Dynatrace 上从 Docker-only 迁移到通用容器指标
Containerd 支持公告:在基于 containerd 的 Kubernetes 环境的获取容器的自动化全栈可见性
CRI-O 支持公告:在基于 CRI-O 的 Kubernetes 环境获取容器的自动化全栈可见性(测试版)
名字中包含以下字符串的 Pod 可能访问 Docker:
如何迁移:
迁移 Falco 从 dockershim
Falco 支持任何与 CRI 兼容的运行时(默认配置中使用 containerd);该文档解释了所有细节。
名字中包含以下字符串的 Pod 可能访问 Docker:
在依赖于 CRI(非 Docker)的集群上安装 Prisma Cloud 时,查看
Prisma Cloud 提供的文档 。
名字中包含以下字符串的 Pod 可能访问 Docker:
SignalFx Smart Agent(已弃用)在 Kubernetes 集群上使用了多种不同的监视器,
包括 kubernetes-cluster
,kubelet-stats/kubelet-metrics
,docker-container-stats
。
kubelet-stats
监视器此前已被供应商所弃用,现支持 kubelet-metrics
。
docker-container-stats
监视器受 dockershim 移除的影响。
不要为 docker-container-stats
监视器使用 Docker Engine 之外的运行时。
如何从依赖 dockershim 的代理迁移:
从所配置的监视器 中移除 docker-container-stats
。
注意,若节点上已经安装了 Docker,在非 dockershim 环境中启用此监视器后会导致报告错误的指标;
如果节点未安装 Docker,则无法获得指标。 启用和配置 kubelet-metrics
监视器。说明: 收集的指标会发生变化。具体请查看你的告警规则和仪表盘。
名字中包含以下字符串的 Pod 可能访问 Docker:
Yahoo Kubectl Flame Flame 不支持 Docker 以外的容器运行时,具体可见 https://github.com/yahoo/kubectl-flame/issues/51
4.2.2 - 用 kubeadm 进行管理 4.2.2.1 - 配置 cgroup 驱动 本页阐述如何配置 kubelet 的 cgroup 驱动以匹配 kubeadm 集群中的容器运行时的 cgroup 驱动。
准备开始 你应该熟悉 Kubernetes 的容器运行时需求 。
配置容器运行时 cgroup 驱动 容器运行时 页面提到:
由于 kubeadm 把 kubelet 视为一个系统服务来管理,所以对基于 kubeadm 的安装,
我们推荐使用 systemd
驱动,不推荐 cgroupfs
驱动。
此页还详述了如何安装若干不同的容器运行时,并将 systemd
设为其默认驱动。
配置 kubelet 的 cgroup 驱动 kubeadm 支持在执行 kubeadm init
时,传递一个 KubeletConfiguration
结构体。
KubeletConfiguration
包含 cgroupDriver
字段,可用于控制 kubelet 的 cgroup 驱动。
说明: 在版本 1.22 中,如果用户没有在 KubeletConfiguration
中设置 cgroupDriver
字段,
kubeadm init
会将它设置为默认值 systemd
。
这是一个最小化的示例,其中显式的配置了此字段:
# kubeadm-config.yaml
kind : ClusterConfiguration
apiVersion : kubeadm.k8s.io/v1beta3
kubernetesVersion : v1.21.0
---
kind : KubeletConfiguration
apiVersion : kubelet.config.k8s.io/v1beta1
cgroupDriver : systemd
这样一个配置文件就可以传递给 kubeadm 命令了:
kubeadm init --config kubeadm-config.yaml
说明: Kubeadm 对集群所有的节点,使用相同的 KubeletConfiguration
。
KubeletConfiguration
存放于 kube-system
命名空间下的某个
ConfigMap 对象中。
执行 init
、join
和 upgrade
等子命令会促使 kubeadm
将 KubeletConfiguration
写入到文件 /var/lib/kubelet/config.yaml
中,
继而把它传递给本地节点的 kubelet。
使用 cgroupfs
驱动 如仍需使用 cgroupfs
且要防止 kubeadm upgrade
修改现有系统中
KubeletConfiguration
的 cgroup 驱动,你必须显式声明它的值。
此方法应对的场景为:在将来某个版本的 kubeadm 中,你不想使用默认的 systemd
驱动。
参阅以下章节“修改 kubelet 的 ConfigMap ”,了解显式设置该值的方法。
如果你希望配置容器运行时来使用 cgroupfs
驱动,
则必须参考所选容器运行时的文档。
迁移到 systemd
驱动 要将现有 kubeadm 集群的 cgroup 驱动就地升级为 systemd
,
需要执行一个与 kubelet 升级类似的过程。
该过程必须包含下面两个步骤:
说明: 还有一种方法,可以用已配置了 systemd
的新节点替换掉集群中的老节点。
按这种方法,在加入新节点、确保工作负载可以安全迁移到新节点、及至删除旧节点这一系列操作之前,
只需执行以下第一个步骤。
修改 kubelet 的 ConfigMap 运行 kubectl edit cm kubelet-config -n kube-system
。
修改现有 cgroupDriver
的值,或者新增如下式样的字段:
该字段必须出现在 ConfigMap 的 kubelet:
小节下。
更新所有节点的 cgroup 驱动 对于集群中的每一个节点:
执行命令 kubectl drain <node-name> --ignore-daemonsets
,以
腾空节点 执行命令 systemctl stop kubelet
,以停止 kubelet 停止容器运行时 修改容器运行时 cgroup 驱动为 systemd
在文件 /var/lib/kubelet/config.yaml
中添加设置 cgroupDriver: systemd
启动容器运行时 执行命令 systemctl start kubelet
,以启动 kubelet 执行命令 kubectl uncordon <node-name>
,以
取消节点隔离 在节点上依次执行上述步骤,确保工作负载有充足的时间被调度到其他节点。
流程完成后,确认所有节点和工作负载均健康如常。
4.2.2.2 - 使用 kubeadm 进行证书管理 特性状态: Kubernetes v1.15 [stable]
由 kubeadm 生成的客户端证书在 1 年后到期。
本页说明如何使用 kubeadm 管理证书续订,同时也涵盖其他与 kubeadm 证书管理相关的说明。
准备开始 你应该熟悉 Kubernetes 中的 PKI 证书和要求 。
使用自定义的证书 默认情况下,kubeadm 会生成运行一个集群所需的全部证书。
你可以通过提供你自己的证书来改变这个行为策略。
如果要这样做,你必须将证书文件放置在通过 --cert-dir
命令行参数或者 kubeadm 配置中的
certificatesDir
配置项指明的目录中。默认的值是 /etc/kubernetes/pki
。
如果在运行 kubeadm init
之前存在给定的证书和私钥对,kubeadm 将不会重写它们。
例如,这意味着你可以将现有的 CA 复制到 /etc/kubernetes/pki/ca.crt
和
/etc/kubernetes/pki/ca.key
中,而 kubeadm 将使用此 CA 对其余证书进行签名。
外部 CA 模式 只提供了 ca.crt
文件但是不提供 ca.key
文件也是可以的
(这只对 CA 根证书可用,其它证书不可用)。
如果所有的其它证书和 kubeconfig 文件已就绪,kubeadm 检测到满足以上条件就会激活
"外部 CA" 模式。kubeadm 将会在没有 CA 密钥文件的情况下继续执行。
否则,kubeadm 将独立运行 controller-manager,附加一个
--controllers=csrsigner
的参数,并且指明 CA 证书和密钥。
PKI 证书和要求 包括集群使用外部 CA 的设置指南。
检查证书是否过期 你可以使用 check-expiration
子命令来检查证书何时过期
kubeadm certs check-expiration
输出类似于以下内容:
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED
admin.conf Dec 30, 2020 23:36 UTC 364d no
apiserver Dec 30, 2020 23:36 UTC 364d ca no
apiserver-etcd-client Dec 30, 2020 23:36 UTC 364d etcd-ca no
apiserver-kubelet-client Dec 30, 2020 23:36 UTC 364d ca no
controller-manager.conf Dec 30, 2020 23:36 UTC 364d no
etcd-healthcheck-client Dec 30, 2020 23:36 UTC 364d etcd-ca no
etcd-peer Dec 30, 2020 23:36 UTC 364d etcd-ca no
etcd-server Dec 30, 2020 23:36 UTC 364d etcd-ca no
front-proxy-client Dec 30, 2020 23:36 UTC 364d front-proxy-ca no
scheduler.conf Dec 30, 2020 23:36 UTC 364d no
CERTIFICATE AUTHORITY EXPIRES RESIDUAL TIME EXTERNALLY MANAGED
ca Dec 28, 2029 23:36 UTC 9y no
etcd-ca Dec 28, 2029 23:36 UTC 9y no
front-proxy-ca Dec 28, 2029 23:36 UTC 9y no
该命令显示 /etc/kubernetes/pki
文件夹中的客户端证书以及
kubeadm(admin.conf
、controller-manager.conf
和 scheduler.conf
)
使用的 KUBECONFIG 文件中嵌入的客户端证书的到期时间/剩余时间。
另外,kubeadm 会通知用户证书是否由外部管理;
在这种情况下,用户应该小心的手动/使用其他工具来管理证书更新。
警告: kubeadm
不能管理由外部 CA 签名的证书
说明: 上面的列表中没有包含
kubelet.conf
,因为 kubeadm 将 kubelet 配置为
自动更新证书 。
轮换的证书位于目录
/var/lib/kubelet/pki
。
要修复过期的 kubelet 客户端证书,请参阅
kubelet 客户端证书轮换失败 。
警告: 在通过 kubeadm init
创建的节点上,在 kubeadm 1.17 版本之前有一个
缺陷 ,该缺陷
使得你必须手动修改 kubelet.conf
文件的内容。
kubeadm init
操作结束之后,你必须更新 kubelet.conf
文件
将 client-certificate-data
和 client-key-data
改为如下所示的内容
以便使用轮换后的 kubelet 客户端证书:
client-certificate : /var/lib/kubelet/pki/kubelet-client-current.pem
client-key : /var/lib/kubelet/pki/kubelet-client-current.pem
自动更新证书 kubeadm 会在控制面
升级
的时候更新所有证书。
这个功能旨在解决最简单的用例;如果你对此类证书的更新没有特殊要求,
并且定期执行 Kubernetes 版本升级(每次升级之间的间隔时间少于 1 年),
则 kubeadm 将确保你的集群保持最新状态并保持合理的安全性。
说明: 最佳的做法是经常升级集群以确保安全。
如果你对证书更新有更复杂的需求,则可通过将 --certificate-renewal=false
传递给
kubeadm upgrade apply
或者 kubeadm upgrade node
,从而选择不采用默认行为。
警告: kubeadm 在 1.17 版本之前有一个
缺陷 ,
该缺陷导致
kubeadm update node
执行时
--certificate-renewal
的默认值被设置为
false
。
在这种情况下,你需要显式地设置
--certificate-renewal=true
。
手动更新证书 你能随时通过 kubeadm certs renew
命令手动更新你的证书。
此命令用 CA(或者 front-proxy-CA )证书和存储在 /etc/kubernetes/pki
中的密钥执行更新。
执行完此命令之后你需要重启控制面 Pods。因为动态证书重载目前还不被所有组件和证书支持,所有这项操作是必须的。
静态 Pods 是被本地 kubelet 而不是 API Server 管理,
所以 kubectl 不能用来删除或重启他们。
要重启静态 Pod 你可以临时将清单文件从 /etc/kubernetes/manifests/
移除并等待 20 秒
(参考 KubeletConfiguration 结构 中的fileCheckFrequency
值)。
如果 Pod 不在清单目录里,kubelet 将会终止它。
在另一个 fileCheckFrequency
周期之后你可以将文件移回去,为了组件可以完成 kubelet 将重新创建 Pod 和证书更新。
警告: 如果你运行了一个 HA 集群,这个命令需要在所有控制面板节点上执行。
说明: certs renew
使用现有的证书作为属性(Common Name、Organization、SAN 等)的权威来源,
而不是 kubeadm-config ConfigMap。强烈建议使它们保持同步。
kubeadm certs renew
提供以下选项:
Kubernetes 证书通常在一年后到期。
--csr-only
可用于经过一个外部 CA 生成的证书签名请求来更新证书(无需实际替换更新证书);
更多信息请参见下节。可以更新单个证书而不是全部证书。 用 Kubernetes 证书 API 更新证书 本节提供有关如何使用 Kubernetes 证书 API 执行手动证书更新的更多详细信息。
注意: 这些是针对需要将其组织的证书基础结构集成到 kubeadm 构建的集群中的用户的高级主题。
如果默认的 kubeadm 配置满足了你的需求,则应让 kubeadm 管理证书。
设置一个签名者(Signer) Kubernetes 证书颁发机构不是开箱即用。你可以配置外部签名者,例如 cert-manager ,
也可以使用内置签名者。
内置签名者是
kube-controller-manager
的一部分。
要激活内置签名者,请传递 --cluster-signing-cert-file
和 --cluster-signing-key-file
参数。
如果你正在创建一个新的集群,你可以使用 kubeadm 的
配置文件 。
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
controllerManager :
extraArgs :
cluster-signing-cert-file : /etc/kubernetes/pki/ca.crt
cluster-signing-key-file : /etc/kubernetes/pki/ca.key
创建证书签名请求 (CSR) 有关使用 Kubernetes API 创建 CSR 的信息,
请参见创建 CertificateSigningRequest 。
通过外部 CA 更新证书 本节提供有关如何使用外部 CA 执行手动更新证书的更多详细信息。
为了更好的与外部 CA 集成,kubeadm 还可以生成证书签名请求(CSR)。
CSR 表示向 CA 请求客户的签名证书。
在 kubeadm 术语中,通常由磁盘 CA 签名的任何证书都可以作为 CSR 生成。但是,CA 不能作为 CSR 生成。
创建证书签名请求 (CSR) 你可以通过 kubeadm certs renew --csr-only
命令创建证书签名请求。
CSR 和随附的私钥都在输出中给出。
你可以传入一个带有 --csr-dir
的目录,将 CSR 输出到指定位置。
如果未指定 --csr-dir
,则使用默认证书目录(/etc/kubernetes/pki
)。
证书可以通过 kubeadm certs renew --csr-only
来续订。
和 kubeadm init
一样,可以使用 --csr-dir
标志指定一个输出目录。
CSR 签署证书后,必须将证书和私钥复制到 PKI 目录(默认情况下为 /etc/kubernetes/pki
)。
CSR 中包含一个证书的名字,域和 IP,但是未指定用法。
颁发证书时,CA 有责任指定正确的证书用法
使用首选方法对证书签名后,必须将证书和私钥复制到 PKI 目录(默认为 /etc/kubernetes/pki
)。
证书机构(CA)轮换 kubeadm 并不直接支持对 CA 证书的轮换或者替换。
关于手动轮换或者置换 CA 的更多信息,可参阅
手动轮换 CA 证书 。
启用已签名的 kubelet 服务证书 默认情况下,kubeadm 所部署的 kubelet 服务证书是自签名(Self-Signed)。
这意味着从 metrics-server
这类外部服务发起向 kubelet 的链接时无法使用 TLS 来完成保护。
要在新的 kubeadm 集群中配置 kubelet 以使用被正确签名的服务证书,
你必须向 kubeadm init
传递如下最小配置数据:
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
---
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
serverTLSBootstrap : true
如果你已经创建了集群,你必须通过执行下面的操作来完成适配:
找到 kube-system
名字空间中名为 kubelet-config-1.25
的 ConfigMap 并编辑之。
在该 ConfigMap 中,kubelet
键下面有一个
KubeletConfiguration
文档作为其取值。编辑该 KubeletConfiguration 文档以设置
serverTLSBootstrap: true
。 在每个节点上,在 /var/lib/kubelet/config.yaml
文件中添加
serverTLSBootstrap: true
字段,并使用 systemctl restart kubelet
来重启 kubelet。 字段 serverTLSBootstrap
将允许启动引导 kubelet 的服务证书,方式
是从 certificates.k8s.io
API 处读取。这种方式的一种局限在于这些
证书的 CSR(证书签名请求)不能被 kube-controller-manager 中默认的
签名组件
kubernetes.io/kubelet-serving
批准。需要用户或者第三方控制器来执行此操作。
可以使用下面的命令来查看 CSR:
NAME AGE SIGNERNAME REQUESTOR CONDITION
csr-9wvgt 112s kubernetes.io/kubelet-serving system:node:worker-1 Pending
csr-lz97v 1m58s kubernetes.io/kubelet-serving system:node:control-plane-1 Pending
你可以执行下面的操作来批准这些请求:
kubectl certificate approve <CSR-名称>
默认情况下,这些服务证书会在一年后过期。
kubeadm 将 KubeletConfiguration
的 rotateCertificates
字段设置为
true
;这意味着证书快要过期时,会生成一组针对服务证书的新的 CSR,而
这些 CSR 也要被批准才能完成证书轮换。
要进一步了解这里的细节,可参阅
证书轮换
文档。
如果你在寻找一种能够自动批准这些 CSR 的解决方案,建议你与你的云提供商
联系,询问他们是否有 CSR 签名组件,用来以带外(out-of-band)的方式检查
节点的标识符。
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
也可以使用第三方定制的控制器:
除非既能够验证 CSR 中的 CommonName,也能检查请求的 IP 和域名,
这类控制器还算不得安全的机制。
只有完成彻底的检查,才有可能避免有恶意的、能够访问 kubelet 客户端证书的第三方
为任何 IP 或域名请求服务证书。
为其他用户生成 kubeconfig 文件 在集群创建过程中,kubeadm 对 admin.conf
中的证书进行签名时,将其配置为
Subject: O = system:masters, CN = kubernetes-admin
。
system:masters
是一个例外的超级用户组,可以绕过鉴权层(例如 RBAC)。
强烈建议不要将 admin.conf
文件与任何人共享。
你要使用 kubeadm kubeconfig user
命令为其他用户生成 kubeconfig 文件,这个命令支持命令行参数和
kubeadm 配置结构 。
以上命令会将 kubeconfig 打印到终端上,也可以使用 kubeadm kubeconfig user ... > somefile.conf
输出到一个文件中。
如下 kubeadm 可以在 --config
后加的配置文件示例:
# example.yaml
apiVersion : kubeadm.k8s.io/v1beta3
kind : ClusterConfiguration
# kubernetes 将作为 kubeconfig 中集群名称
clusterName : "kubernetes"
# some-dns-address:6443 将作为集群 kubeconfig 文件中服务地址(IP 或者 DNS 名称)
controlPlaneEndpoint : "some-dns-address:6443"
# 从本地挂载集群的 CA 秘钥和 CA 证书
certificatesDir : "/etc/kubernetes/pki"
确保这些设置与所需的目标集群设置相匹配。可以使用以下命令查看现有集群的设置:
kubectl get cm kubeadm-config -n kube-system -o= jsonpath = "{.data.ClusterConfiguration}"
以下示例将为在 appdevs
组的 johndoe
用户创建一个有效期为 24 小时的 kubeconfig 文件:
kubeadm kubeconfig user --config example.yaml --org appdevs --client-name johndoe --validity-period 24h
以下示例将为管理员创建一个有效期有一周的 kubeconfig 文件:
kubeadm kubeconfig user --config example.yaml --client-name admin --validity-period 168h
4.2.2.3 - 重新配置 kubeadm 集群 kubeadm 不支持自动重新配置部署在托管节点上的组件的方式。
一种自动化的方法是使用自定义的
operator 。
要修改组件配置,你必须手动编辑磁盘上关联的集群对象和文件。
本指南展示了实现 kubeadm 集群重新配置所需执行的正确步骤顺序。
准备开始 你需要一个使用 kubeadm 部署的集群 拥有管理员凭据(/etc/kubernetes/admin.conf
)
和从安装了 kubectl 的主机到集群中正在运行的 kube-apiserver 的网络连接 在所有主机上安装文本编辑器 重新配置集群 kubeadm 在 ConfigMap 和其他对象中写入了一组集群范围的组件配置选项。
这些对象必须手动编辑,可以使用命令 kubectl edit
。
kubectl edit
命令将打开一个文本编辑器,你可以在其中直接编辑和保存对象。
你可以使用环境变量 KUBECONFIG
和 KUBE_EDITOR
来指定 kubectl
使用的 kubeconfig 文件和首选文本编辑器的位置。
例如:
KUBECONFIG=/etc/kubernetes/admin.conf KUBE_EDITOR=nano kubectl edit <parameters>
说明: 保存对这些集群对象的任何更改后,节点上运行的组件可能不会自动更新。
以下步骤将指导你如何手动执行该操作。
警告: ConfigMaps 中的组件配置存储为非结构化数据(YAML 字符串)。 这意味着在更新
ConfigMap 的内容时不会执行验证。 你必须小心遵循特定组件配置的文档化 API 格式,
并避免引入拼写错误和 YAML 缩进错误。
应用集群配置更改 更新 ClusterConfiguration
在集群创建和升级期间,kubeadm 将其
ClusterConfiguration
写入 kube-system
命名空间中名为 kubeadm-config
的 ConfigMap。
要更改 ClusterConfiguration
中的特定选项,你可以使用以下命令编辑 ConfigMap:
kubectl edit cm -n kube-system kubeadm-config
配置位于 data.ClusterConfiguration
键下。
说明: ClusterConfiguration
包括各种影响单个组件配置的选项, 例如
kube-apiserver、kube-scheduler、kube-controller-manager、
CoreDNS、etcd 和 kube-proxy。 对配置的更改必须手动反映在节点组件上。
在控制平面节点上反映 ClusterConfiguration
更改 kubeadm 将控制平面组件作为位于 /etc/kubernetes/manifests
目录中的静态 Pod 清单进行管理。
对 apiServer
、controllerManager
、scheduler
或 etcd
键下的
ClusterConfiguration
的任何更改都必须反映在控制平面节点上清单目录中的关联文件中。
此类更改可能包括:
extraArgs
- 需要更新传递给组件容器的标志列表extraMounts
- 需要更新组件容器的卷挂载*SANs
- 需要使用更新的主题备用名称编写新证书在继续进行这些更改之前,请确保你已备份目录 /etc/kubernetes/
。
要编写新证书,你可以使用:
kubeadm init phase certs <component-name> --config <config-file>
要在 /etc/kubernetes/manifests
中编写新的清单文件,你可以使用:
kubeadm init phase control-plane <component-name> --config <config-file>
<config-file>
内容必须与更新后的 ClusterConfiguration
匹配。
<component-name>
值必须是组件的名称。
说明: 更新 /etc/kubernetes/manifests
中的文件将告诉 kubelet 重新启动相应组件的静态 Pod。
尝试一次对一个节点进行这些更改,以在不停机的情况下离开集群。
应用 kubelet 配置更改 更新 KubeletConfiguration
在集群创建和升级期间,kubeadm 将其
KubeletConfiguration
写入 kube-system
命名空间中名为 kubelet-config
的 ConfigMap。
你可以使用以下命令编辑 ConfigMap:
kubectl edit cm -n kube-system kubelet-config
配置位于 data.kubelet
键下。
反映 kubelet 的更改 要反映 kubeadm 节点上的更改,你必须执行以下操作:
登录到 kubeadm 节点 运行 kubeadm upgrade node phase kubelet-config
下载最新的
kubelet-config
ConfigMap 内容到本地文件 /var/lib/kubelet/config.conf
编辑文件 /var/lib/kubelet/kubeadm-flags.env
以使用标志来应用额外的配置 使用 systemctl restart kubelet
重启 kubelet 服务 说明: 一次执行一个节点的这些更改,以允许正确地重新安排工作负载。
说明: 在 kubeadm upgrade
期间,kubeadm 从 kubelet-config
ConfigMap
下载 KubeletConfiguration
并覆盖 /var/lib/kubelet/config.conf
的内容。
这意味着节点本地配置必须通过/var/lib/kubelet/kubeadm-flags.env
中的标志或在
kubeadm upgrade 后手动更新
/var/lib/kubelet/config.conf`的内容来应用,然后重新启动 kubelet。
应用 kube-proxy 配置更改 更新 KubeProxyConfiguration
在集群创建和升级期间,kubeadm 将其写入
KubeProxyConfiguration
在名为 kube-proxy
的 kube-system
命名空间中的 ConfigMap 中。
此 ConfigMap 由 kube-system
命名空间中的 kube-proxy
DaemonSet 使用。
要更改 KubeProxyConfiguration
中的特定选项,你可以使用以下命令编辑 ConfigMap:
kubectl edit cm -n kube-system kube-proxy
配置位于 data.config.conf
键下。
反映 kube-proxy 的更改 更新 kube-proxy
ConfigMap 后,你可以重新启动所有 kube-proxy Pod:
获取 Pod 名称:
kubectl get po -n kube-system | grep kube-proxy
使用以下命令删除 Pod:
kubectl delete po -n kube-system <pod-name>
将创建使用更新的 ConfigMap 的新 Pod。
说明: 由于 kubeadm 将 kube-proxy 部署为 DaemonSet,因此不支持特定于节点的配置。
应用 CoreDNS 配置更改 更新 CoreDNS 的 Deployment 和 Service kubeadm 将 CoreDNS 部署为名为 coredns
的 Deployment,并使用 Service kube-dns
,
两者都在 kube-system
命名空间中。
要更新任何 CoreDNS 设置,你可以编辑 Deployment 和 Service:
kubectl edit deployment -n kube-system coredns
kubectl edit service -n kube-system kube-dns
反映 CoreDNS 的更改 应用 CoreDNS 更改后,你可以删除 CoreDNS Pod。
获取 Pod 名称:
kubectl get po -n kube-system | grep coredns
使用以下命令删除 Pod:
kubectl delete po -n kube-system <pod-name>
将创建具有更新的 CoreDNS 配置的新 Pod。
说明: kubeadm 不允许在集群创建和升级期间配置 CoreDNS。
这意味着如果执行了 kubeadm upgrade apply
,你对
CoreDNS 对象的更改将丢失并且必须重新应用。
持久化重新配置 在受管节点上执行 kubeadm upgrade
期间,kubeadm
可能会覆盖在创建集群(重新配置)后应用的配置。
持久化 Node 对象重新配置 kubeadm 在特定 Kubernetes 节点的 Node 对象上写入标签、污点、CRI
套接字和其他信息。要更改此 Node 对象的任何内容,你可以使用:
kubectl edit no <node-name>
在 kubeadm upgrade
期间,此类节点的内容可能会被覆盖。
如果你想在升级后保留对 Node 对象的修改,你可以准备一个
kubectl patch
并将其应用到 Node 对象:
kubectl patch no <node-name> --patch-file <patch-file>
持久化控制平面组件重新配置 控制平面配置的主要来源是存储在集群中的 ClusterConfiguration
对象。
要扩展静态 Pod 清单配置,可以使用
patches 。
这些补丁文件必须作为文件保留在控制平面节点上,以确保它们可以被
kubeadm upgrade ... --patches <directory>
使用。
如果对 ClusterConfiguration
和磁盘上的静态 Pod 清单进行了重新配置,则必须相应地更新节点特定补丁集。
持久化 kubelet 重新配置 对存储在 /var/lib/kubelet/config.conf
中的 KubeletConfiguration
所做的任何更改都将在 kubeadm upgrade
时因为下载集群范围内的 kubelet-config
ConfigMap 的内容而被覆盖。
要持久保存 kubelet 节点特定的配置,文件/var/lib/kubelet/config.conf
必须在升级后手动更新,或者文件/var/lib/kubelet/kubeadm-flags.env
可以包含标志。
kubelet 标志会覆盖相关的 KubeletConfiguration
选项,但请注意,有些标志已被弃用。
更改 /var/lib/kubelet/config.conf
或 /var/lib/kubelet/kubeadm-flags.env
后需要重启 kubelet。
接下来 4.2.2.4 - 升级 kubeadm 集群 本页介绍如何将 kubeadm
创建的 Kubernetes 集群从 1.24.x 版本
升级到 1.25.x 版本以及从 1.25.x
升级到 1.25.y(其中 y > x
)。略过次版本号的升级是
不被支持的。更多详情请访问版本偏差策略 。
要查看 kubeadm 创建的有关旧版本集群升级的信息,请参考以下页面:
升级工作的基本流程如下:
升级主控制平面节点 升级其他控制平面节点 升级工作节点 准备开始 务必仔细认真阅读发行说明 。 集群应使用静态的控制平面和 etcd Pod 或者外部 etcd。 务必备份所有重要组件,例如存储在数据库中应用层面的状态。
kubeadm upgrade
不会影响你的工作负载,只会涉及 Kubernetes 内部的组件,但备份终究是好的。 必须禁用交换分区 。下述说明了在升级过程中何时腾空每个节点。如果你正在对任何 kubelet 进行小版本升级,
你需要先腾空待升级的节点(或多个节点)。对于控制面节点,其上可能运行着 CoreDNS Pod
或者其它非常重要的负载。更多信息见腾空节点 。 升级后,因为容器规约的哈希值已更改,所有容器都会被重新启动。 要验证 kubelet 服务在升级后是否成功重启,可以执行 systemctl status kubelet
或 journalctl -xeu kubelet
查看服务日志。 不建议使用 kubeadm upgrade
的 --config
参数和
kubeadm 配置 API 类型 来重新配置集群,
这样会产生意想不到的结果。
请按照重新配置 kubeadm 集群 中的步骤来进行。 确定要升级到哪个版本 使用操作系统的包管理器找到最新的补丁版本 Kubernetes 1.25:
apt update
apt-cache madison kubeadm
# 在列表中查找最新的 1.25 版本
# 它看起来应该是 1.25.x-00,其中 x 是最新的补丁版本
yum list --showduplicates kubeadm --disableexcludes= kubernetes
# 在列表中查找最新的 1.25 版本
# 它看起来应该是 1.25.x-0,其中 x 是最新的补丁版本
升级控制平面节点 控制面节点上的升级过程应该每次处理一个节点。
首先选择一个要先行升级的控制面节点。该节点上必须拥有
/etc/kubernetes/admin.conf
文件。
执行 “kubeadm upgrade” 对于第一个控制面节点
升级 kubeadm:
# 用最新的补丁版本号替换 1.25.x-00 中的 x
apt-mark unhold kubeadm && \
apt-get update && apt-get install -y kubeadm = 1.25.x-00 && \
apt-mark hold kubeadm
# 用最新的补丁版本号替换 1.25.x-0 中的 x
yum install -y kubeadm-1.25.x-0 --disableexcludes= kubernetes
选择要升级到的目标版本,运行合适的命令。例如:
# 将 x 替换为你为此次升级所选择的补丁版本号
sudo kubeadm upgrade apply v1.25.x
一旦该命令结束,你应该会看到:
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.25.x". Enjoy!
[upgrade/kubelet] Now that your control plane is upgraded, please proceed with upgrading your kubelets if you haven't already done so.
对于其它控制面节点
与第一个控制面节点相同,但是使用:
sudo kubeadm upgrade node
而不是:
sudo kubeadm upgrade apply
此外,不需要执行 kubeadm upgrade plan
和更新 CNI 驱动插件的操作。
腾空节点 升级 kubelet 和 kubectl 升级 kubelet 和 kubectl:
# 用最新的补丁版本替换 1.25.x-00 中的 x
apt-mark unhold kubelet kubectl && \
apt-get update && apt-get install -y kubelet = 1.25.x-00 kubectl = 1.25.x-00 && \
apt-mark hold kubelet kubectl
# 用最新的补丁版本号替换 1.25.x-00 中的 x
yum install -y kubelet-1.25.x-0 kubectl-1.25.x-0 --disableexcludes= kubernetes
重启 kubelet:
sudo systemctl daemon-reload
sudo systemctl restart kubelet
解除节点的保护 通过将节点标记为可调度,让其重新上线:
# 将 <node-to-uncordon> 替换为你的节点名称
kubectl uncordon <node-to-uncordon>
升级工作节点 工作节点上的升级过程应该一次执行一个节点,或者一次执行几个节点,
以不影响运行工作负载所需的最小容量。
升级 kubeadm 升级 kubeadm:
# 将 1.25.x-00 中的 x 替换为最新的补丁版本号
apt-mark unhold kubeadm && \
apt-get update && apt-get install -y kubeadm = 1.25.x-00 && \
apt-mark hold kubeadm
# 用最新的补丁版本替换 1.25.x-00 中的 x
yum install -y kubeadm-1.25.x-0 --disableexcludes= kubernetes
执行 "kubeadm upgrade" 腾空节点 升级 kubelet 和 kubectl 升级 kubelet 和 kubectl:
# 将 1.25.x-00 中的 x 替换为最新的补丁版本
apt-mark unhold kubelet kubectl && \
apt-get update && apt-get install -y kubelet = 1.25.x-00 kubectl = 1.25.x-00 && \
apt-mark hold kubelet kubectl
# 将 1.25.x-0 x 替换为最新的补丁版本
yum install -y kubelet-1.25.x-0 kubectl-1.25.x-0 --disableexcludes= kubernetes
重启 kubelet:
sudo systemctl daemon-reload
sudo systemctl restart kubelet
取消对节点的保护 通过将节点标记为可调度,让节点重新上线:
# 将 <node-to-uncordon> 替换为当前节点的名称
kubectl uncordon <node-to-uncordon>
验证集群的状态 在所有节点上升级 kubelet 后,通过从 kubectl 可以访问集群的任何位置运行以下命令,
验证所有节点是否再次可用:
STATUS
应显示所有节点为 Ready
状态,并且版本号已经被更新。
从故障状态恢复 如果 kubeadm upgrade
失败并且没有回滚,例如由于执行期间节点意外关闭,
你可以再次运行 kubeadm upgrade
。
此命令是幂等的,并最终确保实际状态是你声明的期望状态。
要从故障状态恢复,你还可以运行 kubeadm upgrade apply --force
而无需更改集群正在运行的版本。
在升级期间,kubeadm 向 /etc/kubernetes/tmp
目录下的如下备份文件夹写入数据:
kubeadm-backup-etcd-<date>-<time>
kubeadm-backup-manifests-<date>-<time>
kubeadm-backup-etcd
包含当前控制面节点本地 etcd 成员数据的备份。
如果 etcd 升级失败并且自动回滚也无法修复,则可以将此文件夹中的内容复制到
/var/lib/etcd
进行手工修复。如果使用的是外部的 etcd,则此备份文件夹为空。
kubeadm-backup-manifests
包含当前控制面节点的静态 Pod 清单文件的备份版本。
如果升级失败并且无法自动回滚,则此文件夹中的内容可以复制到
/etc/kubernetes/manifests
目录实现手工恢复。
如果由于某些原因,在升级前后某个组件的清单未发生变化,则 kubeadm 也不会为之生成备份版本。
工作原理 kubeadm upgrade apply
做了以下工作:
检查你的集群是否处于可升级状态:API 服务器是可访问的 所有节点处于 Ready
状态 控制面是健康的 强制执行版本偏差策略。 确保控制面的镜像是可用的或可拉取到服务器上。 如果组件配置要求版本升级,则生成替代配置与/或使用用户提供的覆盖版本配置。 升级控制面组件或回滚(如果其中任何一个组件无法启动)。 应用新的 CoreDNS
和 kube-proxy
清单,并强制创建所有必需的 RBAC 规则。 如果旧文件在 180 天后过期,将创建 API 服务器的新证书和密钥文件并备份旧文件。 kubeadm upgrade node
在其他控制平节点上执行以下操作:
从集群中获取 kubeadm ClusterConfiguration
。 (可选操作)备份 kube-apiserver 证书。 升级控制平面组件的静态 Pod 清单。 为本节点升级 kubelet 配置 kubeadm upgrade node
在工作节点上完成以下工作:
从集群取回 kubeadm ClusterConfiguration
。 为本节点升级 kubelet 配置。 4.2.2.5 - 升级 Windows 节点 特性状态: Kubernetes v1.18 [beta]
本页解释如何升级用 kubeadm 创建的
Windows 节点。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.17.
要获知版本信息,请输入
kubectl version
.
升级工作节点 升级 kubeadm 在 Windows 节点上升级 kubeadm:
# 将 v1.25.0 替换为你希望的版本
curl.exe -Lo <kubeadm.exe 路径> "https://dl.k8s.io/v1.25.0/bin/windows/amd64/kubeadm.exe"
腾空节点 在一个能访问到 Kubernetes API 的机器上,将 Windows 节点标记为不可调度并
驱逐其上的所有负载,以便准备节点维护操作:
# 将 <要腾空的节点> 替换为你要腾空的节点的名称
kubectl drain <要腾空的节点> --ignore-daemonsets
你应该会看到类似下面的输出:
node/ip-172-31-85-18 cordoned
node/ip-172-31-85-18 drained
升级 kubelet 配置 在 Windows 节点上,执行下面的命令来同步新的 kubelet 配置:
升级 kubelet 和 kube-proxy 在 Windows 节点上升级并重启 kubelet:
stop-service kubelet
curl.exe -Lo <kubelet.exe 路径> "https://dl.k8s.io/v1.25.0/bin/windows/amd64/kubelet.exe"
restart-service kubelet
在 Windows 节点上升级并重启 kube-proxy:
stop-service kube-proxy
curl.exe -Lo <kube-proxy .exe 路径> "https://dl.k8s.io/v1.25.0/bin/windows/amd64/kube-proxy.exe"
restart-service kube-proxy
说明: 如果你是在 Pod 内的 HostProcess 容器中运行 kube-proxy,而不是作为 Windows 服务,
你可以通过应用更新版本的 kube-proxy 清单文件来升级 kube-proxy。
对节点执行 uncordon 操作 从一台能够访问到 Kubernetes API 的机器上,通过将节点标记为可调度,使之
重新上线:
# 将 <要腾空的节点> 替换为你的节点名称
kubectl uncordon <要腾空的节点>
4.2.3 - 管理内存、CPU 和 API 资源 4.2.3.1 - 为命名空间配置默认的内存请求和限制 为命名空间定义默认的内存资源限制,这样在该命名空间中每个新建的 Pod 都会被配置上内存资源限制。
本章介绍如何为命名空间 配置默认的内存请求和限制。
一个 Kubernetes 集群可被划分为多个命名空间。
如果你在具有默认内存限制
的命名空间内尝试创建一个 Pod,并且这个 Pod 中的容器没有声明自己的内存资源限制,
那么控制面 会为该容器设定默认的内存限制。
Kubernetes 还为某些情况指定了默认的内存请求,本章后面会进行介绍。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
你的集群中的每个节点必须至少有 2 GiB 的内存。
创建命名空间 创建一个命名空间,以便本练习中所建的资源与集群的其余资源相隔离。
kubectl create namespace default-mem-example
创建 LimitRange 和 Pod 以下为 LimitRange 的示例清单。
清单中声明了默认的内存请求和默认的内存限制。
apiVersion : v1
kind : LimitRange
metadata :
name : mem-limit-range
spec :
limits :
- default :
memory : 512Mi
defaultRequest :
memory : 256Mi
type : Container
在 default-mem-example 命名空间创建限制范围:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults.yaml --namespace= default-mem-example
现在如果你在 default-mem-example 命名空间中创建一个 Pod,
并且该 Pod 中所有容器都没有声明自己的内存请求和内存限制,
控制面
会将内存的默认请求值 256MiB 和默认限制值 512MiB 应用到 Pod 上。
以下为只包含一个容器的 Pod 的清单。该容器没有声明内存请求和限制。
apiVersion : v1
kind : Pod
metadata :
name : default-mem-demo
spec :
containers :
- name : default-mem-demo-ctr
image : nginx
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod.yaml --namespace= default-mem-example
查看 Pod 的详情:
kubectl get pod default-mem-demo --output= yaml --namespace= default-mem-example
输出内容显示该 Pod 的容器有 256 MiB 的内存请求和 512 MiB 的内存限制。
这些都是 LimitRange 设置的默认值。
containers:
- image: nginx
imagePullPolicy: Always
name: default-mem-demo-ctr
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
删除你的 Pod:
kubectl delete pod default-mem-demo --namespace= default-mem-example
声明容器的限制而不声明它的请求会怎么样? 以下为只包含一个容器的 Pod 的清单。该容器声明了内存限制,而没有声明内存请求。
apiVersion : v1
kind : Pod
metadata :
name : default-mem-demo-2
spec :
containers :
- name : default-mem-demo-2-ctr
image : nginx
resources :
limits :
memory : "1Gi"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-2.yaml --namespace= default-mem-example
查看 Pod 的详情:
kubectl get pod default-mem-demo-2 --output= yaml --namespace= default-mem-example
输出结果显示容器的内存请求被设置为它的内存限制相同的值。注意该容器没有被指定默认的内存请求值 256MiB。
resources:
limits:
memory: 1Gi
requests:
memory: 1Gi
声明容器的内存请求而不声明内存限制会怎么样? 以下为只包含一个容器的 Pod 的清单。该容器声明了内存请求,但没有内存限制:
apiVersion : v1
kind : Pod
metadata :
name : default-mem-demo-3
spec :
containers :
- name : default-mem-demo-3-ctr
image : nginx
resources :
requests :
memory : "128Mi"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-defaults-pod-3.yaml --namespace= default-mem-example
查看 Pod 声明:
kubectl get pod default-mem-demo-3 --output= yaml --namespace= default-mem-example
输出结果显示所创建的 Pod 中,容器的内存请求为 Pod 清单中声明的值。
然而同一容器的内存限制被设置为 512MiB,此值是该命名空间的默认内存限制值。
resources:
limits:
memory: 512Mi
requests:
memory: 128Mi
说明: LimitRange
不会 检查它应用的默认值的一致性。 这意味着 LimitRange
设置的 limit 的默认值可能小于客户端提交给
API 服务器的声明中为容器指定的 request 值。如果发生这种情况,最终会导致 Pod 无法调度。更多信息,
请参阅资源限制的 limit 和 request 。
设置默认内存限制和请求的动机 如果你的命名空间设置了内存 资源配额 ,
那么为内存限制设置一个默认值会很有帮助。
以下是内存资源配额对命名空间的施加的三条限制:
命名空间中运行的每个 Pod 中的容器都必须有内存限制。
(如果为 Pod 中的每个容器声明了内存限制,
Kubernetes 可以通过将其容器的内存限制相加推断出 Pod 级别的内存限制)。
内存限制用来在 Pod 被调度到的节点上执行资源预留。
预留给命名空间中所有 Pod 使用的内存总量不能超过规定的限制。
命名空间中所有 Pod 实际使用的内存总量也不能超过规定的限制。
当你添加 LimitRange 时:
如果该命名空间中的任何 Pod 的容器未指定内存限制,
控制面将默认内存限制应用于该容器,
这样 Pod 可以在受到内存 ResourceQuota 限制的命名空间中运行。
清理 删除你的命名空间:
kubectl delete namespace default-mem-example
接下来 集群管理员参考 应用开发者参考 4.2.3.2 - 为命名空间配置默认的 CPU 请求和限制 为命名空间定义默认的 CPU 资源限制,在该命名空间中每个新建的 Pod 都会被配置上 CPU 资源限制。
本章介绍如何为命名空间 配置默认的 CPU 请求和限制。
一个 Kubernetes 集群可被划分为多个命名空间。
如果你在具有默认 CPU限制
的命名空间内创建一个 Pod,并且这个 Pod 中任何容器都没有声明自己的 CPU 限制,
那么控制面 会为容器设定默认的 CPU 限制。
Kubernetes 在一些特定情况还可以设置默认的 CPU 请求,本文后续章节将会对其进行解释。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
如果你还不熟悉 Kubernetes 中 1.0 CPU 的含义,
请阅读 CPU 的含义 。
创建命名空间 创建一个命名空间,以便本练习中创建的资源和集群的其余部分相隔离。
kubectl create namespace default-cpu-example
创建 LimitRange 和 Pod 以下为 LimitRange 的示例清单。
清单中声明了默认 CPU 请求和默认 CPU 限制。
apiVersion : v1
kind : LimitRange
metadata :
name : cpu-limit-range
spec :
limits :
- default :
cpu : 1
defaultRequest :
cpu : 0.5
type : Container
在命名空间 default-cpu-example 中创建 LimitRange 对象:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults.yaml --namespace= default-cpu-example
现在如果你在 default-cpu-example 命名空间中创建一个 Pod,
并且该 Pod 中所有容器都没有声明自己的 CPU 请求和 CPU 限制,
控制面会将 CPU 的默认请求值 0.5 和默认限制值 1 应用到 Pod 上。
以下为只包含一个容器的 Pod 的清单。该容器没有声明 CPU 请求和限制。
apiVersion : v1
kind : Pod
metadata :
name : default-cpu-demo
spec :
containers :
- name : default-cpu-demo-ctr
image : nginx
创建 Pod。
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod.yaml --namespace= default-cpu-example
查看该 Pod 的声明:
kubectl get pod default-cpu-demo --output= yaml --namespace= default-cpu-example
输出显示该 Pod 的唯一的容器有 500m cpu
的 CPU 请求和 1 cpu
的 CPU 限制。
这些是 LimitRange 声明的默认值。
containers:
- image: nginx
imagePullPolicy: Always
name: default-cpu-demo-ctr
resources:
limits:
cpu: "1"
requests:
cpu: 500m
你只声明容器的限制,而不声明请求会怎么样? 以下为只包含一个容器的 Pod 的清单。该容器声明了 CPU 限制,而没有声明 CPU 请求。
apiVersion : v1
kind : Pod
metadata :
name : default-cpu-demo-2
spec :
containers :
- name : default-cpu-demo-2-ctr
image : nginx
resources :
limits :
cpu : "1"
创建 Pod
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-2.yaml --namespace= default-cpu-example
查看你所创建的 Pod 的规约 :
kubectl get pod default-cpu-demo-2 --output=yaml --namespace=default-cpu-example
输出显示该容器的 CPU 请求和 CPU 限制设置相同。注意该容器没有被指定默认的 CPU 请求值 0.5 cpu
:
resources:
limits:
cpu: "1"
requests:
cpu: "1"
你只声明容器的请求,而不声明它的限制会怎么样? 这里给出了包含一个容器的 Pod 的示例清单。该容器声明了 CPU 请求,而没有声明 CPU 限制。
apiVersion : v1
kind : Pod
metadata :
name : default-cpu-demo-3
spec :
containers :
- name : default-cpu-demo-3-ctr
image : nginx
resources :
requests :
cpu : "0.75"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-defaults-pod-3.yaml --namespace= default-cpu-example
查看你所创建的 Pod 的规约 :
kubectl get pod default-cpu-demo-3 --output=yaml --namespace=default-cpu-example
输出显示你所创建的 Pod 中,容器的 CPU 请求为 Pod 清单中声明的值。
然而同一容器的 CPU 限制被设置为 1 cpu
,此值是该命名空间的默认 CPU 限制值。
resources:
limits:
cpu: "1"
requests:
cpu: 750m
默认 CPU 限制和请求的动机 如果你的命名空间设置了 CPU 资源配额 ,
为 CPU 限制设置一个默认值会很有帮助。
以下是 CPU 资源配额对命名空间的施加的两条限制:
预留给命名空间中所有 Pod 使用的 CPU 总量不能超过规定的限制。
当你添加 LimitRange 时:
如果该命名空间中的任何 Pod 的容器未指定 CPU 限制,
控制面将默认 CPU 限制应用于该容器,
这样 Pod 可以在受到 CPU ResourceQuota 限制的命名空间中运行。
清理 删除你的命名空间:
kubectl delete namespace default-cpu-example
接下来 集群管理员参考 应用开发者参考 4.2.3.3 - 配置命名空间的最小和最大内存约束 为命名空间定义一个有效的内存资源限制范围,在该命名空间中每个新创建 Pod 的内存资源是在设置的范围内。
本页介绍如何设置在名字空间
中运行的容器所使用的内存的最小值和最大值。你可以在
LimitRange
对象中指定最小和最大内存值。如果 Pod 不满足 LimitRange 施加的约束,
则无法在名字空间中创建它。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
集群中的每个节点都必须至少有 1 GiB 的内存可供 Pod 使用。
创建命名空间 创建一个命名空间,以便在此练习中创建的资源与集群的其余资源隔离。
kubectl create namespace constraints-mem-example
创建 LimitRange 和 Pod 下面是 LimitRange 的示例清单:
apiVersion : v1
kind : LimitRange
metadata :
name : mem-min-max-demo-lr
spec :
limits :
- max :
memory : 1Gi
min :
memory : 500Mi
type : Container
创建 LimitRange:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints.yaml --namespace= constraints-mem-example
查看 LimitRange 的详情:
kubectl get limitrange mem-min-max-demo-lr --namespace= constraints-mem-example --output= yaml
输出显示预期的最小和最大内存约束。
但请注意,即使你没有在 LimitRange 的配置文件中指定默认值,默认值也会自动生成。
limits:
- default:
memory: 1Gi
defaultRequest:
memory: 1Gi
max:
memory: 1Gi
min:
memory: 500Mi
type: Container
现在,每当在 constraints-mem-example 命名空间中创建 Pod 时,Kubernetes 就会执行下面的步骤:
如果 Pod 中的任何容器未声明自己的内存请求和限制,控制面将为该容器设置默认的内存请求和限制。 确保该 Pod 中的每个容器的内存请求至少 500 MiB。 确保该 Pod 中每个容器内存请求不大于 1 GiB。 以下为包含一个容器的 Pod 清单。该容器声明了 600 MiB 的内存请求和 800 MiB 的内存限制,
这些满足了 LimitRange 施加的最小和最大内存约束。
apiVersion : v1
kind : Pod
metadata :
name : constraints-mem-demo
spec :
containers :
- name : constraints-mem-demo-ctr
image : nginx
resources :
limits :
memory : "800Mi"
requests :
memory : "600Mi"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod.yaml --namespace= constraints-mem-example
确认 Pod 正在运行,并且其容器处于健康状态:
kubectl get pod constraints-mem-demo --namespace= constraints-mem-example
查看 Pod 详情:
kubectl get pod constraints-mem-demo --output= yaml --namespace= constraints-mem-example
输出结果显示该 Pod 的容器的内存请求为 600 MiB,内存限制为 800 MiB。
这些满足这个命名空间中 LimitRange 设定的限制范围。
resources :
limits :
memory : 800Mi
requests :
memory : 600Mi
删除你创建的 Pod:
kubectl delete pod constraints-mem-demo --namespace= constraints-mem-example
尝试创建一个超过最大内存限制的 Pod 以下为包含一个容器的 Pod 的清单。这个容器声明了 800 MiB 的内存请求和 1.5 GiB 的内存限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-mem-demo-2
spec :
containers :
- name : constraints-mem-demo-2-ctr
image : nginx
resources :
limits :
memory : "1.5Gi"
requests :
memory : "800Mi"
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-2.yaml --namespace= constraints-mem-example
输出结果显示 Pod 没有创建成功,因为它定义了一个容器的内存请求超过了允许的值。
Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-2.yaml":
pods "constraints-mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1536Mi.
尝试创建一个不满足最小内存请求的 Pod 以下为只有一个容器的 Pod 的清单。这个容器声明了 100 MiB 的内存请求和 800 MiB 的内存限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-mem-demo-3
spec :
containers :
- name : constraints-mem-demo-3-ctr
image : nginx
resources :
limits :
memory : "800Mi"
requests :
memory : "100Mi"
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-3.yaml --namespace= constraints-mem-example
输出结果显示 Pod 没有创建成功,因为它定义了一个容器的内存请求小于强制要求的最小值:
Error from server (Forbidden): error when creating "examples/admin/resource/memory-constraints-pod-3.yaml":
pods "constraints-mem-demo-3" is forbidden: minimum memory usage per Container is 500Mi, but request is 100Mi.
创建一个没有声明内存请求和限制的 Pod 以下为只有一个容器的 Pod 清单。该容器没有声明内存请求,也没有声明内存限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-mem-demo-4
spec :
containers :
- name : constraints-mem-demo-4-ctr
image : nginx
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/memory-constraints-pod-4.yaml --namespace= constraints-mem-example
查看 Pod 详情:
kubectl get pod constraints-mem-demo-4 --namespace= constraints-mem-example --output= yaml
输出结果显示 Pod 的唯一容器内存请求为 1 GiB,内存限制为 1 GiB。容器怎样获得那些数值呢?
resources:
limits:
memory: 1Gi
requests:
memory: 1Gi
因为你的 Pod 没有为容器声明任何内存请求和限制,集群会从 LimitRange
获取默认的内存请求和限制 。
应用于容器。
这意味着 Pod 的定义会显示这些值。你可以通过 kubectl describe
查看:
# 查看输出结果中的 "Requests:" 的值
kubectl describe pod constraints-mem-demo-4 --namespace= constraints-mem-example
此时,你的 Pod 可能已经运行起来也可能没有运行起来。
回想一下我们本次任务的先决条件是你的每个节点都至少有 1 GiB 的内存。
如果你的每个节点都只有 1 GiB 的内存,那将没有一个节点拥有足够的可分配内存来满足 1 GiB 的内存请求。
删除你的 Pod:
kubectl delete pod constraints-mem-demo-4 --namespace= constraints-mem-example
强制执行内存最小和最大限制 LimitRange 为命名空间设定的最小和最大内存限制只有在 Pod 创建和更新时才会强制执行。
如果你更新 LimitRange,它不会影响此前创建的 Pod。
设置内存最小和最大限制的动因 作为集群管理员,你可能想规定 Pod 可以使用的内存总量限制。例如:
集群的每个节点有 2 GiB 内存。你不想接受任何请求超过 2 GiB 的 Pod,因为集群中没有节点可以满足。 集群由生产部门和开发部门共享。你希望允许产品部门的负载最多耗用 8 GiB 内存,
但是开发部门的负载最多可使用 512 MiB。
这时,你可以为产品部门和开发部门分别创建名字空间,并为各个名字空间设置内存约束。 清理 删除你的命名空间:
kubectl delete namespace constraints-mem-example
接下来 集群管理员参考 应用开发者参考 4.2.3.4 - 为命名空间配置 CPU 最小和最大约束 为命名空间定义一个有效的 CPU 资源限制范围,使得在该命名空间中所有新建 Pod 的 CPU 资源是在你所设置的范围内。
本页介绍如何为命名空间 中的容器和 Pod
设置其所使用的 CPU 资源的最小和最大值。你可以通过 LimitRange
对象声明 CPU 的最小和最大值.
如果 Pod 不能满足 LimitRange 的限制,就无法在该命名空间中被创建。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
集群中的每个节点都必须至少有 1.0 个 CPU 可供 Pod 使用。
请阅读 CPU 的含义
理解 "1 CPU" 在 Kubernetes 中的含义。
创建命名空间 创建一个命名空间,以便本练习中创建的资源和集群的其余资源相隔离。
kubectl create namespace constraints-cpu-example
创建 LimitRange 和 Pod 以下为 LimitRange 的示例清单:
apiVersion : v1
kind : LimitRange
metadata :
name : cpu-min-max-demo-lr
spec :
limits :
- max :
cpu : "800m"
min :
cpu : "200m"
type : Container
创建 LimitRange:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints.yaml --namespace= constraints-cpu-example
查看 LimitRange 详情:
kubectl get limitrange cpu-min-max-demo-lr --output= yaml --namespace= constraints-cpu-example
输出结果显示 CPU 的最小和最大限制符合预期。但需要注意的是,尽管你在 LimitRange
的配置文件中你没有声明默认值,默认值也会被自动创建。
limits :
- default :
cpu : 800m
defaultRequest :
cpu : 800m
max :
cpu : 800m
min :
cpu : 200m
type : Container
现在,每当你在 constraints-cpu-example 命名空间中创建 Pod 时,或者某些其他的
Kubernetes API 客户端创建了等价的 Pod 时,Kubernetes 就会执行下面的步骤:
如果 Pod 中的任何容器未声明自己的 CPU 请求和限制,控制面将为该容器设置默认的 CPU 请求和限制。
确保该 Pod 中的每个容器的 CPU 请求至少 200 millicpu。
确保该 Pod 中每个容器 CPU 请求不大于 800 millicpu。
说明: 当创建 LimitRange 对象时,你也可以声明大页面和 GPU 的限制。
当这些资源同时声明了 'default' 和 'defaultRequest' 参数时,两个参数值必须相同。
以下为某个仅包含一个容器的 Pod 的清单。
该容器声明了 CPU 请求 500 millicpu 和 CPU 限制 800 millicpu 。
这些参数满足了 LimitRange 对象为此名字空间规定的 CPU 最小和最大限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-cpu-demo
spec :
containers :
- name : constraints-cpu-demo-ctr
image : nginx
resources :
limits :
cpu : "800m"
requests :
cpu : "500m"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod.yaml --namespace= constraints-cpu-example
确认 Pod 正在运行,并且其容器处于健康状态:
kubectl get pod constraints-cpu-demo --namespace= constraints-cpu-example
查看 Pod 的详情:
kubectl get pod constraints-cpu-demo --output= yaml --namespace= constraints-cpu-example
输出结果显示该 Pod 的容器的 CPU 请求为 500 millicpu,CPU 限制为 800 millicpu。
这些参数满足 LimitRange 规定的限制范围。
resources :
limits :
cpu : 800m
requests :
cpu : 500m
删除 Pod kubectl delete pod constraints-cpu-demo --namespace= constraints-cpu-example
尝试创建一个超过最大 CPU 限制的 Pod 这里给出了包含一个容器的 Pod 的配置文件。容器声明了 500 millicpu 的 CPU
请求和 1.5 CPU 的 CPU 限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-cpu-demo-2
spec :
containers :
- name : constraints-cpu-demo-2-ctr
image : nginx
resources :
limits :
cpu : "1.5"
requests :
cpu : "500m"
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-2.yaml --namespace= constraints-cpu-example
输出结果表明 Pod 没有创建成功,因为其中定义了一个无法被接受的容器。
该容器之所以无法被接受是因为其中设定了过高的 CPU 限制值:
Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-2.yaml":
pods "constraints-cpu-demo-2" is forbidden: maximum cpu usage per Container is 800m, but limit is 1500m.
尝试创建一个不满足最小 CPU 请求的 Pod 以下为某个只有一个容器的 Pod 的清单。该容器声明了 CPU 请求 100 millicpu 和 CPU 限制 800 millicpu。
apiVersion : v1
kind : Pod
metadata :
name : constraints-cpu-demo-3
spec :
containers :
- name : constraints-cpu-demo-3-ctr
image : nginx
resources :
limits :
cpu : "800m"
requests :
cpu : "100m"
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-3.yaml --namespace= constraints-cpu-example
输出结果显示 Pod 没有创建成功,因为其中定义了一个无法被接受的容器。
该容器无法被接受的原因是其中所设置的 CPU 请求小于最小值的限制:
Error from server (Forbidden): error when creating "examples/admin/resource/cpu-constraints-pod-3.yaml":
pods "constraints-cpu-demo-4" is forbidden: minimum cpu usage per Container is 200m, but request is 100m.
创建一个没有声明 CPU 请求和 CPU 限制的 Pod 以下为一个只有一个容器的 Pod 的清单。该容器没有声明 CPU 请求,也没有声明 CPU 限制。
apiVersion : v1
kind : Pod
metadata :
name : constraints-cpu-demo-4
spec :
containers :
- name : constraints-cpu-demo-4-ctr
image : vish/stress
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/cpu-constraints-pod-4.yaml --namespace= constraints-cpu-example
查看 Pod 的详情:
kubectl get pod constraints-cpu-demo-4 --namespace=constraints-cpu-example --output=yaml
输出结果显示 Pod 的唯一容器的 CPU 请求为 800 millicpu,CPU 限制为 800 millicpu。
容器是怎样获得这些数值的呢?
resources :
limits :
cpu : 800m
requests :
cpu : 800m
因为这一容器没有声明自己的 CPU 请求和限制,
控制面会根据命名空间中配置 LimitRange
设置默认的 CPU 请求和限制 。
此时,你的 Pod 可能已经运行起来也可能没有运行起来。
回想一下我们本次任务的先决条件是你的每个节点都至少有 1 CPU。
如果你的每个节点都只有 1 CPU,那将没有一个节点拥有足够的可分配 CPU 来满足 800 millicpu 的请求。
如果你在用的节点恰好有 2 CPU,那么有可能有足够的 CPU 来满足 800 millicpu 的请求。
删除你的 Pod:
kubectl delete pod constraints-cpu-demo-4 --namespace=constraints-cpu-example
CPU 最小和最大限制的强制执行 只有当 Pod 创建或者更新时,LimitRange 为命名空间规定的 CPU 最小和最大限制才会被强制执行。
如果你对 LimitRange 进行修改,那不会影响此前创建的 Pod。
最小和最大 CPU 限制范围的动机 作为集群管理员,你可能想设定 Pod 可以使用的 CPU 资源限制。例如:
集群中的每个节点有两个 CPU。你不想接受任何请求超过 2 个 CPU 的 Pod,
因为集群中没有节点可以支持这种请求。 你的生产和开发部门共享一个集群。你想允许生产工作负载消耗 3 个 CPU,
而开发部门工作负载的消耗限制为 1 个 CPU。
你可以为生产和开发创建不同的命名空间,并且为每个命名空间都应用 CPU 限制。 清理 删除你的命名空间:
kubectl delete namespace constraints-cpu-example
接下来 集群管理员参考: 应用开发者参考: 4.2.3.5 - 为命名空间配置内存和 CPU 配额 为命名空间定义总的 CPU 和内存资源限制。
本文介绍如何为命名空间 下运行的所有
Pod 设置总的内存和 CPU 配额。你可以通过使用 ResourceQuota
对象设置配额.
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
集群中每个节点至少有 1 GiB 的内存。
创建命名空间 创建一个命名空间,以便本练习中创建的资源和集群的其余部分相隔离。
kubectl create namespace quota-mem-cpu-example
创建 ResourceQuota 下面是 ResourceQuota 的示例清单:
apiVersion : v1
kind : ResourceQuota
metadata :
name : mem-cpu-demo
spec :
hard :
requests.cpu : "1"
requests.memory : 1Gi
limits.cpu : "2"
limits.memory : 2Gi
创建 ResourceQuota:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu.yaml --namespace= quota-mem-cpu-example
查看 ResourceQuota 详情:
kubectl get resourcequota mem-cpu-demo --namespace= quota-mem-cpu-example --output= yaml
ResourceQuota 在 quota-mem-cpu-example 命名空间中设置了如下要求:
在该命名空间中的每个 Pod 的所有容器都必须要有内存请求和限制,以及 CPU 请求和限制。 在该命名空间中所有 Pod 的内存请求总和不能超过 1 GiB。 在该命名空间中所有 Pod 的内存限制总和不能超过 2 GiB。 在该命名空间中所有 Pod 的 CPU 请求总和不能超过 1 cpu。 在该命名空间中所有 Pod 的 CPU 限制总和不能超过 2 cpu。 请阅读 CPU 的含义
理解 "1 CPU" 在 Kubernetes 中的含义。
创建 Pod 以下是 Pod 的示例清单:
apiVersion : v1
kind : Pod
metadata :
name : quota-mem-cpu-demo
spec :
containers :
- name : quota-mem-cpu-demo-ctr
image : nginx
resources :
limits :
memory : "800Mi"
cpu : "800m"
requests :
memory : "600Mi"
cpu : "400m"
创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod.yaml --namespace= quota-mem-cpu-example
确认 Pod 正在运行,并且其容器处于健康状态:
kubectl get pod quota-mem-cpu-demo --namespace= quota-mem-cpu-example
再查看 ResourceQuota 的详情:
kubectl get resourcequota mem-cpu-demo --namespace= quota-mem-cpu-example --output= yaml
输出结果显示了配额以及有多少配额已经被使用。你可以看到 Pod 的内存和 CPU 请求值及限制值没有超过配额。
status:
hard:
limits.cpu: "2"
limits.memory: 2Gi
requests.cpu: "1"
requests.memory: 1Gi
used:
limits.cpu: 800m
limits.memory: 800Mi
requests.cpu: 400m
requests.memory: 600Mi
如果有 jq
工具的话,你可以通过(使用 JSONPath )
直接查询 used
字段的值,并且输出整齐的 JSON 格式。
kubectl get resourcequota mem-cpu-demo --namespace= quota-mem-cpu-example -o jsonpath = '{ .status.used }' | jq .
尝试创建第二个 Pod 以下为第二个 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : quota-mem-cpu-demo-2
spec :
containers :
- name : quota-mem-cpu-demo-2-ctr
image : redis
resources :
limits :
memory : "1Gi"
cpu : "800m"
requests :
memory : "700Mi"
cpu : "400m"
在清单中,你可以看到 Pod 的内存请求为 700 MiB。
请注意新的内存请求与已经使用的内存请求之和超过了内存请求的配额:
600 MiB + 700 MiB > 1 GiB。
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-mem-cpu-pod-2.yaml --namespace= quota-mem-cpu-example
第二个 Pod 不能被创建成功。输出结果显示创建第二个 Pod 会导致内存请求总量超过内存请求配额。
Error from server (Forbidden): error when creating "examples/admin/resource/quota-mem-cpu-pod-2.yaml":
pods "quota-mem-cpu-demo-2" is forbidden: exceeded quota: mem-cpu-demo,
requested: requests.memory=700Mi,used: requests.memory=600Mi, limited: requests.memory=1Gi
讨论 如你在本练习中所见,你可以用 ResourceQuota 限制命名空间中所有 Pod 的内存请求总量。
同样你也可以限制内存限制总量、CPU 请求总量、CPU 限制总量。
除了可以管理命名空间资源使用的总和,如果你想限制单个 Pod,或者限制这些 Pod 中的容器资源,
可以使用 LimitRange
实现这类的功能。
清理 删除你的命名空间:
kubectl delete namespace quota-mem-cpu-example
接下来 集群管理员参考 应用开发者参考 4.2.3.6 - 配置命名空间下 Pod 配额 限制在命名空间中创建的 Pod 数量。
本文主要介绍如何在命名空间 中设置可运行 Pod 总数的配额。
你可以通过使用
ResourceQuota
对象来配置配额。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
在你的集群里你必须要有创建命名空间的权限。
创建一个命名空间 首先创建一个命名空间,这样可以将本次操作中创建的资源与集群其他资源隔离开来。
kubectl create namespace quota-pod-example
创建 ResourceQuota 下面是 ResourceQuota 的示例清单:
apiVersion : v1
kind : ResourceQuota
metadata :
name : pod-demo
spec :
hard :
pods : "2"
创建 ResourceQuota:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod.yaml --namespace= quota-pod-example
查看资源配额的详细信息:
kubectl get resourcequota pod-demo --namespace= quota-pod-example --output= yaml
从输出的信息我们可以看到,该命名空间下 Pod 的配额是 2 个,目前创建的 Pod 数为 0,
配额使用率为 0。
spec :
hard :
pods : "2"
status :
hard :
pods : "2"
used :
pods : "0"
下面是一个 Deployment 的示例清单:
apiVersion : apps/v1
kind : Deployment
metadata :
name : pod-quota-demo
spec :
selector :
matchLabels :
purpose : quota-demo
replicas : 3
template :
metadata :
labels :
purpose : quota-demo
spec :
containers :
- name : pod-quota-demo
image : nginx
在清单中,replicas: 3
告诉 Kubernetes 尝试创建三个新的 Pod,
且运行相同的应用。
创建这个 Deployment:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-pod-deployment.yaml --namespace= quota-pod-example
查看 Deployment 的详细信息:
kubectl get deployment pod-quota-demo --namespace= quota-pod-example --output= yaml
从输出的信息显示,即使 Deployment 指定了三个副本,
也只有两个 Pod 被创建,原因是之前已经定义了配额:
spec :
...
replicas : 3
...
status :
availableReplicas : 2
...
lastUpdateTime : 2021-04-02T20:57:05Z
message: 'unable to create pods : pods "pod-quota-demo-1650323038-" is forbidden:
exceeded quota: pod-demo, requested: pods=1, used: pods=2, limited : pods=2'
资源的选择 在此任务中,你定义了一个限制 Pod 总数的 ResourceQuota,
你也可以限制其他类型对象的总数。
例如,你可以限制在一个命名空间中可以创建的 CronJobs 的数量。
清理 删除你的命名空间:
kubectl delete namespace quota-pod-example
接下来 集群管理人员参考 应用开发人员参考 4.2.4 - 手动生成证书 在使用客户端证书认证的场景下,你可以通过 easyrsa
、openssl
或 cfssl
等工具以手工方式生成证书。
easyrsa easyrsa 支持以手工方式为你的集群生成证书。
下载、解压、初始化打过补丁的 easyrsa3
。
curl -LO https://storage.googleapis.com/kubernetes-release/easy-rsa/easy-rsa.tar.gz
tar xzf easy-rsa.tar.gz
cd easy-rsa-master/easyrsa3
./easyrsa init-pki
生成新的证书颁发机构(CA)。参数 --batch
用于设置自动模式;
参数 --req-cn
用于设置新的根证书的通用名称(CN)。
./easyrsa --batch "--req-cn= ${ MASTER_IP } @`date +%s`" build-ca nopass
生成服务器证书和秘钥。
参数 --subject-alt-name
设置 API 服务器的 IP 和 DNS 名称。
MASTER_CLUSTER_IP
用于 API 服务器和控制器管理器,通常取 CIDR 的第一个 IP,
由 --service-cluster-ip-range
的参数提供。
参数 --days
用于设置证书的过期时间。
下面的示例假定你的默认 DNS 域名为 cluster.local
。
./easyrsa --subject-alt-name= "IP: ${ MASTER_IP } ," \
"IP: ${ MASTER_CLUSTER_IP } ," \
"DNS:kubernetes," \
"DNS:kubernetes.default," \
"DNS:kubernetes.default.svc," \
"DNS:kubernetes.default.svc.cluster," \
"DNS:kubernetes.default.svc.cluster.local" \
--days= 10000 \
build-server-full server nopass
拷贝文件 pki/ca.crt
、pki/issued/server.crt
和 pki/private/server.key
到你的目录中。 在 API 服务器的启动参数中添加以下参数:
--client-ca-file= /yourdirectory/ca.crt
--tls-cert-file= /yourdirectory/server.crt
--tls-private-key-file= /yourdirectory/server.key
openssl openssl 支持以手工方式为你的集群生成证书。
生成一个 2048 位的 ca.key 文件
openssl genrsa -out ca.key 2048
在 ca.key 文件的基础上,生成 ca.crt 文件(用参数 -days
设置证书有效期)
openssl req -x509 -new -nodes -key ca.key -subj "/CN= ${ MASTER_IP } " -days 10000 -out ca.crt
生成一个 2048 位的 server.key 文件:
openssl genrsa -out server.key 2048
创建一个用于生成证书签名请求(CSR)的配置文件。
保存文件(例如:csr.conf
)前,记得用真实值替换掉尖括号中的值(例如:<MASTER_IP>
)。
注意:MASTER_CLUSTER_IP
就像前一小节所述,它的值是 API 服务器的服务集群 IP。
下面的例子假定你的默认 DNS 域名为 cluster.local
。
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = <country>
ST = <state>
L = <city>
O = <organization>
OU = <organization unit>
CN = <MASTER_IP>
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = kubernetes
DNS.2 = kubernetes.default
DNS.3 = kubernetes.default.svc
DNS.4 = kubernetes.default.svc.cluster
DNS.5 = kubernetes.default.svc.cluster.local
IP.1 = <MASTER_IP>
IP.2 = <MASTER_CLUSTER_IP>
[ v3_ext ]
authorityKeyIdentifier = keyid,issuer:always
basicConstraints = CA:FALSE
keyUsage = keyEncipherment,dataEncipherment
extendedKeyUsage = serverAuth,clientAuth
subjectAltName = @alt_names
基于上面的配置文件生成证书签名请求:
openssl req -new -key server.key -out server.csr -config csr.conf
基于 ca.key、ca.crt 和 server.csr 等三个文件生成服务端证书:
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 10000 \
-extensions v3_ext -extfile csr.conf -sha256
查看证书签名请求:
openssl req -noout -text -in ./server.csr
查看证书:
openssl x509 -noout -text -in ./server.crt
最后,为 API 服务器添加相同的启动参数。
cfssl cfssl 是另一个用于生成证书的工具。
下载、解压并准备如下所示的命令行工具。
注意:你可能需要根据所用的硬件体系架构和 cfssl 版本调整示例命令。
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_linux_amd64 -o cfssl
chmod +x cfssl
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_linux_amd64 -o cfssljson
chmod +x cfssljson
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl-certinfo_1.5.0_linux_amd64 -o cfssl-certinfo
chmod +x cfssl-certinfo
创建一个目录,用它保存所生成的构件和初始化 cfssl:
mkdir cert
cd cert
../cfssl print-defaults config > config.json
../cfssl print-defaults csr > csr.json
创建一个 JSON 配置文件来生成 CA 文件,例如:ca-config.json
:
{
"signing" : {
"default" : {
"expiry" : "8760h"
},
"profiles" : {
"kubernetes" : {
"usages" : [
"signing" ,
"key encipherment" ,
"server auth" ,
"client auth"
],
"expiry" : "8760h"
}
}
}
}
创建一个 JSON 配置文件,用于 CA 证书签名请求(CSR),例如:ca-csr.json
。
确认用你需要的值替换掉尖括号中的值。
{
"CN" : "kubernetes" ,
"key" : {
"algo" : "rsa" ,
"size" : 2048
},
"names" :[{
"C" : "<country>" ,
"ST" : "<state>" ,
"L" : "<city>" ,
"O" : "<organization>" ,
"OU" : "<organization unit>"
}]
}
生成 CA 秘钥文件(ca-key.pem
)和证书文件(ca.pem
):
../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca
创建一个 JSON 配置文件,用来为 API 服务器生成秘钥和证书,例如:server-csr.json
。
确认用你需要的值替换掉尖括号中的值。MASTER_CLUSTER_IP
是为 API 服务器 指定的服务集群 IP,就像前面小节描述的那样。
以下示例假定你的默认 DNS 域名为cluster.local
。
{
"CN" : "kubernetes" ,
"hosts" : [
"127.0.0.1" ,
"<MASTER_IP>" ,
"<MASTER_CLUSTER_IP>" ,
"kubernetes" ,
"kubernetes.default" ,
"kubernetes.default.svc" ,
"kubernetes.default.svc.cluster" ,
"kubernetes.default.svc.cluster.local"
],
"key" : {
"algo" : "rsa" ,
"size" : 2048
},
"names" : [{
"C" : "<country>" ,
"ST" : "<state>" ,
"L" : "<city>" ,
"O" : "<organization>" ,
"OU" : "<organization unit>"
}]
}
为 API 服务器生成秘钥和证书,默认会分别存储为server-key.pem
和 server.pem
两个文件。
../cfssl gencert -ca= ca.pem -ca-key= ca-key.pem \
--config= ca-config.json -profile= kubernetes \
server-csr.json | ../cfssljson -bare server
分发自签名的 CA 证书 客户端节点可能不认可自签名 CA 证书的有效性。
对于非生产环境,或者运行在公司防火墙后的环境,你可以分发自签名的 CA 证书到所有客户节点,并刷新本地列表以使证书生效。
在每一个客户节点,执行以下操作:
sudo cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt
sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....
done.
证书 API 你可以通过 certificates.k8s.io
API 提供 x509 证书,用来做身份验证,
如管理集群中的 TLS 认证 文档所述。
4.2.5 - 安装网络策略驱动 4.2.5.1 - 使用 Antrea 提供 NetworkPolicy 本页展示了如何在 kubernetes 中安装和使用 Antrea CNI 插件。
要了解 Antrea 项目的背景,请阅读 Antrea 介绍 。
准备开始 你需要拥有一个 kuernetes 集群。
遵循 kubeadm 入门指南 自行创建一个。
使用 kubeadm 部署 Antrea 遵循入门 指南
为 kubeadm 部署 Antrea 。
接下来 一旦你的集群已经运行,你可以遵循
声明网络策略
来尝试 Kubernetes NetworkPolicy。
4.2.5.2 - 使用 Calico 提供 NetworkPolicy 本页展示了几种在 Kubernetes 上快速创建 Calico 集群的方法。
准备开始 确定你想部署一个云版本 还是本地版本 的集群。
在 Google Kubernetes Engine (GKE) 上创建一个 Calico 集群 先决条件 : gcloud
启动一个带有 Calico 的 GKE 集群,需要加上参数 --enable-network-policy
。
语法
gcloud container clusters create [ CLUSTER_NAME] --enable-network-policy
示例
gcloud container clusters create my-calico-cluster --enable-network-policy
使用如下命令验证部署是否正确。
kubectl get pods --namespace= kube-system
Calico 的 pods 名以 `calico` 打头,检查确认每个 pods 状态为 `Running`。
使用 kubeadm 创建一个本地 Calico 集群 使用 kubeadm 在 15 分钟内得到一个本地单主机 Calico 集群,请参考
Calico 快速入门 。
接下来 集群运行后,你可以按照声明网络策略
去尝试使用 Kubernetes NetworkPolicy。
4.2.5.3 - 使用 Cilium 提供 NetworkPolicy 本页展示如何使用 Cilium 提供 NetworkPolicy。
关于 Cilium 的背景知识,请阅读 Cilium 介绍 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
在 Minikube 上部署 Cilium 用于基本测试 为了轻松熟悉 Cilium 你可以根据
Cilium Kubernetes 入门指南
在 minikube 中执行一个 cilium 的基本 DaemonSet 安装。
要启动 minikube,需要的最低版本为 1.5.2,使用下面的参数运行:
minikube version: v1.5.2
minikube start --network-plugin= cni --memory= 4096
对于 minikube 你可以使用 Cilium 的 CLI 工具安装它。
Cilium 将自动检测集群配置并为成功的集群部署选择合适的组件。
curl -LO https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz
cilium install
🔮 Auto-detected Kubernetes kind: minikube
✨ Running "minikube" validation checks
✅ Detected minikube version "1.20.0"
ℹ️ Cilium version not set, using default version "v1.10.0"
🔮 Auto-detected cluster name: minikube
🔮 Auto-detected IPAM mode: cluster-pool
🔮 Auto-detected datapath mode: tunnel
🔑 Generating CA...
2021/05/27 02:54:44 [INFO] generate received request
2021/05/27 02:54:44 [INFO] received CSR
2021/05/27 02:54:44 [INFO] generating key: ecdsa-256
2021/05/27 02:54:44 [INFO] encoded CSR
2021/05/27 02:54:44 [INFO] signed certificate with serial number 48713764918856674401136471229482703021230538642
🔑 Generating certificates for Hubble...
2021/05/27 02:54:44 [INFO] generate received request
2021/05/27 02:54:44 [INFO] received CSR
2021/05/27 02:54:44 [INFO] generating key: ecdsa-256
2021/05/27 02:54:44 [INFO] encoded CSR
2021/05/27 02:54:44 [INFO] signed certificate with serial number 3514109734025784310086389188421560613333279574
🚀 Creating Service accounts...
🚀 Creating Cluster roles...
🚀 Creating ConfigMap...
🚀 Creating Agent DaemonSet...
🚀 Creating Operator Deployment...
⌛ Waiting for Cilium to be installed...
入门指南其余的部分用一个示例应用说明了如何强制执行 L3/L4(即 IP 地址+端口)的安全策略
以及L7 (如 HTTP)的安全策略。
部署 Cilium 用于生产用途 关于部署 Cilium 用于生产的详细说明,请见
Cilium Kubernetes 安装指南
此文档包括详细的需求、说明和生产用途 DaemonSet 文件示例。
了解 Cilium 组件 部署使用 Cilium 的集群会添加 Pods 到 kube-system
命名空间。要查看 Pod 列表,运行:
kubectl get pods --namespace= kube-system -l k8s-app= cilium
你将看到像这样的 Pods 列表:
NAME READY STATUS RESTARTS AGE
cilium-kkdhz 1/1 Running 0 3m23s
...
你的集群中的每个节点上都会运行一个 cilium
Pod,通过使用 Linux BPF
针对该节点上的 Pod 的入站、出站流量实施网络策略控制。
接下来 集群运行后,你可以按照
声明网络策略
试用基于 Cilium 的 Kubernetes NetworkPolicy。
玩得开心,如果你有任何疑问,请到 Cilium Slack 频道
联系我们。
4.2.5.4 - 使用 kube-router 提供 NetworkPolicy 本页展示如何使用 Kube-router 提供 NetworkPolicy。
准备开始 你需要拥有一个运行中的 Kubernetes 集群。如果你还没有集群,可以使用任意的集群
安装程序如 Kops、Bootkube、Kubeadm 等创建一个。
安装 kube-router 插件 kube-router 插件自带一个网络策略控制器,监视来自于 Kubernetes API 服务器的
NetworkPolicy 和 Pod 的变化,根据策略指示配置 iptables 规则和 ipsets 来允许或阻止流量。
请根据 通过集群安装程序尝试 kube-router 指南安装 kube-router 插件。
接下来 在你安装了 kube-router 插件后,可以参考
声明网络策略
去尝试使用 Kubernetes NetworkPolicy。
4.2.5.5 - 使用 Romana 提供 NetworkPolicy 本页展示如何使用 Romana 作为 NetworkPolicy。
准备开始 完成 kubeadm 入门指南 中的 1、2、3 步。
使用 kubeadm 安装 Romana 按照容器化安装指南 ,
使用 kubeadm 安装。
应用网络策略 使用以下的一种方式应用网络策略:
接下来 Romana 安装完成后,你可以按照
声明网络策略
去尝试使用 Kubernetes NetworkPolicy。
4.2.5.6 - 使用 Weave Net 提供 NetworkPolicy 本页展示如何使用 Weave Net 提供 NetworkPolicy。
准备开始 你需要拥有一个 Kubernetes 集群。按照
kubeadm 入门指南
来启动一个。
安装 Weave Net 插件 按照通过插件集成 Kubernetes
指南执行安装。
Kubernetes 的 Weave Net 插件带有
网络策略控制器 ,
可自动监控 Kubernetes 所有名字空间的 NetworkPolicy 注释,
配置 iptables
规则以允许或阻止策略指示的流量。
测试安装 验证 weave 是否有效。
输入以下命令:
kubectl get pods -n kube-system -o wide
输出类似这样:
NAME READY STATUS RESTARTS AGE IP NODE
weave-net-1t1qg 2/2 Running 0 9d 192.168.2.10 worknode3
weave-net-231d7 2/2 Running 1 7d 10.2.0.17 worknodegpu
weave-net-7nmwt 2/2 Running 3 9d 192.168.2.131 masternode
weave-net-pmw8w 2/2 Running 0 9d 192.168.2.216 worknode2
每个 Node 都有一个 weave Pod,所有 Pod 都是 Running
和 2/2 READY
。
(2/2
表示每个 Pod 都有 weave
和 weave-npc
。)
接下来 安装 Weave Net 插件后,你可以参考
声明网络策略
来试用 Kubernetes NetworkPolicy。
如果你有任何疑问,请通过
Slack 上的 #weave-community 频道或者 Weave 用户组
联系我们。
4.2.6 - IP Masquerade Agent 用户指南 此页面展示如何配置和启用 ip-masq-agent
。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
IP Masquerade Agent 用户指南 ip-masq-agent
配置 iptables 规则以隐藏位于集群节点 IP 地址后面的 Pod 的 IP 地址。
这通常在将流量发送到集群的 Pod
CIDR
范围之外的目的地时使用。
关键术语 NAT (网络地址转译)
是一种通过修改 IP 地址头中的源和/或目标地址信息将一个 IP 地址重新映射
到另一个 IP 地址的方法。通常由执行 IP 路由的设备执行。伪装
NAT 的一种形式,通常用于执行多对一地址转换,其中多个源 IP 地址被隐藏在
单个地址后面,该地址通常是执行 IP 路由的设备。在 Kubernetes 中,
这是节点的 IP 地址。CIDR (无类别域间路由)
基于可变长度子网掩码,允许指定任意长度的前缀。
CIDR 引入了一种新的 IP 地址表示方法,现在通常称为CIDR表示法 ,
其中地址或路由前缀后添加一个后缀,用来表示前缀的位数,例如 192.168.2.0/24。本地链路
本地链路是仅对网段或主机所连接的广播域内的通信有效的网络地址。
IPv4 的本地链路地址在 CIDR 表示法的地址块 169.254.0.0/16 中定义。ip-masq-agent 配置 iptables 规则,以便在将流量发送到集群节点的 IP 和集群 IP 范围之外的目标时
处理伪装节点或 Pod 的 IP 地址。这本质上隐藏了集群节点 IP 地址后面的 Pod IP 地址。
在某些环境中,去往“外部”地址的流量必须从已知的机器地址发出。
例如,在 Google Cloud 中,任何到互联网的流量都必须来自 VM 的 IP。
使用容器时,如 Google Kubernetes Engine,从 Pod IP 发出的流量将被拒绝出站。
为了避免这种情况,我们必须将 Pod IP 隐藏在 VM 自己的 IP 地址后面 - 通常称为“伪装”。
默认情况下,代理配置为将
RFC 1918
指定的三个私有 IP 范围视为非伪装
CIDR 。
这些范围是 10.0.0.0/8,172.16.0.0/12 和 192.168.0.0/16。
默认情况下,代理还将链路本地地址(169.254.0.0/16)视为非伪装 CIDR。
代理程序配置为每隔 60 秒从 /etc/config/ip-masq-agent 重新加载其配置,
这也是可修改的。
代理配置文件必须使用 YAML 或 JSON 语法编写,并且可能包含三个可选值:
nonMasqueradeCIDRs
:
CIDR
表示法中的字符串列表,用于指定不需伪装的地址范围。masqLinkLocal
:布尔值 (true/false),表示是否为本地链路前缀 169.254.0.0/16 的流量提供伪装。
默认为 false。resyncInterval
:代理从磁盘重新加载配置的重试时间间隔。
例如 '30s',其中 's' 是秒,'ms' 是毫秒。10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16 范围内的流量不会被伪装。
任何其他流量(假设是互联网)将被伪装。
Pod 访问本地目的地的例子,可以是其节点的 IP 地址、另一节点的地址或集群的 IP 地址范围内的一个 IP 地址。
默认情况下,任何其他流量都将伪装。以下条目展示了 ip-masq-agent 的默认使用的规则:
iptables -t nat -L IP-MASQ-AGENT
RETURN all -- anywhere 169.254.0.0/16 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN all -- anywhere 10.0.0.0/8 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN all -- anywhere 172.16.0.0/12 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN all -- anywhere 192.168.0.0/16 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
MASQUERADE all -- anywhere anywhere /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL
默认情况下,在 GCE/Google Kubernetes Engine 中,如果启用了网络策略,
或者你使用的集群 CIDR 不在 10.0.0.0/8 范围内,
则 ip-masq-agent
将在你的集群中运行。
如果你在其他环境中运行,可以将 ip-masq-agent
DaemonSet 添加到你的集群中。
创建 ip-masq-agent 通过运行以下 kubectl 指令创建 ip-masq-agent:
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/ip-masq-agent/master/ip-masq-agent.yaml
你必须同时将适当的节点标签应用于集群中希望代理运行的任何节点。
kubectl label nodes my-node beta.kubernetes.io/masq-agent-ds-ready= true
更多信息可以通过 ip-masq-agent 文档 这里 找到。
在大多数情况下,默认的规则集应该足够;但是,如果你的集群不是这种情况,则可以创建并应用
ConfigMap
来自定义受影响的 IP 范围。
例如,要允许 ip-masq-agent 仅作用于 10.0.0.0/8,你可以在一个名为 “config” 的文件中创建以下
ConfigMap 。
说明: 重要的是,该文件之所以被称为 config,因为默认情况下,该文件将被用作
ip-masq-agent
查找的主键:
nonMasqueradeCIDRs :
- 10.0.0.0 /8
resyncInterval : 60s
运行以下命令将 ConfigMap 添加到你的集群:
kubectl create configmap ip-masq-agent --from-file= config --namespace= kube-system
这将更新位于 /etc/config/ip-masq-agent
的一个文件,该文件以 resyncInterval
为周期定期检查并应用于集群节点。
重新同步间隔到期后,你应该看到你的更改在 iptables 规则中体现:
iptables -t nat -L IP-MASQ-AGENT
Chain IP-MASQ-AGENT (1 references)
target prot opt source destination
RETURN all -- anywhere 169.254.0.0/16 /* ip-masq-agent: cluster-local traffic should not be subject to MASQUERADE */ ADDRTYPE match dst-type !LOCAL
RETURN all -- anywhere 10.0.0.0/8 /* ip-masq-agent: cluster-local
MASQUERADE all -- anywhere anywhere /* ip-masq-agent: outbound traffic should be subject to MASQUERADE (this match must come after cluster-local CIDR matches) */ ADDRTYPE match dst-type !LOCAL
默认情况下,本地链路范围 (169.254.0.0/16) 也由 ip-masq agent 处理,
该代理设置适当的 iptables 规则。 要使 ip-masq-agent 忽略本地链路,
可以在 ConfigMap 中将 masqLinkLocal
设置为 true。
nonMasqueradeCIDRs :
- 10.0.0.0 /8
resyncInterval : 60s
masqLinkLocal : true
4.2.7 - Kubernetes 云管理控制器 特性状态: Kubernetes v1.11 [beta]
由于云驱动的开发和发布的步调与 Kubernetes 项目不同,将服务提供商专用代码抽象到
cloud-controller-manager
二进制中有助于云服务厂商在 Kubernetes 核心代码之外独立进行开发。
cloud-controller-manager
可以被链接到任何满足
cloudprovider.Interface
约束的云服务提供商。为了兼容旧版本,Kubernetes 核心项目中提供的
cloud-controller-manager
使用和 kube-controller-manager
相同的云服务类库。
已经在 Kubernetes 核心项目中支持的云服务提供商预计将通过使用 in-tree 的 cloud-controller-manager
过渡为非 Kubernetes 核心代码。
管理 需求 每个云服务都有一套各自的需求用于系统平台的集成,这不应与运行
kube-controller-manager
的需求有太大差异。作为经验法则,你需要:
云服务认证/授权:你的云服务可能需要使用令牌或者 IAM 规则以允许对其 API 的访问 kubernetes 认证/授权:cloud-controller-manager 可能需要 RBAC 规则以访问 kubernetes apiserver 高可用:类似于 kube-controller-manager,你可能希望通过主节点选举(默认开启)配置一个高可用的云管理控制器。 运行云管理控制器 你需要对集群配置做适当的修改以成功地运行云管理控制器:
一定不要为 kube-apiserver
和 kube-controller-manager
指定 --cloud-provider
标志。
这将保证它们不会运行任何云服务专用循环逻辑,这将会由云管理控制器运行。未来这个标记将被废弃并去除。 kubelet
必须使用 --cloud-provider=external
运行。
这是为了保证让 kubelet 知道在执行任何任务前,它必须被云管理控制器初始化。请记住,设置集群使用云管理控制器将用多种方式更改集群行为:
指定了 --cloud-provider=external
的 kubelet 将被添加一个 node.cloudprovider.kubernetes.io/uninitialized
的污点,导致其在初始化过程中不可调度(NoSchedule
)。
这将标记该节点在能够正常调度前,需要外部的控制器进行二次初始化。
请注意,如果云管理控制器不可用,集群中的新节点会一直处于不可调度的状态。
这个污点很重要,因为调度器可能需要关于节点的云服务特定的信息,比如他们的区域或类型
(高端 CPU、GPU 支持、内存较大、临时实例等)。 集群中节点的云服务信息将不再能够从本地元数据中获取,取而代之的是所有获取节点信息的
API 调用都将通过云管理控制器。这意味着你可以通过限制到 kubelet 云服务 API 的访问来提升安全性。
在更大的集群中你可能需要考虑云管理控制器是否会遇到速率限制,
因为它现在负责集群中几乎所有到云服务的 API 调用。 云管理控制器可以实现:
节点控制器 - 负责使用云服务 API 更新 kubernetes 节点并删除在云服务上已经删除的 kubernetes 节点。 服务控制器 - 负责在云服务上为类型为 LoadBalancer 的 service 提供负载均衡器。 路由控制器 - 负责在云服务上配置网络路由。 如果你使用的是 out-of-tree 提供商,请按需实现其余任意特性。 示例 如果当前 Kubernetes 内核支持你使用的云服务,并且想要采用云管理控制器,请参见
kubernetes 内核中的云管理控制器 。
对于不在 Kubernetes 核心代码库中的云管理控制器,你可以在云服务厂商或 SIG 领导者的源中找到对应的项目。
对于已经存在于 Kubernetes 内核中的提供商,你可以在集群中将 in-tree 云管理控制器作为守护进程运行。请使用如下指南:
# 这是一个如何将 cloud-controller-manager 安装为集群中的 Daemonset 的示例。
# 本例假定你的主控节点可以运行 pod 并具有角色 node-role.kubernetes.io/master
# 请注意,这里的 Daemonset 不能直接在你的云上工作,此例只是一个指导。
---
apiVersion : v1
kind : ServiceAccount
metadata :
name : cloud-controller-manager
namespace : kube-system
---
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : system:cloud-controller-manager
roleRef :
apiGroup : rbac.authorization.k8s.io
kind : ClusterRole
name : cluster-admin
subjects :
- kind : ServiceAccount
name : cloud-controller-manager
namespace : kube-system
---
apiVersion : apps/v1
kind : DaemonSet
metadata :
labels :
k8s-app : cloud-controller-manager
name : cloud-controller-manager
namespace : kube-system
spec :
selector :
matchLabels :
k8s-app : cloud-controller-manager
template :
metadata :
labels :
k8s-app : cloud-controller-manager
spec :
serviceAccountName : cloud-controller-manager
containers :
- name : cloud-controller-manager
# 对于树内驱动,我们使用 registry.k8s.io/cloud-controller-manager,
# 镜像可以替换为其他树外驱动的镜像
image : registry.k8s.io/cloud-controller-manager:v1.8.0
command :
- /usr/local/bin/cloud-controller-manager
- --cloud-provider=[YOUR_CLOUD_PROVIDER] # 在此处添加你自己的云驱动!
- --leader-elect=true
- --use-service-account-credentials
# 这些标志因每个云驱动而异
- --allocate-node-cidrs=true
- --configure-cloud-routes=true
- --cluster-cidr=172.17.0.0/16
tolerations :
# 这一设置是必需的,为了让 CCM 可以自行引导
- key : node.cloudprovider.kubernetes.io/uninitialized
value : "true"
effect : NoSchedule
# 这些容忍度使得守护进程能够在控制平面节点上运行
# 如果你的控制平面节点不应该运行 pod,请删除它们
- key : node-role.kubernetes.io/control-plane
operator : Exists
effect : NoSchedule
- key : node-role.kubernetes.io/master
operator : Exists
effect : NoSchedule
# 这是为了限制 CCM 仅在主节点上运行
# 节点选择器可能因你的集群设置而异
nodeSelector :
node-role.kubernetes.io/master : ""
限制 运行云管理控制器会有一些可能的限制。虽然以后的版本将处理这些限制,但是知道这些生产负载的限制很重要。
对 Volume 的支持 云管理控制器未实现 kube-controller-manager
中的任何 volume 控制器,因为和 volume 的集成还需要与 kubelet 协作。由于我们引入了 CSI (容器存储接口,container storage interface) 并对弹性 volume 插件添加了更强大的支持,云管理控制器将添加必要的支持,以使云服务同 volume 更好的集成。请在 这里 了解更多关于 out-of-tree CSI volume 插件的信息。
可扩展性 在以前为云服务提供商提供的架构中,我们依赖 kubelet 的本地元数据服务来获取关于它本身的节点信息。通过这个新的架构,现在我们完全依赖云管理控制器来获取所有节点的信息。对于非常大的集群,你需要考虑可能的瓶颈,例如资源需求和 API 速率限制。
鸡和蛋的问题 云管理控制器的目标是将云服务特性的开发从 Kubernetes 核心项目中解耦。
不幸的是,Kubernetes 项目的许多方面都假设云服务提供商的特性同项目紧密结合。
因此,这种新架构的采用可能导致某些场景下,当一个请求需要从云服务提供商获取信息时,
在该请求没有完成的情况下云管理控制器不能返回那些信息。
Kubelet 中的 TLS 引导特性是一个很好的例子。
目前,TLS 引导认为 kubelet 有能力从云提供商(或本地元数据服务)获取所有的地址类型(私有、公用等),
但在被初始化之前,云管理控制器不能设置节点地址类型,而这需要 kubelet 拥有
TLS 证书以和 API 服务器通信。
随着整个动议的演进,将来的发行版中将作出改变来解决这些问题。
接下来 要构建和开发你自己的云管理控制器,请阅读
开发云管理控制器
文档。
4.2.8 - 安全地清空一个节点 本页展示了如何在确保 PodDisruptionBudget 的前提下,安全地清空一个节点 。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 1.5.
要获知版本信息,请输入 kubectl version
.
此任务假定你已经满足了以下先决条件:
为了确保你的负载在维护期间仍然可用,你可以配置一个 PodDisruptionBudget 。
如果可用性对于正在清空的该节点上运行或可能在该节点上运行的任何应用程序很重要,
首先 配置一个 PodDisruptionBudgets 并继续遵循本指南。
使用 kubectl drain
从服务中删除一个节点 在对节点执行维护(例如内核升级、硬件维护等)之前,
可以使用 kubectl drain
从节点安全地逐出所有 Pods。
安全的驱逐过程允许 Pod 的容器
体面地终止 ,
并确保满足指定的 PodDisruptionBudgets。
说明: 默认情况下,
kubectl drain
将忽略节点上不能杀死的特定系统 Pod;
有关更多细节,请参阅
kubectl drain 文档。
kubectl drain
的成功返回,表明所有的 Pods(除了上一段中描述的被排除的那些),
已经被安全地逐出(考虑到期望的终止宽限期和你定义的 PodDisruptionBudget)。
然后就可以安全地关闭节点,
比如关闭物理机器的电源,如果它运行在云平台上,则删除它的虚拟机。
首先,确定想要清空的节点的名称。可以用以下命令列出集群中的所有节点:
接下来,告诉 Kubernetes 清空节点:
kubectl drain <node name>
一旦它返回(没有报错),
你就可以下线此节点(或者等价地,如果在云平台上,删除支持该节点的虚拟机)。
如果要在维护操作期间将节点留在集群中,则需要运行:
kubectl uncordon <node name>
然后告诉 Kubernetes,它可以继续在此节点上调度新的 Pods。
并行清空多个节点 kubectl drain
命令一次只能发送给一个节点。
但是,你可以在不同的终端或后台为不同的节点并行地运行多个 kubectl drain
命令。
同时运行的多个 drain 命令仍然遵循你指定的 PodDisruptionBudget 。
例如,如果你有一个三副本的 StatefulSet,
并设置了一个 PodDisruptionBudget
,指定 minAvailable: 2
。
如果所有的三个 Pod 均就绪,并且你并行地发出多个 drain 命令,
那么 kubectl drain
只会从 StatefulSet 中逐出一个 Pod,
因为 Kubernetes 会遵守 PodDisruptionBudget 并确保在任何时候只有一个 Pod 不可用
(最多不可用 Pod 个数的计算方法:replicas - minAvailable
)。
任何会导致就绪副本数量低于指定预算的清空操作都将被阻止。
驱逐 API 如果你不喜欢使用
kubectl drain
(比如避免调用外部命令,或者更细化地控制 pod 驱逐过程),
你也可以用驱逐 API 通过编程的方式达到驱逐的效果。
更多信息,请参阅 API 发起的驱逐 。
接下来 4.2.9 - 保护集群 本文档涉及与保护集群免受意外或恶意访问有关的主题,并对总体安全性提出建议。
准备开始 控制对 Kubernetes API 的访问 因为 Kubernetes 是完全通过 API 驱动的,所以,控制和限制谁可以通过 API 访问集群,
以及允许这些访问者执行什么样的 API 动作,就成为了安全控制的第一道防线。
为所有 API 交互使用传输层安全 (TLS) Kubernetes 期望集群中所有的 API 通信在默认情况下都使用 TLS 加密,
大多数安装方法也允许创建所需的证书并且分发到集群组件中。
请注意,某些组件和安装方法可能使用 HTTP 来访问本地端口,
管理员应该熟悉每个组件的设置,以识别可能不安全的流量。
API 认证 安装集群时,选择一个 API 服务器的身份验证机制,去使用与之匹配的公共访问模式。
例如,小型的单用户集群可能希望使用简单的证书或静态承载令牌方法。
更大的集群则可能希望整合现有的、OIDC、LDAP 等允许用户分组的服务器。
所有 API 客户端都必须经过身份验证,即使它是基础设施的一部分,比如节点、代理、调度程序和卷插件。
这些客户端通常使用 服务帐户
或 X509 客户端证书,并在集群启动时自动创建或是作为集群安装的一部分进行设置。
如果你希望获取更多信息,请参考认证参考文档 。
API 授权 一旦通过身份认证,每个 API 的调用都将通过鉴权检查。
Kubernetes 集成基于角色的访问控制(RBAC) 组件,
将传入的用户或组与一组绑定到角色的权限匹配。
这些权限将动作(get、create、delete)和资源(Pod、Service、Node)进行组合,并可在名字空间或者集群范围生效。
Kubernetes 提供了一组可直接使用的角色,这些角色根据客户可能希望执行的操作提供合理的责任划分。
建议你同时使用 Node 和
RBAC 两个鉴权组件,再与
NodeRestriction
准入插件结合使用。
与身份验证一样,简单而广泛的角色可能适合于较小的集群,但是随着更多的用户与集群交互,
可能需要将团队划分到有更多角色限制的、
单独的名字空间 中去。
就鉴权而言,很重要的一点是理解对象上的更新操作如何导致在其它地方发生对应行为。
例如,用户可能不能直接创建 Pod,但允许他们通过创建 Deployment 来创建这些 Pod,
这将让他们间接创建这些 Pod。
同样地,从 API 删除一个节点将导致调度到这些节点上的 Pod 被中止,并在其他节点上重新创建。
原生的角色设计代表了灵活性和常见用例之间的平衡,但须限制的角色应该被仔细审查,
以防止意外的权限升级。如果内置的角色无法满足你的需求,则可以根据使用场景需要创建特定的角色。
如果你希望获取更多信息,请参阅鉴权参考 。
控制对 Kubelet 的访问 Kubelet 公开 HTTPS 端点,这些端点提供了对节点和容器的强大的控制能力。
默认情况下,Kubelet 允许对此 API 进行未经身份验证的访问。
生产级别的集群应启用 Kubelet 身份认证和授权。
进一步的信息,请参考
Kubelet 身份验证/授权参考 。
控制运行时负载或用户的能力 Kubernetes 中的授权故意设计成较高抽象级别,侧重于对资源的粗粒度行为。
更强大的控制是 策略 的形式呈现的,根据使用场景限制这些对象如何作用于集群、自身和其他资源。
限制集群上的资源使用 资源配额(Resource Quota) 限制了赋予命名空间的资源的数量或容量。
资源配额通常用于限制名字空间可以分配的 CPU、内存或持久磁盘的数量,
但也可以控制每个名字空间中存在多少个 Pod、Service 或 Volume。
限制范围(Limit Range)
限制上述某些资源的最大值或者最小值,以防止用户使用类似内存这样的通用保留资源时请求不合理的过高或过低的值,
或者在没有指定的情况下提供默认限制。
控制容器运行的特权 Pod 定义包含了一个安全上下文 ,
用于描述一些访问请求,如以某个节点上的特定 Linux 用户(如 root)身份运行,
以特权形式运行,访问主机网络,以及一些在宿主节点上不受约束地运行的其它控制权限等等。
你可以配置 Pod 安全准入 来在某个
名字空间 中
强制实施特定的
Pod 安全标准(Pod Security Standard) ,
或者检查安全上的缺陷。
一般来说,大多数应用程序需要对主机资源的有限制的访问,
这样它们可以在不访问主机信息的情况下,成功地以 root 账号(UID 0)运行。
但是,考虑到与 root 用户相关的特权,在编写应用程序容器时,你应该使用非 root 用户运行。
类似地,希望阻止客户端应用程序从其容器中逃逸的管理员,应该应用 Baseline
或 Restricted Pod 安全标准。
防止容器加载不需要的内核模块 如果在某些情况下,Linux 内核会根据需要自动从磁盘加载内核模块,
这类情况的例子有挂接了一个硬件或挂载了一个文件系统。
与 Kubernetes 特别相关的是,即使是非特权的进程也可能导致某些网络协议相关的内核模块被加载,
而这只需创建一个适当类型的套接字。
这就可能允许攻击者利用管理员假定未使用的内核模块中的安全漏洞。
为了防止特定模块被自动加载,你可以将它们从节点上卸载或者添加规则来阻止这些模块。
在大多数 Linux 发行版上,你可以通过创建类似 /etc/modprobe.d/kubernetes-blacklist.conf
这种文件来做到这一点,其中的内容如下所示:
# DCCP is unlikely to be needed, has had multiple serious
# vulnerabilities, and is not well-maintained.
blacklist dccp
# SCTP is not used in most Kubernetes clusters, and has also had
# vulnerabilities in the past.
blacklist sctp
为了更大范围地阻止内核模块被加载,你可以使用 Linux 安全模块(如 SELinux)
来彻底拒绝容器的 module_request
权限,从而防止在任何情况下系统为容器加载内核模块。
(Pod 仍然可以使用手动加载的模块,或者使用由内核代表某些特权进程所加载的模块。)
限制网络访问 基于名字空间的网络策略
允许应用程序作者限制其它名字空间中的哪些 Pod 可以访问自身名字空间内的 Pod 和端口。
现在已经有许多支持网络策略的
Kubernetes 网络驱动 。
配额(Quota)和限制范围(Limit Range)也可用于控制用户是否可以请求节点端口或负载均衡服务。
在很多集群上,节点端口和负载均衡服务也可控制用户的应用程序是否在集群之外可见。
此外也可能存在一些基于插件或基于环境的网络规则,能够提供额外的保护能力。
例如各节点上的防火墙、物理隔离集群节点以防止串扰或者高级的网络策略等。
限制云元数据 API 访问 云平台(AWS、Azure、GCE 等)经常将 metadata 本地服务暴露给实例。
默认情况下,这些 API 可由运行在实例上的 Pod 访问,并且可以包含
该云节点的凭据或配置数据(如 kubelet 凭据)。
这些凭据可以用于在集群内升级或在同一账户下升级到其他云服务。
在云平台上运行 Kubernetes 时,需要限制对实例凭据的权限,使用
网络策略
限制 Pod 对元数据 API 的访问,并避免使用配置数据来传递机密信息。
控制 Pod 可以访问的节点 默认情况下,对 Pod 可以运行在哪些节点上是没有任何限制的。
Kubernetes 给最终用户提供了
一组丰富的策略用于控制 Pod 所放置的节点位置 ,
以及基于污点的 Pod 放置和驱逐 。
对于许多集群,使用这些策略来分离工作负载可以作为一种约定,要求作者遵守或者通过工具强制。
对于管理员,Beta 阶段的准入插件 PodNodeSelector
可用于强制某名字空间中的 Pod
使用默认的或特定的节点选择算符。
如果最终用户无法改变名字空间,这一机制可以有效地限制特定工作负载中所有 Pod 的放置位置。
保护集群组件免受破坏 本节描述保护集群免受破坏的一些常用模式。
限制访问 etcd 拥有对 API 的 etcd 后端的写访问权限相当于获得了整个集群的 root 权限,
读访问权限也可能被利用,实现相当快速的权限提升。
对于从 API 服务器访问其 etcd 服务器,管理员应该总是使用比较强的凭证,如通过 TLS
客户端证书来实现双向认证。
通常,我们建议将 etcd 服务器隔离到只有 API 服务器可以访问的防火墙后面。
注意: 允许集群中其它组件对整个主键空间(keyspace)拥有读或写权限去访问 etcd 实例,
相当于授予这些组件集群管理员的访问权限。
对于非主控组件,强烈推荐使用不同的 etcd 实例,或者使用 etcd 的访问控制列表
来限制这些组件只能读或写主键空间的一个子集。
启用审计日志 审计日志 是 Beta 特性,
负责记录 API 操作以便在发生破坏时进行事后分析。
建议启用审计日志,并将审计文件归档到安全服务器上。
限制使用 alpha 和 beta 特性 Kubernetes 的 alpha 和 beta 特性还在努力开发中,可能存在导致安全漏洞的缺陷或错误。
要始终评估 alpha 和 beta 特性可能给你的安全态势带来的风险。
当你怀疑存在风险时,可以禁用那些不需要使用的特性。
经常轮换基础设施证书 一项机密信息或凭据的生命期越短,攻击者就越难使用该凭据。
在证书上设置较短的生命期并实现自动轮换是控制安全的一个好方法。
使用身份验证提供程序时,应该使用那些可以控制所发布令牌的合法时长的提供程序,
并尽可能设置较短的生命期。
如果在外部集成场景中使用服务帐户令牌,则应该经常性地轮换这些令牌。
例如,一旦引导阶段完成,就应该撤销用于配置节点的引导令牌,或者取消它的授权。
在启用第三方集成之前,请先审查它们 许多集成到 Kubernetes 的第三方软件或服务都可能改变你的集群的安全配置。
启用集成时,在授予访问权限之前,你应该始终检查扩展所请求的权限。
例如,许多安全性集成中可能要求查看集群上的所有 Secret 的访问权限,
本质上该组件便成为了集群的管理员。
当有疑问时,如果可能的话,将要集成的组件限制在某指定名字空间中运行。
如果执行 Pod 创建操作的组件能够在 kube-system
这类名字空间中创建 Pod,
则这类组件也可能获得意外的权限,因为这些 Pod 可以访问服务账户的 Secret,
或者,如果对应服务帐户被授权访问宽松的
PodSecurityPolicy ,
它们就能以较高的权限运行。
如果你使用 Pod 安全准入 ,
并且允许任何组件在一个允许执行特权 Pod 的名字空间中创建 Pod,这些 Pod
就可能从所在的容器中逃逸,利用被拓宽的访问权限来实现特权提升。
你不应该允许不可信的组件在任何系统名字空间(名字以 kube-
开头)中创建 Pod,
也不允许它们在访问权限授权可被利用来提升特权的名字空间中创建 Pod。
对 Secret 进行静态加密 一般情况下,etcd 数据库包含了通过 Kubernetes API 可以访问到的所有信息,
并且可能为攻击者提供对你的集群的状态的较多的可见性。
你要始终使用经过充分审查的备份和加密方案来加密备份数据,
并考虑在可能的情况下使用全盘加密。
Kubernetes 支持静态数据加密 。
该功能在 1.7 版引入,在 1.13 版成为 v1 Beta,在 1.25 版成为 v2 Alpha。
它会加密 etcd 里面的 Secret
和 ConfigMap
资源,以防止某一方通过查看 etcd 的备份文件查看到这些
Secret 的内容。虽然目前该功能还只是 Beta 阶段,
在备份未被加密或者攻击者获取到 etcd 的读访问权限时,它仍能提供额外的防御层级。
接收安全更新和报告漏洞的警报 请加入 kubernetes-announce
组,这样你就能够收到有关安全公告的邮件。有关如何报告漏洞的更多信息,
请参见安全报告 页面。
4.2.10 - 调试 DNS 问题 这篇文章提供了一些关于 DNS 问题诊断的方法。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的集群必须使用了 CoreDNS 插件
或者其前身,kube-dns
。
你的 Kubernetes 服务器版本必须不低于版本 v1.6.
要获知版本信息,请输入
kubectl version
.
创建一个简单的 Pod 作为测试环境 apiVersion : v1
kind : Pod
metadata :
name : dnsutils
namespace : default
spec :
containers :
- name : dnsutils
image : registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3
command :
- sleep
- "infinity"
imagePullPolicy : IfNotPresent
restartPolicy : Always
使用上面的清单来创建一个 Pod:
kubectl apply -f https://k8s.io/examples/admin/dns/dnsutils.yaml
pod/dnsutils created
验证其状态:
kubectl get pods dnsutils
NAME READY STATUS RESTARTS AGE
dnsutils 1/1 Running 0 <some-time>
一旦 Pod 处于运行状态,你就可以在该环境里执行 nslookup
。
如果你看到类似下列的内容,则表示 DNS 是正常运行的。
kubectl exec -i -t dnsutils -- nslookup kubernetes.default
输出为:
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: kubernetes.default
Address 1: 10.0.0.1
如果 nslookup
命令执行失败,请检查下列内容:
先检查本地的 DNS 配置 查看 resolv.conf 文件的内容
(阅读定制 DNS 服务 和
后文的已知问题 ,获取更多信息)
kubectl exec -ti dnsutils -- cat /etc/resolv.conf
验证 search 和 nameserver 的配置是否与下面的内容类似
(注意 search 根据不同的云提供商可能会有所不同):
search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal
nameserver 10.0.0.10
options ndots:5
下列错误表示 CoreDNS (或 kube-dns)插件或者相关服务出现了问题:
kubectl exec -i -t dnsutils -- nslookup kubernetes.default
输出为:
Server: 10.0.0.10
Address 1: 10.0.0.10
nslookup: can't resolve 'kubernetes.default'
或者
kubectl exec -i -t dnsutils -- nslookup kubernetes.default
输出为:
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
nslookup: can't resolve 'kubernetes.default'
检查 DNS Pod 是否运行 使用 kubectl get pods
命令来验证 DNS Pod 是否运行。
kubectl get pods --namespace= kube-system -l k8s-app= kube-dns
输出为:
NAME READY STATUS RESTARTS AGE
...
coredns-7b96bf9f76-5hsxb 1/1 Running 0 1h
coredns-7b96bf9f76-mvmmt 1/1 Running 0 1h
...
说明: 对于 CoreDNS 和 kube-dns 部署而言,标签 k8s-app
的值都应该是 kube-dns
。
如果你发现没有 CoreDNS Pod 在运行,或者该 Pod 的状态是 failed 或者 completed,
那可能这个 DNS 插件在你当前的环境里并没有成功部署,你将需要手动去部署它。
检查 DNS Pod 里的错误 使用 kubectl logs
命令来查看 DNS 容器的日志信息。
如查看 CoreDNS 的日志信息:
kubectl logs --namespace= kube-system -l k8s-app= kube-dns
下列是一个正常运行的 CoreDNS 日志信息:
.:53
2018/08/15 14:37:17 [INFO] CoreDNS-1.2.2
2018/08/15 14:37:17 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.2
linux/amd64, go1.10.3, 2e322f6
2018/08/15 14:37:17 [INFO] plugin/reload: Running configuration MD5 = 24e6c59e83ce706f07bcc82c31b1ea1c
查看是否日志中有一些可疑的或者意外的消息。
检查是否启用了 DNS 服务 使用 kubectl get service
命令来检查 DNS 服务是否已经启用。
kubectl get svc --namespace= kube-system
输出为:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 1h
...
说明: 不管是 CoreDNS 还是 kube-dns,这个服务的名字都会是 kube-dns
。
如果你已经创建了 DNS 服务,或者该服务应该是默认自动创建的但是它并没有出现,
请阅读调试服务
来获取更多信息。
DNS 的端点公开了吗? 你可以使用 kubectl get endpoints
命令来验证 DNS 的端点是否公开了。
kubectl get endpoints kube-dns --namespace= kube-system
NAME ENDPOINTS AGE
kube-dns 10.180.3.17:53,10.180.3.17:53 1h
如果你没看到对应的端点,请阅读
调试服务 的端点部分。
若需要了解更多的 Kubernetes DNS 例子,请在 Kubernetes GitHub 仓库里查看
cluster-dns 示例 。
DNS 查询有被接收或者执行吗? 你可以通过给 CoreDNS 的配置文件(也叫 Corefile)添加 log
插件来检查查询是否被正确接收。
CoreDNS 的 Corefile 被保存在一个叫 coredns
的
ConfigMap 里,使用下列命令来编辑它:
kubectl -n kube-system edit configmap coredns
然后按下面的例子给 Corefile 添加 log
。
apiVersion : v1
kind : ConfigMap
metadata :
name : coredns
namespace : kube-system
data :
Corefile : |
.:53 {
log
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
保存这些更改后,你可能会需要等待一到两分钟让 Kubernetes 把这些更改应用到
CoreDNS 的 Pod 里。
接下来,发起一些查询并依照前文所述查看日志信息,如果 CoreDNS 的 Pod 接收到这些查询,
你将可以在日志信息里看到它们。
下面是日志信息里的查询例子:
.:53
2018/08/15 14:37:15 [INFO] CoreDNS-1.2.0
2018/08/15 14:37:15 [INFO] linux/amd64, go1.10.3, 2e322f6
CoreDNS-1.2.0
linux/amd64, go1.10.3, 2e322f6
2018/09/07 15:29:04 [INFO] plugin/reload: Running configuration MD5 = 162475cdf272d8aa601e6fe67a6ad42f
2018/09/07 15:29:04 [INFO] Reloading complete
172.17.0.18:41675 - [07/Sep/2018:15:29:11 +0000] 59925 "A IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR qr,aa,rd,ra 106 0.000066649s
CoreDNS 是否有足够的权限? CoreDNS 必须能够列出 service 和
endpoint 相关的资源来正确解析服务名称。
示例错误消息:
2022-03-18T07:12:15.699431183Z [INFO] 10.96.144.227:52299 - 3686 "A IN serverproxy.contoso.net.cluster.local. udp 52 false 512" SERVFAIL qr,aa,rd 145 0.000091221s
首先,获取当前的 ClusterRole system:coredns
:
kubectl describe clusterrole system:coredns -n kube-system
预期输出:
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
nodes [] [] [get]
endpoints [] [] [list watch]
namespaces [] [] [list watch]
pods [] [] [list watch]
services [] [] [list watch]
endpointslices.discovery.k8s.io [] [] [list watch]
如果缺少任何权限,请编辑 ClusterRole 来添加它们:
kubectl edit clusterrole system:coredns -n kube-system
EndpointSlices 权限的插入示例:
...
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- list
- watch
...
你的服务在正确的名字空间中吗? 未指定名字空间的 DNS 查询仅作用于 Pod 所在的名字空间。
如果 Pod 和服务的名字空间不相同,则 DNS 查询必须指定服务所在的名字空间。
该查询仅限于 Pod 所在的名字空间:
kubectl exec -i -t dnsutils -- nslookup <service-name>
指定名字空间的查询:
kubectl exec -i -t dnsutils -- nslookup <service-name>.<namespace>
要进一步了解名字解析,请查看
Pod 与 Service 的 DNS 。
已知问题 有些 Linux 发行版本(比如 Ubuntu)默认使用一个本地的 DNS 解析器(systemd-resolved)。
systemd-resolved
会用一个存根文件(Stub File)来覆盖 /etc/resolv.conf
内容,
从而可能在上游服务器中解析域名产生转发环(forwarding loop)。 这个问题可以通过手动指定
kubelet 的 --resolv-conf
标志为正确的 resolv.conf
(如果是 systemd-resolved
,
则这个文件路径为 /run/systemd/resolve/resolv.conf
)来解决。
kubeadm 会自动检测 systemd-resolved
并对应的更改 kubelet 的命令行标志。
Kubernetes 的安装并不会默认配置节点的 resolv.conf
文件来使用集群的 DNS 服务,因为这个配置对于不同的发行版本是不一样的。这个问题应该迟早会被解决的。
Linux 的 libc 限制 nameserver
只能有三个记录。不仅如此,对于 glibc-2.17-222
之前的版本(参见此 Issue 了解新版本的更新 ),search
的记录不能超过 6 个
( 详情请查阅这个 2005 年的 bug )。
Kubernetes 需要占用一个 nameserver
记录和三个search
记录。
这意味着如果一个本地的安装已经使用了三个 nameserver
或者使用了超过三个
search
记录,而你的 glibc 版本也在有问题的版本列表中,那么有些配置很可能会丢失。
为了绕过 DNS nameserver
个数限制,节点可以运行 dnsmasq
,以提供更多的
nameserver
记录。你也可以使用kubelet 的 --resolv-conf
标志来解决这个问题。
要想修复 DNS search
记录个数限制问题,可以考虑升级你的 Linux 发行版本,或者
升级 glibc 到一个不再受此困扰的版本。
如果你使用 Alpine 3.3 或更早版本作为你的基础镜像,DNS 可能会由于 Alpine 中
一个已知的问题导致无法正常工作。
请查看这里 获取更多信息。
接下来 4.2.11 - 改变默认 StorageClass 本文展示了如何改变默认的 Storage Class,它用于为没有特殊需求的 PersistentVolumeClaims 配置 volumes。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
为什么要改变默认存储类? 取决于安装模式,你的 Kubernetes 集群可能和一个被标记为默认的已有 StorageClass 一起部署。
这个默认的 StorageClass 以后将被用于动态的为没有特定存储类需求的 PersistentVolumeClaims
配置存储。更多细节请查看
PersistentVolumeClaim 文档 。
预先安装的默认 StorageClass 可能不能很好的适应你期望的工作负载;例如,它配置的存储可能太过昂贵。
如果是这样的话,你可以改变默认 StorageClass,或者完全禁用它以防止动态配置存储。
删除默认 StorageClass 可能行不通,因为它可能会被你集群中的扩展管理器自动重建。
请查阅你的安装文档中关于扩展管理器的细节,以及如何禁用单个扩展。
改变默认 StorageClass 列出你的集群中的 StorageClasses:
输出类似这样:
NAME PROVISIONER AGE
standard ( default) kubernetes.io/gce-pd 1d
gold kubernetes.io/gce-pd 1d
默认 StorageClass 以 (default)
标记。
标记默认 StorageClass 非默认:
默认 StorageClass 的注解 storageclass.kubernetes.io/is-default-class
设置为 true
。
注解的其它任意值或者缺省值将被解释为 false
。
要标记一个 StorageClass 为非默认的,你需要改变它的值为 false
:
kubectl patch storageclass standard -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
这里的 standard
是你选择的 StorageClass 的名字。
标记一个 StorageClass 为默认的:
和前面的步骤类似,你需要添加/设置注解 storageclass.kubernetes.io/is-default-class=true
。
kubectl patch storageclass <your-class-name> -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
请注意,最多只能有一个 StorageClass 能够被标记为默认。
如果它们中有两个或多个被标记为默认,Kubernetes 将忽略这个注解,
也就是它将表现为没有默认 StorageClass。
验证你选用的 StorageClass 为默认的:
输出类似这样:
NAME PROVISIONER AGE
standard kubernetes.io/gce-pd 1d
gold (default) kubernetes.io/gce-pd 1d
接下来 4.2.12 - 更改 PersistentVolume 的回收策略 本文展示了如何更改 Kubernetes PersistentVolume 的回收策略。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
为什么要更改 PersistentVolume 的回收策略 PersistentVolumes 可以有多种回收策略,包括 "Retain"、"Recycle" 和 "Delete"。
对于动态配置的 PersistentVolumes 来说,默认回收策略为 "Delete"。
这表示当用户删除对应的 PersistentVolumeClaim 时,动态配置的 volume 将被自动删除。
如果 volume 包含重要数据时,这种自动行为可能是不合适的。
那种情况下,更适合使用 "Retain" 策略。
使用 "Retain" 时,如果用户删除 PersistentVolumeClaim,对应的 PersistentVolume 不会被删除。
相反,它将变为 Released 状态,表示所有的数据可以被手动恢复。
更改 PersistentVolume 的回收策略 列出你集群中的 PersistentVolumes
输出类似于这样:
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim1 manual 10s
pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim2 manual 6s
pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim3 manual 3s
这个列表同样包含了绑定到每个卷的 claims 名称,以便更容易的识别动态配置的卷。
选择你的 PersistentVolumes 中的一个并更改它的回收策略:
kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
这里的 <your-pv-name>
是你选择的 PersistentVolume 的名字。
说明: 在 Windows 系统上,你必须对包含空格的 JSONPath 模板加双引号(而不是像上面
一样为 Bash 环境使用的单引号)。这也意味着你必须使用单引号或者转义的双引号
来处理模板中的字面值。例如:
kubectl patch pv <your-pv-name> -p "{\" spec\":{\" persistentVolumeReclaimPolicy\":\" Retain\"}}"
验证你选择的 PersistentVolume 拥有正确的策略:
输出类似于这样:
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim1 manual 40s
pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim2 manual 36s
pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Retain Bound default/claim3 manual 33s
在前面的输出中,你可以看到绑定到申领 default/claim3
的卷的回收策略为 Retain
。
当用户删除申领 default/claim3
时,它不会被自动删除。
接下来 参考 4.2.13 - 关键插件 Pod 的调度保证 Kubernetes 核心组件(如 API 服务器、调度器、控制器管理器)在控制平面节点上运行。
但是插件必须在常规集群节点上运行。
其中一些插件对于功能完备的集群至关重要,例如 Heapster、DNS 和 UI。
如果关键插件被逐出(手动或作为升级等其他操作的副作用)或者变成挂起状态,集群可能会停止正常工作。
关键插件进入挂起状态的例子有:集群利用率过高;被逐出的关键插件 Pod 释放了空间,但该空间被之前悬决的 Pod 占用;由于其它原因导致节点上可用资源的总量发生变化。
注意,把某个 Pod 标记为关键 Pod 并不意味着完全避免该 Pod 被逐出;它只能防止该 Pod 变成永久不可用。
被标记为关键性的静态 Pod 不会被逐出。但是,被标记为关键性的非静态 Pod 总是会被重新调度。
标记关键 Pod 要将 Pod 标记为关键性(critical),设置 Pod 的 priorityClassName 为 system-cluster-critical
或者 system-node-critical
。
system-node-critical
是最高级别的可用性优先级,甚至比 system-cluster-critical
更高。
4.2.14 - 静态加密 Secret 数据 本文展示如何启用和配置静态 Secret 数据的加密。
准备开始 配置并确定是否已启用静态数据加密 kube-apiserver
的参数 --encryption-provider-config
控制 API 数据在 etcd 中的加密方式。
该配置作为一个名为 EncryptionConfiguration
的 API 提供。
下面提供了一个示例配置。
注意: 重要: 对于高可用配置(有两个或多个控制平面节点),加密配置文件必须相同!
否则,kube-apiserver
组件无法解密存储在 etcd 中的数据。
理解静态数据加密 apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- identity : {}
- aesgcm :
keys :
- name : key1
secret : c2VjcmV0IGlzIHNlY3VyZQ==
- name : key2
secret : dGhpcyBpcyBwYXNzd29yZA==
- aescbc :
keys :
- name : key1
secret : c2VjcmV0IGlzIHNlY3VyZQ==
- name : key2
secret : dGhpcyBpcyBwYXNzd29yZA==
- secretbox :
keys :
- name : key1
secret : YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
每个 resources
数组项目是一个单独的完整的配置。
resources.resources
字段是要加密的 Kubernetes 资源名称(resource
或 resource.group
)的数组。
providers
数组是可能的加密 provider 的有序列表。
每个条目只能指定一个 provider 类型(可以是 identity
或 aescbc
,但不能在同一个项目中同时指定二者)。
列表中的第一个 provider 用于加密写入存储的资源。
当从存储器读取资源时,与存储的数据匹配的所有 provider 将按顺序尝试解密数据。
如果由于格式或密钥不匹配而导致没有 provider 能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。
有关 EncryptionConfiguration
结构体的更多详细信息,请参阅加密配置 API 。
注意: 如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从底层 etcd 中删除该密钥。
任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。
Providers: Kubernetes 静态数据加密的 Provider 名称 加密类型 强度 速度 密钥长度 其它事项 identity
无 N/A N/A N/A 不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。 secretbox
XSalsa20 和 Poly1305 强 更快 32 字节 较新的标准,在需要高度评审的环境中可能不被接受。 aesgcm
带有随机数的 AES-GCM 必须每 200k 写入一次 最快 16、24 或者 32字节 建议不要使用,除非实施了自动密钥循环方案。 aescbc
填充 PKCS#7 的 AES-CBC 弱 快 32 字节 由于 CBC 容易受到密文填塞攻击(Padding Oracle Attack),不推荐使用。 kms
使用信封加密方案:数据使用带有 PKCS#7 填充的 AES-CBC(v1.25 之前),从 v1.25 开始使用 AES-GCM 通过数据加密密钥(DEK)加密,DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密 最强 快 32 字节 建议使用第三方工具进行密钥管理。为每个加密生成新的 DEK,并由用户控制 KEK 轮换来简化密钥轮换。配置 KMS 提供程序
每个 provider 都支持多个密钥 - 在解密时会按顺序使用密钥,如果是第一个 provider,则第一个密钥用于加密。
注意: 在 EncryptionConfig 中保存原始的加密密钥与不加密相比只会略微地提升安全级别。
请使用 kms
驱动以获得更强的安全性。
默认情况下,identity
驱动被用来对 etcd 中的 Secret 提供保护,而这个驱动不提供加密能力。
EncryptionConfiguration
的引入是为了能够使用本地管理的密钥来在本地加密 Secret 数据。
使用本地管理的密钥来加密 Secret 能够保护数据免受 etcd 破坏的影响,不过无法针对主机被侵入提供防护。
这是因为加密的密钥保存在主机上的 EncryptionConfig YAML 文件中,
有经验的入侵者仍能访问该文件并从中提取出加密密钥。
封套加密(Envelope Encryption)引入了对独立密钥的依赖,而这个密钥并不保存在 Kubernetes 中。
在这种情况下,入侵者需要攻破 etcd、kube-apiserver 和第三方的 KMS
驱动才能获得明文数据,因而这种方案提供了比本地保存加密密钥更高的安全级别。
加密你的数据 创建一个新的加密配置文件:
apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- aescbc :
keys :
- name : key1
secret : <BASE 64 ENCODED SECRET>
- identity : {}
遵循如下步骤来创建一个新的 Secret:
生成一个 32 字节的随机密钥并进行 base64 编码。如果你在 Linux 或 macOS 上,请运行以下命令:
head -c 32 /dev/urandom | base64
将这个值放入到 EncryptionConfiguration
结构体的 secret
字段中。
设置 kube-apiserver
的 --encryption-provider-config
参数,将其指向配置文件所在位置。
你将需要把新的加密配置文件挂载到 kube-apiserver
静态 Pod。以下是这个操作的示例:
将新的加密配置文件保存到控制平面节点上的 /etc/kubernetes/enc/enc.yaml
。 编辑 kube-apiserver
静态 Pod 的清单:/etc/kubernetes/manifests/kube-apiserver.yaml
,
代码范例如下: apiVersion : v1
kind : Pod
metadata :
annotations :
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint : 10.10.30.4 :6443
creationTimestamp : null
labels :
component : kube-apiserver
tier : control-plane
name : kube-apiserver
namespace : kube-system
spec :
containers :
- command :
- kube-apiserver
...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # <-- 增加这一行
volumeMounts :
...
- name : enc # <-- 增加这一行
mountPath : /etc/kubernetes/enc # <-- 增加这一行
readonly : true # <-- 增加这一行
...
volumes :
...
- name : enc # <-- 增加这一行
hostPath : # <-- 增加这一行
path : /etc/kubernetes/enc # <-- 增加这一行
type : DirectoryOrCreate # <-- 增加这一行
...
重启你的 API 服务器。 注意: 你的配置文件包含可以解密 etcd 内容的密钥,因此你必须正确限制控制平面节点的访问权限,
以便只有能运行 kube-apiserver
的用户才能读取它。
验证数据已被加密 数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver
后,任何新创建或更新的密码在存储时都应该被加密。
如果想要检查,你可以使用 etcdctl
命令行程序来检索你的加密内容。
创建一个新的 secret,名称为 secret1
,命名空间为 default
:
kubectl create secret generic secret1 -n default --from-literal= mykey = mydata
使用 etcdctl 命令行,从 etcd 中读取 Secret:
ETCDCTL_API = 3 etcdctl get /registry/secrets/default/secret1 [ ...] | hexdump -C
这里的 [...]
是用来连接 etcd 服务的额外参数。
例如:
ETCDCTL_API = 3 etcdctl \
--cacert= /etc/kubernetes/pki/etcd/ca.crt \
--cert= /etc/kubernetes/pki/etcd/server.crt \
--key= /etc/kubernetes/pki/etcd/server.key \
get /registry/secrets/default/secret1 | hexdump -C
输出类似于(有删减):
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret |
00000010 73 2f 64 65 66 61 75 6c 74 2f 73 65 63 72 65 74 |s/default/secret |
00000020 31 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |1.k8s:enc:aescbc |
00000030 3a 76 31 3a 6b 65 79 31 3a c7 6c e7 d3 09 bc 06 |:v1:key1:.l..... |
00000040 25 51 91 e4 e0 6c e5 b1 4d 7a 8b 3d b9 c2 7c 6e |%Q...l..Mz.=..|n |
00000050 b4 79 df 05 28 ae 0d 8e 5f 35 13 2c c0 18 99 3e |.y..(..._5.,...> |
[...]
00000110 23 3a 0d fc 28 ca 48 2d 6b 2d 46 cc 72 0b 70 4c |#:..(.H-k-F.r.pL |
00000120 a5 fc 35 43 12 4e 60 ef bf 6f fe cf df 0b ad 1f |..5C.N`..o...... |
00000130 82 c4 88 53 02 da 3e 66 ff 0a |...S..>f.. |
0000013a
验证存储的密钥前缀是否为 k8s:enc:aescbc:v1:
,这表明 aescbc
provider 已加密结果数据。
通过 API 检索,验证 Secret 是否被正确解密:
kubectl describe secret secret1 -n default
其输出应该包含 mykey: bXlkYXRh
,mydata
的内容是被加密过的,
请参阅解密 Secret
了解如何完全解码 Secret 内容。
确保所有 Secret 都被加密 由于 Secret 是在写入时被加密,因此对 Secret 执行更新也会加密该内容。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
上面的命令读取所有 Secret,然后使用服务端加密来更新其内容。
说明: 如果由于冲突写入而发生错误,请重试该命令。
对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。
轮换解密密钥 在不发生停机的情况下更改 Secret 需要多步操作,特别是在有多个 kube-apiserver
进程正在运行的高可用环境中。
生成一个新密钥并将其添加为所有服务器上当前提供程序的第二个密钥条目 重新启动所有 kube-apiserver
进程以确保每台服务器都可以使用新密钥进行解密 将新密钥设置为 keys
数组中的第一个条目,以便在配置中使用其进行加密 重新启动所有 kube-apiserver
进程以确保每个服务器现在都使用新密钥进行加密 运行 kubectl get secrets --all-namespaces -o json | kubectl replace -f -
以用新密钥加密所有现有的 Secret 在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥 当只运行一个 kube-apiserver
实例时,第 2 步可以忽略。
解密所有数据 要禁用静态加密,请将 identity
provider
作为配置中的第一个条目并重新启动所有 kube-apiserver
进程。
apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- identity : {}
- aescbc :
keys :
- name : key1
secret : <BASE 64 ENCODED SECRET>
然后运行以下命令以强制解密所有 Secret:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
接下来 进一步学习 EncryptionConfiguration 配置 API (v1) 。
4.2.15 - 开发云控制器管理器 特性状态: Kubernetes v1.11 [beta]
一个 Kubernetes 控制平面 组件,
嵌入了特定于云平台的控制逻辑。
云控制器管理器(Cloud Controller Manager)允许你将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
cloud-controller-manager
组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
背景 由于云驱动的开发和发布与 Kubernetes 项目本身步调不同,将特定于云环境的代码抽象到
cloud-controller-manager
二进制组件有助于云厂商独立于 Kubernetes
核心代码推进其驱动开发。
Kubernetes 项目提供 cloud-controller-manager 的框架代码,其中包含 Go 语言的接口,
便于你(或者你的云驱动提供者)接驳你自己的实现。这意味着每个云驱动可以通过从
Kubernetes 核心代码导入软件包来实现一个 cloud-controller-manager;
每个云驱动会通过调用 cloudprovider.RegisterCloudProvider
接口来注册其自身实现代码,
从而更新一个用来记录可用云驱动的全局变量。
开发 树外(Out of Tree) 要为你的云环境构建一个树外(Out-of-Tree)云控制器管理器:
使用满足 cloudprovider.Interface
接口的实现来创建一个 Go 语言包。 使用来自 Kubernetes 核心代码库的
cloud-controller-manager 中的 main.go
作为 main.go
的模板。如上所述,唯一的区别应该是将导入的云包不同。 在 main.go
中导入你的云包,确保你的包有一个 init
块来运行
cloudprovider.RegisterCloudProvider
。 很多云驱动都将其控制器管理器代码以开源代码的形式公开。
如果你在开发一个新的 cloud-controller-manager,你可以选择某个树外(Out-of-Tree)
云控制器管理器作为出发点。
树内(In Tree) 对于树内(In-Tree)驱动,你可以将树内云控制器管理器作为集群中的
DaemonSet 来运行。
有关详细信息,请参阅云控制器管理器管理 。
4.2.16 - 开启服务拓扑 特性状态: Kubernetes v1.21 [deprecated]
这项功能,特别是 Alpha 状态的 topologyKeys
字段,在 kubernetes v1.21 中已经弃用。
在 kubernetes v1.21 加入的拓扑感知提示
提供了类似的功能。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.17.
要获知版本信息,请输入
kubectl version
.
服务拓扑(Service Topology) 使 服务
能够根据集群中的 Node 拓扑来路由流量。
比如,服务可以指定将流量优先路由到与客户端位于同一节点或者同一可用区域的端点上。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.17.
要获知版本信息,请输入
kubectl version
.
需要下面列的先决条件,才能启用拓扑感知的服务路由:
Kubernetes 1.17 或更新版本 配置 kube-proxy 以 iptables 或者 IPVS 模式运行 启用服务拓扑 特性状态: Kubernetes v1.21 [deprecated]
要启用服务拓扑功能,需要为所有 Kubernetes 组件启用 ServiceTopology
特性门控 :
--feature-gates="ServiceTopology=true`
接下来 4.2.17 - 控制节点上的 CPU 管理策略 特性状态: Kubernetes v1.12 [beta]
按照设计,Kubernetes 对 Pod 执行相关的很多方面进行了抽象,使得用户不必关心。
然而,为了正常运行,有些工作负载要求在延迟和/或性能方面有更强的保证。
为此,kubelet 提供方法来实现更复杂的负载放置策略,同时保持抽象,避免显式的放置指令。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
CPU 管理策略 默认情况下,kubelet 使用 CFS 配额
来执行 Pod 的 CPU 约束。
当节点上运行了很多 CPU 密集的 Pod 时,工作负载可能会迁移到不同的 CPU 核,
这取决于调度时 Pod 是否被扼制,以及哪些 CPU 核是可用的。
许多工作负载对这种迁移不敏感,因此无需任何干预即可正常工作。
然而,有些工作负载的性能明显地受到 CPU 缓存亲和性以及调度延迟的影响。
对此,kubelet 提供了可选的 CPU 管理策略,来确定节点上的一些分配偏好。
配置 CPU 管理策略通过 kubelet 参数 --cpu-manager-policy
或 KubeletConfiguration
中的 cpuManagerPolicy
字段来指定。
支持两种策略:
none
: 默认策略,表示现有的调度行为。static
: 允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性。CPU 管理器定期通过 CRI 写入资源更新,以保证内存中 CPU 分配与 cgroupfs 一致。
同步频率通过新增的 Kubelet 配置参数 --cpu-manager-reconcile-period
来设置。
如果不指定,默认与 --node-status-update-frequency
的周期相同。
Static 策略的行为可以使用 --cpu-manager-policy-options
参数来微调。
该参数采用一个逗号分隔的 key=value
策略选项列表。
此特性可以通过 CPUManagerPolicyOptions
特性门控来完全禁用。
策略选项分为两组:alpha 质量(默认隐藏)和 beta 质量(默认可见)。
这些组分别由 CPUManagerPolicyAlphaOptions
和 CPUManagerPolicyBetaOptions
特性门控来管控。
不同于 Kubernetes 标准,这里是由这些特性门控来管控选项组,因为为每个单独选项都添加一个特性门控过于繁琐。
更改 CPU 管理器策略 由于 CPU 管理器策略只能在 kubelet 生成新 Pod 时应用,所以简单地从 "none" 更改为 "static"
将不会对现有的 Pod 起作用。
因此,为了正确更改节点上的 CPU 管理器策略,请执行以下步骤:
腾空 节点。停止 kubelet。 删除旧的 CPU 管理器状态文件。该文件的路径默认为 /var/lib/kubelet/cpu_manager_state
。
这将清除 CPUManager 维护的状态,以便新策略设置的 cpu-sets 不会与之冲突。 编辑 kubelet 配置以将 CPU 管理器策略更改为所需的值。 启动 kubelet。 对需要更改其 CPU 管理器策略的每个节点重复此过程。
跳过此过程将导致 kubelet crashlooping 并出现以下错误:
could not restore state from checkpoint: configured policy "static" differs from state checkpoint policy "none", please drain this node and delete the CPU manager checkpoint file "/var/lib/kubelet/cpu_manager_state" before restarting Kubelet
none 策略 none
策略显式地启用现有的默认 CPU 亲和方案,不提供操作系统调度器默认行为之外的亲和性策略。
通过 CFS 配额来实现 Guaranteed Pods
和 Burstable Pods
的 CPU 使用限制。
static 策略 static
策略针对具有整数型 CPU requests
的 Guaranteed
Pod,
它允许该类 Pod 中的容器访问节点上的独占 CPU 资源。这种独占性是使用
cpuset cgroup 控制器 来实现的。
说明: 诸如容器运行时和 kubelet 本身的系统服务可以继续在这些独占 CPU 上运行。独占性仅针对其他 Pod。
说明: CPU 管理器不支持运行时下线和上线 CPU。此外,如果节点上的在线 CPU 集合发生变化,
则必须驱逐节点上的 Pod,并通过删除 kubelet 根目录中的状态文件 cpu_manager_state
来手动重置 CPU 管理器。
此策略管理一个 CPU 共享池,该共享池最初包含节点上所有的 CPU 资源。
可独占性 CPU 资源数量等于节点的 CPU 总量减去通过 kubelet --kube-reserved
或 --system-reserved
参数保留的 CPU 资源。
从 1.17 版本开始,可以通过 kubelet --reserved-cpus
参数显式地指定 CPU 预留列表。
由 --reserved-cpus
指定的显式 CPU 列表优先于由 --kube-reserved
和 --system-reserved
指定的 CPU 预留。
通过这些参数预留的 CPU 是以整数方式,按物理核心 ID 升序从初始共享池获取的。
共享池是 BestEffort
和 Burstable
Pod 运行的 CPU 集合。
Guaranteed
Pod 中的容器,如果声明了非整数值的 CPU requests
,也将运行在共享池的 CPU 上。
只有 Guaranteed
Pod 中,指定了整数型 CPU requests
的容器,才会被分配独占 CPU 资源。
说明: 当启用 static 策略时,要求使用 --kube-reserved
和/或 --system-reserved
或
--reserved-cpus
来保证预留的 CPU 值大于零。
这是因为零预留 CPU 值可能使得共享池变空。
当 Guaranteed
Pod 调度到节点上时,如果其容器符合静态分配要求,
相应的 CPU 会被从共享池中移除,并放置到容器的 cpuset 中。
因为这些容器所使用的 CPU 受到调度域本身的限制,所以不需要使用 CFS 配额来进行 CPU 的绑定。
换言之,容器 cpuset 中的 CPU 数量与 Pod 规约中指定的整数型 CPU limit
相等。
这种静态分配增强了 CPU 亲和性,减少了 CPU 密集的工作负载在节流时引起的上下文切换。
考虑以下 Pod 规格的容器:
spec :
containers :
- name : nginx
image : nginx
该 Pod 属于 BestEffort
QoS 类型,因为其未指定 requests
或 limits
值。
所以该容器运行在共享 CPU 池中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
requests :
memory : "100Mi"
该 Pod 属于 Burstable
QoS 类型,因为其资源 requests
不等于 limits
,且未指定 cpu
数量。
所以该容器运行在共享 CPU 池中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "2"
requests :
memory : "100Mi"
cpu : "1"
该 Pod 属于 Burstable
QoS 类型,因为其资源 requests
不等于 limits
。
所以该容器运行在共享 CPU 池中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "2"
requests :
memory : "200Mi"
cpu : "2"
该 Pod 属于 Guaranteed
QoS 类型,因为其 requests
值与 limits
相等。
同时,容器对 CPU 资源的限制值是一个大于或等于 1 的整数值。
所以,该 nginx
容器被赋予 2 个独占 CPU。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "1.5"
requests :
memory : "200Mi"
cpu : "1.5"
该 Pod 属于 Guaranteed
QoS 类型,因为其 requests
值与 limits
相等。
但是容器对 CPU 资源的限制值是一个小数。所以该容器运行在共享 CPU 池中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "2"
该 Pod 属于 Guaranteed
QoS 类型,因其指定了 limits
值,同时当未显式指定时,
requests
值被设置为与 limits
值相等。
同时,容器对 CPU 资源的限制值是一个大于或等于 1 的整数值。
所以,该 nginx
容器被赋予 2 个独占 CPU。
Static 策略选项 你可以使用以下特性门控根据成熟度级别打开或关闭选项组:
CPUManagerPolicyBetaOptions
默认启用。禁用以隐藏 beta 级选项。CPUManagerPolicyAlphaOptions
默认禁用。启用以显示 alpha 级选项。
你仍然必须使用 CPUManagerPolicyOptions
kubelet 选项启用每个选项。静态 CPUManager
策略存在以下策略选项:
full-pcpus-only
(beta,默认可见)(1.22 或更高版本)distribute-cpus-across-numa
(alpha,默认隐藏)(1.23 或更高版本)align-by-socket
(alpha,默认隐藏)(1.25 或更高版本)如果使用 full-pcpus-only
策略选项,static 策略总是会分配完整的物理核心。
默认情况下,如果不使用该选项,static 策略会使用拓扑感知最适合的分配方法来分配 CPU。
在启用了 SMT 的系统上,此策略所分配是与硬件线程对应的、独立的虚拟核。
这会导致不同的容器共享相同的物理核心,
该行为进而会导致吵闹的邻居问题 。
启用该选项之后,只有当一个 Pod 里所有容器的 CPU 请求都能够分配到完整的物理核心时,
kubelet 才会接受该 Pod。
如果 Pod 没有被准入,它会被置于 Failed 状态,错误消息是 SMTAlignmentError
。
如果使用 distribute-cpus-across-numa
策略选项,
在需要多个 NUMA 节点来满足分配的情况下,
static 策略会在 NUMA 节点上平均分配 CPU。
默认情况下,CPUManager
会将 CPU 分配到一个 NUMA 节点上,直到它被填满,
剩余的 CPU 会简单地溢出到下一个 NUMA 节点。
这会导致依赖于同步屏障(以及类似的同步原语)的并行代码出现不期望的瓶颈,
因为此类代码的运行速度往往取决于最慢的工作线程
(由于至少一个 NUMA 节点存在可用 CPU 较少的情况,因此速度变慢)。
通过在 NUMA 节点上平均分配 CPU,
应用程序开发人员可以更轻松地确保没有某个工作线程单独受到 NUMA 影响,
从而提高这些类型应用程序的整体性能。
如果指定了 align-by-socket
策略选项,那么在决定如何分配 CPU 给容器时,CPU 将被视为在 CPU 的插槽边界对齐。
默认情况下,CPUManager
在 NUMA 边界对齐 CPU 分配,如果需要从多个 NUMA 节点提取出 CPU 以满足分配,将可能会导致系统性能下降。
尽管 align-by-socket
策略试图确保从 NUMA 节点的最小 数量分配所有 CPU,但不能保证这些 NUMA 节点将位于同一个 CPU 的插槽上。
通过指示 CPUManager
在 CPU 的插槽边界而不是 NUMA 边界显式对齐 CPU,我们能够避免此类问题。
注意,此策略选项不兼容 TopologyManager
与 single-numa-node
策略,并且不适用于 CPU 的插槽数量大于 NUMA 节点数量的硬件。
可以通过将 full-pcpus-only=true
添加到 CPUManager 策略选项来启用 full-pcpus-only
选项。
同样地,可以通过将 distribute-cpus-across-numa=true
添加到 CPUManager 策略选项来启用 distribute-cpus-across-numa
选项。
当两者都设置时,它们是“累加的”,因为 CPU 将分布在 NUMA 节点的 full-pcpus 块中,而不是单个核心。
可以通过将 align-by-socket=true
添加到 CPUManager
策略选项来启用 align-by-socket
策略选项。
同样,也能够将 distribute-cpus-across-numa=true
添加到 full-pcpus-only
和 distribute-cpus-across-numa
策略选项中。
4.2.18 - 控制节点上的拓扑管理策略 特性状态: Kubernetes v1.18 [beta]
越来越多的系统利用 CPU 和硬件加速器的组合来支持要求低延迟的任务和高吞吐量的并行计算。
这类负载包括电信、科学计算、机器学习、金融服务和数据分析等。
此类混合系统需要有高性能环境支持。
为了获得最佳性能,需要进行与 CPU 隔离、内存和设备局部性有关的优化。
但是,在 Kubernetes 中,这些优化由各自独立的组件集合来处理。
拓扑管理器(Topology Manager) 是一个 kubelet 组件,旨在协调负责这些优化的一组组件。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.18.
要获知版本信息,请输入
kubectl version
.
拓扑管理器如何工作 在引入拓扑管理器之前,Kubernetes 中的 CPU 和设备管理器相互独立地做出资源分配决策。
这可能会导致在多处理系统上出现不符合期望的资源分配情况;由于这些与期望相左的分配,对性能或延迟敏感的应用将受到影响。
这里的不符合期望意指,例如,CPU 和设备是从不同的 NUMA 节点分配的,因此会导致额外的延迟。
拓扑管理器是一个 Kubelet 组件,扮演信息源的角色,以便其他 Kubelet 组件可以做出与拓扑结构相对应的资源分配决定。
拓扑管理器为组件提供了一个称为 建议提供者(Hint Provider) 的接口,以发送和接收拓扑信息。
拓扑管理器具有一组节点级策略,具体说明如下。
拓扑管理器从 建议提供者 接收拓扑信息,作为表示可用的 NUMA 节点和首选分配指示的位掩码。
拓扑管理器策略对所提供的建议执行一组操作,并根据策略对提示进行约减以得到最优解;
如果存储了与预期不符的建议,则该建议的优选字段将被设置为 false。
在当前策略中,首选是最窄的优选掩码。
所选建议将被存储为拓扑管理器的一部分。
取决于所配置的策略,所选建议可用来决定节点接受或拒绝 Pod。
之后,建议会被存储在拓扑管理器中,供 建议提供者 在作资源分配决策时使用。
启用拓扑管理器功能特性 为了使用拓扑管理器,需要先启用 TopologyManager
特性门控 。
从 Kubernetes 1.18 版本开始,这一特性默认是启用的。
拓扑管理器作用域和策略 拓扑管理器目前:
对所有 QoS 类的 Pod 执行对齐操作。 针对建议提供者所提供的拓扑建议,对请求的资源进行对齐。 如果满足这些条件,则拓扑管理器将对齐请求的资源。
为了定制如何进行对齐,拓扑管理器提供了两种不同的方式:scope
和 policy
。
scope
定义了你希望的资源对齐粒度(例如,是在 pod
还是 container
层级上对齐)。
policy
定义了对齐时实际使用的策略(例如,best-effort
、restricted
、single-numa-node
等等)。
可以在下文找到现今可用的各种 scopes
和 policies
的具体信息。
说明: 为了将 Pod 规约中的 CPU 资源与其他请求资源对齐,需要启用 CPU
管理器并在节点上配置适当的 CPU 管理器策略。
参看
控制 CPU 管理策略 .
说明: 为了将 Pod 规约中的内存(和 hugepages)资源与所请求的其他资源对齐,需要启用内存管理器,
并且在节点配置适当的内存管理器策略。
查看
内存管理器 文档。
拓扑管理器作用域 拓扑管理器可以在以下不同的作用域内进行资源对齐:
在 kubelet 启动时,可以使用 --topology-manager-scope
标志来选择其中任一选项。
容器作用域 默认使用的是 container
作用域。
在该作用域内,拓扑管理器依次进行一系列的资源对齐,
也就是,对(Pod 中的)每一个容器计算单独的对齐。
换句话说,在该特定的作用域内,没有根据特定的 NUMA 节点集来把容器分组的概念。
实际上,拓扑管理器会把单个容器任意地对齐到 NUMA 节点上。
容器分组的概念是在以下的作用域内特别实现的,也就是 pod
作用域。
Pod 作用域 启动 kubelet 时附带 --topology-manager-scope=pod
命令行选项,就可以选择 pod
作用域。
该作用域允许把一个 Pod 里的所有容器作为一个分组,分配到一个共同的 NUMA 节点集。
也就是,拓扑管理器会把一个 Pod 当成一个整体,
并且试图把整个 Pod(所有容器)分配到一个单个的 NUMA 节点或者一个共同的 NUMA 节点集。
以下的例子说明了拓扑管理器在不同的场景下使用的对齐方式:
所有容器可以被分配到一个单一的 NUMA 节点,实际上也是这样分配的; 所有容器可以被分配到一个共享的 NUMA 节点集,实际上也是这样分配的。 整个 Pod 所请求的某种资源总量是根据
有效 request/limit
公式来计算的,因此,对某一种资源而言,该总量等于以下数值中的最大值:
pod
作用域与 single-numa-node
拓扑管理器策略一起使用,
对于延时敏感的工作负载,或者对于进行 IPC 的高吞吐量应用程序,都是特别有价值的。
把这两个选项组合起来,你可以把一个 Pod 里的所有容器都放到一个单个的 NUMA 节点,
使得该 Pod 消除了 NUMA 之间的通信开销。
在 single-numa-node
策略下,只有当可能的分配方案中存在合适的 NUMA 节点集时,Pod 才会被接受。
重新考虑上述的例子:
节点集只包含单个 NUMA 节点时,Pod 就会被接受, 然而,节点集包含多个 NUMA 节点时,Pod 就会被拒绝
(因为满足该分配方案需要两个或以上的 NUMA 节点,而不是单个 NUMA 节点)。 简要地说,拓扑管理器首先计算出 NUMA 节点集,然后使用拓扑管理器策略来测试该集合,
从而决定拒绝或者接受 Pod。
拓扑管理器策略 拓扑管理器支持四种分配策略。
你可以通过 Kubelet 标志 --topology-manager-policy
设置策略。
所支持的策略有四种:
none
(默认)best-effort
restricted
single-numa-node
说明: 如果拓扑管理器配置使用 pod 作用域,
那么在策略评估一个容器时,该容器反映的是整个 Pod 的要求,
所以该 Pod 里的每个容器都会应用 相同的 拓扑对齐决策。
none 策略 这是默认策略,不执行任何拓扑对齐。
best-effort 策略 对于 Pod 中的每个容器,具有 best-effort
拓扑管理策略的
kubelet 将调用每个建议提供者以确定资源可用性。
使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。
如果亲和性不是首选,则拓扑管理器将存储该亲和性,并且无论如何都将 Pod 接纳到该节点。
之后 建议提供者 可以在进行资源分配决策时使用这个信息。
restricted 策略 对于 Pod 中的每个容器,配置了 restricted
拓扑管理策略的 kubelet
调用每个建议提供者以确定其资源可用性。
使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。
如果亲和性不是首选,则拓扑管理器将从节点中拒绝此 Pod。
这将导致 Pod 处于 Terminated
状态,且 Pod 无法被节点接受。
一旦 Pod 处于 Terminated
状态,Kubernetes 调度器将不会 尝试重新调度该 Pod。
建议使用 ReplicaSet 或者 Deployment 来触发重新部署 Pod。
还可以通过实现外部控制环,以触发重新部署具有 Topology Affinity
错误的 Pod。
如果 Pod 被允许运行在某节点,则 建议提供者 可以在做出资源分配决定时使用此信息。
single-numa-node 策略 对于 Pod 中的每个容器,配置了 single-numa-nodde
拓扑管理策略的
kubelet 调用每个建议提供者以确定其资源可用性。
使用此信息,拓扑管理器确定是否支持单 NUMA 节点亲和性。
如果支持,则拓扑管理器将存储此信息,然后 建议提供者 可以在做出资源分配决定时使用此信息。
如果不支持,则拓扑管理器将拒绝 Pod 运行于该节点。
这将导致 Pod 处于 Terminated
状态,且 Pod 无法被节点接受。
一旦 Pod 处于 Terminated
状态,Kubernetes 调度器将不会尝试重新调度该 Pod。
建议使用多副本的 Deployment 来触发重新部署 Pod。
还可以通过实现外部控制环,以触发重新部署具有 Topology Affinity
错误的 Pod。
Pod 与拓扑管理器策略的交互 考虑以下 Pod 规范中的容器:
spec :
containers :
- name : nginx
image : nginx
该 Pod 以 BestEffort
QoS 类运行,因为没有指定资源 requests
或 limits
。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
requests :
memory : "100Mi"
由于 requests
数少于 limits
,因此该 Pod 以 Burstable
QoS 类运行。
如果选择的策略是 none
以外的任何其他策略,拓扑管理器都会评估这些 Pod 的规范。
拓扑管理器会咨询建议提供者,获得拓扑建议。
若策略为 static
,则 CPU 管理器策略会返回默认的拓扑建议,因为这些 Pod
并没有显式地请求 CPU 资源。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "2"
example.com/device : "1"
requests :
memory : "200Mi"
cpu : "2"
example.com/device : "1"
此 Pod 独立使用 CPU 请求量,以 Guaranteed
QoS 类运行,因为其 requests
值等于 limits
值。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "300m"
example.com/device : "1"
requests :
memory : "200Mi"
cpu : "300m"
example.com/device : "1"
此 Pod 和其他资源共享 CPU 请求量,以 Guaranteed
QoS 类运行,因为其 requests
值等于 limits
值。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
example.com/deviceA : "1"
example.com/deviceB : "1"
requests :
example.com/deviceA : "1"
example.com/deviceB : "1"
因为未指定 CPU 和内存请求,所以 Pod 以 BestEffort
QoS 类运行。
拓扑管理器将考虑以上 Pod。拓扑管理器将咨询建议提供者即 CPU 和设备管理器,以获取 Pod 的拓扑提示。
对于独立使用 CPU 请求量的 Guaranteed
Pod,static
CPU 管理器策略将返回独占 CPU 相关的拓扑提示,
而设备管理器将返回有关所请求设备的提示。
对于与其他资源 CPU 共享请求量的 Guaranteed
Pod,static
CPU
管理器策略将返回默认的拓扑提示,因为没有独享的 CPU 请求;而设备管理器
则针对所请求的设备返回有关提示。
在上述两种 Guaranteed
Pod 的情况中,none
CPU 管理器策略会返回默认的拓扑提示。
对于 BestEffort
Pod,由于没有 CPU 请求,static
CPU 管理器策略将发送默认拓扑提示,
而设备管理器将为每个请求的设备发送提示。
基于此信息,拓扑管理器将为 Pod 计算最佳提示并存储该信息,并且供
提示提供程序在进行资源分配时使用。
已知的局限性 拓扑管理器所能处理的最大 NUMA 节点个数是 8。若 NUMA 节点数超过 8,
枚举可能的 NUMA 亲和性并为之生成提示时会发生状态爆炸。 调度器无法感知拓扑,所以有可能一个 Pod 被调度到一个节点之后,会因为拓扑管理器的缘故在该节点上启动失败。 4.2.19 - 名字空间演练 Kubernetes 名字空间
有助于不同的项目、团队或客户去共享 Kubernetes 集群。
名字空间通过以下方式实现这点:
为名字 设置作用域. 为集群中的部分资源关联鉴权和策略的机制。 使用多个名字空间是可选的。
此示例演示了如何使用 Kubernetes 名字空间细分集群。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
环境准备 此示例作如下假设:
你已拥有一个配置好的 Kubernetes 集群 。 你已对 Kubernetes 的 Pods 、
Services 和
Deployments
有基本理解。 理解默认名字空间 默认情况下,Kubernetes 集群会在配置集群时实例化一个默认名字空间,用以存放集群所使用的默认
Pod、Service 和 Deployment 集合。
假设你有一个新的集群,你可以通过执行以下操作来检查可用的名字空间:
NAME STATUS AGE
default Active 13m
创建新的名字空间 在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。
我们假设一个场景,某组织正在使用共享的 Kubernetes 集群来支持开发和生产:
开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pod、Service
和 Deployment 列表。在这个空间里,Kubernetes 资源被自由地加入或移除,
对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。
运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程,
对谁可以或谁不可以操作运行生产站点的 Pod、Service 和 Deployment 集合进行控制。
该组织可以遵循的一种模式是将 Kubernetes 集群划分为两个名字空间:development
和 production
。
让我们创建两个新的名字空间来保存我们的工作。
文件 namespace-dev.yaml
描述了 development
名字空间:
apiVersion : v1
kind : Namespace
metadata :
name : development
labels :
name : development
使用 kubectl 创建 development
名字空间。
kubectl create -f https://k8s.io/examples/admin/namespace-dev.yaml
将下列的内容保存到文件 namespace-prod.yaml
中,
这些内容是对 production
名字空间的描述:
apiVersion : v1
kind : Namespace
metadata :
name : production
labels :
name : production
让我们使用 kubectl 创建 production
名字空间。
kubectl create -f https://k8s.io/examples/admin/namespace-prod.yaml
为了确保一切正常,我们列出集群中的所有名字空间。
kubectl get namespaces --show-labels
NAME STATUS AGE LABELS
default Active 32m <none>
development Active 29s name=development
production Active 23s name=production
在每个名字空间中创建 pod Kubernetes 名字空间为集群中的 Pod、Service 和 Deployment 提供了作用域。
与一个名字空间交互的用户不会看到另一个名字空间中的内容。
为了演示这一点,让我们在 development 名字空间中启动一个简单的 Deployment 和 Pod。
我们首先检查一下当前的上下文:
apiVersion : v1
clusters :
- cluster :
certificate-authority-data : REDACTED
server : https://130.211.122.180
name : lithe-cocoa-92103_kubernetes
contexts :
- context :
cluster : lithe-cocoa-92103_kubernetes
user : lithe-cocoa-92103_kubernetes
name : lithe-cocoa-92103_kubernetes
current-context : lithe-cocoa-92103_kubernetes
kind : Config
preferences : {}
users :
- name : lithe-cocoa-92103_kubernetes
user :
client-certificate-data : REDACTED
client-key-data : REDACTED
token : 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name : lithe-cocoa-92103_kubernetes-basic-auth
user :
password : h5M0FtUUIflBSdI7
username : admin
kubectl config current-context
lithe-cocoa-92103_kubernetes
下一步是为 kubectl 客户端定义一个上下文,以便在每个名字空间中工作。
"cluster" 和 "user" 字段的值将从当前上下文中复制。
kubectl config set-context dev --namespace= development \
--cluster= lithe-cocoa-92103_kubernetes \
--user= lithe-cocoa-92103_kubernetes
kubectl config set-context prod --namespace= production \
--cluster= lithe-cocoa-92103_kubernetes \
--user= lithe-cocoa-92103_kubernetes
默认情况下,上述命令会添加两个上下文到 .kube/config
文件中。
你现在可以查看上下文并根据你希望使用的名字空间并在这两个新的请求上下文之间切换。
查看新的上下文:
apiVersion : v1
clusters :
- cluster :
certificate-authority-data : REDACTED
server : https://130.211.122.180
name : lithe-cocoa-92103_kubernetes
contexts :
- context :
cluster : lithe-cocoa-92103_kubernetes
user : lithe-cocoa-92103_kubernetes
name : lithe-cocoa-92103_kubernetes
- context :
cluster : lithe-cocoa-92103_kubernetes
namespace : development
user : lithe-cocoa-92103_kubernetes
name : dev
- context :
cluster : lithe-cocoa-92103_kubernetes
namespace : production
user : lithe-cocoa-92103_kubernetes
name : prod
current-context : lithe-cocoa-92103_kubernetes
kind : Config
preferences : {}
users :
- name : lithe-cocoa-92103_kubernetes
user :
client-certificate-data : REDACTED
client-key-data : REDACTED
token : 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
- name : lithe-cocoa-92103_kubernetes-basic-auth
user :
password : h5M0FtUUIflBSdI7
username : admin
让我们切换到 development
名字空间进行操作。
kubectl config use-context dev
你可以使用下列命令验证当前上下文:
kubectl config current-context
dev
此时,我们从命令行向 Kubernetes 集群发出的所有请求都限定在 development
名字空间中。
让我们创建一些内容。
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : snowflake
name : snowflake
spec :
replicas : 2
selector :
matchLabels :
app : snowflake
template :
metadata :
labels :
app : snowflake
spec :
containers :
- image : registry.k8s.io/serve_hostname
imagePullPolicy : Always
name : snowflake
应用清单文件来创建 Deployment。
我们创建了一个副本大小为 2 的 Deployment,该 Deployment 运行名为 snowflake
的 Pod,
其中包含一个仅提供主机名服务的基本容器。
NAME READY UP-TO-DATE AVAILABLE AGE
snowflake 2/2 2 2 2m
kubectl get pods -l app = snowflake
NAME READY STATUS RESTARTS AGE
snowflake-3968820950-9dgr8 1/1 Running 0 2m
snowflake-3968820950-vgc4n 1/1 Running 0 2m
这很棒,开发人员可以做他们想要的事情,而不必担心影响 production
名字空间中的内容。
让我们切换到 production
名字空间,展示一个名字空间中的资源如何对另一个名字空间不可见。
kubectl config use-context prod
production
名字空间应该是空的,下列命令应该返回的内容为空。
kubectl get deployment
kubectl get pods
生产环境需要以放牛的方式运维,让我们创建一些名为 cattle
的 Pod。
kubectl create deployment cattle --image= registry.k8s.io/serve_hostname --replicas= 5
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
cattle 5/5 5 5 10s
kubectl get pods -l run = cattle
NAME READY STATUS RESTARTS AGE
cattle-2263376956-41xy6 1/1 Running 0 34s
cattle-2263376956-kw466 1/1 Running 0 34s
cattle-2263376956-n4v97 1/1 Running 0 34s
cattle-2263376956-p5p3i 1/1 Running 0 34s
cattle-2263376956-sxpth 1/1 Running 0 34s
此时,应该很清楚的展示了用户在一个名字空间中创建的资源对另一个名字空间是不可见的。
随着 Kubernetes 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。
4.2.20 - 配置 API 对象配额 本文讨论如何为 API 对象配置配额,包括 PersistentVolumeClaim 和 Service。
配额限制了可以在命名空间中创建的特定类型对象的数量。
你可以在 ResourceQuota 对象中指定配额。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建命名空间 创建一个命名空间以便本例中创建的资源和集群中的其余部分相隔离。
kubectl create namespace quota-object-example
创建 ResourceQuota 下面是一个 ResourceQuota 对象的配置文件:
apiVersion : v1
kind : ResourceQuota
metadata :
name : object-quota-demo
spec :
hard :
persistentvolumeclaims : "1"
services.loadbalancers : "2"
services.nodeports : "0"
创建 ResourceQuota:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects.yaml --namespace= quota-object-example
查看 ResourceQuota 的详细信息:
kubectl get resourcequota object-quota-demo --namespace= quota-object-example --output= yaml
输出结果表明在 quota-object-example 命名空间中,至多只能有一个 PersistentVolumeClaim,
最多两个 LoadBalancer 类型的服务,不能有 NodePort 类型的服务。
status :
hard :
persistentvolumeclaims : "1"
services.loadbalancers : "2"
services.nodeports : "0"
used :
persistentvolumeclaims : "0"
services.loadbalancers : "0"
services.nodeports : "0"
创建 PersistentVolumeClaim 下面是一个 PersistentVolumeClaim 对象的配置文件:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : pvc-quota-demo
spec :
storageClassName : manual
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 3Gi
创建 PersistentVolumeClaim:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects-pvc.yaml --namespace= quota-object-example
确认已创建完 PersistentVolumeClaim:
kubectl get persistentvolumeclaims --namespace= quota-object-example
输出信息表明 PersistentVolumeClaim 存在并且处于 Pending 状态:
NAME STATUS
pvc-quota-demo Pending
尝试创建第二个 PersistentVolumeClaim 下面是第二个 PersistentVolumeClaim 的配置文件:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : pvc-quota-demo-2
spec :
storageClassName : manual
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 4Gi
尝试创建第二个 PersistentVolumeClaim:
kubectl apply -f https://k8s.io/examples/admin/resource/quota-objects-pvc-2.yaml --namespace= quota-object-example
输出信息表明第二个 PersistentVolumeClaim 没有创建成功,因为这会超出命名空间的配额。
persistentvolumeclaims "pvc-quota-demo-2" is forbidden:
exceeded quota: object-quota-demo, requested: persistentvolumeclaims=1,
used: persistentvolumeclaims=1, limited: persistentvolumeclaims=1
说明 下面这些字符串可被用来标识那些能被配额限制的 API 资源:
字符串 API 对象 "pods" Pod "services" Service "replicationcontrollers" ReplicationController "resourcequotas" ResourceQuota "secrets" Secret "configmaps" ConfigMap "persistentvolumeclaims" PersistentVolumeClaim "services.nodeports" NodePort 类型的 Service "services.loadbalancers" LoadBalancer 类型的 Service
清理 删除你的命名空间:
kubectl delete namespace quota-object-example
接下来 集群管理员参考 应用开发者参考 4.2.21 - 启用/禁用 Kubernetes API 本页展示怎么用集群的
控制平面 .
启用/禁用 API 版本。
通过 API 服务器的命令行参数 --runtime-config=api/<version>
,
可以开启/关闭某个指定的 API 版本。
此参数的值是一个逗号分隔的 API 版本列表。
此列表中,后面的值可以覆盖前面的值。
命令行参数 runtime-config
支持两个特殊的值(keys):
api/all
:指所有已知的 APIapi/legacy
:指过时的 API。过时的 API 就是明确地
弃用
的 API。例如:为了停用除去 v1 版本之外的全部其他 API 版本,
就用参数 --runtime-config=api/all=false,api/v1=true
启动 kube-apiserver
。
接下来 阅读完整的文档 ,
以了解 kube-apiserver
组件。
4.2.22 - 迁移多副本的控制面以使用云控制器管理器 特性状态: Kubernetes v1.24 [stable]
一个 Kubernetes 控制平面 组件,
嵌入了特定于云平台的控制逻辑。
云控制器管理器(Cloud Controller Manager)允许你将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
cloud-controller-manager
组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
背景 作为云驱动提取工作
的一部分,所有特定于云的控制器都必须移出 kube-controller-manager
。
所有在 kube-controller-manager
中运行云控制器的现有集群必须迁移到特定于云厂商的
cloud-controller-manager
中运行这些控制器。
领导者迁移(Leader Migration)提供了一种机制,使得 HA 集群可以通过这两个组件之间共享资源锁,
在升级多副本的控制平面时,安全地将“特定于云”的控制器从 kube-controller-manager
迁移到
cloud-controller-manager
。
对于单节点控制平面,或者在升级过程中可以容忍控制器管理器不可用的情况,则不需要领导者迁移,
亦可以忽略本指南。
领导者迁移可以通过在 kube-controller-manager
或 cloud-controller-manager
上设置
--enable-leader-migration
来启用。
领导者迁移仅在升级期间适用,并且在升级完成后可以安全地禁用或保持启用状态。
本指南将引导你手动将控制平面从内置的云驱动的 kube-controller-manager
升级为
同时运行 kube-controller-manager
和 cloud-controller-manager
。
如果使用某种工具来部署和管理集群,请参阅对应工具和云驱动的文档以获取迁移的具体说明。
准备开始 假定控制平面正在运行 Kubernetes 版本 N,要升级到版本 N+1。
尽管可以在同一版本内进行迁移,但理想情况下,迁移应作为升级的一部分执行,
以便可以配置的变更可以与发布版本变化对应起来。
N 和 N+1 的确切版本值取决于各个云厂商。例如,如果云厂商构建了一个可与 Kubernetes 1.24
配合使用的 cloud-controller-manager
,则 N 可以为 1.23,N+1 可以为 1.24。
控制平面节点应运行 kube-controller-manager
并启用领导者选举,这也是默认设置。
在版本 N 中,树内云驱动必须设置 --cloud-provider
标志,而且 cloud-controller-manager
应该尚未部署。
树外云驱动必须已经构建了一个实现了领导者迁移的 cloud-controller-manager
。
如果云驱动导入了 v0.21.0 或更高版本的 k8s.io/cloud-provider
和 k8s.io/controller-manager
,
则可以进行领导者迁移。
但是,对 v0.22.0 以下的版本,领导者迁移是一项 Alpha 阶段功能,需要在 cloud-controller-manager
中启用特性门控 ControllerManagerLeaderMigration
。
本指南假定每个控制平面节点的 kubelet 以静态 Pod 的形式启动 kube-controller-manager
和 cloud-controller-manager
,静态 Pod 的定义在清单文件中。
如果组件以其他设置运行,请相应地调整这里的步骤。
关于鉴权,本指南假定集群使用 RBAC。如果其他鉴权模式授予 kube-controller-manager
和 cloud-controller-manager
组件权限,请以与该模式匹配的方式授予所需的访问权限。
授予访问迁移租约的权限 控制器管理器的默认权限仅允许访问其主租约(Lease)对象。为了使迁移正常进行,
需要授权它访问其他 Lease 对象。
你可以通过修改 system::leader-locking-kube-controller-manager
角色来授予
kube-controller-manager
对 Lease API 的完全访问权限。
本任务指南假定迁移 Lease 的名称为 cloud-provider-extraction-migration
。
kubectl patch -n kube-system role 'system::leader-locking-kube-controller-manager' -p '{"rules": [ {"apiGroups":[ "coordination.k8s.io"], "resources": ["leases"], "resourceNames": ["cloud-provider-extraction-migration"], "verbs": ["create", "list", "get", "update"] } ]}' --type=merge
对 system::leader-locking-cloud-controller-manager
角色执行相同的操作。
kubectl patch -n kube-system role 'system::leader-locking-cloud-controller-manager' -p '{"rules": [ {"apiGroups":[ "coordination.k8s.io"], "resources": ["leases"], "resourceNames": ["cloud-provider-extraction-migration"], "verbs": ["create", "list", "get", "update"] } ]}' --type=merge
初始领导者迁移配置 领导者迁移可以选择使用一个表示如何将控制器分配给不同管理器的配置文件。
目前,对于树内云驱动,kube-controller-manager
运行 route
、service
和
cloud-node-lifecycle
。以下示例配置显示的是这种分配。
领导者迁移可以不指定配置的情况下启用。请参阅默认配置
以获取更多详细信息。
kind : LeaderMigrationConfiguration
apiVersion : controllermanager.config.k8s.io/v1
leaderName : cloud-provider-extraction-migration
controllerLeaders :
- name : route
component : kube-controller-manager
- name : service
component : kube-controller-manager
- name : cloud-node-lifecycle
component : kube-controller-manager
或者,由于控制器可以在任一控制器管理器下运行,因此将双方的 component
设置为 *
可以使迁移双方的配置文件保持一致。
# 通配符版本
kind : LeaderMigrationConfiguration
apiVersion : controllermanager.config.k8s.io/v1
leaderName : cloud-provider-extraction-migration
controllerLeaders :
- name : route
component : *
- name : service
component : *
- name : cloud-node-lifecycle
component : *
在每个控制平面节点上,请将如上内容保存到 /etc/leadermigration.conf
中,
并更新 kube-controller-manager
清单,以便将文件挂载到容器内的同一位置。
另外,请更新同一清单,添加以下参数:
--enable-leader-migration
在控制器管理器上启用领导者迁移--leader-migration-config=/etc/leadermigration.conf
设置配置文件在每个节点上重新启动 kube-controller-manager
。这时,kube-controller-manager
已启用领导者迁移,为迁移准备就绪。
部署云控制器管理器 在版本 N+1 中,如何将控制器分配给不同管理器的预期分配状态可以由新的配置文件表示,
如下所示。请注意,各个 controllerLeaders
的 component
字段从 kube-controller-manager
更改为 cloud-controller-manager
。
或者,使用上面提到的通配符版本,它具有相同的效果。
kind : LeaderMigrationConfiguration
apiVersion : controllermanager.config.k8s.io/v1
leaderName : cloud-provider-extraction-migration
controllerLeaders :
- name : route
component : cloud-controller-manager
- name : service
component : cloud-controller-manager
- name : cloud-node-lifecycle
component : cloud-controller-manager
当创建版本 N+1 的控制平面节点时,应将如上内容写入到 /etc/leadermigration.conf
。
你需要更新 cloud-controller-manager
的清单,以与版本 N 的 kube-controller-manager
相同的方式挂载配置文件。
类似地,添加 --enable-leader-migration
和 --leader-migration-config=/etc/leadermigration.conf
到 cloud-controller-manager
的参数中。
使用已更新的 cloud-controller-manager
清单创建一个新的 N+1 版本的控制平面节点,
同时设置 kube-controller-manager
的 --cloud-provider
标志为 external
。
版本为 N+1 的 kube-controller-manager
不能启用领导者迁移,
因为在使用外部云驱动的情况下,它不再运行已迁移的控制器,因此不参与迁移。
请参阅云控制器管理器管理
了解有关如何部署 cloud-controller-manager
的更多细节。
升级控制平面 现在,控制平面同时包含 N 和 N+1 版本的节点。
版本 N 的节点仅运行 kube-controller-manager
,而版本 N+1 的节点同时运行
kube-controller-manager
和 cloud-controller-manager
。
根据配置所指定,已迁移的控制器在版本 N 的 kube-controller-manager
或版本
N+1 的 cloud-controller-manager
下运行,具体取决于哪个控制器管理器拥有迁移租约对象。
任何时候都不会有同一个控制器在两个控制器管理器下运行。
以滚动的方式创建一个新的版本为 N+1 的控制平面节点,并将版本 N 中的一个关闭,
直到控制平面仅包含版本为 N+1 的节点。
如果需要从 N+1 版本回滚到 N 版本,则将 kube-controller-manager
启用了领导者迁移的、
且版本为 N 的节点添加回控制平面,每次替换 N+1 版本中的一个,直到只有版本 N 的节点为止。
(可选)禁用领导者迁移 现在,控制平面已经完成升级,同时运行版本 N+1 的 kube-controller-manager
和 cloud-controller-manager
。领导者迁移的任务已经结束,可以被安全地禁用以节省一个
Lease 资源。在将来可以安全地重新启用领导者迁移,以完成回滚。
在滚动管理器中,更新 cloud-controller-manager
的清单以同时取消设置
--enable-leader-migration
和 --leader-migration-config=
标志,并删除
/etc/leadermigration.conf
的挂载,最后删除 /etc/leadermigration.conf
。
要重新启用领导者迁移,请重新创建配置文件,并将其挂载和启用领导者迁移的标志添加回到
cloud-controller-manager
。
默认配置 从 Kubernetes 1.22 开始,领导者迁移提供了一个默认配置,它适用于控制器与管理器间默认的分配关系。
可以通过设置 --enable-leader-migration
,但不设置 --leader-migration-config=
来启用默认配置。
对于 kube-controller-manager
和 cloud-controller-manager
,如果没有用参数来启用树内云驱动或者改变控制器属主,
则可以使用默认配置来避免手动创建配置文件。
特殊情况:迁移节点 IPAM 控制器 如果你的云供应商提供了节点 IPAM 控制器的实现,你应该切换到 cloud-controller-manager
中的实现。
通过在其标志中添加 --controllers=*,-nodeipam
来禁用 N+1 版本的 kube-controller-manager
中的节点 IPAM 控制器。
然后将 nodeipam
添加到迁移的控制器列表中。
# 通配符版本,带有 nodeipam
kind : LeaderMigrationConfiguration
apiVersion : controllermanager.config.k8s.io/v1
leaderName : cloud-provider-extraction-migration
controllerLeaders :
- name : route
component : *
- name : service
component : *
- name : cloud-node-lifecycle
component : *
- name : nodeipam
- component : *
接下来 4.2.23 - 升级集群 本页概述升级 Kubernetes 集群的步骤。
升级集群的方式取决于你最初部署它的方式、以及后续更改它的方式。
从高层规划的角度看,要执行的步骤是:
升级控制平面 升级集群中的节点 升级 kubectl 之类的客户端 根据新 Kubernetes 版本带来的 API 变化,调整清单文件和其他资源 准备开始 你必须有一个集群。
本页内容涉及从 Kubernetes 1.24
升级到 Kubernetes 1.25。
如果你的集群未运行 Kubernetes 1.24,
那请参考目标 Kubernetes 版本的文档。
升级方法 kubeadm 如果你的集群是使用 kubeadm
安装工具部署而来,
那么升级集群的详细信息,请参阅
升级 kubeadm 集群 。
升级集群之后,要记得
安装最新版本的 kubectl
.
手动部署 注意: 这些步骤不考虑第三方扩展,例如网络和存储插件。
你应该跟随下面操作顺序,手动更新控制平面:
etcd (所有实例) kube-apiserver (所有控制平面的宿主机) kube-controller-manager kube-scheduler cloud controller manager (在你用到时) 现在,你应该
安装最新版本的 kubectl
.
对于集群中的每个节点,
首先需要腾空
节点,然后使用一个运行了 kubelet 1.25 版本的新节点替换它;
或者升级此节点的 kubelet,并使节点恢复服务。
其他部署方式 参阅你的集群部署工具对应的文档,了解用于维护的推荐设置步骤。
升级后的任务 切换集群的存储 API 版本 对象序列化到 etcd,是为了提供集群中活动 Kubernetes 资源的内部表示法,
这些对象都使用特定版本的 API 编写。
当底层的 API 更改时,这些对象可能需要用新 API 重写。
如果不能做到这一点,会导致再也不能用 Kubernetes API 服务器解码、使用该对象。
对于每个受影响的对象,请使用最新支持的 API 读取它,然后使用所支持的最新 API 将其写回。
更新清单 升级到新版本 Kubernetes 就可以获取到新的 API。
你可以使用 kubectl convert
命令在不同 API 版本之间转换清单。
例如:
kubectl convert -f pod.yaml --output-version v1
kubectl
替换了 pod.yaml
的内容,
在新的清单文件中,kind
被设置为 Pod(未变),
但 apiVersion
则被修订了。
4.2.24 - 声明网络策略 本文可以帮助你开始使用 Kubernetes 的
NetworkPolicy API
声明网络策略去管理 Pod 之间的通信
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.8.
要获知版本信息,请输入
kubectl version
.
你首先需要有一个支持网络策略的 Kubernetes 集群。已经有许多支持 NetworkPolicy 的网络提供商,包括:
创建一个nginx
Deployment 并且通过服务将其暴露 为了查看 Kubernetes 网络策略是怎样工作的,可以从创建一个nginx
Deployment 并且通过服务将其暴露开始
kubectl create deployment nginx --image= nginx
deployment.apps/nginx created
将此 Deployment 以名为 nginx
的 Service 暴露出来:
kubectl expose deployment nginx --port= 80
service/nginx exposed
上述命令创建了一个带有一个 nginx 的 Deployment,并将之通过名为 nginx
的
Service 暴露出来。名为 nginx
的 Pod 和 Deployment 都位于 default
名字空间内。
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/kubernetes 10.100.0.1 <none> 443/TCP 46m
svc/nginx 10.100.0.16 <none> 80/TCP 33s
NAME READY STATUS RESTARTS AGE
po/nginx-701339712-e0qfq 1/1 Running 0 35s
通过从 Pod 访问服务对其进行测试 你应该可以从其它的 Pod 访问这个新的 nginx
服务。
要从 default 命名空间中的其它 Pod 来访问该服务。可以启动一个 busybox 容器:
kubectl run busybox --rm -ti --image= busybox:1.28 /bin/sh
在你的 Shell 中,运行下面的命令:
wget --spider --timeout= 1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists
限制 nginx
服务的访问 如果想限制对 nginx
服务的访问,只让那些拥有标签 access: true
的 Pod 访问它,
那么可以创建一个如下所示的 NetworkPolicy 对象:
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : access-nginx
spec :
podSelector :
matchLabels :
app : nginx
ingress :
- from :
- podSelector :
matchLabels :
access : "true"
NetworkPolicy 对象的名称必须是一个合法的
DNS 子域名 .
说明: NetworkPolicy 中包含选择策略所适用的 Pods 集合的 podSelector
。
你可以看到上面的策略选择的是带有标签 app=nginx
的 Pods。
此标签是被自动添加到 nginx
Deployment 中的 Pod 上的。
如果 podSelector
为空,则意味着选择的是名字空间中的所有 Pods。
为服务指定策略 使用 kubectl 根据上面的 nginx-policy.yaml
文件创建一个 NetworkPolicy:
kubectl apply -f https://k8s.io/examples/service/networking/nginx-policy.yaml
networkpolicy.networking.k8s.io/access-nginx created
测试没有定义访问标签时访问服务 如果你尝试从没有设定正确标签的 Pod 中去访问 nginx
服务,请求将会超时:
kubectl run busybox --rm -ti --image= busybox:1.28 -- /bin/sh
在 Shell 中运行命令:
wget --spider --timeout= 1 nginx
Connecting to nginx (10.100.0.16:80)
wget: download timed out
定义访问标签后再次测试 创建一个拥有正确标签的 Pod,你将看到请求是被允许的:
kubectl run busybox --rm -ti --labels= "access=true" --image= busybox:1.28 -- /bin/sh
在 Shell 中运行命令:
wget --spider --timeout= 1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists
4.2.25 - 使用 CoreDNS 进行服务发现 此页面介绍了 CoreDNS 升级过程以及如何安装 CoreDNS 而不是 kube-dns。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.9.
要获知版本信息,请输入
kubectl version
.
关于 CoreDNS CoreDNS 是一个灵活可扩展的 DNS 服务器,可以作为 Kubernetes 集群 DNS。
与 Kubernetes 一样,CoreDNS 项目由 CNCF 托管。
通过替换现有集群部署中的 kube-dns,或者使用 kubeadm 等工具来为你部署和升级集群,
可以在你的集群中使用 CoreDNS 而非 kube-dns。
安装 CoreDNS 有关手动部署或替换 kube-dns,请参阅
CoreDNS GitHub 项目 。
迁移到 CoreDNS 使用 kubeadm 升级现有集群 在 Kubernetes 1.21 版本中,kubeadm 移除了对将 kube-dns
作为 DNS 应用的支持。
对于 kubeadm
v1.25,所支持的唯一的集群 DNS 应用是 CoreDNS。
当你使用 kubeadm
升级使用 kube-dns
的集群时,你还可以执行到 CoreDNS 的迁移。
在这种场景中,kubeadm
将基于 kube-dns
ConfigMap 生成 CoreDNS 配置("Corefile"),
保存存根域和上游名称服务器的配置。
升级 CoreDNS 你可以在 CoreDNS version in Kubernetes
页面查看 kubeadm 为不同版本 Kubernetes 所安装的 CoreDNS 版本。
如果你只想升级 CoreDNS 或使用自己的定制镜像,也可以手动升级 CoreDNS。
参看指南和演练
文档了解如何平滑升级。
在升级你的集群过程中,请确保现有 CoreDNS 的配置("Corefile")被保留下来。
如果使用 kubeadm
工具来升级集群,则 kubeadm
可以自动处理保留现有 CoreDNS
配置这一事项。
CoreDNS 调优 当资源利用方面有问题时,优化 CoreDNS 的配置可能是有用的。
有关详细信息,请参阅有关扩缩 CoreDNS 的文档 。
接下来 你可以通过修改 CoreDNS 的配置("Corefile")来配置 CoreDNS ,
以支持比 kube-dns 更多的用例。
请参考 kubernetes
CoreDNS 插件的文档
或者 CoreDNS 博客上的博文
Custom DNS Entries for Kubernetes ,
以了解更多信息。
4.2.26 - 使用 KMS 驱动进行数据加密 本页展示了如何配置密钥管理服务(Key Management Service,KMS)驱动和插件以启用 Secret 数据加密。
目前有两个 KMS API 版本。KMS v1 将继续工作,而 KMS v2 将开发得逐渐成熟。
如果你不确定要选用哪个 KMS API 版本,可选择 v1。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你所需要的 Kubernetes 版本取决于你已选择的 KMS API 版本。
如果你选择了 KMS API v1,所有受支持的 Kubernetes 版本都可以正常工作。 如果你选择了 KMS API v2,则应使用 Kubernetes v1.25
(如果你正在运行也支持 KMS API v2 的其他 Kubernetes 版本,需查阅该 Kubernetes 版本的文档)。 要获知版本信息,请输入
kubectl version
.
KMS v1 需要 Kubernetes 1.10.0 或更高版本 你的集群必须使用 etcd v3 或更高版本 特性状态: Kubernetes v1.12 [beta]
KMS v2 特性状态: Kubernetes v1.25 [alpha]
KMS 加密驱动使用封套加密模型来加密 etcd 中的数据。
数据使用数据加密密钥(DEK)加密;每次加密都生成一个新的 DEK。
这些 DEK 经一个密钥加密密钥(KEK)加密后在一个远端的 KMS 中存储和管理。
KMS 驱动使用 gRPC 与一个特定的 KMS 插件通信。这个 KMS 插件作为一个 gRPC
服务器被部署在 Kubernetes 控制平面的相同主机上,负责与远端 KMS 的通信。
配置 KMS 驱动 为了在 API 服务器上配置 KMS 驱动,在加密配置文件中的 providers
数组中加入一个类型为 kms
的驱动,并设置下列属性:
KMS v1 name
: KMS 插件的显示名称。一旦设置,就无法更改。endpoint
: gRPC 服务器(KMS 插件)的监听地址。该端点是一个 UNIX 域套接字。cachesize
: 以明文缓存的数据加密密钥(DEK)的数量。一旦被缓存,
就可以直接使用 DEK 而无需另外调用 KMS;而未被缓存的 DEK 需要调用一次 KMS 才能解包。timeout
: 在返回一个错误之前,kube-apiserver
等待 kms-plugin 响应的时间(默认是 3 秒)。KMS v2 apiVersion
:针对 KMS 驱动的 API 版本(允许的值:v2、v1 或空值。任何其他值都将产生一个错误。)
必须设置为 v2 才能使用 KMS v2 API。name
:KMS 插件的显示名称。一旦设置,就无法更改。endpoint
:gRPC 服务器(KMS 插件)的监听地址。该端点是一个 UNIX 域套接字。cachesize
:以明文缓存的数据加密密钥(DEK)的数量。一旦被缓存,
就可以直接使用 DEK 而无需另外调用 KMS;而未被缓存的 DEK 需要调用一次 KMS 才能解包。timeout
:在返回一个错误之前,kube-apiserver
等待 kms-plugin 响应的时间(默认是 3 秒)。参见理解静态配置加密
实现 KMS 插件 为实现一个 KMS 插件,你可以开发一个新的插件 gRPC 服务器或启用一个由你的云服务驱动提供的 KMS 插件。
你可以将这个插件与远程 KMS 集成,并把它部署到 Kubernetes 的主服务器上。
启用由云服务驱动支持的 KMS 有关启用云服务驱动特定的 KMS 插件的说明,请咨询你的云服务驱动商。
开发 KMS 插件 gRPC 服务器 你可以使用 Go 语言的存根文件开发 KMS 插件 gRPC 服务器。
对于其他语言,你可以用 proto 文件创建可以用于开发 gRPC 服务器代码的存根文件。
KMS v1 使用 Go:使用存根文件 api.pb.go
中的函数和数据结构开发 gRPC 服务器代码。 使用 Go 以外的其他语言:用 protoc 编译器编译 proto 文件:
api.proto
为指定语言生成存根文件。 KMS v2 然后使用存根文件中的函数和数据结构开发服务器代码。
注意 KMS v1 KMS v2 将 KMS 插件与远程 KMS 整合 KMS 插件可以用任何受 KMS 支持的协议与远程 KMS 通信。
所有的配置数据,包括 KMS 插件用于与远程 KMS 通信的认证凭据,都由 KMS 插件独立地存储和管理。
KMS 插件可以用额外的元数据对密文进行编码,这些元数据是在把它发往 KMS 进行解密之前可能要用到的。
部署 KMS 插件 确保 KMS 插件与 Kubernetes 主服务器运行在同一主机上。
使用 KMS 驱动加密数据 为了加密数据:
使用适合于 kms
驱动的属性创建一个新的 EncryptionConfiguration
文件,以加密
Secret 和 ConfigMap 等资源。
设置 kube-apiserver 的 --encryption-provider-config
参数指向配置文件的位置。
重启你的 API 服务器。
KMS v1 apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- kms :
name : myKmsPluginFoo
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
timeout : 3s
- kms :
name : myKmsPluginBar
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
timeout : 3s
KMS v2 apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- kms :
apiVersion : v2
name : myKmsPluginFoo
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
timeout : 3s
- kms :
name : myKmsPluginBar
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
timeout : 3s
在执行确保所有 Secret 都加密 中所给步骤之前,
providers
列表应以 identity: {}
提供程序作为结尾,以允许读取未加密的数据。
加密所有资源后,应移除 identity
提供程序,以防止 API 服务器接受未加密的数据。
有关 EncryptionConfiguration
格式的更多详细信息,请参阅
kube-apiserver 加密 API 参考 (v1) .
验证数据已经加密 写入 etcd 时数据被加密。重启 kube-apiserver
后,任何新建或更新的 Secret 在存储时应该已被加密。
要验证这点,你可以用 etcdctl
命令行程序获取 Secret 内容。
在默认的命名空间里创建一个名为 secret1
的 Secret:
kubectl create secret generic secret1 -n default --from-literal= mykey = mydata
用 etcdctl
命令行,从 etcd 读取出 Secret:
ETCDCTL_API = 3 etcdctl get /kubernetes.io/secrets/default/secret1 [ ...] | hexdump -C
其中 [...]
包含连接 etcd 服务器的额外参数。
验证对于 KMS v1,保存的 Secret 以 k8s:enc:kms:v1:
开头,
对于 KMS v2,保存的 Secret 以 k8s:enc:kms:v2:
开头,这表明 kms
驱动已经对结果数据加密。 验证通过 API 获取的 Secret 已被正确解密:
kubectl describe secret secret1 -n default
结果应该是 mykey: mydata
。
确保所有 Secret 都已被加密 因为 Secret 是在写入时被加密的,所以在更新 Secret 时也会加密该内容。
下列命令读取所有 Secret 并更新它们以便应用服务器端加密。如果因为写入冲突导致错误发生,
请重试此命令。对较大的集群,你可能希望根据命名空间或脚本更新去细分 Secret 内容。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
从本地加密驱动切换到 KMS 驱动 为了从本地加密驱动切换到 kms
驱动并重新加密所有 Secret 内容:
在配置文件中加入 kms
驱动作为第一个条目,如下列样例所示
apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- kms :
name : myKmsPlugin
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
- aescbc :
keys :
- name : key1
secret : <BASE 64 ENCODED SECRET>
重启所有 kube-apiserver
进程。
运行下列命令使用 kms
驱动强制重新加密所有 Secret。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
禁用静态数据加密 要禁用静态数据加密:
将 identity
驱动作为配置文件中的第一个条目:
apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- identity : {}
- kms :
name : myKmsPlugin
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
重启所有 kube-apiserver
进程。
运行下列命令强制重新加密所有 Secret。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
4.2.27 - 使用 Kubernetes API 访问集群 本页展示了如何使用 Kubernetes API 访问集群
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
访问集群 API 使用 kubectl 进行首次访问 首次访问 Kubernetes API 时,请使用 Kubernetes 命令行工具 kubectl
。
要访问集群,你需要知道集群位置并拥有访问它的凭证。
通常,当你完成入门指南 时,这会自动设置完成,或者由其他人设置好集群并将凭证和位置提供给你。
使用此命令检查 kubectl 已知的位置和凭证:
许多样例
提供了使用 kubectl 的介绍。完整文档请见 kubectl 手册 。
直接访问 REST API kubectl 处理对 API 服务器的定位和身份验证。如果你想通过 http 客户端(如 curl
或 wget
,
或浏览器)直接访问 REST API,你可以通过多种方式对 API 服务器进行定位和身份验证:
以代理模式运行 kubectl(推荐)。
推荐使用此方法,因为它用存储的 apiserver 位置并使用自签名证书验证 API 服务器的标识。
使用这种方法无法进行中间人(MITM)攻击。 另外,你可以直接为 HTTP 客户端提供位置和身份认证。
这适用于被代理混淆的客户端代码。
为防止中间人攻击,你需要将根证书导入浏览器。 使用 Go 或 Python 客户端库可以在代理模式下访问 kubectl。
使用 kubectl 代理 下列命令使 kubectl 运行在反向代理模式下。它处理 API 服务器的定位和身份认证。
像这样运行它:
kubectl proxy --port= 8080 &
参见 kubectl 代理 获取更多细节。
然后你可以通过 curl,wget,或浏览器浏览 API,像这样:
curl http://localhost:8080/api/
输出类似如下:
{
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
不使用 kubectl 代理 通过将身份认证令牌直接传给 API 服务器,可以避免使用 kubectl 代理,像这样:
使用 grep/cut
方式:
# 查看所有的集群,因为你的 .kubeconfig 文件中可能包含多个上下文
kubectl config view -o jsonpath = '{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
# 从上述命令输出中选择你要与之交互的集群的名称
export CLUSTER_NAME = "some_server_name"
# 指向引用该集群名称的 API 服务器
APISERVER = $( kubectl config view -o jsonpath = "{.clusters[?(@.name==\" $CLUSTER_NAME \")].cluster.server}" )
# 创建一个 secret 来保存默认服务账户的令牌
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: default-token
annotations:
kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
EOF
# 等待令牌控制器使用令牌填充 secret:
while ! kubectl describe secret default-token | grep -E '^token' >/dev/null; do
echo "waiting for token..." >&2
sleep 1
done
# 获取令牌
TOKEN = $( kubectl get secret default-token -o jsonpath = '{.data.token}' | base64 --decode)
# 使用令牌玩转 API
curl -X GET $APISERVER /api --header "Authorization: Bearer $TOKEN " --insecure
输出类似如下:
{
"kind" : "APIVersions" ,
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
上面例子使用了 --insecure
标志位。这使它易受到 MITM 攻击。
当 kubectl 访问集群时,它使用存储的根证书和客户端证书访问服务器。
(已安装在 ~/.kube
目录下)。
由于集群认证通常是自签名的,因此可能需要特殊设置才能让你的 http 客户端使用根证书。
在一些集群中,API 服务器不需要身份认证;它运行在本地,或由防火墙保护着。
对此并没有一个标准。
配置对 API 的访问
讲解了作为集群管理员可如何对此进行配置。
编程方式访问 API Kubernetes 官方支持 Go 、Python 、Java 、
dotnet 、JavaScript 和 Haskell
语言的客户端库。还有一些其他客户端库由对应作者而非 Kubernetes 团队提供并维护。
参考客户端库 了解如何使用其他语言来访问 API
以及如何执行身份认证。
Go 客户端 说明: 注意 client-go 定义了自己的 API 对象,因此如果需要,请从 client-go 而不是主仓库导入
API 定义,例如 import "k8s.io/client-go/kubernetes"
是正确做法。
Go 客户端可以使用与 kubectl 命令行工具相同的
kubeconfig 文件
定位和验证 API 服务器。参见这个
例子 :
package main
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
func main () {
// 在 kubeconfig 中使用当前上下文
// path-to-kubeconfig -- 例如 /root/.kube/config
config, _ := clientcmd.BuildConfigFromFlags ("" , "<path-to-kubeconfig>" )
// 创建 clientset
clientset, _ := kubernetes.NewForConfig (config)
// 访问 API 以列出 Pod
pods, _ := clientset.CoreV1 ().Pods ("" ).List (context.TODO (), v1.ListOptions{})
fmt.Printf ("There are %d pods in the cluster\n" , len (pods.Items))
}
如果该应用程序部署为集群中的一个
Pod,请参阅从 Pod 内访问 API 。
Python 客户端 要使用 Python 客户端 ,运行下列命令:
pip install kubernetes
。
参见 Python 客户端库主页 了解更多安装选项。
Python 客户端可以使用与 kubectl 命令行工具相同的
kubeconfig 文件
定位和验证 API 服务器。参见这个
例子 :
from kubernetes import client, config
config. load_kube_config()
v1= client. CoreV1Api()
print ("Listing pods with their IPs:" )
ret = v1. list_pod_for_all_namespaces(watch= False )
for i in ret. items:
print (" %s \t %s \t %s " % (i. status. pod_ip, i. metadata. namespace, i. metadata. name))
Java 客户端 要安装 Java 客户端 ,运行:
# 克隆 Java 库
git clone --recursive https://github.com/kubernetes-client/java
# 安装项目文件、POM 等
cd java
mvn install
参阅https://github.com/kubernetes-client/java/releases
了解当前支持的版本。
Java 客户端可以使用 kubectl 命令行所使用的
kubeconfig 文件
以定位 API 服务器并向其认证身份。
参看此示例 :
package io.kubernetes.client.examples ;
import io.kubernetes.client.ApiClient ;
import io.kubernetes.client.ApiException ;
import io.kubernetes.client.Configuration ;
import io.kubernetes.client.apis.CoreV1Api ;
import io.kubernetes.client.models.V1Pod ;
import io.kubernetes.client.models.V1PodList ;
import io.kubernetes.client.util.ClientBuilder ;
import io.kubernetes.client.util.KubeConfig ;
import java.io.FileReader ;
import java.io.IOException ;
/**
* A simple example of how to use the Java API from an application outside a kubernetes cluster
*
* <p>Easiest way to run this: mvn exec:java
* -Dexec.mainClass="io.kubernetes.client.examples.KubeConfigFileClientExample"
*
*/
public class KubeConfigFileClientExample {
public static void main ( String[] args) throws IOException, ApiException {
// file path to your KubeConfig
String kubeConfigPath = "~/.kube/config" ;
// loading the out-of-cluster config, a kubeconfig from file-system
ApiClient client =
ClientBuilder. kubeconfig ( KubeConfig. loadKubeConfig ( new FileReader( kubeConfigPath))). build ();
// set the global default api-client to the in-cluster one from above
Configuration. setDefaultApiClient ( client);
// the CoreV1Api loads default api-client from global configuration.
CoreV1Api api = new CoreV1Api();
// invokes the CoreV1Api client
V1PodList list = api. listPodForAllNamespaces ( null , null , null , null , null , null , null , null , null );
System. out . println ( "Listing all pods: " );
for ( V1Pod item : list. getItems ()) {
System. out . println ( item. getMetadata (). getName ());
}
}
}
.Net 客户端 要使用.Net 客户端 ,运行下面的命令:
dotnet add package KubernetesClient --version 1.6.1
。
参见.Net 客户端库页面 了解更多安装选项。
关于可支持的版本,参见https://github.com/kubernetes-client/csharp/releases 。
.Net 客户端可以使用与 kubectl CLI 相同的 kubeconfig 文件
来定位并验证 API 服务器。
参见样例 :
using System ;
using k8s ;
namespace simple
{
internal class PodList
{
private static void Main(string [] args)
{
var config = KubernetesClientConfiguration.BuildDefaultConfig();
IKubernetes client = new Kubernetes(config);
Console.WriteLine("Starting Request!" );
var list = client.ListNamespacedPod("default" );
foreach (var item in list.Items)
{
Console.WriteLine(item.Metadata.Name);
}
if (list.Items.Count == 0 )
{
Console.WriteLine("Empty!" );
}
}
}
}
JavaScript 客户端 要安装 JavaScript 客户端 ,运行下面的命令:
npm install @kubernetes/client-node
。
参考https://github.com/kubernetes-client/javascript/releases 了解可支持的版本。
JavaScript 客户端可以使用 kubectl 命令行所使用的
kubeconfig 文件
以定位 API 服务器并向其认证身份。
参见此例 :
const k8s = require('@kubernetes/client-node' );
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
k8sApi.listNamespacedPod('default' ).then((res) => {
console.log(res.body);
});
Haskell 客户端 参考 https://github.com/kubernetes-client/haskell/releases 了解支持的版本。
Haskell 客户端
可以使用 kubectl 命令行所使用的
kubeconfig 文件
以定位 API 服务器并向其认证身份。
参见此例 :
exampleWithKubeConfig :: IO ()
exampleWithKubeConfig = do
oidcCache <- atomically $ newTVar $ Map . fromList []
(mgr, kcfg) <- mkKubeClientConfig oidcCache $ KubeConfigFile "/path/to/kubeconfig"
dispatchMime
mgr
kcfg
(CoreV1 . listPodForAllNamespaces (Accept MimeJSON ))
>>= print
接下来 4.2.28 - 使用 NUMA 感知的内存管理器 特性状态: Kubernetes v1.22 [beta]
Kubernetes 内存管理器(Memory Manager)为 Guaranteed
QoS 类
的 Pods 提供可保证的内存(及大页面)分配能力。
内存管理器使用提示生成协议来为 Pod 生成最合适的 NUMA 亲和性配置。
内存管理器将这类亲和性提示输入给中央管理器(即 Topology Manager)。
基于所给的提示和 Topology Manager(拓扑管理器)的策略设置,Pod
或者会被某节点接受,或者被该节点拒绝。
此外,内存管理器还确保 Pod 所请求的内存是从尽量少的 NUMA 节点分配而来。
内存管理器仅能用于 Linux 主机。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.21.
要获知版本信息,请输入
kubectl version
.
为了使得内存资源与 Pod 规约中所请求的其他资源对齐:
CPU 管理器应该被启用,并且在节点(Node)上要配置合适的 CPU 管理器策略,
参见控制 CPU 管理策略 ; 拓扑管理器要被启用,并且要在节点上配置合适的拓扑管理器策略,参见
控制拓扑管理器策略 。 从 v1.22 开始,内存管理器通过特性门控
MemoryManager
默认启用。
在 v1.22 之前,kubelet
必须在启动时设置如下标志:
--feature-gates=MemoryManager=true
这样内存管理器特性才会被启用。
内存管理器如何运作? 内存管理器目前为 Guaranteed QoS 类中的 Pod 提供可保证的内存(和大页面)分配能力。
若要立即将内存管理器启用,可参照内存管理器配置 节中的指南,
之后按将 Pod 放入 Guaranteed QoS 类
节中所展示的,准备并部署一个 Guaranteed
Pod。
内存管理器是一个提示驱动组件(Hint Provider),负责为拓扑管理器提供拓扑提示,
后者根据这些拓扑提示对所请求的资源执行对齐操作。
内存管理器也会为 Pods 应用 cgroups
设置(即 cpuset.mems
)。
与 Pod 准入和部署流程相关的完整流程图在Memory Manager KEP: Design Overview ,
下面也有说明。
在这个过程中,内存管理器会更新其内部存储于节点映射和内存映射 中的计数器,
从而管理有保障的内存分配。
内存管理器在启动和运行期间按下述逻辑更新节点映射(Node Map)。
启动 当节点管理员应用 --reserved-memory
预留内存标志 时执行此逻辑。
这时,节点映射会被更新以反映内存的预留,如
Memory Manager KEP: Memory Maps at start-up (with examples)
所说明。
当配置了 Static
策略时,管理员必须提供 --reserved-memory
标志设置。
运行时 参考文献 Memory Manager KEP: Memory Maps at runtime (with examples)
中说明了成功的 Pod 部署是如何影响节点映射的,该文档也解释了可能发生的内存不足
(Out-of-memory,OOM)情况是如何进一步被 Kubernetes 或操作系统处理的。
在内存管理器运作的语境中,一个重要的话题是对 NUMA 分组的管理。
每当 Pod 的内存请求超出单个 NUMA 节点容量时,内存管理器会尝试创建一个包含多个
NUMA 节点的分组,从而扩展内存容量。解决这个问题的详细描述在文档
Memory Manager KEP: How to enable the guaranteed memory allocation over many NUMA nodes?
中。同时,关于 NUMA 分组是如何管理的,你还可以参考文档
Memory Manager KEP: Simulation - how the Memory Manager works? (by examples) 。
内存管理器配置 其他管理器也要预先配置。接下来,内存管理器特性需要被启用,
并且采用 Static
策略(静态策略 )运行。
作为可选操作,可以预留一定数量的内存给系统或者 kubelet 进程以增强节点的稳定性
(预留内存标志 )。
策略 内存管理器支持两种策略。你可以通过 kubelet
标志 --memory-manager-policy
来选择一种策略:
None 策略 这是默认的策略,并且不会以任何方式影响内存分配。该策略的行为好像内存管理器不存在一样。
None
策略返回默认的拓扑提示信息。这种特殊的提示会表明拓扑驱动组件(Hint Provider)
(在这里是内存管理器)对任何资源都没有与 NUMA 亲和性关联的偏好。
Static 策略 对 Guaranteed
Pod 而言,Static
内存管理器策略会返回拓扑提示信息,
该信息与内存分配有保障的 NUMA 节点集合有关,并且内存管理器还通过更新内部的节点映射
对象来完成内存预留。
对 BestEffort
或 Burstable
Pod 而言,因为不存在对有保障的内存资源的请求,
Static
内存管理器策略会返回默认的拓扑提示,并且不会通过内部的节点映射 对象来预留内存。
预留内存标志 节点可分配 机制通常被节点管理员用来为
kubelet 或操作系统进程预留 K8S 节点上的系统资源,目的是提高节点稳定性。
有一组专用的标志可用于这个目的,为节点设置总的预留内存量。
此预配置的值接下来会被用来计算节点上对 Pods “可分配的”内存。
Kubernetes 调度器在优化 Pod 调度过程时,会考虑“可分配的”内存。
前面提到的标志包括 --kube-reserved
、--system-reserved
和 --eviction-threshold
。
这些标志值的综合计作预留内存的总量。
为内存管理器而新增加的 --reserved-memory
标志可以(让节点管理员)将总的预留内存进行划分,
并完成跨 NUMA 节点的预留操作。
标志设置的值是一个按 NUMA 节点的不同内存类型所给的内存预留的值的列表,用逗号分开。
可以使用分号作为分隔符来指定跨多个 NUMA 节点的内存预留。
只有在内存管理器特性被启用的语境下,这个参数才有意义。
内存管理器不会使用这些预留的内存来为容器负载分配内存。
例如,如果你有一个可用内存为 10Gi
的 NUMA 节点 "NUMA0",而参数 --reserved-memory
被设置成要在 "NUMA0" 上预留 1Gi
的内存,那么内存管理器会假定节点上只有 9Gi
内存可用于容器负载。
你也可以忽略此参数,不过这样做时,你要清楚,所有 NUMA
节点上预留内存的数量要等于节点可分配特性
所设定的内存量。如果至少有一个节点可分配参数值为非零,你就需要至少为一个 NUMA
节点设置 --reserved-memory
。实际上,eviction-hard
阈值默认为 100Mi
,
所以当使用 Static
策略时,--reserved-memory
是必须设置的。
此外,应尽量避免如下配置:
重复的配置,即同一 NUMA 节点或内存类型被设置不同的取值; 为某种内存类型设置约束值为零; 使用物理硬件上不存在的 NUMA 节点 ID; 使用名字不是 memory
或 hugepages-<size>
的内存类型名称
(特定的 <size>
的大页面也必须存在)。 语法:
--reserved-memory N:memory-type1=value1,memory-type2=value2,...
N
(整数)- NUMA 节点索引,例如,0
memory-type
(字符串)- 代表内存类型:memory
- 常规内存;hugepages-2Mi
或 hugepages-1Gi
- 大页面value
(字符串) - 预留内存的量,例如 1Gi
用法示例:
--reserved-memory 0:memory=1Gi,hugepages-1Gi=2Gi
或者
--reserved-memory 0:memory=1Gi --reserved-memory 1:memory=2Gi
--reserved-memory '0:memory=1Gi;1:memory=2Gi'
当你为 --reserved-memory
标志指定取值时,必须要遵从之前通过节点可分配特性标志所设置的值。
换言之,对每种内存类型而言都要遵从下面的规则:
sum(reserved-memory(i)) = kube-reserved + system-reserved + eviction-threshold
其中,i
是 NUMA 节点的索引。
如果你不遵守上面的公式,内存管理器会在启动时输出错误信息。
换言之,上面的例子我们一共要预留 3Gi
的常规内存(type=memory
),即:
sum(reserved-memory(i)) = reserved-memory(0) + reserved-memory(1) = 1Gi + 2Gi = 3Gi
下面的例子中给出与节点可分配配置相关的 kubelet 命令行参数:
--kube-reserved=cpu=500m,memory=50Mi
--system-reserved=cpu=123m,memory=333Mi
--eviction-hard=memory.available<500Mi
说明: 默认的硬性驱逐阈值是 100MiB,不是 零。
请记得在使用 --reserved-memory
设置要预留的内存量时,加上这个硬性驱逐阈值。
否则 kubelet 不会启动内存管理器,而会输出一个错误信息。
下面是一个正确配置的示例:
--feature-gates= MemoryManager = true
--kube-reserved= cpu = 4,memory= 4Gi
--system-reserved= cpu = 1,memory= 1Gi
--memory-manager-policy= Static
--reserved-memory '0:memory=3Gi;1:memory=2148Mi'
我们对上面的配置做一个检查:
kube-reserved + system-reserved + eviction-hard(default) = reserved-memory(0) + reserved-memory(1)
4GiB + 1GiB + 100MiB = 3GiB + 2148MiB
5120MiB + 100MiB = 3072MiB + 2148MiB
5220MiB = 5220MiB
(这是对的)将 Pod 放入 Guaranteed QoS 类 若所选择的策略不是 None
,则内存管理器会辨识处于 Guaranteed
QoS 类中的 Pod。
内存管理器为每个 Guaranteed
Pod 向拓扑管理器提供拓扑提示信息。
对于不在 Guaranteed
QoS 类中的其他 Pod,内存管理器向拓扑管理器提供默认的拓扑提示信息。
下面的来自 Pod 清单的片段将 Pod 加入到 Guaranteed
QoS 类中。
当 Pod 的 CPU requests
等于 limits
且为整数值时,Pod 将运行在 Guaranteed
QoS 类中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "2"
example.com/device : "1"
requests :
memory : "200Mi"
cpu : "2"
example.com/device : "1"
此外,共享 CPU 的 Pods 在 requests
等于 limits
值时也运行在 Guaranteed
QoS 类中。
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "300m"
example.com/device : "1"
requests :
memory : "200Mi"
cpu : "300m"
example.com/device : "1"
要注意的是,只有 CPU 和内存请求都被设置时,Pod 才会进入 Guaranteed QoS 类。
故障排查 下面的方法可用来排查为什么 Pod 无法被调度或者被节点拒绝:
Pod 状态 - 可表明拓扑亲和性错误 系统日志 - 包含用来调试的有价值的信息,例如,关于所生成的提示信息 状态文件 - 其中包含内存管理器内部状态的转储(包含节点映射和内存映射 ) 从 v1.22 开始,设备插件资源 API
可以用来检索关于为容器预留的内存的信息 Pod 状态 (TopologyAffinityError) 这类错误通常在以下情形出现:
节点缺少足够的资源来满足 Pod 请求 Pod 的请求因为特定的拓扑管理器策略限制而被拒绝 错误信息会出现在 Pod 的状态中:
NAME READY STATUS RESTARTS AGE
guaranteed 0/1 TopologyAffinityError 0 113s
使用 kubectl describe pod <id>
或 kubectl get events
可以获得详细的错误信息。
Warning TopologyAffinityError 10m kubelet, dell8 Resources cannot be allocated with Topology locality
系统日志 针对特定的 Pod 搜索系统日志。
内存管理器为 Pod 所生成的提示信息可以在日志中找到。
此外,日志中应该也存在 CPU 管理器所生成的提示信息。
拓扑管理器将这些提示信息进行合并,计算得到唯一的最合适的提示数据。
此最佳提示数据也应该出现在日志中。
最佳提示表明要在哪里分配所有的资源。拓扑管理器会用当前的策略来测试此数据,
并基于得出的结论或者接纳 Pod 到节点,或者将其拒绝。
此外,你可以搜索日志查找与内存管理器相关的其他条目,例如 cgroups
和
cpuset.mems
的更新信息等。
检查节点上内存管理器状态 我们首先部署一个 Guaranteed
Pod 示例,其规约如下所示:
apiVersion : v1
kind : Pod
metadata :
name : guaranteed
spec :
containers :
- name : guaranteed
image : consumer
imagePullPolicy : Never
resources :
limits :
cpu : "2"
memory : 150Gi
requests :
cpu : "2"
memory : 150Gi
command : ["sleep" ,"infinity" ]
接下来,我们登录到 Pod 运行所在的节点,检查位于
/var/lib/kubelet/memory_manager_state
的状态文件:
{
"policyName" :"Static" ,
"machineState" :{
"0" :{
"numberOfAssignments" :1 ,
"memoryMap" :{
"hugepages-1Gi" :{
"total" :0 ,
"systemReserved" :0 ,
"allocatable" :0 ,
"reserved" :0 ,
"free" :0
},
"memory" :{
"total" :134987354112 ,
"systemReserved" :3221225472 ,
"allocatable" :131766128640 ,
"reserved" :131766128640 ,
"free" :0
}
},
"nodes" :[
0 ,
1
]
},
"1" :{
"numberOfAssignments" :1 ,
"memoryMap" :{
"hugepages-1Gi" :{
"total" :0 ,
"systemReserved" :0 ,
"allocatable" :0 ,
"reserved" :0 ,
"free" :0
},
"memory" :{
"total" :135286722560 ,
"systemReserved" :2252341248 ,
"allocatable" :133034381312 ,
"reserved" :29295144960 ,
"free" :103739236352
}
},
"nodes" :[
0 ,
1
]
}
},
"entries" :{
"fa9bdd38-6df9-4cf9-aa67-8c4814da37a8" :{
"guaranteed" :[
{
"numaAffinity" :[
0 ,
1
],
"type" :"memory" ,
"size" :161061273600
}
]
}
},
"checksum" :4142013182
}
从这个状态文件,可以推断 Pod 被同时绑定到两个 NUMA 节点,即:
术语绑定(pinned)意味着 Pod 的内存使用被(通过 cgroups
配置)限制到这些 NUMA 节点。
这也直接意味着内存管理器已经创建了一个 NUMA 分组,由这两个 NUMA 节点组成,
即索引值分别为 0
和 1
的 NUMA 节点。
注意 NUMA 分组的管理是有一个相对复杂的管理器处理的,
相关逻辑的进一步细节可在内存管理器的 KEP 中示例1 和跨 NUMA 节点 节找到。
为了分析 NUMA 组中可用的内存资源,必须对分组内 NUMA 节点对应的条目进行汇总。
例如,NUMA 分组中空闲的“常规”内存的总量可以通过将分组内所有 NUMA
节点上空闲内存加和来计算,即将 NUMA 节点 0
和 NUMA 节点 1
的 "memory"
节
(分别是 "free":0
和 "free": 103739236352
)相加,得到此分组中空闲的“常规”
内存总量为 0 + 103739236352
字节。
"systemReserved": 3221225472
这一行表明节点的管理员使用 --reserved-memory
为 NUMA
节点 0
上运行的 kubelet 和系统进程预留了 3221225472
字节 (即 3Gi
)。
设备插件资源 API 通过使用此 API ,
可以获得每个容器的预留内存信息,该信息位于 protobuf 协议的 ContainerMemory
消息中。
只能针对 Guaranteed QoS 类中的 Pod 来检索此信息。
接下来 4.2.29 - 通过名字空间共享集群 本页展示如何查看、使用和删除名字空间 。
本页同时展示如何使用 Kubernetes 名字空间来划分集群。
准备开始 查看名字空间 列出集群中现有的名字空间: NAME STATUS AGE
default Active 11d
kube-system Active 11d
kube-public Active 11d
初始状态下,Kubernetes 具有三个名字空间:
default
无名字空间对象的默认名字空间kube-system
由 Kubernetes 系统创建的对象的名字空间kube-public
自动创建且被所有用户可读的名字空间(包括未经身份认证的)。此名字空间通常在某些资源在整个集群中可见且可公开读取时被集群使用。此名字空间的公共方面只是一个约定,而不是一个必要条件。你还可以通过下列命令获取特定名字空间的摘要:
kubectl get namespaces <name>
或用下面的命令获取详细信息:
kubectl describe namespaces <name>
Name: default
Labels: <none>
Annotations: <none>
Status: Active
No resource quota.
Resource Limits
Type Resource Min Max Default
---- -------- --- --- ---
Container cpu - - 100m
请注意,这些详情同时显示了资源配额(如果存在)以及资源限制区间。
资源配额跟踪并聚合 Namespace 中资源的使用情况,并允许集群运营者定义 Namespace 可能消耗的 Hard 资源使用限制。
限制区间定义了单个实体在一个 Namespace 中可使用的最小/最大资源量约束。
参阅 准入控制:限制区间 。
名字空间可以处于下列两个阶段中的一个:
Active
名字空间正在被使用中Terminating
名字空间正在被删除,且不能被用于新对象。更多细节,参阅 API 参考中的命名空间 。
创建名字空间 说明: 避免使用前缀 kube-
创建名字空间,因为它是为 Kubernetes 系统名字空间保留的。
新建一个名为 my-namespace.yaml
的 YAML 文件,并写入下列内容:
apiVersion : v1
kind : Namespace
metadata :
name : <insert-namespace-name-here>
然后运行:
kubectl create -f ./my-namespace.yaml
或者,你可以使用下面的命令创建名字空间:
kubectl create namespace <insert-namespace-name-here>
请注意,名字空间的名称必须是一个合法的
DNS 标签 。
可选字段 finalizers
允许观察者们在名字空间被删除时清除资源。
记住如果指定了一个不存在的终结器,名字空间仍会被创建,
但如果用户试图删除它,它将陷入 Terminating
状态。
更多有关 finalizers
的信息请查阅
设计文档 中名字空间部分。
删除名字空间 删除名字空间使用命令:
kubectl delete namespaces <insert-some-namespace-name>
警告: 这会删除名字空间下的 所有内容 !
删除是异步的,所以有一段时间你会看到名字空间处于 Terminating
状态。
使用 Kubernetes 名字空间细分你的集群 理解 default 名字空间
默认情况下,Kubernetes 集群会在配置集群时实例化一个 default 名字空间,用以存放集群所使用的默认
Pod、Service 和 Deployment 集合。
假设你有一个新的集群,你可以通过执行以下操作来内省可用的名字空间:
NAME STATUS AGE
default Active 13m
创建新的名字空间
在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。
在某组织使用共享的 Kubernetes 集群进行开发和生产的场景中:
开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pod、Service
和 Deployment 列表。在这个空间里,Kubernetes 资源被自由地加入或移除,
对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。
运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程,
对谁可以或不可以操作运行生产站点的 Pod、Service 和 Deployment 集合进行控制。
该组织可以遵循的一种模式是将 Kubernetes 集群划分为两个名字空间:development
和 production
。
让我们创建两个新的名字空间来保存我们的工作。
使用 kubectl 创建 development
名字空间。
kubectl create -f https://k8s.io/examples/admin/namespace-dev.json
让我们使用 kubectl 创建 production
名字空间。
kubectl create -f https://k8s.io/examples/admin/namespace-prod.json
为了确保一切正常,列出集群中的所有名字空间。
kubectl get namespaces --show-labels
NAME STATUS AGE LABELS
default Active 32m <none>
development Active 29s name=development
production Active 23s name=production
在每个名字空间中创建 Pod
Kubernetes 名字空间为集群中的 Pod、Service 和 Deployment 提供了作用域。
与一个名字空间交互的用户不会看到另一个名字空间中的内容。
为了演示这一点,让我们在 development
名字空间中启动一个简单的 Deployment 和 Pod。
kubectl create deployment snowflake --image= registry.k8s.io/serve_hostname -n= development --replicas= 2
我们创建了一个副本个数为 2 的 Deployment,运行名为 snowflake
的
Pod,其中包含一个负责提供主机名的基本容器。
kubectl get deployment -n= development
NAME READY UP-TO-DATE AVAILABLE AGE
snowflake 2/2 2 2 2m
kubectl get pods -l app = snowflake -n= development
NAME READY STATUS RESTARTS AGE
snowflake-3968820950-9dgr8 1/1 Running 0 2m
snowflake-3968820950-vgc4n 1/1 Running 0 2m
看起来还不错,开发人员能够做他们想做的事,而且他们不必担心会影响到
production
名字空间下面的内容。
让我们切换到 production
名字空间,
展示一下一个名字空间中的资源是如何对另一个名字空间隐藏的。
名字空间 production
应该是空的,下面的命令应该不会返回任何东西。
kubectl get deployment -n= production
kubectl get pods -n= production
生产环境下一般以养牛的方式运行负载,所以让我们创建一些 Cattle(牛)Pod。
kubectl create deployment cattle --image= registry.k8s.io/serve_hostname -n= production
kubectl scale deployment cattle --replicas= 5 -n= production
kubectl get deployment -n= production
NAME READY UP-TO-DATE AVAILABLE AGE
cattle 5/5 5 5 10s
kubectl get pods -l app = cattle -n= production
NAME READY STATUS RESTARTS AGE
cattle-2263376956-41xy6 1/1 Running 0 34s
cattle-2263376956-kw466 1/1 Running 0 34s
cattle-2263376956-n4v97 1/1 Running 0 34s
cattle-2263376956-p5p3i 1/1 Running 0 34s
cattle-2263376956-sxpth 1/1 Running 0 34s
此时,应该很清楚地展示了用户在一个名字空间中创建的资源对另一个名字空间是隐藏的。
随着 Kubernetes 中的策略支持的发展,我们将扩展此场景,以展示如何为每个名字空间提供不同的授权规则。
理解使用名字空间的动机 单个集群应该能满足多个用户及用户组的需求(以下称为 “用户社区”)。
Kubernetes 名字空间 帮助不同的项目、团队或客户去共享 Kubernetes 集群。
名字空间通过以下方式实现这点:
为名字 设置作用域. 为集群中的部分资源关联鉴权和策略的机制。 使用多个名字空间是可选的。
每个用户社区都希望能够与其他社区隔离开展工作。
每个用户社区都有自己的:
资源(Pod、服务、副本控制器等等) 策略(谁能或不能在他们的社区里执行操作) 约束(该社区允许多少配额等等) 集群运营者可以为每个唯一用户社区创建名字空间。
名字空间为下列内容提供唯一的作用域:
命名资源(避免基本的命名冲突) 将管理权限委派给可信用户 限制社区资源消耗的能力 用例包括:
作为集群运营者, 我希望能在单个集群上支持多个用户社区。 作为集群运营者,我希望将集群分区的权限委派给这些社区中的受信任用户。 作为集群运营者,我希望能限定每个用户社区可使用的资源量,以限制对使用同一集群的其他用户社区的影响。 作为集群用户,我希望与我的用户社区相关的资源进行交互,而与其他用户社区在该集群上执行的操作无关。 理解名字空间和 DNS 当你创建服务 时,Kubernetes
会创建相应的 DNS 条目 。
此条目的格式为 <服务名称>.<名字空间名称>.svc.cluster.local
。
这意味着如果容器使用 <服务名称>
,它将解析为名字空间本地的服务。
这对于在多个名字空间(如开发、暂存和生产)中使用相同的配置非常有用。
如果要跨名字空间访问,则需要使用完全限定的域名(FQDN)。
接下来 4.2.30 - 通过配置文件设置 Kubelet 参数 通过保存在硬盘的配置文件设置 kubelet 的部分配置参数,这可以作为命令行参数的替代。
建议通过配置文件的方式提供参数,因为这样可以简化节点部署和配置管理。
创建配置文件 KubeletConfiguration
结构体定义了可以通过文件配置的 Kubelet 配置子集,
配置文件必须是这个结构体中参数的 JSON 或 YAML 表现形式。
确保 kubelet 可以读取该文件。
下面是一个 Kubelet 配置文件示例:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
address : "192.168.0.8"
port : 20250
serializeImagePulls : false
evictionHard :
memory.available : "200Mi"
在这个示例中, Kubelet 被设置为在地址 192.168.0.8 端口 20250 上提供服务,以并行方式拉取镜像,
当可用内存低于 200Mi 时, kubelet 将会开始驱逐 Pod。
由于仅配置了四个 evictionHard 阈值之一,因此其他 evictionHard 阈值被重置为 0,而不是使用其内置默认值。
没有声明的其余配置项都将使用默认值,除非使用命令行参数来重载。
命令行中的参数将会覆盖配置文件中的对应值。
说明: 在示例中,通过只更改 evictionHard 的一个参数的默认值,
其他参数的默认值将不会被继承,他们会被设置为零。如果要提供自定义值,你应该分别设置所有阈值。
启动通过配置文件配置的 Kubelet 进程 启动 Kubelet 需要将 --config
参数设置为 Kubelet 配置文件的路径。Kubelet 将从此文件加载其配置。
请注意,命令行参数与配置文件有相同的值时,就会覆盖配置文件中的该值。
这有助于确保命令行 API 的向后兼容性。
请注意,kubelet 配置文件中的相对文件路径是相对于 kubelet 配置文件的位置解析的,
而命令行参数中的相对路径是相对于 kubelet 的当前工作目录解析的。
请注意,命令行参数和 Kubelet 配置文件的某些默认值不同。
如果设置了 --config
,并且没有通过命令行指定值,则 KubeletConfiguration
版本的默认值生效。在上面的例子中,version 是 kubelet.config.k8s.io/v1beta1
。
接下来 4.2.31 - 为 Kubernetes 运行 etcd 集群
etcd 是 一致且高度可用的键值存储,用作 Kubernetes 的所有集群数据的后台数据库。
如果你的 Kubernetes 集群使用 etcd 作为其后台数据库,
请确保你针对这些数据有一份
备份 计划。
你可以在官方文档 中找到有关 etcd 的深入知识。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
先决条件 运行的 etcd 集群个数成员为奇数。
etcd 是一个 leader-based 分布式系统。确保主节点定期向所有从节点发送心跳,以保持集群稳定。
确保不发生资源不足。
集群的性能和稳定性对网络和磁盘 I/O 非常敏感。任何资源匮乏都会导致心跳超时,
从而导致集群的不稳定。不稳定的情况表明没有选出任何主节点。
在这种情况下,集群不能对其当前状态进行任何更改,这意味着不能调度新的 Pod。
保持 etcd 集群的稳定对 Kubernetes 集群的稳定性至关重要。
因此,请在专用机器或隔离环境上运行 etcd 集群,
以满足所需资源需求 。
在生产中运行的 etcd 的最低推荐版本是 3.2.10+
。
资源需求 使用有限的资源运行 etcd 只适合测试目的。为了在生产中部署,需要先进的硬件配置。
在生产中部署 etcd 之前,请查看所需资源参考文档 。
启动 etcd 集群 本节介绍如何启动单节点和多节点 etcd 集群。
单节点 etcd 集群 只为测试目的使用单节点 etcd 集群。
运行以下命令:
etcd --listen-client-urls= http://$PRIVATE_IP :2379 \
--advertise-client-urls= http://$PRIVATE_IP :2379
使用参数 --etcd-servers=$PRIVATE_IP:2379
启动 Kubernetes API 服务器。
确保将 PRIVATE_IP
设置为 etcd 客户端 IP。
多节点 etcd 集群 出于耐用性和高可用性考量,在生产环境中应以多节点集群的方式运行 etcd,并且定期备份。
建议在生产环境中使用五个成员的集群。
有关该内容的更多信息,请参阅常见问题文档 。
可以通过静态成员信息或动态发现的方式配置 etcd 集群。
有关集群的详细信息,请参阅
etcd 集群文档 。
例如,考虑运行以下客户端 URL 的五个成员的 etcd 集群:http://$IP1:2379
、
http://$IP2:2379
、http://$IP3:2379
、http://$IP4:2379
和 http://$IP5:2379
。
要启动 Kubernetes API 服务器:
运行以下命令:
etcd --listen-client-urls= http://$IP1 :2379,http://$IP2 :2379,http://$IP3 :2379,http://$IP4 :2379,http://$IP5 :2379 --advertise-client-urls= http://$IP1 :2379,http://$IP2 :2379,http://$IP3 :2379,http://$IP4 :2379,http://$IP5 :2379
使用参数 --etcd-servers=$IP1:2379,$IP2:2379,$IP3:2379,$IP4:2379,$IP5:2379
启动 Kubernetes API 服务器。
确保将 IP<n>
变量设置为客户端 IP 地址。
使用负载均衡器的多节点 etcd 集群 要运行负载均衡的 etcd 集群:
建立一个 etcd 集群。 在 etcd 集群前面配置负载均衡器。例如,让负载均衡器的地址为 $LB
。 使用参数 --etcd-servers=$LB:2379
启动 Kubernetes API 服务器。 加固 etcd 集群 对 etcd 的访问相当于集群中的 root 权限,因此理想情况下只有 API 服务器才能访问它。
考虑到数据的敏感性,建议只向需要访问 etcd 集群的节点授予权限。
想要确保 etcd 的安全,可以设置防火墙规则或使用 etcd 提供的安全特性,这些安全特性依赖于 x509 公钥基础设施(PKI)。
首先,通过生成密钥和证书对来建立安全的通信通道。
例如,使用密钥对 peer.key
和 peer.cert
来保护 etcd 成员之间的通信,
而 client.key
和 client.cert
用于保护 etcd 与其客户端之间的通信。
请参阅 etcd 项目提供的示例脚本 ,
以生成用于客户端身份验证的密钥对和 CA 文件。
安全通信 若要使用安全对等通信对 etcd 进行配置,请指定参数 --peer-key-file=peer.key
和 --peer-cert-file=peer.cert
,并使用 HTTPS 作为 URL 模式。
类似地,要使用安全客户端通信对 etcd 进行配置,请指定参数 --key-file=k8sclient.key
和 --cert-file=k8sclient.cert
,并使用 HTTPS 作为 URL 模式。
使用安全通信的客户端命令的示例:
ETCDCTL_API=3 etcdctl --endpoints 10.2.0.9:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
member list
限制 etcd 集群的访问 配置安全通信后,限制只有 Kubernetes API 服务器可以访问 etcd 集群。使用 TLS 身份验证来完成此任务。
例如,考虑由 CA etcd.ca
信任的密钥对 k8sclient.key
和 k8sclient.cert
。
当 etcd 配置为 --client-cert-auth
和 TLS 时,它使用系统 CA 或由 --trusted-ca-file
参数传入的 CA 验证来自客户端的证书。指定参数 --client-cert-auth=true
和
--trusted-ca-file=etcd.ca
将限制对具有证书 k8sclient.cert
的客户端的访问。
一旦正确配置了 etcd,只有具有有效证书的客户端才能访问它。要让 Kubernetes API 服务器访问,
可以使用参数 --etcd-certfile=k8sclient.cert
、--etcd-keyfile=k8sclient.key
和 --etcd-cafile=ca.cert
配置。
替换失败的 etcd 成员 etcd 集群通过容忍少数成员故障实现高可用性。
但是,要改善集群的整体健康状况,请立即替换失败的成员。当多个成员失败时,逐个替换它们。
替换失败成员需要两个步骤:删除失败成员和添加新成员。
虽然 etcd 在内部保留唯一的成员 ID,但建议为每个成员使用唯一的名称,以避免人为错误。
例如,考虑一个三成员的 etcd 集群。假定 URL 分别为:member1=http://10.0.0.1
、member2=http://10.0.0.2
和 member3=http://10.0.0.3
。当 member1
失败时,将其替换为 member4=http://10.0.0.4
。
获取失败的 member1
的成员 ID:
etcdctl --endpoints= http://10.0.0.2,http://10.0.0.3 member list
显示以下信息:
8211f1d0f64f3269, started, member1, http://10.0.0.1:2380, http://10.0.0.1:2379
91bc3c398fb3c146, started, member2, http://10.0.0.2:2380, http://10.0.0.2:2379
fd422379fda50e48, started, member3, http://10.0.0.3:2380, http://10.0.0.3:2379
执行以下操作之一:
如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
请从 --etcd-servers
标志中移除删除失败的成员,然后重新启动每个 Kubernetes API 服务器。 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信,
则停止与失败的 etcd 通信的 Kubernetes API 服务器。 停止故障节点上的 etcd 服务器。除了 Kubernetes API 服务器之外的其他客户端可能会造成流向 etcd 的流量,
可以停止所有流量以防止写入数据目录。 移除失败的成员:
etcdctl member remove 8211f1d0f64f3269
显示以下信息:
Removed member 8211f1d0f64f3269 from cluster
增加新成员:
etcdctl member add member4 --peer-urls= http://10.0.0.4:2380
显示以下信息:
Member 2be1eb8f84b7f63e added to cluster ef37ad9dc622a7c4
在 IP 为 10.0.0.4
的机器上启动新增加的成员:
export ETCD_NAME = "member4"
export ETCD_INITIAL_CLUSTER = "member2=http://10.0.0.2:2380,member3=http://10.0.0.3:2380,member4=http://10.0.0.4:2380"
export ETCD_INITIAL_CLUSTER_STATE = existing
etcd [ flags]
执行以下操作之一:
如果每个 Kubernetes API 服务器都配置为与所有 etcd 成员通信,
则将新增的成员添加到 --etcd-servers
标志,然后重新启动每个 Kubernetes API 服务器。 如果每个 Kubernetes API 服务器都与单个 etcd 成员通信,请启动在第 2 步中停止的 Kubernetes API 服务器。
然后配置 Kubernetes API 服务器客户端以再次将请求路由到已停止的 Kubernetes API 服务器。
这通常可以通过配置负载均衡器来完成。 有关集群重新配置的详细信息,请参阅
etcd 重构文档 。
备份 etcd 集群 所有 Kubernetes 对象都存储在 etcd 上。
定期备份 etcd 集群数据对于在灾难场景(例如丢失所有控制平面节点)下恢复 Kubernetes 集群非常重要。
快照文件包含所有 Kubernetes 状态和关键信息。为了保证敏感的 Kubernetes 数据的安全,可以对快照文件进行加密。
备份 etcd 集群可以通过两种方式完成:etcd 内置快照和卷快照。
内置快照 etcd 支持内置快照。快照可以从使用 etcdctl snapshot save
命令的活动成员中获取,
也可以通过从 etcd 数据目录
复制 member/snap/db
文件,该 etcd 数据目录目前没有被 etcd 进程使用。获取快照不会影响成员的性能。
下面是一个示例,用于获取 $ENDPOINT
所提供的键空间的快照到文件 snapshotdb
:
ETCDCTL_API = 3 etcdctl --endpoints $ENDPOINT snapshot save snapshotdb
验证快照:
ETCDCTL_API = 3 etcdctl --write-out= table snapshot status snapshotdb
+----------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 | 10 | 7 | 2.1 MB |
+----------+----------+------------+------------+
卷快照 如果 etcd 运行在支持备份的存储卷(如 Amazon Elastic Block
存储)上,则可以通过获取存储卷的快照来备份 etcd 数据。
使用 etcdctl 选项的快照 我们还可以使用 etcdctl 提供的各种选项来制作快照。例如:
列出 etcdctl 可用的各种选项。例如,你可以通过指定端点、证书等来制作快照,如下所示:
ETCDCTL_API = 3 etcdctl --endpoints= https://127.0.0.1:2379 \
--cacert= <trusted-ca-file> --cert= <cert-file> --key= <key-file> \
snapshot save <backup-file-location>
可以从 etcd Pod 的描述中获得 trusted-ca-file
、cert-file
和 key-file
。
为 etcd 集群扩容 通过交换性能,对 etcd 集群扩容可以提高可用性。缩放不会提高集群性能和能力。
一般情况下不要扩大或缩小 etcd 集群的集合。不要为 etcd 集群配置任何自动缩放组。
强烈建议始终在任何官方支持的规模上运行生产 Kubernetes 集群时使用静态的五成员 etcd 集群。
合理的扩展是在需要更高可靠性的情况下,将三成员集群升级为五成员集群。
请参阅 etcd 重构文档
以了解如何将成员添加到现有集群中的信息。
恢复 etcd 集群 etcd 支持从 major.minor 或其他不同 patch 版本的 etcd 进程中获取的快照进行恢复。
还原操作用于恢复失败的集群的数据。
在启动还原操作之前,必须有一个快照文件。它可以是来自以前备份操作的快照文件,
也可以是来自剩余数据目录 的快照文件。
例如:
ETCDCTL_API = 3 etcdctl --endpoints 10.2.0.9:2379 snapshot restore snapshotdb
恢复时也可以指定操作选项,例如:
ETCDCTL_API = 3 etcdctl snapshot restore --data-dir <data-dir-location> snapshotdb
另一个例子是先导出环境变量:
export ETCDCTL_API = 3
etcdctl snapshot restore --data-dir <data-dir-location> snapshotdb
有关从快照文件还原集群的详细信息和示例,请参阅
etcd 灾难恢复文档 。
如果还原的集群的访问 URL 与前一个集群不同,则必须相应地重新配置 Kubernetes API 服务器。
在本例中,使用参数 --etcd-servers=$NEW_ETCD_CLUSTER
而不是参数 --etcd-servers=$OLD_ETCD_CLUSTER
重新启动 Kubernetes API 服务器。用相应的 IP 地址替换 $NEW_ETCD_CLUSTER
和 $OLD_ETCD_CLUSTER
。
如果在 etcd 集群前面使用负载均衡,则可能需要更新负载均衡器。
如果大多数 etcd 成员永久失败,则认为 etcd 集群失败。在这种情况下,Kubernetes 不能对其当前状态进行任何更改。
虽然已调度的 Pod 可能继续运行,但新的 Pod 无法调度。在这种情况下,
恢复 etcd 集群并可能需要重新配置 Kubernetes API 服务器以修复问题。
说明: 如果集群中正在运行任何 API 服务器,则不应尝试还原 etcd 的实例。相反,请按照以下步骤还原 etcd:
停止所有 API 服务实例 在所有 etcd 实例中恢复状态 重启所有 API 服务实例 我们还建议重启所有组件(例如 kube-scheduler
、kube-controller-manager
、kubelet
),
以确保它们不会依赖一些过时的数据。请注意,实际中还原会花费一些时间。
在还原过程中,关键组件将丢失领导锁并自行重启。
升级 etcd 集群 有关 etcd 升级的更多详细信息,请参阅 etcd 升级 文档。
说明: 在开始升级之前,请先备份你的 etcd 集群。
4.2.32 - 为节点发布扩展资源 本文展示了如何为节点指定扩展资源(Extended Resource)。
扩展资源允许集群管理员发布节点级别的资源,这些资源在不进行发布的情况下无法被 Kubernetes 感知。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
获取你的节点名称 选择一个节点用于此练习。
在你的一个节点上发布一种新的扩展资源 为在一个节点上发布一种新的扩展资源,需要发送一个 HTTP PATCH 请求到 Kubernetes API server。
例如:假设你的一个节点上带有四个 dongle 资源。
下面是一个 PATCH 请求的示例,该请求为你的节点发布四个 dongle 资源。
PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080
[
{
"op" : "add" ,
"path" : "/status/capacity/example.com~1dongle" ,
"value" : "4"
}
]
注意:Kubernetes 不需要了解 dongle 资源的含义和用途。
前面的 PATCH 请求告诉 Kubernetes 你的节点拥有四个你称之为 dongle 的东西。
启动一个代理(proxy),以便你可以很容易地向 Kubernetes API server 发送请求:
在另一个命令窗口中,发送 HTTP PATCH 请求。 用你的节点名称替换 <your-node-name>
:
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "add", "path": "/status/capacity/example.com~1dongle", "value": "4"}]' \
http://localhost:8001/api/v1/nodes/<your-node-name>/status
说明: 在前面的请求中,
~1
为 patch 路径中 “/” 符号的编码。
JSON-Patch 中的操作路径值被解析为 JSON 指针。
更多细节,请查看
IETF RFC 6901 的第 3 节。
输出显示该节点的 dongle 资源容量(capacity)为 4:
"capacity": {
"cpu": "2",
"memory": "2049008Ki",
"example.com/dongle": "4",
描述你的节点:
kubectl describe node <your-node-name>
输出再次展示了 dongle 资源:
Capacity :
cpu : 2
memory : 2049008Ki
example.com/dongle : 4
现在,应用开发者可以创建请求一定数量 dongle 资源的 Pod 了。
参见将扩展资源分配给容器 。
讨论 扩展资源类似于内存和 CPU 资源。例如,正如一个节点拥有一定数量的内存和 CPU 资源,
它们被节点上运行的所有组件共享,该节点也可以拥有一定数量的 dongle 资源,
这些资源同样被节点上运行的所有组件共享。
此外,正如应用开发者可以创建请求一定数量的内存和 CPU 资源的 Pod,
他们也可以创建请求一定数量 dongle 资源的 Pod。
扩展资源对 Kubernetes 是不透明的。Kubernetes 不知道扩展资源含义相关的任何信息。
Kubernetes 只了解一个节点拥有一定数量的扩展资源。
扩展资源必须以整形数量进行发布。
例如,一个节点可以发布 4 个 dongle 资源,但是不能发布 4.5 个。
存储示例 假设一个节点拥有一种特殊类型的磁盘存储,其容量为 800 GiB。
你可以为该特殊存储创建一个名称,如 example.com/special-storage
。
然后你就可以按照一定规格的块(如 100 GiB)对其进行发布。
在这种情况下,你的节点将会通知它拥有八个 example.com/special-storage
类型的资源。
Capacity :
...
example.com/special-storage : 8
如果你想要允许针对特殊存储任意(数量)的请求,你可以按照 1 字节大小的块来发布特殊存储。
在这种情况下,你将会发布 800Gi 数量的 example.com/special-storage 类型的资源。
Capacity :
...
example.com/special-storage : 800Gi
然后,容器就能够请求任意数量(多达 800Gi)字节的特殊存储。
Capacity :
...
example.com/special-storage : 800Gi
清理 这里是一个从节点移除 dongle 资源发布的 PATCH 请求。
PATCH /api/v1/nodes/<your-node-name>/status HTTP/1.1
Accept: application/json
Content-Type: application/json-patch+json
Host: k8s-master:8080
[
{
"op": "remove",
"path": "/status/capacity/example.com~1dongle",
}
]
启动一个代理,以便你可以很容易地向 Kubernetes API 服务器发送请求:
在另一个命令窗口中,发送 HTTP PATCH 请求。用你的节点名称替换 <your-node-name>
:
curl --header "Content-Type: application/json-patch+json" \
--request PATCH \
--data '[{"op": "remove", "path": "/status/capacity/example.com~1dongle"}]' \
http://localhost:8001/api/v1/nodes/<your-node-name>/status
验证 dongle 资源的发布已经被移除:
kubectl describe node <your-node-name> | grep dongle
(你应该看不到任何输出)
接下来 针对应用开发人员 针对集群管理员 4.2.33 - 为系统守护进程预留计算资源 Kubernetes 的节点可以按照 Capacity
调度。默认情况下 pod 能够使用节点全部可用容量。
这是个问题,因为节点自己通常运行了不少驱动 OS 和 Kubernetes 的系统守护进程。
除非为这些系统守护进程留出资源,否则它们将与 Pod 争夺资源并导致节点资源短缺问题。
kubelet
公开了一个名为 'Node Allocatable' 的特性,有助于为系统守护进程预留计算资源。
Kubernetes 推荐集群管理员按照每个节点上的工作负载密度配置 'Node Allocatable'。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.8.
要获知版本信息,请输入
kubectl version
.
你的 kubernetes 服务器版本必须至少是 1.17 版本,才能使用 kubelet
命令行选项 --reserved-cpus
设置显式预留 CPU 列表 。
节点可分配资源
Kubernetes 节点上的 'Allocatable' 被定义为 Pod 可用计算资源量。
调度器不会超额申请 'Allocatable'。
目前支持 'CPU'、'memory' 和 'ephemeral-storage' 这几个参数。
可分配的节点暴露为 API 中 v1.Node
对象的一部分,也是 CLI 中
kubectl describe node
的一部分。
在 kubelet
中,可以为两类系统守护进程预留资源。
启用 QoS 和 Pod 级别的 cgroups 为了恰当地在节点范围实施节点可分配约束,你必须通过 --cgroups-per-qos
标志启用新的 cgroup 层次结构。这个标志是默认启用的。
启用后,kubelet
将在其管理的 cgroup 层次结构中创建所有终端用户的 Pod。
配置 cgroup 驱动 kubelet
支持在主机上使用 cgroup 驱动操作 cgroup 层次结构。
该驱动通过 --cgroup-driver
标志进行配置。
支持的参数值如下:
cgroupfs
是默认的驱动,在主机上直接操作 cgroup 文件系统以对 cgroup
沙箱进行管理。systemd
是可选的驱动,使用 init 系统支持的资源的瞬时切片管理
cgroup 沙箱。取决于相关容器运行时的配置,操作员可能需要选择一个特定的 cgroup 驱动来保证系统正常运行。
例如,如果操作员使用 containerd
运行时提供的 systemd
cgroup 驱动时,
必须配置 kubelet
使用 systemd
cgroup 驱动。
Kube 预留值 Kubelet 标志 :--kube-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
Kubelet 标志 :--kube-reserved-cgroup=
kube-reserved
用来给诸如 kubelet
、容器运行时、节点问题监测器等
Kubernetes 系统守护进程记述其资源预留值。
该配置并非用来给以 Pod 形式运行的系统守护进程预留资源。kube-reserved
通常是节点上 Pod 密度
的函数。
除了 cpu
、内存
和 ephemeral-storage
之外,pid
可用来指定为
Kubernetes 系统守护进程预留指定数量的进程 ID。
要选择性地对 Kubernetes 系统守护进程上执行 kube-reserved
保护,需要把 kubelet 的
--kube-reserved-cgroup
标志的值设置为 kube 守护进程的父控制组。
推荐将 Kubernetes 系统守护进程放置于顶级控制组之下(例如 systemd 机器上的
runtime.slice
)。
理想情况下每个系统守护进程都应该在其自己的子控制组中运行。
请参考这个设计方案 ,
进一步了解关于推荐控制组层次结构的细节。
请注意,如果 --kube-reserved-cgroup
不存在,Kubelet 将 不会 创建它。
如果指定了一个无效的 cgroup,Kubelet 将会失败。就 systemd
cgroup 驱动而言,
你要为所定义的 cgroup 设置名称时要遵循特定的模式:
所设置的名字应该是你为 --kube-reserved-cgroup
所给的参数值加上 .slice
后缀。
系统预留值 Kubelet 标志 :--system-reserved=[cpu=100m][,][memory=100Mi][,][ephemeral-storage=1Gi][,][pid=1000]
Kubelet 标志 :--system-reserved-cgroup=
system-reserved
用于为诸如 sshd
、udev
等系统守护进程记述其资源预留值。
system-reserved
也应该为 kernel
预留 内存
,因为目前 kernel
使用的内存并不记在 Kubernetes 的 Pod 上。
同时还推荐为用户登录会话预留资源(systemd 体系中的 user.slice
)。
除了 cpu
、内存
和 ephemeral-storage
之外,pid
可用来指定为
Kubernetes 系统守护进程预留指定数量的进程 ID。
要想为系统守护进程上可选地实施 system-reserved
约束,请指定 kubelet 的
--system-reserved-cgroup
标志值为 OS 系统守护进程的父级控制组。
推荐将 OS 系统守护进程放在一个顶级控制组之下(例如 systemd 机器上的
system.slice
)。
请注意,如果 --system-reserved-cgroup
不存在,kubelet
不会 创建它。
如果指定了无效的 cgroup,kubelet
将会失败。就 systemd
cgroup 驱动而言,
你在指定 cgroup 名字时要遵循特定的模式:
该名字应该是你为 --system-reserved-cgroup
参数所设置的值加上 .slice
后缀。
显式预留的 CPU 列表 特性状态: Kubernetes v1.17 [stable]
Kubelet 标志 :--reserved-cpus=0-3
reserved-cpus
旨在为操作系统守护程序和 Kubernetes 系统守护程序预留一组明确指定编号的 CPU。
reserved-cpus
适用于不打算针对 cpuset 资源为操作系统守护程序和 Kubernetes
系统守护程序定义独立的顶级 cgroups 的系统。
如果 Kubelet 没有 指定参数 --system-reserved-cgroup
和 --kube-reserved-cgroup
,
则 reserved-cpus
的设置将优先于 --kube-reserved
和 --system-reserved
选项。
此选项是专门为电信/NFV 用例设计的,在这些用例中不受控制的中断或计时器可能会影响其工作负载性能。
你可以使用此选项为系统或 Kubernetes 守护程序以及中断或计时器显式定义 cpuset,
这样系统上的其余 CPU 可以专门用于工作负载,因不受控制的中断或计时器的影响得以降低。
要将系统守护程序、Kubernetes 守护程序和中断或计时器移动到此选项定义的显式
cpuset 上,应使用 Kubernetes 之外的其他机制。
例如:在 CentOS 系统中,可以使用 tuned 工具集来执行此操作。
驱逐阈值 Kubelet 标志 :--eviction-hard=[memory.available<500Mi]
节点级别的内存压力将导致系统内存不足,这将影响到整个节点及其上运行的所有 Pod。
节点可以暂时离线直到内存已经回收为止。为了防止系统内存不足(或减少系统内存不足的可能性),
kubelet 提供了资源不足 管理。
驱逐操作只支持 memory
和 ephemeral-storage
。
通过 --eviction-hard
标志预留一些内存后,当节点上的可用内存降至预留值以下时,
kubelet
将尝试驱逐 Pod。
如果节点上不存在系统守护进程,Pod 将不能使用超过 capacity-eviction-hard
所指定的资源量。
因此,为驱逐而预留的资源对 Pod 是不可用的。
实施节点可分配约束 Kubelet 标志 :--enforce-node-allocatable=pods[,][system-reserved][,][kube-reserved]
调度器将 'Allocatable' 视为 Pod 可用的 capacity
(资源容量)。
kubelet
默认对 Pod 执行 'Allocatable' 约束。
无论何时,如果所有 Pod 的总用量超过了 'Allocatable',驱逐 Pod 的措施将被执行。
有关驱逐策略的更多细节可以在节点压力驱逐 页找到。
可通过设置 kubelet --enforce-node-allocatable
标志值为 pods
控制这个措施。
可选地,通过在同一标志中同时指定 kube-reserved
和 system-reserved
值,
可以使 kubelet
强制实施 kube-reserved
和 system-reserved
约束。
请注意,要想执行 kube-reserved
或者 system-reserved
约束,
需要对应设置 --kube-reserved-cgroup
或者 --system-reserved-cgroup
。
一般原则 系统守护进程一般会被按照类似
Guaranteed 的 Pod
一样对待。
系统守护进程可以在与其对应的控制组中出现突发资源用量,这一行为要作为 Kubernetes 部署的一部分进行管理。
例如,kubelet
应该有它自己的控制组并和容器运行时共享 kube-reserved
资源。
不过,如果执行了 kube-reserved
约束,则 kubelet 不可出现突发负载并用光节点的所有可用资源。
在执行 system-reserved
预留策略时请加倍小心,因为它可能导致节点上的关键系统服务出现 CPU 资源短缺、
因为内存不足而被终止或者无法在节点上创建进程。
建议只有当用户详尽地描述了他们的节点以得出精确的估计值,
并且对该组中进程因内存不足而被杀死时,有足够的信心将其恢复时,
才可以强制执行 system-reserved
策略。
作为起步,可以先针对 pods
上执行 'Allocatable' 约束。 一旦用于追踪系统守护进程的监控和告警的机制到位,可尝试基于用量估计的方式执行 kube-reserved
策略。 随着时间推进,在绝对必要的时候可以执行 system-reserved
策略。 随着时间推进和越来越多特性被加入,kube 系统守护进程对资源的需求可能也会增加。
以后 Kubernetes 项目将尝试减少对节点系统守护进程的利用,但目前这件事的优先级并不是最高。
所以,将来的发布版本中 Allocatable
容量是有可能降低的。
示例场景 这是一个用于说明节点可分配(Node Allocatable)计算方式的示例:
节点拥有 32Gi
memory
、16 CPU
和 100Gi
Storage
资源 --kube-reserved
被设置为 cpu=1,memory=2Gi,ephemeral-storage=1Gi
--system-reserved
被设置为 cpu=500m,memory=1Gi,ephemeral-storage=1Gi
--eviction-hard
被设置为 memory.available<500Mi,nodefs.available<10%
在这个场景下,'Allocatable' 将会是 14.5 CPUs、28.5Gi 内存以及 88Gi
本地存储。
调度器保证这个节点上的所有 Pod 的内存 requests
总量不超过 28.5Gi,存储不超过 '88Gi'。
当 Pod 的内存使用总量超过 28.5Gi 或者磁盘使用总量超过 88Gi 时,kubelet 将会驱逐它们。
如果节点上的所有进程都尽可能多地使用 CPU,则 Pod 加起来不能使用超过 14.5 CPUs 的资源。
当没有执行 kube-reserved
和/或 system-reserved
策略且系统守护进程使用量超过其预留时,
如果节点内存用量高于 31.5Gi 或 storage
大于 90Gi,kubelet 将会驱逐 Pod。
4.2.34 - 限制存储使用量 此示例演示如何限制一个名字空间中的存储使用量。
演示中用到了以下资源:ResourceQuota 、
LimitRange 和
PersistentVolumeClaim 。
准备开始 场景:限制存储使用量 集群管理员代表用户群操作集群,该管理员希望控制单个名字空间可以消耗多少存储空间以控制成本。
该管理员想要限制:
名字空间中持久卷申领(persistent volume claims)的数量 每个申领(claim)可以请求的存储量 名字空间可以具有的累计存储量 使用 LimitRange 限制存储请求 将 LimitRange
添加到名字空间会为存储请求大小强制设置最小值和最大值。
存储是通过 PersistentVolumeClaim
来发起请求的。
执行限制范围控制的准入控制器会拒绝任何高于或低于管理员所设阈值的 PVC。
在此示例中,请求 10Gi 存储的 PVC 将被拒绝,因为它超过了最大 2Gi。
apiVersion : v1
kind : LimitRange
metadata :
name : storagelimits
spec :
limits :
- type : PersistentVolumeClaim
max :
storage : 2Gi
min :
storage : 1Gi
当底层存储提供程序需要某些最小值时,将会用到所设置最小存储请求值。
例如,AWS EBS volumes 的最低要求为 1Gi。
使用 StorageQuota 限制 PVC 数目和累计存储容量 管理员可以限制某个名字空间中的 PVC 个数以及这些 PVC 的累计容量。
如果 PVC 的数目超过任一上限值,新的 PVC 将被拒绝。
在此示例中,名字空间中的第 6 个 PVC 将被拒绝,因为它超过了最大计数 5。
或者,当与上面的 2Gi 最大容量限制结合在一起时,
意味着 5Gi 的最大配额不能支持 3 个都是 2Gi 的 PVC。
后者实际上是向名字空间请求 6Gi 容量,而该名字空间已经设置上限为 5Gi。
apiVersion : v1
kind : ResourceQuota
metadata :
name : storagequota
spec :
hard :
persistentvolumeclaims : "5"
requests.storage : "5Gi"
小结 限制范围对象可以用来设置可请求的存储量上限,而资源配额对象则可以通过申领计数和
累计存储容量有效地限制名字空间耗用的存储量。
这两种机制使得集群管理员能够规划其集群存储预算而不会发生任一项目超量分配的风险。
4.2.35 - 验证已签名容器镜像 特性状态: Kubernetes v1.24 [alpha]
准备开始 这些说明适用于 Kubernetes 1.25。
如果你想要检查其他版本的 Kubernetes 组件的完整性,请查看对应 Kubernetes 版本的文档。
你需要安装以下工具:
cosign
(安装指南 )curl
(通常由你的操作系统提供)验证镜像签名 完整的镜像签名列表请参见发行版本 。
从这个列表中选择一个镜像,并使用 cosign verify
命令来验证它的签名:
COSIGN_EXPERIMENTAL = 1 cosign verify registry.k8s.io/kube-apiserver-amd64:v1.24.0
验证所有控制平面组件镜像 验证所有已签名的控制平面组件镜像,请运行以下命令:
curl -Ls https://sbom.k8s.io/$( curl -Ls https://dl.k8s.io/release/latest.txt) /release | grep 'PackageName: registry.k8s.io/' | awk '{print $2}' > images.txt
input = images.txt
while IFS = read -r image
do
COSIGN_EXPERIMENTAL = 1 cosign verify " $image "
done < " $input "
当你完成某个镜像的验证时,可以在你的 Pod 清单通过摘要值来指定该镜像,例如:
registry-url/image-name@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
。
要了解更多信息,请参考镜像拉取策略 章节。
使用准入控制器验证镜像签名 有一些非控制平面镜像
(例如 conformance 镜像 ),
也可以在部署时使用
sigstore policy-controller
控制器验证其签名。如要使用 policy-controller
,下面是一些有帮助的资源:
4.2.36 - 以非 root 用户身份运行 Kubernetes 节点组件 特性状态: Kubernetes v1.22 [alpha]
这个文档描述了怎样不使用 root 特权,而是通过使用 用户命名空间
去运行 Kubernetes 节点组件(例如 kubelet、CRI、OCI、CNI)。
这种技术也叫做 rootless 模式(Rootless mode) 。
说明: 这个文档描述了怎么以非 root 用户身份运行 Kubernetes 节点组件以及 Pod。
如果你只是想了解如何以非 root 身份运行 Pod,请参阅
SecurityContext 。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 1.22.
要获知版本信息,请输入 kubectl version
.
使用 Rootless 模式的 Docker/Podman 运行 Kubernetes kind kind 支持使用 Rootless 模式的 Docker 或者 Podman 运行 Kubernetes。
请参阅使用 Rootless 模式的 Docker 运行 kind 。
minikube minikube 也支持使用 Rootless 模式的 Docker 或 Podman 运行 Kubernetes。
请参阅 Minikube 文档:
在非特权容器内运行 Kubernetes 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
sysbox Sysbox 是一个开源容器运行时
(类似于 “runc”),支持在 Linux 用户命名空间隔离的非特权容器内运行系统级工作负载,
比如 Docker 和 Kubernetes。
查看 Sysbox 快速入门指南: Kubernetes-in-Docker
了解更多细节。
Sysbox 支持在非特权容器内运行 Kubernetes,
而不需要 cgroup v2 和 “KubeletInUserNamespace” 特性门控。
Sysbox 通过在容器内暴露特定的 /proc
和 /sys
文件系统,
以及其它一些先进的操作系统虚拟化技术来实现。
直接在主机上运行 Rootless 模式的 Kubernetes 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
K3s K3s 实验性支持了 Rootless 模式。
请参阅使用 Rootless 模式运行 K3s
页面中的用法.
Usernetes Usernetes 是 Kubernetes 的一个参考发行版,
它可以在不使用 root 特权的情况下安装在 $HOME
目录下。
Usernetes 支持使用 containerd 和 CRI-O 作为 CRI 运行时。
Usernetes 支持配置了 Flannel (VXLAN)的多节点集群。
关于用法,请参阅 Usernetes 仓库 。
手动部署一个在用户命名空间运行 kubelet 的节点 本节提供在用户命名空间手动运行 Kubernetes 的注意事项。
说明: 本节是面向 Kubernetes 发行版的开发者,而不是最终用户。
创建用户命名空间 第一步是创建一个 用户命名空间 。
如果你正在尝试使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman 或 LXC/LXD)
运行 Kubernetes,那么你已经准备就绪,可以直接跳到下一小节。
否则你需要通过传递参数 CLONE_NEWUSER
调用 unshare(2)
,自己创建一个命名空间。
用户命名空间也可以通过如下所示的命令行工具取消共享:
在取消命名空间的共享之后,你也必须对其它的命名空间例如 mount 命名空间取消共享。
在取消 mount 命名空间的共享之后,你不 需要调用 chroot()
或者 pivot_root()
,
但是你必须在这个命名空间内 挂载可写的文件系统到几个目录上。
请确保这个命名空间内 (不是这个命名空间外部)至少以下几个目录是可写的:
/etc
/run
/var/logs
/var/lib/kubelet
/var/lib/cni
/var/lib/containerd
(参照 containerd)/var/lib/containers
(参照 CRI-O)创建委派 cgroup 树 除了用户命名空间,你也需要有一个版本为 cgroup v2 的可写 cgroup 树。
说明: Kubernetes 需要 cgroup v2 才支持在用户命名空间运行节点组件。
cgroup v1 是不支持的。
如果你在一个采用 systemd 机制的主机上使用用户命名空间的容器(例如 Rootless 模式的 Docker/Podman
或 LXC/LXD)来运行 Kubernetes,那么你已经准备就绪。
否则你必须创建一个具有 Delegate=yes
属性的 systemd 单元,来委派一个具有可写权限的 cgroup 树。
在你的节点上,systemd 必须已经配置为允许委派。更多细节请参阅 Rootless 容器文档的
cgroup v2 部分。
配置网络 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
节点组件的网络命名空间必须有一个非本地回路的网卡。它可以使用
slirp4netns 、
VPNKit 、
lxc-user-nic(1)
等工具进行配置。
Pod 的网络命名空间可以使用常规的 CNI 插件配置。对于多节点的网络,已知 Flannel (VXLAN、8472/UDP) 可以正常工作。
诸如 kubelet 端口(10250/TCP)和 NodePort
服务端口之类的端口必须通过外部端口转发器
(例如 RootlessKit、slirp4netns 或
socat(1) ) 从节点网络命名空间暴露给主机。
你可以使用 K3s 的端口转发器。更多细节请参阅
在 Rootless 模式下运行 K3s 。
配置 CRI kubelet 依赖于容器运行时。你需要部署一个容器运行时(例如 containerd 或 CRI-O),
并确保它在 kubelet 启动之前已经在用户命名空间内运行。
containerd 1.4 开始支持在用户命名空间运行 containerd 的 CRI 插件。
在用户命名空间运行 containerd 需要在 /etc/containerd/containerd-config.toml
文件包含以下配置:
version = 2
[plugins."io.containerd.grpc.v1.cri" ]
# 禁用 AppArmor
disable_apparmor = true
# 忽略配置 oom_score_adj 时的错误
restrict_oom_score_adj = true
# 禁用 hugetlb cgroup v2 控制器(因为 systemd 不支持委派 hugetlb controller)
disable_hugetlb_controller = true
[plugins."io.containerd.grpc.v1.cri" .containerd]
# 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux
snapshotter = "fuse-overlayfs"
[plugins."io.containerd.grpc.v1.cri" .containerd.runtimes.runc.options]
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 SystemdCgroup 驱动
# (除非你在命名空间内运行了另一个 systemd)
SystemdCgroup = false
CRI-O 1.22 开始支持在用户命名空间运行 CRI-O。
CRI-O 必须配置一个环境变量 _CRIO_ROOTLESS=1
。
也推荐使用 /etc/crio/crio.conf
文件内的以下配置:
[crio]
storage_driver = "overlay"
# 如果内核 >= 5.11 , 也可以使用 non-fuse overlayfs, 但需要禁用 SELinux
storage_option = ["overlay.mount_program=/usr/local/bin/fuse-overlayfs" ]
[crio.runtime]
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动
# (除非你在命名空间内运行了另一个 systemd)
cgroup_manager = "cgroupfs"
配置 kubelet 在用户命名空间运行 kubelet 必须进行如下配置:
apiVersion : kubelet.config.k8s.io/v1beta1
kind : KubeletConfiguration
featureGates :
KubeletInUserNamespace : true
# 我们使用的 cgroupfs 已经被 systemd 委派,所以我们不使用 "systemd" 驱动
# (除非你在命名空间内运行了另一个 systemd)
cgroupDriver : "cgroupfs"
当 KubeletInUserNamespace
特性门控被启用时, kubelet 会忽略节点内由于配置如下几个 sysctl
参数值而可能产生的错误。
vm.overcommit_memory
vm.panic_on_oom
kernel.panic
kernel.panic_on_oops
kernel.keys.root_maxkeys
kernel.keys.root_maxbytes
.在用户命名空间内, kubelet 也会忽略任何由于打开 /dev/kmsg
而产生的错误。
这个特性门控也允许 kube-proxy 忽略由于配置 RLIMIT_NOFILE
而产生的一个错误。
KubeletInUserNamespace
特性门控从 Kubernetes v1.22 被引入, 标记为 "alpha" 状态。
通过挂载特制的 proc 文件系统 (比如 Sysbox ),
也可以在不使用这个特性门控的情况下在用户命名空间运行 kubelet,但这不受官方支持。
配置 kube-proxy 在用户命名空间运行 kube-proxy 需要进行以下配置:
apiVersion : kubeproxy.config.k8s.io/v1alpha1
kind : KubeProxyConfiguration
mode : "iptables" # or "userspace"
conntrack :
# 跳过配置 sysctl 的值 "net.netfilter.nf_conntrack_max"
maxPerCore : 0
# 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_established"
tcpEstablishedTimeout : 0s
# 跳过配置 "net.netfilter.nf_conntrack_tcp_timeout_close"
tcpCloseWaitTimeout : 0s
注意事项 更多细节请参阅 rootlesscontaine.rs 站点的 Caveats and Future work 页面。
另请参见 4.2.37 - 在 Kubernetes 集群中使用 NodeLocal DNSCache 特性状态: Kubernetes v1.18 [stable]
本页概述了 Kubernetes 中的 NodeLocal DNSCache 功能。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
引言 NodeLocal DNSCache 通过在集群节点上作为 DaemonSet 运行 DNS 缓存代理来提高集群 DNS 性能。
在当今的体系结构中,运行在 'ClusterFirst' DNS 模式下的 Pod 可以连接到 kube-dns serviceIP
进行 DNS 查询。
通过 kube-proxy 添加的 iptables 规则将其转换为 kube-dns/CoreDNS 端点。
借助这种新架构,Pod 将可以访问在同一节点上运行的 DNS 缓存代理,从而避免 iptables DNAT 规则和连接跟踪。
本地缓存代理将查询 kube-dns 服务以获取集群主机名的缓存缺失(默认为 "cluster.local
" 后缀)。
动机 使用当前的 DNS 体系结构,如果没有本地 kube-dns/CoreDNS 实例,则具有最高 DNS QPS
的 Pod 可能必须延伸到另一个节点。
在这种场景下,拥有本地缓存将有助于改善延迟。 跳过 iptables DNAT 和连接跟踪将有助于减少
conntrack 竞争 并避免
UDP DNS 条目填满 conntrack 表。 从本地缓存代理到 kube-dns 服务的连接可以升级为 TCP。
TCP conntrack 条目将在连接关闭时被删除,相反 UDP 条目必须超时
(默认
nf_conntrack_udp_timeout
是 30 秒)。 将 DNS 查询从 UDP 升级到 TCP 将减少由于被丢弃的 UDP 包和 DNS 超时而带来的尾部等待时间;
这类延时通常长达 30 秒(3 次重试 + 10 秒超时)。
由于 nodelocal 缓存监听 UDP DNS 查询,应用不需要变更。 可以重新启用负缓存,从而减少对 kube-dns 服务的查询数量。 架构图 启用 NodeLocal DNSCache 之后,DNS 查询所遵循的路径如下:
Nodelocal DNSCache 流 此图显示了 NodeLocal DNSCache 如何处理 DNS 查询。
配置 说明: NodeLocal DNSCache 的本地侦听 IP 地址可以是任何地址,只要该地址不和你的集群里现有的 IP 地址发生冲突。
推荐使用本地范围内的地址,例如,IPv4 链路本地区段 '169.254.0.0/16' 内的地址,
或者 IPv6 唯一本地地址区段 'fd00::/8' 内的地址。
可以使用以下步骤启动此功能:
如果使用 IPv6,在使用 'IP:Port' 格式的时候需要把 CoreDNS 配置文件里的所有 IPv6 地址用方括号包起来。
如果你使用上述的示例清单,
需要把配置行 L70
修改为: "health [__PILLAR__LOCAL__DNS__]:8080
"。 如果 kube-proxy 运行在 IPTABLES 模式:
sed -i "s/__PILLAR__LOCAL__DNS__/ $localdns /g; s/__PILLAR__DNS__DOMAIN__/ $domain /g; s/__PILLAR__DNS__SERVER__/ $kubedns /g" nodelocaldns.yaml
node-local-dns Pod 会设置 __PILLAR__CLUSTER__DNS__
和 __PILLAR__UPSTREAM__SERVERS__
。
在此模式下, node-local-dns Pod 会同时侦听 kube-dns 服务的 IP 地址和
<node-local-address>
的地址,以便 Pod 可以使用其中任何一个 IP 地址来查询 DNS 记录。
如果 kube-proxy 运行在 IPVS 模式:
sed -i "s/__PILLAR__LOCAL__DNS__/ $localdns /g; s/__PILLAR__DNS__DOMAIN__/ $domain /g; s/,__PILLAR__DNS__SERVER__//g; s/__PILLAR__CLUSTER__DNS__/ $kubedns /g" nodelocaldns.yaml
在此模式下,node-local-dns Pod 只会侦听 <node-local-address>
的地址。
node-local-dns 接口不能绑定 kube-dns 的集群 IP 地址,因为 IPVS 负载均衡使用的接口已经占用了该地址。
node-local-dns Pod 会设置 __PILLAR__UPSTREAM__SERVERS__
。
运行 kubectl create -f nodelocaldns.yaml
如果 kube-proxy 运行在 IPVS 模式,需要修改 kubelet 的 --cluster-dns
参数
NodeLocal DNSCache 正在侦听的 <node-local-address>
地址。
否则,不需要修改 --cluster-dns
参数,因为 NodeLocal DNSCache 会同时侦听
kube-dns 服务的 IP 地址和 <node-local-address>
的地址。
启用后,node-local-dns
Pod 将在每个集群节点上的 kube-system
名字空间中运行。
此 Pod 在缓存模式下运行 CoreDNS ,
因此每个节点都可以使用不同插件公开的所有 CoreDNS 指标。
如果要禁用该功能,你可以使用 kubectl delete -f <manifest>
来删除 DaemonSet。
你还应该回滚你对 kubelet 配置所做的所有改动。
StubDomains 和上游服务器配置 node-local-dns
Pod 能够自动读取 kube-system
名字空间中 kube-dns
ConfigMap
中保存的 StubDomains 和上游服务器信息。ConfigMap
中的内容需要遵从此示例 中所给的格式。
node-local-dns
ConfigMap 也可被直接修改,使用 Corefile 格式设置 stubDomain 配置。
某些云厂商可能不允许直接修改 node-local-dns
ConfigMap 的内容。
在这种情况下,可以更新 kube-dns
ConfigMap。
设置内存限制 node-local-dns
Pod 使用内存来保存缓存项并处理查询。
由于它们并不监视 Kubernetes 对象变化,集群规模或者 Service/Endpoints
的数量都不会直接影响内存用量。内存用量会受到 DNS 查询模式的影响。
根据 CoreDNS 文档 ,
The default cache size is 10000 entries, which uses about 30 MB when completely filled.
(默认的缓存大小是 10000 个表项,当完全填充时会使用约 30 MB 内存)
这一数值是(缓存完全被填充时)每个服务器块的内存用量。
通过设置小一点的缓存大小可以降低内存用量。
并发查询的数量会影响内存需求,因为用来处理查询请求而创建的 Go 协程都需要一定量的内存。
你可以在 forward 插件中使用 max_concurrent
选项设置并发查询数量上限。
如果一个 node-local-dns
Pod 尝试使用的内存超出可提供的内存量
(因为系统资源总量的,或者所配置的资源约束 )的原因,
操作系统可能会关闭这一 Pod 的容器。
发生这种情况时,被终止的("OOMKilled")容器不会清理其启动期间所添加的定制包过滤规则。
该 node-local-dns
容器应该会被重启(因其作为 DaemonSet 的一部分被管理),
但因上述原因可能每次容器失败时都会导致 DNS 有一小段时间不可用:
the packet filtering rules direct DNS queries to a local Pod that is unhealthy
(包过滤器规则将 DNS 查询转发到本地某个不健康的 Pod)。
通过不带限制地运行 node-local-dns
Pod 并度量其内存用量峰值,你可以为其确定一个合适的内存限制值。
你也可以安装并使用一个运行在 “Recommender Mode(建议者模式)” 的
VerticalPodAutoscaler ,
并查看该组件输出的建议信息。
4.2.38 - 在 Kubernetes 集群中使用 sysctl 特性状态: Kubernetes v1.21 [stable]
本文档介绍如何通过 sysctl
接口在 Kubernetes 集群中配置和使用内核参数。
说明: 从 Kubernetes 1.23 版本开始,kubelet 支持使用
/
或
.
作为 sysctl 参数的分隔符。
从 Kubernetes 1.25 版本开始,支持为 Pod 设置 sysctl 时使用设置名字带有斜线的 sysctl。
例如,你可以使用点或者斜线作为分隔符表示相同的 sysctl 参数,以点作为分隔符表示为:
kernel.shm_rmid_forced
,
或者以斜线作为分隔符表示为:
kernel/shm_rmid_forced
。
更多 sysctl 参数转换方法详情请参考 Linux man-pages
sysctl.d(5) 。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
对一些步骤,你需要能够重新配置在你的集群里运行的 kubelet 命令行的选项。
获取 Sysctl 的参数列表 在 Linux 中,管理员可以通过 sysctl 接口修改内核运行时的参数。在 /proc/sys/
虚拟文件系统下存放许多内核参数。这些参数涉及了多个内核子系统,如:
内核子系统(通常前缀为: kernel.
) 网络子系统(通常前缀为: net.
) 虚拟内存子系统(通常前缀为: vm.
) MDADM 子系统(通常前缀为: dev.
) 更多子系统请参见内核文档 。 若要获取完整的参数列表,请执行以下命令:
启用非安全的 Sysctl 参数 sysctl 参数分为 安全 和 非安全的 。
安全 的 sysctl 参数除了需要设置恰当的命名空间外,在同一节点上的不同 Pod
之间也必须是 相互隔离的 。这意味着 Pod 上设置 安全 sysctl 参数:
必须不能影响到节点上的其他 Pod 必须不能损害节点的健康 必须不允许使用超出 Pod 的资源限制的 CPU 或内存资源。 至今为止,大多数 有命名空间的 sysctl 参数不一定被认为是 安全 的。
以下几种 sysctl 参数是 安全的 :
kernel.shm_rmid_forced
net.ipv4.ip_local_port_range
net.ipv4.tcp_syncookies
net.ipv4.ping_group_range
(从 Kubernetes 1.18 开始)net.ipv4.ip_unprivileged_port_start
(从 Kubernetes 1.22 开始)。说明: 示例中的 net.ipv4.tcp_syncookies
在Linux 内核 4.4 或更低的版本中是无命名空间的。
在未来的 Kubernetes 版本中,若 kubelet 支持更好的隔离机制,
则上述列表中将会列出更多 安全的 sysctl 参数。
所有 安全的 sysctl 参数都默认启用。
所有 非安全的 sysctl 参数都默认禁用,且必须由集群管理员在每个节点上手动开启。
那些设置了不安全 sysctl 参数的 Pod 仍会被调度,但无法正常启动。
参考上述警告,集群管理员只有在一些非常特殊的情况下(如:高可用或实时应用调整),
才可以启用特定的 非安全的 sysctl 参数。
如需启用 非安全的 sysctl 参数,请你在每个节点上分别设置 kubelet 命令行参数,例如:
kubelet --allowed-unsafe-sysctls \
'kernel.msg*,net.core.somaxconn' ...
如果你使用 Minikube ,可以通过 extra-config
参数来配置:
minikube start --extra-config= "kubelet.allowed-unsafe-sysctls=kernel.msg*,net.core.somaxconn" ...
只有 有命名空间的 sysctl 参数可以通过该方式启用。
设置 Pod 的 Sysctl 参数 目前,在 Linux 内核中,有许多的 sysctl 参数都是 有命名空间的 。
这就意味着可以为节点上的每个 Pod 分别去设置它们的 sysctl 参数。
在 Kubernetes 中,只有那些有命名空间的 sysctl 参数可以通过 Pod 的 securityContext 对其进行配置。
以下列出有命名空间的 sysctl 参数,在未来的 Linux 内核版本中,此列表可能会发生变化。
kernel.shm*
,kernel.msg*
,kernel.sem
,fs.mqueue.*
,net.*
(内核中可以在容器命名空间里被更改的网络配置项相关参数)。然而也有一些特例
(例如,net.netfilter.nf_conntrack_max
和 net.netfilter.nf_conntrack_expect_max
可以在容器命名空间里被更改,但它们是非命名空间的)。没有命名空间的 sysctl 参数称为 节点级别的 sysctl 参数。
如果需要对其进行设置,则必须在每个节点的操作系统上手动地去配置它们,
或者通过在 DaemonSet 中运行特权模式容器来配置。
可使用 Pod 的 securityContext 来配置有命名空间的 sysctl 参数,
securityContext 应用于同一个 Pod 中的所有容器。
此示例中,使用 Pod SecurityContext 来对一个安全的 sysctl 参数
kernel.shm_rmid_forced
以及两个非安全的 sysctl 参数
net.core.somaxconn
和 kernel.msgmax
进行设置。
在 Pod 规约中对 安全的 和 非安全的 sysctl 参数不做区分。
警告: 为了避免破坏操作系统的稳定性,请你在了解变更后果之后再修改 sysctl 参数。
apiVersion : v1
kind : Pod
metadata :
name : sysctl-example
spec :
securityContext :
sysctls :
- name : kernel.shm_rmid_forced
value : "0"
- name : net.core.somaxconn
value : "1024"
- name : kernel.msgmax
value : "65536"
...
警告: 由于 非安全的 sysctl 参数其本身具有不稳定性,在使用 非安全的 sysctl 参数时可能会导致一些严重问题,
如容器的错误行为、机器资源不足或节点被完全破坏,用户需自行承担风险。
最佳实践方案是将集群中具有特殊 sysctl 设置的节点视为 有污点的 ,并且只调度需要使用到特殊
sysctl 设置的 Pod 到这些节点上。建议使用 Kubernetes
的污点和容忍度特性 来实现它。
设置了 非安全的 sysctl 参数的 Pod 在禁用了这两种 非安全的 sysctl 参数配置的节点上启动都会失败。
与 节点级别的 sysctl 一样,
建议开启污点和容忍度特性 或
为节点配置污点 以便将
Pod 调度到正确的节点之上。
4.2.39 - 在集群中使用级联删除 本页面向你展示如何设置在你的集群执行垃圾收集
时要使用的级联删除
类型。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你还需要创建一个 Deployment 示例
以试验不同类型的级联删除。你需要为每种级联删除类型来重建 Deployment。
检查 Pod 上的属主引用 检查确认你的 Pods 上存在 ownerReferences
字段:
kubectl get pods -l app = nginx --output= yaml
输出中包含 ownerReferences
字段,类似这样:
apiVersion : v1
...
ownerReferences :
- apiVersion : apps/v1
blockOwnerDeletion : true
controller : true
kind : ReplicaSet
name : nginx-deployment-6b474476c4
uid : 4fdcd81c-bd5d-41f7-97af-3a3b759af9a7
...
使用前台级联删除 默认情况下,Kubernetes 使用后台级联删除
以删除依赖某对象的其他对象。取决于你的集群所运行的 Kubernetes 版本,
你可以使用 kubectl
或者 Kubernetes API 来切换到前台级联删除。
要获知版本信息,请输入 kubectl version
.
你可以使用 kubectl
或者 Kubernetes API 来基于前台级联删除来删除对象。
使用 kubectl
运行下面的命令:
kubectl delete deployment nginx-deployment --cascade= foreground
使用 Kubernetes API
启动一个本地代理会话:
kubectl proxy --port= 8080
使用 curl
来触发删除操作:
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Foreground"}' \
-H "Content-Type: application/json"
输出中包含 foregroundDeletion
finalizer ,
类似这样:
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "nginx-deployment",
"namespace": "default",
"uid": "d1ce1b02-cae8-4288-8a53-30e84d8fa505",
"resourceVersion": "1363097",
"creationTimestamp": "2021-07-08T20:24:37Z",
"deletionTimestamp": "2021-07-08T20:27:39Z",
"finalizers": [
"foregroundDeletion"
]
...
使用后台级联删除 创建一个 Deployment 示例 。基于你的集群所运行的 Kubernetes 版本,使用 kubectl
或者 Kubernetes API 来删除 Deployment。
要获知版本信息,请输入 kubectl version
. 你可以使用 kubectl
或者 Kubernetes API 来执行后台级联删除方式的对象删除操作。
Kubernetes 默认采用后台级联删除方式,如果你在运行下面的命令时不指定
--cascade
标志或者 propagationPolicy
参数时,用这种方式来删除对象。
使用 kubectl
运行下面的命令:
kubectl delete deployment nginx-deployment --cascade= background
使用 Kubernetes API
启动一个本地代理会话:
kubectl proxy --port= 8080
使用 curl
来触发删除操作:
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Background"}' \
-H "Content-Type: application/json"
输出类似于:
"kind": "Status",
"apiVersion": "v1",
...
"status": "Success",
"details": {
"name": "nginx-deployment",
"group": "apps",
"kind": "deployments",
"uid": "cc9eefb9-2d49-4445-b1c1-d261c9396456"
}
删除属主对象和孤立的依赖对象 默认情况下,当你告诉 Kubernetes 删除某个对象时,
控制器 也会删除依赖该对象
的其他对象。
取决于你的集群所运行的 Kubernetes 版本,你也可以使用 kubectl
或者 Kubernetes
API 来让 Kubernetes 孤立 这些依赖对象。
要获知版本信息,请输入 kubectl version
.
使用 kubectl
运行下面的命令:
kubectl delete deployment nginx-deployment --cascade= orphan
使用 Kubernetes API
启动一个本地代理会话:
kubectl proxy --port= 8080
使用 curl
来触发删除操作:
curl -X DELETE localhost:8080/apis/apps/v1/namespaces/default/deployments/nginx-deployment \
-d '{"kind":"DeleteOptions","apiVersion":"v1","propagationPolicy":"Orphan"}' \
-H "Content-Type: application/json"
输出中在 finalizers
字段中包含 orphan
,如下所示:
"kind": "Deployment",
"apiVersion": "apps/v1",
"namespace": "default",
"uid": "6f577034-42a0-479d-be21-78018c466f1f",
"creationTimestamp": "2021-07-09T16:46:37Z",
"deletionTimestamp": "2021-07-09T16:47:08Z",
"deletionGracePeriodSeconds": 0,
"finalizers": [
"orphan"
],
...
你可以检查 Deployment 所管理的 Pods 仍然处于运行状态:
kubectl get pods -l app = nginx
接下来 4.2.40 - 在运行中的集群上重新配置节点的 kubelet 特性状态: Kubernetes v1.22 [deprecated]
注意: 动态 kubelet 配置
功能在 Kubernetes 1.22 版本弃用,并在 1.24 版本中移除。
请选择其他方法将配置分发到集群中的节点。
动态 kubelet 配置
允许你通过部署并配置节点 使用的
ConfigMap ,
达到更改正在运行的 Kubernetes 集群的 kubelet
配置的目的。
请在早期版本的文档 中寻找有关此功能的文档。
不再使用动态 Kubelet 配置 这里没有跨不同的 Kubernetes 发行版替换这个功能的建议方法。
如果你使用托管 Kubernetes 版本,
请咨询托管 Kubernetes 的供应商,以获得自定义 Kubernetes 的最佳实践。
如果你使用的是 kubeadm
,
请参考使用 kubeadm 配置集群中的各个 kubelet 。
为了停止使用动态 Kubelet 配置功能,
应该使用替代机制分发 kubelet 配置文件。
为了使配置生效,必须更新配置文件并重新启动 kubelet。
请参考通过配置文件设置 Kubelet 参数 。
请注意,从 v1.24 开始 DynamicKubeletConfig
特性门控无法在 kubelet 上设置,
因为不会生效。在 v1.26 之前 API 服务器和控制器管理器不会移除该特性门控。
这是专为控制面支持有旧版本 kubelet 的节点以及满足
Kubernetes 版本偏差策略 。
4.2.41 - 自定义 DNS 服务 本页说明如何配置 DNS Pod ,以及定制集群中 DNS 解析过程。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的集群必须运行 CoreDNS 插件。
你的 Kubernetes 服务器版本必须不低于版本 v1.12.
要获知版本信息,请输入 kubectl version
.
介绍 DNS 是使用 插件管理器
集群插件 自动启动的 Kubernetes 内置服务。
说明: CoreDNS 服务在其 metadata.name
字段被命名为 kube-dns
。
这是为了能够与依靠传统 kube-dns
服务名称来解析集群内部地址的工作负载具有更好的互操作性。
使用 kube-dns
作为服务名称可以抽离共有名称之后运行的是哪个 DNS 提供程序这一实现细节。
如果你在使用 Deployment 运行 CoreDNS,则该 Deployment 通常会向外暴露为一个具有
静态 IP 地址 Kubernetes 服务。
kubelet 使用 --cluster-dns=<DNS 服务 IP>
标志将 DNS 解析器的信息传递给每个容器。
DNS 名称也需要域名。你可在 kubelet 中使用 --cluster-domain=<默认本地域名>
标志配置本地域名。
DNS 服务器支持正向查找(A 和 AAAA 记录)、端口发现(SRV 记录)、反向 IP 地址发现(PTR 记录)等。
更多信息,请参见 Service 与 Pod 的 DNS 。
如果 Pod 的 dnsPolicy
设置为 default
,则它将从 Pod 运行所在节点继承名称解析配置。
Pod 的 DNS 解析行为应该与节点相同。
但请参阅已知问题 。
如果你不想这样做,或者想要为 Pod 使用其他 DNS 配置,则可以使用 kubelet 的
--resolv-conf
标志。将此标志设置为 "" 可以避免 Pod 继承 DNS。
将其设置为有别于 /etc/resolv.conf
的有效文件路径可以设定 DNS 继承不同的配置。
CoreDNS CoreDNS 是通用的权威 DNS 服务器,可以用作集群 DNS,符合
DNS 规范 。
CoreDNS ConfigMap 选项 CoreDNS 是模块化且可插拔的 DNS 服务器,每个插件都为 CoreDNS 添加了新功能。
可以通过维护 Corefile ,即 CoreDNS 配置文件,
来配置 CoreDNS 服务器。作为一个集群管理员,你可以修改 CoreDNS Corefile 的
ConfigMap ,
以更改 DNS 服务发现针对该集群的工作方式。
在 Kubernetes 中,CoreDNS 安装时使用如下默认 Corefile 配置:
apiVersion : v1
kind : ConfigMap
metadata :
name : coredns
namespace : kube-system
data :
Corefile : |
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
Corefile 配置包括以下 CoreDNS 插件 :
errors :错误记录到标准输出。
health :在 http://localhost:8080/health
处提供 CoreDNS 的健康报告。
在这个扩展语法中,lameduck
会使此进程不健康,等待 5 秒后进程被关闭。
ready :在端口 8181 上提供的一个 HTTP 端点,
当所有能够表达自身就绪的插件都已就绪时,在此端点返回 200 OK。
kubernetes :CoreDNS 将基于服务和 Pod 的 IP 来应答 DNS 查询。
你可以在 CoreDNS 网站找到有关此插件的更多细节 。
你可以使用 ttl
来定制响应的 TTL。默认值是 5 秒钟。TTL 的最小值可以是 0 秒钟,
最大值为 3600 秒。将 TTL 设置为 0 可以禁止对 DNS 记录进行缓存。 pods insecure
选项是为了与 kube-dns 向后兼容。你可以使用 pods verified
选项,该选项使得仅在相同名字空间中存在具有匹配 IP 的 Pod 时才返回 A 记录。 如果你不使用 Pod 记录,则可以使用 pods disabled
选项。 prometheus :CoreDNS 的度量指标值以
Prometheus 格式(也称为 OpenMetrics)在 http://localhost:9153/metrics
上提供。forward : 不在 Kubernetes 集群域内的任何查询都将转发到预定义的解析器 (/etc/resolv.conf)。cache :启用前端缓存。loop :检测简单的转发环,如果发现死循环,则中止 CoreDNS 进程。reload :允许自动重新加载已更改的 Corefile。
编辑 ConfigMap 配置后,请等待两分钟,以使更改生效。loadbalance :这是一个轮转式 DNS 负载均衡器,
它在应答中随机分配 A、AAAA 和 MX 记录的顺序。你可以通过修改 ConfigMap 来更改默认的 CoreDNS 行为。
使用 CoreDNS 配置存根域和上游域名服务器 CoreDNS 能够使用 forward 插件 配置存根域和上游域名服务器。
示例 如果集群操作员在 "10.150.0.1" 处运行了 Consul 域服务器,
且所有 Consul 名称都带有后缀 .consul.local
。要在 CoreDNS 中对其进行配置,
集群管理员可以在 CoreDNS 的 ConfigMap 中创建加入以下字段。
consul.local:53 {
errors
cache 30
forward . 10.150.0.1
}
要显式强制所有非集群 DNS 查找通过特定的域名服务器(位于 172.16.0.1),可将 forward
指向该域名服务器,而不是 /etc/resolv.conf
。
forward . 172.16.0.1
最终的包含默认的 Corefile
配置的 ConfigMap 如下所示:
apiVersion : v1
kind : ConfigMap
metadata :
name : coredns
namespace : kube-system
data :
Corefile : |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . 172.16.0.1
cache 30
loop
reload
loadbalance
}
consul.local:53 {
errors
cache 30
forward . 10.150.0.1
}
说明: CoreDNS 不支持 FQDN 作为存根域和域名服务器(例如 "ns.foo.com")。
转换期间,CoreDNS 配置中将忽略所有的 FQDN 域名服务器。
接下来 4.2.42 - 自动扩缩集群 DNS 服务 本页展示了如何在集群中启用和配置 DNS 服务的自动扩缩功能。
准备开始 确定是否 DNS 水平自动扩缩特性已经启用 在 kube-system 命名空间中列出集群中的 Deployments :
kubectl get deployment --namespace= kube-system
输出类似如下这样:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
...
dns-autoscaler 1 1 1 1 ...
...
如果在输出中看到 “dns-autoscaler”,说明 DNS 水平自动扩缩已经启用,可以跳到
调优自动扩缩参数 。
获取 DNS Deployment 的名称 列出集群内 kube-system 名字空间中的 DNS Deployment:
kubectl get deployment -l k8s-app= kube-dns --namespace= kube-system
输出类似如下这样:
NAME READY UP-TO-DATE AVAILABLE AGE
...
coredns 2/2 2 2 ...
...
如果看不到 DNS 服务的 Deployment,你也可以通过名字来查找:
kubectl get deployment --namespace= kube-system
并在输出中寻找名称为 coredns
或 kube-dns
的 Deployment。
你的扩缩目标为:
Deployment/<your-deployment-name>
其中 <your-deployment-name>
是 DNS Deployment 的名称。
例如,如果你的 DNS Deployment 名称是 coredns
,则你的扩展目标是 Deployment/coredns。
说明: CoreDNS 是 Kubernetes 的默认 DNS 服务。CoreDNS 设置标签 k8s-app=kube-dns
,
以便能够在原来使用 kube-dns
的集群中工作。
启用 DNS 水平自动扩缩 在本节,我们创建一个 Deployment。Deployment 中的 Pod 运行一个基于
cluster-proportional-autoscaler-amd64
镜像的容器。
创建文件 dns-horizontal-autoscaler.yaml
,内容如下所示:
kind : ServiceAccount
apiVersion : v1
metadata :
name : kube-dns-autoscaler
namespace : kube-system
---
kind : ClusterRole
apiVersion : rbac.authorization.k8s.io/v1
metadata :
name : system:kube-dns-autoscaler
rules :
- apiGroups : ["" ]
resources : ["nodes" ]
verbs : ["list" , "watch" ]
- apiGroups : ["" ]
resources : ["replicationcontrollers/scale" ]
verbs : ["get" , "update" ]
- apiGroups : ["apps" ]
resources : ["deployments/scale" , "replicasets/scale" ]
verbs : ["get" , "update" ]
# 待以下 issue 修复后,请删除 Configmaps
# kubernetes-incubator/cluster-proportional-autoscaler#16
- apiGroups : ["" ]
resources : ["configmaps" ]
verbs : ["get" , "create" ]
---
kind : ClusterRoleBinding
apiVersion : rbac.authorization.k8s.io/v1
metadata :
name : system:kube-dns-autoscaler
subjects :
- kind : ServiceAccount
name : kube-dns-autoscaler
namespace : kube-system
roleRef :
kind : ClusterRole
name : system:kube-dns-autoscaler
apiGroup : rbac.authorization.k8s.io
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : kube-dns-autoscaler
namespace : kube-system
labels :
k8s-app : kube-dns-autoscaler
kubernetes.io/cluster-service : "true"
spec :
selector :
matchLabels :
k8s-app : kube-dns-autoscaler
template :
metadata :
labels :
k8s-app : kube-dns-autoscaler
spec :
priorityClassName : system-cluster-critical
securityContext :
seccompProfile :
type : RuntimeDefault
supplementalGroups : [ 65534 ]
fsGroup : 65534
nodeSelector :
kubernetes.io/os : linux
containers :
- name : autoscaler
image : registry.k8s.io/cpa/cluster-proportional-autoscaler:1.8.4
resources :
requests :
cpu : "20m"
memory : "10Mi"
command :
- /cluster-proportional-autoscaler
- --namespace=kube-system
- --configmap=kube-dns-autoscaler
# 应该保持目标与 cluster/addons/dns/kube-dns.yaml.base 同步。
- --target=<SCALE_TARGET>
# 当集群使用大节点(有更多核)时,“coresPerReplica”应该占主导地位。
# 如果使用小节点,“nodesPerReplica“ 应该占主导地位。
- --default-params={"linear":{"coresPerReplica":256,"nodesPerReplica":16,"preventSinglePointFailure":true,"includeUnschedulableNodes":true}}
- --logtostderr=true
- --v=2
tolerations :
- key : "CriticalAddonsOnly"
operator : "Exists"
serviceAccountName : kube-dns-autoscaler
在文件中,将 <SCALE_TARGET>
替换成扩缩目标。
进入到包含配置文件的目录中,输入如下命令创建 Deployment:
kubectl apply -f dns-horizontal-autoscaler.yaml
一个成功的命令输出是:
deployment.apps/dns-autoscaler created
DNS 水平自动扩缩在已经启用了。
调优自动扩缩参数 验证 dns-autoscaler ConfigMap 是否存在:
kubectl get configmap --namespace= kube-system
输出类似于:
NAME DATA AGE
...
dns-autoscaler 1 ...
...
修改该 ConfigMap 中的数据:
kubectl edit configmap dns-autoscaler --namespace= kube-system
找到如下这行内容:
linear : '{"coresPerReplica":256,"min":1,"nodesPerReplica":16}'
根据需要修改对应的字段。“min” 字段表明 DNS 后端的最小数量。
实际后端的数量通过使用如下公式来计算:
replicas = max( ceil( cores * 1/coresPerReplica ) , ceil( nodes * 1/nodesPerReplica ) )
注意 coresPerReplica
和 nodesPerReplica
的值都是整数。
背后的思想是,当一个集群使用具有很多核心的节点时,由 coresPerReplica
来控制。
当一个集群使用具有较少核心的节点时,由 nodesPerReplica
来控制。
其它的扩缩模式也是支持的,详情查看
cluster-proportional-autoscaler 。
禁用 DNS 水平自动扩缩 有几个可供调优的 DNS 水平自动扩缩选项。具体使用哪个选项因环境而异。
选项 1:缩容 dns-autoscaler Deployment 至 0 个副本 该选项适用于所有场景。运行如下命令:
kubectl scale deployment --replicas= 0 dns-autoscaler --namespace= kube-system
输出如下所示:
deployment.apps/dns-autoscaler scaled
验证当前副本数为 0:
kubectl get rs --namespace= kube-system
输出内容中,在 DESIRED 和 CURRENT 列显示为 0:
NAME DESIRED CURRENT READY AGE
...
dns-autoscaler-6b59789fc8 0 0 0 ...
...
选项 2:删除 dns-autoscaler Deployment 如果 dns-autoscaler 为你所控制,也就说没有人会去重新创建它,可以选择此选项:
kubectl delete deployment dns-autoscaler --namespace= kube-system
输出内容如下所示:
deployment.apps "dns-autoscaler" deleted
选项 3:从主控节点删除 dns-autoscaler 清单文件 如果 dns-autoscaler 在插件管理器
的控制之下,并且具有操作 master 节点的写权限,可以使用此选项。
登录到主控节点,删除对应的清单文件。
dns-autoscaler 对应的路径一般为:
/etc/kubernetes/addons/dns-horizontal-autoscaler/dns-horizontal-autoscaler.yaml
当清单文件被删除后,插件管理器将删除 dns-autoscaler Deployment。
理解 DNS 水平自动扩缩工作原理 接下来 4.3 - 监控、日志和调试 设置监控和日志记录以对集群进行故障排除或调试容器化应用程序。
有时候事情会出错。本指南旨在解决这些问题。它包含两个部分:
应用排错 -
针对部署代码到 Kubernetes 并想知道代码为什么不能正常运行的用户。集群排错 -
针对集群管理员以及 Kubernetes 集群表现异常的用户。你也应该查看所用发行版本 的已知问题。
获取帮助 如果你的问题在上述指南中没有得到答案,你还有另外几种方式从 Kubernetes 团队获得帮助。
问题 本网站上的文档针对回答各类问题进行了结构化组织和分类。
概念 部分解释 Kubernetes 体系结构以及每个组件的工作方式,
安装 部分提供了安装的实用说明。
任务 部分展示了如何完成常用任务,
教程 部分则提供对现实世界、特定行业或端到端开发场景的更全面的演练。
参考 部分提供了详细的
Kubernetes API 文档
和命令行 (CLI) 接口的文档,例如kubectl
。
求救!我的问题还没有解决!我现在需要帮助! Stack Overflow 社区中的其他人可能已经问过和你类似的问题,也可能能够帮助解决你的问题。
Kubernetes 团队还会监视带有 Kubernetes 标签的帖子 。
如果现有的问题对你没有帮助,在问一个新问题
之前,请确保你的问题是关于 Stack Overflow 的主题
并且你需要阅读关于如何提出新问题
的指南。
Slack Kubernetes 社区中有很多人在 #kubernetes-users
这一 Slack 频道聚集。
Slack 需要注册;你可以请求一份邀请 ,
并且注册是对所有人开放的。欢迎你随时来问任何问题。
一旦注册了,就可以访问通过 Web 浏览器或者 Slack 专用的应用访问
Slack 上的 Kubernetes 组织 。
一旦你完成了注册,就可以浏览各种感兴趣主题的频道列表(一直在增长)。
例如,Kubernetes 新人可能还想加入
#kubernetes-novice
频道。又比如,开发人员应该加入
#kubernetes-dev
频道。
还有许多国家/地区语言频道。请随时加入这些频道以获得本地化支持和信息:
论坛 欢迎你加入 Kubernetes 官方论坛
discuss.kubernetes.io 。
Bugs 和功能请求 如果你发现一个看起来像 Bug 的问题,或者你想提出一个功能请求,请使用
Github 问题跟踪系统 。
在提交问题之前,请搜索现有问题列表以查看是否其中已涵盖你的问题。
如果提交 Bug,请提供如何重现问题的详细信息,例如:
Kubernetes 版本:kubectl version
云平台、OS 发行版、网络配置和 Docker 版本 重现问题的步骤 4.3.1 - 集群故障排查 调试常见的集群问题。
本篇文档是介绍集群故障排查的;我们假设对于你碰到的问题,你已经排除了是由应用程序造成的。
对于应用的调试,请参阅应用故障排查指南 。
你也可以访问故障排查 来获取更多的信息。
列举集群节点 调试的第一步是查看所有的节点是否都已正确注册。
运行以下命令:
验证你所希望看见的所有节点都能够显示出来,并且都处于 Ready
状态。
为了了解你的集群的总体健康状况详情,你可以运行:
kubectl cluster-info dump
示例:调试关闭/无法访问的节点 有时在调试时查看节点的状态很有用 —— 例如,因为你注意到在节点上运行的 Pod 的奇怪行为,
或者找出为什么 Pod 不会调度到节点上。与 Pod 一样,你可以使用 kubectl describe node
和 kubectl get node -o yaml
来检索有关节点的详细信息。
例如,如果节点关闭(与网络断开连接,或者 kubelet 进程挂起并且不会重新启动等),
你将看到以下内容。请注意显示节点为 NotReady 的事件,并注意 Pod 不再运行(它们在 NotReady 状态五分钟后被驱逐)。
NAME STATUS ROLES AGE VERSION
kube-worker-1 NotReady <none> 1h v1.23.3
kubernetes-node-bols Ready <none> 1h v1.23.3
kubernetes-node-st6x Ready <none> 1h v1.23.3
kubernetes-node-unaj Ready <none> 1h v1.23.3
kubectl describe node kube-worker-1
Name: kube-worker-1
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=kube-worker-1
kubernetes.io/os=linux
Annotations: kubeadm.alpha.kubernetes.io/cri-socket: /run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Thu, 17 Feb 2022 16:46:30 -0500
Taints: node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/unreachable:NoSchedule
Unschedulable: false
Lease:
HolderIdentity: kube-worker-1
AcquireTime: <unset>
RenewTime: Thu, 17 Feb 2022 17:13:09 -0500
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Thu, 17 Feb 2022 17:09:13 -0500 Thu, 17 Feb 2022 17:09:13 -0500 WeaveIsUp Weave pod has set this
MemoryPressure Unknown Thu, 17 Feb 2022 17:12:40 -0500 Thu, 17 Feb 2022 17:13:52 -0500 NodeStatusUnknown Kubelet stopped posting node status.
DiskPressure Unknown Thu, 17 Feb 2022 17:12:40 -0500 Thu, 17 Feb 2022 17:13:52 -0500 NodeStatusUnknown Kubelet stopped posting node status.
PIDPressure Unknown Thu, 17 Feb 2022 17:12:40 -0500 Thu, 17 Feb 2022 17:13:52 -0500 NodeStatusUnknown Kubelet stopped posting node status.
Ready Unknown Thu, 17 Feb 2022 17:12:40 -0500 Thu, 17 Feb 2022 17:13:52 -0500 NodeStatusUnknown Kubelet stopped posting node status.
Addresses:
InternalIP: 192.168.0.113
Hostname: kube-worker-1
Capacity:
cpu: 2
ephemeral-storage: 15372232Ki
hugepages-2Mi: 0
memory: 2025188Ki
pods: 110
Allocatable:
cpu: 2
ephemeral-storage: 14167048988
hugepages-2Mi: 0
memory: 1922788Ki
pods: 110
System Info:
Machine ID: 9384e2927f544209b5d7b67474bbf92b
System UUID: aa829ca9-73d7-064d-9019-df07404ad448
Boot ID: 5a295a03-aaca-4340-af20-1327fa5dab5c
Kernel Version: 5.13.0-28-generic
OS Image: Ubuntu 21.10
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://1.5.9
Kubelet Version: v1.23.3
Kube-Proxy Version: v1.23.3
Non-terminated Pods: (4 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
default nginx-deployment-67d4bdd6f5-cx2nz 500m (25%) 500m (25%) 128Mi (6%) 128Mi (6%) 23m
default nginx-deployment-67d4bdd6f5-w6kd7 500m (25%) 500m (25%) 128Mi (6%) 128Mi (6%) 23m
kube-system kube-proxy-dnxbz 0 (0%) 0 (0%) 0 (0%) 0 (0%) 28m
kube-system weave-net-gjxxp 100m (5%) 0 (0%) 200Mi (10%) 0 (0%) 28m
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 1100m (55%) 1 (50%)
memory 456Mi (24%) 256Mi (13%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
Events:
...
kubectl get node kube-worker-1 -o yaml
apiVersion : v1
kind : Node
metadata :
annotations :
kubeadm.alpha.kubernetes.io/cri-socket : /run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl : "0"
volumes.kubernetes.io/controller-managed-attach-detach : "true"
creationTimestamp : "2022-02-17T21:46:30Z"
labels :
beta.kubernetes.io/arch : amd64
beta.kubernetes.io/os : linux
kubernetes.io/arch : amd64
kubernetes.io/hostname : kube-worker-1
kubernetes.io/os : linux
name : kube-worker-1
resourceVersion : "4026"
uid : 98efe7cb-2978-4a0b-842a-1a7bf12c05f8
spec : {}
status :
addresses :
- address : 192.168.0.113
type : InternalIP
- address : kube-worker-1
type : Hostname
allocatable :
cpu : "2"
ephemeral-storage : "14167048988"
hugepages-2Mi : "0"
memory : 1922788Ki
pods : "110"
capacity :
cpu : "2"
ephemeral-storage : 15372232Ki
hugepages-2Mi : "0"
memory : 2025188Ki
pods : "110"
conditions :
- lastHeartbeatTime : "2022-02-17T22:20:32Z"
lastTransitionTime : "2022-02-17T22:20:32Z"
message : Weave pod has set this
reason : WeaveIsUp
status : "False"
type : NetworkUnavailable
- lastHeartbeatTime : "2022-02-17T22:20:15Z"
lastTransitionTime : "2022-02-17T22:13:25Z"
message : kubelet has sufficient memory available
reason : KubeletHasSufficientMemory
status : "False"
type : MemoryPressure
- lastHeartbeatTime : "2022-02-17T22:20:15Z"
lastTransitionTime : "2022-02-17T22:13:25Z"
message : kubelet has no disk pressure
reason : KubeletHasNoDiskPressure
status : "False"
type : DiskPressure
- lastHeartbeatTime : "2022-02-17T22:20:15Z"
lastTransitionTime : "2022-02-17T22:13:25Z"
message : kubelet has sufficient PID available
reason : KubeletHasSufficientPID
status : "False"
type : PIDPressure
- lastHeartbeatTime : "2022-02-17T22:20:15Z"
lastTransitionTime : "2022-02-17T22:15:15Z"
message : kubelet is posting ready status. AppArmor enabled
reason : KubeletReady
status : "True"
type : Ready
daemonEndpoints :
kubeletEndpoint :
Port : 10250
nodeInfo :
architecture : amd64
bootID : 22333234 -7a6b-44d4-9ce1-67e31dc7e369
containerRuntimeVersion : containerd://1.5.9
kernelVersion : 5.13.0-28 -generic
kubeProxyVersion : v1.23.3
kubeletVersion : v1.23.3
machineID : 9384e2927f544209b5d7b67474bbf92b
operatingSystem : linux
osImage : Ubuntu 21.10
systemUUID : aa829ca9-73d7-064d-9019-df07404ad448
查看日志 目前,深入挖掘集群需要登录相关机器。以下是相关日志文件的位置。
在基于 systemd 的系统上,你可能需要使用 journalctl
而不是检查日志文件。
控制平面节点 /var/log/kube-apiserver.log
—— API 服务器,负责提供 API 服务/var/log/kube-scheduler.log
—— 调度器,负责制定调度决策/var/log/kube-controller-manager.log
—— 运行大多数 Kubernetes
内置控制器 的组件,除了调度(kube-scheduler 处理调度)。工作节点 /var/log/kubelet.log
—— 来自 kubelet
的日志,负责在节点运行容器/var/log/kube-proxy.log
—— 来自 kube-proxy
的日志,负责将流量转发到服务端点集群故障模式 这是可能出错的事情的不完整列表,以及如何调整集群设置以缓解问题。
故障原因 虚拟机关闭 集群内或集群与用户之间的网络分区 Kubernetes 软件崩溃 持久存储(例如 GCE PD 或 AWS EBS 卷)的数据丢失或不可用 操作员错误,例如配置错误的 Kubernetes 软件或应用程序软件 具体情况 API 服务器所在的 VM 关机或者 API 服务器崩溃结果不能停止、更新或者启动新的 Pod、服务或副本控制器 现有的 Pod 和服务在不依赖 Kubernetes API 的情况下应该能继续正常工作 API 服务器的后端存储丢失结果kube-apiserver 组件未能成功启动并变健康 kubelet 将不能访问 API 服务器,但是能够继续运行之前的 Pod 和提供相同的服务代理 在 API 服务器重启之前,需要手动恢复或者重建 API 服务器的状态 Kubernetes 服务组件(节点控制器、副本控制器管理器、调度器等)所在的 VM 关机或者崩溃当前,这些控制器是和 API 服务器在一起运行的,它们不可用的现象是与 API 服务器类似的 将来,这些控制器也会复制为多份,并且可能不在运行于同一节点上 它们没有自己的持久状态 单个节点(VM 或者物理机)关机 网络分裂结果分区 A 认为分区 B 中所有的节点都已宕机;分区 B 认为 API 服务器宕机
(假定主控节点所在的 VM 位于分区 A 内)。 kubelet 软件故障结果崩溃的 kubelet 就不能在其所在的节点上启动新的 Pod kubelet 可能删掉 Pod 或者不删 节点被标识为非健康态 副本控制器会在其它的节点上启动新的 Pod 集群操作错误结果丢失 Pod 或服务等等 丢失 API 服务器的后端存储 用户无法读取 API 等等 缓解措施 接下来 4.3.1.1 - 资源指标管道 对于 Kubernetes,Metrics API 提供了一组基本的指标,以支持自动伸缩和类似的用例。
该 API 提供有关节点和 Pod 的资源使用情况的信息,
包括 CPU 和内存的指标。如果将 Metrics API 部署到集群中,
那么 Kubernetes API 的客户端就可以查询这些信息,并且可以使用 Kubernetes 的访问控制机制来管理权限。
HorizontalPodAutoscaler (HPA) 和
VerticalPodAutoscaler (VPA)
使用 metrics API 中的数据调整工作负载副本和资源,以满足客户需求。
你也可以通过 kubectl top
命令来查看资源指标。
说明: Metrics API 及其启用的指标管道仅提供最少的 CPU 和内存指标,以启用使用 HPA 和/或 VPA 的自动扩展。
如果你想提供更完整的指标集,你可以通过部署使用 Custom Metrics API 的第二个
指标管道 来作为简单的 Metrics API 的补充。
图 1 说明了资源指标管道的架构。
flowchart RL
subgraph cluster[Cluster]
direction RL
S[ ]
A[Metrics- Server]
subgraph B[Nodes]
direction TB
D[cAdvisor] --> C[kubelet]
E[Container runtime] --> D
E1[Container runtime] --> D
P[pod data] -.- C
end
L[API server]
W[HPA]
C ---->|Summary API| A -->|metrics API| L --> W
end
L ---> K[kubectl top]
classDef box fill:#fff,stroke:#000,stroke-width:1px,color:#000;
class W,B,P,K,cluster,D,E,E1 box
classDef spacewhite fill:#ffffff,stroke:#fff,stroke-width:0px,color:#000
class S spacewhite
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:1px,color:#fff;
class A,L,C k8s
图 1. 资源指标管道
图中从右到左的架构组件包括以下内容:
cAdvisor : 用于收集、聚合和公开 Kubelet 中包含的容器指标的守护程序。
kubelet : 用于管理容器资源的节点代理。
可以使用 /metrics/resource
和 /stats
kubelet API 端点访问资源指标。
Summary API : kubelet 提供的 API,用于发现和检索可通过 /stats
端点获得的每个节点的汇总统计信息。
metrics-server : 集群插件组件,用于收集和聚合从每个 kubelet 中提取的资源指标。
API 服务器提供 Metrics API 以供 HPA、VPA 和 kubectl top
命令使用。Metrics Server 是 Metrics API 的参考实现。
Metrics API : Kubernetes API 支持访问用于工作负载自动缩放的 CPU 和内存。
要在你的集群中进行这项工作,你需要一个提供 Metrics API 的 API 扩展服务器。
说明: cAdvisor 支持从 cgroups 读取指标,它适用于 Linux 上的典型容器运行时。
如果你使用基于其他资源隔离机制的容器运行时,例如虚拟化,那么该容器运行时必须支持
CRI 容器指标
以便 kubelet 可以使用指标。
Metrics API 特性状态: Kubernetes 1.8 [beta]
metrics-server 实现了 Metrics API。此 API 允许你访问集群中节点和 Pod 的 CPU 和内存使用情况。
它的主要作用是将资源使用指标提供给 K8s 自动缩放器组件。
下面是一个 minikube
节点的 Metrics API 请求示例,通过 jq
管道处理以便于阅读:
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/minikube" | jq '.'
这是使用 curl
来执行的相同 API 调用:
curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/nodes/minikube
响应示例:
{
"kind" : "NodeMetrics" ,
"apiVersion" : "metrics.k8s.io/v1beta1" ,
"metadata" : {
"name" : "minikube" ,
"selfLink" : "/apis/metrics.k8s.io/v1beta1/nodes/minikube" ,
"creationTimestamp" : "2022-01-27T18:48:43Z"
},
"timestamp" : "2022-01-27T18:48:33Z" ,
"window" : "30s" ,
"usage" : {
"cpu" : "487558164n" ,
"memory" : "732212Ki"
}
}
下面是一个 kube-system
命名空间中的 kube-scheduler-minikube
Pod 的 Metrics API 请求示例,
通过 jq
管道处理以便于阅读:
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube" | jq '.'
这是使用 curl
来完成的相同 API 调用:
curl http://localhost:8080/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube
响应示例:
{
"kind" : "PodMetrics" ,
"apiVersion" : "metrics.k8s.io/v1beta1" ,
"metadata" : {
"name" : "kube-scheduler-minikube" ,
"namespace" : "kube-system" ,
"selfLink" : "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/kube-scheduler-minikube" ,
"creationTimestamp" : "2022-01-27T19:25:00Z"
},
"timestamp" : "2022-01-27T19:24:31Z" ,
"window" : "30s" ,
"containers" : [
{
"name" : "kube-scheduler" ,
"usage" : {
"cpu" : "9559630n" ,
"memory" : "22244Ki"
}
}
]
}
Metrics API 在 k8s.io/metrics 代码库中定义。
你必须启用 API 聚合层 并为
metrics.k8s.io
API 注册一个 APIService 。
要了解有关 Metrics API 的更多信息,
请参阅资源 Resource Metrics API Design 、
metrics-server 代码库 和
Resource Metrics API 。
说明: 你必须部署提供 Metrics API 服务的 metrics-server 或其他适配器才能访问它。
度量资源用量 CPU CPU 报告为以 cpu 为单位测量的平均核心使用率。在 Kubernetes 中,
一个 cpu 相当于云提供商的 1 个 vCPU/Core,以及裸机 Intel 处理器上的 1 个超线程。
该值是通过对内核提供的累积 CPU 计数器(在 Linux 和 Windows 内核中)取一个速率得出的。
用于计算 CPU 的时间窗口显示在 Metrics API 的窗口字段下。
要了解更多关于 Kubernetes 如何分配和测量 CPU 资源的信息,请参阅
CPU 的含义 。
内存 内存报告为在收集度量标准的那一刻的工作集大小,以字节为单位。
在理想情况下,“工作集”是在内存压力下无法释放的正在使用的内存量。
然而,工作集的计算因主机操作系统而异,并且通常大量使用启发式算法来产生估计。
Kubernetes 模型中,容器工作集是由容器运行时计算的与相关容器关联的匿名内存。
工作集指标通常还包括一些缓存(文件支持)内存,因为主机操作系统不能总是回收页面。
要了解有关 Kubernetes 如何分配和测量内存资源的更多信息,
请参阅内存的含义 。
Metrics 服务器 metrics-server 从 kubelet 中获取资源指标,并通过 Metrics API 在 Kubernetes API 服务器中公开它们,以供 HPA 和 VPA 使用。
你还可以使用 kubectl top
命令查看这些指标。
metrics-server 使用 Kubernetes API 来跟踪集群中的节点和 Pod。metrics-server 服务器通过 HTTP 查询每个节点以获取指标。
metrics-server 还构建了 Pod 元数据的内部视图,并维护 Pod 健康状况的缓存。
缓存的 Pod 健康信息可通过 metrics-server 提供的扩展 API 获得。
例如,对于 HPA 查询,metrics-server 需要确定哪些 Pod 满足 Deployment 中的标签选择器。
metrics-server 调用 kubelet API
从每个节点收集指标。根据它使用的度量服务器版本:
版本 v0.6.0+ 中,使用指标资源端点 /metrics/resource
旧版本中使用 Summary API 端点 /stats/summary
了解更多 metrics-server,参阅 metrics-server 代码库 。
你还可以查看以下内容:
Summary API 来源 kubelet 在节点、卷、Pod 和容器级别收集统计信息,
并在 Summary API
中提供它们的统计信息供消费者阅读。
下面是一个 minikube
节点的 Summary API 请求示例:
kubectl get --raw "/api/v1/nodes/minikube/proxy/stats/summary"
这是使用 curl
来执行的相同 API 调用:
curl http://localhost:8080/api/v1/nodes/minikube/proxy/stats/summary
说明: 从 metrics-server 0.6.x 开始,Summary API /stats/summary
端点被 /metrics/resource
端点替换。
4.3.1.2 - 节点健康监测 节点问题检测器(Node Problem Detector) 是一个守护程序,用于监视和报告节点的健康状况。
你可以将节点问题探测器以 DaemonSet
或独立守护程序运行。
节点问题检测器从各种守护进程收集节点问题,并以
NodeCondition 和
Event
的形式报告给 API 服务器。
要了解如何安装和使用节点问题检测器,请参阅
节点问题探测器项目文档 。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
局限性 节点问题检测器只支持基于文件类型的内核日志。
它不支持像 journald 这样的命令行日志工具。 节点问题检测器使用内核日志格式来报告内核问题。
要了解如何扩展内核日志格式,请参阅添加对另一个日志格式的支持 。 启用节点问题检测器 一些云供应商将节点问题检测器以插件 形式启用。
你还可以使用 kubectl
或创建插件 Pod 来启用节点问题探测器。
使用 kubectl 启用节点问题检测器 kubectl
提供了节点问题探测器最灵活的管理。
你可以覆盖默认配置使其适合你的环境或检测自定义节点问题。例如:
创建类似于 node-strought-detector.yaml
的节点问题检测器配置:
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : node-problem-detector-v0.1
namespace : kube-system
labels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
spec :
selector :
matchLabels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
template :
metadata :
labels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
spec :
hostNetwork : true
containers :
- name : node-problem-detector
image : registry.k8s.io/node-problem-detector:v0.1
securityContext :
privileged : true
resources :
limits :
cpu : "200m"
memory : "100Mi"
requests :
cpu : "20m"
memory : "20Mi"
volumeMounts :
- name : log
mountPath : /log
readOnly : true
volumes :
- name : log
hostPath :
path : /var/log/
说明: 你应该检查系统日志目录是否适用于操作系统发行版本。
使用 kubectl
启动节点问题检测器:
kubectl apply -f https://k8s.io/examples/debug/node-problem-detector.yaml
使用插件 pod 启用节点问题检测器 如果你使用的是自定义集群引导解决方案,不需要覆盖默认配置,
可以利用插件 Pod 进一步自动化部署。
创建 node-strick-detector.yaml
,并在控制平面节点上保存配置到插件 Pod 的目录
/etc/kubernetes/addons/node-problem-detector
。
覆盖配置文件 构建节点问题检测器的 docker 镜像时,会嵌入
默认配置 。
不过,你可以像下面这样使用 ConfigMap
将其覆盖:
更改 config/
中的配置文件
创建 ConfigMap
node-strick-detector-config
:
kubectl create configmap node-problem-detector-config --from-file= config/
更改 node-problem-detector.yaml
以使用 ConfigMap:
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : node-problem-detector-v0.1
namespace : kube-system
labels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
spec :
selector :
matchLabels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
template :
metadata :
labels :
k8s-app : node-problem-detector
version : v0.1
kubernetes.io/cluster-service : "true"
spec :
hostNetwork : true
containers :
- name : node-problem-detector
image : registry.k8s.io/node-problem-detector:v0.1
securityContext :
privileged : true
resources :
limits :
cpu : "200m"
memory : "100Mi"
requests :
cpu : "20m"
memory : "20Mi"
volumeMounts :
- name : log
mountPath : /log
readOnly : true
- name : config # 使用 ConfigMap 卷中的数据覆盖 config/ 目录内容
mountPath : /config
readOnly : true
volumes :
- name : log
hostPath :
path : /var/log/
- name : config # 定义 ConfigMap 卷
configMap :
name : node-problem-detector-config
使用新的配置文件重新创建节点问题检测器:
# 如果你正在运行节点问题检测器,请先删除,然后再重新创建
kubectl delete -f https://k8s.io/examples/debug/node-problem-detector.yaml
kubectl apply -f https://k8s.io/examples/debug/node-problem-detector-configmap.yaml
说明: 此方法仅适用于通过 kubectl
启动的节点问题检测器。
如果节点问题检测器作为集群插件运行,则不支持覆盖配置。
插件管理器不支持 ConfigMap
。
内核监视器 内核监视器(Kernel Monitor) 是节点问题检测器中支持的系统日志监视器守护进程。
内核监视器观察内核日志并根据预定义规则检测已知的内核问题。
内核监视器根据 config/kernel-monitor.json
中的一组预定义规则列表匹配内核问题。
规则列表是可扩展的,你始终可以通过覆盖配置来扩展它。
添加新的 NodeCondition 要支持新的 NodeCondition
,请在 config/kernel-monitor.json
中的
conditions
字段中创建一个条件定义:
{
"type" : "NodeConditionType" ,
"reason" : "CamelCaseDefaultNodeConditionReason" ,
"message" : "arbitrary default node condition message"
}
检测新的问题 你可以使用新的规则描述来扩展 config/kernel-monitor.json
中的 rules
字段以检测新问题:
{
"type" : "temporary/permanent" ,
"condition" : "NodeConditionOfPermanentIssue" ,
"reason" : "CamelCaseShortReason" ,
"message" : "regexp matching the issue in the kernel log"
}
配置内核日志设备的路径 检查你的操作系统(OS)发行版本中的内核日志路径位置。
Linux 内核日志设备
通常呈现为 /dev/kmsg
。
但是,日志路径位置因 OS 发行版本而异。
config/kernel-monitor.json
中的 log
字段表示容器内的日志路径。
你可以配置 log
字段以匹配节点问题检测器所示的设备路径。
内核监视器使用
Translator
插件转换内核日志的内部数据结构。
你可以为新的日志格式实现新的转换器。
建议和限制 建议在集群中运行节点问题检测器以监控节点运行状况。
运行节点问题检测器时,你可以预期每个节点上的额外资源开销。
通常这是可接受的,因为:
内核日志增长相对缓慢。 已经为节点问题检测器设置了资源限制。 即使在高负载下,资源使用也是可接受的。有关更多信息,请参阅节点问题检测器
基准结果 。 4.3.1.3 - 使用 crictl 对 Kubernetes 节点进行调试 特性状态: Kubernetes v1.11 [stable]
crictl
是 CRI 兼容的容器运行时命令行接口。
你可以使用它来检查和调试 Kubernetes 节点上的容器运行时和应用程序。
crictl
和它的源代码在
cri-tools 代码库。
准备开始 crictl
需要带有 CRI 运行时的 Linux 操作系统。
安装 crictl 你可以从 cri-tools 发布页面
下载一个压缩的 crictl
归档文件,用于几种不同的架构。
下载与你的 kubernetes 版本相对应的版本。
提取它并将其移动到系统路径上的某个位置,例如 /usr/local/bin/
。
一般用法 crictl
命令有几个子命令和运行时参数。
有关详细信息,请使用 crictl help
或 crictl <subcommand> help
获取帮助信息。
你可以用以下方法之一来为 crictl
设置端点:
设置参数 --runtime-endpoint
和 --image-endpoint
。 设置环境变量 CONTAINER_RUNTIME_ENDPOINT
和 IMAGE_SERVICE_ENDPOINT
。 在配置文件 --config=/etc/crictl.yaml
中设置端点。
要设置不同的文件,可以在运行 crictl
时使用 --config=PATH_TO_FILE
标志。 你还可以在连接到服务器并启用或禁用调试时指定超时值,方法是在配置文件中指定
timeout
或 debug
值,或者使用 --timeout
和 --debug
命令行参数。
要查看或编辑当前配置,请查看或编辑 /etc/crictl.yaml
的内容。
例如,使用 containerd
容器运行时的配置会类似于这样:
runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: unix:///var/run/containerd/containerd.sock
timeout: 10
debug: true
要进一步了解 crictl
,参阅
crictl
文档 。
crictl 命令示例 警告: 如果使用 crictl
在正在运行的 Kubernetes 集群上创建 Pod 沙盒或容器,
kubelet 最终将删除它们。
crictl
不是一个通用的工作流工具,而是一个对调试有用的工具。
打印 Pod 清单 打印所有 Pod 的清单:
输出类似于:
POD ID CREATED STATE NAME NAMESPACE ATTEMPT
926f1b5a1d33a About a minute ago Ready sh-84d7dcf559-4r2gq default 0
4dccb216c4adb About a minute ago Ready nginx-65899c769f-wv2gp default 0
a86316e96fa89 17 hours ago Ready kube-proxy-gblk4 kube-system 0
919630b8f81f1 17 hours ago Ready nvidia-device-plugin-zgbbv kube-system 0
根据名称打印 Pod 清单:
crictl pods --name nginx-65899c769f-wv2gp
输出类似于这样:
POD ID CREATED STATE NAME NAMESPACE ATTEMPT
4dccb216c4adb 2 minutes ago Ready nginx-65899c769f-wv2gp default 0
根据标签打印 Pod 清单:
crictl pods --label run = nginx
输出类似于这样:
POD ID CREATED STATE NAME NAMESPACE ATTEMPT
4dccb216c4adb 2 minutes ago Ready nginx-65899c769f-wv2gp default 0
打印镜像清单 打印所有镜像清单:
输出类似于这样:
IMAGE TAG IMAGE ID SIZE
busybox latest 8c811b4aec35f 1.15MB
k8s-gcrio.azureedge.net/hyperkube-amd64 v1.10.3 e179bbfe5d238 665MB
k8s-gcrio.azureedge.net/pause-amd64 3.1 da86e6ba6ca19 742kB
nginx latest cd5239a0906a6 109MB
根据仓库打印镜像清单:
输出类似于这样:
IMAGE TAG IMAGE ID SIZE
nginx latest cd5239a0906a6 109MB
只打印镜像 ID:
输出类似于这样:
sha256:8c811b4aec35f259572d0f79207bc0678df4c736eeec50bc9fec37ed936a472a
sha256:e179bbfe5d238de6069f3b03fccbecc3fb4f2019af741bfff1233c4d7b2970c5
sha256:da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
sha256:cd5239a0906a6ccf0562354852fae04bc5b52d72a2aff9a871ddb6bd57553569
打印容器清单 打印所有容器清单:
输出类似于这样:
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT
1f73f2d81bf98 busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 7 minutes ago Running sh 1
9c5951df22c78 busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 8 minutes ago Exited sh 0
87d3992f84f74 nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f 8 minutes ago Running nginx 0
1941fb4da154f k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a 18 hours ago Running kube-proxy 0
打印正在运行的容器清单:
输出类似于这样:
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT
1f73f2d81bf98 busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47 6 minutes ago Running sh 1
87d3992f84f74 nginx@sha256:d0a8828cccb73397acb0073bf34f4d7d8aa315263f1e7806bf8c55d8ac139d5f 7 minutes ago Running nginx 0
1941fb4da154f k8s-gcrio.azureedge.net/hyperkube-amd64@sha256:00d814b1f7763f4ab5be80c58e98140dfc69df107f253d7fdd714b30a714260a 17 hours ago Running kube-proxy 0
在正在运行的容器上执行命令 crictl exec -i -t 1f73f2d81bf98 ls
输出类似于这样:
bin dev etc home proc root sys tmp usr var
获取容器日志 获取容器的所有日志:
crictl logs 87d3992f84f74
输出类似于这样:
10.240.0.96 - - [06/Jun/2018:02:45:49 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:50 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
获取最近的 N
行日志:
crictl logs --tail= 1 87d3992f84f74
输出类似于这样:
10.240.0.96 - - [06/Jun/2018:02:45:51 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
运行 Pod 沙盒 用 crictl
运行 Pod 沙盒对容器运行时排错很有帮助。
在运行的 Kubernetes 集群中,沙盒会随机地被 kubelet 停止和删除。
编写下面的 JSON 文件:
{
"metadata" : {
"name" : "nginx-sandbox" ,
"namespace" : "default" ,
"attempt" : 1 ,
"uid" : "hdishd83djaidwnduwk28bcsb"
},
"log_directory" : "/tmp" ,
"linux" : {
}
}
使用 crictl runp
命令应用 JSON 文件并运行沙盒。
crictl runp pod-config.json
返回了沙盒的 ID。
创建容器 用 crictl
创建容器对容器运行时排错很有帮助。
在运行的 Kubernetes 集群中,沙盒会随机的被 kubelet 停止和删除。
拉取 busybox 镜像
Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
创建 Pod 和容器的配置:
Pod 配置 :
{
"metadata" : {
"name" : "busybox-sandbox" ,
"namespace" : "default" ,
"attempt" : 1 ,
"uid" : "aewi4aeThua7ooShohbo1phoj"
},
"log_directory" : "/tmp" ,
"linux" : {
}
}
容器配置 :
{
"metadata" : {
"name" : "busybox"
},
"image" :{
"image" : "busybox"
},
"command" : [
"top"
],
"log_path" :"busybox.log" ,
"linux" : {
}
}
创建容器,传递先前创建的 Pod 的 ID、容器配置文件和 Pod 配置文件。返回容器的 ID。
crictl create f84dd361f8dc51518ed291fbadd6db537b0496536c1d2d6c05ff943ce8c9a54f container-config.json pod-config.json
查询所有容器并确认新创建的容器状态为 Created
。
输出类似于这样:
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT
3e025dd50a72d busybox 32 seconds ago Created busybox 0
启动容器 要启动容器,要将容器 ID 传给 crictl start
:
crictl start 3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60
输出类似于这样:
3e025dd50a72d956c4f14881fbb5b1080c9275674e95fb67f965f6478a957d60
确认容器的状态为 Running
。
输出类似于这样:
CONTAINER ID IMAGE CREATED STATE NAME ATTEMPT
3e025dd50a72d busybox About a minute ago Running busybox 0
接下来 4.3.1.4 - Windows 调试技巧 工作节点级别排障 我的 Pod 都卡在 “Container Creating” 或者不断重启
确保你的 pause 镜像跟你的 Windows 版本兼容。
查看 Pause 容器
以了解最新的或建议的 pause 镜像,或者了解更多信息。
说明: 如果你在使用 containerd 作为你的容器运行时,那么 pause 镜像在 config.toml 配置文件的
plugins.plugins.cri.sandbox_image
中指定。
我的 Pod 状态显示 'ErrImgPull' 或者 'ImagePullBackOff'
保证你的 Pod 被调度到兼容的
Windows 节点上。
关于如何为你的 Pod 指定一个兼容节点,
可以查看这个指可以查看这个指南
以了解更多的信息。
网络排障 我的 Windows Pod 没有网络连接
如果你使用的是虚拟机,请确保所有 VM 网卡上都已启用 MAC spoofing。
我的 Windows Pod 不能 ping 通外界资源
Windows Pod 没有为 ICMP 协议编写出站规则,但 TCP/UDP 是支持的。当试图演示与集群外部资源的连接时,
可以把 ping <IP>
替换为 curl <IP>
命令。
如果你仍然遇到问题,很可能你需要额外关注
cni.conf
的配置。你可以随时编辑这个静态文件。更新配置将应用于新的 Kubernetes 资源。
Kubernetes 的网络需求之一(查看 Kubernetes 模型 )
是集群通信不需要内部的 NAT。
为了遵守这一要求,对于你不希望发生的出站 NAT 通信,这里有一个
ExceptionList 。
然而,这也意味着你需要从 ExceptionList
中去掉你试图查询的外部 IP。
只有这样,来自你的 Windows Pod 的流量才会被正确地 SNAT 转换,以接收来自外部环境的响应。
就此而言,你的 cni.conf
中的 ExceptionList
应该如下所示:
"ExceptionList": [
"10.244.0.0/16", # 集群子网
"10.96.0.0/12", # 服务子网
"10.127.130.0/24" # 管理(主机)子网
]
我的 Windows 节点无法访问 NodePort
类型 Service
从节点本身访问本地 NodePort 失败,是一个已知的限制。
你可以从其他节点或外部客户端正常访问 NodePort。
容器的 vNIC 和 HNS 端点正在被删除
当 hostname-override
参数没有传递给
kube-proxy
时可能引发这一问题。想要解决这个问题,用户需要将主机名传递给 kube-proxy,如下所示:
C:\k\kube-proxy .exe --hostname-override=$(hostname)
我的 Windows 节点无法通过服务 IP 访问我的服务
这是 Windows 上网络栈的一个已知限制。但是 Windows Pod 可以访问 Service IP。
启动 kubelet 时找不到网络适配器
Windows 网络栈需要一个虚拟适配器才能使 Kubernetes 网络工作。
如果以下命令没有返回结果(在管理员模式的 shell 中),
则意味着创建虚拟网络失败,而虚拟网络的存在是 kubelet 正常工作的前提:
Get-HnsNetwork | ? Name -ieq "cbr0"
Get-NetAdapter | ? Name -Like "vEthernet (Ethernet*"
如果主机的网络适配器不是 "Ethernet",通常有必要修改 start.ps1
脚本的
InterfaceName
参数。否则,如果虚拟网络创建过程出错,请检查 start-kubelet.ps1
脚本的输出。
DNS 解析工作异常
查阅这一节 了解 Windows 系统上的 DNS 限制。
kubectl port-forward
失败,错误为 "unable to do port forwarding: wincat not found"
在 Kubernetes 1.15 中,pause 基础架构容器 mcr.microsoft.com/oss/kubernetes/pause:3.6
中包含 wincat.exe
来实现端口转发。
请确保使用 Kubernetes 的受支持版本。如果你想构建自己的 pause 基础架构容器,
请确保其中包含 wincat 。
我的 Kubernetes 安装失败,因为我的 Windows 服务器节点使用了代理服务器
如果使用了代理服务器,必须定义下面的 PowerShell 环境变量:
[Environment] ::SetEnvironmentVariable("HTTP_PROXY" , "http://proxy.example.com:80/" , [EnvironmentVariableTarget] ::Machine)
[Environment] ::SetEnvironmentVariable("HTTPS_PROXY" , "http://proxy.example.com:443/" , [EnvironmentVariableTarget] ::Machine)
Flannel 故障排查 使用 Flannel 时,我的节点在重新加入集群后出现问题
当先前删除的节点重新加入集群时, flannelD 尝试为节点分配一个新的 Pod 子网。
用户应该在以下路径中删除旧的 Pod 子网配置文件:
Remove-Item C:\k\SourceVip.json
Remove-Item C:\k\SourceVipRequest.json
Flanneld 卡在 "Waiting for the Network to be created"
关于这个问题 有很多报告;
很可能是 Flannel 网络管理 IP 的设置时机问题。
一个变通方法是重新启动 start.ps1
或按如下方式手动重启:
[Environment] ::SetEnvironmentVariable("NODE_NAME" , "<Windows 工作节点主机名>" )
C:\flannel\flanneld.exe --kubeconfig-file =c:\k\config --iface=<Windows 工作节点 IP> --ip-masq=1 --kube-subnet-mgr=1
我的 Windows Pod 无法启动,因为缺少 /run/flannel/subnet.env
这表明 Flannel 没有正确启动。你可以尝试重启 flanneld.exe
或者你可以将 Kubernetes 控制节点的
/run/flannel/subnet.env
文件手动拷贝到 Windows 工作节点上,放在 C:\run\flannel\subnet.env
;
并且将 FLANNEL_SUBNET
行修改为不同取值。例如,如果期望节点子网为 10.244.4.1/24:
FLANNEL_NETWORK = 10.244.0.0/16
FLANNEL_SUBNET = 10.244.4.1/24
FLANNEL_MTU = 1500
FLANNEL_IPMASQ = true
进一步探查 如果这些步骤都不能解决你的问题,你可以通过以下方式获得关于在 Kubernetes 中运行 Windows 容器的帮助:
4.3.1.5 - 审计 特性状态: Kubernetes v1.25 [beta]
Kubernetes 审计(Auditing) 功能提供了与安全相关的、按时间顺序排列的记录集,
记录每个用户、使用 Kubernetes API 的应用以及控制面自身引发的活动。
审计功能使得集群管理员能够回答以下问题:
发生了什么? 什么时候发生的? 谁触发的? 活动发生在哪个(些)对象上? 在哪观察到的? 它从哪触发的? 活动的后续处理行为是什么? 审计记录最初产生于
kube-apiserver
内部。每个请求在不同执行阶段都会生成审计事件;这些审计事件会根据特定策略
被预处理并写入后端。策略确定要记录的内容和用来存储记录的后端。
当前的后端支持日志文件和 webhook。
每个请求都可被记录其相关的阶段(stage) 。已定义的阶段有:
RequestReceived
- 此阶段对应审计处理器接收到请求后,并且在委托给
其余处理器之前生成的事件。ResponseStarted
- 在响应消息的头部发送后,响应消息体发送前生成的事件。
只有长时间运行的请求(例如 watch)才会生成这个阶段。ResponseComplete
- 当响应消息体完成并且没有更多数据需要传输的时候。Panic
- 当 panic 发生时生成。审计日志记录功能会增加 API server 的内存消耗,因为需要为每个请求存储审计所需的某些上下文。
内存消耗取决于审计日志记录的配置。
审计策略 审计策略定义了关于应记录哪些事件以及应包含哪些数据的规则。
审计策略对象结构定义在
audit.k8s.io
API 组
处理事件时,将按顺序与规则列表进行比较。第一个匹配规则设置事件的
审计级别(Audit Level) 。已定义的审计级别有:
None
- 符合这条规则的日志将不会记录。Metadata
- 记录请求的元数据(请求的用户、时间戳、资源、动词等等),
但是不记录请求或者响应的消息体。Request
- 记录事件的元数据和请求的消息体,但是不记录响应的消息体。
这不适用于非资源类型的请求。RequestResponse
- 记录事件的元数据,请求和响应的消息体。这不适用于非资源类型的请求。你可以使用 --audit-policy-file
标志将包含策略的文件传递给 kube-apiserver
。
如果不设置该标志,则不记录事件。
注意 rules
字段必须 在审计策略文件中提供。没有(0)规则的策略将被视为非法配置。
以下是一个审计策略文件的示例:
apiVersion : audit.k8s.io/v1 # 这是必填项。
kind : Policy
# 不要在 RequestReceived 阶段为任何请求生成审计事件。
omitStages :
- "RequestReceived"
rules :
# 在日志中用 RequestResponse 级别记录 Pod 变化。
- level : RequestResponse
resources :
- group : ""
# 资源 "pods" 不匹配对任何 Pod 子资源的请求,
# 这与 RBAC 策略一致。
resources : ["pods" ]
# 在日志中按 Metadata 级别记录 "pods/log"、"pods/status" 请求
- level : Metadata
resources :
- group : ""
resources : ["pods/log" , "pods/status" ]
# 不要在日志中记录对名为 "controller-leader" 的 configmap 的请求。
- level : None
resources :
- group : ""
resources : ["configmaps" ]
resourceNames : ["controller-leader" ]
# 不要在日志中记录由 "system:kube-proxy" 发出的对端点或服务的监测请求。
- level : None
users : ["system:kube-proxy" ]
verbs : ["watch" ]
resources :
- group : "" # core API 组
resources : ["endpoints" , "services" ]
# 不要在日志中记录对某些非资源 URL 路径的已认证请求。
- level : None
userGroups : ["system:authenticated" ]
nonResourceURLs :
- "/api*" # 通配符匹配。
- "/version"
# 在日志中记录 kube-system 中 configmap 变更的请求消息体。
- level : Request
resources :
- group : "" # core API 组
resources : ["configmaps" ]
# 这个规则仅适用于 "kube-system" 名字空间中的资源。
# 空字符串 "" 可用于选择非名字空间作用域的资源。
namespaces : ["kube-system" ]
# 在日志中用 Metadata 级别记录所有其他名字空间中的 configmap 和 secret 变更。
- level : Metadata
resources :
- group : "" # core API 组
resources : ["secrets" , "configmaps" ]
# 在日志中以 Request 级别记录所有其他 core 和 extensions 组中的资源操作。
- level : Request
resources :
- group : "" # core API 组
- group : "extensions" # 不应包括在内的组版本。
# 一个抓取所有的规则,将在日志中以 Metadata 级别记录所有其他请求。
- level : Metadata
# 符合此规则的 watch 等长时间运行的请求将不会
# 在 RequestReceived 阶段生成审计事件。
omitStages :
- "RequestReceived"
你可以使用最低限度的审计策略文件在 Metadata
级别记录所有请求:
# 在 Metadata 级别为所有请求生成日志
apiVersion : audit.k8s.io/v1beta1
kind : Policy
rules :
- level : Metadata
如果你在打磨自己的审计配置文件,你可以使用为 Google Container-Optimized OS
设计的审计配置作为出发点。你可以参考
configure-helper.sh
脚本,该脚本能够生成审计策略文件。你可以直接在脚本中看到审计策略的绝大部份内容。
你也可以参考 Policy
配置参考
以获取有关已定义字段的详细信息。
审计后端 审计后端实现将审计事件导出到外部存储。Kube-apiserver
默认提供两个后端:
Log 后端,将事件写入到文件系统 Webhook 后端,将事件发送到外部 HTTP API 在这所有情况下,审计事件均遵循 Kubernetes API 在
audit.k8s.io
API 组
中定义的结构。
说明: 对于 patch 请求,请求的消息体需要是设定 patch 操作的 JSON 所构成的一个串,
而不是一个完整的 Kubernetes API 对象 JSON 串。
例如,以下的示例是一个合法的 patch 请求消息体,该请求对应
/apis/batch/v1/namespaces/some-namespace/jobs/some-job-name
。
[
{
"op" : "replace" ,
"path" : "/spec/parallelism" ,
"value" : 0
},
{
"op" : "remove" ,
"path" : "/spec/template/spec/containers/0/terminationMessagePolicy"
}
]
Log 后端 Log 后端将审计事件写入 JSONlines 格式的文件。
你可以使用以下 kube-apiserver
标志配置 Log 审计后端:
--audit-log-path
指定用来写入审计事件的日志文件路径。不指定此标志会禁用日志后端。-
意味着标准化--audit-log-maxage
定义保留旧审计日志文件的最大天数--audit-log-maxbackup
定义要保留的审计日志文件的最大数量--audit-log-maxsize
定义审计日志文件的最大大小(兆字节)如果你的集群控制面以 Pod 的形式运行 kube-apiserver,记得要通过 hostPath
卷来访问策略文件和日志文件所在的目录,这样审计记录才会持久保存下来。例如:
--audit-policy-file= /etc/kubernetes/audit-policy.yaml \
--audit-log-path= /var/log/kubernetes/audit/audit.log
接下来挂载数据卷:
...
volumeMounts :
- mountPath : /etc/kubernetes/audit-policy.yaml
name : audit
readOnly : true
- mountPath : /var/log/kubernetes/audit/
name : audit-log
readOnly : false
最后配置 hostPath
:
...
volumes :
- name : audit
hostPath :
path : /etc/kubernetes/audit-policy.yaml
type : File
- name : audit-log
hostPath :
path : /var/log/kubernetes/audit/
type : DirectoryOrCreate
Webhook 后端 Webhook 后端将审计事件发送到远程 Web API,该远程 API 应该暴露与 kube-apiserver
形式相同的 API,包括其身份认证机制。你可以使用如下 kube-apiserver 标志来配置
Webhook 审计后端:
--audit-webhook-config-file
设置 Webhook 配置文件的路径。Webhook 配置文件实际上是一个
kubeconfig 文件 。--audit-webhook-initial-backoff
指定在第一次失败后重发请求等待的时间。随后的请求将以指数退避重试。Webhook 配置文件使用 kubeconfig 格式指定服务的远程地址和用于连接它的凭据。
事件批处理 日志和 Webhook 后端都支持批处理。以 Webhook 为例,以下是可用参数列表。要获取日志
后端的同样参数,请在参数名称中将 webhook
替换为 log
。
默认情况下,在 webhook
中批处理是被启用的,在 log
中批处理是被禁用的。
同样,默认情况下,在 webhook
中启用带宽限制,在 log
中禁用带宽限制。
--audit-webhook-mode
定义缓存策略,可选值如下:batch
- 以批处理缓存事件和异步的过程。这是默认值。blocking
- 在 API 服务器处理每个单独事件时,阻塞其响应。blocking-strict
- 与 blocking
相同,不过当审计日志在 RequestReceived 阶段
失败时,整个 API 服务请求会失效。以下参数仅用于 batch
模式:
--audit-webhook-batch-buffer-size
定义 batch 之前要缓存的事件数。
如果传入事件的速率溢出缓存区,则会丢弃事件。--audit-webhook-batch-max-size
定义一个 batch 中的最大事件数。--audit-webhook-batch-max-wait
无条件 batch 队列中的事件前等待的最大事件。--audit-webhook-batch-throttle-qps
每秒生成的最大批次数。--audit-webhook-batch-throttle-burst
在达到允许的 QPS 前,同一时刻允许存在的最大 batch 生成数。参数调整 需要设置参数以适应 API 服务器上的负载。
例如,如果 kube-apiserver 每秒收到 100 个请求,并且每个请求仅在 ResponseStarted
和 ResponseComplete
阶段进行审计,则应该考虑每秒生成约 200 个审计事件。
假设批处理中最多有 100 个事件,则应将限制级别设置为每秒至少 2 个查询。
假设后端最多需要 5 秒钟来写入事件,你应该设置缓冲区大小以容纳最多 5 秒的事件,
即 10 个 batch,即 1000 个事件。
但是,在大多数情况下,默认参数应该足够了,你不必手动设置它们。
你可以查看 kube-apiserver 公开的以下 Prometheus 指标,并在日志中监控审计子系统的状态。
apiserver_audit_event_total
包含所有暴露的审计事件数量的指标。apiserver_audit_error_total
在暴露时由于发生错误而被丢弃的事件的数量。日志条目截断 日志后端和 Webhook 后端都支持限制所输出的事件的尺寸。
例如,下面是可以为日志后端配置的标志列表:
audit-log-truncate-enabled
:是否弃用事件和批次的截断处理。audit-log-truncate-max-batch-size
:向下层后端发送的各批次的最大尺寸字节数。audit-log-truncate-max-event-size
:向下层后端发送的审计事件的最大尺寸字节数。默认情况下,截断操作在 webhook
和 log
后端都是被禁用的,集群管理员需要设置
audit-log-truncate-enabled
或 audit-webhook-truncate-enabled
标志来启用此操作。
4.3.1.6 - 使用 telepresence 在本地开发和调试服务 说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Kubernetes 应用程序通常由多个独立的服务组成,每个服务都在自己的容器中运行。
在远端的 Kubernetes 集群上开发和调试这些服务可能很麻烦,
需要在运行的容器上打开 Shell ,
以运行调试工具。
telepresence
是一个工具,用于简化本地开发和调试服务的过程,同时可以将服务代理到远程 Kubernetes 集群。
telepresence
允许你使用自定义工具(例如:调试器 和 IDE)调试服务,
并提供对 Configmap、Secret 和远程集群上运行的服务的完全访问。
本文档描述如何在本地使用 telepresence
开发和调试远程集群上运行的服务。
准备开始 从本机连接到远程 Kubernetes 集群 安装 telepresence
后,运行 telepresence connect
来启动它的守护进程并将本地工作站连接到远程 Kubernetes 集群。
$ telepresence connect
Launching Telepresence Daemon
...
Connected to context default (https://<cluster public IP>)
你可以通过 curl 使用 Kubernetes 语法访问服务,例如:curl -ik https://kubernetes.default
开发和调试现有的服务 在 Kubernetes 上开发应用程序时,通常对单个服务进行编程或调试。
服务可能需要访问其他服务以进行测试和调试。
一种选择是使用连续部署流水线,但即使最快的部署流水线也会在程序或调试周期中引入延迟。
使用 telepresence intercept $SERVICE_NAME --port $LOCAL_PORT:$REMOTE_PORT
命令创建一个 "拦截器" 用于重新路由远程服务流量。
环境变量:
$SERVICE_NAME
是本地服务名称$LOCAL_PORT
是服务在本地工作站上运行的端口$REMOTE_PORT
是服务在集群中侦听的端口运行此命令会告诉 Telepresence 将远程流量发送到本地服务,而不是远程 Kubernetes 集群中的服务中。
在本地编辑保存服务源代码,并在访问远程应用时查看相应变更会立即生效。
还可以使用调试器或任何其他本地开发工具运行本地服务。
Telepresence 是如何工作的? Telepresence 会在远程集群中运行的现有应用程序容器旁边安装流量代理 sidecar。
当它捕获进入 Pod 的所有流量请求时,不是将其转发到远程集群中的应用程序,
而是路由所有流量(当创建全局拦截器 时)
或流量的一个子集(当创建自定义拦截器 时)
到本地开发环境。
接下来 如果你对实践教程感兴趣,请查看本教程 ,其中介绍了在 Google Kubernetes Engine 上本地开发 Guestbook 应用程序。
如需进一步了解,请访问 Telepresence 官方网站 。
4.3.1.7 - 资源监控工具 要扩展应用程序并提供可靠的服务,你需要了解应用程序在部署时的行为。
你可以通过检测容器检查 Kubernetes 集群中的应用程序性能,
Pod ,
服务
和整个集群的特征。
Kubernetes 在每个级别上提供有关应用程序资源使用情况的详细信息。
此信息使你可以评估应用程序的性能,以及在何处可以消除瓶颈以提高整体性能。
在 Kubernetes 中,应用程序监控不依赖单个监控解决方案。
在新集群上,你可以使用资源度量 或
完整度量 管道来收集监视统计信息。
资源度量管道 资源指标管道提供了一组与集群组件,例如
Horizontal Pod Autoscaler
控制器以及 kubectl top
实用程序相关的有限度量。
这些指标是由轻量级的、短期、内存存储的
metrics-server 收集的,
通过 metrics.k8s.io
公开。
度量服务器发现集群中的所有节点,并且查询每个节点的
kubelet
以获取 CPU 和内存使用情况。
Kubelet 充当 Kubernetes 主节点与节点之间的桥梁,管理机器上运行的 Pod 和容器。
kubelet 将每个 Pod 转换为其组成的容器,并通过容器运行时接口从容器运行时获取各个容器使用情况统计信息。
如果某个容器运行时使用 Linux cgroups 和名字空间来实现容器。
并且这一容器运行时不发布资源用量统计信息,
那么 kubelet 可以直接查找这些统计信息(使用来自 cAdvisor 的代码)。
无论这些统计信息如何到达,kubelet 都会通过 metrics-server Resource Metrics API 公开聚合的
Pod 资源用量统计信息。
该 API 在 kubelet 的经过身份验证和只读的端口上的 /metrics/resource/v1beta1
中提供。
完整度量管道 一个完整度量管道可以让你访问更丰富的度量。
Kubernetes 还可以根据集群的当前状态,使用 Pod 水平自动扩缩器等机制,
通过自动调用扩展或调整集群来响应这些度量。
监控管道从 kubelet 获取度量值,然后通过适配器将它们公开给 Kubernetes,
方法是实现 custom.metrics.k8s.io
或 external.metrics.k8s.io
API。
Prometheus 是一个 CNCF 项目,可以原生监控 Kubernetes、
节点和 Prometheus 本身。
完整度量管道项目不属于 CNCF 的一部分,不在 Kubernetes 文档的范围之内。
了解其他调试工具,包括:
4.3.2 - 应用故障排除 调试常见的容器应用问题.
该文档包含一组用于解决容器化应用程序问题的资源。
它涵盖了诸如 Kubernetes 资源(如 Pod、Service 或 StatefulSets)的常见问题、
关于理解容器终止消息的建议以及调试正在运行的容器的方法。
4.3.2.1 - 调试 Pod 本指南帮助用户调试那些部署到 Kubernetes 上后没有正常运行的应用。
本指南 并非 指导用户如何调试集群。
如果想调试集群的话,请参阅这里 。
诊断问题 故障排查的第一步是先给问题分类。问题是什么?是关于 Pod、Replication Controller 还是 Service?
调试 Pod 调试 Pod 的第一步是查看 Pod 信息。用如下命令查看 Pod 的当前状态和最近的事件:
kubectl describe pods ${ POD_NAME }
查看一下 Pod 中的容器所处的状态。这些容器的状态都是 Running
吗?最近有没有重启过?
后面的调试都是要依靠 Pod 的状态的。
Pod 停滞在 Pending 状态 如果一个 Pod 停滞在 Pending
状态,表示 Pod 没有被调度到节点上。通常这是因为
某种类型的资源不足导致无法调度。
查看上面的 kubectl describe ...
命令的输出,其中应该显示了为什么没被调度的原因。
常见原因如下:
资源不足 :
你可能耗尽了集群上所有的 CPU 或内存。此时,你需要删除 Pod、调整资源请求或者为集群添加节点。
更多信息请参阅计算资源文档
使用了 hostPort
:
如果绑定 Pod 到 hostPort
,那么能够运行该 Pod 的节点就有限了。
多数情况下,hostPort
是非必要的,而应该采用 Service 对象来暴露 Pod。
如果确实需要使用 hostPort
,那么集群中节点的个数就是所能创建的 Pod
的数量上限。
Pod 停滞在 Waiting 状态 如果 Pod 停滞在 Waiting
状态,则表示 Pod 已经被调度到某工作节点,但是无法在该节点上运行。
同样,kubectl describe ...
命令的输出可能很有用。
Waiting
状态的最常见原因是拉取镜像失败。要检查的有三个方面:
确保镜像名字拼写正确 确保镜像已被推送到镜像仓库 尝试手动是否能拉取镜像。例如,如果你在你的 PC 上使用 Docker,请运行 docker pull <镜像>
。 Pod 处于 Crashing 或别的不健康状态 一旦 Pod 被调度,就可以采用
调试运行中的 Pod
中的方法来进一步调试。
Pod 处于 Running 态但是没有正常工作 如果 Pod 行为不符合预期,很可能 Pod 描述(例如你本地机器上的 mypod.yaml
)中有问题,
并且该错误在创建 Pod 时被忽略掉,没有报错。
通常,Pod 的定义中节区嵌套关系错误、字段名字拼错的情况都会引起对应内容被忽略掉。
例如,如果你误将 command
写成 commnd
,Pod 虽然可以创建,
但它不会执行你期望它执行的命令行。
可以做的第一件事是删除你的 Pod,并尝试带有 --validate
选项重新创建。
例如,运行 kubectl apply --validate -f mypod.yaml
。
如果 command
被误拼成 commnd
,你将会看到下面的错误信息:
I0805 10:43:25.129850 46757 schema.go:126] unknown field: commnd
I0805 10:43:25.129973 46757 schema.go:129] this may be a false alarm, see https://github.com/kubernetes/kubernetes/issues/6842
pods/mypod
接下来就要检查的是 API 服务器上的 Pod 与你所期望创建的是否匹配
(例如,你原本使用本机上的一个 YAML 文件来创建 Pod)。
例如,运行 kubectl get pods/mypod -o yaml > mypod-on-apiserver.yaml
,
之后手动比较 mypod.yaml
与从 API 服务器取回的 Pod 描述。
从 API 服务器处获得的 YAML 通常包含一些创建 Pod 所用的 YAML 中不存在的行,这是正常的。
不过,如果如果源文件中有些行在 API 服务器版本中不存在,则意味着
Pod 规约是有问题的。
调试副本控制器 副本控制器相对比较简单直接。它们要么能创建 Pod,要么不能。
如果不能创建 Pod,请参阅上述说明 调试 Pod。
你也可以使用 kubectl describe rc ${CONTROLLER_NAME}
命令来检视副本控制器相关的事件。
调试 Service 服务支持在多个 Pod 间负载均衡。
有一些常见的问题可以造成服务无法正常工作。
以下说明将有助于调试服务的问题。
首先,验证服务是否有端点。对于每一个 Service 对象,API 服务器为其提供对应的
endpoints
资源。
通过如下命令可以查看 endpoints 资源:
kubectl get endpoints ${ SERVICE_NAME }
确保 Endpoints 与服务成员 Pod 个数一致。
例如,如果你的 Service 用来运行 3 个副本的 nginx 容器,你应该会在 Service 的 Endpoints
中看到 3 个不同的 IP 地址。
服务缺少 Endpoints 如果没有 Endpoints,请尝试使用 Service 所使用的标签列出 Pod。
假定你的服务包含如下标签选择算符:
...
spec :
- selector :
name : nginx
type : frontend
你可以使用如下命令列出与选择算符相匹配的 Pod,并验证这些 Pod 是否归属于创建的服务:
kubectl get pods --selector= name = nginx,type= frontend
验证 Pod 的 containerPort
与服务的 targetPort
是否匹配。
网络流量未被转发 请参阅调试 Service 了解更多信息。
接下来 如果上述方法都不能解决你的问题,
请按照调试 Service 文档 中的介绍,
确保你的 Service
处于 Running 态,有 Endpoints
被创建,Pod
真的在提供服务;
DNS 服务已配置并正常工作,iptables 规则也以安装并且 kube-proxy
也没有异常行为。
你也可以访问故障排查文档 来获取更多信息。
4.3.2.2 - 调试 Service 对于新安装的 Kubernetes,经常出现的问题是 Service 无法正常运行。你已经通过
Deployment(或其他工作负载控制器)运行了 Pod,并创建 Service ,
但是当你尝试访问它时,没有任何响应。此文档有望对你有所帮助并找出问题所在。
在 Pod 中运行命令 对于这里的许多步骤,你可能希望知道运行在集群中的 Pod 看起来是什么样的。
最简单的方法是运行一个交互式的 busybox Pod:
kubectl run -it --rm --restart=Never busybox --image=gcr.io/google-containers/busybox sh
说明: 如果没有看到命令提示符,请按回车。
如果你已经有了你想使用的正在运行的 Pod,则可以运行以下命令去进入:
kubectl exec <POD-NAME> -c <CONTAINER-NAME> -- <COMMAND>
设置 为了完成本次实践的任务,我们先运行几个 Pod。
由于你可能正在调试自己的 Service,所以,你可以使用自己的信息进行替换,
或者你也可以跟着教程并开始下面的步骤来获得第二个数据点。
kubectl create deployment hostnames --image= registry.k8s.io/serve_hostname
deployment.apps/hostnames created
kubectl
命令将打印创建或变更的资源的类型和名称,它们可以在后续命令中使用。
让我们将这个 deployment 的副本数扩至 3。
kubectl scale deployment hostnames --replicas= 3
deployment.apps/hostnames scaled
请注意这与你使用以下 YAML 方式启动 Deployment 类似:
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : hostnames
name : hostnames
spec :
selector :
matchLabels :
app : hostnames
replicas : 3
template :
metadata :
labels :
app : hostnames
spec :
containers :
- name : hostnames
image : registry.k8s.io/serve_hostname
"app" 标签是 kubectl create deployment
根据 Deployment 名称自动设置的。
确认你的 Pod 是运行状态:
kubectl get pods -l app = hostnames
NAME READY STATUS RESTARTS AGE
hostnames-632524106-bbpiw 1/1 Running 0 2m
hostnames-632524106-ly40y 1/1 Running 0 2m
hostnames-632524106-tlaok 1/1 Running 0 2m
你还可以确认你的 Pod 是否正在提供服务。你可以获取 Pod IP 地址列表并直接对其进行测试。
kubectl get pods -l app = hostnames \
-o go-template= '{{range .items}}{{.status.podIP}}{{"\n"}}{{end}}'
10.244.0.5
10.244.0.6
10.244.0.7
用于本教程的示例容器通过 HTTP 在端口 9376 上提供其自己的主机名,
但是如果要调试自己的应用程序,则需要使用你的 Pod 正在侦听的端口号。
在 Pod 内运行:
for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
wget -qO- $ep
done
输出类似这样:
hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok
如果此时你没有收到期望的响应,则你的 Pod 状态可能不健康,或者可能没有在你认为正确的端口上进行监听。
你可能会发现 kubectl logs
命令对于查看正在发生的事情很有用,
或者你可能需要通过kubectl exec
直接进入 Pod 中并从那里进行调试。
假设到目前为止一切都已按计划进行,那么你可以开始调查为何你的 Service 无法正常工作。
Service 是否存在? 细心的读者会注意到我们实际上尚未创建 Service —— 这是有意而为之。
这一步有时会被遗忘,这是首先要检查的步骤。
那么,如果我尝试访问不存在的 Service 会怎样?
假设你有另一个 Pod 通过名称匹配到 Service,你将得到类似结果:
Resolving hostnames (hostnames)... failed: Name or service not known.
wget: unable to resolve host address 'hostnames'
首先要检查的是该 Service 是否真实存在:
kubectl get svc hostnames
No resources found.
Error from server (NotFound): services "hostnames" not found
让我们创建 Service。 和以前一样,在这次实践中 —— 你可以在此处使用自己的 Service 的内容。
kubectl expose deployment hostnames --port= 80 --target-port= 9376
service/hostnames exposed
重新运行查询命令:
kubectl get svc hostnames
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hostnames ClusterIP 10.0.1.175 <none> 80/TCP 5s
现在你知道了 Service 确实存在。
同前,此步骤效果与通过 YAML 方式启动 Service 一样:
apiVersion : v1
kind : Service
metadata :
labels :
app : hostnames
name : hostnames
spec :
selector :
app : hostnames
ports :
- name : default
protocol : TCP
port : 80
targetPort : 9376
为了突出配置范围的完整性,你在此处创建的 Service 使用的端口号与 Pods 不同。
对于许多真实的 Service,这些值可以是相同的。
是否存在影响目标 Pod 的网络策略入站规则? 如果你部署了任何可能影响到 hostnames-*
Pod 的传入流量的网络策略入站规则,
则需要对其进行检查。
详细信息,请参阅网络策略 。
Service 是否可通过 DNS 名字访问? 通常客户端通过 DNS 名称来匹配到 Service。
从相同命名空间下的 Pod 中运行以下命令:
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: hostnames
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local
如果失败,那么你的 Pod 和 Service 可能位于不同的命名空间中,
请尝试使用限定命名空间的名称(同样在 Pod 内运行):
nslookup hostnames.default
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: hostnames.default
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local
如果成功,那么需要调整你的应用,使用跨命名空间的名称去访问它,
或者在相同的命名空间中运行应用和 Service。如果仍然失败,请尝试一个完全限定的名称:
nslookup hostnames.default.svc.cluster.local
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: hostnames.default.svc.cluster.local
Address 1: 10.0.1.175 hostnames.default.svc.cluster.local
注意这里的后缀:"default.svc.cluster.local"。"default" 是我们正在操作的命名空间。
"svc" 表示这是一个 Service。"cluster.local" 是你的集群域,在你自己的集群中可能会有所不同。
你也可以在集群中的节点上尝试此操作:
说明: 10.0.0.10 是集群的 DNS 服务 IP,你的可能有所不同。
nslookup hostnames.default.svc.cluster.local 10.0.0.10
Server: 10.0.0.10
Address: 10.0.0.10#53
Name: hostnames.default.svc.cluster.local
Address: 10.0.1.175
如果你能够使用完全限定的名称查找,但不能使用相对名称,则需要检查你 Pod 中的
/etc/resolv.conf
文件是否正确。在 Pod 中运行以下命令:
你应该可以看到类似这样的输出:
nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local example.com
options ndots:5
nameserver
行必须指示你的集群的 DNS Service,
它是通过 --cluster-dns
标志传递到 kubelet 的。
search
行必须包含一个适当的后缀,以便查找 Service 名称。
在本例中,它查找本地命名空间(default.svc.cluster.local
)中的服务和所有命名空间
(svc.cluster.local
)中的服务,最后在集群(cluster.local
)中查找服务的名称。
根据你自己的安装情况,可能会有额外的记录(最多 6 条)。
集群后缀是通过 --cluster-domain
标志传递给 kubelet
的。
本文中,我们假定后缀是 “cluster.local”。
你的集群配置可能不同,这种情况下,你应该在上面的所有命令中更改它。
options
行必须设置足够高的 ndots
,以便 DNS 客户端库考虑搜索路径。
在默认情况下,Kubernetes 将这个值设置为 5,这个值足够高,足以覆盖它生成的所有 DNS 名称。
是否存在 Service 能通过 DNS 名称访问? 如果上面的方式仍然失败,DNS 查找不到你需要的 Service ,你可以后退一步,
看看还有什么其它东西没有正常工作。
Kubernetes 主 Service 应该一直是工作的。在 Pod 中运行如下命令:
nslookup kubernetes.default
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: kubernetes.default
Address 1: 10.0.0.1 kubernetes.default.svc.cluster.local
如果失败,你可能需要转到本文的 kube-proxy 节,
或者甚至回到文档的顶部重新开始,但不是调试你自己的 Service ,而是调试 DNS Service。
Service 能够通过 IP 访问么? 假设你已经确认 DNS 工作正常,那么接下来要测试的是你的 Service 能否通过它的 IP 正常访问。
从集群中的一个 Pod,尝试访问 Service 的 IP(从上面的 kubectl get
命令获取)。
for i in $( seq 1 3) ; do
wget -qO- 10.0.1.175:80
done
输出应该类似这样:
hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok
如果 Service 状态是正常的,你应该得到正确的响应。
如果没有,有很多可能出错的地方,请继续阅读。
Service 的配置是否正确? 这听起来可能很愚蠢,但你应该两次甚至三次检查你的 Service 配置是否正确,并且与你的 Pod 匹配。
查看你的 Service 配置并验证它:
kubectl get service hostnames -o json
{
"kind" : "Service" ,
"apiVersion" : "v1" ,
"metadata" : {
"name" : "hostnames" ,
"namespace" : "default" ,
"uid" : "428c8b6c-24bc-11e5-936d-42010af0a9bc" ,
"resourceVersion" : "347189" ,
"creationTimestamp" : "2015-07-07T15:24:29Z" ,
"labels" : {
"app" : "hostnames"
}
},
"spec" : {
"ports" : [
{
"name" : "default" ,
"protocol" : "TCP" ,
"port" : 80 ,
"targetPort" : 9376 ,
"nodePort" : 0
}
],
"selector" : {
"app" : "hostnames"
},
"clusterIP" : "10.0.1.175" ,
"type" : "ClusterIP" ,
"sessionAffinity" : "None"
},
"status" : {
"loadBalancer" : {}
}
}
你想要访问的 Service 端口是否在 spec.ports[]
中列出? targetPort
对你的 Pod 来说正确吗(许多 Pod 使用与 Service 不同的端口)?如果你想使用数值型端口,那么它的类型是一个数值(9376)还是字符串 “9376”? 如果你想使用名称型端口,那么你的 Pod 是否暴露了一个同名端口? 端口的 protocol
和 Pod 的是否对应? Service 有 Endpoints 吗? 如果你已经走到了这一步,你已经确认你的 Service 被正确定义,并能通过 DNS 解析。
现在,让我们检查一下,你运行的 Pod 确实是被 Service 选中的。
早些时候,我们已经看到 Pod 是运行状态。我们可以再检查一下:
kubectl get pods -l app = hostnames
NAME READY STATUS RESTARTS AGE
hostnames-632524106-bbpiw 1/1 Running 0 1h
hostnames-632524106-ly40y 1/1 Running 0 1h
hostnames-632524106-tlaok 1/1 Running 0 1h
-l app=hostnames
参数是在 Service 上配置的标签选择器。
“AGE” 列表明这些 Pod 已经启动一个小时了,这意味着它们运行良好,而未崩溃。
"RESTARTS" 列表明 Pod 没有经常崩溃或重启。经常性崩溃可能导致间歇性连接问题。
如果重启次数过大,通过调试 Pod
了解相关技术。
在 Kubernetes 系统中有一个控制回路,它评估每个 Service 的选择算符,并将结果保存到
Endpoints 对象中。
kubectl get endpoints hostnames
NAME ENDPOINTS
hostnames 10.244.0.5:9376,10.244.0.6:9376,10.244.0.7:9376
这证实 Endpoints 控制器已经为你的 Service 找到了正确的 Pods。
如果 ENDPOINTS
列的值为 <none>
,则应检查 Service 的 spec.selector
字段,
以及你实际想选择的 Pod 的 metadata.labels
的值。
常见的错误是输入错误或其他错误,例如 Service 想选择 app=hostnames
,但是
Deployment 指定的是 run=hostnames
。在 1.18之前的版本中 kubectl run
也可以被用来创建 Deployment。
Pod 工作正常吗? 至此,你知道你的 Service 已存在,并且已匹配到你的Pod。在本实验的开始,你已经检查了 Pod 本身。
让我们再次检查 Pod 是否确实在工作 - 你可以绕过 Service 机制并直接转到 Pod,
如上面的 Endpoints 所示。
说明: 这些命令使用的是 Pod 端口(9376),而不是 Service 端口(80)。
在 Pod 中运行:
for ep in 10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376; do
wget -qO- $ep
done
输出应该类似这样:
hostnames-632524106-bbpiw
hostnames-632524106-ly40y
hostnames-632524106-tlaok
你希望 Endpoint 列表中的每个 Pod 都返回自己的主机名。
如果情况并非如此(或你自己的 Pod 的正确行为是什么),你应调查发生了什么事情。
kube-proxy 正常工作吗? 如果你到达这里,则说明你的 Service 正在运行,拥有 Endpoints,Pod 真正在提供服务。
此时,整个 Service 代理机制是可疑的。让我们一步一步地确认它没问题。
Service 的默认实现(在大多数集群上应用的)是 kube-proxy。
这是一个在每个节点上运行的程序,负责配置用于提供 Service 抽象的机制之一。
如果你的集群不使用 kube-proxy,则以下各节将不适用,你将必须检查你正在使用的 Service 的实现方式。
kube-proxy 正常运行吗? {#is-kube-proxy working} 确认 kube-proxy
正在节点上运行。在节点上直接运行,你将会得到类似以下的输出:
ps auxw | grep kube-proxy
root 4194 0.4 0.1 101864 17696 ? Sl Jul04 25:43 /usr/local/bin/kube-proxy --master=https://kubernetes-master --kubeconfig=/var/lib/kube-proxy/kubeconfig --v=2
下一步,确认它并没有出现明显的失败,比如连接主节点失败。要做到这一点,你必须查看日志。
访问日志的方式取决于你节点的操作系统。
在某些操作系统上日志是一个文件,如 /var/log/messages kube-proxy.log,
而其他操作系统使用 journalctl
访问日志。你应该看到输出类似于:
I1027 22:14:53.995134 5063 server.go:200] Running in resource-only container "/kube-proxy"
I1027 22:14:53.998163 5063 server.go:247] Using iptables Proxier.
I1027 22:14:53.999055 5063 server.go:255] Tearing down userspace rules. Errors here are acceptable.
I1027 22:14:54.038140 5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns-tcp" to [10.244.1.3:53]
I1027 22:14:54.038164 5063 proxier.go:352] Setting endpoints for "kube-system/kube-dns:dns" to [10.244.1.3:53]
I1027 22:14:54.038209 5063 proxier.go:352] Setting endpoints for "default/kubernetes:https" to [10.240.0.2:443]
I1027 22:14:54.038238 5063 proxier.go:429] Not syncing iptables until Services and Endpoints have been received from master
I1027 22:14:54.040048 5063 proxier.go:294] Adding new service "default/kubernetes:https" at 10.0.0.1:443/TCP
I1027 22:14:54.040154 5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns" at 10.0.0.10:53/UDP
I1027 22:14:54.040223 5063 proxier.go:294] Adding new service "kube-system/kube-dns:dns-tcp" at 10.0.0.10:53/TCP
如果你看到有关无法连接主节点的错误消息,则应再次检查节点配置和安装步骤。
kube-proxy
无法正确运行的可能原因之一是找不到所需的 conntrack
二进制文件。
在一些 Linux 系统上,这也是可能发生的,这取决于你如何安装集群,
例如,你是手动开始一步步安装 Kubernetes。如果是这样的话,你需要手动安装
conntrack
包(例如,在 Ubuntu 上使用 sudo apt install conntrack
),然后重试。
Kube-proxy 可以以若干模式之一运行。在上述日志中,Using iptables Proxier
行表示 kube-proxy 在 "iptables" 模式下运行。
最常见的另一种模式是 "ipvs"。先前的 "userspace" 模式已经被这些所代替。
Iptables 模式 在 "iptables" 模式中, 你应该可以在节点上看到如下输出:
iptables-save | grep hostnames
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376
-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR
对于每个 Service 的每个端口,应有 1 条 KUBE-SERVICES
规则、一个 KUBE-SVC-<hash>
链。
对于每个 Pod 末端,在那个 KUBE-SVC-<hash>
链中应该有一些规则与之对应,还应该
有一个 KUBE-SEP-<hash>
链与之对应,其中包含为数不多的几条规则。
实际的规则数量可能会根据你实际的配置(包括 NodePort 和 LoadBalancer 服务)有所不同。
IPVS 模式 在 "ipvs" 模式中, 你应该在节点下看到如下输出:
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
...
TCP 10.0.1.175:80 rr
-> 10.244.0.5:9376 Masq 1 0 0
-> 10.244.0.6:9376 Masq 1 0 0
-> 10.244.0.7:9376 Masq 1 0 0
...
对于每个 Service 的每个端口,还有 NodePort,External IP 和 LoadBalancer 类型服务
的 IP,kube-proxy 将创建一个虚拟服务器。
对于每个 Pod 末端,它将创建相应的真实服务器。
在此示例中,服务主机名(10.0.1.175:80
)拥有 3 个末端(10.244.0.5:9376
、
10.244.0.6:9376
和 10.244.0.7:9376
)。
Userspace 模式 在极少数情况下,你可能会用到 "userspace" 模式。在你的节点上运行:
iptables-save | grep hostnames
-A KUBE-PORTALS-CONTAINER -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j REDIRECT --to-ports 48577
-A KUBE-PORTALS-HOST -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames:default" -m tcp --dport 80 -j DNAT --to-destination 10.240.115.247:48577
对于 Service (本例中只有一个)的每个端口,应当有 2 条规则:
一条 "KUBE-PORTALS-CONTAINER" 和一条 "KUBE-PORTALS-HOST" 规则。
几乎没有人应该再使用 "userspace" 模式,因此你在这里不会花更多的时间。
kube-proxy 是否在执行代理操作? 假设你确实遇到上述情况之一,请重试从节点上通过 IP 访问你的 Service :
hostnames-632524106-bbpiw
如果失败,并且你正在使用用户空间代理,则可以尝试直接访问代理。
如果你使用的是 iptables 代理,请跳过本节。
回顾上面的 iptables-save
输出,并提取 kube-proxy
为你的 Service 所使用的端口号。
在上面的例子中,端口号是 “48577”。现在试着连接它:
hostnames-632524106-tlaok
如果这步操作仍然失败,请查看 kube-proxy
日志中的特定行,如:
Setting endpoints for default/hostnames:default to [10.244.0.5:9376 10.244.0.6:9376 10.244.0.7:9376]
如果你没有看到这些,请尝试将 -V
标志设置为 4 并重新启动 kube-proxy
,然后再查看日志。
边缘案例: Pod 无法通过 Service IP 连接到它本身 这听起来似乎不太可能,但是确实可能发生,并且应该可以工作。
如果网络没有为“发夹模式(Hairpin)”流量生成正确配置,
通常当 kube-proxy
以 iptables
模式运行,并且 Pod 与桥接网络连接时,就会发生这种情况。
kubelet
提供了 hairpin-mode
标志 。
如果 Service 的末端尝试访问自己的 Service VIP,则该端点可以把流量负载均衡回来到它们自身。
hairpin-mode
标志必须被设置为 hairpin-veth
或者 promiscuous-bridge
。
诊断此类问题的常见步骤如下:
确认 hairpin-mode
被设置为 hairpin-veth
或 promiscuous-bridge
。
你应该可以看到下面这样。本例中 hairpin-mode
被设置为 promiscuous-bridge
。
root 3392 1.1 0.8 186804 65208 ? Sl 00:51 11:11 /usr/local/bin/kubelet --enable-debugging-handlers=true --config=/etc/kubernetes/manifests --allow-privileged=True --v=4 --cluster-dns=10.0.0.10 --cluster-domain=cluster.local --configure-cbr0=true --cgroup-root=/ --system-cgroups=/system --hairpin-mode=promiscuous-bridge --runtime-cgroups=/docker-daemon --kubelet-cgroups=/kubelet --babysit-daemons=true --max-pods=110 --serialize-image-pulls=false --outofdisk-transition-frequency=0
确认有效的 hairpin-mode
。要做到这一点,你必须查看 kubelet 日志。
访问日志取决于节点的操作系统。在一些操作系统上,它是一个文件,如 /var/log/kubelet.log,
而其他操作系统则使用 journalctl
访问日志。请注意,由于兼容性,
有效的 hairpin-mode
可能不匹配 --hairpin-mode
标志。在 kubelet.log
中检查是否有带有关键字 hairpin
的日志行。应该有日志行指示有效的
hairpin-mode
,就像下面这样。
I0629 00:51:43.648698 3252 kubelet.go:380] Hairpin mode set to "promiscuous-bridge"
如果有效的发卡模式是 promiscuous-bridge
, 要保证 Kubelet
有操作节点上
Linux 网桥的权限。如果 cbr0
桥正在被使用且被正确设置,你将会看到如下输出:
ifconfig cbr0 |grep PROMISC
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1460 Metric:1
寻求帮助 如果你走到这一步,那么就真的是奇怪的事情发生了。你的 Service 正在运行,有 Endpoints 存在,
你的 Pods 也确实在提供服务。你的 DNS 正常,iptables
规则已经安装,kube-proxy
看起来也正常。
然而 Service 还是没有正常工作。这种情况下,请告诉我们,以便我们可以帮助调查!
通过 Slack 或者 Forum 或者
GitHub 联系我们。
接下来 访问故障排查概述文档 获取更多信息。
4.3.2.3 - 调试 StatefulSet 此任务展示如何调试 StatefulSet。
准备开始 你需要有一个 Kubernetes 集群,已配置好的 kubectl 命令行工具与你的集群进行通信。 你应该有一个运行中的 StatefulSet,以便用于调试。 调试 StatefulSet StatefulSet 在创建 Pod 时为其设置了 app.kubernetes.io/name=MyApp
标签,列出仅属于某 StatefulSet
的所有 Pod 时,可以使用以下命令:
kubectl get pods -l app.kubernetes.io/name= MyApp
如果你发现列出的任何 Pod 长时间处于 Unknown
或 Terminating
状态,请参阅
删除 StatefulSet Pod
了解如何处理它们的说明。
你可以参考调试 Pod
来调试 StatefulSet 中的各个 Pod。
接下来 进一步了解如何调试 Init 容器 。
4.3.2.4 - 确定 Pod 失败的原因 本文介绍如何编写和读取容器的终止消息。
终止消息为容器提供了一种方法,可以将有关致命事件的信息写入某个位置,
在该位置可以通过仪表板和监控软件等工具轻松检索和显示致命事件。
在大多数情况下,你放入终止消息中的信息也应该写入
常规 Kubernetes 日志 。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
读写终止消息 在本练习中,你将创建运行一个容器的 Pod。
配置文件指定在容器启动时要运行的命令。
apiVersion : v1
kind : Pod
metadata :
name : termination-demo
spec :
containers :
- name : termination-demo-container
image : debian
command : ["/bin/sh" ]
args : ["-c" , "sleep 10 && echo Sleep expired > /dev/termination-log" ]
基于 YAML 配置文件创建 Pod:
kubectl apply -f https://k8s.io/examples/debug/termination.yaml
YAML 文件中,在 command
和 args
字段,你可以看到容器休眠 10 秒然后将 "Sleep expired"
写入 /dev/termination-log
文件。
容器写完 "Sleep expired" 消息后就终止了。
显示 Pod 的信息:
kubectl get pod termination-demo
重复前面的命令直到 Pod 不再运行。
显示 Pod 的详细信息:
kubectl get pod termination-demo --output= yaml
输出结果包含 "Sleep expired" 消息:
apiVersion : v1
kind : Pod
...
lastState :
terminated :
containerID : ...
exitCode : 0
finishedAt : ...
message : |
Sleep expired
...
使用 Go 模板过滤输出结果,使其只含有终止消息:
kubectl get pod termination-demo -o go-template= "{{range .status.containerStatuses}}{{.lastState.terminated.message}}{{end}}"
如果你正在运行多容器 Pod,则可以使用 Go 模板来包含容器的名称。这样,你可以发现哪些容器出现故障:
kubectl get pod multi-container-pod -o go-template= '{{range .status.containerStatuses}}{{printf "%s:\n%s\n\n" .name .lastState.terminated.message}}{{end}}'
定制终止消息 Kubernetes 从容器的 terminationMessagePath
字段中指定的终止消息文件中检索终止消息,
默认值为 /dev/termination-log
。
通过定制这个字段,你可以告诉 Kubernetes 使用不同的文件。
Kubernetes 使用指定文件中的内容在成功和失败时填充容器的状态消息。
终止消息旨在简要说明最终状态,例如断言失败消息。
kubelet 会截断长度超过 4096 字节的消息。
所有容器的总消息长度限制为 12KiB,将会在每个容器之间平均分配。
例如,如果有 12 个容器(initContainers
或 containers
),
每个容器都有 1024 字节的可用终止消息空间。
默认的终止消息路径是 /dev/termination-log
。
Pod 启动后不能设置终止消息路径。
在下例中,容器将终止消息写入 /tmp/my-log
给 Kubernetes 来检索:
apiVersion : v1
kind : Pod
metadata :
name : msg-path-demo
spec :
containers :
- name : msg-path-demo-container
image : debian
terminationMessagePath : "/tmp/my-log"
此外,用户可以设置容器的 terminationMessagePolicy
字段,以便进一步自定义。
此字段默认为 "File
",这意味着仅从终止消息文件中检索终止消息。
通过将 terminationMessagePolicy
设置为 "FallbackToLogsOnError
",你就可以告诉 Kubernetes,在容器因错误退出时,如果终止消息文件为空,则使用容器日志输出的最后一块作为终止消息。
日志输出限制为 2048 字节或 80 行,以较小者为准。
接下来 4.3.2.5 - 调试 Init 容器 此页显示如何核查与 Init 容器执行相关的问题。
下面的示例命令行将 Pod 称为 <pod-name>
,而 Init 容器称为 <init-container-1>
和
<init-container-2>
。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
检查 Init 容器的状态 显示你的 Pod 的状态:
kubectl get pod <pod-name>
例如,状态 Init:1/2
表明两个 Init 容器中的一个已经成功完成:
NAME READY STATUS RESTARTS AGE
<pod-name> 0/1 Init:1/2 0 7s
更多状态值及其含义请参考理解 Pod 的状态 。
获取 Init 容器详情 查看 Init 容器运行的更多详情:
kubectl describe pod <pod-name>
例如,对于包含两个 Init 容器的 Pod 可能显示如下信息:
Init Containers:
<init-container-1>:
Container ID: ...
...
State: Terminated
Reason: Completed
Exit Code: 0
Started: ...
Finished: ...
Ready: True
Restart Count: 0
...
<init-container-2>:
Container ID: ...
...
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
Started: ...
Finished: ...
Ready: False
Restart Count: 3
...
你还可以通过编程方式读取 Pod Spec 上的 status.initContainerStatuses
字段,了解 Init 容器的状态:
kubectl get pod nginx --template '{{.status.initContainerStatuses}}'
此命令将返回与原始 JSON 中相同的信息.
通过 Init 容器访问日志 与 Pod 名称一起传递 Init 容器名称,以访问容器的日志。
kubectl logs <pod-name> -c <init-container-2>
运行 Shell 脚本的 Init 容器在执行 Shell 脚本时输出命令本身。
例如,你可以在 Bash 中通过在脚本的开头运行 set -x
来实现。
理解 Pod 的状态 以 Init:
开头的 Pod 状态汇总了 Init 容器执行的状态。
下表介绍调试 Init 容器时可能看到的一些状态值示例。
状态 含义 Init:N/M
Pod 包含 M
个 Init 容器,其中 N
个已经运行完成。 Init:Error
Init 容器已执行失败。 Init:CrashLoopBackOff
Init 容器执行总是失败。 Pending
Pod 还没有开始执行 Init 容器。 PodInitializing
or Running
Pod 已经完成执行 Init 容器。
4.3.2.6 - 调试运行中的 Pod 本页解释如何在节点上调试运行中(或崩溃)的 Pod。
准备开始 使用 kubectl describe pod
命令获取 Pod 详情 与之前的例子类似,我们使用一个 Deployment 来创建两个 Pod。
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx
resources :
limits :
memory : "128Mi"
cpu : "500m"
ports :
- containerPort : 80
使用如下命令创建 Deployment:
kubectl apply -f https://k8s.io/examples/application/nginx-with-request.yaml
deployment.apps/nginx-deployment created
使用如下命令查看 Pod 状态:
NAME READY STATUS RESTARTS AGE
nginx-deployment-67d4bdd6f5-cx2nz 1/1 Running 0 13s
nginx-deployment-67d4bdd6f5-w6kd7 1/1 Running 0 13s
我们可以使用 kubectl describe pod
命令来查询每个 Pod 的更多信息,比如:
kubectl describe pod nginx-deployment-67d4bdd6f5-w6kd7
Name: nginx-deployment-67d4bdd6f5-w6kd7
Namespace: default
Priority: 0
Node: kube-worker-1/192.168.0.113
Start Time: Thu, 17 Feb 2022 16:51:01 -0500
Labels: app=nginx
pod-template-hash=67d4bdd6f5
Annotations: <none>
Status: Running
IP: 10.88.0.3
IPs:
IP: 10.88.0.3
IP: 2001:db8::1
Controlled By: ReplicaSet/nginx-deployment-67d4bdd6f5
Containers:
nginx:
Container ID: containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
Image: nginx
Image ID: docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Thu, 17 Feb 2022 16:51:05 -0500
Ready: True
Restart Count: 0
Limits:
cpu: 500m
memory: 128Mi
Requests:
cpu: 500m
memory: 128Mi
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bgsgp (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-bgsgp:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: Guaranteed
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 34s default-scheduler Successfully assigned default/nginx-deployment-67d4bdd6f5-w6kd7 to kube-worker-1
Normal Pulling 31s kubelet Pulling image "nginx"
Normal Pulled 30s kubelet Successfully pulled image "nginx" in 1.146417389s
Normal Created 30s kubelet Created container nginx
Normal Started 30s kubelet Started container nginx
在这里,你可以看到有关容器和 Pod 的配置信息(标签、资源需求等),
以及有关容器和 Pod 的状态信息(状态、就绪、重启计数、事件等)。
容器状态是 Waiting、Running 和 Terminated 之一。
根据状态的不同,还有对应的额外的信息 —— 在这里你可以看到,
对于处于运行状态的容器,系统会告诉你容器的启动时间。
Ready 指示是否通过了最后一个就绪态探测。
(在本例中,容器没有配置就绪态探测;如果没有配置就绪态探测,则假定容器已经就绪。)
Restart Count 告诉你容器已重启的次数;
这些信息对于定位配置了 “Always” 重启策略的容器持续崩溃问题非常有用。
目前,唯一与 Pod 有关的状态是 Ready 状况,该状况表明 Pod 能够为请求提供服务,
并且应该添加到相应服务的负载均衡池中。
最后,你还可以看到与 Pod 相关的近期事件。
系统通过指示第一次和最后一次看到事件以及看到该事件的次数来压缩多个相同的事件。
“From” 标明记录事件的组件,
“SubobjectPath” 告诉你引用了哪个对象(例如 Pod 中的容器),
“Reason” 和 “Message” 告诉你发生了什么。
例子: 调试 Pending 状态的 Pod 可以使用事件来调试的一个常见的场景是,你创建 Pod 无法被调度到任何节点。
比如,Pod 请求的资源比较多,没有任何一个节点能够满足,或者它指定了一个标签,没有节点可匹配。
假定我们创建之前的 Deployment 时指定副本数是 5(不再是 2),并且请求 600 毫核(不再是 500),
对于一个 4 个节点的集群,若每个节点只有 1 个 CPU,这时至少有一个 Pod 不能被调度。
(需要注意的是,其他集群插件 Pod,比如 fluentd、skydns 等等会在每个节点上运行,
如果我们需求 1000 毫核,将不会有 Pod 会被调度。)
NAME READY STATUS RESTARTS AGE
nginx-deployment-1006230814-6winp 1/1 Running 0 7m
nginx-deployment-1006230814-fmgu3 1/1 Running 0 7m
nginx-deployment-1370807587-6ekbw 1/1 Running 0 1m
nginx-deployment-1370807587-fg172 0/1 Pending 0 1m
nginx-deployment-1370807587-fz9sd 0/1 Pending 0 1m
为了查找 Pod nginx-deployment-1370807587-fz9sd 没有运行的原因,我们可以使用
kubectl describe pod
命令描述 Pod,查看其事件:
kubectl describe pod nginx-deployment-1370807587-fz9sd
Name: nginx-deployment-1370807587-fz9sd
Namespace: default
Node: /
Labels: app=nginx,pod-template-hash=1370807587
Status: Pending
IP:
Controllers: ReplicaSet/nginx-deployment-1370807587
Containers:
nginx:
Image: nginx
Port: 80/TCP
QoS Tier:
memory: Guaranteed
cpu: Guaranteed
Limits:
cpu: 1
memory: 128Mi
Requests:
cpu: 1
memory: 128Mi
Environment Variables:
Volumes:
default-token-4bcbi:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-4bcbi
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 48s 7 {default-scheduler } Warning FailedScheduling pod (nginx-deployment-1370807587-fz9sd) failed to fit in any node
fit failure on node (kubernetes-node-6ta5): Node didn't have enough resource: CPU, requested: 1000, used: 1420, capacity: 2000
fit failure on node (kubernetes-node-wul5): Node didn't have enough resource: CPU, requested: 1000, used: 1100, capacity: 2000
这里你可以看到由调度器记录的事件,它表明了 Pod 不能被调度的原因是 FailedScheduling
(也可能是其他值)。
其 message 部分表明没有任何节点拥有足够多的资源。
要纠正这种情况,可以使用 kubectl scale
更新 Deployment,以指定 4 个或更少的副本。
(或者你可以让 Pod 继续保持这个状态,这是无害的。)
你在 kubectl describe pod
结尾处看到的事件都保存在 etcd 中,
并提供关于集群中正在发生的事情的高级信息。
如果需要列出所有事件,可使用命令:
但是,需要注意的是,事件是区分名字空间的。
如果你对某些名字空间域的对象(比如 my-namespace
名字下的 Pod)的事件感兴趣,
你需要显式地在命令行中指定名字空间:
kubectl get events --namespace= my-namespace
查看所有 namespace 的事件,可使用 --all-namespaces
参数。
除了 kubectl describe pod
以外,另一种获取 Pod 额外信息(除了 kubectl get pod
)的方法
是给 kubectl get pod
增加 -o yaml
输出格式参数。
该命令将以 YAML 格式为你提供比 kubectl describe pod
更多的信息 —— 实际上是系统拥有的关于 Pod 的所有信息。
在这里,你将看到注解(没有标签限制的键值元数据,由 Kubernetes 系统组件在内部使用)、
重启策略、端口和卷等。
kubectl get pod nginx-deployment-1006230814-6winp -o yaml
apiVersion : v1
kind : Pod
metadata :
creationTimestamp : "2022-02-17T21:51:01Z"
generateName : nginx-deployment-67d4bdd6f5-
labels :
app : nginx
pod-template-hash : 67d4bdd6f5
name : nginx-deployment-67d4bdd6f5-w6kd7
namespace : default
ownerReferences :
- apiVersion : apps/v1
blockOwnerDeletion : true
controller : true
kind : ReplicaSet
name : nginx-deployment-67d4bdd6f5
uid : 7d41dfd4-84c0-4be4-88ab-cedbe626ad82
resourceVersion : "1364"
uid : a6501da1-0447-4262-98eb-c03d4002222e
spec :
containers :
- image : nginx
imagePullPolicy : Always
name : nginx
ports :
- containerPort : 80
protocol : TCP
resources :
limits :
cpu : 500m
memory : 128Mi
requests :
cpu : 500m
memory : 128Mi
terminationMessagePath : /dev/termination-log
terminationMessagePolicy : File
volumeMounts :
- mountPath : /var/run/secrets/kubernetes.io/serviceaccount
name : kube-api-access-bgsgp
readOnly : true
dnsPolicy : ClusterFirst
enableServiceLinks : true
nodeName : kube-worker-1
preemptionPolicy : PreemptLowerPriority
priority : 0
restartPolicy : Always
schedulerName : default-scheduler
securityContext : {}
serviceAccount : default
serviceAccountName : default
terminationGracePeriodSeconds : 30
tolerations :
- effect : NoExecute
key : node.kubernetes.io/not-ready
operator : Exists
tolerationSeconds : 300
- effect : NoExecute
key : node.kubernetes.io/unreachable
operator : Exists
tolerationSeconds : 300
volumes :
- name : kube-api-access-bgsgp
projected :
defaultMode : 420
sources :
- serviceAccountToken :
expirationSeconds : 3607
path : token
- configMap :
items :
- key : ca.crt
path : ca.crt
name : kube-root-ca.crt
- downwardAPI :
items :
- fieldRef :
apiVersion : v1
fieldPath : metadata.namespace
path : namespace
status :
conditions :
- lastProbeTime : null
lastTransitionTime : "2022-02-17T21:51:01Z"
status : "True"
type : Initialized
- lastProbeTime : null
lastTransitionTime : "2022-02-17T21:51:06Z"
status : "True"
type : Ready
- lastProbeTime : null
lastTransitionTime : "2022-02-17T21:51:06Z"
status : "True"
type : ContainersReady
- lastProbeTime : null
lastTransitionTime : "2022-02-17T21:51:01Z"
status : "True"
type : PodScheduled
containerStatuses :
- containerID : containerd://5403af59a2b46ee5a23fb0ae4b1e077f7ca5c5fb7af16e1ab21c00e0e616462a
image : docker.io/library/nginx:latest
imageID : docker.io/library/nginx@sha256:2834dc507516af02784808c5f48b7cbe38b8ed5d0f4837f16e78d00deb7e7767
lastState : {}
name : nginx
ready : true
restartCount : 0
started : true
state :
running :
startedAt : "2022-02-17T21:51:05Z"
hostIP : 192.168.0.113
phase : Running
podIP : 10.88.0.3
podIPs :
- ip : 10.88.0.3
- ip : 2001 :db8::1
qosClass : Guaranteed
startTime : "2022-02-17T21:51:01Z"
检查 Pod 的日志 首先,查看受到影响的容器的日志:
kubectl logs ${ POD_NAME } ${ CONTAINER_NAME }
如果你的容器之前崩溃过,你可以通过下面命令访问之前容器的崩溃日志:
kubectl logs --previous ${ POD_NAME } ${ CONTAINER_NAME }
使用容器 exec 进行调试 如果 容器镜像 包含调试程序,
比如从 Linux 和 Windows 操作系统基础镜像构建的镜像,你可以使用 kubectl exec
命令
在特定的容器中运行一些命令:
kubectl exec ${ POD_NAME } -c ${ CONTAINER_NAME } -- ${ CMD } ${ ARG1 } ${ ARG2 } ... ${ ARGN }
说明: -c ${CONTAINER_NAME}
是可选择的。如果 Pod 中仅包含一个容器,就可以忽略它。
例如,要查看正在运行的 Cassandra Pod 中的日志,可以运行:
kubectl exec cassandra -- cat /var/log/cassandra/system.log
你可以在 kubectl exec
命令后面加上 -i
和 -t
来运行一个连接到你的终端的 Shell,比如:
kubectl exec -it cassandra -- sh
若要了解更多内容,可查看获取正在运行容器的 Shell 。
使用临时调试容器来进行调试 特性状态: Kubernetes v1.25 [stable]
当由于容器崩溃或容器镜像不包含调试程序(例如无发行版镜像 等)
而导致 kubectl exec
无法运行时,临时容器 对于排除交互式故障很有用。
使用临时容器来调试的例子 你可以使用 kubectl debug
命令来给正在运行中的 Pod 增加一个临时容器。
首先,像示例一样创建一个 pod:
kubectl run ephemeral-demo --image= k8s.gcr.io/pause:3.1 --restart= Never
说明: 本节示例中使用 pause
容器镜像,因为它不包含调试程序,但是这个方法适用于所有容器镜像。
如果你尝试使用 kubectl exec
来创建一个 shell,你将会看到一个错误,因为这个容器镜像中没有 shell。
kubectl exec -it ephemeral-demo -- sh
OCI runtime exec failed: exec failed: container_linux.go:346: starting container process caused "exec: \"sh\": executable file not found in $PATH": unknown
你可以改为使用 kubectl debug
添加调试容器。
如果你指定 -i
或者 --interactive
参数,kubectl
将自动挂接到临时容器的控制台。
kubectl debug -it ephemeral-demo --image= busybox:1.28 --target= ephemeral-demo
Defaulting debug container name to debugger-8xzrl.
If you don't see a command prompt, try pressing enter.
/ #
此命令添加一个新的 busybox 容器并将其挂接到该容器。--target
参数指定另一个容器的进程命名空间。
这个指定进程命名空间的操作是必需的,因为 kubectl run
不能在它创建的 Pod
中启用共享进程命名空间 。
说明: 容器运行时 必须支持
--target
参数。
如果不支持,则临时容器可能不会启动,或者可能使用隔离的进程命名空间启动,
以便
ps
不显示其他容器内的进程。
你可以使用 kubectl describe
查看新创建的临时容器的状态:
kubectl describe pod ephemeral-demo
...
Ephemeral Containers:
debugger-8xzrl:
Container ID: docker://b888f9adfd15bd5739fefaa39e1df4dd3c617b9902082b1cfdc29c4028ffb2eb
Image: busybox
Image ID: docker-pullable://busybox@sha256:1828edd60c5efd34b2bf5dd3282ec0cc04d47b2ff9caa0b6d4f07a21d1c08084
Port: <none>
Host Port: <none>
State: Running
Started: Wed, 12 Feb 2020 14:25:42 +0100
Ready: False
Restart Count: 0
Environment: <none>
Mounts: <none>
...
使用 kubectl delete
来移除已经结束掉的 Pod:
kubectl delete pod ephemeral-demo
通过 Pod 副本调试 有些时候 Pod 的配置参数使得在某些情况下很难执行故障排查。
例如,在容器镜像中不包含 shell 或者你的应用程序在启动时崩溃的情况下,
就不能通过运行 kubectl exec
来排查容器故障。
在这些情况下,你可以使用 kubectl debug
来创建 Pod 的副本,通过更改配置帮助调试。
在添加新的容器时创建 Pod 副本 当应用程序正在运行但其表现不符合预期时,你会希望在 Pod 中添加额外的调试工具,
这时添加新容器是很有用的。
例如,应用的容器镜像是建立在 busybox
的基础上,
但是你需要 busybox
中并不包含的调试工具。
你可以使用 kubectl run
模拟这个场景:
kubectl run myapp --image= busybox:1.28 --restart= Never -- sleep 1d
通过运行以下命令,建立 myapp
的一个名为 myapp-debug
的副本,
新增了一个用于调试的 Ubuntu 容器,
kubectl debug myapp -it --image= ubuntu --share-processes --copy-to= myapp-debug
Defaulting debug container name to debugger-w7xmf.
If you don't see a command prompt, try pressing enter.
root@myapp-debug:/#
说明: 如果你没有使用 --container
指定新的容器名,kubectl debug
会自动生成的。 默认情况下,-i
标志使 kubectl debug
附加到新容器上。
你可以通过指定 --attach=false
来防止这种情况。
如果你的会话断开连接,你可以使用 kubectl attach
重新连接。 --share-processes
允许在此 Pod 中的其他容器中查看该容器的进程。
参阅在 Pod 中的容器之间共享进程命名空间
获取更多信息。不要忘了清理调试 Pod:
kubectl delete pod myapp myapp-debug
在改变 Pod 命令时创建 Pod 副本 有时更改容器的命令很有用,例如添加调试标志或因为应用崩溃。
为了模拟应用崩溃的场景,使用 kubectl run
命令创建一个立即退出的容器:
kubectl run --image=busybox:1.28 myapp -- false
使用 kubectl describe pod myapp
命令,你可以看到容器崩溃了:
Containers:
myapp:
Image: busybox
...
Args:
false
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 1
你可以使用 kubectl debug
命令创建该 Pod 的一个副本,
在该副本中命令改变为交互式 shell:
kubectl debug myapp -it --copy-to=myapp-debug --container=myapp -- sh
If you don't see a command prompt, try pressing enter.
/ #
现在你有了一个可以执行类似检查文件系统路径或者手动运行容器命令的交互式 shell。
说明: 要更改指定容器的命令,你必须用 --container
命令指定容器的名字,
否则 kubectl debug
将建立一个新的容器运行你指定的命令。 默认情况下,标志 -i
使 kubectl debug
附加到容器。
你可通过指定 --attach=false
来防止这种情况。
如果你的断开连接,可以使用 kubectl attach
重新连接。 不要忘了清理调试 Pod:
kubectl delete pod myapp myapp-debug
在更改容器镜像时拷贝 Pod 在某些情况下,你可能想要改动一个行为异常的 Pod,即从其正常的生产容器镜像更改为包含调试构建程序或其他实用程序的镜像。
下面的例子,用 kubectl run
创建一个 Pod:
kubectl run myapp --image=busybox:1.28 --restart=Never -- sleep 1d
现在可以使用 kubectl debug
创建一个拷贝并将其容器镜像更改为 ubuntu
:
kubectl debug myapp --copy-to=myapp-debug --set-image=*=ubuntu
--set-image
与 container_name=image
使用相同的 kubectl set image
语法。
*=ubuntu
表示把所有容器的镜像改为 ubuntu
。
kubectl delete pod myapp myapp-debug
在节点上通过 shell 来进行调试 如果这些方法都不起作用,你可以找到运行 Pod 的节点,然后创建一个 Pod 运行在该节点上。
你可以通过 kubectl debug
在节点上创建一个交互式 Shell:
kubectl debug node/mynode -it --image= ubuntu
Creating debugging pod node-debugger-mynode-pdx84 with container debugger on node mynode.
If you don't see a command prompt, try pressing enter.
root@ek8s:/#
当在节点上创建调试会话,注意以下要点:
kubectl debug
基于节点的名字自动生成新的 Pod 的名字。节点的根文件系统会被挂载在 /host
。 新的调试容器运行在主机 IPC 名字空间、主机网络名字空间以及主机 PID 名字空间内,
Pod 没有特权,因此读取某些进程信息可能会失败,并且 chroot /host
也会失败。 如果你需要一个特权 Pod,需要手动创建。 当你完成节点调试时,不要忘记清理调试 Pod:
kubectl delete pod node-debugger-mynode-pdx84
4.3.2.7 - 获取正在运行容器的 Shell 本文介绍怎样使用 kubectl exec
命令获取正在运行容器的 Shell。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
获取容器的 Shell 在本练习中,你将创建包含一个容器的 Pod。容器运行 nginx 镜像。下面是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : shell-demo
spec :
volumes :
- name : shared-data
emptyDir : {}
containers :
- name : nginx
image : nginx
volumeMounts :
- name : shared-data
mountPath : /usr/share/nginx/html
hostNetwork : true
dnsPolicy : Default
创建 Pod:
kubectl apply -f https://k8s.io/examples/application/shell-demo.yaml
检查容器是否运行正常:
kubectl get pod shell-demo
获取正在运行容器的 Shell:
kubectl exec --stdin --tty shell-demo -- /bin/bash
说明: 双破折号 "--" 用于将要传递给命令的参数与 kubectl 的参数分开。
在 shell 中,打印根目录:
在 shell 中,实验其他命令。下面是一些示例:
# 你可以在容器中运行这些示例命令
ls /
cat /proc/mounts
cat /proc/1/maps
apt-get update
apt-get install -y tcpdump
tcpdump
apt-get install -y lsof
lsof
apt-get install -y procps
ps aux
ps aux | grep nginx
编写 nginx 的根页面 再看一下 Pod 的配置文件。该 Pod 有个 emptyDir
卷,容器将该卷挂载到了 /usr/share/nginx/html
。
在 shell 中,在 /usr/share/nginx/html
目录创建一个 index.html
文件:
# 在容器内运行如下命令
echo 'Hello shell demo' > /usr/share/nginx/html/index.html
在 shell 中,向 nginx 服务器发送 GET 请求:
# 在容器内运行如下命令
apt-get update
apt-get install curl
curl http://localhost/
输出结果显示了你在 index.html
中写入的文本。
当用完 shell 后,输入 exit
退出。
在容器中运行单个命令 在普通的命令窗口(而不是 shell)中,打印环境运行容器中的变量:
kubectl exec shell-demo env
实验运行其他命令。下面是一些示例:
kubectl exec shell-demo -- ps aux
kubectl exec shell-demo -- ls /
kubectl exec shell-demo -- cat /proc/1/mounts
当 Pod 包含多个容器时打开 shell 如果 Pod 有多个容器,--container
或者 -c
可以在 kubectl exec
命令中指定容器。
例如,你有个名为 my-pod 的 Pod,该 Pod 有两个容器分别为 main-app 和 healper-app 。
下面的命令将会打开一个 shell 访问 main-app 容器。
kubectl exec -i -t my-pod --container main-app -- /bin/bash
说明: 短的命令参数 -i
和 -t
与长的命令参数 --stdin
和 --tty
作用相同。
接下来 4.4 - 配置 Pods 和容器 对 Pod 和容器执行常见的配置任务。
4.4.1 - 为容器和 Pod 分配内存资源 此页面展示如何将内存请求 (request)和内存限制 (limit)分配给一个容器。
我们保障容器拥有它请求数量的内存,但不允许使用超过限制数量的内存。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
你集群中的每个节点必须拥有至少 300 MiB 的内存。
该页面上的一些步骤要求你在集群中运行
metrics-server 服务。
如果你已经有在运行中的 metrics-server,则可以跳过这些步骤。
如果你运行的是 Minikube,可以运行下面的命令启用 metrics-server:
minikube addons enable metrics-server
要查看 metrics-server 或资源指标 API (metrics.k8s.io
) 是否已经运行,请运行以下命令:
如果资源指标 API 可用,则输出结果将包含对 metrics.k8s.io
的引用信息。
NAME
v1beta1.metrics.k8s.io
创建命名空间 创建一个命名空间,以便将本练习中创建的资源与集群的其余部分隔离。
kubectl create namespace mem-example
指定内存请求和限制 要为容器指定内存请求,请在容器资源清单中包含 resources:requests
字段。
同理,要指定内存限制,请包含 resources:limits
。
在本练习中,你将创建一个拥有一个容器的 Pod。
容器将会请求 100 MiB 内存,并且内存会被限制在 200 MiB 以内。
这是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : memory-demo
namespace : mem-example
spec :
containers :
- name : memory-demo-ctr
image : polinux/stress
resources :
requests :
memory : "100Mi"
limits :
memory : "200Mi"
command : ["stress" ]
args : ["--vm" , "1" , "--vm-bytes" , "150M" , "--vm-hang" , "1" ]
配置文件的 args
部分提供了容器启动时的参数。
"--vm-bytes", "150M"
参数告知容器尝试分配 150 MiB 内存。
开始创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit.yaml --namespace= mem-example
验证 Pod 中的容器是否已运行:
kubectl get pod memory-demo --namespace= mem-example
查看 Pod 相关的详细信息:
kubectl get pod memory-demo --output= yaml --namespace= mem-example
输出结果显示:该 Pod 中容器的内存请求为 100 MiB,内存限制为 200 MiB。
...
resources :
requests :
memory : 100Mi
limits :
memory : 200Mi
...
运行 kubectl top
命令,获取该 Pod 的指标数据:
kubectl top pod memory-demo --namespace= mem-example
输出结果显示:Pod 正在使用的内存大约为 162,900,000 字节,约为 150 MiB。
这大于 Pod 请求的 100 MiB,但在 Pod 限制的 200 MiB之内。
NAME CPU(cores) MEMORY(bytes)
memory-demo <something> 162856960
删除 Pod:
kubectl delete pod memory-demo --namespace= mem-example
超过容器限制的内存 当节点拥有足够的可用内存时,容器可以使用其请求的内存。
但是,容器不允许使用超过其限制的内存。
如果容器分配的内存超过其限制,该容器会成为被终止的候选容器。
如果容器继续消耗超出其限制的内存,则终止容器。
如果终止的容器可以被重启,则 kubelet 会重新启动它,就像其他任何类型的运行时失败一样。
在本练习中,你将创建一个 Pod,尝试分配超出其限制的内存。
这是一个 Pod 的配置文件,其拥有一个容器,该容器的内存请求为 50 MiB,内存限制为 100 MiB:
apiVersion : v1
kind : Pod
metadata :
name : memory-demo-2
namespace : mem-example
spec :
containers :
- name : memory-demo-2-ctr
image : polinux/stress
resources :
requests :
memory : "50Mi"
limits :
memory : "100Mi"
command : ["stress" ]
args : ["--vm" , "1" , "--vm-bytes" , "250M" , "--vm-hang" , "1" ]
在配置文件的 args
部分中,你可以看到容器会尝试分配 250 MiB 内存,这远高于 100 MiB 的限制。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-2.yaml --namespace= mem-example
查看 Pod 相关的详细信息:
kubectl get pod memory-demo-2 --namespace= mem-example
此时,容器可能正在运行或被杀死。重复前面的命令,直到容器被杀掉:
NAME READY STATUS RESTARTS AGE
memory-demo-2 0/1 OOMKilled 1 24s
获取容器更详细的状态信息:
kubectl get pod memory-demo-2 --output= yaml --namespace= mem-example
输出结果显示:由于内存溢出(OOM),容器已被杀掉:
lastState :
terminated :
containerID : 65183c1877aaec2e8427bc95609cc52677a454b56fcb24340dbd22917c23b10f
exitCode : 137
finishedAt : 2017-06-20T20:52:19Z
reason : OOMKilled
startedAt : null
本练习中的容器可以被重启,所以 kubelet 会重启它。
多次运行下面的命令,可以看到容器在反复的被杀死和重启:
kubectl get pod memory-demo-2 --namespace= mem-example
输出结果显示:容器被杀掉、重启、再杀掉、再重启……:
kubectl get pod memory-demo-2 --namespace=mem-example
NAME READY STATUS RESTARTS AGE
memory-demo-2 0/1 OOMKilled 1 37s
kubectl get pod memory-demo-2 --namespace=mem-example
NAME READY STATUS RESTARTS AGE
memory-demo-2 1/1 Running 2 40s
查看关于该 Pod 历史的详细信息:
kubectl describe pod memory-demo-2 --namespace=mem-example
输出结果显示:该容器反复的在启动和失败:
... Normal Created Created container with id 66a3a20aa7980e61be4922780bf9d24d1a1d8b7395c09861225b0eba1b1f8511
... Warning BackOff Back-off restarting failed container
查看关于集群节点的详细信息:
kubectl describe nodes
输出结果包含了一条练习中的容器由于内存溢出而被杀掉的记录:
Warning OOMKilling Memory cgroup out of memory: Kill process 4481 (stress) score 1994 or sacrifice child
删除 Pod:
kubectl delete pod memory-demo-2 --namespace= mem-example
超过整个节点容量的内存 内存请求和限制是与容器关联的,但将 Pod 视为具有内存请求和限制,也是很有用的。
Pod 的内存请求是 Pod 中所有容器的内存请求之和。
同理,Pod 的内存限制是 Pod 中所有容器的内存限制之和。
Pod 的调度基于请求。只有当节点拥有足够满足 Pod 内存请求的内存时,才会将 Pod 调度至节点上运行。
在本练习中,你将创建一个 Pod,其内存请求超过了你集群中的任意一个节点所拥有的内存。
这是该 Pod 的配置文件,其拥有一个请求 1000 GiB 内存的容器,这应该超过了你集群中任何节点的容量。
apiVersion : v1
kind : Pod
metadata :
name : memory-demo-3
namespace : mem-example
spec :
containers :
- name : memory-demo-3-ctr
image : polinux/stress
resources :
requests :
memory : "1000Gi"
limits :
memory : "1000Gi"
command : ["stress" ]
args : ["--vm" , "1" , "--vm-bytes" , "150M" , "--vm-hang" , "1" ]
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/memory-request-limit-3.yaml --namespace= mem-example
查看 Pod 状态:
kubectl get pod memory-demo-3 --namespace= mem-example
输出结果显示:Pod 处于 PENDING 状态。
这意味着,该 Pod 没有被调度至任何节点上运行,并且它会无限期的保持该状态:
kubectl get pod memory-demo-3 --namespace=mem-example
NAME READY STATUS RESTARTS AGE
memory-demo-3 0/1 Pending 0 25s
查看关于 Pod 的详细信息,包括事件:
kubectl describe pod memory-demo-3 --namespace= mem-example
输出结果显示:由于节点内存不足,该容器无法被调度:
Events:
... Reason Message
------ -------
... FailedScheduling No nodes are available that match all of the following predicates:: Insufficient memory (3).
内存单位 内存资源的基本单位是字节(byte)。你可以使用这些后缀之一,将内存表示为
纯整数或定点整数:E、P、T、G、M、K、Ei、Pi、Ti、Gi、Mi、Ki。
例如,下面是一些近似相同的值:
128974848, 129e6, 129M, 123Mi
删除 Pod:
kubectl delete pod memory-demo-3 --namespace= mem-example
如果你没有指定内存限制 如果你没有为一个容器指定内存限制,则自动遵循以下情况之一:
内存请求和限制的目的 通过为集群中运行的容器配置内存请求和限制,你可以有效利用集群节点上可用的内存资源。
通过将 Pod 的内存请求保持在较低水平,你可以更好地安排 Pod 调度。
通过让内存限制大于内存请求,你可以完成两件事:
Pod 可以进行一些突发活动,从而更好的利用可用内存。 Pod 在突发活动期间,可使用的内存被限制为合理的数量。 清理 删除命名空间。下面的命令会删除你根据这个任务创建的所有 Pod:
kubectl delete namespace mem-example
接下来 应用开发者扩展阅读 集群管理员扩展阅读 4.4.2 - 创建 Windows HostProcess Pod 特性状态: Kubernetes v1.23 [beta]
Windows HostProcess 容器让你能够在 Windows 主机上运行容器化负载。
这类容器以普通的进程形式运行,但能够在具有合适用户特权的情况下,
访问主机网络名字空间、存储和设备。HostProcess 容器可用来在 Windows
节点上部署网络插件、存储配置、设备插件、kube-proxy 以及其他组件,
同时不需要配置专用的代理或者直接安装主机服务。
类似于安装安全补丁、事件日志收集等这类管理性质的任务可以在不需要集群操作员登录到每个
Windows 节点的前提下执行。HostProcess 容器可以以主机上存在的任何用户账户来运行,
也可以以主机所在域中的用户账户运行,这样管理员可以通过用户许可权限来限制资源访问。
尽管文件系统和进程隔离都不支持,在启动容器时会在主机上创建一个新的卷,
为其提供一个干净的、整合的工作空间。HostProcess 容器也可以基于现有的 Windows
基础镜像来制作,并且不再有 Windows 服务器容器所带有的那些
兼容性需求 ,
这意味着基础镜像的版本不必与主机操作系统的版本匹配。
不过,仍然建议你像使用 Windows 服务器容器负载那样,使用相同的基础镜像版本,
这样你就不会有一些未使用的镜像占用节点上的存储空间。HostProcess 容器也支持
在容器卷内执行卷挂载 。
我何时该使用 Windows HostProcess 容器? 当你准备执行需要访问主机上网络名字空间的任务时,HostProcess
容器能够访问主机上的网络接口和 IP 地址。 当你需要访问主机上的资源,如文件系统、事件日志等等。 需要安装特定的设备驱动或者 Windows 服务时。 需要对管理任务和安全策略进行整合时。使用 HostProcess 容器能够缩小 Windows
节点上所需要的特权范围。 准备开始 本任务指南是特定于 Kubernetes v1.25 的。
如果你运行的不是 Kubernetes v1.25,请移步访问正确
版本的 Kubernetes 文档。
在 Kubernetes v1.25 中,HostProcess 容器功能特性默认是启用的。
kubelet 会直接与 containerd 通信,通过 CRI 将主机进程标志传递过去。
你可以使用 containerd 的最新版本(v1.6+)来运行 HostProcess 容器。
参阅如何安装 containerd 。
要 禁用 HostProcess 容器特性,你需要为 kubelet 和 kube-apiserver
设置下面的特性门控标志:
--feature-gates=WindowsHostProcessContainers=false
进一步的细节可参阅特性门控 文档。
限制 以下限制是与 Kubernetes v1.25 相关的:
HostProcess 容器需要 containerd 1.6 或更高版本的
容器运行时 。 HostProcess Pods 只能包含 HostProcess 容器。这是在 Windows 操作系统上的约束;
非特权的 Windows 容器不能与主机 IP 名字空间共享虚拟网卡(vNIC)。 HostProcess 在主机上以一个进程的形式运行,除了通过 HostProcess
用户账号所实施的资源约束外,不提供任何形式的隔离。HostProcess 容器不支持文件系统或
Hyper-V 隔离。 卷挂载是被支持的,并且要花在到容器卷下。参见卷挂载 。 默认情况下有一组主机用户账户可供 HostProcess 容器使用。
参见选择用户账号 。 对资源约束(磁盘、内存、CPU 个数)的支持与主机上进程相同。 不支持 命名管道或者 UNIX 域套接字形式的挂载,需要使用主机上的路径名来访问
(例如,\\.\pipe\*)。HostProcess Pod 配置需求 启用 Windows HostProcess Pod 需要在 Pod 安全配置中设置合适的选项。
在 Pod
安全标准 中所定义的策略中,
HostProcess Pod 默认是不被 basline 和 restricted 策略支持的。因此建议
HostProcess 运行在与 privileged 模式相看齐的策略下。
当运行在 privileged 策略下时,下面是要启用 HostProcess Pod 创建所需要设置的选项:
配置清单示例(片段) spec :
securityContext :
windowsOptions :
hostProcess : true
runAsUserName : "NT AUTHORITY\\Local service"
hostNetwork : true
containers :
- name : test
image : image1:latest
command :
- ping
- -t
- 127.0.0.1
nodeSelector :
"kubernetes.io/os": windows
卷挂载 HostProcess 容器支持在容器卷空间中挂载卷的能力。
在容器内运行的应用能够通过相对或者绝对路径直接访问卷挂载。
环境变量 $CONTAINER_SANDBOX_MOUNT_POINT
在容器创建时被设置为指向容器卷的绝对主机路径。
相对路径是基于 .spec.containers.volumeMounts.mountPath
配置来推导的。
示例 容器内支持通过下面的路径结构来访问服务账好令牌:
.\var\run\secrets\kubernetes.io\serviceaccount\
$CONTAINER_SANDBOX_MOUNT_POINT\var\run\secrets\kubernetes.io\serviceaccount\
资源约束 资源约束(磁盘、内存、CPU 个数)作用到任务之上,并在整个任务上起作用。
例如,如果内存限制设置为 10MB,任何 HostProcess 任务对象所分配的内存不会超过 10MB。
这一行为与其他 Windows 容器类型相同。资源限制的设置方式与编排系统或容器运行时无关。
唯一的区别是用来跟踪资源所进行的磁盘资源用量的计算,出现差异的原因是因为
HostProcess 容器启动引导的方式造成的。
选择用户账号 HostProcess 容器支持以三种被支持的 Windows 服务账号之一来运行:
你应该为每个 HostProcess 容器选择一个合适的 Windows 服务账号,尝试限制特权范围,
避免给主机代理意外的(甚至是恶意的)伤害。LocalSystem 服务账号的特权级
在三者之中最高,只有在绝对需要的时候才应该使用。只要可能,应该使用
LocalService 服务账号,因为该账号在三者中特权最低。
4.4.3 - 为 Windows Pod 和容器配置 GMSA 特性状态: Kubernetes v1.18 [stable]
本页展示如何为将运行在 Windows 节点上的 Pod 和容器配置
组管理的服务账号(Group Managed Service Accounts,GMSA) 。
组管理的服务账号是活动目录(Active Directory)的一种特殊类型,提供自动化的
密码管理、简化的服务主体名称(Service Principal Name,SPN)管理以及跨多个
服务器将管理操作委派给其他管理员等能力。
在 Kubernetes 环境中,GMSA 凭据规约配置为 Kubernetes 集群范围的自定义资源
(Custom Resources)形式。Windows Pod 以及各 Pod 中的每个容器可以配置为
使用 GMSA 来完成基于域(Domain)的操作(例如,Kerberos 身份认证),以便
与其他 Windows 服务相交互。
准备开始 你需要一个 Kubernetes 集群,以及 kubectl
命令行工具,且工具必须已配置
为能够与你的集群通信。集群预期包含 Windows 工作节点。
本节讨论需要为每个集群执行一次的初始操作。
安装 GMSACredentialSpec CRD 你需要在集群上配置一个用于 GMSA 凭据规约资源的
CustomResourceDefinition (CRD),
以便定义类型为 GMSACredentialSpec
的自定义资源。
首先下载 GMSA CRD YAML
并将其保存为 gmsa-crd.yaml
。接下来执行 kubectl apply -f gmsa-crd.yaml
安装 CRD。
安装 Webhook 来验证 GMSA 用户 你需要为 Kubernetes 集群配置两个 Webhook,在 Pod 或容器级别填充和检查
GMSA 凭据规约引用。
一个修改模式(Mutating)的 Webhook,将对 GMSA 的引用(在 Pod 规约中体现为名字)
展开为完整凭据规约的 JSON 形式,并保存回 Pod 规约中。
一个验证模式(Validating)的 Webhook,确保对 GMSA 的所有引用都是已经授权
给 Pod 的服务账号使用的。
安装以上 Webhook 及其相关联的对象需要执行以下步骤:
创建一个证书密钥对(用于允许 Webhook 容器与集群通信)
安装一个包含如上证书的 Secret
创建一个包含核心 Webhook 逻辑的 Deployment
创建引用该 Deployment 的 Validating Webhook 和 Mutating Webhook 配置
你可以使用这个脚本
来部署和配置上述 GMSA Webhook 及相关联的对象。你还可以在运行脚本时设置 --dry-run=server
选项以便审查脚本将会对集群做出的变更。
脚本所使用的 YAML 模板
也可用于手动部署 Webhook 及相关联的对象,不过需要对其中的参数作适当替换。
在活动目录中配置 GMSA 和 Windows 节点 在配置 Kubernetes 中的 Pod 以使用 GMSA 之前,需要按
Windows GMSA 文档
中描述的那样先在活动目录中准备好期望的 GMSA。
Windows 工作节点(作为 Kubernetes 集群的一部分)需要被配置到活动目录中,以便
访问与期望的 GSMA 相关联的秘密凭据数据。这一操作的描述位于
Windows GMSA 文档
中。
创建 GMSA 凭据规约资源 当(如前所述)安装了 GMSACredentialSpec CRD 之后,你就可以配置包含 GMSA 凭据
规约的自定义资源了。GMSA 凭据规约中并不包含秘密或敏感数据。
其中包含的信息主要用于容器运行时,便于后者向 Windows 描述容器所期望的 GMSA。
GMSA 凭据规约可以使用
PowerShell 脚本
以 YAML 格式生成。
下面是手动以 JSON 格式生成 GMSA 凭据规约并对其进行 YAML 转换的步骤:
导入 CredentialSpec 模块 : ipmo CredentialSpec.psm1
使用 New-CredentialSpec
来创建一个 JSON 格式的凭据规约。
要创建名为 WebApp1
的 GMSA 凭据规约,调用
New-CredentialSpec -Name WebApp1 -AccountName WebApp1 -Domain $(Get-ADDomain -Current LocalComputer)
。
使用 Get-CredentialSpec
来显示 JSON 文件的路径。
将凭据规约从 JSON 格式转换为 YAML 格式,并添加必要的头部字段
apiVersion
、kind
、metadata
和 credspec
,使其成为一个可以在
Kubernetes 中配置的 GMSACredentialSpec 自定义资源。
下面的 YAML 配置描述的是一个名为 gmsa-WebApp1
的 GMSA 凭据规约:
apiVersion : windows.k8s.io/v1
kind : GMSACredentialSpec
metadata :
name : gmsa-WebApp1 # 这是随意起的一个名字,将用作引用
credspec :
ActiveDirectoryConfig :
GroupManagedServiceAccounts :
- Name : WebApp1 # GMSA 账号的用户名
Scope : CONTOSO # NETBIOS 域名
- Name : WebApp1 # GMSA 账号的用户名
Scope : contoso.com # DNS 域名
CmsPlugins :
- ActiveDirectory
DomainJoinConfig :
DnsName : contoso.com # DNS 域名
DnsTreeName : contoso.com # DNS 域名根
Guid : 244818ae-87ac-4fcd-92ec-e79e5252348a # GUID
MachineAccountName : WebApp1 # GMSA 账号的用户名
NetBiosName : CONTOSO # NETBIOS 域名
Sid : S-1-5-21-2126449477-2524075714-3094792973 # GMSA 的 SID
上面的凭据规约资源可以保存为 gmsa-Webapp1-credspec.yaml
,之后使用
kubectl apply -f gmsa-Webapp1-credspec.yml
应用到集群上。
配置集群角色以启用对特定 GMSA 凭据规约的 RBAC 你需要为每个 GMSA 凭据规约资源定义集群角色。
该集群角色授权某主体(通常是一个服务账号)对特定的 GMSA 资源执行 use
动作。
下面的示例显示的是一个集群角色,对前文创建的凭据规约 gmsa-WebApp1
执行鉴权。
将此文件保存为 gmsa-webapp1-role.yaml
并执行 kubectl apply -f gmsa-webapp1-role.yaml
。
# 创建集群角色读取凭据规约
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
name : webapp1-role
rules :
- apiGroups : ["windows.k8s.io" ]
resources : ["gmsacredentialspecs" ]
verbs : ["use" ]
resourceNames : ["gmsa-WebApp1" ]
将角色指派给要使用特定 GMSA 凭据规约的服务账号 你需要将某个服务账号(Pod 配置所对应的那个)绑定到前文创建的集群角色上。
这一绑定操作实际上授予该服务账号使用所指定的 GMSA 凭据规约资源的访问权限。
下面显示的是一个绑定到集群角色 webapp1-role
上的 default 服务账号,使之
能够使用前面所创建的 gmsa-WebApp1
凭据规约资源。
apiVersion : rbac.authorization.k8s.io/v1
kind : RoleBinding
metadata :
name : allow-default-svc-account-read-on-gmsa-WebApp1
namespace : default
subjects :
- kind : ServiceAccount
name : default
namespace : default
roleRef :
kind : ClusterRole
name : webapp1-role
apiGroup : rbac.authorization.k8s.io
在 Pod 规约中配置 GMSA 凭据规约引用 Pod 规约字段 securityContext.windowsOptions.gmsaCredentialSpecName
可用来
设置对指定 GMSA 凭据规约自定义资源的引用。
设置此引用将会配置 Pod 中的所有容器使用所给的 GMSA。
下面是一个 Pod 规约示例,其中包含了对 gmsa-WebApp1
凭据规约的引用:
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
run : with-creds
name : with-creds
namespace : default
spec :
replicas : 1
selector :
matchLabels :
run : with-creds
template :
metadata :
labels :
run : with-creds
spec :
securityContext :
windowsOptions :
gmsaCredentialSpecName : gmsa-webapp1
containers :
- image : mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
imagePullPolicy : Always
name : iis
nodeSelector :
kubernetes.io/os : windows
Pod 中的各个容器也可以使用对应容器的 securityContext.windowsOptions.gmsaCredentialSpecName
字段来设置期望使用的 GMSA 凭据规约。
例如:
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
run : with-creds
name : with-creds
namespace : default
spec :
replicas : 1
selector :
matchLabels :
run : with-creds
template :
metadata :
labels :
run : with-creds
spec :
containers :
- image : mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
imagePullPolicy : Always
name : iis
securityContext :
windowsOptions :
gmsaCredentialSpecName : gmsa-Webapp1
nodeSelector :
kubernetes.io/os : windows
当 Pod 规约中填充了 GMSA 相关字段(如上所述),在集群中应用 Pod 规约时会依次
发生以下事件:
Mutating Webhook 解析对 GMSA 凭据规约资源的引用,并将其全部展开,
得到 GMSA 凭据规约的实际内容。
Validating Webhook 确保与 Pod 相关联的服务账号有权在所给的 GMSA 凭据规约
上执行 use
动作。
容器运行时为每个 Windows 容器配置所指定的 GMSA 凭据规约,这样容器就可以以
活动目录中该 GMSA 所代表的身份来执行操作,使用该身份来访问域中的服务。
使用主机名或 FQDN 对网络共享进行身份验证 如果你在使用主机名或 FQDN 从 Pod 连接到 SMB 共享时遇到问题,但能够通过其 IPv4 地址访问共享,
请确保在 Windows 节点上设置了以下注册表项。
reg add "HKLM\SYSTEM\CurrentControlSet\Services\hns\State" /v EnableCompartmentNamespace /t REG_DWORD /d 1
然后需要重新创建正在运行的 Pod 以使行为更改生效。
有关如何使用此注册表项的更多信息,请参见此处 。
故障排查 如果在你的环境中配置 GMSA 时遇到了困难,你可以采取若干步骤来排查可能的故障。
首先,确保 credspec 已传递给 Pod。为此,你需要先运行 exec
进入到你的一个 Pod 中并检查 nltest.exe /parentdomain
命令的输出。
在下面的例子中,Pod 未能正确地获得凭据规约:
kubectl exec -it iis-auth -7776966999-n5nzr powershell.exe
nltest.exe /parentdomain
导致以下错误:
Getting parent domain failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE
如果 Pod 未能正确获得凭据规约,则下一步就要检查与域之间的通信。
首先,从 Pod 内部快速执行一个 nslookup 操作,找到域根。
这一操作会告诉我们三件事情:
Pod 能否访问域控制器(DC) DC 能否访问 Pod DNS 是否正常工作 如果 DNS 和通信测试通过,接下来你需要检查是否 Pod 已经与域之间建立了
安全通信通道。要执行这一检查,你需要再次通过 exec
进入到你的 Pod 中
并执行 nltest.exe /query
命令。
结果输出如下:
I_NetLogonControl failed: Status = 1722 0x6ba RPC_S_SERVER_UNAVAILABLE
这告诉我们,由于某种原因,Pod 无法使用 credspec 中指定的帐户登录到域。
你可以尝试通过运行以下命令来修复安全通道:
nltest /sc_reset: domain.example
如果命令成功,你将看到类似以下内容的输出:
Flags: 30 HAS_IP HAS_TIMESERV
Trusted DC Name \\dc10.domain.example
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully
如果以上命令修复了错误,你可以通过将以下生命周期回调添加到你的 Pod 规约中来自动执行该步骤。
如果这些操作没有修复错误,你将需要再次检查你的 credspec 并确认它是正确和完整的。
image : registry.domain.example/iis-auth:1809v1
lifecycle :
postStart :
exec :
command : ["powershell.exe" ,"-command" ,"do { Restart-Service -Name netlogon } while ( $($Result = (nltest.exe /query); if ($Result -like '*0x0 NERR_Success*') {return $true} else {return $false}) -eq $false)" ]
imagePullPolicy : IfNotPresent
如果你向你的 Pod 规约中添加如上所示的 lifecycle
节,则 Pod 会自动执行所
列举的命令来重启 netlogon
服务,直到 nltest.exe /query
命令返回时没有错误信息。
4.4.4 - 为 Windows 的 Pod 和容器配置 RunAsUserName 特性状态: Kubernetes v1.18 [stable]
本页展示如何为运行为在 Windows 节点上运行的 Pod 和容器配置 RunAsUserName
。
大致相当于 Linux 上的 runAsUser
,允许在容器中以与默认值不同的用户名运行应用。
准备开始 你必须有一个 Kubernetes 集群,并且 kubectl 必须能和集群通信。
集群应该要有 Windows 工作节点,将在其中调度运行 Windows 工作负载的 pod 和容器。
为 Pod 设置 Username 要指定运行 Pod 容器时所使用的用户名,请在 Pod 声明中包含 securityContext
(PodSecurityContext ) 字段,
并在其内部包含 windowsOptions
(WindowsSecurityContextOptions )
字段的 runAsUserName
字段。
你为 Pod 指定的 Windows SecurityContext 选项适用于该 Pod 中(包括 init 容器)的所有容器。
这儿有一个已经设置了 runAsUserName
字段的 Windows Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : run-as-username-pod-demo
spec :
securityContext :
windowsOptions :
runAsUserName : "ContainerUser"
containers :
- name : run-as-username-demo
image : mcr.microsoft.com/windows/servercore:ltsc2019
command : ["ping" , "-t" , "localhost" ]
nodeSelector :
kubernetes.io/os : windows
创建 Pod:
kubectl apply -f https://k8s.io/examples/windows/run-as-username-pod.yaml
验证 Pod 容器是否在运行:
kubectl get pod run-as-username-pod-demo
获取该容器的 shell:
kubectl exec -it run-as-username-pod-demo -- powershell
检查运行 shell 的用户的用户名是否正确:
输出结果应该是这样:
ContainerUser
为容器设置 Username 要指定运行容器时所使用的用户名,请在容器清单中包含 securityContext
(SecurityContext )
字段,并在其内部包含 windowsOptions
(WindowsSecurityContextOptions )
字段的 runAsUserName
字段。
你为容器指定的 Windows SecurityContext 选项仅适用于该容器,并且它会覆盖 Pod 级别设置。
这里有一个 Pod 的配置文件,其中只有一个容器,并且在 Pod 级别和容器级别都设置了 runAsUserName
:
apiVersion : v1
kind : Pod
metadata :
name : run-as-username-container-demo
spec :
securityContext :
windowsOptions :
runAsUserName : "ContainerUser"
containers :
- name : run-as-username-demo
image : mcr.microsoft.com/windows/servercore:ltsc2019
command : ["ping" , "-t" , "localhost" ]
securityContext :
windowsOptions :
runAsUserName : "ContainerAdministrator"
nodeSelector :
kubernetes.io/os : windows
创建 Pod:
kubectl apply -f https://k8s.io/examples/windows/run-as-username-container.yaml
验证 Pod 容器是否在运行:
kubectl get pod run-as-username-container-demo
获取该容器的 shell:
kubectl exec -it run-as-username-container-demo -- powershell
检查运行 shell 的用户的用户名是否正确(应该是容器级别设置的那个):
输出结果应该是这样:
ContainerAdministrator
Windows Username 的局限性 想要使用此功能,在 runAsUserName
字段中设置的值必须是有效的用户名。
它必须是 DOMAIN\USER
这种格式,其中 DOMAIN\
是可选的。
Windows 用户名不区分大小写。此外,关于 DOMAIN
和 USER
还有一些限制:
runAsUserName
字段不能为空,并且不能包含控制字符(ASCII 值:0x00-0x1F
、0x7F
)DOMAIN
必须是 NetBios 名称或 DNS 名称,每种名称都有各自的局限性:NetBios 名称:最多 15 个字符,不能以 .
(点)开头,并且不能包含以下字符:\ / : * ? " < > |
DNS 名称:最多 255 个字符,只能包含字母、数字、点和中划线,并且不能以 .
(点)或 -
(中划线)开头和结尾。 USER
最多不超过 20 个字符,不能 只 包含点或空格,并且不能包含以下字符:" / \ [ ] : ; | = , + * ? < > @
runAsUserName
字段接受的值的一些示例:ContainerAdministrator
、ContainerUser
、
NT AUTHORITY\NETWORK SERVICE
、NT AUTHORITY\LOCAL SERVICE
。
关于这些限制的更多信息,可以查看这里 和这里 。
接下来 4.4.5 - 为容器和 Pods 分配 CPU 资源 本页面展示如何为容器设置 CPU request(请求) 和 CPU limit(限制) 。
容器使用的 CPU 不能超过所配置的限制。
如果系统有空闲的 CPU 时间,则可以保证给容器分配其所请求数量的 CPU 资源。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
集群中的每个节点必须至少有 1 个 CPU 可用才能运行本任务中的示例。
本页的一些步骤要求你在集群中运行
metrics-server
服务。如果你的集群中已经有正在运行的 metrics-server 服务,可以跳过这些步骤。
如果你正在运行 Minikube ,请运行以下命令启用 metrics-server:
minikube addons enable metrics-server
查看 metrics-server(或者其他资源指标 API metrics.k8s.io
服务提供者)是否正在运行,
请键入以下命令:
如果资源指标 API 可用,则会输出将包含一个对 metrics.k8s.io
的引用。
NAME
v1beta1.metrics.k8s.io
创建一个名字空间 创建一个名字空间 ,以便将
本练习中创建的资源与集群的其余部分资源隔离。
kubectl create namespace cpu-example
指定 CPU 请求和 CPU 限制 要为容器指定 CPU 请求,请在容器资源清单中包含 resources: requests
字段。
要指定 CPU 限制,请包含 resources:limits
。
在本练习中,你将创建一个具有一个容器的 Pod。容器将会请求 0.5 个 CPU,而且最多限制使用 1 个 CPU。
这是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : cpu-demo
namespace : cpu-example
spec :
containers :
- name : cpu-demo-ctr
image : vish/stress
resources :
limits :
cpu : "1"
requests :
cpu : "0.5"
args :
- -cpus
- "2"
配置文件的 args
部分提供了容器启动时的参数。
-cpus "2"
参数告诉容器尝试使用 2 个 CPU。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit.yaml --namespace= cpu-example
验证所创建的 Pod 处于 Running 状态
kubectl get pod cpu-demo --namespace= cpu-example
查看显示关于 Pod 的详细信息:
kubectl get pod cpu-demo --output= yaml --namespace= cpu-example
输出显示 Pod 中的一个容器的 CPU 请求为 500 milliCPU,并且 CPU 限制为 1 个 CPU。
resources :
limits :
cpu : "1"
requests :
cpu : 500m
使用 kubectl top
命令来获取该 Pod 的指标:
kubectl top pod cpu-demo --namespace= cpu-example
此示例输出显示 Pod 使用的是 974 milliCPU,即略低于 Pod 配置中指定的 1 个 CPU 的限制。
NAME CPU(cores) MEMORY(bytes)
cpu-demo 974m <something>
回想一下,通过设置 -cpu "2"
,你将容器配置为尝试使用 2 个 CPU,
但是容器只被允许使用大约 1 个 CPU。
容器的 CPU 用量受到限制,因为该容器正尝试使用超出其限制的 CPU 资源。
说明: CPU 使用率低于 1.0 的另一种可能的解释是,节点可能没有足够的 CPU 资源可用。
回想一下,此练习的先决条件需要你的节点至少具有 1 个 CPU 可用。
如果你的容器在只有 1 个 CPU 的节点上运行,则容器无论为容器指定的 CPU 限制如何,
都不能使用超过 1 个 CPU。
CPU 单位 CPU 资源以 CPU 单位度量。Kubernetes 中的一个 CPU 等同于:
1 个 AWS vCPU 1 个 GCP核心 1 个 Azure vCore 裸机上具有超线程能力的英特尔处理器上的 1 个超线程 小数值是可以使用的。一个请求 0.5 CPU 的容器保证会获得请求 1 个 CPU 的容器的 CPU 的一半。
你可以使用后缀 m
表示毫。例如 100m
CPU、100 milliCPU 和 0.1 CPU 都相同。
精度不能超过 1m。
CPU 请求只能使用绝对数量,而不是相对数量。0.1 在单核、双核或 48 核计算机上的 CPU 数量值是一样的。
删除 Pod:
kubectl delete pod cpu-demo --namespace= cpu-example
设置超过节点能力的 CPU 请求 CPU 请求和限制与都与容器相关,但是我们可以考虑一下 Pod 具有对应的 CPU 请求和限制这样的场景。
Pod 对 CPU 用量的请求等于 Pod 中所有容器的请求数量之和。
同样,Pod 的 CPU 资源限制等于 Pod 中所有容器 CPU 资源限制数之和。
Pod 调度是基于资源请求值来进行的。
仅在某节点具有足够的 CPU 资源来满足 Pod CPU 请求时,Pod 将会在对应节点上运行:
在本练习中,你将创建一个 Pod,该 Pod 的 CPU 请求对于集群中任何节点的容量而言都会过大。
下面是 Pod 的配置文件,其中有一个容器。容器请求 100 个 CPU,这可能会超出集群中任何节点的容量。
apiVersion : v1
kind : Pod
metadata :
name : cpu-demo-2
namespace : cpu-example
spec :
containers :
- name : cpu-demo-ctr-2
image : vish/stress
resources :
limits :
cpu : "100"
requests :
cpu : "100"
args :
- -cpus
- "2"
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/cpu-request-limit-2.yaml --namespace= cpu-example
查看该 Pod 的状态:
kubectl get pod cpu-demo-2 --namespace= cpu-example
输出显示 Pod 状态为 Pending。也就是说,Pod 未被调度到任何节点上运行,
并且 Pod 将无限期地处于 Pending 状态:
NAME READY STATUS RESTARTS AGE
cpu-demo-2 0/1 Pending 0 7m
查看有关 Pod 的详细信息,包含事件:
kubectl describe pod cpu-demo-2 --namespace= cpu-example
输出显示由于节点上的 CPU 资源不足,无法调度容器:
Events:
Reason Message
------ -------
FailedScheduling No nodes are available that match all of the following predicates:: Insufficient cpu (3).
删除你的 Pod:
kubectl delete pod cpu-demo-2 --namespace= cpu-example
如果不指定 CPU 限制 如果你没有为容器指定 CPU 限制,则会发生以下情况之一:
如果你设置了 CPU 限制但未设置 CPU 请求 如果你为容器指定了 CPU 限制值但未为其设置 CPU 请求,Kubernetes 会自动为其
设置与 CPU 限制相同的 CPU 请求值。类似的,如果容器设置了内存限制值但未设置
内存请求值,Kubernetes 也会为其设置与内存限制值相同的内存请求。
CPU 请求和限制的初衷 通过配置你的集群中运行的容器的 CPU 请求和限制,你可以有效利用集群上可用的 CPU 资源。
通过将 Pod CPU 请求保持在较低水平,可以使 Pod 更有机会被调度。
通过使 CPU 限制大于 CPU 请求,你可以完成两件事:
清理 删除名字空间:
kubectl delete namespace cpu-example
接下来 针对应用开发者 针对集群管理员 {for-cluster-administrators} 4.4.6 - 配置 Pod 的服务质量 本页介绍怎样配置 Pod 让其获得特定的服务质量(QoS)类。Kubernetes 使用 QoS 类来决定 Pod 的调度和驱逐策略。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
QoS 类 Kubernetes 创建 Pod 时就给它指定了下列一种 QoS 类:
Guaranteed Burstable BestEffort 创建命名空间 创建一个命名空间,以便将本练习所创建的资源与集群的其余资源相隔离。
kubectl create namespace qos-example
创建一个 QoS 类为 Guaranteed 的 Pod 对于 QoS 类为 Guaranteed 的 Pod:
Pod 中的每个容器都必须指定内存限制和内存请求。 对于 Pod 中的每个容器,内存限制必须等于内存请求。 Pod 中的每个容器都必须指定 CPU 限制和 CPU 请求。 对于 Pod 中的每个容器,CPU 限制必须等于 CPU 请求。 这些限制同样适用于初始化容器和应用程序容器。
下面是包含一个容器的 Pod 配置文件。
该容器设置了内存请求和内存限制,值都是 200 MiB。
该容器设置了 CPU 请求和 CPU 限制,值都是 700 milliCPU:
apiVersion : v1
kind : Pod
metadata :
name : qos-demo
namespace : qos-example
spec :
containers :
- name : qos-demo-ctr
image : nginx
resources :
limits :
memory : "200Mi"
cpu : "700m"
requests :
memory : "200Mi"
cpu : "700m"
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod.yaml --namespace= qos-example
查看 Pod 详情:
kubectl get pod qos-demo --namespace= qos-example --output= yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Guaranteed。
结果也确认了 Pod 容器设置了与内存限制匹配的内存请求,设置了与 CPU 限制匹配的 CPU 请求。
spec :
containers :
...
resources :
limits :
cpu : 700m
memory : 200Mi
requests :
cpu : 700m
memory : 200Mi
...
status :
qosClass : Guaranteed
说明: 如果容器指定了自己的内存限制,但没有指定内存请求,Kubernetes 会自动为它指定与内存限制匹配的内存请求。
同样,如果容器指定了自己的 CPU 限制,但没有指定 CPU 请求,Kubernetes 会自动为它指定与 CPU 限制匹配的 CPU 请求。
删除 Pod:
kubectl delete pod qos-demo --namespace= qos-example
创建一个 QoS 类为 Burstable 的 Pod 如果满足下面条件,将会指定 Pod 的 QoS 类为 Burstable:
Pod 不符合 Guaranteed QoS 类的标准。 Pod 中至少一个容器具有内存或 CPU 的请求或限制。 下面是包含一个容器的 Pod 配置文件。
该容器设置了内存限制 200 MiB 和内存请求 100 MiB。
apiVersion : v1
kind : Pod
metadata :
name : qos-demo-2
namespace : qos-example
spec :
containers :
- name : qos-demo-2-ctr
image : nginx
resources :
limits :
memory : "200Mi"
requests :
memory : "100Mi"
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-2.yaml --namespace= qos-example
查看 Pod 详情:
kubectl get pod qos-demo-2 --namespace= qos-example --output= yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Burstable。
spec :
containers :
- image : nginx
imagePullPolicy : Always
name : qos-demo-2-ctr
resources :
limits :
memory : 200Mi
requests :
memory : 100Mi
...
status :
qosClass : Burstable
删除 Pod:
kubectl delete pod qos-demo-2 --namespace= qos-example
创建一个 QoS 类为 BestEffort 的 Pod 对于 QoS 类为 BestEffort 的 Pod,Pod 中的容器必须没有设置内存和 CPU 限制或请求。
下面是包含一个容器的 Pod 配置文件。
该容器没有设置内存和 CPU 限制或请求。
apiVersion : v1
kind : Pod
metadata :
name : qos-demo-3
namespace : qos-example
spec :
containers :
- name : qos-demo-3-ctr
image : nginx
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-3.yaml --namespace= qos-example
查看 Pod 详情:
kubectl get pod qos-demo-3 --namespace= qos-example --output= yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 BestEffort。
spec :
containers :
...
resources : {}
...
status :
qosClass : BestEffort
删除 Pod:
kubectl delete pod qos-demo-3 --namespace= qos-example
创建包含两个容器的 Pod 下面是包含两个容器的 Pod 配置文件。
一个容器指定了内存请求 200 MiB。
另外一个容器没有指定任何请求和限制。
apiVersion : v1
kind : Pod
metadata :
name : qos-demo-4
namespace : qos-example
spec :
containers :
- name : qos-demo-4-ctr-1
image : nginx
resources :
requests :
memory : "200Mi"
- name : qos-demo-4-ctr-2
image : redis
注意此 Pod 满足 Burstable QoS 类的标准。
也就是说它不满足 Guaranteed QoS 类标准,因为它的一个容器设有内存请求。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/qos/qos-pod-4.yaml --namespace= qos-example
查看 Pod 详情:
kubectl get pod qos-demo-4 --namespace= qos-example --output= yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Burstable:
spec :
containers :
...
name : qos-demo-4-ctr-1
resources :
requests :
memory : 200Mi
...
name : qos-demo-4-ctr-2
resources : {}
...
status :
qosClass : Burstable
删除 Pod:
kubectl delete pod qos-demo-4 --namespace= qos-example
环境清理 删除命名空间:
kubectl delete namespace qos-example
接下来 应用开发者参考 集群管理员参考 4.4.7 - 为容器分派扩展资源 特性状态: Kubernetes v1.25 [stable]
本文介绍如何为容器指定扩展资源。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
在你开始此练习前,请先练习
为节点广播扩展资源 。
在那个练习中将配置你的一个节点来广播 dongle 资源。
给 Pod 分派扩展资源 要请求扩展资源,需要在你的容器清单中包括 resources:requests
字段。
扩展资源可以使用任何完全限定名称,只是不能使用 *.kubernetes.io/
。
有效的扩展资源名的格式为 example.com/foo
,其中 example.com
应被替换为
你的组织的域名,而 foo
则是描述性的资源名称。
下面是包含一个容器的 Pod 配置文件:
apiVersion : v1
kind : Pod
metadata :
name : extended-resource-demo
spec :
containers :
- name : extended-resource-demo-ctr
image : nginx
resources :
requests :
example.com/dongle : 3
limits :
example.com/dongle : 3
在配置文件中,你可以看到容器请求了 3 个 dongles。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/extended-resource-pod.yaml
检查 Pod 是否运行正常:
kubectl get pod extended-resource-demo
描述 Pod:
kubectl describe pod extended-resource-demo
输出结果显示 dongle 请求如下:
Limits :
example.com/dongle : 3
Requests :
example.com/dongle : 3
尝试创建第二个 Pod 下面是包含一个容器的 Pod 配置文件,容器请求了 2 个 dongles。
apiVersion : v1
kind : Pod
metadata :
name : extended-resource-demo-2
spec :
containers :
- name : extended-resource-demo-2-ctr
image : nginx
resources :
requests :
example.com/dongle : 2
limits :
example.com/dongle : 2
Kubernetes 将不能满足 2 个 dongles 的请求,因为第一个 Pod 已经使用了 4 个可用 dongles 中的 3 个。
尝试创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/resource/extended-resource-pod-2.yaml
描述 Pod:
kubectl describe pod extended-resource-demo-2
输出结果表明 Pod 不能被调度,因为没有一个节点上存在两个可用的 dongles。
Conditions:
Type Status
PodScheduled False
...
Events:
...
... Warning FailedScheduling pod (extended-resource-demo-2) failed to fit in any node
fit failure summary on nodes : Insufficient example.com/dongle (1)
查看 Pod 的状态:
kubectl get pod extended-resource-demo-2
输出结果表明 Pod 虽然被创建了,但没有被调度到节点上正常运行。Pod 的状态为 Pending:
NAME READY STATUS RESTARTS AGE
extended-resource-demo-2 0/1 Pending 0 6m
清理 删除本练习中创建的 Pod:
kubectl delete pod extended-resource-demo
kubectl delete pod extended-resource-demo-2
接下来 应用开发者参考 集群管理员参考 4.4.8 - 配置 Pod 以使用卷进行存储 此页面展示了如何配置 Pod 以使用卷进行存储。
只要容器存在,容器的文件系统就会存在,因此当一个容器终止并重新启动,对该容器的文件系统改动将丢失。
对于独立于容器的持久化存储,你可以使用卷 。
这对于有状态应用程序尤为重要,例如键值存储(如 Redis)和数据库。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
在本练习中,你将创建一个运行 Pod,该 Pod 仅运行一个容器并拥有一个类型为
emptyDir 的卷,
在整个 Pod 生命周期中一直存在,即使 Pod 中的容器被终止和重启。以下是 Pod 的配置:
apiVersion : v1
kind : Pod
metadata :
name : redis
spec :
containers :
- name : redis
image : redis
volumeMounts :
- name : redis-storage
mountPath : /data/redis
volumes :
- name : redis-storage
emptyDir : {}
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/storage/redis.yaml
验证 Pod 中的容器是否正在运行,然后留意 Pod 的更改:
kubectl get pod redis --watch
输出如下:
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 13s
在另一个终端,用 shell 连接正在运行的容器:
kubectl exec -it redis -- /bin/bash
在你的 Shell中,切换到 /data/redis
目录下,然后创建一个文件:
root@redis:/data# cd /data/redis/
root@redis:/data/redis# echo Hello > test-file
在你的 Shell 中,列出正在运行的进程:
root@redis:/data/redis# apt-get update
root@redis:/data/redis# apt-get install procps
root@redis:/data/redis# ps aux
输出类似于:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
redis 1 0.1 0.1 33308 3828 ? Ssl 00:46 0:00 redis-server *:6379
root 12 0.0 0.0 20228 3020 ? Ss 00:47 0:00 /bin/bash
root 15 0.0 0.0 17500 2072 ? R+ 00:48 0:00 ps aux
在你的 Shell 中,结束 Redis 进程:
root@redis:/data/redis# kill <pid>
其中 <pid>
是 Redis 进程的 ID (PID)。
在你原先终端中,留意 Redis Pod 的更改。最终你将会看到和下面类似的输出:
NAME READY STATUS RESTARTS AGE
redis 1/1 Running 0 13s
redis 0/1 Completed 0 6m
redis 1/1 Running 1 6m
此时,容器已经终止并重新启动。这是因为 Redis Pod 的
restartPolicy
为 Always
。
用 Shell 进入重新启动的容器中:
kubectl exec -it redis -- /bin/bash
在你的 Shell 中,进入到 /data/redis
目录下,并确认 test-file
文件是否仍然存在。
root@redis:/data/redis# cd /data/redis/
root@redis:/data/redis# ls
test-file
删除为此练习所创建的 Pod:
接下来 参阅 Volume 。 参阅 Pod 。 除了 emptyDir
提供的本地磁盘存储外,Kubernetes 还支持许多不同的网络附加存储解决方案,
包括 GCE 上的 PD 和 EC2 上的 EBS,它们是关键数据的首选,并将处理节点上的一些细节,
例如安装和卸载设备。了解更多详情请参阅卷 。 4.4.9 - 配置 Pod 以使用 PersistentVolume 作为存储 本文将向你介绍如何配置 Pod 使用
PersistentVolumeClaim
作为存储。
以下是该过程的总结:
你作为集群管理员创建由物理存储支持的 PersistentVolume。你不会将该卷与任何 Pod 关联。
你现在以开发人员或者集群用户的角色创建一个 PersistentVolumeClaim,
它将自动绑定到合适的 PersistentVolume。
你创建一个使用以上 PersistentVolumeClaim 作为存储的 Pod。
准备开始 在你的节点上创建一个 index.html 文件 打开集群中的某个节点的 Shell。
如何打开 Shell 取决于集群的设置。
例如,如果你正在使用 Minikube,那么可以通过输入 minikube ssh
来打开节点的 Shell。
在该节点的 Shell 中,创建一个 /mnt/data
目录:
# 这里假定你的节点使用 "sudo" 来以超级用户角色执行命令
sudo mkdir /mnt/data
在 /mnt/data
目录中创建一个 index.html 文件:
# 这里再次假定你的节点使用 "sudo" 来以超级用户角色执行命令
sudo sh -c "echo 'Hello from Kubernetes storage' > /mnt/data/index.html"
说明: 如果你的节点使用某工具而不是 sudo
来完成超级用户访问,你可以将上述命令中的 sudo
替换为该工具的名称。
测试 index.html
文件确实存在:
输出应该是:
Hello from Kubernetes storage
现在你可以关闭节点的 Shell 了。
创建 PersistentVolume 在本练习中,你将创建一个 hostPath 类型的 PersistentVolume。
Kubernetes 支持用于在单节点集群上开发和测试的 hostPath 类型的 PersistentVolume。
hostPath 类型的 PersistentVolume 使用节点上的文件或目录来模拟网络附加存储。
在生产集群中,你不会使用 hostPath。
集群管理员会提供网络存储资源,比如 Google Compute Engine 持久盘卷、NFS 共享卷或 Amazon Elastic Block Store 卷。
集群管理员还可以使用
StorageClasses
来设置动态制备存储 。
下面是 hostPath PersistentVolume 的配置文件:
apiVersion : v1
kind : PersistentVolume
metadata :
name : task-pv-volume
labels :
type : local
spec :
storageClassName : manual
capacity :
storage : 10Gi
accessModes :
- ReadWriteOnce
hostPath :
path : "/mnt/data"
此配置文件指定卷位于集群节点上的 /mnt/data
路径。
其配置还指定了卷的容量大小为 10 GB,访问模式为 ReadWriteOnce
,
这意味着该卷可以被单个节点以读写方式安装。
此配置文件还在 PersistentVolume 中定义了
StorageClass 的名称 为 manual
。
它将用于将 PersistentVolumeClaim 的请求绑定到此 PersistentVolume。
创建 PersistentVolume:
kubectl apply -f https://k8s.io/examples/pods/storage/pv-volume.yaml
查看 PersistentVolume 的信息:
kubectl get pv task-pv-volume
输出结果显示该 PersistentVolume 的状态(STATUS)
为 Available
。
这意味着它还没有被绑定给 PersistentVolumeClaim。
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
task-pv-volume 10Gi RWO Retain Available manual 4s
创建 PersistentVolumeClaim 下一步是创建一个 PersistentVolumeClaim。
Pod 使用 PersistentVolumeClaim 来请求物理存储。
在本练习中,你将创建一个 PersistentVolumeClaim,它请求至少 3 GB 容量的卷,
该卷至少可以为一个节点提供读写访问。
下面是 PersistentVolumeClaim 的配置文件:
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : task-pv-claim
spec :
storageClassName : manual
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 3Gi
创建 PersistentVolumeClaim:
kubectl apply -f https://k8s.io/examples/pods/storage/pv-claim.yaml
创建 PersistentVolumeClaim 之后,Kubernetes 控制平面将查找满足申领要求的 PersistentVolume。
如果控制平面找到具有相同 StorageClass 的适当的 PersistentVolume,
则将 PersistentVolumeClaim 绑定到该 PersistentVolume 上。
再次查看 PersistentVolume 信息:
kubectl get pv task-pv-volume
现在输出的 STATUS
为 Bound
。
NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM STORAGECLASS REASON AGE
task-pv-volume 10Gi RWO Retain Bound default/task-pv-claim manual 2m
查看 PersistentVolumeClaim:
kubectl get pvc task-pv-claim
输出结果表明该 PersistentVolumeClaim 绑定了你的 PersistentVolume task-pv-volume
。
NAME STATUS VOLUME CAPACITY ACCESSMODES STORAGECLASS AGE
task-pv-claim Bound task-pv-volume 10Gi RWO manual 30s
创建 Pod 下一步是创建一个使用你的 PersistentVolumeClaim 作为存储卷的 Pod。
下面是此 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : task-pv-pod
spec :
volumes :
- name : task-pv-storage
persistentVolumeClaim :
claimName : task-pv-claim
containers :
- name : task-pv-container
image : nginx
ports :
- containerPort : 80
name : "http-server"
volumeMounts :
- mountPath : "/usr/share/nginx/html"
name : task-pv-storage
注意 Pod 的配置文件指定了 PersistentVolumeClaim,但没有指定 PersistentVolume。
对 Pod 而言,PersistentVolumeClaim 就是一个存储卷。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/storage/pv-pod.yaml
检查 Pod 中的容器是否运行正常:
kubectl get pod task-pv-pod
打开一个 Shell 访问 Pod 中的容器:
kubectl exec -it task-pv-pod -- /bin/bash
在 Shell 中,验证 Nginx 是否正在从 hostPath 卷提供 index.html
文件:
# 一定要在上一步 "kubectl exec" 所返回的 Shell 中执行下面三个命令
apt update
apt install curl
curl http://localhost/
输出结果是你之前写到 hostPath 卷中的 index.html
文件中的内容:
Hello from Kubernetes storage
如果你看到此消息,则证明你已经成功地配置了 Pod 使用 PersistentVolumeClaim
的存储。
清理 删除 Pod、PersistentVolumeClaim 和 PersistentVolume 对象:
kubectl delete pod task-pv-pod
kubectl delete pvc task-pv-claim
kubectl delete pv task-pv-volume
如果你还没有连接到集群中节点的 Shell,可以按之前所做操作,打开一个新的 Shell。
在节点的 Shell 上,删除你所创建的目录和文件:
# 这里假定你使用 "sudo" 来以超级用户的角色执行命令
sudo rm /mnt/data/index.html
sudo rmdir /mnt/data
你现在可以关闭连接到节点的 Shell。
在两个地方挂载相同的 persistentVolume
apiVersion : v1
kind : Pod
metadata :
name : test
spec :
containers :
- name : test
image : nginx
volumeMounts :
# 网站数据挂载
- name : config
mountPath : /usr/share/nginx/html
subPath : html
# Nginx 配置挂载
- name : config
mountPath : /etc/nginx/nginx.conf
subPath : nginx.conf
volumes :
- name : config
persistentVolumeClaim :
claimName : test-nfs-claim
你可以在 nginx 容器上执行两个卷挂载:
/usr/share/nginx/html
用于静态网站
/etc/nginx/nginx.conf
作为默认配置
访问控制 使用组 ID(GID)配置的存储仅允许 Pod 使用相同的 GID 进行写入。
GID 不匹配或缺失将会导致无权访问错误。
为了减少与用户的协调,管理员可以对 PersistentVolume 添加 GID 注解。
这样 GID 就能自动添加到使用 PersistentVolume 的任何 Pod 中。
使用 pv.beta.kubernetes.io/gid
注解的方法如下所示:
apiVersion : v1
kind : PersistentVolume
apiVersion : v1
metadata :
name : pv1
annotations :
pv.beta.kubernetes.io/gid : "1234"
当 Pod 使用带有 GID 注解的 PersistentVolume 时,注解的 GID 会被应用于 Pod 中的所有容器,
应用的方法与 Pod 的安全上下文中指定的 GID 相同。
每个 GID,无论是来自 PersistentVolume 注解还是来自 Pod 规约,都会被应用于每个容器中运行的第一个进程。
说明: 当 Pod 使用 PersistentVolume 时,与 PersistentVolume 关联的 GID 不会在 Pod
资源本身的对象上出现。
接下来 参考 4.4.10 - 配置 Pod 使用投射卷作存储 本文介绍怎样通过projected
卷将现有的多个卷资源挂载到相同的目录。
当前,secret
、configMap
、downwardAPI
和 serviceAccountToken
卷可以被投射。
说明: serviceAccountToken
不是一种卷类型
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
为 Pod 配置投射卷 本练习中,你将使用本地文件来创建用户名和密码 Secret ,
然后创建运行一个容器的 Pod,
该 Pod 使用projected
卷将 Secret 挂载到相同的路径下。
下面是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : test-projected-volume
spec :
containers :
- name : test-projected-volume
image : busybox:1.28
args :
- sleep
- "86400"
volumeMounts :
- name : all-in-one
mountPath : "/projected-volume"
readOnly : true
volumes :
- name : all-in-one
projected :
sources :
- secret :
name : user
- secret :
name : pass
创建 Secret:
# 创建包含用户名和密码的文件:
echo -n "admin" > ./username.txt
echo -n "1f2d1e2e67df" > ./password.txt
# 在 Secret 中引用上述文件
kubectl create secret generic user --from-file= ./username.txt
kubectl create secret generic pass --from-file= ./password.txt
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/storage/projected.yaml
确认 Pod 中的容器运行正常,然后监视 Pod 的变化:
kubectl get --watch pod test-projected-volume
输出结果和下面类似:
NAME READY STATUS RESTARTS AGE
test-projected-volume 1/1 Running 0 14s
在另外一个终端中,打开容器的 shell:
kubectl exec -it test-projected-volume -- /bin/sh
在 shell 中,确认 projected-volume
目录包含你的投射源:
清理 删除 Pod 和 Secret:
kubectl delete pod test-projected-volume
kubectl delete secret user pass
接下来 4.4.11 - 为 Pod 或容器配置安全上下文 安全上下文(Security Context)定义 Pod 或 Container 的特权与访问控制设置。
安全上下文包括但不限于:
AppArmor :使用程序配置来限制个别程序的权能。
Seccomp :过滤进程的系统调用。
allowPrivilegeEscalation
:控制进程是否可以获得超出其父进程的特权。
此布尔值直接控制是否为容器进程设置
no_new_privs
标志。
当容器满足一下条件之一时,allowPrivilegeEscalation
总是为 true:
以特权模式运行,或者 具有 CAP_SYS_ADMIN
权能 readOnlyRootFilesystem:以只读方式加载容器的根文件系统。
以上条目不是安全上下文设置的完整列表 -- 请参阅
SecurityContext
了解其完整列表。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
为 Pod 设置安全性上下文 要为 Pod 设置安全性设置,可在 Pod 规约中包含 securityContext
字段。securityContext
字段值是一个
PodSecurityContext
对象。你为 Pod 所设置的安全性配置会应用到 Pod 中所有 Container 上。
下面是一个 Pod 的配置文件,该 Pod 定义了 securityContext
和一个 emptyDir
卷:
apiVersion : v1
kind : Pod
metadata :
name : security-context-demo
spec :
securityContext :
runAsUser : 1000
runAsGroup : 3000
fsGroup : 2000
volumes :
- name : sec-ctx-vol
emptyDir : {}
containers :
- name : sec-ctx-demo
image : busybox:1.28
command : [ "sh" , "-c" , "sleep 1h" ]
volumeMounts :
- name : sec-ctx-vol
mountPath : /data/demo
securityContext :
allowPrivilegeEscalation : false
在配置文件中,runAsUser
字段指定 Pod 中的所有容器内的进程都使用用户 ID 1000
来运行。runAsGroup
字段指定所有容器中的进程都以主组 ID 3000 来运行。
如果忽略此字段,则容器的主组 ID 将是 root(0)。
当 runAsGroup
被设置时,所有创建的文件也会划归用户 1000 和组 3000。
由于 fsGroup
被设置,容器中所有进程也会是附组 ID 2000 的一部分。
卷 /data/demo
及在该卷中创建的任何文件的属主都会是组 ID 2000。
创建该 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml
检查 Pod 的容器处于运行状态:
kubectl get pod security-context-demo
开启一个 Shell 进入到运行中的容器:
kubectl exec -it security-context-demo -- sh
在你的 Shell 中,列举运行中的进程:
输出显示进程以用户 1000 运行,即 runAsUser
所设置的值:
PID USER TIME COMMAND
1 1000 0:00 sleep 1h
6 1000 0:00 sh
...
在你的 Shell 中,进入 /data
目录列举其内容:
输出显示 /data/demo
目录的组 ID 为 2000,即 fsGroup
的设置值:
drwxrwsrwx 2 root 2000 4096 Jun 6 20:08 demo
在你的 Shell 中,进入到 /data/demo
目录下创建一个文件:
cd demo
echo hello > testfile
列举 /data/demo
目录下的文件:
输出显示 testfile
的组 ID 为 2000,也就是 fsGroup
所设置的值:
-rw-r--r-- 1 1000 2000 6 Jun 6 20:08 testfile
运行下面的命令:
输出类似于:
uid=1000 gid=3000 groups=2000
从输出中你会看到 gid
值为 3000,也就是 runAsGroup
字段的值。
如果 runAsGroup
被忽略,则 gid
会取值 0(root),而进程就能够与 root
用户组所拥有以及要求 root 用户组访问权限的文件交互。
退出你的 Shell:
为 Pod 配置卷访问权限和属主变更策略 特性状态: Kubernetes v1.23 [stable]
默认情况下,Kubernetes 在挂载一个卷时,会递归地更改每个卷中的内容的属主和访问权限,
使之与 Pod 的 securityContext
中指定的 fsGroup
匹配。
对于较大的数据卷,检查和变更属主与访问权限可能会花费很长时间,降低 Pod 启动速度。
你可以在 securityContext
中使用 fsGroupChangePolicy
字段来控制 Kubernetes
检查和管理卷属主和访问权限的方式。
fsGroupChangePolicy - fsGroupChangePolicy
定义在卷被暴露给 Pod 内部之前对其
内容的属主和访问许可进行变更的行为。此字段仅适用于那些支持使用 fsGroup
来
控制属主与访问权限的卷类型。此字段的取值可以是:
OnRootMismatch
:只有根目录的属主与访问权限与卷所期望的权限不一致时,
才改变其中内容的属主和访问权限。这一设置有助于缩短更改卷的属主与访问
权限所需要的时间。Always
:在挂载卷时总是更改卷中内容的属主和访问权限。例如:
securityContext :
runAsUser : 1000
runAsGroup : 3000
fsGroup : 2000
fsGroupChangePolicy : "OnRootMismatch"
将卷权限和所有权更改委派给 CSI 驱动程序 特性状态: Kubernetes v1.23 [beta]
如果你部署了一个容器存储接口 (CSI)
驱动,而该驱动支持 VOLUME_MOUNT_GROUP
NodeServiceCapability
,
在 securityContext
中指定 fsGroup
来设置文件所有权和权限的过程将由 CSI
驱动而不是 Kubernetes 来执行,前提是 Kubernetes 的 DelegateFSGroupToCSIDriver
特性门控已启用。在这种情况下,由于 Kubernetes 不执行任何所有权和权限更改,
fsGroupChangePolicy
不会生效,并且按照 CSI 的规定,CSI 驱动应该使用所指定的
fsGroup
来挂载卷,从而生成了一个对 fsGroup
可读/可写的卷.
更多的信息请参考 KEP
和 CSI 规范
中的字段 VolumeCapability.MountVolume.volume_mount_group
的描述。
为 Container 设置安全性上下文 若要为 Container 设置安全性配置,可以在 Container 清单中包含 securityContext
字段。securityContext
字段的取值是一个
SecurityContext
对象。你为 Container 设置的安全性配置仅适用于该容器本身,并且所指定的设置在与
Pod 层面设置的内容发生重叠时,会重写 Pod 层面的设置。Container 层面的设置不会影响到 Pod 的卷。
下面是一个 Pod 的配置文件,其中包含一个 Container。Pod 和 Container 都有
securityContext
字段:
apiVersion : v1
kind : Pod
metadata :
name : security-context-demo-2
spec :
securityContext :
runAsUser : 1000
containers :
- name : sec-ctx-demo-2
image : gcr.io/google-samples/node-hello:1.0
securityContext :
runAsUser : 2000
allowPrivilegeEscalation : false
创建该 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-2.yaml
验证 Pod 中的容器处于运行状态:
kubectl get pod security-context-demo-2
启动一个 Shell 进入到运行中的容器内:
kubectl exec -it security-context-demo-2 -- sh
在你的 Shell 中,列举运行中的进程:
输出显示进程以用户 2000 运行。该值是在 Container 的 runAsUser
中设置的。
该设置值重写了 Pod 层面所设置的值 1000。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
2000 1 0.0 0.0 4336 764 ? Ss 20:36 0:00 /bin/sh -c node server.js
2000 8 0.1 0.5 772124 22604 ? Sl 20:36 0:00 node server.js
...
退出你的 Shell:
为 Container 设置权能 使用 Linux 权能 ,
你可以赋予进程 root 用户所拥有的某些特权,但不必赋予其全部特权。
要为 Container 添加或移除 Linux 权能,可以在 Container 清单的 securityContext
节包含 capabilities
字段。
首先,看一下不包含 capabilities
字段时候会发生什么。
下面是一个配置文件,其中没有添加或移除容器的权能:
apiVersion : v1
kind : Pod
metadata :
name : security-context-demo-3
spec :
containers :
- name : sec-ctx-3
image : gcr.io/google-samples/node-hello:1.0
创建该 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-3.yaml
验证 Pod 的容器处于运行状态:
kubectl get pod security-context-demo-3
启动一个 Shell 进入到运行中的容器:
kubectl exec -it security-context-demo-3 -- sh
在你的 Shell 中,列举运行中的进程:
输出显示容器中进程 ID(PIDs):
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4336 796 ? Ss 18:17 0:00 /bin/sh -c node server.js
root 5 0.1 0.5 772124 22700 ? Sl 18:17 0:00 node server.js
在你的 Shell 中,查看进程 1 的状态:
输出显示进程的权能位图:
...
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
...
记下进程权能位图,之后退出你的 Shell:
接下来运行一个与前例中容器相同的容器,只是这个容器有一些额外的权能设置。
下面是一个 Pod 的配置,其中运行一个容器。配置为容器添加 CAP_NET_ADMIN
和
CAP_SYS_TIME
权能:
apiVersion : v1
kind : Pod
metadata :
name : security-context-demo-4
spec :
containers :
- name : sec-ctx-4
image : gcr.io/google-samples/node-hello:1.0
securityContext :
capabilities :
add : ["NET_ADMIN" , "SYS_TIME" ]
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-4.yaml
启动一个 Shell,进入到运行中的容器:
kubectl exec -it security-context-demo-4 -- sh
在你的 Shell 中,查看进程 1 的权能:
输出显示的是进程的权能位图:
...
CapPrm: 00000000aa0435fb
CapEff: 00000000aa0435fb
...
比较两个容器的权能位图:
00000000a80425fb
00000000aa0435fb
在第一个容器的权能位图中,位 12 和 25 是没有设置的。在第二个容器中,位 12
和 25 是设置了的。位 12 是 CAP_NET_ADMIN
而位 25 则是 CAP_SYS_TIME
。
参见 capability.h
了解权能常数的定义。
说明: Linux 权能常数定义的形式为 CAP_XXX
。但是你在 container 清单中列举权能时,
要将权能名称中的 CAP_
部分去掉。例如,要添加 CAP_SYS_TIME
,
可在权能列表中添加 SYS_TIME
。
为容器设置 Seccomp 配置 若要为容器设置 Seccomp 配置(Profile),可在你的 Pod 或 Container 清单的
securityContext
节中包含 seccompProfile
字段。该字段是一个
SeccompProfile
对象,包含 type
和 localhostProfile
属性。
type
的合法选项包括 RuntimeDefault
、Unconfined
和 Localhost
。
localhostProfile
只能在 type: Localhost
配置下才可以设置。
该字段标明节点上预先设定的配置的路径,路径是相对于 kubelet 所配置的
Seccomp 配置路径(使用 --root-dir
设置)而言的。
下面是一个例子,设置容器使用节点上容器运行时的默认配置作为 Seccomp 配置:
...
securityContext :
seccompProfile :
type : RuntimeDefault
下面是另一个例子,将 Seccomp 的样板设置为位于
<kubelet-根目录>/seccomp/my-profiles/profile-allow.json
的一个预先配置的文件。
...
securityContext :
seccompProfile :
type : Localhost
localhostProfile : my-profiles/profile-allow.json
为 Container 赋予 SELinux 标签 若要给 Container 设置 SELinux 标签,可以在 Pod 或 Container 清单的
securityContext
节包含 seLinuxOptions
字段。
seLinuxOptions
字段的取值是一个
SELinuxOptions
对象。下面是一个应用 SELinux 标签的例子:
...
securityContext :
seLinuxOptions :
level : "s0:c123,c456"
说明: 要指定 SELinux,需要在宿主操作系统中装载 SELinux 安全性模块。
高效重打 SELinux 卷标签 特性状态: Kubernetes v1.25 [alpha]
默认情况下,容器运行时递归地将 SELinux 标签赋予所有 Pod 卷上的所有文件。
为了加快该过程,Kubernetes 使用挂载可选项 -o context=<label>
可以立即改变卷的 SELinux 标签。
要使用这项加速功能,必须满足下列条件:
必须启用 Alpha 特性门控 ReadWriteOncePod
和 SELinuxMountReadWriteOncePod
。 Pod 必须以 accessModes: ["ReadWriteOncePod"]
模式使用 PersistentVolumeClaim。 Pod(或其中使用 PersistentVolumeClaim 的所有容器)必须设置 seLinuxOptions
。 对应的 PersistentVolume 必须是使用 {< glossary_tooltip text="CSI" term_id="csi" >}}
驱动程序的卷,或者是传统的 iscsi
卷类型的卷。如果使用基于 CSI 驱动程序的卷,CSI 驱动程序必须能够通过在 CSIDriver
实例中设置 spec.seLinuxMount: true
以支持 -o context
挂载。 对于所有其他卷类型,重打 SELinux 标签的方式有所不同:
容器运行时为卷中的所有节点(文件和目录)递归地修改 SELinux 标签。
卷中的文件和目录越多,重打标签需要耗费的时间就越长。
说明: 在 Kubernetes 1.25 中,kubelet 在重启后会丢失对卷标签的追踪记录。
换言之,kubelet 可能会拒绝启动 Pod,原因类似于 “conflicting
SELinux labels of volume”,
但实际上 Pod 中并没有冲突的标签。在重启 kubelet
之前确保节点已被完全腾空 。
讨论 Pod 的安全上下文适用于 Pod 中的容器,也适用于 Pod 所挂载的卷(如果有的话)。
尤其是,fsGroup
和 seLinuxOptions
按下面的方式应用到挂载卷上:
fsGroup
:支持属主管理的卷会被修改,将其属主变更为 fsGroup
所指定的 GID,
并且对该 GID 可写。进一步的细节可参阅
属主变更设计文档 。
seLinuxOptions
:支持 SELinux 标签的卷会被重新打标签,以便可被 seLinuxOptions
下所设置的标签访问。通常你只需要设置 level
部分。
该部分设置的是赋予 Pod 中所有容器及卷的
多类别安全性(Multi-Category Security,MCS) 标签。
警告: 在为 Pod 设置 MCS 标签之后,所有带有相同标签的 Pod 可以访问该卷。
如果你需要跨 Pod 的保护,你必须为每个 Pod 赋予独特的 MCS 标签。
清理 删除之前创建的所有 Pod:
kubectl delete pod security-context-demo
kubectl delete pod security-context-demo-2
kubectl delete pod security-context-demo-3
kubectl delete pod security-context-demo-4
接下来 4.4.12 - 为 Pod 配置服务账号 服务账号为 Pod 中运行的进程提供了一个标识。
说明: 本文是服务账号的用户使用介绍,描述服务账号在集群中如何起作用。
你的集群管理员可能已经对你的集群做了定制,因此导致本文中所讲述的内容并不适用。
当你(自然人)访问集群时(例如,使用 kubectl
),API 服务器将你的身份验证为
特定的用户账号(当前这通常是 admin
,除非你的集群管理员已经定制了你的集群配置)。
Pod 内的容器中的进程也可以与 API 服务器接触。
当它们进行身份验证时,它们被验证为特定的服务账号(例如,default
)。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
使用默认的服务账号访问 API 服务器 当你创建 Pod 时,如果没有指定服务账号,Pod 会被指定给命名空间中的 default
服务账号。
如果你查看 Pod 的原始 JSON 或 YAML(例如:kubectl get pods/<podname> -o yaml
),
你可以看到 spec.serviceAccountName
字段已经被自动设置 了。
你可以使用自动挂载给 Pod 的服务账号凭据访问 API,
访问集群 页面中有相关描述。
服务账号的 API
许可取决于你所使用的鉴权插件和策略 。
你可以通过在 ServiceAccount 上设置 automountServiceAccountToken: false
来实现不给服务账号自动挂载 API 凭据到 /var/run/secrets/kubernetes.io/serviceaccount/token
的目的:
apiVersion : v1
kind : ServiceAccount
metadata :
name : build-robot
automountServiceAccountToken : false
...
在 1.6 以上版本中,你也可以选择不给特定 Pod 自动挂载 API 凭据:
apiVersion : v1
kind : Pod
metadata :
name : my-pod
spec :
serviceAccountName : build-robot
automountServiceAccountToken : false
...
如果 Pod 和服务账号都指定了 automountServiceAccountToken
值,则 Pod 的 spec 优先于服务账号。
使用多个服务账号 每个命名空间都有一个名为 default
的服务账号资源。
你可以用下面的命令查询这个服务账号以及命名空间中的其他 ServiceAccount 资源:
kubectl get serviceaccounts
输出类似于:
NAME SECRETS AGE
default 1 1d
你可以像这样来创建额外的 ServiceAccount 对象:
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
EOF
ServiceAccount 对象的名字必须是一个有效的
DNS 子域名 .
如果你查询服务账号对象的完整信息,如下所示:
kubectl get serviceaccounts/build-robot -o yaml
输出类似于:
apiVersion : v1
kind : ServiceAccount
metadata :
creationTimestamp : 2015-06-16T00:12:59Z
name : build-robot
namespace : default
resourceVersion : "272500"
uid : 721ab723-13bc-11e5-aec2-42010af0021e
你可以使用授权插件来设置服务账号的访问许可 。
要使用非默认的服务账号,将 Pod 的 spec.serviceAccountName
字段设置为你想用的服务账号名称。
Pod 被创建时服务账号必须存在,否则会被拒绝。
你不能更新已经创建好的 Pod 的服务账号。
说明: spec.serviceAccount
字段是 spec.serviceAccountName
的已弃用别名。
如果要从工作负载资源中删除这些字段,请在
Pod 模板 上将这两个字段显式设置为空。
你可以清除服务账号,如下所示:
kubectl delete serviceaccount/build-robot
手动创建服务账号 API 令牌 假设我们有一个上面提到的名为 "build-robot" 的服务账号,现在我们手动创建一个新的 Secret。
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
annotations:
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
EOF
现在,你可以确认新构建的 Secret 中填充了 "build-robot" 服务账号的 API 令牌。
令牌控制器将清理不存在的服务账号的所有令牌。
kubectl describe secrets/build-robot-secret
输出类似于:
Name: build-robot-secret
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: build-robot
kubernetes.io/service-account.uid: da68f9c6-9d26-11e7-b84e-002dc52800da
Type: kubernetes.io/service-account-token
Data
====
ca.crt: 1338 bytes
namespace: 7 bytes
token: ...
为服务账号添加 ImagePullSecrets 创建 ImagePullSecret 将镜像拉取 Secret 添加到服务账号 接着修改命名空间的 default
服务账号,令其使用该 Secret 用作 imagePullSecret
。
kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrykey"}]}'
你也可以使用 kubectl edit
,或者如下所示手动编辑 YAML 清单:
kubectl get serviceaccounts default -o yaml > ./sa.yaml
sa.yaml
文件的输出类似这样:
apiVersion : v1
kind : ServiceAccount
metadata :
creationTimestamp : 2015-08-07T22:02:39Z
name : default
namespace : default
resourceVersion : "243024"
uid : 052fb0f4-3d50-11e5-b066-42010af0d7b6
使用你常用的编辑器(例如 vi
),打开 sa.yaml
文件,删除带有键名
resourceVersion
的行,添加带有 imagePullSecrets:
的行,最后保存文件。
所得到的 sa.yaml
文件类似于:
apiVersion : v1
kind : ServiceAccount
metadata :
creationTimestamp : 2015-08-07T22:02:39Z
name : default
namespace : default
uid : 052fb0f4-3d50-11e5-b066-42010af0d7b6
imagePullSecrets :
- name : myregistrykey
最后,使用新更新的 sa.yaml
文件替换服务账号。
kubectl replace serviceaccount default -f ./sa.yaml
验证镜像拉取 Secret 已经被添加到 Pod 规约 现在,在当前命名空间中创建使用默认服务账号的新 Pod 时,新 Pod
会自动设置其 .spec.imagePullSecrets
字段:
kubectl run nginx --image= nginx --restart= Never
kubectl get pod nginx -o= jsonpath = '{.spec.imagePullSecrets[0].name}{"\n"}'
输出为:
myregistrykey
服务账号令牌卷投射 特性状态: Kubernetes v1.20 [stable]
为了启用令牌请求投射,你必须为 kube-apiserver
设置以下命令行参数:
kubelet 还可以将服务账号令牌投射到 Pod 中。
你可以指定令牌的期望属性,例如受众和有效期限。
这些属性在 default 服务账号令牌上无法配置。
当删除 Pod 或 ServiceAccount 时,服务账号令牌也将对 API 无效。
使用名为 ServiceAccountToken 的
ProjectedVolume 类型在 PodSpec 上配置此功能。
要向 Pod 提供具有 "vault" 用户以及两个小时有效期的令牌,可以在 PodSpec 中配置以下内容:
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
containers :
- image : nginx
name : nginx
volumeMounts :
- mountPath : /var/run/secrets/tokens
name : vault-token
serviceAccountName : build-robot
volumes :
- name : vault-token
projected :
sources :
- serviceAccountToken :
path : vault-token
expirationSeconds : 7200
audience : vault
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/pod-projected-svc-token.yaml
kubelet
组件会替 Pod 请求令牌并将其保存起来,
通过将令牌存储到一个可配置的路径使之在 Pod 内可用,
并在令牌快要到期的时候刷新它。
kubelet
会在令牌存在期达到其 TTL 的 80% 的时候或者令牌生命期超过
24 小时的时候主动轮换它。
应用程序负责在令牌被轮换时重新加载其内容。对于大多数使用场景而言,
周期性地(例如,每隔 5 分钟)重新加载就足够了。
发现服务账号分发者 特性状态: Kubernetes v1.21 [stable]
当启用服务账号令牌投射时启用发现服务账号分发者(Service Account Issuer Discovery)
这一功能特性,如上文所述 。
说明: 分发者的 URL 必须遵从
OIDC 发现规范 。
这意味着 URL 必须使用 https
模式,并且必须在
{service-account-issuer}/.well-known/openid-configuration
路径给出 OpenID 提供者(Provider)配置。
如果 URL 没有遵从这一规范,ServiceAccountIssuerDiscovery
末端就不会被注册,
即使该特性已经被启用。
发现服务账号分发者这一功能使得用户能够用联邦的方式结合使用 Kubernetes
集群(“Identity Provider”,标识提供者)与外部系统(“Relying Parties”,
依赖方)所分发的服务账号令牌。
当此功能被启用时,Kubernetes API 服务器会在 /.well-known/openid-configuration
提供一个 OpenID 提供者配置文档,并在 /openid/v1/jwks
处提供与之关联的
JSON Web Key Set(JWKS)。
这里的 OpenID 提供者配置有时候也被称作“发现文档(Discovery Document)”。
集群包括一个的默认 RBAC ClusterRole, 名为 system:service-account-issuer-discovery
。
默认的 RBAC ClusterRoleBinding 将此角色分配给 system:serviceaccounts
组,
所有服务账号隐式属于该组。这使得集群上运行的 Pod
能够通过它们所挂载的服务账号令牌访问服务账号发现文档。
此外,管理员可以根据其安全性需要以及期望集成的外部系统选择是否将该角色绑定到
system:authenticated
或 system:unauthenticated
。
说明: 对 /.well-known/openid-configuration
和 /openid/v1/jwks
路径请求的响应被设计为与
OIDC 兼容,但不是与其完全一致。
返回的文档仅包含对 Kubernetes 服务账号令牌进行验证所必须的参数。
JWKS 响应包含依赖方可以用来验证 Kubernetes 服务账号令牌的公钥数据。
依赖方先会查询 OpenID 提供者配置,之后使用返回响应中的 jwks_uri
来查找 JWKS。
在很多场合,Kubernetes API 服务器都不会暴露在公网上,不过对于缓存并向外提供 API
服务器响应数据的公开末端而言,用户或者服务提供商可以选择将其暴露在公网上。
在这种环境中,可能会重载 OpenID 提供者配置中的
jwks_uri
,使之指向公网上可用的末端地址,而不是 API 服务器的地址。
这时需要向 API 服务器传递 --service-account-jwks-uri
参数。
与分发者 URL 类似,此 JWKS URI 也需要使用 https
模式。
接下来 另请参见:
4.4.13 - 从私有仓库拉取镜像 本文介绍如何使用 Secret
从私有的镜像仓库或代码仓库拉取镜像来创建 Pod。
有很多私有镜像仓库正在使用中。这个任务使用的镜像仓库是
Docker Hub 。
🛇 本条目指向第三方项目或产品,而该项目(产品)不是 Kubernetes 的一部分。
更多信息 准备开始 要进行此练习,你需要 docker
命令行工具和一个知道密码的
Docker ID 。 如果你要使用不同的私有的镜像仓库,你需要有对应镜像仓库的命令行工具和登录信息。 登录 Docker 镜像仓库 在个人电脑上,要想拉取私有镜像必须在镜像仓库上进行身份验证。
使用 docker
命令工具来登录到 Docker Hub。
更多详细信息,请查阅
Docker ID accounts 中的 log in 部分。
当出现提示时,输入你的 Docker ID 和登录凭证(访问令牌、
或 Docker ID 的密码)。
登录过程会创建或更新保存有授权令牌的 config.json
文件。
查看 Kubernetes 中如何解析这个文件 。
查看 config.json
文件:
cat ~/.docker/config.json
输出结果包含类似于以下内容的部分:
{
"auths" : {
"https://index.docker.io/v1/" : {
"auth" : "c3R...zE2"
}
}
}
说明: 如果使用 Docker 凭证仓库,则不会看到 auth
条目,看到的将是以仓库名称作为值的 credsStore
条目。
在这种情况下,你可以直接创建一个 Secret。请参阅在命令行上提供凭证来创建 Secret 。
创建一个基于现有凭证的 Secret Kubernetes 集群使用 kubernetes.io/dockerconfigjson
类型的
Secret 来通过镜像仓库的身份验证,进而提取私有镜像。
如果你已经运行了 docker login
命令,你可以复制该镜像仓库的凭证到 Kubernetes:
kubectl create secret generic regcred \
--from-file= .dockerconfigjson= <path/to/.docker/config.json> \
--type= kubernetes.io/dockerconfigjson
如果你需要更多的设置(例如,为新 Secret 设置名字空间或标签),
则可以在存储 Secret 之前对它进行自定义。
请务必:
将 data 项中的名称设置为 .dockerconfigjson
使用 base64 编码方法对 Docker 配置文件进行编码,然后粘贴该字符串的内容,作为字段
data[".dockerconfigjson"]
的值 将 type
设置为 kubernetes.io/dockerconfigjson
示例:
apiVersion : v1
kind : Secret
metadata :
name : myregistrykey
namespace : awesomeapps
data :
.dockerconfigjson : UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
type : kubernetes.io/dockerconfigjson
如果你收到错误消息:error: no objects passed to create
,
这可能意味着 base64 编码的字符串是无效的。如果你收到类似
Secret "myregistrykey" is invalid: data[.dockerconfigjson]: invalid value ...
的错误消息,则表示数据中的 base64 编码字符串已成功解码,
但无法解析为 .docker/config.json
文件。
在命令行上提供凭证来创建 Secret 创建 Secret,命名为 regcred
:
kubectl create secret docker-registry regcred \
--docker-server= <你的镜像仓库服务器> \
--docker-username= <你的用户名> \
--docker-password= <你的密码> \
--docker-email= <你的邮箱地址>
在这里:
<your-registry-server>
是你的私有 Docker 仓库全限定域名(FQDN)。
DockerHub 使用 https://index.docker.io/v1/
。<your-name>
是你的 Docker 用户名。<your-pword>
是你的 Docker 密码。<your-email>
是你的 Docker 邮箱。这样你就成功地将集群中的 Docker 凭证设置为名为 regcred
的 Secret。
说明: 在命令行上键入 Secret 可能会将它们存储在你的 shell 历史记录中而不受保护,
并且这些 Secret 信息也可能在 kubectl
运行期间对你 PC 上的其他用户可见。
检查 Secret regcred
要了解你创建的 regcred
Secret 的内容,可以用 YAML 格式进行查看:
kubectl get secret regcred --output= yaml
输出和下面类似:
apiVersion : v1
data :
.dockerconfigjson : eyJodHRwczovL2luZGV4L ... J0QUl6RTIifX0=
kind : Secret
metadata :
...
name : regcred
...
data :
.dockerconfigjson : eyJodHRwczovL2luZGV4L ... J0QUl6RTIifX0=
type : kubernetes.io/dockerconfigjson
.dockerconfigjson
字段的值是 Docker 凭证的 base64 表示。
要了解 dockerconfigjson
字段中的内容,请将 Secret 数据转换为可读格式:
kubectl get secret regcred --output= "jsonpath={.data.\.dockerconfigjson}" | base64 --decode
输出和下面类似:
{"auths" :{"your.private.registry.example.com" :{"username" :"janedoe" ,"password" :"xxxxxxxxxxx" ,"email" :"jdoe@example.com" ,"auth" :"c3R...zE2" }}}
要了解 auth
字段中的内容,请将 base64 编码过的数据转换为可读格式:
echo "c3R...zE2" | base64 --decode
输出结果中,用户名和密码用 :
链接,类似下面这样:
janedoe:xxxxxxxxxxx
注意,Secret 数据包含与本地 ~/.docker/config.json
文件类似的授权令牌。
这样你就已经成功地将 Docker 凭证设置为集群中的名为 regcred
的 Secret。
创建一个使用你的 Secret 的 Pod 下面是一个 Pod 配置清单示例,该示例中 Pod 需要访问你的 Docker 凭证 regcred
:
apiVersion : v1
kind : Pod
metadata :
name : private-reg
spec :
containers :
- name : private-reg-container
image : <your-private-image>
imagePullSecrets :
- name : regcred
将上述文件下载到你的计算机中:
curl -L -o my-private-reg-pod.yaml https://k8s.io/examples/pods/private-reg-pod.yaml
在 my-private-reg-pod.yaml
文件中,使用私有仓库的镜像路径替换 <your-private-image>
,例如:
your.private.registry.example.com/janedoe/jdoe-private:v1
要从私有仓库拉取镜像,Kubernetes 需要凭证。
配置文件中的 imagePullSecrets
字段表明 Kubernetes 应该通过名为 regcred
的 Secret 获取凭证。
创建使用了你的 Secret 的 Pod,并检查它是否正常运行:
kubectl apply -f my-private-reg-pod.yaml
kubectl get pod private-reg
接下来 4.4.14 - 配置存活、就绪和启动探针 这篇文章介绍如何给容器配置存活(Liveness)、就绪(Readiness)和启动(Startup)探针。
kubelet
使用存活探针来确定什么时候要重启容器。
例如,存活探针可以探测到应用死锁(应用程序在运行,但是无法继续执行后面的步骤)情况。
重启这种状态下的容器有助于提高应用的可用性,即使其中存在缺陷。
kubelet 使用就绪探针可以知道容器何时准备好接受请求流量,当一个 Pod
内的所有容器都就绪时,才能认为该 Pod 就绪。
这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。
若 Pod 尚未就绪,会被从 Service 的负载均衡器中剔除。
kubelet 使用启动探针来了解应用容器何时启动。
如果配置了这类探针,你就可以控制容器在启动成功后再进行存活性和就绪态检查,
确保这些存活、就绪探针不会影响应用的启动。
启动探针可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
定义存活命令 许多长时间运行的应用最终会进入损坏状态,除非重新启动,否则无法被恢复。
Kubernetes 提供了存活探针来发现并处理这种情况。
在本练习中,你会创建一个 Pod,其中运行一个基于 registry.k8s.io/busybox
镜像的容器。
下面是这个 Pod 的配置文件。
apiVersion : v1
kind : Pod
metadata :
labels :
test : liveness
name : liveness-exec
spec :
containers :
- name : liveness
image : registry.k8s.io/busybox
args :
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
livenessProbe :
exec :
command :
- cat
- /tmp/healthy
initialDelaySeconds : 5
periodSeconds : 5
在这个配置文件中,可以看到 Pod 中只有一个 Container
。
periodSeconds
字段指定了 kubelet 应该每 5 秒执行一次存活探测。
initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。
kubelet 在容器内执行命令 cat /tmp/healthy
来进行探测。
如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。
如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
当容器启动时,执行如下的命令:
/bin/sh -c "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"
这个容器生命的前 30 秒,/tmp/healthy
文件是存在的。
所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy
会返回成功代码。
30 秒之后,执行命令 cat /tmp/healthy
就会返回失败代码。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/probe/exec-liveness.yaml
在 30 秒内,查看 Pod 的事件:
kubectl describe pod liveness-exec
输出结果表明还没有存活探针失败:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 11s default-scheduler Successfully assigned default/liveness-exec to node01
Normal Pulling 9s kubelet, node01 Pulling image "registry.k8s.io/busybox"
Normal Pulled 7s kubelet, node01 Successfully pulled image "registry.k8s.io/busybox"
Normal Created 7s kubelet, node01 Created container liveness
Normal Started 7s kubelet, node01 Started container liveness
35 秒之后,再来看 Pod 的事件:
kubectl describe pod liveness-exec
在输出结果的最下面,有信息显示存活探针失败了,这个失败的容器被杀死并且被重建了。
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 57s default-scheduler Successfully assigned default/liveness-exec to node01
Normal Pulling 55s kubelet, node01 Pulling image "registry.k8s.io/busybox"
Normal Pulled 53s kubelet, node01 Successfully pulled image "registry.k8s.io/busybox"
Normal Created 53s kubelet, node01 Created container liveness
Normal Started 53s kubelet, node01 Started container liveness
Warning Unhealthy 10s (x3 over 20s) kubelet, node01 Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal Killing 10s kubelet, node01 Container liveness failed liveness probe, will be restarted
再等 30 秒,确认这个容器被重启了:
kubectl get pod liveness-exec
输出结果显示 RESTARTS
的值增加了 1。
请注意,一旦失败的容器恢复为运行状态,RESTARTS
计数器就会增加 1:
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 1m
定义一个存活态 HTTP 请求接口 另外一种类型的存活探测方式是使用 HTTP GET 请求。
下面是一个 Pod 的配置文件,其中运行一个基于 registry.k8s.io/liveness
镜像的容器。
apiVersion : v1
kind : Pod
metadata :
labels :
test : liveness
name : liveness-http
spec :
containers :
- name : liveness
image : registry.k8s.io/liveness
args :
- /server
livenessProbe :
httpGet :
path : /healthz
port : 8080
httpHeaders :
- name : Custom-Header
value : Awesome
initialDelaySeconds : 3
periodSeconds : 3
在这个配置文件中,你可以看到 Pod 也只有一个容器。
periodSeconds
字段指定了 kubelet 每隔 3 秒执行一次存活探测。
initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 3 秒。
kubelet 会向容器内运行的服务(服务在监听 8080 端口)发送一个 HTTP GET 请求来执行探测。
如果服务器上 /healthz
路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。
如果处理程序返回失败代码,则 kubelet 会杀死这个容器并将其重启。
返回大于或等于 200 并且小于 400 的任何代码都标示成功,其它返回代码都标示失败。
你可以访问 server.go 。
阅读服务的源码。
容器存活期间的最开始 10 秒中,/healthz
处理程序返回 200 的状态码。
之后处理程序返回 500 的状态码。
http.HandleFunc ("/healthz" , func (w http.ResponseWriter, r * http.Request) {
duration := time.Now ().Sub (started)
if duration.Seconds () > 10 {
w.WriteHeader (500 )
w.Write ([]byte (fmt.Sprintf ("error: %v" , duration.Seconds ())))
} else {
w.WriteHeader (200 )
w.Write ([]byte ("ok" ))
}
})
kubelet 在容器启动之后 3 秒开始执行健康检测。所以前几次健康检查都是成功的。
但是 10 秒之后,健康检查会失败,并且 kubelet 会杀死容器再重新启动容器。
创建一个 Pod 来测试 HTTP 的存活检测:
kubectl apply -f https://k8s.io/examples/pods/probe/http-liveness.yaml
10 秒之后,通过查看 Pod 事件来确认存活探针已经失败,并且容器被重新启动了。
kubectl describe pod liveness-http
在 1.13 之前(包括 1.13)的版本中,如果在 Pod 运行的节点上设置了环境变量
http_proxy
(或者 HTTP_PROXY
),HTTP 的存活探测会使用这个代理。
在 1.13 之后的版本中,设置本地的 HTTP 代理环境变量不会影响 HTTP 的存活探测。
定义 TCP 的存活探测 第三种类型的存活探测是使用 TCP 套接字。
使用这种配置时,kubelet 会尝试在指定端口和容器建立套接字链接。
如果能建立连接,这个容器就被看作是健康的,如果不能则这个容器就被看作是有问题的。
apiVersion : v1
kind : Pod
metadata :
name : goproxy
labels :
app : goproxy
spec :
containers :
- name : goproxy
image : registry.k8s.io/goproxy:0.1
ports :
- containerPort : 8080
readinessProbe :
tcpSocket :
port : 8080
initialDelaySeconds : 5
periodSeconds : 10
livenessProbe :
tcpSocket :
port : 8080
initialDelaySeconds : 15
periodSeconds : 20
如你所见,TCP 检测的配置和 HTTP 检测非常相似。
下面这个例子同时使用就绪和存活探针。kubelet 会在容器启动 5 秒后发送第一个就绪探针。
探针会尝试连接 goproxy
容器的 8080 端口。
如果探测成功,这个 Pod 会被标记为就绪状态,kubelet 将继续每隔 10 秒运行一次探测。
除了就绪探针,这个配置包括了一个存活探针。
kubelet 会在容器启动 15 秒后进行第一次存活探测。
与就绪探针类似,存活探针会尝试连接 goproxy
容器的 8080 端口。
如果存活探测失败,容器会被重新启动。
kubectl apply -f https://k8s.io/examples/pods/probe/tcp-liveness-readiness.yaml
15 秒之后,通过看 Pod 事件来检测存活探针:
kubectl describe pod goproxy
定义 gRPC 存活探针 特性状态: Kubernetes v1.24 [beta]
如果你的应用实现了
gRPC 健康检查协议 ,
kubelet 可以配置为使用该协议来执行应用存活性检查。
你必须启用 GRPCContainerProbe
特性门控
才能配置依赖于 gRPC 的检查机制。
下面是一个示例清单:
apiVersion : v1
kind : Pod
metadata :
name : etcd-with-grpc
spec :
containers :
- name : etcd
image : registry.k8s.io/etcd:3.5.1-0
command : [ "/usr/local/bin/etcd" , "--data-dir" , "/var/lib/etcd" , "--listen-client-urls" , "http://0.0.0.0:2379" , "--advertise-client-urls" , "http://127.0.0.1:2379" , "--log-level" , "debug" ]
ports :
- containerPort : 2379
livenessProbe :
grpc :
port : 2379
initialDelaySeconds : 10
要使用 gRPC 探针,必须配置 port
属性。如果健康状态端点配置在非默认服务之上,
你还必须设置 service
属性。
说明: 与 HTTP 和 TCP 探针不同,gRPC 探测不能使用命名端口或定制主机。
配置问题(例如:错误的 port
和 service
、未实现健康检查协议)
都被认作是探测失败,这一点与 HTTP 和 TCP 探针类似。
kubectl apply -f https://k8s.io/examples/pods/probe/grpc-liveness.yaml
15 秒钟之后,查看 Pod 事件确认存活性检查并未失败:
kubectl describe pod etcd-with-grpc
在 Kubernetes 1.23 之前,gRPC 健康探测通常使用
grpc-health-probe
来实现,如博客 Health checking gRPC servers on Kubernetes(对 Kubernetes 上的 gRPC 服务器执行健康检查) 所描述。
内置的 gRPC 探针行为与 grpc-health-probe
所实现的行为类似。
从 grpc-health-probe
迁移到内置探针时,请注意以下差异:
内置探针运行时针对的是 Pod 的 IP 地址,不像 grpc-health-probe
那样通常针对 127.0.0.1
执行探测;
请一定配置你的 gRPC 端点使之监听于 Pod 的 IP 地址之上。 内置探针不支持任何身份认证参数(例如 -tls
)。 对于内置的探针而言,不存在错误代码。所有错误都被视作探测失败。 如果 ExecProbeTimeout
特性门控被设置为 false
,则 grpc-health-probe
不会考虑 timeoutSeconds
设置状态(默认值为 1s),
而内置探针则会在超时时返回失败。 使用命名端口 对于 HTTP 和 TCP 存活检测可以使用命名的
port
(gRPC 探针不支持使用命名端口)。
例如:
ports :
- name : liveness-port
containerPort : 8080
hostPort : 8080
livenessProbe :
httpGet :
path : /healthz
port : liveness-port
使用启动探针保护慢启动容器 有时候,会有一些现有的应用在启动时需要较长的初始化时间。
要这种情况下,若要不影响对死锁作出快速响应的探测,设置存活探测参数是要技巧的。
技巧就是使用相同的命令来设置启动探测,针对 HTTP 或 TCP 检测,可以通过将
failureThreshold * periodSeconds
参数设置为足够长的时间来应对糟糕情况下的启动时间。
这样,前面的例子就变成了:
ports :
- name : liveness-port
containerPort : 8080
hostPort : 8080
livenessProbe :
httpGet :
path : /healthz
port : liveness-port
failureThreshold : 1
periodSeconds : 10
startupProbe :
httpGet :
path : /healthz
port : liveness-port
failureThreshold : 30
periodSeconds : 10
幸亏有启动探测,应用程序将会有最多 5 分钟(30 * 10 = 300s)的时间来完成其启动过程。
一旦启动探测成功一次,存活探测任务就会接管对容器的探测,对容器死锁作出快速响应。
如果启动探测一直没有成功,容器会在 300 秒后被杀死,并且根据 restartPolicy
来执行进一步处置。
定义就绪探针 有时候,应用会暂时性地无法为请求提供服务。
例如,应用在启动时可能需要加载大量的数据或配置文件,或是启动后要依赖等待外部服务。
在这种情况下,既不想杀死应用,也不想给它发送请求。
Kubernetes 提供了就绪探针来发现并缓解这些情况。
容器所在 Pod 上报还未就绪的信息,并且不接受通过 Kubernetes Service 的流量。
说明: 就绪探针在容器的整个生命周期中保持运行状态。
注意: 存活探针 不等待 就绪性探针成功。
如果要在执行存活探针之前等待,应该使用 initialDelaySeconds
或 startupProbe
。
就绪探针的配置和存活探针的配置相似。
唯一区别就是要使用 readinessProbe
字段,而不是 livenessProbe
字段。
readinessProbe :
exec :
command :
- cat
- /tmp/healthy
initialDelaySeconds : 5
periodSeconds : 5
HTTP 和 TCP 的就绪探针配置也和存活探针的配置完全相同。
就绪和存活探测可以在同一个容器上并行使用。
两者共同使用,可以确保流量不会发给还未就绪的容器,当这些探测失败时容器会被重新启动。
Probe
有很多配置字段,可以使用这些字段精确地控制启动、存活和就绪检测的行为:
initialDelaySeconds
:容器启动后要等待多少秒后才启动启动、存活和就绪探针,
默认是 0 秒,最小值是 0。periodSeconds
:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。timeoutSeconds
:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。successThreshold
:探针在失败后,被视为成功的最小连续成功数。默认值是 1。
存活和启动探测的这个值必须是 1。最小值是 1。failureThreshold
:当探测失败时,Kubernetes 的重试次数。
对存活探测而言,放弃就意味着重新启动容器。
对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。说明: 在 Kubernetes 1.20 版本之前,exec
探针会忽略 timeoutSeconds
:
探针会无限期地持续运行,甚至可能超过所配置的限期,直到返回结果为止。
这一缺陷在 Kubernetes v1.20 版本中得到修复。你可能一直依赖于之前错误的探测行为,
甚至都没有觉察到这一问题的存在,因为默认的超时值是 1 秒钟。
作为集群管理员,你可以在所有的 kubelet 上禁用 ExecProbeTimeout
特性门控
(将其设置为 false
),从而恢复之前版本中的运行行为。之后当集群中所有的
exec 探针都设置了 timeoutSeconds
参数后,移除此标志重载。
如果你有 Pod 受到此默认 1 秒钟超时值的影响,你应该更新这些 Pod 对应的探针的超时值,
这样才能为最终去除该特性门控做好准备。
当此缺陷被修复之后,在使用 dockershim
容器运行时的 Kubernetes 1.20+
版本中,对于 exec 探针而言,容器中的进程可能会因为超时值的设置保持持续运行,
即使探针返回了失败状态。
注意: 如果就绪态探针的实现不正确,可能会导致容器中进程的数量不断上升。
如果不对其采取措施,很可能导致资源枯竭的状况。
HTTP 探测 HTTP Probes
允许针对 httpGet
配置额外的字段:
host
:连接使用的主机名,默认是 Pod 的 IP。也可以在 HTTP 头中设置 “Host” 来代替。scheme
:用于设置连接主机的方式(HTTP 还是 HTTPS)。默认是 "HTTP"。path
:访问 HTTP 服务的路径。默认值为 "/"。httpHeaders
:请求中自定义的 HTTP 头。HTTP 头字段允许重复。port
:访问容器的端口号或者端口名。如果数字必须在 1~65535 之间。对于 HTTP 探测,kubelet 发送一个 HTTP 请求到指定的路径和端口来执行检测。
除非 httpGet
中的 host
字段设置了,否则 kubelet 默认是给 Pod 的 IP 地址发送探测。
如果 scheme
字段设置为了 HTTPS
,kubelet 会跳过证书验证发送 HTTPS 请求。
大多数情况下,不需要设置 host
字段。
这里有个需要设置 host
字段的场景,假设容器监听 127.0.0.1,并且 Pod 的 hostNetwork
字段设置为了 true
。那么 httpGet
中的 host
字段应该设置为 127.0.0.1。
可能更常见的情况是如果 Pod 依赖虚拟主机,你不应该设置 host
字段,而是应该在
httpHeaders
中设置 Host
。
针对 HTTP 探针,kubelet 除了必需的 Host
头部之外还发送两个请求头部字段:
User-Agent
和 Accept
。这些头部的默认值分别是 kube-probe/{{ skew currentVersion >}}
(其中 1.25
是 kubelet 的版本号)和 */*
。
你可以通过为探测设置 .httpHeaders
来重载默认的头部字段值;例如:
livenessProbe :
httpGet :
httpHeaders :
- name : Accept
value : application/json
startupProbe :
httpGet :
httpHeaders :
- name : User-Agent
value : MyUserAgent
你也可以通过将这些头部字段定义为空值,从请求中去掉这些头部字段。
livenessProbe :
httpGet :
httpHeaders :
- name : Accept
value : ""
startupProbe :
httpGet :
httpHeaders :
- name : User-Agent
value : ""
TCP 探测 对于 TCP 探测而言,kubelet 在节点上(不是在 Pod 里面)发起探测连接,
这意味着你不能在 host
参数上配置服务名称,因为 kubelet 不能解析服务名称。
探针层面的 terminationGracePeriodSeconds
特性状态: Kubernetes v1.25 [beta]
在 1.21 发行版之前,Pod 层面的 terminationGracePeriodSeconds
被用来终止存活探测或启动探测失败的容器。
这一行为上的关联不是我们想要的,可能导致 Pod 层面设置了 terminationGracePeriodSeconds
时容器要花非常长的时间才能重新启动。
在 1.21 及更高版本中,用户可以指定一个探针层面的 terminationGracePeriodSeconds
作为探针规约的一部分。
当 Pod 层面和探针层面的 terminationGracePeriodSeconds
都已设置,kubelet 将使用探针层面设置的值。
说明: 从 Kubernetes 1.25 开始,默认启用 ProbeTerminationGracePeriod
特性。
选择禁用此特性的用户,请注意以下事项:
ProbeTerminationGracePeriod
特性门控只能用在 API 服务器上。
kubelet 始终优先选用探针级别 terminationGracePeriodSeconds
字段
(如果它存在于 Pod 上)。如果你已经为现有 Pod 设置了 terminationGracePeriodSeconds
字段并且不再希望使用针对每个探针的终止宽限期,则必须删除现有的这类 Pod。 当你(或控制平面或某些其他组件)创建替换 Pod,并且特性门控 ProbeTerminationGracePeriod
被禁用时,即使 Pod 或 Pod 模板指定了 terminationGracePeriodSeconds
字段,
API 服务器也会忽略探针级别的 terminationGracePeriodSeconds
字段设置。 例如:
spec :
terminationGracePeriodSeconds : 3600 # Pod 级别设置
containers :
- name : test
image : ...
ports :
- name : liveness-port
containerPort : 8080
hostPort : 8080
livenessProbe :
httpGet :
path : /healthz
port : liveness-port
failureThreshold : 1
periodSeconds : 60
# 重载 Pod 级别的 terminationGracePeriodSeconds
terminationGracePeriodSeconds : 60
探针层面的 terminationGracePeriodSeconds
不能用于就绪态探针。
这一设置将被 API 服务器拒绝。
接下来 你也可以阅读以下的 API 参考资料:
4.4.15 - 将 Pod 分配给节点 此页面显示如何将 Kubernetes Pod 指派给 Kubernetes 集群中的特定节点。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
给节点添加标签 列出你的集群中的节点 ,
包括这些节点上的标签:
输出类似如下:
NAME STATUS AGE VERSION
worker0 Ready 1d v1.6.0+fff5156
worker1 Ready 1d v1.6.0+fff5156
worker2 Ready 1d v1.6.0+fff5156
从你的节点中选择一个,为它添加标签:
kubectl label nodes <your-node-name> disktype = ssd
<your-node-name>
是你选择的节点的名称。
验证你选择的节点确实带有 disktype=ssd
标签:
kubectl get nodes --show-labels
输出类似如下:
NAME STATUS AGE VERSION LABELS
worker0 Ready 1d v1.6.0+fff5156 ...,disktype=ssd,kubernetes.io/hostname=worker0
worker1 Ready 1d v1.6.0+fff5156 ...,kubernetes.io/hostname=worker1
worker2 Ready 1d v1.6.0+fff5156 ...,kubernetes.io/hostname=worker2
在前面的输出中,你可以看到 worker0
节点有 disktype=ssd
标签。
创建一个将被调度到你选择的节点的 Pod 此 Pod 配置文件描述了一个拥有节点选择器 disktype: ssd
的 Pod。这表明该 Pod
将被调度到有 disktype=ssd
标签的节点。
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
env : test
spec :
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
nodeSelector :
disktype : ssd
使用该配置文件创建一个 Pod,该 Pod 将被调度到你选择的节点上:
kubectl create -f https://k8s.io/examples/pods/pod-nginx.yaml
验证 Pod 确实运行在你选择的节点上:
kubectl get pods --output= wide
输出类似如下:
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 13s 10.200.0.4 worker0
创建一个会被调度到特定节点上的 Pod 你也可以通过设置 nodeName
将某个 Pod 调度到特定的节点。
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
nodeName : foo-node # 调度 Pod 到特定的节点
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
使用此配置文件来创建一个 Pod,该 Pod 将只能被调度到 foo-node
节点。
接下来 4.4.16 - 用节点亲和性把 Pods 分配到节点 本页展示在 Kubernetes 集群中,如何使用节点亲和性把 Kubernetes Pod 分配到特定节点。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.10.
要获知版本信息,请输入
kubectl version
.
给节点添加标签 列出集群中的节点及其标签:
kubectl get nodes --show-labels
输出类似于此:
NAME STATUS ROLES AGE VERSION LABELS
worker0 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker0
worker1 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker1
worker2 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker2
选择一个节点,给它添加一个标签: kubectl label nodes <your-node-name> disktype = ssd
其中 <your-node-name>
是你所选节点的名称。
验证你所选节点具有 disktype=ssd
标签:
kubectl get nodes --show-labels
输出类似于此:
NAME STATUS ROLES AGE VERSION LABELS
worker0 Ready <none> 1d v1.13.0 ...,disktype=ssd,kubernetes.io/hostname=worker0
worker1 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker1
worker2 Ready <none> 1d v1.13.0 ...,kubernetes.io/hostname=worker2
在前面的输出中,可以看到 `worker0` 节点有一个 `disktype=ssd` 标签。
依据强制的节点亲和性调度 Pod 下面清单描述了一个 Pod,它有一个节点亲和性配置 requiredDuringSchedulingIgnoredDuringExecution
,disktype=ssd
。
这意味着 pod 只会被调度到具有 disktype=ssd
标签的节点上。
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : disktype
operator : In
values :
- ssd
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
执行(Apply)此清单来创建一个调度到所选节点上的 Pod:
kubectl apply -f https://k8s.io/examples/pods/pod-nginx-required-affinity.yaml
验证 pod 已经在所选节点上运行: kubectl get pods --output= wide
输出类似于此:
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 13s 10.200.0.4 worker0
使用首选的节点亲和性调度 Pod 本清单描述了一个Pod,它有一个节点亲和性设置 preferredDuringSchedulingIgnoredDuringExecution
,disktype: ssd
。
这意味着 pod 将首选具有 disktype=ssd
标签的节点。
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
affinity :
nodeAffinity :
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 1
preference :
matchExpressions :
- key : disktype
operator : In
values :
- ssd
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
执行此清单创建一个会调度到所选节点上的 Pod:
kubectl apply -f https://k8s.io/examples/pods/pod-nginx-preferred-affinity.yaml
验证 pod 是否在所选节点上运行: kubectl get pods --output= wide
输出类似于此:
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 13s 10.200.0.4 worker0
接下来 进一步了解
节点亲和性 .
4.4.17 - 配置 Pod 初始化 本文介绍在应用容器运行前,怎样利用 Init 容器初始化 Pod。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建一个包含 Init 容器的 Pod 本例中你将创建一个包含一个应用容器和一个 Init 容器的 Pod。Init 容器在应用容器启动前运行完成。
下面是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : init-demo
spec :
containers :
- name : nginx
image : nginx
ports :
- containerPort : 80
volumeMounts :
- name : workdir
mountPath : /usr/share/nginx/html
# 这些容器在 Pod 初始化期间运行
initContainers :
- name : install
image : busybox:1.28
command :
- wget
- "-O"
- "/work-dir/index.html"
- http://info.cern.ch
volumeMounts :
- name : workdir
mountPath : "/work-dir"
dnsPolicy : Default
volumes :
- name : workdir
emptyDir : {}
配置文件中,你可以看到应用容器和 Init 容器共享了一个卷。
Init 容器将共享卷挂载到了 /work-dir
目录,应用容器将共享卷挂载到了 /usr/share/nginx/html
目录。
Init 容器执行完下面的命令就终止:
wget -O /work-dir/index.html http://info.cern.ch
请注意 Init 容器在 nginx 服务器的根目录写入 index.html
。
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/init-containers.yaml
检查 nginx 容器运行正常:
kubectl get pod init-demo
结果表明 nginx 容器运行正常:
NAME READY STATUS RESTARTS AGE
init-demo 1/1 Running 0 1m
通过 shell 进入 init-demo Pod 中的 nginx 容器:
kubectl exec -it init-demo -- /bin/bash
在 shell 中,发送个 GET 请求到 nginx 服务器:
root@nginx:~# apt-get update
root@nginx:~# apt-get install curl
root@nginx:~# curl localhost
结果表明 nginx 正在为 Init 容器编写的 web 页面服务:
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
...
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
...
接下来 4.4.18 - 为容器的生命周期事件设置处理函数 这个页面将演示如何为容器的生命周期事件挂接处理函数。Kubernetes 支持 postStart 和 preStop 事件。
当一个容器启动后,Kubernetes 将立即发送 postStart 事件;在容器被终结之前,
Kubernetes 将发送一个 preStop 事件。容器可以为每个事件指定一个处理程序。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
定义 postStart 和 preStop 处理函数 在本练习中,你将创建一个包含一个容器的 Pod,该容器为 postStart 和 preStop 事件提供对应的处理函数。
下面是对应 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : lifecycle-demo
spec :
containers :
- name : lifecycle-demo-container
image : nginx
lifecycle :
postStart :
exec :
command : ["/bin/sh" , "-c" , "echo Hello from the postStart handler > /usr/share/message" ]
preStop :
exec :
command : ["/bin/sh" ,"-c" ,"nginx -s quit; while killall -0 nginx; do sleep 1; done" ]
在上述配置文件中,你可以看到 postStart 命令在容器的 /usr/share
目录下写入文件 message
。
命令 preStop 负责优雅地终止 nginx 服务。当因为失效而导致容器终止时,这一处理方式很有用。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/lifecycle-events.yaml
验证 Pod 中的容器已经运行:
kubectl get pod lifecycle-demo
使用 shell 连接到你的 Pod 里的容器:
kubectl exec -it lifecycle-demo -- /bin/bash
在 shell 中,验证 postStart
处理函数创建了 message
文件:
root@lifecycle-demo:/# cat /usr/share/message
命令行输出的是 postStart
处理函数所写入的文本
Hello from the postStart handler
讨论 Kubernetes 在容器创建后立即发送 postStart 事件。
然而,postStart 处理函数的调用不保证早于容器的入口点(entrypoint)
的执行。postStart 处理函数与容器的代码是异步执行的,但 Kubernetes
的容器管理逻辑会一直阻塞等待 postStart 处理函数执行完毕。
只有 postStart 处理函数执行完毕,容器的状态才会变成
RUNNING。
Kubernetes 在容器结束前立即发送 preStop 事件。除非 Pod 宽限期限超时,Kubernetes 的容器管理逻辑
会一直阻塞等待 preStop 处理函数执行完毕。更多的相关细节,可以参阅
Pods 的结束 。
说明: Kubernetes 只有在 Pod
结束(Terminated) 的时候才会发送 preStop 事件,
这意味着在 Pod
完成(Completed) 时
preStop 的事件处理逻辑不会被触发。这个限制在
issue #55087 中被追踪。
接下来 参考 4.4.19 - 配置 Pod 使用 ConfigMap 很多应用在其初始化或运行期间要依赖一些配置信息。大多数时候,
存在要调整配置参数所设置的数值的需求。
ConfigMap 是 Kubernetes 用来向应用 Pod 中注入配置数据的方法。
ConfigMap 允许你将配置文件与镜像文件分离,以使容器化的应用程序具有可移植性。
本页提供了一系列使用示例,这些示例演示了如何创建 ConfigMap 以及配置 Pod
使用存储在 ConfigMap 中的数据。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建 ConfigMap 你可以使用 kubectl create configmap
或者在 kustomization.yaml
中的 ConfigMap
生成器来创建 ConfigMap。注意,kubectl
从 1.14 版本开始支持 kustomization.yaml
。
使用 kubectl create configmap 创建 ConfigMap 你可以使用 kubectl create configmap
命令基于目录 、
文件 或者字面值 来创建
ConfigMap:
kubectl create configmap <映射名称> <数据源>
其中,<映射名称>
是为 ConfigMap 指定的名称,<数据源>
是要从中提取数据的目录、
文件或者字面值。ConfigMap 对象的名称必须是合法的
DNS 子域名 .
在你基于文件来创建 ConfigMap 时,<数据源>
中的键名默认取自文件的基本名,
而对应的值则默认为文件的内容。
你可以使用 kubectl describe
或者
kubectl get
获取有关 ConfigMap 的信息。
基于目录创建 ConfigMap 你可以使用 kubectl create configmap
基于同一目录中的多个文件创建 ConfigMap。
当你基于目录来创建 ConfigMap 时,kubectl 识别目录下基本名可以作为合法键名的文件,
并将这些文件打包到新的 ConfigMap 中。普通文件之外的所有目录项都会被忽略
(例如:子目录、符号链接、设备、管道等等)。
例如:
# 创建本地目录
mkdir -p configure-pod-container/configmap/
# 将示例文件下载到 `configure-pod-container/configmap/` 目录
wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties
wget https://kubernetes.io/examples/configmap/ui.properties -O configure-pod-container/configmap/ui.properties
# 创建 configmap
kubectl create configmap game-config --from-file= configure-pod-container/configmap/
以上命令将 configure-pod-container/configmap
目录下的所有文件,也就是
game.properties
和 ui.properties
打包到 game-config ConfigMap
中。你可以使用下面的命令显示 ConfigMap 的详细信息:
kubectl describe configmaps game-config
输出类似以下内容:
Name: game-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties:
----
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
configure-pod-container/configmap/
目录中的 game.properties
和 ui.properties
文件出现在 ConfigMap 的 data
部分。
kubectl get configmaps game-config -o yaml
输出类似以下内容:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2016-02-18T18:52:05Z
name : game-config
namespace : default
resourceVersion : "516"
uid : b4952dc3-d670-11e5-8cd0-68f728db1985
data :
game.properties : |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties : |
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
基于文件创建 ConfigMap 你可以使用 kubectl create configmap
基于单个文件或多个文件创建 ConfigMap。
例如:
kubectl create configmap game-config-2 --from-file= configure-pod-container/configmap/game.properties
将产生以下 ConfigMap:
kubectl describe configmaps game-config-2
输出类似以下内容:
Name: game-config-2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
你可以多次使用 --from-file
参数,从多个数据源创建 ConfigMap。
kubectl create configmap game-config-2 --from-file= configure-pod-container/configmap/game.properties --from-file= configure-pod-container/configmap/ui.properties
你可以使用以下命令显示 game-config-2
ConfigMap 的详细信息:
kubectl describe configmaps game-config-2
输出类似以下内容:
Name: game-config-2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
ui.properties:
----
color.good=purple
color.bad=yellow
allow.textmode=true
how.nice.to.look=fairlyNice
当 kubectl
基于非 ASCII 或 UTF-8 的输入创建 ConfigMap 时,
该工具将这些输入放入 ConfigMap 的 binaryData
字段,而不是 data
中。
同一个 ConfigMap 中可同时包含文本数据和二进制数据源。
如果你想查看 ConfigMap 中的 binaryData
键(及其值),
你可以运行 kubectl get configmap -o jsonpath='{.binaryData}' <name>
。
使用 --from-env-file
选项从环境文件创建 ConfigMap,例如:
Env 文件包含环境变量列表。其中适用以下语法规则:
Env 文件中的每一行必须为 VAR=VAL 格式。 以#开头的行(即注释)将被忽略。 空行将被忽略。 引号不会被特殊处理(即它们将成为 ConfigMap 值的一部分)。 将示例文件下载到 configure-pod-container/configmap/
目录:
wget https://kubernetes.io/examples/configmap/game-env-file.properties -O configure-pod-container/configmap/game-env-file.properties
wget https://kubernetes.io/examples/configmap/ui-env-file.properties -O configure-pod-container/configmap/ui-env-file.properties
Env 文件 game-env-file.properties
如下所示:
cat configure-pod-container/configmap/game-env-file.properties
enemies=aliens
lives=3
allowed="true"
kubectl create configmap game-config-env-file \
--from-env-file= configure-pod-container/configmap/game-env-file.properties
将产生以下 ConfigMap:
kubectl get configmap game-config-env-file -o yaml
输出类似以下内容:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2017-12-27T18:36:28Z
name : game-config-env-file
namespace : default
resourceVersion : "809965"
selfLink : /api/v1/namespaces/default/configmaps/game-config-env-file
uid : d9d1ca5b-eb34-11e7-887b-42010a8002b8
data :
allowed : '"true"'
enemies : aliens
lives : "3"
从 Kubernetes 1.23 版本开始,kubectl
支持多次指定 --from-env-file
参数来从多个数据源创建
ConfigMap。
kubectl create configmap config-multi-env-files \
--from-env-file= configure-pod-container/configmap/game-env-file.properties \
--from-env-file= configure-pod-container/configmap/ui-env-file.properties
将产生以下 ConfigMap:
kubectl get configmap config-multi-env-files -o yaml
输出类似以下内容:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2017-12-27T18:38:34Z
name : config-multi-env-files
namespace : default
resourceVersion : "810136"
uid : 252c4572-eb35-11e7-887b-42010a8002b8
data :
allowed : '"true"'
color : purple
enemies : aliens
how : fairlyNice
lives : "3"
textmode : "true"
定义从文件创建 ConfigMap 时要使用的键 在使用 --from-file
参数时,你可以定义在 ConfigMap 的 data
部分出现键名,
而不是按默认行为使用文件名:
kubectl create configmap game-config-3 --from-file= <我的键名>= <文件路径>
<我的键名>
是你要在 ConfigMap 中使用的键名,<文件路径>
是你想要键所表示的数据源文件的位置。
例如:
kubectl create configmap game-config-3 --from-file= game-special-key= configure-pod-container/configmap/game.properties
将产生以下 ConfigMap:
kubectl get configmaps game-config-3 -o yaml
输出类似以下内容:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2016-02-18T18:54:22Z
name : game-config-3
namespace : default
resourceVersion : "530"
uid : 05f8da22-d671-11e5-8cd0-68f728db1985
data :
game-special-key : |
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
根据字面值创建 ConfigMap 你可以将 kubectl create configmap
与 --from-literal
参数一起使用,
通过命令行定义文字值:
kubectl create configmap special-config --from-literal= special.how= very --from-literal= special.type= charm
你可以传入多个键值对。命令行中提供的每对键值在 ConfigMap 的 data
部分中均表示为单独的条目。
kubectl get configmaps special-config -o yaml
输出类似以下内容:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2016-02-18T19:14:38Z
name : special-config
namespace : default
resourceVersion : "651"
uid : dadce046-d673-11e5-8cd0-68f728db1985
data :
special.how : very
special.type : charm
基于生成器创建 ConfigMap 自 1.14 开始,kubectl
开始支持 kustomization.yaml
。
你还可以基于生成器(Generators)创建 ConfigMap,然后将其应用于 API 服务器上创建对象。
生成器应在目录内的 kustomization.yaml
中指定。
基于文件生成 ConfigMap 例如,要基于 configure-pod-container/configmap/game.properties
文件生成一个 ConfigMap:
# 创建包含 ConfigMapGenerator 的 kustomization.yaml 文件
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: game-config-4
files:
- configure-pod-container/configmap/game.properties
EOF
应用(Apply)kustomization 目录创建 ConfigMap 对象:
configmap/game-config-4-m9dm2f92bt created
你可以检查 ConfigMap 被创建如下:
NAME DATA AGE
game-config-4-m9dm2f92bt 1 37s
kubectl describe configmaps/game-config-4-m9dm2f92bt
Name: game-config-4-m9dm2f92bt
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","data":{"game.properties":"enemies=aliens\nlives=3\nenemies.cheat=true\nenemies.cheat.level=noGoodRotten\nsecret.code.p...
Data
====
game.properties:
----
enemies=aliens
lives=3
enemies.cheat=true
enemies.cheat.level=noGoodRotten
secret.code.passphrase=UUDDLRLRBABAS
secret.code.allowed=true
secret.code.lives=30
Events: <none>
请注意,生成的 ConfigMap 名称具有通过对内容进行散列而附加的后缀,
这样可以确保每次修改内容时都会生成新的 ConfigMap。
定义从文件生成 ConfigMap 时要使用的键 在 ConfigMap 生成器中,你可以定义一个非文件名的键名。
例如,从 configure-pod-container/configmap/game.properties
文件生成 ConfigMap,
但使用 game-special-key
作为键名:
# 创建包含 ConfigMapGenerator 的 kustomization.yaml 文件
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: game-config-5
files:
- game-special-key=configure-pod-container/configmap/game.properties
EOF
应用 Kustomization 目录创建 ConfigMap 对象。
configmap/game-config-5-m67dt67794 created
基于字面值生成 ConfigMap 要基于字符串 special.type=charm
和 special.how=very
生成 ConfigMap,
可以在 kustomization.yaml
中配置 ConfigMap 生成器:
# 创建带有 ConfigMapGenerator 的 kustomization.yaml 文件
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: special-config-2
literals:
- special.how=very
- special.type=charm
EOF
应用 Kustomization 目录创建 ConfigMap 对象。
configmap/special-config-2-c92b5mmcf2 created
使用 ConfigMap 数据定义容器环境变量 使用单个 ConfigMap 中的数据定义容器环境变量 在 ConfigMap 中将环境变量定义为键值对:
kubectl create configmap special-config --from-literal= special.how= very
将 ConfigMap 中定义的 special.how
赋值给 Pod 规约中的 SPECIAL_LEVEL_KEY
环境变量。
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "/bin/sh" , "-c" , "env" ]
env :
# 定义环境变量
- name : SPECIAL_LEVEL_KEY
valueFrom :
configMapKeyRef :
# ConfigMap 包含你要赋给 SPECIAL_LEVEL_KEY 的值
name : special-config
# 指定与取值相关的键名
key : special.how
restartPolicy : Never
创建 Pod:
kubectl create -f https://kubernetes.io/examples/pods/pod-single-configmap-env-variable.yaml
现在,Pod 的输出包含环境变量 SPECIAL_LEVEL_KEY=very
。
使用来自多个 ConfigMap 的数据定义容器环境变量 与前面的示例一样,首先创建 ConfigMap。
apiVersion : v1
kind : ConfigMap
metadata :
name : special-config
namespace : default
data :
special.how : very
---
apiVersion : v1
kind : ConfigMap
metadata :
name : env-config
namespace : default
data :
log_level : INFO
创建 ConfigMap:
kubectl create -f https://kubernetes.io/examples/configmap/configmaps.yaml
说明: Kubernetes v1.6 和更高版本支持此功能。
创建一个包含多个键值对的 ConfigMap。
apiVersion : v1
kind : ConfigMap
metadata :
name : special-config
namespace : default
data :
SPECIAL_LEVEL : very
SPECIAL_TYPE : charm
创建 ConfigMap:
kubectl create -f https://kubernetes.io/examples/configmap/configmap-multikeys.yaml
在 Pod 命令中使用 ConfigMap 定义的环境变量 你可以使用 $(VAR_NAME)
Kubernetes 替换语法在容器的 command
和 args
属性中使用 ConfigMap 定义的环境变量。
例如,以下 Pod 规约
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "/bin/echo" , "$(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]
env :
- name : SPECIAL_LEVEL_KEY
valueFrom :
configMapKeyRef :
name : special-config
key : SPECIAL_LEVEL
- name : SPECIAL_TYPE_KEY
valueFrom :
configMapKeyRef :
name : special-config
key : SPECIAL_TYPE
restartPolicy : Never
通过运行下面命令创建 Pod:
kubectl create -f https://kubernetes.io/examples/pods/pod-configmap-env-var-valueFrom.yaml
在 test-container
容器中产生以下输出:
very charm
将 ConfigMap 数据添加到一个卷中 如基于文件创建 ConfigMap 中所述,当你使用
--from-file
创建 ConfigMap 时,文件名成为存储在 ConfigMap 的 data
部分中的键,
文件内容成为键对应的值。
本节中的示例引用了一个名为 'special-config' 的 ConfigMap,如下所示:
apiVersion : v1
kind : ConfigMap
metadata :
name : special-config
namespace : default
data :
SPECIAL_LEVEL : very
SPECIAL_TYPE : charm
创建 ConfigMap:
kubectl create -f https://kubernetes.io/examples/configmap/configmap-multikeys.yaml
使用存储在 ConfigMap 中的数据填充卷 在 Pod 规约的 volumes
部分下添加 ConfigMap 名称。
这会将 ConfigMap 数据添加到 volumeMounts.mountPath
所指定的目录
(在本例中为 /etc/config
)。
command
部分列出了名称与 ConfigMap 中的键匹配的目录文件。
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "/bin/sh" , "-c" , "ls /etc/config/" ]
volumeMounts :
- name : config-volume
mountPath : /etc/config
volumes :
- name : config-volume
configMap :
# 提供包含要添加到容器中的文件的 ConfigMap 的名称
name : special-config
restartPolicy : Never
创建 Pod:
kubectl create -f https://kubernetes.io/examples/pods/pod-configmap-volume.yaml
Pod 运行时,命令 ls /etc/config/
产生下面的输出:
SPECIAL_LEVEL
SPECIAL_TYPE
注意: 如果在 /etc/config/
目录中有一些文件,这些文件将被删除。
说明: 文本数据会展现为 UTF-8 字符编码的文件。如果使用其他字符编码,
可以使用 binaryData
。
将 ConfigMap 数据添加到卷中的特定路径 使用 path
字段为特定的 ConfigMap 项目指定预期的文件路径。
在这里,ConfigMap 中键 SPECIAL_LEVEL
的内容将挂载在 config-volume
卷中 /etc/config/keys
文件中。
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "/bin/sh" ,"-c" ,"cat /etc/config/keys" ]
volumeMounts :
- name : config-volume
mountPath : /etc/config
volumes :
- name : config-volume
configMap :
name : special-config
items :
- key : SPECIAL_LEVEL
path : keys
restartPolicy : Never
创建Pod:
kubectl create -f https://kubernetes.io/examples/pods/pod-configmap-volume-specific-key.yaml
当 Pod 运行时,命令 cat /etc/config/keys
产生以下输出:
very
注意: 如前,/etc/config/
目录中所有先前的文件都将被删除。
映射键到指定路径并设置文件访问权限 你可以将指定键名投射到特定目录,也可以逐个文件地设定访问权限。
Secret 用户指南
中为这一语法提供了解释。
了解 ConfigMap 和 Pod ConfigMap API 资源将配置数据存储为键值对。
数据可以在 Pod 中使用,也可以用来提供系统组件(如控制器)的配置。
ConfigMap 与 Secret 类似,
但是提供的是一种处理不含敏感信息的字符串的方法。
用户和系统组件都可以在 ConfigMap 中存储配置数据。
说明: ConfigMap 应该引用属性文件,而不是替换它们。可以将 ConfigMap 理解为类似于 Linux
/etc
目录及其内容的东西。例如,如果你基于 ConfigMap 创建
Kubernetes 卷 ,则 ConfigMap
中的每个数据项都由该数据卷中的某个独立的文件表示。
ConfigMap 的 data
字段包含配置数据。如下例所示,它可以简单
(如用 --from-literal
的单个属性定义)或复杂
(如用 --from-file
的配置文件或 JSON blob定义)。
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2016-02-18T19:14:38Z
name : example-config
namespace : default
data :
# 使用 --from-literal 定义的简单属性
example.property.1 : hello
example.property.2 : world
# 使用 --from-file 定义复杂属性的例子
example.property.file : |-
property.1=value-1
property.2=value-2
property.3=value-3
限制 在 Pod 规约中引用某个 ConfigMap
之前,必须先创建这个对象,
或者在 Pod 规约中将 ConfigMap 标记为 optional
(请参阅可选的 ConfigMaps )。
如果所引用的 ConfigMap 不存在,并且没有将应用标记为 optional
则 Pod 将无法启动。
同样,引用 ConfigMap 中不存在的主键也会令 Pod 无法启动,除非你将 Configmap 标记为 optional
。 如果你使用 envFrom
来基于 ConfigMap 定义环境变量,那么无效的键将被忽略。
Pod 可以被启动,但无效名称将被记录在事件日志中(InvalidVariableNames
)。
日志消息列出了每个被跳过的键。例如:
输出与此类似:
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
0s 0s 1 dapi-test-pod Pod Warning InvalidEnvironmentVariableNames {kubelet, 127.0.0.1} Keys [1badkey, 2alsobad] from the EnvFrom configMap default/myconfig were skipped since they are considered invalid environment variable names.
ConfigMap 位于确定的名字空间 中。
每个 ConfigMap 只能被同一名字空间中的 Pod 引用. 你不能将 ConfigMap 用于静态 Pod ,
因为 Kubernetes 不支持这种用法。 可选的 ConfigMap 你可以在 Pod 规约中将对 ConfigMap 的引用标记为 可选(optional) 。
如果 ConfigMap 不存在,那么它在 Pod 中为其提供数据的配置(例如环境变量、挂载的卷)将为空。
如果 ConfigMap 存在,但引用的键不存在,那么数据也是空的。
例如,以下 Pod 规约将 ConfigMap 中的环境变量标记为可选:
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : gcr.io/google_containers/busybox
command : [ "/bin/sh" , "-c" , "env" ]
env :
- name : SPECIAL_LEVEL_KEY
valueFrom :
configMapKeyRef :
name : a-config
key : akey
optional : true # 将环境变量标记为可选
restartPolicy : Never
当你运行这个 Pod 并且名称为 a-config
的 ConfigMap 不存在时,输出空值。
当你运行这个 Pod 并且名称为 a-config
的 ConfigMap 存在,
但是在 ConfigMap 中没有名称为 akey
的键时,控制台输出也会为空。
如果你确实在名为 a-config
的 ConfigMap 中为 akey
设置了键值,
那么这个 Pod 会打印该值,然后终止。
你也可以在 Pod 规约中将 ConfigMap 提供的卷和文件标记为可选。
此时 Kubernetes 将总是为卷创建挂载路径,即使引用的 ConfigMap 或键不存在。
例如,以下 Pod 规约将所引用得 ConfigMap 的卷标记为可选:
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : gcr.io/google_containers/busybox
command : [ "/bin/sh" , "-c" , "ls /etc/config" ]
volumeMounts :
- name : config-volume
mountPath : /etc/config
volumes :
- name : config-volume
configMap :
name : no -config
optional : true # 将引用的 ConfigMap 的卷标记为可选
restartPolicy : Never
挂载的 ConfigMap 将被自动更新 当某个已被挂载的 ConfigMap 被更新,所投射的内容最终也会被更新。
对于 Pod 已经启动之后所引用的、可选的 ConfigMap 才出现的情形,
这一动态更新现象也是适用的。
kubelet 在每次周期性同步时都会检查已挂载的 ConfigMap 是否是最新的。
但是,它使用其本地的基于 TTL 的缓存来获取 ConfigMap 的当前值。
因此,从更新 ConfigMap 到将新键映射到 Pod 的总延迟可能等于 kubelet 同步周期
(默认 1 分钟) + ConfigMap 在 kubelet 中缓存的 TTL(默认 1 分钟)。
说明: 使用 ConfigMap 作为 subPath
的数据卷将不会收到 ConfigMap 更新。
接下来 4.4.20 - 为 Pod 配置用户名字空间 特性状态: Kubernetes v1.25 [alpha]
本页展示如何为无状态 Pod 配置用户名字空间。可以将容器内的用户与主机上的用户隔离开来。
在容器中以 root 用户运行的进程可以以不同的(非 root)用户在宿主机上运行;换句话说,
进程在用户名字空间内部拥有执行操作的全部特权,但在用户名字空间外部并没有执行操作的特权。
你可以使用这个特性来减少有害的容器对同一宿主机上其他容器的影响。
有些安全脆弱性问题 被评为 HIGH or CRITICAL ,但当用户名字空间被启用时,
它们是无法被利用的。相信用户名字空间也能减轻一些未来的漏洞的影响。
在不使用用户名字空间的情况下,对于以 root 用户运行的容器而言,发生容器逃逸时,
容器将拥有在宿主机上的 root 特权。如果容器被赋予了某些权限,则这些权限在宿主机上同样有效。
当使用用户名字空间时这些都不可能发生。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须是 v1.25.
要获知版本信息,请输入
kubectl version
.
🛇 本条目指向第三方项目或产品,而该项目(产品)不是 Kubernetes 的一部分。
更多信息 节点上的操作系统必须为 Linux 你需要在宿主机上执行命令 你需要能够通过 exec 操作进入 Pod 特性 UserNamespacesStatelessPodsSupport
需要被启用。 此外, 需要容器运行时 提供相应的支持,
才能将此特性与 Kubernetes 无状态 Pod 一起使用:
请注意 如果你的容器运行时环境不支持用户名字空间,字段 pod.spec
将被忽略,
并且系统会在没有用户名字空间的环境中创建 Pod。
运行一个使用用户名字空间的 Pod 为一个无状态的 Pod 启用用户名字空间需要设置 .spec
的 hostUsers
字段
为 false
. 例如:
apiVersion : v1
kind : Pod
metadata :
name : userns
spec :
hostUsers : false
containers :
- name : shell
command : ["sleep" , "infinity" ]
image : debian
在你的集群上创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/user-namespaces-stateless.yaml
挂接到容器上并执行 readlink /proc/self/ns/user
:
kubectl attach -it userns bash
执行命令的输出类似于:
readlink /proc/self/ns/user
user:[4026531837]
cat /proc/self/uid_map
0 0 4294967295
然后,在主机中打开一个 Shell 并运行相同的命令。
输出一定是不同的。这意味着主机和 Pod 使用不同的用户名字空间。当未启用用户名字空间时,
宿主机和 Pod 使用相同的用户名字空间。
如果你在用户名字空间中运行 kubelet,则需要将在 Pod 中运行命令的输出与在主机中运行的输出进行比较:
readlink /proc/$pid/ns/user
user:[4026534732]
使用 kubelet 的进程号代替 $pid
4.4.21 - 在 Pod 中的容器之间共享进程命名空间 此页面展示如何为 Pod 配置进程命名空间共享。
当启用进程命名空间共享时,容器中的进程对同一 Pod 中的所有其他容器都是可见的。
你可以使用此功能来配置协作容器,比如日志处理 sidecar 容器,
或者对那些不包含诸如 shell 等调试实用工具的镜像进行故障排查。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
配置 Pod 使用 Pod .spec
中的 shareProcessNamespace
字段可以启用进程命名空间共享。例如:
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
shareProcessNamespace : true
containers :
- name : nginx
image : nginx
- name : shell
image : busybox:1.28
securityContext :
capabilities :
add :
- SYS_PTRACE
stdin : true
tty : true
在集群中创建 nginx
Pod:
kubectl apply -f https://k8s.io/examples/pods/share-process-namespace.yaml
获取容器 shell
,执行 ps
:
kubectl attach -it nginx -c shell
如果没有看到命令提示符,请按 enter 回车键。在容器 shell 中:
# 在 “shell” 容器中运行以下命令
ps ax
输出类似于:
PID USER TIME COMMAND
1 root 0:00 /pause
8 root 0:00 nginx: master process nginx -g daemon off;
14 101 0:00 nginx: worker process
15 root 0:00 sh
21 root 0:00 ps ax
你可以在其他容器中对进程发出信号。例如,发送 SIGHUP
到 nginx
以重启工作进程。
此操作需要 SYS_PTRACE
权能。
# 在 “shell” 容器中运行以下命令
kill -HUP 8 # 如有必要,更改 “8” 以匹配 nginx 领导进程的 PID
ps ax
输出类似于:
PID USER TIME COMMAND
1 root 0:00 /pause
8 root 0:00 nginx: master process nginx -g daemon off;
15 root 0:00 sh
22 101 0:00 nginx: worker process
23 root 0:00 ps ax
甚至可以使用 /proc/$pid/root
链接访问另一个容器的文件系统。
# 在 “shell” 容器中运行以下命令
# 如有必要,更改 “8” 为 Nginx 进程的 PID
head /proc/8/root/etc/nginx/nginx.conf
输出类似于:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
理解进程命名空间共享 Pod 共享许多资源,因此它们共享进程命名空间是很有意义的。
不过,有些容器可能希望与其他容器隔离,因此了解这些差异很重要:
容器进程不再具有 PID 1。 在没有 PID 1 的情况下,一些容器拒绝启动
(例如,使用 systemd
的容器),或者拒绝执行 kill -HUP 1
之类的命令来通知容器进程。
在具有共享进程命名空间的 Pod 中,kill -HUP 1
将通知 Pod 沙箱(在上面的例子中是 /pause
)。进程对 Pod 中的其他容器可见。 这包括 /proc
中可见的所有信息,
例如作为参数或环境变量传递的密码。这些仅受常规 Unix 权限的保护。容器文件系统通过 /proc/$pid/root
链接对 Pod 中的其他容器可见。 这使调试更加容易,
但也意味着文件系统安全性只受文件系统权限的保护。4.4.22 - 创建静态 Pod 静态 Pod 在指定的节点上由 kubelet 守护进程直接管理,不需要
API 服务器 监管。
与由控制面管理的 Pod(例如,Deployment )
不同;kubelet 监视每个静态 Pod(在它失败之后重新启动)。
静态 Pod 始终都会绑定到特定节点的 Kubelet 上。
kubelet 会尝试通过 Kubernetes API 服务器为每个静态 Pod
自动创建一个镜像 Pod 。
这意味着节点上运行的静态 Pod 对 API 服务来说是可见的,但是不能通过 API 服务器来控制。
Pod 名称将把以连字符开头的节点主机名作为后缀。
说明: 如果你在运行一个 Kubernetes 集群,并且在每个节点上都运行一个静态 Pod,
就可能需要考虑使用 DaemonSet
替代这种方式。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
本文假定你在使用 Docker 来运行 Pod,
并且你的节点是运行着 Fedora 操作系统。
其它发行版或者 Kubernetes 部署版本上操作方式可能不一样。
创建静态 Pod 可以通过文件系统上的配置文件 或者
Web 网络上的配置文件 来配置静态 Pod。
文件系统上的静态 Pod 声明文件 声明文件是标准的 Pod 定义文件,以 JSON 或者 YAML 格式存储在指定目录。路径设置在
Kubelet 配置文件 的
staticPodPath: <目录>
字段,kubelet 会定期的扫描这个文件夹下的 YAML/JSON
文件来创建/删除静态 Pod。
注意 kubelet 扫描目录的时候会忽略以点开头的文件。
例如:下面是如何以静态 Pod 的方式启动一个简单 web 服务:
选择一个要运行静态 Pod 的节点。在这个例子中选择 my-node1
。
选择一个目录,比如在 /etc/kubernetes/manifests
目录来保存 Web 服务 Pod 的定义文件,例如
/etc/kubernetes/manifests/static-web.yaml
:
# 在 kubelet 运行的节点上执行以下命令
mkdir -p /etc/kubernetes/manifests/
cat <<EOF >/etc/kubernetes/manifests/static-web.yaml
apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
role: myrole
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
protocol: TCP
EOF
配置这个节点上的 kubelet,使用这个参数执行 --pod-manifest-path=/etc/kubelet.d/
。
在 Fedora 上编辑 /etc/kubernetes/kubelet
以包含下面这行:
KUBELET_ARGS="--cluster-dns=10.254.0.10 --cluster-domain=kube.local --pod-manifest-path=/etc/kubernetes/manifests/"
或者在 Kubelet 配置文件 中添加
staticPodPath: <目录>
字段。
重启 kubelet。在 Fedora 上,你将使用下面的命令:
# 在 kubelet 运行的节点上执行以下命令
systemctl restart kubelet
Web 网上的静态 Pod 声明文件 Kubelet 根据 --manifest-url=<URL>
参数的配置定期的下载指定文件,并且转换成
JSON/YAML 格式的 Pod 定义文件。
与文件系统上的清单文件 使用方式类似,kubelet 调度获取清单文件。
如果静态 Pod 的清单文件有改变,kubelet 会应用这些改变。
按照下面的方式来:
创建一个 YAML 文件,并保存在 web 服务上,为 kubelet 生成一个 URL。
apiVersion : v1
kind : Pod
metadata :
name : static-web
labels :
role : myrole
spec :
containers :
- name : web
image : nginx
ports :
- name : web
containerPort : 80
protocol : TCP
通过在选择的节点上使用 --manifest-url=<manifest-url>
配置运行 kubelet。
在 Fedora 添加下面这行到 /etc/kubernetes/kubelet
:
KUBELET_ARGS="--cluster-dns=10.254.0.10 --cluster-domain=kube.local --manifest-url=<manifest-url>"
重启 kubelet。在 Fedora 上,你将运行如下命令:
# 在 kubelet 运行的节点上执行以下命令
systemctl restart kubelet
观察静态 Pod 的行为 当 kubelet 启动时,会自动启动所有定义的静态 Pod。
当定义了一个静态 Pod 并重新启动 kubelet 时,新的静态 Pod 就应该已经在运行了。
可以在节点上运行下面的命令来查看正在运行的容器(包括静态 Pod):
# 在 kubelet 运行的节点上执行以下命令
crictl ps
输出可能会像这样:
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
129fd7d382018 docker.io/library/nginx@sha256:... 11 minutes ago Running web 0 34533c6729106
说明: crictl
会输出镜像 URI 和 SHA-256 校验和。NAME
看起来像:
docker.io/library/nginx@sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
。
可以在 API 服务上看到镜像 Pod:
NAME READY STATUS RESTARTS AGE
static-web 1/1 Running 0 2m
说明: 要确保 kubelet 在 API 服务上有创建镜像 Pod 的权限。如果没有,创建请求会被 API 服务拒绝。
静态 Pod 上的标签 被传播到镜像 Pod。
你可以通过选择算符 使用这些标签。
如果你用 kubectl
从 API 服务上删除镜像 Pod,kubelet 不会 移除静态 Pod:
kubectl delete pod static-web
pod "static-web" deleted
可以看到 Pod 还在运行:
NAME READY STATUS RESTARTS AGE
static-web 1/1 Running 0 4s
回到 kubelet 运行所在的节点上,你可以手动停止容器。
可以看到过了一段时间后 kubelet 会发现容器停止了并且会自动重启 Pod:
# 在 kubelet 运行的节点上执行以下命令
# 把 ID 换为你的容器的 ID
crictl stop 129fd7d382018
sleep 20
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
89db4553e1eeb docker.io/library/nginx@sha256:... 19 seconds ago Running web 1 34533c6729106
动态增加和删除静态 Pod 运行中的 kubelet 会定期扫描配置的目录(比如例子中的 /etc/kubernetes/manifests
目录)中的变化,
并且根据文件中出现/消失的 Pod 来添加/删除 Pod。
# 这里假定你在用主机文件系统上的静态 Pod 配置文件
# 在 kubelet 运行的节点上执行以下命令
mv /etc/kubernetes/manifests/static-web.yaml /tmp
sleep 20
crictl ps
# 可以看到没有 nginx 容器在运行
mv /tmp/static-web.yaml /etc/kubernetes/manifests/
sleep 20
crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID
f427638871c35 docker.io/library/nginx@sha256:... 19 seconds ago Running web 1 34533c6729106
4.4.23 - 将 Docker Compose 文件转换为 Kubernetes 资源 Kompose 是什么?它是一个转换工具,可将 compose
(即 Docker Compose)所组装的所有内容转换成容器编排器(Kubernetes 或 OpenShift)可识别的形式。
更多信息请参考 Kompose 官网 http://kompose.io 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
安装 Kompose 我们有很多种方式安装 Kompose。首选方式是从最新的 GitHub 发布页面下载二进制文件。
Kompose 通过 GitHub 发布,发布周期为三星期。
你可以在 GitHub 发布页面 上看到所有当前版本。
# Linux
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-linux-amd64 -o kompose
# macOS
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-darwin-amd64 -o kompose
# Windows
curl -L https://github.com/kubernetes/kompose/releases/download/v1.26.0/kompose-windows-amd64.exe -o kompose.exe
chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose
或者,你可以下载 tar 包 。
用 go get
命令从主分支拉取最新的开发变更的方法安装 Kompose。
go get -u github.com/kubernetes/kompose
Kompose 位于 EPEL CentOS 代码仓库。
如果你还没有安装并启用 EPEL 代码仓库,
请运行命令 sudo yum install epel-release
。
如果你的系统中已经启用了 EPEL ,
你就可以像安装其他软件包一样安装 Kompose。
sudo yum -y install kompose
Kompose 位于 Fedora 24、25 和 26 的代码仓库。你可以像安装其他软件包一样安装 Kompose。
sudo dnf -y install kompose
在 macOS 上你可以通过 Homebrew 安装 Kompose 的最新版本:
使用 Kompose 只需几步,我们就把你从 Docker Compose 带到 Kubernetes。
你只需要一个现有的 docker-compose.yml
文件。
进入 docker-compose.yml
文件所在的目录。如果没有,请使用下面这个进行测试。
version : "2"
services :
redis-master :
image : registry.k8s.io/redis:e2e
ports :
- "6379"
redis-slave :
image : gcr.io/google_samples/gb-redisslave:v3
ports :
- "6379"
environment :
- GET_HOSTS_FROM=dns
frontend :
image : gcr.io/google-samples/gb-frontend:v4
ports :
- "80:80"
environment :
- GET_HOSTS_FROM=dns
labels :
kompose.service.type : LoadBalancer
要将 docker-compose.yml
转换为 kubectl
可用的文件,请运行 kompose convert
命令进行转换,然后运行 kubectl apply -f <output file>
进行创建。
输出类似于:
INFO Kubernetes file "frontend-service.yaml" created
INFO Kubernetes file "frontend-service.yaml" created
INFO Kubernetes file "frontend-service.yaml" created
INFO Kubernetes file "redis-master-service.yaml" created
INFO Kubernetes file "redis-master-service.yaml" created
INFO Kubernetes file "redis-master-service.yaml" created
INFO Kubernetes file "redis-slave-service.yaml" created
INFO Kubernetes file "redis-slave-service.yaml" created
INFO Kubernetes file "redis-slave-service.yaml" created
INFO Kubernetes file "frontend-deployment.yaml" created
INFO Kubernetes file "frontend-deployment.yaml" created
INFO Kubernetes file "frontend-deployment.yaml" created
INFO Kubernetes file "redis-master-deployment.yaml" created
INFO Kubernetes file "redis-master-deployment.yaml" created
INFO Kubernetes file "redis-master-deployment.yaml" created
INFO Kubernetes file "redis-slave-deployment.yaml" created
INFO Kubernetes file "redis-slave-deployment.yaml" created
INFO Kubernetes file "redis-slave-deployment.yaml" created
kubectl apply -f frontend-service.yaml,redis-master-service.yaml,redis-slave-service.yaml,frontend-deployment.yaml,redis-master-deployment.yaml,redis-slave-deployment.yaml
输出类似于:
service/frontend created
service/redis-master created
service/redis-slave created
deployment.apps/frontend created
deployment.apps/redis-master created
deployment.apps/redis-slave created
你部署的应用在 Kubernetes 中运行起来了。
访问你的应用。
如果你在开发过程中使用 minikube
,请执行:
minikube service frontend
否则,我们要查看一下你的服务使用了什么 IP!
kubectl describe svc frontend
Name: frontend
Namespace: default
Labels: service=frontend
Selector: service=frontend
Type: LoadBalancer
IP: 10.0.0.183
LoadBalancer Ingress: 192.0.2.89
Port: 80 80/TCP
NodePort: 80 31144/TCP
Endpoints: 172.17.0.4:80
Session Affinity: None
No events.
如果你使用的是云驱动,你的 IP 将在 LoadBalancer Ingress
字段给出。
用户指南 Kompose 支持两种驱动:OpenShift 和 Kubernetes。
你可以通过全局选项 --provider
选择驱动。如果没有指定,
会将 Kubernetes 作为默认驱动。
kompose convert
Kompose 支持将 V1、V2 和 V3 版本的 Docker Compose 文件转换为 Kubernetes 和 OpenShift 资源对象。
Kubernetes kompose convert
示例 kompose --file docker-voting.yml convert
WARN Unsupported key networks - ignoring
WARN Unsupported key build - ignoring
INFO Kubernetes file "worker-svc.yaml" created
INFO Kubernetes file "db-svc.yaml" created
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "result-svc.yaml" created
INFO Kubernetes file "vote-svc.yaml" created
INFO Kubernetes file "redis-deployment.yaml" created
INFO Kubernetes file "result-deployment.yaml" created
INFO Kubernetes file "vote-deployment.yaml" created
INFO Kubernetes file "worker-deployment.yaml" created
INFO Kubernetes file "db-deployment.yaml" created
db-deployment.yaml docker-compose.yml docker-gitlab.yml redis-deployment.yaml result-deployment.yaml vote-deployment.yaml worker-deployment.yaml
db-svc.yaml docker-voting.yml redis-svc.yaml result-svc.yaml vote-svc.yaml worker-svc.yaml
你也可以同时提供多个 docker-compose 文件进行转换:
kompose -f docker-compose.yml -f docker-guestbook.yml convert
INFO Kubernetes file "frontend-service.yaml" created
INFO Kubernetes file "mlbparks-service.yaml" created
INFO Kubernetes file "mongodb-service.yaml" created
INFO Kubernetes file "redis-master-service.yaml" created
INFO Kubernetes file "redis-slave-service.yaml" created
INFO Kubernetes file "frontend-deployment.yaml" created
INFO Kubernetes file "mlbparks-deployment.yaml" created
INFO Kubernetes file "mongodb-deployment.yaml" created
INFO Kubernetes file "mongodb-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "redis-master-deployment.yaml" created
INFO Kubernetes file "redis-slave-deployment.yaml" created
mlbparks-deployment.yaml mongodb-service.yaml redis-slave-service.jsonmlbparks-service.yaml
frontend-deployment.yaml mongodb-claim0-persistentvolumeclaim.yaml redis-master-service.yaml
frontend-service.yaml mongodb-deployment.yaml redis-slave-deployment.yaml
redis-master-deployment.yaml
当提供多个 docker-compose 文件时,配置将会合并。任何通用的配置都将被后续文件覆盖。
OpenShift kompose convert
示例 kompose --provider openshift --file docker-voting.yml convert
WARN [worker] Service cannot be created because of missing port.
INFO OpenShift file "vote-service.yaml" created
INFO OpenShift file "db-service.yaml" created
INFO OpenShift file "redis-service.yaml" created
INFO OpenShift file "result-service.yaml" created
INFO OpenShift file "vote-deploymentconfig.yaml" created
INFO OpenShift file "vote-imagestream.yaml" created
INFO OpenShift file "worker-deploymentconfig.yaml" created
INFO OpenShift file "worker-imagestream.yaml" created
INFO OpenShift file "db-deploymentconfig.yaml" created
INFO OpenShift file "db-imagestream.yaml" created
INFO OpenShift file "redis-deploymentconfig.yaml" created
INFO OpenShift file "redis-imagestream.yaml" created
INFO OpenShift file "result-deploymentconfig.yaml" created
INFO OpenShift file "result-imagestream.yaml" created
kompose 还支持为服务中的构建指令创建 buildconfig。
默认情况下,它使用当前 git 分支的 remote 仓库作为源仓库,使用当前分支作为构建的源分支。
你可以分别使用 --build-repo
和 --build-branch
选项指定不同的源仓库和分支。
kompose --provider openshift --file buildconfig/docker-compose.yml convert
WARN [foo] Service cannot be created because of missing port.
INFO OpenShift Buildconfig using git@github.com:rtnpro/kompose.git::master as source.
INFO OpenShift file "foo-deploymentconfig.yaml" created
INFO OpenShift file "foo-imagestream.yaml" created
INFO OpenShift file "foo-buildconfig.yaml" created
其他转换方式 默认的 kompose
转换会生成 yaml 格式的 Kubernetes
Deployment 和
Service 对象。
你可以选择通过 -j
参数生成 json 格式的对象。
你也可以替换生成 Replication Controllers 对象、
DaemonSet 或
Helm Chart。
INFO Kubernetes file "redis-svc.json" created
INFO Kubernetes file "web-svc.json" created
INFO Kubernetes file "redis-deployment.json" created
INFO Kubernetes file "web-deployment.json" created
*-deployment.json
文件中包含 Deployment 对象。
kompose convert --replication-controller
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-replicationcontroller.yaml" created
INFO Kubernetes file "web-replicationcontroller.yaml" created
*-replicationcontroller.yaml
文件包含 Replication Controller 对象。
如果你想指定副本数(默认为 1),可以使用 --replicas
参数:
kompose convert --replication-controller --replicas 3
kompose convert --daemon-set
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-daemonset.yaml" created
INFO Kubernetes file "web-daemonset.yaml" created
*-daemonset.yaml
文件包含 DaemonSet 对象。
如果你想生成 Helm 可用的 Chart,
只需简单的执行下面的命令:
INFO Kubernetes file "web-svc.yaml" created
INFO Kubernetes file "redis-svc.yaml" created
INFO Kubernetes file "web-deployment.yaml" created
INFO Kubernetes file "redis-deployment.yaml" created
chart created in "./docker-compose/"
docker-compose
├── Chart.yaml
├── README.md
└── templates
├── redis-deployment.yaml
├── redis-svc.yaml
├── web-deployment.yaml
└── web-svc.yaml
这个 Chart 结构旨在为构建 Helm Chart 提供框架。
标签 kompose
支持 docker-compose.yml
文件中用于 Kompose 的标签,
以便在转换时明确定义 Service 的行为。
当前支持的选项有:
键 值 kompose.service.type nodeport / clusterip / loadbalancer kompose.service.expose true / hostname
说明: kompose.service.type
标签应该只用 ports
来定义,否则 kompose
会失败。
重启 如果你想创建没有控制器的普通 Pod,可以使用 docker-compose 的 restart
结构来指定这一行为。请参考下表了解 restart
的不同参数。
docker-compose
restart
创建的对象 Pod restartPolicy
""
控制器对象 Always
always
控制器对象 Always
on-failure
Pod OnFailure
no
Pod Never
说明: 控制器对象可以是 deployment
或 replicationcontroller
。
例如,pival
Service 将在这里变成 Pod。这个容器计算 pi
的取值。
version : '2'
services :
pival :
image : perl
command : ["perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restart : "on-failure"
关于 Deployment Config 的提醒 如果 Docker Compose 文件中为服务声明了卷,Deployment(Kubernetes)或
DeploymentConfig(OpenShift)策略会从 “RollingUpdate”(默认)变为 “Recreate”。
这样做的目的是为了避免服务的多个实例同时访问卷。
如果 Docker Compose 文件中的服务名包含 _
(例如 web_service
),
那么将会被替换为 -
,服务也相应的会重命名(例如 web-service
)。
Kompose 这样做的原因是 “Kubernetes” 不允许对象名称中包含 _
。
请注意,更改服务名称可能会破坏一些 docker-compose
文件。
Docker Compose 版本 Kompose 支持的 Docker Compose 版本包括:1、2 和 3。
对 2.1 和 3.2 版本的支持还有限,因为它们还在实验阶段。
所有三个版本的兼容性列表,
请查看我们的转换文档 ,
文档中列出了所有不兼容的 Docker Compose 关键字。
4.4.24 - 从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器 本页面描述从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器的过程。
这一迁移过程可以通过综合使用试运行、audit
和 warn
模式等来实现,
尽管在使用了变更式 PSP 时会变得有些困难。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 v1.22.
要获知版本信息,请输入 kubectl version
.
如果你目前运行的 Kubernetes 版本不是 {{ skew currentVersion }},
你可能要切换本页面以查阅你实际所运行的 Kubernetes 版本文档。
本页面假定你已经熟悉 Pod 安全性准入 的基本概念。
方法概览 你可以采取多种策略来完成从 PodSecurityPolicy 到 Pod 安全性准入
(Pod Security Admission)的迁移。
下面是一种可能的迁移路径,其目标是尽可能降低生产环境不可用的风险,
以及安全性仍然不足的风险。
确定 Pod 安全性准入是否对于你的使用场景而言比较合适。 审查名字空间访问权限。 简化、标准化 PodSecurityPolicy。 更新名字空间:确定合适的 Pod 安全性级别; 验证该 Pod 安全性级别可工作; 实施该 Pod 安全性级别; 绕过 PodSecurityPolicy。 审阅名字空间创建过程。 禁用 PodSecurityPolicy。 0. 确定是否 Pod 安全性准入适合你 Pod 安全性准入被设计用来直接满足最常见的安全性需求,并提供一组可用于多个集群的安全性级别。
不过,这一机制比 PodSecurityPolicy 的灵活度要低。
值得注意的是,PodSecurityPolicy 所支持的以下特性是 Pod 安全性准入所不支持的:
粒度小于名字空间的策略 - PodSecurityPolicy 允许你为不同的服务账户或用户绑定不同策略,
即使这些服务账户或用户隶属于同一个名字空间。这一方法有很多缺陷,不建议使用。
不过如果你的确需要这种功能,你就需要使用第三方的 Webhook。
唯一的例外是当你只需要完全针对某用户或者
RuntimeClasses 赋予豁免规则时,
Pod 安全性准入的确也为豁免规则暴露一些
静态配置 。即便 Pod 安全性准入无法满足你的所有需求,该机制也是设计用作其他策略实施机制的
补充 ,因此可以和其他准入 Webhook 一起运行,进而提供一种有用的兜底机制。
1. 审查名字空间访问权限 Pod 安全性准入是通过名字空间上的标签
来控制的。这也就是说,任何能够更新(或通过 patch 部分更新或创建)
名字空间的人都可以更改该名字空间的 Pod 安全性级别,而这可能会被利用来绕过约束性更强的策略。
在继续执行迁移操作之前,请确保只有被信任的、有特权的用户具有这类名字空间访问权限。
不建议将这类强大的访问权限授予不应获得权限提升的用户,不过如果你必须这样做,
你需要使用一个准入 Webhook
来针对为 Namespace 对象设置 Pod 安全性级别设置额外的约束。
2. 简化、标准化 PodSecurityPolicy 在本节中,你会削减变更性质的 PodSecurityPolicy,去掉 Pod 安全性标准范畴之外的选项。
针对要修改的、已存在的 PodSecurityPolicy,你应该将这里所建议的更改写入到其离线副本中。
所克隆的 PSP 应该与原来的副本名字不同,并且按字母序要排到原副本之前
(例如,可以向 PSP 名字前加一个 0
)。
先不要在 Kubernetes 中创建新的策略 -
这类操作会在后文的推出更新的策略 部分讨论。
2.a. 去掉纯粹变更性质的字段 如果某个 PodSecurityPolicy 能够变更字段,你可能会在关掉 PodSecurityPolicy
时发现有些 Pod 无法满足 Pod 安全性级别。为避免这类状况,
你应该在执行切换操作之前去掉所有 PSP 的变更操作。
不幸的是,PSP 没有对变更性和验证性字段做清晰的区分,所以这一迁移操作也不够简单直接。
你可以先去掉那些纯粹变更性质的字段,留下验证策略中的其他内容。
这些字段(也列举于将 PodSecurityPolicy 映射到 Pod 安全性标准 参考中)
包括:
.spec.defaultAllowPrivilegeEscalation
.spec.runtimeClass.defaultRuntimeClassName
.metadata.annotations['seccomp.security.alpha.kubernetes.io/defaultProfileName']
.metadata.annotations['apparmor.security.beta.kubernetes.io/defaultProfileName']
.spec.defaultAddCapabilities
- 尽管理论上是一个混合了变更性与验证性功能的字段,
这里的设置应该被合并到 .spec.allowedCapabilities
中,后者会执行相同的验证操作,
但不会执行任何变更动作。注意: 删除这些字段可能导致负载缺少所需的配置信息,进而导致一些问题。
参见后文退出更新的策略 以获得如何安全地将这些变更上线的建议。
2.b. 去掉 Pod 安全性标准未涉及的选项 PodSecurityPolicy 中有一些字段未被 Pod 安全性准入机制覆盖。如果你必须使用这些选项,
你需要在 Pod 安全性准入之外部署
准入 Webhook
以补充这一能力,而这类操作不在本指南范围。
首先,你可以去掉 Pod 安全性标准所未覆盖的那些验证性字段。这些字段(也列举于
将 PodSecurityPolicy 映射到 Pod 安全性标准 参考中,标记为“无意见”)有:
.spec.allowedHostPaths
.spec.allowedFlexVolumes
.spec.allowedCSIDrivers
.spec.forbiddenSysctls
.spec.runtimeClass
你也可以去掉以下字段,这些字段与 POSIX/UNIX 用户组控制有关。
注意: 如果这些字段中存在使用 MustRunAs
策略的情况,则意味着对应字段是变更性质的。
去掉相应的字段可能导致负载无法设置所需的用户组,进而带来一些问题。
关于如何安全地将这类变更上线的相关建议,请参阅后文的推出更新的策略 部分。
.spec.runAsGroup
.spec.supplementalGroups
.spec.fsGroup
剩下的变更性字段是为了适当支持 Pod 安全性标准所需要的,因而需要逐个处理:
.spec.requiredDropCapabilities
- 需要此字段来为 Restricted 配置去掉 ALL
设置。.spec.seLinux
- (仅针对带有 MustRunAs
规则的变更性设置)需要此字段来满足
Baseline 和 Restricted 配置所需要的 SELinux 需求。.spec.runAsUser
- (仅针对带有 RunAsAny
规则的非变更性设置)需要此字段来为
Restricted 配置保证 RunAsNonRoot
。.spec.allowPrivilegeEscalation
- (如果设置为 false
则为变更性设置)
需要此字段来支持 Restricted 配置。2.c. 推出更新的 PSP 接下来,你可以将更新后的策略推出到你的集群上。在继续操作时,你要非常小心,
因为去掉变更性质的选项可能导致有些工作负载缺少必需的配置。
针对更新后的每个 PodSecurityPolicy:
识别运行于原 PSP 之下的 Pod。可以通过 kubernetes.io/psp
注解来完成。
例如,使用 kubectl:
PSP_NAME = "original" # 设置你要检查的 PSP 的名称
kubectl get pods --all-namespaces -o jsonpath = "{range .items[?(@.metadata.annotations.kubernetes\.io\/psp==' $PSP_NAME ')]}{.metadata.namespace} {.metadata.name}{'\n'}{end}"
比较运行中的 Pod 与原来的 Pod 规约,确定 PodSecurityPolicy 是否更改过这些 Pod。
对于通过工作负载资源 所创建的 Pod,
你可以比较 Pod 和控制器资源中的 PodTemplate。如果发现任何变更,则原来的 Pod
或者 PodTemplate 需要被更新以加上所希望的配置。要审查的字段包括:
.metadata.annotations['container.apparmor.security.beta.kubernetes.io/*']
(将 *
替换为每个容器的名称).spec.runtimeClassName
.spec.securityContext.fsGroup
.spec.securityContext.seccompProfile
.spec.securityContext.seLinuxOptions
.spec.securityContext.supplementalGroups
对于容器,在 .spec.containers[*]
和 .spec.initContainers[*]
之下,检查下面字段: .securityContext.allowPrivilegeEscalation
.securityContext.capabilities.add
.securityContext.capabilities.drop
.securityContext.readOnlyRootFilesystem
.securityContext.runAsGroup
.securityContext.runAsNonRoot
.securityContext.runAsUser
.securityContext.seccompProfile
.securityContext.seLinuxOptions
创建新的 PodSecurityPolicy。如果存在 Role 或 ClusterRole 对象为用户授权了在所有 PSP
上使用 use
动词的权限,则所使用的的会是新创建的 PSP 而不是其变更性的副本。 更新你的鉴权配置,为访问新的 PSP 授权。在 RBAC 机制下,这意味着需要更新所有为原 PSP
授予 use
访问权限的 Role 或 ClusterRole 对象,使之也对更新后的 PSP 授权。 验证:经过一段时间后,重新执行步骤 1 中所给的命令,查看是否有 Pod 仍在使用原来的 PSP。
注意,在新的策略被推出到集群之后,Pod 需要被重新创建才可以执行全面验证。 (可选)一旦你已经验证原来的 PSP 不再被使用,你就可以删除这些 PSP。 3. 更新名字空间 下面的步骤需要在集群中的所有名字空间上执行。所列步骤中的命令使用变量
$NAMESPACE
来引用所更新的名字空间。
3.a. 识别合适的 Pod 安全级别 首先请回顾 Pod 安全性标准 内容,
并了解三个安全级别。
为你的名字空间选择 Pod 安全性级别有几种方法:
根据名字空间的安全性需求来确定 - 如果你熟悉某名字空间的预期访问级别,
你可以根据这类需求来选择合适的安全级别,就像大家在为新集群确定安全级别一样。根据现有的 PodSecurityPolicy 来确定 - 基于
将 PodSecurityPolicy 映射到 Pod 安全性标准
参考资料,你可以将各个 PSP 映射到某个 Pod 安全性标准级别。如果你的 PSP 不是基于
Pod 安全性标准的,你可能或者需要选择一个至少与该 PSP 一样宽松的级别,
或者选择一个至少与其一样严格的级别。使用下面的命令你可以查看被 Pod 使用的 PSP 有哪些:
kubectl get pods -n $NAMESPACE -o jsonpath = "{.items[*].metadata.annotations.kubernetes\.io\/psp}" | tr " " "\n" | sort -u
根据现有 Pod 来确定 - 使用检查 Pod 安全性级别 小节所述策略,
你可以测试 Baseline 和 Restricted 级别,检查它们是否对于现有负载而言足够宽松,
并选择二者之间特权级较低的合法级别。注意: 上面的第二和第三种方案是基于 现有 Pod 的,因此可能错失那些当前未处于运行状态的
Pod,例如 CronJobs、缩容到零的负载,或者其他尚未全面铺开的负载。
3.b. 检查 Pod 安全性级别 一旦你已经为名字空间选择了 Pod 安全性级别(或者你正在尝试多个不同级别),
先进行测试是个不错的主意(如果使用 Privileged 级别,则可略过此步骤)。
Pod 安全性包含若干工具可用来测试和安全地推出安全性配置。
首先,你可以试运行新策略,这个过程可以针对所应用的策略评估当前在名字空间中运行的
Pod,但不会令新策略马上生效:
# $LEVEL 是要试运行的级别,可以是 "baseline" 或 "restricted"
kubectl label --dry-run= server --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce= $LEVEL
此命令会针对在所提议的级别下不再合法的所有 现存 Pod 返回警告信息。
第二种办法在抓取当前未运行的负载方面表现的更好:audit 模式。
运行于 audit 模式(而非 enforcing 模式)下时,违反策略级别的 Pod 会被记录到审计日志中,
经过一段时间后可以在日志中查看到,但这些 Pod 不会被拒绝。
warning 模式的工作方式与此类似,不过会立即向用户返回告警信息。
你可以使用下面的命令为名字空间设置 audit 模式的级别:
kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/audit= $LEVEL
当以上两种方法输出意料之外的违例状况时,你就需要或者更新发生违例的负载以满足策略需求,
或者放宽名字空间上的 Pod 安全性级别。
3.c. 实施 Pod 安全性级别 当你对可以安全地在名字空间上实施的级别比较满意时,你可以更新名字空间来实施所期望的级别:
kubectl label --overwrite ns $NAMESPACE pod-security.kubernetes.io/enforce= $LEVEL
3.d. 绕过 PodSecurityPolicy 最后,你可以通过将
完全特权的 PSP
绑定到某名字空间中所有服务账户上,在名字空间层面绕过所有 PodSecurityPolicy。
# 下面集群范围的命令只需要执行一次
kubectl apply -f privileged-psp.yaml
kubectl create clusterrole privileged-psp --verb use --resource podsecuritypolicies.policy --resource-name privileged
# 逐个名字空间地禁用
kubectl create -n $NAMESPACE rolebinding disable-psp --clusterrole privileged-psp --group system:serviceaccounts:$NAMESPACE
由于特权 PSP 是非变更性的,PSP 准入控制器总是优选非变更性的 PSP,
上面的操作会确保对应名字空间中的所有 Pod 不再会被 PodSecurityPolicy
所更改或限制。
按上述操作逐个名字空间地禁用 PodSecurityPolicy 这种做法的好处是,
如果出现问题,你可以很方便地通过删除 RoleBinding 来回滚所作的更改。
你所要做的只是确保之前存在的 PodSecurityPolicy 还在。
# 撤销 PodSecurityPolicy 的禁用
kubectl delete -n $NAMESPACE rolebinding disable-psp
4. 审阅名字空间创建过程 现在,现有的名字空间都已被更新,强制实施 Pod 安全性准入,
你应该确保你用来管控新名字空间创建的流程与/或策略也被更新,这样合适的 Pod
安全性配置会被应用到新的名字空间上。
你也可以静态配置 Pod 安全性准入控制器,为尚未打标签的名字空间设置默认的
enforce、audit 与/或 warn 级别。
详细信息可参阅配置准入控制器 页面。
5. 禁用 PodSecurityPolicy 最后,你已为禁用 PodSecurityPolicy 做好准备。要禁用 PodSecurityPolicy,
你需要更改 API 服务器上的准入配置:
我如何关闭某个准入控制器?
如果需要验证 PodSecurityPolicy 准入控制器不再被启用,你可以通过扮演某个无法访问任何
PodSecurityPolicy 的用户来执行测试(参见
PodSecurityPolicy 示例 ),
或者通过检查 API 服务器的日志来进行验证。在启动期间,API
服务器会输出日志行,列举所挂载的准入控制器插件。
I0218 00:59:44.903329 13 plugins.go:158] Loaded 16 mutating admission controller(s) successfully in the following order: NamespaceLifecycle,LimitRanger,ServiceAccount,NodeRestriction,TaintNodesByCondition,Priority,DefaultTolerationSeconds,ExtendedResourceToleration,PersistentVolumeLabel,DefaultStorageClass,StorageObjectInUseProtection,RuntimeClass,DefaultIngressClass,MutatingAdmissionWebhook.
I0218 00:59:44.903350 13 plugins.go:161] Loaded 14 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority,PersistentVolumeClaimResize,RuntimeClass,CertificateApproval,CertificateSigning,CertificateSubjectRestriction,DenyServiceExternalIPs,ValidatingAdmissionWebhook,ResourceQuota.
你应该会看到 PodSecurity
(在 validating admission controllers 列表中),
并且两个列表中都不应该包含 PodSecurityPolicy
。
一旦你确定 PSP 准入控制器已被禁用(并且这种状况已经持续了一段时间,
这样你才会比较确定不需要回滚),你就可以放心地删除你的 PodSecurityPolicy
以及所关联的所有 Role、ClusterRole、RoleBinding、ClusterRoleBinding 等对象
(仅需要确保他们不再授予其他不相关的访问权限)。
4.4.25 - 使用名字空间标签来实施 Pod 安全性标准 特权(privileged) 、
基线(baseline) 和
受限(restricted)
这三种策略涵盖了广泛安全范围,并由 Pod 安全
准入控制器 实现。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 v1.22.
要获知版本信息,请输入 kubectl version
.
通过名字空间标签来要求实施 baseline
Pod 容器标准 下面的清单定义了一个 my-baseline-namespace
名字空间,其中
阻止 任何不满足 baseline
策略要求的 Pods;针对任何无法满足 restricted
策略要求的、已创建的 Pod 为用户生成警告信息,
并添加审计注解; 将 baseline
和 restricted
策略的版本锁定到 v1.25。 apiVersion : v1
kind : Namespace
metadata :
name : my-baseline-namespace
labels :
pod-security.kubernetes.io/enforce : baseline
pod-security.kubernetes.io/enforce-version : v1.25
# 我们将这些标签设置为我们所 _期望_ 的 `enforce` 级别
pod-security.kubernetes.io/audit : restricted
pod-security.kubernetes.io/audit-version : v1.25
pod-security.kubernetes.io/warn : restricted
pod-security.kubernetes.io/warn-version : v1.25
使用 kubectl label
为现有名字空间添加标签 说明: 在添加或变更 enforce
策略(或版本)标签时,准入插件会测试名字空间中的每个
Pod 以检查其是否满足新的策略。不符合策略的情况会被以警告的形式返回给用户。
在刚开始为名字空间评估安全性策略变更时,使用 --dry-run
标志是很有用的。
Pod 安全性标准会在 dry run(试运行)
模式下运行,在这种模式下会生成新策略如何处理现有 Pod 的信息,
但不会真正更新策略。
kubectl label --dry-run= server --overwrite ns --all \
pod-security.kubernetes.io/enforce= baseline
应用到所有名字空间 如果你是刚刚开始使用 Pod 安全性标准,一种比较合适的初始步骤是针对所有名字空间为类似
baseline
这种比较严格的安全级别配置审计注解。
kubectl label --overwrite ns --all \
pod-security.kubernetes.io/audit= baseline \
pod-security.kubernetes.io/warn= baseline
注意,这里没有设置 enforce 级别,因而没有被显式评估的名字空间可以被识别出来。
你可以使用下面的命令列举那些没有显式设置 enforce 级别的名字空间:
kubectl get namespaces --selector= '!pod-security.kubernetes.io/enforce'
应用到单个名字空间 你也可以更新特定的名字空间。下面的命令将 enforce=restricted
策略应用到
my-existing-namespace
名字空间,将 restricted 策略的版本锁定到 v1.25。
kubectl label --overwrite ns my-existing-namespace \
pod-security.kubernetes.io/enforce= restricted \
pod-security.kubernetes.io/enforce-version= v1.25
4.4.26 - 通过配置内置准入控制器实施 Pod 安全标准 在 v1.22 版本中,Kubernetes 提供一种内置的准入控制器
用来强制实施 Pod 安全标准 。
你可以配置此准入控制器来设置集群范围的默认值和豁免选项 。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 v1.22.
要获知版本信息,请输入 kubectl version
.
apiVersion : apiserver.config.k8s.io/v1
kind : AdmissionConfiguration
plugins :
- name : PodSecurity
configuration :
apiVersion : pod-security.admission.config.k8s.io/v1
kind : PodSecurityConfiguration
# 当未设置 mode 标签时会应用的默认设置
#
# level 标签必须是以下取值之一:
# - "privileged" (默认)
# - "baseline"
# - "restricted"
#
# version 标签必须是如下取值之一:
# - "latest" (默认)
# - 诸如 "v1.25" 这类版本号
defaults :
enforce : "privileged"
enforce-version : "latest"
audit : "privileged"
audit-version : "latest"
warn : "privileged"
warn-version : "latest"
exemptions :
# 要豁免的已认证用户名列表
usernames : []
# 要豁免的运行时类名称列表
runtimeClasses : []
# 要豁免的名字空间列表
namespaces : []
说明: pod-security.admission.config.k8s.io/v1
配置需要 v1.25+。
对于 v1.23 和 v1.24,使用 v1beta1 。
对于 v1.22,使用 v1alpha1 。
v1beta1 配置结构需要使用 v1.23+ 版本;对于 v1.22 版本,可使用 v1alpha1。
4.5 - 管理 Kubernetes 对象 用声明式和命令式范型与 Kubernetes API 交互。
4.5.1 - 使用配置文件对 Kubernetes 对象进行声明式管理 你可以通过在一个目录中存储多个对象配置文件、并使用 kubectl apply
来递归地创建和更新对象来创建、更新和删除 Kubernetes 对象。
这种方法会保留对现有对象已作出的修改,而不会将这些更改写回到对象配置文件中。
kubectl diff
也会给你呈现 apply
将作出的变更的预览。
准备开始 安装 kubectl
。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
权衡取舍 kubectl
工具能够支持三种对象管理方式:
关于每种对象管理的优缺点的讨论,可参见
Kubernetes 对象管理 。
概览 声明式对象管理需要用户对 Kubernetes 对象定义和配置有比较深刻的理解。
如果你还没有这方面的知识储备,请先阅读下面的文档:
以下是本文档中使用的术语的定义:
对象配置文件/配置文件 :一个定义 Kubernetes 对象的配置的文件。
本主题展示如何将配置文件传递给 kubectl apply
。
配置文件通常存储于类似 Git 这种源码控制系统中。现时对象配置/现时配置 :由 Kubernetes 集群所观测到的对象的现时配置值。
这些配置保存在 Kubernetes 集群存储(通常是 etcd)中。声明式配置写者/声明式写者 :负责更新现时对象的人或者软件组件。
本主题中的声明式写者负责改变对象配置文件并执行 kubectl apply
命令
以写入变更。如何创建对象 使用 kubectl apply
来创建指定目录中配置文件所定义的所有对象,除非对应对象已经存在:
此操作会在每个对象上设置 kubectl.kubernetes.io/last-applied-configuration: '{...}'
注解。注解值中包含了用来创建对象的配置文件的内容。
说明: 添加 -R
标志可以递归地处理目录。
下面是一个对象配置文件示例:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
minReadySeconds : 5
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
执行 kubectl diff
可以打印出将被创建的对象:
kubectl diff -f https://k8s.io/examples/application/simple_deployment.yaml
使用 kubectl apply
来创建对象:
kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml
使用 kubectl get
打印其现时配置:
kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml
输出显示注解 kubectl.kubernetes.io/last-applied-configuration
被写入到
现时配置中,并且其内容与配置文件相同:
kind : Deployment
metadata :
annotations :
# ...
# This is the json representation of simple_deployment.yaml
# It was written by kubectl apply when the object was created
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
# ...
minReadySeconds : 5
selector :
matchLabels :
# ...
app : nginx
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.14.2
# ...
name : nginx
ports :
- containerPort : 80
# ...
# ...
# ...
# ...
如何更新对象 你也可以使用 kubectl apply
来更新某个目录中定义的所有对象,即使那些对象已经存在。
这一操作会隐含以下行为:
在现时配置中设置配置文件中出现的字段; 在现时配置中清除配置文件中已删除的字段。 kubectl diff -f <目录>/
kubectl apply -f <目录>/
说明: 使用 -R
标志递归处理目录。
下面是一个配置文件示例:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
minReadySeconds : 5
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
使用 kubectl apply
来创建对象:
kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml
说明: 出于演示的目的,上面的命令引用的是单个文件而不是整个目录。
使用 kubectl get
打印现时配置:
kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml
输出显示,注解 kubectl.kubernetes.io/last-applied-configuration
被写入到
现时配置中,并且其取值与配置文件内容相同。
kind : Deployment
metadata :
annotations :
# ...
# 此为 simple_deployment.yaml 的 JSON 表示
# 在对象创建时由 kubectl apply 命令写入
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
# ...
minReadySeconds : 5
selector :
matchLabels :
# ...
app : nginx
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.14.2
# ...
name : nginx
ports :
- containerPort : 80
# ...
# ...
# ...
# ...
通过 kubectl scale
命令直接更新现时配置中的 replicas
字段。
这一命令没有使用 kubectl apply
:
kubectl scale deployment/nginx-deployment --replicas= 2
使用 kubectl get
来打印现时配置:
kubectl get deployment nginx-deployment -o yaml
输出显示,replicas
字段已经被设置为 2,而 last-applied-configuration
注解中
并不包含 replicas
字段。
apiVersion : apps/v1
kind : Deployment
metadata :
annotations :
# ...
# 注意注解中并不包含 replicas
# 这是因为更新并不是通过 kubectl apply 来执行的
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
replicas : 2 # written by scale
# ...
minReadySeconds : 5
selector :
matchLabels :
# ...
app : nginx
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.14.2
# ...
name : nginx
ports :
- containerPort : 80
# ...
现在更新 simple_deployment.yaml
配置文件,将镜像文件从
nginx:1.14.2
更改为 nginx:1.16.1
,同时删除minReadySeconds
字段:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.16.1 # 更新该镜像
ports :
- containerPort : 80
应用对配置文件所作更改:
kubectl diff -f https://k8s.io/examples/application/update_deployment.yaml
kubectl apply -f https://k8s.io/examples/application/update_deployment.yaml
使用 kubectl get
打印现时配置:
kubectl get -f https://k8s.io/examples/application/update_deployment.yaml -o yaml
输出显示现时配置中发生了以下更改:
字段 replicas
保留了 kubectl scale
命令所设置的值:2;
之所以该字段被保留是因为配置文件中并没有设置 replicas
。 字段 image
的内容已经从 nginx:1.14.2
更改为 nginx:1.16.1
。 注解 last-applied-configuration
内容被更改为新的镜像名称。 字段 minReadySeconds
被移除。 注解 last-applied-configuration
中不再包含 minReadySeconds
字段。 apiVersion : apps/v1
kind : Deployment
metadata :
annotations :
# ...
# 注解中包含更新后的镜像 nginx 1.16.1
# 但是其中并不包含更改后的 replicas 值 2
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
replicas : 2 # 由 `kubectl scale` 设置,被 `kubectl apply` 命令忽略
# minReadySeconds 被 `kubectl apply` 清除
# ...
selector :
matchLabels :
# ...
app : nginx
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.16.1 # 由 `kubectl apply` 设置
# ...
name : nginx
ports :
- containerPort : 80
# ...
# ...
# ...
# ...
警告: 将 kubectl apply
与指令式对象配置命令 kubectl create
或 kubectl replace
混合使用是不受支持的。这是因为 create
和 replace
命令都不会保留
kubectl apply
用来计算更新内容所使用的
kubectl.kubernetes.io/last-applied-configuration
注解值。
如何删除对象 有两种方法来删除 kubectl apply
管理的对象。
建议操作:kubectl delete -f <文件名>
使用指令式命令来手动删除对象是建议的方法,因为这种方法更为明确地给出了
要删除的内容是什么,且不容易造成用户不小心删除了其他对象的情况。
替代方式:kubectl apply -f <目录名称/> --prune -l your=label
只有在充分理解此命令背后含义的情况下才建议这样操作。
警告: kubectl apply --prune
命令本身仍处于 Alpha 状态,在后续发布版本中可能会
引入一些向后不兼容的变化。
警告: 在使用此命令时必须小心,这样才不会无意中删除不想删除的对象。
作为 kubectl delete
操作的替代方式,你可以在目录中对象配置文件被删除之后,
使用 kubectl apply
来辩识要删除的对象。
带 --prune
标志的 apply
命令会首先查询 API 服务器,获得与某组标签相匹配
的对象列表,之后将返回的现时对象配置与目录中的对象配置文件相比较。
如果某对象在查询中被匹配到,但在目录中没有文件与其相对应,并且其中还包含
last-applied-configuration
注解,则该对象会被删除。
kubectl apply -f <directory/> --prune -l <labels>
警告: 带剪裁(prune)行为的 apply
操作应在包含对象配置文件的目录的根目录运行。
如果在其子目录中运行,可能导致对象被不小心删除。
因为某些对象可能与 -l <标签>
的标签选择算符匹配,但其配置文件不在当前
子目录下。
如何查看对象 你可以使用 kubectl get
并指定 -o yaml
选项来查看现时对象的配置:
kubectl get -f <文件名 | URL> -o yaml
apply 操作是如何计算配置差异并合并变更的? 注意: patch 是一种更新操作,其作用域为对象的一些特定字段而不是整个对象。
这使得你可以更新对象的特定字段集合而不必先要读回对象。
kubectl apply
更新对象的现时配置,它是通过向 API 服务器发送一个 patch 请求
来执行更新动作的。
所提交的补丁中定义了对现时对象配置中特定字段的更新。
kubectl apply
命令会使用当前的配置文件、现时配置以及现时配置中保存的
last-applied-configuration
注解内容来计算补丁更新内容。
合并补丁计算 kubectl apply
命令将配置文件的内容写入到
kubectl.kubernetes.io/last-applied-configuration
注解中。
这些内容用来识别配置文件中已经移除的、因而也需要从现时配置中删除的字段。
用来计算要删除或设置哪些字段的步骤如下:
计算要删除的字段,即在 last-applied-configuration
中存在但在
配置文件中不再存在的字段。 计算要添加或设置的字段,即在配置文件中存在但其取值与现时配置不同的字段。 下面是一个例子。假定此文件是某 Deployment 对象的配置文件:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.16.1 # 更新该镜像
ports :
- containerPort : 80
同时假定同一 Deployment 对象的现时配置如下:
apiVersion : apps/v1
kind : Deployment
metadata :
annotations :
# ...
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
replicas : 2
# ...
minReadySeconds : 5
selector :
matchLabels :
# ...
app : nginx
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.14.2
# ...
name : nginx
ports :
- containerPort : 80
# ...
下面是 kubectl apply
将执行的合并计算:
通过读取 last-applied-configuration
并将其与配置文件中的值相比较,
计算要删除的字段。
对于本地对象配置文件中显式设置为空的字段,清除其在现时配置中的设置,
无论这些字段是否出现在 last-applied-configuration
中。
在此例中,minReadySeconds
出现在 last-applied-configuration
注解中,但
并不存在于配置文件中。
动作: 从现时配置中删除 minReadySeconds
字段。 通过读取配置文件中的值并将其与现时配置相比较,计算要设置的字段。
在这个例子中,配置文件中的 image
值与现时配置中的 image
不匹配。
动作 :设置现时配置中的 image
值。 设置 last-applied-configuration
注解的内容,使之与配置文件匹配。 将第 1、2、3 步骤得出的结果合并,构成向 API 服务器发送的补丁请求内容。 下面是此合并操作之后形成的现时配置:
apiVersion : apps/v1
kind : Deployment
metadata :
annotations :
# ...
# 注解中包含更新后的 image,nginx 1.11.9,
# 但不包含更新后的 replicas
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion":"apps/v1","kind":"Deployment",
"metadata":{"annotations":{},"name":"nginx-deployment","namespace":"default"},
"spec":{"selector":{"matchLabels":{"app":nginx}},"template":{"metadata":{"labels":{"app":"nginx"}},
"spec":{"containers":[{"image":"nginx:1.16.1","name":"nginx",
"ports":[{"containerPort":80}]}]}}}}
# ...
spec :
selector :
matchLabels :
# ...
app : nginx
replicas : 2
# minReadySeconds 此字段被清除
# ...
template :
metadata :
# ...
labels :
app : nginx
spec :
containers :
- image : nginx:1.16.1
# ...
name : nginx
ports :
- containerPort : 80
# ...
# ...
# ...
# ...
不同类型字段的合并方式 配置文件中的特定字段与现时配置合并时,合并方式取决于字段类型。
字段类型有几种:
基本类型 :字段类型为 string
、integer
或 boolean
之一。
例如:image
和 replicas
字段都是基本类型字段。
动作: 替换。
map :也称作 object 。类型为 map
或包含子域的复杂结构。例如,labels
、
annotations
、spec
和 metadata
都是 map。
动作: 合并元素或子字段。
list :包含元素列表的字段,其中每个元素可以是基本类型或 map。
例如,containers
、ports
和 args
都是 list。
动作: 不一定。
当 kubectl apply
更新某个 map 或 list 字段时,它通常不会替换整个字段,而是会
更新其中的各个子元素。例如,当合并 Deployment 的 spec
时,kubectl
并不会
将其整个替换掉。相反,实际操作会是对 replicas
这类 spec
的子字段来执行比较和更新。
合并对基本类型字段的更新 基本类型字段会被替换或清除。
说明: -
表示的是“不适用”,因为指定数值未被使用。
字段在对象配置文件中 字段在现时对象配置中 字段在 last-applied-configuration
中 动作 是 是 - 将配置文件中值设置到现时配置上。 是 否 - 将配置文件中值设置到现时配置上。 否 - 是 从现时配置中移除。 否 - 否 什么也不做。保持现时值。
合并对 map 字段的变更 用来表示映射的字段在合并时会逐个子字段或元素地比较:
说明: -
表示的是“不适用”,因为指定数值未被使用。
键存在于对象配置文件中 键存在于现时对象配置中 键存在于 last-applied-configuration
中 动作 是 是 - 比较子域取值。 是 否 - 将现时配置设置为本地配置值。 否 - 是 从现时配置中删除键。 否 - 否 什么也不做,保留现时值。
合并 list 类型字段的变更 对 list 类型字段的变更合并会使用以下三种策略之一:
如果 list 所有元素都是基本类型则替换整个 list。 如果 list 中元素是复合结构则逐个元素执行合并操作。 合并基本类型元素构成的 list。 策略的选择是基于各个字段做出的。
如果 list 中元素都是基本类型则替换整个 list 将整个 list 视为一个基本类型字段。或者整个替换或者整个删除。
此操作会保持 list 中元素顺序不变
示例: 使用 kubectl apply
来更新 Pod 中 Container 的 args
字段。此操作会
将现时配置中的 args
值设为配置文件中的值。
所有之前添加到现时配置中的 args
元素都会丢失。
配置文件中的 args
元素的顺序在被添加到现时配置中时保持不变。
# last-applied-configuration 值
args : ["a" , "b" ]
# 配置文件值
args : ["a" , "c" ]
# 现时配置
args : ["a" , "b" , "d" ]
# 合并结果
args : ["a" , "c" ]
解释: 合并操作将配置文件中的值当做新的 list 值。
如果 list 中元素为复合类型则逐个执行合并 此操作将 list 视为 map,并将每个元素中的特定字段当做其主键。
逐个元素地执行添加、删除或更新操作。结果顺序无法得到保证。
此合并策略会使用每个字段上的一个名为 patchMergeKey
的特殊标签。
Kubernetes 源代码中为每个字段定义了 patchMergeKey
:
types.go
当合并由 map 组成的 list 时,给定元素中被设置为 patchMergeKey
的字段会被
当做该元素的 map 键值来使用。
例如: 使用 kubectl apply
来更新 Pod 规约中的 containers
字段。
此操作会将 containers
列表视作一个映射来执行合并,每个元素的主键为 name
。
# last-applied-configuration 值
containers :
- name : nginx
image : nginx:1.16
- name : nginx-helper-a # 键 nginx-helper-a 会被删除
image : helper:1.3
- name : nginx-helper-b # 键 nginx-helper-b 会被保留
image : helper:1.3
# 配置文件值
containers :
- name : nginx
image : nginx:1.16
- name : nginx-helper-b
image : helper:1.3
- name : nginx-helper-c # 键 nginx-helper-c 会被添加
image : helper:1.3
# 现时配置
containers :
- name : nginx
image : nginx:1.16
- name : nginx-helper-a
image : helper:1.3
- name : nginx-helper-b
image : helper:1.3
args : ["run" ] # 字段会被保留
- name : nginx-helper-d # 键 nginx-helper-d 会被保留
image : helper:1.3
# 合并结果
containers :
- name : nginx
image : nginx:1.16
# 元素 nginx-helper-a 被删除
- name : nginx-helper-b
image : helper:1.3
args : ["run" ] # 字段被保留
- name : nginx-helper-c # 新增元素
image : helper:1.3
- name : nginx-helper-d # 此元素被忽略(保留)
image : helper:1.3
解释:
名为 "nginx-helper-a" 的容器被删除,因为配置文件中不存在同名的容器。 名为 "nginx-helper-b" 的容器的现时配置中的 args
被保留。
kubectl apply
能够辩识出现时配置中的容器 "nginx-helper-b" 与配置文件
中的容器 "nginx-helper-b" 相同,即使它们的字段值有些不同(配置文件中未给定
args
值)。这是因为 patchMergeKey
字段(name)的值在两个版本中都一样。 名为 "nginx-helper-c" 的容器是新增的,因为在配置文件中的这个容器尚不存在
于现时配置中。 名为 "nginx-helper-d" 的容器被保留下来,因为在 last-applied-configuration
中没有与之同名的元素。 合并基本类型元素 list 在 Kubernetes 1.5 中,尚不支持对由基本类型元素构成的 list 进行合并。
说明: 选择上述哪种策略是由源码中给定字段的
patchStrategy
标记来控制的:
types.go
如果 list 类型字段未设置
patchStrategy
,则整个 list 会被替换掉。
默认字段值 API 服务器会在对象创建时其中某些字段未设置的情况下在现时配置中为其设置默认值。
下面是一个 Deployment 的配置文件。文件未设置 strategy
:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
minReadySeconds : 5
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
使用 kubectl apply
创建对象:
kubectl apply -f https://k8s.io/examples/application/simple_deployment.yaml
使用 kubectl get
打印现时配置:
kubectl get -f https://k8s.io/examples/application/simple_deployment.yaml -o yaml
输出显示 API 在现时配置中为某些字段设置了默认值。
这些字段在配置文件中并未设置。
apiVersion : apps/v1
kind : Deployment
# ...
spec :
selector :
matchLabels :
app : nginx
minReadySeconds : 5
replicas : 1 # API 服务器所设默认值
strategy :
rollingUpdate : # API 服务器基于 strategy.type 所设默认值
maxSurge : 1
maxUnavailable : 1
type : RollingUpdate # API 服务器所设默认值
template :
metadata :
creationTimestamp : null
labels :
app : nginx
spec :
containers :
- image : nginx:1.14.2
imagePullPolicy : IfNotPresent # API 服务器所设默认值
name : nginx
ports :
- containerPort : 80
protocol : TCP # API 服务器所设默认值
resources : {} # API 服务器所设默认值
terminationMessagePath : /dev/termination-log # API 服务器所设默认值
dnsPolicy : ClusterFirst # API 服务器所设默认值
restartPolicy : Always # API 服务器所设默认值
securityContext : {} # API 服务器所设默认值
terminationGracePeriodSeconds : 30 # API 服务器所设默认值
# ...
在补丁请求中,已经设置了默认值的字段不会被重新设回其默认值,除非
在补丁请求中显式地要求清除。对于默认值取决于其他字段的某些字段而言,
这可能会引发一些意想不到的行为。当所依赖的其他字段后来发生改变时,
基于它们所设置的默认值只能在显式执行清除操作时才会被更新。
为此,建议在配置文件中为服务器设置默认值的字段显式提供定义,即使所
给的定义与服务器端默认值设定相同。这样可以使得辩识无法被服务器重新
基于默认值来设置的冲突字段变得容易。
示例:
# last-applied-configuration
spec :
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
# 配置文件
spec :
strategy :
type : Recreate # 更新的值
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
# 现时配置
spec :
strategy :
type : RollingUpdate # 默认设置的值
rollingUpdate : # 基于 type 设置的默认值
maxSurge : 1
maxUnavailable : 1
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
# 合并后的结果 - 出错!
spec :
strategy :
type : Recreate # 更新的值:与 rollingUpdate 不兼容
rollingUpdate : # 默认设置的值:与 "type: Recreate" 冲突
maxSurge : 1
maxUnavailable : 1
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
解释:
用户创建 Deployment,未设置 strategy.type
。 服务器为 strategy.type
设置默认值 RollingUpdate
,并为 strategy.rollingUpdate
设置默认值。 用户改变 strategy.type
为 Recreate
。字段 strategy.rollingUpdate
仍会取其
默认设置值,尽管服务器期望该字段被清除。
如果 strategy.rollingUpdate
值最初于配置文件中定义,则它们需要被清除
这一点就更明确一些。 apply
操作失败,因为 strategy.rollingUpdate
未被清除。
strategy.rollingupdate
在 strategy.type
为 Recreate
不可被设定。建议:以下字段应该在对象配置文件中显式定义:
如 Deployment、StatefulSet、Job、DaemonSet、ReplicaSet 和 ReplicationController
这类负载的选择算符和 PodTemplate
标签 Deployment 的上线策略 如何清除服务器端按默认值设置的字段或者被其他写者设置的字段 没有出现在配置文件中的字段可以通过将其值设置为 null
并应用配置文件来清除。
对于由服务器按默认值设置的字段,清除操作会触发重新为字段设置新的默认值。
如何将字段的属主在配置文件和直接指令式写者之间切换 更改某个对象字段时,应该采用下面的方法:
使用 kubectl apply
. 直接写入到现时配置,但不更改配置文件本身,例如使用 kubectl scale
。 将属主从直接指令式写者更改为配置文件 将字段添加到配置文件。针对该字段,不再直接执行对现时配置的修改。
修改均通过 kubectl apply
来执行。
将属主从配置文件改为直接指令式写者 在 Kubernetes 1.5 中,将字段的属主从配置文件切换到某指令式写者需要手动
执行以下步骤:
从配置文件中删除该字段; 将字段从现时对象的 kubectl.kubernetes.io/last-applied-configuration
注解
中删除。 更改管理方法 Kubernetes 对象在同一时刻应该只用一种方法来管理。
从一种方法切换到另一种方法是可能的,但这一切换是一个手动过程。
说明: 在声明式管理方法中使用指令式命令来删除对象是可以的。
从指令式命令管理切换到声明式对象配置 从指令式命令管理切换到声明式对象配置管理的切换包含以下几个手动步骤:
将现时对象导出到本地配置文件:
kubectl get <kind>/<name> -o yaml > <kind>_<name>.yaml
手动移除配置文件中的 status
字段。
说明: 这一步骤是可选的,因为 kubectl apply
并不会更新 status 字段,即便
配置文件中包含 status 字段。
设置对象上的 kubectl.kubernetes.io/last-applied-configuration
注解:
kubectl replace --save-config -f <kind>_<name>.yaml
更改过程,使用 kubectl apply
专门管理对象。
从指令式对象配置切换到声明式对象配置 在对象上设置 kubectl.kubernetes.io/last-applied-configuration
注解:
kubectl replace -save-config -f <kind>_<name>.yaml
自此排他性地使用 kubectl apply
来管理对象。
定义控制器选择算符和 PodTemplate 标签 警告: 强烈不建议更改控制器上的选择算符。
建议的方法是定义一个不可变更的 PodTemplate 标签,仅用于控制器选择算符且
不包含其他语义性的含义。
示例:
selector :
matchLabels :
controller-selector : "apps/v1/deployment/nginx"
template :
metadata :
labels :
controller-selector : "apps/v1/deployment/nginx"
接下来 4.5.2 - 使用 Kustomize 对 Kubernetes 对象进行声明式管理 Kustomize 是一个独立的工具,用来通过
kustomization 文件
定制 Kubernetes 对象。
从 1.14 版本开始,kubectl
也开始支持使用 kustomization 文件来管理 Kubernetes 对象。
要查看包含 kustomization 文件的目录中的资源,执行下面的命令:
kubectl kustomize <kustomization_directory>
要应用这些资源,使用 --kustomize
或 -k
参数来执行 kubectl apply
:
kubectl apply -k <kustomization_directory>
准备开始 安装 kubectl
。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
Kustomize 概述 Kustomize 是一个用来定制 Kubernetes 配置的工具。它提供以下功能特性来管理应用配置文件:
从其他来源生成资源 为资源设置贯穿性(Cross-Cutting)字段 组织和定制资源集合 生成资源 ConfigMap 和 Secret 包含其他 Kubernetes 对象(如 Pod)所需要的配置或敏感数据。
ConfigMap 或 Secret 中数据的来源往往是集群外部,例如某个 .properties
文件或者 SSH 密钥文件。
Kustomize 提供 secretGenerator
和 configMapGenerator
,可以基于文件或字面值来生成 Secret 和 ConfigMap。
configMapGenerator 要基于文件来生成 ConfigMap,可以在 configMapGenerator
的 files
列表中添加表项。
下面是一个根据 .properties
文件中的数据条目来生成 ConfigMap 的示例:
# 生成一个 application.properties 文件
cat <<EOF >application.properties
FOO=Bar
EOF
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-1
files:
- application.properties
EOF
所生成的 ConfigMap 可以使用下面的命令来检查:
所生成的 ConfigMap 为:
apiVersion : v1
data :
application.properties : |
FOO=Bar
kind : ConfigMap
metadata :
name : example-configmap-1-8mbdf7882g
要从 env 文件生成 ConfigMap,请在 configMapGenerator
中的 envs
列表中添加一个条目。
下面是一个用来自 .env
文件的数据生成 ConfigMap 的例子:
# 创建一个 .env 文件
cat <<EOF >.env
FOO=Bar
EOF
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-1
envs:
- .env
EOF
可以使用以下命令检查生成的 ConfigMap:
生成的 ConfigMap 为:
apiVersion : v1
data :
FOO : Bar
kind : ConfigMap
metadata :
name : example-configmap-1-42cfbf598f
说明: .env
文件中的每个变量在生成的 ConfigMap 中成为一个单独的键。
这与之前的示例不同,前一个示例将一个名为 .properties
的文件(及其所有条目)嵌入到同一个键的值中。
ConfigMap 也可基于字面的键值偶对来生成。要基于键值偶对来生成 ConfigMap,
在 configMapGenerator
的 literals
列表中添加表项。下面是一个例子,
展示如何使用键值偶对中的数据条目来生成 ConfigMap 对象:
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-2
literals:
- FOO=Bar
EOF
可以用下面的命令检查所生成的 ConfigMap:
所生成的 ConfigMap 为:
apiVersion : v1
data :
FOO : Bar
kind : ConfigMap
metadata :
name : example-configmap-2-g2hdhfc6tk
要在 Deployment 中使用生成的 ConfigMap,使用 configMapGenerator 的名称对其进行引用。
Kustomize 将自动使用生成的名称替换该名称。
这是使用生成的 ConfigMap 的 deployment 示例:
# 创建一个 application.properties 文件
cat <<EOF >application.properties
FOO=Bar
EOF
cat <<EOF >deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-app
labels :
app : my-app
spec :
selector :
matchLabels :
app : my-app
template :
metadata :
labels :
app : my-app
spec :
containers :
- name : app
image : my-app
volumeMounts :
- name : config
mountPath : /config
volumes :
- name : config
configMap :
name : example-configmap-1
EOF
cat <<EOF >./kustomization.yaml
resources :
- deployment.yaml
configMapGenerator :
- name : example-configmap-1
files :
- application.properties
EOF
生成 ConfigMap 和 Deployment:
生成的 Deployment 将通过名称引用生成的 ConfigMap:
apiVersion : v1
data :
application.properties : |
FOO=Bar
kind : ConfigMap
metadata :
name : example-configmap-1-g4hk9g2ff8
---
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app : my-app
name : my-app
spec :
selector :
matchLabels :
app : my-app
template :
metadata :
labels :
app : my-app
spec :
containers :
- image : my-app
name : app
volumeMounts :
- mountPath : /config
name : config
volumes :
- configMap :
name : example-configmap-1-g4hk9g2ff8
name : config
secretGenerator 你可以基于文件或者键值偶对来生成 Secret。要使用文件内容来生成 Secret,
在 secretGenerator
下面的 files
列表中添加表项。
下面是一个根据文件中数据来生成 Secret 对象的示例:
# 创建一个 password.txt 文件
cat <<EOF >./password.txt
username=admin
password=secret
EOF
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: example-secret-1
files:
- password.txt
EOF
所生成的 Secret 如下:
apiVersion : v1
data :
password.txt : dXNlcm5hbWU9YWRtaW4KcGFzc3dvcmQ9c2VjcmV0Cg==
kind : Secret
metadata :
name : example-secret-1-t2kt65hgtb
type : Opaque
要基于键值偶对字面值生成 Secret,先要在 secretGenerator
的 literals
列表中添加表项。下面是基于键值偶对中数据条目来生成 Secret 的示例:
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: example-secret-2
literals:
- username=admin
- password=secret
EOF
所生成的 Secret 如下:
apiVersion : v1
data :
password : c2VjcmV0
username : YWRtaW4=
kind : Secret
metadata :
name : example-secret-2-t52t6g96d8
type : Opaque
与 ConfigMap 一样,生成的 Secret 可以通过引用 secretGenerator 的名称在 Deployment 中使用:
# 创建一个 password.txt 文件
cat <<EOF >./password.txt
username=admin
password=secret
EOF
cat <<EOF >deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app
volumeMounts:
- name: password
mountPath: /secrets
volumes:
- name: password
secret:
secretName: example-secret-1
EOF
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
secretGenerator:
- name: example-secret-1
files:
- password.txt
EOF
generatorOptions 所生成的 ConfigMap 和 Secret 都会包含内容哈希值后缀。
这是为了确保内容发生变化时,所生成的是新的 ConfigMap 或 Secret。
要禁止自动添加后缀的行为,用户可以使用 generatorOptions
。
除此以外,为生成的 ConfigMap 和 Secret 指定贯穿性选项也是可以的。
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: example-configmap-3
literals:
- FOO=Bar
generatorOptions:
disableNameSuffixHash: true
labels:
type: generated
annotations:
note: generated
EOF
运行 kubectl kustomize ./
来查看所生成的 ConfigMap:
apiVersion : v1
data :
FOO : Bar
kind : ConfigMap
metadata :
annotations :
note : generated
labels :
type : generated
name : example-configmap-3
设置贯穿性字段 在项目中为所有 Kubernetes 对象设置贯穿性字段是一种常见操作。
贯穿性字段的一些使用场景如下:
为所有资源设置相同的名字空间 为所有对象添加相同的前缀或后缀 为对象添加相同的标签集合 为对象添加相同的注解集合 下面是一个例子:
# 创建一个 deployment.yaml
cat <<EOF >./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
EOF
cat <<EOF >./kustomization.yaml
namespace: my-namespace
namePrefix: dev-
nameSuffix: "-001"
commonLabels:
app: bingo
commonAnnotations:
oncallPager: 800-555-1212
resources:
- deployment.yaml
EOF
执行 kubectl kustomize ./
查看这些字段都被设置到 Deployment 资源上:
apiVersion : apps/v1
kind : Deployment
metadata :
annotations :
oncallPager : 800-555-1212
labels :
app : bingo
name : dev-nginx-deployment-001
namespace : my-namespace
spec :
selector :
matchLabels :
app : bingo
template :
metadata :
annotations :
oncallPager : 800-555-1212
labels :
app : bingo
spec :
containers :
- image : nginx
name : nginx
组织和定制资源 一种常见的做法是在项目中构造资源集合并将其放到同一个文件或目录中管理。
Kustomize 提供基于不同文件来组织资源并向其应用补丁或者其他定制的能力。
组织 Kustomize 支持组合不同的资源。kustomization.yaml
文件的 resources
字段定义配置中要包含的资源列表。
你可以将 resources
列表中的路径设置为资源配置文件的路径。
下面是由 Deployment 和 Service 构成的 NGINX 应用的示例:
# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
EOF
# 创建 service.yaml 文件
cat <<EOF > service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
EOF
# 创建 kustomization.yaml 来组织以上两个资源
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF
kubectl kustomize ./
所得到的资源中既包含 Deployment 也包含 Service 对象。
定制 补丁文件(Patches)可以用来对资源执行不同的定制。
Kustomize 通过 patchesStrategicMerge
和 patchesJson6902
支持不同的打补丁机制。
patchesStrategicMerge
的内容是一个文件路径的列表,其中每个文件都应可解析为
策略性合并补丁(Strategic Merge Patch) 。
补丁文件中的名称必须与已经加载的资源的名称匹配。
建议构造规模较小的、仅做一件事情的补丁。
例如,构造一个补丁来增加 Deployment 的副本个数;构造另外一个补丁来设置内存限制。
# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
EOF
# 生成一个补丁 increase_replicas.yaml
cat <<EOF > increase_replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 3
EOF
# 生成另一个补丁 set_memory.yaml
cat <<EOF > set_memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
template:
spec:
containers:
- name: my-nginx
resources:
limits:
memory: 512Mi
EOF
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
patchesStrategicMerge:
- increase_replicas.yaml
- set_memory.yaml
EOF
执行 kubectl kustomize ./
来查看 Deployment:
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
replicas : 3
selector :
matchLabels :
run : my-nginx
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- image : nginx
name : my-nginx
ports :
- containerPort : 80
resources :
limits :
memory : 512Mi
并非所有资源或者字段都支持策略性合并补丁。为了支持对任何资源的任何字段进行修改,
Kustomize 提供通过 patchesJson6902
来应用 JSON 补丁 的能力。
为了给 JSON 补丁找到正确的资源,需要在 kustomization.yaml
文件中指定资源的组(group)、
版本(version)、类别(kind)和名称(name)。
例如,为某 Deployment 对象增加副本个数的操作也可以通过 patchesJson6902
来完成:
# 创建一个 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
EOF
# 创建一个 JSON 补丁文件
cat <<EOF > patch.yaml
- op: replace
path: /spec/replicas
value: 3
EOF
# 创建一个 kustomization.yaml
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
patchesJson6902:
- target:
group: apps
version: v1
kind: Deployment
name: my-nginx
path: patch.yaml
EOF
执行 kubectl kustomize ./
以查看 replicas
字段被更新:
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
replicas : 3
selector :
matchLabels :
run : my-nginx
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- image : nginx
name : my-nginx
ports :
- containerPort : 80
除了补丁之外,Kustomize 还提供定制容器镜像或者将其他对象的字段值注入到容器中的能力,并且不需要创建补丁。
例如,你可以通过在 kustomization.yaml
文件的 images
字段设置新的镜像来更改容器中使用的镜像。
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
EOF
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
images:
- name: nginx
newName: my.image.registry/nginx
newTag: 1.4.0
EOF
执行 kubectl kustomize ./
以查看所使用的镜像已被更新:
apiVersion : apps/v1
kind : Deployment
metadata :
name : my-nginx
spec :
replicas : 2
selector :
matchLabels :
run : my-nginx
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- image : my.image.registry/nginx:1.4.0
name : my-nginx
ports :
- containerPort : 80
有些时候,Pod 中运行的应用可能需要使用来自其他对象的配置值。
例如,某 Deployment 对象的 Pod 需要从环境变量或命令行参数中读取读取
Service 的名称。
由于在 kustomization.yaml
文件中添加 namePrefix
或 nameSuffix
时
Service 名称可能发生变化,建议不要在命令参数中硬编码 Service 名称。
对于这种使用场景,Kustomize 可以通过 vars
将 Service 名称注入到容器中。
# 创建一个 deployment.yaml 文件(引用此处的文档分隔符)
cat <<'EOF' > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
command: ["start", "--host", "$(MY_SERVICE_NAME)"]
EOF
# 创建一个 service.yaml 文件
cat <<EOF > service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
EOF
cat <<EOF >./kustomization.yaml
namePrefix: dev-
nameSuffix: "-001"
resources:
- deployment.yaml
- service.yaml
vars:
- name: MY_SERVICE_NAME
objref:
kind: Service
name: my-nginx
apiVersion: v1
EOF
执行 kubectl kustomize ./
以查看注入到容器中的 Service 名称是 dev-my-nginx-001
:
apiVersion : apps/v1
kind : Deployment
metadata :
name : dev-my-nginx-001
spec :
replicas : 2
selector :
matchLabels :
run : my-nginx
template :
metadata :
labels :
run : my-nginx
spec :
containers :
- command :
- start
- --host
- dev-my-nginx-001
image : nginx
name : my-nginx
基准(Bases)与覆盖(Overlays) Kustomize 中有 基准(bases) 和 覆盖(overlays) 的概念区分。
基准 是包含 kustomization.yaml
文件的一个目录,其中包含一组资源及其相关的定制。
基准可以是本地目录或者来自远程仓库的目录,只要其中存在 kustomization.yaml
文件即可。
覆盖 也是一个目录,其中包含将其他 kustomization 目录当做 bases
来引用的
kustomization.yaml
文件。
基准 不了解覆盖的存在,且可被多个覆盖所使用。
覆盖则可以有多个基准,且可针对所有基准中的资源执行组织操作,还可以在其上执行定制。
# 创建一个包含基准的目录
mkdir base
# 创建 base/deployment.yaml
cat <<EOF > base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
EOF
# 创建 base/service.yaml 文件
cat <<EOF > base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
EOF
# 创建 base/kustomization.yaml
cat <<EOF > base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF
此基准可在多个覆盖中使用。你可以在不同的覆盖中添加不同的 namePrefix
或其他贯穿性字段。
下面是两个使用同一基准的覆盖:
mkdir dev
cat <<EOF > dev/kustomization.yaml
bases:
- ../base
namePrefix: dev-
EOF
mkdir prod
cat <<EOF > prod/kustomization.yaml
bases:
- ../base
namePrefix: prod-
EOF
如何使用 Kustomize 来应用、查看和删除对象 在 kubectl
命令中使用 --kustomize
或 -k
参数来识别被 kustomization.yaml
所管理的资源。
注意 -k
要指向一个 kustomization 目录。例如:
kubectl apply -k <kustomization 目录>/
假定使用下面的 kustomization.yaml
:
# 创建 deployment.yaml 文件
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
EOF
# 创建 kustomization.yaml
cat <<EOF >./kustomization.yaml
namePrefix: dev-
commonLabels:
app: my-nginx
resources:
- deployment.yaml
EOF
执行下面的命令来应用 Deployment 对象 dev-my-nginx
:
deployment.apps/dev-my-nginx created
运行下面的命令之一来查看 Deployment 对象 dev-my-nginx
:
执行下面的命令来比较 Deployment 对象 dev-my-nginx
与清单被应用之后集群将处于的状态:
执行下面的命令删除 Deployment 对象 dev-my-nginx
:
deployment.apps "dev-my-nginx" deleted
Kustomize 功能特性列表 字段 类型 解释 namespace string 为所有资源添加名字空间 namePrefix string 此字段的值将被添加到所有资源名称前面 nameSuffix string 此字段的值将被添加到所有资源名称后面 commonLabels map[string]string 要添加到所有资源和选择算符的标签 commonAnnotations map[string]string 要添加到所有资源的注解 resources []string 列表中的每个条目都必须能够解析为现有的资源配置文件 configMapGenerator []ConfigMapArgs 列表中的每个条目都会生成一个 ConfigMap secretGenerator []SecretArgs 列表中的每个条目都会生成一个 Secret generatorOptions GeneratorOptions 更改所有 ConfigMap 和 Secret 生成器的行为 bases []string 列表中每个条目都应能解析为一个包含 kustomization.yaml 文件的目录 patchesStrategicMerge []string 列表中每个条目都能解析为某 Kubernetes 对象的策略性合并补丁 patchesJson6902 []Patch 列表中每个条目都能解析为一个 Kubernetes 对象和一个 JSON 补丁 vars []Var 每个条目用来从某资源的字段来析取文字 images []Image 每个条目都用来更改镜像的名称、标记与/或摘要,不必生成补丁 configurations []string 列表中每个条目都应能解析为一个包含 Kustomize 转换器配置 的文件 crds []string 列表中每个条目都应能够解析为 Kubernetes 类别的 OpenAPI 定义文件
接下来 4.5.3 - 使用指令式命令管理 Kubernetes 对象 使用构建在 kubectl
命令行工具中的指令式命令可以直接快速创建、更新和删除
Kubernetes 对象。本文档解释这些命令的组织方式以及如何使用它们来管理活跃对象。
准备开始 安装kubectl
。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
权衡取舍 kubectl
工具能够支持三种对象管理方式:
关于每种对象管理的优缺点的讨论,可参见
Kubernetes 对象管理 。
如何创建对象 kubectl
工具支持动词驱动的命令,用来创建一些最常见的对象类别。
命令的名称设计使得不熟悉 Kubernetes 对象类型的用户也能做出判断。
run
:创建一个新的 Pod 来运行一个容器。expose
:创建一个新的 Service 对象为若干 Pod 提供流量负载均衡。autoscale
:创建一个新的 Autoscaler 对象来自动对某控制器(例如:Deployment)
执行水平扩缩。kubectl
命令也支持一些对象类型驱动的创建命令。
这些命令可以支持更多的对象类别,并且在其动机上体现得更为明显,
不过要求用户了解它们所要创建的对象的类别。
create <对象类别> [<子类别>] <实例名称>
某些对象类别拥有自己的子类别,可以在 create
命令中设置。
例如,Service 对象有 ClusterIP、LoadBalancer 和 NodePort 三种子类别。
下面是一个创建 NodePort 子类别的 Service 的示例:
kubectl create service nodeport <服务名称>
在前述示例中,create service nodeport
命令也称作 create service
命令的子命令。
可以使用 -h
标志找到一个子命令所支持的参数和标志。
kubectl create service nodeport -h
如何更新对象 kubectl
命令也支持一些动词驱动的命令,用来执行一些常见的更新操作。
这些命令的设计是为了让一些不了解 Kubernetes 对象的用户也能执行更新操作,
但不需要了解哪些字段必须设置:
scale
:对某控制器进行水平扩缩以便通过更新控制器的副本个数来添加或删除 Pod。annotate
:为对象添加或删除注解。label
:为对象添加或删除标签。kubectl
命令也支持由对象的某一方面来驱动的更新命令。
设置对象的这一方面可能对不同类别的对象意味着不同的字段:
说明: 在 Kubernetes 1.5 版本中,并非所有动词驱动的命令都有对应的方面驱动的命令。
kubectl
工具支持以下额外的方式用来直接更新活跃对象,不过这些操作要求
用户对 Kubernetes 对象的模式定义有很好的了解:
edit
:通过在编辑器中打开活跃对象的配置,直接编辑其原始配置。patch
:通过使用补丁字符串(Patch String)直接更改某活跃对象的特定字段。
关于补丁字符串的更详细信息,参见
API 约定
的 patch 节。如何删除对象 你可以使用 delete
命令从集群中删除一个对象:
说明: 你可以使用 kubectl delete
来执行指令式命令或者指令式对象配置。不同之处在于传递给命令的参数。
要将 kubectl delete
作为指令式命令使用,将要删除的对象作为参数传递给它。
下面是一个删除名为 nginx
的 Deployment 对象的命令:
kubectl delete deployment/nginx
如何查看对象 用来打印对象信息的命令有好几个:
get
:打印匹配到的对象的基本信息。使用 get -h
可以查看选项列表。describe
:打印匹配到的对象的详细信息的汇集版本。logs
:打印 Pod 中运行的容器的 stdout 和 stderr 输出。使用 set
命令在创建对象之前修改对象 有些对象字段在 create
命令中没有对应的标志。
在这些场景中,你可以使用 set
和 create
命令的组合来在对象创建之前设置字段值。
这是通过将 create
命令的输出用管道方式传递给 set
命令来实现的,最后执行 create
命令来创建对象。
下面是一个例子:
kubectl create service clusterip my-svc --clusterip= "None" -o yaml --dry-run= client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
命令 kubectl create service -o yaml --dry-run=client
创建 Service 的配置,
但将其以 YAML 格式在标准输出上打印而不是发送给 API 服务器。 命令 kubectl set selector --local -f - -o yaml
从标准输入读入配置,
并将更新后的配置以 YAML 格式输出到标准输出。 命令 kubectl create -f -
使用标准输入上获得的配置创建对象。 在创建之前使用 --edit
更改对象 你可以用 kubectl create --edit
来在对象被创建之前执行任意的变更。
下面是一个例子:
kubectl create service clusterip my-svc --clusterip= "None" -o yaml --dry-run= client > /tmp/srv.yaml
kubectl create --edit -f /tmp/srv.yaml
命令 kubectl create service
创建 Service 的配置并将其保存到 /tmp/srv.yaml
文件。 命令 kubectl create --edit
在创建 Service 对象打开其配置文件进行编辑。 接下来 4.5.4 - 使用配置文件对 Kubernetes 对象进行命令式管理 可以使用 kubectl
命令行工具以及用 YAML 或 JSON 编写的对象配置文件来创建、更新和删除 Kubernetes 对象。
本文档说明了如何使用配置文件定义和管理对象。
准备开始 安装 kubectl
。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
权衡 kubectl
工具支持三种对象管理:
参看 Kubernetes 对象管理
中关于每种对象管理的优缺点的讨论。
如何创建对象 你可以使用 kubectl create -f
从配置文件创建一个对象。
请参考 kubernetes API 参考 有关详细信息。
kubectl create -f <filename|url>
如何更新对象 警告: 使用 replace
命令更新对象会删除所有未在配置文件中指定的规范的某些部分。
不应将其规范由集群部分管理的对象使用,比如类型为 LoadBalancer
的服务,
其中 externalIPs
字段独立于配置文件进行管理。
必须将独立管理的字段复制到配置文件中,以防止 replace
删除它们。
你可以使用 kubectl replace -f
根据配置文件更新活动对象。
kubectl replace -f <filename|url>
如何删除对象 你可以使用 kubectl delete -f
删除配置文件中描述的对象。
kubectl delete -f <filename|url>
说明: 如果配置文件在 metadata
节中设置了 generateName
字段而非 name
字段,
你无法使用 kubectl delete -f <filename|url>
来删除该对象。
你必须使用其他标志才能删除对象。例如:
kubectl delete <type> <name>
kubectl delete <type> -l <label>
如何查看对象 你可以使用 kubectl get -f
查看有关配置文件中描述的对象的信息。
kubectl get -f <filename|url> -o yaml
-o yaml
标志指定打印完整的对象配置。
使用 kubectl get -h
查看选项列表。
局限性 当完全定义每个对象的配置并将其记录在其配置文件中时,create
、 replace
和delete
命令会很好的工作。
但是,当更新一个活动对象,并且更新没有合并到其配置文件中时,下一次执行 replace
时,更新将丢失。
如果控制器,例如 HorizontalPodAutoscaler ,直接对活动对象进行更新,则会发生这种情况。
这有一个例子:
从配置文件创建一个对象。 另一个源通过更改某些字段来更新对象。 从配置文件中替换对象。在步骤2中所做的其他源的更改将丢失。 如果需要支持同一对象的多个编写器,则可以使用 kubectl apply
来管理该对象。
从 URL 创建和编辑对象而不保存配置 假设你具有对象配置文件的 URL。
你可以在创建对象之前使用 kubectl create --edit
对配置进行更改。
这对于指向可以由读者修改的配置文件的教程和任务特别有用。
kubectl create -f <url> --edit
从命令式命令迁移到命令式对象配置 从命令式命令迁移到命令式对象配置涉及几个手动步骤。
将活动对象导出到本地对象配置文件:
kubectl get <kind>/<name> -o yaml > <kind>_<name>.yaml
从对象配置文件中手动删除状态字段。 对于后续的对象管理,只能使用 replace
。
kubectl replace -f <kind>_<name>.yaml
定义控制器选择器和 PodTemplate 标签 警告: 不建议在控制器上更新选择器。
推荐的方法是定义单个不变的 PodTemplate 标签,该标签仅由控制器选择器使用,而没有其他语义。
标签示例:
selector :
matchLabels :
controller-selector : "apps/v1/deployment/nginx"
template :
metadata :
labels :
controller-selector : "apps/v1/deployment/nginx"
接下来 4.5.5 - 使用 kubectl patch 更新 API 对象 使用 kubectl patch 更新 Kubernetes API 对象。做一个策略性的合并 patch 或 JSON 合并 patch。
这个任务展示如何使用 kubectl patch
就地更新 API 对象。
这个任务中的练习演示了一个策略性合并 patch 和一个 JSON 合并 patch。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
使用策略合并 patch 更新 Deployment 下面是具有两个副本的 Deployment 的配置文件。每个副本是一个 Pod,有一个容器:
apiVersion : apps/v1
kind : Deployment
metadata :
name : patch-demo
spec :
replicas : 2
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : patch-demo-ctr
image : nginx
tolerations :
- effect : NoSchedule
key : dedicated
value : test-team
创建 Deployment:
kubectl apply -f https://k8s.io/examples/application/deployment-patch.yaml
查看与 Deployment 相关的 Pod:
输出显示 Deployment 有两个 Pod。1/1
表示每个 Pod 有一个容器:
NAME READY STATUS RESTARTS AGE
patch-demo-28633765-670qr 1/1 Running 0 23s
patch-demo-28633765-j5qs3 1/1 Running 0 23s
把运行的 Pod 的名字记下来。稍后,你将看到这些 Pod 被终止并被新的 Pod 替换。
此时,每个 Pod 都有一个运行 nginx 镜像的容器。现在假设你希望每个 Pod 有两个容器:一个运行 nginx,另一个运行 redis。
创建一个名为 patch-file.yaml
的文件。内容如下:
spec :
template :
spec :
containers :
- name : patch-demo-ctr-2
image : redis
修补你的 Deployment:
kubectl patch deployment patch-demo --patch-file patch-file.yaml
查看修补后的 Deployment:
kubectl get deployment patch-demo --output yaml
输出显示 Deployment 中的 PodSpec 有两个容器:
containers :
- image : redis
imagePullPolicy : Always
name : patch-demo-ctr-2
...
- image : nginx
imagePullPolicy : Always
name : patch-demo-ctr
...
查看与 patch Deployment 相关的 Pod:
输出显示正在运行的 Pod 与以前运行的 Pod 有不同的名称。Deployment 终止了旧的 Pod,
并创建了两个符合更新后的 Deployment 规约的新 Pod。2/2
表示每个 Pod 有两个容器:
NAME READY STATUS RESTARTS AGE
patch-demo-1081991389-2wrn5 2/2 Running 0 1m
patch-demo-1081991389-jmg7b 2/2 Running 0 1m
仔细查看其中一个 patch-demo Pod:
kubectl get pod <your-pod-name> --output yaml
输出显示 Pod 有两个容器:一个运行 nginx,一个运行 redis:
containers:
- image: redis
...
- image: nginx
...
策略性合并类的 patch 的说明 你在前面的练习中所做的 patch 称为 策略性合并 patch(Strategic Merge Patch)
。
请注意,patch 没有替换 containers
列表。相反,它向列表中添加了一个新 Container。换句话说,
patch 中的列表与现有列表合并。当你在列表中使用策略性合并 patch 时,并不总是这样。
在某些情况下,列表是替换的,而不是合并的。
对于策略性合并 patch,列表可以根据其 patch 策略进行替换或合并。
patch 策略由 Kubernetes 源代码中字段标记中的 patchStrategy
键的值指定。
例如,PodSpec
结构体的 Containers
字段的 patchStrategy
为 merge
:
type PodSpec struct {
...
Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" ...`
...
}
你还可以在
OpenApi 规范 中看到
patch 策略:
"io.k8s.api.core.v1.PodSpec": {
...,
"containers": {
"description": "List of containers belonging to the pod. ...."
},
"x-kubernetes-patch-merge-key": "name" ,
"x-kubernetes-patch-strategy": "merge"
}
你可以在 Kubernetes API 文档
中看到 patch 策略。
创建一个名为 patch-file-tolerations.yaml
的文件。内容如下:
spec :
template :
spec :
tolerations :
- effect : NoSchedule
key : disktype
value : ssd
对 Deployment 执行 patch 操作:
kubectl patch deployment patch-demo --patch-file patch-file-tolerations.yaml
查看修补后的 Deployment:
kubectl get deployment patch-demo --output yaml
输出结果显示 Deployment 中的 PodSpec 只有一个容忍度设置:
tolerations :
- effect : NoSchedule
key : disktype
value : ssd
请注意,PodSpec 中的 tolerations
列表被替换,而不是合并。这是因为 PodSpec 的 tolerations
的字段标签中没有 patchStrategy
键。所以策略合并 patch 操作使用默认的 patch 策略,也就是 replace
。
type PodSpec struct {
...
Tolerations []Toleration `json:"tolerations,omitempty" protobuf:"bytes,22,opt,name=tolerations"`
...
}
使用 JSON 合并 patch 更新 Deployment 策略性合并 patch 不同于 JSON 合并 patch 。
使用 JSON 合并 patch,如果你想更新列表,你必须指定整个新列表。新的列表完全取代现有的列表。
kubectl patch
命令有一个 type
参数,你可以将其设置为以下值之一:
有关 JSON patch 和 JSON 合并 patch 的比较,查看
JSON patch 和 JSON 合并 patch 。
type
参数的默认值是 strategic
。在前面的练习中,我们做了一个策略性的合并 patch。
下一步,在相同的 Deployment 上执行 JSON 合并 patch。创建一个名为 patch-file-2
的文件。内容如下:
spec :
template :
spec :
containers :
- name : patch-demo-ctr-3
image : gcr.io/google-samples/node-hello:1.0
在 patch 命令中,将 type
设置为 merge
:
kubectl patch deployment patch-demo --type merge --patch-file patch-file-2.yaml
查看修补后的 Deployment:
kubectl get deployment patch-demo --output yaml
patch 中指定的 containers
列表只有一个 Container。
输出显示你所给出的 Contaier 列表替换了现有的 containers
列表。
spec :
containers :
- image : gcr.io/google-samples/node-hello:1.0
...
name : patch-demo-ctr-3
列出正运行的 Pod:
在输出中,你可以看到已经终止了现有的 Pod,并创建了新的 Pod。1/1
表示每个新 Pod 只运行一个容器。
NAME READY STATUS RESTARTS AGE
patch-demo-1307768864-69308 1/1 Running 0 1m
patch-demo-1307768864-c86dc 1/1 Running 0 1m
使用带 retainKeys 策略的策略合并 patch 更新 Deployment apiVersion : apps/v1
kind : Deployment
metadata :
name : retainkeys-demo
spec :
selector :
matchLabels :
app : nginx
strategy :
rollingUpdate :
maxSurge : 30 %
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : retainkeys-demo-ctr
image : nginx
创建 Deployment:
kubectl apply -f https://k8s.io/examples/application/deployment-retainkeys.yaml
这时,Deployment 被创建,并使用 RollingUpdate
策略。
创建一个名为 patch-file-no-retainkeys.yaml
的文件,内容如下:
spec :
strategy :
type : Recreate
修补你的 Deployment:
kubectl patch deployment retainkeys-demo --type strategic --patch-file patch-file-no-retainkeys.yaml
在输出中,你可以看到,当 spec.strategy.rollingUpdate
已经拥有取值定义时,
将其 type
设置为 Recreate
是不可能的。
The Deployment "retainkeys-demo" is invalid: spec.strategy.rollingUpdate: Forbidden: may not be specified when strategy `type` is 'Recreate'
更新 type
取值的同时移除 spec.strategy.rollingUpdate
现有值的方法是为策略性合并操作设置 retainKeys
策略:
创建另一个名为 patch-file-retainkeys.yaml
的文件,内容如下:
spec :
strategy :
$retainKeys :
- type
type : Recreate
使用此 patch,我们表达了希望只保留 strategy
对象的 type
键。
这样,在 patch 操作期间 rollingUpdate
会被删除。
使用新的 patch 重新修补 Deployment:
kubectl patch deployment retainkeys-demo --type merge --patch-file patch-file-retainkeys.yaml
检查 Deployment 的内容:
kubectl get deployment retainkeys-demo --output yaml
输出显示 Deployment 中的 strategy
对象不再包含 rollingUpdate
键:
spec :
strategy :
type : Recreate
template :
关于使用 retainKeys 策略的策略合并 patch 操作的说明 在前文练习中所执行的称作 带 retainKeys
策略的策略合并 patch(Strategic Merge
Patch with retainKeys Strategy) 。
这种方法引入了一种新的 $retainKey
指令,具有如下策略:
其中包含一个字符串列表; 所有需要被保留的字段必须在 $retainKeys
列表中给出; 对于已有的字段,会和对象上对应的内容合并; 在修补操作期间,未找到的字段都会被清除; 列表 $retainKeys
中的所有字段必须 patch 操作所给字段的超集,或者与之完全一致。 策略 retainKeys
并不能对所有对象都起作用。它仅对那些 Kubernetes 源码中
patchStrategy
字段标志值包含 retainKeys
的字段有用。
例如 DeploymentSpec
结构的 Strategy
字段就包含了 patchStrategy
为
retainKeys
的标志。
type DeploymentSpec struct {
...
// +patchStrategy=retainKeys
Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" ...`
...
}
你也可以查看
OpenAPI 规范 中的
retainKeys
策略:
"io.k8s.api.apps.v1.DeploymentSpec": {
...,
"strategy": {
"$ref": "#/definitions/io.k8s.api.apps.v1.DeploymentStrategy" ,
"description": "The deployment strategy to use to replace existing pods with new ones." ,
"x-kubernetes-patch-strategy": "retainKeys"
},
....
}
而且你也可以在
Kubernetes API 文档 中看到
retainKey
策略。
kubectl patch
命令使用 YAML 或 JSON。它可以接受以文件形式提供的补丁,也可以接受直接在命令行中给出的补丁。
创建一个文件名称是 patch-file.json
内容如下:
{
"spec" : {
"template" : {
"spec" : {
"containers" : [
{
"name" : "patch-demo-ctr-2" ,
"image" : "redis"
}
]
}
}
}
}
以下命令是等价的:
kubectl patch deployment patch-demo --patch-file patch-file.yaml
kubectl patch deployment patch-demo --patch 'spec:\n template:\n spec:\n containers:\n - name: patch-demo-ctr-2\n image: redis'
kubectl patch deployment patch-demo --patch-file patch-file.json
kubectl patch deployment patch-demo --patch '{"spec": {"template": {"spec": {"containers": [{"name": "patch-demo-ctr-2","image": "redis"}]}}}}'
使用 kubectl patch
和 --subresource
更新一个对象的副本数 特性状态: Kubernetes v1.24 [alpha]
使用 kubectl 命令(如 get、patch、edit 和 replace)时带上 --subresource=[subresource-name]
标志,
可以获取和更新资源的 status
和 scale
子资源(适用于 kubectl v1.24 或更高版本)。
这个标志可用于带有 status
或 scale
子资源的所有 API 资源 (内置资源和 CR 资源)。
Deployment 是支持这些子资源的其中一个例子。
下面是有两个副本的 Deployment 的清单。
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
创建 Deployment:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
查看与 Deployment 关联的 Pod:
kubectl get pods -l app = nginx
在输出中,你可以看到此 Deployment 有两个 Pod。例如:
NAME READY STATUS RESTARTS AGE
nginx-deployment-7fb96c846b-22567 1/1 Running 0 47s
nginx-deployment-7fb96c846b-mlgns 1/1 Running 0 47s
现在用 --subresource=[subresource-name]
标志修补此 Deployment:
kubectl patch deployment nginx-deployment --subresource= 'scale' --type= 'merge' -p '{"spec":{"replicas":3}}'
输出为:
scale.autoscaling/nginx-deployment patched
查看与你所修补的 Deployment 关联的 Pod:
kubectl get pods -l app = nginx
在输出中,你可以看到一个新的 Pod 被创建,因此现在你有 3 个正在运行的 Pod。
NAME READY STATUS RESTARTS AGE
nginx-deployment-7fb96c846b-22567 1/1 Running 0 107s
nginx-deployment-7fb96c846b-lxfr2 1/1 Running 0 14s
nginx-deployment-7fb96c846b-mlgns 1/1 Running 0 107s
查看所修补的 Deployment:
kubectl get deployment nginx-deployment -o yaml
...
spec :
replicas : 3
...
status :
...
availableReplicas : 3
readyReplicas : 3
replicas : 3
说明: 如果你运行 kubectl patch
并指定 --subresource
标志时,所针对的是不支持特定子资源的资源,
则 API 服务器会返回一个 404 Not Found 错误。
总结 在本练习中,你使用 kubectl patch
更改了 Deployment 对象的当前配置。
你没有更改最初用于创建 Deployment 对象的配置文件。
用于更新 API 对象的其他命令包括
kubectl annotate
、
kubectl edit
、
kubectl replace
、
kubectl scale
和
kubectl apply
。
接下来 4.6 - 管理 Secrets 使用 Secrets 管理机密配置数据。
4.6.1 - 使用 kubectl 管理 Secret 使用 kubectl 命令行创建 Secret 对象。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
创建 Secret 一个 Secret
可以包含 Pod 访问数据库所需的用户凭证。
例如,由用户名和密码组成的数据库连接字符串。
你可以在本地计算机上,将用户名存储在文件 ./username.txt
中,将密码存储在文件 ./password.txt
中。
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt
在这些命令中,-n
标志确保生成的文件在文本末尾不包含额外的换行符。
这一点很重要,因为当 kubectl
读取文件并将内容编码为 base64 字符串时,多余的换行符也会被编码。
kubectl create secret
命令将这些文件打包成一个 Secret 并在 API 服务器上创建对象。
kubectl create secret generic db-user-pass \
--from-file= ./username.txt \
--from-file= ./password.txt
输出类似于:
secret/db-user-pass created
默认密钥名称是文件名。 你可以选择使用 --from-file=[key=]source
来设置密钥名称。例如:
kubectl create secret generic db-user-pass \
--from-file= username = ./username.txt \
--from-file= password = ./password.txt
你不需要对文件中包含的密码字符串中的特殊字符进行转义。
你还可以使用 --from-literal=<key>=<value>
标签提供 Secret 数据。
可以多次使用此标签,提供多个键值对。
请注意,特殊字符(例如:$
,\
,*
,=
和 !
)由你的 shell
解释执行,而且需要转义。
在大多数 shell 中,转义密码最简便的方法是用单引号括起来。
比如,如果你的密码是 S!B\*d$zDsb=
,
可以像下面一样执行命令:
kubectl create secret generic db-user-pass \
--from-literal= username = devuser \
--from-literal= password = 'S!B\*d$zDsb='
验证 Secret 检查 secret 是否已创建:
输出类似于:
NAME TYPE DATA AGE
db-user-pass Opaque 2 51s
你可以查看 Secret
的描述:
kubectl describe secrets/db-user-pass
输出类似于:
Name: db-user-pass
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 12 bytes
username: 5 bytes
kubectl get
和 kubectl describe
命令默认不显示 Secret
的内容。
这是为了防止 Secret
被意外暴露或存储在终端日志中。
查看编码数据的实际内容,请参考解码 Secret 。
解码 Secret 要查看创建的 Secret 的内容,运行以下命令:
kubectl get secret db-user-pass -o jsonpath = '{.data}'
输出类似于:
{"password" :"MWYyZDFlMmU2N2Rm" ,"username" :"YWRtaW4=" }
现在你可以解码 password
的数据:
# 这是一个用于文档说明的示例。
# 如果你这样做,数据 'MWYyZDFlMmU2N2Rm' 可以存储在你的 shell 历史中。
# 可以进入你电脑的人可以找到那个记住的命令并可以在你不知情的情况下 base-64 解码这个 Secret。
# 通常最好将这些步骤结合起来,如页面后面所示。
echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
输出类似于:
1f2d1e2e67df
为了避免在 shell 历史记录中存储 Secret 的编码值,可以执行如下命令:
kubectl get secret db-user-pass -o jsonpath = '{.data.password}' | base64 --decode
输出应与上述类似。
清理 删除创建的 Secret:
kubectl delete secret db-user-pass
接下来 4.6.2 - 使用配置文件管理 Secret 使用资源配置文件创建 Secret 对象。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
创建 Secret 你可以先用 JSON 或 YAML 格式在一个清单文件中定义 Secret
对象,然后创建该对象。
Secret
资源包含 2 个键值对:data
和 stringData
。
data
字段用来存储 base64 编码的任意数据。
提供 stringData
字段是为了方便,它允许 Secret 使用未编码的字符串。
data
和 stringData
的键必须由字母、数字、-
、_
或 .
组成。
以下示例使用 data
字段在 Secret 中存储两个字符串:
将这些字符串转换为 base64:
echo -n 'admin' | base64
echo -n '1f2d1e2e67df' | base64
说明: Secret 数据的 JSON 和 YAML 序列化结果是以 base64 编码的。
换行符在这些字符串中无效,必须省略。
在 Darwin/macOS 上使用 base64
工具时,用户不应该使用 -b
选项分割长行。
相反地,Linux 用户应该 在 base64
地命令中添加 -w 0
选项,
或者在 -w
选项不可用的情况下,输入 base64 | tr -d '\n'
。
输出类似于:
YWRtaW4=
MWYyZDFlMmU2N2Rm
创建清单:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
data :
username : YWRtaW4=
password : MWYyZDFlMmU2N2Rm
注意,Secret 对象的名称必须是有效的 DNS 子域名 。
使用 kubectl apply
创建 Secret:
kubectl apply -f ./secret.yaml
输出类似于:
secret/mysecret created
若要验证 Secret 被创建以及想要解码 Secret 数据,
请参阅使用 kubectl 管理 Secret
创建 Secret 时提供未编码的数据 对于某些场景,你可能希望使用 stringData
字段。
这个字段可以将一个非 base64 编码的字符串直接放入 Secret 中,
当创建或更新该 Secret 时,此字段将被编码。
上述用例的实际场景可能是这样:当你部署应用时,使用 Secret 存储配置文件,
你希望在部署过程中,填入部分内容到该配置文件。
例如,如果你的应用程序使用以下配置文件:
apiUrl : "https://my.api.com/api/v1"
username : "<user>"
password : "<password>"
你可以使用以下定义将其存储在 Secret 中:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
stringData :
config.yaml : |
apiUrl: "https://my.api.com/api/v1"
username: <user>
password: <password>
当你检索 Secret 数据时,此命令将返回编码的值,并不是你在 stringData
中提供的纯文本值。
例如,如果你运行以下命令:
kubectl get secret mysecret -o yaml
输出类似于:
apiVersion : v1
data :
config.yaml : YXBpVXJsOiAiaHR0cHM6Ly9teS5hcGkuY29tL2FwaS92MSIKdXNlcm5hbWU6IHt7dXNlcm5hbWV9fQpwYXNzd29yZDoge3twYXNzd29yZH19
kind : Secret
metadata :
creationTimestamp : 2018-11-15T20:40:59Z
name : mysecret
namespace : default
resourceVersion : "7225"
uid : c280ad2e-e916-11e8-98f2-025000000001
type :
同时指定 data
和 stringData
如果你在 data
和 stringData
中设置了同一个字段,则使用来自 stringData
中的值。
例如,如果你定义以下 Secret:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
data :
username : YWRtaW4=
stringData :
username : administrator
所创建的 Secret
对象如下:
apiVersion : v1
data :
username : YWRtaW5pc3RyYXRvcg==
kind : Secret
metadata :
creationTimestamp : 2018-11-15T20:46:46Z
name : mysecret
namespace : default
resourceVersion : "7579"
uid : 91460ecb-e917-11e8-98f2-025000000001
type : Opaque
YWRtaW5pc3RyYXRvcg==
解码成 administrator
。
编辑 Secret 要编辑使用清单创建的 Secret 中的数据,请修改清单中的 data
或 stringData
字段并将此清单文件应用到集群。
你可以编辑现有的 Secret
对象,除非它是不可变的 。
例如,如果你想将上一个示例中的密码更改为 birdsarentreal
,请执行以下操作:
编码新密码字符串:
echo -n 'birdsarentreal' | base64
输出类似于:
YmlyZHNhcmVudHJlYWw=
使用你的新密码字符串更新 data
字段:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
data :
username : YWRtaW4=
password : YmlyZHNhcmVudHJlYWw=
将清单应用到你的集群:
kubectl apply -f ./secret.yaml
输出类似于:
secret/mysecret configured
Kubernetes 更新现有的 Secret
对象。具体而言,kubectl
工具发现存在一个同名的现有 Secret
对象。
kubectl
获取现有对象,计划对其进行更改,并将更改后的 Secret
对象提交到你的集群控制平面。
如果你指定了 kubectl apply --server-side
,则 kubectl
使用服务器端应用(Server-Side Apply) 。
清理 删除你创建的 Secret:
kubectl delete secret mysecret
接下来 4.6.3 - 使用 Kustomize 管理 Secret 使用 kustomization.yaml 文件创建 Secret 对象。
kubectl
支持使用 Kustomize 对象管理工具 来管理
Secret 和 ConfigMap。你可以使用 Kustomize 创建资源生成器(Resource Generator) ,
该生成器会生成一个 Secret,让你能够通过 kubectl
应用到 API 服务器。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
创建 Secret 你可以在 kustomization.yaml
文件中定义 secreteGenerator
字段,
并在定义中引用其它本地文件、.env
文件或文字值生成 Secret。
例如:下面的指令为用户名 admin
和密码 1f2d1e2e67df
创建 Kustomization 文件。
创建 Kustomization 文件
secretGenerator :
- name : database-creds
literals :
- username=admin
- password=1f2d1e2e67df
用 base64 编码的值存储凭据到文件中:
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txt
-n
标志确保文件结尾处没有换行符。
创建 kustomization.yaml
文件:
secretGenerator :
- name : database-creds
files :
- username.txt
- password.txt
你也可以使用 .env
文件在 kustomization.yaml
中定义 secretGenerator
。
例如下面的 kustomization.yaml
文件从 .env.secret
文件获取数据:
secretGenerator :
- name : db-user-pass
envs :
- .env.secret
在所有情况下,你都不需要对取值作 base64 编码。
YAML 文件的名称必须 是 kustomization.yaml
或 kustomization.yml
。
应用 kustomization 文件 若要创建 Secret,应用包含 kustomization 文件的目录。
输出类似于:
secret/database-creds-5hdh7hhgfk created
生成 Secret 时,Secret 的名称最终是由 name
字段和数据的哈希值拼接而成。
这将保证每次修改数据时生成一个新的 Secret。
要验证 Secret 是否已创建并解码 Secret 数据,
请参阅使用 kubectl 管理 Secret 。
编辑 Secret 在 kustomization.yaml
文件中,修改诸如 password
等数据。 应用包含 kustomization 文件的目录: kubectl apply -k <directory-path>
输出类似于:
secret/db-user-pass-6f24b56cc8 created
编辑过的 Secret 被创建为一个新的 Secret
对象,而不是更新现有的 Secret
对象。
你可能需要在 Pod 中更新对该 Secret 的引用。
清理 要删除 Secret,请使用 kubectl
:
kubectl delete secret db-user-pass-96mffmfh4k
接下来 4.7 - 给应用注入数据 给你的工作负载 Pod 指定配置和其他数据。
4.7.1 - 为容器设置启动时要执行的命令和参数 本页将展示如何为 Pod
中容器设置启动时要执行的命令及其参数。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建 Pod 时设置命令及参数 创建 Pod 时,可以为其下的容器设置启动时要执行的命令及其参数。如果要设置命令,就填写在配置文件的
command
字段下,如果要设置命令的参数,就填写在配置文件的 args
字段下。
一旦 Pod 创建完成,该命令及其参数就无法再进行更改了。
如果在配置文件中设置了容器启动时要执行的命令及其参数,那么容器镜像中自带的命令与参数将会被覆盖而不再执行。
如果配置文件中只是设置了参数,却没有设置其对应的命令,那么容器镜像中自带的命令会使用该新参数作为其执行时的参数。
说明: 在有些容器运行时中,command
字段对应 entrypoint
,请参阅下面的说明事项 。
本示例中,将创建一个只包含单个容器的 Pod。在此 Pod 配置文件中设置了一个命令与两个参数:
apiVersion : v1
kind : Pod
metadata :
name : command-demo
labels :
purpose : demonstrate-command
spec :
containers :
- name : command-demo-container
image : debian
command : ["printenv" ]
args : ["HOSTNAME" , "KUBERNETES_PORT" ]
restartPolicy : OnFailure
基于 YAML 文件创建一个 Pod:
kubectl apply -f https://k8s.io/examples/pods/commands.yaml
获取正在运行的 Pod:
查询结果显示在 command-demo 这个 Pod 下运行的容器已经启动完成。
如果要获取容器启动时执行命令的输出结果,可以通过 Pod 的日志进行查看:
kubectl logs command-demo
日志中显示了 HOSTNAME 与 KUBERNETES_PORT 这两个环境变量的值:
command-demo
tcp://10.3.240.1:443
使用环境变量来设置参数 在上面的示例中,我们直接将一串字符作为命令的参数。除此之外,我们还可以将环境变量作为命令的参数。
env :
- name : MESSAGE
value : "hello world"
command : ["/bin/echo" ]
args : ["$(MESSAGE)" ]
这意味着你可以将那些用来设置环境变量的方法应用于设置命令的参数,其中包括了
ConfigMap 与
Secret 。
说明: 环境变量需要加上括号,类似于 "$(VAR)"
。这是在 command
或 args
字段使用变量的格式要求。
在 Shell 来执行命令 有时候,你需要在 Shell 脚本中运行命令。
例如,你要执行的命令可能由多个命令组合而成,或者它就是一个 Shell 脚本。
这时,就可以通过如下方式在 Shell 中执行命令:
command: [ "/bin/sh" ]
args: [ "-c" , "while true; do echo hello; sleep 10;done" ]
接下来 4.7.2 - 定义相互依赖的环境变量 本页展示了如何为 Kubernetes Pod 中的容器定义相互依赖的环境变量。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
为容器定义相互依赖的环境变量 当创建一个 Pod 时,你可以为运行在 Pod 中的容器设置相互依赖的环境变量。
若要设置相互依赖的环境变量,你可以在配置清单文件的 env
的 value
中使用 $(VAR_NAME)。
在本练习中,你会创建一个单容器的 Pod。
此 Pod 的配置文件定义了一个已定义常用用法的相互依赖的环境变量。
下面是此 Pod 的配置清单:
apiVersion : v1
kind : Pod
metadata :
name : dependent-envars-demo
spec :
containers :
- name : dependent-envars-demo
args :
- while true; do echo -en '\n'; printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n'; sleep 30; done;
command :
- sh
- -c
image : busybox:1.28
env :
- name : SERVICE_PORT
value : "80"
- name : SERVICE_IP
value : "172.17.0.1"
- name : UNCHANGED_REFERENCE
value : "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
- name : PROTOCOL
value : "https"
- name : SERVICE_ADDRESS
value : "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
- name : ESCAPED_REFERENCE
value : "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
依据清单创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dependent-envars.yaml
pod/dependent-envars-demo created
列出运行的 Pod:
kubectl get pods dependent-envars-demo
NAME READY STATUS RESTARTS AGE
dependent-envars-demo 1/1 Running 0 9s
检查 Pod 中运行容器的日志:
kubectl logs pod/dependent-envars-demo
UNCHANGED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
SERVICE_ADDRESS=https://172.17.0.1:80
ESCAPED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
如上所示,你已经定义了 SERVICE_ADDRESS
的正确依赖引用,
UNCHANGED_REFERENCE
的错误依赖引用,
并跳过了 ESCAPED_REFERENCE
的依赖引用。
如果环境变量被引用时已事先定义,则引用可以正确解析,
比如 SERVICE_ADDRESS
的例子。
请注意,env
列表中的顺序很重要。如果某环境变量定义出现在列表的尾部,
则在解析列表前部环境变量时不会视其为“已被定义”。
这就是为什么 UNCHANGED_REFERENCE
在上面的示例中解析 $(PROTOCOL)
失败的原因。
当环境变量未定义或仅包含部分变量时,未定义的变量会被当做普通字符串对待,
比如 UNCHANGED_REFERENCE
的例子。
注意,解析不正确的环境变量通常不会阻止容器启动。
$(VAR_NAME)
这样的语法可以用两个 $
转义,即:$$(VAR_NAME)
。
无论引用的变量是否定义,转义的引用永远不会展开。
这一点可以从上面 ESCAPED_REFERENCE
的例子得到印证。
接下来 4.7.3 - 为容器设置环境变量 本页将展示如何为 Kubernetes Pod 下的容器设置环境变量。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
为容器设置一个环境变量 创建 Pod 时,可以为其下的容器设置环境变量。通过配置文件的 env
或者 envFrom
字段来设置环境变量。
本示例中,将创建一个只包含单个容器的 Pod。此 Pod 的配置文件中设置环境变量的名称为 DEMO_GREETING
,
其值为 "Hello from the environment"
。下面是此 Pod 的配置清单:
apiVersion : v1
kind : Pod
metadata :
name : envar-demo
labels :
purpose : demonstrate-envars
spec :
containers :
- name : envar-demo-container
image : gcr.io/google-samples/node-hello:1.0
env :
- name : DEMO_GREETING
value : "Hello from the environment"
- name : DEMO_FAREWELL
value : "Such a sweet sorrow"
基于配置清单创建一个 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/envars.yaml
获取正在运行的 Pod 信息:
kubectl get pods -l purpose = demonstrate-envars
查询结果应为:
NAME READY STATUS RESTARTS AGE
envar-demo 1/1 Running 0 9s
列出 Pod 容器的环境变量:
kubectl exec envar-demo -- printenv
打印结果应为:
NODE_VERSION=4.4.2
EXAMPLE_SERVICE_PORT_8080_TCP_ADDR=10.3.245.237
HOSTNAME=envar-demo
...
DEMO_GREETING=Hello from the environment
DEMO_FAREWELL=Such a sweet sorrow
说明: 通过 env
或 envFrom
字段设置的环境变量将覆盖容器镜像中指定的所有环境变量。
说明: 环境变量可以互相引用,但是顺序很重要。
使用在相同上下文中定义的其他变量的变量必须在列表的后面。
同样,请避免使用循环引用。
在配置中使用环境变量 你在 Pod 的配置中定义的环境变量可以在配置的其他地方使用,
例如可用在为 Pod 的容器设置的命令和参数中。
在下面的示例配置中,环境变量 GREETING
、HONORIFIC
和 NAME
分别设置为 Warm greetings to
、
The Most Honorable
和 Kubernetes
。然后这些环境变量在传递给容器 env-print-demo
的 CLI 参数中使用。
apiVersion : v1
kind : Pod
metadata :
name : print-greeting
spec :
containers :
- name : env-print-demo
image : bash
env :
- name : GREETING
value : "Warm greetings to"
- name : HONORIFIC
value : "The Most Honorable"
- name : NAME
value : "Kubernetes"
command : ["echo" ]
args : ["$(GREETING) $(HONORIFIC) $(NAME)" ]
创建后,命令 echo Warm greetings to The Most Honorable Kubernetes
将在容器中运行。
接下来 4.7.4 - 通过环境变量将 Pod 信息呈现给容器 此页面展示 Pod 如何使用 downward API 通过环境变量把自身的信息呈现给 Pod 中运行的容器。
你可以使用环境变量来呈现 Pod 的字段、容器字段或两者。
在 Kubernetes 中有两种方式可以将 Pod 和容器字段呈现给运行中的容器:
这两种呈现 Pod 和容器字段的方式统称为 downward API。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
用 Pod 字段作为环境变量的值 在这部分练习中,你将创建一个包含一个容器的 Pod。并将 Pod 级别的字段作为环境变量投射到正在运行的容器中。
apiVersion : v1
kind : Pod
metadata :
name : dapi-envars-fieldref
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox
command : [ "sh" , "-c" ]
args :
- while true; do
echo -en '\n';
printenv MY_NODE_NAME MY_POD_NAME MY_POD_NAMESPACE;
printenv MY_POD_IP MY_POD_SERVICE_ACCOUNT;
sleep 10;
done;
env :
- name : MY_NODE_NAME
valueFrom :
fieldRef :
fieldPath : spec.nodeName
- name : MY_POD_NAME
valueFrom :
fieldRef :
fieldPath : metadata.name
- name : MY_POD_NAMESPACE
valueFrom :
fieldRef :
fieldPath : metadata.namespace
- name : MY_POD_IP
valueFrom :
fieldRef :
fieldPath : status.podIP
- name : MY_POD_SERVICE_ACCOUNT
valueFrom :
fieldRef :
fieldPath : spec.serviceAccountName
restartPolicy : Never
这个清单中,你可以看到五个环境变量。env
字段定义了一组环境变量。
数组中第一个元素指定 MY_NODE_NAME
这个环境变量从 Pod 的 spec.nodeName
字段获取变量值。
同样,其它环境变量也是从 Pod 的字段获取它们的变量值。
说明: 本示例中的字段是 Pod 字段,不是 Pod 中 Container 的字段。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dapi-envars-pod.yaml
验证 Pod 中的容器运行正常:
# 如果新创建的 Pod 还是处于不健康状态,请重新运行此命令几次。
kubectl get pods
查看容器日志:
kubectl logs dapi-envars-fieldref
输出信息显示了所选择的环境变量的值:
minikube
dapi-envars-fieldref
default
172.17.0.4
default
要了解为什么这些值出现在日志中,请查看配置文件中的 command
和 args
字段。
当容器启动时,它将五个环境变量的值写入标准输出。每十秒重复执行一次。
接下来,进入 Pod 中运行的容器,打开一个 Shell:
kubectl exec -it dapi-envars-fieldref -- sh
在 Shell 中,查看环境变量:
# 在容器内的 `shell` 中运行
printenv
输出信息显示环境变量已经设置为 Pod 字段的值。
MY_POD_SERVICE_ACCOUNT=default
...
MY_POD_NAMESPACE=default
MY_POD_IP=172.17.0.4
...
MY_NODE_NAME=minikube
...
MY_POD_NAME=dapi-envars-fieldref
使用容器字段作为环境变量的值 前面的练习中,你将 Pod 级别的字段作为环境变量的值。
接下来这个练习中,你将传递属于 Pod 定义的字段,但这些字段取自特定容器而不是整个 Pod。
这里是只包含一个容器的 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : dapi-envars-resourcefieldref
spec :
containers :
- name : test-container
image : registry.k8s.io/busybox:1.24
command : [ "sh" , "-c" ]
args :
- while true; do
echo -en '\n';
printenv MY_CPU_REQUEST MY_CPU_LIMIT;
printenv MY_MEM_REQUEST MY_MEM_LIMIT;
sleep 10;
done;
resources :
requests :
memory : "32Mi"
cpu : "125m"
limits :
memory : "64Mi"
cpu : "250m"
env :
- name : MY_CPU_REQUEST
valueFrom :
resourceFieldRef :
containerName : test-container
resource : requests.cpu
- name : MY_CPU_LIMIT
valueFrom :
resourceFieldRef :
containerName : test-container
resource : limits.cpu
- name : MY_MEM_REQUEST
valueFrom :
resourceFieldRef :
containerName : test-container
resource : requests.memory
- name : MY_MEM_LIMIT
valueFrom :
resourceFieldRef :
containerName : test-container
resource : limits.memory
restartPolicy : Never
这个清单中,你可以看到四个环境变量。env
字段定义了一组环境变量。
数组中第一个元素指定 MY_CPU_REQUEST
这个环境变量从容器的 requests.cpu
字段获取变量值。同样,其它的环境变量也是从特定于这个容器的字段中获取它们的变量值。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dapi-envars-container.yaml
验证 Pod 中的容器运行正常:
# 如果新创建的 Pod 还是处于不健康状态,请重新运行此命令几次。
kubectl get pods
查看容器日志:
kubectl logs dapi-envars-resourcefieldref
输出信息显示了所选择的环境变量的值:
1
1
33554432
67108864
接下来 在旧版 API 参考中阅读有关 Pod、容器和环境变量的信息:
4.7.5 - 通过文件将 Pod 信息呈现给容器 此页面描述 Pod 如何使用
downwardAPI
卷
把自己的信息呈现给 Pod 中运行的容器。
downwardAPI
卷可以呈现 Pod 和容器的字段。
在 Kubernetes 中,有两种方式可以将 Pod 和容器字段呈现给运行中的容器:
这两种呈现 Pod 和容器字段的方式都称为 downward API 。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
存储 Pod 字段 在这部分的练习中,你将创建一个包含一个容器的 Pod,并将 Pod 级别的字段作为文件投射到正在运行的容器中。
Pod 的清单如下:
apiVersion : v1
kind : Pod
metadata :
name : kubernetes-downwardapi-volume-example
labels :
zone : us-est-coast
cluster : test-cluster1
rack : rack-22
annotations :
build : two
builder : john-doe
spec :
containers :
- name : client-container
image : registry.k8s.io/busybox
command : ["sh" , "-c" ]
args :
- while true; do
if [[ -e /etc/podinfo/labels ]]; then
echo -en '\n\n'; cat /etc/podinfo/labels; fi;
if [[ -e /etc/podinfo/annotations ]]; then
echo -en '\n\n'; cat /etc/podinfo/annotations; fi;
sleep 5;
done;
volumeMounts :
- name : podinfo
mountPath : /etc/podinfo
volumes :
- name : podinfo
downwardAPI :
items :
- path : "labels"
fieldRef :
fieldPath : metadata.labels
- path : "annotations"
fieldRef :
fieldPath : metadata.annotations
在 Pod 清单中,你可以看到 Pod 有一个 downwardAPI
类型的卷,并且挂载到容器中的
/etc/podinfo
目录。
查看 downwardAPI
下面的 items
数组。
数组的每个元素定义一个 downwardAPI
卷。
第一个元素指示 Pod 的 metadata.labels
字段的值保存在名为 labels
的文件中。
第二个元素指示 Pod 的 annotations
字段的值保存在名为 annotations
的文件中。
说明: 本示例中的字段是 Pod 字段,不是 Pod 中容器的字段。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dapi-volume.yaml
验证 Pod 中的容器运行正常:
查看容器的日志:
kubectl logs kubernetes-downwardapi-volume-example
输出显示 labels
文件和 annotations
文件的内容:
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"
build="two"
builder="john-doe"
进入 Pod 中运行的容器,打开一个 Shell:
kubectl exec -it kubernetes-downwardapi-volume-example -- sh
在该 Shell中,查看 labels
文件:
/# cat /etc/podinfo/labels
输出显示 Pod 的所有标签都已写入 labels
文件。
cluster="test-cluster1"
rack="rack-22"
zone="us-est-coast"
同样,查看 annotations
文件:
/# cat /etc/podinfo/annotations
查看 /etc/podinfo
目录下的文件:
在输出中可以看到,labels
和 annotations
文件都在一个临时子目录中。
在这个例子中,这个临时子目录为 ..2982_06_02_21_47_53.299460680
。
在 /etc/podinfo
目录中,..data
是指向该临时子目录的符号链接。
另外在 /etc/podinfo
目录中,labels
和 annotations
也是符号链接。
drwxr-xr-x ... Feb 6 21:47 ..2982_06_02_21_47_53.299460680
lrwxrwxrwx ... Feb 6 21:47 ..data -> ..2982_06_02_21_47_53.299460680
lrwxrwxrwx ... Feb 6 21:47 annotations -> ..data/annotations
lrwxrwxrwx ... Feb 6 21:47 labels -> ..data/labels
/etc/..2982_06_02_21_47_53.299460680:
total 8
-rw-r--r-- ... Feb 6 21:47 annotations
-rw-r--r-- ... Feb 6 21:47 labels
用符号链接可实现元数据的动态原子性刷新;更新将写入一个新的临时目录,
然后通过使用 rename(2)
完成 ..data
符号链接的原子性更新。
说明: 如果容器以
subPath 卷挂载方式来使用
Downward API,则该容器无法收到更新事件。
退出 Shell:
存储容器字段 前面的练习中,你使用 downward API 使 Pod 级别的字段可以被 Pod 内正在运行的容器访问。
接下来这个练习,你将只传递由 Pod 定义的部分的字段到 Pod 内正在运行的容器中,
但这些字段取自特定容器 而不是整个 Pod。
下面是一个同样只有一个容器的 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : kubernetes-downwardapi-volume-example-2
spec :
containers :
- name : client-container
image : registry.k8s.io/busybox:1.24
command : ["sh" , "-c" ]
args :
- while true; do
echo -en '\n';
if [[ -e /etc/podinfo/cpu_limit ]]; then
echo -en '\n'; cat /etc/podinfo/cpu_limit; fi;
if [[ -e /etc/podinfo/cpu_request ]]; then
echo -en '\n'; cat /etc/podinfo/cpu_request; fi;
if [[ -e /etc/podinfo/mem_limit ]]; then
echo -en '\n'; cat /etc/podinfo/mem_limit; fi;
if [[ -e /etc/podinfo/mem_request ]]; then
echo -en '\n'; cat /etc/podinfo/mem_request; fi;
sleep 5;
done;
resources :
requests :
memory : "32Mi"
cpu : "125m"
limits :
memory : "64Mi"
cpu : "250m"
volumeMounts :
- name : podinfo
mountPath : /etc/podinfo
volumes :
- name : podinfo
downwardAPI :
items :
- path : "cpu_limit"
resourceFieldRef :
containerName : client-container
resource : limits.cpu
divisor : 1m
- path : "cpu_request"
resourceFieldRef :
containerName : client-container
resource : requests.cpu
divisor : 1m
- path : "mem_limit"
resourceFieldRef :
containerName : client-container
resource : limits.memory
divisor : 1Mi
- path : "mem_request"
resourceFieldRef :
containerName : client-container
resource : requests.memory
divisor : 1Mi
在这个清单中,你可以看到 Pod 有一个
downwardAPI
卷 ,
并且这个卷会挂载到 Pod 内的单个容器的 /etc/podinfo
目录。
查看 downwardAPI
下面的 items
数组。
数组的每个元素定义一个 downwardAPI
卷。
第一个元素指定在名为 client-container
的容器中,
以 1m
所指定格式的 limits.cpu
字段的值应推送到名为 cpu_limit
的文件中。
divisor
字段是可选的,默认值为 1
。1 的除数表示 CPU 资源的核数或内存资源的字节数。
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dapi-volume-resources.yaml
打开一个 Shell,进入 Pod 中运行的容器:
kubectl exec -it kubernetes-downwardapi-volume-example-2 -- sh
在 Shell 中,查看 cpu_limit
文件:
# 在容器内的 Shell 中运行
cat /etc/podinfo/cpu_limit
你可以使用同样的命令查看 cpu_request
、mem_limit
和 mem_request
文件。
投射键名到指定路径并且指定文件权限 你可以将键名投射到指定路径并且指定每个文件的访问权限。
更多信息,请参阅 Secret 。
接下来 参阅 Pod spec
API 的定义,其中包括了容器(Pod 的一部分)的定义。 参阅你可以使用 downward API 公开的可用字段 列表。 阅读旧版的 API 参考中关于卷的内容:
4.7.6 - 使用 Secret 安全地分发凭证 本文展示如何安全地将敏感数据(如密码和加密密钥)注入到 Pod 中。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
将 Secret 数据转换为 base-64 形式 假设用户想要有两条 Secret 数据:用户名 my-app
和密码 39528$vdg7Jb
。
首先使用 Base64 编码 将用户名和密码转化为 base-64 形式。
下面是一个使用常用的 base64 程序的示例:
echo -n 'my-app' | base64
echo -n '39528$vdg7Jb' | base64
结果显示 base-64 形式的用户名为 bXktYXBw
,
base-64 形式的密码为 Mzk1MjgkdmRnN0pi
。
注意: 使用你的操作系统所能信任的本地工具以降低使用外部工具的风险。
创建 Secret 这里是一个配置文件,可以用来创建存有用户名和密码的 Secret:
apiVersion : v1
kind : Secret
metadata :
name : test-secret
data :
username : bXktYXBw
password : Mzk1MjgkdmRnN0pi
创建 Secret:
kubectl apply -f https://k8s.io/examples/pods/inject/secret.yaml
查看 Secret 相关信息:
kubectl get secret test-secret
输出:
NAME TYPE DATA AGE
test-secret Opaque 2 1m
查看 Secret 相关的更多详细信息:
kubectl describe secret test-secret
输出:
Name: test-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 13 bytes
username: 7 bytes
直接用 kubectl 创建 Secret 如果你希望略过 Base64 编码的步骤,你也可以使用 kubectl create secret
命令直接创建 Secret。例如:
kubectl create secret generic test-secret --from-literal= 'username=my-app' --from-literal= 'password=39528$vdg7Jb'
这是一种更为方便的方法。
前面展示的详细分解步骤有助于了解究竟发生了什么事情。
创建一个可以通过卷访问 Secret 数据的 Pod 这里是一个可以用来创建 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : secret-test-pod
spec :
containers :
- name : test-container
image : nginx
volumeMounts :
# name 必须与下面的卷名匹配
- name : secret-volume
mountPath : /etc/secret-volume
# Secret 数据通过一个卷暴露给该 Pod 中的容器
volumes :
- name : secret-volume
secret :
secretName : test-secret
创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/secret-pod.yaml
确认 Pod 正在运行:
kubectl get pod secret-test-pod
输出:
NAME READY STATUS RESTARTS AGE
secret-test-pod 1/1 Running 0 42m
获取一个 Shell 进入 Pod 中运行的容器:
kubectl exec -i -t secret-test-pod -- /bin/bash
Secret 数据通过挂载在 /etc/secret-volume
目录下的卷暴露在容器中。
在 Shell 中,列举 /etc/secret-volume
目录下的文件:
# 在容器中 Shell 运行下面命令
ls /etc/secret-volume
输出包含两个文件,每个对应一个 Secret 数据条目:
password username
在 Shell 中,显示 username
和 password
文件的内容:
# 在容器中 Shell 运行下面命令
echo " $( cat /etc/secret-volume/username ) "
echo " $( cat /etc/secret-volume/password ) "
输出为用户名和密码:
my-app
39528$vdg7Jb
使用 Secret 数据定义容器变量 使用来自 Secret 中的数据定义容器变量 定义环境变量为 Secret 中的键值偶对:
kubectl create secret generic backend-user --from-literal= backend-username= 'backend-admin'
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/inject/pod-single-secret-env-variable.yaml
使用来自多个 Secret 的数据定义环境变量 和前面的例子一样,先创建 Secret:
kubectl create secret generic backend-user --from-literal= backend-username= 'backend-admin'
kubectl create secret generic db-user --from-literal= db-username= 'db-admin'
在 Pod 规约中定义环境变量:
apiVersion : v1
kind : Pod
metadata :
name : envvars-multiple-secrets
spec :
containers :
- name : envars-test-container
image : nginx
env :
- name : BACKEND_USERNAME
valueFrom :
secretKeyRef :
name : backend-user
key : backend-username
- name : DB_USERNAME
valueFrom :
secretKeyRef :
name : db-user
key : db-username
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/inject/pod-multiple-secret-env-variable.yaml
说明: 此功能在 Kubernetes 1.6 版本之后可用。
创建包含多个键值偶对的 Secret:
kubectl create secret generic test-secret --from-literal= username = 'my-app' --from-literal= password = '39528$vdg7Jb'
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/inject/pod-secret-envFrom.yaml
参考 接下来 4.8 - 运行应用 运行和管理无状态和有状态的应用程序。
4.8.1 - 使用 Deployment 运行一个无状态应用 本文介绍如何通过 Kubernetes Deployment 对象去运行一个应用。
教程目标 创建一个 nginx Deployment。 使用 kubectl 列举该 Deployment 的相关信息。 更新该 Deployment。 准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.9.
要获知版本信息,请输入
kubectl version
.
创建并了解一个 nginx Deployment 你可以通过创建一个 Kubernetes Deployment 对象来运行一个应用, 且你可以在一个
YAML 文件中描述 Deployment。例如, 下面这个 YAML 文件描述了一个运行 nginx:1.14.2
Docker 镜像的 Deployment:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
通过 YAML 文件创建一个 Deployment:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
显示该 Deployment 的相关信息:
kubectl describe deployment nginx-deployment
输出类似于这样:
Name: nginx-deployment
Namespace: default
CreationTimestamp: Tue, 30 Aug 2016 18:11:37 -0700
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=1
Selector: app=nginx
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:1.7.9
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-1771418926 (2/2 replicas created)
No events.
列出该 Deployment 创建的 Pod:
kubectl get pods -l app = nginx
输出类似于这样:
NAME READY STATUS RESTARTS AGE
nginx-deployment-1771418926-7o5ns 1/1 Running 0 16h
nginx-deployment-1771418926-r18az 1/1 Running 0 16h
展示某一个 Pod 信息:
kubectl describe pod <pod-name>
这里的 <pod-name>
是某一 Pod 的名称。
更新 Deployment 你可以通过应用一个新的 YAML 文件来更新 Deployment。下面的 YAML 文件指定该
Deployment 镜像更新为 nginx 1.16.1。
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.16.1 # 将 nginx 版本从 1.14.2 更新为 1.16.1
ports :
- containerPort : 80
应用新的 YAML:
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
查看该 Deployment 以新的名称创建 Pod 同时删除旧的 Pod:
kubectl get pods -l app = nginx
通过增加副本数来扩缩应用 你可以通过应用新的 YAML 文件来增加 Deployment 中 Pod 的数量。
下面的 YAML 文件将 replicas
设置为 4,指定该 Deployment 应有 4 个 Pod:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 4 # 将副本数从 2 更新为 4
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : nginx:1.14.2
ports :
- containerPort : 80
应用新的 YAML 文件:
kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
验证该 Deployment 有 4 个 Pod:
kubectl get pods -l app = nginx
输出的结果类似于:
NAME READY STATUS RESTARTS AGE
nginx-deployment-148880595-4zdqq 1/1 Running 0 25s
nginx-deployment-148880595-6zgi1 1/1 Running 0 25s
nginx-deployment-148880595-fxcez 1/1 Running 0 2m
nginx-deployment-148880595-rwovn 1/1 Running 0 2m
删除 Deployment 基于名称删除 Deployment:
kubectl delete deployment nginx-deployment
ReplicationController —— 旧的方式 创建一个多副本应用首选方法是使用 Deployment,该 Deployment 内部将轮流使用 ReplicaSet。
在 Deployment 和 ReplicaSet 被引入到 Kubernetes 之前,多副本应用通过
ReplicationController
来配置。
接下来 4.8.2 - 运行一个单实例有状态应用 本文介绍在 Kubernetes 中如何使用 PersistentVolume 和 Deployment 运行一个单实例有状态应用。该应用是 MySQL.
教程目标 在你的环境中创建一个引用磁盘的 PersistentVolume 创建一个 MySQL Deployment. 在集群内以一个已知的 DNS 名称将 MySQL 暴露给其他 Pod 准备开始 部署 MySQL 你可以通过创建一个 Kubernetes Deployment 并使用 PersistentVolumeClaim 将其连接到
某已有的 PV 卷来运行一个有状态的应用。
例如,这里的 YAML 描述的是一个运行 MySQL 的 Deployment,其中引用了 PVC 申领。
文件为 /var/lib/mysql 定义了加载卷,并创建了一个 PVC 申领,寻找一个 20G 大小的卷。
该申领可以通过现有的满足需求的卷来满足,也可以通过动态供应卷的机制来满足。
注意:在配置的 YAML 文件中定义密码的做法是不安全的。具体安全解决方案请参考
Kubernetes Secrets .
apiVersion : v1
kind : Service
metadata :
name : mysql
spec :
ports :
- port : 3306
selector :
app : mysql
clusterIP : None
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : mysql
spec :
selector :
matchLabels :
app : mysql
strategy :
type : Recreate
template :
metadata :
labels :
app : mysql
spec :
containers :
- image : mysql:5.6
name : mysql
env :
# 在实际中使用 secret
- name : MYSQL_ROOT_PASSWORD
value : password
ports :
- containerPort : 3306
name : mysql
volumeMounts :
- name : mysql-persistent-storage
mountPath : /var/lib/mysql
volumes :
- name : mysql-persistent-storage
persistentVolumeClaim :
claimName : mysql-pv-claim
apiVersion : v1
kind : PersistentVolume
metadata :
name : mysql-pv-volume
labels :
type : local
spec :
storageClassName : manual
capacity :
storage : 20Gi
accessModes :
- ReadWriteOnce
hostPath :
path : "/mnt/data"
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : mysql-pv-claim
spec :
storageClassName : manual
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 20Gi
部署 YAML 文件中定义的 PV 和 PVC:
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-pv.yaml
部署 YAML 文件中定义的 Deployment:
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-deployment.yaml
展示 Deployment 相关信息:
kubectl describe deployment mysql
输出类似于:
Name: mysql
Namespace: default
CreationTimestamp: Tue, 01 Nov 2016 11:18:45 -0700
Labels: app=mysql
Annotations: deployment.kubernetes.io/revision=1
Selector: app=mysql
Replicas: 1 desired | 1 updated | 1 total | 0 available | 1 unavailable
StrategyType: Recreate
MinReadySeconds: 0
Pod Template:
Labels: app=mysql
Containers:
mysql:
Image: mysql:5.6
Port: 3306/TCP
Environment:
MYSQL_ROOT_PASSWORD: password
Mounts:
/var/lib/mysql from mysql-persistent-storage (rw)
Volumes:
mysql-persistent-storage:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: mysql-pv-claim
ReadOnly: false
Conditions:
Type Status Reason
---- ------ ------
Available False MinimumReplicasUnavailable
Progressing True ReplicaSetUpdated
OldReplicaSets: <none>
NewReplicaSet: mysql-63082529 (1/1 replicas created)
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
33s 33s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set mysql-63082529 to 1
列举出 Deployment 创建的 pods:
kubectl get pods -l app = mysql
输出类似于:
NAME READY STATUS RESTARTS AGE
mysql-63082529-2z3ki 1/1 Running 0 3m
查看 PersistentVolumeClaim:
kubectl describe pvc mysql-pv-claim
输出类似于:
Name: mysql-pv-claim
Namespace: default
StorageClass:
Status: Bound
Volume: mysql-pv-volume
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed=yes
pv.kubernetes.io/bound-by-controller=yes
Capacity: 20Gi
Access Modes: RWO
Events: <none>
访问 MySQL 实例 前面 YAML 文件中创建了一个允许集群内其他 Pod 访问的数据库服务。该服务中选项
clusterIP: None
让服务 DNS 名称直接解析为 Pod 的 IP 地址。
当在一个服务下只有一个 Pod 并且不打算增加 Pod 的数量这是最好的.
运行 MySQL 客户端以连接到服务器:
kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
此命令在集群内创建一个新的 Pod 并运行 MySQL 客户端,并通过 Service 连接到服务器。
如果连接成功,你就知道有状态的 MySQL 数据库正处于运行状态。
Waiting for pod default/mysql-client-274442439-zyp6i to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.
mysql>
更新 Deployment 中镜像或其他部分同往常一样可以通过 kubectl apply
命令更新。
以下是特定于有状态应用的一些注意事项:
不要对应用进行规模扩缩。这里的设置仅适用于单实例应用。下层的 PersistentVolume
仅只能挂载到一个 Pod 上。对于集群级有状态应用,请参考
StatefulSet 文档 . 在 Deployment 的 YAML 文件中使用 strategy:
type: Recreate
。
该选项指示 Kubernetes 不 使用滚动升级。滚动升级无法工作,因为这里一次不能
运行多个 Pod。在使用更新的配置文件创建新的 Pod 前,Recreate
策略将
保证先停止第一个 Pod。 删除 Deployment 通过名称删除部署的对象:
kubectl delete deployment,svc mysql
kubectl delete pvc mysql-pv-claim
kubectl delete pv mysql-pv-volume
如果通过手动的方式供应 PersistentVolume, 那么也需要手动删除它以释放下层资源。
如果是用动态供应方式创建的 PersistentVolume,在删除 PersistentVolumeClaim 后
PersistentVolume 将被自动删除。
一些存储服务(比如 EBS 和 PD)也会在 PersistentVolume 被删除时自动回收下层资源。
接下来 4.8.3 - 运行一个有状态的应用程序 本页展示如何使用 StatefulSet
控制器运行一个有状态的应用程序。此例是多副本的 MySQL 数据库。
示例应用的拓扑结构有一个主服务器和多个副本,使用异步的基于行(Row-Based)
的数据复制。
说明: 这不是生产环境下配置 。
尤其注意,MySQL 设置都使用的是不安全的默认值,这是因为我们想把重点放在 Kubernetes
中运行有状态应用程序的一般模式上。
准备开始 教程目标 使用 StatefulSet 部署多副本 MySQL 拓扑架构。 发送 MySQL 客户端请求 观察对宕机的抵抗力 扩缩 StatefulSet 的规模 部署 MySQL MySQL 示例部署包含一个 ConfigMap、两个 Service 与一个 StatefulSet。
创建一个 ConfigMap 使用以下的 YAML 配置文件创建 ConfigMap :
apiVersion : v1
kind : ConfigMap
metadata :
name : mysql
labels :
app : mysql
app.kubernetes.io/name : mysql
data :
primary.cnf : |
# 仅在主服务器上应用此配置
[mysqld]
log-bin
replica.cnf : |
# 仅在副本服务器上应用此配置
[mysqld]
super-read-only
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
这个 ConfigMap 提供 my.cnf
覆盖设置,使你可以独立控制 MySQL 主服务器和副本服务器的配置。
在这里,你希望主服务器能够将复制日志提供给副本服务器,
并且希望副本服务器拒绝任何不是通过复制进行的写操作。
ConfigMap 本身没有什么特别之处,因而也不会出现不同部分应用于不同的 Pod 的情况。
每个 Pod 都会在初始化时基于 StatefulSet 控制器提供的信息决定要查看的部分。
创建 Service 使用以下 YAML 配置文件创建服务:
# 为 StatefulSet 成员提供稳定的 DNS 表项的无头服务(Headless Service)
apiVersion : v1
kind : Service
metadata :
name : mysql
labels :
app : mysql
app.kubernetes.io/name : mysql
spec :
ports :
- name : mysql
port : 3306
clusterIP : None
selector :
app : mysql
---
# 用于连接到任一 MySQL 实例执行读操作的客户端服务
# 对于写操作,你必须连接到主服务器:mysql-0.mysql
apiVersion : v1
kind : Service
metadata :
name : mysql-read
labels :
app : mysql
app.kubernetes.io/name : mysql
readonly : "true"
spec :
ports :
- name : mysql
port : 3306
selector :
app : mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
这个无头 Service 给 StatefulSet 控制器
为集合中每个 Pod 创建的 DNS 条目提供了一个宿主。
因为无头服务名为 mysql
,所以可以通过在同一 Kubernetes 集群和命名空间中的任何其他 Pod
内解析 <Pod 名称>.mysql
来访问 Pod。
客户端 Service 称为 mysql-read
,是一种常规 Service,具有其自己的集群 IP。
该集群 IP 在报告就绪的所有 MySQL Pod 之间分配连接。
可能的端点集合包括 MySQL 主节点和所有副本节点。
请注意,只有读查询才能使用负载平衡的客户端 Service。
因为只有一个 MySQL 主服务器,所以客户端应直接连接到 MySQL 主服务器 Pod
(通过其在无头 Service 中的 DNS 条目)以执行写入操作。
创建 StatefulSet 最后,使用以下 YAML 配置文件创建 StatefulSet:
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : mysql
spec :
selector :
matchLabels :
app : mysql
app.kubernetes.io/name : mysql
serviceName : mysql
replicas : 3
template :
metadata :
labels :
app : mysql
app.kubernetes.io/name : mysql
spec :
initContainers :
- name : init-mysql
image : mysql:5.7
command :
- bash
- "-c"
- |
set -ex
# 基于 Pod 序号生成 MySQL 服务器的 ID。
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# 添加偏移量以避免使用 server-id=0 这一保留值。
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# 将合适的 conf.d 文件从 config-map 复制到 emptyDir。
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts :
- name : conf
mountPath : /mnt/conf.d
- name : config-map
mountPath : /mnt/config-map
- name : clone-mysql
image : gcr.io/google-samples/xtrabackup:1.0
command :
- bash
- "-c"
- |
set -ex
# 如果已有数据,则跳过克隆。
[[ -d /var/lib/mysql/mysql ]] && exit 0
# 跳过主实例(序号索引 0)的克隆。
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# 从原来的对等节点克隆数据。
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# 准备备份。
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts :
- name : data
mountPath : /var/lib/mysql
subPath : mysql
- name : conf
mountPath : /etc/mysql/conf.d
containers :
- name : mysql
image : mysql:5.7
env :
- name : MYSQL_ALLOW_EMPTY_PASSWORD
value : "1"
ports :
- name : mysql
containerPort : 3306
volumeMounts :
- name : data
mountPath : /var/lib/mysql
subPath : mysql
- name : conf
mountPath : /etc/mysql/conf.d
resources :
requests :
cpu : 500m
memory : 1Gi
livenessProbe :
exec :
command : ["mysqladmin" , "ping" ]
initialDelaySeconds : 30
periodSeconds : 10
timeoutSeconds : 5
readinessProbe :
exec :
# 检查我们是否可以通过 TCP 执行查询(skip-networking 是关闭的)。
command : ["mysql" , "-h" , "127.0.0.1" , "-e" , "SELECT 1" ]
initialDelaySeconds : 5
periodSeconds : 2
timeoutSeconds : 1
- name : xtrabackup
image : gcr.io/google-samples/xtrabackup:1.0
ports :
- name : xtrabackup
containerPort : 3307
command :
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# 确定克隆数据的 binlog 位置(如果有的话)。
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# XtraBackup 已经生成了部分的 “CHANGE MASTER TO” 查询
# 因为我们从一个现有副本进行克隆。(需要删除末尾的分号!)
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# 在这里要忽略 xtrabackup_binlog_info (它是没用的)。
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# 我们直接从主实例进行克隆。解析 binlog 位置。
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# 检查我们是否需要通过启动复制来完成克隆。
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mysql-0.mysql', \
MASTER_USER='root', \
MASTER_PASSWORD='', \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
# 如果容器重新启动,最多尝试一次。
mv change_master_to.sql.in change_master_to.sql.orig
fi
# 当对等点请求时,启动服务器发送备份。
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts :
- name : data
mountPath : /var/lib/mysql
subPath : mysql
- name : conf
mountPath : /etc/mysql/conf.d
resources :
requests :
cpu : 100m
memory : 100Mi
volumes :
- name : conf
emptyDir : {}
- name : config-map
configMap :
name : mysql
volumeClaimTemplates :
- metadata :
name : data
spec :
accessModes : ["ReadWriteOnce" ]
resources :
requests :
storage : 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
你可以通过运行以下命令查看启动进度:
kubectl get pods -l app = mysql --watch
一段时间后,你应该看到所有 3 个 Pod 进入 Running
状态:
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 2m
mysql-1 2/2 Running 0 1m
mysql-2 2/2 Running 0 1m
输入 Ctrl+C 结束监视操作。
说明: 如果你看不到任何进度,确保已启用前提条件
中提到的动态 PersistentVolume 制备程序。
此清单使用多种技术来管理作为 StatefulSet 的一部分的有状态 Pod。
下一节重点介绍其中的一些技巧,以解释 StatefulSet 创建 Pod 时发生的状况。
了解有状态的 Pod 初始化 StatefulSet 控制器按序数索引顺序地每次启动一个 Pod。
它一直等到每个 Pod 报告就绪才再启动下一个 Pod。
此外,控制器为每个 Pod 分配一个唯一、稳定的名称,形如 <statefulset 名称>-<序数索引>
,
其结果是 Pods 名为 mysql-0
、mysql-1
和 mysql-2
。
上述 StatefulSet 清单中的 Pod 模板利用这些属性来执行 MySQL 副本的有序启动。
生成配置 在启动 Pod 规约中的任何容器之前,Pod 首先按顺序运行所有的
初始化容器 。
第一个名为 init-mysql
的初始化容器根据序号索引生成特殊的 MySQL 配置文件。
该脚本通过从 Pod 名称的末尾提取索引来确定自己的序号索引,而 Pod 名称由 hostname
命令返回。
然后将序数(带有数字偏移量以避免保留值)保存到 MySQL conf.d
目录中的文件 server-id.cnf
。
这一操作将 StatefulSet 所提供的唯一、稳定的标识转换为 MySQL 服务器 ID,
而这些 ID 也是需要唯一性、稳定性保证的。
通过将内容复制到 conf.d
中,init-mysql
容器中的脚本也可以应用 ConfigMap 中的
primary.cnf
或 replica.cnf
。
由于示例部署结构由单个 MySQL 主节点和任意数量的副本节点组成,
因此脚本仅将序数 0
指定为主节点,而将其他所有节点指定为副本节点。
与 StatefulSet 控制器的
部署顺序保证
相结合,
可以确保 MySQL 主服务器在创建副本服务器之前已准备就绪,以便它们可以开始复制。
克隆现有数据 通常,当新 Pod 作为副本节点加入集合时,必须假定 MySQL 主节点可能已经有数据。
还必须假设复制日志可能不会一直追溯到时间的开始。
这些保守的假设是允许正在运行的 StatefulSet 随时间扩大和缩小而不是固定在其初始大小的关键。
第二个名为 clone-mysql
的初始化容器,第一次在带有空 PersistentVolume 的副本 Pod
上启动时,会在从属 Pod 上执行克隆操作。
这意味着它将从另一个运行中的 Pod 复制所有现有数据,使此其本地状态足够一致,
从而可以开始从主服务器复制。
MySQL 本身不提供执行此操作的机制,因此本示例使用了一种流行的开源工具 Percona XtraBackup。
在克隆期间,源 MySQL 服务器性能可能会受到影响。
为了最大程度地减少对 MySQL 主服务器的影响,该脚本指示每个 Pod 从序号较低的 Pod 中克隆。
可以这样做的原因是 StatefulSet 控制器始终确保在启动 Pod N + 1
之前 Pod N
已准备就绪。
开始复制 初始化容器成功完成后,应用容器将运行。MySQL Pod 由运行实际 mysqld
服务的 mysql
容器和充当辅助工具 的
xtrabackup 容器组成。
xtrabackup
sidecar 容器查看克隆的数据文件,并确定是否有必要在副本服务器上初始化 MySQL 复制。
如果是这样,它将等待 mysqld
准备就绪,然后使用从 XtraBackup 克隆文件中提取的复制参数执行
CHANGE MASTER TO
和 START SLAVE
命令。
一旦副本服务器开始复制后,它会记住其 MySQL 主服务器,并且如果服务器重新启动或连接中断也会自动重新连接。
另外,因为副本服务器会以其稳定的 DNS 名称查找主服务器(mysql-0.mysql
),
即使由于重新调度而获得新的 Pod IP,它们也会自动找到主服务器。
最后,开始复制后,xtrabackup
容器监听来自其他 Pod 的连接,处理其数据克隆请求。
如果 StatefulSet 扩大规模,或者下一个 Pod 失去其 PersistentVolumeClaim 并需要重新克隆,
则此服务器将无限期保持运行。
发送客户端请求 你可以通过运行带有 mysql:5.7
镜像的临时容器并运行 mysql
客户端二进制文件,
将测试查询发送到 MySQL 主服务器(主机名 mysql-0.mysql
)。
kubectl run mysql-client --image= mysql:5.7 -i --rm --restart= Never --\
mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF
使用主机名 mysql-read
将测试查询发送到任何报告为就绪的服务器:
kubectl run mysql-client --image= mysql:5.7 -i -t --rm --restart= Never --\
mysql -h mysql-read -e "SELECT * FROM test.messages"
你应该获得如下输出:
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
为了演示 mysql-read
服务在服务器之间分配连接,你可以在循环中运行 SELECT @@server_id
:
kubectl run mysql-client-loop --image= mysql:5.7 -i -t --rm --restart= Never --\
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
你应该看到报告的 @@server_id
发生随机变化,因为每次尝试连接时都可能选择了不同的端点:
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 101 | 2006-01-02 15:04:07 |
+-------------+---------------------+
要停止循环时可以按 Ctrl+C ,但是让它在另一个窗口中运行非常有用,
这样你就可以看到以下步骤的效果。
模拟 Pod 和 Node 失效 为了证明从副本节点缓存而不是单个服务器读取数据的可用性提高,请在使 Pod 退出 Ready
状态时,保持上述 SELECT @@server_id
循环一直运行。
破坏就绪态探测 mysql
容器的就绪态探测
运行命令 mysql -h 127.0.0.1 -e 'SELECT 1'
,以确保服务器已启动并能够执行查询。
迫使就绪态探测失败的一种方法就是中止该命令:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
此命令会进入 Pod mysql-2
的实际容器文件系统,重命名 mysql
命令,导致就绪态探测无法找到它。
几秒钟后, Pod 会报告其中一个容器未就绪。你可以通过运行以下命令进行检查:
在 READY
列中查找 1/2
:
NAME READY STATUS RESTARTS AGE
mysql-2 1/2 Running 0 3m
此时,你应该会看到 SELECT @@server_id
循环继续运行,尽管它不再报告 102
。
回想一下,init-mysql
脚本将 server-id
定义为 100 + $ordinal
,
因此服务器 ID 102
对应于 Pod mysql-2
。
现在修复 Pod,几秒钟后它应该重新出现在循环输出中:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
删除 Pods 如果删除了 Pod,则 StatefulSet 还会重新创建 Pod,类似于 ReplicaSet 对无状态 Pod 所做的操作。
kubectl delete pod mysql-2
StatefulSet 控制器注意到不再存在 mysql-2
Pod,于是创建一个具有相同名称并链接到相同
PersistentVolumeClaim 的新 Pod。
你应该看到服务器 ID 102
从循环输出中消失了一段时间,然后又自行出现。
腾空节点 如果你的 Kubernetes 集群具有多个节点,则可以通过发出以下
drain
命令来模拟节点停机(就好像节点在被升级)。
首先确定 MySQL Pod 之一在哪个节点上:
kubectl get pod mysql-2 -o wide
节点名称应显示在最后一列中:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Running 0 15m 10.244.5.27 kubernetes-node-9l2t
接下来,通过运行以下命令腾空节点,该命令将其保护起来,以使新的 Pod 不能调度到该节点,
然后逐出所有现有的 Pod。将 <节点名称>
替换为在上一步中找到的节点名称。
注意: 腾空一个 Node 可能影响到在该节点上运行的其他负载和应用。
只应在测试集群上执行下列步骤
# 关于对其他负载的影响,参见前文建议
kubectl drain <节点名称> --force --delete-local-data --ignore-daemonsets
现在,你可以监视 Pod 被重新调度到其他节点上:
kubectl get pod mysql-2 -o wide --watch
它看起来应该像这样:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Terminating 0 15m 10.244.1.56 kubernetes-node-9l2t
[...]
mysql-2 0/2 Pending 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:0/2 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:1/2 0 20s 10.244.5.32 kubernetes-node-fjlm
mysql-2 0/2 PodInitializing 0 21s 10.244.5.32 kubernetes-node-fjlm
mysql-2 1/2 Running 0 22s 10.244.5.32 kubernetes-node-fjlm
mysql-2 2/2 Running 0 30s 10.244.5.32 kubernetes-node-fjlm
再次,你应该看到服务器 ID 102
从 SELECT @@server_id
循环输出中消失一段时间,然后再次出现。
现在去掉节点保护(Uncordon),使其恢复为正常模式:
扩展副本节点数量 使用 MySQL 复制时,你可以通过添加副本节点来扩展读取查询的能力。
使用 StatefulSet,你可以使用单个命令执行此操作:
kubectl scale statefulset mysql --replicas= 5
运行下面的命令,监视新的 Pod 启动:
kubectl get pods -l app = mysql --watch
一旦 Pod 启动,你应该看到服务器 IDs 103
和 104
开始出现在 SELECT @@server_id
循环输出中。
你还可以验证这些新服务器在存在之前已添加了数据:
kubectl run mysql-client --image= mysql:5.7 -i -t --rm --restart= Never --\
mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
向下缩容操作也是很平滑的:
kubectl scale statefulset mysql --replicas= 3
说明: 扩容操作会自动创建新的 PersistentVolumeClaim,但是缩容时不会自动删除这些 PVC。
这使你可以选择保留那些已被初始化的 PVC,以加速再次扩容,或者在删除它们之前提取数据。
你可以通过运行以下命令查看此效果:
kubectl get pvc -l app = mysql
这表明,尽管将 StatefulSet 缩小为3,所有5个 PVC 仍然存在:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
data-mysql-0 Bound pvc-8acbf5dc-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-1 Bound pvc-8ad39820-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-2 Bound pvc-8ad69a6d-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-3 Bound pvc-50043c45-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
data-mysql-4 Bound pvc-500a9957-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
如果你不打算重复使用多余的 PVC,则可以删除它们:
kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4
清理现场 通过在终端上按 Ctrl+C 取消 SELECT @@server_id
循环,或从另一个终端运行以下命令:
kubectl delete pod mysql-client-loop --now
删除 StatefulSet。这也会开始终止 Pod。
kubectl delete statefulset mysql
验证 Pod 消失。他们可能需要一些时间才能完成终止。
kubectl get pods -l app = mysql
当上述命令返回如下内容时,你就知道 Pod 已终止:
No resources found.
删除 ConfigMap、Service 和 PersistentVolumeClaim。
kubectl delete configmap,service,pvc -l app = mysql
如果你手动供应 PersistentVolume,则还需要手动删除它们,并释放下层资源。
如果你使用了动态预配器,当得知你删除 PersistentVolumeClaim 时,它将自动删除 PersistentVolume。
一些动态预配器(例如用于 EBS 和 PD 的预配器)也会在删除 PersistentVolume 时释放下层资源。 接下来 4.8.4 - 删除 StatefulSet 本任务展示如何删除 StatefulSet。
准备开始 本任务假设在你的集群上已经运行了由 StatefulSet 创建的应用。 删除 StatefulSet 你可以像删除 Kubernetes 中的其他资源一样删除 StatefulSet:使用 kubectl delete
命令,并按文件或者名字指定 StatefulSet。
kubectl delete -f <file.yaml>
kubectl delete statefulsets <statefulset 名称>
删除 StatefulSet 之后,你可能需要单独删除关联的无头服务。
kubectl delete service <服务名称>
当通过 kubectl
删除 StatefulSet 时,StatefulSet 会被缩容为 0。
属于该 StatefulSet 的所有 Pod 也被删除。
如果你只想删除 StatefulSet 而不删除 Pod,使用 --cascade=orphan
。
kubectl delete -f <file.yaml> --cascade= orphan
通过将 --cascade=orphan
传递给 kubectl delete
,在删除 StatefulSet 对象之后,
StatefulSet 管理的 Pod 会被保留下来。如果 Pod 具有标签 app.kubernetes.io/name=MyApp
,则可以按照
如下方式删除它们:
kubectl delete pods -l app.kubernetes.io/name= MyApp
持久卷 删除 StatefulSet 管理的 Pod 并不会删除关联的卷。这是为了确保你有机会在删除卷之前从卷中复制数据。
在 Pod 离开终止状态
后删除 PVC 可能会触发删除背后的 PV 持久卷,具体取决于存储类和回收策略。
永远不要假定在 PVC 删除后仍然能够访问卷。
说明: 删除 PVC 时要谨慎,因为这可能会导致数据丢失。
完全删除 StatefulSet 要删除 StatefulSet 中的所有内容,包括关联的 pods,你可以运行
一系列如下所示的命令:
grace = $( kubectl get pods <stateful-set-pod> --template '{{.spec.terminationGracePeriodSeconds}}' )
kubectl delete statefulset -l app.kubernetes.io/name= MyApp
sleep $grace
kubectl delete pvc -l app.kubernetes.io/name= MyApp
在上面的例子中,Pod 的标签为 app.kubernetes.io/name=MyApp
;适当地替换你自己的标签。
强制删除 StatefulSet 的 Pod 如果你发现 StatefulSet 的某些 Pod 长时间处于 'Terminating' 或者 'Unknown' 状态,
则可能需要手动干预以强制从 API 服务器中删除这些 Pod。
这是一项有点危险的任务。详细信息请阅读
删除 StatefulSet 类型的 Pods 。
接下来 进一步了解强制删除 StatefulSet 的 Pods 。
4.8.5 - 强制删除 StatefulSet 中的 Pod 本文介绍如何删除 StatefulSet
管理的 Pod,并解释这样操作时需要记住的注意事项。
准备开始 这是一项相当高级的任务,并且可能会违反 StatefulSet 固有的某些属性。 继续任务之前,请熟悉下面列举的注意事项。 StatefulSet 注意事项 在 StatefulSet 的正常操作中,永远不 需要强制删除 StatefulSet 管理的 Pod。
StatefulSet 控制器 负责创建、
扩缩和删除 StatefulSet 管理的 Pod。它尝试确保指定数量的从序数 0 到 N-1 的 Pod
处于活跃状态并准备就绪。StatefulSet 确保在任何时候,集群中最多只有一个具有给定标识的 Pod。
这就是所谓的由 StatefulSet 提供的最多一个(At Most One) Pod 的语义。
应谨慎进行手动强制删除操作,因为它可能会违反 StatefulSet 固有的至多一个的语义。
StatefulSets 可用于运行分布式和集群级的应用,这些应用需要稳定的网络标识和可靠的存储。
这些应用通常配置为具有固定标识固定数量的成员集合。
具有相同身份的多个成员可能是灾难性的,并且可能导致数据丢失 (例如:票选系统中的脑裂场景)。
删除 Pod 你可以使用下面的命令执行体面地删除 Pod:
kubectl delete pods <pod>
为了让上面操作能够体面地终止 Pod,Pod 一定不能 设置 pod.Spec.TerminationGracePeriodSeconds
为 0。
将 pod.Spec.TerminationGracePeriodSeconds
设置为 0 秒的做法是不安全的,强烈建议 StatefulSet 类型的
Pod 不要使用。体面删除是安全的,并且会在 kubelet 从 API 服务器中删除资源名称之前确保
体面地结束 Pod 。
当某个节点不可达时,不会引发自动删除 Pod。
在无法访问的节点上运行的 Pod 在
超时
后会进入 “Terminating” 或者 “Unknown” 状态。
当用户尝试体面地删除无法访问的节点上的 Pod 时 Pod 也可能会进入这些状态。
从 API 服务器上删除处于这些状态 Pod 的仅有可行方法如下:
删除 Node 对象(要么你来删除, 要么节点控制器
来删除) 无响应节点上的 kubelet 开始响应,杀死 Pod 并从 API 服务器上移除 Pod 对象 用户强制删除 pod 推荐使用第一种或者第二种方法。
如果确认节点已经不可用了(比如,永久断开网络、断电等),
则应删除 Node 对象。
如果节点遇到网裂问题,请尝试解决该问题或者等待其解决。
当网裂愈合时,kubelet 将完成 Pod 的删除并从 API 服务器上释放其名字。
通常,Pod 一旦不在节点上运行,或者管理员删除了节点,系统就会完成其删除动作。
你也可以通过强制删除 Pod 来绕过这一机制。
强制删除 强制删除不会 等待来自 kubelet 对 Pod 已终止的确认消息。
无论强制删除是否成功杀死了 Pod,它都会立即从 API 服务器中释放该名字。
这将让 StatefulSet 控制器创建一个具有相同标识的替身 Pod;因而可能导致正在运行 Pod 的重复,
并且如果所述 Pod 仍然可以与 StatefulSet 的成员通信,则将违反 StatefulSet 所要保证的
最多一个的语义。
当你强制删除 StatefulSet 类型的 Pod 时,你要确保有问题的 Pod 不会再和 StatefulSet 管理的其他
Pod 通信并且可以安全地释放其名字以便创建替代 Pod。
如果要使用 kubectl 1.5 以上版本强制删除 Pod,请执行下面命令:
kubectl delete pods <pod> --grace-period= 0 --force
如果你使用 kubectl 的 1.4 以下版本,则应省略 --force
选项:
kubectl delete pods <pod> --grace-period= 0
如果在这些命令后 Pod 仍处于 Unknown
状态,请使用以下命令从集群中删除 Pod:
kubectl patch pod <pod> -p '{"metadata":{"finalizers":null}}'
请始终谨慎地执行强制删除 StatefulSet 类型的 Pod,并充分了解强制删除操作所涉及的风险。
接下来 进一步了解调试 StatefulSet 。
4.8.6 - Pod 水平自动扩缩 在 Kubernetes 中,HorizontalPodAutoscaler 自动更新工作负载资源
(例如 Deployment 或者
StatefulSet ),
目的是自动扩缩工作负载以满足需求。
水平扩缩意味着对增加的负载的响应是部署更多的 Pod 。
这与 “垂直(Vertical)” 扩缩不同,对于 Kubernetes,
垂直扩缩意味着将更多资源(例如:内存或 CPU)分配给已经为工作负载运行的 Pod。
如果负载减少,并且 Pod 的数量高于配置的最小值,
HorizontalPodAutoscaler 会指示工作负载资源(Deployment、StatefulSet 或其他类似资源)缩减。
水平 Pod 自动扩缩不适用于无法扩缩的对象(例如:DaemonSet 。)
HorizontalPodAutoscaler 被实现为 Kubernetes API
资源和控制器 。
资源决定了控制器的行为。
在 Kubernetes 控制平面 内运行的水平
Pod 自动扩缩控制器会定期调整其目标(例如:Deployment)的所需规模,以匹配观察到的指标,
例如,平均 CPU 利用率、平均内存利用率或你指定的任何其他自定义指标。
使用水平 Pod 自动扩缩演练示例 。
HorizontalPodAutoscaler 是如何工作的? HorizontalPodAutoscaler 控制 Deployment 及其 ReplicaSet 的规模
Kubernetes 将水平 Pod 自动扩缩实现为一个间歇运行的控制回路(它不是一个连续的过程)。间隔由
kube-controller-manager
的 --horizontal-pod-autoscaler-sync-period
参数设置(默认间隔为 15 秒)。
在每个时间段内,控制器管理器都会根据每个 HorizontalPodAutoscaler 定义中指定的指标查询资源利用率。
控制器管理器找到由 scaleTargetRef
定义的目标资源,然后根据目标资源的 .spec.selector
标签选择 Pod,
并从资源指标 API(针对每个 Pod 的资源指标)或自定义指标获取指标 API(适用于所有其他指标)。
对于按 Pod 统计的资源指标(如 CPU),控制器从资源指标 API 中获取每一个
HorizontalPodAutoscaler 指定的 Pod 的度量值,如果设置了目标使用率,
控制器获取每个 Pod 中的容器资源使用 情况,
并计算资源使用率。如果设置了 target 值,将直接使用原始数据(不再计算百分比)。
接下来,控制器根据平均的资源使用率或原始值计算出扩缩的比例,进而计算出目标副本数。
需要注意的是,如果 Pod 某些容器不支持资源采集,那么控制器将不会使用该 Pod 的 CPU 使用率。
下面的算法细节 章节将会介绍详细的算法。
如果 Pod 使用自定义指示,控制器机制与资源指标类似,区别在于自定义指标只使用原始值,而不是使用率。 如果 Pod 使用对象指标和外部指标(每个指标描述一个对象信息)。
这个指标将直接根据目标设定值相比较,并生成一个上面提到的扩缩比例。
在 autoscaling/v2beta2
版本 API 中,这个指标也可以根据 Pod 数量平分后再计算。 HorizontalPodAutoscaler 的常见用途是将其配置为从聚合 API
(metrics.k8s.io
、custom.metrics.k8s.io
或 external.metrics.k8s.io
)获取指标。
metrics.k8s.io
API 通常由名为 Metrics Server 的插件提供,需要单独启动。有关资源指标的更多信息,
请参阅 Metrics Server 。
对 Metrics API 的支持 解释了这些不同 API 的稳定性保证和支持状态
HorizontalPodAutoscaler 控制器访问支持扩缩的相应工作负载资源(例如:Deployment 和 StatefulSet)。
这些资源每个都有一个名为 scale
的子资源,该接口允许你动态设置副本的数量并检查它们的每个当前状态。
有关 Kubernetes API 子资源的一般信息,
请参阅 Kubernetes API 概念 。
算法细节 从最基本的角度来看,Pod 水平自动扩缩控制器根据当前指标和期望指标来计算扩缩比例。
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
例如,如果当前指标值为 200m
,而期望值为 100m
,则副本数将加倍,
因为 200.0 / 100.0 == 2.0
如果当前值为 50m
,则副本数将减半,
因为 50.0 / 100.0 == 0.5
。如果比率足够接近 1.0(在全局可配置的容差范围内,默认为 0.1),
则控制平面会跳过扩缩操作。
如果 HorizontalPodAutoscaler 指定的是 targetAverageValue
或 targetAverageUtilization
,
那么将会把指定 Pod 度量值的平均值做为 currentMetricValue
。
在检查容差并决定最终值之前,控制平面还会考虑是否缺少任何指标,
以及有多少 Pod 已就绪
。
所有设置了删除时间戳的 Pod(带有删除时间戳的对象正在关闭/移除的过程中)都会被忽略,
所有失败的 Pod 都会被丢弃。
如果某个 Pod 缺失度量值,它将会被搁置,只在最终确定扩缩数量时再考虑。
当使用 CPU 指标来扩缩时,任何还未就绪(还在初始化,或者可能是不健康的)状态的 Pod 或
最近的指标度量值采集于就绪状态前的 Pod,该 Pod 也会被搁置。
由于技术限制,HorizontalPodAutoscaler 控制器在确定是否保留某些 CPU 指标时无法准确确定 Pod 首次就绪的时间。
相反,如果 Pod 未准备好并在其启动后的一个可配置的短时间窗口内转换为准备好,它会认为 Pod “尚未准备好”。
该值使用 --horizontal-pod-autoscaler-initial-readiness-delay
标志配置,默认值为 30 秒。
一旦 Pod 准备就绪,如果它发生在自启动后较长的、可配置的时间内,它就会认为任何向准备就绪的转换都是第一个。
该值由 -horizontal-pod-autoscaler-cpu-initialization-period
标志配置,默认为 5 分钟。
在排除掉被搁置的 Pod 后,扩缩比例就会根据 currentMetricValue/desiredMetricValue
计算出来。
如果缺失某些度量值,控制平面会更保守地重新计算平均值,在需要缩小时假设这些 Pod 消耗了目标值的 100%,
在需要放大时假设这些 Pod 消耗了 0% 目标值。这可以在一定程度上抑制扩缩的幅度。
此外,如果存在任何尚未就绪的 Pod,工作负载会在不考虑遗漏指标或尚未就绪的 Pod 的情况下进行扩缩,
控制器保守地假设尚未就绪的 Pod 消耗了期望指标的 0%,从而进一步降低了扩缩的幅度。
考虑到尚未准备好的 Pod 和缺失的指标后,控制器会重新计算使用率。
如果新的比率与扩缩方向相反,或者在容差范围内,则控制器不会执行任何扩缩操作。
在其他情况下,新比率用于决定对 Pod 数量的任何更改。
注意,平均利用率的 原始 值是通过 HorizontalPodAutoscaler 状态体现的,
而不考虑尚未准备好的 Pod 或缺少的指标,即使使用新的使用率也是如此。
如果创建 HorizontalPodAutoscaler 时指定了多个指标,
那么会按照每个指标分别计算扩缩副本数,取最大值进行扩缩。
如果任何一个指标无法顺利地计算出扩缩副本数(比如,通过 API 获取指标时出错),
并且可获取的指标建议缩容,那么本次扩缩会被跳过。
这表示,如果一个或多个指标给出的 desiredReplicas
值大于当前值,HPA 仍然能实现扩容。
最后,在 HPA 控制器执行扩缩操作之前,会记录扩缩建议信息。
控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。
这个值可通过 kube-controller-manager
服务的启动参数
--horizontal-pod-autoscaler-downscale-stabilization
进行配置,
默认值为 5 分钟。
这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
API 对象 HorizontalPodAutoscaler 是 Kubernetes autoscaling
API 组中的 API 资源。
当前的稳定版本可以在 autoscaling/v2
API 版本中找到,其中包括对基于内存和自定义指标执行扩缩的支持。
在使用 autoscaling/v1
时,autoscaling/v2
中引入的新字段作为注释保留。
创建 HorizontalPodAutoscaler 对象时,需要确保所给的名称是一个合法的
DNS 子域名 。
有关 API 对象的更多信息,请查阅
HorizontalPodAutoscaler 对象文档 。
工作量规模的稳定性 在使用 HorizontalPodAutoscaler 管理一组副本的规模时,由于评估的指标的动态特性,
副本的数量可能会经常波动。这有时被称为 抖动(thrashing) 或 波动(flapping) 。
它类似于控制论中的 滞后(hysteresis) 概念。
滚动升级时扩缩 Kubernetes 允许你在 Deployment 上执行滚动更新。在这种情况下,Deployment 为你管理下层的 ReplicaSet。
当你为一个 Deployment 配置自动扩缩时,你要为每个 Deployment 绑定一个 HorizontalPodAutoscaler。
HorizontalPodAutoscaler 管理 Deployment 的 replicas
字段。
Deployment Controller 负责设置下层 ReplicaSet 的 replicas
字段,
以便确保在上线及后续过程副本个数合适。
如果你对一个副本个数被自动扩缩的 StatefulSet 执行滚动更新,该 StatefulSet
会直接管理它的 Pod 集合 (不存在类似 ReplicaSet 这样的中间资源)。
对资源指标的支持 HPA 的任何目标资源都可以基于其中的 Pods 的资源用量来实现扩缩。
在定义 Pod 规约时,类似 cpu
和 memory
这类资源请求必须被设定。
这些设定值被用来确定资源利用量并被 HPA 控制器用来对目标资源完成扩缩操作。
要使用基于资源利用率的扩缩,可以像下面这样指定一个指标源:
type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 60
基于这一指标设定,HPA 控制器会维持扩缩目标中的 Pods 的平均资源利用率在 60%。
利用率是 Pod 的当前资源用量与其请求值之间的比值。
关于如何计算利用率以及如何计算平均值的细节可参考算法 小节。
说明: 由于所有的容器的资源用量都会被累加起来,Pod 的总体资源用量值可能不会精确体现各个容器的资源用量。
这一现象也会导致一些问题,例如某个容器运行时的资源用量非常高,但因为 Pod
层面的资源用量总值让人在可接受的约束范围内,HPA 不会执行扩大目标对象规模的操作。
容器资源指标 特性状态: Kubernetes v1.20 [alpha]
HorizontalPodAutoscaler API 也支持容器指标源,这时 HPA 可以跟踪记录一组 Pod
中各个容器的资源用量,进而触发扩缩目标对象的操作。
容器资源指标的支持使得你可以为特定 Pod 中最重要的容器配置规模扩缩阈值。
例如,如果你有一个 Web 应用和一个执行日志操作的边车容器,你可以基于 Web
应用的资源用量来执行扩缩,忽略边车容器的存在及其资源用量。
如果你更改扩缩目标对象,令其使用新的、包含一组不同的容器的 Pod 规约,你就需要修改
HPA 的规约才能基于新添加的容器来执行规模扩缩操作。
如果指标源中指定的容器不存在或者仅存在于部分 Pod 中,那么这些 Pod 会被忽略,
HPA 会重新计算资源用量值。参阅算法 小节进一步了解计算细节。
要使用容器资源用量来完成自动扩缩,可以像下面这样定义指标源:
type : ContainerResource
containerResource :
name : cpu
container : application
target :
type : Utilization
averageUtilization : 60
在上面的例子中,HPA 控制器会对目标对象执行扩缩操作以确保所有 Pod 中
application
容器的平均 CPU 用量为 60%。
说明: 如果你要更改 HorizontalPodAutoscaler 所跟踪记录的容器的名称,你可以按一定顺序来执行这一更改,
确保在应用更改的过程中用来判定扩缩行为的容器可用。
在更新定义容器的资源(如 Deployment)之前,你需要更新相关的 HPA,
使之能够同时跟踪记录新的和老的容器名称。这样,HPA 就能够在整个更新过程中继续计算并提供扩缩操作建议。
一旦你已经将容器名称变更这一操作应用到整个负载对象至上,就可以从 HPA
的规约中去掉老的容器名称,完成清理操作。
扩展自定义指标 特性状态: Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本将此功能作为 beta 功能提供)
如果你使用 autoscaling/v2
API 版本,则可以将 HorizontalPodAutoscaler
配置为基于自定义指标(未内置于 Kubernetes 或任何 Kubernetes 组件)进行扩缩。
HorizontalPodAutoscaler 控制器能够从 Kubernetes API 查询这些自定义指标。
有关要求,请参阅对 Metrics APIs 的支持 。
基于多个指标来执行扩缩 特性状态: Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本将此功能作为 beta 功能提供)
如果你使用 autoscaling/v2
API 版本,你可以为 HorizontalPodAutoscaler 指定多个指标以进行扩缩。
HorizontalPodAutoscaler 控制器评估每个指标,并根据该指标提出一个新的比例。
HorizontalPodAutoscaler 采用为每个指标推荐的最大比例,
并将工作负载设置为该大小(前提是这不大于你配置的总体最大值)。
对 Metrics API 的支持 默认情况下,HorizontalPodAutoscaler 控制器会从一系列的 API 中检索度量值。
集群管理员需要确保下述条件,以保证 HPA 控制器能够访问这些 API:
启用了 API 聚合层
相应的 API 已注册:
对于资源指标,将使用 metrics.k8s.io
API,一般由 metrics-server 提供。
它可以作为集群插件启动。
对于自定义指标,将使用 custom.metrics.k8s.io
API。
它由其他度量指标方案厂商的“适配器(Adapter)” API 服务器提供。
检查你的指标管道以查看是否有可用的 Kubernetes 指标适配器。
对于外部指标,将使用 external.metrics.k8s.io
API。可能由上面的自定义指标适配器提供。
关于指标来源以及其区别的更多信息,请参阅相关的设计文档,
HPA V2 ,
custom.metrics.k8s.io 和
external.metrics.k8s.io 。
关于如何使用它们的示例,
请参考使用自定义指标的教程
和使用外部指标的教程 。
可配置的扩缩行为 特性状态: Kubernetes v1.23 [stable]
(之前的 autoscaling/v2beta2
API 版本将此功能作为 beta 功能提供)
如果你使用 v2
HorizontalPodAutoscaler API,你可以使用 behavior
字段
(请参阅 API 参考 )
来配置单独的放大和缩小行为。你可以通过在行为字段下设置 scaleUp
和/或 scaleDown
来指定这些行为。
你可以指定一个 “稳定窗口”,以防止扩缩目标的副本计数发生波动 。
扩缩策略还允许你在扩缩时控制副本的变化率。
扩缩策略 可以在规约的 behavior
部分中指定一个或多个扩缩策略。当指定多个策略时,
允许最大更改量的策略是默认选择的策略。以下示例显示了缩小时的这种行为:
behavior :
scaleDown :
policies :
- type : Pods
value : 4
periodSeconds : 60
- type : Percent
value : 10
periodSeconds : 60
periodSeconds
表示在过去的多长时间内要求策略值为真。
第一个策略(Pods)允许在一分钟内最多缩容 4 个副本。第二个策略(Percent)
允许在一分钟内最多缩容当前副本个数的百分之十。
由于默认情况下会选择容许更大程度作出变更的策略,只有 Pod 副本数大于 40 时,
第二个策略才会被采用。如果副本数为 40 或者更少,则应用第一个策略。
例如,如果有 80 个副本,并且目标必须缩小到 10 个副本,那么在第一步中将减少 8 个副本。
在下一轮迭代中,当副本的数量为 72 时,10% 的 Pod 数为 7.2,但是这个数字向上取整为 8。
在 autoscaler 控制器的每个循环中,将根据当前副本的数量重新计算要更改的 Pod 数量。
当副本数量低于 40 时,应用第一个策略(Pods),一次减少 4 个副本。
可以指定扩缩方向的 selectPolicy
字段来更改策略选择。
通过设置 Min
的值,它将选择副本数变化最小的策略。
将该值设置为 Disabled
将完全禁用该方向的扩缩。
稳定窗口 当用于扩缩的指标不断波动时,稳定窗口用于限制副本计数的波动 。
自动扩缩算法使用此窗口来推断先前的期望状态并避免对工作负载规模进行不必要的更改。
例如,在以下示例代码段中,为 scaleDown
指定了稳定窗口。
behavior :
scaleDown :
stabilizationWindowSeconds : 300
当指标显示目标应该缩容时,自动扩缩算法查看之前计算的期望状态,并使用指定时间间隔内的最大值。
在上面的例子中,过去 5 分钟的所有期望状态都会被考虑。
这近似于滚动最大值,并避免了扩缩算法频繁删除 Pod 而又触发重新创建等效 Pod。
默认行为 要使用自定义扩缩,不必指定所有字段。
只有需要自定义的字段才需要指定。
这些自定义值与默认值合并。
默认值与 HPA 算法中的现有行为匹配。
behavior :
scaleDown :
stabilizationWindowSeconds : 300
policies :
- type : Percent
value : 100
periodSeconds : 15
scaleUp :
stabilizationWindowSeconds : 0
policies :
- type : Percent
value : 100
periodSeconds : 15
- type : Pods
value : 4
periodSeconds : 15
selectPolicy : Max
用于缩小稳定窗口的时间为 300 秒(或是 --horizontal-pod-autoscaler-downscale-stabilization
参数设定值)。
只有一种缩容的策略,允许 100% 删除当前运行的副本,这意味着扩缩目标可以缩小到允许的最小副本数。
对于扩容,没有稳定窗口。当指标显示目标应该扩容时,目标会立即扩容。
这里有两种策略,每 15 秒添加 4 个 Pod 或 100% 当前运行的副本数,直到 HPA 达到稳定状态。
示例:更改缩容稳定窗口 将下面的 behavior 配置添加到 HPA 中,可提供一个 1 分钟的自定义缩容稳定窗口:
behavior :
scaleDown :
stabilizationWindowSeconds : 60
示例:限制缩容速率 将下面的 behavior 配置添加到 HPA 中,可限制 Pod 被 HPA 删除速率为每分钟 10%:
behavior :
scaleDown :
policies :
- type : Percent
value : 10
periodSeconds : 60
为了确保每分钟删除的 Pod 数不超过 5 个,可以添加第二个缩容策略,大小固定为 5,并将 selectPolicy
设置为最小值。
将 selectPolicy
设置为 Min
意味着 autoscaler 会选择影响 Pod 数量最小的策略:
behavior :
scaleDown :
policies :
- type : Percent
value : 10
periodSeconds : 60
- type : Pods
value : 5
periodSeconds : 60
selectPolicy : Min
示例:禁用缩容 selectPolicy
的值 Disabled
会关闭对给定方向的缩容。
因此使用以下策略,将会阻止缩容:
behavior :
scaleDown :
selectPolicy : Disabled
kubectl 对 HorizontalPodAutoscaler 的支持 与每个 API 资源一样,HorizontalPodAutoscaler 都被 kubectl
以标准方式支持。
你可以使用 kubectl create
命令创建一个新的自动扩缩器。
你可以通过 kubectl get hpa
列出自动扩缩器或通过 kubectl describe hpa
获取详细描述。
最后,你可以使用 kubectl delete hpa
删除自动扩缩器。
此外,还有一个特殊的 kubectl autoscale
命令用于创建 HorizontalPodAutoscaler 对象。
例如,执行 kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80
将为 ReplicaSet foo 创建一个自动扩缩器,目标 CPU 利用率设置为 80%
,副本数在 2 到 5 之间。
隐式维护状态禁用 你可以在不必更改 HPA 配置的情况下隐式地为某个目标禁用 HPA。
如果此目标的期望副本个数被设置为 0,而 HPA 的最小副本个数大于 0,
则 HPA 会停止调整目标(并将其自身的 ScalingActive
状况设置为 false
),
直到你通过手动调整目标的期望副本个数或 HPA 的最小副本个数来重新激活。
将 Deployment 和 StatefulSet 迁移到水平自动扩缩 当启用 HPA 时,建议从它们的清单 中删除
Deployment 和/或 StatefulSet 的 spec.replicas
的值。
如果不这样做,则只要应用对该对象的更改,例如通过 kubectl apply -f deployment.yaml
,
这将指示 Kubernetes 将当前 Pod 数量扩缩到 spec.replicas
键的值。这可能不是所希望的,
并且当 HPA 处于活动状态时可能会很麻烦。
请记住,删除 spec.replicas
可能会导致 Pod 计数一次性降级,因为此键的默认值为 1
(参考 Deployment Replicas )。
更新后,除 1 之外的所有 Pod 都将开始其终止程序。之后的任何部署应用程序都将正常运行,
并根据需要遵守滚动更新配置。你可以根据修改部署的方式选择以下两种方法之一来避免这种降级:
kubectl apply edit-last-applied deployment/<Deployment 名称>
在编辑器中,删除 spec.replicas
。当你保存并退出编辑器时,kubectl
会应用更新。
在此步骤中不会更改 Pod 计数。 你现在可以从清单中删除 spec.replicas
。如果你使用源代码管理,
还应提交你的更改或采取任何其他步骤来修改源代码,以适应你如何跟踪更新。 从这里开始,你可以运行 kubectl apply -f deployment.yaml
接下来 如果你在集群中配置自动扩缩,你可能还需要考虑运行集群级别的自动扩缩器,
例如 Cluster Autoscaler 。
有关 HorizontalPodAutoscaler 的更多信息:
4.8.7 - HorizontalPodAutoscaler 演练 HorizontalPodAutoscaler (简称 HPA )
自动更新工作负载资源(例如 Deployment 或者
StatefulSet ),
目的是自动扩缩工作负载以满足需求。
水平扩缩意味着对增加的负载的响应是部署更多的 Pod 。
这与 “垂直(Vertical)” 扩缩不同,对于 Kubernetes,
垂直扩缩意味着将更多资源(例如:内存或 CPU)分配给已经为工作负载运行的 Pod。
如果负载减少,并且 Pod 的数量高于配置的最小值,
HorizontalPodAutoscaler 会指示工作负载资源(Deployment、StatefulSet 或其他类似资源)缩减。
本文档将引导你完成启用 HorizontalPodAutoscaler 以自动管理示例 Web 应用程序的扩缩的示例。
此示例工作负载是运行一些 PHP 代码的 Apache httpd。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.23.
要获知版本信息,请输入
kubectl version
.
如果你运行的是旧版本的 Kubernetes,请参阅该版本的文档版本
(可用的文档版本 )。
按照本演练进行操作,你需要一个部署并配置了
Metrics Server 的集群。
Kubernetes Metrics Server 从集群中的 kubelets 收集资源指标,
并通过 Kubernetes API 公开这些指标,
使用 APIService 添加代表指标读数的新资源。
要了解如何部署 Metrics Server,请参阅
metrics-server 文档 。
运行 php-apache 服务器并暴露服务 为了演示 HorizontalPodAutoscaler,你将首先启动一个 Deployment 用 hpa-example
镜像运行一个容器,
然后使用以下清单文件将其暴露为一个 服务(Service) :
apiVersion : apps/v1
kind : Deployment
metadata :
name : php-apache
spec :
selector :
matchLabels :
run : php-apache
replicas : 1
template :
metadata :
labels :
run : php-apache
spec :
containers :
- name : php-apache
image : registry.k8s.io/hpa-example
ports :
- containerPort : 80
resources :
limits :
cpu : 500m
requests :
cpu : 200m
---
apiVersion : v1
kind : Service
metadata :
name : php-apache
labels :
run : php-apache
spec :
ports :
- port : 80
selector :
run : php-apache
为此,运行下面的命令:
kubectl apply -f https://k8s.io/examples/application/php-apache.yaml
deployment.apps/php-apache created
service/php-apache created
创建 HorizontalPodAutoscaler 现在服务器正在运行,使用 kubectl
创建自动扩缩器。
kubectl autoscale
子命令是 kubectl
的一部分,
可以帮助你执行此操作。
你将很快运行一个创建 HorizontalPodAutoscaler 的命令,
该 HorizontalPodAutoscaler 维护由你在这些说明的第一步中创建的 php-apache Deployment 控制的 Pod 存在 1 到 10 个副本。
粗略地说,HPA 控制器 将增加和减少副本的数量
(通过更新 Deployment)以保持所有 Pod 的平均 CPU 利用率为 50%。
Deployment 然后更新 ReplicaSet —— 这是所有 Deployment 在 Kubernetes 中工作方式的一部分 ——
然后 ReplicaSet 根据其 .spec
的更改添加或删除 Pod。
由于每个 Pod 通过 kubectl run
请求 200 milli-cores,这意味着平均 CPU 使用率为 100 milli-cores。
有关算法的更多详细信息,
请参阅算法详细信息 。
创建 HorizontalPodAutoscaler:
kubectl autoscale deployment php-apache --cpu-percent= 50 --min= 1 --max= 10
horizontalpodautoscaler.autoscaling/php-apache autoscaled
你可以通过运行以下命令检查新制作的 HorizontalPodAutoscaler 的当前状态:
# 你可以使用 “hpa” 或 “horizontalpodautoscaler”;任何一个名字都可以。
kubectl get hpa
输出类似于:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 18s
(如果你看到其他具有不同名称的 HorizontalPodAutoscalers,这意味着它们已经存在,这通常不是问题)。
请注意当前的 CPU 利用率是 0%,这是由于我们尚未发送任何请求到服务器
(TARGET
列显示了相应 Deployment 所控制的所有 Pod 的平均 CPU 利用率)。
增加负载 接下来,看看自动扩缩器如何对增加的负载做出反应。
为此,你将启动一个不同的 Pod 作为客户端。
客户端 Pod 中的容器在无限循环中运行,向 php-apache 服务发送查询。
# 在单独的终端中运行它
# 以便负载生成继续,你可以继续执行其余步骤
kubectl run -i --tty load-generator --rm --image= busybox:1.28 --restart= Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
现在执行:
# 准备好后按 Ctrl+C 结束观察
kubectl get hpa php-apache --watch
一分钟时间左右之后,通过以下命令,我们可以看到 CPU 负载升高了;例如:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 305% / 50% 1 10 1 3m
然后,更多的副本被创建。例如:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 305% / 50% 1 10 7 3m
这时,由于请求增多,CPU 利用率已经升至请求值的 305%。
可以看到,Deployment 的副本数量已经增长到了 7:
kubectl get deployment php-apache
你应该会看到与 HorizontalPodAutoscaler 中的数字与副本数匹配
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 7/7 7 7 19m
说明: 有时最终副本的数量可能需要几分钟才能稳定下来。由于环境的差异,
不同环境中最终的副本数量可能与本示例中的数量不同。
停止产生负载 要完成该示例,请停止发送负载。
在我们创建 busybox
容器的终端中,输入 <Ctrl> + C
来终止负载的产生。
然后验证结果状态(大约一分钟后):
# 准备好后按 Ctrl+C 结束观察
kubectl get hpa php-apache --watch
输出类似于:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 11m
Deployment 也显示它已经缩小了:
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 1/1 1 1 27m
一旦 CPU 利用率降至 0,HPA 会自动将副本数缩减为 1。
自动扩缩完成副本数量的改变可能需要几分钟的时间。
基于多项度量指标和自定义度量指标自动扩缩 利用 autoscaling/v2
API 版本,你可以在自动扩缩 php-apache 这个
Deployment 时使用其他度量指标。
首先,将 HorizontalPodAutoscaler 的 YAML 文件改为 autoscaling/v2
格式:
kubectl get hpa php-apache -o yaml > /tmp/hpa-v2.yaml
在编辑器中打开 /tmp/hpa-v2.yaml
,你应看到如下所示的 YAML 文件:
apiVersion : autoscaling/v2
kind : HorizontalPodAutoscaler
metadata :
name : php-apache
spec :
scaleTargetRef :
apiVersion : apps/v1
kind : Deployment
name : php-apache
minReplicas : 1
maxReplicas : 10
metrics :
- type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 50
status :
observedGeneration : 1
lastScaleTime : <some-time>
currentReplicas : 1
desiredReplicas : 1
currentMetrics :
- type : Resource
resource :
name : cpu
current :
averageUtilization : 0
averageValue : 0
需要注意的是,targetCPUUtilizationPercentage
字段已经被名为 metrics
的数组所取代。
CPU 利用率这个度量指标是一个 resource metric (资源度量指标),因为它表示容器上指定资源的百分比。
除 CPU 外,你还可以指定其他资源度量指标。默认情况下,目前唯一支持的其他资源度量指标为内存。
只要 metrics.k8s.io
API 存在,这些资源度量指标就是可用的,并且他们不会在不同的 Kubernetes 集群中改变名称。
你还可以指定资源度量指标使用绝对数值,而不是百分比,你需要将 target.type
从
Utilization
替换成 AverageValue
,同时设置 target.averageValue
而非 target.averageUtilization
的值。
还有两种其他类型的度量指标,他们被认为是 custom metrics (自定义度量指标):
即 Pod 度量指标和 Object 度量指标。
这些度量指标可能具有特定于集群的名称,并且需要更高级的集群监控设置。
第一种可选的度量指标类型是 Pod 度量指标 。这些指标从某一方面描述了 Pod,
在不同 Pod 之间进行平均,并通过与一个目标值比对来确定副本的数量。
它们的工作方式与资源度量指标非常相像,只是它们 仅 支持 target
类型为 AverageValue
。
Pod 度量指标通过如下代码块定义:
type : Pods
pods :
metric :
name : packets-per-second
target :
type : AverageValue
averageValue : 1k
第二种可选的度量指标类型是对象 (Object)度量指标 。
这些度量指标用于描述在相同名字空间中的别的对象,而非 Pod。
请注意这些度量指标不一定来自某对象,它们仅用于描述这些对象。
对象度量指标支持的 target
类型包括 Value
和 AverageValue
。
如果是 Value
类型,target
值将直接与 API 返回的度量指标比较,
而对于 AverageValue
类型,API 返回的度量值将按照 Pod 数量拆分,
然后再与 target
值比较。
下面的 YAML 文件展示了一个表示 requests-per-second
的度量指标。
type : Object
object :
metric :
name : requests-per-second
describedObject :
apiVersion : networking.k8s.io/v1
kind : Ingress
name : main-route
target :
type : Value
value : 2k
如果你指定了多个上述类型的度量指标,HorizontalPodAutoscaler 将会依次考量各个指标。
HorizontalPodAutoscaler 将会计算每一个指标所提议的副本数量,然后最终选择一个最高值。
比如,如果你的监控系统能够提供网络流量数据,你可以通过 kubectl edit
命令将上述 Horizontal Pod Autoscaler 的定义更改为:
apiVersion : autoscaling/v2
kind : HorizontalPodAutoscaler
metadata :
name : php-apache
spec :
scaleTargetRef :
apiVersion : apps/v1
kind : Deployment
name : php-apache
minReplicas : 1
maxReplicas : 10
metrics :
- type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 50
- type : Pods
pods :
metric :
name : packets-per-second
target :
type : AverageValue
averageValue : 1k
- type : Object
object :
metric :
name : requests-per-second
describedObject :
apiVersion : networking.k8s.io/v1
kind : Ingress
name : main-route
target :
type : Value
value : 10k
status :
observedGeneration : 1
lastScaleTime : <some-time>
currentReplicas : 1
desiredReplicas : 1
currentMetrics :
- type : Resource
resource :
name : cpu
current :
averageUtilization : 0
averageValue : 0
- type : Object
object :
metric :
name : requests-per-second
describedObject :
apiVersion : networking.k8s.io/v1
kind : Ingress
name : main-route
current :
value : 10k
这样,你的 HorizontalPodAutoscaler 将会尝试确保每个 Pod 的 CPU 利用率在 50% 以内,
每秒能够服务 1000 个数据包请求,
并确保所有在 Ingress 后的 Pod 每秒能够服务的请求总数达到 10000 个。
基于更特别的度量值来扩缩 许多度量流水线允许你通过名称或附加的 标签 来描述度量指标。
对于所有非资源类型度量指标(Pod、Object 和后面将介绍的 External),
可以额外指定一个标签选择算符。例如,如果你希望收集包含 verb
标签的
http_requests
度量指标,可以按如下所示设置度量指标块,使得扩缩操作仅针对
GET 请求执行:
type : Object
object :
metric :
name : http_requests
selector : {matchLabels : {verb : GET}}
这个选择算符使用与 Kubernetes 标签选择算符相同的语法。
如果名称和标签选择算符匹配到多个系列,监测管道会决定如何将多个系列合并成单个值。
选择算符是可以累加的,它不会选择目标以外的对象(类型为 Pods
的目标 Pod 或者类型为 Object
的目标对象)。
运行在 Kubernetes 上的应用程序可能需要基于与 Kubernetes
集群中的任何对象没有明显关系的度量指标进行自动扩缩,
例如那些描述与任何 Kubernetes 名字空间中的服务都无直接关联的度量指标。
在 Kubernetes 1.10 及之后版本中,你可以使用外部度量指标(external metrics)。
使用外部度量指标时,需要了解你所使用的监控系统,相关的设置与使用自定义指标时类似。
外部度量指标使得你可以使用你的监控系统的任何指标来自动扩缩你的集群。
你需要在 metric
块中提供 name
和 selector
,同时将类型由 Object
改为 External
。
如果 metricSelector
匹配到多个度量指标,HorizontalPodAutoscaler 将会把它们加和。
外部度量指标同时支持 Value
和 AverageValue
类型,这与 Object
类型的度量指标相同。
例如,如果你的应用程序处理来自主机上消息队列的任务,
为了让每 30 个任务有 1 个工作者实例,你可以将下面的内容添加到
HorizontalPodAutoscaler 的配置中。
- type : External
external :
metric :
name : queue_messages_ready
selector :
matchLabels :
queue : "worker_tasks"
target :
type : AverageValue
averageValue : 30
如果可能,还是推荐定制度量指标而不是外部度量指标,因为这便于让系统管理员加固定制度量指标 API。
而外部度量指标 API 可以允许访问所有的度量指标。
当暴露这些服务时,系统管理员需要仔细考虑这个问题。
附录:Horizontal Pod Autoscaler 状态条件 使用 autoscaling/v2
格式的 HorizontalPodAutoscaler 时,你将可以看到
Kubernetes 为 HorizongtalPodAutoscaler 设置的状态条件(Status Conditions)。
这些状态条件可以显示当前 HorizontalPodAutoscaler 是否能够执行扩缩以及是否受到一定的限制。
status.conditions
字段展示了这些状态条件。
可以通过 kubectl describe hpa
命令查看当前影响 HorizontalPodAutoscaler
的各种状态条件信息:
kubectl describe hpa cm-test
Name: cm-test
Namespace: prom
Labels: <none>
Annotations: <none>
CreationTimestamp: Fri, 16 Jun 2017 18:09:22 +0000
Reference: ReplicationController/cm-test
Metrics: ( current / target )
"http_requests" on pods: 66m / 500m
Min replicas: 1
Max replicas: 4
ReplicationController pods: 1 current / 1 desired
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ReadyForNewScale the last scale time was sufficiently old as to warrant a new scale
ScalingActive True ValidMetricFound the HPA was able to successfully calculate a replica count from pods metric http_requests
ScalingLimited False DesiredWithinRange the desired replica count is within the acceptable range
Events:
对于上面展示的这个 HorizontalPodAutoscaler,我们可以看出有若干状态条件处于健康状态。
首先,AbleToScale
表明 HPA 是否可以获取和更新扩缩信息,以及是否存在阻止扩缩的各种回退条件。
其次,ScalingActive
表明 HPA 是否被启用(即目标的副本数量不为零) 以及是否能够完成扩缩计算。
当这一状态为 False
时,通常表明获取度量指标存在问题。
最后一个条件 ScalingLimited
表明所需扩缩的值被 HorizontalPodAutoscaler
所定义的最大或者最小值所限制(即已经达到最大或者最小扩缩值)。
这通常表明你可能需要调整 HorizontalPodAutoscaler 所定义的最大或者最小副本数量的限制了。
量纲 HorizontalPodAutoscaler 和 度量指标 API 中的所有的度量指标使用 Kubernetes
中称为量纲(Quantity) 的特殊整数表示。
例如,数量 10500m
用十进制表示为 10.5
。
如果可能的话,度量指标 API 将返回没有后缀的整数,否则返回以千分单位的数量。
这意味着你可能会看到你的度量指标在 1
和 1500m
(也就是在十进制记数法中的 1
和 1.5
)之间波动。
其他可能的情况 以声明式方式创建 Autoscaler 除了使用 kubectl autoscale
命令,也可以使用以下清单以声明方式创建 HorizontalPodAutoscaler:
apiVersion : autoscaling/v1
kind : HorizontalPodAutoscaler
metadata :
name : php-apache
spec :
scaleTargetRef :
apiVersion : apps/v1
kind : Deployment
name : php-apache
minReplicas : 1
maxReplicas : 10
targetCPUUtilizationPercentage : 50
使用如下命令创建 Autoscaler:
kubectl create -f https://k8s.io/examples/application/hpa/php-apache.yaml
horizontalpodautoscaler.autoscaling/php-apache created
4.8.8 - 为应用程序设置干扰预算(Disruption Budget) 特性状态: Kubernetes v1.21 [stable]
本文展示如何限制应用程序的并发干扰数量,在允许集群管理员管理集群节点的同时保证高可用。
准备开始 你的 Kubernetes 服务器版本必须不低于版本 v1.21.
要获知版本信息,请输入
kubectl version
.
你是 Kubernetes 集群中某应用的所有者,该应用有高可用要求。 你应了解如何部署无状态应用
和/或有状态应用 。 你应当已经阅读过关于 Pod 干扰 的文档。 用户应当与集群所有者或服务提供者确认其遵从 Pod 干扰预算(Pod Disruption Budgets)的规则。 用 PodDisruptionBudget 来保护应用 确定想要使用 PodDisruptionBudget (PDB) 来保护的应用。 考虑应用对干扰的反应。 以 YAML 文件形式定义 PDB 。 通过 YAML 文件创建 PDB 对象。 确定要保护的应用 用户想要保护通过内置的 Kubernetes 控制器指定的应用,这是最常见的使用场景:
Deployment ReplicationController ReplicaSet StatefulSet 在这种情况下,在控制器的 .spec.selector
字段中做记录,并在 PDB 的
.spec.selector
字段中加入同样的选择算符。
从 1.15 版本开始,PDB 支持启用
Scale 子资源
的自定义控制器。
用户也可以用 PDB 来保护不受上述控制器控制的 Pod,或任意的 Pod 集合,但是正如
任意控制器和选择算符 中描述的,这里存在一些限制。
考虑应用对干扰的反应 确定在自发干扰时,多少实例可以在短时间内同时关闭。
无状态的前端:关注:不能降低服务能力 10% 以上。解决方案:例如,使用 PDB,指定其 minAvailable 值为 90%。 单实例有状态应用:关注:不要在不通知的情况下终止该应用。可能的解决方案 1:不使用 PDB,并忍受偶尔的停机。 可能的解决方案 2:设置 maxUnavailable=0 的 PDB。
意为(Kubernetes 范畴之外的)集群操作人员需要在终止应用前与用户协商,
协商后准备停机,然后删除 PDB 表示准备接受干扰,后续再重新创建。 多实例有状态应用,如 Consul、ZooKeeper 或 etcd:关注:不要将实例数量减少至低于仲裁规模,否则将出现写入失败。可能的解决方案 1:设置 maxUnavailable 值为 1 (适用于不同规模的应用)。 可能的解决方案 2:设置 minAvailable 值为仲裁规模(例如规模为 5 时设置为 3)。
(允许同时出现更多的干扰)。 可重新启动的批处理任务:关注:自发干扰的情况下,需要确保任务完成。可能的解决方案:不创建 PDB。 任务控制器会创建一个替换 Pod。 指定百分比时的舍入逻辑 minAvailable
或 maxUnavailable
的值可以表示为整数或百分比。
指定整数值时,它表示 Pod 个数。例如,如果将 minAvailable 设置为 10,
那么即使在干扰期间,也必须始终有 10 个Pod可用。 通过将值设置为百分比的字符串表示形式(例如 “50%”)来指定百分比时,它表示占总 Pod 数的百分比。
例如,如果将 "maxUnavailable" 设置为 “50%”,则干扰期间只允许 50% 的 Pod 不可用。 如果将值指定为百分比,则可能无法映射到确切数量的 Pod。例如,如果你有 7 个 Pod,
并且你将 minAvailable
设置为 "50%"
,具体是 3 个 Pod 或 4 个 Pod 必须可用
并非显而易见。
Kubernetes 采用向上取整到最接近的整数的办法,因此在这种情况下,必须有 4 个 Pod。
你可以检查控制此行为的
代码 。
指定 PodDisruptionBudget 一个 PodDisruptionBudget
有 3 个字段:
标签选择算符 .spec.selector
用于指定其所作用的 Pod 集合,该字段为必需字段。 .spec.minAvailable
表示驱逐后仍须保证可用的 Pod 数量。即使因此影响到 Pod 驱逐
(即该条件在和 Pod 驱逐发生冲突时优先保证)。
minAvailable
值可以是绝对值,也可以是百分比。.spec.maxUnavailable
(Kubernetes 1.7 及更高的版本中可用)表示驱逐后允许不可用的
Pod 的最大数量。其值可以是绝对值或是百分比。说明: policy/v1beta1
和 policy/v1
API 中 PodDisruptionBudget 的空选择算符的行为
略有不同。在 policy/v1beta1
中,空的选择算符不会匹配任何 Pods,而
policy/v1
中,空的选择算符会匹配名字空间中所有 Pods。
用户在同一个 PodDisruptionBudget
中只能够指定 maxUnavailable
和 minAvailable
中的一个。
maxUnavailable
只能够用于控制存在相应控制器的 Pod 的驱逐(即不受控制器控制的 Pod 不在
maxUnavailable
控制范围内)。在下面的示例中,
“所需副本” 指的是相应控制器的 scale
,控制器对 PodDisruptionBudget
所选择的 Pod 进行管理。
示例 1:设置 minAvailable
值为 5 的情况下,驱逐时需保证 PodDisruptionBudget 的 selector
选中的 Pod 中 5 个或 5 个以上处于健康状态。
示例 2:设置 minAvailable
值为 30% 的情况下,驱逐时需保证 Pod 所需副本的至少 30% 处于健康状态。
示例 3:设置 maxUnavailable
值为 5 的情况下,驱逐时需保证所需副本中最多 5 个处于不可用状态。
示例 4:设置 maxUnavailable
值为 30% 的情况下,驱逐时需保证所需副本中最多 30% 处于不可用状态。
在典型用法中,干扰预算会被用于一个控制器管理的一组 Pod 中 —— 例如:一个 ReplicaSet 或 StatefulSet
中的 Pod。
说明: 干扰预算并不能真正保证指定数量/百分比的 Pod 一直处于运行状态。例如: 当 Pod 集合的
规模处于预算指定的最小值时,承载集合中某个 Pod 的节点发生了故障,这样就导致集合中可用 Pod 的
数量低于预算指定值。预算只能够针对自发的驱逐提供保护,而不能针对所有 Pod 不可用的诱因。
如果你将 maxUnavailable
的值设置为 0%(或 0)或设置 minAvailable
值为 100%(或等于副本数)
则会阻止所有的自愿驱逐。
当你为 ReplicaSet 等工作负载对象设置阻止自愿驱逐时,你将无法成功地腾空运行其中一个 Pod 的节点。
如果你尝试腾空正在运行着被阻止驱逐的 Pod 的节点,则腾空永远不会完成。
按照 PodDisruptionBudget
的语义,这是允许的。
用户可以在下面看到 pod 干扰预算定义的示例,它们与带有 app: zookeeper
标签的 pod 相匹配:
使用 minAvailable 的PDB 示例:
apiVersion : policy/v1
kind : PodDisruptionBudget
metadata :
name : zk-pdb
spec :
minAvailable : 2
selector :
matchLabels :
app : zookeeper
使用 maxUnavailable 的 PDB 示例:
apiVersion : policy/v1
kind : PodDisruptionBudget
metadata :
name : zk-pdb
spec :
maxUnavailable : 1
selector :
matchLabels :
app : zookeeper
例如,如果上述 zk-pdb
选择的是一个规格为 3 的 StatefulSet 对应的 Pod,
那么上面两种规范的含义完全相同。
推荐使用 maxUnavailable
,因为它自动响应控制器副本数量的变化。
创建 PDB 对象 你可以使用 kubectl 创建或更新 PDB 对象。
kubectl apply -f mypdb.yaml
PDB 对象无法更新,必须删除后重新创建。
检查 PDB 的状态 使用 kubectl 来确认 PDB 被创建。
假设用户的名字空间下没有匹配 app: zookeeper
的 Pod,用户会看到类似下面的信息:
kubectl get poddisruptionbudgets
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
zk-pdb 2 N/A 0 7s
假设有匹配的 Pod (比如说 3 个), 那么用户会看到类似下面的信息:
kubectl get poddisruptionbudgets
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
zk-pdb 2 N/A 1 7s
ALLOWED DISRUPTIONS
值非 0 意味着干扰控制器已经感知到相应的 Pod,对匹配的 Pod 进行统计,
并更新了 PDB 的状态。
用户可以通过以下命令获取更多 PDB 状态相关信息:
kubectl get poddisruptionbudgets zk-pdb -o yaml
apiVersion : policy/v1
kind : PodDisruptionBudget
metadata :
annotations :
…
creationTimestamp : "2020-03-04T04:22:56Z"
generation : 1
name : zk-pdb
…
status :
currentHealthy : 3
desiredHealthy : 2
disruptionsAllowed : 1
expectedPods : 3
observedGeneration : 1
任意控制器和选择算符 如果你只使用与内置的应用控制器(Deployment、ReplicationController、ReplicaSet 和 StatefulSet)
对应的 PDB,也就是 PDB 的选择算符与 控制器的选择算符相匹配,那么可以跳过这一节。
你可以使用这样的 PDB:它对应的 Pod 可能由其他类型的控制器控制,可能由 "operator" 控制,
也可能为“裸的(不受控制器控制)” Pod,但该类 PDB 存在以下限制:
只能够使用 .spec.minAvailable
,而不能够使用 .spec.maxUnavailable。
只能够使用整数作为 .spec.minAvailable
的值,而不能使用百分比。 你可以令选择算符选择一个内置控制器所控制 Pod 的子集或父集。
驱逐 API 将不允许驱逐被多个 PDB 覆盖的任何 Pod,因此大多数用户都希望避免重叠的选择算符。重叠 PDB 的一种合理用途是当 Pod 从一个 PDB 过渡到另一个 PDB 时再使用。
4.8.9 - 从 Pod 中访问 Kubernetes API 本指南演示了如何从 Pod 中访问 Kubernetes API。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
从 Pod 中访问 API 从 Pod 内部访问 API 时,定位 API 服务器和向服务器认证身份的操作
与外部客户端场景不同。
从 Pod 使用 Kubernetes API 的最简单的方法就是使用官方的
客户端库 。
这些库可以自动发现 API 服务器并进行身份验证。
使用官方客户端库 从一个 Pod 内部连接到 Kubernetes API 的推荐方式为:
在以上场景中,客户端库都使用 Pod 的服务账号凭据来与 API 服务器安全地通信。
直接访问 REST API 在运行在 Pod 中时,可以通过 default
命名空间中的名为 kubernetes
的服务访问
Kubernetes API 服务器。也就是说,Pod 可以使用 kubernetes.default.svc
主机名
来查询 API 服务器。官方客户端库自动完成这个工作。
向 API 服务器进行身份认证的推荐做法是使用
服务账号 凭据。
默认情况下,每个 Pod 与一个服务账号关联,该服务账户的凭证(令牌)放置在此 Pod 中
每个容器的文件系统树中的 /var/run/secrets/kubernetes.io/serviceaccount/token
处。
如果证书包可用,则凭证包被放入每个容器的文件系统树中的
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
处,
且将被用于验证 API 服务器的服务证书。
最后,用于命名空间域 API 操作的默认命名空间放置在每个容器中的
/var/run/secrets/kubernetes.io/serviceaccount/namespace
文件中。
使用 kubectl proxy 如果你希望不使用官方客户端库就完成 API 查询,可以将 kubectl proxy
作为
command
在 Pod 中启动一个边车(Sidecar)容器。这样,kubectl proxy
自动完成对 API
的身份认证,并将其暴露到 Pod 的 localhost
接口,从而 Pod 中的其他容器可以
直接使用 API。
不使用代理 通过将认证令牌直接发送到 API 服务器,也可以避免运行 kubectl proxy 命令。
内部的证书机制能够为链接提供保护。
# 指向内部 API 服务器的主机名
APISERVER = https://kubernetes.default.svc
# 服务账号令牌的路径
SERVICEACCOUNT = /var/run/secrets/kubernetes.io/serviceaccount
# 读取 Pod 的名字空间
NAMESPACE = $( cat ${ SERVICEACCOUNT } /namespace)
# 读取服务账号的持有者令牌
TOKEN = $( cat ${ SERVICEACCOUNT } /token)
# 引用内部证书机构(CA)
CACERT = ${ SERVICEACCOUNT } /ca.crt
# 使用令牌访问 API
curl --cacert ${ CACERT } --header "Authorization: Bearer ${ TOKEN } " -X GET ${ APISERVER } /api
输出类似于:
{
"kind" : "APIVersions" ,
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
4.8.10 - 扩缩 StatefulSet 本文介绍如何扩缩StatefulSet。StatefulSet 的扩缩指的是增加或者减少副本个数。
准备开始 StatefulSets 仅适用于 Kubernetes 1.5 及以上版本。
不是所有 Stateful 应用都能很好地执行扩缩操作。
如果你不是很确定是否要扩缩你的 StatefulSet,可先参阅
StatefulSet 概念
或者 StatefulSet 教程 。
仅当你确定你的有状态应用的集群是完全健康的,才可执行扩缩操作.
扩缩 StatefulSet 使用 kubectl
扩缩 StatefulSet 首先,找到你要扩缩的 StatefulSet。
kubectl get statefulsets <statefulset 名称>
更改 StatefulSet 中副本个数:
kubectl scale statefulsets <statefulset 名称> --replicas= <新的副本数>
对 StatefulSet 执行就地更新 另外, 你可以就地更新 StatefulSet。
如果你的 StatefulSet 最初通过 kubectl apply
或 kubectl create --save-config
创建,
你可以更新 StatefulSet 清单中的 .spec.replicas
, 然后执行命令 kubectl apply
:
kubectl apply -f <更新后的 statefulset 文件>
否则,可以使用 kubectl edit
编辑副本字段:
kubectl edit statefulsets <statefulset 名称>
或者使用 kubectl patch
:
kubectl patch statefulsets <statefulset 名称> -p '{"spec":{"replicas":<new-replicas>}}'
故障排查 缩容操作无法正常工作 当 Stateful 所管理的任何 Pod 不健康时,你不能对该 StatefulSet 执行缩容操作。
仅当 StatefulSet 的所有 Pod 都处于运行状态和 Ready 状况后才可缩容.
如果 spec.replicas
大于 1,Kubernetes 无法判定 Pod 不健康的原因。
Pod 不健康可能是由于永久性故障造成也可能是瞬态故障。
瞬态故障可能是节点升级或维护而引起的节点重启造成的。
如果该 Pod 不健康是由于永久性故障导致, 则在不纠正该故障的情况下进行缩容可能会导致
StatefulSet 进入一种状态,其成员 Pod 数量低于应正常运行的副本数。
这种状态也许会导致 StatefulSet 不可用。
如果由于瞬态故障而导致 Pod 不健康并且 Pod 可能再次变为可用,那么瞬态错误可能会干扰
你对 StatefulSet 的扩容/缩容操作。 一些分布式数据库在同时有节点加入和离开时
会遇到问题。在这些情况下,最好是在应用级别进行分析扩缩操作的状态, 并且只有在确保
Stateful 应用的集群是完全健康时才执行扩缩操作。
接下来 4.9 - 运行 Jobs 使用并行处理运行 Jobs。
4.9.1 - 使用 CronJob 运行自动化任务 你可以利用 CronJob
执行基于时间调度的 Job 。
这些自动化任务和 Linux 或者 Unix 系统的 Cron 任务类似。
CronJob 在创建周期性以及重复性的任务时很有帮助,例如执行备份操作或者发送邮件。
CronJob 也可以在特定时间调度单个任务,例如你想调度低活跃周期的任务。
CronJob 有一些限制和特点。
例如,在特定状况下,同一个 CronJob 可以创建多个任务。
因此,任务应该是幂等的。
有关更多限制,请参考 CronJob 。
准备开始 创建 CronJob CronJob 需要一个配置文件。
以下是针对一个 CronJob 的清单,该 CronJob 每分钟运行一个简单的演示任务:
apiVersion : batch/v1
kind : CronJob
metadata :
name : hello
spec :
schedule : "* * * * *"
jobTemplate :
spec :
template :
spec :
containers :
- name : hello
image : busybox:1.28
imagePullPolicy : IfNotPresent
command :
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy : OnFailure
执行以下命令以运行此 CronJob 示例:
kubectl create -f https://k8s.io/examples/application/job/cronjob.yaml
输出类似于:
cronjob.batch/hello created
创建好 CronJob 后,使用下面的命令来获取其状态:
kubectl get cronjob hello
输出类似于:
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 0 <none> 10s
就像你从命令返回结果看到的那样,CronJob 还没有调度或执行任何任务。大约需要一分钟任务才能创建好。
输出类似于:
NAME COMPLETIONS DURATION AGE
hello-4111706356 0/1 0s
hello-4111706356 0/1 0s 0s
hello-4111706356 1/1 5s 5s
现在你已经看到了一个运行中的任务被 “hello” CronJob 调度。
你可以停止监视这个任务,然后再次查看 CronJob 就能看到它调度任务:
kubectl get cronjob hello
输出类似于:
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello */1 * * * * False 0 50s 75s
你应该能看到 hello
CronJob 在 LAST SCHEDULE
声明的时间点成功地调度了一次任务。
目前有 0 个活跃的任务,这意味着任务执行完毕或者执行失败。
现在,找到最后一次调度任务创建的 Pod 并查看一个 Pod 的标准输出。
说明: Job 名称与 Pod 名称不同。
# 在你的系统上将 "hello-4111706356" 替换为 Job 名称
pods = $( kubectl get pods --selector= job-name= hello-4111706356 --output= jsonpath ={ .items..metadata.name} )
查看 Pod 日志:
输出类似于:
Fri Feb 22 11:02:09 UTC 2019
Hello from the Kubernetes cluster
删除 CronJob 当你不再需要 CronJob 时,可以用 kubectl delete cronjob <cronjob name>
删掉它:
kubectl delete cronjob hello
删除 CronJob 会清除它创建的所有任务和 Pod,并阻止它创建额外的任务。
你可以查阅垃圾收集 。
编写 CronJob 声明信息 像 Kubernetes 的其他对象一样,CronJob 需要 apiVersion
、kind
和 metadata
字段。
有关 Kubernetes 对象及它们的清单 的更多信息,
请参考资源管理 和
使用 kubectl 管理资源 文档。
CronJob 配置也需要包括
.spec
部分。
说明: 如果你修改了一个 CronJob,你所做的修改将只被应用到将来所运行的任务上,
对当前 CronJob 内处于运行中的 Job 集合(和 Job 里面的 Pod)不会产生任何变化,它们将继续运行。
也就是说,对 CronJob 的修改不更新现有的任务,即使这些任务处于运行状态。
排期表 .spec.schedule
是 .spec
中的必需字段。它接受 Cron
格式串,例如 0 * * * *
or @hourly
,作为它的任务被创建和执行的调度时间。
该格式也包含了扩展的 “Vixie cron” 步长值。
FreeBSD 手册 中解释如下:
步长可被用于范围组合。范围后面带有 /<数字>
可以声明范围内的步幅数值。
例如,0-23/2
可被用在小时字段来声明命令在其他数值的小时数执行
(V7 标准中对应的方法是 0,2,4,6,8,10,12,14,16,18,20,22
)。
步长也可以放在通配符后面,因此如果你想表达 “每两小时”,就用 */2
。
说明: 调度中的问号 (?
) 和星号 *
含义相同,它们用来表示给定字段的任何可用值。
任务模板 .spec.jobTemplate
是任务的模板,它是必需的。它和
Job 的语法完全一样,
只不过它是嵌套的,没有 apiVersion
和 kind
。
有关如何编写一个任务的 .spec
,
请参考编写 Job 规约 。
开始的最后期限 .spec.startingDeadlineSeconds
字段是可选的。
它表示任务如果由于某种原因错过了调度时间,开始该任务的截止时间的秒数。
过了截止时间,CronJob 就不会开始任务。
不满足这种最后期限的任务会被统计为失败任务。如果此字段未设置,那任务就没有最后期限。
如果 .spec.startingDeadlineSeconds
字段被设置(非空),
CronJob 控制器将会计算从预期创建 Job 到当前时间的时间差。
如果时间差大于该限制,则跳过此次执行。
例如,如果将其设置为 200
,则 Job 控制器允许在实际调度之后最多 200 秒内创建 Job。
并发性规则 .spec.concurrencyPolicy
也是可选的。它声明了 CronJob 创建的任务执行时发生重叠如何处理。
spec 仅能声明下列规则中的一种:
Allow
(默认):CronJob 允许并发任务执行。Forbid
: CronJob 不允许并发任务执行;如果新任务的执行时间到了而老任务没有执行完,CronJob 会忽略新任务的执行。Replace
:如果新任务的执行时间到了而老任务没有执行完,CronJob 会用新任务替换当前正在运行的任务。请注意,并发性规则仅适用于相同 CronJob 创建的任务。如果有多个 CronJob,它们相应的任务总是允许并发执行的。
挂起 .spec.suspend
字段也是可选的。如果设置为 true
,后续发生的执行都会被挂起。
这个设置对已经开始的执行不起作用。默认是 false
。
注意: 在调度时间内挂起的执行都会被统计为错过的任务。当
.spec.suspend
从
true
改为
false
时,
且没有
开始的最后期限 ,错过的任务会被立即调度。
任务历史限制 .spec.successfulJobsHistoryLimit
和 .spec.failedJobsHistoryLimit
是可选的。
这两个字段指定应保留多少已完成和失败的任务。
默认设置分别为 3 和 1。设置为 0
代表相应类型的任务完成后不会保留。
4.9.2 - 使用工作队列进行粗粒度并行处理 本例中,我们会运行包含多个并行工作进程的 Kubernetes Job。
本例中,每个 Pod 一旦被创建,会立即从任务队列中取走一个工作单元并完成它,然后将工作单元从队列中删除后再退出。
下面是本次示例的主要步骤:
启动一个消息队列服务 本例中,我们使用 RabbitMQ,你也可以用其他的消息队列服务。在实际工作环境中,你可以创建一次消息队列服务然后在多个任务中重复使用。
创建一个队列,放上消息数据 每个消息表示一个要执行的任务。本例中,每个消息是一个整数值。我们将基于这个整数值执行很长的计算操作。
启动一个在队列中执行这些任务的 Job 。该 Job 启动多个 Pod。每个 Pod 从消息队列中取走一个任务,处理它,然后重复执行,直到队列的队尾。
准备开始 要熟悉 Job 基本用法(非并行的),请参考
Job 。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
启动消息队列服务 本例使用了 RabbitMQ,但你可以更改该示例,使用其他 AMQP 类型的消息服务。
在实际工作中,在集群中一次性部署某个消息队列服务,之后在很多 Job 中复用,包括需要长期运行的服务。
按下面的方法启动 RabbitMQ:
kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.3/examples/celery-rabbitmq/rabbitmq-service.yaml
service "rabbitmq-service" created
kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.3/examples/celery-rabbitmq/rabbitmq-controller.yaml
replicationcontroller "rabbitmq-controller" created
我们仅用到 celery-rabbitmq 示例 中描述的部分功能。
测试消息队列服务 现在,我们可以试着访问消息队列。我们将会创建一个临时的可交互的 Pod,在它上面安装一些工具,然后用队列做实验。
首先创建一个临时的可交互的 Pod:
# 创建一个临时的可交互的 Pod
kubectl run -i --tty temp --image ubuntu:14.04
Waiting for pod default/temp-loe07 to be running, status is Pending, pod ready: false
... [ previous line repeats several times .. hit return when it stops ] ...
请注意你的 Pod 名称和命令提示符将会不同。
接下来安装 amqp-tools
,这样我们就能用消息队列了。
# 安装一些工具
root@temp-loe07:/# apt-get update
.... [ lots of output ] ....
root@temp-loe07:/# apt-get install -y curl ca-certificates amqp-tools python dnsutils
.... [ lots of output ] ....
后续,我们将制作一个包含这些包的 Docker 镜像。
接着,我们将要验证我们发现 RabbitMQ 服务:
# 请注意 rabbitmq-service 拥有一个由 Kubernetes 提供的 DNS 名称:
root@temp-loe07:/# nslookup rabbitmq-service
Server: 10.0.0.10
Address: 10.0.0.10#53
Name: rabbitmq-service.default.svc.cluster.local
Address: 10.0.147.152
# 你的 IP 地址会不同
如果 Kube-DNS 没有正确安装,上一步可能会出错。
你也可以在环境变量中找到服务 IP。
# env | grep RABBIT | grep HOST
RABBITMQ_SERVICE_SERVICE_HOST=10.0.147.152
# 你的 IP 地址会有所不同
接着我们将要确认可以创建队列,并能发布消息和消费消息。
# 下一行,rabbitmq-service 是访问 rabbitmq-service 的主机名。5672是 rabbitmq 的标准端口。
root@temp-loe07:/# export BROKER_URL = amqp://guest:guest@rabbitmq-service:5672
# 如果上一步中你不能解析 "rabbitmq-service",可以用下面的命令替换:
# root@temp-loe07:/# BROKER_URL=amqp://guest:guest@$RABBITMQ_SERVICE_SERVICE_HOST:5672
# 现在创建队列:
root@temp-loe07:/# /usr/bin/amqp-declare-queue --url= $BROKER_URL -q foo -d foo
# 向它推送一条消息:
root@temp-loe07:/# /usr/bin/amqp-publish --url= $BROKER_URL -r foo -p -b Hello
# 然后取回它.
root@temp-loe07:/# /usr/bin/amqp-consume --url= $BROKER_URL -q foo -c 1 cat && echo
Hello
root@temp-loe07:/#
最后一个命令中, amqp-consume
工具从队列中取走了一个消息,并把该消息传递给了随机命令的标准输出。
在这种情况下,cat
会打印它从标准输入中读取的字符,echo 会添加回车符以便示例可读。
为队列增加任务 现在让我们给队列增加一些任务。在我们的示例中,任务是多个待打印的字符串。
实践中,消息的内容可以是:
待处理的文件名 程序额外的参数 数据库表的关键字范围 模拟任务的配置参数 待渲染的场景的帧序列号 本例中,如果有大量的数据需要被 Job 的所有 Pod 读取,典型的做法是把它们放在一个共享文件系统中,
如 NFS(Network File System 网络文件系统),并以只读的方式挂载到所有 Pod,或者 Pod 中的程序从类似 HDFS
(Hadoop Distributed File System 分布式文件系统)的集群文件系统中读取。
例如,我们创建队列并使用 amqp 命令行工具向队列中填充消息。实践中,你可以写个程序来利用 amqp 客户端库来填充这些队列。
/usr/bin/amqp-declare-queue --url= $BROKER_URL -q job1 -d job1
for f in apple banana cherry date fig grape lemon melon
do
/usr/bin/amqp-publish --url= $BROKER_URL -r job1 -p -b $f
done
这样,我们给队列中填充了8个消息。
创建镜像 现在我们可以创建一个做为 Job 来运行的镜像。
我们将用 amqp-consume
来从队列中读取消息并实际运行我们的程序。这里给出一个非常简单的示例程序:
#!/usr/bin/env python
# Just prints standard out and sleeps for 10 seconds.
import sys
import time
print ("Processing " + sys. stdin. readlines()[0 ])
time. sleep(10 )
赋予脚本执行权限:
现在,编译镜像。如果你在用源代码树,那么切换到目录 examples/job/work-queue-1
。
否则的话,创建一个临时目录,切换到这个目录。下载
Dockerfile ,和
worker.py 。
无论哪种情况,都可以用下面的命令编译镜像
docker build -t job-wq-1 .
对于 Docker Hub , 给你的应用镜像打上标签,
标签为你的用户名,然后用下面的命令推送到 Hub。用你的 Hub 用户名替换 <username>
。
docker tag job-wq-1 <username>/job-wq-1
docker push <username>/job-wq-1
如果你在用谷歌容器仓库 ,
用你的项目 ID 作为标签打到你的应用镜像上,然后推送到 GCR。
用你的项目 ID 替换 <project>
。
docker tag job-wq-1 gcr.io/<project>/job-wq-1
gcloud docker -- push gcr.io/<project>/job-wq-1
定义 Job 这里给出一个 Job 定义 yaml文件。你需要拷贝一份并编辑镜像以匹配你使用的名称,保存为 ./job.yaml
。
apiVersion : batch/v1
kind : Job
metadata :
name : job-wq-1
spec :
completions : 8
parallelism : 2
template :
metadata :
name : job-wq-1
spec :
containers :
- name : c
image : gcr.io/<project>/job-wq-1
env :
- name : BROKER_URL
value : amqp://guest:guest@rabbitmq-service:5672
- name : QUEUE
value : job1
restartPolicy : OnFailure
本例中,每个 Pod 使用队列中的一个消息然后退出。这样,Job 的完成计数就代表了完成的工作项的数量。本例中我们设置 .spec.completions: 8
,因为我们放了8项内容在队列中。
运行 Job 现在我们运行 Job:
kubectl apply -f ./job.yaml
稍等片刻,然后检查 Job。
kubectl describe jobs/job-wq-1
Name: job-wq-1
Namespace: default
Selector: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
Labels: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
job-name=job-wq-1
Annotations: <none>
Parallelism: 2
Completions: 8
Start Time: Wed, 06 Sep 2017 16:42:02 +0800
Pods Statuses: 0 Running / 8 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=41d75705-92df-11e7-b85e-fa163ee3c11f
job-name=job-wq-1
Containers:
c:
Image: gcr.io/causal-jigsaw-637/job-wq-1
Port:
Environment:
BROKER_URL: amqp://guest:guest@rabbitmq-service:5672
QUEUE: job1
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
───────── ──────── ───── ──── ───────────── ────── ────── ───────
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-hcobb
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-weytj
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-qaam5
27s 27s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-b67sr
26s 26s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-xe5hj
15s 15s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-w2zqe
14s 14s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-d6ppa
14s 14s 1 {job } Normal SuccessfulCreate Created pod: job-wq-1-p17e0
我们所有的 Pod 都成功了。耶!
替代方案 本文所讲述的处理方法的好处是你不需要修改你的 "worker" 程序使其知道工作队列的存在。
本文所描述的方法需要你运行一个消息队列服务。如果不方便运行消息队列服务,你也许会考虑另外一种
任务模式 。
本文所述的方法为每个工作项创建了一个 Pod。
如果你的工作项仅需数秒钟,为每个工作项创建 Pod会增加很多的常规消耗。
可以考虑另外的方案请参考示例 ,
这种方案可以实现每个 Pod 执行多个工作项。
示例中,我们使用 amqp-consume
从消息队列读取消息并执行我们真正的程序。
这样的好处是你不需要修改你的程序使其知道队列的存在。
要了解怎样使用客户端库和工作队列通信,请参考
不同的示例 。
友情提醒 如果设置的完成数量小于队列中的消息数量,会导致一部分消息项不会被执行。
如果设置的完成数量大于队列中的消息数量,当队列中所有的消息都处理完成后,
Job 也会显示为未完成。Job 将创建 Pod 并阻塞等待消息输入。
当发生下面两种情况时,即使队列中所有的消息都处理完了,Job 也不会显示为完成状态:
在 amqp-consume 命令拿到消息和容器成功退出之间的时间段内,执行杀死容器操作; 在 kubelet 向 api-server 传回 Pod 成功运行之前,发生节点崩溃。 4.9.3 - 使用工作队列进行精细的并行处理 在这个例子中,我们会运行一个 Kubernetes Job,其中的 Pod 会运行多个并行工作进程。
在这个例子中,当每个 Pod 被创建时,它会从一个任务队列中获取一个工作单元,处理它,然后重复,直到到达队列的尾部。
下面是这个示例的步骤概述:
启动存储服务用于保存工作队列。 在这个例子中,我们使用 Redis 来存储工作项。
在上一个例子中,我们使用了 RabbitMQ。
在这个例子中,由于 AMQP 不能为客户端提供一个良好的方法来检测一个有限长度的工作队列是否为空,
我们使用了 Redis 和一个自定义的工作队列客户端库。
在实践中,你可能会设置一个类似于 Redis 的存储库,并将其同时用于多项任务或其他事务的工作队列。创建一个队列,然后向其中填充消息。 每个消息表示一个将要被处理的工作任务。
在这个例子中,消息是一个我们将用于进行长度计算的整数。启动一个 Job 对队列中的任务进行处理 。这个 Job 启动了若干个 Pod。
每个 Pod 从消息队列中取出一个工作任务,处理它,然后重复,直到到达队列的尾部。准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
熟悉基本的、非并行的 Job 。
启动 Redis 对于这个例子,为了简单起见,我们将启动一个单实例的 Redis。
了解如何部署一个可伸缩、高可用的 Redis 例子,请查看
Redis 示例
你也可以直接下载如下文件:
使用任务填充队列 现在,让我们往队列里添加一些 “任务”。在这个例子中,我们的任务是一些将被打印出来的字符串。
启动一个临时的可交互的 Pod 用于运行 Redis 命令行界面。
kubectl run -i --tty temp --image redis --command "/bin/sh"
输出类似于:
Waiting for pod default/redis2-c7h78 to be running, status is Pending, pod ready: false
Hit enter for command prompt
现在按回车键,启动 Redis 命令行界面,然后创建一个存在若干个工作项的列表。
# redis-cli -h redis
redis:6379> rpush job2 "apple"
( integer) 1
redis:6379> rpush job2 "banana"
( integer) 2
redis:6379> rpush job2 "cherry"
( integer) 3
redis:6379> rpush job2 "date"
( integer) 4
redis:6379> rpush job2 "fig"
( integer) 5
redis:6379> rpush job2 "grape"
( integer) 6
redis:6379> rpush job2 "lemon"
( integer) 7
redis:6379> rpush job2 "melon"
( integer) 8
redis:6379> rpush job2 "orange"
( integer) 9
redis:6379> lrange job2 0 -1
1) "apple"
2) "banana"
3) "cherry"
4) "date"
5) "fig"
6) "grape"
7) "lemon"
8) "melon"
9) "orange"
因此,这个键为 job2
的列表就是我们的工作队列。
注意:如果你还没有正确地配置 Kube DNS,你可能需要将上面的第一步改为
redis-cli -h $REDIS_SERVICE_HOST
。
创建镜像 现在我们已经准备好创建一个我们要运行的镜像。
我们会使用一个带有 Redis 客户端的 Python 工作程序从消息队列中读出消息。
这里提供了一个简单的 Redis 工作队列客户端库,名为 rediswq.py (下载 )。
Job 中每个 Pod 内的 “工作程序” 使用工作队列客户端库获取工作。具体如下:
#!/usr/bin/env python
import time
import rediswq
host= "redis"
# 如果你未在运行 Kube-DNS,请取消下面两行的注释
# import os
# host = os.getenv("REDIS_SERVICE_HOST")
q = rediswq. RedisWQ(name= "job2" , host= host)
print ("Worker with sessionID: " + q. sessionID())
print ("Initial queue state: empty=" + str (q. empty()))
while not q. empty():
item = q. lease(lease_secs= 10 , block= True , timeout= 2 )
if item is not None :
itemstr = item. decode("utf-8" )
print ("Working on " + itemstr)
time. sleep(10 ) # 将你的实际工作放在此处来取代 sleep
q. complete(item)
else :
print ("Waiting for work" )
print ("Queue empty, exiting" )
你也可以下载 worker.py
、
rediswq.py
和
Dockerfile
文件。然后构建镜像:
docker build -t job-wq-2 .
Push 镜像 对于 Docker Hub ,请先用你的用户名给镜像打上标签,
然后使用下面的命令 push 你的镜像到仓库。请将 <username>
替换为你自己的 Hub 用户名。
docker tag job-wq-2 <username>/job-wq-2
docker push <username>/job-wq-2
你需要将镜像 push 到一个公共仓库或者
配置集群访问你的私有仓库 。
如果你使用的是 Google Container Registry ,
请先用你的 project ID 给你的镜像打上标签,然后 push 到 GCR 。请将 <project>
替换为你自己的 project ID。
docker tag job-wq-2 gcr.io/<project>/job-wq-2
gcloud docker -- push gcr.io/<project>/job-wq-2
定义一个 Job 这是 Job 定义:
apiVersion : batch/v1
kind : Job
metadata :
name : job-wq-2
spec :
parallelism : 2
template :
metadata :
name : job-wq-2
spec :
containers :
- name : c
image : gcr.io/myproject/job-wq-2
restartPolicy : OnFailure
请确保将 Job 模板中的 gcr.io/myproject
更改为你自己的路径。
在这个例子中,每个 Pod 处理了队列中的多个项目,直到队列中没有项目时便退出。
因为是由工作程序自行检测工作队列是否为空,并且 Job 控制器不知道工作队列的存在,
这依赖于工作程序在完成工作时发出信号。
工作程序以成功退出的形式发出信号表示工作队列已经为空。
所以,只要有任意一个工作程序成功退出,控制器就知道工作已经完成了,所有的 Pod 将很快会退出。
因此,我们将 Job 的完成计数(Completion Count)设置为 1。
尽管如此,Job 控制器还是会等待其它 Pod 完成。
运行 Job 现在运行这个 Job:
kubectl apply -f ./job.yaml
稍等片刻,然后检查这个 Job。
kubectl describe jobs/job-wq-2
Name: job-wq-2
Namespace: default
Selector: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
Labels: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
job-name=job-wq-2
Annotations: <none>
Parallelism: 2
Completions: <unset>
Start Time: Mon, 11 Jan 2016 17:07:59 -0800
Pods Statuses: 1 Running / 0 Succeeded / 0 Failed
Pod Template:
Labels: controller-uid=b1c7e4e3-92e1-11e7-b85e-fa163ee3c11f
job-name=job-wq-2
Containers:
c:
Image: gcr.io/exampleproject/job-wq-2
Port:
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
33s 33s 1 {job-controller } Normal SuccessfulCreate Created pod: job-wq-2-lglf8
运行以下命令查看日志:
kubectl logs pods/job-wq-2-7r7b2
日志类似于:
Worker with sessionID: bbd72d0a-9e5c-4dd6-abf6-416cc267991f
Initial queue state: empty=False
Working on banana
Working on date
Working on lemon
你可以看到,其中的一个 Pod 处理了若干个工作单元。
替代方案 如果你不方便运行一个队列服务或者修改你的容器用于运行一个工作队列,你可以考虑其它的
Job 模式 。
如果你有持续的后台处理业务,那么可以考虑使用 ReplicaSet
来运行你的后台业务,
和运行一个类似 https://github.com/resque/resque
的后台处理库。
4.9.4 - 使用索引作业完成静态工作分配下的并行处理 特性状态: Kubernetes v1.24 [stable]
在此示例中,你将运行一个使用多个并行工作进程的 Kubernetes Job。
每个 worker 都是在自己的 Pod 中运行的不同容器。
Pod 具有控制平面自动设置的索引编号(index number) ,
这些编号使得每个 Pod 能识别出要处理整个任务的哪个部分。
Pod 索引在注解
batch.kubernetes.io/job-completion-index
中呈现,具体表示为一个十进制值字符串。
为了让容器化的任务进程获得此索引,你可以使用
downward API
机制发布注解的值。为方便起见,
控制平面自动设置 Downward API 以在 JOB_COMPLETION_INDEX
环境变量中公开索引。
以下是此示例中步骤的概述:
定义使用带索引完成信息的 Job 清单 。
Downward API 使你可以将 Pod 索引注解作为环境变量或文件传递给容器。根据该清单启动一个带索引(Indexed
)的 Job 。准备开始 你应该已经熟悉 Job 的基本的、非并行的用法。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.21.
要获知版本信息,请输入
kubectl version
.
选择一种方法 要从工作程序访问工作项,你有几个选项:
读取 JOB_COMPLETION_INDEX
环境变量。Job
控制器
自动将此变量链接到包含完成索引的注解。 读取包含完整索引的文件。 假设你无法修改程序,你可以使用脚本包装它,
该脚本使用上述任意方法读取索引并将其转换为程序可以用作输入的内容。 对于此示例,假设你选择了选项 3 并且想要运行
rev 实用程序。
这个程序接受一个文件作为参数并按逆序打印其内容。
你将使用 busybox
容器镜像中的 rev
工具。
由于这只是一个例子,每个 Pod 只做一小部分工作(反转一个短字符串)。
例如,在实际工作负载中,你可能会创建一个表示基于场景数据制作 60 秒视频任务的 Job 。
此视频渲染 Job 中的每个工作项都将渲染该视频剪辑的特定帧。
索引完成意味着 Job 中的每个 Pod 都知道通过从剪辑开始计算帧数,来确定渲染和发布哪一帧。
定义索引作业 这是一个使用 Indexed
完成模式的示例 Job 清单:
apiVersion : batch/v1
kind : Job
metadata :
name : 'indexed-job'
spec :
completions : 5
parallelism : 3
completionMode : Indexed
template :
spec :
restartPolicy : Never
initContainers :
- name : 'input'
image : 'docker.io/library/bash'
command :
- "bash"
- "-c"
- |
items=(foo bar baz qux xyz)
echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt
volumeMounts :
- mountPath : /input
name : input
containers :
- name : 'worker'
image : 'docker.io/library/busybox'
command :
- "rev"
- "/input/data.txt"
volumeMounts :
- mountPath : /input
name : input
volumes :
- name : input
emptyDir : {}
在上面的示例中,你使用 Job 控制器为所有容器设置的内置 JOB_COMPLETION_INDEX
环境变量。
Init 容器
将索引映射到一个静态值,并将其写入一个文件,该文件通过
emptyDir 卷
与运行 worker 的容器共享。或者,你可以
通过 Downward API 定义自己的环境变量
将索引发布到容器。你还可以选择从
包含 ConfigMap 的环境变量或文件
加载值列表。
或者也可以直接
使用 Downward API 将注解值作为卷文件传递 ,
如下例所示:
apiVersion : batch/v1
kind : Job
metadata :
name : 'indexed-job'
spec :
completions : 5
parallelism : 3
completionMode : Indexed
template :
spec :
restartPolicy : Never
containers :
- name : 'worker'
image : 'docker.io/library/busybox'
command :
- "rev"
- "/input/data.txt"
volumeMounts :
- mountPath : /input
name : input
volumes :
- name : input
downwardAPI :
items :
- path : "data.txt"
fieldRef :
fieldPath : metadata.annotations['batch.kubernetes.io/job-completion-index']
执行 Job {running-the-job} 现在执行 Job:
# 使用第一种方法(依赖于 $JOB_COMPLETION_INDEX)
kubectl apply -f https://kubernetes.io/examples/application/job/indexed-job.yaml
当你创建此 Job 时,控制平面会创建一系列 Pod,你指定的每个索引都会运行一个 Pod。
.spec.parallelism
的值决定了一次可以运行多少个 Pod,
而 .spec.completions
决定了 Job 总共创建了多少个 Pod。
因为 .spec.parallelism
小于 .spec.completions
,
所以控制平面在启动更多 Pod 之前,将等待第一批的某些 Pod 完成。
创建 Job 后,稍等片刻,就能检查进度:
kubectl describe jobs/indexed-job
输出类似于:
Name: indexed-job
Namespace: default
Selector: controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
Labels: controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
job-name=indexed-job
Annotations: <none>
Parallelism: 3
Completions: 5
Start Time: Thu, 11 Mar 2021 15:47:34 +0000
Pods Statuses: 2 Running / 3 Succeeded / 0 Failed
Completed Indexes: 0-2
Pod Template:
Labels: controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
job-name=indexed-job
Init Containers:
input:
Image: docker.io/library/bash
Port: <none>
Host Port: <none>
Command:
bash
-c
items=(foo bar baz qux xyz)
echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt
Environment: <none>
Mounts:
/input from input (rw)
Containers:
worker:
Image: docker.io/library/busybox
Port: <none>
Host Port: <none>
Command:
rev
/input/data.txt
Environment: <none>
Mounts:
/input from input (rw)
Volumes:
input:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 4s job-controller Created pod: indexed-job-njkjj
Normal SuccessfulCreate 4s job-controller Created pod: indexed-job-9kd4h
Normal SuccessfulCreate 4s job-controller Created pod: indexed-job-qjwsz
Normal SuccessfulCreate 1s job-controller Created pod: indexed-job-fdhq5
Normal SuccessfulCreate 1s job-controller Created pod: indexed-job-ncslj
在此示例中,你使用每个索引的自定义值运行 Job。
你可以检查其中一个 Pod 的输出:
kubectl logs indexed-job-fdhq5 # 更改它以匹配来自该 Job 的 Pod 的名称
输出类似于:
xuq
4.9.5 - 使用展开的方式进行并行处理 本任务展示基于一个公共的模板运行多个Jobs 。
你可以用这种方法来并行执行批处理任务。
在本任务示例中,只有三个工作条目:apple 、banana 和 cherry 。
示例任务处理每个条目时打印一个字符串之后结束。
参考在真实负载中使用 Job 了解更适用于真实使用场景的模式。
准备开始 你应先熟悉基本的、非并行的 Job
的用法。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
任务中的基本模板示例要求安装命令行工具 sed
。
要使用较高级的模板示例,你需要安装 Python ,
并且要安装 Jinja2 模板库。
一旦 Python 已经安装好,你可以运行下面的命令安装 Jinja2:
pip install --user jinja2
基于模板创建 Job 首先,将以下作业模板下载到名为 job-tmpl.yaml
的文件中。
apiVersion : batch/v1
kind : Job
metadata :
name : process-item-$ITEM
labels :
jobgroup : jobexample
spec :
template :
metadata :
name : jobexample
labels :
jobgroup : jobexample
spec :
containers :
- name : c
image : busybox:1.28
command : ["sh" , "-c" , "echo Processing item $ITEM && sleep 5" ]
restartPolicy : Never
# 使用 curl 下载 job-tmpl.yaml
curl -L -s -O https://k8s.io/examples/application/job/job-tmpl.yaml
你所下载的文件不是一个合法的 Kubernetes 清单 。
这里的模板只是 Job 对象的 yaml 表示,其中包含一些占位符,在使用它之前需要被填充。
$ITEM
语法对 Kubernetes 没有意义。
基于模板创建清单 下面的 Shell 代码片段使用 sed
将字符串 $ITEM
替换为循环变量,并将结果
写入到一个名为 jobs
的临时目录。
# 展开模板文件到多个文件中,每个文件对应一个要处理的条目
mkdir ./jobs
for i in apple banana cherry
do
cat job-tmpl.yaml | sed "s/\$ITEM/ $i /" > ./jobs/job-$i .yaml
done
检查上述脚本的输出:
输出类似于:
job-apple.yaml
job-banana.yaml
job-cherry.yaml
你可以使用任何一种模板语言(例如:Jinja2、ERB),或者编写一个程序来
生成 Job 清单。
基于清单创建 Job 接下来用一个 kubectl 命令创建所有的 Job:
输出类似于:
job.batch/process-item-apple created
job.batch/process-item-banana created
job.batch/process-item-cherry created
现在检查 Job:
kubectl get jobs -l jobgroup = jobexample
输出类似于:
NAME COMPLETIONS DURATION AGE
process-item-apple 1/1 14s 22s
process-item-banana 1/1 12s 21s
process-item-cherry 1/1 12s 20s
使用 kubectl 的 -l
选项可以仅选择属于当前 Job 组的对象
(系统中可能存在其他不相关的 Job)。
你可以使用相同的 标签选择算符
来过滤 Pods:
kubectl get pods -l jobgroup = jobexample
输出类似于:
NAME READY STATUS RESTARTS AGE
process-item-apple-kixwv 0/1 Completed 0 4m
process-item-banana-wrsf7 0/1 Completed 0 4m
process-item-cherry-dnfu9 0/1 Completed 0 4m
我们可以用下面的命令查看所有 Job 的输出:
kubectl logs -f -l jobgroup = jobexample
输出类似于:
Processing item apple
Processing item banana
Processing item cherry
清理 # 删除所创建的 Job
# 集群会自动清理 Job 对应的 Pod
kubectl delete job -l jobgroup = jobexample
使用高级模板参数 在第一个例子 中,模板的每个示例都有一个参数
而该参数也用在 Job 名称中。不过,对象
名称
被限制只能使用某些字符。
这里的略微复杂的例子使用 Jinja 模板语言
来生成清单,并基于清单来生成对象,每个 Job 都有多个参数。
在本任务中,你将会使用一个一行的 Python 脚本,将模板转换为一组清单文件。
首先,复制下面的 Job 对象模板到一个名为 job.yaml.jinja2
的文件。
{% set params = [{ "name": "apple", "url": "http://dbpedia.org/resource/Apple", },
{ "name": "banana", "url": "http://dbpedia.org/resource/Banana", },
{ "name": "cherry", "url": "http://dbpedia.org/resource/Cherry" }]
%}
{% for p in params %}
{% set name = p["name"] %}
{% set url = p["url"] %}
---
apiVersion: batch/v1
kind: Job
metadata:
name: jobexample-{{ name }}
labels:
jobgroup: jobexample
spec:
template:
metadata:
name: jobexample
labels:
jobgroup: jobexample
spec:
containers:
- name: c
image: busybox:1.28
command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"]
restartPolicy: Never
{% endfor %}
上面的模板使用 python 字典列表(第 1-4 行)定义每个作业对象的参数。
然后使用 for 循环为每组参数(剩余行)生成一个作业 yaml 对象。
我们利用了多个 YAML 文档(这里的 Kubernetes 清单)可以用 ---
分隔符连接的事实。
我们可以将输出直接传递给 kubectl 来创建对象。
接下来我们用单行的 Python 程序将模板展开。
alias render_template = 'python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"'
使用 render_template
将参数和模板转换成一个 YAML 文件,其中包含 Kubernetes
资源清单:
# 此命令需要之前定义的别名
cat job.yaml.jinja2 | render_template > jobs.yaml
你可以查看 jobs.yaml
以验证 render_template
脚本是否正常工作。
当你对输出结果比较满意时,可以用管道将其输出发送给 kubectl,如下所示:
cat job.yaml.jinja2 | render_template | kubectl apply -f -
Kubernetes 接收清单文件并执行你所创建的 Job。
清理 # 删除所创建的 Job
# 集群会自动清理 Job 对应的 Pod
kubectl delete job -l jobgroup = jobexample
在真实负载中使用 Job 在真实的负载中,每个 Job 都会执行一些重要的计算,例如渲染电影的一帧,
或者处理数据库中的若干行。这时,$ITEM
参数将指定帧号或行范围。
在此任务中,你运行一个命令通过取回 Pod 的日志来收集其输出。
在真实应用场景中,Job 的每个 Pod 都会在结束之前将其输出写入到某持久性存储中。
你可以为每个 Job 指定 PersistentVolume 卷,或者使用其他外部存储服务。
例如,如果你在渲染视频帧,你可能会使用 HTTP 协议将渲染完的帧数据
用 'PUT' 请求发送到某 URL,每个帧使用不同的 URl。
Job 和 Pod 上的标签 你创建了 Job 之后,Kubernetes 自动为 Job 的 Pod 添加
标签 ,以便能够将一个 Job
的 Pod 与另一个 Job 的 Pod 区分开来。
在本例中,每个 Job 及其 Pod 模板有一个标签: jobgroup=jobexample
。
Kubernetes 自身对标签名 jobgroup
没有什么要求。
为创建自同一模板的所有 Job 使用同一标签使得我们可以方便地同时操作组中的所有作业。
在第一个例子 中,你使用模板来创建了若干 Job。
模板确保每个 Pod 都能够获得相同的标签,这样你可以用一条命令检查这些模板化
Job 所生成的全部 Pod。
说明: 标签键
jobgroup
没什么特殊的,也不是保留字。 你可以选择你自己的标签方案。
如果愿意,有一些
建议的标签
可供使用。
替代方案 如果你有计划创建大量 Job 对象,你可能会发现:
即使使用标签,管理这么多 Job 对象也很麻烦。 如果你一次性创建很多 Job,很可能会给 Kubernetes 控制面带来很大压力。
一种替代方案是,Kubernetes API 可能对请求施加速率限制,通过 429 返回
状态值临时拒绝你的请求。 你可能会受到 Job 相关的资源配额
限制:如果你在一个批量请求中触发了太多的任务,API 服务器会永久性地拒绝你的某些请求。 还有一些其他作业模式
可供选择,这些模式都能用来处理大量任务而又不会创建过多的 Job 对象。
你也可以考虑编写自己的控制器
来自动管理 Job 对象。
4.9.6 - 使用 Pod 失效策略处理可重试和不可重试的 Pod 失效 特性状态: Kubernetes v1.25 [alpha]
本文向你展示如何结合默认的 Pod 回退失效策略 来使用
Pod 失效策略 ,
以改善 Job 内处理容器级别或 Pod 级别的失效。
Pod 失效策略的定义可以帮助你:
避免不必要的 Pod 重试,以更好地利用计算资源。 避免由于 Pod 干扰(例如抢占 、
API 发起的驱逐 或基于污点 的驱逐)
而造成的 Job 失败。 准备开始 你应该已熟悉了 Job 的基本用法。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须是 v1.25.
要获知版本信息,请输入
kubectl version
.
说明: 因为这些特性还处于 Alpha 阶段,所以在准备 Kubernetes
集群时要启用两个特性门控 :
JobPodFailurePolicy
和 PodDisruptionConditions
。
使用 Pod 失效策略以避免不必要的 Pod 重试 借用以下示例,你可以学习在 Pod 失效表明有一个不可重试的软件漏洞时如何使用
Pod 失效策略来避免不必要的 Pod 重启。
首先,基于配置创建一个 Job:
apiVersion : batch/v1
kind : Job
metadata :
name : job-pod-failure-policy-failjob
spec :
completions : 8
parallelism : 2
template :
spec :
restartPolicy : Never
containers :
- name : main
image : docker.io/library/bash:5
command : ["bash" ]
args :
- -c
- echo "Hello world! I'm going to exit with 42 to simulate a software bug." && sleep 30 && exit 42
backoffLimit : 6
podFailurePolicy :
rules :
- action : FailJob
onExitCodes :
containerName : main
operator : In
values : [42 ]
运行以下命令:
kubectl create -f job-pod-failure-policy-failjob.yaml
大约 30 秒后,整个 Job 应被终止。通过运行以下命令来查看 Job 的状态:
kubectl get jobs -l job-name= job-pod-failure-policy-failjob -o yaml
在 Job 状态中,看到一个任务状况为 Failed
,其 reason
字段等于 PodFailurePolicy
。
此外,message
字段包含有关 Job 终止更详细的信息,例如:
Container main for pod default/job-pod-failure-policy-failjob-8ckj8 failed with exit code 42 matching FailJob rule at index 0
。
为了比较,如果 Pod 失效策略被禁用,将会让 Pod 重试 6 次,用时至少 2 分钟。
清理 删除你创建的 Job:
kubectl delete jobs/job-pod-failure-policy-failjob
集群自动清理这些 Pod。
使用 Pod 失效策略来忽略 Pod 干扰 通过以下示例,你可以学习如何使用 Pod 失效策略将 Pod 重试计数器朝着 .spec.backoffLimit
限制递增来忽略 Pod 干扰。
注意: 这个示例的时机比较重要,因此你可能需要在执行之前阅读这些步骤。
为了触发 Pod 干扰,重要的是在 Pod 在其上运行时(自 Pod 调度后的 90 秒内)腾空节点。
基于配置创建 Job:
apiVersion : batch/v1
kind : Job
metadata :
name : job-pod-failure-policy-ignore
spec :
completions : 4
parallelism : 2
template :
spec :
restartPolicy : Never
containers :
- name : main
image : docker.io/library/bash:5
command : ["bash" ]
args :
- -c
- echo "Hello world! I'm going to exit with 0 (success)." && sleep 90 && exit 0
backoffLimit : 0
podFailurePolicy :
rules :
- action : Ignore
onPodConditions :
- type : DisruptionTarget
运行以下命令:
kubectl create -f job-pod-failure-policy-ignore.yaml
运行以下这条命令检查将 Pod 调度到的 nodeName
:
nodeName = $( kubectl get pods -l job-name= job-pod-failure-policy-ignore -o jsonpath = '{.items[0].spec.nodeName}' )
腾空该节点以便在 Pod 完成任务之前将其驱逐(90 秒内):
kubectl drain nodes/$nodeName --ignore-daemonsets --grace-period= 0
查看 .status.failed
以检查针对 Job 的计数器未递增:
kubectl get jobs -l job-name= job-pod-failure-policy-ignore -o yaml
解除节点的保护:
kubectl uncordon nodes/$nodeName
Job 恢复并成功完成。
为了比较,如果 Pod 失效策略被禁用,Pod 干扰将使得整个 Job 终止(随着 .spec.backoffLimit
设置为 0)。
清理 删除你创建的 Job:
kubectl delete jobs/job-pod-failure-policy-ignore
集群自动清理 Pod。
替代方案 通过指定 Job 的 .spec.backoffLimit
字段,你可以完全依赖
Pod 回退失效策略 。
然而在许多情况下,难题在于如何找到一个平衡,为 .spec.backoffLimit
设置一个较小的值以避免不必要的 Pod 重试,
同时这个值又足以确保 Job 不会因 Pod 干扰而终止。
4.10 - 访问集群中的应用程序 配置负载平衡、端口转发或设置防火墙或 DNS 配置,以访问集群中的应用程序。
4.10.1 - 部署和访问 Kubernetes 仪表板(Dashboard) Dashboard 是基于网页的 Kubernetes 用户界面。
你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中,也可以对容器应用排错,还能管理集群资源。
你可以使用 Dashboard 获取运行在集群中的应用的概览信息,也可以创建或者修改 Kubernetes 资源
(如 Deployment,Job,DaemonSet 等等)。
例如,你可以对 Deployment 实现弹性伸缩、发起滚动升级、重启 Pod 或者使用向导创建新的应用。
Dashboard 同时展示了 Kubernetes 集群中的资源状态信息和所有报错信息。
部署 Dashboard UI 默认情况下不会部署 Dashboard。可以通过以下命令部署:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml
访问 Dashboard 用户界面 为了保护你的集群数据,默认情况下,Dashboard 会使用最少的 RBAC 配置进行部署。
当前,Dashboard 仅支持使用 Bearer 令牌登录。
要为此样本演示创建令牌,你可以按照
创建示例用户
上的指南进行操作。
警告: 在教程中创建的样本用户将具有管理特权,并且仅用于教育目的。
命令行代理 你可以使用 kubectl
命令行工具来启用 Dashboard 访问,命令如下:
kubectl proxy
kubectl 会使得 Dashboard 可以通过 http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ 访问。
UI 只能 通过执行这条命令的机器进行访问。更多选项参见 kubectl proxy --help
。
说明: Kubeconfig 身份验证方法不 支持外部身份提供程序或基于 x509 证书的身份验证。
欢迎界面 当访问空集群的 Dashboard 时,你会看到欢迎界面。
页面包含一个指向此文档的链接,以及一个用于部署第一个应用程序的按钮。
此外,你可以看到在默认情况下有哪些默认系统应用运行在 kube-system
名字空间 中,比如 Dashboard 自己。
部署容器化应用 通过一个简单的部署向导,你可以使用 Dashboard 将容器化应用作为一个 Deployment 和可选的
Service 进行创建和部署。你可以手工指定应用的详细配置,或者上传一个包含应用配置的 YAML
或 JSON _清单_文件。
点击任何页面右上角的 CREATE 按钮以开始。
指定应用的详细配置 部署向导需要你提供以下信息:
容器镜像 (必填):公共镜像仓库上的 Docker
容器镜像 或者私有镜像仓库
(通常是 Google Container Registry 或者 Docker Hub)的 URL。容器镜像参数说明必须以冒号结尾。服务 (可选):对于部分应用(比如前端),你可能想对外暴露一个
Service ,这个 Service
可能用的是集群之外的公网 IP 地址(外部 Service)。
说明: 对于外部服务,你可能需要开放一个或多个端口才行。
其它只能对集群内部可见的 Service 称为内部 Service。
不管哪种 Service 类型,如果你选择创建一个 Service,而且容器在一个端口上开启了监听(入向的),
那么你需要定义两个端口。创建的 Service 会把(入向的)端口映射到容器可见的目标端口。
该 Service 会把流量路由到你部署的 Pod。支持 TCP 协议和 UDP 协议。
这个 Service 的内部 DNS 解析名就是之前你定义的应用名称的值。
如果需要,你可以打开 Advanced Options 部分,这里你可以定义更多设置:
描述 :这里你输入的文本会作为一个
注解
添加到 Deployment,并显示在应用的详细信息中。名字空间 :Kubernetes 支持多个虚拟集群依附于同一个物理集群。
这些虚拟集群被称为
名字空间 ,
可以让你将资源划分为逻辑命名的组。
Dashboard 通过下拉菜单提供所有可用的名字空间,并允许你创建新的名字空间。
名字空间的名称最长可以包含 63 个字母或数字和中横线(-),但是不能包含大写字母。
名字空间的名称不能只包含数字。如果名字被设置成一个数字,比如 10,pod 就
在名字空间创建成功的情况下,默认会使用新创建的名字空间。如果创建失败,那么第一个名字空间会被选中。
镜像拉取 Secret :如果要使用私有的 Docker 容器镜像,需要拉取
Secret 凭证。
Dashboard 通过下拉菜单提供所有可用的 Secret,并允许你创建新的 Secret。
Secret 名称必须遵循 DNS 域名语法,比如 new.image-pull.secret
。
Secret 的内容必须是 base64 编码的,并且在一个
.dockercfg
文件中声明。Secret 名称最大可以包含 253 个字符。
在镜像拉取 Secret 创建成功的情况下,默认会使用新创建的 Secret。
如果创建失败,则不会使用任何 Secret。
CPU 需求(核数) 和 内存需求(MiB) :你可以为容器定义最小的
资源限制 。
默认情况下,Pod 没有 CPU 和内存限制。运行命令 和运行命令参数 :默认情况下,你的容器会运行 Docker 镜像的默认
入口命令 。
你可以使用 command 选项覆盖默认值。以特权模式运行 :这个设置决定了在
特权容器
中运行的进程是否像主机中使用 root 运行的进程一样。
特权容器可以使用诸如操纵网络堆栈和访问设备的功能。环境变量 :Kubernetes 通过
环境变量
暴露 Service。你可以构建环境变量,或者将环境变量的值作为参数传递给你的命令。
它们可以被应用用于查找 Service。值可以通过 $(VAR_NAME)
语法关联其他变量。上传 YAML 或者 JSON 文件 Kubernetes 支持声明式配置。所有的配置都存储在清单文件
(YAML 或者 JSON 配置文件)中。这些
清单使用 Kubernetes API 定义的资源模式。
作为一种替代在部署向导中指定应用详情的方式,你可以在一个或多个清单文件中定义应用,并且使用
Dashboard 上传文件。
使用 Dashboard 以下各节描述了 Kubernetes Dashboard UI 视图;包括它们提供的内容,以及怎么使用它们。
导航 当在集群中定义 Kubernetes 对象时,Dashboard 会在初始视图中显示它们。
默认情况下只会显示 默认 名字空间中的对象,可以通过更改导航栏菜单中的名字空间筛选器进行改变。
Dashboard 展示大部分 Kubernetes 对象,并将它们分组放在几个菜单类别中。
管理概述 集群和名字空间管理的视图,Dashboard 会列出节点、名字空间和持久卷,并且有它们的详细视图。
节点列表视图包含从所有节点聚合的 CPU 和内存使用的度量值。
详细信息视图显示了一个节点的度量值,它的规格、状态、分配的资源、事件和这个节点上运行的 Pod。
负载 显示选中的名字空间中所有运行的应用。
视图按照负载类型(如 Deployment、ReplicaSet、StatefulSet 等)罗列应用,并且每种负载都可以单独查看。
列表总结了关于负载的可执行信息,比如一个 ReplicaSet 的就绪状态的 Pod 数量,或者目前一个 Pod 的内存用量。
工作负载的详情视图展示了对象的状态、详细信息和相互关系。
例如,ReplicaSet 所控制的 Pod,或者 Deployment 所关联的新 ReplicaSet 和
HorizontalPodAutoscalers。
服务 展示允许暴露给外网服务和允许集群内部发现的 Kubernetes 资源。
因此,Service 和 Ingress 视图展示他们关联的 Pod、给集群连接使用的内部端点和给外部用户使用的外部端点。
存储 存储视图展示持久卷申领(PVC)资源,这些资源被应用程序用来存储数据。
ConfigMap 和 Secret 展示的所有 Kubernetes 资源是在集群中运行的应用程序的实时配置。
通过这个视图可以编辑和管理配置对象,并显示那些默认隐藏的 Secret。
日志查看器 Pod 列表和详细信息页面可以链接到 Dashboard 内置的日志查看器。
查看器可以深入查看属于同一个 Pod 的不同容器的日志。
接下来 更多信息,参见 Kubernetes Dashboard 项目页面 .
4.10.2 - 访问集群 本文阐述多种与集群交互的方法。
使用 kubectl 完成集群的第一次访问 当你第一次访问 Kubernetes API 的时候,我们建议你使用 Kubernetes CLI 工具 kubectl
。
访问集群时,你需要知道集群的地址并且拥有访问的凭证。通常,这些在你通过
启动安装 安装集群时都是自动安装好的,或者其他人安装时
也应该提供了凭证和集群地址。
通过以下命令检查 kubectl 是否知道集群地址及凭证:
有许多例子 介绍了如何使用 kubectl,
可以在 kubectl 参考 中找到更完整的文档。
直接访问 REST API Kubectl 处理 apiserver 的定位和身份验证。
如果要使用 curl 或 wget 等 http 客户端或浏览器直接访问 REST API,可以通过
多种方式查找和验证:
以代理模式运行 kubectl。推荐此方式。 使用已存储的 apiserver 地址。 使用自签名的证书来验证 apiserver 的身份。杜绝 MITM 攻击。 对 apiserver 进行身份验证。 未来可能会实现智能化的客户端负载均衡和故障恢复。 直接向 http 客户端提供位置和凭据。可选的方案。 适用于代理可能引起混淆的某些客户端类型。 需要引入根证书到你的浏览器以防止 MITM 攻击。 使用 kubectl proxy 以下命令以反向代理的模式运行 kubectl。它处理 apiserver 的定位和验证。
像这样运行:
kubectl proxy --port= 8080
参阅 kubectl proxy
获取更多详细信息。
然后,你可以使用 curl、wget 或浏览器访问 API,如果是 IPv6 则用 [::1] 替换 localhost,
如下所示:
curl http://localhost:8080/api/
输出类似于:
{
"kind" : "APIVersions" ,
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
不使用 kubectl proxy 使用 kubectl apply
和 kubectl describe secret ...
及 grep 和剪切操作来为 default 服务帐户创建令牌,如下所示:
首先,创建 Secret,请求默认 ServiceAccount 的令牌:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: default-token
annotations:
kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
EOF
接下来,等待令牌控制器使用令牌填充 Secret:
while ! kubectl describe secret default-token | grep -E '^token' >/dev/null; do
echo "waiting for token..." >&2
sleep 1
done
捕获并使用生成的令牌:
APISERVER = $( kubectl config view --minify | grep server | cut -f 2- -d ":" | tr -d " " )
TOKEN = $( kubectl describe secret default-token | grep -E '^token' | cut -f2 -d':' | tr -d " " )
curl $APISERVER /api --header "Authorization: Bearer $TOKEN " --insecure
输出类似于:
{
"kind" : "APIVersions" ,
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
jsonpath
方法实现:
APISERVER = $( kubectl config view --minify -o jsonpath = '{.clusters[0].cluster.server}' )
TOKEN = $( kubectl get secret default-token -o jsonpath = '{.data.token}' | base64 --decode)
curl $APISERVER /api --header "Authorization: Bearer $TOKEN " --insecure
输出类似于:
{
"kind" : "APIVersions" ,
"versions" : [
"v1"
],
"serverAddressByClientCIDRs" : [
{
"clientCIDR" : "0.0.0.0/0" ,
"serverAddress" : "10.0.1.149:443"
}
]
}
上面的例子使用了 --insecure
参数,这使得它很容易受到 MITM 攻击。
当 kubectl 访问集群时,它使用存储的根证书和客户端证书来访问服务器
(它们安装在 ~/.kube
目录中)。
由于集群证书通常是自签名的,因此可能需要特殊配置才能让你的 http 客户端使用根证书。
在一些集群中,apiserver 不需要身份验证;它可能只服务于 localhost,或者被防火墙保护,
这个没有一定的标准。
配置对 API 的访问
描述了集群管理员如何进行配置。此类方法可能与未来的高可用性支持相冲突。
以编程方式访问 API Kubernetes 官方提供对 Go 和 Python 的客户端库支持。
Go 客户端 想要获得这个库,请运行命令:go get k8s.io/client-go@kubernetes-<kubernetes-version-number>
,
有关详细安装说明,请参阅 INSTALL.md 。
请参阅 https://github.com/kubernetes/client-go 以查看支持的版本。 基于这个 client-go 客户端库编写应用程序。
请注意,client-go 定义了自己的 API 对象,因此如果需要,请从 client-go 而不是从主存储库
导入 API 定义,例如,import "k8s.io/client-go/kubernetes"
才是对的。 Go 客户端可以像 kubectl CLI 一样使用相同的
kubeconfig 文件
来定位和验证 apiserver。可参阅
示例 。
如果应用程序以 Pod 的形式部署在集群中,那么请参阅
下一章 。
Python 客户端 如果想要使用 Python 客户端 ,
请运行命令:pip install kubernetes
。参阅
Python Client Library page
以获得更详细的安装参数。
Python 客户端可以像 kubectl CLI 一样使用相同的
kubeconfig 文件
来定位和验证 apiserver,可参阅
示例 。
其它语言 目前有多个客户端库
为其它语言提供访问 API 的方法。
参阅其它库的相关文档以获取他们是如何验证的。
从 Pod 中访问 API 当你从 Pod 中访问 API 时,定位和验证 API 服务器会有些许不同。
请参阅从 Pod 中访问 API
了解更多详情。
访问集群上运行的服务 上一节介绍了如何连接到 Kubernetes API 服务器。
有关连接到 Kubernetes 集群上运行的其他服务的信息,请参阅
访问集群服务 。
请求重定向 重定向功能已弃用并被删除。请改用代理(见下文)。
多种代理 使用 Kubernetes 时可能会遇到几种不同的代理:
kubectl 代理 :
在用户的桌面或 Pod 中运行 代理从本地主机地址到 Kubernetes apiserver 客户端到代理将使用 HTTP 代理到 apiserver 使用 HTTPS 定位 apiserver 添加身份验证头部 apiserver 代理 :
内置于 apiserver 中 将集群外部的用户连接到集群 IP,否则这些 IP 可能无法访问 运行在 apiserver 进程中 客户端代理使用 HTTPS(也可配置为 http) 代理将根据可用的信息决定使用 HTTP 或者 HTTPS 代理到目标 可用于访问节点、Pod 或服务 在访问服务时进行负载平衡 kube proxy :
运行在每个节点上 代理 UDP 和 TCP 不能代理 HTTP 提供负载均衡 只能用来访问服务 位于 apiserver 之前的 Proxy/Load-balancer:
存在和实现因集群而异(例如 nginx) 位于所有客户和一个或多个 apiserver 之间 如果有多个 apiserver,则充当负载均衡器 外部服务上的云负载均衡器:
由一些云提供商提供(例如 AWS ELB,Google Cloud Load Balancer) 当 Kubernetes 服务类型为 LoadBalancer
时自动创建 只使用 UDP/TCP 具体实现因云提供商而异。 除了前两种类型之外,Kubernetes 用户通常不需要担心任何其他问题。
集群管理员通常会确保后者的正确配置。
4.10.3 - 配置对多集群的访问 本文展示如何使用配置文件来配置对多个集群的访问。
在将集群、用户和上下文定义在一个或多个配置文件中之后,用户可以使用
kubectl config use-context
命令快速地在集群之间进行切换。
说明: 用于配置集群访问的文件有时被称为 kubeconfig 文件 。
这是一种引用配置文件的通用方式,并不意味着存在一个名为 kubeconfig
的文件。
警告: 只使用来源可靠的 kubeconfig 文件。使用特制的 kubeconfig 文件可能会导致恶意代码执行或文件暴露。
如果必须使用不受信任的 kubeconfig 文件,请首先像检查 shell 脚本一样仔细检查它。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要检查 kubectl 是否安装,
执行 kubectl version --client
命令。
kubectl 的版本应该与集群的 API
服务器使用同一次版本号 。
定义集群、用户和上下文 假设用户有两个集群,一个用于正式开发工作,一个用于其它临时用途(scratch)。
在 development
集群中,前端开发者在名为 frontend
的名字空间下工作,
存储开发者在名为 storage
的名字空间下工作。在 scratch
集群中,
开发人员可能在默认名字空间下工作,也可能视情况创建附加的名字空间。
访问开发集群需要通过证书进行认证。
访问其它临时用途的集群需要通过用户名和密码进行认证。
创建名为 config-exercise
的目录。在
config-exercise
目录中,创建名为 config-demo
的文件,其内容为:
apiVersion : v1
kind : Config
preferences : {}
clusters :
- cluster :
name : development
- cluster :
name : scratch
users :
- name : developer
- name : experimenter
contexts :
- context :
name : dev-frontend
- context :
name : dev-storage
- context :
name : exp-scratch
配置文件描述了集群、用户名和上下文。config-demo
文件中含有描述两个集群、
两个用户和三个上下文的框架。
进入 config-exercise
目录。输入以下命令,将集群详细信息添加到配置文件中:
kubectl config --kubeconfig= config-demo set-cluster development --server= https://1.2.3.4 --certificate-authority= fake-ca-file
kubectl config --kubeconfig= config-demo set-cluster scratch --server= https://5.6.7.8 --insecure-skip-tls-verify
将用户详细信息添加到配置文件中:
注意: 将密码保存到 Kubernetes 客户端配置中有风险。
一个较好的替代方式是使用凭据插件并单独保存这些凭据。
参阅 client-go 凭据插件
kubectl config --kubeconfig= config-demo set-credentials developer --client-certificate= fake-cert-file --client-key= fake-key-seefile
kubectl config --kubeconfig= config-demo set-credentials experimenter --username= exp --password= some-password
说明: 要删除用户,可以运行 kubectl --kubeconfig=config-demo config unset users.<name>
要删除集群,可以运行 kubectl --kubeconfig=config-demo config unset clusters.<name>
要删除上下文,可以运行 kubectl --kubeconfig=config-demo config unset contexts.<name>
将上下文详细信息添加到配置文件中:
kubectl config --kubeconfig= config-demo set-context dev-frontend --cluster= development --namespace= frontend --user= developer
kubectl config --kubeconfig= config-demo set-context dev-storage --cluster= development --namespace= storage --user= developer
kubectl config --kubeconfig= config-demo set-context exp-scratch --cluster= scratch --namespace= default --user= experimenter
打开 config-demo
文件查看添加的详细信息。也可以使用 config view
命令进行查看:
kubectl config --kubeconfig= config-demo view
输出展示了两个集群、两个用户和三个上下文:
apiVersion : v1
clusters :
- cluster :
certificate-authority : fake-ca-file
server : https://1.2.3.4
name : development
- cluster :
insecure-skip-tls-verify : true
server : https://5.6.7.8
name : scratch
contexts :
- context :
cluster : development
namespace : frontend
user : developer
name : dev-frontend
- context :
cluster : development
namespace : storage
user : developer
name : dev-storage
- context :
cluster : scratch
namespace : default
user : experimenter
name : exp-scratch
current-context : ""
kind : Config
preferences : {}
users :
- name : developer
user :
client-certificate : fake-cert-file
client-key : fake-key-file
- name : experimenter
user :
# 文档说明(本注释不属于命令输出)。
# 将密码保存到 Kubernetes 客户端配置有风险。
# 一个较好的替代方式是使用凭据插件并单独保存这些凭据。
# 参阅 https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
password : some-password
username : exp
其中的 fake-ca-file
、fake-cert-file
和 fake-key-file
是证书文件路径名的占位符。
你需要更改这些值,使之对应你的环境中证书文件的实际路径名。
有时你可能希望在这里使用 BASE64 编码的数据而不是一个个独立的证书文件。
如果是这样,你需要在键名上添加 -data
后缀。例如,
certificate-authority-data
、client-certificate-data
和 client-key-data
。
每个上下文包含三部分(集群、用户和名字空间),例如,
dev-frontend
上下文表明:使用 developer
用户的凭证来访问 development
集群的
frontend
名字空间。
设置当前上下文:
kubectl config --kubeconfig= config-demo use-context dev-frontend
现在当输入 kubectl
命令时,相应动作会应用于 dev-frontend
上下文中所列的集群和名字空间,
同时,命令会使用 dev-frontend
上下文中所列用户的凭证。
使用 --minify
参数,来查看与当前上下文相关联的配置信息。
kubectl config --kubeconfig= config-demo view --minify
输出结果展示了 dev-frontend
上下文相关的配置信息:
apiVersion : v1
clusters :
- cluster :
certificate-authority : fake-ca-file
server : https://1.2.3.4
name : development
contexts :
- context :
cluster : development
namespace : frontend
user : developer
name : dev-frontend
current-context : dev-frontend
kind : Config
preferences : {}
users :
- name : developer
user :
client-certificate : fake-cert-file
client-key : fake-key-file
现在假设用户希望在其它临时用途集群中工作一段时间。
将当前上下文更改为 exp-scratch
:
kubectl config --kubeconfig= config-demo use-context exp-scratch
现在你发出的所有 kubectl
命令都将应用于 scratch
集群的默认名字空间。
同时,命令会使用 exp-scratch
上下文中所列用户的凭证。
查看更新后的当前上下文 exp-scratch
相关的配置:
kubectl config --kubeconfig= config-demo view --minify
最后,假设用户希望在 development
集群中的 storage
名字空间下工作一段时间。
将当前上下文更改为 dev-storage
:
kubectl config --kubeconfig= config-demo use-context dev-storage
查看更新后的当前上下文 dev-storage
相关的配置:
kubectl config --kubeconfig= config-demo view --minify
创建第二个配置文件 在 config-exercise
目录中,创建名为 config-demo-2
的文件,其中包含以下内容:
apiVersion : v1
kind : Config
preferences : {}
contexts :
- context :
cluster : development
namespace : ramp
user : developer
name : dev-ramp-up
上述配置文件定义了一个新的上下文,名为 dev-ramp-up
。
设置 KUBECONFIG 环境变量 查看是否有名为 KUBECONFIG
的环境变量。
如有,保存 KUBECONFIG
环境变量当前的值,以便稍后恢复。
例如:
Linux export KUBECONFIG_SAVED = " $KUBECONFIG "
Windows PowerShell $Env:KUBECONFIG_SAVED =$ENV:KUBECONFIG
KUBECONFIG
环境变量是配置文件路径的列表,该列表在 Linux 和 Mac 中以冒号分隔,
在 Windows 中以分号分隔。
如果有 KUBECONFIG
环境变量,请熟悉列表中的配置文件。
临时添加两条路径到 KUBECONFIG
环境变量中。例如:
Linux export KUBECONFIG = " ${ KUBECONFIG } :config-demo:config-demo-2"
Windows PowerShell $Env:KUBECONFIG =("config-demo;config-demo-2" )
在 config-exercise
目录中输入以下命令:
输出展示了 KUBECONFIG
环境变量中所列举的所有文件合并后的信息。
特别地,注意合并信息中包含来自 config-demo-2
文件的 dev-ramp-up
上下文和来自
config-demo
文件的三个上下文:
contexts :
- context :
cluster : development
namespace : frontend
user : developer
name : dev-frontend
- context :
cluster : development
namespace : ramp
user : developer
name : dev-ramp-up
- context :
cluster : development
namespace : storage
user : developer
name : dev-storage
- context :
cluster : scratch
namespace : default
user : experimenter
name : exp-scratch
关于 kubeconfig 文件如何合并的更多信息,
请参考使用 kubeconfig 文件组织集群访问
探索 $HOME/.kube 目录 如果用户已经拥有一个集群,可以使用 kubectl
与集群进行交互,
那么很可能在 $HOME/.kube
目录下有一个名为 config
的文件。
进入 $HOME/.kube
目录,看看那里有什么文件。通常会有一个名为
config
的文件,目录中可能还有其他配置文件。请简单地熟悉这些文件的内容。
将 $HOME/.kube/config 追加到 KUBECONFIG 环境变量中 如果有 $HOME/.kube/config
文件,并且还未列在 KUBECONFIG
环境变量中,
那么现在将它追加到 KUBECONFIG
环境变量中。
例如:
Linux export KUBECONFIG = " ${ KUBECONFIG } : $HOME /.kube/config"
Windows Powershell $Env:KUBECONFIG ="$Env:KUBECONFIG;$HOME\.kube\config"
在配置练习目录中输入以下命令,查看当前 KUBECONFIG
环境变量中列举的所有文件合并后的配置信息:
清理 将 KUBECONFIG
环境变量还原为原始值。例如:
Linux export KUBECONFIG = " $KUBECONFIG_SAVED "
Windows PowerShell $Env:KUBECONFIG =$ENV:KUBECONFIG_SAVED
接下来 4.10.4 - 使用端口转发来访问集群中的应用 本文展示如何使用 kubectl port-forward
连接到在 Kubernetes 集群中运行的 MongoDB 服务。
这种类型的连接对数据库调试很有用。
准备开始 创建 MongoDB Deployment 和服务 创建一个运行 MongoDB 的 Deployment:
kubectl apply -f https://k8s.io/examples/application/mongodb/mongo-deployment.yaml
成功执行的命令的输出可以证明创建了 Deployment:
deployment.apps/mongo created
查看 Pod 状态,检查其是否准备就绪:
输出显示创建的 Pod:
NAME READY STATUS RESTARTS AGE
mongo-75f59d57f4-4nd6q 1/1 Running 0 2m4s
查看 Deployment 状态:
输出显示创建的 Deployment:
NAME READY UP-TO-DATE AVAILABLE AGE
mongo 1/1 1 1 2m21s
该 Deployment 自动管理一个 ReplicaSet。查看该 ReplicaSet 的状态:
输出显示 ReplicaSet 已被创建:
NAME DESIRED CURRENT READY AGE
mongo-75f59d57f4 1 1 1 3m12s
创建一个在网络上公开的 MongoDB 服务:
kubectl apply -f https://k8s.io/examples/application/mongodb/mongo-service.yaml
成功执行的命令的输出可以证明 Service 已经被创建:
service/mongo created
检查所创建的 Service:
kubectl get service mongo
输出显示已被创建的 Service:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mongo ClusterIP 10.96.41.183 <none> 27017/TCP 11s
验证 MongoDB 服务是否运行在 Pod 中并且在监听 27017 端口:
# 将 mongo-75f59d57f4-4nd6q 改为 Pod 的名称
kubectl get pod mongo-75f59d57f4-4nd6q --template= '{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
输出应该显示对应 Pod 中 MongoDB 的端口:
27017
27017 是分配给 MongoDB 的互联网 TCP 端口。
转发一个本地端口到 Pod 端口 kubectl port-forward
允许使用资源名称
(例如 Pod 名称)来选择匹配的 Pod 来进行端口转发。
# 将 mongo-75f59d57f4-4nd6q 改为 Pod 的名称
kubectl port-forward mongo-75f59d57f4-4nd6q 28015:27017
这相当于
kubectl port-forward pods/mongo-75f59d57f4-4nd6q 28015:27017
或者
kubectl port-forward deployment/mongo 28015:27017
或者
kubectl port-forward replicaset/mongo-75f59d57f4 28015:27017
或者
kubectl port-forward service/mongo 28015:27017
以上所有命令都有效。输出类似于:
Forwarding from 127.0.0.1:28015 -> 27017
Forwarding from [::1]:28015 -> 27017
说明: kubectl port-forward
不会返回。你需要打开另一个终端来继续这个练习。
启动 MongoDB 命令行接口:
在 MongoDB 命令行提示符下,输入 ping
命令:
db.runCommand( { ping: 1 } )
成功的 ping 请求应该返回:
{ ok: 1 }
(可选操作)让 kubectl 来选择本地端口 如果你不需要指定特定的本地端口,你可以让 kubectl
来选择和分配本地端口,
这样你就不需要管理本地端口冲突。该命令使用稍微不同的语法:
kubectl port-forward deployment/mongo :27017
kubectl
工具会找到一个未被使用的本地端口号(避免使用低段位的端口号,
因为他们可能会被其他应用程序使用)。输出类似于:
Forwarding from 127.0.0.1:63753 -> 27017
Forwarding from [::1]:63753 -> 27017
讨论 与本地 28015 端口建立的连接将被转发到运行 MongoDB 服务器的 Pod 的 27017 端口。
通过此连接,你可以使用本地工作站来调试在 Pod 中运行的数据库。
说明: kubectl port-forward
仅实现了 TCP 端口 支持。
在 issue 47862
中跟踪了对 UDP 协议的支持。
接下来 进一步了解 kubectl port-forward 。
4.10.5 - 使用服务来访问集群中的应用 本文展示如何创建一个 Kubernetes 服务对象,能让外部客户端访问在集群中运行的应用。
该服务为一个应用的两个运行实例提供负载均衡。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
教程目标 运行 Hello World 应用的两个实例。 创建一个服务对象来暴露 node port。 使用服务对象来访问正在运行的应用。 为运行在两个 Pod 中的应用创建一个服务 这是应用程序部署的配置文件:
apiVersion : apps/v1
kind : Deployment
metadata :
name : hello-world
spec :
selector :
matchLabels :
run : load-balancer-example
replicas : 2
template :
metadata :
labels :
run : load-balancer-example
spec :
containers :
- name : hello-world
image : gcr.io/google-samples/node-hello:1.0
ports :
- containerPort : 8080
protocol : TCP
在你的集群中运行一个 Hello World 应用:
使用上面的文件创建应用程序 Deployment:
kubectl apply -f https://k8s.io/examples/service/access/hello-application.yaml
上面的命令创建一个
Deployment 对象
和一个关联的 ReplicaSet 对象。
这个 ReplicaSet 有两个 Pod ,
每个 Pod 都运行着 Hello World 应用。
展示 Deployment 的信息:
kubectl get deployments hello-world
kubectl describe deployments hello-world
展示你的 ReplicaSet 对象信息:
kubectl get replicasets
kubectl describe replicasets
创建一个服务对象来暴露 Deployment:
kubectl expose deployment hello-world --type= NodePort --name= example-service
展示 Service 信息:
kubectl describe services example-service
输出类似于:
Name: example-service
Namespace: default
Labels: run = load-balancer-example
Annotations: <none>
Selector: run = load-balancer-example
Type: NodePort
IP: 10.32.0.16
Port: <unset> 8080/TCP
TargetPort: 8080/TCP
NodePort: <unset> 31496/TCP
Endpoints: 10.200.1.4:8080,10.200.2.5:8080
Session Affinity: None
Events: <none>
注意服务中的 NodePort 值。例如在上面的输出中,NodePort 是 31496。
列出运行 Hello World 应用的 Pod:
kubectl get pods --selector= "run=load-balancer-example" --output= wide
输出类似于:
NAME READY STATUS ... IP NODE
hello-world-2895499144-bsbk5 1/1 Running ... 10.200.1.4 worker1
hello-world-2895499144-m1pwt 1/1 Running ... 10.200.2.5 worker2
获取运行 Hello World 的 pod 的其中一个节点的公共 IP 地址。如何获得此地址取决于你设置集群的方式。
例如,如果你使用的是 Minikube,则可以通过运行 kubectl cluster-info
来查看节点地址。
如果你使用的是 Google Compute Engine 实例,
则可以使用 gcloud compute instances list
命令查看节点的公共地址。
在你选择的节点上,创建一个防火墙规则以开放节点端口上的 TCP 流量。
例如,如果你的服务的 NodePort 值为 31568,请创建一个防火墙规则以允许 31568 端口上的 TCP 流量。
不同的云提供商提供了不同方法来配置防火墙规则。
使用节点地址和 node port 来访问 Hello World 应用:
curl http://<public-node-ip>:<node-port>
这里的 <public-node-ip>
是你节点的公共 IP 地址,<node-port>
是你服务的 NodePort 值。
对于请求成功的响应是一个 hello 消息:
使用服务配置文件 作为 kubectl expose
的替代方法,
你可以使用服务配置文件 来创建服务。
清理现场 想要删除服务,输入以下命令:
kubectl delete services example-service
想要删除运行 Hello World 应用的 Deployment、ReplicaSet 和 Pod,输入以下命令:
kubectl delete deployment hello-world
接下来 进一步了解通过服务连接应用 。
4.10.6 - 使用 Service 把前端连接到后端 本任务会描述如何创建前端(Frontend)微服务和后端(Backend)微服务。后端微服务是一个 hello 欢迎程序。
前端通过 nginx 和一个 Kubernetes 服务
暴露后端所提供的服务。
教程目标 使用部署对象(Deployment object)创建并运行一个 hello
后端微服务 使用一个 Service 对象将请求流量发送到后端微服务的多个副本 同样使用一个 Deployment 对象创建并运行一个 nginx
前端微服务 配置前端微服务将请求流量发送到后端微服务 使用 type=LoadBalancer
的 Service 对象将前端微服务暴露到集群外部 准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
本任务使用外部负载均衡服务 ,
所以需要对应的可支持此功能的环境。如果你的环境不能支持,你可以使用
NodePort
类型的服务代替。
使用部署对象(Deployment)创建后端 后端是一个简单的 hello 欢迎微服务应用。这是后端应用的 Deployment 配置文件:
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : backend
spec :
selector :
matchLabels :
app : hello
tier : backend
track : stable
replicas : 3
template :
metadata :
labels :
app : hello
tier : backend
track : stable
spec :
containers :
- name : hello
image : "gcr.io/google-samples/hello-go-gke:1.0"
ports :
- name : http
containerPort : 80
...
创建后端 Deployment:
kubectl apply -f https://k8s.io/examples/service/access/backend-deployment.yaml
查看后端的 Deployment 信息:
kubectl describe deployment backend
输出类似于:
Name: backend
Namespace: default
CreationTimestamp: Mon, 24 Oct 2016 14:21:02 -0700
Labels: app=hello
tier=backend
track=stable
Annotations: deployment.kubernetes.io/revision=1
Selector: app=hello,tier=backend,track=stable
Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: app=hello
tier=backend
track=stable
Containers:
hello:
Image: "gcr.io/google-samples/hello-go-gke:1.0"
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: hello-3621623197 (3/3 replicas created)
Events:
...
创建 hello
Service 对象 将请求从前端发送到后端的关键是后端 Service。Service 创建一个固定 IP 和 DNS 解析名入口,
使得后端微服务总是可达。Service 使用
选择算符
来寻找目标 Pod。
首先,浏览 Service 的配置文件:
---
apiVersion : v1
kind : Service
metadata :
name : hello
spec :
selector :
app : hello
tier : backend
ports :
- protocol : TCP
port : 80
targetPort : http
...
配置文件中,你可以看到名为 hello
的 Service 将流量路由到包含 app: hello
和 tier: backend
标签的 Pod。
创建后端 Service:
kubectl apply -f https://k8s.io/examples/service/access/backend-service.yaml
此时,你已经有了一个运行着 hello
应用的三个副本的 backend
Deployment,你也有了
一个 Service 用于路由网络流量。不过,这个服务在集群外部无法访问也无法解析。
创建前端应用 现在你已经有了运行中的后端应用,你可以创建一个可在集群外部访问的前端,并通过代理
前端的请求连接到后端。
前端使用被赋予后端 Service 的 DNS 名称将请求发送到后端工作 Pods。这一 DNS
名称为 hello
,也就是 examples/service/access/backend-service.yaml
配置
文件中 name
字段的取值。
前端 Deployment 中的 Pods 运行一个 nginx 镜像,这个已经配置好的镜像会将请求转发
给后端的 hello Service。下面是 nginx 的配置文件:
# Backend 是 nginx 的内部标识符,用于命名以下特定的 upstream
upstream Backend {
# hello 是 Kubernetes 中的后端服务所使用的内部 DNS 名称
server hello;
}
server {
listen 80;
location / {
# 以下语句将流量通过代理方式转发到名为 Backend 的上游
proxy_pass http://Backend;
}
}
与后端类似,前端用包含一个 Deployment 和一个 Service。后端与前端服务之间的一个
重要区别是前端 Service 的配置文件包含了 type: LoadBalancer
,也就是说,Service
会使用你的云服务商的默认负载均衡设备,从而实现从集群外访问的目的。
---
apiVersion : v1
kind : Service
metadata :
name : frontend
spec :
selector :
app : hello
tier : frontend
ports :
- protocol : "TCP"
port : 80
targetPort : 80
type : LoadBalancer
...
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : frontend
spec :
selector :
matchLabels :
app : hello
tier : frontend
track : stable
replicas : 1
template :
metadata :
labels :
app : hello
tier : frontend
track : stable
spec :
containers :
- name : nginx
image : "gcr.io/google-samples/hello-frontend:1.0"
lifecycle :
preStop :
exec :
command : ["/usr/sbin/nginx" ,"-s" ,"quit" ]
...
创建前端 Deployment 和 Service:
kubectl apply -f https://k8s.io/examples/service/access/frontend-deployment.yaml
kubectl apply -f https://k8s.io/examples/service/access/frontend-service.yaml
通过输出确认两个资源都已经被创建:
deployment.apps/frontend created
service/frontend created
说明: 这个 nginx 配置文件是被打包在
容器镜像 里的。
更好的方法是使用
ConfigMap ,
这样的话你可以更轻易地更改配置。
与前端 Service 交互 一旦你创建了 LoadBalancer 类型的 Service,你可以使用这条命令查看外部 IP:
kubectl get service frontend
外部 IP 字段的生成可能需要一些时间。如果是这种情况,外部 IP 会显示为 <pending>
。
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend 10.51.252.116 <pending> 80/TCP 10s
当外部 IP 地址被分配可用时,配置会更新,在 EXTERNAL-IP
头部下显示新的 IP:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend 10.51.252.116 XXX.XXX.XXX.XXX 80/TCP 1m
这一新的 IP 地址就可以用来从集群外与 frontend
服务交互了。
通过前端发送流量 前端和后端已经完成连接了。你可以使用 curl 命令通过你的前端 Service 的外部
IP 访问服务端点。
curl http://${ EXTERNAL_IP } # 将 EXTERNAL_P 替换为你之前看到的外部 IP
输出显示后端生成的消息:
清理现场 要删除服务,输入下面的命令:
kubectl delete services frontend backend
要删除在前端和后端应用中运行的 Deployment、ReplicaSet 和 Pod,输入下面的命令:
kubectl delete deployment frontend backend
接下来 4.10.7 - 创建外部负载均衡器 本文展示如何创建一个外部负载均衡器。
创建 服务 时,你可以选择自动创建云网络负载均衡器。
负载均衡器提供外部可访问的 IP 地址,可将流量发送到集群节点上的正确端口上
( 假设集群在支持的环境中运行,并配置了正确的云负载均衡器驱动包 )。
你还可以使用 Ingress 代替 Service。
更多信息,请参阅 Ingress 文档。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的集群必须在已经支持配置外部负载均衡器的云或其他环境中运行。
创建服务 基于清单文件创建服务 要创建外部负载均衡器,请将以下内容添加到你的 Service 清单文件:
你的清单文件可能会如下所示:
apiVersion : v1
kind : Service
metadata :
name : example-service
spec :
selector :
app : example
ports :
- port : 8765
targetPort : 9376
type : LoadBalancer
使用 kubectl 创建 Service 你也可以使用 kubectl expose
命令及其 --type=LoadBalancer
参数创建服务:
kubectl expose deployment example --port= 8765 --target-port= 9376 \
--name= example-service --type= LoadBalancer
此命令通过使用与引用资源(在上面的示例的情况下,名为 example
的
Deployment )
相同的选择器来创建一个新的服务。
更多信息(包括更多的可选参数),请参阅
kubectl expose
指南 。
找到你的 IP 地址 你可以通过 kubectl
获取服务信息,找到为你的服务创建的 IP 地址:
kubectl describe services example-service
这将获得类似如下输出:
Name: example-service
Namespace: default
Labels: app=example
Annotations: <none>
Selector: app=example
Type: LoadBalancer
IP Families: <none>
IP: 10.3.22.96
IPs: 10.3.22.96
LoadBalancer Ingress: 192.0.2.89
Port: <unset> 8765/TCP
TargetPort: 9376/TCP
NodePort: <unset> 30593/TCP
Endpoints: 172.17.0.3:9376
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
负载均衡器的 IP 地址列在 LoadBalancer Ingress
旁边。
说明: 如果你在 Minikube 上运行服务,你可以通过以下命令找到分配的 IP 地址和端口:
minikube service example-service --url
保留客户端源 IP 默认情况下,目标容器中看到的源 IP 将不是客户端的原始源 IP 。
要启用保留客户端 IP,可以在服务的 .spec
中配置以下字段:
.spec.externalTrafficPolicy
- 表示此 Service 是否希望将外部流量路由到节点本地或集群范围的端点。
有两个可用选项:Cluster
(默认)和 Local
。
Cluster
隐藏了客户端源 IP,可能导致第二跳到另一个节点,但具有良好的整体负载分布。
Local
保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳,
但存在潜在的不均衡流量传播风险。.spec.healthCheckNodePort
- 指定服务的 healthcheck nodePort(数字端口号)。
如果你未指定 healthCheckNodePort
,服务控制器从集群的 NodePort 范围内分配一个端口。
你可以通过设置 API 服务器的命令行选项 --service-node-port-range
来配置上述范围。
在服务 type
设置为 LoadBalancer 并且 externalTrafficPolicy
设置为 Local
时,
Service 将会使用用户指定的 healthCheckNodePort
值(如果你指定了它)。可以通过在服务的清单文件中将 externalTrafficPolicy
设置为 Local 来激活此功能。比如:
apiVersion : v1
kind : Service
metadata :
name : example-service
spec :
selector :
app : example
ports :
- port : 8765
targetPort : 9376
externalTrafficPolicy : Local
type : LoadBalancer
保留源 IP 时的注意事项和限制 一些云服务供应商的负载均衡服务不允许你为每个目标配置不同的权重。
由于每个目标在向节点发送流量方面的权重相同,因此外部流量不会在不同 Pod 之间平均负载。
外部负载均衡器不知道每个节点上用作目标的 Pod 数量。
在 NumServicePods << _NumNodes
或 NumServicePods >> NumNodes
时,
即使没有权重,也会看到接近相等的分布。
内部 Pod 到 Pod 的流量应该与 ClusterIP 服务类似,所有 Pod 的概率相同。
回收负载均衡器 特性状态: Kubernetes v1.17 [stable]
在通常情况下,应在删除 LoadBalancer 类型 Service 后立即清除云服务供应商中的相关负载均衡器资源。
但是,众所周知,在删除关联的服务后,云资源被孤立的情况很多。
引入了针对服务负载均衡器的终结器保护,以防止这种情况发生。
通过使用终结器,在删除相关的负载均衡器资源之前,也不会删除服务资源。
具体来说,如果服务具有 type
LoadBalancer,则服务控制器将附加一个名为
service.kubernetes.io/load-balancer-cleanup
的终结器。
仅在清除负载均衡器资源后才能删除终结器。
即使在诸如服务控制器崩溃之类的极端情况下,这也可以防止负载均衡器资源悬空。
外部负载均衡器供应商 请务必注意,此功能的数据路径由 Kubernetes 集群外部的负载均衡器提供。
当服务 type
设置为 LoadBalancer 时,Kubernetes 向集群中的 Pod 提供的功能等同于
type
设置为 ClusterIP,并通过使用托管了相关 Kubernetes Pod 的节点作为条目对负载均衡器
(从外部到 Kubernetes)进行编程来扩展它。
Kubernetes 控制平面自动创建外部负载均衡器、健康检查(如果需要)和包过滤规则(如果需要)。
一旦云服务供应商为负载均衡器分配了 IP 地址,控制平面就会查找该外部 IP 地址并将其填充到 Service 对象中。
接下来 4.10.8 - 列出集群中所有运行容器的镜像 本文展示如何使用 kubectl 来列出集群中所有运行 Pod 的容器的镜像
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
在本练习中,你将使用 kubectl 来获取集群中运行的所有 Pod,并格式化输出来提取每个 Pod 中的容器列表。
列出所有命名空间下的所有容器 使用 kubectl get pods --all-namespaces
获取所有命名空间下的所有 Pod 使用 -o jsonpath={.items[*].spec.containers[*].image}
来格式化输出,以仅包含容器镜像名称。
这将以递归方式从返回的 json 中解析出 image
字段。 使用标准化工具来格式化输出:tr
, sort
, uniq
使用 tr
以用换行符替换空格 使用 sort
来对结果进行排序 使用 uniq
来聚合镜像计数 kubectl get pods --all-namespaces -o jsonpath = "{.items[*].spec.containers[*].image}" |\
tr -s '[[:space:]]' '\n' |\
sort |\
uniq -c
jsonpath 解释如下:
.items[*]
: 对于每个返回的值.spec
: 获取 spec.containers[*]
: 对于每个容器.image
: 获取镜像说明: 按名字获取单个 Pod 时,例如 kubectl get pod nginx
,路径的 .items[*]
部分应该省略,
因为返回的是一个 Pod 而不是一个项目列表。
按 Pod 列出容器镜像 可以使用 range
操作进一步控制格式化,以单独操作每个元素。
kubectl get pods --all-namespaces -o jsonpath = '{range .items[*]}{"\n"}{.metadata.name}{":\t"}{range .spec.containers[*]}{.image}{", "}{end}{end}' |\
sort
列出以标签过滤后的 Pod 的所有容器 要获取匹配特定标签的 Pod,请使用 -l 参数。以下匹配仅与标签 app=nginx
相符的 Pod。
kubectl get pods --all-namespaces -o jsonpath = "{.items[*].spec.containers[*].image}" -l app = nginx
列出以命名空间过滤后的 Pod 的所有容器 要获取匹配特定命名空间的 Pod,请使用 namespace 参数。以下仅匹配 kube-system
命名空间下的 Pod。
kubectl get pods --namespace kube-system -o jsonpath = "{.items[*].spec.containers[*].image}"
使用 go-template 代替 jsonpath 来获取容器 作为 jsonpath 的替代,Kubectl 支持使用 go-templates 来格式化输出:
kubectl get pods --all-namespaces -o go-template --template= "{{range .items}}{{range .spec.containers}}{{.image}} {{end}}{{end}}"
接下来 参考 4.10.9 - 在 Minikube 环境中使用 NGINX Ingress 控制器配置 Ingress Ingress 是一种 API 对象,其中定义了一些规则使得集群中的
服务可以从集群外访问。
Ingress 控制器
负责满足 Ingress 中所设置的规则。
本节为你展示如何配置一个简单的 Ingress,根据 HTTP URI 将服务请求路由到
服务 web
或 web2
。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.19.
要获知版本信息,请输入
kubectl version
.
如果你使用的是较早的 Kubernetes 版本,请切换到该版本的文档。
创建一个 Minikube 集群 使用 Katacoda
Launch Terminal 本地 如果已经在本地安装Minikube ,
请运行 minikube start
创建一个集群。 启用 Ingress 控制器 为了启用 NGINIX Ingress 控制器,可以运行下面的命令:
minikube addons enable ingress
检查验证 NGINX Ingress 控制器处于运行状态:
kubectl get pods -n ingress-nginx
说明: 最多可能需要等待一分钟才能看到这些 Pod 运行正常。
输出类似于:
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-g9g49 0/1 Completed 0 11m
ingress-nginx-admission-patch-rqp78 0/1 Completed 1 11m
ingress-nginx-controller-59b45fb494-26npt 1/1 Running 0 11m
kubectl get pods -n kube-system
说明: 最多可能需要等待一分钟才能看到这些 Pod 运行正常。
输出类似于:
NAME READY STATUS RESTARTS AGE
default-http-backend-59868b7dd6-xb8tq 1/1 Running 0 1m
kube-addon-manager-minikube 1/1 Running 0 3m
kube-dns-6dcb57bcc8-n4xd4 3/3 Running 0 2m
kubernetes-dashboard-5498ccf677-b8p5h 1/1 Running 0 2m
nginx-ingress-controller-5984b97644-rnkrg 1/1 Running 0 1m
storage-provisioner 1/1 Running 0 2m
请确保可以在输出中看到一个名称以 nginx-ingress-controller-
为前缀的 Pod。
部署一个 Hello World 应用 使用下面的命令创建一个 Deployment:
kubectl create deployment web --image= gcr.io/google-samples/hello-app:1.0
输出:
deployment.apps/web created
将 Deployment 暴露出来:
kubectl expose deployment web --type= NodePort --port= 8080
输出:
service/web exposed
验证 Service 已经创建,并且可以从节点端口访问:
输出类似于:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT( S) AGE
web NodePort 10.104.133.249 <none> 8080:31637/TCP 12m
使用节点端口信息访问服务:
minikube service web --url
输出类似于:
说明: 如果使用的是 Katacoda 环境,在终端面板顶端,请点击加号标志。
然后点击 Select port to view on Host 1 。
输入节点和端口号(这里是31637
),之后点击 Display Port 。
输出类似于:
Hello, world!
Version: 1.0.0
Hostname: web-55b8c6998d-8k564
你现在应该可以通过 Minikube 的 IP 地址和节点端口来访问示例应用了。
下一步是让自己能够通过 Ingress 资源来访问应用。
创建一个 Ingress 下面是一个定义 Ingress 的配置文件,负责通过 hello-world.info
将请求
转发到你的服务。
根据下面的 YAML 创建文件 example-ingress.yaml
:
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : example-ingress
annotations :
nginx.ingress.kubernetes.io/rewrite-target : /$1
spec :
rules :
- host : hello-world.info
http :
paths :
- path : /
pathType : Prefix
backend :
service :
name : web
port :
number : 8080
通过运行下面的命令创建 Ingress 对象:
kubectl apply -f https://k8s.io/examples/service/networking/example-ingress.yaml
输出:
ingress.networking.k8s.io/example-ingress created
验证 IP 地址已被设置:
说明: 此操作可能需要几分钟时间。
接下来你将会在ADDRESS列中看到IPv4地址,例如:
NAME CLASS HOSTS ADDRESS PORTS AGE
example-ingress <none> hello-world.info 172.17.0.15 80 38s
在 /etc/hosts
文件的末尾添加以下内容(需要管理员访问权限):
说明: 如果你在本地运行 Minikube 环境,需要使用 minikube ip
获得外部 IP 地址。
Ingress 列表中显示的 IP 地址会是内部 IP 地址。
172.17.0.15 hello-world.info
添加完成后,在浏览器中访问URL hello-world.info
,请求将被发送到 Minikube。
验证 Ingress 控制器能够转发请求流量:
你应该看到类似输出:
Hello, world!
Version: 1.0.0
Hostname: web-55b8c6998d-8k564
说明: 如果你在使用本地 Minikube 环境,你可以从浏览器中访问 hello-world.info。
创建第二个 Deployment 使用下面的命令创建第二个 Deployment:
kubectl create deployment web2 --image= gcr.io/google-samples/hello-app:2.0
输出:
deployment.apps/web2 created
将第二个 Deployment 暴露出来:
kubectl expose deployment web2 --port= 8080 --type= NodePort
输出:
service/web2 exposed
编辑现有的 Ingress 编辑现有的 example-ingress.yaml
,在文件最后添加以下行:
- path : /v2
pathType : Prefix
backend :
service :
name : web2
port :
number : 8080
应用变更:
kubectl apply -f example-ingress.yaml
输出:
ingress.networking/example-ingress configured
测试你的 Ingress 访问 HelloWorld 应用的第一个版本:
输出类似于:
Hello, world!
Version: 1.0.0
Hostname: web-55b8c6998d-8k564
访问 HelloWorld 应用的第二个版本:
输出类似于:
Hello, world!
Version: 2.0.0
Hostname: web2-75cd47646f-t8cjk
说明: 如果你在本地运行 Minikube 环境,你可以使用浏览器来访问
hello-world.info 和 hello-world.info/v2。
接下来 4.10.10 - 为集群配置 DNS Kubernetes 提供 DNS 集群插件,大多数支持的环境默认情况下都会启用。
在 Kubernetes 1.11 及其以后版本中,推荐使用 CoreDNS,
kubeadm 默认会安装 CoreDNS。
要了解关于如何为 Kubernetes 集群配置 CoreDNS 的更多信息,参阅
定制 DNS 服务 。
关于如何利用 kube-dns 配置 kubernetes DNS 的演示例子,参阅
Kubernetes DNS 插件示例 。
4.10.11 - 访问集群上运行的服务 本文展示了如何连接 Kubernetes 集群上运行的服务。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
访问集群上运行的服务 在 Kubernetes 里,节点 、
Pod 和
服务 都有自己的 IP。
许多情况下,集群上的节点 IP、Pod IP 和某些服务 IP 是路由不可达的,
所以不能从集群之外访问它们,例如从你自己的台式机。
连接方式 你有多种可选方式从集群外连接节点、Pod 和服务:
通过公网 IP 访问服务使用类型为 NodePort
或 LoadBalancer
的服务,可以从外部访问它们。
请查阅服务 和
kubectl expose 文档。 取决于你的集群环境,你可以仅把服务暴露在你的企业网络环境中,也可以将其暴露在
因特网上。需要考虑暴露的服务是否安全,它是否有自己的用户认证? 将 Pod 放置于服务背后。如果要访问一个副本集合中特定的 Pod,例如用于调试目的,
请给 Pod 指定一个独特的标签并创建一个新服务选择该标签。 大部分情况下,都不需要应用开发者通过节点 IP 直接访问节点。 通过 Proxy 动词访问服务、节点或者 Pod在访问远程服务之前,利用 API 服务器执行身份认证和鉴权。
如果你的服务不够安全,无法暴露到因特网中,或者需要访问节点 IP 上的端口,
又或者出于调试目的,可使用这种方式。 代理可能给某些应用带来麻烦 此方式仅适用于 HTTP/HTTPS 进一步的描述在这里 从集群中的 node 或者 pod 访问。 从集群中的一个节点或 Pod 访问运行一个 Pod,然后使用
kubectl exec
连接到它的 Shell。从那个 Shell 连接其他的节点、Pod 和 服务 某些集群可能允许你 SSH 到集群中的节点。你可能可以从那儿访问集群服务。
这是一个非标准的方式,可能在一些集群上能工作,但在另一些上却不能。
浏览器和其他工具可能已经安装也可能没有安装。集群 DNS 可能不会正常工作。 发现内置服务 典型情况下,kube-system 名字空间中会启动集群的几个服务。
使用 kubectl cluster-info
命令获取这些服务的列表:
输出类似于:
Kubernetes master is running at https://192.0.2.1
elasticsearch-logging is running at https://192.0.2.1/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
kibana-logging is running at https://192.0.2.1/api/v1/namespaces/kube-system/services/kibana-logging/proxy
kube-dns is running at https://192.0.2.1/api/v1/namespaces/kube-system/services/kube-dns/proxy
grafana is running at https://192.0.2.1/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
heapster is running at https://192.0.2.1/api/v1/namespaces/kube-system/services/monitoring-heapster/proxy
这一输出显示了用 proxy 动词访问每个服务时可用的 URL。例如,此集群
(使用 Elasticsearch)启用了集群层面的日志。如果提供合适的凭据,可以通过
https://192.0.2.1/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/
访问,或通过一个 kubectl proxy
来访问:
http://localhost:8080/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/
。
手动构建 API 服务器代理 URLs 如前所述,你可以使用 kubectl cluster-info
命令取得服务的代理 URL。
为了创建包含服务末端、后缀和参数的代理 URLs,你可以在服务的代理 URL 中添加:
http://
kubernetes_master_address
/api/v1/namespaces/
namespace_name
/services/
service_name[:port_name]
/proxy
如果还没有为你的端口指定名称,你可以不用在 URL 中指定 port_name 。
对于命名和未命名端口,你还可以使用端口号代替 port_name 。
默认情况下,API 服务器使用 HTTP 为你的服务提供代理。 要使用 HTTPS,请在服务名称前加上 https:
:
http://<kubernetes_master_address>/api/v1/namespaces/<namespace_name>/services/<service_name>/proxy
URL 的 <service_name>
段支持的格式为:
<service_name>
- 使用 http 代理到默认或未命名端口<service_name>:<port_name>
- 使用 http 代理到指定的端口名称或端口号https:<service_name>:
- 使用 https 代理到默认或未命名端口(注意尾随冒号)https:<service_name>:<port_name>
- 使用 https 代理到指定的端口名称或端口号示例 通过 Web 浏览器访问集群中运行的服务 你或许能够将 API 服务器代理的 URL 放入浏览器的地址栏,然而:
Web 服务器通常不能传递令牌,所以你可能需要使用基本(密码)认证。
API 服务器可以配置为接受基本认证,但你的集群可能并没有这样配置。 某些 Web 应用可能无法工作,特别是那些使用客户端 Javascript 构造 URL 的
应用,所构造的 URL 可能并不支持代理路径前缀。 4.10.12 - 同 Pod 内的容器使用共享卷通信 本文旨在说明如何让一个 Pod 内的两个容器使用一个卷(Volume)进行通信。
参阅如何让两个进程跨容器通过
共享进程名字空间 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建一个包含两个容器的 Pod 在这个练习中,你会创建一个包含两个容器的 Pod。两个容器共享一个卷用于他们之间的通信。
Pod 的配置文件如下:
apiVersion : v1
kind : Pod
metadata :
name : two-containers
spec :
restartPolicy : Never
volumes :
- name : shared-data
emptyDir : {}
containers :
- name : nginx-container
image : nginx
volumeMounts :
- name : shared-data
mountPath : /usr/share/nginx/html
- name : debian-container
image : debian
volumeMounts :
- name : shared-data
mountPath : /pod-data
command : ["/bin/sh" ]
args : ["-c" , "echo Hello from the debian container > /pod-data/index.html" ]
在配置文件中,你可以看到 Pod 有一个共享卷,名为 shared-data
。
配置文件中的第一个容器运行了一个 nginx 服务器。共享卷的挂载路径是 /usr/share/nginx/html
。
第二个容器是基于 debian 镜像的,有一个 /pod-data
的挂载路径。第二个容器运行了下面的命令然后终止。
echo Hello from the debian container > /pod-data/index.html
注意,第二个容器在 nginx 服务器的根目录下写了 index.html
文件。
创建一个包含两个容器的 Pod:
kubectl apply -f https://k8s.io/examples/pods/two-container-pod.yaml
查看 Pod 和容器的信息:
kubectl get pod two-containers --output= yaml
这是输出的一部分:
apiVersion : v1
kind : Pod
metadata :
...
name : two-containers
namespace : default
...
spec :
...
containerStatuses :
- containerID : docker://c1d8abd1 ...
image : debian
...
lastState :
terminated :
...
name : debian-container
...
- containerID : docker://96c1ff2c5bb ...
image : nginx
...
name : nginx-container
...
state :
running :
...
你可以看到 debian 容器已经被终止了,而 nginx 服务器依然在运行。
进入 nginx 容器的 shell:
kubectl exec -it two-containers -c nginx-container -- /bin/bash
在 shell 中,确认 nginx 还在运行。
root@two-containers:/# apt-get update
root@two-containers:/# apt-get install curl procps
root@two-containers:/# ps aux
输出类似于这样:
USER PID ... STAT START TIME COMMAND
root 1 ... Ss 21:12 0:00 nginx: master process nginx -g daemon off;
回忆一下,debian 容器在 nginx 的根目录下创建了 index.html
文件。
使用 curl
向 nginx 服务器发送一个 GET 请求:
root@two-containers:/# curl localhost
输出表示 nginx 提供了 debian 容器写的页面:
Hello from the debian container
讨论 Pod 能有多个容器的主要原因是为了支持辅助应用(helper applications),以协助主应用(primary application)。
辅助应用的典型例子是数据抽取,数据推送和代理。辅助应用和主应用经常需要相互通信。
就如这个练习所示,通信通常是通过共享文件系统完成的,或者,也通过回环网络接口 localhost 完成。
举个网络接口的例子,web 服务器带有一个协助程序用于拉取 Git 仓库的更新。
在本练习中的卷为 Pod 生命周期中的容器相互通信提供了一种方法。如果 Pod 被删除或者重建了,
任何共享卷中的数据都会丢失。
接下来 4.11 - 扩展 Kubernetes 了解针对工作环境需要来调整 Kubernetes 集群的进阶方法。
4.11.1 - 配置聚合层 配置聚合层 可以允许
Kubernetes apiserver 使用其它 API 扩展,这些 API 不是核心 Kubernetes API 的一部分。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
说明: 要使聚合层在你的环境中正常工作以支持代理服务器和扩展 apiserver 之间的相互 TLS 身份验证,
需要满足一些设置要求。Kubernetes 和 kube-apiserver 具有多个 CA,
因此请确保代理是由聚合层 CA 签名的,而不是由 Kubernetes 通用 CA 签名的。
注意: 对不同的客户端类型重复使用相同的 CA 会对集群的功能产生负面影响。
有关更多信息,请参见 CA 重用和冲突 。
身份认证流程 与自定义资源定义(CRD)不同,除标准的 Kubernetes apiserver 外,Aggregation API
还涉及另一个服务器:扩展 apiserver。
Kubernetes apiserver 将需要与你的扩展 apiserver 通信,并且你的扩展 apiserver
也需要与 Kubernetes apiserver 通信。
为了确保此通信的安全,Kubernetes apiserver 使用 x509 证书向扩展 apiserver 认证。
本节介绍身份认证和鉴权流程的工作方式以及如何配置它们。
大致流程如下:
Kubernetes apiserver:对发出请求的用户身份认证,并对请求的 API 路径执行鉴权。 Kubernetes apiserver:将请求转发到扩展 apiserver 扩展 apiserver:认证来自 Kubernetes apiserver 的请求 扩展 apiserver:对来自原始用户的请求鉴权 扩展 apiserver:执行 本节的其余部分详细描述了这些步骤。
该流程可以在下图中看到。
以上泳道的来源可以在本文档的源码中找到。
Kubernetes Apiserver 认证和授权 由扩展 apiserver 服务的对 API 路径的请求以与所有 API 请求相同的方式开始:
与 Kubernetes apiserver 的通信。该路径已通过扩展 apiserver 在
Kubernetes apiserver 中注册。
用户与 Kubernetes apiserver 通信,请求访问路径。
Kubernetes apiserver 使用它的标准认证和授权配置来对用户认证,以及对特定路径的鉴权。
有关对 Kubernetes 集群认证的概述,
请参见对集群认证 。
有关对 Kubernetes 集群资源的访问鉴权的概述,
请参见鉴权概述 。
到目前为止,所有内容都是标准的 Kubernetes API 请求,认证与鉴权。
Kubernetes apiserver 现在准备将请求发送到扩展 apiserver。
Kubernetes Apiserver 代理请求 Kubernetes apiserver 现在将请求发送或代理到注册以处理该请求的扩展 apiserver。
为此,它需要了解几件事:
Kubernetes apiserver 应该如何向扩展 apiserver 认证,以通知扩展
apiserver 通过网络发出的请求来自有效的 Kubernetes apiserver?
Kubernetes apiserver 应该如何通知扩展 apiserver
原始请求已通过认证的用户名和组?
为提供这两条信息,你必须使用若干标志来配置 Kubernetes apiserver。
Kubernetes Apiserver 客户端认证 Kubernetes apiserver 通过 TLS 连接到扩展 apiserver,并使用客户端证书认证。
你必须在启动时使用提供的标志向 Kubernetes apiserver 提供以下内容:
通过 --proxy-client-key-file
指定私钥文件 通过 --proxy-client-cert-file
签名的客户端证书文件 通过 --requestheader-client-ca-file
签署客户端证书文件的 CA 证书 通过 --requestheader-allowed-names
在签署的客户端证书中有效的公用名(CN) Kubernetes apiserver 将使用由 --proxy-client-*-file
指示的文件来向扩展 apiserver认证。
为了使合规的扩展 apiserver 能够将该请求视为有效,必须满足以下条件:
连接必须使用由 CA 签署的客户端证书,该证书的证书位于 --requestheader-client-ca-file
中。 连接必须使用客户端证书,该客户端证书的 CN 是 --requestheader-allowed-names
中列出的证书之一。 说明: 你可以将此选项设置为空白,即为--requestheader-allowed-names=""
。
这将向扩展 apiserver 指示任何 CN 都是可接受的。
使用这些选项启动时,Kubernetes apiserver 将:
使用它们向扩展 apiserver 认证。 在 kube-system
命名空间中创建一个名为
extension-apiserver-authentication
的 ConfigMap,
它将在其中放置 CA 证书和允许的 CN。
反过来,扩展 apiserver 可以检索这些内容以验证请求。 请注意,Kubernetes apiserver 使用相同的客户端证书对所有扩展 apiserver 认证。
它不会为每个扩展 apiserver 创建一个客户端证书,而是创建一个证书作为
Kubernetes apiserver 认证。所有扩展 apiserver 请求都重复使用相同的请求。
原始请求用户名和组 当 Kubernetes apiserver 将请求代理到扩展 apiserver 时,
它将向扩展 apiserver 通知原始请求已成功通过其验证的用户名和组。
它在其代理请求的 HTTP 头部中提供这些。你必须将要使用的标头名称告知
Kubernetes apiserver。
通过 --requestheader-username-headers
标明用来保存用户名的头部 通过 --requestheader-group-headers
标明用来保存 group 的头部 通过 --requestheader-extra-headers-prefix
标明用来保存拓展信息前缀的头部 这些头部名称也放置在 extension-apiserver-authentication
ConfigMap 中,
因此扩展 apiserver 可以检索和使用它们。
扩展 Apiserver 认证请求 扩展 apiserver 在收到来自 Kubernetes apiserver 的代理请求后,
必须验证该请求确实确实来自有效的身份验证代理,
该认证代理由 Kubernetes apiserver 履行。扩展 apiserver 通过以下方式对其认证:
如上所述,从 kube-system
中的 ConfigMap 中检索以下内容:
客户端 CA 证书 允许名称(CN)列表 用户名,组和其他信息的头部 使用以下证书检查 TLS 连接是否已通过认证:
由其证书与检索到的 CA 证书匹配的 CA 签名。 在允许的 CN 列表中有一个 CN,除非列表为空,在这种情况下允许所有 CN。 从适当的头部中提取用户名和组。 如果以上均通过,则该请求是来自合法认证代理(在本例中为 Kubernetes apiserver)
的有效代理请求。
请注意,扩展 apiserver 实现负责提供上述内容。
默认情况下,许多扩展 apiserver 实现利用 k8s.io/apiserver/
软件包来做到这一点。
也有一些实现可能支持使用命令行选项来覆盖这些配置。
为了具有检索 configmap 的权限,扩展 apiserver 需要适当的角色。
在 kube-system
名字空间中有一个默认角色
extension-apiserver-authentication-reader
可用于设置。
扩展 Apiserver 对请求鉴权 扩展 apiserver 现在可以验证从标头检索的user/group
是否有权执行给定请求。
通过向 Kubernetes apiserver 发送标准
SubjectAccessReview 请求来实现。
为了使扩展 apiserver 本身被鉴权可以向 Kubernetes apiserver 提交 SubjectAccessReview 请求,
它需要正确的权限。
Kubernetes 包含一个具有相应权限的名为 system:auth-delegator
的默认 ClusterRole
,
可以将其授予扩展 apiserver 的服务帐户。
扩展 Apiserver 执行 如果 SubjectAccessReview
通过,则扩展 apiserver 执行请求。
启用 Kubernetes Apiserver 标志 通过以下 kube-apiserver
标志启用聚合层。
你的服务提供商可能已经为你完成了这些工作:
--requestheader-client-ca-file=<path to aggregator CA cert>
--requestheader-allowed-names=front-proxy-client
--requestheader-extra-headers-prefix=X-Remote-Extra-
--requestheader-group-headers=X-Remote-Group
--requestheader-username-headers=X-Remote-User
--proxy-client-cert-file=<path to aggregator proxy cert>
--proxy-client-key-file=<path to aggregator proxy key>
CA 重用和冲突 Kubernetes apiserver 有两个客户端 CA 选项:
--client-ca-file
--requestheader-client-ca-file
这些功能中的每个功能都是独立的;如果使用不正确,可能彼此冲突。
--client-ca-file
:当请求到达 Kubernetes apiserver 时,如果启用了此选项,
则 Kubernetes apiserver 会检查请求的证书。
如果它是由 --client-ca-file
引用的文件中的 CA 证书之一签名的,
并且用户是公用名 CN=
的值,而组是组织 O=
的取值,则该请求被视为合法请求。
请参阅关于 TLS 身份验证的文档 。
--requestheader-client-ca-file
:当请求到达 Kubernetes apiserver 时,
如果启用此选项,则 Kubernetes apiserver 会检查请求的证书。
如果它是由文件引用中的 --requestheader-client-ca-file 所签署的 CA 证书之一签名的,
则该请求将被视为潜在的合法请求。
然后,Kubernetes apiserver 检查通用名称 CN=
是否是
--requestheader-allowed-names
提供的列表中的名称之一。
如果名称允许,则请求被批准;如果不是,则请求被拒绝。
如果同时提供了 --client-ca-file
和 --requestheader-client-ca-file
,
则首先检查 --requestheader-client-ca-file
CA,然后再检查 --client-ca-file
。
通常,这些选项中的每一个都使用不同的 CA(根 CA 或中间 CA)。
常规客户端请求与 --client-ca-file
相匹配,而聚合请求要与
--requestheader-client-ca-file
相匹配。
但是,如果两者都使用同一个 CA,则通常会通过 --client-ca-file
传递的客户端请求将失败,因为 CA 将与 --requestheader-client-ca-file
中的 CA 匹配,但是通用名称 CN=
将不匹配 --requestheader-allowed-names
中可接受的通用名称之一。
这可能导致你的 kubelet 和其他控制平面组件以及最终用户无法向 Kubernetes
apiserver 认证。
因此,请对用于控制平面组件和最终用户鉴权的 --client-ca-file
选项和
用于聚合 apiserver 鉴权的 --requestheader-client-ca-file
选项使用
不同的 CA 证书。
警告: 除非你了解风险和保护 CA 用法的机制,否则 不要 重用在不同上下文中使用的 CA。
如果你未在运行 API 服务器的主机上运行 kube-proxy,则必须确保使用以下
kube-apiserver
标志启用系统:
--enable-aggregator-routing=true
注册 APIService 对象 你可以动态配置将哪些客户端请求代理到扩展 apiserver。以下是注册示例:
apiVersion : apiregistration.k8s.io/v1
kind : APIService
metadata :
name : <注释对象名称>
spec :
group : <扩展 Apiserver 的 API 组名>
version : <扩展 Apiserver 的 API 版本>
groupPriorityMinimum : <APIService 对应组的优先级, 参考 API 文档>
versionPriority : <版本在组中的优先排序, 参考 API 文档>
service :
namespace : <拓展 Apiserver 服务的名字空间>
name : <拓展 Apiserver 服务的名称>
caBundle : <PEM 编码的 CA 证书,用于对 Webhook 服务器的证书签名>
APIService
对象的名称必须是合法的路径片段名称 。
调用扩展 apiserver 一旦 Kubernetes apiserver 确定应将请求发送到扩展 apiserver,
它需要知道如何调用它。
service
部分是对扩展 apiserver 的服务的引用。
服务的名字空间和名字是必需的。端口是可选的,默认为 443。
下面是一个扩展 apiserver 的配置示例,它被配置为在端口 1234
上调用。
并针对 ServerName my-service-name.my-service-namespace.svc
使用自定义的 CA 包来验证 TLS 连接使用自定义 CA 捆绑包的
my-service-name.my-service-namespace.svc
。
apiVersion : apiregistration.k8s.io/v1
kind : APIService
...
spec :
...
service :
namespace : my-service-namespace
name : my-service-name
port : 1234
caBundle : "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...
接下来
4.11.2 - 使用自定义资源 4.11.2.1 - 使用 CustomResourceDefinition 扩展 Kubernetes API 本页展示如何使用
CustomResourceDefinition
将定制资源(Custom Resource)
安装到 Kubernetes API 上。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.16.
要获知版本信息,请输入
kubectl version
.
如果你在使用较老的、仍处于被支持范围的 Kubernetes 版本,
请切换到该版本的文档查看对于你的集群而言有用的建议。
创建 CustomResourceDefinition 当你创建新的 CustomResourceDefinition(CRD)时,Kubernetes API 服务器会为你所指定的每个版本生成一个新的
RESTful 资源路径。
基于 CRD 对象所创建的自定义资源可以是名字空间作用域的,也可以是集群作用域的,
取决于 CRD 对象 spec.scope
字段的设置。
与其它的内置对象一样,删除名字空间也将删除该名字空间中的所有自定义对象。
CustomResourceDefinitions 本身是无名字空间的,可在所有名字空间中访问。
例如,如果你将下面的 CustomResourceDefinition 保存到 resourcedefinition.yaml
文件:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
# 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name : crontabs.stable.example.com
spec :
# 组名称,用于 REST API: /apis/<组>/<版本>
group : stable.example.com
# 列举此 CustomResourceDefinition 所支持的版本
versions :
- name : v1
# 每个版本都可以通过 served 标志来独立启用或禁止
served : true
# 其中一个且只有一个版本必需被标记为存储版本
storage : true
schema :
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
image :
type : string
replicas :
type : integer
# 可以是 Namespaced 或 Cluster
scope : Namespaced
names :
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural : crontabs
# 名称的单数形式,作为命令行使用时和显示时的别名
singular : crontab
# kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
kind : CronTab
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames :
- ct
之后创建它:
kubectl apply -f resourcedefinition.yaml
这样一个新的受名字空间约束的 RESTful API 端点会被创建在:
/apis/stable.example.com/v1/namespaces/*/crontabs/...
此端点 URL 自此可以用来创建和管理定制对象。对象的 kind
将是来自你上面创建时
所用的 spec 中指定的 CronTab
。
创建端点的操作可能需要几秒钟。你可以监测你的 CustomResourceDefinition 的
Established
状况变为 true,或者监测 API 服务器的发现信息等待你的资源出现在
那里。
创建定制对象 在创建了 CustomResourceDefinition 对象之后,你可以创建定制对象(Custom
Objects)。定制对象可以包含定制字段。这些字段可以包含任意的 JSON 数据。
在下面的例子中,在类别为 CronTab
的定制对象中,设置了cronSpec
和 image
定制字段。类别 CronTab
来自你在上面所创建的 CRD 的规约。
如果你将下面的 YAML 保存到 my-crontab.yaml
:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * * */5"
image : my-awesome-cron-image
并执行创建命令:
kubectl apply -f my-crontab.yaml
你就可以使用 kubectl 来管理你的 CronTab 对象了。例如:
应该会输出如下列表:
NAME AGE
my-new-cron-object 6s
使用 kubectl 时,资源名称是大小写不敏感的,而且你既可以使用 CRD 中所定义的单数
形式或复数形式,也可以使用其短名称:
你可以看到输出中包含了你创建定制对象时在 YAML 文件中指定的定制字段 cronSpec
和 image
:
apiVersion : v1
items :
- apiVersion : stable.example.com/v1
kind : CronTab
metadata :
annotations :
kubectl.kubernetes.io/last-applied-configuration : |
{"apiVersion" :"stable.example.com/v1" ,"kind" :"CronTab" ,"metadata" :{"annotations" :{},"name" :"my-new-cron-object" ,"namespace" :"default" },"spec" :{"cronSpec" :"* * * * */5" ,"image" :"my-awesome-cron-image" }}
creationTimestamp : "2021-06-20T07:35:27Z"
generation : 1
name : my-new-cron-object
namespace : default
resourceVersion : "1326"
uid : 9aab1d66-628e-41bb-a422-57b8b3b1f5a9
spec :
cronSpec : '* * * * */5'
image : my-awesome-cron-image
kind : List
metadata :
resourceVersion : ""
selfLink : ""
删除 CustomResourceDefinition 当你删除某 CustomResourceDefinition 时,服务器会卸载其 RESTful API
端点,并删除服务器上存储的所有定制对象。
kubectl delete -f resourcedefinition.yaml
kubectl get crontabs
Error from server (NotFound): Unable to list {"stable.example.com" "v1" "crontabs"}: the server could not
find the requested resource (get crontabs.stable.example.com)
如果你在以后创建相同的 CustomResourceDefinition 时,该 CRD 会是一个空的结构。
设置结构化的模式 CustomResource 对象在定制字段中保存结构化的数据,这些字段和内置的字段
apiVersion
、kind
和 metadata
等一起存储,不过内置的字段都会被 API
服务器隐式完成合法性检查。有了 OpenAPI v3.0 检查
能力之后,你可以设置一个模式(Schema),在创建和更新定制对象时,这一模式会被用来
对对象内容进行合法性检查。参阅下文了解这类模式的细节和局限性。
在 apiextensions.k8s.io/v1
版本中,CustomResourceDefinition 的这一结构化模式
定义是必需的。
在 CustomResourceDefinition 的 beta 版本中,结构化模式定义是可选的。
结构化模式本身是一个 OpenAPI v3.0 验证模式 ,其中:
为对象根(root)设置一个非空的 type 值(藉由 OpenAPI 中的 type
),对每个
object 节点的每个字段(藉由 OpenAPI 中的 properties
或 additionalProperties
)以及
array 节点的每个条目(藉由 OpenAPI 中的 items
)也要设置非空的 type 值,
除非:节点包含属性 x-kubernetes-int-or-string: true
节点包含属性 x-kubernetes-preserve-unknown-fields: true
对于 object 的每个字段或 array 中的每个条目,如果其定义中包含 allOf
、anyOf
、oneOf
或 not
,则模式也要指定这些逻辑组合之外的字段或条目(试比较例 1 和例 2)。 在 allOf
、anyOf
、oneOf
或 not
上下文内不设置 description
、type
、default
、
additionalProperties
或者 nullable
。此规则的例外是
x-kubernetes-int-or-string
的两种模式(见下文)。 如果 metadata
被设置,则只允许对 metadata.name
和 metadata.generateName
设置约束。 非结构化的例 1:
allOf:
- properties:
foo:
...
违反了第 2 条规则。下面的是正确的:
properties:
foo:
...
allOf:
- properties:
foo:
...
非结构化的例 2:
allOf:
- items:
properties:
foo:
...
违反了第 2 条规则。下面的是正确的:
items:
properties:
foo:
...
allOf:
- items:
properties:
foo:
...
非结构化的例 3:
properties:
foo:
pattern: "abc"
metadata:
type: object
properties:
name:
type: string
pattern: "^a"
finalizers:
type: array
items:
type: string
pattern: "my-finalizer"
anyOf:
- properties:
bar:
type: integer
minimum: 42
required: ["bar"]
description: "foo bar object"
不是一个结构化的模式,因为其中存在以下违例:
根节点缺失 type 设置(规则 1) foo
的 type 缺失(规则 1)anyOf
中的 bar
未在外部指定(规则 2)bar
的 type
位于 anyOf
中(规则 3)anyOf
中设置了 description
(规则 3)metadata.finalizers
不可以被限制 (规则 4)作为对比,下面的 YAML 所对应的模式则是结构化的:
type : object
description : "foo bar object"
properties :
foo :
type : string
pattern : "abc"
bar :
type : integer
metadata :
type : object
properties :
name :
type : string
pattern : "^a"
anyOf :
- properties :
bar :
minimum : 42
required : ["bar" ]
如果违反了结构化模式规则,CustomResourceDefinition 的 NonStructural
状况中会包含报告信息。
字段剪裁 CustomResourceDefinition 在集群的持久性存储
etcd
中保存经过合法性检查的资源数据。
就像原生的 Kubernetes 资源,例如 ConfigMap ,
如果你指定了 API 服务器所无法识别的字段,则该未知字段会在保存资源之前被
剪裁(Pruned) 掉(删除)。
从 apiextensions.k8s.io/v1beta1
转换到 apiextensions.k8s.io/v1
的 CRD
可能没有结构化的模式定义,因此其 spec.preserveUnknownFields
可能为 true
。
对于使用 apiextensions.k8s.io/v1beta1
且将 spec.preserveUnknownFields
设置为 true
创建的旧 CustomResourceDefinition 对象,有以下表现:
为了与 apiextensions.k8s.io/v1
兼容,将你的定制资源定义更新为:
使用结构化的 OpenAPI 模式。 spec.preserveUnknownFields
设置为 false
。如果你将下面的 YAML 保存到 my-crontab.yaml
文件:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * * */5"
image : my-awesome-cron-image
someRandomField : 42
并创建之:
kubectl create --validate= false -f my-crontab.yaml -o yaml
输出类似于:
apiVersion : stable.example.com/v1
kind : CronTab
metadata :
creationTimestamp : 2017-05-31T12:56:35Z
generation : 1
name : my-new-cron-object
namespace : default
resourceVersion : "285"
uid : 9423255b-4600-11e7-af6a-28d2447dc82b
spec :
cronSpec : '* * * * */5'
image : my-awesome-cron-image
注意其中的字段 someRandomField
已经被剪裁掉。
本例中通过 --validate=false
命令行选项 关闭了客户端的合法性检查以展示 API 服务器的行为,
因为 OpenAPI 合法性检查模式也会发布到
客户端,kubectl
也会检查未知的字段并在对象被发送到 API
服务器之前就拒绝它们。
控制剪裁 默认情况下,定制资源的所有版本中的所有未规定的字段都会被剪裁掉。
通过在结构化的 OpenAPI v3 检查模式定义
中为特定字段的子树添加 x-kubernetes-preserve-unknown-fields: true
属性,
可以选择不对其执行剪裁操作。
例如:
type : object
properties :
json :
x-kubernetes-preserve-unknown-fields : true
字段 json
可以保存任何 JSON 值,其中内容不会被剪裁掉。
你也可以部分地指定允许的 JSON 数据格式;例如:
type : object
properties :
json :
x-kubernetes-preserve-unknown-fields : true
type : object
description : this is arbitrary JSON
通过这样设置,JSON 中只能设置 object
类型的值。
对于所指定的每个属性(或 additionalProperties
),剪裁会再次被启用。
type : object
properties :
json :
x-kubernetes-preserve-unknown-fields : true
type : object
properties :
spec :
type : object
properties :
foo :
type : string
bar :
type : string
对于上述定义,如果提供的数值如下:
json :
spec :
foo : abc
bar : def
something : x
status :
something : x
则该值会被剪裁为:
json :
spec :
foo : abc
bar : def
status :
something : x
这意味着所指定的 spec
对象中的 something
字段被剪裁掉,而其外部的内容都被保留。
IntOrString 模式定义中标记了 x-kubernetes-int-or-string: true
的节点不受前述规则 1
约束,因此下面的定义是结构化的模式:
type : object
properties :
foo :
x-kubernetes-int-or-string : true
此外,所有这类节点也不再受规则 3 约束,也就是说,下面两种模式是被允许的
(注意,仅限于这两种模式,不支持添加新字段的任何其他变种):
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
...
和
x-kubernetes-int-or-string: true
allOf:
- anyOf:
- type: integer
- type: string
- ... # zero or more
...
在以上两种规约中,整数值和字符串值都会被认为是合法的。
在合法性检查模式定义的发布时 ,
x-kubernetes-int-or-string: true
会被展开为上述两种模式之一。
RawExtension RawExtensions(就像在
k8s.io/apimachinery
项目中 runtime.RawExtension
所定义的那样)
可以保存完整的 Kubernetes 对象,也就是,其中会包含 apiVersion
和 kind
字段。
通过 x-kubernetes-embedded-resource: true
来设定这些嵌套对象的规约
(无论是完全无限制还是部分指定都可以)是可能的。例如:
type : object
properties :
foo :
x-kubernetes-embedded-resource : true
x-kubernetes-preserve-unknown-fields : true
这里,字段 foo
包含一个完整的对象,例如:
foo:
apiVersion: v1
kind: Pod
spec:
...
由于字段上设置了 x-kubernetes-preserve-unknown-fields: true
,其中的内容不会
被剪裁。不过,在这个语境中,x-kubernetes-preserve-unknown-fields: true
的
使用是可选的。
设置了 x-kubernetes-embedded-resource: true
之后,apiVersion
、kind
和
metadata
都是隐式设定并隐式完成合法性验证。
提供 CRD 的多个版本 关于如何为你的 CustomResourceDefinition 提供多个版本的支持,
以及如何将你的对象从一个版本迁移到另一个版本,
详细信息可参阅 CustomResourceDefinition 的版本管理 。
高级主题 Finalizers Finalizer 能够让控制器实现异步的删除前(Pre-delete)回调。
与内置对象类似,定制对象也支持 Finalizer。
你可以像下面一样为定制对象添加 Finalizer:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
finalizers :
- stable.example.com/finalizer
自定义 Finalizer 的标识符包含一个域名、一个正向斜线和 finalizer 的名称。
任何控制器都可以在任何对象的 finalizer 列表中添加新的 finalizer。
对带有 Finalizer 的对象的第一个删除请求会为其 metadata.deletionTimestamp
设置一个值,但不会真的删除对象。一旦此值被设置,finalizers
列表中的表项只能被移除。
在列表中仍然包含 finalizer 时,无法强制删除对应的对象。
当 metadata.deletionTimestamp
字段被设置时,监视该对象的各个控制器会执行它们所能处理的
finalizer,并在完成处理之后将其从列表中移除。
每个控制器负责将其 finalizer 从列表中删除。
metadata.deletionGracePeriodSeconds
的取值控制对更新的轮询周期。
一旦 finalizers 列表为空时,就意味着所有 finalizer 都被执行过,
Kubernetes 会最终删除该资源,
合法性检查 定制资源是通过
OpenAPI v3 模式定义
来执行合法性检查的,当启用验证规则特性 时,通过 x-kubernetes-validations
验证,
你可以通过使用准入控制 Webhook
来添加额外的合法性检查逻辑。
此外,对模式定义存在以下限制:
以下字段不可设置:
definitions
dependencies
deprecated
discriminator
id
patternProperties
readOnly
writeOnly
xml
$ref
字段 uniqueItems
不可设置为 true
字段 additionalProperties
不可设置为 false
字段 additionalProperties
与 properties
互斥,不可同时使用
当验证规则特性 被启用并且 CustomResourceDefinition
模式是一个结构化的模式定义 时,
x-kubernetes-validations
扩展可以使用通用表达式语言 (CEL) 表达式来验证定制资源。
关于对某些 CustomResourceDefinition 特性所必需的限制,
可参见结构化的模式定义 小节。
模式定义是在 CustomResourceDefinition 中设置的。在下面的例子中,
CustomResourceDefinition 对定制对象执行以下合法性检查:
spec.cronSpec
必须是一个字符串,必须是正则表达式所描述的形式;spec.replicas
必须是一个整数,且其最小值为 1、最大值为 10。将此 CustomResourceDefinition 保存到 resourcedefinition.yaml
文件中:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
name : crontabs.stable.example.com
spec :
group : stable.example.com
versions :
- name : v1
served : true
storage : true
schema :
# openAPIV3Schema 是验证自定义对象的模式。
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
pattern : '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
image :
type : string
replicas :
type : integer
minimum : 1
maximum : 10
scope : Namespaced
names :
plural : crontabs
singular : crontab
kind : CronTab
shortNames :
- ct
并创建 CustomResourceDefinition:
kubectl apply -f resourcedefinition.yaml
对于一个创建 CronTab 类别对象的定制对象的请求而言,如果其字段中包含非法值,则
该请求会被拒绝。
在下面的例子中,定制对象中包含带非法值的字段:
spec.cronSpec
与正则表达式不匹配spec.replicas
数值大于 10。如果你将下面的 YAML 保存到 my-crontab.yaml
:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * *"
image : my-awesome-cron-image
replicas : 15
并尝试创建定制对象:
kubectl apply -f my-crontab.yaml
你会看到下面的错误信息:
The CronTab "my-new-cron-object" is invalid: []: Invalid value: map[string]interface {}{"apiVersion":"stable.example.com/v1", "kind":"CronTab", "metadata":map[string]interface {}{"name":"my-new-cron-object", "namespace":"default", "deletionTimestamp":interface {}(nil), "deletionGracePeriodSeconds":(*int64)(nil), "creationTimestamp":"2017-09-05T05:20:07Z", "uid":"e14d79e7-91f9-11e7-a598-f0761cb232d1", "clusterName":""}, "spec":map[string]interface {}{"cronSpec":"* * * *", "image":"my-awesome-cron-image", "replicas":15}}:
validation failure list:
spec.cronSpec in body should match '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
spec.replicas in body should be less than or equal to 10
如果所有字段都包含合法值,则对象创建的请求会被接受。
将下面的 YAML 保存到 my-crontab.yaml
文件:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * * */5"
image : my-awesome-cron-image
replicas : 5
并创建定制对象:
kubectl apply -f my-crontab.yaml
crontab "my-new-cron-object" created
验证规则 特性状态: Kubernetes v1.25 [beta]
验证规则从 1.25 开始处于 Beta 状态,
默认情况下,CustomResourceValidationExpressions
特性门控
是被启用的,以便根据验证规则 来验证定制资源。
对于 kube-apiserver
组件,
特性门控显式设置为 false 来禁用此特性。
这个特性只有在模式是结构化的模式 时才可用。
验证规则使用通用表达式语言(CEL) 来验证定制资源的值。
验证规则使用 x-kubernetes-validations
扩展包含在 CustomResourceDefinition
模式定义中。
规则的作用域是模式定义中 x-kubernetes-validations
扩展所在的位置。
CEL 表达式中的 self
变量被绑定到限定作用域的取值。
所有验证规则都是针对当前对象的:不支持跨对象或有状态的验证规则。
例如:
...
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-validations:
- rule: "self.minReplicas <= self.replicas"
message: "replicas should be greater than or equal to minReplicas."
- rule: "self.replicas <= self.maxReplicas"
message: "replicas should be smaller than or equal to maxReplicas."
properties:
...
minReplicas:
type: integer
replicas:
type: integer
maxReplicas:
type: integer
required:
- minReplicas
- replicas
- maxReplicas
将拒绝创建这个定制资源的请求:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
minReplicas : 0
replicas : 20
maxReplicas : 10
返回响应为:
The CronTab "my-new-cron-object" is invalid:
* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: replicas should be smaller than or equal to maxReplicas.
x-kubernetes-validations
可以有多条规则。
x-kubernetes-validations
下的 rule
代表将由 CEL 评估的表达式。
message
代表验证失败时显示的信息。如果消息没有设置,上述响应将是:
The CronTab "my-new-cron-object" is invalid:
* spec: Invalid value: map[string]interface {}{"maxReplicas":10, "minReplicas":0, "replicas":20}: failed rule: self.replicas <= self.maxReplicas
当 CRD 被创建/更新时,验证规则被编译。
如果验证规则的编译失败,CRD 的创建/更新请求将失败。
编译过程也包括类型检查。
编译失败:
no_matching_overload
:此函数没有参数类型的重载。
例如,像 self == true
这样的规则对一个整数类型的字段将得到错误:
Invalid value: apiextensions.ValidationRule{Rule:"self == true", Message:""}: compilation failed: ERROR: \<input>:1:6: found no matching overload for '_==_' applied to '(int, bool)'
no_such_field
:不包含所需的字段。
例如,针对一个不存在的字段,像 self.nonExistingField > 0
这样的规则将返回错误:
Invalid value: apiextensions.ValidationRule{Rule:"self.nonExistingField > 0", Message:""}: compilation failed: ERROR: \<input>:1:5: undefined field 'nonExistingField'
invalid argument
:对宏的无效参数。
例如,像 has(self)
这样的规则将返回错误:
Invalid value: apiextensions.ValidationRule{Rule:"has(self)", Message:""}: compilation failed: ERROR: <input>:1:4: invalid argument to has() macro
验证规则例子:
规则 目的 self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas
验证定义副本数的三个字段大小顺序是否正确 'Available' in self.stateCounts
验证 map 中是否存在键名为 Available
的条目 (size(self.list1) == 0) != (size(self.list2) == 0)
验证两个 list 之一是非空的,但不是二者都非空 !('MY_KEY' in self.map1) || self['MY_KEY'].matches('^[a-zA-Z]*$')
如果某个特定的 key 在 map 中,验证 map 中这个 key 的 value self.envars.filter(e, e.name = 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$')
验证一个 listMap 中主键 'name' 为 'MY_ENV' 'value' 的表项,检查其取值 'value' has(self.expired) && self.created + self.ttl < self.expired
验证 'Expired' 日期是否晚于 'Create' 日期加上 'ttl' 持续时间 self.health.startsWith('ok')
验证 'health' 字符串字段有前缀 'ok' self.widgets.exists(w, w.key == 'x' && w.foo < 10)
验证 key 为 'x' 的 listMap 项的 'foo' 属性是否小于 10 type(self) == string ? self == '100%' : self == 1000
在 int 型和 string 型两种情况下验证 int-or-string 字段 self.metadata.name.startsWith(self.prefix)
验证对象的名称是否具有另一个字段值的前缀 self.set1.all(e, !(e in self.set2))
验证两个 listSet 是否不相交 size(self.names) == size(self.details) && self.names.all(n, n in self.details)
验证 'details' map 是由 'names' listSet 的项目所决定的。 size(self.clusters.filter(c, c.name == self.primary)) == 1
验证 'primary' 属性仅在 'clusters' listMap 中出现一次且只有一次
参考:CEL 中支持的求值
如果规则的作用域是某资源的根,则它可以对 CRD 的 OpenAPIv3 模式表达式中声明的任何字段进行字段选择,
以及 apiVersion
、kind
、metadata.name
和 metadata.generateName
。
这包括在同一表达式中对 spec
和 status
的字段进行选择:
...
openAPIV3Schema:
type: object
x-kubernetes-validations:
- rule: "self.status.availableReplicas >= self.spec.minReplicas"
properties:
spec:
type: object
properties:
minReplicas:
type: integer
...
status:
type: object
properties:
availableReplicas:
type: integer
如果规则的作用域是具有属性的对象,那么可以通过 self.field
对该对象的可访问属性进行字段选择,
而字段存在与否可以通过 has(self.field)
来检查。
在 CEL 表达式中,Null 值的字段被视为不存在的字段。
...
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-validations:
- rule: "has(self.foo)"
properties:
...
foo:
type: integer
如果规则的作用域是一个带有 additionalProperties 的对象(即map),那么 map 的值
可以通过 self[mapKey]
访问,map 的包含性可以通过 mapKey in self
检查,
map 中的所有条目可以通过 CEL 宏和函数如 self.all(...)
访问。
...
openAPIV3Schema:
type: object
properties:
spec:
type: object
x-kubernetes-validations:
- rule: "self['xyz'].foo > 0"
additionalProperties:
...
type: object
properties:
foo:
type: integer
例子:
规则作用域字段类型 规则示例 根对象 self.status.actual <= self.spec.maxDesired
对象映射 self.components['Widget'].priority < 10
整数列表 self.values.all(value, value >= 0 && value < 100)
字符串 self.startsWith('kube')
apiVersion
、kind
、metadata.name
和 metadata.generateName
始终可以从对象的根目录和任何带有 x-kubernetes-embedded-resource
注解的对象访问。
其他元数据属性都不可访问。
通过 x-kubernetes-preserve-unknown-fields
保存在定制资源中的未知数据在 CEL 表达中无法访问。
这包括:
只有 [a-zA-Z_.-/][a-zA-Z0-9_.-/]*
形式的属性名是可访问的。
当在表达式中访问时,可访问的属性名称会根据以下规则进行转义:
转义序列 属性名称等效为 __underscores__
__
__dot__
.
__dash__
-
__slash__
/
__{keyword}__
CEL 保留关键字
注意:CEL 保留关键字需要与要转义的确切属性名匹配(例如,单词 sprint
中的 int
不会转义)。
转义的例子:
属性名 转义属性名规则 namespace self.__namespace__ > 0
x-prop self.x__dash__prop > 0
redact__d self.redact__underscores__d > 0
string self.startsWith('kube')
set
或 map
的 x-Kubernetes-list-type
的数组的等值比较会忽略元素顺序,即 [1,2] == [2,1]
。
使用 x-kubernetes-list-type
对数组进行串联时,使用 List 类型的语义:
以下是 OpenAPIV3 和 CEL 类型之间的声明类型映射:
OpenAPIv3 类型 CEL 类型 带有 Properties 的对象 对象 / "消息类型" 带有 AdditionalProperties 的对象 map 带有 x-kubernetes-embedded-type 的对象 对象 / "消息类型",'apiVersion'、'kind'、'metadata.name' 和 'metadata.generateName' 都隐式包含在模式中 带有 x-kubernetes-preserve-unknown-fields 的对象 对象 / "消息类型",未知字段无法从 CEL 表达式中访问 x-kubernetes-int-or-string 可能是整数或字符串的动态对象,可以用 type(value)
来检查类型 数组 list 带有 x-kubernetes-list-type=map 的数组 列表,基于集合等值和唯一键名保证的 map 组成 带有 x-kubernetes-list-type=set 的数组 列表,基于集合等值和唯一键名保证的 set 组成 布尔值 boolean 数字 (各种格式) double 整数 (各种格式) int (64) 'null' null_type 字符串 string 带有 format=byte (base64 编码)字符串 bytes 带有 format=date 字符串 timestamp (google.protobuf.Timestamp) 带有 format=datetime 字符串 timestamp (google.protobuf.Timestamp) 带有 format=duration 字符串 duration (google.protobuf.Duration)
参考:CEL 类型 、
OpenAPI 类型 、
Kubernetes 结构化模式 。
验证函数 可用的函数包括:
转换规则 包含引用标识符 oldSself
的表达式的规则被隐式视为 转换规则(Transition Rule) 。
转换规则允许模式作者阻止两个原本有效的状态之间的某些转换。例如:
type : string
enum : ["low" , "medium" , "high" ]
x-kubernetes-validations :
- rule : "!(self == 'high' && oldSelf == 'low') && !(self == 'low' && oldSelf == 'high')"
message : cannot transition directly between 'low' and 'high'
与其他规则不同,转换规则仅适用于满足以下条件的操作:
旧的值和新的值都存在。仍然可以通过在父节点上放置转换规则来检查值是否已被添加或移除。
转换规则从不应用于定制资源创建。当被放置在可选字段上时,转换规则将不适用于设置或取消设置该字段的更新操作。 被转换规则验证的模式节点的路径必须解析到一个在旧对象和新对象之间具有可比性的节点。
例如,列表项和它们的后代(spec.foo[10].bar
)不一定能在现有对象和后来对同一对象的更新之间产生关联。 如果一个模式节点包含一个永远不能应用的转换规则,在 CRD 写入时将会产生错误,例如:
"path : update rule rule cannot be set on schema because the schema or its parent
schema is not mergeable"。
转换规则只允许在模式的 可关联部分(Correlatable Portions) 中使用。
如果所有 array
父模式都是 x-kubernetes-list-type=map
类型的,那么该模式的一部分就是可关联的;
任何 set
或者 atomic
数组父模式都不支持确定性地将 self
与 oldSelf
关联起来。
这是一些转换规则的例子:
转换规则样例 用例 规则 不可变 self.foo == oldSelf.foo
赋值后禁止修改/删除 oldSelf != 'bar' || self == 'bar'
or !has(oldSelf.field) || has(self.field)
仅附加的 set self.all(element, element in oldSelf)
如果之前的值为 X,则新值只能为 A 或 B,不能为 Y 或 Z oldSelf != 'X' || self in ['A', 'B']
单调(非递减)计数器 self >= oldSelf
验证函数的资源使用 当你创建或更新一个使用验证规则的 CustomResourceDefinition 时,
API 服务器会检查运行这些验证规则可能产生的影响。
如果一个规则的执行成本过高,API 服务器会拒绝创建或更新操作,并返回一个错误信息。
运行时也使用类似的系统来观察解释器的行动。如果解释器执行了太多的指令,规则的执行将被停止,并且会产生一个错误。
每个 CustomResourceDefinition 也被允许有一定数量的资源来完成其所有验证规则的执行。
如果在创建时估计其规则的总和超过了这个限制,那么也会发生验证错误。
如果你只指定那些无论输入量有多大都要花费相同时间的规则,你不太可能遇到验证的资源预算问题。
例如,一个断言 self.foo == 1
的规则本身不存在因为资源预算组验证而导致被拒绝的风险。
但是,如果 foo
是一个字符串,而你定义了一个验证规则 self.foo.contains("someString")
,
这个规则需要更长的时间来执行,取决于 foo
有多长。
另一个例子是如果 foo
是一个数组,而你指定了验证规则 self.foo.all(x, x > 5)
。
如果没有给出 foo
的长度限制,成本系统总是假设最坏的情况,这将发生在任何可以被迭代的事物上(list、map 等)。
因此,通过 maxItems
,maxProperties
和 maxLength
进行限制被认为是最佳实践,
以在验证规则中处理任何内容,以防止在成本估算期间验证错误。例如,给定具有一个规则的模式:
openAPIV3Schema :
type : object
properties :
foo :
type : array
items :
type : string
x-kubernetes-validations :
- rule : "self.all(x, x.contains('a string'))"
API 服务器以验证预算为由拒绝该规则,并显示错误:
spec.validation.openAPIV3Schema.properties[spec].properties[foo].x-kubernetes-validations[0].rule: Forbidden:
CEL rule exceeded budget by more than 100x (try simplifying the rule, or adding maxItems, maxProperties, and
maxLength where arrays, maps, and strings are used)
这个拒绝会发生是因为 self.all
意味着对 foo
中的每一个字符串调用 contains()
,
而这又会检查给定的字符串是否包含 'a string'
。如果没有限制,这是一个非常昂贵的规则。
如果你不指定任何验证限制,这个规则的估计成本将超过每条规则的成本限制。
但如果你在适当的地方添加限制,该规则将被允许:
openAPIV3Schema :
type : object
properties :
foo :
type : array
maxItems : 25
items :
type : string
maxLength : 10
x-kubernetes-validations :
- rule : "self.all(x, x.contains('a string'))"
成本评估系统除了考虑规则本身的估计成本外,还考虑到规则将被执行的次数。
例如,下面这个规则的估计成本与前面的例子相同(尽管该规则现在被定义在单个数组项上):
openAPIV3Schema :
type : object
properties :
foo :
type : array
maxItems : 25
items :
type : string
x-kubernetes-validations :
- rule : "self.contains('a string'))"
maxLength : 10
如果在一个列表内部的一个列表有一个使用 self.all
的验证规则,那就会比具有相同规则的非嵌套列表的成本高得多。
一个在非嵌套列表中被允许的规则可能需要在两个嵌套列表中设置较低的限制才能被允许。
例如,即使没有设置限制,下面的规则也是允许的:
openAPIV3Schema :
type : object
properties :
foo :
type : array
items :
type : integer
x-kubernetes-validations :
- rule : "self.all(x, x == 5)"
但是同样的规则在下面的模式中(添加了一个嵌套数组)产生了一个验证错误:
openAPIV3Schema :
type : object
properties :
foo :
type : array
items :
type : array
items :
type : integer
x-kubernetes-validations :
- rule : "self.all(x, x == 5)"
这是因为 foo
的每一项本身就是一个数组,而每一个子数组依次调用 self.all
。
在使用验证规则的地方,尽可能避免嵌套的列表和字典。
设置默认值 说明: 要使用设置默认值功能,你的 CustomResourceDefinition 必须使用 API 版本 apiextensions.k8s.io/v1
。
设置默认值的功能允许在 OpenAPI v3 合法性检查模式定义 中设置默认值:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
name : crontabs.stable.example.com
spec :
group : stable.example.com
versions :
- name : v1
served : true
storage : true
schema :
# openAPIV3Schema 是用来检查定制对象的模式定义
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
pattern : '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
default : "5 0 * * *"
image :
type : string
replicas :
type : integer
minimum : 1
maximum : 10
default : 1
scope : Namespaced
names :
plural : crontabs
singular : crontab
kind : CronTab
shortNames :
- ct
使用此 CRD 定义时,cronSpec
和 replicas
都会被设置默认值:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
image : my-awesome-cron-image
会生成:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "5 0 * * *"
image : my-awesome-cron-image
replicas : 1
默认值设定的行为发生在定制对象上:
在向 API 服务器发送的请求中,基于请求版本的设定设置默认值; 在从 etcd 读取对象时,使用存储版本来设置默认值; 在 Mutating 准入控制插件执行非空的补丁操作时,基于准入 Webhook 对象
版本设置默认值。 从 etcd 中读取数据时所应用的默认值设置不会被写回到 etcd 中。
需要通过 API 执行更新请求才能将这种方式设置的默认值写回到 etcd。
默认值一定会被剪裁(除了 metadata
字段的默认值设置),且必须通过所提供的模式定义的检查。
针对 x-kubernetes-embedded-resource: true
节点(或者包含 metadata
字段的结构的默认值)
的 metadata
字段的默认值设置不会在 CustomResourceDefinition 创建时被剪裁,
而是在处理请求的字段剪裁阶段被删除。
设置默认值和字段是否可为空(Nullable) 对于未设置其 nullable 标志的字段或者将该标志设置为 false
的字段,其空值(Null)
会在设置默认值之前被剪裁掉。如果对应字段存在默认值,则默认值会被赋予该字段。
当 nullable
被设置为 true
时,字段的空值会被保留,且不会在设置默认值时被覆盖。
例如,给定下面的 OpenAPI 模式定义:
type : object
properties :
spec :
type : object
properties :
foo :
type : string
nullable : false
default : "default"
bar :
type : string
nullable : true
baz :
type : string
像下面这样创建一个为 foo
、bar
和 baz
设置空值的对象时:
spec :
foo : null
bar : null
baz : null
其结果会是这样:
spec :
foo : "default"
bar : null
其中的 foo
字段被剪裁掉并重新设置默认值,因为该字段是不可为空的。
bar
字段的 nullable: true
使得其能够保有其空值。
baz
字段则被完全剪裁掉,因为该字段是不可为空的,并且没有默认值设置。
以 OpenAPI v2 形式发布合法性检查模式 CustomResourceDefinition 的结构化的 、
启用了剪裁的 OpenAPI v3 合法性检查模式 会在
Kubernetes API 服务器上作为
OpenAPI v2 规约 的一部分发布出来。
kubectl 命令行工具会基于所发布的模式定义来执行客户端的合法性检查
(kubectl create
和 kubectl apply
),为定制资源的模式定义提供解释(kubectl explain
)。
所发布的模式还可被用于其他目的,例如生成客户端或者生成文档。
OpenAPI v3 合法性检查模式定义会被转换为 OpenAPI v2 模式定义,并出现在
OpenAPI v2 规范 的
definitions
和 paths
字段中。
在转换过程中会发生以下修改,目的是保持与 1.13 版本以前的 kubectl 工具兼容。
这些修改可以避免 kubectl 过于严格,以至于拒绝它无法理解的 OpenAPI 模式定义。
转换过程不会更改 CRD 中定义的合法性检查模式定义,因此不会影响到
API 服务器中的合法性检查 。
以下字段会被移除,因为它们在 OpenAPI v2 中不支持(在将来版本中将使用 OpenAPI v3,
因而不会有这些限制)
字段 allOf
、anyOf
、oneOf
和 not
会被删除 如果设置了 nullable: true
,我们会丢弃 type
、nullable
、items
和 properties
OpenAPI v2 无法表达 Nullable。为了避免 kubectl 拒绝正常的对象,这一转换是必要的。
额外的打印列 kubectl
工具依赖服务器端的输出格式化。你的集群的 API 服务器决定 kubectl get
命令要显示的列有哪些。
你可以为 CustomResourceDefinition 定制这些要打印的列。
下面的例子添加了 Spec
、Replicas
和 Age
列:
将此 CustomResourceDefinition 保存到 resourcedefinition.yaml
文件:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
name : crontabs.stable.example.com
spec :
group : stable.example.com
scope : Namespaced
names :
plural : crontabs
singular : crontab
kind : CronTab
shortNames :
- ct
versions :
- name : v1
served : true
storage : true
schema :
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
image :
type : string
replicas :
type : integer
additionalPrinterColumns :
- name : Spec
type : string
description : The cron spec defining the interval a CronJob is run
jsonPath : .spec.cronSpec
- name : Replicas
type : integer
description : The number of jobs launched by the CronJob
jsonPath : .spec.replicas
- name : Age
type : date
jsonPath : .metadata.creationTimestamp
创建 CustomResourceDefinition:
kubectl apply -f resourcedefinition.yaml
使用前文中的 my-crontab.yaml
创建一个实例。
启用服务器端打印输出:
kubectl get crontab my-new-cron-object
注意输出中的 NAME
、SPEC
、REPLICAS
和 AGE
列:
NAME SPEC REPLICAS AGE
my-new-cron-object * * * * * 1 7s
说明: NAME
列是隐含的,不需要在 CustomResourceDefinition 中定义。
优先级 每个列都包含一个 priority
(优先级)字段。当前,优先级用来区分标准视图(Standard
View)和宽视图(Wide View)(使用 -o wide
标志)中显示的列:
优先级为 0
的列会在标准视图中显示。 优先级大于 0
的列只会在宽视图中显示。 类型 列的 type
字段可以是以下值之一
(比较 OpenAPI v3 数据类型 ):
integer
– 非浮点数字number
– 浮点数字string
– 字符串boolean
– true
或 false
date
– 显示为以自此时间戳以来经过的时长如果定制资源中的值与列中指定的类型不匹配,该值会被忽略。
你可以通过定制资源的合法性检查来确保取值类型是正确的。
列的 format
字段可以是以下值之一:
int32
int64
float
double
byte
date
date-time
password
列的 format
字段控制 kubectl
打印对应取值时采用的风格。
子资源 定制资源支持 /status
和 /scale
子资源。
通过在 CustomResourceDefinition 中定义 status
和 scale
,
可以有选择地启用这些子资源。
Status 子资源 当启用了 status 子资源时,对应定制资源的 /status
子资源会被暴露出来。
status 和 spec 内容分别用定制资源内的 .status
和 .spec
JSON 路径来表达;
对 /status
子资源的 PUT
请求要求使用定制资源对象作为其输入,但会忽略
status 之外的所有内容。
对 /status
子资源的 PUT
请求仅对定制资源的 status 内容进行合法性检查。
对定制资源的 PUT
、POST
、PATCH
请求会忽略 status 内容的改变。
对所有变更请求,除非改变是针对 .metadata
或 .status
,.metadata.generation
的取值都会增加。
在 CRD OpenAPI 合法性检查模式定义的根节点,只允许存在以下结构:
description
example
exclusiveMaximum
exclusiveMinimum
externalDocs
format
items
maximum
maxItems
maxLength
minimum
minItems
minLength
multipleOf
pattern
properties
required
title
type
uniqueItems
Scale 子资源 当启用了 scale 子资源时,定制资源的 /scale
子资源就被暴露出来。
针对 /scale
所发送的对象是 autoscaling/v1.Scale
。
为了启用 scale 子资源,CustomResourceDefinition 定义了以下字段:
在下面的例子中,status
和 scale
子资源都被启用。
将此 CustomResourceDefinition 保存到 resourcedefinition.yaml
文件:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
name : crontabs.stable.example.com
spec :
group : stable.example.com
versions :
- name : v1
served : true
storage : true
schema :
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
image :
type : string
replicas :
type : integer
status :
type : object
properties :
replicas :
type : integer
labelSelector :
type : string
# subresources 描述定制资源的子资源
subresources :
# status 启用 status 子资源
status : {}
# scale 启用 scale 子资源
scale :
# specReplicasPath 定义定制资源中对应 scale.spec.replicas 的 JSON 路径
specReplicasPath : .spec.replicas
# statusReplicasPath 定义定制资源中对应 scale.status.replicas 的 JSON 路径
statusReplicasPath : .status.replicas
# labelSelectorPath 定义定制资源中对应 scale.status.selector 的 JSON 路径
labelSelectorPath : .status.labelSelector
scope : Namespaced
names :
plural : crontabs
singular : crontab
kind : CronTab
shortNames :
- ct
之后创建此 CustomResourceDefinition:
kubectl apply -f resourcedefinition.yaml
CustomResourceDefinition 对象创建完毕之后,你可以创建定制对象,。
如果你将下面的 YAML 保存到 my-crontab.yaml
文件:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * * */5"
image : my-awesome-cron-image
replicas : 3
并创建定制对象:
kubectl apply -f my-crontab.yaml
那么会创建新的、命名空间作用域的 RESTful API 端点:
/apis/stable.example.com/v1/namespaces/*/crontabs/status
和
/apis/stable.example.com/v1/namespaces/*/crontabs/scale
定制资源可以使用 kubectl scale
命令来扩缩其规模。
例如下面的命令将前面创建的定制资源的 .spec.replicas
设置为 5:
kubectl scale --replicas= 5 crontabs/my-new-cron-object
crontabs "my-new-cron-object" scaled
kubectl get crontabs my-new-cron-object -o jsonpath = '{.spec.replicas}'
5
你可以使用 PodDisruptionBudget
来保护启用了 scale 子资源的定制资源。
分类 分类(Categories)是定制资源所归属的分组资源列表(例如,all
)。
你可以使用 kubectl get <分类名称>
来列举属于某分类的所有资源。
下面的示例在 CustomResourceDefinition 中将 all
添加到分类列表中,
并展示了如何使用 kubectl get all
来输出定制资源:
将下面的 CustomResourceDefinition 保存到 resourcedefinition.yaml
文件中:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
name : crontabs.stable.example.com
spec :
group : stable.example.com
versions :
- name : v1
served : true
storage : true
schema :
openAPIV3Schema :
type : object
properties :
spec :
type : object
properties :
cronSpec :
type : string
image :
type : string
replicas :
type : integer
scope : Namespaced
names :
plural : crontabs
singular : crontab
kind : CronTab
shortNames :
- ct
# categories 是定制资源所归属的分类资源列表
categories :
- all
之后创建此 CRD:
kubectl apply -f resourcedefinition.yaml
创建了 CustomResourceDefinition 对象之后,你可以创建定制对象。
将下面的 YAML 保存到 my-crontab.yaml
中:
apiVersion : "stable.example.com/v1"
kind : CronTab
metadata :
name : my-new-cron-object
spec :
cronSpec : "* * * * */5"
image : my-awesome-cron-image
并创建定制对象:
kubectl apply -f my-crontab.yaml
你可以在使用 kubectl get
时指定分类:
输出中将包含类别为 CronTab
的定制资源:
NAME AGE
crontabs/my-new-cron-object 3s
接下来 4.11.2.2 - CustomResourceDefinition 的版本 本页介绍如何添加版本信息到
CustomResourceDefinitions 。
目的是标明 CustomResourceDefinitions 的稳定级别或者服务于 API 升级。
API 升级时需要在不同 API 表示形式之间进行转换。
本页还描述如何将对象从一个版本升级到另一个版本。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你应该对定制资源 有一些初步了解。
你的 Kubernetes 服务器版本必须不低于版本 v1.16.
要获知版本信息,请输入
kubectl version
.
概览 CustomResourceDefinition API 提供了引入和升级 CustomResourceDefinition 新版本所用的工作流程。
创建 CustomResourceDefinition 时,会在 CustomResourceDefinition spec.versions
列表设置适当的稳定级别和版本号。例如,v1beta1
表示第一个版本尚未稳定。
所有定制资源对象将首先用这个版本保存。
创建 CustomResourceDefinition 后,客户端可以开始使用 v1beta1
API。
稍后可能需要添加新版本,例如 v1
。
添加新版本:
选择一种转化策略。由于定制资源对象需要能够两种版本都可用,
这意味着它们有时会以与存储版本不同的版本来提供服务。为了能够做到这一点,
有时必须在它们存储的版本和提供的版本之间进行转换。如果转换涉及模式变更,
并且需要自定义逻辑,则应该使用 Webhook 来完成。如果没有模式变更,
则可使用默认的 None
转换策略,为不同版本提供服务时只有 apiVersion
字段会被改变。 如果使用转换 Webhook,请创建并部署转换 Webhook。更多详细信息请参见
Webhook 转换 。 更新 CustomResourceDefinition,将新版本设置为 served:true
,加入到
spec.versions
列表。另外,还要设置 spec.conversion
字段为所选的转换策略。
如果使用转换 Webhook,请配置 spec.conversion.webhookClientConfig
来调用 Webhook。 添加新版本后,客户端可以逐步迁移到新版本。
让某些客户使用旧版本的同时支持其他人使用新版本是相当安全的。
将存储的对象迁移到新版本:
请参阅将现有对象升级到新的存储版本 节。
对于客户来说,在将对象升级到新的存储版本之前、期间和之后使用旧版本和新版本都是安全的。
删除旧版本:
确保所有客户端都已完全迁移到新版本。
可以查看 kube-apiserver 的日志以识别仍通过旧版本进行访问的所有客户端。 在 spec.versions
列表中将旧版本的 served
设置为 false
。
如果仍有客户端意外地使用旧版本,他们可能开始会报告采用旧版本尝试访问定制资源的错误消息。
如果发生这种情况,请将旧版本的 served:true
恢复,然后迁移余下的客户端使用新版本,然后重复此步骤。 确保已完成将现有对象升级到新存储版本 的步骤。在 CustomResourceDefinition 的 spec.versions
列表中,确认新版本的
storage
已被设置为 true
。 确认旧版本不在 CustomResourceDefinition status.storedVersions
中。 从 CustomResourceDefinition spec.versions
列表中删除旧版本。 在转换 Webhooks 中放弃对旧版本的转换支持。 指定多个版本 CustomResourceDefinition API 的 versions
字段可用于支持你所开发的定制资源的多个版本。
版本可以具有不同的模式,并且转换 Webhook 可以在多个版本之间转换定制资源。
在适当的情况下,Webhook 转换应遵循
Kubernetes API 约定 。
具体来说,请查阅
API 变更文档
以获取一些有用的问题和建议。
说明: 在 apiextensions.k8s.io/v1beta1
版本中曾经有一个 version
字段,
名字不叫做 versions
。该 version
字段已经被废弃,成为可选项。
不过如果该字段不是空,则必须与 versions
字段中的第一个条目匹配。
下面的示例显示了两个版本的 CustomResourceDefinition。
第一个例子中假设所有的版本使用相同的模式而它们之间没有转换。
YAML 中的注释提供了更多背景信息。
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
# name 必须匹配后面 spec 中的字段,且使用格式 <plural>.<group>
name : crontabs.example.com
spec :
# 组名,用于 REST API: /apis/<group>/<version>
group : example.com
# 此 CustomResourceDefinition 所支持的版本列表
versions :
- name : v1beta1
# 每个 version 可以通过 served 标志启用或禁止
served : true
# 有且只能有一个 version 必须被标记为存储版本
storage : true
# schema 是必需字段
schema :
openAPIV3Schema :
type : object
properties :
host :
type : string
port :
type : string
- name : v1
served : true
storage : false
schema :
openAPIV3Schema :
type : object
properties :
host :
type : string
port :
type : string
# conversion 节是 Kubernetes 1.13+ 版本引入的,其默认值为无转换,即 strategy 子字段设置为 None。
conversion :
# None 转换假定所有版本采用相同的模式定义,仅仅将定制资源的 apiVersion 设置为合适的值.
strategy : None
# 可以是 Namespaced 或 Cluster
scope : Namespaced
names :
# 名称的复数形式,用于 URL: /apis/<group>/<version>/<plural>
plural : crontabs
# 名称的单数形式,用于在命令行接口和显示时作为其别名
singular : crontab
# kind 通常是驼峰编码(CamelCased)的单数形式,用于资源清单中
kind : CronTab
# shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
shortNames :
- ct
# 在 v1.16 中被弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
metadata :
# name 必须匹配后面 spec 中的字段,且使用格式 <plural>.<group>
name : crontabs.example.com
spec :
# 组名,用于 REST API: /apis/<group>/<version>
group : example.com
# 此 CustomResourceDefinition 所支持的版本列表
versions :
- name : v1beta1
# 每个 version 可以通过 served 标志启用或禁止
served : true
# 有且只能有一个 version 必须被标记为存储版本
storage : true
- name : v1
served : true
storage : false
validation :
openAPIV3Schema :
type : object
properties :
host :
type : string
port :
type : string
# conversion 节是 Kubernetes 1.13+ 版本引入的,其默认值为无转换,即 strategy 子字段设置为 None。
conversion :
# None 转换假定所有版本采用相同的模式定义,仅仅将定制资源的 apiVersion 设置为合适的值.
strategy : None
# 可以是 Namespaced 或 Cluster
scope : Namespaced
names :
# 名称的复数形式,用于 URL: /apis/<group>/<version>/<plural>
plural : crontabs
# 名称的单数形式,用于在命令行接口和显示时作为其别名
singular : crontab
# kind 通常是大驼峰编码(PascalCased)的单数形式,用于资源清单中
kind : CronTab
# shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
shortNames :
- ct
你可以将 CustomResourceDefinition 存储在 YAML 文件中,然后使用
kubectl apply
来创建它。
kubectl apply -f my-versioned-crontab.yaml
在创建之后,API 服务器开始在 HTTP REST 端点上为每个已启用的版本提供服务。
在上面的示例中,API 版本可以在 /apis/example.com/v1beta1
和
/apis/example.com/v1
处获得。
版本优先级 不考虑 CustomResourceDefinition 中版本被定义的顺序,kubectl
使用具有最高优先级的版本作为访问对象的默认版本。
优先级是通过解析 name 字段来确定版本号、稳定性(GA、Beta 或 Alpha)
以及该稳定性级别内的序列。
用于对版本进行排序的算法在设计上与 Kubernetes 项目对 Kubernetes 版本进行排序的方式相同。
版本以 v
开头跟一个数字,一个可选的 beta
或者 alpha
和一个可选的附加数字作为版本信息。
从广义上讲,版本字符串可能看起来像 v2
或者 v2beta1
。
使用以下算法对版本进行排序:
遵循 Kubernetes 版本模式的条目在不符合条件的条目之前进行排序。 对于遵循 Kubernetes 版本模式的条目,版本字符串的数字部分从最大到最小排序。 如果第一个数字后面有字符串 beta
或 alpha
,它们首先按去掉 beta
或
alpha
之后的版本号排序(相当于 GA 版本),之后按 beta
先、alpha
后的顺序排序, 如果 beta
或 alpha
之后还有另一个数字,那么也会针对这些数字从大到小排序。 不符合上述格式的字符串按字母顺序排序,数字部分不经过特殊处理。
请注意,在下面的示例中,foo1
排在 foo10
之前。
这与遵循 Kubernetes 版本模式的条目的数字部分排序不同。 如果查看以下版本排序列表,这些规则就容易懂了:
- v10
- v2
- v1
- v11beta2
- v10beta3
- v3beta1
- v12alpha1
- v11alpha2
- foo1
- foo10
对于指定多个版本 中的示例,版本排序顺序为
v1
,后跟着 v1beta1
。
这导致了 kubectl 命令使用 v1
作为默认版本,除非所提供的对象指定了版本。
版本废弃 特性状态: Kubernetes v1.19 [stable]
从 v1.19 开始,CustomResourceDefinition 可以指示其定义的资源的特定版本已废弃。
当该资源的已废弃版本发出 API 请求时,会在 API 响应中以报头的形式返回警告消息。
如果需要,可以自定义每个不推荐使用的资源版本的警告消息。
定制的警告消息应该标明废弃的 API 组、版本和类别(kind),
并且应该标明应该使用(如果有的话)哪个 API 组、版本和类别作为替代。
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
name : crontabs.example.com
spec :
group : example.com
names :
plural : crontabs
singular : crontab
kind : CronTab
scope : Namespaced
versions :
- name : v1alpha1
served : true
storage : false
# 此属性标明此定制资源的 v1alpha1 版本已被弃用。
# 发给此版本的 API 请求会在服务器响应中收到警告消息头。
deprecated : true
# 此属性设置用来覆盖返回给发送 v1alpha1 API 请求的客户端的默认警告信息。
deprecationWarning : "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"
schema : ...
- name : v1beta1
served : true
# 此属性标明该定制资源的 v1beta1 版本已被弃用。
# 发给此版本的 API 请求会在服务器响应中收到警告消息头。
# 针对此版本的请求所返回的是默认的警告消息。
deprecated : true
schema : ...
- name : v1
served : true
storage : true
schema : ...
# 在 v1.16 中弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
metadata :
name : crontabs.example.com
spec :
group : example.com
names :
plural : crontabs
singular : crontab
kind : CronTab
scope : Namespaced
validation : ...
versions :
- name : v1alpha1
served : true
storage : false
# 此属性标明此定制资源的 v1alpha1 版本已被弃用。
# 发给此版本的 API 请求会在服务器响应中收到警告消息头。
deprecated : true
# 此属性设置用来覆盖返回给发送 v1alpha1 API 请求的客户端的默认警告信息。
deprecationWarning : "example.com/v1alpha1 CronTab is deprecated; see http://example.com/v1alpha1-v1 for instructions to migrate to example.com/v1 CronTab"
- name : v1beta1
served : true
# 此属性标明该定制资源的 v1beta1 版本已被弃用。
# 发给此版本的 API 请求会在服务器响应中收到警告消息头。
# 针对此版本的请求所返回的是默认的警告消息。
deprecated : true
- name : v1
served : true
storage : true
版本删除 在为所有提供旧版本自定义资源的集群将现有数据迁移到新 API 版本,并且从 CustomResourceDefinition 的
status.storedVersions
中删除旧版本之前,无法从 CustomResourceDefinition 清单文件中删除旧 API 版本。
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
name : crontabs.example.com
spec :
group : example.com
names :
plural : crontabs
singular : crontab
kind : CronTab
scope : Namespaced
versions :
- name : v1beta1
# 此属性标明该自定义资源的 v1beta1 版本已不再提供。
# 发给此版本的 API 请求会在服务器响应中收到未找到的错误。
served : false
schema : ...
- name : v1
served : true
# 新提供的版本应该设置为存储版本。
storage : true
schema : ...
Webhook 转换 特性状态: Kubernetes v1.16 [stable]
说明: Webhook 转换在 Kubernetes 1.13 版本作为 Alpha 功能引入,在 Kubernetes 1.15 版本中成为 Beta 功能。
要使用此功能,应启用 CustomResourceWebhookConversion
特性。
在大多数集群上,这类 Beta 特性应该是自动启用的。
请参阅特性门控 文档以获得更多信息。
上面的例子在版本之间有一个 None 转换,它只在转换时设置 apiVersion
字段而不改变对象的其余部分。
API 服务器还支持在需要转换时调用外部服务的 webhook 转换。例如:
定制资源的请求版本与其存储版本不同。 使用某版本创建了 Watch 请求,但所更改对象以另一版本存储。 定制资源的 PUT 请求所针对版本与存储版本不同。 为了涵盖所有这些情况并优化 API 服务器所作的转换,转换请求可以包含多个对象,
以便减少外部调用。Webhook 应该独立执行各个转换。
编写一个转换 Webhook 服务器 请参考定制资源转换 Webhook 服务器 的实现;
该实现在 Kubernetes e2e 测试中得到验证。
Webhook 处理由 API 服务器发送的 ConversionReview
请求,并在
ConversionResponse
中封装发回转换结果。
请注意,请求包含需要独立转换的定制资源列表,这些对象在被转换之后不能改变其在列表中的顺序。
该示例服务器的组织方式使其可以复用于其他转换。大多数常见代码都位于
framework 文件 中,
只留下一个函数 用于实现不同的转换。
说明: 转换 Webhook 服务器示例中将 ClientAuth
字段设置为空 ,
默认为 NoClientCert
。
这意味着 webhook 服务器没有验证客户端(也就是 API 服务器)的身份。
如果你需要双向 TLS 或者其他方式来验证客户端,
请参阅如何验证 API 服务 。
被允许的变更 转换 Webhook 不可以更改被转换对象的 metadata
中除 labels
和 annotations
之外的任何属性。
尝试更改 name
、UID
和 namespace
时都会导致引起转换的请求失败。
所有其他变更都被忽略。
部署转换 Webhook 服务 用于部署转换 Webhook
的文档与准入 Webhook 服务示例 相同。
这里的假设是转换 Webhook 服务器被部署为 default
名字空间中名为
example-conversion-webhook-server
的服务,并在路径 /crdconvert
上处理请求。
说明: 当 Webhook 服务器作为一个服务被部署到 Kubernetes 集群中时,它必须通过端口 443
公开其服务(服务器本身可以使用任意端口,但是服务对象应该将它映射到端口 443)。
如果为服务器使用不同的端口,则 API 服务器和 Webhook 服务器之间的通信可能会失败。
通过修改 spec
中的 conversion
部分,可以扩展 None
转换示例来使用转换 Webhook。
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
metadata :
# name 必须匹配后面 spec 中的字段,且使用格式 <plural>.<group>
name : crontabs.example.com
spec :
# 组名,用于 REST API: /apis/<group>/<version>
group : example.com
# 此 CustomResourceDefinition 所支持的版本列表
versions :
- name : v1beta1
# 每个 version 可以通过 served 标志启用或禁止
served : true
# 有且只能有一个 version 必须被标记为存储版本
storage : true
# 当不存在顶级模式定义时,每个版本(version)可以定义其自身的模式
schema :
openAPIV3Schema :
type : object
properties :
hostPort :
type : string
- name : v1
served : true
storage : false
schema :
openAPIV3Schema :
type : object
properties :
host :
type : string
port :
type : string
conversion :
# Webhook strategy 告诉 API 服务器调用外部 Webhook 来完成定制资源之间的转换
strategy : Webhook
# 当 strategy 为 "Webhook" 时,webhook 属性是必需的,该属性配置将被 API 服务器调用的 Webhook 端点
webhook :
# conversionReviewVersions 标明 Webhook 所能理解或偏好使用的
# ConversionReview 对象版本。
# API 服务器所能理解的列表中的第一个版本会被发送到 Webhook
# Webhook 必须按所接收到的版本响应一个 ConversionReview 对象
conversionReviewVersions : ["v1" ,"v1beta1" ]
clientConfig :
service :
namespace : default
name : example-conversion-webhook-server
path : /crdconvert
caBundle : "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
# 可以是 Namespaced 或 Cluster
scope : Namespaced
names :
# 名称的复数形式,用于 URL: /apis/<group>/<version>/<plural>
plural : crontabs
# 名称的单数形式,用于在命令行接口和显示时作为其别名
singular : crontab
# kind 通常是驼峰编码(CamelCased)的单数形式,用于资源清单中
kind : CronTab
# shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
shortNames :
- ct
# 在 v1.16 中被弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
metadata :
# name 必须匹配后面 spec 中的字段,且使用格式 <plural>.<group>
name : crontabs.example.com
spec :
# 组名,用于 REST API: /apis/<group>/<version>
group : example.com
# 裁剪掉下面的 OpenAPI 模式中未曾定义的对象字段
preserveUnknownFields : false
# 此 CustomResourceDefinition 所支持的版本列表
versions :
- name : v1beta1
# 每个 version 可以通过 served 标志启用或禁止
served : true
# 有且只能有一个 version 必须被标记为存储版本
storage : true
# 当不存在顶级模式定义时,每个版本(version)可以定义其自身的模式
schema :
openAPIV3Schema :
type : object
properties :
hostPort :
type : string
- name : v1
served : true
storage : false
schema :
openAPIV3Schema :
type : object
properties :
host :
type : string
port :
type : string
conversion :
# Webhook strategy 告诉 API 服务器调用外部 Webhook 来完成定制资源
strategy : Webhook
# 当 strategy 为 "Webhook" 时,webhookClientConfig 属性是必需的
# 该属性配置将被 API 服务器调用的 Webhook 端点
webhookClientConfig :
service :
namespace : default
name : example-conversion-webhook-server
path : /crdconvert
caBundle : "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
# 可以是 Namespaced 或 Cluster
scope : Namespaced
names :
# 名称的复数形式,用于 URL: /apis/<group>/<version>/<plural>
plural : crontabs
# 名称的单数形式,用于在命令行接口和显示时作为其别名
singular : crontab
# kind 通常是驼峰编码(CamelCased)的单数形式,用于资源清单中
kind : CronTab
# shortNames 允许你在命令行接口中使用更短的字符串来匹配你的资源
shortNames :
- ct
你可以将 CustomResourceDefinition 保存在 YAML 文件中,然后使用
kubectl apply
来应用它。
kubectl apply -f my-versioned-crontab-with-conversion.yaml
在应用新更改之前,请确保转换服务器已启动并正在运行。
API 服务器一旦确定请求应发送到转换 Webhook,它需要知道如何调用 Webhook。
这是在 webhookClientConfig
中指定的 Webhook 配置。
转换 Webhook 可以通过 URL 或服务引用来调用,并且可以选择包含自定义 CA 包,
以用于验证 TLS 连接。
URL url
以标准 URL 形式给出 Webhook 的位置(scheme://host:port/path
)。
host
不应引用集群中运行的服务,而应通过指定 service
字段来提供服务引用。
在某些 API 服务器中,host
可以通过外部 DNS 进行解析(即
kube-apiserver
无法解析集群内 DNS,那样会违反分层规则)。
host
也可以是 IP 地址。
请注意,除非你非常小心地在所有运行着可能调用 Webhook 的 API 服务器的主机上运行此 Webhook,
否则将 localhost
或 127.0.0.1
用作 host
是风险很大的。
这样的安装可能是不可移植的,或者不容易在一个新的集群中运行。
HTTP 协议必须为 https
;URL 必须以 https://
开头。
尝试使用用户或基本身份验证(例如,使用 user:password@
)是不允许的。
URL 片段(#...
)和查询参数(?...
)也是不允许的。
下面是为调用 URL 来执行转换 Webhook 的示例,其中期望使用系统信任根来验证
TLS 证书,因此未指定 caBundle:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhook :
clientConfig :
url : "https://my-webhook.example.com:9443/my-webhook-path"
...
# 在 v1.16 中已弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhookClientConfig :
url : "https://my-webhook.example.com:9443/my-webhook-path"
...
服务引用 webhookClientConfig
内部的 service
段是对转换 Webhook 服务的引用。
如果 Webhook 在集群中运行,则应使用 service
而不是 url
。
服务的名字空间和名称是必需的。端口是可选的,默认为 443。
路径是可选的,默认为/
。
下面配置中,服务配置为在端口 1234
、子路径 /my-path
上被调用。
例子中针对 ServerName my-service-name.my-service-namespace.svc
,
使用自定义 CA 包验证 TLS 连接。
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhook :
clientConfig :
service :
namespace : my-service-namespace
name : my-service-name
path : /my-path
port : 1234
caBundle : "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...
# v1.16 中被弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhookClientConfig :
service :
namespace : my-service-namespace
name : my-service-name
path : /my-path
port : 1234
caBundle : "Ci0tLS0tQk...<base64-encoded PEM bundle>...tLS0K"
...
Webhook 请求和响应 请求 向 Webhook 发起请求的动词是 POST,请求的 Content-Type
为 application/json
。
请求的主题为 JSON 序列化形式的
apiextensions.k8s.io API 组的 ConversionReview API 对象。
Webhook 可以在其 CustomResourceDefinition 中使用 conversionReviewVersions
字段设置它们接受的 ConversionReview
对象的版本:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhook :
conversionReviewVersions : ["v1" , "v1beta1" ]
...
创建 apiextensions.k8s.io/v1
版本的自定义资源定义时,
conversionReviewVersions
是必填字段。
Webhook 要求支持至少一个 ConversionReview
当前和以前的 API 服务器可以理解的版本。
# v1.16 已弃用以推荐使用 apiextensions.k8s.io/v1
apiVersion : apiextensions.k8s.io/v1beta1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
conversionReviewVersions : ["v1" , "v1beta1" ]
...
创建 apiextensions.k8s.io/v1beta1 定制资源定义时若未指定
conversionReviewVersions
,则默认值为 v1beta1。
API 服务器将 conversionReviewVersions
列表中他们所支持的第一个
ConversionReview
资源版本发送给 Webhook。
如果列表中的版本都不被 API 服务器支持,则无法创建自定义资源定义。
如果某 API 服务器遇到之前创建的转换 Webhook 配置,并且该配置不支持
API 服务器知道如何发送的任何 ConversionReview
版本,调用 Webhook
的尝试会失败。
下面的示例显示了包含在 ConversionReview
对象中的数据,
该请求意在将 CronTab
对象转换为 example.com/v1
:
{
"apiVersion": "apiextensions.k8s.io/v1" ,
"kind": "ConversionReview" ,
"request": {
# 用来唯一标识此转换调用的随机 UID
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002" ,
# 对象要转换到的目标 API 组和版本
"desiredAPIVersion": "example.com/v1" ,
# 要转换的对象列表,其中可能包含一个或多个对象,版本可能相同也可能不同
"objects": [
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1beta1" ,
"metadata": {
"creationTimestamp": "2019-09-04T14:03:02Z" ,
"name": "local-crontab" ,
"namespace": "default" ,
"resourceVersion": "143" ,
"uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
},
"hostPort": "localhost:1234"
},
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1beta1" ,
"metadata": {
"creationTimestamp": "2019-09-03T13:02:01Z" ,
"name": "remote-crontab" ,
"resourceVersion": "12893" ,
"uid": "359a83ec-b575-460d-b553-d859cedde8a0"
},
"hostPort": "example.com:2345"
}
]
}
}
{
# v1.16 中已废弃以推荐使用 apiextensions.k8s.io/v1
"apiVersion": "apiextensions.k8s.io/v1beta1" ,
"kind": "ConversionReview" ,
"request": {
# 用来唯一标识此转换调用的随机 UID
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002" ,
# 对象要转换到的目标 API 组和版本
"desiredAPIVersion": "example.com/v1" ,
# 要转换的对象列表,其中可能包含一个或多个对象,版本可能相同也可能不同
"objects": [
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1beta1" ,
"metadata": {
"creationTimestamp": "2019-09-04T14:03:02Z" ,
"name": "local-crontab" ,
"namespace": "default" ,
"resourceVersion": "143" ,
"uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
},
"hostPort": "localhost:1234"
},
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1beta1" ,
"metadata": {
"creationTimestamp": "2019-09-03T13:02:01Z" ,
"name": "remote-crontab" ,
"resourceVersion": "12893" ,
"uid": "359a83ec-b575-460d-b553-d859cedde8a0"
},
"hostPort": "example.com:2345"
}
]
}
}
响应 Webhook 响应包含 200 HTTP 状态代码、Content-Type: application/json
,
在主体中包含 JSON 序列化形式的数据,在 response
节中给出
ConversionReview
对象(与发送的版本相同)。
如果转换成功,则 Webhook 应该返回包含以下字段的 response
节:
uid
,从发送到 webhook 的 request.uid
复制而来result
,设置为 {"status":"Success"}}
convertedObjects
,包含来自 request.objects
的所有对象,均已转换为
request.desiredVersion
Webhook 的最简单成功响应示例:
{
"apiVersion": "apiextensions.k8s.io/v1" ,
"kind": "ConversionReview" ,
"response": {
# 必须与 <request.uid> 匹配
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002" ,
"result": {
"status": "Success"
},
# 这里的对象必须与 request.objects 中的对象顺序相同并且其 apiVersion 被设置为 <request.desiredAPIVersion>。
# kind、metadata.uid、metadata.name 和 metadata.namespace 等字段都不可被 Webhook 修改。
# Webhook 可以更改 metadata.labels 和 metadata.annotations 字段值。
# Webhook 对 metadata 中其他字段的更改都会被忽略
"convertedObjects": [
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1" ,
"metadata": {
"creationTimestamp": "2019-09-04T14:03:02Z" ,
"name": "local-crontab" ,
"namespace": "default" ,
"resourceVersion": "143" ,
"uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
},
"host": "localhost" ,
"port": "1234"
},
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1" ,
"metadata": {
"creationTimestamp": "2019-09-03T13:02:01Z" ,
"name": "remote-crontab" ,
"resourceVersion": "12893" ,
"uid": "359a83ec-b575-460d-b553-d859cedde8a0"
},
"host": "example.com" ,
"port": "2345"
}
]
}
}
{
# v1.16 中已弃用以推荐使用 apiextensions.k8s.io/v1
"apiVersion": "apiextensions.k8s.io/v1beta1" ,
"kind": "ConversionReview" ,
"response": {
# 必须与 <request.uid> 匹配
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002" ,
"result": {
"status": "Failed"
},
# 这里的对象必须与 request.objects 中的对象顺序相同并且其 apiVersion 被设置为 <request.desiredAPIVersion>。
# kind、metadata.uid、metadata.name 和 metadata.namespace 等字段都不可被 Webhook 修改。
# Webhook 可以更改 metadata.labels 和 metadata.annotations 字段值。
# Webhook 对 metadata 中其他字段的更改都会被忽略。
"convertedObjects": [
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1" ,
"metadata": {
"creationTimestamp": "2019-09-04T14:03:02Z" ,
"name": "local-crontab" ,
"namespace": "default" ,
"resourceVersion": "143" ,
"uid": "3415a7fc-162b-4300-b5da-fd6083580d66"
},
"host": "localhost" ,
"port": "1234"
},
{
"kind": "CronTab" ,
"apiVersion": "example.com/v1" ,
"metadata": {
"creationTimestamp": "2019-09-03T13:02:01Z" ,
"name": "remote-crontab" ,
"resourceVersion": "12893" ,
"uid": "359a83ec-b575-460d-b553-d859cedde8a0"
},
"host": "example.com" ,
"port": "2345"
}
]
}
}
如果转换失败,则 Webhook 应该返回包含以下字段的 response
节:
uid
,从发送到 Webhook 的 request.uid
复制而来result
,设置为 {"status": "Failed"}
警告: 转换失败会破坏对定制资源的读写访问,包括更新或删除资源的能力。
转换失败应尽可能避免,并且不可用于实施合法性检查约束
(应改用验证模式或 Webhook 准入插件)。
来自 Webhook 的响应示例,指示转换请求失败,并带有可选消息:
{
"apiVersion": "apiextensions.k8s.io/v1" ,
"kind": "ConversionReview" ,
"response": {
"uid": "<value from request.uid>" ,
"result": {
"status": "Failed" ,
"message": "hostPort could not be parsed into a separate host and port"
}
}
}
{
# v1.16 中弃用以推荐使用 apiextensions.k8s.io/v1
"apiVersion": "apiextensions.k8s.io/v1beta1" ,
"kind": "ConversionReview" ,
"response": {
"uid": "<value from request.uid>" ,
"result": {
"status": "Failed" ,
"message": "hostPort could not be parsed into a separate host and port"
}
}
}
编写、读取和更新版本化的 CustomResourceDefinition 对象 写入对象时,将使用写入时指定的存储版本来存储。如果存储版本发生变化,
现有对象永远不会被自动转换。然而,新创建或被更新的对象将以新的存储版本写入。
对象写入的版本不再被支持是有可能的。
当读取对象时,作为路径的一部分,你需要指定版本。
如果所指定的版本与对象的持久版本不同,Kubernetes 会按所请求的版本将对象返回,
但是在满足服务请求时,被持久化的对象既不会在磁盘上更改,
也不会以任何方式进行转换(除了 apiVersion
字符串被更改之外)。
你可以以当前提供的任何版本来请求对象。
如果你更新一个现有对象,它将以当前的存储版本被重写。
这是可以将对象从一个版本改到另一个版本的唯一办法。
为了说明这一点,请考虑以下假设的一系列事件:
存储版本是 v1beta1
。你创建一个对象。该对象以版本 v1beta1
存储。 你将为 CustomResourceDefinition 添加版本 v1
,并将其指定为存储版本。 你使用版本 v1beta1
来读取你的对象,然后你再次用版本 v1
读取对象。
除了 apiVersion 字段之外,返回的两个对象是完全相同的。 你创建一个新对象。对象以版本 v1
保存在存储中。
你现在有两个对象,其中一个是 v1beta1
,另一个是 v1
。 你更新第一个对象。该对象现在以版本 v1
保存,因为 v1
是当前的存储版本。 以前的存储版本 API 服务器在状态字段 storedVersions
中记录曾被标记为存储版本的每个版本。
对象可能以任何曾被指定为存储版本的版本保存。
存储中不会出现从未成为存储版本的版本的对象。
将现有对象升级到新的存储版本 弃用版本并删除其支持时,请选择存储升级过程。
选项 1: 使用存储版本迁移程序(Storage Version Migrator)
运行存储版本迁移程序 从 CustomResourceDefinition 的 status.storedVersions
字段中去掉老的版本。 选项 2: 手动将现有对象升级到新的存储版本
以下是从 v1beta1
升级到 v1
的示例过程。
在 CustomResourceDefinition 文件中将 v1
设置为存储版本,并使用 kubectl 应用它。
storedVersions
现在是v1beta1, v1
。 编写升级过程以列出所有现有对象并使用相同内容将其写回存储。
这会强制后端使用当前存储版本(即 v1
)写入对象。 从 CustomResourceDefinition 的 status.storedVersions
字段中删除 v1beta1
。 说明: --subresource
标志在 kubectl get、patch、edit 和 replace 命令中用于获取和更新所有支持它们的
API 资源的子资源、status
和 scale
。此标志从 kubectl 版本 v1.24 开始可用。
以前通过 kubectl 读取子资源(如 status
)涉及使用 kubectl --raw
,并且根本不可能使用 kubectl 更新子资源。
从 v1.24 开始,kubectl
工具可用于编辑或修补有关 CRD 对象的 status
子资源。
请参阅如何使用子资源标志修补 Deployment 。
此页面是 Kubernetes v1.25 文档的一部分。
如果你运行的是不同版本的 Kubernetes,请查阅相应版本的文档。
以下是如何使用 kubectl
为一个 CRD 对象修补 status
子资源的示例:
kubectl patch customresourcedefinitions <CRD_Name> --subresource= 'status' --type= 'merge' -p '{"status":{"storedVersions":["v1"]}}'
4.11.3 - 安装一个扩展的 API server 安装扩展的 API 服务器来使用聚合层以让 Kubernetes API 服务器使用
其它 API 进行扩展,
这些 API 不是核心 Kubernetes API 的一部分。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
你必须配置聚合层
并且启用 API 服务器的相关参数。 安装一个扩展的 API 服务器来使用聚合层 以下步骤描述如何 在一个高层次 设置一个扩展的 apiserver。无论你使用的是 YAML 配置还是使用 API,这些步骤都适用。
目前我们正在尝试区分出两者的区别。有关使用 YAML 配置的具体示例,你可以在 Kubernetes 库中查看
sample-apiserver 。
或者,你可以使用现有的第三方解决方案,例如
apiserver-builder ,
它将生成框架并自动执行以下所有步骤。
确保启用了 APIService API(检查 --runtime-config
)。默认应该是启用的,除非被特意关闭了。 你可能需要制定一个 RBAC 规则,以允许你添加 APIService 对象,或让你的集群管理员创建一个。
(由于 API 扩展会影响整个集群,因此不建议在实时集群中对 API 扩展进行测试/开发/调试) 创建 Kubernetes 命名空间,扩展的 api-service 将运行在该命名空间中。 创建(或获取)用来签署服务器证书的 CA 证书,扩展 api-server 中将使用该证书做 HTTPS 连接。 为 api-server 创建一个服务端的证书(或秘钥)以使用 HTTPS。这个证书应该由上述的 CA 签署。
同时应该还要有一个 Kube DNS 名称的 CN,这是从 Kubernetes 服务派生而来的,
格式为 <service name>.<service name namespace>.svc
。 使用命名空间中的证书(或秘钥)创建一个 Kubernetes secret。 为扩展 api-server 创建一个 Kubernetes Deployment,并确保以卷的方式挂载了 Secret。
它应该包含对扩展 api-server 镜像的引用。Deployment 也应该在同一个命名空间中。 确保你的扩展 apiserver 从该卷中加载了那些证书,并在 HTTPS 握手过程中使用它们。 在你的命名空间中创建一个 Kubernetes 服务账号。 为资源允许的操作创建 Kubernetes 集群角色。 用你命名空间中的服务账号创建一个 Kubernetes 集群角色绑定,绑定到你创建的角色上。 用你命名空间中的服务账号创建一个 Kubernetes 集群角色绑定,绑定到 system:auth-delegator
集群角色,以将 auth 决策委派给 Kubernetes 核心 API 服务器。 以你命名空间中的服务账号创建一个 Kubernetes 集群角色绑定,绑定到
extension-apiserver-authentication-reader
角色。
这将让你的扩展 api-server 能够访问 extension-apiserver-authentication
configmap。 创建一个 Kubernetes apiservice。
上述的 CA 证书应该使用 base64 编码,剥离新行并用作 apiservice 中的 spec.caBundle。
该资源不应放到任何名字空间。如果使用了
kube-aggregator API ,那么只需要传入
PEM 编码的 CA 绑定,因为 base 64 编码已经完成了。 使用 kubectl 来获得你的资源。
它应该返回 "找不到资源"。此消息表示一切正常,但你目前还没有创建该资源类型的对象。 接下来 4.11.4 - 配置多个调度器 Kubernetes 自带了一个默认调度器,其详细描述请查阅
这里 。
如果默认调度器不适合你的需求,你可以实现自己的调度器。
而且,你甚至可以和默认调度器一起同时运行多个调度器,并告诉 Kubernetes 为每个
Pod 使用哪个调度器。
让我们通过一个例子讲述如何在 Kubernetes 中运行多个调度器。
关于实现调度器的具体细节描述超出了本文范围。
请参考 kube-scheduler 的实现,规范示例代码位于
pkg/scheduler 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
打包调度器 将调度器可执行文件打包到容器镜像中。出于示例目的,可以使用默认调度器
(kube-scheduler)作为第二个调度器。
克隆 GitHub 上 Kubernetes 源代码 ,
并编译构建源代码。
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
make
创建一个包含 kube-scheduler 二进制文件的容器镜像。用于构建镜像的 Dockerfile
内容如下:
FROM busybox
ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler
将文件保存为 Dockerfile
,构建镜像并将其推送到镜像仓库。
此示例将镜像推送到 Google 容器镜像仓库(GCR) 。
有关详细信息,请阅读 GCR 文档 。
docker build -t gcr.io/my-gcp-project/my-kube-scheduler:1.0 .
gcloud docker -- push gcr.io/my-gcp-project/my-kube-scheduler:1.0
为调度器定义 Kubernetes Deployment 现在将调度器放在容器镜像中,为它创建一个 Pod 配置,并在 Kubernetes 集群中
运行它。但是与其在集群中直接创建一个 Pod,不如使用
Deployment 。
Deployment 管理一个 ReplicaSet ,
ReplicaSet 再管理 Pod,从而使调度器能够免受一些故障的影响。
以下是 Deployment 配置,将其保存为 my-scheduler.yaml
:
apiVersion : v1
kind : ServiceAccount
metadata :
name : my-scheduler
namespace : kube-system
---
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : my-scheduler-as-kube-scheduler
subjects :
- kind : ServiceAccount
name : my-scheduler
namespace : kube-system
roleRef :
kind : ClusterRole
name : system:kube-scheduler
apiGroup : rbac.authorization.k8s.io
---
apiVersion : v1
kind : ConfigMap
metadata :
name : my-scheduler-config
namespace : kube-system
data :
my-scheduler-config.yaml : |
apiVersion: kubescheduler.config.k8s.io/v1beta2
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: my-scheduler
leaderElection:
leaderElect: false
---
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : my-scheduler-as-volume-scheduler
subjects :
- kind : ServiceAccount
name : my-scheduler
namespace : kube-system
roleRef :
kind : ClusterRole
name : system:volume-scheduler
apiGroup : rbac.authorization.k8s.io
---
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
component : scheduler
tier : control-plane
name : my-scheduler
namespace : kube-system
spec :
selector :
matchLabels :
component : scheduler
tier : control-plane
replicas : 1
template :
metadata :
labels :
component : scheduler
tier : control-plane
version : second
spec :
serviceAccountName : my-scheduler
containers :
- command :
- /usr/local/bin/kube-scheduler
- --config=/etc/kubernetes/my-scheduler/my-scheduler-config.yaml
image : gcr.io/my-gcp-project/my-kube-scheduler:1.0
livenessProbe :
httpGet :
path : /healthz
port : 10259
scheme : HTTPS
initialDelaySeconds : 15
name : kube-second-scheduler
readinessProbe :
httpGet :
path : /healthz
port : 10259
scheme : HTTPS
resources :
requests :
cpu : '0.1'
securityContext :
privileged : false
volumeMounts :
- name : config-volume
mountPath : /etc/kubernetes/my-scheduler
hostNetwork : false
hostPID : false
volumes :
- name : config-volume
configMap :
name : my-scheduler-config
在以上的清单中,你使用 KubeSchedulerConfiguration
来自定义调度器实现的行为。当使用 --config
选项进行初始化时,该配置被传递到 kube-scheduler
。
my-scheduler-config
ConfigMap 存储配置数据。
my-scheduler
Deployment 的 Pod 将 my-scheduler-config
ConfigMap 挂载为一个卷。
在前面提到的调度器配置中,你的调度器通过 KubeSchedulerProfile 进行实现。
说明: 要确定一个调度器是否可以调度特定的 Pod,PodTemplate 或 Pod 清单中的 spec.schedulerName
字段必须匹配 KubeSchedulerProfile
中的 schedulerName
字段。
所有运行在集群中的调度器必须拥有唯一的名称。
还要注意,我们创建了一个专用服务账号 my-scheduler
并将集群角色 system:kube-scheduler
绑定到它,以便它可以获得与 kube-scheduler
相同的权限。
请参阅 kube-scheduler 文档
获取其他命令行参数以及 Scheduler 配置参考
获取自定义 kube-scheduler
配置的详细说明。
在集群中运行第二个调度器 为了在 Kubernetes 集群中运行我们的第二个调度器,在 Kubernetes 集群中创建上面配置中指定的 Deployment:
kubectl create -f my-scheduler.yaml
验证调度器 Pod 正在运行:
kubectl get pods --namespace= kube-system
输出类似于:
NAME READY STATUS RESTARTS AGE
....
my-scheduler-lnf4s-4744f 1/1 Running 0 2m
...
此列表中,除了默认的 kube-scheduler
Pod 之外,你应该还能看到处于 “Running” 状态的
my-scheduler
Pod。
启用领导者选举 要在启用了 leader 选举的情况下运行多调度器,你必须执行以下操作:
更新你的 YAML 文件中的 my-scheduler-config
ConfigMap 里的 KubeSchedulerConfiguration 相关字段如下:
leaderElection.leaderElect
to true
leaderElection.resourceNamespace
to <lock-object-namespace>
leaderElection.resourceName
to <lock-object-name>
说明: 控制平面会为你创建锁对象,但是命名空间必须已经存在。
你可以使用 kube-system
命名空间。
如果在集群上启用了 RBAC,则必须更新 system:kube-scheduler
集群角色。
将调度器名称添加到应用了 endpoints
和 leases
资源的规则的 resourceNames 中,如以下示例所示:
kubectl edit clusterrole system:kube-scheduler
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
annotations :
rbac.authorization.kubernetes.io/autoupdate : "true"
labels :
kubernetes.io/bootstrapping : rbac-defaults
name : system:kube-scheduler
rules :
- apiGroups :
- coordination.k8s.io
resources :
- leases
verbs :
- create
- apiGroups :
- coordination.k8s.io
resourceNames :
- kube-scheduler
- my-scheduler
resources :
- leases
verbs :
- get
- update
- apiGroups :
- ""
resourceNames :
- kube-scheduler
- my-scheduler
resources :
- endpoints
verbs :
- delete
- get
- patch
- update
为 Pod 指定调度器 现在第二个调度器正在运行,创建一些 Pod,并指定它们由默认调度器或部署的调度器进行调度。
为了使用特定的调度器调度给定的 Pod,在那个 Pod 的 spec 中指定调度器的名称。让我们看看三个例子。
Pod spec 设置为 default-scheduler
apiVersion : v1
kind : Pod
metadata :
name : annotation-default-scheduler
labels :
name : multischeduler-example
spec :
schedulerName : default-scheduler
containers :
- name : pod-with-default-annotation-container
image : registry.k8s.io/pause:2.0
通过将调度器名称作为 spec.schedulerName
参数的值来指定调度器。
在这种情况下,我们提供默认调度器的名称,即 default-scheduler
。
将此文件另存为 pod2.yaml
,并将其提交给 Kubernetes 集群。
kubectl create -f pod2.yaml
Pod spec 设置为 my-scheduler
apiVersion : v1
kind : Pod
metadata :
name : annotation-second-scheduler
labels :
name : multischeduler-example
spec :
schedulerName : my-scheduler
containers :
- name : pod-with-second-annotation-container
image : registry.k8s.io/pause:2.0
在这种情况下,我们指定此 Pod 使用我们部署的 my-scheduler
来调度。
请注意,spec.schedulerName
参数的值应该与调度器提供的 KubeSchedulerProfile
中的 schedulerName
字段相匹配。
将此文件另存为 pod3.yaml
,并将其提交给 Kubernetes 集群。
kubectl create -f pod3.yaml
确认所有三个 pod 都在运行。
验证是否使用所需的调度器调度了 pod 为了更容易地完成这些示例,我们没有验证 Pod 实际上是使用所需的调度程序调度的。
我们可以通过更改 Pod 的顺序和上面的部署配置提交来验证这一点。
如果我们在提交调度器部署配置之前将所有 Pod 配置提交给 Kubernetes 集群,
我们将看到注解了 annotation-second-scheduler
的 Pod 始终处于 “Pending” 状态,
而其他两个 Pod 被调度。
一旦我们提交调度器部署配置并且我们的新调度器开始运行,注解了
annotation-second-scheduler
的 pod 就能被调度。
或者,可以查看事件日志中的 “Scheduled” 条目,以验证是否由所需的调度器调度了 Pod。
你也可以使用自定义调度器配置
或自定义容器镜像,用于集群的主调度器,方法是在相关控制平面节点上修改其静态 pod 清单。
4.11.5 - 使用 HTTP 代理访问 Kubernetes API 本文说明如何使用 HTTP 代理访问 Kubernetes API。
准备开始 如果你的集群中还没有任何应用,使用如下命令启动一个 Hello World 应用:
kubectl create deployment node-hello --image= gcr.io/google-samples/node-hello:1.0 --port= 8080
使用 kubectl 启动代理服务器 使用如下命令启动 Kubernetes API 服务器的代理:
kubectl proxy --port= 8080
探究 Kubernetes API 当代理服务器在运行时,你可以通过 curl
、wget
或者浏览器访问 API。
获取 API 版本:
curl http://localhost:8080/api/
输出应该类似这样:
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.2.15:8443"
}
]
}
获取 Pod 列表:
curl http://localhost:8080/api/v1/namespaces/default/pods
输出应该类似这样:
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "33074"
},
"items": [
{
"metadata": {
"name": "kubernetes-bootcamp-2321272333-ix8pt",
"generateName": "kubernetes-bootcamp-2321272333-",
"namespace": "default",
"uid": "ba21457c-6b1d-11e6-85f7-1ef9f1dab92b",
"resourceVersion": "33003",
"creationTimestamp": "2016-08-25T23:43:30Z",
"labels": {
"pod-template-hash": "2321272333",
"run": "kubernetes-bootcamp"
},
...
}
接下来 想了解更多信息,请参阅 kubectl 代理 。
4.11.6 - 使用 SOCKS5 代理访问 Kubernetes API 特性状态: Kubernetes v1.24 [stable]
本文展示了如何使用 SOCKS5 代理访问远程 Kubernetes 集群的 API。
当你要访问的集群不直接在公共 Internet 上公开其 API 时,这很有用。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.24.
要获知版本信息,请输入
kubectl version
.
你需要 SSH 客户端软件(ssh
工具),并在远程服务器上运行 SSH 服务。
你必须能够登录到远程服务器上的 SSH 服务。
任务上下文 说明: 此示例使用 SSH 隧道传输流量,SSH 客户端和服务器充当 SOCKS 代理。
你可以使用其他任意类型的 SOCKS5 代理代替。
图 1 表示你将在此任务中实现的目标。
你有一台在后面的步骤中被称为本地计算机的客户端计算机,你将在这台计算机上创建与
Kubernetes API 对话的请求。 Kubernetes 服务器/API 托管在远程服务器上。 你将使用 SSH 客户端和服务器软件在本地和远程服务器之间创建安全的 SOCKS5 隧道。
客户端和 Kubernetes API 之间的 HTTPS 流量将流经 SOCKS5 隧道,该隧道本身通过
SSH 进行隧道传输。 graph LR;
subgraph local[本地客户端机器]
client([客户端])-- 本地 流量.-> local_ssh[本地 SSH SOCKS5 代理];
end
ocal_ssh[SSH SOCKS5 代理]-- SSH 隧道 -->sshd
subgraph remote[远程服务器]
sshd[SSH 服务器]-- 本地流量 -->service1;
end
client([客户端])-. 通过代理传递的 HTTPS 流量 .->service1[Kubernetes API];
classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000;
classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff;
classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5;
class ingress,service1,service2,pod1,pod2,pod3,pod4 k8s;
class client plain;
class cluster cluster;
图 1. SOCKS5 教程组件
使用 SSH 创建 SOCKS5 代理 此命令在你的客户端计算机和远程服务器之间启动一个 SOCKS5 代理。
SOCKS5 代理允许你连接到集群的 API 服务器。
# 运行此命令后,SSH 隧道继续在前台运行
ssh -D 1080 -q -N username@kubernetes-remote-server.example
-D 1080
: 在本地端口 1080 上打开一个 SOCKS 代理。-q
: 静音模式。导致大多数警告和诊断消息被抑制。-N
: 不执行远程命令。仅用于转发端口。username@kubernetes-remote-server.example
:运行 Kubernetes 集群的远程 SSH 服务器。客户端配置 要探索 Kubernetes API,你首先需要指示你的客户端通过我们之前创建的 SOCKS5
代理发送他们的查询。
对于命令行工具,设置 https_proxy
环境变量并将其传递给你运行的命令。
export https_proxy = socks5h://localhost:1080
当你设置 https_proxy
变量时,curl
等工具会通过你配置的代理路由 HTTPS 流量。
为此,该工具必须支持 SOCKS5 代理。
说明: 在 URL https://localhost:6443/api 中,localhost
不是指你的本地客户端计算机。
它指的是远程服务器上称为 localhost
的端点。
curl
工具通过 SOCKS 从 HTTPS URL 发送主机名,远程服务器在本地解析(到属于其环回接口的地址)。
curl -k -v https://localhost:6443/api
要将 Kubernetes 官方客户端 kubectl
与代理一起使用,
请在 ~/.kube/config
文件中为相关的
cluster
条目设置 proxy-url
元素。例如:
apiVersion : v1
clusters :
- cluster :
certificate-authority-data : LRMEMMW2 # 简化以便阅读
# “Kubernetes API”服务器,换言之,kubernetes-remote-server.example 的 IP 地址
server : https://<API_SERVER_IP_ADRESS>:6443
# 上图中的“SSH SOCKS5代理”(内置 DNS 解析)
proxy-url : socks5://localhost:1080
name : default
contexts :
- context :
cluster : default
user : default
name : default
current-context : default
kind : Config
preferences : {}
users :
- name : default
user :
client-certificate-data : LS0tLS1CR== # 节略,为了便于阅读
client-key-data : LS0tLS1CRUdJT= # 节略,为了便于阅读
如果隧道能够正常工作,并且你调用 kubectl
时使用此集群的上下文,
则可以通过该代理与你的集群交互。例如:
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-85cb69466-klwq8 1/1 Running 0 5m46s
清理 通过在运行它的终端上按 CTRL+C
来停止 SSH 端口转发进程。
在终端中键入 unset https_proxy
以停止通过代理转发 http 流量。
进一步阅读 4.11.7 - 设置 Konnectivity 服务 Konnectivity 服务为控制平面提供集群通信的 TCP 级别代理。
准备开始 你需要有一个 Kubernetes 集群,并且 kubectl 命令可以与集群通信。
建议在至少有两个不充当控制平面主机的节点的集群上运行本教程。
如果你还没有集群,可以使用
minikube 创建一个集群。
接下来的步骤需要出口配置,比如:
apiVersion : apiserver.k8s.io/v1beta1
kind : EgressSelectorConfiguration
egressSelections :
# 由于我们要控制集群的出站流量,所以将 “cluster” 用作 name。
# 其他支持的值有 “etcd” 和 “controlplane”。
- name : cluster
connection :
# 这一属性将控制 API 服务器 Konnectivity 服务器之间的协议。
# 支持的值为 “GRPC” 和 “HTTPConnect”。
# 最终用户不会察觉这两种模式之间的差异。
# 你需要将 Konnectivity 服务器设为在相同模式下工作。
proxyProtocol : GRPC
transport :
# 此属性控制 API 服务器使用哪种传输方式与 Konnectivity 服务器通信。
# 如果 Konnectivity 服务器与 API 服务器位于同一台机器上,建议使用 UDS。
# 你需要将 Konnectivity 服务器配置为侦听同一个 UDS 套接字。
# 另一个支持的传输方式是 “tcp”。
# 你将需要设置 TLS config 以确保 TCP 传输的安全。
uds :
udsName : /etc/kubernetes/konnectivity-server/konnectivity-server.socket
你需要配置 API 服务器来使用 Konnectivity 服务,并将网络流量定向到集群节点:
确保服务账号令牌卷投射 特性被启用。
该特性自 Kubernetes v1.20 起默认已被启用。
创建一个出站流量配置文件,比如 admin/konnectivity/egress-selector-configuration.yaml
。
将 API 服务器的 --egress-selector-config-file
参数设置为你的 API
服务器的离站流量配置文件路径。
如果你在使用 UDS 连接,须将卷配置添加到 kube-apiserver:
spec :
containers :
volumeMounts :
- name : konnectivity-uds
mountPath : /etc/kubernetes/konnectivity-server
readOnly : false
volumes :
- name : konnectivity-uds
hostPath :
path : /etc/kubernetes/konnectivity-server
type : DirectoryOrCreate
为 konnectivity-server 生成或者取得证书和 kubeconfig 文件。
例如,你可以使用 OpenSSL 命令行工具,基于存放在某控制面主机上
/etc/kubernetes/pki/ca.crt
文件中的集群 CA 证书来发放一个 X.509 证书。
openssl req -subj "/CN=system:konnectivity-server" -new -newkey rsa:2048 -nodes -out konnectivity.csr -keyout konnectivity.key
openssl x509 -req -in konnectivity.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out konnectivity.crt -days 375 -sha256
SERVER = $( kubectl config view -o jsonpath = '{.clusters..server}' )
kubectl --kubeconfig /etc/kubernetes/konnectivity-server.conf config set-credentials system:konnectivity-server --client-certificate konnectivity.crt --client-key konnectivity.key --embed-certs= true
kubectl --kubeconfig /etc/kubernetes/konnectivity-server.conf config set-cluster kubernetes --server " $SERVER " --certificate-authority /etc/kubernetes/pki/ca.crt --embed-certs= true
kubectl --kubeconfig /etc/kubernetes/konnectivity-server.conf config set-context system:konnectivity-server@kubernetes --cluster kubernetes --user system:konnectivity-server
kubectl --kubeconfig /etc/kubernetes/konnectivity-server.conf config use-context system:konnectivity-server@kubernetes
rm -f konnectivity.crt konnectivity.key konnectivity.csr
接下来,你需要部署 Konnectivity 服务器和代理。
kubernetes-sigs/apiserver-network-proxy
是一个参考实现。
在控制面节点上部署 Konnectivity 服务。
下面提供的 konnectivity-server.yaml
配置清单假定在你的集群中
Kubernetes 组件都是部署为静态 Pod 的。
如果不是,你可以将 Konnectivity 服务部署为 DaemonSet。
apiVersion : v1
kind : Pod
metadata :
name : konnectivity-server
namespace : kube-system
spec :
priorityClassName : system-cluster-critical
hostNetwork : true
containers :
- name : konnectivity-server-container
image : registry.k8s.io/kas-network-proxy/proxy-server:v0.0.32
command : ["/proxy-server" ]
args : [
"--logtostderr=true" ,
# 下一行需与 egressSelectorConfiguration 中设置的值一致。
"--uds-name=/etc/kubernetes/konnectivity-server/konnectivity-server.socket" ,
# 下面两行假定 Konnectivity 服务器被部署在与 apiserver 相同的机器上,
# 并且该 API 服务器的证书和密钥位于指定的位置。
"--cluster-cert=/etc/kubernetes/pki/apiserver.crt" ,
"--cluster-key=/etc/kubernetes/pki/apiserver.key" ,
# 下一行需与 egressSelectorConfiguration 中设置的值一致。
"--mode=grpc" ,
"--server-port=0" ,
"--agent-port=8132" ,
"--admin-port=8133" ,
"--health-port=8134" ,
"--agent-namespace=kube-system" ,
"--agent-service-account=konnectivity-agent" ,
"--kubeconfig=/etc/kubernetes/konnectivity-server.conf" ,
"--authentication-audience=system:konnectivity-server"
]
livenessProbe :
httpGet :
scheme : HTTP
host : 127.0.0.1
port : 8134
path : /healthz
initialDelaySeconds : 30
timeoutSeconds : 60
ports :
- name : agentport
containerPort : 8132
hostPort : 8132
- name : adminport
containerPort : 8133
hostPort : 8133
- name : healthport
containerPort : 8134
hostPort : 8134
volumeMounts :
- name : k8s-certs
mountPath : /etc/kubernetes/pki
readOnly : true
- name : kubeconfig
mountPath : /etc/kubernetes/konnectivity-server.conf
readOnly : true
- name : konnectivity-uds
mountPath : /etc/kubernetes/konnectivity-server
readOnly : false
volumes :
- name : k8s-certs
hostPath :
path : /etc/kubernetes/pki
- name : kubeconfig
hostPath :
path : /etc/kubernetes/konnectivity-server.conf
type : FileOrCreate
- name : konnectivity-uds
hostPath :
path : /etc/kubernetes/konnectivity-server
type : DirectoryOrCreate
在你的集群中部署 Konnectivity 代理:
apiVersion : apps/v1
# 作为另一种替代方案,你可以将代理部署为 Deployment。
# 没有必要在每个节点上都有一个代理。
kind : DaemonSet
metadata :
labels :
addonmanager.kubernetes.io/mode : Reconcile
k8s-app : konnectivity-agent
namespace : kube-system
name : konnectivity-agent
spec :
selector :
matchLabels :
k8s-app : konnectivity-agent
template :
metadata :
labels :
k8s-app : konnectivity-agent
spec :
priorityClassName : system-cluster-critical
tolerations :
- key : "CriticalAddonsOnly"
operator : "Exists"
containers :
- image : us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-agent:v0.0.16
name : konnectivity-agent
command : ["/proxy-agent" ]
args : [
"--logtostderr=true" ,
"--ca-cert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" ,
# 由于 konnectivity 服务器以 hostNetwork=true 运行,
# 所以这是控制面节点的 IP 地址。
"--proxy-server-host=35.225.206.7" ,
"--proxy-server-port=8132" ,
"--admin-server-port=8133" ,
"--health-server-port=8134" ,
"--service-account-token-path=/var/run/secrets/tokens/konnectivity-agent-token"
]
volumeMounts :
- mountPath : /var/run/secrets/tokens
name : konnectivity-agent-token
livenessProbe :
httpGet :
port : 8134
path : /healthz
initialDelaySeconds : 15
timeoutSeconds : 15
serviceAccountName : konnectivity-agent
volumes :
- name : konnectivity-agent-token
projected :
sources :
- serviceAccountToken :
path : konnectivity-agent-token
audience : system:konnectivity-server
最后,如果你的集群启用了 RBAC,请创建相关的 RBAC 规则:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : system:konnectivity-server
labels :
kubernetes.io/cluster-service : "true"
addonmanager.kubernetes.io/mode : Reconcile
roleRef :
apiGroup : rbac.authorization.k8s.io
kind : ClusterRole
name : system:auth-delegator
subjects :
- apiGroup : rbac.authorization.k8s.io
kind : User
name : system:konnectivity-server
---
apiVersion : v1
kind : ServiceAccount
metadata :
name : konnectivity-agent
namespace : kube-system
labels :
kubernetes.io/cluster-service : "true"
addonmanager.kubernetes.io/mode : Reconcile
4.12 - TLS 了解如何使用传输层安全性( TLS )保护集群中的流量。
4.12.1 - 管理集群中的 TLS 认证 Kubernetes 提供 certificates.k8s.io
API,可让你配置由你控制的证书颁发机构(CA)
签名的 TLS 证书。 你的工作负载可以使用这些 CA 和证书来建立信任。
certificates.k8s.io
API使用的协议类似于
ACME 草案 。
说明: 使用 certificates.k8s.io
API 创建的证书由指定 CA 颁发。
将集群配置为使用集群根目录 CA 可以达到这个目的,但是你永远不要依赖这一假定。
不要以为这些证书将针对群根目录 CA 进行验证。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你需要 cfssl
工具。
你可以从 https://github.com/cloudflare/cfssl/releases
下载 cfssl
。
本文中某些步骤使用 jq
工具。如果你没有 jq
,你可以通过操作系统的软件源安装,
或者从 https://stedolan.github.io/jq/ 获取。
集群中的 TLS 信任 信任 Pod 中运行的应用程序所提供的自定义 CA 通常需要一些额外的应用程序配置。
你需要将 CA 证书包添加到 TLS 客户端或服务器信任的 CA 证书列表中。
例如,你可以使用 Golang TLS 配置通过解析证书链并将解析的证书添加到
tls.Config
结构中的 RootCAs
字段中。
说明: 即使自定义 CA 证书可能包含在文件系统中(在 ConfigMap kube-root-ca.crt
中),
除了验证内部 Kubernetes 端点之外,你不应将该证书颁发机构用于任何目的。
内部 Kubernetes 端点的一个示例是默认命名空间中名为 kubernetes
的服务。
如果你想为你的工作负载使用自定义证书颁发机构,你应该单独生成该 CA,
并使用你的 Pod 有读权限的 ConfigMap
分发该 CA 证书。
请求证书 以下部分演示如何为通过 DNS 访问的 Kubernetes 服务创建 TLS 证书。
说明: 本教程使用 CFSSL:Cloudflare's PKI 和 TLS 工具包
点击此处 了解更多信息。
创建证书签名请求 通过运行以下命令生成私钥和证书签名请求(或 CSR):
cat <<EOF | cfssl genkey - | cfssljson -bare server
{
"hosts": [
"my-svc.my-namespace.svc.cluster.local",
"my-pod.my-namespace.pod.cluster.local",
"192.0.2.24",
"10.0.34.2"
],
"CN": "my-pod.my-namespace.pod.cluster.local",
"key": {
"algo": "ecdsa",
"size": 256
}
}
EOF
其中 192.0.2.24
是服务的集群 IP,my-svc.my-namespace.svc.cluster.local
是服务的 DNS 名称,10.0.34.2
是 Pod 的 IP,而
my-pod.my-namespace.pod.cluster.local
是 Pod 的 DNS 名称。
你能看到的输出类似于:
2022/02/01 11:45:32 [INFO] generate received request
2022/02/01 11:45:32 [INFO] received CSR
2022/02/01 11:45:32 [INFO] generating key: ecdsa-256
2022/02/01 11:45:32 [INFO] encoded CSR
此命令生成两个文件;它生成包含 PEM 编码
PKCS#10 证书请求的 server.csr
,
以及 PEM 编码密钥的 server-key.pem
,用于待生成的证书。
创建证书签名请求(CSR)对象发送到 Kubernetes API 使用以下命令创建 CSR YAML 文件,并发送到 API 服务器:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: my-svc.my-namespace
spec:
request: $(cat server.csr | base64 | tr -d '\n')
signerName: example.com/serving
usages:
- digital signature
- key encipherment
- server auth
EOF
请注意,在步骤 1 中创建的 server.csr
文件是 base64 编码并存储在
.spec.request
字段中的。你还要求提供 “digital signature(数字签名)”,
“密钥加密(key encipherment)” 和 “服务器身份验证(server auth)” 密钥用途,
由 example.com/serving
示例签名程序签名的证书。
你也可以要求使用特定的 signerName
。更多信息可参阅
支持的签署者名称 。
在 API server 中可以看到这些 CSR 处于 Pending 状态。执行下面的命令你将可以看到:
kubectl describe csr my-svc.my-namespace
Name: my-svc.my-namespace
Labels: <none>
Annotations: <none>
CreationTimestamp: Tue, 01 Feb 2022 11:49:15 -0500
Requesting User: yourname@example.com
Signer: example.com/serving
Status: Pending
Subject:
Common Name: my-pod.my-namespace.pod.cluster.local
Serial Number:
Subject Alternative Names:
DNS Names: my-pod.my-namespace.pod.cluster.local
my-svc.my-namespace.svc.cluster.local
IP Addresses: 192.0.2.24
10.0.34.2
Events: <none>
批准证书签名请求(CSR) 证书签名请求
的批准或者是通过自动批准过程完成的,或由集群管理员一次性完成。
如果你被授权批准证书请求,你可以使用 kubectl
来手动完成此操作;例如:
kubectl certificate approve my-svc.my-namespace
certificatesigningrequest.certificates.k8s.io/my-svc.my-namespace approved
你现在应该能看到如下输出:
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
my-svc.my-namespace 10m example.com/serving yourname@example.com <none> Approved
这意味着证书请求已被批准,并正在等待请求的签名者对其签名。
签名证书签名请求(CSR) 接下来,你将扮演证书签署者的角色,颁发证书并将其上传到 API 服务器。
签名者通常会使用其 signerName
查看对象的 CertificateSigningRequest API,
检查它们是否已被批准,为这些请求签署证书,并使用已颁发的证书更新 API 对象状态。
创建证书颁发机构 你需要授权在新证书上提供数字签名。
首先,通过运行以下命令创建签名证书:
cat <<EOF | cfssl gencert -initca - | cfssljson -bare ca
{
"CN": "My Example Signer",
"key": {
"algo": "rsa",
"size": 2048
}
}
EOF
你应该看到类似于以下的输出:
2022/02/01 11:50:39 [INFO] generating a new CA key and certificate from CSR
2022/02/01 11:50:39 [INFO] generate received request
2022/02/01 11:50:39 [INFO] received CSR
2022/02/01 11:50:39 [INFO] generating key: rsa-2048
2022/02/01 11:50:39 [INFO] encoded CSR
2022/02/01 11:50:39 [INFO] signed certificate with serial number 263983151013686720899716354349605500797834580472
这会产生一个证书颁发机构密钥文件(ca-key.pem
)和证书(ca.pem
)。
颁发证书 {
"signing" : {
"default" : {
"usages" : [
"digital signature" ,
"key encipherment" ,
"server auth"
],
"expiry" : "876000h" ,
"ca_constraint" : {
"is_ca" : false
}
}
}
}
使用 server-signing-config.json
签名配置、证书颁发机构密钥文件和证书来签署证书请求:
kubectl get csr my-svc.my-namespace -o jsonpath = '{.spec.request}' | \
base64 --decode | \
cfssl sign -ca ca.pem -ca-key ca-key.pem -config server-signing-config.json - | \
cfssljson -bare ca-signed-server
你应该看到类似于以下的输出:
2022/02/01 11:52:26 [INFO] signed certificate with serial number 576048928624926584381415936700914530534472870337
这会生成一个签名的服务证书文件,ca-signed-server.pem
。
上传签名证书 最后,在 API 对象的状态中填充签名证书:
kubectl get csr my-svc.my-namespace -o json | \
jq '.status.certificate = "' $( base64 ca-signed-server.pem | tr -d '\n' ) '"' | \
kubectl replace --raw /apis/certificates.k8s.io/v1/certificatesigningrequests/my-svc.my-namespace/status -f -
说明: 这使用命令行工具 jq
在 .status.certificate
字段中填充 base64 编码的内容。
如果你没有 jq
工具,你还可以将 JSON 输出保存到文件中,手动填充此字段,然后上传结果文件。
批准 CSR 并上传签名证书后,运行:
输入类似于:
NAME AGE SIGNERNAME REQUESTOR REQUESTEDDURATION CONDITION
my-svc.my-namespace 20m example.com/serving yourname@example.com <none> Approved,Issued
下载证书并使用它 现在,作为请求用户,你可以通过运行以下命令下载颁发的证书并将其保存到 server.crt
文件中:
CSR 被签署并获得批准后,你应该看到以下内容:
kubectl get csr my-svc.my-namespace -o jsonpath = '{.status.certificate}' \
| base64 --decode > server.crt
现在你可以将 server.crt
和 server-key.pem
填充到
Secret 中,
稍后你可以将其挂载到 Pod 中(例如,用于提供 HTTPS 的网络服务器)。
kubectl create secret tls server --cert server.crt --key server-key.pem
secret/server created
最后,你可以将 ca.pem
填充到
ConfigMap
并将其用作信任根来验证服务证书:
kubectl create configmap example-serving-ca --from-file ca.crt= ca.pem
configmap/example-serving-ca created
批准证书签名请求(CSR) Kubernetes 管理员(具有适当权限)可以使用 kubectl certificate approve
和
kubectl certificate deny
命令手动批准(或拒绝)证书签名请求(CSR)。
但是,如果你打算大量使用此 API,则可以考虑编写自动化的证书控制器。
注意: 批准证书 CSR 的能力决定了在你的环境中谁信任谁。
不应广泛或轻率地授予批准 CSR 的能力。
在授予 approve
权限之前,你应该确保自己充分了解批准人的验证要求和 颁发特定证书的后果。
无论上述机器或人使用 kubectl,“批准者”的作用是验证 CSR 满足如下两个要求:
CSR 的 subject 控制用于签署 CSR 的私钥。这解决了伪装成授权主体的第三方的威胁。
在上述示例中,此步骤将验证该 Pod 控制了用于生成 CSR 的私钥。 CSR 的 subject 被授权在请求的上下文中执行。
这点用于处理不期望的主体被加入集群的威胁。
在上述示例中,此步骤将是验证该 Pod 是否被允许加入到所请求的服务中。 当且仅当满足这两个要求时,审批者应该批准 CSR,否则拒绝 CSR。
有关证书批准和访问控制的更多信息,
请阅读证书签名请求 参考页。
给集群管理员的一个建议 本页面假设已经为 certificates API 配置了签名者。
Kubernetes 控制器管理器提供了一个签名者的默认实现。要启用它,请为控制器管理器设置
--cluster-signing-cert-file
和 --cluster-signing-key-file
参数,
使之取值为你的证书机构的密钥对的路径。
4.12.2 - 手动轮换 CA 证书 本页展示如何手动轮换证书机构(CA)证书。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
要了解 Kubernetes 中用户认证的更多信息,参阅
认证 ; 要了解与 CA 证书最佳实践有关的更多信息,
参阅单根 CA 。 手动轮换 CA 证书 注意: 确保备份你的证书目录、配置文件以及其他必要文件。
这里的方法假定 Kubernetes 的控制面通过运行多个 API 服务器以高可用配置模式运行。
另一假定是 API 服务器可体面地终止,因而客户端可以彻底地与一个 API 服务器断开
连接并连接到另一个 API 服务器。
如果集群中只有一个 API 服务器,则在 API 服务器重启期间会经历服务中断期。
将新的 CA 证书和私钥(例如:ca.crt
、ca.key
、front-proxy-ca.crt
和
front-proxy-client.key
)分发到所有控制面节点,放在其 Kubernetes 证书目录下。 更新 kube-controller-manager
的 --root-ca-file
标志,使之同时包含老的和新的 CA,之后重启组件。
自此刻起,所创建的所有ServiceAccount
都会获得同时包含老的 CA 和新的 CA 的 Secret。
说明: kube-controller-manager 标志 --client-ca-file
和 --cluster-signing-cert-file
所引用的文件不能是 CA 证书包。如果这些标志和 --root-ca-file
指向同一个 ca.crt
包文件
(包含老的和新的 CA 证书),你将会收到出错信息。
要解决这个问题,可以将新的 CA 证书复制到单独的文件中,并将 --client-ca-file
和
--cluster-signing-cert-file
标志指向该副本。一旦 ca.crt
不再是证书包文件,
就可以恢复有问题的标志指向 ca.crt
并删除该副本。
kubeadm 的 Issue 1350
在跟踪一个导致 kube-controller-manager 无法接收 CA 证书包的问题。
等待该控制器管理器更新服务账号 Secret 中的 ca.crt
,使之同时包含老的和新的 CA 证书。
如果在 API 服务器使用新的 CA 之前启动了新的 Pod,这些新的 Pod
也会获得此更新并且同时信任老的和新的 CA 证书。
重启所有使用集群内配置的 Pod(例如:kube-proxy、CoreDNS 等),以便这些 Pod
能够使用与 ServiceAccount 相关联的 Secret 中的、已更新的证书机构数据。
确保 CoreDNS、kube-proxy 和其他使用集群内配置的 Pod 都正按预期方式工作。 将老的和新的 CA 都追加到 kube-apiserver
配置的 --client-ca-file
和
--kubelet-certificate-authority
标志所指的文件。
将老的和新的 CA 都追加到 kube-scheduler
配置的 --client-ca-file
标志所指的文件。
通过替换 client-certificate-data
和 client-key-data
中的内容,更新用户账号的证书。
有关为独立用户账号创建证书的更多信息,可参阅
为用户帐号配置证书 。
另外,还要更新 kubeconfig 文件中的 certificate-authority-data
节,
使之包含 Base64 编码的老的和新的证书机构数据。
更新 云控制器管理器(Cloud Controller Manager) 的 --root-ca-file
标志值,使之同时包含老的和新的 CA,之后重新启动 cloud-controller-manager。
说明: 如果你的集群中不包含 cloud-controller-manager,你可以略过这一步。
遵循下列步骤执行滚动更新
重新启动所有其他被聚合的 API 服务器
或者 Webhook 处理程序,使之信任新的 CA 证书。
在所有节点上更新 kubelet 配置中的 clientCAFile
所指文件以及 kubelet.conf 中的
certificate-authority-data
并重启 kubelet 以同时使用老的和新的 CA 证书。
如果你的 kubelet 并未使用客户端证书轮换,则在所有节点上更新 kubelet.conf 中
client-certificate-data
和 client-key-data
以及 kubelet
客户端证书文件(通常位于 /var/lib/kubelet/pki
目录下)
使用用新的 CA 签名的证书
(apiserver.crt
、apiserver-kubelet-client.crt
和 front-proxy-client.crt
)
来重启 API 服务器。
你可以使用现有的私钥,也可以使用新的私钥。
如果你改变了私钥,则要将更新的私钥也放到 Kubernetes 证书目录下。 由于集群中的 Pod 既信任老的 CA 也信任新的 CA,Pod 中的客户端会经历短暂的连接断开状态,
之后再使用新的 CA 所签名的证书连接到新的 API 服务器。
* 重启 kube-scheduler 以使用并信任新的
CA 证书。
确保控制面组件的日志中没有 TLS 相关的错误信息。 说明: 要使用 `openssl` 命令行为集群生成新的证书和私钥,可参阅
[证书(`openssl`)](/docs/tasks/administer-cluster/certificates/#openssl)。
你也可以使用[`cfssl`](/docs/tasks/administer-cluster/certificates/#cfssl).
为 Daemonset 和 Deployment 添加注解,从而触发较安全的滚动更新,替换 Pod。 for namespace in $( kubectl get namespace -o jsonpath = '{.items[*].metadata.name}' ) ; do
for name in $( kubectl get deployments -n $namespace -o jsonpath = '{.items[*].metadata.name}' ) ; do
kubectl patch deployment -n ${ namespace } ${ name } -p '{"spec":{"template":{"metadata":{"annotations":{"ca-rotation": "1"}}}}}' ;
done
for name in $( kubectl get daemonset -n $namespace -o jsonpath = '{.items[*].metadata.name}' ) ; do
kubectl patch daemonset -n ${ namespace } ${ name } -p '{"spec":{"template":{"metadata":{"annotations":{"ca-rotation": "1"}}}}}' ;
done
done
说明: 要限制应用可能受到的并发干扰数量,
可以参阅[配置 Pod 干扰预算](/docs/tasks/run-application/configure-pdb/)。
取决于你在如何使用 StatefulSet,你可能需要对其执行类似的滚动替换操作。
如果你的集群使用启动引导令牌来添加节点,则需要更新 kube-public
名字空间下的
ConfigMap cluster-info
,使之包含新的 CA 证书。
base64_encoded_ca = " $( base64 -w0 /etc/kubernetes/pki/ca.crt) "
kubectl get cm/cluster-info --namespace kube-public -o yaml | \
/bin/sed "s/\(certificate-authority-data:\).*/\1 ${ base64_encoded_ca } /" | \
kubectl apply -f -
验证集群的功能正常。
检查控制面组件以及 kubelet
和 kube-proxy
的日志,确保其中没有抛出 TLS 错误,
参阅查看日志 。
验证被聚合的 API 服务器的日志,以及所有使用集群内配置的 Pod 的日志。
完成集群功能的检查之后:
更新所有的服务账号令牌,使之仅包含新的 CA 证书。
使用集群内 kubeconfig 的 Pod 最终也需要被重启,以获得新的服务账号 Secret
数据,这样就不会有 Pod 再依赖老的集群 CA。 从 kubeconfig 文件和 --client-ca-file
以及 --root-ca-file
标志所指向的文件
中去除老的 CA 数据,之后重启控制面组件。
在每个节点上,移除 clientCAFile
标志所指向的文件,以删除老的 CA 数据,并从
kubelet kubeconfig 文件中去掉老的 CA,重启 kubelet。
你应该用滚动更新的方式来执行这一步骤的操作。
如果你的集群允许你执行这一变更,你也可以通过替换节点而不是重新配置节点的方式来将其上线。
4.12.3 - 为 kubelet 配置证书轮换 本文展示如何在 kubelet 中启用并配置证书轮换。
特性状态: Kubernetes v1.19 [stable]
准备开始 要求 Kubernetes 1.8.0 或更高的版本 概述 Kubelet 使用证书进行 Kubernetes API 的认证。
默认情况下,这些证书的签发期限为一年,所以不需要太频繁地进行更新。
Kubernetes 包含特性
kubelet 证书轮换 ,
在当前证书即将过期时,
将自动生成新的秘钥,并从 Kubernetes API 申请新的证书。 一旦新的证书可用,它将被用于与
Kubernetes API 间的连接认证。
启用客户端证书轮换 kubelet
进程接收 --rotate-certificates
参数,该参数决定 kubelet 在当前使用的
证书即将到期时,是否会自动申请新的证书。
kube-controller-manager
进程接收 --cluster-signing-duration
参数
(在 1.19 版本之前为 --experimental-cluster-signing-duration
),用来
控制签发证书的有效期限。
理解证书轮换配置 当 kubelet 启动时,如被配置为自举(使用--bootstrap-kubeconfig
参数),kubelet
会使用其初始证书连接到 Kubernetes API ,并发送证书签名的请求。
可以通过以下方式查看证书签名请求的状态:
最初,来自节点上 kubelet 的证书签名请求处于 Pending
状态。 如果证书签名请求满足特定条件,
控制器管理器会自动批准,此时请求会处于 Approved
状态。 接下来,控制器管理器会签署证书,
证书的有效期限由 --cluster-signing-duration
参数指定,签署的证书会被附加到证书签名请求中。
Kubelet 会从 Kubernetes API 取回签署的证书,并将其写入磁盘,存储位置通过 --cert-dir
参数指定。
然后 kubelet 会使用新的证书连接到 Kubernetes API。
当签署的证书即将到期时,kubelet 会使用 Kubernetes API,自动发起新的证书签名请求。
该请求会发生在证书的有效时间剩下 30% 到 10% 之间的任意时间点。
同样地,控制器管理器会自动批准证书请求,并将签署的证书附加到证书签名请求中。 Kubelet
会从 Kubernetes API 取回签署的证书,并将其写入磁盘。 然后它会更新与 Kubernetes API
的连接,使用新的证书重新连接到 Kubernetes API。
4.13 - 管理集群守护进程 执行 DaemonSet 管理的常见任务,例如执行滚动更新。
4.13.1 - 对 DaemonSet 执行滚动更新 本文介绍了如何对 DaemonSet 执行滚动更新。
准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
DaemonSet 更新策略 DaemonSet 有两种更新策略:
OnDelete
: 使用 OnDelete
更新策略时,在更新 DaemonSet 模板后,只有当你手动删除老的
DaemonSet pods 之后,新的 DaemonSet Pod 才会 被自动创建。跟 Kubernetes 1.6 以前的版本类似。RollingUpdate
: 这是默认的更新策略。使用 RollingUpdate
更新策略时,在更新 DaemonSet 模板后,
老的 DaemonSet Pod 将被终止,并且将以受控方式自动创建新的 DaemonSet Pod。
更新期间,最多只能有 DaemonSet 的一个 Pod 运行于每个节点上。要启用 DaemonSet 的滚动更新功能,必须设置 .spec.updateStrategy.type
为 RollingUpdate
。
你可能想设置
.spec.updateStrategy.rollingUpdate.maxUnavailable
(默认为 1),
.spec.minReadySeconds
(默认为 0) 和
.spec.updateStrategy.rollingUpdate.maxSurge
(默认为 0)。
创建带有 RollingUpdate
更新策略的 DaemonSet 下面的 YAML 包含一个 DaemonSet,其更新策略为 'RollingUpdate':
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : fluentd-elasticsearch
namespace : kube-system
labels :
k8s-app : fluentd-logging
spec :
selector :
matchLabels :
name : fluentd-elasticsearch
updateStrategy :
type : RollingUpdate
rollingUpdate :
maxUnavailable : 1
template :
metadata :
labels :
name : fluentd-elasticsearch
spec :
tolerations :
# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
- key : node-role.kubernetes.io/master
effect : NoSchedule
containers :
- name : fluentd-elasticsearch
image : quay.io/fluentd_elasticsearch/fluentd:v2.5.2
volumeMounts :
- name : varlog
mountPath : /var/log
- name : varlibdockercontainers
mountPath : /var/lib/docker/containers
readOnly : true
terminationGracePeriodSeconds : 30
volumes :
- name : varlog
hostPath :
path : /var/log
- name : varlibdockercontainers
hostPath :
path : /var/lib/docker/containers
检查了 DaemonSet 清单中更新策略的设置之后,创建 DaemonSet:
kubectl create -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml
另一种方式是如果你希望使用 kubectl apply
来更新 DaemonSet 的话,
也可以使用 kubectl apply
来创建 DaemonSet:
kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml
检查 DaemonSet 的滚动更新策略 首先,检查 DaemonSet 的更新策略,确保已经将其设置为 RollingUpdate
:
kubectl get ds/fluentd-elasticsearch -o go-template= '{{.spec.updateStrategy.type}}{{"\n"}}' -n kube-system
如果还没在系统中创建 DaemonSet,请使用以下命令检查 DaemonSet 的清单:
kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset.yaml --dry-run= client -o go-template= '{{.spec.updateStrategy.type}}{{"\n"}}'
两个命令的输出都应该为:
RollingUpdate
如果输出不是 RollingUpdate
,请返回并相应地修改 DaemonSet 对象或者清单。
更新 DaemonSet 模板 对 RollingUpdate
DaemonSet 的 .spec.template
的任何更新都将触发滚动更新。
这可以通过几个不同的 kubectl
命令来完成。
apiVersion : apps/v1
kind : DaemonSet
metadata :
name : fluentd-elasticsearch
namespace : kube-system
labels :
k8s-app : fluentd-logging
spec :
selector :
matchLabels :
name : fluentd-elasticsearch
updateStrategy :
type : RollingUpdate
rollingUpdate :
maxUnavailable : 1
template :
metadata :
labels :
name : fluentd-elasticsearch
spec :
tolerations :
# 这些容忍度设置是为了让该守护进程集在控制平面节点上运行
# 如果你不希望自己的控制平面节点运行 Pod,可以删除它们
- key : node-role.kubernetes.io/control-plane
operator : Exists
effect : NoSchedule
- key : node-role.kubernetes.io/master
operator : Exists
effect : NoSchedule
containers :
- name : fluentd-elasticsearch
image : quay.io/fluentd_elasticsearch/fluentd:v2.5.2
resources :
limits :
memory : 200Mi
requests :
cpu : 100m
memory : 200Mi
volumeMounts :
- name : varlog
mountPath : /var/log
- name : varlibdockercontainers
mountPath : /var/lib/docker/containers
readOnly : true
terminationGracePeriodSeconds : 30
volumes :
- name : varlog
hostPath :
path : /var/log
- name : varlibdockercontainers
hostPath :
path : /var/lib/docker/containers
声明式命令 如果你使用配置文件 来更新
DaemonSet,请使用 kubectl apply
:
kubectl apply -f https://k8s.io/examples/controllers/fluentd-daemonset-update.yaml
指令式命令 如果你使用指令式命令 来更新
DaemonSets,请使用 kubectl edit
:
kubectl edit ds/fluentd-elasticsearch -n kube-system
只更新容器镜像 如果你只需要更新 DaemonSet 模板里的容器镜像,比如 .spec.template.spec.containers[*].image
,
请使用 kubectl set image
:
kubectl set image ds/fluentd-elasticsearch fluentd-elasticsearch= quay.io/fluentd_elasticsearch/fluentd:v2.6.0 -n kube-system
监视滚动更新状态 最后,观察 DaemonSet 最新滚动更新的进度:
kubectl rollout status ds/fluentd-elasticsearch -n kube-system
当滚动更新完成时,输出结果如下:
daemonset "fluentd-elasticsearch" successfully rolled out
故障排查 DaemonSet 滚动更新卡住 有时,DaemonSet 滚动更新可能卡住,以下是一些可能的原因:
一些节点可用资源耗尽 DaemonSet 滚动更新可能会卡住,其 Pod 至少在某个节点上无法调度运行。
当节点上可用资源耗尽 时,
这是可能的。
发生这种情况时,通过对 kubectl get nodes
和下面命令行的输出作比较,
找出没有调度 DaemonSet Pod 的节点:
kubectl get pods -l name = fluentd-elasticsearch -o wide -n kube-system
一旦找到这些节点,从节点上删除一些非 DaemonSet Pod,为新的 DaemonSet Pod 腾出空间。
不完整的滚动更新 如果最近的 DaemonSet 模板更新被破坏了,比如,容器处于崩溃循环状态或者容器镜像不存在
(通常由于拼写错误),就会发生 DaemonSet 滚动更新中断。
要解决此问题,需再次更新 DaemonSet 模板。新的滚动更新不会被以前的不健康的滚动更新阻止。
时钟偏差 如果在 DaemonSet 中指定了 .spec.minReadySeconds
,主控节点和工作节点之间的时钟偏差会使
DaemonSet 无法检测到正确的滚动更新进度。
清理 从名字空间中删除 DaemonSet:
kubectl delete ds fluentd-elasticsearch -n kube-system
接下来 4.13.2 - 对 DaemonSet 执行回滚 本文展示了如何对 DaemonSet 执行回滚。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 1.7.
要获知版本信息,请输入
kubectl version
.
你应该已经了解如何为 DaemonSet 执行滚东更新 。
对 DaemonSet 执行回滚 步骤 1:找到想要 DaemonSet 回滚到的历史修订版本(revision) 如果只想回滚到最后一个版本,可以跳过这一步。
列出 DaemonSet 的所有版本:
kubectl rollout history daemonset <daemonset-name>
此命令返回 DaemonSet 版本列表:
daemonsets "<daemonset-name>"
REVISION CHANGE-CAUSE
1 ...
2 ...
...
在创建时,DaemonSet 的变化原因从 kubernetes.io/change-cause
注解(annotation)
复制到其修订版本中。用户可以在 kubectl
命令中设置 --record=true
,
将执行的命令记录在变化原因注解中。 执行以下命令,来查看指定版本的详细信息:
kubectl rollout history daemonset <daemonset-name> --revision= 1
该命令返回相应修订版本的详细信息:
daemonsets "<daemonset-name>" with revision #1
Pod Template:
Labels: foo = bar
Containers:
app:
Image: ...
Port: ...
Environment: ...
Mounts: ...
Volumes: ...
步骤 2:回滚到指定版本 # 在 --to-revision 中指定你从步骤 1 中获取的修订版本
kubectl rollout undo daemonset <daemonset-name> --to-revision= <revision>
如果成功,命令会返回:
daemonset "<daemonset-name>" rolled back
说明: 如果 --to-revision
参数未指定,将选中最近的版本。
步骤 3:监视 DaemonSet 回滚进度 kubectl rollout undo daemonset
向服务器表明启动 DaemonSet 回滚。
真正的回滚是在集群的
控制面
异步完成的。
执行以下命令,来监视 DaemonSet 回滚进度:
kubectl rollout status ds/<daemonset-name>
回滚完成时,输出形如:
daemonset "<daemonset-name>" successfully rolled out
理解 DaemonSet 修订版本 在前面的 kubectl rollout history
步骤中,你获得了一个修订版本列表,每个修订版本都存储在名为
ControllerRevision
的资源中。
要查看每个修订版本中保存的内容,可以找到 DaemonSet 修订版本的原生资源:
kubectl get controllerrevision -l <daemonset-selector-key>= <daemonset-selector-value>
该命令返回 ControllerRevisions
列表:
NAME CONTROLLER REVISION AGE
<daemonset-name>-<revision-hash> DaemonSet/<daemonset-name> 1 1h
<daemonset-name>-<revision-hash> DaemonSet/<daemonset-name> 2 1h
每个 ControllerRevision
中存储了相应 DaemonSet 版本的注解和模板。
kubectl rollout undo
选择特定的 ControllerRevision
,并用
ControllerRevision
中存储的模板代替 DaemonSet 的模板。
kubectl rollout undo
相当于通过其他命令(如 kubectl edit
或 kubectl apply
)
将 DaemonSet 模板更新至先前的版本。
说明: 注意 DaemonSet 修订版本只会正向变化。也就是说,回滚完成后,所回滚到的
ControllerRevision
版本号 (.revision
字段) 会增加。
例如,如果用户在系统中有版本 1 和版本 2,并从版本 2 回滚到版本 1,
带有 .revision: 1
的 ControllerRevision
将变为 .revision: 3
。
故障排查 4.14.1 - 使用 HostAliases 向 Pod /etc/hosts 文件添加条目 当 DNS 配置以及其它选项不合理的时候,通过向 Pod 的 /etc/hosts
文件中添加条目,
可以在 Pod 级别覆盖对主机名的解析。你可以通过 PodSpec 的 HostAliases
字段来添加这些自定义条目。
建议通过使用 HostAliases 来进行修改,因为该文件由 Kubelet 管理,并且
可以在 Pod 创建/重启过程中被重写。
默认 hosts 文件内容 让我们从一个 Nginx Pod 开始,该 Pod 被分配一个 IP:
kubectl run nginx --image nginx
pod/nginx created
检查 Pod IP:
kubectl get pods --output= wide
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 13s 10.200.0.4 worker0
主机文件的内容如下所示:
kubectl exec nginx -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.200.0.4 nginx
默认情况下,hosts
文件只包含 IPv4 和 IPv6 的样板内容,像 localhost
和主机名称。
通过 HostAliases 增加额外条目 除了默认的样板内容,我们可以向 hosts
文件添加额外的条目。
例如,要将 foo.local
、bar.local
解析为 127.0.0.1
,
将 foo.remote
、 bar.remote
解析为 10.1.2.3
,我们可以在
.spec.hostAliases
下为 Pod 配置 HostAliases。
apiVersion : v1
kind : Pod
metadata :
name : hostaliases-pod
spec :
restartPolicy : Never
hostAliases :
- ip : "127.0.0.1"
hostnames :
- "foo.local"
- "bar.local"
- ip : "10.1.2.3"
hostnames :
- "foo.remote"
- "bar.remote"
containers :
- name : cat-hosts
image : busybox:1.28
command :
- cat
args :
- "/etc/hosts"
你可以使用以下命令用此配置启动 Pod:
kubectl apply -f https://k8s.io/examples/service/networking/hostaliases-pod.yaml
pod/hostaliases-pod created
检查 Pod 详情,查看其 IPv4 地址和状态:
kubectl get pod --output= wide
NAME READY STATUS RESTARTS AGE IP NODE
hostaliases-pod 0/1 Completed 0 6s 10.200.0.5 worker0
hosts
文件的内容看起来类似如下所示:
kubectl logs hostaliases-pod
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.200.0.5 hostaliases-pod
# Entries added by HostAliases.
127.0.0.1 foo.local bar.local
10.1.2.3 foo.remote bar.remote
在最下面额外添加了一些条目。
为什么 kubelet 管理 hosts 文件? kubelet 管理每个Pod 容器的 hosts
文件,以防止容器运行时在容器已经启动后修改文件。
由于历史原因,Kubernetes 总是使用 Docker Engine 作为其容器运行时,而 Docker Engine
将在容器启动后修改 /etc/hosts
文件。
当前的 Kubernetes 可以使用多种容器运行时;即便如此,kubelet 管理在每个容器中创建 hosts文件,
以便你使用任何容器运行时运行容器时,结果都符合预期。
注意: 请避免手工更改容器内的 hosts 文件内容。
如果你对 hosts 文件做了手工修改,这些修改都会在容器退出时丢失。
4.14.2 - 验证 IPv4/IPv6 双协议栈 本文分享了如何验证 IPv4/IPv6 双协议栈的 Kubernetes 集群。
准备开始 驱动程序对双协议栈网络的支持 (云驱动或其他方式必须能够为 Kubernetes 节点提供可路由的 IPv4/IPv6 网络接口) 一个能够支持双协议栈 网络的
网络插件 。 启用双协议栈 集群你的 Kubernetes 服务器版本必须不低于版本 v1.23.
要获知版本信息,请输入
kubectl version
.
说明: 虽然你可以使用较早的版本进行验证,但该功能是从 v1.23 版本进入 GA 状态并正式支持的。
验证寻址 验证节点寻址 每个双协议栈节点应分配一个 IPv4 块和一个 IPv6 块。
通过运行以下命令来验证是否配置了 IPv4/IPv6 Pod 地址范围。
将示例节点名称替换为集群中的有效双协议栈节点。
在此示例中,节点的名称为 k8s-linuxpool1-34450317-0
:
kubectl get nodes k8s-linuxpool1-34450317-0 -o go-template --template= '{{range .spec.podCIDRs}}{{printf "%s\n" .}}{{end}}'
10.244.1.0/24
2001:db8::/64
应该分配一个 IPv4 块和一个 IPv6 块。
验证节点是否检测到 IPv4 和 IPv6 接口。用集群中的有效节点替换节点名称。
在此示例中,节点名称为 k8s-linuxpool1-34450317-0
:
kubectl get nodes k8s-linuxpool1-34450317-0 -o go-template --template= '{{range .status.addresses}}{{printf "%s: %s\n" .type .address}}{{end}}'
Hostname: k8s-linuxpool1-34450317-0
InternalIP: 10.0.0.5
InternalIP: 2001:db8:10::5
验证 Pod 寻址 验证 Pod 已分配了 IPv4 和 IPv6 地址。用集群中的有效 Pod 替换 Pod 名称。
在此示例中,Pod 名称为 pod01
:
kubectl get pods pod01 -o go-template --template= '{{range .status.podIPs}}{{printf "%s\n" .ip}}{{end}}'
10.244.1.4
2001:db8::4
你也可以通过 status.podIPs
使用 Downward API 验证 Pod IP。
以下代码段演示了如何通过容器内称为 MY_POD_IPS
的环境变量公开 Pod 的 IP 地址。
env :
- name : MY_POD_IPS
valueFrom :
fieldRef :
fieldPath : status.podIPs
使用以下命令打印出容器内部 MY_POD_IPS
环境变量的值。
该值是一个逗号分隔的列表,与 Pod 的 IPv4 和 IPv6 地址相对应。
kubectl exec -it pod01 -- set | grep MY_POD_IPS
MY_POD_IPS=10.244.1.4,2001:db8::4
Pod 的 IP 地址也将被写入容器内的 /etc/hosts
文件中。
在双栈 Pod 上执行 cat /etc/hosts
命令操作。
从输出结果中,你可以验证 Pod 的 IPv4 和 IPv6 地址。
kubectl exec -it pod01 -- cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.244.1.4 pod01
2001:db8::4 pod01
验证服务 创建以下未显式定义 .spec.ipFamilyPolicy
的 Service。
Kubernetes 将从首个配置的 service-cluster-ip-range
给 Service 分配集群 IP,
并将 .spec.ipFamilyPolicy
设置为 SingleStack
。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
使用 kubectl
查看 Service 的 YAML 定义。
kubectl get svc my-service -o yaml
该 Service 通过在 kube-controller-manager 的 --service-cluster-ip-range
标志设置的第一个配置范围,将 .spec.ipFamilyPolicy
设置为 SingleStack
,
将 .spec.clusterIP
设置为 IPv4 地址。
apiVersion : v1
kind : Service
metadata :
name : my-service
namespace : default
spec :
clusterIP : 10.0.217.164
clusterIPs :
- 10.0.217.164
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 9376
selector :
app.kubernetes.io/name : MyApp
sessionAffinity : None
type : ClusterIP
status :
loadBalancer : {}
创建以下显式定义 .spec.ipFamilies
数组中的第一个元素为 IPv6 的 Service。
Kubernetes 将 service-cluster-ip-range
配置的 IPv6 地址范围给 Service 分配集群 IP,
并将 .spec.ipFamilyPolicy
设置为 SingleStack
。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilies :
- IPv6
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
使用 kubectl
查看 Service 的 YAML 定义。
kubectl get svc my-service -o yaml
该 Service 通过在 kube-controller-manager 的 --service-cluster-ip-range
标志设置的 IPv6 地址范围,将 .spec.ipFamilyPolicy
设置为 SingleStack
,
将 .spec.clusterIP
设置为 IPv6 地址。
apiVersion : v1
kind : Service
metadata :
labels :
app.kubernetes.io/name : MyApp
name : my-service
spec :
clusterIP : 2001 :db8:fd00::5118
clusterIPs :
- 2001 :db8:fd00::5118
ipFamilies :
- IPv6
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app.kubernetes.io/name : MyApp
sessionAffinity : None
type : ClusterIP
status :
loadBalancer : {}
创建以下显式定义 .spec.ipFamilyPolicy
为 PreferDualStack
的 Service。
Kubernetes 将分配 IPv4 和 IPv6 地址(因为该集群启用了双栈),
并根据 .spec.ipFamilies
数组中第一个元素的地址族,
从 .spec.ClusterIPs
列表中选择 .spec.ClusterIP
。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
说明: kubectl get svc
命令将仅在 CLUSTER-IP
字段中显示主 IP。
kubectl get svc -l app.kubernetes.io/name= MyApp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT( S) AGE
my-service ClusterIP 10.0.216.242 <none> 80/TCP 5s
使用 kubectl describe
验证服务是否从 IPv4 和 IPv6 地址块中获取了集群 IP。
然后你就可以通过 IP 和端口,验证对服务的访问。
kubectl describe svc -l app.kubernetes.io/name= MyApp
Name: my-service
Namespace: default
Labels: app.kubernetes.io/name=MyApp
Annotations: <none>
Selector: app.kubernetes.io/name=MyApp
Type: ClusterIP
IP Family Policy: PreferDualStack
IP Families: IPv4,IPv6
IP: 10.0.216.242
IPs: 10.0.216.242,2001:db8:fd00::af55
Port: <unset> 80/TCP
TargetPort: 9376/TCP
Endpoints: <none>
Session Affinity: None
Events: <none>
创建双协议栈负载均衡服务 如果云提供商支持配置启用 IPv6 的外部负载均衡器,则创建如下 Service 时将
.spec.ipFamilyPolicy
设置为 PreferDualStack
, 并将 spec.ipFamilies
字段
的第一个元素设置为 IPv6
,将 type
字段设置为 LoadBalancer
:
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app.kubernetes.io/name : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
type : LoadBalancer
selector :
app.kubernetes.io/name : MyApp
ports :
- protocol : TCP
port : 80
检查服务:
kubectl get svc -l app.kubernetes.io/name= MyApp
验证服务是否从 IPv6 地址块中接收到 CLUSTER-IP
地址以及 EXTERNAL-IP
。
然后,你可以通过 IP 和端口验证对服务的访问。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT( S) AGE
my-service LoadBalancer 2001:db8:fd00::7ebc 2603:1030:805::5 80:30790/TCP 35s
4.15 - 调度 GPU 配置和调度 GPU 成一类资源以供集群中节点使用。
特性状态: Kubernetes v1.10 [beta]
Kubernetes 支持对若干节点上的 GPU(图形处理单元)进行管理,目前处于实验 状态。
本页介绍用户如何使用 GPU 以及当前存在的一些限制。
使用设备插件 Kubernetes 实现了设备插件(Device Plugin)
以允许 Pod 访问类似 GPU 这类特殊的硬件功能特性。
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
作为集群管理员,你要在节点上安装来自对应硬件厂商的 GPU 驱动程序,并运行来自
GPU 厂商的对应设备插件。
一旦你安装了插件,你的集群就会暴露一个自定义可调度的资源,例如 amd.com/gpu
或 nvidia.com/gpu
。
你可以通过请求这个自定义的 GPU 资源在你的容器中使用这些 GPU,其请求方式与请求 cpu
或 memory
时相同。
不过,在如何指定自定义设备的资源请求方面存在一些限制。
GPU 只能在 limits
部分指定,这意味着:你可以指定 GPU 的 limits
而不指定其 requests
,因为 Kubernetes 将默认使用限制
值作为请求值。 你可以同时指定 limits
和 requests
,不过这两个值必须相等。 你不可以仅指定 requests
而不指定 limits
。 以下是一个 Pod 请求 GPU 的示例清单:
apiVersion : v1
kind : Pod
metadata :
name : example-vector-add
spec :
restartPolicy : OnFailure
containers :
- name : example-vector-add
image : "registry.example/example-vector-add:v42"
resources :
limits :
gpu-vendor.example/example-gpu : 1 # 请求 1 个 GPU
集群内存在不同类型的 GPU 如果集群内部的不同节点上有不同类型的 NVIDIA GPU,
那么你可以使用节点标签和节点选择器 来将
Pod 调度到合适的节点上。
例如:
# 为你的节点加上它们所拥有的加速器类型的标签
kubectl label nodes node1 accelerator = example-gpu-x100
kubectl label nodes node2 accelerator = other-gpu-k915
这个标签键 accelerator
只是一个例子;如果你愿意,可以使用不同的标签键。
自动节点标签 如果你在使用 AMD GPU,你可以部署
Node Labeller ,
它是一个 控制器 ,
会自动给节点打上 GPU 设备属性标签。目前支持的属性:
设备 ID (-device-id) VRAM 大小 (-vram) SIMD 数量(-simd-count) 计算单位数量(-cu-count) 固件和特性版本 (-firmware) GPU 系列,两个字母的首字母缩写(-family)SI - Southern Islands CI - Sea Islands KV - Kaveri VI - Volcanic Islands CZ - Carrizo AI - Arctic Islands RV - Raven kubectl describe node cluster-node-23
Name: cluster-node-23
Roles: <none>
Labels: beta.amd.com/gpu.cu-count.64=1
beta.amd.com/gpu.device-id.6860=1
beta.amd.com/gpu.family.AI=1
beta.amd.com/gpu.simd-count.256=1
beta.amd.com/gpu.vram.16G=1
kubernetes.io/arch=amd64
kubernetes.io/os=linux
kubernetes.io/hostname=cluster-node-23
Annotations: node.alpha.kubernetes.io/ttl: 0
…
使用了 Node Labeller 的时候,你可以在 Pod 的规约中指定 GPU 的类型:
apiVersion : v1
kind : Pod
metadata :
name : cuda-vector-add
spec :
restartPolicy : OnFailure
containers :
- name : cuda-vector-add
# https://github.com/kubernetes/kubernetes/blob/v1.7.11/test/images/nvidia-cuda/Dockerfile
image : "registry.k8s.io/cuda-vector-add:v0.1"
resources :
limits :
nvidia.com/gpu : 1
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
– matchExpressions :
– key : beta.amd.com/gpu.family.AI # Arctic Islands GPU 系列
operator : Exist
这能够保证 Pod 能够被调度到你所指定类型的 GPU 的节点上去。
4.16 - 管理巨页(HugePages) 将大页配置和管理为集群中的可调度资源。
特性状态: Kubernetes v1.25 [stable]
Kubernetes 支持在 Pod 应用中使用预先分配的巨页。本文描述了用户如何使用巨页,以及当前的限制。
准备开始 为了使节点能够上报巨页容量,Kubernetes 节点必须预先分配巨页。每个节点能够预先分配多种规格的巨页。 节点会自动发现全部巨页资源,并作为可供调度的资源进行上报。
API 用户可以通过在容器级别的资源需求中使用资源名称 hugepages-<size>
来使用巨页,其中的 size 是特定节点上支持的以整数值表示的最小二进制单位。
例如,如果一个节点支持 2048KiB 和 1048576KiB 页面大小,它将公开可调度的资源
hugepages-2Mi
和 hugepages-1Gi
。与 CPU 或内存不同,巨页不支持过量使用(overcommit)。
注意,在请求巨页资源时,还必须请求内存或 CPU 资源。
同一 Pod 的 spec 中可能会消耗不同尺寸的巨页。在这种情况下,它必须对所有挂载卷使用
medium: HugePages-<hugepagesize>
标识。
apiVersion : v1
kind : Pod
metadata :
name : huge-pages-example
spec :
containers :
- name : example
image : fedora:latest
command :
- sleep
- inf
volumeMounts :
- mountPath : /hugepages-2Mi
name : hugepage-2mi
- mountPath : /hugepages-1Gi
name : hugepage-1gi
resources :
limits :
hugepages-2Mi : 100Mi
hugepages-1Gi : 2Gi
memory : 100Mi
requests :
memory : 100Mi
volumes :
- name : hugepage-2mi
emptyDir :
medium : HugePages-2Mi
- name : hugepage-1gi
emptyDir :
medium : HugePages-1Gi
Pod 只有在请求同一大小的巨页时才使用 medium:HugePages
。
apiVersion : v1
kind : Pod
metadata :
name : huge-pages-example
spec :
containers :
- name : example
image : fedora:latest
command :
- sleep
- inf
volumeMounts :
- mountPath : /hugepages
name : hugepage
resources :
limits :
hugepages-2Mi : 100Mi
memory : 100Mi
requests :
memory : 100Mi
volumes :
- name : hugepage
emptyDir :
medium : HugePages
巨页的资源请求值必须等于其限制值。该条件在指定了资源限制,而没有指定请求的情况下默认成立。 巨页是被隔离在 pod 作用域的,因此每个容器在 spec 中都对 cgroup 沙盒有自己的限制。 巨页可用于 EmptyDir 卷,不过 EmptyDir 卷所使用的巨页数量不能够超出 Pod 请求的巨页数量。 通过带有 SHM_HUGETLB
的 shmget()
使用巨页的应用,必须运行在一个与
proc/sys/vm/hugetlb_shm_group
匹配的补充组下。 通过 ResourceQuota 资源,可以使用 hugepages-<size>
标记控制每个命名空间下的巨页使用量,
类似于使用 cpu
或 memory
来控制其他计算资源。 4.17 - 配置 kubelet 镜像凭据提供程序 配置 kubelet 的镜像凭据提供程序插件
特性状态: Kubernetes v1.24 [beta]
从 Kubernetes v1.20 开始,kubelet 可以使用 exec 插件动态获得针对某容器镜像库的凭据。
kubelet 使用 Kubernetes 版本化 API 通过标准输入输出(标准输入、标准输出和标准错误)
和 exec 插件通信。
这些插件允许 kubelet 动态请求容器仓库的凭据,而不是将静态凭据存储在磁盘上。
例如,插件可能会与本地元数据服务器通信,以获得 kubelet 正在拉取的镜像的短期凭据。
如果以下任一情况属实,你可能对此功能感兴趣:
需要调用云提供商的 API 来获得镜像库的身份验证信息。 凭据的到期时间很短,需要频繁请求新凭据。 将镜像库凭据存储在磁盘或者 imagePullSecret 是不可接受的。 准备开始 kubelet 镜像凭证提供程序在 v1.20 版本作为 Alpha 特性引入。
与其他 Alpha 功能一样,当前仅当在 kubelet 启用 KubeletCredentialProviders
特性门控时,该功能才能正常工作。 凭据提供程序 exec 插件的一种可用的实现。你可以构建自己的插件或使用云提供商提供的插件。 在节点上安装插件 凭据提供程序插件是将由 kubelet 运行的可执行二进制文件。
你需要确保插件可执行文件存在于你的集群的每个节点上,并存储在已知目录中。
稍后配置 kubelet 标志需要该目录。
配置 kubelet 为了使用这个特性,kubelet 需要设置以下两个标志:
--image-credential-provider-config
—— 凭据提供程序插件配置文件的路径。--image-credential-provider-bin-dir
—— 凭据提供程序插件二进制可执行文件所在目录的路径。kubelet 会读取通过 --image-credential-provider-config
设定的配置文件,
以确定应该为哪些容器镜像调用哪些 exec 插件。
如果你正在使用基于 ECR 的插件,
这里有个样例配置文件你可能最终会使用到:
apiVersion : kubelet.config.k8s.io/v1alpha1
kind : CredentialProviderConfig
# providers 是将由 kubelet 启用的凭证提供程序插件列表。
# 多个提供程序可能与单个镜像匹配,在这种情况下,来自所有提供程序的凭据将返回到 kubelet。
# 如果为单个镜像调用了多个提供程序,则返回结果会被合并。
# 如果提供程序返回重叠的身份验证密钥,则使用提供程序列表中较早的值。
providers :
# name 是凭据提供程序的必需名称。
# 它必须与 kubelet 看到的提供程序可执行文件的名称相匹配。
# 可执行文件必须在 kubelet 的 bin 目录中
# (由 --image-credential-provider-bin-dir 标志设置)。
- name : ecr
# matchImages 是一个必需的字符串列表,用于匹配镜像以确定是否应调用此提供程序。
# 如果其中一个字符串与 kubelet 请求的镜像相匹配,则该插件将被调用并有机会提供凭据。
# 镜像应包含注册域和 URL 路径。
#
# matchImages 中的每个条目都是一个模式字符串,可以选择包含端口和路径。
# 可以在域中使用通配符,但不能在端口或路径中使用。
# 支持通配符作为子域(例如 "*.k8s.io" 或 "k8s.*.io")和顶级域(例如 "k8s.*")。
# 还支持匹配部分子域,如 "app*.k8s.io"。
# 每个通配符只能匹配一个子域段,因此 "*.io" 不匹配 "*.k8s.io"。
#
# 当以下所有条件都为真时,镜像和 matchImage 之间存在匹配:
#
# - 两者都包含相同数量的域部分并且每个部分都匹配。
# - imageMatch 的 URL 路径必须是目标镜像 URL 路径的前缀。
# - 如果 imageMatch 包含端口,则该端口也必须在镜像中匹配。
#
# matchImages 的示例值:
#
# - 123456789.dkr.ecr.us-east-1.amazonaws.com
# - *.azurecr.io
# - gcr.io
# - *.*.registry.io
# - registry.io:8080/path
matchImages :
- "*.dkr.ecr.*.amazonaws.com"
- "*.dkr.ecr.*.amazonaws.cn"
- "*.dkr.ecr-fips.*.amazonaws.com"
- "*.dkr.ecr.us-iso-east-1.c2s.ic.gov"
- "*.dkr.ecr.us-isob-east-1.sc2s.sgov.gov"
# defaultCacheDuration 是插件将在内存中缓存凭据的默认持续时间。
# 如果插件响应中未提供缓存持续时间。此字段是必需的。
defaultCacheDuration : "12h"
# exec CredentialProviderRequest 的必需输入版本。
# 返回的 CredentialProviderResponse 必须使用与输入相同的编码版本。当前支持的值为:
# - credentialprovider.kubelet.k8s.io/v1alpha1
apiVersion : credentialprovider.kubelet.k8s.io/v1alpha1
# 执行命令时传递给命令的参数。
# 可选
args :
- get-credentials
# env 定义了额外的环境变量以暴露给进程。
# 这些与主机环境以及 client-go 用于将参数传递给插件的变量结合在一起。
# 可选
env :
- name : AWS_PROFILE
value : example_profile
providers
字段是 kubelet 所使用的已启用插件列表。每个条目都有几个必填字段:
name
:插件的名称,必须与传入--image-credential-provider-bin-dir
的目录中存在的可执行二进制文件的名称相匹配。matchImages
:字符串列表,用于匹配镜像以确定是否应调用此提供程序。
更多相关信息参见后文。defaultCacheDuration
:如果插件未指定缓存时长,kubelet 将在内存中缓存凭据的默认时长。apiVersion
:kubelet 和 exec 插件在通信时将使用的 API 版本。每个凭证提供程序也可以被赋予可选的参数和环境变量。
你可以咨询插件实现者以确定给定插件需要哪些参数和环境变量集。
kubelet 使用每个凭证提供程序的 matchImages
字段来确定是否应该为 Pod
正在使用的给定镜像调用插件。
matchImages
中的每个条目都是一个镜像模式字符串,可以选择包含端口和路径。
可以在域中使用通配符,但不能在端口或路径中使用。
支持通配符作为子域,如 *.k8s.io
或 k8s.*.io
,以及顶级域,如 k8s.*
。
还支持匹配部分子域,如 app*.k8s.io
。每个通配符只能匹配一个子域段,
因此 *.io
不匹配 *.k8s.io
。
当以下所有条件都为真时,镜像名称和 matchImage
条目之间存在匹配:
两者都包含相同数量的域部分并且每个部分都匹配。 匹配图片的 URL 路径必须是目标图片 URL 路径的前缀。 如果 imageMatch 包含端口,则该端口也必须在镜像中匹配。 matchImages
模式的一些示例值:
123456789.dkr.ecr.us-east-1.amazonaws.com
*.azurecr.io
gcr.io
*.*.registry.io
foo.registry.io:8080/path
接下来 4.18 - 用插件扩展 kubectl 通过创建和安装 kubectl 插件扩展 kubectl。
本指南演示了如何为 kubectl 安装和编写扩展。
通过将核心 kubectl
命令看作与 Kubernetes 集群交互的基本构建块,
集群管理员可以将插件视为一种利用这些构建块创建更复杂行为的方法。
插件用新的子命令扩展了 kubectl
,允许新的和自定义的特性不包括在 kubectl
的主要发行版中。
准备开始 你需要安装一个可用的 kubectl
可执行文件。
安装 kubectl 插件 插件是一个独立的可执行文件,名称以 kubectl-
开头。
要安装插件,将其可执行文件移动到 PATH
中的任何位置。
你也可以使用 Krew 来发现和安装开源的 kubectl 插件。
Krew 是一个由 Kubernetes SIG CLI 社区维护的插件管理器。
注意: Krew
插件索引 所维护的 kubectl 插件并未经过安全性审查。
你要了解安装和运行第三方插件的安全风险,因为它们本质上时是一些在你的机器上
运行的程序。
发现插件 kubectl
提供一个命令 kubectl plugin list
,用于搜索 PATH
查找有效的插件可执行文件。
执行此命令将遍历 PATH
中的所有文件。任何以 kubectl-
开头的可执行文件都将在这个命令的输出中以它们在
PATH
中出现的顺序 显示。
任何以 kubectl-
开头的文件如果不可执行
,都将包含一个警告。
对于任何相同的有效插件文件,都将包含一个警告。
你可以使用 Krew 从社区策划的插件索引
中发现和安装 kubectl
插件。
限制 目前无法创建覆盖现有 kubectl
命令的插件。
例如,创建一个插件 kubectl-version
将导致该插件永远不会被执行,
因为现有的 kubectl version
命令总是优先于它执行。
由于这个限制,也不可能使用插件将新的子命令添加到现有的 kubectl
命令中。
例如,通过将插件命名为 kubectl-create-foo
来添加子命令 kubectl create foo
将导致该插件被忽略。
对于任何试图这样做的有效插件 kubectl plugin list
的输出中将显示警告。
编写 kubectl 插件 你可以用任何编程语言或脚本编写插件,允许你编写命令行命令。
不需要安装插件或预加载,插件可执行程序从 kubectl
二进制文件接收继承的环境,
插件根据其名称确定它希望实现的命令路径。
例如,名为 kubectl-foo
的插件提供了命令 kubectl foo
。
必须将插件的可执行文件安装在 PATH
中的某个位置。
示例插件 #!/bin/bash
# 可选的参数处理
if [[ "$1" == "version" ]]
then
echo "1.0.0"
exit 0
fi
# 可选的参数处理
if [[ "$1" == "config" ]]
then
echo $KUBECONFIG
exit 0
fi
echo "I am a plugin named kubectl-foo"
使用插件 要使用某插件,先要使其可执行:
sudo chmod +x ./kubectl-foo
并将它放在你的 PATH
中的任何地方:
sudo mv ./kubectl-foo /usr/local/bin
你现在可以调用你的插件作为 kubectl
命令:
I am a plugin named kubectl-foo
所有参数和标记按原样传递给可执行文件:
1.0.0
所有环境变量也按原样传递给可执行文件:
export KUBECONFIG = ~/.kube/config
kubectl foo config
/home/<user>/.kube/config
KUBECONFIG = /etc/kube/config kubectl foo config
/etc/kube/config
此外,传递给插件的第一个参数总是调用它的位置的绝对路径(在上面的例子中,$0
将等于 /usr/local/bin/kubectl-foo
)。
命名插件 如上面的例子所示,插件根据文件名确定要实现的命令路径,插件所针对的命令路径中的每个子命令都由破折号(-
)分隔。
例如,当用户调用命令 kubectl foo bar baz
时,希望调用该命令的插件的文件名为 kubectl-foo-bar-baz
。
参数和标记处理 说明: 插件机制不会 为插件进程创建任何定制的、特定于插件的值或环境变量。
较老的插件机制会提供环境变量(例如 KUBECTL_PLUGINS_CURRENT_NAMESPACE
);这种机制已被废弃。
kubectl 插件必须解析并检查传递给它们的所有参数。
参阅使用命令行运行时包 了解针对
插件开发人员的 Go 库的细节。
这里是一些用户调用你的插件的时候提供额外标志和参数的场景。
这些场景时基于上述案例中的 kubectl-foo-bar-baz
插件的。
如果你运行 kubectl foo bar baz arg1 --flag=value arg2
,kubectl 的插件机制将首先尝试找到
最长可能名称的插件,在本例中是 kubectl-foo-bar-baz-arg1
。
当没有找到这个插件时,kubectl 就会将最后一个以破折号分隔的值视为参数(在本例中为 arg1
),
并尝试找到下一个最长的名称 kubectl-foo-bar-baz
。
在找到具有此名称的插件后,它将调用该插件,并在其名称之后将所有参数和标志传递给插件进程。
示例:
# 创建一个插件
echo -e '#!/bin/bash\n\necho "My first command-line argument was $1"' > kubectl-foo-bar-baz
sudo chmod +x ./kubectl-foo-bar-baz
# 将插件放到 PATH 下完成"安装"
sudo mv ./kubectl-foo-bar-baz /usr/local/bin
# 确保 kubectl 能够识别我们的插件
kubectl plugin list
The following kubectl-compatible plugins are available:
/usr/local/bin/kubectl-foo-bar-baz
# 测试通过 "kubectl" 命令来调用我们的插件时可行的
# 即使我们给插件传递一些额外的参数或标志
kubectl foo bar baz arg1 --meaningless-flag= true
My first command-line argument was arg1
正如你所看到的,你的插件是基于用户指定的 kubectl
命令找到的,
所有额外的参数和标记都是按原样传递给插件可执行文件的。
带有破折号和下划线的名称 虽然 kubectl
插件机制在插件文件名中使用破折号(-
)分隔插件处理的子命令序列,
但是仍然可以通过在文件名中使用下划线(_
)来创建命令行中包含破折号的插件命令。
例子:
# 创建文件名中包含下划线的插件
echo -e '#!/bin/bash\n\necho "I am a plugin with a dash in my name"' > ./kubectl-foo_bar
sudo chmod +x ./kubectl-foo_bar
# 将插件放到 PATH 下
sudo mv ./kubectl-foo_bar /usr/local/bin
# 现在可以通过 kubectl 来调用插件
kubectl foo-bar
I am a plugin with a dash in my name
请注意,在插件文件名中引入下划线并不会阻止你使用 kubectl foo_bar
之类的命令。
可以使用破折号(-
)或下划线(_
)调用上面示例中的命令:
# 我们的插件也可以用破折号来调用
kubectl foo-bar
I am a plugin with a dash in my name
# 你也可以使用下划线来调用我们的定制命令
kubectl foo_bar
I am a plugin with a dash in my name
命名冲突和弊端 可以在 PATH
的不同位置提供多个文件名相同的插件,
例如,给定一个 PATH
为: PATH=/usr/local/bin/plugins:/usr/local/bin/moreplugins
,
在 /usr/local/bin/plugins
和 /usr/local/bin/moreplugins
中可以存在一个插件
kubectl-foo
的副本,这样 kubectl plugin list
命令的输出就是:
PATH = /usr/local/bin/plugins:/usr/local/bin/moreplugins kubectl plugin list
The following kubectl-compatible plugins are available:
/usr/local/bin/plugins/kubectl-foo
/usr/local/bin/moreplugins/kubectl-foo
- warning: /usr/local/bin/moreplugins/kubectl-foo is overshadowed by a similarly named plugin: /usr/local/bin/plugins/kubectl-foo
error: one plugin warning was found
在上面的场景中 /usr/local/bin/moreplugins/kubectl-foo
下的警告告诉你这个插件永远不会被执行。
相反,首先出现在你 PATH
中的可执行文件 /usr/local/bin/plugins/kubectl-foo
总是首先被 kubectl
插件机制找到并执行。
解决这个问题的一种方法是你确保你希望与 kubectl
一起使用的插件的位置总是在你的 PATH
中首先出现。
例如,如果你总是想使用 /usr/local/bin/moreplugins/kubectl foo
,
那么在调用 kubectl
命令 kubectl foo
时,你只需将路径的值更改为 /usr/local/bin/moreplugins:/usr/local/bin/plugins
。
调用最长的可执行文件名 对于插件文件名而言还有另一种弊端,给定用户 PATH
中的两个插件 kubectl-foo-bar
和 kubectl-foo-bar-baz
,
kubectl
插件机制总是为给定的用户命令选择尽可能长的插件名称。下面的一些例子进一步的说明了这一点:
# 对于给定的 kubectl 命令,最长可能文件名的插件是被优先选择的
kubectl foo bar baz
Plugin kubectl-foo-bar-baz is executed
Plugin kubectl-foo-bar is executed
Plugin kubectl-foo-bar-baz is executed, with "buz" as its first argument
Plugin kubectl-foo-bar is executed, with "buz" as its first argument
这种设计选择确保插件子命令可以跨多个文件实现,如果需要,这些子命令可以嵌套在"父"插件命令下:
kubectl-parent
kubectl-parent-subcommand
kubectl-parent-subcommand-subsubcommand
检查插件警告 你可以使用前面提到的 kubectl plugin list
命令来确保你的插件可以被 kubectl
看到,
并且验证没有警告防止它被称为 kubectl
命令。
The following kubectl-compatible plugins are available:
test/fixtures/pkg/kubectl/plugins/kubectl-foo
/usr/local/bin/kubectl-foo
- warning: /usr/local/bin/kubectl-foo is overshadowed by a similarly named plugin: test/fixtures/pkg/kubectl/plugins/kubectl-foo
plugins/kubectl-invalid
- warning: plugins/kubectl-invalid identified as a kubectl plugin, but it is not executable
error: 2 plugin warnings were found
使用命令行运行时包 如果你在编写 kubectl 插件,而且你选择使用 Go 语言,你可以利用
cli-runtime 工具库。
这些库提供了一些辅助函数,用来解析和更新用户的
kubeconfig
文件,向 API 服务器发起 REST 风格的请求,或者将参数绑定到某配置上,
抑或将其打印输出。
关于 CLI Runtime 仓库所提供的工具的使用实例,可参考
CLI 插件示例 项目。
分发 kubectl 插件 如果你开发了一个插件给别人使用,你应该考虑如何为其封装打包、如何分发软件
以及将来的更新到用户。
Krew Krew 提供了一种对插件进行打包和分发的跨平台方式。
基于这种方式,你会在所有的目标平台(Linux、Windows、macOS 等)使用同一
种打包形式,包括为用户提供更新。
Krew 也维护一个插件索引(plugin index)
以便其他人能够发现你的插件并安装之。
原生的与特定平台的包管理 另一种方式是,你可以使用传统的包管理器(例如 Linux 上 的 apt
或 yum
,
Windows 上的 Chocolatey、macOs 上的 Homebrew)。
只要能够将新的可执行文件放到用户的 PATH
路径上某处,这种包管理器就符合需要。
作为一个插件作者,如果你选择这种方式来分发,你就需要自己来管理和更新
你的 kubectl 插件的分发包,包括所有平台和所有发行版本。
源代码 你也可以发布你的源代码,例如,发布为某个 Git 仓库。
如果你选择这条路线,希望使用该插件的用户必须取回代码、配置一个构造环境
(如果需要编译的话)并部署该插件。
如果你也提供编译后的软件包,或者使用 Krew,那就会大大简化安装过程了。
接下来 5 - 教程 Kubernetes 文档的这一部分包含教程。
每个教程展示了如何完成一个比单个任务 更大的目标。
通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前,
你可能希望将标准化术语表 页面添加到书签,供以后参考。
基础知识 配置 无状态应用程序 有状态应用程序 服务 安全 接下来 如果你要编写教程,请参阅内容页面类型
以获取有关教程页面类型的信息。
5.1 - 你好,Minikube 本教程向你展示如何使用 Minikube 和 Katacoda
在 Kubernetes 上运行一个应用示例。Katacoda 提供免费的浏览器内 Kubernetes 环境。
教程目标 将一个示例应用部署到 Minikube。 运行应用程序。 查看应用日志。 准备开始 本教程提供了容器镜像,使用 NGINX 来对所有请求做出回应:
创建 Minikube 集群 点击 Launch Terminal 。
Launch Terminal 说明: 如果你在本地安装了 Minikube,运行 minikube start
。
在运行 minikube dashboard
之前,你应该打开一个新终端,
在此启动 minikube dashboard
,然后切换回主终端。
在浏览器中打开 Kubernetes 仪表板(Dashboard):
仅限 Katacoda 环境:在终端窗口的顶部,单击加号,然后单击 Select port to view on Host 1 。 仅限 Katacoda 环境:输入 30000
,然后单击 Display Port 。 说明: dashboard
命令启用仪表板插件,并在默认的 Web 浏览器中打开代理。
你可以在仪表板上创建 Kubernetes 资源,例如 Deployment 和 Service。
如果你以 root 用户身份在环境中运行,
请参见使用 URL 打开仪表板 。
默认情况下,仪表板只能从内部 Kubernetes 虚拟网络中访问。
dashboard
命令创建一个临时代理,使仪表板可以从 Kubernetes 虚拟网络外部访问。
要停止代理,请运行 Ctrl+C
退出该进程。仪表板仍在运行中。
命令退出后,仪表板仍然在 Kubernetes 集群中运行。
你可以再次运行 dashboard
命令创建另一个代理来访问仪表板。
使用 URL 打开仪表板 如果你不想打开 Web 浏览器,请使用 --url
标志运行显示板命令以得到 URL:
创建 Deployment Kubernetes Pod
是由一个或多个为了管理和联网而绑定在一起的容器构成的组。本教程中的 Pod 只有一个容器。
Kubernetes Deployment
检查 Pod 的健康状况,并在 Pod 中的容器终止的情况下重新启动新的容器。
Deployment 是管理 Pod 创建和扩展的推荐方法。
使用 kubectl create
命令创建管理 Pod 的 Deployment。该 Pod 根据提供的 Docker
镜像运行 Container。
kubectl create deployment hello-node --image= registry.k8s.io/echoserver:1.4
查看 Deployment:
输出结果类似于这样:
NAME READY UP-TO-DATE AVAILABLE AGE
hello-node 1/1 1 1 1m
查看 Pod:
输出结果类似于这样:
NAME READY STATUS RESTARTS AGE
hello-node-5f76cf6ccf-br9b5 1/1 Running 0 1m
查看集群事件:
查看 kubectl
配置:
创建 Service 默认情况下,Pod 只能通过 Kubernetes 集群中的内部 IP 地址访问。
要使得 hello-node
容器可以从 Kubernetes 虚拟网络的外部访问,你必须将 Pod
暴露为 Kubernetes Service 。
使用 kubectl expose
命令将 Pod 暴露给公网:
kubectl expose deployment hello-node --type= LoadBalancer --port= 8080
这里的 --type=LoadBalancer
参数表明你希望将你的 Service 暴露到集群外部。
镜像 registry.k8s.io/echoserver
中的应用程序代码仅监听 TCP 8080 端口。
如果你用 kubectl expose
暴露了其它的端口,客户端将不能访问其它端口。
查看你创建的 Service:
输出结果类似于这样:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node LoadBalancer 10.108.144.78 <pending> 8080:30369/TCP 21s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23m
对于支持负载均衡器的云服务平台而言,平台将提供一个外部 IP 来访问该服务。
在 Minikube 上,LoadBalancer
使得服务可以通过命令 minikube service
访问。
运行下面的命令:
minikube service hello-node
仅限 Katacoda 环境:单击加号,然后单击 Select port to view on Host 1 。 仅限 Katacoda 环境:请注意在 service 输出中与 8080
对应的长度为 5 位的端口号。
此端口号是随机生成的,可能与你的不同。
在端口号文本框中输入你自己的端口号,然后单击 Display Port 。
对应于上面的例子,需要输入 30369
。
这将打开一个浏览器窗口,为你的应用程序提供服务并显示应用的响应。
启用插件 Minikube 有一组内置的 插件 ,
可以在本地 Kubernetes 环境中启用、禁用和打开。
列出当前支持的插件:
输出结果类似于这样:
addon-manager: enabled
dashboard: enabled
default-storageclass: enabled
efk: disabled
freshpod: disabled
gvisor: disabled
helm-tiller: disabled
ingress: disabled
ingress-dns: disabled
logviewer: disabled
metrics-server: disabled
nvidia-driver-installer: disabled
nvidia-gpu-device-plugin: disabled
registry: disabled
registry-creds: disabled
storage-provisioner: enabled
storage-provisioner-gluster: disabled
启用插件,例如 metrics-server
:
minikube addons enable metrics-server
输出结果类似于这样:
The 'metrics-server' addon is enabled
查看创建的 Pod 和 Service:
kubectl get pod,svc -n kube-system
输出结果类似于这样:
NAME READY STATUS RESTARTS AGE
pod/coredns-5644d7b6d9-mh9ll 1/1 Running 0 34m
pod/coredns-5644d7b6d9-pqd2t 1/1 Running 0 34m
pod/metrics-server-67fb648c5 1/1 Running 0 26s
pod/etcd-minikube 1/1 Running 0 34m
pod/influxdb-grafana-b29w8 2/2 Running 0 26s
pod/kube-addon-manager-minikube 1/1 Running 0 34m
pod/kube-apiserver-minikube 1/1 Running 0 34m
pod/kube-controller-manager-minikube 1/1 Running 0 34m
pod/kube-proxy-rnlps 1/1 Running 0 34m
pod/kube-scheduler-minikube 1/1 Running 0 34m
pod/storage-provisioner 1/1 Running 0 34m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/metrics-server ClusterIP 10.96.241.45 <none> 80/TCP 26s
service/kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 34m
service/monitoring-grafana NodePort 10.99.24.54 <none> 80:30002/TCP 26s
service/monitoring-influxdb ClusterIP 10.111.169.94 <none> 8083/TCP,8086/TCP 26s
禁用 metrics-server
:
minikube addons disable metrics-server
输出结果类似于这样:
metrics-server was successfully disabled
清理 现在可以清理你在集群中创建的资源:
kubectl delete service hello-node
kubectl delete deployment hello-node
可选地,停止 Minikube 虚拟机(VM):
可选地,删除 Minikube 虚拟机(VM):
接下来 5.2 - 学习 Kubernetes 基础知识 Kubernetes 基础 本教程介绍了 Kubernetes 集群编排系统的基础知识。每个模块包含关于 Kubernetes 主要特性和概念的一些背景信息,并包括一个在线互动教程。这些互动教程让你可以自己管理一个简单的集群及其容器化应用程序。
使用互动教程,你可以学习:
在集群上部署容器化应用程序 弹性部署 使用新的软件版本,更新容器化应用程序 调试容器化应用程序 教程 Katacoda 在你的浏览器中运行一个虚拟终端,在浏览器中运行 Minikube,这是一个可在任何地方小规模本地部署的 Kubernetes 集群。不需要安装任何软件或进行任何配置;每个交互性教程都直接从你的网页浏览器上运行。
Kubernetes 可以为你做些什么? 通过现代的 Web 服务,用户希望应用程序能够 24/7 全天候使用,开发人员希望每天可以多次发布部署新版本的应用程序。 容器化可以帮助软件包达成这些目标,使应用程序能够以简单快速的方式发布和更新,而无需停机。Kubernetes 帮助你确保这些容器化的应用程序在你想要的时间和地点运行,并帮助应用程序找到它们需要的资源和工具。Kubernetes 是一个可用于生产的开源平台,根据 Google 容器集群方面积累的经验,以及来自社区的最佳实践而设计。
5.2.1 - 创建集群 了解 Kubernetes 集群 并使用 Minikube
创建一个简单的集群。
5.2.1.1 - 使用 Minikube 创建集群 目标 了解 Kubernetes 集群。 了解 Minikube 。 使用在线终端开启一个 Kubernetes 集群。 Kubernetes 集群 Kubernetes 协调一个高可用计算机集群,每个计算机作为独立单元互相连接工作。 Kubernetes 中的抽象允许你将容器化的应用部署到集群,而无需将它们绑定到某个特定的独立计算机。为了使用这种新的部署模型,应用需要以将应用与单个主机分离的方式打包:它们需要被容器化。与过去的那种应用直接以包的方式深度与主机集成的部署模型相比,容器化应用更灵活、更可用。 Kubernetes 以更高效的方式跨集群自动分发和调度应用容器。 Kubernetes 是一个开源平台,并且可应用于生产环境。
一个 Kubernetes 集群包含两种类型的资源:
Master 调度整个集群Nodes 负责运行应用Kubernetes 是一个生产级别的开源平台,可协调在计算机集群内和跨计算机集群的应用容器的部署(调度)和执行.
Master 负责管理整个集群。 Master 协调集群中的所有活动,例如调度应用、维护应用的所需状态、应用扩容以及推出新的更新。
Node 是一个虚拟机或者物理机,它在 Kubernetes 集群中充当工作机器的角色 每个Node都有 Kubelet , 它管理 Node 而且是 Node 与 Master 通信的代理。 Node 还应该具有用于处理容器操作的工具,例如 Docker 或 rkt 。处理生产级流量的 Kubernetes 集群至少应具有三个 Node,因为如果一个 Node 出现故障其对应的 etcd 成员和控制平面实例都会丢失,并且冗余会受到影响。 你可以通过添加更多控制平面节点来降低这种风险 。
Master 管理集群,Node 用于托管正在运行的应用。
在 Kubernetes 上部署应用时,你告诉 Master 启动应用容器。 Master 就编排容器在集群的 Node 上运行。 Node 使用 Master 暴露的 Kubernetes API 与 Master 通信。 终端用户也可以使用 Kubernetes API 与集群交互。
Kubernetes 既可以部署在物理机上也可以部署在虚拟机上。你可以使用 Minikube 开始部署 Kubernetes 集群。 Minikube 是一种轻量级的 Kubernetes 实现,可在本地计算机上创建 VM 并部署仅包含一个节点的简单集群。 Minikube 可用于 Linux , macOS 和 Windows 系统。Minikube CLI 提供了用于引导集群工作的多种操作,包括启动、停止、查看状态和删除。在本教程里,你可以使用预装有 Minikube 的在线终端进行体验。
既然你已经知道 Kubernetes 是什么,让我们转到在线教程并启动我们的第一个 Kubernetes 集群!
5.2.2 - 部署应用 5.2.2.1 - 使用 kubectl 创建 Deployment 目标 学习了解应用的部署 使用 kubectl 在 Kubernetes 上部署第一个应用 Kubernetes 部署 一旦运行了 Kubernetes 集群,就可以在其上部署容器化应用程序。
为此,你需要创建 Kubernetes Deployment 配置。Deployment 指挥 Kubernetes 如何创建和更新应用程序的实例。创建 Deployment 后,Kubernetes master 将应用程序实例调度到集群中的各个节点上。
创建应用程序实例后,Kubernetes Deployment 控制器会持续监视这些实例。 如果托管实例的节点关闭或被删除,则 Deployment 控制器会将该实例替换为集群中另一个节点上的实例。 这提供了一种自我修复机制来解决机器故障维护问题。
在没有 Kubernetes 这种编排系统之前,安装脚本通常用于启动应用程序,但它们不允许从机器故障中恢复。通过创建应用程序实例并使它们在节点之间运行, Kubernetes Deployments 提供了一种与众不同的应用程序管理方法。
Deployment 负责创建和更新应用程序的实例
部署你在 Kubernetes 上的第一个应用程序 你可以使用 Kubernetes 命令行界面 Kubectl 创建和管理 Deployment。Kubectl 使用 Kubernetes API 与集群进行交互。在本单元中,你将学习创建在 Kubernetes 集群上运行应用程序的 Deployment 所需的最常见的 Kubectl 命令。
创建 Deployment 时,你需要指定应用程序的容器镜像以及要运行的副本数。你可以稍后通过更新 Deployment 来更改该信息; 模块 5 和 6 讨论了如何扩展和更新 Deployments。
应用程序需要打包成一种受支持的容器格式,以便部署在 Kubernetes 上
对于我们的第一次部署,我们将使用打包在 Docker 容器中的 Node.js 应用程序。
要创建 Node.js 应用程序并部署 Docker 容器,请按照
你好 Minikube 教程 .
现在你已经了解了 Deployment 的内容,让我们转到在线教程并部署我们的第一个应用程序!
5.2.3 - 了解你的应用 5.2.3.1 - 查看 pod 和工作节点 目标 了解 Kubernetes Pod。 了解 Kubernetes 工作节点。 对已部署的应用故障排除。 Kubernetes Pods 在模块 2 创建 Deployment 时, Kubernetes 添加了一个 Pod 来托管你的应用实例。Pod 是 Kubernetes 抽象出来的,表示一组一个或多个应用程序容器(如 Docker),以及这些容器的一些共享资源。这些资源包括:
共享存储,当作卷 网络,作为唯一的集群 IP 地址 有关每个容器如何运行的信息,例如容器镜像版本或要使用的特定端口。 Pod 为特定于应用程序的“逻辑主机”建模,并且可以包含相对紧耦合的不同应用容器。例如,Pod 可能既包含带有 Node.js 应用的容器,也包含另一个不同的容器,用于提供 Node.js 网络服务器要发布的数据。Pod 中的容器共享 IP 地址和端口,始终位于同一位置并且共同调度,并在同一工作节点上的共享上下文中运行。
Pod是 Kubernetes 平台上的原子单元。 当我们在 Kubernetes 上创建 Deployment 时,该 Deployment 会在其中创建包含容器的 Pod (而不是直接创建容器)。每个 Pod 都与调度它的工作节点绑定,并保持在那里直到终止(根据重启策略)或删除。 如果工作节点发生故障,则会在集群中的其他可用工作节点上调度相同的 Pod。
Pod 是一组一个或多个应用程序容器(例如 Docker),包括共享存储(卷), IP 地址和有关如何运行它们的信息。
工作节点 一个 pod 总是运行在 工作节点 。工作节点是 Kubernetes 中的参与计算的机器,可以是虚拟机或物理计算机,具体取决于集群。每个工作节点由主节点管理。工作节点可以有多个 pod ,Kubernetes 主节点会自动处理在集群中的工作节点上调度 pod 。 主节点的自动调度考量了每个工作节点上的可用资源。
每个 Kubernetes 工作节点至少运行:
Kubelet,负责 Kubernetes 主节点和工作节点之间通信的过程; 它管理 Pod 和机器上运行的容器。 容器运行时(如 Docker)负责从仓库中提取容器镜像,解压缩容器以及运行应用程序。 如果它们紧耦合并且需要共享磁盘等资源,这些容器应在一个 Pod 中编排。
使用 kubectl 进行故障排除 在模块 2 ,你使用了 Kubectl 命令行界面。 你将继续在第3单元中使用它来获取有关已部署的应用程序及其环境的信息。 最常见的操作可以使用以下 kubectl 命令完成:
kubectl get - 列出资源kubectl describe - 显示有关资源的详细信息kubectl logs - 打印 pod 和其中容器的日志kubectl exec - 在 pod 中的容器上执行命令你可以使用这些命令查看应用程序的部署时间,当前状态,运行位置以及配置。
现在我们了解了有关集群组件和命令行的更多信息,让我们来探索一下我们的应用程序。
工作节点是 Kubernetes 中的负责计算的机器,可能是VM或物理计算机,具体取决于集群。多个 Pod 可以在一个工作节点上运行。
5.2.4 - 公开地暴露你的应用 5.2.4.1 - 使用 Service 暴露你的应用 目标 了解 Kubernetes 中的 Service 了解 标签(Label) 和 标签选择器(Label Selector) 对象如何与 Service 关联 在 Kubernetes 集群外用 Service 暴露应用 Kubernetes Service 总览 Kubernetes Pod 是转瞬即逝的。 Pod 实际上拥有 生命周期 。 当一个工作 Node 挂掉后, 在 Node 上运行的 Pod 也会消亡。 ReplicaSet 会自动地通过创建新的 Pod 驱动集群回到目标状态,以保证应用程序正常运行。 换一个例子,考虑一个具有3个副本数的用作图像处理的后端程序。这些副本是可替换的; 前端系统不应该关心后端副本,即使 Pod 丢失或重新创建。也就是说,Kubernetes 集群中的每个 Pod (即使是在同一个 Node 上的 Pod )都有一个唯一的 IP 地址,因此需要一种方法自动协调 Pod 之间的变更,以便应用程序保持运行。
Kubernetes 中的服务(Service)是一种抽象概念,它定义了 Pod 的逻辑集和访问 Pod 的协议。Service 使从属 Pod 之间的松耦合成为可能。 和其他 Kubernetes 对象一样, Service 用 YAML (更推荐) 或者 JSON 来定义. Service 下的一组 Pod 通常由 LabelSelector (请参阅下面的说明为什么你可能想要一个 spec 中不包含selector
的服务)来标记。
尽管每个 Pod 都有一个唯一的 IP 地址,但是如果没有 Service ,这些 IP 不会暴露在集群外部。Service 允许你的应用程序接收流量。Service 也可以用在 ServiceSpec 标记type
的方式暴露
ClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<NodeIP>:<NodePort>
从集群外部访问 Service。是 ClusterIP 的超集。LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话),并为 Service 分配一个固定的外部IP。是 NodePort 的超集。ExternalName - 通过返回带有该名称的 CNAME 记录,使用任意名称(由 spec 中的externalName
指定)公开 Service。不使用代理。这种类型需要kube-dns
的v1.7或更高版本。更多关于不同 Service 类型的信息可以在使用源 IP 教程。 也请参阅 连接应用程序和 Service 。
另外,需要注意的是有一些 Service 的用例没有在 spec 中定义selector
。 一个没有selector
创建的 Service 也不会创建相应的端点对象。这允许用户手动将服务映射到特定的端点。没有 selector 的另一种可能是你严格使用type: ExternalName
来标记。
总结 将 Pod 暴露给外部通信 跨多个 Pod 的负载均衡 使用标签(Label) Kubernetes 的 Service 是一个抽象层,它定义了一组 Pod 的逻辑集,并为这些 Pod 支持外部流量暴露、负载平衡和服务发现。
Service 通过一组 Pod 路由通信。Service 是一种抽象,它允许 Pod 死亡并在 Kubernetes 中复制,而不会影响应用程序。在依赖的 Pod (如应用程序中的前端和后端组件)之间进行发现和路由是由Kubernetes Service 处理的。
Service 匹配一组 Pod 是使用 标签(Label)和选择器(Selector) , 它们是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。标签(Label)是附加在对象上的键/值对,可以以多种方式使用:
指定用于开发,测试和生产的对象 嵌入版本标签 使用 Label 将对象进行分类 你也可以在创建 Deployment 的同时用 --expose
创建一个 Service 。
标签(Label)可以在创建时或之后附加到对象上。他们可以随时被修改。现在使用 Service 发布我们的应用程序并添加一些 Label 。
5.2.5 - 缩放你的应用 5.2.5.1 - 运行应用程序的多个实例 扩缩应用程序 在之前的模块中,我们创建了一个 Deployment ,然后通过 Service 让其可以开放访问。Deployment 仅为跑这个应用程序创建了一个 Pod。 当流量增加时,我们需要扩容应用程序满足用户需求。
扩缩 是通过改变 Deployment 中的副本数量来实现的。
在运行 kubectl run 命令时,你可以通过设置 --replicas 参数来设置 Deployment 的副本数。
扩展 Deployment 将创建新的 Pods,并将资源调度请求分配到有可用资源的节点上,收缩 会将 Pods 数量减少至所需的状态。Kubernetes 还支持 Pods 的自动缩放 ,但这并不在本教程的讨论范围内。将 Pods 数量收缩到0也是可以的,但这会终止 Deployment 上所有已经部署的 Pods。
运行应用程序的多个实例需要在它们之间分配流量。服务 (Service)有一种负载均衡器类型,可以将网络流量均衡分配到外部可访问的 Pods 上。服务将会一直通过端点来监视 Pods 的运行,保证流量只分配到可用的 Pods 上。
扩缩是通过改变 Deployment 中的副本数量来实现的。
一旦有了多个应用实例,就可以没有宕机地滚动更新。我们将会在下面的模块中介绍这些。现在让我们使用在线终端来体验一下应用程序的扩缩过程。
5.2.6 - 更新你的应用 5.2.6.1 - 执行滚动更新 更新应用程序 用户希望应用程序始终可用,而开发人员则需要每天多次部署它们的新版本。在 Kubernetes 中,这些是通过滚动更新(Rolling Updates)完成的。 滚动更新 允许通过使用新的实例逐步更新 Pod 实例,零停机进行 Deployment 更新。新的 Pod 将在具有可用资源的节点上进行调度。
在前面的模块中,我们将应用程序扩展为运行多个实例。这是在不影响应用程序可用性的情况下执行更新的要求。默认情况下,更新期间不可用的 pod 的最大值和可以创建的新 pod 数都是 1。这两个选项都可以配置为(pod)数字或百分比。
在 Kubernetes 中,更新是经过版本控制的,任何 Deployment 更新都可以恢复到以前的(稳定)版本。
滚动更新允许通过使用新的实例逐步更新 Pod 实例从而实现 Deployments 更新,停机时间为零。
与应用程序扩展类似,如果 Deployment 是公开的,服务将在更新期间仅对可用的 pod 进行负载均衡。可用 Pod 是应用程序用户可用的实例。
滚动更新允许以下操作:
将应用程序从一个环境提升到另一个环境(通过容器镜像更新) 回滚到以前的版本 持续集成和持续交付应用程序,无需停机 如果 Deployment 是公开的,则服务将仅在更新期间对可用的 pod 进行负载均衡。
在下面的交互式教程中,我们将应用程序更新为新版本,并执行回滚。
5.3 - 配置
5.3.1 - 示例:配置 java 微服务 5.3.1.1 - 使用 MicroProfile、ConfigMaps、Secrets 实现外部化应用配置 在本教程中,你会学到如何以及为什么要实现外部化微服务应用配置。
具体来说,你将学习如何使用 Kubernetes ConfigMaps 和 Secrets 设置环境变量,
然后在 MicroProfile config 中使用它们。
准备开始 创建 Kubernetes ConfigMaps 和 Secrets 在 Kubernetes 中,为 docker 容器设置环境变量有几种不同的方式,比如:
Dockerfile、kubernetes.yml、Kubernetes ConfigMaps、和 Kubernetes Secrets。
在本教程中,你将学到怎么用后两个方式去设置你的环境变量,而环境变量的值将注入到你的微服务里。
使用 ConfigMaps 和 Secrets 的一个好处是他们能在多个容器间复用,
比如赋值给不同的容器中的不同环境变量。
ConfigMaps 是存储非机密键值对的 API 对象。
在互动教程中,你会学到如何用 ConfigMap 来保存应用名字。
ConfigMap 的更多信息,你可以在这里 找到文档。
Secrets 尽管也用来存储键值对,但区别于 ConfigMaps 的是:它针对机密/敏感数据,且存储格式为 Base64 编码。
secrets 的这种特性使得它适合于存储证书、密钥、令牌,上述内容你将在交互教程中实现。
Secrets 的更多信息,你可以在这里 找到文档。
从代码外部化配置 外部化应用配置之所以有用处,是因为配置常常根据环境的不同而变化。
为了实现此功能,我们用到了 Java 上下文和依赖注入(Contexts and Dependency Injection, CDI)、MicroProfile 配置。
MicroProfile config 是 MicroProfile 的功能特性,
是一组开放 Java 技术,用于开发、部署云原生微服务。
CDI 提供一套标准的依赖注入能力,使得应用程序可以由相互协作的、松耦合的 beans 组装而成。
MicroProfile Config 为 app 和微服务提供从各种来源,比如应用、运行时、环境,获取配置参数的标准方法。
基于来源定义的优先级,属性可以自动的合并到单独一组应用可以通过 API 访问到的属性。
CDI & MicroProfile 都会被用在互动教程中,
用来从 Kubernetes ConfigMaps 和 Secrets 获得外部提供的属性,并注入应用程序代码中。
很多开源框架、运行时支持 MicroProfile Config。
对于整个互动教程,你都可以使用开放的库、灵活的开源 Java 运行时,去构建并运行云原生的 apps 和微服务。
然而,任何 MicroProfile 兼容的运行时都可以用来做替代品。
教程目标 创建 Kubernetes ConfigMap 和 Secret 使用 MicroProfile Config 注入微服务配置 示例:使用 MicroProfile、ConfigMaps、Secrets 实现外部化应用配置 启动互动教程
5.3.1.2 - 互动教程 - 配置 java 微服务 5.3.2 - 使用 ConfigMap 来配置 Redis 这篇文档基于配置 Pod 以使用 ConfigMap
这个任务,提供了一个使用 ConfigMap 来配置 Redis 的真实案例。
教程目标 使用 Redis 配置的值创建一个 ConfigMap 创建一个 Redis Pod,挂载并使用创建的 ConfigMap 验证配置已经被正确应用 准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
真实世界的案例:使用 ConfigMap 来配置 Redis 按照下面的步骤,使用 ConfigMap 中的数据来配置 Redis 缓存。
首先创建一个配置模块为空的 ConfigMap:
cat <<EOF >./example-redis-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: example-redis-config
data:
redis-config: ""
EOF
应用上面创建的 ConfigMap 以及 Redis pod 清单:
kubectl apply -f example-redis-config.yaml
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml
检查 Redis pod 清单的内容,并注意以下几点:
由 spec.volumes[1]
创建一个名为 config
的卷。 spec.volumes[1].items[0]
下的 key
和 path
会将来自 example-redis-config
ConfigMap 中的 redis-config
密钥公开在 config
卷上一个名为 redis.conf
的文件中。然后 config
卷被 spec.containers[0].volumeMounts[1]
挂载在 /redis-master
。 这样做的最终效果是将上面 example-redis-config
配置中 data.redis-config
的数据作为 Pod 中的 /redis-master/redis.conf
公开。
apiVersion : v1
kind : Pod
metadata :
name : redis
spec :
containers :
- name : redis
image : redis:5.0.4
command :
- redis-server
- "/redis-master/redis.conf"
env :
- name : MASTER
value : "true"
ports :
- containerPort : 6379
resources :
limits :
cpu : "0.1"
volumeMounts :
- mountPath : /redis-master-data
name : data
- mountPath : /redis-master
name : config
volumes :
- name : data
emptyDir : {}
- name : config
configMap :
name : example-redis-config
items :
- key : redis-config
path : redis.conf
检查创建的对象:
kubectl get pod/redis configmap/example-redis-config
你应该可以看到以下输出:
NAME READY STATUS RESTARTS AGE
pod/redis 1/1 Running 0 8s
NAME DATA AGE
configmap/example-redis-config 1 14s
回顾一下,我们在 example-redis-config
ConfigMap 保留了空的 redis-config
键:
kubectl describe configmap/example-redis-config
你应该可以看到一个空的 redis-config
键:
Name: example-redis-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
redis-config:
使用 kubectl exec
进入 pod,运行 redis-cli
工具检查当前配置:
kubectl exec -it redis -- redis-cli
查看 maxmemory
:
127.0.0.1:6379> CONFIG GET maxmemory
它应该显示默认值 0:
同样,查看 maxmemory-policy
:
127.0.0.1:6379> CONFIG GET maxmemory-policy
它也应该显示默认值 noeviction
:
1) "maxmemory-policy"
2) "noeviction"
现在,向 example-redis-config
ConfigMap 添加一些配置:
apiVersion : v1
kind : ConfigMap
metadata :
name : example-redis-config
data :
redis-config : |
maxmemory 2mb
maxmemory-policy allkeys-lru
应用更新的 ConfigMap:
kubectl apply -f example-redis-config.yaml
确认 ConfigMap 已更新:
kubectl describe configmap/example-redis-config
你应该可以看到我们刚刚添加的配置:
Name: example-redis-config
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
redis-config:
----
maxmemory 2mb
maxmemory-policy allkeys-lru
通过 kubectl exec
使用 redis-cli
再次检查 Redis Pod,查看是否已应用配置:
kubectl exec -it redis -- redis-cli
查看 maxmemory
:
127.0.0.1:6379> CONFIG GET maxmemory
它保持默认值 0:
同样,maxmemory-policy
保留为默认设置 noeviction
:
127.0.0.1:6379> CONFIG GET maxmemory-policy
返回:
1) "maxmemory-policy"
2) "noeviction"
配置值未更改,因为需要重新启动 Pod 才能从关联的 ConfigMap 中获取更新的值。
让我们删除并重新创建 Pod:
kubectl delete pod redis
kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/pods/config/redis-pod.yaml
现在,最后一次重新检查配置值:
kubectl exec -it redis -- redis-cli
查看 maxmemory
:
127.0.0.1:6379> CONFIG GET maxmemory
现在,它应该返回更新后的值 2097152:
1) "maxmemory"
2) "2097152"
同样,maxmemory-policy
也已更新:
127.0.0.1:6379> CONFIG GET maxmemory-policy
现在它反映了期望值 allkeys-lru
:
1) "maxmemory-policy"
2) "allkeys-lru"
删除创建的资源,清理你的工作:
kubectl delete pod/redis configmap/example-redis-config
接下来
5.4 - 安全 5.4.1 - 使用 AppArmor 限制容器对资源的访问 特性状态: Kubernetes v1.4 [beta]
AppArmor 是一个 Linux 内核安全模块,
它补充了基于标准 Linux 用户和组的权限,将程序限制在一组有限的资源中。
AppArmor 可以配置为任何应用程序减少潜在的攻击面,并且提供更加深入的防御。
它通过调整配置文件进行配置,以允许特定程序或容器所需的访问,
如 Linux 权能字、网络访问、文件权限等。
每个配置文件都可以在 强制(enforcing)
模式(阻止访问不允许的资源)或 投诉(complain) 模式(仅报告冲突)下运行。
AppArmor 可以通过限制允许容器执行的操作,
和/或通过系统日志提供更好的审计来帮助你运行更安全的部署。
但是,重要的是要记住 AppArmor 不是灵丹妙药,
只能做部分事情来防止应用程序代码中的漏洞。
提供良好的限制性配置文件,并从其他角度强化你的应用程序和集群非常重要。
教程目标 查看如何在节点上加载配置文件示例 了解如何在 Pod 上强制执行配置文件 了解如何检查配置文件是否已加载 查看违反配置文件时会发生什么 查看无法加载配置文件时会发生什么 准备开始 确保:
Kubernetes 版本至少是 v1.4 —— AppArmor 在 Kubernetes v1.4 版本中才添加了对 AppArmor 的支持。
早于 v1.4 版本的 Kubernetes 组件不知道新的 AppArmor
注解并且将会 默认忽略 提供的任何 AppArmor 设置。
为了确保你的 Pod 能够得到预期的保护,必须验证节点的 Kubelet 版本:
kubectl get nodes -o= jsonpath = $'{range .items[*]}{@.metadata.name}: {@.status.nodeInfo.kubeletVersion}\n{end}'
gke-test-default-pool-239f5d02-gyn2: v1.4.0
gke-test-default-pool-239f5d02-x1kf: v1.4.0
gke-test-default-pool-239f5d02-xwux: v1.4.0
AppArmor 内核模块已启用 —— 要使 Linux 内核强制执行 AppArmor 配置文件,
必须安装并且启动 AppArmor 内核模块。默认情况下,有几个发行版支持该模块,
如 Ubuntu 和 SUSE,还有许多发行版提供可选支持。要检查模块是否已启用,请检查
/sys/module/apparmor/parameters/enabled
文件:
cat /sys/module/apparmor/parameters/enabled
Y
如果 Kubelet 包含 AppArmor 支持(>= v1.4),
但是内核模块未启用,它将拒绝运行带有 AppArmor 选项的 Pod。
说明: Ubuntu 携带了许多没有合并到上游 Linux 内核中的 AppArmor 补丁,
包括添加附加钩子和特性的补丁。Kubernetes 只在上游版本中测试过,不承诺支持其他特性。
容器运行时支持 AppArmor —— 目前所有常见的 Kubernetes 支持的容器运行时都应该支持 AppArmor,
像 Docker 、CRI-O
或 containerd 。
请参考相应的运行时文档并验证集群是否满足使用 AppArmor 的要求。 配置文件已加载 —— 通过指定每个容器都应使用的 AppArmor 配置文件,
AppArmor 会被应用到 Pod 上。如果指定的任何配置文件尚未加载到内核,
Kubelet(>= v1.4)将拒绝 Pod。
通过检查 /sys/kernel/security/apparmor/profiles
文件,
可以查看节点加载了哪些配置文件。例如:
ssh gke-test-default-pool-239f5d02-gyn2 "sudo cat /sys/kernel/security/apparmor/profiles | sort"
apparmor-test-deny-write (enforce)
apparmor-test-audit-write (enforce)
docker-default (enforce)
k8s-nginx (enforce)
有关在节点上加载配置文件的详细信息,请参见使用配置文件设置节点 。
只要 Kubelet 版本包含 AppArmor 支持(>=v1.4),
如果不满足这些先决条件,Kubelet 将拒绝带有 AppArmor 选项的 Pod。
你还可以通过检查节点就绪状况消息来验证节点上的 AppArmor 支持(尽管这可能会在以后的版本中删除):
kubectl get nodes -o= jsonpath = '{range .items[*]}{@.metadata.name}: {.status.conditions[?(@.reason=="KubeletReady")].message}{"\n"}{end}'
gke-test-default-pool-239f5d02-gyn2: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-x1kf: kubelet is posting ready status. AppArmor enabled
gke-test-default-pool-239f5d02-xwux: kubelet is posting ready status. AppArmor enabled
保护 Pod 说明: AppArmor 目前处于 Beta 阶段,因此选项以注解形式设定。
一旦 AppArmor 支持进入正式发布阶段,注解将被替换为一阶的资源字段
(更多详情参见升级到 GA 的途径 )。
AppArmor 配置文件是按 逐个容器 的形式来设置的。
要指定用来运行 Pod 容器的 AppArmor 配置文件,请向 Pod 的 metadata 添加注解:
container.apparmor.security.beta.kubernetes.io/<container_name> : <profile_ref>
<container_name>
的名称是配置文件所针对的容器的名称,<profile_def>
则设置要应用的配置文件。
<profile_ref>
可以是以下取值之一:
runtime/default
应用运行时的默认配置localhost/<profile_name>
应用在主机上加载的名为 <profile_name>
的配置文件unconfined
表示不加载配置文件有关注解和配置文件名称格式的详细信息,请参阅 API 参考 。
Kubernetes AppArmor 强制执行机制首先检查所有先决条件都已满足,
然后将所选的配置文件转发到容器运行时进行强制执行。
如果未满足先决条件,Pod 将被拒绝,并且不会运行。
要验证是否应用了配置文件,可以在容器创建事件中查找所列出的 AppArmor 安全选项:
kubectl get events | grep Created
22s 22s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet e2e-test-stclair-node-pool-31nt} Created container with docker id 269a53b202d3; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
你还可以通过检查容器的 proc attr,直接验证容器的根进程是否以正确的配置文件运行:
kubectl exec <pod_name> -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
举例 本例假设你已经设置了一个集群使用 AppArmor 支持。
首先,我们需要将要使用的配置文件加载到节点上。配置文件拒绝所有文件写入:
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags =( attach_disconnected) {
#include <abstractions/base>
file,
# 拒绝所有文件写入
deny /** w,
}
由于我们不知道 Pod 将被调度到哪里,我们需要在所有节点上加载配置文件。
在本例中,我们将使用 SSH 来安装概要文件,
但是在使用配置文件设置节点 中讨论了其他方法。
NODES =(
# 你的节点的可通过 SSH 访问的域名
gke-test-default-pool-239f5d02-gyn2.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-x1kf.us-central1-a.my-k8s
gke-test-default-pool-239f5d02-xwux.us-central1-a.my-k8s)
for NODE in ${ NODES [*]} ; do ssh $NODE 'sudo apparmor_parser -q <<EOF
#include <tunables/global>
profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
# Deny all file writes.
deny /** w,
}
EOF'
done
接下来,我们将运行一个带有拒绝写入配置文件的简单 “Hello AppArmor” Pod:
apiVersion : v1
kind : Pod
metadata :
name : hello-apparmor
annotations :
# 告知 Kubernetes 去应用 AppArmor 配置 "k8s-apparmor-example-deny-write"。
# 请注意,如果节点上运行的 Kubernetes 不是 1.4 或更高版本,此注解将被忽略。
container.apparmor.security.beta.kubernetes.io/hello : localhost/k8s-apparmor-example-deny-write
spec :
containers :
- name : hello
image : busybox:1.28
command : [ "sh" , "-c" , "echo 'Hello AppArmor!' && sleep 1h" ]
kubectl create -f ./hello-apparmor.yaml
如果我们查看 Pod 事件,我们可以看到 Pod 容器是用 AppArmor
配置文件 “k8s-apparmor-example-deny-write” 所创建的:
kubectl get events | grep hello-apparmor
14s 14s 1 hello-apparmor Pod Normal Scheduled {default-scheduler } Successfully assigned hello-apparmor to gke-test-default-pool-239f5d02-gyn2
14s 14s 1 hello-apparmor Pod spec.containers{hello} Normal Pulling {kubelet gke-test-default-pool-239f5d02-gyn2} pulling image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Pulled {kubelet gke-test-default-pool-239f5d02-gyn2} Successfully pulled image "busybox"
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Created {kubelet gke-test-default-pool-239f5d02-gyn2} Created container with docker id 06b6cd1c0989; Security:[seccomp=unconfined apparmor=k8s-apparmor-example-deny-write]
13s 13s 1 hello-apparmor Pod spec.containers{hello} Normal Started {kubelet gke-test-default-pool-239f5d02-gyn2} Started container with docker id 06b6cd1c0989
我们可以通过检查该配置文件的 proc attr 来验证容器是否实际使用该配置文件运行:
kubectl exec hello-apparmor -- cat /proc/1/attr/current
k8s-apparmor-example-deny-write (enforce)
最后,我们可以看到,如果我们尝试通过写入文件来违反配置文件会发生什么:
kubectl exec hello-apparmor -- touch /tmp/test
touch: /tmp/test: Permission denied
error: error executing remote command: command terminated with non-zero exit code: Error executing in Docker Container: 1
最后,让我们看看如果我们试图指定一个尚未加载的配置文件会发生什么:
kubectl create -f /dev/stdin <<EOF
apiVersion : v1
kind : Pod
metadata :
name : hello-apparmor-2
annotations :
container.apparmor.security.beta.kubernetes.io/hello : localhost/k8s-apparmor-example-allow-write
spec :
containers :
- name : hello
image : busybox:1.28
command : [ "sh" , "-c" , "echo 'Hello AppArmor!' && sleep 1h" ]
EOF
pod/hello-apparmor-2 created
kubectl describe pod hello-apparmor-2
Name: hello-apparmor-2
Namespace: default
Node: gke-test-default-pool-239f5d02-x1kf/
Start Time: Tue, 30 Aug 2016 17:58:56 -0700
Labels: <none>
Annotations: container.apparmor.security.beta.kubernetes.io/hello=localhost/k8s-apparmor-example-allow-write
Status: Pending
Reason: AppArmor
Message: Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
IP:
Controllers: <none>
Containers:
hello:
Container ID:
Image: busybox
Image ID:
Port:
Command:
sh
-c
echo 'Hello AppArmor!' && sleep 1h
State: Waiting
Reason: Blocked
Ready: False
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-dnz7v (ro)
Conditions:
Type Status
Initialized True
Ready False
PodScheduled True
Volumes:
default-token-dnz7v:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-dnz7v
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
23s 23s 1 {default-scheduler } Normal Scheduled Successfully assigned hello-apparmor-2 to e2e-test-stclair-node-pool-t1f5
23s 23s 1 {kubelet e2e-test-stclair-node-pool-t1f5} Warning AppArmor Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
注意 Pod 呈现 Pending 状态,并且显示一条有用的错误信息:
Pod Cannot enforce AppArmor: profile "k8s-apparmor-example-allow-write" is not loaded
。
还用相同的消息记录了一个事件。
管理 使用配置文件设置节点 Kubernetes 目前不提供任何本地机制来将 AppArmor 配置文件加载到节点上。
有很多方法可以设置配置文件,例如:
通过在每个节点上运行 Pod 的
DaemonSet 来确保加载了正确的配置文件。
可以在这里 找到实现示例。 在节点初始化时,使用节点初始化脚本(例如 Salt、Ansible 等)或镜像。 通过将配置文件复制到每个节点并通过 SSH 加载它们,如示例 。 调度程序不知道哪些配置文件加载到哪个节点上,因此必须将全套配置文件加载到每个节点上。
另一种方法是为节点上的每个配置文件(或配置文件类)添加节点标签,
并使用节点选择器 确保
Pod 在具有所需配置文件的节点上运行。
禁用 AppArmor 如果你不希望 AppArmor 在集群上可用,可以通过命令行标志禁用它:
--feature-gates=AppArmor=false
禁用时,任何包含 AppArmor 配置文件的 Pod 都将导致验证失败,且返回 “Forbidden” 错误。
说明: 即使此 Kubernetes 特性被禁用,运行时仍可能强制执行默认配置文件。
当 AppArmor 升级为正式版 (GA) 时,禁用 AppArmor 功能的选项将被删除。
编写配置文件 获得正确指定的 AppArmor 配置文件可能是一件棘手的事情。幸运的是,有一些工具可以帮助你做到这一点:
aa-genprof
和 aa-logprof
通过监视应用程序的活动和日志并准许它所执行的操作来生成配置文件规则。
AppArmor 文档 提供了进一步的指导。bane
是一个用于 Docker的 AppArmor 配置文件生成器,它使用一种简化的画像语言(profile language)。想要调试 AppArmor 的问题,你可以检查系统日志,查看具体拒绝了什么。
AppArmor 将详细消息记录到 dmesg
,
错误通常可以在系统日志中或通过 journalctl
找到。
更多详细信息参见 AppArmor 失败 。
API 参考 Pod 注解 指定容器将使用的配置文件:
键名 :container.apparmor.security.beta.kubernetes.io/<container_name>
,
其中 <container_name>
与 Pod 中某容器的名称匹配。
可以为 Pod 中的每个容器指定单独的配置文件。键值 :对配置文件的引用,如下所述配置文件引用 runtime/default
:指默认运行时配置文件。localhost/<profile_name>
:按名称引用加载到节点(localhost)上的配置文件。unconfined
:这相当于为容器禁用 AppArmor。任何其他配置文件引用格式无效。
接下来 其他资源:
5.4.2 - 在集群级别应用 Pod 安全标准 Pod 安全准入(PSA)在 v1.23 及更高版本默认启用,
因为它已进阶为 Beta 。
Pod 安全准入是在创建 Pod 时应用
Pod 安全标准 的准入控制器。
本教程将向你展示如何在集群级别实施 baseline
Pod 安全标准,
该标准将标准配置应用于集群中的所有名字空间。
要将 Pod 安全标准应用于特定名字空间,
请参阅在名字空间级别应用 Pod 安全标准 。
如果你正在运行 v1.25 以外的 Kubernetes 版本,
请查阅该版本的文档。
准备开始 在你的工作站中安装以下内容:
正确选择要应用的 Pod 安全标准 Pod 安全准入
允许你使用以下模式应用内置的
Pod 安全标准 :
enforce
、audit
和 warn
。
要收集信息以便选择最适合你的配置的 Pod 安全标准,请执行以下操作:
创建一个没有应用 Pod 安全标准的集群:
kind create cluster --name psa-wo-cluster-pss --image kindest/node:v1.24.0
输出类似于:
Creating cluster "psa-wo-cluster-pss" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-wo-cluster-pss"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-wo-cluster-pss
Thanks for using kind! 😊
将 kubectl 上下文设置为新集群:
kubectl cluster-info --context kind-psa-wo-cluster-pss
输出类似于:
Kubernetes control plane is running at https://127.0.0.1:61350
CoreDNS is running at https://127.0.0.1:61350/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
获取集群中的名字空间列表:
输出类似于:
NAME STATUS AGE
default Active 9m30s
kube-node-lease Active 9m32s
kube-public Active 9m32s
kube-system Active 9m32s
local-path-storage Active 9m26s
使用 --dry-run=server
来了解应用不同的 Pod 安全标准时会发生什么:
Privileged
kubectl label --dry-run= server --overwrite ns --all \
pod-security.kubernetes.io/enforce= privileged
输出类似于:
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
namespace/kube-system labeled
namespace/local-path-storage labeled
Baseline
kubectl label --dry-run= server --overwrite ns --all \
pod-security.kubernetes.io/enforce= baseline
输出类似于:
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "baseline:latest"
Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes
Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes
Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged
namespace/kube-system labeled
namespace/local-path-storage labeled
Restricted
kubectl label --dry-run= server --overwrite ns --all \
pod-security.kubernetes.io/enforce= restricted
输出类似于:
namespace/default labeled
namespace/kube-node-lease labeled
namespace/kube-public labeled
Warning: existing pods in namespace "kube-system" violate the new PodSecurity enforce level "restricted:latest"
Warning: coredns-7bb9c7b568-hsptc (and 1 other pod): unrestricted capabilities, runAsNonRoot != true, seccompProfile
Warning: etcd-psa-wo-cluster-pss-control-plane (and 3 other pods): host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true
Warning: kindnet-vzj42: non-default capabilities, host namespaces, hostPath volumes, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
Warning: kube-proxy-m6hwf: host namespaces, hostPath volumes, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, restricted volume types, runAsNonRoot != true, seccompProfile
namespace/kube-system labeled
Warning: existing pods in namespace "local-path-storage" violate the new PodSecurity enforce level "restricted:latest"
Warning: local-path-provisioner-d6d9f7ffc-lw9lh: allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true, seccompProfile
namespace/local-path-storage labeled
从前面的输出中,你会注意到应用 privileged
Pod 安全标准不会显示任何名字空间的警告。
然而,baseline
和 restricted
标准都有警告,特别是在 kube-system
名字空间中。
设置模式、版本和标准 在本节中,你将以下 Pod 安全标准应用于最新(latest
)版本:
在 enforce
模式下的 baseline
标准。 warn
和 audit
模式下的 restricted
标准。baseline
Pod 安全标准提供了一个方便的中间立场,能够保持豁免列表简短并防止已知的特权升级。
此外,为了防止 kube-system
中的 Pod 失败,你将免除该名字空间应用 Pod 安全标准。
在你自己的环境中实施 Pod 安全准入时,请考虑以下事项:
根据应用于集群的风险状况,更严格的 Pod 安全标准(如 restricted
)可能是更好的选择。
对 kube-system
名字空间进行赦免会允许 Pod 在其中以 privileged
模式运行。
对于实际使用,Kubernetes 项目强烈建议你应用严格的 RBAC 策略来限制对 kube-system
的访问,
遵循最小特权原则。
创建一个配置文件,Pod 安全准入控制器可以使用该文件来实现这些 Pod 安全标准:
mkdir -p /tmp/pss
cat <<EOF > /tmp/pss/cluster-level-pss.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces: [kube-system]
EOF
说明: pod-security.admission.config.k8s.io/v1
配置需要 v1.25+。
对于 v1.23 和 v1.24,使用 v1beta1 。
对于 v1.22,使用 v1alpha1 。
在创建集群时配置 API 服务器使用此文件:
cat <<EOF > /tmp/pss/cluster-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: ClusterConfiguration
apiServer:
extraArgs:
admission-control-config-file: /etc/config/cluster-level-pss.yaml
extraVolumes:
- name: accf
hostPath: /etc/config
mountPath: /etc/config
readOnly: false
pathType: "DirectoryOrCreate"
extraMounts:
- hostPath: /tmp/pss
containerPath: /etc/config
# optional: if set, the mount is read-only.
# default false
readOnly: false
# optional: if set, the mount needs SELinux relabeling.
# default false
selinuxRelabel: false
# optional: set propagation mode (None, HostToContainer or Bidirectional)
# see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation
# default None
propagation: None
EOF
说明: 如果你在 macOS 上使用 Docker Desktop 和 KinD,
你可以在菜单项 Preferences > Resources > File Sharing
下添加 /tmp
作为共享目录。
创建一个使用 Pod 安全准入的集群来应用这些 Pod 安全标准:
kind create cluster --name psa-with-cluster-pss --image kindest/node:v1.24.0 --config /tmp/pss/cluster-config.yaml
输出类似于:
Creating cluster "psa-with-cluster-pss" ...
✓ Ensuring node image (kindest/node:v1.24.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-with-cluster-pss"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-with-cluster-pss
Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂
将 kubectl 指向集群
kubectl cluster-info --context kind-psa-with-cluster-pss
输出类似于:
Kubernetes control plane is running at https://127.0.0.1:63855
CoreDNS is running at https://127.0.0.1:63855/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
创建以下 Pod 规约作为在 default 名字空间中的一个最小配置:
cat <<EOF > /tmp/pss/nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
EOF
在集群中创建 Pod:
kubectl apply -f /tmp/pss/nginx-pod.yaml
输出类似于:
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/nginx created
清理 运行 kind delete cluster --name psa-with-cluster-pss
和
kind delete cluster --name psa-wo-cluster-pss
来删除你创建的集群。
接下来 5.4.3 - 在名字空间级别应用 Pod 安全标准 Pod 安全准入(PSA)在 v1.23 及更高版本默认启用,
因为它升级到测试版(beta) 。
Pod 安全准入是在创建 Pod 时应用
Pod 安全标准 的准入控制器。
在本教程中,你将应用 baseline
Pod 安全标准,每次一个名字空间。
你还可以在集群级别一次将 Pod 安全标准应用于多个名称空间。
有关说明,请参阅在集群级别应用 Pod 安全标准 。
准备开始 在你的工作站中安装以下内容:
创建集群 按照如下方式创建一个 KinD
集群:
kind create cluster --name psa-ns-level --image kindest/node:v1.23.0
输出类似于:
Creating cluster "psa-ns-level" ...
✓ Ensuring node image (kindest/node:v1.23.0) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-psa-ns-level"
You can now use your cluster with:
kubectl cluster-info --context kind-psa-ns-level
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
将 kubectl 上下文设置为新集群:
kubectl cluster-info --context kind-psa-ns-level
输出类似于:
Kubernetes control plane is running at https://127.0.0.1:50996
CoreDNS is running at https://127.0.0.1:50996/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
创建名字空间 创建一个名为 example
的新名字空间:
kubectl create ns example
输出类似于:
namespace/example created
应用 Pod 安全标准 使用内置 Pod 安全准入所支持的标签在此名字空间上启用 Pod 安全标准。
在这一步中,我们将根据最新版本(默认值)对基线 Pod 安全标准发出警告。
kubectl label --overwrite ns example \
pod-security.kubernetes.io/warn= baseline \
pod-security.kubernetes.io/warn-version= latest
可以使用标签在任何名字空间上启用多个 Pod 安全标准。
以下命令将强制(enforce
) 执行基线(baseline
)Pod 安全标准,
但根据最新版本(默认值)对受限(restricted
)Pod 安全标准执行警告(warn
)和审核(audit
)。
kubectl label --overwrite ns example \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
验证 Pod 安全标准 在 example
名字空间中创建一个最小的 Pod:
cat <<EOF > /tmp/pss/nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
EOF
将 Pod 规约应用到集群中的 example
名字空间中:
kubectl apply -n example -f /tmp/pss/nginx-pod.yaml
输出类似于:
Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "nginx" must set securityContext allowPrivilegeEscalation=false), unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "nginx" must set securityContext seccompProfile.type to "RuntimeDefault" or "Localhost")
pod/nginx created
将 Pod 规约应用到集群中的 default
名字空间中:
kubectl apply -n default -f /tmp/pss/nginx-pod.yaml
输出类似于:
pod/nginx created
以上 Pod 安全标准仅被应用到 example
名字空间。
你可以在没有警告的情况下在 default
名字空间中创建相同的 Pod。
清理 运行 kind delete cluster --name psa-ns-level
删除创建的集群。
接下来 5.4.4 - 使用 seccomp 限制容器的系统调用 特性状态: Kubernetes v1.19 [stable]
Seccomp 代表安全计算(Secure Computing)模式,自 2.6.12 版本以来,一直是 Linux 内核的一个特性。
它可以用来沙箱化进程的权限,限制进程从用户态到内核态的调用。
Kubernetes 能使你自动将加载到 节点 上的
seccomp 配置文件应用到你的 Pod 和容器。
识别你的工作负载所需要的权限是很困难的。在本篇教程中,
你将了解如何将 seccomp 配置文件加载到本地的 Kubernetes 集群中,
如何将它们应用到 Pod,以及如何开始制作只为容器进程提供必要的权限的配置文件。
教程目标 了解如何在节点上加载 seccomp 配置文件 了解如何将 seccomp 配置文件应用到容器上 观察容器进程对系统调用的审计 观察指定的配置文件缺失时的行为 观察违反 seccomp 配置文件的行为 了解如何创建细粒度的 seccomp 配置文件 了解如何应用容器运行时所默认的 seccomp 配置文件 准备开始 为了完成本篇教程中的所有步骤,你必须安装 kind
和 kubectl 。
本篇教程演示的某些示例仍然是 Beta 状态(自 v1.25 起),另一些示例则仅使用 seccomp 正式发布的功能。
你应该确保,针对你使用的版本,
正确配置 了集群。
本篇教程也使用了 curl
工具来下载示例到你的计算机上。
你可以使用其他自己偏好的工具来自适应这些步骤。
说明: 无法将 seccomp 配置文件应用于在容器的 securityContext
中设置了 privileged: true
的容器。
特权容器始终以 Unconfined
的方式运行。
下载示例 seccomp 配置文件 这些配置文件的内容将在稍后进行分析,
现在先将它们下载到名为 profiles/
的目录中,以便将它们加载到集群中。
{
"defaultAction" : "SCMP_ACT_LOG"
}
{
"defaultAction" : "SCMP_ACT_ERRNO"
}
{
"defaultAction" : "SCMP_ACT_ERRNO" ,
"architectures" : [
"SCMP_ARCH_X86_64" ,
"SCMP_ARCH_X86" ,
"SCMP_ARCH_X32"
],
"syscalls" : [
{
"names" : [
"accept4" ,
"epoll_wait" ,
"pselect6" ,
"futex" ,
"madvise" ,
"epoll_ctl" ,
"getsockname" ,
"setsockopt" ,
"vfork" ,
"mmap" ,
"read" ,
"write" ,
"close" ,
"arch_prctl" ,
"sched_getaffinity" ,
"munmap" ,
"brk" ,
"rt_sigaction" ,
"rt_sigprocmask" ,
"sigaltstack" ,
"gettid" ,
"clone" ,
"bind" ,
"socket" ,
"openat" ,
"readlinkat" ,
"exit_group" ,
"epoll_create1" ,
"listen" ,
"rt_sigreturn" ,
"sched_yield" ,
"clock_gettime" ,
"connect" ,
"dup2" ,
"epoll_pwait" ,
"execve" ,
"exit" ,
"fcntl" ,
"getpid" ,
"getuid" ,
"ioctl" ,
"mprotect" ,
"nanosleep" ,
"open" ,
"poll" ,
"recvfrom" ,
"sendto" ,
"set_tid_address" ,
"setitimer" ,
"writev"
],
"action" : "SCMP_ACT_ALLOW"
}
]
}
执行这些命令:
mkdir ./profiles
curl -L -o profiles/audit.json https://k8s.io/examples/pods/security/seccomp/profiles/audit.json
curl -L -o profiles/violation.json https://k8s.io/examples/pods/security/seccomp/profiles/violation.json
curl -L -o profiles/fine-grained.json https://k8s.io/examples/pods/security/seccomp/profiles/fine-grained.json
ls profiles
你应该看到在最后一步的末尾列出有三个配置文件:
audit.json fine-grained.json violation.json
使用 kind 创建本地 Kubernetes 集群 为简单起见,kind 可用来创建加载了 seccomp 配置文件的单节点集群。
Kind 在 Docker 中运行 Kubernetes,因此集群的每个节点都是一个容器。
这允许将文件挂载到每个容器的文件系统中,类似于将文件加载到节点上。
apiVersion : kind.x-k8s.io/v1alpha4
kind : Cluster
nodes :
- role : control-plane
extraMounts :
- hostPath : "./profiles"
containerPath : "/var/lib/kubelet/seccomp/profiles"
下载该示例 kind 配置,并将其保存到名为 kind.yaml
的文件中:
curl -L -O https://k8s.io/examples/pods/security/seccomp/kind.yaml
你可以通过设置节点的容器镜像来设置特定的 Kubernetes 版本。
有关此类配置的更多信息,
参阅 kind 文档中节点 小节。
本篇教程假定你正在使用 Kubernetes v1.25。
作为 Beta 特性,你可以将 Kubernetes
配置为使用容器运行时 默认首选的配置文件,
而不是回退到 Unconfined
。
如果你想尝试,请在继续之前参阅
启用使用 RuntimeDefault
作为所有工作负载的默认 seccomp 配置文件 。
有了 kind 配置后,使用该配置创建 kind 集群:
kind create cluster --config= kind.yaml
新的 Kubernetes 集群准备就绪后,找出作为单节点集群运行的 Docker 容器:
你应该看到输出中名为 kind-control-plane
的容器正在运行。
输出类似于:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6a96207fed4b kindest/node:v1.18.2 "/usr/local/bin/entr…" 27 seconds ago Up 24 seconds 127.0.0.1:42223->6443/tcp kind-control-plane
如果观察该容器的文件系统,
你应该会看到 profiles/
目录已成功加载到 kubelet 的默认 seccomp 路径中。
使用 docker exec
在 Pod 中运行命令:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的容器 ID
docker exec -it 6a96207fed4b ls /var/lib/kubelet/seccomp/profiles
audit.json fine-grained.json violation.json
你已验证这些 seccomp 配置文件可用于在 kind 中运行的 kubelet。
启用使用 RuntimeDefault
作为所有工作负载的默认 seccomp 配置文件 特性状态: Kubernetes v1.25 [beta]
要使用 Seccomp(安全计算模式)配置文件来设定默认值,你必须要在启用 SeccompDefault
特性门控 的情况下运行 kubelet
(这是默认值)。
你还必须显式地启用每个节点的默认行为,以及相应的
--seccomp-default
命令行标志 。两者必须同时启用才能使用该特性。
如果启用,kubelet 将会默认使用 RuntimeDefault
seccomp 配置文件,
(这一配置文明是由容器运行时定义的),而不是使用 Unconfined
(禁用 seccomp)模式。
默认的配置文件旨在提供一组限制性较强且能保留工作负载功能的安全默认值。
不同容器运行时及其不同发布版本之间的默认配置文件可能有所不同,
例如在比较来自 CRI-O 和 containerd 的配置文件时。
说明: 启用该功能既不会更改 Kubernetes securityContext.seccompProfile
API 字段,
也不会添加已弃用的工作负载注解。
这为用户提供了随时回滚的可能性,而且无需实际更改工作负载配置。
crictl inspect
之类的工具可用于验证容器正在使用哪个 seccomp 配置文件。
与其他工作负载相比,某些工作负载可能需要更少的系统调用限制。
这意味着即使使用 RuntimeDefault
配置文件,它们也可能在运行时失败。
要应对此类故障,你可以:
将工作负载显式运行为 Unconfined
。 禁用节点的 SeccompDefault
特性。还要确保工作负载被调度到禁用该特性的节点上。 为工作负载创建自定义 seccomp 配置文件。 如果你将此特性引入到类似的生产集群中,
Kubernetes 项目建议你在部分节点上启用此特性门控,
然后在整个集群范围内推出更改之前,测试工作负载执行情况。
你可以在相关的 Kubernetes 增强提案(KEP)
中找到可能的升级和降级策略的更详细信息:
默认启用 Seccomp 。
Kubernetes 1.25 允许你配置 Seccomp 配置文件,
当 Pod 的规约未定义特定的 Seccomp 配置文件时应用该配置文件。
这是一个 Beta 特性,默认启用相应的 SeccompDefault
特性门控 。
但是,你仍然需要为要使用它的每个节点启用此默认设置。
如果你正在运行 Kubernetes 1.25
集群并希望启用该特性,请使用 --seccomp-default
命令行参数运行 kubelet,
或通过 kubelet 配置文件 启用。
要在 kind 启用特性门控,
请确保 kind
提供所需的最低 Kubernetes 版本,
并在 kind 配置中
启用了 SeccompDefault
特性:
kind : Cluster
apiVersion : kind.x-k8s.io/v1alpha4
featureGates :
SeccompDefault : true
nodes :
- role : control-plane
image : kindest/node:v1.23.0@sha256:49824ab1727c04e56a21a5d8372a402fcd32ea51ac96a2706a12af38934f81ac
kubeadmConfigPatches :
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
seccomp-default: "true"
- role : worker
image : kindest/node:v1.23.0@sha256:49824ab1727c04e56a21a5d8372a402fcd32ea51ac96a2706a12af38934f81ac
kubeadmConfigPatches :
- |
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
feature-gates: SeccompDefault=true
seccomp-default: "true"
如果集群已就绪,则运行一个 Pod:
kubectl run --rm -it --restart= Never --image= alpine alpine -- sh
现在应该附加了默认的 seccomp 配置文件。
这可以通过使用 docker exec
为 kind 上的容器运行 crictl inspect
来验证:
docker exec -it kind-worker bash -c \
'crictl inspect $(crictl ps --name=alpine -q) | jq .info.runtimeSpec.linux.seccomp'
{
"defaultAction" : "SCMP_ACT_ERRNO" ,
"architectures" : ["SCMP_ARCH_X86_64" , "SCMP_ARCH_X86" , "SCMP_ARCH_X32" ],
"syscalls" : [
{
"names" : ["..." ]
}
]
}
使用 seccomp 配置文件创建 Pod 以进行系统调用审计 首先,将 audit.json
配置文件应用到新的 Pod 上,该配置文件将记录进程的所有系统调用。
这是该 Pod 的清单:
apiVersion : v1
kind : Pod
metadata :
name : audit-pod
labels :
app : audit-pod
spec :
securityContext :
seccompProfile :
type : Localhost
localhostProfile : profiles/audit.json
containers :
- name : test-container
image : hashicorp/http-echo:0.2.3
args :
- "-text=just made some syscalls!"
securityContext :
allowPrivilegeEscalation : false
说明: 已弃用的 seccomp 注解 seccomp.security.alpha.kubernetes.io/pod
(针对整个 Pod)和
container.seccomp.security.alpha.kubernetes.io/[name]
(针对单个容器)
将随着未来 Kubernetes 的发布而被删除。
请在可能的情况下使用原生 API 字段而不是注解。
从 Kubernetes v1.25 开始,kubelet 不再支持这些注解,
也不再支持在静态 Pod 中使用注解,并且当创建带有 seccomp 字段的 Pod 时不再自动填充 seccomp 注解。
从注释中自动填充 seccomp 字段的特性,将计划在未来的版本中删除。
在集群中创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/audit-pod.yaml
此配置文件不限制任何系统调用,因此 Pod 应该成功启动。
kubectl get pod/audit-pod
NAME READY STATUS RESTARTS AGE
audit-pod 1/1 Running 0 30s
为了能够与容器暴露的端点交互,
创建一个 NodePort 类型的 Service ,
允许从 kind 控制平面容器内部访问端点。
kubectl expose pod audit-pod --type NodePort --port 5678
检查 Service 在节点上分配的端口。
kubectl get service audit-pod
输出类似于:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
audit-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
现在,你可以使用 curl
从 kind 控制平面容器内部访问该端点,位于该服务所公开的端口上。
使用 docker exec
在属于该控制平面容器的容器中运行 curl
命令:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
你可以看到该进程正在运行,但它实际上进行了哪些系统调用?
因为这个 Pod 在本地集群中运行,你应该能够在 /var/log/syslog
中看到它们。
打开一个新的终端窗口并 tail
来自 http-echo
的调用的输出:
tail -f /var/log/syslog | grep 'http-echo'
你应该已经看到了一些由 http-echo
进行的系统调用的日志,
如果你在控制平面容器中 curl
端点,你会看到更多的写入。
例如:
Jul 6 15:37:40 my-machine kernel: [369128.669452] audit: type=1326 audit(1594067860.484:14536): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=51 compat=0 ip=0x46fe1f code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669453] audit: type=1326 audit(1594067860.484:14537): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=54 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669455] audit: type=1326 audit(1594067860.484:14538): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669456] audit: type=1326 audit(1594067860.484:14539): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=288 compat=0 ip=0x46fdba code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669517] audit: type=1326 audit(1594067860.484:14540): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=0 compat=0 ip=0x46fd44 code=0x7ffc0000
Jul 6 15:37:40 my-machine kernel: [369128.669519] audit: type=1326 audit(1594067860.484:14541): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671648] audit: type=1326 audit(1594067920.488:14559): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=270 compat=0 ip=0x4559b1 code=0x7ffc0000
Jul 6 15:38:40 my-machine kernel: [369188.671726] audit: type=1326 audit(1594067920.488:14560): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=29064 comm="http-echo" exe="/http-echo" sig=0 arch=c000003e syscall=202 compat=0 ip=0x455e53 code=0x7ffc0000
通过查看每一行的 syscall=
条目,你可以开始了解 http-echo
进程所需的系统调用。
虽然这些不太可能包含它使用的所有系统调用,但它可以作为此容器的 seccomp 配置文件的基础。
在转到下一部分之前清理该 Pod 和 Service:
kubectl delete service audit-pod --wait
kubectl delete pod audit-pod --wait --now
使用导致违规的 seccomp 配置文件创建 Pod 出于演示目的,将配置文件应用于不允许任何系统调用的 Pod 上。
此演示的清单是:
apiVersion : v1
kind : Pod
metadata :
name : violation-pod
labels :
app : violation-pod
spec :
securityContext :
seccompProfile :
type : Localhost
localhostProfile : profiles/violation.json
containers :
- name : test-container
image : hashicorp/http-echo:0.2.3
args :
- "-text=just made some syscalls!"
securityContext :
allowPrivilegeEscalation : false
尝试在集群中创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/violation-pod.yaml
Pod 创建,但存在问题。
如果你检查 Pod 状态,你应该看到它没有启动。
kubectl get pod/violation-pod
NAME READY STATUS RESTARTS AGE
violation-pod 0/1 CrashLoopBackOff 1 6s
如上例所示,http-echo
进程需要相当多的系统调用。
这里 seccomp 已通过设置 "defaultAction": "SCMP_ACT_ERRNO"
被指示为在发生任何系统调用时报错。
这是非常安全的,但消除了做任何有意义的事情的能力。
你真正想要的是只给工作负载它们所需要的权限。
在转到下一部分之前清理该 Pod:
kubectl delete pod violation-pod --wait --now
使用只允许必要的系统调用的 seccomp 配置文件创建 Pod 如果你看一看 fine-grained.json
配置文件,
你会注意到第一个示例的 syslog 中看到的一些系统调用,
其中配置文件设置为 "defaultAction": "SCMP_ACT_LOG"
。
现在的配置文件设置 "defaultAction": "SCMP_ACT_ERRNO"
,
但在 "action": "SCMP_ACT_ALLOW"
块中明确允许一组系统调用。
理想情况下,容器将成功运行,并且你看到没有消息发送到 syslog
。
此示例的清单是:
apiVersion : v1
kind : Pod
metadata :
name : fine-pod
labels :
app : fine-pod
spec :
securityContext :
seccompProfile :
type : Localhost
localhostProfile : profiles/fine-grained.json
containers :
- name : test-container
image : hashicorp/http-echo:0.2.3
args :
- "-text=just made some syscalls!"
securityContext :
allowPrivilegeEscalation : false
在你的集群中创建 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/fine-pod.yaml
此 Pod 应该显示为已成功启动:
NAME READY STATUS RESTARTS AGE
fine-pod 1/1 Running 0 30s
打开一个新的终端窗口并使用 tail
来监视提到来自 http-echo
的调用的日志条目:
# 你计算机上的日志路径可能与 “/var/log/syslog” 不同
tail -f /var/log/syslog | grep 'http-echo'
接着,使用 NodePort Service 公开 Pod:
kubectl expose pod fine-pod --type NodePort --port 5678
检查节点上的 Service 分配了什么端口:
kubectl get service fine-pod
输出类似于:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
fine-pod NodePort 10.111.36.142 <none> 5678:32373/TCP 72s
使用 curl
从 kind 控制平面容器内部访问端点:
# 将 6a96207fed4b 更改为你从 “docker ps” 看到的控制平面容器 ID
docker exec -it 6a96207fed4b curl localhost:32373
just made some syscalls!
你应该在 syslog
中看不到任何输出。
这是因为配置文件允许所有必要的系统调用,并指定如果调用列表之外的系统调用应发生错误。
从安全角度来看,这是一种理想的情况,但需要在分析程序时付出一些努力。
如果有一种简单的方法可以在不需要太多努力的情况下更接近这种安全性,那就太好了。
在转到下一部分之前清理该 Pod 和服务:
kubectl delete service fine-pod --wait
kubectl delete pod fine-pod --wait --now
创建使用容器运行时默认 seccomp 配置文件的 Pod 大多数容器运行时都提供了一组合理的默认系统调用,以及是否允许执行这些系统调用。
你可以通过将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault
来为你的工作负载采用这些默认值。
说明: 如果你已经启用了 SeccompDefault
特性门控 ,
只要没有指定其他 seccomp 配置文件,那么 Pod 就会使用 SeccompDefault
的 seccomp 配置文件。
否则,默认值为 Unconfined
。
这是一个 Pod 的清单,它要求其所有容器使用 RuntimeDefault
seccomp 配置文件:
apiVersion : v1
kind : Pod
metadata :
name : default-pod
labels :
app : default-pod
spec :
securityContext :
seccompProfile :
type : RuntimeDefault
containers :
- name : test-container
image : hashicorp/http-echo:0.2.3
args :
- "-text=just made some more syscalls!"
securityContext :
allowPrivilegeEscalation : false
创建此 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/seccomp/ga/default-pod.yaml
kubectl get pod default-pod
此 Pod 应该显示为成功启动:
NAME READY STATUS RESTARTS AGE
default-pod 1/1 Running 0 20s
最后,你看到一切正常之后,请清理:
kubectl delete pod default-pod --wait --now
接下来 你可以了解有关 Linux seccomp 的更多信息:
5.5 - 无状态应用程序 5.5.1 - 公开外部 IP 地址以访问集群中应用程序 此页面显示如何创建公开外部 IP 地址的 Kubernetes 服务对象。
准备开始 安装 kubectl 。 使用 Google Kubernetes Engine 或 Amazon Web Services 等云供应商创建 Kubernetes 集群。
本教程创建了一个外部负载均衡器 ,
需要云供应商。 配置 kubectl
与 Kubernetes API 服务器通信。有关说明,请参阅云供应商文档。 教程目标 运行 Hello World 应用程序的五个实例。 创建一个公开外部 IP 地址的 Service 对象。 使用 Service 对象访问正在运行的应用程序。 为一个在五个 pod 中运行的应用程序创建服务 在集群中运行 Hello World 应用程序:
apiVersion : apps/v1
kind : Deployment
metadata :
labels :
app.kubernetes.io/name : load-balancer-example
name : hello-world
spec :
replicas : 5
selector :
matchLabels :
app.kubernetes.io/name : load-balancer-example
template :
metadata :
labels :
app.kubernetes.io/name : load-balancer-example
spec :
containers :
- image : gcr.io/google-samples/node-hello:1.0
name : hello-world
ports :
- containerPort : 8080
kubectl apply -f https://k8s.io/examples/service/load-balancer-example.yaml
前面的命令创建一个
Deployment
对象和一个关联的
ReplicaSet 对象。
ReplicaSet 有五个 Pods ,
每个都运行 Hello World 应用程序。
显示有关 Deployment 的信息:
kubectl get deployments hello-world
kubectl describe deployments hello-world
显示有关 ReplicaSet 对象的信息:
kubectl get replicasets
kubectl describe replicasets
创建公开 Deployment 的 Service 对象:
kubectl expose deployment hello-world --type= LoadBalancer --name= my-service
显示有关 Service 的信息:
kubectl get services my-service
输出类似于:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-service LoadBalancer 10.3.245.137 104.198.205.71 8080/TCP 54s
提示:type=LoadBalancer
服务由外部云服务提供商提供支持,本例中不包含此部分,
详细信息请参考此页
提示:如果外部 IP 地址显示为 <pending>,请等待一分钟再次输入相同的命令。
显示有关 Service 的详细信息:
kubectl describe services my-service
输出类似于:
Name: my-service
Namespace: default
Labels: app.kubernetes.io/name=load-balancer-example
Annotations: <none>
Selector: app.kubernetes.io/name=load-balancer-example
Type: LoadBalancer
IP: 10.3.245.137
LoadBalancer Ingress: 104.198.205.71
Port: <unset> 8080/TCP
NodePort: <unset> 32377/TCP
Endpoints: 10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more...
Session Affinity: None
Events: <none>
记下服务公开的外部 IP 地址(LoadBalancer Ingress
)。
在本例中,外部 IP 地址是 104.198.205.71。还要注意 Port
和 NodePort
的值。
在本例中,Port
是 8080,NodePort
是 32377。
在前面的输出中,你可以看到服务有几个端点:
10.0.0.6:8080、10.0.1.6:8080、10.0.1.7:8080 和另外两个,
这些都是正在运行 Hello World 应用程序的 Pod 的内部地址。
要验证这些是 Pod 地址,请输入以下命令:
kubectl get pods --output= wide
输出类似于:
NAME ... IP NODE
hello-world-2895499144-1jaz9 ... 10.0.1.6 gke-cluster-1-default-pool-e0b8d269-1afc
hello-world-2895499144-2e5uh ... 10.0.1.8 gke-cluster-1-default-pool-e0b8d269-1afc
hello-world-2895499144-9m4h1 ... 10.0.0.6 gke-cluster-1-default-pool-e0b8d269-5v7a
hello-world-2895499144-o4z13 ... 10.0.1.7 gke-cluster-1-default-pool-e0b8d269-1afc
hello-world-2895499144-segjf ... 10.0.2.5 gke-cluster-1-default-pool-e0b8d269-cpuc
使用外部 IP 地址(LoadBalancer Ingress
)访问 Hello World 应用程序:
curl http://<external-ip>:<port>
其中 <external-ip>
是你的服务的外部 IP 地址(LoadBalancer Ingress
),
<port>
是你的服务描述中的 port
的值。
如果你正在使用 minikube,输入 minikube service my-service
将在浏览器中自动打开 Hello World 应用程序。
成功请求的响应是一条问候消息:
清理现场 要删除 Service,请输入以下命令:
kubectl delete services my-service
要删除正在运行 Hello World 应用程序的 Deployment,ReplicaSet 和 Pod,请输入以下命令:
kubectl delete deployment hello-world
接下来 进一步了解将应用程序与服务连接 。
5.5.2 - 示例:使用 Redis 部署 PHP 留言板应用程序 本教程向你展示如何使用 Kubernetes 和 Docker
构建和部署一个简单的 (非面向生产的) 多层 web 应用程序。本例由以下组件组成:
单实例 Redis 以保存留言板条目 多个 web 前端实例 教程目标 启动 Redis 领导者(Leader) 启动两个 Redis 跟随者(Follower) 公开并查看前端服务 清理 准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
你的 Kubernetes 服务器版本必须不低于版本 v1.14.
要获知版本信息,请输入
kubectl version
.
启动 Redis 数据库 留言板应用程序使用 Redis 存储数据。
创建 Redis Deployment 下面包含的清单文件指定了一个 Deployment 控制器,该控制器运行一个 Redis Pod 副本。
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : apps/v1
kind : Deployment
metadata :
name : redis-leader
labels :
app : redis
role : leader
tier : backend
spec :
replicas : 1
selector :
matchLabels :
app : redis
template :
metadata :
labels :
app : redis
role : leader
tier : backend
spec :
containers :
- name : leader
image : "docker.io/redis:6.0.5"
resources :
requests :
cpu : 100m
memory : 100Mi
ports :
- containerPort : 6379
在下载清单文件的目录中启动终端窗口。
从 redis-leader-deployment.yaml
文件中应用 Redis Deployment:
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-deployment.yaml
查询 Pod 列表以验证 Redis Pod 是否正在运行:
响应应该与此类似:
NAME READY STATUS RESTARTS AGE
redis-leader-fb76b4755-xjr2n 1/1 Running 0 13s
运行以下命令查看 Redis Deployment 中的日志:
kubectl logs -f deployment/redis-leader
创建 Redis 领导者服务 留言板应用程序需要往 Redis 中写数据。因此,需要创建
Service 来转发 Redis Pod
的流量。Service 定义了访问 Pod 的策略。
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : v1
kind : Service
metadata :
name : redis-leader
labels :
app : redis
role : leader
tier : backend
spec :
ports :
- port : 6379
targetPort : 6379
selector :
app : redis
role : leader
tier : backend
使用下面的 redis-leader-service.yaml
文件创建 Redis的服务:
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-leader-service.yaml
查询服务列表验证 Redis 服务是否正在运行:
响应应该与此类似:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT( S) AGE
kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 1m
redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 16s
说明: 这个清单文件创建了一个名为 redis-leader
的 Service,其中包含一组
与前面定义的标签匹配的标签,因此服务将网络流量路由到 Redis Pod 上。
设置 Redis 跟随者 尽管 Redis 领导者只有一个 Pod,你可以通过添加若干 Redis 跟随者来将其配置为高可用状态,
以满足流量需求。
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : apps/v1
kind : Deployment
metadata :
name : redis-follower
labels :
app : redis
role : follower
tier : backend
spec :
replicas : 2
selector :
matchLabels :
app : redis
template :
metadata :
labels :
app : redis
role : follower
tier : backend
spec :
containers :
- name : follower
image : gcr.io/google_samples/gb-redis-follower:v2
resources :
requests :
cpu : 100m
memory : 100Mi
ports :
- containerPort : 6379
应用下面的 redis-follower-deployment.yaml
文件创建 Redis Deployment:
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-deployment.yaml
通过查询 Pods 列表,验证两个 Redis 跟随者副本在运行:
响应应该类似于这样:
NAME READY STATUS RESTARTS AGE
redis-follower-dddfbdcc9-82sfr 1/1 Running 0 37s
redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 38s
redis-leader-fb76b4755-xjr2n 1/1 Running 0 11m
创建 Redis 跟随者服务 Guestbook 应用需要与 Redis 跟随者通信以读取数据。
为了让 Redis 跟随者可被发现,你必须创建另一个
Service 。
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : v1
kind : Service
metadata :
name : redis-follower
labels :
app : redis
role : follower
tier : backend
spec :
ports :
# 此服务应使用的端口
- port : 6379
selector :
app : redis
role : follower
tier : backend
应用如下所示 redis-follower-service.yaml
文件中的 Redis Service:
kubectl apply -f https://k8s.io/examples/application/guestbook/redis-follower-service.yaml
查询 Service 列表,验证 Redis 服务在运行:
响应应该类似于这样:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d19h
redis-follower ClusterIP 10.110.162.42 <none> 6379/TCP 9s
redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 6m10s
说明: 清单文件创建了一个名为 redis-follower
的 Service,该 Service
具有一些与之前所定义的标签相匹配的标签,因此该 Service 能够将网络流量
路由到 Redis Pod 之上。
设置并公开留言板前端 现在你有了一个为 Guestbook 应用配置的 Redis 存储处于运行状态,
接下来可以启动 Guestbook 的 Web 服务器了。
与 Redis 跟随者类似,前端也是使用 Kubernetes Deployment 来部署的。
Guestbook 应用使用 PHP 前端。该前端被配置成与后端的 Redis 跟随者或者
领导者服务通信,具体选择哪个服务取决于请求是读操作还是写操作。
前端对外暴露一个 JSON 接口,并提供基于 jQuery-Ajax 的用户体验。
创建 Guestbook 前端 Deployment # 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : apps/v1
kind : Deployment
metadata :
name : frontend
spec :
replicas : 3
selector :
matchLabels :
app : guestbook
tier : frontend
template :
metadata :
labels :
app : guestbook
tier : frontend
spec :
containers :
- name : php-redis
image : gcr.io/google_samples/gb-frontend:v5
env :
- name : GET_HOSTS_FROM
value : "dns"
resources :
requests :
cpu : 100m
memory : 100Mi
ports :
- containerPort : 80
应用来自 frontend-deployment.yaml
文件的前端 Deployment:
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-deployment.yaml
查询 Pod 列表,验证三个前端副本正在运行:
kubectl get pods -l app = guestbook -l tier = frontend
响应应该与此类似:
NAME READY STATUS RESTARTS AGE
frontend-85595f5bf9-5tqhb 1/1 Running 0 47s
frontend-85595f5bf9-qbzwm 1/1 Running 0 47s
frontend-85595f5bf9-zchwc 1/1 Running 0 47s
创建前端服务 应用的 Redis
服务只能在 Kubernetes 集群中访问,因为服务的默认类型是
ClusterIP 。
ClusterIP
为服务指向的 Pod 集提供一个 IP 地址。这个 IP 地址只能在集群中访问。
如果你希望访客能够访问你的 Guestbook,你必须将前端服务配置为外部可见的,
以便客户端可以从 Kubernetes 集群之外请求服务。
然而即便使用了 ClusterIP
,Kubernetes 用户仍可以通过
kubectl port-forward
访问服务。
说明: 一些云提供商,如 Google Compute Engine 或 Google Kubernetes Engine,
支持外部负载均衡器。如果你的云提供商支持负载均衡器,并且你希望使用它,
只需取消注释 type: LoadBalancer
。
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : v1
kind : Service
metadata :
name : frontend
labels :
app : guestbook
tier : frontend
spec :
# 如果你的集群支持,请取消注释以下内容以自动为前端服务创建一个外部负载均衡 IP。
# type: LoadBalancer
#type: LoadBalancer
ports :
# 此服务应使用的端口
- port : 80
selector :
app : guestbook
tier : frontend
应用来自 frontend-service.yaml
文件中的前端服务:
kubectl apply -f https://k8s.io/examples/application/guestbook/frontend-service.yaml
查询 Service 列表以验证前端服务正在运行:
响应应该与此类似:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend ClusterIP 10.97.28.230 <none> 80/TCP 19s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d19h
redis-follower ClusterIP 10.110.162.42 <none> 6379/TCP 5m48s
redis-leader ClusterIP 10.103.78.24 <none> 6379/TCP 11m
通过 kubectl port-forward
查看前端服务 运行以下命令将本机的 8080
端口转发到服务的 80
端口。
kubectl port-forward svc/frontend 8080:80
响应应该与此类似:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
在浏览器中加载 http://localhost:8080
页面以查看 Guestbook。 通过 LoadBalancer
查看前端服务 如果你部署了 frontend-service.yaml
,需要找到用来查看 Guestbook 的
IP 地址。
运行以下命令以获取前端服务的 IP 地址。
kubectl get service frontend
响应应该与此类似:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 10.51.242.136 109.197.92.229 80:32372/TCP 1m
复制这里的外部 IP 地址,然后在浏览器中加载页面以查看留言板。 说明: 尝试通过输入消息并点击 Submit 来添加一些留言板条目。
你所输入的消息会在前端显示。这一消息表明数据被通过你
之前所创建的 Service 添加到 Redis 存储中。
扩展 Web 前端 你可以根据需要执行伸缩操作,这是因为服务器本身被定义为使用一个
Deployment 控制器的 Service。
运行以下命令扩展前端 Pod 的数量:
kubectl scale deployment frontend --replicas= 5
查询 Pod 列表验证正在运行的前端 Pod 的数量:
响应应该类似于这样:
NAME READY STATUS RESTARTS AGE
frontend-85595f5bf9-5df5m 1/1 Running 0 83s
frontend-85595f5bf9-7zmg5 1/1 Running 0 83s
frontend-85595f5bf9-cpskg 1/1 Running 0 15m
frontend-85595f5bf9-l2l54 1/1 Running 0 14m
frontend-85595f5bf9-l9c8z 1/1 Running 0 14m
redis-follower-dddfbdcc9-82sfr 1/1 Running 0 97m
redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 97m
redis-leader-fb76b4755-xjr2n 1/1 Running 0 108m
运行以下命令缩小前端 Pod 的数量:
kubectl scale deployment frontend --replicas= 2
查询 Pod 列表验证正在运行的前端 Pod 的数量:
响应应该类似于这样:
NAME READY STATUS RESTARTS AGE
frontend-85595f5bf9-cpskg 1/1 Running 0 16m
frontend-85595f5bf9-l9c8z 1/1 Running 0 15m
redis-follower-dddfbdcc9-82sfr 1/1 Running 0 98m
redis-follower-dddfbdcc9-qrt5k 1/1 Running 0 98m
redis-leader-fb76b4755-xjr2n 1/1 Running 0 109m
清理现场 删除 Deployments 和服务还会删除正在运行的 Pod。
使用标签用一个命令删除多个资源。
运行以下命令以删除所有 Pod,Deployments 和 Services。
kubectl delete deployment -l app = redis
kubectl delete service -l app = redis
kubectl delete deployment frontend
kubectl delete service frontend
响应应该是:
deployment.apps "redis-follower" deleted
deployment.apps "redis-leader" deleted
deployment.apps "frontend" deleted
service "frontend" deleted
查询 Pod 列表,确认没有 Pod 在运行:
响应应该是:
No resources found in default namespace.
接下来
5.6 - 有状态的应用 5.6.1 - StatefulSet 基础 本教程介绍了如何使用
StatefulSet
来管理应用。
演示了如何创建、删除、扩容/缩容和更新 StatefulSet 的 Pod。
准备开始 在开始本教程之前,你应该熟悉以下 Kubernetes 的概念:
说明: 本教程假设你的集群被配置为动态制备 PersistentVolume 卷。
如果没有这样配置,在开始本教程之前,你需要手动准备 2 个 1 GiB 的存储卷。
教程目标 StatefulSet 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes
上管理有状态应用和分布式系统是一个宽泛而复杂的话题。
为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,你将会使用 StatefulSet 部署一个简单的 Web 应用。
在阅读本教程后,你将熟悉以下内容:
如何创建 StatefulSet StatefulSet 怎样管理它的 Pod 如何删除 StatefulSet 如何对 StatefulSet 进行扩容/缩容 如何更新一个 StatefulSet 的 Pod 创建 StatefulSet 作为开始,使用如下示例创建一个 StatefulSet。它和
StatefulSet 概念中的示例相似。
它创建了一个 Headless Service
nginx
用来发布 StatefulSet web
中的 Pod 的 IP 地址。
apiVersion : v1
kind : Service
metadata :
name : nginx
labels :
app : nginx
spec :
ports :
- port : 80
name : web
clusterIP : None
selector :
app : nginx
---
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : web
spec :
serviceName : "nginx"
replicas : 2
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : registry.k8s.io/nginx-slim:0.8
ports :
- containerPort : 80
name : web
volumeMounts :
- name : www
mountPath : /usr/share/nginx/html
volumeClaimTemplates :
- metadata :
name : www
spec :
accessModes : [ "ReadWriteOnce" ]
resources :
requests :
storage : 1Gi
下载上面的例子并保存为文件 web.yaml
。
你需要使用两个终端窗口。在第一个终端中,使用
kubectl get
来监视 StatefulSet 的 Pod 的创建情况。
kubectl get pods -w -l app = nginx
在另一个终端中,使用 kubectl apply
来创建定义在 web.yaml
中的 Headless Service 和 StatefulSet。
kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
上面的命令创建了两个 Pod,每个都运行了一个 NginX Web 服务器。
获取 nginx
Service:
kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 12s
然后获取 web
StatefulSet,以验证两者均已成功创建:
kubectl get statefulset web
NAME DESIRED CURRENT AGE
web 2 1 20s
顺序创建 Pod 对于一个拥有 n 个副本的 StatefulSet,Pod 被部署时是按照 {0..n-1} 的序号顺序创建的。
在第一个终端中使用 kubectl get
检查输出。这个输出最终将看起来像下面的样子。
kubectl get pods -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 19s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 18s
请注意,直到 web-0
Pod 处于 Running (请参阅
Pod 阶段 )
并 Ready (请参阅 Pod 状况 中的
type
)状态后,web-1
Pod 才会被启动。
StatefulSet 中的 Pod StatefulSet 中的每个 Pod 拥有一个唯一的顺序索引和稳定的网络身份标识。
检查 Pod 的顺序索引 获取 StatefulSet 的 Pod:
kubectl get pods -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 1m
web-1 1/1 Running 0 1m
如同 StatefulSet 概念中所提到的,
StatefulSet 中的每个 Pod 拥有一个具有黏性的、独一无二的身份标志。
这个标志基于 StatefulSet
控制器 分配给每个
Pod 的唯一顺序索引。
Pod 名称的格式为 <statefulset 名称>-<序号索引>
。
web
StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0
和 web-1
。
使用稳定的网络身份标识 每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用
kubectl exec
在每个 Pod 中执行 hostname
:
for i in 0 1; do kubectl exec "web- $i " -- sh -c 'hostname' ; done
web-0
web-1
使用 kubectl run
运行一个提供 nslookup
命令的容器,该命令来自于 dnsutils
包。
通过对 Pod 的主机名执行 nslookup
,你可以检查这些主机名在集群内部的 DNS 地址:
kubectl run -i --tty --image busybox:1.28 dns-test --restart= Never --rm
这将启动一个新的 Shell。在新 Shell 中运行:
# 在 dns-test 容器 Shell 中运行以下命令
nslookup web-0.nginx
输出类似于:
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.6
nslookup web-1.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.6
(现在可以退出容器 Shell:exit
)
headless service 的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready 状态的 Pod)。
SRV 记录指向一个包含 Pod IP 地址的记录表项。
在一个终端中监视 StatefulSet 的 Pod:
kubectl get pod -w -l app = nginx
在另一个终端中使用
kubectl delete
删除 StatefulSet 中所有的 Pod:
kubectl delete pod -l app = nginx
pod "web-0" deleted
pod "web-1" deleted
等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态:
kubectl get pod -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 34s
使用 kubectl exec
和 kubectl run
查看 Pod 的主机名和集群内部的 DNS 表项。
首先,查看 Pod 的主机名:
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname' ; done
web-0
web-1
然后,运行:
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm /bin/sh
这将启动一个新的 Shell。在新 Shell 中,运行:
# 在 dns-test 容器 Shell 中运行以下命令
nslookup web-0.nginx
输出类似于:
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.7
nslookup web-1.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.8
(现在可以退出容器 Shell:exit
)
Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。
在本教程中使用的集群中它们就改变了。这就是为什么不要在其他应用中使用
StatefulSet 中 Pod 的 IP 地址进行连接,这点很重要。
如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME。
和 CNAME 相关联的 SRV 记录只会包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。
如果你的应用已经实现了用于测试是否已存活(liveness)并就绪(readiness)的连接逻辑,
你可以使用 Pod 的 SRV 记录(web-0.nginx.default.svc.cluster.local
、
web-1.nginx.default.svc.cluster.local
)。因为它们是稳定的,并且当你的
Pod 的状态变为 Running 和 Ready 时,你的应用就能够发现它们的地址。
写入稳定的存储 获取 web-0
和 web-1
的 PersistentVolumeClaims:
kubectl get pvc -l app = nginx
输出类似于:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s
StatefulSet 控制器创建了两个
PersistentVolumeClaims ,
绑定到两个
PersistentVolumes 。
由于本教程使用的集群配置为动态制备
PersistentVolume 卷,所有的 PersistentVolume 卷都是自动创建和绑定的。
NginX Web 服务器默认会加载位于 /usr/share/nginx/html/index.html
的 index 文件。
StatefulSet spec
中的 volumeMounts
字段保证了 /usr/share/nginx/html
文件夹由一个 PersistentVolume 卷支持。
将 Pod 的主机名写入它们的 index.html
文件并验证 NginX Web 服务器使用该主机名提供服务:
for i in 0 1; do kubectl exec "web- $i " -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html' ; done
for i in 0 1; do kubectl exec -i -t "web- $i " -- curl http://localhost/; done
web-0
web-1
说明: 请注意,如果你看见上面的 curl 命令返回了 403 Forbidden 的响应,你需要像这样修复使用 volumeMounts
(原因归咎于使用 hostPath 卷时存在的缺陷 )
挂载的目录的权限,先运行:
for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done
再重新尝试上面的 curl
命令。
在一个终端监视 StatefulSet 的 Pod:
kubectl get pod -w -l app = nginx
在另一个终端删除 StatefulSet 所有的 Pod:
kubectl delete pod -l app = nginx
pod "web-0" deleted
pod "web-1" deleted
在第一个终端里检查 kubectl get
命令的输出,等待所有 Pod 变成 Running 和 Ready 状态。
kubectl get pod -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 34s
验证所有 Web 服务器在继续使用它们的主机名提供服务:
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
虽然 web-0
和 web-1
被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的
PersistentVolumeClaim 相关联的 PersistentVolume 卷被重新挂载到了各自的 volumeMount
上。
不管 web-0
和 web-1
被调度到了哪个节点上,它们的 PersistentVolume 卷将会被挂载到合适的挂载点上。
扩容/缩容 StatefulSet 扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 replicas
字段完成。
你可以使用 kubectl scale
或者 kubectl patch
来扩容/缩容一个 StatefulSet。
扩容 在一个终端窗口监视 StatefulSet 的 Pod:
kubectl get pods -w -l app = nginx
在另一个终端窗口使用 kubectl scale
扩展副本数为 5:
kubectl scale sts web --replicas= 5
statefulset.apps/web scaled
在第一个 终端中检查 kubectl get
命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。
kubectl get pods -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2h
web-1 1/1 Running 0 2h
NAME READY STATUS RESTARTS AGE
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 19s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 ContainerCreating 0 0s
web-3 1/1 Running 0 18s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 19s
StatefulSet 控制器扩展了副本的数量。
如同创建 StatefulSet 所述,StatefulSet 按序号索引顺序创建各个
Pod,并且会等待前一个 Pod 变为 Running 和 Ready 才会启动下一个 Pod。
缩容 在一个终端监视 StatefulSet 的 Pod:
kubectl get pods -w -l app = nginx
在另一个终端使用 kubectl patch
将 StatefulSet 缩容回三个副本:
kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched
等待 web-4
和 web-3
状态变为 Terminating。
kubectl get pods -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3h
web-1 1/1 Running 0 3h
web-2 1/1 Running 0 55s
web-3 1/1 Running 0 36s
web-4 0/1 ContainerCreating 0 18s
NAME READY STATUS RESTARTS AGE
web-4 1/1 Running 0 19s
web-4 1/1 Terminating 0 24s
web-4 1/1 Terminating 0 24s
web-3 1/1 Terminating 0 42s
web-3 1/1 Terminating 0 42s
顺序终止 Pod 控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。
获取 StatefulSet 的 PersistentVolumeClaims:
kubectl get pvc -l app = nginx
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 13h
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 13h
www-web-2 Bound pvc-e1125b27-b508-11e6-932f-42010a800002 1Gi RWO 13h
www-web-3 Bound pvc-e1176df6-b508-11e6-932f-42010a800002 1Gi RWO 13h
www-web-4 Bound pvc-e11bb5f8-b508-11e6-932f-42010a800002 1Gi RWO 13h
五个 PersistentVolumeClaims 和五个 PersistentVolume 卷仍然存在。
查看 Pod 的稳定存储 ,我们发现当删除 StatefulSet 的
Pod 时,挂载到 StatefulSet 的 Pod 的 PersistentVolume 卷不会被删除。
当这种删除行为是由 StatefulSet 缩容引起时也是一样的。
更新 StatefulSet 从 Kubernetes 1.7 版本开始,StatefulSet 控制器支持自动更新。
更新策略由 StatefulSet API 对象的 spec.updateStrategy
字段决定。这个特性能够用来更新一个
StatefulSet 中 Pod 的的容器镜像、资源请求和限制、标签和注解。
RollingUpdate
更新策略是 StatefulSet 默认策略。
滚动更新 RollingUpdate
更新策略会更新一个 StatefulSet 中的所有
Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。
对 web
StatefulSet 应用 Patch 操作来应用 RollingUpdate
更新策略:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}'
statefulset.apps/web patched
在一个终端窗口中对 web
StatefulSet 执行 patch 操作来再次改变容器镜像:
kubectl patch statefulset web --type= 'json' -p= '[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.8"}]'
statefulset.apps/web patched
在另一个终端监控 StatefulSet 中的 Pod:
kubectl get pod -l app = nginx -w
输出类似于:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m
web-1 1/1 Running 0 7m
web-2 1/1 Running 0 8m
web-2 1/1 Terminating 0 8m
web-2 1/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 19s
web-1 1/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 6s
web-0 1/1 Terminating 0 7m
web-0 1/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 10s
StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet
控制器终止每个 Pod 并等待它们变成 Running 和 Ready。
请注意,虽然在顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个
Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们当前的版本。
已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。
像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。
获取 Pod 来查看它们的容器镜像:
for p in 0 1 2; do kubectl get pod "web- $p " --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' ; echo; done
registry.k8s.io/nginx-slim:0.8
registry.k8s.io/nginx-slim:0.8
registry.k8s.io/nginx-slim:0.8
StatefulSet 中的所有 Pod 现在都在运行之前的容器镜像。
说明: 你还可以使用 kubectl rollout status sts/<名称>
来查看
StatefulSet 的滚动更新状态。
分段更新 你可以使用 RollingUpdate
更新策略的 partition
参数来分段更新一个 StatefulSet。
分段的更新将会使 StatefulSet 中的其余所有 Pod 保持当前版本的同时允许改变
StatefulSet 的 .spec.template
。
对 web
StatefulSet 执行 Patch 操作为 updateStrategy
字段添加一个分区:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched
再次 Patch StatefulSet 来改变容器镜像:
kubectl patch statefulset web --type= 'json' -p= '[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.7"}]'
statefulset.apps/web patched
删除 StatefulSet 中的 Pod:
pod "web-2" deleted
等待 Pod 变成 Running 和 Ready。
kubectl get pod -l app = nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m
web-1 1/1 Running 0 4m
web-2 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 18s
获取 Pod 的容器镜像:
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.8
请注意,虽然更新策略是 RollingUpdate
,StatefulSet 还是会使用原始的容器恢复 Pod。
这是因为 Pod 的序号比 updateStrategy
指定的 partition
更小。
金丝雀发布 你可以通过减少上文 指定的
partition
来进行金丝雀发布,以此来测试你的程序的改动。
通过 patch 命令修改 StatefulSet 来减少分区:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched
等待 web-2
变成 Running 和 Ready。
kubectl get pod -l app = nginx -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m
web-1 1/1 Running 0 4m
web-2 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 18s
获取 Pod 的容器:
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.7
当你改变 partition
时,StatefulSet 会自动更新 web-2
Pod,这是因为 Pod 的序号大于或等于 partition
。
删除 web-1
Pod:
pod "web-1" deleted
等待 web-1
变成 Running 和 Ready。
kubectl get pod -l app = nginx -w
输出类似于:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6m
web-1 0/1 Terminating 0 6m
web-2 1/1 Running 0 2m
web-1 0/1 Terminating 0 6m
web-1 0/1 Terminating 0 6m
web-1 0/1 Terminating 0 6m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 18s
获取 web-1
Pod 的容器镜像:
kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.8
web-1
被按照原来的配置恢复,因为 Pod 的序号小于分区。当指定了分区时,如果更新了
StatefulSet 的 .spec.template
,则所有序号大于或等于分区的 Pod 都将被更新。
如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。
分阶段的发布 你可以使用类似金丝雀发布 的方法执行一次分阶段的发布
(例如一次线性的、等比的或者指数形式的发布)。
要执行一次分阶段的发布,你需要设置 partition
为希望控制器暂停更新的序号。
分区当前为 2
。请将分区设置为 0
:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched
等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready。
kubectl get pod -l app = nginx -w
输出类似于:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m
web-1 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 2m
web-1 1/1 Running 0 18s
web-0 1/1 Terminating 0 3m
web-0 1/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 3s
获取 StatefulSet 中 Pod 的容器镜像详细信息:
for p in 0 1 2; do kubectl get pod "web- $p " --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' ; echo; done
registry.k8s.io/nginx-slim:0.7
registry.k8s.io/nginx-slim:0.7
registry.k8s.io/nginx-slim:0.7
将 partition
改变为 0
以允许 StatefulSet 继续更新过程。
OnDelete 策略 OnDelete
更新策略实现了传统(1.7 之前)行为,它也是默认的更新策略。
当你选择这个更新策略并修改 StatefulSet 的 .spec.template
字段时,StatefulSet 控制器将不会自动更新 Pod。
删除 StatefulSet StatefulSet 同时支持级联和非级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet
的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。
非级联删除 在一个终端窗口监视 StatefulSet 中的 Pod。
kubectl get pods -w -l app=nginx
使用 kubectl delete
删除 StatefulSet。请确保提供了 --cascade=orphan
参数给命令。这个参数告诉
Kubernetes 只删除 StatefulSet 而不要删除它的任何 Pod。
kubectl delete statefulset web --cascade= orphan
statefulset.apps "web" deleted
获取 Pod 来检查它们的状态:
kubectl get pods -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6m
web-1 1/1 Running 0 7m
web-2 1/1 Running 0 5m
虽然 web
已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。
删除 web-0
:
pod "web-0" deleted
获取 StatefulSet 的 Pod:
kubectl get pods -l app = nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 7m
由于 web
StatefulSet 已经被删除,web-0
没有被重新启动。
在一个终端监控 StatefulSet 的 Pod。
kubectl get pods -w -l app = nginx
在另一个终端里重新创建 StatefulSet。请注意,除非你删除了 nginx
Service(你不应该这样做),你将会看到一个错误,提示 Service 已经存在。
kubectl apply -f web.yaml
statefulset.apps/web created
service/nginx unchanged
请忽略这个错误。它仅表示 kubernetes 进行了一次创建 nginx headless Service
的尝试,尽管那个 Service 已经存在。
在第一个终端中运行并检查 kubectl get
命令的输出。
kubectl get pods -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 16m
web-2 1/1 Running 0 2m
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 18s
web-2 1/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m
当重新创建 web
StatefulSet 时,web-0
被第一个重新启动。
由于 web-1
已经处于 Running 和 Ready 状态,当 web-0
变成 Running 和 Ready 时,
StatefulSet 会接收这个 Pod。由于你重新创建的 StatefulSet 的 replicas
等于 2,
一旦 web-0
被重新创建并且 web-1
被认为已经处于 Running 和 Ready 状态时,web-2
将会被终止。
让我们再看看被 Pod 的 Web 服务器加载的 index.html
的内容:
for i in 0 1; do kubectl exec -i -t "web- $i " -- curl http://localhost/; done
web-0
web-1
尽管你同时删除了 StatefulSet 和 web-0
Pod,但它仍然使用最初写入 index.html
文件的主机名进行服务。
这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolume 卷。
当你重建这个 StatefulSet 并且重新启动了 web-0
时,它原本的 PersistentVolume 卷会被重新挂载。
级联删除 在一个终端窗口监视 StatefulSet 里的 Pod。
kubectl get pods -w -l app = nginx
在另一个窗口中再次删除这个 StatefulSet。这次省略 --cascade=orphan
参数。
kubectl delete statefulset web
statefulset.apps "web" deleted
在第一个终端检查 kubectl get
命令的输出,并等待所有的 Pod 变成 Terminating 状态。
kubectl get pods -w -l app = nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 27m
NAME READY STATUS RESTARTS AGE
web-0 1/1 Terminating 0 12m
web-1 1/1 Terminating 0 29m
web-0 0/1 Terminating 0 12m
web-0 0/1 Terminating 0 12m
web-0 0/1 Terminating 0 12m
web-1 0/1 Terminating 0 29m
web-1 0/1 Terminating 0 29m
web-1 0/1 Terminating 0 29m
如同你在缩容 章节看到的,这些 Pod 按照与其序号索引相反的顺序每次终止一个。
在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。
说明: 尽管级联删除会删除 StatefulSet 及其 Pod,但级联不会删除与 StatefulSet
关联的 Headless Service。你必须手动删除 nginx
Service。
kubectl delete service nginx
service "nginx" deleted
再一次重新创建 StatefulSet 和 headless Service:
kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 index.html
文件的内容:
for i in 0 1; do kubectl exec -i -t "web- $i " -- curl http://localhost/; done
web-0
web-1
即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的
PersistentVolume 卷,并且 web-0
和 web-1
将继续使用它的主机名提供服务。
最后删除 nginx
service
kubectl delete service nginx
service "nginx" deleted
并且删除 web
StatefulSet:
kubectl delete statefulset web
statefulset "web" deleted
Pod 管理策略 对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的。
这些系统仅仅要求唯一性和身份标志。为了解决这个问题,在 Kubernetes 1.7 中
我们针对 StatefulSet API 对象引入了 .spec.podManagementPolicy
。
此选项仅影响扩缩操作的行为。更新不受影响。
OrderedReady Pod 管理策略 OrderedReady
Pod 管理策略是 StatefulSet 的默认选项。它告诉
StatefulSet 控制器遵循上文展示的顺序性保证。
Parallel Pod 管理策略 Parallel
Pod 管理策略告诉 StatefulSet 控制器并行的终止所有 Pod,
在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。
apiVersion : v1
kind : Service
metadata :
name : nginx
labels :
app : nginx
spec :
ports :
- port : 80
name : web
clusterIP : None
selector :
app : nginx
---
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : web
spec :
serviceName : "nginx"
podManagementPolicy : "Parallel"
replicas : 2
selector :
matchLabels :
app : nginx
template :
metadata :
labels :
app : nginx
spec :
containers :
- name : nginx
image : registry.k8s.io/nginx-slim:0.8
ports :
- containerPort : 80
name : web
volumeMounts :
- name : www
mountPath : /usr/share/nginx/html
volumeClaimTemplates :
- metadata :
name : www
spec :
accessModes : [ "ReadWriteOnce" ]
resources :
requests :
storage : 1Gi
下载上面的例子并保存为 web-parallel.yaml
。
这份清单和你在上文下载的完全一样,只是 web
StatefulSet 的
.spec.podManagementPolicy
设置成了 Parallel
。
在一个终端窗口监视 StatefulSet 中的 Pod。
kubectl get pod -l app = nginx -w
在另一个终端窗口创建清单中的 StatefulSet 和 Service:
kubectl apply -f web-parallel.yaml
service/nginx created
statefulset.apps/web created
查看你在第一个终端中运行的 kubectl get
命令的输出。
kubectl get pod -l app = nginx -w
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-1 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 10s
web-1 1/1 Running 0 10s
StatefulSet 控制器同时启动了 web-0
和 web-1
。
保持第二个终端打开,并在另一个终端窗口中扩容 StatefulSet:
kubectl scale statefulset/web --replicas= 4
statefulset.apps/web scaled
在 kubectl get
命令运行的终端里检查它的输出。
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 7s
web-3 0/1 ContainerCreating 0 7s
web-2 1/1 Running 0 10s
web-3 1/1 Running 0 26s
StatefulSet 启动了两个新的 Pod,而且在启动第二个之前并没有等待第一个变成 Running 和 Ready 状态。
清理现场 你应该打开两个终端,准备在清理过程中运行 kubectl
命令。
kubectl delete sts web
# sts is an abbreviation for statefulset
你可以监视 kubectl get
来查看那些 Pod 被删除
kubectl get pod -l app = nginx -w
web-3 1/1 Terminating 0 9m
web-2 1/1 Terminating 0 9m
web-3 1/1 Terminating 0 9m
web-2 1/1 Terminating 0 9m
web-1 1/1 Terminating 0 44m
web-0 1/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-3 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-1 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-2 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-1 0/1 Terminating 0 44m
web-1 0/1 Terminating 0 44m
web-1 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-3 0/1 Terminating 0 9m
web-3 0/1 Terminating 0 9m
web-3 0/1 Terminating 0 9m
在删除过程中,StatefulSet 将并发的删除所有 Pod,在删除一个
Pod 前不会等待它的顺序后继者终止。
关闭 kubectl get
命令运行的终端并删除 nginx
Service:
删除本教程中用到的 PersistentVolume 卷的持久化存储介质。
+shell +kubectl get pvc +
+ +NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE +www-web-0 Bound pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO standard 25m +www-web-1 Bound pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO standard 24m +www-web-2 Bound pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO standard 15m +www-web-3 Bound pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO standard 15m +www-web-4 Bound pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO standard 14m +
+
+shell +kubectl get pv +
+ +NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE +pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO Delete Bound default/www-web-3 standard 15m +pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO Delete Bound default/www-web-0 standard 25m +pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO Delete Bound default/www-web-4 standard 14m +pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO Delete Bound default/www-web-1 standard 24m +pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO Delete Bound default/www-web-2 standard 15m +
+
+shell +kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4 +
+
+ +persistentvolumeclaim "www-web-0" deleted +persistentvolumeclaim "www-web-1" deleted +persistentvolumeclaim "www-web-2" deleted +persistentvolumeclaim "www-web-3" deleted +persistentvolumeclaim "www-web-4" deleted +
+
+shell +kubectl get pvc +
+
+ +No resources found in default namespace. +
说明: 你需要删除本教程中用到的 PersistentVolume 卷的持久化存储介质。
基于你的环境、存储配置和制备方式,按照必需的步骤保证回收所有的存储。
5.6.2 - 示例:使用持久卷部署 WordPress 和 MySQL 本示例描述了如何通过 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。
这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。
PersistentVolume (PV)是在集群里由管理员手动制备或
Kubernetes 通过 StorageClass 动态制备的一块存储。
PersistentVolumeClaim
是用户对存储的请求,该请求可由某个 PV 来满足。
PersistentVolumes 和 PersistentVolumeClaims 独立于 Pod 生命周期而存在,
在 Pod 重启、重新调度甚至删除过程中用于保存数据。
说明: 本教程中提供的文件使用 GA Deployment API,并且特定于 kubernetes 1.9 或更高版本。
如果你希望将本教程与 Kubernetes 的早期版本一起使用,请相应地更新 API 版本,或参考本教程的早期版本。
教程目标 创建 PersistentVolumeClaims 和 PersistentVolumes 创建 kustomization.yaml
以使用Secret 生成器 MySQL 资源配置 WordPress 资源配置 kubectl apply -k ./
来应用整个 kustomization 目录清理 准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
此例在 kubectl
1.14 或者更高版本有效。
下载下面的配置文件:
mysql-deployment.yaml
wordpress-deployment.yaml
创建 PersistentVolumeClaims 和 PersistentVolumes MySQL 和 Wordpress 都需要一个 PersistentVolume 来存储数据。
它们的 PersistentVolumeClaims 将在部署步骤中创建。
许多集群环境都安装了默认的 StorageClass。如果在 PersistentVolumeClaim 中未指定 StorageClass,
则使用集群的默认 StorageClass。
创建 PersistentVolumeClaim 时,将根据 StorageClass 配置动态制备一个 PersistentVolume。
警告: 在本地集群中,默认的 StorageClass 使用 hostPath
制备程序。hostPath
卷仅适用于开发和测试。
使用 hostPath
卷时,你的数据位于 Pod 调度到的节点上的 /tmp
中,并且不会在节点之间移动。
如果 Pod 死亡并被调度到集群中的另一个节点,或者该节点重新启动,则数据将丢失。
说明: 如果要建立需要使用 hostPath
制备程序的集群,
则必须在 controller-manager
组件中设置 --enable-hostpath-provisioner
标志。
说明: 如果你已经有运行在 Google Kubernetes Engine 的集群,
请参考此指南 。
创建 kustomization.yaml 创建 Secret 生成器 Secret 是存储诸如密码或密钥之类敏感数据的对象。
从 1.14 开始,kubectl
支持使用一个 kustomization 文件来管理 Kubernetes 对象。
你可以通过 kustomization.yaml
中的生成器创建一个 Secret。
通过以下命令在 kustomization.yaml
中添加一个 Secret 生成器。
你需要将 YOUR_PASSWORD
替换为自己要用的密码。
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
literals:
- password=YOUR_PASSWORD
EOF
补充 MySQL 和 WordPress 的资源配置 以下清单文件描述的是一个单实例的 MySQL Deployment。MySQL 容器将 PersistentVolume 挂载在 /var/lib/mysql
。
MYSQL_ROOT_PASSWORD
环境变量根据 Secret 设置数据库密码。
apiVersion : v1
kind : Service
metadata :
name : wordpress-mysql
labels :
app : wordpress
spec :
ports :
- port : 3306
selector :
app : wordpress
tier : mysql
clusterIP : None
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : mysql-pv-claim
labels :
app : wordpress
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 20Gi
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : wordpress-mysql
labels :
app : wordpress
spec :
selector :
matchLabels :
app : wordpress
tier : mysql
strategy :
type : Recreate
template :
metadata :
labels :
app : wordpress
tier : mysql
spec :
containers :
- image : mysql:5.6
name : mysql
env :
- name : MYSQL_ROOT_PASSWORD
valueFrom :
secretKeyRef :
name : mysql-pass
key : password
ports :
- containerPort : 3306
name : mysql
volumeMounts :
- name : mysql-persistent-storage
mountPath : /var/lib/mysql
volumes :
- name : mysql-persistent-storage
persistentVolumeClaim :
claimName : mysql-pv-claim
以下清单文件描述的是一个单实例 WordPress Deployment。WordPress 容器将 PersistentVolume
挂载到 /var/www/html
,用于保存网站数据文件。
WORDPRESS_DB_HOST
环境变量设置上面定义的 MySQL Service 的名称,WordPress 将通过 Service 访问数据库。
WORDPRESS_DB_PASSWORD
环境变量根据使用 kustomize 生成的 Secret 设置数据库密码。
apiVersion : v1
kind : Service
metadata :
name : wordpress
labels :
app : wordpress
spec :
ports :
- port : 80
selector :
app : wordpress
tier : frontend
type : LoadBalancer
---
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : wp-pv-claim
labels :
app : wordpress
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 20Gi
---
apiVersion : apps/v1
kind : Deployment
metadata :
name : wordpress
labels :
app : wordpress
spec :
selector :
matchLabels :
app : wordpress
tier : frontend
strategy :
type : Recreate
template :
metadata :
labels :
app : wordpress
tier : frontend
spec :
containers :
- image : wordpress:4.8-apache
name : wordpress
env :
- name : WORDPRESS_DB_HOST
value : wordpress-mysql
- name : WORDPRESS_DB_PASSWORD
valueFrom :
secretKeyRef :
name : mysql-pass
key : password
ports :
- containerPort : 80
name : wordpress
volumeMounts :
- name : wordpress-persistent-storage
mountPath : /var/www/html
volumes :
- name : wordpress-persistent-storage
persistentVolumeClaim :
claimName : wp-pv-claim
下载 MySQL Deployment 配置文件。
curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
下载 WordPress 配置文件。
curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
将上述内容追加到 kustomization.yaml
文件。
cat <<EOF >>./kustomization.yaml
resources:
- mysql-deployment.yaml
- wordpress-deployment.yaml
EOF
应用和验证 kustomization.yaml
包含用于部署 WordPress 网站以及 MySQL 数据库的所有资源。你可以通过以下方式应用目录:
现在,你可以验证所有对象是否存在。
通过运行以下命令验证 Secret 是否存在:
响应应如下所示:
NAME TYPE DATA AGE
mysql-pass-c57bb4t7mf Opaque 1 9s
验证是否已动态制备 PersistentVolume:
响应应如下所示:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pv-claim Bound pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s
wp-pv-claim Bound pvc-8cd0df54-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s
通过运行以下命令来验证 Pod 是否正在运行:
说明: 等待 Pod 状态变成 RUNNING
可能会花费几分钟。
响应应如下所示:
NAME READY STATUS RESTARTS AGE
wordpress-mysql-1894417608-x5dzt 1/1 Running 0 40s
通过运行以下命令来验证 Service 是否正在运行:
kubectl get services wordpress
响应应如下所示:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
wordpress ClusterIP 10.0.0.89 <pending> 80:32406/TCP 4m
说明: Minikube 只能通过 NodePort 公开服务。EXTERNAL-IP 始终处于 pending 状态。
运行以下命令以获取 WordPress 服务的 IP 地址:
minikube service wordpress --url
响应应如下所示:
http://1.2.3.4:32406
复制 IP 地址,然后将页面加载到浏览器中来查看你的站点。
你应该看到类似于以下屏幕截图的 WordPress 设置页面。
警告: 不要在此页面上保留 WordPress 安装。如果其他用户找到了它,他们可以在你的实例上建立一个网站并使用它来提供恶意内容。 通过创建用户名和密码来安装 WordPress 或删除你的实例。
清理现场 运行以下命令删除你的 Secret、Deployment、Service 和 PersistentVolumeClaims:
接下来 5.6.3 - 示例:使用 StatefulSet 部署 Cassandra 本教程描述了如何在 Kubernetes 上运行 Apache Cassandra 。
数据库 Cassandra 需要永久性存储提供数据持久性(应用状态 )。
在此示例中,自定义 Cassandra seed provider 使数据库在接入 Cassandra 集群时能够发现新的 Cassandra 实例。
使用StatefulSet 可以更轻松地将有状态的应用程序部署到你的 Kubernetes 集群中。
有关本教程中使用的功能的更多信息,
请参阅 StatefulSet 。
说明: Cassandra 和 Kubernetes 都使用术语节点 来表示集群的成员。
在本教程中,属于 StatefulSet 的 Pod 是 Cassandra 节点,并且是 Cassandra 集群的成员(称为 ring )。
当这些 Pod 在你的 Kubernetes 集群中运行时,Kubernetes 控制平面会将这些 Pod 调度到 Kubernetes 的
节点 上。
当 Cassandra 节点启动时,使用 seed 列表 来引导发现 ring 中的其他节点。
本教程部署了一个自定义的 Cassandra seed provider,
使数据库可以发现 Kubernetes 集群中出现的新的 Cassandra Pod。
教程目标 创建并验证 Cassandra 无头(headless)Service 。 使用 StatefulSet 创建一个 Cassandra ring。 验证 StatefulSet。 修改 StatefulSet。 删除 StatefulSet 及其 Pod 。 准备开始 你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
建议在至少有两个节点的集群上运行本教程,且这些节点不作为控制平面主机。
如果你还没有集群,你可以通过 Minikube
构建一个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要完成本教程,你应该已经熟悉 Pod 、
Service 和
StatefulSet 。
为 Cassandra 创建无头(headless) Services 在 Kubernetes 中,一个 Service
描述了一组执行相同任务的 Pod 。
以下 Service 用于在 Cassandra Pod 和集群中的客户端之间进行 DNS 查找:
apiVersion : v1
kind : Service
metadata :
labels :
app : cassandra
name : cassandra
spec :
clusterIP : None
ports :
- port : 9042
selector :
app : cassandra
创建一个 Service 来跟踪 cassandra-service.yaml
文件中的所有 Cassandra StatefulSet:
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml
验证(可选) 获取 Cassandra Service。
kubectl get svc cassandra
响应是:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cassandra ClusterIP None <none> 9042/TCP 45s
如果没有看到名为 cassandra
的服务,则表示创建失败。
请阅读调试服务 ,以解决常见问题。
使用 StatefulSet 创建 Cassandra Ring 下面包含的 StatefulSet 清单创建了一个由三个 Pod 组成的 Cassandra ring。
说明: 本示例使用 Minikube 的默认配置程序。
请为正在使用的云更新以下 StatefulSet。
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : cassandra
labels :
app : cassandra
spec :
serviceName : cassandra
replicas : 3
selector :
matchLabels :
app : cassandra
template :
metadata :
labels :
app : cassandra
spec :
terminationGracePeriodSeconds : 1800
containers :
- name : cassandra
image : gcr.io/google-samples/cassandra:v13
imagePullPolicy : Always
ports :
- containerPort : 7000
name : intra-node
- containerPort : 7001
name : tls-intra-node
- containerPort : 7199
name : jmx
- containerPort : 9042
name : cql
resources :
limits :
cpu : "500m"
memory : 1Gi
requests :
cpu : "500m"
memory : 1Gi
securityContext :
capabilities :
add :
- IPC_LOCK
lifecycle :
preStop :
exec :
command :
- /bin/sh
- -c
- nodetool drain
env :
- name : MAX_HEAP_SIZE
value : 512M
- name : HEAP_NEWSIZE
value : 100M
- name : CASSANDRA_SEEDS
value : "cassandra-0.cassandra.default.svc.cluster.local"
- name : CASSANDRA_CLUSTER_NAME
value : "K8Demo"
- name : CASSANDRA_DC
value : "DC1-K8Demo"
- name : CASSANDRA_RACK
value : "Rack1-K8Demo"
- name : POD_IP
valueFrom :
fieldRef :
fieldPath : status.podIP
readinessProbe :
exec :
command :
- /bin/bash
- -c
- /ready-probe.sh
initialDelaySeconds : 15
timeoutSeconds : 5
# 这些卷挂载是持久的。它们类似内联申领,但并不完全相同,
# 因为这些卷挂载的名称需要与 StatefulSet 中某 Pod 卷完全匹配。
volumeMounts :
- name : cassandra-data
mountPath : /cassandra_data
# 这些将被控制器转换为卷申领,并挂载在上述路径。
# 请勿将此设置用于生产环境,除非使用了 GCEPersistentDisk 或其他 SSD 持久盘。
volumeClaimTemplates :
- metadata :
name : cassandra-data
spec :
accessModes : [ "ReadWriteOnce" ]
storageClassName : fast
resources :
requests :
storage : 1Gi
---
kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : fast
provisioner : k8s.io/minikube-hostpath
parameters :
type : pd-ssd
使用 cassandra-statefulset.yaml
文件创建 Cassandra StatefulSet:
# 如果你能未经修改地应用 cassandra-statefulset.yaml,请使用此命令
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml
如果你为了适合你的集群需要修改 cassandra-statefulset.yaml
,
下载 https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml ,
然后应用修改后的清单。
# 如果使用本地的 cassandra-statefulset.yaml ,请使用此命令
kubectl apply -f cassandra-statefulset.yaml
验证 Cassandra StatefulSet 获取 Cassandra StatefulSet:
kubectl get statefulset cassandra
响应应该与此类似:
NAME DESIRED CURRENT AGE
cassandra 3 0 13s
StatefulSet
资源会按顺序部署 Pod。
获取 Pod 查看已排序的创建状态:
kubectl get pods -l= "app=cassandra"
响应应该与此类似:
NAME READY STATUS RESTARTS AGE
cassandra-0 1/1 Running 0 1m
cassandra-1 0/1 ContainerCreating 0 8s
这三个 Pod 要花几分钟的时间才能部署。部署之后,相同的命令将返回类似于以下的输出:
NAME READY STATUS RESTARTS AGE
cassandra-0 1/1 Running 0 10m
cassandra-1 1/1 Running 0 9m
cassandra-2 1/1 Running 0 8m
运行第一个 Pod 中的 Cassandra nodetool ,
以显示 ring 的状态。
kubectl exec -it cassandra-0 -- nodetool status
响应应该与此类似:
Datacenter: DC1-K8Demo
======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 172.17.0.5 83.57 KiB 32 74.0% e2dd09e6-d9d3-477e-96c5-45094c08db0f Rack1-K8Demo
UN 172.17.0.4 101.04 KiB 32 58.8% f89d6835-3a42-4419-92b3-0e62cae1479c Rack1-K8Demo
UN 172.17.0.6 84.74 KiB 32 67.1% a6a1e8c2-3dc5-4417-b1a0-26507af2aaad Rack1-K8Demo
修改 Cassandra StatefulSet 使用 kubectl edit
修改 Cassandra StatefulSet 的大小。
运行以下命令:
kubectl edit statefulset cassandra
此命令你的终端中打开一个编辑器。需要更改的是 replicas
字段。下面是 StatefulSet 文件的片段示例:
# 请编辑以下对象。以 '#' 开头的行将被忽略,
# 且空文件将放弃编辑。如果保存此文件时发生错误,
# 将重新打开并显示相关故障。
apiVersion : apps/v1
kind : StatefulSet
metadata :
creationTimestamp : 2016-08-13T18:40:58Z
generation : 1
labels :
app : cassandra
name : cassandra
namespace : default
resourceVersion : "323"
uid : 7a219483-6185-11e6-a910-42010a8a0fc0
spec :
replicas : 3
将副本数(replicas)更改为 4,然后保存清单。
StatefulSet 现在可以扩展到运行 4 个 Pod。
获取 Cassandra StatefulSet 验证更改:
kubectl get statefulset cassandra
响应应该与此类似:
NAME DESIRED CURRENT AGE
cassandra 4 4 36m
清理现场 删除或缩小 StatefulSet 不会删除与 StatefulSet 关联的卷。
这个设置是出于安全考虑,因为你的数据比自动清除所有相关的 StatefulSet 资源更有价值。
警告: 根据存储类和回收策略,删除 PersistentVolumeClaims 可能导致关联的卷也被删除。
千万不要认为其容量声明被删除,你就能访问数据。
运行以下命令(连在一起成为一个单独的命令)删除 Cassandra StatefulSet 中的所有内容:
grace = $( kubectl get pod cassandra-0 -o= jsonpath = '{.spec.terminationGracePeriodSeconds}' ) \
&& kubectl delete statefulset -l app = cassandra \
&& echo "Sleeping ${ grace } seconds" 1>&2 \
&& sleep $grace \
&& kubectl delete persistentvolumeclaim -l app = cassandra
运行以下命令,删除你为 Cassandra 设置的 Service:
kubectl delete service -l app = cassandra
Cassandra 容器环境变量 本教程中的 Pod 使用来自 Google 容器镜像库
的 gcr.io/google-samples/cassandra:v13
镜像。上面的 Docker 镜像基于 debian-base ,
并且包含 OpenJDK 8。
该镜像包括来自 Apache Debian 存储库的标准 Cassandra 安装。
通过使用环境变量,你可以更改插入到 cassandra.yaml
中的值。
环境变量 默认值 CASSANDRA_CLUSTER_NAME
'Test Cluster'
CASSANDRA_NUM_TOKENS
32
CASSANDRA_RPC_ADDRESS
0.0.0.0
接下来