Service Mesh 浅析:从概念、产品到实践

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Service Mesh 浅析:从概念、产品到实践相关的知识,希望对你有一定的参考价值。

参考技术A 近几年,微服务架构逐渐发展成熟,从最初的星星之火到现在大规模的落地和实践,几乎已经成为分布式环境下的首选架构。然而软件开发没有银弹,基于微服务构建的应用系统在享受其优势的同时,痛点也越加明显。Service Mesh 技术也因此而生,受到越来越多的开发者关注,并拥有了大批拥趸。本文会从概念介绍开始,让大家理解 Service Mesh 技术出现的原因以及愿景;接着会对目前最主流的两个产品 Istio 和 AWS App Mesh 进行详细的比较;最后简要介绍一下我们目前在该领域的一些探索与实践。

Service Mesh - 服务通信的济世良方

Service Mesh 是什么?

Service Mesh(中文译做服务网格)这一概念由 Buoyant 公司的 CEO,William Morg」n 首先提出。2017 年 4 月该公司发布了第一个 Service Mesh 产品 Linkerd,这篇同一时间发表的文章 What’s a service mesh? And why do I need one? 也被业界公认是 Service Mesh 的权威定义。

“A service mesh is a dedicated infrastructure layer for handling service-to-service communication. It’s responsible for the reliable delivery of requests through the complex topology of services that comprise a modern, cloud native application. In practice, the service mesh is typically implemented as an array of lightweight network proxies that are deployed alongside application code, without the application needing to be aware.”

其定义翻译为:Service Mesh 是一个处理服务通讯的专门的基础设施层。它的职责是在由云原生应用组成服务的复杂拓扑结构下进行可靠的请求传送。在实践中,它是一组和应用服务部署在一起的轻量级的网络代理,对应用服务透明。这段话有点晦涩难懂,但只要抓住下面 4 个关键点就能轻松理解:

本质:基础设施层

功能:请求分发

部署形式:网络代理

特点:透明

如果用一句话来总结,我个人对它的定义是:Service Mesh 是一组用来处理服务间通讯的网络代理。

为什么需要 Service Mesh?

上面晦涩抽象的定义很难让你真正理解 Service Mesh 存在的意义。你可能会想,服务间通信(service-to-service communication)无非就是通过 RPC、HTTP 这些方式进行,有什么可处理的?没错,服务间只需要遵循这些标准协议进行交互就可以了,但是在微服务这样的分布式环境下,分散的服务势必带来交互的复杂性,而规模越大的系统其通信越加错综复杂。 分布式计算下的 8 个谬论 很好的归纳了分布式环境下存在的网络问题。而为了解决这些问题,提高系统的容错能力和可用性,出现了服务注册与发现、负载均衡、熔断、降级、限流等等和通信相关的功能,而这些才是 Service Mesh 要真正处理的问题。

Pattern:Service Mesh 这篇文章详细的讲述了微服务架构下通讯处理的演进,由此引出 Service Mesh 出现的意义和核心价值。下图为服务通信演变的过程:

最初,流量管理和控制能力(比如图例中的熔断、服务发现)是和业务逻辑耦合在一起,即便以引用包的方式被调用,依然解决不了异构系统无法重用的问题。

流控功能和业务耦合相当不美好,于是出现了提供这些功能的公共库和框架。但这些库通常比较复杂,无论是学习使用,与业务系统整合、维护都会带来很大的成本。

为避免花费太多时间开发和维护这些通用库,人们希望流量控制能力可以下沉到网络通讯栈的层面,但几乎无法实现。

于是另一种思路出现,就是将这些功能独立成一个代理,由它先接管业务服务的流量,处理完成后再转发给业务服务本身,这就是 Sidecar 模式。

为统一管理 Sidecar,该模式进一步进化,形成网络拓扑,增加了控制平面,演变成 Service Mesh(最后的网格图中,绿色代表业务服务,蓝色代表 sidecar 服务)。

可以说,Service Mesh 就是 Sidecar 的网络拓扑形态,Mesh 这个词也由此而来。(关于 Sidecar 模式这里不做讨论,你可以自行 Google)。

业务系统的核心价值应该是业务本身,而不是服务,微服务只是一种实现手段,实现业务才是目标。现有的微服务架构下,为解决可能出现的网络通信问题,提升系统的弹性,开发人员不得不花费大量时间和精力去实现流量控制相关的非业务需求,不能聚焦在业务本身。而 Service Mesh 的出现解决了这一问题,带来了下面 2 个变革:

解决了微服务框架中的服务流量管理的痛点,使开发人员专注于业务本身;

将服务通信及相关管控功能从业务程序中分离并下层到基础设施层,使其和业务系统完全解耦。

在云原生应用中,面对数百个服务或数千个实例,单个业务链路的请求经由服务的拓扑路径可能会非常复杂,单独处理非常必要。这就是 Service Mesh 的意义所在。

Service Mesh 的主要功能

那么 Service Mesh 到底能带来哪些实用的功能呢?可以把它们归纳为下面 4 个部分:

流量控制:流控是最主要也是最重要的功能,通过 Service Mesh,我们可以为应用提供智能路由(蓝绿部署、金丝雀发布、A/B test)、超时重试、熔断、故障注入、流量镜像等各种控制能力;

-安全:在安全层面上,授权和身份认证也可以托管给 Service Mesh;

策略:可以为流量设置配额、黑白名单等策略;

可观察性:服务的可观察性一般是通过指标数据、日志、追踪三个方式展现的,目前的 Service Mesh 产品可以很容易和和主流的后端设施整合,提供给应用系统完整的监控能力。

通过上面的讲述,我相信 Service Mesh 的概念大家都已经有所了解。接下来我们来介绍两个重要的网格产品,让大家进一步了解 Service Mesh 的产品形态是什么样的。

Istio vs AWS App Mesh - 开源与闭环之争

目前市面上比较成熟的开源服务网格主要有下面几个:Linkerd,这是第一个出现在公众视野的服务网格产品,由 Twitter 的 finagle 库衍生而来,目前由 Buoyant 公司负责开发和维护;Envoy,Lyft 开发并且是第一个从 CNCF 孵化的服务网格产品,定位于通用的数据平面或者单独作为 Sidecar 代理使用;Istio,由 Google、IBM、Lyft 联合开发的所谓第二代服务网格产品,控制平面的加入使得服务网格产品的形态更加完整。

从今年的风向看,作为构建云原生应用的重要一环,Service Mesh 已经被各大云厂商认可,并看好它的发展前景。在 Istio 红透半边天的情况下,作为和 Google 在云服务市场竞争的 Amazon 来说,自然不愿错失这块巨大的蛋糕。他们在今年 4 月份发布了自己的服务网格产品:AWS App Mesh。这一部分内容我们会聚焦于 Istio 和 App Mesh 这两个产品,通过横向的对比分析让大家对 Service Mesh 的产品形态和两大云厂商的策略有一个更深入的认识。

产品定位

从官方的介绍来看,Istio 和 App Mesh 都明确的表示自己是一种服务网格产品。Istio 强调了自己在连接、安全、控制和可视化 4 个方面的能力;而 App Mesh 主要强调了一致的可见性和流量控制这两方面能力,当然也少不了强调作为云平台下的产品的好处:托管服务,无需自己维护。

