文档章节

持续集成案例学习:Docker、Java与Maven

Michaelyn
 Michaelyn
发布于 2017/09/11 20:41
字数 2715
阅读 14
收藏 0
点赞 0
评论 0

对于使用Java技术栈的企业,Maven往往是其持续集成的核心工具,在当前的Docker化的运动中,要如何把Docker镜像的构建也加入到传统的 Maven构建为基础的持续集成流程中呢?Alooma公司在本文中分享了他们使用Maven对Docker镜像构建进行持续集成的经验。

在Alooma,我们非常非常非常喜爱Docker.

这是真的。 我们试着尽可能的在Docker容器内部运行我们的应用。 虽然在容器中打包模块有大量的好处,我们在这里并不是要说服你用Docker。相反的是,我们将仅仅假定你像我们一样热爱Docker。

接下来,让我们谈谈Alooma是如何在生产环境使用Docker精简开发流程和快速push代码的。
 

概述

Docker允许你把你的基础架构当作代码一样来对待。这个代码就是你的Dockerfile。

像其它代码一样,我们想要使用一个紧密的改变->提交->构建->测试的周期(一个完整的持续集成解决方案)。为了实现这个目标,我们需要构建一个流畅的DevOps流水线。

让我们把目标分解为更加详细的需求:

  • 在版本控制系统中管理Dockerfile
  • 在CI服务器上为每个commit构建Docker镜像
  • 上传构件并打标签(这个构件要能够简单的部署)

我们的工作流

我们的DevOps流水线围绕GitHub、Jenkins和Maven构建。下面是它的工作流程:

  1. GitHub将repo的每一个push通知给Jenkins
  2. Jenkins触发一个Maven build
  3. Maven 构建所有的东西,包括Docker镜像
  4. 最后,Maven会把镜像推送到私有的Docker Registry。

这个工作流的好处是它允许我们能够很容易的为每个发布版本打标签(所有的commit都被构建并且在我们的Docker Registry中准备好了)。然后我们可以非常容易地通过pull和run这些Docker镜像进行部署。

事实上这个部署过程是非常简单的,我们通过发送一个命令给我们信任的Slack机器人:"Aloominion"(关于我们的机器人朋友的更多情况将在未来的文章中发表)开始这个过程。

你可能对这个工作流中的其他元素非常熟悉,因为它们都很常见。所以,让我们来深入了解如何使用Maven构建Docker镜像。

深入Docker 构建

Alooma是一个Java公司。我们已经使用Maven作为我们构建流水线的中心工具,所以很自然的想到把构建Docker的过程也加入到我们的Maven构建过程中去。

当搜索和Docker交互的Maven插件时,出现了3个选项。我们选择使用Spotify的maven-docker-plugin —— 虽然rhus的和alexec的同名插件看起来也是一个不错的选择。

另一个我们的构建计划依赖的Maven插件是maven-git-commit-id-plugin。我们使用这个插件,所以我们的Docker镜像能使用git的commit ID来打标签 —— 这在部署过程中非常有帮助,我们可以了解运行的是哪个版本。

给我看代码!

每一个docker镜像有它自己的Maven模块(所有上面提到的docker-maven 插件在一个模块一个Dockerfile时都能顺利地工作)

让我们从Spotify插件的一个简单配置开始:

<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.2.3</version>
<executions>
    <execution>
        <phase>package</phase>
        <goals>
            <goal>build</goal>
        </goals>
    </execution>
</executions>
<configuration>
    <dockerDirectory>${project.basedir}</dockerDirectory>
    <imageName>alooma/${project.artifactId}</imageName>
</configuration>
</plugin>


我们看到这里我们把插件的build目标和Maven的package阶段绑定,我们也指导它去在我们模块的根目录下来寻找Dockerfile(使用dockerDirectory 元素来指定),我们还把镜像名称用它的构件Id来命名(用"alloma/"做前缀)。

我们注意到的第一件事情是这个镜像没有被push到任何地方,我们可以通过加入<pushImage>true</pushImage>到配置中来解决这个问题。

但是现在这个镜像会被push到默认的Docker Hub Registry上。糟糕。

为了解决这个问题,我们定义了一个新的Maven属性<docker.registry>http://docker-registry.alooma.io:5000/</docker.registry>并且把镜像名称imageName改为${docker.registry}alooma/${project.artifactId}。 你可能会想,“为什么需要为Docker Registry设置一个属性?”, 你是对的!但是有这个属性可以使我们在Regsitry URL改变的时候能够更方便的修改。

有一个更重要的事情我们还没有处理——我们想让每一个镜像用它的git commit ID来打标签。这可以通过改变imageName为${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}来实现。

