資源采集
節(jié)點CPU、NUMA信息采集
通過lscpu -e=CPU,NODE,SOCKET,CORE,CACHE,ONLINE來獲取節(jié)點的CPU信息,相關(guān)字段含義:
NODE:一個 NUMA Node,包含一組 CPU 核心和與之直接連接的內(nèi)存;一個 NUMA 節(jié)點 ≈ 一個物理 CPU 或一個內(nèi)存域
SOCKET:物理 CPU 插槽(通常指主板上的一個物理處理器),一臺多路服務(wù)器上可能有多個物理 CPU,每個插在一個 Socket 上
CORE:CPU 核心,一個物理處理單元,一個 SOCKET可以有多個核心,每個核心可以運行一個線程(或多個,如果啟用了超線程)
另外Thread(s) per core:如果開啟了 超線程(Hyper-Threading),每個核心可以執(zhí)行多個線程,這個值通常是 2
比如:
Socket(s): 2
Core(s) per socket: 16
Thread(s) per core: 2
NUMA node(s): 2
有 2 個物理 CPU(2 個 SOCKET)。
每個 SOCKET 有 8 個核心,一共 16 個核心。
每個核心支持 2 個線程,總共 32 個邏輯處理器
系統(tǒng)是一個 2 節(jié)點 NUMA 架構(gòu)。
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
通常情況下,每個NUMA節(jié)點綁定的是一個物理SOCKET,對應(yīng)這個SOCKET上的16個CPU核心以及對應(yīng)的超線程
查詢到的節(jié)點CPU信息會通過node_cpu_info這個key緩存在內(nèi)存中
NUMA的內(nèi)存信息通過目錄文件/sys/bus/node/devices/node*/meminfo查看
Node 0 MemTotal: 65493172 kB
Node 0 MemFree: 11067960 kB
Node 0 MemUsed: 54425212 kB
Node 0 Active: 17109804 kB
Node 0 Inactive: 28585064 kB
Node 0 Active(anon): 10321392 kB
Node 0 Inactive(anon): 205232 kB
Node 0 Active(file): 6788412 kB
Node 0 Inactive(file): 28379832 kB
Node 0 Unevictable: 357096 kB
Node 0 Mlocked: 357096 kB
Node 0 Dirty: 5428 kB
Node 0 Writeback: 0 kB
Node 0 FilePages: 35681028 kB
Node 0 Mapped: 3333980 kB
Node 0 AnonPages: 8702720 kB
Node 0 Shmem: 505684 kB
Node 0 KernelStack: 64248 kB
Node 0 PageTables: 56056 kB
Node 0 NFS_Unstable: 0 kB
Node 0 Bounce: 0 kB
Node 0 WritebackTmp: 0 kB
Node 0 Slab: 6558764 kB
Node 0 SReclaimable: 5381668 kB
Node 0 SUnreclaim: 1177096 kB
Node 0 AnonHugePages: 6512640 kB
Node 0 ShmemHugePages: 0 kB
Node 0 ShmemPmdMapped: 0 kB
Node 0 HugePages_Total: 0
Node 0 HugePages_Free: 0
Node 0 HugePages_Surp: 0
NUMA內(nèi)存大頁(2MB大頁、1GB大頁)通過文件/sys/bus/node/devices/node*/hugepages/hugepages-2048kB/nr_hugepages查看
查到的節(jié)點NUMA信息會通過node_numa_info這個key緩存在內(nèi)存中
節(jié)點CPU、內(nèi)存使用率采集
讀取文件/proc/meminfo,使用MemTotal-MemAvailable獲取內(nèi)存的使用量
讀取文件/proc/stat,各個字段的含義:
cpu user nice system idle iowait irq softirq steal guest guest_nice
cpu 314640167 350395 240274826 13874244194 8256006 0 6051222 0 0 0
所有CPU核心使用的cpu時間總和,每列對應(yīng)的是用戶空間占用的時間、低優(yōu)先級進程用戶空間占用的時間、內(nèi)核占用使用、空間時間、等待 I/O 操作的時間、處理硬中斷時間、軟中斷處理時間
通過累加user + nice + system + irq + softirq時間總和,對比兩個時間間隔的時間總和計算利用率
(currentCPUTotal - lastCPUTotal) / ((currentTimeStamp - lastTimeStamp)/10ms)
節(jié)點Pod的CPU、內(nèi)存使用率采集
通過cgroup文件cpuacct.usage獲取Pod使用 CPU 的 總時間(單位:納秒 ns),通過和上次的使用時間計算得到cpu使用時間
cpuUsageValue := float64(currentCPUUsage-lastCPUStat.CPUUsage) / float64(collectTime.Sub(lastCPUStat.Timestamp))
通過cgroup文件memory.stat獲取Pod的內(nèi)存使用量,這里采用的是累加下面三項內(nèi)存:
total_inactive_anon + total_active_anon + total_unevictable
采用相同的方式可以計算得到Pod每個容器的cpu和內(nèi)存使用
Informer
NodeInformer
通過fieldselector=metadata.name={1.1.1.1}來list+watch當前節(jié)點信息,緩存Node信息,監(jiān)聽到Node的metadata更新后,更新緩存的Node信息
然后再通知RegisterTypeNodeMetadata類型的callback
PodInformer
因為會直接調(diào)用kubelet的接口,需要先等待NodeInformer同步完成,即擁有Node的緩存信息
定時(10s)通過調(diào)用所在節(jié)點的kubelet的接口https://1.1.1.1:10250/pods獲取所在節(jié)點上的所有Pod信息,并緩存
除了定時刷新之外,還會啟動PLEG監(jiān)聽所在主機上的這些目錄,分別對應(yīng)k8s三種QOS類型Pod所在的目錄
/sys/fs/cgroup/cpu/kubepods.slice/
/sys/fs/cgroup/cpu/kubepods.slice/kubepods-besteffort.slice/
/sys/fs/cgroup/cpu/kubepods.slice/kubepods-burstable.slice/
PLEG會監(jiān)聽這些目錄下的目錄的創(chuàng)建和刪除,其中目錄的創(chuàng)建對應(yīng)有Pod創(chuàng)建,目錄的刪除對應(yīng)有Pod刪除
監(jiān)聽到Pod的創(chuàng)建和刪除之后,會立即觸發(fā)一次刷新Pod信息緩存
每次緩存刷新后會通知RegisterTypeAllPods類型的callback
NodeSLOInformer
通過fieldselector=metadata.name={1.1.1.1}來list+watch當前節(jié)點的NodeSLO信息,緩存NodeSLO信息
監(jiān)聽到NodeSLO的spec配置更新后,更新緩存的NodeSLO信息
然后再通知RegisterTypeNodeSLOSpec類型的callback
NodeMetricInformer
通過fieldselector=metadata.name={1.1.1.1}來list+watch當前節(jié)點的NodeMetric信息,緩存NodeMetric信息
監(jiān)聽到NodeMetric的spec配置更新后,更新緩存的NodeMetric信息
NodeMetric信息更新完成后,還會啟動協(xié)程定時更新NodeMetric的Status,依賴NodeMetric的spec中配置的監(jiān)聽相關(guān)的配置
會讀取上面采集到的節(jié)點的cpu、內(nèi)存使用以及Pod的cpu、內(nèi)存使用更新到NodeMetric的Status字段里
NodeResourceTopologyInformer
通過fieldselector=metadata.name={1.1.1.1}來list+watch當前節(jié)點的NodeResourceTopology信息
如果NodeResourceTopology不存在,會自動創(chuàng)建一份NodeResourceTopology
然后根據(jù)節(jié)點的CPU拓撲信息更新NodeResourceTopology,每個NUMA節(jié)點會作為一個區(qū)域,統(tǒng)計這個區(qū)域的所有CPU數(shù)量總和,內(nèi)存數(shù)量總和
- CPU數(shù)量總和:即這個NUMA節(jié)點關(guān)聯(lián)的所有cpu核心,一般就是一個物理SOCKET上的所有cpu核心及其超線程
- 內(nèi)存數(shù)量總和:即這個NUMA節(jié)點關(guān)聯(lián)的內(nèi)存大小,如果啟用了hugepage,則還需要排除這部分大小
Callback
所有類型的callback都注冊了同一個回調(diào)函數(shù),用于更新插件規(guī)則
會維護一個全局的插件規(guī)則,每次回調(diào)時候會根據(jù)規(guī)則的適用類型來選擇是否調(diào)用規(guī)則函數(shù)進行更新
如果發(fā)生了更新,那么繼續(xù)調(diào)用規(guī)則的更新回調(diào)函數(shù)
規(guī)則
內(nèi)置了10種插件,每個插件都有自己的規(guī)則
CPU歸一化插件
僅針對RegisterTypeNodeMetadata類型的callback,即只有在Node的metadata變化后才會執(zhí)行的插件
這是因為CPU歸一化插件主要依賴的是Node上的配置的歸一化比例注解,必須是大于1的比例
這個插件會動態(tài)獲取所在節(jié)點Pod的Limit數(shù)據(jù),轉(zhuǎn)換為cfs_quota,cfs_quota = limit/1000 * 100000,即pod的cpu核數(shù)*100ms
然后讀取Node的歸一化比例,計算得到新的cfs_quota = old_cfs_quota / rate,由于rate是大于1的,因此新的cfs_quota會變小
修改僅針對低QoS的Pod生效,包括LS和None兩種低QoS的Pod,對應(yīng)的K8s的QoS包括besteffort和burstable兩種QoS(這兩種K8s的原生QoS對應(yīng)的是besteffort沒有設(shè)置request和limit,burstable只設(shè)置了request和limit的一種或者request不等于limit)
Reconciler
他也注冊了RegisterTypeAllPods類型的callback,在Pod同步完成后也會收到回調(diào),收到回調(diào)后他會對節(jié)點上的所有Pod進行必要的更新
更新的方式也是來自內(nèi)置的10種插件注冊的更新函數(shù)
reconciler.RegisterCgroupReconciler(reconciler.PodLevel, sysutil.CPUCFSQuota, description+" (pod cfs quota)",
p.AdjustPodCFSQuota, reconciler.PodQOSFilter(), podQOSConditions...)
reconciler.RegisterCgroupReconciler(reconciler.ContainerLevel, sysutil.CPUCFSQuota, description+" (container cfs quota)",
p.AdjustContainerCFSQuota, reconciler.PodQOSFilter(), podQOSConditions...)
每種插件都會注冊自己關(guān)心的回調(diào)接口
Reconciler每次收到pod同步完成的信號后,就會遍歷節(jié)點上的所有Pod,依次執(zhí)行每個插件注冊的回調(diào)函數(shù)
比如CPU歸一化插件注冊的仍然是動態(tài)修改LS和None兩種低QoS的Pod的cfs_quota
Hooks
Koordlet還會運行一個grpc服務(wù),監(jiān)聽的是unix:///var/run/koordlet/koordlet.sock
同時在部署Koordlet的時候,還可以在每個節(jié)點上以systemd的方式運行額外的一個runtime-proxy代理服務(wù)
如果需要runtime-proxy生效,則需要配置節(jié)點上kubelet啟動是的runtime為這個代理,而不是以前配置的docker或者containerd
kubelet <other options> \
--container-runtime-endpoint=unix:///vagir/run/koord-runtimeproxy/runtimeproxy.sock
代理服務(wù)會對容器相關(guān)的創(chuàng)建、更新、啟動、停止操作進行攔截
interceptor.router = map[*regexp.Regexp]func(context.Context, http.ResponseWriter, *http.Request){
regexp.MustCompile(`^/(v\d\.\d+/)?containers(/\w+)?/update$`): interceptor.HandleUpdateContainer,
regexp.MustCompile(`^/(v\d\.\d+/)?containers/create$`): interceptor.HandleCreateContainer,
regexp.MustCompile(`^/(v\d\.\d+/)?containers(/\w+)?/start$`): interceptor.HandleStartContainer,
regexp.MustCompile(`^/(v\d\.\d+/)?containers(/\w+)?/stop`): interceptor.HandleStopContainer,
}
同時這個代理服務(wù)會監(jiān)聽宿主機的目錄/etc/runtime/hookserver.d
而Koordlet的grpc服務(wù)在啟動時會往這個目錄寫入一個文件,對應(yīng)的是自己提供grpc服務(wù)的socket文件,以及需要代理進行攔截的一些階段
{
"remote-endpoint": "/var/run/koordlet/koordlet.sock",
"failure-policy": "Ignore",
"runtime-hooks": [
"PreRunPodSandbox",
"PreCreateContainer",
"PreStartContainer"
]
}
上述文件寫入后,會被runtime-proxy監(jiān)聽到
因此,當api-server創(chuàng)建容器時,kubelet會將容器的創(chuàng)建請求發(fā)送到runtime-proxy,runtime-proxy的攔截邏輯里會先調(diào)用上述10種插件注冊的回調(diào)函數(shù)
hooks.Register(rmconfig.PreRunPodSandbox, name, description+" (pod)", p.AdjustPodCFSQuota)
hooks.Register(rmconfig.PreCreateContainer, name, description+" (container)", p.AdjustContainerCFSQuota)
hooks.Register(rmconfig.PreUpdateContainerResources, name, description+" (container)", p.AdjustContainerCFSQuota)
比如CPU歸一化插件注冊的仍然是動態(tài)調(diào)整LS和None兩種低QoS的Pod的cfs_quota,針對的階段是創(chuàng)建Sandbox、創(chuàng)建容器、以及更新容器
因此runtime-proxy會依次調(diào)用這些插件注冊的回調(diào)函數(shù),完成后才將請求轉(zhuǎn)發(fā)到實際的runtime,比如docker或者containerd
QoSManager
注冊了一些定時調(diào)用的QoS策略,默認是這8種,通過FeatureGate的形式來確定是否需要啟動
StrategyPlugins = map[string]framework.QOSStrategyFactory{
blkio.BlkIOReconcileName: blkio.New,
cgreconcile.CgroupReconcileName: cgreconcile.New,
cpuburst.CPUBurstName: cpuburst.New,
cpuevict.CPUEvictName: cpuevict.New,
cpusuppress.CPUSuppressName: cpusuppress.New,
memoryevict.MemoryEvictName: memoryevict.New,
resctrl.ResctrlReconcileName: resctrl.New,
sysreconcile.SystemConfigReconcileName: sysreconcile.New,
}
以cpusuppress為例,他會動態(tài)調(diào)整BE這種QoS的Pod的cpuset,即綁定的cpu核心
他會讀取/sys/fs/cgroup/cpu/kubepods.slice/kubepods-besteffort.slice/目錄下的所有Pod,默認情況下BE這種QoS會綁定節(jié)點上的所有CPU核心
在節(jié)點負載較高時,他會動態(tài)調(diào)整綁定的CPU核心數(shù)量,進一步壓縮BE這種QoS的Pod的運行