3.7 标签
在前面的学习中,我们学到了 Deployment 部署,以及副本数(ReplicaSet),但是 Pod 部署到哪个 Worker 节点是随机的,即使有 3个 Woker 和 3个 Pod 副本,不一定每个 Node 刚刚好运行一个 Pod,也可能其中一个 Node 运行着三个副本。当然使用 Daemont 可以保证每个 Node 平均运行这种一个 Pod 副本。但是有的机器内存大,我想尽量将 Redis Pod 调度到此节点;而有的服务器磁盘容量大,我想尽可能将日志服务等调度到这些节点,那么该如何处理呢?为什么 master 不会部署我们创建的 Pod 呢?
在本篇我们将探究 Kubernetes 中的 DaemonSet、容忍度、亲和性、Label、选择器等概念,以便控制 Pod 的调度。
标签和选择器
标签
标签主要是用于表示对用户有意义的对象的属性标识。
标签(Label)是附加到 Kubernetes 对象上的键值对,在对象描述信息中以 Labels
字段保存,例如 Pod 可通过 kubectl describe pods
查询,可以看到每个 Pod 都带有 Labels: app=...
。
... ...
Labels: app=nginx
pod-template-hash=85b45874d9
... ...
[Info] 提示
每个 Pod 都有 app 标签,表示 Pod 模板的名称,app 标签的值跟 Pod 模板名称一致。
当我们创建 Deployment 时,如果没有指定 Pod 的 app 标签值,那么一般跟 Deployment 名称一致。如
kubectl create deployment nginx --image=nginx:latest
创建的 Pod,其 app 标签值就是 nginx。Deployment 、ReplicaSet 等都是通过 Pod 的 app 标签识别 Pod 是不是属于1自己管控的。
在 kubectl get
后面加上 --show-labels
可以快速筛选获得对象的 Label :
kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-55495b586f-4hlzm 1/1 Running 0 4h16m app=nginx,pod-template-hash=55495b586f
nginx-55495b586f-5m985 1/1 Running 0 4h16m app=nginx,pod-template-hash=55495b586f
nginx-55495b586f-cs4zh 1/1 Running 0 4h16m app=nginx,pod-template-hash=55495b586f
... ...
在对象的 YAML 文件中,metadata.label
存储了这个对象的标签元数据。
如果用 JSON 表示附加到 metadata 的 label:
"metadata": {
"labels": {
"key1" : "value1",
"key2" : "value2"
}
}
如果用 YAML 表示附加到 metadata 的 label
metadata:
labels:
key1: "value1"
key2: "value2"
使用
kubectl get pod {名称} -o yaml
查询,或者使用kubectl edit pod {名称}
查看修改 Pod 的 标签。
在 Deployment 中,selector 字段设置了如何查找属于此对象的 Pod。
selector:
matchLabels:
app: nginx
Label 可以在多个地方使用,例如在 Node 上添加 Label,标识此 Node;而在 NodeSelector 里使用,可以选择合适的 Node 运行 Pod;在 metadata
中使用,可以对元数据加以描述。在 metadata 中添加的 Label,可以在命令查询时做筛选。
设置 Label
我们也可以手动给一个 Node 或者别的对象 添加标签。
kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label pod {pod名称} <node-name> <label-key>=<label-value>
例如我们给一个 Pod 设置一个 disksize
,表示 Pod 需要很大的硬盘。
kubectl label pod nginx-55495b586f-4hlzm disksize=big
如果要删除一个标签,只需要在标签名字后面加上 -
。
kubectl label pod nginx-55495b586f-4hlzm disksize-
如果标签已经存在,需要覆盖:
kubectl label pod nginx-55495b586f-4hlzm disksize=big --overwrite
在 kube-system 命名空间中,运行着 Kubernetes 的核心组件,我们可以查看此命名空间中所有组件的 Label。
kubectl get nodes --namespace=kube-system --show-labels
beta.kubernetes.io/arch=amd64,
beta.kubernetes.io/os=linux,
kubernetes.io/arch=amd64,
... ...
节点选择器
假如有个 Pod 需要很大的存储空间,而集群中的不同机器的硬盘容量都不同,我们希望 Pod 在硬盘很大的服务器上部署,我们可以通过给节点设置标签,然后在 Pod 的 YAML 文件中加上选择器,让 Pod 自动查找这类容量大的节点,并部署。
获取集群中的所有节点的 Label:
kubectl get nodes --show-labels
root@master:~# kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
master Ready control-plane,master 47h v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=,node.kubernetes.io/exclude-from-external-load-balancers=
slave1 Ready <none> 47h v1.22.2 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=slave1,kubernetes.io/os=linux
我们的节点名称不一样,读者按照实际名称修改后面的命令。
给节点加上标签:
kubectl label node instance-2 disksize=big
然后我们在编写 yaml 文件时,希望这个 pod 在容量大的 Node 上运行,可以这样写:
nodeSelector:
disksize=big
下面我们来练习一下,先删除以前的 Deployment:kubectl delete deployment nginx
。
然后我们使用 YAML 创建一个 Deployment,保存下面 YAML 到 nginx.yaml 文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:latest
name: nginx
nodeSelector:
disksize: "big"
执行 kubectl apply -f nginx.yaml
,部署 Pod。
上面的例子由于是 Deployment 的,selector 和别的字段是必填的,所以看起来内容比较多,但是这里核心是 nodeSelector
。
我们需要使用节点选择器时,在 spec 下面增加 nodeSelector
,其位置如下 YAML 所示:
spec:
containers:
- image: nginx:latest
name: nginx
nodeSelector:
disksize: "big"
顺便聊一下官方的一个例子,设置 Node 的 Label,表示硬盘是 ssd。
kubectl label nodes node-1 disktype=ssd
在 yaml 文件的节点选择器中,添加选择。
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
使用 Pod 选择节点:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
命令式标签选择
在前面,我们学习了 nodeSelector ,可以帮助我们选择合适的 Node 运行 Pod,实际上 Kubernets 的标签选择是丰富多样的,例如:
nodeSelector:
disktype: ssd
disksize: big
上面的例子表示节点选择器是等值选择,表达式是 disktype=ssd && disksize=big
,节点需要具备这两个 Label (key和value都要符合)。
我们也可以使用类似方式筛选符合条件的 Pod。
查询 Pod 所有的 Label:
kubectl get pods --show-labels
查找符合条件的 pod(参考 LABELS 字段,可以根据里面的标签选择):
kubectl get pods -l app=nginx
kubectl get pods -l app!=nginx
列出不包含某个标签的 Pod:
kubectl get pods -l '!env'
kubectl get pods -l '!app'
获取同时包含两个标签的 Pod:
kubectl get pods -l app,env
标签选择有等值和集合两种,其中等值选择有 =
、==
、!=
三种符号,=
和 ==
无任何区别,所以实际只有 等于
、不等于
两种选择情况。
在多个需求(多个label)的情况下,可以使用 &&
运算符,表示需要同时符合多个条件,但是选择器不存在 ||
这种逻辑或运算符。
查看符合两个条件的 节点:
kubectl get nodes -l disktype=ssd,disksize!=big
# 多个条件使用 逗号","" 隔开,而不是 "&&"。
注意,多条件使用
,
隔开,而不是&&
,不能写成disktype=ssd && disksize!=big
。
对于集合选择方式,支持三种操作符:in
、notin
和 exists
。不过别理解成是从集合中选择,下面举个例子。
假如有三个 Node,其 disksize 有 big、medium、small 三种类型。
NAME LABELS
node1 disksize=big
node2 disksize=medium
node3 disksize=small
我们要部署一个 pod,在 big、medium 中都可以运行,则可以写成
kubectl get nodes -l disksize in (big,medium)
# ... -l disksize in (big,medium)
kubectl get nodes -l disksize notin (small)
# ... -l disksize notin (small)
# 不在 small 中运行
而 exists 则跟 !=
类似,但是 exists 表示只要存在这个 label 即可,而不论其设置了是什么值。
kubectl get nodes -l exists disksize
# -l exists disksize
# 等同 -l disksize in (big,medium,small)
我们也可以使用 ''
把选择表达式包起来。
kubectl get pods -l 'app=nginx'
目前提到的标签选择,主要是命令式选择,比较容易使用,而且支持的选择方式比较多;但是写进 YAML 或 JSON 中的选择器,则编写比较复杂,而且不支持所有的运算符。
selector
前面已经提到了 YAML 的 nodeSelector
和 命令式的选择,这里我们介绍 YAML 的 selector
。
在 Deployment 的 YAML 中,有这样的内容:
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
由 Deployment 部署的每个 Pod 的名称都不同(随机生成),所以不可能以 Pod 名称作为表示,何况 Pod 只是临时性的,所以会为每个 Pod 加上一个相同的 label,表示它们都是同一个 Deployment 创建的。
YAML 文件配置示例:
template: metadata: labels: app: nginx
其中 spec.template
是定义所有 Pod 的模板,template.metadata
可以为所有 Pod 设置一些元数据,例如 labels
,对于 Deployment 等控制器,设置标签是必不可少的。
接下来是 spec.selector
,selector
字段定义 Deployment 如何查找要管理的 Pods。spec.selector
有个 matchLabels
字段,每个 Deployment 都要配置 matchLabels
。matchLabels
里面的标签是集合运算的 in
,Pod 只需要包含这些标签,就会被此 Deployment 管理。
在之前的创建的 Deployment(nginx) ,其 Pod 都会带上 app=nginx
的标签,我们可以在创建 Pod 之前,设置 Pod 的标签。 Deployment 的 YAML 文件如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx666
template:
metadata:
labels:
app: nginx666
type: test
spec:
containers:
- image: nginx:latest
name: nginx
查询创建的 Pod:
NAME READY STATUS RESTARTS AGE LABELS
nginx-64b465ddc9-8t9c8 1/1 Running 0 23s app=nginx666,pod-template-hash=64b465ddc9,type=test
同样,当我们创建 Service 或者使用 ReplicationController 时,也可以使用标签选择合适的 Pod。
回忆一下,我们之前已经学习过怎么创建 Service,示例命令:
kubectl expose deployment xxx ...
命令式的选择很简单,加上 deployment xxx
即可。对于 YAML ,我们可以使用 seletcor
选择器,表示该 Service 在什么对象上起效。
假如我们已经部署了 nginx,那么查询 kubectl get pods --show-labels
时,其 Pod 的 LABELS 会有 app=nginx
,那么我们可以这样选择:
selector:
app: nginx
使用 Service 时,要绑定具体的 Pod,完整的 YAML 示例如下:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 6666
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
选择器的复合运算
nodeSelector、selector 还支持以下选择方式 matchLabels
、matchExpressions
,在前面我们已经了解 Deployment 有 matchLabels
,接下来我们继续深入了解。
matchExpressions
是 Pod 选择算符需求的列表。 有效的运算符包括 In
、NotIn
、Exists
和 DoesNotExist
。 来自 matchLabels
和 matchExpressions
的所有要求都按逻辑与的关系组合到一起 -- 它们必须都满足才能匹配。
在
In
和NotIn
标签中,设置的值必须是非空的。Exists、DoesNotExist 表示某个标签是否存在;
In、NotIn 表示 A 集合中的标签是否都在 B 集合中。
matchExpressions
的表达比较复杂,下面是一个示例:
matchExpressions:
- {key: tier, operator: In, values: [cache1,cache2]}
- {key: environment, operator: NotIn, values: [dev]}
- {Key: disksize, operator: Exists}
Pod 必须有 tier 这个标签,并且值必须在 [cache1,cach2] 中找到。
Pod 必须有 environment 这个标签,并且值不能为
dev
,或者说不能在 [dev] 中出现。Pod 必须存在 disksize 这个标签。
还可以使用 YAML 格式表示:
- matchExpressions:
- key: kubernetes.io/e2e-az-name
operator: In
values:
- e2e-az1
- e2e-az2
读者可以根据自己喜好使用其中一种编写格式。
matchLabels
是由 {key,value}
对组成的映射。 matchLabels
映射中的单个 {key,value }
等同于 matchExpressions
的元素, 其 key
字段为 "key",operator
为 "In",而 values
数组仅包含 "value"。
举个例子,
matchLabels
示例为:matchLabels: component: redis
相对于
matchExpressions
:matchExpressions: - {key: component, operator: In, values: [redis]}
两种选择器可以结合使用,示例如下:
selector:
matchLabels:
component: redis
matchExpressions:
- {key: tier, operator: In, values: [cache]}
- {key: environment, operator: NotIn, values: [dev]}
[Info] 提示
复合选择比较复杂,只需了解即可。
注解
我们可以使用 Kubernetes 注解为对象附加任意的非标识的元数据,注解使用 annotations 标识。客户端程序(例如工具和库)能够获取这些元数据信息。
我们查看 dashboard 的相关 annotations :
kubectl describe services -n kubernetes-dashboard
... ...
Labels: k8s-app=kubernetes-dashboard
Annotations: <none>
... ...
annotations 由 key/value 组成,类似 label,但是 annotations 支持一些特殊字符,可以用作构建发布镜像时的信息、日志记录等。
kubectl annotate service kubernetes-dashboard -n kubernetes-dashboard description='my test'
key=description
value=my test
重新查看 describe,可以看到:
Annotations: description: my test
如果要覆盖 key 的值,需要加上 --overwrite
。
如果要删除一个 key:
kubectl annotate service kubernetes-dashboard description-