从某种程度上讲,Istio 是一个相对重一点的解决方案,提供了不限于流量管理的各个方面的能力;而 App Mesh 是更加纯粹的服务于运行在 AWS 之上的应用并提供流控功能。笔者认为这和它目前的产品形态还不完善有关(后面会具体提到)。从与 AWS 技术支持团队的沟通中可以感觉到,App Mesh 应该是一盘很大的棋,目前只是初期阶段。

核心术语

和 AWS 里很多产品一样,App Mesh 也不是独创,而是基于 Envoy 开发的。AWS 这样的闭环生态必然要对其进行改进和整合。同时,也为了把它封装成一个对外的服务,提供适当的 API 接口,在 App Mesh 这个产品中提出了下面几个重要的技术术语,我们来一一介绍一下。

服务网格(Service mesh):服务间网络流量的逻辑边界。这个概念比较好理解,就是为使用 App mesh 的服务圈一个虚拟的边界。

虚拟服务(Virtual services):是真实服务的抽象。真实服务可以是部署于抽象节点的服务,也可以是间接的通过路由指向的服务。

虚拟节点(Virtual nodes):虚拟节点是指向特殊工作组(task group)的逻辑指针。例如 AWS 的 ECS 服务,或者 Kubernetes 的 Deployment。可以简单的把它理解为是物理节点或逻辑节点的抽象。

Envoy:AWS 改造后的 Envoy(未来会合并到 Envoy 的官方版本),作为 App Mesh 里的数据平面,Sidecar 代理。

虚拟路由器(Virtual routers):用来处理来自虚拟服务的流量。可以理解为它是一组路由规则的封装。

路由(Routes):就是路由规则,用来根据这个规则分发请求。

上面的图展示了这几个概念的关系:当用户请求一个虚拟服务时,服务配置的路由器根据路由策略将请求指向对应的虚拟节点,这些节点最终会与集群中某个对外提供服务的 DNS 或者服务名一一对应。

那么这些 App Mesh 自创的术语是否能在 Istio 中找到相似甚至相同的对象呢?我归纳了下面的表格来做一个对比:

App MeshIstio

服务网格(Service mesh)Istio并未显示的定义这一概念,我们可以认为在一个集群中,由Istio管理的服务集合,它们组成的网络拓扑即是服务网格。

虚拟服务(Virtual services)Istio中也存在虚拟服务的概念。它的主要功能是定义路由规则,使请求可以根据这些规则被分发到对应的服务。从这一点来说,它和App Mesh的虚拟服务的概念基本上是一致的。

虚拟节点(Virtual nodes)Istio没有虚拟节点的概念,可以认为类似Kubernetes里的Deployment。

虚拟路由器(Virtual routers)Istio也没有虚拟路由器的概念。

路由(Routes)Istio中的目标规则(DestinationRule)和路由的概念类似,为路由设置一些策略。从配置层面讲,其中的子集(subset)和App Mesh路由里选择的目标即虚拟节点对应。但Istio的目标规则更加灵活,也支持更多的路由策略。

从上面的对比看出,App Mesh 目前基本上实现了最主要的流量控制(路由)的功能,但像超时重试、熔断、流量复制等高级一些的功能还没有提供,有待进一步完善。

架构

AWS App Mesh 是一个商业产品,目前还没有找到架构上的技术细节,不过我们依然可以从现有的、公开的文档或介绍中发现一些有用的信息。

从这张官网的结构图中可以看出,每个服务的橙色部分就是 Sidecar 代理:Envoy。而中间的 AWS App Mesh 其实就是控制平面,用来控制服务间的交互。那么这个控制平面具体的功能是什么呢?我们可以从今年的 AWS Summit 的一篇 PPT 中看到这样的字样:

控制平面用来把逻辑意图转换成代理配置,并进行分发。

熟悉 Istio 架构的朋友有没有觉得似曾相识?没错,这个控制平面的职责和 Pilot 基本一致。由此可见,不管什么产品的控制平面,也必须具备这些核心的功能。

那么在平台的支持方面呢?下面这张图展示了 App Mesh 可以被运行在如下的基础设施中,包括 EKS、ECS、EC2 等等。当然,这些都必须存在于 AWS 这个闭环生态中。

而 Istio 这方面就相对弱一些。尽管 Istio 宣称是支持多平台的,但目前来看和 Kubernetes 还是强依赖。不过它并不受限于单一的云平台,这一点有较大的优势。

Istio 的架构大家都比较熟悉,数据平面由 Envoy sidecar 代理组成,控制平面包括了 Pilot、Mixer、Citadel、Galley 等控件。它们的具体功能这里就不再赘述了,感兴趣的同学可以直接去 官网 查看详细信息。

功能与实现方式

部署

无论是 Istio 还是 App Mesh 都使用了控制平面+数据平面的模式,且 Sidecar 都使用了 Envoy 代理。Istio 的控制平面组件较多,功能也更复杂,1.0.x 版本完整安装后的 CRD 有 50 个左右。架构修改后 Mixer 的一些 adapter 被独立出去,crd 有所降低。下面是最新的 1.4 版本,安装后仍然有 24 个 crd。

而 App Mesh 就简单得多,只针对核心概念添加了如下 3 个 crd,用一个 controller 进行管理。

尽管 Istio 更多的 crd 在一定程度上代表了更加丰富的功能,但同时也为维护和 troubleshooting 增加了困难。

流量控制

尽管两者的数据平面都基于 Envoy,但它们提供的流量控制能力目前还是有比较大的差距的。在路由的设置方面,App Mesh 提供了相对比较丰富的匹配策略,基本能满足大部分使用场景。下面是 App Mesh 控制台里的路由配置截图,可以看出,除了基本的 URI 前缀、HTTP Method 和 Scheme 外,也支持请求头的匹配。

Istio 的匹配策略更加完善,除了上面提到的,还包括 HTTP Authority,端口匹配,请求参数匹配等,具体信息可以从官方文档的虚拟服务设置查看。下面两段 yaml 分别展示了两个产品在虚拟服务配置上的差异。

App Mesh 配置:

Istio 配置:

另外一个比较大的不同是,App Mesh 需要你对不同版本的服务分开定义(即定义成不同的虚拟服务),而 Istio 是通过目标规则 DestinationRule 里的子集 subsets 和路由配置做的关联。本质上它们没有太大区别。

除了路由功能外,App Mesh 就显得捉襟见肘了。就在笔者撰写本文时,AWS 刚刚添加了重试功能。而 Istio 借助于强大的 Envoy,提供了全面的流量控制能力,如超时重试、故障注入、熔断、流量镜像等。

安全

在安全方面,两者的实现方式具有较大区别。默认情况下,一个用户不能直接访问 App Mesh 的资源,需要通过 AWS 的 IAM 策略给用户授权。比如下面的配置是容许用户用任意行为去操作网格内的任意资源:

因此,App Mesh 的授权和认证都是基于 AWS 自身的 IAM 策略。

