面試官留步!聽我跟你侃會兒Docker原理

1 Docker 簡介

1.1 Docker 由來

Docker 是基於 Go 語言開發的一個容器引擎,Docker是應用程序與系統之間的隔離層。通常應用程序對安裝的系統環境會有各種嚴格要求,當服務器很多時部署時系統環境的配置工作是非常繁瑣的。Docker讓應用程序不必再關心主機環境,各個應用安裝在Docker鏡像裏,Docker引擎負責運行包裹了應用程序的docker鏡像。

Docker的理念是讓開發人員可以簡單地把應用程序及依賴裝載到容器中,然後輕鬆地部署到任何地方,Docker具有如下特性。

  1. Docker容器是輕量級的虛擬技術,佔用更少系統資源。

  2. 使用 Docker容器,不同團隊(如開發、測試,運維)之間更容易合作。

  3. 可以在任何地方部署 Docker 容器,比如在任何物理和虛擬機上,甚至在雲上。

  4. 由於Docker容器非常輕量級,因此可擴展性很強。

1.2 Docker 基本組成

鏡像(image):

Docker 鏡像就好比是一個目標,可以通過這個目標來創建容器服務,可以簡單的理解爲編程語言中的類。

容器(container):

Docker 利用容器技術,獨立運行一個或者一組應用,容器是通過鏡像來創建的,在容器中可執行啓動、停止、刪除等基本命令,最終服務運行或者項目運行就是在容器中的,可理解爲是類的實例。

倉庫(repository):

倉庫就是存放鏡像的地方!倉庫分爲公有倉庫和私有倉庫,類似Git。一般我們用的時候都是用國內docker鏡像來加速。

1.3 VM 跟 Docker

虛擬機

傳統的虛擬機需要模擬整臺機器包括硬件,每臺虛擬機都需要有自己的操作系統,虛擬機一旦被開啓,預分配給他的資源將全部被佔用。每一個虛擬機包括應用,必要的二進制和庫,以及一個完整的用戶操作系統。

Docker

容器技術是和我們的宿主機共享硬件資源及操作系統可以實現資源的動態分配。容器包含應用和其所有的依賴包,但是與其他容器共享內核。容器在宿主機操作系統中,在用戶空間以分離的進程運行。

1.4 Docker 跟 DevOps

DevOps 是一組過程、方法與系統的統稱,用於促進開發(應用程序/軟件工程)、技術運營和質量保障(QA)部門之間的溝通、協作與整合。

DevOps 是兩個傳統角色 Dev(Development) 和 Ops(Operations) 的結合,Dev 負責開發,Ops 負責部署上線,但 Ops 對 Dev 開發的應用缺少足夠的瞭解,而 Dev 來負責上線,很多服務軟件不知如何部署運行,二者中間有一道明顯的鴻溝,DevOps 就是爲了彌補這道鴻溝。DevOps 要做的事,是偏 Ops 的;但是做這個事的人,是偏 Dev 的, 說白了就是要有一個瞭解 Dev 的人能把 Ops 的事幹了。而Docker 是適合 DevOps 的。

1.5 Docker 跟 k8s

k8s 的全稱是 kubernetes,它是基於容器的集羣管理平臺,是管理應用的全生命週期的一個工具,從創建應用、應用的部署、應用提供服務、擴容縮容應用、應用更新、都非常的方便,而且可以做到故障自愈,例如一個服務器掛了,可以自動將這個服務器上的服務調度到另外一個主機上進行運行,無需進行人工干涉。k8s 依託於Google自家的強大實踐應用,目前市場佔有率已經超過Docker自帶的Swarm了。

如果你有很多 Docker 容器要啓動、維護、監控,那就上k8s吧

1.6 hello world

docker run hello-world 的大致流程圖如下:

 

2 Docker 常見指令

官方文檔

https://docs.docker.com/engine/reference/commandline/build/



 

3 Docker 運行原理

Docker 只提供一個運行環境,他跟 VM 不一樣,是不需要運行一個獨立的 OS,容器中的系統內核跟宿主機的內核是公用的。docker容器本質上是宿主機的進程。對 Docker 項目來說,它最核心的原理實際上就是爲待創建的用戶進程做如下操作:

  1. 啓用 Linux Namespace 配置。

  2. 設置指定的 Cgroups 參數。

  3. 切換進程的根目錄(Change Root),優先使用 pivot_root 系統調用,如果系統不支持,纔會使用 chroot

