抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Kubernetes 高级资源控制器

image-20220525094307190

概述

Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类:

  • 自主式pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不会重建
  • 控制器创建的pod:kubernetes通过控制器创建的pod,这种pod删除了之后还会自动重建

Pod控制器是什么

Pod控制器就是帮助我们自动的调度管理Pod,并满足期望的Pod数量。

​ Pod控制器是用于实现管理pod的中间层,确保pod资源符合预期的状态,pod的资源出现故障时,会尝试 进行重启,当根据重启策略无效,则会重新新建pod的资源。

​ 创建为具体的控制器对象之后,每个控制器均通过API Server提供的接口持续监控相关资源对象的当前状态,并在因故障、更新或其他原因导致系统状态发生变化时,尝试让资源的当前状态想期望状态迁移和逼近。

Pod和Pod控制器

Pod控制器资源通过持续性地监控集群中运行着的Pod资源对象来确保受其管控的资源严格符合用户期望的状态,例如资源副本的数量要精确符合期望等。

通常,一个Pod控制器资源至少应该包含三个基本的组成部分:

  • 标签选择器:匹配并关联Pod资源对象,并据此完成受其管控的Pod资源计数。
  • 期望的副本数:期望在集群中精确运行着的Pod资源的对象数量。
  • Pod模板:用于新建Pod资源对象的Pod模板资源。

控制器的必要性

​ 自主式Pod对象由调度器调度到目标工作节点后即由相应节点上的kubelet负责监控其容器的存活状态,容器主进程崩溃后,kubelet能够自动重启相应的容器。

​ 但对出现非主进程崩溃类的容器错误却无从感知,这便依赖于pod资源对象定义的存活探测,以便kubelet能够探知到此类故障。

​ 但若pod被删除或者工作节点自身发生故障(工作节点上都有kubeletkubelet不可用,因此其健康状态便无法保证),则便需要控制器来处理相应的容器重启和配置。

常见的控制器

Pod控制器由masterkube-controller-manager组件提供,常见的此类控制器有

ReplicationController

比较原始的pod控制器,已经被废弃,由ReplicaSet替代

ReplicaSet

代用户创建指定数量的pod副本数量,确保pod副本数量符合预期状态,并且支持滚动式自动扩容和缩容功能

Deployment

工作在ReplicaSet之上,用于管理无状态应用,目前来说最好的控制器。支持滚动更新和回滚功能,还提供声明式配置。

Horizontal Pod Autoscaler

可以根据集群负载自动水平调整Pod的数量,实现削峰填谷

DaemonSet

用于确保集群中的每一个节点只运行特定的pod副本,常用于实现系统级后台任务,比如ELK服务

Job

它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务

Cronjob

它创建的Pod负责周期性任务控制,不需要持续后台运行,用于执行周期性任务

StatefulSet

管理有状态应用

DaemonSet(DS)控制器

img

DaemonSet概述

​ DaemonSet类型的控制器可以保证在集群中的每一台(或指定)节点上都运行一个副本。一般适用于日志收集、节点监控等场景。也就是说,如果一个Pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类Pod就适合使用DaemonSet类型的控制器创建。

特点
  • 每当向集群中添加一个节点时,指定的 Pod 副本也将添加到该节点上
  • 当节点从集群中移除时,Pod 也就被垃圾回收了
应用场景
  • 集群存储守护程序,如 glusterd、ceph 要部署在每个节点上以提供持久性存储;
  • 节点监控守护进程,如 Prometheus 监控集群,可以在每个节点上运行一个 node-exporter 进程来收集监控节点的信息;
  • 日志收集守护程序,如 fluentd 或 logstash,在每个节点上运行以收集容器的日志
  • 节点网络插件,比如 flannel、calico,在每个节点上运行为 Pod 提供网络服务
调度问题

这里需要特别说明的一个就是关于 DaemonSet 运行的 Pod 的调度问题

