技术调优之 大规模 Kubernetes 集群调优设置

大规模 Kubernetes 集群调优设置

Posted by 董江 on Thursday, September 1, 2022

大规模 Kubernetes 集群调优设置

0. 前置条件

0.1 网段划分

首先集群网段划分,支持5000+ Node, 10000+ Service 和 100000+ Pod, 因此需要:

1个B段IP 用于Node划分 3个B段IP 用于Pod Service划分

为了支持1w以上Pod,避免iptable 规则指数级增长炸裂,必须使用 ipvs 模式

0.2 单机Node Pod支持

其次,为了 kubemock 1w+ ServicePodKubelet 单机Pod上线更改

  --node-status-update-frequency=3s #Node状态上报周期。默认10s
  --max-pods=500 #Node启动Pod个数。默认110

0.3 其他组件参数调优

kube-controller-manager配置调优:

  --node-monitor-period=2s #检查 kubelet 的状态时间间隔
  --node-monitor-grace-period=20s #检查 notready node 时间间隔
  --pod-eviction-timeout=30s # pod 绑定失败后重新调度时间间隔
  --concurrent-deployment-syncs=50 # deployment并发度
  --concurrent-endpoint-syncs=50 # endpoint并发度
  --concurrent-job-syncs-50 # job并发度
  --concurrent-namespace-syncs=100 # namespace并发度
  --concurrent-replicaset-syncs=50 # replicaset并发度
  --concurrent-service-syncs=100 # service并发度
  --kube-api-qps=500 # 与apiserver 限流 500qps
  --kube-api-burst=100 # 与apiserver 限并发 100连接并发

1. etcd调优

1.1 etcd 请求时间调优

heartbeat-interval就是所谓的心跳间隔,即主节点通知从节点它还是领导者的频率。实践数据表明,该参数应该设置成节点之间 RTT 的时间。Etcd 的心跳间隔默认是 100 毫秒。 election-timeout是选举超时时间,即从节点等待多久没收到主节点的心跳就尝试去竞选领导者。Etcd 的选举超时时间默认是 1000 毫秒。

    # RTT时间是 60ms
    --heartbeat-interval=100 #心跳间隔 100ms
    --election-timeout=500 #选举超时时间 500ms

1.2 快照调优

存储创建快照的代价是很高的,所以只用当参数累积到一定的数量时,Etcd 才会创建快照文件。 默认值是 10000 在超大集群中,Etcd 的内存使用和磁盘使用过高,那么应该尝试调低快照触发的阈值

   --snapshot-count=5000 #数量达到 5000 时才会建立快照

1.3 磁盘 IO 调优

etcd 的存储目录分为 snapshotwal,他们写入的方式是不同的,snapshot 是内存直接 dump file,而 wal 是顺序追加写。因此可以将 snapwal 进行分盘,放在两块 SSD 盘上,提高整体的 IO 效率,这种方式可以提升 etcd 20%左右的性能。

Linux 中 etcd 的磁盘优先级可以使用 ionice 配置:

$ ionice -c2 -n0 -p `pgrep etcd`

1.4 CPU 优先级调整

$ renice -n -20 -P $(pgrep etcd)

其中 nice 值可以由用户指定,默认值为 0,root 用户的取值范围是[-20, 19],普通用户的值取值范围是[0, 19],数字越小,CPU 执行优先级越高。

1.5 数据规模和自动整理

etcd 的硬盘存储上限(默认是 2GB),当 etcd 数据量超过默认 quota 值后便不再接受写请求,可以通过设置 --quota-backend-bytes 参数来增加存储大小, quota-backend-bytes 默认值 2GB,上限值为 8 GB, 3.4版本支持100GB

  --quota-backend-bytes=8589934592  # 后端存储 8G
  --auto-compaction-mode=revision
  --auto-compaction-retention=1000  # 开启每5分钟就自动压缩,并保留lastet 1000个revision

1.6 TC网络优化

master节点上,通过TC流量控制机制对对等流量进行优先级排序

$ NETWORK_INTERFACE=eth0

# 针对 2379、2380 端口放行
$ tc qdisc add dev ${NETWORK_INTERFACE} root handle 1: prio bands 3
$ tc filter add dev ${NETWORK_INTERFACE} parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
$ tc filter add dev ${NETWORK_INTERFACE} parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
$ tc filter add dev ${NETWORK_INTERFACE} parent 1: protocol ip prio 2 u32 match ip sport 2739 0xffff flowid 1:1
$ tc filter add dev ${NETWORK_INTERFACE} parent 1: protocol ip prio 2 u32 match ip dport 2739 0xffff flowid 1:1

