Docker的內核,性能與調優
首先我們拋出3個問題:
- docker容器的內核與宿主機內核是怎樣的關系?
- 容器在運行時如何調用系統資源?
- docker的性能參數有沒有作用范圍?
能夠將這3個問題全部解答,關於docker的內核與調優策略便有了一定程度的認識。
一、容器與宿主機的內核關系 —— 共享內核
docker鏡像是一個“應用程序和它運行依賴環境”的封裝。當鏡像運行起來后,即是docker容器。運行時的容器本質是操作系統下的一個進程,這些進程共享同一個宿主機OS的內核。
與傳統VM相比,docker是一種操作系統虛擬化技術,並不需要在鏡像內安裝GuestOS。
docker在共享宿主機內核的基礎上包裝內核提供的一系列API,這些API中最重要的就是namespace和cgroup。通過namespace實現隔離,通過cgroup實現資源限制。
二、Namespace--命名空間
- PIDnamespace:每當在此空間中啟動一個程序,內核就給它分配唯一的ID。與宿主機所見的不同,容器內的進程都有自己的進程ID空間。
- MNTnamespace:每個容器都有自己目錄掛載的命名空間。
- NETnamespace:每個容器都有自己單獨的網絡棧,其中的socket和網卡設備都是其他容器不能訪問的。
- UTSnamespace:在此命名空間中的進程擁有自己的主機名和NIS域名。
- IPCnamespace:擁有相同IPC命名空間的進程才可以利用“共享內存、信號量、消息隊列”方式進行通信。
- User namespace:用於隔離容器中的UID,GID和根目錄
三、Cgroup--控制組
顧名思義,控制、分組,用於對進程進行層次化分組,並以組為粒度實現資源限制和策略控制。每一個容器內的進程都收到組策略的管控。
Cgroup包含了很多控制器。
Cgroup對進程分組的控制是通過一個虛擬文件系統下的目錄節點和節點文件實現的,控制器是目錄節點中的文件,文件內容規定了系統資源的配額。
以全局cgroup為例 # ls /sys/fs/cgroup

默認的情況下,docker會在這些目錄下面創建自己的節點子目錄,而每個容器都會創建自己容器ID的節點子目錄;這些目錄都包含了對應層次的控制器文件,用於控制每一級的資源配額。下文簡述一些控制器的功能:
- cpu:限制組中進程的cpu使用量
- cpuacct:對進程的CPU使用程度進行計量
- memory:限制組中進程的內存使用量,並計量
- blkio:限制組中進程對塊設備的IO操作
- net_prio:用於設置組相關網絡包的優先級
- hugetlb:用於限制和報告組內進程對大頁面的使用量
- freezer:用於組內進程的掛起和解凍
四、docker內核與調優
理解了上面的幾部分,我們現在應該有個印象:
- docker容器共享宿主機OS的內核
- docker容器視為一個進程
- docker容器的資源配額受docker引擎和宿主機OS限制
所以在進行docker內核調優時,可以推論:
- docker的內核參數大多繼承自宿主機內核
- docker容器可以在某些情況、某些范圍內調整自己的內核參數
那么某些情況和某些范圍指的是什么呢?讓我們來分享一些特殊情況
4.1 docker的 --sysctl 參數的白名單
語法:docker run --sysctl key=value IMAGE:TAG CMD
該參數允許docker容器啟動時設置某些內核參數,這些參數是有限制的,可以在docker源碼中查看到
// docker/opts/opts.go func ValidateSysctl(val string) (string, error) { validSysctlMap := map[string]bool{ "kernel.msgmax": true, "kernel.msgmnb": true, "kernel.msgmni": true, "kernel.sem": true, "kernel.shmall": true, "kernel.shmmax": true, "kernel.shmmni": true, "kernel.shm_rmid_forced": true, } validSysctlPrefixes := []string{ "net.", "fs.mqueue.", } ...
如上所示,只有被標識為 True 的 kernel.*, net.* 以及fs.mqueue.* 是可以傳入參數並修改的
如果傳入其他參數,會提示:for --sysctl: sysctl'kernel.acpi_video_flags=0' is not whitelisted
即白名單未通過
4.2 容器中看不到的參數
通常容器內可以修改的參數,都是可以通過sysctl -a查看到的,但有些則不行:
例如 net.core.rmem_max參數(定義內核用於所有類型的連接的最大接收緩沖大小)
root@host01:~/tmp# sysctl -a | grep rmem_max net.core.rmem_max = 212992 root@host01:~/tmp# docker run hub.c.163.com/public/debian:7.9 sysctl -a | grep rmem_max root@host01:~/tmp#
原因是這些參數是隸屬於kernel的namespace
4.3 不能被namespace化的參數
當一些內核參數在容器中修改后,會反過來作用於宿主機,這一類內核參數的特點是無法被namespace化;因為docker使用共享內核的機制,大多數和kernel相關的參數都有這樣的特點:
# step1.檢查當前進程數最大值
root@host01:~/tmp# sysctl -a | grep pid_max
kernel.pid_max =32768
# step2.啟動一個測試用的容器,--privileged 表示獲得root權限
root@host01:~/tmp# docker run -d --privileged --name test hub.c.163.com/public/debian:7.9
a43e89ee85d36e250e0886331e9d6213094f31260eb9e1539b83f0e9cfc91848
# step3.在容器內修改進程數最大值
root@host01:~/tmp# docker exec test sysctl -w kernel.pid_max=86723
kernel.pid_max = 86723
# step4.再次檢查進程數最大值,發現變更成了上一步docker容器內設定的值
root@host01:~/tmp# sysctl -a | grep pid_max
kernel.pid_max = 86723
引入3中特殊情況對應的圖形:

綜上,我們可以得出結論:
- 程序白名單以外的參數不可調整;想調整請修改docker源碼
- 容器內systcl看不到的繼承自宿主機
- 容器內能影響宿主機的不要修改
針對docker容器的內存調優策略:
- 請盡量在宿主機OS進行內核優化操作;
- 需要特化&滿足條件的參數請通過 --sysctl參數傳入或寫入dockerfile
五、操作系統參數調優
限於篇幅,本文對於操作系統的參數調優不再闡述,給與幾篇相關擴展閱讀:
- 文件句柄數 ulimit -n & /etc/security/limits.conf
- Docker Container繼承自Docker Daemon的ulimit設置,也可以修改/etc/sysconfig/docker,指定docker-deamon啟動參數
- TCP/IP協議棧優化、內核參數默認值和建議值