3.1 namespace 進程隔離

Linux Namespaces 機制提供一種進程資源隔離方案。PID、IPC、Network 等系統資源不再是全局性的,而是屬於某個特定的Namespace。每個namespace下的資源對於其他 namespace 下的資源都是透明,不可見的。系統中可以同時存在兩個進程號爲0、1、2的進程,由於屬於不同的namespace,所以它們之間並不衝突。

PS:Linux 內核提拱了6種 namespace 隔離的系統調用,如下圖所示。

3.2 CGroup 分配資源

Docker 通過 Cgroup 來控制容器使用的資源配額,一旦超過這個配額就發出OOM。配額主要包括 CPU、內存、磁盤三大方面, 基本覆蓋了常見的資源配額和使用量控制。

Cgroup 是 Control Groups 的縮寫,是Linux 內核提供的一種可以限制、記錄、隔離進程組所使用的物理資源(如 CPU、內存、磁盤 IO 等等)的機制,被 LXC(Linux container)、Docker 等很多項目用於實現進程資源控制。Cgroup 本身是提供將進程進行分組化管理的功能和接口的基礎結構,I/O 或內存的分配控制等具體的資源管理是通過該功能來實現的,這些具體的資源 管理功能稱爲 Cgroup 子系統。

3.3  chroot 跟 pivot_root 文件系統

chroot(change root file system)命令的功能是 改變進程的根目錄到指定的位置。比如我們現在有一個$HOME/test目錄,想要把它作爲一個 /bin/bash 進程的根目錄。

  1. 首先,創建一個 HOME/test/{bin,lib64,lib}
  2. 把bash命令拷貝到test目錄對應的bin路徑下 cp -v /bin/{bash,ls} $HOME/test/bin
  3. 把bash命令需要的所有so文件,也拷貝到test目錄對應的lib路徑下
  4. 執行chroot命令,告訴操作系統,我們將使用HOME/test /bin/bash
被chroot的進程此時執行 ls / 返回的都是 $HOME/test 目錄下面的內容,Docker就是這樣實現容器根目錄的。爲了能夠讓容器的這個根目錄看起來更 真實 ,一般在容器的根目錄下掛載一個完整操作系統的文件系統,比如Ubuntu16.04的ISO。這樣在容器啓動之後,容器裏執行 ls / 查看到的就是Ubuntu 16.04的所有目錄和文件。
而掛載在容器根目錄上、用來爲容器進程提供隔離後執行環境的文件系統,就是所謂的 容器鏡像 。更專業的名字叫作:rootfs(根文件系統)。所以一個最常見的 rootfs 會包括如下所示的一些目錄和文件:

$ ls /
bin dev etc home lib lib64 mnt opt proc root run sbin sys tmp usr var

chroot 只改變當前進程的 /,pivot_root改變當前 mount namespace的 / 。pivot_root 可以認爲是 chroot 的改良版。

3.4 一致性

由於 rootfs 裏打包的不只是應用,而是整個操作系統的文件和目錄,也就意味着應用以及它運行所需要的所有依賴都被封裝在了一起。有了容器鏡像 打包操作系統 的能力,這個最基礎的依賴環境也終於變成了應用沙盒的一部分。這就賦予了容器所謂的一致性:
無論在本地、雲端,還是在一臺任何地方的機器上,用戶只需要解壓打包好的容器鏡像,那麼這個應用運行所需要的完整的執行環境就被重現出來了。

3.5 UnionFS 聯合文件系統

如何實現rootfs的高效可重複利用呢?Docker在鏡像的設計中引入了層(layer)的概念。也就是說用戶製作鏡像的每一步操作都會生成一個層,也就是一個增量rootfs。介紹分層前我們先說個重要知識點,聯合文件系統。
聯合文件系統(UnionFS)是一種分層、輕量級並且高性能的文件系統,它支持對文件系統的修改作爲一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬文件系統下。比如現在有水果fruits、蔬菜vegetables兩個目錄,其中水果中有蘋果和蕃茄,蔬菜有胡蘿蔔和蕃茄:

$ tree
.
├── fruits
│  ├── apple
│  └── tomato
└── vegetables
  ├── carrots
  └── tomato

