docker 5 section

使用 Dockerfile 定製鏡像

docker 鏡像的定製實際上就是定製每一層所添加的配置、文件。如果我們可以把每一層修改、安裝、構建、操作的命令都寫入一個腳本,用這個腳本來構建、定製鏡像,那麼之前提及的無法重複的問題、鏡像構建透明性的問題、體積的問題就都會解決。這個腳本就是 Dockerfile。
Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。
還以之前定製 nginx 鏡像爲例,這次我們使用 Dockerfile 來定製。
在一個空白目錄中,建立一個文本文件,並命名爲 Dockerfile :

[root@work ~]# cd mynginx
[root@work mynginx]# vim Dockerfile
其內容爲:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
這個 Dockerfile 很簡單,一共就兩行。涉及到了兩條指令, FROM 和 RUN 。
[root@work mynginx]# docker build -t mynginx:v1 .
Sending build context to Docker daemon 2.048 kB
Step 1/2 : FROM nginx
 ---> 881bd08c0b08
Step 2/2 : RUN echo '<h1>Hello,Docker!</h1>'> /usr/share/nginx/html/index.html
 ---> Running in d746b7475d39

 ---> 3afa909d5ebc
Removing intermediate container d746b7475d39
Successfully built 3afa909d5ebc

FROM 指定基礎鏡像

所謂定製鏡像,那一定是以一個鏡像爲基礎,在其上進行定製。就像我們之前運行了一個nginx 鏡像的容器,再進行修改一樣,基礎鏡像是必須指定的。而 FROM 就是指定基礎鏡像,因此一個 Dockerfile 中 FROM 是必備的指令,並且必須是第一條指令。
在 Docker Store 上有非常多的高質量的官方鏡像,有可以直接拿來使用的服務類的鏡像,除了選擇現有鏡像爲基礎鏡像外,Docker 還存在一個特殊的鏡像,名爲 scratch 。這個鏡像是虛擬的概念,並不實際存在,它表示一個空白的鏡像。

FROM scratch
...

如果以 scratch 爲基礎鏡像的話,意味着不以任何鏡像爲基礎,接下來所寫的指令將作爲鏡像第一層開始存在。
不以任何系統爲基礎,直接將可執行文件複製進鏡像的做法並不罕見,比如swarm 、 coreos/etcd 。對於 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 centos
RUN yum update
RUN yum 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 層鏡像。這是完全沒有意義的,而且很多運行時不需要的東西,都被裝進了鏡像裏,比如編譯環境、更新的軟件包等等。結果就是產生非常臃腫、非常多層的鏡像,不僅僅增加了構建部署的時間,也很容易出錯。
Union FS 是有最大層數限制的,比如 AUFS,曾經是最大不得超過 42 層,現在是不得超過127 層。
上面的 Dockerfile 正確的寫法應該是這樣:

FROM centos
RUN buildDeps='gcc libc6-dev make' \
&& yum update \
&& yum 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 \

首先,所有的命令只有一個目的,就是編譯、安裝 redis 可執行文件。因此沒有必要建立很多層,這只是一層的事情。因此,這裏沒有使用很多個 RUN 對一一對應不同的命令,而是僅僅使用一個 RUN 指令,並使用 && 將各個所需命令串聯起來。將之前的 7 層,簡化爲了1 層。
在撰寫 Dockerfile 的時候,要經常提醒自己,這並不是在寫 Shell 腳本,而是在定義每一層該如何構建。
並且,這裏爲了格式化還進行了換行。Dockerfile 支持 Shell 類的行尾添加 \ 的命令換行方式,以及行首 # 進行註釋的格式。良好的格式,比如換行、縮進、註釋等,會讓維護、排障更爲容易,這是一個比較好的習慣。
此外,還可以看到這一組命令的最後添加了清理工作的命令,刪除了爲了編譯構建所需要的軟件,清理了所有下載、展開的文件,並且還清理了 apt 緩存文件。這是很重要的一步,鏡像是多層存儲,每一層的東西並不會在下一層被刪除,會一直跟隨着鏡像。
因此鏡像構建時,一定要確保每一層只添加真正需要添加的東西,任何無關的東西都應該清理掉。

兩個Dockerfile進行對比:
[root@work mynginx]# docker images

REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
redisa                  v2                  0c4656647196        44 seconds ago      348 MB
redisa                  v1                  701a9869c78b        About an hour ago   408 MB
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章