今天的文章要來介紹如何產生 Docker 的映像檔了,有了上一篇文章的介紹相信讀者對於 Docker 已經有了初步的了解了,應該知道要有映像檔是可以用來虛擬化應用程式及其執行環境的檔案,但執行環境基本上不用自己寫因為 DockerHub 上面都有,所以今天這篇文章主要會介紹如何產生應用程式的映像檔。
不曉得大家有沒有寫過 C++,要產生一個可以執行的 .out
檔通常都會寫一個 Makefile 最後再下 make
的指令來產生 .out
檔,之所以要先講這個跟 Docker 毫無相關的原因是因為 Docker 要產生映像檔的方式跟這個幾乎是一樣的做法,而要產生映像檔時要寫的檔案就叫做 Dockerfile 也是本篇文章要講的重要內容。
由於筆者本身是前端出身因此這篇文章會來介紹如何產生前端的應用程式,接下來就正式進入本篇文章的重點吧!
Dockerfile 的寫法簡單來說分為四個階段:
所以簡單的寫法就會像下面這樣:
接下來就簡單的用白話文來解釋一下上面每一行在做的事情:
/app
的資料夾中,但要如何可以讓這個 /app
的資料夾中有程式碼可以讓我執行呢?/app
中,這就是 COPY . /app
的作用。npm install
並且利用 node index.js
來執行這個 index.js
檔。其實除了上面的寫法外,Dockerfile 還有以下幾個比較進階的寫法,分別是:
ARG
中先被定義。CMD
,最後只會執行最後寫的那個 CMD
。CMD
來說 ENTRYPOINT
會更優先執行。上面的 CMD
跟 ENTRYPOINT
看起來很像但其實兩者是不太一樣的,除了優先執行順序外還有一個很重要的觀念是,當今天 Dockerfile 只設定 CMD
而沒有設定 ENTRYPOINT
時,Docker 在啟動容器時會先使用預設的 ENTRYPOINT
指令,而預設的指令為 /bin/sh -c
,所以假如今天要讓容器不用這種方式啟動的話,就要記得使用 ENTRYPOINT
這個寫法喔!
其實 Dockerfile 還有一個很重要的觀念要跟讀者說,就是 Dockerfile 在每次要產生一個新的映像檔前都會先看 Dockerfile 中有沒有哪個是被修改過的,有點像是 Git 在檢查檔案有沒有被修改的道理,如果發現該行的結果其實是不需要修改的話,Docker 就會很聰明的直接利用上一次產生的映像檔當成是 cache 來當作是該行的結果。
舉例來說:在上面的例子每一次都一定會拉到正確的 node:10-alpine 所以每次在產生映像檔前我就不需要重新下載從之前已經下載好的 node:10-alpine 中拿過來用就好,也因為這個特性可以讓 Dockerfile 在寫法上可是有著效能上的差距,接下來我們就來一起看一下如何改善上面的 Dockerfile 寫法吧!
首先我們先來看原本的寫法:
上面這份 Dockerfile 最大的問題就在於 COPY . /app
以及 RUN npm i && node index.js
這兩行,每次只要我資料夾內有檔案變動,我就要重新複製所有的檔案到 /app
這個資料夾,乍看之下好像沒什麼問題,但熟悉前端的人就會知道這些檔案也包括了 package.json
以及 package-lock.json
這兩個用來管理 mode_modules
的檔案,也就會造成每次我複製檔案時都會重新跑底下 npm install
這就會讓整個 build 的過程變得相當久。
所以我們可以簡單調整一下,先複製 package.json
以及 package-lock.json
這兩個檔案,然後跑 npm install
,這樣只要我沒有變動到安裝套件就不用花很長的時間再重新安裝套件了,最終改善後的寫法就像下面這樣:
雖然行數變多了,但我們來看一下執行結果:
可以發現只要我沒有動到 package.json
也就是沒有安裝新的套件,我就不會執行 npm i
這個指令,也因此讓 Docker 可以更有效率的產生這個映像檔,不用每次都重新安裝套件。
multi-stage build 可以想像成是一個檔案有多個階段的 build,也就是我要把某一階段 build 好的檔案傳給另一階段讓他可以利用,舉個例子來說:前端通常都會利用 webpack 進行專案內容打包的工作,但打包完會變成一個又一個的靜態檔,這時候就必須要利用 multi-stage build 的觀念,把第一個 build 也就是經由 webpack 打包好的靜態檔傳給下一個 build 可能是利用 nginx 來 serve 這些靜態檔。
multi-stage build 的寫法也很簡單,只要用到 as
這個關鍵字就好,透過 as
就可以把這個映像檔的內容快速地提供給該 Dockerfile 下的其他映像檔使用,寫法如下:
可以發現這個 as
的關鍵字最主要的做法就是要把這個 FROM
的階段當作是一個標籤可以讓其他階段在進行操作時可以用 --from=標籤
的方式進行使用,所以 multi-stage build 最主要的作用就是幫助一個複雜的 Dockerfile 可以有效的管理各個階段的 build 並且可以讓下一個階段的 build 能使用上一個階段的產物。
-t
代表的是 tag 的意思。但有時候可能會出現這個畫面:
這是因為這個映像檔正在被某個容器使用中,因此想要移除該映像檔前必須要先移除該容器。
但有時候可能會出現這個畫面:
這是因為這個容器正在使用中,因此想要移除該容器前必須要先把該容器暫停。
今天介紹了如何撰寫 Dockerfile 來產生 Docker 映像檔,但假如我今天有多個檔案每個檔案都必須要執行,那我不就要連續下三次 docker build
以及 docker run
的指令,這樣也太麻煩,即便我用 shellscript 的方式來統整這些指令還是很不方便,因此在明天的文章筆者要來介紹一個進階的 Docker 用法叫 Docker Compose,就敬請期待明天的文章吧XD
如果對於文章有什麼問題都歡迎在下面留言給筆者,讓我們就明天的文章見吧!