午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

從零開(kāi)始學(xué)習(xí) Docker

 碼農(nóng)書館 2018-05-10

Docker

這篇文章是我學(xué)習(xí) Docker 的記錄,大部分內(nèi)容摘抄自 <<Docker — 從入門到實(shí)踐>> 一書,并非本人原創(chuàng).
學(xué)習(xí)過(guò)程中整理成適合我自己的筆記,其中也包含了我自己的實(shí)踐記錄.

最近工作中遇到項(xiàng)目部署的問(wèn)題,因?yàn)樵扰f項(xiàng)目還需要繼續(xù)在線服役,所以生產(chǎn)環(huán)境的一整套東西一直都停留在很低版本的 CentOS 中,很多時(shí)候想擴(kuò)展或想部署一個(gè)新功能因?yàn)樯a(chǎn)環(huán)境的問(wèn)題而不得不花費(fèi)更多的時(shí)間,有時(shí)候還不得不放棄.最要命的是我們新項(xiàng)目的開(kāi)發(fā)環(huán)境是 Windows 環(huán)境,而且都是用較新的開(kāi)發(fā)環(huán)境;而測(cè)試環(huán)境卻又是較新的 CentOS 環(huán)境,導(dǎo)致很多時(shí)候在這個(gè)環(huán)境運(yùn)行沒(méi)有問(wèn)題,在另一個(gè)環(huán)境卻無(wú)緣無(wú)故出問(wèn)題,期間為了這些事浪費(fèi)了很多時(shí)間.還好發(fā)現(xiàn)有 Docker 能夠解決這些頭痛的問(wèn)題,當(dāng)然 Docker 不單單只能解決以上問(wèn)題,它還有很多強(qiáng)大的功能.接下來(lái)就從零開(kāi)始講講 Docker.

什么是 Docker

Docker 是 Docker 公司的開(kāi)源項(xiàng)目,使用 Google 公司推出的 Go 語(yǔ)言開(kāi)發(fā)的,并于 2013 年 3 月以 Apache 2.0 授權(quán)協(xié)議開(kāi)源,主要項(xiàng)目代碼在 GitHub 上進(jìn)行維護(hù)。

下面的圖片比較了 Docker 和傳統(tǒng)虛擬化方式的不同之處。傳統(tǒng)虛擬機(jī)技術(shù)是虛擬出一套硬件后,在其上運(yùn)行一個(gè)完整操作系統(tǒng),在該系統(tǒng)上再運(yùn)行所需應(yīng)用進(jìn)程;而容器內(nèi)的應(yīng)用進(jìn)程直接運(yùn)行于宿主的內(nèi)核,容器內(nèi)沒(méi)有自己的內(nèi)核,而且也沒(méi)有進(jìn)行硬件虛擬。因此容器要比傳統(tǒng)虛擬機(jī)更為輕便。
傳統(tǒng)虛擬化

Docker

為什么要使用 Docker?

Docker 跟傳統(tǒng)的虛擬化方式相比具有以下優(yōu)勢(shì):

更高效的利用系統(tǒng)資源

由于容器不需要進(jìn)行硬件虛擬以及運(yùn)行完整操作系統(tǒng)等額外開(kāi)銷,Docker 對(duì)系統(tǒng)資源的利用率更高。無(wú)論是應(yīng)用執(zhí)行速度、內(nèi)存損耗或者文件存儲(chǔ)速度,都要比傳統(tǒng)虛擬機(jī)技術(shù)更高效。因此,相比虛擬機(jī)技術(shù),一個(gè)相同配置的主機(jī),往往可以運(yùn)行更多數(shù)量的應(yīng)用。

更快速的啟動(dòng)時(shí)間

傳統(tǒng)的虛擬機(jī)技術(shù)啟動(dòng)應(yīng)用服務(wù)往往需要數(shù)分鐘,而 Docker 容器應(yīng)用,由于直接運(yùn)行于宿主內(nèi)核,無(wú)需啟動(dòng)完整的操作系統(tǒng),因此可以做到秒級(jí)、甚至毫秒級(jí)的啟動(dòng)時(shí)間。大大的節(jié)約了開(kāi)發(fā)、測(cè)試、部署的時(shí)間。

一致的運(yùn)行環(huán)境

開(kāi)發(fā)過(guò)程中一個(gè)常見(jiàn)的問(wèn)題是環(huán)境一致性問(wèn)題。由于開(kāi)發(fā)環(huán)境、測(cè)試環(huán)境、生產(chǎn)環(huán)境不一致,導(dǎo)致有些 bug 并未在開(kāi)發(fā)過(guò)程中被發(fā)現(xiàn)。而 Docker 的鏡像提供了除內(nèi)核外完整的運(yùn)行時(shí)環(huán)境,確保了應(yīng)用運(yùn)行環(huán)境一致性,從而不會(huì)再出現(xiàn) “這段代碼在我機(jī)器上沒(méi)問(wèn)題啊” 這類問(wèn)題。

持續(xù)交付和部署

對(duì)開(kāi)發(fā)和運(yùn)維人員來(lái)說(shuō),最希望的就是一次創(chuàng)建或配置,可以在任意地方正常運(yùn)行。

使用 Docker 可以通過(guò)定制應(yīng)用鏡像來(lái)實(shí)現(xiàn)持續(xù)集成、持續(xù)交付、部署。開(kāi)發(fā)人員可以通過(guò) Dockerfile 來(lái)進(jìn)行鏡像構(gòu)建,并結(jié)合 持續(xù)集成系統(tǒng)進(jìn)行集成測(cè)試,而運(yùn)維人員則可以直接在生產(chǎn)環(huán)境中快速部署該鏡像,甚至結(jié)合持續(xù)部署系統(tǒng)進(jìn)行自動(dòng)部署。

而且使用 Dockerfile 使鏡像構(gòu)建透明化,不僅僅開(kāi)發(fā)團(tuán)隊(duì)可以理解應(yīng)用運(yùn)行環(huán)境,也方便運(yùn)維團(tuán)隊(duì)理解應(yīng)用運(yùn)行所需條件,幫助更好的生產(chǎn)環(huán)境中部署該鏡像。

更輕松的遷移

由于 Docker 確保了執(zhí)行環(huán)境的一致性,使得應(yīng)用的遷移更加容易。Docker 可以在很多平臺(tái)上運(yùn)行,無(wú)論是物理機(jī)、虛擬機(jī)、公有云、私有云,甚至是筆記本,其運(yùn)行結(jié)果是一致的。因此用戶可以很輕易的將在一個(gè)平臺(tái)上運(yùn)行的應(yīng)用,遷移到另一個(gè)平臺(tái)上,而不用擔(dān)心運(yùn)行環(huán)境的變化導(dǎo)致應(yīng)用無(wú)法正常運(yùn)行的情況。

更輕松的維護(hù)和擴(kuò)展

Docker 使用的分層存儲(chǔ)以及鏡像的技術(shù),使得應(yīng)用重復(fù)部分的復(fù)用更為容易,也使得應(yīng)用的維護(hù)更新更加簡(jiǎn)單,基于基礎(chǔ)鏡像進(jìn)一步擴(kuò)展鏡像也變得非常簡(jiǎn)單。此外,Docker 團(tuán)隊(duì)同各個(gè)開(kāi)源項(xiàng)目團(tuán)隊(duì)一起維護(hù)了一大批高質(zhì)量的官方鏡像,既可以直接在生產(chǎn)環(huán)境使用,又可以作為基礎(chǔ)進(jìn)一步定制,大大的降低了應(yīng)用服務(wù)的鏡像制作成本。

對(duì)比傳統(tǒng)虛擬機(jī)總結(jié)

image.png

基本概念

Docker 包括三個(gè)基本概念

  • 鏡像(Image)
  • 容器(Container)
  • 倉(cāng)庫(kù)(Repository)

理解了這三個(gè)概念,就理解了 Docker 的整個(gè)生命周期。

Docker 鏡像

我們都知道,操作系統(tǒng)分為內(nèi)核和用戶空間。對(duì)于 Linux 而言,內(nèi)核啟動(dòng)后,會(huì)掛載 root 文件系統(tǒng)為其提供用戶空間支持。而 Docker 鏡像,就相當(dāng)于是一個(gè) root 文件系統(tǒng)。比如 Docker 官方鏡像 ubuntu:14.04 就包含了完整的一套 Ubuntu 14.04 最小系統(tǒng)的 root 文件系統(tǒng)。

Docker 鏡像是一個(gè)特殊的文件系統(tǒng),除了提供容器運(yùn)行時(shí)所需的程序、庫(kù)、資源、配置等文件外,還包含了一些為運(yùn)行時(shí)準(zhǔn)備的一些配置參數(shù)(如匿名卷、環(huán)境變量、用戶等)。鏡像不包含任何動(dòng)態(tài)數(shù)據(jù),其內(nèi)容在構(gòu)建之后也不會(huì)被改變。

Docker 容器

鏡像和容器的關(guān)系,就像是面向?qū)ο蟪绦蛟O(shè)計(jì)中的實(shí)例一樣,鏡像是靜態(tài)的定義,容器是鏡像運(yùn)行時(shí)的實(shí)體。容器可以被創(chuàng)建、啟動(dòng)、停止、刪除、暫停等。

每一個(gè)容器運(yùn)行時(shí),是以鏡像為基礎(chǔ)層,在其上創(chuàng)建一個(gè)當(dāng)前容器的存儲(chǔ)層,我們可以稱這個(gè)為容器運(yùn)行時(shí)讀寫而準(zhǔn)備的存儲(chǔ)層為容器存儲(chǔ)層。

容器存儲(chǔ)層的生存周期和容器一樣,容器消亡時(shí),容器存儲(chǔ)層也隨之消亡。因此,任何保存于容器存儲(chǔ)層的信息都會(huì)隨容器刪除而丟失。

按照 Docker 最佳實(shí)踐的要求,容器不應(yīng)該向其存儲(chǔ)層內(nèi)寫入任何數(shù)據(jù),容器存儲(chǔ)層要保持無(wú)狀態(tài)化。所有的文件寫入操作,都應(yīng)該使用 數(shù)據(jù)卷(Volume)、或者綁定宿主目錄,在這些位置的讀寫會(huì)跳過(guò)容器存儲(chǔ)層,直接對(duì)宿主(或網(wǎng)絡(luò)存儲(chǔ))發(fā)生讀寫,其性能和穩(wěn)定性更高。

數(shù)據(jù)卷的生存周期獨(dú)立于容器,容器消亡,數(shù)據(jù)卷不會(huì)消亡。因此,使用數(shù)據(jù)卷后,容器可以隨意刪除、重新 run,數(shù)據(jù)卻不會(huì)丟失。

Docker 倉(cāng)庫(kù)

鏡像構(gòu)建完成后,可以很容易的在當(dāng)前宿主上運(yùn)行,但是,如果需要在其它服務(wù)器上使用這個(gè)鏡像,我們就需要一個(gè)集中的存儲(chǔ)、分發(fā)鏡像的服務(wù),Docker Registry 就是這樣的服務(wù)。

安裝 Docker

官方網(wǎng)站上有各種環(huán)境下的 安裝指南,這里主要介紹下 CentOS 的安裝。

CentOS 操作系統(tǒng)安裝 Docker

系統(tǒng)要求

Docker 需要安裝在 CentOS 7 64 位的平臺(tái),并且內(nèi)核版本不低于 3.10. CentOS 7.× 滿足要求的最低內(nèi)核版本要求,但由于 CentOS 7 內(nèi)核版本比較低,部分功能(如 overlay2 存儲(chǔ)層驅(qū)動(dòng))無(wú)法使用,并且部分功能可能不太穩(wěn)定。所以建議大家升級(jí)到最新的 CentOS 版本,并且內(nèi)核也更新到最新的穩(wěn)定版本.更新的方法可以看看我的<<CentOS 7. × 系統(tǒng)及內(nèi)核升級(jí)指南>>

使用阿里云的安裝腳本自動(dòng)安裝

為了簡(jiǎn)化 Docker 安裝流程,我們可以使用阿里云提供的一套安裝腳本,CentOS 系統(tǒng)上可以使用這套腳本安裝 Docker :

curl -sSL http://acs-public-mirror.oss-cn-hangzhou./docker-engine/internet | sh -

執(zhí)行這個(gè)命令后,腳本就會(huì)自動(dòng)的將一切準(zhǔn)備工作做好,并且把 Docker 安裝在系統(tǒng)中。

Docker 通過(guò)運(yùn)行 hello-world 映像驗(yàn)證是否正確安裝。

$ docker run hello-world

> Unable to find image 'hello-world:latest' locally
> latest: Pulling from library/hello-world
> b04784fba78d: Pull complete 
> Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
> Status: Downloaded newer image for hello-world:latest
 
> Hello from Docker!
> This message shows that your installation appears to be working correctly.

> To generate this message, Docker took the following steps:
>  1. The Docker client contacted the Docker daemon.
>  2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
>  3. The Docker daemon created a new container from that image which runs the
>     executable that produces the output you are currently reading.
>  4. The Docker daemon streamed that output to the Docker client, which sent it
>     to your terminal.

> To try something more ambitious, you can run an Ubuntu container with:
>  $ docker run -it ubuntu bash

> Share images, automate workflows, and more with a free Docker ID:
>  https://cloud./

> For more examples and ideas, visit:
>  https://docs./engine/userguide/

此命令下載測(cè)試鏡像并在容器中運(yùn)行它。當(dāng)容器運(yùn)行時(shí),它打印一條信息消息并退出。如果你沒(méi)有配置鏡像加速器的話,運(yùn)行 hello-world 映像驗(yàn)證也是不會(huì)成功的.因?yàn)閲?guó)內(nèi)網(wǎng)絡(luò)的原因,無(wú)法下載測(cè)試鏡像,更別說(shuō)運(yùn)行測(cè)試鏡像了,所以這一步可以先跳過(guò),繼續(xù)往下看,等一下配置完鏡像加速器再來(lái)驗(yàn)證.

查看當(dāng)前 Docker 的版本

$ docker -v

> Docker version 17.05.0-ce, build 89658be

可以看出當(dāng)前的 Docker 為 Docker CE 17.05.0 版本,CE 代表 Docker 社區(qū)版,EE 代表 Docker 企業(yè)版.

