共计 10874 个字符,预计需要花费 28 分钟才能阅读完成。
如何用 Docker 重新定义 Java 虚拟化部署,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
今天就和大家分享在 docker 里部署 java 应用的实战案例。
Dockerfiles
Dockerfile 包含了一系列指令,告诉 Docker 如何去构建一个镜像,它指定了镜像的基点,以及配置镜像的每个细节。以下是一个 Dockerfile 示例,是 CentOS 镜像的 Dockerfile。
代码清单 1. CentOS Dockerfile
“`sh
FROM scratch
MAINTAINER The CentOS Project – 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 说明了这个文件的架构。
这个架构未必如你想象中那么简单,但我们接下来会慢慢学习它,其实它是非常有逻辑的。上边已经提过所有 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 [/bin/bash]
“`
在上图中我们还见到有安装两个额外的镜像,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 apt-get install -y –no-install-recommends \
ca-certificates \
curl \
wget \
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 apt-get install -y –no-install-recommends \
bzr \
git \
mercurial \
openssh-client \
subversion \
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 apt-get install -y unzip rm -rf /var/lib/apt/lists/*
RUN echo deb 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
# and
ENV CA_CERTIFICATES_JAVA_VERSION 20140324
RUN set -x \
apt-get update \
apt-get install -y \
openjdk-8-jdk= $JAVA_DEBIAN_VERSION ca-certificates-java= $CA_CERTIFICATES_JAVA_VERSION \
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 $CATALINA_HOME
WORKDIR $CATALINA_HOME
# see
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
RUN set -x \
curl -fSL $TOMCAT_TGZ_URL -o tomcat.tar.gz \
curl -fSL $TOMCAT_TGZ_URL.asc -o tomcat.tar.gz.asc \
gpg –verify tomcat.tar.gz.asc \
tar -xvf tomcat.tar.gz –strip-components=1 \
rm bin/*.bat \
rm tomcat.tar.gz*
EXPOSE 8080
CMD [catalina.sh , run]
“`
严格来说,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。
简单回顾
用 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
build
finalName helloworld /finalName
plugins
plugin
groupId org.jolokia /groupId
artifactId docker-maven-plugin /artifactId
version 0.13.4 /version
configuration
dockerHost tcp://192.168.99.100:2376 /dockerHost certPath /Users/shaines/.docker/machine/machines/default /certPath
useColor true /useColor
images
image
name lygado/tomcat-with-my-app:0.1 /name
alias tomcat /alias
build
from tomcat /from
assembly
mode dir /mode
basedir /usr/local/tomcat/webapps /basedir
descriptor assembly.xml /descriptor
/assembly
/build
run
ports
port 8080:8080 /port
/ports
/run
/image
/images
/configuration
/plugin
/plugins
/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
assembly xmlns= http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2
xmlns:xsi= http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation= http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd
dependencySets
dependencySet
includes
include com.geekcap.vmturbo:hello-world-servlet-example /include
/includes
outputDirectory . /outputDirectory
outputFileNameMapping helloworld.war /outputFileNameMapping
/dependencySet
/dependencySets
/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 中包含一系列配置镜像的指令。
看完上述内容,你们掌握如何用 Docker 重新定义 Java 虚拟化部署的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注丸趣 TV 行业资讯频道,感谢各位的阅读!