Kubernetes Namespace 和 Node 做亲和部署
背景
在共享集群(多租户共享底层硬件资源)中, 遇到特殊租户
需要独享
特定资源(比如:独占GPU资源、独占Node节点等)时候,可以对namespace
下的Pod
请求统一做亲和性
操作
Kubernetes 具体使用和实现
kubernetes中,最最核心部署通通过: Pod节点亲和性Affinity
和 Pod污点Taint和容忍度Toleration
组合方式实现 Pod真实部署在具体那个Node上
节点亲和性Affinity
亲和性主要分为两类:nodeAffinity
和podAffinity
nodeSelector
Label
是kubernetes
中一个非常重要的概念,用户可以非常灵活的利用 label
来管理集群中的资源,比如最常见的一个就是 service
通过匹配 label
去选择 POD
的。而 POD
的调度也可以根据节点的 label
进行特定的部署。
$ kubectl label nodes docker-desktop key=value
node/docker-desktop labeled
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
docker-desktop Ready control-plane,master 109d v1.22.5 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,key=value,kubernetes.io/arch=amd64,kubernetes.io/hostname=docker-desktop,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
可以对Pod进行nodeSelector
亲和性部署
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox
name: test
spec:
containers:
- command:
- sleep
- "1d"
image: busybox
name: test
nodeSelector:
# 通过nodeSelector强制进行亲和部署,对于有 key: value label的node没有资源等限制,Pod会直接失败
key: value
nodeSelector
的方式比较直观,但是还够灵活,控制粒度偏大
nodeAffinity
nodeAffinity
就是节点亲和性, 可以进行一些简单的逻辑组合了,不只是简单的相等匹配.
nodeAffinity调度可以分成软策略
和硬策略
两种方式.
软策略
就是如果你没有满足调度要求的节点的话,POD 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓了的策略;硬策略
就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不干的策略。
apiVersion: v1
kind: Pod
metadata:
name: node-affinity
labels:
app: node-affinity-test
spec:
containers:
- name: node-affinity
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In #可以有 In、NotIn、Gt、Lt、Exists、DoesNotExist
values:
- Linux
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- Window
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: key
operator: In
values:
- value
- value1
如果
nodeSelectorTerms
下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions
有多个选项的话,则必须同时满足这些条件才能正常调度POD
污点Taint和容忍度Toleration
Taint
在一类服务器上打上污点,让不能容忍这个污点的Pod
不能部署在打了污点的服务器上。(锁)
Toleration
是让Pod
容忍节点上配置的污点,可以让一些需要特殊配置的Pod
能够调用到具有污点和特殊配置的节点上。(钥匙)
污点Taint
Taint Effect
对 Node
打 Label
包括以下3
种:
NoSchedule:禁止调度到该节点,已经在该节点上的Pod不受影响
NoExecute:禁止调度到该节点,如果不符合这个污点,会立马被驱逐(或在一段时间后驱逐,默认300s,可单独设置驱逐时间)
PreferNoSchedule:尽量避免将Pod调度到指定的节点上,如果没有更合适的节点,可以部署到该节点
$ kubectl get node
NAME STATUS ROLES AGE VERSION
docker-desktop Ready control-plane,master 109d v1.22.5
$ kubectl taint nodes docker-desktop key=value:PreferNoSchedule
node/docker-desktop tainted
$ kubectl get node docker-desktop -o go-template --template {{.spec.taints}}
[map[effect:PreferNoSchedule key:key value:value]]
对Node
打了基于key=value
的PreferNoSchedule
类型污点
容忍度Toleration
容忍度Toleration 就是Pod
对 Node Taints的选择关系
- 完全匹配
tolerations:
- key: "keu"
operator: "Equal"
value: "value"
effect: "PreferNoSchedule"
- 不完全匹配
tolerations:
- key: "key"
operator: "Exists"
effect: "PreferNoSchedule"
tolerationSeconds: 600 # 节点不健康,600秒后再驱逐
- node.kubernetes.io/not-ready:节点未准备好,相当于节点状态Ready的值为False
- node.kubernetes.io/unreachable:Node Controller访问不到节点,相当于节点状态Ready的值为Unknown
- node.kubernetes.io/out-of-disk:节点磁盘耗尽
- node.kubernetes.io/memory-pressure:节点存在内存压力
- node.kubernetes.io/disk-pressure:节点存在磁盘压力
- node.kubernetes.io/network-unavailable:节点网络不可达
Namespace 和 Node 做亲和部署设计
首先,对于特定的Namespace
通过 Label
, 通过namespace-node-affinity: enabled
进行开启。
apiVersion: v1
kind: Namespace
metadata:
name: test-demo
labels:
# namespace级生效,对本namespace开启
namespace-node-affinity: enabled
通过configmap
对namespace下的亲和性最全局配置
apiVersion: v1
kind: ConfigMap
metadata:
name: namespace-node-affinity-webhook
namespace: kube-system # 这个namespace是 webhook所在的namespace
data:
# namespace 名称: test-demo、default
setting-namespace-name: |
nodeSelectorTerms: #做 nodeAffinity 下的 硬策略
- matchExpressions:
- key: "kubernetes.io/os"
operator: In
values:
- "linux"
tolerations: #做 tolerations 策略
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
excludedLabels: #如果匹配其中全部key-value 可跳过强绑定
namespace-node-affinity.cmss.com: disabled
对于Pod
和Deployment
, 可以通过统一标签,跳过以上白名单,namespace-node-affinity.cmss.com: disabled
# Pod
apiVersion: v1
kind: Pod
metadata:
name: xxxx
annotations:
namespace-node-affinity.cmss.com: disabled
...
# Deployment
...
spec:
template:
metadata:
#添加注解
annotations:
namespace-node-affinity.cmss.com: disabled
...
逻辑: 通过webhook
监听Pod
的Create
事件,如果监听到事件后,先判断Pod
中是否有亲和性策略,并且判断namespace的 node亲和性是否开启,
如果都开启,将亲和性指令merge一起
//...
func buildNodeSelectorTermsPath(podSpec coreV1.PodSpec) PatchPath {
var path PatchPath
//pod没有亲和性设置
if podSpec.Affinity == nil {
path = CreateAffinity
} else if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity == nil { //pod有亲和性设置,但是没有NodeAffinity
path = CreateNodeAffinity
} else if podSpec.Affinity.NodeAffinity != nil && podSpec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { //pod有亲和性设置,且有NodeAffinity,但是没有强策略
path = AddRequiredDuringScheduling
} else {
path = AddNodeSelectorTerms
}
return path
}
//...
func buildTolerationsPath(podSpec coreV1.PodSpec) PatchPath {
// pod是否有Tolerations
if podSpec.Tolerations == nil {
return CreateTolerations
}
return AddTolerations
}