随着容器化的进行,测试环境和线上环境开始尝试容器化发布。因此需要将现有的maven工程进行容器化,容器化的好处不言而喻,但是针对原先没有解耦的应用(容器配置和代码耦合在一起),制作镜像还是有些成本的。本文主要记录对于webx和springboot应用的镜像制作。

springboot镜像制作

springboot制作官方有介绍,最主要的就是在pom.xml中增加docker maven plugin,然后配置读取最终生成的jar即可。

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <configuration>
        <imageName>aegis-package-switch:1.0</imageName>
        <dockerDirectory>${project.basedir}/docker</dockerDirectory>
        <resources>
            <resource>
                <targetPath>/</targetPath>
                <directory>bin</directory>
                <include>run.sh</include>
            </resource>
            <resource>
                <targetPath>/</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${project.build.finalName}.jar</include>
            </resource>
        </resources>
    </configuration>
</plugin>

docker-maven-plugin主要配置有:

  • 镜像名称
  • dockerfile文件
  • 需要添加到镜像中的资源

docker-maven-plugin插件本身可以通过xml配置设置类似dockerfile中的简单操作(如添加文件等),但是为了统一和可读性,还是建议统一使用dockerfile。上面的示例中dockerfile位于项目目录的docker子目录中,目录结构类似:

.
├── bin
│   └── run.sh
├── docker
│   └── Dockerfile
├── pom.xml
├── src
│   ├── main
│   └── test

resources标签中包含需要添加到镜像中的文件,实际执行时插件会将它们复制到target/docker目录中,供dockerfile使用,否则dockerfile中将无法引用到文件。

然后就是最重要的dockerfile,springboot应用启动比较方便,依赖也很少,只要使用包含java的基础镜像即可。

FROM j8:1.0
RUN mkdir /work
WORKDIR /work
ADD run.sh /work/run.sh
ADD package.switch-1.0-SNAPSHOT.jar app.jar
CMD ["/bin/bash","./run.sh"]

dockerfile中需要添加的文件,只要在插件中配置resource后,直接使用即可。这里的启动脚本也可以省略,直接java -jar即可。

webx应用镜像制作

webx应用在插件配置上完全相同,主要是因为没有解耦,dockerfile会相对比较复杂。

FROM jetty-j7-httpd:1.0
RUN useradd -m admin
RUN mkdir -p /home/admin/output/logs && mkdir /home/admin/web-deploy && mkdir /home/admin/agent-deploy
ADD gaea.env.deploy.tar.gz /home/admin/web-deploy
RUN AGENT_VERSION=`cat /home/admin/web-deploy/conf/agent.version` && mkdir /home/admin/agent-deploy/$AGENT_VERSION \
    && cp /home/admin/web-deploy/agent/gaea.env.agent.zip /home/admin/agent-deploy/$AGENT_VERSION/
RUN chown -R admin:admin /home/admin
WORKDIR /home/admin/web-deploy/bin
USER admin
EXPOSE 8080
CMD ["./startws.sh", "-d"]

注意,在打包前需要确保打出来的包已经通过auto-config插件完成autoconfig操作。默认pom中的asscembly插件会将最终的包打包成tgz包,因此直接ADD到镜像中即可,docker制作镜像的时候,ADD指令会自动将压缩包解压缩(这里有个小问题,ADD指令不会解压缩zip包,如果使用ZIP包,需要使用unzip命令解压缩)。

另外一个需要注意的问题就是启动脚本。之前应用的启动脚本将java容器(如tomcat、jetty等)启动后,就会自动退出。但是docker容器启动时,第一个执行的进程PID为1,如果该进程退出,会导致整个容器退出。因此这里在原先的脚本中增加了一个-d参数,配置脚本修改成如果有-d参数,则一直循环等待(sleep)。

if [[ $1 == "-d" ]]; then
  while true; do sleep 1000; done
fi

整合git插件

上面的docker插件配置会有一个问题,每次提交的镜像版本都相同。这样可能会导致两个问题:

