K8S入门篇-资源调度
一、Job
1 Job可以干什么
Job可以干什么?在容器启动或退出时做一些任务。Job可以并行执行。
1、需要等待后执行的任务
2、导入SQL文件
3、创建用户、清理表
........等等
示例:
cat job-pi.yaml
apiVersion: batch/v1
kind: Job
metadata:
labels:
job-name: echo-pi
name: echo-pi
namespace: default
spec:
#suspend: true # 1.21+
#ttlSecondsAfterFinished: 100
backoffLimit: 4
completions: 10
parallelism: 3
template:
spec:
containers:
- name: pi
image: perl:5.34.0
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
imagePullPolicy: Always
resources: {}
restartPolicy: Never
查看job:


注意事项:

二、CronJob
CronJob用于以时间为基准周期性地执行任务,这些自动化任务和运行在Linux或UNIX系统上的CronJob一样。CronJob对于创建定期和重复任务非常有用,例如执行备份任务、周期性调度程序接口、发送电子邮件等。
对于Kubernetes 1.8以前的版本,需要添加--runtime-config=batch/v2alpha1=true参数至APIServer中,然后重启APIServer和Controller Manager用于启用API,对于1.8以后的版本无须修改任何参数,可以直接使用,本节的示例基于1.8以上的版本。

2.1 创建CronJob
创建CronJob有两种方式,一种是直接使用kubectl创建,一种是使用yaml文件创建。
使用kubectl创建CronJob的命令如下:
kubectl run hello --schedule="*/1 * * * *" --restart=OnFailure --image=busybox -- /bin/sh -c "date; echo Hello from the Kubernetes cluster"
对应的yaml文件如下:
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
本例创建一个每分钟执行一次、打印当前时间和Hello from the Kubernetes cluster的计划任务。
查看创建的CronJob:
kubectl get cj

kubectl logs hello-27779598-pzznh

2.2 CronJob参数

2.3 suspend参数测试
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
suspend: true #加上suspend参数
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure

再次查看cronjob,超过1分钟后,也没有继续执行:

查看是否被挂起:
kubectl get cronjob/hello -oyaml

恢复挂起:
kubectl patch cronjob/hello --type=strategic --patch '{"spec":{"suspend":false}}'

再次查看是否被挂起:

三、InitContainer
3.1 初始化容器的用途
在主应用启动之前,做一些初始化的操作,比如创建文件、修改内核参数、等待依赖程序启动或其他需要在主程序启动之前需要做的工作。

