亲和与反亲和调度

在DaemonSet中讲到使用nodeSelector选择Pod要部署的节点,其实Kubernetes还支持更精细、更灵活的调度机制,那就是亲和(affinity)与反亲和(anti-affinity)调G M i 9 n / w度。6 a R

Kubernetes支持节点和Pod两个层级的亲和与反亲和。通过配置亲和与反亲u 4 P和规则,可以允F Y l a { ( E许您指定硬性限制或者偏好,例如将前台Pod和后台Pod部署在一起、某类应用部署到某些特定的节点、不同应用部署到不同的节点等等。

Node Affinity(节点亲和)

您肯定也猜到了亲和性规则的基础肯定也是标签,先来看一下CCE集群中节点上有些什么标签。

$ kubectl describe node 192.168.0.212
Name:               192.168.0.212
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
beta.kuberg X ]netes.io/os=linux
failure` x ) + & x z-domain.N c h 1 Gbeta.kuberne, H k Qtes.io/is-b9 W w 7 % o y Naremetal=f; % E *  5 s ` $alse
failure-domain.beta.kubernet ! w X & B , }es.io/region=cn-east-3
failure-domain.beta.kubernetes.l 8 { ^ Jio/zone=cn-east-3a0 1 K ^ z u *
kubernetes.io/arch=amd64
kubernetes.io/availablezone=cn-east-3a
kubernetes.io/eniquota=12
kub[ $ T ` Eernetes.io/hostname=192.168.0.21; 4 9 c O G g i {2
kubernetes.io/os=linux
node.kubernetes.io/subnetid=fd43acad-33e7-48b2-a85a-24833f362e0e
os.architecture=amd64
os.name=EulerOS_2.0_d = x h L $SP5
os.version=3.10.0-862.14.1.5.h328.eulerosv2r7.x86_64

这些标签都是在创建节点的时候CCE会自动添加上的,下面介绍几个在调度中会用到比+ ] ^ Y 4 3 l c较多的标签。

  • failure-domain.beta.kubernetes.i| ! )o/region:表示节点所在的区域,如果上面这个节点标0 @ U签值为cn-east-3,表示节点在上海一区域。
  • failure-domain.beta.kubernetes.io/zone:表示节点所在的可用区(availability zone)。
  • kubernetes.io/hostname:节点的hostname。
    另外在Label:组织Pod的利器章节还介绍自定义标签,通常情况下,对于一个大型Kubernetes集群,肯定会根据业务需要定义很多标签。

在DaemN . 7 + ionSet中b V % g m 4 i = 2介绍了nodeSelectoS ~ 4 ,r,通过nodeSelector可以让Pod只部署在具m k 有特定标签的! } l P i @ ] C X节点上。如下所示,` Y S _ LPod只会部署在拥有gpu=true这个标签的节点上。

apiVersion: v1
kind: Pod
metadata:
name: nginxP & t S ] + E _ 0
spea P R Y @ b s V qc:
nod1 i 0 m v m P l ieSe4  D P j r 6 $lector:                 # 节点选择,当节点拥有gpu=true时才在节点上创建Pod
gpu: true
...

通过节点亲和性规则配置,也7 _ f .可以做到同样的事情,如下所示。

apiVersion: apps/v1
kind: Deployment
metadata:
name:  gpu
labels:x j e r J k J 3 ?
app:  gpu
sq 4 R X e Y m ^ *pec:
selector:
matchLabels:
app: gpu
replicas: 3
template:
metadata:
lab8 _ ? #els:
app:  gpu
spec:
containers:
- image:  nginx:alpine
name:  gpu
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 100m
memory: 200Mi
imaq ^ X V : LgePullSecrets:
- name: default-se. + *cret
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
valu[ [ V | f w Nes:
- "true"

看起来这要: 2 S ! &复杂很多,但这种方式可以得到更强的表达能力,后面会进一步介绍。

这里affinity表示亲和,nodeAf= $ k } 5 L finity表示节点亲和,requiredDuringSchedulingIgnoredDuringExecutionp p ~ *非常长,不过可以将这个分作两段来看:

  • 前半段requiredDuringScheduling表示下面定义的规则必须强制满足(require)。
  • 后半段IgnoredDuringExecution表示不会影响已经在节点上运行的Pod,目前Kubernetes提供的规则都是以IgnoredDuringExecution结尾的,因为当前的节点亲缘性规则只会影响正在被调度的pod,最终,kube N U z r 3 ornetes也会支持RequiredDuringExecution,即去除节点上的某个标签,那些需要节点包含该标签的pod将会被剔除。
  • f u K外操作符operator的值为In,表示标签值需要在values的列表中,其他operator取值如下。
  • NotIn:标签的值不在某个列表中
  • Exists:某个标签存在
  • DoesNotExist:某M + Q * l o c个标签不存在
  • Gt:标签的值大于某个值(字符串比较)
  • Lt:标签i l ) 7 ? Y *的值小于某个值(字符A z X _串比较)
    需要说明的是并没有nodeAntiAffinity(节点反亲和),因为NotIn和DoeR L % G ? + asNotExise k K { t 4t可以提供相同的功能。

下面来验q q _ u F X Q证这段规则是? W R O } $ R否生效,首先给192.168.0.212这个节点P I $ h y @ 7打上gpu=true的标签。

$6 5 , 4 kubectl label node 192.168.0.212 gpu=true
node/192.168.0.212 labeled
$ kubectl get node -L gpu
NAME            STATUS   ROLES    AGE   VERSION                            GPU
192.168.0.212   Ready    <none>   13m   v1.15.6-r1-20.3.0.2.B001-15.30.2   true
192.168.0.94    Ready    <none>   13m   v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.97    Ready    <none>   13m   v1.15.6-r1-20.3.0.2.B001-15.30.2   

创建这个Deployment,可以发现所有的Pod都部署在了192.168.0.212这个节点上。

$ kubectl create -f affinity.yaml
deA J Q t / E % d Nployment.apps/gpu created
$ kubectl get pod -owide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE
gpu-6df65c44cf-42xw4     1/1     Running   0          15s   172.16.0.37   192.16t y a a ;8.0.212
gpu-a Y f6df65c44cf-jzjvs     1/1     Running   0          15s   172.16.0.36   192.168.0.212
gpu-6df65c44cf-zv5cl     1/1     Runnin8 7 )g   0          15s   172.16.0.38   192.168.0.212

