如何开发容器化的Java程序

上一篇 / 目录 / 下一篇

参考资料: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语法,那该怎么做呢?

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

上一篇 / 目录 / 下一篇

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容