如何开发容器化的Java程序

作者: 谈飞 | 来源:发表于2019-08-24 22:47 被阅读37次

    参考资料:https://github.com/docker/labs/tree/master/developer-tools/java/

    如果读者朋友阅读了我之前的文章《Docker入门详解》,那么大家应该对Docker已经有了一个基本的了解,也理解了容器化程序的概念,以及容器化为软件行业特别是云计算领域的软件行业带来的便利性。接下来,我将为大家介绍如何使用Docker来开发容器化的Java程序。

    创建基于JDK 8的Docker镜像

    这里笔者将介绍如何创建基于JDK 8的Docker镜像。首先使用下面的maven命令创建一个java项目。

    mvn archetype:generate -DgroupId=org.examples.java -DartifactId=helloworld -DinteractiveMode=false
    

    上述maven命令会在helloworld目录中创建一个最简单的java程序。

    我们使用mvn package来进行编译。

    cd helloworld
    mvn package
    

    编译成功后,会在target目录下生成打包好的jar文件。

    接下来使用java命令来运行这个程序。

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

    读者会看到该程序输出一行字符串:

    Hello World!
    

    但是,如果想要让这个程序在其他用户的电脑上正常的运行,用户还需要在电脑上安装一个JRE。假如是更加复杂的程序,可能还需要其他的依赖软件支持才能正常工作。借助docker,则可以把这些依赖的软件都打包到docker镜像中,这样用户想使用这个软件时,只需要下载这个镜像就可以运行了,不再需要安装其他的依赖软件。

    下面我们就把这个helloworld程序打包到docker镜像中。我们准备让java程序在基于JDK 8的环境下运行,因此基础镜像需要包含JDK 8。这里我们使用的基础镜像是openjdk:8。运行下面的命令可以启动一个openjdk:8的容器:

    docker container run -it openjdk:8
    

    -it 表示以交互模式启动容器,因此你会看到类似下面的输出:

    root@2e1c05978452:/#
    

    这时可以在交互模式下输入命令java -version来查看jdk 的版本信息:

    openjdk version "1.8.0_222"
    OpenJDK Runtime Environment (build 1.8.0_222-b10)
    OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
    

    输入exit命令可以停止openjdk:8容器。

    如果我们以openjdk:8为基础镜像来创建一个新的镜像,那么需要在之前的helloworld目录,即我们的java项目所在的目录下创建一个文本文件,命名为Dockerfile。其内容如下:

    FROM openjdk:8
    COPY target/helloworld-1.0-SNAPSHOT.jar /usr/src/helloworld-1.0-SNAPSHOT.jar
    CMD java -cp /usr/src/helloworld-1.0-SNAPSHOT.jar org.examples.java.App
    

    第一行,FROM openjdk:8 的作用是以openjdk:8作为基础镜像。因此新创建的镜像将包含openjdk:8的所有功能。

    第二行,COPY target/helloworld-1.0-SNAPSHOT.jar /usr/src/helloworld-1.0-SNAPSHOT.jar,它的作用是进行一个文件拷贝。拷贝哪个文件呢,就是我们使用mvn package命令编译java程序后所生成的jar文件。具体来说,就是把当前主机上的jar文件target/helloworld-1.0-SNAPSHOT.jar,拷贝到docker镜像的目录/usr/src/中。

    第三行,CMD java -cp /usr/src/helloworld-1.0-SNAPSHOT.jar org.examples.java.App,作用是当启动一个容器来运行镜像时,执行命令java -cp /usr/src/helloworld-1.0-SNAPSHOT.jar org.examples.java.App来启动这个java程序。

    有了Dockerfile,就可以使用下面的命令来创建镜像了:

    docker image build -t hello-java .
    

    不要忘了上述命令末尾的小数点,它表示当前目录,这样docker就会在当前目录中查找Dockerfile文件,并根据Dockerfile的描述来创建镜像。-t hello-java表示把新的镜像命名为hello-java。

    上述命令会得到类似下面的输出信息:

    Sending build context to Docker daemon  50.69kB
    Step 1/3 : FROM openjdk:8
     ---> 27da2af61908
    Step 2/3 : COPY target/helloworld-1.0-SNAPSHOT.jar /usr/src/helloworld-1.0-SNAPSHOT.jar
     ---> cb30cc329413
    Step 3/3 : CMD java -cp /usr/src/helloworld-1.0-SNAPSHOT.jar org.examples.java.App
     ---> Running in 9b85e8fbd443
    Removing intermediate container 9b85e8fbd443
     ---> df751388fd1b
    Successfully built df751388fd1b
    Successfully tagged hello-java:latest
    

    想要运行这个镜像也很简单,只需要执行下面的命令:

    docker container run hello-java
    

    容器运行后会输出字符串:

    Hello World!
    

    使用Docker Maven Plugin

    maven不但可以编译java程序,也可以用来创建docker镜像。利用docker-maven-plugin插件可以在编译java程序的同时自动创建镜像,这样省去了创建Dockerfile并输入docker命令行的步骤,显得更加方便。

    为此,需要修改java项目中的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</artifactId>
      <packaging>jar</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>helloworld</name>
      <url>http://maven.apache.org</url>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    
      <profiles>
        <profile>
          <id>docker</id>
          <build>
            <plugins>
              <plugin>
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>0.31.0</version>
                <configuration>
                  <images>
                    <image>
                      <name>hello-java</name>
                      <build>
                        <from>openjdk:8</from>
                        <assembly>
                          <descriptorRef>artifact</descriptorRef>
                        </assembly>
                        <cmd>java -cp maven/${project.name}-${project.version}.jar org.examples.java.App</cmd>
                      </build>
                      <run>
                        <wait>
                          <log>Hello World!</log>
                        </wait>
                      </run>
                    </image>
                  </images>
                </configuration>
                <executions>
                  <execution>
                    <id>docker:build</id>
                    <phase>package</phase>
                    <goals>
                      <goal>build</goal>
                    </goals>
                  </execution>
                  <execution>
                    <id>docker:start</id>
                    <phase>install</phase>
                    <goals>
                      <goal>run</goal>
                      <goal>logs</goal>
                    </goals>
                  </execution>
                </executions>
              </plugin>
            </plugins>
          </build>
        </profile>
      </profiles>
    </project>
    

    关于maven-docker-plugin,截止笔者发布此博客时,其最新的版本是0.31.0。读者可以在maven repository网站上查询这个plugin的发布版本号。在插件的github主页有更加详细的使用说明。

    在我们的示例中用到了这么几个maven goals:

    • docker:build 编译并创建docker镜像。

    要使这个maven goal 命令正常工作,需要在pom的<configuration>中添加<build>元素来定义如何创建docker镜像。示例中定义的<build>元素如下:

    <build>
        <from>openjdk:8</from>
        <assembly>
            <descriptorRef>artifact</descriptorRef>
        </assembly>
        <cmd>java -cp maven/${project.name}-${project.version}.jar org.examples.java.App</cmd>
    </build>
    

    <from>表示基础镜像为openjdk:8。<assembly>元素用来定义镜像需要包含的文件,即我们需要把哪些文件拷贝到新创建的镜像之中。一个简单的配置就是使用<descriptorRef>artifact</descriptorRef>,这样就可以把编译后的jar文件拷贝到镜像中。<cmd>元素用来定义容器运行时执行的命令行,本例执行一个java命令来运行我们的java程序。

    • docker:start 和 docker:run 创建并运行docker容器。

    <configuration>中添加<run>元素,可以对容器的运行方式进行配置。示例中定义的<run>元素如下:

    <run>
        <wait>
            <log>Hello World!</log>
        </wait>
    </run>
    

    这里的<wait>用来阻塞maven命令的执行,直到检测到容器输出日志"Hello World!"时,再恢复maven命令的执行过程。

    • docker:logs 把容器输出的日志打印到当前的命令行窗口中。

    接下来,使用命令mvn -Pdocker package编译java项目,并且创建docker镜像。这个命令会输出类似下面的日志:

    [INFO] Copying files to C:\Users\i062893\OneDrive\code\docker-workspace\docker-java\helloworld\target\docker\hello-java\build\maven
    [INFO] Building tar: C:\Users\i062893\OneDrive\code\docker-workspace\docker-java\helloworld\target\docker\hello-java\tmp\docker-build.tar
    [INFO] DOCKER> [hello-java:latest]: Created docker-build.tar in 155 milliseconds
    [INFO] DOCKER> [hello-java:latest]: Built image sha256:cf28b
    [INFO] DOCKER> [hello-java:latest]: Removed old image sha256:df751
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

    注意这三行以"DOCKER>"开头的日志了吗?这说明hello-java:latest镜像已经创建成功了。使用docker image ls来看一下本机的镜像列表。在列表中可以发现hello-java镜像:

    REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
    hello-java                latest              cf28b4b83a26        2 minutes ago       488MB
    

    我们也可以使用mvn -Pdocker install命令来编译java项目,创建镜像,并启动一个容器来运行这个镜像。该命令输出的日志类似下面这样:

    [INFO] --- docker-maven-plugin:0.31.0:run (docker:start) @ helloworld ---
    [INFO] DOCKER> [hello-java:latest]: Start container f8deb08d2692
    [INFO] DOCKER> Pattern 'Hello World!' matched for container f8deb08d2692
    f8deb0> Hello World!
    [INFO] DOCKER> [hello-java:latest]: Waited on log out 'Hello World!' 674 ms
    

    读者可以看到,hello-java容器已经启动,并且输出了字符串Hello World!。此时按下Ctrl + C可以停止这个容器。

    至此,我们的容器化JAVA程序开发指南就告一段落了。在最后,留给大家两个思考题。

    1. 笔者介绍了两种打包docker镜像的方法,一种是使用Dockerfile,另一种是使用maven插件maven-docker-plugin。这两种方法都会把新创建的镜像保存在本机的镜像仓库中。但是怎样才能把本机的镜像发布出去,供其他用户安装和使用呢?

    2. 笔者创建了一个JAVA程序,并且将其打包到基于JDK 8的docker镜像中了,这意味着我们的JAVA程序不能包含JDK 9及以上版本的新功能。那么如果想要使用JDK 12的新API和新的java语法,那该怎么做呢?

    读者朋友如果知道答案的话,欢迎大家在博客下方留言。笔者将在下一篇博客中为大家揭晓答案。谢谢大家。

    相关文章

      网友评论

        本文标题:如何开发容器化的Java程序

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