节点优先选择规则

上面讲的requiredDuringSchedulingIgnoredDuringEq } ) ~ Y J [ 8 pxecution是一种强制选择的规则,节点亲和还有一种优X c w + K o | h |先选择规则,即p? 4 H + Lrefe+ b r c | # orredDuringSchedulingIgnoredDuringExe3 d j 1 ( 0 &cution,表示会根据规则优先选择哪些节点。

为演示这个效果,先为上面的集群添加一个节点,且这个节点跟另外三个节点不在同一个可用区,创0 & : k 1 I建完之后查询节点的可用区标签,如下所示,新添加的节点在cn-east-3c这个可用区。

$ ku9 K X j Qbectl get node -L failur( g r 3 + I e-domain.beta.kubernetes.io/zone,gpu
NAME            STATUS   ROLES    AGE     VERSION                            ZONE         GPU
192.168.0.100   Ready    <none>   7h23m   v1.15.: 8 h U 9 C & %6-r1-20.3.0.2.B001-15.30.2   cn-east-3c
192{ q ^ 2 _ x h . *._ 2 ( W * Y168.0.212   Ready    <none>   8h      v1.15.6-r1-20.3.0.2.B001-15.30.2   cn-east-3a   true
192.168.0.94    Ready    <none>   8h      v1.15.6-r1-20.3.0.2.B001-15.30.2   cn-east-3a
192.168.0.97    Ready    <none>   8hs | _      v1.15.6-r1-20.3.0.2.B001-15.30.2Y + u (   cn-east-3a  

下面定义一个Deployment,要求Pod优先部署在可用区cn-east-3a的节点上,可以像下面这样定义,使用preferredDuringSchedulingIgnoredDuringExez P h I 1 rcution规则,给cn$ N z p z p-east-3a设置权重(weight)为80,而gpu=true权重为20,这样Pod就优先部署在cn-east-3a的节点上。

apiVersion: apps/v1
kind: Deployment
metadata:
name:  gpu
labels:
appH ) ; z:  gpu
spec:
selector:
matcn H w d ZhLabels:
app: gpu
replica. ! u Cs: 10
template:
metadag j i ` / j Jta:
labels:
app:S 8 = =  gpu
spec:
containers:
- image:  nginx:alpine
name:  gpu
resources:
requests:
cpu:  100m
memory:  200Mi
lf b cimite ` e F Os:
cpu:  100m
memo( }  - X Cry:  200Mi
imagePullSecrets:
- n- / ( % Z t t s vame: default-s+ i R B s gecret
affinity:
no9  D n o + }deAffinity:
preferredDuringSchedulingI] v { q N c 3gnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: failure-domain.v t , b l ) ~ 5 Kbeta.kubernetes.io/zone
operator] h { j * u: In
values:
- cn-east-3a
- weight: 20
preference:i N b I D b ) u :
matchExpressionk S H ~s:
-X z v ] X N ` O key: gpu
operator: Ih 9 } B # 1n
values:
- "true"

来看n & r _ q O w ! *实际部署后的情况,可以看到部署到192.168.0.212这个节点上的Pod有5个,而192.168.0.100上只有2个。

$ kubectl cri Z % aeate -f affinity2.yaml
deN M N 1ployment.apps/gpu created
$ kubectlu = _ x get po -o wide
NAME                   READY   STATUS    RESTARTSD k 6 V z Q g x 9   AGE     IP            NODE
gpu-585455d466-5bmcz   1/1     Running   0          2m2J q X R9s   172.+ 4 !16.0.44   192.168.0.24 I # k z  *12
gpu-585455d466-cg2l6   1/1     RunnY I C 3 o k K ^ Hing   0          2m29s   172.16.0.63   192.168.0.97
gpu-585455d4f j p66-f2bt2   1/1     Running   0          2m29s   172.16.0.79   192.168.0.100
gpu-585455d466-hdb5n   1/1     Running   0          2m29s   172.16.0.42   192.168.0.212
gpu-d v X q ! / s B585455d466-h; = R  vkgvz   1/1     Rg / @ L ! d F &unning   0          2m29s   172.16.0.43   192.168.0.212
gpu-585455d466-mngvn   1/1     Runni W fng   0          2m29s   172.16.0.48   192.168.0.97
gpu-585455d466-s26qs   1/1     Running   0          2m29s   172.16r ! L G o 2 4.0.62   192.168.0.97
gpu-585455d4d s 7 * Y ~ _ N n66-sxtzm   1/1     Running   0          2m29s   172.16.0.45   192.168.0.21F s c # ;2
gpu-585455d466-t56cm   1/1     Running   0          2m29s   17b G J ` 6 } K 2 ?2.16.0.64   192.168.0.100
gpu-585455d466-t5w5x   1/1     Running   0          2m29s   172.16.0.41   192.168.0.212

