在了解學習 Docker 之前我們就非常有必要介紹下 Docker 的前生 LXC(Linux Container)。
###

###
## LXC 介紹
###
LXC 可以提供輕量級的虛擬化,用來隔離進程和資源,和我們傳統觀念中的全虛擬化完全不一樣,非常輕量級。LXC 可以將單個操作系統管理的資源劃分到獨立的組中,和傳統的虛擬化技術相比,LXC 有如下一些優勢:
###
* 和宿主機使用同一個內核,所以性能損耗小
* 不需要指令級模擬
* 不需要即時編譯
* 容器可以在 CPU 核心的本地運行指令,不需要任何專門的解釋機制
* 避免了虛擬化和系統調用中的復雜性
* 輕量級隔離,隔離的同時還可以和宿主機共享資源
###
LXC 有點像[chroot](https://zh.wikipedia.org/zh-cn/Chroot),提供了一個擁有自己進程和網絡空間的虛擬環境,但是和虛擬機又不一樣,因為 LXC 是一種操作系統層面上的資源的虛擬化。
###
> chroot簡介
> chroot(change root),在 Linux 系統中,系統默認的目錄就都是以`/`也就是根目錄開頭的,chroot 的使用能夠改變當前的系統根目錄結構,通過改變當前系統的根目錄,我們能夠限制用戶的權利,在新的根目錄下并不能夠訪問舊系統根目錄的結構個文件,也就建立了一個與原系統完全隔離的目錄結構。
>
> 更多關于 chroot 的介紹,可以查看[理解 chroot](https://www.ibm.com/developerworks/cn/linux/l-cn-chroot/index.html)一文。
###
## 什么是 Docker
###
Docker 并不是 LXC 替代品,Docker 底層就是使用的 LXC 來實現,LXC 將 Linux 進程沙盒化,使得進程之間相互隔離,還可以共享宿主機的資源。在 LXC 的基礎上,Docker 提供了一系列更加強大方便的功能,使得 Docker 成為了現在最火的虛擬化技術。
###
由于之前我們的后臺在開發和運維階段的環境是不一致的,這就導致了 Docker 的出現,因為我們通過 Docker 可以將程序運行的環境也一起打包到版本控制去了,這樣就排除了因為環境不同造成的各種麻煩事情了,也不會出現在本地可以在線上卻不行這樣的窘境了。
###
Docker 是一個開源的應用容器引擎,基于 go 語言開發,可以讓開發者打包他們的應用以及依賴包到一個輕量級、可移植的容器中,然后發布到任何流行的 Linux 服務器。容器是一個沙箱機制,相互之間不會有影響(類似于我們手機上運行的 app),并且容器開銷是很低的。
###
用官方的話來說,Docker 受歡迎,是因為以下幾個特點:
###
* 靈活性:即使是最復雜的應用也可以集裝箱化
* 輕量級:容器利用并共享主機內核
* 可互換:您可以即時部署更新和升級
* 便攜式:您可以在本地構建,部署到云,并在任何地方運行
* 可擴展:您可以增加并自動分發容器副本
* 可堆疊:您可以垂直和即時堆疊服務
###
### Docker 幾個重要概念
###
在了解了 Docker 是什么之后,我們需要先了解下 Docker 中最重要的3個概念:鏡像、容器和倉庫。
###
**鏡像**是一個只讀模板,帶有創建 Docker 容器的說明,一般來說的,鏡像會基于另外的一些基礎鏡像并加上一些額外的自定義功能來組成。比如,你可以構建一個基于 Centos 的鏡像,然后在這個基礎鏡像上面安裝一個 Nginx 服務器,這樣就可以構成一個屬于我們自己的鏡像了。
###
**容器**是一個鏡像的可運行的實例,可以使用 Docker REST API 或者 CLI 命令行工具來操作容器,容器的本質是進程,但與直接在宿主執行的進程不同,容器進程運行于屬于自己的獨立的命名空間。因此容器可以擁有自己的 root 文件系統、自己的網絡配置、自己的進程空間,甚至自己的用戶 ID 空間。容器內的進程是運行在一個隔離的環境里,使用起來,就好像是在一個獨立于宿主的系統下操作一樣。這種特性使得容器封裝的應用比直接在宿主運行更加安全。
###
**registry**是用來存儲 Docker 鏡像的倉庫,Docker Hub 是 Docker 官方提供的一個公共倉庫,而且 Docker 默認也是從 Docker Hub 上查找鏡像的,當然你也可以很方便的運行一個私有倉庫,當我們使用`docker pull`或者`docker run`命令時,就會從我們配置的 Docker 鏡像倉庫中去拉取鏡像,使用`docker push`命令時,會將我們構建的鏡像推送到對應的鏡像倉庫中,registry 可以理解為用于鏡像的 github 這樣的托管服務。
###
### 容器和虛擬機
###
上面我們說到了容器是在 Linux 上本機運行,并與其他容器共享主機的內核,它運行一個獨立的進程,不占用其他任何可執行文件的內存,非常輕量。
###
而虛擬機運行的是一個完整的操作系統,通過虛擬機管理程序對主機資源進行虛擬訪問,相比之下需要的資源需要更多,但是非常安全,因為是獨立的操作系統,獨立的內核。
###

###
### 支持 Docker 的底層技術
###
**docker 本質就是宿主機的一個特殊進程**,Docker 是通過`namespace`實現資源隔離,通過`cgroup`實現資源限制,通過寫時復制技術(copy-on-write)實現了高效的文件操作(類似虛擬機的磁盤比如分配 500g 并不是實際占用物理磁盤 500g)
###
#### Namespaces
###
命名空間 (namespaces) 是 Linux 為我們提供的用于分離進程樹、網絡接口、掛載點以及進程間通信等資源的方法。在日常使用個人 PC 時,我們并沒有運行多個完全分離的服務器的需求,但是如果我們在服務器上啟動了多個服務,這些服務其實會相互影響的,每一個服務都能看到其他服務的進程,也可以訪問宿主機器上的任意文件,一旦服務器上的某一個服務被入侵,那么入侵者就能夠訪問當前機器上的所有服務和文件,這是我們不愿意看到的,我們更希望運行在同一臺機器上的不同服務能做到完全隔離,就像運行在多臺不同的機器上一樣。而 Docker 其實就通過 Linux 的 Namespaces 技術來實現的對不同的容器進行隔離。
###
當我們運行(docker run 或者 docker start)一個 Docker 容器時,Docker 會為該容器設置一系列的 namespaces,這些 namespaces 提供了一層隔離,容器的各個方面都在單獨的 namespace 中運行,并且對其的訪問僅限于該 namespace。
###
Docker 在 Linux 上使用以下幾個命名空間(上面說的各個方面):
###
* pid namespace:用于進程隔離(PID:進程ID)
* net namespace:管理網絡接口(NET:網絡)
* ipc namespace:管理對 IPC 資源的訪問(IPC:進程間通信(信號量、消息隊列和共享內存))
* mnt namespace:管理文件系統掛載點(MNT:掛載)
* uts namespace:隔離主機名和域名
* user namespace:隔離用戶和用戶組(3.8以后的內核才支持)
###
#### CGroups
###
我們通過 Linux 的 namespaces 技術為新創建的進程隔離了文件系統、網絡、進程等資源,但是 namespaces 并不能夠為我們提供物理資源上的隔離,比如 CPU、內存、IO 或者網絡帶寬等,所以如果我們運行多個容器的話,則容器之間就會搶占資源互相影響了,所以對容器資源的使用進行限制就非常重要了,而 Control Groups(CGroups)技術就能夠隔離宿主機上的物理資源。CGroups 由 7 個主要的子系統組成:分別是 cpuset、cpu、cpuacct、blkio、devices、freezer、memory,不同類型資源的分配和管理是由各個 CGroup 子系統負責完成的。
###
> CGroup 簡介
> 在 CGroup 中,所有的任務就是一個系統的一個進程,而 CGroup 就是一組按照某種標準劃分的進程,在 CGroup 這種機制中,所有的資源控制都是以 CGroup 作為單位實現的,每一個進程都可以隨時加入一個 CGroup 也可以隨時退出一個 CGroup。
> [CGroup 介紹、應用實例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)
> [Docker 背后的內核 Cgroups 機制](https://www.infoq.cn/article/docker-resource-management-cgroups/)
###
CGroup 具有以下幾個特點:
###
* CGroup 的 API 以一個偽文件系統(/sys/fs/cgroup/)的實現方式,用戶的程序可以通過文件系統實現 CGroup 的組件管理
* CGroup 的組件管理操作單元可以細粒度到線程級別,用戶可以創建和銷毀 CGroup,從而實現資源載分配和再利用
* 所有資源管理的功能都以子系統(cpu、cpuset 這些)的方式實現,接口統一子任務創建之初與其父任務處于同一個 CGroup 的控制組
###
另外 CGroup 具有四大功能:
###
* 資源限制:可以對任務使用的資源總額進行限制
* 優先級分配:通過分配的 cpu 時間片數量以及磁盤 IO 帶寬大小等,實際上相當于控制了任務運行優先級
* 資源統計:可以統計系統的資源使用量,如 cpu 時長,內存用量等
* 任務控制:cgroup 可以對任務執行掛起、恢復等操作
###
#### UnionFS
###
Linux 的命名空間和控制組分別解決了不同資源隔離的問題,前者解決了進程、網絡以及文件系統的隔離,后者實現了 CPU、內存等資源的隔離,但是在 Docker 中還有另一個非常重要的問題需要解決 - 也就是鏡像。
###
鏡像到底是什么,它又是如何組成和組織的呢?而這其中最重要的概念就是鏡像層(Layers)(如下圖)的概念,而鏡像層依賴于一系列的底層技術,比如文件系統(filesystems)、寫時復制(copy-on-write)、聯合掛載(union mounts)等。
###

###
Docker 鏡像是由一系列的層組成的,每層代表 Dockerfile 中的一條指令,比如下面的 Dockerfile 文件:
###
```
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
```
###
> Dockerfile 介紹
> `Dockerfile`是一個文本文件,其內包含了一條條的指令,每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。Dockerfile 是我們用來構建 Docker 鏡像的一個說明文檔,也是我們學習的重點,必須要要掌握如何編寫 Dockerfile。
###
這里的 Dockerfile 包含4條命令,其中每一行就創建了一層,`FROM`語句從`ubuntu:18.04`這個基礎鏡像創建一個層開始,`COPY`命令從 Docker 客戶端的當前目錄添加一些新的文件,`RUN`指令使用 make 命令構建應用,最后一層指定在容器中運行什么命令。
###
鏡像就是由這些層一層一層堆疊起來的,鏡像中的這些層都是只讀的,當我們運行容器的時候,就可以在這些基礎層之上添加新的可寫層,也就是我們通常說的`容器層`,對于運行中的容器所做的所有更改(比如寫入新文件、修改現有文件、刪除文件)都將寫入這個容器層,下面顯示了基于 Ubuntu 15.04 鏡像運行的容器層的結構:
###

###
容器和鏡像之間的主要區別就是容器在鏡像頂部由一個可寫層,在容器中的所有操作都會存儲在這個容器層中,刪除容器后,容器層也會被刪除,但是鏡像不會變化。正因為每個容器都有自己的可寫容器層,所有更改都存儲在自己的容器層中,所以多個容器之間可以共享同一基礎鏡像的訪問,但仍然具有自己的數據狀態。如下圖演示了多個容器共享同一鏡像的請情況:
###

###
Docker 使用存儲驅動程序來管理鏡像層和可寫容器層的內容,每個存儲驅動程序的處理方式不同,但是所有的驅動都使用可堆疊的鏡像層和寫時復制(Cow)策略,這些驅動程序管理的這些層其實就是 UnionFS(聯合文件系統),現在 Docker 主要支持的存儲驅動有 aufs、devicemapper、overlay、overlay2、zfs 和 vfs 等等,在新的 Docker 版本中,overlay2 取代了 aufs 成為了推薦的存儲驅動。
###
> Copy-on-write
> 寫時復制是一種共享和復制文件的策略,可以最大程度地提高效率,如果文件或目錄位于鏡像的較低層中,而另一層(包括可寫層)需要對其進行讀取訪問,則它直接使用現有文件即可。另一層第一次需要修改文件時(在構建鏡像或運行容器時),將文件復制到該層并進行修改。這樣可以將 I/O 和每個后續層的大小最小化。
>
> 選擇存儲驅動
> 對于 Docker 如何選擇一個合適的存儲驅動程序,可以查看官方文檔[Docker storage drivers](https://docs.docker.com/storage/storagedriver/select-storage-driver/)。
###
### Docker 架構
###
ocker 使用 C/S (客戶端/服務器)體系的架構,Docker 客戶端與 Docker 守護進程(Dockerd)通信,Docker 守護進程負責構建,運行和分發 Docker 容器。Docker 客戶端和守護進程可以在同一個系統上運行,也可以將 Docker 客戶端連接到遠程 Docker 守護進程。Docker 客戶端和守護進程使用 REST API 通過 UNIX 套接字或網絡接口進行通信。
###
