基于JIRA工具的需求研发管理

随着互联网金融企业的业务发展,每研发一个新产品(即需求),都会涉及产品、需求、安全、开发、测试、运维等多个部门的共同协作。如何提升跨部门工作的协同和效率,对于一家追求快速创新的互联网金融企业来说显得尤为重要。

当前主流的的管理工具有Redmine、ITSM、TechExcel、JIRA等,除JIRA外都提供统一的标准化流程,无法做到针对不同的组织或团队进行个性化的定制。

早期JIRA在我司仅作为一个专业的Bug管理工具来使用,在逐步摸索的过程中发现JIRA也具备需求管理、敏捷研发管理、流程体系管理
、产品Bug跟踪等功能。JIRA的优势在于可以根据不同的团队和制度流程,从项目、问题类型、工作流、界面、过滤器、分析统计报表、个性化页面导航等维度实现随需定制。

现阶段我司将JIRA作为需求研发的管理工具,实现了需求从提出到分析进而到研发测试的统一管理,并与Confluence对接实现文档流程一体化。让各部门能及时了解需求的排期及研发进度,降低沟通成本,提高沟通效率。

现分享一下JIRA实现需求研发管理的具体配置:

通过JIRA工具,我们实现了需求研发任务的可视化管理。举个简单例子:利用JIRA中的时间跟踪字段,可以对研发任务的进度情况、人力情况进行统计,并以甘特图或燃尽图的形式进行展示,便于管理者了解项目研发的整体进度情况。然而同一个需求下的研发测试任务之间普遍存在互相依赖的关系,我们采用父子任务以及任务状态的强校验形式对这类研发任务进行管理。在前期需求分析时创建子任务,若子任务没有完成,作为父任务的需求工单将无法提交发布,从而避免因模块间依赖关系而导致的发布质量问题。

针对每个公司或者每个项目的不同情况,JIRA的工作流和界面制定都是灵活的。项目经理可以在JIRA中定制出适合本公司项目的流程,各实施人也可以在切身使用过程中提出优化建议,实现流程的不断改进完善。

除了“独角兽”,微服务架构还适合哪些公司

微服务已经从少数几个“独角兽”公司的内部开发实践变成了广为人知的架构模式,很多开发者正在考虑是否采用微服务架构。也有很多人认为,为了从DevOps中获得好处,采用微服务架构是必要的前提。

在过去几年,我们见证了很多促进微服务成形的新技术,它们在与面向服务架构(SOA)寻求差异化的同时却也巩固了它们之间的关系。有些人认为,如果采用微服务的组织没有做出相应的调整,那么微服务技术和方法论可能不会起到太大作用。

InfoQ与五位专家从不同的角度针对微服务当前的状况展开讨论,预测了微服务的发展趋势,专家们也分享了他们在微服务开发中获得的各种经验。

参与讨论的专家:

  • Chris Richardson —— 开发者和架构师,Java Champion,《POJO in Action》的作者
  • James Lewis —— ThoughtWorks技术委员会成员
  • Martijn Verburg —— jClarity的CEO和联合创始人
  • Christian Posta —— Red Hat首席架构师,《Microservices for Java Developers》的作者
  • Adam Bien —— Java(SE/EE/FX)咨询师和布道者

我们关注微服务已经好多年了,从一开始到现在,我们从中学到了什么?

Richardson:“微服务”是一个很糟糕的名字。它强调了服务的大小,导致开发人员创建了很多粒度很小的服务,每个服务拥有一个单独的REST端点。不仅如此,这个名字还暗示了微服务在开发者心目中的重要位置。例如,人们会说“我们可以用微服务来解决这个问题”,我也看到了越来越多的“某某微服务框架”,而实际上,这些框架跟微服务架构不一定有太多联系,它们只是简单的Web框架。

使用“微服务架构”这个名字会更恰当些。它是一种架构风格,它把一系列协作的服务组织成一个系统来支撑业务。

Lewis:我认为,微服务的大范围采用造成了一定程度的语义折射。我和Martin Fowler对微服务进行了清晰的定义,微服务的特性可以帮助公司走向成功。我与很多大型公司进行过交流,他们想要采用微服务架构,他们接受了微服务的第一个特性,对服务进行组件化,但他们并没有根据微服务的其它特性对组织结构做出相应调整,特别是对于产品、业务能力和去中心化的监管来说。我个人认为组织调整是成功实施微服务的关键因素。

Verburg:太多了!不过我挑几个我最喜欢的:

  • 服务发现 —— 不管是在开发时还是在运行时,服务发现比我们想象得要难得多。我经常看见整个团队的开发人员在争论“下一步应该把消息发到哪里”。
  • 分布式跟踪 —— 我们很难通过简单的方式完成对业务逻辑和事务的跟踪。例如,如果一个业务逻辑需要经过十个以上的微服务,而且这些微服务使用了不同的技术,那么该如何为此创建一条跟踪信息?
  • 分布式架构 —— 基于微服务的应用一般都是分布式的,需要横向伸缩和负载均衡。传统单体应用开发人员和系统管理员必须学习一系列新的技能。例如,如何对Websocket连接进行负载均衡?要知道,Websocket连接是双向且“持久”的。

Posta:现在微服务的概念如日中天,我希望我们能够弄明白一件事情,乌托邦式的架构是不存在,简单地使用时髦技术并不等于微服务,而且组织的沟通结构与服务架构有着更深层次的关系,这个联系比我们先前意识到的要更加紧密。

还有一点很关键,在微服务架构里,我们应用了很多分布式的理论和实践,它们都是我们从过去的40年经验里学习而来的,多年来它们鲜有变化,我们只是用它们来解决新的业务问题。

Bien:我花了很多时间在Java EE项目上。在2009年推出的Java EE 6成为推进微服务架构发展的主力。在过去,我们把业务逻辑打包成WAR,不过Java EE 6打破了这条规则。在2009年,我们把我们的项目架构称为“非共享式架构”,那时“微服务”这个名词还没有出现。

在当时,监控和压力测试是一个艰难的任务。有了微服务,现在可以专注于压力测试、系统测试,并对各种使用场景进行监控。在微服务之前,很多项目专注于使用单元测试来提高代码覆盖率。而这一切开始在发生变化。

早期Java EE 6项目和现在的项目之间存在的最大区别在于通信协议的不同。在2009,我们更多地依赖二进制RPC协议,而现在默认使用JAX-RS(REST/HTTP)和Websocket。

