docker技術(shù)剖析--docker資源限制及應(yīng)用總結(jié)

2018-06-08 18:11 更新

防偽碼:有花堪折直須折,莫待無花空折枝。

一、本文將介紹 cgroup 如何做到內(nèi)存,cpu 和 io 速率的隔離

本文用腳本運行示例進程,來驗證 Cgroups 關(guān)于 cpu、內(nèi)存、io 這三部分的隔離效果。

測試機器環(huán)境(docker 1.12版本)

啟動 Cgroups

systemctl enable cgconfig.service

systemctl start cgconfig.service

執(zhí)行 mount 命令查看 cgroup 的掛載點

從上圖可以看到 cgroup 掛載在/sys/fs/cgroup 目錄

groups 可以限制 blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、ns 等系

統(tǒng)的資源,以下是主要子系統(tǒng)的說明:

blkio 這個子系統(tǒng)設(shè)置限制每個塊設(shè)備的輸入輸出控制。例如:磁盤,光盤以及 usb 等等。

cpu 這個子系統(tǒng)使用調(diào)度程序為 cgroup 任務(wù)提供 cpu 的訪問。

cpuacct 產(chǎn)生 cgroup 任務(wù)的 cpu 資源報告。

cpuset 如果是多核心的 cpu,這個子系統(tǒng)會為 cgroup 任務(wù)分配單獨的 cpu 和內(nèi)存。

devices 允許或拒絕 cgroup 任務(wù)對設(shè)備的訪問。

freezer 暫停和恢復(fù) cgroup 任務(wù)。

memory 設(shè)置每個 cgroup 的內(nèi)存限制以及產(chǎn)生內(nèi)存資源報告。

net_cls 標(biāo)記每個網(wǎng)絡(luò)包以供 cgroup 方便使用,它通過使用等級識別符(classid)標(biāo)記網(wǎng)絡(luò)數(shù)

據(jù)包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體 cgroup 中生成

的數(shù)據(jù)包。

ns:命名空間子系統(tǒng)

cgroups 管理進程 cpu 資源

我們先看一個限制 cpu 資源的例子:

跑一個耗 cpu 的腳本

運行一個容器,在容器內(nèi)創(chuàng)建腳本并運行腳本,腳本內(nèi)容:

將容器切換到后臺運行

ctrl + p && ctrl+q

在宿主機上 top 可以看到這個腳本基本占了 90%多的 cpu 資源

下面用 cgroups 控制這個進程的 cpu 資源

對于 centos7 來說,通過 systemd-cgls 來查看系統(tǒng) cgroups tree:

#systemd-cgls

注:5028 就是我們所運行的容器 pid

將 cpu.cfs_quota_us 設(shè)為 50000,相對于 cpu.cfs_period_us 的 100000 是 50%

進入容器,再次執(zhí)行腳本,打開宿主機的另一個終端執(zhí)行 top 命令

然后 top 的實時統(tǒng)計數(shù)據(jù)如下,cpu 占用率將近 50%,看來 cgroups 關(guān)于 cpu 的控制起了效果

CPU 資源控制

CPU 資源的控制也有兩種策略,一種是完全公平調(diào)度 (CFS:Completely Fair Scheduler)

策略,提供了限額和按比例分配兩種方式進行資源控制;另一種是實時調(diào)度(Real-Time

Scheduler)策略,針對實時進程按周期分配固定的運行時間。配置時間都以微秒(μs)為

單位,文件名中用 us 表示。

CFS  調(diào)度策略下的配置

按權(quán)重比例設(shè)定 CPU 的分配

docker 提供了–cpu-shares 參數(shù),在創(chuàng)建容器時指定容器所使用的 CPU 份額值。例如:

使用命令 docker run -tid –cpu-shares 100 鏡像,創(chuàng)建容器,則最終生成的 cgroup 的 cpu 份額


配置可以下面的文件中找到:

# cat /sys/fs/cgroup/cpu/docker-<容器的完整長 ID>/cpu.shares

cpu-shares 的值不能保證可以獲得 1 個 vcpu 或者多少 GHz 的 CPU 資源,僅僅只是一個加權(quán)

值。

該加權(quán)值是一個整數(shù)(必須大于等于 2)表示相對權(quán)重,最后除以權(quán)重總和算出相對比例,

按比例分配 CPU 時間。