​ 正常情况下,Pod 运行在哪个节点上是由 Kubernetes 的调度器策略来决定的,然而,由 DaemonSet 控制器创建的 Pod 实际上提前已经确定了在哪个节点上了(Pod创建时指定了.spec.nodeName

  • DaemonSet 并不关心一个节点的 unshedulable 字段
  • DaemonSet 可以创建 Pod,即使调度器还没有启动。
如何管理调度

DaemonSet 控制器是如何保证每个 Node 上有且只有一个被管理的 Pod 呢?

  • 首先控制器从 Etcd 获取到所有的 Node 列表,然后遍历所有的 Node。
  • 根据资源对象定义是否有调度相关的配置,然后分别检查 Node 是否符合要求。
  • 在可运行 Pod 的节点上检查是否已有对应的 Pod,如果没有,则在这个 Node 上创建该 Pod;如果有,并且数量大于 1,那就把多余的 Pod 从这个节点上删除;如果有且只有一个 Pod,那就说明是正常情况。

使用案例

DaemonSet控制器的spec字段中嵌套使用的相同字段selectortemplateminReadySeconds,并且功能和用法基本相同,但它不支持replicas,因为毕竟不能通过期望值来确定Pod资源的数量,而是基于节点数量

基本使用
编辑资源清单
1
vi nginx-daemonset.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1    #api版本定义
kind: DaemonSet #定义资源类型为DaemonSet
metadata: #元数据定义
name: nginx-daemon-set #daemonset控制器名称
namespace: default #名称空间
labels: #设置daemonset的标签
app: daem-nginx
spec: #DaemonSet控制器的规格定义
selector: #指定匹配pod的标签
matchLabels: #指定匹配pod的标签
app: daem-nginx #注意:这里需要和template中定义的标签一样
template: #Pod的模板定义
metadata: #Pod的元数据定义
name: nginx
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
app: daem-nginx
spec: #Pod的规格定义
containers: #容器定义
- name: nginx-pod #容器名字
image: nginx:1.12 #容器镜像
ports: #暴露端口
- name: http #端口名称
containerPort: 80 #暴露的端口

注意:我们可以看到,DaemonSet的yaml写法和Deployment非常类似,只是改变了下kind名称,注意下DaemonSet是没有副本数这一参数选项的。

应用配置
1
kubectl apply -f nginx-daemonset.yml

image-20220531090853014

查看POD情况
1
2
kubectl get node
kubectl get pod -o wide

我们观察可以发现除了 master 节点之外的2个节点上都有一个相应的 Pod 运行,因为 master 节点上默认被打上了污点(taints),所以默认情况下不能调度普通的 Pod 上去

image-20220531090947461

查看节点污点

首先来看看这3个节点上是否打了污点呢

1
2
3
kubectl describe node master |grep Taints
kubectl describe node node01 |grep Taints
kubectl describe node node02 |grep Taints

我们可以看到只有master节点被打上了污点:NoSchedule

image-20220531091218416

破坏性测试

我们再来把其中一个pod删除下,观察下这个pod是否会被立马重建

1
2
kubectl delete pod -l app=daem-nginx
kubectl get pod -o wide

我们可以发现,一旦某个节点的pod被删除后,它会立马被重建的

image-20220531091535478

这个就是daemonset控制器的作用,它是一个loop,会一直监听节点上的pod是否满足副本数为1这个条件,如果不满足,会立即执行相应操作,使其符合期望的状态。

更新DaemonSe

DaemonSet自Kubernetes1.6版本起也开始支持更新机制,相关配置嵌套在kubectl explain daemonset.spec.updateStrategy字段中

其支持RollingUpdate(滚动更新)和OnDelete(删除时更新)两种策略,滚动更新为默认的更新策略。

命令更新

命令更新适用于临时进行更新

查看当前版本
1
2
kubectl get ds -o wide
kubectl get pods -l app=daem-nginx -o custom-columns=NAME:metadata.name,NODE:spec.nodeName,Image:spec.containers[0].image

我们看到当前的DS和pod都是1.12版本

image-20220531093507406

执行更新

通过以下命令可以进行执行更新

1
kubectl set image ds nginx-daemon-set nginx-pod=nginx:1.15

这样就完成了更新

image-20220531092949481

查看更新后版本
1
kubectl get pods -l app=daem-nginx -o custom-columns=NAME:metadata.name,NODE:spec.nodeName,Image:spec.containers[0].image

我们看到POD已经被更新了

image-20220531093204055

配置文件更新

配置文件更新适合于永久更新

编辑配置文件

我们上面将配置文件更新为了1.15,下面我们将配置文件版本改为1.20

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1    #api版本定义
kind: DaemonSet #定义资源类型为DaemonSet
metadata: #元数据定义
name: nginx-daemon-set #daemonset控制器名称
namespace: default #名称空间
labels: #设置daemonset的标签
app: daem-nginx
spec: #DaemonSet控制器的规格定义
selector: #指定匹配pod的标签
matchLabels: #指定匹配pod的标签
app: daem-nginx #注意:这里需要和template中定义的标签一样
template: #Pod的模板定义
metadata: #Pod的元数据定义
name: nginx
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
app: daem-nginx
spec: #Pod的规格定义
containers: #容器定义
- name: nginx-pod #容器名字
image: nginx:1.20 #容器镜像
ports: #暴露端口
- name: http #端口名称
containerPort: 80 #暴露的端口
应用更新
1
kubectl apply -f nginx-daemonset.yml

image-20220531093612081

查看更新后版本
1
2
kubectl get ds -o wide
kubectl get pods -l app=daem-nginx -o custom-columns=NAME:metadata.name,NODE:spec.nodeName,Image:spec.containers[0].image

我们发现已经更新到了1.20

image-20220531093653403

筛选节点运行

对于特殊的硬件的节点来说,可能有的运行程序只需要在某一些节点上运行,那么通过Pod模板的spec字段中嵌套使用nodeSelector字段,并确保其值定义的标签选择器与部分特定工作节点的标签匹配即可。

创建标签

给node2添加上disk=ssd的标签

1
2
kubectl label nodes node02 disk=ssd
kubectl get nodes -L disk

这样我们就在node02加上了一个disk=ssd的标签

image-20220531093922054

编辑资源清单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: apps/v1    #api版本定义
kind: DaemonSet #定义资源类型为DaemonSet
metadata: #元数据定义
name: nginx-daemon-set #daemonset控制器名称
namespace: default #名称空间
labels: #设置daemonset的标签
app: daem-nginx
spec: #DaemonSet控制器的规格定义
selector: #指定匹配pod的标签
matchLabels: #指定匹配pod的标签
app: daem-nginx #注意:这里需要和template中定义的标签一样
template: #Pod的模板定义
metadata: #Pod的元数据定义
name: nginx
labels: #定义Pod的标签,需要和上面的标签选择器内匹配规则中定义的标签一致,可以多出其他标签
app: daem-nginx
spec: #Pod的规格定义
nodeSelector:
disk: ssd # pod部署在节点是disk:ssd的标签
containers: #容器定义
- name: nginx-pod #容器名字
image: nginx:1.12 #容器镜像
ports: #暴露端口
- name: http #端口名称
containerPort: 80 #暴露的端口

该配置中我们增加了节点选择,我们只需要DaemonSet在有disk=ssd的标签的节点上运行

应用配置
1
2
kubectl apply -f nginx-daemonset.yml
kubectl get pod -o wide

我们发现我们的POD只能运行在node01节点上了,有时候我们可以通过这种策略来控制daemonset的运行范围

image-20220531092313222

Job控制器

Job,主要用于负责**批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)**任务

