曾国藩:一勤天下无难事

Kubernetes之pod调度

2019.05.06

Pod的调度在默认情况下是Scheduler Controller 采用默认算法的全自动调度,在实际使用中并不满足我们的需求,因为并不能事先掌握pod被调度到哪个Node之上,所以kubernetes又提供了好几种方式让我们自已选择调度到什么Node中,比如有NodeSelector(定向调度)、NodeAffinity(Node亲和性)、PodAffinity(Pod亲和性)。

NodeSelector调度算法比较简单,NodeSelector 只调度到某个拥有特定标签的Node上,如果没有满足条件的Node,那么此Pod将不会被运行,即使在集群中还有可用Node列表,这就限制了它的使用场景,现在基本上被NodeAffinity取代了,NodeAffinity在NodeSelector的基础之上的进行了扩展,使调度更加灵活,除了有一个必须要满足条件的Node之外,还要可设置优先条件,下面来看下NodeAffinity的使用方式:

一、NodeAffinity

NodeAffinity 亲和性有两种表达方式:

  • RequiredDuringSchedulingIgnoredDuringExecution :必须满足指定的规则才可以调度Pod到Node上,相当于硬限制。
  • PreferredDuringSchedulingIgnoredDuringExecution:强调优先满足指定的规则,相当于软限制,并不强求,如果多个优先级规则,还可以设置权重,以定义执行顺序。

IgnoredDuringExecution 表示 ,如果一个pod所在的节点 在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略Node上Label的变化,该pod继续在该节点上运行。

示例:

apiVersion: v1
kind: Pod
metadata:
 name: ubuntu-aff
 labels:
  os: centos
spec:
 affinity:  #亲和性设置
  nodeAffinity: #设置node亲和性
   requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:   
    - matchExpressions:   #设置node拥有的标签
     - key: disktype    
      operator: In
      values:
      - hdd
   #表示一定会调度在拥有此标签(disktype=hdd)的Node上。
如果将requiredDuringSchedulingIgnoredDuringExecution改为 preferredDuringSchedulingIgnoredDuringExecution
那么就不一定会调度到有disktype=hdd标签的Node,它会看情况,如果有这个标签的Node负载已经是很高的了,那么就会调度到负载较低的Node上

从上面的配置中可以看到,它操作符operator为In,NodeAffinity语法支持的操作符有In、NotIn、Exists、DoesNotExist、Gt、Lt虽然没有节点排斥功能,但是用NotIn和DoesNotExist就可以实现排斥功能了。

NodeAffinity规则设置的注意事项如下:

  • 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能最终运行在指定的Node上

  • 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可,nodeSelectorTerms 属性是设置要调度到的node的标签的,因此nodeSelectorTerms 如果有多个说明是可以调度到不同的标签,即不同的Node之上,所以只需要匹配一个就行了

  • 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的 matchExpressions 才能匹配成功。matchExpressions 是在nodeSelectorTerms 下,设置具体的一个Node标签,设置有多个如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的matchExpressions 才能匹配成功。matchExpressions 是在nodeSelectorTerms 下,设置具体的一个Node标签,设置有多个nodeSelectorTerms ,意味着匹配的Node需要有多个标签,且多个标签必须同时要有。nodeSelectorTerms ,意味着匹配的Node需要有多个标签,且多个标签必须同时要有。

上面的例子中,node亲和性条件只有一个,可以设置多个条件:

apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-aff
  labels:
    os: centos
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:   #第一条件
        nodeSelectorTerms:
        - matchExpressions:
          - key: beta.kubernetes.io/arch
            operator: In
            values:
            - amd64
      preferredDuringSchedulingIgnoredDuringExecution:  #第二个条件
      - weight: 1
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - hdd
  containers:
  - name: ubuntu-aff
    image: 10.3.1.15:5000/ubuntu:16.04
    env:  #在容器内配置环境变量
    - name: Test
      value: "7890"
    command: ["bash","-c","while true;do date;sleep 1;done"]

如上,创建pod时,节点亲和性有两个,也就是说如果某个Node运行这个Pod,那么这个Node需要满足的条件有两个,

  • 第一个条件是 属性为 requiredDuringSchedulingIgnoredDuringExecution ,此为硬性条件,必须要满足的: beta.kubernetes.io/arch=amd64

  • 第二个条件属性为 preferredDuringSchedulingIgnoredDuringExecution ,此为软性条件,即优先满足的条件,尽可能会满足,如果无法满足就退而求其次,运行在其它Node之上。

那么调度时该如何选择调度到哪个Node上?

为什么要设置两个条件呢,因为满足第一个条件的Node有很多,即拥有 beta.kubernetes.io/arch=amd64 的标签的Node有很多,这里第二个条件是说 要尽量运行在 disktype=hdd的Node上。

总的来说,即 只运行在arch=amd64的node上,在此条件之下优先运行在disktype=hdd的Node上。

二、PodAffinity: Pod亲和性与互斥性调度