最开始是那些“独角兽”在驱动微服务,你们认为现在还是这种情况吗?如果不是,那么谁是继任者呢?

Richardson:是的。大部分关于微服务架构的大会似乎都是在谈论“关于Netflix/Uber/Slack/Twitter……的微服务架构主题”。一方面,这些讨论是很有意义的,而且促进了微服务架构的传播。另一方面,这让很多开发人员认为微服务架构是为了解决应用程序的伸缩性问题,但实际上,它解决的是复杂性问题。总的来说,能够从主流公司听到关于他们使用微服务的经验是很有价值的。

Lewis:是的,我认为这些公司仍然处在不可取代的位置上。他们为微服务带来了很多先进的东西。它不是一种“101”式的架构风格,当微服务架构被大规模使用时,你就能明显地看到它的好处。

Verburg:他们还是的。BBC、Netflix、Twitter、Amazon这类公司都是基于微服务架构的,因为它们有横向伸缩的需求。不过这也是大部分IT组织在盲目跟风时无法解决的一个主要问题。“我们真的需要微服务吗?我们的规模需要使用微服务吗?我们的业务需要微服务吗?”对于很多组织来说,答案是否定的。

Posta:引用Branden Williams博士的一句话“再也不存在什么独角兽了,只有那些蹦向胶水工厂的纯种马……”那些互联网公司已经向我们展示过这一点,不过我认为在传统企业领域(FS、制造商、零售商,等等),还是有很多公司正在展示他们使用新技术快步前进的能力。

Bien:最开始没有人知道“微”意味着什么。关于服务大小的讨论是没有意义的。在Java EE里,一个微服务就是一个WAR包,它们通常由“单披萨”团队创建(“双披萨”的团队规模太大了)。

在我看来,继任者是那些基于微服务的企业项目。独角兽来了又去,如果他们只是在刚开始获得成功,那么对于他们的成功就很难给予平价。企业项目会持续更长时间。

有些公司将微服务架构用于全新的应用开发,而有些则关注如何将单体应用迁移到微服务架构。对于这两种情况,微服务的原则是否同时适用于架构师和开发人员?

Richardson:对于大型的复杂系统来说,不管是哪一种情况,微服务架构都是适用的。这要取决于实际的开发场景。例如,对于一个初创公司来说,他们还处在探索业务模型的阶段,那么应该更倾向于使用单体架构。

我认为大部分关键性的业务应用都是大型的复杂单体应用。这些业务无法快速演化,只能循序渐进地将其迁移到微服务架构。

Lewis:我参与过的团队经历过上述两种开发场景。对于迁移开发来说,迁移到微服务通常是有好处的,因为它会给我们带来更多的选择,而且它们包含了之前系统的功能。对于全新的开发来说,就要考虑功能和跨功能需求,以及系统所运行的环境。有时候可以使用微服务架构,但有时候不是必需的。

Verburg:原则都是适用的,但会有一些折衷。完全分解一个单体应用是终极目标,但应用在它生命周期的大部分时间里都会处于微服务和单体共存的状态(有些人把这称为怪物Cthulu的化身)。例如,微服务纯化论者在对单体应用进行迁移时,他们会把原则撇在一边,仍然会“通过存储过程来传递消息”,直到他们把整个应用重构完毕。

这个时候集成测试变得很重要。

Posta:从它们对开发速度的影响来说,这些原则是相似的。你可能会有很多全新的开发项目,不过难点在于如何找到它们与现有系统的结合点,从而对其进行安全的扩展、加速和创新。

Bien: 2016年,大部分用户对于引入微服务架构是抱着很大希望的,他们希望微服务能够带来更好的可维护性,并降低成本。微服务并不缺乏受众,只是有太多人盲从,缺乏好的实现模式。

把一个超级复杂的单体分解成更加复杂的小型单体只会让事情变得更糟。对于已有的项目来说,首先要避免盲从,并重新对领域概念进行分析。将一个精益的单体分解成独立的单元完全是一件可有可无的事情。

对于新项目来说,首先要聚焦在业务逻辑上,在最开始的迭代中可以使用单体。只有在你对优缺点做过明确的了解之后才考虑引入微服务。毕竟交付一个精益的单体应用仍然是最为简单的方案。

不管怎样,新项目和已有项目都需要聚焦业务逻辑。

你们能列出关于微服务的5个要做和5个不要做的注意事项吗?

Richardson:最重要的一点是要知道微服务并非银弹。你需要仔细评估你的应用,并做出权衡。

Lewis

要做的:

  • 监控,监控,监控。
  • 做好服务的独立部署。
  • 倾向于使用快速变更和canary部署而非集成测试。
  • 倾向于编排而非编配。
  • 对调用结构进行限制。系统的服务越多,就越难保证可用性。

不要做的:

  • 不要一下子构建中500个服务,而是先构建合理数量的服务,最起码当前的基础设施能够支撑得起。
  • 不要把它们看成银弹。要构建好微服务系统,你需要了解更多的分布式计算知识。
  • 不要被厂商的万灵油蒙蔽了双眼,面向服务架构当初就是这么死掉的。
  • 不要忽略了可替换性。微服务要小到可以直接被替换掉。
  • 不要使用分布式事务。

Verburg

要做的:

  • 确保你的团队使用的是敏捷开发模式。
  • 确保你的团队具有DevOps文化。
  • 在构建整个系统之前先构建3个互相交互的服务原型,找出实现非功能需求的解决方案,比如安全问题、服务发现、健康监控、回压、失效备援,等等。
  • 让工程师为每个服务选择合适的技术,这是微服务的一个主要优势。关注集成测试。

不要做的

  • 不要因为Netflix使用了微服务你也跟着用。
  • 忽略了数据一致性。“我们的微服务架构没有ACID事务,让你的金钱蒙受损失我们感到很抱歉”,这样的情况是不能被接受的。
  • 忽略了基础设施需求,甚至忽略了开发者的开发环境。所以要尽快让开发人员可以在一个模拟的生产环境里进行开发。
  • 忽视了命名的重要性。一旦发布了公开的API,你就被牢牢地绑在一起了。另外,不要忘了为你的API添加版本管理。
  • 抛弃多年的开发经验和已经写好的业务逻辑。要知道,微服务开发是一个演化的过程,不是重新发明轮子的过程。

Posta