3.2 Init容器和PostStart的区别
PostStart:依赖主应用的环境,而且并不一定先于Command运行【如果执行失败,则主容器会不断重启】
InitContainer:不依赖主应用的环境,可以有更高的权限和更多的工具,一定会在主应用启动之前完成【如果执行失败的话,主容器是不会执行的】
3.3 Init容器和普通容器的区别
Init 容器与普通的容器非常像,除了如下几点:
- 它们总是运行到完成;
- 上一个运行完成才会运行下一个;
- 如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止,但是Pod 对应的 restartPolicy 值为 Never,Kubernetes 不会重新启动 Pod。
- Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe
3.4 Init容器配置解析
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
spec:
serviceName: elasticsearch
replicas: 3
selector:
matchLabels:
app: elasticsearch
template:
metadata:
labels:
app: elasticsearch
spec:
volumes:
- name: data #设置一个emptyDir类型的共享目录,用于容器间内容共享
emptyDir: {}
initContainers: #定义初始化容器
- name: fix-permissions
image: busybox #定义一个容器,用于修改目录权限
command: ["sh", "-c", "chown -R 1000:1000 /usr/share/elasticsearch/data"]
securityContext:
privileged: true
volumeMounts: #进行目录挂载
- name: data
mountPath: /usr/share/elasticsearch/data
- name: increase-vm-max-map #修改内核参数
image: busybox
command: ["sysctl", "-w", "vm.max_map_count=262144"]
securityContext:
privileged: true
- name: increase-fd-ulimit # 修改内核参数
image: busybox
command: ["sh", "-c", "ulimit -n 65536"]
securityContext:
privileged: true
containers: #实际使用的容器
- name: elasticsearch
#image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3
image: dotbalo/es:2.4.6-cluster
imagePullPolicy: Always
resources:
limits:
cpu: 1000m
requests:
cpu: 100m
ports:
- containerPort: 9200
name: rest
protocol: TCP
- containerPort: 9300
name: inter-node
protocol: TCP
volumeMounts:
- name: data #挂载数据目录,由于是以普通用户启动,所以没权限修改所属用户和组,需要通过初始化容器来进行所属组和用户
mountPath: /usr/share/elasticsearch/data
env:
- name: "cluster.name"
value: "pscm-cluster"
- name: "CLUSTER_NAME"
value: "pscm-cluster"
- name: "discovery.zen.minimum_master_nodes"
value: "2"
- name: "MINIMUM_MASTER_NODES"
value: "2"
- name: "node.name"
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: "NODE_NAME"
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: "discovery.zen.ping.unicast.hosts"
value: "es-cluster-0.elasticsearch, es-cluster-1.elasticsearch, es-cluster-2.elasticsearch"
#- name: ES_JAVA_OPTS
# value: "-Xms512m -Xmx512m"
3.5 初始化容器示例
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: test-init
name: test-init
namespace: kube-public
spec:
replicas: 3
selector:
matchLabels:
app: test-init
template:
metadata:
labels:
app: test-init
spec:
volumes:
- name: data
emptyDir: {}
initContainers:
- command:
- sh
- -c
- touch /mnt/test-init.txt
image: nginx
imagePullPolicy: IfNotPresent
name: init-touch
volumeMounts:
- name: data
mountPath: /mnt
- command:
- sh
- -c
- for i in `seq 1 100`; do echo $i; sleep 1; done
image: nginx
imagePullPolicy: IfNotPresent
name: echo
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: test-init
volumeMounts:
- name: data
mountPath: /mnt
我们创建了2个初始化容器,第一个是创建一个文件;第二个是打印一串数字。


