天啦噜!看国外大神如何用Docker+Jenkins&CI/CD打造微服务架构?

导读:

Docker、微服务、持续交付是目前编程领域中最受欢迎的话题。在由几十个相互通信的微服务组成的环境中,特别重要的是测试、构建和部署过程的自动化。Docker是一种极好的微服务解决方案,因为它可以创建和运行独立的容器和服务。将Docker容器连接在一起,它们之间能够通信,确保所有的工具和微服务都在同一台机器上运行。

今天,将介绍如何使用流行的软件自动化工具:Jenkins,创建一个基本的持续交付管道,用于示例微服务。

微服务样本

在讨论本文主题之前,首先介绍一些用于微服务创建的架构和工具。此示例包含两个微服务,它们彼此通信(account、customer)、发现服务器(Eureka)和API网关(Zuul)。使用Spring Boot和Spring Cloud框架实现的。源代码可以在GitHub上找到。Spring Cloud支持微服务服务发现和网关,只需要在maven项目配置文件(pom.xml)中定义正确的依赖关系。

下图说明了所采用的解决方案架构。客户、帐户REST API服务、发现服务器和网关都在独立的Docker容器中运行。网关是微服务系统的入口。它与所有其他服务交互。将代理请求到所选的微服务在发现服务中搜索其地址。在存在多个帐户或客户微服务实例的情况下,该请求是用Ribbon和Feign客户端进行负载平衡的。

帐户和客户服务在启动后自己注册到发现服务器中。它们之间也有交互的可能——例如,如果想找到并返回所有客户的帐户细节。

一般来说,Spring框架对微服务提供了全面的支持,包括诸如Ribbon、Hystrix和Eureka等所有的Netflix OSS工具。本文描述了如何实现服务发现、分布式跟踪、负载平衡、日志跟踪ID propagation,以及使用这些解决方案的微服务 API 网关。

Dockerfiles

示例源代码中的每个服务都有一个Dockerfile和Docker映像构建定义。这是很简单的。这里是帐户服务的Dockerfile。我们使用OpenJDK作为基本映像。将目标中的JAR文件添加到映像中,然后使用java -jar命令运行。服务在端口2222上运行,该端口暴露在外面。

MAINTAINER Piotr Minkowski piotr.minkowski@gmail.com
ADD target/account-service.jar account-service.jar
ENTRYPOINT ["java", "-jar", "/account-service.jar"]
EXPOSE 2222

(向右滑动)

我们还必须在JAR清单中设置主类。在模块pom.xml中使用spring-boot-maven-plugin实现。碎片在下面可见。我们还设置了build finalName来从目标JAR文件中删除版本号。对于所有其他微服务,Dockerfile和Maven构建定义非常相似。


account-service org.springframework.boot spring-boot-maven-plugin 1.5.2.RELEASE pl.piomin.microservices.account.Application true repackage

(向下向右滑动)

Jenkins Pipelines

使用Pipeline Plugin为微服务构建持续交付。除了在Jenkins中设置的标准插件外,还需要CloudBees的Docker Pipeline Plugin。这里定义了四条管道,如下图所示。

这是用Groovy语言编写的用于发现服务的管道定义。有五个执行阶段。在Checkout阶段,为项目的远程Git存储库进行更改。然后,使用MVN clean install命令构建项目,并从pom.xml读取Maven版本。在镜像阶段,从发现服务Dockerfile中构建Docker镜像,然后将该镜像推送到本地注册表。在第4步中,运行构建的镜像,默认端口是公开的,主机名对linked docker容器可见。最后,帐户管道开始时没有等待选项,这意味着源管道已经完成,并且不必等待帐户管道执行结束。

node {
withMaven(maven:'maven') { stage('Checkout') { git url: 'https://github.com/piomin/sample-spring-microservices.git', credentialsId: 'github-piomin', branch: 'master' } stage('Build') { sh 'mvn clean install' def pom = readMavenPom file:'pom.xml' print pom.version env.version = pom.version } stage('Image') { dir ('discovery-service') { def app = docker.build "localhost:5000/discovery-service:${env.version}" app.push() } } stage ('Run') { docker.image("localhost:5000/discovery-service:${env.version}").run('-p 8761:8761 -h discovery --name discovery') } stage ('Final') { build job: 'account-service-pipeline', wait: false }
} }

(向下向右滑动)

