一、node节点选择器

我们在创建pod资源的时候,pod会根据schduler进行调度,那么默认会调度到随机的一个工作节点,如果我们想要pod调度到指定节点或者调度到一些具有相同特点的node节点,怎么办呢?

可以使用pod中的nodeName或者nodeSelector字段指定要调度到的node节点。

1、nodeName

指定pod节点运行在哪个具体node上

[root@k8s-master ~]# cat pod-node.yaml

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod1
  namespace: default
  labels:
    app: myapp
    env: dev
spec:
  nodeName: k8s-node1
  containers:
  - name:  nginx-nodename
    ports:
    - containerPort: 80
    image: nginx
    imagePullPolicy: IfNotPresent

[root@k8s-master ~]# kubectl apply -f pod-node.yaml
#查看pod调度到哪个节点
[root@k8s-master ~]# kubectl get pods  -o wide

2、nodeSelector

指定pod调度到具有哪些标签的node节点上

#给node节点打标签,打个具有disk=ceph的标签
[root@k8s-master ~]# kubectl label nodes k8s-node1 node=worker01
#查看节点的详细信息
[root@hd1 node]# kubectl describe nodes k8s-node1
#定义pod的时候指定要调度到具有 node=worker01标签的node上
[root@k8s-master ~]# cat pod-1.yaml

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod-1
  namespace: default
  labels:
    app: myapp
    env: dev
spec:
  nodeSelector:
    node: worker01
  containers:
  - name:  nginx-nodename
    ports:
    - containerPort: 8080
    image: tomcat:8.5-jre8-alpine
    imagePullPolicy: IfNotPresent

[root@k8s-master ~]# kubectl apply -f pod-1.yaml
#查看pod调度到哪个节点
[root@k8s-master ~]# kubectl get pods  -o wide

二、Pod亲和性

生产上为了保证应用的高可用性,需要将同一应用的不同pod分散在不同的宿主机上,以防宿主机出现宕机等情况导致pod重建,影响到业务的连续性。要想实现这样的效果,需要用到k8s自带的pod亲和性和反亲和性特性。

Pod 的亲和性与反亲和性有两种类型:

requiredDuringSchedulingIgnoredDuringExecution ##一定满足
preferredDuringSchedulingIgnoredDuringExecution ##尽量满足

podAffinity(亲和性):pod和pod更倾向腻在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;

podAntiAffinity(反亲和性):pod和pod更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。

第一个pod随机选则一个节点,做为评判后续的pod能否到达这个pod所在的节点上的运行方式,这就称为pod亲和性;我们怎么判定哪些节点是相同位置的,哪些节点是不同位置的;我们在定义pod亲和性时需要有一个前提,哪些pod在同一个位置,哪些pod不在同一个位置,这个位置是怎么定义的,标准是什么?以节点名称为标准,这个节点名称相同的表示是同一个位置,节点名称不相同的表示不是一个位置。

1、 常用字段解析

[root@k8s-master ~]# kubectl explain pods.spec.affinity.podAffinity
FIELDS:
   preferredDuringSchedulingIgnoredDuringExecution    <[]Object>
   requiredDuringSchedulingIgnoredDuringExecution <[]Object>
#硬亲和性
requiredDuringSchedulingIgnoredDuringExecution
#软亲和性
preferredDuringSchedulingIgnoredDuringExecution
[root@k8s-master ~]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution
FIELDS:
   labelSelector   <Object>
   namespaces  <[]string>
   topologyKey <string> -required-
#位置拓扑的键,这个是必须字段
#topologyKey
#怎么判断是不是同一个位置?
#使用rack的键是同一个位置
#rack=rack1
#使用row的键是同一个位置
#row=row1
#要判断pod跟别的pod亲和,跟哪个pod亲和,需要靠labelSelector,通过labelSelector选则一组能作为亲和对象的pod资源
#labelSelector:
#labelSelector需要选则一组资源,那么这组资源是在哪个名称空间中呢,通过namespace指定,如果不指定namespaces,那么就是当前创建pod的名称空间
#namespace:
[root@k8s-master ~]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector
FIELDS:
   matchExpressions    <[]Object>
   matchLabels <map[string]string>
#匹配表达式
#matchExpressions    <[]Object>

2、 案例演示

2.1 pod节点亲和性

[root@k8s-master ~]# cat pod-required-affinity-demo.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod-first
  labels:
    app2: myapp2
    tier: frontend
spec:
  containers:
  - name: myapp
    image: nginx
    imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-second
  labels:
    app: backend
    tier: db
