Docker容器學習梳理 - 基礎知識(2)

散盡浮華發表於2017-03-14

 

之前已經總結了Docker容器學習梳理--基礎知識(1),但是不夠詳細,下面再完整補充下Docker學習的一些基礎。

Docker是個什麼東西

Docker是一個程式執行、測試、交付的開放平臺,Docker被設計為能夠使你快速地交付應用。
在Docker中,你可以將你的程式分為不同的基礎部分,對於每一個基礎部分都可以當做一個應用程式來管理。
Docker能夠幫助你快速地測試、快速地編碼、快速地交付,並且縮短你從編碼到執行應用的週期。

Docker使用輕量級的容器虛擬化平臺,並且結合工作流和工具,來幫助你管理、部署你的應用程式。
Docker在其核心,Docker實現了讓幾乎任何程式都可以在一個安全、隔離的容器中執行。安全和隔離可以使你可以同時在機器上執行多個容器。
Docker容器輕量級的特性,意味著你可以得到更多的硬體效能。

圍繞著Docker容器的虛擬化工具和平臺,可以在以下幾個方面為你提供幫助:
1)幫助你把應用程式(包括其餘的支援元件)放入到Docker容器中。
2)分發和轉移你的容器至你的團隊其它成員來進行進一步的開發和測試。
3)部署這些應用程式至你的生產環境,不論是本地的資料中心還是雲平臺。

Docker的用途

1)快速交付你的應用程式
Docker可以為你的開發過程提供完美的幫助。Docker允許開發者在本地包含了應用程式和服務的容器進行開發,之後可以整合到連續的一體化和部署工作流中。
舉個例子,開發者們在本地編寫程式碼並且使用Docker和同事分享其開發棧。當開發者們準備好了之後,他們可以將程式碼和開發棧推送到測試環境中,在該環境進行一切所需要的測試。從測試環境中,你可以將Docker映象推送到伺服器上進行部署。

2)開發和擴充更加簡單
Docker的以容器為基礎的平臺允許高度可移植的工作。Docker容器可以在開發者機器上執行,也可以在實體或者虛擬機器上執行,也可以在雲平臺上執行。
Docker的可移植、輕量特性同樣讓動態地管理負載更加簡單。你可以用Docker快速地增加應用規模或者關閉應用程式和服務。Docker的快速意味著變動幾乎是實時的。

3)達到高密度和更多負載
Docker輕巧快速,它提供了一個可行的、符合成本效益的替代基於虛擬機器管理程式的虛擬機器。這在高密度的環境下尤其有用。例如,構建你自己的雲平臺或者PaaS,在中小的部署環境下同樣可以獲取到更多的資源效能。

Docker的主要組成

Docker有兩個主要的部件:
Docker:     開源的容器虛擬化平臺。
Docker Hub: 用於分享、管理Docker容器的Docker SaaS平臺。

Docker的架構

Docker使用客戶端-伺服器(client-server)架構模式。
Docker 客戶端會與Docker守護程式進行通訊。Docker 守護程式會處理複雜繁重的任務,例如建立、執行、釋出你的 Docker 容器。
Docker 客戶端和守護程式可以執行在同一個系統上,當然也可以使用Docker客戶端去連線一個遠端的 Docker 守護程式。
Docker 客戶端和守護程式之間通過socket或者RESTful API進行通訊。

1)Docker守護程式
如上圖所示,Docker守護程式執行在一臺主機上。使用者並不直接和守護程式進行互動,而是通過 Docker 客戶端間接和其通訊。

2)Docker 客戶端
Docker 客戶端,實際上是 docker 的二進位制程式,是主要的使用者與 Docker 互動方式。它接收使用者指令並且與背後的 Docker 守護程式通訊,如此來回往復。

3)Docker 內部
要理解 Docker 內部構建,需要理解以下三種部件:
Docker 映象 - Docker images
Docker 倉庫 - Docker registeries
Docker 容器 - Docker containers

Docker 映象
Docker 映象是Docker容器執行時的只讀模板,每一個映象由一系列的層 (layers) 組成。Docker 使用 UnionFS 來將這些層聯合到單獨的映象中。UnionFS 允許獨立檔案系統中的檔案和資料夾(稱之為分支)被透明覆蓋,形成一個單獨連貫的檔案系統。正因為有了這些層的存在,Docker 是如此的輕量。當你改變了一個 Docker 映象,比如升級到某個程式到新的版本,一個新的層會被建立。因此,不用替換整個原先的映象或者重新建立(在使用虛擬機器的時候你可能會這麼做),只是一個新 的層被新增或升級了。現在你不用重新發布整個映象,只需要升級,層使得分發 Docker 映象變得簡單和快速。

Docker 倉庫
Docker 倉庫用來儲存映象,可以理解為程式碼控制中的程式碼倉庫。同樣的,Docker 倉庫也有公有和私有的概念。公有的 Docker 倉庫名字是 Docker Hub。Docker Hub 提供了龐大的映象集合供使用。這些映象可以是自己建立,或者在別人的映象基礎上建立。Docker 倉庫是 Docker 的分發部分。

Docker 容器
Docker 容器和資料夾很類似,一個Docker容器包含了所有的某個應用執行所需要的環境。每一個 Docker 容器都是從 Docker 映象建立的。Docker 容器可以執行、開始、停止、移動和刪除。每一個 Docker 容器都是獨立和安全的應用平臺,Docker 容器是 Docker 的執行部分。

4)libcontainer
Docker 從 0.9 版本開始使用 libcontainer 替代 lxc,libcontainer 和 Linux 系統的互動圖如下:

5)名稱空間「Namespaces」

1)pid namespace
不同使用者的程式就是通過pid namespace隔離開的,且不同 namespace 中可以有相同 PID。
具有以下特徵:
每個namespace中的pid是有自己的pid=1的程式(類似 /sbin/init 程式)
每個 namespace 中的程式只能影響自己的同一個 namespace 或子 namespace 中的程式
因為 /proc 包含正在執行的程式,因此在 container 中的 pseudo-filesystem 的 /proc 目錄只能看到自己namespace 中的程式
因為 namespace 允許巢狀,父 namespace 可以影響子 namespace 的程式,所以子 namespace 的程式可以在父namespace中看到,但是具有不同的 pid

2)mnt namespace
類似 chroot,將一個程式放到一個特定的目錄執行。mnt namespace 允許不同namespace的程式看到的檔案結構不同,這樣每個namespace 中的程式所看到的檔案目錄就被隔離開了。同 chroot 不同,每個 namespace 中的 container 在 /proc/mounts 的資訊只包含所在namespace的mount point。

3)net namespace
網路隔離是通過 net namespace 實現的, 每個 net namespace 有獨立的 network devices, IP addresses, IP routing tables, /proc/net 目錄。這樣每個 container 的網路就能隔離開來。 docker 預設採用 veth 的方式將 container 中的虛擬網路卡同 host 上的一個 docker bridge 連線在一起。

4)uts namespace
UTS ("UNIX Time-sharing System") namespace 允許每個 container 擁有獨立的 hostname 和 domain name, 使其在網路上可以被視作一個獨立的節點而非 Host 上的一個程式。

5)ipc namespace
container 中程式互動還是採用 Linux 常見的程式間互動方法 (interprocess communication - IPC), 包括常見的訊號量、訊息佇列和共享記憶體。然而同 VM 不同,container 的程式間互動實際上還是 host 上具有相同 pid namespace 中的程式間互動,因此需要在IPC資源申請時加入 namespace 資訊 - 每個 IPC 資源有一個唯一的 32bit ID。

6)user namespace
每個 container 可以有不同的 user 和 group id, 也就是說可以以 container 內部的使用者在 container 內部執行程式而非 Host 上的使用者。

有了以上6種namespace從程式、網路、IPC、檔案系統、UTS 和使用者角度的隔離,一個 container 就可以對外展現出一個獨立計算機的能力,並且不同container從OS層面實現了隔離。然而不同 namespace 之間資源還是相互競爭的,仍然需要類似ulimit 來管理每個container所能使用的資源。

6)資源配額「cgroups」