四、临时容器(EphemeralContainers)
在pod里面注入一个具有很多debug工具的容器,用于排查问题或故障。
开启临时容器功能:
Master节点 vi /usr/lib/systemd/system/kube-apiserver.service --feature-gates=EphemeralContainers=true vi /usr/lib/systemd/system/kube-controller-manager.service --feature-gates=EphemeralContainers=true vi /usr/lib/systemd/system/kube-scheduler.service --feature-gates=EphemeralContainers=true 所有节点 vi /usr/lib/systemd/system/kube-proxy.service --feature-gates=EphemeralContainers=true vi /etc/kubernetes/kubelet-conf.yml featureGates: EphemeralContainers: true 重启所有服务 System restart kube-apiserver kube-controller-manager kube-scheduler kube-proxy kubelet
临时容器使用
K8s 1.20+ kubectl debug redis-new-5b577b46c7-2jv4j -ti --image=debug-tools
五、污点和容忍
5.1 调度的场景
Master节点:保证master组件正常运行,一般不建议生产pod调度到master上。
新增节点:灰度一些pod上来看看是否正常
维护节点:节点维护时,临时不让新的pod调度上来
特殊节点:比如ssd机器、GPU机器等特殊属性的节点,只能指定pod调度上来
5.2 什么是污点和容忍度?
污点(Taint) 是应用在节点之上的,从这个名字就可以看出来,是为了排斥pod 所存在的。
容忍度(Toleration)是应用于 Pod 上的,允许(但并不要求)Pod 调度到带有与之匹配的污点的节点上。
5.3 污点和容忍度的作用?
Taint(污点)和 Toleration(容忍)可以作用于node和 pod 上,其目的是优化pod在集群间的调度,这跟节点亲和性类似,只不过它们作用的方式相反,具有taint的node和pod是互斥关系,而具有节点亲和性关系的node和pod是相吸的。另外还有可以给node节点设置label,通过给pod设置nodeSelector将pod调度到具有匹配标签的节点上。
Taint 和 toleration 相互配合,可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的 pod,是不会被该节点接受的。如果将toleration应用于pod上,则表示这些pod可以(但不要求)被调度到具有相应taint的节点上。
5.4 给节点打污点
给节点node1增加一个污点,它的键名是key1,键值是value1,效果是NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到node1这个节点。
kubectl taint nodes node1 key1=value1:NoSchedule
若要移除上述命令所添加的污点,你可以执行:
kubectl taint nodes node1 key1=value1:NoSchedule- kubectl taint nodes node1 key1:NoSchedule-
二者都可以,k8s不会看value值,只会对比key和effect。
5.5 在pod 中定义容忍度
可以在 PodSpec 中定义 Pod 的容忍度。 下面两个容忍度均与上面例子中使用kubectl taint命令创建的污点相匹配, 因此如果一个 Pod 拥有其中的任何一个容忍度都能够被分配到node1:
#完全匹配 tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" #模糊匹配 tolerations: - key: "key1" operator: "Exists" effect: "NoSchedule"
5.6 toleration实例
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "ssd" #存在ssd这个key,且effect为NoSchedule的节点,可以被容忍,也就是可以被调度上去。
operator: "Exists"
effect: "NoSchedule"
operator 的默认值是 Equal。
一个容忍度和一个污点相“匹配”是指它们有一样的键名和效果,并且:
- 如果
operator是Exists(此时容忍度不能指定value) - 如果
operator是Equal,则它们的value应该相等 - 如果
operator不指定,则默认为Equal
注意:
- 如果一个容忍度的
key为空且 operator 为Exists, 表示这个容忍度与任意的 key 、value 和 effect 都匹配,即这个容忍度能容忍任意 taint。 - 如果
effect为空,则可以与所有键名key1的效果相匹配。
effect 的参数含义
- NoSchedule 新的不能容忍的pod不能再调度过来,但是之前运行在node节点中的Pod不受影响
- NoExecute 新的不能容忍的pod不能调度过来,老的pod也会被驱逐
- PreferNoScheduler 表示尽量不调度到污点节点中去
5.7 多个污点的匹配原则
可以给一个节点添加多个污点,也可以给一个 Pod 添加多个容忍度设置。
Kubernetes 处理多个污点和容忍度的过程就像一个过滤器:从一个节点的所有污点开始遍历, 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点,特别是以下情况:
- 如果未被过滤的污点中存在至少一个 effect 值为
NoSchedule的污点, 则 Kubernetes 不会将 Pod 分配到该节点。 - 如果未被过滤的污点中不存在 effect 值为
NoSchedule的污点, 但是存在 effect 值为PreferNoSchedule的污点, 则 Kubernetes 会 尝试 不将 Pod 分配到该节点。 - 如果未被过滤的污点中存在至少一个 effect 值为
NoExecute的污点, 则 Kubernetes 不会将 Pod 分配到该节点(如果 Pod 还未在节点上运行), 或者将 Pod 从该节点驱逐(如果 Pod 已经在节点上运行)。
例如,假设您给一个节点添加了如下污点
kubectl taint nodes node1 key1=value1:NoSchedule kubectl taint nodes node1 key1=value1:NoExecute kubectl taint nodes node1 key2=value2:NoSchedule
假定有一个 Pod,它有两个容忍度:
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule" - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute"
上述 Pod 不会被分配到 node1 节点,因为其没有容忍度和第三个污点相匹配。
但是如果在给节点添加上述污点之前,该 Pod 已经在上述节点运行, 那么它还可以继续运行在该节点上,因为第三个污点是三个污点中唯一不能被这个 Pod 容忍的。
5.8 设置NoExcute 的驱逐时间
通常情况下,如果给一个节点添加了一个 effect 值为NoExecute的污点, 则任何不能忍受这个污点的 Pod 都会马上被驱逐, 任何可以忍受这个污点的 Pod 都不会被驱逐。
但是,如果 Pod 存在一个 effect 值为NoExecute的容忍度指定了可选属性tolerationSeconds的值,则表示在给节点添加了上述污点之后, Pod 还能继续在节点上运行的时间。
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" tolerationSeconds: 3600
这表示如果这个 Pod 正在运行,同时一个匹配的污点被添加到其所在的节点, 那么 Pod 还将继续在节点上运行 3600 秒,然后被驱逐。 如果在此之前上述污点被删除了,则 Pod 不会被驱逐。
5.9 容忍的匹配方式
#方式一、完全匹配 key为ssd,且值是true,effect是NoSchedule tolerations: - key: "ssd" operator: "Equal" value: "true" effect: "NoSchedule" #方式二、不完全匹配 key存在,且effect为NoSchedule tolerations: - key: "ssd" operator: "Exists" effect: "NoSchedule" #方式三、大范围匹配 key存在就行,不推荐为内置Taint tolerations: - key: "ssd" operator: "Exists" #方式四、匹配所有,不推荐!! tolerations: - operator: "Exists" #方式五、完全匹配 key为ssd,且值是true,effect是NoSchedule,保留时间配置,1小时后就迁移走 tolerations: - key: "ssd" operator: "Equal" value: "true" effect: "NoExecute" tolerationSeconds: 3600
5.10 基于污点的驱逐
这是在每个 Pod 中配置的在节点出现问题时的驱逐行为。
前文提到过污点的 effect 值 NoExecute会影响已经在节点上运行的 Pod
- 如果 Pod 没有配置忍受 effect 值为
NoExecute的污点,那么 Pod 将马上被驱逐 - 如果 Pod 配置了忍受 effect 值为
NoExecute的污点,但是在容忍度定义中没有指定tolerationSeconds,则 Pod 还会一直在这个节点上运行。 - 如果 Pod 配置了忍受 effect 值为
NoExecute的污点,而且指定了tolerationSeconds, 则 Pod 还能在这个节点上继续运行这个指定的时间长度。
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态Ready的值为 "False"。node.kubernetes.io/unreachable:节点控制器访问不到节点. 这相当于节点状态Ready的值为 "Unknown"。node.kubernetes.io/memory-pressure:节点存在内存压力。node.kubernetes.io/disk-pressure:节点存在磁盘压力。node.kubernetes.io/pid-pressure: 节点的 PID 压力。node.kubernetes.io/network-unavailable:节点网络不可用。node.kubernetes.io/unschedulable: 节点不可调度。node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 启动时指定了一个 "外部" 云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
在节点被驱逐时,节点控制器或者 kubelet 会添加带有NoExecute效应的相关污点。 如果异常状态恢复正常,kubelet 或节点控制器能够移除相关的污点。
六、affinity
6.1 将pod指派给node
我们前面知道了,有nodeSelector来对pod进行节点选择,但是这种方法比较粗狂,只能根据node的标签进行节点选择。比如:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector: #pod部署在具备disktype=ssd的节点上
disktype: ssd
如果我们需要更复杂的方式进行pod调度到node时,只有nodeSelector还是不够,我们需要更丰富的方法来进行调度。这里就是亲和性和反亲和性。
6.2 亲和性与反亲和性
nodeSelector 提供了一种最简单的方法来将 Pod 约束到具有特定标签的节点上。 亲和性和反亲和性扩展了你可以定义的约束类型。使用亲和性与反亲和性的一些好处有:
- 亲和性、反亲和性语言的表达能力更强。
nodeSelector只能选择拥有所有指定标签的节点。 亲和性、反亲和性为你提供对选择逻辑的更强控制能力。【逻辑能力更强】 - 你可以标明某规则是“硬需求”或者“软需求”,这样调度器在无法找到匹配节点时仍然调度该 Pod。【选择方式更多】
- 你可以使用节点上(或其他拓扑域中)运行的其他 Pod 的标签来实施调度约束, 而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起。【灵活组合】
亲和性功能由两种类型的亲和性组成:
- 节点亲和性功能类似于
nodeSelector字段,但它的表达能力更强,并且允许你指定软规则。 - Pod 间亲和性/反亲和性允许你根据其他 Pod 的标签来约束 Pod。
6.3 节点亲和性
节点亲和性概念上类似于 nodeSelector, 它使你可以根据节点上的标签来约束 Pod 可以调度到哪些节点上。 节点亲和性有两种:
requiredDuringSchedulingIgnoredDuringExecution: 调度器只有在规则被满足的时候才能执行调度。此功能类似于nodeSelector, 但其语法表达能力更强。preferredDuringSchedulingIgnoredDuringExecution: 调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Pod。
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
pod会被调度到这样的node上:
- 节点必须包含一个键名为
topology.kubernetes.io/zone的标签, 并且该标签的取值必须为antarctica-east1或antarctica-west1。 - 节点最好具有一个键名为
another-node-label-key且取值为another-node-label-value的标签。
你可以使用 operator 字段来为 Kubernetes 设置在解释规则时要使用的逻辑操作符。 你可以使用 In、NotIn、Exists、DoesNotExist、Gt 和 Lt 之一作为操作符。
NotIn 和 DoesNotExist 可用来实现节点反亲和性行为。 你也可以使用节点污点 将 Pod 从特定节点上驱逐。
说明:
如果你同时指定了 nodeSelector 和 nodeAffinity,两者 必须都要满足, 才能将 Pod 调度到候选节点上。
如果你指定了多个与 nodeAffinity 类型关联的 nodeSelectorTerms, 只要其中一个 nodeSelectorTerms 满足的话,Pod 就可以被调度到节点上。
如果你指定了多个与同一 nodeSelectorTerms 关联的 matchExpressions, 则只有当所有 matchExpressions 都满足时 Pod 才可以被调度到节点上。
weight 权重
你可以为 preferredDuringSchedulingIgnoredDuringExecution 亲和性类型的每个实例设置 weight 字段,其取值范围是 1 到 100。 当调度器找到能够满足 Pod 的其他调度请求的节点时,调度器会遍历节点满足的所有的偏好性规则, 并将对应表达式的 weight 值加和。
最终的加和值会添加到该节点的其他优先级函数的评分之上。 在调度器为 Pod 作出调度决定时,总分最高的节点的优先级也最高。
apiVersion: v1
kind: Pod
metadata:
name: with-affinity-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: label-1
operator: In
values:
- key-1
- weight: 50 #优先调度
preference:
matchExpressions:
- key: label-2
operator: In
values:
- key-2
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
案例2:尽量调度到ssd,非gpu的机器上
通过节点亲和力,调度到期望的节点上:
- 尽量调度到ssd,且不是gpu的机器上(优先匹配,通过优先级weight配置;如果有多个匹配,则需要都满足;)
- 尽量调度到物理机上
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: prefer-ssd
name: prefer-ssd
namespace: kube-public
spec:
replicas: 1
selector:
matchLabels:
app: prefer-ssd
template:
metadata:
creationTimestamp: null
labels:
app: prefer-ssd
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: ssd
operator: In
values:
- "true"
- key: gpu
operator: NotIn
values:
- "true"
weight: 14
- preference:
matchExpressions:
- key: type
operator: In
values:
- physical
weight: 10
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: LANG
value: C.UTF-8
image: nginx
imagePullPolicy: IfNotPresent
name: prefer-ssd
6.2 pod间亲和和反亲和
案例1:通过反亲和将同一个项目的pod部署到不同的节点上
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: must-be-diff-nodes #deployment的标签,可以和pod的不同
name: must-be-diff-nodes
namespace: kube-public
spec:
replicas: 3
selector:
matchLabels:
app: must-be-diff-nodes
template:
metadata:
labels:
app: must-be-diff-nodes
spec:
affinity:
podAntiAffinity: #配置pod反亲和
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions: #具有app=must-be-diff-nodes的节点不在本节点上调度,也就是每个节点确保只有1个同项目的pod
- key: app
operator: In
values:
- must-be-diff-nodes
topologyKey: kubernetes.io/hostname
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: must-be-diff-nodes
案例2 topologyKey使用
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: must-be-diff-zone
name: must-be-diff-zone
namespace: kube-public
spec:
replicas: 4
selector:
matchLabels:
app: must-be-diff-zone
template:
metadata:
labels:
app: must-be-diff-zone
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- must-be-diff-zone
topologyKey: region
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: must-be-diff-zone
实现效果:通过pod反亲和实现:同一个应用部署在不同地区,每个地区1个pod。



浙公网安备 33010602011771号