从资源管理角度认识 K8S

https://developer.aliyun.com/article/778848

 

 

简介: 笔者认为应用开发者为了适应云原生趋势,需要掌握必要的K8S基础知识点,详细介绍在《从应用开发角度认识K8S》: https://developer.aliyun.com/article/778441。

作者个人介绍
刘晨 Lorraine 坐标Fintech,精通持续集成与发布,曾具有全平台100+应用持续部署持续发布实战经验,现在立志于成为K8S玩家。

 


1.jpeg

前言

大家好,笔者最近学习和整理K8S知识点的时候,一直以多维角度重新K8S,笔者认为应用开发者为了适应云原生趋势,需要掌握必要的K8S基础知识点,详细介绍在https://developer.aliyun.com/article/778441
笔者做为团队的Devops,承担着管理K8S集群的职责,本篇就从资源管理角度重新认识K8S。

资源类型

在物理机或虚拟机集群时代,我们管理的资源主要是以主机为单元的集群。单个主机节点,可以是一台物理机,或者是基于Hypervitor虚拟化中间件的VM,资源类型主要包括CPU,内存,磁盘IO,以及网络带宽。

K8S集群一般由至少一个Master节点和若干个Worker节点组成。Master节点包含了调度器Scheduler,控制器Controller,资源对象接口APIServer以及持久化存储Etcd。Worker节点通过Kubelet跟Master节点通信,其资源以及负载Pod受Master的调度以及控制。K8S本身架构是C/S模式,即worker节点的资源管理与调度都由Master节点控制。

认识K8S集群资源时,不再按照传统方式以节点单元切分,笔者选择工作负载,存储以及网络这三个方面介绍资源类型,是基于K8S提供的核心资源对象Pod,PVC/PV以及Service/Ingress实现了最基本的集群资源管理与容器编排。在K8S集群中,CPU以及内存资源定义包含在Pod内,用户无法从K8S直接管理。PVC/PV模式解耦了存储资源与节点资源。Service提供了自动服务发现,使得K8S集群具备自愈能力。Ingress提供了对集群网络流量的控制,使得K8S可以一体化考虑工作负载与网络负载,达到弹性伸缩。

工作负载

  • K8S通过Pod来管理一组容器组的生命周期,Pod是K8S调度的最小单元。应用服务部署与调度也是以Pod为单元,Pod也可称之为工作负载。对于无状态的应用部署,以ReplicaSet定义与管理;对于有依赖关系,有状态的应用部署,以StatefulSet定义与管理。ReplicaSet与StatefulSet又是一个有意思的话题,笔者会在后续系列详细介绍。应用所需的CPU/内存资源定义在Pod内,因为CPU/内存资源是容器级别的,由Pod的spec->containers->resources定义。
  • K8S提供了requests和limits两个配置参数来定义资源的范围和额度。

requests定义了工作负载的资源下限,是容器启动时K8S资源分配的默认值。

limits定义了工作负载的资源上限,是容器运行时K8S资源预分配的额度。

  • 容器共享所在Pod的存储Volume,网络命名空间以及PID命名空间,容器拥有CPU/内存资源分配与资源额度。在进行资源管理时,要区分CPU/内存资源分配额度和工作负载实际的CPU/内存利用率。K8S集群信息控制台一般展示工作负载定义的CPU/内存分配额度。

2.png

存储

  • PVC全称为PersistentVolumeClaim,持久化卷声明;PV全称为PersistentVolume,持久化卷。顾名思义,持久化卷是K8S描述各种存储资源类型(块存储,NAS,以及对象存储)的资源对象定义。基于各个云厂商提供的存储管理插件,可以直接使用IaaS层的存储资源作为K8S集群存储。
  • 看起来PV抽象就已足够解决K8S存储资源适配各个云厂商的IaaS存储资源,为什么还要再多此一举搞一个PVC资源对象抽象呢?PVC类似OOP中的抽象类。在开发中使用抽象类通常是想解耦对象调用与对象实现。Pod绑定PVC而非PV,是将Pod部署与PV资源分配解耦。

Pod部署一般属于研发团队应用部署的子环节,由开发人员控制。PV存储资源定义与分配则多属于devops团队,由集群管理员控制。