默認情況下,每個 docker 容器的 cpu 份額都是 1024。單獨一個容器的份額是沒有意義的,

只有在同時運行多個容器時,容器的 cpu 加權(quán)的效果才能體現(xiàn)出來。例如,兩個容器 A、B

的 cpu 份額分別為 1000 和 500,在 cpu 進行時間片分配的時候,容器 A 比容器 B 多一倍的

機會獲得 CPU 的時間片。如果容器 A 的進程一直是空閑的,那么容器 B 是可以獲取比容器

A 更多的 CPU 時間片的。極端情況下,比如說主機上只運行了一個容器,即使它的 cpu 份額

只有 50,它也可以獨占整個主機的 cpu 資源。

cgroups 只在容器分配的資源緊缺時,也就是說在需要對容器使用的資源進行限制時,才會

生效。因此,無法單純根據(jù)某個容器的 cpu 份額來確定有多少 cpu 資源分配給它,資源分配

結(jié)果取決于同時運行的其他容器的 cpu 分配和容器中進程運行情況。


cpu-shares 演示案例:

先刪除 docker 主機上運行的容器

Docker 通過--cpu-shares 指定 CPU 份額

運行一個容器指定 cpu 份額為 1024

注:

--cpu-shares 指定 CPU 份額,默認就是 1024

--cpuset-cpus 可以綁定 CPU。例如,指定容器在--cpuset-cpus 0,1 或--cpuset-cpus 0-3

--cpu 是 stress 命令的選項表示產(chǎn)生 n 個進程 每個進程都反復(fù)不停的計算隨機數(shù)的平方根

stress 命令是 linux 下的一個壓力測試工具。

在 docker 宿主機上打開一個 terminal 執(zhí)行 top

然后再啟動一個容器, --cpu-shares 為 512 。

查看 top 的顯示結(jié)果

可以看到 container1 的 CPU 占比為 1024/(1024+512)=2/3,container2 的 CPU 占比為

512/(1024+512)=1/3

將 container1 的 cpu.shares 改為 512,

#echo “512” > /sys/fs/cgroup/cpu/docker-<容器的完整長 ID>/cpu.shares

可以看到兩個容器的 CPU 占比趨于平均

設(shè)定 CPU 使用周期使用時間上限

cgroups 里,可以用 cpu.cfs_period_us 和 cpu.cfs_quota_us 來限制該組中的所有進程在單

位時間里可以使用的 cpu 時間。cpu.cfs_period_us 就是時間周期,默認為 100000,即百毫

秒。cpu.cfs_quota_us 就是在這期間內(nèi)可使用的 cpu 時間,默認 -1,即無限制。

cpu.cfs_period_us:設(shè)定時間周期(單位為微秒(μs)),必須與 cfs_quota_us 配合使用。

cpu.cfs_quota_us :設(shè)定周期內(nèi)最多可使用的時間(單位為微秒(μs))。這里的配置指 task

對單個 cpu 的使用上限。

舉個例子,如果容器進程需要每 1 秒使用單個 CPU 的 0.2 秒時間,可以將 cpu-period 設(shè)置為

1000000(即 1 秒),cpu-quota 設(shè)置為 200000(0.2 秒)。

當(dāng)然,在多核情況下,若 cfs_quota_us 是 cfs_period_us 的兩倍,就表示在兩個核上

完全使用 CPU,例如如果允許容器進程需要完全占用兩個 CPU,則可以將 cpu-period 設(shè)置為

100000(即 0.1 秒),cpu-quota 設(shè)置為 200000(0.2 秒)。

使用示例:

使用命令 docker run 創(chuàng)建容器

在宿主機上執(zhí)行 top

從上圖可以看到基本占了 100%的 cpu 資源

則最終生成的 cgroup 的 cpu 周期配置可以下面的目錄中找到:

/sys/fs/cgroup/cpu/docker-<容器的完整長 ID>/

修改容器的 cpu.cfs_period_us 和 cpu.cfs_quota_us 值

執(zhí)行 top 查看 cpu 資源

從上圖可以看到基本占了 50%的 cpu 資源


RT  調(diào)度策略下的配置 實時調(diào)度策略與公平調(diào)度策略中的按周期分配時間的方法類似,也是

在周期內(nèi)分配一個固定的運行時間。

cpu.rt_period_us :設(shè)定周期時間。

