Kubernetes - 资源调度
# Label 和 Selector
# 标签(Label)
标签在各类资源的 metadata.labels 中进行配置。
metadata:
labels:
name: nginx
2
3
kubectl 可以手动给已经创建的 pod 加一个 label,但是这是暂时性的,pod 一旦删除则新添加 label 也被删除。
kubectl label po <资源名称> app=hello
# 给 nginx 加一个 label: app=hello
kubectl label po nginx app=hello
2
3
kubectl 修改已经存在的标签
kubectl label po <资源名称> app=hello2 --overwrite
# 修改 nginx 的 label: app=hello
kubectl label po nginx app=hello app=hello2 --overwrite
2
3
kubectl 根据 Label 查询 pod,-l
代表搜索 label
# selector 按照 label 单值查找节点
kubectl get po -A -l app=hello
# 查看所有节点的 labels
kubectl get po --show-labels
2
3
4
5
# 选择器(Selector)
在各对象的配置 spec.selector 或其他可以写 selector 的属性中编写。
kubectl 使用选择器查询
# 匹配单个值,查找 app=hello 的 pod
kubectl get po -A -l app=hello
# 匹配多个值
kubectl get po -A -l 'k8s-app in (metrics-server, kubernetes-dashboard)'
# 查找 version!=1 and app=nginx 的 pod 信息
kubectl get po -l version!=1,app=nginx
# 不等值 + 语句
kubectl get po -A -l version!=1,'app in (busybox, nginx)'
2
3
4
5
6
7
8
9
10
11
标签 Label 和 选择器 Selector 的使用看下面的 yaml 文件
# Deployment
配置文件 Demo
apiVersion: apps/v1 # deployment api 版本
kind: Deployment # 资源类型为 deployment
metadata: # 元信息
labels: # 标签
app: nginx-deploy # 具体的 key: value 配置形式
name: nginx-deploy # deployment 的名字
namespace: default # 所在的命名空间
spec:
replicas: 1 # 期望副本数
revisionHistoryLimit: 10 # 进行滚动更新后,保留的历史版本数
selector: # 选择器,用于找到匹配的 RS
matchLabels: # 按照标签匹配
app: nginx-deploy # 匹配的标签 key/value
strategy: # 更新策略
rollingUpdate: # 滚动更新配置
maxSurge: 25% # 进行滚动更新时,更新的个数最多可以超过期望副本数的个数/比例,如果副本数是 10,则最大可以生成 12.5 个副本
maxUnavailable: 25% # 进行滚动更新时,最大不可用比例更新比例,表示在所有副本数中,最多可以有多少个不更新成功
type: RollingUpdate # 更新类型,采用滚动更新
template: # pod 模板
metadata: # pod 的元信息
labels: # pod 的标签
app: nginx-deploy
spec: # pod 期望信息
containers: # pod 的容器
- image: nginx:1.7.9 # 镜像
imagePullPolicy: IfNotPresent # 拉取策略
name: nginx # 容器名称
restartPolicy: Always # 重启策略
terminationGracePeriodSeconds: 30 # 删除操作最多宽限多长时间
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建功能
创建一个 deployment
kubectl create deploy nginx-deploy --image=nginx:1.7.9
或执行
kubectl create -f xxx.yaml --record
--record
会在 annotation 中记录当前命令创建或升级了资源,后续可以查看做过哪些变动操作。
查看部署信息
kubectl get deployments
查看 rs
kubectl get rs
查看 pod 以及展示标签,可以看到是关联的那个 rs
kubectl get pods --show-labels
# 编辑功能
编辑一个 deployment
kubectl edit deploy nginx-deploy
# 滚动更新
只有修改了 deployment 配置文件中的 template 中的属性 后,才会触发更新操作,修改其他不会触发更新。
# 相比较 edit,该命令更快速修改容器 image
kubectl set image deployment/nginx-deploy nginx=nginx:1.9.1
2
此时就会进行滚动更新,即自动更新为新的 1.9.1 的 nginx 镜像。
多个滚动更新并行:假设当前有 5 个 nginx:1.7.9 版本,你想将版本更新为 1.9.1,当更新成功第三个以后,你马上又将期望更新的版本改为 1.9.2,那么此时会立马删除之前的三个,并且立马开启更新 1.9.2 的任务。
# 回滚
有时候你可能想回退一个 Deployment,例如,当 Deployment 不稳定时,比如一直 crash looping。
默认情况下,kubernetes 会在系统中保存前两次的 Deployment 的 rollout 历史记录,以便你可以随时会退(你可以修改 revision history limit
来更改保存的 revision 数)。
案例:更新 deployment 时参数不小心写错,如 nginx:1.9.1 写成了 nginx:1.91(1.91 版本不存在)
kubectl set image deployment/nginx-deploy nginx=nginx:1.91
监控滚动升级状态,由于镜像名称错误,下载镜像失败,因此更新过程会卡住
# 查看滚动状态
kubectl rollout status deployments nginx-deploy
2
结束监听后,获取 rs 信息,我们可以看到新增的 rs 副本数是 2 个
kubectl get rs
通过 kubectl get pods
获取 pods 信息,我们可以看到关联到新的 rs 的 pod,状态处于 ImagePullBackOff 状态。
为了修复这个问题,我们需要找到需要回退的 revision 进行回退,通过 kubectl rollout history deployment/nginx-deploy
可以获取 revison 的列表。
kubectl rollout history deployment/nginx-deploy
# 历史版本结果
REVISION CHANGE-CAUSE
2 <none>
3 <none>
4 <none>
2
3
4
5
6
7
通过 kubectl rollout history deployment/nginx-deploy --revision=2
可以查看版本 2 的详细信息。
kubectl rollout history deployment/nginx-deploy --revision=2
# 此时内容会告诉你 Image 的版本是 nginx:1.9.1 以及其他内容
2
3
确认要回退的版本后,可以通过 kubectl rollout undo deployment/nginx-deploy
可以回退到上一个版本。
# 回退到版本 3
kubectl rollout undo deployment/nginx-deploy
2
也可以回退到指定的 revision
kubectl rollout undo deployment/nginx-deploy --to-revision=2
再次通过 kubectl get deployment
和 kubectl describe deployment
可以看到,我们的版本已经回退到对应的 revison 上了。
可以通过在 yaml 文件设置 .spec.revisonHistoryLimit
来指定 deployment 保留多少 revison,如果设置为 0,则不保存版本,即不允许 deployment 回退了。
# 扩容缩容
通过 kubectl scale
命令可以进行自动扩容/缩容,以及通过 kube edit
编辑 replcas 也可以实现扩容/缩容。
扩容与缩容只是直接创建副本数,没有更新 pod template 因此不会创建新的 rs。
# 扩容为 3 个副本
kubectl scale --replicas=3 deploy nginx-deploy
2
# 暂停与恢复
由于每次对 pod template 中的信息发生修改后,都会触发更新 deployment 操作,那么此时如果频繁修改信息,就会产生多次更新,而实际上只需要执行最后一次更新即可,当出现此类情况时我们就可以暂停 deployment 的 rollout。
通过 kubectl rollout pause deployment <name>
就可以实现暂停,直到你下次恢复后才会继续进行滚动更新。
kubectl rollout pause deployment nginx-deploy
尝试对容器进行修改,然后查看是否发生更新操作了
kubectl set image deploy nginx-deploy nginx=nginx:1.17.9
kubectl get po
2
通过以上操作可以看到实际并没有发生修改,此时我们再次进行修改一些属性,如限制 nginx 容器的最大 cpu 为 0.2 核,最大内存为 128M,最小内存为 64M,最小 cpu 为 0.1 核。
# deploy_name 是 deploy 名,container_name 是 yaml 里的 container 下的 name
kubectl set resources deploy <deploy_name> -c <container_name> --limits=cpu=200m,memory=128Mi --requests=cpu100m,memory=64Mi
2
通过格式化输出 kubectl get deploy <name> -o yaml
,可以看到配置确实发生了修改,再通过 kubectl get po
可以看到 pod 没有被更新。
那么此时我们再恢复 rollout,通过命令 kubectl rollout resume deployment <name>
kubectl rollout resume deployment nginx-deploy
恢复后,我们再次查看 rs 和 po 信息,我们可以看到就开始进行滚动更新操作了。
kubectl get rs
kubectl get po
2
# StatefulSet
配置文件 Demo
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 服务 IP
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet # StatefulSet 类型的资源
metadata:
name: web # StatefulSet 对象的名字
spec:
serviceName: "nginx" # 使用哪个 service 来管理 dns
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports: # 容器内部要暴露的端口
- containerPort: 80 # 具体暴露的端口号
name: web # 该端口配置的名字
volumeMounts: # 加载数据卷
- name: www # 指定加载哪个数据卷
mountPath: /usr/share/nginx/html # 加载到容器中的哪个目录
volumeClaimTemplates: # 数据卷模板
- metadata: # 数据卷描述
name: www # 数据卷的名称
annotations: # 数据卷的注解
volume.alpha.kubernetes.io/storage-class: anything
spec: # 数据卷的规约
accessModes: [ "ReadWriteOnce" ] # 访问模式
resources:
requests:
storage: 1Gi # 需要 1G 的存储资源
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 创建功能
kubectl create -f web.yaml
# 查看 service 和 statefulset => sts
kubectl get service nginx
kubectl get statefulset web
# 查看 PVC 信息
kubectl get pvc
# 查看创建的 pod,这些 pod 是有序的
kubectl get pods -l app=nginx
# 查看这些 pod 的 dns
# 运行一个 pod,基础镜像为 busybox 工具包,利用里面的 nslookup 可以看到 dns 信息
kubectl run -i --tty --image busybox dns-tes t --restart=Never --rm /bin/sh
nslookup web-0.nginx
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 扩容缩容
# 扩容
kubectl scale statefulset web --replicas=5
# 缩容(基于 replicas=5 改成 3)
kubectl scale statefulset web --replicas=3
# 或
kubectl patch statefulset web -p '{"spec":{"replicas":3}}'
2
3
4
5
6
7
# 滚动更新
# 镜像更新(目前还不支持直接更新 image,需要 patch 来间接实现)
kubectl patch sts web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx:1.9.1"}]'
2
滚动更新有两种形式:
- 修改 partition 后更新 序号 >= partition 数的 Pod
- 删除 Pod 后更新其他 Pod
RollingUpdate
StatefulSet 也可以采用滚动更新策略,同样是修改 pod template 属性后会触发更新,但是由于 pod 是有序的,在 StatefulSet 中更新时是基于 pod 的顺序倒序更新的。
灰度发布 / 金丝雀发布
灰度发布目标:将项目上线后产生的问题影响,尽量降到最低。
和滚动更新的区别:
- 滚动更新是逐个把 Pod 进行更新,是 所有 Pod
- 灰度发布是把指定的 某个 / 某些 Pod 进行更新,如果这些 Pod 经过测试没有问题,则把其他的 Pod 全部更新,如果这些 Pod 有问题,则只回退这部分 Pod
利用滚动更新中的 partition 属性,可以实现简易的灰度发布的效果。
例如我们有 5 个 pod,如果当前 partition 设置为 3,那么此时滚动更新时,只会更新那些 序号 >= 3 的 2 个 pod(序号从 0 开始,所以是 0、1、2、3、4、5),即两个 Pod。
利用该机制,我们可以通过控制 partition 的值,来决定只更新其中一部分 pod,确认没有问题后再主键增大更新的 pod 数量,最终实现全部 pod 更新。
OnDelete
在 yaml 将 spec.updateStrategy.type
配置 OnDelete,它只有在 pod 被删除时会进行更新操作。
spec:
updateStrategy:
type: OnDelete
2
3
# 级联删除/非级联删除
# 删除 StatefulSet 和 Headless Service
# 级联删除:删除 statefulset 时会同时删除 pods
kubectl delete statefulset web
# 非级联删除:删除 statefulset 时不会删除 pods,删除 sts 后,pods 就没人管了,此时再删除 pod 不会重建的
kubectl deelte sts web --cascade=false
# 删除 service
kubectl delete service nginx
2
3
4
5
6
7
8
9
# 删除 pvc
# StatefulSet 删除后 PVC 还会保留着,数据不再使用的话也需要删除
kubectl delete pvc www-web-0 www-web-1
2
# DaemonSet
配置文件 Demo
apiVersion: apps/v1
kind: DaemonSet # 创建 DaemonSet 资源
metadata:
name: fluentd # 名字
spec:
selector:
matchLabels:
app: logging
template:
metadata:
labels:
app: logging
id: fluentd
name: fluentd
spec:
odeSelector: # 选择 Node 节点
type: log # 找到 Label 为 type=log 的节点
containers:
- name: fluentd-es
image: agilestacks/fluentd-elasticsearch:v1.3.0
env: # 环境变量配置
- name: FLUENTD_ARGS # 环境变量的 key
value: -qq # 环境变量的 value
volumeMounts: # 加载数据卷,避免数据丢失
- name: containers # 数据卷的名字
mountPath: /var/lib/docker/containers # 将数据卷挂载到容器内的哪个目录
- name: varlog
mountPath: /var/log
volumes: # 定义数据卷
- hostPath: # 数据卷类型,主机路径的模式,也就是与 node 共享目录
path: /var/lib/docker/containers # node 中的共享目录
name: containers # 定义的数据卷的名称
- hostPath:
path: /var/log
name: varlog
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 指定 Node 节点
DaemonSet 会忽略 Node 的 unschedulable 状态,有两种方式来指定 Pod 只运行在指定的 Node 节点上:
- nodeSelector:只调度到匹配指定 label 的 Node 上
- nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作
- podAffinity:调度到满足条件的 Pod 所在的 Node 上
nodeSelector
先为 Node 打上标签
kubectl label nodes k8s-node1 svc_type=microsvc
然后再 daemonset 配置中设置 nodeSelector
spec:
template:
spec:
nodeSelector:
svc_type: microsvc
2
3
4
5
# nodeAffinity
nodeAffinity 目前支持两种:
requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
,分别代表必须满足条件和优选条件。
比如下面的例子代表调度到包含标签 wolfcode.cn/framework-name
并且值为 spring 或 springboot 的 Node 上,并且优选还带有标签 another-node-label-key=another-node-label-value
的 Node。
# podAffinity
podAffinity 基于 Pod 的标签来选择 Node,仅调度到满足条件 Pod 所在的 Node 上,支持 podAffinity 和 podAntiAffinity。这个功能比较绕,以下面的例子为例:
- 如果一个「Node 所在空间中包含至少一个带有
auth=oauth2
标签且运行中的 Pod」,那么可以调度到该 Node - 不调度到「包含至少一个带有
auth=jwt
标签且运行中 Pod」的 Node 上
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: auth
operator: In
values:
- oauth2
topologyKey: failure-domain.beta.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: auth
operator: In
values:
- jwt
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: pauseyyf/pause
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 滚动更新
不建议使用 RollingUpdate,建议使用 OnDelete 模式,这样避免频繁更新 ds。
# HPA 自动扩/缩容
通过观察 pod 的 cpu、内存使用率或自定义 metrics 指标进行自动的扩容或缩容 pod 的数量。
通常用于 Deployment,不适用于无法扩/缩容的对象,如 DaemonSet。
控制管理器每隔 30s(可以通过 –horizontal-pod-autoscaler-sync-period
修改)查询 metrics 的资源使用情况。
# 开启指标服务
# 下载 metrics-server 组件配置文件
wget https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -O metrics-server-components.yaml
# 修改镜像地址为国内的地址
sed -i 's/k8s.gcr.io\/metrics-server/registry.cn-hangzhou.aliyuncs.com\/google_containers/g' metrics-server-components.yaml
# 修改容器的 tls 配置,不验证 tls,在 containers 的 args 参数中增加 --kubelet-insecure-tls 参数
# 安装组件
kubectl apply -f metrics-server-components.yaml
# 查看 pod 状态
kubectl get pods --all-namespaces | grep metrics
2
3
4
5
6
7
8
9
10
11
12
13
# cpu、内存指标监控
实现 cpu 或内存的监控,首先有个前提条件是该对象必须配置了 resources.requests.cpu
或 resources.requests.memory
才可以,可以配置当 cpu/memory 达到上述配置的百分比后进行扩容或缩容。
创建一个 HPA:
先准备一个好一个有做资源限制的 deployment
执行命令
kubectl autoscale deploy nginx-deploy --cpu-percent=20 --min=2 --max=5
kubectl autoscale deploy nginx-deploy --cpu-percent=20 --min=2 --max=5
1通过
kubectl get hpa
可以获取 HPA 信息
测试:找到对应服务的 service,编写循环测试脚本提升内存与 cpu 负载。
while true; do wget -q -O- http://<ip:port> > /dev/null ; done
可以通过多台机器执行上述命令,增加负载,当超过负载后可以查看 pods 的扩容情况 kubectl get pods
。
查看 pods 资源使用情况
kubectl top pods
扩容测试完成后,再关闭循环执行的指令,让 cpu 占用率降下来,然后过 5 分钟后查看自动缩容情况。
# 自定义 metrics
- 控制管理器开启
–horizontal-pod-autoscaler-use-rest-clients
- 控制管理器的
–apiserver
指向 API Server Aggregator (opens new window) - 在 API Server Aggregator 中注册自定义的 metrics API