Pod和PVC是命名空间访问控制范围内的资源对象,PV则是集群访问控制范围内的资源对象。

  • PVC/PV模式解耦了存储资源与节点资源,即由PVC/PV定义的存储资源独立于节点资源,通过动态绑定方式随工作负载在节点资源间迁移。具体细节依赖于云厂商提供的存储插件的实现方式。

网络

  • Service实现了服务端自动发现机制,即提供应用服务的一组Pod可以通过同一个服务域名访问地址对外提供服务,Pod的新增或者销毁不会影响该服务整体服务性能。工作负载通过Service绑定了一个虚拟IP以及端口,对于Pod分配的节点资源并不敏感,解耦了Service与节点网络资源,使得整个集群具备负载迁移,故障自愈的能力。
  • Ingress与Service对象不同,是K8S对集群外部提供服务的一种资源对象。在K8S出现之前,我们对于负载与流量控制,多是采用Nginx反向代理和负载均衡,横向扩展服务端处理负载的能力。Ingress本质上就是一个NginxPod,这个Pod也是通过Service对象暴露出来,基于LoadBalancer模式对外提供反向代理与负载均衡。Ingress很像一个集群的路由器和访问入口,通过在ingress配置路由规则绑定工作负载与对应域名解析路径,达到只使用一个外部IP就能将多个集群内部的服务暴露出来,从而节省IP资源。

Pod调度与管理

K8S做为当前最流行的容器编排平台,提供了平台级别的弹性伸缩以及故障自愈等解决方案。K8S集群基于C/S架构,由Master统一控制管理集群资源与负载均衡。Pod是K8S调度的最小单元,我们要掌握集群资源管理与调度就要从Pod调度与管理开始。

健康检查

  • K8S要掌握应用服务运行状态,才有可能进行平台级别的弹性伸缩和故障自愈。最简单的做法就是不断检查容器进程是否处于Running状态。如果检查到容器进程失败,就不断自动尝试重启进程。很多情况下,重启进程就能解决问题,所以这种健康检查虽然简单,但是十分有效和必要。
  • 但是,如果一个java应用运行时抛出了OOM异常或者死锁了,但JVM进程仍然在运行中;Pod仍处于Running状态,但应用进程已经无法提供服务了。这种情况,上述健康检查就无法处理了,K8S提供的
    livenessProbe可以捕获到应用服务级别的异常状态,全面掌握应用服务运行是否健康。

livenessProbe类似进程健康检查,都是对容器进程进行健康检查,当检测到失败时,就重启该进程,以达到自动修复。

不同的是,livenessProbe是通过调用应用服务定义的HTTP GET API来连接Pod的暴露的IP和Port,通过请求的返回码是否属于200~399来判定容器进程运行状态的。

除了HTTP方式,还可以通过TCP Socket是否联通成功判定应用服务运行状态。

这种来自K8S而不是应用服务内部的应用运行状态判定逻辑,使得K8S能够掌握应用服务级别的健康状态。

  • 当负载过大时,即使容器进程一直处于健康状态,仍有可能出现该应用服务无法正常提供服务。这种情况,K8S是通过readinessProbe进行检测的。

ReadinessProbe提供的接入方式与livenessProbe一样,可以通过HTTP GET API或TCP Socket Connection方式由Kubelet探针将Pod工作负载的情况上报给K8S管理节点。

当检测到readinessProbe失败,应用服务进程无法正常处理请求时,该Pod不再被重启,而是从Service端点摘除,不再接收Service的请求负载,类似流量降级,已确保该Pod可以正确处理已接收的请求负载。