# 查看现有的队列
$ tc -s qdisc ls dev enp0s8

1.7 K8s events 拆到单独的 etcd 集群

apiserver是通过event驱动的服务,因此,将apiserver中不同类型的数据存储到不同类型的etcd集群中。 从 etcd 内部看,也就对应了不同的数据目录,通过将不同目录的数据路由到不同的后端 etcd 中,从而降低了单个 etcd 集群中存储的数据总量,提高了扩展性。

简单计算下: 如果有5000个Pod, 每次kubelet 上报信息是15Kb大小; 10000个Pod 事件变更信息,每次变更4Kb

etcd 接受Node信息: 15KB * (60s/3s) * 5000 = 150000Kb = 1465Mb/min

etcd 接受Pod event信息:10000 * 4Kb * 30% = 12Mb/min

这些更新将产生近 1.2GB/min 的 transaction logs(etcd 会记录变更历史)

拆解原则:

pod etcd

lease etcd

event etcd

其他etcd (node、job、deployment等等)

apiserver events拆解:

  --etcd-servers="http://etcd1:2379,http://etcd2:2379,http://etcd3:2379" \
  --etcd-servers-overrides="/events#http://etcd4:2379,http://etcd5:2379,http://etcd6:2379"
  --etcd-servers-overrides="coordination.k8s.io/leases#http://etcd7:2379,http://etcd8:2379,http://etcd9:2379"
  --etcd-servers-overrides="/pods#http://etcd10:2379,http://etcd11:2379,http://etcd12:2379"

1.8 性能对比验证:etcd benchmark (基于移动云KCS etcd性能验证)

条件:

kubernetes 1.21.5, 3 master * 4Core 16G, 2 work * 4Core 8G

kubemark mock 1700 个 Node; 部署了 6400个Pod(包括calico 和 csi)

工具与被测对象:

[root@kcs-dongjiang--m-4w48b /]#  crictl images | grep etcd
cis-hub-huadong-4.cmecloud.cn/ecloud/etcd                               3.4.13-0            0369cf4303ffd       86.7MB
cis-hub-huadong-4.cmecloud.cn/testdj/etcd-tools-benchmark               3.4.4               76e8f59bb5a90       193MB

其中etcd Etcd DBSize 数据 1.56GB

写入测试

// leader
$ benchmark --endpoints="http://10.179.0.13:2379" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256

$ benchmark --endpoints="http://10.179.0.13:2379" --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256


// 所有 members
$ benchmark --endpoints="http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379" --target-leader --conns=1 --clients=1 put --key-size=8 --sequential-keys --total=10000 --val-size=256

$ benchmark --endpoints=""http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379"  --target-leader --conns=100 --clients=1000 put --key-size=8 --sequential-keys --total=100000 --val-size=256
key 数量 Key 大小 Value的大小 连接数量 客户端数量 目标 etcd 服务器 平均写入 QPS 每请求平均延迟 IO优先级 & 网络带宽调优
10,000 8 256 1 1 只有主 75 50.0ms
10,000 8 256 1 1 只有主 322 13.2ms
100,000 8 256 100 1000 只有主 1871 1207.7ms
100,000 8 256 100 1000 只有主 2239 992.4ms
10,000 8 256 1 1 所有 members 326 13.4ms
100,000 8 256 1 1 所有 members 352 12.6ms
100,000 8 256 100 1000 所有 members 1132 1649.1ms
100,000 8 256 100 1000 所有 members 1198 1536.8ms

读取测试

$ benchmark --endpoints="http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379"  --conns=1 --clients=1  range foo --consistency=l --total=10000

$ benchmark --endpoints="http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379"  --conns=1 --clients=1  range foo --consistency=s --total=10000

$ benchmark --endpoints="http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379"  --conns=100 --clients=1000  range foo --consistency=l --total=100000

$ benchmark --endpoints="http://10.179.0.13:2379,http://10.179.0.2:2379,http://10.179.0.6:2379"  --conns=100 --clients=1000  range foo --consistency=s --total=100000
key 数量 Key 大小 Value的大小 连接数量 客户端数量 一致性(线性化/串行化) 每请求平均延迟(99%) 平均读取 QPS
10,000 8 256 1 1 Linearizable 36.2ms 319
10,000 8 256 1 1 Serializable 34.4ms 916
100,000 8 256 100 1000 Linearizable 1302.7ms 1680
100,000 8 256 100 1000 Serializable 1097.6ms 2401

2. APIServer 调优

2.1 APIServer load balancing

解决APIServer流量不均衡问题

apiserver 添加以下服务goaway 优雅重连机制

--goaway-chance=0.001  # 1/1000的连接 请求后,断开connection