cgroups 
實現了對資源的配額和度量。 cgroups 的使用非常簡單,提供類似檔案的介面,在 /cgroup 目錄下新建一個資料夾即可新建一個 group,在此資料夾中新建 task 檔案,並將 pid 寫入該檔案,即可實現對該程式的資源控制。具體的資源配置選項可以在該資料夾中新建子 subsystem ,{子系統字首}.{資源項} 是典型的配置方法, 如 memory.usageinbytes 就定義了該 group 在 subsystem memory 中的一個記憶體限制選項。 
另外,cgroups 中的 subsystem 可以隨意組合,一個 subsystem 可以在不同的 group 中,也可以一個 group 包含多個 subsystem - 也就是說一個 subsystem。

memory
記憶體相關的限制

cpu
在 cgroup 中,並不能像硬體虛擬化方案一樣能夠定義 CPU 能力,但是能夠定義 CPU 輪轉的優先順序,因此具有較高 CPU 優先順序的程式會更可能得到 CPU 運算。 通過將引數寫入 cpu.shares ,即可定義改 cgroup 的 CPU 優先順序 - 這裡是一個相對權重,而非絕對值

blkio
block IO 相關的統計和限制,byte/operation 統計和限制 (IOPS 等),讀寫速度限制等,但是這裡主要統計的都是同步 IO

devices
裝置許可權限制

Docker的工作原理

1)可以建立一個容納應用程式的容器。
2)可以從Docker映象建立Docker容器來執行應用程式。
3)可以通過Docker Hub或者自己的Docker倉庫分享Docker映象。

Docker映象是如何工作的?

Docker映象是Docker容器執行時的只讀模板,每一個映象由一系列的層(layers)組成;
Docker使用UnionFS(聯合檔案系統)來將這些層聯合到一二映象中,UnionFS檔案系統允許獨立檔案系統中的檔案和資料夾(稱之為分支)被透明覆蓋,形成一個單獨連貫的檔案系統。

正因為有了這些層(layers)的存在,Docker才會如此的輕量。當你改變了一個Docker映象,比如升級到某個程式到新的版本,一個新的層會被建立。因此,不用替換整個原先的映象或者重新建立(在使用虛擬機器的時候你可能會這麼做),只是一個新的層被新增或升級了。所以你不用重新發布整個映象,只需要升級。層使得分發Docker映象變得簡單和快速。

每個映象都是從一個基礎的映象開始的,比如ubuntu,一個基礎的Ubuntu映象,或者是Centos,一個基礎的Centos映象。你可以使用你自己的映象作為新映象的基礎,例如你有一個基礎的安裝了Nginx的映象,你可以使用該映象來建立你的Web應用程式映象。(Docker通常從Docker Hub獲取基礎映象)

Docker映象從這些基礎的映象建立,通過一種簡單、具有描述性的步驟,我們稱之為 指令(instructions)。 
每一個指令會在映象中建立一個新的層,指令可以包含這些動作:
1)執行一個命令。
2)增加檔案或者資料夾。
3)建立一個環境變數。
5)當執行容器的時候哪些程式會執行。

這些指令儲存在Dockerfile檔案中。當你需要建立映象的時候,Docker可以從Dockerfile中讀取這些指令並且執行,然後返回一個最終的映象。

Docker倉庫是如何工作的?

Docker倉庫是Docker映象的儲存倉庫。可以推送映象到Docker倉庫中,然後在Docker客戶端,可以從Docker倉庫中搜尋映象。

Docker容器是如何工作的?

一個Docker容器包含了一個作業系統、使用者新增的檔案和後設資料(meta-data)。每個容器都是從映象建立的,映象告訴Docker容器內包含了什麼,當容器啟動時執行什麼程式,還有許多配置資料。
Docker映象是隻讀的,當Docker執行一個從映象建立的容器,它會在映象頂部新增一個可讀寫的層,應用程式可以在這裡執行。

當執行docker容器時發生了什麼?

使用docker命令時,Docker客戶端都告訴Docker守護程式執行一個容器。

$ sudo docker run -i -t ubuntu /bin/bash
可以來分析這個命令,Docker客戶端使用docker命令來執行,run參數列明客戶端要執行一個新的容器。
Docker客戶端要執行一個容器需要告訴Docker守護程式的最小引數資訊是:
1)這個容器從哪個映象建立,這裡是ubuntu,基礎的Ubuntu映象。
2)在容器中要執行的命令,這裡是/bin/bash,在容器中執行Bash shell。

那麼執行這個命令之後在底層發生了什麼呢?
按照順序,Docker做了這些事情:
1)拉取ubuntu映象: Docker檢查ubuntu映象是否存在,如果在本地沒有該映象,Docker會從Docker Hub下載。如果映象已經存在,Docker會使用它來建立新的容器。
2)建立新的容器: 當Docker有了這個映象之後,Docker會用它來建立一個新的容器。
3)分配檔案系統並且掛載一個可讀寫的層: 容器會在這個檔案系統中建立,並且一個可讀寫的層被新增到映象中。
4)分配網路/橋接介面: 建立一個允許容器與本地主機通訊的網路介面。
5)設定一個IP地址: 從池中尋找一個可用的IP地址並且服加到容器上。
6)執行你指定的程式: 執行指定的程式。
7)捕獲並且提供應用輸出: 連線並且記錄標準輸出、輸入和錯誤讓你可以看到你的程式是如何執行的。

由此你就可以擁有一個執行著的Docker容器了!從這裡開始你可以管理你的容器,與應用互動,應用完成之後,可以停止或者刪除你的容器。

Docker的底層技術

Docker使用Go語言編寫,並且使用了一系列Linux核心提供的效能來實現我們已經看到的這些功能。

名稱空間(Namespaces)(這個上面也詳細說明了)
Docker充分利用了一項稱為namespaces的技術來提供隔離的工作空間,我們稱之為 container(容器)。當你執行一個容器的時候,Docker為該容器建立了一個名稱空間集合。
這樣提供了一個隔離層,每一個應用在它們自己的名稱空間中執行而且不會訪問到名稱空間之外。
一些Docker使用到的名稱空間有:
pid名稱空間: 使用在程式隔離(PID: Process ID)。
net名稱空間: 使用在管理網路介面(NET: Networking)。
ipc名稱空間: 使用在管理程式間通訊資源 (IPC: InterProcess Communication)。
mnt名稱空間: 使用在管理掛載點 (MNT: Mount)。
uts名稱空間: 使用在隔離核心和版本標識 (UTS: Unix Timesharing System)。

群組控制
Docker還使用到了cgroups技術來管理群組。使應用隔離執行的關鍵是讓它們只使用你想要的資源。這樣可以確保在機器上執行的容器都是良民(good multi-tenant citizens)。群組控制允許Docker分享或者限制容器使用硬體資源。例如,限制指定的容器的內容使用。

聯合檔案系統
聯合檔案系統(UnionFS)是用來操作建立層的,使它們輕巧快速。Docker使用UnionFS提供容器的構造塊。Docker可以使用很多種類的UnionFS包括AUFS, btrfs, vfs, and DeviceMapper。

容器格式
Docker連線這些組建到一個包裝中,稱為一個 container format(容器格式)。預設的容器格式是libcontainer。Docker同樣支援傳統的Linux容器使用LXC。在未來,Docker也許會支援其它的容器格式,例如與BSD Jails 或 Solaris Zone整合。

Docker的安裝
這個可以參考:Linux下部署docker記錄(0)-基礎環境安裝

Docker的預設路徑:
Docker執行時根目錄:/var/lib/docker    
Docker的配置檔案:在ubuntu中的位置是:/etc/default/docker;在centos7中的位置是:/etc/sysconfig/docker

Docker映象使用
Docker HUB : Docker映象首頁,包括官方映象和其它公開映象。Docker Hub上最受歡迎的10大映象(通過Docker registry API獲取不了映象被pull的個數,只能通過映象的star數量,來衡量映象的流行度。毫無疑問,擁有最高star數量的庫都是官方庫):