自动调度策略

  • 2015年Google开源K8S时,提出了最初始的云原生定义,即应用容器化、面向微服务架构、应用支持容器的编排调度。容器编排调度是K8S最核心的技术。当面对一个成百上千的微服务容器集群时,Pod调度与资源管理就成了一件复杂的事情。Pod内的一组容器在运行时具有相关性,运行在同一节点,共享节点资源。当应用负载发现变化时,容器对节点资源消耗也随之发生变化,节点资源的容量与可用性也会影响应用服务的性能与稳定性。
  • K8S的Scheduler组件基于APIServer定义的Pod资源对象和Kubelet上报的各个节点资源的使用情况,来选择合适的节点进行调度。Scheduler控制Pod的创建,弹性伸缩以及遇到故障时的负载迁移。Scheduler基于容器运行时依赖关系,资源需求设置以及默认调度策略进行决策。默认的调度策略通常会考虑调度的节点可以保证工作负载具有高可用,高性能以及低延迟。所以,除非对于节点选择具有特殊使用用途,否则建议用户一般直接采用默认调度策略。
  • 定制化Scheduler可以操作集群的配置文件,如下所示的JSON文件。其中predicates表示的是调度Pod时只考虑满足上述规则的节点资源。priorities表示的是对于满足predicates规则筛选的节点资源的集合按照priorities进行权重排序,选取权重最高的节点资源进行调度。例如,Scheduler会按照如下示例所示,优先选择资源需求最低的节点进行调度。
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [ 
        {"name" : "PodFitsHostPorts"},
        {"name" : "PodFitsResources"},
        {"name" : "NoDiskConflict"},
        {"name" : "NoVolumeZoneConflict"},
        {"name" : "MatchNodeSelector"},
        {"name" : "MaxEBSVolumeCount"},
        {"name" : "MaxAzureDiskVolumeCount"},
        {"name" : "checkServiceAffinity"},
        {"name" : "PodToleratesNodeNoExecuteTaints"},
        {"name" : "MaxGCEPDVolumeCount"},
        {"name" : "MatchInterPodAffinity"},
        {"name" : "PodToleratesNodeTaints"},
        {"name" : "HostName"}
        ],
"priorities" : [
        {"name" : "LeastRequestedPriority", "weight" : 2},
        {"name" : "BalancedResourceAllocation", "weight" : 1},
        {"name" : "ServiceSpreadingPriority", "weight" : 1},
        {"name" : "EqualPriority", "weight" : 1}
        ]
}
  • 当Scheduler检测到APIServer生成的Pod资源对象定义时,首先会通过Predicates筛选出符合规则的节点资源集合;然后依据Priority对这些节点资源进行权重排序,选出最优节点;分配节点资源创建Pod。

    3.png

 

Scheduler调度流程 From Kubernetes-Patterns

资源配置与限制

  • K8S集群通常会提供足够的资源容量供Scheduler进行Pod调度。Pod一般会被调度到资源容量大于Pod资源需求的节点上。一般情况下,节点OS和K8S管理组件会预分配一些节点资源,可分配的资源容量通常小于节点资源总量。Scheduler可以调度的资源配置是指节点可分配资源,也称节点容量。它的计算方式为:
Allocatable Capacity= Node Capacity - Kube-Reserved - System-Reserved

Allocatable Capacity为Scheduler可为应用服务Pod分配的节点资源

Node Capacity为节点资源总量

Kube-Reserved为K8S的后台进程预留资源,例如Kubelet,CRI,CNI等组件

System-Reserved为节点的操作系统后台进程预留资源,例如sshd, udev等

  • 在上文介绍工作负载时,我们提到可以通过requests和limits来确定容器运行资源消耗的用量和额度。因为节点资源并不能全部用来做资源调度,建议在定义Pod模版时,最好对容器的resources->requests和resources->limits都做明确的定义,以防止工作负载与K8S组件竞争资源导致调度失败。
  • Limits定义了容器资源使用的上限,Requests定义了容器资源初始化配置,一般启动容器时按照requests分配资源。容器运行时,资源消耗一般比requests分配的少,如下图所示。
    4.png
  • 这种金字塔型的资源分配方式存在着不小的资源碎片的情况。当工作负载竞争资源时,K8S提供了三种级别的服务质量保证。
    Best-Effort:Pod不设置requests和limits。这种Pod的QOS优先级最低,当节点资源调度不足或者竞争冲突时,优先销毁或迁移该类Pod。

Burstable:Pod设置requests和limits,但是requests小于limits。这种Pod具有最低的资源保证,即当节点资源竞争,不再有Best-Effort这种Pod时,优先销毁或迁移该类Pod。

Guaranteed:Pod设置requests和limits,且requests 等于limits。这种Pod具有最高的资源保证。QOS级别高于Burstable和Best-Effort。所以,建议在定义应用服务的Pod模版时,对于容器资源保证,服务质量要求高的Pod,尽量配置合适的requests和limits。

服务发现

