天天看點

九個編寫Dockerfiles的常見錯誤或許還要安裝python依賴?

本文講的是<b>九個編寫Dockerfiles的常見錯誤</b>編者的話】我們每天基于Dockerfiles工作;所有運作的代碼都來自一系列的Dockerfiles。這篇文章将會讨論編寫Dockerfile時人們經常犯的錯誤以及如何改進。對于Docker專家說,這篇文章裡的許多技巧可能會非常明顯進而會得到很多的認同。但是對于初級到中級開發者,該文章将會是一份很有用的指南,它有助于理清以及加速你們的工作流程。

執行<code>apt-get install</code>是每一個Dockerfile都有的東西之一。你需要安裝一些外部的包來運作代碼。但使用<code>apt-get</code>相應地會帶來一些問題。

一個是運作<code>apt-get upgrade</code> 會更新所有包到最新版本 —— 不能這樣做的理由是它會妨礙Dockerfile建構的持久與一緻性。

另一個是在不同的行之間運作<code>apt-get update</code>與<code>apt-get install</code>指令。不能這樣做的原因是,隻有<code>apt-get update</code>的代碼會在建構過程中被緩存,而且你需要運作<code>apt-get install</code>指令的時候不會每次都被執行。是以,你需要将<code>apt-get update</code>跟所要安裝的包都在同一行執行,來確定它們正确的更新。

<code>ADD</code>與<code>COPY</code>是完全不同的指令。<code>COPY</code>是這兩個中最簡單的,它隻是從主機複制一份檔案或者目錄到鏡像裡。<code>ADD</code>同樣可以這麼做,但是它還有更神奇的功能,像解壓TAR檔案或從遠端URLs擷取檔案。為了降低Dockerfile的複雜度以及防止意外的操作,最好用<code>COPY</code>來複制檔案。

明确代碼的哪些部分以及什麼時候應該放在建構鏡像内或許是最重要的事了,它可以顯著加快建構速度。

Dockerfile裡經常會看到如下這些内容:

這就意味着每次修改檔案之後都需要重新建構那行以下的所有東西。多數情況下(包括上面的例子),它意味着重新安裝應用依賴。為了盡可能地使用Docker的緩存,首先複制所有安裝依賴所需要的檔案,然後執行指令安裝這些依賴。在複制剩餘檔案(這一步盡可能放到最後一行)之前先做這兩個步驟,會使代碼的變更被快速的重建。

COPY ./my-app/requirements.txt /home/app/requirements.txt

RUN pip install -r requirements.txt

COPY ./my-app/ /home/app/

這樣做會確定建構盡可能快的執行。

許多Dockerfiles在開頭都使用<code>FROM node:latest</code>模闆,用來從Docker registry拉取最新的鏡像。簡單地說,使用<code>latest</code>标簽的鏡像意味着如果這個鏡像得到更新,那麼Dockerfile的建構可能會突然中斷。弄清這件事可能會非常難,因為Dockerfile的維護者實際上并沒做任何修改。為了防止這種情況,隻需要確定鏡像使用特定的标簽(例如:<code>node:6.2.1</code>)。這樣就可以確定Dockerfile的一緻性。

很多人會忽視建構Docker鏡像與運作一個Docker容器的差別。在建構鏡像時,Docker讀取Dockerfile裡的指令并建立鏡像。在依賴或代碼修改之前,鏡像是保持不變以及可重複使用的。這個過程完全獨立于其它容器。需要與其它容器或服務(如資料庫)進行互動則會在容器運作的時候發生。

舉一個例子,執行資料庫遷移。很多人試圖在建構鏡像時執行此操作。這樣做會導緻許多問題。首先,在建構時資料庫可能不可用,因為它可能沒建在它将要運作的伺服器上。其次,你可能想使用同一個鏡像來連接配接不同的資料庫(在開發或生産環境中),在這種情況下,如果它在建構過程中,遷移是不能進行的。

EXPOSE和ENV是廉價的執行指令。如果你破壞它們的緩存,幾乎瞬時就可以重建。是以,最好盡可能晚地聲明這些指令。在建構過程中應該直到需要的時候才聲明ENV。如果在建構的時候不需要他們,那麼應該在Dockerfile的末尾附加<code>EXPOSE</code>。

再次檢視Golang的Dockerfile,你會看到,所有<code>ENVS</code>都是在使用前聲明的,并且在最後聲明其餘的:

如需修改<code>ENV GOPATH</code>或<code>ENV PATH</code>,鏡像幾乎會馬上重建成功。

嘗試使用多個<code>FROM</code>聲明來将不同的鏡像組合到一起,這樣不會起任何作用。Docker僅使用最後一個<code>FROM</code>并且忽略前面所有的。

是以如果你有這樣的Dockerfile:

那麼<code>docker exec</code>進入運作的容器中,會得到下面的結果:

這其實是GitHub上的一個問題:合并不同的鏡像,但它看起來不會很快就增加的功能。

這可能是了解Docker的開發者遇到的最大問題。而公認的最佳實踐是:每個不同的服務,包括應用,應該在它自己的容器中運作。在一個Docker鏡像裡面加入多個服務非常容易,但是有一定的負面影響。

首先,橫向擴充應用會變得很困難。其次,額外的依賴和層次會使鏡像建構變慢。最終,增大了Dockerfile的編寫、維護以及調試難度。

當然,像所有的技術建議一樣,你需要用你的最佳判斷。如果想快速安裝一個<code>Django</code>+<code>Nginx</code>的應用的開發環境,那麼讓它們運作在同一個容器裡面,同時生産環境中有一個不同的Dockerfile,讓他們分開運作,是合理可行的。

<code>Volume</code>是在運作容器時候加入的,而不是建構的時候。與第五個誤區類似,在建構過程中不應該與你聲明的<code>volume</code>有互動。相反地,你隻是在運作容器的時候使用它。例如,如果在以下建構過程中建立檔案并且在運作那個鏡像時候使用它,一切正常:

但是,如果我對一個存儲在<code>volume</code>上的檔案做同樣的事,就不會起作用。

一個有趣的問題是:如果你前面的任何一個層次聲明了一個<code>VOLUME</code>(也可能是幾個<code>FROMS</code>)依然會遇到同樣的問題。是以,最好留意一下父類鏡像都聲明了什麼<code>volume</code>。如果遇到問題,請使用<code>docker inspect</code>檢查。

了解怎樣寫好一個<code>Dockerfile</code>将會是一個漫長的路程,它會帶你了解<code>Docker</code>是如何工作的,同時也幫助你建立你的基礎架構。了解Docker緩存會為你節省好多等待建構完成的時間!

原文釋出時間為:2016-06-16

本文來自雲栖社群合作夥伴Dockerone.io,了解相關資訊可以關注Dockerone.io。

原文标題:九個編寫Dockerfiles的常見錯誤