首頁 > 軟體

使用Dockerfile指令碼客製化映象的方法

2022-07-18 14:03:23

前言

映象的客製化實際上就是客製化每⼀層所新增的設定、⽂件等資訊。

但是命令畢竟只是命令,一般用 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'

到此這篇關於使用Dockerfile指令碼客製化映象的文章就介紹到這了,更多相關Dockerfile客製化映象內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com