Kubernetes节点(Node)
Kubernetes通过将容器放入运行在节点(Node)上的 Pod 中来执行工作负载,每个节点可以是虚拟机或物理机器,具体取决于集群的配置。节点包含了运行 Pod 所需的服务,而这些节点则由控制面负责管理。
通常,一个 Kubernetes 集群会包含多个节点,但在学习或资源受限的环境中,集群可能只有一个节点。
节点上的组件主要包括 kubelet、容器运行时以及 kube-proxy。kubelet 是运行在每个节点上的代理,负责与控制面通信,管理 Pod 的生命周期和监控节点状态。容器运行时则负责在节点上运行容器,并提供容器的隔离环境和资源管理功能。kube-proxy 则负责实现 Kubernetes 服务发现和负载均衡机制,以便让集群内的不同 Pod 和服务能够互相通信。
向 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。 否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。
注意: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 地址列表。你只能为每个地址簇指定一个地址。 例如在单协议栈 IPv4 集群中,需要将此值设置为 kubelet 应使用的节点 IPv4 地址。
- 如果你未提供这个参数,kubelet 将使用节点默认的 IPv4 地址(如果有); 如果节点没有 IPv4 地址,则 kubelet 使用节点的默认 IPv6 地址。
- node-labels – 在集群中注册节点时要添加的标签。
- node-status-update-frequency – 指定 kubelet 向 API 服务器发送其节点状态的频率。
手动节点管理
- 你可以使用 kubectl 来创建和修改 Node 对象。
- 如果你希望手动创建节点对象时,请设置 kubelet 标志 –register-node=false。
- 你可以修改 Node 对象(忽略 –register-node 设置)。 例如,你可以修改节点上的标签或并标记其为不可调度。
- 你可以结合使用 Node 上的标签和 Pod 上的选择算符来控制调度。 例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。
- 如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该 Node 之上, 但不会影响任何已经在其上的 Pod。 这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
要标记一个 Node 为不可调度,执行以下命令:
kubectl cordon $NODENAME
说明:
被 DaemonSet 控制器创建的 Pod 能够容忍节点的不可调度属性。 DaemonSet 通常提供节点本地的服务,即使节点上的负载应用已经被腾空, 这些服务也仍需运行在节点之上。
节点状态
一个节点的状态包含以下信息:
地址(Addresses)
状况(Condition)
容量与可分配(Capacity)
信息(Info)
你可以使用 kubectl 来查看节点状态和其他细节信息:
kubectl describe node <节点名称>
心跳
Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。
对于节点,有两种形式的心跳:
更新节点的 .status
kube-node-lease 名字空间中的 Lease(租约)对象。 每个节点都有一个关联的 Lease 对象。
与 Node 的 .status 更新相比,Lease 是一种轻量级资源。 使用 Lease 来表达心跳在大型集群中可以减少这些更新对性能的影响。
kubelet 负责创建和更新节点的 .status,以及更新它们对应的 Lease。
当节点状态发生变化时,或者在配置的时间间隔内没有更新事件时,kubelet 会更新 .status。 .status 更新的默认间隔为 5 分钟(比节点不可达事件的 40 秒默认超时时间长很多)。
kubelet 会创建并每 10 秒(默认更新间隔时间)更新 Lease 对象。 Lease 的更新独立于 Node 的 .status 更新而发生。 如果 Lease 的更新操作失败,kubelet 会采用指数回退机制,从 200 毫秒开始重试, 最长重试间隔为 7 秒钟。
节点控制器
节点控制器是 Kubernetes 控制面组件, 管理节点的方方面面。
节点控制器在节点的生命周期中扮演多个角色。 第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。
第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。 如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。 如果不可用,节点控制器会将该节点从它的节点列表删除。
第三个是监控节点的健康状况。节点控制器负责:
在节点不可达的情况下,在 Node 的 .status 中更新 Ready 状况。 在这种情况下,节点控制器将 NodeReady 状况更新为 Unknown。
如果节点仍然无法访问:对于不可达节点上的所有 Pod 触发 API 发起的逐出操作。 默认情况下,节点控制器在将节点标记为 Unknown 后等待 5 分钟提交第一个驱逐请求。
默认情况下,节点控制器每 5 秒检查一次节点状态,可以使用 kube-controller-manager 组件上的 –node-monitor-period 参数来配置周期。
节点与控制面
节点到控制面
Kubernetes 采用的是中心辐射型(Hub-and-Spoke)API 模式。 所有从节点(或运行于其上的 Pod)发出的 API 调用都终止于 API 服务器。 其它控制面组件都没有被设计为可暴露远程服务。 API 服务器被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求, 并启用一种或多种形式的客户端身份认证机制。 一种或多种客户端鉴权机制应该被启用, 特别是在允许使用匿名请求 或服务账户令牌的时候。
应该使用集群的公共根证书开通节点, 这样它们就能够基于有效的客户端凭据安全地连接 API 服务器。 一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。 请查看 kubelet TLS 启动引导 以了解如何自动提供 kubelet 客户端证书。
想要连接到 API 服务器的 Pod 可以使用服务账号安全地进行连接。 当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。 kubernetes 服务(位于 default 名字空间中)配置了一个虚拟 IP 地址, 用于(通过 kube-proxy)转发请求到 API 服务器的 HTTPS 末端。
控制面组件也通过安全端口与集群的 API 服务器通信。
这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的, 能够在不可信的网络或公网上运行。
控制面到节点
从控制面(API 服务器)到节点有两种主要的通信路径。 第一种是从 API 服务器到集群中每个节点上运行的 kubelet 进程。 第二种是从 API 服务器通过它的代理功能连接到任何节点、Pod 或者服务。
API 服务器到 kubelet
从 API 服务器到 kubelet 的连接用于:
获取 Pod 日志。
挂接(通过 kubectl)到运行中的 Pod。
提供 kubelet 的端口转发功能。
这些连接终止于 kubelet 的 HTTPS 末端。 默认情况下,API 服务器不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击,在非受信网络或公开网络上运行也是不安全的。
为了对这个连接进行认证,使用 –kubelet-certificate-authority 标志给 API 服务器提供一个根证书包,用于 kubelet 的服务证书。
如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 API 服务器和 kubelet 之间使用 SSH 隧道。
最后,应该启用 Kubelet 认证/鉴权 来保护kubelet API。
API 服务器到节点、Pod 和服务
从 API 服务器到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。 这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https: 来运行在安全的 HTTPS 连接上。
不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。 因此,虽然连接是加密的,仍无法提供任何完整性保证。 这些连接 目前还不能安全地 在非受信网络或公共网络上运行。
SSH 隧道
Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。 在这种配置下,API 服务器建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务器) 并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。 这一隧道保证通信不会被暴露到集群节点所运行的网络之外。
说明:
SSH 隧道目前已被废弃。除非你了解个中细节,否则不应使用。 Konnectivity 服务是 SSH 隧道的替代方案。
Konnectivity 服务
特性状态: Kubernetes v1.18 [beta]
作为 SSH 隧道的替代方案,Konnectivity 服务提供 TCP 层的代理,以便支持从控制面到集群的通信。
Konnectivity 服务包含两个部分:Konnectivity 服务器和 Konnectivity 代理, 分别运行在控制面网络和节点网络中。 Konnectivity 代理建立并维持到 Konnectivity 服务器的网络连接。 启用 Konnectivity 服务之后,所有控制面到节点的通信都通过这些连接传输。
控制器
在机器人技术和自动化领域,控制回路(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 来实现这点。
租约(Lease)
分布式系统通常需要租约(Lease);租约提供了一种机制来锁定共享资源并协调集合成员之间的活动。 在 Kubernetes 中,租约概念表示为 coordination.k8s.io API 组中的 Lease 对象, 常用于类似节点心跳和组件级领导者选举等系统核心能力。
节点心跳
Kubernetes 使用 Lease API 将 kubelet 节点心跳传递到 Kubernetes API 服务器。 对于每个 Node,在 kube-node-lease 名字空间中都有一个具有匹配名称的 Lease 对象。 在此基础上,每个 kubelet 心跳都是对该 Lease 对象的 update 请求,更新该 Lease 的 spec.renewTime 字段。 Kubernetes 控制平面使用此字段的时间戳来确定此 Node 的可用性。
领导者选举
Kubernetes 也使用 Lease 确保在任何给定时间某个组件只有一个实例在运行。 这在高可用配置中由 kube-controller-manager 和 kube-scheduler 等控制平面组件进行使用, 这些组件只应有一个实例激活运行,而其他实例待机。
API 服务器身份
特性状态: Kubernetes v1.26 [beta]
从 Kubernetes v1.26 开始,每个 kube-apiserver 都使用 Lease API 将其身份发布到系统中的其他位置。 虽然它本身并不是特别有用,但为客户端提供了一种机制来发现有多少个 kube-apiserver 实例正在操作 Kubernetes 控制平面。kube-apiserver 租约的存在使得未来可以在各个 kube-apiserver 之间协调新的能力。
你可以检查 kube-system 名字空间中名为 kube-apiserver-<sha256-hash> 的 Lease 对象来查看每个 kube-apiserver 拥有的租约。你还可以使用标签选择算符
apiserver.kubernetes.io/identity=kube-apiserver: kubectl -n kube-system get lease -l apiserver.kubernetes.io/identity=kube- apiserver
租约名称中使用的 SHA256 哈希基于 API 服务器所看到的操作系统主机名生成。 每个 kube-apiserver 都应该被配置为使用集群中唯一的主机名。 使用相同主机名的 kube-apiserver 新实例将使用新的持有者身份接管现有 Lease,而不是实例化新的 Lease 对象。 你可以通过检查 kubernetes.io/hostname 标签的值来查看 kube-apisever 所使用的主机名:
kubectl -n kube-system get lease apiserver-07a5ea9b9b072c4a5f3d1c3702 -o yaml apiVersion: coordination.k8s.io/v1 kind: Lease metadata: creationTimestamp: "2023-07-02T13:16:48Z" labels: apiserver.kubernetes.io/identity: kube- apiserver kubernetes.io/hostname: master-1 name: apiserver- 07a5ea9b9b072c4a5f3d1c3702 namespace: kube-system resourceVersion: "334899" uid: 90870ab5-1ba9-4523-b215-e4d4e662acb1 spec: holderIdentity: apiserver- 07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35 -440e-8676-7844977d3a05 leaseDurationSeconds: 3600 renewTime: "2023-07-04T21:58:48.065888Z"
kube-apiserver 中不再存续的已到期租约将在到期 1 小时后被新的 kube-apiserver 作为垃圾收集。
你可以通过禁用 APIServerIdentity 特性门控来禁用 API 服务器身份租约。
工作负载
你自己的工作负载可以定义自己使用的 Lease。例如, 你可以运行自定义的控制器, 让主要成员或领导者成员在其中执行其对等方未执行的操作。 你定义一个 Lease,以便控制器副本可以使用 Kubernetes API 进行协调以选择或选举一个领导者。 如果你使用 Lease,良好的做法是为明显关联到产品或组件的 Lease 定义一个名称。 例如,如果你有一个名为 Example Foo 的组件,可以使用名为 example-foo 的 Lease。
如果集群操作员或其他终端用户可以部署一个组件的多个实例, 则选择名称前缀并挑选一种机制(例如 Deployment 名称的哈希)以避免 Lease 的名称冲突。
你可以使用另一种方式来达到相同的效果:不同的软件产品不相互冲突。
云控制管理器
特性状态: Kubernetes v1.11 [beta]
使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。 Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。
组件 cloud-controller-manager 是指云控制器管理器, 一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager)允许你将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑, cloud-controller-manager 组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
cloud-controller-manager 组件是基于一种插件机制来构造的, 这种机制使得不同的云厂商都能将其平台与 Kubernetes 集成。
设计
云控制器管理器以一组多副本的进程集合的形式运行在控制面中,通常表现为 Pod 中的容器。每个 cloud-controller-manager 在同一进程中实现多个控制器。