因為國情的原因,國內下載 Docker HUB 官方的相關映象比較慢,可以使用國內(docker.io)的一些映象加速器,映象保持和官方一致,關鍵是速度塊,推薦使用。
這裡需要明確一個問題,就是Mirror與Private Registry的區別。二者有著本質的差別:

1)Private Registry(私有倉庫)是開發者或者企業自建的映象儲存庫,通常用來儲存企業內部的 Docker 映象,用於內部開發流程和產品的釋出、版本控制。 
2)Mirror是一種代理中轉服務,我們(比如daocloud)提供的Mirror服務,直接對接Docker Hub的官方Registry。Docker Hub 上有數以十萬計的各類 Docker 映象。 
3)在使用Private Registry時,需要在Docker Pull 或Dockerfile中直接鍵入Private Registry 的地址,通常這樣會導致與 Private Registry 的繫結,缺乏靈活性。 
4)使用 Mirror 服務,只需要在 Docker 守護程式(Daemon)的配置檔案中加入 Mirror 引數,即可在全域性範圍內透明的訪問官方的 Docker Hub,避免了對 Dockerfile 映象引用來源的修改。
5)簡單來說,Mirror類似CDN,本質是官方的cache;Private Registry類似私服,跟官方沒什麼關係。對使用者來說,由於使用者是要拖docker hub上的image,對應的是Mirror。 yum/apt-get的Mirror又有點不一樣,它其實是把官方的庫檔案整個拖到自己的伺服器上做映象(不管有沒有用),並定時與官方做同步;而Docker Mirror只會快取曾經使用過的image。

目前國內訪問docker hub速度上有點尷尬,使用docker Mirror勢在必行。
現有國內提供docker映象加速服務的商家有不少,下面重點介紹幾家:

1)ustc的映象
ustc是老牌的linux映象服務提供者了,還在遙遠的ubuntu 5.04版本的時候就在用。之前在blog裡有提到可以用ustc的docker倉庫映象.
使用方法參考ustc docker映象使用幫助

ustc的docker映象加速器速度很不錯,一直用的挺happy。ustc docker mirror的優勢之一就是不需要註冊,真正是公共服務啊。

----------------------------------這裡順便說下在新版Docker裡使用ustc的做法-------------------------------------
新版的Docker配置方法:
[root@localhost ~]# vim /etc/docker/daemon.json   //如果沒有該檔案的話,就手動建立。在該檔案裡新增下面內容
{
  "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}

然後就可以直接docker pull下載映象了,速度槓槓滴!!!
[root@localhost docker]# docker pull ubuntu
Using default tag: latest
Trying to pull repository docker.io/library/ubuntu ... 
latest: Pulling from docker.io/library/ubuntu
d54efb8db41d: Pull complete 
f8b845f45a87: Pull complete 
e8db7bf7c39f: Pull complete 
9654c40e9079: Pull complete 
6d9ef359eaaa: Pull complete 
Digest: sha256:dd7808d8792c9841d0b460122f1acf0a2dd1f56404f8d1e56298048885e45535
Status: Downloaded newer image for docker.io/ubuntu:latest

[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/ubuntu    latest              0ef2e08ed3fa        2 weeks ago         130 MB
----------------------------------------------------------------------------------------------------------------------

2)daocloud映象
DaoCloud也提供了docker加速器,但是跟ustc不同,需要使用者註冊後才能使用,並且每月限制流量10GB。linux上使用比較簡單,一條指令碼命令搞定:

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://{your_id}.m.daocloud.io

實際上面的指令碼執行後,改的是/usr/lib/systemd/system/docker.service檔案,加了個–registry-mirror引數。如果不執行上面的指令碼命令,可以如下直接修改這個檔案也可

ExecStart=/usr/bin/docker-current daemon --registry-mirror=http://{your_id}.m.daocloud.io\

設定後,需要重新載入配置&重啟:

# systemctl enable docker
# systemctl daemon-reload
# systemctl restart docker

但是最近使用DaoCloud的docker加速器體驗非常差,加速效果不是很明顯。

3)alicloud
阿里雲也提供了docker加速器,不過比daocloud更麻煩:不光要註冊為阿里雲的使用者,還得加入開發者平臺。
不過雖然麻煩,但是它的服務還真是不錯,pull速度很溜!配置方法跟daocloud類似,也是開通加速器以後給一個url。
可以直接去改/usr/lib/systemd/system/docker.service:

ExecStart=/usr/bin/docker-current daemon --registry-mirror=https://{your_id}.mirror.aliyuncs.com\

重新載入配置&重啟:

# systemctl enable docker
# systemctl daemon-reload
# systemctl restart docker

pull的時候還是顯示docker.io,但速度一點都不docker.io。

# docker pull ubuntu
Using default tag: latest
Trying to pull repository docker.io/library/ubuntu ...
latest: Pulling from docker.io/library/ubuntu
cad964aed91d: Pull complete
3a80a22fea63: Pull complete
50de990d7957: Pull complete
61e032b8f2cb: Pull complete
9f03ce1741bf: Pull complete
Digest: sha256:28d4c5234db8d5a634d5e621c363d900f8f241240ee0a6a978784c978fe9c737
Status: Downloaded newer image for docker.io/ubuntu:latest

4)網易映象
網易也提供了Docker映象服務:網易蜂巢

$ sudo echo "DOCKER_OPTS=\"\$DOCKER_OPTS --registry-mirror=http://hub-mirror.c.163.com\"" >> /etc/default/docker
$ service docker restart

綜上,雖然aliyun docker mirror用之前的流程有點繁瑣,但服務講真是很不錯的。不過我還是傾向於用ustc。

Docker新增特性和命令
1)docker exec
Docker 1.3 版本將使用數字簽名自動驗證所有官方庫的來源和完整性。如果一個官方映象被篡改或者被破壞,目前Docker只會對這種情況發出警告而並不阻止容器的執行。

[root@localhost ~]# docker exec --help

Usage:	docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

Run a command in a running container

  -d, --detach         Detached mode: run command in the background
  --detach-keys        Override the key sequence for detaching a container
  --help               Print usage
  -i, --interactive    Keep STDIN open even if not attached
  --privileged         Give extended privileges to the command
  -t, --tty            Allocate a pseudo-TTY
  -u, --user           Username or UID (format: <name|uid>[:<group|gid>])

為了簡化除錯,可以使用docker exec命令通過 Docker API 和 CLI 在執行的容器上執行程式。

[root@localhost ~]# docker exec -it af40bd07fa0f /bin/bash

上例將在容器af40bd07fa0f中建立一個新的Bash會話。

2)docker create
可以通過docker run <image name>命令建立一個容器並執行其中的程式,因為有很多使用者要求建立容器的時候不啟動容器,所以docker create應運而生了。

[root@localhost docker]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos7             7.3.1611            d5ebea14da54        3 weeks ago         311 MB

[root@localhost docker]# docker create -t -i centos7:7.3.1611 /bin/bash
c51e8d8e9d214e0a842f267f738259538ce4ea744e76d66a2f6df29cc8d73748

上例建立了一個可寫的容器層,但是並不執行它。
[root@localhost docker]# docker ps
[root@localhost docker]# docker ps -a
CONTAINER ID        IMAGE               COMMAND               CREATED              STATUS                      PORTS                     NAMES
c51e8d8e9d21        centos7:7.3.1611    "/bin/bash"           About a minute ago   Created                                               boring_wilson

可以使用以下命令執行該容器並且登陸該容器
[root@localhost docker]# docker start -a -i c51e8d8e9d21
[root@c51e8d8e9d21 /]#

[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND               CREATED              STATUS              PORTS                     NAMES
c51e8d8e9d21        centos7:7.3.1611    "/bin/bash"           About a minute ago   Up 13 seconds                                 boring_wilson

Docker的網路配置

Dokcer 通過使用Linux橋接提供容器之間的通訊,docker0橋接介面的目的就是方便Docker管理。當Docker daemon啟動時需要做以下操作:

1)如果docker0不存在則建立
2)搜尋一個與當前路由不衝突的ip段
3)在確定的範圍中選擇 ip
4)繫結 ip 到 docker0