根据节点上正在运行的pod的标签来调度,而非node的标签,要求对节点和Pod两个条件进行匹配,其规则为:如果在具有标签X的Node上运行了一个或多个符合条件Y的Pod,那么Pod应该运行在此Node上,如果是互斥,则拒绝运行在此Node上。 也就是说根据某个已存在的pod,来决定要不要和此pod在一个Node上,在一起就需要设置亲和性,不和它在一起就设置互斥性。X指的是一个集群中的节点、机架、、区域等概念,通过Kubernetes内置节点标签中的key来进行声明,这个key的名字为topologyKey,用来表达节点所属的拓朴结构之下。

pod的亲和性表达方式与Node亲和性是一样的表达方式。

kubernetes内置标签:

  • kubernetes.io/hostname

  • failure-domain.beta.kubernetes.io/zone

  • failure-domain.beta.kubernetes.io/region

  • beta.kubernetes.io/instance-type

  • beta.kubernetes.io/os

  • beta.kubernetes.io/arch

1、创建参照Pod

#pod affinity的参照Pod
apiVersion: v1
kind: Pod
metadata:
  name: pod-flag
  labels:    # 定义多个标签,以便其它pod设置亲和与互斥
    app: nginx
    security: s1
spec:
  containers:
  - name: nginx-1-10
    image: 10.3.1.15:5000/nginx:1.10
    ports:
    - containerPort: 80

#查看调度到哪个Node之上:
root@ubuntu:# kubectl get pod -o wide
NAME     READY     STATUS     RESTARTS  AGE     IP       NODE
pod-flag   1/1      Running      0     4m  192.168.150.232   10.3.1.16

#运行在Node 10.3.1.16上

2、创建一个pod,与参照的pod必须要在同一个node上,即,具有亲和性。

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
  labels:
    os: ubuntu
spec:
  affinity: #亲和性设置
    podAffinity:  #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: #必须要满足的条件
      - labelSelector:  #与哪个pod有亲和性,在此设置此pod具有的标签
          matchExpressions:  #要匹配的pod的,标签定义,如果定义了多个matchExpressions,则所有标签必须同时满足。
          - key: security #标签的key
            operator: In  #操作符
            values:
            - s1  #标签的值,即拥有label security=s1
        topologyKey: kubernetes.io/hostname #节点所属拓朴域
  #上面表达的意思是此处创建的Pod必须要与拥有标签security=s1的pod在同一Node上.
  containers:
  - name: wish-pod-affinity
    image: 10.3.1.15:5000/ubuntu:16.04
    env: 
    - name: Test
      value: "7890"
    command: ["bash","-c","while true;do date;sleep 1;done"]
    
 
   # 因为pod是属于某个命名空间的,所以设置符合条件的目标Pod时,还可以指定哪个命名空间或全部命名空间里的Pod,
   #  namespace的定义与labelSelector同级,如果不指定命名空间,则与此处创建的pod在一个namespace之中
root@ubuntu:# kubectl get pod -o wide
NAME           READY   STATUS    RESTARTS   AGE      IP        NODE
with-pod-affinity     1/1    Running     0     11s   192.168.150.252   10.3.1.16
pod-flag          1/1    Running     0     1h    192.168.150.232   10.3.1.16

#的确是在同一个Node上。
#如果在创建时,pod状态一直处于Pending状态,很有可能是因为找不到满足条件的Node。

3、创建一个pod,与参照的pod,一定不能在同一个Node上,即具有互斥性。

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-antiffinity
  labels:
    os: ubuntu
spec:
  affinity: #亲和性设置
   podAntiAffinity:  #设置pod反亲和性
      requiredDuringSchedulingIgnoredDuringExecution: #必须要满足的条件
      - labelSelector:  #与哪个pod有反亲和性,互斥性,在此设置此pod具有的标签
          matchExpressions:
          - key: app #标签的key
            operator: In
            values:
            - nginx  #标签的值,即拥有label app=nginx
        topologyKey: kubernetes.io/hostname  #节点所属拓朴域
  #上面表达的意思是此处创建的Pod必须要与拥有标签app=nginx的pod不在在同一个Node上.如果所有 Node将无法满足条件,则创建的pod一直处于Pending状态。
  containers:
  - name: wish-pod-antiaffinity
    image: 10.3.1.15:5000/ubuntu:16.04
    env: 
    - name: Test
      value: "7890"
    command: ["bash","-c","while true;do date;sleep 1;done"]
root@ubuntu15:/data/yaml# kubectl get pod -o wide
NAME                       READY        STATUS    RESTARTS     AGE           IP                NODE
pod-flag                    1/1         Running      0          1h        192.168.150.232    10.3.1.16
with-pod-affinity           1/1         Running      0          1h        192.168.150.252    10.3.1.16
with-pod-antiffinity        1/1         Running      0          1m        192.168.77.204     10.3.1.17

#可以看到与参照pod不在同一个node之上。

更多的的Pod亲和性调度信息可以参考官方文档。