img

Job概述

Job 负责处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束

Job特点

Job控制器有以下特点

  • 当Job创建的pod执行成功结束时,Job将记录成功结束的pod数量
  • 当成功结束的pod达到指定的数量时,Job将完成执行
配置描述
1
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
apiVersion: batch/v1 # 版本号
kind: Job # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: job
spec: # 详情描述
completions: 1 # 指定job需要成功运行Pods的次数。默认值: 1,即需要运行多少个任务(1个任务1个pod)
parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1,比如一共需要6个pod,这里设置为3,即一次运行3个,分为2轮运行完毕
activeDeadlineSeconds: 30 # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止。
backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6
manualSelector: true # 是否可以使用selector选择器选择pod,默认是false
selector: # 选择器,通过它指定该控制器管理哪些pod
matchLabels: # Labels匹配规则
app: counter-pod
matchExpressions: # Expressions匹配规则
- {key: app, operator: In, values: [counter-pod]}
template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never # 重启策略只能设置为Never或者OnFailure
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]
重启策略说明
  • 如果指定为OnFailure,则job会在pod出现故障时重启容器,而不是创建pod,failed次数不变
  • 如果指定为Never,则job会在pod出现故障时创建新的pod,并且故障pod不会消失,也不会重启,failed次数加1
  • 如果指定为Always的话,就意味着一直重启,意味着job任务会重复去执行了,当然不对,所以不能设置为Always

使用案例

基本使用
创建资源清单
1
vi job-demo.yml

这里的container要是一个任务才行,这里的image如果是nginx,那是不行的,因为nginx在前台,会一直运行下去的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: batch/v1 #batch就是批处理
kind: Job
metadata:
name: job-demo
spec:
manualSelector: true
selector:
matchLabels:
app: counter-pod
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done"

​ 我们可以看到 Job 中也是一个 Pod 模板,和之前的 Deployment、StatefulSet 之类的是一致的,只是 Pod 中的容器要求是一个任务,而不是一个常驻前台的进程了,因为需要退出。

​ 另外值得注意的是 Job 的 RestartPolicy 仅支持 Never 和 OnFailure 两种,不支持 Always,我们知道 Job 就相当于来执行一个批处理任务,执行完就结束了,如果支持 Always 的话是不是就陷入了死循环了?

应用配置
1
kubectl apply -f job-demo.yaml

image-20220531095447382

查看Job情况
1
2
kubectl get job -o wide
kubectl get pod -o wide

注意下,pod详情就很奇怪了,0/1ready,证明目前0个job准备好了,也就是没有job正在运行了,并且已经完成退出了

image-20220531095607372

