筆者在《如何開發(fā)容器化的Java程序》中創(chuàng)建了一個(gè)Java程序,并且將其打包到基于JDK8的docker鏡像中。然而在文章末尾有兩個(gè)問題還沒有解答:
- 怎樣才能把本機(jī)的鏡像發(fā)布出去,供其他用戶安裝和使用呢?
- 如何使用Docker虛擬化一個(gè)基于JDK12的Java程序呢?
下面我們就一起來看一下具體的操作步驟吧。
1 修改Dockerfile
如果Java程序想運(yùn)行在JDK12的JVM上,那么最簡單的方法就是修改Dockerfile,將基礎(chǔ)鏡像從openjdk:8 修改為openjdk:12 (或openjdk:latest) 即可。openjdk:12的鏡像包含了一個(gè)JDK12的JVM,基于此為基礎(chǔ)鏡像創(chuàng)建的所有鏡像自然也就包含了JDK12。當(dāng)然,所有openjdk提供的docker鏡像版本都可以在其github文檔中查詢到,網(wǎng)址是:https://github.com/docker-library/docs/blob/master/openjdk/README.md
讀者們也可以從中選擇一種作為基礎(chǔ)鏡像。比如,我們訪問上述鏈接,可以看到很多openjdk的鏡像版本:
往下滑動(dòng)頁面,可以找到JDK12的版本,如下圖所示。可以看出,openjdk:12, openjdk:jdk, openjdk:latest都是相同的鏡像,均包含JDK12.
不過,筆者還將介紹另外一種創(chuàng)建Java鏡像的方法,那就是自己動(dòng)手拷貝一個(gè)JDK 12到鏡像中。
2 拷貝JDK12到鏡像中
到OpenJDK 的官網(wǎng)下載OpenJDK 12 for Linux 版本,網(wǎng)址是: http://jdk.java.net/12/
下載的文件名為:openjdk-12.0.2_linux-x64_bin.tar.gz
接下來創(chuàng)建目錄docker-jdk12用來保存Java代碼:
mkdir docker-jdk12
cd docker-jdk12
將剛才下載的openjdk 12 拷貝到docker-jdk12目錄中。
然后創(chuàng)建一個(gè)Dockerfile文件,文件名為jdk-12-debian-slim.Dockerfile
,文件包含下面的內(nèi)容:
# A JDK 12 with Debian slim
FROM debian:stable-slim
# Add openjdk12 to folder /opt
ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt
# Set up env variables
ENV JAVA_HOME=/opt/jdk-12.0.2
ENV PATH=$PATH:$JAVA_HOME/bin
CMD ["jshell"]
這里我們使用的基礎(chǔ)鏡像是debian:stable-slim
。debian是一個(gè)免費(fèi)的Linux操作系統(tǒng),stable-slim版本是debian的穩(wěn)定版,并且鏡像的容量進(jìn)行了精簡。在Docker Hub網(wǎng)站上可以看到,debian:stable-slim的鏡像大小只有25.84MB:
第二行腳本ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt
,使用ADD命令把當(dāng)前目錄中的openjdk 12 壓縮包解壓到鏡像的/opt
目錄下。網(wǎng)上有很多文章介紹ADD和COPY命令的區(qū)別,其實(shí)筆者總結(jié)了一條最簡單的區(qū)別:COPY正如我們在windows CMD中的copy指令一樣,功能是文件的拷貝;而ADD則會(huì)把壓縮包的內(nèi)容解壓到鏡像中。
接下來的腳本則是設(shè)置了JAVA_HOME
和PATH
環(huán)境變量。熟悉Java開發(fā)的讀者對(duì)此應(yīng)該都比較熟悉。
最后,則是運(yùn)行命令jshell
。jshell
命令是JDK9新增加的一個(gè)功能,它提供了交互式的Java代碼運(yùn)行環(huán)境。
使用下面的命令創(chuàng)建這個(gè)鏡像,為新的鏡像命名為jdk-12-debian-slim
:
docker image build -t jdk-12-debian-slim -f jdk-12-debian-slim.Dockerfile .
如果成功,則可以看到類似下面這樣的日志輸出:
Sending build context to Docker daemon 198.2MB
Step 1/5 : FROM debian:stable-slim
---> a4eb8e3265f8
Step 2/5 : ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt
---> Using cache
---> ba5157a8c5e5
Step 3/5 : ENV JAVA_HOME=/opt/jdk-12.0.2
---> Using cache
---> d2f758a76f4a
Step 4/5 : ENV PATH=$PATH:$JAVA_HOME/bin
---> Using cache
---> 7cc00f2a4013
Step 5/5 : CMD ["jshell"]
---> Using cache
---> 754ef0950e57
Successfully built 754ef0950e57
Successfully tagged jdk-12-debian-slim:latest
使用docker image ls
看一下新創(chuàng)建的鏡像:
REPOSITORY TAG IMAGE ID CREATED SIZE
jdk-12-debian-slim latest 8866af1a6786 16 minutes ago 404MB
然后可以使用下面的命令來運(yùn)行這個(gè)鏡像。其中-m=200M
參數(shù)的作用是給容器分配200MB的內(nèi)存空間。-it
表示以交互模式啟動(dòng)容器。--rm
表示當(dāng)容器停止時(shí),自動(dòng)將該容器刪除。
docker container run -m=200M -it --rm jdk-12-debian-slim
容器啟動(dòng)后,你會(huì)看到如下的輸出:
Sep 07, 2019 9:00:57 AM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
| Welcome to JShell -- Version 12.0.2
| For an introduction type: /help intro
jshell>
此時(shí)我們已經(jīng)啟動(dòng)了一個(gè)jdk-12-debian-slim
鏡像容器,并且進(jìn)入了jshell
交互程序。我們輸入一行Java語句,看看能否執(zhí)行成功:
jshell> System.out.println("Hello World!");
Hello World!
jshell>
如果想退出容器,可以直接按Ctrl + D
。
3 Hello World程序
下面來創(chuàng)建一個(gè)簡單的Hello World程序。
首先新建一個(gè)目錄helloworld-java-12
存放我們的程序:
mkdir helloworld-java-12
cd helloworld-java-12
然后在該目錄中新建pom.xml
文件,內(nèi)容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.examples.java</groupId>
<artifactId>helloworld-java-12</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>helloworld-java-12</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.target>12</maven.compiler.target>
<maven.compiler.source>12</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
接下來新建目錄src\main\java\org\examples\java
mkdir src\main\java\org\examples\java
在src/main/java/org/examples/java
目錄中新建App.java
文件,文件內(nèi)容如下:
package org.examples.java;
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
最后回到helloworld-java-12
目錄,輸入mvn package
進(jìn)行編譯。
mvn package
編譯成功后在target
目錄下會(huì)生成helloworld-java-12-1.0-SNAPSHOT.jar
輸入下面的命令啟動(dòng)helloworld程序:
java -classpath target\helloworld-java-12-1.0-SNAPSHOT.jar org.examples.java.App
如果運(yùn)行成功,你會(huì)看到日志輸出:
Hello World!
4 創(chuàng)建Hello World的Docker鏡像
編譯好了上述Java程序之后,我們就可以把它打包成一個(gè)Docker鏡像了。為此,這里需要對(duì)之前的jdk-12-debian-slim.Dockerfile
內(nèi)容稍作修改,步驟如下:
把openjdk-12的壓縮包openjdk-12.0.2_linux-x64_bin.tar.gz
拷貝到helloworld-java-12
目錄下。
在helloworld-java-12
目錄下新建一個(gè)名為Dockerfile
的文本文件,內(nèi)容如下:
FROM debian:stable-slim
ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt
COPY target/helloworld-java-12-1.0-SNAPSHOT.jar /opt/helloworld/helloworld-java-12-1.0-SNAPSHOT.jar
ENV JAVA_HOME=/opt/jdk-12.0.2
ENV PATH=$PATH:$JAVA_HOME/bin
CMD java -cp /opt/helloworld/helloworld-java-12-1.0-SNAPSHOT.jar org.examples.java.App
這個(gè)Dockerfile
內(nèi)容和jdk-12-debian-slim.Dockerfile
內(nèi)容差不多,這里不再贅述。
輸入下面的命令創(chuàng)建鏡像:
docker image build -t helloworld-jdk-12 .
最后啟動(dòng)這個(gè)鏡像容器試試看:
docker container run --rm helloworld-jdk-12
你會(huì)看到如下的日志輸出:
Hello World!
至此,我們成功的創(chuàng)建了一個(gè)基于JDK12 的Java程序鏡像。不過稍等,我們離成功還差最后一步。因?yàn)槲覀冞€需要進(jìn)一步裁剪鏡像的大小。
5 進(jìn)一步裁剪鏡像的大小
如果我們通過docker image ls
命令查看一下剛創(chuàng)建的鏡像,可以看出這個(gè)鏡像文件的大小達(dá)到了驚人的404MB
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld-jdk-12 latest 9a766a690d43 2 minutes ago 404MB
雖然我們使用了精簡過的Linux鏡像debian:stable-slim
作為基礎(chǔ)鏡像,使得大小比直接用openjdk:12要小一些,但是對(duì)于一個(gè)簡單的Hello World程序,占用404MB的空間仍然是不可接收的一件事。
為此 ,我們可以對(duì)JVM runtime進(jìn)行裁剪。JVM runtime包含了很多的模塊,但是我們的Hello World程序僅僅使用了java.base
這一個(gè)模塊,為什么不裁剪出一個(gè)輕量級(jí)的JVM呢?這樣我們的docker鏡像的文件尺寸會(huì)小很多。
首先打開powershell,進(jìn)入我們之前創(chuàng)建的helloworld-java-12
目錄。還記得嗎,在該目錄下我們存放了一個(gè)linux平臺(tái)的openjdk-12: 文件名是openjdk-12.0.2_linux-x64_bin.tar.gz
。
它是一個(gè)tar.gz壓縮包,使用tar -xf .\openjdk-12.0.2_linux-x64_bin.tar.gz
命令將其解壓縮到當(dāng)前目錄,會(huì)生成一個(gè)新的目錄jdk-12.0.2
:
讀者也可以使用其他的解壓縮工具,比如Windows中最常用的WinRAR或7-zip。
下面使用jlink
命令創(chuàng)建一個(gè)裁剪過的JVM,使其只包含java.base
模塊。生成的JVM將保存到目錄target\openjdk-12-base_linux-x64
中。
jlink --compress 2 --no-header-files --verbose --module-path .\jdk-12.0.2\jmods --output .\target\openjdk-12-base_linux-x64 --add-modules java.base
然后修改Dockerfile,新的Dockerfile內(nèi)容如下:
# Hello world application with custom Java runtime
FROM debian:stable-slim
COPY target/openjdk-12-base_linux-x64 /opt/jdk-12.0.2
COPY target/helloworld-java-12-1.0-SNAPSHOT.jar /opt/helloworld/helloworld-java-12-1.0-SNAPSHOT.jar
ENV JAVA_HOME=/opt/jdk-12.0.2
ENV PATH=$PATH:$JAVA_HOME/bin
CMD java -cp /opt/helloworld/helloworld-java-12-1.0-SNAPSHOT.jar org.examples.java.App
注意,和之前的Dockerfile相比,我們只是修改了一行命令,把ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt
替換成了COPY target/openjdk-12-base_linux-x64 /opt/jdk-12.0.2
, 這樣,我們拷貝到鏡像中的JVM不再是完整的JVM,而是經(jīng)過裁剪后的輕量級(jí)JVM。
使用docker命令創(chuàng)建新的鏡像。
docker image build -t helloworld-jdk-12-base .
最后執(zhí)行docker image ls
看看新創(chuàng)建的鏡像信息
REPOSITORY TAG IMAGE ID CREATED SIZE
helloworld-jdk-12-base latest 6bd8f688f404 36 seconds ago 106MB
helloworld-jdk-12 latest 9a766a690d43 3 days ago 404MB
效果很明顯。經(jīng)過裁剪,helloworld-jdk-12-base
鏡像的大小比helloworld-jdk-12
要小很多,只有106MB,是原始大小的26%。
使用docker container run --rm helloworld-jdk-12-base
,仍然可以運(yùn)行這個(gè)新的鏡像容器。
6 導(dǎo)出本地的鏡像
如果按照上面的步驟操作,那么已經(jīng)在本地的docker鏡像倉庫中創(chuàng)建了一個(gè)鏡像,名稱為helloworld-jdk-12-base:latest
。假如想把這個(gè)鏡像發(fā)布給其他的用戶,有兩種辦法可以實(shí)現(xiàn)。
6.1 發(fā)布到Docker Hub
Docker Hub是Docker官方維護(hù)的一個(gè)鏡像倉庫。每個(gè)人都可以免費(fèi)申請一個(gè)Docker Hub賬號(hào),這樣就可以把我們創(chuàng)建的鏡像發(fā)布到Docker Hub賬號(hào)所對(duì)應(yīng)的鏡像倉庫中。任何其他用戶,只要能連上Docker Hub,就可以下載我們發(fā)布過的鏡像并安裝運(yùn)行鏡像中的程序了。
方法是先登錄到Docker Hub:
docker login
根據(jù)提示輸入用戶名和密碼。例如:
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: feiandytan
Password:
Login Succeeded
然后輸入下面的命令給鏡像制作一個(gè)新的標(biāo)簽:
docker tag helloworld-jdk-12-base [docker-id]/helloworld-jdk-12-base
讀者們需要把[docker-id]
替換成你自己的Docker ID,例如:
docker tag helloworld-jdk-12-base feiandytan/helloworld-jdk-12-base
完整的標(biāo)簽格式是:[Docker ID]/[repository]:[tag]
其中,[Docker ID]/[repositroy]
表明鏡像會(huì)上傳到你ID中的某個(gè)鏡像倉庫中。
使用docker push
來上傳鏡像。
docker push [docker-id]/helloworld-jdk-12-base
這里[docker-id]
需要替換成你自己的docker id。例如:
docker push feiandytan/helloworld-jdk-12-base
最后,在用戶的機(jī)器上,只要輸入docker container run --rm feiandytan/helloworld-jdk-12-base
就可以自動(dòng)從Docker Hub下載我們的鏡像文件了。
6.2 保存為tar文件
讀者也可以使用下面的命令把本機(jī)鏡像倉庫中的鏡像另存為一個(gè)tar文件,然后把這個(gè)文件放在網(wǎng)站上給用戶下載:
docker image save helloworld-jdk-12-base > helloworld-jdk-12-base.tar
用戶下載了tar文件后,使用下面的命令可以把它導(dǎo)入到本地鏡像倉庫中:
docker image load -i helloworld-jdk-12-base.tar