本文所阐述的解决方案能够在任意的云或虚拟化平台中自动构建、部署和管理基于Docker的 Java微服务应用。我们扩展了一个已有的项目,这就是Chris Richardson针对事件溯源、CQRS和Docker所创建的转账样例,我们将会为这个项目引入自动构建的功能。我们的这个项目为每个微服务都创建了一个Dockerfile,同时也提供了统一的前端页面,这个前端功能会用到所有的微服务,它可以运行在任意的Web服务器上。我们将会使用Nginx作为Web服务器,并将前端的JavaScript代码放到默认的/usr/share/nginx/html/目录中。我们的前端会暴露如下的功能:
我们所创建的转账应用将会作为构建和部署微服务的样例,这些微服务包括了事件溯源、CQRS和Docker。这个基于微服务的应用架构是高度可扩展和高度可重用的,用到了polyglot持久化(polyglot persistence)、事件溯源(event sourcing,ES)以及命令与查询的责任分离(command query responsibility segregation,CQRS)。微服务应用是由松耦合的组件所组成的,它们会使用事件来实现通信。这些组件可以部署为独立的服务,也可以将其打包为一个单体应用,以便于简化开发和测试。在这个项目中,我们会关注于前一种方式的自动化——也就是,使用独立的服务来部署应用,这些服务会运行在Docker容器上。
我们的目标就是在13个不同的云和虚拟化平台中(包括vSphere、OpenStack、AWS、Rackspace、Microsoft Azure、Google Compute Engine、DigitalOcean、IBM SoftLayer等)运行和管理本项目中的事件溯源Docker Java微服务应用模板。我们建议你通过如下的方式之一来跟随我们的步骤:
对企业级Java应用进行容器化是一项具有挑战性的事情,这在很大程度上是因为现有的应用组合框架并没有解决掉复杂的依赖、外部集成以及添加服务实例之后(Post-Provision)的自动扩展流程等问题。另外,容器生存周期的短暂性会强迫开发人员生成新的容器,并且在每次版本更新的时候,重新创建复杂的依赖和外部集成。
DCHQ可以通过托管版本和on-premise版本进行使用,它解决了上述的挑战并简化了企业级Java应用的容器化,DCHQ是通过一个高级应用组合框架来实现的,这个框架扩展了Docker Compose,支持跨镜像的环境变量绑定,可扩展的BASH脚本插件,这个脚本可以在请求的时候执行,也可以在提供实例之后执行,同时,为了实现高可用性,还提供了应用集群的功能,这个集群能够跨多个主机和region,支持自动扩展。
在应用添加进来之后,用户就可以:
除此之外,内置的工作流程有助于使用Jenkins实现持续集成(稍后将会支持更多的构建服务器),这样允许开发人员刷新正在运行应用的Java WAR文件,而不会打乱已有的依赖和集成。
在本文作者的其他博客文章中,我们已经阐述了更为传统的或者说典型的Java应用(如命名目录、Pizza商店以及电影商店应用)如何在基于Docker的多层应用栈上实现端到端的部署自动化,这些技术栈涵盖了13种不同的云和虚拟化平台。
在当前的这个项目中,我们将会聚焦于微服务架构,它根本不需要应用服务器。每个微服务都运行在特别轻量级的Java容器中。我们会构建统一的前端,它会发送REST API调用到相关的服务,从而执行特定的任务(如创建账户、查询账户或者将钱从一个账户转到另一个账户)。微服务的主要优势之一(与传统的单体应用相比)就是这些模块化的服务能够很容易地进行替换和扩展,而不需要对其他微服务做出修改。按照这种方式,会消除单点故障,也会让开发人员更容易地将精力投入到整体的项目中。
在这个项目中,我们将会提供step-by-step的指南,指导读者在不同的云/虚拟化基础设施下部署和管理该Java应用。
我们将会执行如下的步骤,稍后将会进行详细介绍:
接下来,我们将会详细介绍每个步骤:
为了单独的运行微服务,我们需要为Event Store获取凭证。
将为EVENTUATE_API_KEY_ID和EVENTUATE_API_KEY_SECRET获取到的值复制并粘贴到事件溯源Docker Java Microservices应用的模板中。
Docker镜像中所使用的JAR文件是通过该项目构建的。
所有的JAR文件都是在2015年12月27日构建的,并且已经嵌入到了Docker镜像中,可以查看该地址。
在构建JAR文件之前,将CORSFilter.java文件复制到“event-sourcing-examples/java-spring/common-web/src/main/java/net/chrisrichardson/eventstore/javaexamples/banking/web/util”目录中,我们可以这样执行:
`./gradlew assemble. git clone https://github.com/cer/event-sourcing-examples.git wget https://github.com/dchqinc/event-sourcing-microservices/raw/master/patch/CORSFilter.java -O /event-sourcing-examples/java-spring/common-web/src/main/java/net/chrisrichardson/eventstore/javaexamples/banking/web/util/CORSFilter.java cd /event-sourcing-examples/java-spring ./gradlew assemble`
该项目中的所有镜像构建好了,并推送到了DCHQ公开的Docker Hub仓库中,便于读者进行引用。如下就是在当前的应用模板中要使用的自定义镜像:
如果要构建镜像并将将其推送到自己的Docker Hub或Quay仓库中,那么可以使用DCHQ。如下就是这些镜像所使用的GitHub项目:
在登录进DCHQ之后(托管的DCHQ.io版和On-Premise版本均一样),我们可以导航到Automate > Image Build,然后点击+按钮来创建新的Dockerfile(Git/GitHub/BitBucket)镜像构建。
为必填域提供如下的值:
在必填域完成之后,点击Save。
这样,我们就可以点击Play按钮,按需构建Docker镜像了。
在登录进DCHQ之后(托管的DCHQ.io版和On-Premise版本均一样),用户可以导航至Manage >App/Machine,然后点击+按钮来创建新的Docker Compose模板。关于如何创建Docker Compose应用模板,可以参考这里的详细文档。
我们已经使用上一步中所构建的Docker镜像创建了一个应用模板。这个模板包含了如下的组件:
在应用模板中,你可能会注意到Nginx容器在请求时(request time)会触发一个BASH脚本插件,以便于配置容器。这个插件也可以在新增服务实例之后执行。
我们可以通过导航至Manage > Plugins来创建这些插件。在提供完BASH脚本之后,DCHQ agent将会在容器内执行该脚本。我们还可以指定参数,这些参数能够在请求时或提供实例后进行重写。所有以$符号作为前缀的内容均会被视为参数——例如,$file_url可以作为参数,允许开发人员指定WAR文件的下载URL。如果用户想要在一个运行时的容器中刷新Java WAR文件的话,这个参数可以在请求时和提供实例后进行重写。
在定义基于YAML的应用模板时,我们需要提供插件ID。例如,要触发Nginx的BASH脚本插件,我们需要按照如下的方式来引用插件ID:
`nginx: image: dchq/nginx-microservices:latest publish_all: true mem_min: 50m host: host1 plugins: - !plugin id: Gl5Hi restart: true lifecycle: on_create arguments: - ACCOUNT_CMD_IP={{accountscommandside | ip}} - ACCOUNT_CMD_PORT={{accountscommandside | port_8080}} - ACCOUNT_TRANSFER_IP={{transactionscommandside | ip}} - ACCOUNT_TRANSFER_PORT={{transactionscommandside | port_8080}} - ACCOUNT_QUERY_IP={{accountsqueryside | ip}} - ACCOUNT_QUERY_PORT={{accountsqueryside | port_8080}}`
在这个样例中,Nginx会调用一个BASH脚本插件,它会动态(或在请求时)将微服务容器的IP和端口号注入到/usr/share/nginx/html/js/app.js文件中。插件的ID是Gl5Hi。
通过使用插件中的lifecycle参数,我们能够在指定的精确阶段或事件发生之时,执行该插件。如果没有指定lifecycle的话,那么按照默认的情况,插件将会在on_create之时执行。你可以参考这里的详细文档,以了解如何搭建Docker服务的发现功能。如下是支持的生命周期阶段:
我们将会看到通过cluster_size参数能够指定要搭建容器的数量(它们具有相同的应用依赖)。
host参数能够用来指定想要部署容器的主机。当创建集群的时候,如果选择了Weave作为网络层,那么就可以确保应用服务器集群的高可用性,它们会跨多个主机(或region),允许我们遵循关联性规则(affinity rule),比如说能够将数据库运行在不同的主机上。如下是host参数所支持的值:
除此之外,通过引用其他镜像的环境变量,用户可以创建跨镜像的环境变量绑定。在本例中,我们也使用了多个这样的绑定——包括ACCOUNT_CMD_IP={{accountscommandside | ip}}——Account Creation微服务容器的IP是在请求时动态解析的,它还用来确保Nginx能够建立与该微服务的连接。
如下是所支持环境变量值列表:
`nginx: image: dchq/nginx-microservices:latest publish_all: true mem_min: 50m host: host1 plugins: - !plugin id: Gl5Hi restart: true lifecycle: on_create arguments: - ACCOUNT_CMD_IP={{accountscommandside | ip}} - ACCOUNT_CMD_PORT={{accountscommandside | port_8080}} - ACCOUNT_TRANSFER_IP={{transactionscommandside | ip}} - ACCOUNT_TRANSFER_PORT={{transactionscommandside | port_8080}} - ACCOUNT_QUERY_IP={{accountsqueryside | ip}} - ACCOUNT_QUERY_PORT={{accountsqueryside | port_8080}} accountscommandside: image: dchq/accounts-command-side-service mem_min: 300m cluster_size: 1 host: host1 publish_all: true environment: - EVENTUATE_API_KEY_ID= - EVENTUATE_API_KEY_SECRET= transactionscommandside: image: dchq/transactions-command-side-service mem_min: 300m cluster_size: 1 host: host1 publish_all: true environment: - EVENTUATE_API_KEY_ID= - EVENTUATE_API_KEY_SECRET= accountsqueryside: image: dchq/accounts-query-side-service mem_min: 300m cluster_size: 1 host: host1 publish_all: true environment: - EVENTUATE_API_KEY_ID= - EVENTUATE_API_KEY_SECRET= - SPRING_DATA_MONGODB_URI=mongodb://{{mongodb | container_private_ip}}/mydb mongodb: image: mongo:3.0.4 host: host1`
应用保存之后,我们就可以注册一个云提供商,从而自动化地在集群中提供服务实例和横向扩展,它支持12中不同的云终端,包括VMware vSphere、OpenStack、 CloudStack、Amazon Web Services、Rackspace、Microsoft Azure、DigitalOcean、IBM SoftLayer、Google Compute Engine等。
比如说,要将Rackspace注册为云提供商,导航至Manage > Cloud Providers and Repos,并点击+按钮来选择Rackspace。这里需要提供Rackspace API Key——在Rackspace Cloud Control Panel的Account Settings区域可以得到这个Key。
这样,我们就可以基于一个自动扩展的策略来创建集群,该策略会自动生成新的云服务器。这项任务可以通过导航至Manage > Clusters页面,然后点击+按钮完成。我们可以选择一个基于处理能力的部署策略(capacity-based placement policy),然后选择Weave作为网络层,这样在一个集群内就能实现多主机间安全的、密码保护的跨容器通信。例如,如果使用Auto-Scale Policy的话,我们可能会将VM(或云服务器)的最大数量设置为10。
这样在新创建的集群上,我们就能提供一定数量的云服务器了,这可以通过基于UI的工作流程来实现,也可以定义一个简单的基于YAML的Machine Compose模板,Self-Service Library将会请求使用该模板。
基于UI的工作流程——如果要请求Rackspace云服务器的话,我们可以导航至Manage > Machines,然后点击+按钮来选择Rackspace。在选择完云提供商之后,接下来就可以选择region、大小以及所需的镜像。在Rackspace云服务器上,为了满足一些端口方面的需求,有些端口默认是打开的(如32000-59000留给Docker,6783留给Weave而5672留给RabbitMQ)。然后,可以选择集群并指定云服务器的数量。
基于YAML的Machine Compose模板——我们可以首先为Rackspace创建一个Machine Compose模板,这需要通过导航至Manage > App/Machine,然后选择Machine Compose来实现。
如下就是请求4GB云服务器的模板:
Medium: region: IAD description: Rackspace small instance instanceType: general1-4 image: IAD/5ed162cc-b4eb-4371-b24a-a0ae73376c73 count: 1`
Machine Compose模板所支持的参数概述如下:
在Machine Compose模板保存之后,就可以通过Self-Service Library来请求该机器。我们可以点击Customize,然后点击Cloud Provider和Cluster来请求这些Rackspace云服务器。
在提供了云服务器之后,我们就可以在这些新的云服务器上部署多层的、基于Docker的Java应用。这可以通过导航至Self-Service Library并点击Customize来实现。
选择一个环境标签(如DEV或QE)和你所创建的Rackspace集群,然后点击Run。
在Live Apps页面中,容器名称的旁边会有一个命令行的提示图标。这允许用户使用安全的通信协议,借助agent的消息队列进入到容器中。通过Tenant Admin,可以定义白名单的命令,从而保证用户不会对正在运行中的容器做出有损害的变更。
例如,对于Nginx容器来说,我们使用命令提示来确保app.js文件包含了合适的IP和端口,从而能够访问Docker Java微服务。
在这个截图中,我们使用浏览器中的终端来展现Nginx容器里面/usr/share/nginx/html/js/app.js 文件的内容,可以看到Docker Java微服务的IP和端口已经借助DCHQ的插件框架正确注入到了文件之中。
应用启动并运行之后,开发人员就能监控运行时容器的CPU、内存和I/O,从而在它们超出某个预定义的阈值时得到告警信息。在执行功能性和负载测试的时候,这会特别有用。
我们还可以进行历史数据的监控分析,然后对容器更新或构建部署的相关问题进行分析。这可以通过点击Stats,然后选择自定义的日期范围来查看CPU、内存和I/O的历史状况。
“不可变(immutable)”的容器模型通常是最佳实践,这需要重新构建包含应用代码的Docker镜像,并且在每次更新的时候,生成新的容器。DCHQ提供了自动构建的特性,允许开发人员通过Dockerfiles或包含Dockerfiles的私有GitHub项目自动化地创建Docker镜像。然后,这些镜像会被推送到Docker Private Registry、Docker Hub或Quay上已注册的私有或公开repositories中。
我们可以用新容器自动化地“替换”正在运行中的容器,这些新容器是通过推送到Docker registry的最新镜像生成的。这个过程可以请求式地执行(on-demand),也可以在Docker registry上探测到新镜像时自动化地执行。要使用包含最新JAR文件的新容器替换Docker Java微服务容器时,用户只需点击Actions菜单并点击Replace即可。然后,用户输入镜像的名称,这个镜像就是用来生成新容器的,这个容器会按照相同的应用依赖替换掉正在运行的容器。另外一种方案,用户可以为容器替换指定一个触发器——这个触发器基于简单的CRON的表达式(如预先定义的调度)或基于推送到Docker registry上的最新镜像。
很多开发人员可能会希望使用最新的Java JAR文件来更新运行中的容器。要实现这一点,DCHQ允许开发人员借助Jenkins启用自动构建的流程。在运行的应用上点击Actions菜单,然后选择Continuous Delivery。我们可以选择已经在DCHQ上注册的Jenkins实例,Jenkins的实际任务就是生成最新的JAR文件,一个BASH脚本插件跟踪到这个构建过程并将其部署到一个运行中的应用服务器上。这个策略保存之后,每当构建触发之后,DCHQ都会从Jenkins上获取最新的JAR文件,并将其部署到运行中的应用服务器上。
这样的话,在DEV/TEST环境下,开发人员始终能够将最新的JAR文件部署到运行中的容器里面。
容器化企业级Java应用的主要挑战在于已有的应用组合框架并没有解决掉复杂的依赖、服务发现以及添加服务实例之后(Post-Provision)的自动扩展流程等问题。
DCHQ可以通过托管版本和On-Premise版本进行使用,它解决了上述的所有挑战并简化了企业级Java应用的容器化,DCHQ是通过一个高级应用组合框架来实现的,这个框架支持跨镜像的环境变量绑定,可扩展的BASH脚本插件,这个脚本可以在应用部署的不同生命周期阶段调用,同时,为了实现高可用性,还提供了应用集群的功能,这个集群能够跨多个主机和region,支持自动扩展。
你可以在http://DCHQ.io免费注册或下载DCHQ On-Premise版本,使用内置的多层Java应用模板以及应用生命周期管理功能,如监控、容器更新、扩展/伸缩以及持续交付。
Amjad Afanah是DCHQ的联合创始人,这是一个Docker管理解决方案,关注于企业级应用的建模、部署、服务发现以及生命周期管理。在创建DCHQ之前,Amjad曾经在Oracle和VMware担任高级产品管理职位,当时他推动着云系统管理和应用部署&管理方面的战略性产品。
查看英文原文:Automate Deployment & Management of Docker Cloud/Virtual Java Microservices with DCHQ