查看Job详情

Job 对象创建成功后,我们可以查看下对象的详细描述信息

1
kubectl describe job job-demo

image-20220531100018507

​ 可以看到,Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label 标签,而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系。

​ 而 Job 控制器之所以要使用这种携带了 UID 的 Label,就是为了避免不同 Job 对象所管理的 Pod 发生重合。

查看运行日志

我们可以看到很快 Pod 变成了 Completed 状态,这是因为容器的任务执行完成正常退出了,我们可以查看对应的日志

1
kubectl logs job-demo-pnlq9

下图就是我们这里的 Job 任务对应的 Pod 在运行结束后,会变成 Completed 状态。

image-20220531100207336

限制运行时长

在 Job 对象中通过设置字段 spec.activeDeadlineSeconds 来限制任务运行的最长时间

​ 但是如果执行任务的 Pod 因为某种原因一直没有结束怎么办呢?同样我们可以在 Job 对象中通过设置字段 spec.activeDeadlineSeconds 来限制任务运行的最长时间

编辑配置清单

我们修改资源清单,将命令改为休眠100s,当这个job超时10后就会结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: batch/v1 #batch就是批处理
kind: Job
metadata:
name: job-demo
spec:
manualSelector: true
activeDeadlineSeconds: 10 # 最长执行时长不能超过100s
selector:
matchLabels:
app: counter-pod
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 100s;done"

​ 那么当我们的任务 Pod 运行超过了 10s 后,这个 Job 的所有 Pod 都会被终止,并且, Pod 的终止原因会变成 DeadlineExceeded

应用配置
1
kubectl apply -f job-demo.yml

image-20220531101535560

状态监控

我们可以提前打开POD的状态监控,来查看POD的变化

1
kubectl get pod -o wide -w

我们发现任务超过10S后来实强制结束POD,虽然POD任务还在运行

image-20220531101958379

并行运行

有时候任务量比较大,我们可以考虑使用多个POD并行运行任务

修改资源清单

接下来,调整下pod运行的总数量和并行数量 ,在spec下设置下面两个选项

  • completions: 6 # 指定job需要成功运行Pods的次数为6

  • parallelism: 3 # 指定job并发运行Pods的数量为3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: batch/v1 #batch就是批处理
kind: Job
metadata:
name: job-demo
spec:
manualSelector: true
completions: 6 #一共创建6个一次性任务,即6个pod
parallelism: 3 #允许3个并行执行
selector:
matchLabels:
app: counter-pod
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3s;done"
应用配置
1
kubectl apply -f job-demo.yml

image-20220531101535560

Job状态监控
1
kubectl get job -o wide -w

image-20220531103740153

POD状态监控
1
kubectl get pod -o wide -w

如下可以看到,3个为一轮创建一共6个任务

image-20220531103722164

CronJob

CronJob 其实就是在 Job 的基础上加上了时间调度,我们可以在给定的时间点运行一个任务,也可以周期性地在给定时间点运行,这个实际上和我们 Linux 中的 crontab 就非常类似了。

img

CronJob概述

​ CronJob控制器以 Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务。

crontab 格式

crontab 的格式为:分 时 日 月 星期 要运行的命令

  • 第1列分钟 0~59
  • 第2列小时 0~23
  • 第3列日 1~31
  • 第4列月 1~12
  • 第5列星期 0~7(0和7表示星期天)
  • 第6列要运行的命令
配置描述
1
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
apiVersion: batch/v1beta1 # 版本号
kind: CronJob # 类型
metadata: # 元数据
name: # rs名称
namespace: # 所属命名空间
labels: #标签
controller: cronjob
spec: # 详情描述
schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
startingDeadlineSeconds: # 启动作业错误的超时时长
jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象(crontabjob是通过job去管理的);下面其实就是job的定义
metadata:
spec:
completions: 1
parallelism: 1
activeDeadlineSeconds: 30
backoffLimit: 6
manualSelector: true
selector:
matchLabels:
app: counter-pod
matchExpressions: 规则
- {key: app, operator: In, values: [counter-pod]}
template:
metadata:
labels:
app: counter-pod
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done"]
参数解释
  • schedule: cron表达式,用于指定任务的执行时间
    */1 * * * *
    <分钟> <小时> <日> <月份> <星期>

    1
    2
    3
    4
    5
    6
    分钟 值从 0 到 59.
    小时 值从 0 到 23.
    日 值从 1 到 31.
    月 值从 1 到 12.
    星期 值从 0 到 6, 0 代表星期日
    多个时间可以用逗号隔开; 范围可以用连字符给出;*可以作为通配符; /表示每...
  • concurrencyPolicy:
    Allow: 允许Jobs并发运行(默认)
    Forbid: 禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行
    Replace: 替换,取消当前正在运行的作业并用新作业替换它

