Docker 學習新手筆記:從入門到放棄
全文較長,大致需要半小時左右時間閱讀,若你已經有所基礎,可以根據自己的了解程度選擇從何處看起。
本文記錄的是作為一個新手,從了解 Docker 是什麼、Docker 技術包含哪些概念到上手使用、安裝以及發布 Docker 鏡像的整個過程。作者在學習過程中參閱了諸多文檔和教程,在此一併感謝,與此同時本文結尾也列出了參考文獻的鏈接,供讀者進一步參考。遵循簡介、入門、上手到深入的順序,本文根據個人學習實踐過程進行書寫,結構如下:
- 一、Docker 簡介
- 1.1 Docker 概念掃盲:什麼是 Docker?
- 1.2 Docker 和虛擬機的區別與特點
- 二、Docker 基本概念
- 2.1 核心概念:鏡像、容器與倉庫
- 2.2 Docker 三劍客
- 三、Docker 安裝與使用
- 3.1 Docker 安裝、運行與加速
- 3.2 Hello World
- 四、鏡像使用與發布
- 4.1 鏡像的獲取與使用
- 4.2 通過 commit 命令理解鏡像構成
- 4.3 利用 Dockerfile 定製鏡像
- 五、總結
- 六、參考
其中第一章與第二章非常詳細地介紹了 Docker 的相關概念與基本組成,主要是概論介紹等文字描述,第三章與第四章偏重於上手實踐,從 Docker 環境安裝、運行加速、鏡像使用、鏡像構成到鏡像定製與發布,分解了各步驟的流程,通過教程加註解的形式加深讀者印象。其中建立鏡像的示例代碼可以從 GitHub 打包下載。
由於軟硬體環境變化日新月異,除概念介紹與解讀部分,在運行本文代碼之前請確保運行環境以及 Docker 自身版本。本文代碼均運行於 macOS 環境(macOS Sierra 10.12.5),所使用 Docker 版本為 18.03.0-ce。
如果文中有描述不清楚的地方,請評論告知;文中難免存在錯誤與疏漏的地方,也請多多指教。以下開始正文。
一、Docker 簡介
談到 Docker,不論我們是否實踐過,都應該對它或多或少有一個印象,即「環境一次創建,多端一致性運行」,因為它正解決了曾經困擾我們已久「這段代碼在我電腦上運行沒問題啊」的煩惱。首先,簡單介紹一下 Docker 技術是什麼。
1.1 Docker 概念掃盲:什麼是 Docker?
Docker 是一個開放源代碼軟體項目,項目主要代碼在2013年開源於 GitHub。它是雲服務技術上的一次創新,讓應用程序布署在軟體容器下的工作可以自動化進行,藉此在 Linux 操作系統上,提供一個額外的軟體抽象層,以及操作系統層虛擬化的自動管理機制。
Docker 利用 Linux 核心中的資源分離線制,例如 cgroups,以及 Linux 核心名字空間(name space),來創建獨立的軟體容器(containers),屬於操作系統層面的虛擬化技術。由於隔離的進程獨立於宿主和其它的隔離的進程,因此也稱其為容器。Docker 在容器的基礎上進行了進一步的封裝,從文件系統、網路互聯到進程隔離等等,極大的簡化了容器的創建和維護,使得其比虛擬機技術更為輕便、快捷。Docker 可以在單一 Linux 實體下運作,避免因為創建一個虛擬機而造成的額外負擔。
1.2 Docker 和虛擬機的區別與特點
對於新手來說,第一個覺得困惑的地方可能就是不清楚 Docker 和虛擬機之間到底是什麼關係。以下兩張圖分別介紹了虛擬機與 Docker 容器的結構。
對於虛擬機技術來說,傳統的虛擬機需要模擬整台機器包括硬體,每台虛擬機都需要有自己的操作系統,虛擬機一旦被開啟,預分配給他的資源將全部被佔用。每一個虛擬機包括應用,必要的二進位和庫,以及一個完整的用戶操作系統。
容器技術和我們的宿主機共享硬體資源及操作系統,可以實現資源的動態分配。容器包含應用和其所有的依賴包,但是與其他容器共享內核。容器在宿主機操作系統中,在用戶空間以分離的進程運行。容器內沒有自己的內核,也沒有進行硬體虛擬。
具體來說與虛擬機技術對比,Docker 容器存在以下幾個特點:
- 更快的啟動速度:因為 Docker 直接運行於宿主內核,無需啟動完整的操作系統,因此啟動速度屬於秒級別,而虛擬機通常需要幾分鐘去啟動。
- 更高效的資源利用率:由於容器不需要進行硬體虛擬以及運行完整操作系統等額外開銷,Docker 對系統資源的利用率更高。
- 更高的系統支持量:Docker 的架構可以共用一個內核與共享應用程序庫,所佔內存極小。同樣的硬體環境,Docker 運行的鏡像數遠多於虛擬機數量,對系統的利用率非常高。
- 持續交付與部署:對開發和運維人員來說,最希望的就是一次創建或配置,可以在任意地方正常運行。使用 Docker 可以通過定製應用鏡像來實現持續集成、持續交付、部署。開發人員可以通過 Dockerfile 來進行鏡像構建,並進行集成測試,而運維人員則可以直接在生產環境中快速部署該鏡像,甚至進行自動部署。
- 更輕鬆的遷移:由於 Docker 確保了執行環境的一致性,使得應用的遷移更加容易。Docker 可以在很多平台上運行,無論是物理機、虛擬機、公有雲、私有雲,甚至是筆記本,其運行結果是一致的。因此用戶可以很輕易的將在一個平台上運行的應用,遷移到另一個平台上,而不用擔心運行環境的變化導致應用無法正常運行的情況。
- 更輕鬆的維護與擴展:Docker 使用的分層存儲以及鏡像的技術,使得應用重複部分的復用更為容易,也使得應用的維護更新更加簡單,基於基礎鏡像進一步擴展鏡像也變得非常簡單。此外,Docker 團隊同各個開源項目團隊一起維護了一大批高質量的 官方鏡像,既可以直接在生產環境使用,又可以作為基礎進一步定製,大大的降低了應用服務的鏡像製作成本。
- 更弱的隔離性:Docker 屬於進程之間的隔離,虛擬機可實現系統級別隔離。
- 更弱的安全性:Docker 的租戶 root 和宿主機 root 等同,一旦容器內的用戶從普通用戶許可權提升為 root 許可權,它就直接具備了宿主機的 root 許可權,進而可進行無限制的操作。虛擬機租戶 root 許可權和宿主機的 root 虛擬機許可權是分離的,並且利用硬體隔離技術可以防止虛擬機突破和彼此交互,而容器至今還沒有任何形式的硬體隔離,這使得容器容易受到攻擊。
二、Docker 基本概念
2.1 核心概念:鏡像、容器與倉庫
Docker 主要包含三個基本概念,分別是鏡像、容器和倉庫,理解了這三個概念,就理解了 Docker 的整個生命周期。以下簡要總結一下這三點,詳細介紹可以移步Docker 從入門到實踐對應章節。
- 鏡像:Docker 鏡像是一個特殊的文件系統,除了提供容器運行時所需的程序、庫、資源、配置等文件外,還包含了一些為運行時準備的一些配置參數(如匿名卷、環境變數、用戶等)。鏡像不包含任何動態數據,其內容在構建之後也不會被改變。
- 容器:容器的實質是進程,但與直接在宿主執行的進程不同,容器進程運行於屬於自己的獨立的命名空間容器可以被。創建、啟動、停止、刪除和暫停等等,說到鏡像與容器之間的關係,可以類比面向對象程序設計中的類和實例。
- 倉庫:鏡像構建完成後,可以很容易的在當前宿主機上運行,但是,如果需要在其它伺服器上使用這個鏡像,我們就需要一個集中的存儲、分發鏡像的服務,Docker Registry 就是這樣的服務。一個 Docker Registry 中可以包含多個倉庫;每個倉庫可以包含多個標籤;每個標籤對應一個鏡像,其中標籤可以理解為鏡像的版本號。
2.2 Docker 三劍客
docker-compose:Docker 鏡像在創建之後,往往需要自己手動 pull 來獲取鏡像,然後執行 run 命令來運行。當服務需要用到多種容器,容器之間又產生了各種依賴和連接的時候,部署一個服務的手動操作是令人感到十分厭煩的。
dcoker-compose 技術,就是通過一個 .yml
配置文件,將所有的容器的部署方法、文件映射、容器連接等等一系列的配置寫在一個配置文件里,最後只需要執行 docker-compose up
命令就會像執行腳本一樣的去一個個安裝容器並自動部署他們,極大的便利了複雜服務的部署。
docker-machine:Docker 技術是基於 Linux 內核的 cgroup 技術實現的,那麼問題來了,在非 Linux 平台上是否就不能使用 docker 技術了呢?答案是可以的,不過顯然需要藉助虛擬機去模擬出 Linux 環境來。
docker-machine 就是 docker 公司官方提出的,用於在各種平台上快速創建具有 docker 服務的虛擬機的技術,甚至可以通過指定 driver 來定製虛擬機的實現原理(一般是 virtualbox)。
docker-swarm:swarm 是基於 docker 平台實現的集群技術,他可以通過幾條簡單的指令快速的創建一個 docker 集群,接著在集群的共享網路上部署應用,最終實現分散式的服務。
三、Docker 安裝與使用
Docker 按版本劃分,可以區分為 CE 和 EE。CE 即社區版(免費,支持周期三個月),EE 即企業版,強調安全,付費使用。
3.1 Docker 安裝、運行與加速
本部分只介紹 macOS 上的安裝,其餘操作系統上的使用方法可以參見官網。
Docker for Mac 要求系統最低為 macOS 10.10.3 Yosemite。如果系統不滿足需求,可以安裝 Docker Toolbox。
安裝方法有兩種:homebrew 和手動下載安裝。Homebrew-Cask 已經支持 Docker for Mac,如果使用該方式,你只需要運行如下命令:
brew cask install docker
若是手動下載,也很容易。從這裡選擇合適你的版本下載 dmg 文件,然後拖進應用中即可。
啟動應用之後,你可以在命令行查看 Docker 版本(Docker 不同技術對應的版本用不同命令進行查看):
docker --version> Docker version 18.03.0-ce, build 0520e24docker-compose --version> docker-compose version 1.20.1, build 5d8c71bdocker-machine --version> docker-machine version 0.14.0, build 89b8332
當然了,由於某些原因,國內從 Docker Hub 上拉取內容會非常緩慢,這個時候就可以配置一個鏡像加速器環境。詳情說明可以移步 Docker 中國官方鏡像加速,對於 macOS 用戶,在任務欄點擊應用圖標 -> Perferences... -> Daemon -> Registry mirrors,在列表中填寫加速器地址 https://registry.docker-cn.com
即可。修改完成之後,點擊 Apply & Restart 即可。
為了檢測加速器是否生效,可以在命令行輸入以下命令:
docker info# 如果查看到如下 registry mirrors 信息則表示修改生效Registry Mirrors: https://registry.docker-cn.com/
3.2 Hello World
大多數編程語言以及一些軟體的第一個示例都是 Hello Wolrd,Docker 也不例外,運行 docker run hello-world
驗證一下吧(該命令下 Docker 會在本地查找鏡像是否存在,但因為環境剛裝上所以肯定不存在,之後 Docker 會去遠程 Docker registry server 下載這個鏡像),以下為運行信息。
Unable to find image hello-world:latest locallylatest: Pulling from library/hello-world9bb5a5d4561a: Pull completeDigest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77Status: Downloaded newer image for hello-world:latestHello 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. (amd64) 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 bashShare images, automate workflows, and more with a free Docker ID: https://hub.docker.com/For more examples and ideas, visit: https://docs.docker.com/engine/userguide/
網上有不少文章說到 macOS 的特殊配製,即由於 Docker 引擎使用了特定於 Linux 內核的特性,需要安裝一個輕量級的虛擬機(如 VirtualBox)來保證在 macOS 上正常運行 Docker。於是建議下載官方提供的 Boot2Docker 來運行 Docker 守護進程。但是查看了項目動態後發現,由於 Docker for Mac 和 Docker for Windows 的發布,這一操作已不被推薦,即兩個客戶端環境中已經繼承了該功能。
Boot2Docker is officialy in maintenance mode -- it is recommended that users transition from Boot2Docker over to Docker for Mac or Docker for Windows instead. From boot2docker/boot2docker
所以在看到任何教程準備上手之前,最好看看教程本身依賴的環境版本以及軟體最新的發布動態,這個習慣對於本文的作者來說也不例外。
四、鏡像使用與發布
4.1 鏡像的獲取與使用
之前提到 Docker Hub 上有大量優秀的鏡像,下面先來說說如何獲取鏡像。和 git 類似,docker 也使用 pull 命令,其格式如下:
docker pull [選項] [Docker Registry 地址[:埠號]/]倉庫名[:標籤]
其中 Docker 鏡像倉庫地址若不寫則默認為 Docker Hub,而倉庫名是兩段式名稱,即 <用戶名>/<軟體名>
。對於 Docker Hub,如果不給出用戶名,則默認為 library,也就是官方鏡像。我們拉取一個 ubuntu 16.04 鏡像試試:
docker pull ubuntu:16.04# 輸出信息16.04: Pulling from library/ubuntud3938036b19c: Pull completea9b30c108bda: Pull complete67de21feec18: Pull complete817da545be2b: Pull completed967c497ce23: Pull completeDigest: sha256:9ee3b83bcaa383e5e3b657f042f4034c92cdd50c03f73166c145c9ceaea9ba7cStatus: Downloaded newer image for ubuntu:16.04
上面的命令中沒有給出 Docker 鏡像倉庫地址,因此將會從 Docker Hub 獲取鏡像。而鏡像名稱是 ubuntu:16.04
,因此將會獲取官方鏡像 library/ubuntu
倉庫中標籤為 16.04
的鏡像。這裡需要說明的是,由於 Docker for Mac 的支持即前文所述的原因,在 Docker 容器中使用的操作系統不必與主機操作系統相匹配。比如,你可以在 CentOS 主機中運行 Ubuntu,反之亦然。好了,下載完畢我們通過如下命令運行一個容器:
docker run -it --rm ubuntu:16.04 bash
運行完成後你便可以使用諸多命令來測試環境了,比如再來一遍 Hello Wolrd:
echo "Hello World"
或者查看系統信息:
cat /etc/os-release# 輸出信息AME="Ubuntu"VERSION="16.04.4 LTS (Xenial Xerus)"ID=ubuntuID_LIKE=debianPRETTY_NAME="Ubuntu 16.04.4 LTS"VERSION_ID="16.04"HOME_URL="http://www.ubuntu.com/"SUPPORT_URL="http://help.ubuntu.com/"BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"VERSION_CODENAME=xenialUBUNTU_CODENAME=xenial
docker run
就是運行容器的命令,這裡簡要的說明一下上面用到的參數,詳細用法可以查看 help。
-it
:這是兩個參數,一個是-i
表示互動式操作,一個是-t
為終端。我們這裡打算進入 bash 執行一些命令並查看返回結果,因此我們需要互動式終端。--rm
:這個參數是說容器退出後隨之將其刪除。默認情況下,為了排障需求,退出的容器並不會立即刪除,除非手動docker rm
。ubuntu:16.04
:這是指用ubuntu:16.04
鏡像為基礎來啟動容器。bash
:放在鏡像名後的是命令,這裡我們希望有個互動式 Shell,因此用 bash。
Docker 鏡像還有一些常用操作,比如:
docker image ls
- 列出本地已下載鏡像docker image rm [選項] <鏡像1> [<鏡像2> ...]
- 刪除鏡像docker logs <id/container_name>
- 查看容器日誌docker ps
- 列出當前所有正在運行的容器docker ps -l
- 列出最近一次啟動的容器docker search image_name
- 從 Docker Hub 檢索鏡像docker history image_name
- 顯示鏡像歷史docker push new_image_name
- 發布鏡像
4.2 通過 commit 命令理解鏡像構成
docker commit
命令除了學習之外,還有一些特殊的應用場合,比如被入侵後保存現場等。但是,不要使用docker commit
定製鏡像,定製鏡像應該使用 Dockerfile 來完成。
以下通過定製化一個 web 伺服器,來說明鏡像的構成。
docker run --name webserver -d -p 4000:80 nginx
首先,以上命令會用 nginx
鏡像啟動一個容器,命名為 webserver,並映射在 4000 埠,接下來我們用瀏覽器去訪問它,效果如下所示。
註:如果使用的是 Docker Toolbox,或者是在虛擬機、雲伺服器上安裝的 Docker,則需要將 localhost 換為虛擬機地址或者實際雲伺服器地址。
現在,假設我們非常不喜歡這個歡迎頁面,我們希望改成歡迎 Docker 的文字,我們可以使用 docker exec
命令進入容器,修改其內容。
docker exec -it webserver bashroot@7603bd94b5e:/# echo <h1>Hello, Docker!</h1> > /usr/share/nginx/html/index.htmlroot@27603bd94b5e:/# exitexit
這時刷新瀏覽器,我們發現效果變了。
我們修改了容器的文件,也就是改動了容器的存儲層。我們可以通過 docker diff
命令看到具體的改動。
docker diff webserver# 輸出C /rootA /root/.bash_historyC /runA /run/nginx.pidC /usr/share/nginx/html/index.htmlC /var/cache/nginxA /var/cache/nginx/client_tempA /var/cache/nginx/fastcgi_tempA /var/cache/nginx/proxy_tempA /var/cache/nginx/scgi_tempA /var/cache/nginx/uwsgi_temp
當我們運行一個容器的時候(如果不使用卷的話),我們做的任何文件修改都會被記錄於容器存儲層里。而 Docker 提供了一個 docker commit
命令,可以將容器的存儲層保存下來成為鏡像。換句話說,就是在原有鏡像的基礎上,再疊加上容器的存儲層,並構成新的鏡像。以後我們運行這個新鏡像的時候,就會擁有原有容器最後的文件變化。
假設以上操作已經符合我們的定製了,接下來就將它保存下來。commit 語法格式如下:
docker commit [選項] <容器ID或容器名> [<倉庫名>[:<標籤>]]
我們在命令行輸入(其中 author、message 等信息可以省略):
docker commit --author "Joe Jiang <hijiangtao@gmail.com>" --message "modify: Nginx default page to Hello Docker" webserver nginx:v2# 輸出結果sha256:48bcb7b903a401085b33421d7805d4553408d86b45befa4a8d3f97319fb6306b
接下來我們可以在本地鏡像列表中看到這個新鏡像:
docker image ls# 輸出REPOSITORY TAG IMAGE ID CREATED SIZEnginx v2 48bcb7b903a4 About a minute ago 109MBubuntu 16.04 c9d990395902 4 days ago 113MBhello-world latest e38bc07ac18e 5 days ago 1.85kBnginx latest b175e7467d66 6 days ago 109MB
也可以查看這個鏡像最新版的歷史記錄:
docker history nginx:v2# 輸出IMAGE CREATED CREATED BY SIZE COMMENT48bcb7b903a4 3 minutes ago nginx -g daemon off; 135B modify: Nginx default page to Hello Dockerb175e7467d66 6 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B<missing> 6 days ago /bin/sh -c #(nop) STOPSIGNAL [SIGTERM] 0B...
當然也可以查看當前主機的所有容器,並終止我們啟動的 webserver:
# 查看所有容器$ docker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES27603bd94b5e nginx "nginx -g daemon of…" 18 minutes ago Up 18 minutes 0.0.0.0:4000->80/tcp webserverfdb4b0c9a5e1 hello-world "/hello" 2 hours ago Exited (0) 2 hours ago angry_bhabha# 終止 webserver$ docker container stop webserverwebserver# 再次查看所有容器,確保 webserver 終止$ docker container ls -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES27603bd94b5e nginx "nginx -g daemon of…" 21 minutes ago Exited (0) 27 seconds ago webserverfdb4b0c9a5e1 hello-world "/hello" 2 hours ago Exited (0) 2 hours ago angry_bhabha
接下來我們運行這個新鏡像,記得瀏覽器訪問應該打開 http://localhost:4001:
docker run --name web2 -d -p 4001:80 nginx:v2# 輸出190e998ff582aac8a3dd4188040c499270e2891ae05b74f54c456d450fc06950
但是實際環境中並不推薦如上所示的 commit 用法,具體可以查看《Docker 從入門到實踐》中的描述,總結下來為三點:
- 由於 commit 命令的執行,有很多文件被改動或添加了。這還僅僅是最簡單的操作,如果是安裝軟體包、編譯構建,那會有大量的無關內容被添加進來,如果不小心清理,將會導致鏡像極為臃腫。
- 使用 commit 生成的鏡像也被稱為黑箱鏡像,換句話說,就是除了製作鏡像的人知道執行過什麼命令、怎麼生成的鏡像,別人根本無從得知。
- 不符合分層存儲的概念,即除當前層外,之前的每一層都是不會發生改變的。
本節最後再說一點,細心的讀者肯定觀察到我們啟動 webserver 的時候 docker run
命令用到了 -d
參數,那麼不用這個命令會有什麼效果呢?我們來試一下:
docker run ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"# 輸出hello worldhello worldhello worldhello world
容器會把輸出的結果 (STDOUT) 列印到宿主機上面。是的,這個參數的作用就是讓 Docker 容器在後台以守護態(Daemonized)形式運行,即後台運行。
-d Run container in background and print container ID
如果我們在如上命令中加入 -d
命令,那麼這些 hello world 信息就需要通過如下命令來查看了,其中 ID 是容器的名稱:
docker logs ID
4.3 利用 Dockerfile 定製鏡像
利用 Dockerfile,之前提及的無法重複的問題、鏡像構建透明性的問題、體積的問題就都會解決。Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。
在本機上登陸你的 Docker 賬戶,以便之後發布使用,如果沒有請到 cloud.docker.com 註冊一個。本機登陸命令:
docker login
所謂定製鏡像,那一定是以一個鏡像為基礎,在其上進行定製。本部分以一個簡單的 web 伺服器為例,記錄定製鏡像的操作步驟。首先,新建一個文件夾 newdocker 並進入:
mkdir newdocker && cd newdocker
然後在當前目錄新建一個 Dockerfile 文件,內容如下所示:
# Dockerfile# 使用 Python 運行時作為基礎鏡像FROM python:2.7-slim# 設置 /app 為工作路徑WORKDIR /app# 將當前目錄所有內容複製到容器的 /app 目錄下ADD . /app# 安裝 requirements.txt 中指定的包RUN pip install --trusted-host pypi.python.org -r requirements.txt# 對容器外開放80埠EXPOSE 80# 定義環境變數ENV NAME World# 當容器啟動時運行 app.py CMD ["python", "app.py"]
以上內容中,我們用 FROM
指定基礎鏡像,在 Docker Store 上有非常多的高質量的官方鏡像,服務類鏡像如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;各種語言應用鏡像如 node、openjdk、python、ruby、golang 等;操作系統鏡像如 ubuntu、debian、centos、fedora、alpine。除了選擇現有鏡像為基礎鏡像外,Docker 還存在一個特殊的鏡像,名為 scratch。直接 FROM scratch
會讓鏡像體積更加小巧。
RUN
指令是用來執行命令行命令的。由於我們的例子中只有一行 RUN 代碼,我們來看看多行 RUN 的情況,如下代碼所示:
RUN apt-get updateRUN apt-get install -y gcc libc6-dev makeRUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"RUN mkdir -p /usr/src/redisRUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1RUN make -C /usr/src/redisRUN make -C /usr/src/redis install
Dockerfile 中每一個指令都會建立一層,RUN 也不例外。每一個 RUN 的行為,就和剛才我們手工建立鏡像的過程一樣:新建立一層,在其上執行這些命令,執行結束後,commit 這一層的修改,構成新的鏡像。
而上面的這種寫法,創建了 7 層鏡像。這是完全沒有意義的,而且很多運行時不需要的東西,都被裝進了鏡像里,比如編譯環境、更新的軟體包等等。結果就是產生非常臃腫、非常多層的鏡像,不僅僅增加了構建部署的時間,也很容易出錯。 這是很多初學 Docker 的人常犯的一個錯誤。
正確的寫法應該這樣:
RUN buildDeps=gcc libc6-dev make && apt-get update && apt-get install -y $buildDeps && wget -O redis.tar.gz "http://download.redis.io/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
值得注意的是最後添加了清理工作的命令,刪除了為了編譯構建所需要的軟體,清理了所有下載、展開的文件,並且還清理了 apt 緩存文件。
好了,以上內容走的有些遠,我們回到 Dockerfile。由於以上代碼用到了 app.py 和 requirements.txt 兩個文件,接下來我們來進一步完善,requirements.txt 內容如下:
FlaskRedis
app.py 內容如下:
from flask import Flaskfrom redis import Redis, RedisErrorimport osimport socket# Connect to Redisredis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)app = Flask(__name__)@app.route("/")def hello(): try: visits = redis.incr("counter") except RedisError: visits = "<i>cannot connect to Redis, counter disabled</i>" html = "<h3>Hello {name}!</h3>" "<b>Hostname:</b> {hostname}<br/>" "<b>Visits:</b> {visits}" return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)if __name__ == "__main__": app.run(host=0.0.0.0, port=80)
我們不探討以上代碼具體的實現,其實簡單來看我們可以知道這是用 python 寫的一個簡單的 web 伺服器。其中需要注意的地方是由於沒有運行 Redis (我們只安裝了 Python 庫,而不是 Redis 本身),所以在運行鏡像時應該期望在這部分產生錯誤消息(見下文圖)。到這裡,當前目錄應該是這樣的:
ls# 輸出Dockerfile app.py requirements.txt
接著,我們在當前目錄開始構建這個鏡像吧。
# -t 可以給這個鏡像取一個名字(tag)docker build -t friendlyhello .
以上代碼用到了 docker build
命令,其格式如下:
docker build [選項] <上下文路徑/URL/->
值得注意的是上下文路徑這個概念。在本例中我們用 .
定義,那麼什麼是上下文環境呢?這裡我們需要了解整個 build 的工作原理。
Docker 在運行時分為 Docker 引擎(也就是服務端守護進程)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱為 Docker Remote API,而如 docker 命令這樣的客戶端工具,則是通過這組 API 與 Docker 引擎交互,從而完成各種功能。因此,雖然表面上我們好像是在本機執行各種 docker 功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker 引擎)完成。
docker build
得知這個路徑後,會將路徑下的所有內容打包,然後上傳給 Docker 引擎。當我們代碼中使用到了諸如 COPY
等指令時,服務端會根據該指令複製調用相應的文件。但由於整個文件中
構建完畢,新鏡像放在哪裡呢?
docker image ls# 輸出REPOSITORY TAG IMAGE ID CREATED SIZEfriendlyhello latest 73ff8ff81235 5 minutes ago 151MBnginx v2 48bcb7b903a4 About an hour ago 109MBubuntu 16.04 c9d990395902 4 days ago 113MBhello-world latest e38bc07ac18e 5 days ago 1.85kBnginx latest b175e7467d66 6 days ago 109MBpython 2.7-slim b16fde09c92c 3 weeks ago 139MB
接下來,我們運行它(若是要後台運行則記得加上 -d
參數):
docker run -p 4000:80 friendlyhello
你可以通過瀏覽器查看效果,也可以通過 curl
命令獲得相同的內容:
curl http://localhost:4000# 輸出結果<h3>Hello World!</h3><b>Hostname:</b> 98750b60e766<br/><b>Visits:</b> <i>cannot connect to Redis, counter disabled</i>
接下來我們發布這個鏡像到 Docker Hub。首先,你可以給這個鏡像打上一個 tag(可選項):
# tag 格式docker tag image username/repository:tag# 本例中的運行命令docker tag friendlyhello hijiangtao/friendlyhello:v1
接著將它發布:
# push 格式docker push username/repository:tag# 本例中的運行命令docker push hijiangtao/friendlyhello:v1
至此,大功告成。現在你可以在任意一台配有 docker 環境的機器上運行該鏡像了。
docker run -p 4000:80 hijiangtao/friendlyhello:v1
如果我們經常更新鏡像,可以嘗試使用自動創建(Automated Builds)功能來簡化我們的流程,這一塊可以結合 GitHub 一起操作,對於需要經常升級鏡像內程序來說,這會十分方便。本文中所述的鏡像就託管在 GitHub。
使用自動構建前,首先要要將 GitHub 或 Bitbucket 帳號連接到 Docker Hub。如果你使用了這種方式,那麼每次你 git commit
之後都應該可以在你的 docker cloud 上看到類似下方的鏡像構建狀態。
五、總結
整體來說,Docker 是一個越看越有意思的概念,值得我們深入了解。但是其中也存在很多技巧,比如就 Docker 技術三劍客來說,這些是官方插件,簡化了 Docker在多服務和多機器環境下的功能配置,但若沒有弄懂 Docker 之前就去了解反而會讓自己越看越繞。另一方面則是隨著時間的推移,很多曾經 Docker 推薦的方式正在被淘汰,比如在非 Linux 環境下的 Boot2Docker,這也需要我們學習者在了解的過程中對做好區分,否則也會事倍功半。
本文著重總結了 Docker 的概念與簡單的上手實踐,對於 Docker 的廣泛運用場景沒有做過多介紹,但其實我們可以利用 Docker 做很多有意思的事情,作為一個可以用於流水線管理、能夠應用隔離、具有快速高效部署等諸多特點的技術,往好的方面想可以允許我們快速實現應用在線上部署的表現一致化,遠離「這段代碼在我電腦上運行沒問題啊」的煩惱並減輕硬體伺服器負擔,往不好的方面想可以通過伺服器超售大賺一筆...
六、參考
- Docker
- Docker 從入門到實踐
- Docker容器與虛擬機區別
- Docker三劍客實踐之部署集群
- Get Started with Docker
原文鏈接 Docker 學習新手筆記:從入門到放棄
個人公眾號,微信搜索「黯曉」或者掃描 二維碼 關注,會同步我在知乎及個人博客上發表的文章,談談前端技術與日常有趣事。使用知乎的同學可以關注我的專欄初級前端工程師。
生活中難免犯錯,請多多指教!
推薦閱讀: