【Docker】Docker底層實(shí)現(xiàn)概覽
Docker解決了云計(jì)算環(huán)境難于分發(fā)并且管理復(fù)雜,而用KVM、Xen等虛擬化又浪費(fèi)系統(tǒng)資源的問(wèn)題。Docker最初是基于lxc構(gòu)建了容器引擎,為了提供跨平臺(tái)支持,后又專(zhuān)門(mén)開(kāi)發(fā)了libcontainer來(lái)抽象容器引擎。但無(wú)論是libcontainer還是lxc,其底層所依賴(lài)的內(nèi)核特性都是相同的。我們來(lái)看看docker都使用了技術(shù)來(lái)實(shí)現(xiàn)容器引擎的。
命名空間
Docker使用了pid、network、ipc、美m(xù)nt、uts等命名空間來(lái)隔離網(wǎng)絡(luò)、文件系統(tǒng)、進(jìn)程等資源。注意,由于Linux并不是namespace了所有東西(如cgroups、/sys、SELinux、/dev/sd*、內(nèi)核模塊等),僅靠這幾個(gè)namespace是無(wú)法實(shí)現(xiàn)像KVM那樣的完全資源隔離的。
- pid namespace:實(shí)現(xiàn)進(jìn)程隔離,容器只能看到自己的進(jìn)程,并且每個(gè)容器都有一個(gè)pid為1的父進(jìn)程,kill掉該進(jìn)程容器內(nèi)的所有進(jìn)程都會(huì)停止;
- net namespace:實(shí)現(xiàn)網(wǎng)絡(luò)隔離,每個(gè)容器都可以設(shè)置自己的interface、routers、iptables等;docker默認(rèn)采用veth的方式將container中的虛擬網(wǎng)卡同host上的一個(gè)docker bridge: docker0連接在一起;
- ipc namespace:container中進(jìn)程交互還是采用linux常見(jiàn)的進(jìn)程間交互方法(interprocess communication - IPC), 包括常見(jiàn)的信號(hào)量、消息隊(duì)列和共享內(nèi)存。然而同 VM 不同的是,container 的進(jìn)程間交互實(shí)際上還是host上具有相同pid namespace中的進(jìn)程間交互,因此需要在IPC資源申請(qǐng)時(shí)加入namespace信息 - 每個(gè)IPC資源有一個(gè)唯一的 32 位 ID;
- mnt namespace:類(lèi)似chroot,將一個(gè)進(jìn)程放到一個(gè)特定的目錄執(zhí)行。mnt namespace允許不同namespace的進(jìn)程看到的文件結(jié)構(gòu)不同,這樣每個(gè) namespace 中的進(jìn)程所看到的文件目錄就被隔離開(kāi)了。同chroot不同,每個(gè)namespace中的container在/proc/mounts的信息只包含所在namespace的mount point;
- uts namspace:允許每個(gè)container擁有獨(dú)立的hostname和domain name, 使其在網(wǎng)絡(luò)上可以被視作一個(gè)獨(dú)立的節(jié)點(diǎn)而非Host上的一個(gè)進(jìn)程;
- user namespace:每個(gè)container可以有不同的 user 和 group id, 也就是說(shuō)可以在container內(nèi)部用container內(nèi)部的用戶(hù)執(zhí)行程序而非Host上的用戶(hù)。
對(duì)于容器所依賴(lài)的內(nèi)核文件系統(tǒng)(這些都是non-namespaced),為了保證安全性,docker將其限制為只讀的:
- . /sys . /proc/sys . /proc/sysrq-trigger . /proc/irq . /proc/bus
cgroups機(jī)制
cgroups 實(shí)現(xiàn)了對(duì)資源的配額和度量。 cgroups 的使用非常簡(jiǎn)單,提供類(lèi)似文件的接口,在 /cgroup目錄下新建一個(gè)文件夾即可新建一個(gè)group,在此文件夾中新建task文件,并將pid寫(xiě)入該文件,即可實(shí)現(xiàn)對(duì)該進(jìn)程的資源控制。groups可以限制blkio、cpu、cpuacct、cpuset、devices、freezer、memory、net_cls、ns九大子系統(tǒng)的資源,以下是每個(gè)子系統(tǒng)的詳細(xì)說(shuō)明:
- blkio 這個(gè)子系統(tǒng)設(shè)置限制每個(gè)塊設(shè)備的輸入輸出控制。例如:磁盤(pán),光盤(pán)以及usb等等。
- cpu 這個(gè)子系統(tǒng)使用調(diào)度程序?yàn)閏group任務(wù)提供cpu的訪問(wèn)。
- cpuacct 產(chǎn)生cgroup任務(wù)的cpu資源報(bào)告。
- cpuset 如果是多核心的cpu,這個(gè)子系統(tǒng)會(huì)為cgroup任務(wù)分配單獨(dú)的cpu和內(nèi)存。
- devices 允許或拒絕cgroup任務(wù)對(duì)設(shè)備的訪問(wèn)。
- freezer 暫停和恢復(fù)cgroup任務(wù)。
- memory 設(shè)置每個(gè)cgroup的內(nèi)存限制以及產(chǎn)生內(nèi)存資源報(bào)告。
- net_cls 標(biāo)記每個(gè)網(wǎng)絡(luò)包以供cgroup方便使用。
- ns 名稱(chēng)空間子系統(tǒng)。
對(duì)于centos7來(lái)說(shuō),通過(guò)systemd-cgls來(lái)查看系統(tǒng)cgroups tree:
- ... ├─docker-b1f965f8e682e9d2ff9ed3039fca63c008810efd9c5e6d796344b0270d329a98.scope │ ├─18853 /usr/lib/systemd/systemd │ └─system.slice │ ├─keepalived.service │ │ ├─19307 /usr/sbin/keepalived -D -d -S 7 │ │ └─19309 /usr/sbin/keepalived -D -d -S 7 │ ├─haproxy.service │ │ ├─25195 /usr/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid │ │ ├─25210 /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -Ds │ │ └─25211 /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -Ds │ ├─rsyslog.service │ │ └─23648 /usr/sbin/rsyslogd -n │ └─systemd-journald.service │ └─18990 /usr/lib/systemd/systemd-journald ...
特權(quán)模式下的容器:
- └─system.slice ├─NetworkManager-dispatcher.service │ └─2580 /usr/libexec/nm-dispatcher.action ├─var-lib-docker-devicemapper-mnt-a80a5f851d00842414d5fb03866c19424df1f2afb47a04f41dd056b54c4df7ac.mount ├─docker-a80a5f851d00842414d5fb03866c19424df1f2afb47a04f41dd056b54c4df7ac.scope │ ├─2558 /usr/sbin/init │ └─system.slice │ ├─systemd-journald.service │ │ └─2605 /usr/lib/systemd/systemd-journald │ ├─dbus.socket │ ├─dbus.service │ ├─system-getty.slice │ ├─rc-local.service │ ├─systemd-user-sessions.service │ ├─dev-dm\x2d0.swap │ ├─etc-yum.repos.d.mount │ ├─etc-hosts.mount │ ├─etc-hostname.mount │ ├─etc-resolv.conf.mount │ └─-.mount
cgroups配置方法:
(1) cpu相對(duì)權(quán)重:docker run -it --rm -c 512,如果未設(shè)置,默認(rèn)為1024
- # cat /sys/fs/cgroup/cpu/system.slice/docker-a80a5f851d00842414d5fb03866c19424df1f2afb47a04f41dd056b54c4df7ac.scope/cpu.shares 1024
如果在容器開(kāi)啟的時(shí)候沒(méi)有設(shè)置cpu權(quán)重,可以在容器啟動(dòng)后修改,如
- [root@fei ~]# systemctl set-property --runtime docker-a80a5f851d00842414d5fb03866c19424df1f2afb47a04f41dd056b54c4df7ac.scope CPUShares=128 [root@fei ~]# cat /sys/fs/cgroup/cpu/system.slice/docker-a80a5f851d00842414d5fb03866c19424df1f2afb47a04f41dd056b54c4df7ac.scope/cpu.shares 128
(2) 設(shè)置cpu pin:docker run -it --rm --cpuset=0,1
- # cat /sys/fs/cgroup/cpuset/system.slice/docker-b0848aa49a03b8541bb698c1544b6c411c584dce5e86831f84803228e93e61d4.scope/cpuset.cpus
- 0-1
(3) 內(nèi)存限制: docker run -it --rm -m 128m,默認(rèn)swap為mem的兩倍
- [root@fei ~]# cat /sys/fs/cgroup/memory/system.slice/docker-c9fed54afc8986be888b231b984be9c1a2a533c739f7a5458a56882fb13b4b93.scope/memory.limit_in_bytes 134217728 [root@fei ~]# cat /sys/fs/cgroup/memory/system.slice/docker-c9fed54afc8986be888b231b984be9c1a2a533c739f7a5458a56882fb13b4b93.scope/memory.memsw.limit_in_bytes 268435456
如果不設(shè)置-m 128m,則默認(rèn)容器內(nèi)存是不設(shè)限的
- [root@fei ~]# cat /sys/fs/cgroup/memory/system.slice/docker-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2.scope/memory.limit_in_bytes 9223372036854775807
(4) 磁盤(pán)IO限制,docker本身默認(rèn)沒(méi)有做磁盤(pán)io的限制,不過(guò)我們可以通過(guò)直接操作cgroups來(lái)實(shí)現(xiàn)
- # 磁盤(pán)寫(xiě) [root@fei ~]# cid=641cdebd22b5 [root@fei ~]# nsenter --target $(docker inspect -f '{{ .State.Pid }}' $cid) --mount --uts --ipc --net --pid mount | head -1 /dev/mapper/docker-253:1-138011042-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2 on / type ext4 (rw,relatime,discard,stripe=16,data=ordered) [root@fei ~]# systemctl set-property --runtime docker-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2.scope "BlockIOWriteBandwidth=/dev/mapper/docker-253:1-138011042-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2 1M" # 磁盤(pán)讀 [root@fei ~]# systemctl set-property --runtime docker-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2.scope "BlockIOReadBandwidth =/dev/mapper/docker-253:1-138011042-641cdebd22b55f2656a560cd250e661ab181dcf2f5c5b78dc306df7ce62231f2 1M"
(5) 磁盤(pán)大小,docker容器默認(rèn)都會(huì)分配10GB的空間,如果想改變這個(gè)值,需要修改docker服務(wù)啟動(dòng)參數(shù),并重啟docker服務(wù):docker -d --storage-opt dm.basesize=5G。其他磁盤(pán)相關(guān)的配置可以參考https://github.com/docker/docker/tree/master/daemon/graphdriver/devmapper。
Capability機(jī)制
Linux把原來(lái)和超級(jí)用戶(hù)相關(guān)的高級(jí)權(quán)限劃分成為不同的單元,稱(chēng)為Capability,這樣就可以獨(dú)立對(duì)特定的Capability進(jìn)行使能或禁止。通常來(lái)講,不合理的禁止Capability,會(huì)導(dǎo)致應(yīng)用崩潰。
Docker默認(rèn)為容器刪除了以下capability:
- CAP_SETPCAP Modify process capabilities CAP_SYS_MODULE Insert/Remove kernel modules CAP_SYS_RAWIO Modify Kernel Memory CAP_SYS_PACCT Configure process accounting CAP_SYS_NICE Modify Priority of processes CAP_SYS_RESOURCE Override Resource Limits CAP_SYS_TIME Modify the system clock CAP_SYS_TTY_CONFIG Configure tty devices CAP_AUDIT_WRITE Write the audit log CAP_AUDIT_CONTROL Configure Audit Subsystem CAP_MAC_OVERRIDE Ignore Kernel MAC Policy CAP_MAC_ADMIN Configure MAC Configuration CAP_SYSLOG Modify Kernel printk behavior CAP_NET_ADMIN Configure the network CAP_SYS_ADMIN Catch all
如果確實(shí)需要這些capability,可以通過(guò)--cap-add or --cap-drop添加或刪除,如docker run --cap-add all --cap-drop sys-admin -ti rhel7 /bin/sh。
SELinux
SELinux是一個(gè)標(biāo)簽系統(tǒng),進(jìn)程有標(biāo)簽,每個(gè)文件、目錄、系統(tǒng)對(duì)象都有標(biāo)簽。SELinux通過(guò)撰寫(xiě)標(biāo)簽進(jìn)程和標(biāo)簽對(duì)象之間訪問(wèn)規(guī)則來(lái)進(jìn)行安全保護(hù)。
Union FS
對(duì)于這種疊加的文件系統(tǒng),有一個(gè)很好的實(shí)現(xiàn)是AUFS,在Ubuntu比較新的發(fā)行版里都是自帶的,這個(gè)可以做到以文件為粒度的copy-on-write,為海量的container的瞬間啟動(dòng),提供了技術(shù)支持,也會(huì)持續(xù)部署提供了幫助(注意,centos7系統(tǒng)是基于devicemapper來(lái)實(shí)現(xiàn)類(lèi)似的功能的)。
AUFS支持為每一個(gè)成員目錄(類(lèi)似Git Branch)設(shè)定readonly、readwrite 和 whiteout-able 權(quán)限, 同時(shí) AUFS 里有一個(gè)類(lèi)似分層的概念, 對(duì) readonly 權(quán)限的 branch 可以邏輯上進(jìn)行修改(增量地, 不影響 readonly 部分的)。通常 Union FS 有兩個(gè)用途, 一方面可以實(shí)現(xiàn)不借助 LVM、RAID 將多個(gè)disk掛到同一個(gè)目錄下, 另一個(gè)更常用的就是將一個(gè) readonly 的 branch 和一個(gè) writeable 的 branch 聯(lián)合在一起,Live CD正是基于此方法可以允許在 OS image 不變的基礎(chǔ)上允許用戶(hù)在其上進(jìn)行一些寫(xiě)操作。Docker 在 AUFS 上構(gòu)建的 container image 也正是如此。
Iptables, netfilter
主要用來(lái)做ip數(shù)據(jù)包的過(guò)濾,比如可以做container之間無(wú)法通信,container可以無(wú)法訪問(wèn)host的網(wǎng)絡(luò),但是可以通過(guò)host的網(wǎng)卡訪問(wèn)外網(wǎng)等這樣的網(wǎng)絡(luò)策略
setrlimit
可以限制container中打開(kāi)的進(jìn)程數(shù),限制打開(kāi)的文件個(gè)數(shù)等