使用案例

基本使用
创建资源清单

现在,我们用 CronJob 来管理我们上面的 Job 任务,定义如下所示的资源清单

1
vi cronjob-demo.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: batch/v1
kind: CronJob
metadata:
name: cronjob-demo
labels:
controller: cronjob-demo
spec:
schedule: "*/1 * * * *" #每分钟执行一次
jobTemplate:
metadata:
spec:
template:
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox:1.30
command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 3;done"]

​ 这里的 Kind 变成了 CronJob 了,要注意的是 .spec.schedule 字段是必须填写的,用来指定任务运行的周期,格式就和 crontab 一样。

​ 另外一个字段是 .spec.jobTemplate, 用来指定需要运行的任务,格式当然和 Job 是一致的。

​ 还有一些值得我们关注的字段 .spec.successfulJobsHistoryLimit(默认为3) 和 .spec.failedJobsHistoryLimit(默认为1),表示历史限制,是可选的字段,指定可以保留多少完成和失败的 Job。然而,当运行一个 CronJob 时,Job 可以很快就堆积很多,所以一般推荐设置这两个字段的值,如果设置限制的值为 0,那么相关类型的 Job 完成后将不会被保留。

应用配置
1
kubectl apply -f cronjob-demo.yml

image-20220531110844120

查看Cronjob

可以查看对应的 Cronjob 资源对象

1
kubectl get cj -o wide

image-20220531111028149

监控POD

稍微等一会儿查看可以发现多了几个 Job 资源对象,这个就是因为上面我们设置的 CronJob 资源对象,每1分钟执行一个新的 Job

1
kubectl get pod -o wide -w

image-20220531111218787

HPA(Pod 水平自动伸缩)控制器

image-20211121180103777

HPA概述

在前面的学习中我们使用了一个 kubectl scale 命令可以来实现 Pod 的扩缩容功能,但是这个是完全手动操作的,要应对线上的各种复杂情况

我们需要能够做到自动化去感知业务,来自动进行扩缩容。为此,Kubernetes 也为我们提供了这样的一个资源对象:Horizontal Pod Autoscaling(Pod 水平自动伸缩),简称 HPA,HPA 通过监控分析一些控制器控制的所有 Pod 的负载变化情况来确定是否需要调整 Pod 的副本数量。

​ 我们可以简单的通过 kubectl autoscale 命令来创建一个 HPA 资源对象,HPA Controller 默认30s轮询一次(可通过 kube-controller-manager 的–horizontal-pod-autoscaler-sync-period 参数进行设置),查询指定的资源中的 Pod 资源使用率,并且与创建时设定的值和指标做对比,从而实现自动伸缩的功能。

资源监控及资源指标

资源监控系统是容器编排系统必不可少的组件,它为用户提供了快速了解系统资源分配和利用状态的有效途径,同时也是系统编排赖以实现的基础要件。

资源监控及Heapster

​ Kubernetes有多个数据指标需要采集相关的数据,而这些指标大体上由监控集群本身和监控Pod对象两部分组成,监控集群需要监控节点资源状态、节点数量、运行的pod数量;监控Pod资源对象需要监控kubernetes指标,容器指标和应用程序指标。

​ Kubernetes系统与kubelet程序中集成相关的工具程序cAdisor用于对节点上的资源及容器进行实时监控及指标数据采集,这类监控的实现方式有Heapster。从kubernetes1.11开始Heapster被废弃不在使用,metrics-server 替代了heapster。

新一代的监控架构

新一代的kubernetes监控系统架构主要由核心指标流水线和监控指标流水线协同组成。

​ 核心指标流水线由kubelet、资源评估器、metrics-server及API server提供的API群组组成,可用于为kubernetes系统提供核心指标从而能够了解其内部组件和核心程序,监控指标流水线用于从系统收集各种指标数据并提供给终端用户、存储系统及HPA控制器使用。

​ 资源指标API主流的实现是metrics-server,自定义指标API以构建在监控系统Prometheus之上到k8s-prometheus-adapter使用最为广泛。

聚合 API

​ Aggregator 允许开发人员编写一个自己的服务,把这个服务注册到 Kubernetes 的 APIServer 里面去,这样我们就可以像原生的 APIServer 提供的 API 使用自己的 API 了,我们把自己的服务运行在 Kubernetes 集群里面,然后 Kubernetes 的 Aggregator 通过 Service 名称就可以转发到我们自己写的 Service 里面去了。这样这个聚合层就带来了很多好处:

  • 增加了 API 的扩展性:开发人员可以编写自己的 API 服务来暴露他们想要的 API。
  • 丰富了 API:核心 kubernetes 团队阻止了很多新的 API 提案,通过允许开发人员将他们的 API 作为单独的服务公开,这样就无须社区繁杂的审查了。
  • 开发分阶段实验性 API:新的 API 可以在单独的聚合服务中开发,当它稳定之后,在合并会 APIServer 就很容易了。
  • 确保新 API 遵循 Kubernetes 约定:如果没有这里提出的机制,社区成员可能会被迫推出自己的东西,这样很可能造成社区成员和社区约定不一致。