Istio 提供了两种认证方式,基于 mTLS 的传输认证,和 基于 JWT 的身份认证。而 Istio 的授权是通过 RBAC 实现的,可以提供基于命名空间、服务和 HTTP 方法级别的访问控制。这里就不具体展示了,大家可以通过官网 文档 来查看。

可观察性

一般来说,可以通过三种方式来观察你的应用:指标数据、分布式追踪、日志。Istio 在这三个方面都有比较完整的支持。指标方面,可以通过 Envoy 获取请求相关的数据,同时还提供了服务级别的指标,以及控制平面的指标来检测各个组件的运行情况。通过内置的 Prometheus 来收集指标,并使用 Grafana 展示出来。分布式追踪也支持各种主流的 OpenTracing 工具,如 Jaeger、Zipkin 等。访问日志一般都通过 ELK 去完成收集、分析和展示。另外,Istio 还拥有 Kiali 这样的可视化工具,给你提供整个网格以及微服务应用的拓扑视图。总体来说,Istio 在可观察方面的能力是非常强大的,这主要是因为 Mixer 组件的插件特性带来了巨大的灵活性。

App Mesh 在这方面做的也不错。从最新发布的 官方 repo 中,App Mesh 已经提供了集成主流监控工具的 helm chart,包括 Prometheus、Grafana、Jaeger 等。同时,AWS 又一次发挥了自己闭环生态的优势,提供了与自家的监控工具 CloudWatch、X-Ray 的整合。总的来说,App Mesh 在对服务的可观察性上也不落下风。

Amazon 与 Google 的棋局

AWS App Mesh 作为一个 2019 年 4 月份才发布的产品(GA),在功能的完整性上和 Istio 有差距也是情有可原的。从 App Mesh 的 Roadmap 可以看出,很多重要的功能,比如熔断已经在开发计划中。从笔者与 AWS 的支持团队了解的信息来看,他们相当重视这个产品,优先级很高,进度也比较快,之前还在预览阶段的重试功能在上个月也正式发布了。另外,App Mesh 是可以免费使用的,用户只需要对其中的实例资源付费即可,没有额外费用。对 AWS 来说,该产品的开发重点是和现有产品的整合,比如 Roadmap 列出的使用 AWS Gateway 作为 App Mesh 的 Ingress。借助着自己的生态优势,这种整合即方便快捷的完善了 App Mesh,同时又让生态内的产品结合的更紧密,使得闭环更加的牢固,不得不说是一步好棋。

和 App Mesh 目前只强调流控能力不同,Istio 更多的是把自己打造成一个更加完善的、全面的服务网格系统。架构优雅,功能强大,但性能上受到质疑。在产品的更迭上貌似也做的不尽如人意(不过近期接连发布了 1.3 到 1.4 版本,让我们对它的未来发展又有了期待)。Istio 的优势在于 3 大顶级技术公司加持的强大资源,加上开源社区的反哺,利用好的话容易形成可持续发展的局面,并成为下一个明星级产品。然而 Google 目前对 Istio 的态度有一些若即若离,一方面很早就在自家的云服务 gcloud 里提供了 Istio 的默认安装选项,但同时又发布了和 Istio 有竞争关系的 Traffic director 这个托管的控制平面。笔者的猜测是 Google 也意识到 Istio 的成熟不可能一蹴而就,鉴于网格技术托管需求的越发强烈,先提供一个轻量化的产品以占领市场。

目前各大厂商都意识到了网格技术的重要性并陆续推出了自己的产品(包括 AWS App Mesh,Kong 的 Kuma,国内的蚂蚁金服、腾讯云等),竞争也会逐渐激烈。未来是三分天下还是一统山河,让我们拭目以待。

我们的实践 - 从 Service Mesh 迈向云原生

最后这部分给大家简要介绍一下我们(FreeWheel)在 Service Mesh 领域的实践。希望通过一些前瞻性的探索,总结出最佳实践,为我们将来的微服务应用全面拥抱云原生提供一定的经验和指导。目前我们已经开发完成的 Data service 项目就整合了 AWS App Mesh,即将上线,并使用网格的能力进行智能路由和发布。

Data service 项目只包含两个服务:Forecast service 和 Query service,前者作为业务服务通过 Query service 查询来自持久层(ClickHouse)的数据;后者作为数据访问代理,从持久层获取数据并进行对象化的封装。这个 mini 的微服务系统非常适合作为一个先行者为我们探索网格的功能、性能、易用性等方面的能力,且范围足够小,不会影响到其他业务系统。

选择 AWS App Mesh 作为该项目的网格产品主要原因如下:

FreeWheel 是一个重度使用 AWS 各项服务的公司,未来所有的服务也都会全部托管的 AWS 上。作为一个闭环生态,App Mesh 可以和现有服务无缝整合,提高易用性;

相比 Istio 这样还不太成熟的开源产品,我们可以得到 AWS 技术团队的全力支持;

数据平面基于成熟高效的 Envoy,控制平面不存在 Istio 中的性能问题;

完全免费

下图是该项目的部署视图。前端请求从我们的业务系统 UIF 发送到 Forecast service,它作为 App Mesh 的一个虚拟节点,调用 Data service 进行数据查询。Data service 作为数据平面,会注入 App Mesh 的 sidecar 代理。这两个服务组成了一个 Mesh 网格,并无缝整合在 AWS 的 EKS 中。

下图是网格内部的逻辑视图,展示了如何利用 App Mesh 进行智能路由。Forecast service 作为调用者被定义为虚拟节点,Data service 作为虚拟服务,挂载着虚拟路由,内部根据需要可以设定若干路由规则。用 App Mesh 实现一个金丝雀发布的过程非常简单:假设在 Data service 的新版本(V2)发布前,流量都被指向 V1 版本;此时我们在 App Mesh 里配置好一个新的路由规则,将 10%的流量指向新的 V2 版本;只需要将新的规则通过 kubectl apply -f new-rule.yaml 应用到集群中,金丝雀发布就可以完成,且无缝切换,对用户透明。

在后续的工作中,我们会先验证 App Mesh 的性能和可靠性,然后逐渐的将更多的流量控制(如超时重试、熔断等)功能添加进来,并总结出一整套可行的实施方案,供大家参考。也欢迎对 Service Mesh 感兴趣的同学加入到我们的团队中,一起学习,共同进步。

总结

解耦是软件开发中永恒的主题,开发人员对消除重复的偏执是推动软件、以及架构模式演进的主要动力。而 Service Mesh 的核心价值就是将服务通信功能从业务逻辑中解耦,并下沉为基础设施,由控制平面统一管理。有人将 Service Mesh、Kubernetes 和 Serverless 技术称为云原生应用开发的三驾马车。Kubernetes 是云应用实际意义上的操作系统;Service Mesh 将服务通信功能剥离,实现了与业务的解耦;Serverless 让你不用关心应用的服务器。这三项技术让我们有能力实现应用开发的终极目标,那就是:只关注业务本身。而它们,也会引领我们通向未来云原生应用的诗和远方。

万字长文:Service Mesh · Istio · 以实践入门


Photo @ Jez Timms

文  |  三辰


