整理自《Docker技術入門與實踐》(楊保華 戴王劍 曹亞侖) - Docker核心技術一文。
Docker是一種基於Linux Container(LXC)技術實現的容器虛擬化技術,現又引入libcontainer。從操做系統功能上的角度出發,Docker的核心可分爲:Linux操做系統的命名空間(Namespaces)、控制組(Control Groups)、聯合文件系統(Union File System)、Linux虛擬網絡。docker
Docker採用的是標準的C/S架構,客戶端和服務端能夠運行在同一機器上,也能夠經過socket或RESTful API進行通訊。ubuntu
服務端
Docker daemon通常做爲服務端運行在宿主機的後臺,它是一個很是鬆耦合的架構,會經過專門的Engine模塊來分發管理來自各個客戶端的任務,並根據請求來建立、運行、分發容器。Docker支持經過HTTPS認證方式來驗證訪問。Docker daemon默認監聽本地的unix:///var/run/docker.sock
套接字,只容許本地的root訪問,可是能夠經過-H選項來修改監聽的方式。例如Ubuntu系統中,Docker daemon的默認啓動配置就在/etc/default/docker
中。vim
$ vim /etc/default/docker # Docker Upstart and SysVinit configuration file # # THIS FILE DOES NOT APPLY TO SYSTEMD # # Please see the documentation for "systemd drop-ins": # https://docs.docker.com/engine/articles/systemd/ # # Customize location of Docker binary (especially for development testing). #DOCKER="/usr/local/bin/docker" # Use DOCKER_OPTS to modify the daemon startup options. #DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4" # If you need Docker to use an HTTP proxy, it can also be specified here. #export http_proxy="http://127.0.0.1:3128/" # This is also a handy place to tweak where Docker's temporary files go. #export TMPDIR="/mnt/bigdrive/docker-tmp"
客戶端
客戶端爲用戶提供了一系列可執行的命令,從而達到與Docker daemon交互的目的。一樣的,客戶端則默認經過本地的unix:///var/run/docker.sock
套接字向服務端發送指令。不一樣的是服務端會一直處於監聽狀態,而客戶端在發送指令後等待服務端返回,一旦收到返回就會當即執行結束並退出。在交互過程當中,若是服務端未監聽到默認套接字,則須要客戶端在執行命令的時候顯示指定套接字。如服務端在監聽本地的9527端口,那麼若是要查詢Docker的版本信息:sudo docker -H tcp://127.0.0.1:9527 version
segmentfault
命名空間是Linux內核爲實現容器虛擬化而引入的特性。每一個容器都有本身的命名空間,這保證了容器之間的互不影響。利用該特性,容器實現了在內核、文件系統、網絡、PID、UID、IPC、內存、硬盤、CPU等資源的隔離,而再也不是應用進程直接共享的狀態。緩存
進程命名空間
Linux經過命名空間管理進程號,同一進程在不一樣的命名空間中的進程號是不一樣的。進程命名空間是一個父子關係的結構,子空間的進程可看到父進程的ID。bash
網絡命名空間
經過網絡命名空間能夠實現網絡的徹底隔離。一個網絡命名空間爲進程提供了一個徹底獨立的網絡協議棧的視圖。包括網絡設備接口、IPv4和IPv6協議棧、IP路由表、防火牆規則、sockets等。Docker可採用虛擬網絡設備(Virtual Network Device)的方式將不一樣命名空間的網絡設備鏈接在一塊兒。默認狀況下,容器的虛擬網卡將與宿主機的docker0網橋鏈接在一塊兒。網絡
IPC命名空間
進程間交互(Interprocess Communication - IPC)的信息包括信號量、消息隊列、共享內存等。同一IPC命名空間的進程能夠交互;不然不行。PID命名空間和IPC命名空間能夠組合使用。架構
掛載命名空間
掛載命名空間能夠將一個進程放到一個特定的目錄執行,且容許不一樣命名空間的進程看到的文件結構不一樣,將各個命名空間中的進程看到的文件目錄隔離。app
UTS命名空間
UTS(UNIX Time-sharing System)命名空間能夠另每一個容器擁有獨立的主機名和域名,從而虛擬出一個擁有獨立主機名和獨立網絡空間的環境。默認狀況下,Docker容器的主機名就是容器的ID。socket
用戶命名空間
每一個容器擁有不一樣的用戶和組ID,容器可使用自身內部的特定用戶執行程序,而非宿主機系統上存在的用戶。每一個容器內部均可以有root帳號,且跟宿主機不在同一命名空間。
控制組(CGroup)用於對共享資源進行隔離、限制、審計等。控制分配到容器的資源可避免多個容器同時運行時形成的系統資源競爭。控制組的目標:爲不一樣應用狀況提供統一的接口,從控制單一進程到系統級虛擬化。控制組具有如下功能:
資源限制(Resource Limiting)
組能夠設置爲不超過設定的內存限制;
優先級(Priority)
可讓一些組優先獲得更多的CPU等資源;
資源審計(Accounting)
可使用cpuacct子系通通計某個進程使用的CPU時間;
隔離(Isolation)
爲組隔離命名空間(進程、網絡、文件系統等的隔離);
控制(Control)
掛起、恢復、重啓等操做;
/sys/fs/cgroup/memory/docker
目錄下能夠看到對Docker應用的各類限制項:
$ ls /sys/fs/cgroup/memory/docker #返回結果以下: 0ad2418d6f1bcf17fe5e11071f5b7d538beb9be09847df3f18c596b72258a238 dfbfc284e5a70d79262fede4137b596d0016d9ea2f4f3d28c45cfb284bfd6a54 memory.max_usage_in_bytes 138563cf074e3652c29d3785b618966c3da2db32522f4010bd1148128bbbd10e ff9af226242bd90e6b05e92e5453ce7fc879f4a425276534701c1b091b027444 memory.move_charge_at_immigrate 36cc81e8c6fee35e553eeecdc179a06e38bac49a3a82fd2147d6390309c5833a memory.failcnt memory.numa_stat 3740d5bede34056a53bdce5ba7f18756457262397eaea079b571423a98d61553 memory.force_empty memory.oom_control 3e25c30117030f7f8cbedd08bb9311457fb4cdb64300d76aa0ba7a0cda4f3e21 memory.kmem.failcnt memory.pressure_level 40af5268aeeeb4637283f39e2d22dd34ec7ea73704f2e05a4705890148c72158 memory.kmem.limit_in_bytes memory.soft_limit_in_bytes 59a96d5e5bb0dccf983f2e0a6d05c119af4d053abef11ff336c8d978064061eb memory.kmem.max_usage_in_bytes memory.stat 8129411947b0713d47a2e05304e6ec3a5ec6eacf5c4ce142e3d867f387fde531 memory.kmem.slabinfo memory.swappiness be000c273f97427f7205cdaf6735f145486b584366b8460a83b6b6cba44af248 memory.kmem.tcp.failcnt memory.usage_in_bytes cgroup.clone_children memory.kmem.tcp.limit_in_bytes memory.use_hierarchy cgroup.event_control memory.kmem.tcp.max_usage_in_bytes notify_on_release cgroup.procs memory.kmem.tcp.usage_in_bytes tasks d0767e3dc5ca913816ed1f9b6c60fb0da8c27cfae0c5f168e0ee40b185b9c831 memory.kmem.usage_in_bytes d54ea29dd70b792ba6aa58df056eb156408f3cc96b284d33c04c32cecc83d341 memory.limit_in_bytes
經過修改這些文件值來限制Docker佔用的資源。如限制Docker組中的全部進程使用的物理內存總量不超過100MB:
$ sudo echo 104857600 >/sys/fs/cgroup/memory/docker/memory.limit_in_bytes
查看對應的容器文件夾的內容,能夠看到對應容器的一些狀態:
$ cd /sys/fs/cgroup/memory/docker/d54ea29dd70b792ba6aa58df056eb156408f3cc96b284d33c04c32cecc83d341 $ cat memory.stat #返回結果以下: cache 31596544 rss 103755776 rss_huge 69206016 mapped_file 19787776 writeback 0 pgpgin 34059 pgpgout 19921 pgfault 40135 pgmajfault 346 inactive_anon 45056 active_anon 103804928 inactive_file 22183936 active_file 9318400 unevictable 0 hierarchical_memory_limit 18446744073709551615 total_cache 31596544 total_rss 103755776 total_rss_huge 69206016 total_mapped_file 19787776 total_writeback 0 total_pgpgin 34059 total_pgpgout 19921 total_pgfault 40135 total_pgmajfault 346 total_inactive_anon 45056 total_active_anon 103804928 total_inactive_file 22183936 total_active_file 9318400 total_unevictable 0
在容器工具的開發過程當中,每每須要查看一些容器運行的狀態數據,此時就能夠從這裏獲取更多的信息。另外,也能夠在建立或啓動容器的時候爲每一個容器指定資源的限制。能夠經過docker run --help
來查看幫助信息。也能夠參考「Docker(1.11.1)命令」。
聯合文件系統(UFS)是一種輕量級的高性能分層文件系統,支持將文件系統中的修改信息做爲一次提交,並層層疊加,同時能夠將不一樣目錄掛載到同一虛擬文件系統下。UFS是實現Docker鏡像的技術基礎,鏡像能夠經過分層來進行繼承。例如,用戶基於基礎鏡像(沒有父鏡像的鏡像被稱爲基礎鏡像)來構建各類不一樣用途的鏡像,而這些鏡像共享同一個基礎鏡像,提升了存儲效率。而當用戶改變了一個Docker鏡像(如升級程序,添加/修改文件等),則一個新的的鏡像層(layer)會被建立。所以,新鏡像只是在原有的鏡像層上添加新的鏡像層便可,而不需刪除或替換。在分發鏡像的時候,也只需分發新增的鏡像層。這讓Docker的鏡像管理變得十分輕量與迅速。
Docker中使用的AUFS(Another Union File System 或 v2版本之後的 Advanced Multi-layered Unification File System)就是一種UFS。AUFS支持位每個成員目錄設定只讀、讀寫、寫出權限。同時,AUFS有一個相似分層的概念,對只讀權限的分支能夠邏輯上進行增量的修改而不影響只讀部分。
Docker利用鏡像啓動容器時,將利用鏡像分配文件系統而且掛載一個新的可讀寫的層給容器,容器會在該文件系統中建立,而且這個可讀寫的層會被添加到鏡像中。
Docker目前支持的聯合文件系統類型包括:AUFS、btrfs、vfs和DeviceMapper等。
Docker網絡的實現實際上是利用Linux上的網絡命名空間和虛擬網絡設備(特別是veth pair)。
基本原理
要想實現網絡通訊,機器至少須要一個網絡接口(物理接口或虛擬接口)與外界相通,並能夠收發數據包;另外,若是不一樣子網之間要進行通訊,則須要額外的路由機制。Docker的網絡接口默認都是虛擬接口。虛擬接口的最大優點就是轉發效率極高!之因此會這樣,那是由於Linux經過在內核中進行數據複製來實現虛擬接口間的數據轉發,即直接複製發送接口的發送緩存中的數據包到接收接口的接收緩存中,而無需經過外部物理網絡設備進行交換。對於本地系統和容器內系統來看,虛擬接口和一個正常的以太網卡相比並沒有區別,只是虛擬接口的速度要快得多。
網絡建立過程
建立一對虛擬接口,分別放到宿主機和容器的命名空間中;
宿主機一端的虛擬接口鏈接到默認的docker0網橋或指定網橋上,並具備一個以veth開頭的惟一的名字;
容器一端的虛擬接口將被放到容器中,並修更名稱爲eth0,且這個接口只對該容器的命名空間可見;
從網橋可用地址段中獲取一個空閒的地址分配給容器的eth0(如
172.17.0.2/16),並配置默認路由網關爲docker0網卡的內部接口docker0的IP地址(如 172.17.42.1/16);
完成以上這些,容器就可使用自身可見的eth0虛擬網卡來鏈接其餘容器和訪問外部網絡。另外,能夠在容器建立啓動時經過--net
參數來指定容器的網絡配置,請參考「Docker網絡」。
網絡配置細節
使用--net=none
運行容器後,Docker將不對容器網絡進行配置。接下來將演示手動配置網絡。
啓動一個/bin/bash
容器,指定--net=none
參數:
$ sudo docker run -i -t --rm --net=none ubuntu /bin/bash
在宿主機中查找容器的進程ID,併爲容器建立網絡命名空間:
$ sudo docker inspect -f '{{.State.Pid}}' ContainerID #返回結果(一串數字):pidnum $ pid=pidnum $ sudo mkdir -p /var/run/netns $ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid
檢查橋接網卡的IP和子網掩碼信息:
$ ip addr show docker0
建立一對「veth pair」接口A和B,綁定A接口到網橋docker0,並啓用它:
$ sudo ip link add A type veth peer name B $ sudo brctl addif docker0 A $ sudo ip link set A up
將B接口放到容器的網絡命名空間,命名爲eth0,啓動它並配置一個可用的IP(橋接網段)和默認網關:
$ sudo ip link set B netns $pid $ sudo ip netns exec $pid ip link set dev B name eth0 $ sudo ip netns exec $pid ip link set eth0 up $ sudo ip netns exec $pid ip addr add 172.17.42.99/16 dev eth0 $ sudo ip netns exec $pid ip route add default via 172.17.42.1
以上即爲Docker配置網絡的全過程。當容器終止後,Docker會清空容器,容器內的網絡接口會隨着網絡命名空間一塊兒被清除,A接口也會被自動從docker0中卸載並清除。此外,在刪除/var/run/netns/
下的內容前,用戶可用ip netns exec
命令在指定網絡命名空間中進行配置,從而影響容器內的網絡。