要做的

  • 对微服务架构的实施进行评审,并把它作为指南。微服务可以用于衡量团队在不影响其他服务的情况下能够以多快的速度做出变更和部署。可以关注一些指标,比如构建的次数、部署的次数、缺陷的个数、部署的审批时间、恢复服务的平均时间,等等。
  • 为功能团队建立反馈闭环。对系统或服务做出变更,却不知道变更将会带来怎样的影响,这样做一点好处都没有。让开发人员和功能团队尽可能地接近用户(或者站在用户的角度),这样他们就可以更直观地感受他们的系统给用户带来的痛苦。
  • 关注数据。数据是公司的生存之本。在构建微服务时,要注意用例边界、事务边界、一致性问题,以及数据的处理(数据流、数据存储,等等)。
  • 赋予微服务开发团队自主权和自由,让他们担起职责。为他们提供自助的工具服务、API服务和基础设施服务。
  • 让微服务仪表化、可调试,并提供度量指标,把测试作为一等公民,而不是马后炮。

不要做的

  • 不要简单地复制独角兽公司的模式,要找到他们成功的原理,并把这些原则作为指引。只是简单地采用Netflix的技术不会让你的公司也成为Netflix。
  • 不要试图通过微服务来降低成本,微服务能够带来创新和业务成果,但不能用来最小化运营成本,这个跟传统的IT不一样。
  • 不要为了分解系统而分解系统。随意分解系统可能会让你的系统变成难以伸缩的分布式单体系统,事务处理会成为大难题。
  • 不要忽略了分布式系统存在的陷阱以及在系统集成方面存在的挑战。
  • 如果在CI/CD、API、DevOps、自助平台、自助团队等方面存在问题,那么就不要强行使用微服务架构。在使用微服务架构前要先了解微服务的原则和最佳实践。使用微服务本身不是目的,通过微服务打造具有创新能力且能够快速行动的团队才是目的。所以首先要打好实施微服务的基础。

Bien

要做的

  • 对容器部署技术进行评估。容器技术有很大优势。
  • 关注业务逻辑。
  • 对关键用例或性能指标进行监控。
  • 关注系统测试,而不是单元测试。
  • 通过CI/CD进行自动化。

不要做的

  • 不要盲目复制Netflix、Twitter、Facebook或者Google的做法,除非你也有他们那样的规模和需求。
  • 不要忽视缓慢的部署过程。部署一个大的WAR包要比一个小包要花更多时间。效率很重要。
  • 不要尝试在微服务间协调事物。
  • 不要在新项目里使用太多技术。少即是多。每一个依赖项都会拖慢部署速度,它们还需要额外的安全审计和缺陷修复工作。从来不存在“免费”的依赖项。
  • 如果可以不使用分布式,精益的单体会是更好的选择。

HTTP或REST/HTTP通常被认为是微服务事实上的通信标准,不过最近有很多组织在谈论使用异步或消息服务作为取代方案,你们是怎么看待这个问题的?

Richardson:是的。HTTP/REST是目前主要的IPC通信协议。它为人们所熟知,使用起来很简单。不足之处在于它在服务和客户端之间引入了一定程度的耦合,这在某些情况下多多少少会成为一个问题。例如,在处理一个需要从多个服务获取数据的请求时,就不会有问题,但如果这个请求是一个更新数据的操作,那么就需要使用异步的消息服务来实现事务的最终一致性,也就是sagas。

Lewis:对于我来说,微服务的端点问题不在于是否使用了REST/HTTP,而是微服务之间是否具有统一的接口。我曾经所在的一个团队同时使用了轻量级的消息服务和RESTful框架来实现统一的接口,而且这两种方式都很成功。解决问题的关键在于为问题选择合适的模式。如果你的业务流程具有异步特点,那么就应该使用异步的集成技术。反过来,如果你的问题可以使用map-reduce的模式来处理,那么就应该使用反应式的方案。我们总是尝试寻找简约的方案来解决问题,这完全取决于你自己是怎么想的。

Verburg:我认为,随着时间推移,这两种模式都会流行起来的。以jClarity为例,我们使用了异步的消息服务,不过同时也为开放接口使用了REST/HTTP(S)。Posta:在对微服务系统进行扩展时,它们会表现出类似其他复杂自适应系统的特征(股票交易系统、蚁群系统、社区系统):自治的代理、独立的决策、反馈驱动的自我学习、非线性的迭代,等等。在这类系统里,事件、消息和时间戳让它们看起来更像是一个“异步”模型。时间戳成为这些系统的关注点(系统间的通信通道是不可靠的,这也是一个客观存在的事实),我们必须事先处理好这些问题,并让已知的模型在其它应用里得到扩展。

Bien:在我的项目里,HTTP/REST(一般是JAX-RS)已经足以应付大多数的使用场景。有时候我们也会使用Websocket进行异步通信、P2P(peer-2-peer)交互和消息传递。这些场景更像是例外,而不是规则。

相比传统的开发,微服务架构更偏向于分布式,对于一个微服务新手来说,他应该从哪里开始入手?

Richardson:开发人员依然要使用很多他们熟悉的框架和软件包来开发单独的服务,这个跟过去相比没有多大改变。不过,如果使用了微服务架构,事务管理和查询操作需要用不同的方式来处理。可以参考我最近发表的InfoQ文章和我的网站来了解这方面的问题以及如何解决它们。

微服务架构的核心是为业务能力和业务子领域组织微服务。我推荐阅读Eric Evan的《Domain Driven Design》这本书,他的设计策略都是以微服务架构为中心的。

Lewis:我认为Sam Newman的《Building Microservices》是一本很好的书,我会从这本书读起。如果要了解微服务的背景,我推荐阅读Eric Evans的《Domain Driven Design》,还有Webber、Robinson和Parastatidis合著的《REST in Practice》、Hohpe和Woolf合著的《Enterprice Integration Patterns》,以及Michael Nygard的《Release It!》。关于构建组合系统背后的哲学,我极力推荐Eric Raymond的《The Art of UNIX Programming》。在Martin Fowler的网站上可以找到更多相关资源。

Verburg:对于从传统Java企业开发转到微服务开发的新手来说,microprofile.io社区是一个很好的地方。不管他们从什么地方开始入手,他们必须知道该如何建立基础设施。可以租用一些Linux主机(或者使用Docker),并构建一个“hello world”服务,这个服务与另一个“机器”上的“Hi Back!”服务进行交谈。在这个过程中,你需要使用到HTTP(S)、认证、负载均衡、IP tables、分布式数据存储引擎(比如MongoDB)等等。

在微服务架构里,应用程序代码是最简单的部分,难点在于系统工程。