卸載 Docker CE

卸載Docker軟件包:

$ yum remove docker-ce

卸載舊版本 Docker

較老版本的 Docker 被稱為 docker 或 docker-engine。如果這些已安裝,請(qǐng)卸載它們以及關(guān)聯(lián)的依賴關(guān)系。

$ yum remove docker docker-common docker-selinux docker-engine

主機(jī)上的圖像,容器,卷或自定義配置文件不會(huì)自動(dòng)刪除。必須手動(dòng)刪除任何已編輯的配置文件。刪除所有圖像,容器和卷:

$ rm -rf /var/lib/docker

參考文檔

參見(jiàn) Docker 官方 CentOS 安裝文檔.

鏡像加速器

國(guó)內(nèi)訪問(wèn) Docker Hub 有時(shí)會(huì)遇到困難,此時(shí)可以配置鏡像加速器。國(guó)內(nèi)很多云服務(wù)商都提供了加速器服務(wù),例如:

注冊(cè)用戶并且申請(qǐng)加速器,會(huì)獲得如 https://jxus37ad.mirror. 這樣的地址。我們需要將其配置給 Docker 引擎。

systemctl enable docker 啟用服務(wù)后,編輯 /etc/systemd/system/multi-user.target.wants/docker.service 文件,找到 ExecStart= 這一行,在這行最后添加加速器地址 --registry-mirror=<加速器地址>,如:

ExecStart=/usr/bin/dockerd --registry-mirror=https://jxus37ad.mirror.

注:對(duì)于 1.12 以前的版本,dockerd 換成 docker daemon

重新加載配置并且重新啟動(dòng)。

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

檢查加速器是否生效

Linux系統(tǒng)下配置完加速器需要檢查是否生效,在命令行執(zhí)行 ps -ef | grep dockerd,如果從結(jié)果中看到了配置的 --registry-mirror 參數(shù)說(shuō)明配置成功。

$ sudo ps -ef | grep dockerd

> root      5346     1  0 19:03 ?        00:00:00 /usr/bin/dockerd --registry-mirror=https://jxus37ad.mirror.

使用 Docker 鏡像

Docker 運(yùn)行容器前需要本地存在對(duì)應(yīng)的鏡像,如果鏡像不存在本地,Docker 會(huì)從鏡像倉(cāng)庫(kù)下載(默認(rèn)是 Docker Hub 公共注冊(cè)服務(wù)器中的倉(cāng)庫(kù))。

獲取鏡像

阿里云鏡像庫(kù) 上有大量的高質(zhì)量的鏡像可以用,這里我們就說(shuō)一下怎么獲取這些鏡像并運(yùn)行。

獲取鏡像的命令是 docker pull。其命令格式為:

docker pull [選項(xiàng)] [Docker Registry地址]<倉(cāng)庫(kù)名>:<標(biāo)簽>

具體的選項(xiàng)可以通過(guò) docker pull --help 命令看到,這里我們說(shuō)一下鏡像名稱的格式。

  • Docker Registry地址:地址的格式一般是 <域名/IP>[:端口號(hào)]。默認(rèn)地址是 Docker Hub。
  • 倉(cāng)庫(kù)名:如之前所說(shuō),這里的倉(cāng)庫(kù)名是兩段式名稱,既 <用戶名>/<軟件名>。對(duì)于 Docker Hub,如果不給出用戶名,則默認(rèn)為 library,也就是官方鏡像.一定要配置鏡像加速器,不然下載速度很慢。

比如:

$ docker pull ubuntu:14.04

14.04: Pulling from library/ubuntu
bf5d46315322: Pull complete
9f13e0ac480c: Pull complete
e8988b5b3097: Pull complete
40af181810e7: Pull complete
e6f7c7e5c03e: Pull complete
Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
Status: Downloaded newer image for ubuntu:14.04

上面的命令中沒(méi)有給出 Docker Registry 地址,因此將會(huì)從 Docker Hub 獲取鏡像。而鏡像名稱是 ubuntu:14.04,因此將會(huì)獲取官方鏡像 library/ubuntu 倉(cāng)庫(kù)中標(biāo)簽為 14.04 的鏡像。

查看已下載的鏡像

要想列出已經(jīng)下載下來(lái)的鏡像,可以使用 docker images 命令。

$ docker images

REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
hello-world          latest              1815c82652c0        3 weeks ago         1.84kB
ubuntu               14.04               4a2820e686c4        2 weeks ago         188 MB

列表包含了倉(cāng)庫(kù)名、標(biāo)簽、鏡像 ID、創(chuàng)建時(shí)間以及所占用的空間。

運(yùn)行

有了鏡像后,我們就可以以這個(gè)鏡像為基礎(chǔ)啟動(dòng)一個(gè)容器來(lái)運(yùn)行。以上面的 ubuntu:14.04 為例,如果我們打算啟動(dòng)里面的 bash 并且進(jìn)行交互式操作的話,可以執(zhí)行下面的命令。

$ docker run -it --rm ubuntu:14.04 bash

root@e7009c6ce357:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="14.04.5 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.5 LTS"
VERSION_ID="14.04"
HOME_URL="http://www./"
SUPPORT_URL="http://help./"
BUG_REPORT_URL="http://bugs./ubuntu/"
root@e7009c6ce357:/# exit
exit

docker run 就是運(yùn)行容器的命令,具體格式我們會(huì)在后面的章節(jié)講解,我們這里簡(jiǎn)要的說(shuō)明一下上面用到的參數(shù)。

  • -it:這是兩個(gè)參數(shù),一個(gè)是 -i:交互式操作,一個(gè)是 -t 終端。我們這里打算進(jìn)入 bash 執(zhí)行一些命令并查看返回結(jié)果,因此我們需要交互式終端。
  • --rm:這個(gè)參數(shù)是說(shuō)容器退出后隨之將其刪除。默認(rèn)情況下,為了排障需求,退出的容器并不會(huì)立即刪除,除非手動(dòng) docker rm。我們這里只是隨便執(zhí)行個(gè)命令,看看結(jié)果,不需要排障和保留結(jié)果,因此使用 --rm 可以避免浪費(fèi)空間。
  • ubuntu:14.04:這是指用 ubuntu:14.04 鏡像為基礎(chǔ)來(lái)啟動(dòng)容器。
  • bash:放在鏡像名后的是命令,這里我們希望有個(gè)交互式 Shell,因此用的是 bash。

進(jìn)入容器后,我們可以在 Shell 下操作,執(zhí)行任何所需的命令。這里,我們執(zhí)行了 cat /etc/os-release,這是 Linux 常用的查看當(dāng)前系統(tǒng)版本的命令,從返回的結(jié)果可以看到容器內(nèi)是 Ubuntu 14.04.5 LTS 系統(tǒng)。

最后我們通過(guò) exit 退出了這個(gè)容器。

定制鏡像

現(xiàn)在讓我們以定制一個(gè) Web 服務(wù)器為例子,來(lái)講解鏡像是如何構(gòu)建的。

$ docker run --name webserver -d -p 80:80 nginx

這條命令會(huì)用 nginx 鏡像啟動(dòng)一個(gè)容器,命名為 webserver,并且映射了 80 端口,這樣我們可以用瀏覽器去訪問(wèn)這個(gè) nginx 服務(wù)器。

如果是在 Linux 本機(jī)運(yùn)行的 Docker,或者如果使用的是 Docker for Mac、Docker for Windows,那么可以直接訪問(wèn):http://localhost;如果使用的是 Docker Toolbox,或者是在虛擬機(jī)、云服務(wù)器上安裝的 Docker,則需要將 localhost 換為虛擬機(jī)地址或者實(shí)際云服務(wù)器地址,還要配置安全組放通對(duì)應(yīng)的端口。

直接用瀏覽器訪問(wèn)的話,我們會(huì)看到默認(rèn)的 Nginx 歡迎頁(yè)面。

Nginx 歡迎頁(yè)面

現(xiàn)在,改動(dòng)這個(gè)歡迎頁(yè)面,改成Hello, Docker!,我們可以使用 docker exec 命令進(jìn)入容器,修改其內(nèi)容。

$ docker exec -it webserver bash

root@f532879089c6:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@f532879089c6:/# exit
exit

我們以交互式終端方式進(jìn)入 webserver 容器,并執(zhí)行了 bash 命令,也就是獲得一個(gè)可操作的 Shell。

然后,我們用 <h1>Hello, Docker!</h1> 覆蓋了 /usr/share/nginx/html/index.html 的內(nèi)容。

現(xiàn)在我們?cè)偎⑿聻g覽器的話,會(huì)發(fā)現(xiàn)內(nèi)容被改變了。

Nginx 歡迎頁(yè)面

我們修改了容器的文件,也就是改動(dòng)了容器的存儲(chǔ)層。我們可以通過(guò) docker diff 命令看到具體的改動(dòng)。

$ docker diff webserver

C /root
A /root/.bash_history
C /run
A /run/nginx.pid
C /usr/share/nginx/html/index.html
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

現(xiàn)在已經(jīng)定制好了,那我們?nèi)绾伟阉4嫦聛?lái)形成鏡像?

要知道,當(dāng)我們運(yùn)行一個(gè)容器的時(shí)候(如果不使用卷的話),我們做的任何文件修改都會(huì)被記錄于容器存儲(chǔ)層里。而 Docker 提供了一個(gè) docker commit 命令,可以將容器的存儲(chǔ)層保存下來(lái)成為鏡像。換句話說(shuō),就是在原有鏡像的基礎(chǔ)上,再疊加上容器的存儲(chǔ)層,并構(gòu)成新的鏡像。以后我們運(yùn)行這個(gè)新鏡像的時(shí)候,就會(huì)擁有原有容器最后的文件變化。

docker commit 的語(yǔ)法格式為:

docker commit [選項(xiàng)] <容器ID或容器名> [<倉(cāng)庫(kù)名>[:<標(biāo)簽>]]

我們可以用下面的命令將容器保存為鏡像:

$ docker commit --author "longhui <653155073@qq.com>" --message "修改了Nginx 歡迎頁(yè)面"  webserver nginx:v2

> sha256:ed889f9d550dd84d81b58eb9e340d49ecbb012b40f5b6507bd388dc335c0d4f5

其中 --author 是指定修改的作者,而 --message 則是記錄本次修改的內(nèi)容。

可以用 docker images 命令看到這個(gè)新定制的鏡像:

$ docker images

  REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
  nginx               v2                  ed889f9d550d        4 minutes ago       108MB
  nginx               latest              2f7f7bce8929        5 days ago          108MB
  hello-world         latest              1815c82652c0        3 weeks ago         1.84kB

我們還可以用 docker history 具體查看鏡像內(nèi)的歷史記錄,如果比較 nginx:latest 的歷史記錄,我們會(huì)發(fā)現(xiàn)新增了我們剛剛提交的這一層。

$ docker history nginx:v2

  IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
  ed889f9d550d        20 minutes ago      nginx -g daemon off;                            164B                修改了Nginx 歡迎頁(yè)面
  2f7f7bce8929        5 days ago          /bin/sh -c #(nop)  CMD ["nginx" "-g" "daem...   0B
  <missing>           5 days ago          /bin/sh -c #(nop)  STOPSIGNAL [SIGTERM]         0B
  <missing>           5 days ago          /bin/sh -c #(nop)  EXPOSE 80/tcp                0B
  <missing>           5 days ago          /bin/sh -c ln -sf /dev/stdout /var/log/ngi...   22B
  <missing>           5 days ago          /bin/sh -c apt-get update  && apt-get inst...   52.2MB
  <missing>           5 days ago          /bin/sh -c #(nop)  ENV NJS_VERSION=1.13.2....   0B
  <missing>           5 days ago          /bin/sh -c #(nop)  ENV NGINX_VERSION=1.13....   0B
  <missing>           2 weeks ago         /bin/sh -c #(nop)  MAINTAINER NGINX Docker...   0B
  <missing>           2 weeks ago         /bin/sh -c #(nop)  CMD ["bash"]                 0B
  <missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:54d82a3a8fe8d47...   55.3MB

新的鏡像定制好后,我們可以來(lái)運(yùn)行這個(gè)鏡像。

docker run --name web2 -d -p 81:80 nginx:v2

這里我們命名為新的服務(wù)為 web2,并且映射到 81 端口。如果是 Docker for Mac/Windows 或 Linux 桌面的話,我們就可以直接訪問(wèn) http://localhost:81 看到結(jié)果,其內(nèi)容應(yīng)該和之前修改后的 webserver 一樣。

完成了第一次定制鏡像,使用的是 docker commit 命令,手動(dòng)操作給舊的鏡像添加了新的一層,形成新的鏡像,對(duì)鏡像多層存儲(chǔ)應(yīng)該有了更直觀的感覺(jué)。

慎用 docker commit

使用 docker commit 命令雖然可以比較直觀的幫助理解鏡像分層存儲(chǔ)的概念,但是實(shí)際環(huán)境中并不會(huì)這樣使用。

首先,如果仔細(xì)觀察之前的 docker diff webserver 的結(jié)果,你會(huì)發(fā)現(xiàn)除了真正想要修改的 /usr/share/nginx/html/index.html 文件外,由于命令的執(zhí)行,還有很多文件被改動(dòng)或添加了。這還僅僅是最簡(jiǎn)單的操作,如果是安裝軟件包、編譯構(gòu)建,那會(huì)有大量的無(wú)關(guān)內(nèi)容被添加進(jìn)來(lái),如果不小心清理,將會(huì)導(dǎo)致鏡像極為臃腫。

此外,使用 docker commit 意味著所有對(duì)鏡像的操作都是黑箱操作,生成的鏡像也被稱為黑箱鏡像,換句話說(shuō),就是除了制作鏡像的人知道執(zhí)行過(guò)什么命令、怎么生成的鏡像,別人根本無(wú)從得知。而且,即使是這個(gè)制作鏡像的人,過(guò)一段時(shí)間后也無(wú)法記清具體在操作的。雖然 docker diff 或許可以告訴得到一些線索,但是遠(yuǎn)遠(yuǎn)不到可以確保生成一致鏡像的地步。這種黑箱鏡像的維護(hù)工作是非常痛苦的。

