鲲鹏社区首页
中文
注册
一次k8s pod并发拉起慢的定位过程

一次k8s pod并发拉起慢的定位过程

k8s

发表于 2025/05/09

0

作者|廖宏科


问题背景

在k8s环境中,并发拉起100个pod,统计这100个pod全部启动完成的平均耗时,平均耗时较大。


应用信息

软件名称

版本

说明

k8s

1.20.2

k8s框架

openjdk

1.8

pod中运行openjdk的镜像

calico

3.15

k8s中使用的cni组件
docker19.03.10docker版本


测试方法

k8s的组网为一个master,两个node节点。通过脚本在master节点上并发下发创建100个pod的指令,等待这100全部为running状态之后,通过k8s日志统计每个pod从开始到完成创建的精确耗时,最终得出整体的平均耗时,一开始默认测出来为8s左右,时间偏大,需要调优。


问题定位

1)耗时拆分

结合源码,我们将整个容器部署流程进行拆分,以便分析实际延迟原因及性能瓶颈,大致步骤如下:

1)  输入部署指令:通过kubectl输入job部署指令,向kube-apiserver发送请求,apiserver收到后将job对象持久化写入etcd并响应成功。

2)  创建pod对象:job-controller通过观察apiserver发现有新的job建立后,将其从队列中拉出,根据其配置创建对应pod对象,返回apiserver并写入etcd。

3)  调度至特定工作节点上:scheduler通过观察apiserver发现有新创建且未被调度的pod,根据特定调度规则选择其运行的工作节点,将节点信息写入pod描述后,返回apiserver并写入etcd。

4)  创建前检查:特定工作节点上kubelet发现有pod属于该节点后,将其从队列中拉出,根据其描述创建容器。创建容器前需要进行相关检查,包括权限检查,数据目录创建,volume挂载等。

5)  创建沙盒容器:创建业务容器前,需要先创建沙盒容器及网络接口,用于为pod内容器提供通信服务。

6)  拉取镜像:根据pod描述拉取对应容器镜像,默认在本地有镜像缓存时直接拉取本地镜像。

7)  创建容器:镜像拉取完毕后创建业务容器。

8)  启动容器:容器创建完成后启动,可以开始执行实际业务。

为了方便问题定位,我们将整个过程跨分为三个阶段:调度阶段,创建阶段(包括创建前准备),启动阶段

K8S集群(日志时间戳分析阶段)

时间戳阶段介绍

调度阶段

100并发容器从收到任务指令到调度到node节点为调度阶段

创建阶段(docker容器创建、网络创建)

docker容器创建和k8s pod网络创建为创建阶段

启动阶段

创建完到启动过程为启动阶段

三个阶段的时间获取方式如下,通过调整master节点的 kube-scheduler的日志等级,可以在scheduler的日志中获取调度完成事件的精确时间;通过调整node节点的kubelet日志等级,可以在node节点的kubelet日志中获取创建完成和启动完成事件的精确时间。

在/etc/kubernetes/manifests/kube-scheduler.yaml添加--v=2,设置Scheduler 的日志等级;

在/usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf添加--v=3, 设置kubelet的日志等级。

阶段

事件

日志记录(v: 打印等级)

调度阶段

Scheduled

Scheduler (v=2):“Successfully bound pod to node ……”

创建阶段

Created

Kubelet (v=3):“Created ……”

启动阶段

Started

Kubelet (v=3):“Started ……”

经过拆分,我们发现总耗时8s中,调度阶段的耗时占比最大:

阶段

耗时

调度阶段

7.7s

创建阶段

0.5s

启动阶段

0.1s

2)调度阶段分析

通过分析调度阶段scheduler日志,我们提取每个pod调度完成时间,发现调度过程存在阻塞。接着,我们进一步研读日志打印的信息,发现Throttling requests异常信息。该异常指向一条scheduler向apiserver发起的http请求,因为触发限流的缘故,等待了比正常耗时远长的时间才得到响应。

耗时日志:"Throttling request  took 298.07161ms, request: PUT", 表示触发了kube-apiserver请求限流。

调度阶段,job-controller和scheduler都要频繁向apiserver发送http请求来新建对象,获取状态,修改状态等,这样的限流机制显然极大地延缓了调度的进行,进而导致延迟加大。

因此,我们需要增大限流的阈值,从而提高网络资源的利用,加速调度阶段的进行,进而减少延迟。

问题解决

1)调度阶段优化

通过上面的分析,我们要增大scheduler和controller的并发限制,还要增大工作节点的kubelet的并发限制,k8s的并发参数主要有两个:

K8S参数值

调整前

调整后

kube-api-qps

50

150

kube-api-burst

100

200

k8s调整前:kube-api-qps:50(默认值),kube-api-burst: 100(默认值)

参数介绍:kube-api-qps 参数是指k8s api 的每秒请求数上限;kube-api-burst 是指 k8s api 的突发请求数。调整以上两个值,来控制每秒请求的阈值和突发请求的阈值。在100并发的测试场景中,调大该值能提升调度效率,降低调度阶段的时延。

具体调整方式如下:

1. 管理节点上Scheduler参数修改

vi /etc/kubernetes/manifests/kube-scheduler.yaml

在spec:containers:-command:中添加如下语句

- --kube-api-qps=150

- --kube-api-burst=200

2. 管理节点上Controller-manager参数修改

vi /etc/kubernetes/manifests/kube-controller-manager.yaml

在spec:containers:-command:中添加如下语句

- --kube-api-qps=150

- --kube-api-burst=200

3. 工作节点上Kubelet参数修改

vi /var/lib/kubelet/kubeadm-flags.env

在KUBELET_KUBEADM_ARGS属性中添加如下语句

  --kube-api-qps=150

--kube-api-burst=200

调整完成之后重启kubelet,重新测试发现调度时间从7.7s降低到了0.5s左右,但是创建时间从0.5涨到了5.5s,整体耗时从8s下降到了6s左右。有一定优化,但创建时间大涨。这一现象说明并发调度没有瓶颈之后,并发创建存在较大瓶颈,整体是个此消彼长的态势。

2)创建阶段分析与优化

因为之前创建没有瓶颈,怀疑是之前并发程度不够没有触发创建的瓶颈,因为之前瓶颈在与调度,调度“限流”了并发之后,同一时刻进入创建阶段的pod数量不多,现在调度快速完成之后,同时进入创建阶段的pod就变多,导致创建阶段的并发调度就变得激烈。

通过对比基线环境的各项配置,我们发现存在几个操作系统内核参数的差异:

内核参数

调整前

调整后

CONFIG_HZ

CONFIG_HZ=250

CONFIG_HZ=1000

CONFIG_HZ_250=y

CONFIG_HZ_1000=y

我们修改内核参数,重新编译操作系统内核之后并且重新测试,发现创建时间从5.5s降低到了1s左右,整体耗时来到了1.8s左右。

在Linux内核中,CONFIG_HZ 定义了系统定时器中断的频率,即每秒产生的时钟中断次数。CONFIG_HZ=250, 时间为1s/250 = 4ms,表示CPU 4ms产生一次时钟中断,CONFIG_HZ=1000 表示1ms产生一次时钟中断。

更快的产生时钟中断对于对,调度器能更频繁地调整进程的虚拟运行时间,确保多任务间的资源分配更公平。对于当前这类场景来看,更精确的时间调度,能够减少一些线程等待情况,更好的提高并发拉起性能。