把Java装进Docker容器

244 阅读4分钟
  • 概述

“容器,云原生时代无处不在的基础设施,除了使用他可以方便的启动依赖的组件,以及他带来的运行一致性的便捷性,我们平时的开发中经常会发现在项目工程里有容器化的一些脚本文件,一般常见的会有一个dockerfile和一个或者多个sh脚本。作为云原生时代的猿,掌握容器相关的技术也将是研发必备能力” ---- 在 云原生中的容器一文中,曾以此结尾。本篇继续科普,我们就来聊聊这项所谓的云原生时代程序猿必备的技能,神雕先以简单的java工程为例,通过编译、打包成docker镜像、并运行容器进行阐述,然后基于对hello world案例的学习来理解线上一个真实案例。

  • HelloWorld

言归正传,先看第一个例子:HelloWorld ! 为了让容器能够多运行一小段时间,示例中每五秒钟程序会打印一次“HelloWorld”,总共打印十次。

如果按传统套路来,那么接下来我们会在IDE里运行或者命令行运行

javac sd/docker/demo/HelloWorld.java 
java sd/docker/demo/HelloWorld

为什么要提一下上面“传统套路”的javac和java两条命令?因为最简单的用Docker部署&运行java程序的方法,就是把上述两条命令写到Dockerfile里,如此而已!So,第一个HelloWorld的的Dockerfile长这个样子:

FROM java:8WORKDIR /app
COPY  . /app
ENV PATH=$PATH:$JAVA_HOME/bin
ENV JRE_HOME=$JAVA_HOME/jre
ENV CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib
CMD ["/bin/bash","-c","javac sd/docker/demo/HelloWorld.java 
      && java sd/docker/demo/HelloWorld"]

写好了这个Dockerfile,我们接下来可以build成一个镜像了,docker build命令:

docker build --rm -f "Dockerfile" -t sd.docker/java-helloworld .

开始build

查看生成的镜像:docker images

然后,我们可以用docker run来运行刚刚打好的镜像:

终于,把一个简单的java程序通过docker部署运行起来了,我们需要去查看一下运行的情况。通过docker ps查看刚刚运行的镜像id为b09eb42063dd(见上图第二条命令),先进容器看看

docker exec -it b09eb42063dd bash   #进入容器
ps -ef|grep java  #查看java进程

意料之中,1号进程运行着Dockerfile里CMD命令,也就是我们的HelloWorld。同时我们用docker logs命令查看下容器运行日志,毫不意外,程序正在每五秒打印一行 HelloWorld,当打印完十次之后,程序运行结束,容器也就退出了,此时用docker ps查看已经看不到容器了(不用担心,我们可以用docker ps -a看到退出的容器,需要的话还以用docker start来重新运行)。

  • Dockerfile

两条基础的命令(javac,java),和一个简单的Dockerfile文件,就可以把Java程序装进容器里了。javac和java暂且不表,我们还是来简单的科普下Dockerfile

>> 先引用下官方文档描述:

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

>> 再解释下相关的命令

严格来说,上面Dockerfile并不规范,因为它过于简单,所以神雕没有写注释(#开头)、也没有加MAINTAINER信息。但也正因为简单直接,更加易于理解,也足以达到把java装进容器的目的。随着学习的深入,关于Dockerfile后续文章会再详细分享,本篇只解释一下刚刚用到了五个命令:FROM, WORKDIR, COPY, ENV, CMD,详见注释:

#docker file 第一行必须是 FROM 指令,指明所基于的基础镜像名
#此处我们基于java 8 来构建
FROM java:8

#WORKDIR 用来指定后续命令运行的工作目录,此处我们把程序运行在/app下
#如果回头看前文容器截图发现exec进容器时就在/app目录下
WORKDIR /app

#COPY 命令是把本地主机目录或文件复制到容器中
#此处我们把sd.docker.demo.HelloWorld.java复制到/app下
COPY  . /app

#接下来用ENC设置环境变量,相信javaer们都很熟悉啦
ENV PATH=$PATH:$JAVA_HOME/bin
ENV JRE_HOME=$JAVA_HOME/jre
ENV CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib

#CMD命令是容器启动时候会执行的命令
#当然,如果容器启动指定了其他命令,则该命令不会被执行
#所以,此处用RUN也许更合适
CMD ["/bin/bash","-c","javac sd/docker/demo/HelloWorld.java         
     && java sd/docker/demo/HelloWorld"]

>> 最后为不过瘾的朋友附上Dockerfile最佳实践官方文档

docs.docker.com/develop/dev…

  • 再看HelloWorld

记得几年前神雕刚刚转去搞云原生PaaS平台时,原小组同学问我云原生是在搞啥,能不能给个不绕歪歪直接明了的描述。我当时说:从咱们应用研发角度看,就是容器化发布应用系统,容器调度平台做编排,应用系统自动具备弹性伸缩能力。这个回答当时是“挨批”的,我记得山鸡同学听完之后让我给个怎么把java程序发布成docker容器的例子,于是我就抛出了程序猿万能的“hello world”。回顾当时场景,我突然发现上面的例子是被认为有瑕疵的,因为没有人会用javac和java去发布系统,一般都是把编译后的jar作为载体,无论你是基于mvn还是gradle都不受影响。所以再看HelloWorld,我想适当调整下,我们以mvn工程来编译成jar,然后把jar添加到镜像,而不需要COPY当前目录中(java源文件)到镜像里。

>>添加mvn工程pom,工程目录

>>修改Dockerfile

FROM java:8

WORKDIR /app

#COPY . /app

ADD target/demo-1.0.SNAPSHOT.jar app.jar

ENV PATH=PATH:PATH:JAVA_HOME/bin

ENV JRE_HOME=$JAVA_HOME/jre

ENV CLASSPATH=.:JAVA_HOME/lib:JAVA\_HOME/lib:JRE_HOME/lib

#CMD ["/bin/bash","-c","javac sd/docker/demo/HelloWorld.java && java #sd/docker/demo/HelloWorld"]

CMD ["/bin/bash", "-c", "exec java -jar app.jar sd.docker.demo.HelloWorld"]

Note: 无需COPY, 直接ADD jar文件,CMD直接写运行jar的命令

>> 运行

先mvn clean install之后,通过docker build打包成镜像java-helloworld2

然后和前文类似的命令运行、查看等操作,效果是一样的

  • 一个真实的案例

最后我们来看一个比较真实的项目案例,一个简单的springboot工程,具体的业务

功能就不介绍了,隐去内部私有镜像中心地址,Dockerfile如下:

相信通过上面hello world demo的介绍,应该非常容易理解这个真实案例的Dockerfile

- FROM 一个内部镜像中心的base镜像

- 以 /home/admin/app作为工作目录(WORKDIR)

- 添加(ADD) setenv.sh文件

- 添加(ADD) 打包好的jar到工作目录的app.jar

- EXPORSE(新命令,暴露端口)80,因为我们这个是内网使用的web程序

- ENV 设置环境变量

- CMD 执行命令三个,mkdir、source并执行setenv.sh、java -jar

附setenv.sh

  • 小结

终于把java装进了docker容器里了,后续我们可以逐步深入学习和理解容器