如何利用Docker虚拟化JDK12的Java程序

作者: 谈飞 | 来源:发表于2019-09-27 22:44 被阅读0次

    上一篇 / 目录 / 下一篇

    笔者在《如何开发容器化的Java程序》中创建了一个Java程序,并且将其打包到基于JDK8的docker镜像中。然而在文章末尾有两个问题还没有解答:

    1. 怎样才能把本机的镜像发布出去,供其他用户安装和使用呢?
    2. 如何使用Docker虚拟化一个基于JDK12的Java程序呢?

    下面我们就一起来看一下具体的操作步骤吧。

    1 修改Dockerfile

    如果Java程序想运行在JDK12的JVM上,那么最简单的方法就是修改Dockerfile,将基础镜像从openjdk:8 修改为openjdk:12 (或openjdk:latest) 即可。openjdk:12的镜像包含了一个JDK12的JVM,基于此为基础镜像创建的所有镜像自然也就包含了JDK12。当然,所有openjdk提供的docker镜像版本都可以在其github文档中查询到,网址是:https://github.com/docker-library/docs/blob/master/openjdk/README.md
    读者们也可以从中选择一种作为基础镜像。比如,我们访问上述链接,可以看到很多openjdk的镜像版本:

    openjdk docker image的github网页

    往下滑动页面,可以找到JDK12的版本,如下图所示。可以看出,openjdk:12, openjdk:jdk, openjdk:latest都是相同的镜像,均包含JDK12.

    包含JDK12的版本

    不过,笔者还将介绍另外一种创建Java镜像的方法,那就是自己动手拷贝一个JDK 12到镜像中。

    2 拷贝JDK12到镜像中

    到OpenJDK 的官网下载OpenJDK 12 for Linux 版本,网址是: http://jdk.java.net/12/

    OpenJDK下载页面

    下载的文件名为:openjdk-12.0.2_linux-x64_bin.tar.gz

    接下来创建目录docker-jdk12用来保存Java代码:

    mkdir docker-jdk12
    cd docker-jdk12
    

    将刚才下载的openjdk 12 拷贝到docker-jdk12目录中。

    然后创建一个Dockerfile文件,文件名为jdk-12-debian-slim.Dockerfile,文件包含下面的内容:

    # 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"]
    

    这里我们使用的基础镜像是debian:stable-slim。debian是一个免费的Linux操作系统,stable-slim版本是debian的稳定版,并且镜像的容量进行了精简。在Docker Hub网站上可以看到,debian:stable-slim的镜像大小只有25.84MB:

    Docker Hub上显示的debian:stable-slim

    第二行脚本ADD openjdk-12.0.2_linux-x64_bin.tar.gz /opt,使用ADD命令把当前目录中的openjdk 12 压缩包解压到镜像的/opt目录下。网上有很多文章介绍ADD和COPY命令的区别,其实笔者总结了一条最简单的区别:COPY正如我们在windows CMD中的copy指令一样,功能是文件的拷贝;而ADD则会把压缩包的内容解压到镜像中。

    接下来的脚本则是设置了JAVA_HOMEPATH环境变量。熟悉Java开发的读者对此应该都比较熟悉。

    最后,则是运行命令jshelljshell命令是JDK9新增加的一个功能,它提供了交互式的Java代码运行环境。

    使用下面的命令创建这个镜像,为新的镜像命名为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看一下新创建的镜像:

    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
    jdk-12-debian-slim        latest              8866af1a6786        16 minutes ago      404MB
    

    然后可以使用下面的命令来运行这个镜像。其中-m=200M参数的作用是给容器分配200MB的内存空间。-it表示以交互模式启动容器。--rm表示当容器停止时,自动将该容器删除。

    docker container run -m=200M -it --rm jdk-12-debian-slim
    

    容器启动后,你会看到如下的输出:

    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>
    

    此时我们已经启动了一个jdk-12-debian-slim镜像容器,并且进入了jshell交互程序。我们输入一行Java语句,看看能否执行成功:

    jshell> System.out.println("Hello World!");
    Hello World!
    
    jshell>
    

    如果想退出容器,可以直接按Ctrl + D

    3 Hello World程序

    下面来创建一个简单的Hello World程序。

    首先新建一个目录helloworld-java-12存放我们的程序:

    mkdir helloworld-java-12
    cd helloworld-java-12
    

    然后在该目录中新建pom.xml文件,内容如下:

    <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文件,文件内容如下:

    package org.examples.java;
    public class App
    {
      public static void main( String[] args )
      {
        System.out.println( "Hello World!" );
      }
    }
    

    最后回到helloworld-java-12目录,输入mvn package进行编译。

    mvn package
    

    编译成功后在target目录下会生成helloworld-java-12-1.0-SNAPSHOT.jar

    输入下面的命令启动helloworld程序:

    java -classpath target\helloworld-java-12-1.0-SNAPSHOT.jar org.examples.java.App
    

    如果运行成功,你会看到日志输出:

    Hello World!
    

    4 创建Hello World的Docker镜像

    编译好了上述Java程序之后,我们就可以把它打包成一个Docker镜像了。为此,这里需要对之前的jdk-12-debian-slim.Dockerfile内容稍作修改,步骤如下:

    把openjdk-12的压缩包openjdk-12.0.2_linux-x64_bin.tar.gz拷贝到helloworld-java-12目录下。

    helloworld-java-12目录下新建一个名为Dockerfile的文本文件,内容如下:

    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
    

    这个Dockerfile内容和jdk-12-debian-slim.Dockerfile内容差不多,这里不再赘述。

    输入下面的命令创建镜像:

    docker image build -t helloworld-jdk-12 .
    

    最后启动这个镜像容器试试看:

    docker container run --rm helloworld-jdk-12
    

    你会看到如下的日志输出:

    Hello World!
    

    至此,我们成功的创建了一个基于JDK12 的Java程序镜像。不过稍等,我们离成功还差最后一步。因为我们还需要进一步裁剪镜像的大小。

    5 进一步裁剪镜像的大小

    如果我们通过docker image ls命令查看一下刚创建的镜像,可以看出这个镜像文件的大小达到了惊人的404MB

    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
    helloworld-jdk-12         latest              9a766a690d43        2 minutes ago       404MB
    

    虽然我们使用了精简过的Linux镜像debian:stable-slim作为基础镜像,使得大小比直接用openjdk:12要小一些,但是对于一个简单的Hello World程序,占用404MB的空间仍然是不可接收的一件事。

    为此 ,我们可以对JVM runtime进行裁剪。JVM runtime包含了很多的模块,但是我们的Hello World程序仅仅使用了java.base这一个模块,为什么不裁剪出一个轻量级的JVM呢?这样我们的docker镜像的文件尺寸会小很多。

    首先打开powershell,进入我们之前创建的helloworld-java-12目录。还记得吗,在该目录下我们存放了一个linux平台的openjdk-12: 文件名是openjdk-12.0.2_linux-x64_bin.tar.gz

    openjdk-12的压缩包

    它是一个tar.gz压缩包,使用tar -xf .\openjdk-12.0.2_linux-x64_bin.tar.gz命令将其解压缩到当前目录,会生成一个新的目录jdk-12.0.2:

    解压后的目录jdk-12.0.2

    读者也可以使用其他的解压缩工具,比如Windows中最常用的WinRAR或7-zip。

    下面使用jlink命令创建一个裁剪过的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内容如下:

    # 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,而是经过裁剪后的轻量级JVM。

    使用docker命令创建新的镜像。

    docker image build -t helloworld-jdk-12-base .
    

    最后执行docker image ls看看新创建的镜像信息

    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
    

    效果很明显。经过裁剪,helloworld-jdk-12-base镜像的大小比helloworld-jdk-12要小很多,只有106MB,是原始大小的26%。

    使用docker container run --rm helloworld-jdk-12-base,仍然可以运行这个新的镜像容器。

    运行Hello World容器

    6 导出本地的镜像

    如果按照上面的步骤操作,那么已经在本地的docker镜像仓库中创建了一个镜像,名称为helloworld-jdk-12-base:latest。假如想把这个镜像发布给其他的用户,有两种办法可以实现。

    6.1 发布到Docker Hub

    Docker Hub是Docker官方维护的一个镜像仓库。每个人都可以免费申请一个Docker Hub账号,这样就可以把我们创建的镜像发布到Docker Hub账号所对应的镜像仓库中。任何其他用户,只要能连上Docker Hub,就可以下载我们发布过的镜像并安装运行镜像中的程序了。

    方法是先登录到Docker Hub:

    docker login
    

    根据提示输入用户名和密码。例如:

    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
    

    然后输入下面的命令给镜像制作一个新的标签:

    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
    

    完整的标签格式是:[Docker ID]/[repository]:[tag]

    其中,[Docker ID]/[repositroy] 表明镜像会上传到你ID中的某个镜像仓库中。

    使用docker push来上传镜像。

    docker push [docker-id]/helloworld-jdk-12-base
    

    这里[docker-id]需要替换成你自己的docker id。例如:

    docker push feiandytan/helloworld-jdk-12-base
    

    最后,在用户的机器上,只要输入docker container run --rm feiandytan/helloworld-jdk-12-base就可以自动从Docker Hub下载我们的镜像文件了。

    6.2 保存为tar文件

    读者也可以使用下面的命令把本机镜像仓库中的镜像另存为一个tar文件,然后把这个文件放在网站上给用户下载:

    docker image save helloworld-jdk-12-base > helloworld-jdk-12-base.tar
    

    用户下载了tar文件后,使用下面的命令可以把它导入到本地镜像仓库中:

    docker image load -i helloworld-jdk-12-base.tar
    

    上一篇 / 目录 / 下一篇

    相关文章

      网友评论

        本文标题:如何利用Docker虚拟化JDK12的Java程序

        本文链接:https://www.haomeiwen.com/subject/odcluctx.html