cpu.rt_runtime_us:設(shè)定周期中的運行時間。


cpuset - CPU 綁定

對多核 CPU 的服務(wù)器,docker 還可以控制容器運行限定使用哪些 cpu 內(nèi)核和內(nèi)存節(jié)點,即

使用–cpuset-cpus 和–cpuset-mems 參數(shù)。對具有 NUMA 拓撲(具有多 CPU、多內(nèi)存節(jié)點)的

服務(wù)器尤其有用,可以對需要高性能計算的容器進行性能最優(yōu)的配置。如果服務(wù)器只有一個

內(nèi)存節(jié)點,則–cpuset-mems 的配置基本上不會有明顯效果

注:

現(xiàn)在的機器上都是有多個 CPU 和多個內(nèi)存塊的。以前我們都是將內(nèi)存塊看成是一大塊內(nèi)存,

所有 CPU 到這個共享內(nèi)存的訪問消息是一樣的。但是隨著處理器的增加,共享內(nèi)存可能會

導(dǎo)致內(nèi)存訪問沖突越來越厲害,且如果內(nèi)存訪問達到瓶頸的時候,性能就不能隨之增加。

NUMA(Non-Uniform Memory Access)就是這樣的環(huán)境下引入的一個模型。比如一臺機

器是有2個處理器,有4個內(nèi)存塊。我們將1個處理器和兩個內(nèi)存塊合起來,稱為一個NUMA


node,這樣這個機器就會有兩個 NUMA node。在物理分布上,NUMA node 的處理器和內(nèi)

存塊的物理距離更小,因此訪問也更快。比如這臺機器會分左右兩個處理器(cpu1, cpu2),

在每個處理器兩邊放兩個內(nèi)存塊(memory1.1, memory1.2, memory2.1,memory2.2),這樣

NUMA node1 的 cpu1 訪問 memory1.1 和 memory1.2 就比訪問 memory2.1 和 memory2.2

更快。所以使用 NUMA 的模式如果能盡量保證本 node 內(nèi)的 CPU 只訪問本 node 內(nèi)的內(nèi)存

塊,那這樣的效率就是最高的。

使用示例:

表示創(chuàng)建的容器只能用 0、1、2 這三個內(nèi)核。最終生成的 cgroup 的 cpu 內(nèi)核配置如下:

cpuset.cpus:在這個文件中填寫 cgroup 可使用的 CPU 編號,如 0-2,16 代表 0、1、2 和 16

這 4 個 CPU。

cpuset.mems:與 CPU 類似,表示 cgroup 可使用的 memory node,格式同上

通過 docker exec <容器 ID> taskset -c -p 1(容器內(nèi)部第一個進程編號一般為 1),可以看到容器

中進程與 CPU 內(nèi)核的綁定關(guān)系,可以認為達到了綁定 CPU 內(nèi)核的目的。

總結(jié):

CPU 配額控制參數(shù)的混合使用

當(dāng)上面這些參數(shù)中時,cpu-shares 控制只發(fā)生在容器競爭同一個內(nèi)核的時間片時,如果通過

cpuset-cpus 指定容器 A 使用內(nèi)核 0,容器 B 只是用內(nèi)核 1,在主機上只有這兩個容器使用對

應(yīng)內(nèi)核的情況,它們各自占用全部的內(nèi)核資源,cpu-shares 沒有明顯效果。

cpu-period、cpu-quota 這兩個參數(shù)一般聯(lián)合使用,在單核情況或者通過 cpuset-cpus 強制容

器使用一個 cpu 內(nèi)核的情況下,即使 cpu-quota 超過 cpu-period,也不會使容器使用更多的

CPU 資源。

cpuset-cpus、cpuset-mems 只在多核、多內(nèi)存節(jié)點上的服務(wù)器上有效,并且必須與實際的物

理配置匹配,否則也無法達到資源控制的目的。

在系統(tǒng)具有多個 CPU 內(nèi)核的情況下,需要通過 cpuset-cpus 為容器 CPU 內(nèi)核才能比較方便地進行測試。


內(nèi)存配額控制

和 CPU 控制一樣,docker 也提供了若干參數(shù)來控制容器的內(nèi)存使用配額,可以控制容器的

swap 大小、可用內(nèi)存大小等各種內(nèi)存方面的控制。主要有以下參數(shù):

