1. 程式人生 > >每天5分鐘玩轉docker容器——讀書筆記

每天5分鐘玩轉docker容器——讀書筆記

這兩天草草的過了一遍cloudman的《每天5分鐘玩轉容器技術》,鞏固了一下docker的知識,並且學習到了不少新內容,對於docker的理解更加的深入了一些,特此記錄一下在學習過程中比較在意的內容,以供以後翻閱。

容器runtime:runtime 是容器真正執行的地方。runtime 需要跟作業系統 kernel 緊密協作,為容器提供執行環境。
可以類比java,Java 程式就好比是容器,JVM 則好比是 runtime。JVM 為 Java 程式提供執行環境。同樣的道理,容器只有在 runtime 中才能執行。
這裡寫圖片描述
lxc、runc 和 rkt 是目前主流的三種容器 runtime。

容器管理工具:容器管理工具對內與 runtime 互動,對外為使用者提供 interface,比如 CLI。
這裡寫圖片描述
runc 的管理工具是 docker engine。docker engine 包含後臺 deamon 和 cli 兩個部分。我們通常提到 Docker,一般就是指的 docker engine。

容器定義工具:容器定義工具允許使用者定義容器的內容和屬性,這樣容器就能夠被儲存,共享和重建。
這裡寫圖片描述
docker image 是 docker 容器的模板,runtime 依據 docker image 建立容器。
dockerfile 是包含若干命令的文字檔案,可以通過這些命令創建出 docker image。

容器編排引擎:所謂編排(orchestration),通常包括容器管理、排程、叢集定義和服務發現等。通過容器編排引擎,容器被有機的組合成微服務應用,實現業務需求。
這裡寫圖片描述

容器支援技術:被用於支援基於容器的基礎設施。
這裡寫圖片描述

容器網路
這裡寫圖片描述

服務發現
動態變化是微服務應用的一大特點。當負載增加時,叢集會自動建立新的容器;負載減小,多餘的容器會被銷燬。容器也會根據 host 的資源使用情況在不同 host 中遷移,容器的 IP 和埠也會隨之發生變化。在這種動態的環境下,必須要有一種機制讓 client 能夠知道如何訪問容器提供的服務。這就是服務發現技術要完成的工作。
服務發現會儲存容器叢集中所有微服務最新的資訊,比如 IP 和埠,並對外提供 API,提供服務查詢功能。
這裡寫圖片描述

什麼是容器?

容器是一種輕量級、可移植、自包含的軟體打包技術,使應用程式可以在幾乎任何地方以相同的方式執行。
容器由兩部分組成:

  • 應用程式本身
  • 依賴:比如應用程式需要的庫或其他軟體

容器在Host作業系統的使用者空間中執行,與作業系統的其他程序隔離。這一點顯著區別於虛擬機器。

Docker架構
這裡寫圖片描述
Docker 採用的是 Client/Server 架構。客戶端向伺服器傳送請求,伺服器負責構建、執行和分發容器。客戶端和伺服器可以執行在同一個 Host 上,客戶端也可以通過 socket 或 REST API 與遠端的伺服器通訊。

Docker客戶端:最常用的 Docker 客戶端是 docker 命令。通過 docker 我們可以方便地在 Host 上構建和執行容器。
Docker伺服器:Docker daemon 是伺服器元件,執行在 Docker host 上,負責建立、執行、監控容器,構建、儲存映象。預設配置下,Docker daemon 只能響應來自本地 Host 的客戶端請求。如果要允許遠端客戶端請求,需要在配置檔案中開啟 TCP 監聽。
Docker映象:可將 Docker 映象看著只讀模板,通過它可以建立 Docker 容器。我們可以將映象的內容和建立步驟描述在一個文字檔案中,這個檔案被稱作 Dockerfile,通過執行 docker build <docker-file> 命令可以構建出 Docker 映象。
Docker容器:Docker 容器就是 Docker 映象的執行例項。使用者可以通過 CLI(docker)或是 API 啟動、停止、移動或刪除容器。可以這麼認為,對於應用軟體,映象是軟體生命週期的構建和打包階段,而容器則是啟動和執行階段。

base映象:們希望映象能提供一個基本的作業系統環境,使用者可以根據需要安裝和配置軟體。這樣的映象我們稱作 base 映象:

  • 不依賴其他映象從scratch構建
  • 其他映象可以作為基礎進行擴充套件