2.2 APIServer Local Cache

本地APIServer Local Cache调整

--max-mutating-requests-inflight=3000 #在给定时间内的最⼤ mutating 请求数,调整 apiserver 的流控 qos,可以调整⾄ 3000 ,默认为 200
--max-requests-inflight=1000  #在给定时间内的最⼤ non-mutating 请求数,默认400 ,可以调整⾄ 1000
--watch-cache=true
--watch-cache-sizes=node#5000,pod#10000,event#1000,namespace#500,service#1000  #调⼤ resources 的 watch size,默认为 100,当集群中 node、lease、service、namespace 以及pod 数量⾮常多时可以稍微调⼤,⽐如: --watch-cache-sizes=node#5000,pod#10000,event#1000,namespace#500

2.3 APIServer bookmark 和 reflector 机制

bookmarkreflector 机制,都是对自定义的controlleroperatorkubelet等请求方依赖client-go 进行优化实现 确保使用规范,尽量使用watch方式获得obeject变更;List & Watch 某些资源对象,并将此“增量”事件 push 入一个 DeltaFIFO 队列。

bookmark demo例子:

func (c *Cacher) GetList(ctx , key string, opts storage.ListOptions, listObj runtime.Object) error {
    // 情况一:ListOption 要求必须从 etcd 读
    ...
    // 情况二:apiserver 缓存未建好,只能从 etcd 读
    ...
    // 情况三:apiserver 缓存正常,从缓存读:保证返回的 objects 版本不低于 `listRV`
    listPtr := meta.GetItemsPtr(listObj) // List elements with at least 'listRV' from cache.
    listVal := conversion.EnforcePtr(listPtr)
    filter  := filterWithAttrsFunction(key, pred) // 最终的过滤器

    objs, readResourceVersion, indexUsed := c.listItems(listRV, key, pred, ...) // 根据 index 预筛,性能优化
    for _, obj := range objs {
        elem := obj.(*storeElement)
        if filter(elem.Key, elem.Labels, elem.Fields)   {                        // 真正的过滤
            listVal.Set(reflect.Append(listVal, reflect.ValueOf(elem))
        }
    }

    if c.versioner != nil
        c.versioner.UpdateList(listObj, readResourceVersion, "", nil)
    return nil
}

reflector demo例子:

func (r *Reflector) Run(stopCh <-chan struct{}) {
    klog.V(2).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
    wait.BackoffUntil(func() {
        if err := r.ListAndWatch(stopCh); err != nil {
            utilruntime.HandleError(err)
        }
    }, r.backoffManager, true, stopCh)
    klog.V(2).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
}

3. kubelet 调优

3.1 node lease信息上报调优

  --node-status-update-frequency=3s #Node状态上报周期。默认10s
  --kube-api-qps=50 #node lease信息上报qps
  --kube-api-burst=100 #node lease信息上报并发
  --event-qps=100 #pod event信息上报 qps
  --event-burst=100 #pod event信息上报并发

注意

  1. 集群中Node数据越少, kubelet的event/api qps与burst数越大,单机器上的产生的event数和apiserver交互的请求数就越大;
  2. 集群数据Node数据越大, apiserveretcd的个数也需要相应按比例增加

初略计算方式: qps值 = apiserver的qps数 / node个数

比如(之前华为云CCE上线自定义版本配置)

Node数据最大 控制面个数
200 3 master节点
1000 5 master节点
2500 7 master节点
4000 9 master节点
5000 11 master节点

3.2 cpu 预留, 确保整个node资源可控

--enforce-node-allocatable=pods,kube-reserved,system-reserved # 对pods、kube-reserved 和 system-reserved预留
--kube-reserved-cgroup=/system.slice/kubelet.service  # 确保kubelet管理的进程可用
--system-reserved-cgroup=/system.slice
--kube-reserved=cpu=200m,memory=2Gi,ephemeral-storage=1Gi
--system-reserved=cpu=500m,memory=1Gi,ephemeral-storage=1Gi
--eviction-hard=memory.available<1Gi,nodefs.available<5%  #node资源低于这个值,驱逐现有pod

提前,创建好kubelet.service 和 system.slice cgroup目录 mkdir -p /sys/fs/cgroup/cpuset/system.slice/kubelet.service mkdir -p /sys/fs/cgroup/hugetlb/system.slice/kubelet.service

3.3 kubelet 测试

测试 kubelet部署Pod速度kubelet 缩容后 event上报速度 两个关键指标

kubelet部署Pod速度

亲和到单机,pod 1个数 扩容到 200个, 用时 78,123s 下降到了 24.769s

[root@kcs-dj-test-m-4vdkh /]#  time kubectl rollout status deployment/nginx-deployment
Waiting for deployment "nginx-deployment" rollout to finish: 1 of 200 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 2 of 200 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 3 of 200 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 4 of 200 updated replicas are available...
...
Waiting for deployment "nginx-deployment" rollout to finish: 197 of 200 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 198 of 200 updated replicas are available...
Waiting for deployment "nginx-deployment" rollout to finish: 199 of 200 updated replicas are available...
deployment "nginx-deployment" successfully rolled out

real    0m24.769s
user    0m0.075s
sys     0m0.012s

kubelet 缩容 event上报速度

亲和到单机,pod 200个数 缩容到 1个, 释放 199 个pod 时间, 从 32s 下降到 9.05s

[root@kcs-dj-test-m-4vdkh /]#  watch time kubectl get rs  
Every 2.0s: time kubectl get rs                                                                                       Thu Sep 15 15:16:33 2022

NAME                         DESIRED   CURRENT   READY   AGE
nginx-deployment-b56784d9b   1         1         1       28h

real    0m9.058s
user    0m0.058s
sys     0m0.015s

4. Kube-Proxy 调优

4.1 ipvs vs iptable

kube-proxy分配的是ClusterIP, calico/flannel分配的是Pod IP

通过Service 访问具体实例的Endpoint, 核心是在kube-proxy

Service基础 1 5000 20000
rules基数 8 40000 160000
增加1条 iptable 规则 50us 11min 5h
增加1条 ipvs 规则 30us 50us 75us

必须 ipvs

因此 kube-proxy 更改

--ipvs-min-sync-period=1s #默认:最小刷新间隔1s
--ipvs-sync-period=5s  # 默认:5s周期性刷新
--cleanup=false
--ipvs-min-sync-period=0s # 发生事件实时刷新
--ipvs-sync-period=30s # 30s周期性刷新, 刷新一次节点 iptables 规则
--cleanup=true (清理 iptables 和 ipvs 规则并退出)

对于数据面高并发场景,服务服务配置

--ipvs-scheduler=lc #最小连接,默认是rr round-robin

IPVS 内置在 Kernel 中, Kernel 的版本对 IPVS 还是有很大影响

4.2 conntracker设置

无论是iptable 或者 ipvs 底层都会走conntracker

$ sysctl -w net.netfilter.nf_conntrack_max=2310720

kube-proxy 启动的时候会重新设置, 因此配置参数可以按node CPU 个数进行调整

--conntrack-max-per-core=144420  # 2310720/cpu个数。 2310720/16核 = 144420
--conntrack-min=2310720 # 设置为nf_conntrack_max 

5. kube-controller-manager

5.1 controller manager 检查 kubelet 周期

   --node-monitor-period=2s #检查 kubelet 的状态时间间隔 默认:5s
   --node-monitor-grace-period=20s #检查 notready node 时间间隔, 默认: 40s
   --pod-eviction-timeout=30s # pod 绑定失败后重新调度时间间隔, 默认 5min

controller manager 通过node controller watch了 node状态,并进行bind score计分;

5.2 调整 controller manager 并发度

  --concurrent-deployment-syncs=50 #并发创建deployment的并发数
  --concurrent-endpoint-syncs=50 #并发创建endpoint的并发数
  --concurrent-namespace-syncs=100 #并发创建namespace的并发数
  --concurrent-replicaset-syncs=50 #并发创建replicaset的并发数
  --concurrent-service-syncs=100 #并发创建service-的并发数
  --kube-api-qps=100 
  --kube-api-burst=500

6. kube-scheduler

6.1 调整 scheduler 并发度

  --kube-api-qps=500 # 默认值500
  --kube-api-burst=1000 # 默认值100

7. 整体测试

7.1 集群环境:

5个 Master 节点: 192Core、384G机器 50个 Node 节点:32Core 64G机器 CNI:calico IPIP 网络; CRI:containerd; 网段: 4个B段

7.2 验证部署方式:

  1. 使用45台机器 kubermarkkubermark namespace下 mock 出来5000个 node
  2. 使用e2e工具 创建出来50个namespace, 1000个service, 40000个pod
  3. 其中中1/4的namespace是大规模1000+ pod,1/4是中型规模400+ pod; 1/2的namespace是小型50+ pod; 按 namespace下的 configmapserviceaccountsecrets等,按pod的个数等4:1比例随机创建

7.3 结果

本结果统计apiserver metrics聚合结果 (不包括网络传输请求时延)

apiserver get/list P99请求响应: apiserver get/list P99请求响应

整体满足, 有部分 list cluster pods list接口 P99响应超过一秒, 后续继续优化

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付