Docker 提供參數(shù)-m, --memory=""限制容器的內(nèi)存使用量,如果不設(shè)置-m,則默認容器內(nèi)存

是不設(shè)限的,容器可以使用主機上的所有空閑內(nèi)存

內(nèi)存配額控制使用示例

設(shè)置容器的內(nèi)存上限,參考命令如下所示

#docker run -dit --memory 128m 鏡像

默認情況下,除了–memory 指定的內(nèi)存大小以外,docker 還為容器分配了同樣大小的 swap

分區(qū),也就是說,上面的命令創(chuàng)建出的容器實際上最多可以使用 256MB 內(nèi)存,而不是 128MB內(nèi)存。如果需要自定義 swap 分區(qū)大小,則可以通過聯(lián)合使用–memory–swap 參數(shù)來實現(xiàn)控制。

可以發(fā)現(xiàn),使用 256MB 進行壓力測試時,由于超過了內(nèi)存上限(128MB 內(nèi)存+128MB swap),進程被 OOM(out of memory)殺死。

使用 250MB 進行壓力測試時,進程可以正常運行。

通過 docker stats 可以查看到容器的內(nèi)存已經(jīng)滿負載了。

#docker stats test2

對上面的命令創(chuàng)建的容器,可以查看到在 cgroups 的配置文件中,查看到容器的內(nèi)存大小為

128MB (128×1024×1024=134217728B), 內(nèi) 存 和 swap 加 起 來 大 小 為 256MB

(256×1024×1024=268435456B)。

#cat /sys/fs/cgroup/memory/docker-<容器的完整 ID>/memory.limit_in_bytes

134217728

#cat  /sys/fs/cgroup/memory/docker-< 容 器 的 完 整ID>/memory.memsw.limit_in_bytes

268435456

盤 磁盤 IO  配額控制

主要包括以下參數(shù):

--device-read-bps:限制此設(shè)備上的讀速度(bytes per second),單位可以是 kb、mb 或者 gb。

--device-read-iops:通過每秒讀 IO 次數(shù)來限制指定設(shè)備的讀速度。

--device-write-bps :限制此設(shè)備上的寫速度(bytes per second),單位可以是 kb、mb 或者 gb。

--device-write-iops:通過每秒寫 IO 次數(shù)來限制指定設(shè)備的寫速度。

--blkio-weight:容器默認磁盤 IO 的加權(quán)值,有效值范圍為 10-1000。

--blkio-weight-device: 針對特定設(shè)備的 IO 加權(quán)控制。其格式為 DEVICE_NAME:WEIGHT

磁盤 IO 配額控制示例

blkio-weight

使用下面的命令創(chuàng)建兩個–blkio-weight 值不同的容器:

在容器中同時執(zhí)行下面的 dd 命令,進行測試

注:oflag=direct 規(guī)避掉文件系統(tǒng)的 cache,把寫請求直接封裝成 io 指令發(fā)到硬盤


二、學(xué)習(xí) Docker 也有一段時間了,了解了 Docker 的基本實現(xiàn)原理,也知道了 Docker 的使

用方法,這里對 Docker 的一些典型應(yīng)用場景做一個總結(jié)

1、配置簡化

這是 Docker 的主要使用場景。將應(yīng)用的所有配置工作寫入 Dockerfile 中,創(chuàng)建好鏡像,

以后就可以無限次使用這個鏡像進行應(yīng)用部署了。這大大簡化了應(yīng)用的部署,不需要為每次

部署都進行繁瑣的配置工作,實現(xiàn)了一次打包,多次部署。這大大加快了應(yīng)用的開發(fā)效率,

使得程序員可以快速搭建起開發(fā)測試環(huán)境,不用關(guān)注繁瑣的配置工作,而是將所有精力都盡

可能用到開發(fā)工作中去。

2、代碼流水線管理

代碼從開發(fā)環(huán)境到測試環(huán)境再到生產(chǎn)環(huán)境,需要經(jīng)過很多次中間環(huán)節(jié),Docker 給應(yīng)用

提供了一個從開發(fā)到上線均一致的環(huán)境,開發(fā)測試人員均只需關(guān)注應(yīng)用的代碼,使得代碼的

流水線變得非常簡單,這樣應(yīng)用才能持續(xù)集成和發(fā)布。

3、快速部署