前言


本文是笔者在学习官方文档、相关博客文章和实践过程中,整理了一些知识概念和自己的思考,主要在探索 lstio 的实际应用场景, Sidecar 原理, Service Mesh 为什么出现、要解决什么问题等,帮助我们思考微服务技术架构的升级和落地的可行性。
本文不是 Istio 的全部,但是希望入门仅此一篇就够。

概念



围绕云原生(CN)的概念,给人一种知识大爆炸的感觉,但假如你深入了解每一个概念的细节,你会发现它和你很近,甚至就是你手里每天做的事情。

万字长文:Service Mesh · Istio · 以实践入门

图片来源:https://landscape.cncf.io/


关键词:Service Mesh、Istio、Sidecar、Envoy 等。

服务网格


服务网格( Service Mesh )是一个新瓶装旧酒的概念,它的发展随着微服务兴起,必然是早于 Kubernates 出现了。但 Kubernates 和 Istio 的出现,促使它成为了一种更火更标准化的概念。

Sidecar 是服务网格技术中常用的(其中)一种设计架构,在 Kubernates 中,不同的容器允许被运行在同一个 Pod 中(即多个进程运行在同一个 cgroup 下),这在很大程度上给 Sidecar 模式提供了良好的土壤。

首先看看 Sidecar 的设计:


万字长文:Service Mesh · Istio · 以实践入门
图片来源于网络

为什么是新瓶旧酒?任何技术的发展都不是凭空地跳跃式发展的。

历史


万字长文:Service Mesh · Istio · 以实践入门

原始的应用程序--图片来源于网络


万字长文:Service Mesh · Istio · 以实践入门
独立的网络层--图片来源于网络

万字长文:Service Mesh · Istio · 以实践入门
出现网络层(4层协议)控制的需求--图片来源于网络

万字长文:Service Mesh · Istio · 以实践入门
控制逻辑下移到网络层--图片来源于网络

早期,应用程序随着功能迭代发展,尤其是一个大型项目,程序堆积了越来越多的功能,功能之间紧密耦合在一起,变得越来越难以维护(因为模块耦合度较高,没有人敢动古老的模块代码),迭代周期变长(工程复杂度成几何增长)。

于是,人们提出,将不同的功能分离到不同的程序(进程)中,减低模块的耦合度,敏捷开发迭代,这就是微服务概念的兴起。

万字长文:Service Mesh · Istio · 以实践入门
出现新的应用层(7层协议)需求(服务发现、熔断、超时重试等)--图片来源于网络

万字长文:Service Mesh · Istio · 以实践入门
封装成三方库(服务发现:Dubbo/HSF)--图片来源于网络

困难:
服务被拆分成众多的微服务,最困难的问题就是——调用它自己:
1、原本在进程中互相调用那么简单的事情,都要变成一次在 7 层网络上的远程调用。
2、原本公共工具类做的事情,现在需要写成二方库 SDK 等,在每一个进程中使用,版本迭代成为了灾难。
3、原本是内部透明调用的不需要做任何防护,分离后却要额外增加安全防护和隔离的工作。
4、不再是代码即文档,需要维护大量的 API 定义和版本管理。

万字长文:Service Mesh · Istio · 以实践入门
封装到隔离的进程中代理--图片来源于网络

到这里,独立进程的方式基本成型,即为Sidecar模式。

Sidecar 解决什么问题?
这里有个服务网格里的概念:微服务之间的调用,一般在架构图中是横向的,被称为东西流量。服务暴露到外部被公网可见的外部调用,被称为南北流量。

Sidecar 的设计就是为了解决微服务互相调用(东西流量)的问题。

先来一张我们比较熟悉的图:

万字长文:Service Mesh · Istio · 以实践入门

图片来源于网络


Consumer 与 Provider 就是微服务互相调用的一种解决方案。


毫无疑问,我们熟知的一整套中间件解决方案,解决的正是东西流量的问题,图为Dubbo 架构。

只不过, Dubbo 中间件一整套组件是基于 SPI 机制以一种较为隔离的方式侵入到运行时的代码中。并且,这只能限定 Java 这样被官方支持的语言来开发服务应用。

小结

归纳一下与东西流量有关的问题:

流量管理(服务发现、负载均衡、路由、限流、熔断、容错等)、可观测性(监控、日志聚合、计量、跟踪)、安全(认证、授权),再甚至更高级的动态配置、故障注入、镜像流量等

相比来说, Sidecar 的模式更为巧妙并更进一步。通过容器机制,在进程上是隔离的,基于 L7 代理进行通讯,允许微服务是由任何语言进行开发的。

万字长文:Service Mesh · Istio · 以实践入门
图片来源于网络

以下是微服务集群基于Sidecar互相通讯的简化场景:


万字长文:Service Mesh · Istio · 以实践入门

图片来源于网络


所以说,回到服务网格的概念上来,虽然概念是不同的,但是逻辑上我们可以理解成:所有使用中间件的服务组成了一个大的服务网格,这样可以帮助我们理解。服务网格基于 Kubernates 这样的容器技术,将东西流量的问题解决得更加透明无感。

一句话总结,服务网格( Service Mesh )是解决微服务之间的网络问题和可观测性问题的(事实)标准,并且正在走向标准化。

Service Mesh 是 Kubernetes 支撑微服务能力拼图的最后一块

Istio 和 Envoy


Istio,第一个字母是(ai)。
Istio 实现的服务网格分为数据平面和控制平面。核心能力包括4大块:
1、流量控制(Traffic Management)。
2、安全(Security)。
3、动态规则(Policy)。
4、可观测能力(Observability)。

Envoy 面向数据平面,也就是服务之间调用的代理。

Envoy 是 Istio Service Mesh 中默认的 Sidecar 方案。

Istio 在 Enovy 的基础上按照 Envoy 的 xDS 协议扩展了其控制平面。

万字长文:Service Mesh · Istio · 以实践入门

万字长文:Service Mesh · Istio · 以实践入门

Istio基于Envoy实现Service Mesh数据平面--图片来源于网络


万字长文:Service Mesh · Istio · 以实践入门
Envoy角色--图片来源于网络

Envoy 是一个由 C++ 实现的高性能代理,与其等价的,还有 Nginx、Traefik ,这就不难理解了。

也就是下图中的 Proxy :

万字长文:Service Mesh · Istio · 以实践入门
图片来源于Istio官网

Istio 在控制平面上主要解决流量管理、安全、可观测性三个方面的问题,也就是前面提到的东西流量相关的问题。类似一个有配置中心的微服务集群架构。具体细节不在这里赘述。

Sidecar注入


前面在介绍服务网格时,只是简单地提到Sidecar设计在其中的作用和特性,这里详细展开介绍其中的原理。

首先是一些预备概念:
1、Sidecar 模式:容器应用模式之一,Service Mesh 架构的一种实现方式
2、Init 容器:Pod 中的一种专用的容器,在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。
3、iptables:流量劫持是通过 iptables 转发实现的。

