Pod是Kubernetes中可以创建、调度和部署的最小,也是最简单的单元。Pod是基于Kubernetes部署和运维应用的基础。本文重点介绍下Pod各字段的含义及Pod的使用,关于Pod更多的知识细节可以参考Kubernetes Pod详解一文。
本文参考的主要内容来源于Kubernetes 1.29版本,在阅读时要稍微注意下,避免出现因版本不兼容导致的功能差异。
API对象公共属性配置
Pod作为API对象,具有API对象需要包含的一些公共属性。其中:apiVersions属性用来标识API对象对应资源的版本,这里是Pod的API版本;kind用来标识API对象的资源类型,这里是"Pod"类型;metadata用来记录API对象的元数据信息,如name(名称)、uid(唯一id)、namespace(命名空间)、labels(标签)、annotations(注释)、finalizers(终结器)、ownerReferences(属主引用)等,更多API对象metadata的介绍可以参考Kubernetes官网ObjectMeta一文;spec用来描述对象的期望状态以及关于对象的一些基本信息。对于Pod来说,spec声明了需要使用的容器信息和多个容器共享的信息(如重启策略、挂载在Pod的存储卷、安全上下文等),是学习和使用Pod的重点;status用来描述系统当前达到的状态,由Kubernetes自动更新。不同类型的对象可以有不同的status信息,这里是PodStatus。不推荐用户去修改该属性。可以通过该属性查看Pod最新状态,辅助定位问题。API对象公共属性配置在Pod的声明文件的内容如下:
apiVersion: v1 # 对象基础属性,标识API对象对应资源的版本,这里是Pod版本
kind: Pod # 对象基础属性,标识API对象的资源类型,这里是Pod类型
metadata: # 对象基础属性,记录API对象的元数据信息name: string # 标识当前API对象在同类资源中的唯一性uid: string # 标识当前API对象在整个集群中的唯一性,由Kubernetes自动填充namespace: string # 用于将同一集群的资源进行逻辑分组labels: # 给资源打标签,用于指定对用户有意义且相关的对象的标识属性name1: stringname2: stringannotations: # 用来记录一些注释信息name1: stringname2: stringfinalizers: # 用于告诉 Kubernetes等到特定的条件被满足后,再完全删除被标记为删除的资源- string- stringownerReferences: # 标记其属主信息- apiVersion: string # 属主版本kind: string # 属主资源类型blockOwnerDeletion: boolean # 默认为 false,如果为 true,且属主具有 "foregroundDeletion“ 终结器,则在删除此引用之前,无法删除属主controller: boolean # 如果为 true,则此引用指向管理的控制器name: string # 属主名称uid: string # 属主uid
spec: # 对象基础属性,描述该对象的期望状态,不同类型的API对象其spec的格式都是不同的,在使用时要根据API对象的类型具体分析. containers: # Pod中声明的容器,容器相关的字段很多,后面再展开描述- name: stringimage: stringimagePullPolicy: [Always | Never | IfNotPresent]restartPolocy: [Always | Never | OnFailure ]volumes: # Pod专属属性,描述Pod上挂载的存储卷,可被Pod中的容器共享,支持挂载多种存储类型,如Secret、ConfigMap等- name: string # 定义创建volume的名称,容器中需要使用这个名字来引用存储卷
status: # 对象基本属性,描述系统当前达到的状态,由Kubernetes自动更新。不同类型的对象可以有不同的status信息。这里对应PodStatushostIP: string # Pod所在Node的IPphase: [Pending | Running | Succeeded | Failed] # Pod所处生命周期阶段qosClass: [BestEffort | Guaranteed | Burstable] # Pod的服务质量类(Quality of Service class,QoS class),在Node资源不足时使用QoS类来就驱逐Pod作出决定PodIP: string # Pod的IPreason: string # 如果运行失败,记录具体原因startTime: time # 记录状态的时间conditions: # Status的细分,描述造成当前Status的具体原因- lastProbeTime: timelastTransitionTime: timestatus: stringtype: stringreason: stringinitContainerStatuses: # Pod中容器初始状态的记录- containerID: stringimge: stringimageID: stringname: stringlastState: [running | terminated | waiting]state: [running | terminated | waiting]containerStatuses: # Pod中容器当前状态的记录- containerID: stringimge: stringimageID: stringname: stringlastState: [running | terminated | waiting]state: [running | terminated | waiting]
注意,上面只是列举了常用的属性,完整的属性信息可以参考官网PodSpec一文。更多API对象公共属性的知识细节可以参考Kubernetes API对象一文。接下来重点介绍Pod独有属性的使用。
在Pod中声明容器
一个Pod里可以封装一个容器或多个容器,可将Pod看成是对容器的编排。接下来介绍如何在Pod中声明容器。在Pod的声明文件的spec.containers属性声明容器,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers: # Pod中声明容器- name: string # 容器名称image: string # 镜像名称imagePullPolicy: [Always | Never | IfNotPresent] # 镜像拉取策略command: [string] # 容器启动时执行的命令,一般其启动容器镜像里的应用args: string # 命令参数workingDir: string # 容器的工作目录。如果未指定,将使用容器运行时的默认值,该默认值可能在容器镜像中配置stdin: boolean # 是否使用stdin作为容器的标准输出,默认是falsestdinOnce: boolean # 在第一个客户端附加到stdin时打开并接受数据,直到客户端断开连接,并关闭 stdintty: boolean # 当使用stdin作为标准输出时,使用tty接收用户的标准输输入,返回操作系统的标准输出terminationMessagePath: string # 指定容器中文件路径用于写入容器的终止消息terminationMessagePolicy: [File | FallbackToLogsOnError] # 指定写入策略,File类型指无论容器是否成功终止,都写入,FallbackToLogsOnError指在容器错误终止时,写入ports: # 指定容器公开的端口列表- name: string # 端口名称,Service会使用端口名来引用端口containerPort: string # 容器的公开端口protocol: string # 端口协议。必须是UDP、TCP 或 SCTP的一种。默认为TCPhostIP: string # Node的IPhostPort: string # Node的端口
上面仅介绍spec.containers的部分属性,更多属性的介绍可以参考Kubernetes官网Container v1 core一文。
资源申请及限制–CPU和内存的申请及限制
容器可以正常启动后,接下来要考虑的一个问题就是容器CPU和内存等资源的分配及限制。这里重点介绍下CPU和内存资源,其他资源的申请和限制有相似之处。可以在Pod声明文件的spec.containers.resources属性中指定需要申请的资源及对其使用的上限,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers: # Pod中声明容器- name: string # 容器名称image: string # 镜像名称command: [string] # 容器启动时执行的命令,一般其启动容器镜像里的应用args: string # 命令参数resources: # 容器使用资源requests: # 需要申请的资源memory: string # 指定内存资源大小cpu: string # 指定CPU资源大小limits: # 需要限制的资源memory: string # 指定内存资源大小cpu: string # 指定CPU资源大小
需要注意的是,不同资源使用的单位不同,如内存资源的基本单位是字节(byte),其他还有:E、P、T、G、M、K、Ei、Pi、Ti、Gi、Mi、Ki等;而CPU资源以CPU单位度量。Kubernetes中的一个 CPU 等同于:1个AWS vCPU或1个GCP核心或1个Azure vCore或裸机上具有超线程能力的英特尔处理器上的1个超线程。在CPU资源中,小数值是可以使用的。一个请求 0.5 CPU 的容器保证会获得请求 1 个 CPU 的容器的 CPU 的一半。 同时也可以使用后缀m表示毫。如 100m CPU、100 milliCPU 和 0.1 CPU 都是相同的。CPU资源的精度不能超过1m。需要注意的是,CPU资源只能使用绝对数量,而不是相对数量。0.1在单核、双核或48核上的CPU数量值是一样的。使用示例如下:
...
spec:containers:- name: demo-appimage: demo-image...resources: # 容器资源requests: # 当前容器申请的资源cpu: 1500m # 申请CPU资源1500m,也即1.5个CPUmemory: 1.5Gi # 申请内存资源1.5Gilimits: # 当前容器可使用资源上限cpu: 2 # CPU资源上限2000m,也即2个CPUmemory: 2Gi # 内存资源上限2Gi
上述示例表明名为"demo-app"的容器的内存请求为1.5GiB,内存限制为2GiB,CPU请求为1500milliCPU,CPU限制为2个CPU。
在分配资源时,可能存在以下情况:
(1) 容器申请的资源超过了Node能力。如果没有Node可以满足新创建Pod里容器请求的资源,那么这个Pod将无法正常运行,会一直处于Pending状态。可以通过kubectl describe pod查看详细信息。
(2) 指定了需要申请的资源,但未指定资源的限制。如果没有为容器指定资源限制,则会发生以下情况之一:容器在可以使用的资源上没有上限。因而可以使用所在节点上所有的可用资源;容器在具有默认CPU限制的名字空间中运行,系统会自动为容器设置默认限制。集群管理员可以使用LimitRange指定CPU限制的默认值。
(3) 指定了资源的限制,但未指定需要申请的资源。这时,Kubernetes会自动为其设置与资源的限制相同的资源请求值。
(4) 未指定需要申请的资源,也未指定资源的限制。这种情况统一归类为"未指定资源的限制"进行处理。
常用的资源主要指CPU和内存,其他还包括分页大小等,同时Kubernetes也支持对一些扩展资源进行申请和限制。更多资源申请及限制相关的属性介绍可以参考Kubernetes官网ResourceRequirements v1 core一文。
配置探针
Kubernetes对Pod的健康状态可以通过三类探针来检查:LivenessProbe(存活探针)、ReadinessProbe(就绪探针)及StartupProbe(启动探针)。其中,LivenessProbe指示容器是否正在运行。如果存活态探测失败,则kubelet会杀死容器,并且容器将根据其重启策略来进一步处理。ReadinessProbe指示容器是否准备好为请求提供服务(准备接收流量)。如果就绪态探测失败,Endpoint Controller将从与Pod匹配的所有服务的Endpoint列表中删除该Pod的IP地址,这样流量就不会到达该Pod。StartupProbe指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被禁用,直到此探针成功为止。如果启动探测失败,kubelet将杀死容器,而容器依其重启策略来进一步处理。在Pod声明文件定义探针的内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers: # Pod中声明容器- name: stringimage: stringargs: stringlivenessProbe: # 配置容器的liveness探针exec: # 通过在容器中执行命令来完成探测,注意需要执行shell命令,还需显式调用shellcommand: [string] # 需要执行的命令列表initialDelaySeconds: int # 表示在容器启动后,延时多少秒才开始探测,避免因容器启动但内部服务还未启动就启动探测并失败的情况periodSeconds: int # 探测的频率,默认10s,最小值是1timeoutSeconds: int # 探测超时限制,到了超时时间,如果探测还没返回结果,则说明此处探测失败。默认值是1,最小值是1failureThreshold: int # 最小连续失败次数,如果达到,则表示容器未就绪。默认值是3,最小值是1successThreshold: int # 最小连续成功次数,如果达到,则表示容器启动成功。默认值是1,最小值是1readinessProbe: # 配置容器的readiness探针httpGet: # 通过HTTP GET请求来完成探测host: string # 待访问的主机名称,默认是Pod IPpath: string # HTTP GET请求访问的资源路径port: int # 待访问的端口scheme: string # 连接host使用的协议,默认是HTTPhttpHeaders: # HTTP GET请求中需要填充的HEADER- name: stringvalue: stringinitialDelaySeconds: intperiodSeconds: inttimeoutSeconds: intfailureThreshold: intsuccessThreshold: intstartupProbe: # 配置容器的startup探针tcpSocket: # 通过tcp套接字请求来完成探测host: string # 待访问的主机名称,默认是Pod IPport: int # 待访问的端口initialDelaySeconds: intperiodSeconds: inttimeoutSeconds: intfailureThreshold: intsuccessThreshold: int
LivenessProbe、ReadinessProbe、StartupProbe均使用相同的数据结构Probe描述探针。更多探针属性介绍可以参考Kubernetes官网Probe v1 core一文。
在使用探针时,需要选择探测方式。目前,Kubernetes支持四种不同的探测方式:exec方式用来在容器内执行指定命令、httpGet方式用来对容器的IP地址上指定端口和路径执行HTTP GET请求、tcpSocket方式用来对容器的IP地址上的指定端口执行TCP请求、grpc方式(v1.27稳定支持)使用gRPC执行一个远程过程调用。
由于LivenessProbe、ReadinessProbe、StartupProbe三种探针使用相同的数据结构,所以仅以其中一种探针作为事例,这里选择LivenessProbe。示例代码如下:
apiVersion: v1
kind: Pod
metadata:name: probe-demo-http
spec:containers:- name: liveness-demoimage: registry.k8s.io/liveness-demolivenessProbe:httpGet:path: /healthzport: 8080initialDelaySeconds: 30 # 延迟探测时间periodSeconds: 3 # 重试时间间隔timeoutSeconds: 10 # 探测超时限制,到了超时时间,如果探测还没返回结果说明探测失败failureThreshold: 3 # 检测失败3次,就表示未就绪successThreshold: 2 # 检查成功2次,就表示就绪
在上述配置中,periodSeconds字段指定了kubelet每隔3秒执行一次存活探测。initialDelaySeconds字段告诉kubelet在执行第一次探测前应该等待30秒。kubelet会向容器内运行的服务(服务在监听8080端口)发送一个HTTP GET请求来执行探测。如果服务器上/healthz路径下的处理程序返回成功代码,则kubelet认为容器是健康存活的。如果处理程序返回失败代码,则kubelet会杀死这个容器并将其重启。对于HttpGet请求来说,返回大于或等于200并且小于400的任何代码都表示成功,其它返回代码都表示失败。
使用Hook
在容器的生命周期中,Kubernetes为其定义了postStart和preStop两个Hook,用来执行用户的一些扩展操作。
当一个容器启动后,Kubernetes将立即发送postStart事件;在容器被终结之前,Kubernetes将发送一个preStop事件。容器可以为每个事件指定一个处理程序。可以在Pod声明文件的spec.containers.lifecycle属性中对postStart事件或preStop事件指定的处理程序,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers:- name: stringimage: stringargs: stringlifecycle: # 在容器的生命周期配置HookpostStart: # 创建容器后立即调用postStartexec: # 在容器中执行命令command: [string] # 需要执行的命令列表preStop: # 容器终止前调用preStop,如果容器崩溃或退出,则不会调用该处理程序。sleep: # 配置休眠信息seconds: int # 休眠时间,单位是秒
postStart和preStop更多属性的介绍可以参考Kubernetes官网Lifecycle v1 core一文。
在Hook上指定操作时,Kubernetes支持三种四种的探测方式:exec方式用来在容器内执行指定命令、httpGet方式用来对容器的IP地址上指定端口和路径执行HTTP GET请求、tcpSocket方式用来对容器的IP地址上的指定端口执行TCP请求、sleep方式设置休眠时间。示例代码如下:
...
spec:containers:- name: demo-appimage: demo-image...lifecycle:postStart: # 创建容器后立即调用postStartexec: # 在容器中执行命令,这里是打印指定字符串command: ["/bin/sh", "-c", "echo Hello World > /usr/share/message"]preStop: # 容器终止前调用preStop,如果容器崩溃或退出,则不会调用该处理程序。sleep: # 配置休眠信息seconds: 10 # 休眠10秒
在上述示例中,postStart事件在容器的/usr/share目录下写入文件message。preStop事件则让容器休眠了10秒。
在Pod中配置Init Container
Kubernetes引入Init Container(初始化容器)执行应用容器(app Contianer)启动之前的一些初始化操作。Init Container与应用容器在本质上是一样的,只是它们仅运行一次就结束,并且必须在成功运行完成后,系统才能继续执行下一个容器。Init Container中定义的容器,都会比spec.containers定义的应用容器先启动。如果为一个Pod指定了多个 Init Container,那些这个容器会按顺序逐一启动,直至都启动并退出,应用容器才会启动。
如果Pod的Init Container失败,则认为该Pod失败,Kubernetes会不断地重启该Pod,直到Init Container成功为止。然而,如果Pod对应的restartPolicy为Never,它就不会重新启动。
可以在Pod的声明文件的spec.initContainers属性配置Init Container,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:initContainers: # Pod中声明Init Container- name: string # 容器名称image: string # 镜像名称command: [string] # 容器启动时执行的命令,一般其启动容器镜像里的应用args: string # 命令参数ports: # 指定容器公开的端口列表- name: string # 端口名称,Service会使用端口名来引用端口containerPort: string # 容器的公开端口protocol: string # 端口协议。必须是UDP、TCP 或 SCTP的一种。默认为TCPhostIP: string # Node的IPhostPort: string # Node的端口
Init Container配置可用属性与spec.containers一致,更多属性的介绍可以参考Kubernetes官网Container v1 core一文。
只是考虑到Init Container的使用场景,一般无需给Init Container配置生命周期操作(如postStart、preStop等)、探针(如Liveness、Readiness、Startup)等属性。
具体使用示例可以参考Kubernetes官网Init Containers一文。
在Pod中挂载存储卷
容器内部存储的生命周期是短暂的,会随着容器环境的销毁而销毁,具有不稳定性。如果多个容器希望共享同一份存储,则仅仅依赖容器是很难实现的。所以在Kubernetes中,把所有的存储资源抽象成存储卷(Volume)并将其设计在Pod层级来解决这个问题。存储卷是与Pod绑定的,且与Pod具有相同生命周期的资源对象。Pod的存储卷是被定义在Pod上,然后被各个容器挂载到自己的文件系统中的。同一个Pod中的多个容器能够共享Pod级别的存储卷。
可以在Pod的声明文件的spec.volumes属性,在Pod中挂载存储卷,然后在spec.containers.volumeMounts配置Init Container,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers:- name: stringimage: stringcommand: [string]args: stringvolumeMounts: # 在容器中挂载存储卷- name: string # 存储卷的名称,对应Pod中的存储卷mountPath: string # 存储卷的挂载路径,这里指容器中的路径volumes: # Pod专属属性,描述Pod上挂载的存储卷,可被Pod中的容器共享,支持挂载多种存储类型,如Secret、ConfigMap等- name: string # 定义创建volume的名称,容器中需要使用这个名字来引用存储卷emptyDir: object # 存储卷的类型,这里是emptyDir,表示临时目录- name: stringconfigMap: object # 存储卷的类型,这里是configMap,存储键值对等配置信息- name: stringpersistentVolumeClaim: object # 存储卷的类型,这里是persistentVolumeClaim,用来声明持久化存储- name: stringsecret: object # 存储卷的类型,这里是secret,存储敏感键值对等信息
Kubernetes支持多种不同类型的存储卷的挂载,如azureDisk、cephfs、configMap、emptyDir、persistentVolumeClaim、secret等,更多存储卷类型以及spec.volumes属性可以参考Kubernetes官网Volume v1 core一文。在Pod上定义存储卷后,就可以在各个容器中将其挂载到文件系统中。更多容器中挂载卷的属性参考Kubernetes官网VolumeMount v1 core一文。使用示例如下:
...
spec:containers:- name: demo-appimage: demo-image...volumeMounts: # 容器中使用存储卷- name: demo-volume-1 # 指定需要使用的存储卷,该存储卷已在sepc.volumes中声明mountPath: /data/app # 存储卷在容器中的挂载路径volumes:- name: demo-volume-1 # 定义创建volume的名称emptyDir: {} # 存储卷的类型,这里是emptyDir,表示临时目录
上述示例中将类型为emptyDir的存储卷demo-volume-1挂载在Pod上,并将其应用到"demo-app"容器中,并对应到容器的"/data/app"路径。可以使用emptyDir存储容器日志等易失性数据。
在Pod中使用亲和性和反亲和性
在生产环境中,出于健壮性、性能等方面的考虑,需要将Pod部署到特定的Node或者将存在某些相互依赖、频繁调用的Pod部署在同一个Node,亦或让某些Pod尽可能地远离某些特定的Pod等调度需求。如两个应用频繁交互,就有必要利用亲和性让两个应用尽可能靠近,这样可以减少因网络通信而带来的性能损耗;如某个应用采用多副本部署时,就有必要采用反亲和性让各个应用实例分布在各个Node节点上,这样可以提高服务的高可用性。以上诉求可以通过亲和性和反亲和性这一概念来实现。亲和性和反亲和性主要分为三类:
节点亲和性(Node Affinity):以Node为目标,解决Pod可以调度到哪些Node的问题
Pod亲和性(Pod Affinity):以Pod为目标,解决Pod可以和哪些已存在pod部署到同一个拓扑域中的问题
Pod反亲和性(Pod AntiAffinity):以Pod为目标,解决Pod不能和哪些已存在的pod部署在同一个拓扑域中的问题
这里的拓扑域由一些Node节点组成,这些Node节点通常有相同的地理空间坐标,如在同一个机架、机房或地区,一般用region表示机架、机房等拓扑域,用Zone表示地区跨度更大的拓扑域。在极端情况下,也可以认为一个Node就是一个拓扑域。
可以在Pod的声明文件的spec.affinity属性配置亲和性和反亲和性,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers:- name: stringimage: stringaffinity: # Pod的亲和性配置,支持三种配置:nodeAffinity、podAffinity、podAntiAffinitynodeAffinity: # Pod亲和Node配置preferredDuringSchedulingIgnoredDuringExecution: # 调度期间尽量满足- weight: int #筛选条件的权重,优先选择权重总和最大的Nodepreference: # 多个筛选条件,所有的条件都要满足,AND关系matchExpressions: # 基于Node的标签(label)匹配的规则- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchFields: # 基于Node的字段(field)匹配的规则- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]requiredDuringSchedulingIgnoredDuringExecution: # 调度期间必须满足nodeSelectorTerms: # 多个筛选条件,满足一个就行,OR关系- matchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchFields:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]podAffinity: # Pod间亲和配置preferredDuringSchedulingIgnoredDuringExecution:- weight: intpodAffinityTerm: # Pod亲和筛选项topologyKey: string # 指定拓扑域,Pod在亲和筛选时,仅能匹配拓扑域相同的NodenamespaceSelector: # 基于命名空间的筛选器matchExpressions: # 匹配的表达式,所有的条件都要满足,AND关系- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabels: object # matchLabels 是 {key,value} 对的映射。matchLabels映射中的单个{key,value}相当于matchExpressions的一个元素,其key字段是“key”,运算符是“In”,values数组只包含“value”。这些要求是“AND”。namespaces: [string] # 指定命名空间列表,限制namespaceSelector作用的命名空间labelSelector: # 基于标签的筛选器,匹配规则与基于命名空间的筛选器的匹配规则一致matchLabels: objectmatchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabelKeys: [string]mismatchLabelKeys: [string]requiredDuringSchedulingIgnoredDuringExecution:- topologyKey: stringnamespaceSelector:matchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabels: objectnamespaces: [string]labelSelector:matchLabels: objectmatchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabelKeys: [string]mismatchLabelKeys: [string]podAntiAffinity: # Pod反亲和配置,其配置项与podAffinity一致,只是作用效果相反preferredDuringSchedulingIgnoredDuringExecution:- weight: intpodAffinityTerm: # Pod亲和筛选项topologyKey: string # 指定拓扑域,Pod在亲和筛选时,仅能匹配拓扑域相同的NodenamespaceSelector: # 基于命名空间的筛选器matchExpressions: # 匹配的表达式,所有的条件都要满足,AND关系- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabels: object # matchLabels 是 {key,value} 对的映射。matchLabels映射中的单个{key,value}相当于matchExpressions的一个元素,其key字段是“key”,运算符是“In”,values数组只包含“value”。这些要求是“AND”。namespaces: [string] # 指定命名空间列表,限制namespaceSelector作用的命名空间labelSelector: # 基于标签的筛选器,匹配规则与基于命名空间的筛选器的匹配规则一致matchLabels: objectmatchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabelKeys: [string]mismatchLabelKeys: [string]requiredDuringSchedulingIgnoredDuringExecution:- topologyKey: stringnamespaceSelector:matchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabels: objectnamespaces: [string]labelSelector:matchLabels: objectmatchExpressions:- key: stringoperator: [In | NotIn | Exists | DoesNotExist | Gt | Lt]values: [string]matchLabelKeys: [string]mismatchLabelKeys: [string]
对亲和性来说,提供了两种亲和等级:软亲和(preferredDuringSchedulingIgnoredDuringExecution,也称软需求、软限制)和硬亲和(requiredDuringSchedulingIgnoredDuringExecution,也称硬需求、硬限制)。对硬亲和来说,必须满足指定的规则,如果不存在这样的Node,则无法调度Pod,Pod会一致处于pending状态。对软亲和来说,强调优先满足指定规则,如果不存在这样的Node,也不强求,仍能调度到无法满足条件的Node上。对于多个规则的场景,可以通过设置权重(weight)值,来定义执行的先后顺序。权重值越高,越先匹配。
更多Affinity属性的介绍可以参考Kubernetes官网Affinity v1 core一文。
NodeAffinity
NodeAffinity(节点亲和性)关注Pod与Node之间的亲和性关系。通过NodeAffinity,可以要求或禁止将某个Pod调度到具有特定亲和性关系的Node上,以满足应用程序的性能和资源需求。使用示例如下:
spec:...affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: topology.kubernetes.io/zoneoperator: Invalues:- antarctica-east1- antarctica-west1- matchFields:- key: topology.kubernetes.io/zoneoperator: Invalues: ["antarctica-south1","antarctica-north1"]preferredDuringSchedulingIgnoredDuringExecution:- weight: 1preference:matchExpressions:- key: another-node-label-keyoperator: Invalues:- another-node-label-value- weight: 2preference:matchFields:- key: another-node-field-keyoperator: Invalues:- another-node-field-value
在这一示例中,所应用的规则如下:
(1) Node必须包含一个键名为topology.kubernetes.io/zone的标签,并且该标签的取值必须为 antarctica-east1 或 antarctica-west1,并且必须保证字段的取值为antarctica-south1或antarctica-north1。
(2) Node最好具有一个键名为another-node-label-key 且取值为 another-node-label-value 的标签,
或具有一个键名为another-node-field-key且取值为another-node-field-value的字段,且优先匹配后面的规则。
PodAffinity和PodAntiAffinity
PodAffinity(Pod亲和性)关注Pod间的亲和性关系,而PodAntiAffinity关注Pod间的反亲和性关系。通过PodAffinity和PodAntiAffinity可以将不同的Pod调度到同一个拓扑域或者调度到不同的拓扑域,以满足应用程序的性能和资源需求。尽管PodAffinity和PodAntiAffinity使用不同的对象描述,但是从结构上来说,目前两者结构相同,具体可以参考Kubernetes官网PodAffinity v1 core和PodAntiAffinity v1 core。使用示例如下:
...
spec:...affinity:podAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: securityoperator: Invalues:- S1topologyKey: topology.kubernetes.io/zone- namespaceSelector:matchExpressions:- key: namespacke-keyoperator: Invalues:- namespacke-valuetopologyKey: topology.kubernetes.io/zonepodAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: securityoperator: Invalues:- S2topologyKey: topology.kubernetes.io/zone- weight: 10podAffinityTerm:namespaces:- namespace-1- namespace-2namespaceSelector:matchExpressions:- key: namespacke-keyoperator: Invalues:- namespace-valuetopologyKey: topology.kubernetes.io/zone
上述示例中,定义了一条Pod亲和性规则和一条Pod反亲和性规则。其中:
亲和性规则规定,只有Node属于特定的拓扑域(这里是topology.kubernetes.io/zone)且该区域中的其他Pod已打上security=S1标签且该区域中的其他Pod命名空间namespacke-key的键包含namespacke-value的值时,调度器才可以将示例Pod调度到此节点上。
反亲和性规则规定,如果Node属于特定的拓扑域(这里是topology.kubernetes.io/zone)且该区域中的其他Pod已打上security=S2标签或该区域中的其他Pod命名空间namespacke-key的键包含namespacke-value的值,则调度器应尝试避免将示例Pod调度到此节点上。但是对于这条规则来说,因为是软需求,所以如果没有符合条件的Node,仍有可能调度到这类Node。
需要注意的是,Pod间亲和性和反亲和性都需要不小的计算量,因此会在大规模集群中显著降低调度速度。不建议在包含数百个节点的集群中使用这类设置。
Taints(污点)和Tolerations(容忍度)
如果说亲和性使Pod被吸引到一类特定的节点,那么Taint(污点)则相反,它的作用是使节点能够排斥一类特定的Pod。Toleration(容忍度)则允许调度器调度带有对应Taint的Pod。注意,Toleration允许调度但并不保证调度:作为其功能的一部分,调度器也会评估其他参数。Taint和Toleration相互配合,可以用来避免Pod被分配到不合适的Node上。每个节点上都可以应用一个或多个Taint,这表示对于那些不能容忍Taint的Pod,是不会被该Node接受的。
可以使用命令kubectl taint给Node增加一个Taint,示例如下:
$kubectl taint nodes node1 key1=value1:NoSchedule
这样就给名为node1的Node增加了一个Taint,它的键名是key1,键值是value1,效果是NoSchedule。
而Pod为了能够将其部署到拥有Taint的Node上,需要在其声明文件中声明spec.Tolerations,内容如下:
apiVersion: v1
kind: Pod
metadata:name: stringnamespace: string
spec:containers:- name: stringimage: stringtolerations: # 声明可容忍Taint的Node- key: string # 待匹配的taint的keyoperator: [Exists | Equal] # 匹配操作类型:相等或模糊匹配effect: [NoSchedule | PreferNoSchedule | NoExecute] # 容忍Taint的效果,如PreferNoSchedule将尝试避免将不能容忍污点的Pod调度到的节点上,但不能保证一定避免tolerationSeconds: int # 容忍的时长,单位是秒,如一个Node之前没有Taint,再新添加Taint后,其上的Pod还可以继续执行tolerationSecondsvalue: string # taint的键对应的值,不适用operator为Exists的场景
更多Toleration属性的介绍可以参考Kubernetes官网Toleration v1 core一文。使用示例如下:
...
spec:...tolerations:- key: "example-key"operator: "Exists"effect: "NoSchedule"tolerationSeconds: 3600
在上述示例中,待创建的Pod可以被分配到taint的键中包含“example-key”的Node上或没有taint的Node上。如果该Pod运行在一个没有taint的Node上,如果手动给这个Node添加taint,那么这个Pod仍能运行3600秒。
在API对象中使用Pod
因为Pod生命周期是短暂的,一旦运行完成则立即回收,且涉及Pod的创建、自愈、删除等操作比较复杂,所以很少在Kubernetes中直接使用Pod。而是使用更高级的API对象来管理Pod。当前将应用的类型划分为以下几种:无状态应用(stateless application)、批处理型(batch)、节点后台支撑型(node-daemon)和有状态应用型(stateful application)。对应Kubernetes中API对象是Deployment、Job、DaemonSet和StatefulSet。
在Deployment、Job、DaemonSet和StatefulSet等资源对象中声明Pod的方式一致,都是在spec.template属性上声明PodTemplate,而PodTemplate遵循Pod的Schema规范。示例如下:
apiVersion: apps/v1
kind: Deployment
metadata:name: demo-deployment
spec:replicas: 3selector:matchLabels:app: demo-apptemplate: # template字段用来声明Pod,遵循Pod的Schema规范metadata:labels:app: demo-appspec:containers:- name: demo-appimage: demo-app-image
所以说,掌握了Pod的Schema后,就可轻松地在Deployment、Job、DaemonSet和StatefulSet等资源对象中声明Pod,然后将关注点转移到对各个应用类型资源对象的使用上。
Pod Cheat Sheet
Pod是学习和使用Kubernetes的基础,Pod中各个字段的含义及作用是掌握Pod的基础。针对Pod中的字段,可参考如下全景图:
需要说明的是,上述Pod字段全景图是基于Kubernetes 1.6版本,在使用的时候要稍微注意下。
参考
《Kubernetes权威指南 从Docker到Kubernetes实践全接触》 龚正 吴治辉 闫健勇 编著
《深入剖析Kubernetes》 张磊 著
https://lib.jimmysong.io/kubernetes-handbook/objects/Pod-overview Pod 概览
https://jimmysong.io/kubernetes-handbook/concepts/pod.html Pod 解析
https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/ Using Finalizers to Control Deletion
https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/common-definitions/object-meta/ 对象元数据
https://www.runoob.com/w3cnote/yaml-intro.html YAML 入门教程
https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/ Pod
https://www.cnblogs.com/liugp/p/16366688.html Kubernetes(k8s)pod详解
https://kubernetes.io/zh/docs/tasks/configure-pod-container/quality-service-pod/ 配置 Pod 的服务质量
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/ 配置 Pods 和容器
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#podstatus-v1-core PodStatus v1 core
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ Pod Lifecycle
https://cloud.tencent.com/developer/article/1683483 理解 Kubernetes 的亲和性调度
https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/ 为容器的生命周期事件设置处理函数
https://developer.aliyun.com/article/932905 Kubernetes–Pod亲和性调度
https://blog.csdn.net/qq_45631844/article/details/122080509 Kubernetes 亲和性与反亲和性
https://zhuanlan.zhihu.com/p/676245970 K8S学习指南(44)-k8s调度之NodeAffinity