over 2 years ago

Translated from "Using Docker in Java Applications - The lightweight virtualization container is fast becoming the preferred way to package and deploy Java web apps" by Arun Gupta, Java Magazine, November/December 2015, page 51. Copyright Oracle Corporation.

應用Docker在Java應用程式上

輕量的虛擬容器快速成為打包與布署Java網頁程式更好的方式

軟體容器讓開發者能夠用新的方式將他們的程式與相依的底層一起打包,且容器是可攜的,在他們自己的機器上、正式的機器、資料中心裡或是在雲端上都可以一致地運作,在可攜的容器之中,Docker最廣泛被使用。

在這有二篇文章的系列中,首篇文章解釋Docker的關鍵概念與它如何運作,其中,我展示如何用Toolbox開始使用Docker,並用一個簡單的“Hello World”程式來驗證,接著解釋Docker映像檔的概念及它如何被建立。透過打包Java應用程式成為一個Docker映像檔並讓它執行成為一個容器將幫助您了解整個基礎,一些用來檢閱映像檔與容器的基礎指令也同樣會介紹到。最後,我會示範如何用Docker布署一個Java EE (WildFly)應用程式。這系列的第二部分將會展示如何建立需要多個Docker容器(包含叢集)的應用程式,並用其他工具檢查Docker的整合。

Docker是什麼?

一個應用程式一般需要一個特定版本的作業系統、JDK、應用程式伺服器、資料庫伺服器與一些基礎架構的元件,為了提供最佳化的體驗,它可能須要綁定特定的通訊埠、一定數量的記憶體與針對不同元件有各自的配置設定。應用程式、基礎架構的元件與配置合起來是一個應用程式運作系統。[譯註:這裡有點刻意,若翻成應用程式作業系統擔心造成不必要的誤會]

一個用來建置應用程式運作系統的安裝腳本通常會執行下載、安裝與配置需要的組件,Docker用建立包含所有元件的映像檔來簡化這過程並當成一個單位來管理,然後,用映像檔來建立執行期的容器,在Docker提供的虛擬化平台上執行,這些容器可視為輕量化的虛擬機器(VMs),作業系統由虛擬化平台提供,以不用包含完整的作業系統複本來看,容器是輕量的。

Docker容器提供如隔離、沙箱、可重現性、資源限制與快照等其他許多優點,不需要虛擬機器監視器(hypervisor)就能執行,由於相當輕量,容器執行密度可以比標準的虛擬機器要高。[譯註:在一台實體機器上運行較虛擬機器更多數量的容器]

Docker有三個主要元件:

  • 映像檔:Docker的建構元件,包含一個唯讀的應用程式運作系統範本,例如一個映像檔可以是一個Fedora作業系統並安裝有WildFly及您的Java EE應用程式。您可以簡易地建立一個新的映像檔或是更新既有的映像檔。
  • 容器:一個由映像檔建立的執行期,容器是Docker的運行元件,可以被運轉、啟動、停止、搬移與刪除,每個容器都是被隔離與安全的應用程式平台。
  • 倉庫[譯註:這名詞參考《Docker —— 從入門到實踐》正體中文版的翻譯]:Docker的散布元件,映像檔可以上傳到這或從這下載,倉庫可是公開的,例如Docker’s own registry,或在您的防火牆內架設私有的倉庫。

Docker如何運作?

Docker使用client/server架構,其中Docker daemon是執行在host機器上的server,持續地維持Docker容器的執行,而Docker client可以安裝在您的機器上,透過傳送管理指令(例如取得一個映像檔或是執行一個容器)與Docker daemon溝通。Docker倉庫是儲存所有映像檔的地方,Figure 1顯示它的設計配置。

Figure 1. Docker的結構

在開發初期Docker host可能與Docker client在同台機器,但一般來說為了擴展性,它通常會移至獨立的機器。一般工作流程需要如下步驟:

  • Docker client請求以某個給定的映像檔執行容器
  • Docker daemon檢查host機器上是否有指定的映像檔,如果有,運轉成為容器,如果沒有,會從Docker倉庫下載映像檔後運轉成為容器。同個映像檔可以輕鬆運轉成多個容器。

Docker client提供建立與更新映像檔、下載或上傳映像檔到倉庫、運轉、查詢、觀測、刪除運轉中的容器等指令,與Docker daemon之間可以用socket或REST API溝通[譯註:傳送指令]。如果有必要,Docker daemon與Docker倉庫溝通以完成需要的操作。

身為開發者,為您的應用建立映像檔,用映像檔運轉成為容器,然後上傳映像檔到Docker倉庫讓其他人可以試用它。

開始使用Docker

Linux機器原生支援Docker,且很容易用預設的套件管理員安裝,如果您使用Windows或Mac機器,Docker Toolbox提供開始使用Docker的另類必要工具,它包含:

  • Docker client (即docker執行檔),它就和先前討論過的client元件一樣,透過與Docker daemon溝通來操作映像檔與容器。
  • Docker Machine (即docker-machine執行檔),讓您在自己電腦上、雲端提供者或自己資料中心裡的虛擬機器中建立Docker host,接著Docker daemon會安裝在虛擬機器中,然後client可以設定成與這個host溝通。

Docker Machine使用driver建立虛擬機器,driver是一個多重意義的術語,這裡指的是一個虛擬環境,例如:Oracle VM VirtualBox driver可以用在Mac或Windows系統上,AWS、Microsoft Azure和其他driver可以用在雲端上建立host。[譯註:這裡就不把driver翻譯成驅動程式,不然整段話讀起來就失去味道了]

  • Docker Compose (即dockercompose執行檔),通常,您的應用程式由多個容器組成,例如:WildFly、MySQL與Apache網站伺服器,Docker Compose能讓您用單一配置檔定義與運轉多個容器組成的應用程式。
  • Kitematic,一個功能強大的容器管理GUI,它提供在command-line介面與GUI畫面間一個無縫的體驗,而且還提供與Docker Hub的整合。
  • Docker Quickstart Terminal,一個終端介面的應用程式,建立預設的Docker Machine並配置一個Docker client與預設的Docker host (Toolbox安裝時建立)溝通。
  • Oracle VM VirtualBox 5.0.0,虛擬化技術的提供者,用來在本機上建立Docker host。

這些元件,每一個都可以單獨下載,Docker Toolbox將它們巧妙地打包成單一一個下載,且使用Docker Toolbox是開始使用Docker最簡單的方式。

要開始使用,請下載Docker Toolbox並安裝在您的機器上,執行Docker Quickstart Terminal建立預設的Docker host並配置Docker client與這個Docker host溝通,應該會產生如下的輸出:

Creating Machine default...
Creating VirtualBox VM...
Creating SSH key...
Starting VirtualBox VM...
Starting VM...
To see how to connect Docker to this machine,
run: docker-machine env default
Starting machine default...
Setting environment variables for machine default..

這輸出顯示Docker host在一個VirtualBox的虛擬機器中被建立、SSH金鑰被建立、虛擬機器被啟動,然後Docker client被配置成於這個Docker host溝通。client以環境變數如DOCKER_HOSTDOCKER_CERT_PATH配置,如前所示,這些配置是用docker-machine env default指令完成,這(Docker) machine名字是default

eval $(docker-machine env default)指令可以用來配置任何shell與此host溝通,最後,它會顯示如下的輸出:

docker is configured to use the default machine with IP 192.168.99.100

docker-machine ip default指令顯示指派給此host的IP位址,這是在/etc/hosts或在您作業系統中等效的檔案中映對名稱與IP位置的一個好方法,例如:可以加入以下這行:

192.168.99.100 dockerhost

然後執行ping dockerhost確認Docker host的映對是正確的。現在,Docker client已經準備完畢可以和Docker host溝通了。

Docker Toolbox是開始使用Docker最簡單的方式。

Docker Hello World

在我們開始使用Docker執行Hello World例子之前,先來看些基本的指令:

docker images條列在host可以使用的映像檔。

docker ps條列執行中的容器列表。目前,容器列表是空的,因為沒有容器被啟動。如果您想看到先前已結束的容器,需要加上-a選項。這指令的輸出最合適在128字元寬的畫面上觀看。

docker --help條列全部的指令,與此類似,docker ps --help條列這指令可接受的選項,隨意嘗試不同指令,試著調整選項以符合您的需求。

現在,開始執行可以在Docker Hub上找到預先建立的“Hello World”映像檔,只需用下列的指令:

docker run hello-world

這指令的輸出如下(有些在此文字之前或之後的冗詞被省略):

Hello from Docker.

這輸出確認

  • Docker client和daemon正確被安裝
  • hello-world映像檔雖不存在此Docker daemon上,但Docker可以從Docker Hub上下載
  • 從下載的映像檔啟動容器,並將輸出串流到Docker client上

如您所見,可以很輕鬆地執行您的第一個容器。

建立您第一個使用Java的Docker映像檔

Docker映像檔為唯讀的範本可以啟動Docker容器,每個映像檔由好幾層組合起來,Docker使用union檔案系統結合成單一的映像檔,Union檔案系統可以覆蓋在不同檔案系統的檔案和目錄上,形成單一一致的檔案系統。

這分層結構讓Docker相當輕量化,對映像檔的任何變更,例如:更新一個應用程式到新版本或是更換JDBC driver,只需重建受影響的那一層,因此,不像VM那樣,需換掉整個映像檔或整個重建,只需新增或更新某一層,這讓布署變得更快更簡單。

每個映像檔都從基礎的作業系統映像檔開始,例如fedora是一個基礎的Fedora映像檔,然後許多層加在它之上,例如,如Figure 2所示,jboss/wildfly由多層映像檔建構而成:

Figure 2. 建立一個Java EE的映像檔

一個Docker映像檔是透過從一個通常稱作Dockerfile的文字檔讀取指令來建構,這檔案包含建立映像檔所需的所有指令。例如,它指定基礎的作業系統、JDK、應用伺服器與其他相依套件。JDK與應用伺服器一般是用shell指令下載與安裝,像是GETCOPYRUNCOPY指令可以從local檔案系統複製檔案到容器中。或者,您可以在已經含JDK與
WildFly的基礎映像檔上建構新的映像檔,Docker Hub上有許多您需要的基礎映像檔。

每個Dockerfile可以有一個CMD指令,提供啟動容器的執行檔,若指定多個CMD指令,只有最後一個會生效。

網路上有完整的語法參考手冊,與最佳實務

一個只顯示JDK版本的簡單Dockerfile看起來如下:

FROM java:8
CMD ["java", "-version"]

將上述內容複製到一個檔案,並將檔案命名為Dockerfile,然後建立映像檔:

docker build java-version .

build指令建立一個名為java-version的映像檔, . 指示建構映像檔指令的檔案在目前的目錄中。

當映像檔建立完成,可以看到(下面輸出沒有顯示所有欄位):

> docker images
REPOSITORY  TAG    IMAGE ID     VIRTUAL SIZE
java-sample latest 53bd2cdf4aa2 425.4 MB

docker run java-sample執行容器可以看到如下輸出:

openjdk version "1.8.0_66-internal"

單獨使用docker ps將不會再輸出看到容器,因為這容器沒有正在執行,但是可以透過docker ps –a指令看到已經結束的容器。

如果想在這容器中執行一個JAR檔,可以用從local檔案系統COPY複製該檔案到容器中,或是用GET下載JAR檔,然後將JAR加到CMD指令列中,任何JVM的組態設定也用這方式套用。

一個Docker映像檔是透過從一個通常稱作Dockerfile的文字檔讀取指令來建構,這檔案包含建立映像檔所需的所有指令。

使用Docker布署一個Java EE應用程式

我們已經跑過一個非常基礎的例子,現在,我們開始布署一個Java EE應用程式到一個WildFly容器中,以下是Dockerfile的內容:

FROM jboss/wildfly
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-c", "standalone-full.xml", "-b", "0.0.0.0"]
RUN curl -L https://github.com/javaee-samples/javaee7-hol/raw/master/solution/movieplex7-1.0-SNAPSHOT.war –o /opt/jboss/wildfly/standalone/deployments/movieplex7-1.0-SNAPSHOT.war

這檔案使用稱作jboss/wildfly的基礎映像檔,在其中WildFly已經預設安裝在/opt/jboss/wildfly目錄裡,這目錄用來啟動WildFly容器[譯註:這指的應該是Java EE容器],網路介面用-b與公開的IP位置綁定,從repository下載一個WAR到WildFly監控布署用的目錄。

用下列指令建立映像檔:

docker build –t javaee-sample .

現在,我們來執行這映像檔!一個Docker容器預設在前景執行且不允許透過terminal互動,-i選項允許透過stdin互動,-t選項則是替process繫上TTY (一個console介面),選項可以合併,-i-t可以合併成-it。用下列指令啟動容器:

docker run -it -p 8080:8080 javaee-sample

WildFly映像檔暴露8080通訊埠可以存取,但這個通訊埠需要映對到我們的host,透過-p選項映對,第一個“8080”是host端的通訊埠,而第二個“8080”則是容器內的通訊埠。

容器啟動後,布署到WildFly的Java EE應用程式可以在本機端用dockerhost:8080/movieplex7存取,要注意的是host到IP位置的映對在更早之前就介紹過了,Figure 3顯示URL的執行結果。

Figure 3. 在8080上的範例應用程式

當容器以互動模式啟動,它可以用Ctrl+C組合鍵停止,下面輸出可以確認容器已經停止。

docker ps –a
CONTAINER ID IMAGE         COMMAND                CREATED            STATUS                          PORTS NAMES
1efa5d6f618d jboss/wildfly "/opt/jboss/wildfly/b" About a minute ago Exited (130) About a minute ago       compassionate_mestorf

輸出的每一欄傳達關於容器相當有用的資訊:

  • 第一欄顯示指派給每個容器唯一的ID
  • 用來啟動容器的映像檔名稱
  • 啟動容器的指令
  • 容器建立的時間
  • 任何容器暴露的通訊埠 (在此例中,因為容器已經終止,所以此欄是空的)
  • 目前的狀態
  • 指派給容器的隨機名稱,除非您用--name選項給予一個名稱

在Linux/UNIX的系統上,容器的ID可以用下列指令取得:

docker ps | grep jboss/wildfly | awk '{ print $1 }'

然後,可以用docker stop 終止容器,接著用docker rm 移除容器,或是用docker rm -f 終止與移除容器。同樣,可以用docker start 重新啟動容器。

或是,您可以將-it選項改成-d選項,讓容器以單獨(背景)模式啟動。

docker inspect是另一個重要的指令,顯示更多關於容器的細節,例如,透過以下指令可以條列容器的網路通訊埠:

docker inspect --format '{{ .Config.ExposedPorts }}' <CONTAINER_ID>

更進一步,-P選項可以映對容器內的通訊埠到local host較高的通訊埠(一般來說從23768到61000),可以看到容器通訊埠與host通訊埠的映對:

docker port <CONTAINER_ID>
8080/tcp -> 0.0.0.0:32768

在這情況下,應用程式可以在dockerhost:32768/movieplex7上存取。

結論

本文解釋Docker的關鍵概念,以及如何用它打包您的Java應用程式,Docker開啟打包一次布署到任何地方(Package Once Deploy Anywhere, PODA)的典範,且改變應用程式如何建立、布署與拓展的方式,Docker減少開發環境、測試環境與正式環境之間不匹配的阻抗。

準備好了嗎?Docker已經在此,而且將會以輕量化的容器技術和我們在一起很久。此系列的下一篇文章,我將探討多重容器的應用程式與在叢集上執行容器。

LEARN MORE
Getting started with Docker
Overview of containers
Kubernetes: Docker orchestration tool

譯者的告白
過去沒特別計算複製原文(含調整格式)到部落格花的時間,這次嘗試用番茄時鐘法,將翻譯文章切成好幾個tasks,但複製只切成一個,最後這個task用了三顆番茄才完成,意思是超過一小時,這讓我有點意外。

← 首部曲:使用WebSockets建構應用程式 《瘋狂改變世界:我就是這樣創立Twitter的!》書摘 →