推送到hub之后版本相同,可能会覆盖之前的版本,导致回滚困难; 无法追踪当前镜像的代码版本 为了解决上面两个问题,可以在pom中增加git-commit-id-plugin,获取当前版本号,并且将版本号添加到镜像版本中。

<plugin>
    <groupId>pl.project13.maven</groupId>
    <artifactId>git-commit-id-plugin</artifactId>
    <version>2.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>revision</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <dateFormatTimeZone>${user.timezone}</dateFormatTimeZone>
        <verbose>true</verbose>
    </configuration>
</plugin>
<plugin>
	<groupId>com.spotify</groupId>
	<artifactId>docker-maven-plugin</artifactId>
	<configuration>
		<imageName>master:1.0.0-${git.commit.id.abbrev}</imageName>
        <dockerDirectory>${project.basedir}/docker</dockerDirectory>
		<resources>
			<resource>
				<targetPath>/</targetPath>
				<directory>${project.parent.build.directory}</directory>
                <include>${project.artifactId}.tar.gz</include>
            </resource>
        </resources>
    </configuration>
</plugin>

在配置了git-commit-id-plugin之后,镜像名字可以直接使用git.commit.id.abbrev变量。该变量会取git commit id中的前n位(默认是7位),这样可以确保大版本不变的时候,每个镜像的小版本都不相同。这里会有个问题,git commit id是sha的哈希值,没有可读性,也无法知道commit的顺序。如果用于发布,建议和git tag关联,通过git-commit-id-plugin插件获取当前tag作为版本后缀。

推送和私有hub

由于内部有多个不同的私有hub,而docker又需要将hub地址放到镜像名称中,如果需要将一个镜像推送到不同的hub,需要动态修改插件的配置。这里可以使用maven的变量机制。首先我们在顶级pom中定义一个变量docker.repo,可以在其中设置一个默认值,然后插件中使用该变量:

<properties>
    <docker.repo>a.b.com:5000</docker.repo>
</properties>
...
<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <configuration>
        <imageName>master:1.0-${git.commit.id.abbrev}</imageName>
        <dockerDirectory>${project.basedir}/docker</dockerDirectory>
        <resources>
            <resource>
                <targetPath>/</targetPath>
                <directory>bin</directory>
                <include>run.sh</include>
            </resource>
            <resource>
                <targetPath>/</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${project.build.finalName}.jar</include>
            </resource>
        </resources>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>build</goal>
            </goals>
        </execution>
        <execution>
            <id>tag-image</id>
            <phase>package</phase>
            <goals>
                <goal>tag</goal>
            </goals>
            <configuration>
                <image>master:1.0-${git.commit.id.abbrev}</image>
                <newName>${docker.repo}/master:1.0-${git.commit.id.abbrev}</newName>
                <forceTags>true</forceTags>
            </configuration>
        </execution>
        <execution>
            <id>push-image</id>
            <phase>install</phase>
            <goals>
                <goal>push</goal>
            </goals>
            <configuration>
                <imageName>${docker.repo}/master:1.0-${git.commit.id.abbrev}</imageName>
            </configuration>
        </execution>
    </executions>
</plugin>

这里配置比较长,主要分为两个部分,首先是将制作镜像(build)、添加tag、推送镜像(push)三个操作分别挂到maven的package和install两个步骤中。

build不再赘述,tag就是用来从刚才的镜像设置一个独立的标签,使用该功能,就可以实现镜像“重命名”工作。新的镜像名字需要包含hub地址,既使用上面定义的docker.repo变量。注意如果本地已经存在tag后的镜像名称,必须加上true标签,使其忽略这个错误强制进行tag操作。

推送操作绑定在install goal中。该插件的官方示例中,推送到仓库被绑定到了deploy goal中,但是由于我们不希望将web工程的包deploy到maven的仓库,所以绑定到install上。

如果需要切换仓库,只需要在执行maven操作的时候指定docker.repo值即可覆盖当前的值:

install -DskipTests -Ddocker.repo=a.c.com:5000