${git.commit.id.abbrev}属性是通过我上面提到的maven-git-commit-id-plugin插件来实现的。

所以,现在我们的插件配置看起来像下面这样:

<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.2.3</version>
<executions>
    <execution>
        <phase>package</phase>
        <goals>
            <goal>build</goal>
        </goals>
    </execution>
</executions>
<configuration>
    <dockerDirectory>${project.basedir}</dockerDirectory>
    <imageName>
        ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
    </imageName>
    <pushImage>true</pushImage>
</configuration>
</plugin>


我们的下一个挑战是在我们的pom.xml中表达我们的Dockerfile的依赖。一些我们的Docker镜像在构建时使用了FROM其它的Docker 镜像作为基础镜像(也在同一个构建周期中构建)。例如,我们的webgate镜像(是我们的机遇Tomcat的WebApp)基于我们的base镜像(包含Java 8、更新到最新的 apt-get、等等)。

这些镜像在同一个构建过程中构建意味着我们不能简单的使用FROM http://docker-registry.alooma.io/alooma/base:some-tag因为我们需要这个标签编程当前构建的标签(即 git commit ID)。

为了在Dockerfile中获得这些属性,我们使用了Maven的resource filtering功能。这在一个资源文件中替换Maven 的属性。

<resource>
<directory>${project.basedir}</directory>
<filtering>true</filtering>
<includes>
    <include>**/Dockerfile</include>
</includes>
</resource>


在Dockerfile的内部我们有一个这样的FROM:

FROM ${docker.registry}alooma/base:${git.commit.id.abbrevs} 


一些更多的事情.......我们需要的是我们的配置来找到正确的Dockerfile(过滤过之后的),这可以在target/classes文件夹内找到,所以我们把dockerDirectory改为${project.build.directory}/classes。

这意味着现在我们的配置文件长这样:

<resources>
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>build</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dockerDirectory>${project.build.directory}/classes</dockerDirectory>
            <pushImage>true</pushImage>
            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>


此外,我们还要添加base构件作为webgate模块的一个Maven依赖来保证正确的Maven构建顺序。

但是我们还有另一个挑战:我们如何把我们编译和打包了的源文件添加到我们的Docker镜像中呢?我们的Dockerfile依赖于很多其它文件,它们通过ADD或COPY命令插入。(你可以在这里读到更多的关于Dockerfile的指导。)

为了让这些文件可以被获取,我们需要使用插件配置的resources标签。

<resources>
<resource>
    <targetPath>/</targetPath>
    <directory>${project.basedir}</directory>
    <excludes>
        <exclude>target/**/*</exclude>
        <exclude>pom.xml</exclude>
        <exclude>*.iml</exclude>
    </excludes>
</resource>
</resources>


注意到我们排除了一些文件。

记住这个resources标签不应该和通常的Mavenresources标签弄混,看看下面的例子,它来自于我们的pom.xml的一部分:

<resources>            <!-- general Maven resources -->
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>build</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dockerDirectory>${project.build.directory}/classes</dockerDirectory>
            <pushImage>true</pushImage>
            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
            <resources>        <!-- Dockerfile building resources -->
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.basedir}</directory>
                    <excludes>
                        <exclude>target/**/*</exclude>
                        <exclude>pom.xml</exclude>
                        <exclude>*.iml</exclude>
                    </excludes>
                </resource>
            </resources>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>


前一个添加在我们想添加一些静态资源到镜像时工作,但是如果我们想要添加一个在同一个构建中构建的构件时需要更多的调整。

例如,我们的webgateDocker镜像包含了我们的webgate.war,这是由另一个模块构建的。

为了添加这个war作为资源,我们首先必须把它作为我们的Maven依赖加进来,然后使用maven-dependency-plugin插件的copy目标来把它加到我们当前的构建目录中。

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
    <execution>
        <goals>
            <goal>copy</goal>
        </goals>
        <configuration>
            <artifactItems>
                <artifactItem>
                    <groupId>com.alooma</groupId>
                    <artifactId>webgate</artifactId>
                    <version>${project.parent.version}</version>
                    <type>war</type>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                    <destFileName>webgate.war</destFileName>
                </artifactItem>
            </artifactItems>
        </configuration>
    </execution>
</executions>
</plugin>


现在这允许我们简单的把这个文件加到Docker插件的resources中去。

<resources>
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>build</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dockerDirectory>${project.build.directory}/classes</dockerDirectory>
            <pushImage>true</pushImage>
            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
            <resources>
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.basedir}</directory>
                    <excludes>
                        <exclude>target/**/*</exclude>
                        <exclude>pom.xml</exclude>
                        <exclude>*.iml</exclude>
                    </excludes>
                </resource>
                <rescource>
                    <targetPath>/</targetPath>
                    <directory>${project.build.directory}</directory>
                    <include>webgate.war</include>
                </rescource>
            </resources>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>