spec:
  containers:
  - name: busybox
    image: busybox:1.28
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep 3600"]
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app2, operator: 'In', values: ["myapp2"]}
        topologyKey: kubernetes.io/hostname

上面表示创建的pod必须与拥有app=myapp2标签的pod在一个节点上

[root@k8s-master ~]# kubectl apply -f pod-required-affinity-demo.yaml
[root@k8s-master ~]# kubectl get pods -o wide

2.2 pod节点反亲和性

[root@hd1 node]# kubectl  delete  -f pod-required-affinity-demo.yaml
[root@k8s-master ~]# cat pod-required-anti-affinity-demo.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod1-first
  labels:
    app1: myapp1
    tier: frontend
spec:
    containers:
    - name: myapp
      image: nginx
      imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Pod
metadata:
  name: pod2-second
  labels:
    app: backend
    tier: db
spec:
    containers:
    - name: busybox
      image: busybox:1.28
      imagePullPolicy: IfNotPresent
      command: ["sh","-c","sleep 3600"]
    affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app1
                      operator: 'In'
                      values: 
                        - myapp1
                topologyKey: kubernetes.io/hostname

[root@k8s-master ~]# kubectl apply -f pod-required-anti-affinity-demo.yaml
[root@k8s-master ~]# kubectl get pods -o wide 

2.3 换一个topologykey

[root@k8s-master ~]# kubectl label nodes k8s-node1 zone=foo
[root@k8s-master ~]# kubectl label node k8s-node2 zone=foo --overwrite
[root@k8s-master ~]# cat pod-first-required-anti-affinity-demo-1.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod3-first
  labels:
    app3: myapp3
    tier: frontend
spec:
    containers:
    - name: myapp
      image: ikubernetes/myapp:v1
      imagePullPolicy: IfNotPresent

[root@k8s-master ~]# cat pod-second-required-anti-affinity-demo-2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod3-second
  labels:
    app: backend
    tier: db
spec:
    containers:
    - name: busybox
      image: busybox:1.28
      imagePullPolicy: IfNotPresent
      command: ["sh","-c","sleep 3600"]
    affinity:
      podAntiAffinity:
         requiredDuringSchedulingIgnoredDuringExecution:
         - labelSelector:
              matchExpressions:
              - {key: app3 ,operator: 'In', values: ["myapp3"]}
           topologyKey:  zone

[root@k8s-master ~]# kubectl apply -f pod-first-required-anti-affinity-demo-1.yaml
[root@k8s-master ~]# kubectl apply -f pod-second-required-anti-affinity-demo-2.yaml
[root@k8s-master ~]# kubectl get pods -o wide

#发现第二个节点是pending,因为两个节点是同一个位置,现在不是同一个位置的了,而且我们要求反亲和性,所以就会处于pending状态,如果在反亲和性这个位置把required改成preferred,那么也会运行。
#podaffinity:pod节点亲和性,pod倾向于哪个pod
#nodeaffinity:node节点亲和性,pod倾向于哪个node

常见topologyKey
topologyKey 拓扑域范围 主要用途
kubernetes.io/hostname 节点(Node) 最常用。实现 Pod 在节点级别的分散或集中。
topology.kubernetes.io/zone 可用区(Zone) 实现跨可用区的高可用,容灾能力更强。
topology.kubernetes.io/region 区域(Region) 实现跨地域的部署,通常用于多地域容灾或全球化应用。
node.kubernetes.io/instance-type 实例类型 根据机器硬件类型来调度 Pod,较少使用。

三、资源限制

resources    

四、污点与容忍

前面介绍了节点亲和性调度,它可以使得我们的Pod调度到指定的Node节点上,而污点(Taints)与之相反,它可以让Node拒绝Pod的运行,甚至驱逐已经在该Node上运行的Pod

污点是Node上设置的一个属性,通常设置污点表示该节点有问题,比如磁盘要满了,资源不足,或者该Node正在升级暂时不能提供使用等情况,这时不希望再有新的Pod进来,这个时候就可以给该节点设置一个污点。

但是有的时候其实Node节点并没有故障,只是不想让一些Pod调度进来,比如这台节点磁盘空间比较大,希望是像Elasticsearch、Minio这样需要较大磁盘空间的Pod才调度进来,那么就可以给节点设置一个污点,给Pod设置容忍(Tolerations)对应污点,如果再配合节点亲和性功能还可以达到独占节点的效果

一般时候 Taints 总是与 Tolerations配合使用

1、污点(Taints)

