1 - Kubernetes 文档
Kubernetes 是一个开源的容器编排引擎,用来对容器化应用进行自动部署、 扩缩和管理。此开源项目由云原生计算基金会(CNCF)托管。
1.1 - Kubernetes 文档支持的版本
本网站包含当前版本和之前四个版本的 Kubernetes 文档。
2 - 入门
本节列出了设置和运行 Kubernetes 的不同方法。
安装 Kubernetes 时,请根据以下条件选择安装类型:易于维护、安全性、可控制性、可用资源以及操作和管理 Kubernetes 集群所需的专业知识。
可以下载 Kubernetes ,在本地机器、云或你自己的数据中心上部署 Kubernetes 集群。
如果你不想自己管理 Kubernetes 集群,则可以选择托管服务,包括经过认证的平台 。
在各种云和裸机环境中,还有其他标准化和定制的解决方案。
学习环境
如果正打算学习 Kubernetes,请使用 Kubernetes 社区支持
或生态系统中的工具在本地计算机上设置 Kubernetes 集群。
请参阅安装工具 。
生产环境
在评估生产环境 的解决方案时,
请考虑要自己管理 Kubernetes 集群(或相关抽象)的哪些方面,将哪些托付给提供商。
对于你自己管理的集群,官方支持的用于部署 Kubernetes 的工具是
kubeadm 。
接下来
Kubernetes 的设计是让其控制平面 在 Linux 上运行的。
在集群中,你可以在 Linux 或其他操作系统(包括 Windows)上运行应用程序。
2.1 - 学习环境
kind
你可以使用 kind
来在本地计算机上运行 Kubernetes。
此工具要求你已经安装并配置了 Docker 。
kind 快速入门 页面
为你展示了如何开始使用 kind 的相关信息。
minikube
与 kind
类似,minikube
是一个允许你在
本地运行 Kubernetes 的工具。minikube
在你的个人计算机上运行一个单节点的
Kubernetes 集群(包括 Windows、macOS 和 Linux PC 机),这样你可以尝试
Kubernetes 或者执行每天的开发工作。
如果你所关注的是如何安装该工具,可以查阅官方的
Get Started!
文档。
2.2 - 生产环境
生产质量的 Kubernetes 集群需要规划和准备。
如果你的 Kubernetes 集群是用来运行关键负载的,该集群必须被配置为弹性的(Resilient)。
本页面阐述你在安装生产就绪的集群或将现有集群升级为生产用途时可以遵循的步骤。
如果你已经熟悉生产环境安装,因此只关注一些链接,则可以跳到接下来 节。
生产环境考量
通常,一个生产用 Kubernetes 集群环境与个人学习、开发或测试环境所使用的
Kubernetes 相比有更多的需求。生产环境可能需要被很多用户安全地访问,需要
提供一致的可用性,以及能够与需求变化相适配的资源。
在你决定在何处运行你的生产用 Kubernetes 环境(在本地或者在云端),以及
你希望承担或交由他人承担的管理工作量时,需要考察以下因素如何影响你对
Kubernetes 集群的需求:
可用性 :一个单机的 Kubernetes 学习环境
具有单点失效特点。创建高可用的集群则意味着需要考虑:
将控制面与工作节点分开
在多个节点上提供控制面组件的副本
为针对集群的 API 服务器
的流量提供负载均衡
随着负载的合理需要,提供足够的可用的(或者能够迅速变为可用的)工作节点
规模 :如果你预期你的生产用 Kubernetes 环境要承受固定量的请求,
你可能可以针对所需要的容量来一次性完成安装。
不过,如果你预期服务请求会随着时间增长,或者因为类似季节或者特殊事件的
原因而发生剧烈变化,你就需要规划如何处理请求上升时对控制面和工作节点
的压力,或者如何缩减集群规模以减少未使用资源的消耗。
安全性与访问管理 :在你自己的学习环境 Kubernetes 集群上,你拥有完全的管理员特权。
但是针对运行着重要工作负载的共享集群,用户账户不止一两个时,就需要更细粒度
的方案来确定谁或者哪些主体可以访问集群资源。
你可以使用基于角色的访问控制(RBAC )
和其他安全机制来确保用户和负载能够访问到所需要的资源,同时确保工作负载及集群
自身仍然是安全的。
你可以通过管理策略 和
容器资源 来
针对用户和工作负载所可访问的资源设置约束,
在自行构造 Kubernetes 生产环境之前,请考虑将这一任务的部分或者全部交给
云方案承包服务
提供商或者其他 Kubernetes 合作伙伴 。
选项有:
无服务 :仅是在第三方设备上运行负载,完全不必管理集群本身。你需要为
CPU 用量、内存和磁盘请求等付费。
托管控制面 :让供应商决定集群控制面的规模和可用性,并负责打补丁和升级等操作。
托管工作节点 :配置一个节点池来满足你的需要,由供应商来确保节点始终可用,
并在需要的时候完成升级。
集成 :有一些供应商能够将 Kubernetes 与一些你可能需要的其他服务集成,
这类服务包括存储、容器镜像仓库、身份认证方法以及开发工具等。
无论你是自行构造一个生产用 Kubernetes 集群还是与合作伙伴一起协作,请审阅
下面章节以评估你的需求,因为这关系到你的集群的 控制面 、工作节点 、
用户访问 以及 负载资源 。
生产用集群安装
在生产质量的 Kubernetes 集群中,控制面用不同的方式来管理集群和可以
分布到多个计算机上的服务。每个工作节点则代表的是一个可配置来运行
Kubernetes Pods 的实体。
生产用控制面
最简单的 Kubernetes 集群中,整个控制面和工作节点服务都运行在同一台机器上。
你可以通过添加工作节点来提升环境能力,正如
Kubernetes 组件 示意图所示。
如果只需要集群在很短的一段时间内可用,或者可以在某些事物出现严重问题时直接丢弃,
这种配置可能符合你的需要。
如果你需要一个更为持久的、高可用的集群,那么你就需要考虑扩展控制面的方式。
根据设计,运行在一台机器上的单机控制面服务不是高可用的。
如果保持集群处于运行状态并且需要确保在出现问题时能够被修复这点很重要,
可以考虑以下步骤:
管理证书 :控制面服务之间的安全通信是通过证书来完成的。证书是在部署期间
自动生成的,或者你也可以使用你自己的证书机构来生成它们。
参阅 PKI 证书和需求 了解细节。
为 API 服务器配置负载均衡 :配置负载均衡器来将外部的 API 请求散布给运行在
不同节点上的 API 服务实例。参阅
创建外部负载均衡器
了解细节。
创建多控制面系统 :为了实现高可用性,控制面不应被限制在一台机器上。
如果控制面服务是使用某 init 服务(例如 systemd)来运行的,每个服务应该
至少运行在三台机器上。不过,将控制面作为服务运行在 Kubernetes Pods
中可以确保你所请求的个数的服务始终保持可用。
调度器应该是可容错的,但不是高可用的。
某些部署工具会安装 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
以便以这些方式来添加节点。
向集群中添加 Windows 节点 :Kubernetes 提供对 Windows 工作节点的支持;
这使得你可以运行实现于 Windows 容器内的工作负载。参阅
Kubernetes 中的 Windows
了解进一步的详细信息。
扩缩节点 :制定一个扩充集群容量的规划,你的集群最终会需要这一能力。
参阅大规模集群考察事项
以确定你所需要的节点数;这一规模是基于你要运行的 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 可以运行在上面。本文概述了所涉及的内容并描述了与节点设置相关的任务。
本文列出了在 Linux 上结合 Kubernetes 使用的几种通用容器运行时的详细信息:
提示:对于其他操作系统,请查阅特定于你所使用平台的相关文档。
Cgroup 驱动程序
控制组用来约束分配给进程的资源。
当某个 Linux 系统发行版使用 systemd
作为其初始化系统时,初始化进程会生成并使用一个 root 控制组 (cgroup
), 并充当 cgroup 管理器。
Systemd 与 cgroup 集成紧密,并将为每个 systemd 单元分配一个 cgroup。
你也可以配置容器运行时和 kubelet 使用 cgroupfs
。
连同 systemd 一起使用 cgroupfs
意味着将有两个不同的 cgroup 管理器。
单个 cgroup 管理器将简化分配资源的视图,并且默认情况下将对可用资源和使用
中的资源具有更一致的视图。
当有两个管理器共存于一个系统中时,最终将对这些资源产生两种视图。
在此领域人们已经报告过一些案例,某些节点配置让 kubelet 和 docker 使用
cgroupfs
,而节点上运行的其余进程则使用 systemd; 这类节点在资源压力下
会变得不稳定。
更改设置,令容器运行时和 kubelet 使用 systemd
作为 cgroup 驱动,以此使系统更为稳定。
对于 Docker, 设置 native.cgroupdriver=systemd
选项。
注意:更改已加入集群的节点的 cgroup 驱动是一项敏感的操作。
如果 kubelet 已经使用某 cgroup 驱动的语义创建了 pod,更改运行时以使用
别的 cgroup 驱动,当为现有 Pods 重新创建 PodSandbox 时会产生错误。
重启 kubelet 也可能无法解决此类问题。
如果你有切实可行的自动化方案,使用其他已更新配置的节点来替换该节点,
或者使用自动化方案来重新安装。
Cgroup v2
Cgroup v2 是 cgroup Linux API 的下一个版本。与 cgroup v1 不同的是,
Cgroup v2 只有一个层次结构,而不是每个控制器有一个不同的层次结构。
新版本对 cgroup v1 进行了多项改进,其中一些改进是:
更简洁、更易于使用的 API
可将安全子树委派给容器
更新的功能,如压力失速信息(Pressure Stall Information)
尽管内核支持混合配置,即其中一些控制器由 cgroup v1 管理,另一些由 cgroup v2 管理,
Kubernetes 仅支持使用同一 cgroup 版本来管理所有控制器。
如果 systemd 默认不使用 cgroup v2,你可以通过在内核命令行中添加
systemd.unified_cgroup_hierarchy=1
来配置系统去使用它。
# dnf install -y grubby && \
sudo grubby \
--update-kernel= ALL \
--args= ”systemd.unified_cgroup_hierarchy= 1"
要应用配置,必须重新启动节点。
切换到 cgroup v2 时,用户体验不应有任何明显差异,
除非用户直接在节点上或在容器内访问 cgroup 文件系统。
为了使用它,CRI 运行时也必须支持 cgroup v2。
将 kubeadm 托管的集群迁移到 systemd
驱动
如果你想迁移到现有 kubeadm 托管集群中的 systemd
cgroup 驱动程序,
遵循此迁移指南 。
容器运行时
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
containerd
本节包含使用 containerd 作为 CRI 运行时的必要步骤。
使用以下命令在系统上安装 Containerd:
安装和配置的先决条件:
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 设置必需的 sysctl 参数,这些参数在重新启动后仍然存在。
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
# 应用 sysctl 参数而无需重新启动
sudo sysctl --system
安装 containerd:
从官方Docker仓库安装 containerd.io
软件包。可以在
安装 Docker 引擎
中找到有关为各自的 Linux 发行版设置 Docker 存储库和安装 containerd.io
软件包的说明。
配置 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
# (可选 - 不过强烈建议) 禁止 Windows Defender 扫描 containerd
Add-MpPreference -ExclusionProcess "$Env:ProgramFiles\containerd\containerd.exe"
启动 containerd:
.\containerd.exe --register-service
Start-Service 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
如果您应用此更改,请确保再次重新启动 containerd:
sudo systemctl restart containerd
当使用 kubeadm 时,请手动配置
kubelet 的 cgroup 驱动 .
CRI-O
本节包含安装 CRI-O 作为容器运行时的必要步骤。
使用以下命令在系统中安装 CRI-O:
说明:
CRI-O 的主要以及次要版本必须与 Kubernetes 的主要和次要版本相匹配。
更多信息请查阅
CRI-O 兼容性列表 。
安装并配置前置环境:
# 创建 .conf 文件以在启动时加载模块
cat <<EOF | sudo tee /etc/modules-load.d/crio.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 配置 sysctl 参数,这些配置在重启之后仍然起作用
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
在下列操作系统上安装 CRI-O, 使用下表中合适的值设置环境变量 OS
:
操作系统
$OS
Debian Unstable
Debian_Unstable
Debian Testing
Debian_Testing
然后,将 `$VERSION` 设置为与你的 Kubernetes 相匹配的 CRI-O 版本。
例如,如果你要安装 CRI-O 1.20, 请设置 `VERSION=1.20`.
你也可以安装一个特定的发行版本。
例如要安装 1.20.0 版本,设置 `VERSION=1.20.0:1.20.0`.
然后执行
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION /$OS /Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS /Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
sudo apt-get update
sudo apt-get install cri-o cri-o-runc
在下列操作系统上安装 CRI-O, 使用下表中合适的值设置环境变量 OS
:
操作系统
$OS
Ubuntu 20.04
xUbuntu_20.04
Ubuntu 19.10
xUbuntu_19.10
Ubuntu 19.04
xUbuntu_19.04
Ubuntu 18.04
xUbuntu_18.04
然后,将 `$VERSION` 设置为与你的 Kubernetes 相匹配的 CRI-O 版本。
例如,如果你要安装 CRI-O 1.20, 请设置 `VERSION=1.20`.
你也可以安装一个特定的发行版本。
例如要安装 1.20.0 版本,设置 `VERSION=1.20:1.20.0`.
然后执行
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /
EOF
cat <<EOF | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list
deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /
EOF
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS /Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers.gpg add -
curl -L https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION /$OS /Release.key | sudo apt-key --keyring /etc/apt/trusted.gpg.d/libcontainers-cri-o.gpg add -
sudo apt-get update
sudo apt-get install cri-o cri-o-runc
在下列操作系统上安装 CRI-O, 使用下表中合适的值设置环境变量 OS
:
操作系统
$OS
Centos 8
CentOS_8
Centos 8 Stream
CentOS_8_Stream
Centos 7
CentOS_7
然后,将 `$VERSION` 设置为与你的 Kubernetes 相匹配的 CRI-O 版本。
例如,如果你要安装 CRI-O 1.20, 请设置 `VERSION=1.20`.
你也可以安装一个特定的发行版本。
例如要安装 1.20.0 版本,设置 `VERSION=1.20:1.20.0`.
然后执行
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS /devel:kubic:libcontainers:stable.repo
sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable:cri-o:$VERSION .repo https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable:cri-o:$VERSION /$OS /devel:kubic:libcontainers:stable:cri-o:$VERSION .repo
sudo yum install cri-o
sudo zypper install cri-o
将 $VERSION
设置为与你的 Kubernetes 相匹配的 CRI-O 版本。
例如,如果要安装 CRI-O 1.20,请设置 VERSION=1.20
。
你可以用下列命令查找可用的版本:
sudo dnf module list cri-o
CRI-O 不支持在 Fedora 上固定到特定的版本。
然后执行
sudo dnf module enable cri-o:$VERSION
sudo dnf install cri-o --now
启动 CRI-O:
sudo systemctl daemon-reload
sudo systemctl enable crio --now
参阅CRI-O 安装指南
了解进一步的详细信息。
cgroup 驱动
默认情况下,CRI-O 使用 systemd cgroup 驱动程序。要切换到 cgroupfs
驱动程序,或者编辑 / etc / crio / crio.conf
或放置一个插件
在 /etc/crio/crio.conf.d/02-cgroup-manager.conf
中的配置,例如:
[crio.runtime]
conmon_cgroup = "pod"
cgroup_manager = "cgroupfs"
另请注意更改后的 conmon_cgroup
,将 CRI-O 与 cgroupfs
一起使用时,
必须将其设置为 pod
。通常有必要保持 kubelet 的 cgroup 驱动程序配置
(通常透过 kubeadm 完成)和 CRI-O 一致。
Docker
在每个节点上,根据安装 Docker 引擎
为你的 Linux 发行版安装 Docker。
你可以在此文件中找到最新的经过验证的 Docker 版本
依赖关系 。
配置 Docker 守护程序,尤其是使用 systemd 来管理容器的 cgroup。
sudo mkdir /etc/docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
说明:
对于运行 Linux 内核版本 4.0 或更高版本,或使用 3.10.0-51 及更高版本的 RHEL
或 CentOS 的系统,overlay2
是首选的存储驱动程序。
重新启动 Docker 并在启动时启用:
sudo systemctl enable docker
sudo systemctl daemon-reload
sudo systemctl restart docker
2.2.2 - Turnkey 云解决方案
本页列示 Kubernetes 认证解决方案供应商。
在每一个供应商分页,你可以学习如何安装和设置生产就绪的集群。
2.2.3 - 使用部署工具安装 Kubernetes
2.2.3.1 - 使用 kubeadm 引导集群
2.2.3.1.1 - 安装 kubeadm
本页面显示如何安装 kubeadm
工具箱。
有关在执行此安装过程后如何使用 kubeadm 创建集群的信息,请参见
使用 kubeadm 创建集群 页面。
准备开始
一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux
发行版以及一些不提供包管理器的发行版提供通用的指令
每台机器 2 GB 或更多的 RAM (如果少于这个数字将会影响你应用的运行内存)
2 CPU 核或更多
集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)
节点之中不可以有重复的主机名、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 集群就可以通过对应的适配器完成连接。
允许 iptables 检查桥接流量
确保 br_netfilter
模块被加载。这一操作可以通过运行 lsmod | grep 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
br_netfilter
EOF
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
更多的相关细节可查看网络插件需求 页面。
检查所需端口
控制平面节点
协议
方向
端口范围
作用
使用者
TCP
入站
6443
Kubernetes API 服务器
所有组件
TCP
入站
2379-2380
etcd 服务器客户端 API
kube-apiserver, etcd
TCP
入站
10250
Kubelet API
kubelet 自身、控制平面组件
TCP
入站
10251
kube-scheduler
kube-scheduler 自身
TCP
入站
10252
kube-controller-manager
kube-controller-manager 自身
工作节点
协议
方向
端口范围
作用
使用者
TCP
入站
10250
Kubelet API
kubelet 自身、控制平面组件
TCP
入站
30000-32767
NodePort 服务†
所有组件
† NodePort 服务 的默认端口范围。
使用 * 标记的任意端口号都可以被覆盖,所以你需要保证所定制的端口是开放的。
虽然控制平面节点已经包含了 etcd 的端口,你也可以使用自定义的外部 etcd 集群,或是指定自定义端口。
你使用的 Pod 网络插件 (见下) 也可能需要某些特定端口开启。由于各个 Pod 网络插件都有所不同,
请参阅他们各自文档中对端口的要求。
安装 runtime
为了在 Pod 中运行容器,Kubernetes 使用
容器运行时(Container Runtime) 。
默认情况下,Kubernetes 使用
容器运行时接口(Container Runtime Interface,CRI)
来与你所选择的容器运行时交互。
如果你不指定运行时,则 kubeadm 会自动尝试检测到系统上已经安装的运行时,
方法是扫描一组众所周知的 Unix 域套接字。
下面的表格列举了一些容器运行时及其对应的套接字路径:
运行时
域套接字
Docker
/var/run/dockershim.sock
containerd
/run/containerd/containerd.sock
CRI-O
/var/run/crio/crio.sock
如果同时检测到 Docker 和 containerd,则优先选择 Docker。
这是必然的,因为 Docker 18.09 附带了 containerd 并且两者都是可以检测到的,
即使你仅安装了 Docker。
如果检测到其他两个或多个运行时,kubeadm 输出错误信息并退出。
kubelet 通过内置的 dockershim
CRI 实现与 Docker 集成。
参阅容器运行时
以了解更多信息。
默认情况下, kubeadm 使用 Docker 作为容器运行时。
kubelet 通过内置的 dockershim
CRI 实现与 Docker 集成。
参阅容器运行时
以了解更多信息。
安装 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
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg 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 不支持的部分配置
安装 CNI 插件(大多数 Pod 网络都需要):
CNI_VERSION = "v0.8.2"
ARCH = "amd64"
sudo mkdir -p /opt/cni/bin
curl -L "https://github.com/containernetworking/plugins/releases/download/ ${ CNI_VERSION } /cni-plugins-linux- ${ ARCH } - ${ CNI_VERSION } .tgz" | sudo tar -C /opt/cni/bin -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.17.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://storage.googleapis.com/kubernetes-release/release/${ RELEASE } /bin/linux/${ ARCH } /{ kubeadm,kubelet,kubectl}
sudo chmod +x { kubeadm,kubelet,kubectl}
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
:
systemctl enable --now kubelet
说明:
Flatcar Container Linux 发行版会将 /usr/
目录挂载为一个只读文件系统。
在启动引导你的集群之前,你需要执行一些额外的操作来配置一个可写入的目录。
参见 kubeadm 故障排查指南
以了解如何配置一个可写入的目录。
kubelet 现在每隔几秒就会重启,因为它陷入了一个等待 kubeadm 指令的死循环。
容器运行时和 kubelet 都具有名字为
"cgroup driver"
的属性,该属性对于在 Linux 机器上管理 CGroups 而言非常重要。
警告:
你需要确保容器运行时和 kubelet 所使用的是相同的 cgroup 驱动,否则 kubelet
进程会失败。
相关细节可参见配置 cgroup 驱动 。
故障排查
如果你在使用 kubeadm 时遇到困难,请参阅我们的
故障排查文档 。
接下来
2.2.3.1.2 - 对 kubeadm 进行故障排查
与任何程序一样,你可能会在安装或者运行 kubeadm 时遇到错误。
本文列举了一些常见的故障场景,并提供可帮助你理解和解决这些问题的步骤。
如果你的问题未在下面列出,请执行以下步骤:
在安装过程中没有找到 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 阻塞
如果 Docker 停止并且不删除 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
:
sudo systemctl restart docker.service
sudo kubeadm reset
检查 docker 的日志也可能有用:
Pods 处于 RunContainerError
、CrashLoopBackOff
或者 Error
状态
在 kubeadm init
命令运行后,系统中不应该有 pods 处于这类状态。
在 kubeadm init
命令执行完后,如果有 pods 处于这些状态之一,请在 kubeadm
仓库提起一个 issue。coredns
(或者 kube-dns
) 应该处于 Pending
状态,
直到你部署了网络插件为止。
如果在部署完网络插件之后,有 Pods 处于 RunContainerError
、CrashLoopBackOff
或 Error
状态之一,并且coredns
(或者 kube-dns
)仍处于 Pending
状态,
那很可能是你安装的网络插件由于某种原因无法工作。你或许需要授予它更多的
RBAC 特权或使用较新的版本。请在 Pod Network 提供商的问题跟踪器中提交问题,
然后在此处分类问题。
如果你安装的 Docker 版本早于 1.12.1,请在使用 systemd
来启动 dockerd
和重启 docker
时,
删除 MountFlags=slave
选项。
你可以在 /usr/lib/systemd/system/docker.service
中看到 MountFlags。
MountFlags 可能会干扰 Kubernetes 挂载的卷, 并使 Pods 处于 CrashLoopBackOff
状态。
当 Kubernetes 不能找到 var/run/secrets/kubernetes.io/serviceaccount
文件时会发生错误。
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 -d
命令可以用来解码证书,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
重新创建。
在故障节点上运行 kubeadm init phase kubelet-finalize all
。
这将使新的 kubelet.conf
文件使用 /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 进行通信的缘故,
可能是由机器提供商的政策所导致的。
Digital Ocean 既分配一个共有 IP 给 eth0
,也分配一个私有 IP 在内部用作其浮动 IP 功能的锚点,
然而 kubelet
将选择后者作为节点的 InternalIP
而不是公共 IP
使用 ip addr show
命令代替 ifconfig
命令去检查这种情况,因为 ifconfig
命令
不会显示有问题的别名 IP 地址。或者指定的 Digital Ocean 的 API 端口允许从 droplet 中
查询 anchor IP:
curl http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address
解决方法是通知 kubelet
使用哪个 --node-ip
。当使用 Digital Ocean 时,可以是公网IP(分配给 eth0
的),
或者是私网IP(分配给 eth1
的)。私网 IP 是可选的。
kubadm NodeRegistrationOptions
结构
的 KubeletExtraArgs
部分被用来处理这种情况。
然后重启 kubelet
:
systemctl daemon-reload
systemctl restart kubelet
coredns
pods 有 CrashLoopBackOff
或者 Error
状态
如果有些节点运行的是旧版本的 Docker,同时启用了 SELinux,你或许会遇到 coredns
pods 无法启动的情况。
要解决此问题,你可以尝试以下选项之一:
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 pods 持续重启
如果你遇到以下错误:
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/master" } ] } } } }'
此问题的跟踪在这里 。
节点上的 /usr
被以只读方式挂载
在类似 Fedora CoreOS 或者 Flatcar Container Linux 这类 Linux 发行版本中,
目录 /usr
是以只读文件系统的形式挂载的。
在支持 FlexVolume 时,
类似 kubelet 和 kube-controller-manager 这类 Kubernetes 组件使用默认路径
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/
,
而 FlexVolume 的目录 必须是可写入的 ,该功能特性才能正常工作。
为了解决这个问题,你可以使用 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.3.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.23 编写的。
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
不去下载存放在 k8s.gcr.io
上的默认的容器镜像的情况。
当你在离线的节点上创建一个集群的时候,Kubeadm 有一些命令可以帮助你预拉取所需的镜像。
阅读离线运行 kubeadm
获取更多的详情。
Kubeadm 允许你给所需要的镜像指定一个自定义的镜像仓库。
阅读使用自定义镜像
获取更多的详情。
初始化控制平面节点
控制平面节点是运行控制平面组件的机器,
包括 etcd (集群数据库)
和 API Server
(命令行工具 kubectl 与之通信)。
(推荐)如果计划将单个控制平面 kubeadm 集群升级成高可用,
你应该指定 --control-plane-endpoint
为所有控制平面节点设置共享端点。
端点可以是负载均衡器的 DNS 名称或 IP 地址。
选择一个 Pod 网络插件,并验证是否需要为 kubeadm init
传递参数。
根据你选择的第三方网络插件,你可能需要设置 --pod-network-cidr
的值。
请参阅 安装Pod网络附加组件 。
(可选)从版本1.14开始,kubeadm
尝试使用一系列众所周知的域套接字路径来检测 Linux 上的容器运行时。
要使用不同的容器运行时,
或者如果在预配置的节点上安装了多个容器,请为 kubeadm init
指定 --cri-socket
参数。
请参阅安装运行时 。
(可选)除非另有说明,否则 kubeadm
使用与默认网关关联的网络接口来设置此控制平面节点 API server 的广播地址。
要使用其他网络接口,请为 kubeadm init
设置 --apiserver-advertise-address=<ip-address>
参数。
要部署使用 IPv6 地址的 Kubernetes 集群,
必须指定一个 IPv6 地址,例如 --apiserver-advertise-address=fd00::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 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 文件,完成对他们的定制授权。
记录 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
的
故障排除指南 。
控制平面节点隔离
默认情况下,出于安全原因,你的集群不会在控制平面节点上调度 Pod。
如果你希望能够在控制平面节点上调度 Pod,
例如用于开发的单机 Kubernetes 集群,请运行:
kubectl taint nodes --all node-role.kubernetes.io/master-
输出看起来像:
node "test-01" untainted
taint "node-role.kubernetes.io/master:" not found
taint "node-role.kubernetes.io/master:" not found
这将从任何拥有 node-role.kubernetes.io/master
taint 标记的节点中移除该标记,
包括控制平面节点,这意味着调度程序将能够在任何地方调度 Pods。
加入节点
节点是你的工作负载(容器和 Pod 等)运行的地方。要将新节点添加到集群,请对每台计算机执行以下操作:
SSH 到机器
成为 root (例如 sudo su -
)
运行 kubeadm init
输出的命令。例如:
kubeadm join --token <token> <control-plane-host>:<control-plane-port> --discovery-token-ca-cert-hash sha256:<hash>
如果没有令牌,可以通过在控制平面节点上运行以下命令来获取令牌:
输出类似于以下内容:
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 地址括在方括号中,例如:[fd00::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
,你会注意到该节点出现在输出中。
(可选)从控制平面节点以外的计算机控制集群
为了使 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
参考文档。
下一步
反馈
版本倾斜政策
版本 v1.23 的kubeadm 工具可以使用版本 v1.23 或 v1.22 的控制平面部署集群。kubeadm v1.23 还可以升级现有的 kubeadm 创建的 v1.22 版本的集群。
由于我们不能预见未来,kubeadm CLI v1.23 可能会或可能无法部署 v1.24 集群。
这些资源提供了有关 kubelet 与控制平面以及其他 Kubernetes 组件之间受支持的版本倾斜的更多信息:
局限性
集群弹性
此处创建的集群具有单个控制平面节点,运行单个 etcd 数据库。
这意味着如果控制平面节点发生故障,你的集群可能会丢失数据并且可能需要从头开始重新创建。
解决方法:
定期备份 etcd 。
kubeadm 配置的 etcd 数据目录位于控制平面节点上的 /var/lib/etcd
中。
kubeadm deb/rpm 软件包和二进制文件是为 amd64,arm (32-bit),arm64,ppc64le 和 s390x 构建的遵循多平台提案 。
从 v1.12 开始还支持用于控制平面和附加组件的多平台容器镜像。
只有一些网络提供商为所有平台提供解决方案。请查阅上方的网络提供商清单或每个提供商的文档以确定提供商是否支持你选择的平台。
故障排除
如果你在使用 kubeadm 时遇到困难,请查阅我们的故障排除文档 。
2.2.3.1.4 - 使用 kubeadm API 定制组件
本页面介绍了如何自定义 kubeadm 部署的组件。
你可以使用 ClusterConfiguration
结构中定义的参数,或者在每个节点上应用补丁来定制控制平面组件。
你可以使用 KubeletConfiguration
和 KubeProxyConfiguration
结构分别定制 kubelet 和 kube-proxy 组件。
所有这些选项都可以通过 kubeadm 配置 API 实现。
有关配置中的每个字段的详细信息,你可以导航到我们的
API 参考页面 。
FEATURE STATE: Kubernetes 1.12 [stable]
使用 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
使用补丁定制控制平面
FEATURE STATE: Kubernetes v1.22 [beta]
Kubeadm 允许将包含补丁文件的目录传递给各个节点上的 InitConfiguration
和 JoinConfiguration
。
这些补丁可被用作控制平面组件清单写入磁盘之前的最后一个自定义步骤。
可以使用 --config <你的 YAML 格式控制文件>
将配置文件传递给 kubeadm init
:
apiVersion : kubeadm.k8s.io/v1beta3
kind : InitConfiguration
nodeRegistration :
patches :
directory : /home/user/somedir
说明:
对于 kubeadm init
,你可以传递一个包含 ClusterConfiguration
和 InitConfiguration
的文件,以 ---
分隔。
你可以使用 --config <你的 YAML 格式配置文件>
将配置文件传递给 kubeadm join
:
apiVersion : kubeadm.k8s.io/v1beta3
kind : JoinConfiguration
nodeRegistration :
patches :
directory : /home/user/somedir
补丁目录必须包含名为 target[suffix][+patchtype].extension
的文件。
例如,kube-apiserver0+merge.yaml
或只是 etcd.json
。
target
可以是 kube-apiserver
、kube-controller-manager
、kube-scheduler
和 etcd
之一。
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
配置应用于集群中的所有节点。
要应用节点特定设置,你可以使用 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.3.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.3.1.6 - 利用 kubeadm 创建高可用集群
本文讲述了使用 kubeadm 设置一个高可用的 Kubernetes 集群的两种不同方式:
使用具有堆叠的控制平面节点。这种方法所需基础设施较少。etcd 成员和控制平面节点位于同一位置。
使用外部集群。这种方法所需基础设施较多。控制平面的节点和 etcd 成员是分开的。
在下一步之前,你应该仔细考虑哪种方法更好的满足你的应用程序和环境的需求。
这是对比文档 讲述了每种方法的优缺点。
如果你在安装 HA 集群时遇到问题,请在 kubeadm 问题跟踪 里向我们提供反馈。
你也可以阅读 升级文件
注意: 这篇文档没有讲述在云提供商上运行集群的问题。在云环境中,此处记录的方法不适用于类型为 LoadBalancer 的服务对象,或者具有动态的 PersistentVolumes。
准备开始
对于这两种方法,你都需要以下基础设施:
配置满足 kubeadm 的最低要求
的三台机器作为控制面节点
配置满足 kubeadm 的最低要求
的三台机器作为工作节点
在集群中,确保所有计算机之间存在全网络连接(公网或私网)
在所有机器上具有 sudo 权限
从某台设备通过 SSH 访问系统中所有节点的能力
所有机器上已经安装 kubeadm
和 kubelet
,kubectl
是可选的。
仅对于外部 etcd 集群来说,你还需要:
这两种方法的第一步
为 kube-apiserver 创建负载均衡器
说明:
使用负载均衡器需要许多配置。你的集群搭建可能需要不同的配置。
下面的例子只是其中的一方面配置。
创建一个名为 kube-apiserver 的负载均衡器解析 DNS。
在云环境中,应该将控制平面节点放置在 TCP 后面转发负载平衡。
该负载均衡器将流量分配给目标列表中所有运行状况良好的控制平面节点。
API 服务器的健康检查是在 kube-apiserver 的监听端口(默认值 :6443
)
上进行的一个 TCP 检查。
不建议在云环境中直接使用 IP 地址。
负载均衡器必须能够在 API 服务器端口上与所有控制平面节点通信。
它还必须允许其监听端口的入站流量。
确保负载均衡器的地址始终匹配 kubeadm 的 ControlPlaneEndpoint
地址。
阅读软件负载平衡选项指南 以获取更多详细信息。
添加第一个控制平面节点到负载均衡器并测试连接:
nc -v LOAD_BALANCER_IP PORT
由于 apiserver 尚未运行,预期会出现一个连接拒绝错误。
然而超时意味着负载均衡器不能和控制平面节点通信。
如果发生超时,请重新配置负载均衡器与控制平面节点进行通信。
将其余控制平面节点添加到负载均衡器目标组。
使用堆控制平面和 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 配置](/docs/reference/config-api/kubeadm-config.v1beta3/),你必须在相应的配置文件
(位于 `InitConfiguration` 和 `JoinConfiguration: controlPlane`)添加 `certificateKey` 字段。
说明:
一些 CNI 网络插件如 Calico 需要 CIDR 例如 `192.168.0.0/16` 和一些像 Weave 没有。参考
[CNI 网络文档](/zh/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/#pod-network)。
通过传递 `--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
说明:
`kubeadm-certs` 密钥和解密密钥会在两个小时后失效。
注意:
正如命令输出中所述,证书密钥可访问群集敏感数据。请妥善保管!
应用你所选择的 CNI 插件:
请遵循以下指示
安装 CNI 提供程序。如果适用,请确保配置与 kubeadm 配置文件中指定的 Pod
CIDR 相对应。
在此示例中,我们使用 Weave Net:
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version= $( kubectl version | base64 | tr -d '\n' ) "
输入以下内容,并查看控制平面组件的 Pods 启动:
kubectl get pod -n kube-system -w
其余控制平面节点的步骤
说明:
从 kubeadm 1.15 版本开始,你可以并行加入多个控制平面节点。
在此版本之前,你必须在第一个节点初始化后才能依序的增加新的控制平面节点。
对于每个其他控制平面节点,你应该:
执行先前由第一个节点上的 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 集群
按照 这些指示
去设置 etcd 集群。
根据这里 的描述配置 SSH。
将以下文件从集群中的任何 etcd 节点复制到第一个控制平面节点:
export CONTROL_PLANE = "[email protected] "
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/v1beta2
kind : ClusterConfiguration
kubernetesVersion : stable
controlPlaneEndpoint : "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT"
etcd :
external :
endpoints :
- https://ETCD_0_IP:2379
- https://ETCD_1_IP:2379
- https://ETCD_2_IP:2379
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 插件。以下示例适用于 Weave Net:
kubectl apply -f "https://cloud.weave.works/k8s/net?k8s-version= $( kubectl version | base64 | tr -d '\n' ) "
其他控制平面节点的步骤
步骤与设置内置 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 之后,你应该在运行过 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
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
mv /home/${ USER } /etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
2.2.3.1.7 - 使用 kubeadm 创建一个高可用 etcd 集群
说明:
在本指南中,当 kubeadm 用作为外部 etcd 节点管理工具,请注意 kubeadm 不计划支持此类节点的证书更换或升级。对于长期规划是使用 etcdadm 增强工具来管理这方面。
默认情况下,kubeadm 运行单成员的 etcd 集群,该集群由控制面节点上的 kubelet 以静态 Pod 的方式进行管理。由于 etcd 集群只包含一个成员且不能在任一成员不可用时保持运行,所以这不是一种高可用设置。本任务,将告诉你如何在使用 kubeadm 创建一个 kubernetes 集群时创建一个外部 etcd:有三个成员的高可用 etcd 集群。
准备开始
三个可以通过 2379 和 2380 端口相互通信的主机。本文档使用这些作为默认端口。不过,它们可以通过 kubeadm 的配置文件进行自定义。
一些可以用来在主机间复制文件的基础设施。例如 ssh
和 scp
就可以满足需求。
建立集群
一般来说,是在一个节点上生成所有证书并且只分发这些必要 的文件到其它节点上。
说明:
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"。
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
export HOST0 = 10.0.0.6
export HOST1 = 10.0.0.7
export HOST2 = 10.0.0.8
# 创建临时目录来存储将被分发到其它主机上的文件
mkdir -p /tmp/${ HOST0 } / /tmp/${ HOST1 } / /tmp/${ HOST2 } /
ETCDHOSTS =( ${ HOST0 } ${ HOST1 } ${ HOST2 } )
NAMES =( "infra0" "infra1" "infra2" )
for i in " ${ !ETCDHOSTS[@]} " ; do
HOST = ${ ETCDHOSTS [$i ]}
NAME = ${ NAMES [$i ]}
cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml
apiVersion: "kubeadm.k8s.io/v1beta3"
kind: ClusterConfiguration
etcd:
local:
serverCertSANs:
- "${HOST}"
peerCertSANs:
- "${HOST}"
extraArgs:
initial-cluster: infra0=https://${ETCDHOSTS[0]}:2380,infra1=https://${ETCDHOSTS[1]}:2380,infra2=https://${ETCDHOSTS[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= /tmp/${ HOST1 } /kubeadmcfg.yaml
root@HOST2 $ kubeadm init phase etcd local --config= /tmp/${ HOST2 } /kubeadmcfg.yaml
可选:检查群集运行状况
docker run --rm -it \
--net host \
-v /etc/kubernetes:/etc/kubernetes k8s.gcr.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.3.1.8 - 使用 kubeadm 配置集群中的每个 kubelet
FEATURE STATE: Kubernetes 1.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 提供默认值。
有趣的示例包括使用其他 CRI 运行时或通过服务器设置不同的默认子网。
如果你想使用子网 10.96.0.0/12
作为services的默认网段,你可以给 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 无法自动检测 CRI 运行时使用的 cgroup 驱动程序,
但是值 --cgroup-driver
必须与 CRI 运行时使用的 cgroup 驱动程序匹配,以确保 kubelet 的健康运行状况。
取决于你的集群所使用的 CRI 运行时,你可能需要为 kubelet 指定不同的标志。
例如,当使用 Docker 时,你需要指定如 --network-plugin=cni
这类标志;但是如果你使用的是外部运行时,
则需要指定 --container-runtime=remote
并使用 --container-runtime-endpoint=<path>
指定 CRI 端点。
你可以在服务管理器(例如 systemd)中设定某个 kubelet 的配置来指定这些参数。
使用 kubeadm 配置 kubelet
如果自定义的 KubeletConfiguration
API 对象使用像 kubeadm ... --config some-config-file.yaml
这样的配置文件进行传递,则可以配置 kubeadm 启动的 kubelet。
通过调用 kubeadm config print init-defaults --component-configs KubeletConfiguration
,
你可以看到此结构中的所有默认值。
也可以阅读 KubeletConfiguration 参考
来获取有关各个字段的更多信息。
当使用 kubeadm init
时的工作流程
当调用 kubeadm init
时,kubelet 配置被编组到磁盘上的 /var/lib/kubelet/config.yaml
中,
并且上传到集群中的 ConfigMap。
ConfigMap 名为 kubelet-config-1.X
,其中 X
是你正在初始化的 kubernetes 版本的次版本。
在集群中所有 kubelet 的基准集群范围内配置,将 kubelet 配置文件写入 /etc/kubernetes/kubelet.conf
中。
此配置文件指向允许 kubelet 与 API 服务器通信的客户端证书。
这解决了将集群级配置传播到每个 kubelet 的需求。
该文档 提供特定实例的配置详细信息 是第二种解决模式,
kubeadm 将环境文件写入 /var/lib/kubelet/kubeadm-flags.env
,其中包含了一个标志列表,
当 kubelet 启动时,该标志列表会传递给 kubelet 标志在文件中的显示方式如下:
KUBELET_KUBEADM_ARGS = "--flag1=value1 --flag2=value2 ..."
除了启动 kubelet 时使用该标志外,该文件还包含动态参数,例如 cgroup 驱动程序以及是否使用其他 CRI 运行时 socket(--cri-socket
)。
将这两个文件编组到磁盘后,如果使用 systemd,则 kubeadm 尝试运行以下两个命令:
systemctl daemon-reload && systemctl restart kubelet
如果重新加载和重新启动成功,则正常的 kubeadm init
工作流程将继续。
当使用 kubeadm join
时的工作流程
当运行 kubeadm join
时,kubeadm 使用 Bootstrap Token 证书执行 TLS 引导,该引导会获取一份证书,该证书需要下载 kubelet-config-1.X
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
中。
当此文件被写入后,kubelet 就完成了执行 TLS 引导程序。
kubelet 的 systemd 文件
kubeadm
中附带了有关系统如何运行 kubelet 的 systemd 配置文件。
请注意 kubeadm CLI 命令不会修改此文件。
通过 kubeadm
DEB
或者 RPM 包
安装的配置文件被写入 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
并由系统使用。
它对原来的 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
该文件为 kubelet 指定由 kubeadm 管理的所有文件的默认位置。
用于 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 软件包是:
Package name
Description
kubeadm
给 kubelet 安装 /usr/bin/kubeadm
CLI 工具和 kubelet 的 systemd 文件 。
kubelet
安装 kubelet 可执行文件到 /usr/bin
路径,安装 CNI 可执行文件到 /opt/cni/bin
路径。
kubectl
安装 /usr/bin/kubectl
可执行文件。
cri-tools
从 cri-tools git 仓库 中安装 /usr/bin/crictl
可执行文件。
2.2.3.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 工具,则可以通过运行以下步骤再次检查集群是否配置正确:
你应该看到 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 实现的。你可以有多个实例组。
例如,如果你想要的是混合实例和按需实例的节点,或者 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
接下来
了解有关 Kubernetes 的概念 和
kubectl
有关的更多信息。
了解 kops
高级用法 。
请参阅 kops
文档 获取教程、
最佳做法和高级配置选项。
2.2.3.3 - 使用 Kubespray 安装 Kubernetes
此快速入门有助于使用 Kubespray
安装在 GCE、Azure、OpenStack、AWS、vSphere、Packet(裸机)、Oracle Cloud
Infrastructure(实验性)或 Baremetal 上托管的 Kubernetes 集群。
Kubespray 是一个由 Ansible playbooks、
清单(inventory) 、
制备工具和通用 OS/Kubernetes 集群配置管理任务的领域知识组成的。
Kubespray 提供:
高可用性集群
可组合属性
支持大多数流行的 Linux 发行版
Ubuntu 16.04、18.04、20.04
CentOS / RHEL / Oracle Linux 7、8
Debian Buster、Jessie、Stretch、Wheezy
Fedora 31、32
Fedora CoreOS
openSUSE Leap 15
Kinvolk 的 Flatcar Container Linux
持续集成测试
要选择最适合你的用例的工具,请阅读
kubeadm 和
kops 之间的
这份比较 。
。
创建集群
(1/5)满足下层设施要求
按以下要求 来配置服务器:
在将运行 Ansible 命令的计算机上安装 Ansible v2.9 和 python-netaddr
运行 Ansible Playbook 需要 Jinja 2.11(或更高版本)
目标服务器必须有权访问 Internet 才能拉取 Docker 镜像。否则,
需要其他配置(请参见离线环境 )
目标服务器配置为允许 IPv4 转发
你的 SSH 密钥必须复制 到清单中的所有服务器部分
防火墙不受管理,你将需要按照以前的方式实施自己的规则。
为了避免在部署过程中出现任何问题,你应该禁用防火墙
如果从非 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 操作。
这些 Pods 模仿其余工作负载的类似行为,并用作集群运行状况指示器。
集群操作
Kubespray 提供了其他 Playbooks 来管理集群: scale 和 upgrade 。
扩展集群
你可以通过运行 scale playbook 向集群中添加工作节点。有关更多信息,
请参见 “添加节点 ”。
你可以通过运行 remove-node playbook 来从集群中删除工作节点。有关更多信息,
请参见 “删除节点 ”。
升级集群
你可以通过运行 upgrade-cluster Playbook 来升级集群。有关更多信息,请参见
“升级 ”。
清理
你可以通过 reset Playbook
重置节点并清除所有与 Kubespray 一起安装的组件。
注意: 运行 reset playbook 时,请确保不要意外地将生产集群作为目标!
反馈
接下来
查看有关 Kubespray 的
路线图
的计划工作。
2.2.4 - Windows Kubernetes
2.2.4.1 - Kubernetes 对 Windows 的支持
在很多组织中,其服务和应用的很大比例是 Windows 应用。
Windows 容器 提供了一种对进程和包依赖关系
进行封装的现代方式,这使得用户更容易采用 DevOps 实践,令 Windows 应用同样遵从
云原生模式。
Kubernetes 已经成为事实上的标准容器编排器,Kubernetes 1.14 发行版本中包含了将
Windows 容器调度到 Kubernetes 集群中 Windows 节点上的生产级支持,从而使得巨大
的 Windows 应用生态圈能够充分利用 Kubernetes 的能力。
对于同时投入基于 Windows 应用和 Linux 应用的组织而言,他们不必寻找不同的编排系统
来管理其工作负载,其跨部署的运维效率得以大幅提升,而不必关心所用操作系统。
kubernetes 中的 Windows 容器
若要在 Kubernetes 中启用对 Windows 容器的编排,可以在现有的 Linux 集群中
包含 Windows 节点。在 Kubernetes 上调度 Pods
中的 Windows 容器与调用基于 Linux 的容器类似。
为了运行 Windows 容器,你的 Kubernetes 集群必须包含多个操作系统,控制面
节点运行 Linux,工作节点则可以根据负载需要运行 Windows 或 Linux。
Windows Server 2019 是唯一被支持的 Windows 操作系统,在 Windows 上启用
Kubernetes 节点
支持(包括 kubelet, 容器运行时 、
以及 kube-proxy)。关于 Windows 发行版渠道的详细讨论,可参见
Microsoft 文档 。
说明: Kubernetes 控制面,包括
主控组件 ,
继续在 Linux 上运行。
目前没有支持完全是 Windows 节点的 Kubernetes 集群的计划。
说明: 在本文中,当我们讨论 Windows 容器时,我们所指的是具有进程隔离能力的 Windows
容器。具有
Hyper-V 隔离能力
的 Windows 容器计划在将来发行版本中推出。
支持的功能与局限性
支持的功能
Windows 操作系统版本支持
参考下面的表格,了解 Kubernetes 中支持的 Windows 操作系统。
同一个异构的 Kubernetes 集群中可以同时包含 Windows 和 Linux 工作节点。
Windows 容器仅能调度到 Windows 节点,Linux 容器则只能调度到 Linux 节点。
Kubernetes 版本
Windows Server LTSC 版本
Windows Server SAC 版本
Kubernetes v1.20
Windows Server 2019
Windows Server ver 1909, Windows Server ver 2004
Kubernetes v1.21
Windows Server 2019
Windows Server ver 2004, Windows Server ver 20H2
Kubernetes v1.22
Windows Server 2019
Windows Server ver 2004, Windows Server ver 20H2
关于不同的 Windows Server 版本的服务渠道,包括其支持模式等相关信息可以在
Windows Server servicing channels
找到。
我们并不指望所有 Windows 客户都为其应用频繁地更新操作系统。
对应用的更新是向集群中引入新代码的根本原因。
对于想要更新运行于 Kubernetes 之上的容器中操作系统的客户,我们会在添加对新
操作系统版本的支持时提供指南和分步的操作指令。
该指南会包含与集群节点一起来升级用户应用的建议升级步骤。
Windows 节点遵从 Kubernetes
版本偏差策略 (节点到控制面的
版本控制),与 Linux 节点的现行策略相同。
Windows Server 主机操作系统会受
Windows Server
授权策略控制。Windows 容器镜像则遵从
Windows 容器的补充授权条款
约定。
带进程隔离的 Windows 容器受一些严格的兼容性规则约束,
其中宿主 OS 版本必须与容器基准镜像的 OS 版本相同 。
一旦我们在 Kubernetes 中支持带 Hyper-V 隔离的 Windows 容器,
这一约束和兼容性规则也会发生改变。
Pause 镜像
Kubernetes 维护着一个多体系结构镜像,其中包括对 Windows 的支持。
对于 Kubernetes v1.22,推荐的 pause 镜像是 k8s.gcr.io/pause:3.5
。
源代码 可在 GitHub 上找到。
Microsoft 维护了一个支持 Linux 和 Windows amd64 的多体系结构镜像: mcr.microsoft.com/oss/kubernetes/pause:3.5
。
此镜像与 Kubernetes 维护的镜像是从同一来源构建,但所有 Windows 二进制文件
均由 Microsoft 签名 。
当生产环境需要被签名的二进制文件时,建议使用 Microsoft 维护的镜像。
计算
从 API 和 kubectl 的角度,Windows 容器的表现在很大程度上与基于 Linux 的容器
是相同的。不过也有一些与关键功能相关的差别值得注意,这些差别列举于
局限性 小节中。
关键性的 Kubernetes 元素在 Windows 下与其在 Linux 下工作方式相同。我们在本节中
讨论一些关键性的负载支撑组件及其在 Windows 中的映射。
Pods
Pod 是 Kubernetes 中最基本的构造模块,是 Kubernetes 对象模型中你可以创建或部署的
最小、最简单元。你不可以在同一 Pod 中部署 Windows 和 Linux 容器。
Pod 中的所有容器都会被调度到同一节点(Node),而每个节点代表的是一种特定的平台
和体系结构。Windows 容器支持 Pod 的以下能力、属性和事件:
在带进程隔离和卷共享支持的 Pod 中运行一个或多个容器
Pod 状态字段
就绪态(Readiness)和活跃性(Liveness)探针
postStart 和 preStop 容器生命周期事件
ConfigMap、Secrets:用作环境变量或卷
emptyDir 卷
从宿主系统挂载命名管道
资源限制
控制器(Controllers)
Kubernetes 控制器处理 Pod 的期望状态。Windows 容器支持以下负载控制器:
ReplicaSet
ReplicationController
Deployment
StatefulSet
DaemonSet
Job
CronJob
服务(Services)
Kubernetes Service 是一种抽象对象,用来定义 Pod 的一个逻辑集合及用来访问这些
Pod 的策略。Service 有时也称作微服务(Micro-service)。你可以使用服务来实现
跨操作系统的连接。在 Windows 系统中,服务可以使用下面的类型、属性和能力:
Service 环境变量
NodePort
ClusterIP
LoadBalancer
ExternalName
无头(Headless)服务
Pods、控制器和服务是在 Kubernetes 上管理 Windows 负载的关键元素。
不过,在一个动态的云原生环境中,这些元素本身还不足以用来正确管理
Windows 负载的生命周期。我们为此添加了如下功能特性:
Pod 和容器的度量(Metrics)
对水平 Pod 自动扩展的支持
对 kubectl exec 命令的支持
资源配额
调度器抢占
容器运行时
Docker EE
FEATURE STATE: Kubernetes v1.14 [stable]
Docker EE-basic 19.03+ 是建议所有 Windows Server 版本采用的容器运行时。
该容器运行时能够与 kubelet 中的 dockershim 代码协同工作。
CRI-ContainerD
FEATURE STATE: Kubernetes v1.20 [stable]
ContainerD 1.4.0+
也可作为 Windows Kubernetes 节点上的容器运行时。
持久性存储
使用 Kubernetes 卷 ,对数据持久性和 Pod 卷
共享有需求的复杂应用也可以部署到 Kubernetes 上。
管理与特定存储后端或协议相关的持久卷时,相关的操作包括:对卷的配备(Provisioning)、
去配(De-provisioning)和调整大小,将卷挂接到 Kubernetes 节点或从节点上解除挂接,
将卷挂载到需要持久数据的 Pod 中的某容器或从容器上卸载。
负责实现为特定存储后端或协议实现卷管理动作的代码以 Kubernetes 卷
插件 的形式发布。
Windows 支持以下大类的 Kubernetes 卷插件:
树内卷插件
与树内卷插件(In-Tree Volume Plugin)相关的代码都作为核心 Kubernetes 代码基
的一部分发布。树内卷插件的部署不需要安装额外的脚本,也不需要额外部署独立的
容器化插件组件。这些插件可以处理:对应存储后端上存储卷的配备、去配和尺寸更改,
将卷挂接到 Kubernetes 或从其上解挂,以及将卷挂载到 Pod 中各个容器上或从其上
卸载。以下树内插件支持 Windows 节点:
FlexVolume 插件
与 FlexVolume 插件相关的代码是作为
树外(Out-of-tree)脚本或可执行文件来发布的,因此需要在宿主系统上直接部署。
FlexVolume 插件处理将卷挂接到 Kubernetes 节点或从其上解挂、将卷挂载到 Pod 中
各个容器上或从其上卸载等操作。对于与 FlexVolume 插件相关联的持久卷的配备和
去配操作,可以通过外部的配置程序来处理。这类配置程序通常与 FlexVolume 插件
相分离。下面的 FlexVolume
插件
可以以 PowerShell 脚本的形式部署到宿主系统上,支持 Windows 节点:
CSI 插件
FEATURE STATE: Kubernetes v1.22 [stable]
与 CSI 插件相关联的代码作为
树外脚本和可执行文件来发布且通常发布为容器镜像形式,并使用 DaemonSet 和
StatefulSet 这类标准的 Kubernetes 构造体来部署。
CSI 插件处理 Kubernetes 中的很多卷管理操作:对卷的配备、去配和调整大小,
将卷挂接到 Kubernetes 节点或从节点上解除挂接,将卷挂载到需要持久数据的 Pod
中的某容器或从容器上卸载,使用快照和克隆来备份或恢复持久数据。
来支持;csi-proxy 是一个社区管理的、独立的可执行文件,需要预安装在每个
Windows 节点之上。请参考你要部署的 CSI 插件的部署指南以进一步了解其细节。
CSI 插件与执行本地存储操作的 CSI 节点插件通信。
在 Windows 节点上,CSI 节点插件通常调用处理本地存储操作的 csi-proxy
公开的 API, csi-proxy 由社区管理。
有关安装的更多详细信息,请参阅你要部署的 Windows CSI 插件的环境部署指南。
你也可以参考以下安装步骤 。
联网
Windows 容器的联网是通过
CNI 插件
来暴露出来的。Windows 容器的联网行为与虚拟机的联网行为类似。
每个容器有一块虚拟的网络适配器(vNIC)连接到 Hyper-V 的虚拟交换机(vSwitch)。
宿主的联网服务(Host Networking Service,HNS)和宿主计算服务(Host Compute
Service,HCS)协同工作,创建容器并将容器的虚拟网卡连接到网络上。
HCS 负责管理容器,HNS 则负责管理网络资源,例如:
虚拟网络(包括创建 vSwitch)
端点(Endpoint)/ vNIC
名字空间(Namespace)
策略(报文封装、负载均衡规则、访问控制列表、网络地址转译规则等等)
支持的服务规约类型如下:
NodePort
ClusterIP
LoadBalancer
ExternalName
网络模式
Windows 支持五种不同的网络驱动/模式:二层桥接(L2bridge)、二层隧道(L2tunnel)、
覆盖网络(Overlay)、透明网络(Transparent)和网络地址转译(NAT)。
在一个包含 Windows 和 Linux 工作节点的异构集群中,你需要选择一种对 Windows 和
Linux 兼容的联网方案。下面是 Windows 上支持的一些树外插件及何时使用某种
CNI 插件的建议:
网络驱动
描述
容器报文更改
网络插件
网络插件特点
L2bridge
容器挂接到外部 vSwitch 上。容器挂接到下层网络之上,但由于容器的 MAC
地址在入站和出站时被重写,物理网络不需要这些地址。
MAC 地址被重写为宿主系统的 MAC 地址,IP 地址也可能依据 HNS OutboundNAT
策略重写为宿主的 IP 地址。
win-bridge 、
Azure-CNI 、
Flannel 宿主网关(host-gateway)使用 win-bridge
win-bridge 使用二层桥接(L2bridge)网络模式,将容器连接到下层宿主系统上,
从而提供最佳性能。需要用户定义的路由(User-Defined Routes,UDR)才能
实现节点间的连接。
L2Tunnel
这是二层桥接的一种特殊情形,但仅被用于 Azure 上。
所有报文都被发送到虚拟化环境中的宿主机上并根据 SDN 策略进行处理。
MAC 地址被改写,IP 地址在下层网络上可见。
Azure-CNI
Azure-CNI 使得容器能够与 Azure vNET 集成,并允许容器利用
[Azure 虚拟网络](https://azure.microsoft.com/en-us/services/virtual-network/)
所提供的功能特性集合。例如,可以安全地连接到 Azure 服务上或者使用 Azure NSG。
你可以参考
[azure-cni](https://docs.microsoft.com/en-us/azure/aks/concepts-network#azure-cni-advanced-networking)
所提供的一些示例。
覆盖网络(Kubernetes 中为 Windows 提供的覆盖网络支持处于 *alpha* 阶段)
每个容器会获得一个连接到外部 vSwitch 的虚拟网卡(vNIC)。
每个覆盖网络都有自己的、通过定制 IP 前缀来定义的 IP 子网。
覆盖网络驱动使用 VxLAN 封装。
封装于外层包头内。
Win-overlay 、
Flannel VXLAN(使用 win-overlay)
当(比如出于安全原因)期望虚拟容器网络与下层宿主网络隔离时,
应该使用 win-overlay。如果你的数据中心可用 IP 地址受限,
覆盖网络允许你在不同的网络中复用 IP 地址(每个覆盖网络有不同的 VNID 标签)。
这一选项要求在 Windows Server 2009 上安装
[KB4489899](https://support.microsoft.com/help/4489899) 补丁。
透明网络([ovn-kubernetes](https://github.com/openvswitch/ovn-kubernetes) 的特殊用例)
需要一个外部 vSwitch。容器挂接到某外部 vSwitch 上,该 vSwitch
通过逻辑网络(逻辑交换机和路由器)允许 Pod 间通信。
报文或者通过 [GENEVE](https://datatracker.ietf.org/doc/draft-gross-geneve/) 来封装,
或者通过 [STT](https://datatracker.ietf.org/doc/draft-davie-stt/) 隧道来封装,
以便能够到达不在同一宿主系统上的每个 Pod。
报文通过 OVN 网络控制器所提供的隧道元数据信息来判定是转发还是丢弃。
北-南向通信通过 NAT 网络地址转译来实现。
ovn-kubernetes
[通过 Ansible 来部署](https://github.com/openvswitch/ovn-kubernetes/tree/master/contrib)。
所发布的 ACL 可以通过 Kubernetes 策略来应用实施。支持 IPAM 。
负载均衡能力不依赖 kube-proxy。
网络地址转译(NAT)也不需要 iptables 或 netsh。
NAT(未在 Kubernetes 中使用 )
容器获得一个连接到某内部 vSwitch 的 vNIC 接口。
DNS/DHCP 服务通过名为
[WinNAT](https://blogs.technet.microsoft.com/virtualization/2016/05/25/windows-nat-winnat-capabilities-and-limitations/)
的内部组件来提供。
MAC 地址和 IP 地址都被重写为宿主系统的 MAC 地址和 IP 地址。
nat
列在此表中仅出于完整性考虑
如前所述,Flannel CNI
meta 插件
在 Windows 上也是
被支持
的,方法是通过 VXLAN 网络后端
(alpha 阶段 :委托给 win-overlay)和
主机-网关(host-gateway)网络后端
(稳定版本;委托给 win-bridge 实现)。
此插件支持将操作委托给所引用的 CNI 插件(win-overlay、win-bridge)之一,
从而能够与 Windows 上的 Flannel 守护进程(Flanneld)一同工作,自动为节点
分配子网租期,创建 HNS 网络。
该插件读入其自身的配置文件(cni.conf),并将其与 FlannelD 所生成的 subnet.env
文件中的环境变量整合,之后将其操作委托给所引用的 CNI 插件之一以完成网络发现,
并将包含节点所被分配的子网信息的正确配置发送给 IPAM 插件(例如 host-local)。
对于节点、Pod 和服务对象,可针对 TCP/UDP 流量支持以下网络数据流:
Pod -> Pod (IP 寻址)
Pod -> Pod (名字寻址)
Pod -> 服务(集群 IP)
Pod -> 服务(部分限定域名,仅适用于名称中不包含“.”的情形)
Pod -> 服务(全限定域名)
Pod -> 集群外部(IP 寻址)
Pod -> 集群外部(DNS 寻址)
节点 -> Pod
Pod -> 节点
IP 地址管理(IPAM)
Windows 上支持以下 IPAM 选项:
负载均衡与服务
在 Windows 系统上,你可以使用以下配置来设定服务和负载均衡行为:
Windows 服务设置
功能特性
描述
所支持的 Kubernetes 版本
所支持的 Windows OS 版本
如何启用
会话亲和性
确保来自特定客户的连接每次都被交给同一 Pod。
v1.20+
[Windows Server vNext Insider Preview Build 19551](https://blogs.windows.com/windowsexperience/2020/01/28/announcing-windows-server-vnext-insider-preview-build-19551/)
或更高版本
将 service.spec.sessionAffinitys
设置为 "ClientIP"
直接服务器返回(DSR)
这是一种负载均衡模式,IP 地址的修正和负载均衡地址转译(LBNAT)
直接在容器的 vSwitch 端口上处理;服务流量到达时,其源端 IP 地址
设置为来源 Pod 的 IP。
v1.20+
Windows Server 2019
为 kube-proxy 设置标志:`--feature-gates="WinDSR=true" --enable-dsr=true`
保留目标地址
对服务流量略过 DNAT 步骤,这样就可以在到达后端 Pod 的报文中保留目标服务的
虚拟 IP 地址。还要禁止节点之间的转发。
v1.20+
Windows Server 1903 或更高版本
在服务注解中设置 `"preserve-destination": "true"` 并启用
kube-proxy 中的 DSR 标志。
IPv4/IPv6 双栈网络
在集群内外同时支持原生的 IPv4-到-IPv4 和 IPv6-到-IPv6 通信。
v1.19+
Windows Server 2004 或更高版本
参见 [IPv4/IPv6 双栈网络](#ipv4ipv6-dual-stack)
保留客户端 IP
确保入站流量的源 IP 地址被保留。同样要禁止节点之间的转发。
v1.20+
Windows Server 2019 或更高版本
将 service.spec.externalTrafficPolicy
设置为 "Local",
并在 kube-proxy 上启用 DSR。
IPv4/IPv6 双栈支持
你可以通过使用 IPv6DualStack
特性门控
来为 l2bridge
网络启用 IPv4/IPv6 双栈联网支持。
进一步的细节可参见
启用 IPv4/IPv6 双协议栈 。
对 Windows 而言,在 Kubernetes 中使用 IPv6 需要
Windows Server 2004 (内核版本 10.0.19041.610)或更高版本。
目前 Windows 上的覆盖网络(VXLAN)还不支持双协议栈联网。
局限性
在 Kubernetes 架构和节点阵列中仅支持将 Windows 作为工作节点使用。
这意味着 Kubernetes 集群必须总是包含 Linux 主控节点,零个或者多个 Linux
工作节点以及零个或者多个 Windows 工作节点。
资源处理
Linux 上使用 Linux 控制组(CGroups)作为 Pod 的边界,以实现资源控制。
容器都创建于这一边界之内,从而实现网络、进程和文件系统的隔离。
控制组 CGroups API 可用来收集 CPU、I/O 和内存的统计信息。
与此相比,Windows 为每个容器创建一个带有系统名字空间过滤设置的 Job 对象,
以容纳容器中的所有进程并提供其与宿主系统间的逻辑隔离。
没有现成的名字空间过滤设置是无法运行 Windows 容器的。
这也意味着,系统特权无法在宿主环境中评估,因而 Windows 上也就不存在特权容器。
归咎于独立存在的安全账号管理器(Security Account Manager,SAM),容器也不能
获得宿主系统上的任何身份标识。
资源预留
内存预留
Windows 不像 Linux 一样有一个内存耗尽(Out-of-memory)进程杀手(Process
Killer)机制。Windows 总是将用户态的内存分配视为虚拟请求,页面文件(Pagefile)
是必需的。这一差异的直接结果是 Windows 不会像 Linux 那样出现内存耗尽的状况,
系统会将进程内存页面写入磁盘而不会因内存耗尽而终止进程。
当内存被过量使用且所有物理内存都被用光时,系统的换页行为会导致性能下降。
使用 kubelet 参数 --kubelet-reserve
与/或 -system-reserve
可以统计
节点上的内存用量(各容器之外),进而可能将内存用量限制在一个合理的范围,。
这样做会减少节点可分配内存
(NodeAllocatable )。
在你部署工作负载时,对容器使用资源限制(必须仅设置 limits 或者让 limits 等于
requests 值)。这也会从 NodeAllocatable 中耗掉部分内存量,从而避免在节点
负荷已满时调度器继续向节点添加 Pods。
避免过量分配的最佳实践是为 kubelet 配置至少 2 GB 的系统预留内存,以供
Windows、Docker 和 Kubernetes 进程使用。
CPU 预留
为了统计 Windows、Docker 和其他 Kubernetes 宿主进程的 CPU 用量,建议
预留一定比例的 CPU,以便对事件作出相应。此值需要根据 Windows 节点上
CPU 核的个数来调整,要确定此百分比值,用户需要为其所有节点确定 Pod
密度的上线,并监控系统服务的 CPU 用量,从而选择一个符合其负载需求的值。
使用 kubelet 参数 --kubelet-reserve
与/或 -system-reserve
可以统计
节点上的 CPU 用量(各容器之外),进而可能将 CPU 用量限制在一个合理的范围,。
这样做会减少节点可分配 CPU
(NodeAllocatable )。
功能特性限制
终止宽限期(Termination Grace Period):未实现
单文件映射:将用 CRI-ContainerD 来实现
终止消息(Termination message):将用 CRI-ContainerD 来实现
特权容器:Windows 容器当前不支持
巨页(Huge Pages):Windows 容器当前不支持
现有的节点问题探测器(Node Problem Detector)仅适用于 Linux,且要求使用特权容器。
一般而言,我们不设想此探测器能用于 Windows 节点,因为 Windows 不支持特权容器。
并非支持共享名字空间的所有功能特性(参见 API 节以了解详细信息)
与 Linux 相比参数行为的差别
以下 kubelet 参数的行为在 Windows 节点上有些不同,描述如下:
--kubelet-reserve
、--system-reserve
和 --eviction-hard
标志
会更新节点可分配资源量
未实现通过使用 --enforce-node-allocable
来完成的 Pod 驱逐
未实现通过使用 --eviction-hard
和 --eviction-soft
来完成的 Pod 驱逐
MemoryPressure
状况未实现
kubelet
不会采取措施来执行基于 OOM 的驱逐动作
Windows 节点上运行的 kubelet 没有内存约束。
--kubelet-reserve
和 --system-reserve
不会为 kubelet 或宿主系统上运行
的进程设限。这意味着 kubelet 或宿主系统上的进程可能导致内存资源紧张,
而这一情况既不受节点可分配量影响,也不会被调度器感知。
在 Windows 节点上存在一个额外的参数用来设置 kubelet 进程的优先级,称作
--windows-priorityclass
。此参数允许 kubelet 进程获得与 Windows 宿主上
其他进程相比更多的 CPU 时间片。
关于可用参数值及其含义的进一步信息可参考
Windows Priority Classes 。
为了让 kubelet 总能够获得足够的 CPU 周期,建议将此参数设置为
ABOVE_NORMAL_PRIORITY_CLASS
或更高。
存储
Windows 上包含一个分层的文件系统来挂载容器的分层,并会基于 NTFS 来创建一个
拷贝文件系统。容器中的所有文件路径都仅在该容器的上下文内完成解析。
Docker 卷挂载仅可针对容器中的目录进行,不可针对独立的文件。
这一限制不适用于 CRI-containerD。
卷挂载无法将文件或目录投射回宿主文件系统。
不支持只读文件系统,因为 Windows 注册表和 SAM 数据库总是需要写访问权限。
不过,Windows 支持只读的卷。
不支持卷的用户掩码和访问许可,因为宿主与容器之间并不共享 SAM,二者之间不存在
映射关系。所有访问许可都是在容器上下文中解析的。
因此,Windows 节点上不支持以下存储功能特性:
卷的子路径挂载;只能在 Windows 容器上挂载整个卷。
为 Secret 执行子路径挂载;
宿主挂载投射;
默认访问模式 defaultMode(因为该特性依赖 UID/GID);
只读的根文件系统;映射的卷仍然支持 readOnly
;
块设备映射;
将内存作为存储介质;
类似 UUID/GUID、每用户不同的 Linux 文件系统访问许可等文件系统特性;
基于 NFS 的存储和卷支持;
扩充已挂载卷(resizefs)。
联网
Windows 容器联网与 Linux 联网有着非常重要的差别。
Microsoft documentation for Windows Container Networking
中包含额外的细节和背景信息。
Windows 宿主联网服务和虚拟交换机实现了名字空间隔离,可以根据需要为 Pod 或容器
创建虚拟的网络接口(NICs)。不过,很多类似 DNS、路由、度量值之类的配置数据都
保存在 Windows 注册表数据库中而不是像 Linux 一样保存在 /etc/...
文件中。
Windows 为容器提供的注册表与宿主系统的注册表是分离的,因此类似于将 /etc/resolv.conf
文件从宿主系统映射到容器中的做法不会产生与 Linux 系统相同的效果。
这些信息必须在容器内部使用 Windows API 来配置。
因此,CNI 实现需要调用 HNS,而不是依赖文件映射来将网络细节传递到 Pod
或容器中。
Windows 节点不支持以下联网功能:
Windows Pod 不能使用宿主网络模式;
从节点本地访问 NodePort 会失败(但从其他节点或外部客户端可访问)
Windows Server 的未来版本中会支持从节点访问服务的 VIP;
每个服务最多支持 64 个后端 Pod 或独立的目标 IP 地址;
kube-proxy 的覆盖网络支持是 Beta 特性。此外,它要求在 Windows Server 2019 上安装
KB4482887 补丁;
非 DSR(保留目标地址)模式下的本地流量策略;
连接到覆盖网络的 Windows 容器不支持使用 IPv6 协议栈通信。
要使得这一网络驱动支持 IPv6 地址需要在 Windows 平台上开展大量的工作,
还需要在 Kubernetes 侧修改 kubelet、kube-proxy 以及 CNI 插件。
Kubernetes v1.15 中添加了以下功能特性:
CNI 插件
DNS
不支持 DNS 的 ClusterFirstWithHostNet 配置。Windows 将所有包含 “.” 的名字
视为全限定域名(FQDN),因而不会对其执行部分限定域名(PQDN)解析。
在 Linux 上,你可以有一个 DNS 后缀列表供解析部分限定域名时使用。
在 Windows 上,我们只有一个 DNS 后缀,即与该 Pod 名字空间相关联的 DNS
后缀(例如 mydns.svc.cluster.local
)。
Windows 可以解析全限定域名、或者恰好可用该后缀来解析的服务名称。
例如,在 default 名字空间中生成的 Pod 会获得 DNS 后缀
default.svc.cluster.local
。在 Windows Pod 中,你可以解析
kubernetes.default.svc.cluster.local
和 kubernetes
,但无法解析二者
之间的形式,如 kubernetes.default
或 kubernetes.default.svc
。
在 Windows 上,可以使用的 DNS 解析程序有很多。由于这些解析程序彼此之间
会有轻微的行为差别,建议使用 Resolve-DNSName
工具来完成名字查询解析。
IPv6
Windows 上的 Kubernetes 不支持单协议栈的“只用 IPv6”联网选项。
不过,系统支持在 IPv4/IPv6 双协议栈的 Pod 和节点上运行单协议家族的服务。
更多细节可参阅 IPv4/IPv6 双协议栈联网 一节。
会话亲和性
不支持使用 service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
来为
Windows 服务设置最大会话粘滞时间。
安全性
Secret 以明文形式写入节点的卷中(而不是像 Linux 那样写入内存或 tmpfs 中)。
这意味着客户必须做以下两件事:
使用文件访问控制列表来保护 Secret 文件所在的位置
使用 BitLocker
来执行卷层面的加密
用户可以为 Windows Pods 或 Container 设置
RunAsUserName
以便以非节点默认用户来执行容器中的进程。这大致等价于设置
RunAsUser
。
不支持特定于 Linux 的 Pod 安全上下文特权,例如 SELinux、AppArmor、Seccomp、
权能字(POSIX 权能字)等等。
此外,如前所述,Windows 不支持特权容器。
API
对 Windows 而言,大多数 Kubernetes API 的工作方式没有变化。
一些不易察觉的差别通常体现在 OS 和容器运行时上的不同。
在某些场合,负载 API (如 Pod 或 Container)的某些属性在设计时假定其
在 Linux 上实现,因此会无法在 Windows 上运行。
在较高层面,不同的 OS 概念有:
身份标识 - Linux 使用证书类型来表示用户 ID(UID)和组 ID(GID)。用户和组名
没有特定标准,它们是 /etc/groups
或 /etc/passwd
中的别名表项,会映射回
UID+GID。Windows 使用一个更大的二进制安全标识符(SID),保存在 Windows
安全访问管理器(Security Access Manager,SAM)数据库中。此数据库并不在宿主系统
与容器间,或者任意两个容器之间共享。
文件许可 - Windows 使用基于 SID 的访问控制列表,而不是基于 UID+GID 的访问权限位掩码。
退出代码遵从相同的习惯,0 表示成功,非 0 值表示失败。
特定的错误代码在 Windows 和 Linux 上可能会不同。不过,从 Kubernetes 组件
(kubelet、kube-proxy)所返回的退出代码是没有变化的。
v1.Container.ResourceRequirements.requests.cpu
和
v1.Container.ResourceRequirements.requests.memory
- 请求
值会从节点可分配资源中扣除,从而可用来避免节点上的资源过量分配。
但是,它们无法用来在一个已经过量分配的节点上提供资源保障。
如果操作员希望彻底避免过量分配,作为最佳实践,他们就需要为所有容器设置资源请求值。
v1.Container.SecurityContext.allowPrivilegeEscalation
- 在 Windows
上无法实现,对应的权能无一可在 Windows 上生效。
v1.Container.SecurityContext.Capabilities
- Windows 上未实现 POSIX 权能机制
v1.Container.SecurityContext.privileged
- Windows 不支持特权容器
v1.Container.SecurityContext.procMount
- Windows 不包含 /proc
文件系统
v1.Container.SecurityContext.readOnlyRootFilesystem
- 在 Windows 上无法实现,
要在容器内使用注册表或运行系统进程就必需写访问权限。
v1.Container.SecurityContext.runAsGroup
- 在 Windows 上无法实现,没有 GID 支持
v1.Container.SecurityContext.runAsNonRoot
- Windows 上没有 root 用户。
与之最接近的等价用户是 ContainerAdministrator
,而该身份标识在节点上并不存在。
v1.Container.SecurityContext.runAsUser
- 在 Windows 上无法实现,
因为没有作为整数支持的 GID。
v1.Container.SecurityContext.seLinuxOptions
- 在 Windows 上无法实现,
因为没有 SELinux
V1.Container.terminationMessagePath
- 因为 Windows 不支持单个文件的映射,这一功能
在 Windows 上也受限。默认值 /dev/termination-log
在 Windows 上也无法使用因为
对应路径在 Windows 上不存在。
V1.Pod
v1.Pod.hostIPC
、v1.Pod.hostPID
- Windows 不支持共享宿主系统的名字空间
v1.Pod.hostNetwork
- Windows 操作系统不支持共享宿主网络
v1.Pod.dnsPolicy
- 不支持 ClusterFirstWithHostNet
,因为 Windows 不支持宿主网络
v1.Pod.podSecurityContext
- 参见下面的 v1.PodSecurityContext
v1.Pod.shareProcessNamespace
- 此为 Beta 特性且依赖于 Windows 上未实现
的 Linux 名字空间。
Windows 无法共享进程名字空间或者容器的根文件系统。只能共享网络。
v1.Pod.terminationGracePeriodSeconds
- 这一特性未在 Windows 版本的 Docker 中完全实现。
参见问题报告 。
目前实现的行为是向 ENTRYPOINT
进程发送 CTRL_SHUTDOWN_EVENT
事件,之后 Windows 默认
等待 5 秒钟,并最终使用正常的 Windows 关机行为关闭所有进程。
这里的 5 秒钟默认值实际上保存在
容器内
的 Windows 注册表中,因此可以在构造容器时重载。
v1.Pod.volumeDevices
- 此为 Beta 特性且未在 Windows 上实现。Windows 无法挂接
原生的块设备到 Pod 中。
V1.PodSecurityContext
PodSecurityContext 的所有选项在 Windows 上都无法工作。这些选项列在下面仅供参考。
v1.PodSecurityContext.seLinuxOptions
- Windows 上无 SELinux
v1.PodSecurityContext.runAsUser
- 提供 UID;Windows 不支持
v1.PodSecurityContext.runAsGroup
- 提供 GID;Windows 不支持
v1.PodSecurityContext.runAsNonRoot
- Windows 上没有 root 用户
最接近的等价账号是 ContainerAdministrator
,而该身份标识在节点上不存在
v1.PodSecurityContext.supplementalGroups
- 提供 GID;Windows 不支持
v1.PodSecurityContext.sysctls
- 这些是 Linux sysctl 接口的一部分;Windows 上
没有等价机制。
操作系统版本限制
Windows 有着严格的兼容性规则,宿主 OS 的版本必须与容器基准镜像 OS 的版本匹配。
目前仅支持容器操作系统为 Windows Server 2019 的 Windows 容器。
对于容器的 Hyper-V 隔离、允许一定程度上的 Windows 容器镜像版本向后兼容性等等,
都是将来版本计划的一部分。
获取帮助和故障排查
对你的 Kubernetes 集群进行排查的主要帮助信息来源应该是
这份文档 。
该文档中包含了一些额外的、特定于 Windows 系统的故障排查帮助信息。
Kubernetes 中日志是故障排查的一个重要元素。确保你在尝试从其他贡献者那里获得
故障排查帮助时提供日志信息。你可以按照 SIG-Windows
贡献指南和收集日志
所给的指令来操作。
我的 Windows Pods 无法 ping 外部资源
Windows Pods 目前没有为 ICMP 协议提供出站规则。不过 TCP/UDP 是支持的。
尝试与集群外资源连接时,可以将 ping <IP>
命令替换为对应的 curl <IP>
命令。
如果你还遇到问题,很可能你在
cni.conf
中的网络配置值得额外的注意。你总是可以编辑这一静态文件。
配置的更新会应用到所有新创建的 Kubernetes 资源上。
Kubernetes 网络的需求之一(参见
Kubernetes 网络模型 )
是集群内部无需网络地址转译(NAT)即可实现通信。
为了符合这一要求,对所有我们不希望出站时发生 NAT 的通信都存在一个
ExceptionList 。
然而这也意味着你需要将你要查询的外部 IP 从 ExceptionList 中移除。
只有这时,从你的 Windows Pod 发起的网络请求才会被正确地通过 SNAT 转换以接收到
来自外部世界的响应。
就此而言,你在 cni.conf
中的 ExceptionList
应该看起来像这样:
"ExceptionList": [
"10.244.0.0/16", # 集群子网
"10.96.0.0/12", # 服务子网
"10.127.130.0/24" # 管理(主机)子网
]
我的 Windows Pods 无法启动,因为缺少 /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
启动 kubelet 时找不到网络适配器
Windows 网络堆栈需要一个虚拟的适配器,这样 Kubernetes 网络才能工作。
如果下面的命令(在管理员 Shell 中)没有任何返回结果,证明虚拟网络创建
(kubelet 正常工作的必要前提之一)失败了:
Get-HnsNetwork | ? Name -ieq "cbr0"
Get-NetAdapter | ? Name -Like "vEthernet (Ethernet*"
当宿主系统的网络适配器名称不是 "Ethernet" 时,通常值得更改 start.ps1
脚本中的
InterfaceName
参数来重试。否则可以查验 start-kubelet.ps1
的输出,看看是否在虚拟网络创建
过程中报告了其他错误。
我的 Pods 停滞在 "Container Creating" 状态或者反复重启
检查你的 pause 镜像是与你的 OS 版本兼容的。
这里的指令
假定你的 OS 和容器版本都是 1803。如果你安装的是更新版本的 Windows,比如说
某个 Insider 构造版本,你需要相应地调整要使用的镜像。
请参照 Microsoft 的 Docker 仓库
了解镜像。不管怎样,pause 镜像的 Dockerfile 和示例服务都期望镜像的标签
为 :latest
。
kubectl port-forward
失败,错误信息为 "unable to do port forwarding: wincat not found"
此功能是在 Kubernetes v1.15 中实现的,pause 基础设施容器
mcr.microsoft.com/oss/kubernetes/pause:3.4.1
中包含了 wincat.exe。
请确保你使用的是这些版本或者更新版本。
如果你想要自行构造你自己的 pause 基础设施容器,要确保其中包含了
wincat
Windows 的端口转发支持需要在 pause 基础设施容器 中提供 wincat.exe。
确保你使用的是与你的 Windows 操作系统版本兼容的受支持镜像。
如果你想构建自己的 pause 基础架构容器,请确保包含 wincat .。
进一步探究
如果以上步骤未能解决你遇到的问题,你可以通过以下方式获得在 Kubernetes
中的 Windows 节点上运行 Windows 容器的帮助:
报告问题和功能需求
如果你遇到看起来像是软件缺陷的问题,或者你想要提起某种功能需求,请使用
GitHub 问题跟踪系统 。
你可以在 GitHub
上发起 Issue 并将其指派给 SIG-Windows。你应该首先搜索 Issue 列表,看看是否
该 Issue 以前曾经被报告过,以评论形式将你在该 Issue 上的体验追加进去,并附上
额外的日志信息。SIG-Windows Slack 频道也是一个获得初步支持的好渠道,可以在
生成新的 Ticket 之前对一些想法进行故障分析。
在登记软件缺陷时,请给出如何重现该问题的详细信息,例如:
Kubernetes 版本:kubectl 版本
环境细节:云平台、OS 版本、网络选型和配置情况以及 Docker 版本
重现该问题的详细步骤
相关的日志
通过为该 Issue 添加 /sig windows
评论为其添加 sig/windows
标签,
进而引起 SIG-Windows 成员的注意。
接下来
在我们的未来蓝图中包含很多功能特性(要实现)。下面是一个浓缩的简要列表,不过我们
鼓励你查看我们的 roadmap 项目 并
通过贡献 的方式
帮助我们把 Windows 支持做得更好。
Hyper-V 隔离
要满足 Kubernetes 中 Windows 容器的如下用例,需要利用 Hyper-V 隔离:
在 Pod 之间实施基于监管程序(Hypervisor)的隔离,以增强安全性
出于向后兼容需要,允许添加运行新 Windows Server 版本的节点时不必
重新创建容器
为 Pod 设置特定的 CPU/NUMA 配置
实施内存隔离与预留
使用 kubeadm 和 Cluster API 来部署
kubeadm 已经成为用户部署 Kubernetes 集群的事实标准。
kubeadm 对 Windows 节点的支持目前还在开发过程中,不过你可以阅读相关的
指南 。
我们也在投入资源到 Cluster API,以确保 Windows 节点被正确配置。
2.2.4.2 - Kubernetes 中 Windows 容器的调度指南
Windows 应用程序构成了许多组织中运行的服务和应用程序的很大一部分。
本指南将引导您完成在 Kubernetes 中配置和部署 Windows 容器的步骤。
目标
配置一个示例 deployment 以在 Windows 节点上运行 Windows 容器
(可选)使用组托管服务帐户(GMSA)为您的 Pod 配置 Active Directory 身份
在你开始之前
创建一个 Kubernetes 集群,其中包括一个控制平面和
运行 Windows 服务器的工作节点
重要的是要注意,对于 Linux 和 Windows 容器,在 Kubernetes
上创建和部署服务和工作负载的行为几乎相同。
与集群接口的 kubectl 命令 相同。
提供以下部分中的示例只是为了快速启动 Windows 容器的使用体验。
入门:部署 Windows 容器
要在 Kubernetes 上部署 Windows 容器,您必须首先创建一个示例应用程序。
下面的示例 YAML 文件创建了一个简单的 Web 服务器应用程序。
创建一个名为 win-webserver.yaml
的服务规约,其内容如下:
apiVersion : v1
kind : Service
metadata :
name : win-webserver
labels :
app : win-webserver
spec :
ports :
# the port that this service should serve on
- 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 直接暴露给服务。
检查所有节点是否健康:
部署服务并观察 pod 更新:
kubectl apply -f win-webserver.yaml
kubectl get pods -o wide -w
正确部署服务后,两个 Pod 都标记为“Ready”。要退出 watch 命令,请按 Ctrl + C。
检查部署是否成功。验证:
Windows 节点上每个 Pod 有两个容器,使用 docker ps
Linux 控制平面节点列出两个 Pod,使用 kubectl get pods
跨网络的节点到 Pod 通信,从 Linux 控制平面节点 curl
您的 pod IPs 的端口80,以检查 Web 服务器响应
Pod 到 Pod 的通信,使用 docker exec 或 kubectl exec 在 Pod 之间
(以及跨主机,如果你有多个 Windows 节点)进行 ping 操作
服务到 Pod 的通信,从 Linux 控制平面节点和各个 Pod 中 curl
虚拟服务 IP
(在 kubectl get services
下可见)
服务发现,使用 Kubernetes curl
服务名称
默认 DNS 后缀
入站连接,从 Linux 控制平面节点或集群外部的计算机 curl
NodePort
出站连接,使用 kubectl exec 从 Pod 内部 curl 外部 IP
说明: 由于当前平台对 Windows 网络堆栈的限制,Windows 容器主机无法访问在其上调度的服务的 IP。只有 Windows pods 才能访问服务 IP。
可观测性
抓取来自工作负载的日志
日志是可观测性的重要一环;使用日志用户可以获得对负载运行状况的洞察,
因而日志是故障排查的一个重要手法。
因为 Windows 容器中的 Windows 容器和负载与 Linux 容器的行为不同,
用户很难收集日志,因此运行状态的可见性很受限。
例如,Windows 工作负载通常被配置为将日志输出到 Windows 事件跟踪
(Event Tracing for Windows,ETW),或者将日志条目推送到应用的事件日志中。
LogMonitor
是 Microsoft 提供的一个开源工具,是监视 Windows 容器中所配置的日志源
的推荐方式。
LogMonitor 支持监视时间日志、ETW 提供者模块以及自定义的应用日志,
并使用管道的方式将其输出到标准输出(stdout),以便 kubectl logs <pod>
这类命令能够读取这些数据。
请遵照 LogMonitor GitHub 页面上的指令,将其可执行文件和配置文件复制到
你的所有容器中,并为其添加必要的入口点(Entrypoint),以便 LogMonitor
能够将你的日志输出推送到标准输出(stdout)。
使用可配置的容器用户名
从 Kubernetes v1.16 开始,可以为 Windows 容器配置与其镜像默认值不同的用户名
来运行其入口点和进程。
此能力的实现方式和 Linux 容器有些不同。
在此处
可了解更多信息。
使用组托管服务帐户管理工作负载身份
从 Kubernetes v1.14 开始,可以将 Windows 容器工作负载配置为使用组托管服务帐户(GMSA)。
组托管服务帐户是 Active Directory 帐户的一种特定类型,它提供自动密码管理,
简化的服务主体名称(SPN)管理以及将管理委派给跨多台服务器的其他管理员的功能。
配置了 GMSA 的容器可以访问外部 Active Directory 域资源,同时携带通过 GMSA 配置的身份。
在此处 了解有关为
Windows 容器配置和使用 GMSA 的更多信息。
污点和容忍度
目前,用户需要将 Linux 和 Windows 工作负载运行在各自特定的操作系统的节点上,
因而需要结合使用污点和节点选择算符。 这可能仅给 Windows 用户造成不便。
推荐的方法概述如下,其主要目标之一是该方法不应破坏与现有 Linux 工作负载的兼容性。
确保特定操作系统的工作负载落在适当的容器主机上
用户可以使用污点和容忍度确保 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 charts,以及程序化 Pod 生成案例,例如 Operators。
在这些情况下,您可能会不愿意更改配置添加 nodeSelector。替代方法是使用污点。
由于 kubelet 可以在注册期间设置污点,因此可以轻松修改它,使其仅在 Windows 上运行时自动添加污点。
例如:--register-with-taints='os=windows:NoSchedule'
向所有 Windows 节点添加污点后,Kubernetes 将不会在它们上调度任何负载(包括现有的 Linux Pod)。
为了使某 Windows Pod 调度到 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 版本相匹配。
如果要在同一集群中使用多个 Windows Server 版本,则应该设置其他节点标签和
nodeSelector。
Kubernetes 1.17 自动添加了一个新标签 node.kubernetes.io/windows-build
来简化此操作。
如果您运行的是旧版本,则建议手动将此标签添加到 Windows 节点。
此标签反映了需要兼容的 Windows 主要、次要和内部版本号。以下是当前每个
Windows Server 版本使用的值。
产品名称
内部编号
Windows Server 2019
10.0.17763
Windows Server version 1809
10.0.17763
Windows Server version 1903
10.0.18362
使用 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
2.3 - 最佳实践
2.3.1 - 运行于多可用区环境
本页描述如何跨多个区(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.2 - 大规模集群的注意事项
集群是运行 Kubernetes 代理的、
由控制平面 管理的一组
节点 (物理机或虚拟机)。
Kubernetes v1.23 支持的最大节点数为 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.3 - 校验节点设置
节点一致性测试
节点一致性测试 是一个容器化的测试框架,提供了针对节点的系统验证和功能测试。
该测试主要检测节点是否满足 Kubernetes 的最低要求,通过检测的节点有资格加入 Kubernetes 集群。
节点的前提条件
要运行节点一致性测试,节点必须满足与标准 Kubernetes 节点相同的前提条件。节点至少应安装以下守护程序:
运行节点一致性测试
要运行节点一致性测试,请执行以下步骤:
得出 kubelet 的 --kubeconfig
的值;例如:--kubeconfig=/var/lib/kubelet/config.yaml
.
由于测试框架启动了本地控制平面来测试 kubelet, 因此使用 http://localhost:8080
作为API 服务器的 URL。
一些其他的 kubelet 命令行参数可能会被用到:
--pod-cidr
: 如果使用 kubenet
, 需要为 Kubelet 任意指定一个 CIDR,
例如 --pod-cidr=10.180.0.0/24
。
--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 \
k8s.gcr.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
k8s.gcr.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 测试外的所有一致性测试内容
k8s.gcr.io/node-test:0.2
节点一致性测试是节点端到端测试 的容器化版本。
默认情况下,它会运行所有一致性测试。
理论上,只要合理地配置容器和挂载所需的卷,就可以运行任何的节点端到端测试用例。 但是这里强烈建议只运行一致性测试 ,因为运行非一致性测试需要很多复杂的配置。
注意
测试会在节点上遗留一些 Docker 镜像, 包括节点一致性测试本身的镜像和功能测试相关的镜像。
测试会在节点上遗留一些死的容器。这些容器是在功能测试的过程中创建的。
2.3.4 - PKI 证书和要求
Kubernetes 需要 PKI 证书才能进行基于 TLS 的身份验证。如果你是使用
kubeadm 安装的 Kubernetes,
则会自动生成集群所需的证书。你还可以生成自己的证书。
例如,不将私钥存储在 API 服务器上,可以让私钥更加安全。此页面说明了集群必需的证书。
集群是如何使用证书的
Kubernetes 需要 PKI 才能执行以下操作:
Kubelet 的客户端证书,用于 API 服务器身份验证
API 服务器端点的证书
集群管理员的客户端证书,用于 API 服务器身份认证
API 服务器的客户端证书,用于和 Kubelet 的会话
API 服务器的客户端证书,用于和 etcd 的会话
控制器管理器的客户端证书/kubeconfig,用于和 API 服务器的会话
调度器的客户端证书/kubeconfig,用于和 API 服务器的会话
前端代理 的客户端及服务端证书
说明: 只有当你运行 kube-proxy 并要支持
扩展 API 服务器
时,才需要
front-proxy
证书
etcd 还实现了双向 TLS 来对客户端和对其他对等节点进行身份验证。
证书存放的位置
如果你是通过 kubeadm 安装的 Kubernetes,所有证书都存放在 /etc/kubernetes/pki
目录下。本文所有相关的路径都是基于该路径的相对路径。
手动配置证书
如果你不想通过 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 的私钥拷贝至你的集群中,你也可以自己生成全部的证书。
需要这些证书:
默认 CN
父级 CA
O (位于 Subject 中)
类型
主机 (SAN)
kube-etcd
etcd-ca
server, client
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
为用户帐户配置证书
你必须手动配置以下管理员帐户和服务帐户:
文件名
凭据名称
默认 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
清单中
3 - 概念
概念部分可以帮助你了解 Kubernetes 的各个组成部分以及 Kubernetes 用来表示集群的一些抽象概念,并帮助你更加深入的理解 Kubernetes 是如何工作的。
3.1 - 概述
获得 Kubernetes 及其构件的高层次概要。
3.1.1 - Kubernetes 是什么?
Kubernetes 是一个可移植的,可扩展的开源平台,用于管理容器化的工作负载和服务,方便了声明式配置和自动化。它拥有一个庞大且快速增长的生态系统。Kubernetes 的服务,支持和工具广泛可用。
此页面是 Kubernetes 的概述。
Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。
Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。
Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。k8s 这个缩写是因为 k 和 s 之间有八个字符的关系。
Google 在 2014 年开源了 Kubernetes 项目。Kubernetes 建立在
Google 在大规模运行生产工作负载方面拥有十几年的经验
的基础上,结合了社区中最好的想法和实践。
时光回溯
让我们回顾一下为什么 Kubernetes 如此有用。
传统部署时代:
早期,各个组织机构在物理服务器上运行应用程序。无法为物理服务器中的应用程序定义资源边界,这会导致资源分配问题。
例如,如果在物理服务器上运行多个应用程序,则可能会出现一个应用程序占用大部分资源的情况,
结果可能导致其他应用程序的性能下降。
一种解决方案是在不同的物理服务器上运行每个应用程序,但是由于资源利用不足而无法扩展,
并且维护许多物理服务器的成本很高。
虚拟化部署时代:
作为解决方案,引入了虚拟化。虚拟化技术允许你在单个物理服务器的 CPU 上运行多个虚拟机(VM)。
虚拟化允许应用程序在 VM 之间隔离,并提供一定程度的安全,因为一个应用程序的信息
不能被另一应用程序随意访问。
虚拟化技术能够更好地利用物理服务器上的资源,并且因为可轻松地添加或更新应用程序
而可以实现更好的可伸缩性,降低硬件成本等等。
每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。
容器部署时代:
容器类似于 VM,但是它们具有被放宽的隔离属性,可以在应用程序之间共享操作系统(OS)。
因此,容器被认为是轻量级的。容器与 VM 类似,具有自己的文件系统、CPU、内存、进程空间等。
由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。
容器因具有许多优势而变得流行起来。下面列出的是容器的一些好处:
敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。
持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性),支持可靠且频繁的
容器镜像构建和部署。
关注开发与运维的分离:在构建/发布时而不是在部署时创建应用程序容器镜像,
从而将应用程序与基础架构分离。
可观察性:不仅可以显示操作系统级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。
跨开发、测试和生产的环境一致性:在便携式计算机上与在云中相同地运行。
跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、
Google Kubernetes Engine 和其他任何地方运行。
以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在
OS 上运行应用程序。
松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分,
并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。
资源隔离:可预测的应用程序性能。
资源利用:高效率和高密度。
为什么需要 Kubernetes,它能做什么?
容器是打包和运行应用程序的好方式。在生产环境中,你需要管理运行应用程序的容器,并确保不会停机。
例如,如果一个容器发生故障,则需要启动另一个容器。如果系统处理此行为,会不会更容易?
这就是 Kubernetes 来解决这些问题的方法!
Kubernetes 为你提供了一个可弹性运行分布式系统的框架。
Kubernetes 会满足你的扩展要求、故障转移、部署模式等。
例如,Kubernetes 可以轻松管理系统的 Canary 部署。
Kubernetes 为你提供:
Kubernetes 不是什么
Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。
由于 Kubernetes 在容器级别而不是在硬件级别运行,它提供了 PaaS 产品共有的一些普遍适用的功能,
例如部署、扩展、负载均衡、日志记录和监视。
但是,Kubernetes 不是单体系统,默认解决方案都是可选和可插拔的。
Kubernetes 提供了构建开发人员平台的基础,但是在重要的地方保留了用户的选择和灵活性。
Kubernetes:
不限制支持的应用程序类型。
Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。
如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。
不部署源代码,也不构建你的应用程序。
持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。
不提供应用程序级别的服务作为内置服务,例如中间件(例如,消息中间件)、
数据处理框架(例如,Spark)、数据库(例如,mysql)、缓存、集群存储系统
(例如,Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在
Kubernetes 上的应用程序通过可移植机制(例如,
开放服务代理 )来访问。
不要求日志记录、监视或警报解决方案。
它提供了一些集成作为概念证明,并提供了收集和导出指标的机制。
不提供或不要求配置语言/系统(例如 jsonnet),它提供了声明性 API,
该声明性 API 可以由任意形式的声明性规范所构成。
不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。
此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。
编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。
相比之下,Kubernetes 包含一组独立的、可组合的控制过程,
这些过程连续地将当前状态驱动到所提供的所需状态。
如何从 A 到 C 的方式无关紧要,也不需要集中控制,这使得系统更易于使用
且功能更强大、系统更健壮、更为弹性和可扩展。
接下来
3.1.2 - Kubernetes 组件
Kubernetes 集群由代表控制平面的组件和一组称为节点的机器组成。
当你部署完 Kubernetes, 即拥有了一个完整的集群。
一个 Kubernetes 集群由一组被称作节点的机器组成。这些节点上运行 Kubernetes 所管理的容器化应用。集群具有至少一个工作节点。
工作节点托管作为应用负载的组件的 Pod 。控制平面管理集群中的工作节点和 Pod 。
为集群提供故障转移和高可用性,这些控制平面一般跨多主机运行,集群跨多个节点运行。
本文档概述了交付正常运行的 Kubernetes 集群所需的各种组件。
这张图表展示了包含所有相互关联组件的 Kubernetes 集群。
控制平面组件(Control Plane Components)
控制平面的组件对集群做出全局决策(比如调度),以及检测和响应集群事件(例如,当不满足部署的
replicas
字段时,启动新的 pod )。
控制平面组件可以在集群中的任何节点上运行。
然而,为了简单起见,设置脚本通常会在同一个计算机上启动所有控制平面组件,
并且不会在此计算机上运行用户容器。
请参阅使用 kubeadm 构建高可用性集群
中关于多 VM 控制平面设置的示例。
kube-apiserver
API 服务器是 Kubernetes 控制面 的组件,
该组件公开了 Kubernetes API。
API 服务器是 Kubernetes 控制面的前端。
Kubernetes API 服务器的主要实现是 kube-apiserver 。
kube-apiserver 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。
你可以运行 kube-apiserver 的多个实例,并在这些实例之间平衡流量。
etcd
etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
您的 Kubernetes 集群的 etcd 数据库通常需要有个备份计划。
要了解 etcd 更深层次的信息,请参考 etcd 文档 。
kube-scheduler
控制平面组件,负责监视新创建的、未指定运行节点(node) 的 Pods ,选择节点让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。
kube-controller-manager
运行控制器 进程的控制平面组件。
从逻辑上讲,每个控制器 都是一个单独的进程,
但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。
这些控制器包括:
节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应
任务控制器(Job controller): 监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成
端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)
服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌
cloud-controller-manager
云控制器管理器是指嵌入特定云的控制逻辑的
控制平面 组件。
云控制器管理器使得你可以将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
cloud-controller-manager
仅运行特定于云平台的控制回路。
如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境,
所部署的环境中不需要云控制器管理器。
与 kube-controller-manager
类似,cloud-controller-manager
将若干逻辑上独立的
控制回路组合到同一个可执行文件中,供你以同一进程的方式运行。
你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。
下面的控制器都包含对云平台驱动的依赖:
节点控制器(Node Controller): 用于在节点终止响应后检查云提供商以确定节点是否已被删除
路由控制器(Route Controller): 用于在底层云基础架构中设置路由
服务控制器(Service Controller): 用于创建、更新和删除云提供商负载均衡器
Node 组件
节点组件在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行环境。
kubelet
一个在集群中每个节点(node) 上运行的代理。
它保证容器(containers) 都
运行在 Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs
中描述的容器处于运行状态且健康。
kubelet 不会管理不是由 Kubernetes 创建的容器。
kube-proxy
kube-proxy 是集群中每个节点上运行的网络代理,
实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
如果操作系统提供了数据包过滤层并可用的话,kube-proxy 会通过它来实现网络规则。否则, kube-proxy 仅转发流量本身。
容器运行时(Container Runtime)
容器运行环境是负责运行容器的软件。
Kubernetes 支持多个容器运行环境: Docker 、
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.3 - 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 来表述的。
Kubernetes API 服务器通过 /openapi/v2
末端提供 OpenAPI 规范。
你可以按照下表所给的请求头部,指定响应的格式:
头部
可选值
说明
Accept-Encoding
gzip
不指定此头部也是可以的
Accept
application/[email protected] +protobuf
主要用于集群内部
application/json
默认值
*
提供 application/json
OpenAPI v2 查询请求的合法头部值
Kubernetes 为 API 实现了一种基于 Protobuf 的序列化格式,主要用于集群内部通信。
关于此格式的详细信息,可参考
Kubernetes Protobuf 序列化
设计提案。每种模式对应的接口描述语言(IDL)位于定义 API 对象的 Go 包中。
API 变更
任何成功的系统都要随着新的使用案例的出现和现有案例的变化来成长和变化。
为此,Kubernetes 的功能特性设计考虑了让 Kubernetes API 能够持续变更和成长的因素。
Kubernetes 项目的目标是 不要 引发现有客户端的兼容性问题,并在一定的时期内
维持这种兼容性,以便其他项目有机会作出适应性变更。
一般而言,新的 API 资源和新的资源字段可以被频繁地添加进来。
删除资源或者字段则要遵从
API 废弃策略 。
关于什么是兼容性的变更、如何变更 API 等详细信息,可参考
API 变更 。
API 组和版本
为了简化删除字段或者重构资源表示等工作,Kubernetes 支持多个 API 版本,
每一个版本都在不同 API 路径下,例如 /api/v1
或
/apis/rbac.authorization.k8s.io/v1alpha1
。
版本化是在 API 级别而不是在资源或字段级别进行的,目的是为了确保 API
为系统资源和行为提供清晰、一致的视图,并能够控制对已废止的和/或实验性 API 的访问。
为了便于演化和扩展其 API,Kubernetes 实现了
可被启用或禁用 的
API 组 。
API 资源之间靠 API 组、资源类型、名字空间(对于名字空间作用域的资源而言)和
名字来相互区分。API 服务器可能通过多个 API 版本来向外提供相同的下层数据,
并透明地完成不同 API 版本之间的转换。所有这些不同的版本实际上都是同一资源
的(不同)表现形式。例如,假定同一资源有 v1
和 v1beta1
版本,
使用 v1beta1
创建的对象则可以使用 v1beta1
或者 v1
版本来读取、更改
或者删除。
关于 API 版本级别的详细定义,请参阅
API 版本参考 。
API 扩展
有两种途径来扩展 Kubernetes API:
你可以使用自定义资源
来以声明式方式定义 API 服务器如何提供你所选择的资源 API。
你也可以选择实现自己的
聚合层
来扩展 Kubernetes API。
接下来
3.1.4 - 使用 Kubernetes 对象
Kubernetes 对象是 Kubernetes 系统中的持久性实体。Kubernetes 使用这些实体表示您的集群状态。了解 Kubernetes 对象模型以及如何使用这些对象。
3.1.4.1 - 理解 Kubernetes 对象
本页说明了 Kubernetes 对象在 Kubernetes API 中是如何表示的,以及如何在 .yaml
格式的文件中表示。
理解 Kubernetes 对象
在 Kubernetes 系统中,Kubernetes 对象 是持久化的实体。
Kubernetes 使用这些实体去表示整个集群的状态。特别地,它们描述了如下信息:
哪些容器化应用在运行(以及在哪些节点上)
可以被应用使用的资源
关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略
Kubernetes 对象是 “目标性记录” —— 一旦创建对象,Kubernetes 系统将持续工作以确保对象存在。
通过创建对象,本质上是在告知 Kubernetes 系统,所需要的集群工作负载看起来是什么样子的,
这就是 Kubernetes 集群的 期望状态(Desired State) 。
操作 Kubernetes 对象 —— 无论是创建、修改,或者删除 —— 需要使用
Kubernetes API 。
比如,当使用 kubectl
命令行接口时,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 规约,并启动我们所期望的应用的 3 个实例
—— 更新状态以与规约相匹配。
如果这些实例中有的失败了(一种状态变更),Kubernetes 系统通过执行修正操作
来响应规约和状态间的不一致 —— 在这里意味着它会启动一个新的实例来替换。
关于对象 spec、status 和 metadata 的更多信息,可参阅
Kubernetes API 约定 。
描述 Kubernetes 对象
创建 Kubernetes 对象时,必须提供对象的规约,用来描述该对象的期望状态,
以及关于对象的一些基本信息(例如名称)。
当使用 Kubernetes API 创建对象时(或者直接创建,或者基于kubectl
),
API 请求必须在请求体中包含 JSON 格式的信息。
大多数情况下,需要在 .yaml 文件中为 kubectl
提供这些信息 。
kubectl
在发起 API 请求时,将这些信息转换成 JSON 格式。
这里有一个 .yaml
示例文件,展示了 Kubernetes Deployment 的必需字段和对象规约:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 2 # tells deployment to run 2 pods matching the template
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 --record
输出类似如下这样:
deployment.apps/nginx-deployment created
必需字段
在想要创建的 Kubernetes 对象对应的 .yaml
文件中,需要配置如下的字段:
apiVersion
- 创建该对象所使用的 Kubernetes API 的版本
kind
- 想要创建的对象的类别
metadata
- 帮助唯一性标识对象的一些数据,包括一个 name
字符串、UID 和可选的 namespace
spec
- 你所期望的该对象的状态
对象 spec
的精确格式对每个 Kubernetes 对象来说是不同的,包含了特定于该对象的嵌套字段。
Kubernetes API 参考
能够帮助我们找到任何我们想创建的对象的规约格式。
例如,Pod 参考文档详细说明了 API 中 Pod 的 spec
字段 ,
Deployment 的参考文档则详细说明了 Deployment 的 spec
字段 。
在这些 API 参考页面中,你将看到提到的 PodSpec 和 DeploymentSpec。
这些名字是 Kubernetes 用来实现其 API 的 Golang 代码的实现细节。
接下来
3.1.4.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.4.3 - 对象名称和 IDs
集群中的每一个对象都有一个名称 来标识在同类资源中的唯一性。
每个 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
说明: 某些资源类型可能具有额外的命名约束。
UIDs
Kubernetes 系统生成的字符串,唯一标识对象。
在 Kubernetes 集群的整个生命周期中创建的每个对象都有一个不同的 uid,它旨在区分类似实体的历史事件。
Kubernetes UIDs 是全局唯一标识符(也叫 UUIDs)。
UUIDs 是标准化的,见 ISO/IEC 9834-8 和 ITU-T X.667.
接下来
3.1.4.4 - 名字空间
Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。
这些虚拟集群被称为名字空间。
在一些文档里名字空间也称为命名空间。
何时使用多个名字空间
名字空间适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。当需要名称空间提供的功能时,请开始使用它们。
名字空间为名称提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。
名字空间不能相互嵌套,每个 Kubernetes 资源只能在一个名字空间中。
名字空间是在多个用户之间划分集群资源的一种方法(通过资源配额 )。
不必使用多个名字空间来分隔仅仅轻微不同的资源,例如同一软件的不同版本:
应该使用标签
来区分同一名字空间中的不同资源。
使用名字空间
名字空间的创建和删除在名字空间的管理指南文档 描述。
说明: 避免使用前缀 kube-
创建名字空间,因为它是为 Kubernetes 系统名字空间保留的。
查看名字空间
你可以使用以下命令列出集群中现存的名字空间:
NAME STATUS AGE
default Active 1d
kube-node-lease Active 1d
kube-system Active 1d
kube-public 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 | grep namespace:
名字空间和 DNS
当你创建一个服务 时,
Kubernetes 会创建一个相应的 DNS 条目 。
该条目的形式是 <服务名称>.<名字空间名称>.svc.cluster.local
,这意味着如果容器只使用
<服务名称>
,它将被解析到本地名字空间的服务。这对于跨多个名字空间(如开发、分级和生产)
使用相同的配置非常有用。如果你希望跨名字空间访问,则需要使用完全限定域名(FQDN)。
并非所有对象都在名字空间中
大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某些名字空间中。
但是名字空间资源本身并不在名字空间中。而且底层资源,例如
节点 和持久化卷不属于任何名字空间。
查看哪些 Kubernetes 资源在名字空间中,哪些不在名字空间中:
# 位于名字空间中的资源
kubectl api-resources --namespaced= true
# 不在名字空间中的资源
kubectl api-resources --namespaced= false
自动打标签
FEATURE STATE: Kubernetes 1.21 [beta]
Kubernetes 控制面会为所有名字空间设置一个不可变更的
标签
kubernetes.io/metadata.name
,只要 NamespaceDefaultLabelName
这一
特性门控
被启用。标签的值是名字空间的名称。
接下来
3.1.4.5 - 标签和选择算符
标签(Labels) 是附加到 Kubernetes 对象(比如 Pods)上的键值对。
标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。
标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。
每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。
"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]
)开头和结尾
包含破折号(-
)、下划线(_
)、点(.
)和字母或数字。
标签选择算符
与名称和 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 : "k8s.gcr.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
指向的一组 Pods 是由标签选择算符定义的。同样,一个 ReplicationController
应该管理的 pods 的数量也是由标签选择算符定义的。
两个对象的标签选择算符都是在 json
或者 yaml
文件中使用映射定义的,并且只支持
基于等值 需求的选择算符:
"selector" : {
"component" : "redis" ,
}
或者
selector :
component : redis
这个选择算符(分别在 json
或者 yaml
格式中) 等价于 component=redis
或 component in (redis)
。
支持基于集合需求的资源
比较新的资源,例如 Job
、
Deployment
、
Replica Set
和
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.4.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.4.7 - Finalizers
Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后,
再完全删除被标记为删除的资源。
Finalizer 提醒控制器 清理被删除的对象拥有的资源。
当你告诉 Kubernetes 删除一个指定了 Finalizer 的对象时,
Kubernetes API 会将该对象标记为删除,使其进入只读状态。
此时控制平面或其他组件会采取 Finalizer 所定义的行动,
而目标对象仍然处于终止中(Terminating)的状态。
这些行动完成后,控制器会删除目标对象相关的 Finalizer。
当 metadata.finalizers
字段为空时,Kubernetes 认为删除已完成。
你可以使用 Finalizer 控制资源的垃圾收集 。
例如,你可以定义一个 Finalizer,在删除目标资源前清理相关资源或基础设施。
你可以通过使用 Finalizers 提醒控制器
在删除目标资源前执行特定的清理任务,
来控制资源的垃圾收集 。
Finalizers 通常不指定要执行的代码。
相反,它们通常是特定资源上的键的列表,类似于注解。
Kubernetes 自动指定了一些 Finalizers,但你也可以指定你自己的。
Finalizers 如何工作
当你使用清单文件创建资源时,你可以在 metadata.finalizers
字段指定 Finalizers。
当你试图删除该资源时,管理该资源的控制器会注意到 finalizers
字段中的值,
并进行以下操作:
修改对象,将你开始执行删除的时间添加到 metadata.deletionTimestamp
字段。
将该对象标记为只读,直到其 metadata.finalizers
字段为空。
然后,控制器试图满足资源的 Finalizers 的条件。
每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers
字段中删除该键。
当该字段为空时,垃圾收集继续进行。
你也可以使用 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 通常因为特殊原因被添加到资源上,所以强行删除它们会导致集群出现问题。
接下来
3.1.4.8 - 字段选择器
字段选择器(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.4.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 不允许跨名字空间指定属主。
名字空间范围的附属可以指定集群范围的或者名字空间范围的属主。
名字空间范围的属主必须 和该附属处于相同的名字空间。
如果名字空间范围的属主和附属不在相同的名字空间,那么该属主引用就会被认为是缺失的,
并且当附属的所有属主引用都被确认不再存在之后,该附属就会被删除。
集群范围的附属只能指定集群范围的属主。
在 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.4.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
字符串
app.kubernetes.io/created-by
创建该资源的控制器或者用户
controller-manager
字符串
为说明这些标签的实际使用情况,请看下面的 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
app.kubernetes.io/created-by : controller-manager
应用和应用实例
应用可以在 Kubernetes 集群中安装一次或多次。在某些情况下,可以安装在同一命名空间中。例如,可以不止一次地为不同的站点安装不同的 WordPress。
应用的名称和实例的名称是分别记录的。例如,WordPress 应用的
app.kubernetes.io/name
为 wordpress
,而其实例名称
app.kubernetes.io/instance
为 wordpress-abcxzy
。
这使得应用和应用的实例均可被识别,应用的每个实例都必须具有唯一的名称。
示例
为了说明使用这些标签的不同方式,以下示例具有不同的复杂性。
一个简单的无状态服务
考虑使用 Deployment
和 Service
对象部署的简单无状态服务的情况。以下两个代码段表示如何以最简单的形式使用标签。
下面的 Deployment
用于监督运行应用本身的 pods。
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 中来执行你的工作负载。
节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。
每个节点包含运行 Pods 所需的服务;
这些节点由 控制面 负责管理。
通常集群中会有若干个节点;而在一个学习用或者资源受限的环境中,你的集群中也可能
只有一个节点。
节点上的组件 包括
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 子域名 。
节点自注册
当 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 向控制面发送状态的频率。
启用节点授权模式 和
NodeRestriction 准入插件
时,仅授权 kubelet
创建或修改其自己的节点资源。
手动节点管理
你可以使用 kubectl
来创建和修改 Node 对象。
如果你希望手动创建节点对象时,请设置 kubelet 标志 --register-node=false
。
你可以修改 Node 对象(忽略 --register-node
设置)。
例如,修改节点上的标签或标记其为不可调度。
你可以结合使用节点上的标签和 Pod 上的选择算符来控制调度。
例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。
如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该节点之上,但不会
影响任何已经在其上的 Pod。
这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
要标记一个节点为不可调度,执行以下命令:
更多细节参考安全腾空节点 。
说明: 被
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 对象并释放它们的名字。
节点生命周期控制器会自动创建代表状况的
污点 。
当调度器将 Pod 指派给某节点时,会考虑节点上的污点。
Pod 则可以通过容忍度(Toleration)表达所能容忍的污点。
容量与可分配
描述节点上的可用资源:CPU、内存和可以调度到节点上的 Pod 的个数上限。
capacity
块中的字段标示节点拥有的资源总量。
allocatable
块指示节点上可供普通 Pod 消耗的资源量。
可以在学习如何在节点上预留计算资源
的时候了解有关容量和可分配资源的更多信息。
信息
描述节点的一般信息,如内核版本、Kubernetes 版本(kubelet
和 kube-proxy
版本)、
容器运行时详细信息,以及 节点使用的操作系统。
kubelet
从节点收集这些信息并将其发布到 Kubernetes API。
心跳
Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。
对于节点,有两种形式的心跳:
更新节点的 .status
Lease 对象
在 kube-node-lease
命名空间 中。
每个节点都有一个关联的 Lease 对象。
与 Node 的 .status
更新相比,Lease
是一种轻量级资源。
使用 Leases
心跳在大型集群中可以减少这些更新对性能的影响。
kubelet 负责创建和更新节点的 .status
,以及更新它们对应的 Lease
。
当状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet 会更新 .status
。
.status
更新的默认间隔为 5 分钟(比不可达节点的 40 秒默认超时时间长很多)。
kubelet
会每 10 秒(默认更新间隔时间)创建并更新其 Lease
对象。
Lease
更新独立于 NodeStatus
更新而发生。
如果 Lease
的更新操作失败,kubelet
会采用指数回退机制,从 200 毫秒开始
重试,最长重试间隔为 7 秒钟。
节点控制器
节点控制器 是
Kubernetes 控制面组件,管理节点的方方面面。
节点控制器在节点的生命周期中扮演多个角色。
第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。
第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。
如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。
如果不可用,节点控制器会将该节点从它的节点列表删除。
第三个是监控节点的健康状况。 节点控制器是负责:
在节点节点不可达的情况下,在 Node 的 .status
中更新 NodeReady
状况。
在这种情况下,节点控制器将 NodeReady
状况更新为 ConditionUnknown
。
如果节点仍然无法访问:对于不可达节点上的所有 Pod触发
API-发起的逐出 。
默认情况下,节点控制器 在将节点标记为 ConditionUnknown
后等待 5 分钟 提交第一个驱逐请求。
节点控制器每隔 --node-monitor-period
秒检查每个节点的状态。
逐出速率限制
大部分情况下,节点控制器把逐出速率限制在每秒 --node-eviction-rate
个(默认为 0.1)。
这表示它每 10 秒钟内至多从一个节点驱逐 Pod。
当一个可用区域(Availability Zone)中的节点变为不健康时,节点的驱逐行为将发生改变。
节点控制器会同时检查可用区域中不健康(NodeReady 状况为 ConditionUnknown
或 ConditionFalse
)
的节点的百分比:
如果不健康节点的比例超过 --unhealthy-zone-threshold
(默认为 0.55),
驱逐速率将会降低。
如果集群较小(意即小于等于 --large-cluster-size-threshold
个节点 - 默认为 50),驱逐操作将会停止。
否则驱逐速率将降为每秒 --secondary-node-eviction-rate
个(默认为 0.01)。
在单个可用区域实施这些策略的原因是当一个可用区域可能从控制面脱离时其它可用区域
可能仍然保持连接。
如果你的集群没有跨越云服务商的多个可用区域,那(整个集群)就只有一个可用区域。
跨多个可用区域部署你的节点的一个关键原因是当某个可用区域整体出现故障时,
工作负载可以转移到健康的可用区域。
因此,如果一个可用区域中的所有节点都不健康时,节点控制器会以正常的速率
--node-eviction-rate
进行驱逐操作。
在所有的可用区域都不健康(也即集群中没有健康节点)的极端情况下,
节点控制器将假设控制面与节点间的连接出了某些问题,它将停止所有驱逐动作(如果故障后部分节点重新连接,
节点控制器会从剩下不健康或者不可达节点中驱逐 pods
)。
节点控制器还负责驱逐运行在拥有 NoExecute
污点的节点上的 Pod,
除非这些 Pod 能够容忍此污点。
节点控制器还负责根据节点故障(例如节点不可访问或没有就绪)为其添加
污点 。
这意味着调度器不会将 Pod 调度到不健康的节点上。
资源容量跟踪
Node 对象会跟踪节点上资源的容量(例如可用内存和 CPU 数量)。
通过自注册 机制生成的 Node 对象会在注册期间报告自身容量。
如果你手动 添加了 Node,你就需要在添加节点时
手动设置节点容量。
Kubernetes 调度器 保证节点上
有足够的资源供其上的所有 Pod 使用。它会检查节点上所有容器的请求的总和不会超过节点的容量。
总的请求包括由 kubelet 启动的所有容器,但不包括由容器运行时直接启动的容器,
也不包括不受 kubelet
控制的其他进程。
节点拓扑
FEATURE STATE: Kubernetes v1.16 [alpha]
如果启用了 TopologyManager
特性门控 ,
kubelet
可以在作出资源分配决策时使用拓扑提示。
参考控制节点上拓扑管理策略
了解详细信息。
节点体面关闭
FEATURE STATE: Kubernetes v1.21 [beta]
kubelet 会尝试检测节点系统关闭事件并终止在节点上运行的 Pods。
在节点终止期间,kubelet 保证 Pod 遵从常规的 Pod 终止流程 。
体面节点关闭特性依赖于 systemd,因为它要利用
systemd 抑制器锁
在给定的期限内延迟节点关闭。
体面节点关闭特性受 GracefulNodeShutdown
特性门控
控制,在 1.21 版本中是默认启用的。
注意,默认情况下,下面描述的两个配置选项,ShutdownGracePeriod
和
ShutdownGracePeriodCriticalPods
都是被设置为 0 的,因此不会激活
体面节点关闭功能。
要激活此功能特性,这两个 kubelet 配置选项要适当配置,并设置为非零值。
在体面关闭节点过程中,kubelet 分两个阶段来终止 Pods:
终止在节点上运行的常规 Pod。
终止在节点上运行的关键 Pod 。
节点体面关闭的特性对应两个
KubeletConfiguration
选项:
ShutdownGracePeriod
:
指定节点应延迟关闭的总持续时间。此时间是 Pod 体面终止的时间总和,不区分常规 Pod 还是
关键 Pod 。
ShutdownGracePeriodCriticalPods
:
在节点关闭期间指定用于终止
关键 Pod
的持续时间。该值应小于 ShutdownGracePeriod
。
例如,如果设置了 ShutdownGracePeriod=30s
和 ShutdownGracePeriodCriticalPods=10s
,
则 kubelet 将延迟 30 秒关闭节点。
在关闭期间,将保留前 20(30 - 10)秒用于体面终止常规 Pod,
而保留最后 10 秒用于终止
关键 Pod 。
说明: 当 Pod 在正常节点关闭期间被驱逐时,它们会被标记为 failed
。
运行 kubectl get pods
将被驱逐的 pod 的状态显示为 Shutdown
。
并且 kubectl describe pod
表示 pod 因节点关闭而被驱逐:
Status: Failed
Reason: Shutdown
Message: Node is shutting, evicting pods
Failed
的 pod 对象将被保留,直到被明确删除或
由 GC 清理 。
与突然的节点终止相比这是一种行为变化。
交换内存管理
FEATURE STATE: Kubernetes v1.22 [alpha]
在 Kubernetes 1.22 之前,节点不支持使用交换内存,并且
默认情况下,如果在节点上检测到交换内存配置,kubelet 将无法启动。 在 1.22
以后,可以在每个节点的基础上启用交换内存支持。
要在节点上启用交换内存,必须启用kubelet 的 NodeSwap
特性门控,
同时使用 --fail-swap-on
命令行参数或者将 failSwapOn
配置
设置为false。
用户还可以选择配置 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 模式。
所有从集群(或所运行的 Pods)发出的 API 调用都终止于 apiserver。
其它控制面组件都没有被设计为可暴露远程服务。
apiserver 被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求,
并启用一种或多种形式的客户端身份认证 机制。
一种或多种客户端鉴权机制 应该被启用,
特别是在允许使用匿名请求
或服务账号令牌 的时候。
应该使用集群的公共根证书开通节点,这样它们就能够基于有效的客户端凭据安全地连接 apiserver。
一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。
请查看 kubelet TLS 启动引导
以了解如何自动提供 kubelet 客户端证书。
想要连接到 apiserver 的 Pod 可以使用服务账号安全地进行连接。
当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。
kubernetes
服务(位于 default
名字空间中)配置了一个虚拟 IP 地址,用于(通过 kube-proxy)转发
请求到 apiserver 的 HTTPS 末端。
控制面组件也通过安全端口与集群的 apiserver 通信。
这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的,
能够在不可信的网络或公网上运行。
控制面到节点
从控制面(apiserver)到节点有两种主要的通信路径。
第一种是从 apiserver 到集群中每个节点上运行的 kubelet 进程。
第二种是从 apiserver 通过它的代理功能连接到任何节点、Pod 或者服务。
API 服务器到 kubelet
从 apiserver 到 kubelet 的连接用于:
获取 Pod 日志
挂接(通过 kubectl)到运行中的 Pod
提供 kubelet 的端口转发功能。
这些连接终止于 kubelet 的 HTTPS 末端。
默认情况下,apiserver 不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击,
在非受信网络或公开网络上运行也是 不安全的 。
为了对这个连接进行认证,使用 --kubelet-certificate-authority
标志给 apiserver
提供一个根证书包,用于 kubelet 的服务证书。
如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 apiserver 和
kubelet 之间使用 SSH 隧道 。
最后,应该启用
kubelet 用户认证和/或鉴权
来保护 kubelet API。
apiserver 到节点、Pod 和服务
从 apiserver 到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。
这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https:
来运行在安全的 HTTPS 连接上。
不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。
因此,虽然连接是加密的,仍无法提供任何完整性保证。
这些连接 目前还不能安全地 在非受信网络或公共网络上运行。
SSH 隧道
Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。在这种配置下,apiserver
建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务)
并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。
这一隧道保证通信不会被暴露到集群节点所运行的网络之外。
SSH 隧道目前已被废弃。除非你了解个中细节,否则不应使用。
Konnectivity 服务是对此通信通道的替代品。
Konnectivity 服务
FEATURE STATE: 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 使用了很多控制器,每个控制器管理集群状态的一个特定方面。
最常见的一个特定的控制器使用一种类型的资源作为它的期望状态,
控制器管理控制另外一种类型的资源向它的期望状态演化。
使用简单的控制器而不是一组相互连接的单体控制回路是很有用的。
控制器会失败,所以 Kubernetes 的设计正是考虑到了这一点。
说明: 可以有多个控制器来创建或者更新相同类型的对象。
在后台,Kubernetes 控制器确保它们只关心与其控制资源相关联的资源。
例如,你可以创建 Deployment 和 Job;它们都可以创建 Pod。
Job 控制器不会删除 Deployment 所创建的 Pod,因为有信息
(标签 )让控制器可以区分这些 Pod。
运行控制器的方式
Kubernetes 内置一组控制器,运行在 kube-controller-manager 内。
这些内置的控制器提供了重要的核心功能。
Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。
Kubernetes 允许你运行一个稳定的控制平面,这样即使某些内置控制器失败了,
控制平面的其他部分会接替它们的工作。
你会遇到某些控制器运行在控制面之外,用以扩展 Kubernetes。
或者,如果你愿意,你也可以自己编写新控制器。
你可以以一组 Pod 来运行你的控制器,或者运行在 Kubernetes 之外。
最合适的方案取决于控制器所要执行的功能是什么。
接下来
3.2.4 - 云控制器管理器
FEATURE STATE: Kubernetes v1.11 [beta]
使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。
Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。
组件 cloud-controller-manager 是指云控制器管理器, 云控制器管理器是指嵌入特定云的控制逻辑的
控制平面 组件。
云控制器管理器使得你可以将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
云控制器管理器组件使云提供商能够以不同于 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 对象。
为了访问 Service 对象,它需要 List、Watch 访问权限;为了更新 Service 对象
它需要 Patch 和 Update 访问权限。
为了能够配置 Service 对应的 Endpoints 资源,它需要 Create、List、Get、Watch
和 Update 等访问权限。
v1/Service
:
List
Get
Watch
Patch
Update
其他
云控制器管理器的实现中,其核心部分需要创建 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.3 - 容器
打包应用及其运行依赖环境的技术。
每个运行的容器都是可重复的;
包含依赖环境在内的标准,意味着无论您在哪里运行它,您都会得到相同的行为。
容器将应用程序从底层的主机设施中解耦。
这使得在不同的云或 OS 环境中部署更加容易。
容器镜像
容器镜像 是一个随时可以运行的软件包,
包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。
根据设计,容器是不可变的:你不能更改已经运行的容器的代码。
如果有一个容器化的应用程序需要修改,则需要构建包含更改的新镜像,然后再基于新构建的镜像重新运行容器。
容器运行时
容器运行环境是负责运行容器的软件。
Kubernetes 支持多个容器运行环境: Docker 、
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 对私有仓库认证
如果你在节点上运行的是 Docker,你可以配置 Docker
容器运行时来向私有容器仓库认证身份。
此方法适用于能够对节点进行配置的场合。
说明: Kubernetes 默认仅支持 Docker 配置中的 auths
和 HttpHeaders
部分,
不支持 Docker 凭据辅助程序(credHelpers
或 credsStore
)。
Docker 将私有仓库的密钥保存在 $HOME/.dockercfg
或 $HOME/.docker/config.json
文件中。如果你将相同的文件放在下面所列的搜索路径中,kubelet
会在拉取镜像时将其用作凭据
数据来源:
{--root-dir:-/var/lib/kubelet}/config.json
{kubelet 当前工作目录}/config.json
${HOME}/.docker/config.json
/.docker/config.json
{--root-dir:-/var/lib/kubelet}/.dockercfg
{kubelet 当前工作目录}/.dockercfg
${HOME}/.dockercfg
/.dockercfg
说明: 你可能不得不为 kubelet
进程显式地设置 HOME=/root
环境变量。
推荐采用如下步骤来配置节点以便访问私有仓库。以下示例中,在 PC 或笔记本电脑中操作:
针对你要使用的每组凭据,运行 docker login [服务器]
命令。这会更新
你本地环境中的 $HOME/.docker/config.json
文件。
在编辑器中打开查看 $HOME/.docker/config.json
文件,确保其中仅包含你要
使用的凭据信息。
获得节点列表;例如:
如果想要节点名称:nodes=$(kubectl get nodes -o jsonpath='{range.items[*].metadata}{.name} {end}')
如果想要节点 IP ,nodes=$(kubectl get nodes -o jsonpath='{range .items[*].status.addresses[?(@.type=="ExternalIP")]}{.address} {end}')
将本地的 .docker/config.json
拷贝到所有节点,放入如上所列的目录之一:
例如,可以试一下:for n in $nodes; do scp ~/.docker/config.json root@"$n":/var/lib/kubelet/config.json; done
说明: 对于产品环境的集群,可以使用配置管理工具来将这些设置应用到
你所期望的节点上。
创建使用私有镜像的 Pod 来验证。例如:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: private-image-test-1
spec:
containers:
- name: uses-private-image
image: $PRIVATE_IMAGE_NAME
imagePullPolicy: Always
command: [ "echo", "SUCCESS" ]
EOF
输出类似于:
pod/private-image-test-1 created
如果一切顺利,那么一段时间后你可以执行:
kubectl logs private-image-test-1
然后可以看到命令的输出:
SUCCESS
如果你怀疑命令失败了,你可以运行:
kubectl describe pods/private-image-test-1 | grep 'Failed'
如果命令确实失败,输出类似于:
Fri, 26 Jun 2015 15:36:13 -0700 Fri, 26 Jun 2015 15:39:13 -0700 19 {kubelet node-i2hq} spec.containers{uses-private-image} failed Failed to pull image "user/privaterepo:v1": Error: image user/privaterepo:v1 not found
你必须确保集群中所有节点的 .docker/config.json
文件内容相同。
否则,Pod 会能在一些节点上正常运行而无法在另一些节点上启动。
例如,如果使用节点自动扩缩,那么每个实例模板都需要包含 .docker/config.json
,
或者挂载一个包含该文件的驱动器。
在 .docker/config.json
中配置了私有仓库密钥后,所有 Pod 都将能读取私有仓库中的镜像。
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 中设置容器镜像仓库的密钥。
使用 Docker Config 创建 Secret
运行以下命令,将大写字母代替为合适的值:
kubectl create secret docker-registry <名称> \
--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
现在,在创建 Pod 时,可以在 Pod 定义中增加 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
配置结合使用。
来自不同来源的凭据会被合并。
使用案例
配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。
集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。
使用 Docker hub 上的公开镜像
无需配置
某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间
集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见
使用托管的私有 Docker 仓库 。
可以托管在 Docker Hub 或者其他地方
按照上面的描述,在每个节点上手动配置 .docker/config.json
文件
或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限
使用控制镜像访问的托管容器镜像仓库服务
与手动配置节点相比,这种方案能更好地处理集群自动扩缩容
或者,在不方便更改节点配置的集群中,使用 imagePullSecrets
集群使用专有镜像,且有些镜像需要更严格的访问控制
集群是多租户的并且每个租户需要自己的私有仓库
确保 AlwaysPullImages 准入控制器 。否则,所有租户的所有的 Pod 都可以使用所有镜像。
为私有仓库启用鉴权
为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secrert 发布到各租户的命名空间下。
租户将 Secret 添加到每个名字空间中的 imagePullSecrets
如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。
kubelet
将所有 imagePullSecrets
合并为一个虚拟的 .docker/config.json
文件。
接下来
3.3.2 - 容器环境
本页描述了在容器环境里容器可用的资源。
容器环境
Kubernetes 的容器环境给容器提供了几个重要的资源:
文件系统,其中包含一个镜像
和一个或多个的卷
容器自身的信息
集群中其他对象的信息
容器信息
容器的 hostname 是它所运行在的 pod 的名称。它可以通过 hostname
命令或者调用 libc 中的
gethostname
函数来获取。
Pod 名称和命名空间可以通过
下行 API
转换为环境变量。
Pod 定义中的用户所定义的环境变量也可在容器中使用,就像在 Docker 镜像中静态指定的任何环境变量一样。
集群信息
创建容器时正在运行的所有服务都可用作该容器的环境变量。
这里的服务仅限于新容器的 Pod 所在的名字空间中的服务,以及 Kubernetes 控制面的服务。
这些环境变量与 Docker 链接的语法相同。
对于名为 foo 的服务,当映射到名为 bar 的容器时,以下变量是被定义了的:
FOO_SERVICE_HOST = <the host the service is running on>
FOO_SERVICE_PORT = <the port the service is running on>
服务具有专用的 IP 地址。如果启用了
DNS 插件 ,
可以在容器中通过 DNS 来访问服务。
接下来
3.3.3 - 容器运行时类(Runtime Class)
FEATURE STATE: 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
)。
对象定义如下所示:
apiVersion : node.k8s.io/v1 # RuntimeClass 定义于 node.k8s.io API 组
kind : RuntimeClass
metadata :
name : myclass # 用来引用 RuntimeClass 的名字
# RuntimeClass 是一个集群层面的资源
handler : myconfiguration # 对应的 CRI 配置的名称
说明: 建议将 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 安装 。
dockershim
为 dockershim 设置 RuntimeClass 时,必须将运行时处理程序设置为 docker
。
Dockershim 不支持自定义的可配置的运行时处理程序。
通过 containerd 的 /etc/containerd/config.toml
配置文件来配置运行时 handler。
handler 需要配置在 runtimes 块中:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.${HANDLER_NAME}]
更详细信息,请查阅 containerd 配置文档:
https://github.com/containerd/cri/blob/master/docs/config.md
通过 cri-o 的 /etc/crio/crio.conf
配置文件来配置运行时 handler。
handler 需要配置在
crio.runtime 表
下面:
[crio.runtime.runtimes.${HANDLER_NAME}]
runtime_path = "${PATH_TO_BINARY}"
更详细信息,请查阅 CRI-O 配置文档 。
调度
FEATURE STATE: 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 开销
FEATURE STATE: Kubernetes v1.18 [beta]
你可以指定与运行 Pod 相关的 开销 资源。声明开销即允许集群(包括调度器)在决策 Pod 和资源时将其考虑在内。
若要使用 Pod 开销特性,你必须确保 PodOverhead
特性门控
处于启用状态(默认为启用状态)。
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
事件。
您可以通过运行 kubectl describe pod <pod_name>
命令来查看这些事件。
下面是运行这个命令的一些事件输出示例:
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
1m 1m 1 {default-scheduler } Normal Scheduled Successfully assigned test-1730497541-cq1d2 to gke-test-cluster-default-pool-a07e5d30-siqd
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulling pulling image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Created Created container with docker id 5c6a256a2567; Security:[seccomp=unconfined]
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Pulled Successfully pulled image "test:1.0"
1m 1m 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Started Started container with docker id 5c6a256a2567
38s 38s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 5c6a256a2567: PostStart handler: Error executing in Docker Container: 1
37s 37s 1 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Normal Killing Killing container with docker id 8df9fdfd7054: PostStart handler: Error executing in Docker Container: 1
38s 37s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} Warning FailedSync Error syncing pod, skipping: failed to "StartContainer" for "main" with RunContainerError: "PostStart handler: Error executing in Docker Container: 1"
1m 22s 2 {kubelet gke-test-cluster-default-pool-a07e5d30-siqd} spec.containers{main} Warning FailedPostStartHook
接下来
3.4 - 工作负载
理解 Pods,Kubernetes 中可部署的最小计算对象,以及辅助它运行它们的高层抽象对象。
工作负载是在 Kubernetes 上运行的应用程序。
无论你的负载是单一组件还是由多个一同工作的组件构成,在 Kubernetes 中你
可以在一组 Pods 中运行它。
在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组
容器 。
Kubernetes Pods 有确定的生命周期 。
例如,当某 Pod 在你的集群中运行时,Pod 运行所在的
节点 出现致命错误时,
所有该节点上的 Pods 都会失败。Kubernetes 将这类失败视为最终状态:
即使该节点后来恢复正常运行,你也需要创建新的 Pod 来恢复应用。
不过,为了让用户的日子略微好过一些,你并不需要直接管理每个 Pod。
相反,你可以使用 负载资源 来替你管理一组 Pods。
这些资源配置 控制器
来确保合适类型的、处于运行状态的 Pod 个数是正确的,与你所指定的状态相一致。
Kubernetes 提供若干种内置的工作负载资源:
DaemonSet
定义提供节点本地支撑设施的 Pods
。这些 Pods 可能对于你的集群的运维是
非常重要的,例如作为网络链接的辅助工具或者作为网络
插件
的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某 DaemonSet
的规约匹配,则控制面会为该 DaemonSet
调度一个 Pod
到该新节点上运行。
Job 和
CronJob 。
定义一些一直运行到结束并停止的任务。Job
用来表达的是一次性的任务,而
CronJob
会根据其时间规划反复运行。
在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方
工作负载资源。通过使用
定制资源定义(CRD) ,
你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。
例如,如果你希望运行一组 Pods
,但要求所有 Pods 都可用时才执行操作
(比如针对某种高吞吐量的分布式任务),你可以实现一个能够满足这一需求
的扩展,并将其安装到集群中运行。
接下来
除了阅读了解每类资源外,你还可以了解与这些资源相关的任务:
要了解 Kubernetes 将代码与配置分离的实现机制,可参阅
配置部分 。
关于 Kubernetes 如何为应用管理 Pods,还有两个支撑概念能够提供相关背景信息:
一旦你的应用处于运行状态,你就可能想要以
Service
的形式使之可在互联网上访问;或者对于 Web 应用而言,使用
Ingress
资源将其暴露到互联网上。
3.4.1 - Pods
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod (就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个)
容器 ;
这些容器共享存储、网络、以及怎样运行这些容器的声明。
Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。
Pod 所建模的是特定于应用的“逻辑主机”,其中包含一个或多个应用容器,
这些容器是相对紧密的耦合在一起的。
在非云环境中,在相同的物理机或虚拟机上运行的应用类似于
在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的
Init 容器 。
你也可以在集群中支持临时性容器
的情况下,为调试的目的注入临时性容器。
什么是 Pod?
说明: 除了 Docker 之外,Kubernetes 支持
很多其他
容器运行时 ,
Docker 是最有名的运行时,
使用 Docker 的术语来描述 Pod 会很有帮助。
Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离
方面,即用来隔离 Docker 容器的技术。
在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
就 Docker 概念的术语而言,Pod 类似于共享名字空间和文件系统卷的一组 Docker
容器。
使用 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 服务器支持,以及一个单独的
“sidecar(挂斗)”容器负责从远端更新这些文件,如下图所示:
有些 Pod 具有 Init 容器 和
应用容器 。
Init 容器会在启动应用容器之前运行并完成。
Pod 天生地为其成员容器提供了两种共享资源:网络 和
存储 。
使用 Pod
你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。
这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。
当 Pod 由你或者间接地由 控制器
创建时,它被调度在集群中的节点 上运行。
Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被
驱逐 或者节点失效为止。
说明: 重启 Pod 中的容器不应与重启 Pod 混淆。
Pod 不是进程,而是容器运行的环境。
在被删除之前,Pod 会一直存在。
当你为 Pod 对象创建清单时,要确保所指定的 Pod 名称是合法的
DNS 子域名 。
Pod 和控制器
你可以使用工作负载资源来创建和管理多个 Pod。
资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。
例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作,
就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。
下面是一些管理一个或者多个 Pod 的工作负载资源的示例:
Pod 模版
负载 资源的控制器通常使用
Pod 模板(Pod Template) 来替你创建 Pod 并管理它们。
Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括
Deployment 、
Job 和
DaemonSets 等。
工作负载的控制器会使用负载对象中的 PodTemplate
来生成实际的 Pod。
PodTemplate
是你用来运行应用时指定的负载资源的目标状态的一部分。
下面的示例是一个简单的 Job 的清单,其中的 template
指示启动一个容器。
该 Pod 中的容器会打印一条消息之后暂停。
apiVersion : batch/v1
kind : Job
metadata :
name : hello
spec :
template :
# 这里是 Pod 模版
spec :
containers :
- name : hello
image : busybox
command : ['sh' , '-c' , 'echo "Hello, Kubernetes!" && sleep 3600' ]
restartPolicy : OnFailure
# 以上为 Pod 模版
修改 Pod 模版或者切换到新的 Pod 模版都不会对已经存在的 Pod 起作用。
Pod 不会直接收到模版的更新。相反,
新的 Pod 会被创建出来,与更改后的 Pod 模版匹配。
例如,Deployment 控制器针对每个 Deployment 对象确保运行中的 Pod 与当前的 Pod
模版匹配。如果模版被更新,则 Deployment 必须删除现有的 Pod,基于更新后的模版
创建新的 Pod。每个工作负载资源都实现了自己的规则,用来处理对 Pod 模版的更新。
在节点上,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 地址互不相同,没有
特殊配置 就不能使用 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.4.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 所在主机通信失败。
如果某节点死掉或者与集群中其他节点失联,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 可能通过也可能未通过其中的一些状况测试。
PodScheduled
:Pod 已经被调度到某节点;
ContainersReady
:Pod 中所有容器都已就绪;
Initialized
:所有的 Init 容器
都已成功启动;
Ready
:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称
描述
type
Pod 状况的名称
status
表明该状况是否适用,可能的取值有 "True
", "False
" 或 "Unknown
"
lastProbeTime
上次探测 Pod 状况时的时间戳
lastTransitionTime
Pod 上次从一种状态转换到另一种状态时的时间戳
reason
机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因
message
人类可读的消息,给出上次状态转换的详细信息
Pod 就绪态
FEATURE STATE: 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
。
容器探针
Probe
是由 kubelet 对容器执行的定期诊断。
要执行诊断,kubelet 调用由容器实现的
Handler
(处理程序)。有三种类型的处理程序:
ExecAction :
在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
TCPSocketAction :
对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。
HTTPGetAction :
对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。如果响应的状态码大于等于 200
且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
Success
(成功):容器通过了诊断。
Failure
(失败):容器未通过诊断。
Unknown
(未知):诊断失败,因此不会采取任何行动。
针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
:指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器,
并且容器将根据其重启策略 决定未来。如果容器不提供存活探针,
则默认状态为 Success
。
readinessProbe
:指示容器是否准备好为请求提供服务。如果就绪态探测失败,
端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。
初始延迟之前的就绪态的状态值默认为 Failure
。
如果容器不提供就绪态探针,则默认状态为 Success
。
startupProbe
: 指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被
禁用,直到此探针成功为止。如果启动探测失败,kubelet
将杀死容器,而容器依其
重启策略 进行重启。
如果容器没有提供启动探测,则默认状态为 Success
。
如欲了解如何设置存活态、就绪态和启动探针的进一步细节,可以参阅
配置存活态、就绪态和启动探针 。
何时该使用存活态探针?
FEATURE STATE: Kubernetes v1.0 [stable]
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针;
kubelet
将根据 Pod 的restartPolicy
自动执行修复操作。
如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针,
并指定restartPolicy
为 "Always
" 或 "OnFailure
"。
何时该使用就绪态探针?
FEATURE STATE: Kubernetes v1.0 [stable]
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。
在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着
Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针,检查某个特定于
就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。
当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。
这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移,你可以使用
启动探针 。
然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
说明:
请注意,如果你只是想在 Pod 被删除时能够排空请求,则不一定需要使用就绪态探针;
在删除 Pod 时,Pod 会自动将自身置于未就绪状态,无论就绪态探针是否存在。
等待 Pod 中的容器停止期间,Pod 会一直处于未就绪状态。
何时该使用启动探针?
FEATURE STATE: Kubernetes v1.18 [beta]
对于所包含的容器需要较长时间才能启动就绪的 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 从对应的端点列表(以及端点切片列表,
如果启用了的话)中移除,过滤条件是 Pod 被对应的
服务 以某
选择算符 选定。
ReplicaSets 和其他工作负载资源
不再将关闭进程中的 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.4.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 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。
例如,没有必要仅为了在安装过程中使用类似 sed
、awk
、python
或 dig
这样的工具而去 FROM
一个镜像来生成一个新的镜像。
Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可以访问
应用容器不能访问的 Secret 的权限。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器
提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。
一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
示例
下面是一些如何使用 Init 容器的想法:
等待一个 Service 完成创建,通过类似如下 shell 命令:
for i in { 1..100} ; do sleep 1; if dig myservice; then exit 0; fi ; exit 1
注册这个 Pod 到远程服务器,通过在命令中调用 API,类似如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST :$MANAGEMENT_SERVICE_PORT /register \
-d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
在启动应用容器之前等一段时间,使用类似命令:
克隆 Git 仓库到卷 中。
将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。
例如,在配置文件中存放 POD_IP
值,并使用 Jinja 生成主应用配置文件。
使用 Init 容器的情况
下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice
启动,
第二个等待 mydb
启动。 一旦这两个 Init容器 都启动完成,Pod 将启动 spec
节中的应用容器。
apiVersion : v1
kind : Pod
metadata :
name : myapp-pod
labels :
app : 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=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 with docker id 5ced34a04634; Security:[seccomp=unconfined]
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634
如需查看 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 create -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.4.1.3 - Pod 拓扑分布约束
FEATURE STATE: Kubernetes v1.19 [stable]
你可以使用 拓扑分布约束(Topology Spread Constraints) 来控制
Pods 在集群内故障域
之间的分布,例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。
这样做有助于实现高可用并提升资源利用率。
说明: 在 v1.18 之前的 Kubernetes 版本中,如果要使用 Pod 拓扑扩展约束,你必须在
API 服务器
和
调度器
中启用
EvenPodsSpread
特性门控 。
先决条件
节点标签
拓扑分布约束依赖于节点标签来标识每个节点所在的拓扑域。
例如,某节点可能具有标签:node=node1,zone=us-east-1a,region=us-east-1
假设你拥有具有以下标签的一个 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;
[JavaScript must be enabled to view content]
你可以复用在大多数集群上自动创建和填充的
常用标签 ,
而不是手动添加标签。
Pod 的分布约束
API
pod.spec.topologySpreadConstraints
字段定义如下所示:
apiVersion : v1
kind : Pod
metadata :
name : mypod
spec :
topologySpreadConstraints :
- maxSkew : <integer>
topologyKey : <string>
whenUnsatisfiable : <string>
labelSelector : <object>
你可以定义一个或多个 topologySpreadConstraint
来指示 kube-scheduler
如何根据与现有的 Pod 的关联关系将每个传入的 Pod 部署到集群中。字段包括:
maxSkew 描述 Pod 分布不均的程度。这是给定拓扑类型中任意两个拓扑域中
匹配的 pod 之间的最大允许差值。它必须大于零。取决于 whenUnsatisfiable
的
取值,其语义会有不同。
当 whenUnsatisfiable
等于 "DoNotSchedule" 时,maxSkew
是目标拓扑域
中匹配的 Pod 数与全局最小值之间可存在的差异。
当 whenUnsatisfiable
等于 "ScheduleAnyway" 时,调度器会更为偏向能够降低
偏差值的拓扑域。
topologyKey 是节点标签的键。如果两个节点使用此键标记并且具有相同的标签值,
则调度器会将这两个节点视为处于同一拓扑域中。调度器试图在每个拓扑域中放置数量
均衡的 Pod。
whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:
DoNotSchedule
(默认)告诉调度器不要调度。
ScheduleAnyway
告诉调度器仍然继续调度,只是根据如何能将偏差最小化来对
节点进行排序。
labelSelector 用于查找匹配的 pod。匹配此标签的 Pod 将被统计,以确定相应
拓扑域中 Pod 的数量。
有关详细信息,请参考标签选择算符 。
你可以执行 kubectl explain Pod.spec.topologySpreadConstraints
命令以
了解关于 topologySpreadConstraints 的更多信息。
例子:单个 TopologySpreadConstraint
假设你拥有一个 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;
[JavaScript must be enabled to view content]
如果希望新来的 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 : k8s.gcr.io/pause:3.1
topologyKey: zone
意味着均匀分布将只应用于存在标签键值对为
"zone:<any value>" 的节点。
whenUnsatisfiable: DoNotSchedule
告诉调度器如果新的 Pod 不满足约束,
则让它保持悬决状态。
如果调度器将新的 Pod 放入 "zoneA",Pods 分布将变为 [3, 1],因此实际的偏差
为 2(3 - 1)。这违反了 maxSkew: 1
的约定。此示例中,新 Pod 只能放置在
"zoneB" 上:
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;
[JavaScript must be enabled to view content]
或者
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;
[JavaScript must be enabled to view content]
你可以调整 Pod 规约以满足各种要求:
将 maxSkew
更改为更大的值,比如 "2",这样新的 Pod 也可以放在 "zoneA" 上。
将 topologyKey
更改为 "node",以便将 Pod 均匀分布在节点上而不是区域中。
在上面的例子中,如果 maxSkew
保持为 "1",那么传入的 Pod 只能放在 "node4" 上。
将 whenUnsatisfiable: DoNotSchedule
更改为 whenUnsatisfiable: ScheduleAnyway
,
以确保新的 Pod 始终可以被调度(假设满足其他的调度 API)。
但是,最好将其放置在匹配 Pod 数量较少的拓扑域中。
(请注意,这一优先判定会与其他内部调度优先级(如资源使用率等)排序准则一起进行标准化。)
例子:多个 TopologySpreadConstraints
下面的例子建立在前面例子的基础上。假设你拥有一个 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;
[JavaScript must be enabled to view content]
可以使用 2 个 TopologySpreadConstraint 来控制 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 : k8s.gcr.io/pause:3.1
在这种情况下,为了匹配第一个约束,新的 Pod 只能放置在 "zoneB" 中;而在第二个约束中,
新的 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;
[JavaScript must be enabled to view content]
如果对集群应用 "two-constraints.yaml",会发现 "mypod" 处于 Pending
状态。
这是因为:为了满足第一个约束,"mypod" 只能放在 "zoneB" 中,而第二个约束要求
"mypod" 只能放在 "node2" 上。Pod 调度无法满足两种约束。
为了克服这种情况,你可以增加 maxSkew
或修改其中一个约束,让其使用
whenUnsatisfiable: ScheduleAnyway
。
节点亲和性与节点选择器的相互作用
如果 Pod 定义了 spec.nodeSelector
或 spec.affinity.nodeAffinity
,调度器将从倾斜计算中跳过不匹配的节点。
假设你有一个跨越 zoneA 到 zoneC 的 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;
[JavaScript must be enabled to view content]
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;
[JavaScript must be enabled to view content]
而且你知道 "zoneC" 必须被排除在外。在这种情况下,可以按如下方式编写 yaml,
以便将 "mypod" 放置在 "zoneB" 上,而不是 "zoneC" 上。同样,`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 : k8s.gcr.io/pause:3.1
调度器不会预先知道集群拥有的所有区域和其他拓扑域。拓扑域由集群中存在的节点确定。
在自动伸缩的集群中,如果一个节点池(或节点组)的节点数量为零,
而用户正期望其扩容时,可能会导致调度出现问题。
因为在这种情况下,调度器不会考虑这些拓扑域信息,因为它们是空的,没有节点。
其他值得注意的语义
这里有一些值得注意的隐式约定:
只有与新的 Pod 具有相同命名空间的 Pod 才能作为匹配候选者。
调度器会忽略没有 topologySpreadConstraints[*].topologyKey
的节点。这意味着:
位于这些节点上的 Pod 不影响 maxSkew
的计算。
在上面的例子中,假设 "node1" 没有标签 "zone",那么 2 个 Pod 将被忽略,
因此传入的 Pod 将被调度到 "zoneA" 中。
新的 Pod 没有机会被调度到这类节点上。
在上面的例子中,假设一个带有标签 {zone-typo: zoneC}
的 "node5" 加入到集群,
它将由于没有标签键 "zone" 而被忽略。
注意,如果新 Pod 的 topologySpreadConstraints[*].labelSelector
与自身的
标签不匹配,将会发生什么。
在上面的例子中,如果移除新 Pod 上的标签,Pod 仍然可以调度到 "zoneB",因为约束仍然满足。
然而,在调度之后,集群的不平衡程度保持不变。zoneA 仍然有 2 个带有 {foo:bar} 标签的 Pod,
zoneB 有 1 个带有 {foo:bar} 标签的 Pod。
因此,如果这不是你所期望的,建议工作负载的 topologySpreadConstraints[*].labelSelector
与其自身的标签匹配。
集群级别的默认约束
为集群设置默认的拓扑分布约束也是可能的。默认拓扑分布约束在且仅在以下条件满足
时才会应用到 Pod 上:
Pod 没有在其 .spec.topologySpreadConstraints
设置任何约束;
Pod 隶属于某个服务、副本控制器、ReplicaSet 或 StatefulSet。
你可以在 调度方案(Scheduling Profile)
中将默认约束作为 PodTopologySpread
插件参数的一部分来设置。
约束的设置采用如前所述的 API ,只是 labelSelector
必须为空。
选择算符是根据 Pod 所属的服务、副本控制器、ReplicaSet 或 StatefulSet 来设置的。
配置的示例可能看起来像下面这个样子:
apiVersion : kubescheduler.config.k8s.io/v1beta1
kind : KubeSchedulerConfiguration
profiles :
- pluginConfig :
- name : PodTopologySpread
args :
defaultConstraints :
- maxSkew : 1
topologyKey : topology.kubernetes.io/zone
whenUnsatisfiable : ScheduleAnyway
defaultingType : List
说明:
默认调度约束所生成的评分可能与
SelectorSpread
插件 .
所生成的评分有冲突。
建议你在为 PodTopologySpread
设置默认约束是禁用调度方案中的该插件。
内部默认约束
FEATURE STATE: Kubernetes v1.20 [beta]
当你使用了默认启用的 DefaultPodTopologySpread
特性门控时,原来的
SelectorSpread
插件会被禁用。
kube-scheduler 会使用下面的默认拓扑约束作为 PodTopologySpread
插件的
配置:
defaultConstraints :
- maxSkew : 3
topologyKey : "kubernetes.io/hostname"
whenUnsatisfiable : ScheduleAnyway
- maxSkew : 5
topologyKey : "topology.kubernetes.io/zone"
whenUnsatisfiable : ScheduleAnyway
此外,原来用于提供等同行为的 SelectorSpread
插件也会被禁用。
说明: 如果你的节点不会 同时 设置 kubernetes.io/hostname
和
topology.kubernetes.io/zone
标签,你应该定义自己的约束而不是使用
Kubernetes 的默认约束。
插件 PodTopologySpread
不会为未设置分布约束中所给拓扑键的节点评分。
如果你不想为集群使用默认的 Pod 分布约束,你可以通过设置 defaultingType
参数为 List
和
将 PodTopologySpread
插件配置中的 defaultConstraints
参数置空来禁用默认 Pod 分布约束。
apiVersion : kubescheduler.config.k8s.io/v1beta1
kind : KubeSchedulerConfiguration
profiles :
- pluginConfig :
- name : PodTopologySpread
args :
defaultConstraints : []
defaultingType : List
与 PodAffinity/PodAntiAffinity 相比较
在 Kubernetes 中,与“亲和性”相关的指令控制 Pod 的调度方式(更密集或更分散)。
对于 PodAffinity
,你可以尝试将任意数量的 Pod 集中到符合条件的拓扑域中。
对于 PodAntiAffinity
,只能将一个 Pod 调度到某个拓扑域中。
要实现更细粒度的控制,你可以设置拓扑分布约束来将 Pod 分布到不同的拓扑域下,
从而实现高可用性或节省成本。这也有助于工作负载的滚动更新和平稳地扩展副本规模。
有关详细信息,请参考
动机 文档。
已知局限性
Deployment 缩容操作可能导致 Pod 分布不平衡。
具有污点的节点上的 Pods 也会被统计。
参考 Issue 80921 。
接下来
3.4.1.4 - 干扰(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 提供特性来满足在出现频繁自愿干扰的同时运行高可用的应用程序。我们称这些特性为
干扰预算(Disruption Budget) 。
干扰预算
FEATURE STATE: 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 控制器的 .spec.replicas
计算“预期的” Pod 数量。
根据 Pod 对象的 .metadata.ownerReferences
字段来发现控制器。
PDB 不能阻止非自愿干扰 的发生,但是确实会计入
预算。
由于应用程序的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算,
但是控制器(如 Deployment 和 StatefulSet)在进行滚动升级时不受 PDB
的限制。应用程序更新期间的故障处理方式是在对应的工作负载资源的 spec
中配置的。
当使用驱逐 API 驱逐 Pod 时,Pod 会被体面地
终止 ,期间会
参考 PodSpec
中的 terminationGracePeriodSeconds
配置值。
PDB 例子
假设集群有 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 available
pod-c available
pod-e pending
pod-d available
pod-y
此时,集群管理员需要增加一个节点到集群中以继续升级操作。
可以看到 Kubernetes 如何改变干扰发生的速率,根据:
应用程序需要多少个副本
优雅关闭应用实例需要多长时间
启动应用新实例需要多长时间
控制器的类型
集群的资源能力
分离集群所有者和应用所有者角色
通常,将集群管理者和应用所有者视为彼此了解有限的独立角色是很有用的。这种责任分离在下面这些场景下是有意义的:
当有许多应用程序团队共用一个 Kubernetes 集群,并且有自然的专业角色
当第三方工具或服务用于集群自动化管理
Pod 干扰预算通过在角色之间提供接口来支持这种分离。
如果你的组织中没有这样的责任分离,则可能不需要使用 Pod 干扰预算。
如何在集群上执行干扰性操作
如果你是集群管理员,并且需要对集群中的所有节点执行干扰操作,例如节点或系统软件升级,则可以使用以下选项
接受升级期间的停机时间。
故障转移到另一个完整的副本集群。
没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。
编写可容忍干扰的应用程序和使用 PDB。
不停机。
最小的资源重复。
允许更多的集群管理自动化。
编写可容忍干扰的应用程序是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非
自愿干扰所做工作相比,有大量的重叠
接下来
3.4.1.5 - 临时容器
FEATURE STATE: Kubernetes v1.22 [alpha]
本页面概述了临时容器:一种特殊的容器,该容器在现有
Pod
中临时运行,以便完成用户发起的操作,例如故障排查。
你会使用临时容器来检查服务,而不是用它来构建应用程序。
警告:
临时容器处于 Alpha 阶段,不适用于生产环境集群。
根据 Kubernetes 弃用政策 ,
此 Alpha 功能将来可能发生重大变化或被完全删除。
了解临时容器
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.4.2 - 工作负载资源
3.4.2.1 - Deployments
一个 Deployment 为 Pods
和 ReplicaSets
提供声明式的更新能力。
你负责描述 Deployment 中的 目标状态 ,而 Deployment 控制器(Controller)
以受控速率更改实际状态,
使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment,
并通过新的 Deployment 收养其资源。
说明: 不要管理 Deployment 所拥有的 ReplicaSet 。
如果存在下面未覆盖的使用场景,请考虑在 Kubernetes 仓库中提出 Issue。
用例
以下是 Deployments 的典型用例:
创建 Deployment
下面是 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx
Pods:
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 创建三个(由 replicas
字段标明)Pod 副本。
template
字段包含以下子字段:
Pod 被使用 labels
字段打上 app: nginx
标签。
Pod 模板规约(即 .template.spec
字段)指示 Pods 运行一个 nginx
容器,
该容器运行版本为 1.14.2 的 nginx
Docker Hub 镜像。
创建一个容器并使用 name
字段将其命名为 nginx
。
开始之前,请确保的 Kubernetes 集群已启动并运行。
按照以下步骤创建上述 Deployment :
通过运行以下命令创建 Deployment :
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
说明: 你可以设置 --record
标志将所执行的命令写入资源注解 kubernetes.io/change-cause
中。
这对于以后的检查是有用的。例如,要查看针对每个 Deployment 修订版本所执行过的命令。
运行 kubectl get deployments
检查 Deployment 是否已创建。如果仍在创建 Deployment,
则输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 3 0 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 DESIRED CURRENT 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名称]-[随机字符串]
。
其中的随机字符串是使用 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 --record deployment.apps/nginx-deployment set image \
deployment.v1.apps/nginx-deployment nginx = nginx:1.16.1
或者使用下面的命令:
kubectl set image deployment/nginx-deployment nginx = nginx:1.16.1 --record
输出类似于:
deployment.apps/nginx-deployment image updated
或者,可以 edit
Deployment 并将 .spec.template.spec.containers[0].image
从
nginx:1.14.2
更改至 nginx:1.16.1
。
kubectl edit deployment.v1.apps/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
应仅显示新的 Pods:
输出类似于:
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
下次要更新这些 Pods 时,只需再次更新 Deployment Pod 模板即可。
Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pods
75% 处于运行状态(最大不可用比例为 25%)。
Deployment 还确保仅所创建 Pod 数量只可能比期望 Pods 数高一点点。
默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 25%(最大峰值 25%)。
例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除了一些旧的 Pods,
并创建了新的 Pods。它不会杀死老 Pods,直到有足够的数量新的 Pods 已经出现。
在足够数量的旧 Pods 被杀死前并没有创建新 Pods。它确保至少 2 个 Pod 可用,同时
最多总共 4 个 Pod 可用。
获取 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,
以便至少有 2 个 Pod 可用且最多创建 4 个 Pod。
然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。
最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。
翻转(多 Deployment 动态更新)
Deployment 控制器每次注意到新的 Deployment 时,都会创建一个 ReplicaSet 以启动所需的 Pods。
如果更新了 Deployment,则控制标签匹配 .spec.selector
但模板不匹配 .spec.template
的
Pods 的现有 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
Pods,
并开始创建 nginx:1.16.1
Pods。它不会等待 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 上线历史
按照如下步骤检查回滚历史:
首先,检查 Deployment 修订历史:
kubectl rollout history deployment.v1.apps/nginx-deployment
输出类似于:
deployments "nginx-deployment"
REVISION CHANGE-CAUSE
1 kubectl apply --filename= https://k8s.io/examples/controllers/nginx-deployment.yaml --record= true
2 kubectl set image deployment.v1.apps/nginx-deployment nginx = nginx:1.9.1 --record= true
3 kubectl set image deployment.v1.apps/nginx-deployment nginx = nginx:1.91 --record= true
CHANGE-CAUSE
的内容是从 Deployment 的 kubernetes.io/change-cause
注解复制过来的。
复制动作发生在修订版本创建时。你可以通过以下方式设置 CHANGE-CAUSE
消息:
使用 kubectl annotate deployment.v1.apps/nginx-deployment kubernetes.io/change-cause="image updated to 1.9.1"
为 Deployment 添加注解。
追加 --record
命令行标志以保存正在更改资源的 kubectl
命令。
手动编辑资源的清单。
要查看修订历史的详细信息,运行:
kubectl rollout history deployment.v1.apps/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.v1.apps/nginx-deployment nginx = nginx:1.16.1 --record= true
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.v1.apps/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment
或者,你也可以通过使用 --to-revision
来回滚到特定修订版本:
kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision= 2
输出类似于:
deployment.apps/nginx-deployment
与回滚相关的指令的更详细信息,请参考 kubectl rollout
。
现在,Deployment 正在回滚到以前的稳定版本。正如你所看到的,Deployment 控制器生成了
回滚到修订版本 2 的 DeploymentRollback
事件。
检查回滚是否成功以及 Deployment 是否正在运行,运行:
kubectl get deployment nginx-deployment
输出类似于:
NAME DESIRED CURRENT 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.v1.apps/nginx-deployment nginx=nginx:1.16.1 --record=true
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.v1.apps/nginx-deployment --replicas= 10
输出类似于:
deployment.apps/nginx-deployment scaled
假设集群启用了Pod 的水平自动缩放 ,
你可以为 Deployment 设置自动缩放器,并基于现有 Pods 的 CPU 利用率选择
要运行的 Pods 个数下限和上限。
kubectl autoscale deployment.v1.apps/nginx-deployment --min= 10 --max= 15 --cpu-percent= 80
输出类似于:
deployment.apps/nginx-deployment scaled
比例缩放
RollingUpdate 的 Deployment 支持同时运行应用程序的多个版本。
当自动缩放器缩放处于上线进程(仍在进行中或暂停)中的 RollingUpdate Deployment 时,
Deployment 控制器会平衡现有的活跃状态的 ReplicaSets(含 Pods 的 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,然后再恢复其执行。
这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。
使用如下指令暂停运行:
kubectl rollout pause deployment.v1.apps/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment paused
注意没有新的上线被触发:
kubectl rollout history deployment.v1.apps/nginx-deployment
输出类似于:
deployments "nginx"
REVISION CHANGE-CAUSE
1 <none>
你可以根据需要执行很多更新操作,例如,可以要使用的资源:
kubectl set resources deployment.v1.apps/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) 缩容
新的 Pods 已经就绪或者可用(就绪至少持续了 MinReadySeconds 秒)。
你可以使用 kubectl rollout status
监视 Deployment 的进度。
完成的 Deployment
当 Deployment 具有以下特征时,Kubernetes 将其标记为 完成(Complete) :
与 Deployment 关联的所有副本都已更新到指定的最新版本,这意味着之前请求的所有更新都已完成。
与 Deployment 关联的所有副本都可用。
未运行 Deployment 的旧副本。
你可以使用 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
$ echo $?
0
失败的 Deployment
你的 Deployment 可能会在尝试部署其最新的 ReplicaSet 受挫,一直处于未完成状态。
造成此情况一些可能因素如下:
配额(Quota)不足
就绪探测(Readiness Probe)失败
镜像拉取错误
权限不足
限制范围(Limit Ranges)问题
应用程序运行时的配置错误
检测此状况的一种方法是在 Deployment 规约中指定截止时间参数:
([.spec.progressDeadlineSeconds
](#progress-deadline-seconds))。
.spec.progressDeadlineSeconds
给出的是一个秒数值,Deployment 控制器在(通过 Deployment 状态)
标示 Deployment 进展停滞之前,需要等待所给的时长。
以下 kubectl
命令设置规约中的 progressDeadlineSeconds
,从而告知控制器
在 10 分钟后报告 Deployment 没有进展:
kubectl patch deployment.v1.apps/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
输出类似于:
deployment.apps/nginx-deployment patched
超过截止时间后,Deployment 控制器将添加具有以下属性的 DeploymentCondition 到
Deployment 的 .status.conditions
中:
Type=Progressing
Status=False
Reason=ProgressDeadlineExceeded
参考
Kubernetes API 约定
获取更多状态状况相关的信息。
说明: 除了报告 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
and 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。
选择算符
.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 会终结之。
如果 Pods 总数未达到期望值,Deployment 会基于 .spec.template
创建新的 Pod。
说明: 你不应直接创建、或者通过创建另一个 Deployment,或者创建类似 ReplicaSet
或 ReplicationController 这类控制器来创建标签与此选择算符匹配的 Pod。
如果这样做,第一个 Deployment 会认为它创建了这些 Pod。
Kubernetes 不会阻止你这么做。
如果有多个控制器的选择算符发生重叠,则控制器之间会因冲突而无法正常工作。
策略
.spec.strategy
策略指定用于用新 Pods 替换旧 Pods 的策略。
.spec.strategy.type
可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。
重新创建 Deployment
如果 .spec.strategy.type==Recreate
,在创建新 Pods 之前,所有现有的 Pods 会被杀死。
滚动更新 Deployment
Deployment 会在 .spec.strategy.type==RollingUpdate
时,采取
滚动更新的方式更新 Pods。你可以指定 maxUnavailable
和 maxSurge
来控制滚动更新
过程。
最大不可用
.spec.strategy.rollingUpdate.maxUnavailable
是一个可选字段,用来指定
更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是
所需 Pods 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。
如果 .spec.strategy.rollingUpdate.maxSurge
为 0,则此值不能为 0。
默认值为 25%。
例如,当此值设置为 30% 时,滚动更新开始时会立即将旧 ReplicaSet 缩容到期望 Pod 个数的70%。
新 Pod 准备就绪后,可以继续缩容旧有的 ReplicaSet,然后对新的 ReplicaSet 扩容,确保在更新期间
可用的 Pods 总数在任何时候都至少为所需的 Pod 个数的 70%。
最大峰值
.spec.strategy.rollingUpdate.maxSurge
是一个可选字段,用来指定可以创建的超出
期望 Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pods 的百分比(例如,10%)。
如果 MaxUnavailable
为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。
此字段的默认值为 25%。
例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod
的总数不超过所需 Pod 总数的 130%。一旦旧 Pods 被杀死,新的 ReplicaSet 可以进一步扩容,
同时确保更新期间的任何时候运行中的 Pods 总数最多为所需 Pods 总数的 130%。
进度期限秒数
.spec.progressDeadlineSeconds
是一个可选字段,用于指定系统在报告 Deployment
进展失败 之前等待 Deployment 取得进展的秒数。
这类报告会在资源状态中体现为 Type=Progressing
、Status=False
、
Reason=ProgressDeadlineExceeded
。Deployment 控制器将持续重试 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.4.2.2 - ReplicaSet
ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。
因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。
ReplicaSet 的工作原理
RepicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 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 :
# modify replicas according to your case
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
最后可以查看启动了的 Pods:
你会看到类似如下的 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
你也可以查看 Pods 的属主引用被设置为前端的 ReplicaSet。
要实现这点,可取回运行中的 Pods 之一的 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 的获得
尽管你完全可以直接创建裸的 Pods,强烈建议你确保这些裸的 Pods 并不包含可能与你
的某个 ReplicaSet 的选择算符相匹配的标签。原因在于 ReplicaSet 并不仅限于拥有
在其模板中设置的 Pods,它还可以像前面小节中所描述的那样获得其他 Pods。
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 已经被部署之后创建 Pods,并且你已经在 ReplicaSet
中设置了其初始的 Pod 副本数以满足其副本计数需要:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
新的 Pods 会被该 ReplicaSet 获取,并立即被 ReplicaSet 终止,因为
它们的存在会使得 ReplicaSet 中 Pod 个数超出其期望值。
取回 Pods:
输出显示新的 Pods 或者已经被终止,或者处于终止过程中:
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
如果你先行创建 Pods:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
之后再创建 ReplicaSet:
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你会看到 ReplicaSet 已经获得了该 Pods,并仅根据其规约创建新的 Pods,直到
新的 Pods 和原来的 Pods 的总数达到其预期个数。
这时取回 Pods:
将会生成下面的输出:
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 中可以包含异质的 Pods 集合。
编写 ReplicaSet 的 spec
与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersion
、kind
、和 metadata
字段。
对于 ReplicaSets 而言,其 kind
始终是 ReplicaSet。
在 Kubernetes 1.9 中,ReplicaSet 上的 API 版本 apps/v1
是其当前版本,且被
默认启用。API 版本 apps/v1beta2
已被废弃。
参考 frontend.yaml
示例的第一行。
ReplicaSet 对象的名称必须是合法的
DNS 子域名 。
ReplicaSet 也需要 .spec
部分。
Pod 模版
.spec.template
是一个Pod 模版 ,
要求设置标签。在 frontend.yaml
示例中,我们指定了标签 tier: frontend
。
注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。
对于模板的重启策略
字段,.spec.template.spec.restartPolicy
,唯一允许的取值是 Always
,这也是默认值.
Pod 选择算符
.spec.selector
字段是一个标签选择算符 。
如前文中所讨论的 ,这些是用来标识要被获取的 Pods
的标签。在签名的 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 所
创建的 Pods。
Replicas
你可以通过设置 .spec.replicas
来指定要同时运行的 Pod 个数。
ReplicaSet 创建、删除 Pods 以与此值匹配。
如果你没有指定 .spec.replicas
, 那么默认值为 1。
使用 ReplicaSets
删除 ReplicaSet 和它的 Pod
要删除 ReplicaSet 和它的所有 Pod,使用
kubectl delete
命令。
默认情况下,垃圾收集器
自动删除所有依赖的 Pod。
当使用 REST API 或 client-go
库时,你必须在删除选项中将 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 而不影响它的 Pods,方法是使用
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 将被自动替换(假设副本的数量没有改变)。
缩放 RepliaSet
通过更新 .spec.replicas
字段,ReplicaSet 可以被轻松的进行缩放。ReplicaSet
控制器能确保匹配标签选择器的数量的 Pod 是可用的和可操作的。
在降低集合规模时,ReplicaSet 控制器通过对可用的 Pods 进行排序来优先选择
要被删除的 Pods。其一般性算法如下:
首先选择剔除悬决(Pending,且不可调度)的 Pods
如果设置了 controller.kubernetes.io/pod-deletion-cost
注解,则注解值
较小的优先被裁减掉
所处节点上副本个数较多的 Pod 优先于所处节点上副本较少者
如果 Pod 的创建时间不同,最近创建的 Pod 优先于早前创建的 Pod 被裁减。
(当 LogarithmicScaleDown
这一
特性门控
被启用时,创建时间是按整数幂级来分组的)。
如果以上比较结果都相同,则随机选择。
Pod 删除开销
FEATURE STATE: Kubernetes v1.22 [beta]
通过使用 controller.kubernetes.io/pod-deletion-cost
注解,用户可以对 ReplicaSet 缩容时要先删除哪些 Pods 设置偏好。
此注解要设置到 Pod 上,取值范围为 [-2147483647, 2147483647]。
所代表的的是删除同一 ReplicaSet 中其他 Pod 相比较而言的开销。
删除开销较小的 Pods 比删除开销较高的 Pods 更容易被删除。
Pods 如果未设置此注解,则隐含的设置值为 0。负值也是可接受的。
如果注解值非法,API 服务器会拒绝对应的 Pod。
此功能特性处于 Beta 阶段,默认被禁用。你可以通过为 kube-apiserver 和
kube-controller-manager 设置
特性门控
PodDeletionCost
来启用此功能。
说明:
此机制实施时仅是尽力而为,并不能对 Pod 的删除顺序作出任何保证;
用户应避免频繁更新注解值,例如根据某观测度量值来更新此注解值是应该避免的。
这样做会在 API 服务器上产生大量的 Pod 更新操作。
使用场景示例
同一应用的不同 Pods 可能其利用率是不同的。在对应用执行缩容操作时,可能
希望移除利用率较低的 Pods。为了避免频繁更新 Pods,应用应该在执行缩容
操作之前更新一次 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 并使用声明式方式在服务器端完成对 Pods 滚动更新的对象。
尽管 ReplicaSet 可以独立使用,目前它们的主要用途是提供给 Deployment 作为
编排 Pod 创建、删除和更新的一种机制。当使用 Deployment 时,你不必关心
如何管理它所创建的 ReplicaSet,Deployment 拥有并管理其 ReplicaSet。
因此,建议你在需要 ReplicaSet 时使用 Deployment。
裸 Pod
与用户直接创建 Pod 的情况不同,ReplicaSet 会替换那些由于某些原因被删除或被终止的
Pod,例如在节点故障或破坏性的节点维护(如内核升级)的情况下。
因为这个原因,我们建议你使用 ReplicaSet,即使应用程序只需要一个 Pod。
想像一下,ReplicaSet 类似于进程监视器,只不过它在多个节点上监视多个 Pod,
而不是在单个节点上监视单个进程。
ReplicaSet 将本地容器重启的任务委托给了节点上的某个代理(例如,Kubelet 或 Docker)去完成。
Job
使用Job
代替ReplicaSet,
可以用于那些期望自行终止的 Pod。
DaemonSet
对于管理那些提供主机级别功能(如主机监控和主机日志)的容器,
就要用 DaemonSet
而不用 ReplicaSet。
这些 Pod 的寿命与主机寿命有关:这些 Pod 需要先于主机上的其他 Pod 运行,
并且在机器准备重新启动/关闭时安全地终止。
ReplicationController
ReplicaSet 是 ReplicationController
的后继者。二者目的相同且行为类似,只是 ReplicationController 不支持
标签用户指南
中讨论的基于集合的选择算符需求。
因此,相比于 ReplicationController,应优先考虑 ReplicaSet。
接下来
3.4.2.3 - StatefulSets
StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理某 Pod 集合的部署和扩缩,
并为这些 Pod 提供持久存储和持久标识符。
和 Deployment 类似,
StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是,
StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的,
但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。
尽管 StatefulSet 中的单个 Pod 仍可能出现故障,
但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易。
使用 StatefulSets
StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:
稳定的、唯一的网络标识符。
稳定的、持久的存储。
有序的、优雅的部署和缩放。
有序的、自动的滚动更新。
在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。
如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用
由一组无状态的副本控制器提供的工作负载来部署应用程序,比如
Deployment 或者
ReplicaSet
可能更适用于你的无状态应用部署需要。
限制
给定 Pod 的存储必须由
PersistentVolume 驱动
基于所请求的 storage class
来提供,或者由管理员预先提供。
删除或者收缩 StatefulSet 并不会 删除它关联的存储卷。
这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
StatefulSet 当前需要无头服务
来负责 Pod 的网络标识。你需要负责创建此服务。
当删除 StatefulSets 时,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 # has to match .spec.template.metadata.labels
serviceName : "nginx"
replicas : 3 # by default is 1
template :
metadata :
labels :
app : nginx # has to match .spec.selector.matchLabels
spec :
terminationGracePeriodSeconds : 10
containers :
- name : nginx
image : k8s.gcr.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
将通过 PersistentVolumes 驱动提供的
PersistentVolumes 来提供稳定的存储。
StatefulSet 的命名需要遵循DNS 子域名 规范。
Pod 选择算符
你必须设置 StatefulSet 的 .spec.selector
字段,使之匹配其在
.spec.template.metadata.labels
中设置的标签。在 Kubernetes 1.8 版本之前,
被忽略 .spec.selector
字段会获得默认设置值。
在 1.8 和以后的版本中,未指定匹配的 Pod 选择器将在创建 StatefulSet 期间导致验证错误。
Pod 标识
StatefulSet 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 控制器(Controller) 创建 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 管理策略
在 Kubernetes 1.7 及以后的版本中,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 管理策略 (OrderedReady
) 下使用
滚动更新 ,可能进入需要人工干预才能修复的损坏状态。
如果更新后 Pod 模板配置进入无法运行或就绪的状态(例如,由于错误的二进制文件
或应用程序级配置错误),StatefulSet 将停止回滚并等待。
在这种状态下,仅将 Pod 模板还原为正确的配置是不够的。由于
已知问题 ,StatefulSet
将继续等待损坏状态的 Pod 准备就绪(永远不会发生),然后再尝试将其恢复为正常工作配置。
恢复模板后,还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样,
StatefulSet 才会开始使用被还原的模板来重新创建 Pod。
最短就绪秒数
FEATURE STATE: Kubernetes v1.22 [alpha]
.spec.minReadySeconds
是一个可选字段,用于指定新创建的 Pod 就绪(没有任何容器崩溃)后被认为可用的最小秒数。
默认值是 0(Pod 就绪时就被认为可用)。要了解 Pod 何时被认为已就绪,请参阅容器探针 。
请注意只有当你启用 StatefulSetMinReadySeconds
特性门控 时,该字段才会生效。
接下来
3.4.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 :
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- 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
基于 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
的作用是相同的。
从 Kubernetes 1.8 开始,您必须指定与 .spec.template
的标签匹配的 Pod 选择算符。
用户不指定 Pod 选择算符时,该字段不再有默认值。
选择算符的默认值生成结果与 kubectl apply
不兼容。
此外,一旦创建了 DaemonSet,它的 .spec.selector
就不能修改。
修改 Pod 选择算符可能导致 Pod 意外悬浮,并且这对用户来说是费解的。
spec.selector
是一个对象,如下两个字段组成:
matchLabels
- 与 ReplicationController
的 .spec.selector
的作用相同。
matchExpressions
- 允许构建更加复杂的选择器,可以通过指定 key、value
列表以及将 key 和 value 列表关联起来的 operator。
当上述两个字段都指定时,结果会按逻辑与(AND)操作处理。
如果指定了 .spec.selector
,必须与 .spec.template.metadata.labels
相匹配。
如果与后者不匹配,则 DeamonSet 会被 API 拒绝。
仅在某些节点上运行 Pod
如果指定了 .spec.template.spec.nodeSelector
,DaemonSet 控制器将在能够与
Node 选择算符 匹配的节点上创建 Pod。
类似这种情况,可以指定 .spec.template.spec.affinity
,之后 DaemonSet 控制器
将在能够与节点亲和性
匹配的节点上创建 Pod。
如果根本就没有指定,则 DaemonSet Controller 将在所有节点上创建 Pod。
Daemon Pods 是如何被调度的
通过默认调度器调度
FEATURE STATE: Kubernetes v1.23 [stable]
DaemonSet 确保所有符合条件的节点都运行该 Pod 的一个副本。
通常,运行 Pod 的节点由 Kubernetes 调度器选择。
不过,DaemonSet Pods 由 DaemonSet 控制器创建和调度。这就带来了以下问题:
Pod 行为的不一致性:正常 Pod 在被创建后等待调度时处于 Pending
状态,
DaemonSet Pods 创建后不会处于 Pending
状态下。这使用户感到困惑。
Pod 抢占
由默认调度器处理。启用抢占后,DaemonSet 控制器将在不考虑 Pod 优先级和抢占
的情况下制定调度决策。
ScheduleDaemonSetPods
允许您使用默认调度器而不是 DaemonSet 控制器来调度 DaemonSets,
方法是将 NodeAffinity
条件而不是 .spec.nodeName
条件添加到 DaemonSet Pods。
默认调度器接下来将 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 Pods。在调度 DaemonSet Pod 时,默认调度器会忽略 unschedulable
节点。
污点和容忍度
尽管 Daemon Pods 遵循污点和容忍度
规则,根据相关特性,控制器会自动将以下容忍度添加到 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 中运行之来实现。
例如,直接基于 Docker 启动。
裸 Pod
直接创建 Pod并指定其运行在特定的节点上也是可以的。
然而,DaemonSet 能够替换由于任何原因(例如节点失败、例行节点维护、内核升级)
而被删除或终止的 Pod。
由于这个原因,你应该使用 DaemonSet 而不是单独创建 Pod。
静态 Pod
通过在一个指定的、受 kubelet
监视的目录下编写文件来创建 Pod 也是可行的。
这类 Pod 被称为静态 Pod 。
不像 DaemonSet,静态 Pod 不受 kubectl
和其它 Kubernetes API 客户端管理。
静态 Pod 不依赖于 API 服务器,这使得它们在启动引导新集群的情况下非常有用。
此外,静态 Pod 在将来可能会被废弃。
Deployments
DaemonSet 与 Deployments 非常类似,
它们都能创建 Pod,并且 Pod 中的进程都不希望被终止(例如,Web 服务器、存储服务器)。
建议为无状态的服务使用 Deployments,比如前端服务。
对这些服务而言,对副本的数量进行扩缩容、平滑升级,比精确控制 Pod 运行在某个主机上要重要得多。
当需要 Pod 副本总是运行在全部或特定主机上,并且当该 DaemonSet 提供了节点级别的功能(允许其他 Pod 在该特定节点上正确运行)时,
应该使用 DaemonSet。
例如,网络插件 通常包含一个以 DaemonSet 运行的组件。
这个 DaemonSet 组件确保它所在的节点的集群网络正常工作。
接下来
3.4.2.5 - Jobs
Job 会创建一个或者多个 Pods,并将继续重试 Pods 的执行,直到指定数量的 Pods 成功终止。
随着 Pods 成功结束,Job 跟踪记录成功完成的 Pods 个数。
当数量达到指定的成功个数阈值时,任务(即 Job)结束。
删除 Job 的操作会清除所创建的全部 Pods。
挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行。
一种简单的使用场景下,你会创建一个 Job 对象以便以一种可靠的方式运行某 Pod 直到完成。
当第一个 Pod 失败或者被删除(比如因为节点硬件失效或者重启)时,Job
对象会启动一个新的 Pod。
你也可以使用 Job 以并行的方式运行多个 Pod。
运行示例 Job
下面是一个 Job 配置示例。它负责计算 π 到小数点后 2000 位,并将结果打印出来。
此计算大约需要 10 秒钟完成。
apiVersion : batch/v1
kind : Job
metadata :
name : pi
spec :
template :
spec :
containers :
- name : pi
image : perl
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
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 对应的已完成的 Pods,可以执行 kubectl get pods
。
要以机器可读的方式列举隶属于某 Job 的全部 Pods,你可以使用类似下面这条命令:
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 相当于启动之后便被暂停,直到此值被增加。
实际并行性(在任意时刻运行状态的 Pods 个数)可能比并行性请求略大或略小,
原因如下:
对于 确定完成计数 Job,实际上并行执行的 Pods 个数不会超出剩余的完成数。
如果 .spec.parallelism
值较高,会被忽略。
对于 工作队列 Job,有任何 Job 成功结束之后,不会有新的 Pod 启动。
不过,剩下的 Pods 允许执行完毕。
如果 Job 控制器 没有来得及作出响应,或者
如果 Job 控制器因为任何原因(例如,缺少 ResourceQuota
或者没有权限)无法创建 Pods。
Pods 个数可能比请求的数目小。
Job 控制器可能会因为之前同一 Job 中 Pod 失效次数过多而压制新 Pod 的创建。
当 Pod 处于体面终止进程中,需要一定时间才能停止。
完成模式
FEATURE STATE: Kubernetes v1.22 [beta]
带有 确定完成计数 的 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 中的 Pods 可以通过 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 分钟。
当 Job 的 Pod 被删除时,或者 Pod 成功时没有其它 Pod 处于失败状态,失效回退的次数也会被重置(为 0)。
说明: 如果你的 Job 的 restartPolicy
被设置为 "OnFailure",就要注意运行该 Job 的 Pod
会在 Job 到达失效回退次数上限时自动被终止。
这会使得调试 Job 中可执行文件的工作变得非常棘手。
我们建议在调试 Job 时将 restartPolicy
设置为 "Never",
或者使用日志系统来确保失效 Jobs 的输出不会意外遗失。
Job 终止与清理
Job 完成时不会再创建新的 Pod,不过已有的 Pod 通常 也不会被删除。
保留这些 Pod 使得你可以查看已完成的 Pod 的日志输出,以便检查错误、警告
或者其它诊断性输出。
Job 完成时 Job 对象也一样被保留下来,这样你就可以查看它的状态。
在查看了 Job 状态之后删除老的 Job 的操作留给了用户自己。
你可以使用 kubectl
来删除 Job(例如,kubectl delete jobs/pi
或者 kubectl delete -f ./job.yaml
)。
当使用 kubectl
来删除 Job 时,该 Job 所创建的 Pods 也会被删除。
默认情况下,Job 会持续运行,除非某个 Pod 失败(restartPolicy=Never
)
或者某个容器出错退出(restartPolicy=OnFailure
)。
这时,Job 基于前述的 spec.backoffLimit
来决定是否以及如何重试。
一旦重试次数到达 .spec.backoffLimit
所设的上限,Job 会被标记为失败,
其中运行的 Pods 都会被终止。
终止 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
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 由某种更高级别的控制器来管理,例如
CronJobs ,
则 Job 可以被 CronJob 基于特定的根据容量裁定的清理策略清理掉。
已完成 Job 的 TTL 机制
FEATURE STATE: Kubernetes v1.21 [beta]
自动清理已完成 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
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
FEATURE STATE: Kubernetes v1.21 [alpha]
说明:
该特性在 Kubernetes 1.21 版本中是 Alpha 阶段,启用该特性需要额外的步骤;
请确保你正在阅读与集群版本一致的文档 。
Job 被创建时,Job 控制器会马上开始执行 Pod 创建操作以满足 Job 的需求,
并持续执行此操作直到 Job 完成为止。
不过你可能想要暂时挂起 Job 执行,之后再恢复其执行。
要挂起一个 Job,你可以将 Job 的 .spec.suspend
字段更新为 true。
之后,当你希望恢复其执行时,将其更新为 false。
创建一个 .spec.suspend
被设置为 true 的 Job 本质上会将其创建为被挂起状态。
当 Job 被从挂起状态恢复执行时,其 .status.startTime
字段会被重置为
当前的时间。这意味着 .spec.activeDeadlineSeconds
计时器会在 Job 挂起时
被停止,并在 Job 恢复执行时复位。
要记住的是,挂起 Job 会删除其所有活跃的 Pod。当 Job 被挂起时,你的 Pod 会
收到 SIGTERM 信号而被终止 。
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 的 status
可以用来确定 Job 是否被挂起,或者曾经被挂起。
kubectl get jobs/myjob -o yaml
apiVersion: batch/v 1
kind: Job
# .metadata and .spec omitted
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 创建操作立即被重启执行。
指定你自己的 Pod 选择算符
通常,当你创建一个 Job 对象时,你不会设置 .spec.selector
。
系统的默认值填充逻辑会在创建 Job 时添加此字段。
它会选择一个不会与任何其他 Job 重叠的选择算符设置。
不过,有些场合下,你可能需要重载这个自动设置的选择算符。
为了实现这点,你可以手动设置 Job 的 spec.selector
字段。
做这个操作时请务必小心。
如果你所设定的标签选择算符并不唯一针对 Job 对应的 Pod 集合,甚或该算符还能匹配
其他无关的 Pod,这些无关的 Job 的 Pod 可能会被删除。
或者当前 Job 会将另外一些 Pod 当作是完成自身工作的 Pods,
又或者两个 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
是在告诉系统你知道自己在干什么并要求系统允许这种不匹配
的存在。
使用 Finalizer 追踪 Job
FEATURE STATE: Kubernetes v1.22 [alpha]
说明: 要使用该行为,你必须为 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、如何向其分派工作的完全控制能力,
接下来
了解 Pods 。
了解运行 Job 的不同的方式:
跟随自动清理完成的 Job 文中的链接,了解你的集群如何清理完成和失败的任务。
Job
是 Kubernetes REST API 的一部分。阅读
对象定义理解关于该资源的 API。
阅读 CronJob
,它允许你定义一系列定期运行的 Job,类似于 Unix 工具 cron
。
3.4.2.6 - 已完成资源的 TTL 控制器
FEATURE STATE: Kubernetes v1.21 [beta]
TTL 控制器提供了一种 TTL 机制来限制已完成执行的资源对象的生命周期。
TTL 控制器目前只处理 Job ,
可能以后会扩展以处理将完成执行的其他资源,例如 Pod 和自定义资源。
此功能目前是 Beta 版而自动启用,并且可以通过 kube-apiserver
和
kube-controller-manager
上的
特性门控
TTLAfterFinished
禁用。
TTL 控制器
TTL 控制器现在只支持 Job。集群操作员可以通过指定 Job 的 .spec.ttlSecondsAfterFinished
字段来自动清理已结束的作业(Complete
或 Failed
),如
示例
所示。
TTL 控制器假设资源能在执行完成后的 TTL 秒内被清理,也就是当 TTL 过期后。
当 TTL 控制器清理资源时,它将做级联删除操作,即删除资源对象的同时也删除其依赖对象。
注意,当资源被删除时,由该资源的生命周期保证其终结器(Finalizers)等被执行。
可以随时设置 TTL 秒。以下是设置 Job 的 .spec.ttlSecondsAfterFinished
字段的一些示例:
警告
更新 TTL 秒
请注意,在创建资源或已经执行结束后,仍可以修改其 TTL 周期,例如 Job 的
.spec.ttlSecondsAfterFinished
字段。
但是一旦 Job 变为可被删除状态(当其 TTL 已过期时),即使您通过 API 增加其 TTL
时长得到了成功的响应,系统也不保证 Job 将被保留。
时间偏差
由于 TTL 控制器使用存储在 Kubernetes 资源中的时间戳来确定 TTL 是否已过期,
因此该功能对集群中的时间偏差很敏感,这可能导致 TTL 控制器在错误的时间清理资源对象。
在 Kubernetes 中,需要在所有节点上运行 NTP(参见
#6159 )
以避免时间偏差。时钟并不总是如此正确,但差异应该很小。
设置非零 TTL 时请注意避免这种风险。
接下来
3.4.2.7 - CronJob
FEATURE STATE: 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 : "*/1 * * * *"
jobTemplate :
spec :
template :
spec :
containers :
- name : hello
image : busybox
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 也是星期日)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6) (周日到周一;在某些系统上,7 也是星期日)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * *
输入
描述
相当于
@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 限制
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 所依赖的 Pods 与 Job 的概念。
阅读 CronJob .spec.schedule
字段的格式 。
有关创建和使用 CronJob 的说明及示例规约文件,请参见
使用 CronJob 运行自动化任务 。
CronJob
是 Kubernetes REST API 的一部分,
阅读
对象定义以了解关于该资源的 API。
3.4.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 也需要一个 .spec
部分 。
Pod 模板
.spec.template
是 .spec
的唯一必需字段。
.spec.template
是一个 Pod 模板 。
它的模式与 Pod 完全相同,只是它是嵌套的,没有 apiVersion
或 kind
属性。
除了 Pod 所需的字段外,ReplicationController 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。
对于标签,请确保不与其他控制器重叠。参考 Pod 选择算符 。
只允许 .spec.template.spec.restartPolicy
等于 Always
,如果没有指定,这是默认值。
对于本地容器重启,ReplicationController 委托给节点上的代理,
例如 Kubelet 或 Docker。
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 或 Go 客户端库时,你需要明确地执行这些步骤(缩放副本为 0、
等待 Pod 删除,之后删除 ReplicationController 资源)。
只删除 ReplicationController
你可以删除一个 ReplicationController 而不影响它的任何 Pod。
使用 kubectl,为 kubectl delete
指定 --cascade=false
选项。
当使用 REST API 或 Go 客户端库时,只需删除 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 主要容器的镜像标签,因为通常是镜像更新触发滚动更新。
滚动更新是在客户端工具 kubectl rolling-update
中实现的。访问 kubectl rolling-update
任务 以获得更多的具体示例。
多个版本跟踪
除了在滚动更新过程中运行应用程序的多个版本之外,通常还会使用多个版本跟踪来长时间,
甚至持续运行多个版本。这些跟踪将根据标签加以区分。
例如,一个服务可能把具有 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 对象,
它以类似于 kubectl rolling-update
的方式更新其底层 ReplicaSet 及其 Pod。
如果你想要这种滚动更新功能,那么推荐使用 Deployment,因为与 kubectl rolling-update
不同,
它们是声明式的、服务端的,并且具有其它特性。
裸 Pod
与用户直接创建 Pod 的情况不同,ReplicationController 能够替换因某些原因
被删除或被终止的 Pod ,例如在节点故障或中断节点维护的情况下,例如内核升级。
因此,我们建议你使用 ReplicationController,即使你的应用程序只需要一个 Pod。
可以将其看作类似于进程管理器,它只管理跨多个节点的多个 Pod ,而不是单个节点上的单个进程。
ReplicationController 将本地容器重启委托给节点上的某个代理(例如,Kubelet 或 Docker)。
Job
对于预期会自行终止的 Pod (即批处理任务),使用
Job
而不是 ReplicationController。
DaemonSet
对于提供机器级功能(例如机器监控或机器日志记录)的 Pod,
使用 DaemonSet
而不是
ReplicationController。
这些 Pod 的生命期与机器的生命期绑定:它们需要在其他 Pod 启动之前在机器上运行,
并且在机器准备重新启动或者关闭时安全地终止。
更多信息
请阅读运行无状态的 ReplicationController 。
3.5 - 服务、负载均衡和联网
Kubernetes 网络背后的概念和资源。
Kubernetes 网络解决四方面的问题:
一个 Pod 中的容器之间通过本地回路(loopback)通信。
集群网络在不同 pod 之间提供通信。
Service 资源允许你对外暴露 Pods 中运行的应用程序,以支持来自于集群外部的访问。
可以使用 Services 来发布仅供集群内部使用的服务。
3.5.1 - 使用拓扑键实现拓扑感知的流量路由
FEATURE STATE: 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.5.2 - 服务
将运行在一组
Pods 上的应用程序公开为网络服务的抽象方法。
使用 Kubernetes,你无需修改应用程序即可使用不熟悉的服务发现机制。
Kubernetes 为 Pods 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名,
并且可以在它们之间进行负载均衡。
动机
创建和销毁 Kubernetes Pod 以匹配集群状态。
Pod 是非永久性资源。
如果你使用 Deployment
来运行你的应用程序,则它可以动态创建和销毁 Pod。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,
那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
进入 Services 。
Service 资源
Kubernetes Service 定义了这样一种抽象:逻辑上的一组 Pod,一种可以访问它们的策略 —— 通常称为微服务。
Service 所针对的 Pods 集合通常是通过选择算符 来确定的。
要了解定义服务端点的其他方法,请参阅不带选择算符的服务 。
举个例子,考虑一个图片处理后端,它运行了 3 个副本。这些副本是可互换的 ——
前端不需要关心它们调用了哪个后端副本。
然而组成这一组后端程序的 Pod 实际上可能会发生变化,
前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。
Service 定义的抽象能够解耦这种关联。
云原生服务发现
如果你想要在应用程序中使用 Kubernetes API 进行服务发现,则可以查询
API 服务器
的 Endpoints 资源,只要服务中的 Pod 集合发生更改,Endpoints 就会被更新。
对于非本机应用程序,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 : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
上述配置创建一个名称为 "my-service" 的 Service 对象,它会将请求代理到使用
TCP 端口 9376,并且具有标签 "app=MyApp"
的 Pod 上。
Kubernetes 为该服务分配一个 IP 地址(有时称为 "集群IP"),该 IP 地址由服务代理使用。
(请参见下面的 VIP 和 Service 代理 ).
服务选择算符的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为
“my-service” 的 Endpoint 对象。
说明: 需要注意的是,Service 能够将一个接收 port
映射到任意的 targetPort
。
默认情况下,targetPort
将被设置为与 port
字段相同的值。
Pod 中的端口定义是有名字的,你可以在服务的 targetPort
属性中引用这些名称。
即使服务中使用单个配置的名称混合使用 Pod,并且通过不同的端口号提供相同的网络协议,此功能也可以使用。
这为部署和发展服务提供了很大的灵活性。
例如,你可以更改 Pods 在新版本的后端软件中公开的端口号,而不会破坏客户端。
服务的默认协议是 TCP;你还可以使用任何其他受支持的协议 。
由于许多服务需要公开多个端口,因此 Kubernetes 在服务对象上支持多个端口定义。
每个端口定义可以具有相同的 protocol
,也可以具有不同的协议。
没有选择算符的 Service
服务最常见的是抽象化对 Kubernetes Pod 的访问,但是它们也可以抽象化其他种类的后端。
实例:
希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。
希望服务指向另一个 名字空间(Namespace) 中或其它集群中的服务。
你正在将工作负载迁移到 Kubernetes。 在评估该方法时,你仅在 Kubernetes 中运行一部分后端。
在任何这些场景中,都能够定义没有选择算符的 Service。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
ports :
- protocol : TCP
port : 80
targetPort : 9376
由于此服务没有选择算符,因此不会自动创建相应的 Endpoint 对象。
你可以通过手动添加 Endpoint 对象,将服务手动映射到运行该服务的网络地址和端口:
apiVersion : v1
kind : Endpoints
metadata :
name : my-service
subsets :
- addresses :
- ip : 192.0.2.42
ports :
- port : 9376
Endpoints 对象的名称必须是合法的
DNS 子域名 。
说明: 端点 IPs 必须不可以 是:本地回路(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 作为目标。
访问没有选择算符的 Service,与有选择算符的 Service 的原理相同。
请求将被路由到用户定义的 Endpoint,YAML 中为:192.0.2.42:9376
(TCP)。
ExternalName Service 是 Service 的特例,它没有选择算符,但是使用 DNS 名称。
有关更多信息,请参阅本文档后面的ExternalName 。
超出容量的 Endpoints
如果某个 Endpoints 资源中包含的端点个数超过 1000,则 Kubernetes v1.22 版本
(及更新版本)的集群会将为该 Endpoints 添加注解
endpoints.kubernetes.io/over-capacity: truncated
。
这一注解表明所影响到的 Endpoints 对象已经超出容量,此外 Endpoints 控制器还会将 Endpoints 对象数量截断到 1000。
EndpointSlice
FEATURE STATE: Kubernetes v1.21 [stable]
Endpoint 切片是一种 API 资源,可以为 Endpoint 提供更可扩展的替代方案。
尽管从概念上讲与 Endpoint 非常相似,但 Endpoint 切片允许跨多个资源分布网络端点。
默认情况下,一旦到达100个 Endpoint,该 Endpoint 切片将被视为“已满”,
届时将创建其他 Endpoint 切片来存储任何其他 Endpoint。
Endpoint 切片提供了附加的属性和功能,这些属性和功能在
EndpointSlices
中有详细描述。
应用协议
FEATURE STATE: 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 带来高负载,从而使管理变得困难。
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 代理模式
FEATURE STATE: 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 小时)。
多端口 Service
对于某些服务,你需要公开多个端口。
Kubernetes 允许你在 Service 对象上配置多个端口定义。
为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : 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 不会转发请求相关服务的任何流量。
说明:
FEATURE STATE: Kubernetes v1.22 [alpha]
如果你启用了 kube-proxy 的 ProxyTerminatingEndpoints
特性门控 ,
kube-proxy 会检查节点是否有本地的端点,以及是否所有的本地端点都被标记为终止中。
如果本地有端点,而且所有端点处于终止中的状态,那么 kube-proxy 会忽略任何设为 Local
的外部流量策略。
在所有本地端点处于终止中的状态的同时,kube-proxy 将请求指定服务的流量转发到位于其它节点的
状态健康的端点,如同外部流量策略设为 Cluster
。
针对处于正被终止状态的端点这一转发行为使得外部负载均衡器可以优雅地排出由
NodePort
服务支持的连接,就算是健康检查节点端口开始失败也是如此。
否则,当节点还在负载均衡器的节点池内,在 Pod 终止过程中的流量会被丢掉,这些流量可能会丢失。
内部流量策略
FEATURE STATE: Kubernetes v1.22 [beta]
你可以设置 spec.internalTrafficPolicy
字段来控制内部来源的流量是如何转发的。可设置的值有 Cluster
和 Local
。
将字段设置为 Cluster
会将内部流量路由到所有就绪端点,设置为 Local
只会路由到当前节点上就绪的端点。
如果流量策略是 Local
,而且当前节点上没有就绪的端点,那么 kube-proxy 会丢弃流量。
服务发现
Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。
环境变量
当 Pod 运行在 Node
上,kubelet 会为每个活跃的 Service 添加一组环境变量。
它同时支持 Docker links兼容 变量
(查看 makeLinkVariables )、
简单的 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量。
这里 Service 的名称需大写,横线被转换成下划线。
举个例子,一个名称为 redis-master
的 Service 暴露了 TCP 端口 6379,
同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
REDIS_MASTER_SERVICE_HOST = 10.0.0.11
REDIS_MASTER_SERVICE_PORT = 6379
REDIS_MASTER_PORT = tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP = tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO = tcp
REDIS_MASTER_PORT_6379_TCP_PORT = 6379
REDIS_MASTER_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
的实现捆绑在一起。
对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,
而且平台也不会为它们进行负载均衡和路由。
DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。
带选择算符的服务
对定义了选择算符的无头服务,Endpoint 控制器在 API 中创建了 Endpoints 记录,
并且修改 DNS 配置返回 A 记录(IP 地址),通过这个地址直接到达 Service
的后端 Pod 上。
无选择算符的服务
对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints
记录。
然而 DNS 系统会查找和配置,无论是:
对于 ExternalName
类型的服务,查找其 CNAME 记录
对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints
的记录
发布服务(服务类型)
对一些应用的某些部分(如前端),可能希望将其暴露给 Kubernetes 集群外部
的 IP 地址。
Kubernetes ServiceTypes
允许指定你所需要的 Service 类型,默认是 ClusterIP
。
Type
的取值以及行为如下:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。
这也是默认的 ServiceType
。
NodePort
:通过每个节点上的 IP 和静态端口(NodePort
)暴露服务。
NodePort
服务会路由到自动创建的 ClusterIP
服务。
通过请求 <节点 IP>:<节点端口>
,你可以从集群的外部访问一个 NodePort
服务。
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
字段中要求分配的端口。
如果你想指定特定的 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 Services 的本地回路接口。
--nodeport-addresses
的默认值是一个空列表。
这意味着 kube-proxy 应该考虑 NodePort 的所有可用网络接口。
(这也与早期的 Kubernetes 版本兼容)。
如果需要特定的端口号,你可以在 nodePort
字段中指定一个值。
控制平面将为你分配该端口或报告 API 事务失败。
这意味着你需要自己注意可能发生的端口冲突。
你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。
使用 NodePort 可以让你自由设置自己的负载均衡解决方案,
配置 Kubernetes 不完全支持的环境,
甚至直接暴露一个或多个节点的 IP。
需要注意的是,Service 能够通过 <NodeIP>:spec.ports[*].nodePort
和
spec.clusterIp:spec.ports[*].port
而对外可见。
如果设置了 kube-proxy 的 --nodeport-addresses
参数或 kube-proxy 配置文件中的等效字段,
<NodeIP>
将被过滤 NodeIP。
例如:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
type : NodePort
selector :
app : MyApp
ports :
# 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。
- port : 80
targetPort : 80
# 可选字段
# 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767)
nodePort : 30007
LoadBalancer 类型
在使用支持外部负载均衡器的云提供商的服务时,设置 type
的值为 "LoadBalancer"
,
将为 Service 提供负载均衡器。
负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的
status.loadBalancer
字段发布出去。
实例:
apiVersion : v1
kind : Service
metadata :
name : my-service
spec :
selector :
app : 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
值将会被忽略掉。
混合协议类型的负载均衡器
FEATURE STATE: Kubernetes v1.20 [alpha]
默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时,所有
端口必须具有相同的协议,并且该协议必须是受云提供商支持的协议。
如果为 kube-apiserver 启用了 MixedProtocolLBService
特性门控,
则当定义了多个端口时,允许使用不同的协议。
说明: 可用于 LoadBalancer 类型服务的协议集仍然由云提供商决定。
禁用负载均衡器节点端口分配
FEATURE STATE: Kubernetes v1.20 [alpha]
从 v1.20 版本开始, 你可以通过设置 spec.allocateLoadBalancerNodePorts
为 false
对类型为 LoadBalancer 的服务禁用节点端口分配。
这仅适用于直接将流量路由到 Pod 而不是使用节点端口的负载均衡器实现。
默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,
LoadBalancer 类型的服务继续分配节点端口。
如果现有服务已被分配节点端口,将参数 spec.allocateLoadBalancerNodePorts
设置为 false
时,这些服务上已分配置的节点端口不会被自动释放。
你必须显式地在每个服务端口中删除 nodePorts
项以释放对应端口。
你必须启用 ServiceLBNodePortControl
特性门控才能使用该字段。
设置负载均衡器实现的类别
FEATURE STATE: Kubernetes v1.22 [beta]
spec.loadBalancerClass
允许你不使用云提供商的默认负载均衡器实现,转而使用指定的负载均衡器实现。
这个特性从 v1.21 版本开始可以使用,你在 v1.21 版本中使用这个字段必须启用 ServiceLoadBalancerClass
特性门控,这个特性门控从 v1.22 版本及以后默认打开。
默认情况下,.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"
[...]
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 上网络负载均衡器支持
FEATURE STATE: 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
(defaults to 0.0.0.0/0
)
kubernetes.io/rule/nlb/client=<loadBalancerName>
MTU Discovery
ICMP
3,4
.spec.loadBalancerSourceRanges
(defaults to 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 流量也可以到达那些修改后的安全组中的所有实例。
腾讯 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 地址进行硬编码,请考虑使用
headless 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 : 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 需要 ),但当前 API 是强制要求的。
虚拟IP实施
对很多想使用 Service 的人来说,前面的信息应该足够了。
然而,有很多内部原理性的内容,还是值去理解的。
避免冲突
Kubernetes 最主要的哲学之一,是用户不应该暴露那些能够导致他们操作失败、但又不是他们的过错的场景。
对于 Service 资源的设计,这意味着如果用户的选择有可能与他人冲突,那就不要让用户自行选择端口号。
这是一个隔离性的失败。
为了使用户能够为他们的 Service 选择一个端口号,我们必须确保不能有2个 Service 发生冲突。
Kubernetes 通过为每个 Service 分配它们自己的 IP 地址来实现。
为了保证每个 Service 被分配到一个唯一的 IP,需要一个内部的分配器能够原子地更新
etcd 中的一个全局分配映射表,
这个更新操作要先于创建每一个 Service。
为了使 Service 能够获取到 IP,这个映射表对象必须在注册中心存在,
否则创建 Service 将会失败,指示一个 IP 不能被分配。
在控制平面中,一个后台 Controller 的职责是创建映射表
(需要支持从使用了内存锁的 Kubernetes 的旧版本迁移过来)。
同时 Kubernetes 会通过控制器检查不合理的分配(如管理员干预导致的)
以及清理已被分配但不再被任何 Service 使用的 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
FEATURE STATE: 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.5.3 - Pod 与 Service 的 DNS
Kubernetes 为服务和 Pods 创建 DNS 记录。
你可以使用一致的 DNS 名称而非 IP 地址来访问服务。
介绍
Kubernetes DNS 在集群上调度 DNS Pod 和服务,并配置 kubelet 以告知各个容器
使用 DNS 服务的 IP 来解析 DNS 名称。
集群中定义的每个 Service (包括 DNS 服务器自身)都被赋予一个 DNS 名称。
默认情况下,客户端 Pod 的 DNS 搜索列表会包含 Pod 自身的名字空间和集群
的默认域。
Service 的名字空间
DNS 查询可能因为执行查询的 Pod 所在的名字空间而返回不同的结果。
不指定名字空间的 DNS 查询会被限制在 Pod 所在的名字空间内。
要访问其他名字空间中的服务,需要在 DNS 查询中给出名字空间。
例如,假定名字空间 test
中存在一个 Pod,prod
名字空间中存在一个服务
data
。
Pod 查询 data
时没有返回结果,因为使用的是 Pod 的名字空间 test
。
Pod 查询 data.prod
时则会返回预期的结果,因为查询中指定了名字空间。
DNS 查询可以使用 Pod 中的 /etc/resolv.conf
展开。kubelet 会为每个 Pod
生成此文件。例如,对 data
的查询可能被展开为 data.test.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 的服务发现 。
服务
A/AAAA 记录
“普通” 服务(除了无头服务)会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于服务的 IP 协议族。
该名称会解析成对应服务的集群 IP。
“无头(Headless)” 服务(没有集群 IP)也会以
my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个 DNS A 或 AAAA 记录,
具体取决于服务的 IP 协议族。
与普通服务不同,这一记录会被解析成对应服务所选择的 Pod 集合的 IP。
客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。
SRV 记录
Kubernetes 会为命名端口创建 SRV 记录,这些端口是普通服务或
无头服务 的一部分。
对每个命名端口,SRV 记录具有 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
这种形式。
对普通服务,该记录会被解析成端口号和域名:my-svc.my-namespace.svc.cluster-domain.example
。
对无头服务,该记录会被解析成多个结果,服务对应的每个后端 Pod 各一个;
其中包含 Pod 端口号和形为 auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
的域名。
Pods
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
.
Deployment 或通过 Service 暴露出来的 DaemonSet 所创建的 Pod 会有如下 DNS
解析名称可用:
pod-ip-address.deployment-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
如果某无头服务与某 Pod 在同一个名字空间中,且它们具有相同的子域名,
集群的 DNS 服务器也会为该 Pod 的全限定主机名返回 A 记录或 AAAA 记录。
例如,在同一个名字空间中,给定一个主机名为 “busybox-1”、
子域名设置为 “default-subdomain” 的 Pod,和一个名称为 “default-subdomain
”
的无头服务,Pod 将看到自己的 FQDN 为
"busybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
"。
DNS 会为此名字提供一个 A 记录或 AAAA 记录,指向该 Pod 的 IP。
“busybox1
” 和 “busybox2
” 这两个 Pod 分别具有它们自己的 A 或 AAAA 记录。
Endpoints 对象可以为任何端点地址及其 IP 指定 hostname
。
说明: 因为没有为 Pod 名称创建 A 记录或 AAAA 记录,所以要创建 Pod 的 A 记录
或 AAAA 记录需要 hostname
。
没有设置 hostname
但设置了 subdomain
的 Pod 只会为
无头服务创建 A 或 AAAA 记录(default-subdomain.my-namespace.svc.cluster-domain.example
)
指向 Pod 的 IP 地址。
另外,除非在服务上设置了 publishNotReadyAddresses=True
,否则只有 Pod 进入就绪状态
才会有与之对应的记录。
Pod 的 setHostnameAsFQDN 字段
FEATURE STATE: Kubernetes v1.22 [stable]
前置条件 :SetHostnameAsFQDN
特性门控
必须在 API 服务器
上启用。
当你在 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
"。
"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 配置
FEATURE STATE: 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 fd00:79:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5
扩展 DNS 配置
FEATURE STATE: Kubernetes 1.22 [alpha]
对于 Pod DNS 配置,Kubernetes 默认允许最多 6 个 搜索域( Search Domain)
以及一个最多 256 个字符的搜索域列表。
如果启用 kube-apiserver 和 kubelet 的特性门控 ExpandedDNSConfig
,Kubernetes 将可以有最多 32 个
搜索域以及一个最多 2048 个字符的搜索域列表。
功能的可用性
Pod DNS 配置和 DNS 策略 "None
" 的可用版本对应如下所示。
k8s 版本
特性支持
1.14
稳定
1.10
Beta(默认启用)
1.9
Alpha
接下来
有关管理 DNS 配置的指导,请查看
配置 DNS 服务
3.5.4 - 使用 Service 连接到应用
Kubernetes 连接容器模型
既然有了一个持续运行、可复制的应用,我们就能够将它暴露到网络上。
在讨论 Kubernetes 网络连接的方式之前,非常值得与 Docker 中 “正常” 方式的网络进行对比。
默认情况下,Docker 使用私有主机网络连接,只能与同在一台机器上的容器进行通信。
为了实现容器的跨节点通信,必须在机器自己的 IP 上为这些容器分配端口,为容器进行端口转发或者代理。
多个开发人员或是提供容器的团队之间协调端口的分配很难做到规模化,那些难以控制的集群级别的问题,都会交由用户自己去处理。
Kubernetes 假设 Pod 可与其它 Pod 通信,不管它们在哪个主机上。
Kubernetes 给 Pod 分配属于自己的集群私有 IP 地址,所以没必要在 Pod 或映射到的容器的端口和主机端口之间显式地创建连接。
这表明了在 Pod 内的容器都能够连接到本地的每个端口,集群中的所有 Pod 不需要通过 NAT 转换就能够互相看到。
文档的剩余部分详述如何在一个网络模型之上运行可靠的服务。
该指南使用一个简单的 Nginx server 来演示并证明谈到的概念。
在集群中暴露 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 yaml | grep podIP
podIP: 10.244.3.4
podIP: 10.244.2.5
应该能够通过 ssh 登录到集群中的任何一个节点上,使用 curl 也能调通所有 IP 地址。
需要注意的是,容器不会使用该节点上的 80 端口,也不会使用任何特定的 NAT 规则去路由流量到 Pod 上。
这意味着可以在同一个节点上运行多个 Pod,使用相同的容器端口,并且可以从集群中任何其他的 Pod 或节点上使用 IP 的方式访问到它们。
像 Docker 一样,端口能够被发布到主机节点的接口上,但是出于网络模型的原因应该从根本上减少这种用法。
如果对此好奇,可以获取更多关于 如何实现网络模型 的内容。
创建 Service
我们有 Pod 在一个扁平的、集群范围的地址空间中运行 Nginx 服务,可以直接连接到这些 Pod,但如果某个节点死掉了会发生什么呢?
Pod 会终止,Deployment 将创建新的 Pod,且使用不同的 IP。这正是 Service 要解决的问题。
Kubernetes Service 从逻辑上定义了运行在集群中的一组 Pod,这些 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,对应具有标签 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 由一组 backend Pod 组成。这些 Pod 通过 endpoints
暴露出来。
Service Selector 将持续评估,结果被 POST 到一个名称为 my-nginx
的 Endpoint 对象上。
当 Pod 终止后,它会自动从 Endpoint 中移除,新的能够匹配上 Service Selector 的 Pod 将自动地被添加到 Endpoint 中。
检查该 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>
NAME ENDPOINTS AGE
my-nginx 10.244.2.5:80,10.244.3.4:80 1m
现在,能够从集群中任意节点上使用 curl 命令请求 Nginx Service <CLUSTER-IP>:<PORT>
。
注意 Service IP 完全是虚拟的,它从来没有走过网络,如果对它如何工作的原理感到好奇,
可以进一步阅读服务代理
的内容。
访问 Service
Kubernetes支持两种查找服务的主要模式: 环境变量和DNS。 前者开箱即用,而后者则需要[CoreDNS集群插件]
CoreDNS 集群插件 .
说明: 如果不需要服务环境变量(因为可能与预期的程序冲突,可能要处理的变量太多,或者仅使用DNS等),则可以通过在
pod spec
上将
enableServiceLinks
标志设置为
false
来禁用此模式。
环境变量
当 Pod 在 Node 上运行时,kubelet 会为每个活跃的 Service 添加一组环境变量。
这会有一个顺序的问题。想了解为何,检查正在运行的 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 会 先于 副本存在。这将实现调度器级别的 Service,能够使 Pod 分散创建(假定所有的 Node 都具有同样的容量),以及正确的环境变量:
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 提供了一个 DNS 插件 Service,它使用 skydns 自动为其它 Service 指派 DNS 名字。
如果它在集群中处于运行状态,可以通过如下命令来检查:
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
如果没有在运行,可以启用它 。
本段剩余的内容,将假设已经有一个 Service,它具有一个长久存在的 IP(my-nginx),
一个为该 IP 指派名称的 DNS 服务器。 这里我们使用 CoreDNS 集群插件(应用名为 kube-dns
),
所以可以通过标准做法,使在集群中的任何 Pod 都能与该 Service 通信(例如:gethostbyname()
)。
如果 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
default-token-il9rc kubernetes.io/service-account-token 1 1d
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 上):
# Create a public private key pair
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"
# Convert the keys to base64 encoding
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"
现在使用文件创建 Secrets:
kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret kubernetes.io/tls 2 1m
现在修改 nginx 副本,启动一个使用在秘钥中的证书的 HTTPS 服务器和 Service,暴露端口(80 和 443):
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 -o yaml | grep -i podip
podIP: 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 来测试(为了简化使用同一个秘钥,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
,因此 Nginx https 副本已经就绪,
如果使用一个公网 IP,能够处理 Internet 上的流量。
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,它使用主机名(比较长),而不是 IP。
它太长以至于不能适配标准 kubectl get svc
的输出,事实上需要通过执行 kubectl describe service my-nginx
命令来查看它。
可以看到类似如下内容:
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...
接下来
3.5.5 - Ingress
FEATURE STATE: Kubernetes v1.19 [stable]
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
术语
为了表达更加清晰,本指南定义了以下术语:
节点(Node): Kubernetes 集群中其中一台工作机器,是集群的一部分。
集群(Cluster): 一组运行由 Kubernetes 管理的容器化应用程序的节点。
在此示例和在大多数常见的 Kubernetes 部署环境中,集群中的节点都不在公共网络中。
边缘路由器(Edge router): 在集群中强制执行防火墙策略的路由器(router)。
可以是由云提供商管理的网关,也可以是物理硬件。
集群网络(Cluster network): 一组逻辑的或物理的连接,根据 Kubernetes
网络模型 在集群内实现通信。
服务(Service):Kubernetes 服务 使用
标签 选择算符(selectors)标识的一组 Pod。
除非另有说明,否则假定服务只具有在集群网络中可路由的虚拟 IP。
Ingress 是什么?
Ingress
公开了从集群外部到集群内服务 的 HTTP 和 HTTPS 路由。
流量路由由 Ingress 资源上定义的规则控制。
下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress];
ingress-->|路由规则|service[Service];
subgraph cluster
ingress;
service-->pod1[Pod];
service-->pod2[Pod];
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 ingress,service,pod1,pod2 k8s;
class client plain;
class cluster cluster;
[JavaScript must be enabled to view content]
可以将 Ingress 配置为服务提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及提供基于名称的虚拟主机等能力。
Ingress 控制器
通常负责通过负载均衡器来实现 Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。
Ingress 不会公开任意端口或协议。
将 HTTP 和 HTTPS 以外的服务公开到 Internet 时,通常使用
Service.Type=NodePort
或 Service.Type=LoadBalancer
类型的服务。
环境准备
你必须具有 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 :
rules :
- http :
paths :
- path : /testpath
pathType : Prefix
backend :
service :
name : test
port :
number : 80
与所有其他 Kubernetes 资源一样,Ingress 需要使用 apiVersion
、kind
和 metadata
字段。
Ingress 对象的命名必须是合法的 DNS 子域名名称 。
有关使用配置文件的一般信息,请参见部署应用 、
配置容器 、
管理资源 。
Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress 控制器,例如
重写目标注解 。
不同的 Ingress 控制器
支持不同的注解。查看文档以供你选择 Ingress 控制器,以了解支持哪些注解。
Ingress 规约
提供了配置负载均衡器或者代理服务器所需的所有信息。
最重要的是,其中包含与所有传入请求匹配的规则列表。
Ingress 资源仅支持用于转发 HTTP 流量的规则。
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
的请求。
DefaultBackend
没有 rules
的 Ingress 将所有流量发送到同一个默认后端。
defaultBackend
通常是 Ingress 控制器
的配置选项,而非在 Ingress 资源中指定。
如果 hosts
或 paths
都没有与 Ingress 对象中的 HTTP 请求匹配,则流量将路由到默认后端。
资源后端
Resource
后端是一个 ObjectRef
,指向同一名字空间中的另一个
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 资源包含一个可选的 parameters
字段,可用于为该类引用额外的、
特定于具体实现的配置。
名字空间域的参数
FEATURE STATE: Kubernetes v1.22 [beta]
parameters
字段有一个 scope
和 namespace
字段,可用来引用特定
于名字空间的资源,对 Ingress 类进行配置。
scope
字段默认为 Cluster
,表示默认是集群作用域的资源。
将 scope
设置为 Namespace
并设置 namespace
字段就可以引用某特定
名字空间中的参数资源。
有了名字空间域的参数,就不再需要为一个参数资源配置集群范围的 CustomResourceDefinition。
除此之外,之前对访问集群范围的资源进行授权,需要用到 RBAC 相关的资源,现在也不再需要了。
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
namespace : external-configuration
scope : Namespace
废弃的注解
在 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 类型
由单个 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 允许你将负载均衡器的数量降至最低。例如,这样的设置:
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress, 178.91.123.132];
ingress-->|/foo|service1[Service service1:4200];
ingress-->|/bar|service2[Service service2:8080];
subgraph cluster
ingress;
service1-->pod1[Pod];
service1-->pod2[Pod];
service2-->pod3[Pod];
service2-->pod4[Pod];
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 ingress,service1,service2,pod1,pod2,pod3,pod4 k8s;
class client plain;
class cluster cluster;
[JavaScript must be enabled to view content]
将需要一个如下所示的 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 地址上。
graph LR;
client([客户端])-. Ingress-管理的 负载均衡器 .->ingress[Ingress, 178.91.123.132];
ingress-->|Host: foo.bar.com|service1[Service service1:80];
ingress-->|Host: bar.foo.com|service2[Service service2:80];
subgraph cluster
ingress;
service1-->pod1[Pod];
service1-->pod2[Pod];
service2-->pod3[Pod];
service2-->pod4[Pod];
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 ingress,service1,service2,pod1,pod2,pod3,pod4 k8s;
class client plain;
class cluster cluster;
[JavaScript must be enabled to view content]
以下 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
,
而针对该 IP 地址的、没有在请求中定义主机名的请求流量会被路由(即,不提供请求标头)
到 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.crt
和 tls.key
的键名。
这些数据包含用于 TLS 的证书和私钥。例如:
apiVersion : v1
kind : Secret
metadata :
name : testsecret-tls
namespace : default
data :
tls.crt : base64 编码的 cert
tls.key : base64 编码的 key
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.5.6 - Ingress 控制器
为了让 Ingress 资源工作,集群必须有一个正在运行的 Ingress 控制器。
与作为 kube-controller-manager
可执行文件的一部分运行的其他类型的控制器不同,
Ingress 控制器不是随集群自动启动的。
基于此页面,你可选择最适合你的集群的 ingress 控制器实现。
Kubernetes 作为一个项目,目前支持和维护
AWS ,
GCE
和 nginx Ingress 控制器。
其他控制器
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
使用多个 Ingress 控制器
你可以在集群中部署任意数量的 ingress 控制器 。
创建 ingress 时,应该使用适当的
ingress.class
注解每个 Ingress 以表明在集群中如果有多个 Ingress 控制器时,应该使用哪个 Ingress 控制器。
如果不定义 ingress.class
,云提供商可能使用默认的 Ingress 控制器。
理想情况下,所有 Ingress 控制器都应满足此规范,但各种 Ingress 控制器的操作略有不同。
说明: 确保你查看了 ingress 控制器的文档,以了解选择它的注意事项。
接下来
3.5.7 - 拓扑感知提示
FEATURE STATE: Kubernetes v1.21 [alpha]
拓扑感知提示 包含客户怎么使用服务端点的建议,从而实现了拓扑感知的路由功能。
这种方法添加了元数据,以启用 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.5.8 - 服务内部流量策略
FEATURE STATE: Kubernetes v1.21 [alpha]
服务内部流量策略 开启了内部流量限制,只路由内部流量到和发起方处于相同节点的服务端点。
这里的”内部“流量指当前集群中的 Pod 所发起的流量。
这种机制有助于节省开销,提升效率。
使用服务内部流量策略
一旦你启用了 ServiceInternalTrafficPolicy
这个
特性门控 ,
你就可以通过将 Services 的
.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 : MyApp
ports :
- protocol : TCP
port : 80
targetPort : 9376
internalTrafficPolicy : Local
工作原理
kube-proxy 基于 spec.internalTrafficPolicy
的设置来过滤路由的目标服务端点。
当它的值设为 Local
时,只选择节点本地的服务端点。
当它的值设为 Cluster
或缺省时,则选择所有的服务端点。
启用特性门控
ServiceInternalTrafficPolicy
后,
spec.internalTrafficPolicy
的值默认设为 Cluster
。
限制
在一个Service上,当 externalTrafficPolicy
已设置为 Local
时,服务内部流量策略无法使用。
换句话说,在一个集群的不同 Service 上可以同时使用这两个特性,但在一个 Service 上不行。
接下来
3.5.9 - 端点切片(Endpoint Slices)
FEATURE STATE: Kubernetes v1.21 [stable]
端点切片(EndpointSlices) 提供了一种简单的方法来跟踪 Kubernetes 集群中的网络端点
(network endpoints)。它们为 Endpoints 提供了一种可伸缩和可拓展的替代方案。
动机
Endpoints API 提供了在 Kubernetes 跟踪网络端点的一种简单而直接的方法。
不幸的是,随着 Kubernetes 集群和 服务
逐渐开始为更多的后端 Pods 处理和发送请求,原来的 API 的局限性变得越来越明显。
最重要的是那些因为要处理大量网络端点而带来的挑战。
由于任一服务的所有网络端点都保存在同一个 Endpoints 资源中,这类资源可能变得
非常巨大,而这一变化会影响到 Kubernetes 组件(比如主控组件)的性能,并
在 Endpoints 变化时产生大量的网络流量和额外的处理。
EndpointSlice 能够帮助你缓解这一问题,还能为一些诸如拓扑路由这类的额外
功能提供一个可扩展的平台。
Endpoint Slice 资源
在 Kubernetes 中,EndpointSlice
包含对一组网络端点的引用。
指定选择器后控制面会自动为设置了 选择算符
的 Kubernetes 服务创建 EndpointSlice。
这些 EndpointSlice 将包含对与服务选择算符匹配的所有 Pod 的引用。
EndpointSlice 通过唯一的协议、端口号和服务名称将网络端点组织在一起。
EndpointSlice 的名称必须是合法的
DNS 子域名 。
例如,下面是 Kubernetes 服务 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 API 存储了可能对使用者有用的、有关端点的状况。
这三个状况分别是 ready
、serving
和 terminating
。
Ready(就绪)
ready
状况是映射 Pod 的 Ready
状况的。
处于运行中的 Pod,它的 Ready
状况被设置为 True
,应该将此 EndpointSlice 状况也设置为 true
。
出于兼容性原因,当 Pod 处于终止过程中,ready
永远不会为 true
。
消费者应参考 serving
状况来检查处于终止中的 Pod 的就绪情况。
该规则的唯一例外是将 spec.publishNotReadyAddresses
设置为 true
的服务。
这些服务(Service)的端点将始终将 ready
状况设置为 true
。
Serving(服务中)
FEATURE STATE: Kubernetes v1.20 [alpha]
serving
状况与 ready
状况相同,不同之处在于它不考虑终止状态。
如果 EndpointSlice API 的使用者关心 Pod 终止时的就绪情况,就应检查此状况。
说明:
尽管 serving
与 ready
几乎相同,但是它是为防止破坏 ready
的现有含义而增加的。
如果对于处于终止中的端点,ready
可能是 true
,那么对于现有的客户端来说可能是有些意外的,
因为从始至终,Endpoints 或 EndpointSlice API 从未包含处于终止中的端点。
出于这个原因,ready
对于处于终止中的端点 总是 false
,
并且在 v1.20 中添加了新的状况 serving
,以便客户端可以独立于 ready
的现有语义来跟踪处于终止中的 Pod 的就绪情况。
Terminating(终止中)
FEATURE STATE: Kubernetes v1.20 [alpha]
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
标签来标明的,
目的是方便查找隶属于某服务的所有 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 都有一组端口值,适用于资源内的所有端点。
当为服务使用命名端口时,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 的实现必须能够处理端点出现在多个切片中的状况。
关于如何执行端点去重(deduplication)的参考实现,你可以在 kube-proxy
的
EndpointSlice
实现中找到。
接下来
3.5.10 - 网络策略
如果你希望在 IP 地址或端口层面(OSI 第 3 层或第 4 层)控制网络流量,
则你可以考虑为集群中特定应用使用 Kubernetes 网络策略(NetworkPolicy)。
NetworkPolicy 是一种以应用为中心的结构,允许你设置如何允许
Pod 与网络上的各类网络“实体”
(我们这里使用实体以避免过度使用诸如“端点”和“服务”这类常用术语,
这些术语在 Kubernetes 中有特定含义)通信。
Pod 可以通信的 Pod 是通过如下三个标识符的组合来辩识的:
其他被允许的 Pods(例外:Pod 无法阻塞对自身的访问)
被允许的名字空间
IP 组块(例外:与 Pod 运行所在的节点的通信总是被允许的,
无论 Pod 或节点的 IP 地址)
在定义基于 Pod 或名字空间的 NetworkPolicy 时,你会使用
选择算符 来设定哪些流量
可以进入或离开与该算符匹配的 Pod。
同时,当基于 IP 的 NetworkPolicy 被创建时,我们基于 IP 组块(CIDR 范围)
来定义策略。
前置条件
网络策略通过网络插件
来实现。要使用网络策略,你必须使用支持 NetworkPolicy 的网络解决方案。
创建一个 NetworkPolicy 资源对象而没有控制器来使它生效的话,是没有任何作用的。
隔离和非隔离的 Pod
默认情况下,Pod 是非隔离的,它们接受任何来源的流量。
Pod 在被某 NetworkPolicy 选中时进入被隔离状态。
一旦名字空间中有 NetworkPolicy 选择了特定的 Pod,该 Pod 会拒绝该 NetworkPolicy
所不允许的连接。
(名字空间下其他未被 NetworkPolicy 所选择的 Pod 会继续接受所有的流量)
网络策略不会冲突,它们是累积的。
如果任何一个或多个策略选择了一个 Pod, 则该 Pod 受限于这些策略的
入站(Ingress)/出站(Egress)规则的并集。因此评估的顺序并不会影响策略的结果。
为了允许两个 Pods 之间的网络数据流,源端 Pod 上的出站(Egress)规则和
目标端 Pod 上的入站(Ingress)规则都需要允许该流量。
如果源端的出站(Egress)规则或目标端的入站(Ingress)规则拒绝该流量,
则流量将被拒绝。
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
字段。关于配置文件操作的一般信息,请参考
使用 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 规则)允许从带有 "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 被视为
“隔离”的策略),则可以创建一个策略来明确允许该名字空间中的所有流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-ingress
spec :
podSelector : {}
ingress :
- {}
policyTypes :
- Ingress
默认拒绝所有出站流量
你可以通过创建选择所有容器但不允许来自这些容器的任何出站流量的 NetworkPolicy
来为名字空间创建 "default" egress 隔离策略。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-egress
spec :
podSelector : {}
policyTypes :
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被允许流出流量。
此策略不会更改默认的入站流量隔离行为。
默认允许所有出站流量
如果要允许来自名字空间中所有 Pod 的所有流量(即使添加了导致某些 Pod 被视为“隔离”的策略),
则可以创建一个策略,该策略明确允许该名字空间中的所有出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : allow-all-egress
spec :
podSelector : {}
egress :
- {}
policyTypes :
- Egress
默认拒绝所有入口和所有出站流量
你可以为名字空间创建“默认”策略,以通过在该名字空间中创建以下 NetworkPolicy
来阻止所有入站和出站流量。
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : default-deny-all
spec :
podSelector : {}
policyTypes :
- Ingress
- Egress
此策略可以确保即使没有被其他任何 NetworkPolicy 选择的 Pod 也不会被
允许入站或出站流量。
SCTP 支持
FEATURE STATE: Kubernetes v1.20 [stable]
作为一个稳定特性,SCTP 支持默认是被启用的。
要在集群层面禁用 SCTP,你(或你的集群管理员)需要为 API 服务器指定
--feature-gates=SCTPSupport=false,...
来禁用 SCTPSupport
特性门控 。
启用该特性门控后,用户可以将 NetworkPolicy 的 protocol
字段设置为 SCTP
。
说明:
你必须使用支持 SCTP 协议网络策略的 CNI 插件。
针对某个端口范围
FEATURE STATE: Kubernetes v1.22 [beta]
在编写 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
中所有带有标签 db
的 Pod 使用 TCP 协议
与 10.0.0.0/24
范围内的 IP 通信,只要目标端口介于 32000 和 32768 之间就可以。
使用此字段时存在以下限制:
作为一种 Beta 阶段的特性,端口范围设定默认是被启用的。要在整个集群
范围内禁止使用 endPort
字段,你(或者你的集群管理员)需要为 API
服务器设置 -feature-gates=NetworkPolicyEndPort=false,...
以禁用
NetworkPolicyEndPort
特性门控 。
endPort
字段必须等于或者大于 port
字段的值。
两个字段的设置值都只能是数字。
说明:
你的集群所使用的 CNI 插件
必须支持在 NetworkPolicy 规约中使用 endPort
字段。
如果你的网络插件
不支持 endPort
字段,而你指定了一个包含 endPort
字段的 NetworkPolicy,
策略只对单个 port
字段生效。
基于名字指向某名字空间
FEATURE STATE: Kubernetes 1.21 [beta]
只要 NamespaceDefaultLabelName
特性门控
被启用,Kubernetes 控制面会在所有名字空间上设置一个不可变更的标签
kubernetes.io/metadata.name
。该标签的值是名字空间的名称。
如果 NetworkPolicy 无法在某些对象字段中指向某名字空间,你可以使用标准的
标签方式来指向特定名字空间。
通过网络策略(至少目前还)无法完成的工作
到 Kubernetes 1.23 为止,NetworkPolicy API 还不支持以下功能,不过
你可能可以使用操作系统组件(如 SELinux、OpenVSwitch、IPTables 等等)
或者第七层技术(Ingress 控制器、服务网格实现)或准入控制器来实现一些
替代方案。
如果你对 Kubernetes 中的网络安全性还不太了解,了解使用 NetworkPolicy API
还无法实现下面的用户场景是很值得的。
强制集群内部流量经过某公用网关(这种场景最好通过服务网格或其他代理来实现);
与 TLS 相关的场景(考虑使用服务网格或者 Ingress 控制器);
特定于节点的策略(你可以使用 CIDR 来表达这一需求不过你无法使用节点在
Kubernetes 中的其他标识信息来辩识目标节点);
基于名字来选择服务(不过,你可以使用 标签
来选择目标 Pod 或名字空间,这也通常是一种可靠的替代方案);
创建或管理由第三方来实际完成的“策略请求”;
实现适用于所有名字空间或 Pods 的默认策略(某些第三方 Kubernetes 发行版本
或项目可以做到这点);
高级的策略查询或者可达性相关工具;
生成网络安全事件日志的能力(例如,被阻塞或接收的连接请求);
显式地拒绝策略的能力(目前,NetworkPolicy 的模型默认采用拒绝操作,
其唯一的能力是添加允许策略);
禁止本地回路或指向宿主的网络流量(Pod 目前无法阻塞 localhost 访问,
它们也无法禁止来自所在节点的访问请求)。
接下来
参阅声明网络策略
演练了解更多示例;
有关 NetworkPolicy 资源所支持的常见场景的更多信息,请参见
此指南 。
3.5.11 - IPv4/IPv6 双协议栈
FEATURE STATE: Kubernetes v1.21 [beta]
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 网络接口)
支持双协议栈的网络插件(如 Kubenet 或 Calico)
配置 IPv4/IPv6 双协议栈
要使用 IPv4/IPv6 双协议栈,确保为集群的相关组件启用 IPv6DualStack
特性门控 ,
(从 1.21 版本开始,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>
说明:
IPv4 CIDR 的一个例子:10.244.0.0/16
(尽管你会提供你自己的地址范围)。
IPv6 CIDR 的一个例子:fdXY:IJKL:MNOP:15::/64
(这里演示的是格式而非有效地址 - 请看 RFC 4193 )。
从 1.21 开始 IPv4/IPv6 双协议栈默认为启用状态。
你可以在必要的时候通过为 kube-apiserver、kube-controller-manager、kubelet
和 kube-proxy 命令行设置 --feature-gates="IPv6DualStack=false"
来禁用
此特性。
服务
你可以使用 IPv4 或 IPv6 地址来创建
Service 。
服务的地址族默认为第一个服务集群 IP 范围的地址族(通过 kube-apiserver 的
--service-cluster-ip-range
参数配置)。
当你定义服务时,可以选择将其配置为双栈。若要指定所需的行为,你可以设置
.spec.ipFamilyPolicy
字段为以下值之一:
SingleStack
:单栈服务。控制面使用第一个配置的服务集群 IP 范围为服务分配集群 IP。
PreferDualStack
:
为服务分配 IPv4 和 IPv6 集群 IP 地址。
(如果集群设置了 --feature-gates="IPv6DualStack=false"
,则此设置的行为与
SingleStack
设置相同。)
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 : MyApp
spec :
selector :
app : 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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app : 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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
- IPv4
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
现有服务的双栈默认值
下面示例演示了在服务已经存在的集群上新启用双栈时的默认行为。
(将现有集群升级到 1.21 会启用双协议栈支持,除非设置了
--feature-gates="IPv6DualStack=false"
)
在集群上启用双栈时,控制面会将现有服务(无论是 IPv4
还是 IPv6
)配置
.spec.ipFamilyPolicy
为 SingleStack
并设置 .spec.ipFamilies
为服务的当前地址族。
apiVersion : v1
kind : Service
metadata :
name : my-service
labels :
app : MyApp
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查现有服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app : 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 : 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 : MyApp
spec :
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
你可以通过使用 kubectl 检查带有选择算符的现有无头服务来验证此行为。
kubectl get svc my-service -o yaml
apiVersion : v1
kind : Service
metadata :
labels :
app : MyApp
name : my-service
spec :
clusterIP : None
clusterIPs :
- None
ipFamilies :
- IPv4
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app : 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 伪装。
接下来
3.6 - 存储
为集群中的 Pods 提供长期和临时存储的方法。
3.6.1 - 卷
Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用
程序带来一些问题。问题之一是当容器崩溃时文件丢失。kubelet 会重新启动容器,
但容器会以干净的状态重启。
第二个问题会在同一 Pod
中运行多个容器并共享文件时出现。
Kubernetes 卷(Volume)
这一抽象概念能够解决这两个问题。
阅读本文前建议你熟悉一下 Pods 。
背景
Docker 也有 卷(Volume) 的概念,但对它只有少量且松散的管理。
Docker 卷是磁盘上或者另外一个容器内的一个目录。
Docker 提供卷驱动程序,但是其功能非常有限。
Kubernetes 支持很多类型的卷。
Pod 可以同时使用任意数目的卷类型。
临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。
当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁
持久卷。对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。
所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放
的内容。
使用卷时, 在 .spec.volumes
字段中设置为 Pod 提供的卷,并在
.spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。
容器中的进程看到的是由它们的 Docker 镜像和卷组成的文件系统视图。
Docker 镜像
位于文件系统层次结构的根部。各个卷则挂载在镜像内的指定路径上。
卷不能挂载到其他卷之上,也不能与其他卷有硬链接。
Pod 配置中的每个容器必须独立指定各个卷的挂载位置。
卷类型
Kubernetes 支持下列类型的卷:
awsElasticBlockStore
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 实例上。
创建 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 : k8s.gcr.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 卷迁移
FEATURE STATE: Kubernetes v1.17 [beta]
如果启用了对 awsElasticBlockStore
的 CSIMigration
特性支持,所有插件操作都
不再指向树内插件(In-Tree Plugin),转而指向 ebs.csi.aws.com
容器存储接口
(Container Storage Interface,CSI)驱动。为了使用此特性,必须在集群中安装
AWS EBS CSI 驱动 ,
并确保 CSIMigration
和 CSIMigrationAWS
Beta 功能特性被启用。
AWS EBS CSI 迁移结束
FEATURE STATE: Kubernetes v1.17 [alpha]
要禁止控制器管理器和 kubelet 加载 awsElasticBlockStore
存储插件,
请将 InTreePluginAWSUnregister
标志设置为 true
。
azureDisk
azureDisk
卷类型用来在 Pod 上挂载 Microsoft Azure
数据盘(Data Disk) 。
若需了解更多详情,请参考 azureDisk
卷插件 。
azureDisk 的 CSI 迁移
FEATURE STATE: Kubernetes v1.19 [beta]
启用 azureDisk
的 CSIMigration
功能后,所有插件操作从现有的树内插件重定向到
disk.csi.azure.com
容器存储接口(CSI)驱动程序。
为了使用此功能,必须在集群中安装
Azure 磁盘 CSI 驱动程序 ,
并且 CSIMigration
和 CSIMigrationAzureDisk
功能必须被启用。
azureFile
azureFile
卷类型用来在 Pod 上挂载 Microsoft Azure 文件卷(File Volume)(SMB 2.1 和 3.0)。
更多详情请参考 azureFile
卷插件 。
azureFile CSI 迁移
FEATURE STATE: Kubernetes v1.21 [beta]
启用 azureFile
的 CSIMigration
功能后,所有插件操作将从现有的树内插件重定向到
file.csi.azure.com
容器存储接口(CSI)驱动程序。要使用此功能,必须在集群中安装
Azure 文件 CSI 驱动程序 ,
并且 CSIMigration
和 CSIMigrationAzureFile
特性门控
必须被启用。
Azure 文件 CSI 驱动尚不支持为同一卷设置不同的 fsgroup。
如果 AzureFile CSI 迁移被启用,用不同的 fsgroup 来使用同一卷也是不被支持的。
cephfs
cephfs
卷允许你将现存的 CephFS 卷挂载到 Pod 中。
不像 emptyDir
那样会在 Pod 被删除的同时也会被删除,cephfs
卷的内容在 Pod 被删除
时会被保留,只是卷被卸载了。这意味着 cephfs
卷可以被预先填充数据,且这些数据可以在
Pod 之间共享。同一 cephfs
卷可同时被多个写者挂载。
说明: 在使用 Ceph 卷之前,你的 Ceph 服务器必须已经运行并将要使用的 share 导出(exported)。
更多信息请参考 CephFS 示例 。
cinder
说明: Kubernetes 必须配置了 OpenStack Cloud Provider。
cinder
卷类型用于将 OpenStack Cinder 卷挂载到 Pod 中。
Cinder 卷示例配置
apiVersion : v1
kind : Pod
metadata :
name : test-cinder
spec :
containers :
- image : k8s.gcr.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 迁移
FEATURE STATE: Kubernetes v1.21 [beta]
Cinder 的 CSIMigration
功能在 Kubernetes 1.21 版本中是默认被启用的。
此特性会将插件的所有操作从现有的树内插件重定向到
cinder.csi.openstack.org
容器存储接口(CSI)驱动程序。
为了使用此功能,必须在集群中安装
OpenStack Cinder CSI 驱动程序 ,
你可以通过设置 CSIMigrationOpenStack
特性门控
为 false
来禁止 Cinder CSI 迁移。
如果你禁用了 CSIMigrationOpenStack
功能特性,则树内的 Cinder 卷插件
会负责 Cinder 卷存储管理的方方面面。
configMap
configMap
卷
提供了向 Pod 注入配置数据的方法。
ConfigMap 对象中存储的数据可以被 configMap
类型的卷引用,然后被 Pod 中运行的
容器化应用使用。
引用 configMap 对象时,你可以在 volume 中通过它的名称来引用。
你可以自定义 ConfigMap 中特定条目所要使用的路径。
下面的配置显示了如何将名为 log-config
的 ConfigMap 挂载到名为 configmap-pod
的 Pod 中:
apiVersion : v1
kind : Pod
metadata :
name : configmap-pod
spec :
containers :
- name : test
image : busybox
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 卷挂载方式使用 downwardAPI 时,将不能接收到它的更新。
更多详细信息请参考 downwardAPI
卷示例 。
emptyDir
当 Pod 分派到某个 Node 上时,emptyDir
卷会被创建,并且在 Pod 在该节点上运行期间,卷一直存在。
就像其名称表示的那样,卷最初是空的。
尽管 Pod 中的容器挂载 emptyDir
卷的路径可能相同也可能不同,这些容器都可以读写
emptyDir
卷中相同的文件。
当 Pod 因为某些原因被从节点上删除时,emptyDir
卷中的数据也会被永久删除。
说明: 容器崩溃并不 会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir
卷中的数据是安全的。
emptyDir
的一些用途:
缓存空间,例如基于磁盘的归并排序。
为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。
取决于你的环境,emptyDir
卷存储在该节点所使用的介质上;这里的介质可以是磁盘或 SSD
或网络存储。但是,你可以将 emptyDir.medium
字段设置为 "Memory"
,以告诉 Kubernetes
为你挂载 tmpfs(基于 RAM 的文件系统)。
虽然 tmpfs 速度非常快,但是要注意它与磁盘不同。
tmpfs 在节点重启时会被清除,并且你所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。
说明: 当启用
SizeMemoryBackedVolumes
特性门控 时,
你可以为基于内存提供的卷指定大小。
如果未指定大小,则基于内存的卷的大小为 Linux 主机上内存的 50%。
emptyDir 配置示例
apiVersion : v1
kind : Pod
metadata :
name : test-pd
spec :
containers :
- image : k8s.gcr.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /cache
name : cache-volume
volumes :
- name : cache-volume
emptyDir : {}
fc (光纤通道)
fc
卷类型允许将现有的光纤通道块存储卷挂载到 Pod 中。
可以使用卷配置中的参数 targetWWNs
来指定单个或多个目标 WWN(World Wide Names)。
如果指定了多个 WWN,targetWWNs 期望这些 WWN 来自多路径连接。
说明: 你必须配置 FC SAN Zoning,以便预先向目标 WWN 分配和屏蔽这些 LUN(卷),
这样 Kubernetes 主机才可以访问它们。
更多详情请参考 FC 示例 。
flocker (已弃用)
Flocker 是一个开源的、集群化的容器数据卷管理器。
Flocker 提供了由各种存储后端所支持的数据卷的管理和编排。
使用 flocker
卷可以将一个 Flocker 数据集挂载到 Pod 中。
如果数据集在 Flocker 中不存在,则需要首先使用 Flocker CLI 或 Flocker API 创建数据集。
如果数据集已经存在,那么 Flocker 将把它重新附加到 Pod 被调度的节点。
这意味着数据可以根据需要在 Pod 之间共享。
说明: 在使用 Flocker 之前你必须先安装运行自己的 Flocker。
更多详情请参考 Flocker 示例 。
gcePersistentDisk
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 : k8s.gcr.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 beta compute disks create --size= 500GB my-data-disk
--region us-central1
--replica-zones us-central1-a,us-central1-b
PersistentVolume 示例:
apiVersion : v1
kind : PersistentVolume
metadata :
name : test-volume
labels :
failure-domain.beta.kubernetes.io/zone : us-central1-a__us-central1-b
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 迁移
FEATURE STATE: Kubernetes v1.17 [beta]
启用 GCE PD 的 CSIMigration
功能后,所有插件操作将从现有的树内插件重定向到
pd.csi.storage.gke.io
容器存储接口( CSI )驱动程序。
为了使用此功能,必须在集群中上安装
GCE PD CSI驱动程序 ,
并且 CSIMigration
和 CSIMigrationGCE
Beta 功能必须被启用。
GCE CSI 迁移完成
FEATURE STATE: 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
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 : k8s.gcr.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 : k8s.gcr.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 之间共享。
注意: 在使用 NFS 卷之前,你必须运行自己的 NFS 服务器并将目标 share 导出备用。
要了解更多详情请参考 NFS 示例 。
persistentVolumeClaim
persistentVolumeClaim
卷用来将持久卷 (PersistentVolume)
挂载到 Pod 中。
持久卷申领(PersistentVolumeClaim)是用户在不知道特定云环境细节的情况下"申领"持久存储
(例如 GCE PersistentDisk 或者 iSCSI 卷)的一种方法。
更多详情请参考持久卷示例 。
portworxVolume
portworxVolume
是一个可伸缩的块存储层,能够以超融合(hyperconverged)的方式与 Kubernetes 一起运行。
Portworx 支持对服务器上存储的指纹处理、
基于存储能力进行分层以及跨多个服务器整合存储容量。
Portworx 可以以 in-guest 方式在虚拟机中运行,也可以在裸金属 Linux 节点上运行。
portworxVolume
类型的卷可以通过 Kubernetes 动态创建,也可以预先配备并在
Kubernetes Pod 内引用。
下面是一个引用预先配备的 PortworxVolume 的示例 Pod:
apiVersion : v1
kind : Pod
metadata :
name : test-portworx-volume-pod
spec :
containers :
- image : k8s.gcr.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 卷 。
projected
projected
卷类型能将若干现有的卷来源映射到同一目录上。
目前,可以映射的卷来源类型如下:
所有的卷来源需要和 Pod 处于相同的命名空间。
更多详情请参考一体化卷设计文档 。
包含 Secret、downwardAPI 和 configMap 的 Pod 示例
apiVersion : v1
kind : Pod
metadata :
name : volume-test
spec :
containers :
- name : container-test
image : busybox
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 的 Pod 示例:
apiVersion : v1
kind : Pod
metadata :
name : volume-test
spec :
containers :
- name : container-test
image : busybox
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
值。
当开启 TokenRequestProjection
功能时,可以将当前
服务帐号
的令牌注入 Pod 中的指定路径。
下面是一个例子:
apiVersion : v1
kind : Pod
metadata :
name : sa-token-test
spec :
containers :
- name : container-test
image : busybox
volumeMounts :
- name : token-vol
mountPath : "/service-account"
readOnly : true
volumes :
- name : token-vol
projected :
sources :
- serviceAccountToken :
audience : api
expirationSeconds : 3600
path : token
示例 Pod 具有包含注入服务帐户令牌的映射卷。
该令牌可以被 Pod 中的容器用来访问 Kubernetes API 服务器。
audience
字段包含令牌的预期受众。
令牌的接收者必须使用令牌的受众中指定的标识符来标识自己,否则应拒绝令牌。
此字段是可选的,默认值是 API 服务器的标识符。
expirationSeconds
是服务帐户令牌的有效期时长。
默认值为 1 小时,必须至少 10 分钟(600 秒)。
管理员还可以通过设置 API 服务器的 --service-account-max-token-expiration
选项来
限制其最大值。
path
字段指定相对于映射卷的挂载点的相对路径。
说明:
使用投射卷源作为 subPath 卷挂载的容器将不会接收这些卷源的更新。
quobyte (已弃用)
quobyte
卷允许将现有的 Quobyte 卷挂载到你的 Pod 中。
说明: 在使用 Quobyte 卷之前,你首先要进行安装 Quobyte 并创建好卷。
Quobyte 支持容器存储接口(CSI) 。
推荐使用 CSI 插件以在 Kubernetes 中使用 Quobyte 卷。
Quobyte 的 GitHub 项目包含以 CSI 形式部署 Quobyte 的
说明
及使用示例。
rbd
rbd
卷允许将 Rados 块设备 卷挂载到你的 Pod 中.
不像 emptyDir
那样会在删除 Pod 的同时也会被删除,rbd
卷的内容在删除 Pod 时
会被保存,卷只是被卸载。
这意味着 rbd
卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。
注意: 在使用 RBD 之前,你必须安装运行 Ceph。
RBD 的一个特性是它可以同时被多个用户以只读方式挂载。
这意味着你可以用数据集预先填充卷,然后根据需要在尽可能多的 Pod 中并行地使用卷。
不幸的是,RBD 卷只能由单个使用者以读写模式安装。不允许同时写入。
更多详情请参考
RBD 示例 。
secret
secret
卷用来给 Pod 传递敏感信息,例如密码。你可以将 Secret 存储在 Kubernetes
API 服务器上,然后以文件的形式挂在到 Pod 中,无需直接与 Kubernetes 耦合。
secret
卷由 tmpfs(基于 RAM 的文件系统)提供存储,因此它们永远不会被写入非易失性
(持久化的)存储器。
说明: 使用前你必须在 Kubernetes API 中创建 secret。
说明: 容器以
subPath 卷挂载方式挂载 Secret 时,将感知不到 Secret 的更新。
更多详情请参考配置 Secrets 。
storageOS (已弃用)
storageos
卷允许将现有的 StorageOS 卷挂载到你的 Pod 中。
StorageOS 在 Kubernetes 环境中以容器的形式运行,这使得应用能够从 Kubernetes
集群中的任何节点访问本地的或挂接的存储。为应对节点失效状况,可以复制数据。
若需提高利用率和降低成本,可以考虑瘦配置(Thin Provisioning)和数据压缩。
作为其核心能力之一,StorageOS 为容器提供了可以通过文件系统访问的块存储。
StorageOS 容器需要 64 位的 Linux,并且没有其他的依赖关系。
StorageOS 提供免费的开发者授权许可。
注意: 你必须在每个希望访问 StorageOS 卷的或者将向存储资源池贡献存储容量的节点上运行
StorageOS 容器。有关安装说明,请参阅
StorageOS 文档 。
apiVersion : v1
kind : Pod
metadata :
labels :
name : redis
role : master
name : test-storageos-redis
spec :
containers :
- name : master
image : kubernetes/redis:v1
env :
- name : MASTER
value : "true"
ports :
- containerPort : 6379
volumeMounts :
- mountPath : /redis-master-data
name : redis-data
volumes :
- name : redis-data
storageos :
# `redis-vol01` 卷必须在 StorageOS 中存在,并位于 `default` 名字空间内
volumeName : redis-vol01
fsType : ext4
关于 StorageOS 的进一步信息、动态供应和持久卷申领等等,请参考
StorageOS 示例 。
vsphereVolume
说明: 你必须配置 Kubernetes 的 vSphere 云驱动。云驱动的配置方法请参考
vSphere 使用指南 。
vsphereVolume
用来将 vSphere VMDK 卷挂载到你的 Pod 中。
在卸载卷时,卷的内容会被保留。
vSphereVolume 卷类型支持 VMFS 和 VSAN 数据仓库。
注意: 在挂载到 Pod 之前,你必须用下列方式之一创建 VMDK。
创建 VMDK 卷
选择下列方式之一创建 VMDK。
首先 ssh 到 ESX,然后使用下面的命令来创建 VMDK:
vmkfstools -c 2G /vmfs/volumes/DatastoreName/volumes/myDisk.vmdk
使用下面的命令创建 VMDK:
vmware-vdiskmanager -c -t 0 -s 40GB -a lsilogic myDisk.vmdk
vSphere VMDK 配置示例
apiVersion : v1
kind : Pod
metadata :
name : test-vmdk
spec :
containers :
- image : k8s.gcr.io/test-webserver
name : test-container
volumeMounts :
- mountPath : /test-vmdk
name : test-volume
volumes :
- name : test-volume
# 此 VMDK 卷必须已经存在
vsphereVolume :
volumePath : "[DatastoreName] volumes/myDisk"
fsType : ext4
进一步信息可参考
vSphere 卷 。
vSphere CSI 迁移
FEATURE STATE: Kubernetes v1.19 [beta]
当 vsphereVolume
的 CSIMigration
特性被启用时,所有插件操作都被从树内插件重定向到
csi.vsphere.vmware.com
CSI 驱动。
为了使用此功能特性,必须在集群中安装
vSphere CSI 驱动 ,
并启用 CSIMigration
和 CSIMigrationvSphere
特性门控 。
此特性还要求 vSphere vCenter/ESXi 的版本至少为 7.0u1,且 HW 版本至少为
VM version 15。
说明:
vSphere CSI 驱动不支持内置 vsphereVolume
的以下 StorageClass 参数:
diskformat
hostfailurestotolerate
forceprovisioning
cachereservation
diskstripes
objectspacereservation
iopslimit
使用这些参数创建的现有卷将被迁移到 vSphere CSI 驱动,不过使用 vSphere
CSI 驱动所创建的新卷都不会理会这些参数。
vSphere CSI 迁移完成
FEATURE STATE: 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
FEATURE STATE: 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
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) (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) 对象引用
使用一般性的临时卷
(Alpha 特性)
使用 CSI 临时卷 ,
前提是驱动支持这种用法(Beta 特性)
存储管理员可以使用以下字段来配置 CSI 持久卷:
driver
:指定要使用的卷驱动名称的字符串值。
这个值必须与 CSI 驱动程序在 GetPluginInfoResponse
中返回的值相对应;
该接口定义在 CSI 规范 中。
Kubernetes 使用所给的值来标识要调用的 CSI 驱动程序;CSI 驱动程序也使用该值来辨识
哪些 PV 对象属于该 CSI 驱动程序。
volumeHandle
:唯一标识卷的字符串值。
该值必须与 CSI 驱动在 CreateVolumeResponse
的 volume_id
字段中返回的值相对应;
接口定义在 CSI spec 中。
在所有对 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_attributes
字段传递给 CSI 驱动。
controllerPublishSecretRef
:对包含敏感信息的 Secret 对象的引用;
该敏感信息会被传递给 CSI 驱动来完成 CSI ControllerPublishVolume
和
ControllerUnpublishVolume
调用。
此字段是可选的;在不需要 Secret 时可以是空的。
如果 Secret 对象包含多个 Secret 条目,则所有的 Secret 条目都会被传递。
nodeStageSecretRef
:对包含敏感信息的 Secret 对象的引用。
该信息会传递给 CSI 驱动来完成 CSI NodeStageVolume
调用。
此字段是可选的,如果不需要 Secret,则可能是空的。
如果 Secret 对象包含多个 Secret 条目,则传递所有 Secret 条目。
nodePublishSecretRef
:对包含敏感信息的 Secret 对象的引用。
该信息传递给 CSI 驱动来完成 CSI NodePublishVolume
调用。
此字段是可选的,如果不需要 Secret,则可能是空的。
如果 Secret 对象包含多个 Secret 条目,则传递所有 Secret 条目。
CSI 原始块卷支持
FEATURE STATE: Kubernetes v1.18 [stable]
具有外部 CSI 驱动程序的供应商能够在 Kubernetes 工作负载中实现原始块卷支持。
你可以和以前一样,安装自己的
带有原始块卷支持的 PV/PVC ,
采用 CSI 对此过程没有影响。
CSI 临时卷
FEATURE STATE: Kubernetes v1.16 [beta]
你可以直接在 Pod 规约中配置 CSI 卷。采用这种方式配置的卷都是临时卷,
无法在 Pod 重新启动后继续存在。
进一步的信息可参阅临时卷 。
有关如何开发 CSI 驱动的更多信息,请参考 kubernetes-csi 文档 。
从树内插件迁移到 CSI 驱动程序
FEATURE STATE: Kubernetes v1.17 [beta]
启用 CSIMigration
功能后,针对现有树内插件的操作会被重定向到相应的 CSI 插件
(应已安装和配置)。
因此,操作员在过渡到取代树内插件的 CSI 驱动时,无需对现有存储类、PV 或 PVC
(指树内插件)进行任何配置更改。
所支持的操作和功能包括:配备(Provisioning)/删除、挂接(Attach)/解挂(Detach)、
挂载(Mount)/卸载(Unmount)和调整卷大小。
上面的卷类型 节列出了支持 CSIMigration
并已实现相应 CSI
驱动程序的树内插件。
flexVolume
FlexVolume 是一个自 1.2 版本(在 CSI 之前)以来在 Kubernetes 中一直存在的树外插件接口。
它使用基于 exec 的模型来与驱动程序对接。
用户必须在每个节点(在某些情况下是主控节点)上的预定义卷插件路径中安装
FlexVolume 驱动程序可执行文件。
Pod 通过 flexvolume
树内插件与 Flexvolume 驱动程序交互。
更多详情请参考 FlexVolume 示例。
挂载卷的传播
挂载卷的传播能力允许将容器安装的卷共享到同一 Pod 中的其他容器,
甚至共享到同一节点上的其他 Pod。
卷的挂载传播特性由 Container.volumeMounts
中的 mountPropagation
字段控制。
它的值包括:
HostToContainer
- 此卷挂载将会感知到主机后续针对此卷或其任何子目录的挂载操作。
换句话说,如果主机在此挂载卷中挂载任何内容,容器将能看到它被挂载在那里。
类似的,配置了 Bidirectional
挂载传播选项的 Pod 如果在同一卷上挂载了内容,
挂载传播设置为 HostToContainer
的容器都将能看到这一变化。
该模式等同于 Linux 内核文档
中描述的 rslave
挂载传播选项。
Bidirectional
- 这种卷挂载和 HostToContainer
挂载表现相同。
另外,容器创建的卷挂载将被传播回至主机和使用同一卷的所有 Pod 的所有容器。
该模式等同于 Linux 内核文档
中描述的 rshared
挂载传播选项。
警告: Bidirectional
形式的挂载传播可能比较危险。
它可以破坏主机操作系统,因此它只被允许在特权容器中使用。
强烈建议你熟悉 Linux 内核行为。
此外,由 Pod 中的容器创建的任何卷挂载必须在终止时由容器销毁(卸载)。
配置
在某些部署环境中,挂载传播正常工作前,必须在 Docker 中正确配置挂载共享(mount share),
如下所示。
编辑你的 Docker systemd
服务文件,按下面的方法设置 MountFlags
:
或者,如果存在 MountFlags=slave
就删除掉。然后重启 Docker 守护进程:
sudo systemctl daemon-reload
sudo systemctl restart docker
接下来
参考使用持久卷部署 WordPress 和 MySQL 示例。
3.6.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>
回收
当用户不再使用其存储卷时,他们可以从 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 : "k8s.gcr.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
通过在 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 申领
FEATURE STATE: Kubernetes v1.11 [beta]
现在,对扩充 PVC 申领的支持默认处于被启用状态。你可以扩充以下类型的卷:
gcePersistentDisk
awsElasticBlockStore
Cinder
glusterfs
rbd
Azure File
Azure Disk
Portworx
FlexVolumes
CSI
只有当 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 卷来满足此申领的请求。
与之相反,现有的卷会被调整大小。
CSI 卷的扩充
FEATURE STATE: Kubernetes v1.16 [beta]
对 CSI 卷的扩充能力默认是被启用的,不过扩充 CSI 卷要求 CSI 驱动支持
卷扩充操作。可参阅特定 CSI 驱动的文档了解更多信息。
重设包含文件系统的卷的大小
只有卷中包含的文件系统是 XFS、Ext3 或者 Ext4 时,你才可以重设卷的大小。
当卷中包含文件系统时,只有在 Pod 使用 ReadWrite
模式来使用 PVC 申领的
情况下才能重设其文件系统的大小。
文件系统扩充的操作或者是在 Pod 启动期间完成,或者在下层文件系统支持在线
扩充的前提下在 Pod 运行期间完成。
如果 FlexVolumes 的驱动将 RequiresFSResize
能力设置为 true
,则该
FlexVolume 卷可以在 Pod 重启期间调整大小。
重设使用中 PVC 申领的大小
FEATURE STATE: Kubernetes v1.15 [beta]
说明: Kubernetes 从 1.15 版本开始将调整使用中 PVC 申领大小这一能力作为 Beta
特性支持;该特性在 1.11 版本以来处于 Alpha 阶段。
ExpandInUsePersistentVolumes
特性必须被启用;在很多集群上,与此类似的
Beta 阶段的特性是自动启用的。
可参考
特性门控
文档了解更多信息。
在这种情况下,你不需要删除和重建正在使用某现有 PVC 的 Pod 或 Deployment。
所有使用中的 PVC 在其文件系统被扩充之后,立即可供其 Pod 使用。
此功能特性对于没有被 Pod 或 Deployment 使用的 PVC 而言没有效果。
你必须在执行扩展操作之前创建一个使用该 PVC 的 Pod。
与其他卷类型类似,FlexVolume 卷也可以在被 Pod 使用期间执行扩充操作。
说明: FlexVolume 卷的重设大小只能在下层驱动支持重设大小的时候才可进行。
说明: 扩充 EBS 卷的操作非常耗时。同时还存在另一个配额限制:
每 6 小时只能执行一次(尺寸)修改操作。
处理扩充卷过程中的失败
如果扩充下层存储的操作失败,集群管理员可以手动地恢复 PVC 申领的状态并
取消重设大小的请求。否则,在没有管理员干预的情况下,控制器会反复重试
重设大小的操作。
将绑定到 PVC 申领的 PV 卷标记为 Retain
回收策略;
删除 PVC 对象。由于 PV 的回收策略为 Retain
,我们不会在重建 PVC 时丢失数据。
删除 PV 规约中的 claimRef
项,这样新的 PVC 可以绑定到该卷。
这一操作会使得 PV 卷变为 "可用(Available)"。
使用小于 PV 卷大小的尺寸重建 PVC,设置 PVC 的 volumeName
字段为 PV 卷的名称。
这一操作将把新的 PVC 对象绑定到现有的 PV 卷。
不要忘记恢复 PV 卷上设置的回收策略。
持久卷的类型
PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件:
以下的持久卷已被弃用。这意味着当前仍是支持的,但是 Kubernetes 将来的发行版会将其移除。
旧版本的 Kubernetes 仍支持这些“树内(In-Tree)”持久卷类型:
photonPersistentDisk
- Photon 控制器持久化盘。(v1.15 之后 不可用 )
scaleIO
- ScaleIO 卷(v1.21 之后 不可用 )
持久卷
每个 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
属性来设置的。
参考 Kubernetes
资源模型(Resource Model)
设计提案,了解 capacity
字段可以接受的单位。
目前,存储大小是可以设置和请求的唯一资源。
未来可能会包含 IOPS、吞吐量等属性。
卷模式
FEATURE STATE: 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
重要提醒! 每个卷同一时刻只能以一种访问模式挂载,即使该卷能够支持
多种访问模式。例如,一个 GCEPersistentDisk 卷可以被某节点以 ReadWriteOnce
模式挂载,或者被多个节点以 ReadOnlyMany 模式挂载,但不可以同时以两种模式
挂载。
卷插件
ReadWriteOnce
ReadOnlyMany
ReadWriteMany
ReadWriteOncePod
AWSElasticBlockStore
✓
-
-
-
AzureFile
✓
✓
✓
-
AzureDisk
✓
-
-
-
CephFS
✓
✓
✓
-
Cinder
✓
-
-
-
CSI
取决于驱动
取决于驱动
取决于驱动
取决于驱动
FC
✓
✓
-
-
FlexVolume
✓
✓
取决于驱动
-
Flocker
✓
-
-
-
GCEPersistentDisk
✓
✓
-
-
Glusterfs
✓
✓
✓
-
HostPath
✓
-
-
-
iSCSI
✓
✓
-
-
Quobyte
✓
✓
✓
-
NFS
✓
✓
✓
-
RBD
✓
✓
-
-
VsphereVolume
✓
-
- (Pod 运行于同一节点上时可行)
-
PortworxVolume
✓
-
✓
-
StorageOS
✓
-
-
-
类
每个 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 (OpenStack 块存储)
GCEPersistentDisk
Glusterfs
NFS
Quobyte 卷
RBD (Ceph 块设备)
StorageOS
VsphereVolume
iSCSI
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 都只能绑定到未设置存储类的 PV 卷。
在这种情况下,未设置 storageClassName
的 PVC 与 storageClassName
设置未
""
的 PVC 的处理方式相同。
取决于安装方法,默认的 StorageClass 可能在集群安装期间由插件管理器(Addon
Manager)部署到集群中。
当某 PVC 除了请求 StorageClass 之外还设置了 selector
,则这两种需求会按
逻辑与关系处理:只有隶属于所请求类且带有所请求标签的 PV 才能绑定到 PVC。
说明: 目前,设置了非空 selector
的 PVC 对象无法让集群为其动态供应 PV 卷。
早前,Kubernetes 使用注解 volume.beta.kubernetes.io/storage-class
而不是
storageClassName
属性。这一注解目前仍然起作用,不过在将来的 Kubernetes
发布版本中该注解会被彻底废弃。
使用申领作为卷
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
卷示例 。
原始块卷支持
FEATURE STATE: 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
卷填充器(Populator)与数据源
FEATURE STATE: Kubernetes v1.22 [alpha]
说明: Kubernetes 支持自定义的卷填充器;Kubernetes 1.18 版本引入了这个 alpha 特性。
Kubernetes 1.22 使用重新设计的 API 重新实现了该机制。
确认你正在阅读与你的集群版本一致的 Kubernetes 文档。
要获知版本信息,请输入 kubectl version
.
要使用自定义的卷填充器,你必须为 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 上时,该控制器的职责是上报与卷创建有关的事件,以及在该过程中发生的问题。
在容器中添加原始块设备路径的 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 发行版本中仅支持静态供应的卷。
管理员需要在处理原始块设备时小心处理这些值。
对卷快照及从卷快照中恢复卷的支持
FEATURE STATE: Kubernetes v1.17 [beta]
卷快照(Volume Snapshot)功能的添加仅是为了支持 CSI 卷插件。
有关细节可参阅卷快照 文档。
要启用从卷快照数据源恢复数据卷的支持,可在 API 服务器和控制器管理器上启用
VolumeSnapshotDataSource
特性门控。
基于卷快照创建 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
编写可移植的配置
如果你要编写配置模板和示例用来在很多集群上运行并且需要持久性存储,建议你使用以下模式:
将 PersistentVolumeClaim 对象包含到你的配置包(Bundle)中,和 Deployment
以及 ConfigMap 等放在一起。
不要在配置中包含 PersistentVolume 对象,因为对配置进行实例化的用户很可能
没有创建 PersistentVolume 的权限。
为用户提供在实例化模板时指定存储类名称的能力。
仍按用户提供存储类名称,将该名称放到 persistentVolumeClaim.storageClassName
字段中。
这样会使得 PVC 在集群被管理员启用了存储类支持时能够匹配到正确的存储类,
如果用户未指定存储类名称,将 persistentVolumeClaim.storageClassName
留空(nil)。
这样,集群会使用默认 StorageClass
为用户自动供应一个存储卷。
很多集群环境都配置了默认的 StorageClass
,或者管理员也可以自行创建默认的
StorageClass
。
在你的工具链中,监测经过一段时间后仍未被绑定的 PVC 对象,要让用户知道这些对象,
因为这可能意味着集群不支持动态存储(因而用户必须先创建一个匹配的 PV),或者
集群没有配置存储系统(因而用户无法配置需要 PVC 的工作负载配置)。
接下来
API 参考
阅读以下页面中描述的 API:
3.6.3 - CSI 卷克隆
本文档介绍 Kubernetes 中克隆现有 CSI 卷的概念。阅读前建议先熟悉
卷 。
介绍
CSI 卷克隆功能增加了通过在
dataSource
字段中指定存在的
PVC ,
来表示用户想要克隆的 卷(Volume) 。
克隆(Clone),意思是为已有的 Kubernetes 卷创建副本,它可以像任何其它标准卷一样被使用。
唯一的区别就是配置后,后端设备将创建指定完全相同的副本,而不是创建一个“新的”空卷。
从 Kubernetes API 的角度看,克隆的实现只是在创建新的 PVC 时,
增加了指定一个现有 PVC 作为数据源的能力。源 PVC 必须是 bound
状态且可用的(不在使用中)。
用户在使用该功能时,需要注意以下事项:
克隆支持(VolumePVCDataSource
)仅适用于 CSI 驱动。
克隆支持仅适用于 动态供应器。
CSI 驱动可能实现,也可能未实现卷克隆功能。
仅当 PVC 与目标 PVC 存在于同一命名空间(源和目标 PVC 必须在相同的命名空间)时,才可以克隆 PVC。
仅在同一存储类中支持克隆。
目标卷必须和源卷具有相同的存储类
可以使用默认的存储类并且 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.6.4 - 卷快照类
本文档描述了 Kubernetes 中 VolumeSnapshotClass 的概念。建议熟悉
卷快照(Volume Snapshots) 和
存储类(Storage Class) 。
介绍
就像 StorageClass 为管理员提供了一种在配置卷时描述存储“类”的方法,
VolumeSnapshotClass 提供了一种在配置卷快照时描述存储“类”的方法。
VolumeSnapshotClass 资源
每个 VolumeSnapshotClass 都包含 driver
、deletionPolicy
和 parameters
字段,
在需要动态配置属于该类的 VolumeSnapshot 时使用。
VolumeSnapshotClass 对象的名称很重要,是用户可以请求特定类的方式。
管理员在首次创建 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.6.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 会使用它们被创建时指定的回收政策。
允许卷扩展
FEATURE STATE: 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
模式:
FEATURE STATE: 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
允许的拓扑结构
FEATURE STATE: Kubernetes v1.12 [beta]
当集群操作人员使用了 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-central1-a
- us-central1-b
参数
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 文档。
说明:
zone
和 zones
已被弃用并被 允许的拓扑结构 取代。
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 集群节点
所在的活动区域中轮转调度。
说明:
FEATURE STATE: Kubernetes 1.11 [deprecated]
OpenStack 的内部驱动已经被弃用。请使用
OpenStack 的外部云驱动 。
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
。默认是 "",没有功能打开。
Quobyte
FEATURE STATE: Kubernetes v1.22 [deprecated]
Quobyte 树内(in-tree)存储插件已弃用,
你可以在 Quobyte CSI 仓库中找到用于树外(out-of-tree)Quobyte 插件的 StorageClass
示例 。
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : slow
provisioner : kubernetes.io/quobyte
parameters :
quobyteAPIServer : "http://138.68.74.142:7860"
registry : "138.68.74.142:7861"
adminSecretName : "quobyte-admin-secret"
adminSecretNamespace : "kube-system"
user : "root"
group : "root"
quobyteConfig : "BASE"
quobyteTenant : "DEFAULT"
quobyteAPIServer
:Quobyte API 服务器的格式是 "http(s)://api-server:7860"
registry
:用于挂载卷的 Quobyte 仓库。你可以指定仓库为 <host>:<port>
或者如果你想指定多个 registry,在它们之间添加逗号,例如
<host1>:<port>,<host2>:<port>,<host3>:<port>
。
主机可以是一个 IP 地址,或者如果你有正在运行的 DNS,你也可以提供 DNS 名称。
adminSecretNamespace
:adminSecretName
的名字空间。
默认值是 "default"。
user
:对这个用户映射的所有访问权限。默认是 "root"。
group
:对这个组映射的所有访问权限。默认是 "nfsnobody"。
quobyteConfig
:使用指定的配置来创建卷。你可以创建一个新的配置,
或者,可以修改 Web 控制台或 quobyte CLI 中现有的配置。默认是 "BASE"。
quobyteTenant
:使用指定的租户 ID 创建/删除卷。这个 Quobyte 租户必须
已经于 Quobyte 中存在。默认是 "DEFAULT"。
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
。
ScaleIO
kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : slow
provisioner : kubernetes.io/scaleio
parameters :
gateway : https://192.168.99.200:443/api
system : scaleio
protectionDomain : pd0
storagePool : sp1
storageMode : ThinProvisioned
secretRef : sio-secret
readOnly : false
fsType : xfs
provisioner
:属性设置为 kubernetes.io/scaleio
gateway
到 ScaleIO API 网关的地址(必需)
system
:ScaleIO 系统的名称(必需)
protectionDomain
:ScaleIO 保护域的名称(必需)
storagePool
:卷存储池的名称(必需)
storageMode
:存储提供模式:ThinProvisioned
(默认)或 ThickProvisioned
secretRef
:对已配置的 Secret 对象的引用(必需)
readOnly
:指定挂载卷的访问模式(默认为 false)
fsType
:卷的文件系统(默认是 ext4)
ScaleIO Kubernetes 卷插件需要配置一个 Secret 对象。
Secret 必须用 kubernetes.io/scaleio
类型创建,并与引用它的
PVC 所属的名称空间使用相同的值。如下面的命令所示:
kubectl create secret generic sio-secret --type= "kubernetes.io/scaleio" \
--from-literal= username = sioadmin --from-literal= password = d2NABDNjMA == \
--namespace= default
StorageOS
apiVersion : storage.k8s.io/v1
kind : StorageClass
metadata :
name : fast
provisioner : kubernetes.io/storageos
parameters :
pool : default
description : Kubernetes volume
fsType : ext4
adminSecretNamespace : default
adminSecretName : storageos-secret
pool
:制备卷的 StorageOS 分布式容量池的名称。如果未指定,则使用
通常存在的 default
池。
description
:指定给动态创建的卷的描述。所有卷描述对于存储类而言都是相同的,
但不同的 storage class 可以使用不同的描述,以区分不同的使用场景。
默认为 Kubernetes volume
。
fsType
:请求的默认文件系统类型。
请注意,在 StorageOS 中用户定义的规则可以覆盖此值。默认为 ext4
adminSecretNamespace
:API 配置 secret 所在的命名空间。
如果设置了 adminSecretName,则是必需的。
adminSecretName
:用于获取 StorageOS API 凭证的 secret 名称。
如果未指定,则将尝试默认值。
StorageOS Kubernetes 卷插件可以使 Secret 对象来指定用于访问 StorageOS API 的端点和凭据。
只有当默认值已被更改时,这才是必须的。
Secret 必须使用 kubernetes.io/storageos
类型创建,如以下命令:
kubectl create secret generic storageos-secret \
--type= "kubernetes.io/storageos" \
--from-literal= apiAddress = tcp://localhost:5705 \
--from-literal= apiUsername = storageos \
--from-literal= apiPassword = storageos \
--namespace= default
用于动态制备卷的 Secret 可以在任何名称空间中创建,并通过
adminSecretNamespace
参数引用。
预先配置的卷使用的 Secret 必须在与引用它的 PVC 在相同的名称空间中。
本地
FEATURE STATE: Kubernetes v1.14 [stable]
kind : StorageClass
apiVersion : storage.k8s.io/v1
metadata :
name : local-storage
provisioner : kubernetes.io/no-provisioner
volumeBindingMode : WaitForFirstConsumer
本地卷还不支持动态制备,然而还是需要创建 StorageClass 以延迟卷绑定,
直到完成 Pod 的调度。这是由 WaitForFirstConsumer
卷绑定模式指定的。
延迟卷绑定使得调度器在为 PersistentVolumeClaim 选择一个合适的
PersistentVolume 时能考虑到所有 Pod 的调度限制。
3.6.6 - 动态卷供应
动态卷供应允许按需创建存储卷。
如果没有动态供应,集群管理员必须手动地联系他们的云或存储提供商来创建新的存储卷,
然后在 Kubernetes 集群创建
PersistentVolume
对象 来表示这些卷。
动态供应功能消除了集群管理员预先配置存储的需要。 相反,它在用户请求时自动供应存储。
背景
动态卷供应的实现基于 storage.k8s.io
API 组中的 StorageClass
API 对象。
集群管理员可以根据需要定义多个 StorageClass
对象,每个对象指定一个卷插件 (又名 provisioner ),
卷插件向卷供应商提供在创建卷时需要的数据卷信息及相关参数。
集群管理员可以在集群中定义和公开多种存储(来自相同或不同的存储系统),每种都具有自定义参数集。
该设计也确保终端用户不必担心存储供应的复杂性和细微差别,但仍然能够从多个存储选项中进行选择。
点击这里 查阅有关存储类的更多信息。
启用动态卷供应
要启用动态供应功能,集群管理员需要为用户预先创建一个或多个 StorageClass
对象。
StorageClass
对象定义当动态供应被调用时,哪一个驱动将被使用和哪些参数将被传递给驱动。
以下清单创建了一个 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.6.7 - 卷快照
FEATURE STATE: Kubernetes 1.17 [beta]
在 Kubernetes 中,卷快照是一个存储系统上卷的快照,本文假设你已经熟悉了 Kubernetes
的 持久卷 。
介绍
与 PersistentVolume
和 PersistentVolumeClaim
两个 API 资源用于给用户和管理员提供卷类似,VolumeSnapshotContent
和 VolumeSnapshot
两个 API 资源用于给用户和管理员创建卷快照。
VolumeSnapshotContent
是一种快照,从管理员已提供的集群中的卷获取。就像持久卷是集群的资源一样,它也是集群中的资源。
VolumeSnapshot
是用户对于卷的快照的请求。它类似于持久卷声明。
VolumeSnapshotClass
允许指定属于 VolumeSnapshot
的不同属性。在从存储系统的相同卷上获取的快照之间,这些属性可能有所不同,因此不能通过使用与 PersistentVolumeClaim
相同的 StorageClass
来表示。
卷快照能力为 Kubernetes 用户提供了一种标准的方式来在指定时间点
复制卷的内容,并且不需要创建全新的卷。例如,这一功能使得数据库管理员
能够在执行编辑或删除之类的修改之前对数据库执行备份。
当使用该功能时,用户需要注意以下几点:
API 对象 VolumeSnapshot
,VolumeSnapshotContent
和 VolumeSnapshotClass
是 CRDs ,
不属于核心 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 负责 CRDs 和快照控制器的安装。
卷快照和卷快照内容的生命周期
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 和一个状态。
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
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
volumeSnapshotRef :
name : new-snapshot-test
namespace : default
snapshotHandle
是存储后端创建卷的唯一标识符。对于预设置快照,这个字段是必须的。它指定此 VolumeSnapshotContent
表示的存储系统上的 CSI 快照 id。
从快照供应卷
你可以配置一个新卷,该卷预填充了快照中的数据,在 持久卷声明
对象中使用 dataSource 字段。
更多详细信息,请参阅
卷快照和从快照还原卷 。
3.6.8 - 存储容量
存储容量是有限的,并且会因为运行 Pod 的节点不同而变化:
网络存储可能并非所有节点都能够访问,或者对于某个节点存储是本地的。
FEATURE STATE: Kubernetes v1.19 [alpha]
FEATURE STATE: Kubernetes v1.21 [beta]
本页面描述了 Kubernetes 如何跟踪存储容量以及调度程序如何为了余下的尚未挂载的卷使用该信息将
Pod 调度到能够访问到足够存储容量的节点上。
如果没有跟踪存储容量,调度程序可能会选择一个没有足够容量来提供卷的节点,并且需要多次调度重试。
容器存储接口 (CSI)驱动程序支持跟踪存储容量,
并且在安装 CSI 驱动程序时需要启用 该功能。
API
这个特性有两个 API 扩展接口:
CSIStorageCapacity 对象:这些对象由 CSI 驱动程序在安装驱动程序的命名空间中产生。
每个对象都包含一个存储类的容量信息,并定义哪些节点可以访问该存储。
CSIDriverSpec.StorageCapacity
字段 :
设置为 true 时,Kubernetes 调度程序将考虑使用 CSI 驱动程序的卷的存储容量。
调度
如果有以下情况,存储容量信息将会被 Kubernetes 调度程序使用:
CSIStorageCapacity
特性门控被设置为 true,
Pod 使用的卷还没有被创建,
卷使用引用了 CSI 驱动的 StorageClass ,
并且使用了 WaitForFirstConsumer
卷绑定模式 ,
驱动程序的 CSIDriver
对象的 StorageCapacity
被设置为 true。
在这种情况下,调度程序仅考虑将 Pod 调度到有足够存储容量的节点上。这个检测非常简单,
仅将卷的大小与 CSIStorageCapacity
对象中列出的容量进行比较,并使用包含该节点的拓扑。
对于具有 Immediate
卷绑定模式的卷,存储驱动程序将决定在何处创建该卷,而不取决于将使用该卷的 Pod。
然后,调度程序将 Pod 调度到创建卷后可使用该卷的节点上。
对于 CSI 临时卷 ,调度总是在不考虑存储容量的情况下进行。
这是基于这样的假设:该卷类型仅由节点本地的特殊 CSI 驱动程序使用,并且不需要大量资源。
重新调度
当为带有 WaitForFirstConsumer
的卷的 Pod 来选择节点时,该决定仍然是暂定的。
下一步是要求 CSI 存储驱动程序创建卷,并提示该卷在被选择的节点上可用。
因为 Kubernetes 可能会根据已经过时的存储容量信息来选择一个节点,因此可能无法真正创建卷。
然后就会重置节点选择,Kubernetes 调度器会再次尝试为 Pod 查找节点。
限制
存储容量跟踪增加了调度器第一次尝试即成功的机会,但是并不能保证这一点,因为调度器必须根据可能过期的信息来进行决策。
通常,与没有任何存储容量信息的调度相同的重试机制可以处理调度失败。
当 Pod 使用多个卷时,调度可能会永久失败:一个卷可能已经在拓扑段中创建,而该卷又没有足够的容量来创建另一个卷,
要想从中恢复,必须要进行手动干预,比如通过增加存储容量或者删除已经创建的卷。
需要进一步工作 来自动处理此问题。
开启存储容量跟踪
存储容量跟踪是一个 Beta 特性,从 Kubernetes 1.21 版本起在 Kubernetes 集群
中默认被启用。除了在集群中启用此功能特性之外,还要求 CSI 驱动支持此特性。
请参阅驱动的文档了解详细信息。
接下来
3.6.9 - 临时卷
本文档描述 Kubernetes 中的 临时卷(Ephemeral Volume) 。
建议先了解卷 ,特别是 PersistentVolumeClaim 和 PersistentVolume。
有些应用程序需要额外的存储,但并不关心数据在重启后仍然可用,既是否被持久地保存。
例如,缓存服务经常受限于内存大小,将不常用的数据转移到比内存慢、但对总体性能的影响很小的存储中。
另有些应用程序需要以文件形式注入的只读数据,比如配置数据或密钥。
临时卷 就是为此类用例设计的。因为卷会遵从 Pod 的生命周期,与 Pod 一起创建和删除,
所以停止和重新启动 Pod 时,不会受持久卷在何处可用的限制。
临时卷在 Pod 规范中以 内联 方式定义,这简化了应用程序的部署和管理。
临时卷的类型
Kubernetes 为了不同的目的,支持几种不同类型的临时卷:
emptyDir
、configMap
、downwardAPI
、secret
是作为
本地临时存储
提供的。它们由各个节点上的 kubelet 管理。
CSI 临时卷 必须 由第三方 CSI 存储驱动程序提供。
通用临时卷 可以 由第三方 CSI 存储驱动程序提供,也可以由支持动态配置的任何其他存储驱动程序提供。
一些专门为 CSI 临时卷编写的 CSI 驱动程序,不支持动态供应:因此这些驱动程序不能用于通用临时卷。
使用第三方驱动程序的优势在于,它们可以提供 Kubernetes 本身不支持的功能,
例如,与 kubelet 管理的磁盘具有不同运行特征的存储,或者用来注入不同的数据
CSI 临时卷
FEATURE STATE: Kubernetes v1.16 [beta]
该特性需要启用参数 CSIInlineVolume
特性门控(feature gate) 。
该参数从 Kubernetes 1.16 开始默认启用。
说明: 只有一部分 CSI 驱动程序支持 CSI 临时卷。Kubernetes CSI
驱动程序列表
显示了支持临时卷的驱动程序。
从概念上讲,CSI 临时卷类似于 configMap
、downwardAPI
和 secret
类型的卷:
其存储在每个节点本地管理,并在将 Pod 调度到节点后与其他本地资源一起创建。
在这个阶段,Kubernetes 没有重新调度 Pods 的概念。卷创建不太可能失败,否则 Pod 启动将会受阻。
特别是,这些卷 不 支持感知存储容量的 Pod 调度 。
它们目前也没包括在 Pod 的存储资源使用限制中,因为 kubelet 只能对它自己管理的存储强制执行。
下面是使用 CSI 临时存储的 Pod 的示例清单:
kind : Pod
apiVersion : v1
metadata :
name : my-csi-app
spec :
containers :
- name : my-frontend
image : busybox
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 驱动程序的文档。
作为一个集群管理员,你可以使用
PodSecurityPolicy
来控制在 Pod 中可以使用哪些 CSI 驱动程序,
具体则是通过 allowedCSIDrivers
字段
指定。
通用临时卷
FEATURE STATE: Kubernetes v1.21 [beta]
这个特性需要启用 GenericEphemeralVolume
特性门控 。
因为这是一个 beta 特性,默认情况下启用。
通用临时卷类似于 emptyDir
卷,因为它为每个 Pod 提供临时数据存放目录,
在最初制备完毕时一般为空。不过通用临时卷也有一些额外的功能特性:
存储可以是本地的,也可以是网络连接的。
卷可以有固定的大小,pod不能超量使用。
卷可能有一些初始数据,这取决于驱动程序和参数。
当驱动程序支持,卷上的典型操作将被支持,包括
(快照 、
克隆 、
调整大小 和
存储容量跟踪 )。
示例:
kind : Pod
apiVersion : v1
metadata :
name : my-app
spec :
containers :
- name : my-frontend
image : busybox
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 的命名
自动创建的 PVCs 的命名是确定的:此名称是 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 就无法启动。
注意: 当命名 Pods 和卷出现在同一个命名空间中时,要小心,以防止发生此类冲突。
安全
启用 GenericEphemeralVolume 特性会导致那些没有 PVCs 创建权限的用户,
在创建 Pods 时,被允许间接的创建 PVCs。
集群管理员必须意识到这一点。
如果这不符合他们的安全模型,他们有如下选择:
通过特性门控显式禁用该特性。
当 volumes
列表不包含 ephemeral
卷类型时,使用
Pod 安全策略 。
(这一方式在 Kubernetes 1.21 版本已经弃用)
使用一个准入 Webhook
拒绝包含通用临时卷的 Pods。
为 PVC 卷所设置的逐名字空间的配额
仍然有效,因此即使允许用户使用这种新机制,他们也不能使用它来规避其他策略。
接下来
kubelet 管理的临时卷
参阅本地临时存储 。
CSI 临时卷
通用临时卷
3.6.10 - 卷健康监测
FEATURE STATE: 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 上触发一个事件。
说明: 你需要启用
CSIVolumeHealth
特性门控
,才能从节点测使用此特性。
接下来
参阅 CSI 驱动程序文档 ,
可以找出有那些 CSI 驱动程序已实现了此特性。
3.6.11 - 特定于节点的卷数限制
此页面描述了各个云供应商可关联至一个节点的最大卷数。
谷歌、亚马逊和微软等云供应商通常对可以关联到节点的卷数量进行限制。
Kubernetes 需要尊重这些限制。 否则,在节点上调度的 Pod 可能会卡住去等待卷的关联。
Kubernetes 的默认限制
The Kubernetes 调度器对关联于一个节点的卷数有默认限制:
自定义限制
您可以通过设置 KUBE_MAX_PD_VOLS
环境变量的值来设置这些限制,然后再启动调度器。
CSI 驱动程序可能具有不同的过程,关于如何自定义其限制请参阅相关文档。
如果设置的限制高于默认限制,请谨慎使用。请参阅云提供商的文档以确保节点可支持您设置的限制。
此限制应用于整个集群,所以它会影响所有节点。
动态卷限制
FEATURE STATE: Kubernetes v1.17 [stable]
以下卷类型支持动态卷限制。
Amazon EBS
Google Persistent Disk
Azure Disk
CSI
对于由内建插件管理的卷,Kubernetes 会自动确定节点类型并确保节点上可关联的卷数目合规。 例如:
3.7 - 配置
Kubernetes 为配置 Pods 提供的资源。
3.7.1 - 配置最佳实践
本文档重点介绍并整合了整个用户指南、入门文档和示例中介绍的配置最佳实践。
这是一份不断改进的文件。
如果您认为某些内容缺失但可能对其他人有用,请不要犹豫,提交 Issue 或提交 PR。
一般配置提示
在推送到集群之前,配置文件应存储在版本控制中。
这允许您在必要时快速回滚配置更改。
它还有助于集群重新创建和恢复。
使用 YAML 而不是 JSON 编写配置文件。虽然这些格式几乎可以在所有场景中互换使用,但 YAML 往往更加用户友好。
另请注意,可以在目录上调用许多kubectl
命令。
例如,你可以在配置文件的目录中调用kubectl apply
。
除非必要,否则不指定默认值:简单的最小配置会降低错误的可能性。
“Naked” Pods 与 ReplicaSet,Deployment 和 Jobs
Deployment 既可以创建一个 ReplicaSet 来确保预期个数的 Pod 始终可用,也可以指定替换 Pod 的策略(例如
RollingUpdate )。
除了一些显式的 restartPolicy: Never
场景外,Deployment 通常比直接创建 Pod 要好得多。Job 也可能是合适的选择。
服务
这确实意味着在顺序上的要求 - 必须在 Pod
本身被创建之前创建 Pod
想要访问的任何 Service
,
否则将环境变量不会生效。DNS 没有此限制。
一个可选(尽管强烈推荐)的集群插件
是 DNS 服务器。DNS 服务器为新的 Services
监视 Kubernetes API,并为每个创建一组 DNS 记录。
如果在整个集群中启用了 DNS,则所有 Pods
应该能够自动对 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: myapp, tier: frontend, phase: test, deployment: v3 }
。
你可以使用这些标签为其他资源选择合适的 Pod;
例如,一个选择所有 tier: frontend
Pod 的服务,或者 app: myapp
的所有 phase: test
组件。
有关此方法的示例,请参阅guestbook 。
通过从选择器中省略特定发行版的标签,可以使服务跨越多个 Deployment。
当你需要不停机的情况下更新正在运行的服务,可以使用Deployment 。
Deployment 描述了对象的期望状态,并且如果对该规范的更改被成功应用,
则 Deployment 控制器以受控速率将实际状态改变为期望状态。
您可以操纵标签进行调试。
由于 Kubernetes 控制器(例如 ReplicaSet)和服务使用选择器标签来匹配 Pod,
从 Pod 中删除相关标签将阻止其被控制器考虑或由服务提供服务流量。
如果删除现有 Pod 的标签,其控制器将创建一个新的 Pod 来取代它。
这是在"隔离"环境中调试先前"活跃"的 Pod 的有用方法。
要以交互方式删除或添加标签,请使用 kubectl label
。
容器镜像
imagePullPolicy 和镜像标签会影响
kubelet 何时尝试拉取指定的镜像。
imagePullPolicy: IfNotPresent
:仅当镜像在本地不存在时才被拉取。
imagePullPolicy: Always
:每次启动 Pod 的时候都会拉取镜像。
imagePullPolicy
省略时,镜像标签为 :latest
或不存在,其值自动被设置为 Always
。注意,如果镜像标签的值发生改变,imagePullPolicy
的值不会被更新为 IfNotPresent
。
imagePullPolicy
省略时,指定镜像标签并且不是 :latest
,其值自动被设置为 IfNotPresent
。注意,如果镜像标签的值之后被移除或者修改为 latest
,imagePullPolicy
的值不会被更新为 Always
。
imagePullPolicy: Never
:假设镜像已经存在本地,不会尝试拉取镜像。
说明: 要确保容器始终使用相同版本的镜像,你可以指定其
摘要 ,
例如
sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
。
摘要唯一地标识出镜像的指定版本,因此除非您更改摘要值,否则 Kubernetes 永远不会更新它。
说明: 在生产中部署容器时应避免使用 :latest
标记,因为这样更难跟踪正在运行的镜像版本,并且更难以正确回滚。
说明: 只要镜像仓库是可访问的,底层镜像驱动程序的缓存语义能够使即便 imagePullPolicy: Always
的配置也很高效。
例如,对于 Docker,如果镜像已经存在,则拉取尝试很快,因为镜像层都被缓存并且不需要下载。
使用 kubectl
使用 kubectl apply -f <directory>
。
它在 <directory>
中的所有 .yaml
、.yml
和 .json
文件中查找 Kubernetes 配置,并将其传递给 apply
。
使用标签选择器进行 get
和 delete
操作,而不是特定的对象名称。
请参阅标签选择器 和
有效使用标签 部分。
3.7.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 必须要在同一个
名字空间 中。
这是一个 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:
apiVersion : v1
kind : Pod
metadata :
name : configmap-demo-pod
spec :
containers :
- name : demo
image : alpine
command : ["sleep" , "3600" ]
env :
# 定义环境变量
- name : PLAYER_INITIAL_LIVES # 请注意这里和 ConfigMap 中的键名是不一样的
valueFrom :
configMapKeyRef :
name : game-demo # 这个值来自 ConfigMap
key : player_initial_lives # 需要取值的键
- name : UI_PROPERTIES_FILE_NAME
valueFrom :
configMapKeyRef :
name : game-demo
key : ui_properties_file_name
volumeMounts :
- name : config
mountPath : "/config"
readOnly : true
volumes :
# 你可以在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中
- name : config
configMap :
# 提供你想要挂载的 ConfigMap 的名字
name : game-demo
# 来自 ConfigMap 的一组键,将被创建为文件
items :
- key : "game.properties"
path : "game.properties"
- key : "user-interface.properties"
path : "user-interface.properties"
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 当做文件使用
创建一个 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
FEATURE STATE: Kubernetes v1.21 [stable]
Kubernetes 特性 不可变更的 Secret 和 ConfigMap 提供了一种将各个
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.7.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 启用静态加密 ;
启用或配置 RBAC 规则 来限制读取 Secret 的数据(包括通过间接方式)。
在适当的情况下,还可以使用 RBAC 等机制来限制允许哪些主体创建新 Secret 或替换现有 Secret。
Secret 概览
要使用 Secret,Pod 需要引用 Secret。
Pod 可以用三种方式之一来使用 Secret:
Kubernetes 控制平面也使用 Secret;
例如,引导令牌 Secret
是一种帮助自动化节点注册的机制。
Secret 对象的名称必须是合法的 DNS 子域名 。
在为创建 Secret 编写配置文件时,你可以设置 data
与/或 stringData
字段。
data
和 stringData
字段都是可选的。data
字段中所有键值都必须是 base64
编码的字符串。如果不希望执行这种 base64 字符串的转换操作,你可以选择设置
stringData
字段,其中可以使用任何字符串作为其取值。
Secret 的类型
创建 Secret 时,你可以使用 Secret 资源的 type
字段,
或者与其等价的 kubectl
命令行参数(如果有的话)为其设置类型。
Secret 的 type
有助于对不同类型机密数据的编程处理。
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 并不对类型的名称作任何限制。不过,如果你要使用内置类型之一,
则你必须满足为该类型所定义的所有要求。
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 用来存放标识某
服务账号的令牌。使用这种 Secret 类型时,你需要确保对象的注解
kubernetes.io/service-account-name
被设置为某个已有的服务账号名称。
某个 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==
Kubernetes 在创建 Pod 时会自动创建一个服务账号 Secret 并自动修改你的 Pod
以使用该 Secret。该服务账号令牌 Secret 中包含了访问 Kubernetes API
所需要的凭据。
如果需要,可以禁止或者重载这种自动创建并使用 API 凭据的操作。
不过,如果你仅仅是希望能够安全地访问 API 服务器,这是建议的工作方式。
参考 ServiceAccount
文档了解服务账号的工作原理。你也可以查看
Pod
资源中的 automountServiceAccountToken
和 serviceAccountName
字段文档,了解
从 Pod 中引用服务账号。
Docker 配置 Secret
你可以使用下面两种 type
值之一来创建 Secret,用以存放访问 Docker 仓库
来下载镜像的凭据。
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 配置文件内容。
kubectl create secret docker-registry secret-tiger-docker \
--docker-username= tiger \
--docker-password= pass113 \
--docker-email= [email protected]
上面的命令创建一个类型为 kubernetes.io/dockerconfigjson
的 Secret。
如果你对 data
字段中的 .dockerconfigjson
内容进行转储,你会得到下面的
JSON 内容,而这一内容是一个合法的 Docker 配置文件。
{
"auths" : {
"https://index.docker.io/v1/" : {
"username" : "tiger" ,
"password" : "pass113" ,
"email" : "[email protected] " ,
"auth" : "dGlnZXI6cGFzczExMw=="
}
}
}
基本身份认证 Secret
kubernetes.io/basic-auth
类型用来存放用于基本身份认证所需的凭据信息。
使用这种 Secret 类型时,Secret 的 data
字段必须包含以下两个键:
username
: 用于身份认证的用户名;
password
: 用于身份认证的密码或令牌。
以上两个键的键值都是 base64 编码的字符串。
当然你也可以在创建 Secret 时使用 stringData
字段来提供明文形式的内容。
下面的 YAML 是基本身份认证 Secret 的一个示例清单:
apiVersion : v1
kind : Secret
metadata :
name : secret-basic-auth
type : kubernetes.io/basic-auth
stringData :
username : admin
password : t0p-Secret
提供基本身份认证类型的 Secret 仅仅是出于用户方便性考虑。
你也可以使用 Opaque
类型来保存用于基本身份认证的凭据。
不过,使用内置的 Secret 类型的有助于对凭据格式进行归一化处理,并且
API 服务器确实会检查 Secret 配置中是否提供了所需要的主键。
SSH 身份认证 Secret
Kubernetes 所提供的内置类型 kubernetes.io/ssh-auth
用来存放 SSH 身份认证中
所需要的凭据。使用这种 Secret 类型时,你就必须在其 data
(或 stringData
)
字段中提供一个 ssh-privatekey
键值对,作为要使用的 SSH 凭据。
下面的 YAML 是一个 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 类型的有助于对凭据格式进行归一化处理,并且
API 服务器确实会检查 Secret 配置中是否提供了所需要的主键。
注意: SSH 私钥自身无法建立 SSH 客户端与服务器端之间的可信连接。
需要其它方式来建立这种信任关系,以缓解“中间人(Man In The Middle)”
攻击,例如向 ConfigMap 中添加一个 known_hosts
文件。
TLS Secret
Kubernetes 提供一种内置的 kubernetes.io/tls
Secret 类型,用来存放证书
及其相关密钥(通常用在 TLS 场合)。
此类数据主要提供给 Ingress 资源,用以终结 TLS 链接,不过也可以用于其他
资源或者负载。当使用此类型的 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
的公钥证书必须是 .PEM 编码的
(Base64 编码的 DER 格式),且与 --key
所给定的私钥匹配。
私钥必须是通常所说的 PEM 私钥格式,且未加密。对这两个文件而言,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
有几种不同的方式来创建 Secret:
编辑 Secret
你可以通过下面的命令编辑现有的 Secret:
kubectl edit secrets mysecret
这一命令会打开默认的编辑器,允许你更新 data
字段中包含的
base64 编码的 Secret 值:
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion : v1
data :
username : YWRtaW4=
password : MWYyZDFlMmU2N2Rm
kind : Secret
metadata :
annotations :
kubectl.kubernetes.io/last-applied-configuration : { ... }
creationTimestamp : 2016-01-22T18:41:56Z
name : mysecret
namespace : default
resourceVersion : "164619"
uid : cfee02d6-c137-11e5-8d73-42010af00002
type : Opaque
使用 Secret
Secret 可以作为数据卷被挂载,或作为环境变量
暴露出来以供 Pod 中的容器使用。它们也可以被系统的其他部分使用,而不直接暴露在 Pod 内。
例如,它们可以保存凭据,系统的其他部分将用它来代表你与外部系统进行交互。
在 Pod 中使用 Secret 文件
在 Pod 中使用存放在卷中的 Secret:
创建一个 Secret 或者使用已有的 Secret。多个 Pod 可以引用同一个 Secret。
修改你的 Pod 定义,在 spec.volumes[]
下增加一个卷。可以给这个卷随意命名,
它的 spec.volumes[].secret.secretName
必须是 Secret 对象的名字。
将 spec.containers[].volumeMounts[]
加到需要用到该 Secret 的容器中。
指定 spec.containers[].volumeMounts[].readOnly = true
和
spec.containers[].volumeMounts[].mountPath
为你想要该 Secret 出现的尚未使用的目录。
修改你的镜像并且/或者命令行,让程序从该目录下寻找文件。
Secret 的 data
映射中的每一个键都对应 mountPath
下的一个文件名。
这是一个在 Pod 中使用存放在挂载卷中 Secret 的例子:
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
您想要用的每个 Secret 都需要在 spec.volumes
中引用。
如果 Pod 中有多个容器,每个容器都需要自己的 volumeMounts
配置块,
但是每个 Secret 只需要一个 spec.volumes
。
您可以打包多个文件到一个 Secret 中,或者使用的多个 Secret,怎样方便就怎样来。
将 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
将会发生什么呢:
username
Secret 存储在 /etc/foo/my-group/my-username
文件中而不是 /etc/foo/username
中。
password
Secret 没有被映射
如果使用了 spec.volumes[].secret.items
,只有在 items
中指定的键会被映射。
要使用 Secret 中所有键,就必须将它们都列在 items
字段中。
所有列出的键名必须存在于相应的 Secret 中。否则,不会创建卷。
Secret 文件权限
你还可以指定 Secret 将拥有的权限模式位。如果不指定,默认使用 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 : 256
之后,Secret 将被挂载到 /etc/foo
目录,而所有通过该 Secret 卷挂载
所创建的文件的权限都是 0400
。
请注意,JSON 规范不支持八进制符号,因此使用 256 值作为 0400 权限。
如果你使用 YAML 而不是 JSON,则可以使用八进制符号以更自然的方式指定权限。
注意,如果你通过 kubectl exec
进入到 Pod 中,你需要沿着符号链接来找到
所期望的文件模式。例如,下面命令检查 Secret 文件的访问模式:
kubectl exec mypod -it sh
cd /etc/foo
ls -l
输出类似于:
total 0
lrwxrwxrwx 1 root root 15 May 18 00:18 password -> ..data/password
lrwxrwxrwx 1 root root 15 May 18 00:18 username -> ..data/username
沿着符号链接,可以查看文件的访问模式:
输出类似于:
total 8
-r-------- 1 root root 12 May 18 00:18 password
-r-------- 1 root root 5 May 18 00:18 username
你还可以使用映射,如上一个示例,并为不同的文件指定不同的权限,如下所示:
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
items :
- key : username
path : my-group/my-username
mode : 511
在这里,位于 /etc/foo/my-group/my-username
的文件的权限值为 0777
。
由于 JSON 限制,必须以十进制格式指定模式,即 511
。
请注意,如果稍后读取此权限值,可能会以十进制格式显示。
使用来自卷中的 Secret 值
在挂载了 Secret 卷的容器内,Secret 键名显示为文件名,并且 Secret 的值
使用 base-64 解码后存储在这些文件中。
这是在上面的示例容器内执行的命令的结果:
输出类似于:
username
password
输出类似于:
admin
输出类似于:
1f2d1e2e67df
容器中的程序负责从文件中读取 secret。
挂载的 Secret 会被自动更新
当已经存储于卷中被使用的 Secret 被更新时,被映射的键也将终将被更新。
组件 kubelet 在周期性同步时检查被挂载的 Secret 是不是最新的。
但是,它会使用其本地缓存的数值作为 Secret 的当前值。
缓存的类型可以使用 KubeletConfiguration 结构
中的 ConfigMapAndSecretChangeDetectionStrategy
字段来配置。
它可以通过 watch 操作来传播(默认),基于 TTL 来刷新,也可以
将所有请求直接重定向到 API 服务器。
因此,从 Secret 被更新到将新 Secret 被投射到 Pod 的那一刻的总延迟可能与
kubelet 同步周期 + 缓存传播延迟一样长,其中缓存传播延迟取决于所选的缓存类型。
对应于不同的缓存类型,该延迟或者等于 watch 传播延迟,或者等于缓存的 TTL,
或者为 0。
说明: 使用 Secret 作为
子路径 卷挂载的容器
不会收到 Secret 更新。
以环境变量的形式使用 Secrets
将 Secret 作为 Pod 中的环境变量 使用:
创建一个 Secret 或者使用一个已存在的 Secret。多个 Pod 可以引用同一个 Secret。
修改 Pod 定义,为每个要使用 Secret 的容器添加对应 Secret 键的环境变量。
使用 Secret 键的环境变量应在 env[x].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
- name : SECRET_PASSWORD
valueFrom :
secretKeyRef :
name : mysecret
key : password
restartPolicy : Never
使用来自环境变量的 Secret 值
在一个以环境变量形式使用 Secret 的容器中,Secret 键表现为常规的环境变量,其中
包含 Secret 数据的 base-64 解码值。这是从上面的示例在容器内执行的命令的结果:
输出类似于:
admin
输出类似于:
1f2d1e2e67df
Secret 更新之后对应的环境变量不会被更新
如果某个容器已经在通过环境变量使用某 Secret,对该 Secret 的更新不会被
容器马上看见,除非容器被重启。有一些第三方的解决方案能够在 Secret 发生
变化时触发容器重启。
不可更改的 Secret
FEATURE STATE: Kubernetes v1.21 [stable]
Kubernetes 的特性 不可变的 Secret 和 ConfigMap 提供了一种可选配置,
可以设置各个 Secret 和 ConfigMap 为不可变的。
对于大量使用 Secret 的集群(至少有成千上万各不相同的 Secret 供 Pod 挂载),
禁止变更它们的数据有下列好处:
防止意外(或非预期的)更新导致应用程序中断
通过将 Secret 标记为不可变来关闭 kube-apiserver 对其的监视,从而显著降低
kube-apiserver 的负载,提升集群性能。
这个特性通过 ImmutableEmphemeralVolumes
特性门控
来控制,从 v1.19 开始默认启用。
你可以通过将 Secret 的 immutable
字段设置为 true
创建不可更改的 Secret。
例如:
apiVersion : v1
kind : Secret
metadata :
...
data :
...
immutable : true
说明:
一旦一个 Secret 或 ConfigMap 被标记为不可更改,撤销此操作或者更改 data
字段的内容都是 不 可能的。
只能删除并重新创建这个 Secret。现有的 Pod 将维持对已删除 Secret 的挂载点 - 建议重新创建这些 Pod。
使用 imagePullSecret
imagePullSecrets
字段中包含一个列表,列举对同一名字空间中的 Secret 的引用。
你可以使用 imagePullSecrets
将包含 Docker(或其他)镜像仓库密码的 Secret 传递给
kubelet。kubelet 使用此信息来替你的 Pod 拉取私有镜像。
关于 imagePullSecrets
字段的更多信息,请参考
PodSpec API 文档。
手动指定 imagePullSecret
你可以阅读容器镜像文档
以了解如何设置 imagePullSecrets
。
设置自动附加 imagePullSecrets
您可以手动创建 imagePullSecret
,并在 ServiceAccount 中引用它。
使用该 ServiceAccount 创建的任何 Pod 和默认使用该 ServiceAccount 的
Pod 将会将其的 imagePullSecret 字段设置为服务帐户的 imagePullSecret 值。
有关该过程的详细说明,请参阅
将 ImagePullSecrets 添加到服务帐户 。
详细说明
限制
Kubernetes 会验证 Secret 作为卷来源时所给的对象引用确实指向一个类型为
Secret 的对象。因此,Secret 需要先于任何依赖于它的 Pod 创建。
Secret API 对象处于某名字空间
中。它们只能由同一命名空间中的 Pod 引用。
每个 Secret 的大小限制为 1MB。这是为了防止创建非常大的 Secret 导致 API 服务器
和 kubelet 的内存耗尽。然而,创建过多较小的 Secret 也可能耗尽内存。
更全面得限制 Secret 内存用量的功能还在计划中。
kubelet 仅支持从 API 服务器获得的 Pod 使用 Secret。
这包括使用 kubectl
创建的所有 Pod,以及间接通过副本控制器创建的 Pod。
它不包括通过 kubelet --manifest-url
标志,--config
标志或其 REST API
创建的 Pod(这些不是创建 Pod 的常用方法)。
静态 Pod
的 spec
不能引用 Secret 或任何其他 API 对象。
以环境变量形式在 Pod 中使用 Secret 之前必须先创建
Secret,除非该环境变量被标记为可选的。
Pod 中引用不存在的 Secret 时将无法启动。
使用 secretKeyRef
时,如果引用了指定 Secret 不存在的键,对应的 Pod 也无法启动。
对于通过 envFrom
填充环境变量的 Secret,如果 Secret 中包含的键名无法作为
合法的环境变量名称,对应的键会被跳过,该 Pod 将被允许启动。
不过这时会产生一个事件,其原因为 InvalidVariableNames
,其消息中包含被跳过的无效键的列表。
下面的示例显示一个 Pod,它引用了包含 2 个无效键 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 与 Pod 生命周期的关系
通过 API 创建 Pod 时,不会检查引用的 Secret 是否存在。一旦 Pod 被调度,kubelet
就会尝试获取该 Secret 的值。如果获取不到该 Secret,或者暂时无法与 API 服务器建立连接,
kubelet 将会定期重试。kubelet 将会报告关于 Pod 的事件,并解释它无法启动的原因。
一旦获取到 Secret,kubelet 将创建并挂载一个包含它的卷。在 Pod 的所有卷被挂载之前,
Pod 中的容器不会启动。
使用案例
案例:以环境变量的形式使用 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 : k8s.gcr.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
你也可以创建一个带有包含 SSH 密钥的 secretGenerator
字段的
kustomization.yaml
文件。
注意: 发送自己的 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。
一个 Pod 使用包含生产环境凭据的 Secret,另一个 Pod 使用包含测试环境凭据的 Secret。
你可以创建一个带有 secretGenerator
字段的 kustomization.yaml
文件,或者执行 kubectl create secret
:
kubectl create secret generic prod-db-secret \
--from-literal= username = produser \
--from-literal= password = Y4nys7f11
输出类似于:
secret "prod-db-secret" created
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
通过下面的命令应用所有对象
两个容器都会在其文件系统上存在以下文件,其中包含容器对应的环境的值:
/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 : k8s.gcr.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
才能看到它们。
案例:Secret 仅对 Pod 中的一个容器可见
考虑一个需要处理 HTTP 请求、执行一些复杂的业务逻辑,然后使用 HMAC 签署一些消息的应用。
因为应用程序逻辑复杂,服务器中可能会存在一个未被注意的远程文件读取漏洞,
可能会将私钥暴露给攻击者。
解决的办法可以是将应用分为两个进程,分别运行在两个容器中:
前端容器,用于处理用户交互和业务逻辑,但无法看到私钥;
签名容器,可以看到私钥,响应来自前端(例如通过本地主机网络)的简单签名请求。
使用这种分割方法,攻击者现在必须欺骗应用程序服务器才能进行任意的操作,
这可能比使其读取文件更难。
最佳实践
客户端使用 Secret API
当部署与 Secret API 交互的应用程序时,应使用
鉴权策略 ,
例如 RBAC ,来限制访问。
Secret 中的值对于不同的环境来说重要性可能不同。
很多 Secret 都可能导致 Kubernetes 集群内部的权限越界(例如服务账号令牌)
甚至逃逸到集群外部。
即使某一个应用程序可以就所交互的 Secret 的能力作出正确抉择,但是同一命名空间中
的其他应用程序却可能不这样做。
由于这些原因,在命名空间中 watch
和 list
Secret 的请求是非常强大的能力,
是应该避免的行为。列出 Secret 的操作可以让客户端检查该命名空间中存在的所有 Secret。
在群集中 watch
和 list
所有 Secret 的能力应该只保留给特权最高的系统级组件。
需要访问 Secret API 的应用程序应该针对所需要的 Secret 执行 get
请求。
这样,管理员就能限制对所有 Secret 的访问,同时为应用所需要的
实例设置访问允许清单 。
为了获得高于轮询操作的性能,客户端设计资源时,可以引用 Secret,然后对资源执行 watch
操作,在引用更改时重新检索 Secret。
此外,社区还存在一种 “批量监控” API
的提案,允许客户端 watch
独立的资源,该功能可能会在将来的 Kubernetes 版本中提供。
安全属性
保护
因为 Secret 对象可以独立于使用它们的 Pod 而创建,所以在创建、查看和编辑 Pod 的流程中
Secret 被暴露的风险较小。系统还可以对 Secret 对象采取额外的预防性保护措施,
例如,在可能的情况下避免将其写到磁盘。
只有当某节点上的 Pod 需要用到某 Secret 时,该 Secret 才会被发送到该节点上。
Secret 不会被写入磁盘,而是被 kubelet 存储在 tmpfs 中。
一旦依赖于它的 Pod 被删除,Secret 数据的本地副本就被删除。
同一节点上的很多个 Pod 可能拥有多个 Secret。
但是,只有 Pod 所请求的 Secret 在其容器中才是可见的。
因此,一个 Pod 不能访问另一个 Pod 的 Secret。
同一个 Pod 中可能有多个容器。但是,Pod 中的每个容器必须通过 volumeeMounts
请求挂载 Secret 卷才能使卷中的 Secret 对容器可见。
这一实现可以用于在 Pod 级别构建安全分区 。
在大多数 Kubernetes 发行版中,用户与 API 服务器之间的通信以及
从 API 服务器到 kubelet 的通信都受到 SSL/TLS 的保护。
通过这些通道传输时,Secret 受到保护。
FEATURE STATE: Kubernetes v1.13 [beta]
你可以为 Secret 数据开启静态加密 ,
这样 Secret 数据就不会以明文形式存储到etcd 中。
风险
API 服务器上的 Secret 数据以纯文本的方式存储在 etcd 中,因此:
管理员应该为集群数据开启静态加密(要求 v1.13 或者更高版本)。
管理员应该限制只有 admin 用户能访问 etcd;
API 服务器中的 Secret 数据位于 etcd 使用的磁盘上;管理员可能希望在不再使用时擦除/粉碎 etcd 使用的磁盘
如果 etcd 运行在集群内,管理员应该确保 etcd 之间的通信使用 SSL/TLS 进行加密。
如果您将 Secret 数据编码为 base64 的清单(JSON 或 YAML)文件,共享该文件或将其检入代码库,该密码将会被泄露。 Base64 编码不是一种加密方式,应该视同纯文本。
应用程序在从卷中读取 Secret 后仍然需要保护 Secret 的值,例如不会意外将其写入日志或发送给不信任方。
可以创建使用 Secret 的 Pod 的用户也可以看到该 Secret 的值。即使 API 服务器策略不允许用户读取 Secret 对象,用户也可以运行 Pod 导致 Secret 暴露。
接下来
3.7.4 - 为容器管理资源
当你定义 Pod 时可以选择性地为每个
容器 设定所需要的资源数量。
最常见的可设定资源是 CPU 和内存(RAM)大小;此外还有其他类型的资源。
当你为 Pod 中的 Container 指定了资源 请求 时,调度器就利用该信息决定将 Pod 调度到哪个节点上。
当你还为 Container 指定了资源 约束 时,kubelet 就可以确保运行的容器不会使用超出所设约束的资源。
kubelet 还会为容器预留所 请求 数量的系统资源,供其使用。
请求和约束
如果 Pod 运行所在的节点具有足够的可用资源,容器可能(且可以)使用超出对应资源
request
属性所设置的资源量。不过,容器不可以使用超出其资源 limit
属性所设置的资源量。
例如,如果你将容器的 memory
的请求量设置为 256 MiB,而该容器所处的 Pod
被调度到一个具有 8 GiB 内存的节点上,并且该节点上没有其他 Pods
运行,那么该容器就可以尝试使用更多的内存。
如果你将某容器的 memory
约束设置为 4 GiB,kubelet (和
容器运行时 )
就会确保该约束生效。
容器运行时会禁止容器使用超出所设置资源约束的资源。
例如:当容器中进程尝试使用超出所允许内存量的资源时,系统内核会将尝试申请内存的进程终止,
并引发内存不足(OOM)错误。
约束值可以以被动方式来实现(系统会在发现违例时进行干预),或者通过强制生效的方式实现
(系统会避免容器用量超出约束值)。不同的容器运行时采用不同方式来实现相同的限制。
说明:
如果某 Container 设置了自己的内存限制但未设置内存请求,Kubernetes
自动为其设置与内存限制相匹配的请求值。类似的,如果某 Container 设置了
CPU 限制值但未设置 CPU 请求值,则 Kubernetes 自动为其设置 CPU 请求
并使之与 CPU 限制值匹配。
资源类型
CPU 和内存 都是资源类型 。每种资源类型具有其基本单位。
CPU 表达的是计算处理能力,其单位是 Kubernetes CPUs 。
内存的单位是字节。
如果你使用的是 Kubernetes v1.14 或更高版本,则可以指定巨页(Huge Page)资源。
巨页是 Linux 特有的功能,节点内核在其中分配的内存块比默认页大小大得多。
例如,在默认页面大小为 4KiB 的系统上,你可以指定约束 hugepages-2Mi: 80Mi
。
如果容器尝试分配 40 个 2MiB 大小的巨页(总共 80 MiB ),则分配请求会失败。
说明:
你不能过量使用 hugepages- *
资源。
这与 memory
和 cpu
资源不同。
CPU 和内存统称为计算资源 ,或简称为资源 。
计算资源的数量是可测量的,可以被请求、被分配、被消耗。
它们与 API 资源 不同。
API 资源(如 Pod 和 Service )是可通过
Kubernetes API 服务器读取和修改的对象。
Pod 和 容器的资源请求和约束
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 个 vCPU/核 和裸机 Intel
处理器上的 1 个超线程 。
你也可以表达带小数 CPU 的请求。spec.containers[].resources.requests.cpu
为 0.5
的 Container 肯定能够获得请求 1 CPU 的容器的一半 CPU 资源。表达式 0.1
等价于表达式 100m
,
可以看作 “100 millicpu”。有些人说成是“一百毫 cpu”,其实说的是同样的事情。
具有小数点(如 0.1
)的请求由 API 转换为 100m
;最大精度是 1m
。
因此,或许你应该优先考虑使用 100m
的形式。
CPU 总是按绝对数量来请求的,不可以使用相对数量;
0.1 的 CPU 在单核、双核、48 核的机器上的意义是一样的。
内存的含义
内存的约束和请求以字节为单位。你可以使用以下后缀之一以一般整数或定点数字形式来表示内存:
E、P、T、G、M、k。你也可以使用对应的 2 的幂数:Ei、Pi、Ti、Gi、Mi、Ki。
例如,以下表达式所代表的是大致相同的值:
128974848、129e6、129M、123Mi
下面是个例子。
以下 Pod 有两个 Container。每个 Container 的请求为 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
env :
- name : MYSQL_ROOT_PASSWORD
value : "password"
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。
当稍后节点上资源用量增加,例如到达请求率的每日峰值区间时,节点上也不会出现资源不足的问题。
带资源约束的 Pod 如何运行
当 kubelet 启动 Pod 中的 Container 时,它会将 CPU 和内存约束信息传递给容器运行时。
当使用 Docker 时:
spec.containers[].resources.requests.cpu
先被转换为可能是小数的基础值,再乘以 1024。
这个数值和 2 的较大者用作 docker run
命令中的
--cpu-shares
标志的值。
spec.containers[].resources.limits.cpu
先被转换为 millicore 值,再乘以 100。
其结果就是每 100 毫秒内容器可以使用的 CPU 时间总量,单位为微秒。在此期间(100ms),
容器所使用的 CPU 时间不可以超过它被分配的时间。
说明: 默认的配额(Quota)周期为 100 毫秒。CPU 配额的最小精度为 1 毫秒。
spec.containers[].resources.limits.memory
被转换为整数值,作为 docker run
命令中的
--memory
参数值。
如果 Container 超过其内存限制,则可能会被终止。如果容器可重新启动,则与所有其他类型的
运行时失效一样,kubelet 将重新启动容器。
如果一个 Container 内存用量超过其内存请求值,那么当节点内存不足时,容器所处的 Pod 可能被逐出。
每个 Container 可能被允许也可能不被允许使用超过其 CPU 约束的处理时间。
但是,容器不会由于 CPU 使用率过高而被杀死。
要确定 Container 是否会由于资源约束而无法调度或被杀死,请参阅疑难解答 部分。
监控计算和内存资源用量
Pod 的资源使用情况是作为 Pod 状态的一部分来报告的。
如果为集群配置了可选的
监控工具 ,
则可以直接从
指标 API
或者监控工具获得 Pod 的资源使用情况。
本地临时存储
FEATURE STATE: Kubernetes v1.10 [beta]
节点通常还可以具有本地的临时性存储,由本地挂接的可写入设备或者有时也用 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 能够度量其本地存储的用量。实现度量机制的前提是:
LocalStorageCapacityIsolation
特性门控
被启用(默认状态),并且
你已经对节点进行了配置,使之使用所支持的本地临时性储存配置方式之一
如果你的节点配置不同于以上预期,kubelet 就无法对临时性本地存储的资源约束实施限制。
说明: kubelet 会将 tmpfs
emptyDir 卷的用量当作容器内存用量,而不是本地临时性存储来统计。
为本地临时性存储设置请求和约束值
你可以使用 ephemeral-storage 来管理本地临时性存储。
Pod 中的每个 Container 可以设置以下属性:
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。
例如,下面的表达式所表达的大致是同一个值:
128974848, 129e6, 129M, 123Mi
在下面的例子中,Pod 包含两个 Container。每个 Container 请求 2 GiB 大小的本地临时性存储。
每个 Container 都设置了 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 选择一个节点来运行之。
每个节点都有一个本地临时性存储的上限,是其可提供给 Pods 使用的总量。
欲了解更多信息,可参考
节点可分配资源
节。
调度器会确保所调度的 Containers 的资源请求总和不会超出节点的资源容量。
临时性存储消耗的管理
如果 kubelet 将本地临时性存储作为资源来管理,则 kubelet 会度量以下各处的存储用量:
emptyDir
卷,除了 tmpfs emptyDir
卷
保存节点层面日志的目录
可写入的容器镜像层
如果某 Pod 的临时存储用量超出了你所允许的范围,kubelet
会向其发出逐出(eviction)信号,触发该 Pod 被逐出所在节点。
就容器层面的隔离而言,如果某容器的可写入镜像层和日志用量超出其存储约束,
kubelet 也会将所在的 Pod 标记为逐出候选。
就 Pod 层面的隔离而言,kubelet 会将 Pod 中所有容器的约束值相加,得到 Pod
存储约束的总值。如果所有容器的本地临时性存储用量总和加上 Pod 的 emptyDir
卷的用量超出 Pod 存储约束值,kubelet 也会将该 Pod 标记为逐出候选。
注意: 如果 kubelet 没有度量本地临时性存储的用量,即使 Pod
的本地存储用量超出其约束值也不会被逐出。
不过,如果用于可写入容器镜像层、节点层面日志或者 emptyDir
卷的文件系统中可用空间太少,
节点会为自身设置本地存储不足的污点 标签。
这一污点会触发对那些无法容忍该污点的 Pods 的逐出操作。
关于临时性本地存储的配置信息,请参考这里
kubelet 支持使用不同方式来度量 Pod 的存储用量:
kubelet 按预定周期执行扫描操作,检查 emptyDir
卷、容器日志目录以及可写入容器镜像层。
这一扫描会度量存储空间用量。
说明: 在这种模式下,kubelet 并不检查已删除文件所对应的、仍处于打开状态的文件描述符。
如果你(或者容器)在 emptyDir
卷中创建了一个文件,写入一些内容之后再次打开
该文件并执行了删除操作,所删除文件对应的 inode 仍然存在,直到你关闭该文件为止。
kubelet 不会将该文件所占用的空间视为已使用空间。
FEATURE STATE: Kubernetes v1.15 [alpha]
项目配额(Project Quota)是一个操作系统层的功能特性,用来管理文件系统中的存储用量。
在 Kubernetes 中,你可以启用项目配额以监视存储用量。
你需要确保节点上为 emptyDir
提供存储的文件系统支持项目配额。
例如,XFS 和 ext4fs 文件系统都支持项目配额。
说明: 项目配额可以帮你监视存储用量,但无法对存储约束执行限制。
Kubernetes 所使用的项目 ID 始于 1048576
。
所使用的 IDs 会注册在 /etc/projects
和 /etc/projid
文件中。
如果该范围中的项目 ID 已经在系统中被用于其他目的,则已占用的项目 IDs
也必须注册到 /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 的资源总量不会超过可用数量。
说明: 扩展资源取代了非透明整数资源(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 可以调度的地方时,
会产生一个事件,如下所示:
kubectl describe pod frontend | grep -A 3 Events
Events:
FirstSeen LastSeen Count From Subobject PathReason Message
36s 5s 6 {scheduler} FailedScheduling Failed for reason PodExceedsFreeCPU and possibly others
在上述示例中,由于节点上的 CPU 资源不足,名为 “frontend” 的 Pod 无法被调度。
由于内存不足(PodExceedsFreeMemory)而导致失败时,也有类似的错误消息。
一般来说,如果 Pod 处于悬决状态且有这种类型的消息时,你可以尝试如下几件事情:
向集群添加更多节点。
终止不需要的 Pod,为悬决的 Pod 腾出空间。
检查 Pod 所需的资源是否超出所有节点的资源容量。例如,如果所有节点的容量都是cpu:1
,
那么一个请求为 cpu: 1.1
的 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 (12%) 1070Mi (14%)
在上面的输出中,你可以看到如果 Pod 请求超过 1120m CPU 或者 6.23Gi 内存,节点将无法满足。
通过查看 Pods
部分,你将看到哪些 Pod 占用了节点上的资源。
可供 Pod 使用的资源量小于节点容量,因为系统守护程序也会使用一部分可用资源。
NodeStatus
的 allocatable
字段给出了可用于 Pod 的资源量。
有关更多信息,请参阅 节点可分配资源 。
可以配置 资源配额 功能特性
以限制可以使用的资源总量。
如果与名字空间配合一起使用,就可以防止一个团队占用所有资源。
我的容器被终止了
你的容器可能因为资源紧张而被终止。要查看容器是否因为遇到资源限制而被杀死,
请针对相关的 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
Replication Controllers: simmemleak (1/1 replicas created)
Containers:
simmemleak:
Image: saadali/simmemleak
Limits:
cpu: 100m
memory: 50Mi
State: Running
Started: Tue, 07 Jul 2015 12:54:41 -0700
Last Termination State: Terminated
Exit Code: 1
Started: Fri, 07 Jul 2015 12:54:30 -0700
Finished: Fri, 07 Jul 2015 12:54:33 -0700
Ready: False
Restart Count: 5
Conditions:
Type Status
Ready False
Events:
FirstSeen LastSeen Count From SubobjectPath Reason Message
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {scheduler } scheduled Successfully assigned simmemleak-hra99 to kubernetes-node-tf0f
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD pulled Pod container image "k8s.gcr.io/pause:0.8.0" already present on machine
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD created Created with docker id 6a41280f516d
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} implicitly required container POD started Started with docker id 6a41280f516d
Tue, 07 Jul 2015 12:53:51 -0700 Tue, 07 Jul 2015 12:53:51 -0700 1 {kubelet kubernetes-node-tf0f} spec.containers{simmemleak} created Created with docker id 87348f12526a
在上面的例子中,Restart Count: 5
意味着 Pod 中的 simmemleak
容器被终止并重启了五次。
你可以使用 kubectl get pod
命令加上 -o go-template=...
选项来获取之前终止容器的状态。
kubectl get pod -o go-template= '{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}' simmemleak-hra99
Container Name: simmemleak
LastState: map[terminated:map[exitCode:137 reason:OOM Killed startedAt:2015-07-07T20:58:43Z finishedAt:2015-07-07T20:58:43Z containerID:docker://0e4095bba1feccdfe7ef9fb6ebffe972b4b14285d5acdec6f0d3ae8a22fad8b2]]
你可以看到容器因为 reason:OOM killed
而被终止,OOM
表示内存不足(Out Of Memory)。
接下来
3.7.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 元素,使用简便的名称来对访问参数进行分组。每个上下文都有三个参数: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
中,相对路径按相对路径存储,绝对路径按绝对路径存储。
接下来
3.8 - 安全
确保云原生工作负载安全的一组概念。
3.8.1 - 云原生安全概述
在云原生安全的背景下思考 Kubernetes 安全模型。
本概述定义了一个模型,用于在 Cloud Native 安全性上下文中考虑 Kubernetes 安全性。
警告: 此容器安全模型只提供建议,而不是经过验证的信息安全策略。
云原生安全的 4 个 C
你可以分层去考虑安全性,云原生安全的 4 个 C 分别是云(Cloud)、集群(Cluster)、容器(Container)和代码(Code)。
说明: 这种分层方法增强了
深度防护方法 在安全性方面的
防御能力,该方法被广泛认为是保护软件系统的最佳实践。
云原生安全的 4C
云原生安全模型的每一层都是基于下一个最外层,代码层受益于强大的基础安全层(云、集群、容器)。你无法通过在代码层解决安全问题来为基础层中糟糕的安全标准提供保护。
云
在许多方面,云(或者位于同一位置的服务器,或者是公司数据中心)是 Kubernetes 集群中的
可信计算基 。
如果云层容易受到攻击(或者被配置成了易受攻击的方式),就不能保证在此基础之上构建的组件是安全的。
每个云提供商都会提出安全建议,以在其环境中安全地运行工作负载。
云提供商安全性
如果您是在您自己的硬件或者其他不同的云提供商上运行 Kubernetes 集群,
请查阅相关文档来获取最好的安全实践。
下面是一些比较流行的云提供商的安全性文档链接:
基础设施安全
关于在 Kubernetes 集群中保护你的基础设施的建议:
基础设施安全
Kubetnetes 基础架构关注领域
建议
通过网络访问 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 握手。除少数情况外,请加密传输中的所有内容。更进一步,加密服务之间的网络流量是一个好主意。这可以通过被称为相互 LTS 或 mTLS 的过程来完成,该过程对两个证书持有服务之间的通信执行双向验证。
限制通信端口范围
此建议可能有点不言自明,但是在任何可能的情况下,你都只应公开服务上对于通信或度量收集绝对必要的端口。
第三方依赖性安全
最好定期扫描应用程序的第三方库以了解已知的安全漏洞。每种编程语言都有一个自动执行此检查的工具。
静态代码分析
大多数语言都提供给了一种方法,来分析代码段中是否存在潜在的不安全的编码实践。只要有可能,你都应该使用自动工具执行检查,该工具可以扫描代码库以查找常见的安全错误,一些工具可以在以下连接中找到:https://owasp.org/www-community/Source_Code_Analysis_Tools
动态探测攻击
您可以对服务运行一些自动化工具,来尝试一些众所周知的服务攻击。这些攻击包括 SQL 注入、CSRF 和 XSS。OWASP Zed Attack 代理工具是最受欢迎的动态分析工具之一。
接下来
学习了解相关的 Kubernetes 安全主题:
3.8.2 - Pod 安全性标准
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 节点的特权访问成为可能。
基线策略中对宿主的特权访问是被禁止的。
HostProcess Pod 是 Kubernetes v1.22 版本的
alpha 特性。
限制的字段
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
允许的值
Undefined/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 Profile。
基线策略应避免覆盖或者禁用默认策略,以及限制覆盖一些 Profile 集合的权限。
限制的字段
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 Profile 禁止被显式设置为 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 策略规范
控制(Control)
策略(Policy)
基线策略的所有要求。
卷类型
除了限制 HostPath 卷之外,此类策略还限制可以通过 PersistentVolumes 定义的非核心卷类型。
限制的字段
spec.volumes[*].hostPath
spec.volumes[*].gcePersistentDisk
spec.volumes[*].awsElasticBlockStore
spec.volumes[*].gitRepo
spec.volumes[*].nfs
spec.volumes[*].iscsi
spec.volumes[*].glusterfs
spec.volumes[*].rbd
spec.volumes[*].flexVolume
spec.volumes[*].cinder
spec.volumes[*].cephfs
spec.volumes[*].flocker
spec.volumes[*].fc
spec.volumes[*].azureFile
spec.volumes[*].vsphereVolume
spec.volumes[*].quobyte
spec.volumes[*].azureDisk
spec.volumes[*].portworxVolume
spec.volumes[*].scaleIO
spec.volumes[*].storageos
spec.volumes[*].photonPersistentDisk
允许的值
特权提升(v1.8+)
禁止(通过 SetUID 或 SetGID 文件模式)获得特权提升。
限制的字段
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 组(可选)
禁止容器使用 root 作为主要或辅助 GID 来运行。
限制的字段
spec.securityContext.runAsGroup
spec.securityContext.supplementalGroups[*]
spec.securityContext.fsGroup
spec.containers[*].securityContext.runAsGroup
spec.initContainers[*].securityContext.runAsGroup
spec.ephemeralContainers[*].securityContext.runAsGroup
允许的值
未定义/nil(*.runAsGroup
除外)
非零值
Seccomp (v1.19+)
Seccomp Profile 必须被显式设置成一个允许的值。禁止使用 Unconfined
Profile 或者指定 不存在的 Profile。
限制的字段
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
权能。
限制的字段
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 安全性准入控制器
PodSecurityPolicy
常见问题
为什么不存在介于 Privileged 和 Baseline 之间的策略类型
这里定义的三种策略框架有一个明晰的线性递进关系,从最安全(Restricted)到最不安全,
并且覆盖了很大范围的工作负载。特权要求超出 Baseline 策略者通常是特定于应用的需求,
所以我们没有在这个范围内提供标准框架。
这并不意味着在这样的情形下仍然只能使用 Privileged 框架,只是说处于这个范围的
策略需要因地制宜地定义。
SIG Auth 可能会在将来考虑这个范围的框架,前提是有对其他框架的需求。
安全策略与安全上下文的区别是什么?
安全上下文 在运行时配置 Pod
和容器。安全上下文是在 Pod 清单中作为 Pod 和容器规约的一部分来定义的,所代表的是
传递给容器运行时的参数。
安全策略则是控制面用来对安全上下文以及安全性上下文之外的参数实施某种设置的机制。
在 2020 年 7 月,
Pod 安全性策略 已被废弃,
取而代之的是内置的 Pod 安全性准入控制器 。
Kubernetes 生态系统中还在开发一些其他的替代方案,例如
我应该为我的 Windows Pod 实施哪种框架?
Kubernetes 中的 Windows 负载与标准的基于 Linux 的负载相比有一些局限性和区别。
尤其是 Pod SecurityContext 字段
对 Windows 不起作用 。
因此,目前没有对应的标准 Pod 安全性框架。
如果你为一个 Windows Pod 应用了 Restricted 策略,可能会 对该 Pod 的运行时产生影响。
Restricted 策略需要强制执行 Linux 特有的限制(如 seccomp Profile,并且禁止特权提升)。
如果 kubelet 和/或其容器运行时忽略了 Linux 特有的值,那么应该不影响 Windows Pod 正常工作。
然而,对于使用 Windows 容器的 Pod 来说,缺乏强制执行意味着相比于 Restricted 策略,没有任何额外的限制。
你应该只在 Privileged 策略下使用 HostProcess 标志来创建 HostProcess Pod。
在 Baseline 和 Restricted 策略下,创建 Windows HostProcess Pod 是被禁止的,
因此任何 HostProcess Pod 都应该被认为是有特权的。
沙箱(Sandboxed) Pod 怎么处理?
现在还没有 API 标准来控制 Pod 是否被视作沙箱化 Pod。
沙箱 Pod 可以通过其是否使用沙箱化运行时(如 gVisor 或 Kata Container)来辨别,不过
目前还没有关于什么是沙箱化运行时的标准定义。
沙箱化负载所需要的保护可能彼此各不相同。例如,当负载与下层内核直接隔离开来时,
限制特权化操作的许可就不那么重要。这使得那些需要更多许可权限的负载仍能被有效隔离。
此外,沙箱化负载的保护高度依赖于沙箱化的实现方法。
因此,现在还没有针对所有沙箱化负载的建议策略。
3.8.3 - Kubernetes API 访问控制
本页面概述了对 Kubernetes API 的访问控制。
用户使用 kubectl
、客户端库或构造 REST 请求来访问 Kubernetes API 。
人类用户和 Kubernetes 服务账户 都可以被鉴权访问 API。
当请求到达 API 时,它会经历多个阶段,如下图所示:
传输安全
在典型的 Kubernetes 集群中,API 服务器在 443 端口上提供服务,受 TLS 保护。
API 服务器出示证书。
该证书可以使用私有证书颁发机构(CA)签名,也可以基于链接到公认的 CA 的公钥基础架构签名。
如果你的集群使用私有证书颁发机构,你需要在客户端的 ~/.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 所示)。
API 服务器端口和 IP
前面的讨论适用于发送到 API 服务器的安全端口的请求(典型情况)。 API 服务器实际上可以在 2 个端口上提供服务:
默认情况下,Kubernetes API 服务器在 2 个端口上提供 HTTP 服务:
localhost
端口:
用于测试和引导,以及主控节点上的其他组件(调度器,控制器管理器)与 API 通信
没有 TLS
默认为端口 8080
默认 IP 为 localhost,使用 --insecure-bind-address
进行更改
请求 绕过 身份认证和鉴权模块
由准入控制模块处理的请求
受需要访问主机的保护
“安全端口”:
尽可能使用
使用 TLS。 用 --tls-cert-file
设置证书,用 --tls-private-key-file
设置密钥
默认端口 6443,使用 --secure-port
更改
默认 IP 是第一个非本地网络接口,使用 --bind-address
更改
请求须经身份认证和鉴权组件处理
请求须经准入控制模块处理
身份认证和鉴权模块运行
接下来
阅读更多有关身份认证、鉴权和 API 访问控制的文档:
你可以了解
3.9 - 策略
可配置的、可应用到一组资源的策略。
3.9.1 - 限制范围
默认情况下, Kubernetes 集群上的容器运行使用的计算资源 没有限制。
使用资源配额,集群管理员可以以名字空间 为单位,限制其资源的使用与创建。
在命名空间中,一个 Pod 或 Container 最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。
有人担心,一个 Pod 或 Container 会垄断所有可用的资源。
LimitRange 是在命名空间内限制资源分配(给多个 Pod 或 Container)的策略对象。
一个 LimitRange(限制范围) 对象提供的限制能够做到:
在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
在一个命名空间中实施对每个 PersistentVolumeClaim 能申请的最小和最大的存储空间大小的限制。
在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。
启用 LimitRange
对 LimitRange 的支持自 Kubernetes 1.10 版本默认启用。
LimitRange 支持在很多 Kubernetes 发行版本中也是默认启用的。
LimitRange 的名称必须是合法的
DNS 子域名 。
限制范围总览
管理员在一个命名空间内创建一个 LimitRange
对象。
用户在命名空间内创建 Pod ,Container 和 PersistentVolumeClaim 等资源。
LimitRanger
准入控制器对所有没有设置计算资源需求的 Pod 和 Container 设置默认值与限制值,
并跟踪其使用量以保证没有超出命名空间中存在的任意 LimitRange 对象中的最小、最大资源使用量以及使用量比值。
若创建或更新资源(Pod、 Container、PersistentVolumeClaim)违反了 LimitRange 的约束,
向 API 服务器的请求会失败,并返回 HTTP 状态码 403 FORBIDDEN
与描述哪一项约束被违反的消息。
若命名空间中的 LimitRange 启用了对 cpu
和 memory
的限制,
用户必须指定这些值的需求使用量与限制使用量。否则,系统将会拒绝创建 Pod。
LimitRange 的验证仅在 Pod 准入阶段进行,不对正在运行的 Pod 进行验证。
能够使用限制范围创建的策略示例有:
在一个有两个节点,8 GiB 内存与16个核的集群中,限制一个命名空间的 Pod 申请
100m 单位,最大 500m 单位的 CPU,以及申请 200Mi,最大 600Mi 的内存。
为 spec 中没有 cpu 和内存需求值的 Container 定义默认 CPU 限制值与需求值
150m,内存默认需求值 300Mi。
在命名空间的总限制值小于 Pod 或 Container 的限制值的总和的情况下,可能会产生资源竞争。
在这种情况下,将不会创建 Container 或 Pod。
竞争和对 LimitRange 的改变都不会影响任何已经创建了的资源。
接下来
参阅 LimitRanger 设计文档 获取更多信息。
关于使用限值的例子,可参看
3.9.2 - 资源配额
当多个用户或团队共享具有固定节点数目的集群时,人们会担心有人使用超过其基于公平原则所分配到的资源量。
资源配额是帮助管理员解决这一问题的工具。
资源配额,通过 ResourceQuota
对象来定义,对每个命名空间的资源消耗总量提供限制。
它可以限制命名空间中某种类型的对象的总数目上限,也可以限制命令空间中的 Pod 可以使用的计算资源的总上限。
资源配额的工作方式如下:
不同的团队可以在不同的命名空间下工作,目前这是非约束性的,在未来的版本中可能会通过
ACL (Access Control List 访问控制列表) 来实现强制性约束。
集群管理员可以为每个命名空间创建一个或多个 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)来设置资源配额
FEATURE STATE: 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 亲和性配额
FEATURE STATE: Kubernetes v1.22 [beta]
集群运维人员可以使用 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
字段的 Pods
个数时,才可以在该名字空间中继续创建在其 Pod 亲和性规则中设置 namespaces
或 namespaceSelector
的新 Pod。
此功能特性处于 Beta 阶段,默认被禁用。你可以通过为 kube-apiserver 和
kube-scheduler 设置
特性门控
PodAffinityNamespaceSelector
来启用此特性。
请求与限制的比较
分配计算资源时,每个容器可以为 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.9.3 - Pod 安全策略
FEATURE STATE: Kubernetes v1.21 [deprecated]
PodSecurityPolicy 在 Kubernetes v1.21 版本中被弃用,将在 v1.25 中删除。
关于弃用的更多信息,请查阅 PodSecurityPolicy Deprecation: Past, Present, and Future 。
Pod 安全策略使得对 Pod 创建和更新进行细粒度的权限控制成为可能。
什么是 Pod 安全策略?
Pod 安全策略(Pod Security Policy) 是集群级别的资源,它能够控制 Pod 规约
中与安全性相关的各个方面。
PodSecurityPolicy
对象定义了一组 Pod 运行时必须遵循的条件及相关字段的默认值,只有 Pod 满足这些条件
才会被系统接受。
Pod 安全策略允许管理员控制如下方面:
Pod 安全策略 由设置和策略组成,它们能够控制 Pod 访问的安全特征。这些设置分为如下三类:
基于布尔值控制 :这种类型的字段默认为最严格限制的值。
基于被允许的值集合控制 :这种类型的字段会与这组值进行对比,以确认值被允许。
基于策略控制 :设置项通过一种策略提供的机制来生成该值,这种机制能够确保指定的值落在被允许的这组值中。
启用 Pod 安全策略
Pod 安全策略实现为一种可选的
准入控制器 。
启用了准入控制器
即可强制实施 Pod 安全策略,不过如果没有授权认可策略之前即启用
准入控制器 将导致集群中无法创建任何 Pod 。
由于 Pod 安全策略 API(policy/v1beta1/podsecuritypolicy
)是独立于准入控制器
来启用的,对于现有集群而言,建议在启用准入控制器之前先添加策略并对其授权。
授权策略
PodSecurityPolicy 资源被创建时,并不执行任何操作。为了使用该资源,需要对
发出请求的用户或者目标 Pod 的
服务账号
授权,通过允许其对策略执行 use
动词允许其使用该策略。
大多数 Kubernetes Pod 不是由用户直接创建的。相反,这些 Pod 是由
Deployment 、
ReplicaSet
或者经由控制器管理器模版化的控制器创建。
赋予控制器访问策略的权限意味着对应控制器所创建的 所有 Pod 都可访问策略。
因此,对策略进行授权的优先方案是为 Pod 的服务账号授予访问权限
(参见示例 )。
通过 RBAC 授权
RBAC 是一种标准的 Kubernetes
鉴权模式,可以很容易地用来授权策略访问。
首先,某 Role
或 ClusterRole
需要获得使用 use
访问目标策略的权限。
访问授权的规则看起来像这样:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRole
metadata :
name : <Role 名称>
rules :
- apiGroups : ['policy' ]
resources : ['podsecuritypolicies' ]
verbs : ['use' ]
resourceNames :
- <要授权的策略列表>
接下来将该 Role
(或 ClusterRole
)绑定到授权的用户:
apiVersion : rbac.authorization.k8s.io/v1
kind : ClusterRoleBinding
metadata :
name : <binding name>
roleRef :
kind : ClusterRole
name : <role name>
apiGroup : rbac.authorization.k8s.io
subjects :
# 授权命名空间下的所有服务账号(推荐):
- kind : Group
apiGroup : rbac.authorization.k8s.io
name : system:serviceaccounts:<authorized namespace>
# 授权特定的服务账号(不建议这样操作):
- kind : ServiceAccount
name : <authorized service account name>
namespace : <authorized pod namespace>
# 授权特定的用户(不建议这样操作):
- kind : User
apiGroup : rbac.authorization.k8s.io
name : <authorized user name>
如果使用的是 RoleBinding
(而不是 ClusterRoleBinding
),授权仅限于
与该 RoleBinding
处于同一名字空间中的 Pods。
可以考虑将这种授权模式和系统组结合,对名字空间中的所有 Pod 授予访问权限。
# 授权某名字空间中所有服务账号
- kind : Group
apiGroup : rbac.authorization.k8s.io
name : system:serviceaccounts
# 或者与之等价,授权给某名字空间中所有被认证过的用户
- kind : Group
apiGroup : rbac.authorization.k8s.io
name : system:authenticated
参阅角色绑定示例
查看 RBAC 绑定的更多实例。
参阅下文 ,查看对 PodSecurityPolicy 进行授权的完整示例。
推荐实践
PodSecurityPolicy 正在被一个新的、简化的 PodSecurity
准入控制器 替代。
有关此变更的更多详细信息,请参阅 PodSecurityPolicy Deprecation: Past, Present, and
Future 。
参照下述指导,简化从 PodSecurityPolicy 迁移到新的准入控制器步骤:
将 PodSecurityPolicies 限制为 Pod 安全性标准 所定义的策略:
通过配置 system:serviceaccounts:<namespace>
组(<namespace>
是目标命名空间),仅将 PSP 绑定到整个命名空间。示例:
apiVersion : rbac.authorization.k8s.io/v1
# This cluster role binding allows all pods in the "development" namespace to use the baseline PSP.
kind : ClusterRoleBinding
metadata :
name : psp-baseline-namespaces
roleRef :
kind : ClusterRole
name : psp-baseline
apiGroup : rbac.authorization.k8s.io
subjects :
- kind : Group
name : system:serviceaccounts:development
apiGroup : rbac.authorization.k8s.io
- kind : Group
name : system:serviceaccounts:canary
apiGroup : rbac.authorization.k8s.io
故障排查
控制器管理器组件
必须运行在
安全的 API 端口 ,
并且一定不能具有超级用户权限。
否则其请求会绕过身份认证和鉴权模块控制,从而导致所有 PodSecurityPolicy 对象
都被启用,用户亦能创建特权容器。
关于配置控制器管理器鉴权相关的详细信息,可参阅
控制器角色 。
策略顺序
除了限制 Pod 创建与更新,Pod 安全策略也可用来为其所控制的很多字段
设置默认值。当存在多个策略对象时,Pod 安全策略控制器依据以下条件选择
策略:
优先考虑允许 Pod 保持原样,不会更改 Pod 字段默认值或其他配置的 PodSecurityPolicy。
这类非更改性质的 PodSecurityPolicy 对象之间的顺序无关紧要。
如果必须要为 Pod 设置默认值或者其他配置,(按名称顺序)选择第一个允许
Pod 操作的 PodSecurityPolicy 对象。
说明: 在更新操作期间(这时不允许更改 Pod 规约),仅使用非更改性质的
PodSecurityPolicy 来对 Pod 执行验证操作。
示例
本示例假定你已经有一个启动了 PodSecurityPolicy 准入控制器的集群并且
你拥有集群管理员特权。
配置
为运行此示例,配置一个名字空间和一个服务账号。我们将用这个服务账号来
模拟一个非管理员账号的用户。
kubectl create namespace psp-example
kubectl create serviceaccount -n psp-example fake-user
kubectl create rolebinding -n psp-example fake-editor --clusterrole= edit --serviceaccount= psp-example:fake-user
创建两个别名,以更清晰地展示我们所使用的用户账号,同时减少一些键盘输入:
alias kubectl-admin= 'kubectl -n psp-example'
alias kubectl-user= 'kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
创建一个策略和一个 Pod
在一个文件中定义一个示例的 PodSecurityPolicy 对象。
这里的策略只是用来禁止创建有特权要求的 Pods。
PodSecurityPolicy 对象的名称必须是合法的
DNS 子域名 。
apiVersion : policy/v1beta1
kind : PodSecurityPolicy
metadata :
name : example
spec :
privileged : false # Don't allow privileged pods!
# The rest fills in some required fields.
seLinux :
rule : RunAsAny
supplementalGroups :
rule : RunAsAny
runAsUser :
rule : RunAsAny
fsGroup :
rule : RunAsAny
volumes :
- '*'
使用 kubectl 执行创建操作:
kubectl-admin create -f example-psp.yaml
现在,作为一个非特权用户,尝试创建一个简单的 Pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "pause" is forbidden: unable to validate against any pod security policy: []
发生了什么? 尽管 PodSecurityPolicy 被创建,Pod 的服务账号或者
fake-user
用户都没有使用该策略的权限。
kubectl-user auth can-i use podsecuritypolicy/example
no
创建角色绑定,赋予 fake-user
使用 use
访问示例策略的权限:
说明: 不建议使用这种方法!
欲了解优先考虑的方法,请参见
下节 。
kubectl-admin create role psp:unprivileged \
--verb= use \
--resource= podsecuritypolicy \
--resource-name= example
输出:
role "psp:unprivileged" created
kubectl-admin create rolebinding fake-user:psp:unprivileged \
--role= psp:unprivileged \
--serviceaccount= psp-example:fake-user
输出:
rolebinding "fake-user:psp:unprivileged" created
kubectl-user auth can-i use podsecuritypolicy/example
输出:
yes
现在重试创建 Pod:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
输出类似于:
pod "pause" created
此次尝试不出所料地成功了!
不过任何创建特权 Pod 的尝试还是会被拒绝:
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: privileged
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
securityContext:
privileged: true
EOF
输出类似于:
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
继续此例之前先删除该 Pod:
kubectl-user delete pod pause
运行另一个 Pod
我们再试一次,稍微有些不同:
kubectl-user create deployment pause --image= k8s.gcr.io/pause
输出为:
deployment "pause" created
输出为:
No resources found.
kubectl-user get events | head -n 2
输出为:
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
1m 2m 15 pause-7774d79b5 ReplicaSet Warning FailedCreate replicaset-controller Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request
发生了什么? 我们已经为用户 fake-user
绑定了 psp:unprivileged
角色,
为什么还会收到错误 Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request (创建错误:pods "pause-7774d79b5" 被禁止:没有可用来验证 pod 请求的驱动)
?
答案在于源文件 - replicaset-controller
。
fake-user
用户成功地创建了 Deployment,而后者也成功地创建了 ReplicaSet,
不过当 ReplicaSet 创建 Pod 时,发现未被授权使用示例 PodSecurityPolicy 资源。
为了修复这一问题,将 psp:unprivileged
角色绑定到 Pod 的服务账号。
在这里,因为我们没有给出服务账号名称,默认的服务账号是 default
。
kubectl-admin create rolebinding default:psp:unprivileged \
--role= psp:unprivileged \
--serviceaccount= psp-example:default
输出为:
rolebinding "default:psp:unprivileged" created
现在如果你给 ReplicaSet 控制器一分钟的时间来重试,该控制器最终将能够
成功地创建 Pod:
kubectl-user get pods --watch
输出类似于:
NAME READY STATUS RESTARTS AGE
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 ContainerCreating 0 1s
pause-7774d79b5-qrgcb 1/1 Running 0 2s
清理
删除名字空间即可清理大部分示例资源:
kubectl-admin delete ns psp-example
输出类似于:
namespace "psp-example" deleted
注意 PodSecurityPolicy
资源不是名字空间域的资源,必须单独清理:
kubectl-admin delete psp example
输出类似于:
podsecuritypolicy "example" deleted
示例策略
下面是一个你可以创建的约束性非常弱的策略,其效果等价于没有使用 Pod 安全
策略准入控制器:
apiVersion : policy/v1beta1
kind : PodSecurityPolicy
metadata :
name : privileged
annotations :
seccomp.security.alpha.kubernetes.io/allowedProfileNames : '*'
spec :
privileged : true
allowPrivilegeEscalation : true
allowedCapabilities :
- '*'
volumes :
- '*'
hostNetwork : true
hostPorts :
- min : 0
max : 65535
hostIPC : true
hostPID : true
runAsUser :
rule : 'RunAsAny'
seLinux :
rule : 'RunAsAny'
supplementalGroups :
rule : 'RunAsAny'
fsGroup :
rule : 'RunAsAny'
下面是一个具有约束性的策略,要求用户以非特权账号运行,禁止可能的向 root 权限
的升级,同时要求使用若干安全机制。
apiVersion : policy/v1beta1
kind : PodSecurityPolicy
metadata :
name : restricted
annotations :
seccomp.security.alpha.kubernetes.io/allowedProfileNames : 'docker/default,runtime/default'
apparmor.security.beta.kubernetes.io/allowedProfileNames : 'runtime/default'
seccomp.security.alpha.kubernetes.io/defaultProfileName : 'runtime/default'
apparmor.security.beta.kubernetes.io/defaultProfileName : 'runtime/default'
spec :
privileged : false
# Required to prevent escalations to root.
allowPrivilegeEscalation : false
# This is redundant with non-root + disallow privilege escalation,
# but we can provide it for defense in depth.
requiredDropCapabilities :
- ALL
# Allow core volume types.
volumes :
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that persistentVolumes set up by the cluster admin are safe to use.
- 'persistentVolumeClaim'
hostNetwork : false
hostIPC : false
hostPID : false
runAsUser :
# Require the container to run without root privileges.
rule : 'MustRunAsNonRoot'
seLinux :
# This policy assumes the nodes are using AppArmor rather than SELinux.
rule : 'RunAsAny'
supplementalGroups :
rule : 'MustRunAs'
ranges :
# Forbid adding the root group.
- min : 1
max : 65535
fsGroup :
rule : 'MustRunAs'
ranges :
# Forbid adding the root group.
- min : 1
max : 65535
readOnlyRootFilesystem : false
更多的示例可参考
Pod 安全标准 。
策略参考
Privileged
Privileged - 决定是否 Pod 中的某容器可以启用特权模式。
默认情况下,容器是不可以访问宿主上的任何设备的,不过一个“privileged(特权的)”
容器则被授权访问宿主上所有设备。
这种容器几乎享有宿主上运行的进程的所有访问权限。
对于需要使用 Linux 权能字(如操控网络堆栈和访问设备)的容器而言是有用的。
宿主名字空间
HostPID - 控制 Pod 中容器是否可以共享宿主上的进程 ID 空间。
注意,如果与 ptrace
相结合,这种授权可能被利用,导致向容器外的特权逃逸
(默认情况下 ptrace
是被禁止的)。
HostIPC - 控制 Pod 容器是否可共享宿主上的 IPC 名字空间。
HostNetwork - 控制是否 Pod 可以使用节点的网络名字空间。
此类授权将允许 Pod 访问本地回路(loopback)设备、在本地主机(localhost)
上监听的服务、还可能用来监听同一节点上其他 Pod 的网络活动。
HostPorts -提供可以在宿主网络名字空间中可使用的端口范围列表。
该属性定义为一组 HostPortRange
对象的列表,每个对象中包含
min
(含)与 max
(含)值的设置。
默认不允许访问宿主端口。
卷和文件系统
Volumes - 提供一组被允许的卷类型列表。可被允许的值对应于创建卷时可以
设置的卷来源。卷类型的完整列表可参见
卷类型 。
此外, *
可以用来允许所有卷类型。
对于新的 Pod 安全策略设置而言,建议设置的卷类型的最小列表 包含:
configMap
downwardAPI
emptyDir
persistentVolumeClaim
secret
projected
警告: PodSecurityPolicy 并不限制可以被 PersistentVolumeClaim
所引用的
PersistentVolume
对象的类型。
此外 hostPath
类型的 PersistentVolume
不支持只读访问模式。
应该仅赋予受信用户创建 PersistentVolume
对象的访问权限。
FSGroup - 控制应用到某些卷上的附加用户组。
MustRunAs - 要求至少指定一个 range
。
使用范围中的最小值作为默认值。所有 range 值都会被用来执行验证。
MayRunAs - 要求至少指定一个 range
。
允许不设置 FSGroups
,且无默认值。
如果 FSGroup
被设置,则所有 range 值都会被用来执行验证检查。
RunAsAny - 不提供默认值。允许设置任意 fsGroup
ID 值。
AllowedHostPaths - 设置一组宿主文件目录,这些目录项可以在 hostPath
卷中
使用。列表为空意味着对所使用的宿主目录没有限制。
此选项定义包含一个对象列表,表中对象包含 pathPrefix
字段,用来表示允许
hostPath
卷挂载以所指定前缀开头的路径。
对象中还包含一个 readOnly
字段,用来表示对应的卷必须以只读方式挂载。
例如:
allowedHostPaths :
# 下面的设置允许 "/foo"、"/foo/"、"/foo/bar" 等路径,但禁止
# "/fool"、"/etc/foo" 这些路径。
# "/foo/../" 总会被当作非法路径。
- pathPrefix : "/foo"
readOnly : true # 仅允许只读模式挂载
警告: 容器如果对宿主文件系统拥有不受限制的访问权限,就可以有很多种方式提升自己的特权,
包括读取其他容器中的数据、滥用系统服务(如 kubelet
)的凭据信息等。
由可写入的目录所构造的 hostPath
卷能够允许容器写入数据到宿主文件系统,
并且在写入时避开 pathPrefix
所设置的目录限制。
readOnly: true
这一设置在 Kubernetes 1.11 版本之后可用。
必须针对 allowedHostPaths
中的 所有 条目设置此属性才能有效地限制容器
只能访问 pathPrefix
所指定的目录。
ReadOnlyRootFilesystem - 要求容器必须以只读方式挂载根文件系统来运行
(即不允许存在可写入层)。
FlexVolume 驱动
此配置指定一个可以被 FlexVolume 卷使用的驱动程序的列表。
空的列表或者 nil 值意味着对驱动没有任何限制。
请确保volumes
字段包含了 flexVolume
卷类型,
否则所有 FlexVolume 驱动都被禁止。
apiVersion : policy/v1beta1
kind : PodSecurityPolicy
metadata :
name : allow-flex-volumes
spec :
# spec d的其他字段
volumes :
- flexVolume
allowedFlexVolumes :
- driver : example/lvm
- driver : example/cifs
用户和组
RunAsUser - 控制使用哪个用户 ID 来运行容器。
MustRunAs - 必须配置一个 range
。使用该范围内的第一个值作为默认值。
所有 range 值都被用于验证检查。
MustRunAsNonRoot - 要求提交的 Pod 具有非零 runAsUser
值,或在镜像中
(使用 UID 数值)定义了 USER
环境变量。
如果 Pod 既没有设置 runAsNonRoot
,也没有设置 runAsUser
,则该 Pod 会被
修改以设置 runAsNonRoot=true
,从而要求容器通过 USER
指令给出非零的数值形式
的用户 ID。此配置没有默认值。采用此配置时,强烈建议设置
allowPrivilegeEscalation=false
。
RunAsAny - 没有提供默认值。允许指定任何 runAsUser
配置。
RunAsGroup - 控制运行容器时使用的主用户组 ID。
MustRunAs - 要求至少指定一个 range
值。第一个 range
中的最小值作为默认值。所有 range 值都被用来执行验证检查。
MayRunAs - 不要求设置 RunAsGroup
。
不过,如果指定了 RunAsGroup
被设置,所设置值必须处于所定义的范围内。
RunAsAny - 未指定默认值。允许 runAsGroup
设置任何值。
SupplementalGroups - 控制容器可以添加的组 ID。
MustRunAs - 要求至少指定一个 range
值。
第一个 range 中的最小值用作默认值。
所有 range 值都被用来执行验证检查。
MayRunAs - 要求至少指定一个 range
值。
允许不指定 supplementalGroups
且不设置默认值。
如果 supplementalGroups
被设置,则所有 range 值都被用来执行验证检查。
RunAsAny - 未指定默认值。允许为 supplementalGroups
设置任何值。
特权提升
这一组选项控制容器的allowPrivilegeEscalation
属性。该属性直接决定是否为
容器进程设置
no_new_privs
参数。此参数会禁止 setuid
属性的可执行文件更改有效用户 ID(EUID),并且
禁止启用额外权能的文件。例如,no_new_privs
会禁止使用 ping
工具。
如果想有效地实施 MustRunAsNonRoot
控制,需要配置这一选项。
AllowPrivilegeEscalation - 决定是否用户可以将容器的安全上下文设置为
allowPrivilegeEscalation=true
。默认设置下,这样做是允许的,目的是避免
造成现有的 setuid
应用无法运行。将此选项设置为 false
可以确保容器的所有
子进程都无法获得比父进程更多的特权。
DefaultAllowPrivilegeEscalation - 为 allowPrivilegeEscalation
选项设置
默认值。不设置此选项时的默认行为是允许特权提升,以便运行 setuid 程序。
如果不希望运行 setuid 程序,可以使用此字段将选项的默认值设置为禁止,同时
仍然允许 Pod 显式地请求 allowPrivilegeEscalation
。
权能字
Linux 权能字(Capabilities)将传统上与超级用户相关联的特权作了细粒度的分解。
其中某些权能字可以用来提升特权,打破容器边界,可以通过 PodSecurityPolicy
来限制。关于 Linux 权能字的更多细节,可参阅
capabilities(7) 。
下列字段都可以配置为权能字的列表。表中的每一项都是 ALL_CAPS
中的一个权能字
名称,只是需要去掉 CAP_
前缀。
AllowedCapabilities - 给出可以被添加到容器的权能字列表。
默认的权能字集合是被隐式允许的那些。空集合意味着只能使用默认权能字集合,
不允许添加额外的权能字。*
可以用来设置允许所有权能字。
RequiredDropCapabilities - 必须从容器中去除的权能字。
所给的权能字会从默认权能字集合中去除,并且一定不可以添加。
RequiredDropCapabilities
中列举的权能字不能出现在
AllowedCapabilities
或 DefaultAddCapabilities
所给的列表中。
DefaultAddCapabilities - 默认添加到容器的权能字集合。
这一集合是作为容器运行时所设值的补充。
关于使用 Docker 容器运行引擎时默认的权能字列表,可参阅
Docker 文档 。
SELinux
MustRunAs - 要求必须配置 seLinuxOptions
。默认使用 seLinuxOptions
。
针对 seLinuxOptions
所给值执行验证检查。
RunAsAny - 没有提供默认值。允许任意指定的 seLinuxOptions
选项。
AllowedProcMountTypes
allowedProcMountTypes
是一组可以允许的 proc 挂载类型列表。
空表或者 nil 值表示只能使用 DefaultProcMountType
。
DefaultProcMount
使用容器运行时的默认值设置来决定 /proc
的只读挂载模式
和路径屏蔽。大多数容器运行时都会屏蔽 /proc
下面的某些路径以避免特殊设备或
信息被不小心暴露给容器。这一配置使所有 Default
字符串值来表示。
此外唯一的ProcMountType 是 UnmaskedProcMount
,意味着即将绕过容器运行时的
路径屏蔽行为,确保新创建的 /proc
不会被容器修改。此配置用字符串
Unmasked
来表示。
AppArmor
通过 PodSecurityPolicy 上的注解来控制。
详情请参阅
AppArmor 文档 。
Seccomp
Pod 对 seccomp 模版的使用可以通过在 PodSecurityPolicy 上设置注解来控制。
Seccomp 是 Kubernetes 的一项 alpha 阶段特性。
seccomp.security.alpha.kubernetes.io/defaultProfileName - 注解用来
指定为容器配置默认的 seccomp 模版。可选值为:
unconfined
- 如果没有指定其他替代方案,Seccomp 不会被应用到容器进程上
(Kubernets 中的默认设置)。
runtime/default
- 使用默认的容器运行时模版。
docker/default
- 使用 Docker 的默认 seccomp 模版。自 1.11 版本废弃。
应改为使用 runtime/default
。
localhost/<路径名>
- 指定节点上路径 <seccomp_root>/<路径名>
下的一个
文件作为其模版。其中 <seccomp_root>
是通过 kubelet
的标志
--seccomp-profile-root
来指定的。
seccomp.security.alpha.kubernetes.io/allowedProfileNames - 指定可以为
Pod seccomp 注解配置的值的注解。取值为一个可用值的列表。
表中每项可以是上述各值之一,还可以是 *
,用来表示允许所有的模版。
如果没有设置此注解,意味着默认的 seccomp 模版是不可更改的。
Sysctl
默认情况下,所有的安全的 sysctl 都是被允许的。
forbiddenSysctls
- 用来排除某些特定的 sysctl。
你可以在此列表中禁止一些安全的或者不安全的 sysctl。
此选项设置为 *
意味着禁止设置所有 sysctl。
allowedUnsafeSysctls
- 用来启用那些被默认列表所禁用的 sysctl,
前提是所启用的 sysctl 没有被列在 forbiddenSysctls
中。
参阅 Sysctl 文档 。
接下来
3.9.4 - 进程 ID 约束与预留
FEATURE STATE: 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.9.5 - 节点资源管理器
Kubernetes 提供了一组资源管理器,用于支持延迟敏感的、高吞吐量的工作负载。
资源管理器的目标是协调和优化节点资源,以支持对 CPU、设备和内存(巨页)等资源有特殊需求的 Pod。
主管理器,也叫拓扑管理器(Topology Manager),是一个 Kubelet 组件,
它通过策略 ,
协调全局的资源管理过程。
各个管理器的配置方式会在专项文档中详细阐述:
3.10 - 调度,抢占和驱逐
在Kubernetes中,调度 (scheduling) 指的是确保 Pods 匹配到合适的节点, 以便 kubelet 能够运行它们。抢占 (Preemption) 指的是终止低优先级的 Pods 以便高优先级的 Pods 可以 调度运行的过程。驱逐 (Eviction) 是在资源匮乏的节点上,主动让一个或多个 Pods 失效的过程。
调度
Pod 干扰
3.10.1 - Kubernetes 调度器
在 Kubernetes 中,调度 是指将 Pod 放置到合适的
Node 上,然后对应 Node 上的
Kubelet 才能够运行这些 pod。
调度概览
调度器通过 kubernetes 的监测(Watch)机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。
调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。
调度器会依据下文的调度原则来做出调度选择。
如果你想要理解 Pod 为什么会被调度到特定的 Node 上,或者你想要尝试实现
一个自定义的调度器,这篇文章将帮助你了解调度。
kube-scheduler
kube-scheduler
是 Kubernetes 集群的默认调度器,并且是集群
控制面 的一部分。
如果你真的希望或者有这方面的需求,kube-scheduler 在设计上是允许
你自己写一个调度组件并替换原有的 kube-scheduler。
对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler 会选择一个最优的
Node 去运行这个 Pod。然而,Pod 内的每一个容器对资源都有不同的需求,而且
Pod 本身也有不同的资源需求。因此,Pod 在被调度到 Node 上之前,
根据这些特定的资源调度需求,需要对集群中的 Node 进行一次过滤。
在一个集群中,满足一个 Pod 调度请求的所有 Node 称之为 可调度节点 。
如果没有任何一个 Node 能满足 Pod 的资源请求,那么这个 Pod 将一直停留在
未调度状态直到调度器能够找到合适的 Node。
调度器先在集群中找到一个 Pod 的所有可调度节点,然后根据一系列函数对这些可调度节点打分,
选出其中得分最高的 Node 来运行 Pod。之后,调度器将这个调度决定通知给
kube-apiserver,这个过程叫做 绑定 。
在做调度决定时需要考虑的因素包括:单独和整体的资源请求、硬件/软件/策略限制、
亲和以及反亲和要求、数据局域性、负载间的干扰等等。
kube-scheduler 调度流程
kube-scheduler 给一个 pod 做调度选择包含两个步骤:
过滤
打分
过滤阶段会将所有满足 Pod 调度需求的 Node 选出来。
例如,PodFitsResources 过滤函数会检查候选 Node 的可用资源能否满足 Pod 的资源请求。
在过滤之后,得出一个 Node 列表,里面包含了所有可调度节点;通常情况下,
这个 Node 列表包含不止一个 Node。如果这个列表是空的,代表这个 Pod 不可调度。
在打分阶段,调度器会为 Pod 从所有可调度节点中选取一个最合适的 Node。
根据当前启用的打分规则,调度器会给每一个可调度节点进行打分。
最后,kube-scheduler 会将 Pod 调度到得分最高的 Node 上。
如果存在多个得分最高的 Node,kube-scheduler 会从中随机选取一个。
支持以下两种方式配置调度器的过滤和打分行为:
调度策略 允许你配置过滤的 断言(Predicates)
和打分的 优先级(Priorities) 。
调度配置 允许你配置实现不同调度阶段的插件,
包括:QueueSort
, Filter
, Score
, Bind
, Reserve
, Permit
等等。
你也可以配置 kube-scheduler 运行不同的配置文件。
接下来
3.10.2 - 将 Pod 分配给节点
你可以约束一个 Pod 只能在特定的
节点 上运行。
有几种方法可以实现这点,推荐的方法都是用
标签选择算符 来进行选择。
通常这样的约束不是必须的,因为调度器将自动进行合理的放置(比如,将 Pod 分散到节点上,
而不是将 Pod 放置在可用资源不足的节点上等等)。但在某些情况下,你可能需要进一步控制
Pod 停靠的节点,例如,确保 Pod 最终落在连接了 SSD 的机器上,或者将来自两个不同的服务
且有大量通信的 Pods 被放置在同一个可用区。
nodeSelector
nodeSelector
是节点选择约束的最简单推荐形式。nodeSelector
是 PodSpec 的一个字段。
它包含键值对的映射。为了使 pod 可以在某个节点上运行,该节点的标签中
必须包含这里的每个键值对(它也可以具有其他标签)。
最常见的用法的是一对键值对。
让我们来看一个使用 nodeSelector
的例子。
步骤零:先决条件
本示例假设你已基本了解 Kubernetes 的 Pod 并且已经建立一个 Kubernetes 集群 。
步骤一:添加标签到节点
执行 kubectl get nodes
命令获取集群的节点名称。
选择一个你要增加标签的节点,然后执行
kubectl label nodes <node-name> <label-key>=<label-value>
命令将标签添加到你所选择的节点上。
例如,如果你的节点名称为 'kubernetes-foo-node-1.c.a-robinson.internal'
并且想要的标签是 'disktype=ssd',则可以执行
kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd
命令。
你可以通过重新运行 kubectl get nodes --show-labels
,
查看节点当前具有了所指定的标签来验证它是否有效。
你也可以使用 kubectl describe node "nodename"
命令查看指定节点的标签完整列表。
步骤二:添加 nodeSelector 字段到 Pod 配置中
选择任何一个你想运行的 Pod 的配置文件,并且在其中添加一个 nodeSelector 部分。
例如,如果下面是我的 pod 配置:
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
env : test
spec :
containers :
- name : nginx
image : nginx
然后像下面这样添加 nodeSelector:
apiVersion : v1
kind : Pod
metadata :
name : nginx
labels :
env : test
spec :
containers :
- name : nginx
image : nginx
imagePullPolicy : IfNotPresent
nodeSelector :
disktype : ssd
当你之后运行 kubectl apply -f https://k8s.io/examples/pods/pod-nginx.yaml
命令,
Pod 将会调度到将标签添加到的节点上。
你可以通过运行 kubectl get pods -o wide
并查看分配给 pod 的 “NODE” 来验证其是否有效。
插曲:内置的节点标签
除了你添加 的标签外,节点还预制了一组标准标签。
参见这些常用的标签,注解以及污点 :
说明:
这些标签的值是特定于云供应商的,因此不能保证可靠。
例如,kubernetes.io/hostname
的值在某些环境中可能与节点名称相同,
但在其他环境中可能是一个不同的值。
节点隔离/限制
向 Node 对象添加标签可以将 pod 定位到特定的节点或节点组。
这可以用来确保指定的 Pod 只能运行在具有一定隔离性,安全性或监管属性的节点上。
当为此目的使用标签时,强烈建议选择节点上的 kubelet 进程无法修改的标签键。
这可以防止受感染的节点使用其 kubelet 凭据在自己的 Node 对象上设置这些标签,
并影响调度器将工作负载调度到受感染的节点。
NodeRestriction
准入插件防止 kubelet 使用 node-restriction.kubernetes.io/
前缀设置或修改标签。要使用该标签前缀进行节点隔离:
检查是否在使用 Kubernetes v1.11+,以便 NodeRestriction 功能可用。
确保你在使用节点授权 并且已经_启用_
NodeRestriction 准入插件 。
将 node-restriction.kubernetes.io/
前缀下的标签添加到 Node 对象,
然后在节点选择器中使用这些标签。
例如,example.com.node-restriction.kubernetes.io/fips=true
或
example.com.node-restriction.kubernetes.io/pci-dss=true
。
亲和性与反亲和性
nodeSelector
提供了一种非常简单的方法来将 Pod 约束到具有特定标签的节点上。
亲和性/反亲和性功能极大地扩展了你可以表达约束的类型。关键的增强点包括:
语言更具表现力(不仅仅是“对完全匹配规则的 AND”)
你可以发现规则是“软需求”/“偏好”,而不是硬性要求,因此,
如果调度器无法满足该要求,仍然调度该 Pod
你可以使用节点上(或其他拓扑域中)的 Pod 的标签来约束,而不是使用
节点本身的标签,来允许哪些 pod 可以或者不可以被放置在一起。
亲和性功能包含两种类型的亲和性,即“节点亲和性”和“Pod 间亲和性/反亲和性”。
节点亲和性就像现有的 nodeSelector
(但具有上面列出的前两个好处),然而
Pod 间亲和性/反亲和性约束 Pod 标签而不是节点标签(在上面列出的第三项中描述,
除了具有上面列出的第一和第二属性)。
节点亲和性
节点亲和性概念上类似于 nodeSelector
,它使你可以根据节点上的标签来约束
Pod 可以调度到哪些节点。
目前有两种类型的节点亲和性,分别为 requiredDuringSchedulingIgnoredDuringExecution
和
preferredDuringSchedulingIgnoredDuringExecution
。
你可以视它们为“硬需求”和“软需求”,意思是,前者指定了将 Pod 调度到一个节点上
必须 满足的规则(就像 nodeSelector
但使用更具表现力的语法),
后者指定调度器将尝试执行但不能保证的偏好 。
名称的“IgnoredDuringExecution”部分意味着,类似于 nodeSelector
的工作原理,
如果节点的标签在运行时发生变更,从而不再满足 Pod 上的亲和性规则,那么 Pod
将仍然继续在该节点上运行。
将来我们计划提供 requiredDuringSchedulingRequiredDuringExecution
,
它将与 requiredDuringSchedulingIgnoredDuringExecution
完全相同,
只是它会将 Pod 从不再满足 Pod 的节点亲和性要求的节点上驱逐。
因此,requiredDuringSchedulingIgnoredDuringExecution
的示例将是
“仅将 Pod 运行在具有 Intel CPU 的节点上”,而
preferredDuringSchedulingIgnoredDuringExecution
的示例为
“尝试将这组 Pod 运行在 XYZ 故障区域,如果这不可能的话,则允许一些
Pod 在其他地方运行”。
节点亲和性通过 PodSpec 的 affinity
字段下的 nodeAffinity
字段进行指定。
下面是一个使用节点亲和性的 Pod 的实例:
apiVersion : v1
kind : Pod
metadata :
name : with-node-affinity
spec :
affinity :
nodeAffinity :
requiredDuringSchedulingIgnoredDuringExecution :
nodeSelectorTerms :
- matchExpressions :
- key : kubernetes.io/e2e-az-name
operator : In
values :
- e2e-az1
- e2e-az2
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 1
preference :
matchExpressions :
- key : another-node-label-key
operator : In
values :
- another-node-label-value
containers :
- name : with-node-affinity
image : k8s.gcr.io/pause:2.0
此节点亲和性规则表示,Pod 只能放置在具有标签键 kubernetes.io/e2e-az-name
且标签值为 e2e-az1
或 e2e-az2
的节点上。
另外,在满足这些标准的节点中,具有标签键为 another-node-label-key
且标签值为 another-node-label-value
的节点应该优先使用。
你可以在上面的例子中看到 In
操作符的使用。新的节点亲和性语法支持下面的操作符:
In
,NotIn
,Exists
,DoesNotExist
,Gt
,Lt
。
你可以使用 NotIn
和 DoesNotExist
来实现节点反亲和性行为,或者使用
节点污点
将 Pod 从特定节点中驱逐。
如果你同时指定了 nodeSelector
和 nodeAffinity
,两者 必须都要满足,
才能将 Pod 调度到候选节点上。
如果你指定了多个与 nodeAffinity
类型关联的 nodeSelectorTerms
,则
如果其中一个 nodeSelectorTerms
满足的话,pod将可以调度到节点上。
如果你指定了多个与 nodeSelectorTerms
关联的 matchExpressions
,则
只有当所有 matchExpressions
满足的话,Pod 才会可以调度到节点上。
如果你修改或删除了 pod 所调度到的节点的标签,Pod 不会被删除。
换句话说,亲和性选择只在 Pod 调度期间有效。
preferredDuringSchedulingIgnoredDuringExecution
中的 weight
字段值的
范围是 1-100。
对于每个符合所有调度要求(资源请求、RequiredDuringScheduling 亲和性表达式等)
的节点,调度器将遍历该字段的元素来计算总和,并且如果节点匹配对应的
MatchExpressions,则添加“权重”到总和。
然后将这个评分与该节点的其他优先级函数的评分进行组合。
总分最高的节点是最优选的。
逐个调度方案中设置节点亲和性
FEATURE STATE: Kubernetes v1.20 [beta]
在配置多个调度方案 时,
你可以将某个方案与节点亲和性关联起来,如果某个调度方案仅适用于某组
特殊的节点时,这样做是很有用的。
要实现这点,可以在调度器配置 中为
NodeAffinity
插件
添加 addedAffinity
参数。
例如:
apiVersion : kubescheduler.config.k8s.io/v1beta1
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
。
说明: DaemonSet 控制器
为 DaemonSet 创建 Pods ,
但该控制器不理会调度方案。因此,建议你保留一个调度方案,例如
default-scheduler
,不要在其中设置
addedAffinity
。
这样,DaemonSet 的 Pod 模板将会使用此调度器名称。
否则,DaemonSet 控制器所创建的某些 Pods 可能持续处于不可调度状态。
pod 间亲和性与反亲和性
Pod 间亲和性与反亲和性使你可以 基于已经在节点上运行的 Pod 的标签 来约束
Pod 可以调度到的节点,而不是基于节点上的标签。
规则的格式为“如果 X 节点上已经运行了一个或多个 满足规则 Y 的 Pod,
则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 X 节点”。
Y 表示一个具有可选的关联命令空间列表的 LabelSelector;
与节点不同,因为 Pod 是命名空间限定的(因此 Pod 上的标签也是命名空间限定的),
因此作用于 Pod 标签的标签选择算符必须指定选择算符应用在哪个命名空间。
从概念上讲,X 是一个拓扑域,如节点、机架、云供应商可用区、云供应商地理区域等。
你可以使用 topologyKey
来表示它,topologyKey
是节点标签的键以便系统
用来表示这样的拓扑域。
请参阅上面插曲:内置的节点标签 部分中列出的标签键。
说明:
Pod 间亲和性与反亲和性需要大量的处理,这可能会显著减慢大规模集群中的调度。
我们不建议在超过数百个节点的集群中使用它们。
说明:
Pod 反亲和性需要对节点进行一致的标记,即集群中的每个节点必须具有适当的标签能够匹配
topologyKey
。如果某些或所有节点缺少指定的 topologyKey
标签,可能会导致意外行为。
与节点亲和性一样,当前有两种类型的 Pod 亲和性与反亲和性,即
requiredDuringSchedulingIgnoredDuringExecution
和
preferredDuringSchedulingIgnoredDuringExecution
,分别表示“硬性”与“软性”要求。
请参阅前面节点亲和性部分中的描述。
requiredDuringSchedulingIgnoredDuringExecution
亲和性的一个示例是
“将服务 A 和服务 B 的 Pod 放置在同一区域,因为它们之间进行大量交流”,而
preferredDuringSchedulingIgnoredDuringExecution
反亲和性的示例将是
“将此服务的 pod 跨区域分布”(硬性要求是说不通的,因为你可能拥有的
Pod 数多于区域数)。
Pod 间亲和性通过 PodSpec 中 affinity
字段下的 podAffinity
字段进行指定。
而 Pod 间反亲和性通过 PodSpec 中 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 : k8s.gcr.io/pause:2.0
在这个 Pod 的亲和性配置定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。
在此示例中,podAffinity
配置为 requiredDuringSchedulingIgnoredDuringExecution
,
然而 podAntiAffinity
配置为 preferredDuringSchedulingIgnoredDuringExecution
。
Pod 亲和性规则表示,仅当节点和至少一个已运行且有键为“security”且值为“S1”的标签
的 Pod 处于同一区域时,才可以将该 Pod 调度到节点上。
(更确切的说,如果节点 N 具有带有键 topology.kubernetes.io/zone
和某个值 V 的标签,
则 Pod 有资格在节点 N 上运行,以便集群中至少有一个节点具有键
topology.kubernetes.io/zone
和值为 V 的节点正在运行具有键“security”和值
“S1”的标签的 pod。)
Pod 反亲和性规则表示,如果节点处于 Pod 所在的同一可用区且具有键“security”和值“S2”的标签,
则该 pod 不应将其调度到该节点上。
(如果 topologyKey
为 topology.kubernetes.io/zone
,则意味着当节点和具有键
“security”和值“S2”的标签的 Pod 处于相同的区域,Pod 不能被调度到该节点上。)
查阅设计文档
以获取 Pod 亲和性与反亲和性的更多样例,包括
requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
两种配置。
Pod 亲和性与反亲和性的合法操作符有 In
,NotIn
,Exists
,DoesNotExist
。
原则上,topologyKey
可以是任何合法的标签键。
然而,出于性能和安全原因,topologyKey 受到一些限制:
对于 Pod 亲和性而言,在 requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
中,topologyKey
不允许为空。
对于 Pod 反亲和性而言,requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
中,topologyKey
都不可以为空。
对于 requiredDuringSchedulingIgnoredDuringExecution
要求的 Pod 反亲和性,
准入控制器 LimitPodHardAntiAffinityTopology
被引入以确保 topologyKey
只能是 kubernetes.io/hostname
。如果你希望 topologyKey
也可用于其他定制
拓扑逻辑,你可以更改准入控制器或者禁用之。
除上述情况外,topologyKey
可以是任何合法的标签键。
除了 labelSelector
和 topologyKey
,你也可以指定表示命名空间的
namespaces
队列,labelSelector
也应该匹配它
(这个与 labelSelector
和 topologyKey
的定义位于相同的级别)。
如果忽略或者为空,则默认为 Pod 亲和性/反亲和性的定义所在的命名空间。
所有与 requiredDuringSchedulingIgnoredDuringExecution
亲和性与反亲和性
关联的 matchExpressions
必须满足,才能将 pod 调度到节点上。
名字空间选择算符
FEATURE STATE: Kubernetes v1.22 [beta]
用户也可以使用 namespaceSelector
选择匹配的名字空间,namespaceSelector
是对名字空间集合进行标签查询的机制。
亲和性条件会应用到 namespaceSelector
所选择的名字空间和 namespaces
字段中
所列举的名字空间之上。
注意,空的 namespaceSelector
({})会匹配所有名字空间,而 null 或者空的
namespaces
列表以及 null 值 namespaceSelector
意味着“当前 Pod 的名字空间”。
此功能特性是 Beta 版本的,默认是被启用的。你可以通过针对 kube-apiserver 和
kube-scheduler 设置
特性门控
PodAffinityNamespaceSelector
来禁用此特性。
更实际的用例
Pod 间亲和性与反亲和性在与更高级别的集合(例如 ReplicaSets、StatefulSets、
Deployments 等)一起使用时,它们可能更加有用。
可以轻松配置一组应位于相同定义拓扑(例如,节点)中的工作负载。
始终放置在相同节点上
在三节点集群中,一个 web 应用程序具有内存缓存,例如 redis。
我们希望 web 服务器尽可能与缓存放置在同一位置。
下面是一个简单 redis Deployment 的 YAML 代码段,它有三个副本和选择器标签 app=store
。
Deployment 配置了 PodAntiAffinity
,用来确保调度器不会将副本调度到单个节点上。
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
下面 webserver Deployment 的 YAML 代码段中配置了 podAntiAffinity
和 podAffinity
。
这将通知调度器将它的所有副本与具有 app=store
选择器标签的 Pod 放置在一起。
这还确保每个 web 服务器副本不会调度到单个节点上。
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,我们的三节点集群将如下表所示。
node-1
node-2
node-3
webserver-1
webserver-2
webserver-3
cache-1
cache-2
cache-3
如你所见,web-server
的三个副本都按照预期那样自动放置在同一位置。
kubectl get pods -o wide
输出类似于如下内容:
NAME READY STATUS RESTARTS AGE IP NODE
redis-cache-1450370735-6dzlj 1/1 Running 0 8m 10.192.4.2 kube-node-3
redis-cache-1450370735-j2j96 1/1 Running 0 8m 10.192.2.2 kube-node-1
redis-cache-1450370735-z73mh 1/1 Running 0 8m 10.192.3.1 kube-node-2
web-server-1287567482-5d4dz 1/1 Running 0 7m 10.192.2.3 kube-node-1
web-server-1287567482-6f7v5 1/1 Running 0 7m 10.192.4.3 kube-node-3
web-server-1287567482-s330j 1/1 Running 0 7m 10.192.3.2 kube-node-2
永远不放置在相同节点
上面的例子使用 PodAntiAffinity
规则和 topologyKey: "kubernetes.io/hostname"
来部署 redis 集群以便在同一主机上没有两个实例。
参阅 ZooKeeper 教程 ,
以获取配置反亲和性来达到高可用性的 StatefulSet 的样例(使用了相同的技巧)。
nodeName
nodeName
是节点选择约束的最简单方法,但是由于其自身限制,通常不使用它。
nodeName
是 PodSpec 的一个字段。
如果它不为空,调度器将忽略 Pod,并且给定节点上运行的 kubelet 进程尝试执行该 Pod。
因此,如果 nodeName
在 PodSpec 中指定了,则它优先于上面的节点选择方法。
使用 nodeName
来选择节点的一些限制:
如果指定的节点不存在,
如果指定的节点没有资源来容纳 Pod,Pod 将会调度失败并且其原因将显示为,
比如 OutOfmemory 或 OutOfcpu。
云环境中的节点名称并非总是可预测或稳定的。
下面的是使用 nodeName
字段的 Pod 配置文件的例子:
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
containers :
- name : nginx
image : nginx
nodeName : kube-01
上面的 pod 将运行在 kube-01 节点上。
接下来
污点
允许节点排斥 一组 Pod。
节点亲和性 与
pod 间亲和性/反亲和性
的设计文档包含这些功能的其他背景信息。
一旦 Pod 分配给 节点,kubelet 应用将运行该 pod 并且分配节点本地资源。
拓扑管理器
可以参与到节点级别的资源分配决定中。
3.10.3 - Pod 开销
FEATURE STATE: Kubernetes v1.18 [beta]
在节点上运行 Pod 时,Pod 本身占用大量系统资源。这些资源是运行 Pod 内容器所需资源的附加资源。
POD 开销 是一个特性,用于计算 Pod 基础设施在容器请求和限制之上消耗的资源。
Pod 开销
在 Kubernetes 中,Pod 的开销是根据与 Pod 的 RuntimeClass 相关联的开销在
准入 时设置的。
如果启用了 Pod Overhead,在调度 Pod 时,除了考虑容器资源请求的总和外,还要考虑 Pod 开销。
类似地,kubelet 将在确定 Pod cgroups 的大小和执行 Pod 驱逐排序时也会考虑 Pod 开销。
启用 Pod 开销
您需要确保在集群中启用了 PodOverhead
特性门控
(在 1.18 默认是开启的),以及一个用于定义 overhead
字段的 RuntimeClass
。
使用示例
要使用 PodOverhead 特性,需要一个定义 overhead
字段的 RuntimeClass。
作为例子,可以在虚拟机和寄宿操作系统中通过一个虚拟化容器运行时来定义
RuntimeClass 如下,其中每个 Pod 大约使用 120MiB:
---
kind : RuntimeClass
apiVersion : node.k8s.io/v1
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
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 或者 Bustrable 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 内存,包含了 PodOverhead 在内:
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 的标识符:
# 在该 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
指标来协助确定何时使用 PodOverhead 以及协助观察以一个既定
开销运行的工作负载的稳定性。
该特性在 kube-state-metrics 的 1.9 发行版本中不可用,不过预计将在后续版本中发布。
在此之前,用户需要从源代码构建 kube-state-metrics。
接下来
3.10.4 - 污点和容忍度
节点亲和性
是 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-
您可以在 PodSpec 中定义 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 都匹配,即这个容忍度能容忍任意 taint。
如果 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 添加一个相对应的 toleration(通过编写一个自定义的
准入控制器 ,很容易就能做到)。
拥有上述容忍度的 Pod 就能够被分配到上述专用节点,同时也能够被分配到集群中的其它节点。
如果您希望这些 Pod 只能被分配到上述专用节点,那么您还需要给这些专用节点另外添加一个和上述
污点类似的 label (例如:dedicated=groupName
),同时 还要在上述准入控制器中给 Pod
增加节点亲和性要求上述 Pod 只能被分配到添加了 dedicated=groupName
标签的节点上。
配备了特殊硬件的节点 :在部分节点配备了特殊硬件(比如 GPU)的集群中,
我们希望不需要这类硬件的 Pod 不要被分配到这些特殊节点,以便为后继需要这类硬件的 Pod 保留资源。
要达到这个目的,可以先给配备了特殊硬件的节点添加 taint
(例如 kubectl taint nodes nodename special=true:NoSchedule
或
kubectl taint nodes nodename special=true:PreferNoSchedule
),
然后给使用了这类特殊硬件的 Pod 添加一个相匹配的 toleration。
和专用节点的例子类似,添加这个容忍度的最简单的方法是使用自定义
准入控制器 。
比如,我们推荐使用扩展资源
来表示特殊硬件,给配置了特殊硬件的节点添加污点时包含扩展资源名称,
然后运行一个 ExtendedResourceToleration
准入控制器。此时,因为节点已经被设置污点了,没有对应容忍度的 Pod
不会被调度到这些节点。但当你创建一个使用了扩展资源的 Pod 时,
ExtendedResourceToleration
准入控制器会自动给 Pod 加上正确的容忍度,
这样 Pod 就会被自动调度到这些配置了特殊硬件件的节点上。
这样就能够确保这些配置了特殊硬件的节点专门用于运行需要使用这些硬件的 Pod,
并且您无需手动给这些 Pod 添加容忍度。
基于污点的驱逐 : 这是在每个 Pod 中配置的在节点出现问题时的驱逐行为,接下来的章节会描述这个特性。
基于污点的驱逐
FEATURE STATE: Kubernetes v1.18 [stable]
前文提到过污点的 effect 值 NoExecute
会影响已经在节点上运行的 Pod
如果 Pod 不能忍受 effect 值为 NoExecute
的污点,那么 Pod 将马上被驱逐
如果 Pod 能够忍受 effect 值为 NoExecute
的污点,但是在容忍度定义中没有指定
tolerationSeconds
,则 Pod 还会一直在这个节点上运行。
如果 Pod 能够忍受 effect 值为 NoExecute
的污点,而且指定了 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 驱逐
速率限制 行为正常,
系统实际上会以限定速率的方式添加污点。在像主控节点与工作节点间通信中断等场景下,
这样做可以避免 Pod 被大量驱逐。
使用这个功能特性,结合 tolerationSeconds
,Pod 就可以指定当节点出现一个
或全部上述问题时还将在这个节点上运行多长的时间。
比如,一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间,
愿意等待网络恢复以避免被驱逐。在这种情况下,Pod 的容忍度可能是下面这样的:
tolerations :
- key : "node.kubernetes.io/unreachable"
operator : "Exists"
effect : "NoExecute"
tolerationSeconds : 6000
说明: Kubernetes 会自动给 Pod 添加一个 key 为 node.kubernetes.io/not-ready
的容忍度
并配置 tolerationSeconds=300
,除非用户提供的 Pod 配置中已经已存在了 key 为
node.kubernetes.io/not-ready
的容忍度。
同样,Kubernetes 会给 Pod 添加一个 key 为 node.kubernetes.io/unreachable
的容忍度
并配置 tolerationSeconds=300
,除非用户提供的 Pod 配置中已经已存在了 key 为
node.kubernetes.io/unreachable
的容忍度。
这种自动添加的容忍度意味着在其中一种问题被检测到时 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.10.5 - Pod 优先级和抢占
FEATURE STATE: 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
FEATURE STATE: Kubernetes v1.19 [beta]
配置了 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.10.6 - 节点压力驱逐
节点压力驱逐是 kubelet 主动终止 Pod 以回收节点上资源的过程。
kubelet
监控集群节点的 CPU、内存、磁盘空间和文件系统的 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 指定自定义驱逐条件,以便在作出驱逐决定时使用。
驱逐条件的形式为 [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
、docker
和 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.10.7 - API 发起的驱逐
API 发起的驱逐是一个先调用
Eviction API
创建驱逐对象,再由该对象体面地中止 Pod 的过程。
你可以通过 kube-apiserver 的客户端,比如 kubectl drain
这样的命令,直接调用 Eviction API 发起驱逐。
此操作创建一个 Eviction
对象,该对象再驱动 API 服务器终止选定的 Pod。
API 发起的驱逐将遵从你的
PodDisruptionBudgets
和 terminationGracePeriodSeconds
配置。
接下来
3.10.8 - 扩展资源的资源装箱
FEATURE STATE: Kubernetes 1.16 [alpha]
使用 RequestedToCapacityRatioResourceAllocation
优先级函数,可以将 kube-scheduler
配置为支持包含扩展资源在内的资源装箱操作。
优先级函数可用于根据自定义需求微调 kube-scheduler 。
使用 RequestedToCapacityRatioResourceAllocation 启用装箱
Kubernetes 允许用户指定资源以及每类资源的权重,
以便根据请求数量与可用容量之比率为节点评分。
这就使得用户可以通过使用适当的参数来对扩展资源执行装箱操作,从而提高了大型集群中稀缺资源的利用率。
RequestedToCapacityRatioResourceAllocation
优先级函数的行为可以通过名为
RequestedToCapacityRatioArgs
的配置选项进行控制。
该标志由两个参数 shape
和 resources
组成。
shape
允许用户根据 utilization
和 score
值将函数调整为
最少请求(least requested)或最多请求(most requested)计算。
resources
包含由 name
和 weight
组成,name
指定评分时要考虑的资源,
weight
指定每种资源的权重。
以下是一个配置示例,该配置将 requestedToCapacityRatioArguments
设置为对扩展资源
intel.com/foo
和 intel.com/bar
的装箱行为
apiVersion : kubescheduler.config.k8s.io/v1beta1
kind : KubeSchedulerConfiguration
profiles :
# ...
pluginConfig :
- name : RequestedToCapacityRatio
args :
shape :
- utilization : 0
score : 10
- utilization : 100
score : 0
resources :
- name : intel.com/foo
weight : 3
- name : intel.com/bar
weight : 5
使用 kube-scheduler 标志 --config=/path/to/config/file
引用 KubeSchedulerConfiguration
文件将配置传递给调度器。
默认情况下此功能处于被禁用状态
调整 RequestedToCapacityRatioResourceAllocation 优先级函数
shape
用于指定 RequestedToCapacityRatioPriority
函数的行为。
{"utilization": 0, "score": 0 },
{"utilization": 100, "score": 10 }
上面的参数在 utilization
为 0% 时给节点评分为 0,在 utilization
为
100% 时给节点评分为 10,因此启用了装箱行为。
要启用最少请求(least requested)模式,必须按如下方式反转得分值。
{"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 不能设置为负值。
RequestedToCapacityRatioResourceAllocation 优先级函数如何对节点评分
本节适用于希望了解此功能的内部细节的人员。
以下是如何针对给定的一组值来计算节点得分的示例。
请求的资源
intel.com/foo: 2
Memory: 256MB
CPU: 2
资源权重
intel.com/foo: 5
Memory: 1
CPU: 3
FunctionShapePoint {{0, 0}, {100, 10}}
节点 Node 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
= rawScoringFunction(75)
= 7
Memory = resourceScoringFunction((256+256),1024)
= (100 -((1024-512)*100/1024))
= 50
= rawScoringFunction(50)
= 5
CPU = resourceScoringFunction((2+1),8)
= (100 -((8-3)*100/8))
= 37.5
= rawScoringFunction(37.5)
= 3
NodeScore = (7 * 5) + (5 * 1) + (3 * 3) / (5 + 1 + 3)
= 5
节点 Node 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.10.9 - 调度框架
FEATURE STATE: Kubernetes 1.19 [stable]
调度框架是面向 Kubernetes 调度器的一种插件架构,
它为现有的调度器添加了一组新的“插件” API。插件会被编译到调度器之中。
这些 API 允许大多数调度功能以插件的形式实现,同时使调度“核心”保持简单且可维护。
请参考调度框架的设计提案
获取框架设计的更多技术信息。
框架工作流程
调度框架定义了一些扩展点。调度器插件注册后在一个或多个扩展点处被调用。
这些插件中的一些可以改变调度决策,而另一些仅用于提供信息。
每次调度一个 Pod 的尝试都分为两个阶段,即 调度周期 和 绑定周期 。
调度周期和绑定周期
调度周期为 Pod 选择一个节点,绑定周期将该决策应用于集群。
调度周期和绑定周期一起被称为“调度上下文”。
调度周期是串行运行的,而绑定周期可能是同时运行的。
如果确定 Pod 不可调度或者存在内部错误,则可以终止调度周期或绑定周期。
Pod 将返回队列并重试。
扩展点
下图显示了一个 Pod 的调度上下文以及调度框架公开的扩展点。
在此图片中,“过滤器”等同于“断言”,“评分”相当于“优先级函数”。
一个插件可以在多个扩展点处注册,以执行更复杂或有状态的任务。
调度框架扩展点
队列排序
队列排序插件用于对调度队列中的 Pod 进行排序。
队列排序插件本质上提供 less(Pod1, Pod2)
函数。
一次只能启动一个队列插件。
前置过滤
前置过滤插件用于预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。
如果 PreFilter 插件返回错误,则调度周期将终止。
过滤
过滤插件用于过滤出不能运行该 Pod 的节点。对于每个节点,
调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行,
则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
后置过滤
这些插件在筛选阶段后调用,但仅在该 Pod 没有可行的节点时调用。
插件按其配置的顺序调用。如果任何后过滤器插件标记节点为“可调度”,
则其余的插件不会调用。典型的后筛选实现是抢占,试图通过抢占其他 Pod
的资源使该 Pod 可以调度。
前置评分
前置评分插件用于执行 “前置评分” 工作,即生成一个可共享状态供评分插件使用。
如果 PreScore 插件返回错误,则调度周期将终止。
评分
评分插件用于对通过过滤阶段的节点进行排名。调度器将为每个节点调用每个评分插件。
将有一个定义明确的整数范围,代表最小和最大分数。
在标准化评分 阶段之后,调度器将根据配置的插件权重
合并所有插件的节点分数。
标准化评分
标准化评分插件用于在调度器计算节点的排名之前修改分数。
在此扩展点注册的插件将使用同一插件的评分 结果被调用。
每个插件在每个调度周期调用一次。
例如,假设一个 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 处于保留状态,它将在绑定周期结束时触发不保留 插件
(失败时)或 绑定后 插件(成功时)。
Permit
Permit 插件在每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定。
一个允许插件可以做以下三件事之一:
批准
一旦所有 Permit 插件批准 Pod 后,该 Pod 将被发送以进行绑定。
拒绝
如果任何 Permit 插件拒绝 Pod,则该 Pod 将被返回到调度队列。
这将触发Unreserve 插件。
等待 (带有超时)
如果一个 Permit 插件返回 “等待” 结果,则 Pod 将保持在一个内部的 “等待中”
的 Pod 列表,同时该 Pod 的绑定周期启动时即直接阻塞直到得到
批准 。如果超时发生,等待 变成 拒绝 ,并且 Pod
将返回调度队列,从而触发 Unreserve 插件。
说明: 尽管任何插件可以访问 “等待中” 状态的 Pod 列表并批准它们
(查看
FrameworkHandle
)。
我们期望只有允许插件可以批准处于 “等待中” 状态的预留 Pod 的绑定。
一旦 Pod 被批准了,它将发送到
预绑定 阶段。
预绑定
预绑定插件用于执行 Pod 绑定前所需的任何工作。
例如,一个预绑定插件可能需要提供网络卷并且在允许 Pod 运行在该节点之前
将其挂载到目标节点上。
如果任何 PreBind 插件返回错误,则 Pod 将被拒绝 并且
退回到调度队列中。
Bind
Bind 插件用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。
各绑定插件按照配置顺序被调用。绑定插件可以选择是否处理指定的 Pod。
如果绑定插件选择处理 Pod,剩余的绑定插件将被跳过 。
绑定后
这是个信息性的扩展点。
绑定后插件在 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.10.10 - 调度器性能调优
FEATURE STATE: 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 - 集群管理
关于创建和管理 Kubernetes 集群的底层细节。
集群管理概述面向任何创建和管理 Kubernetes 集群的读者人群。
我们假设你大概了解一些核心的 Kubernetes 概念 。
规划集群
查阅安装 中的指导,获取如何规划、建立以及配置 Kubernetes
集群的示例。本文所列的文章称为发行版 。
说明: 并非所有发行版都是被积极维护的。
请选择使用最近 Kubernetes 版本测试过的发行版。
在选择一个指南前,有一些因素需要考虑:
你是打算在你的计算机上尝试 Kubernetes,还是要构建一个高可用的多节点集群?
请选择最适合你需求的发行版。
你正在使用类似 Google Kubernetes Engine
这样的被托管的 Kubernetes 集群 , 还是管理你自己的集群 ?
你的集群是在本地 还是云(IaaS) 上?Kubernetes 不能直接支持混合集群。
作为代替,你可以建立多个集群。
如果你在本地配置 Kubernetes ,需要考虑哪种
网络模型 最适合。
你的 Kubernetes 在裸金属硬件 上还是虚拟机(VMs) 上运行?
你是想运行一个集群 ,还是打算参与开发 Kubernetes 项目代码 ?
如果是后者,请选择一个处于开发状态的发行版。
某些发行版只提供二进制发布版,但提供更多的选择。
让你自己熟悉运行一个集群所需的组件 。
管理集群
保护集群
保护 kubelet
可选集群服务
DNS 集成
描述了如何将一个 DNS 名解析到一个 Kubernetes service。
记录和监控集群活动
阐述了 Kubernetes 的日志如何工作以及怎样实现。
3.11.1 - 证书
要了解如何为集群生成证书,参阅证书 。
3.11.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/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.11.3 - 集群网络系统
集群网络系统是 Kubernetes 的核心部分,但是想要准确了解它的工作原理可是个不小的挑战。
下面列出的是网络系统的的四个主要问题:
高度耦合的容器间通信:这个已经被 Pods
和 localhost
通信解决了。
Pod 间通信:这个是本文档的重点要讲述的。
Pod 和服务间通信:这个已经在服务 里讲述过了。
外部和服务间通信:这也已经在服务 讲述过了。
Kubernetes 的宗旨就是在应用之间共享机器。
通常来说,共享机器需要两个应用之间不能使用相同的端口,但是在多个应用开发者之间
去大规模地协调端口是件很困难的事情,尤其是还要让用户暴露在他们控制范围之外的集群级别的问题上。
动态分配端口也会给系统带来很多复杂度 - 每个应用都需要设置一个端口的参数,
而 API 服务器还需要知道如何将动态端口数值插入到配置模块中,服务也需要知道如何找到对方等等。
与其去解决这些问题,Kubernetes 选择了其他不同的方法。
Kubernetes 网络模型
每一个 Pod
都有它自己的IP地址,这就意味着你不需要显式地在每个 Pod
之间创建链接,
你几乎不需要处理容器端口到主机端口之间的映射。
这将创建一个干净的、向后兼容的模型,在这个模型里,从端口分配、命名、服务发现、
负载均衡、应用配置和迁移的角度来看,Pod
可以被视作虚拟机或者物理主机。
Kubernetes 对所有网络设施的实施,都需要满足以下的基本要求(除非有设置一些特定的网络分段策略):
节点上的 Pod 可以不通过 NAT 和其他任何节点上的 Pod 通信
节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有Pod通信
备注:仅针对那些支持 Pods
在主机网络中运行的平台(比如:Linux):
那些运行在节点的主机网络里的 Pod 可以不通过 NAT 和所有节点上的 Pod 通信
这个模型不仅不复杂,而且还和 Kubernetes 的实现廉价的从虚拟机向容器迁移的初衷相兼容,
如果你的工作开始是在虚拟机中运行的,你的虚拟机有一个 IP ,
这样就可以和其他的虚拟机进行通信,这是基本相同的模型。
Kubernetes 的 IP 地址存在于 Pod
范围内 - 容器共享它们的网络命名空间 - 包括它们的 IP 地址和 MAC 地址。
这就意味着 Pod
内的容器都可以通过 localhost
到达各个端口。
这也意味着 Pod
内的容器都需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同,
这也被称为“一个 Pod 一个 IP”模型。
如何实现这一点是正在使用的容器运行时的特定信息。
也可以在 node
本身通过端口去请求你的 Pod
(称之为主机端口),
但这是一个很特殊的操作。转发方式如何实现也是容器运行时的细节。
Pod
自己并不知道这些主机端口是否存在。
如何实现 Kubernetes 的网络模型
有很多种方式可以实现这种网络模型,本文档并不是对各种实现技术的详细研究,
但是希望可以作为对各种技术的详细介绍,并且成为你研究的起点。
接下来的网络技术是按照首字母排序,顺序本身并无其他意义。
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
ACI
Cisco Application Centric Infrastructure
提供了一个集成覆盖网络和底层 SDN 的解决方案来支持容器、虚拟机和其他裸机服务器。
ACI 为 ACI 提供了容器网络集成。
点击这里 查看概述。
Antrea
Antrea 项目是一个开源的联网解决方案,旨在成为
Kubernetes 原生的网络解决方案。它利用 Open vSwitch 作为网络数据平面。
Open vSwitch 是一个高性能可编程的虚拟交换机,支持 Linux 和 Windows 平台。
Open vSwitch 使 Antrea 能够以高性能和高效的方式实现 Kubernetes 的网络策略。
借助 Open vSwitch 可编程的特性,Antrea 能够在 Open vSwitch 之上实现广泛的联网、安全功能和服务。
Apstra 的 AOS
AOS 是一个基于意图的网络系统,
可以通过一个简单的集成平台创建和管理复杂的数据中心环境。
AOS 利用高度可扩展的分布式设计来消除网络中断,同时将成本降至最低。
AOS 参考设计当前支持三层连接的主机,这些主机消除了旧的两层连接的交换问题。
这些三层连接的主机可以是 Linux(Debian、Ubuntu、CentOS)系统,
它们直接在机架式交换机(TOR)的顶部创建 BGP 邻居关系。
AOS 自动执行路由邻接,然后提供对 Kubernetes 部署中常见的路由运行状况注入(RHI)的精细控制。
AOS 具有一组丰富的 REST API 端点,这些端点使 Kubernetes 能够根据应用程序需求快速更改网络策略。
进一步的增强功能将用于网络设计的 AOS Graph 模型与工作负载供应集成在一起,
从而为私有云和公共云提供端到端管理系统。
AOS 支持使用包括 Cisco、Arista、Dell、Mellanox、HPE 在内的制造商提供的通用供应商设备,
以及大量白盒系统和开放网络操作系统,例如 Microsoft SONiC、Dell OPX 和 Cumulus Linux。
想要更详细地了解 AOS 系统是如何工作的可以点击这里:https://www.apstra.com/products/how-it-works/
Kubernetes 的 AWS VPC CNI
AWS VPC CNI 为 Kubernetes 集群提供了集成的
AWS 虚拟私有云(VPC)网络。该 CNI 插件提供了高吞吐量和可用性,低延迟以及最小的网络抖动。
此外,用户可以使用现有的 AWS VPC 网络和安全最佳实践来构建 Kubernetes 集群。
这包括使用 VPC 流日志、VPC 路由策略和安全组进行网络流量隔离的功能。
使用该 CNI 插件,可使 Kubernetes Pod 拥有与在 VPC 网络上相同的 IP 地址。
CNI 将 AWS 弹性网络接口(ENI)分配给每个 Kubernetes 节点,并将每个 ENI 的辅助 IP 范围用于该节点上的 Pod 。
CNI 包含用于 ENI 和 IP 地址的预分配的控件,以便加快 Pod 的启动时间,并且能够支持多达 2000 个节点的大型集群。
此外,CNI 可以与
用于执行网络策略的 Calico 一起运行。
AWS VPC CNI 项目是开源的,请查看 GitHub 上的文档 。
Kubernetes 的 Azure CNI
Azure CNI
是一个开源插件 ,
将 Kubernetes Pods 和 Azure 虚拟网络(也称为 VNet)集成在一起,可提供与 VM 相当的网络性能。
Pod 可以通过 Express Route 或者 站点到站点的 VPN 来连接到对等的 VNet ,
也可以从这些网络来直接访问 Pod。Pod 可以访问受服务端点或者受保护链接的 Azure 服务,比如存储和 SQL。
你可以使用 VNet 安全策略和路由来筛选 Pod 流量。
该插件通过利用在 Kubernetes 节点的网络接口上预分配的辅助 IP 池将 VNet 分配给 Pod 。
Azure CNI 可以在
Azure Kubernetes Service (AKS) 中获得。
Big Switch Networks 的 Big Cloud Fabric
Big Cloud Fabric 是一个基于云原生的网络架构,
旨在在私有云或者本地环境中运行 Kubernetes。
它使用统一的物理和虚拟 SDN,Big Cloud Fabric 解决了固有的容器网络问题,
比如负载均衡、可见性、故障排除、安全策略和容器流量监控。
在 Big Cloud Fabric 的虚拟 Pod 多租户架构的帮助下,容器编排系统
(比如 Kubernetes、RedHat OpenShift、Mesosphere DC/OS 和 Docker Swarm)
将与 VM 本地编排系统(比如 VMware、OpenStack 和 Nutanix)进行本地集成。
客户将能够安全地互联任意数量的这些集群,并且在需要时启用他们之间的租户间通信。
在最新的 Magic Quadrant 上,
BCF 被 Gartner 认为是非常有远见的。
而 BCF 的一条关于 Kubernetes 的本地部署(其中包括 Kubernetes、DC/OS 和在不同地理区域的多个
DC 上运行的 VMware)也在这里 被引用。
Calico
Calico 是一个开源的联网及网络安全方案,
用于基于容器、虚拟机和本地主机的工作负载。
Calico 支持多个数据面,包括:纯 Linux eBPF 的数据面、标准的 Linux 联网数据面
以及 Windows HNS 数据面。Calico 在提供完整的联网堆栈的同时,还可与
云驱动 CNIs 联合使用,以保证网络策略实施。
Cilium
Cilium 是一个开源软件,用于提供并透明保护应用容器间的网络连接。
Cilium 支持 L7/HTTP,可以在 L3-L7 上通过使用与网络分离的基于身份的安全模型寻址来实施网络策略,
并且可以与其他 CNI 插件结合使用。
华为的 CNI-Genie
CNI-Genie 是一个 CNI 插件,
可以让 Kubernetes 在运行时使用不同的网络模型 的
实现同时被访问 。
这包括以
CNI 插件 运行的任何实现,比如
Flannel 、
Calico 、
Romana 、
Weave-net 。
CNI-Genie 还支持将多个 IP 地址分配给 Pod ,
每个都来自不同的 CNI 插件。
cni-ipvlan-vpc-k8s
cni-ipvlan-vpc-k8s
包含了一组 CNI 和 IPAM 插件来提供一个简单的、本地主机、低延迟、高吞吐量
以及通过使用 Amazon 弹性网络接口(ENI)并使用 Linux 内核的 IPv2 驱动程序
以 L2 模式将 AWS 管理的 IP 绑定到 Pod 中,
在 Amazon Virtual Private Cloud(VPC)环境中为 Kubernetes 兼容的网络堆栈。
这些插件旨在直接在 VPC 中进行配置和部署,Kubelets 先启动,
然后根据需要进行自我配置和扩展它们的 IP 使用率,而无需经常建议复杂的管理
覆盖网络、BGP、禁用源/目标检查或调整 VPC 路由表以向每个主机提供每个实例子网的
复杂性(每个 VPC 限制为50-100个条目)。
简而言之,cni-ipvlan-vpc-k8s 大大降低了在 AWS 中大规模部署 Kubernetes 所需的网络复杂性。
Coil
Coil 是一个为易于集成、提供灵活的出站流量网络而设计的 CNI 插件。
与裸机相比,Coil 的额外操作开销低,并允许针对外部网络的出站流量任意定义 NAT 网关。
Contiv
Contiv
为各种使用情况提供了一个可配置网络(使用了 BGP 的本地 L3,
使用 vxlan 、经典 L2 或 Cisco-SDN/ACI 的覆盖网络)。
Contiv 是完全开源的。
Contrail/Tungsten Fabric
Contrail
是基于 Tungsten Fabric 的,真正开放的多云网络虚拟化和策略管理平台。
Contrail 和 Tungsten Fabric 与各种编排系统集成在一起,例如 Kubernetes、OpenShift、OpenStack 和 Mesos,
并为虚拟机、容器或 Pods 以及裸机工作负载提供了不同的隔离模式。
DANM
DANM 是一个针对在 Kubernetes 集群中运行的电信工作负载的网络解决方案。
它由以下几个组件构成:
能够配置具有高级功能的 IPVLAN 接口的 CNI 插件
一个内置的 IPAM 模块,能够管理多个、群集内的、不连续的 L3 网络,并按请求提供动态、静态或无 IP 分配方案
CNI 元插件能够通过自己的 CNI 或通过将任务授权给其他任何流行的 CNI 解决方案(例如 SRI-OV 或 Flannel)来实现将多个网络接口连接到容器
Kubernetes 控制器能够集中管理所有 Kubernetes 主机的 VxLAN 和 VLAN 接口
另一个 Kubernetes 控制器扩展了 Kubernetes 的基于服务的服务发现概念,以在 Pod 的所有网络接口上工作
通过这个工具集,DANM 可以提供多个分离的网络接口,可以为 Pod 使用不同的网络后端和高级 IPAM 功能。
Flannel
Flannel 是一个非常简单的能够满足
Kubernetes 所需要的覆盖网络。已经有许多人报告了使用 Flannel 和 Kubernetes 的成功案例。
Google Compute Engine (GCE)
对于 Google Compute Engine 的集群配置脚本,
高级路由器 用于为每个虚机分配一个子网(默认是 /24
- 254个 IP),
绑定到该子网的任何流量都将通过 GCE 网络结构直接路由到虚机。
这是除了分配给虚机的“主” IP 地址之外的一个补充,该 IP 地址经过 NAT 转换以用于访问外网。
Linux 网桥(称为“cbr0”)被配置为存在于该子网中,并被传递到 Docker 的 --bridge 参数上。
Docker 会以这样的参数启动:
DOCKER_OPTS = "--bridge=cbr0 --iptables=false --ip-masq=false"
这个网桥是由 Kubelet(由 --network-plugin=kubenet
参数控制)根据节点的 .spec.podCIDR
参数创建的。
Docker 将会从 cbr-cidr
块分配 IP。
容器之间可以通过 cbr0
网桥相互访问,也可以访问节点。
这些 IP 都可以在 GCE 的网络中被路由。
而 GCE 本身并不知道这些 IP,所以不会对访问外网的流量进行 NAT。
为了实现此目的,使用了 iptables
规则来伪装(又称为 SNAT,使数据包看起来好像是来自“节点”本身),
将通信绑定到 GCE 项目网络(10.0.0.0/8)之外的 IP。
iptables -t nat -A POSTROUTING ! -d 10.0.0.0/8 -o eth0 -j MASQUERADE
最后,在内核中启用了 IP 转发(因此内核将处理桥接容器的数据包):
sysctl net.ipv4.ip_forward= 1
所有这些的结果是所有 Pod 都可以互相访问,并且可以将流量发送到互联网。
Jaguar
Jaguar 是一个基于 OpenDaylight 的 Kubernetes 网络开源解决方案。
Jaguar 使用 vxlan 提供覆盖网络,而 Jaguar CNIPlugin 为每个 Pod 提供一个 IP 地址。
k-vswitch
k-vswitch 是一个基于
Open vSwitch 的简易 Kubernetes 网络插件。
它利用 Open vSwitch 中现有的功能来提供强大的网络插件,该插件易于操作,高效且安全。
Knitter
Knitter 是一个支持 Kubernetes 中实现多个网络系统的解决方案。
它提供了租户管理和网络管理的功能。除了多个网络平面外,Knitter 还包括一组端到端的 NFV 容器网络解决方案,
例如为应用程序保留 IP 地址、IP 地址迁移等。
Kube-OVN
Kube-OVN 是一个基于 OVN 的用于企业的 Kubernetes 网络架构。
借助于 OVN/OVS ,它提供了一些高级覆盖网络功能,例如子网、QoS、静态 IP 分配、流量镜像、网关、
基于 openflow 的网络策略和服务代理。
Kube-router
Kube-router 是 Kubernetes 的专用网络解决方案,
旨在提供高性能和易操作性。
Kube-router 提供了一个基于 Linux LVS/IPVS
的服务代理、一个基于 Linux 内核转发的无覆盖 Pod-to-Pod 网络解决方案和基于 iptables/ipset 的网络策略执行器。
L2 networks and linux bridging
如果你具有一个“哑”的L2网络,例如“裸机”环境中的简单交换机,则应该能够执行与上述 GCE 设置类似的操作。
请注意,这些说明仅是非常简单的尝试过-似乎可行,但尚未经过全面测试。
如果您使用此技术并完善了流程,请告诉我们。
根据 Lars Kellogg-Stedman 的这份非常不错的“Linux 网桥设备”
使用说明 来进行操作。
Multus (a Multi Network plugin)
Multus 是一个多 CNI 插件,
使用 Kubernetes 中基于 CRD 的网络对象来支持实现 Kubernetes 多网络系统。
Multus 支持所有参考插件 (比如:
Flannel 、
DHCP 、
Macvlan )
来实现 CNI 规范和第三方插件(比如:
Calico 、
Weave 、
Cilium 、
Contiv )。
除此之外, Multus 还支持
SRIOV 、
DPDK 、
OVS-DPDK & VPP 的工作负载,
以及 Kubernetes 中基于云的本机应用程序和基于 NFV 的应用程序。
NSX-T
VMware NSX-T 是一个网络虚拟化和安全平台。
NSX-T 可以为多云及多系统管理程序环境提供网络虚拟化,并专注于具有异构端点和技术堆栈的新兴应用程序框架和体系结构。
除了 vSphere 管理程序之外,这些环境还包括其他虚拟机管理程序,例如 KVM、容器和裸机。
NSX-T Container Plug-in (NCP)
提供了 NSX-T 与容器协调器(例如 Kubernetes)之间的结合,
以及 NSX-T 与基于容器的 CaaS/PaaS 平台(例如 Pivotal Container Service(PKS)和 OpenShift)之间的集成。
Nuage Networks VCS (Virtualized Cloud Services)
Nuage 提供了一个高度可扩展的基于策略的软件定义网络(SDN)平台。
Nuage 使用开源的 Open vSwitch 作为数据平面,以及基于开放标准构建具有丰富功能的 SDN 控制器。
Nuage 平台使用覆盖层在 Kubernetes Pod 和非 Kubernetes 环境(VM 和裸机服务器)之间提供基于策略的无缝联网。
Nuage 的策略抽象模型在设计时就考虑到了应用程序,并且可以轻松声明应用程序的细粒度策略。
该平台的实时分析引擎可为 Kubernetes 应用程序提供可见性和安全性监控。
OpenVSwitch
OpenVSwitch 是一个较为成熟的解决方案,但同时也增加了构建覆盖网络的复杂性。
这也得到了几个网络系统的“大商店”的拥护。
OVN (开放式虚拟网络)
OVN 是一个由 Open vSwitch 社区开发的开源的网络虚拟化解决方案。
它允许创建逻辑交换器、逻辑路由、状态 ACL、负载均衡等等来建立不同的虚拟网络拓扑。
该项目有一个特定的Kubernetes插件和文档 ovn-kubernetes 。
Romana
Romana 是一个开源网络和安全自动化解决方案。
它可以让你在没有覆盖网络的情况下部署 Kubernetes。
Romana 支持 Kubernetes 网络策略 ,
来提供跨网络命名空间的隔离。
Weaveworks 的 Weave Net
Weave Net 是 Kubernetes 及其
托管应用程序的弹性且易于使用的网络系统。
Weave Net 可以作为 CNI 插件 运行或者独立运行。
在这两种运行方式里,都不需要任何配置或额外的代码即可运行,并且在两种情况下,
网络都为每个 Pod 提供一个 IP 地址 -- 这是 Kubernetes 的标准配置。
接下来
网络模型的早期设计、运行原理以及未来的一些计划,都在
联网设计文档
里有更详细的描述。
3.11.4 - 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 指标
FEATURE STATE: 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.11.5 - 日志架构
应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。
大部分现代化应用都有某种日志记录机制。同样地,容器引擎也被设计成支持日志记录。
针对容器化应用,最简单且最广泛采用的日志记录方式就是写入标准输出和标准错误流。
但是,由容器引擎或运行时提供的原生功能通常不足以构成完整的日志记录方案。
例如,如果发生容器崩溃、Pod 被逐出或节点宕机等情况,你可能想访问应用日志。
在集群中,日志应该具有独立的存储和生命周期,与节点、Pod 或容器的生命周期相独立。
这个概念叫 集群级的日志 。
集群级日志架构需要一个独立的后端用来存储、分析和查询日志。
Kubernetes 并不为日志数据提供原生的存储解决方案。
相反,有很多现成的日志方案可以集成到 Kubernetes 中。
下面各节描述如何在节点上处理和存储日志。
Kubernetes 中的基本日志记录
这里的示例使用包含一个容器的 Pod 规约,每秒钟向标准输出写入数据。
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox
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
输出结果为:
pod/counter created
像下面这样,使用 kubectl logs
命令获取日志:
输出结果为:
0: Mon Jan 1 00:00:00 UTC 2001
1: Mon Jan 1 00:00:01 UTC 2001
2: Mon Jan 1 00:00:02 UTC 2001
...
你可以使用命令 kubectl logs --previous
检索之前容器实例的日志。
如果 Pod 中有多个容器,你应该为该命令附加容器名以访问对应容器的日志。
详见 kubectl logs
文档 。
节点级日志记录
容器化应用写入 stdout
和 stderr
的任何数据,都会被容器引擎捕获并被重定向到某个位置。
例如,Docker 容器引擎将这两个输出流重定向到某个
日志驱动(Logging Driver) ,
该日志驱动在 Kubernetes 中配置为以 JSON 格式写入文件。
说明: Docker JSON 日志驱动将日志的每一行当作一条独立的消息。
该日志驱动不直接支持多行消息。你需要在日志代理级别或更高级别处理多行消息。
默认情况下,如果容器重启,kubelet 会保留被终止的容器日志。
如果 Pod 在工作节点被驱逐,该 Pod 中所有的容器也会被驱逐,包括容器日志。
节点级日志记录中,需要重点考虑实现日志的轮转,以此来保证日志不会消耗节点上全部可用空间。
Kubernetes 并不负责轮转日志,而是通过部署工具建立一个解决问题的方案。
例如,在用 kube-up.sh
部署的 Kubernetes 集群中,存在一个
logrotate
,每小时运行一次。
你也可以设置容器运行时来自动地轮转应用日志。
例如,你可以找到关于 kube-up.sh
为 GCP 环境的 COS 镜像设置日志的详细信息,
脚本为
configure-helper
脚本 。
当使用某 CRI 容器运行时 时,kubelet 要负责对日志进行轮换,并
管理日志目录的结构。kubelet 将此信息发送给 CRI 容器运行时,后者
将容器日志写入到指定的位置。在 kubelet 配置文件
中的两个 kubelet 参数
containerLogMaxSize
和 containerLogMaxFiles
可以用来配置每个日志文件的最大长度和每个容器可以生成的日志文件个数上限。
当运行 kubectl logs
时,
节点上的 kubelet 处理该请求并直接读取日志文件,同时在响应中返回日志文件内容。
说明: 如果有外部系统执行日志轮转或者使用了 CRI 容器运行时,那么 kubectl logs
仅可查询到最新的日志内容。
比如,对于一个 10MB 大小的文件,通过 logrotate
执行轮转后生成两个文件,
一个 10MB 大小,一个为空,kubectl logs
返回最新的日志文件,而该日志文件
在这个例子中为空。
系统组件日志
系统组件有两种类型:在容器中运行的和不在容器中运行的。例如:
在容器中运行的 kube-scheduler 和 kube-proxy。
不在容器中运行的 kubelet 和容器运行时。
在使用 systemd 机制的服务器上,kubelet 和容器容器运行时将日志写入到 journald 中。
如果没有 systemd,它们将日志写入到 /var/log
目录下的 .log
文件中。
容器中的系统组件通常将日志写到 /var/log
目录,绕过了默认的日志机制。
他们使用 klog 日志库。
你可以在日志开发文档
找到这些组件的日志告警级别约定。
和容器日志类似,/var/log
目录中的系统组件日志也应该被轮转。
通过脚本 kube-up.sh
启动的 Kubernetes 集群中,日志被工具 logrotate
执行每日轮转,或者日志大小超过 100MB 时触发轮转。
集群级日志架构
虽然Kubernetes没有为集群级日志记录提供原生的解决方案,但你可以考虑几种常见的方法。
以下是一些选项:
使用在每个节点上运行的节点级日志记录代理。
在应用程序的 Pod 中,包含专门记录日志的边车(Sidecar)容器。
将日志直接从应用程序中推送到日志记录后端。
使用节点级日志代理
你可以通过在每个节点上使用 节点级的日志记录代理 来实现群集级日志记录。
日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。
通常,日志记录代理程序是一个容器,它可以访问包含该节点上所有应用程序容器的日志文件的目录。
由于日志记录代理必须在每个节点上运行,通常可以用 DaemonSet
的形式运行该代理。
节点级日志在每个节点上仅创建一个代理,不需要对节点上的应用做修改。
容器向标准输出和标准错误输出写出数据,但在格式上并不统一。
节点级代理
收集这些日志并将其进行转发以完成汇总。
使用 sidecar 容器运行日志代理
你可以通过以下方式之一使用边车(Sidecar)容器:
边车容器将应用程序日志传送到自己的标准输出。
边车容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。
传输数据流的 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
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
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
args : [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
volumeMounts :
- name : varlog
mountPath : /var/log
- name : count-log-2
image : busybox
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: Mon Jan 1 00:00:00 UTC 2001
1: Mon Jan 1 00:00:01 UTC 2001
2: Mon Jan 1 00:00:02 UTC 2001
...
kubectl logs counter count-log-2
输出为:
Mon Jan 1 00:00:00 UTC 2001 INFO 0
Mon Jan 1 00:00:01 UTC 2001 INFO 1
Mon Jan 1 00:00:02 UTC 2001 INFO 2
...
集群中安装的节点级代理会自动获取这些日志流,而无需进一步配置。
如果你愿意,你也可以配置代理程序来解析源容器的日志行。
注意,尽管 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 边车容器的 Pod 。
flutend 通过 Pod 的挂载卷获取它的配置数据。
apiVersion : v1
kind : Pod
metadata :
name : counter
spec :
containers :
- name : count
image : busybox
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 : k8s.gcr.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
在示例配置中,你可以将 fluentd 替换为任何日志代理,从应用容器内
的任何来源读取数据。
从应用中直接暴露日志目录
从各个应用中直接暴露和推送日志数据的集群日志机制
已超出 Kubernetes 的范围。
3.11.6 - 系统日志
系统组件的日志记录集群中发生的事件,这对于调试非常有用。
你可以配置日志的精细度,以展示更多或更少的细节。
日志可以是粗粒度的,如只显示组件内的错误,
也可以是细粒度的,如显示事件的每一个跟踪步骤(比如 HTTP 访问日志、pod 状态更新、控制器动作或调度器决策)。
Klog
klog 是 Kubernetes 的日志库。
klog
为 Kubernetes 系统组件生成日志消息。
有关 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]
结构化日志
FEATURE STATE: Kubernetes v1.19 [alpha]
警告: 到结构化日志消息的迁移是一个持续的过程。
在此版本中,并非所有日志消息都是结构化的。
解析日志文件时,你也必须要处理非结构化日志消息。
日志格式和值的序列化可能会发生变化。
结构化日志记录旨在日志消息中引入统一结构,以便以编程方式提取信息。
你可以方便地用更小的开销来处理结构化日志。
新的消息格式向后兼容,并默认启用。
结构化日志的格式:
<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"
JSON 日志格式
FEATURE STATE: 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
- 精细度(必选项,整数,默认值 0)
err
- 错误字符串(可选项,字符串)
msg
- 消息(必选项,字符串)
当前支持JSON格式的组件列表:
日志清理
FEATURE STATE: Kubernetes v1.20 [alpha]
警告: 日志清理可能会导致大量的计算开销,因此不应启用在生产环境中。
--experimental-logging-sanitization
参数可用来启用 klog 清理过滤器。
如果启用后,将检查所有日志参数中是否有标记为敏感数据的字段(比如:密码,密钥,令牌),并且将阻止这些字段的记录。
当前支持日志清理的组件列表:
kube-controller-manager
kube-apiserver
kube-scheduler
kubelet
说明: 日志清理过滤器不会阻止用户工作负载日志泄漏敏感数据。
日志精细度级别
参数 -v
控制日志的精细度。增大该值会增大日志事件的数量。
减小该值可以减小日志事件的数量。
增大精细度会记录更多的不太严重的事件。
精细度设置为 0 时只记录关键(critical)事件。
日志位置
有两种类型的系统组件:运行在容器中的组件和不运行在容器中的组件。例如:
Kubernetes 调度器和 kube-proxy 在容器中运行。
kubelet 和容器运行时,例如 Docker,不在容器中运行。
在使用 systemd 的系统中,kubelet 和容器运行时写入 journald。
在别的系统中,日志写入 /var/log
目录下的 .log
文件中。
容器中的系统组件总是绕过默认的日志记录机制,写入 /var/log
目录下的 .log
文件。
与容器日志类似,你应该轮转 /var/log
目录下系统组件日志。
在 kube-up.sh
脚本创建的 Kubernetes 集群中,日志轮转由 logrotate
工具配置。
logrotate
工具,每天或者当日志大于 100MB 时,轮转日志。
接下来
3.11.7 - 追踪 Kubernetes 系统组件
FEATURE STATE: Kubernetes v1.22 [alpha]
系统组件追踪功能记录各个集群操作的时延信息和这些操作之间的关系。
Kubernetes 组件基于 gRPC 导出器的
OpenTelemetry 协议
发送追踪信息,并用
OpenTelemetry Collector
收集追踪信息,再将其转交给追踪系统的后台。
追踪信息的收集
关于收集追踪信息、以及使用收集器的完整指南,可参见
Getting Started with the OpenTelemetry Collector 。
不过,还有一些特定于 Kubernetes 组件的事项值得注意。
默认情况下,Kubernetes 组件使用 gRPC 的 OTLP 导出器来导出追踪信息,将信息写到
IANA OpenTelemetry 端口 。
举例来说,如果收集器以 Kubernetes 组件的边车模式运行,以下接收器配置会收集 spans 信息,并将它们写入到标准输出。
receivers :
otlp :
protocols :
grpc :
exporters :
# 用适合你后端环境的导出器替换此处的导出器
logging :
logLevel : debug
service :
pipelines :
traces :
receivers : [otlp]
exporters : [logging]
组件追踪
kube-apiserver 追踪
kube-apiserver 为传入的 HTTP 请求、传出到 webhook 和 etcd 的请求以及重入的请求生成 spans。
由于 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) 。
稳定性
追踪工具仍在积极开发中,未来它会以多种方式发生变化。
这些变化包括:span 名称、附加属性、检测端点等等。
此类特性在达到稳定版本之前,不能保证追踪工具的向后兼容性。
接下来
3.11.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.11.9 - API 优先级和公平性
FEATURE STATE: Kubernetes v1.20 [beta]
对于集群管理员来说,控制 Kubernetes API 服务器在过载情况下的行为是一项关键任务。
kube-apiserver
有一些控件(例如:命令行标志 --max-requests-inflight
和 --max-mutating-requests-inflight
),
可以限制将要接受的未处理的请求,从而防止过量请求入站,潜在导致 API 服务器崩溃。
但是这些标志不足以保证在高流量期间,最重要的请求仍能被服务器接受。
API 优先级和公平性(APF)是一种替代方案,可提升上述最大并发限制。
APF 以更细粒度的方式对请求进行分类和隔离。
它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
通过使用公平排队技术从队列中分发请求,这样,
一个行为不佳的 控制器
就不会饿死其他控制器(即使优先级相同)。
注意: 属于“长时间运行”类型的请求(主要是 watch)不受 API 优先级和公平性过滤器的约束。
如果未启用 APF 特性,即便设置 --max-requests-inflight
标志,该类请求也不受约束。
启用/禁用 API 优先级和公平性
API 优先级与公平性(APF)特性由特性门控控制,默认情况下启用。
有关特性门控的一般性描述以及如何启用和禁用特性门控,
请参见特性门控 。
APF 的特性门控称为 APIPriorityAndFairness
。
此特性也与某个 API 组
相关:
(a) 一个 v1alpha1
版本,默认被禁用;
(b) 一个 v1beta1
版本,默认被启用。
你可以在启动 kube-apiserver
时,添加以下命令行标志来禁用此功能门控
及 v1beta1 API 组:
kube-apiserver \
--feature-gates= APIPriorityAndFairness = false \
--runtime-config= flowcontrol.apiserver.k8s.io/v1beta1= 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 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。
排队
即使在同一优先级内,也可能存在大量不同的流量源。
在过载情况下,防止一个请求流饿死其他流是非常有价值的
(尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求,
理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。
公平排队算法在处理具有相同优先级的请求时,实现了上述场景。
每个请求都被分配到某个 流 中,该 流 由对应的 FlowSchema 的名字加上一个
流区分项(Flow Distinguisher) 来标识。
这里的流区分项可以是发出请求的用户、目标资源的名称空间或什么都不是。
系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。
将请求划分到流中之后,APF 功能将请求分配到队列中。
分配时使用一种称为 混洗分片(Shuffle-Sharding)
的技术。
该技术可以相对有效地利用队列隔离低强度流与高强度流。
排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、
公平性(当总流量超标时,各个独立的流将都会取得进展)、
突发流量的容忍度以及排队引发的额外延迟之间进行权衡。
豁免请求
某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用 API 服务器。
默认值
APF 特性附带推荐配置,该配置对实验场景应该足够;
如果你的集群有可能承受较大的负载,那么你应该考虑哪种配置最有效。
推荐配置将请求分为五个优先级:
system
优先级用于 system:nodes
组(即 Kubelets)的请求;
kubelets 必须能连上 API 服务器,以便工作负载能够调度到其上。
leader-election
优先级用于内置控制器的领导选举的请求
(特别是来自 kube-system
名称空间中 system:kube-controller-manager
和
system:kube-scheduler
用户和服务账号,针对 endpoints
、configmaps
或 leases
的请求)。
将这些请求与其他流量相隔离非常重要,因为领导者选举失败会导致控制器发生故障并重新启动,
这反过来会导致新启动的控制器在同步信息时,流量开销更大。
workload-high
优先级用于内置控制器的请求。
workload-low
优先级适用于来自任何服务帐户的请求,通常包括来自 Pods
中运行的控制器的所有请求。
global-default
优先级可处理所有其他流量,例如:非特权用户运行的交互式 kubectl
命令。
系统内置了两个 PriorityLevelConfiguration 和两个 FlowSchema,它们是不可重载的:
特殊的 exempt
优先级的请求完全不受流控限制:它们总是立刻被分发。
特殊的 exempt
FlowSchema 把 system:masters
组的所有请求都归入该优先级组。
如果合适,你可以定义新的 FlowSchema,将其他请求定向到该优先级。
特殊的 catch-all
优先级与特殊的 catch-all
FlowSchema 结合使用,以确保每个请求都分类。
一般地,你不应该依赖于 catch-all
的配置,而应适当地创建自己的 catch-all
FlowSchema 和 PriorityLevelConfigurations(或使用默认安装的 global-default
配置)。
为了帮助捕获部分请求未分类的配置错误,强制要求 catch-all
优先级仅允许一个并发份额,
并且不对请求进行排队,使得仅与 catch-all
FlowSchema 匹配的流量被拒绝的可能性更高,
并显示 HTTP 429 错误。
健康检查并发豁免
推荐配置没有为本地 kubelet 对 kube-apiserver 执行健康检查的请求进行任何特殊处理
——它们倾向于使用安全端口,但不提供凭据。
在推荐配置中,这些请求将分配 global-default
FlowSchema 和 global-default
优先级,
这样其他流量可以排除健康检查。
如果添加以下 FlowSchema,健康检查请求不受速率限制。
注意: 进行此更改后,任何敌对方都可以发送与此 FlowSchema 匹配的任意数量的健康检查请求。
如果你有 Web 流量过滤器或类似的外部安全机制保护集群的 API 服务器免受常规网络流量的侵扰,
则可以配置规则,阻止所有来自集群外部的健康检查请求。
apiVersion : flowcontrol.apiserver.k8s.io/v1alpha1
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
资源
流控 API 涉及两种资源。
PriorityLevelConfigurations
定义隔离类型和可处理的并发预算量,还可以微调排队行为。
FlowSchemas
用于对每个入站请求进行分类,并与一个 PriorityLevelConfigurations 相匹配。
此外同一 API 组还有一个 v1alpha1
版本,其中包含语法和语义都相同的资源类别。
PriorityLevelConfiguration
一个 PriorityLevelConfiguration 表示单个隔离类型。每个 PriorityLevelConfigurations
对未完成的请求数有各自的限制,对排队中的请求数也有限制。
PriorityLevelConfigurations 的并发限制不是指定请求绝对数量,而是在“并发份额”中指定。
API 服务器的总并发量限制通过这些份额按例分配到现有 PriorityLevelConfigurations 中。
集群管理员可以更改 --max-requests-inflight
(或 --max-mutating-requests-inflight
)的值,
再重新启动 kube-apiserver
来增加或减小服务器的总流量,
然后所有的 PriorityLevelConfigurations 将看到其最大并发增加(或减少)了相同的比例。
注意: 启用 APF 功能后,服务器的总并发量限制将设置为
--max-requests-inflight
和 --max-mutating-requests-inflight
之和。
可变请求和不可变请求之间不再有任何区别;
如果对于某种资源,你需要区别对待不同请求,请创建不同的 FlowSchema 分别匹配可变请求和不可变请求。
当入站请求的数量大于分配的 PriorityLevelConfigurations 中允许的并发级别时, type
字段将确定对额外请求的处理方式。
Reject
类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。
Queue
类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。
公平排队算法支持通过排队配置对优先级微调。 可以在增强建议 中阅读算法的详细信息,但总之:
queues
递增能减少不同流之间的冲突概率,但代价是增加了内存使用量。
值为 1 时,会禁用公平排队逻辑,但仍允许请求排队。
queueLengthLimit
递增可以在不丢弃任何请求的情况下支撑更大的突发流量,
但代价是增加了等待时间和内存使用量。
修改 handSize
允许你调整过载情况下不同流之间的冲突概率以及单个流可用的整体并发性。
说明: 较大的 handSize
使两个单独的流程发生碰撞的可能性较小(因此,一个流可以饿死另一个流),
但是更有可能的是少数流可以控制 apiserver。
较大的 handSize
还可能增加单个高并发流的延迟量。
单个流中可能排队的请求的最大数量为 handSize * queueLengthLimit
。
下表显示了有趣的随机分片配置集合,
每行显示给定的老鼠(低强度流)被不同数量的大象挤压(高强度流)的概率。
表来源请参阅: https://play.golang.org/p/Gi0PLgVHiUg
Example Shuffle Sharding Configurations
随机分片
队列数
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。
问题诊断
启用了 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 到 PriorityLevelConfigurations 的名称的映射。
可观察性
指标
说明: 在 Kubernetes v1.20 之前的版本中,标签 flow_schema
和 priority_level
的命名有时被写作 flowSchema
和 priorityLevel
,即存在不一致的情况。
如果你在运行 Kubernetes v1.19 或者更早版本,你需要参考你所使用的集群
版本对应的文档。
当你开启了 APF 后,kube-apiserver 会暴露额外指标。
监视这些指标有助于判断你的配置是否不当地限制了重要流量,
或者发现可能会损害系统健康的,行为不良的工作负载。
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
)拆分。定期以高速率观察该值。
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 服务器的总并发限制和 PriorityLevelConfigurations
的并发份额),并按标签 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 APIMachinery
或特性的 Slack 频道
提出建议和特性请求。
3.11.10 - 安装扩展(Addons)
说明:
本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循
CNCF 网站指南 ,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读
内容指南 。
Add-ons 扩展了 Kubernetes 的功能。
本文列举了一些可用的 add-ons 以及到它们各自安装说明的链接。
每个 Add-ons 按字母顺序排序 - 顺序不代表任何优先地位。
网络和网络策略
ACI 通过 Cisco ACI 提供集成的容器网络和安全网络。
Antrea 在第 3/4 层执行操作,为 Kubernetes
提供网络连接和安全服务。Antrea 利用 Open vSwitch 作为网络的数据面。
Calico
是一个安全的 L3 网络和网络策略驱动。
Canal 结合 Flannel 和 Calico,提供网络和网络策略。
Cilium 是一个 L3 网络和网络策略插件,能够透明的实施 HTTP/API/L7 策略。
同时支持路由(routing)和覆盖/封装(overlay/encapsulation)模式。
CNI-Genie 使 Kubernetes 无缝连接到一种 CNI 插件,
例如:Flannel、Calico、Canal、Romana 或者 Weave。
Contiv 为多种用例提供可配置网络(使用 BGP 的原生 L3,使用 vxlan 的覆盖网络,
经典 L2 和 Cisco-SDN/ACI)和丰富的策略框架。Contiv 项目完全开源 。
安装工具 同时提供基于和不基于 kubeadm 的安装选项。
基于 Tungsten Fabric 的
Contrail
是一个开源的多云网络虚拟化和策略管理平台,Contrail 和 Tungsten Fabric 与业务流程系统
(例如 Kubernetes、OpenShift、OpenStack和Mesos)集成在一起,
为虚拟机、容器或 Pod 以及裸机工作负载提供了隔离模式。
Flannel
是一个可以用于 Kubernetes 的 overlay 网络提供者。
Knitter 是为 kubernetes 提供复合网络解决方案的网络组件。
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 实现的负载均衡器
和网络策略。
OVN4NFV-K8S-Plugin 是一个基于 OVN 的 CNI
控制器插件,提供基于云原生的服务功能链条(Service Function Chaining,SFC)、多种 OVN 覆盖
网络、动态子网创建、动态虚拟网络创建、VLAN 驱动网络、直接驱动网络,并且可以
驳接其他的多网络插件,适用于基于边缘的、多集群联网的云原生工作负载。
NSX-T 容器插件(NCP)
提供了 VMware NSX-T 与容器协调器(例如 Kubernetes)之间的集成,以及 NSX-T 与基于容器的
CaaS / PaaS 平台(例如关键容器服务(PKS)和 OpenShift)之间的集成。
Nuage
是一个 SDN 平台,可在 Kubernetes Pods 和非 Kubernetes 环境之间提供基于策略的联网,并具有可视化和安全监控。
Romana 是一个 pod 网络的第三层解决方案,并支持
NetworkPolicy API 。
Kubeadm add-on 安装细节可以在这里 找到。
Weave Net
提供在网络分组两端参与工作的网络和网络策略,并且不需要额外的数据库。
服务发现
CoreDNS 是一种灵活的,可扩展的 DNS 服务器,可以
安装 为集群内的 Pod 提供 DNS 服务。
可视化管理
基础设施
KubeVirt 是可以让 Kubernetes
运行虚拟机的 add-ons。通常运行在裸机集群上。
遗留 Add-ons
还有一些其它 add-ons 归档在已废弃的 cluster/addons 路径中。
维护完善的 add-ons 应该被链接到这里。欢迎提出 PRs!
3.12 - 扩展 Kubernetes
改变你的 Kubernetes 集群的行为的若干方法。
Kubernetes 是高度可配置且可扩展的。因此,大多数情况下,你不需要
派生自己的 Kubernetes 副本或者向项目代码提交补丁。
本指南描述定制 Kubernetes 的可选方式。主要针对的读者是希望了解如何针对自身工作环境
需要来调整 Kubernetes 的集群管理者 。
对于那些充当平台开发人员
的开发人员或 Kubernetes 项目的贡献者
而言,他们也会在本指南中找到有用的介绍信息,了解系统中存在哪些扩展点和扩展模式,
以及它们所附带的各种权衡和约束等等。
概述
定制化的方法主要可分为 配置(Configuration) 和 扩展(Extensions) 两种。
前者主要涉及改变参数标志、本地配置文件或者 API 资源;
后者则需要额外运行一些程序或服务。
本文主要关注扩展。
Configuration
配置文件 和参数标志 的说明位于在线文档的参考章节,按可执行文件组织:
在托管的 Kubernetes 服务中或者受控安装的发行版本中,参数标志和配置文件不总是可以
修改的。即使它们是可修改的,通常其修改权限也仅限于集群管理员。
此外,这些内容在将来的 Kubernetes 版本中很可能发生变化,设置新参数或配置文件可能
需要重启进程。
有鉴于此,通常应该在没有其他替代方案时才应考虑更改参数标志和配置文件。
内置的策略 API ,例如ResourceQuota 、
PodSecurityPolicies 、
NetworkPolicy
和基于角色的访问控制(RBAC )等等
都是内置的 Kubernetes API。
API 通常用于托管的 Kubernetes 服务和受控的 Kubernetes 安装环境中。
这些 API 是声明式的,与 Pod 这类其他 Kubernetes 资源遵从相同的约定,所以
新的集群配置是可复用的,并且可以当作应用程序来管理。
此外,对于稳定版本的 API 而言,它们与其他 Kubernetes API 一样,采纳的是
一种预定义的支持策略 。
出于以上原因,在条件允许的情况下,基于 API 的方案应该优先于配置文件 和参数标志 。
扩展
扩展(Extensions)是一些扩充 Kubernetes 能力并与之深度集成的软件组件。
它们调整 Kubernetes 的工作方式使之支持新的类型和新的硬件种类。
大多数集群管理员会使用一种托管的 Kubernetes 服务或者其某种发行版本。
因此,大多数 Kubernetes 用户不需要安装扩展,
至于需要自己编写新的扩展的情况就更少了。
扩展模式
Kubernetes 从设计上即支持通过编写客户端程序来将其操作自动化。
任何能够对 Kubernetes API 发出读写指令的程序都可以提供有用的自动化能力。
自动化组件 可以运行在集群上,也可以运行在集群之外。
通过遵从本文中的指南,你可以编写高度可用的、运行稳定的自动化组件。
自动化组件通常可以用于所有 Kubernetes 集群,包括托管的集群和受控的安装环境。
编写客户端程序有一种特殊的 Controller(控制器) 模式,能够与 Kubernetes 很好地
协同工作。控制器通常会读取某个对象的 .spec
,或许还会执行一些操作,之后更新
对象的 .status
。
Controller(控制器) 是 Kubernetes 的客户端。
当 Kubernetes 充当客户端,调用某远程服务时,对应
的远程组件称作Webhook 。 远程服务称作Webhook 后端 。
与控制器模式相似,Webhook 也会在整个架构中引入新的失效点(Point of Failure)。
在 Webhook 模式中,Kubernetes 向远程服务发起网络请求。
在 可执行文件插件(Binary Plugin) 模式中,Kubernetes 执行某个可执行文件(程序)。
可执行文件插件在 kubelet (例如,
FlexVolume 插件
和网络插件 )
和 kubectl 中使用。
下面的示意图中展示了这些扩展点如何与 Kubernetes 控制面交互。
扩展点
此示意图显示的是 Kubernetes 系统中的扩展点。
用户通常使用 kubectl
与 Kubernetes API 交互。
kubectl 插件 能够扩展 kubectl 程序的行为。
这些插件只会影响到每个用户的本地环境,因此无法用来强制实施整个站点范围的策略。
API 服务器处理所有请求。API 服务器中的几种扩展点能够使用户对请求执行身份认证、
基于其内容阻止请求、编辑请求内容、处理删除操作等等。
这些扩展点在 API 访问扩展
节详述。
API 服务器向外提供不同类型的资源(resources) 。
内置的资源类型 ,如 pods
,是由 Kubernetes 项目所定义的,无法改变。
你也可以添加自己定义的或者其他项目所定义的称作自定义资源(Custom Resources)
的资源,正如自定义资源 节所描述的那样。
自定义资源通常与 API 访问扩展点结合使用。
Kubernetes 调度器负责决定 Pod 要放置到哪些节点上执行。
有几种方式来扩展调度行为。这些方法将在
调度器扩展 节中展开。
Kubernetes 中的很多行为都是通过称为控制器(Controllers)的程序来实现的,这些程序也都是 API 服务器
的客户端。控制器常常与自定义资源结合使用。
组件 kubelet 运行在各个节点上,帮助 Pod 展现为虚拟的服务器并在集群网络中拥有自己的 IP。
网络插件 使得 Kubernetes 能够采用
不同实现技术来连接 Pod 网络。
组件 kubelet 也会为容器增加或解除存储卷的挂载。
通过存储插件 ,可以支持新的存储类型。
如果你无法确定从何处入手,下面的流程图可能对你有些帮助。
注意,某些方案可能需要同时采用几种类型的扩展。
API 扩展
用户定义的类型
如果你想要定义新的控制器、应用配置对象或者其他声明式 API,并且使用 Kubernetes
工具(如 kubectl
)来管理它们,可以考虑向 Kubernetes 添加自定义资源。
不要使用自定义资源来充当应用、用户或者监控数据的数据存储。
关于自定义资源的更多信息,可参见自定义资源概念指南 。
结合使用新 API 与自动化组件
自定义资源 API 与控制回路的组合称作
Operator 模式 。
Operator 模式用来管理特定的、通常是有状态的应用。
这些自定义 API 和控制回路也可用来控制其他资源,如存储或策略。
更改内置资源
当你通过添加自定义资源来扩展 Kubernetes 时,所添加的资源通常会被放在一个新的
API 组中。你不可以替换或更改现有的 API 组。
添加新的 API 不会直接让你影响现有 API (如 Pods)的行为,不过 API
访问扩展能够实现这点。
API 访问扩展
当请求到达 Kubernetes API 服务器时,首先要经过身份认证,之后是鉴权操作,
再之后要经过若干类型的准入控制器的检查。
参见控制 Kubernetes API 访问
以了解此流程的细节。
这些步骤中都存在扩展点。
Kubernetes 提供若干内置的身份认证方法。
它也可以运行在某中身份认证代理的后面,并且可以将来自鉴权头部的令牌发送到
某个远程服务(Webhook)来执行验证操作。
所有这些方法都在身份认证文档
中有详细论述。
身份认证
身份认证 负责将所有请求中
的头部或证书映射到发出该请求的客户端的用户名。
Kubernetes 提供若干种内置的认证方法,以及
认证 Webhook
方法以备内置方法无法满足你的要求。
鉴权
鉴权 操作负责确定特定的用户
是否可以读、写 API 资源或对其执行其他操作。
此操作仅在整个资源集合的层面进行。
换言之,它不会基于对象的特定字段作出不同的判决。
如果内置的鉴权选项无法满足你的需要,你可以使用
鉴权 Webhook 来调用用户提供
的代码,执行定制的鉴权操作。
动态准入控制
请求的鉴权操作结束之后,如果请求的是写操作,还会经过
准入控制 处理步骤。
除了内置的处理步骤,还存在一些扩展点:
基础设施扩展
存储插件
FlexVolumes
卷可以让用户挂载无需内建支持的卷类型,kubelet 会调用可执行文件插件
来挂载对应的存储卷。
设备插件
使用设备插件 ,
节点能够发现新的节点资源(除了内置的类似 CPU 和内存这类资源)。
网络插件
通过节点层面的网络插件 ,可以支持
不同的网络设施。
调度器扩展
调度器是一种特殊的控制器,负责监视 Pod 变化并将 Pod 分派给节点。
默认的调度器可以被整体替换掉,同时继续使用其他 Kubernetes 组件。
或者也可以在同一时刻使用
多个调度器 。
这是一项非同小可的任务,几乎绝大多数 Kubernetes
用户都会发现其实他们不需要修改调度器。
调度器也支持一种
Webhook ,
允许使用某种 Webhook 后端(调度器扩展)来为 Pod 可选的节点执行过滤和优先排序操作。
接下来
3.12.1 - 扩展 Kubernetes API
3.12.1.1 - 定制资源
定制资源(Custom Resource) 是对 Kubernetes API 的扩展。
本页讨论何时向 Kubernetes 集群添加定制资源,何时使用独立的服务。
本页描述添加定制资源的两种方法以及怎样在二者之间做出抉择。
定制资源
资源(Resource) 是
Kubernetes API 中的一个端点,
其中存储的是某个类别的
API 对象
的一个集合。
例如内置的 pods 资源包含一组 Pod 对象。
定制资源(Custom Resource) 是对 Kubernetes API 的扩展,不一定在默认的
Kubernetes 安装中就可用。定制资源所代表的是对特定 Kubernetes 安装的一种定制。
不过,很多 Kubernetes 核心功能现在都用定制资源来实现,这使得 Kubernetes
更加模块化。
定制资源可以通过动态注册的方式在运行中的集群内或出现或消失,集群管理员可以独立于集群
更新定制资源。一旦某定制资源被安装,用户可以使用
kubectl
来创建和访问其中的对象,就像他们为 pods 这种内置资源所做的一样。
定制控制器
就定制资源本身而言,它只能用来存取结构化的数据。
当你将定制资源与 定制控制器(Custom Controller) 相结合时,定制资源就能够
提供真正的 声明式 API(Declarative API) 。
使用声明式 API ,
你可以 声明 或者设定你的资源的期望状态,并尝试让 Kubernetes 对象的当前状态
同步到其期望状态。控制器负责将结构化的数据解释为用户所期望状态的记录,并
持续地维护该状态。
你可以在一个运行中的集群上部署和更新定制控制器,这类操作与集群的生命周期无关。
定制控制器可以用于任何类别的资源,不过它们与定制资源结合起来时最为有效。
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 支持特性 。
你不需要这类特性。
声明式 APIs
典型地,在声明式 API 中:
你的 API 包含相对而言为数不多的、尺寸较小的对象(资源)。
对象定义了应用或者基础设施的配置信息。
对象更新操作频率较低。
通常需要人来读取或写入对象。
对象的主要操作是 CRUD 风格的(创建、读取、更新和删除)。
不需要跨对象的事务支持:API 对象代表的是期望状态而非确切实际状态。
命令式 API(Imperative API)与声明式有所不同。
以下迹象表明你的 API 可能不是声明式的:
客户端发出“做这个操作”的指令,之后在该操作结束时获得同步响应。
客户端发出“做这个操作”的指令,并获得一个操作 ID,之后需要检查一个 Operation(操作)
对象来判断请求是否成功完成。
你会将你的 API 类比为远程过程调用(Remote Procedure Call,RPCs)。
直接存储大量数据;例如每个对象几 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)相区分。
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 更容易创建
CRDs
聚合 API
无需编程。用户可选择任何语言来实现 CRD 控制器。
需要使用 Go 来编程,并构建可执行文件和镜像。
无需额外运行服务;CRD 由 API 服务器处理。
需要额外创建服务,且该服务可能失效。
一旦 CRD 被创建,不需要持续提供支持。Kubernetes 主控节点升级过程中自动会带入缺陷修复。
可能需要周期性地从上游提取缺陷修复并更新聚合 API 服务器。
无需处理 API 的多个版本;例如,当你控制资源的客户端时,你可以更新它使之与 API 同步。
你需要处理 API 的多个版本;例如,在开发打算与很多人共享的扩展时。
高级特性与灵活性
聚合 API 可提供更多的高级 API 特性,也可对其他特性实行定制;例如,对存储层进行定制。
特性
描述
CRDs
聚合 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.12.1.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 服务器无法满足这一延迟要求,应考虑如何更改配置以满足需要。
接下来
或者,学习如何使用自定义资源定义扩展 Kubernetes API 。
3.12.2 - 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.12.3 - 计算、存储和网络扩展
3.12.3.1 - 网络插件
Kubernetes中的网络插件有几种类型:
安装
kubelet 有一个单独的默认网络插件,以及一个对整个集群通用的默认网络。
它在启动时探测插件,记住找到的内容,并在 Pod 生命周期的适当时间执行
所选插件(这仅适用于 Docker,因为 CRI 管理自己的 CNI 插件)。
在使用插件时,需要记住两个 kubelet 命令行参数:
cni-bin-dir
: kubelet 在启动时探测这个目录中的插件
network-plugin
: 要使用的网络插件来自 cni-bin-dir
。
它必须与从插件目录探测到的插件报告的名称匹配。
对于 CNI 插件,其值为 "cni"。
网络插件要求
除了提供
NetworkPlugin
接口
来配置和清理 Pod 网络之外,该插件还可能需要对 kube-proxy 的特定支持。
iptables 代理显然依赖于 iptables,插件可能需要确保 iptables 能够监控容器的网络通信。
例如,如果插件将容器连接到 Linux 网桥,插件必须将 net/bridge/bridge-nf-call-iptables
系统参数设置为1
,以确保 iptables 代理正常工作。
如果插件不使用 Linux 网桥(而是类似于 Open vSwitch 或者其它一些机制),
它应该确保为代理对容器通信执行正确的路由。
默认情况下,如果未指定 kubelet 网络插件,则使用 noop
插件,
该插件设置 net/bridge/bridge-nf-call-iptables=1
,以确保简单的配置
(如带网桥的 Docker )与 iptables 代理正常工作。
CNI
通过给 Kubelet 传递 --network-plugin=cni
命令行选项可以选择 CNI 插件。
Kubelet 从 --cni-conf-dir
(默认是 /etc/cni/net.d
) 读取文件并使用
该文件中的 CNI 配置来设置各个 Pod 的网络。
CNI 配置文件必须与
CNI 规约
匹配,并且配置所引用的所有所需的 CNI 插件都应存在于
--cni-bin-dir
(默认是 /opt/cni/bin
)下。
如果这个目录中有多个 CNI 配置文件,kubelet 将会使用按文件名的字典顺序排列
的第一个作为配置文件。
除了配置文件指定的 CNI 插件外,Kubernetes 还需要标准的 CNI
lo
插件,最低版本是0.2.0。
支持 hostPort
CNI 网络插件支持 hostPort
。 你可以使用官方
portmap
插件,它由 CNI 插件团队提供,或者使用你自己的带有 portMapping 功能的插件。
如果你想要启动 hostPort
支持,则必须在 cni-conf-dir
指定 portMappings capability
。
例如:
{
"name" : "k8s-pod-network" ,
"cniVersion" : "0.3.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.3.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
...
kubenet
Kubenet 是一个非常基本的、简单的网络插件,仅适用于 Linux。
它本身并不实现更高级的功能,如跨节点网络或网络策略。
它通常与云驱动一起使用,云驱动为节点间或单节点环境中的通信设置路由规则。
Kubenet 创建名为 cbr0
的网桥,并为每个 pod 创建了一个 veth 对,
每个 Pod 的主机端都连接到 cbr0
。
这个 veth 对的 Pod 端会被分配一个 IP 地址,该 IP 地址隶属于节点所被分配的 IP
地址范围内。节点的 IP 地址范围则通过配置或控制器管理器来设置。
cbr0
被分配一个 MTU,该 MTU 匹配主机上已启用的正常接口的最小 MTU。
使用此插件还需要一些其他条件:
需要标准的 CNI bridge
、lo
以及 host-local
插件,最低版本是0.2.0。
kubenet 首先在 /opt/cni/bin
中搜索它们。 指定 cni-bin-dir
以提供
其它搜索路径。首次找到的匹配将生效。
Kubelet 必须和 --network-plugin=kubenet
参数一起运行,才能启用该插件。
Kubelet 还应该和 --non-masquerade-cidr=<clusterCidr>
参数一起运行,
以确保超出此范围的 IP 流量将使用 IP 伪装。
节点必须被分配一个 IP 子网,通过kubelet 命令行的 --pod-cidr
选项或
控制器管理器的命令行选项 --allocate-node-cidrs=true --cluster-cidr=<cidr>
来设置。
自定义 MTU(使用 kubenet)
要获得最佳的网络性能,必须确保 MTU 的取值配置正确。
网络插件通常会尝试推断出一个合理的 MTU,但有时候这个逻辑不会产生一个最优的 MTU。
例如,如果 Docker 网桥或其他接口有一个小的 MTU, kubenet 当前将选择该 MTU。
或者如果你正在使用 IPSEC 封装,则必须减少 MTU,并且这种计算超出了大多数网络插件的能力范围。
如果需要,你可以使用 network-plugin-mtu
kubelet 选项显式的指定 MTU。
例如:在 AWS 上 eth0
MTU 通常是 9001,因此你可以指定 --network-plugin-mtu=9001
。
如果你正在使用 IPSEC ,你可以减少它以允许封装开销,例如 --network-plugin-mtu=8873
。
此选项会传递给网络插件; 当前 仅 kubenet 支持 network-plugin-mtu
。
用法总结
--network-plugin=cni
用来表明我们要使用 cni
网络插件,实际的 CNI 插件
可执行文件位于 --cni-bin-dir
(默认是 /opt/cni/bin
)下, CNI 插件配置位于
--cni-conf-dir
(默认是 /etc/cni/net.d
)下。
--network-plugin=kubenet
用来表明我们要使用 kubenet
网络插件,CNI bridge
,lo
和 host-local
插件位于 /opt/cni/bin
或 cni-bin-dir
中。
--network-plugin-mtu=9001
指定了我们使用的 MTU,当前仅被 kubenet
网络插件使用。
接下来
3.12.3.2 - 设备插件
使用 Kubernetes 设备插件框架来实现适用于 GPU、NIC、FPGA、InfiniBand 以及类似的需要特定于供应商设置的资源的插件。
FEATURE STATE: 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" 设备并且是可用的。
然后用户需要请求其他类型的资源的时候,就可以在
Container
规范请求这类设备,但是有以下的限制:
扩展资源仅可作为整数资源使用,并且不能被过量使用
设备不能在容器之间共享
假设 Kubernetes 集群正在运行一个设备插件,该插件在一些节点上公布的资源为 hardware-vendor.example/foo
。
下面就是一个 Pod 示例,请求此资源以运行某演示负载:
---
apiVersion : v1
kind : Pod
metadata :
name : demo-pod
spec :
containers :
- name : demo-container-1
image : k8s.gcr.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 {
// 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
版本的节点上运行设备插件,请在升级这些节点之前先升级设备插件以支持这两个版本。
采用该方法将确保升级期间设备分配的连续运行。
监控设备插件资源
FEATURE STATE: Kubernetes v1.15 [beta]
为了监控设备插件提供的资源,监控代理程序需要能够发现节点上正在使用的设备,
并获取元数据来描述哪个指标与容器相关联。
设备监控代理暴露给 Prometheus 的指标应该遵循
Kubernetes Instrumentation Guidelines ,
使用 pod
、namespace
和 container
标签来标识容器。
kubelet 提供了 gRPC 服务来使得正在使用中的设备被发现,并且还未这些设备提供了元数据:
// PodResourcesLister 是一个由 kubelet 提供的服务,用来提供供节点上
// Pods 和容器使用的节点资源的信息
service PodResourcesLister {
rpc List(ListPodResourcesRequest) returns (ListPodResourcesResponse) {}
rpc GetAllocatableResources(AllocatableResourcesRequest) returns (AllocatableResourcesResponse) {}
}
这一 List
端点提供运行中 Pods 的资源信息,包括类似独占式分配的
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;
}
端点 GetAllocatableResources
提供最初在工作节点上可用的资源的信息。
此端点所提供的信息比导出给 API 服务器的信息更丰富。
// AllocatableResourcesResponses 包含 kubelet 所了解到的所有设备的信息
message AllocatableResourcesResponse {
repeated ContainerDevices devices = 1;
repeated int64 cpu_ids = 2;
repeated ContainerMemory memory = 3;
}
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。
设备插件与拓扑管理器的集成
FEATURE STATE: 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
结构体的示例:
pluginapi.Device{ID: "25102017", Health: pluginapi.Healthy, Topology:&pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{&pluginapi.NUMANode{ID: 0,},}}}
设备插件示例
下面是一些设备插件实现的示例:
接下来
3.12.4 - 服务目录
服务目录(Service Catalog)是 服务目录是一种扩展 API,它能让 Kubernetes 集群中运行的应用易于使用外部托管的的软件服务,例如云供应商提供的数据仓库服务。
服务目录可以检索、供应、和绑定由 服务代理人(Service Brokers)
提供的外部托管服务(Managed Services) ,
而无需知道那些服务具体是怎样创建和托管的。
服务代理(Service Broker)是由Open Service Broker API 规范 定义的一组托管服务的端点,这些服务由第三方提供并维护,其中的第三方可以是 AWS、GCP 或 Azure 等云服务提供商。
托管服务的一些示例是 Microsoft Azure Cloud Queue、Amazon Simple Queue Service 和 Google Cloud Pub/Sub,但它们可以是应用程序能够使用的任何软件交付物。
使用服务目录,集群操作员
可以浏览某服务代理所提供的托管服务列表,供应托管服务实例并与之绑定,
以使其可以被 Kubernetes 集群中的应用程序使用。
示例用例
应用开发人员 ,
希望使用消息队列,作为其在 Kubernetes 集群中运行的应用程序的一部分。
但是,他们不想承受构造这种服务的开销,也不想自行管理。
幸运的是,有一家云服务提供商通过其服务代理以托管服务的形式提供消息队列服务。
集群操作员可以设置服务目录并使用它与云服务提供商的服务代理通信,进而部署消息队列服务的实例
并使其对 Kubernetes 中的应用程序可用。
应用开发者于是可以不关心消息队列的实现细节,也不用对其进行管理。
他们的应用程序可以简单的将其作为服务使用。
架构
服务目录使用Open Service Broker API
与服务代理进行通信,并作为 Kubernetes API 服务器的中介,以便协商启动部署和获取
应用程序使用托管服务时必须的凭据。
服务目录实现为一个扩展 API 服务器和一个控制器,使用 Etcd 提供存储。
它还使用了 Kubernetes 1.7 之后版本中提供的
聚合层
来呈现其 API。
API 资源
服务目录安装 servicecatalog.k8s.io
API 并提供以下 Kubernetes 资源:
ClusterServiceBroker
:服务目录的集群内表现形式,封装了其服务连接细节。集群运维人员创建和管理这些资源,并希望使用该代理服务在集群中提供新类型的托管服务。
ClusterServiceClass
:由特定服务代理提供的托管服务。当新的 ClusterServiceBroker
资源被添加到集群时,服务目录控制器将连接到服务代理以获取可用的托管服务列表。然后为每个托管服务创建对应的新 ClusterServiceClass
资源。
ClusterServicePlan
:托管服务的特定产品。例如托管服务可能有不同的计划可用,如免费版本和付费版本,或者可能有不同的配置选项,例如使用 SSD 存储或拥有更多资源。与 ClusterServiceClass
类似,当一个新的 ClusterServiceBroker
被添加到集群时,服务目录会为每个托管服务的每个可用服务计划创建对应的新 ClusterServicePlan
资源。
ServiceInstance
:ClusterServiceClass
提供的示例。由集群运维人员创建,以使托管服务的特定实例可供一个或多个集群内应用程序使用。当创建一个新的 ServiceInstance
资源时,服务目录控制器将连接到相应的服务代理并指示它调配服务实例。
ServiceBinding
:ServiceInstance
的访问凭据。由希望其应用程序使用服务 ServiceInstance
的集群运维人员创建。创建之后,服务目录控制器将创建一个 Kubernetes Secret
,其中包含服务实例的连接细节和凭据,可以挂载到 Pod 中。
认证
服务目录支持这些认证方法:
使用方式
集群运维人员可以使用服务目录 API 资源来供应托管服务并使其在 Kubernetes 集群内可用。涉及的步骤有:
列出服务代理提供的托管服务和服务计划。
配置托管服务的新实例。
绑定到托管服务,它将返回连接凭证。
将连接凭证映射到应用程序中。
列出托管服务和服务计划
首先,集群运维人员在 servicecatalog.k8s.io
组内创建一个 ClusterServiceBroker
资源。此资源包含访问服务代理终结点所需的 URL 和连接详细信息。
这是一个 ClusterServiceBroker
资源的例子:
apiVersion : servicecatalog.k8s.io/v1beta1
kind : ClusterServiceBroker
metadata :
name : cloud-broker
spec :
# 指向服务代理的末端。(这里的 URL 是无法使用的。)
url : https://servicebroker.somecloudprovider.com/v1alpha1/projects/service-catalog/brokers/default
#####
# 这里可以添加额外的用来与服务代理通信的属性值,
# 例如持有者令牌信息或者 TLS 的 CA 包。
#####
下面的时序图展示了从服务代理列出可用托管服务和计划所涉及的各个步骤:
一旦 ClusterServiceBroker
资源被添加到了服务目录之后,将会触发一个到外部服务代理的
调用,以列举所有可用服务;
服务代理返回可用的托管服务和服务计划列表,这些列表将本地缓存在 ClusterServiceClass
和 ClusterServicePlan
资源中。
集群运维人员接下来可以使用以下命令获取可用托管服务的列表:
kubectl get clusterserviceclasses \
-o= custom-columns= SERVICE\ NAME:.metadata.name,EXTERNAL\ NAME:.spec.externalName
它应该输出一个和以下格式类似的服务名称列表:
SERVICE NAME EXTERNAL NAME
4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468 cloud-provider-service
... ...
他们还可以使用以下命令查看可用的服务计划:
kubectl get clusterserviceplans \
-o= custom-columns= PLAN\ NAME:.metadata.name,EXTERNAL\ NAME:.spec.externalName
它应该输出一个和以下格式类似的服务计划列表:
PLAN NAME EXTERNAL NAME
86064792-7ea2-467b-af93-ac9694d96d52 service-plan-name
... ...
供应一个新实例
集群运维人员 可以通过创建一个 ServiceInstance
资源来启动一个新实例的配置。
下面是一个 ServiceInstance
资源的例子:
apiVersion : servicecatalog.k8s.io/v1beta1
kind : ServiceInstance
metadata :
name : cloud-queue-instance
namespace : cloud-apps
spec :
# 引用之前返回的服务之一
clusterServiceClassExternalName : cloud-provider-service
clusterServicePlanExternalName : service-plan-name
#####
# 这里可添加额外的参数,供服务代理使用
#####
以下时序图展示了配置托管服务新实例所涉及的步骤:
创建 ServiceInstance
资源时,服务目录将启动一个到外部服务代理的调用,
请求供应一个实例。
服务代理创建一个托管服务的新实例并返回 HTTP 响应。
接下来,集群运维人员可以检查实例的状态是否就绪。
绑定到托管服务
在设置新实例之后,集群运维人员必须绑定到托管服务才能获取应用程序使用服务所需的连接凭据和服务账户的详细信息。该操作通过创建一个 ServiceBinding
资源完成。
以下是 ServiceBinding
资源的示例:
apiVersion : servicecatalog.k8s.io/v1beta1
kind : ServiceBinding
metadata :
name : cloud-queue-binding
namespace : cloud-apps
spec :
instanceRef :
name : cloud-queue-instance
#####
# 这里可以添加供服务代理使用的额外信息,例如 Secret 名称或者服务账号参数,
#####
以下顺序图展示了绑定到托管服务实例的步骤:
在创建 ServiceBinding
之后,服务目录调用外部服务代理,请求绑定服务实例所需的信息。
服务代理为相应服务账户启用应用权限/角色。
服务代理返回连接和访问托管服务示例所需的信息。这是由提供商和服务特定的,故返回的信息可能因服务提供商和其托管服务而有所不同。
映射连接凭据
完成绑定之后的最后一步就是将连接凭据和服务特定的信息映射到应用程序中。这些信息存储在 secret 中,集群中的应用程序可以访问并使用它们直接与托管服务进行连接。
Pod 配置文件
执行此映射的一种方法是使用声明式 Pod 配置。
以下示例描述了如何将服务账户凭据映射到应用程序中。名为 sa-key
的密钥保存在一个名为
provider-cloud-key
的卷中,应用程序会将该卷挂载在 /var/secrets/provider/key.json
路径下。环境变量 PROVIDER_APPLICATION_CREDENTIALS
将映射为挂载文件的路径。
...
spec :
volumes :
- name : provider-cloud-key
secret :
secretName : sa-key
containers :
...
volumeMounts :
- name : provider-cloud-key
mountPath : /var/secrets/provider
env :
- name : PROVIDER_APPLICATION_CREDENTIALS
value : "/var/secrets/provider/key.json"
以下示例描述了如何将 Secret 值映射为应用程序的环境变量。
在这个示例中,消息队列的主题名从 Secret provider-queue-credentials
中名为
topic
的主键映射到环境变量 TOPIC
中。
...
env :
- name : "TOPIC"
valueFrom :
secretKeyRef :
name : provider-queue-credentials
key : topic
接下来
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.23 版本的客户端能与 v1.22、
v1.23 和 v1.24 版本的控制面通信。
用最新兼容版的 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.23.0 版本,请输入:
curl -LO https://dl.k8s.io/release/v1.23.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 " $( <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/kubectl
mv ./kubectl ~/.local/bin/kubectl
# 之后将 ~/.local/bin/kubectl 添加到 $PATH
执行测试,以保障你安装的版本是最新的:
用原生包管理工具安装
<!--
1. Update the ` apt` package index and install packages needed to use the Kubernetes ` apt` repository:
-->
1. 更新 ` apt` 包索引,并安装使用 Kubernetes ` apt` 仓库锁需要的包:
``` shell
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl
```
<!--
2. Download the Google Cloud public signing key:
-->
2. 下载 Google Cloud 公开签名秘钥:
``` shell
sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
```
<!--
3. Add the Kubernetes ` apt` repository:
-->
3. 添加 Kubernetes ` apt` 仓库:
``` shell
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
```
<!--
4. Update ` apt` package index with the new repository and install kubectl:
-->
4. 更新 ` apt` 包索引,使之包含新的仓库并安装 kubectl:
``` shell
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-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg 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 提供自动补全功能,可以减轻许多输入的负担。
下面是为 Bash 和 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 自动补全功能
你现在需要确保一点:kubectl 补全脚本已经导入(sourced)到 shell 会话中。
这里有两种验证方法:
如果 kubectl 有关联的别名,你可以扩展 shell 补全来适配此别名:
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -F __start_kubectl k' >>~/.bashrc
说明:
bash-completion 负责导入 /etc/bash_completion.d
目录中的所有补全脚本。
两种方式的效果相同。重新加载 shell 后,kubectl 自动补全功能即可生效。
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,可以扩展脚本补全,以兼容该别名。
echo 'alias k=kubectl' >>~/.zshrc
echo 'compdef __start_kubectl k' >>~/.zshrc
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 complete:13: 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 " $( <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.23 版本的客户端能与 v1.22、
v1.23 和 v1.24 版本的控制面通信。
用最新兼容版本的 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.23.0 版本,则输入:
curl -LO "https://dl.k8s.io/release/v1.23.0/bin/darwin/amd64/kubectl"
对于 Apple Silicon 版本的 macOS,输入:
curl -LO "https://dl.k8s.io/release/v1.23.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 " $( <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 环境变量中。
测试一下,确保你安装的是最新的版本:
用 Homebrew 在 macOS 系统上安装
如果你是 macOS 系统,且用的是 Homebrew 包管理工具,
则可以用 Homebrew 安装 kubectl。
运行安装命令:
或
brew install kubernetes-cli
测试一下,确保你安装的是最新的版本:
用 Macports 在 macOS 上安装
如果你用的是 macOS,且用 Macports 包管理工具,则你可以用 Macports 安装kubectl。
运行安装命令:
sudo port selfupdate
sudo port install kubectl
测试一下,确保你安装的是最新的版本:
作为谷歌云 SDK 的一部分,在 macOS 上安装
kubectl 可以作为 Google Cloud SDK 的一部分被安装。
安装 Google Cloud SDK 。
运行安装 kubectl
的命令:
gcloud components 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 提供自动补全功能,这可以节省许多输入的麻烦。
下面是为 Bash 和 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 zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,可以扩展脚本补全,以兼容该别名。
echo 'alias k=kubectl' >>~/.zshrc
echo 'compdef __start_kubectl k' >>~/.zshrc
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 complete:13: 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 " $( <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.23 版本的客户端能与 v1.22、
v1.23 和 v1.24 版本的控制面通信。
用最新兼容版的 kubectl 有助于避免不可预见的问题。
在 Windows 上安装 kubectl
在 Windows 系统中安装 kubectl 有如下几种方法:
用 curl 在 Windows 上安装 kubectl
下载 最新发行版 v1.23.0 。
如果你已安装了 curl
,也可以使用此命令:
curl -LO "https://dl.k8s.io/release/v1.23.0/bin/windows/amd64/kubectl.exe"
验证该可执行文件(可选步骤)
下载 kubectl 校验和文件:
curl -LO "https://dl.k8s.io/v1.23.0/bin/windows/amd64/kubectl.exe.sha256"
基于校验和文件,验证 kubectl 的可执行文件:
将可执行文件的路径添加到 PATH
。
测试一下,确保此 kubectl
的版本和期望版本一致:
说明: Windows 版的 Docker Desktop
将其自带版本的
kubectl
添加到
PATH
。
如果你之前安装过 Docker Desktop,可能需要把此
PATH
条目置于 Docker Desktop 安装的条目之前,
或者直接删掉 Docker Desktop 的
kubectl
。
在 Windows 上用 Chocolatey 或 Scoop 安装
要在 Windows 上安装 kubectl,你可以使用包管理器 Chocolatey
或是命令行安装器 Scoop 。
choco install kubernetes-cli
测试一下,确保安装的是最新版本:
导航到你的 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 提供自动补全功能,可以减轻许多输入的负担。
下面是设置 Zsh 自动补全功能的操作步骤,前提是你在 Windows 上面运行的是 Zsh。
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,可以扩展脚本补全,以兼容该别名。
echo 'alias k=kubectl' >>~/.zshrc
echo 'compdef __start_kubectl k' >>~/.zshrc
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 complete:13: command not found: compdef
这样的错误提示,那请将下面内容添加到 ~/.zshrc
文件的开头:
autoload -Uz compinit
compinit
安装 kubectl convert
插件
一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
在将清单迁移到具有较新 Kubernetes 版本的未弃用 API 版本时,这个插件特别有用。
更多信息请访问 迁移到非弃用 API
用以下命令下载最新发行版:
curl -LO "https://dl.k8s.io/release/v1.23.0/bin/windows/amd64/kubectl-convert.exe"
验证该可执行文件(可选步骤)
下载 kubectl-convert 校验和文件:
curl -LO "https://dl.k8s.io/v1.23.0/bin/windows/amd64/kubectl-convert.exe.sha256"
基于校验和,验证 kubectl-convert 的可执行文件:
将可执行文件添加到你的 PATH
环境变量。
验证插件是否安装成功
如果你没有看到任何错误就代表插件安装成功了。
接下来
4.1.4 - 内含的工具
在页面 kubectl-installs-*.md 中包含的代码片段
4.1.4.1 - kubectl-convert 概述
一个 kubectl 插件,允许你将清单从一个 Kubernetes API 版本转换到不同的版本。
一个 Kubernetes 命令行工具 kubectl
的插件,允许你将清单在不同 API 版本间转换。
在将清单迁移到具有较新 Kubernetes 版本的未弃用 API 版本时,这个插件特别有用。
更多信息请访问 迁移到非弃用 API
4.1.4.2 - 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 自动补全功能
你现在需要确保一点:kubectl 补全脚本已经导入(sourced)到 shell 会话中。
这里有两种验证方法:
如果 kubectl 有关联的别名,你可以扩展 shell 补全来适配此别名:
echo 'alias k=kubectl' >>~/.bashrc
echo 'complete -F __start_kubectl k' >>~/.bashrc
说明:
bash-completion 负责导入 /etc/bash_completion.d
目录中的所有补全脚本。
两种方式的效果相同。重新加载 shell 后,kubectl 自动补全功能即可生效。
4.1.4.3 - 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.4 - zsh 自动补全
zsh 自动补全的一些可选配置
kubectl 通过命令 kubectl completion zsh
生成 Zsh 自动补全脚本。
在 shell 中导入(Sourcing)该自动补全脚本,将启动 kubectl 自动补全功能。
为了在所有的 shell 会话中实现此功能,请将下面内容加入到文件 ~/.zshrc
中。
source <( kubectl completion zsh)
如果你为 kubectl 定义了别名,可以扩展脚本补全,以兼容该别名。
echo 'alias k=kubectl' >>~/.zshrc
echo 'compdef __start_kubectl k' >>~/.zshrc
重新加载 shell 后,kubectl 自动补全功能将立即生效。
如果你收到 complete:13: command not found: compdef
这样的错误提示,那请将下面内容添加到 ~/.zshrc
文件的开头:
autoload -Uz compinit
compinit
4.1.4.5 - 后续内容
安装 kubectl 之后,还可以做些什么?
4.1.4.6 - 通过 gcloud 安装 kubectl
用各个特定操作系统标签页中包含的 gcloud 指令片段安装 kubectl。
kubectl 可以作为 Google Cloud SDK 的一部分被安装。
安装 Google Cloud SDK 。
运行安装 kubectl
的命令:
gcloud components install kubectl
验证一下,确保安装的是最新的版本:
4.1.4.7 - 验证 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 迁移到其他替代的容器运行时。
请参阅容器运行时
一节以了解可用的备选项。
当在迁移过程中遇到麻烦,请上报问题 。
那么问题就可以及时修复,你的集群也可以进入移除 dockershim 前的就绪状态。
4.2.1.1 - 检查弃用 Dockershim 对你的影响
Kubernetes 的 dockershim
组件使得你可以把 Docker 用作 Kubernetes 的
容器运行时 。
在 Kubernetes v1.20 版本中,内建组件 dockershim
被弃用。
本页讲解你的集群把 Docker 用作容器运行时的运作机制,
并提供使用 dockershim
时,它所扮演角色的详细信息,
继而展示了一组验证步骤,可用来检查弃用 dockershim
对你的工作负载的影响。
检查你的应用是否依赖于 Docker
虽然你通过 Docker 创建了应用容器,但这些容器却可以运行于所有容器运行时。
所以这种使用 Docker 容器运行时的方式并不构成对 Docker 的依赖。
当用了替代的容器运行时之后,Docker 命令可能不工作,甚至产生意外的输出。
这才是判定你是否依赖于 Docker 的方法。
确认没有特权 Pod 执行 docker 命令。
检查 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 这类容器运行时来运行和管理,操作体验也和以前一样。
但是现在,由于直接用容器运行时调度容器,所以它们对 Docker 来说是不可见的。
因此,你以前用来检查这些容器的 Docker 工具或漂亮的 UI 都不再可用。
你不能再使用 docker ps
或 docker inspect
命令来获取容器信息。
由于你不能列出容器,因此你不能获取日志、停止容器,甚至不能通过 docker exec
在容器中执行命令。
说明: 如果你用 Kubernetes 运行工作负载,最好通过 Kubernetes API停止容器,而不是通过容器运行时
(此建议适用于所有容器运行时,不仅仅是针对 Docker)。
你仍然可以下载镜像,或者用 docker build
命令创建它们。
但用 Docker 创建、下载的镜像,对于容器运行时和 Kubernetes,均不可见。
为了在 Kubernetes 中使用,需要把镜像推送(push)到某注册中心。
4.2.1.2 - 从 dockershim 迁移遥测和安全代理
在 Kubernetes 1.20 版本中,dockershim 被弃用。
在博文弃用 Dockershim 常见问题 中,
你大概已经了解到,大多数应用并没有直接通过运行时来托管容器。
但是,仍然有大量的遥测和安全代理依赖 docker 来收集容器元数据、日志和指标。
本文汇总了一些信息和链接:信息用于阐述如何探查这些依赖,链接用于解释如何迁移这些代理去使用通用的工具或其他容器运行。
遥测和安全代理
为了让代理运行在 Kubernetes 集群中,我们有几种办法。
代理既可以直接在节点上运行,也可以作为守护进程运行。
为什么遥测代理依赖于 Docker?
因为历史原因,Kubernetes 建立在 Docker 之上。
Kubernetes 管理网络和调度,Docker 则在具体的节点上定位并操作容器。
所以,你可以从 Kubernetes 取得调度相关的元数据,比如 Pod 名称;从 Docker 取得容器状态信息。
后来,人们开发了更多的运行时来管理容器。
同时一些项目和 Kubernetes 特性也不断涌现,支持跨多个运行时收集容器状态信息。
一些代理和 Docker 工具紧密绑定。此类代理可以这样运行命令,比如用
docker ps
或 docker top
这类命令来列出容器和进程,用
docker logs
订阅 Docker 的日志。
但随着 Docker 作为容器运行时被弃用,这些命令将不再工作。
识别依赖于 Docker 的 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 迁移的最新说明。
4.2.2 - 用 kubeadm 进行管理
4.2.2.1 - 使用 kubeadm 进行证书管理
FEATURE STATE: Kubernetes v1.15 [stable]
由 kubeadm 生成的客户端证书在 1 年后到期。
本页说明如何使用 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
的目录,将 CRS 输出到指定位置。
如果未指定 --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.23
的 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 或域名请求服务证书。
4.2.2.2 - 配置 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
驱动
正如本指南阐述的:不推荐与 kubeadm 一起使用 cgroupfs
驱动。
如仍需使用 cgroupfs
,
且要防止 kubeadm upgrade
修改现有系统中 KubeletConfiguration
的 cgroup 驱动,
你必须显式声明它的值。
此方法应对的场景为:在将来某个版本的 kubeadm 中,你不想使用默认的 systemd
驱动。
参阅以下章节“修改 kubelet 的 ConfigMap”,了解显式设置该值的方法。
如果你希望配置容器运行时来使用 cgroupfs
驱动,
则必须参考所选容器运行时的文档。
迁移到 systemd
驱动
要将现有 kubeadm 集群的 cgroup 驱动就地升级为 systemd
,
需要执行一个与 kubelet 升级类似的过程。
该过程必须包含下面两个步骤:
说明: 还有一种方法,可以用已配置了 systemd
的新节点替换掉集群中的老节点。
按这种方法,在加入新节点、确保工作负载可以安全迁移到新节点、及至删除旧节点这一系列操作之前,
只需执行以下第一个步骤。
修改 kubelet 的 ConfigMap
用命令 kubectl get cm -n kube-system | grep kubelet-config
找到 kubelet 的 ConfigMap 名称。
运行 kubectl edit cm kubelet-config-x.yy -n kube-system
(把 x.yy
替换为 Kubernetes 版本)。
修改现有 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.3 - 升级 kubeadm 集群
本页介绍如何将 kubeadm
创建的 Kubernetes 集群从 1.22.x 版本
升级到 1.23.x 版本以及从 1.23.x
升级到 1.23.y(其中 y > x
)。略过次版本号的升级是
不被支持的。
要查看 kubeadm 创建的有关旧版本集群升级的信息,请参考以下页面:
升级工作的基本流程如下:
升级主控制平面节点
升级其他控制平面节点
升级工作节点
准备开始
务必仔细认真阅读发行说明 。
集群应使用静态的控制平面和 etcd Pod 或者外部 etcd。
务必备份所有重要组件,例如存储在数据库中应用层面的状态。
kubeadm upgrade
不会影响你的工作负载,只会涉及 Kubernetes 内部的组件,但备份终究是好的。
必须禁用交换分区 。
附加信息
在对 kubelet 作次版本升版时需要腾空节点 。
对于控制面节点,其上可能运行着 CoreDNS Pods 或者其它非常重要的负载。
升级后,因为容器规约的哈希值已更改,所有容器都会被重新启动。
确定要升级到哪个版本
使用操作系统的包管理器找到最新的补丁版本 Kubernetes 1.23:
apt update
apt-cache madison kubeadm
# 在列表中查找最新的 1.23 版本
# 它看起来应该是 1.23.x-00,其中 x 是最新的补丁版本
yum list --showduplicates kubeadm --disableexcludes=kubernetes
# 在列表中查找最新的 1.23 版本
# 它看起来应该是 1.23.x-0,其中 x 是最新的补丁版本
升级控制平面节点
控制面节点上的升级过程应该每次处理一个节点。
首先选择一个要先行升级的控制面节点。该节点上必须拥有
/etc/kubernetes/admin.conf
文件。
执行 "kubeadm upgrade"
升级第一个控制面节点
# 用最新的补丁版本号替换 1.23.x-00 中的 x
apt-mark unhold kubeadm && \
apt-get update && apt-get install -y kubeadm = 1.23.x-00 && \
apt-mark hold kubeadm
-
# 从 apt-get 1.1 版本起,你也可以使用下面的方法
apt-get update && \
apt-get install -y --allow-change-held-packages kubeadm = 1.23.x-00
# 用最新的补丁版本号替换 1.23.x-0 中的 x
yum install -y kubeadm-1.23.x-0 --disableexcludes= kubernetes
选择要升级到的目标版本,运行合适的命令。例如:
# 将 x 替换为你为此次升级所选择的补丁版本号
sudo kubeadm upgrade apply v1.23.x
一旦该命令结束,你应该会看到:
[upgrade/successful] SUCCESS! Your cluster was upgraded to "v1.23.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.23.x-00 中的 x
apt-mark unhold kubelet kubectl && \
apt-get update && apt-get install -y kubelet=1.23.x-00 kubectl=1.23.x-00 && \
apt-mark hold kubelet kubectl
-
# 从 apt-get 的 1.1 版本开始,你也可以使用下面的方法:
apt-get update && \
apt-get install -y --allow-change-held-packages kubelet=1.23.x-00 kubectl=1.23.x-00
# 用最新的补丁版本号替换 1.23.x-00 中的 x
yum install -y kubelet-1.23.x-0 kubectl-1.23.x-0 --disableexcludes=kubernetes
重启 kubelet
sudo systemctl daemon-reload
sudo systemctl restart kubelet
解除节点的保护
通过将节点标记为可调度,让其重新上线:
# 将 <node-to-drain> 替换为你的节点名称
kubectl uncordon <node-to-drain>
升级工作节点
工作节点上的升级过程应该一次执行一个节点,或者一次执行几个节点,
以不影响运行工作负载所需的最小容量。
升级 kubeadm
升级 kubeadm:
# 将 1.23.x-00 中的 x 替换为最新的补丁版本号
apt-mark unhold kubeadm && \
apt-get update && apt-get install -y kubeadm = 1.23.x-00 && \
apt-mark hold kubeadm
-
# 从 apt-get 的 1.1 版本开始,你也可以使用下面的方法:
apt-get update && \
apt-get install -y --allow-change-held-packages kubeadm = 1.23.x-00
# 用最新的补丁版本替换 1.23.x-00 中的 x
yum install -y kubeadm-1.23.x-0 --disableexcludes= kubernetes
执行 "kubeadm upgrade"
腾空节点
升级 kubelet 和 kubectl
升级 kubelet 和 kubectl:
# 将 1.23.x-00 中的 x 替换为最新的补丁版本
apt-mark unhold kubelet kubectl && \
apt-get update && apt-get install -y kubelet = 1.23.x-00 kubectl = 1.23.x-00 && \
apt-mark hold kubelet kubectl
# 从 apt-get 的 1.1 版本开始,你也可以使用下面的方法:
apt-get update && \
apt-get install -y --allow-change-held-packages kubelet = 1.23.x-00 kubectl = 1.23.x-00
# 将 1.23.x-0 x 替换为最新的补丁版本
yum install -y kubelet-1.23.x-0 kubectl-1.23.x-0 --disableexcludes= kubernetes
重启 kubelet
sudo systemctl daemon-reload
sudo systemctl restart kubelet
取消对节点的保护
通过将节点标记为可调度,让节点重新上线:
# 将 <node-to-drain> 替换为当前节点的名称
kubectl uncordon <node-to-drain>
验证集群的状态
在所有节点上升级 kubelet 后,通过从 kubectl 可以访问集群的任何位置运行以下命令,
验证所有节点是否再次可用:
STATUS
应显示所有节点为 Ready
状态,并且版本号已经被更新。
从故障状态恢复
如果 kubeadm upgrade
失败并且没有回滚,例如由于执行期间节点意外关闭,
你可以再次运行 kubeadm upgrade
。
此命令是幂等的,并最终确保实际状态是你声明的期望状态。
要从故障状态恢复,你还可以运行 kubeadm upgrade --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.4 - 添加 Windows 节点
FEATURE STATE: Kubernetes v1.18 [beta]
你可以使用 Kubernetes 来混合运行 Linux 和 Windows 节点,这样你就可以
混合使用运行于 Linux 上的 Pod 和运行于 Windows 上的 Pod。
本页面展示如何将 Windows 节点注册到你的集群。
准备开始
您的 Kubernetes 服务器版本必须不低于版本 1.17.
要获知版本信息,请输入
kubectl version
.
教程目标
将一个 Windows 节点注册到集群上
配置网络,以便 Linux 和 Windows 上的 Pod 和 Service 之间能够相互通信。
开始行动:向你的集群添加一个 Windows 节点
联网配置
一旦你有了一个基于 Linux 的 Kubernetes 控制面节点,你就可以为其选择联网方案。
出于简单考虑,本指南展示如何使用 VXLAN 模式的 Flannel。
配置 Flannel
为 Flannel 准备 Kubernetes 的控制面
在我们的集群中,建议对 Kubernetes 的控制面进行少许准备处理。
建议在使用 Flannel 时为 iptables 链启用桥接方式的 IPv4 流处理,
必须在所有 Linux 节点上执行如下命令:
sudo sysctl net.bridge.bridge-nf-call-iptables= 1
下载并配置 Linux 版本的 Flannel
下载最新的 Flannel 清单文件:
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
修改 Flannel 清单中的 net-conf.json
部分,将 VNI 设置为 4096,并将 Port 设置为 4789。
结果看起来像下面这样:
net-conf.json: |
{
"Network" : "10.244.0.0/16" ,
"Backend" : {
"Type" : "vxlan" ,
"VNI" : 4096 ,
"Port" : 4789
}
}
说明: 在 Linux 节点上 VNI 必须设置为 4096,端口必须设置为 4789,这样才能令其与 Windows 上的
Flannel 互操作。关于这些字段的详细说明,请参见
VXLAN 文档 。
说明: 如要使用 L2Bridge/Host-gateway 模式,则可将 Type
值设置为
"host-gw"
,并忽略 VNI
和 Port
的设置。
应用 Flannel 清单并验证
首先应用 Flannel 配置:
kubectl apply -f kube-flannel.yml
几分钟之后,如果 Flannel Pod 网络被正确部署,你应该会看到所有 Pods 都处于运行中状态。
kubectl get pods -n kube-system
输出中应该包含处于运行中状态的 Linux Flannel DaemonSet:
NAMESPACE NAME READY STATUS RESTARTS AGE
...
kube-system kube-flannel-ds-54954 1/1 Running 0 1m
添加 Windows Flannel 和 kube-proxy DaemonSet
现在你可以添加 Windows 兼容版本的 Flannel 和 kube-proxy。为了确保你能获得兼容
版本的 kube-proxy,你需要替换镜像中的标签。
下面的例子中展示的是针对 Kubernetes v1.23.0 版本的用法,
不过你应该根据你自己的集群部署调整其中的版本号。
curl -L https://github.com/kubernetes-sigs/sig-windows-tools/releases/latest/download/kube-proxy.yml | sed 's/VERSION/v1.23.0/g' | kubectl apply -f -
kubectl apply -f https://github.com/kubernetes-sigs/sig-windows-tools/releases/latest/download/flannel-overlay.yml
说明: 如果你在 Windows 节点上使用的不是以太网(即,"Ethernet0 2")接口,你需要
修改 flannel-host-gw.yml
或 flannel-overlay.yml
文件中的下面这行:
wins cli process run --path /k/flannel/setup.exe --args "--mode=overlay --interface=Ethernet"
在其中根据情况设置要使用的网络接口。
# Example
curl -L https://github.com/kubernetes-sigs/sig-windows-tools/releases/latest/download/flannel-overlay.yml | sed 's/Ethernet/Ethernet0 2/g' | kubectl apply -f -
加入 Windows 工作节点
说明:
Windows 节的所有代码片段都需要在 PowerShell 环境中执行,并且要求在
Windows 工作节点上具有提升的权限(Administrator)。
安装 Docker EE
Install-WindowsFeature -Name containers
安装 Docker
操作指南在 Install Docker Engine - Enterprise on Windows Servers 。
安装 wins、kubelet 和 kubeadm
curl.exe -LO https: //raw.githubusercontent.com/kubernetes-sigs /sig-windows -tools/master/kubeadm/scripts/PrepareNode.ps1
.\PrepareNode.ps1 -KubernetesVersion v1.23.0
运行 kubeadm
添加节点
当你在控制面主机上运行 kubeadm init
时,输出了一个命令。现在运行这个命令。
如果你找不到这个命令,或者命令中对应的令牌已经过期,你可以(在一个控制面主机上)运行
kubeadm token create --print-join-command
来生成新的令牌和 join 命令。
安装 containerD
curl.exe -LO https: //github.com/kubernetes-sigs /sig-windows -tools/releases/latest/download/Install-Containerd .ps1
.\Install-Containerd .ps1
说明:
要安装特定版本的 containerD,使用参数 -ContainerDVersion指定版本。
# Example
.\Install-Containerd .ps1 -ContainerDVersion 1.4.1
说明:
如果你在 Windows 节点上使用了与 Ethernet 不同的接口(例如 "Ethernet0 2"),使用参数 -netAdapterName
指定名称。
# Example
.\Install-Containerd .ps1 -netAdapterName "Ethernet0 2"
安装 wins,kubelet 和 kubeadm
curl.exe -LO https: //raw.githubusercontent.com/kubernetes-sigs /sig-windows -tools/master/kubeadm/scripts/PrepareNode.ps1
.\PrepareNode.ps1 -KubernetesVersion v1.23.0 -ContainerRuntime containerD
运行 kubeadm
添加节点
使用当你在控制面主机上运行 kubeadm init
时得到的命令。
如果你找不到这个命令,或者命令中对应的令牌已经过期,你可以(在一个控制面主机上)运行
kubeadm token create --print-join-command
来生成新的令牌和 join 命令。
说明: If using CRI-containerD add --cri-socket "npipe:////./pipe/containerd-containerd"
to the kubeadm call
检查你的安装
你现在应该能够通过运行下面的命令来查看集群中的 Windows 节点了:
kubectl get nodes -o wide
如果你的新节点处于 NotReady
状态,很可能的原因是系统仍在下载 Flannel 镜像。
你可以像之前一样,通过检查 kube-system
名字空间中的 Flannel Pods 来了解
安装进度。
kubectl -n kube-system get pods -l app = flannel
一旦 Flannel Pod 运行起来,你的节点就应该能进入 Ready
状态并可
用来处理负载。
接下来
4.2.2.5 - 升级 Windows 节点
FEATURE STATE: Kubernetes v1.18 [beta]
本页解释如何升级用 kubeadm 创建的
Windows 节点。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 1.17.
要获知版本信息,请输入
kubectl version
.
升级工作节点
升级 kubeadm
在 Windows 节点上升级 kubeadm:
# 将 v1.23.0 替换为你希望的版本
curl.exe -Lo C:\k\kubeadm.exe https: //dl.k8s.io//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
在 Windows 节点上升级并重启 kubelet:
stop-service kubelet
curl.exe -Lo C:\k\kubelet.exe https: //dl.k8s.io//bin/windows/amd64/kubelet.exe
restart-service kubelet
对节点执行 uncordon 操作
从一台能够访问到 Kubernetes API 的机器上,通过将节点标记为可调度,使之
重新上线:
# 将 <要腾空的节点> 替换为你的节点名称
kubectl uncordon <要腾空的节点>
升级 kube-proxy
在一台可访问 Kubernetes API 的机器上和,将 v1.23.0 替换成你
期望的版本后再次执行下面的命令:
curl -L https://github.com/kubernetes-sigs/sig-windows-tools/releases/latest/download/kube-proxy.yml | sed 's/VERSION/v1.23.0/g' | kubectl apply -f -
4.2.3 - 管理内存,CPU 和 API 资源
4.2.3.1 - 为命名空间配置默认的内存请求和限制
本文介绍怎样给命名空间配置默认的内存请求和限制。
如果在一个有默认内存限制的命名空间创建容器,该容器没有声明自己的内存限制时,
将会被指定默认内存限制。
Kubernetes 还为某些情况指定了默认的内存请求,本章后面会进行介绍。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
你的集群中的每个节点必须至少有 2 GiB 的内存。
创建命名空间
创建一个命名空间,以便本练习中所建的资源与集群的其余资源相隔离。
kubectl create namespace default-mem-example
创建 LimitRange 和 Pod
这里给出了一个限制范围对象的配置文件。该配置声明了一个默认的内存请求和一个默认的内存限制。
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 命名空间创建容器,并且该容器没有声明自己的内存请求和限制值,
它将被指定默认的内存请求 256 MiB 和默认的内存限制 512 MiB。
下面是具有一个容器的 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
输出结果显示该容器的内存请求被设置为了容器配置文件中声明的数值。
容器的内存限制被设置为 512MiB,即命名空间的默认内存限制。
resources:
limits:
memory: 512Mi
requests:
memory: 128Mi
设置默认内存限制和请求的动机
如果你的命名空间有资源配额,那么默认内存限制是很有帮助的。
下面是一个例子,通过资源配额为命名空间设置两项约束:
运行在命名空间中的每个容器必须有自己的内存限制。
命名空间中所有容器的内存使用量之和不能超过声明的限制值。
如果一个容器没有声明自己的内存限制,会被指定默认限制,然后它才会被允许在限定了配额的命名空间中运行。
清理
删除你的命名空间:
kubectl delete namespace default-mem-example
接下来
集群管理员参考
应用开发者参考
4.2.3.2 - 为命名空间配置默认的 CPU 请求和限制
本章介绍怎样为命名空间配置默认的 CPU 请求和限制。
一个 Kubernetes 集群可被划分为多个命名空间。如果在配置了 CPU 限制的命名空间创建容器,
并且该容器没有声明自己的 CPU 限制,那么这个容器会被指定默认的 CPU 限制。
Kubernetes 在一些特定情况还会指定 CPU 请求,本文后续章节将会对其进行解释。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建命名空间
创建一个命名空间,以便本练习中创建的资源和集群的其余部分相隔离。
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 命名空间创建一个容器,该容器没有声明自己的 CPU 请求和限制时,
将会给它指定默认的 CPU 请求0.5和默认的 CPU 限制值1.
这里给出了包含一个容器的 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 的容器有一个500 millicpus的 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
结果显示该容器的 CPU 请求被设置为容器配置文件中声明的数值。
容器的CPU限制被设置为 1 CPU,即该命名空间的默认 CPU 限制值。
resources:
limits:
cpu: "1"
requests:
cpu: 750m
默认 CPU 限制和请求的动机
如果你的命名空间有一个
资源配额 ,
那么有一个默认的 CPU 限制是有帮助的。这里有资源配额强加给命名空间的两条限制:
命名空间中运行的每个容器必须有自己的 CPU 限制。
命名空间中所有容器使用的 CPU 总和不能超过一个声明值。
如果容器没有声明自己的 CPU 限制,将会给它一个默认限制,这样它就能被允许运行在一个有配额限制的命名空间中。
清理
删除你的命名空间:
kubectl delete namespace constraints-cpu-example
接下来
集群管理员参考
应用开发者参考
4.2.3.3 - 配置命名空间的最小和最大内存约束
本页介绍如何设置在命名空间中运行的容器使用的内存的最小值和最大值。 你可以在
LimitRange
对象中指定最小和最大内存值。如果 Pod 不满足 LimitRange 施加的约束,则无法在命名空间中创建它。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
集群中每个节点必须至少要有 1 GiB 的内存。
创建命名空间
创建一个命名空间,以便在此练习中创建的资源与群集的其余资源隔离。
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 命名空间中创建容器,Kubernetes 就会执行下面的步骤:
如果 Container 未指定自己的内存请求和限制,将为它指定默认的内存请求和限制。
验证 Container 的内存请求是否大于或等于500 MiB。
验证 Container 的内存限制是否小于或等于1 GiB。
这里给出了包含一个 Container 的 Pod 配置文件。Container 声明了 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
输出结果显示容器的内存请求为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
因为你的容器没有声明自己的内存请求和限制,它从 LimitRange 那里获得了
默认的内存请求和限制 。
此时,你的容器可能运行起来也可能没有运行起来。
回想一下我们本次任务的先决条件是你的每个节点都至少有1 GiB 的内存。
如果你的每个节点都只有1 GiB 的内存,那将没有一个节点拥有足够的可分配内存来满足1 GiB 的内存请求。
删除你的 Pod:
kubectl delete pod constraints-mem-demo-4 --namespace=constraints-mem-example
强制执行内存最小和最大限制
LimitRange 为命名空间设定的最小和最大内存限制只有在 Pod 创建和更新时才会强制执行。
如果你更新 LimitRange,它不会影响此前创建的 Pod。
设置内存最小和最大限制的动因
作为集群管理员,你可能想规定 Pod 可以使用的内存总量限制。例如:
集群的每个节点有 2 GB 内存。你不想接受任何请求超过 2 GB 的 Pod,因为集群中没有节点可以满足。
集群由生产部门和开发部门共享。你希望允许产品部门的负载最多耗用 8 GB 内存,
但是开发部门的负载最多可使用 512 MiB。
这时,你可以为产品部门和开发部门分别创建名字空间,并为各个名字空间设置内存约束。
清理
删除你的命名空间:
kubectl delete namespace constraints-mem-example
接下来
集群管理员参考
应用开发者参考
4.2.3.4 - 为命名空间配置 CPU 最小和最大约束
本页介绍如何为命名空间中容器和 Pod 使用的 CPU 资源设置最小和最大值。
你可以通过
LimitRange
对象声明 CPU 的最小和最大值. 如果 Pod 不能满足 LimitRange 的限制,它就不能在命名空间中创建。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
你的集群中每个节点至少要有 1 个 CPU 可用才能运行本任务示例。
创建命名空间
创建一个命名空间,以便本练习中创建的资源和集群的其余资源相隔离。
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 命名空间中创建容器,Kubernetes 都会执行下面这些步骤:
如果容器没有声明自己的 CPU 请求和限制,将为容器指定默认 CPU 请求和限制。
核查容器声明的 CPU 请求确保其大于或者等于 200 millicpu。
核查容器声明的 CPU 限制确保其小于或者等于 800 millicpu。
说明: 当创建 LimitRange 对象时,你也可以声明大页面和 GPU 的限制。
当这些资源同时声明了 'default' 和 'defaultRequest' 参数时,两个参数值必须相同。
这里给出了包含一个容器的 Pod 的配置文件。
该容器声明了 500 millicpu 的 CPU 请求和 800 millicpu 的 CPU 限制。
这些参数满足了 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
输出结果表明容器的 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 的配置文件。该容器声明了100 millicpu的 CPU 请求和800 millicpu的 CPU 限制。
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 的容器有个 800 millicpu 的 CPU 请求和 800 millicpu 的 CPU 限制。
容器是怎样得到那些值的呢?
resources :
limits :
cpu : 800m
requests :
cpu : 800m
因为你的 Container 没有声明自己的 CPU 请求和限制,LimitRange 给它指定了
默认的 CPU 请求和限制
此时,你的容器可能运行也可能没有运行。
回想一下,本任务的先决条件是你的节点要有 1 个 CPU。
如果你的每个节点仅有 1 个 CPU,那么可能没有任何一个节点可以满足 800 millicpu 的 CPU 请求。
如果你在用的节点恰好有两个 CPU,那么你才可能有足够的 CPU 来满足 800 millicpu 的请求。
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 总量。你可以通过
ResourceQuota
对象设置配额.
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
集群中每个节点至少有 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 命名空间中设置了如下要求:
每个容器必须有内存请求和限制,以及 CPU 请求和限制。
所有容器的内存请求总和不能超过1 GiB。
所有容器的内存限制总和不能超过2 GiB。
所有容器的 CPU 请求总和不能超过1 cpu。
所有容器的 CPU 限制总和不能超过2 cpu。
创建 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
尝试创建第二个 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 限制命名空间中所有容器的内存请求总量。
同样你也可以限制内存限制总量、CPU 请求总量、CPU 限制总量。
如果你想对单个容器而不是所有容器进行限制,就请使用
LimitRange 。
清理
删除你的命名空间:
kubectl delete namespace quota-mem-cpu-example
接下来
集群管理员参考
应用开发者参考
4.2.3.6 - 配置命名空间下 Pod 配额
本文主要描述如何配置一个命名空间下可运行的 Pod 个数配额。
你可以使用
ResourceQuota
对象来配置配额。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建一个命名空间
首先创建一个命名空间,这样可以将本次操作中创建的资源与集群其他资源隔离开来。
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 尝试创建三个 Pods,且运行相同的应用。
创建这个 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
从输出的信息我们可以看到,尽管尝试创建三个 Pod,但是由于配额的限制,只有两个 Pod 能被成功创建。
spec :
...
replicas : 3
...
status :
availableReplicas : 2
...
lastUpdateTime : 2017-07-07T20: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'
清理
删除命名空间:
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.key 和 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
查看证书签名请求:
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,就像前面小节描述的那样。
以下示例假定你的默认 DSN 域名为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 证书,用来做身份验证,
如本 文档所述。
4.2.5 - 安装网络规则驱动
4.2.5.1 - Use Antrea for NetworkPolicy
title: 使用 Antrea 提供 NetworkPolicy
content_type: task
weight: 10
本页展示了如何在 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 po -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
默认情况下,从 Kubernetes 1.7.0 版本开始的 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
运行以下命令将配置映射添加到你的集群:
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 忽略本地链路,
可以在配置映射中将 masqLinkLocal 设置为 true。
nonMasqueradeCIDRs:
- 10.0.0.0/8
resyncInterval: 60s
masqLinkLocal: true
4.2.7 - Kubernetes 云管理控制器
FEATURE STATE: 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 云管理控制器作为守护进程运行。请使用如下指南:
# This is an example of how to setup cloud-controller-manager as a Daemonset in your cluster.
# It assumes that your masters can run pods and has the role node-role.kubernetes.io/master
# Note that this Daemonset will not work straight out of the box for your cloud, this is
# meant to be a guideline.
---
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
# for in-tree providers we use k8s.gcr.io/cloud-controller-manager
# this can be replaced with any other image for out-of-tree providers
image : k8s.gcr.io/cloud-controller-manager:v1.8.0
command :
- /usr/local/bin/cloud-controller-manager
- --cloud-provider=[YOUR_CLOUD_PROVIDER] # Add your own cloud provider here!
- --leader-elect=true
- --use-service-account-credentials
# these flags will vary for every cloud provider
- --allocate-node-cidrs=true
- --configure-cloud-routes=true
- --cluster-cidr=172.17.0.0/16
tolerations :
# this is required so CCM can bootstrap itself
- key : node.cloudprovider.kubernetes.io/uninitialized
value : "true"
effect : NoSchedule
# this is to have the daemonset runnable on master nodes
# the taint may vary depending on your cluster setup
- key : node-role.kubernetes.io/master
effect : NoSchedule
# this is to restrict CCM to only run on master nodes
# the node selector may vary depending on your cluster setup
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 - 为 Kubernetes 运行 etcd 集群
etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
您的 Kubernetes 集群的 etcd 数据库通常需要有个备份计划。
要了解 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/client.crt \
--key=/etc/kubernetes/pki/etcd/client.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
member list
限制 etcd 集群的访问
配置安全通信后,将 etcd 集群的访问限制在 Kubernetes API 服务器上。使用 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
移除失败的成员
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-servers
参数,使 Kubernetes 知道配置进行了更改,然后重新启动 Kubernetes API 服务器。
如果在 deployment 中使用了负载均衡,更新负载均衡配置。
有关集群重新配置的详细信息,请参阅 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 --data-dir <data-dir-location> snapshot restore 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
),以确保它们不会
依赖一些过时的数据。请注意,实际中还原会花费一些时间。
在还原过程中,关键组件将丢失领导锁并自行重启。
4.2.9 - 为系统守护进程预留计算资源
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 驱动
来保证系统正常运行。
例如,如果操作员使用 docker
运行时提供的 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 将会失败。
系统预留值
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
将会失败。
显式保留的 CPU 列表
FEATURE STATE: 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
memeory
,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 或存储
大于 90Gi,
kubelet 将会驱逐 Pod。
4.2.10 - 为节点发布扩展资源
本文展示了如何为节点指定扩展资源(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.11 - 以非root用户身份运行 Kubernetes 节点组件
FEATURE STATE: 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 运行 Kubernetes。
请参阅 Minikube 文档中的 docker 驱动页面。
它不支持 Rootless 模式的 Podman。
直接在主机上运行 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 文件系统,也可以在不使用这个特性门控的情况下在用户命名空间运行 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.12 - 使用 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,可以在集群中使用 CoreDNS 代替 kube-dns 部署,
或者使用 kubeadm 等工具来为你部署和升级集群。
安装 CoreDNS
有关手动部署或替换 kube-dns,请参阅
CoreDNS GitHub 工程 。
迁移到 CoreDNS
使用 kubeadm 升级现有集群
在 Kubernetes 1.10 及更高版本中,当你使用 kubeadm
升级使用 kube-dns
的集群时,你还可以迁移到 CoreDNS。
在本例中 kubeadm
将生成 CoreDNS 配置("Corefile")基于 kube-dns
ConfigMap,
保存存根域和上游名称服务器的配置。
如果你正在从 kube-dns 迁移到 CoreDNS,请确保在升级期间将 CoreDNS
特性门设置为 true
。
例如,v1.11.0
升级应该是这样的:
kubeadm upgrade apply v1.11.0 --feature-gates=CoreDNS=true
在 Kubernetes 版本 1.13 和更高版本中,CoreDNS
特性门已经删除,CoreDNS 在默认情况下使用。
在 1.11 之前的版本中,核心文件将被升级过程中创建的文件覆盖。
如果已对其进行自定义,则应保存现有的 ConfigMap。
在新的 ConfigMap 启动并运行后,你可以重新应用自定义。
如果你在 Kubernetes 1.11 及更高版本中运行 CoreDNS,则在升级期间,将保留现有的 Corefile。
在 kubernetes 1.21 中,kubeadm 移除了对 kube-dns
的支持。
升级 CoreDNS
从 v1.9 起,Kubernetes 提供了 CoreDNS。
你可以在此处
查看 Kubernetes 随附的 CoreDNS 版本以及对 CoreDNS 所做的更改。
如果你只想升级 CoreDNS 或使用自己的自定义镜像,则可以手动升级 CoreDNS。
参看指南和演练
文档了解如何平滑升级。
CoreDNS 调优
当资源利用方面有问题时,优化 CoreDNS 的配置可能是有用的。
有关详细信息,请参阅有关扩缩 CoreDNS 的文档 。
接下来
你可以通过修改 Corefile
来配置 CoreDNS ,以支持比 kube-dns 更多的用例。
请参考 CoreDNS 网站
以了解更多信息。
4.2.13 - 使用 KMS 驱动进行数据加密
本页展示了如何配置秘钥管理服务—— Key Management Service (KMS) 驱动和插件以启用
Secret 数据加密。
准备开始
需要 Kubernetes 1.10.0 或更新版本
需要 etcd v3 或更新版本
FEATURE STATE: Kubernetes v1.12 [beta]
KMS 加密驱动使用封套加密模型来加密 etcd 中的数据。
数据使用数据加密秘钥(DEK)加密;每次加密都生成一个新的 DEK。
这些 DEK 经一个秘钥加密秘钥(KEK)加密后在一个远端的 KMS 中存储和管理。
KMS 驱动使用 gRPC 与一个特定的 KMS 插件通信。这个 KMS 插件作为一个 gRPC
服务器被部署在 Kubernetes 主服务器的同一个主机上,负责与远端 KMS 的通信。
配置 KMS 驱动
为了在 API 服务器上配置 KMS 驱动,在加密配置文件中的驱动数组中加入一个类型为 kms
的驱动,并设置下列属性:
name
: KMS 插件的显示名称。
endpoint
: gRPC 服务器(KMS 插件)的监听地址。该端点是一个 UNIX 域套接字。
cachesize
: 以明文缓存的数据加密秘钥(DEKs)的数量。一旦被缓存,
就可以直接使用 DEKs 而无需另外调用 KMS;而未被缓存的 DEKs 需要调用一次 KMS 才能解包。
timeout
: 在返回一个错误之前,kube-apiserver 等待 kms-plugin 响应的时间(默认是 3 秒)。
参见理解静态数据加密配置
实现 KMS 插件
为实现一个 KMS 插件,你可以开发一个新的插件 gRPC 服务器或启用一个由你的云服务驱动提供的 KMS 插件。
你可以将这个插件与远程 KMS 集成,并把它部署到 Kubernetes 的主服务器上。
启用由云服务驱动支持的 KMS
有关启用云服务驱动特定的 KMS 插件的说明,请咨询你的云服务驱动商。
开发 KMS 插件 gRPC 服务器
你可以使用 Go 语言的存根文件开发 KMS 插件 gRPC 服务器。
对于其他语言,你可以用 proto 文件创建可以用于开发 gRPC 服务器代码的存根文件。
使用 Go 以外的其他语言:用 protoc 编译器编译 proto 文件:
service.proto
为指定语言生成存根文件。
然后使用存根文件中的函数和数据结构开发服务器代码。
注意:
将 KMS 插件与远程 KMS 整合
KMS 插件可以用任何受 KMS 支持的协议与远程 KMS 通信。
所有的配置数据,包括 KMS 插件用于与远程 KMS 通信的认证凭据,都由 KMS 插件独立地存储和管理。
KMS 插件可以用额外的元数据对密文进行编码,这些元数据是在把它发往 KMS 进行解密之前可能要用到的。
部署 KMS 插件
确保 KMS 插件与 Kubernetes 主服务器运行在同一主机上。
使用 KMS 驱动加密数据
为了加密数据:
使用 kms
驱动的相应的属性创建一个新的加密配置文件:
kind : EncryptionConfiguration
apiVersion : apiserver.config.k8s.io/v1
resources :
- resources :
- secrets
providers :
- kms :
name : myKmsPlugin
endpoint : unix:///tmp/socketfile.sock
cachesize : 100
timeout : 3s
- identity : {}
设置 kube-apiserver 的 --encryption-provider-config
参数指向配置文件的位置。
重启 API 服务器。
验证数据已经加密
写入 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 服务器的额外参数。
验证保存的 Secret 是否是以 k8s:enc:kms:v1:
开头的,这表明 kms
驱动已经对结果数据加密。
验证 Secret 在被 API 获取时已被正确解密:
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
驱动作为第一个条目,如下列样例所示
kind : EncryptionConfiguration
apiVersion : apiserver.config.k8s.io/v1
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
驱动作为配置文件中的第一个条目:
kind : EncryptionConfiguration
apiVersion : apiserver.config.k8s.io/v1
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.14 - 使用 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}" )
# 获得令牌
TOKEN = $( kubectl get secrets -o jsonpath = "{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='default')].data.token}" |base64 -d)
# 使用令牌玩转 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"
}
]
}
使用 jsonpath
方式:
APISERVER = $( kubectl config view --minify -o jsonpath = '{.clusters[0].cluster.server}' )
TOKEN = $( kubectl get secret $( kubectl get serviceaccount default -o jsonpath = '{.secrets[0].name}' ) -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 客户端使用根证书。
在一些集群中,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 () {
// uses the current context in kubeconfig
// path-to-kubeconfig -- for example, /root/.kube/config
config, _ := clientcmd.BuildConfigFromFlags ("" , "<path-to-kubeconfig>" )
// creates the clientset
clientset, _ := kubernetes.NewForConfig (config)
// access the API to list pods
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.15 - 保护集群安全
本文档涉及与保护集群免受意外或恶意访问有关的主题,并对总体安全性提出建议。
准备开始
控制对 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)在命名空间或者集群范围内结合起来,
根据客户可能希望执行的操作,提供了一组提供合理的违约责任分离的外包角色。
建议你将节点 和
RBAC 一起作为授权者,再与
NodeRestriction
准入插件结合使用。
与身份验证一样,简单而广泛的角色可能适合于较小的集群,但是随着更多的用户与集群交互,
可能需要将团队划分成有更多角色限制的单独的命名空间。
就鉴权而言,理解怎么样更新一个对象可能导致在其它地方的发生什么样的行为是非常重要的。
例如,用户可能不能直接创建 Pod,但允许他们通过创建一个 Deployment 来创建这些 Pod,
这将让他们间接创建这些 Pod。
同样地,从 API 删除一个节点将导致调度到这些节点上的 Pod 被中止,并在其他节点上重新创建。
原生的角色设计代表了灵活性和常见用例之间的平衡,但有限制的角色应该仔细审查,
以防止意外升级。如果外包角色不满足你的需求,则可以为用例指定特定的角色。
如果你希望获取更多信息,请参阅鉴权参考 。
控制对 Kubelet 的访问
Kubelet 公开 HTTPS 端点,这些端点授予节点和容器强大的控制权。
默认情况下,Kubelet 允许对此 API 进行未经身份验证的访问。
生产级别的集群应启用 Kubelet 身份验证和授权。
如果你希望获取更多信息,请参考
Kubelet 身份验证/授权参考 。
控制运行时负载或用户的能力
Kubernetes 中的授权故意设置为了高层级,它侧重于对资源的粗粒度行为。
更强大的控制是以通过用例限制这些对象如何作用于集群、自身和其他资源上的策略 存在的。
限制集群上的资源使用
资源配额
限制了授予命名空间的资源的数量或容量。
这通常用于限制命名空间可以分配的 CPU、内存或持久磁盘的数量,但也可以控制
每个命名空间中有多少个 Pod、服务或卷的存在。
限制范围 限制
上述某些资源的最大值或者最小值,以防止用户使用类似内存这样的通用保留资源时请求
不合理的过高或过低的值,或者在没有指定的情况下提供默认限制。
控制容器运行的特权
Pod 定义包含了一个安全上下文 ,
用于描述允许它请求访问某个节点上的特定 Linux 用户(如 root)、获得特权或访问主机网络、
以及允许它在主机节点上不受约束地运行的其它控件。
Pod 安全策略
可以限制哪些用户或服务帐户可以提供危险的安全上下文设置。
例如,Pod 的安全策略可以限制卷挂载,尤其是 hostpath
,这些都是 Pod 应该控制的一些方面。
一般来说,大多数应用程序需要限制对主机资源的访问,
他们可以在不能访问主机信息的情况下成功以根进程(UID 0)运行。
但是,考虑到与 root 用户相关的特权,在编写应用程序容器时,你应该使用非 root 用户运行。
类似地,希望阻止客户端应用程序逃避其容器的管理员,应该使用限制性的 pod 安全策略。
限制网络访问
基于命名空间的网络策略
允许应用程序作者限制其它命名空间中的哪些 Pod 可以访问它们命名空间内的 Pod 和端口。
现在已经有许多支持网络策略的
Kubernetes 网络供应商 。
对于可以控制用户的应用程序是否在集群之外可见的许多集群,配额和限制范围也可用于
控制用户是否可以请求节点端口或负载均衡服务。
在插件或者环境基础上控制网络规则可以增加额外的保护措施,比如节点防火墙、物理分离
群集节点以防止串扰、或者高级的网络策略。
云平台(AWS, Azure, GCE 等)经常讲 metadate 本地服务暴露给实例。
默认情况下,这些 API 可由运行在实例上的 Pod 访问,并且可以包含
该云节点的凭据或配置数据(如 kubelet 凭据)。
这些凭据可以用于在集群内升级或在同一账户下升级到其他云服务。
在云平台上运行 Kubernetes 时,限制对实例凭据的权限,使用
网络策略
限制对 metadata API 的 pod 访问,并避免使用配置数据来传递机密。
控制 Pod 可以访问哪些节点
默认情况下,对哪些节点可以运行 pod 没有任何限制。
Kubernetes 给最终用户提供了
一组丰富的策略用于控制 pod 放在节点上的位置 ,
以及基于污点的 Pod 放置和驱逐 。
对于许多集群,可以约定由作者采用或者强制通过工具使用这些策略来分离工作负载。
对于管理员,Beta 阶段的准入插件 PodNodeSelector
可用于强制命名空间中的 Pod
使用默认或需要使用特定的节点选择器。
如果最终用户无法改变命名空间,这可以强烈地限制所有的 pod 在特定工作负载的位置。
保护集群组件免受破坏
本节描述保护集群免受破坏的一些常见模式。
限制访问 etcd
对于 API 来说,拥有 etcd 后端的写访问权限,相当于获得了整个集群的 root 权限,
并且可以使用写访问权限来相当快速地升级。
从 API 服务器访问它们的 etcd 服务器,管理员应该使用广受信任的凭证,
如通过 TLS 客户端证书的相互认证。
通常,我们建议将 etcd 服务器隔离到只有API服务器可以访问的防火墙后面。
注意: 允许集群中其它组件拥有读或写全空间的权限去访问 etcd 实例,相当于授予群集管理员访问的权限。
对于非主控组件,强烈推荐使用单独的 etcd 实例,或者使用 etcd 的访问控制列表
去限制只能读或者写空间的一个子集。
开启审计日志
审计日志 是 Beta 特性,
负责记录 API 操作以便在发生破坏时进行事后分析。
建议启用审计日志,并将审计文件归档到安全服务器上。
限制使用 alpha 和 beta 特性
Kubernetes 的 alpha 和 beta 特性还在努力开发中,可能存在导致安全漏洞的缺陷或错误。
要始终评估 alpha 和 beta 特性可能为你的安全态势带来的风险。
当你怀疑存在风险时,可以禁用那些不需要使用的特性。
频繁回收基础设施证书
一个 Secret 或凭据的寿命越短,攻击者就越难使用该凭据。
在证书上设置短生命周期并实现自动回收,是控制安全的一个好方法。
因此,使用身份验证提供程序时,应该要求可以控制发布令牌的可用时间,并尽可能使用短寿命。
如果在外部集成中使用服务帐户令牌,则应该频繁地回收这些令牌。
例如,一旦引导阶段完成,就应该撤销用于设置节点的引导令牌,或者取消它的授权。
在启用第三方集成之前,请先审查它们
许多集成到 Kubernetes 的第三方都可以改变你集群的安全配置。
启用集成时,在授予访问权限之前,你应该始终检查扩展所请求的权限。
例如,许多安全集成可以请求访问来查看集群上的所有 Secret,
从而有效地使该组件成为集群管理。
当有疑问时,如果可能的话,将集成限制在单个命名空间中运行。
如果组件创建的 Pod 能够在命名空间中做一些类似 kube-system
命名空间中的事情,
那么它也可能是出乎意料的强大。
因为这些 Pod 可以访问服务账户的 Secret,或者,如果这些服务帐户被授予访问许可的
Pod 安全策略 的权限,它们能以高权限运行。
对 Secret 进行静态加密
一般情况下,etcd 数据库包含了通过 Kubernetes API 可以访问到的所有信息,
并且可以授予攻击者对集群状态的可见性。
始终使用经过良好审查的备份和加密解决方案来加密备份,并考虑在可能的情况下使用全磁盘加密。
Kubernetes 支持 静态数据加密 ,
该功能在 1.7 版本引入,并在 1.13 版本成为 Beta。它会加密 etcd 里面的 Secret
资源,以防止某一方通过查看
etcd 的备份文件查看到这些 Secret 的内容。虽然目前这还只是 Beta 阶段的功能,
但是在备份没有加密或者攻击者获取到 etcd 的读访问权限的时候,它能提供额外的防御层级。
接收安全更新和报告漏洞的警报
加入 kubernetes-announce
组,能够获取有关安全公告的邮件。有关如何报告漏洞的更多信息,请参见
安全报告 页面。
4.2.16 - 关键插件 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.17 - 升级集群
本页概述升级 Kubernetes 集群的步骤。
升级集群的方式取决于你最初部署它的方式、以及后续更改它的方式。
从高层规划的角度看,要执行的步骤是:
升级控制平面
升级集群中的节点
升级 kubectl 之类的客户端
根据新 Kubernetes 版本带来的 API 变化,调整清单文件和其他资源
准备开始
你必须有一个集群。
本页内容涉及从 Kubernetes 1.22
升级到 Kubernetes 1.23。
如果你的集群未运行 Kubernetes 1.22,
那请参考目标 Kubernetes 版本的文档。
升级方法
kubeadm
如果你的集群是使用 kubeadm
安装工具部署而来,
那么升级群集的详细信息,请参阅
升级 kubeadm 集群 。
升级集群之后,要记得
安装最新版本的 kubectl
.
手动部署
注意: 这些步骤不考虑第三方扩展,例如网络和存储插件。
你应该跟随下面操作顺序,手动更新控制平面:
etcd (所有实例)
kube-apiserver (所有控制平面的宿主机)
kube-controller-manager
kube-scheduler
cloud controller manager, 在你用到时
现在,你应该
安装最新版本的 kubectl
.
对于群集中的每个节点,
排空
节点,然后,或者用一个运行了 1.23 kubelet 的新节点替换它;
或者升级此节点的 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.18 - 名字空间演练
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.json
描述了 development
名字空间:
{
"apiVersion" : "v1" ,
"kind" : "Namespace" ,
"metadata" : {
"name" : "development" ,
"labels" : {
"name" : "development"
}
}
}
使用 kubectl 创建 development
名字空间。
kubectl create -f https://k8s.io/examples/admin/namespace-dev.json
将下列的内容保存到文件 namespace-prod.json
中,
这些内容是对 production
名字空间的描述:
{
"apiVersion" : "v1" ,
"kind" : "Namespace" ,
"metadata" : {
"name" : "production" ,
"labels" : {
"name" : "production"
}
}
}
让我们使用 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。
我们首先检查一下当前的上下文:
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 : k8s.gcr.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= k8s.gcr.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.19 - 启用/禁用 Kubernetes API
本页展示怎么用集群的
控制平面 .
启用/禁用 API 版本。
通过 API 服务器的命令行参数 --runtime-config=api/<version>
,
可以开启/关闭某个指定的 API 版本。
此参数的值是一个逗号分隔的 API 版本列表。
此列表中,后面的值可以覆盖前面的值。
命令行参数 runtime-config
支持两个特殊的值(keys):
api/all
:指所有已知的 API
api/legacy
:指过时的 API。过时的 API 就是明确地
弃用
的 API。
例如:为了停用除去 v1 版本之外的全部其他 API 版本,
就用参数 --runtime-config=api/all=false,api/v1=true
启动 kube-apiserver
。
接下来
阅读完整的文档 ,
以了解 kube-apiserver
组件。
4.2.20 - 启用拓扑感知提示
FEATURE STATE: Kubernetes v1.21 [alpha]
拓扑感知提示 启用具有拓扑感知能力的路由,其中拓扑感知信息包含在
EndpointSlices 中。
此功能尽量将流量限制在它的发起区域附近;
可以降低成本,或者提高网络性能。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 1.21.
要获知版本信息,请输入
kubectl version
.
为了启用拓扑感知提示,先要满足以下先决条件:
配置 kube-proxy
以 iptables 或 IPVS 模式运行
确保未禁用 EndpointSlices
启动拓扑感知提示
要启用服务拓扑感知,请启用 kube-apiserver、kube-controller-manager、和 kube-proxy 的
特性门控
TopologyAwareHints
。
--feature-gates="TopologyAwareHints=true"
接下来
4.2.21 - 在 Kubernetes 集群中使用 NodeLocal DNSCache
FEATURE STATE: 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 端点。
借助这种新架构,Pods 将可以访问在同一节点上运行的 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 秒超时)。
可以重新启用负缓存,从而减少对 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 Pods 会设置 __PILLAR__CLUSTER__DNS__
和 __PILLAR__UPSTREAM__SERVERS__
。
在此模式下, node-local-dns Pods 会同时侦听 kube-dns 服务的 IP 地址和 <node-local-address>
的地址,
以便 Pods 可以使用其中任何一个 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 Pods 只会侦听 <node-local-address>
的地址。
node-local-dns 接口不能绑定 kube-dns 的集群 IP 地址,因为 IPVS 负载均衡
使用的接口已经占用了该地址。
node-local-dns Pods 会设置 __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 Pods 将在每个集群节点上的 kube-system 名字空间中运行。
此 Pod 在缓存模式下运行 CoreDNS ,因此每个节点都可以使用不同插件公开的所有 CoreDNS 指标。
如果要禁用该功能,你可以使用 kubectl delete -f <manifest>
来删除 DaemonSet。你还应该恢复你对 kubelet 配置所做的所有改动。
4.2.22 - 在 Kubernetes 集群中使用 sysctl
FEATURE STATE: Kubernetes v1.21 [stable]
本文档介绍如何通过 sysctl
接口在 Kubernetes 集群中配置和使用内核参数。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
对一些步骤,你需要能够重新配置在你的集群里运行的 kubelet 命令行的选项。
获取 Sysctl 的参数列表
在 Linux 中,管理员可以通过 sysctl 接口修改内核运行时的参数。在 /proc/sys/
虚拟文件系统下存放许多内核参数。这些参数涉及了多个内核子系统,如:
内核子系统(通常前缀为: kernel.
)
网络子系统(通常前缀为: net.
)
虚拟内存子系统(通常前缀为: vm.
)
MDADM 子系统(通常前缀为: dev.
)
更多子系统请参见内核文档 。
若要获取完整的参数列表,请执行以下命令
启用非安全的 Sysctl 参数
sysctl 参数分为 安全 和 非安全的 。
安全 sysctl 参数除了需要设置恰当的命名空间外,在同一 node 上的不同 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.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 调度到正确的节点之上。
PodSecurityPolicy
FEATURE STATE: Kubernetes v1.21 [deprecated]
你可以通过在 PodSecurityPolicy 的 forbiddenSysctls
和/或 allowedUnsafeSysctls
字段中,指定 sysctl 或填写 sysctl 匹配模式来进一步为 Pod 设置 sysctl 参数。
sysctl 参数匹配模式以 *
字符结尾,如 kernel.*
。
单独的 *
字符匹配所有 sysctl 参数。
所有 安全的 sysctl 参数都默认启用。
forbiddenSysctls
和 allowedUnsafeSysctls
的值都是字符串列表类型,
可以添加 sysctl 参数名称,也可以添加 sysctl 参数匹配模式(以*
结尾)。
只填写 *
则匹配所有的 sysctl 参数。
forbiddenSysctls
字段用于禁用特定的 sysctl 参数。
你可以在列表中禁用安全和非安全的 sysctl 参数的组合。
要禁用所有的 sysctl 参数,请设置为 *
。
如果要在 allowedUnsafeSysctls
字段中指定一个非安全的 sysctl 参数,
并且它在 forbiddenSysctls
字段中未被禁用,则可以在 Pod 中通过
PodSecurityPolicy 启用该 sysctl 参数。
若要在 PodSecurityPolicy 中开启所有非安全的 sysctl 参数,
请设 allowedUnsafeSysctls
字段值为 *
。
allowedUnsafeSysctls
与 forbiddenSysctls
两字段的配置不能重叠,
否则这就意味着存在某个 sysctl 参数既被启用又被禁用。
警告: 如果你通过 PodSecurityPolicy 中的 allowedUnsafeSysctls
字段将非安全的 sysctl
参数列入白名单,但该 sysctl 参数未通过 kubelet 命令行参数
--allowed-unsafe-sysctls
在节点上将其列入白名单,则设置了这个 sysctl
参数的 Pod 将会启动失败。
以下示例设置启用了以 kernel.msg
为前缀的非安全的 sysctl 参数,同时禁用了
sysctl 参数 kernel.shm_rmid_forced
。
apiVersion : policy/v1beta1
kind : PodSecurityPolicy
metadata :
name : sysctl-psp
spec :
allowedUnsafeSysctls :
- kernel.msg*
forbiddenSysctls :
- kernel.shm_rmid_forced
...
4.2.23 - 在运行中的集群上重新配置节点的 kubelet
FEATURE STATE: Kubernetes v1.22 [deprecated]
动态 kubelet 配置
允许你通过部署一个所有节点都会使用的 ConfigMap
达到在运行中的 Kubernetes 集群中更改 kubelet 配置的目的。
警告: 所有 kubelet 配置参数都可以被动态更改,但对某些参数来说这类更改是不安全的。
在决定动态更改参数之前,你需要深刻理解这个改动将会如何影响集群的行为。
在将变更扩散到整个集群之前,你需要先在小规模的节点集合上仔细地测试这些配置变动。
特定字段相关的配置建议可以在文档
KubeletConfiguration
中找到。
准备开始
你需要一个 Kubernetes 集群。
你需要 v1.11 或更高版本的 kubectl,并配置好与集群的通信。
要获知版本信息,请输入 kubectl version
.
你的集群 API 服务器版本(如 v1.12)不能和你的 kubectl
版本相差超过一个小版本号。
例如,如果你的集群在运行 v1.16,那么你可以使用 v1.15、v1.16、v1.17 的 kubectl,
所有其他的组合都是
不支持的 。
在某些例子中使用了命令行工具 jq 。
你并不一定需要 jq
才能完成这些任务,因为总是有一些手工替代的方式。
针对你重新配置的每个节点,你必须设置 kubelet 的标志
-dynamic-config-dir
,使之指向一个可写的目录。
重配置 集群中运行节点上的 kubelet
基本工作流程概览
在运行中的集群中配置 kubelet 的基本工作流程如下:
编写一个包含 kubelet 配置的 YAML 或 JSON 文件。
将此文件包装在 ConfigMap 中并将其保存到 Kubernetes 控制平面。
更新 kubelet 所在节点对象以使用此 ConfigMap。
每个 kubelet 都会在其各自的节点对象上监测(Watch)配置引用。当引用更改时,kubelet 将下载新的配置文件,
更新本地引用指向该文件,然后退出。
为了使该功能正常地工作,你必须运行操作系统级别的服务管理器(如 systemd),
它将会在 kubelet 退出后将其重启。
kubelet 重新启动时,将开始使用新配置。
新配置将会完全地覆盖 --config
所提供的配置,并被命令行标志覆盖。
新配置中未指定的值将收到适合配置版本的默认值
(e.g. kubelet.config.k8s.io/v1beta1
),除非被命令行标志覆盖。
节点 kubelet 配置状态可通过 node.spec.status.config
获取。
一旦你更新了一个节点去使用新的 ConfigMap,
就可以通过观察此状态来确认该节点是否正在使用预期配置。
本文中使用命令 kubectl edit
来编辑节点,还有其他的方式可以修改节点的规约,
比如更利于脚本化工作流程的 kubectl patch
。
本文仅仅讲述在单节点上使用每个 ConfigMap。请注意对于多个节点使用相同的 ConfigMap
也是合法的。
警告: 尽管通过就地更新 ConfigMap 来更改配置是 可能的 。
但是这样做会导致所有使用该 ConfigMap 配置的 kubelet 同时更新。
更安全的做法是按惯例将 ConfigMap 视为不可变更的,借助于
kubectl
的 --append-hash
选项逐步把更新推广到 node.spec.configSource
。
节点鉴权器的自动 RBAC 规则
以前,你需要手动创建 RBAC 规则以允许节点访问其分配的 ConfigMap。节点鉴权器现在
能够自动配置这些规则。
生成包含当前配置的文件
动态 kubelet 配置特性允许你为整个配置对象提供一个重载配置,而不是靠单个字段的叠加。
这是一个更简单的模型,可以更轻松地跟踪配置值的来源,更便于调试问题。
然而,相应的代价是你必须首先了解现有配置,以确保你只更改你打算修改的字段。
组件 kubelet 从其配置文件中加载配置数据,不过你可以通过设置命令行标志
来重载文件中的一些配置。这意味着,如果你仅知道配置文件的内容,而你不知道
命令行重载了哪些配置,你就无法知道 kubelet 的运行时配置是什么。
因为你需要知道运行时所使用的配置才能重载之,你可以从 kubelet 取回其运行时配置。
你可以通过访问 kubelet 的 configz
末端来生成包含节点当前配置的配置文件;
这一操作可以通过 kubectl proxy
来完成。
下一节解释如何完成这一操作。
注意: 组件 kubelet
上的 configz
末端是用来协助调试的,并非 kubelet 稳定行为的一部分。
请不要在产品环境下依赖此末端的行为,也不要在自动化工具中使用此末端。
关于如何使用配置文件来配置 kubelet 行为的更多信息可参见
通过配置文件设置 kubelet 参数
文档。
生成配置文件
说明: 下面的任务步骤中使用了 jq
命令以方便处理 JSON 数据。为了完成这里讲述的任务,
你需要安装 jq
。如果你更希望手动提取 kubeletconfig
子对象,也可以对这里
的对应步骤做一些调整。
选择要重新配置的节点。在本例中,此节点的名称为 NODE_NAME
。
使用以下命令在后台启动 kubectl 代理:
kubectl proxy --port= 8001 &
运行以下命令从 configz
端点中下载并解压配置。这个命令很长,因此在复制粘贴时要小心。
如果你使用 zsh ,请注意常见的 zsh 配置要添加反斜杠转义 URL 中变量名称周围的大括号。
例如:在粘贴时,${NODE_NAME}
将被重写为 $\{NODE_NAME\}
。
你必须在运行命令之前删除反斜杠,否则命令将失败。
NODE_NAME = "the-name-of-the-node-you-are-reconfiguring" ; curl -sSL "http://localhost:8001/api/v1/nodes/ ${ NODE_NAME } /proxy/configz" | jq '.kubeletconfig|.kind="KubeletConfiguration"|.apiVersion="kubelet.config.k8s.io/v1beta1"' > kubelet_configz_${ NODE_NAME }
说明: 你需要手动将 kind
和 apiVersion
添加到下载对象中,因为它们不是由 configz
末端
返回的。
修改配置文件
使用文本编辑器,改变上述操作生成的文件中一个参数。
例如,你或许会修改 QPS 参数 eventRecordQPS
。
把配置文件推送到控制平面
用以下命令把编辑后的配置文件推送到控制平面:
kubectl -n kube-system create configmap my-node-config \
--from-file= kubelet = kubelet_configz_${ NODE_NAME } \
--append-hash -o yaml
下面是合法响应的一个例子:
apiVersion : v1
kind : ConfigMap
metadata :
creationTimestamp : 2017-09-14T20:23:33Z
name : my-node-config-gkt4c2m4b2
namespace : kube-system
resourceVersion : "119980"
selfLink : /api/v1/namespaces/kube-system/configmaps/my-node-config-gkt4c2m4b2
uid : 946d785e-998a-11e7-a8dd-42010a800006
data :
kubelet : |
{...}
你会在 kube-system
命名空间中创建 ConfigMap,因为 kubelet 是 Kubernetes 的系统组件。
--append-hash
选项给 ConfigMap 内容附加了一个简短校验和。
这对于先编辑后推送的工作流程很方便,
因为它自动并确定地为新 ConfigMap 生成新的名称。
在以下示例中,包含生成的哈希字符串的对象名被称为 CONFIG_MAP_NAME
。
配置节点使用新的配置
kubectl edit node ${ NODE_NAME }
在你的文本编辑器中,在 spec
下增添以下 YAML:
configSource :
configMap :
name : CONFIG_MAP_NAME
namespace : kube-system
kubeletConfigKey : kubelet
你必须同时指定 name
、namespace
和 kubeletConfigKey
这三个属性。
kubeletConfigKey
这个参数通知 kubelet ConfigMap 中的哪个键下面包含所要的配置。
观察节点开始使用新配置
用 kubectl get node ${NODE_NAME} -o yaml
命令读取节点并检查 node.status.config
内容。
状态部分报告了对应 active
(使用中的)配置、assigned
(被赋予的)配置和
lastKnownGood
(最近已知可用的)配置的配置源。
active
是 kubelet 当前运行时所使用的版本。
assigned
参数是 kubelet 基于 node.spec.configSource
所解析出来的最新版本。
lastKnownGood
参数是 kubelet 的回退版本;如果在 node.spec.configSource
中
包含了无效的配置值,kubelet 可以回退到这个版本。
如果用本地配置部署节点,使其设置成默认值,这个 lastKnownGood
配置可能不存在。
在 kubelet 配置好后,将更新 lastKnownGood
为一个有效的 assigned
配置。
决定如何确定某配置成为 lastKnownGood
配置的细节并不在 API 保障范畴,
不过目前实现中采用了 10 分钟的宽限期。
你可以使用以下命令(使用 jq
)过滤出配置状态:
kubectl get no ${ NODE_NAME } -o json | jq '.status.config'
以下是一个响应示例:
{
"active" : {
"configMap" : {
"kubeletConfigKey" : "kubelet" ,
"name" : "my-node-config-9mbkccg2cc" ,
"namespace" : "kube-system" ,
"resourceVersion" : "1326" ,
"uid" : "705ab4f5-6393-11e8-b7cc-42010a800002"
}
},
"assigned" : {
"configMap" : {
"kubeletConfigKey" : "kubelet" ,
"name" : "my-node-config-9mbkccg2cc" ,
"namespace" : "kube-system" ,
"resourceVersion" : "1326" ,
"uid" : "705ab4f5-6393-11e8-b7cc-42010a800002"
}
},
"lastKnownGood" : {
"configMap" : {
"kubeletConfigKey" : "kubelet" ,
"name" : "my-node-config-9mbkccg2cc" ,
"namespace" : "kube-system" ,
"resourceVersion" : "1326" ,
"uid" : "705ab4f5-6393-11e8-b7cc-42010a800002"
}
}
}
如果你没有安装 jq
,你可以查看整个响应对象,查找其中的 node.status.config
部分。
如果发生错误,kubelet 会在 Node.Status.Config.Error
中显示出错误信息的结构体。
错误可能出现在列表理解节点状态配置错误信息 中。
你可以在 kubelet 日志中搜索相同的文本以获取更多详细信息和有关错误的上下文。
做出更多的改变
按照下面的工作流程做出更多的改变并再次推送它们。
你每次推送一个 ConfigMap 的新内容时,kubeclt 的 --append-hash
选项都会给
ConfigMap 创建一个新的名称。
最安全的上线策略是首先创建一个新的 ConfigMap,然后更新节点以使用新的 ConfigMap。
重置节点以使用其本地默认配置
要重置节点,使其使用节点创建时使用的配置,可以用
kubectl edit node $ {NODE_NAME}
命令编辑节点,并删除 node.spec.configSource
字段。
观察节点正在使用本地默认配置
在删除此字段后,node.status.config
最终变成空,所有配置源都已重置为 nil
。
这表示本地默认配置成为了 assigned
、active
和 lastKnownGood
配置,
并且没有报告错误。
kubectl patch
示例
你可以使用几种不同的机制来更改节点的 configSource。
本例使用kubectl patch
:
kubectl patch node ${ NODE_NAME } -p "{\"spec\":{\"configSource\":{\"configMap\":{\"name\":\" ${ CONFIG_MAP_NAME } \",\"namespace\":\"kube-system\",\"kubeletConfigKey\":\"kubelet\"}}}}"
了解 Kubelet 如何为配置生成检查点
当为节点赋予新配置时,kubelet 会下载并解压配置负载为本地磁盘上的一组文件。
kubelet 还记录一些元数据,用以在本地跟踪已赋予的和最近已知良好的配置源,以便
kubelet 在重新启动时知道使用哪个配置,即使 API 服务器变为不可用。
在为配置信息和相关元数据生成检查点之后,如果检测到已赋予的配置发生改变,则 kubelet 退出。
当 kubelet 被 OS 级服务管理器(例如 systemd
)重新启动时,它会读取新的元数据并使用新配置。
当记录的元数据已被完全解析时,意味着它包含选择一个指定的配置版本所需的所有信息
-- 通常是 UID
和 ResourceVersion
。
这与 node.spec.configSource
形成对比,后者通过幂等的 namespace/name
声明来标识
目标 ConfigMap;kubelet 尝试使用此 ConfigMap 的最新版本。
当你在调试节点上问题时,可以检查 kubelet 的配置元数据和检查点。kubelet 的检查点目录结构是:
- --dynamic-config-dir (用于管理动态配置的根目录)
|-- meta
| - assigned (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应赋予的配置)
| - last-known-good (编码后的 kubeletconfig/v1beta1.SerializedNodeConfigSource 对象,对应最近已知可用配置)
| - checkpoints
| - uid1 (用 uid1 来标识的对象版本目录)
| - resourceVersion1 (uid1 对象 resourceVersion1 版本下所有解压文件的目录)
| - ...
| - ...
理解 Node.Status.Config.Error
消息
下表描述了使用动态 kubelet 配置时可能发生的错误消息。
你可以在 kubelet 日志中搜索相同的文本来获取有关错误的其他详细信息和上下文。
理解 node.status.config.error 消息
错误信息
可能的原因
failed to load config, see Kubelet log for details
kubelet 可能无法解析下载配置的有效负载,或者当尝试从磁盘中加载有效负载时,遇到文件系统错误。
failed to validate config, see Kubelet log for details
有效负载中的配置,与命令行标志所产生的覆盖配置以及特行门控的组合、配置文件本身、远程负载被 kubelet 判定为无效。
invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil
由于 API 服务器负责对 node.spec.configSource 执行验证,检查其中是否包含至少一个非空子字段,这个消息可能意味着 kubelet 比 API 服务器版本低,因而无法识别更新的源类型。
failed to sync: failed to download config, see Kubelet log for details
kubelet 无法下载配置数据。可能是 node.spec.configSource 无法解析为具体的 API 对象,或者网络错误破坏了下载。处于此错误状态时,kubelet 将重新尝试下载。
failed to sync: internal failure, see Kubelet log for details
kubelet 遇到了一些内部问题,因此无法更新其配置。 例如:发生文件系统错误或无法从内部缓存中读取对象。
internal failure, see Kubelet log for details
在对配置进行同步的循环之外操作配置时,kubelet 遇到了一些内部问题。
接下来
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 命名空间中的其它s Pod 来访问该服务。可以启动一个 busybox 容器:
kubectl run busybox --rm -ti --image= busybox /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 -- /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 -- /bin/sh
在 Shell 中运行命令:
wget --spider --timeout= 1 nginx
Connecting to nginx (10.100.0.16:80)
remote file exists
4.2.25 - 安全地清空一个节点
本页展示了如何在确保 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 通过编程的方式达到驱逐的效果。
首先应该熟悉使用
Kubernetes 语言客户端 。
Pod 的 Eviction 子资源可以看作是一种策略控制的 DELETE 操作,作用于 Pod 本身。
要尝试驱逐(更准确地说,尝试 创建 一个 Eviction),需要用 POST 发出所尝试的操作。这里有一个例子:
说明: policy/v1
驱逐在 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
尝试驱逐:
curl -v -H 'Content-type: application/json' http://127.0.0.1:8080/api/v1/namespaces/default/pods/quux/eviction -d @eviction.json
API 可以通过以下三种方式之一进行响应:
如果驱逐被授权,那么 Pod 将被删掉,并且你会收到 200 OK
,
就像你向 Pod 的 URL 发送了 DELETE
请求一样。
如果按照预算中规定,目前的情况不允许的驱逐,你会收到 429 Too Many Requests
。
这通常用于对 一些 请求进行通用速率限制,
但这里我们的意思是:此请求 现在 不允许,但以后可能会允许。
目前,调用者不会得到任何 Retry-After
的提示,但在将来的版本中可能会得到。
如果有一些错误的配置,比如多个预算指向同一个 Pod,你将得到 500 Internal Server Error
。
对于一个给定的驱逐请求,有两种情况:
没有匹配这个 Pod 的预算。这种情况,服务器总是返回 200 OK
。
至少匹配一个预算。在这种情况下,上述三种回答中的任何一种都可能适用。
驱逐阻塞
在某些情况下,应用程序可能会到达一个中断状态,除了 429 或 500 之外,它将永远不会返回任何内容。
例如 ReplicaSet 创建的替换 Pod 没有变成就绪状态,或者被驱逐的最后一个
Pod 有很长的终止宽限期,就会发生这种情况。
在这种情况下,有两种可能的解决方案:
中止或暂停自动操作。调查应用程序卡住的原因,并重新启动自动化。
经过适当的长时间等待后,从集群中删除 Pod 而不是使用驱逐 API。
Kubernetes 并没有具体说明在这种情况下应该采取什么行为,
这应该由应用程序所有者和集群所有者紧密沟通,并达成对行动一致意见。
接下来
4.2.26 - 将重复的控制平面迁至云控制器管理器
FEATURE STATE: Kubernetes v1.22 [beta]
云管理控制器是 云控制器管理器是指嵌入特定云的控制逻辑的
控制平面 组件。
云控制器管理器使得你可以将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
云控制器管理器组件使云提供商能够以不同于 Kubernetes 主项目的
步调发布新特征。
背景
作为云驱动提取工作
的一部分,所有特定于云的控制器都必须移出 kube-controller-manager
。
所有在 kube-controller-manager
中运行云控制器的现有集群必须迁移到云驱动特定的 cloud-controller-manager
中运行控制器。
领导者迁移提供了一种机制,使得 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.22 配合使用的 cloud-controller-manager
,
则 N 可以为 1.21,N+1 可以为 1.22。
控制平面节点应运行 kube-controller-manager
,并通过 --leader-elect=true
启用领导者选举。
从版本 N 开始,树内云驱动必须设置 --cloud-provider
标志,而且 cloud-controller-manager
尚未部署。
树外云驱动必须已经构建了一个实现领导者迁移的 cloud-controller-manager
。
如果云驱动导入了 v0.21.0 或更高版本的 k8s.io/cloud-provider
和 k8s.io/controller-manager
,
则可以进行领导者迁移。
但是,对 v0.22.0 以下的版本,领导者迁移是一项 Alpha 阶段功能,它需要启用特性门控 ControllerManagerLeaderMigration
。
本指南假定每个控制平面节点的 kubelet 以静态 pod 的形式启动 kube-controller-manager
和 cloud-controller-manager
,静态 pod 的定义在清单文件中。
如果组件以其他设置运行,请相应地调整步骤。
为了获得授权,本指南假定集群使用 RBAC。
如果其他授权模式授予 kube-controller-manager
和 cloud-controller-manager
组件权限,
请以与该模式匹配的方式授予所需的访问权限。
授予访问迁移 Lease 的权限
控制器管理器的默认权限仅允许访问其主 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/v1beta1
leaderName : cloud-provider-extraction-migration
resourceLock : leases
controllerLeaders :
- name : route
component : kube-controller-manager
- name : service
component : kube-controller-manager
- name : cloud-node-lifecycle
component : kube-controller-manager
在每个控制平面节点上,将内容保存到 /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/v1beta1
leaderName : cloud-provider-extraction-migration
resourceLock : leases
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
相同的方式挂载配置文件。
类似地,添加 --feature-gates=ControllerManagerLeaderMigration=true
、--enable-leader-migration
和 --leader-migration-config=/etc/leadermigration.conf
到 cloud-controller-manager
的参数中。
使用已更新的 cloud-controller-manager
清单创建一个新的 N+1 版本的控制平面节点。
并且没有设置 kube-controller-manager
的 --cloud-provider
标志。
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
下运行,
具体取决于哪个控制器管理器拥有迁移 Lease 对象。任何时候都不存在一个控制器在两个控制器管理器下运行。
以滚动的方式创建一个新的版本为 N+1 的控制平面节点,并将 N+1 版本中的一个关闭,
直到控制平面仅包含版本为 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
,如果没有用参数来启用树内云驱动或者改变控制器属主,
则可以使用默认配置来避免手动创建配置文件。
接下来
4.2.27 - 开发云控制器管理器
FEATURE STATE: Kubernetes v1.11 [beta]
组件 cloud-controller-manager 是 云控制器管理器是指嵌入特定云的控制逻辑的
控制平面 组件。
云控制器管理器使得你可以将你的集群连接到云提供商的 API 之上,
并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
云控制器管理器组件使云提供商能够以不同于 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 驱动,你可以将 in-tree 云控制器管理器作为群集中的
Daemonset 来运行。
有关详细信息,请参阅云控制器管理器管理 。
4.2.28 - 开启服务拓扑
FEATURE STATE: 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 模式运行
启用服务拓扑
FEATURE STATE: Kubernetes v1.21 [deprecated]
要启用服务拓扑功能,需要为所有 Kubernetes 组件启用 ServiceTopology
特性门控 :
--feature-gates="ServiceTopology=true`
接下来
4.2.29 - 控制节点上的 CPU 管理策略
FEATURE STATE: 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
来指定。支持两种策略:
none
: 默认策略,表示现有的调度行为。
static
: 允许为节点上具有某些资源特征的 pod 赋予增强的 CPU 亲和性和独占性。
CPU 管理器定期通过 CRI 写入资源更新,以保证内存中 CPU 分配与 cgroupfs 一致。
同步频率通过新增的 Kubelet 配置参数 --cpu-manager-reconcile-period
来设置。
如果不指定,默认与 --node-status-update-frequency
的周期相同。
Static 策略的行为可以使用 --cpu-manager-policy-options
参数来微调。
该参数采用一个逗号分隔的 key=value
策略选项列表。
none 策略
none
策略显式地启用现有的默认 CPU 亲和方案,不提供操作系统调度器默认行为之外的亲和性策略。
通过 CFS 配额来实现 Guaranteed pods
和 Burstable pods
的 CPU 使用限制。
static 策略
static
策略针对具有整数型 CPU requests
的 Guaranteed
Pod ,它允许该类 Pod
中的容器访问节点上的独占 CPU 资源。这种独占性是使用
cpuset cgroup 控制器 来实现的。
说明: 诸如容器运行时和 kubelet 本身的系统服务可以继续在这些独占 CPU 上运行。独占性仅针对其他 Pod。
说明: CPU 管理器不支持运行时下线和上线 CPUs。此外,如果节点上的在线 CPUs 集合发生变化,
则必须驱逐节点上的 Pod,并通过删除 kubelet 根目录中的状态文件 cpu_manager_state
来手动重置 CPU 管理器。
该策略管理一个共享 CPU 资源池,最初,该资源池包含节点上所有的 CPU 资源。可用
的独占性 CPU 资源数量等于节点的 CPU 总量减去通过 --kube-reserved
或 --system-reserved
参数保留的 CPU 。从1.17版本开始,CPU保留列表可以通过 kublet 的 '--reserved-cpus' 参数显式地设置。
通过 '--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 策略选项
如果使用 full-pcpus-only
策略选项,static 策略总是会分配完整的物理核心。
你可以通过在 CPUManager 策略选项里加上 full-pcups-only=true
来启用该选项。
默认情况下,如果不使用该选项,static 策略会使用拓扑感知最适合的分配方法来分配 CPU。
在启用了 SMT 的系统上,此策略所分配是与硬件线程对应的、独立的虚拟核。
这会导致不同的容器共享相同的物理核心,该行为进而会导致
吵闹的邻居问题 。
启用该选项之后,只有当一个 Pod 里所有容器的 CPU 请求都能够分配到完整的物理核心时,kubelet 才会接受该 Pod。
如果 Pod 没有被准入,它会被置于 Failed 状态,错误消息是 SMTAlignmentError
。
4.2.30 - 控制节点上的拓扑管理策略
FEATURE STATE: 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 Providers) 的接口,以发送和接收拓扑信息。
拓扑管理器具有一组节点级策略,具体说明如下。
拓扑管理器从 建议提供者 接收拓扑信息,作为表示可用的 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 规约中的 memory(和 hugepages)资源与所请求的其他资源对齐,需要启用内存管理器,
并且在节点配置适当的内存管理器策略。查看
内存管理器
文档。
拓扑管理器作用域
拓扑管理器可以在以下不同的作用域内进行资源对齐:
在 kubelet 启动时,可以使用 --topology-manager-scope
标志来选择其中任一选项。
容器作用域
默认使用的是 container
作用域。
在该作用域内,拓扑管理器依次进行一系列的资源对齐,
也就是,对每一个容器(包含在一个 Pod 里)计算单独的对齐。
换句话说,在该特定的作用域内,没有根据特定的 NUMA 节点集来把容器分组的概念。
实际上,拓扑管理器会把单个容器任意地对齐到 NUMA 节点上。
容器分组的概念是在以下的作用域内特别实现的,也就是 pod
作用域。
Pod 作用域
使用命令行选项 --topology-manager-scope=pod
来启动 kubelet,就可以选择 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 策略
对于 Guaranteed 类的 Pod 中的每个容器,具有 best-effort
拓扑管理策略的
kubelet 将调用每个建议提供者以确定资源可用性。
使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。
如果亲和性不是首选,则拓扑管理器将存储该亲和性,并且无论如何都将 pod 接纳到该节点。
之后 建议提供者 可以在进行资源分配决策时使用这个信息。
restricted 策略
对于 Guaranteed 类 Pod 中的每个容器, 配置了 restricted
拓扑管理策略的 kubelet
调用每个建议提供者以确定其资源可用性。。
使用此信息,拓扑管理器存储该容器的首选 NUMA 节点亲和性。
如果亲和性不是首选,则拓扑管理器将从节点中拒绝此 Pod 。
这将导致 Pod 处于 Terminated
状态,且 Pod 无法被节点接纳。
一旦 Pod 处于 Terminated
状态,Kubernetes 调度器将不会尝试重新调度该 Pod。
建议使用 ReplicaSet 或者 Deployment 来重新部署 Pod。
还可以通过实现外部控制环,以启动对具有 Topology Affinity
错误的 Pod 的重新部署。
如果 Pod 被允许运行在某节点,则 建议提供者 可以在做出资源分配决定时使用此信息。
single-numa-node 策略
对于 Guaranteed 类 Pod 中的每个容器, 配置了 single-numa-nodde
拓扑管理策略的
kubelet 调用每个建议提供者以确定其资源可用性。
使用此信息,拓扑管理器确定单 NUMA 节点亲和性是否可能。
如果是这样,则拓扑管理器将存储此信息,然后 建议提供者 可以在做出资源分配决定时使用此信息。
如果不可能,则拓扑管理器将拒绝 Pod 运行于该节点。
这将导致 Pod 处于 Terminated
状态,且 Pod 无法被节点接受。
一旦 Pod 处于 Terminated
状态,Kubernetes 调度器将不会尝试重新调度该 Pod。
建议使用 ReplicaSet 或者 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 以 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 的拓扑提示。
对于 Guaranteed
类的 CPU 请求数为整数的 Pod,static
CPU 管理器策略将返回与 CPU 请求有关的提示,
而设备管理器将返回有关所请求设备的提示。
对于 Guaranteed
类的 CPU 请求可共享的 Pod,static
CPU
管理器策略将返回默认的拓扑提示,因为没有排他性的 CPU 请求;而设备管理器
则针对所请求的设备返回有关提示。
在上述两种 Guaranteed
Pod 的情况中,none
CPU 管理器策略会返回默认的拓扑提示。
对于 BestEffort
Pod,由于没有 CPU 请求,static
CPU 管理器策略将发送默认提示,
而设备管理器将为每个请求的设备发送提示。
基于此信息,拓扑管理器将为 Pod 计算最佳提示并存储该信息,并且供
提示提供程序在进行资源分配时使用。
已知的局限性
拓扑管理器所能处理的最大 NUMA 节点个数是 8。若 NUMA 节点数超过 8,
枚举可能的 NUMA 亲和性并为之生成提示时会发生状态爆炸。
调度器不是拓扑感知的,所以有可能一个 Pod 被调度到一个节点之后,会因为拓扑管理器的缘故在该节点上启动失败。
4.2.31 - 搭建高可用的 Kubernetes Masters
FEATURE STATE: Kubernetes 1.5 [alpha]
你可以在谷歌计算引擎(GCE)的 kubeup
或 kube-down
脚本中复制 Kubernetes Master。
本文描述了如何使用 kube-up/down 脚本来管理高可用(HA)的 Master,
以及如何使用 GCE 实现高可用控制节点。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
启动一个兼容高可用的集群
要创建一个新的兼容高可用的集群,你必须在 kubeup
脚本中设置以下标志:
MULTIZONE=true
- 为了防止从不同于服务器的默认区域的区域中删除 kubelets 副本。
如果你希望在不同的区域运行副本,那么这一项是必需并且推荐的。
ENABLE_ETCD_QUORUM_READ=true
- 确保从所有 API 服务器读取数据时将返回最新的数据。
如果为 true,读操作将被定向到主 etcd 副本。可以选择将这个值设置为 true,
那么读取将更可靠,但也会更慢。
你还可以指定一个 GCE 区域,在这里创建第一个主节点副本。设置以下标志:
KUBE_GCE_ZONE=zone
- 将运行第一个主节点副本的区域。
下面的命令演示在 GCE europe-west1-b 区域中设置一个兼容高可用的集群:
MULTIZONE = true KUBE_GCE_ZONE = europe-west1-b ENABLE_ETCD_QUORUM_READS = true ./cluster/kube-up.sh
注意,上面的命令创建一个只有单一主节点的集群;
但是,你可以使用后续命令将新的主节点副本添加到集群中。
增加一个新的主节点副本
在创建了兼容高可用的集群之后,可以向其中添加主节点副本。
你可以使用带有如下标记的 kubeup
脚本添加主节点副本:
你无需设置 MULTIZONE
或 ENABLE_ETCD_QUORUM_READS
标志,因为他们可以从兼容高可用的集群中继承。
使用下面的命令可以复制现有兼容高可用的集群上的 Master:
KUBE_GCE_ZONE = europe-west1-c KUBE_REPLICATE_EXISTING_MASTER = true ./cluster/kube-up.sh
删除主节点副本
你可以使用一个 kube-down
脚本从高可用集群中删除一个主节点副本,并可以使用以下标记:
KUBE_DELETE_NODES=false
- 限制删除 kubelets。
KUBE_GCE_ZONE=zone
- 将移除主节点副本的区域。
KUBE_REPLICA_NAME=replica_name
- (可选)要删除的主节点副本的名称。
如果为空:将删除给定区域中的所有副本。
使用下面的命令可以从一个现有的高可用集群中删除一个 Master副本:
KUBE_DELETE_NODES = false KUBE_GCE_ZONE = europe-west1-c ./cluster/kube-down.sh
处理主节点副本失败
如果高可用集群中的一个主节点副本失败,最佳实践是从集群中删除副本,
并在相同的区域中添加一个新副本。
下面的命令演示了这个过程:
删除失败的副本:
KUBE_DELETE_NODES = false KUBE_GCE_ZONE = replica_zone KUBE_REPLICA_NAME = replica_name ./cluster/kube-down.sh
在原有位置增加一个新副本:
KUBE_GCE_ZONE = replica-zone KUBE_REPLICATE_EXISTING_MASTER = true ./cluster/kube-up.sh
高可用集群复制主节点的最佳实践
尝试将主节点副本放置在不同的区域。在某区域故障时,放置在该区域内的所有主机都将失败。
为了在区域故障中幸免,请同样将工作节点放置在多区域中
(详情请见多区域 )。
不要使用具有两个主节点副本的集群。在双副本集群上达成一致需要在更改持久状态时
两个副本都处于运行状态。
因此,两个副本都是需要的,任一副本的失败都会将集群带入多数失败状态。
因此,就高可用而言,双副本集群不如单个副本集群。
添加主节点副本时,集群状态(etcd)会被复制到一个新实例。如果集群很大,
可能需要很长时间才能复制它的状态。
这个操作可以通过迁移 etcd 数据存储来加速, 详情参见
这里
(我们正在考虑在未来添加对迁移 etcd 数据存储的支持)。
实现说明
概述
每个主节点副本将以以下模式运行以下组件:
etcd 实例: 所有实例将会以共识方式组建集群;
API 服务器: 每个服务器将与本地 etcd 通信——集群中的所有 API 服务器都可用;
控制器、调度器和集群自动扩缩器:将使用租约机制 —— 每个集群中只有一个实例是可用的;
插件管理器:每个管理器将独立工作,试图保持插件同步。
此外,在 API 服务器前面将有一个负载均衡器,用于将外部和内部通信路由到他们。
负载均衡
启动第二个主节点副本时,将创建一个包含两个副本的负载均衡器,
并将第一个副本的 IP 地址提升为负载均衡器的 IP 地址。
类似地,在删除倒数第二个主节点副本之后,将删除负载均衡器,
并将其 IP 地址分配给最后一个剩余的副本。
请注意,创建和删除负载均衡器是复杂的操作,可能需要一些时间(约20分钟)来同步。
主节点服务 & kubelets
Kubernetes 并不试图在其服务中保持 apiserver 的列表为最新,
相反,它将将所有访问请求指向外部 IP:
在拥有一个主节点的集群中,IP 指向单一的主节点,
在拥有多个主节点的集群中,IP 指向主节点前面的负载均衡器。
类似地,kubelets 将使用外部 IP 与主节点通信。
主节点证书
Kubernetes 为每个副本的外部公共 IP 和本地 IP 生成主节点 TLS 证书。
副本的临时公共 IP 没有证书;
要通过其临时公共 IP 访问副本,必须跳过 TLS 检查。
etcd 集群
为了允许 etcd 组建集群,需开放 etcd 实例之间通信所需的端口(用于集群内部通信)。
为了使这种部署安全,etcd 实例之间的通信使用 SSL 进行鉴权。
API 服务器标识
FEATURE STATE: Kubernetes v1.20 [alpha]
使用 API 服务器标识功能需要启用特性门控 ,
该功能默认不启用。
你可以在启动 API 服务器 的时候启用特性门控 APIServerIdentity
来激活 API 服务器标识:
kube-apiserver \
--feature-gates= APIServerIdentity = true \
# …其他标记照常
在启动引导过程中,每个 kube-apiserver 会给自己分配一个唯一 ID。
该 ID 的格式是 kube-apiserver-{UUID}
。
每个 kube-apiserver 会在 kube-system 名字空间 里创建一个 Lease
对象 。
Lease
对象的名字是 kube-apiserver 的唯一 ID。
Lease
对象包含一个标签 k8s.io/component=kube-apiserver
。
每个 kube-apiserver 每过 IdentityLeaseRenewIntervalSeconds
(默认是 10 秒)就会刷新它的 Lease
对象。
每个 kube-apiserver 每过 IdentityLeaseDurationSeconds
(默认是 3600 秒)也会检查所有 kube-apiserver 的标识 Lease
对象,
并且会删除超过 IdentityLeaseDurationSeconds
时间还没被刷新的 Lease
对象。
可以在 kube-apiserver 的 identity-lease-renew-interval-seconds
和 identity-lease-duration-seconds
标记里配置 IdentityLeaseRenewIntervalSeconds
和 IdentityLeaseDurationSeconds
。
启用该功能是使用 HA API 服务器协调相关功能(例如,StorageVersionAPI
特性门控)的前提条件。
拓展阅读
自动化高可用集群部署 - 设计文档
4.2.32 - 改变默认 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.33 - 更改 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 REASON AGE
pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim1 10s
pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim2 6s
pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim3 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 REASON AGE
pvc-b6efd8da-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim1 40s
pvc-b95650f8-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Delete Bound default/claim2 36s
pvc-bb3ca71d-b7b5-11e6-9d58-0ed433a7dd94 4Gi RWO Retain Bound default/claim3 33s
在前面的输出中,你可以看到绑定到申领 default/claim3
的卷的回收策略为 Retain
。
当用户删除申领 default/claim3
时,它不会被自动删除。
接下来
参考
4.2.34 - 自动扩缩集群 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
,内容如下所示:
apiVersion : apps/v1
kind : Deployment
metadata :
name : dns-autoscaler
namespace : kube-system
labels :
k8s-app : dns-autoscaler
spec :
selector :
matchLabels :
k8s-app : dns-autoscaler
template :
metadata :
labels :
k8s-app : dns-autoscaler
spec :
containers :
- name : autoscaler
image : k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.6.0
resources :
requests :
cpu : 20m
memory : 10Mi
command :
- /cluster-proportional-autoscaler
- --namespace=kube-system
- --configmap=dns-autoscaler
- --target=<SCALE_TARGET>
# When cluster is using large nodes(with more cores), "coresPerReplica" should dominate.
# If using small nodes, "nodesPerReplica" should dominate.
- --default-params={"linear":{"coresPerReplica":256,"nodesPerReplica":16,"min":1}}
- --logtostderr=true
- --v=2
在文件中,将 <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.2.35 - 自定义 DNS 服务
本页说明如何配置 DNS Pod(s) ,以及定制集群中 DNS 解析过程。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.12.
要获知版本信息,请输入
kubectl version
.
你的集群必须运行 CoreDNS 插件。
文档迁移到 CoreDNS
解释了如何使用 kubeadm
从 kube-dns
迁移到 CoreDNS。
介绍
DNS 是使用集群插件
管理器自动启动的内置的 Kubernetes 服务。
从 Kubernetes v1.12 开始,CoreDNS 是推荐的 DNS 服务器,取代了 kube-dns。 如果
你的集群原来使用 kube-dns,你可能部署的仍然是 kube-dns
而不是 CoreDNS。
说明: 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 记录)等。
更多信息,请参见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 Corefile 的 ConfigMap,以更改服务发现的工作方式。
在 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 的健康报告。
ready :在端口 8181 上提供的一个 HTTP 末端,当所有能够
表达自身就绪的插件都已就绪时,在此末端返回 200 OK。
kubernetes :CoreDNS 将基于 Kubernetes 的服务和 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 格式在 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
}
工具 kubeadm
支持将 kube-dns ConfigMap 自动转换为 CoreDNS ConfigMap。
说明: 尽管 kube-dns 接受 FQDN(例如:ns.foo.com)作为存根域和名字服务器,CoreDNS 不支持此功能。
转换期间,CoreDNS 配置中将忽略所有的 FQDN 域名服务器。
CoreDNS 配置等同于 kube-dns
CoreDNS 不仅仅提供 kube-dns 的功能。
为 kube-dns 创建的 ConfigMap 支持 StubDomains
和 upstreamNameservers
转换为 CoreDNS 中的 forward
插件。
示例
用于 kubedns 的此示例 ConfigMap 描述了 stubdomains 和 upstreamnameservers:
apiVersion : v1
data :
stubDomains : |
{"abc.com" : ["1.2.3.4" ], "my.cluster.local" : ["2.3.4.5" ]}
upstreamNameservers : |
["8.8.8.8" , "8.8.4.4" ]
kind : ConfigMap
CoreDNS 中的等效配置将创建一个 Corefile:
针对 stubDomains:
abc.com:53 {
errors
cache 30
proxy . 1.2.3.4
}
my.cluster.local:53 {
errors
cache 30
proxy . 2.3.4.5
}
带有默认插件的完整 Corefile:
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
federation cluster.local {
foo foo.feddomain.com
}
prometheus :9153
forward . 8.8.8.8 8.8.4.4
cache 30
}
abc.com:53 {
errors
cache 30
forward . 1.2.3.4
}
my.cluster.local:53 {
errors
cache 30
forward . 2.3.4.5
}
迁移到 CoreDNS
要从 kube-dns 迁移到 CoreDNS,此博客
提供了帮助用户将 kube-dns 替换为 CoreDNS。
集群管理员还可以使用部署脚本
进行迁移。
接下来
4.2.36 - 访问集群上运行的服务
本文展示了如何连接 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://104.197.5.247
elasticsearch-logging is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
kibana-logging is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/kibana-logging/proxy
kube-dns is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/kube-dns/proxy
grafana is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
heapster is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/monitoring-heapster/proxy
这一输出显示了用 proxy 动词访问每个服务时可用的 URL。例如,此集群
(使用 Elasticsearch)启用了集群层面的日志。如果提供合适的凭据,可以通过
https://104.197.5.247/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 。
示例
通过 Web 浏览器访问集群中运行的服务
你或许能够将 API 服务器代理的 URL 放入浏览器的地址栏,然而:
Web 服务器通常不能传递令牌,所以你可能需要使用基本(密码)认证。
API 服务器可以配置为接受基本认证,但你的集群可能并没有这样配置。
某些 Web 应用可能无法工作,特别是那些使用客户端 Javascript 构造 URL 的
应用,所构造的 URL 可能并不支持代理路径前缀。
4.2.37 - 调试 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 : k8s.gcr.io/e2e-test-images/jessie-dnsutils:1.3
command :
- sleep
- "3600"
imagePullPolicy : IfNotPresent
restartPolicy : Always
说明: 此示例在
default
命名空间创建 pod。 服务的 DNS 名字解析取决于 pod 的命名空间。 详细信息请查阅
服务和 Pod 的 DNS 。
使用上面的清单来创建一个 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'
或者
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 容器的日志信息。
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 ep 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
你的服务在正确的命名空间中吗?
未指定命名空间的 DNS 查询仅作用于 pod 所在的命名空间。
如果 pod 和服务的命名空间不相同,则 DNS 查询必须指定服务所在的命名空间。
该查询仅限于 pod 所在的名称空间:
kubectl exec -i -t dnsutils -- nslookup <service-name>
指定命名空间的查询:
kubectl exec -i -t dnsutils -- nslookup <service-name>.<namespace>
要进一步了解名字解析,请查看
服务和 Pod 的 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.38 - 通过名字空间共享集群
本页展示如何查看、使用和删除名字空间 。
本页同时展示如何使用 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 名字空间,用以存放集群所使用的默认
Pods、Services 和 Deployments 集合。
假设你有一个新的集群,你可以通过执行以下操作来内省可用的名字空间
NAME STATUS AGE
default Active 13m
创建新的名字空间
在本练习中,我们将创建两个额外的 Kubernetes 名字空间来保存我们的内容。
在某组织使用共享的 Kubernetes 集群进行开发和生产的场景中:
开发团队希望在集群中维护一个空间,以便他们可以查看用于构建和运行其应用程序的 Pods、Services
和 Deployments 列表。在这个空间里,Kubernetes 资源被自由地加入或移除,
对谁能够或不能修改资源的限制被放宽,以实现敏捷开发。
运维团队希望在集群中维护一个空间,以便他们可以强制实施一些严格的规程,
对谁可以或不可以操作运行生产站点的 Pods、Services 和 Deployments 集合进行控制。
该组织可以遵循的一种模式是将 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 名字空间为集群中的 Pods、Services 和 Deployments 提供了作用域。
与一个名字空间交互的用户不会看到另一个名字空间中的内容。
为了演示这一点,让我们在 development
名字空间中启动一个简单的 Deployment 和 Pod。
kubectl create deployment snowflake --image= k8s.gcr.io/serve_hostname -n= development
kubectl scale deployment snowflake --replicas= 2 -n= development
我们创建了一个副本个数为 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= k8s.gcr.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 集群。
名字空间通过以下方式实现这点:
为名字 设置作用域.
为集群中的部分资源关联鉴权和策略的机制。
使用多个名字空间是可选的。
每个用户社区都希望能够与其他社区隔离开展工作。
每个用户社区都有自己的:
资源(pods、服务、 副本控制器等等)
策略(谁能或不能在他们的社区里执行操作)
约束(该社区允许多少配额,等等)
集群运营者可以为每个唯一用户社区创建名字空间。
名字空间为下列内容提供唯一的作用域:
命名资源(避免基本的命名冲突)
将管理权限委派给可信用户
限制社区资源消耗的能力
用例包括:
作为集群运营者, 我希望能在单个集群上支持多个用户社区。
作为集群运营者,我希望将集群分区的权限委派给这些社区中的受信任用户。
作为集群运营者,我希望能限定每个用户社区可使用的资源量,以限制对使用同一集群的其他用户社区的影响。
作为群集用户,我希望与我的用户社区相关的资源进行交互,而与其他用户社区在该集群上执行的操作无关。
理解名字空间和 DNS
当你创建服务 时,Kubernetes
会创建相应的 DNS 条目 。
此条目的格式为 <服务名称>.<名字空间名称>.svc.cluster.local
。
这意味着如果容器使用 <服务名称>
,它将解析为名字空间本地的服务。
这对于在多个名字空间(如开发、暂存和生产)中使用相同的配置非常有用。
如果要跨名字空间访问,则需要使用完全限定的域名(FQDN)。
接下来
4.2.39 - 通过配置文件设置 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 将会开始驱逐 Pods。
没有声明的其余配置项都将使用默认值,除非使用命令行参数来重载。
命令行中的参数将会覆盖配置文件中的对应值。
启动通过配置文件配置的 Kubelet 进程
启动 Kubelet 需要将 --config
参数设置为 Kubelet 配置文件的路径。Kubelet 将从此文件加载其配置。
请注意,命令行参数与配置文件有相同的值时,就会覆盖配置文件中的该值。
这有助于确保命令行 API 的向后兼容性。
请注意,kubelet 配置文件中的相对文件路径是相对于 kubelet 配置文件的位置解析的,
而命令行参数中的相对路径是相对于 kubelet 的当前工作目录解析的。
请注意,命令行参数和 Kubelet 配置文件的某些默认值不同。
如果设置了 --config
,并且没有通过命令行指定值,则 KubeletConfiguration
版本的默认值生效。在上面的例子中,version 是 kubelet.config.k8s.io/v1beta1
。
接下来
4.2.40 - 配置 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 create -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 资源:
String API Object
"pods" Pod
"services" Service
"replicationcontrollers" ReplicationController
"resourcequotas" ResourceQuota
"secrets" Secret
"configmaps" ConfigMap
"persistentvolumeclaims" PersistentVolumeClaim
"services.nodeports" Service of type NodePort
"services.loadbalancers" Service of type LoadBalancer
清理
删除你的命名空间:
kubectl delete namespace quota-object-example
接下来
集群管理员参考
应用开发者参考
4.2.41 - 配置资源不足时的处理方式
本页介绍如何使用 kubelet
配置资源不足时的处理方式。
当可用计算资源较少时,kubelet
需要保证节点稳定性。
这在处理如内存和硬盘之类的不可压缩资源时尤为重要。
如果任意一种资源耗尽,节点将会变得不稳定。
驱逐信号
kubelet
支持按照以下表格中描述的信号触发驱逐决定。
每个信号的值在 description 列描述,基于 kubelet
摘要 API。
驱逐信号
描述
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
上面的每个信号都支持字面值或百分比的值。基于百分比的值的计算与每个信号对应的总容量相关。
memory.available
的值从 cgroupfs 获取,而不是通过类似 free -m
的工具。
这很重要,因为 free -m
不能在容器中工作,并且如果用户使用了
节点可分配资源
特性,资源不足的判定将同时在本地 cgroup 层次结构的终端用户 Pod 部分和根节点做出。
这个脚本
复现了与 kubelet
计算 memory.available
相同的步骤。
kubelet
将 inactive_file
(意即活动 LRU 列表上基于文件后端的内存字节数)从计算中排除,
因为它假设内存在出现压力时将被回收。
kubelet
只支持两种文件系统分区。
nodefs
文件系统,kubelet 将其用于卷和守护程序日志等。
imagefs
文件系统,容器运行时用于保存镜像和容器可写层。
imagefs
可选。kubelet
使用 cAdvisor 自动发现这些文件系统。
kubelet
不关心其它文件系统。当前不支持配置任何其它类型。
例如,在专用 filesytem
中存储卷和日志是 不可以 的。
在将来的发布中,kubelet
将废除当前存在的
垃圾回收
机制,这种机制目前支持将驱逐操作作为对磁盘压力的响应。
驱逐阈值
kubelet
支持指定驱逐阈值,用于触发 kubelet
回收资源。
每个阈值形式如下:
[eviction-signal][operator][quantity]
合法的 eviction-signal
标志如上所示。
operator
是所需的关系运算符,例如 <
。
quantity
是驱逐阈值值标志,例如 1Gi
。合法的标志必须匹配 Kubernetes 使用的数量表示。
驱逐阈值也可以使用 %
标记表示百分比。
举例说明,如果一个节点有 10Gi
内存,希望在可用内存下降到 1Gi
以下时引起驱逐操作,
则驱逐阈值可以使用下面任意一种方式指定(但不是两者同时)。
memory.available<10%
memory.available<1Gi
软驱逐阈值
软驱逐阈值使用一对由驱逐阈值和管理员必须指定的宽限期组成的配置对。在超过宽限期前,kubelet
不会采取任何动作回收和驱逐信号关联的资源。如果没有提供宽限期,kubelet
启动时将报错。
此外,如果达到了软驱逐阈值,操作员可以指定从节点驱逐 pod 时,在宽限期内允许结束的 pod 的最大数量。
如果指定了 pod.Spec.TerminationGracePeriodSeconds
值,
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
将立即采取行动回收关联的短缺资源。
如果满足硬驱逐阈值,kubelet
将立即结束 Pod 而不是体面地终止它们。
硬驱逐阈值的配置支持下列标记:
eviction-hard
描述了驱逐阈值的集合(例如 memory.available<1Gi
),如果满足条件将触发 Pod 驱逐。
kubelet
有如下所示的默认硬驱逐阈值:
memory.available<100Mi
nodefs.available<10%
imagefs.available<15%
在Linux节点上,默认值还包括 nodefs.inodesFree<5%
。
驱逐监控时间间隔
kubelet
根据其配置的整理时间间隔计算驱逐阈值。
housekeeping-interval
是容器管理时间间隔。
节点状态
kubelet
会将一个或多个驱逐信号映射到对应的节点状态。
如果满足硬驱逐阈值,或者满足独立于其关联宽限期的软驱逐阈值时,kubelet
将报告节点处于压力下的状态。
下列节点状态根据相应的驱逐信号定义。
节点状态
驱逐信号
描述
MemoryPressure
memory.available
节点上可用内存量达到逐出阈值
DiskPressure
nodefs.available
, nodefs.inodesFree
, imagefs.available
, 或 imagefs.inodesFree
节点或者节点的根文件系统或镜像文件系统上可用磁盘空间和 i 节点个数达到逐出阈值
PIDPressure
pid.available
在(Linux)节点上的可用进程标识符已降至驱逐阈值以下
kubelet
将以 --node-status-update-frequency
指定的频率连续报告节点状态更新,其默认值为 10s
。
节点状态振荡
如果节点在软驱逐阈值的上下振荡,但没有超过关联的宽限期时,将引起对应节点的状态持续在
true 和 false 间跳变,并导致不好的调度结果。
为了防止这种振荡,可以定义下面的标志,用于控制 kubelet
从压力状态中退出之前必须等待的时间。
eviction-pressure-transition-period
是 kubelet
从压力状态中退出之前必须等待的时长。
kubelet
将确保在设定的时间段内没有发现和指定压力条件相对应的驱逐阈值被满足时,才会将状态变回 false
。
回收节点层级资源
如果满足驱逐阈值并超过了宽限期,kubelet
将启动回收压力资源的过程,直到它发现低于设定阈值的信号为止。
kubelet
将尝试在驱逐终端用户 pod 前回收节点层级资源。
发现磁盘压力时,如果节点针对容器运行时配置有独占的 imagefs
,kubelet
回收节点层级资源的方式将会不同。
使用 imagefs
如果 nodefs
文件系统满足驱逐阈值,kubelet
通过驱逐 pod 及其容器来释放磁盘空间。
如果 imagefs
文件系统满足驱逐阈值,kubelet
通过删除所有未使用的镜像来释放磁盘空间。
未使用 imagefs
如果 nodefs
满足驱逐阈值,kubelet
将以下面的顺序释放磁盘空间:
删除停止运行的 pod/container
删除全部没有使用的镜像
驱逐最终用户的 pod
如果 kubelet
在节点上无法回收足够的资源,kubelet
将开始驱逐 pod。
kubelet
首先根据他们对短缺资源的使用是否超过请求来排除 pod 的驱逐行为,
然后通过优先级 ,
然后通过相对于 pod 的调度请求消耗急需的计算资源。
kubelet
按以下顺序对要驱逐的 pod 排名:
BestEffort
或 Burstable
,其对短缺资源的使用超过了其请求,此类 pod 按优先级排序,然后使用高于请求。
Guaranteed
pod 和 Burstable
pod,其使用率低于请求,最后被驱逐。
Guaranteed
Pod 只有为所有的容器指定了要求和限制并且它们相等时才能得到保证。
由于另一个 Pod 的资源消耗,这些 Pod 保证永远不会被驱逐。
如果系统守护进程(例如 kubelet
、docker
、和 journald
)消耗的资源多于通过
system-reserved
或 kube-reserved
分配保留的资源,并且该节点只有 Guaranteed
或
Burstable
Pod 使用少于剩余的请求,然后节点必须选择驱逐这样的 Pod
以保持节点的稳定性并限制意外消耗对其他 pod 的影响。
在这种情况下,它将首先驱逐优先级最低的 pod。
必要时,kubelet
会在遇到 DiskPressure
时逐个驱逐 Pod 来回收磁盘空间。
如果 kubelet
响应 inode
短缺,它会首先驱逐服务质量最低的 Pod 来回收 inodes
。
如果 kubelet
响应缺少可用磁盘,它会将 Pod 排在服务质量范围内,该服务会消耗大量的磁盘并首先结束这些磁盘。
使用 imagefs
如果是 nodefs
触发驱逐,kubelet
将按 nodefs
用量 - 本地卷 + pod 的所有容器日志的总和对其排序。
如果是 imagefs
触发驱逐,kubelet
将按 pod 所有可写层的用量对其进行排序。
未使用 imagefs
如果是 nodefs
触发驱逐,kubelet
会根据磁盘的总使用情况对 pod 进行排序 - 本地卷 + 所有容器的日志及其可写层。
最小驱逐回收
在某些场景,驱逐 pod 会导致回收少量资源。这将导致 kubelet
反复碰到驱逐阈值。除此之外,对如 disk
这类资源的驱逐时比较耗时的。
为了减少这类问题,kubelet
可以为每个资源配置一个 minimum-reclaim
。
当 kubelet
发现资源压力时,kubelet
将尝试至少回收驱逐阈值之下 minimum-reclaim
数量的资源。
例如使用下面的配置:
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
如果 memory.available
驱逐阈值被触发,kubelet
将保证 memory.available
至少为 500Mi
。
对于 nodefs.available
,kubelet
将保证 nodefs.available
至少为 1.5Gi
。
对于 imagefs.available
,kubelet
将保证 imagefs.available
至少为 102Gi
,
直到不再有相关资源报告压力为止。
所有资源的默认 eviction-minimum-reclaim
值为 0
。
调度器
当资源处于压力之下时,节点将报告状态。调度器将那种状态视为一种信号,阻止更多 pod 调度到这个节点上。
节点状态
调度器行为
MemoryPressure
新的 BestEffort
Pod 不会被调度到该节点
DiskPressure
没有新的 Pod 会被调度到该节点
节点 OOM 行为
如果节点在 kubelet
回收内存之前经历了系统 OOM(内存不足)事件,它将基于
oom-killer 做出响应。
kubelet
基于 pod 的 service 质量为每个容器设置一个 oom_score_adj
值。
Service 质量
oom_score_adj
Guaranteed
-998
BestEffort
1000
Burstable
min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
如果 kubelet
在节点经历系统 OOM 之前无法回收内存,oom_killer
将基于它在节点上
使用的内存百分比算出一个 oom_score
,并加上 oom_score_adj
得到容器的有效
oom_score
,然后结束得分最高的容器。
预期的行为应该是拥有最低服务质量并消耗和调度请求相关内存量最多的容器第一个被结束,以回收内存。
和 pod 驱逐不同,如果一个 Pod 的容器是被 OOM 结束的,基于其 RestartPolicy
,
它可能会被 kubelet
重新启动。
最佳实践
以下部分描述了资源外处理的最佳实践。
可调度资源和驱逐策略
考虑以下场景:
节点内存容量:10Gi
操作员希望为系统守护进程保留 10% 内存容量(内核、kubelet
等)。
操作员希望在内存用量达到 95% 时驱逐 pod,以减少对系统的冲击并防止系统 OOM 的发生。
为了促成这个场景,kubelet
将像下面这样启动:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
这个配置的暗示是理解系统保留应该包含被驱逐阈值覆盖的内存数量。
要达到这个容量,要么某些 pod 使用了超过它们请求的资源,要么系统使用的内存超过 1.5Gi - 500Mi = 1Gi
。
这个配置将保证在 pod 使用量都不超过它们配置的请求值时,如果可能立即引起内存压力并触发驱逐时,调度器不会将 pod 放到这个节点上。
DaemonSet
我们永远都不希望 kubelet
驱逐一个从 DaemonSet
派生的 Pod,因为这个 Pod 将立即被重建并调度回相同的节点。
目前,kubelet
没有办法区分一个 Pod 是由 DaemonSet
还是其他对象创建。
如果/当这个信息可用时,kubelet
可能会预先将这些 pod 从提供给驱逐策略的候选集合中过滤掉。
总之,强烈推荐 DaemonSet
不要创建 BestEffort
的 Pod,防止其被识别为驱逐的候选 Pod。
相反,理想情况下 DaemonSet
应该启动 Guaranteed
的 pod。
现有的回收磁盘特性标签已被弃用
kubelet
已经按需求清空了磁盘空间以保证节点稳定性。
当磁盘驱逐成熟时,下面的 kubelet
标志将被标记为废弃的,以简化支持驱逐的配置。
现有标签
新标签
--image-gc-high-threshold
--eviction-hard
or eviction-soft
--image-gc-low-threshold
--eviction-minimum-reclaim
--maximum-dead-containers
deprecated
--maximum-dead-containers-per-container
deprecated
--minimum-container-ttl-duration
deprecated
--low-diskspace-threshold-mb
--eviction-hard
or eviction-soft
--outofdisk-transition-frequency
--eviction-pressure-transition-period
已知问题
以下部分描述了与资源外处理有关的已知问题。
kubelet 可能无法立即发现内存压力
kubelet
当前通过以固定的时间间隔轮询 cAdvisor
来收集内存使用数据。如果内存使用在那个时间窗口内迅速增长,kubelet
可能不能足够快的发现 MemoryPressure
,OOMKiller
将不会被调用。我们准备在将来的发行版本中通过集成 memcg
通知 API 来减小这种延迟。当超过阈值时,内核将立即告诉我们。
如果您想处理可察觉的超量使用而不要求极端精准,可以设置驱逐阈值为大约 75% 容量作为这个问题的变通手段。这将增强这个特性的能力,防止系统 OOM,并提升负载卸载能力,以再次平衡集群状态。
kubelet 可能会驱逐超过需求数量的 pod
由于状态采集的时间差,驱逐操作可能驱逐比所需的更多的 pod。将来可通过添加从根容器获取所需状态的能力
https://github.com/google/cadvisor/issues/1247
来减缓这种状况。
4.2.42 - 限制存储消耗
此示例演示了如何限制一个名字空间中的存储使用量。
演示中用到了以下资源: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 数目和累计存储容量
管理员可以限制某个名字空间中的 PVCs 个数以及这些 PVCs 的累计容量。
新 PVCs 请求如果超过任一上限值将被拒绝。
在此示例中,名字空间中的第 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.43 - 静态加密 Secret 数据
本文展示如何启用和配置静态 Secret 数据的加密
准备开始
配置并确定是否已启用静态数据加密
kube-apiserver
的参数 --experimental-encryption-provider-config
控制 API 数据在 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 能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。
注意: 重要: 如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从底层 etcd 中删除该密钥。
任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。
Providers:
Kubernetes 静态数据加密的 Providers
名称
加密类型
强度
速度
密钥长度
其它事项
identity
无
N/A
N/A
N/A
不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。
aescbc
填充 PKCS#7 的 AES-CBC
最强
快
32字节
建议使用的加密项,但可能比 secretbox
稍微慢一些。
secretbox
XSalsa20 和 Poly1305
强
更快
32字节
较新的标准,在需要高度评审的环境中可能不被接受。
aesgcm
带有随机数的 AES-GCM
必须每 200k 写入一次
最快
16, 24 或者 32字节
建议不要使用,除非实施了自动密钥循环方案。
kms
使用信封加密方案:数据使用带有 PKCS#7 填充的 AES-CBC 通过数据加密密钥(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 或 Mac OS X 上,请运行以下命令:
head -c 32 /dev/urandom | base64
将这个值放入到 secret 字段中。
设置 kube-apiserver
的 --experimental-encryption-provider-config
参数,将其指向
配置文件所在位置。
重启你的 API server。
注意: 你的配置文件包含可以解密 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 服务的额外参数。
验证存储的密钥前缀是否为 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 -
以用新密钥加密所有现有的秘密
在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥
如果只有一个 kube-apiserver
,第 2 步可能可以忽略。
解密所有数据
要禁用 rest 加密,请将 identity
provider 作为配置中的第一个条目:
apiVersion : apiserver.config.k8s.io/v1
kind : EncryptionConfiguration
resources :
- resources :
- secrets
providers :
- identity : {}
- aescbc :
keys :
- name : key1
secret : <BASE 64 ENCODED SECRET>
并重新启动所有 kube-apiserver
进程。然后运行:
kubectl get secrets -all-namespaces -o json | kubectl replace -f -`
以强制解密所有 secret。
4.3 - 配置 Pods 和容器
对 Pod 和容器执行常见的配置任务。
4.3.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 :
limits :
memory : "200Mi"
requests :
memory : "100Mi"
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 :
limits :
memory : 200Mi
requests :
memory : 100Mi
...
运行 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: docker://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 :
limits :
memory : "1000Gi"
requests :
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.3.2 - 为 Windows Pod 和容器配置 GMSA
FEATURE STATE: 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 1.16 版本起,Docker 运行时为
Windows 负载支持 GMSA。
准备开始
你需要一个 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/v1alpha1
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 所代表的身份来执行操作,使用该身份来访问域中的服务。
Containerd
在 Windows Server 2019 上对 containerd 使用 GMSA,需要使用 Build 17763.1817(或更新的版本),
你可以安装补丁 KB5000822 。
containerd 场景从 Pod 连接 SMB 共享的时候有一个已知问题:
配置了 GMSA 以后,无法通过主机名或者 FQDN 访问 SMB共享,但是通过 IP 地址访问没有问题。
主机名可以被解析为 IPv4 地址,输出类似如下所示:
Pinging adserver.ad.local [192.168.111.18] with 32 bytes of data:
Reply from 192.168.111.18: bytes=32 time=6ms TTL=124
Reply from 192.168.111.18: bytes=32 time=5ms TTL=124
Reply from 192.168.111.18: bytes=32 time=5ms TTL=124
Reply from 192.168.111.18: bytes=32 time=5ms TTL=124
但是,当尝试使用主机名浏览目录时:
cd \\adserver.ad.local\test
你会看到一个错误,提示目标共享不存在:
cd : Cannot find path '\\adserver.ad.local\test' because it does not exist.
At line:1 char:1
+ cd \\adserver.ad.local\test
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (\\adserver.ad.local\test:String) [Set-Location], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.SetLocationCommand
但是你会注意到,如果你改为使用其 IPv4 地址浏览共享,错误就会消失;例如:
切换到共享中的目录后,你会看到类似于以下内容的提示:
Microsoft.PowerShell.Core\FileSystem::\\192.168.111.18\test>
要解决问题,你需要在节点上运行以下命令以添加所需的注册表项
reg add "HKLM\SYSTEM\CurrentControlSet\Services\hns\State" /v EnableCompartmentNamespace /t REG_DWORD /d 1
。
此更改只会在新创建的 Pod 中生效,这意味着你必须重新创建任何需要访问 SMB 共享的正在运行的 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
命令。
这告诉我们,由于某种原因,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.3.3 - 为 Windows 的 Pod 和容器配置 RunAsUserName
FEATURE STATE: 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 的用户的用户名是否正确:
输出结果应该是这样:
为容器设置 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.3.4 - 为容器和 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 milli CPU,并且 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 请求,你可以完成两件事:
Pod 可能会有突发性的活动,它可以利用碰巧可用的 CPU 资源。
Pod 在突发负载期间可以使用的 CPU 资源数量仍被限制为合理的数量。
清理
删除名称空间:
kubectl delete namespace cpu-example
接下来
针对应用开发者
针对集群管理员
4.3.5 - 配置 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 create -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 create -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 create -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 create -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.3.6 - 为容器分派扩展资源
FEATURE STATE: Kubernetes v1.23 [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.3.7 - 配置 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.3.8 - 配置 Pod 以使用 PersistentVolume 作为存储
本文介绍如何配置 Pod 使用
PersistentVolumeClaim
作为存储。
以下是该过程的总结:
你作为集群管理员创建由物理存储支持的 PersistentVolume。你不会将卷与任何 Pod 关联。
你现在以开发人员或者集群用户的角色创建一个 PersistentVolumeClaim,
它将自动绑定到合适的 PersistentVolume。
你创建一个使用 PersistentVolumeClaim 作为存储的 Pod。
准备开始
你需要一个包含单个节点的 Kubernetes 集群,并且必须配置 kubectl 命令行工具以便与集群交互。
如果还没有单节点集群,可以使用
Minikube 创建一个。
.
熟悉持久卷 中的材料。
在你的节点上创建一个 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"
创建 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 create -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
下一步是创建一个 Pod, 该 Pod 使用你的 PersistentVolumeClaim 作为存储卷。
下面是 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 中执行下面三个命令
root@task-pv-pod:/# apt-get update
root@task-pv-pod:/# apt-get install curl
root@task-pv-pod:/# curl 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。
访问控制
使用组 ID(GID)配置的存储仅允许 Pod 使用相同的 GID 进行写入。
GID 不匹配或缺失将会导致无权访问错误。
为了减少与用户的协调,管理员可以对 PersistentVolume 添加 GID 注解。
这样 GID 就能自动添加到使用 PersistentVolume 的任何 Pod 中。
使用 pv.beta.kubernetes.io/gid
注解的方法如下所示:
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.3.9 - 配置 Pod 使用投射卷作存储
本文介绍怎样通过projected
卷将现有的多个卷资源挂载到相同的目录。
当前,secret
、configMap
、downwardAPI
和 serviceAccountToken
卷可以被投射。
说明: serviceAccountToken
不是一种卷类型
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
为 Pod 配置 projected 卷
本练习中,您将从本地文件来创建包含有用户名和密码的 Secret。然后创建运行一个容器的 Pod,
该 Pod 使用projected
卷将 Secret 挂载到相同的路径下。
下面是 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : test-projected-volume
spec :
containers :
- name : test-projected-volume
image : busybox
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 create -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.3.10 - 为 Pod 或容器配置安全性上下文
安全上下文(Security Context)定义 Pod 或 Container 的特权与访问控制设置。
安全上下文包括但不限于:
AppArmor :使用程序框架来限制个别程序的权能。
Seccomp :过滤进程的系统调用。
AllowPrivilegeEscalation:控制进程是否可以获得超出其父进程的特权。
此布尔值直接控制是否为容器进程设置
no_new_privs
标志。
当容器以特权模式运行或者具有 CAP_SYS_ADMIN
权能时,AllowPrivilegeEscalation 总是为 true。
readOnlyRootFilesystem:以只读方式加载容器的根文件系统。
以上条目不是安全上下文设置的完整列表 -- 请参阅
SecurityContext
了解其完整列表。
关于在 Linux 系统中的安全机制的更多信息,可参阅
Linux 内核安全性能力概述 。
准备开始
你必须拥有一个 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
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 配置卷访问权限和属主变更策略
FEATURE STATE: Kubernetes v1.20 [beta]
默认情况下,Kubernetes 在挂载一个卷时,会递归地更改每个卷中的内容的属主和访问权限,使之与 Pod
的 securityContext
中指定的 fsGroup
匹配。
对于较大的数据卷,检查和变更属主与访问权限可能会花费很长时间,降低 Pod 启动速度。
你可以在 securityContext
中使用 fsGroupChangePolicy
字段来控制 Kubernetes
检查和管理卷属主和访问权限的方式。
fsGroupChangePolicy - fsGroupChangePolicy
定义在卷被暴露给 Pod 内部之前对其
内容的属主和访问许可进行变更的行为。此字段仅适用于那些支持使用 fsGroup
来
控制属主与访问权限的卷类型。此字段的取值可以是:
OnRootMismatch
:只有根目录的属主与访问权限与卷所期望的权限不一致时,
才改变其中内容的属主和访问权限。这一设置有助于缩短更改卷的属主与访问
权限所需要的时间。
Always
:在挂载卷时总是更改卷中内容的属主和访问权限。
例如:
securityContext :
runAsUser : 1000
runAsGroup : 3000
fsGroup : 2000
fsGroupChangePolicy : "OnRootMismatch"
将卷权限和所有权更改委派给 CSI 驱动程序
FEATURE STATE: Kubernetes v1.22 [alpha]
如果你部署了一个容器存储接口 (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 层面设置的内容发生重叠时,会重载后者。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 安全性模块。
讨论
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.3.11 - 为 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 许可取决于你所使用的
鉴权插件和策略 。
在 1.6 以上版本中,你可以通过在服务账户上设置 automountServiceAccountToken: false
来实现不给服务账号自动挂载 API 凭据:
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 create -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
secrets :
- name : build-robot-token-bvbk5
那么你就能看到系统已经自动创建了一个令牌并且被服务账户所引用。
你可以使用授权插件来
设置服务账户的访问许可 。
要使用非默认的服务账户,将 Pod 的 spec.serviceAccountName
字段设置为你想用的服务账户名称。
Pod 被创建时服务账户必须存在,否则会被拒绝。
你不能更新已经创建好的 Pod 的服务账户。
你可以清除服务账户,如下所示:
kubectl delete serviceaccount/build-robot
手动创建服务账户 API 令牌
假设我们有一个上面提到的名为 "build-robot" 的服务账户,然后我们手动创建一个新的 Secret。
kubectl create -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-secret created
现在,你可以确认新构建的 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: ...
说明: 这里省略了 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
secrets :
- name : default-token-uudge
使用你常用的编辑器(例如 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
secrets :
- name : default-token-uudge
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
服务帐户令牌卷投射
FEATURE STATE: Kubernetes v1.20 [stable]
说明: 为了启用令牌请求投射,你必须为 kube-apiserver
设置以下命令行参数:
--service-account-issuer
--service-account-key-file
--service-account-signing-key-file
--api-audiences
kubelet 还可以将服务帐户令牌投影到 Pod 中。
你可以指定令牌的所需属性,例如受众和有效持续时间。
这些属性在默认服务帐户令牌上无法配置。
当删除 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 分钟)重新加载就足够了。
发现服务账号分发者
FEATURE STATE: 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.3.12 - 从私有仓库拉取镜像
本文介绍如何使用 Secret 从私有的 Docker 镜像仓库或代码仓库拉取镜像来创建 Pod。
准备开始
你需要 Docker ID 和密码来进行本练习。
登录 Docker 镜像仓库
在个人电脑上,要想拉取私有镜像必须在镜像仓库上进行身份验证。
当出现提示时,输入 Docker 用户名和密码。
登录过程会创建或更新保存有授权令牌的 config.json
文件。
查看 config.json
文件:
cat ~/.docker/config.json
输出结果包含类似于以下内容的部分:
{
"auths" : {
"https://index.docker.io/v1/" : {
"auth" : "c3R...zE2"
}
}
}
说明: 如果使用 Docker 凭证仓库,则不会看到 auth
条目,看到的将是以仓库名称作为值的 credsStore
条目。
在集群中创建保存授权令牌的 Secret
Kubernetes 集群使用 docker-registry
类型的 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 regcred
要了解你创建的 regcred
Secret 的内容,可以用 YAML 格式进行查看:
kubectl get secret regcred --output= yaml
输出和下面类似:
apiVersion : v1
data :
.dockerconfigjson : eyJodHRwczovL2luZGV4L ... J0QUl6RTIifX0=
kind : Secret
metadata :
...
name : regcred
...
type : kubernetes.io/dockerconfigjson
.dockerconfigjson
字段的值是 Docker 凭据的 base64 表示。
要了解 dockerconfigjson
字段中的内容,请将 Secret 数据转换为可读格式:
kubectl get secret regcred --output= "jsonpath={.data.\.dockerconfigjson}" | base64 --decode
输出和下面类似:
{"auths" :{"yourprivateregistry.com" :{"username" :"janedoe" ,"password" :"xxxxxxxxxxx" ,"email" :"[email protected] " ,"auth" :"c3R...zE2" }}}
要了解 auth
字段中的内容,请将 base64 编码过的数据转换为可读格式:
echo "c3R...zE2" | base64 --decode
输出结果中,用户名和密码用 :
链接,类似下面这样:
janedoe:xxxxxxxxxxx
注意,Secret 数据包含与本地 ~/.docker/config.json
文件类似的授权令牌。
这样你就已经成功地将 Docker 凭据设置为集群中的名为 regcred
的 Secret。
创建一个使用你的 Secret 的 Pod
下面是一个 Pod 配置文件,它需要访问 regcred
中的 Docker 凭据:
apiVersion : v1
kind : Pod
metadata :
name : private-reg
spec :
containers :
- name : private-reg-container
image : <your-private-image>
imagePullSecrets :
- name : regcred
下载上述文件:
wget -O my-private-reg-pod.yaml https://k8s.io/examples/pods/private-reg-pod.yaml
在my-private-reg-pod.yaml
文件中,使用私有仓库的镜像路径替换 <your-private-image>
,例如:
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.3.13 - 配置存活、就绪和启动探测器
这篇文章介绍如何给容器配置存活、就绪和启动探测器。
kubelet
使用存活探测器来知道什么时候要重启容器。
例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。
这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。
kubelet 使用就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod
内的所有容器都准备好了,才能把这个 Pod 看作就绪了。
这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。
在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。
kubelet 使用启动探测器可以知道应用程序容器什么时候启动了。
如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查,
确保这些存活、就绪探测器不会影响应用程序的启动。
这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
定义存活命令
许多长时间运行的应用程序最终会过渡到断开的状态,除非重新启动,否则无法恢复。
Kubernetes 提供了存活探测器来发现并补救这种情况。
在这篇练习中,你会创建一个 Pod,其中运行一个基于 k8s.gcr.io/busybox
镜像的容器。
下面是这个 Pod 的配置文件。
apiVersion : v1
kind : Pod
metadata :
labels :
test : liveness
name : liveness-exec
spec :
containers :
- name : liveness
image : k8s.gcr.io/busybox
args :
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe :
exec :
command :
- cat
- /tmp/healthy
initialDelaySeconds : 5
periodSeconds : 5
在这个配置文件中,可以看到 Pod 中只有一个容器。
periodSeconds
字段指定了 kubelet 应该每 5 秒执行一次存活探测。
initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 5 秒。
kubelet 在容器内执行命令 cat /tmp/healthy
来进行探测。
如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。
如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
当容器启动时,执行如下的命令:
/bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /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
输出结果表明还没有存活探测器失败:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
24s 24s 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-exec to worker0
23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Pulling pulling image "k8s.gcr.io/busybox"
23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "k8s.gcr.io/busybox"
23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Created Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
23s 23s 1 {kubelet worker0} spec.containers{liveness} Normal Started Started container with docker id 86849c15382e
35 秒之后,再来看 Pod 的事件:
kubectl describe pod liveness-exec
在输出结果的最下面,有信息显示存活探测器失败了,这个容器被杀死并且被重建了。
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
37s 37s 1 {default-scheduler } Normal Scheduled Successfully assigned liveness-exec to worker0
36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Pulling pulling image "k8s.gcr.io/busybox"
36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Pulled Successfully pulled image "k8s.gcr.io/busybox"
36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Created Created container with docker id 86849c15382e; Security:[seccomp=unconfined]
36s 36s 1 {kubelet worker0} spec.containers{liveness} Normal Started Started container with docker id 86849c15382e
2s 2s 1 {kubelet worker0} spec.containers{liveness} Warning Unhealthy Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
再等另外 30 秒,检查看这个容器被重启了:
kubectl get pod liveness-exec
输出结果显示 RESTARTS
的值增加了 1。
NAME READY STATUS RESTARTS AGE
liveness-exec 1/1 Running 1 1m
定义一个存活态 HTTP 请求接口
另外一种类型的存活探测方式是使用 HTTP GET 请求。
下面是一个 Pod 的配置文件,其中运行一个基于 k8s.gcr.io/liveness
镜像的容器。
apiVersion : v1
kind : Pod
metadata :
labels :
test : liveness
name : liveness-http
spec :
containers :
- name : liveness
image : k8s.gcr.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 : k8s.gcr.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
使用命名端口
对于 HTTP 或者 TCP 存活检测可以使用命名的
ContainerPort 。
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
来设置 Pod 状态。
定义就绪探测器
有时候,应用程序会暂时性的不能提供通信服务。
例如,应用程序在启动时可能需要加载很大的数据或配置文件,或是启动后要依赖等待外部服务。
在这种情况下,既不想杀死应用程序,也不想给它发送请求。
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
参数后,移除此标志重载。
如果你有 Pods 受到此默认 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 latestVersion >}}
(其中 1.23
是 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
FEATURE STATE: Kubernetes v1.22 [beta]
在 1.21 及更高版本中,当特性门控 ProbeTerminationGracePeriod
为
启用状态时,用户可以指定一个探测级别的 terminationGracePeriodSeconds
作为
探针规格的一部分。当特性门控被启用时,并且
Pod 级和探针级的 terminationGracePeriodSeconds
都已设置,kubelet 将
使用探针级设置的值。
说明:
从 Kubernetes 1.22 开始,ProbeTerminationGracePeriod
特性门控只
在 API 服务器上可用。 kubelet 始终遵守探针级别
terminationGracePeriodSeconds
字段(如果它存在于 Pod 上)。
如果你已经为现有 Pod 设置了 “terminationGracePeriodSeconds” 字段并且
不再希望使用针对每个探针的终止宽限期,则必须删除那些现有的 Pod。
当你(或控制平面或某些其他组件)创建替换
Pods,并且特性门控 “ProbeTerminationGracePeriod” 被禁用,那么
API 服务器会忽略 Pod 级别的 terminationGracePeriodSeconds
字段,即使
Pod 或 Pod 模板指定了它。
例如:
spec :
terminationGracePeriodSeconds : 3600 # pod-level
containers :
- name : test
image : ...
ports :
- name : liveness-port
containerPort : 8080
hostPort : 8080
livenessProbe :
httpGet :
path : /healthz
port : liveness-port
failureThreshold : 1
periodSeconds : 60
# Override pod-level terminationGracePeriodSeconds #
terminationGracePeriodSeconds : 60
探测器级别的 terminationGracePeriodSeconds
不能用于设置就绪态探针。
它将被 API 服务器拒绝。
接下来
你也可以阅读以下的 API 参考资料:
4.3.14 - 将 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
接下来
进一步了解标签和选择器
4.3.15 - 用节点亲和性把 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.3.16 - 配置 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
# These containers are run during pod initialization
initContainers :
- name : install
image : busybox
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.3.17 - 为容器的生命周期事件设置处理函数
这个页面将演示如何为容器的生命周期事件挂接处理函数。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.3.18 - 配置 Pod 使用 ConfigMap
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 <map-name> <data-source>
其中,<map-name> 是要设置的 ConfigMap 名称,<data-source> 是要从中提取数据的目录、
文件或者字面值。
ConfigMap 对象的名称必须是合法的
DNS 子域名 .
在你基于文件来创建 ConfigMap 时,<data-source> 中的键名默认取自
文件的基本名,而对应的值则默认为文件的内容。
你可以使用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"
selfLink : /api/v1/namespaces/default/configmaps/game-config
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
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"
注意: 当多次使用 --from-env-file
来从多个数据源创建 ConfigMap 时,仅仅最后一个 env 文件有效。
下面是一个多次使用 --from-env-file
参数的示例:
# 将样本文件下载到 `configure-pod-container/configmap/` 目录
wget https://k8s.io/examples/configmap/ui-env-file.properties -O configure-pod-container/configmap/ui-env-file.properties
# 创建 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"
selfLink : /api/v1/namespaces/default/configmaps/config-multi-env-files
uid : 252c4572-eb35-11e7-887b-42010a8002b8
data :
color : purple
how : fairlyNice
textmode : "true"
定义从文件创建 ConfigMap 时要使用的键
在使用 --from-file
参数时,你可以定义在 ConfigMap 的 data
部分出现键名,
而不是按默认行为使用文件名:
kubectl create configmap game-config-3 --from-file= <my-key-name>= <path-to-file>
<my-key-name>
是你要在 ConfigMap 中使用的键名,<path-to-file>
是你想要键表示数据源文件的位置。
例如:
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"
selfLink : /api/v1/namespaces/default/configmaps/game-config-3
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"
selfLink : /api/v1/namespaces/default/configmaps/special-config
uid : dadce046-d673-11e5-8cd0-68f728db1985
data :
special.how : very
special.type : charm
基于生成器创建 ConfigMap
自 1.14 开始,kubectl
开始支持 kustomization.yaml
。
你还可以基于生成器创建 ConfigMap,然后将其应用于 API 服务器上创建对象。
生成器应在目录内的 kustomization.yaml
中指定。
基于文件生成 ConfigMap
例如,要从 configure-pod-container/configmap/kubectl/game.properties
文件生成一个 ConfigMap:
# 创建包含 ConfigMapGenerator 的 kustomization.yaml 文件
cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: game-config-4
files:
- configure-pod-container/configmap/kubectl/game.properties
EOF
使用 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/kubectl/game.properties
EOF
使用 Kustomization 目录创建 ConfigMap 对象。
configmap/game-config-5-m67dt67794 created
从字面值生成 ConfigMap
要基于字符串 special.type=charm
和 special.how=very
生成 ConfigMap,
可以在 kusotmization.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 : k8s.gcr.io/busybox
command : [ "/bin/sh" , "-c" , "env" ]
env :
# Define the environment variable
- name : SPECIAL_LEVEL_KEY
valueFrom :
configMapKeyRef :
# The ConfigMap containing the value you want to assign to SPECIAL_LEVEL_KEY
name : special-config
# Specify the key associated with the value
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
将 ConfigMap 中的所有键值对配置为容器环境变量
说明: 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 : k8s.gcr.io/busybox
command : [ "/bin/sh" , "-c" , "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 中的 special.level
。
apiVersion : v1
kind : Pod
metadata :
name : dapi-test-pod
spec :
containers :
- name : test-container
image : k8s.gcr.io/busybox
command : [ "/bin/sh" , "-c" , "ls /etc/config/" ]
volumeMounts :
- name : config-volume
mountPath : /etc/config
volumes :
- name : config-volume
configMap :
# Provide the name of the ConfigMap containing the files you want
# to add to the container
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 : k8s.gcr.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 将自动更新
更新已经在数据卷中使用的 ConfigMap 时,已映射的键最终也会被更新。
kubelet
在每次定期同步时都会检查已挂载的 ConfigMap 是否是最新的。
但是,它使用其本地的基于 TTL 的缓存来获取 ConfigMap 的当前值。
因此,从更新 ConfigMap 到将新键映射到 Pod 的总延迟可能与
kubelet 同步周期 + ConfigMap 在 kubelet 中缓存的 TTL 一样长。
说明: 使用 ConfigMap 作为
subPath
的数据卷将不会收到 ConfigMap 更新。
了解 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(除非将 ConfigMap 标记为"可选")。
如果引用的 ConfigMap 不存在,则 Pod 将不会启动。同样,引用 ConfigMap 中不存在的键也会阻止 Pod 启动。
如果你使用 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 不支持这种用法。
接下来
4.3.19 - 在 Pod 中的容器之间共享进程命名空间
FEATURE STATE: Kubernetes v1.17 [stable]
此页面展示如何为 pod 配置进程命名空间共享。
当启用进程命名空间共享时,容器中的进程对该 pod 中的所有其他容器都是可见的。
您可以使用此功能来配置协作容器,比如日志处理 sidecar 容器,或者对那些不包含诸如 shell 等调试实用工具的镜像进行故障排查。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.10.
要获知版本信息,请输入
kubectl version
.
配置 Pod
进程命名空间共享使用 v1.PodSpec
中的 ShareProcessNamespace
字段启用。例如:
apiVersion : v1
kind : Pod
metadata :
name : nginx
spec :
shareProcessNamespace : true
containers :
- name : nginx
image : nginx
- name : shell
image : busybox
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 回车键。
/ # 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
功能。
/ # kill -HUP 8
/ # 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
链接访问另一个容器镜像。
/ # 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.3.20 - 创建静态 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/kubelet.d
目录来保存 web 服务 Pod 的定义文件,
/etc/kubelet.d/static-web.yaml
:
# 在 kubelet 运行的节点上执行以下命令
mkdir /etc/kubelet.d/
cat <<EOF >/etc/kubelet.d/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/kubelet.d/"
或者在 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 运行的节点上执行以下命令
docker ps
输出可能会像这样:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f6d05272b57e nginx:latest "nginx" 8 minutes ago Up 8 minutes k8s_web.6f802af4_static-web-fk-node1_default_67e24ed9466ba55986d120c867395f3c_378e5f3c
可以在 API 服务上看到镜像 Pod:
NAME READY STATUS RESTARTS AGE
static-web-my-node1 1/1 Running 0 2m
说明: 要确保 kubelet 在 API 服务上有创建镜像 Pod 的权限。如果没有,创建请求会被 API 服务拒绝。
可以看
Pod安全策略 。
静态 Pod 上的标签 被传到镜像 Pod。
你可以通过 选择算符 使用这些标签。
如果你用 kubectl
从 API 服务上删除镜像 Pod,kubelet 不会 移除静态 Pod:
kubectl delete pod static-web-my-node1
pod "static-web-my-node1" deleted
可以看到 Pod 还在运行:
NAME READY STATUS RESTARTS AGE
static-web-my-node1 1/1 Running 0 12s
回到 kubelet 运行的节点上,可以手工停止 Docker 容器。
可以看到过了一段时间后 kubelet 会发现容器停止了并且会自动重启 Pod:
# 在 kubelet 运行的节点上执行以下命令
# 把 ID 换为你的容器的 ID
docker stop f6d05272b57e
sleep 20
docker ps
CONTAINER ID IMAGE COMMAND CREATED ...
5b920cbaf8b1 nginx:latest "nginx -g 'daemon of 2 seconds ago ...
动态增加和删除静态 pod
运行中的 kubelet 会定期扫描配置的目录(比如例子中的 /etc/kubelet.d
目录)中的变化,
并且根据文件中出现/消失的 Pod 来添加/删除 Pod。
# 前提是你在用主机文件系统上的静态 Pod 配置文件
# 在 kubelet 运行的节点上执行以下命令
mv /etc/kubelet.d/static-web.yaml /tmp
sleep 20
docker ps
# 可以看到没有 nginx 容器在运行
mv /tmp/static-web.yaml /etc/kubelet.d/
sleep 20
docker ps
CONTAINER ID IMAGE COMMAND CREATED ...
e7a62e3427f1 nginx:latest "nginx -g 'daemon of 27 seconds ago
4.3.21 - 将 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.24.0/kompose-linux-amd64 -o kompose
# macOS
curl -L https://github.com/kubernetes/kompose/releases/download/v1.24.0/kompose-darwin-amd64 -o kompose
# Windows
curl -L https://github.com/kubernetes/kompose/releases/download/v1.24.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 : k8s.gcr.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 create -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,
输出类似于:
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 [email protected] :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
说明: 如果使用 oc create -f
手动推送 Openshift 工件,则需要确保在构建配置工件之前推送
imagestream 工件,以解决 Openshift 的这个问题:https://github.com/openshift/origin/issues/4518 。
kompose up
Kompose 支持通过 kompose up
直接将你的"复合的(composed)" 应用程序
部署到 Kubernetes 或 OpenShift。
Kubernetes kompose up
示例
kompose --file ./examples/docker-guestbook.yml up
We are going to create Kubernetes deployments and services for your Dockerized application.
If you need different kind of resources, use the 'kompose convert' and 'kubectl create -f' commands instead.
INFO Successfully created service: redis-master
INFO Successfully created service: redis-slave
INFO Successfully created service: frontend
INFO Successfully created deployment: redis-master
INFO Successfully created deployment: redis-slave
INFO Successfully created deployment: frontend
Your application has been deployed to Kubernetes. You can run 'kubectl get deployment,svc,pods' for details.
kubectl get deployment,svc,pods
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.extensions/frontend 1 1 1 1 4m
deployment.extensions/redis-master 1 1 1 1 4m
deployment.extensions/redis-slave 1 1 1 1 4m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/frontend ClusterIP 10.0.174.12 <none> 80/TCP 4m
service/kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 13d
service/redis-master ClusterIP 10.0.202.43 <none> 6379/TCP 4m
service/redis-slave ClusterIP 10.0.1.85 <none> 6379/TCP 4m
NAME READY STATUS RESTARTS AGE
pod/frontend-2768218532-cs5t5 1/1 Running 0 4m
pod/redis-master-1432129712-63jn8 1/1 Running 0 4m
pod/redis-slave-2504961300-nve7b 1/1 Running 0 4m
说明:
你必须有一个运行正常的 Kubernetes 集群,该集群具有预先配置的 kubectl 上下文。
此操作仅生成 Deployment 和 Service 对象并将其部署到 Kubernetes。
如果需要部署其他不同类型的资源,请使用 kompose convert
和 kubectl create -f
命令。
OpenShift kompose up
示例
kompose --file ./examples/docker-guestbook.yml --provider openshift up
We are going to create OpenShift DeploymentConfigs and Services for your Dockerized application.
If you need different kind of resources, use the 'kompose convert' and 'oc create -f' commands instead.
INFO Successfully created service: redis-slave
INFO Successfully created service: frontend
INFO Successfully created service: redis-master
INFO Successfully created deployment: redis-slave
INFO Successfully created ImageStream: redis-slave
INFO Successfully created deployment: frontend
INFO Successfully created ImageStream: frontend
INFO Successfully created deployment: redis-master
INFO Successfully created ImageStream: redis-master
Your application has been deployed to OpenShift. You can run 'oc get dc,svc,is' for details.
NAME REVISION DESIRED CURRENT TRIGGERED BY
dc/frontend 0 1 0 config,image(frontend:v4)
dc/redis-master 0 1 0 config,image(redis-master:e2e)
dc/redis-slave 0 1 0 config,image(redis-slave:v1)
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
svc/frontend 172.30.46.64 <none> 80/TCP 8s
svc/redis-master 172.30.144.56 <none> 6379/TCP 8s
svc/redis-slave 172.30.75.245 <none> 6379/TCP 8s
NAME DOCKER REPO TAGS UPDATED
is/frontend 172.30.12.200:5000/fff/frontend
is/redis-master 172.30.12.200:5000/fff/redis-master
is/redis-slave 172.30.12.200:5000/fff/redis-slave v1
说明:
你必须有一个运行正常的 OpenShift 集群,该集群具有预先配置的 oc
上下文 (oc login
)。
kompose down
你一旦将"复合(composed)" 应用部署到 Kubernetes,kompose down
命令将能帮你通过删除 Deployment 和 Service 对象来删除应用。
如果需要删除其他资源,请使用 'kubectl' 命令。
kompose --file docker-guestbook.yml down
INFO Successfully deleted service: redis-master
INFO Successfully deleted deployment: redis-master
INFO Successfully deleted service: redis-slave
INFO Successfully deleted deployment: redis-slave
INFO Successfully deleted service: frontend
INFO Successfully deleted deployment: frontend
说明:
你必须有一个运行正常的 Kubernetes 集群,该集群具有预先配置的 kubectl 上下文。
构建和推送 Docker 镜像
Kompose 支持构建和推送 Docker 镜像。如果 Docker Compose 文件中使用了 build
关键字,你的镜像将会:
使用文档中指定的 image
键自动构建 Docker 镜像
使用本地凭据推送到正确的 Docker 仓库
使用 Docker Compose 文件示例
version : "2"
services :
foo :
build : "./build"
image : docker.io/foo/bar
使用带有 build
键的 kompose up
命令:
INFO Build key detected. Attempting to build and push image 'docker.io/foo/bar'
INFO Building image 'docker.io/foo/bar' from directory 'build'
INFO Image 'docker.io/foo/bar' from directory 'build' built successfully
INFO Pushing image 'foo/bar:latest' to registry 'docker.io'
INFO Attempting authentication credentials 'https://index.docker.io/v1/
INFO Successfully pushed image 'foo/bar:latest' to registry 'docker.io'
INFO We are going to create Kubernetes Deployments, Services and PersistentVolumeClaims for your Dockerized application. If you need different kind of resources, use the 'kompose convert' and 'kubectl create -f' commands instead.
INFO Deploying application in "default" namespace
INFO Successfully created Service: foo
INFO Successfully created Deployment: foo
Your application has been deployed to Kubernetes. You can run 'kubectl get deployment,svc,pods,pvc' for details.
要想禁用该功能,或者使用 BuildConfig 中的版本(在 OpenShift 中),
可以通过传递 --build (local|build-config|none)
参数来实现。
# 禁止构造和推送 Docker 镜像
kompose up --build none
# 为 OpenShift 生成 Build Config 工件
kompose up --provider openshift --build build-config
其他转换方式
默认的 kompose
转换会生成 yaml 格式的 Kubernetes
Deployment 和
Service 对象。
你可以选择通过 -j
参数生成 json 格式的对象。
你也可以替换生成 Replication Controllers 对象、
Daemon Sets 或
Helm charts。
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 - 管理 Kubernetes 对象
用声明式和命令式范型与 Kubernetes API 交互。
4.4.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
# ...
# ...
# ...
# ...
通过 kubeclt 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 # update the image
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 # update the image
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.4.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-8mbdf7882g
说明: .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
与 ConfigMaps 一样,生成的 Secrets 可以通过引用 secretGenerator 的名称在部署中使用:
# 创建一个 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.4.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.4.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.4.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 create -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-containers.yaml
的文件。内容如下:
spec :
template :
spec :
containers :
- name : patch-demo-ctr-2
image : redis
修补你的 Deployment:
kubectl patch deployment patch-demo --patch " $( cat patch-file-containers.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,并创建了两个
符合更新的部署规范的新 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 spec
规范中看到 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 "$(cat patch-file-containers.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
...
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 " $( cat 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 merge --patch " $( cat patch-file-no-retainkeys.yaml) "
kubectl patch deployment retainkeys-demo --type merge --patch $(Get-Content patch-file -no-retainkeys.yaml -Raw)
在输出中,你可以看到,当 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 " $( cat patch-file-retainkeys.yaml) "
kubectl patch deployment retainkeys-demo --type merge --patch $(Get-Content patch-file -retainkeys.yaml -Raw)
检查 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 命令的其他形式
kubectl patch
命令使用 YAML 或 JSON。它可以接受以文件形式提供的补丁,也可以
接受直接在命令行中给出的补丁。
创建一个文件名称是 patch-file.json
内容如下:
{
"spec" : {
"template" : {
"spec" : {
"containers" : [
{
"name" : "patch-demo-ctr-2" ,
"image" : "redis"
}
]
}
}
}
}
以下命令是等价的:
kubectl patch deployment patch-demo --patch " $( cat 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 " $( cat patch-file.json) "
kubectl patch deployment patch-demo --patch '{"spec": {"template": {"spec": {"containers": [{"name": "patch-demo-ctr-2","image": "redis"}]}}}}'
总结
在本练习中,你使用 kubectl patch
更改了 Deployment 对象的当前配置。
你没有更改最初用于创建 Deployment 对象的配置文件。
用于更新 API 对象的其他命令包括
kubectl annotate
,
kubectl edit
,
kubectl replace
,
kubectl scale
,
和
kubectl apply
。
说明: 定制资源不支持策略性合并 patch。
接下来
4.5 - 管理 Secrets
使用 Secrets 管理机密配置数据。
4.5.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 的内容,运行以下命令:
kubectl get secret db-user-pass -o jsonpath = '{.data}'
输出类似于:
{"password" :"MWYyZDFlMmU2N2Rm" ,"username" :"YWRtaW4=" }
现在你可以解码 password
的数据:
echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
输出类似于:
1f2d1e2e67df
清理
删除创建的 Secret:
kubectl delete secret db-user-pass
接下来
4.5.2 - 使用配置文件管理 Secret
使用资源配置文件创建 Secret 对象。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
创建配置文件
你可以先用 JSON 或 YAML 格式在文件中创建 Secret,然后创建该对象。
Secret
资源包含2个键值对: data
和 stringData
。
data
字段用来存储 base64 编码的任意数据。
提供 stringData
字段是为了方便,它允许 Secret 使用未编码的字符串。
data
和 stringData
的键必须由字母、数字、-
,_
或 .
组成。
例如,要使用 Secret 的 data
字段存储两个字符串,请将字符串转换为 base64 ,如下所示:
输出类似于:
YWRtaW4=
echo -n '1f2d1e2e67df' | base64
输出类似于:
MWYyZDFlMmU2N2Rm
编写一个 Secret 配置文件,如下所示:
apiVersion : v1
kind : Secret
metadata :
name : mysecret
type : Opaque
data :
username : YWRtaW4=
password : MWYyZDFlMmU2N2Rm
注意,Secret 对象的名称必须是有效的 DNS 子域名 .
说明:
Secret 数据的 JSON 和 YAML 序列化结果是以 base64 编码的。
换行符在这些字符串中无效,必须省略。
在 Darwin/macOS 上使用 base64
工具时,用户不应该使用 -b
选项分割长行。
相反地,Linux 用户 应该 在 base64
地命令中添加 -w 0
选项,
或者在 -w
选项不可用的情况下,输入 base64 | tr -d '\n'
。
对于某些场景,你可能希望使用 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 对象
现在使用 kubectl apply
创建 Secret:
kubectl apply -f ./secret.yaml
输出类似于:
secret/mysecret created
检查 Secret
stringData
字段是只写的。获取 Secret 时,此字段永远不会输出。
例如,如果你运行以下命令:
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 : Opaque
命令 kubectl get
和 kubectl describe
默认不显示 Secret
的内容。
这是为了防止 Secret
意外地暴露给旁观者或者保存在终端日志中。
检查编码数据的实际内容,请参考解码 secret .
如果在 data
和 stringData
中都指定了一个字段,比如 username
,字段值来自 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:
kubectl delete secret mysecret
接下来
4.5.3 - 使用 Kustomize 管理 Secret
使用 kustomization.yaml 文件创建 Secret 对象。
从 kubernetes v1.14 开始,kubectl
支持使用 Kustomize 管理对象 。
Kustomize 提供了资源生成器(Generators)来创建 Secret 和 ConfigMap。
Kustomize 生成器应该在某个目录的 kustomization.yaml
文件中指定。
生成 Secret 后,你可以使用 kubectl apply
在 API 服务器上创建该 Secret。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
创建 Kustomization 文件
你可以在 kustomization.yaml
中定义 secreteGenerator
,并在定义中引用其他现成的文件,生成 Secret。
例如:下面的 kustomization 文件 引用了 ./username.txt
和 ./password.txt
文件:
secretGenerator :
- name : db-user-pass
files :
- username.txt
- password.txt
你也可以在 kustomization.yaml
文件中指定一些字面量定义 secretGenerator
。
例如:下面的 kustomization.yaml
文件中包含了 username
和 password
两个字面量:
secretGenerator :
- name : db-user-pass
literals :
- username=admin
- password=1f2d1e2e67df
你也可以使用 .env
文件在 kustomization.yaml
中定义 secretGenerator
。
例如:下面的 kustomization.yaml
文件从 .env.secret
文件获取数据。
secretGenerator :
- name : db-user-pass
envs :
- .env.secret
注意,上面两种情况,你都不需要使用 base64 编码。
创建 Secret
使用 kubectl apply
命令应用包含 kustomization.yaml
文件的目录创建 Secret。
输出类似于:
secret/db-user-pass-96mffmfh4k created
请注意,生成 Secret 时,Secret 的名称最终是由 name
字段和数据的哈希值拼接而成。
这将保证每次修改数据时生成一个新的 Secret。
检查创建的 Secret
你可以检查刚才创建的 Secret:
输出类似于:
NAME TYPE DATA AGE
db-user-pass-96mffmfh4k Opaque 2 51s
你可以看到 Secret 的描述:
kubectl describe secrets/db-user-pass-96mffmfh4k
输出类似于:
Name: db-user-pass-96mffmfh4k
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password.txt: 12 bytes
username.txt: 5 bytes
kubectl get
和 kubectl describe
命令默认不显示 Secret
的内容。
这是为了防止 Secret
被意外暴露给旁观者或存储在终端日志中。
检查编码后的实际内容,请参考解码 secret 。
-->
清理
删除你创建的 Secret:
kubectl delete secret db-user-pass-96mffmfh4k
接下来
4.6 - 给应用注入数据
给你的工作负载 Pod 指定配置和其他数据。
4.6.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
获取正在运行的 Pods:
查询结果显示在 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)" ]
这意味着你可以将那些用来设置环境变量的方法应用于设置命令的参数,其中包括了
ConfigMaps 与
Secrets 。
说明: 环境变量需要加上括号,类似于 "$(VAR)"
。这是在 command
或 args
字段使用变量的格式要求。
在 Shell 来执行命令
有时候,你需要在 Shell 脚本中运行命令。
例如,你要执行的命令可能由多个命令组合而成,或者它就是一个 Shell 脚本。
这时,就可以通过如下方式在 Shell 中执行命令:
command: [ "/bin/sh" ]
args: [ "-c" , "while true; do echo hello; sleep 10;done" ]
说明事项
下表给出了 Docker 与 Kubernetes 中对应的字段名称。
描述
Docker 字段名称
Kubernetes 字段名称
容器执行的命令
Entrypoint
command
传给命令的参数
Cmd
args
如果要覆盖默认的 Entrypoint 与 Cmd,需要遵循如下规则:
如果在容器配置中没有设置 command
或者 args
,那么将使用 Docker 镜像自带的命令及其参数。
如果在容器配置中只设置了 command
但是没有设置 args
,那么容器启动时只会执行该命令,
Docker 镜像中自带的命令及其参数会被忽略。
如果在容器配置中只设置了 args
,那么 Docker 镜像中自带的命令会使用该新参数作为其执行时的参数。
如果在容器配置中同时设置了 command
与 args
,那么 Docker 镜像中自带的命令及其参数会被忽略。
容器启动时只会执行配置中设置的命令,并使用配置中设置的参数作为命令的参数。
下面是一些例子:
镜像 Entrypoint
镜像 Cmd
容器 command
容器 args
命令执行
[/ep-1]
[foo bar]
<not set>
<not set>
[ep-1 foo bar]
[/ep-1]
[foo bar]
[/ep-2]
<not set>
[ep-2]
[/ep-1]
[foo bar]
<not set>
[zoo boo]
[ep-1 zoo boo]
[/ep-1]
[foo bar]
[/ep-2]
[zoo boo]
[ep-2 zoo boo]
接下来
4.6.2 - 为容器设置环境变量
本页将展示如何为 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
获取一下当前正在运行的 Pods 信息:
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.6.3 - 定义相互依赖的环境变量
本页展示了如何为 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
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
的例子。
当环境变量未定义或仅包含部分变量时,未定义的变量会被当做普通字符串对待,
比如 UNCHANGED_REFERENCE
的例子。
注意,解析不正确的环境变量通常不会阻止容器启动。
$(VAR_NAME)
这样的语法可以用两个 $
转义,既:$$(VAR_NAME)
。
无论引用的变量是否定义,转义的引用永远不会展开。
这一点可以从上面 ESCAPED_REFERENCE
的例子得到印证。
接下来
4.6.4 - 通过环境变量将 Pod 信息呈现给容器
此页面展示 Pod 如何使用环境变量把自己的信息呈现给 Pod 中运行的容器。
环境变量可以呈现 Pod 的字段和容器字段。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
Downward API
有两种方式可以将 Pod 和 Container 字段呈现给运行中的容器:
这两种呈现 Pod 和 Container 字段的方式统称为 Downward API 。
用 Pod 字段作为环境变量的值
在这个练习中,你将创建一个包含一个容器的 Pod。这是该 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : dapi-envars-fieldref
spec :
containers :
- name : test-container
image : k8s.gcr.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
字段是一个
EnvVars .
对象的数组。
数组中第一个元素指定 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 中的容器运行正常:
kubectl get pods
查看容器日志:
kubectl logs dapi-envars-fieldref
输出信息显示了所选择的环境变量的值:
minikube
dapi-envars-fieldref
default
172.17.0.4
default
要了解为什么这些值在日志中,请查看配置文件中的command
和 args
字段。
当容器启动时,它将五个环境变量的值写入 stdout。每十秒重复执行一次。
接下来,通过打开一个 Shell 进入 Pod 中运行的容器:
kubectl exec -it dapi-envars-fieldref -- sh
在 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
用 Container 字段作为环境变量的值
前面的练习中,你将 Pod 字段作为环境变量的值。
接下来这个练习中,你将用 Container 字段作为环境变量的值。这里是包含一个容器的 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : dapi-envars-resourcefieldref
spec :
containers :
- name : test-container
image : k8s.gcr.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
字段是一个
EnvVars .
对象的数组。数组中第一个元素指定 MY_CPU_REQUEST
这个环境变量从 Container 的 requests.cpu
字段获取变量值。同样,其它环境变量也是从 Container 的字段获取它们的变量值。
说明: 本例中使用的是 Container 的字段而不是 Pod 的字段。
创建Pod:
kubectl apply -f https://k8s.io/examples/pods/inject/dapi-envars-container.yaml
验证 Pod 中的容器运行正常:
kubectl get pods
查看容器日志:
kubectl logs dapi-envars-resourcefieldref
输出信息显示了所选择的环境变量的值:
1
1
33554432
67108864
接下来
4.6.5 - 通过文件将 Pod 信息呈现给容器
此页面描述 Pod 如何使用 DownwardAPIVolumeFile 把自己的信息呈现给 Pod 中运行的容器。
DownwardAPIVolumeFile 可以呈现 Pod 的字段和容器字段。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
Downward API
有两种方式可以将 Pod 和 Container 字段呈现给运行中的容器:
这两种呈现 Pod 和 Container 字段的方式都称为 Downward API 。
存储 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 : k8s.gcr.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 有一个 downwardAPI
类型的卷,并且挂载到容器中的
/etc/podinfo
目录。
查看 downwardAPI
下面的 items
数组。
每个数组元素都是一个
DownwardAPIVolumeFile
对象。
第一个元素指示 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/podinfo/..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:
存储容器字段
前面的练习中,你将 Pod 字段保存到 DownwardAPIVolumeFile 中。
接下来这个练习,你将存储 Container 字段。这里是包含一个容器的 Pod 的配置文件:
apiVersion : v1
kind : Pod
metadata :
name : kubernetes-downwardapi-volume-example-2
spec :
containers :
- name : client-container
image : k8s.gcr.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
类型的卷,并且挂载到容器的
/etc/podinfo
目录。
查看 downwardAPI
下面的 items
数组。每个数组元素都是一个 DownwardAPIVolumeFile。
第一个元素指定名为 client-container
的容器中 limits.cpu
字段的值应保存在名为
cpu_limit
的文件中。
创建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
文件:
/# cat /etc/podinfo/cpu_limit
你可以使用同样的命令查看 cpu_request
、mem_limit
和 mem_request
文件.
Downward API 的能力
下面这些信息可以通过环境变量和 downwardAPI
卷提供给容器:
能通过 fieldRef
获得的:
metadata.name
- Pod 名称
metadata.namespace
- Pod 名字空间
metadata.uid
- Pod 的 UID
metadata.labels['<KEY>']
- Pod 标签 <KEY>
的值 (例如, metadata.labels['mylabel']
)
metadata.annotations['<KEY>']
- Pod 的注解 <KEY>
的值(例如, metadata.annotations['myannotation']
)
能通过 resourceFieldRef
获得的:
容器的 CPU 约束值
容器的 CPU 请求值
容器的内存约束值
容器的内存请求值
容器的巨页限制值(前提是启用了 DownwardAPIHugePages
特性门控 )
容器的巨页请求值(前提是启用了 DownwardAPIHugePages
特性门控 )
容器的临时存储约束值
容器的临时存储请求值
此外,以下信息可通过 downwardAPI
卷从 fieldRef
获得:
metadata.labels
- Pod 的所有标签,以 label-key="escaped-label-value"
格式显示,每行显示一个标签
metadata.annotations
- Pod 的所有注解,以 annotation-key="escaped-annotation-value"
格式显示,每行显示一个标签
以下信息可通过环境变量获得:
status.podIP
- 节点 IP
spec.serviceAccountName
- Pod 服务帐号名称, 版本要求 v1.4.0-alpha.3
spec.nodeName
- 节点名称, 版本要求 v1.4.0-alpha.3
status.hostIP
- 节点 IP, 版本要求 v1.7.0-alpha.1
说明: 如果容器未指定 CPU 和内存限制,则 Downward API 默认将节点可分配值
视为容器的 CPU 和内存限制。
投射键名到指定路径并且指定文件权限
你可以将键名投射到指定路径并且指定每个文件的访问权限。
更多信息,请参阅Secrets .
Downward API的动机
对于容器来说,有时候拥有自己的信息是很有用的,可避免与 Kubernetes 过度耦合。
Downward API 使得容器使用自己或者集群的信息,而不必通过 Kubernetes 客户端或
API 服务器来获得。
一个例子是有一个现有的应用假定要用一个非常熟悉的环境变量来保存一个唯一标识。
一种可能是给应用增加处理层,但这样是冗余和易出错的,而且它违反了低耦合的目标。
更好的选择是使用 Pod 名称作为标识,把 Pod 名称注入这个环境变量中。
接下来
4.6.6 - 使用 Secret 安全地分发凭证
本文展示如何安全地将敏感数据(如密码和加密密钥)注入到 Pods 中。
准备开始
你必须拥有一个 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 must match the volume name below
- name : secret-volume
mountPath : /etc/secret-volume
# The secret data is exposed to Containers in the Pod through a Volume.
volumes :
- name : secret-volume
secret :
secretName : test-secret
创建 Pod:
kubectl create -f 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 -it secret-test-pod -- /bin/bash
Secret 数据通过挂载在 /etc/secret-volume
目录下的卷暴露在容器中。
在 shell 中,列举 /etc/secret-volume
目录下的文件:
输出包含两个文件,每个对应一个 Secret 数据条目:
password username
在 Shell 中,显示 username
和 password
文件的内容:
# 在容器中 Shell 运行下面命令
echo " $( cat /etc/secret-volume/username) "
echo " $( cat /etc/secret-volume/password) "
输出为用户名和密码:
使用 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
将 Secret 中的所有键值偶对定义为环境变量
说明: 此功能在 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.7 - 运行应用
运行和管理无状态和有状态的应用程序。
4.7.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 # tells deployment to run 2 pods matching the template
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 创建的 Pods:
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 # Update the version of nginx from 1.14.2 to 1.16.1
ports :
- containerPort : 80
应用新的 YAML:
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
查看该 Deployment 以新的名称创建 Pods 同时删除旧的 Pods:
kubectl get pods -l app = nginx
通过增加副本数来扩缩应用
你可以通过应用新的 YAML 文件来增加 Deployment 中 Pods 的数量。
下面的 YAML 文件将 replicas
设置为 4,指定该 Deployment 应有 4 个 Pods:
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx-deployment
spec :
selector :
matchLabels :
app : nginx
replicas : 4 # Update the replicas from 2 to 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 个 Pods:
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
ReplicationControllers -- 旧的方式
创建一个多副本应用首选方法是使用 Deployment,Deployment 内部使用 ReplicaSet。
在 Deployment 和 ReplicaSet 被引入到 Kubernetes 之前,多副本应用通过
ReplicationController
来配置。
接下来
4.7.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 :
# Use secret in real usage
- 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.7.3 - 运行一个有状态的应用程序
本页展示如何使用 StatefulSet
控制器运行一个有状态的应用程序。此例是多副本的 MySQL 数据库。
示例应用的拓扑结构有一个主服务器和多个副本,使用异步的基于行(Row-Based)
的数据复制。
说明: 这不是生产环境下配置 。
尤其注意,MySQL 设置都使用的是不安全的默认值,这是因为我们想把重点放在 Kubernetes
中运行有状态应用程序的一般模式上。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
您需要有一个带有默认StorageClass 的动态持续卷供应程序,或者自己静态的提供持久卷 来满足这里使用的持久卷请求 。
教程目标
使用 StatefulSet 控制器部署多副本 MySQL 拓扑架构。
发送 MySQL 客户端请求
观察对宕机的抵抗力
扩缩 StatefulSet 的规模
部署 MySQL
MySQL 示例部署包含一个 ConfigMap、两个 Service 与一个 StatefulSet。
ConfigMap
使用以下的 YAML 配置文件创建 ConfigMap :
apiVersion : v1
kind : ConfigMap
metadata :
name : mysql
labels :
app : mysql
data :
master.cnf : |
# Apply this config only on the master.
[mysqld]
log-bin
slave.cnf : |
# Apply this config only on slaves.
[mysqld]
super-read-only
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
这个 ConfigMap 提供 my.cnf
覆盖设置,使你可以独立控制 MySQL 主服务器和从服务器的配置。
在这里,你希望主服务器能够将复制日志提供给副本服务器,并且希望副本服务器拒绝任何不是通过
复制进行的写操作。
ConfigMap 本身没有什么特别之处,因而也不会出现不同部分应用于不同的 Pod 的情况。
每个 Pod 都会在初始化时基于 StatefulSet 控制器提供的信息决定要查看的部分。
服务
使用以下 YAML 配置文件创建服务:
# Headless service for stable DNS entries of StatefulSet members.
apiVersion : v1
kind : Service
metadata :
name : mysql
labels :
app : mysql
spec :
ports :
- name : mysql
port : 3306
clusterIP : None
selector :
app : mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the master: mysql-0.mysql.
apiVersion : v1
kind : Service
metadata :
name : mysql-read
labels :
app : mysql
spec :
ports :
- name : mysql
port : 3306
selector :
app : mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
这个无头服务给 StatefulSet 控制器为集合中每个 Pod 创建的 DNS 条目提供了一个宿主。
因为服务名为 mysql
,所以可以通过在同一 Kubernetes 集群和名字中的任何其他 Pod
内解析 <Pod 名称>.mysql
来访问 Pod。
客户端服务称为 mysql-read
,是一种常规服务,具有其自己的集群 IP。
该集群 IP 在报告就绪的所有MySQL Pod 之间分配连接。
可能的端点集合包括 MySQL 主节点和所有副本节点。
请注意,只有读查询才能使用负载平衡的客户端服务。
因为只有一个 MySQL 主服务器,所以客户端应直接连接到 MySQL 主服务器 Pod
(通过其在无头服务中的 DNS 条目)以执行写入操作。
StatefulSet
最后,使用以下 YAML 配置文件创建 StatefulSet:
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : mysql
spec :
selector :
matchLabels :
app : mysql
serviceName : mysql
replicas : 3
template :
metadata :
labels :
app : mysql
spec :
initContainers :
- name : init-mysql
image : mysql:5.7
command :
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
cp /mnt/config-map/slave.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
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Skip the clone on master (ordinal index 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone data from previous peer.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare the backup.
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 :
# Check we can execute queries over TCP (skip-networking is off).
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
# Determine binlog position of cloned data, if any.
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# XtraBackup already generated a partial "CHANGE MASTER TO" query
# because we're cloning from an existing slave. (Need to remove the tailing semicolon!)
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# Ignore xtrabackup_binlog_info in this case (it's useless).
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# We're cloning directly from master. Parse binlog position.
[[ `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
# Check if we need to complete a clone by starting replication.
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
# In case of container restart, attempt this at-most-once.
mv change_master_to.sql.in change_master_to.sql.orig
fi
# Start a server to send backups when requested by peers.
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 结束 watch 操作。
如果你看不到任何进度,确保已启用前提条件
中提到的动态 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 容器 。
第一个名为 init-mysql
的 Init 容器根据序号索引生成特殊的 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
的 Init 容器,第一次在带有空 PersistentVolume 的副本 Pod
上启动时,会在从属 Pod 上执行克隆操作。
这意味着它将从另一个运行中的 Pod 复制所有现有数据,使此其本地状态足够一致,
从而可以开始从主服务器复制。
MySQL 本身不提供执行此操作的机制,因此本示例使用了一种流行的开源工具 Percona XtraBackup。
在克隆期间,源 MySQL 服务器性能可能会受到影响。
为了最大程度地减少对 MySQL 主服务器的影响,该脚本指示每个 Pod 从序号较低的 Pod 中克隆。
可以这样做的原因是 StatefulSet 控制器始终确保在启动 Pod N + 1 之前 Pod N 已准备就绪。
开始复制
Init 容器成功完成后,应用容器将运行。
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。将 <节点名称>
替换为在上一步中找到的节点名称。
这可能会影响节点上的其他应用程序,因此最好 仅在测试集群中执行此操作 。
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
但是请注意,按比例扩大会自动创建新的 PersistentVolumeClaims,而按比例缩小不会自动删除这些 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、Services 和 PersistentVolumeClaims。
kubectl delete configmap,service,pvc -l app = mysql
如果你手动供应 PersistentVolume,则还需要手动删除它们,并释放下层资源。
如果你使用了动态预配器,当得知你删除 PersistentVolumeClaims 时,它将自动删除 PersistentVolumes。
一些动态预配器(例如用于 EBS 和 PD 的预配器)也会在删除 PersistentVolumes 时释放下层资源。
接下来
4.7.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=myapp
,则可以按照
如下方式删除它们:
kubectl delete pods -l app = 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 = myapp
sleep $grace
kubectl delete pvc -l app = myapp
在上面的例子中,Pod 的标签为 app=myapp
;适当地替换你自己的标签。
强制删除 StatefulSet 的 Pod
如果你发现 StatefulSet 的某些 Pod 长时间处于 'Terminating' 或者 'Unknown' 状态,
则可能需要手动干预以强制从 API 服务器中删除这些 Pod。
这是一项有点危险的任务。详细信息请阅读
删除 StatefulSet 类型的 Pods 。
接下来
进一步了解强制删除 StatefulSet 的 Pods 。
4.7.5 - 强制删除 StatefulSet 中的 Pods
本文介绍如何删除 StatefulSet
管理的 Pods,并解释这样操作时需要记住的注意事项。
准备开始
这是一项相当高级的任务,并且可能会违反 StatefulSet 固有的某些属性。
继续任务之前,请熟悉下面列举的注意事项。
StatefulSet 注意事项
在 StatefulSet 的正常操作中,永远不 需要强制删除 StatefulSet 管理的 Pod。
StatefulSet 控制器 负责创建、
扩缩和删除 StatefulSet 管理的 Pods。它尝试确保指定数量的从序数 0 到 N-1 的 Pod
处于活跃状态并准备就绪。StatefulSet 确保在任何时候,集群中最多只有一个具有给定标识的 Pod。
这就是所谓的由 StatefulSet 提供的*最多一个(At Most One)*的语义。
应谨慎进行手动强制删除操作,因为它可能会违反 StatefulSet 固有的至多一个的语义。
StatefulSets 可用于运行分布式和集群级的应用,这些应用需要稳定的网络标识和可靠的存储。
这些应用通常配置为具有固定标识固定数量的成员集合。
具有相同身份的多个成员可能是灾难性的,并且可能导致数据丢失 (例如:票选系统中的脑裂场景)。
删除 Pods
你可以使用下面的命令执行体面地删除 Pod:
kubectl delete pods <pod>
为了让上面操作能够体面地终止 Pod,Pod 一定不能 设置 pod.Spec.TerminationGracePeriodSeconds
为 0。
将 pod.Spec.TerminationGracePeriodSeconds
设置为 0s 的做法是不安全的,强烈建议 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 类型的 pods,并完全了解所涉及地风险。
接下来
进一步了解调试 StatefulSet 。
4.7.6 - Pod 水平自动扩缩
Pod 水平自动扩缩(Horizontal Pod Autoscaler)
可以基于 CPU 利用率自动扩缩 ReplicationController、Deployment、ReplicaSet 和
StatefulSet 中的 Pod 数量。
除了 CPU 利用率,也可以基于其他应程序提供的
自定义度量指标
来执行自动扩缩。
Pod 自动扩缩不适用于无法扩缩的对象,比如 DaemonSet。
Pod 水平自动扩缩特性由 Kubernetes API 资源和控制器实现。资源决定了控制器的行为。
控制器会周期性地调整副本控制器或 Deployment 中的副本数量,以使得类似 Pod 平均 CPU
利用率、平均内存利用率这类观测到的度量值与用户所设定的目标值匹配。
Pod 水平自动扩缩工作机制
Pod 水平自动扩缩器的实现是一个控制回路,由控制器管理器的 --horizontal-pod-autoscaler-sync-period
参数指定周期(默认值为 15 秒)。
每个周期内,控制器管理器根据每个 HorizontalPodAutoscaler 定义中指定的指标查询资源利用率。
控制器管理器可以从资源度量指标 API(按 Pod 统计的资源用量)和自定义度量指标
API(其他指标)获取度量值。
对于按 Pod 统计的资源指标(如 CPU),控制器从资源指标 API 中获取每一个
HorizontalPodAutoscaler 指定的 Pod 的度量值,如果设置了目标使用率,
控制器获取每个 Pod 中的容器资源使用情况,并计算资源使用率。
如果设置了 target 值,将直接使用原始数据(不再计算百分比)。
接下来,控制器根据平均的资源使用率或原始值计算出扩缩的比例,进而计算出目标副本数。
需要注意的是,如果 Pod 某些容器不支持资源采集,那么控制器将不会使用该 Pod 的 CPU 使用率。
下面的算法细节 章节将会介绍详细的算法。
如果 Pod 使用自定义指示,控制器机制与资源指标类似,区别在于自定义指标只使用
原始值,而不是使用率。
如果 Pod 使用对象指标和外部指标(每个指标描述一个对象信息)。
这个指标将直接根据目标设定值相比较,并生成一个上面提到的扩缩比例。
在 autoscaling/v2beta2
版本 API 中,这个指标也可以根据 Pod 数量平分后再计算。
通常情况下,控制器将从一系列的聚合 API(metrics.k8s.io
、custom.metrics.k8s.io
和 external.metrics.k8s.io
)中获取度量值。
metrics.k8s.io
API 通常由 Metrics 服务器(需要额外启动)提供。
可以从 metrics-server 获取更多信息。
另外,控制器也可以直接从 Heapster 获取指标。
说明:
FEATURE STATE: Kubernetes 1.11 [deprecated]
自 Kubernetes 1.11 起,从 Heapster 获取指标特性已废弃。
关于指标 API 更多信息,请参考度量值指标 API 的支持 。
自动扩缩控制器使用 scale 子资源访问相应可支持扩缩的控制器(如副本控制器、
Deployment 和 ReplicaSet)。
scale
是一个可以动态设定副本数量和检查当前状态的接口。
关于 scale 子资源的更多信息,请参考这里 .
算法细节
从最基本的角度来看,Pod 水平自动扩缩控制器根据当前指标和期望指标来计算扩缩比例。
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
例如,当前度量值为 200m
,目标设定值为 100m
,那么由于 200.0/100.0 == 2.0
,
副本数量将会翻倍。
如果当前指标为 50m
,副本数量将会减半,因为50.0/100.0 == 0.5
。
如果计算出的扩缩比例接近 1.0
(根据--horizontal-pod-autoscaler-tolerance
参数全局配置的容忍值,默认为 0.1),
将会放弃本次扩缩。
如果 HorizontalPodAutoscaler 指定的是 targetAverageValue
或 targetAverageUtilization
,
那么将会把指定 Pod 度量值的平均值做为 currentMetricValue
。
然而,在检查容忍度和决定最终扩缩值前,我们仍然会把那些无法获取指标的 Pod 统计进去。
所有被标记了删除时间戳(Pod 正在关闭过程中)的 Pod 和失败的 Pod 都会被忽略。
如果某个 Pod 缺失度量值,它将会被搁置,只在最终确定扩缩数量时再考虑。
当使用 CPU 指标来扩缩时,任何还未就绪(例如还在初始化)状态的 Pod 或 最近的指标
度量值采集于就绪状态前的 Pod,该 Pod 也会被搁置。
由于受技术限制,Pod 水平扩缩控制器无法准确的知道 Pod 什么时候就绪,
也就无法决定是否暂时搁置该 Pod。
--horizontal-pod-autoscaler-initial-readiness-delay
参数(默认为 30s)用于设置 Pod 准备时间,
在此时间内的 Pod 统统被认为未就绪。
--horizontal-pod-autoscaler-cpu-initialization-period
参数(默认为5分钟)
用于设置 Pod 的初始化时间,
在此时间内的 Pod,CPU 资源度量值将不会被采纳。
在排除掉被搁置的 Pod 后,扩缩比例就会根据 currentMetricValue/desiredMetricValue
计算出来。
如果缺失任何的度量值,我们会更保守地重新计算平均值,
在需要缩小时假设这些 Pod 消耗了目标值的 100%,
在需要放大时假设这些 Pod 消耗了 0% 目标值。
这可以在一定程度上抑制扩缩的幅度。
此外,如果存在任何尚未就绪的 Pod,我们可以在不考虑遗漏指标或尚未就绪的 Pod 的情况下进行扩缩,
我们保守地假设尚未就绪的 Pod 消耗了期望指标的 0%,从而进一步降低了扩缩的幅度。
在扩缩方向(缩小或放大)确定后,我们会把未就绪的 Pod 和缺少指标的 Pod 考虑进来再次计算使用率。
如果新的比率与扩缩方向相反,或者在容忍范围内,则跳过扩缩。
否则,我们使用新的扩缩比例。
注意,平均利用率的原始 值会通过 HorizontalPodAutoscaler 的状态体现(
即使使用了新的使用率,也不考虑未就绪 Pod 和 缺少指标的 Pod)。
如果创建 HorizontalPodAutoscaler 时指定了多个指标,
那么会按照每个指标分别计算扩缩副本数,取最大值进行扩缩。
如果任何一个指标无法顺利地计算出扩缩副本数(比如,通过 API 获取指标时出错),
并且可获取的指标建议缩容,那么本次扩缩会被跳过。
这表示,如果一个或多个指标给出的 desiredReplicas
值大于当前值,HPA 仍然能实现扩容。
最后,在 HPA 控制器执行扩缩操作之前,会记录扩缩建议信息。
控制器会在操作时间窗口中考虑所有的建议信息,并从中选择得分最高的建议。
这个值可通过 kube-controller-manager
服务的启动参数
--horizontal-pod-autoscaler-downscale-stabilization
进行配置,
默认值为 5 分钟。
这个配置可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
API 对象
HorizontalPodAutoscaler 是 Kubernetes autoscaling
API 组的资源。
在当前稳定版本(autoscaling/v1
)中只支持基于 CPU 指标的扩缩。
API 的 beta 版本(autoscaling/v2beta2
)引入了基于内存和自定义指标的扩缩。
在 autoscaling/v2beta2
版本中新引入的字段在 autoscaling/v1
版本中以注解
的形式得以保留。
创建 HorizontalPodAutoscaler 对象时,需要确保所给的名称是一个合法的
DNS 子域名 。
有关 API 对象的更多信息,请查阅
HorizontalPodAutoscaler 对象设计文档 。
kubectl 对 Horizontal Pod Autoscaler 的支持
与其他 API 资源类似,kubectl
以标准方式支持 HPA。
我们可以通过 kubectl create
命令创建一个 HPA 对象,
通过 kubectl get hpa
命令来获取所有 HPA 对象,
通过 kubectl describe hpa
命令来查看 HPA 对象的详细信息。
最后,可以使用 kubectl delete hpa
命令删除对象。
此外,还有个简便的命令 kubectl autoscale
来创建 HPA 对象。
例如,命令 kubectl autoscale rs foo --min=2 --max=5 --cpu-percent=80
将会为名
为 foo 的 ReplicationSet 创建一个 HPA 对象,
目标 CPU 使用率为 80%
,副本数量配置为 2 到 5 之间。
滚动升级时扩缩
Kubernetes 允许你在 Deployment 上执行滚动更新。在这种情况下,Deployment 为你管理下层的 ReplicaSet。
当你为一个 Deployment 配置自动扩缩时,你要为每个 Deployment 绑定一个 HorizontalPodAutoscaler。
HorizontalPodAutoscaler 管理 Deployment 的 replicas
字段。
Deployment Controller 负责设置下层 ReplicaSet 的 replicas
字段,
以便确保在上线及后续过程副本个数合适。
如果你对一个副本个数被自动扩缩的 StatefulSet 执行滚动更新, 该 StatefulSet
会直接管理它的 Pod 集合 (不存在类似 ReplicaSet 这样的中间资源)。
冷却/延迟支持
当使用 Horizontal Pod Autoscaler 管理一组副本扩缩时,
有可能因为指标动态的变化造成副本数量频繁的变化,有时这被称为
抖动(Thrashing) 。
从 v1.6 版本起,集群操作员可以调节某些 kube-controller-manager
的全局参数来
缓解这个问题。
从 v1.12 开始,算法调整后,扩容操作时的延迟就不必设置了。
--horizontal-pod-autoscaler-downscale-stabilization
: 设置缩容冷却时间窗口长度。
水平 Pod
扩缩器能够记住过去建议的负载规模,并仅对此时间窗口内的最大规模执行操作。
默认值是 5 分钟(5m0s
)。
说明: 当调整这些参数时,集群操作员需要明白其可能的影响。
如果延迟(冷却)时间设置的太长,Horizontal Pod Autoscaler 可能会不能很好的改变负载。
如果延迟(冷却)时间设置的太短,那么副本数量有可能跟以前一样出现抖动。
对资源指标的支持
HPA 的任何目标资源都可以基于其中的 Pods 的资源用量来实现扩缩。
在定义 Pod 规约时,类似 cpu
和 memory
这类资源请求必须被设定。
这些设定值被用来确定资源利用量并被 HPA 控制器用来对目标资源完成扩缩操作。
要使用基于资源利用率的扩缩,可以像下面这样指定一个指标源:
type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 60
基于这一指标设定,HPA 控制器会维持扩缩目标中的 Pods 的平均资源利用率在 60%。
利用率是 Pod 的当前资源用量与其请求值之间的比值。关于如何计算利用率以及如何计算平均值
的细节可参考算法 小节。
说明:
由于所有的容器的资源用量都会被累加起来,Pod 的总体资源用量值可能不会精确体现
各个容器的资源用量。这一现象也会导致一些问题,例如某个容器运行时的资源用量非常
高,但因为 Pod 层面的资源用量总值让人在可接受的约束范围内,HPA 不会执行扩大
目标对象规模的操作。
容器资源指标
FEATURE STATE: Kubernetes v1.20 [alpha]
HorizontalPodAutoscaler
也支持容器指标源,这时 HPA 可以跟踪记录一组 Pods 中各个容器的
资源用量,进而触发扩缩目标对象的操作。
容器资源指标的支持使得你可以为特定 Pod 中最重要的容器配置规模缩放阈值。
例如,如果你有一个 Web 应用和一个执行日志操作的边车容器,你可以基于 Web 应用的
资源用量来执行扩缩,忽略边车容器的存在及其资源用量。
如果你更改缩放目标对象,令其使用新的、包含一组不同的容器的 Pod 规约,你就需要
修改 HPA 的规约才能基于新添加的容器来执行规模扩缩操作。
如果指标源中指定的容器不存在或者仅存在于部分 Pods 中,那么这些 Pods 会被忽略,
HPA 会重新计算资源用量值。参阅算法 小节进一步了解计算细节。
要使用容器资源用量来完成自动扩缩,可以像下面这样定义指标源:
type : ContainerResource
containerResource :
name : cpu
container : application
target :
type : Utilization
averageUtilization : 60
在上面的例子中,HPA 控制器会对目标对象执行扩缩操作以确保所有 Pods 中
application
容器的平均 CPU 用量为 60%。
说明:
如果你要更改 HorizontalPodAutoscaler 所跟踪记录的容器的名称,你可以按一定顺序
来执行这一更改,确保在应用更改的过程中用来判定扩缩行为的容器可用。
在更新定义容器的资源(如 Deployment)之前,你需要更新相关的 HPA,使之能够同时
跟踪记录新的和老的容器名称。这样,HPA 就能够在整个更新过程中继续计算并提供扩缩操作建议。
一旦你已经将容器名称变更这一操作应用到整个负载对象至上,就可以从 HPA
的规约中去掉老的容器名称,完成清理操作。
多指标支持
Kubernetes 1.6 开始支持基于多个度量值进行扩缩。
你可以使用 autoscaling/v2beta2
API 来为 Horizontal Pod Autoscaler 指定多个指标。
Horizontal Pod Autoscaler 会根据每个指标计算,并生成一个扩缩建议。
幅度最大的扩缩建议会被采纳。
自定义指标支持
说明: 在 Kubernetes 1.2 增加了支持基于使用特殊注解表达的、特定于具体应用的扩缩能力,
此能力处于 Alpha 阶段。
从 Kubernetes 1.6 起,由于新的 autoscaling API 的引入,这些 annotation 就被废弃了。
虽然收集自定义指标的旧方法仍然可用,Horizontal Pod Autoscaler 调度器将不会再使用这些度量值。
同时,Horizontal Pod Autoscaler 也不再使用之前用于指定用户自定义指标的注解。
自 Kubernetes 1.6 起,Horizontal Pod Autoscaler 支持使用自定义指标。
你可以使用 autoscaling/v2beta2
API 为 Horizontal Pod Autoscaler 指定用户自定义指标。
Kubernetes 会通过用户自定义指标 API 来获取相应的指标。
关于指标 API 的要求,请参阅对 Metrics API 的支持 。
对 Metrics API 的支持
默认情况下,HorizontalPodAutoscaler 控制器会从一系列的 API 中检索度量值。
集群管理员需要确保下述条件,以保证 HPA 控制器能够访问这些 API:
关于指标来源以及其区别的更多信息,请参阅相关的设计文档,
the HPA V2 、
custom.metrics.k8s.io 和
external.metrics.k8s.io 。
关于如何使用它们的示例,请参考
使用自定义指标的教程
和使用外部指标的教程 。
支持可配置的扩缩
从 v1.18
开始,v2beta2
API 允许通过 HPA 的 behavior
字段配置扩缩行为。
在 behavior
字段中的 scaleUp
和 scaleDown
分别指定扩容和缩容行为。
可以两个方向指定一个稳定窗口,以防止扩缩目标中副本数量的波动。
类似地,指定扩缩策略可以控制扩缩时副本数的变化率。
扩缩策略
在 spec 字段的 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
。
scaleDown :
stabilizationWindowSeconds : 300
当指标显示目标应该缩容时,自动扩缩算法查看之前计算的期望状态,并使用指定时间间隔内的最大值。
在上面的例子中,过去 5 分钟的所有期望状态都会被考虑。
默认行为
要使用自定义扩缩,不必指定所有字段。
只有需要自定义的字段才需要指定。
这些自定义值与默认值合并。
默认值与 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
隐式维护状态禁用
你可以在不必更改 HPA 配置的情况下隐式地为某个目标禁用 HPA。
如果此目标的期望副本个数被设置为 0,而 HPA 的最小副本个数大于 0,
则 HPA 会停止调整目标(并将其自身的 ScalingActive
状况设置为 false
),
直到你通过手动调整目标的期望副本个数或 HPA 的最小副本个数来重新激活。
接下来
4.7.7 - Horizontal Pod Autoscaler 演练
Horizontal Pod Autoscaler 可以根据 CPU 利用率自动扩缩 ReplicationController、
Deployment、ReplicaSet 或 StatefulSet 中的 Pod 数量
(也可以基于其他应用程序提供的度量指标,目前这一功能处于 beta 版本)。
本文将引领你了解如何为 php-apache 服务器配置和使用 Horizontal Pod Autoscaler。
与 Horizontal Pod Autoscaler 相关的更多信息请参阅
Horizontal Pod Autoscaler 用户指南 。
准备开始
本文示例需要一个运行中的 Kubernetes 集群以及 kubectl,版本为 1.2 或更高。
Metrics 服务器
需要被部署到集群中,以便通过 Metrics API
提供度量数据。
Horizontal Pod Autoscaler 根据此 API 来获取度量数据。
要了解如何部署 metrics-server,请参考
metrics-server 文档 。
如果需要为 Horizontal Pod Autoscaler 指定多种资源度量指标,你的 Kubernetes
集群以及 kubectl 至少需要达到 1.6 版本。
此外,如果要使用自定义度量指标,你的 Kubernetes 集群还必须能够与提供这些自定义指标
的 API 服务器通信。
最后,如果要使用与 Kubernetes 对象无关的度量指标,则 Kubernetes 集群版本至少需要
达到 1.10 版本,同样,需要保证集群能够与提供这些外部指标的 API 服务器通信。
更多详细信息,请参阅
Horizontal Pod Autoscaler 用户指南 。
运行 php-apache 服务器并暴露服务
为了演示 Horizontal Pod Autoscaler,我们将使用一个基于 php-apache 镜像的
定制 Docker 镜像。Dockerfile 内容如下:
FROM php:5-apache
COPY index.php /var/www/html/index.php
RUN chmod a+rx index.php
该文件定义了一个 index.php 页面来执行一些 CPU 密集型计算:
<?php
$x = 0.0001;
for ($i = 0; $i <= 1000000; $i++) {
$x += sqrt($x);
}
echo "OK!";
?>
首先,我们使用下面的配置启动一个 Deployment 来运行这个镜像并暴露一个服务:
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 : k8s.gcr.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
创建 Horizontal Pod Autoscaler
现在,php-apache 服务器已经运行,我们将通过
kubectl autoscale
命令创建 Horizontal Pod Autoscaler。
以下命令将创建一个 Horizontal Pod Autoscaler 用于控制我们上一步骤中创建的
Deployment,使 Pod 的副本数量维持在 1 到 10 之间。
大致来说,HPA 将(通过 Deployment)增加或者减少 Pod 副本的数量以保持所有 Pod
的平均 CPU 利用率在 50% 左右。由于每个 Pod 请求 200 毫核的 CPU,这意味着平均
CPU 用量为 100 毫核。
算法的详情请参阅相关文档 。
kubectl autoscale deployment php-apache --cpu-percent= 50 --min= 1 --max= 10
horizontalpodautoscaler.autoscaling/php-apache autoscaled
我们可以通过以下命令查看 Autoscaler 的状态:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 18s
请注意当前的 CPU 利用率是 0%,这是由于我们尚未发送任何请求到服务器
(CURRENT
列显示了相应 Deployment 所控制的所有 Pod 的平均 CPU 利用率)。
增加负载
现在,我们将看到 Autoscaler 如何对增加负载作出反应。
我们将启动一个容器,并通过一个循环向 php-apache 服务器发送无限的查询请求
(请在另一个终端中运行以下命令):
kubectl run -i --tty load-generator --rm --image= busybox --restart= Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
一分钟时间左右之后,通过以下命令,我们可以看到 CPU 负载升高了:
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 305% / 50% 1 10 1 3m
这时,由于请求增多,CPU 利用率已经升至请求值的 305%。
可以看到,Deployment 的副本数量已经增长到了 7:
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 7/7 7 7 19m
说明: 有时最终副本的数量可能需要几分钟才能稳定下来。由于环境的差异,
不同环境中最终的副本数量可能与本示例中的数量不同。
停止负载
我们将通过停止负载来结束我们的示例。
在我们创建 busybox 容器的终端中,输入<Ctrl> + C
来终止负载的产生。
然后我们可以再次检查负载状态(等待几分钟时间):
NAME REFERENCE TARGET MINPODS MAXPODS REPLICAS AGE
php-apache Deployment/php-apache/scale 0% / 50% 1 10 1 11m
kubectl get deployment php-apache
NAME READY UP-TO-DATE AVAILABLE AGE
php-apache 1/1 1 1 27m
这时,CPU 利用率已经降到 0,所以 HPA 将自动缩减副本数量至 1。
说明: 自动扩缩完成副本数量的改变可能需要几分钟的时间。
基于多项度量指标和自定义度量指标自动扩缩
利用 autoscaling/v2beta2
API 版本,你可以在自动扩缩 php-apache 这个
Deployment 时使用其他度量指标。
首先,将 HorizontalPodAutoscaler 的 YAML 文件改为 autoscaling/v2beta2
格式:
kubectl get hpa php-apache -o yaml > /tmp/hpa-v2.yaml
在编辑器中打开 /tmp/hpa-v2.yaml
:
apiVersion : autoscaling/v2beta2
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)度量指标。这些度量指标用于描述
在相同名字空间中的别的对象,而非 Pods。
请注意这些度量指标不一定来自某对象,它们仅用于描述这些对象。
对象度量指标支持的 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/v2beta1
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 : AverageUtilization
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/v1beta1
kind : Ingress
name : main-route
target :
kind : 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/v1beta1
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 : `verb=GET`
这个选择算符使用与 Kubernetes 标签选择算符相同的语法。
如果名称和标签选择算符匹配到多个系列,监测管道会决定如何将多个系列合并成单个值。
选择算符是可以累加的,它不会选择目标以外的对象(类型为 Pods
的目标 Pods 或者
类型为 Object
的目标对象)。
基于与 Kubernetes 对象无关的度量指标执行扩缩
运行在 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/v2beta2
格式的 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
时,通常表明获取度量指标存在问题。
最后一个条件 ScalingLimitted
表明所需扩缩的值被 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.7.8 - 为应用程序设置干扰预算(Disruption Budget)
FEATURE STATE: 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 数的百分比。
例如,如果将 "minUnavailable" 设置为 “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%(或等于副本数)
可能会阻塞节点,导致资源耗尽。按照 PodDisruptionBudget
的语义,这是允许的。
用户可以在下面看到 pod 干扰预算定义的示例,它们与带有 app: zookeeper
标签的 pod 相匹配:
使用 minAvailable 的PDB 示例:
apiVersion : policy/v1beta1
kind : PodDisruptionBudget
metadata :
name : zk-pdb
spec :
minAvailable : 2
selector :
matchLabels :
app : zookeeper
使用 maxUnavailable 的 PDB 示例:
apiVersion : policy/v1beta1
kind : PodDisruptionBudget
metadata :
name : zk-pdb
spec :
maxUnavailable : 1
selector :
matchLabels :
app : zookeeper
例如,如果上述 zk-pdb
选择的是一个规格为 3 的 StatefulSet 对应的 Pod,
那么上面两种规范的含义完全相同。
推荐使用 maxUnavailable
,因为它自动响应控制器副本数量的变化。
创建 PDB 对象
你可以通过类似 kubectl apply -f mypdb.yaml
的命令来创建 PDB。
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 :
anntation : {}
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 的子集或父集。
然而,当名字空间下存在多个 PDB 时,用户必须小心,保证 PDB 的选择算符之间不重叠。
4.7.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.7.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.8 - 运行 Jobs
使用并行处理运行 Jobs。
4.8.1 - 使用 CronJob 运行自动化任务
在Kubernetes v1.21 版本中,CronJob 被提升为通用版本。如果你使用的是旧版本的 Kubernetes,请参考你正在使用的 Kubernetes 版本的文档,这样你就能看到准确的信息。旧的 Kubernetes 版本不支持batch/v1
CronJob API。
你可以利用 CronJobs 执行基于时间调度的任务。这些自动化任务和 Linux 或者 Unix 系统的 Cron 任务类似。
CronJobs 在创建周期性以及重复性的任务时很有帮助,例如执行备份操作或者发送邮件。CronJobs 也可以在特定时间调度单个任务,例如你想调度低活跃周期的任务。
CronJobs 有一些限制和特点。
例如,在特定状况下,同一个 CronJob 可以创建多个任务。
因此,任务应该是幂等的。
查看更多限制,请参考 CronJobs 。
准备开始
创建 CronJob
CronJob 需要一个配置文件。
本例中 CronJob 的.spec
配置文件每分钟打印出当前时间和一个问好信息:
apiVersion : batch/v1
kind : CronJob
metadata :
name : hello
spec :
schedule : "*/1 * * * *"
jobTemplate :
spec :
template :
spec :
containers :
- name : hello
image : busybox
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 50s 75s
就像你从命令返回结果看到的那样,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 的标准输出。请注意任务名称和 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
域。
配置文件的一般信息,请参考
部署应用 和
使用 kubectl 管理资源 .
CronJob 配置也需要包括
.spec
.
说明: 对 CronJob 的所有改动,特别是它的 .spec
,只会影响将来的运行实例。
时间安排
.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 。
开始的最后期限
.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
,后续发生的执行都会挂起。
这个设置对已经开始的执行不起作用。默认是关闭的。
注意: 在调度时间内挂起的执行都会被统计为错过的任务。当
.spec.suspend
从
true
改为
false
时,
且没有
开始的最后期限 ,错过的任务会被立即调度。
任务历史限制
.spec.successfulJobsHistoryLimit
和 .spec.failedJobsHistoryLimit
是可选的。
这两个字段指定应保留多少已完成和失败的任务。
默认设置为3和1。限制设置为0代表相应类型的任务完成后不会保留。
4.8.2 - 使用工作队列进行粗粒度并行处理
本例中,我们会运行包含多个并行工作进程的 Kubernetes Job。
本例中,每个 Pod 一旦被创建,会立即从任务队列中取走一个工作单元并完成它,然后将工作单元从队列中删除后再退出。
下面是本次示例的主要步骤:
启动一个消息队列服务 本例中,我们使用 RabbitMQ,你也可以用其他的消息队列服务。在实际工作环境中,你可以创建一次消息队列服务然后在多个任务中重复使用。
创建一个队列,放上消息数据 每个消息表示一个要执行的任务。本例中,每个消息是一个整数值。我们将基于这个整数值执行很长的计算操作。
启动一个在队列中执行这些任务的 Job 。该 Job 启动多个 Pod。每个 Pod 从消息队列中取走一个任务,处理它,然后重复执行,直到队列的队尾。
准备开始
要熟悉 Job 基本用法(非并行的),请参考
Job 。
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.8.
要获知版本信息,请输入
kubectl version
.
启动消息队列服务
本例使用了 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,并以只读的方式挂载到所有 Pod,或者 Pod 中的程序从类似 HDFS 的集群文件系统中读取。
例如,我们创建队列并使用 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 create -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.8.3 - 使用工作队列进行精细的并行处理
在这个例子中,我们会运行一个Kubernetes Job,其中的 Pod 会运行多个并行工作进程。
在这个例子中,当每个pod被创建时,它会从一个任务队列中获取一个工作单元,处理它,然后重复,直到到达队列的尾部。
下面是这个示例的步骤概述:
启动存储服务用于保存工作队列。 在这个例子中,我们使用 Redis 来存储工作项。
在上一个例子中,我们使用了 RabbitMQ。
在这个例子中,由于 AMQP 不能为客户端提供一个良好的方法来检测一个有限长度的工作队列是否为空,
我们使用了 Redis 和一个自定义的工作队列客户端库。
在实践中,你可能会设置一个类似于 Redis 的存储库,并将其同时用于多项任务或其他事务的工作队列。
创建一个队列,然后向其中填充消息。 每个消息表示一个将要被处理的工作任务。
在这个例子中,消息是一个我们将用于进行长度计算的整数。
启动一个 Job 对队列中的任务进行处理 。这个 Job 启动了若干个 Pod 。
每个 Pod 从消息队列中取出一个工作任务,处理它,然后重复,直到到达队列的尾部。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.8.
要获知版本信息,请输入
kubectl version
.
熟悉基本的、非并行的 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"
# Uncomment next two lines if you do not have Kube-DNS working.
# 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 ) # Put your actual work here instead of 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.8.4 - 使用索引作业完成静态工作分配下的并行处理
FEATURE STATE: Kubernetes v1.22 [beta]
在此示例中,你将运行一个使用多个并行工作进程的 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
现在执行 Job:
# 使用第一种方法(依赖于 $JOB_COMPLETION_INDEX)
kubectl apply -f https://kubernetes.io/examples/application/job/indexed-job.yaml
当你创建此 Job 时,控制平面会创建一系列 Pod,每个索引都由你指定。
.spec.parallelism
的值决定了一次可以运行多少个,
而 .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.8.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
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
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 - 访问集群中的应用程序
配置负载平衡、端口转发或设置防火墙或 DNS 配置,以访问集群中的应用程序。
4.9.1 - Web 界面 (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.2.0/aio/deploy/recommended.yaml
访问 Dashboard UI
为了保护你的集群数据,默认情况下,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 支持声明式配置。所有的配置都存储在遵循 Kubernetes
API 规范的 YAML 或者 JSON 配置文件中。
作为一种替代在部署向导中指定应用详情的方式,你可以在 YAML 或者 JSON 文件中定义应用,并且使用 Dashboard 上传文件:
使用 Dashboard
以下各节描述了 Kubernetes Dashboard UI 视图;包括它们提供的内容,以及怎么使用它们。
导航
当在集群中定义 Kubernetes 对象时,Dashboard 会在初始视图中显示它们。
默认情况下只会显示 默认 名字空间中的对象,可以通过更改导航栏菜单中的名字空间筛选器进行改变。
Dashboard 展示大部分 Kubernetes 对象,并将它们分组放在几个菜单类别中。
管理概述
集群和名字空间管理的视图, Dashboard 会列出节点、名字空间和持久卷,并且有它们的详细视图。
节点列表视图包含从所有节点聚合的 CPU 和内存使用的度量值。
详细信息视图显示了一个节点的度量值,它的规格、状态、分配的资源、事件和这个节点上运行的 Pod。
负载
显示选中的名字空间中所有运行的应用。
视图按照负载类型(如 Deployment、ReplicaSet、StatefulSet 等)罗列应用,并且每种负载都可以单独查看。
列表总结了关于负载的可执行信息,比如一个 ReplicaSet 的准备状态的 Pod 数量,或者目前一个 Pod 的内存使用量。
工作负载的详情视图展示了对象的状态、详细信息和相互关系。
例如,ReplicaSet 所控制的 Pod,或者 Deployment 关联的 新 ReplicaSet 和 Pod 水平扩展控制器。
服务
展示允许暴露给外网服务和允许集群内部发现的 Kubernetes 资源。
因此,Service 和 Ingress 视图展示他们关联的 Pod、给集群连接使用的内部端点和给外部用户使用的外部端点。
存储
存储视图展示持久卷申领(PVC)资源,这些资源被应用程序用来存储数据。
ConfigMap 和 Secret
展示的所有 Kubernetes 资源是在集群中运行的应用程序的实时配置。
通过这个视图可以编辑和管理配置对象,并显示那些默认隐藏的 secret。
日志查看器
Pod 列表和详细信息页面可以链接到 Dashboard 内置的日志查看器。查看器可以钻取属于同一个 Pod 的不同容器的日志。
接下来
更多信息,参见 Kubernetes Dashboard 项目页面 .
4.9.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
在 Kubernetes 1.3 或更高版本中,kubectl config view
不再显示 token。
使用 kubectl describe secret ...
来获取默认服务帐户的 token,如下所示:
grep/cut
方法实现:
APISERVER = $( kubectl config view | grep server | cut -f 2- -d ":" | tr -d " " )
TOKEN = $( kubectl describe secret $( kubectl get secrets | grep default | cut -f1 -d ' ' ) | 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 $( kubectl get serviceaccount default -o jsonpath = '{.secrets[0].name}' ) -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/<version number>/kubernetes
。
参阅 https://github.com/kubernetes/client-go
来查看目前支持哪些版本。
基于这个 client-go 客户端库编写应用程序。
请注意,client-go 定义了自己的 API 对象,因此如果需要,请从 client-go 而不是从主存储库
导入 API 定义,例如,import "k8s.io/client-go/1.4/pkg/api/v1"
才是对的。
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 时,定位和验证 apiserver 会有些许不同。
在 Pod 中定位 apiserver 的推荐方式是通过 kubernetes.default.svc
这个 DNS 名称,该名称将会解析为服务 IP,然后服务 IP 将会路由到 apiserver。
向 apiserver 进行身份验证的推荐方法是使用
服务帐户 凭据。
通过 kube-system,Pod 与服务帐户相关联,并且该服务帐户的凭证(token)
被放置在该 Pod 中每个容器的文件系统中,位于
/var/run/secrets/kubernetes.io/serviceaccount/token
。
如果可用,则将证书放入每个容器的文件系统中的
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
,
并且应该用于验证 apiserver 的服务证书。
最后,名字空间作用域的 API 操作所使用的 default 名字空间将被放置在
每个容器的 /var/run/secrets/kubernetes.io/serviceaccount/namespace
文件中。
在 Pod 中,建议连接 API 的方法是:
在 Pod 的边车容器中运行 kubectl proxy
,或者以后台进程的形式运行。
这将把 Kubernetes API 代理到当前 Pod 的 localhost 接口,
所以 Pod 中的所有容器中的进程都能访问它。
使用 Go 客户端库,并使用 rest.InClusterConfig()
和
kubernetes.NewForConfig()
函数创建一个客户端。
他们处理 apiserver 的定位和身份验证。
示例
在每种情况下,Pod 的凭证都是为了与 apiserver 安全地通信。
访问集群中正在运行的服务
上一节介绍了如何连接 Kubernetes API 服务。本节介绍如何连接到 Kubernetes
集群上运行的其他服务。
在 Kubernetes 中,节点 、
pods 和
服务 都有自己的 IP。
在许多情况下,集群上的节点 IP、Pod IP 和某些服务 IP 将无法路由,
因此无法从集群外部的计算机(例如桌面计算机)访问它们。
连接的方法
有多种方式可以从集群外部连接节点、Pod 和服务:
通过公共 IP 访问服务。
类型为 NodePort
或 LoadBalancer
的服务,集群外部可以访问。
请参阅 服务 和
kubectl expose 文档。
取决于你的集群环境,该服务可能仅暴露给你的公司网络,或者也可能暴露给
整个互联网。
请考虑公开该服务是否安全。它是否进行自己的身份验证?
在服务后端放置 Pod。要从一组副本中访问一个特定的 Pod,例如进行调试,
请在 Pod 上设置一个唯一的标签,然后创建一个选择此标签的新服务。
在大多数情况下,应用程序开发人员不应该通过其 nodeIP 直接访问节点。
使用 proxy 动词访问服务、节点或者 Pod。
在访问远程服务之前进行 apiserver 身份验证和授权。
如果服务不能够安全地暴露到互联网,或者服务不能获得节点 IP 端口的
访问权限,或者是为了调试,那么请使用此选项。
代理可能会给一些 web 应用带来问题。
只适用于 HTTP/HTTPS。
更多详细信息在这里 。
从集群中的节点或者 Pod 中访问。
运行一个 Pod,然后使用 kubectl exec
来连接 Pod 里的 Shell。
然后从 Shell 中连接其它的节点、Pod 和服务。
有些集群可能允许你通过 SSH 连接到节点,从那你可能可以访问集群的服务。
这是一个非正式的方式,可能可以运行在个别的集群上。
浏览器和其它一些工具可能没有被安装。集群的 DNS 可能无法使用。
发现内建服务
通常来说,集群中会有 kube-system 创建的一些运行的服务。
通过 kubectl cluster-info
命令获得这些服务列表:
Kubernetes master is running at https://104.197.5.247
elasticsearch-logging is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy
kibana-logging is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/kibana-logging/proxy
kube-dns is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/kube-dns/proxy
grafana is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/monitoring-grafana/proxy
heapster is running at https://104.197.5.247/api/v1/namespaces/kube-system/services/monitoring-heapster/proxy
这展示了访问每个服务的 proxy-verb URL。
例如,如果集群启动了集群级别的日志(使用 Elasticsearch),并且传递合适的凭证,
那么可以通过
https://104.197.5.247/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/
进行访问。日志也能通过 kubectl 代理获取,例如:
http://localhost:8080/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/
。
(参阅使用 Kubernetes API 访问集群
了解如何传递凭据,或者使用 kubectl proxy)
手动构建 apiserver 代理 URL
如上所述,你可以使用 kubectl cluster-info
命令来获得服务的代理 URL。
要创建包含服务端点、后缀和参数的代理 URL,需添加到服务的代理 URL:
http://
kubernetes_master_address
/api/v1/namespaces/
namespace_name
/services/
service_name[:port_name]
/proxy
如果尚未为端口指定名称,则不必在 URL 中指定 port_name 。
对于已命名和未命名的端口,也可以使用端口号代替 port_name 。
默认情况下,API server 使用 HTTP 代理你的服务。
要使用 HTTPS,请在服务名称前加上 https:
:
http://
kubernetes_master_address
/api/v1/namespaces/
namespace_name
/services/
https:service_name:[port_name]
/proxy
URL 名称段支持的格式为:
<service_name>
- 使用 http 代理到默认或未命名的端口
<service_name>:<port_name>
- 使用 http 代理到指定的端口名称或端口号
https:<service_name>:
- 使用 https 代理到默认或未命名的端口(注意后面的冒号)
https:<service_name>:<port_name>
- 使用 https 代理到指定的端口名称或端口号
示例
要访问 Elasticsearch 服务端点 _search?q=user:kimchy
,你需要使用:
http://104.197.5.247/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/_search?q=user:kimchy
要访问 Elasticsearch 集群健康信息 _cluster/health?pretty=true
,你需要使用:
https://104.197.5.247/api/v1/namespaces/kube-system/services/elasticsearch-logging/proxy/_cluster/health?pretty=true
{
"cluster_name" : "kubernetes_logging" ,
"status" : "yellow" ,
"timed_out" : false ,
"number_of_nodes" : 1 ,
"number_of_data_nodes" : 1 ,
"active_primary_shards" : 5 ,
"active_shards" : 5 ,
"relocating_shards" : 0 ,
"initializing_shards" : 0 ,
"unassigned_shards" : 5
}
使用 web 浏览器访问运行在集群上的服务
你可以在浏览器地址栏中输入 apiserver 代理 URL。但是:
Web 浏览器通常不能传递令牌,因此你可能需要使用基本(密码)身份验证。
Apiserver 可以配置为接受基本身份验证,但你的集群可能未进行配置。
某些 Web 应用程序可能无法运行,尤其是那些使用客户端 javascript
以不知道代理路径前缀的方式构建 URL 的应用程序。
请求重定向
重定向功能已弃用并被删除。请改用代理(见下文)。
多种代理
使用 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.9.3 - 使用端口转发来访问集群中的应用
本文展示如何使用 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 端口:
# Change mongo-75f59d57f4-4nd6q to the name of the Pod
kubectl get pod mongo-75f59d57f4-4nd6q --template= '{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
输出应该显示 Pod 中 MongoDB 的端口:
27017
(这是 Internet 分配给 MongoDB 的 TCP 端口)。
转发一个本地端口到 Pod 端口
kubectl port-forward
允许使用资源名称
(例如 pod 名称)来选择匹配的 pod 来进行端口转发。
# Change mongo-75f59d57f4-4nd6q to the name of the 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.9.4 - 使用服务来访问集群中的应用
本文展示如何创建一个 Kubernetes 服务对象,能让外部客户端访问在集群中运行的应用。
该服务为一个应用的两个运行实例提供负载均衡。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
教程目标
运行 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.9.5 - 使用 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 的配置文件:
# The identifier Backend is internal to nginx, and used to name this specific upstream
upstream Backend {
# hello is the internal DNS name used by the backend Service inside Kubernetes
server hello;
}
server {
listen 80;
location / {
# The following statement will proxy traffic to the upstream named 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.9.6 - 创建外部负载均衡器
本文展示如何创建一个外部负载均衡器。
说明: 此功能仅适用于支持外部负载均衡器的云提供商或环境。
创建服务时,你可以选择自动创建云网络负载均衡器。这提供了一个外部可访问的 IP 地址,
可将流量分配到集群节点上的正确端口上
( 假设集群在支持的环境中运行,并配置了正确的云负载平衡器提供商包 )。
有关如何配置和使用 Ingress 资源为服务提供外部可访问的 URL、负载均衡流量、终止 SSL 等功能,
请查看 Ingress 文档。
准备开始
配置文件
要创建外部负载均衡器,请将以下内容添加到
服务配置文件 :
你的配置文件可能会如下所示:
apiVersion : v1
kind : Service
metadata :
name : example-service
spec :
selector :
app : example
ports :
- port : 8765
targetPort : 9376
type : LoadBalancer
使用 kubectl
你也可以使用 kubectl expose
命令及其 --type=LoadBalancer
参数创建服务:
kubectl expose rc example --port= 8765 --target-port= 9376 \
--name= example-service --type= LoadBalancer
此命令通过使用与引用资源(在上面的示例的情况下,名为 example
的 replication controller)相同的选择器来创建一个新的服务。
更多信息(包括更多的可选参数),请参阅
kubectl expose
指南 。
找到你的 IP 地址
你可以通过 kubectl
获取服务信息,找到为你的服务创建的 IP 地址:
kubectl describe services example-service
这将获得如下输出:
Name: example-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app = example
Type: LoadBalancer
IP: 10.67.252.103
LoadBalancer Ingress: 192.0.2.89
Port: <unnamed> 80/TCP
NodePort: <unnamed> 32445/TCP
Endpoints: 10.64.0.4:80,10.64.1.5:80,10.64.2.4:80
Session Affinity: None
Events: <none>
IP 地址列在 LoadBalancer Ingress
旁边。
说明: 如果你在 Minikube 上运行服务,你可以通过以下命令找到分配的 IP 地址和端口:
minikube service example-service --url
保留客户端源 IP
由于此功能的实现,目标容器中看到的源 IP 将 不是客户端的原始源 IP 。
要启用保留客户端 IP,可以在服务的 spec 中配置以下字段(支持 GCE/Google Kubernetes Engine 环境):
service.spec.externalTrafficPolicy
- 表示此服务是否希望将外部流量路由到节点本地或集群范围的端点。
有两个可用选项:Cluster(默认)和 Local。
Cluster 隐藏了客户端源 IP,可能导致第二跳到另一个节点,但具有良好的整体负载分布。
Local 保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳,
但存在潜在的不均衡流量传播风险。
service.spec.healthCheckNodePort
- 指定服务的 healthcheck nodePort(数字端口号)。
如果未指定 healthCheckNodePort
,服务控制器从集群的 NodePort 范围内分配一个端口。
你可以通过设置 API 服务器的命令行选项 --service-node-port-range
来配置上述范围。
它将会使用用户指定的 healthCheckNodePort
值(如果被客户端指定)。
仅当 type
设置为 LoadBalancer 并且 externalTrafficPolicy
设置为 Local 时才生效。
可以通过在服务的配置文件中将 externalTrafficPolicy
设置为 Local 来激活此功能。
apiVersion : v1
kind : Service
metadata :
name : example-service
spec :
selector :
app : example
ports :
- port : 8765
targetPort : 9376
externalTrafficPolicy : Local
type : LoadBalancer
回收负载均衡器
在通常情况下,应在删除 LoadBalancer 类型服务后立即清除云提供商中的相关负载均衡器资源。
但是,众所周知,在删除关联的服务后,云资源被孤立的情况很多。
引入了针对服务负载均衡器的终结器保护,以防止这种情况发生。
通过使用终结器,在删除相关的负载均衡器资源之前,也不会删除服务资源。
具体来说,如果服务具有 type
LoadBalancer,则服务控制器将附加一个名为
service.kubernetes.io/load-balancer-cleanup
的终结器。
仅在清除负载均衡器资源后才能删除终结器。
即使在诸如服务控制器崩溃之类的极端情况下,这也可以防止负载均衡器资源悬空。
外部负载均衡器提供商
请务必注意,此功能的数据路径由 Kubernetes 集群外部的负载均衡器提供。
当服务 type
设置为 LoadBalancer 时,Kubernetes 向集群中的 Pod 提供的功能等同于
type
等于 ClusterIP,并通过使用 Kubernetes pod 的条目对负载均衡器(从外部到 Kubernetes)
进行编程来扩展它。
Kubernetes 服务控制器自动创建外部负载均衡器、健康检查(如果需要)、防火墙规则(如果需要),
并获取云提供商分配的外部 IP 并将其填充到服务对象中。
保留源 IP 时的注意事项和限制
GCE/AWS 负载均衡器不为其目标池提供权重。
对于旧的 LB kube-proxy 规则来说,这不是一个问题,它可以在所有端点之间正确平衡。
使用新功能,外部流量不会在 pod 之间平均负载,而是在节点级别平均负载
(因为 GCE/AWS 和其他外部 LB 实现无法指定每个节点的权重,
因此它们的平衡跨所有目标节点,并忽略每个节点上的 Pod 数量)。
但是,我们可以声明,对于 NumServicePods << NumNodes
或 NumServicePods >> NumNodes
时,
即使没有权重,也会看到接近相等的分布。
一旦外部负载平衡器提供权重,就可以将此功能添加到 LB 编程路径中。
未来工作:1.4 版本不提供权重支持,但可能会在将来版本中添加
内部 Pod 到 Pod 的流量应该与 ClusterIP 服务类似,所有 Pod 的概率相同。
4.9.7 - 列出集群中所有运行容器的镜像
本文展示如何使用 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
上面的命令将递归获取所有返回项目的名为 image
的字段。
作为替代方案,可以使用 Pod 的镜像字段的绝对路径。这确保即使字段名称重复的情况下也能检索到正确的字段,例如,特定项目中的许多字段都称为 name
:
kubectl get pods --all-namespaces -o jsonpath = "{.items[*].spec.containers[*].image}"
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.9.8 - 在 Minikube 环境中使用 NGINX Ingress 控制器配置 Ingress
Ingress 是一种 API 对象,其中定义了一些规则使得集群中的
服务可以从集群外访问。
Ingress 控制器
负责满足 Ingress 中所设置的规则。
本节为你展示如何配置一个简单的 Ingress,根据 HTTP URI 将服务请求路由到
服务 web
或 web2
。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
创建一个 Minikube 集群
点击 Launch Terminal
Launch Terminal
(可选操作)如果你在本地安装了 Minikube,运行下面的命令:
启用 Ingress 控制器
为了启用 NGINIX Ingress 控制器,可以运行下面的命令:
minikube addons enable ingress
检查验证 NGINX Ingress 控制器处于运行状态:
kubectl get pods -n kube-system
说明: 这一操作可能需要近一分钟时间。
输出:
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
部署一个 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 地址已被设置:
说明: 此操作可能需要几分钟时间。
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
此设置使得来自 hello-world.info
的请求被发送到 Minikube。
验证 Ingress 控制器能够转发请求流量:
输出:
Hello, world!
Version: 1.0.0
Hostname: web-55b8c6998d-8k564
说明: 如果你在使用本地 Minikube 环境,你可以从浏览器中访问 hello-world.info。
创建第二个 Deployment
使用下面的命令创建 v2 的 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.9.9 - 为集群配置 DNS
Kubernetes 提供 DNS 集群插件,大多数支持的环境默认情况下都会启用。
在 Kubernetes 1.11 及其以后版本中,推荐使用 CoreDNS,
kubeadm 默认会安装 CoreDNS。
要了解关于如何为 Kubernetes 集群配置 CoreDNS 的更多信息,参阅
定制 DNS 服务 。
关于如何利用 kube-dns 配置 kubernetes DNS 的演示例子,参阅
Kubernetes DNS 插件示例 。
4.9.10 - 同 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:/# 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.9.11 - 配置对多集群的访问
本文展示如何使用配置文件来配置对多个集群的访问。 在将集群、用户和上下文定义在一个或多个配置文件中之后,用户可以使用 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
将用户详细信息添加到配置文件中:
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 :
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.10.1 - 使用 crictl 对 Kubernetes 节点进行调试
FEATURE STATE: 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
默认连接到 unix:///var/run/dockershim.sock
。
对于其他的运行时,你可以用多种不同的方法设置端点:
通过设置参数 --runtime-endpoint
和 --image-endpoint
通过设置环境变量 CONTAINER_RUNTIME_ENDPOINT
和 IMAGE_SERVICE_ENDPOINT
通过在配置文件中设置端点 --config=/etc/crictl.yaml
你还可以在连接到服务器并启用或禁用调试时指定超时值,方法是在配置文件中指定
timeout
或 debug
值,或者使用 --timeout
和 --debug
命令行参数。
要查看或编辑当前配置,请查看或编辑 /etc/crictl.yaml
的内容。
runtime-endpoint: unix:///var/run/dockershim.sock
image-endpoint: unix:///var/run/dockershim.sock
timeout: 10
debug: true
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"
},
"logDirectory" : "/tmp" ,
"linux" : {
}
}
使用 crictl runp
命令应用 JSON 文件并运行沙盒。
crictl runp pod-config.json
返回了沙盒的 ID。
创建容器
用 crictl
创建容器对容器运行时排错很有帮助。
在运行的 Kubernetes 集群中,沙盒会随机的被 kubelet 停止和删除。
拉取 busybox 镜像
crictl pull busybox
Image is up to date for busybox@sha256:141c253bc4c3fd0a201d32dc1f493bcf3fff003b6df416dea4f41046e0f37d47
创建 Pod 和容器的配置:
Pod 配置 :
{
"metadata": {
"name": "nginx-sandbox" ,
"namespace": "default" ,
"attempt": 1 ,
"uid": "hdishd83djaidwnduwk28bcsb"
},
"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
更多信息请参考 kubernetes-sigs/cri-tools 。
Docker CLI 和 crictl 的映射
以下的映射表格只适用于 Docker CLI v1.40 和 crictl v1.19.0 版本。
请注意该表格并不详尽。例如,其中不包含 Docker CLI 的实验性命令。
说明: 尽管有些命令的输出缺少了一些数据列,CRICTL 的输出格式与 Docker CLI 是类似的。
如果你的脚本程序需要解析命令的输出,请确认检查该特定命令的输出。
mapping from docker cli to crictl - retrieve debugging information
docker cli
crictl
描述
不支持的功能
attach
attach
连接到一个运行中的容器
--detach-keys
, --sig-proxy
exec
exec
在运行中的容器里运行一个命令
--privileged
, --user
, --detach-keys
images
images
列举镜像
info
info
显示系统级的信息
inspect
inspect
, inspecti
返回容器、镜像或者任务的详细信息
logs
logs
获取容器的日志
--details
ps
ps
列举容器
stats
stats
实时显示容器的资源使用统计信息
列:NET/BLOCK I/O, PIDs
version
version
显示运行时(Docker、ContainerD、或者其他) 的版本信息
mapping from docker cli to crictl - perform changes
docker cli
crictl
描述
不支持的功能
create
create
创建一个新的容器
kill
stop
(timeout=0)
杀死一个或多个正在运行的容器
--signal
pull
pull
从镜像仓库拉取镜像或者代码仓库
--all-tags
, --disable-content-trust
rm
rm
移除一个或多个容器
rmi
rmi
移除一个或多个镜像
run
run
在新容器里运行一个命令
start
start
启动一个或多个停止的容器
--detach-keys
stop
stop
停止一个或多个正运行的容器
update
update
更新一个或多个容器的配置
CRI 不支持 --restart
、--blkio-weight
以及一些其他的资源限制选项。
mapping from docker cli to crictl - supported only in crictl
crictl
描述
imagefsinfo
返回镜像的文件系统信息
inspectp
显示一个或多个 Pod 的状态
port-forward
转发本地端口到 Pod
pods
列举 Pod
runp
运行一个新的 Pod
rmp
移除一个或多个 Pod
stopp
停止一个或多个正运行的 Pod
4.10.2 - 在本地开发和调试服务
Kubernetes 应用程序通常由多个独立的服务组成,每个服务都在自己的容器中运行。
在远端的 Kubernetes 集群上开发和调试这些服务可能很麻烦,需要
在运行的容器上打开 Shell ,
然后在远端 Shell 中运行你所需的工具。
telepresence
是一种工具,用于在本地轻松开发和调试服务,同时将服务代理到远程 Kubernetes 集群。
使用 telepresence
可以为本地服务使用自定义工具(如调试器和 IDE),
并提供对 Configmap、Secret 和远程集群上运行的服务的完全访问。
本文档描述如何在本地使用 telepresence
开发和调试远程集群上运行的服务。
准备开始
打开终端,不带参数运行 telepresence
,以打开 telepresence
Shell。
这个 Shell 在本地运行,使你可以完全访问本地文件系统。
telepresence
Shell 的使用方式多种多样。
例如,在你的笔记本电脑上写一个 Shell 脚本,然后直接在 Shell 中实时运行它。
你也可以在远端 Shell 上执行此操作,但这样可能无法使用首选的代码编辑器,并且在容器终止时脚本将被删除。
开发和调试现有的服务
在 Kubernetes 上开发应用程序时,通常对单个服务进行编程或调试。
服务可能需要访问其他服务以进行测试和调试。
一种选择是使用连续部署流水线,但即使最快的部署流水线也会在程序或调试周期中引入延迟。
使用 --swap-deployment
选项将现有部署与 Telepresence 代理交换。
交换允许你在本地运行服务并能够连接到远端的 Kubernetes 集群。
远端集群中的服务现在就可以访问本地运行的实例。
要运行 telepresence 并带有 --swap-deployment
选项,请输入:
telepresence --swap-deployment $DEPLOYMENT_NAME
这里的 $DEPLOYMENT_NAME
是你现有的部署名称。
运行此命令将生成 Shell。在该 Shell 中,启动你的服务。
然后,你就可以在本地对源代码进行编辑、保存并能看到更改立即生效。
你还可以在调试器或任何其他本地开发工具中运行服务。
接下来
如果你对实践教程感兴趣,请查看本教程 ,其中介绍了在 Google Kubernetes Engine 上本地开发 Guestbook 应用程序。
Telepresence 有多种代理选项 ,以满足你的各种情况。
要了解更多信息,请访问 Telepresence 网站 。
4.10.3 - 审计
FEATURE STATE: Kubernetes v1.23 [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 # This is required.
kind : Policy
# Don't generate audit events for all requests in RequestReceived stage.
omitStages :
- "RequestReceived"
rules :
# Log pod changes at RequestResponse level
- level : RequestResponse
resources :
- group : ""
# Resource "pods" doesn't match requests to any subresource of pods,
# which is consistent with the RBAC policy.
resources : ["pods" ]
# Log "pods/log", "pods/status" at Metadata level
- level : Metadata
resources :
- group : ""
resources : ["pods/log" , "pods/status" ]
# Don't log requests to a configmap called "controller-leader"
- level : None
resources :
- group : ""
resources : ["configmaps" ]
resourceNames : ["controller-leader" ]
# Don't log watch requests by the "system:kube-proxy" on endpoints or services
- level : None
users : ["system:kube-proxy" ]
verbs : ["watch" ]
resources :
- group : "" # core API group
resources : ["endpoints" , "services" ]
# Don't log authenticated requests to certain non-resource URL paths.
- level : None
userGroups : ["system:authenticated" ]
nonResourceURLs :
- "/api*" # Wildcard matching.
- "/version"
# Log the request body of configmap changes in kube-system.
- level : Request
resources :
- group : "" # core API group
resources : ["configmaps" ]
# This rule only applies to resources in the "kube-system" namespace.
# The empty string "" can be used to select non-namespaced resources.
namespaces : ["kube-system" ]
# Log configmap and secret changes in all other namespaces at the Metadata level.
- level : Metadata
resources :
- group : "" # core API group
resources : ["secrets" , "configmaps" ]
# Log all other resources in core and extensions at the Request level.
- level : Request
resources :
- group : "" # core API group
- group : "extensions" # Version of group should NOT be included.
# A catch-all rule to log all other requests at the Metadata level.
- level : Metadata
# Long-running requests like watches that fall under this rule will not
# generate an audit event in 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/audit.log
接下来挂载数据卷:
volumeMounts :
- mountPath : /etc/kubernetes/audit-policy.yaml
name : audit
readOnly : true
- mountPath : /var/log/audit.log
name : audit-log
readOnly : false
最后配置 hostPath
:
- name : audit
hostPath :
path : /etc/kubernetes/audit-policy.yaml
type : File
- name : audit-log
hostPath :
path : /var/log/audit.log
type : FileOrCreate
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.10.4 - 应用故障排查
本指南帮助用户调试那些部署到 Kubernetes 上后没有正常运行的应用。
本指南 并非 指导用户如何调试集群。
如果想调试集群的话,请参阅这里 。
诊断问题
故障排查的第一步是先给问题分类。问题是什么?是关于 Pods、Replication Controller 还是 Service?
调试 Pods
调试 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
状态的最常见原因是拉取镜像失败。要检查的有三个方面:
确保镜像名字拼写正确
确保镜像已被推送到镜像仓库
用手动命令 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}
命令来检视副本控制器相关的事件。
调试服务
服务支持在多个 Pod 间负载均衡。
有一些常见的问题可以造成服务无法正常工作。
以下说明将有助于调试服务的问题。
首先,验证服务是否有端点。对于每一个 Service 对象,API 服务器为其提供
对应的 endpoints
资源。
通过如下命令可以查看 endpoints 资源:
kubectl get endpoints ${ SERVICE_NAME }
确保 Endpoints 与服务成员 Pod 个数一致。
例如,如果你的 Service 用来运行 3 个副本的 nginx 容器,你应该会在服务的 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
处于 Running 态,有 Endpoints
被创建,Pod
真的在提供服务;
DNS 服务已配置并正常工作,iptables 规则也以安装并且 kube-proxy
也没有异常行为。
你也可以访问故障排查文档 来获取更多信息。
4.10.5 - 应用自测与调试
运行应用时,不可避免的需要定位问题。
前面我们介绍了如何使用 kubectl get pods
来查询 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-1006230814-6winp 1/1 Running 0 11s
nginx-deployment-1006230814-fmgu3 1/1 Running 0 11s
我们可以使用 kubectl describe pod
命令来查询每个 Pod 的更多信息,比如:
kubectl describe pod nginx-deployment-1006230814-6winp
Name: nginx-deployment-1006230814-6winp
Namespace: default
Node: kubernetes-node-wul5/10.240.0.9
Start Time: Thu, 24 Mar 2016 01:39:49 +0000
Labels: app=nginx,pod-template-hash=1006230814
Annotations: kubernetes.io/created-by={"kind":"SerializedReference","apiVersion":"v1","reference":{"kind":"ReplicaSet","namespace":"default","name":"nginx-deployment-1956810328","uid":"14e607e7-8ba1-11e7-b5cb-fa16" ...
Status: Running
IP: 10.244.0.6
Controllers: ReplicaSet/nginx-deployment-1006230814
Containers:
nginx:
Container ID: docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
Image: nginx
Image ID: docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
Port: 80/TCP
QoS Tier:
cpu: Guaranteed
memory: Guaranteed
Limits:
cpu: 500m
memory: 128Mi
Requests:
memory: 128Mi
cpu: 500m
State: Running
Started: Thu, 24 Mar 2016 01:39:51 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-5kdvl (ro)
Conditions:
Type Status
Initialized True
Ready True
PodScheduled True
Volumes:
default-token-4bcbi:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-4bcbi
Optional: false
QoS Class: Guaranteed
Node-Selectors: <none>
Tolerations: <none>
Events:
FirstSeen LastSeen Count From SubobjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
54s 54s 1 {default-scheduler } Normal Scheduled Successfully assigned nginx-deployment-1006230814-6winp to kubernetes-node-wul5
54s 54s 1 {kubelet kubernetes-node-wul5} spec.containers{nginx} Normal Pulling pulling image "nginx"
53s 53s 1 {kubelet kubernetes-node-wul5} spec.containers{nginx} Normal Pulled Successfully pulled image "nginx"
53s 53s 1 {kubelet kubernetes-node-wul5} spec.containers{nginx} Normal Created Created container with docker id 90315cc9f513
53s 53s 1 {kubelet kubernetes-node-wul5} spec.containers{nginx} Normal Started Started container with docker id 90315cc9f513
这里可以看到容器和 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 :
annotations :
kubernetes.io/created-by : |
{"kind" :"SerializedReference" ,"apiVersion" :"v1" ,"reference" :{"kind" :"ReplicaSet" ,"namespace" :"default" ,"name" :"nginx-deployment-1006230814" ,"uid" :"4c84c175-f161-11e5-9a78-42010af00005" ,"apiVersion" :"extensions" ,"resourceVersion" :"133434" }}
creationTimestamp : 2016-03-24T01:39:50Z
generateName : nginx-deployment-1006230814-
labels :
app : nginx
pod-template-hash : "1006230814"
name : nginx-deployment-1006230814-6winp
namespace : default
resourceVersion : "133447"
uid : 4c879808-f161-11e5-9a78-42010af00005
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
volumeMounts :
- mountPath : /var/run/secrets/kubernetes.io/serviceaccount
name : default-token-4bcbi
readOnly : true
dnsPolicy : ClusterFirst
nodeName : kubernetes-node-wul5
restartPolicy : Always
securityContext : {}
serviceAccount : default
serviceAccountName : default
terminationGracePeriodSeconds : 30
volumes :
- name : default-token-4bcbi
secret :
secretName : default-token-4bcbi
status :
conditions :
- lastProbeTime : null
lastTransitionTime : 2016-03-24T01:39:51Z
status : "True"
type : Ready
containerStatuses :
- containerID : docker://90315cc9f513c724e9957a4788d3e625a078de84750f244a40f97ae355eb1149
image : nginx
imageID : docker://6f62f48c4e55d700cf3eb1b5e33fa051802986b77b874cc351cce539e5163707
lastState : {}
name : nginx
ready : true
restartCount : 0
state :
running :
startedAt : 2016-03-24T01:39:51Z
hostIP : 10.240.0.9
phase : Running
podIP : 10.244.0.6
startTime : 2016-03-24T01:39:49Z
示例:调试宕机或无法联系的节点
有时候,在调试时,查看节点的状态是很有用的 —— 例如,因为你已经注意到节点上运行的 Pod 的奇怪行为,
或者想了解为什么 Pod 不会调度到节点上。
与 Pod 一样,你可以使用 kubectl describe node
和 kubectl get node -o yaml
来查询节点的详细信息。
例如,如果某个节点宕机(与网络断开连接,或者 kubelet 挂掉无法重新启动等等),你将看到以下情况。
请注意显示节点未就绪的事件,也请注意 Pod 不再运行(它们在5分钟未就绪状态后被驱逐)。
NAME STATUS ROLES AGE VERSION
kubernetes-node-861h NotReady <none> 1h v1.13.0
kubernetes-node-bols Ready <none> 1h v1.13.0
kubernetes-node-st6x Ready <none> 1h v1.13.0
kubernetes-node-unaj Ready <none> 1h v1.13.0
kubectl describe node kubernetes-node-861h
Name: kubernetes-node-861h
Role
Labels: kubernetes.io/arch=amd64
kubernetes.io/os=linux
kubernetes.io/hostname=kubernetes-node-861h
Annotations: node.alpha.kubernetes.io/ttl=0
volumes.kubernetes.io/controller-managed-attach-detach=true
Taints: <none>
CreationTimestamp: Mon, 04 Sep 2017 17:13:23 +0800
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
OutOfDisk Unknown Fri, 08 Sep 2017 16:04:28 +0800 Fri, 08 Sep 2017 16:20:58 +0800 NodeStatusUnknown Kubelet stopped posting node status.
MemoryPressure Unknown Fri, 08 Sep 2017 16:04:28 +0800 Fri, 08 Sep 2017 16:20:58 +0800 NodeStatusUnknown Kubelet stopped posting node status.
DiskPressure Unknown Fri, 08 Sep 2017 16:04:28 +0800 Fri, 08 Sep 2017 16:20:58 +0800 NodeStatusUnknown Kubelet stopped posting node status.
Ready Unknown Fri, 08 Sep 2017 16:04:28 +0800 Fri, 08 Sep 2017 16:20:58 +0800 NodeStatusUnknown Kubelet stopped posting node status.
Addresses: 10.240.115.55,104.197.0.26
Capacity:
cpu: 2
hugePages: 0
memory: 4046788Ki
pods: 110
Allocatable:
cpu: 1500m
hugePages: 0
memory: 1479263Ki
pods: 110
System Info:
Machine ID: 8e025a21a4254e11b028584d9d8b12c4
System UUID: 349075D1-D169-4F25-9F2A-E886850C47E3
Boot ID: 5cd18b37-c5bd-4658-94e0-e436d3f110e0
Kernel Version: 4.4.0-31-generic
OS Image: Debian GNU/Linux 8 (jessie)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://1.12.5
Kubelet Version: v1.6.9+a3d1dfa6f4335
Kube-Proxy Version: v1.6.9+a3d1dfa6f4335
ExternalID: 15233045891481496305
Non-terminated Pods: (9 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
......
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
900m (60%) 2200m (146%) 1009286400 (66%) 5681286400 (375%)
Events: <none>
kubectl get node kubernetes-node-861h -o yaml
apiVersion : v1
kind : Node
metadata :
creationTimestamp : 2015-07-10T21:32:29Z
labels :
kubernetes.io/hostname : kubernetes-node-861h
name : kubernetes-node-861h
resourceVersion : "757"
selfLink : /api/v1/nodes/kubernetes-node-861h
uid : 2a69374e-274b-11e5-a234-42010af0d969
spec :
externalID : "15233045891481496305"
podCIDR : 10.244.0.0 /24
providerID : gce://striped-torus-760/us-central1-b/kubernetes-node-861h
status :
addresses :
- address : 10.240.115.55
type : InternalIP
- address : 104.197.0.26
type : ExternalIP
capacity :
cpu : "1"
memory : 3800808Ki
pods : "100"
conditions :
- lastHeartbeatTime : 2015-07-10T21:34:32Z
lastTransitionTime : 2015-07-10T21:35:15Z
reason : Kubelet stopped posting node status.
status : Unknown
type : Ready
nodeInfo :
bootID : 4e316776 -b40d-4f78-a4ea-ab0d73390897
containerRuntimeVersion : docker://Unknown
kernelVersion : 3.16.0-0. bpo.4-amd64
kubeProxyVersion : v0.21.1-185-gffc5a86098dc01
kubeletVersion : v0.21.1-185-gffc5a86098dc01
machineID : ""
osImage : Debian GNU/Linux 7 (wheezy)
systemUUID : ABE5F6B4-D44B-108B-C46A-24CCE16C8B6E
接下来
了解更多的调试工具:
4.10.6 - 故障诊断
有时候事情会出错。本指南旨在解决这些问题。它包含两个部分:
应用排错 -
针对部署代码到 Kubernetes 并想知道代码为什么不能正常运行的用户。
集群排错 -
针对集群管理员以及 Kubernetes 集群表现异常的用户。
你也应该查看所用发行版本 的已知问题。
获取帮助
如果你的问题在上述指南中没有得到答案,你还有另外几种方式从 Kubernetes 团队获得帮助。
问题
本网站上的文档针对回答各类问题进行了结构化组织和分类。
概念 部分解释 Kubernetes 体系结构以及每个组件的工作方式,
安装 部分提供了安装的实用说明。
任务 部分展示了如何完成常用任务,
教程 部分则提供对现实世界、特定行业或端到端开发场景的更全面的演练。
参考 部分提供了详细的
Kubernetes API 文档
和命令行 (CLI) 接口的文档,例如kubectl
。
求救!我的问题还没有解决!我现在需要帮助!
Stack Overflow
社区中的其他人可能已经问过和你类似的问题,也可能能够帮助解决你的问题。
Kubernetes 团队还会监视带有 Kubernetes 标签的帖子 。
如果现有的问题对你没有帮助,请问一个新问题 !
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.10.7 - 确定 Pod 失败的原因
本文介绍如何编写和读取容器的终止消息。
终止消息为容器提供了一种方法,可以将有关致命事件的信息写入某个位置,
在该位置可以通过仪表板和监控软件等工具轻松检索和显示致命事件。
在大多数情况下,您放入终止消息中的信息也应该写入
常规 Kubernetes 日志 。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
读写终止消息
在本练习中,您将创建运行一个容器的 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 create -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 --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}}"
定制终止消息
Kubernetes 从容器的 terminationMessagePath
字段中指定的终止消息文件中检索终止消息,
默认值为 /dev/termination-log
。
通过定制这个字段,您可以告诉 Kubernetes 使用不同的文件。
Kubernetes 使用指定文件中的内容在成功和失败时填充容器的状态消息。
在下例中,容器将终止消息写入 /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.10.8 - 节点健康监测
节点问题检测器(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 : k8s.gcr.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 : k8s.gcr.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 # Overwrite the config/ directory with ConfigMap volume
mountPath : /config
readOnly : true
volumes :
- name : log
hostPath :
path : /var/log/
- name : config # Define ConfigMap volume
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.10.9 - 获取正在运行容器的 Shell
本文介绍怎样使用 kubectl exec
命令获取正在运行容器的 Shell。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
获取容器的 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 create -f https://k8s.io/examples/application/shell-demo.yaml
检查容器是否运行正常:
kubectl get pod shell-demo
获取正在运行容器的 Shell:
kubectl exec -it shell-demo -- /bin/bash
说明:
双破折号 "--" 用于将要传递给命令的参数与 kubectl 的参数分开。
在 shell 中,打印根目录:
在 shell 中,实验其他命令。下面是一些示例:
root@shell-demo:/# ls /
root@shell-demo:/# cat /proc/mounts
root@shell-demo:/# cat /proc/1/maps
root@shell-demo:/# apt-get update
root@shell-demo:/# apt-get install -y tcpdump
root@shell-demo:/# tcpdump
root@shell-demo:/# apt-get install -y lsof
root@shell-demo:/# lsof
root@shell-demo:/# apt-get install -y procps
root@shell-demo:/# ps aux
root@shell-demo:/# ps aux | grep nginx
编写 nginx 的根页面
在看一下 Pod 的配置文件。该 Pod 有个 emptyDir
卷,容器将该卷挂载到了 /usr/share/nginx/html
。
在 shell 中,在 /usr/share/nginx/html
目录创建一个 index.html
文件:
root@shell-demo:/# echo Hello shell demo > /usr/share/nginx/html/index.html
在 shell 中,向 nginx 服务器发送 GET 请求:
root@shell-demo:/# apt-get update
root@shell-demo:/# apt-get install curl
root@shell-demo:/# curl 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 有两个容器分别为 main-app 和 healper-app。
下面的命令将会打开一个 shell 访问 main-app 容器。
kubectl exec -it my-pod --container main-app -- /bin/bash
接下来
4.10.10 - 调试 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.10.11 - 调试 Pods 和 ReplicationControllers
此页面展示如何调试 Pod 和 ReplicationController。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
要获知版本信息,请输入
kubectl version
.
调试 Pod
调试一个 pod 的第一步是观察它。使用下面的命令检查 Pod 的当前状态和最近事件:
kubectl describe pods ${ POD_NAME }
看看 Pod 中的容器的状态。它们都是 Running
吗?最近有重启吗?
根据 Pod 的状态继续调试。
我的 Pod 停滞在 Pending 状态
如果 Pod 被卡在 Pending
状态,就意味着它不能调度在某个节点上。一般来说,这是因为某种类型的资源不足而
导致无法调度。 查看上面的命令 kubectl describe ...
的输出。调度器的消息中应该会包含无法调度 Pod 的原因。
原因包括:
资源不足
你可能已经耗尽了集群中供应的 CPU 或内存。在这个情况下你可以尝试几件事情:
向集群中添加节点。
终止不需要的 Pod
为 Pending 状态的 Pod 提供空间。
检查该 Pod 是否不大于你的节点。例如,如果全部节点具有 cpu:1
容量,那么具有
请求为 cpu: 1.1
的 Pod 永远不会被调度。
你可以使用 kubectl get nodes -o <format>
命令来检查节点容量。
下面是一些能够提取必要信息的命令示例:
kubectl get nodes -o yaml | egrep '\sname:|cpu:|memory:'
kubectl get nodes -o json | jq '.items[] | {name: .metadata.name, cap: .status.capacity}'
可以考虑配置资源配额 来限制可耗用的资源总量。
如果与命名空间一起使用,它可以防止一个团队吞噬所有的资源。
使用 hostPort
当你将一个 Pod 绑定到某 hostPort
时,这个 Pod 能被调度的位置数量有限。
在大多数情况下,hostPort
是不必要的; 尝试使用服务对象来暴露你的 Pod。
如果你需要 hostPort
,那么你可以调度的 Pod 数量不能超过集群的节点个数。
我的 Pod 一直在 Waiting
如果 Pod 一直停滞在 Waiting
状态,那么它已被调度在某个工作节点,但它不能在该机器上运行。
再次,来自 kubectl describe ...
的内容应该是可以是很有用的。
最常见的原因 Waiting
的 Pod 是无法拉取镜像。有三件事要检查:
确保你的镜像的名称正确。
你是否将镜像推送到存储库?
在你的机器上手动运行 docker pull <image>
,看看是否可以拉取镜像。
我的 Pod 一直 Crashing 或者其他不健康状态
一旦 Pod 已经被调度,就可以依据
调试运行中的 Pod
展开进一步的调试工作。
调试 Replication Controller
Replication Controller 相当简单。它们或者能或者不能创建 Pod。如果它们无法创建 Pod,
请参考上面的说明 来调试你的 Pod。
你也可以使用 kubectl describe rc ${CONTROLLER_NAME}
来检查和副本控制器有关的事件。
4.10.12 - 调试 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= k8s.gcr.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 : k8s.gcr.io/serve_hostname
"app" 标签是 kubectl create deployment
根据 Deployment 名称自动设置的。
确认你的 Pods 是运行状态:
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 :
name : hostnames
spec :
selector :
app : hostnames
ports :
- name : default
protocol : TCP
port : 80
targetPort : 9376
为了突出配置范围的完整性,你在此处创建的 Service 使用的端口号与 Pods 不同。
对于许多真实的 Service,这些值可以是相同的。
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,如上面的 Endpoint 所示。
说明: 这些命令使用的是 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 正常运行吗?
确认 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.10.13 - 调试StatefulSet
此任务展示如何调试 StatefulSet。
准备开始
你需要有一个 Kubernetes 集群,已配置好的 kubectl 命令行工具与你的集群进行通信。
你应该有一个运行中的 StatefulSet,以便用于调试。
调试 StatefulSet
StatefulSet 在创建 Pod 时为其设置了 app=myapp
标签,列出仅属于某 StatefulSet
的所有 Pod 时,可以使用以下命令:
kubectl get pods -l app = myapp
如果你发现列出的任何 Pod 长时间处于 Unknown
或 Terminating
状态,请参阅
删除 StatefulSet Pods
了解如何处理它们的说明。
你可以参考调试 Pods
来调试 StatefulSet 中的各个 Pod。
接下来
4.10.14 - 调试运行中的 Pod
本页解释如何在节点上调试运行中(或崩溃)的 Pod。
准备开始
检查 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 。
使用临时调试容器来进行调试
FEATURE STATE: Kubernetes v1.22 [alpha]
当由于容器崩溃或容器镜像不包含调试程序(例如无发行版镜像 等)
而导致 kubectl exec
无法运行时,临时容器 对于排除交互式故障很有用。
使用临时容器来调试的例子
说明: 本示例需要你的集群已经开启
EphemeralContainers
特性门控 ,
kubectl
版本为 v1.22 或者更高。
你可以使用 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 --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 --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 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 --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 的名字。
新的调试容器运行在宿主命名空间里(IPC, 网络 还有PID命名空间)。
节点的根文件系统会被挂载在 /host
。
当你完成节点调试时,不要忘记清理调试 Pod:
kubectl delete pod node-debugger-mynode-pdx84
4.10.15 - 资源指标管道
资源使用指标,例如容器 CPU 和内存使用率,可通过 Metrics API 在 Kubernetes 中获得。
这些指标可以直接被用户访问,比如使用 kubectl top
命令行,或者被集群中的控制器
(例如 Horizontal Pod Autoscalers) 使用来做决策。
Metrics API
通过 Metrics API,你可以获得指定节点或 Pod 当前使用的资源量。
此 API 不存储指标值,因此想要获取某个指定节点 10 分钟前的
资源使用量是不可能的。
此 API 与其他 API 没有区别:
此 API 和其它 Kubernetes API 一起位于同一端点(endpoint)之下且可发现,
路径为 /apis/metrics.k8s.io/
它具有相同的安全性、可扩展性和可靠性保证
Metrics API 在 k8s.io/metrics
仓库中定义。你可以在那里找到有关 Metrics API 的更多信息。
说明: Metrics API 需要在集群中部署 Metrics Server。否则它将不可用。
度量资源用量
CPU
CPU 用量按其一段时间内的平均值统计,单位为
CPU 核 。
此度量值通过在内核(包括 Linux 和 Windows)提供的累积 CPU 计数器乘以一个系数得到。
kubelet
组件负责选择计算系数所使用的窗口大小。
内存
内存用量按工作集(Working Set)的大小字节数统计,其数值为收集度量值的那一刻的内存用量。
如果一切都很理想化,“工作集” 是任务在使用的内存总量,该内存是不可以在内存压力较大
的情况下被释放的。
不过,具体的工作集计算方式取决于宿主 OS,有很大不同,且通常都大量使用启发式
规则来给出一个估计值。
其中包含所有匿名内存使用(没有后台文件提供存储者),因为 Kubernetes 不支持交换分区。
度量值通常包含一些高速缓存(有后台文件提供存储)内存,因为宿主操作系统并不是总能
回收这些页面。
Metrics 服务器
Metrics 服务器
是集群范围资源用量数据的聚合器。
默认情况下,在由 kube-up.sh
脚本创建的集群中会以 Deployment 的形式被部署。
如果你使用其他 Kubernetes 安装方法,则可以使用提供的
部署组件 components.yaml
来部署。
Metric 服务器从每个节点上的 kubelet
公开的 Summary API 中采集指标信息。
该 API 通过
Kubernetes 聚合器
注册到主 API 服务器上。
在设计文档
中可以了解到有关 Metrics 服务器的更多信息。
4.10.16 - 资源监控工具
要扩展应用程序并提供可靠的服务,你需要了解应用程序在部署时的行为。
你可以通过检测容器检查 Kubernetes 集群中的应用程序性能,
Pods ,
服务
和整个集群的特征。
Kubernetes 在每个级别上提供有关应用程序资源使用情况的详细信息。
此信息使你可以评估应用程序的性能,以及在何处可以消除瓶颈以提高整体性能。
在 Kubernetes 中,应用程序监控不依赖单个监控解决方案。
在新集群上,你可以使用资源度量 或
完整度量 管道来收集监视统计信息。
资源度量管道
资源指标管道提供了一组与集群组件,例如
Horizontal Pod Autoscaler
控制器以及 kubectl top
实用程序相关的有限度量。
这些指标是由轻量级的、短期、内存存储的
metrics-server 收集的,
通过 metrics.k8s.io
公开。
度量服务器发现集群中的所有节点,并且查询每个节点的
kubelet
以获取 CPU 和内存使用情况。
Kubelet 充当 Kubernetes 主节点与节点之间的桥梁,管理机器上运行的 Pod 和容器。
kubelet 将每个 Pod 转换为其组成的容器,并在容器运行时通过容器运行时接口
获取各个容器使用情况统计信息。
kubelet 从集成的 cAdvisor 获取此信息,以进行旧式 Docker 集成。
然后,它通过 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.10.17 - 集群故障排查
本篇文档是介绍集群故障排查的;我们假设对于你碰到的问题,你已经排除了是由应用程序造成的。
对于应用的调试,请参阅
应用故障排查指南 。
你也可以访问故障排查
来获取更多的信息。
列举集群节点
调试的第一步是查看所有的节点是否都已正确注册。
运行
验证你所希望看见的所有节点都能够显示出来,并且都处于 Ready
状态。
为了了解你的集群的总体健康状况详情,你可以运行:
kubectl cluster-info dump
查看日志
到这里,挖掘出集群更深层的信息就需要登录到相关的机器上。下面是相关日志文件所在的位置。
(注意,对于基于 systemd 的系统,你可能需要使用journalctl
)。
主控节点
/var/log/kube-apiserver.log
- API 服务器, 提供API服务
/var/log/kube-scheduler.log
- 调度器, 负责产生调度决策
/var/log/kube-controller-manager.log
- 管理副本控制器的控制器
工作节点
/var/log/kubelet.log
- kubelet
,负责在节点运行容器
/var/log/kube-proxy.log
- kube-proxy
, 负责服务的负载均衡
集群故障模式的一般性概述
下面是一个不完整的列表,列举了一些可能的出错场景,以及通过调整集群配置来解决相关问题的方法。
根本原因
VM(s) 关机
集群之间,或者集群和用户之间网络分裂
Kubernetes 软件本身崩溃
数据丢失或者持久化存储不可用(如:GCE PD 或 AWS EBS 卷)
操作错误,如:Kubernetes 或者应用程序配置错误
具体情况
API 服务器所在的 VM 关机或者 API 服务器崩溃
结果
不能停止、更新或者启动新的 Pod、服务或副本控制器
现有的 Pod 和服务在不依赖 Kubernetes API 的情况下应该能继续正常工作
API 服务器的后端存储丢失
结果
API 服务器应该不能启动
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.11 - 扩展 Kubernetes
了解针对工作环境需要来调整 Kubernetes 集群的进阶方法。
4.11.1 - 使用自定义资源
4.11.1.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 的 scope
字段设置。和其他现有的内置对象一样,删除
一个名字空间时,该名字空间下的所有定制对象也会被删除。CustomResourceDefinition
本身是不受名字空间限制的,对所有名字空间可用。
例如,如果你将下面的 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 通常是单数形式的帕斯卡编码(PascalCased)形式。你的资源清单会使用这一形式。
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 数据。
在下面的例子中,在类别为 CrontTab
的定制对象中,设置了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 提供多个版本的支持,以及如何将你的对象
从一个版本迁移到另一个版本, 详细信息可参阅
定制资源定义的版本 。
高级主题
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 模式定义
来执行合法性检查的,
你可以通过使用准入控制 Webhook
来添加额外的合法性检查逻辑。
此外,对模式定义存在以下限制:
以下字段不可设置:
definitions
dependencies
deprecated
discriminator
id
patternProperties
readOnly
writeOnly
xml
$ref
字段 uniqueItems
不可设置为 true
字段 additionalProperties
不可设置为 false
字段 additionalProperties
与 properties
互斥,不可同时使用
当设置默认值特性 被启用时,可以设置字段 default
。
就 apiextensions.k8s.io/v1
组的 CustomResourceDefinitions,这一条件是满足的。
设置默认值的功能特性从 1.17 开始正式发布。该特性在 1.16 版本中处于
Beta 状态,要求 CustomResourceDefaulting
特性门控
被启用。对于大多数集群而言,Beta 状态的特性门控默认都是自动启用的。
关于对某些 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 is the schema for validating custom objects.
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
设置默认值
说明: 要使用设置默认值功能,你的 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)
1.20 版本新增: 对于未设置其 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
的取值都会增加。
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.1.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 conversion 。
更新 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
字段可用于支持你所开发的
定制资源的多个版本。版本可以具有不同的模式,并且转换 Webhooks
可以在多个版本之间转换定制资源。
在适当的情况下,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
作为默认版本,除非所提供的对象指定了版本。
版本废弃
FEATURE STATE: Kubernetes v1.19 [stable]
从 v1.19 开始,CustomResourceDefinition 可用来标明所定义的资源的特定版本
被废弃。当发起对已废弃的版本的 API 请求时,会在 API 响应中以 HTTP 头部
的形式返回警告消息。
如果需要,可以对资源的每个废弃版本定制该警告消息。
定制的警告消息应该标明废弃的 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
# 此属性标明此定制资源的 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
# 此属性标明此定制资源的 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
Webhook 转换
FEATURE STATE: Kubernetes v1.16 [stable]
说明: Webhook 转换在 Kubernetes 1.13 版本引入,在 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 服务器之间的通信
可能会失败。
配置 CustomResourceDefinition 以使用转换 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
在应用新更改之前,请确保转换服务器已启动并正在运行。
调用 Webhook
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 请求和响应
请求
向 Webhooks 发起请求的动词是 POST,请求的 Content-Type
为 application/json
。
请求的主题为 JSON 序列化形式的
apiextensions.k8s.io API 组的 ConversionReview API 对象。
Webhooks 可以在其 CustomResourceDefinition 中使用conversionReviewVersions
字段
设置它们接受的 ConversionReview
对象的版本:
apiVersion : apiextensions.k8s.io/v1
kind : CustomResourceDefinition
...
spec :
...
conversion :
strategy : Webhook
webhook :
conversionReviewVersions : ["v1" , "v1beta1" ]
...
创建 apiextensions.k8s.io/v1
版本的自定义资源定义时,
conversionReviewVersions
是必填字段。
Webhooks 要求支持至少一个 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
响应
Webhooks 响应包含 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
)写入对象。
通过从 storedVersions
字段中删除 v1beta1
来更新 CustomResourceDefinition
的Status
。
4.11.2 - 配置聚合层
配置聚合层
可以允许 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。
路径配置是可选的,默认为 /
。
下面是为可在端口 1234
上调用的扩展 apiserver 的配置示例
服务位于子路径 /my-path
下,并针对 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.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 : 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
- --address=0.0.0.0
- --leader-elect=false
- --scheduler-name=my-scheduler
image : gcr.io/my-gcp-project/my-kube-scheduler:1.0
livenessProbe :
httpGet :
path : /healthz
port : 10251
initialDelaySeconds : 15
name : kube-second-scheduler
readinessProbe :
httpGet :
path : /healthz
port : 10251
resources :
requests :
cpu : '0.1'
securityContext :
privileged : false
volumeMounts : []
hostNetwork : false
hostPID : false
volumes : []
这里需要注意的是,在容器规约中配置的调度器启动命令参数(--scheduler-name)所指定的
调度器名称应该是唯一的。
这个名称应该与 Pod 上的可选参数 spec.schedulerName
的值相匹配,也就是说调度器名称的匹配
关系决定了 Pods 的调度任务由哪个调度器负责。
还要注意,我们创建了一个专用服务账号 my-scheduler
并将集群角色 system:kube-scheduler
绑定到它,以便它可以获得与 kube-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 选举的情况下运行多调度器,你必须执行以下操作:
首先,更新上述 Deployment YAML(my-scheduler.yaml)文件中的以下字段:
--leader-elect=true
--lock-object-namespace=<lock-object-namespace>
--lock-object-name=<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 : k8s.gcr.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 : k8s.gcr.io/pause:2.0
在这种情况下,我们指定此 pod 使用我们部署的 my-scheduler
来调度。
请注意,spec.schedulerName
参数的值应该与 Deployment 中配置的提供给
scheduler 命令的参数名称匹配。
将此文件另存为 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 - 设置 Konnectivity 服务
Konnectivity 服务为控制平面提供集群通信的 TCP 级别代理。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
配置 Konnectivity 服务
接下来的步骤需要出口配置,比如:
apiVersion : apiserver.k8s.io/v1beta1
kind : EgressSelectorConfiguration
egressSelections :
# Since we want to control the egress traffic to the cluster, we use the
# "cluster" as the name. Other supported values are "etcd", and "master".
- name : cluster
connection :
# This controls the protocol between the API Server and the Konnectivity
# server. Supported values are "GRPC" and "HTTPConnect". There is no
# end user visible difference between the two modes. You need to set the
# Konnectivity server to work in the same mode.
proxyProtocol : GRPC
transport :
# This controls what transport the API Server uses to communicate with the
# Konnectivity server. UDS is recommended if the Konnectivity server
# locates on the same machine as the API Server. You need to configure the
# Konnectivity server to listen on the same UDS socket.
# The other supported transport is "tcp". You will need to set up TLS
# config to secure the TCP transport.
uds :
udsName : /etc/kubernetes/konnectivity-server/konnectivity-server.socket
你需要配置 API 服务器来使用 Konnectivity 服务,并将网络流量定向到集群节点:
确保 ServiceAccountTokenVolumeProjection
特性门控
被启用。你可以通过为 kube-apiserver 提供以下标志启用
服务账号令牌卷保护 :
--service-account-issuer=api
--service-account-signing-key-file=/etc/kubernetes/pki/sa.key
--api-audiences=system:konnectivity-server
创建一个出站流量配置文件,比如 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 -out konnectivity.csr
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 : us.gcr.io/k8s-artifacts-prod/kas-network-proxy/proxy-server:v0.0.16
command : ["/proxy-server" ]
args : [
"--logtostderr=true" ,
# This needs to be consistent with the value set in egressSelectorConfiguration.
"--uds-name=/etc/kubernetes/konnectivity-server/konnectivity-server.socket" ,
# The following two lines assume the Konnectivity server is
# deployed on the same machine as the apiserver, and the certs and
# key of the API Server are at the specified location.
"--cluster-cert=/etc/kubernetes/pki/apiserver.crt" ,
"--cluster-key=/etc/kubernetes/pki/apiserver.key" ,
# This needs to be consistent with the value set in 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
# Alternatively, you can deploy the agents as Deployments. It is not necessary
# to have an agent on each node.
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" ,
# Since the konnectivity server runs with hostNetwork=true,
# this is the IP address of the master machine.
"--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 - 为 kubelet 配置证书轮换
本文展示如何在 kubelet 中启用并配置证书轮换。
FEATURE STATE: 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.12.2 - 手动轮换 CA 证书
本页展示如何手动轮换证书机构(CA)证书。
准备开始
你必须拥有一个 Kubernetes 的集群,同时你的 Kubernetes 集群必须带有 kubectl 命令行工具。
如果你还没有集群,你可以通过 Minikube 构建一
个你自己的集群,或者你可以使用下面任意一个 Kubernetes 工具构建:
您的 Kubernetes 服务器版本必须不低于版本 v1.13.
要获知版本信息,请输入
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,之后重启组件。
自此刻起,所创建的所有服务账号都会获得同时包含老的 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
并删除该副本。
更新所有服务账号令牌,使之同时包含老的和新的 CA 证书。
如果在 API 服务器使用新的 CA 之前启动了新的 Pod,这些 Pod
也会获得此更新并且同时信任老的和新的 CA 证书。
base64_encoded_ca = " $( base64 -w0 <path to file containing both old and new CAs>) "
for namespace in $( kubectl get ns --no-headers | awk '{print $1}' ) ; do
for token in $( kubectl get secrets --namespace " $namespace " --field-selector type = kubernetes.io/service-account-token -o name) ; do
kubectl get $token --namespace " $namespace " -o yaml | \
/bin/sed "s/\(ca.crt:\).*/\1 ${ base64_encoded_ca } /" | \
kubectl apply -f -
done
done
重启所有使用集群内配置的 Pods(例如: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 编码的老的和新的证书机构数据。
遵循下列步骤执行滚动更新
重新启动所有其他 被聚合的 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 服务器。
重启调度器以使用新的 CA 证书。
确保控制面组件的日志中没有 TLS 相关的错误信息。
为 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
如果你的集群使用启动引导令牌来添加节点,则需要更新 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
数据,进而不再信任老的 CA 证书。
从 kubeconfig 文件和 --client-ca-file
以及 --root-ca-file
标志所指向的文件
中去除老的 CA 数据,之后重启控制面组件。
重启 kubelet,移除 clientCAFile
标志所指向的文件以及 kubelet kubeconfig 文件中
的老的 CA 数据。
4.12.3 - 管理集群中的 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 工具构建:
要获知版本信息,请输入
kubectl version
.
集群中的 TLS 信任
信任 Pod 中运行的应用程序所提供的 CA 通常需要一些额外的应用程序配置。
你需要将 CA 证书包添加到 TLS 客户端或服务器信任的 CA 证书列表中。
例如,你可以使用 Golang TLS 配置通过解析证书链并将解析的证书添加到
tls.Config
结构中的 RootCAs
字段中。
你可以用你的应用能够访问到的
ConfigMap
的形式来发布 CA 证书。
请求证书
以下部分演示如何为通过 DNS 访问的 Kubernetes 服务创建 TLS 证书。
说明: 本教程使用 CFSSL:Cloudflare's PKI 和 TLS 工具包
点击此处 了解更多信息。
下载并安装 CFSSL
本例中使用的 cfssl 工具可以在 github.com/cloudflare/cfssl/releases 下载。
创建证书签名请求
通过运行以下命令生成私钥和证书签名请求(或 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": "system:node:my-pod.my-namespace.pod.cluster.local",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"O": "system:nodes"
}
]
}
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 名称。
你能看到以下的输出:
2017/03/21 06:48:17 [INFO] generate received request
2017/03/21 06:48:17 [INFO] received CSR
2017/03/21 06:48:17 [INFO] generating key: ecdsa-256
2017/03/21 06:48:17 [INFO] encoded CSR
此命令生成两个文件;它生成包含 PEM 编码
pkcs#10 证书请求的 server.csr
,
以及 PEM 编码密钥的 server-key.pem
,用于待生成的证书。
创建证书签名请求对象发送到 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: kubernetes.io/kubelet-serving
usages:
- digital signature
- key encipherment
- server auth
EOF
请注意,在步骤 1 中创建的 server.csr
文件是 base64 编码并存储在
.spec.request
字段中的。我们还要求提供 “digital signature(数字签名)”,
“密钥加密(key encipherment)” 和 “服务器身份验证(server auth)” 密钥用途,
由 kubernetes.io/kubelet-serving
签名程序签名的证书。
你也可以要求使用特定的 signerName
。更多信息可参阅
支持的签署者名称 。
在 API server 中可以看到这些 CSR 处于 Pending 状态。执行下面的命令你将可以看到:
kubectl describe csr my-svc.my-namespace
Name: my-svc.my-namespace
Labels: <none>
Annotations: <none>
CreationTimestamp: Tue, 21 Mar 2017 07:03:51 -0700
Requesting User: [email protected]
Status: Pending
Subject:
Common Name: my-svc.my-namespace.svc.cluster.local
Serial Number:
Subject Alternative Names:
DNS Names: my-svc.my-namespace.svc.cluster.local
IP Addresses: 192.0.2.24
10.0.34.2
Events: <none>
批准证书签名请求
批准证书签名请求是通过自动批准过程完成的,或由集群管理员一次性完成。
有关这方面涉及的更多信息,请参见下文。
下载证书并使用它
CSR 被签署并获得批准后,你应该看到以下内容:
NAME AGE REQUESTOR CONDITION
my-svc.my-namespace 10m [email protected] Approved,Issued
你可以通过运行以下命令下载颁发的证书并将其保存到 server.crt
文件中:
kubectl get csr my-svc.my-namespace -o jsonpath = '{.status.certificate}' \
| base64 --decode > server.crt
现在你可以将 server.crt
和 server-key.pem
作为键值对来启动 HTTPS 服务器。
批准证书签名请求
Kubernetes 管理员(具有适当权限)可以使用 kubectl certificate approve
和
kubectl certificate deny
命令手动批准(或拒绝)证书签名请求。
但是,如果你打算大量使用此 API,则可以考虑编写自动化的证书控制器。
无论上述机器或人使用 kubectl,批准者的作用是验证 CSR 满足如下两个要求:
CSR 的 subject 控制用于签署 CSR 的私钥。这解决了伪装成授权主体的第三方的威胁。
在上述示例中,此步骤将验证该 Pod 控制了用于生成 CSR 的私钥。
CSR 的 subject 被授权在请求的上下文中执行。
这点用于处理不期望的主体被加入集群的威胁。
在上述示例中,此步骤将是验证该 Pod 是否被允许加入到所请求的服务中。
当且仅当满足这两个要求时,审批者应该批准 CSR,否则拒绝 CSR。
关于批准权限的警告
批准 CSR 的能力决定了群集中的信任关系。这也包括 Kubernetes API 所信任的人。
批准 CSR 的能力不能过于广泛和轻率。
在给予本许可之前,应充分了解上一节中提到的挑战和发布特定证书的后果。
给集群管理员的一个建议
本教程假设已经为 certificates API 配置了签名者。Kubernetes 控制器管理器
提供了一个签名者的默认实现。要启用它,请为控制器管理器设置
--cluster-signing-cert-file
和 --cluster-signing-key-file
参数,
使之取值为你的证书机构的密钥对的路径。
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 pods 将被终止,并且将以受控方式自动创建新的 DaemonSet pods。
更新期间,最多只能有 DaemonSet 的一个 Pod 运行于每个节点上。
执行滚动更新
要启用 DaemonSet 的滚动更新功能,必须设置 .spec.updateStrategy.type
为 RollingUpdate
。
你可能想设置
.spec.updateStrategy.rollingUpdate.maxUnavailable
(默认为 1),
.spec.minReadySeconds
(默认为 0) 和
.spec.maxSurge
(一种 Beta 阶段的特性,默认为 25%)
创建带有 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 :
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- 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 :
# this toleration is to have the daemonset runnable on master nodes
# remove it if your masters can't run pods
- key : node-role.kubernetes.io/master
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 Pods 的节点:
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 - 安装服务目录
安装服务目录扩展 API。
4.14.1 - 使用 Helm 安装 Service Catalog
服务目录(Service Catalog)是 服务目录是一种扩展 API,它能让 Kubernetes 集群中运行的应用易于使用外部托管的的软件服务,例如云供应商提供的数据仓库服务。
服务目录可以检索、供应、和绑定由 服务代理人(Service Brokers)
提供的外部托管服务(Managed Services) ,
而无需知道那些服务具体是怎样创建和托管的。
使用 Helm 在 Kubernetes 集群上安装 Service Catalog。
要获取有关此过程的最新信息,请浏览 kubernetes-incubator/service-catalog 仓库。
准备开始
理解服务目录 的关键概念。
Service Catalog 需要 Kubernetes 集群版本在 1.7 或更高版本。
你必须启用 Kubernetes 集群的 DNS 功能。
如果使用基于云的 Kubernetes 集群或 Minikube ,则可能已经启用了集群 DNS。
如果你正在使用 hack/local-up-cluster.sh
,请确保设置了 KUBE_ENABLE_CLUSTER_DNS
环境变量,然后运行安装脚本。
安装和设置 v1.7 或更高版本的 kubectl ,确保将其配置为连接到 Kubernetes 集群。
安装 v2.7.0 或更高版本的 Helm 。
遵照 Helm 安装说明 。
如果已经安装了适当版本的 Helm,请执行 helm init
来安装 Helm 的服务器端组件 Tiller。
添加 service-catalog Helm 仓库
安装 Helm 后,通过执行以下命令将 service-catalog Helm 存储库添加到本地计算机:
helm repo add svc-cat https://kubernetes-sigs.github.io/service-catalog
通过执行以下命令进行检查,以确保安装成功:
helm search service-catalog
如果安装成功,该命令应输出以下内容:
NAME VERSION DESCRIPTION
svc-cat/catalog 0.0.1 service-catalog API server and controller-manag...
启用 RBAC
你的 Kubernetes 集群必须启用 RBAC,这需要你的 Tiller Pod 具有 cluster-admin
访问权限。
如果你使用的是 Minikube,请使用以下参数运行 minikube start
命令:
minikube start --extra-config= apiserver.Authorization.Mode= RBAC
如果你使用 hack/local-up-cluster.sh
,请使用以下值设置 AUTHORIZATION_MODE
环境变量:
AUTHORIZATION_MODE=Node,RBAC hack/local-up-cluster.sh -O
默认情况下,helm init
将 Tiller Pod 安装到 kube-system
命名空间,Tiller 配置为使用 default
服务帐户。
说明: 如果在运行 helm init
时使用了 --tiller-namespace
或 --service-account
参数,
则需要调整以下命令中的 --serviceaccount
参数以引用相应的名字空间和服务账号名称。
配置 Tiller 以获得 cluster-admin
访问权限:
kubectl create clusterrolebinding tiller-cluster-admin \
--clusterrole= cluster-admin \
--serviceaccount= kube-system:default
在 Kubernetes 集群中安装 Service Catalog
使用以下命令从 Helm 存储库的根目录安装 Service Catalog:
helm install catalog svc-cat/catalog --namespace catalog
helm install svc-cat/catalog --name catalog --namespace catalog
接下来
4.14.2 - 使用 SC 安装服务目录
服务目录(Service Catalog)是 服务目录是一种扩展 API,它能让 Kubernetes 集群中运行的应用易于使用外部托管的的软件服务,例如云供应商提供的数据仓库服务。
服务目录可以检索、供应、和绑定由 服务代理人(Service Brokers)
提供的外部托管服务(Managed Services) ,
而无需知道那些服务具体是怎样创建和托管的。
使用 GCP 服务目录安装程序
工具可以轻松地在 Kubernetes 集群上安装或卸载服务目录,并将其链接到 Google Cloud 项目。
服务目录不仅可以与 Google Cloud 一起使用,还可以与任何类型的托管服务一起使用。
准备开始
了解服务目录
的主要概念。
安装 Go 1.6+ 以及设置 GOPATH
。
安装生成 SSL 工件所需的 cfssl 工具。
服务目录需要 Kubernetes 1.7+ 版本。
安装和设置 kubectl ,
以便将其配置为连接到 Kubernetes v1.7+ 集群。
要安装服务目录,kubectl 用户必须绑定到 cluster-admin 角色。
为了确保这是正确的,请运行以下命令:
kubectl create clusterrolebinding cluster-admin-binding --clusterrole= cluster-admin --user= <user-name>
在本地环境中安装 sc
安装程序在你的本地计算机上以 CLI 工具的形式运行,名为 sc
。
使用 go get
安装:
go get github.com/GoogleCloudPlatform/k8s-service-catalog/installer/cmd/sc
现在,sc
应该已经被安装在 GOPATH/bin
目录中了。
在 Kubernetes 集群中安装服务目录
首先,检查是否已经安装了所有依赖项。运行:
如检查通过,应输出:
Dependency check passed. You are good to go.
接下来,运行安装命令并指定要用于备份的 storageclass
:
sc install --etcd-backup-storageclass "standard"
卸载服务目录
如果您想使用 sc
工具从 Kubernetes 集群卸载服务目录,请运行:
接下来
4.15.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
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 文件,避免 Docker 在容器已经启动之后去
修改 该文件。
注意:
请避免手工更改容器内的 hosts 文件内容。
如果你对 hosts 文件做了手工修改,这些修改都会在容器退出时丢失。
4.15.2 - 验证 IPv4/IPv6 双协议栈
本文分享了如何验证 IPv4/IPv6 双协议栈的 Kubernetes 集群。
准备开始
提供程序对双协议栈网络的支持 (云供应商或其他方式必须能够为 Kubernetes 节点
提供可路由的 IPv4/IPv6 网络接口)
一个能够支持双协议栈 的
网络插件 ,
(如 Calico,Cilium 或 Kubenet)。
启用双协议栈 集群
验证寻址
验证节点寻址
每个双协议栈节点应分配一个 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
a00:100::/24
应该分配一个 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.240.0.5
InternalIP: 2001:1234:5678:9abc::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
a00:100::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,a00:100::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
a00:100::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 : MyApp
spec :
selector :
app : 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 : 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 : MyApp
spec :
ipFamilies :
- IPv6
selector :
app : 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 : MyApp
name : my-service
spec :
clusterIP : fd00::5118
clusterIPs :
- fd00::5118
ipFamilies :
- IPv6
ipFamilyPolicy : SingleStack
ports :
- port : 80
protocol : TCP
targetPort : 80
selector :
app : 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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
说明:
kubectl get svc
命令将仅在 CLUSTER-IP
字段中显示主 IP。
kubectl get svc -l app = MyApp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT( S) AGE
my-service ClusterIP fe80:20d::d06b <none> 80/TCP 9s
使用 kubectl describe
验证服务是否从 IPv4 和 IPv6 地址块中获取了集群 IP。
然后你就可以通过 IP 和端口,验证对服务的访问。
kubectl describe svc -l app = MyApp
Name: my-service
Namespace: default
Labels: app=MyApp
Annotations: <none>
Selector: app=MyApp
Type: ClusterIP
IP Family Policy: PreferDualStack
IP Families: IPv4,IPv6
IP: 10.0.216.242
IPs: 10.0.216.242,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 : MyApp
spec :
ipFamilyPolicy : PreferDualStack
ipFamilies :
- IPv6
type : LoadBalancer
selector :
app : MyApp
ports :
- protocol : TCP
port : 80
检查服务:
kubectl get svc -l app = MyApp
验证服务是否从 IPv6 地址块中接收到 CLUSTER-IP
地址以及 EXTERNAL-IP
。
然后,你可以通过 IP 和端口验证对服务的访问。
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-service LoadBalancer fd00::7ebc 2603:1030:805::5 80:30790/TCP 35s
4.16 - 用插件扩展 kubectl
通过创建和安装 kubectl 插件扩展 kubectl。
本指南演示了如何为 kubectl 安装和编写扩展。
通过将核心 kubectl
命令看作与 Kubernetes 集群交互的基本构建块,
集群管理员可以将插件视为一种利用这些构建块创建更复杂行为的方法。
插件用新的子命令扩展了 kubectl
,允许新的和自定义的特性不包括在 kubectl
的主要发行版中。
准备开始
你需要安装一个可用的 kubectl
可执行文件。
安装 kubectl 插件
插件是一个独立的可执行文件,名称以 kubectl-
开头。
要安装插件,将其可执行文件移动到 PATH
中的任何位置。
你也可以使用 Krew 来发现和安装开源的 kubectl 插件。
Krew 是一个由 Kubernetes SIG CLI 社区维护的插件管理器。
注意: Krew
插件索引 所维护的 kubectl 插件并未经过安全性审查。
你要了解安装和运行第三方插件的安全风险,因为它们本质上时是一些在你的机器上
运行的程序。
发现插件
kubectl
提供一个命令 kubectl plugin list
,用于搜索路径查找有效的插件可执行文件。
执行此命令将遍历路径中的所有文件。任何以 kubectl-
开头的可执行文件都将在这个命令的输出中以它们在路径中出现的顺序显示。
任何以 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
下的警告告诉我们这个插件永远不会被执行。
相反,首先出现在我们路径中的可执行文件 /usr/local/bin/plugins/kubectl-foo
总是首先被 kubectl
插件机制找到并执行。
解决这个问题的一种方法是你确保你希望与 kubectl
一起使用的插件的位置总是在你的路径中首先出现。
例如,如果我们总是想使用 /usr/local/bin/moreplugins/kubectl foo
,
那么在调用 kubectl
命令 kubectl foo
时,我们只需将路径的值更改为 PATH=/usr/local/bin/moreplugins:/usr/local/bin/plugins
。
调用最长的可执行文件名
对于插件文件名而言还有另一种弊端,给定用户路径中的两个插件 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,那就会大大简化安装过程了。
接下来
查看 CLI 插件库示例,查看用 Go 编写的插件的详细示例
如有任何问题,请随时联系 SIG CLI
了解 Krew ,一个 kubectl 插件管理器。
4.17 - 管理巨页(HugePages)
将大页配置和管理为集群中的可调度资源。
FEATURE STATE: Kubernetes v1.23 [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.18 - 调度 GPUs
配置和调度 GPU 成一类资源以供集群中节点使用。
FEATURE STATE: Kubernetes v1.10 [beta]
Kubernetes 支持对节点上的 AMD 和 NVIDIA GPU (图形处理单元)进行管理,目前处于实验 状态。
本页介绍用户如何在不同的 Kubernetes 版本中使用 GPU,以及当前存在的一些限制。
使用设备插件
Kubernetes 实现了设备插件(Device Plugins)
以允许 Pod 访问类似 GPU 这类特殊的硬件功能特性。
作为集群管理员,你要在节点上安装来自对应硬件厂商的 GPU 驱动程序,并运行
来自 GPU 厂商的对应的设备插件。
当以上条件满足时,Kubernetes 将暴露 amd.com/gpu
或 nvidia.com/gpu
为
可调度的资源。
你可以通过请求 <vendor>.com/gpu
资源来使用 GPU 设备,就像你为 CPU
和内存所做的那样。
不过,使用 GPU 时,在如何指定资源需求这个方面还是有一些限制的:
GPUs 只能设置在 limits
部分,这意味着:
你可以指定 GPU 的 limits
而不指定其 requests
,Kubernetes 将使用限制
值作为默认的请求值;
你可以同时指定 limits
和 requests
,不过这两个值必须相等。
你不可以仅指定 requests
而不指定 limits
。
容器(以及 Pod)之间是不共享 GPU 的。GPU 也不可以过量分配(Overcommitting)。
每个容器可以请求一个或者多个 GPU,但是用小数值来请求部分 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 : "k8s.gcr.io/cuda-vector-add:v0.1"
resources :
limits :
nvidia.com/gpu : 1 # requesting 1 GPU
部署 AMD GPU 设备插件
官方的 AMD GPU 设备插件 有以下要求:
Kubernetes 节点必须预先安装 AMD GPU 的 Linux 驱动。
如果你的集群已经启动并且满足上述要求的话,可以这样部署 AMD 设备插件:
kubectl create -f https://raw.githubusercontent.com/RadeonOpenCompute/k8s-device-plugin/r1.10/k8s-ds-amdgpu-dp.yaml
你可以到 RadeonOpenCompute/k8s-device-plugin
项目报告有关此设备插件的问题。
部署 NVIDIA GPU 设备插件
对于 NVIDIA GPUs,目前存在两种设备插件的实现:
官方的 NVIDIA GPU 设备插件
官方的 NVIDIA GPU 设备插件 有以下要求:
Kubernetes 的节点必须预先安装了 NVIDIA 驱动
Kubernetes 的节点必须预先安装 nvidia-docker 2.0
Docker 的默认运行时 必须设置为 nvidia-container-runtime,而不是 runc
NVIDIA 驱动版本 ~= 384.81
如果你的集群已经启动并且满足上述要求的话,可以这样部署 NVIDIA 设备插件:
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta4/nvidia-device-plugin.yml
请到 NVIDIA/k8s-device-plugin 项目报告有关此设备插件的问题。
GCE 中使用的 NVIDIA GPU 设备插件
GCE 使用的 NVIDIA GPU 设备插件 并不要求使用 nvidia-docker,并且对于任何实现了 Kubernetes CRI 的容器运行时,都应该能够使用。这一实现已经在 Container-Optimized OS 上进行了测试,并且在 1.9 版本之后会有对于 Ubuntu 的实验性代码。
你可以使用下面的命令来安装 NVIDIA 驱动以及设备插件:
# 在 COntainer-Optimized OS 上安装 NVIDIA 驱动:
kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/stable/daemonset.yaml
# 在 Ubuntu 上安装 NVIDIA 驱动 (实验性质):
kubectl create -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/stable/nvidia-driver-installer/ubuntu/daemonset.yaml
# 安装设备插件:
kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.12/cluster/addons/device-plugins/nvidia-gpu/daemonset.yaml
请到 GoogleCloudPlatform/container-engine-accelerators 报告有关此设备插件以及安装方法的问题。
关于如何在 GKE 上使用 NVIDIA GPUs,Google 也提供自己的指令 。
集群内存在不同类型的 GPU
如果集群内部的不同节点上有不同类型的 NVIDIA GPU,那么你可以使用
节点标签和节点选择器
来将 pod 调度到合适的节点上。
例如:
# 为你的节点加上它们所拥有的加速器类型的标签
kubectl label nodes <node-with-k80> accelerator = nvidia-tesla-k80
kubectl label nodes <node-with-p100> accelerator = nvidia-tesla-p100
自动节点标签
如果你在使用 AMD GPUs,你可以部署
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
beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=cluster-node-23
Annotations: kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
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 : "k8s.gcr.io/cuda-vector-add:v0.1"
resources :
limits :
nvidia.com/gpu : 1
nodeSelector :
accelerator : nvidia-tesla-p100 # or nvidia-tesla-k80 etc.
这能够保证 Pod 能够被调度到你所指定类型的 GPU 的节点上去。
4.19 - 配置 kubelet 镜像凭据提供程序
配置 kubelet 的镜像凭据提供程序插件
FEATURE STATE: Kubernetes v1.20 [alpha]
从 Kubernetes v1.20 开始,kubelet 可以使用 exec 插件动态检索容器镜像注册中心的凭据。
kubelet 和 exec 插件使用 Kubernetes 版本化 API 通过标准输入输出(标准输入、标准输出和标准错误)通信。
这些插件允许 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 插件,
这里有个样例配置文件你可能最终会使用到:
kind : CredentialProviderConfig
apiVersion : kubelet.config.k8s.io/v1alpha1
# 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
5 - 教程
Kubernetes 文档的这一部分包含教程。每个教程展示了如何完成一个比单个
任务 更大的目标。
通常一个教程有几个部分,每个部分都有一系列步骤。在浏览每个教程之前,
您可能希望将标准化术语表 页面添加到书签,供以后参考。
基础知识
配置
无状态应用程序
有状态应用程序
集群
服务
接下来
如果您想编写教程,请参阅内容页面类型
以获取有关教程页面类型的信息。
5.1 - 你好,Minikube
本教程向你展示如何使用 Minikube 和 Katacoda
在 Kubernetes 上运行一个应用示例。Katacoda 提供免费的浏览器内 Kubernetes 环境。
教程目标
将一个示例应用部署到 Minikube。
运行应用程序。
查看应用日志
准备开始
本教程提供了容器镜像,使用 NGINX 来对所有请求做出回应:
创建 Minikube 集群
点击 启动终端
Launch Terminal
说明: 如果你在本地安装了 Minikube,运行 minikube start
。
在运行 minikube dashboard
之前,你应该打开一个新终端,
在此启动 minikube dashboard
,然后切换回主终端。
在浏览器中打开 Kubernetes 仪表板(Dashboard):
仅限 Katacoda 环境:在终端窗口的顶部,单击加号,然后单击 选择要在主机 1 上查看的端口 。
仅限 Katacoda 环境:输入“30000”,然后单击 显示端口 。
说明: 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= k8s.gcr.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 暴露到集群外部。
镜像 k8s.gcr.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 环境:单击加号,然后单击 选择要在主机 1 上查看的端口 。
仅限 Katacoda 环境:请注意在 service 输出中与 8080
对应的长度为 5 位的端口号。
此端口号是随机生成的,可能与你的不同。
在端口号文本框中输入你自己的端口号,然后单击显示端口。
对应于上面的例子,需要输入 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 - 创建集群
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 。
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.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
这篇文档基于使用 ConfigMap 来配置 Containers 这个任务,提供了一个使用 ConfigMap 来配置 Redis 的真实案例。
教程目标
使用 Redis 配置的值创建一个 ConfigMap
创建一个 Redis Pod,挂载并使用创建的 ConfigMap
验证配置已经被正确应用。
准备开始
真实世界的案例:使用 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://k8s.io/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-config
的文件中。
然后 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://k8s.io/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 - 公开外部 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 应用程序。
成功请求的响应是一条问候消息:
清理现场
要删除服务,请输入以下命令:
kubectl delete services my-service
要删除正在运行 Hello World 应用程序的 Deployment,ReplicaSet 和 Pod,请输入以下命令:
kubectl delete deployment hello-world
接下来
进一步了解将应用程序与服务连接 。
5.4.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 副本。
# SOURCE: 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 的策略。
# SOURCE: 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 跟随者来将其配置为高可用状态,
以满足流量需求。
# SOURCE: 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 。
# SOURCE: 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 :
# the port that this service should serve on
- 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
# SOURCE: 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
。
# SOURCE: https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion : v1
kind : Service
metadata :
name : frontend
labels :
app : guestbook
tier : frontend
spec :
# if your cluster supports it, uncomment the following to automatically create
# an external load-balanced IP for the frontend service.
# type: LoadBalancer
#type: LoadBalancer
ports :
# the port that this service should serve on
- 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.5 - 有状态的应用
5.5.1 - 示例:使用 Persistent Volumes 部署 WordPress 和 MySQL
本示例描述了如何通过 Minikube 在 Kubernetes 上安装 WordPress 和 MySQL。这两个应用都使用 PersistentVolumes 和 PersistentVolumeClaims 保存数据。
PersistentVolume (PV)是一块集群里由管理员手动提供,或 kubernetes 通过 StorageClass 动态创建的存储。
PersistentVolumeClaim (PVC)是一个满足对 PV 存储需要的请求。PersistentVolumes 和 PersistentVolumeClaims 是独立于 Pod 生命周期而在 Pod 重启,重新调度甚至删除过程中保存数据。
警告:
deployment 在生产场景中并不适合,它使用单实例 WordPress 和 MySQL Pods。考虑使用 WordPress Helm Chart 在生产场景中部署 WordPress。
说明:
本教程中提供的文件使用 GA Deployment API,并且特定于 kubernetes 1.9 或更高版本。如果您希望将本教程与 Kubernetes 的早期版本一起使用,请相应地更新 API 版本,或参考本教程的早期版本。
教程目标
创建 PersistentVolumeClaims 和 PersistentVolumes
创建 kustomization.yaml
使用
Secret 生成器
MySQL 资源配置
WordPress 资源配置
应用整个 kustomization 目录 kubectl apply -k ./
清理
准备开始
你必须拥有一个 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 的集群,请参考 this guide 。
创建 kustomization.yaml
创建 Secret 生成器
A 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 的资源配置
以下 manifest 文件描述了单实例 MySQL 部署。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
以下 manifest 文件描述了单实例 WordPress 部署。WordPress 容器将网站数据文件位于/var/www/html
的 PersistentVolume。WORDPRESS_DB_HOST
环境变量集上面定义的 MySQL Service 的名称,WordPress 将通过 Service 访问数据库。WORDPRESS_DB_PASSWORD
环境变量设置从 Secret kustomize 生成的数据库密码。
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:
说明: 设置和绑定 PV 可能要花费几分钟。
响应应如下所示:
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 始终处于挂起状态
运行以下命令以获取 WordPress 服务的 IP 地址:
minikube service wordpress --url
响应应如下所示:
http://1.2.3.4:32406
复制 IP 地址,然后将页面加载到浏览器中来查看您的站点。
您应该看到类似于以下屏幕截图的 WordPress 设置页面。
警告:
不要在此页面上保留 WordPress 安装。如果其他用户找到了它,他们可以在您的实例上建立一个网站并使用它来提供恶意内容。 通过创建用户名和密码来安装 WordPress 或删除您的实例。
清理现场
运行一下命令删除您的 Secret,Deployments,Services and PersistentVolumeClaims:
接下来
5.5.2 - 示例:使用 StatefulSet 部署 Cassandra
本教程描述拉如何在 Kubernetes 上运行 Apache Cassandra 。
数据库 Cassandra 需要永久性存储提供数据持久性(应用 状态 )。
在此示例中,自定义 Cassandra seed provider 使数据库在加入 Cassandra 集群时发现新的 Cassandra 实例。
使用 StatefulSets 可以更轻松地将有状态的应用程序部署到你的 Kubernetes 集群中。
有关本教程中使用的功能的更多信息,
参阅 StatefulSet 。
说明: Cassandra 和 Kubernetes 都使用术语 node 来表示集群的成员。
在本教程中,属于 StatefulSet 的 Pod 是 Cassandra 节点,并且是 Cassandra 集群的成员(称为 ring )。
当这些 Pod 在你的 Kubernetes 集群中运行时,Kubernetes 控制平面会将这些 Pod 调度到 Kubernetes 的
节点 上。
当 Cassandra 节点启动时,使用 seed列表 来引导发现 ring 中其他节点。
本教程部署了一个自定义的 Cassandra seed provider,使数据库可以发现新的 Cassandra Pod 出现在 Kubernetes 集群中。
教程目标
创建并验证 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
的服务,则表示创建失败。
请阅读Debug Services ,以解决常见问题。
使用 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
# These volume mounts are persistent. They are like inline claims,
# but not exactly because the names need to match exactly one of
# the stateful pod volumes.
volumeMounts :
- name : cassandra-data
mountPath : /cassandra_data
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
# do not use these in production until ssd GCEPersistentDisk or other ssd pd
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 :
# 如果你能未经修改地 apply 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 ,
然后 apply 修改后的清单。
# 如果使用本地的 cassandra-statefulset.yaml ,请使用此命令
kubectl apply -f cassandra-statefulset.yaml
验证 Cassandra StatefulSet
1.获取 Cassandra StatefulSet:
```shell
kubectl get statefulset cassandra
```
响应应该与此类似:
```
NAME DESIRED CURRENT AGE
cassandra 3 0 13s
```
StatefulSet
资源会按顺序部署 Pod。
2.获取 Pod 查看已排序的创建状态:
```shell
kubectl get pods -l="app=cassandra"
```
响应应该与此类似:
```shell
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
```
3.运行第一个 Pod 中的 Cassandra nodetool ,以显示 ring 的状态。
```shell
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 的大小。
1.运行以下命令:
```shell
kubectl edit statefulset cassandra
```
此命令你的终端中打开一个编辑器。需要更改的是 replicas
字段。下面是 StatefulSet 文件的片段示例:
```yaml
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
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
```
2.将副本数 (replicas) 更改为 4,然后保存清单。
StatefulSet 现在可以扩展到运行 4 个 Pod。
3.获取 Cassandra StatefulSet 验证更改:
```shell
kubectl get statefulset cassandra
```
响应应该与此类似:
```
NAME DESIRED CURRENT AGE
cassandra 4 4 36m
```
清理现场
删除或缩小 StatefulSet 不会删除与 StatefulSet 关联的卷。
这个设置是出于安全考虑,因为你的数据比自动清除所有相关的 StatefulSet 资源更有价值。
警告: 根据存储类和回收策略,删除 PersistentVolumeClaims 可能导致关联的卷也被删除。
千万不要认为其容量声明被删除,你就能访问数据。
1.运行以下命令(连在一起成为一个单独的命令)删除 Cassandra StatefulSet 中的所有内容:
```shell
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
```
2.运行以下命令,删除你为 Cassandra 设置的 Service:
```shell
kubectl delete service -l app=cassandra
```
Cassandra 容器环境变量
本教程中的 Pod 使用来自 Google container registry
的 gcr.io/google-samples/cassandra:v13
镜像。
上面的 Docker 镜像基于 debian-base ,并且包含 OpenJDK 8。
该映像包括来自 Apache Debian 存储库的标准 Cassandra 安装。
通过使用环境变量,您可以更改插入到 cassandra.yaml
中的值。
Environment variable
Default value
CASSANDRA_CLUSTER_NAME
'Test Cluster'
CASSANDRA_NUM_TOKENS
32
CASSANDRA_RPC_ADDRESS
0.0.0.0
接下来