1)Docker四種網路模式

docker run建立Docker容器時,可以用--net選項指定容器的網路模式,Docker有以下4種網路模式:
1)host模式,使用 --net=host指定。
2)container模式,使用 --net=container:NAMEorID 指定。
3)none模式,使用 --net=none指定。
4)bridge模式,使用 --net=bridge指定,預設設定。

host 模式
如果啟動容器的時候使用 host 模式,那麼這個容器將不會獲得一個獨立的 Network Namespace,而是和宿主機共用一個 Network Namespace。容器將不會虛擬出自己的網路卡,配置自己的 IP 等,而是使用宿主機的 IP 和埠。

比如:我們在 10.10.101.105/24 的機器上用 host 模式啟動一個含有 web 應用的 Docker 容器,監聽 tcp 80 埠。當我們在容器中執行任何類似 ifconfig 命令檢視網路環境時,看到的都是宿主機上的資訊。而外界訪問容器中的應用,則直接使用 10.10.101.105:80 即可,不用任何 NAT 轉換,就如直接跑在宿主機中一樣。但是,容器的其他方面,如檔案系統、程式列表等還是和宿主機隔離的。

container 模式
這個模式指定新建立的容器和已經存在的一個容器共享一個 Network Namespace,而不是和宿主機共享。新建立的容器不會建立自己的網路卡,配置自己的 IP,而是和一個指定的容器共享 IP、埠範圍等。同樣,兩個容器除了網路方面,其他的如檔案系統、程式列表等還是隔離的。兩個容器的程式可以通過 lo 網路卡裝置通訊。

none模式
這個模式和前兩個不同。在這種模式下,Docker 容器擁有自己的 Network Namespace,但是,並不為 Docker容器進行任何網路配置。也就是說,這個 Docker 容器沒有網路卡、IP、路由等資訊。需要我們自己為 Docker 容器新增網路卡、配置 IP 等。

bridge模式

bridge 模式是 Docker 預設的網路設定,此模式會為每一個容器分配 Network Namespace、設定 IP 等,並將一個主機上的 Docker 容器連線到一個虛擬網橋上。當 Docker server 啟動時,會在主機上建立一個名為 docker0 的虛擬網橋,此主機上啟動的 Docker 容器會連線到這個虛擬網橋上。虛擬網橋的工作方式和物理交換機類似,這樣主機上的所有容器就通過交換機連在了一個二層網路中。接下來就要為容器分配 IP 了,Docker 會從 RFC1918 所定義的私有 IP 網段中,選擇一個和宿主機不同的IP地址和子網分配給 docker0,連線到 docker0 的容器就從這個子網中選擇一個未佔用的 IP 使用。如一般 Docker 會使用 172.17.0.0/16 這個網段,並將 172.17.42.1/16 分配給 docker0 網橋(在主機上使用 ifconfig 命令是可以看到 docker0 的,可以認為它是網橋的管理介面,在宿主機上作為一塊虛擬網路卡使用)

2)列出當前主機網橋

[root@localhost ~]# brctl show                                        //brctl工具依賴bridge-utils軟體包
bridge name	bridge id		STP enabled	interfaces
docker0		8000.024223421c41	no		veth4b4f1b1

3)檢視當前 docker0 ip

[root@localhost ~]# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:23ff:fe42:1c41  prefixlen 64  scopeid 0x20<link>
        ether 02:42:23:42:1c:41  txqueuelen 0  (Ethernet)
        RX packets 181704  bytes 9952837 (9.4 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 286903  bytes 598331176 (570.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

在容器執行時,每個容器都會分配一個特定的虛擬機器口並橋接到 docker0。每個容器都會配置同 docker0 ip 相同網段的專用 ip 地址,docker0 的 IP 地址被用於所有容器的預設閘道器。

4)執行一個容器

[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/ubuntu    latest              0ef2e08ed3fa        2 weeks ago         130 MB

[root@localhost ~]# docker run -t -i -d docker.io/ubuntu /bin/bash
bad5133ecd6a4c740d57621c8fdb536dd9512718a3cae371789b8063ec730944          //這個字串的前12位字元就是容器的ID

[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS                     NAMES
bad5133ecd6a        docker.io/ubuntu    "/bin/bash"           33 seconds ago      Up 32 seconds                                 suspicious_rosalind

[root@localhost ~]# brctl show
bridge name        bridge id            STP enabled       interfaces
docker0            8000.024223421c41    no                veth4b4f1b1
                                                          vetheaa4add
以上, docker0 扮演著bad5133ecd6a這個容器的虛擬介面vetheaa4add interface 橋接的角色。

使用特定範圍的IP

Docker 會嘗試尋找沒有被主機使用的 ip 段,儘管它適用於大多數情況下,但是它不是萬能的,有時候我們還是需要對ip進一步規劃。Docker允許你管理docker0橋接或者通過-b選項自定義橋接網路卡,需要安裝bridge-utils軟體包。
基本步驟如下:
1)確保docker的程式是停止的
2)建立自定義網橋
3)給網橋分配特定的ip
4)以-b的方式指定網橋

具體操作如下:
$ sudo service docker stop 
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
$ sudo brctl addbr bridge0
$ sudo ip addr add 192.168.5.1/24 dev bridge0
$ sudo ip link set dev bridge0 up
$ ip addr show bridge0
$ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker 
$ sudo service docker start

5)不同主機間容器通訊

不同容器之間的通訊可以藉助於 pipework 這個工具:
$ git clone https://github.com/jpetazzo/pipework.git
$ sudo cp -rp pipework/pipework /usr/local/bin/

安裝相應依賴軟體
$ sudo apt-get install iputils-arping bridge-utils -y

橋接網路
# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.000c291412cd       no              eth0
docker0         8000.56847afe9799       no              vetheb48029

可以刪除docker0,直接把docker的橋接指定為 br0。也可以保留使用預設的配置,這樣單主機容器之間的通訊可以通過 docker0,而跨主機不同容器之間通過pipework新建docker容器的網路卡橋接到br0,這樣跨主機容器之間就可以通訊了。
具體操作如下:
1)ubuntu操作:
$ sudo service docker stop
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
$ echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker
$ sudo service docker start

2)CentOS 7操作
$ sudo systemctl stop docker
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
$ cat /etc/sysconfig/docker | grep 'OPTIONS=' OPTIONS=--selinux-enabled -b=br0 -H fd://
$ sudo systemctl start docker

pipework

不同容器之間的通訊可以藉助於pipework這個工具給docker容器新建虛擬網路卡並繫結IP橋接到br0

$ git clone https://github.com/jpetazzo/pipework.git
$ sudo cp -rp pipework/pipework /usr/local/bin/
$ pipework 
Syntax:
pipework <hostinterface> [-i containerinterface] <guest> <ipaddr>/<subnet>[@default_gateway] [macaddr][@vlan]
pipework <hostinterface> [-i containerinterface] <guest> dhcp [macaddr][@vlan]
pipework --wait [-i containerinterface]

如果刪除了預設的 docker0 橋接,把 docker 預設橋接指定到了 br0,則最好在建立容器的時候加上--net=none,防止自動分配的 IP 在區域網中有衝突。

$ sudo docker run --rm -ti --net=none ubuntu:14.04 /bin/bash
root@a46657528059:/#
$                  # Ctrl-P + Ctrl-Q 回到宿主機 shell,容器 detach 狀態
$ sudo docker  ps
CONTAINER ID    IMAGE          COMMAND       CREATED         STATUS          PORTS      NAMES
a46657528059    ubuntu:14.04   "/bin/bash"   4 minutes ago   Up 4 minutes               hungry_lalande
$ sudo pipework br0 -i eth0 a46657528059 192.168.115.10/24@192.168.115.2 
# 預設不指定網路卡裝置名,則預設新增為 eth1
# 另外 pipework 不能新增靜態路由,如果有需求則可以在 run 的時候加上 --privileged=true 許可權在容器中手動新增,
# 但這種安全性有缺陷,可以通過 ip netns 操作
$ sudo docker attach a46657528059
root@a46657528059:/# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 86:b6:6b:e8:2e:4d  
          inet addr:192.168.115.10  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::84b6:6bff:fee8:2e4d/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:648 (648.0 B)  TX bytes:690 (690.0 B)