metrics-server安装

现在我们要使用 HPA,就需要在集群中安装 Metrics Server 服务,要安装 Metrics Server 就需要开启 Aggregator,因为 Metrics Server 就是通过该代理进行扩展的,不过我们集群是通过 Kubeadm 搭建的,默认已经开启了

什么是metrics-server

​ 在 HPA 的第一个版本中,我们需要 Heapster(目前这个已经废弃了) 提供 CPU 和内存指标,在 HPA v2 过后就需要安装 Metrcis Server 了,Metrics Server 可以通过标准的 Kubernetes API 把监控数据暴露出来,有了 Metrics Server 之后,我们就完全可以通过标准的 Kubernetes API 来访问我们想要获取的监控数据了:

1
https://10.96.0.1/apis/metrics.k8s.io/v1beta1/namespaces/<namespace-name>/pods/<pod-name>

​ 比如当我们访问上面的 API 的时候,我们就可以获取到该 Pod 的资源数据,这些数据其实是来自于 kubelet 的 Summary API 采集而来的。

​ 不过需要说明的是我们这里可以通过标准的 API 来获取资源监控数据,并不是因为 Metrics Server 就是 APIServer 的一部分,而是通过 Kubernetes 提供的 Aggregator(聚合器) 汇聚插件来实现的,是独立于 APIServer 之外运行的

查看metrics server地址

我们可以先来看看这个metrics server的gitbub地址https://github.com/kubernetes-sigs/metrics-server

image-20220531142633363

下载资源清单

下载仓库官方metrics server安装的资源清单,Aggregator 聚合层启动完成后,就可以来安装 Metrics Server 了,我们可以获取该仓库的官方安装资源清单:

1
wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.1/components.yaml

image-20220531143135611

编辑资源清单
1
vi components.yaml

修改资源清单的140行的代码,将镜像的地址改为bitnami/metrics-server:0.6.1

image-20220531150430188

完整配置文件如下

1
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-view: "true"
name: system:aggregated-metrics-reader
rules:
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- apiGroups:
- ""
resources:
- pods
- nodes
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server-auth-reader
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: extension-apiserver-authentication-reader
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: metrics-server:system:auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
k8s-app: metrics-server
name: system:metrics-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:metrics-server
subjects:
- kind: ServiceAccount
name: metrics-server
namespace: kube-system
---
apiVersion: v1
kind: Service
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
ports:
- name: https
port: 443
protocol: TCP
targetPort: https
selector:
k8s-app: metrics-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-app: metrics-server
name: metrics-server
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: metrics-server
strategy:
rollingUpdate:
maxUnavailable: 0
template:
metadata:
labels:
k8s-app: metrics-server
spec:
containers:
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --metric-resolution=15s
image: bitnami/metrics-server:0.6.1 #修改镜像地址
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /livez
port: https
scheme: HTTPS
periodSeconds: 10
name: metrics-server
ports:
- containerPort: 4443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /readyz
port: https
scheme: HTTPS
initialDelaySeconds: 20
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 200Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
volumeMounts:
- mountPath: /tmp
name: tmp-dir
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
volumes:
- emptyDir: {}
name: tmp-dir
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: metrics-server
namespace: kube-system
version: v1beta1
versionPriority: 100
应用配置
1
kubectl apply -f components.yaml

image-20220531144407785

检查POD情况

因为该配置的namespace是kube-system,所以查看POD需要指定命名空间

1
kubectl get pod -nkube-system  -l k8s-app=metrics-server -o wide

我们发现当前的状态是运行的,但是还是未准备状态

image-20220531151212980

检查启启动错误

我们就需要查看日志查看是否启动错误

1
kubectl logs -f metrics-server-6c96d6dc56-g7t95 -nkube-system

image-20220531151320212

跳过证书校验

上面问题因为我们没有做证书签名的缘故

​ 因为部署集群的时候,CA 证书并没有把各个节点的 IP 签上去,所以这里 Metrics Server 通过 IP 去请求时,提示签的证书没有对应的 IP(错误:x509: cannot validate certificate for 192.168.245.151 because it doesn’t contain any IP SANs” node=”master”),我们可以添加一个--kubelet-insecure-tls参数跳过证书校验:

1
2
3
4
5
6
7
- args:
- --cert-dir=/tmp
- --secure-port=4443
- --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
- --kubelet-use-node-status-port
- --kubelet-insecure-tls #跳过证书校验
- --metric-resolution=15s

image-20220531151758071

重新应用配置
1
kubectl apply -f components.yaml

image-20220531151949345

查看POD情况
1
kubectl get pod -nkube-system  -l k8s-app=metrics-server -o wide

我们发现已经恢复正常了

image-20220531152030707

验证效果

现在我们可以通过 kubectl top 命令来获取到资源数据了,证明 Metrics Server 已经安装成功了

Node指标监控

可以通过以下命令进行监控Node的指标了

1
kubectl top node

image-20220531152542356

pod监控指标

我们还可以监控具体pod的指标

1
kubectl top pod --all-namespaces

image-20220531152737618

Top精简查看

默认执行会有一堆提示,如果不想要这些提示,直接在后面加上参数:--use-protocol-buffers即可【提示中最后有参数说明的哈】

正常查看指标

正常查看Node指标会有一堆的体提示

1
kubectl top nodes

image-20220531153017960

精简查看

加入--use-protocol-buffers参数就可以不显示提示信息了

1
kubectl top nodes --use-protocol-buffers

image-20220531153129128

基于CPU的HPA

默认情况下就是基于CPU的HAP

创建资源清单

现在我们用 Deployment 来创建一个 Nginx Pod,然后利用 HPA 来进行自动扩缩容

1
vi hpa-demo.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-demo
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
应用配置

然后直接创建 Deployment,注意一定先把之前创建的具有 app=nginx 的 Pod 先清除掉

1
2
kubectl apply -f hpa-demo.yml
kubectl get pod -o wide

我们创建了资源清单,当前只有一个POD对象

image-20220531154340152

创建HPA对象

现在我们来创建一个 HPA 资源对象,可以使用kubectl autoscale命令来创建

1
kubectl autoscale deployment hpa-demo --cpu-percent=10 --min=1 --max=10

该命令创建了一个关联资源 hpa-demo 的 HPA,最小的 Pod 副本数为1,最大为10,HPA 会根据设定的 cpu 使用率(10%)动态的增加或者减少 Pod 数量

image-20220531160154425

查看HPA详情

下面我们可以看下HAP的信息

1
kubectl get hpa hpa-demo  -o wide

我们发现指标信息是unknown,读取不到指标信息

image-20220531160927197

查看HPA详细信息

我们可以查看HAP的详细信息,看下具体情况

1
kubectl describe hpa hpa-demo                           

查看详情的时候我们发现了一些错误信息

image-20220531160435543

​ 我们可以看到上面的事件信息里面出现了 failed to get cpu utilization: missing request for cpu 这样的错误信息。

​ 这是因为我们上面创建的 Pod 对象没有添加 request 资源声明,这样导致 HPA 读取不到 CPU 指标信息,所以如果要想让 HPA 生效,对应的 Pod 资源必须添加 requests 资源声明,来更新我们的资源清单文件。

重新配置

下面我们重新修改配置文件解决问题

编辑资源清单

更新我们的资源清单文件,添加requests资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-demo
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
resources:
requests:
memory: 50Mi
cpu: 50m
重新应用配置

然后重新更新 Deployment

1
2
kubectl apply -f hpa-demo.yaml
kubectl get pod -o wide

image-20220531161307339

重新创建HPA对象

重新创建HPA对象,需要先删除HPA对象然后在重新创建

1
2
kubectl delete hpa hpa-demo
kubectl autoscale deployment hpa-demo --cpu-percent=10 --min=1 --max=10

这样我们就重新创建了HPA控制器

image-20220531161528148

查看HPA对象

我们现在再来查看以下HPA的列表信息

1
kubectl get hpa hpa-demo  -o wide

现在我们发现已经获取到了指标信息了

image-20220531161633867

我们还可以查看下HPA的详细信息

1
kubectl describe hpa hpa-demo

我们发现一切正常

image-20220531161739532

模拟压测
验证得到pod-IP

首先验证Pod-IP是否能够访问

1
kubectl get pod -o wide

image-20220531163001359

我们发现当前POD的IP是 10.244.1.96,我们尝试请求访问

1
curl 10.244.1.96

我们发现访问没有任务问题

image-20220531163102828

busybox压测

用另一个终端(我这里是master)使用busybox镜像产生一个测试pod,对10.244.1.96进行压测

1
kubectl run -it --image busybox test-hpa --restart=Never --rm /bin/sh

我们新打开一个终端,并执行以下命令,运行一个pod,并进入控制台,然后输入以下命令来进行压测

1
while true; do wget -q -O- http://10.244.1.96; done

image-20220531163857297

注意:ctrl+c取消压力测试

监控HPA状态