在虛擬機之前,引入新的硬件資源需要消耗幾天的時間。Docker 的虛擬化技術(shù)將這個

時間降到了幾分鐘,Docker 只是創(chuàng)建一個容器進程而無需啟動操作系統(tǒng),這個過程只需要

秒級的時間。

4、應(yīng)用隔離

資源隔離對于提供共享 hosting 服務(wù)的公司是個強需求。 如果使用 VM,雖然隔離性非

常徹底,但部署密度相對較低,會造成成本增加。

Docker 容器充分利用 linux 內(nèi)核的 namespace 提供資源隔離功能。結(jié)合 cgroups,可以方便

的設(shè)置每個容器的資源配額。既能滿足資源隔離的需求,又能方便的為不同級別的用戶設(shè)置

不同級別的配額限制。

5、服務(wù)器資源整合

正如通過 VM 來整合多個應(yīng)用,Docker 隔離應(yīng)用的能力使得 Docker 同樣可以整合服務(wù)

器資源。由于沒有額外的操作系統(tǒng)的內(nèi)存占用,以及能在多個實例之間共享沒有使用的內(nèi)存,

Docker 可以比 VM 提供更好的服務(wù)器整合解決方案。

通常數(shù)據(jù)中心的資源利用率只有 30%,通過使用 Docker 并進行有效的資源分配可以提高資

源的利用率。

6、多版本混合部署

隨著產(chǎn)品的不斷更新?lián)Q代,一臺服務(wù)器上部署多個應(yīng)用或者同一個應(yīng)用的多個版本在企

業(yè)內(nèi)部非常常見。但一臺服務(wù)器上部署同一個軟件的多個版本,文件路徑、端口等資源往往

會發(fā)生沖突,造成多個版本無法共存的問題。

如果用 docker,這個問題將非常簡單。由于每個容器都有自己獨立的文件系統(tǒng),所以根本不

存在文件路徑?jīng)_突的問題; 對于端口沖突問題,只需要在啟動容器時指定不同的端口映射即

可解決問題。

7、版本升級回滾

一次升級,往往不僅僅是應(yīng)用軟件本身的升級,通過還會包含依賴項的升級。 但新舊

軟件的依賴項很可能是不同的,甚至是有沖突的,所以在傳統(tǒng)的環(huán)境下做回滾一般比較困難。

如果使用 docker,我們只需要每次應(yīng)用軟件升級時制作一個新的 docker 鏡像,升級時先停

掉舊的容器, 然后把新的容器啟動。 需要回滾時,把新的容器停掉,舊的啟動即可完成回

滾,整個過程各在秒級完成,非常方便。

8、內(nèi)部開發(fā)環(huán)境

在容器技術(shù)出現(xiàn)之前,公司往往是通過為每個開發(fā)人員提供一臺或者多臺虛擬機來充當(dāng)

開發(fā)測試環(huán)境。開發(fā)測試環(huán)境一般負載較低,大量的系統(tǒng)資源都被浪費在虛擬機本身的進程

上了。

Docker容器沒有任何CPU和內(nèi)存上的額外開銷,很適合用來提供公司內(nèi)部的開發(fā)測試環(huán)境。

而且由于 Docker 鏡像可以很方便的在公司內(nèi)部共享,這對開發(fā)環(huán)境的規(guī)范性也有極大的幫

助。

9、PaaS

使用 Docker 搭建大規(guī)模集群,提供 PaaS。這一應(yīng)用是最有前景的一個了,目前已有很

多創(chuàng)業(yè)公司在使用 Docker 做 PaaS 了,例如云雀云平臺。用戶只需提交代碼,所有運維工作

均由服務(wù)公司來做。而且對用戶來說,整個應(yīng)用部署上線是一鍵式的,非常方便。

10、云桌面

在每一個容器內(nèi)部運行一個圖形化桌面,用戶通過 RDP 或者 VNC 協(xié)議連接到容器。該

方案所提供的虛擬桌面相比于傳統(tǒng)的基于硬件虛擬化的桌面方案更輕量級,運行速率大大提

升。不過該方案仍處于實驗階段,不知是否可行??梢詤⒖家幌?Docker-desktop 方案。


謝謝觀看,真心的希望能夠幫到您!

本文出自 “一盞燭光” 博客,謝絕轉(zhuǎn)載!

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號