Docker(linux container)所依賴的底層技術
1 Namespace
用來做容器的隔離,有了namespace,在docker container裏頭看來,就是一個完整的linux的世界。在host看來,container裏的進程,就是一個普通的host進程,namespace提供這種pid的映射和隔離效果,host承載着container,就好比一個世外桃源。
namespace包括:pid namespace、net namespace、ipc namespace、mnt namespace、uts
2 Cgroups
在前面了解了Docker背後使用的資源隔離技術namespace,通過系統調用構建一個相對隔離的shell環境,也可以稱之爲一個簡單的“容器”。下面我們則要開始講解另一個強大的內核工具——cgroups。他不僅可以限制被namespace隔離起來的資源,還可以爲資源設置權重、計算使用量、操控進程啓停等等。所以cgroups(Control groups)實現了對資源的配額和度量。
cgroups是什麼?
cgroups(Control Groups)最初叫Process Container,由Google工程師(Paul Menage和Rohit Seth)於2006年提出,後來因爲Container有多重含義容易引起誤解,就在2007年更名爲Control Groups,並被整合進Linux內核。顧名思義就是把進程放到一個組裏面統一加以控制
groups的作用
通俗的來說,cgroups可以限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。Cgroups提供了以下四大功能。
1)資源限制(Resource Limitation):cgroups可以對進程組使用的資源總額進行限制。如設定應用運行時使用內存的上限,一旦超過這個配額就發出OOM(Out of Memory)。
2)優先級分配(Prioritization):通過分配的CPU時間片數量及硬盤IO帶寬大小,實際上就相當於控制了進程運行的優先級。
3)資源統計(Accounting): cgroups可以統計系統的資源使用量,如CPU使用時長、內存用量等等,這個功能非常適用於計費。
4)進程控制(Control):cgroups可以對進程組執行掛起、恢復等操作。
下面就介紹cgroup如何做到內存,cpu和io速率的隔離
本文用腳本運行示例進程,來驗證Cgroups關於cpu、內存、io這三部分的隔離效果。
1)測試機器環境
2)執行mount命令查看cgroup的掛載點
從上圖可以看到cgroup掛載在/sys/fs/cgroup目錄
groups可以限制blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、ns等系統的資源,以下是主要子系統的說明:
blkio 這個子系統設置限制每個塊設備的輸入輸出控制。例如:磁盤,光盤以及usb等等。
cpu 這個子系統使用調度程序爲cgroup任務提供cpu的訪問。
cpuacct 產生cgroup任務的cpu資源報告。
cpuset 如果是多核心的cpu,這個子系統會爲cgroup任務分配單獨的cpu和內存。
devices 允許或拒絕cgroup任務對設備的訪問。
freezer 暫停和恢復cgroup任務。
memory 設置每個cgroup的內存限制以及產生內存資源報告。
net_cls 標記每個網絡包以供cgroup方便使用,它通過使用等級識別符(classid)標記網絡數據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體cgroup中生成的數據包。
ns:命名空間子系統
3)cgroups管理進程cpu資源
我們先看一個限制cpu資源的例子:
跑一個耗cpu的腳本
4)將容器切換到後臺運行(或重新打開一個終端)
在宿主機上top可以看到這個腳本基本佔了90%多的cpu資源
5)下面用cgroups控制這個進程的cpu資源
對於centos7來說,通過systemd-cgls來查看系統cgroups tree:
#systemd-cgls
或
注:41738就是我們所運行的容器pid
將cpu.cfs_quota_us設爲50000,相對於cpu.cfs_period_us的100000是50%
進入容器,再次執行腳本,打開宿主機的另一個終端執行top命令
然後top的實時統計數據如下,cpu佔用率將近50%,看來cgroups關於cpu的控制起了效果
CPU資源控制
CPU資源的控制也有兩種策略,
一種是完全公平調度(CFS:Completely Fair Scheduler)策略,提供了限額和按比例分配兩種方式進行資源控制;
另一種是實時調度(Real-Time Scheduler)策略,針對實時進程按週期分配固定的運行時間。配置時間都以微秒(s)爲單位,文件名中用us表示。
CFS調度策略下的配置
docker提供了–cpu-shares參數,在創建容器時指定容器所使用的CPU份額值。例如:
使用命令docker run -tid –-cpu-shares 100 鏡像,創建容器,則最終生成的cgroup的cpu份額配置可以下面的文件中找到:
# cat /sys/fs/cgroup/cpu/system.slice/docker-<容器的完整長
ID>/cpu.shares
cpu-shares的值不能保證可以獲得1個vcpu或者多少GHz的CPU資源,僅僅只是一個加權值。
該加權值是一個整數(必須大於等於2)表示相對權重,最後除以權重總和算出相對比例,按比例分配CPU時間。
默認情況下,每個docker容器的cpu份額都是1024。單獨一個容器的份額是沒有意義的,只有在同時運行多個容器時,容器的cpu加權的效果才能體現出來。例如,兩個容器A、B的cpu份額分別爲1000和500,在cpu進行時間片分配的時候,容器A比容器B多一倍的機會獲得CPU的時間片。如果容器A的進程一直是空閒的,那麼容器B是可以獲取比容器A更多的CPU時間片的。極端情況下,比如說主機上只運行了一個容器,即使它的cpu份額只有50,它也可以獨佔整個主機的cpu資源。
cgroups只在容器分配的資源緊缺時,也就是說在需要對容器使用的資源進行限制時,纔會生效。因此,無法單純根據某個容器的cpu份額來確定有多少cpu資源分配給它,資源分配結果取決於同時運行的其他容器的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命令的選項表示產生n個進程每個進程都反覆不停的計算隨機數的平方根
stress命令是linux下的一個壓力測試工具。
在docker宿主機上打開一個terminal執行top
然後再啓動一個容器,--cpu-shares爲512。
查看top的現實結果
可以看到container1的CPU佔比爲1024/(1024+512)=2/3,container2的CPU佔比爲512/(1024+512)=1/3
將container1的cpu.shares改爲512,
#echo “512” >/sys/fs/cgroup/cpu/system.slice/docker-<容器的完整長ID>/cpu.shares
可以看到兩個容器的CPU佔比趨於平均
設定CPU使用週期使用時間上限
cgroups 裏,可以用 cpu.cfs_period_us 和 cpu.cfs_quota_us 來限制該組中的所有進程在單位時間裏可以使用的 cpu 時間。cpu.cfs_period_us 就是時間週期,默認爲 100000,即百毫秒。cpu.cfs_quota_us 就是在這期間內可使用的 cpu 時間,默認 -1,即無限制。
cpu.cfs_period_us:設定時間週期(單位爲微秒(μs)),必須與cfs_quota_us配合使用。
cpu.cfs_quota_us :設定週期內最多可使用的時間(單位爲微秒(μs))。這裏的配置指task對單個cpu的使用上限。
1秒=1000毫秒
1毫秒=1000微妙
舉個例子,如果容器進程需要每1秒使用單個CPU的0.2秒時間,可以將cpu-period設置爲1000000(即1秒),cpu-quota設置爲200000(0.2秒)。
當然,在多核情況下,若cfs_quota_us是cfs_period_us的兩倍,就表示在兩個核上完全使用CPU,例如如果允許容器進程需要完全佔用兩個CPU,則可以將cpu-period設置爲100000(即0.1秒),cpu-quota設置爲200000(0.2秒)。
使用示例:
使用命令docker run創建容器
在宿主機上執行top
從上圖可以看到基本佔了100%的cpu資源
則最終生成的cgroup的cpu週期配置可以下面的目錄中找到:
/sys/fs/cgroup/cpu/system.slice/docker-<容器的完整長ID>/
修改容器的cpu.cfs_period_us 和 cpu.cfs_quota_us值
執行top查看cpu資源
從上圖可以看到基本佔了50%的cpu資源
RT調度策略下的配置 實時調度策略與公平調度策略中的按週期分配時間的方法類似,也是在週期內分配一個固定的運行時間。
cpu.rt_period_us :設定週期時間。
cpu.rt_runtime_us:設定週期中的運行時間。
cpuset - CPU綁定
對多核CPU的服務器,docker還可以控制容器運行限定使用哪些cpu內核和內存節點,即使用–cpuset-cpus和–cpuset-mems參數。對具有NUMA拓撲(具有多CPU、多內存節點)的服務器尤其有用,可以對需要高性能
計算的容器進行性能最優的配置。如果服務器只有一個內存節點,則–cpuset-mems的配置基本上不會有明顯效果
注:
現在的機器上都是有多個CPU和多個內存塊的。以前我們都是將內存塊看成是一大塊內存,所有CPU到這個共享內存的訪問消息是一樣的。但是隨着處理器的增加,共享內存可能會導致內存訪問衝突越來越厲害,且如果內存訪問達到瓶頸的時候,性能就不能隨之增加。NUMA(Non-Uniform Memory Access)就是這樣的環境下引入的一個模型。比如一臺機器是有2個處理器,有4個內存塊。我們將1個處理器和兩個內存塊合起來,稱爲一個NUMA node,這樣這個機器就會有兩個NUMA node。在物理分佈上,NUMA node的處理器和內存塊的物理距離更小,因此訪問也更快。比如這臺機器會分左右兩個處理器(cpu1, cpu2),在每個處理器兩邊放兩個內存塊(memory1.1, memory1.2, memory2.1,memory2.2),這樣NUMA node1的cpu1訪問memory1.1和memory1.2就比訪問memory2.1和memory2.2更快。所以使用NUMA的模式如果能儘量保證本node內的CPU只訪問本node內的內存塊,那這樣的效率就是最高的。
使用示例:
表示創建的容器只能用0、1、2這三個內核。最終生成的cgroup的cpu內核配置如下:
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(容器內部第一個進程編號一般爲1),可以看到容器中進程與CPU內核的綁定關係,可以認爲達到了綁定CPU內核的目的。
總結:
CPU配額控制參數的混合使用
當上面這些參數中時,cpu-shares控制只發生在容器競爭同一個內核的時間片時,如果通過cpuset-cpus指定容器A使用內核0,容器B只是用內核1,在主機上只有這兩個容器使用對應內核的情況,它們各自佔用全部的內核資源,cpu-shares沒有明顯效果。
cpu-period、cpu-quota這兩個參數一般聯合使用,在單核情況或者通過cpuset-cpus強制容器使用一個cpu內核的情況下,即使cpu-quota超過cpu-period,也不會使容器使用更多的CPU資源。
cpuset-cpus、cpuset-mems只在多核、多內存節點上的服務器上有效,並且必須與實際的物理配置匹配,否則也無法達到資源控制的目的。
在系統具有多個CPU內核的情況下,需要通過cpuset-cpus爲容器CPU內核才能比較方便地進行測試。
內存配額控制
和CPU控制一樣,docker也提供了若干參數來控制容器的內存使用配額,可以控制容器的swap大小、可用內存大小等各種內存方面的控制。主要有以下參數:
Docker提供參數-m, --memory=""限制容器的內存使用量,如果不設置-m,則默認容器內存是不設限的,容器可以使用主機上的所有空閒內存
內存配額控制使用示例
設置容器的內存上限,參考命令如下所示
#docker run -dit --memory128m 鏡像
默認情況下,除了–memory指定的內存大小以外,docker還爲容器分配了同樣大小的swap分區,也就是說,上面的命令創建出的容器實際上最多可以使用256MB內存,而不是128MB內存。如果需要自定義swap分區大小,則可以通過聯合使用–memory–swap參數來實現控制
可以發現,使用256MB進行壓力測試時,由於超過了內存上限(128MB內存+128MB swap),進程被OOM(out of memory)殺死。
使用250MB進行壓力測試時,進程可以正常運行。
通過docker stats可以查看到容器的內存已經滿負載了。
#docker stats test2
對上面的命令創建的容器,可以查看到在cgroups的配置文件中,查看到容器的內存大小爲128MB (128×1024×1024=134217728B),內存和swap加起來大小爲256MB (256×1024×1024=268435456B)。
#cat /sys/fs/cgroup/memory/system.slice/docker-<容器的完整ID>/memory.limit_in_bytes
134217728
#cat /sys/fs/cgroup/memory/system.slice/docker-<容器的完整ID>/memory.memsw.limit_in_bytes
268435456
磁盤IO配額控制
主要包括以下參數:
--device-read-bps:限制此設備上的讀速度(bytes per second),單位可以是kb、mb或者gb。--device-read-iops:通過每秒讀IO次數來限制指定設備的讀速度。
--device-write-bps :限制此設備上的寫速度(bytes per second),單位可以是kb、mb或者gb。
--device-write-iops:通過每秒寫IO次數來限制指定設備的寫速度。
--blkio-weight:容器默認磁盤IO的加權值,有效值範圍爲10-1000。
--blkio-weight-device:針對特定設備的IO加權控制。其格式爲DEVICE_NAME:WEIGHT
磁盤IO配額控制示例
blkio-weight
使用下面的命令創建兩個–blkio-weight值不同的容器:
在容器中同時執行下面的dd命令,進行測試
注:oflag=direct規避掉文件系統的cache,把寫請求直接封裝成io指令發到硬盤,而按正常規律,數據先寫入緩存,在由緩存封裝給硬盤
3 Chroot
如何在container裏頭,看到的文件系統,就是一個完整的linux系統,有/etc、/lib 等,通過chroot實現
4 Veth
container裏,執行ifconfig可以看到eth0的網卡,如何通信呢?其實是在host上虛擬了一張網卡出來(veth73f7),跟container裏的網卡做了橋接,所有從container出來的流量都要過host的虛擬網卡,進container的流量也是如此。
5 Union FS
對於這種疊加的文件系統,有一個很好的實現是AUFS,這個可以做到以文件爲粒度的copy-on-write,爲海量的container的瞬間啓動。
6 Iptables, netfilter
主要用來做ip數據包的過濾,比如可以做container之間無法通信,container可以無法訪問host的網絡,但是可以通過host的網卡訪問外網等這樣的網絡策略
二、學習Docker也有一段時間了,瞭解了Docker的基本實現原理,也知道了Docker的使用方法,這裏對Docker的一些典型應用場景做一個總結
1、配置簡化
這是Docker的主要使用場景。將應用的所有配置工作寫入Dockerfile中,創建好鏡像,以後就可以無限次使用這個鏡像進行應用部署了。這大大簡化了應用的部署,不需要爲每次部署都進行繁瑣的配置工作,實現了一次打包,多次部署。這大大加快了應用的開發效率,使得程序員可以快速搭建起開發測試環境,不用關注繁瑣的配置工作,而是將所有精力都儘可能用到開發工作中去。
2、代碼流水線管理
代碼從開發環境到測試環境再到生產環境,需要經過很多次中間環節,Docker給應用提供了一個從開發到上線均一致的環境,開發測試人員均只需關注應用的代碼,使得代碼的流水線變得非常簡單,這樣應用才能持續集成和發佈。
3、快速部署
在虛擬機之前,引入新的硬件資源需要消耗幾天的時間。Docker的虛擬化技術將這個時間降到了幾分鐘,Docker只是創建一個容器進程而無需啓動操作系統,這個過程只需要秒級的時間。
4、應用隔離
資源隔離對於提供共享hosting服務的公司是個強需求。如果使用VM,雖然隔離性非常徹底,但部署密度相對較低,會造成成本增加。
Docker容器充分利用linux內核的namespace提供資源隔離功能。結合cgroups,可以方便的設置每個容器的資源配額。既能滿足資源隔離的需求,又能方便的爲不同級別的用戶設置不同級別的配額限制。
5、服務器資源整合
正如通過VM來整合多個應用,Docker隔離應用的能力使得Docker同樣可以整合服務器資源。由於沒有額外的操作系統的內存佔用,以及能在多個實例之間共享沒有使用的內存,Docker可以比VM提供更好的服務器整合解決方案。
通常數據中心的資源利用率只有30%,通過使用Docker並進行有效的資源分配可以提高資源的利用率。
6、多版本混合部署
隨着產品的不斷更新換代,一臺服務器上部署多個應用或者同一個應用的多個版本在企業內部非常常見。但一臺服務器上部署同一個軟件的多個版本,文件路徑、端口等資源往往會發生衝突,造成多個版本無法共存的問題。
如果用docker,這個問題將非常簡單。由於每個容器都有自己獨立的文件系統,所以根本不存在文件路徑衝突的問題; 對於端口衝突問題,只需要在啓動容器時指定不同的端口映射即可解決問題。
7、版本升級回滾
一次升級,往往不僅僅是應用軟件本身的升級,通過還會包含依賴項的升級。但新舊軟件的依賴項很可能是不同的,甚至是有衝突的,所以在傳統的環境下做回滾一般比較困難。
如果使用docker,我們只需要每次應用軟件升級時製作一個新的docker鏡像,升級時先停掉舊的容器,然後把新的容器啓動。需要回滾時,把新的容器停掉,舊的啓動即可完成回滾,整個過程各在秒級完成,非常方便。
8、內部開發環境
在容器技術出現之前,公司往往是通過爲每個開發人員提供一臺或者多臺虛擬機來充當開發測試環境。開發測試環境一般負載較低,大量的系統資源都被浪費在虛擬機本身的進程上了。
Docker容器沒有任何CPU和內存上的額外開銷,很適合用來提供公司內部的開發測試環境。而且由於Docker鏡像可以很方便的在公司內部共享,這對開發環境的規範性也有極大的幫助。
9、PaaS
使用Docker搭建大規模集羣,提供PaaS。這一應用是最有前景的一個了,目前已有很多創業公司在使用Docker做PaaS了,例如雲雀雲平臺。用戶只需提交代碼,所有運維工作均由服務公司來做。而且對用戶來說,整個應用部署上線是一鍵式的,非常方便。
10、雲桌面
在每一個容器內部運行一個圖形化桌面,用戶通過RDP或者VNC協議連接到容器。該方案所提供的虛擬桌面相比於傳統的基於硬件虛擬化的桌面方案更輕量級,運行速率大大提升。不過該方案仍處於實驗階段,不知是否