目录
一、Pod介绍
1、Pod的基础概念
2、pod定义(资源清单)
二、Pod相关实例
1、初始化容器(init容器)
2、镜像拉取策略(image PullPolicy)
3、pod容器重启策略(restartPolicy)
三、Pod进阶
1、资源限制
2、健康检查:又称为探针(Probe)
2.1 livenessProbe (存活探针
2.2 readinessProbe(就绪探针)
2.3 启动、退出动作
3、pod的状态
四、Pod调度
1、自动调度
1.1 list-watch监听机制
1.2 调度过程
2、定向调度
2.1 NodeName(节点名称)
2.2 NodeSeletor(node标签)
3、亲和性调度
3.1 NodeAffinity(节点亲和性)
3.2 PodAffinity(Pod亲和性)
3.3 PodAntiAffinity(Pod反亲和性)
4、Pod污点(Taints)
4.1 NoExecute实例
4.2 NoSchedule实例
5、Pod容忍(Toleration)
实例1:设置 NoSchedule 污点
实例2:设置NoExecute污点
Pod是kubernetes中最小的资源管理组件,Pod也是最小化运行容器化应用的资源对象,一个pod代表着集群中运行的一个进程。kubernetes中其它大多数组件都是围绕着pod来进行支持和扩展pod功能的。
例如,用于管理pod运行的statefulset和deployment 等控制器对象,用于暴露应用的service和ingress对象,为pod提供存储的persistentVolumes存储资源对象。
在kubernetes集群中Pod有如下两种使用方式
一个Pod下的容器必须运行在同一个节点上,现代容器技术建议一个容器只运行一个进程,该进程在容器中PID命名空间中的进程号为1,可直接接受并处理信号,进程终止时容器生命周期也就结束了。
若想在容器内运行多个进程,需要有一个类似linux操作系统init进程的管控类进程,以树状结构完成多进程的生命周期管理,运行于各自容器内的 进程无法直接完成网络通信,这是由于容器间的隔离机制导致,k8s中的Pod资源抽象正式解决此类问题,Pod对象是一组容器的集合,这些容器共享network、UTS及IPC命名空间,因此具有相同的域名,主句名和网络接口,并可通过IPC直接通信。
namespace | 功能 |
---|---|
mnt(mount) | 提供磁盘挂载点和文件系统的隔离能力 |
ipc(inter-Process Communication) | 提供进程间通信的隔离能力 |
net(network) | 提供网络隔离能力 |
uts(Unix Time Sharing) | 提供主机名隔离能力 |
pid | 提供进程号隔离能力 |
user | 提供用户隔离能力 |
Pod中的pause容器
Pod资源中针对各容器提供网络命名空间等共享机制的是底层基础容器pause,pause就是为了管理Pod容器间的共享操作,这个副容器需要能够准确的知道如何去创建共享运行环境的容器,还能管理这些容器的生命周期,为了实现这个副容器的构想,kubernetes中用pause容器来作为一个Pod中所有容器的副容器, 这个pause容器有两个核心的功能,一个是它提供整个Pod的Linux命名空间的基础,二是启动PID命名空间,它在每个Pod中都作为PID为1的进程(init进程),并回收僵尸进程。
pause容器使得Pod中的所有容器可以共享两种资源:网络和存储
网络
存储
总结:
每个Pod都有一个特殊的被称为“基础容器”的pause容器,pause容器对应的镜像属于kubernetes平台的一部分,除了pause容器,每个Pod号包含一个或多个紧密相关的用户应用容器。
kubernetes中pause容器主要为每个容器提供一下功能:
kubernetes涉及这样的Pod概念和特殊组成结构有什么用意?
就是产生pause容器的两个原因是,1、通过pause容器判断整个pod中容器是否正常,2、通过pause容器共享网络和挂载。
Pod的分类
自主式Pod
控制器管理的Pod
Pod的容器分类
基础容器(infrastructure container)
初始化容器(init container)
Init 容器必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法。Init 容器与普通的容器非常像,除了以下两点
如果 Pod 的 Init 容器失败,k8s 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的重启策略(restartPolicy)为 Never,它不会重新启动。
#init容器的作用
因为 init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势
1、Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。例如,没有必要仅为了在安装过程中使用类似 sed、 awk、python 或 dig 这样的工具而去 FROM 一个镜像来生成一个新的镜像。
2、Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
3、应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
4、Init 容器能以不同于 Pod 内应用容器的文件系统视图运行。因此,Init 容器可具有访问 Secrets的权限,而应用容器不能够访问。
5、由于 Init 容器必须在应用容器启动之前运行完成,因此 Init容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
应用容器( main container)
apiVersion: v1 #必选,版本号,例如v1
kind: Pod #必选,资源类型,例如 Pod
metadata: #必选,元数据
name: string #必选,Pod名称
namespace: string #Pod所属的命名空间,默认为"default"
labels: #自定义标签列表
- name: string
spec: #必选,Pod中容器的详细定义
containers: #必选,Pod中容器列表
- name: string #必选,容器名称
image: string #必选,容器的镜像名称
imagePullPolicy: [ Always|Never|IfNotPresent ] #获取镜像的策略
command: [string] #容器的启动命令列表,如不指定,使用打包时使用的启动命令
args: [string] #容器的启动命令参数列表
workingDir: string #容器的工作目录
volumeMounts: #挂载到容器内部的存储卷配置
- name: string #引用pod定义的共享存储卷的名称,需用volumes[]部分定义的的卷名
mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
readOnly: boolean #是否为只读模式
ports: #需要暴露的端口库号列表
- name: string #端口的名称
containerPort: int #容器需要监听的端口号
hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
protocol: string #端口协议,支持TCP和UDP,默认TCP
env: #容器运行前需设置的环境变量列表
- name: string #环境变量名称
value: string #环境变量的值
resources: #资源限制和请求的设置
limits: #资源限制的设置
cpu: string #Cpu的限制,单位为core数,将用于docker run --cpu-shares参数
memory: string #内存限制,单位可以为Mib/Gib,将用于docker run --memory参数
requests: #资源请求的设置
cpu: string #Cpu请求,容器启动的初始可用数量
memory: string #内存请求,容器启动的初始可用数量
lifecycle: #生命周期钩子
postStart: #容器启动后立即执行此钩子,如果执行失败,会根据重启策略进行重启
preStop: #容器终止前执行此钩子,无论结果如何,容器都会终止
livenessProbe: #对Pod内各容器健康检查的设置,当探测无响应几次后将自动重启该容器
exec: #对Pod容器内检查方式设置为exec方式
command: [string] #exec方式需要制定的命令或脚本
httpGet: #对Pod内个容器健康检查方法设置为HttpGet,需要制定Path、port
path: string
port: number
host: string
scheme: string
HttpHeaders:
- name: string
value: string
tcpSocket: #对Pod内个容器健康检查方式设置为tcpSocket方式
port: number
initialDelaySeconds: 0 #容器启动完成后首次探测的时间,单位为秒
timeoutSeconds: 0 #对容器健康检查探测等待响应的超时时间,单位秒,默认1秒
periodSeconds: 0 #对容器监控检查的定期探测时间设置,单位秒,默认10秒一次
successThreshold: 0
failureThreshold: 0
securityContext:
privileged: false
restartPolicy: [Always | Never | OnFailure] #Pod的重启策略
nodeName: #设置NodeName表示将该Pod调度到指定到名称的node节点上
nodeSelector: obeject #设置NodeSelector表示将该Pod调度到包含这个label的node上
imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
- name: string
hostNetwork: false #是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
volumes: #在该pod上定义共享存储卷列表
- name: string #共享存储卷名称 (volumes类型有很多种)
emptyDir: {} #类型为emtyDir的存储卷,与Pod同生命周期的一个临时目录。为空值
hostPath: string #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
path: string #Pod所在宿主机的目录,将被用于同期中mount的目录
secret: #类型为secret的存储卷,挂载集群与定义的secret对象到容器内部
scretname: string
items:
- key: string
path: string
configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
name: string
items:
- key: string
path: string
在kubernetes中所有资源的一级属性都是一样的,主要包含5部分
在上面的属性中,spec 是接下来研究的重点,继续看它的常见子属性
官网示例:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/
下述例子是定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice 启动, 第二个等待 mydb 启动。 一旦这两个 Init容器都启动完成,Pod 将启动 spec 中的应用容器。
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
#查看pod的详细信息
kubectl describe pod myapp-pod
#查看具体的日志信息
kubectl logs myapp-pod -c init-myservice
#配置myservice的yaml文件
vim myservice.yaml
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
kubectl create -f myservice.yaml
kubectl get svc
kubectl get pods -n kube-system
kubectl get pods
特别说明:
Pod 的核心是运行容器,必须指定容器引擎,比如 Docker,启动容器时,需要拉取镜像,k8s 的镜像拉取策略可以由用户指定:
注意:对于标签为“:latest”的镜像文件,其默认的镜像获取策略即为“Always”;而对于其他标签的镜像,其默认策略则为“IfNotPresent”。
创建Pod资源时,因为设置镜像的版本资源为:lastest,默认的镜像拉取策略为Always
设置镜像的版本资源为:1.14
重新创建一个Pod资源,修改镜像版本为latest,然后再设置镜像策略为IfNotPresent,会按设置的顺序进行。
当 Pod 中的容器退出时通过节点上的 kubelet 重启容器。适用于 Pod 中的所有容器。
1、Always
当容器终止退出后,总是重启容器,默认策略
容器退出总是重启容器,不管返回状态码如何,默认的Pod容器重启策略
2、OnFailure
当容器异常退出(退出状态码非0)时,重启容器;正常退出则不重启容器
仅在容器异常退出时,返回码为非0时,会重启容器。
3、Never
当容器终止退出,从不重启容器。
容器退出时从不重启容器,不管返回状态码如何
#注意:K8S 中不支持重启 Pod 资源,只有删除重建
创建的Pod资源,默认的重启策略都为:Always
重新创建Pod资源,并且添加默认的重启策略为:Never
当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源。
这种机制主要通过resources选项实现,他有两个子选项:
如果 Pod 运行所在的节点具有足够的可用资源,容器可以使用超出所设置的 request 资源量。不过,容器不可以使用超出所设置的 limit 资源量。
如果给容器设置了内存的 limit 值,但未设置内存的 request 值,Kubernetes 会自动为其设置与内存 limit 相匹配的 request 值。
官网示例:
https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
CPU 资源单位:
内存 资源单位 :
案例:
vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: web
image: nginx
env:
- name: WEB_ROOT_PASSWORD
value: "password"
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: db
image: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "abc123"
resources:
requests:
memory: "512Mi"
cpu: "0.5"
limits:
memory: "1Gi"
cpu: "1"
kubectl apply -f pod-resources.yaml
kubectl describe pod frontend
#查看详细信息
kubectl get pods -o wide
kubectl describe nodes node02
#由于当前虚拟机有2个CPU,所以Pod的CPU Limits一共占用了75%
探针是由kubelet对容器执行的定期诊断。
探针的三种规则:
Probe支持三种检查方法:
每次探测都将获得以下三种结果之一:
●成功:容器通过了诊断。
●失败:容器未通过诊断。
●未知:诊断失败,因此不会采取任何行动
官网示例:
https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
实例1:exec方式
vim exec.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
namespace: default
spec:
containers:
- name: liveness-exec-container
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/live ; sleep 30; rm -rf /tmp/live; sleep 3600"]
livenessProbe:
exec:
command: ["test","-e","/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
可以看到 Pod 中只有一个容器。kubelet 在执行第一次探测前需要等待 2 秒,kubelet 会每 3 秒执行一次存活探测。kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 当到达第 31 秒时,这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。
探测策略:
#创建资源
kubectl create -f exec.yaml
#查看资源的详细信息
kubectl describe pods liveness-exec
#查看资源实时状态
kubectl get pods -w
实例2:httpGet方式
vim httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-httpget
namespace: default
spec:
containers:
- name: liveness-httpget-container
image: nginx
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: http
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 10
在这个配置文件中,可以看到 Pod 也只有一个容器。initialDelaySeconds 字段告诉 kubelet 在执行第一次探测前应该等待 1 秒。periodSeconds 字段指定了 kubelet 每隔 3 秒执行一次存活探测。kubelet 会向容器内运行的服务(服务会监听 80 端口)发送一个 HTTP GET 请求来执行探测。如果服务器上 /index.html 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。
任何大于或等于 200 并且小于 400 的返回代码标示成功,其它返回代码都标示失败。
#创建pod资源
kubectl create -f httpget.yaml
#测试:将容器内检测的文件删除
kubectl exec -it liveness-httpget -- rm -rf /usr/share/nginx/html/index.html
实例3:tcpSocket方式
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: k8s.gcr.io/goproxy:0.1
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
上述例子同时使用 readinessProbe 和 livenessProbe 探测。kubelet 会在容器启动 5 秒后发送第一个 readinessProbe 探测。这会尝试连接 goproxy 容器的 8080 端口。如果探测成功,kubelet 将继续每隔 10 秒运行一次检测。除了 readinessProbe 探测,这个配置包括了一个 livenessProbe 探测。kubelet 会在容器启动 15 秒后进行第一次 livenessProbe 探测。就像 readinessProbe 探测一样,会尝试连接 goproxy 容器的 8080 端口。如果 livenessProbe 探测失败,这个容器会被重新启动。
vim tcpsocket.yaml
apiVersion: v1
kind: Pod
metadata:
name: probe-tcp
spec:
containers:
- name: nginx
image: nginx:1.13
livenessProbe:
initialDelaySeconds: 5
timeoutSeconds: 1
tcpSocket:
port: 8080
periodSeconds: 10
failureThreshold: 2
实例1:
vim readiness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget
namespace: default
spec:
containers:
- name: readiness-httpget-container
image: soscscs/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
livenessProbe:
httpGet:
port: http
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 10
#创建pod资源
kubectl create -f readiness-httpget.yaml
kubectl get pods
实例2:验证readiness探测失败,Pod 无法进入READY状态,且端点控制器将从 endpoints 中剔除删除该 Pod 的 IP 地址
vim readiness-myapp.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp1
labels:
app: myapp
spec:
containers:
- name: myapp
image: soscscs/myapp:v1
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 10
---
apiVersion: v1
kind: Pod
metadata:
name: myapp2
labels:
app: myapp
spec:
containers:
- name: myapp
image: soscscs/myapp:v1
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 10
---
apiVersion: v1
kind: Pod
metadata:
name: myapp3
labels:
app: myapp
spec:
containers:
- name: myapp
image: soscscs/myapp:v1
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
vim post.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: soscscs/myapp:v1
lifecycle: #此为关键字段
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler >> /var/log/nginx/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo Hello from the poststop handler >> /var/log/nginx/message"]
volumeMounts:
- name: message-log
mountPath: /var/log/nginx/
readOnly: false
initContainers:
- name: init-myservice
image: soscscs/myapp:v1
command: ["/bin/sh", "-c", "echo 'Hello initContainers' >> /var/log/nginx/message"]
volumeMounts:
- name: message-log
mountPath: /var/log/nginx/
readOnly: false
volumes:
- name: message-log
hostPath:
path: /data/volumes/nginx/log/
type: DirectoryOrCreate
由上可知,init Container先执行,然后当一个主容器启动后,Kubernetes 将立即发送 postStart 事件。
由上可知,当在容器被终结之前, Kubernetes 将发送一个 preStop 事件。
在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:
各组件协作介绍
kubernets是通过List-Watch的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。
用户是通过kubelet 根据配置文件,向APIServer发送命名,在Node节点上建立Pod和container。
APIServer经过API 调用,权限控制,调用资源和存储资源的过程,实际上还没有真正开始部署应用,这里需要controller Manager、Scheduler 和 kubelet 的协助才能完成整个部署过程。
在kubernetes中,所有部署的信息都会写到etcd中保存,实际上etcd在存储部署信息的时候,会发送Create 事件给APIServer,而APIServer会通过监听(watch)etcd。
Pod 启动典型创建过程
(1)这里有三个 List-Watch,分别是 Controller Manager(运行在 Master),Scheduler(运行在 Master),kubelet(运行在 Node)。 他们在进程已启动就会监听(Watch)APIServer 发出来的事件。
(2)用户通过 kubectl 或其他 API 客户端提交请求给 APIServer 来建立一个 Pod 对象副本。
(3)APIServer 尝试着将 Pod 对象的相关元信息存入 etcd 中,待写入操作执行完成,APIServer 即会返回确认信息至客户端。
(4)当 etcd 接受创建 Pod 信息以后,会发送一个 Create 事件给 APIServer。
(5)由于 Controller Manager 一直在监听(Watch,通过https的6443端口)APIServer 中的事件。此时 APIServer 接受到了 Create 事件,又会发送给 Controller Manager。
(6)Controller Manager 在接到 Create 事件以后,调用其中的 Replication Controller 来保证 Node 上面需要创建的副本数量。一旦副本数量少于 RC 中定义的数量,RC 会自动创建副本。总之它是保证副本数量的 Controller(PS:扩容缩容的担当)。
(7)在 Controller Manager 创建 Pod 副本以后,APIServer 会在 etcd 中记录这个 Pod 的详细信息。例如 Pod 的副本数,Container 的内容是什么。
(8)同样的 etcd 会将创建 Pod 的信息通过事件发送给 APIServer。
(9)由于 Scheduler 在监听(Watch)APIServer,并且它在系统中起到了 “承上启下” 的作用,“承上”是指它负责接收创建的 Pod 事件,为其安排 Node;“启下”是指安置工作完成后,Node 上的 kubelet 进程会接管后继工作,负责 Pod 生命周期中的“下半生”。 换句话说, Scheduler 的作用是将待调度的 Pod 按照调度算法和策略绑定到集群中 Node 上。
(10)Scheduler 调度完毕以后会更新 Pod 的信息,此时的信息更加丰富了。除了知道 Pod 的副本数量,副本内容。还知道部署到哪个 Node 上面了。并将上面的 Pod 信息更新至 API Server,由 APIServer 更新至 etcd 中,保存起来。
(11)etcd 将更新成功的事件发送给 APIServer,APIServer 也开始反映此 Pod 对象的调度结果。
(12)kubelet 是在 Node 上面运行的进程,它也通过 List-Watch 的方式监听(Watch,通过https的6443端口)APIServer 发送的 Pod 更新的事件。kubelet 会尝试在当前节点上调用 Docker 启动容器,并将 Pod 以及容器的结果状态回送至 APIServer。
(13)APIServer 将 Pod 状态信息存入 etcd 中。在 etcd 确认写入操作成功完成后,APIServer将确认信息发送至相关的 kubelet,事件将通过它被接受。
#注意:在创建 Pod 的工作就已经完成了后,
#为什么 kubelet 还要一直监听呢?
原因很简单,假设这个时候 kubectl 发命令,要扩充 Pod 副本数量,那么上面的流程又会触发一遍,kubelet 会根据最新的 Pod 的部署情况调整 Node 的资源。又或者 Pod 副本数量没有发生变化,但是其中的镜像文件升级了,kubelet 也会自动获取最新的镜像文件并且加载。
scheduler是kubernetes 的调度器,主要的任务是把定义的Pod分配到集群的节点上,其主要考虑的问题如下。
调度分为几个部分: 首先是过滤掉不满足条件的节点,这个过程称为预算策略(predicate);然后对通过的节点按照优先级排序,这个是优选策略(priorities);最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误。
Predicate 有一系列的常见的算法可以使用:
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。
经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。
priorities一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。有一系列的常见的优先级选项包括:
定向调度、指的是利用在Pod上声明nodeName或nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使调度的目标node不存在,也会向上面进行调度,只不过pod运行失败而已。
NodeName用于强制约束将Pod调度到指定的Name的Node节点上,这种方式,起始就是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。
创建一个pod-nodename.yaml文件
apiVersion: v1
kind: Pod
metadata:
name: pod-nodename
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
nodeName: node01 # 指定调度到node01节点上
kubectl apply -f myapp.yaml
kubectl get pods -o wide
##查看详细事件(发现未经过 scheduler 调度分配)
kubectl describe pod pod-nodename
NodeSelector 用于将pod调度到添加了指定标签的node节点上,它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后pod调度到目标节点,该匹配规则是强制约束。
给节点添加标签
创建pod-nodeselector.yaml文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp1
spec:
replicas: 3
selector:
matchLabels:
app: myapp1
template:
metadata:
labels:
app: myapp1
spec:
nodeSelector:
kfc: a
containers:
- name: myapp1
image: soscscs/myapp:v1
ports:
- containerPort: 80
kubectl apply -f pod-nodeselector.yaml
kubectl get pods pod-nodeselector -o wide
##此时调度到指定标签的节点上,如果在yaml文件中,将标签修改,它将会调度到标签所在的node,即使node不存在。
标签相关设置:
#修改一个 label 的值,需要加上 --overwrite 参数
kubectl label nodes node02 kgc=a --overwrite
#删除一个 label,只需在命令行最后指定 label 的 key 名并与一个减号相连即可:
kubectl label nodes node02 kgc-
#指定标签查询 node 节点
kubectl get node -l kgc=a
官方文档:https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/
定向调度使用起来非常方便,但是也存在一定的问题,那就是没有满足条件的node,那么Pod将不会被运行,即使在集群中还有可用的Node列表也不行,这就限制了它的使用场景
基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity),它在NodeSelector的基础之上进行了扩展,可用通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足的节点上,使调度更加灵活。
Affinity主要分为三类
#关于亲和性(反亲和性)使用场景的说明
//亲和性:
如果两个应用频繁交互,那就有必要利用亲和性让两个应用的竟可能靠近,这样可以减少因网络通信而带来的性能损耗。
//反亲和性:
当应用的采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。
kubectl explain pod.spec.affinity.nodeAffinity #查看解释资源
----------------------------------硬限制----------------------------------------------------
requiredDuringSchedulingIgnoredDuringExecution #Node节点必须满足指定的所有规则才可以,相当于硬限制
nodeSelectorTerms #节点选择列表
matchFields #按节点字段列出的节点选择器要求列表
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
values #值
operator #关系符:支持Exists, DoesNotExist, In, NotIn, Gt, Lt
----------------------------------软限制----------------------------------------------------
preferredDuringSchedulingIgnoredDuringExecution #优先调度到满足指定的规则的Node,相当于软限制 (倾向)
preference #一个节点选择器项,与相应的权重相关联
matchFields #按节点字段列出的节点选择器要求列表
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
values #值
operator #关系符:支持In, NotIn, Exists, DoesNotExist, Gt, Lt
weight #倾向权重,在范围1-100。
#键值运算关系
●In:label 的值在某个列表中 pending
●NotIn:label 的值不在某个列表中
●Gt:label 的值大于某个值
●Lt:label 的值小于某个值
●Exists:某个 label 存在
●DoesNotExist:某个 label 不存在
关系符的使用说明:
- matchExpressions:
- key: nodeenv # 匹配存在标签的key为nodeenv的节点
operator: Exists
- key: nodeenv # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
operator: In
values: ["xxx","yyy"]
- key: nodeenv # 匹配标签的key为nodeenv,且value大于"xxx"的节点(大于小于的是资源的大小。)
operator: Gt
values: "xxx"
硬限制案例
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname #指定node的标签
operator: NotIn #设置Pod安装到kubernetes.io/hostname的标签值不在values列表中的node上
values::
- node02
软限制案例
apiVersion: v1
kind: Pod
metadata:
name: affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1 #如果有多个软策略选项的话,权重越大,优先级越高
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node03
硬策略和软策略一起使用
如果把硬策略和软策略合在一起使用,则要先满足硬策略之后才会满足软策略
nodeAffinity规则设置的注意事项
1、如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上。
2、如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
3、如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
4、如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化
---------------------------------------硬限制-----------------------------------------------
kubectl explain pod.spec.affinity.podAffinity #查看pod亲和度的解释
requiredDuringSchedulingIgnoredDuringExecution #硬限制
namespaces #指定参照pod的namespace
topologyKey #指定调度作用域
labelSelector #标签选择器
matchExpressions #按节点标签列出的节点选择器要求列表(推荐)
key #键
values #值
operator 关系符 #支持In, NotIn, Exists, DoesNotExist.
matchLabels #指多个matchExpressions映射的内容
---------------------------------------软限制-----------------------------------------------
preferredDuringSchedulingIgnoredDuringExecution #软限制
podAffinityTerm #选项
namespaces
topologyKey
labelSelector
matchExpressions
key 键
values 值
operator
matchLabels
weight 倾向权重,在范围1-100
#topologyKey 是节点标签的键
1、如果两个节点使用此键标记并且具有相同的标签值,则调度器会将这两个节点视为处于同一拓扑域中;
2、调度器试图在每个拓扑域中放置数量均衡的 Pod;
3、如果 kgc 对应的值不一样就是不同的拓扑域。
#例如:
比如 Pod1 在 kgc=a 的 Node 上,Pod2 在 kgc=b 的 Node 上,Pod3 在 kgc=a 的 Node 上,则 Pod2 和 Pod1、Pod3 不在同一个拓扑域,而Pod1 和 Pod3在同一个拓扑域。
硬限制案例
创建一个标签为 app=myapp01 的 Pod
apiVersion: v1
kind: Pod
metadata:
name: myapp01
labels:
app: myapp01
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
创建pod02.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod02
labels:
app: myapp02
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity: #亲和性设置
podAffinity: #设置pod亲和性
requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp001
topologyKey: kubernetes.io/hostname
----------------------------------------------------------------
# 上面配置表达的意思是: 新Pod必须要和拥有标签app: myapp001 的pod在同一node上,显然现在没有这样的pod。
可将亲和度的标签修改如下:
spec:
containers:
- name: nginx
image: nginx:1.17.1
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp01 ##更改为存在的node节点的标签
topologyKey: kubernetes.io/hostname
软限制案例
PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。
实例1:
创建pod3.yaml,
apiVersion: v1
kind: Pod
metadata:
name: myapp03
labels:
app: myapp03
spec:
containers:
- name: myapp03
image: soscscs/myapp:v1
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp01
topologyKey: kubernetes.io/hostname
实例2:
创建pod4.yaml,
apiVersion: v1
kind: Pod
metadata:
name: myapp04
labels:
app: myapp04
spec:
containers:
- name: myapp04
image: soscscs/myapp:v1
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp01
topologyKey: kfc
上述问题: 由于指定 Pod 所在的 node01 节点上具有带有键 kfc 和标签值 a 的标签,node02 也有这个kfc=a的标签,所以 node01 和 node02 是在一个拓扑域中,反亲和要求新 Pod 与指定 Pod 不在同一拓扑域,所以新 Pod 没有可用的 node 节点,即为 Pending 状态。
前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加 污点 属性,来决定是否允许Pod调度过来。
Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。
污点的格式为:key=value:effect ,key和 value是污点的标签,effect描述污点的作用,支持下面三个选项
#注意!!!
//在我们kubernetes集群中,目前有三个节点,两个node一个master,那为什么schedule不把pod调度到master节点上呢?
//因为master节点就设置了最高级的污点,也就是NoEcxecute。pod无法运行。
使用kubectl设置和去除污点的命令
kubectl taint nodes node01 key=value:NoExecute
#设置污点
kubectl taint nodes node01 key:NoExecute-
#去除污点
kubectl describe node node-name
#节点说明中,查找 Taints 字段
kubectl taint node node02 check=mycheck:NoExecute
#查看 Pod 状态,会发现 node02 上的 Pod 已经被全部驱逐(注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod)
kubectl get pods -o wide
kubectl taint node node02 check=mycheck:NoExecute
注:如果是 Deployment 或者 StatefulSet 资源类型,为了维持副本数量则会在别的 Node 上再创建新的 Pod
上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍。
#污点就是拒绝,容忍就是忽略,Node通过污点拒绝Pod调度上去,Pod通过容忍忽略拒绝
#查看容忍的详细配置
kubectl explain pod.spec.tolerations
....
FIELDS:
key //对应着要容忍的污点的键,空意味着匹配所有的键
value //对应着要容忍的污点的值
operator //key-value的运算符,支持Equal和Exists(默认)
effect //对应污点的effect,空意味着匹配所有影响
tolerationSeconds //容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
在2个node上都设置 NoSchedule 污点
创建Pod使用Pod容忍
apiVersion: v1
kind: Pod
metadata:
name: myapp01
labels:
app: myapp01
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
tolerations:
- key: "ket2"
operator: "Equal"
value: "check"
effect: "NoSchedule"
#其中的 key、vaule、effect 都要与 Node 上设置的 taint 保持一致
#operator 的值为 Exists 将会忽略 value 值,即存在即可
#tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Node 上继续保留运行的时间
tolerationSeconds: 3600
创建Pod使用Pod容忍
apiVersion: v1
kind: Pod
metadata:
name: myapp02
labels:
app: myapp02
spec:
containers:
- name: with-node-affinity
image: soscscs/myapp:v1
tolerations:
- key: "key1"
operator: "Equal"
value: "check"
effect: "NoExecute"
tolerationSeconds: 180
#其中的 key、vaule、effect 都要与 Node 上设置的 taint 保持一致
#operator 的值为 Exists 将会忽略 value 值,即存在即可
#tolerationSeconds 用于描述当 Pod 需要被驱逐时可以在 Node 上继续保留运行的时间
#其它注意事项
(1)当不指定 key 值时,表示容忍所有的污点 key
tolerations:
- operator: "Exists"
(2)当不指定 effect 值时,表示容忍所有的污点作用
tolerations:
- key: "key"
operator: "Exists"
(3)有多个 Master 存在时,防止资源浪费,可以如下设置
kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule
1、如果某个 Node 更新升级系统组件,为了防止业务长时间中断,可以先在该 Node 设置 NoExecute 污点,把该 Node 上的 Pod 都驱逐出去
kubectl taint node node01 check=mycheck:NoExecute
2、此时如果别的 Node 资源不够用,可临时给 Master 设置 PreferNoSchedule 污点,让 Pod 可在 Master 上临时创建
kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule
3、待所有 Node 的更新操作都完成后,再去除污点
kubectl taint node node01 check=mycheck:NoExecute-
维护操作
#cordon 和 drain
##对节点执行维护操作:
kubectl get nodes
#将 Node 标记为不可调度的状态,这样就不会让新创建的 Pod 在此 Node 上运行
kubectl cordon #该node将会变为SchedulingDisabled状态
#kubectl drain 可以让 Node 节点开始释放所有 pod,并且不接收新的 pod 进程。drain 本意排水,意思是将出问题的 Node 下的 Pod 转移到其它 Node 下运行
kubectl drain --ignore-daemonsets --delete-local-data --force
--ignore-daemonsets:无视 DaemonSet 管理下的 Pod。
--delete-local-data:如果有 mount local volume 的 pod,会强制杀掉该 pod。
--force:强制释放不是控制器管理的 Pod,例如 kube-proxy。
注:执行 drain 命令,会自动做了两件事情:
(1)设定此 node 为不可调度状态(cordon)
(2)evict(驱逐)了 Pod
#kubectl uncordon 将 Node 标记为可调度的状态
kubectl uncordon
#Pod启动阶段(相位 phase)
Pod 创建完之后,一直到持久运行起来,中间有很多步骤,也就有很多出错的可能,因此会有很多不同的状态。
一般来说,pod 这个过程包含以下几个步骤:
(1)调度到某台 node 上。kubernetes 根据一定的优先级算法选择一台 node 节点将其作为 Pod 运行的 node
(2)拉取镜像
(3)挂载存储配置等
(4)运行起来。如果有健康检查,会根据检查的结果来设置其状态。
phase 的可能状态有:
●Pending:表示APIServer创建了Pod资源对象并已经存入了etcd中,但是它并未被调度完成(比如还没有调度到某台node上),或者仍然处于从仓库下载镜像的过程中。
●Running:Pod已经被调度到某节点之上,并且Pod中所有容器都已经被kubelet创建。至少有一个容器正在运行,或者正处于启动或者重启状态(也就是说Running状态下的Pod不一定能被正常访问)。
●Succeeded:有些pod不是长久运行的,比如job、cronjob,一段时间后Pod中的所有容器都被成功终止,并且不会再重启。需要反馈任务执行的结果。
●Failed:Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止,比如 command 写的有问题。
●Unknown:表示无法读取 Pod 状态,通常是 kube-controller-manager 无法与 Pod 通信。
##故障排除步骤:
//查看Pod事件
kubectl describe TYPE NAME_PREFIX
//查看Pod日志(Failed状态下)
kubectl logs [-c Container_NAME]
//进入Pod(状态为running,但是服务没有提供)
kubectl exec –it bash
//查看集群信息
kubectl get nodes
//发现集群状态正常
kubectl cluster-info
//查看kubelet日志发现
journalctl -xefu kubelet