zhangguanzhang's Blog

k8s node 热扩容内存导致的一次业务故障

字数统计: 730阅读时长: 3 min
2024/05/01

k8s node 热扩容内存导致的一次业务故障

由来

2024/04 月底发生的,k8s node 热扩容内存导致的一次业务故障,此次不是排查,而是过程梳理。k8s 版本是 1.27.4。

过程

我们有些业务的 cpu 和内存是根据使用人数上涨的,多副本资源占用 > 单个高配副本,例如 1000 个人使用某个功能,多副本可能需要 6个 4c4G,但是单个副本 18c16G 就可以。所以我们的核心业务服务都是不加 limit 的。该服务也有自我限制,例如它最多使用机器的 85% 内存后,后续请求就不处理。

客户使用人数上升,告警后现场人员让客户给机器加了内存(hotplug) 由 64g -> 128g ,然后过了一天后机器频繁驱逐导致 cpu 高。看 kubelet 日志报错内存压力触发驱逐,但是看监控使用还好(node_exporter获取是实时的)内存没到 75% 以上才 65%,核心服务日志里也没到百分比上线内存,后面发现是 kubelet 的内存信息还是老的:

1
2
3
4
5
6
7
8
9
10
Capacity:
cpu: 16
...
memory: 65789716Ki
pods: 253
Allocatable:
cpu: 15400m
...
memory: 63225620Ki
pods: 253

重启 kubelet 后,内存信息就正常了:

1
2
3
4
5
6
7
8
9
10
Capacity:
cpu: 16
...
memory: 103538444Ki
pods: 253
Allocatable:
cpu: 15400m
...
memory: 100974348Ki
pods: 253

代码分析

func NewMainKubelet( 里,只获取一次

1
2
3
4
5
6
7
8
9
10
// https://github.com/kubernetes/kubernetes/blob/7b359a2f9e1ff5cdc49cfcc4e350e9d796f502c0/pkg/kubelet/kubelet.go#L607-L614

machineInfo, err := klet.cadvisor.MachineInfo()
if err != nil {
return nil, err
}
// Avoid collector collects it as a timestamped metric
// See PR #95210 and #97006 for more details.
machineInfo.Timestamp = time.Time{}
klet.setCachedMachineInfo(machineInfo)

全局搜 setCachedMachineInfo( 找到了 kubernetes/pkg/kubelet/kubelet_getters.go 下的:

1
2
3
4
5
6
7
8
9
10
11
12
// GetCachedMachineInfo assumes that the machine info can't change without a reboot
func (kl *Kubelet) GetCachedMachineInfo() (*cadvisorapiv1.MachineInfo, error) {
kl.machineInfoLock.RLock()
defer kl.machineInfoLock.RUnlock()
return kl.machineInfo, nil
}

func (kl *Kubelet) setCachedMachineInfo(info *cadvisorapiv1.MachineInfo) {
kl.machineInfoLock.Lock()
defer kl.machineInfoLock.Unlock()
kl.machineInfo = info
}

GetCachedMachineInfo 找到 kubernetes/pkg/kubelet/kubelet_node_status.go 下:

1
2
3
4
5
6
7
// https://github.com/kubernetes/kubernetes/blob/d1c7f7a0e9d59aa88aa5b4d07db7e14772b3e386/pkg/kubelet/kubelet_node_status.go#L733
func (kl *Kubelet) defaultNodeStatusFuncs()
....
setters = append(setters,
...
nodestatus.MachineInfo(.... kl.GetCachedMachineInfo, ...

然后 kubernetes/pkg/kubelet/nodestatus/setters.go 里的 func MachineInfo(

1
2
3
4
5
6
// https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/nodestatus/setters.go#L283
func MachineInfo(nodeName string,
...
machineInfoFunc func() (*cadvisorapiv1.MachineInfo, error),
...
info, err := machineInfoFunc()

defaultNodeStatusFuncs 是 node kubelet 每 nodeStatusUpdateFrequency 时候查看一些信息变化否,变了后面会上报更新,从上面整个代码流程看是只获取了一次,开了个虚拟机搭建修改代码 GetCachedMachineInfo() 内尾部追加,编译替换后运行,扩容测试了下可以不重启动态更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    // Avoid collector collects it as a timestamped metric
    // See PR #95210 and #97006 for more details.
    // cannot use kl.machineInfo.Timestamp
    if kl.lastStatusReportTime.Before(time.Now().Add(-50 * time.Second)) {
        currentMachineInfo, err := kl.cadvisor.MachineInfo()
        if err != nil {
            return nil, err
        }

        if currentMachineInfo.NumCores > kl.machineInfo.NumCores {
            kl.machineInfo.NumCores = currentMachineInfo.NumCores
        }
        if currentMachineInfo.MemoryCapacity > kl.machineInfo.MemoryCapacity {
            kl.machineInfo.MemoryCapacity = currentMachineInfo.MemoryCapacity
        }
    }

后面去提了 kep Hot increase cpu/memory/storage without restarting kubelet 但是发现之前就有了,只不过依旧没符合要求和合入。

CATALOG
  1. 1. 由来
  2. 2. 过程
  3. 3. 代码分析