而且,回顧之前提及的鏡像所使用的分層存儲(chǔ)的概念,除當(dāng)前層外,之前的每一層都是不會(huì)發(fā)生改變的,換句話說(shuō),任何修改的結(jié)果僅僅是在當(dāng)前層進(jìn)行標(biāo)記、添加、修改,而不會(huì)改動(dòng)上一層。如果使用 docker commit 制作鏡像,以及后期修改的話,每一次修改都會(huì)讓鏡像更加臃腫一次,所刪除的上一層的東西并不會(huì)丟失,會(huì)一直如影隨形的跟著這個(gè)鏡像,即使根本無(wú)法訪問(wèn)到。這會(huì)讓鏡像更加臃腫。

docker commit 命令除了學(xué)習(xí)之外,還有一些特殊的應(yīng)用場(chǎng)合,比如被入侵后保存現(xiàn)場(chǎng)等。但是,不要使用 docker commit 定制鏡像,定制行為應(yīng)該使用 Dockerfile 來(lái)完成。

使用 Dockerfile 定制鏡像

從剛才的學(xué)習(xí)中,我們可以了解到,鏡像的定制實(shí)際上就是定制每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構(gòu)建、操作的命令都寫入一個(gè)腳本,用這個(gè)腳本來(lái)構(gòu)建、定制鏡像,那么之前提及的無(wú)法重復(fù)的問(wèn)題、鏡像構(gòu)建透明性的問(wèn)題、體積的問(wèn)題就都會(huì)解決。這個(gè)腳本就是 Dockerfile。

Dockerfile 是一個(gè)文本文件,其內(nèi)包含了一條條的指令(Instruction),每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層應(yīng)當(dāng)如何構(gòu)建。

還以之前定制 nginx 鏡像為例,這次我們使用 Dockerfile 來(lái)定制。

在一個(gè)空白目錄中,建立一個(gè)文本文件,并命名為 Dockerfile

$ mkdir mynginx
$ cd mynginx/
$ touch Dockerfile

添加以下內(nèi)容:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

這個(gè) Dockerfile 很簡(jiǎn)單,一共就兩行。涉及到了兩條指令,FROMRUN。

FROM 指定基礎(chǔ)鏡像

所謂定制鏡像,那一定是以一個(gè)鏡像為基礎(chǔ),在其上進(jìn)行定制。就像我們之前運(yùn)行了一個(gè) nginx 鏡像的容器,再進(jìn)行修改一樣,基礎(chǔ)鏡像是必須指定的。而 FROM 就是指定基礎(chǔ)鏡像,因此一個(gè) DockerfileFROM 是必備的指令,并且必須是第一條指令。

RUN 執(zhí)行命令

RUN 指令是用來(lái)執(zhí)行命令行命令的。由于命令行的強(qiáng)大能力,RUN 指令在定制鏡像時(shí)是最常用的指令之一。其格式有兩種:

  • shell 格式:RUN <命令>,就像直接在命令行中輸入的命令一樣。剛才寫的 Dockrfile 中的 RUN 指令就是這種格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式:RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"],這更像是函數(shù)調(diào)用中的格式。

既然 RUN 就像 Shell 腳本一樣可以執(zhí)行命令,那么我們是否就可以像 Shell 腳本一樣把每一層構(gòu)建需要的命令寫出來(lái),比如這樣:

FROM debian:jessie

