可執行鏡像 - 開發環境的Docker化之路

jopen 9年前發布 | 15K 次閱讀 Docker
 

每位開發者都經歷過軟件不兼容之痛。當我們需要同時開發幾個使用不同Java運行時版本的項目時,這些問題會急劇爆發,特別是在OsX平臺上。為 此,Ruby使用自己的版本管理工具。我的兩個同事曾用了幾小時來調試他們各自用Homebrew管理的OpenSSL和Python版本之間的不兼容。 我們是否可以使用容器來解決這些問題呢?答案是肯定的!

容器的主要目標是交付軟件。新成立的 開放容器項目 給出以下定義:

標準容器的目標是使用自描述和可移植的格式,封裝軟件組件及其全部依賴,以便任何兼容運行時都可以運行,無需額外的依賴,不必關心底層機器和容器的內容。

這份定義沒有提及任何關于軟件分發類型的描述。這是有意而為之的,因為容器的設計是內容無關的。我們要交付什么以及如何使用完全取決于我們自己。在這篇文章中,我將闡述服務鏡像和可執行鏡像之間的區別,并建議讀者使用可執行鏡像。

可執行鏡像沒有服務鏡像那么常見,但卻是一個非常有用的補充。可執行鏡像要解決的是軟件兼容性等問題。我們拿 官方的Maven鏡像 作為例子,探索可執行鏡像是什么、它們是如何工作的,以及我們如何創建可執行鏡像。其中,Dockerfile中的ENTRYPOINT指令是演繹可執行鏡像的核心角色。

1 服務鏡像 VS. 可執行鏡像

傳統上,容器鏡像被用作長時間運行的進程:在服務器上運行的服務,不會影響主機,因為它們存在與容器內。我們稱其為 服務鏡像 。Web服務器、負載均衡服務器和數據庫服務器都是服務鏡像的好例子。這類容器可以很容易與虛擬機對比。

容器鏡像也可以用作短暫的進程:在我們計算機上運行的、容器化的可執行命令。這些容器執行單一的任務,生命周期短暫,而且通常可以在使用后被刪除。我們稱之為 可執行鏡像 。舉例來說,比如編譯器(Golang)或者構建工具(Maven)、演示軟件(我很喜歡用Markdown格式寫一個演示,然后用 RevealJS Docker鏡像將其展示出來),以及瀏覽器。可執行鏡像的終極布道者是Docker公司的Jessie Frazelle。如果你希望獲得更多啟發,一定要閱讀 她博客中相關的內容 ,或者看下她在DockerCon 2015上的 演講

其實,服務鏡像和可執行鏡像之間的界限并非涇渭分明。鏡像都是可執行的,因為它們的任務就是運行一個進程。在容器中運行一個演示或者瀏覽器是非常 好的本地工具示例,因此我將稱其為可執行鏡像。縱然他們是長時間運行的進程。話雖如此,我希望讀者能夠認同這樣分類的道理。如此定義的出發點,更多是從鏡 像的目的,而不是進程存活的長短。

2 可執行鏡像的優勢

那么,可執行鏡像的優勢是什么呢?它們是如何解決前述問題的呢?

其中一個原因是,對可執行鏡像的體驗是一種很好的開始使用Docker的方式。這種體驗非常有用,而且不會影響生產環境。此中的趣味無窮!

另一個原因是安裝方便。眾所周知的包管理器apt-get、yum、MacPorts和homebrew等,通常在大部分時間有完美的表現,但是 當我們真的需要它們的時候……問題在于,這些工具的偉大之處是同一件事情:管理依賴。但是,它們沒有強大到可以管理同一個包的兩個版本,包括其依賴關系 樹。容器的設計沒有依賴性:所有的依賴都被固化到鏡像中。安裝本身只意味著運行Docker、執行命令。如果鏡像不存在于系統中,Docker會自動下載 (pull)該鏡像。通過將軟件與其依賴一起封裝在容器鏡像中的方式,實現了可靠的軟件分發。測試容器鏡像即是測試依賴是否能與主要功能一起工作。

容器化的可執行文件僅指容器化,換個說法叫沙箱。這降低了運行不完全信任軟件的風險,避免了許多程序的漏洞。一個例子是瀏覽器中的可疑鏈接。在一個干凈的 文件系統中運行一個全新的瀏覽器會更安全。另一個例子是關于幾個月前Valve軟件的Steam刪除了所有用戶的文件,包括連接的驅動器的 缺陷 !Docker的沙箱機制并非完美,但它肯定會避免發生清除照片庫這樣的事情。

因為進程及其依賴是封裝在容器中的,運行同一軟件的不同版本變得非常簡單!通常情況下,要開始一個Java/Maven項目,我們需要安裝所需版 本的Java開發套件(JDK)和Maven。而使用Docker,我們就可以跳過這步。 JDK和Maven由某個團隊安裝在一個可執行鏡像中。于是,其他人就可以在此基礎上遷出源代碼,并直接編譯和測試它們。我們可以為另一個使用不同JDK 版本的項目使用另一個鏡像。甚至可以在同一時間編譯這些項目!而不需要擔心$JAVA_HOME環境變量。

可執行鏡像 - 開發環境的Docker化之路

3 Maven鏡像

構建服務鏡像的目的是以指定的方式運行一個服務。這也許需要一些環境相關的信息,比如數據庫地址,但不會很多。構建可執行鏡像的目的是運行一個以 指定方式與系統交互的工具。有很多技術可以實現這一目的。我們將以Maven編譯器鏡像作為這一技術的實現示例。需要注意的是,這里所指的技術是通用的, 所以縱然你不喜歡Java,請稍安勿躁。

4 使用卷傳遞文件

假設我們有一個包含Java源代碼的Maven項目,該項目至少在根目錄下,包含一個pom.xml文件和/src/main/java目錄。對 于本文而言,可以采用任何你想用的Maven項目。如果你沒有任何Maven項目,你可以去下載Spring Boot(選擇Maven類型)。使用命令行cd到項目目錄(包含pom.xml文件的目錄),執行如下命令:

user:project$  docker run --rm \
               -v $(pwd):/project \
               -w /project \ maven:3.3.3-jdk-8 mvn install

該命令做了如下的事情:

  • docker run 創建了maven:3.3.3-jdk-8鏡像的一個實例。該實例中執行了 mvn install 命令。原則上,這不會影響主機系統。
  • -v $(pwd):/project 將當前目錄掛載到容器中,作為/project目錄。這樣以來,容器就可以讀寫主機系統的當前目錄了。
  • -w /project 設置了/project作為工作目錄。這意味著執行mvn命令將在project目錄中有效。
  • --rm 將在執行完畢后刪除容器。甩掉包袱!

這與在主機上直接運行mvn install的結果是一樣的,只是不必實際安裝Java或Maven。我們以在項目目錄下,獲得target目錄而告終,該目錄包含了編譯好的Java應用程序。

可以運行maven clean命令清理項目:

user:project$  docker run --rm \
               -v $(pwd):/project \
               -w /project \ maven:3.3.3-jdk-8 mvn clean

5 使用entry point傳遞參數

Maven鏡像的功能是運行mvn [args]。因此,我們可以認為在Docker命令中指定mvn是多余的。為此,可以使用Docker提供的entrypoint。這個 entrypoint是與命令強關聯的。可以在Dockerfile中分別使用ENTRYPOINT和CMD指令。這兩個指令將作為容器鏡像的元數據,覆 蓋 docker run 命令。我們可以這樣執行 mvn clean install

user:project$ docker run --rm \
              -v $(pwd):/project \
              -w /project \
              --entrypoint mvn \ maven:3.3.3-jdk-8 clean install

entrypoint和命令將連接在一起執行。它的優點是關注點分離。對于可執行容器鏡像而言,entrypoint可以用作定義恒定部分,命令可以用作定義可變部分。

如果我們將entrypoint融入容器鏡像,分離會更加優雅。為此,我們在另一目錄中創建一個Dockerfile文件,內容如下:

FROM maven:3.3.3-jdk-8
WORKDIR /project
ENTRYPOINT ["mvn"]
CMD ["-h"]

其中,我們同樣增加了一個工作目錄,因此我們的新鏡像希望Maven項目掛載在/project目錄之下。Dockerfile以exec的形式定義了 ENTRYPOINT和CMD,方括號內的參數最終被解析為shell。在Dockerfile文件所在的目錄下,執行 docker build -t my_mvn . 命令構建鏡像,這個鏡像簡化了前述的執行命令:

user:project$ docker run --rm \
              -v $(pwd):/project \
              my_mvn clean install

其中, clean install 當然可以替換為mvn的其他參數。如果我們忘記包含命令參數,將會打印 maven help ,因為在Dockerfile文件中定義了默認的命令參數, -h 即表示help。

entrypoint的另一個很好的用途是在方括號內定義輔助腳本。例如,如果在實際服務正常啟動之前,我們需要執行一些命令,輔助腳本可以很好地處理。 另外,這樣的腳本還可以檢查當前是否具備了必要的全部運行時配置,比如鏈接或環境變量等。命令本身作為啟動腳本的參數,但是對執行腳本是透明的。關于這一 點的更多信息以及簡單示例,請參閱Docker文檔中的 Dockerfile最佳實踐

6 為可執行鏡像創建別名

我們可以為可執行鏡像創建一個別名。這樣,我們就可以輸入簡短的指令,就像普通程序一樣。在~/.profile中添加:

mvn() {
  docker run --rm \
    -v $(pwd):/project \
    my_mvn $* }

因為我們要傳遞參數,所以使用函數代替了別名。在執行 source ~/.profile 命令,加載變更后,我們就可以這樣簡單地使用了:

user:project$ mvn clean install

7 使用卷緩存Maven本地倉庫

當前方案的缺點是,每次執行時都需要下載Maven工件。本地Maven安裝總會包含一個倉庫目錄,其中存儲了所有的Maven工件。目前的方法是很簡潔,但是并不實用。讓我們將Maven倉庫作為卷添加進來。創建一個目錄,比如 /usr/tmp/.m2 ,然后運行:

user:project$ docker run --rm \
             -v $(pwd):/project \
             -v /usr/tmp/.m2:/root/.m2 \
             my_mvn install

現在,主機上的 /usr/tmp/.m2 目錄中存儲了Maven下載下來的工件。我們以后每次用這種方式啟動Maven容器鏡像,因為引入了這個目錄,所以Maven會重用那些工件。可以重復執行 mvn install 兩次來檢驗不同。

我們只是讓Maven構建更快了。但是,為此,我們不得不在主機上管理一個目錄。在本文的最后一步中,我們將使用Docker管理這個卷。首先,我們創建一個叫data的容器:

user:project$ docker run --name maven_data \
              -v /root/.m2 \
              maven:3.3.3-jdk-8 echo 'data for maven'

容器創建完畢會打印“data for maven”,該容器創建了一個卷。這里使用什么鏡像不是核心問題,在本例中使用maven:3.3.3-jdk-8是方便,因為它已經下載到主機了,而 使用my_mvn不太方便,因為entrypoint要預先考慮echo聲明。注意,這里沒有 -v /root/.m2: 中的冒號,因為我們不再引入主機目錄。而是讓Docker在主機上創建自己的數據目錄。使用“data”作為名字并非是必需的命令,但是這樣是為了顯式說明這是一個數據容器, 當執行 docker ps 時,該名稱將會反射顯示。我們可以通過 --volumes-from 使用這個容器的卷,而無需考慮Docker持有的實際目錄。這樣做會引入容器中的/root/.m2作為掛載卷。這種技術對共享容器之間的數據也非常有用。我們修改~/.profile如下:

mvn() {
  docker run --rm \
    -v $(pwd):/project \
    --volumes-from maven_data \
    my_mvn $* }

現在,當我們運行mvn時,Maven主目錄將映射到這個卷。Maven容器自身會被刪除,但是卷會在緩存的本地倉庫中保留。如果我們希望清理系統,可以使用如下命令刪除數據容器:

docker rm -v maven_data

-v 表示與之相關的容器滿足如下條件時,刪除該卷:

  • 卷是由Docker管理的
  • 沒有其他容器引用

一個忠告:如果你忘記了使用 -v 選項,最終會產生孤兒卷目錄。

8 總結

可執行容器鏡像是一種強大的Docker應用程序。對于軟件分發,以及以限制和驗證的方式在計算機上運行時,非常有用。此外,這是一種有趣的開始Docker體驗的方式。我希望你能通過此文,在開始嘗試Docker和使用相關技術上,得到了啟發。

關于作者

可執行鏡像 - 開發環境的Docker化之路 Quinten Krijger 在開始他的IT職業生涯前,曾經研究過物理學和一年的古典唱法。后來,他搬到阿姆斯特丹的Trifork,主要的工作是繼續使用開源技術,比如Java、 Spring、ElasticSearch和MongoDB,完成項目后端的工作,對最新的前端設計頗有感覺。他熱衷于縮短反饋周期和啟用敏捷開發:測 試、CI和DevOps是此中的關鍵詞。在Docker出現后不久,他便產生了興趣,并深刻地認識到,有效的容器可以提供非常廣泛的可能性。他致力于啟動 容器解決方案上已經有半年了,目前是ING的一名DevOps。

查看英文原文: Executable Images - How to Dockerize Your Development Machine

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!