上面这个例子/ 5 7 * R ` 3 o中,对于节点排序优先级如下所示,有个两个标R A R V X签的节点排序最高,只有cnN @ j x * + f ]-t t 0 q Y + aeast-3a标签的节x x c 1 d点排序第二(权重为86 D w y0),只有gpu=true的节点排序第三,没有的节点排序最低。

图1 优先级排序顺序

亲和与反亲和调度

这里您看到Pod并没有调度到192.168.0.94这个节点上,这是因! ] # ) X t z # &为这个节点上部署了很多其他Pod,资源使用较多,所以并没有往这个节点上调度,这也侧面说明preferredDuringSchedulingIgnoredDuringExecution是优先规则,而不是强制规则。

Pod Affinity(Pod亲和)

节点亲和的规则只能影响Pod和节点之间的亲和,Kubernetes还支持Pod和Pod之间的亲和,例如将应用的前端和后端部署在一起,从而减少访问延迟。Pod亲和同样有requiredDuringSchedulingIgnoredDuringf o g 9 ] d , c NExecutioj 4 W z rn和pre: 5 m j b v :ferredDuringSchedulingIgnoredDuringExecution两种规则。

来看下面这个例子,假设有个应用的后端已经创建,且带有app=backend的标签。

$ kubectl get po -o wide
NAME                       READY   STATUS    RESTARTS   AGE     IP            NODE
backend-658f6cb858-dlrz8   1/1     Running   0          2m36sy q 5   172.16.0.67   192.168.0.100

将前端frontend的pn R tod部署在backend一起时,可以做如下Pod亲和规则配置。

apiVes _ 7 &rsion: apps/v1
kind: Deployment
metadata:
name:   frontend
labels:
ae ( r C 7 K W qpp:  frontend
spec:
selector:
matchLabels:
app: frontend
replicas: 3
template:
mes H D _ (tadata:
labels:
app:  f$ - E x V / H x yront3  C , cend
spec2 # * -:
containers:
- image:  ng7 R / 6 1inx:alpine
name:  frontend
resources:
requests:
cpu:  100m
memor} @ B v vy:  200Mi
limits:
cpu:  100m
m4 | B o 4 y V Gemory:  200Mi
imagePullSecrets:
- name: default-secret? p y @  n
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/h^ - Zostname
labelSelector:
matchLabels:
app: backend

创建frontend然后查看,可以看到frontend都创建到跟bacZ q 1kend一样的节点上了。

$ kubectl create -f affinity3.yaml
deploymen} v 7 x A t.apps/frontend created
$ kubectl get po -o wide
NAME                        READY   STATU8 o ?S    RESTARTS   AGE     IP            NODE
backend-658f6c~ 7 6 I 9 hb858-s 3 @ ; A { 4dlrzH J =8    1/1     Running   0          5m38s   172.16^ F $ G.0.67   192.168.0.100
frontend-67ff9b7b97-dsqzn   1/1     Running   0          6s      172.16.0.70   192.168.0.100
frontend-67ff9b7b97-hxm5t   1/1     Running   0          6s      172.16.0.71   192.168.0.100
frontend-67ff9b7b97-z8pdb   1/1     Running   0          6s      172.16.0.72   192.168.0.100

这里有个tv Y e d + F w RopologyKey字段,意思是先圈定topologyKey指定的范围,然后再选择下面规则定义的内容。这里每个节点上都有kuc p 0 6bernetes.io/hostname,所以看不出topologyKey起到的作用。

如果b| * } N o S y 5ackend有两个Pod,分别在不同的节点上。= p ^ |

$ kubectl get po -o wide
NAME                       READY   STAu ] l 7 P B a XTUS    RESTARTS   AGE     IP            NODE
backend8 j m ^ u | D 0-658f6cb858-5bpd6   1/1     Running   0          23m     172.16.0.40   192.168.0.97
ba7 . ] ! @ zckend-658f6cb858y [ M * M k S 7 R-dlrz8   1/1     Running   0          2m36s   172.16.0.67   192.168.0.100

给192.168.0.97和192.168.0.94打一个p% $ S * { M e ~ herfer=true的标签。

$ kubectl label node 192.168.0.97 perfer=tru/ 7 & k - Y .e
node/192.168.0.97 labeled
$ kubectl label node 192.168.0.94 perfer=true
nol 9 A ; j 0 E + Pde/192.168.0.94 labeled
$C ~ D | r kubectl get node -L p0 M W G , ; b N uerfh ] [ Jer
N1 X a 2 C AMEs z k 1 i O ! X            STATUS   ROLES    AGE   VERSION                            PERFER
19F E g g R $ b l2.168.0.100   Ready    <none>   4 5 H `4m   v1.15.6-r1-20.3.0.2.B001-15.30.2
192.168.0.212   Ready    <none>   91m   v1.15.6-r1-20.3.0.2.B001-15.30.2
192.16{ s B t D x8.0.94    Ready    <none>   91m   v1.15.6-r1-20.3.0.2.B001-15.30.2   tr* f @ue
192.168.0.97    Read@ I d { F 7 0y    <none>   91m   v1.15.6-r1-20.3.0.2.B001-15.30.2   true

将podAffinity的topologyKey定义为perfer。

        affin] - 5 x O * $ @ity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: perfer
labelSelector:
matchLabels:
app: backend

调度时,先圈定拥有perfer标签的节点,这里也就是192.168.0.97和192.168.0.94,然后再匹配app=backend标签的Pod,从而fr6 y $ g e ; U Jontend就会全部部署在192.168.0.97上。

$ kubectl cre+ o k 5ate -f affinity3.yaml
deployment.apps/fron/ ^ 9 r J ; 0 ` -tend created
$ kubectl get po -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP            NODE
backend-658f6cb858-5bpd6    1/1     Running   0          26m     172.16.0.40   1922 v u r p M.168.0.97
backend-658f6cb858-dlrz8    1/1     Running   0          5m38s   172.1M Z , . $ #6.0.67   192.168.0.100
frontend-67ff9b7b97-dsqzn   1/1     Running   0          6s      172.16.0.70   192.168.0.97
frontend-67ff9b7b97-hxm5t   1/1     Running   0          6s      172.16.0.71   192.168.0.97
frontend-67ff9b7b97-z8pdb   1/1- @ J 3 K Q     Running   0          6s$ d T A o      172.16.0.72   192.168.0.97

Pod AntiAffinix Q + { 5 5ty(Pod反亲和)

前面讲a p d了Pod的亲和,通过亲和将Pod部署在一起,有时候需求却恰恰相反,需要将Pod分开部署,例如Pod之间部署在一起会影响性} / w 1 ~能的情况。

下面例子中定义了反亲h l [ $ 0 P 和规则,这个规则表示Pod不能调度到拥有app=frontend标签Pod的节点上,也就是下面将frontend分别调度到不同的节点上(每个/ : m T节点只有一个Pod)。

apiVersion: apps/v1
kind: Deployment
metadata:
name:   frontenK p ` T + O 9d
labels:
app:  frontend
spec:4 # = i A X S , b
selector:
matchLabels:
app: frontend
replicas: 5
template:
metadata:
labels:
app:  frontend
spec:
containers:
- image:  nginx:alpine
name:  frontend
resources:
requests:
cpu:  100m
memory:  200Mi
limits:
cpu:  100m
memory:  200Mi
imagePullSecrets:
- name: default-secret
affinity:
podAntiAffu k  |inity:
requiredDuringSchedulingIgnoredDuric M k & ;ngExecution:B B o W i ,
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: frontend

创建并查看,可以看到每个节点上只有一个frontend的PodC f D ; 7 W ( & },还有一个在Pending,因为在部署第5个时4个节点上都有了app=frontend的Pod,所以第5个一直是Pending。

$ kubectl create -f af u j ffinity4.yaml
dD ! % # ? S reployment.apps/frontend created
$ kubectl get po -o wide
NAME                        READY   STATUS    RG . N ` zESTARTS   AGE   IP            NODE
frontend-6f686d8d87-8dlsc   1/1     Running   0          18s   172.16.0.76   192.168.0.100
frontend-6f686d8d87-d6l8p   0/1     Pending   0          18s   <none>        <nong K oe>
fronteZ T -nd-6f686d8d87-hgcq2   1/1     Running   0          18s   172.16.0.54   192.168.0.97
frontendy h 8 2 -6f686d8d87-q7cfq   1/1     Running   0          18s   172.16.0.47   192.168.0.212
frontE Y Gend-6f686d8d87-xl8hx   1/1     Rd 2 v 9unning   0          18s   172.16.0.23   192.168.0.94