3.4 节点资源
Kubernetes把工作节点也抽象成了名为Node的API资源,但在现实中,工作节点是指需要运行kubelet、kube-proxy和以docker或containerd为代表的容器运行时这3个关键组件的物理服务器或虚拟机,其核心任务在于以Pod形式运行工作负载,而这些工作负载将消耗节点上的计算资源(CPU和内存),必要时还会占用一定的存储资源。显然,在创建一个Node资源对象时,Kubernetes内部无法真正构建出这个主机设备来,而是仅创建了一个资源对象来代表该主机,真正的主机设备需要由集群外部的云服务商创建或由管理员手动创建。
3.4.1 节点心跳与节点租约
节点控制器负责Node对象生命周期中的多个管理任务,包括节点注册到集群时的CIDR分配、与服务器交互以维护可用节点列表,以及监控节点的健康状态等。对于每一个Node对象,节点控制器都会根据其元数据字段metadata.name执行健康状态检查,以验证节点是否可用。可用的节点意味着它已经运行kubelet、kube-proxy和容器运行时,满足了运行Pod的基本条件。对于那些不可用的节点,Kubernetes将持续对其进行健康状态检测,直到变为可用节点或由管理员手动删除相应的Node对象为止。
kubelet是运行于节点之上的主代理程序,它负责从API Server接收并执行由自身承载的Pod管理任务,并需要向Master上报自身运行状态(心跳消息)以维持集群正常运行。在Kubernetes 1.13版本之前,节点心跳通过NodeStatus信息每10秒发送一次。如果在参数node-monitor-grace-period指定的时长内(默认为40秒)没有收到心跳信息,节点控制器将把相应节点标记为NotReady,而如果在参数pod-eviction-timeout指定的时长内仍然没有收到节点的心跳信息,则节点控制器将开始从该节点驱逐Pod对象。
考虑到节点之上维持的容器镜像和存储卷等信息,NodeStatus信息在节点上有着较多镜像和存储卷的场景中可能会变得较大,进而也必将影响到etcd的存储效率,因此Kubernetes 1.13版本引入了与NodeStatus协同工作的更加轻便且可扩展的心跳指示器——节点租约(node lease)。每个节点的kubelet负责在专用的名称空间kube-node-lease中创建一个与节点同名的Lease对象以表示心跳信息,该资源隶属于名为coordination.k8s.io的新内置API群组。
~$ kubectl get leases -n kube-node-lease NAME HOLDER AGE k8s-master01.ilinux.io k8s-master01.ilinux.io 3d k8s-master02.ilinux.io k8s-master02.ilinux.io 3d k8s-master03.ilinux.io k8s-master03.ilinux.io 3d k8s-node01.ilinux.io k8s-node01.ilinux.io 3d k8s-node02.ilinux.io k8s-node02.ilinux.io 3d k8s-node03.ilinux.io k8s-node03.ilinux.io 3d
节点租约与NodeStatus协同工作的逻辑如下:
▪Kubelet定期更新自己的Lease对象,默认为10秒钟;
▪Kubelet定期(默认为10秒)计算一次NodeStatus,但并不直接上报给Master;
▪仅NodeStatus发生变动,或者已经超过了由参数node-status-update-period指定的时长(默认为5分钟)时,kubelet将发送NodeStatus心跳给Master。
无论是NodeStatus还是Lease对象的更新,都会被节点控制器视为给定的kubelet健康状态信息,但承载节点信息的NodeStatus的平均频次被大大降低,从而显著降低了etcd存储数据的压力,有效改善了系统性能。
3.4.2 节点状态
节点状态信息可使用kubectl describe nodes [NODE]命令予以打印,它通常包括节点地址(Addresses)、系统属性(System Info)、租约(Lease)、污点(Taints)、不可调度性(Unschedulable)、状况(Conditions)、系统容量(Capacity)、已分资源量(Allocated resources)、可分配容量(Allocatable)和Pod的可用地址池(PodCIDR和PodCIDRs)等。
节点地址包括节点IP地址和主机名,其中节点地址包括InternalIP和ExternalIP,前者用于集群内部通信,后者是能够从集群外部路由并访问的IP地址。Pod可用地址池则是指为当前节点中运行的Pod对象分配IP地址的CIDR格式的网络,若存在多个可用CIDR网络,则将其保存在PodCIDRs属性中。系统信息用于描述主机ID、主机操作系统及标识(UUID)、内核版本、平台架构、kubelet程序版本、kube-proxy程序版本和容器运行时及版本等节点通用信息,它们由kubelet从其所在的节点收集而来。
系统容量用于描述节点上的总体可用资源:CPU、临时存储(指节点本地可被容器作为存储卷使用的存储空间)、大内存页、内存空间,以及可以调度到节点上的Pod对象的最大数量等,可分配容量则描述这些资源剩余总量。为了便于同Kubernetes上的“资源”区分开来,CPU和内存通常被称为计算资源,而临时存储空间则被称为存储资源。
Capacity: cpu: 2 ephemeral-storage: 39043456Ki hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 1941648Ki pods: 110 Allocatable: cpu: 2 ephemeral-storage: 35982448991 hugepages-1Gi: 0 hugepages-2Mi: 0 memory: 1839248Ki pods: 110
已分配资源量用于描述当前节点上CPU、内存和临时存储资源的已分配比例。Kubernetes系统上的每个Pod在创建时可分别声明其计算资源及存储资源的需求量(request)和限制量(limit),需求量表示运行时一个Pod必须确保的某项最小资源,而限制量表示该Pod能够申请占用的某项资源的上限。在已分配资源量中,Requests代表所有Pod对象声明的资源需求量之和所占节点资源的比例,而Limits则表示所有Pod对象声明的资源限制量之和所占节点资源的比例。
Allocated resources: (Total limits may be over 100 percent, i.e., overcommitted.) Resource Requests Limits -------- -------- ------ cpu 100m (5%) 100m (5%) memory 50Mi (2%) 50Mi (2%) ephemeral-storage 0 (0%) 0 (0%)
Kubernetes调度器负责确保每个节点上所有Pod对象的总容量需求不会超过节点拥有容量,它计算已有的资源占用量时包括所有由Scheduler调度以及kubelet自身启动的Pod对象,但不包括由容器运行时自行启动的容器,也不包括容器外运行的任何进程。若需要为非Pod进程显式保留资源,需要使用--system-reserved和--reserved-cpus等选项进行定义。
Conditions字段则用于描述节点当前所处的“境况”或者“条件”,每个条件都需要用布尔型值来表达其满足与否的状态,可用条件及其意义如表3-2所示。
一旦Ready条件的值不为True的时长超过kube-controller-manager程序的--pod-eviction-timeout选项指定的值(默认为5分钟),则该节点上的所有Pod对象都将被节点控制器计算删除。不过,当kubelet无法同Master通信时,必然无法接收到删除Pod对象的指令,但调度器可能已经将这些Pod的替代实例指派到了其他健康的节点之上运行。
3.4.3 手动管理Node资源与节点
借助kubconfig配置文件连接并认证到API Server后,kubelet默认会主动将自身注册到API Server,创建Node对象,并报告自身的CPU资源和内存资源的容量。若管理员希望手动创建Node对象,则应该将--register-node选项的值设定为False,同时需要手动设置节点的CPU、内存和临时存储等资源的容量。
下面的配置清单定义了名为temp-node.ilinux.io的Node对象,定义中仅给出了Pod可用地址池,并未明确定义节点的容量及其他属性值。
apiVersion: v1 kind: Node metadata: name: temp-node.ilinux.io spec: podCIDR: 10.244.6.0/24 podCIDRs: [10.244.6.0/24]
使用命令式对象配置或声明式对象配置即可把该资源创建于集群之上,那些未明确定义的字段通常会以默认值填充。命令及响应结果如下所示:
~$ kubectl create -f node-demo.yaml node/temp-node.ilinux.io created
随后可运行get命令打印该Node对象的状态信息,若主机名temp-node.ilinux.io无法正常解析,或解析结果对应的地址不可达,则其STATUS为Unknow,且1分钟之后变为NotReady。
~$ kubectl get nodes/temp-node.ilinux.io NAME STATUS ROLES AGE VERSION temp-node.ilinux.io Unknown <none> 11s
通过describe命令打印出的详细描述信息可以看出,该节点并不存在Capacity和Allocable状态信息,Conditions中的各条件均为Unknown,未设置Lease对象,且System Info的各属性值亦为空值。限于篇幅,下面的命令只给出部分结果。
~ $ kubectl describe node/temp-node.ilinux.io …… Lease: HolderIdentity: <unset> AcquireTime: <unset> RenewTime: <unset> ……
管理员随后部署一个名为temp-node.ilinux.io的工作节点,该名称可被集群正确解析到该节点的IP地址,关闭kubelet的自动注册功能,将其连接、认证到API Server之上即可将该工作节点以手动创建Node对象的方式加入集群中。若不打算加入对应的工作节点,就需要在测试完成之后将对象删除,以避免节点控制器持续对其进行健康状态监测。
~$ kubectl delete node/temp-node.ilinux.io node "temp-node.ilinux.io" deleted
考虑到系统维护或硬件升级等原因,管理员有时候需要手动重启或下线某个工作节点,安全的操作步骤是先手动禁止调度器继续向该节点调度新的Pod对象以封锁(cordon)该节点,但封锁操作并不会影响节点上现有的Pod对象,接下来还需要正常逐出该节点上运行着的工作负载以“排空”(drain)该节点。
封锁节点的命令是kubectl cordon,它专用于Node对象,因此命令中无须指明资源类型。例如,下面的命令可封锁k8s-node03节点。被封锁的节点的状态会在Ready后多一个SchedulingDisabled。
~$ kubectl cordon k8s-node03.ilinux.io node/k8s-node03.ilinux.io cordoned ~$ kubectl get nodes/k8s-node03.ilinux.io NAME STATUS ROLES AGE VERSION k8s-node03.ilinux.io Ready,SchedulingDisabled <none> 24d v1.17.3
注意
封锁工作节点对DaemonSet控制器创建的Pod对象无效。
排空节点的目的是确保正常终止Pod对象,因此容器应该处理SIGTERM信号以关闭与客户端的活动连接,并干净彻底地提交或回滚数据库事务等,随后才能安全地由其他工作节点启动的同类实例所替代。
~$ kubectl drain nodes/k8s-node03.ilinux.io node/k8s-node03.ilinux.io already cordoned node/k8s-node03.ilinux.io drained
由命令结果可以看出,排空命令自身也会先封锁目标节点而后再进行排空操作,即完成封锁和排空两个步骤,故而不必事先进行单独的封锁操作。不过,仅期望封锁工作节点时,cordon命令显然更适用。随后,无论是运行cordon还是drain命令,若期望工作节点回归正常工作状态,都需要使用uncordo命令对节点进行解封。
~$ kubectl uncordon nodes/k8s-node03.ilinux.io node/k8s-node03.ilinux.io uncordoned
需要注意的是,drain默认只能排空受控制器(如Deployment、DaemonSet或StatefulSet等)管理的Pod对象,而不受控于控制器的Pod(例如静态Pod)则会阻止命令的运行。如果要忽略这种阻止操作,可以为drain附加--force选项,以清理系统级Pod对象。