设置污点后,一般Pod将不会调度到该节点上来。每次Pod都不会调度到master节点,那是因为搭建K8s集群的时候,K8s给master自带了一个污点。

1.1、查看污点

查看master上是否有污点,通过describe命令可以看到节点上的所有污点

[root@k8s-master ~]# kubectl describe node k8s-master
...
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
...

还可以通过如下命令查看

[root@k8s-master ~]# kubectl get nodes k8s-master -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-role.kubernetes.io/control-plane]]

发现master上自带了一个没有value的污点 node-role.kubernetes.io/control-plane ,effect 为 NoSchedule,由于我们的Pod都没有容忍这个污点,所以一直都不会调度到master

1.2、污点类别

上面我们看到了污点有一个effect 属性为NoSchedule,其实还有其它两种类别分别是 PreferNoSchedule与 NoExecute

  • NoSchedule: 如果没有容忍该污点就不能调度进来。

  • PreferNoSchedule: 相当于NoExecute的一个软限制,如果没有容忍该污点,尽量不要把Pod调度进来,但也不是强制的。

  • NoExecute: 如果没有设置容忍该污点,新的Pod肯定不会调度进来, 并且已经在运行的Pod没有容忍该污点也会被驱逐。

1.3、设置污点

污点分别由key、value(可以为空)、effect 组成,设置命令如下

# 设置污点并不允许Pod调度到该节点
$ kubectl taint node k8s-node1 key=value:NoSchedule
# 设置污点尽量不要让Pod调度到该节点
$ kubectl taint node k8s-node1 key=value:PreferNoSchedule
# 设置污点,不允许Pod调度到该节点,并且且将该节点上没有容忍该污点的Pod将进行驱逐
$ kubectl taint node k8s-node1 key=value:NoExecute

1.4、删除污点

# 删除该key的所有污点
$ kubectl taint node k8s-node1 key-
# 删除该key的某一个污点
$ kubectl taint node k8s-node1 key=value:NoSchedule-
# 删除该key的某一个污点可以不写value
$ kubectl taint node k8s-node1 key:NoSchedule-

1.5、污点测试

给k8s-node1节点设置一个污点,表示只有前台web应用才能调度,后端app应用不能调度

[root@k8s-master ~]# kubectl taint node k8s-node1 web=true:NoSchedule
node/k8s-node1 tainted

编写 web-taint.yaml 内容如下,有三个Pod均没有容忍该污点

apiVersion: v1
kind: Pod
metadata:
  name: web-taint1
spec:
  containers:
  - name: nginx
   image: nginx
   imagePullPolicy: IfNotPresent # 本地有不拉取镜像
---
apiVersion: v1
kind: Pod
metadata:
  name: web-taint2
spec:
  containers:
  - name: nginx
   image: nginx
   imagePullPolicy: IfNotPresent # 本地有不拉取镜像
---
apiVersion: v1
kind: Pod
metadata:
  name: web-taint3
spec:
  containers:
  - name: nginx
   image: nginx
   imagePullPolicy: IfNotPresent # 本地有不拉取镜像

启动Pod,观察Pod是否都被调度到k8s-node2节点

# 启动三个Pod
[root@k8s-master taint]# kubectl apply -f web-taint.yaml 
pod/web-taint1 applyd
pod/web-taint2 applyd
pod/web-taint3 applyd

# 查看pod详情发现均被调度到k8s-node2节点
[root@k8s-master taint]# kubectl get pod -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
web-taint1   1/1     Running   0          13s   10.244.2.35   k8s-node2   <none>           <none>
web-taint2   1/1     Running   0          13s   10.244.2.34   k8s-node2   <none>           <none>
web-taint3   1/1     Running   0          13s   10.244.2.36   k8s-node2   <none>           <none>

给k8s-node2节点添加污点 app=true:NoExecute,表示只有后端服务才能调度进来,并且已经在k8s-node2节点上运行的不是后端服务的节点将被驱逐

# 设置污点
[root@k8s-master taint]# kubectl taint node k8s-node2 app=true:NoExecute
node/k8s-node2 tainted
# 查看Pod详情,发现三个Pod已经被驱逐
[root@k8s-master taint]# kubectl get pod -o wide
No resources found in default namespace.

从上面可以知道我们虽然设置了污点,但是我们的节点其实很正常,既然是正常节点,那么就可以有服务运行,这就需要用到容忍机制来运行这些Pod

2、容忍(Toletations)

如果需要Pod忽略Node上的污点,就需要给Pod设置容忍,并且是需要容忍该Pod上的所有污点。

通过 kubectl explain pod.spec.tolerations 可以查看容忍的配置项