Sidecar 模式解决微服务之间的网络通讯(远程调用),通常通讯层的实现方式,有以下选择:
1、在微服务应用程序中导入 SDK 类库。
2、节点代理(使用纵向的API网关或者是本地 Agent ),代理接口的调用路由规则,转发到特定的机器。
3、用 Sidecar 容器的形式运行,和应用容器一同运行,透明地劫持所有应用容器的出入流量。

SDK 库的方式是很自然的,并且调用方式是进程内的,没有安全隔离的包袱。但是随着编程语言的发展,很多新的语言为特定的场景而生,而SDK库的方式限制了使用方必须用支持列表中的语言。

节点代理的方式,是使用一个特定的服务专门代理微服务中的请求,是一个中间人的角色。但这个代理人的安全性要求非常高,因为它需要处理来自不同微服务的请求,并鉴别它们各自的身份。

Sidecar 模型是介于 SDK 库和节点代理中间的一种形式,相当于是给每个微服务都配上一个自己独有的代理。这样,每个微服务自己的 Sidecar 就代表了自己特定的身份,有利于调用的安全审计。因此,从外部看, Sidecar 与其附属的应用程序具有相同的权限。

万字长文:Service Mesh · Istio · 以实践入门
图片来源:https://toutiao.io/posts/uva4uy/preview

以 Istio 为例:
在 Istio 中, Sidecar 模式启动时会首先执行一个init 容器 istio-init ,容器只做一件事情,通过  iptables 命令配置 Pod 的网络路由规则,让 Envoy 代理可以拦截所有的进出 Pod 的流量。

之后,微服务应用通过 Pod 中共享的网络命名空间内的 loopback ( localhost )与 Sidecar 通讯。而外部流量也会通过 Sidecar 处理后,传入到微服务。

因为它们共享一个 Pod ,对其他 Pod 和节点代理都是不可见的,可以理解为两个容器共享存储、网络等资源,可以广义的将这个注入了 Sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。

下图是具体 iptables 与 Sidecar 之间互作用原理,来源:
https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/

万字长文:Service Mesh · Istio · 以实践入门

具体原理上的细节,我们可以通过实践,慢慢挖掘。

小结


最后给概念章节有个阶段性的总结:

万字长文:Service Mesh · Istio · 以实践入门
图片来源于网络

所以我们打算卖什么?


实践



铺垫这么多概念,我们可以实操起来了。具体怎么做?从安装 Istio 开始。

准备工作



首先,预备一个Kubernates集群,这里不赘述。

如果是本地测试,Docker-Desktop也可以启动一个单机的k8s集群

装 Istio 的命令行工具 istioctl :
下载 istio-release(包括 istioctl 、示例文件和安装配置)。

 
curl -sL "https://github.com/istio/istio/releases/download/1.4.2/istio-1.4.2-osx.tar.gz" | tar xz

安装 helm (可选):
从 1.4.0 版本开始,不再使用 helm 来安装 Istio
 
# helm工具$ brew install kubernetes-helm


安装Istio


进入到安装文件的目录,开始将 Istio 安装到 k8s 上。

首先确认 kubectl  连接的正确的 k8s 集群。


选择以下其中一种方式:

方式1、使用 istioctl  安装
 
cd istio-1.4.2# 安装istioctlcp bin/istioctl /usr/local/bin/ # 也可以加一下PATH# (可选)先查看配置文件istioctl manifest generate --set profile=demo > istio.demo.yaml# 安装istioistioctl manifest apply --set profile=demo## 以下是旧版本istio的helm安装方式 ### 创建istio专属的namespacekubectl create namespace istio-system# 通过helm初始化istiohelm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -# 通过helm安装istio的所有组件helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -

方式2 、使用 helm 安装
 
## 以下是旧版本istio的helm安装方式 ### 创建istio专属的namespacekubectl create namespace istio-system# 通过helm初始化istiohelm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -# 通过helm安装istio的所有组件helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -


等待所有的 Istio 组件的容器启动,直到:
 
$ kubectl get crds | grep 'istio.io' | wc -l23
如果是阿里云ACS集群,安装完Istio后,会有对应的一个SLB被创建出来,转发到Istio提供的虚拟服务器组

示例:Hello World


示例代码在源码的 samples 目录中
 
   
   
 
cd samples/hello-world

注入

Istio Sidecar 的注入有两种方式:自动、手动。

这里先通过 istioctl 命令直接手工inject:
 
istioctl kube-inject -f helloworld.yaml -o helloworld-istio.yaml
实际上就是通过脚本修改了原文件,增加了:
1、sidecar init容器。
2、istio proxy sidecar容器。

分析

我们可以简单对比一下注入的配置,原文件:
 
apiVersion: v1kind: Servicemetadata: name: helloworld labels: app: helloworldspec: ports: - port: 5000 name: http selector: app: helloworld---apiVersion: apps/v1kind: Deploymentmetadata: creationTimestamp: null labels: version: v1 name: helloworld-v1spec: replicas: 1 selector: matchLabels: app: helloworld version: v1 strategy: {} template: metadata: labels: app: helloworld version: v1 spec: containers: - image: docker.io/istio/examples-helloworld-v1 imagePullPolicy: IfNotPresent name: helloworld ports: - containerPort: 5000 resources: requests: cpu: 100m---apiVersion: apps/v1kind: Deploymentmetadata: creationTimestamp: null labels: version: v2 name: helloworld-v2spec: replicas: 1 selector: matchLabels: app: helloworld version: v2 strategy: {} template: metadata: labels: app: helloworld version: v2 spec: containers: - image: docker.io/istio/examples-helloworld-v2 imagePullPolicy: IfNotPresent name: helloworld ports: - containerPort: 5000 resources: requests: cpu: 100m


可以看到,需要部署两个版本 helloworld-v1/v2 的容器,挂载在同一个服务下。

这是一个典型的蓝绿部署方式,后续我们可以通过配置 Istio ,来调整它们的流量权重,这是真实生产环境版本升级的场景。

再来看增加的部分:

万字长文:Service Mesh · Istio · 以实践入门


这里增加了一部分 Istio 的配置,是 K8s 中的标准做法 annotations 。


万字长文:Service Mesh · Istio · 以实践入门