所以能稱作 base 映象的通常都是各種 Linux 發行版的 Docker 映象比如 Ubuntu, Debian, CentOS 等。

Linux 作業系統由核心空間和使用者空間組成。
這裡寫圖片描述
Linux 剛啟動時會載入 bootfs 檔案系統,之後 bootfs 會被解除安裝掉。使用者空間的檔案系統是 rootfs包含我們熟悉的 /dev, /proc, /bin 等目錄。

對於 base 映象來說底層直接用 Host 的 kernel自己只需要提供 rootfs 就行了。

base 映象提供的是最小安裝的 Linux 發行版。

不同 Linux 發行版的區別主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服務 , apt 管理軟體包;而 CentOS 7 使用 systemd 和 yum。
所有容器都共用 host 的 kernel在容器中沒辦法對 kernel 升級。

新映象是從 base 映象一層一層疊加生成的。每安裝一個軟體,就在現有映象的基礎上增加一層。
這裡寫圖片描述
這種分層結構的最大好處就是:共享資源

容器 Copy-on-Write 特性:只有當需要修改時才複製一份資料,這種特性被稱作 Copy-on-Write。

當容器啟動時,一個新的可寫層被載入到映象的頂部。這一層通常被稱作“容器層”,“容器層”之下的都叫“映象層”。

這裡寫圖片描述
所有對容器的改動 - 無論新增、刪除、還是修改檔案都只會發生在容器層中。

只有容器層是可寫的,容器層下面的所有映象層都是隻讀的。

映象層數量可能會很多,所有映象層會聯合在一起組成一個統一的檔案系統。如果不同層中有一個相同路徑的檔案,比如 /a,上層的 /a 會覆蓋下層的 /a,也就是說使用者只能訪問到上層中的檔案 /a。在容器層中,使用者看到的是一個疊加之後的檔案系統。

  • 新增檔案:在容器中建立檔案時,新檔案被新增到容器層中。
  • 讀取檔案:在容器中讀取某個檔案時,Docker 會從上往下依次在各映象層中查詢此檔案。一旦找到,立即將其複製到容器層,然後開啟並讀入記憶體。
  • 修改檔案:在容器中修改已存在的檔案時,Docker 會從上往下依次在各映象層中查詢此檔案。一旦找到,立即將其複製到容器層,然後修改之。
  • 刪除檔案:在容器中刪除檔案時,Docker 也是從上往下依次在映象層中查詢此檔案。找到後,會在容器層中記錄下此刪除操作。

可見,容器層儲存的是映象變化的部分,不會對映象本身進行任何修改。
容器層記錄對映象的修改,所有映象層都是隻讀的,不會被容器修改,所以映象可以被多個容器共享

使用docker commits命令來建立新映象(通過進入老映象進行修改來構建新映象) 不推薦

即便是用 Dockerfile(推薦方法)構建映象,底層也是 docker commit 一層一層構建新映象的。

docker build命令可以通過-f引數指定Dockerfile的位置。
映象的構建過程:首先 Docker 將 build context 中的所有檔案傳送給 Docker daemon。build context 為映象構建提供所需要的檔案或目錄。Dockerfile 中的 ADDCOPY 等命令可以將 build context 中的檔案新增到映象。
所以,使用 build context 就得小心了,不要將多餘檔案放到 build context,特別不要把 //usr 作為 build context,否則構建過程會相當緩慢甚至失敗。

映象的快取:Docker 會快取已有映象的映象層,構建新映象時,如果某映象層已經存在,就直接使用,無需重新建立。

但是注意:Dockerfile中每一個指令都會建立一個映象層,上層是依賴於下層的。無論什麼時候,只要某一層發生變化,其上面所有層的快取都會失效。也就是說,如果我們改變Dockerfile 指令的執行順序,或者修改或新增指令,都會使快取失效。

除錯Dockerfile:通過執行最新的映象定位指令失敗的原因。

Dockerfile的書寫點這還有這

映象tag的社群方案
假設我們現在釋出了一個映象 myimage,版本為 v1.9.1。那麼我們可以給映象打上四個 tag:1.9.1、1.9、1 和 latest。
這種 tag 方案使映象的版本很直觀,使用者在選擇非常靈活:

  • myimage:1 始終指向 1 這個分支中最新的映象。
  • myimage:1.9 始終指向 1.9.x 中最新的映象。
  • myimage:latest 始終指向所有版本中最新的映象。
  • 如果想使用特定版本,可以選擇 myimage:1.9.1、myimage:1.9.2 或 myimage:2.0.0。