RUN buildDeps='gcc libc6-dev make'     && apt-get update     && apt-get install -y $buildDeps     && wget -O redis.tar.gz "http://download./releases/redis-3.2.5.tar.gz"     && mkdir -p /usr/src/redis     && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1     && make -C /usr/src/redis     && make -C /usr/src/redis install     && rm -rf /var/lib/apt/lists/*     && rm redis.tar.gz     && rm -r /usr/src/redis     && apt-get purge -y --auto-remove $buildDeps

僅僅使用一個(gè) RUN 指令,并使用 && 將各個(gè)所需命令串聯(lián)起來(lái)。在撰寫 Dockerfile 的時(shí)候,要經(jīng)常提醒自己,這并不是在寫 Shell 腳本,而是在定義每一層該如何構(gòu)建。

并且,這里為了格式化還進(jìn)行了換行。Dockerfile 支持 Shell 類的行尾添加 \ 的命令換行方式,以及行首 # 進(jìn)行注釋的格式。良好的格式,比如換行、縮進(jìn)、注釋等,會(huì)讓維護(hù)、排障更為容易,這是一個(gè)比較好的習(xí)慣。

此外,還可以看到這一組命令的最后添加了清理工作的命令,刪除了為了編譯構(gòu)建所需要的軟件,清理了所有下載、展開(kāi)的文件,并且還清理了 apt 緩存文件。這是很重要的一步,我們之前說(shuō)過(guò),鏡像是多層存儲(chǔ),每一層的東西并不會(huì)在下一層被刪除,會(huì)一直跟隨著鏡像。因此鏡像構(gòu)建時(shí),一定要確保每一層只添加真正需要添加的東西,任何無(wú)關(guān)的東西都應(yīng)該清理掉。

很多人初學(xué) Docker 制作出了很臃腫的鏡像的原因之一,就是忘記了每一層構(gòu)建的最后一定要清理掉無(wú)關(guān)文件。

構(gòu)建鏡像

再回到之前定制的 nginx 鏡像的 Dockerfile 來(lái)?,F(xiàn)在我們明白了這個(gè) Dockerfile 的內(nèi)容,那么讓我們來(lái)構(gòu)建這個(gè)鏡像吧。

Dockerfile 文件所在目錄執(zhí)行:

$ docker build -t nginx:v3 .

Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> 2f7f7bce8929
Step 2/2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in f3f1e0d41576
 ---> e189d22f23b5
Removing intermediate container f3f1e0d41576
Successfully built e189d22f23b5
Successfully tagged nginx:v3

從命令的輸出結(jié)果中,我們可以清晰的看到鏡像的構(gòu)建過(guò)程。在 Step 2/2 中,如同我們之前所說(shuō)的那樣,RUN 指令啟動(dòng)了一個(gè)容器 f3f1e0d41576,執(zhí)行了所要求的命令,并最后提交了這一層 e189d22f23b5,隨后刪除了所用到的這個(gè)容器 f3f1e0d41576。

這里我們使用了 docker build 命令進(jìn)行鏡像構(gòu)建。其格式為:

docker build [選項(xiàng)] <上下文路徑/URL/->

在這里我們指定了最終鏡像的名稱 -t nginx:v3,構(gòu)建成功后,我們可以像之前運(yùn)行 nginx:v2 那樣來(lái)運(yùn)行這個(gè)鏡像,其結(jié)果會(huì)和 nginx:v2 一樣。

鏡像構(gòu)建上下文(Context)

如果注意,會(huì)看到 docker build 命令最后有一個(gè) .。. 表示當(dāng)前目錄,而 Dockerfile 就在當(dāng)前目錄,因此不少初學(xué)者以為這個(gè)路徑是在指定 Dockerfile 所在路徑,這么理解其實(shí)是不準(zhǔn)確的。如果對(duì)應(yīng)上面的命令格式,你可能會(huì)發(fā)現(xiàn),這是在指定上下文路徑。那么什么是上下文呢?

首先我們要理解 docker build 的工作原理。Docker 在運(yùn)行時(shí)分為 Docker 引擎(也就是服務(wù)端守護(hù)進(jìn)程)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱為 Docker Remote API,而如 docker 命令這樣的客戶端工具,則是通過(guò)這組 API 與 Docker 引擎交互,從而完成各種功能。因此,雖然表面上我們好像是在本機(jī)執(zhí)行各種 docker 功能,但實(shí)際上,一切都是使用的遠(yuǎn)程調(diào)用形式在服務(wù)端(Docker 引擎)完成。也因?yàn)檫@種 C/S 設(shè)計(jì),讓我們操作遠(yuǎn)程服務(wù)器的 Docker 引擎變得輕而易舉。

當(dāng)我們進(jìn)行鏡像構(gòu)建的時(shí)候,并非所有定制都會(huì)通過(guò) RUN 指令完成,經(jīng)常會(huì)需要將一些本地文件復(fù)制進(jìn)鏡像,比如通過(guò) COPY 指令、ADD 指令等。而 docker build 命令構(gòu)建鏡像,其實(shí)并非在本地構(gòu)建,而是在服務(wù)端,也就是 Docker 引擎中構(gòu)建的。那么在這種客戶端/服務(wù)端的架構(gòu)中,如何才能讓服務(wù)端獲得本地文件呢?

這就引入了上下文的概念。當(dāng)構(gòu)建的時(shí)候,用戶會(huì)指定構(gòu)建鏡像上下文的路徑,docker build 命令得知這個(gè)路徑后,會(huì)將路徑下的所有內(nèi)容打包,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個(gè)上下文包后,展開(kāi)就會(huì)獲得構(gòu)建鏡像所需的一切文件。

如果在 Dockerfile 中這么寫:

COPY ./package.json /app/

這并不是要復(fù)制執(zhí)行 docker build 命令所在的目錄下的 package.json,也不是復(fù)制 Dockerfile 所在目錄下的 package.json,而是復(fù)制 上下文(context) 目錄下的 package.json

因此,COPY 這類指令中的源文件的路徑都是相對(duì)路徑。這也是初學(xué)者經(jīng)常會(huì)問(wèn)的為什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app 無(wú)法工作的原因,因?yàn)檫@些路徑已經(jīng)超出了上下文的范圍,Docker 引擎無(wú)法獲得這些位置的文件。如果真的需要那些文件,應(yīng)該將它們復(fù)制到上下文目錄中去。

現(xiàn)在就可以理解剛才的命令 docker build -t nginx:v3 . 中的這個(gè) .,實(shí)際上是在指定上下文的目錄,docker build 命令會(huì)將該目錄下的內(nèi)容打包交給 Docker 引擎以幫助構(gòu)建鏡像。

如果觀察 docker build 輸出,我們其實(shí)已經(jīng)看到了這個(gè)發(fā)送上下文的過(guò)程:

$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...

理解構(gòu)建上下文對(duì)于鏡像構(gòu)建是很重要的,避免犯一些不應(yīng)該的錯(cuò)誤。比如有些初學(xué)者在發(fā)現(xiàn) COPY /opt/xxxx /app 不工作后,于是干脆將 Dockerfile 放到了硬盤根目錄去構(gòu)建,結(jié)果發(fā)現(xiàn) docker build 執(zhí)行后,在發(fā)送一個(gè)幾十 GB 的東西,極為緩慢而且很容易構(gòu)建失敗。那是因?yàn)檫@種做法是在讓 docker build 打包整個(gè)硬盤,這顯然是使用錯(cuò)誤。

一般來(lái)說(shuō),應(yīng)該會(huì)將 Dockerfile 置于一個(gè)空目錄下,或者項(xiàng)目根目錄下。如果該目錄下沒(méi)有所需文件,那么應(yīng)該把所需文件復(fù)制一份過(guò)來(lái)。如果目錄下有些東西確實(shí)不希望構(gòu)建時(shí)傳給 Docker 引擎,那么可以用 .gitignore 一樣的語(yǔ)法寫一個(gè) .dockerignore,該文件是用于剔除不需要作為上下文傳遞給 Docker 引擎的。

那么為什么會(huì)有人誤以為 . 是指定 Dockerfile 所在目錄呢?這是因?yàn)樵谀J(rèn)情況下,如果不額外指定 Dockerfile 的話,會(huì)將上下文目錄下的名為 Dockerfile 的文件作為 Dockerfile。

這只是默認(rèn)行為,實(shí)際上 Dockerfile 的文件名并不要求必須為 Dockerfile,而且并不要求必須位于上下文目錄中,比如可以用 -f ../Dockerfile.php 參數(shù)指定某個(gè)文件作為 Dockerfile。

當(dāng)然,一般大家習(xí)慣性的會(huì)使用默認(rèn)的文件名 Dockerfile,以及會(huì)將其置于鏡像構(gòu)建上下文目錄中。

Dockerfile 指令詳解

COPY 復(fù)制文件

格式:

  • COPY <源路徑>... <目標(biāo)路徑>
  • COPY ["<源路徑1>",... "<目標(biāo)路徑>"]

RUN 指令一樣,也有兩種格式,一種類似于命令行,一種類似于函數(shù)調(diào)用。

COPY 指令將從構(gòu)建上下文目錄中 <源路徑> 的文件/目錄復(fù)制到新的一層的鏡像內(nèi)的 <目標(biāo)路徑> 位置。比如:

COPY package.json /usr/src/app/

<源路徑> 可以是多個(gè),甚至可以是通配符,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目標(biāo)路徑> 可以是容器內(nèi)的絕對(duì)路徑,也可以是相對(duì)于工作目錄的相對(duì)路徑(工作目錄可以用 WORKDIR 指令來(lái)指定)。目標(biāo)路徑不需要事先創(chuàng)建,如果目錄不存在會(huì)在復(fù)制文件前先行創(chuàng)建缺失目錄。

此外,還需要注意一點(diǎn),使用 COPY 指令,源文件的各種元數(shù)據(jù)都會(huì)保留。比如讀、寫、執(zhí)行權(quán)限、文件變更時(shí)間等。這個(gè)特性對(duì)于鏡像定制很有用。特別是構(gòu)建相關(guān)文件都在使用 Git 進(jìn)行管理的時(shí)候。

ADD 更高級(jí)的復(fù)制文件

ADD 指令和 COPY 的格式和性質(zhì)基本一致。但是在 COPY 基礎(chǔ)上增加了一些功能。

比如 <源路徑> 可以是一個(gè) URL,這種情況下,Docker 引擎會(huì)試圖去下載這個(gè)鏈接的文件放到 <目標(biāo)路徑> 去。下載后的文件權(quán)限自動(dòng)設(shè)置為 600,如果這并不是想要的權(quán)限,那么還需要增加額外的一層 RUN 進(jìn)行權(quán)限調(diào)整,另外,如果下載的是個(gè)壓縮包,需要解壓縮,也一樣還需要額外的一層 RUN 指令進(jìn)行解壓縮。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下載,處理權(quán)限、解壓縮、然后清理無(wú)用文件更合理。因此,這個(gè)功能其實(shí)并不實(shí)用,而且不推薦使用。

如果 <源路徑> 為一個(gè) tar 壓縮文件的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會(huì)自動(dòng)解壓縮這個(gè)壓縮文件到 <目標(biāo)路徑> 去。

在某些情況下,這個(gè)自動(dòng)解壓縮的功能非常有用,比如官方鏡像 ubuntu 中:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

但在某些情況下,如果我們真的是希望復(fù)制個(gè)壓縮文件進(jìn)去,而不解壓縮,這時(shí)就不可以使用 ADD 命令了。

在 Docker 官方的最佳實(shí)踐文檔中要求,盡可能的使用 COPY,因?yàn)?COPY 的語(yǔ)義很明確,就是復(fù)制文件而已,而 ADD 則包含了更復(fù)雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場(chǎng)合,就是所提及的需要自動(dòng)解壓縮的場(chǎng)合。

另外需要注意的是,ADD 指令會(huì)令鏡像構(gòu)建緩存失效,從而可能會(huì)令鏡像構(gòu)建變得比較緩慢。

因此在 COPYADD 指令中選擇的時(shí)候,可以遵循這樣的原則,所有的文件復(fù)制均使用 COPY 指令,僅在需要自動(dòng)解壓縮的場(chǎng)合使用 ADD。

CMD 容器啟動(dòng)命令

Docker 不是虛擬機(jī),容器就是進(jìn)程。既然是進(jìn)程,那么在啟動(dòng)容器的時(shí)候,需要指定所運(yùn)行的程序及參數(shù)。CMD 指令就是用于指定默認(rèn)的容器主進(jìn)程的啟動(dòng)命令的。

CMD 指令的格式和 RUN 相似,也是兩種格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]
  • 參數(shù)列表格式:CMD ["參數(shù)1", "參數(shù)2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數(shù)。

在運(yùn)行時(shí)可以指定新的命令來(lái)替代鏡像設(shè)置中的這個(gè)默認(rèn)命令,比如,ubuntu 鏡像默認(rèn)的 CMD/bin/bash,如果我們直接 docker run -it ubuntu 的話,會(huì)直接進(jìn)入 bash。我們也可以在運(yùn)行時(shí)指定運(yùn)行別的命令,如 docker run -it ubuntu cat /etc/os-release。這就是用 cat /etc/os-release 命令替換了默認(rèn)的 /bin/bash 命令了,輸出了系統(tǒng)版本信息。

在指令格式上,一般推薦使用 exec 格式,這類格式在解析時(shí)會(huì)被解析為 JSON 數(shù)組,因此一定要使用雙引號(hào) ",而不要使用單引號(hào)。

如果使用 shell 格式的話,實(shí)際的命令會(huì)被包裝為 sh -c 的參數(shù)的形式進(jìn)行執(zhí)行。比如:

CMD echo $HOME

在實(shí)際執(zhí)行中,會(huì)將其變更為:

CMD [ "sh", "-c", "echo $HOME" ]

這就是為什么我們可以使用環(huán)境變量的原因,因?yàn)檫@些環(huán)境變量會(huì)被 shell 進(jìn)行解析處理。

提到 CMD 就不得不提容器中應(yīng)用在前臺(tái)執(zhí)行和后臺(tái)執(zhí)行的問(wèn)題。這是初學(xué)者常出現(xiàn)的一個(gè)混淆。

Docker 不是虛擬機(jī),容器中的應(yīng)用都應(yīng)該以前臺(tái)執(zhí)行,而不是像虛擬機(jī)、物理機(jī)里面那樣,用 upstart/systemd 去啟動(dòng)后臺(tái)服務(wù),容器內(nèi)沒(méi)有后臺(tái)服務(wù)的概念。

一些初學(xué)者將 CMD 寫為:

CMD service nginx start

然后發(fā)現(xiàn)容器執(zhí)行后就立即退出了。甚至在容器內(nèi)去使用 systemctl 命令結(jié)果卻發(fā)現(xiàn)根本執(zhí)行不了。這就是因?yàn)闆](méi)有搞明白前臺(tái)、后臺(tái)的概念,沒(méi)有區(qū)分容器和虛擬機(jī)的差異,依舊在以傳統(tǒng)虛擬機(jī)的角度去理解容器。

對(duì)于容器而言,其啟動(dòng)程序就是容器應(yīng)用進(jìn)程,容器就是為了主進(jìn)程而存在的,主進(jìn)程退出,容器就失去了存在的意義,從而退出,其它輔助進(jìn)程不是它需要關(guān)心的東西。

而使用 service nginx start 命令,則是希望 upstart 來(lái)以后臺(tái)守護(hù)進(jìn)程形式啟動(dòng) nginx 服務(wù)。而剛才說(shuō)了 CMD service nginx start 會(huì)被理解為 CMD [ "sh", "-c", "service nginx start"],因此主進(jìn)程實(shí)際上是 sh。那么當(dāng) service nginx start 命令結(jié)束后,sh 也就結(jié)束了,sh 作為主進(jìn)程退出了,自然就會(huì)令容器退出。

正確的做法是直接執(zhí)行 nginx 可執(zhí)行文件,并且要求以前臺(tái)形式運(yùn)行。比如:

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 入口點(diǎn)

ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。

ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動(dòng)程序及參數(shù)。ENTRYPOINT 在運(yùn)行時(shí)也可以替代,不過(guò)比 CMD 要略顯繁瑣,需要通過(guò) docker run 的參數(shù) --entrypoint 來(lái)指定。

當(dāng)指定了 ENTRYPOINT 后,CMD 的含義就發(fā)生了改變,不再是直接的運(yùn)行其命令,而是將 CMD 的內(nèi)容作為參數(shù)傳給 ENTRYPOINT 指令,換句話說(shuō)實(shí)際執(zhí)行時(shí),將變?yōu)椋?/p>

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,為什么還要有 ENTRYPOINT 呢?這種 <ENTRYPOINT> "<CMD>" 有什么好處么?讓我們來(lái)看幾個(gè)場(chǎng)景。

場(chǎng)景一:讓鏡像變成像命令一樣使用

假設(shè)我們需要一個(gè)得知自己當(dāng)前公網(wǎng) IP 的鏡像,那么可以先用 CMD 來(lái)實(shí)現(xiàn):

FROM ubuntu:16.04
RUN apt-get update     && apt-get install -y curl     && rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://" ]

假如我們使用 docker build -t myip . 來(lái)構(gòu)建鏡像的話,如果我們需要查詢當(dāng)前公網(wǎng) IP,只需要執(zhí)行:

$ docker run myip
當(dāng)前 IP:61.148.226.66 來(lái)自:北京市 聯(lián)通

嗯,這么看起來(lái)好像可以直接把鏡像當(dāng)做命令使用了,不過(guò)命令總有參數(shù),如果我們希望加參數(shù)呢?比如從上面的 CMD 中可以看到實(shí)質(zhì)的命令是 curl,那么如果我們希望顯示 HTTP 頭信息,就需要加上 -i 參數(shù)。那么我們可以直接加 -i 參數(shù)給 docker run myip 么?

$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".

我們可以看到可執(zhí)行文件找不到的報(bào)錯(cuò),executable file not found。之前我們說(shuō)過(guò),跟在鏡像名后面的是 command,運(yùn)行時(shí)會(huì)替換 CMD 的默認(rèn)值。因此這里的 -i 替換了原來(lái)的 CMD,而不是添加在原來(lái)的 curl -s http:// 后面。而 -i 根本不是命令,所以自然找不到。

那么如果我們希望加入 -i 這參數(shù),我們就必須重新完整的輸入這個(gè)命令:

$ docker run myip curl -s http:// -i

這顯然不是很好的解決方案,而使用 ENTRYPOINT 就可以解決這個(gè)問(wèn)題?,F(xiàn)在我們重新用 ENTRYPOINT 來(lái)實(shí)現(xiàn)這個(gè)鏡像:

FROM ubuntu:16.04
RUN apt-get update     && apt-get install -y curl     && rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://" ]

這次我們?cè)賮?lái)嘗試直接使用 docker run myip -i

$ docker run myip
當(dāng)前 IP:61.148.226.66 來(lái)自:北京市 聯(lián)通

$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive

當(dāng)前 IP:61.148.226.66 來(lái)自:北京市 聯(lián)通

可以看到,這次成功了。這是因?yàn)楫?dāng)存在 ENTRYPOINT 后,CMD 的內(nèi)容將會(huì)作為參數(shù)傳給 ENTRYPOINT,而這里 -i 就是新的 CMD,因此會(huì)作為參數(shù)傳給 curl,從而達(dá)到了我們預(yù)期的效果。

場(chǎng)景二:應(yīng)用運(yùn)行前的準(zhǔn)備工作

啟動(dòng)容器就是啟動(dòng)主進(jìn)程,但有些時(shí)候,啟動(dòng)主進(jìn)程前,需要一些準(zhǔn)備工作。

比如 mysql 類的數(shù)據(jù)庫(kù),可能需要一些數(shù)據(jù)庫(kù)配置、初始化的工作,這些工作要在最終的 mysql 服務(wù)器運(yùn)行之前解決。

此外,可能希望避免使用 root 用戶去啟動(dòng)服務(wù),從而提高安全性,而在啟動(dòng)服務(wù)前還需要以 root 身份執(zhí)行一些必要的準(zhǔn)備工作,最后切換到服務(wù)用戶身份啟動(dòng)服務(wù)。或者除了服務(wù)外,其它命令依舊可以使用 root 身份執(zhí)行,方便調(diào)試等。

這些準(zhǔn)備工作是和容器 CMD 無(wú)關(guān)的,無(wú)論 CMD 為什么,都需要事先進(jìn)行一個(gè)預(yù)處理的工作。這種情況下,可以寫一個(gè)腳本,然后放入 ENTRYPOINT 中去執(zhí)行,而這個(gè)腳本會(huì)將接到的參數(shù)(也就是 <CMD>)作為命令,在腳本最后執(zhí)行。比如官方鏡像 redis 中就是這么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

可以看到其中為了 redis 服務(wù)創(chuàng)建了 redis 用戶,并在最后指定了 ENTRYPOINTdocker-entrypoint.sh 腳本。

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
    chown -R redis .
    exec su-exec redis "$0" "$@"
fi

exec "$@"

該腳本的內(nèi)容就是根據(jù) CMD 的內(nèi)容來(lái)判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動(dòng)服務(wù)器,否則依舊使用 root 身份執(zhí)行。比如:

$ docker run -it redis id
uid=0(root) gid=0(root) groups=0(root)

ENV 設(shè)置環(huán)境變量

格式有兩種:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

這個(gè)指令很簡(jiǎn)單,就是設(shè)置環(huán)境變量而已,無(wú)論是后面的其它指令,如 RUN,還是運(yùn)行時(shí)的應(yīng)用,都可以直接使用這里定義的環(huán)境變量。

ENV VERSION=1.0 DEBUG=on     NAME="Happy Feet"

這個(gè)例子中演示了如何換行,以及對(duì)含有空格的值用雙引號(hào)括起來(lái)的辦法,這和 Shell 下的行為是一致的。

定義了環(huán)境變量,那么在后續(xù)的指令中,就可以使用這個(gè)環(huán)境變量。比如在官方 node 鏡像 Dockerfile 中,就有類似這樣的代碼:

ENV NODE_VERSION 7.2.0

RUN curl -SLO "https:///dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"   && curl -SLO "https:///dist/v$NODE_VERSION/SHASUMS256.txt.asc"   && gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc   && grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c -   && tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1   && rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt   && ln -s /usr/local/bin/node /usr/local/bin/nodejs

在這里先定義了環(huán)境變量 NODE_VERSION,其后的 RUN 這層里,多次使用 $NODE_VERSION 來(lái)進(jìn)行操作定制??梢钥吹剑瑢?lái)升級(jí)鏡像構(gòu)建版本的時(shí)候,只需要更新 7.2.0 即可,Dockerfile 構(gòu)建維護(hù)變得更輕松了。

下列指令可以支持環(huán)境變量引用: ADD、COPY、ENV、EXPOSE、LABELUSER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。

可以從這個(gè)指令列表里感覺(jué)到,環(huán)境變量可以使用的地方很多,很強(qiáng)大。通過(guò)環(huán)境變量,我們可以讓一份 Dockerfile 制作更多的鏡像,只需使用不同的環(huán)境變量即可。

ARG 構(gòu)建參數(shù)

格式:ARG <參數(shù)名>[=<默認(rèn)值>]

構(gòu)建參數(shù)和 ENV 的效果一樣,都是設(shè)置環(huán)境變量。所不同的是,ARG 所設(shè)置的構(gòu)建環(huán)境的環(huán)境變量,在將來(lái)容器運(yùn)行時(shí)是不會(huì)存在這些環(huán)境變量的。但是不要因此就使用 ARG 保存密碼之類的信息,因?yàn)?docker history 還是可以看到所有值的。

Dockerfile 中的 ARG 指令是定義參數(shù)名稱,以及定義其默認(rèn)值。該默認(rèn)值可以在構(gòu)建命令 docker build 中用 --build-arg <參數(shù)名>=<值> 來(lái)覆蓋。

在 1.13 之前的版本,要求 --build-arg 中的參數(shù)名,必須在 Dockerfile 中用 ARG 定義過(guò)了,換句話說(shuō),就是 --build-arg 指定的參數(shù),必須在 Dockerfile 中使用了。如果對(duì)應(yīng)參數(shù)沒(méi)有被使用,則會(huì)報(bào)錯(cuò)退出構(gòu)建。從 1.13 開(kāi)始,這種嚴(yán)格的限制被放開(kāi),不再報(bào)錯(cuò)退出,而是顯示警告信息,并繼續(xù)構(gòu)建。這對(duì)于使用 CI 系統(tǒng),用同樣的構(gòu)建流程構(gòu)建不同的 Dockerfile 的時(shí)候比較有幫助,避免構(gòu)建命令必須根據(jù)每個(gè) Dockerfile 的內(nèi)容修改。

VOLUME 定義匿名卷

格式為:

  • VOLUME ["<路徑1>", "<路徑2>"...]
  • VOLUME <路徑>

之前說(shuō)過(guò),容器運(yùn)行時(shí)應(yīng)該盡量保持容器存儲(chǔ)層不發(fā)生寫操作,對(duì)于數(shù)據(jù)庫(kù)類需要保存動(dòng)態(tài)數(shù)據(jù)的應(yīng)用,其數(shù)據(jù)庫(kù)文件應(yīng)該保存于卷(volume)中,后面的章節(jié)我們會(huì)進(jìn)一步介紹 Docker 卷的概念。為了防止運(yùn)行時(shí)用戶忘記將動(dòng)態(tài)文件所保存目錄掛載為卷,在 Dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在運(yùn)行時(shí)如果用戶不指定掛載,其應(yīng)用也可以正常運(yùn)行,不會(huì)向容器存儲(chǔ)層寫入大量數(shù)據(jù)。

VOLUME /data

這里的 /data 目錄就會(huì)在運(yùn)行時(shí)自動(dòng)掛載為匿名卷,任何向 /data 中寫入的信息都不會(huì)記錄進(jìn)容器存儲(chǔ)層,從而保證了容器存儲(chǔ)層的無(wú)狀態(tài)化。當(dāng)然,運(yùn)行時(shí)可以覆蓋這個(gè)掛載設(shè)置。比如:

docker run -d -v mydata:/data xxxx

在這行命令中,就使用了 mydata 這個(gè)命名卷掛載到了 /data 這個(gè)位置,替代了 Dockerfile 中定義的匿名卷的掛載配置。

EXPOSE 聲明端口

格式為 EXPOSE <端口1> [<端口2>...]

EXPOSE 指令是聲明運(yùn)行時(shí)容器提供服務(wù)端口,這只是一個(gè)聲明,在運(yùn)行時(shí)并不會(huì)因?yàn)檫@個(gè)聲明應(yīng)用就會(huì)開(kāi)啟這個(gè)端口的服務(wù)。在 Dockerfile 中寫入這樣的聲明有兩個(gè)好處,一個(gè)是幫助鏡像使用者理解這個(gè)鏡像服務(wù)的守護(hù)端口,以方便配置映射;另一個(gè)用處則是在運(yùn)行時(shí)使用隨機(jī)端口映射時(shí),也就是 docker run -P 時(shí),會(huì)自動(dòng)隨機(jī)映射 EXPOSE 的端口。

要將 EXPOSE 和在運(yùn)行時(shí)使用 -p <宿主端口>:<容器端口> 區(qū)分開(kāi)來(lái)。-p,是映射宿主端口和容器端口,換句話說(shuō),就是將容器的對(duì)應(yīng)端口服務(wù)公開(kāi)給外界訪問(wèn),而 EXPOSE 僅僅是聲明容器打算使用什么端口而已,并不會(huì)自動(dòng)在宿主進(jìn)行端口映射。

WORKDIR 指定工作目錄

格式為 WORKDIR <工作目錄路徑>

使用 WORKDIR 指令可以來(lái)指定工作目錄(或者稱為當(dāng)前目錄),以后各層的當(dāng)前目錄就被改為指定的目錄,如該目錄不存在,WORKDIR 會(huì)幫你建立目錄。

之前提到一些初學(xué)者常犯的錯(cuò)誤是把 Dockerfile 等同于 Shell 腳本來(lái)書寫,這種錯(cuò)誤的理解還可能會(huì)導(dǎo)致出現(xiàn)下面這樣的錯(cuò)誤:

RUN cd /app
RUN echo "hello" > world.txt

如果將這個(gè) Dockerfile 進(jìn)行構(gòu)建鏡像運(yùn)行后,會(huì)發(fā)現(xiàn)找不到 /app/world.txt 文件,或者其內(nèi)容不是 hello。原因其實(shí)很簡(jiǎn)單,在 Shell 中,連續(xù)兩行是同一個(gè)進(jìn)程執(zhí)行環(huán)境,因此前一個(gè)命令修改的內(nèi)存狀態(tài),會(huì)直接影響后一個(gè)命令;而在 Dockerfile 中,這兩行 RUN 命令的執(zhí)行環(huán)境根本不同,是兩個(gè)完全不同的容器。這就是對(duì) Dokerfile 構(gòu)建分層存儲(chǔ)的概念不了解所導(dǎo)致的錯(cuò)誤。

之前說(shuō)過(guò)每一個(gè) RUN 都是啟動(dòng)一個(gè)容器、執(zhí)行命令、然后提交存儲(chǔ)層文件變更。第一層 RUN cd /app 的執(zhí)行僅僅是當(dāng)前進(jìn)程的工作目錄變更,一個(gè)內(nèi)存上的變化而已,其結(jié)果不會(huì)造成任何文件變更。而到第二層的時(shí)候,啟動(dòng)的是一個(gè)全新的容器,跟第一層的容器更完全沒(méi)關(guān)系,自然不可能繼承前一層構(gòu)建過(guò)程中的內(nèi)存變化。

因此如果需要改變以后各層的工作目錄的位置,那么應(yīng)該使用 WORKDIR 指令。

HEALTHCHECK 健康檢查

格式:

  • HEALTHCHECK [選項(xiàng)] CMD <命令>:設(shè)置檢查容器健康狀況的命令
  • HEALTHCHECK NONE:如果基礎(chǔ)鏡像有健康檢查指令,使用這行可以屏蔽掉其健康檢查指令

HEALTHCHECK 指令是告訴 Docker 應(yīng)該如何進(jìn)行判斷容器的狀態(tài)是否正常.

在沒(méi)有 HEALTHCHECK 指令前,Docker 引擎只可以通過(guò)容器內(nèi)主進(jìn)程是否退出來(lái)判斷容器是否狀態(tài)異常。很多情況下這沒(méi)問(wèn)題,但是如果程序進(jìn)入死鎖狀態(tài),或者死循環(huán)狀態(tài),應(yīng)用進(jìn)程并不退出,但是該容器已經(jīng)無(wú)法提供服務(wù)了。在 1.12 以前,Docker 不會(huì)檢測(cè)到容器的這種狀態(tài),從而不會(huì)重新調(diào)度,導(dǎo)致可能會(huì)有部分容器已經(jīng)無(wú)法提供服務(wù)了卻還在接受用戶請(qǐng)求。

而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通過(guò)該指令指定一行命令,用這行命令來(lái)判斷容器主進(jìn)程的服務(wù)狀態(tài)是否還正常,從而比較真實(shí)的反應(yīng)容器實(shí)際狀態(tài)。

當(dāng)在一個(gè)鏡像指定了 HEALTHCHECK 指令后,用其啟動(dòng)容器,初始狀態(tài)會(huì)為 starting,在 HEALTHCHECK 指令檢查成功后變?yōu)?healthy,如果連續(xù)一定次數(shù)失敗,則會(huì)變?yōu)?unhealthy。

HEALTHCHECK 支持下列選項(xiàng):

  • --interval=<間隔>:兩次健康檢查的間隔,默認(rèn)為 30 秒;
  • --timeout=<時(shí)長(zhǎng)>:健康檢查命令運(yùn)行超時(shí)時(shí)間,如果超過(guò)這個(gè)時(shí)間,本次健康檢查就被視為失敗,默認(rèn) 30 秒;
  • --retries=<次數(shù)>:當(dāng)連續(xù)失敗指定次數(shù)后,則將容器狀態(tài)視為 unhealthy,默認(rèn) 3 次。

CMD, ENTRYPOINT 一樣,HEALTHCHECK 只可以出現(xiàn)一次,如果寫了多個(gè),只有最后一個(gè)生效。

HEALTHCHECK [選項(xiàng)] CMD 后面的命令,格式和 ENTRYPOINT 一樣,分為 shell 格式,和 exec 格式。命令的返回值決定了該次健康檢查的成功與否:0:成功;1:失??;2:保留,不要使用這個(gè)值。

假設(shè)我們有個(gè)鏡像是個(gè)最簡(jiǎn)單的 Web 服務(wù),我們希望增加健康檢查來(lái)判斷其 Web 服務(wù)是否在正常工作,我們可以用 curl 來(lái)幫助判斷,其 DockerfileHEALTHCHECK 可以這么寫:

FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s   CMD curl -fs http://localhost/ || exit 1

這里我們?cè)O(shè)置了每 5 秒檢查一次(這里為了試驗(yàn)所以間隔非常短,實(shí)際應(yīng)該相對(duì)較長(zhǎng)),如果健康檢查命令超過(guò) 3 秒沒(méi)響應(yīng)就視為失敗,并且使用 curl -fs http://localhost/ || exit 1 作為健康檢查命令。

使用 docker build 來(lái)構(gòu)建這個(gè)鏡像:

$ docker build -t myweb:v1 .

構(gòu)建好了后,我們啟動(dòng)一個(gè)容器:

$ docker run -d --name web -p 80:80 myweb:v1

當(dāng)運(yùn)行該鏡像后,可以通過(guò) docker ps 看到最初的狀態(tài)為 (health: starting)

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   3 seconds ago       Up 2 seconds (health: starting)   80/tcp, 443/tcp     web

在等待幾秒鐘后,再次 docker ps,就會(huì)看到健康狀態(tài)變化為了 (healthy)

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES
03e28eb00bd0        myweb:v1            "nginx -g 'daemon off"   18 seconds ago      Up 16 seconds (healthy)   80/tcp, 443/tcp     web

如果健康檢查連續(xù)失敗超過(guò)了重試次數(shù),狀態(tài)就會(huì)變?yōu)?(unhealthy)。

為了幫助排障,健康檢查命令的輸出(包括 stdout 以及 stderr)都會(huì)被存儲(chǔ)于健康狀態(tài)里,可以用 docker inspect 來(lái)查看。

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
    "FailingStreak": 0,
    "Log": [
        {
            "End": "2016-11-25T14:35:37.940957051Z",
            "ExitCode": 0,
            "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http:///\"></a>.<br/>\nCommercial support is available at\n<a href=\"http:///\"></a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
            "Start": "2016-11-25T14:35:37.780192565Z"
        }
    ],
    "Status": "healthy"
}

ONBUILD 鏡像復(fù)用及項(xiàng)目環(huán)境管理

格式:ONBUILD <其它指令>。

ONBUILD 是一個(gè)特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而這些指令,在當(dāng)前鏡像構(gòu)建時(shí)并不會(huì)被執(zhí)行。只有當(dāng)以當(dāng)前鏡像為基礎(chǔ)鏡像,去構(gòu)建下一級(jí)鏡像的時(shí)候才會(huì)被執(zhí)行。

Dockerfile 中的其它指令都是為了定制當(dāng)前鏡像而準(zhǔn)備的,唯有 ONBUILD 是為了幫助別人定制自己而準(zhǔn)備的。

假設(shè)我們要制作 Node.js 所寫的應(yīng)用的鏡像。我們都知道 Node.js 使用 npm 進(jìn)行包管理,所有依賴、配置、啟動(dòng)信息等會(huì)放到 package.json 文件里。在拿到程序代碼后,需要先進(jìn)行 npm install 才可以獲得所有需要的依賴。然后就可以通過(guò) npm start 來(lái)啟動(dòng)應(yīng)用。因此,一般來(lái)說(shuō)會(huì)這樣寫 Dockerfile

FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]

把這個(gè) Dockerfile 放到 Node.js 項(xiàng)目的根目錄,構(gòu)建好鏡像后,就可以直接拿來(lái)啟動(dòng)容器運(yùn)行。但是如果我們還有第二個(gè) Node.js 項(xiàng)目也差不多呢?好吧,那就再把這個(gè) Dockerfile 復(fù)制到第二個(gè)項(xiàng)目里。那如果有第三個(gè)項(xiàng)目呢?再?gòu)?fù)制么?文件的副本越多,版本控制就越困難,讓我們繼續(xù)看這樣的場(chǎng)景維護(hù)的問(wèn)題。

如果第一個(gè) Node.js 項(xiàng)目在開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)這個(gè) Dockerfile 里存在問(wèn)題,比如敲錯(cuò)字了、或者需要安裝額外的包,然后開(kāi)發(fā)人員修復(fù)了這個(gè) Dockerfile,再次構(gòu)建,問(wèn)題解決。第一個(gè)項(xiàng)目沒(méi)問(wèn)題了,但是第二個(gè)項(xiàng)目呢?雖然最初 Dockerfile 是復(fù)制、粘貼自第一個(gè)項(xiàng)目的,但是并不會(huì)因?yàn)榈谝粋€(gè)項(xiàng)目修復(fù)了他們的 Dockerfile,而第二個(gè)項(xiàng)目的 Dockerfile 就會(huì)被自動(dòng)修復(fù)。

那么我們可不可以做一個(gè)基礎(chǔ)鏡像,然后各個(gè)項(xiàng)目使用這個(gè)基礎(chǔ)鏡像呢?這樣基礎(chǔ)鏡像更新,各個(gè)項(xiàng)目不用同步 Dockerfile 的變化,重新構(gòu)建后就繼承了基礎(chǔ)鏡像的更新?好吧,可以,讓我們看看這樣的結(jié)果。那么上面的這個(gè) Dockerfile 就會(huì)變?yōu)椋?/p>

FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]

這里我們把項(xiàng)目相關(guān)的構(gòu)建指令拿出來(lái),放到子項(xiàng)目里去。假設(shè)這個(gè)基礎(chǔ)鏡像的名字為 my-node 的話,各個(gè)項(xiàng)目?jī)?nèi)的自己的 Dockerfile 就變?yōu)椋?/p>

FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/

基礎(chǔ)鏡像變化后,各個(gè)項(xiàng)目都用這個(gè) Dockerfile 重新構(gòu)建鏡像,會(huì)繼承基礎(chǔ)鏡像的更新。

那么,問(wèn)題解決了么?沒(méi)有。準(zhǔn)確說(shuō),只解決了一半。如果這個(gè) Dockerfile 里面有些東西需要調(diào)整呢?比如 npm install 都需要加一些參數(shù),那怎么辦?這一行 RUN 是不可能放入基礎(chǔ)鏡像的,因?yàn)樯婕暗搅水?dāng)前項(xiàng)目的 ./package.json,難道又要一個(gè)個(gè)修改么?所以說(shuō),這樣制作基礎(chǔ)鏡像,只解決了原來(lái)的 Dockerfile 的前4條指令的變化問(wèn)題,而后面三條指令的變化則完全沒(méi)辦法處理。

ONBUILD 可以解決這個(gè)問(wèn)題。讓我們用 ONBUILD 重新寫一下基礎(chǔ)鏡像的 Dockerfile:

FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

這次我們回到原始的 Dockerfile,但是這次將項(xiàng)目相關(guān)的指令加上 ONBUILD,這樣在構(gòu)建基礎(chǔ)鏡像的時(shí)候,這三行并不會(huì)被執(zhí)行。然后各個(gè)項(xiàng)目的 Dockerfile 就變成了簡(jiǎn)單地:

FROM my-node

是的,只有這么一行。當(dāng)在各個(gè)項(xiàng)目目錄中,用這個(gè)只有一行的 Dockerfile 構(gòu)建鏡像時(shí),之前基礎(chǔ)鏡像的那三行 ONBUILD 就會(huì)開(kāi)始執(zhí)行,成功的將當(dāng)前項(xiàng)目的代碼復(fù)制進(jìn)鏡像、并且針對(duì)本項(xiàng)目執(zhí)行 npm install,生成應(yīng)用鏡像。

刪除本地鏡像

如果要?jiǎng)h除本地的鏡像,可以使用 docker rmi 命令,其格式為:

docker rmi [選項(xiàng)] <鏡像1> [<鏡像2> ...]

注意 docker rm 命令是刪除容器,不要混淆。

用 ID、鏡像名、摘要?jiǎng)h除鏡像

其中,<鏡像> 可以是 鏡像短 ID、鏡像長(zhǎng) ID鏡像名 或者 鏡像摘要。

比如我們有這么一些鏡像:

$ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
centos                      latest              0584b3d2cf6d        3 weeks ago         196.5 MB
redis                       alpine              501ad78535f0        3 weeks ago         21.03 MB
docker                      latest              cf693ec9b5c7        3 weeks ago         105.1 MB
nginx                       latest              e43d811ce2f4        5 weeks ago         181.5 MB

我們可以用鏡像的完整 ID,也稱為 長(zhǎng) ID,來(lái)刪除鏡像。使用腳本的時(shí)候可能會(huì)用長(zhǎng) ID,但是人工輸入就太累了,所以更多的時(shí)候是用 短 ID 來(lái)刪除鏡像。docker images 默認(rèn)列出的就已經(jīng)是短 ID 了,一般取前3個(gè)字符以上,只要足夠區(qū)分于別的鏡像就可以了。

比如這里,如果我們要?jiǎng)h除 redis:alpine 鏡像,可以執(zhí)行:

$ docker rmi 501
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7

我們也可以用鏡像名,也就是 <倉(cāng)庫(kù)名>:<標(biāo)簽>,來(lái)刪除鏡像。

$ docker rmi centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38

當(dāng)然,更精確的是使用 鏡像摘要 刪除鏡像。

$ docker images --digests
REPOSITORY                  TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
node                        slim                sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228   6e0c4c8e3913        3 weeks ago         214 MB

$ docker rmi node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228

用 docker images 命令來(lái)配合

像其它可以承接多個(gè)實(shí)體的命令一樣,可以使用 docker images -q 來(lái)配合使用 docker rmi,這樣可以成批的刪除希望刪除的鏡像。比如之前我們介紹過(guò)的,刪除虛懸鏡像的指令是:

$ docker rmi $(docker images -q -f dangling=true)

我們?cè)凇扮R像列表”章節(jié)介紹過(guò)很多過(guò)濾鏡像列表的方式都可以拿過(guò)來(lái)使用。

比如,我們需要?jiǎng)h除所有倉(cāng)庫(kù)名為 redis 的鏡像:

$ docker rmi $(docker images -q redis)

或者刪除所有在 mongo:3.2 之前的鏡像:

$ docker rmi $(docker images -q -f before=mongo:3.2)

充分利用你的想象力和 Linux 命令行的強(qiáng)大,你可以完成很多非常贊的功能。

參考文檔

操作 Docker 容器

容器是 Docker 又一核心概念。

簡(jiǎn)單的說(shuō),容器是獨(dú)立運(yùn)行的一個(gè)或一組應(yīng)用,以及它們的運(yùn)行態(tài)環(huán)境。對(duì)應(yīng)的,虛擬機(jī)可以理解為模擬運(yùn)行的一整套操作系統(tǒng)(提供了運(yùn)行態(tài)環(huán)境和其他系統(tǒng)環(huán)境)和跑在上面的應(yīng)用。

啟動(dòng)容器

啟動(dòng)容器有兩種方式,一種是基于鏡像新建一個(gè)容器并啟動(dòng),另外一個(gè)是將在終止?fàn)顟B(tài)(stopped)的容器重新啟動(dòng)。

因?yàn)?Docker 的容器實(shí)在太輕量級(jí)了,很多時(shí)候用戶都是隨時(shí)刪除和新創(chuàng)建容器。

新建并啟動(dòng)

所需要的命令主要為 docker run

例如,下面的命令輸出一個(gè) “Hello World”,之后終止容器。

$ docker run ubuntu:14.04 /bin/echo 'Hello world'

Unable to find image 'ubuntu:14.04' locally
14.04: Pulling from library/ubuntu
cb56c90f0b30: Pull complete
0acc551e5716: Pull complete
8956dcd35143: Pull complete
908242721214: Pull complete
b44ff14dd3bb: Pull complete
Digest: sha256:5faf6cb681da2be979a177b60d8c18497f962e3d82268c49db6c74008d0c294d
Status: Downloaded newer image for ubuntu:14.04
Hello world

這跟在本地直接執(zhí)行 /bin/echo 'hello world' 幾乎感覺(jué)不出任何區(qū)別。

下面的命令則啟動(dòng)一個(gè) bash 終端,允許用戶進(jìn)行交互。

$ docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#

其中,-t 選項(xiàng)讓Docker分配一個(gè)偽終端(pseudo-tty)并綁定到容器的標(biāo)準(zhǔn)輸入上, -i 則讓容器的標(biāo)準(zhǔn)輸入保持打開(kāi)。

在交互模式下,用戶可以通過(guò)所創(chuàng)建的終端來(lái)輸入命令,例如

root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

容器的核心為所執(zhí)行的應(yīng)用程序,所需要的資源都是應(yīng)用程序運(yùn)行所必需的。除此之外,并沒(méi)有其它的資源??梢栽趥谓K端中利用 pstop 來(lái)查看進(jìn)程信息。

root@ba267838cc1b:/# ps
  PID TTY          TIME CMD
    1 ?        00:00:00 bash
   11 ?        00:00:00 ps

可見(jiàn),容器中僅運(yùn)行了指定的 bash 應(yīng)用。這種特點(diǎn)使得 Docker 對(duì)資源的利用率極高,是貨真價(jià)實(shí)的輕量級(jí)虛擬化。

當(dāng)利用 docker run 來(lái)創(chuàng)建容器時(shí),Docker 在后臺(tái)運(yùn)行的標(biāo)準(zhǔn)操作包括:

  • 檢查本地是否存在指定的鏡像,不存在就從公有倉(cāng)庫(kù)下載
  • 利用鏡像創(chuàng)建并啟動(dòng)一個(gè)容器
  • 分配一個(gè)文件系統(tǒng),并在只讀的鏡像層外面掛載一層可讀寫層
  • 從宿主主機(jī)配置的網(wǎng)橋接口中橋接一個(gè)虛擬接口到容器中去
  • 從地址池配置一個(gè) ip 地址給容器
  • 執(zhí)行用戶指定的應(yīng)用程序
  • 執(zhí)行完畢后容器被終止

查看正在運(yùn)行中的容器

利用 docker ps 命令可以查看正在運(yùn)行中的容器

$ docker ps

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
9dea98e12fc0        nginx:v2            "nginx -g 'daemon ..."   47 hours ago        Up 47 hours         0.0.0.0:82->80/tcp   web2
71e33c548d3d        nginx               "nginx -g 'daemon ..."   47 hours ago        Up 47 hours         0.0.0.0:81->80/tcp   webserver

查看所有容器

利用 docker ps -a 命令可以查看所有容器

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                NAMES
1b6890b715ec        ubuntu:14.04        "/bin/echo 'Hello ..."   25 minutes ago      Exited (0) 25 minutes ago                        relaxed_kilby
9dea98e12fc0        nginx:v2            "nginx -g 'daemon ..."   47 hours ago        Up 47 hours                 0.0.0.0:82->80/tcp   web2
71e33c548d3d        nginx               "nginx -g 'daemon ..."   47 hours ago        Up 47 hours                 0.0.0.0:81->80/tcp   webserver
e708a9002164        hello-world         "/hello"                 47 hours ago        Exited (0) 47 hours ago                          peaceful_brown

啟動(dòng)已終止的容器

可以利用 docker start 命令和上面使用 docker ps -a 查看到的 CONTAINER IDNAMES,直接將一個(gè)已經(jīng)終止的容器啟動(dòng)運(yùn)行。

$ docker start relaxed_kilby

relaxed_kilby

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                NAMES
1b6890b715ec        ubuntu:14.04        "/bin/echo 'Hello ..."   45 minutes ago      Exited (0) 3 seconds ago                        relaxed_kilby
9dea98e12fc0        nginx:v2            "nginx -g 'daemon ..."   47 hours ago        Up 47 hours                0.0.0.0:82->80/tcp   web2
71e33c548d3d        nginx               "nginx -g 'daemon ..."   47 hours ago        Up 47 hours                0.0.0.0:81->80/tcp   webserver
e708a9002164        hello-world         "/hello"                 47 hours ago        Exited (0) 47 hours ago                         peaceful_brown

這里把 新建并啟動(dòng) 章節(jié)中的容器又啟動(dòng)了一次,這次這個(gè)容器和之前不一樣,他啟動(dòng)之后就會(huì)被終止,不會(huì)輸出一個(gè) “Hello World”,之后才終止容器??梢钥?STATUS 輸出,這個(gè)容器的確被啟動(dòng)過(guò).

容器后臺(tái)運(yùn)行

更多的時(shí)候,需要讓 Docker在后臺(tái)運(yùn)行而不是直接把執(zhí)行命令的結(jié)果輸出在當(dāng)前宿主機(jī)下。此時(shí),可以通過(guò)添加 -d 參數(shù)來(lái)實(shí)現(xiàn)。

下面舉兩個(gè)例子來(lái)說(shuō)明一下。

如果不使用 -d 參數(shù)運(yùn)行容器。

$ sudo docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world

容器會(huì)把輸出的結(jié)果(STDOUT)打印到宿主機(jī)上面

如果使用了 -d 參數(shù)運(yùn)行容器。

$ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a

此時(shí)容器會(huì)在后臺(tái)運(yùn)行并不會(huì)把輸出的結(jié)果(STDOUT)打印到宿主機(jī)上面(輸出結(jié)果可以用docker logs 查看)。

注: 容器是否會(huì)長(zhǎng)久運(yùn)行,是和docker run指定的命令有關(guān),和 -d 參數(shù)無(wú)關(guān)。

使用 -d 參數(shù)啟動(dòng)后會(huì)返回一個(gè)唯一的 id,也可以通過(guò) docker ps 命令來(lái)查看容器信息。

$ sudo docker ps
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
77b2dc01fe0f  ubuntu:14.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright

要獲取容器的輸出信息,可以通過(guò) docker logs 命令。

$ sudo docker logs [container ID or NAMES]
hello world
hello world
hello world
. . .

終止容器

可以使用 docker stop 命令和上面使用的 docker ps -a 查看到的 CONTAINER IDNAMES,來(lái)終止一個(gè)運(yùn)行中的容器。

$ docker stop web2

web2

$ docker ps -a

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                NAMES
1b6890b715ec        ubuntu:14.04        "/bin/echo 'Hello ..."   About an hour ago   Exited (0) 15 minutes ago                        relaxed_kilby
9dea98e12fc0        nginx:v2            "nginx -g 'daemon ..."   47 hours ago        Exited (0) 3 seconds ago                         web2
71e33c548d3d        nginx               "nginx -g 'daemon ..."   47 hours ago        Up 47 hours                 0.0.0.0:81->80/tcp   webserver
e708a9002164        hello-world         "/hello"                 2 days ago          Exited (0) 2 days ago                            peaceful_brown

此外,當(dāng) Docker 容器中指定的應(yīng)用終結(jié)時(shí),容器也自動(dòng)終止。例如啟動(dòng)了一個(gè)終端的容器,用戶通過(guò) exit 命令或 Ctrl+d 來(lái)退出終端時(shí),所創(chuàng)建的容器立刻終止。

重啟容器

docker restart 命令會(huì)將一個(gè)運(yùn)行態(tài)的容器終止,然后再重新啟動(dòng)它。

進(jìn)入容器

在使用 -d 參數(shù)時(shí),容器啟動(dòng)后會(huì)進(jìn)入后臺(tái)。
某些時(shí)候需要進(jìn)入容器進(jìn)行操作,有很多種方法,包括使用 docker attach 命令或 nsenter 工具等。

attach 命令

docker attach 是Docker自帶的命令。下面示例如何使用該命令。

$ sudo docker run -idt ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
243c32535da7        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           nostalgic_hypatia
$sudo docker attach nostalgic_hypatia
root@243c32535da7:/#

但是使用 attach 命令有時(shí)候并不方便。當(dāng)多個(gè)窗口同時(shí) attach 到同一個(gè)容器的時(shí)候,所有窗口都會(huì)同步顯示。當(dāng)某個(gè)窗口因命令阻塞時(shí),其他窗口也無(wú)法執(zhí)行操作了。

nsenter 命令

安裝

nsenter 工具在 util-linux 包2.23版本后包含。
可以使用 nsenter -V 查看系統(tǒng)是否安裝了 nsenter 工具.

$ nsenter -V

nsenter from util-linux 2.23.2

如果系統(tǒng)中 util-linux 包沒(méi)有該命令,可以按照下面的方法從源碼安裝。

$ cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-; cd util-linux-2.24;
$ ./configure --without-ncurses
$ make nsenter && sudo cp nsenter /usr/local/bin

使用

nsenter 啟動(dòng)一個(gè)新的shell進(jìn)程(默認(rèn)是/bin/bash), 同時(shí)會(huì)把這個(gè)新進(jìn)程切換到和目標(biāo)(target)進(jìn)程相同的命名空間,這樣就相當(dāng)于進(jìn)入了容器內(nèi)部。nsenter 要正常工作需要有 root 權(quán)限。

為了連接到容器,你還需要找到容器的第一個(gè)進(jìn)程的 PID,可以通過(guò)下面的命令獲取。

PID=$(docker inspect --format "{{ .State.Pid }}" <container>)

通過(guò)這個(gè) PID,就可以連接到這個(gè)容器:

$ nsenter --target $PID --mount --uts --ipc --net --pid

如果無(wú)法通過(guò)以上命令連接到這個(gè)容器,有可能是因?yàn)樗拗鞯哪J(rèn) shell 在容器中并不存在,比如zsh,可以使用如下命令顯式地使用bash。

$ nsenter --target $pid --mount --uts --ipc --net --pid  -- /usr/bin/env \ 
--ignore-environment HOME=/root /bin/bash --login

下面給出一個(gè)完整的例子。

$ sudo docker run -idt ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
243c32535da7        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           nostalgic_hypatia
$ PID=$(docker-pid 243c32535da7)
10981
$ sudo nsenter --target 10981 --mount --uts --ipc --net --pid
root@243c32535da7:/#

更簡(jiǎn)單的,建議大家下載
.bashrc_docker,并將內(nèi)容放到 .bashrc 中。

$ wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker;
$ echo "[ -f ~/.bashrc_docker ] && . ~/.bashrc_docker" >> ~/.bashrc; source ~/.bashrc

這個(gè)文件中定義了很多方便使用 Docker 的命令,例如 docker-pid 可以獲取某個(gè)容器的 PID;而 docker-enter 可以進(jìn)入容器或直接在容器內(nèi)執(zhí)行命令。

$ echo $(docker-pid <container>)
$ docker-enter <container> ls

導(dǎo)出和導(dǎo)入容器快照

導(dǎo)出容器快照

如果要導(dǎo)出本地某個(gè)容器,可以使用 docker export 命令。

$ sudo docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
7691a814370e        ubuntu:14.04        "/bin/bash"         36 hours ago        Exited (0) 21 hours ago                       test
$ sudo docker export 7691a814370e > ubuntu.tar

這樣將導(dǎo)出容器快照到本地文件。

導(dǎo)入容器快照

可以使用 docker import 從容器快照文件中再導(dǎo)入為鏡像,例如

$ cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0
$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
test/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB

此外,也可以通過(guò)指定 URL 或者某個(gè)目錄來(lái)導(dǎo)入,例如

$sudo docker import http://example.com/exampleimage.tgz example/imagerepo

*注:用戶既可以使用 docker load 來(lái)導(dǎo)入鏡像存儲(chǔ)文件到本地鏡像庫(kù),也可以使用 docker import 來(lái)導(dǎo)入一個(gè)容器快照到本地鏡像庫(kù)。這兩者的區(qū)別在于容器快照文件將丟棄所有的歷史記錄和元數(shù)據(jù)信息(即僅保存容器當(dāng)時(shí)的快照狀態(tài)),而鏡像存儲(chǔ)文件將保存完整記錄,體積也要大。此外,從容器快照文件導(dǎo)入時(shí)可以重新指定標(biāo)簽等元數(shù)據(jù)信息。

刪除容器

可以使用 docker rm 來(lái)刪除一個(gè)處于終止?fàn)顟B(tài)的容器。
例如

$sudo docker rm  trusting_newton
trusting_newton

如果要?jiǎng)h除一個(gè)運(yùn)行中的容器,可以添加 -f 參數(shù)。Docker 會(huì)發(fā)送 SIGKILL 信號(hào)給容器。

清理所有處于終止?fàn)顟B(tài)的容器(不建議使用)

docker ps -a 命令可以查看所有已經(jīng)創(chuàng)建的包括終止?fàn)顟B(tài)的容器,如果數(shù)量太多要一個(gè)個(gè)刪除可能會(huì)很麻煩,用 docker rm $(docker ps -a -q) 可以全部清理掉。

*注意:這個(gè)命令其實(shí)會(huì)試圖刪除所有的包括還在運(yùn)行中的容器,不過(guò)就像上面提過(guò)的 docker rm 默認(rèn)并不會(huì)刪除運(yùn)行中的容器。

私有倉(cāng)庫(kù)

有時(shí)候使用阿里云這樣的公共倉(cāng)庫(kù)可能不方便,用戶可以創(chuàng)建一個(gè)本地倉(cāng)庫(kù)供自己使用。

如何使用本地倉(cāng)庫(kù)。

docker-registry 是官方提供的工具,可以用于構(gòu)建私有的鏡像倉(cāng)庫(kù)。

安裝運(yùn)行 docker-registry

容器中運(yùn)行 docker-registry

在安裝了 Docker 后,可以通過(guò)獲取官方 registry 鏡像來(lái)運(yùn)行。

$ sudo docker run -d -p 5000:5000 registry

這將使用官方的 registry 鏡像來(lái)啟動(dòng)本地的私有倉(cāng)庫(kù)。
用戶可以通過(guò)指定參數(shù)來(lái)配置私有倉(cāng)庫(kù)位置,例如配置鏡像存儲(chǔ)到 Amazon S3 服務(wù)。

$ sudo docker run          -e SETTINGS_FLAVOR=s3          -e AWS_BUCKET=acme-docker          -e STORAGE_PATH=/registry          -e AWS_KEY=AKIAHSHB43HS3J92MXZ          -e AWS_SECRET=xdDowwlK7TJajV1Y7EoOZrmuPEJlHYcNP2k4j49T          -e SEARCH_BACKEND=sqlalchemy          -p 5000:5000          registry

此外,還可以指定本地路徑(如 /home/user/registry-conf )下的配置文件。

$ sudo docker run -d -p 5000:5000 -v /home/user/registry-conf:/registry-conf -e DOCKER_REGISTRY_CONFIG=/registry-conf/config.yml registry

默認(rèn)情況下,倉(cāng)庫(kù)會(huì)被創(chuàng)建在容器的 /var/lib/registry (v1 中是/tmp/registry)下??梢酝ㄟ^(guò) -v 參數(shù)來(lái)將鏡像文件存放在本地的指定路徑。
例如下面的例子將上傳的鏡像放到 /opt/data/registry 目錄。

$ sudo docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry registry

本地安裝 docker-registry

對(duì)于 CentOS 發(fā)行版,可以直接通過(guò)源安裝。

$ sudo yum install -y python-devel libevent-devel python-pip gcc xz-devel
$ sudo python-pip install docker-registry

也可以從 docker-registry 項(xiàng)目下載源碼進(jìn)行安裝。

$ sudo apt-get install build-essential python-dev libevent-dev python-pip libssl-dev liblzma-dev libffi-dev
$ git clone https://github.com/docker/docker-registry.git
$ cd docker-registry
$ sudo python setup.py install

然后修改配置文件,主要修改 dev 模板段的 storage_path 到本地的存儲(chǔ)倉(cāng)庫(kù)的路徑。

$ cp config/config_sample.yml config/config.yml

之后啟動(dòng) Web 服務(wù)。

$ sudo gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application

或者

$ sudo gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application

此時(shí)使用 curl 訪問(wèn)本地的 5000 端口,看到輸出 docker-registry 的版本信息說(shuō)明運(yùn)行成功。

*注:config/config_sample.yml 文件是示例配置文件。

在私有倉(cāng)庫(kù)上傳、下載、搜索鏡像

創(chuàng)建好私有倉(cāng)庫(kù)之后,就可以使用 docker tag 來(lái)標(biāo)記一個(gè)鏡像,然后推送它到倉(cāng)庫(kù),別的機(jī)器上就可以下載下來(lái)了。例如私有倉(cāng)庫(kù)地址為 192.168.7.26:5000。

先在本機(jī)查看已有的鏡像。

$ sudo docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB
ubuntu                            14.04               ba5877dc9bec        6 weeks ago         192.7 MB

使用docker tagba58 這個(gè)鏡像標(biāo)記為 192.168.7.26:5000/test(格式為 docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG])。

$ sudo docker tag ba58 192.168.7.26:5000/test
root ~ # docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                            14.04               ba5877dc9bec        6 weeks ago         192.7 MB
ubuntu                            latest              ba5877dc9bec        6 weeks ago         192.7 MB
192.168.7.26:5000/test            latest              ba5877dc9bec        6 weeks ago         192.7 MB

使用 docker push 上傳標(biāo)記的鏡像。

$ sudo docker push 192.168.7.26:5000/test
The push refers to a repository [192.168.7.26:5000/test] (len: 1)
Sending image list
Pushing repository 192.168.7.26:5000/test (1 tags)
Image 511136ea3c5a already pushed, skipping
Image 9bad880da3d2 already pushed, skipping
Image 25f11f5fb0cb already pushed, skipping
Image ebc34468f71d already pushed, skipping
Image 2318d26665ef already pushed, skipping
Image ba5877dc9bec already pushed, skipping
Pushing tag for rev [ba5877dc9bec] on {http://192.168.7.26:5000/v1/repositories/test/tags/latest}

用 curl 查看倉(cāng)庫(kù)中的鏡像。

$ curl http://192.168.7.26:5000/v1/search
{"num_results": 7, "query": "", "results": [{"description": "", "name": "library/miaxis_j2ee"}, {"description": "", "name": "library/tomcat"}, {"description": "", "name": "library/ubuntu"}, {"description": "", "name": "library/ubuntu_office"}, {"description": "", "name": "library/desktop_ubu"}, {"description": "", "name": "dockerfile/ubuntu"}, {"description": "", "name": "library/test"}]}

這里可以看到 {"description": "", "name": "library/test"},表明鏡像已經(jīng)被成功上傳了。

現(xiàn)在可以到另外一臺(tái)機(jī)器去下載這個(gè)鏡像。

$ sudo docker pull 192.168.7.26:5000/test
Pulling repository 192.168.7.26:5000/test
ba5877dc9bec: Download complete
511136ea3c5a: Download complete
9bad880da3d2: Download complete
25f11f5fb0cb: Download complete
ebc34468f71d: Download complete
2318d26665ef: Download complete
$ sudo docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
192.168.7.26:5000/test             latest              ba5877dc9bec        6 weeks ago         192.7 MB

可以使用 這個(gè)腳本 批量上傳本地的鏡像到注冊(cè)服務(wù)器中,默認(rèn)是本地注冊(cè)服務(wù)器 127.0.0.1:5000。例如:

$ wget https://github.com/yeasy/docker_practice/raw/master/_local/push_images.sh; sudo chmod a+x push_images.sh
$ ./push_images.sh ubuntu:latest centos:centos7
The registry server is 127.0.0.1
Uploading ubuntu:latest...
The push refers to a repository [127.0.0.1:5000/ubuntu] (len: 1)
Sending image list
Pushing repository 127.0.0.1:5000/ubuntu (1 tags)
Image 511136ea3c5a already pushed, skipping
Image bfb8b5a2ad34 already pushed, skipping
Image c1f3bdbd8355 already pushed, skipping
Image 897578f527ae already pushed, skipping
Image 9387bcc9826e already pushed, skipping
Image 809ed259f845 already pushed, skipping
Image 96864a7d2df3 already pushed, skipping
Pushing tag for rev [96864a7d2df3] on {http://127.0.0.1:5000/v1/repositories/ubuntu/tags/latest}
Untagged: 127.0.0.1:5000/ubuntu:latest
Done
Uploading centos:centos7...
The push refers to a repository [127.0.0.1:5000/centos] (len: 1)
Sending image list
Pushing repository 127.0.0.1:5000/centos (1 tags)
Image 511136ea3c5a already pushed, skipping
34e94e67e63a: Image successfully pushed
70214e5d0a90: Image successfully pushed
Pushing tag for rev [70214e5d0a90] on {http://127.0.0.1:5000/v1/repositories/centos/tags/centos7}
Untagged: 127.0.0.1:5000/centos:centos7
Done

Docker 數(shù)據(jù)管理

數(shù)據(jù)卷

數(shù)據(jù)卷是一個(gè)可供一個(gè)或多個(gè)容器使用的特殊目錄,它繞過(guò) UFS,可以提供很多有用的特性:

  • 數(shù)據(jù)卷可以在容器之間共享和重用
  • 對(duì)數(shù)據(jù)卷的修改會(huì)立馬生效
  • 對(duì)數(shù)據(jù)卷的更新,不會(huì)影響鏡像
  • 數(shù)據(jù)卷默認(rèn)會(huì)一直存在,即使容器被刪除

*注意:數(shù)據(jù)卷的使用,類似于 Linux 下對(duì)目錄或文件進(jìn)行 mount,鏡像中的被指定為掛載點(diǎn)的目錄中的文件會(huì)隱藏掉,能顯示看的是掛載的數(shù)據(jù)卷。

創(chuàng)建一個(gè)數(shù)據(jù)卷

在用 docker run 命令的時(shí)候,使用 -v 標(biāo)記來(lái)創(chuàng)建一個(gè)數(shù)據(jù)卷并掛載到容器里。在一次 run 中多次使用可以掛載多個(gè)數(shù)據(jù)卷。

下面創(chuàng)建一個(gè)名為 web 的容器,并加載一個(gè)數(shù)據(jù)卷到容器的 /webapp 目錄。

$ sudo docker run -d -P --name web -v /webapp training/webapp python app.py

*注意:也可以在 Dockerfile 中使用 VOLUME 來(lái)添加一個(gè)或者多個(gè)新的卷到由該鏡像創(chuàng)建的任意容器。

刪除數(shù)據(jù)卷

數(shù)據(jù)卷是被設(shè)計(jì)用來(lái)持久化數(shù)據(jù)的,它的生命周期獨(dú)立于容器,Docker不會(huì)在容器被刪除后自動(dòng)刪除數(shù)據(jù)卷,并且也不存在垃圾回收這樣的機(jī)制來(lái)處理沒(méi)有任何容器引用的數(shù)據(jù)卷。如果需要在刪除容器的同時(shí)移除數(shù)據(jù)卷??梢栽趧h除容器的時(shí)候使用 docker rm -v 這個(gè)命令。無(wú)主的數(shù)據(jù)卷可能會(huì)占據(jù)很多空間,要清理會(huì)很麻煩。Docker官方正在試圖解決這個(gè)問(wèn)題,相關(guān)工作的進(jìn)度可以查看這個(gè)PR。

掛載一個(gè)主機(jī)目錄作為數(shù)據(jù)卷

使用 -v 標(biāo)記也可以指定掛載一個(gè)本地主機(jī)的目錄到容器中去。

$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

上面的命令加載主機(jī)的 /src/webapp 目錄到容器的 /opt/webapp
目錄。這個(gè)功能在進(jìn)行測(cè)試的時(shí)候十分方便,比如用戶可以放置一些程序到本地目錄中,來(lái)查看容器是否正常工作。本地目錄的路徑必須是絕對(duì)路徑,如果目錄不存在 Docker 會(huì)自動(dòng)為你創(chuàng)建它。

*注意:Dockerfile 中不支持這種用法,這是因?yàn)?Dockerfile 是為了移植和分享用的。然而,不同操作系統(tǒng)的路徑格式不一樣,所以目前還不能支持。

Docker 掛載數(shù)據(jù)卷的默認(rèn)權(quán)限是讀寫,用戶也可以通過(guò) :ro 指定為只讀。

$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro
training/webapp python app.py

加了 :ro 之后,就掛載為只讀了。

查看數(shù)據(jù)卷的具體信息

在主機(jī)里使用以下命令可以查看指定容器的信息

$ docker inspect web
...

在輸出的內(nèi)容中找到其中和數(shù)據(jù)卷相關(guān)的部分,可以看到所有的數(shù)據(jù)卷都是創(chuàng)建在主機(jī)的/var/lib/docker/volumes/下面的

"Volumes": {
    "/webapp": "/var/lib/docker/volumes/fac362...80535"
},
"VolumesRW": {
    "/webapp": true
}
...

注:從Docker 1.8.0起,數(shù)據(jù)卷配置在"Mounts"Key下面,可以看到所有的數(shù)據(jù)卷都是創(chuàng)建在主機(jī)的/mnt/sda1/var/lib/docker/volumes/....下面了。

"Mounts": [
            {
                "Name": "b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29",
                "Source": "/mnt/sda1/var/lib/docker/volumes/b53ebd40054dae599faf7c9666acfe205c3e922fc3e8bc3f2fd178ed788f1c29/_data",
                "Destination": "/webapp",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ]
...

掛載一個(gè)本地主機(jī)文件作為數(shù)據(jù)卷

-v 標(biāo)記也可以從主機(jī)掛載單個(gè)文件到容器中

$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

這樣就可以記錄在容器輸入過(guò)的命令了。

*注意:如果直接掛載一個(gè)文件,很多文件編輯工具,包括 vi 或者 sed --in-place,可能會(huì)造成文件 inode 的改變,從 Docker 1.1
.0起,這會(huì)導(dǎo)致報(bào)錯(cuò)誤信息。所以最簡(jiǎn)單的辦法就直接掛載文件的父目錄。

數(shù)據(jù)卷容器

如果你有一些持續(xù)更新的數(shù)據(jù)需要在容器之間共享,最好創(chuàng)建數(shù)據(jù)卷容器。

數(shù)據(jù)卷容器,其實(shí)就是一個(gè)正常的容器,專門用來(lái)提供數(shù)據(jù)卷供其它容器掛載的。

首先,創(chuàng)建一個(gè)名為 dbdata 的數(shù)據(jù)卷容器:

$ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres

然后,在其他容器中使用 --volumes-from 來(lái)掛載 dbdata 容器中的數(shù)據(jù)卷。

$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres
$ sudo docker run -d --volumes-from dbdata --name db2 training/postgres

可以使用超過(guò)一個(gè)的 --volumes-from 參數(shù)來(lái)指定從多個(gè)容器掛載不同的數(shù)據(jù)卷。
也可以從其他已經(jīng)掛載了數(shù)據(jù)卷的容器來(lái)級(jí)聯(lián)掛載數(shù)據(jù)卷。

$ sudo docker run -d --name db3 --volumes-from db1 training/postgres

*注意:使用 --volumes-from 參數(shù)所掛載數(shù)據(jù)卷的容器自己并不需要保持在運(yùn)行狀態(tài)。

如果刪除了掛載的容器(包括 dbdata、db1 和 db2),數(shù)據(jù)卷并不會(huì)被自動(dòng)刪除。如果要?jiǎng)h除一個(gè)數(shù)據(jù)卷,必須在刪除最后一個(gè)還掛載著它的容器時(shí)使用 docker rm -v 命令來(lái)指定同時(shí)刪除關(guān)聯(lián)的容器。
這可以讓用戶在容器之間升級(jí)和移動(dòng)數(shù)據(jù)卷。

利用數(shù)據(jù)卷容器來(lái)備份、恢復(fù)、遷移數(shù)據(jù)卷

可以利用數(shù)據(jù)卷對(duì)其中的數(shù)據(jù)進(jìn)行進(jìn)行備份、恢復(fù)和遷移。

備份

首先使用 --volumes-from 標(biāo)記來(lái)創(chuàng)建一個(gè)加載 dbdata 容器卷的容器,并從主機(jī)掛載當(dāng)前目錄到容器的 /backup 目錄。命令如下:

$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

容器啟動(dòng)后,使用了 tar 命令來(lái)將 dbdata 卷備份為容器中 /backup/backup.tar 文件,也就是主機(jī)當(dāng)前目錄下的名為 backup.tar 的文件。

恢復(fù)

如果要恢復(fù)數(shù)據(jù)到一個(gè)容器,首先創(chuàng)建一個(gè)帶有空數(shù)據(jù)卷的容器 dbdata2。

$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后創(chuàng)建另一個(gè)容器,掛載 dbdata2 容器卷中的數(shù)據(jù)卷,并使用 untar 解壓備份文件到掛載的容器卷中。

$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf
/backup/backup.tar

為了查看/驗(yàn)證恢復(fù)的數(shù)據(jù),可以再啟動(dòng)一個(gè)容器掛載同樣的容器卷來(lái)查看

$ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata

 Docker 中的網(wǎng)絡(luò)功能介紹

Docker 允許通過(guò)外部訪問(wèn)容器或容器互聯(lián)的方式來(lái)提供網(wǎng)絡(luò)服務(wù)。

外部訪問(wèn)容器

容器中可以運(yùn)行一些網(wǎng)絡(luò)應(yīng)用,要讓外部也可以訪問(wèn)這些應(yīng)用,可以通過(guò) -P-p 參數(shù)來(lái)指定端口映射。

當(dāng)使用 -P 標(biāo)記時(shí),Docker 會(huì)隨機(jī)映射一個(gè) 49000~49900 的端口到內(nèi)部容器開(kāi)放的網(wǎng)絡(luò)端口。

使用 docker ps 可以看到,本地主機(jī)的 49155 被映射到了容器的 5000 端口。此時(shí)訪問(wèn)本機(jī)的 49155 端口即可訪問(wèn)容器內(nèi) web 應(yīng)用提供的界面。

$ sudo docker run -d -P training/webapp python app.py
$ sudo docker ps -l
CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155->5000/tcp  nostalgic_morse

同樣的,可以通過(guò) docker logs 命令來(lái)查看應(yīng)用的信息。

$ sudo docker logs -f nostalgic_morse
* Running on http://0.0.0.0:5000/
10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 -

-p(小寫的)則可以指定要映射的端口,并且,在一個(gè)指定端口上只可以綁定一個(gè)容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。

映射所有接口地址

使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以執(zhí)行

$ sudo docker run -d -p 5000:5000 training/webapp python app.py

此時(shí)默認(rèn)會(huì)綁定本地所有接口上的所有地址。

映射到指定地址的指定端口

可以使用 ip:hostPort:containerPort 格式指定映射使用一個(gè)特定地址,比如 localhost 地址 127.0.0.1

$ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py

映射到指定地址的任意端口

使用 ip::containerPort 綁定 localhost 的任意端口到容器的 5000 端口,本地主機(jī)會(huì)自動(dòng)分配一個(gè)端口。

$ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py

還可以使用 udp 標(biāo)記來(lái)指定 udp 端口

$ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py

查看映射端口配置

使用 docker port 來(lái)查看當(dāng)前映射的端口配置,也可以查看到綁定的地址

$ docker port nostalgic_morse 5000
127.0.0.1:49155.

注意:

  • 容器有自己的內(nèi)部網(wǎng)絡(luò)和 ip 地址(使用 docker inspect 可以獲取所有的變量,Docker 還可以有一個(gè)可變的網(wǎng)絡(luò)配置。)
  • -p 標(biāo)記可以多次使用來(lái)綁定多個(gè)端口

例如

$ sudo docker run -d -p 5000:5000  -p 3000:80 training/webapp python app.py

容器互聯(lián)

容器的連接(linking)系統(tǒng)是除了端口映射外,另一種跟容器中應(yīng)用交互的方式。

該系統(tǒng)會(huì)在源和接收容器之間創(chuàng)建一個(gè)隧道,接收容器可以看到源容器指定的信息。

自定義容器命名

連接系統(tǒng)依據(jù)容器的名稱來(lái)執(zhí)行。因此,首先需要自定義一個(gè)好記的容器命名。

雖然當(dāng)創(chuàng)建容器的時(shí)候,系統(tǒng)默認(rèn)會(huì)分配一個(gè)名字。自定義命名容器有2個(gè)好處:

  • 自定義的命名,比較好記,比如一個(gè)web應(yīng)用容器我們可以給它起名叫web
  • 當(dāng)要連接其他容器時(shí)候,可以作為一個(gè)有用的參考點(diǎn),比如連接web容器到db容器

使用 --name 標(biāo)記可以為容器自定義命名。

$ sudo docker run -d -P --name web training/webapp python app.py

使用 docker ps 來(lái)驗(yàn)證設(shè)定的命名。

$ sudo docker ps -l
CONTAINER ID  IMAGE                  COMMAND        CREATED       STATUS       PORTS                    NAMES
aed84ee21bde  training/webapp:latest python app.py  12 hours ago  Up 2 seconds 0.0.0.0:49154->5000/tcp  web

也可以使用 docker inspect 來(lái)查看容器的名字

$ sudo docker inspect -f "{{ .Name }}" aed84ee21bde
/web

注意:容器的名稱是唯一的。如果已經(jīng)命名了一個(gè)叫 web 的容器,當(dāng)你要再次使用 web 這個(gè)名稱的時(shí)候,需要先用docker rm 來(lái)刪除之前創(chuàng)建的同名容器。

在執(zhí)行 docker run 的時(shí)候如果添加 --rm 標(biāo)記,則容器在終止后會(huì)立刻刪除。注意,--rm-d 參數(shù)不能同時(shí)使用。

容器互聯(lián)

使用 --link 參數(shù)可以讓容器之間安全的進(jìn)行交互。

下面先創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)容器。

$ sudo docker run -d --name db training/postgres

刪除之前創(chuàng)建的 web 容器

$ docker rm -f web

然后創(chuàng)建一個(gè)新的 web 容器,并將它連接到 db 容器

$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

此時(shí),db 容器和 web 容器建立互聯(lián)關(guān)系。

--link 參數(shù)的格式為 --link name:alias,其中 name 是要鏈接的容器的名稱,alias 是這個(gè)連接的別名。

使用 docker ps 來(lái)查看容器的連接

$ docker ps
CONTAINER ID  IMAGE                     COMMAND               CREATED             STATUS             PORTS                    NAMES
349169744e49  training/postgres:latest  su postgres -c '/usr  About a minute ago  Up About a minute  5432/tcp                 db, web/db
aed84ee21bde  training/webapp:latest    python app.py         16 hours ago        Up 2 minutes       0.0.0.0:49154->5000/tcp  web

可以看到自定義命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。這表示 web 容器鏈接到 db 容器,web 容器將被允許訪問(wèn) db 容器的信息。

Docker 在兩個(gè)互聯(lián)的容器之間創(chuàng)建了一個(gè)安全隧道,而且不用映射它們的端口到宿主主機(jī)上。在啟動(dòng) db 容器的時(shí)候并沒(méi)有使用 -p-P 標(biāo)記,從而避免了暴露數(shù)據(jù)庫(kù)端口到外部網(wǎng)絡(luò)上。

Docker 通過(guò) 2 種方式為容器公開(kāi)連接信息:

  • 環(huán)境變量
  • 更新 /etc/hosts 文件

使用 env 命令來(lái)查看 web 容器的環(huán)境變量

$ 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_5000_TCP=tcp://172.17.0.5:5432
DB_PORT_5000_TCP_PROTO=tcp
DB_PORT_5000_TCP_PORT=5432
DB_PORT_5000_TCP_ADDR=172.17.0.5
. . .

其中 DB_ 開(kāi)頭的環(huán)境變量是供 web 容器連接 db 容器使用,前綴采用大寫的連接別名。

除了環(huán)境變量,Docker 還添加 host 信息到父容器的 /etc/hosts 的文件。下面是父容器 web 的 hosts 文件

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

這里有 2 個(gè) hosts,第一個(gè)是 web 容器,web 容器用 id 作為他的主機(jī)名,第二個(gè)是 db 容器的 ip 和主機(jī)名。
可以在 web 容器中安裝 ping 命令來(lái)測(cè)試跟db容器的連通。

root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping
root@aed84ee21bde:/opt/webapp# ping db
PING db (172.17.0.5): 48 data bytes
56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms
56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms
56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms

用 ping 來(lái)測(cè)試db容器,它會(huì)解析成 172.17.0.5。
*注意:官方的 ubuntu 鏡像默認(rèn)沒(méi)有安裝 ping,需要自行安裝。

用戶可以鏈接多個(gè)父容器到子容器,比如可以鏈接多個(gè) web 到 db 容器上。

資源鏈接

官方網(wǎng)站

實(shí)踐參考

技術(shù)交流

其它

常見(jiàn)問(wèn)題總結(jié)

這篇文章是我學(xué)習(xí) Docker 的記錄,大部分內(nèi)容摘抄自 <<Docker — 從入門到實(shí)踐>> 一書,并非本人原創(chuàng).
學(xué)習(xí)過(guò)程中整理成適合我自己的筆記,其中也包含了我自己的實(shí)踐記錄.

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多