我们都知道基于maven的Java工程,使用mvn package
命令即可实现构建,Docker化只需将构建结果COPY到镜像中就完成了,如果是Spring Boot工程,只需要拷贝一个jar进去。
但是,如果我们的工程是一个Java和C++混合的工程,Docker化就没有那么简单了。
C++构建的so文件不像Java构建的jar,可以直接复制到镜像中(只要镜像中的Java版本和构建环境中的版本一致),so与操作系统环境上的很多因素有关,我们不敢贸然将实现构建好的so复制到镜像中。这就要求我们要将C++的源代码复制到镜像中,然后在镜像中构建,最后删除源代码。
随之而来的问题是,C++的源代码如何使用mvn
命令一起打包?为什么会有这样的疑问呢?因为在持续集成/持续发布的流程中,从我们提交代码到工程以Docker镜像的形式上线,需要经历构建平台(阿里巴巴内部使用的是AONE)。我们通常用一条'`mvn'命令告诉构建平台构建Java,但面对混合工程,我们也许要写一堆脚本了。这样不优雅,这应该是我们的直觉,所以才有上述的问题。这个问题的答案是使用maven的assembly插件。
我们通过在工程根目录下的pom.xml中配置该插件:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<finalName>${YOUR_MICRO_SERVICE_NAME}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</plugin>
相应的assembly.xml如下:
<assembly>
<id>assembly</id>
<includeBaseDirectory>false</includeBaseDirectory>
<formats>
<format>dir</format>
</formats>
<fileSets>
<fileSet>
<includes>
<include>${project.basedir}/${YOUR_JAVA_SRC_DIRECTORY}/target/${YOUR_JAR}</include>
</includes>
<outputDirectory>/</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.basedir}/${YOUR_CPP_SRC_DIRECTORY}</directory>
<outputDirectory>/${CPP_DIRCTORY}</outputDirectory>
</fileSet>
</fileSets>
</assembly>
这样配置后,告诉构建平台执行mvn clean install assembly:assembly -DskipTests
这个构建命令,然后到target/${YOUR_ARTIFACTID}
这个构建输出目录下面tgz包即可。
这里,我踩过一个小坑。最初只在assembly.xml中定义了一个<fileSet>
,然后通过定义多条<include>
来打包jar和C++源代码。因为C++源代码只有一层目录:
<include>${project.basedir}/${YOUR_CPP_SRC_DIRECTORY}/*</include>
但没多久,C++的开发者根据业务逻辑,增加了一层目录,这还好,于是这个定义变成:
<include>${project.basedir}/${YOUR_CPP_SRC_DIRECTORY}/*</include>
<include>${project.basedir}/${YOUR_CPP_SRC_DIRECTORY}/*/*</include>
又没过几天,C++的源代码出现了4层目录,我石化了……所以才有了第二个<fileSet>
。其实最初就应该这样设计,只是那个时间点觉得一个<fileSet>
更优雅:(。
有了tgz包,我们的注意力就可以集中到Dockerfile的编写上了。依次执行如下步骤即可:
- 解压缩tgz
- 通过软连接处理32位和64位lib、so.xxx
- 进入C++源代码目录,执行
make
- 复制构建好的so到
/usr/lib64
- 删除tgz包,删除C++源代码目录
- 复制jar到指定位置
- 复制各种脚本,配置各种日志路径,修改目录或者文件的权限
- 使用
VOLUME
挂载日志等路径,以免镜像的新容器启动后丢失日志等文件 - 使用
EXPOSE
开放Docker容器的端口 - 使用
ENV
设定服务所需的环境变量 - 使用
ENTRYPOINT
执行启动脚本