搭建本地RegistryHere

容器在 docker host 中實際上是一個程序,docker stop 命令本質上是向該程序傳送一個 SIGTERM 訊號。如果想快速停止容器,可使用 docker kill 命令,其作用是向容器程序傳送 SIGKILL 訊號。

--restart=always 意味著無論容器因何種原因退出(包括正常退出),就立即重啟。該引數的形式還可以是 --restart=on-failure:3,意思是如果啟動程序退出程式碼非0,則重啟容器,最多重啟3次。

有時我們只是希望暫時讓容器暫停工作一段時間,比如要對容器的檔案系統打個快照,或者 dcoker host 需要使用 CPU,這時可以執行 docker pause。處於暫停狀態的容器不會佔用 CPU 資源,直到通過 docker unpause 恢復執行。

使用 docker 一段時間後,host 上可能會有大量已經退出了的容器。這些容器依然會佔用 host 的檔案系統資源,如果確認不會再重啟此類容器,可以通過 docker rm 刪除。
如果希望批量刪除所有已經退出的容器,可以執行如下命令:

docker rm -v $(docker ps -aq -f status=exited)

順便說一句:docker rm 是刪除容器,而 docker rmi 是刪除映象。

與作業系統類似,容器可使用的記憶體包括兩部分:實體記憶體和swap。通過-m--memory:設定記憶體的使用限額;--memory-swap:設定 記憶體+swap 的使用限額。
通過 cpu share 可以設定容器使用 CPU 的優先順序。

底層實現技術

cgroup 和 namespace 是最重要的兩種技術。cgroup 實現資源限額, namespace 實現資源隔離。

cgroup 全稱 Control Group。Linux 作業系統通過 cgroup 可以設定程序使用 CPU、記憶體 和 IO 資源的限額。

namespace 管理著 host 中全域性唯一的資源,並可以讓每個容器都覺得只有自己在使用它。換句話說,namespace 實現了容器間資源的隔離
Linux 使用了六種 namespace,分別對應六種資源:Mount、UTS、IPC、PID、Network 和 User。

  • Mount namespace 讓容器看上去擁有整個檔案系統。
  • UTS namespace 讓容器有自己的 hostname。 預設情況下,容器的 hostname 是它的短ID,可以通過 -h--hostname 引數設定。
  • IPC namespace 讓容器擁有自己的共享記憶體和訊號量(semaphore)來實現程序間通訊,而不會與 host 和其他容器的 IPC 混在一起。
  • 容器中程序的 PID 不同於 host 中對應程序的 PID,容器中 PID=1 的程序當然也不是 host 的 init 程序。也就是說:容器擁有自己獨立的一套 PID,這就是 PID namespace 提供的功能。
  • Network namespace 讓容器擁有自己獨立的網絡卡、IP、路由等資源。
  • User namespace 讓容器能夠管理自己的使用者,host 不能看到容器中建立的使用者。

Dockers提供幾種原生網路,Docker安裝時會自動在host上建立三個網路,分別為none、host、bridge。預設為bridge網路。

關於bridge的較詳細介紹:Click Here

除了 none, host, bridge 這三個自動建立的網路,使用者也可以根據業務需要建立 user-defined 網路。Docker 提供三種 user-defined 網路驅動:bridge, overlay 和 macvlan。

容器之間可通過 IP,Docker DNS Server 或 joined 容器三種方式通訊。

容器預設就能訪問外部世界,這裡指的是容器網路外的網路環境。
實現的原理是做了一次NAT,如下圖:
這裡寫圖片描述

  1. busybox 傳送 ping 包:172.17.0.2 > www.bing.com。
  2. docker0 收到包,發現是傳送到外網的,交給 NAT 處理。
  3. NAT 將源地址換成 enp0s3 的 IP:10.0.2.15 > www.bing.com。
  4. ping 包從 enp0s3 傳送出去,到達 www.bing.com。

而外部網路訪問容器則是通過:埠對映