然後使用聯合掛載的方式將這兩個目錄掛載到一個公共的目錄 mnt 上:

$ mkdir mnt
$ sudo mount -t aufs -o dirs=./fruits:./vegetables none ./mnt

這時再查看目錄 mnt 的內容,就能看到目錄 fruits 和 vegetables 下的文件被合併到了一起:

$ tree ./mnt
./mnt
├── apple
├── carrots
└── tomato

可以看到在 mnt 目錄下有三個文件,蘋果apple、胡蘿蔔carrots和蕃茄tomato。水果和蔬菜的目錄被union到了 mnt 目錄下了。

 $ echo mnt > ./mnt/apple
 $ cat ./mnt/apple
 mnt
 $ cat ./fruits/apple
 mnt

可以看到./mnt/apple的內容改了,./fruits/apple的內容也改了。

 $ echo mnt_carrots > ./mnt/carrots
 $ cat ./vegetables/carrots
 old
 $ cat ./fruits/carrots
 mnt_carrots

./vegetables/carrots 並沒有變化,反而是 ./fruits/carrots 的目錄中出現了 carrots 文件,其內容是我們在 ./mnt/carrots 裏的內容。
結論
在mount aufs命令時候,沒有對 vegetables 跟 fruits 設置權限,默認命令行上第一個的目錄是可讀可寫的,後面的全都是隻讀的。有重複的文件名,在mount命令行上,越往前的被操作的優先級越高。

3.6 layer 分層

說完聯合文件系統後我們再說下Docker中的分層,鏡像可以通過分層來進行繼承,基於基礎鏡像(沒有父鏡像)用戶可以製作各種具體的應用鏡像。不同 Docker 容器可以共享一些基礎的文件系統層,同時再加上自己獨有的改動層,大大提高了存儲的效率。
Docker 中使用一種叫 AUFS(Anothe rUnionFS)的聯合文件系統。AUFS 支持爲每一個成員目錄設定不同的讀寫權限。
  1. rw 表示可寫可讀read-write。
  2. ro 表示read-only,如果你不指權限,那麼除了第一個外,ro是默認值,對於ro分支,其永遠不會收到寫操作,也不會收到查找whiteout的操作。
  3. rr 表示 real-read-only,與read-only不同的是,rr 標記的是天生就是隻讀的分支,這樣,AUFS可以提高性能,比如不再設置inotify來檢查文件變動通知。
當我們想修改ro層的文件時咋辦?因爲ro是不允許修改的啊!Docker中一般ro層還帶個wh的能力。我們就需要對這個ro目錄裏的文件作whiteoutAUFSwhiteout的實現是通過在上層的可寫的目錄下建立對應的whiteout隱藏文件來實現的。比如我們有三個目錄和文件如下所示:

$ tree
.
├── fruits
│   ├── apple
│   └── tomato
├── test #目錄爲空
└── vegetables
    ├── carrots
    └── tomato

執行如下:

 $ mkdir mnt
 $ mount -t aufs -o dirs=./test=rw:./fruits=ro:./vegetables=ro none ./mnt
 $ ls ./mnt/
 apple  carrots  tomato

在權限爲 rw 的 test 目錄下建個 whiteout 的隱藏文件 .wh.apple ,你就會發現 ./mnt/apple 這個文件就消失了,跟執行了 rm ./mnt/apple 是一樣的結果:

 $ touch ./test/.wh.apple
 $ ls ./mnt
 carrots  tomato

