)
深入掌握Pod9、玩转Pod调度9.1、Deployment或RC全自动调度9.2、NodeSelector定向调度9.3、NodeAffinityNode亲和性调度9.4、PodAffinityPod亲和与互斥调度策略9、玩转Pod调度在Kubernetes平台上我们很少会直接创建一个Pod在大多数情况下会通过RC、Deployment、DaemonSet、Job等控制器完成对一组Pod副本的创建、调度及全生命周期的自动控制任务。在最早的Kubernetes版本里是没有这么多Pod副本控制器的只有一个Pod副本控制器RCReplicationController这个控制器是这样设计实现的RC独立于所控制的Pod并通过Label标签这个松耦合关联关系控制目标Pod实例的创建和销毁随着Kubernetes的发展RC也出现了新的继任者—Deployment用于更加自动地完成Pod副本的部署、版本更新、回滚等功能。严谨地说RC的继任者其实并不是Deployment而是 ReplicaSet因为ReplicaSet进一步增强了RC标签选择器的灵活性。之前RC的标签选择器只能选择一个标签而ReplicaSet拥有集合式的标签选择器可以选择多个Pod标签如下所示selector:matchLabels:tier:frontendmatchExpressions:-{key:tier,operator:In,values:[frontend]}与RC不同ReplicaSet被设计成能控制多个不同标签的Pod副本。一种常见的应用场景是应用MyApp目前发布了v1与v2两个版本用户希望MyApp的Pod副本数保持为3个可以同时包含v1和v2版本的Pod就可以用ReplicaSet来实现这种控制写法如下selector:matchLabels:version:v2matchExpressions:-{key:version,operator:In,values:[v1,v2]}其实Kubernetes的滚动升级就是巧妙运用ReplicaSet的这个特性来实现的同时Deployment也是通过ReplicaSet来实现Pod副本自动控制功能的。我们不应该直接使用底层的ReplicaSet来控制Pod副本而应该使用管理ReplicaSet的Deployment对象来控制副本这是来自官方的建议。在大多数情况下我们希望Deployment创建的Pod副本被成功调度到集群中的任何一个可用节点而不关心具体会调度到哪个节点。但是在真实的生产环境中的确也存在一种需求希望某种Pod的副本全部在指定的一个或者一些节点上运行比如希望将MySQL数据库调度到一个具有SSD磁盘的目标节点上此时Pod模板中的NodeSelector属性就开始发挥作用了上述MySQL定向调度案例的实现方式可分为以下两步。1把具有SSD磁盘的Node都打上自定义标签“diskssd”。2在Pod模板中设定NodeSelector的值为“disk:ssd”。如此一来Kubernetes在调度Pod副本的时候就会先按照Node的标签过滤出合适的目标节点然后选择一个最佳节点进行调度。上述逻辑看起来既简单又完美但在真实的生产环境中可能面临以下令人尴尬的问题。1如果NodeSelector选择的Label不存在或者不符合条件比如这些目标节点此时宕机或者资源不足该怎么办2如果要选择多种合适的目标节点比如SSD磁盘的节点或者超高速硬盘的节点该怎么办Kubernates引入了NodeAffinity节点亲和性设置来解决该需求。在真实的生产环境中还存在如下所述的特殊需求。1不同Pod之间的亲和性Affinity。比如MySQL数据库与Redis中间件不能被调度到同一个目标节点上或者两种不同的Pod必须被调度到同一个Node上以实现本地文件共享或本地网络通信等特殊需求这就是PodAffinity要解决的问题。2有状态集群的调度。对于ZooKeeper、Elasticsearch、MongoDB、Kafka等有状态集群虽然集群中的每个Worker节点看起来都是相同的但每个Worker节点都必须有明确的、不变的唯一ID主机名或IP地址这些节点的启动和停止次序通常有严格的顺序。此外由于集群需要持久化保存状态数据所以集群中的Worker节点对应的Pod不管在哪个Node上恢复都需要挂载原来的Volume因此这些Pod还需要捆绑具体的PV。针对这种复杂的需求Kubernetes提供了StatefulSet这种特殊的副本控制器来解决问题在Kubernetes 1.9版本发布后StatefulSet才可用于正式生产环境中。3在每个Node上调度并且仅仅创建一个Pod副本。这种调度通常用于系统监控相关的Pod比如主机上的日志采集、主机性能采集等进程需要被部署到集群中的每个节点并且只能部署一个副本这就是DaemonSet这种特殊Pod副本控制器所解决的问题。4对于批处理作业需要创建多个Pod副本来协同工作当这些Pod副本都完成自己的任务时整个批处理作业就结束了。这种Pod运行且仅运行一次的特殊调度用常规的RC或者Deployment都无法解决所以Kubernates引入了新的Pod调度控制器Job来解决问题并继续延伸了定时作业的调度控制器CronJob。与单独的Pod实例不同由RC、ReplicaSet、Deployment、DaemonSet等控制器创建的Pod副本实例都是归属于这些控制器的这就产生了一个问题控制器被删除后归属于控制器的Pod副本该何去何从在Kubernates 1.9之前在RC等对象被删除后它们所创建的Pod副本都不会被删除在Kubernates 1.9以后这些Pod副本会被一并删除。如果不希望这样做则可以通过kubectl命令的--cascadefalse参数来取消这一默认特性kubectl delete replicaset my-repset--cascadefalse接下来深入理解和实践这些Pod调度控制器的各种功能和特性。9.1、Deployment或RC全自动调度Deployment或RC的主要功能之一就是自动部署一个容器应用的多份副本以及持续监控副本的数量在集群内始终维持用户指定的副本数量。下面是一个Deployment配置的例子使用这个配置文件可以创建一个ReplicaSet这个ReplicaSet会创建3个Nginx应用的Pod# nginx-deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deploymentspec:replicas:3selector:matchLabels:app:nginxtemplate:metadata:labels:app:nginxspec:containers:-name:nginximage:nginx:1.25ports:-containerPort:80运行kubectl create命令创建这个Deployment# kubectl create -f nginx-deployment.yamldeploymentnginx-deploymentcreated查看Deployment的状态# kubectl get deploymentsNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment333318s该状态说明Deployment已创建好所有3个副本并且所有副本都是最新的可用的。通过运行kubectl get rs和kubectl get pods可以查看已创建的ReplicaSetRS和Pod的信息。# kubectl get rsNAME DESIRED CURRENT READY AGE nginx-deployment-408700447333353s# kubectl get podsNAME READY STATUS RESTARTS AGE nginx-deployment-4087004473-9jqqs1/1 Running01m nginx-deployment-4087004473-cq0cf1/1 Running01m nginx-deployment-4087004473-vxn561/1 Running01m从调度策略上来说这3个Nginx Pod由系统全自动完成调度。它们各自最终运行在哪个节点上完全由Master的Scheduler经过一系列算法计算得出用户无法干预调度过程和结果。除了使用系统自动调度算法完成一组Pod的部署Kubernetes也提供了多种丰富的调度策略用户只需在Pod的定义中使用NodeSelector、NodeAffinity、PodAffinity、Pod驱逐等更加细粒度的调度策略设置就能完成对Pod的精准调度。下面对这些策略进行说明。9.2、NodeSelector定向调度Kubernetes Master上的Scheduler服务kube-scheduler进程负责实现Pod的调度整个调度过程通过执行一系列复杂的算法最终为每个Pod都计算出一个最佳的目标节点这一过程是自动完成的通常我们无法知道Pod最终会被调度到哪个节点上。在实际情况下也可能需要将Pod调度到指定的一些Node上可以通过Node的标签Label和Pod的nodeSelector属性相匹配来达到上述目的。1首先通过kubectl label命令给目标Node打上一些标签kubectl label nodesnode-namelabel-keylabel-value这里我们为k8s-node-1节点打上一个zonenorth标签表明它是“北方”的一个节点$ kubectl label nodes k8s-node-1zonenorth NAME LABELS STATUS k8s-node-1 kubernetes.io/hostnamek8s-node-1,zonenorth Ready上述命令行操作也可以通过修改资源定义文件的方式并执行kubectl replace -f xxx.yaml命令来完成。2然后在Pod的定义中加上nodeSelector的设置以redis-master-controller.yaml为例apiVersion:v1kind:ReplicationControllermetadata:name:redis-masterlabels:name:redis-masterspec:replicas:1selector:name:redis-mastertemplate:metadata:labels:name:redis-masterspec:containers:-name:masterimage:kubeguide/redis-masterports:-containerPort:6379nodeSelector:zone:north运行kubectl create -f命令创建Podscheduler就会将该Pod调度到拥有zonenorth标签的Node上。使用kubectl get pods -o wide命令可以验证Pod所在的Node# kubectl get pods -o wideNAME READY STATUS RESTARTS AGE NODE redis-master-f0rqj1/1 Running019s k8s-node-1如果我们给多个Node都定义了相同的标签例如zonenorth则scheduler会根据调度算法从这组Node中挑选一个可用的Node进行Pod调度。通过基于Node标签的调度方式我们可以把集群中具有不同特点的Node都贴上不同的标签例如“rolefrontend”“rolebackend”“roledatabase”等标签在部署应用时就可以根据应用的需求设置NodeSelector来进行指定Node范围的调度。需要注意的是如果我们指定了Pod的nodeSelector条件且在集群中不存在包含相应标签的Node则即使在集群中还有其他可供使用的Node这个Pod也无法被成功调度。除了用户可以自行给Node添加标签Kubernetes也会给Node预定义一些标签包括kubernetes.io/hostnamebeta.kubernetes.io/os从1.14版本开始更新为稳定版到1.18版本删除beta.kubernetes.io/arch从1.14版本开始更新为稳定版到1.18版本删除kubernetes.io/os从1.14版本开始启用kubernetes.io/arch从1.14版本开始启用用户也可以使用这些系统标签进行Pod的定向调度。NodeSelector通过标签的方式简单实现了限制Pod所在节点的方法。亲和性调度机制则极大扩展了Pod的调度能力主要的增强功能如下。更具表达力不仅仅是“符合全部”的简单情况。可以使用软限制、优先采用等限制方式代替之前的硬限制这样调度器在无法满足优先需求的情况下会退而求其次继续运行该Pod。可以依据节点上正在运行的其他Pod的标签来进行限制而非节点本身的标签。这样就可以定义一种规则来描述Pod之间的亲和或互斥关系。亲和性调度功能包括节点亲和性NodeAffinity和Pod亲和性PodAffinity两个维度的设置。节点亲和性与NodeSelector类似增强了上述前两点优势Pod的亲和与互斥限制则通过Pod标签而不是节点标签来实现也就是上面第4点内容所陈述的方式同时具有前两点提到的优点。NodeSelector将会继续使用随着节点亲和性越来越能够表达nodeSelector的功能最终NodeSelector会被废弃。9.3、NodeAffinityNode亲和性调度NodeAffinity意为Node亲和性的调度策略是用于替换NodeSelector的全新调度策略。目前有两种节点亲和性表达。RequiredDuringSchedulingIgnoredDuringExecution必须满足指定的规则才可以调度Pod到Node上功能与nodeSelector很像但是使用的是不同的语法相当于硬限制。PreferredDuringSchedulingIgnoredDuringExecution强调优先满足指定规则调度器会尝试调度Pod到Node上但并不强求相当于软限制。多个优先级规则还可以设置权重weight值以定义执行的先后顺序。IgnoredDuringExecution的意思是如果一个Pod所在的节点在Pod运行期间标签发生了变更不再符合该Pod的节点亲和性需求则系统将忽略Node上Label的变化该Pod能继续在该节点运行。下面的例子设置了NodeAffinity调度的如下规则。requiredDuringSchedulingIgnoredDuringExecution要求只运行在amd64的节点上beta.kubernetes.io/arch In amd64。preferredDuringSchedulingIgnoredDuringExecution的要求是尽量运行在磁盘类型为ssddisk-type Inssd的节点上。代码如下apiVersion:v1kind:Podmetadata:name:with-node-affinity# Pod 的名称spec:affinity:nodeAffinity:# 1. 硬亲和性Pod 调度时“必须”满足的条件这里限制节点架构必须是 amd64requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:beta.kubernetes.io/archoperator:Invalues:-amd64# 2. 软亲和性Pod 调度时“优先”满足的条件这里优先选择带有 ssd 磁盘类型的节点weight 表示权重优先级preferredDuringSchedulingIgnoredDuringExecution:-weight:1preference:matchExpressions:-key:disk-typeoperator:Invalues:-ssdcontainers:-name:with-node-affinity# 容器名称image:gcr.io/google_containers/pause:2.0# 容器镜像从上面的配置中可以看到In操作符NodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能但是用NotIn和DoesNotExist就可以实现排斥的功能了。NodeAffinity规则设置的注意事项如下。如果同时定义了nodeSelector和nodeAffinity那么必须两个条件都得到满足Pod才能最终运行在指定的Node上。如果nodeAffinity指定了多个nodeSelectorTerms那么其中一个能够匹配成功即可。如果在nodeSelectorTerms中有多个matchExpressions则一个节点必须满足所有matchExpressions才能运行该Pod。9.4、PodAffinityPod亲和与互斥调度策略Pod间的亲和与互斥从Kubernetes 1.4版本开始引入。这一功能让用户从另一个角度来限制Pod所能运行的节点根据在节点上正在运行的Pod的标签而不是节点的标签进行判断和调度要求对节点和Pod两个条件进行匹配。这种规则可以描述为如果在具有标签X的Node上运行了一个或者多个符合条件Y的Pod那么Pod应该如果是互斥的情况那么就变成拒绝运行在这个Node上。这里X指的是一个集群中的节点、机架、区域等概念通过Kubernetes内置节点标签中的key来进行声明。这个key的名字为topologyKey意为表达节点所属的topology范围。kubernetes.io/hostnamefailure-domain.beta.kubernetes.io/zonefailure-domain.beta.kubernetes.io/region与节点不同的是Pod是属于某个命名空间的所以条件Y表达的是一个或者全部命名空间中的一个LabelSelector。和节点亲和相同Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution和preferredDuringSchedulingIgnoredDuringExecution。Pod的亲和性被定义于PodSpec的affinity字段下的podAffinity子字段中。Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中。下面通过实例来说明Pod间的亲和性和互斥性策略设置。** 1参照目标Pod**首先创建一个名为pod-flag的Pod带有标签securityS1和appnginx后面的例子将使用pod-flag作为Pod亲和与互斥的目标PodapiVersion:v1kind:Podmetadata:name:pod-flag# Pod 的名称labels:security:S1# 安全级别标签app:nginx# 应用名称标签spec:containers:-name:nginx# 容器名称image:nginx# 容器镜像名称2Pod的亲和性调度下面创建第2个Pod来说明Pod的亲和性调度这里定义的亲和标签是securityS1对应上面的Pod“pod-flag”topologyKey的值被设置为“kubernetes.io/hostname”apiVersion:v1kind:Podmetadata:name:pod-affinity# Pod 的名称spec:affinity:podAffinity:# 硬亲和性要求 Pod 调度到满足以下条件的节点上requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:securityoperator:Invalues:-S1topologyKey:kubernetes.io/hostname# 拓扑域键表示必须在同一个主机上containers:-name:with-pod-affinity# 容器名称image:gcr.io/google_containers/pause:2.0# 容器镜像创建Pod之后使用kubectl get pods -o wide命令可以看到这两个Pod在同一个Node上运行。有兴趣的读者还可以测试一下在创建这个Pod之前删掉这个节点的kubernetes.io/hostname标签重复上面的创建步骤将会发现Pod一直处于Pending状态这是因为找不到满足条件的Node了。3Pod的互斥性调度创建第3个Pod我们希望它不与目标Pod运行在同一个Node上apiVersion:v1kind:Podmetadata:name:anti-affinity# Pod 的名称spec:affinity:podAffinity:# Pod 亲和性要求 Pod 调度到与具有特定标签的 Pod 处于同一可用区的节点上requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:securityoperator:Invalues:-S1topologyKey:failure-domain.beta.kubernetes.io/zonepodAntiAffinity:# Pod 反亲和性要求 Pod 不能与具有 appnginx 标签的 Pod 调度到同一个主机上requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-nginxtopologyKey:kubernetes.io/hostnamecontainers:-name:anti-affinity# 容器名称image:gcr.io/google_containers/pause:2.0# 容器镜像这里要求这个新Pod与securityS1的Pod为同一个zone但是不与appnginx的Pod为同一个Node。创建Pod之后同样用kubectl get pods -o wide来查看会看到新的Pod被调度到了同一Zone内的不同Node上。与节点亲和性类似Pod亲和性的操作符也包括In、NotIn、Exists、DoesNotExist、Gt、Lt。原则上topologyKey可以使用任何合法的标签Key赋值但是出于性能和安全方面的考虑对topologyKey有如下限制。在Pod亲和性和RequiredDuringScheduling的Pod互斥性的定义中不允许使用空的topologyKey。如果Admission controller包含了LimitPodHardAntiAffinityTopology那么针对Required DuringScheduling的Pod互斥性定义就被限制为kubernetes.io/hostname要使用自定义的topologyKey就要改写或禁用该控制器。在PreferredDuringScheduling类型的Pod互斥性定义中空的topologyKey会被解释为kubernetes.io/hostname 、 failure-domain.beta.kubernetes.io/zone及failure-domain.beta.kubernetes.io/region的组合。如果不是上述情况就可以采用任意合法的topologyKey了。PodAffinity规则设置的注意事项如下。除了设置Label Selector和topologyKey用户还可以指定Namespace列表来进行限制同样使用Label Selector对Namespace进行选择。Namespace的定义和Label Selector及topologyKey同级。省略Namespace的设置表示使用定义了affinity/anti-affinity的Pod所在的Namespace。如果Namespace被设置为空值“”则表示所有Namespace。在所有关联requiredDuringSchedulingIgnoredDuringExecution的matchExpressions全都满足之后系统才能将Pod调度到某个Node上。关于Pod亲和性和互斥性调度的更多信息可以参考其设计文档网址为https://github.com/kubernetes/kubernetes/blob/master/docs/design/podaffinity.md。