运行在K8S集群的应用服务多是基于微服务架构的分布式系统。服务与服务之间常常具有互相调用关系。当调度应用服务Pod时,Scheduler会选择最佳的节点进行资源分配创建Pod,在启动容器前,随机分配ClusterIP地址给这个Pod。所以,当另一个应用服务Pod想要与该应用服务Pod通信,很难获取到这个随机分配的ClusterIP信息。

客户端自动发现

传统的分布式系统,比如ZooKeeper,常常使用的是客户端发现方式,进行服务间自动发现。客户端服务内置了可以发现服务注册中心以及选择其中某个服务实例进行通信的探针Agent。服务端服务实例将自己的状态上报到服务注册中心,客户端服务通过查询服务注册中心信息,选择并唤醒响应的服务实例进行交互。

5.png

 

客户端服务发现ByAgent

服务端自动发现

  • K8S实现的服务发现是基于服务端方式,即服务端Pod要主动上报自己的服务容量到服务注册中心。客户端Pod要能访问服务注册中心,并且可以通过服务注册中心提供服务信息,可以访问到响应服务端Pod。客户端Pod通过代理服务,使用一个恒定不变的虚拟IP对同一个服务访问,而不在意提供服务的Pod是哪个。

    6.png

 

服务端发现ByProxy

  • K8S实现服务端发现逻辑的是Service资源对象。Service通过Pod selector和port number定义,可以给一组Pod绑定一个虚拟IP,也称为clusterIP,示例如下:
apiVersion: v1
kind: Service
metadata:
  name: index-helm
  namespace: bss-dev
spec:
  clusterIP: 172.21.7.94
  ports:
    - name: http
      port: 8081 //service对外提供服务的端口号
      protocol: TCP
      targetPort: 8081 //Pod监听的端口号
  selector:
    app.kubernetes.io/instance: index //spec->selector绑定了对应的Pod
    app.kubernetes.io/name: helm
  sessionAffinity: None
  type: ClusterIP //ClusterIP 是默认service类型
  • 既然ClusterIP都是在Pod启动之后随机分配的,那么其他服务Pod是如何发现这个ClusterIP并与其通信的呢?主要有两种方式:
    环境变量

当Pod被创建后,与之绑定的Service服务对象也随之被创建,并且立即开始监听绑定的端口号。与Service相关的ClusterIP和Port值以环境变量方式自动设置到Pod中,该应用服务就能通过clusterIP和port对外提供服务了。

由于Pod启动以后,无法再将service对应的环境变量注入,所以,基于环境变量绑定clusterIP和port只能发生在Pod启动过程。

DNS查询

K8S提供了一个平台级别的DNS服务,可以配置给所有Pod使用。当一个Service资源对象被创建,DNS服务可以绑定一个DNS访问地址到对应Pod,供访问使用。这个DNS服务管理着应用服务DNS访问地址与Pod启动时分配的ClusterIP和port的对应关系,负责解析从该DNS访问地址过来的流量负载到对应的Pod上。

如果客户端服务已知ServiceName和对应的Namespace,就可以直接通过内部域名地址service-name.namsapce.svc.cluster.local直接访问应用服务Pod。

service-name是service对象定义的名称

namespace是service和pod所在的命名空间名

svc代表这是一个service资源

cluster.local是K8S的coreDNS服务默认集群内部访
问地址域名前缀

结语

笔者最近在整理K8S入门的基础知识点,本篇做为“从应用开发角度认识K8S”的姐妹篇,从资源管理角度重新认识了K8S。K8S平台的用户一般分为应用开发和集群管理员。集群管理员在掌握K8S的时候,更多时候要从集群层面,资源层面,性能层面多维度认识K8S,这时就需要我们掌握和了解最基本的Pod调度与资源管理,存储与网络资源以及服务与流量管理等知识点。

本文介绍的内容算是抛砖引入,如果读者有兴趣继续深入学习相关知识,可以参考学习
https://medium.com/@kumargaurav1247/components-of-kubernetes-architecture-6feea4d5c712

https://medium.com/@yashbindlish1/under-the-hood-an-introduction-to-kubernetes-architecture-bb9d8599f837

https://developers.redhat.com/blog/2020/05/11/top-10-must-know-kubernetes-design-patterns/

posted @ 2024-01-02 12:35  张同光  阅读(17)  评论(0编辑  收藏  举报