Posta:这是一个很好的问题。过去40年关于分布式系统的研究和实践是实现微服务架构的核心基础。认识ACID数据库对你的重要程度,了解在采用分布式时所要面临的挑战,这是首要的任务。有很多这方面的论文可以参考,Jim Gray、Peter Bailis、Alan Fekete、Pat Helland、Leslie Lamport,他们的论文我都很喜欢。我主攻系统集成和消息服务,不过这些论文加固了我对基础概念的理解。

Bien:关注领域概念、目标领域和用户。举个例子,最好的Java EE项目只包含业务逻辑,这些业务逻辑只有少量的注解,没有多余的杂七杂八的东西、模式或间接依赖。

忘记以往的模块化概念,让代码保持简洁,小型化的WAR就是最好的模块。

总是假设微服务的基础设施会发生故障,并针对故障场景进行测试,提供最简单的解决方案(在类级别,而不是框架级别)。

是否有一些开发语言或技术可以推荐用于开发微服务?如果有,为什么?是否有一些是需要避免使用的?如果有,又是为什么呢?

Richardson:简而言之,微服务架构是独立于开发语言和框架的。不过有些经典的框架是基于某些语言开发的,这些框架可以用于构建分布式的应用。例如,Java开发者可以在Spring Cloud里使用Eureka/Ribbon来实现客户端的服务发现,也可以使用Hystrix的回路断路器。另外,也有人建议使用一些能够提供服务端发现的部署平台,这样开发者就不需要关注服务发现细节了。

Lewis:过去几年,人们更关注简单的事物,这是个好现象。我喜欢谈论Java的生态系统,那么就从Dropwizard和Spring Boot开始吧。我特别喜欢Dropwizard的设计思想,我记得关于它的宣传词是“一系列不那么糟糕的软件包”,相比那些重型框架,或许这些才是你需要的东西。在生态系统之外,我知道有很多团队在使用Go和Elixir,他们也很成功。不过我不能对Java生态系统做任何评论,因为我对它并不了解。

Verburg

  • microprofile.io、Vert.x、Spring Boot、JHipster对Java开发者来说都是很好的资源。在jClarity,我们使用了Vert.x,它是一种基于JVM的混合型语言,非常适合用于开发微服务,我极力推荐。
  • Scala开发者可以使用Akka。
  • Java开发者可以使用NodeJS。

Posta:使用合适的东西可以帮助你走得更快。在我看来,Go、Java、.NET、NodeJS是最为常用的微服务开发语言。

在企业里,如果你需要将Java服务模块化,可以使用一些技术,比如Linux容器和像Dropwizard这样的“微框架”、WildFly Swarm和Spring Boot。如果要使用基于领域驱动的事件框架,那么像Vert.x这样的反应式框架是个不错的选择。其它的跟平台和应用相关的云服务技术,比如Kubernetes、Hystrix和Envoy可以用于解决复杂的分布式系统问题。

Bien: Java已经诞生20多年了,它是一门成熟的开发语言,具有强大的工具和监控能力。Java在一开始就融入了微服务概念,比如Jini/JXTA框架,它们与No-SQL数据库(比如JavaSpaces)混在一起。可以说,Java超前了15年,那个时候市场还没有做好使用这些技术的准备。不过,从1999年以来的那些技术在今天仍然适用。我们并没有重新发明轮子。

我经常会建议将Java EE的应用服务器(Payara、TomEE和Wildfly)作为初创项目的微服务平台。在最初的业务逻辑探索阶段,我们没有必要把时间浪费在这些事情上。在一开始我们要十分注重效率。开发人员会对现代应用服务器的开发效率、内存使用和内建特性感到惊讶。Java SE/EE内建的功能可以完成很多事情,这要超出开发人员的想象。目前我只看到正向的反馈。

Docker是另一个关键因素。Docker结合Java EE是最理想的组合。

你们认为2年之后微服务会有怎样的发展?

Richardson:谁知道呢?早在2012年4月,我做了一场关于当时微服务架构情况的演讲。到目前为止,微服务架构的核心概念并没有发生改变。不过我见证了技术上的巨大变化:比如Docker和AWS Lambda的崛起。所以,我们很难对此做出预言。不过话虽如此,我仍然希望微服务架构能够顺着Gartner的发展曲线一路进入平稳状态。

Lewis:我认为,将来有些公司会因此大赚一笔,而有些公司则在尝试了微服务之后仍然不明白其深层的意义,并给自己招来大麻烦。我希望能够出现一些很酷的工具,用于服务可视化、请求消息跟踪和更智能的故障检测。那样的话就太棒了!

Verburg:从趋势周期来看,曲线会先到达顶峰,然后再跌入谷底。有部分组织会沿着斜坡前进。

Posta:如果你不介意,我想重新组织一下你的问题:假设给微服务2年时间,那么它是否可以达到给面向服务架构4年时间所达到的相同状态?答案是肯定的。

不同之处在于,互联网公司和初创企业把技术作为抗击传统企业的主要武器(游戏规则已经发生了变化)。至于技术趋势,它跟面向服务架构不会有太多的不同。只是那些没有采用DevOps、敏捷和微服务的公司会落后于那些采用了这些技术的公司。

Bien:有个开发者在一个Java User Group会议上自豪地声称:“我的团队只有4个人,但我们交付了35个微服务”。还有个咨询顾问向我演示了他如何在笔记本上运行70个JVM实例。

我的问题是:“你们为什么要这么做?这样做给你们的服务带来什么好处了吗?”

我期望看到第一批言过其实的微服务项目会比预想的需要耗费更高的成本。这些项目在一开始成为一些文章或大会的谈资,但却降低了开发者的效率。

糟糕的大会把事情引向了另一个极端。我不知道以后会不会出现Macroservice或者Nanoservice。不过我可以肯定的是,它们只是新瓶装旧酒。

讨论总结

在这个虚拟研讨会里,我们了解了微服务当前的状态、微服务的最佳实践和一些关于微服务在未来几年发展情况的预言。我们从这些专家那里得到了关于如何成功实施微服务的各种技术建议,不过我们也要意识到,微服务架构是一种分布式系统,过去几十年的理论和实践经验深藏其中,等待开发者去发现。我们还了解了专家们关于将REST/HTTP作为微服务通信手段的看法,并将其与异步和消息服务进行了对比。

04_MySQL网络安装

说明

  1. 假设安装的服务器为:123.1.1.1

采购了百度云服务器,发现,体验太差了,推荐还是阿里云

安装 Mysql