root@a46657528059:/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.115.2   0.0.0.0         UG    0      0        0 eth0
192.168.115.0   0.0.0.0         255.255.255.0   U     0      0        0 eth0

使用ip netns新增靜態路由,避免建立容器使用--privileged=true選項造成一些不必要的安全問題:
$ docker inspect --format="{{ .State.Pid }}" a46657528059 # 獲取指定容器 pid
6350
$ sudo ln -s /proc/6350/ns/net /var/run/netns/6350
$ sudo ip netns exec 6350 ip route add 192.168.0.0/16 dev eth0 via 192.168.115.2
$ sudo ip netns exec 6350 ip route    # 新增成功
192.168.0.0/16 via 192.168.115.2 dev eth0 
... ...
在其它宿主機進行相應的配置,新建容器並使用 pipework 新增虛擬網路卡橋接到 br0,測試通訊情況即可。

另外,pipework 可以建立容器的vlan網路,這裡不作過多的介紹了,官方文件已經寫的很清楚了,可以檢視以下兩篇文章:
Pipework 官方文件
Docker 網路詳解及 pipework 原始碼解讀與實踐

Dockerfile
Docker 可以通過 Dockerfile 的內容來自動構建映象。Dockerfile 是一個包含建立映象所有命令的文字檔案,通過docker build命令可以根據 Dockerfile 的內容構建映象,在介紹如何構建之前先介紹下 Dockerfile 的基本語法結構。

Dockerfile 有以下指令選項:
FROM
MAINTAINER
RUN
CMD
EXPOSE
ENV
ADD
COPY
ENTRYPOINT
VOLUME
USER
WORKDIR
ONBUILD

1)FROM

用法:
FROM <image>
或者
FROM <image>
FROM指定構建映象的基礎源映象,如果本地沒有指定的映象,則會自動從 Docker 的公共庫 pull 映象下來。
FROM必須是 Dockerfile 中非註釋行的第一個指令,即一個 Dockerfile 從FROM語句開始。
FROM可以在一個 Dockerfile 中出現多次,如果有需求在一個 Dockerfile 中建立多個映象。
如果FROM語句沒有指定映象標籤,則預設使用latest標籤。

2)MAINTAINER

用法:
MAINTAINER <name>
指定建立映象的使用者

RUN 有兩種使用方式:
RUN
RUN "executable", "param1", "param2"

每條RUN指令將在當前映象基礎上執行指定命令,並提交為新的映象,後續的RUN都在之前RUN提交後的映象為基礎,映象是分層的,可以通過一個映象的任何一個歷史提交點來建立,類似原始碼的版本控制。

exec 方式會被解析為一個 JSON 陣列,所以必須使用雙引號而不是單引號。exec 方式不會呼叫一個命令 shell,所以也就不會繼承相應的變數,如:

RUN [ "echo", "$HOME" ]
這種方式是不會達到輸出 HOME 變數的,正確的方式應該是這樣的

RUN [ "sh", "-c", "echo", "$HOME" ]
RUN產生的快取在下一次構建的時候是不會失效的,會被重用,可以使用--no-cache選項,即docker build --no-cache,如此便不會快取。

3)CMD

CMD有三種使用方式:
CMD "executable","param1","param2"
CMD "param1","param2"
CMD command param1 param2 (shell form)

CMD指定在 Dockerfile 中只能使用一次,如果有多個,則只有最後一個會生效。
CMD的目的是為了在啟動容器時提供一個預設的命令執行選項。如果使用者啟動容器時指定了執行的命令,則會覆蓋掉CMD指定的命令。

另外注意不要弄混以下的概念:
CMD會在啟動容器的時候執行,build時不執行,而RUN只是在構建映象的時候執行,後續映象構建完成之後,啟動容器就與RUN無關了

4)EXPOSE

EXPOSE <port> [<port>...]
告訴 Docker 服務端容器對外對映的本地埠,需要在 docker run 的時候使用-p或者-P選項生效。

5)ENV

ENV <key> <value>       # 只能設定一個變數
ENV <key>=<value> ...   # 允許一次設定多個變數
指定一個環節變數,會被後續RUN指令使用,並在容器執行時保留。

例子:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
等同於

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

6)ADD

ADD <src>... <dest>
ADD複製本地主機檔案、目錄或者遠端檔案 URLS 從 並且新增到容器指定路徑中 。

支援通過GO的正則模糊匹配,具體規則可參見https://golang.org/pkg/path/filepath/#Match

ADD hom* /mydir/        # adds all files starting with "hom"
ADD hom?.txt /mydir/    # ? is replaced with any single character
路徑必須是絕對路徑,如果 不存在,會自動建立對應目錄
路徑必須是 Dockerfile 所在路徑的相對路徑
如果是一個目錄,只會複製目錄下的內容,而目錄本身則不會被複制

7)COPY

COPY <src>... <dest>
COPY複製新檔案或者目錄從 並且新增到容器指定路徑中 。用法同ADD,唯一的不同是不能指定遠端檔案 URLS。

8)ENTRYPOINT

ENTRYPOINT "executable", "param1", "param2"
ENTRYPOINT command param1 param2 (shell form)
配置容器啟動後執行的命令,並且不可被 docker run 提供的引數覆蓋,而CMD是可以被覆蓋的。如果需要覆蓋,則可以使用docker run --entrypoint選項。

每個 Dockerfile 中只能有一個ENTRYPOINT,當指定多個時,只有最後一個生效。

Exec form ENTRYPOINT 例子
通過ENTRYPOINT使用 exec form 方式設定穩定的預設命令和選項,而使用CMD新增預設之外經常被改動的選項。

FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
通過 Dockerfile 使用ENTRYPOINT展示前臺執行 Apache 服務

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
Shell form ENTRYPOINT 例子
這種方式會在/bin/sh -c中執行,會忽略任何CMD或者docker run命令列選項,為了確保docker stop能夠停止長時間執行ENTRYPOINT的容器,確保執行的時候使用exec選項。

FROM ubuntu
ENTRYPOINT exec top -b
如果在ENTRYPOINT忘記使用exec選項,則可以使用CMD補上:

FROM ubuntu
ENTRYPOINT top -b
CMD --ignored-param1 # --ignored-param2 ... --ignored-param3 ... 依此類推

9)VOLUME

VOLUME ["/data"]
建立一個可以從本地主機或其他容器掛載的掛載點,後續具體介紹。

10)USER

USER daemon
指定執行容器時的使用者名稱或 UID,後續的RUN、CMD、ENTRYPOINT也會使用指定使用者。

11)WORKDIR

WORKDIR /path/to/workdir
為後續的RUN、CMD、ENTRYPOINT指令配置工作目錄。可以使用多個WORKDIR指令,後續命令如果引數是相對路徑,則會基於之前命令指定的路徑。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最終路徑是/a/b/c。

WORKDIR指令可以在ENV設定變數之後呼叫環境變數:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
最終路徑則為 /path/$DIRNAME。

12)ONBUILD

ONBUILD [INSTRUCTION]
配置當所建立的映象作為其它新建立映象的基礎映象時,所執行的操作指令。

例如,Dockerfile 使用如下的內容建立了映象 image-A:

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
如果基於 image-A 建立新的映象時,新的 Dockerfile 中使用 FROM image-A 指定基礎映象時,會自動執行 ONBUILD 指令內容,等價於在後面新增了兩條指令。

# Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的映象,推薦在標籤中註明,例如 ruby:1.9-onbuild。

13)Dockerfile 示例

# Nginx
#
# VERSION               0.0.1

FROM      ubuntu
MAINTAINER Victor Vieux <victor@docker.com>

RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server

# Firefox over VNC
#
# VERSION               0.3

FROM ubuntu

