用Docker重新定义Java虚拟化部署实战案例

jopen 8年前

 

上周希云和大家分享了《用Docker重新定义Java虚拟化部署(基础篇)》,估计有些小伙伴早已按耐不住着急的心情了吧。今天希云就和大家分享在docker里部署java应用的实战案例。

Dockerfiles

Dockerfile包含了一系列指令,告诉Docker如何去构建一个镜像,它指定了镜像的基点,以及配置镜像的每个细节。以下是一个Dockerfile示例,是CentOS镜像的Dockerfile。

代码清单1. CentOS Dockerfile

sh
FROM scratch
MAINTAINER The CentOS Project <cloud-ops@centos.org> - ami_creator
ADD centos-7-20150616_1752-docker.tar.xz /

Volumes for systemd

VOLUME ["/run", "/tmp"]

Environment for systemd

ENV container=docker

For systemd usage this changes to /usr/sbin/init

Keeping it as /bin/bash for compatibility with previous

CMD ["/bin/bash"]

大部分内容是注释,主要有四句命令:

1. <code>FROM scratch</code>:所有Dockerfile都要从一个基础镜像继承,在这个例子中,CentOS镜像是继承 于"scratch"镜像,这个镜像是所有镜像的根。这个配置是固定的,表明了这个是Docker的根镜像之一。

2. <code>MAINTAINER ...</code>:<code>MAINTAINER</code>指令指明了镜像的所有者,这个例子中所有者是CentOS Project。

3. <code>ADD centos...tar.xz</code>:<code>ADD</code>指令告诉Docker把指定文件 上传到镜像中,如果文件是压缩过的,会把它解压到指定路径。这个例子中,Docker会上传一个CentOS操作系统的Gzip包,并解压到系统的根目 录。

4. <code>CMD ["/bin/bash"]</code>:最后,<code>CMD</code>指令告诉Docker要执行什 么命令,这个例子中,最后会进入Bourne Again Shell (bash)终端。

现在你知道Docker大概是长什么样子了,接下来再看看Tomcat官方的Dockerfile,图2说明了这个文件的架构。

用Docker重新定义Java虚拟化部署实战案例

这个架构未必如你想象中那么简单,但我们接下来会慢慢学习它,其实它是非常有逻辑的。上边已经提过所有Dockerfile的根 是<code>scratch</code>,接下来指定的是<code>debian:jessie< /code>镜像,这个官方镜像是基于标准镜像构建的,Docker不需要重复发明轮子,每次都创建一个新镜像了,只要基于一个稳定的镜像来继续构 建新镜像即可,在这个例子中,<code>debian:jessie</code>是一个官方Debian Linux镜像,就像上边的CentOS一样,它只有三行指令。

代码清单 2. debian:jessie Dockerfile

sh
FROM scratch
ADD rootfs.tar.xz /
CMD [&quot;/bin/bash&quot;]

在上图中我们还见到有安装两个额外的镜像,CURL 和 Source Code Management,镜像<code>buildpack-deps:jessie-curl</code>的Dockerfile如清单3所示。

代码清单 3. buildpack-deps:jessie-curl Dockerfile

sh
FROM debian:jessie
RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
ca-certificates \
curl \
wget \
&amp;&amp; rm -rf /var/lib/apt/lists/

这个Dockerfile中使用<code>apt-get</code>去安 装<code>curl</code>和<code>wget</code>,使这个镜像能从其他服务 器下载软件。<code>RUN</code>指令让Docker在运行的实例中执行具体的命令,这个例子中,它会更新所有库 (<code>apt-get update</code>),然后执行<code>apt-get install</code>去安装<code>curl</code> 和<code>wget</code>。

<code>buildpack-deps:jessie-scp</code>的Dockerfile如清单4所示.

代码清单 4. buildpack-deps:jessie-scp Dockerfile

sh
FROM buildpack-deps:jessie-curl
RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends \
bzr \
git \
mercurial \
openssh-client \
subversion \
&amp;&amp; rm -rf /var/lib/apt/lists/

这个 Dockerfile 会安装源码管理工具,例如Git,Mercurial, 和 Subversion。

Java的 Dockerfile 会更加复杂些,如清单5所示。

代码清单 5. Java Dockerfile

sh
FROM buildpack-deps:jessie-scm