安装过程使用root操作

  1. 安装依赖软件
sudo yum install -y cmake , make , gcc , gcc-c++ , bison , ncurses , ncurses-devel
  1. 添加库
    vim /etc/yum.repos.d/MariaDB.repo
  2. 添加数据源

系统及版本选择:https://downloads.mariadb.org/mariadb/repositories/#mirror=tuna

# MariaDB 10.3 CentOS repository list - created 2019-03-29 02:33 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.3/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

清理yum缓存

yum clean headers  #清理/var/cache/yum的headers
yum clean packages #清理/var/cache/yum下的软件包
yum clean metadata

yum clean all   #清除Yum缓存
yum makecache   #把服务器的包信息下载到本地电脑缓存起来
yum update   #升级包同时也升级软件和系统内核

释放内存

sync; echo 2 > /proc/sys/vm/drop_caches
sync; echo 3 > /proc/sys/vm/drop_caches

free -m    #查看内存
  1. 安装mariadb
yum -y install MariaDB-server MariaDB-client
  1. 启动数据库
systemctl restart mariadb
systemctl status mariadb

假如密码忘记:
修改/etc/my.cnf文件,在[mysqld]下添加 skip-grant-tables , 再启动mariadb

[client-server]

# [mysqld]
# skip-grant-tables

#
# include all files from the config directory
#
!includedir /etc/my.cnf.d
  1. 登陆
mysql

// 安全配置
mysql_secure_installation

添加防火墙

sudo firewall-cmd --zone=public --permanent --add-service=mysql
sudo systemctl restart firewalld
  1. 重置密码
// 重启服务
systemctl restart mysqld
systemctl status mysqld
  1. 重置密码
mysql -uroot -p
use mysql;
update user set password=password('HouGuiYu@123!@#') where user='root' ;
  1. 开启远程访问
mysql -uroot -p

// %表示是所有的外部机器,如果指定某一台机,就将%改为相应的机器名;‘root’则是指要使用的用户名
grant all privileges on *.* to 'root'@'%' identified by 'HouGuiYu@123!@#' with grant option;

// 运行此句才生效,或者重启MySQL
flush privileges;

安装phpadmin

mysql的界面管理工具,用于mysql的可视化管理

  1. 安装依赖包
yum -y install httpd php php-fpm php-mysql
  1. 升级php
rpm -Uvh https://mirror.webtatic.com/yum/el7/epel-release.rpm #更新源
rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm
yum remove php-common -y  #移除系统自带的php-common
yum install -y php72w php72w-opcache php72w-xml php72w-mcrypt php72w-gd php72w-devel php72w-mysql php72w-intl php72w-mbstring  #安装依赖包
  1. 安装apache
// 安装
yum install httpd

// 重启
systemctl restart httpd

// 查看运行状态
systemctl status httpd

访问地址:http://123.1.1.1/

  1. 安装phpadmin

此需进入目录 /var/www/html 目录操作

// 下载phpadmin
wget https://files.phpmyadmin.net/phpMyAdmin/4.8.5/phpMyAdmin-4.8.5-all-languages.zip

// 解压
unzip phpMyAdmin-4.8.5-all-languages.zip
  1. 修改名称
mv phpMyAdmin-4.8.5-all-languages phpMyAdmin

SpringCloud微服务迁移至Kubernetes实践

前言
原SpringCloud基础上的微服务已稳定运行近1年,遗留了一些问题不太好处理。原SpringCloud的整理文章见基于SpringCloud微服务的服务平台搭建的一些总结
问题如下:

客户端侧负载均衡在服务实例故障下线时候,不能及时发现,导致请求到故障实例地址造成请求错误,若增加请求重试配置,对于非幂等接口处理困难。
基于SpringCloud Config的配置中心有时候会有不及时刷新svn上的配置信息的情况(需要重新config),没找到这个问题的原因。另外多环境(生产、测试环境独立的配置)、多级配置(本地、配置中心)的组合设置对于组内其他开发者而言,学习成本较大,经常出现用错配置的情况。
若要使用微服务,则必须使用SpringCloud技术栈开发,对开发人员的技术选型是一种不必要的约束,另外大量的SpringCloud应用对与机器内存资源的占用比较严重(一个应用动辄上百兆内存),拆分成大量微服务后对于机器内存的浪费尤为严重。
虽然SpringCloud有Eureka,但是由于各个服务不是在所有物理机器上都起着副本,当服务器故障需要恢复时,仍然需要留意当时机器上运行程序的情况,常常出现机器上残留的旧版本应用被其他人启动起来的情况。无法做到不关心实例的具体部署。
服务的升级较为麻烦,在缺乏有效的自动化部署程序,缺乏有效滚动升级方案,以达到不中断服务的同时逐个替换现有服务实例。
改造方式
部署Kubernetes集群
网上教程挺多的,这里就不详细写了,用的1.11。但是遇到过问题。我们集群用的3.10的kernel,在1.11的kubernetes中网络默认用的ipvs,经常导致kernel panic,后来重新部署时候把ipvs去掉,换回原来的iptables就好了。

SpringCloud应用改造
首先要去除对于eureka的maven依赖和config的依赖,并去除@EnableEurekaClient的注解即可。

其次服务调用方面由于没有Eureka了,FeignClient也需要进行一些变动,但是又要尽量简化开发和修改步骤,如果还得由开发人员记住每个服务的IP和端口那就有点退化的厉害了。我们做如下设置。

1) 应用在Kubernetes内的封装
– 关于端口:每一个微服务在kubernetes内都有一个Deployment负责Pod的部署、端口暴露(Expose出Spring应用的server.port即可)
– 对服务的封装:每个应用还需要一个Service来做服务实例的负载均衡,这样服务之间调用就只要找服务对应的Service就好。Service的ip地址由Kubernetes内部DNS负责解析。服务对外统一用80端口,这样集群内部访问时候就只要写服务名称即可。同时Service的名称和微服务的应用名称一致。

2)FeignClient的改造
原SpringCloud内只要指定serviceName就能通过Ribbon自行负载到对应的实例上,现在则需要通过Kubernetes的service实现调用,由于1)已经约定了Service名称与原服务名称一致,同时端口是80。因此只要加上url就行。
如:

// 这里的url是新增参数
@FeignClient(name=”remote-service”, url=”remote-service”)
public interface RemoteServiceApi {}
环境改造
1)将数据库连接改为Service和Endpoint
这样应用里的数据库连接jdbc就从:

jdbc:10.2.3.2:5433/dbname
改为