# Install vnc, xvfb in order to create a 'fake' display and firefox
RUN apt-get update && apt-get install -y x11vnc xvfb firefox
RUN mkdir ~/.vnc
# Setup a password
RUN x11vnc -storepasswd 1234 ~/.vnc/passwd
# Autostart firefox (might not be the best way, but it does the trick)
RUN bash -c 'echo "firefox" >> /.bashrc'

EXPOSE 5900
CMD    ["x11vnc", "-forever", "-usepw", "-create"]

# Multiple images example
#
# VERSION               0.1

FROM ubuntu
RUN echo foo > bar
# Will output something like ===> 907ad6c2736f

FROM ubuntu
RUN echo moo > oink
# Will output something like ===> 695d7793cbe4

# You᾿ll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
# /oink.

14)docker build

$ docker build --help

Usage: docker build [OPTIONS] PATH | URL | -

Build a new image from the source code at PATH

  --force-rm=false     Always remove intermediate containers, even after unsuccessful builds # 移除過渡容器,即使構建失敗
  --no-cache=false     Do not use cache when building the image                              # 不實用 cache        
  -q, --quiet=false    Suppress the verbose output generated by the containers               
  --rm=true            Remove intermediate containers after a successful build               # 構建成功後移除過渡層容器
  -t, --tag=""         Repository name (and optionally a tag) to be applied to the resulting image in case of success

參考文件:Dockerfile Reference

15)dockerfile 最佳實踐

使用.dockerignore檔案
為了在docker build過程中更快上傳和更加高效,應該使用一個.dockerignore檔案用來排除構建映象時不需要的檔案或目錄。例如,除非.git在構建過程中需要用到,否則你應該將它新增到.dockerignore檔案中,這樣可以節省很多時間。

避免安裝不必要的軟體包
為了降低複雜性、依賴性、檔案大小以及構建時間,應該避免安裝額外的或不必要的包。例如,不需要在一個資料庫映象中安裝一個文字編輯器。

每個容器都跑一個程式
在大多數情況下,一個容器應該只單獨跑一個程式。解耦應用到多個容器使其更容易橫向擴充套件和重用。如果一個服務依賴另外一個服務,可以參考 https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/

最小化層
我們知道每執行一個指令,都會有一次映象的提交,映象是分層的結構,對於Dockerfile,應該找到可讀性和最小化層之間的平衡。

多行引數排序
如果可能,通過字母順序來排序,這樣可以避免安裝包的重複並且更容易更新列表,另外可讀性也會更強,新增一個空行使用\換行:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

建立快取
映象構建過程中會按照Dockerfile的順序依次執行,每執行一次指令 Docker 會尋找是否有存在的映象快取可複用,如果沒有則建立新的映象。如果不想使用快取,則可以在docker build時新增--no-cache=true選項。

從基礎映象開始就已經在快取中了,下一個指令會對比所有的子映象尋找是否執行相同的指令,如果沒有則快取失效。在大多數情況下只對比Dockerfile指令和子映象就足夠了。ADD和COPY指令除外,執行ADD和COPY時存放到映象的檔案也是需要檢查的,完成一個檔案的校驗之後再利用這個校驗在快取中查詢,如果檢測的檔案改變則快取失效。RUN apt-get -y update命令只檢查命令是否匹配,如果匹配就不會再執行更新了。

為了有效地利用快取,你需要保持你的 Dockerfile 一致,並且儘量在末尾修改。

Dockerfile 指令

FROM: 只要可能就使用官方映象庫作為基礎映象
RUN: 為保持可讀性、方便理解、可維護性,把長或者複雜的RUN語句使用\分隔符分成多行
不建議RUN apt-get update獨立成行,否則如果後續包有更新,那麼也不會再執行更新
避免使用RUN apt-get upgrade或者dist-upgrade,很多必要的包在一個非privileged許可權的容器裡是無法升級的。如果知道某個包更新,使用apt-get install -y xxx

標準寫法
RUN apt-get update && apt-get install -y package-bar package-foo

例子:

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    btrfs-tools \
    build-essential \
    curl \
    dpkg-sig \
    git \
    iptables \
    libapparmor-dev \
    libcap-dev \
    libsqlite3-dev \
    lxc=1.0* \
    mercurial \
    parallel \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.0*

CMD: 推薦使用CMD [“executable”, “param1”, “param2”…]這種格式,CMD [“param”, “param”]則配合ENTRYPOINT使用
EXPOSE: Dockerfile 指定要公開的埠,使用docker run時指定對映到宿主機的埠即可
ENV: 為了使新的軟體更容易執行,可以使用ENV更新PATH變數。如ENV PATH /usr/local/nginx/bin:$PATH確保CMD ["nginx"]即可執行

ENV也可以這樣定義變數:
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
ADDorCOPY:ADD比COPY多一些特性「tar 檔案自動解包和支援遠端 URL」,不推薦新增遠端URL

如不推薦這種方式:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

推薦使用 curl 或者 wget 替換,使用如下方式:
RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.gz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all
    
如果不需要新增 tar 檔案,推薦使用COPY。

可參考下面兩篇文件:
Dockerfile最佳實踐(一)
Dockerfile最佳實踐(二)

Docker容器資料管理

docker管理資料的方式有兩種:
資料卷
資料卷容器

1)資料卷

資料卷是一個或多個容器專門指定繞過Union File System的目錄,為持續性或共享資料提供一些有用的功能:
1)資料卷可以在容器間共享和重用
2)資料卷資料改變是直接修改的
3)資料卷資料改變不會被包括在容器中
4)資料卷是持續性的,直到沒有容器使用它們
5)新增一個資料卷

你可以使用-v選項新增一個資料卷,或者可以使用多次-v選項為一個 docker 容器執行掛載多個資料卷。
$ sudo docker run --name data -v /data -t -i ubuntu:14.04 /bin/bash # 建立資料卷繫結到到新建容器,新建容器中會建立 /data 資料卷 bash-4.1# ls -ld /data/
drwxr-xr-x 2 root root 4096 Jul 23 06:59 /data/
bash-4.1# df -Th
Filesystem    Type    Size  Used Avail Use% Mounted on
... ...
              ext4     91G  4.6G   82G   6% /data

建立的資料卷可以通過docker inspect獲取宿主機對應路徑
$ sudo docker inspect data
... ... "Volumes": { "/data": "/var/lib/docker/volumes/151de401d268226f96d824fdf444e77a4500aed74c495de5980c807a2ffb7ea9" }, # 可以看到建立的資料卷宿主機路徑 ... ...

或者直接指定獲取
$ sudo docker inspect --format="{{ .Volumes }}" data
map[/data: /var/lib/docker/volumes/151de401d268226f96d824fdf444e77a4500aed74c495de5980c807a2ffb7ea9]

掛載宿主機目錄為一個資料卷
-v選項除了可以建立卷,也可以掛載當前主機的一個目錄到容器中。
$ sudo docker run --name web -v /source/:/web -t -i ubuntu:14.04 /bin/bash
bash-4.1# ls -ld /web/
drwxr-xr-x 2 root root 4096 Jul 23 06:59 /web/
bash-4.1# df -Th
... ...
              ext4     91G  4.6G   82G   6% /web
bash-4.1# exit 

預設掛載卷是可讀寫的,可以在掛載時指定只讀
$ sudo docker run --rm --name test -v /source/:/test:ro -t -i ubuntu:14.04 /bin/bash

2)建立和掛載一個資料卷容器(這樣即使刪除了剛開始的第一個資料卷容器或者中間層的資料卷容器,只要有其他容器使用資料卷,資料卷都不會被刪除的。)

如果你有一些永續性的資料並且想在容器間共享,或者想用在非永續性的容器上,最好的方法是建立一個資料卷容器,然後從此容器上掛載資料。
這樣就可以在容器之間共享資料了。

建立資料卷容器
$ sudo docker run -t -i -d -v /test --name test ubuntu:14.04 echo hello

使用--volumes-from選項在另一個容器中掛載 /test 卷。不管 test 容器是否執行,其它容器都可以掛載該容器資料卷,當然如果只是單獨的資料卷是沒必要執行容器的。
$ sudo docker run -t -i -d --volumes-from test --name test1 ubuntu:14.04 /bin/bash