我们需要做的最后一件事情是让我们的CI服务器(Jenkins)真的将镜像push到Docker Registry上。请记住本地构件默认是不会push镜像的。

为了push这些镜像,我们改变我们的&lt;pushImage>标签的值从true变为${push.image}属性,这默认是被设置为false,并且只会在CI服务器上设置为true。(译注:这里的意思是,由于开发人员也要在本地构建然后测试之后才会提交,而测试的镜像不应该被提交到Registry,所以&lt;pushImage>应该使用一个属性,默认为false,在CI服务器上覆盖为true在构建后去push镜像。)

这就完成了!让我们看一下最终的代码:

<resources>
<resource>
    <directory>${project.basedir}</directory>
    <filtering>true</filtering>
    <includes>
        <include>**/Dockerfile</include>
    </includes>
</resource>
</resources>
<pluginManagement>
<plugins>
    <plugin>
        <groupId>com.spotify</groupId>
        <artifactId>docker-maven-plugin</artifactId>
        <version>0.2.3</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>build</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dockerDirectory>${project.build.directory}/classes</dockerDirectory>

            <pushImage>${push.image}</pushImage>      <!-- true when Jenkins builds, false otherwise -->

            <imageName>
                ${docker.registry}alooma/${project.artifactId}:${git.commit.id.abbrev}
            </imageName>
            <resources>
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.basedir}</directory>
                    <excludes>
                        <exclude>target/**/*</exclude>
                        <exclude>pom.xml</exclude>
                        <exclude>*.iml</exclude>
                    </excludes>
                </resource>
                <rescource>
                    <targetPath>/</targetPath>
                    <directory>${project.build.directory}</directory>
                    <include>webgate.war</include>
                </rescource>
            </resources>
        </configuration>
    </plugin>
</plugins>
</pluginManagement>

 

性能

这个过程有两个能够提高你的构建和部署的性能的改进地方:

  • 让你的基础的机器镜像(在EC2的例子下是AMI)包含一些你的Docker镜像的基础版本。这样会使得docker pull只去pull那些改变了的层,即增量(相对于整个镜像来说要小得多)。
  • 在Docker Registry的前端放一个Redis缓存。这可以缓存标签和元数据,减少和真实存储(在我们的例子下是S3)的回环。

我们现在已经使用这个构建过程一段时间了,并且对它非常满意。然而仍然有提高的空间,如果你有任何关于让这个过程更加流畅的建议,我很乐意在评论中听到你的想法。

本文转载自:https://zhuanlan.zhihu.com/p/27563553

共有 人打赏支持
Michaelyn
粉丝 5
博文 135
码字总数 30289
作品 0
广州
其他
跟我一起学docker(十)--jenkins的使用

