基于 Gradle,无需安装 Docker Toolbox,即可生成 Docker 镜像。
Docker是一个开源的基于LXC的轻量级容器管理器,在Docker的帮助下,开发者只需要将应用和依赖的运行环境打包成一个可移植的容器,就可以正常运行,不受语言、框架和底层操作系统的限制,使用Docker可以缩短开发时间、探索时间和部署周期, 这样他们就可以更高效地编码,简化开发环境,消除依赖冲突等,并最终提高开发生产力。
但是,当 Windows 平台使用 Docker 技术时,需要安装和配置额外的 Docker 工具,这不可避免地会花费额外的工作时间,如果想在 Dev 环境中配置一组与生产环境一致的构建环境,则需要花费大量的精力。
为了加快开发构建速度,显著提高开发场景中的开发效率,建议使用 Gradle 进行构建,并使用 Gradle 脚本高效便捷地管理项目中的差异、依赖、编译、打包和部署过程。 本文将重点介绍 Gradle 的使用以及如何使用 Gradle 创建 Docker 镜像,如何利用 Gradle 脚本以及如何从开发人员的角度使用中央 Docker 引擎构建 Docker 镜像。
在传统的构建系统中,会使用 ant 和 m**en 作为构建工具,这里推荐的是 gradle,它不使用 xml 作为配置文件,而是使用基于 groovy 开发的 DSL,因此 gradle 的配置文件比 ant 和 m**en 的配置文件更高效、更简洁。 以下是 Gradle 和 Ant 以及 M**En 之间的主要区别
ANT是最早的构建工具,它的核心是用J**A编写的,使用XML作为构建脚本,因为它基于J**A,允许在任何环境中运行构建。 ANT基于任务链的思想,它定义了任务之间的依赖关系并形成一个序列。 缺点是使用 XML 定义构建脚本会导致脚本臃肿,而 ANT 本身不提供项目构建的指导,导致构建脚本不同,开发人员需要熟悉每个项目的脚本内容,并且不提供 ANT 生态系统中的依赖管理工具。 M**en 意识到 ANTU 的不足,采用标准的项目布局、统一的生命周期和强大的依赖管理,使用“约定胜于配置”的思想,减少构建脚本的配置,提高开发效率和管理秩序,拥有活跃的社区,可以通过合适的插件轻松扩展。 缺点是它有一个默认的结构和生命周期,有很多限制,编写插件扩展很麻烦,并且使用xml作为构建脚本,使构建脚本冗长。 Gradle 兼具 ANT 和 M**EN 的优势,具有 ANT 的强大和灵活性,并继承了 M**EN 的生命周期管理。 它使用基于 Groovy 的 DSL,并提供声明式构建语言,简单明了,使 Gradle 构建脚本更加简洁明了,并使用具有完全可配置性的标准项目布局。 您还可以扩展插件以提供默认的构建生命周期、自定义任务、单独运行任务以及定义任务之间的依赖关系。 Gradle 继承并改进了 m**en 的依赖管理,可以与 m**en 和 ivy 仓库结合使用; 同时,它与 ant 本身兼容,并有效地复用 ant 的任务; 插件实现方式多样,有很强的官方插件库; 从构建级别来看,它支持从 ant 和 m**en 的逐步迁移; 通过包装器实现跨平台的无缝兼容性。 JDK:最新的 J**A8 版本,安装和配置环境变量。 gradle 构建工具:请转到最新的完整版本。 Eclipse:Eclipse IDE for j**a EE Developers 版本,请转到 *** 最新版本。 Gradle 的核心是 Groovy,而 Groovy 的本质是 J**A,所以很明显 Gradle 环境必须依赖 JDK 和 Groovy 库,所以需要先安装 JDK 并配置环境变量。
* gradle-4.5-all,从 Gralde 中选择最新版本,然后解压到指定目录,将 gradle 的 bin 目录添加到 path 变量中,配置环境变量。 检查是否配置了gradle环境变量。 在 cmd 命令行中输入 gradle -v,如果出现类似如下消息的消息,则表示 gradle 环境配置成功。
---gradle 4.5---build time: 2018-01-24 17:04:52 utcrevision: 77d0ec90636f43669dc794ca17ef80dd65457becgroovy: 2.4.12ant: apache ant(tm) version 1.9.9 compiled on february 2 2017jvm: 1.8.0_151 (ibm corporation 2.9)os: windows 7 6.1 amd64
注意:以上信息可能因 Gradle 版本或不同环境而异。
新建一个名为 gradle project 的根目录,以多个子项目为例,所以还需要在 gradle 项目目录下创建一个子目录
gradle_project_util;gradle_project_model;gradle_project_web;
创建内部版本gradle 文件,其中包含以下内容:
清单 1根项目 gradle 配置。
subprojects jcenter } dependencies {}version = '1.0' jar }
创建设置Gradle 文件, 设置gradle 文件用于管理整个项目,声明当前项目中有哪些模块等。 具体如下:
清单 2根项目设置配置。
rootproject.name = "gradle_project"include "gradle_project_util","gradle_project_model","gradle_project_web"
创建完成后,显示如下图:
图1目录结构的示例。
在子项目中创建生成gradle 文件并添加如下内容: 清单 3子项目 gradle 脚本。
apply plugin: 'j**a' repositories dependencies /***project template***/ task createdirs sourcesets*.resources.srcdirs*.each }
在 cmd 命令行上执行 :gradle createdirs 命令行,命令执行后会创建 j**a 项目的标准目录结构。
Gradle 遵循 COC(约定重于配置)理念,默认提供与 M**en 相同的项目结构配置。 一般结构如下:
sub-project root
src main j**a (项目目录)src main resources (project resource directory)src test j**a (test source directory)src test resources (test resource directory) 将 Gradle 项目导入 Eclipse: 在 Eclipse 菜单中选择 file->import->gradle,如下图所示:
图2项目导入示例。
选择下一步后,选择 gradle 项目所在的目录,然后继续选择默认值继续下一步,直到项目结构解析完成,如下图所示:
图3Gradle 项目结构示例。
导入完成后,下图显示了 Eclipse 中 Project Explore 中显示的项目结构,并创建了与 M**en 一致的项目结构。
图4项目探索示例。
基于 gradle 的脚本的本质是 groovy 脚本,当执行一种类型的配置脚本时,会创建一个关联对象,例如,在执行构建脚本脚本时会创建一个项目对象,这个对象实际上是一个 gradle 对象。
下面介绍了 Gradle 的三个主要对象:
gradle object:这是构建初始化时创建的唯一对象,一般不建议修改此默认配置。 设置对象:每个设置gradle(gradle 的设置文件,其约定名称由 settings 定义。gradle) 将转换为设置对象,该对象在初始化阶段执行,对于多项目构建,您必须确保有设置gradle 文件,对于单项目构建设置文件是可选的,但建议您仍然需要配置它。 项目对象:每个生成Gradle 将转换为 Project 对象。 Gradle 的三种不同纪元脚本分别用于以下用途:
初始化脚本 init script(gradle)。
Gradle 对象:初始化脚本 Init 脚本 (Gradle) 与其他类型的 Gradle 脚本类似,因为它们在构建开始之前运行,主要用于为下一个构建脚本做准备。 如果我们需要编写一个初始化脚本,init 脚本,我们可以按照规则将其放置在用户家中gradle。
初始化脚本的 gradle 对象表示 gradle 的调度,我们可以通过调用项目对象的 getgradle() 方法获取 gradle 实例对象。
设置脚本设置脚本(settings)。
Settings 对象:设置实例和设置gradle 文件是一对一的对应关系,用于配置一些项目设置。 此文件通常放在项目的根目录中。
构建脚本(项目)。
在 Gradle 中,每个要编译的项目都是一个项目(每个项目的构建gradle 对应一个项目对象),每个项目都是用一系列任务构建的,其中许多任务默认由 gradle 插件支持。当我们编写 gradle 脚本时,我们大部分时间都是在编写构建脚本,因此项目和脚本对象的属性和方法等 API 非常重要。
每个项目对象和生成Gradle 是一对一的对应关系,一个项目在构建时有如程:
为当前项目创建类型设置的实例。 如果当前项目中有设置gradle 文件,请使用此文件来配置您刚刚创建的 settings 实例。 通过设置实例的配置,在项目层次结构中创建项目对象的实例。 最后,上面创建的项目对象实例执行生成。 对于每个项目gradle 脚本。 作为构建脚本的开发者,你不应该只局限于编写任务动作或配置逻辑,有时候你想在指定的生命周期事件发生时执行一个**,这里你需要了解生命周期事件。 生命周期事件可以发生在指定的生命周期之前、期间或之后,在执行阶段之后发生的生命周期事件被视为完成。 具体实施周期如下图所示
图5Gradle 生命周期图。
初始化阶段:Gradle 支持单项目和多项目构建,在初始化阶段,gradle 决定哪些项目需要添加到构建中,并为这些需要添加到构建中的项目创建项目实例,本质上是执行设置gradple 脚本。
配置阶段:配置阶段决定了整个构建的项目和任务之间的关系,它会建立一个有向图来描述任务之间的相互依赖关系,并解析构建。 添加到构建项目的每个生成gradle 脚本。
执行阶段:执行阶段是生成 gradle 来执行根项目和每个子项目下的自定义任务及其所依赖的任务,以实现最终的构建目标。
正如你所看到的,生命周期实际上可以与上面构建脚本的执行过程相关。
在多项目构造中,需要指定一个树根,树中的每个节点代表一个项目,每个项目对象都指定了一个表示树中位置的路径。 一般做法是确保有设置gradle 文件是在最初创建 gradle 项目时定义的,因此稍后将在构建脚本文件中对其进行扩展。
如前所述,gradle 构建脚本由一系列任务组成,首先要了解如何编写任务
清单 4有操作任务脚本,也没有操作任务脚本。
TaskTask 你好 }没有 Action 的任务,这是一个快捷方式,替换 Dolast,稍后解释 Task Hello <<
在上面的例子中,如果任务没有用“”执行,则任务在脚本的初始化阶段执行(即执行任何任务),如果执行,则会在gradle actiontask之后执行。 因为如果不添加 “”,则会在任务函数返回之前执行闭包,如果添加 “”,则会调用 actiontaskdolast(),所以会等到gradle actiontask执行完毕,这一点必须记住,因为在具体的使用过程中,你会发现同一个任务可能没有按照任务依赖的顺序执行,你应该考虑是否受到这种写入的影响。
通过上面这个任务的基本感觉的例子,可以发现一个构建gradle 文件定义了多个彼此无关的任务,并确定了 gradle 命令后跟的任务名称。 如果我们希望它们之间有依赖关系,先做什么,以后做什么怎么办? 让我们看一下以下内容:
清单 5任务取决于脚本。
task taskx(dependson: 'tasky')$ gradle taskx> task :taskytasky> task :taskxtaskxbuild successful in 0s2 actionable tasks: 2 executed
这是如何使用 dependson 将两个单独的任务相互连接。
您还可以在 Gradle 中使用 Groovy 创建动态任务,如下所示:
清单 6创建动态任务脚本。
4.times }
结果如下:
$ gradle task1> task :task1i'm task number 1build successful in 0s1 actionable task: 1 executed
除了在定义上述任务时指定依赖关系外,还可以通过 API 为任务添加依赖关系,如下所示:
清单 7task 表示依赖脚本。
4.times }task0.dependson task1, task1
结果如下:
$ gradle task0> task :task1i'm task number 1> task :task2i'm task number 2> task :task0i'm task number 0build successful in 0s3 actionable tasks: 3 executed
您还可以通过 API 向任务添加一些新行为,例如:
清单 8任务 dofirst,dolast 脚本。
task hello <$gradle hello> task :hellohello venushello earthhello marshello jupiterbuild successful in 0s1 actionable task: 1 executed
正如你所看到的,dofirst 和 dolast 可以多次执行,“运算符本质上是 dolast。
您还可以使用美元符号作为其他任务的属性,如下所示:
清单 9任务引用脚本。
task hello <$gradle hello> task :hellohello world!greetings from the hello task.build successful in 0s1 actionable task: 1 executed
上面脚本中使用的名称实际上是任务的默认属性,它表示当前任务的名称。
您还可以向任务添加自定义属性,如以下示例所示:
清单 10任务自定义属性脚本。
task task1 task printtaskproperties <<
结果如下:
$ gradle printtaskproperties> task :printtaskpropertiesvaluebuild successful in 0s1 actionable task: 1 executed
此外,gradle 允许您在脚本中定义一个或多个默认任务,如下所示:
清单 11任务自定义任务脚本。
defaulttasks 'clean', 'run'task clean <$gradle> task :cleandefault cleaning!> task :rundefault running!build successful in 0s2 actionable tasks: 2 executed
如前所述,gradle 在构建脚本中定义了一个项目,对于构建脚本中的每个子项目,gradle 都会创建一个要关联的项目对象,在执行构建脚本时,它会配置关联的项目对象; 生成脚本中调用的每个方法和属性都委托给当前 Project 对象。
项目对象提供了一些标准属性,这些属性便于在生成脚本中使用。
有关特定于项目的方法的详细信息,请参阅项目的文档。 对于 J**A 项目,一般需要在构建开始前清理之前的构建结果,启动构建,将构建结果复制到指定目录,将其他需要的配置、脚本等复制到指定目录,以上步骤都是构建 Docker 镜像的准备步骤。 具体如下:
清单 12Gradle 构建脚本。
apply plugin: 'j**a' repositories dependencies task cleandocker(type: delete) /docker") task copybuild(type: copy, dependson: build) -jar" into 'build/docker' } task copyscript(type: copy, dependson: copybuild) /resources/main/script") into project.file("$/docker/script")}
执行完成后,结果如下:
$ gradle copyscriptbuild successful in 1s5 actionable tasks: 5 executed
同时,可以发现在构建$projectdir的项目构建目录下有构建生成的文件、复制的配置文件和脚本文件,如下图所示
图6构建结果图。
在 J**A 项目中,一些常用的 ** 是同时被多个项目引用的,对于这部分代码需要在常用项目中提及,并编译成一个 jar 包并发布到仓库中,这样需要使用 common ** 的项目只需要根据 jar 包信息下载仓库中最新版本的 jar 包即可。
通常,您可以按如下方式添加项目依赖项:
dependencies
在 gradle 中,依赖可以组合成配置,配置就是一系列依赖,通常理解为依赖配置,可以用来声明项目的外部依赖,也可以用来声明项目的发布。 下面我们给出一些 j**a 插件中的常见配置,如下所示:
compile:用于编译对项目源码的依赖; runtime:运行时生成的类所需的依赖项,默认项,包括编译时的依赖项; testcompile:编译测试依赖,默认项,包含运行生成的类所需的依赖和编译源的依赖;testRuntime:运行测试所需的依赖,默认项,包括以上三个依赖; Gradle 可以声明许多依赖项,其中一个是外部依赖项,即当前构建之外的依赖项,通常存储在远程(例如 m**en)或本地存储库中。 下面是外部依赖项的示例:
dependencies
如您所见,引用外部依赖项需要 group、name 和 version 属性。 以上是两种不同的外部依赖书写方式,其中第二种是速记。
根据 j**a 发布 jar 包的规范,一般发布 jar 包还需要附带一个源**,供用户参考和使用。 因此,当它被打包成一个 jar 时,源代码也会被打包,如下**:
清单 13jar 打包脚本。
task sourcejar(type: jar)
此外,您还需要配置上传 jar 包和仓库信息
清单 14jar 发布脚本。
publishing }artifactory defaults }}
这里的配置是:远程仓库的URL地址、用户名、密码等信息,根据上面的gradle脚本,将发布通用项目的**,供其他项目使用。
与过去使用 war 包发布 J**A 应用类似,现在的微服务和 Web 应用大多是使用 docker 镜像发布的,本文开头已经介绍了 docker 镜像发布的便利性,这里就不深入展开了。
在本节中,我们将介绍如何确保每个开发人员发布用于测试的 Docker 镜像与生产环境中发布的镜像一致。 这里介绍中央 Docker 引擎的概念,是为了保证每个开发者都通过中央引擎构建一个 Docker 镜像,而不需要每个开发者在开发环境中配置 Docker Engine,从而消除因 Docker Toolbox 版本不同、操作系统不同而导致的 Docker Build 环境差异。 中央 Docker 引擎的概念如下图所示:
图7中央 Docker 引擎示例图。
集中式构建需要中央 Docker 引擎,配置信息基于 CentOS 73.其他操作系统也可以基于上述概念进行配置。 配置的主要思想是在 Linux Server 上安装 Docker Engine,在 Docker Engine 中配置 2375 TCP 端口,并在防火墙中释放此端口,以便其他服务可以连接远程构建。 具体步骤如下:
使用以下命令安装 docker 和 docker 引擎:
sudo yum install docker-io安装上述命令后,还需要配置 docker。sudo yum install docker-engine
修改 lib systemd 系统 docker服务中的以下两行是:
environmentfile=-/etc/sysconfig/docker将 etc sysconfig docker 的内容修改为以下值:execstart=/usr/bin/dockerd $docker_opts
docker_opts=’-h tcp: -h unix:///var/run/docker.sock’防火墙释放端口 2375。
firewall-cmd –zone=public –add-port=2375/tcp –permanent完成上述配置后,中央 Docker 引擎就已设置完毕。firewall-cmd –reload
由于主要使用plugin:combmuschko.docker-remote-api 完成对远程 docker 引擎的调用,这里将介绍 gradle 插件。
其实gradle的核心主要是一个框架,所谓gradle就是简单快捷的构建,其实它是由一系列插件支持的,插件增加了新的任务。 Gradle 中一般有两种类型的插件,如下所示:
脚本插件是额外的构建脚本,用于进一步配置构建,通常在构建内部使用。 脚本插件可以从本地文件系统获取,也可以远程获取,如果从文件系统获取,则相对于项目目录,如果远程获取,则由 http url 指定。
二进制插件是实现插件接口的类,并以编程方式为构建提供一些有用的任务。
引用插件需要通过项目完成apply() 方法完成声明申请,同一个插件可以多次应用。 下面是一个示例:
脚本插件应用自:'build.gradle'二进制插件应用插件:'j**a'插件还可以使用插件 ID,这些 ID 用作给定插件的唯一标识符,并且可以使用缩写字符 ID 进行注册以供以后使用。 例如,下面是一个示例:
由 j**a 插件的 id 引用。在上一节中,我们谈到了如何使用 Gradle 插件,在本节中,我们将使用 Gradle 插件来构建一个依赖于 plugin:com 的 Docker 镜像bmuschko.docker-remote-api 完成 Docker 镜像的远程构建和发布。apply plugin: codecheck
在构建 Docker 镜像之前,需要使用 gradle 脚本完成一系列的准备工作,包括将配置文件和启动脚本复制到指定目录,构建目标 jar 包,然后使用 gradle 插件生成 dockerfile,然后根据 dockerfile 信息进行发布。
配置 Docker 引擎信息。
清单 15Docker 引擎信息配置脚本。
docker else registrycredentials }
上面的脚本会根据是 Windows 平台还是 Linux 平台使用不同的 URL,直接使用 DockerSock、Windows 平台需要通过 TCP 端口连接到 Docker Engine。
准备工作,包括清除以前构建的旧版本以及将编译结果复制到目标目录。
清单 16docker 预准备脚本。
task cleandocker(type: delete) /docker") }task copybuild(type: copy, dependson: build) -jar" into 'build/docker'}task copyscript(type: copy, dependson: copybuild) /resources/main/script") into project.file("$/docker/script")}
生成 dockerfile
清单 17生成 dockerfile 脚本。
task createdockerfile(type: dockerfile, dependson: copyscript) -jar", docker_work_home addfile 'script', "$/script" environmentvariable 'j**a_opts' , j**a_opts environmentvariable 'boot_target' , "$-$jar" runcommand "chmod +x /home/root/script/*.sh" entrypoint("sh", "-c", /home/root /script/startup.sh")}
构建 Docker 镜像、标记、发布
清单 18发布 docker 镜像脚本。
task builddockerimage(type: dockerbuildimage, dependson: createdockerfile) task builddockertagimage(type: dockertagimage, dependson: builddockerimage) repository = docker_image conventionmapping.tag = force = true}task pushimage(type: dockerpushimage, dependson: builddockertagimage) conventionmapping.tag = } artifactorypublish
最后,在 cmd 中运行命令:gradle artifactorypublish 以完成 docker 镜像构建和发布。
对于团队协作环境,与其为每个开发者单独构建一个Docker环境,不如构建一套与生产环境一致的Central Docker Engine环境,这样既方便维护,又保证了开发者生成的Docker镜像与生产环境生成的Docker镜像一致。 以上只是实际项目中的一点个人想法,如果有不足之处,希望读者能够海瀚,如果有,希望读者能够反馈,交流经验,共同进步。