jdbc:pg-master/dbname
Service和Endpoint的配置方式:

apiVersion: v1
kind: Endpoints
metadata:
name: pg-endpoint-loadbalancer
subsets:
– addresses:
– ip: 10.2.xxx.xxx
ports:
– port: 4432
protocol: TCP

apiVersion: v1
kind: Service
metadata:
name: pg-endpoint-loadbalancer
spec:
ports:
– port: 5432
targetPort: 4432
protocol: TCP

由此改变的好处在于,数据库切换无需逐个更换应用配置再重启,而是仅需修改集群内Endpoint设置即可。

2)部分通用配置改为ConfigMap
将通用配置,如zipkin配置、redis配置等全局范围内应用都会用到的参数提取到ConfigMap中,在通过EnvFrom的方式挂在在应用的镜像中。此时跑在容器内的镜像就可以通过环境变量获取到这些全局的设置。
比如 bootstrap.properties:

spring.rabbitmq.username=${rabbitmq_username}
环境变量挂载方式:

env:
– name: rabbit_mq_host
valueFrom:
configMapKeyRef:
name: cloud-env
key: rabbit_mq_host
– name: rabbit_mq_address
valueFrom:
configMapKeyRef:
name: cloud-env
key: rabbit_mq_address
– name: redis_cluster
valueFrom:
configMapKeyRef:
name: cloud-env
key: redis_cluster

日志改造
原应用日志统一写入${HOME}/logs/${application-name.log}里边,再有logstash采集后发送到elastic search。现在采用容器方式运行后则不再将日志文件与物理机器挂钩,而是在一个Pod内再启动一个filebeat日志采集容器,两个容器共同挂载一个/log/目录。

# 部分yaml
template:
spec:
volumes:
– name: app-logs
emptyDir: {}
containers:
– name: xxxx(application-image)
# ……
volumeMounts:
– name: app-logs
mountPath: /log
– name: filebeat-image
# ……
volumeMounts:
– name: app-logs
mountPath: /log
结构如图所示:

日志采集结构改变为如下图所示:

应用部署方式
考虑到减少其余开发人员的新增学习成本我们将应用的打包发布流程封装成部署脚本,提供给开发人员,开发人员仅需准备jar包、lib依赖(可选)以及配置文件即可。
通过统一的模板和配置脚本将:打包Docker image、上传私服、生成yaml文件等自动完成。
为自动化部署和升级需要,yaml中默认采用Alwayl的Image拉去imagePullPolicy: Always,同时增加rollout 升级的配置。这样研发人员在部署应用时就能自动进行滚动升级,并支持回滚到旧版本。

spec:
replicas: REPLICAS
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1

发布于部署的关系如下所示:
一键发布脚本会自动完成打包docker image、推送私服、生成yaml文件三件事。研发人员仅需应用生成的yaml文件即可完成部署任务。

集群服务入口
原服务: 互联网 -> CDN -> 防火墙 -> Nginx -> zuul网关 -> 微服务
改为: 互联网 -> CDN -> 防火墙 -> Nginx -> traefik Ingress -> zuul网关 -> 微服务。
从traefik之后就已经进入微服务集群了。当然这里nginx仍然保留是因为历史元英,有些端口和应用二级目录共用问题,暂时无法完全由traefik接管。

结束
综上完成全部改造流程,将应用迁移进Kubernetes,并适时切换nginx负载即可。

service 和 kube-proxy 原理

简介

在 kubernetes 集群中,网络是非常基础也非常重要的一部分。对于大规模的节点和容器来说,要保证网络的连通性、网络转发的高效,同时能做的 ip 和 port 自动化分配和管理,并让用户用直观简单的方式来访问需要的应用,这是需要复杂且细致设计的。

kubernetes 在这方面下了很大的功夫,它通过 servicednsingress 等概念,解决了服务发现、负载均衡的问题,也大大简化了用户的使用和配置。

这篇文章就讲解如何配置 kubernetes 的网络,最终从集群内部和集群外部都能访问应用。

跨主机网络配置:flannel

一直以来,kubernetes 并没有专门的网络模块负责网络配置,它需要用户在主机上已经配置好网络。kubernetes 对网络的要求是:容器之间(包括同一台主机上的容器,和不同主机的容器)可以互相通信,容器和集群中所有的节点也能直接通信。

至于具体的网络方案,用户可以自己选择,目前使用比较多的是 flannel,因为它比较简单,而且刚好满足 kubernetes 对网络的要求。我们会使用 flannel vxlan 模式,具体的配置我在博客之前有文章介绍过,这里不再赘述。

以后 kubernetes 网络的发展方向是希望通过插件的方式来集成不同的网络方案, CNI 就是这一努力的结果,flannel 也能够通过 CNI 插件的形式使用。

kube-proxy 和 service

配置好网络之后,集群是什么情况呢?我们可以创建 pod,也能通过 ReplicationController 来创建特定副本的 pod(这是更推荐也是生产上要使用的方法,即使某个 rc 中只有一个 pod 实例)。可以从集群中获取每个 pod ip 地址,然后也能在集群内部直接通过 podIP:Port 来获取对应的服务。

但是还有一个问题:pod 是经常变化的,每次更新 ip 地址都可能会发生变化,如果直接访问容器 ip 的话,会有很大的问题。而且进行扩展的时候,rc 中会有新的 pod 创建出来,出现新的 ip 地址,我们需要一种更灵活的方式来访问 pod 的服务。

Service 和 cluster IP

针对这个问题,kubernetes 的解决方案是“服务”(service),每个服务都一个固定的虚拟 ip(这个 ip 也被称为 cluster IP),自动并且动态地绑定后面的 pod,所有的网络请求直接访问服务 ip,服务会自动向后端做转发。Service 除了提供稳定的对外访问方式之外,还能起到负载均衡(Load Balance)的功能,自动把请求流量分布到后端所有的服务上,服务可以做到对客户透明地进行水平扩展(scale)。

而实现 service 这一功能的关键,就是 kube-proxy。kube-proxy 运行在每个节点上,监听 API Server 中服务对象的变化,通过管理 iptables 来实现网络的转发。

NOTE: kube-proxy 要求 NODE 节点操作系统中要具备 /sys/module/br_netfilter 文件,而且还要设置 bridge-nf-call-iptables=1,如果不满足要求,那么 kube-proxy 只是将检查信息记录到日志中,kube-proxy 仍然会正常运行,但是这样通过 Kube-proxy 设置的某些 iptables 规则就不会工作。