對於AUFS來說鏡像的若干基礎層放置在 /var/lib/docker/aufs/diff 目錄下,然後通過查詢 /sys/fs/aufs 查看被聯合掛載在一起的各個層的信息,多個基礎層最終被聯合掛載在 /var/lib/docker/aufs/mnt 裏面,這裏面存儲的就是一個成品。
Docker 目前支持的聯合文件系統包括 OverlayFS, AUFS, Btrfs, VFS, ZFS 和 Device Mapper。推薦使用 overlay2 存儲驅動,overlay2 是目前 Docker 默認的存儲驅動,以前則是 AUFS。
3.6.1 只讀層
我們以Ubuntu爲例,當執行 docker image inspect ubuntu:latest 會發現容器的rootfs最下面的四層,對應的正是ubuntu:latest鏡像的四層。它們的掛載方式都是隻讀的(ro+wh),都以增量的方式分別包含了Ubuntu操作系統的一部分,四層聯合起來組成了一個成品。
3.6.2 可讀寫層
rootfs 最上層的操作權限爲 rw, 在沒有寫入文件之前,這個目錄是空的。而一旦在容器裏做了寫操作,你修改產生的內容就會以增量的方式出現在這個層中。如果你想刪除只讀層裏的文件,咋辦呢?這個問題上面已經講解過了。
最上面這個可讀寫層就是專門用來存放修改 rootfs 後產生的增量,無論是增、刪、改,都發生在這裏。而當我們使用完了這個被修改過的容器之後,還可以使用 docker commit 和 push 指令,保存這個被修改過的可讀寫層,並上傳到 Docker Hub上,供其他人使用。並且原先的只讀層裏的內容則不會有任何變化,這就是增量 rootfs 的好處。
3.6.3 init 層
它是一個以 -init 結尾的層,夾在只讀層和讀寫層之間。Init層是Docker項目單獨生成的一個內部層,專門用來存放 /etc/hosts 等信息。
需要這樣一層的原因是這些文件本來屬於只讀的Ubuntu鏡像的一部分,但是用戶往往需要在啓動容器時寫入一些指定的值比如 hostname,那就需要在可讀寫層對它們進行修改。可是,這些修改往往只對當前的容器有效,我們並不希望執行 docker commit 時,把 init 層的信息連同可讀寫層一起提交。
最後這6層被組合起來形成了一個完整的 Ubuntu 操作系統供容器使用。

 

4 Docker 網絡

由上面的 Docker 原理可知 Docker 使用了 Linux 的 Namespaces 技術來進行資源隔離,如 PID Namespace 隔離進程,Mount Namespace 隔離文件系統,Network Namespace 隔離網絡等。一個Network Namespace 提供了一份獨立的網絡環境(包括網卡、路由、Iptable規則)與其他的Network Namespace隔離,一個Docker容器一般會分配一個獨立的Network Namespace。
當你安裝Docker時,執行 docker network ls 會發現它會自動創建三個網絡。

[root@server1 ~]$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
0147b8d16c64        bridge              bridge              local
2da931af3f0b        host                host                local
63d31338bcd9        none                null                local

我們在使用docker run創建Docker容器時,可以用 --net 選項指定容器的網絡模式,Docker可以有以下4種網絡模式:

4.1 Host 模式

等價於Vmware中的橋接模式,當啓動容器的時候用host模式,容器將不會虛擬出自己的網卡,配置自己的IP等,而是使用宿主機的IP和端口。但是容器的其他方面,如文件系統、進程列表等還是和宿主機隔離的。

4.2 Container 模式

Container 模式指定新創建的容器和已經存在的一個容器共享一個 Network Namespace,而不是和宿主機共享。新創建的容器不會創建自己的網卡,配置自己的IP,而是和一個指定的容器共享IP、端口範圍等。同樣,兩個容器除了網絡方面,其他的如文件系統、進程列表等還是隔離的。兩個容器的進程可以通過lo網卡設備通信。

4.3 None 模式

None 模式將容器放置在它自己的網絡棧中,並不進行任何配置。實際上,該模式關閉了容器的網絡功能,該模式下容器並不需要網絡(例如只需要寫磁盤卷的批處理任務)。

4.4 Bridge 模式

Bridge 模式是 Docker 默認的網絡設置,此模式會爲每一個容器分配 Network Namespace、設置IP等。當Docker Server啓動時,會在主機上創建一個名爲docker0的虛擬網橋,此主機上啓動的Docker容器會連接到這個虛擬網橋上。虛擬網橋的工作方式和物理交換機類似,主機上的所有容器就通過交換機連在了一個二層網絡中。