不断查询hpa状态,大概一分钟后才会看到效果

1
kubectl get hpa -o wide -w

我们发现随着服务器压力增加,然后观察 Pod 列表,可以看到,HPA 已经开始工作,POD不断的扩容

image-20220531163504223

查看POD状态

我们现在看下POD的状态信息

1
kubectl get pod -o wide

我们可以看到已经自动拉起了很多新的 Pod,最后会定格在了我们上面设置的 10 个 Pod,同时查看资源 hpa-demo 的副本数量,副本数量已经从原来的1变成了10个

image-20220531163533725

退出测试

我们使用ctrl+c取消压力测试,并监控HPA状态,要等几分钟甚至更久后,就看到cpu与pod数量都回去了

1
kubectl get hpa -o wide -w

可以看到副本数量已经由 10 变为 1,当前我们只是演示了 CPU 使用率这一个指标,在后面的课程中我们还会学习到根据自定义的监控指标来自动对 Pod 进行扩缩容。

image-20220531164439943

缩放间隙

默认为5分钟,也就是默认需要等待5分钟后才会开始自动缩放

​ 从 Kubernetes v1.12 版本开始我们可以通过设置 kube-controller-manager 组件的–horizontal-pod-autoscaler-downscale-stabilization 参数来设置一个持续时间,用于指定在当前操作完成后,HPA 必须等待多长时间才能执行另一次缩放操作。

清理环境

注意:在做本次实验前,把本次实验的全部资源全部卸载掉,以方便下一次精确测试实验。

1
2
kubectl delete hpa hpa-demo
kubectl delete deployment hpa-demo

image-20220531164601706

基于内存的HPA

创建资源清单

然后需要创建一个基于内存的 HPA 资源对象

1
vi hpa-mem-demo.yml

基于当前脚本我们创建一个Deployment对象

1
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: hpa-mem-demo
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
requests:
cpu: 0.01
memory: 25Mi
limits:
cpu: 0.05
memory: 60Mi
应用配置文件
1
2
kubectl apply -f hpa-mem-demo.yml
kubectl get pod -o wide

这样我们就将资源清单的对象创建出来了

image-20220531174411579

创建HPA对象
创建资源清单

我们本次基于配置文件创建HPA

1
vi hpa-mem.yml

要注意这里使用的 apiVersionautoscaling/v2beta1,然后 metrics 属性里面指定的是内存的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: autoscaling/v2beta1 #注意:这里是v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: hpa-mem-demo
namespace: default
spec:
maxReplicas: 10
minReplicas: 1 # 1-10个pod范围内扩容与裁剪
scaleTargetRef: #配置扩容目标
apiVersion: apps/v1
kind: Deployment # 扩容对象的类型
name: hpa-mem-demo # 扩容对象的名称
metrics: # 指定内存的一个配置
- type: Resource
resource:
name: memory
targetAverageUtilization: 10 # 10%内存利用
应用配置

直接创建上面的资源对象即可

1
2
kubectl apply -f hpa-mem.yml
kubectl get hpa -o wide

这样就启动了一个基于内存的HPA对象,到这里证明 HPA 资源对象已经部署成功了

image-20220531175529017

进行压测
验证POD

我们首先查看下POD的名字信息

1
kubectl get pod -o wide

这样我们就获取了POD的名称了hpa-mem-demo-644b6585df-x9psj

image-20220531172846281

内存压测压测

接下来我们对应用进行压测,将内存压上去,换一个终端(master),进入pod后进行dd命令测试

1
2
3
kubectl exec -it hpa-mem-demo-58899f49cd-vmhqn -- /bin/sh
#登录终端执行下面的命令
dd if=/dev/zero of=/tmp/file1

image-20220531175617183

监控HPA状态

然后打开另外一个终端观察 HPA 资源对象的变化情况,不断查询hpa状态,大概一分钟后才会看到效果

1
kubectl get hpa -o wide -w

我们发现副本数随着内存占用率的增加而扩容

image-20220531175718601

查看POD状态
1
kubectl get pod -o wide

我们发现已经扩容出来了10个POD

image-20220531175840329

中断压测
清理文件

ctrl+c取消后,删除dd的文件

1
rm -rf /tmp/file1 

image-20220531182215390

监控HPA状态

等几分钟甚至更久后,就看到内存与pod数量都回去了

1
kubectl get hpa -o wide -w

image-20220531182056323

查看POD状态
1
kubectl get pod -o wide

我们发现已经缩容到了1个

image-20220531182115247

清理环境

注意:在做本次实验前,把本次实验的全部资源全部卸载掉,以方便下一次精确测试实验。

1
2
kubectl delete hpa hpa-mem-demo
kubectl delete deployment hpa-mem-demo

image-20220531182335047

评论