![65e2adafb792d04da043d02c4a6ba858.png](https://i-blog.csdnimg.cn/blog_migrate/466bff2d1a330f80fd538d15f2ffeb1c.jpeg)
Kubernetes是生产级容器编排、自动化容器部署、扩展和管理的系统;这里会告诉你如何对约束一个 Pod 只能在特定的 Node上运行,或者优先运行在特定的节点上。
1 - 实验的环境
集群环境:
![fdea142f61150357cfaa1fe853cf2b5b.png](https://i-blog.csdnimg.cn/blog_migrate/99510ff9dce4269e4863a2e12d5e9bc5.jpeg)
里面包含的IP地址均是内部网络使用的虚拟IP;
命名空间:
![a496f54b256220d4af9bd330fa8be145.png](https://i-blog.csdnimg.cn/blog_migrate/738d3998443e4f1ec10ef9a85ab79d30.jpeg)
默认命名空间此时只存在默认的service资源,无任何Pod在运行;
Pod部署配置:
apiVersion: v1kind: Podmetadata: name: counter namespace: default labels: app: counter release: 0.1.0spec: # 根据各实践案例,植入的调度策略配置 # ${scheduler_policy} containers: - name: count image: alpine:3.8 args: [/bin/sh, -c, 'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done'] resources: requests: cpu: 100m memory: 100Mi
如果不设置任何调度策略,直接使用以上配置,Pod将会在任意节点运行;以下所有测试均基于k8s集群1.10版本,涉及内容均为核心功能,即使后期新版也不会轻易被废弃,所有Pod调度由kube-scheduler进程管理控制。
2 - NodeName方式调度
2.1 - 调度策略
${scheduler_policy}
spec: nodeName: 10.5.119.105
2.2 - 数据结构
type PodSpec struct { ...... // Pod所在的Node节点 // 如果是运行时候,表示Pod所在的节点 // 如果是部署时候,设置为对应节点的名称,则默认调度器会忽略调度,直接将Pod部署至该节点 NodeName string ......}
2.3 - 运行示例
- 查看默认命名空间下的Pod
![fd01c09b283482f0fc85717a3de5f0ee.png](https://i-blog.csdnimg.cn/blog_migrate/5c4e5f91a3b6fda151d5e3ffbd93e600.jpeg)
- 运行部署配置与查询结果
![9f036902b665f41a5e8dcba5c051ce2c.png](https://i-blog.csdnimg.cn/blog_migrate/0fcecb3d1bae1abb555ae0f203b6932b.jpeg)
根据调度策略中的设置,Pod被调度至正确的主机上了
- 删除该Pod资源,以免影响后续实验
![b4bb7b580387c8ef83660c547fdfe311.png](https://i-blog.csdnimg.cn/blog_migrate/18c1ed617e2e7a6f990d4798a447cc3b.jpeg)
3 - NodeSelector方式调度
3.1 - 调度策略
${scheduler_policy}
spec: nodeSelector: a-demo-web.site/disk-type=ssd
3.2 - 数据结构
type PodSpec struct { ...... // 首先集群中各个Node均设置各自的标签 // 然后这里设置与Node对应的标签名称与值,则该Pod将会调度至该Node上 // 示例:a-demo-web.site/disk-type=ssd NodeSelector map[string]string ......}
3.3 - 运行示例
- 查看集群是否存在a-demo-web.site开头的标签,并添加调度策略部署Pod,观察状态
![1b807d258bd4557030201275560cb181.png](https://i-blog.csdnimg.cn/blog_migrate/19f725b7951b56cb9aa7bf7421b4eed4.jpeg)
持续处于Pending状态,通过观察事件:
0/12 nodes are available: 12 node(s) didn't match node selector.
发现集群中没有存在符合调度的Node资源,所以Pod会一直处于Pending状态
- 为主机10.5.119.137绑定标签:a-demo-web.site/disk-type=ssd
![b8cb01747a81bb40bbcf1224ce775bfa.png](https://i-blog.csdnimg.cn/blog_migrate/6c09667019d88cbef7f82538a12a2f66.jpeg)
- 通过添加标签后,另一个窗口实时监听Pod运行状态(Pod能否获得调度?)
![2435b9a58e32baff2fe26750e9879c61.png](https://i-blog.csdnimg.cn/blog_migrate/0075d60e315d58a74a226001d3ffb58e.jpeg)
根据调度策略中的设置,Pod被调度至正确的主机上了
- 已经运行的Pod,此时撤销对应节点标签(标签名末尾添加中划线:-)
观察Pod的变化,能否导致Pod被驱逐、下线?
![3375947ee0ae1dbc6e847c88b9b051bb.png](https://i-blog.csdnimg.cn/blog_migrate/bebf7c3a373b2b2db8f666febbfbeb23.jpeg)
已完成调度的Pod,尽管此时节点标签变化不满足运行需求,是不会导致Pod被驱逐的
- 删除该Pod资源,以免影响后续实验
3.4 - 内置标签
除了使用自定义的标签外,集群也内置了几个标签,可以设置它们进行相应调度
![c1623f36299910a1c1cf297cf7e9cf07.png](https://i-blog.csdnimg.cn/blog_migrate/2d0283ec9f234f0e994b51ea34001458.jpeg)
3.5 - 应用场景
如果我们指定了Pod的nodeSelector条件,而集群中不存在包含相关条件的节点,此时该Pod是无法被调度,即使集群其他节点是空闲状态。
所以实际上在生产环境中使用nodeSelector局限性还是比较大,可控性低,随着k8s集群版本的更新,在后续将会被废弃,由NodeAffinity代替。
4 - NodeAffinity:节点的亲和性
4.1 - 配置示例
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: a-demo-web.site/host-type operator: Exists preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: a-demo-web.site/host-type operator: In values: - "physical" - key: a-demo-web.site/disk-type operator: In values: - "local"
自定义标签说明:
![73970930290684308699de4ff240a3f5.png](https://i-blog.csdnimg.cn/blog_migrate/fe02e8c78702c50c80cba8681994f7f5.jpeg)
4.2 - 配置说明
存在两种表达式:
- requiredDuringSchedulingIgnoredDuringExecution
- preferredDuringSchedulingIgnoredDuringExecution
注意:
- 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都要得到满足,Pod才能运行到指定节点
- 如果nodeAffinity指定了多个nodeSelectorTerms,那么只要其中一个匹配即可
- 如果nodeSelectorTerms中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该Pod
对于每个符合所有调度要求的节点,调度器将遍历该字段的元素来计算总和,并且如果节点匹配对应的MatchExpressions,则添加权重到总和,然后对各个节点评分,总分最高的节点是最优选的。
4.3 - 数据结构
Affinity
// 一组亲和性的调度规则type Affinity struct { // 节点亲和性调度策略 // json:"nodeAffinity,omitempty" NodeAffinity *NodeAffinity // Pod的亲和性调度策略 // 用于控制相同的Pod出现在同一个主机、可用区等 // json:"podAffinity,omitempty" PodAffinity *PodAffinity // Pod的互斥性调度策略 // 用于控制相同的Pod避免在同一个主机、可用区等 // json:"podAntiAffinity,omitempty" PodAntiAffinity *PodAntiAffinity}
NodeAffinity
// 节点亲和性的数据结构type NodeAffinity struct { // 硬限制;必须满足指定的规则才可以调度Pod到节点上(与nodeSelector功能一样,但语法不同) // json:"requiredDuringSchedulingIgnoredDuringExecution,omitempty" RequiredDuringSchedulingIgnoredDuringExecution *NodeSelector // 软限制;根据节点权重总分最高为最优,调度器调度Pod到该节点上 // 同时必须满足nodeSelector、requiredDuringScheduling的规则(AND关系) // json:"preferredDuringSchedulingIgnoredDuringExecution,omitempty" PreferredDuringSchedulingIgnoredDuringExecution []PreferredSchedulingTerm}
XXXXXXXIgnoredDuringExecution的含义:如果一个Pod已经运行,且它所在Node标签发生了变更,不在符合调度策略,则系统忽略,该Pod可以继续运行,也就是亲和性实际只有在调度期间才会使用(类型nodeSelector方式调度,调度成功后移除Node标签,并不会导致Pod被驱逐)
NodeSelector
// 节点选择器的数据结构// 表示一组节点上多个标签查询结果的并集type NodeSelector struct { // 在RequiredDuringScheduling调度策略下如果设置多个,则它们之间是OR的关系 // 也就是只要满足其中之一即可 // json:"nodeSelectorTerms" NodeSelectorTerms []NodeSelectorTerm}
PreferredSchedulingTerm
// 首选调度项的数据结构type PreferredSchedulingTerm struct { // 节点选择项的权重,取值范围:1至100 // json:"weight" Weight int32 // 具体节点选择项设置,关联上面的权重 // json:"preference" Preference NodeSelectorTerm}
NodeSelectorTerm
// 节点选择项的数据结构// 初始化一个策略,但不设置任何规则,则不匹配任何对象// 如果设置了多个Match规则,它们之间是AND关系,也就是说必须全匹配type NodeSelectorTerm struct { // 根据自定义的标签名称、标签值,编写表达式去匹配 // json:"matchExpressions,omitempty" MatchExpressions []NodeSelectorRequirement // 早于kubernetes1.11版本不存在 // 根据内置固定的字段调度,如:metadata.name // TODO: 待后续的案例分析详解释 // json:"matchFields,omitempty" MatchFields []NodeSelectorRequirement}
NodeSelectorRequirement
// 节点选择项规则的数据结构// 它由key、operator、values组成,不同的operator会有不一样的values结构type NodeSelectorRequirement struct { // 这个是标签名称,对应CMDB中规定的值,非顺便乱写 // json:"key" Key string // 节点选择操作符,表示对Key对应Value的操作 // 可取六个值:In、NotIn、Exists、DoesNotExist、Gt、Lt // json:"operator" Operator NodeSelectorOperator // 这里编写value均是以字符串表示 Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"`}
这里Values是数组对应标签值,根据不同的Operator,可以设置0、1、多个标签值。
这里要区分注意每个主机节点设置的标签键值是1对1的,如:a-demo-web.site/disk-type=ssd,而不是可以设置数组形式,如:a-demo-web.site/disk-type=[ssd, local](没有这种写法)。
当Operator=In 或 NotIn 时,Values可以设置为数组(多个标签值),这些标签值是来自集群中各个节点的并集。
NodeSelectorOperator
// 节点选择操作符type NodeSelectorOperator stringconst ( NodeSelectorOpIn NodeSelectorOperator = "In" NodeSelectorOpNotIn NodeSelectorOperator = "NotIn" NodeSelectorOpExists NodeSelectorOperator = "Exists" NodeSelectorOpDoesNotExist NodeSelectorOperator = "DoesNotExist" NodeSelectorOpGt NodeSelectorOperator = "Gt" NodeSelectorOpLt NodeSelectorOperator = "Lt")
所以规则中只支持这六种操作符运算
4.4 - 节点选择操作符逻辑代码
switch op { // 如果 Operator=In 或 NotIn,则 Values 必须设置至少一个值 case selection.In, selection.NotIn: if len(vals) == 0 { return nil, fmt.Errorf("for 'in', 'notin' operators, values set can't be empty") } // NodeSelectorRequirement 不支持这三种操作符 case selection.Equals, selection.DoubleEquals, selection.NotEquals: if len(vals) != 1 { return nil, fmt.Errorf("exact-match compatibility requires one single value") } // 如果 Operator=Exists 或 DoesNotExist,则 Values 必须为空 case selection.Exists, selection.DoesNotExist: if len(vals) != 0 { return nil, fmt.Errorf("values set must be empty for exists and does not exist") } // 如果 Operator=Gt 或 Lt,则 Values 必须只能设置一个值,且必须为整数(不能包含小数点) case selection.GreaterThan, selection.LessThan: if len(vals) != 1 { return nil, fmt.Errorf("for 'Gt', 'Lt' operators, exactly one value is required") } for i := range vals { if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil { return nil, fmt.Errorf("for 'Gt', 'Lt' operators, the value must be an integer") } } default: return nil, fmt.Errorf("operator '%v' is not recognized", op)}
5 - NodeAffinity "硬限制" 调度
5.1 - 调度策略
${scheduler_policy}
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: a-demo-web.site/disk-type operator: In values: - local_ssd - key: a-demo-web.site/host-type operator: In values: - kvm - matchExpressions: - key: a-demo-web.site/host-type operator: In values: - vmware - physical
策略说明部署Pod至包含标签:a-demo-web.site/disk-type=local_ssd 且 a-demo-web.site/host-type=vmware 或 physciall 其中之一。
5.2 - 应用示例:多个 nodeSelectorTerms 的关系
- 确认当前集群不包含任何 a-demo-web.site 开头标签的节点
![5430200304c074f9ad8e1a45fd023606.png](https://i-blog.csdnimg.cn/blog_migrate/61c73f1c58b5aa62b06c7050223d7d4b.jpeg)
- 打开另外一个终端,开启实时监听默认命名空间下Pod的状态
![3480c0aebbf2ce269255eccafbf2370c.png](https://i-blog.csdnimg.cn/blog_migrate/4dc3acda8bc7a0bacf5ed90723570ac3.jpeg)
- 添加调度策略,部署应用,此时观察到Pod持续处于Pending状态
![d00c48ca4cc3276cf2c3f1196f81dc1e.png](https://i-blog.csdnimg.cn/blog_migrate/05902062bd23010d4a0224103badf1fe.jpeg)
![370b776212abc5b68556d84791fd05fc.png](https://i-blog.csdnimg.cn/blog_migrate/3f6a2004d40bf476dfc3be25e9b45f76.jpeg)
- 为节点225添加标签 a-demo-web.site/host-type=vmware
![6d232b1bd06dc924d0af4a904a91c34f.png](https://i-blog.csdnimg.cn/blog_migrate/60eacd8330b79547d4637b2e580c2cba.jpeg)
此时等待一会...
![41a69d3843d7d4809f3e24a0125d5c06.png](https://i-blog.csdnimg.cn/blog_migrate/760858d21cfbb4b03f0fabb4b78b50ff.jpeg)
可以看到Pod被调度至225节点上了,也验证了多个nodeSelectorTerms之间为OR的关系
- 移除225的标签 a-demo-web.site/host-type=vmware,已调度的Pod不会影响,仍然继续运行
![696e05dc330256c053b062a6d156b091.png](https://i-blog.csdnimg.cn/blog_migrate/e5783f8a359e536f862b38bdac9cfcf2.jpeg)
- 删除该Pod资源,以免影响后续实验
5.3 - 应用示例:多个 matchExpressions 的关系
- 为避免干扰同样验证集群中需不存在 a-demo-web.site 开头的标签,然后部署应用
![f1af17dc0981d72b873b8669b781cd32.png](https://i-blog.csdnimg.cn/blog_migrate/9e985bcb3ae0ce08f70479393e862150.jpeg)
- 为节点224添加标签:a-demo-web.site/disk-type=local_ssd
![440310c26345c06b0881ebc603a2503d.png](https://i-blog.csdnimg.cn/blog_migrate/b52eb46532f8d81bb1e34f52edc9398f.jpeg)
由于并不满足所有条件,此时Pod还是维持Pending状态
- 为节点224添加标签:a-demo-web.site/host-type=kvm
![688334bbc02d879f1660bed29fd4ee72.png](https://i-blog.csdnimg.cn/blog_migrate/97591538940c2d4faef82a96b7191bc3.jpeg)
- 观察原先开启的实时监听窗口
![c4ebdbb420d467afba9eab1d78d32668.png](https://i-blog.csdnimg.cn/blog_migrate/076e9e5da0bbc880b61a808de2fa581a.jpeg)
由于策略寻找到合适的节点,成功调度Pod,同时验证多个matchExpressions之间是AND的关系
- 同样,移除节点的标签,并不会导致原先已调度的Pod
![45b2068e68815f5e453d5f0e6af1dca9.png](https://i-blog.csdnimg.cn/blog_migrate/09f8f34da081e1874520fa03ff54cffc.jpeg)
- 删除该Pod资源,以免影响后续实验
6 - NodeAffinity "软限制" 调度
6.1 - 调度策略
${scheduler_policy}
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: a-demo-web.site/host-type operator: Exists preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: a-demo-web.site/host-type operator: In values: - "physical" - key: a-demo-web.site/disk-type operator: In values: - "local" - weight: 80 preference: matchExpressions: - key: a-demo-web.site/host-type operator: In values: - "kvm" - "vmware" - key: a-demo-web.site/disk-type operator: In values: - "local" - weight: 30 preference: matchExpressions: - key: a-demo-web.site/cloud-provider operator: In values: - "aws"
由于preferredDuringSchedulingIgnoredDuringExecution是"软限制",也就是说为匹配最优节点,也会被调度并部署至其他节点,所以这里添加了requiredDuringSchedulingIgnoredDuringExecution策略,以配合实验的成功演示。
匹配 a-demo-web.site/host-type=physical 且 a-demo-web.site/disk-type=local(物理服务器本地普通磁盘)则节点权重为100
匹配 a-demo-web.site/host-type=kvm 或 vmware 且 a-demo-web.site/disk-type=local(基于kvm或vmware的虚拟机)则节点权重为80
匹配 a-demo-web.site/cloud-provider=aws(主机云服务提供商为亚马逊)则节点权重为30
6.2 - 请求示例
- 检查当前集群不包含a-demo-web.site开头的标签,并创建Pod
![e5e73c9af13ba1fcc1e984d9f6f4b689.png](https://i-blog.csdnimg.cn/blog_migrate/1326dfbea5a944ed3e3b1c9d89eeffde.jpeg)
- 查看创建的Pod并实时监听状态的变化
![d9bb6f102cec31b3d68c20a3dd78e68b.png](https://i-blog.csdnimg.cn/blog_migrate/692067473241c6123a5a3fccf0474d28.jpeg)
不管等待多久,会持续处于Pending状态,查询事件,没有匹配的节点。
0/12 nodes are available: 12 node(s) didn't match node selector.
- 为节点135/136/137添加标签:a-demo-web.site/disk-type=local
![8b067e11ac32d4a9f599bc079790349f.png](https://i-blog.csdnimg.cn/blog_migrate/b37dedb034b16a225e449d606c3d45cf.jpeg)
此时只匹配到标签 a-demo-web.site/disk-type=local,对Pod并没有什么影响,还是处于Pending状态。
- 为节点135添加标签:a-demo-web.site/cloud-provider=aws
![cafea37a50f10e78f29cd5cca245b52d.png](https://i-blog.csdnimg.cn/blog_migrate/258af40e429fc2eb8f9f96274ccb9a2a.jpeg)
此时节点135相比其他,拥有30的权重,但还是不会吧Pod调度过来,因为前面设置了 requiredDuringSchedulingIgnoredDuringExecution 策略,如果没有责135就是最优的节点。
- 为节点136添加标签:a-demo-web.site/host-type=physical
![5991bcf2e57db47676df2f12bd9cd1e5.png](https://i-blog.csdnimg.cn/blog_migrate/426206a513a312412c4ab6f369b0592b.jpeg)
![2fbc481903af6e27bc9bbe7595dc889a.png](https://i-blog.csdnimg.cn/blog_migrate/b13cff898157ecb970a2deb5cded8a9f.jpeg)
此时节点136相比其他,拥有100的权重,同时又满足requiredDuringSchedulingIgnoredDuringExecution设置的策略,最符合调度,所以Pod被部署至该节点上。
- 删除该Pod,并为节点137添加标签:a-demo-web.site/host-type=kvm
![c552a1b253756f4e8048117a3b1efe30.png](https://i-blog.csdnimg.cn/blog_migrate/3fb636194d379ec7b4616bc7d419ff21.jpeg)
- 重新部署nodeaffinity-preferred.yaml,观察此时会被调度至哪个节点
![4547e56b46bf77997b95b73bcb761fd8.png](https://i-blog.csdnimg.cn/blog_migrate/7a0144b0613d69e237f2b893c542b58d.jpeg)
![8568785a858d8c0dd37144ad0eac6e18.png](https://i-blog.csdnimg.cn/blog_migrate/6bcf1be37b05bf44b8a11f9956a47069.jpeg)
此时节点137拥有权重80,而136拥有权重100,仍然是136是最佳被调度节点。
- 删除该Pod,为节点135添加标签:a-demo-web.site/host-type=kvm
![4d73c262085d5b9367bbd3189511833e.png](https://i-blog.csdnimg.cn/blog_migrate/16d367ea64533b3678f92a07bb46ef02.jpeg)
- 重新部署nodeaffinity-preferred.yaml,观察此时会被调度至哪个节点
![5f0180bc95f6dd9980128fc6cf2e5625.png](https://i-blog.csdnimg.cn/blog_migrate/32361fb7048e7bc05963f0300104c22c.jpeg)
![26434c5742392bc9b9ed75861788f34e.png](https://i-blog.csdnimg.cn/blog_migrate/97eafa3d9d0e9a8d7ec97eccbb8051da.jpeg)
此时135的权重为80+30=110,136节点的权重为100,137节点的权重为80,所以被调度部署至135节点。
7 - 最佳实践的总结
- 所有涉及的标签应该包含一个前缀,例如:a-demo-web.site/host-type;
- 所有涉及的标签应该来自一个权威数据中心,而非各集群管理员私下随意定义;
- 一般情况下不使用nodeName、nodeSelector进行Pod调度,一律通过nodeAffinity代替;
- 保障nodeAffinity中规则的简单,尽量使用“软限制"方式;