新增另一個容器
$ sudo docker run -t -i -d --volumes-from test --name test2 ubuntu:14.04 /bin/bash

也可以繼承其它掛載有 /test 卷的容器
$ sudo docker run -t -i -d --volumes-from test1 --name test3 ubuntu:14.04 /bin/bash

3)備份、恢復或遷移資料卷

資料卷備份

$ sudo docker run --rm --volumes-from test -v $(pwd):/backup ubuntu:14.04 tar cvf /backup/test.tar /test
tar: Removing leading `/' from member names
/test/
/test/b
/test/d
/test/c
/test/a

以上命令表示:
啟動一個新的容器並且從test容器中掛載卷,然後掛載當前目錄到容器中為backup,並備份test卷中所有的資料為test.tar,執行完成之後刪除容器--rm,此時備份就在當前的目錄下,名為test.tar。
注意:後面的/test是資料卷的目錄路徑(即資料卷建立時在容器裡的路徑)

$ ls 
宿主機當前目錄下產生了test卷的備份檔案test.tar

---------------------------------------------看看下面的一個例項---------------------------------------------
先建立一個容器wang,包含兩個資料卷/var/volume1和/var/volume2(這兩個目錄是在容器裡的資料卷路徑)
[root@localhost ~]# docker run -t -i -v /var/volume1 -v /var/volume2 --name wang docker.io/centos /bin/bash
[root@83eb43492ae7 /]#

根據Docker的資料持久化之資料卷容器可知,上面建立的wang資料卷容器掛載了/var/volume1和/var/volume2兩個目錄
然後在資料卷裡寫些資料,以供測試。
[root@83eb43492ae7 /]# cd /var/volume1
[root@83eb43492ae7 volume1]# echo "test1" > test1
[root@83eb43492ae7 volume1]# echo "test11" > test11
[root@83eb43492ae7 volume1]# echo "test111" > test111
[root@83eb43492ae7 volume1]# ls
test1  test11  test111
[root@83eb43492ae7 volume1]# cd ../volume2
[root@83eb43492ae7 volume2]# echo "test2" > test2
[root@83eb43492ae7 volume2]# echo "test22" > test22
[root@83eb43492ae7 volume2]# echo "test222" > test222
[root@83eb43492ae7 volume2]# ls
test2  test22  test222
[root@83eb43492ae7 volume2]# 

然後進行這兩個資料卷的備份
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS                     NAMES
83eb43492ae7        docker.io/centos    "/bin/bash"           2 minutes ago       Up 2 minutes                                  wang


現在開始進行資料卷的備份操作:
為了利用資料卷容器備份,使用--volumes-from標記來建立一個載入wang容器卷的容器,並從主機掛載當前目錄到容器的/backup目錄。並備份wang卷中的資料,執行完成之後刪除容器--rm,此時備份就在當前的目錄下了。

1)備份wang容器中的/var/volume1資料卷資料
[root@localhost ~]# docker run -i -t --volumes-from wang -v $(pwd):/backup docker.io/centos tar cvf /backup/backup1.tar /var/volume1 
tar: Removing leading `/' from member names
/var/volume1/
/var/volume1/test1
/var/volume1/test11
/var/volume1/test111

2)備份wang容器中的/var/volume2資料卷資料
[root@localhost ~]# docker run -i -t --volumes-from wang -v $(pwd):/backup docker.io/centos tar cvf /backup/backup2.tar /var/volume2 
tar: Removing leading `/' from member names
/var/volume2/
/var/volume2/test2
/var/volume2/test22
/var/volume2/test222

3)備份wang容器中的/var/volume1和/var/volume2資料卷資料
[root@localhost ~]# docker run -i -t --volumes-from wang -v $(pwd):/backup docker.io/centos tar cvf /backup/backup.tar /var/volume1 /var/volume2
tar: Removing leading `/' from member names
/var/volume1/
/var/volume1/test1
/var/volume1/test11
/var/volume1/test111
/var/volume2/
/var/volume2/test2
/var/volume2/test22
/var/volume2/test222
[root@localhost ~]# ls
anaconda-ks.cfg  a.py  backup1.tar  backup2.tar  backup.tar  mkimage-yum.sh  pipework  var  wang.tar

這樣,資料卷容器中的資料就備份完成了. 簡言之就是:
先建立一個容器,並掛載要備份的容器資料卷,再掛載資料卷(pwd):/backup目錄到容器/bakcup,在容器中執行備份/data目錄到/backup,也就是備份到宿主機$(pwd):/backup目錄。

資料卷恢復或遷移

可以恢復給同一個容器或者另外的容器,新建容器並解壓備份檔案到新的容器資料卷
$ sudo docker run -t -i -d -v /test --name test4 ubuntu:14.04  /bin/bash 
$ sudo docker run --rm --volumes-from test4 -v $(pwd):/backup ubuntu:14.04 tar xvf /backup/test.tar -C / 
恢復之前的檔案到新建卷中,執行完後自動刪除容器 test/ test/b test/d test/c test/a

-----------------------------接著上面的例項進行資料卷恢復操作--------------------------
[root@localhost ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS                     NAMES
531c9d8adf4c        docker.io/centos    "/bin/bash"           2 minutes ago       Up 44 seconds                                 wang

1)恢復資料給同一個容器
測了測試效果,先刪除資料卷(注意:資料卷目錄刪除不了,只能刪除其中的資料。)
[root@localhost ~]# docker attach wang
[root@531c9d8adf4c ~]# ls /var/volume1
test1  test11  test111
[root@531c9d8adf4c ~]# ls /var/volume2
test2  test22  test222
[root@531c9d8adf4c ~]# rm -rf /var/volume1 /var/volume2
rm: cannot remove '/var/volume1': Device or resource busy   
rm: cannot remove '/var/volume2': Device or resource busy
[root@531c9d8adf4c ~]# ls /var/volume2
[root@531c9d8adf4c ~]# ls /var/volume1

現在進行資料卷恢復,恢復資料卷中的所有資料:
[root@localhost ~]# ls
anaconda-ks.cfg  a.py  backup1.tar  backup2.tar  backup.tar  mkimage-yum.sh  pipework  var  wang.tar

注意-C後面的路徑,這個路徑表示將資料恢復到容器裡的路徑。
命令中用"/",即表示將backup.tar中的資料解壓到容器的/路徑下。後面跟什麼路徑,就解壓到這個路徑下。因此這裡用"/"
[root@localhost ~]# docker run --rm --volumes-from wang -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C / 
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222

再次到容器裡檢視,發現資料卷裡的資料已經恢復了
[root@531c9d8adf4c ~]# ls /var/volume1
test1  test11  test111
[root@531c9d8adf4c ~]# ls /var/volume2
test2  test22  test222

2)恢復資料給另外的容器,新建容器並解壓備份檔案到新的容器資料卷
即新建一個容器huihui,將上面備份的資料卷資料恢復到這個新容器裡。
[root@localhost ~]# docker run -t -i -v /var/volume1 -v /var/volume2 --name huihui docker.io/centos /bin/bash
[root@f6ff380e0b7f var]# ls /var/volume1
[root@f6ff380e0b7f var]# ls /var/volume2

[root@localhost ~]# ls
anaconda-ks.cfg  a.py  backup1.tar  backup2.tar  backup.tar  mkimage-yum.sh  pipework  var  wang.tar

[root@localhost ~]# docker run --rm --volumes-from huihui -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C /
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222

[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS                     NAMES
813afe297b60        docker.io/centos    "/bin/bash"           17 seconds ago      Up 16 seconds                                 huihui


這裡注意一下:
新容器建立時掛載的資料卷路徑最好是和之前備份的資料卷路徑一致
如下:
1)新建容器掛載的資料卷只是備份資料卷的一部分,那麼恢復的時候也只是恢復一部分資料。如下,新容器建立時只掛載/var/volume1
[root@localhost ~]# docker run -t -i -v /var/volume1 --name huihui docker.io/centos /bin/bash
[root@12dd8d742259 /]# ls /var/volume1/
[root@12dd8d742259 /]# ls /var/volume2 
ls: cannot access /var/volume2: No such file or directory

