大规模 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+ Service
与 Pod
,Kubelet
单机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
的存储目录分为 snapshot
和 wal
,他们写入的方式是不同的,snapshot
是内存直接 dump file
,而 wal
是顺序追加写。因此可以将 snap
与 wal
进行分盘,放在两块 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
添加以下服务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 机制
bookmark
和 reflector
机制,都是对自定义的controller
、operator
、kubelet
等请求方依赖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信息上报并发
注意
- 集群中
Node数据越少
, kubelet的event/api qps与burst数越大
,单机器上的产生的event
数和apiserver
交互的请求数就越大; - 集群数据
Node
数据越大,apiserver
和etcd
的个数也需要相应按比例增加
初略计算方式: 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 验证部署方式:
- 使用45台机器
kubermark
在kubermark
namespace下 mock 出来5000个 node - 使用e2e工具 创建出来50个
namespace
, 1000个service
, 40000个pod
。 - 其中中1/4的
namespace
是大规模1000+pod
,1/4是中型规模400+pod
; 1/2的namespace是小型50+pod
; 按namespace
下的configmap
、serviceaccount
、secrets
等,按pod
的个数等4:1
比例随机创建
7.3 结果
本结果统计apiserver metrics聚合结果 (不包括网络传输请求时延
)
apiserver get/list P99请求响应:
整体满足, 有部分 list cluster pods list接口 P99响应超过一秒, 后续继续优化
「如果这篇文章对你有用,请随意打赏」
如果这篇文章对你有用,请随意打赏
使用微信扫描二维码完成支付