2.1、配置项

tolerations: # 数组类型,可以设置多个容忍
- key: # 污点key
  operator: # 操作符,有两个选项 Exists and Equal 默认是Equal
  value: # 污点value,如果使用Equal需要设置,如果是Exists就不需要设置
  effect: # 可以设置为NoSchedule、PreferNoSchedule、NoExecute,如果为空表示匹配该key下所有污点,
  tolerationSeconds: # 如果污点类型为NoExecute,还可以设置一个时间,表示这一个时间段内Pod不会被驱逐,过了这个时间段会立刻驱逐,0或者负数会被立刻驱逐

2.2、设置容忍

给Pod设置容忍k8s-node1节点的污点web=true:NoSchedule

编写 web-tolerations.yaml 内容如下,容忍污点web=true:NoSchedule

apiVersion: v1
kind: Pod
metadata:
  name: web-tolerations
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent # 本地有不拉取镜像
  tolerations:
  - key: web
    operator: Equal
    value: "true"
    effect: NoSchedule

启动web-tolerations,观察Pod是否正常运行

# 启动web-tolerations
[root@k8s-master taint]# kubectl apply -f web-tolerations.yaml  
pod/web-tolerations applyd

# Pod正常运行,且运行在k8s-node1节点
[root@k8s-master taint]# kubectl get pod -o wide
NAME              READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
web-tolerations   1/1     Running   0          8s    10.244.1.92   k8s-node1   <none>           <none>

查看该Pod上的容忍

[root@k8s-master taint]# kubectl describe pod web-tolerations

Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
                             web=true:NoSchedule

可以看到我们设置的Tolerations在里边,除了我们自己的,K8s还会给每个Pod设置两个默认Tolerations,并且Tolerations时间为五分钟,表示某些节点发生一些临时性问题,Pod能够继续在该节点上运行五分钟等待节点恢复,并不是立刻被驱逐,从而避免系统的异常波动。

编写 app-tolerations.yaml 内容如下,容忍污点app=true:NoExecute,并且只容忍60s

apiVersion: v1
kind: Pod
metadata:
  name: app-tolerations
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: app
    operator: Exists
    effect: NoExecute
    tolerationSeconds: 60

启动app-tolerations,查看Pod是否能够正常运行,且运行在k8s-node2节点,等待60秒,查看Pod是否已被驱逐

# 启动app-tolerations
[root@k8s-master taint]# kubectl apply -f app-tolerations.yaml 
pod/app-tolerations applyd

# 查看详情,已经运行成功并且调度到了k8s-node2节点,此时运行59秒
[root@k8s-master taint]# kubectl get pod -o wide
NAME              READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
app-tolerations   1/1     Running   0          59s   10.244.2.37   k8s-node2   <none>           <none>

# 运行60秒查看,状态已经变为Terminating
[root@k8s-master taint]# kubectl get pod -o wide
NAME              READY   STATUS        RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
app-tolerations   1/1     Terminating   0          60s   10.244.2.37   k8s-node2   <none>           <none>

# 立刻再次查看,已经被驱逐
[root@k8s-master taint]# kubectl get pod -o wide
No resources found in default namespace.

如果一个节点没有任何污点,另一个节点有污点,并且Pod容忍了该污点,那么最终会调度到哪个节点呢?

删除k8s-node2节点的污点app=true:NoExecute,保留k8s-node1节点的web=true:NoSchedule,再次启动之前的web-tolerations.yaml 观察Pod所在节点

# 删除k8s-node2上的污点
[root@k8s-master taint]# kubectl taint node k8s-node2 app:NoExecute-
node/k8s-node2 untainted

# 启动Pod web-tolerations
[root@k8s-master taint]# kubectl apply -f web-tolerations.yaml
pod/web-tolerations applyd

# Pod正常运行,并且运行在k8s-node2节点
[root@k8s-master taint]# kubectl get pod -o wide                
NAME              READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
web-tolerations   1/1     Running   0          2s    10.244.2.38   k8s-node2   <none>           <none>

上面表明,即使是容忍了该污点,如果有其它节点不需要容忍就可以调度的话,那还是会优先选择没有污点的机器,但是生产上往往设置污点就是为了给你这个Pod使用的,不能调度到其它节点,这个时候需要配合节点亲和性调度一起使用。

2.3、Equal 与 Exists

Exists 表示key是否存在所以无需设置value,Equal 则必须设置value需要完全匹配,默认为Equal

空的key配合Exists 可以匹配所有的key-value,也就是容忍所有的污点,生产上非常不建议使用

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