一、k8s的架构及工作原理

基本架构

1. Master

K8S中的Master是集群控制节点,负责整个集群的管理和控制,其中master节点的基本组件有:

  • apiserver:提供了k8s各类资源对象(pod,RC,Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和数据中心。
  • controller-manager:负责对所有资源对象进行控制,通过 apiserver 监控整个集群的状态,并确保集群处于预期的工作状态。
  • scheduler:负责资源调度的进程,通过API Server的Watch接口监听新建Pod副本信息,并通过调度算法为该Pod选择一个最合适的Node。
  • etcd:负责保存k8s里的所有资源对象以及状态的数据。
2.Node

Node是k8s集群中的工作节点,每个Node都会被Master分配一些进程,当某个Node发生异常时,其上的工作负载会被Master自动转移到其他节点上,node节点的基本组件包括:

  • kubelet:负责Pod对应的容器的创建等任务,同时与Master密切协作,实现集群管理的基本功能
  • kube-proxy:实现svc的通信与负载均衡机制的重要组件。
  • Docker Engine:就是docker,负责本node节点上的容器的创建与管理。

在默认情况下Kubelet会向Master注册自己,一旦Node被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的信息(例如机器的CPU和内存情况以及有哪些Pod在运行等),这样Master就可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。而某个Node在超过指定时间不上报信息时,会被Master判定为失败,Node的状态被标记为不可用,随后Master会触发工作负载转移的自动流程

k8s master 支持管理多少容器 k8s三个master工作原理_优先级

工作原理

创建pod的过程
  1. 用户通过kubectl命令行工具发送创建pod的请求
  2. apiserver处理用户请求,并将pod数据存储到etcd
  3. scheduler通过apiserver的watch机制,查看到新的pod,并为pod绑定node
  4. 绑定过程中需要过滤掉有污点的node,污点包括主观上不想调用的node,一般会被用户主动打上污点,另外还有被自动打上的污点,包括资源被限制的node(内存或者磁盘不符合pod的标准,空间不足等),node状态异常的(Ready处于false或者unknown状态)等。
  5. 在过滤后留下来的主机中选择最合适的node节点,比如负载最低的节点,将不同副本分布在不同节点上等等,最终得到最优化的节点,并将数据存入etcd
  6. 之后kubelet根据调度结果执行Pod创建操作,在相应节点上,使用docker engine根据相应的镜像创建相对应的容器,产生所需的pod节点。
  7. pod开始正常运行之后,由controller-manager来管理副本数等。
  8. pod节点创建成功!

集群调度

简介

Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上。听起来非常简单,但有很多要考虑的问题:

  • 公平:如何保证每个节点都能被分配资源
  • 资源高效利用:集群所有资源最大化被使用
  • 效率:调度的性能要好,能够尽快地对大批量的 pod 完成调度工作
  • 灵活:允许用户根据自己的需求控制调度的逻辑

Sheduler 是作为单独的程序运行的,启动之后会一直监听 API Server,获取Pod中Spec.NodeName为空的 pod,对每个 pod 都会创建一个 binding,表明该 pod 应该放到哪个节点上。

调度过程

调度分为几个部分:

  1. 首先是过滤掉不满足条件的节点,这个过程称为predicate
  2. 然后对通过的节点按照优先级排序,这个是priority;
  3. 最后从中选择优先级最高的节点。

如果中间任何一步骤有错误,就直接返回错误。

Predicate 有一系列的算法可以使用:

  • PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源
  • PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
  • PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突
  • PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点
  • NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读

如果在 predicate 过程中没有合适的节点,pod 会一直在pending状态,不断重试调度,直到有节点满足条件。

经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:

按照优先级大小对节点排序优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:

  • LeastRequestedPriority:通过计算 CPU 和 Memory 的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
  • BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
  • ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高通过算法对所有的优先级项目和权重进行计算,得出最终的结果
节点亲和性

节点亲和性一般存放在pod的pod.spec.nodeAffinity中

  • preferredDuringSchedulingIgnoredDuringExecution 软策略:指的是该pod最好要部署在某个node上,指的是优先级上的最高
apiVersion: v1
kind: Pod
metadata:
  name: affinity  
  labels:    
    app: node-affinity-pod
spec:
  containers:  
  - name: with-node-affinity    
    image: doggymo/myapp:v1  
  affinity:    
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1      
        preference:
          matchExpressions:          
          - key: source
  • requiredDuringSchedulingIgnoredDuringExecution 硬策略: 指的是该pod一定要被部署在某个node上,指的是必须被部署到
apiVersion: v1
kind: Pod
metadata:
  name: affinity
  labels:
    app: node-affinity-pod
spec:
  containers:
  - name: with-node-affinity    
    image: doggymo/myapp:v1  
  affinity:
    nodeAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution:   
        nodeSelectorTerms:
        - matchExpressions:        
          - key: kubernetes.io/hostname
            operator: NotIn  # 键值运算关系           
            values:            
            - k8s-node02

键值运算关系还包括:

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在
Pod 亲和性

pod亲和性定义在:pod.spec.affinity.podAffinity(pod亲和性)/podAntiAffinity(pod反亲和性)

  • preferredDuringSchedulingIgnoredDuringExecution 软策略,指的是尽量可以和所选pod在同一拓扑域中
  • requiredDuringSchedulingIgnoredDuringExecution 硬策略,指的是必须和所选pod处于同拓扑域。

为什么这里说的是拓扑域,在下图中,node1和node2其实就是属于同拓扑域。

k8s master 支持管理多少容器 k8s三个master工作原理_kubernetes_02

污点与容忍

一个node可以设置多个污点,不容忍污点的pod不能调度到这个节点,容忍度设置给pod,这样的pod允许但不是必须调度到有这些污点的node。

污点的组成

使用kubectl taint命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去每个污点的组成如下:key=value:effect包括以下三种 effect:

  • NoSchedule:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
  • PreferNoSchedule:表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
  • NoExecute:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去
忍受

设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。但我们可以在 Pod 上设置容忍 ( Toleration ) ,意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。

tolerations:
- key: "key1"  
  operator: "Equal"  
  value: "value1"  
  effect: "NoSchedule"  
  tolerationSeconds: 3600
- key: "key1"  
  operator: "Equal"  
  value: "value1"  
  effect: "NoExecute"
- key: "key2"  
  operator: "Exists"  
  effect: "NoSchedule"
  • 其中 key, vaule, effect 要与 Node 上设置的 taint 保持一致
  • operator 的值为 Exists 将会忽略 value 值
  • tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Pod 上继续保留运行的时间
tolerations:
- operator: "Exists"

如果不设置key,表示容忍所有的污点

tolerations:
- key: "key"  
  operator: "Exists"

如果不设置effect值,表示可以容忍所有的污点作用。

2.k8s中的Qos

QoS简介

QoS(Quality of Service),大部分译为 “服务质量等级”,又译作 “服务质量保证”,是作用在 Pod 上的一个配置,当 Kubernetes 创建一个 Pod 时,它就会给这个 Pod 分配一个 QoS 等级,可以是以下等级之一:

  • Guaranteed:Pod 里的每个容器都必须有内存/CPU 限制和请求,而且值必须相等。如果一个容器只指明limit而未设定request,则request的值等于limit值。
  • Burstable:Pod 里至少有一个容器有内存或者 CPU 请求且不满足 Guarantee 等级的要求,即内存/CPU 的值设置的不同。
  • BestEffort:容器必须没有任何内存或者 CPU 的限制或请求。

该配置不是通过一个配置项来配置的,而是通过配置 CPU/MEM的 limits 与 requests 值的大小来确认服务质量等级。

首先我们来看Qos为guaranteed的pod

k8s master 支持管理多少容器 k8s三个master工作原理_Pod_03


然后我们来看Qos为brustable的pod

k8s master 支持管理多少容器 k8s三个master工作原理_权重_04


最后我们来看Qos为besteffort的pod

k8s master 支持管理多少容器 k8s三个master工作原理_权重_05


我们可以通过限制limits和需求requests的关系来改变Qos的状态

QoS 优先级

三种 QoS 优先级,从高到低(从左往右)

Guaranteed --> Burstable --> BestEffort

Kubernetes 资源回收策略

Kubernetes 资源回收策略:当集群监控到 node 节点内存或者CPU资源耗尽时,为了保护node正常工作,就会启动资源回收策略,通过驱逐节点上Pod来减少资源占用。

三种 QoS 策略被驱逐优先级,从高到低(从左往右)

BestEffort --> Burstable --> Guaranteed

优先级和抢占

首先需要明确的是:优先级和抢占机制,解决的是pod调度失败时该怎么办的问题。

正常情况下,当一个pod调度失败后,就会被暂时 “搁置”pending状态,直到pod被更新,或者集群状态发生变化,调度器才会对这个pod进行重新调度。

场景分析:当一个高优先级的pod调度失败后,该pod并不会被”搁置”,而是会”挤走”某个node上的一些低优先级的Pod。这样就可以保证高优先级pod的调度成功。

  1. PriotityClass
    而在kubernetes中,优先级和抢占机制是在1.10版本后才逐步可用的。要使用这个机制,首先需要在kubernetes里提交一个PriotityClass的定义,如下所示:
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for high priority service pods only."

kubernetes规定,优先级是一个32bit的整数,最大值不超过1000000000(10亿),并且值越大优先级越高。

上述yaml文件中的globalDefault被设置成true的话,那就意味着这个PriorityClass的值会成为系统的默认值。false表示的是只希望声明使用该PriorityClass的Pod拥有值为1000000的优先级,而对于没有声明Priority的Pod来说,优先级就是0。

创建PriorityClass对象之后,Pod就可以声明使用它,如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

pod通过priorityClassName字段,声明了要使用high-priority的PriorityClass。当pod提交给kubernetes之后,kubernetes的PriorityAdmissionController就会将这个pod的spec.priority字段设置为100000。

而当一个高优先级的pod调度失败的时候,调度器的抢占能力就会被触发。这时,调度器就会试图从当前集群里寻找一个节点,使得当这个节点上的一个或者多个低优先级pod被删除后,待调度的高优先级pod就可以被调度到这个节点上。这个过程,就是“抢占”在kubernetes中的主要体现。

对于两个需要同时被调度的pod来说,会首先调度优先级大的pod;当一个优先级低的pod01节点被调度成功之后(在node01上),一个优先级高的pod02由于node01节点资源不足出现调度失败的情况,这个时候会删除pod01,并把pod02部署到node01上,这个过程就叫抢占。

资源管理(cgroup树)

子系统(subsystem)实际上是cgroup对进程组进行资源控制的具体体现。子系统具有多种类型,每个类型的子系统都代表一种系统资源,比如CPU、memory等。当创建一个cgroup实例时,必须至少指定一种子系统。也就是说,这个新建的进程组在访问子系统对应的系统资源时就有了一些限制。

  1. 系统中第一个被创建的cgroup被称为root cgroup,也就是cgroup树的根,该cgroup的成员包含系统中所有的进程。
  2. 一个子系统只能位于一个层级中。
  3. 每个层级中可以关联多个子系统。
  4. 一个进程可以位于不同层级的cgroup中。
  5. 一个进程创建了子进程后,该子进程默认为父进程所在cgroup的成员。子进程被创建后,继承父进程的cgroup,但是后续可根据需求将子进程移动到其他cgroup中。

Cgroup自身通过文件系统的形式在内核中实现,通过对子系统配置文件的读写即可完成对进程组资源的控制。不过,cgroup对各种资源的实际控制则分布到整个内核代码中。