zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

2023-09-27 14:27:26 时间
本文讲的是持续集成案例学习:Docker、Java与Maven,【编者的话】对于使用Java技术栈的企业,Maven往往是其持续集成的核心工具,在当前的Docker化的运动中,要如何把Docker镜像的构建也加入到传统的Maven构建为基础的持续集成流程中呢?Alooma公司在本文中分享了他们使用Maven对Docker镜像构建进行持续集成的经验。

在Alooma,我们非常非常非常喜爱Docker。真的, 我们想完全容器化我们的应用。 虽然容器化应用有非常多的好处,但在这里,我并不是要说服你用Docker。我们只是认为你和我们一样喜欢这东西。

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

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

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

让我们把目标分解为更加详细的需求:
在版本控制系统中管理Dockerfile 在CI服务器上为每个commit构建Docker镜像 上传构件并打标签(这个构件要能够简单的部署)
我们的工作流 我们的DevOps流水线围绕GitHub、Jenkins和Maven构建。下面是它的工作流程:
GitHub将repo的每一个push通知给Jenkins Jenkins触发一个Maven build Maven 构建所有的东西,包括Docker镜像 最后,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 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 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标签不应该和通常的Maven resources标签弄混,看看下面的例子,它来自于我们的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 


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

例如,我们的webgate Docker镜像包含了我们的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这些镜像,我们改变我们的 pushImage 标签的值从true变为${push.image}属性,这默认是被设置为false,并且只会在CI服务器上设置为true。(译注:这里的意思是,由于开发人员也要在本地构建然后测试之后才会提交,而测试的镜像不应该被提交到Registry,所以 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)的回环。
我们现在已经使用这个构建过程一段时间了,并且对它非常满意。然而仍然有提高的空间,如果你有任何关于让这个过程更加流畅的建议,我很乐意在评论中听到你的想法。

原文链接:Continuous Integration for Dockers: Case study (翻译:陈光)
原文发布时间为:2015-08-12 本文作者:Casgy 本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。 原文标题:持续集成案例学习:Docker、Java与Maven
Mybatis学习(一):Maven工程部署Mybatis项目 MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程,换句话说就是补了JDBC的坑,让操作数据库变得更方便。
Maven入门学习——使用IDEA创建Maven文件的两种方式(内含配置setting文件) 由于我们IDEA中使用3.6.1版本的Maven比较稳定,所以要先用原先配置好的setting.xml文件覆盖3.6.1版本下的setting.xml文件在此分享一下完整的setting.xml文件(仅显示修改部分)