帐户管道非常类似。主要的区别是在第四个阶段,帐户服务容器与发现容器相关联。我们需要将这些容器链接起来,因为account服务在发现服务器中注册,并且必须能够使用主机名连接。

node {
withMaven(maven:'maven') { stage('Checkout') { git url: 'https://github.com/piomin/sample-spring-microservices.git', credentialsId: 'github-piomin', branch: 'master' } stage('Build') { sh 'mvn clean install' def pom = readMavenPom file:'pom.xml' print pom.version env.version = pom.version } stage('Image') { dir ('account-service') { def app = docker.build "localhost:5000/account-service:${env.version}" app.push() } } stage ('Run') { docker.image("localhost:5000/account-service:${env.version}").run('-p 2222:2222 -h account --name account --link discovery') } stage ('Final') { build job: 'customer-service-pipeline', wait: false }
} }

类似的管道也为客户和网关服务定义。它们在每个微服务的主项目目录中作为Jenkinsfile可用。在pipeline执行过程中构建的每个镜像都被推到本地Docker注册表。在主机上启用本地注册表,需要拉取和运行Docker注册表镜像,并在拉取时使用该注册中心地址作为镜像名称前缀。本地注册表暴露在默认的5000端口上。本地注册表显示在其默认的5000端口上。 可以通过调用其REST API(例如http://localhost:5000 / v2 / _catalog)将推送的镜像列表显示到本地注册表中。 docker run -d --name registry -p 5000:5000 registry
测试 应该启动“发现-服务”管道的构建。这条管道不仅将为发现服务运行构建,而且还将在最后调用下一个管道构建(account-service-pipeline)。

同样的规则被配置为account-service-pipeline,它是调用服务管道和客户服务管道(callgateway-service-pipeline)。

因此,在所有管道完成之后,可以通过调用docker ps命令来检查运行docker容器的列表。你应该已经看到了五个容器:本地注册表和四个微服务。

还可以通过运行命令docker logs(例如dockerlogs帐户)来检查每个容器的日志。 如果一切正常,您应该可以调用服务,如http://localhost:2222/accounts or via the Zuul gateway http://localhost:8765/account/account. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fa3b9e408bb4 localhost:5000/gateway-service:1.0-SNAPSHOT "java -jar /gatewa..." About an hour ago Up About an hour 0.0.0.0:8765->8765/tcp gateway
cc9e2b44fe44 localhost:5000/customer-service:1.0-SNAPSHOT "java -jar /custom..." About an hour ago Up About an hour 0.0.0.0:3333->3333/tcp customer
49657f4531de localhost:5000/account-service:1.0-SNAPSHOT "java -jar /accoun..." About an hour ago Up About an hour 0.0.0.0:2222->2222/tcp account
fe07b8dfe96c localhost:5000/discovery-service:1.0-SNAPSHOT "java -jar /discov..." About an hour ago Up About an hour 0.0.0.0:8761->8761/tcp discovery
f9a7691ddbba registry "/entrypoint.sh /e..." About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp registry

小结

上面介绍了使用Docker和Jenkins进行微服务持续交付的基本示例。你可以很容易地发现提出的解决方案的局限性。例如,我们已经将Docker容器连接在一起,以便在它们之间进行通信,或者所有的工具和微服务都在同一台机器上运行。

对于更高级的示例,可以使用运行在不同机器或Docker容器上的Jenkins slaves,例如用于编排和集群的Kubernetes工具,或者用于模拟多台Docker机器的Docker-in-Docker容器。

2
学习5种微服务模式成为更出色的工程师

对于很多工程师来说,进入微服务领域是很困难的。99%的服务属于五类之一,通过这种方式划分责任考虑如何通过管道服务一起管理特性,就像在Unixshell脚本中一样。

所有的微服务有什么共同之处,域驱动设计之父Eric Evans将其定义为:“(服务)可以消费和生成消息。”

考虑到这一点,对于每个服务模式,会讨论产生或使用消息的类型。

这些消息可以再分为两类:事件和命令。

开始之前,因为上下文很重要,我首先从Matt Walters那里听说了这些微服务模式,它是libraryservicebus的创建者。Servicebus是一个名为NServiceBus的流行.Net库的节点改编,由Udi Dahan创建和推广。

通过Servicebus,可以轻松编写发送和监听命令,并使用AMQP作为通用语言发布和订阅事件,使用JSON有效负载。这意味着其他编程语言可以轻松实现相同的接口,并能够无缝地参与由多种语言编写的部件组成的系统。

不再赘述,5个微服务模式。

1.模型服务(Model Services)

如果想到MVC,那么可以使用这种类型的服务。模型服务是模型应该存在的地方。边界通常是在聚合或实体层面进行,具体取决于域的复杂性。

模型服务使用与上下文相关的消息。例如,如果有一个库存服务,与消耗相关的命令消息将是inventory.product.create或inventory.product.increaseStock。作为响应,你希望生成一些事件消息,以便系统的其余部分能够了解模型是如何变化的,并响应这些更改。本例中生成的事件消息将是inventory.product.created和inventory.product.stockLevelIncreased。

2. Denormalizer服务

除了分布式系统之外,Denormalizer正是关系数据库所做的事情。它们将多个规范化的输入源合并到一个可读的数据结构中,客户端可以使用这些数据结构。

例如,假设一个电商应用。当库存水平增加或减少,或在库存中可用时,应用程序应该知道它。

想象一下,如果你是应用工程师,他们使用的是与MongoDB类似的东西——他们只是从外部系统获得了实时的库存,而无需编写一行代码。这也适用于RethinkDB和GraphQL订阅!

如果团队需要在Kafka中为大数据提供数据,只需添加一个Kafka的denormalizer服务。

3.网关服务

网关服务非常类似于Denormalizers。但是,它不是连接到数据库,而是与API连接。

最近在和一个叫LiftIgniter的推荐引擎合作,库存需要同步。该服务订阅inventory.product.updated 和inventory.product.added 事件,并将格式化数据发布到适当的端点。

后来,又增加了一项服务,监听相同的事件,并通过建立Magento网关服务,保持更新库存的水平。

4.Ingestor服务

到目前为止,我们讨论的都是通过系统传播的数据,或者在模型服务中创建的数据。但是,经常需要将外部数据输入到系统中。从概念上讲,来自外部源的数据需要被输入到系统其它部分所讲的通用语言中。

Ingestor服务通常只会产生信息。这些服务通常包括通过HTTP接收API POST,或者运行CRON作业,并在一个时间间隔内抓取。获取或接收的数据随后使用通用语言(AMQP w/ JSON)发布到系统中。

5.适配器服务

适配器服务是更少见,但值得一提。与网关服务类似,适配器使用消息,使用该数据来调用系统上的库。这个例子是使用ImageMagick这样的图形处理工具。ImageMagick是一个强大的工具,但是没有Node.js绑定。适配器服务通过执行子进程来解决这个问题,然后以系统的通用语言生成消息。

API服务

API服务应该保持轻量级。如果您正在将一大堆业务逻辑构建到API中,那么正在构建一个庞然大物。 它比我们用“n层”架构看到的应用程序和服务器的组合稍好一些,但最终导致臭名昭著的“大泥球”。

要实现这一点,可以使用上面的 Denormalizer 服务,将数据的查询效率视图映射到API读取的数据库中。这就产生了一个单向的数据流。

Unidirectional Systems

使用上述模式可以让企业在单向工作流中使用不可变事件。如果你已经进入应用程序开发,肯定熟悉Redux如何改变了状态管理的游戏。有一个存储在组件树下的状态可以轻松地解释操作如何影响状态,因为它们是所有发生在集中位置的简单的不可变事实。

如果遵循上述模式,将使用更复杂的称为命令查询责任隔离(Command Query Responsibility Segregation ,CQRS)的方法。命令是由模型服务消费的,而事件的生成则是由Denormalizer或网关服务所消耗的。然后对读模型进行查询。

因为使用的是不可变消息,这使得事件采购成为构建模型服务的完美模式。值得一提的是Matt Walters的另一个创造,一个名为[sourced]的微观框架,与servicebus完美协调,可以轻松添加事件采购功能来消费服务的事件,并持久存储到数据库中。

原文链接: 1、Microservices With Continuous DeliveryUsing Docker and Jenkins
https://dzone.com/articles/microservices-continuous-delivery-with-docker-and
2、Learningthese 5 microservice patterns will make you a better engineer
https://hackernoon.com/learning-these-5-microservice-patterns-will-make-you-a-better-engineer-52fc779c470a

添加小数微信:xiaoshu062 备注公司、姓名、职位 小数将拉您进入相应技术群