什么是Jenkins? jenkins是一个广泛用于持续构建的可视化web工具,持续构建说得更直白点,就是各种项目的"自动化"编译、打包、分发部署。jenkins可以很好的支持各种语言(比如:java, c#, php...

IT人故事会 ⋅ 05/03 ⋅ 0

使用Docker运行Java应用程序

使用Docker运行Java应用程序 部署软件混乱; 需要克服许多问题才能生成顺畅且无痛的部署过程。诸如环境设置,依赖性地狱,编排管理以及系统可用性等问题是部署新应用程序时遇到的许多问题中的...

优惠券发放 ⋅ 05/30 ⋅ 0

快速构建docker镜像:maven插件

越来越多的项目开始了docker容器化部署的进化,在容器化之前我们部署一个项目,可能由源代码产出一个jar或者war即可直接发布了,启动之后是一个java进程;容器化之后,由源代码产出的是一个d...

rabbitGYK ⋅ 04/15 ⋅ 0

关注这些技术号,你将拥有半个互联网圈

“ IT 行业技术变更周期越来越快,作为技术人最重要的是持续学习,现在的学习途径有很多,我们到底该如何做出选择? 我觉得最重要有两方面:第一,需要保持良好的技术视野,持续关注行业内技...

g6u8w7p06dco99fq3 ⋅ 04/18 ⋅ 0

加强Docker容器与Java 10集成

很多运行在Java虚拟机(JVM)中的应用,包括数据服务如Apache Spark和Kafka以及传统企业应用,都运行在容器中。最近,运行在容器里的JVM出现了由于内存和CPU资源限制和使用率导致性能损失问题...

java高级架构牛人 ⋅ 06/04 ⋅ 0

跟我一起学docker(11)--jenkins+github+Docker

Github+Jenkins+Docker持续集成 这次要做的就是我本地git push到github后,jenkins自动构建 注意:本次课程jenkins必须有公网ip,保证github可以通知jenkins构建。 公网ip服务器 登陆github ...

IT人故事会 ⋅ 05/05 ⋅ 0

2.2 Selenium 环境搭建

Java 环境搭建 下载JDK:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 安装JDK 配置Java 环境 校验是否安装完成 控制台输入: java –version 提示版......

米阳MeYoung ⋅ 04/24 ⋅ 0

Java程序员必读书单,家族又添新成员

点击关注异步图书,置顶公众号 每天与你分享IT好书 技术干货 职场知识 参与文末话题讨论,每日赠送异步图书。 ——异步小编 有些革命出其不意地吸引了全世界的眼球。Twitter、Linux操作系统和...

异步社区 ⋅ 05/09 ⋅ 0

java编程学习之Eclipse中透视图和视图简聊

  虽然使用JDK编译工具可以白那些java程序,但在项目开发时需要使用大兴的集成开发工具,以避免编码错误,而Eclipse为编程人员提供了一流的java开发环境,深受java开发人员的喜爱,那么今天...

老男孩Linux培训 ⋅ 06/05 ⋅ 0

🛠VS Code编辑器配置Java开发环境

🛠VS Code编辑器配置Java开发环境 一、简述 及讨论 由于学校课程的原因,是基本以java开发为主线的课程,但是我对java兴趣不大,又加上我不太喜欢 「Eclipse」这个java的集成开发环境(简称...

Cc卿 ⋅ 06/02 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Thrift RPC实战(二) Thrift 网络服务模型

TServer类层次体系 TSimpleServer/TThreadPoolServer是阻塞服务模型 TNonblockingServer/THsHaServer/TThreadedSelectotServer是非阻塞服务模型(NIO) 1 TServer抽象类的定义 内部静态类Args的...

lemonLove ⋅ 11分钟前 ⋅ 0

vim命令用法

第五章 vim命令 vim和vi几乎是一样的,唯一的区别就是当编辑一个文本时,使用vi不会显示颜色,而使用vim会显示颜色。 vim有三个模式:一般模式,编辑模式,命令模式。 系统最小化安装时没有安...

弓正 ⋅ 13分钟前 ⋅ 0

MyBatis源码解读之配置

1. 目的 本文主要介绍MyBatis配置文件解析,通过源码解读mybatis-config.xml(官方默认命名)、Mapper.xml 与Java对象的映射。 2. MyBatis结构 查看大图 MyBatis结构图,原图实在太模糊了,所以...

无忌 ⋅ 17分钟前 ⋅ 0

Ignite的jdbc与网格的连接方式的查询性能对比

环境: 数据量100万 Ignite2.5 Windows10 8g jdbc方式连接 import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; i......

仔仔1993 ⋅ 31分钟前 ⋅ 0

收集自网络的wordpress 分页导航的代码教程(全网最全版)

wordpress 分页导航是用来切换文章的一个功能,添加了 wordpress 分页导航后,用户即可自由到达指定的页面数浏览分类文章,而这样的一个很简单功能却有很多朋友在用插件:WP-PageNavi,插件的...

Rhymo-Wu ⋅ 47分钟前 ⋅ 0

微服务 WildFly Swarm 入门

Hello World 就像前面章节中的其他框架一样,我们希望添加一些基本的 Hello-world 功能,然后在其上逐步添加更多的功能。让我们从在我们的项目中创建一个 HolaResources 开始。您可以使用您的...

woshixin ⋅ 54分钟前 ⋅ 0

Maven的安装和Eclipse的配置

1. 下载Maven 下载地址 2. 解压压缩包,放到自己习惯的硬盘中 此处我将其放到了 D:\Tools 目录下。 3. 配置环境变量 右键此电脑 -> 属性 -> 高级系统设置 -> 环境变量。 在系统变量中新建,变...

影狼 ⋅ 今天 ⋅ 0

python pip使用国内镜像的方法

国内源 清华:https://pypi.tuna.tsinghua.edu.cn/simple 阿里云:http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/ 华中理工大学:http://......

良言 ⋅ 今天 ⋅ 0

对于url变化的spa应该如何使用微信jssdk

使用vue单页面碰上微信jssdk config验证失败的坑。第一次成功 之后切换页面全部失败,找到了解决方法,第一次验证成功后保存验证信息 切换页面时验证信息直接拿来用,加一个wx.error() 失败时...

孙冠峰 ⋅ 今天 ⋅ 0

Spring Cloud Gateway 一般集成

SCF发布,带来很多新东西,不过少了点教程,打开方式又和以前的不一样,比如这个SCG,压根就没有入门指导,所以这里写一个,以备后用。 一、集成 pom.xml <dependency> <groupI...

kut ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部