kube-proxy 有两种实现 service 的方案:userspace 和 iptables

  • userspace 是在用户空间监听一个端口,所有的 service 都转发到这个端口,然后 kube-proxy 在内部应用层对其进行转发。因为是在用户空间进行转发,所以效率也不高
  • iptables 完全实现 iptables 来实现 service,是目前默认的方式,也是推荐的方式,效率很高(只有内核中 netfilter 一些损耗)。

这篇文章通过 iptables 模式运行 kube-proxy,后面的分析也是针对这个模式的,userspace 只是旧版本支持的模式,以后可能会放弃维护和支持。

kube-proxy 参数介绍

kube-proxy 的功能相对简单一些,也比较独立,需要的配置并不是很多,比较常用的启动参数包括:

参数 含义 默认值
–alsologtostderr 打印日志到标准输出 false
–bind-address HTTP 监听地址 0.0.0.0
–cleanup-iptables 如果设置为 true,会清理 proxy 设置的 iptables 选项并退出 false
–healthz-bind-address 健康检查 HTTP API 监听端口 127.0.0.1
–healthz-port 健康检查端口 10249
–iptables-masquerade-bit 使用 iptables 进行 SNAT 的掩码长度 14
–iptables-sync-period iptables 更新频率 30s
–kubeconfig kubeconfig 配置文件地址
–log-dir 日志文件目录/路径
–masquerade-all 如果使用 iptables 模式,对所有流量进行 SNAT 处理 false
–master kubernetes master API Server 地址
–proxy-mode 代理模式,userspace 或者 iptables, 目前默认是 iptables,如果系统或者 iptables 版本不够新,会 fallback 到 userspace 模式 iptables
–proxy-port-range 代理使用的端口范围, 格式为 beginPort-endPort,如果没有指定,会随机选择 0-0
–udp-timeout UDP 空连接 timeout 时间,只对 userspace 模式有用 250ms
–v 日志级别 0

kube-proxy 的工作模式可以通过 --proxy-mode 进行配置,可以选择 userspace 或者 iptables

实例启动和测试

我们可以在终端上启动 kube-proxy,也可以使用诸如 systemd 这样的工具来管理它,比如下面就是一个简单的 kube-proxy.service 配置文件

[root@localhost]# cat /usr/lib/systemd/system/kube-proxy.service
[Unit]
Description=Kubernetes Proxy Service
Documentation=http://kubernetes.com
After=network.target
Wants=network.target

