講解如何從內(nèi)核角度理解K8s CPU限流的原理
在使用?Kubernetes(簡稱K8s)
?時(shí),通常會(huì)在同一臺(tái)機(jī)器上部署多個(gè) Pod。如果某個(gè) Pod 中的服務(wù)出現(xiàn)問題(如出現(xiàn)死循環(huán)),將會(huì)導(dǎo)致占用大量的 CPU 時(shí)間,從而影響到其他 Pod 的正常運(yùn)行。
為了解決這個(gè)問題,K8s 提供了一個(gè)限制 Pod 使用 CPU 資源的配置項(xiàng),如下所示:
resources:
?limits:
? ?cpu: "0.5"
上述配置限制了 Pod 只能使用 0.5 個(gè) CPU 資源。
K8s 通過使用 Linux 資源控制組(cgroup)中的?CPU子系統(tǒng)
?來限制 Pod 對 CPU 資源的使用。
下面我們來分析一下 Linux 內(nèi)核是如何限制進(jìn)程對 CPU 資源的使用。
CPU限流原理
如果讓我們來設(shè)計(jì)一個(gè)限制進(jìn)程對 CPU 資源使用的算法(如限制進(jìn)程 A 只能使用 10% 的 CPU 運(yùn)行時(shí)間),應(yīng)該如何實(shí)現(xiàn)呢?
最簡單的方法是,將 CPU 的運(yùn)行時(shí)間劃分成一個(gè)個(gè)時(shí)間片段(稱為?周期(period)
,如 100 毫秒)。由于進(jìn)程 A 被限制為只能使用 10% 的 CPU 運(yùn)行時(shí)間,所以在一個(gè)周期內(nèi),進(jìn)程 A 只能獲得 10 毫秒的運(yùn)行時(shí)間。當(dāng)進(jìn)程 A 在一個(gè)周期內(nèi)運(yùn)行超過 10 毫秒后,將會(huì)被內(nèi)核移除可運(yùn)行隊(duì)列。那么在當(dāng)前周期內(nèi),進(jìn)程 A 將不會(huì)被調(diào)度,從而實(shí)現(xiàn)?CPU限流
?的目的。
當(dāng)一個(gè)新的周期開始時(shí),進(jìn)程 A 將會(huì)重新獲得 10% 的 CPU 運(yùn)行時(shí)間。如下圖所示:

進(jìn)程在一個(gè)周期內(nèi)能運(yùn)行的時(shí)間被稱為?配額(quota)
。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??


CPU限流實(shí)現(xiàn)
內(nèi)核以?進(jìn)程組
?作為資源控制的主體,進(jìn)程組使用?task_group
?結(jié)構(gòu)體來描述,其定義如下:
struct task_group {
? ?...
? ?// 可運(yùn)行隊(duì)列,每個(gè)CPU一個(gè)
? ?struct cfs_rq **cfs_rq;
? ?...
? ?// 用于限制進(jìn)程組對CPU資源的使用
? ?struct cfs_bandwidth cfs_bandwidth;
};
可以看出,每個(gè)進(jìn)程組都有一個(gè)可運(yùn)行隊(duì)列,可運(yùn)行隊(duì)列中包含了進(jìn)程組中所有可以被調(diào)度的進(jìn)程。
在多核環(huán)境下,每個(gè) CPU 都有一個(gè)可運(yùn)行隊(duì)列,主要為了解決資源競爭問題。
另外,task_group
?結(jié)構(gòu)體中還有個(gè)類型為?cfs_bandwidth
?的字段,用于控制進(jìn)程組對 CPU 資源的使用。cfs_bandwidth
?結(jié)構(gòu)體的定義如下:
struct cfs_bandwidth {
? ?...
? ?ktime_t period;
? ?u64 quota;
? ?u64 runtime;
? ?...
? ?struct hrtimer period_timer;
};
下面介紹一下?cfs_bandwidth
?結(jié)構(gòu)體各個(gè)字段的作用:
period
:就是上面介紹的周期,用戶可以通過修改 cgroup 的?cpu.cfs_period_us
?文件進(jìn)行設(shè)置。quota
:進(jìn)程組在周期內(nèi)能夠運(yùn)行的時(shí)間,用戶可以通過修改 cgroup 的?cpu.cfs_quota_us
?文件進(jìn)行設(shè)置。runtime
:進(jìn)程組在周期內(nèi)剩余的可運(yùn)行時(shí)間。period_timer
:定時(shí)器,每隔一個(gè)周期執(zhí)行一次,主要用于更新?runtime
?字段的值。
runtime
?字段用于保存進(jìn)程組在當(dāng)前周期內(nèi)剩余的可運(yùn)行時(shí)間,如果調(diào)度器選中了進(jìn)程組中某個(gè)進(jìn)程進(jìn)行運(yùn)行時(shí),將會(huì)減少進(jìn)程組的剩余可運(yùn)行時(shí)間。如下圖所示:
在上圖中,進(jìn)程組 A 中的進(jìn)程 D 被調(diào)度器選中運(yùn)行。如果進(jìn)程組 A 原來的可運(yùn)行時(shí)間為 50 毫秒,而進(jìn)程 D 運(yùn)行了 10 毫秒。那么,進(jìn)程組 A 的可運(yùn)行時(shí)間將從會(huì)減少 10 毫秒,從而變?yōu)?40 毫秒。
在一個(gè)周期內(nèi),當(dāng)進(jìn)程組的可運(yùn)行時(shí)間變?yōu)?0 時(shí),那么此進(jìn)程組將會(huì)被限制運(yùn)行(稱為 CPU Throttling),直到下一個(gè)周期開始。
當(dāng)進(jìn)程組被限制運(yùn)行時(shí),進(jìn)程組內(nèi)的所有進(jìn)程都不能被執(zhí)行。
當(dāng)進(jìn)程組開啟了 CPU 限流功能時(shí)(也就是設(shè)置了?period
?和?quota
?的值),內(nèi)核將會(huì)為其啟動(dòng)一個(gè)定時(shí)器。定時(shí)器每隔一個(gè)周期觸發(fā)一次,用于更新進(jìn)程組的可運(yùn)行時(shí)間,從而解除進(jìn)程組被限制運(yùn)行的情況。
定時(shí)器通過調(diào)用?__refill_cfs_bandwidth_runtime()
?函數(shù)來更新進(jìn)程組的可運(yùn)行時(shí)間,其代碼如下:
void __refill_cfs_bandwidth_runtime(struct cfs_bandwidth *cfs_b)
{
? ?...
? ?cfs_b->runtime = cfs_b->quota;
? ?...
}
上面的代碼將?quota
?字段的值賦給了?runtime
?字段,所以進(jìn)程組重新獲得了可運(yùn)行時(shí)間,從而解除被限制運(yùn)行的狀態(tài)。
原文作者:Linux內(nèi)核那些事