这部分可以看到,原有的服务容器没有任何改动,只是增加了一个sidecar容器,包括启动参数和环境变量(因为配置排序的问题, args 排在了最前面,整体的定义:
 
 - args: - proxy - sidecar - ... env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - ... image: docker.io/istio/proxyv2:1.3.2 imagePullPolicy: IfNotPresent name: istio-proxy ports: - containerPort: 15090 name: http-envoy-prom protocol: TCP readinessProbe: failureThreshold: 30 httpGet: path: /healthz/ready port: 15020 initialDelaySeconds: 1 periodSeconds: 2 resources: limits: cpu: "2" memory: 1Gi requests: cpu: 100m memory: 128Mi securityContext: readOnlyRootFilesystem: true runAsUser: 1337 volumeMounts: - mountPath: /etc/istio/proxy name: istio-envoy - mountPath: /etc/certs/ name: istio-certs readOnly: true

镜像名 docker.io/istio/proxyv2:1.3.2  。


另外一部分,就是 initContainer :
 
 initContainers: - args: - -p - "15001" - -z - "15006" - -u - "1337" - -m - REDIRECT - -i - '*' - -x - "" - -b - '*' - -d - "15020" image: docker.io/istio/proxy_init:1.3.2 imagePullPolicy: IfNotPresent name: istio-init resources: limits: cpu: 100m memory: 50Mi requests: cpu: 10m memory: 10Mi securityContext: capabilities: add: - NET_ADMIN runAsNonRoot: false runAsUser: 0 volumes: - emptyDir: medium: Memory name: istio-envoy - name: istio-certs secret: optional: true secretName: istio.default


部署

 
$ kubectl apply -f helloworld-istio.yamlservice/helloworld createddeployment.apps/helloworld-v1 createddeployment.apps/helloworld-v2 created$ kubectl get deployments.apps -o wideNAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTORhelloworld-v1 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v1,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v1helloworld-v2 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v2,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v2

并启用一个简单的gateway来监听,便于我们访问测试页面$ kubectl apply -f helloworld-gateway.yamlgateway.networking.istio.io/helloworld-gateway createdvirtualservice.networking.istio.io/helloworld created

部署完成之后,我们就可以通过gateway访问hello服务了:$ curl "localhost/hello"Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5$ curl "localhost/hello"Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5$ curl "localhost/hello"Hello version: v1, instance: helloworld-v1-57bdc65497-js7cm
两个版本的 Deployment 都可以随机被访问到


深入探索



接着刚才我们部署好的 hello-world ,我们随着Istio的feature进行探索。

流量控制 - 切流


首先,我们尝试控制一下流量,比如只走v2。参考Traffic Shifting:
https://istio.io/docs/tasks/traffic-management/traffic-shifting/

我们可以通过 VirtualService 配置控制版本流量,详情参考:
https://istio.io/docs/reference/config/networking/v1alpha3/virtual-service/

先查看一下当前 Gateway 和 VirtualService 的配置:
 
$ kubectl get gw helloworld-gateway -o yamlapiVersion: networking.istio.io/v1alpha3kind: Gatewaymetadata: name: helloworld-gatewayspec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - "*"$ kubectl get vs helloworld -o yamlapiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: helloworldspec: hosts: - "*" gateways: - helloworld-gateway http: - match: - uri: exact: /hello route: - destination: host: helloworld # short for helloworld.${namespace}.svc.cluster.local port: number: 5000


可以看到,VS 转发 /hello 路径的请求到 helloworld:5000 ,不过,这种 short 写法不推荐。我们可以改成 helloworld.${namespace}.svc.cluster.local 。

我们将其中 VirtualService 的配置修改为:
 
apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata: name: helloworldspec: hosts: - "*" gateways: - helloworld-gateway http: - match: - uri: exact: /hello route: - destination: host: helloworld.default.svc.cluster.local subset: v1 weight: 0 - destination: host: helloworld.default.svc.cluster.local subset: v2 weight: 100


在 http.route 里增加一个 destination ,并将 v2 的 weight 权重配置到100 。

并增加一个 DestinationRule 对 subset 进行定义。
 
apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata: name: helloworld-destinationspec: host: helloworld.default.svc.cluster.local subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2

然后应用更新:
 
$ kubectl apply -f helloworld-gateway.yamlgateway.networking.istio.io/helloworld-gateway unchangedvirtualservice.networking.istio.io/helloworld configureddestinationrule.networking.istio.io/helloworld-destination created

测试一下效果:
 
$ while true;do sleep 0.05 ;curl localhost/hello;doneHello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6

流量完美切走。不过,到目前为止我们只接触了Gateway 、VirtualService和DestinationRule。我们来回顾一下:

Gateway

Gateway 用于处理服务网格的边界,定义了出入负载的域名、端口、协议等规则。

VirtualService

VirtualService 可以控制路由(包括subset/version权重、匹配、重定向等)、故障注入、TLS 。

DestinationRule

DestinationRule 定义确定路由的细节规则,比如 subset 定义、负载均衡的策略,甚至可以针对特定的端口再重新定义规则。

示例:Bookinfo


前面的例子,通过控制流量权重达到版本切流的目的。

下面,我们再通过另外一个 Bookinfo 的例子继续探索其它Istio的feature。

万字长文:Service Mesh · Istio · 以实践入门
图片来源于 Istio 官网


本例是一个多实例微服务架构,并且由不同语言开发。

开始
 
$ cd samples/bookinfo


注入

这次Pod定义比较多,我们打开auto sidecar-injection
 
$ kubectl label namespace default istio-injection=enabled
打开之后,每次创建的Pod都会自动注入上istio-proxy和相应的initContainer

部署

 
$ kubectl apply -f platform/kube/bookinfo.yamlservice/details createdserviceaccount/bookinfo-details createddeployment.apps/details-v1 createdservice/ratings createdserviceaccount/bookinfo-ratings createddeployment.apps/ratings-v1 createdservice/reviews createdserviceaccount/bookinfo-reviews createddeployment.apps/reviews-v1 createddeployment.apps/reviews-v2 createddeployment.apps/reviews-v3 createdservice/productpage createdserviceaccount/bookinfo-productpage createddeployment.apps/productpage-v1 created
创建一个Gateway用于查看页面:
 
$ kubectl apply -f networking/bookinfo-gateway.yamlgateway.networking.istio.io/bookinfo-gateway createdvirtualservice.networking.istio.io/bookinfo created

访问 http://localhost/productpage 页面:

万字长文:Service Mesh · Istio · 以实践入门


不断刷新可以看到右侧Reviews有三个版本:


万字长文:Service Mesh · Istio · 以实践入门
万字长文:Service Mesh · Istio · 以实践入门
万字长文:Service Mesh · Istio · 以实践入门

流量控制 - 网络可见性


基于前面安装好的 Bookinfo 应用,起一个 Pod 探索一下网络可见性:

 
$ kubectl run --image centos:7 -it probe# 请求productpage服务上的接口[root@probe-5577ddd7b9-rbmh7 /]# curl -sL http://productpage:9080 | grep -o "<title>.*</title>"<title>Simple Bookstore App</title>$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl www.baidu.com | grep -o "<title>.*</title>"<title>百度一下,你就知道</title>
我们可以看到,默认情况下,所有的微服务(容器)之间都是公开可访问的,并且微服务可以访问外部网络。

接下来,介绍 Sidecar 配置对可见性进行控制。

Sidecar

由于每个容器都自动注入了Sidecar容器,托管了所有的出入请求。所以基于这个 Sidecar 容器,我们可以很容易对它进行配置。

Sidecar 配置就是 Istio 中专门用于配置 sidecar 之间的网络可见性。

首先,修改全局配置,使用 blocked-by-default 的方式。
 
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -configmap "istio" replaced$ kubectl get configmap istio -n istio-system -o yaml | grep -n1 -m1 "mode: REGISTRY_ONLY"67- outboundTrafficPolicy:68: mode: REGISTRY_ONLY


outboundTrafficPolicy.mode=REGISTRY_ONLY  表示默认容器不允许访问外部网络,只允许访问已知的ServiceEntry。

然后,我们设置一个全局性的 Sidecar 配置:
 
$ kubectl apply -f - <<EOFapiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata: name: default namespace: istio-systemspec: egress: - hosts: - "./*" - "istio-system/*"EOFsidecar.networking.istio.io/default configured


  • 每个namespace只允许一个无 workloadSelector 的配置
  • rootNamespace中无 workloadSelector 的配置是全局的,影响所有namespace,默认的rootNamespace=istio-system
这个配置的含义是:
所有namespace里的容器出流量(egress)只能访问自己的namespace或namespace=istio-system 中提供的 services 。

egress

我们先测试一下外网连通性, Sidecar 的出流量被称作 egress 流量。
这里需要等待一会生效,或者直接销毁重新部署一个测试容器
 
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com* About to connect() to www.baidu.com port 80 (#0)* Trying 220.181.38.150...* Connected to www.baidu.com (220.181.38.150) port 80 (#0)> GET / HTTP/1.1> User-Agent: curl/7.29.0> Host: www.baidu.com> Accept: */*>* Recv failure: Connection reset by peer* Closing connection 0curl: (56) Recv failure: Connection reset by peercommand terminated with exit code 56
效果是:外网已经访问不通。

恢复:这时,我们将需要访问的域名注册到 ServiceEntry 中,并且增加一个 Sidecar 的  egress 规则,例如:
 
apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata: name: baiduspec: hosts: - www.baidu.com ports: - number: 80 name: http protocol: HTTP resolution: DNS location: MESH_EXTERNAL---apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata: name: defaultspec: egress: - hosts: - "./www.baidu.com" port: number: 80 protocol: HTTP name: http


重新请求,确认恢复了。
 
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com* About to connect() to www.baidu.com port 80 (#0)* Trying 220.181.38.150...* Connected to www.baidu.com (220.181.38.150) port 80 (#0)> GET / HTTP/1.1> User-Agent: curl/7.29.0> Host: www.baidu.com> Accept: */*>< HTTP/1.1 200 OK< accept-ranges: bytes< cache-control: private, no-cache, no-store, proxy-revalidate, no-transform< content-length: 2381< content-type: text/html< date: Tue, 15 Oct 2019 07:45:33 GMT< etag: "588604c8-94d"< last-modified: Mon, 23 Jan 2017 13:27:36 GMT< pragma: no-cache< server: envoy< set-cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/< x-envoy-upstream-service-time: 21

同样地,容器之间的流量同理:
 
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080curl: (56) Recv failure: Connection reset by peercommand terminated with exit code 56配置上ServiceEntryapiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata: name: defaultspec: egress: - hosts: - "./www.baidu.com" - "./productpage.default.svc.cluster.local" # 这里必须用长名称---apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata: name: baiduspec: hosts: - www.baidu.com resolution: DNS location: MESH_EXTERNAL---apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata: name: productpagespec: hosts: - productpage resolution: DNS location: MESH_EXTERNAL

 
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080 | grep -o "<title>.*</title>"<title>Simple Bookstore App</title>

需要留意的是,不带workloadSelector的(不指定特定容器的)Sidecar配置只能有一个,所以规则都需要写在一起。

ingress

下面我们探究容器入流量的配置:
 
apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata: name: productpage-sidecarspec: workloadSelector: labels: app: productpage ingress: - port: number: 9080 protocol: HTTP defaultEndpoint: 127.0.0.1:10080 egress: - hosts: - "*/*"
这个配置的效果是让 productpage 应用的容器收到 9080 端口的 HTTP 请求时,转发到容器内的10080端口。
由于容器内没有监听 10080 ,所以会访问失败
 
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -s productpage:9080upstream connect error or disconnect/reset before headers. reset reason: connection failure


小结


Sidecar 的示例说明就到这里,这只是一个示例。

egress 配置覆盖的域名访问规则没必要在 ingress 中重复,所以 ingress 配置主要用于配置代理流量的规则。例如,我们可以将所有的入口流量传入 sidecar 中的监听进程(做一些定制开发的权限检查等),然后再传给下游微服务。

egress 的配置更多的是关注在服务网格对外访问的能力,服务内部如果限制了,应用自身访问都会需要大量的 ServiceEntry 注册,所以微服务之间东西流量的信任访问,需要靠安全机制来解决。

安全机制

概述

万字长文:Service Mesh · Istio · 以实践入门
图片来源于Istio官网

Istio 提供包含了南北流量和东西流量两部分的防御机制:
1、Security by default:微服务应用不需要提供任何代码或引入三方库来保证自身安全。
2、Defense in depth:能够与已有的安全体系融合,深度定制安全能力。
3、Zero-trust Network:提供安全的方案都是假定服务网格的内部和外部都是0信任(不安全)网络。

下图是 Istio 中每个组件的角色:

图片来源于Istio官网

1、Citadel,证书(CA)管理
2、Sidecar等Envoy Proxy,提供TLS保障
3、Pilot,策略(Policy)和身份信息下发
4、Mixer,认证和审计

策略(Policy)

Istio 支持在运行时实时地配置策略(Policy),支持:
1、服务入流量速率控制。
2、服务访问控制,黑白名单规则等。
3、Header重写,重定向。

也可以自己定制 Policy Adapter 来定义业务逻辑。

TLS

在介绍安全机制之前,有必要先科普一下 TLS 。

SSL ( Security Socket Layer ,安全 Socket 层),是一个解决 4 层 TCP 和 7 层HTTPS 中间的协议层,解决安全传输的问题。
TLS ( Transport Layer Security ,传输层安全协议),是基于 SSL v3 的后续升级版本,可以理解为相当于 SSL v3.1 。

主要提供:
1、认证(Transport Authentication),用户、服务器的身份,确保数据发送到正确的目的地。
2、加密数据,防止数据明文泄露。
3、数据一致性,传输过程不被串改。

Istio 中的安全传输机制都是建立在 TLS 之上的。

更多信息可以查看官方概念,详情参考:
https://istio.io/docs/concepts/security

认证(Authentication)与鉴权(Authorization)

这两个词很相近,甚至缩写 auth 都是相同的,所以有时候很容混淆。

在 istioctl 中有两个命令 authn 和 authz ,这样就可以区分它们。

认证和鉴权分别做什么,在后文两节会具体介绍。这里先说一下它们的关系。

认证 实际上是 鉴权 的必要条件
认证 实际上是 鉴权 的必要条件
认证 实际上是 鉴权 的必要条件

为什么?

1、认证是识别身份(Identification)。
2、鉴权是检查特定身份(Identity)的权限。

这样就很好理解了。二者时常相随,我们常说的比如登录,就是:
1、基于登录机制的cookie来识别访问来源的身份——认证。
2、然后判断来源的身份是否具备登录系统的权限(或者是访问某一个页面的具体细节的权限)——鉴权。

那么在 Istio 体系中,Authentication 是基于 mTLS 机制来做的,那么开启mTLS之后,就可以设置一些 AuthorizationPolicy 来做访问控制。细节可以看下文。

认证(Authentication)

Istio 中的认证包含两种:
1、Transport Authentication ,传输层认证。基于 mTLS ( Mutual TLS ),检查东西流量的合法性。
2、Origin Authentication ,客户端认证。基于 JWT 等校验南北流量的登录身份。

示例:配置Policy

这次我们跟着 Task: Authentication Policy 例子走,这里简化一下过程不做全程搬运,只分析关键点。

准备环境:
这个例子中,创建了 3 个 namespace ,其中两个 foo 和 bar 注入了Sidecar, legacy 不注入用于对比。
 
 
#!/bin/bash kubectl create ns fookubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n fookubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n fookubectl create ns barkubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n barkubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n barkubectl create ns legacykubectl apply -f samples/httpbin/httpbin.yaml -n legacykubectl apply -f samples/sleep/sleep.yaml -n legacy
默认情况下,容器之间是互通的(mTLS运行在PRESSIVE_MODE)。

这里通过一个 check.sh 脚本检查连通性:
 
 
#!/bin/bashfor from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}
"; done; done$ ./check.shsleep.foo to httpbin.foo: 200sleep.foo to httpbin.bar: 200sleep.foo to httpbin.legacy: 200sleep.bar to httpbin.foo: 200sleep.bar to httpbin.bar: 200sleep.bar to httpbin.legacy: 200sleep.legacy to httpbin.foo: 200sleep.legacy to httpbin.bar: 200sleep.legacy to httpbin.legacy: 200


打开TLS:
通过全局的 MeshPolicy 配置打开mTLS:
 
 
$ kubectl apply -f - <<EOFapiVersion: "authentication.istio.io/v1alpha1"kind: "MeshPolicy"metadata: name: "default"spec: peers: - mtls: {}EOF
这时,原本互通的容器访问不通了

执行:
 
 
$ ./check.shsleep.foo to httpbin.foo: 503sleep.foo to httpbin.bar: 503sleep.foo to httpbin.legacy: 200sleep.bar to httpbin.foo: 503sleep.bar to httpbin.bar: 503sleep.bar to httpbin.legacy: 200sleep.legacy to httpbin.foo: 000command terminated with exit code 56sleep.legacy to httpbin.bar: 000command terminated with exit code 56sleep.legacy to httpbin.legacy: 200
Sidecar 注入的 namespace 中,会返回 503.  而没有注入的 ns 上,连接会直接被重置(connection reset)。

配置托管的 mTLS 能力


接着,通过 DestinationRule ,重新对注入Sidecar的微服务增加 mTLS 能力:
 
 
kubectl apply -f - <<EOFapiVersion: "networking.istio.io/v1alpha3"kind: "DestinationRule"metadata: name: "default" namespace: "istio-system"spec: host: "*.local" trafficPolicy: tls: mode: ISTIO_MUTUALEOF


1、*.local 配置的含义是,对所有 K8s 集群内任意 namespace 之间的东西流量有效
2、tls.mode=ISTIO_MUTUAL :查看文档,表示完全由 Istio 托管 mTLS 的实现,其它选项失效。具体配置后面再涉及。

重新运行 check.sh :
 
 
$ ./check.shsleep.foo to httpbin.foo: 200sleep.foo to httpbin.bar: 200sleep.foo to httpbin.legacy: 503sleep.bar to httpbin.foo: 200sleep.bar to httpbin.bar: 200sleep.bar to httpbin.legacy: 503sleep.legacy to httpbin.foo: 000command terminated with exit code 56sleep.legacy to httpbin.bar: 000command terminated with exit code 56sleep.legacy to httpbin.legacy: 200

注意,如果走了前面的例子会有全局 default 的 Sidecar Egress 配置,限制了只能访问同 namespace 的服务,那么跨 namespace 的调用仍然会 503 :
 
 
sleep.foo to httpbin.bar: 503

可以自己试验一下,回顾一下配置:
 
 
apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata: name: default namespace: istio-systemspec: egress: - hosts: - ./* # <-- - istio-system/*

分析
对比之前的结果,有两点变化:
1、同样注入Sidecar的微服务互相可以访问了(200)。
2、没有注入Sidecar(ns=legacy)的微服务不能被已注入Sidecar的微服务访问(503)。
ns=legacy中的行为仍然不变
变化1:说明微服务之间的 TLS 已经由 Istio 托管,这个期间我们没有修改任何服务的代码,很魔性。
变化2:说明服务网格对外部容器也要求具备 TLS 能力,因为 legacy 中的服务没有注入 Sidecar ,因此访问失败。

鉴权(Authorization)

Istio 的鉴权机制的前提就是打开 mTLS 认证,让每一个或特定的微服务的 sidecar 互相访问都基于 mTLS 机制。
不是必要前提
有一部分鉴权规则是不依赖mTLS的,但是很少。

鉴权基于 istio CRD :AuthorizationPolicy

例如,默认拒绝所有微服务互相访问:
 
 
apiVersion: security.istio.io/v1beta1kind: AuthorizationPolicymetadata: name: deny-all namespace: foospec:
需要留意的是,如果默认全部拒绝,那么甚至 istio-system 中的 istio-ingressgateway 流量访问 foo 这个namespace的服务也都会被拒绝。就无法从外部访问所有 foo 的服务了。所以我们可以改为:
 
 
apiVersion: security.istio.io/v1beta1kind: AuthorizationPolicymetadata: name: deny-all namespace: foospec: rules: - from: - source: namespaces: - "istio-system"
AuthorizationPolicy 的规则文档里都已经很详细了,这里就不再赘述。

应用配置之后,在任意一个微服务中访问另外一个微服务,就都会遇到 403 错误,消息为 RBAC access denied 。


其它



Istio 能力本文仅覆盖了流量控制(Traffic Management)、安全机制(Security)中比较浅显的部分,有关高级的 Policy 设置(限流、路由的干预等)、服务观测(Telemetry)等能力没有涉及。

此外,如何地高效运维管理(比如升级 istio 版本、管理不同集群的配置等),0 信任基础下的安全访问策略配置,基于istio做二次开发扩展,等等问题都是在生产实践当中需要关注的点,以后有机会再分享整理。

参考文档

  • Istio官方文档
    https://istio.io/docs/
  • Istio Handbook
    https://www.servicemesher.com/istio-handbook/concepts-and-principle/what-is-service-mesh.html
  • Pattern Service Mesh
    https://philcalcado.com/2017/08/03/pattern_service_mesh.html


作者信息:

袁赓拓,花名三辰,阿里云智能-计算平台事业部技术专家,负责数加平台 &DataWorks 的微服务生态建设,目前主要关注微服务、Service Mesh 等相关技术方向。


↓↓ 点击"阅读原文" 【加入云技术社区】

相关阅读:



更多文章请关注


文章好看点这里[在看]

以上是关于Service Mesh 浅析:从概念、产品到实践的主要内容,如果未能解决你的问题,请参考以下文章

“热搜”|微博微服务架构的Service Mesh实践之路

微博微服务架构的Service Mesh实践之路

腾讯云Service Mesh生产实践及架构演进

胡忠想|微博微服务架构的Service Mesh实践之路

Service Mesh 是新瓶装旧酒吗?

Service Mesh 在中国工商银行的探索与实践