Koordlet節(jié)點代理

資源采集

節(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的運行

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容