[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/kube-proxy
ExecStart=/usr/bin/kube-proxy \
    --master=http://172.17.8.100:8080 \
    --v=4 \
    --proxy-mode=iptables
TimeoutStartSec=0
Restart=on-abnormal

[Install]
WantedBy=multi-user.target

为了方便测试,我们创建一个 rc,里面有三个 pod。这个 pod 运行的是 cizixs/whoami 容器,它是一个简单的 HTTP 服务器,监听在 3000 端口,访问它会返回容器的 hostname。

[root@localhost ~]# cat whoami-rc.yml
apiVersion: v1
kind: ReplicationController
metadata:
  name: whoami
spec:
  replicas: 3
  selector:
    app: whoami
  template:
    metadata:
      name: whoami
      labels:
        app: whoami
        env: dev
    spec:
      containers:
      - name: whoami
        image: cizixs/whoami:v0.5
        ports:
        - containerPort: 3000
        env:
          - name: MESSAGE
            value: viola

我们为每个 pod 设置了两个 label:app=whoami 和 env=dev,这两个标签很重要,也是后面服务进行绑定 pod 的关键。

为了使用 service,我们还要定义另外一个文件,并通过 kubectl create -f ./whoami-svc.yml 来创建出来对象:

apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami
  name: whoami
spec:
  ports:
    - port: 3000
      targetPort: 3000
      protocol: TCP
  selector:
    app: whoami
    env: dev

其中 selector 告诉 kubernetes 这个 service 和后端哪些 pod 绑定在一起,这里包含的键值对会对所有 pod 的 labels 进行匹配,只要完全匹配,service 就会把 pod 作为后端。也就是说,service 和 rc 并不是对应的关系,一个 service 可能会使用多个 rc 管理的 pod 作为后端应用。

ports 字段指定服务的端口信息:

  • port:虚拟 ip 要绑定的 port,每个 service 会创建出来一个虚拟 ip,通过访问 vip:port 就能获取服务的内容。这个 port 可以用户随机选取,因为每个服务都有自己的 vip,也不用担心冲突的情况
  • targetPort:pod 中暴露出来的 port,这是运行的容器中具体暴露出来的端口,一定不能写错
  • protocol:提供服务的协议类型,可以是 TCP 或者 UDP

创建之后可以列出 service ,发现我们创建的 service 已经分配了一个虚拟 ip (10.10.10.28),这个虚拟 ip 地址是不会变化的(除非 service 被删除)。查看 service 的详情可以看到它的 endpoints 列出,对应了具体提供服务的 pod 地址和端口。

[root@localhost ~]# kubectl get svc
NAME         CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
kubernetes   10.10.10.1    <none>        443/TCP    19d
whoami       10.10.10.28   <none>        3000/TCP   1d

[root@localhost ~]# kubectl describe svc whoami
Name:                   whoami
Namespace:              default
Labels:                 name=whoami
Selector:               app=whoami
Type:                   ClusterIP
IP:                     10.10.10.28
Port:                   <unset> 3000/TCP
Endpoints:              10.11.32.6:3000,10.13.192.4:3000,10.16.192.3:3000
Session Affinity:       None
No events.

默认的 service 类型是 ClusterIP,这个也可以从上面输出看出来。在这种情况下,只能从集群内部访问这个 IP,不能直接从集群外部访问服务。如果想对外提供服务,我们后面会讲解决方案。

测试一下,访问 service 服务的时候可以看到它会随机地访问后端的 pod,给出不同的返回:

[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-8fpqp
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-c0x6h
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-8fpqp
[root@localhost ~]# curl http://10.10.10.28:3000
viola from whoami-dc9ds

默认情况下,服务会随机转发到可用的后端。如果希望保持会话(同一个 client 永远都转发到相同的 pod),可以把 service.spec.sessionAffinity 设置为 ClientIP

NOTE: 需要注意的是,服务分配的 cluster IP 是一个虚拟 ip,如果你尝试 ping 这个 IP 会发现它没有任何响应,这也是刚接触 kubernetes service 的人经常会犯的错误。实际上,这个虚拟 IP 只有和它的 port 一起的时候才有作用,直接访问它,或者想访问该 IP 的其他端口都是徒劳。

外部能够访问的服务

上面创建的服务只能在集群内部访问,这在生产环境中还不能直接使用。如果希望有一个能直接对外使用的服务,可以使用 NodePort 或者 LoadBalancer 类型的 Service。我们先说说 NodePort ,它的意思是在所有 worker 节点上暴露一个端口,这样外部可以直接通过访问 nodeIP:Port 来访问应用。

我们先把刚才创建的服务删除:

[root@localhost ~]# kubectl delete rc whoami
replicationcontroller "whoami" deleted

[root@localhost ~]# kubectl delete svc whoami
service "whoami" deleted

[root@localhost ~]# kubectl get pods,svc,rc
NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   10.10.10.1   <none>        443/TCP   14h

对我们原来的 Service 配置文件进行修改,把 spec.type 写成 NodePort 类型:

[root@localhost ~]# cat whoami-svc.yml
apiVersion: v1
kind: Service
metadata:
  labels:
    name: whoami
  name: whoami
spec:
  ports:
    - port: 3000
      protocol: TCP
      # nodePort: 31000
  selector:
    app: whoami
  type: NodePort

因为我们的应用比较简单,只有一个端口。如果 pod 有多个端口,也可以在 spec.ports中继续添加,只有保证多个 port 之间不冲突就行。

重新创建 rc 和 svc:

[root@localhost ~]# kubectl create -f ./whoami-svc.yml
service "whoami" created
[root@localhost ~]# kubectl get rc,pods,svc
NAME        DESIRED   CURRENT   READY     AGE
rc/whoami   3         3         3         10s

NAME              READY     STATUS    RESTARTS   AGE
po/whoami-8zc3d   1/1       Running   0          10s
po/whoami-mc2fg   1/1       Running   0          10s
po/whoami-z6skj   1/1       Running   0          10s

NAME             CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
svc/kubernetes   10.10.10.1     <none>        443/TCP          14h
svc/whoami       10.10.10.163   <nodes>       3000:31647/TCP   7s

需要注意的是,因为我们没有指定 nodePort 的值,kubernetes 会自动给我们分配一个,比如这里的 31647(默认的取值范围是 30000-32767)。当然我们也可以删除配置中 # nodePort: 31000 的注释,这样会使用 31000 端口。

nodePort 类型的服务会在所有的 worker 节点(运行了 kube-proxy)上统一暴露出端口对外提供服务,也就是说外部可以任意选择一个节点进行访问。比如我本地集群有三个节点:172.17.8.100172.17.8.101 和 172.17.8.102

[root@localhost ~]# curl http://172.17.8.100:31647
viola from whoami-mc2fg
[root@localhost ~]# curl http://172.17.8.101:31647
viola from whoami-8zc3d
[root@localhost ~]# curl http://172.17.8.102:31647
viola from whoami-z6skj

有了 nodePort,用户可以通过外部的 Load Balance 或者路由器把流量转发到任意的节点,对外提供服务的同时,也可以做到负载均衡的效果。

nodePort 类型的服务并不影响原来虚拟 IP 的访问方式,内部节点依然可以通过 vip:port 的方式进行访问。

LoadBalancer 类型的服务需要公有云支持,如果你的集群部署在公有云(GCE、AWS等)可以考虑这种方式。

service 原理解析

目前 kube-proxy 默认使用 iptables 模式,上述展现的 service 功能都是通过修改 iptables 实现的。

我们来看一下从主机上访问 service:port 的时候发生了什么(通过 iptables-save 命令打印出来当前机器上的 iptables 规则)。

所有发送出去的报文会进入 KUBE-SERVICES 进行处理

*nat
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

KUBE-SERVICES 每条规则对应了一个 service,它告诉继续进入到某个具体的 service chain 进行处理,比如这里的 KUBE-SVC-OQCLJJ5GLLNFY3XB

-A KUBE-SERVICES -d 10.10.10.28/32 -p tcp -m comment --comment "default/whoami: cluster IP" -m tcp --dport 3000 -j KUBE-SVC-OQCLJJ5GLLNFY3XB

更具体的 chain 中定义了怎么转发到对应 endpoint 的规则,比如我们的 rc 有三个 pods,这里也就会生成三个规则。这里利用了 iptables 随机和概率转发的功能

-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-VN72UHNM6XOXLRPW
-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YXCSPWPTUFI5WI5Y
-A KUBE-SVC-OQCLJJ5GLLNFY3XB -m comment --comment "default/whoami:" -j KUBE-SEP-FN74S3YUBFMWHBLF

我们来看第一个 chain,这个 chain 有两个规则,第一个表示给报文打上 mark;第二个是进行 DNAT(修改报文的目的地址),转发到某个 pod 地址和端口。

-A KUBE-SEP-VN72UHNM6XOXLRPW -s 10.11.32.6/32 -m comment --comment "default/whoami:" -j KUBE-MARK-MASQ
-A KUBE-SEP-VN72UHNM6XOXLRPW -p tcp -m comment --comment "default/whoami:" -m tcp -j DNAT --to-destination 10.11.32.6:3000

因为地址是发送出去的,报文会根据路由规则进行处理,后续的报文就是通过 flannel 的网络路径发送出去的。

nodePort 类型的 service 原理也是类似的,在 KUBE-SERVICES chain 的最后,如果目标地址不是 VIP 则会通过 KUBE-NODEPORTS :

Chain KUBE-SERVICES (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 KUBE-NODEPORTS  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

而 KUBE-NODEPORTS chain 和 KUBE-SERVICES chain 其他规则一样,都是转发到更具体的 service chain,然后转发到某个 pod 上面。

-A KUBE-NODEPORTS -p tcp -m comment --comment "default/whoami:" -m tcp --dport 31647 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/whoami:" -m tcp --dport 31647 -j KUBE-SVC-OQCLJJ5GLLNFY3XB

不足之处

看起来 service 是个完美的方案,可以解决服务访问的所有问题,但是 service 这个方案(iptables 模式)也有自己的缺点。

首先,如果转发的 pod 不能正常提供服务,它不会自动尝试另一个 pod,当然这个可以通过 readiness probes 来解决。每个 pod 都有一个健康检查的机制,当有 pod 健康状况有问题时,kube-proxy 会删除对应的转发规则。

另外,nodePort 类型的服务也无法添加 TLS 或者更复杂的报文路由机制。