Docker 會從RFC1918所定義的私有IP網段中,選擇一個和宿主機不同的IP地址子網分配給docker0,連接到 docker0 的容器從子網中選擇個未佔用 IP 使用。一般 Docker 會用 172.17.0.0/16 這個網段,並將172.17.0.1/16 分配給 docker0 網橋(在主機上使用ifconfig命令是可以看到docker0的,可以認爲它是網橋的管理接口,在宿主機上作爲一塊虛擬網卡使用)。
網絡配置的過程大致3步
  1. 在主機上創建一對虛擬網卡 veth pair 設備。veth設備總是成對出現的,它們組成了一個數據的通道,數據從一個設備進入,就會從另一個設備出來。因此veth設備常用來連接兩個網絡設備。
  2. Docker 將 veth pair 設備的一端放在新創建的容器中,並命名爲eth0。另一端放在主機中,以veth65f9 這樣類似的名字命名,並將這個網絡設備加入到docker0網橋中,可以通過brctl show命令查看。
  3. 從 docker0 子網中分配一個IP給容器使用,並設置 docker0 的IP地址爲容器的默認網關。
Bridge 模式下容器的通信
  1. 容器訪問外部
假設主機網卡爲eth0,IP地址10.10.101.105/24,網關10.10.101.254。從主機上一個IP爲172.17.0.1/16 的容器中ping百度(180.76.3.151)。首先IP包從容器發往自己的默認網關 docker0,包到達docker0後,會查詢主機的路由表,發現包應該從主機的 eth0 發往主機的網關10.10.105.254/24。接着包會轉發給eth0,並從eth0發出去。這時Iptable規則就會起作用,將源地址換爲 eth0 的地址。這樣,在外界看來,這個包就是從10.10.101.105上發出來的,Docker容器對外是不可見的。
  1. 外部訪問容器
創建容器並將容器的80端口映射到主機的80端口。當我們對主機 eth0 收到的目的端口爲80的訪問時候,Iptable規則會進行DNAT轉換,將流量發往172.17.0.2:80,也就是我們上面創建的Docker容器。所以,外界只需訪問10.10.101.105:80就可以訪問到容器中的服務。

4.5 --link

容器創建後我們想通過容器名字來ping。此時需要用到--link,如下:

docker run -d -P --name linux03 --link linux02 linux
docker exec -it linux03 ping linux02 可ping通。
docker exec -it linux02 ping linux03 不可ping通。

追本溯源 看下 linux03 的 /etc/hosts 會發現本質只是做了個host映射。

172.17.0.3 linux03 12ft4tesa # 跟Windows的host文件一樣,只是做了地址綁定

4.6 自建Bridge

我們之前直接啓動的命令 (默認是使用--net bridge,可省),這個bridge就是我們的docker0。下面倆是等價的。

docker run -d -P --name linux01 LinuxSelf
docker run -d -P --name linux01 --net bridge LinuxSelf

docker0默認不支持域名訪問 , 只能用 --link 打通連接。如果我們使用自定義的網絡時,docker底層已經幫我們維護好了對應關係,可以實現域名訪問。

# --driver bridge 網絡模式定義爲 :橋接 
# --subnet 192.168.0.0/16 定義子網 ,範圍爲:192.168.0.2 ~ 192.168.255.255 
# --gateway 192.168.0.1 子網網關設爲: 192.168.0.1 
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

接下來

docker run -d -P --name linux-net-01 --net mynet LinuxSelf
docker run -d -P --name linux-net-02 --net mynet LinuxSelf
docker exec -it linux-net-01 ping linux-net-02的IP  # 結果OK
docker exec -it linux-net-01 ping linux-net-02     # 結果OK

 

5 可視化界面

5.1 Portainer

Portainer 是 Docker 的圖形化管理工具,提供狀態顯示面板、應用模板快速部署、容器鏡像網絡數據卷的基本操作(包括上傳下載鏡像,創建容器等操作)、事件日誌顯示、容器控制檯操作、Swarm集羣和服務等集中管理和操作、登錄用戶管理和控制等功能。功能十分全面,基本能滿足中小型單位對容器管理的全部需求。

5.2 DockerUI

DockerUI基於Docker API,提供等同Docker命令行的大部分功能,支持container管理,image管理。不過DockerUI一個致命的缺點就是 不支持多主機。

5.3 Shipyard

Shipyard 是一個集成管理docker容器、鏡像、Registries的系統,它可以簡化對橫跨多個主機的Docker容器集羣進行管理. 通過Web用戶界面,你可以大致瀏覽相關信息,比如你的容器在使用多少處理器和內存資源、在運行哪些容器,還可以檢查所有集羣上的事件日誌。

有道無術,術可成;有術無道,止於術

歡迎大家關注Java之道公衆號


好文章,我在看❤️

本文分享自微信公衆號 - Hollis(hollischuang)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章