[root@localhost ~]# docker run --rm --volumes-from huihui -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C /
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222
[root@localhost ~]# 

檢視容器,發現只恢復了/var/volume1的資料,/var/volume2資料沒有恢復,因為沒有容器建立時沒有掛載這個。
[root@localhost ~]# docker run -t -i -v /var/volume1 --name huihui docker.io/centos /bin/bash
[root@12dd8d742259 /]# ls /var/volume1/
[root@12dd8d742259 /]# ls /var/volume2 
ls: cannot access /var/volume2: No such file or directory

2)新容器建立時只掛載/var/volume2
[root@localhost ~]# docker run -t -i -v /var/volume2 --name huihui docker.io/centos /bin/bash
[root@da3a3d2c95e0 /]# ls /var/volume2/
[root@da3a3d2c95e0 /]# ls /var/volume1 
ls: cannot access /var/volume1: No such file or directory

[root@localhost ~]# docker run --rm --volumes-from huihui -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C /
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222
[root@localhost ~]# 

[root@da3a3d2c95e0 /]# ls /var/volume1
ls: cannot access /var/volume1: No such file or directory
[root@da3a3d2c95e0 /]# ls /var/volume2/
test2  test22  test222

3)如果新容器建立時掛載的資料卷目錄跟之前備份的路徑不一致
[root@localhost ~]# docker run -t -i -v /var/huihui --name huihui docker.io/centos /bin/bash
[root@9bad9b3bde71 /]# ls /var/huihui/
[root@9bad9b3bde71 /]# 

如果解壓時-C後面跟的路徑不是容器掛載的容器,那麼資料恢復不了,如下
[root@localhost ~]# docker run --rm --volumes-from huihui -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C /
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222

發現容器內資料沒有恢復
[root@9bad9b3bde71 /]# ls /var/huihui/
[root@9bad9b3bde71 /]# 

但是如果解壓時-C後面跟的是容器掛載的路徑,資料就能正常恢復
[root@localhost ~]# docker run --rm --volumes-from huihui -v $(pwd):/backup docker.io/centos tar xvf /backup/backup.tar -C /var/huihui
var/volume1/
var/volume1/test1
var/volume1/test11
var/volume1/test111
var/volume2/
var/volume2/test2
var/volume2/test22
var/volume2/test222
[root@localhost ~]# 

發現容器內資料已經恢復了
[root@9bad9b3bde71 /]# ls /var/huihui/
var
[root@9bad9b3bde71 /]# ls /var/huihui/var/
volume1  volume2
[root@9bad9b3bde71 /]# ls /var/huihui/var/volume1
test1  test11  test111
[root@9bad9b3bde71 /]# ls /var/huihui/var/volume2
test2  test22  test222

刪除 Volumes

Volume 只有在下列情況下才能被刪除:
1)docker rm -v刪除容器時新增了-v選項
2)docker run --rm執行容器時新增了--rm選項
否則,會在/var/lib/docker/volumes目錄中遺留很多不明目錄。

可以使用下面方式找出,然後刪除_data目錄下的資料檔案
[root@localhost volumes]# docker inspect huihui|grep /var/lib/docker/volumes
                "Source": "/var/lib/docker/volumes/97aa95420e66de20abbe618fad8d0c1da31c54ce97e32a3892fa921c7942d42b/_data",

Docker連結容器

docker允許把多個容器連線在一起,相互互動資訊。docker 連結會建立一種容器父子級別的關係,其中父容器可以看到其子容器提供的資訊。
1)容器命名
在建立容器時,如果不指定容器的名字,則預設會自動建立一個名字。
一般推薦給容器命名,好處是:
a)給容器命名方便記憶,如命名執行web應用的容器為web
b)為docker容器提供一個參考,允許方便其他容器呼叫,如把容器web連結到容器db

可以通過--name選項給容器自定義命名:
[root@localhost ~]# docker run -d -t -i --name test ubuntu:14.04 /bin/bash              
[root@localhost ~]# docker  inspect --format="{{ .Name }}" test
/test

注意:容器名稱必須唯一,即你只能命名一個叫test的容器。如果你想複用容器名,則必須在建立新的容器前通過docker rm刪除舊的容器或者建立容器時新增--rm選項。

2)連結容器
連結允許容器間安全通訊,使用--link選項建立連結。比如:

$ sudo docker run -d --name db training/postgres
基於training/postgres映象建立一個名為db的容器,然後下面建立一個叫做web的容器,並且將它與db相互連線在一起

$ sudo docker run -d --name web --link db:db training/webapp 

其中:
--link <container_name or container_id>:alias選項指定連結到的容器。

檢視web容器的連結關係:
$ sudo docker inspect -f "{{ .HostConfig.Links }}" web
[/db:/web/db]

可以看到web容器被連結到db容器為/web/db,這允許web容器訪問 db 容器的資訊。

容器之間的連結實際做了什麼?
一個連結允許一個源容器提供資訊訪問給一個接收容器。
在本例中,web容器作為一個接收者,允許訪問源容器db的相關服務資訊。
Docker建立了一個安全隧道而不需要對外公開任何埠給外部容器,因此不需要在建立容器的時候新增-p或-P指定對外公開的埠,這也是連結容器的最大好處,本例為PostgreSQL資料庫。


Docker 主要通過以下兩個方式提供連線資訊給接收容器:
a)環境變數
b)更新/etc/hosts檔案

------環境變數------
當兩個容器連結,Docker會在目標容器上設定一些環境變數,以獲取源容器的相關資訊。
首先,Docker會在每個通過--link選項指定別名的目標容器上設定一個<alias>_NAME環境變數。
如果一個名為web的容器通過--link db:webdb被連結到一個名為db的資料庫容器,那麼web容器上會設定一個環境變數為WEBDB_NAME=/web/webdb

以之前的為例,Docker 還會設定埠變數:
$ sudo docker run --rm --name web2 --link db:db training/webapp env
. . .
DB_NAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432           
DB_PORT_5432_TCP=tcp://172.17.0.5:5432      //<name>_PORT_<port>_<protocol> 協議可以是 TCP 或 UDP
DB_PORT_5432_TCP_PROTO=tcp
DB_PORT_5432_TCP_PORT=5432
DB_PORT_5432_TCP_ADDR=172.17.0.5
. . .

注:這些環境變數只設定給容器中的第一個程式,類似一些守護程式 (如 sshd ) 當他們派生 shells 時會清除這些變數

------更新/etc/hosts檔案------
除了環境變數,Docker會在目標容器上新增相關主機條目到/etc/hosts中,上例中就是web容器。

$ sudo docker run -t -i --rm --link db:db training/webapp /bin/bash
[root@21af536003d1 /]# cat /etc/hosts
172.17.0.7  aed84ee21bde
. . .
172.17.0.5  db

注:/etc/host檔案在源容器被重啟之後會自動更新IP地址,而環境變數中的IP地址則不會自動更新的。

Docker構建私有庫
Docker 官方提供了docker registry的構建方法docker-registry

1)快速構建
快速構建 docker registry 通過以下兩步:
安裝 docker
執行 registry:docker run -p 5000:5000 registry

 

這種方法通過 Docker hub 使用官方映象 official image from the Docker hub

2)不使用容器構建registry

安裝必要的軟體
$ sudo apt-get install build-essential python-dev libevent-dev python-pip liblzma-dev

配置 docker-registry
sudo pip install docker-registry

或者使用 github clone手動安裝
$ git clone https://github.com/dotcloud/docker-registry.git
$ cd docker-registry/
$ cp config/config_sample.yml config/config.yml
$ mkdir /data/registry -p
$ pip install .

執行
docker-registry


------高階啟動方式 [不推薦]------
使用gunicorn控制:
gunicorn -c contrib/gunicorn_config.py docker_registry.wsgi:application

或者對外監聽開放
gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application

3)提交指定容器到私有庫
$ docker tag ubuntu:12.04 私有庫IP:5000/ubuntu:12.04
$ docker push 私有庫IP:5000/ubuntu

相關文章