目錄
- 前言
- 一、Dockerfile介紹
- 二、FROM指定基礎鏡像
- 三、RUN執行命令
- 四、構建鏡像
- 五、鏡像構建上下文(Context)
- 六、遷移鏡像
前言
鏡像的定制實際上就是定制每?層所添加的配置、?件等信息。
但是命令畢竟只是命令,一般用 docker commit 每次定制都得去重復執?這個命令,?且還不夠直觀,如果我們可以把每?層修改、安裝、構建、操作的命令都寫??個腳本,?這個腳本來構建、定制鏡像,那么這些問題就迎刃而解了,而這個腳本就是我們今天要說的 Dockerfile 。
一、Dockerfile介紹
Dockerfile 是?個?本?件,其內包含了?條條的指令(Instruction),每?條指令構建?層,因此每?條指令的內容,就是描述該層應當如何構建。
還以之前定制 nginx 鏡像為例,這次我們使? Dockerfile 來定制。在?個空??錄中,建??個?本 ?件,并命名為 Dockerfile:
$ mkdir mynginx $ cd mynginx $ touch Dockerfile
其內容為:
FROM nginx RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
這個 Dockerfile 很簡單,?共就兩?。涉及到了兩條指令,FROM 和 RUN。
二、FROM指定基礎鏡像
所謂定制鏡像,那?定是以?個鏡像為基礎,在其上進?定制。就像我們之前運?了?個 nginx 鏡像 的容器,再進?修改?樣,基礎鏡像是必須指定的。? FROM 就是指定基礎鏡像,因此?個 Dockerfile 中 FROM 是必備的指令,并且必須是第?條指令。
在Docker Store上有?常多的?質量的官?鏡像,有可以直接拿來使?的服務類的鏡像,如 nginx、 redis、mongo、mysql、httpd、php、tomcat 等;也有?些?便開發、構建、運?各種語?應?的鏡 像,如 node、openjdk、python、ruby、golang 等。可以在其中尋找?個最符合我們最終?標的鏡像 為基礎鏡像進?定制。
如果沒有找到對應服務的鏡像,官?鏡像中還提供了?些更為基礎的操作系統鏡像,如 ubuntu、 debian、centos、fedora、alpine 等,這些操作系統的軟件庫為我們提供了更?闊的擴展空間。
除了選擇現有鏡像為基礎鏡像外,Docker 還存在?個特殊的鏡像,名為 scratch 。這個鏡像是虛擬的 概念,并不實際存在,它表示?個空?的鏡像。
FROM scratch ...
如果你以 scratch 為基礎鏡像的話,意味著你不以任何鏡像為基礎,接下來所寫的指令將作為鏡像第 ?層開始存在。有的同學可能感覺很奇怪,沒有任何基礎鏡像,我怎么去執?我的程序呢,其實對于 Linux 下靜態編譯的程序來說,并不需要有操作系統提供運?時?持,所需的?切庫都已經在可執?? 件?了,因此直接 FROM scratch 會讓鏡像體積更加?巧。使? Go 語? 開發的應?很多會使?這種? 式來制作鏡像,這也是為什么有?認為 Go 是特別適合容器微服務架構的語?的原因之?。
三、RUN執行命令
RUN 指令是?來執?命令?命令的。由于命令?的強?能?, RUN 指令在定制鏡像時是最常?的指令 之?。其格式有兩種:
shell 格式:RUN <命令>,就像直接在命令?中輸?的命令?樣。剛才寫的 Dockerfile 中的 RUN 指令就是這種格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec 格式:RUN ["可執??件", "參數1", "參數2"],這更像是函數調?中的格式。 既然 RUN 就像 Shell 腳本?樣可以執?命令,那么我們是否就可以像 Shell 腳本?樣把每個命令對應?個 RUN 呢??如這樣:
FROM debian:jessie RUN apt-get update RUN apt-get install -y gcc libc6-dev make RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" RUN mkdir -p /usr/src/redis RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install
之前說過,Dockerfile 中每?個指令都會建??層,RUN 也不例外。每?個 RUN 的?為,就和剛才 我們??建?鏡像的過程?樣:新建??層,在其上執?這些命令,執?結束后,commit 這?層的修改,構成新的鏡像。
?上?的這種寫法,創建了 7 層鏡像。這是完全沒有意義的,?且很多運?時不需要的東?,都被裝 進了鏡像?,?如編譯環境、更新的軟件包等等。結果就是產??常臃腫、?常多層的鏡像,不僅僅 增加了構建部署的時間,也很容易出錯。 這是很多初學 Docker 的?常犯的?個錯誤。
Union FS 是有最?層數限制的,?如 AUFS,曾經是最?不得超過 42 層,現在是不得超過 127 層。
上?的 Dockerfile 正確的寫法應該是這樣:
FROM debian:jessie 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
?先,之前所有的命令只有?個?的,就是編譯、安裝 redis 可執??件。因此沒有必要建?很多層, 這只是?層的事情。因此,這?沒有使?很多個 RUN 對??對應不同的命令,?是僅僅使??個 RUN 指令,并使? && 將各個所需命令串聯起來。將之前的 7 層,簡化為了 1 層。在撰寫 Dockerfile 的時候,要經常提醒??,這并不是在寫 Shell 腳本,?是在定義每?層該如何構建。
并且,這?為了格式化還進?了換?。Dockerfile ?持 Shell 類的?尾添加 \ 的命令換??式,以及 ?? # 進?注釋的格式。良好的格式,?如換?、縮進、注釋等,會讓維護、排障更為容易,這是? 個?較好的習慣。
此外,還可以看到這?組命令的最后添加了清理?作的命令,刪除了為了編譯構建所需要的軟件,清 理了所有下載、展開的?件,并且還清理了 apt 緩存?件。這是很重要的?步,我們之前說過,鏡像 是多層存儲,每?層的東?并不會在下?層被刪除,會?直跟隨著鏡像。因此鏡像構建時,?定要確 保每?層只添加真正需要添加的東?,任何?關的東?都應該清理掉。 很多?初學 Docker 制作出了 很臃腫的鏡像的原因之?,就是忘記了每?層構建的最后?定要清理掉?關?件。
四、構建鏡像
好了,讓我們再回到之前定制的 nginx 鏡像的 Dockerfile 來。現在我們明?了這個 Dockerfile 的內 容,那么讓我們來構建這個鏡像吧。在 Dockerfile ?件所在?錄執?:
$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048 kB Step 1 : FROM nginx ---> e43d811ce2f4 Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html ---> Running in 9cdc27646c7b ---> 44aa4490ce2c Removing intermediate container 9cdc27646c7b Successfully built 44aa4490ce2c
從命令的輸出結果中,我們可以清晰的看到鏡像的構建過程。在 Step 2 中,如同我們之前所說的那 樣,RUN 指令啟動了?個容器 9cdc27646c7b,執?了所要求的命令,并最后提交了這?層 44aa4490ce2c,隨后刪除了所?到的這個容器 9cdc27646c7b。這?我們使?了 docker build 命令 進?鏡像構建。其格式為:
$ docker build [選項] <上下?路徑/URL/->
在這?我們指定了最終鏡像的名稱 -t nginx:v3,構建成功后,我們可以像之前運? nginx:v2 那樣來運 ?這個鏡像,其結果會和 nginx:v2 ?樣。
五、鏡像構建上下文(Context)
如果注意,會看到 docker build 命令最后有?個 . 。 . 表示當前?錄,? Dockerfile 就在當前?錄, 因此不少初學者以為這個路徑是在指定 Dockerfile 所在路徑,這么理解其實是不準確的。如果對應上?的命令格式,你可能會發現,這是在指定上下?路徑。那么什么是上下?呢?
?先我們要理解 docker build 的?作原理。Docker 在運?時分為 Docker 引擎(也就是服務端守護進 程)和客戶端?具。Docker 的引擎提供了?組 REST API,被稱為 Docker Remote API,?如 docker 命令這樣的客戶端?具,則是通過這組 API 與 Docker 引擎交互,從?完成各種功能。因此,雖然表 ?上我們好像是在本機執?各種 docker 功能,但實際上,?切都是使?的遠程調?形式在服務端 (Docker 引擎)完成。也因為這種 C/S 設計,讓我們操作遠程服務器的 Docker 引擎變得輕?易舉。
當我們進?鏡像構建的時候,并?所有定制都會通過 RUN 指令完成,經常會需要將?些本地?件復制 進鏡像,?如通過 COPY 指令、ADD 指令等。? docker build 命令構建鏡像,其實并?在本地構建, ?是在服務端,也就是 Docker 引擎中構建的。那么在這種客戶端/服務端的架構中,如何才能讓服務 端獲得本地?件呢?
這就引?了上下?的概念。當構建的時候,?戶會指定構建鏡像上下?的路徑,docker build 命令得知 這個路徑后,會將路徑下的所有內容打包,然后上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下 ?包后,展開就會獲得構建鏡像所需的?切?件。如果在 Dockerfile 中這么寫:
COPY ./package.json /app/
這并不是要復制執? docker build 命令所在的?錄下的 package.json,也不是復制 Dockerfile 所在?錄下的 package.json,?是復制 上下?(context) ?錄下的 package.json。
因此, COPY 這類指令中的源?件的路徑都是相對路徑。這也是初學者經常會問的為什么 COPY ../package.json /app 或者 COPY /opt/xxxx /app ?法?作的原因,因為這些路徑已經超出了上下?的 范圍,Docker 引擎?法獲得這些位置的?件。如果真的需要那些?件,應該將它們復制到上下??錄 中去。
現在就可以理解剛才的命令 docker build -t nginx:v3 . 中的這個 . ,實際上是在指定上下?的? 錄,docker build 命令會將該?錄下的內容打包交給 Docker 引擎以幫助構建鏡像。
如果觀察 docker build 輸出,我們其實已經看到了這個發送上下?的過程:
$ docker build -t nginx:v3 . Sending build context to Docker daemon 2.048 kB ...
理解構建上下?對于鏡像構建是很重要的,可以避免犯?些不應該的錯誤。?如有些初學者在發現 COPY /opt/xxxx /app 不?作后,于是?脆將 Dockerfile 放到了硬盤根?錄去構建,結果發現 docker build 執?后,在發送?個?? GB 的東?,極為緩慢?且很容易構建失敗。那是因為這種做法是在讓 docker build 打包整個硬盤,這顯然是使?錯誤。
?般來說,應該會將 Dockerfile 置于?個空?錄下,或者項?根?錄下。如果該?錄下沒有所需? 件,那么應該把所需?件復制?份過來。如果?錄下有些東?確實不希望構建時傳給 Docker 引擎,那 么可以? .gitignore ?樣的語法寫?個 .dockerignore ,該?件是?于剔除不需要作為上下?傳遞給 Docker 引擎的。
那么為什么會有?誤以為 . 是指定 Dockerfile 所在?錄呢?這是因為在默認情況下,如果不額外指定 Dockerfile 的話,會將上下??錄下的名為 Dockerfile 的?件作為 Dockerfile。
這只是默認?為,實際上 Dockerfile 的?件名并不要求必須為 Dockerfile,?且并不要求必須位于上下 ??錄中,?如可以? -f ../Dockerfile.php 參數指定某個?件作為 Dockerfile。
當然,?般?家習慣性的會使?默認的?件名 Dockerfile,以及會將其置于鏡像構建上下??錄中。
六、遷移鏡像
Docker 還提供了 docker load 和 docker save 命令,?以將鏡像保存為?個 tar ?件,然后傳輸到另 ?個位置上,再加載進來。這是在沒有 Docker Registry 時的做法,現在已經不推薦,鏡像遷移應該直 接使? Docker Registry,?論是直接使? Docker Hub 還是使?內?私有 Registry 都可以。
使? docker save 命令可以將鏡像保存為歸檔?件。?如我們希望保存這個 alpine 鏡像。
$ docker image ls alpine REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest baa5d63471ea 5 weeks ago 4.803 MB
保存鏡像的命令為:
$ docker save alpine | gzip > alpine-latest.tar.gz
然后我們將 alpine-latest.tar.gz ?件復制到了到了另?個機器上,可以?下?這個命令加載鏡像:
$ docker load -i alpine-latest.tar.gz Loaded image: alpine:latest
如果我們結合這兩個命令以及 ssh 甚? pv 的話,利? Linux 強?的管道,我們可以寫?個命令完成從 ?個機器將鏡像遷移到另?個機器,并且帶進度條的功能:
docker save <鏡像名> | bzip2 | pv | ssh <?戶名>@<主機名> 'cat | docker load'