共計 10874 個字符,預計需要花費 28 分鐘才能閱讀完成。
如何用 Docker 重新定義 Java 虛擬化部署,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
今天就和大家分享在 docker 里部署 java 應用的實戰案例。
Dockerfiles
Dockerfile 包含了一系列指令,告訴 Docker 如何去構建一個鏡像,它指定了鏡像的基點,以及配置鏡像的每個細節。以下是一個 Dockerfile 示例,是 CentOS 鏡像的 Dockerfile。
代碼清單 1. CentOS Dockerfile
“`sh
FROM scratch
MAINTAINER The CentOS Project – ami_creator
ADD centos-7-20150616_1752-docker.tar.xz /
# Volumes for systemd
# VOLUME [/run , /tmp]
# Environment for systemd
# ENV container=docker
# For systemd usage this changes to /usr/sbin/init
# Keeping it as /bin/bash for compatibility with previous
CMD [/bin/bash]
“`
大部分內容是注釋,主要有四句命令:
1. code FROM scratch /code:所有 Dockerfile 都要從一個基礎鏡像繼承,在這個例子中,CentOS 鏡像是繼承于 scratch 鏡像,這個鏡像是所有鏡像的根。這個配置是固定的,表明了這個是 Docker 的根鏡像之一。
2. code MAINTAINER … /code:code MAINTAINER /code 指令指明了鏡像的所有者,這個例子中所有者是 CentOS Project。
3. code ADD centos…tar.xz /code:code ADD /code 指令告訴 Docker 把指定文件上傳到鏡像中,如果文件是壓縮過的,會把它解壓到指定路徑。這個例子中,Docker 會上傳一個 CentOS 操作系統的 Gzip 包,并解壓到系統的根目錄。
4. code CMD [/bin/bash] /code:最后,code CMD /code 指令告訴 Docker 要執行什么命令,這個例子中,最后會進入 Bourne Again Shell (bash)終端。
現在你知道 Docker 大概是長什么樣子了,接下來再看看 Tomcat 官方的 Dockerfile,圖 2 說明了這個文件的架構。
這個架構未必如你想象中那么簡單,但我們接下來會慢慢學習它,其實它是非常有邏輯的。上邊已經提過所有 Dockerfile 的根是 code scratch /code,接下來指定的是 code debian:jessie /code 鏡像,這個官方鏡像是基于標準鏡像構建的,Docker 不需要重復發明輪子,每次都創建一個新鏡像了,只要基于一個穩定的鏡像來繼續構建新鏡像即可,在這個例子中,code debian:jessie /code 是一個官方 Debian Linux 鏡像,就像上邊的 CentOS 一樣,它只有三行指令。
代碼清單 2. debian:jessie Dockerfile
“`sh
FROM scratch
ADD rootfs.tar.xz /
CMD [/bin/bash]
“`
在上圖中我們還見到有安裝兩個額外的鏡像,CURL 和 Source Code Management,鏡像 code buildpack-deps:jessie-curl /code 的 Dockerfile 如清單 3 所示。
代碼清單 3. buildpack-deps:jessie-curl Dockerfile
“`sh
FROM debian:jessie
RUN apt-get update apt-get install -y –no-install-recommends \
ca-certificates \
curl \
wget \
rm -rf /var/lib/apt/lists/*
“`
這個 Dockerfile 中使用 code apt-get /code 去安裝 code curl /code 和 code wget /code,使這個鏡像能從其他服務器下載軟件。code RUN /code 指令讓 Docker 在運行的實例中執行具體的命令,這個例子中,它會更新所有庫(code apt-get update /code),然后執行 code apt-get install /code 去安裝 code curl /code 和 code wget /code。
code buildpack-deps:jessie-scp /code 的 Dockerfile 如清單 4 所示.
代碼清單 4. buildpack-deps:jessie-scp Dockerfile
“`sh
FROM buildpack-deps:jessie-curl
RUN apt-get update apt-get install -y –no-install-recommends \
bzr \
git \
mercurial \
openssh-client \
subversion \
rm -rf /var/lib/apt/lists/*
“`
這個 [Dockerfile] 會安裝源碼管理工具,例如 Git,Mercurial, 和 Subversion。
Java 的[Dockerfile 會更加復雜些,如清單 5 所示。
代碼清單 5. Java Dockerfile
“`sh
FROM buildpack-deps:jessie-scm
# A few problems with compiling Java from source:
# 1. Oracle. Licensing prevents us from redistributing the official JDK.
# 2. Compiling OpenJDK also requires the JDK to be installed, and it gets
# really hairy.
RUN apt-get update apt-get install -y unzip rm -rf /var/lib/apt/lists/*
RUN echo deb jessie-backports main /etc/apt/sources.list.d/jessie-backports.list
# Default to UTF-8 file.encoding
ENV LANG C.UTF-8
ENV JAVA_VERSION 8u66
ENV JAVA_DEBIAN_VERSION 8u66-b01-1~bpo8+1
# see
# and
ENV CA_CERTIFICATES_JAVA_VERSION 20140324
RUN set -x \
apt-get update \
apt-get install -y \
openjdk-8-jdk= $JAVA_DEBIAN_VERSION ca-certificates-java= $CA_CERTIFICATES_JAVA_VERSION \
rm -rf /var/lib/apt/lists/*
# see CA_CERTIFICATES_JAVA_VERSION notes above
RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure
# If you re reading this and have any feedback on how this image could be
# improved, please open an issue or a pull request so we can discuss it!
“`
簡單來說,這個 Dockerfile 使用了安全參數去執行 code apt-get install -y openjdk-8-jdk /code 去下載安裝 Java,而 ENV 指令配置系統的環境變量。
最后,清單 6 是 Tomcat 的[Dockerfile。
代碼清單 6. Tomcat Dockerfile
“`sh
FROM java:7-jre
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p $CATALINA_HOME
WORKDIR $CATALINA_HOME
# see
RUN gpg –keyserver pool.sks-keyservers.net –recv-keys \
05AB33110949707C93A279E3D3EFE6B686867BA6 \
07E48665A34DCAFAE522E5E6266191C37C037D42 \
47309207D818FFD8DCD3F83F1931D684307A10A5 \
541FBE7D8F78B25E055DDEE13C370389288584E7 \
61B832AC2F1C5A90F0F9B00A1C506407564C17A3 \
79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED \
9BA44C2621385CB966EBA586F72C284D731FABEE \
A27677289986DB50844682F8ACB77FC2E86E29AC \
A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 \
DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 \
F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE \
F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23
ENV TOMCAT_MAJOR 8
ENV TOMCAT_VERSION 8.0.26
ENV TOMCAT_TGZ_URL
RUN set -x \
curl -fSL $TOMCAT_TGZ_URL -o tomcat.tar.gz \
curl -fSL $TOMCAT_TGZ_URL.asc -o tomcat.tar.gz.asc \
gpg –verify tomcat.tar.gz.asc \
tar -xvf tomcat.tar.gz –strip-components=1 \
rm bin/*.bat \
rm tomcat.tar.gz*
EXPOSE 8080
CMD [catalina.sh , run]
“`
嚴格來說,Tomcat 使用了 Java 7 的父級 Dockerfile(默認的最新 Java 版本是 8)。這個 Dockerfile 設置了 code CATALINA_HOME /code 和 code PATH /code 環境變量,然后用 code mkdir /code 命令新建了 code CATALINA_HOME /code 目錄,code WORKDIR /code 指令把當前工作路徑更改為 code CATALINA_HOME /code,然后 code RUN /code 指令執行了同一行中一系列的命令:
1. 下載 Tomcat 壓縮包。
2. 下載文件校驗碼。
3. 驗證下載的文件正確。
4. 解壓 Tomcat 壓縮包。
5. 刪除所有批處理文件(我們是在 Linux 上運行)。
6. 刪除壓縮包文件。
把這些命令寫在同一行,對應 Docker 來說就是一條命令,最后 Docker 會把執行的結果緩存起來,Docker 有個策略是檢測鏡像何時需要重建,以及驗證構建過程中的指令是否正確。當一條指令會使鏡像更改,Docker 會把每個步的結果緩存起來,Docker 能把最上一個正確指令產生的鏡像啟動起來。
code EXPOSE /code 指令會讓 Docker 啟動一個容器時暴露指定的端口,正如之前我們啟動時那樣,我們需要告訴 Docker 哪個物理端口會被映射到容器上(code -p /code 參數),code EXPOSE /code 的作用就是這個定義 Docker 容器端口。最后 Dockerfile 使用 catalina.sh 腳本啟動 Tomcat。
簡單回顧
用 Dockerfile 從頭開始構建 Tomcat 是一個漫長的過程,我們總結一下目前為止的步驟:
1. 安裝 Debian Linux。
2. 安裝 curl 和 wget。
3. 安裝源碼管理工具。
4. 下載并安裝 Java。
5. 下載并安裝 Tomcat。
6. 暴露 Docker 實例的 8080 端口。
7. 用 catalina.sh 啟動 Tomcat。
現在你應該成為一個 Dockerfile 專家了,下一步我們將嘗試構建一個自定義 Docker 鏡像。
部署自定義應用到 Docker
因為本篇指南主要關注點是如何在 Docker 中部署 Java 應用,而不是應用本身,我會構建一個簡單的 Hello World servlet。你可以從 [GitHub] 獲取到這個項目,源碼并無任何特別,只是一個輸出 Hello World! 的 servlet。更加有趣的是相應的[Dockerfile],如清單 7 所示。
代碼清單 7. Hello World servlet 的 Dockerfile
“`sh
FROM tomcat
ADD deploy /usr/local/tomcat/webapps
“`
可能看起來不大一樣,但你應該能理解以上代碼的作用是:
* code FROM tomcat /code 指明這個 Dockerfile 是基于 Tomcat 鏡像構建。
* code ADD deploy /code 告訴 Docker 把本地文件系統中的 deploy 目錄,復制到 Tomcat 鏡像中的 /usr/local/tomcat/webapps 路徑。
在本地使用 maven 命令編譯這個項目:
“`sh
mvn clean install
“`
這樣將會生成一個 war 包,target/helloworld.war,把這個文件復制到項目的 docker/deploy 目錄(你需要先創建好),最后你要使用上邊的 Dockerfile 構建 Docker 鏡像,在項目的 docker 目錄中執行以下命令:
“`sh
docker build -t lygado/docker-tomcat .
“`
這個命令讓 Docker 從當前目錄(用點號. 表示)構建一個新的鏡像,并用 code -t /code 打上標簽 code lygado/docker-tomcat /code,這個例子中,lygado 是我的 DockerHub 用戶名,docker-image 是鏡像名稱(你需要替換成你自己的用戶名)。查看是否構建成功你可以執行以下命令:
“`sh
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
lygado/docker-tomcat latest ccb455fabad9 42 seconds ago 849.5 MB
“`
最后,你可以用以下命令加載這個鏡像:
“`sh
docker run -d -p 8080:8080 lygado/docker-tomcat
“`
這個實例啟動之后,你可以用以下 URL 訪問(請把 URL 中的 IP 替換成你虛擬機的 IP):
“`sh
http://192.168.99.100:8080/helloworld/hello
“`
還是那樣,你可以用容器的 ID 來終止這個實例。
Docker push
一旦你構建并測試過了你的 Docker 鏡像,你可以把這個鏡像推送到你 DockerHub 的賬號中:
“`sh
docker push lygado/docker-tomcat
“`
這樣,你的鏡像就能被全世界訪問到了,當然,為了隱私起見,你也可以推送到私有的 Docker 倉庫。
下面,我們將把 Docker 集成到應用的構建過程,目標是在構建應用完成后,會產出一個包含應用的 Docker 鏡像。
把 Docker 集成到 Maven 構建過程
在前邊的部分,我們創建了一個自定義的 Dockerfile,并把 WAR 包部署到它里邊。這樣意味著把 WAR 包從項目的 target 目錄,復制到 code docker/deploy /code 目錄下,并且從命令行中運行 docker。這并沒花多少功夫,但如果你需要頻繁的改動并測試代碼,你會發現這個過程很煩瑣。而且,如果你需要在一個 CI 服務器上構建應用,并產出一個 Docker 鏡像,那你需要弄明白怎樣把 Docker 和 CI 工具整合起來。
現在我們嘗試一種更有效的方法,使用 Maven 和 Maven Docker 插件來構建一個 Docker 鏡像。
我的用例有這些:
1. 能創建基于 Tomcat 的 Docker 鏡像,以用于部署我的應用。
2. 能在測試中自行構建。
3. 能整合到前期集成測試和后期集成測試。
docker-maven-plugin 能滿足這些需求,而且易于使用和理解。
關于 Maven Docker 插件
這個插件本身有良好的[文檔],這里特別說明一下兩個主要的組件:
1. 在 POM.xml 中配置 Docker 鏡像的構建和運行。
2. 描述哪些文件要包含在鏡像中。
清單 8 是 POM.xml 中插件的配置,定義了鏡像的構建和運行的配置。
代碼清單 8. POM 文件的 build 小節,Docker Maven plug-in 配置
“`xml
build
finalName helloworld /finalName
plugins
plugin
groupId org.jolokia /groupId
artifactId docker-maven-plugin /artifactId
version 0.13.4 /version
configuration
dockerHost tcp://192.168.99.100:2376 /dockerHost certPath /Users/shaines/.docker/machine/machines/default /certPath
useColor true /useColor
images
image
name lygado/tomcat-with-my-app:0.1 /name
alias tomcat /alias
build
from tomcat /from
assembly
mode dir /mode
basedir /usr/local/tomcat/webapps /basedir
descriptor assembly.xml /descriptor
/assembly
/build
run
ports
port 8080:8080 /port
/ports
/run
/image
/images
/configuration
/plugin
/plugins
/build
“`
正如你所見,這個配置相當簡單,包含了以下元素:
Plug-in 定義
code groupId /code , code artifactId /code 和 code version /code 這些信息指定要用哪個插件。
全局設置
code dockerHost /code 和 code certPath /code 元素,定義了 Docker 主機的位置,這些配置會用于啟動容器,以及指定 Docker 證書。Docker 證書的路徑在 code DOCKER_CERT_PATH /code 環境變量中能看到。
鏡像設置
在 code build /code 元素下的所有 code image /code 元素都定義在 code images /code 元素下,每個 code image /code 元素都有鏡像相關的配置,與 code build /code 和 code run /code 的配置一樣,主要的配置是鏡像的名稱,在這個例子中,是我的 DockerHub 用戶名 (code lygado /code),鏡像的名稱(code tomcat-with-my-app /code) 和鏡像的版本號(0.1)。你也可以用 Maven 的屬性來定義這些值。
鏡像構建配置
一般構建鏡像時,我們會使用 code docker build /code 命令,以及一個 Dockerfile 來定義構建過程。Maven Docker 插件也允許你使用 Dockerfile,但在例子中,我們使用一個運行時生成在內存中的 Dockerfile 來構建。因此,我們在 code from /code 元素中定義父級鏡像,這個例子中是 tomcat,然后在 code assembly /code 中作其他配置。
使用 Maven 的 code maven-assembly-plugin /code,可以定義一個項目的輸出內容,指定包含依賴,模塊,文檔,或其他文件到一個獨立分發的包中。code docker-maven-plugin /code 繼承了這個標準,在這個例子中,我們選擇了 code dir /code 模式,也就是說定義在 code src/main/docker/assembly.xml /code 中的文件會被拷貝到 Docker 鏡像中的 basedir 中。其他模式還有 code tar /code,code tgz /code 和 code zip /code。code basedir /code 元素中定義了放置文件的路徑,這個例子中是 Tomcat 的 webapps 目錄。
最后,code descriptor /code 元素指定了 code assembly /code 文件,這個文件位于 code basedir /code 中定義的 code src/main/docker /code 中。以上是一個很簡單的例子,我建議你通讀一下相關文檔,特別地,可以了解 code entrypoint /code 和 code cmd /code 元素,這兩個元素可以指定啟動 Docker 鏡像的命令,code env /code 元素可以指定環境變量,code runCmds /code 元素類似 Dockerfile 中的 code RUN /code 指令,code workdir /code 元素可以指定工作路徑,code volumes /code 元素可以指定要掛載的磁盤卷。簡言之,這個插件實現了所有 Dockerfile 中所需要的語法,所以前面所用到的 Dockerfile 指令都可以在這個插件中使用。
鏡像運行配置
啟動 Docker 鏡像時會用到 code docker run /code 命令,你可以傳一些參數給 Docker。這個例子中,我們要用 code docker run -d -p 8080:8080 lygado/tomcat-with-my-app:0.1 /code 這個命令啟動鏡像,所以我們只需要指定一下端口映射。
run 元素可以讓我們指定所有運行時參數,所以我們指定了把 Docker 容器中的 8080 映射到 Docker 宿主機的 8080。另外,還可以在 run 這節中指定要掛載的卷(使用 code volumes /code),或者要鏈接起來的容器(使用 code links /code)。code docker:start /code 在集成測試中很常用,在 run 小節中,我們可以使用 wait 參數來指定一個時間周期,這樣就可以等待到某個日志輸出,或者一個 URL 可用時,才繼續執行下去,這樣就可以保證在集成測試開始前鏡像已經運行起來了。
加載依賴
code src/main/docker/assembly.xml /code 文件定義了哪些文件需要復制到 Docker 鏡像中,如清單 9 所示:
清單 9. assembly.xml
“`xml
assembly xmlns= http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
xmlns:xsi= http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation= http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd
dependencySets
dependencySet
includes
include com.geekcap.vmturbo:hello-world-servlet-example /include
/includes
outputDirectory . /outputDirectory
outputFileNameMapping helloworld.war /outputFileNameMapping
/dependencySet
/dependencySets
/assembly
“`
在清單 9 中,我們可以看到包含 code hello-world-servlet-example /code 在內的一個依賴集合,以及復制的目標路徑,code outputDirectory /code 這個路徑是相對于前面提到的 code basedir /code 的,也就是 Tomcat 的 webapps 目錄。
這個插件有以下幾個 Maven targets:
1. docker:build: 構建鏡像
2. docker:start: 啟動鏡像
3. docker:stop: 停止鏡像
4. docker:push: 把鏡像推送到鏡像倉庫,如 DockerHub
5. docker:remove: 本地刪除鏡像
6. docker:logs: 輸出容器日志
構建鏡像
你可以從 [GitHub] 中獲取源碼,然后用下邊的命令構建:
“`sh
mvn clean install
“`
用以下命令構建 Docker 鏡像:
“`sh
mvn clean package docker:build
“`
一旦鏡像構建成功,你可以在 code docker images /code 的返回結果中看到:
“`sh
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
lygado/tomcat-with-my-app 0.1 1d49e6924d19 16 minutes ago 347.7 MB
“`
可以用以下命令啟動鏡像:
“`sh
mvn docker:start
“`
現在可以在 code docker ps /code 中看到已經啟動了,然后可以通過以下 URL 訪問:
“`sh
http://192.168.99.100:8080/helloworld/hello
“`
最后,可以用以下命令停止容器:
“`sh
mvn docker:stop
“`
Docker 是一種使進程虛擬化的容器技術,它提供了一系列 Docker 客戶端命令來調用 Docker 守護進程。在 Linux 上,Docker 守護進程可以直接運行于 Linux 操作系統,但是在 Windows 和 Mac 上,需要有一個 Linux 虛擬機來運行 Docker 守護進程。Docker 鏡像包含了一個輕量級的操作系統,還額外包含了應用運行的依賴庫。Docker 鏡像由 Dockerfile 定義,可以在 Dockerfile 中包含一系列配置鏡像的指令。
看完上述內容,你們掌握如何用 Docker 重新定義 Java 虛擬化部署的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注丸趣 TV 行業資訊頻道,感謝各位的閱讀!