A few problems with compiling Java from source:

1. Oracle. Licensing prevents us from redistributing the official JDK.

2. Compiling OpenJDK also requires the JDK to be installed, and it gets

really hairy.

RUN apt-get update &amp;&amp; apt-get install -y unzip &amp;&amp; rm -rf /var/lib/apt/lists/*

RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list

Default to UTF-8 file.encoding

ENV LANG C.UTF-8

ENV JAVA_VERSION 8u66

ENV JAVA_DEBIAN_VERSION 8u66-b01-1~bpo8+1

see https://bugs.debian.org/775775

and https://github.com/docker-libr ... 46872

ENV CA_CERTIFICATES_JAVA_VERSION 20140324

RUN set -x \

&amp;&amp; apt-get update \

&amp;&amp; apt-get install -y \

openjdk-8-jdk=&quot;$JAVA_DEBIAN_VERSION&quot; \

ca-certificates-java=&quot;$CA_CERTIFICATES_JAVA_VERSION&quot; \

&amp;&amp; rm -rf /var/lib/apt/lists/*

see CA_CERTIFICATES_JAVA_VERSION notes above

RUN /var/lib/dpkg/info/ca-certificates-java.postinst configure

If you're reading this and have any feedback on how this image could be

improved, please open an issue or a pull request so we can discuss it!

简单来说,这个Dockerfile使用了安全参数去执行<code>apt-get install -y openjdk-8-jdk</code>去下载安装Java,而ENV指令配置系统的环境变量。

最后,清单6是Tomcat的 Dockerfile

代码清单 6. Tomcat Dockerfile

sh
FROM java:7-jre
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p &quot;$CATALINA_HOME&quot;
WORKDIR $CATALINA_HOME

see https://www.apache.org/dist/tomcat/tomcat-8/KEYS

RUN gpg --keyserver pool.sks-keyservers.net --recv-keys \

05AB33110949707C93A279E3D3EFE6B686867BA6 \

07E48665A34DCAFAE522E5E6266191C37C037D42 \

47309207D818FFD8DCD3F83F1931D684307A10A5 \

541FBE7D8F78B25E055DDEE13C370389288584E7 \

61B832AC2F1C5A90F0F9B00A1C506407564C17A3 \

79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED \

9BA44C2621385CB966EBA586F72C284D731FABEE \

A27677289986DB50844682F8ACB77FC2E86E29AC \

A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 \

DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 \

F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE \

F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23

ENV TOMCAT_MAJOR 8

ENV TOMCAT_VERSION 8.0.26

ENV TOMCAT_TGZ_URL https://www.apache.org/dist/tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz

RUN set -x \

&amp;&amp; curl -fSL &quot;$TOMCAT_TGZ_URL&quot; -o tomcat.tar.gz \

&amp;&amp; curl -fSL &quot;$TOMCAT_TGZ_URL.asc&quot; -o tomcat.tar.gz.asc \

&amp;&amp; gpg --verify tomcat.tar.gz.asc \

&amp;&amp; tar -xvf tomcat.tar.gz --strip-components=1 \

&amp;&amp; rm bin/*.bat \

&amp;&amp; rm tomcat.tar.gz*

EXPOSE 8080

CMD [&quot;catalina.sh&quot;, &quot;run&quot;]

严格来说,Tomcat使用了Java 7的父级Dockerfile(默认的最新Java版本是8)。这个Dockerfile设置 了<code>CATALINA_HOME</code>和<code>PATH</code>环境变 量,然后用<code>mkdir</code>命令新建了<code>CATALINA_HOME< /code>目录,<code>WORKDIR</code>指令把当前工作路径更改 为<code>CATALINA_HOME</code>,然后<code>RUN</code>指令 执行了同一行中一系列的命令:

  1. 下载Tomcat压缩包。
  2. 下载文件校验码。
  3. 验证下载的文件正确。
  4. 解压Tomcat压缩包。
  5. 删除所有批处理文件(我们是在Linux上运行)。
  6. 删除压缩包文件。

把这些命令写在同一行,对应Docker来说就是一条命令,最后Docker会把执行的结果缓存起来,Docker有个策略是检测镜像何时需要重 建,以及验证构建过程中的指令是否正确。当一条指令会使镜像更改,Docker会把每个步的结果缓存起来,Docker能把最上一个正确指令产生的镜像启 动起来。

<code>EXPOSE</code>指令会让Docker启动一个容器时暴露指定的端口,正如之前我们启动时那样, 我们需要告诉Docker哪个物理端口会被映射到容器上(<code>-p</code>参 数),<code>EXPOSE</code>的作用就是这个定义Docker容器端口。最后Dockerfile使用 catalina.sh脚本启动Tomcat。

用Docker重新定义Java虚拟化部署实战案例

简单回顾

用Dockerfile从头开始构建Tomcat是一个漫长的过程,我们总结一下目前为止的步骤:

  1. 安装Debian Linux。
  2. 安装curl和wget。
  3. 安装源码管理工具。
  4. 下载并安装Java。
  5. 下载并安装Tomcat。
  6. 暴露Docker实例的8080端口。
  7. 用catalina.sh启动Tomcat。

现在你应该成为一个Dockerfile专家了,下一步我们将尝试构建一个自定义Docker镜像。

部署自定义应用到Docker

因为本篇指南主要关注点是如何在Docker中部署Java应用,而不是应用本身,我会构建一个简单的Hello World servlet。你可以从 GitHub 获取到这个项目,源码并无任何特别,只是一个输出"Hello World!"的servlet。更加有趣的是相应的 Dockerfile ,如清单7所示。

代码清单 7. Hello World servlet的Dockerfile

sh
FROM tomcat
ADD deploy /usr/local/tomcat/webapps

可能看起来不大一样,但你应该能理解以上代码的作用是:

* <code>FROM tomcat</code>指明这个Dockerfile是基于Tomcat镜像构建。

* <code>ADD deploy </code>告诉Docker把本地文件系统中的"deploy"目录,复制到Tomcat镜像中的 /usr/local/tomcat/webapps路径 。

在本地使用maven命令编译这个项目:

sh
mvn clean install

这样将会生成一个war包,target/helloworld.war,把这个文件复制到项目的docker/deploy目录(你需要先创建好),最后你要使用上边的Dockerfile构建Docker镜像,在项目的docker目录中执行以下命令:

sh
docker build -t lygado/docker-tomcat .

这个命令让Docker从当前目录(用点号.表示)构建一个新的镜像,并用"<code>-t</code>"打上标 签<code>lygado/docker-tomcat</code>,这个例子中,lygado是我的DockerHub用 户名,docker-image是镜像名称(你需要替换成你自己的用户名)。查看是否构建成功你可以执行以下命令:

sh
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
lygado/docker-tomcat latest ccb455fabad9 42 seconds ago 849.5 MB

最后,你可以用以下命令加载这个镜像:

sh
docker run -d -p 8080:8080 lygado/docker-tomcat

这个实例启动之后 ,你可以用以下URL访问(请把URL中的IP替换成你虚拟机的IP):

sh
http://192.168.99.100:8080/helloworld/hello

还是那样,你可以用容器的ID来终止这个实例。

Docker push

一旦你构建并测试过了你的Docker镜像,你可以把这个镜像推送到你DockerHub的账号中:

sh
docker push lygado/docker-tomcat

这样,你的镜像就能被全世界访问到了,当然,为了隐私起见,你也可以推送到私有的Docker仓库。

下面,我们将把Docker集成到应用的构建过程,目标是在构建应用完成后,会产出一个包含应用的Docker镜像。

把Docker集成到Maven构建过程

在前边的部分,我们创建了一个自定义的Dockerfile,并把WAR包部署到它里边。这样意味着把WAR包从项目的target目录,复制 到<code>docker/deploy</code>目录下,并且从命令行中运行docker。这并没花多少功夫,但如果你 需要频繁的改动并测试代码,你会发现这个过程很烦琐。而且,如果你需要在一个CI服务器上构建应用,并产出一个Docker镜像,那你需要弄明白怎样把 Docker和CI工具整合起来。

现在我们尝试一种更有效的方法,使用Maven和Maven Docker插件来构建一个Docker镜像。

我的用例有这些:

1. 能创建基于Tomcat的Docker镜像,以用于部署我的应用。

2. 能在测试中自行构建。

3. 能整合到前期集成测试和后期集成测试。

docker-maven-plugin能满足这些需求,而且易于使用和理解。

关于 Maven Docker插件

这个插件本身有良好的 文档 ,这里特别说明一下两个主要的组件:

  1. 在POM.xml中配置Docker镜像的构建和运行。
  2. 描述哪些文件要包含在镜像中。

清单8是POM.xml中插件的配置,定义了镜像的构建和运行的配置。

代码清单 8. POM 文件的 build 小节, Docker Maven plug-in 配置

xml
&lt;build>
&lt;finalName>helloworld&lt;/finalName>
&lt;plugins>
&lt;plugin>
&lt;groupId>org.jolokia&lt;/groupId>
&lt;artifactId>docker-maven-plugin&lt;/artifactId>
&lt;version>0.13.4&lt;/version>
&lt;configuration>
&lt;dockerHost>tcp://192.168.99.100:2376&lt;/dockerHost> &lt;certPath>/Users/shaines/.docker/machine/machines/default&lt;/certPath>
&lt;useColor>true&lt;/useColor>
&lt;images>
&lt;image>
&lt;name>lygado/tomcat-with-my-app:0.1&lt;/name>
&lt;alias>tomcat&lt;/alias>
&lt;build>
&lt;from>tomcat&lt;/from>
&lt;assembly>
&lt;mode>dir&lt;/mode
&lt;basedir>/usr/local/tomcat/webapps&lt;/basedir
&lt;descriptor>assembly.xml&lt;/descriptor>
&lt;/assembly>
&lt;/build>
&lt;run>
&lt;ports>
&lt;port>8080:8080&lt;/port>
&lt;/ports>
&lt;/run>
&lt;/image>
&lt;/images>
&lt;/configuration>
&lt;/plugin>
&lt;/plugins>
&lt;/build>

正如你所见,这个配置相当简单,包含了以下元素:

Plug-in定义

<code>groupId</code>, <code>artifactId</code> 和 <code>version</code> 这些信息指定要用哪个插件。

全局设置

<code>dockerHost</code>和<code>certPath< /code>元素,定义了Docker主机的位置,这些配置会用于启动容器,以及指定Docker证书。Docker证书的路径 在<code>DOCKER_CERT_PATH</code>环境变量中能看到。

镜像设置

在<code>build</code>元素下的所有<code>image</code>元 素都定义在<code>images</code>元素下,每个<code>image</code> 元素都有镜像相关的配置,与<code>build</code>和<code>run</code> 的配置一样,主要的配置是镜像的名称,在这个例子中,是我的DockerHub用户名 (<code>lygado</code>),镜像的名称(<code>tomcat-with-my- app</code>)和镜像的版本号(0.1)。你也可以用Maven的属性来定义这些值。

镜像构建配置

一般构建镜像时,我们会使用<code>docker build</code>命令,以及一个Dockerfile来定义构建过程。Maven Docker插件也允许你使用Dockerfile,但在例子中,我们使用一个运行时生成在内存中的Dockerfile来构建。因此,我们 在<code>from</code>元素中定义父级镜像,这个例子中是tomcat,然后 在<code>assembly</code>中作其他配置。

使用Maven的<code>maven-assembly-plugin</code>,可以定义一个项目的输出内 容,指定包含依赖,模块,文档,或其他文件到一个独立分发的包中。<code>docker-maven- plugin</code>继承了这个标准,在这个例子中,我们选择了<code>dir</code>模式,也就 是说定义在<code>src/main/docker/assembly.xml</code>中的文件会被拷贝到 Docker镜像中的basedir中。其他模式还 有<code>tar</code>,<code>tgz</code> 和<code>zip</code>。<code>basedir</code>元素中定义了放置文件 的路径,这个例子中是Tomcat的webapps目录。

最后,<code>descriptor</code>元素指定了<code>assembly< /code>文件,这个文件位于<code>basedir</code>中定义的<code>src /main/docker</code>中。以上是一个很简单的例子,我建议你通读一下相关文档,特别地,可以了 解<code>entrypoint</code>和<code>cmd</code>元素,这两个元 素可以指定启动Docker镜像的命令,<code>env</code>元素可以指定环境变 量,<code>runCmds</code>元素类似Dockerfile中的<code>RUN< /code>指令,<code>workdir</code>元素可以指定工作路 径,<code>volumes</code>元素可以指定要挂载的磁盘卷。简言之,这个插件实现了所有Dockerfile中 所需要的语法,所以前面所用到的Dockerfile指令都可以在这个插件中使用。

镜像运行配置

启动Docker镜像时会用到<code>docker run</code>命令,你可以传一些参数给Docker。这个例子中,我们要用<code>docker run -d -p 8080:8080 lygado/tomcat-with-my-app:0.1</code>这个命令启动镜像,所以我们只需要指定一下端口映射。

run元素可以让我们指定所有运行时参数,所以我们指定了把Docker容器中的8080映射到Docker宿主机的8080。另外,还可以在 run这节中指定要挂载的卷(使用<code>volumes</code>),或者要链接起来的容器(使 用<code>links</code>)。<code>docker:start</code>在集 成测试中很常用,在run小节中,我们可以使用wait参数来指定一个时间周期,这样就可以等待到某个日志输出,或者一个URL可用时,才继续执行下去, 这样就可以保证在集成测试开始前镜像已经运行起来了。

加载依赖

<code>src/main/docker/assembly.xml</code>文件定义了哪些文件需要复制到Docker镜像中,如清单9所示:

清单 9. assembly.xml

xml
&lt;assembly xmlns=&quot;http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2&quot;
xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
xsi:schemaLocation=&quot;http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd&quot;&gt;
&lt;dependencySets>
&lt;dependencySet>
&lt;includes>
&lt;include>com.geekcap.vmturbo:hello-world-servlet-example&lt;/include>
&lt;/includes>
&lt;outputDirectory>.&lt;/outputDirectory>
&lt;outputFileNameMapping>helloworld.war&lt;/outputFileNameMapping>
&lt;/dependencySet>
&lt;/dependencySets>
&lt;/assembly>

在清单 9 中,我们可以看到包含<code>hello-world-servlet-example</code>在内的一个依赖集合, 以及复制的目标路径,<code>outputDirectory</code>这个路径是相对于前面提到 的<code>basedir</code>的,也就是Tomcat的webapps目录。

这个插件有以下几个Maven targets:

1. docker:build: 构建镜像

2. docker:start: 启动镜像

3. docker:stop: 停止镜像

4. docker:push: 把镜像推送到镜像仓库,如DockerHub

5. docker:remove: 本地删除镜像

6. docker:logs: 输出容器日志

构建镜像

你可以从 GitHub 中获取源码,然后用下边的命令构建:

sh
mvn clean install

用以下命令构建Docker镜像:

sh
mvn clean package docker:build

一旦镜像构建成功,你可以在<code>docker images</code>的返回结果中看到:

sh
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
lygado/tomcat-with-my-app 0.1 1d49e6924d19 16 minutes ago 347.7 MB

可以用以下命令启动镜像:

sh
mvn docker:start

现在可以在<code>docker ps</code>中看到已经启动了,然后可以通过以下URL访问:

sh
http://192.168.99.100:8080/helloworld/hello

最后,可以用以下命令停止容器:

sh
mvn docker:stop

总结

Docker是一种使进程虚拟化的容器技术,它提供了一系列Docker客户端命令来调用Docker守护进程。在Linux上,Docker守 护进程可以直接运行于Linux操作系统,但是在Windows和Mac上,需要有一个Linux虚拟机来运行Docker守护进程。Docker镜像包 含了一个轻量级的操作系统,还额外包含了应用运行的依赖库。Docker镜像由Dockerfile定义,可以在Dockerfile中包含一系列配置镜 像的指令。

在这个开源Java项目指南中,我介绍了Docker的基础,讲解了CentOS、Java、Tomcat等镜像的Dockerfile细节,并 演示了如何用Tomcat镜像来构建新的镜像。最后,我们使用docker-maven-plugin来把Docker集成到Maven的构建过程中。通 过这样,使得测试更加简单了,还可以把构建过程配置在CI服务器上部署到生产环境。

本文中的示例应用非常简单,但是涉及的构建步骤同样可以用在更复杂的企业级应用中。好好享受Docker带给我们的乐趣吧。

感谢您阅读此文!本周四我们将继续分享docker技术文章,请保持关注!

如了解更多docker相关知识,请观看培训视频: https://csphere.cn/training

如需要docker相关产品,请访问希云官网首页: https://csphere.cn