每一個對映的埠,host 都會啟動一個 docker-proxy 程序來處理訪問容器的流量:
這裡寫圖片描述
以 0.0.0.0:32773->80/tcp 為例分析整個過程:

這裡寫圖片描述

  1. docker-proxy 監聽 host 的 32773 埠。
  2. 當 curl 訪問 10.0.2.15:32773 時,docker-proxy 轉發給容器 172.17.0.2:80。
  3. httpd 容器響應請求並返回結果。

Docker 為容器提供了兩種存放資料的資源:

  • 由 storage driver 管理的映象層和容器層。
  • Data Volume。

storage driver
這裡寫圖片描述
分層結構使映象和容器的建立、共享以及分發變得非常高效,而這些都要歸功於 Docker storage driver。正是 storage driver 實現了多層資料的堆疊併為使用者提供一個單一的合併之後的統一檢視。
Docker 支援多種 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。

優先使用 Linux 發行版預設的 storage driver。

對於某些容器,直接將資料放在由 storage driver 維護的層中是很好的選擇,比如那些無狀態的應用。無狀態意味著容器沒有需要持久化的資料,隨時可以從映象直接建立。

但對於另一類應用這種方式就不合適了,它們有持久化資料的需求,容器啟動時需要載入已有的資料,容器銷燬時希望保留產生的新資料,也就是說,這類容器是有狀態的。這就要用到 Docker 的另一種儲存機制:Data Volume

Data Volume 本質上是 Docker Host 檔案系統中的目錄或檔案,能夠直接被 mount 到容器的檔案系統中。Data Volume 有以下特點:

  • Data Volume 是目錄或檔案,而非沒有格式化的磁碟(塊裝置)。
  • 容器可以讀寫 volume 中的資料。
  • volume 資料可以被永久的儲存,即使使用它的容器已經銷燬。

docker 提供了兩種型別的 volume:bind mountdocker managed volume

bind mountHere
docker managed volumeHere

兩者的不同點:
這裡寫圖片描述

共享資料我們可以使用volume container或data-packed volume container。

備份、恢復、遷移和銷燬Data Volume:Click Here

這裡寫圖片描述
跨主機網路:Here

  • docker 原生的 overlay 和 macvlan。
  • 第三方方案:常用的包括 flannel、weave 和 calico。

libnetwork 是 docker 容器網路庫,最核心的內容是其定義的 Container Network Model (CNM),這個模型對容器網路進行了抽象,由以下三類元件組成:

  • Sandbox 是容器的網路棧,包含容器的 interface、路由表和 DNS 設定。 Linux Network Namespace 是 Sandbox 的標準實現。Sandbox 可以包含來自不同 Network 的 Endpoint。
  • Endpoint 的作用是將 Sandbox 接入 Network。Endpoint 的典型實現是 veth pair,後面我們會舉例。一個 Endpoint 只能屬於一個網路,也只能屬於一個 Sandbox。
  • Network 包含一組 Endpoint,同一 Network 的 Endpoint 可以直接通訊。Network 的實現可以是 Linux Bridge、VLAN 等。

這裡寫圖片描述

flannel 是 CoreOS 開發的容器網路解決方案。flannel 為每個 host 分配一個 subnet,容器從此 subnet 中分配 IP,這些 IP 可以在 host 間路由,容器間無需 NAT 和 port mapping 就可以跨主機通訊。
每個 subnet 都是從一個更大的 IP 池中劃分的,flannel 會在每個主機上執行一個叫 flanneld 的 agent,其職責就是從池子中分配 subnet。為了在各個主機間共享資訊,flannel 用 etcd(與 consul 類似的 key-value 分散式資料庫)存放網路配置、已分配的 subnet、host 的 IP 等資訊。

使用flannel來進行跨主機通訊的資料流向:
這裡寫圖片描述

從業務資料的角度看,容器可以分為兩類:無狀態(stateless)容器和有狀態(stateful)容器。

無狀態是指容器在執行過程中不需要儲存資料,每次訪問的結果不依賴上一次訪問,比如提供靜態頁面的 web 伺服器。
有狀態是指容器需要儲存資料,而且資料會發生變化,訪問的結果依賴之前請求的處理結果,最典型的就是資料庫伺服器。

簡單來講,狀態(state)就是資料,如果容器需要處理並存儲資料,它就是有狀態的,反之則無狀態。

簡單的總結到這裡也就結束了。