最近几年,软件体系结构领域发生了巨大的变化。我们都见证了这一重大转变,就是将大型的整体应用程序和粗粒度应用程序分解为被称为微服务的细粒度部署单元,主要通过同步REST和gRPC接口以及异步事件和消息传递进行通信。这种架构的好处很多,但缺点同样明显。在“旧世界”中曾经很简单的软件开发过程,例如调试,概要分析和性能管理,现在变得复杂了一个数量级。此外,微服务架构也带来了自己独特的挑战。
服务更具流动性和弹性,并且跟踪它们的实例、它们的版本和依赖关系是一项艰巨的挑战,随着微服务的发展,它的复杂性迅速增加。最重要的是,单个服务可能会孤立地失败,并且这种失败会由于不可靠的网络而进一步加剧。如果系统足够大,则在任何给定时间点,部分系统可能会遭受轻微故障,可能会影响一部分用户,而这往往是在操作员不知情的情况下发生的。拥有如此众多的“活动部件”,如何应对这些挑战并确保系统平稳运行而又既不影响客户,又能避免让开发人员精疲力尽呢?
随着微服务的日益普及,该行业一直在稳步提出各种模式和最佳实践,这些模式和最佳实践使整个体验更佳。弹性模式,服务发现,容器编排,Canary版本,可观察性模式,BFF,API网关…这些是从业人员将用来构建更健壮和可持续的分布式系统的概念。但是这些概念仅仅是抽象概念和模式,它们需要有人在系统中的某个地方实现它们。很多时候,那个“某人”就是你,“某个地方”便是处不在。
一、开始之前
本文旨在提供对Istio的“温和”介绍。所谓“温和”,不是指快速。我们将从基本概念入手,然后浏览一下如何将它们与增量示例结合在一起。请尝试按照顺序执行这些示例,因为某些示例将取决于前面的示例。到最后,您应该了解Istio是什么,可以在哪里使用它,并有信心自己使用它。
本文介绍的材料在Kubernetes知识范围内将被分类为中级或高级。因此对Kubernetes和Docker的掌握至关重要:您应该对Kubernetes核心概念有透彻的实践了解,具有部署和管理容器化工作负载的能力,并可以轻松地使用kubectl
导航和更改Kubernetes集群的状态。
二、介绍Istio
Istio是一个服务网格—一种应用程序感知的基础结构层,用于促进服务到服务的通信。“应用程序感知”是指服务网格在某种程度上了解服务通信的本质,可以以增量的方式进行干预。例如,服务网格可以实现弹性模式(重试,断路器),更改流量(调整流量,影响路由行为),以及添加大量全面的安全控制措施。Istio本质上了解服务之间传递的流量,因此还可以提供细粒度的仪表和遥测见解,从而为原本不透明的分布式系统提供一定程度的可观察性。
注意:*服务网格是指*OSI参考模型中的第7层协议,但也可以配置为在第3层和第4层运行。
Istio得到了Google,IBM和Lyft的支持,并且是目前使用最广泛的服务网格体系结构。Kubernetes最初由Google设计,也很好地融入了Istio。因此将Istio标记为“ Kubernetes-native服务网格”将是合理的。
它怎么运行的
像大多数服务网格实现一样,Istio用称为sidecar的代理容器补充了现有的应用程序容器。Sidecar代理是经过特殊配置的Envoy实例,可拦截进入和离开服务容器的网络流量,并通过专用网络重新路由流量,如下所示。
Sidecar代理是针对延迟和吞吐量进行了优化的轻量级组件,具有最小的配置和路由智能。路由决策是基于由单独的控制平面(服务网格的隐喻“大脑”)托管的策略做出的。控制平面包括部署在Kubernetes集群中的一组专用组件-与任何其他容器化应用程序一样,驻留在专用istio-system
名称空间中。控制平面的组件有意与数据平面分离,这些元素直接执行应用程序容器的网络流量实际路由和接口。下图说明了这种分离。
笔记:*Sidecar代理模式不是服务网格实现的唯一方法。由Netflix技术套件首创的另一种方法是使用客户端库(Ribbon,Hysterix,Eureka和Archaius)来填充数据平面的角色,并从集中控制平面获取路由提示。这种方法的好处是,与容器编排引擎(或者实际上完全是容器)无关,它可以以嵌入式形式部署在传统的无容器应用程序中。Netflix库的好处也是它的缺点-它们需要对应用程序进行侵入式更改,并为有限的编程语言提供支持,这些编程语言主要针对JVM。
毫不奇怪,基于Sidecar的服务网格在运营和DevOps团队中得到了广泛采用-在现代服务网格设计中,业务逻辑和基础架构之间的高度分离得以实现。
三、核心概念
Istio扩展了具有几种特定于Istio的资源类型的 Kubernetes设置的命名法。作为Kubernetes原生服务网格,Istio使用自定义资源定义(CRDs)来实现这些概念。CRDs文件更多的是使用YAML声明配置片段和使用kubectl
进行管理,类似于内置的类型,如Pod
,Service
,Deployment
。
Virtual services
一个虚拟服务指定请求是如何入站到一个特定的虚拟主机,并路由到底层的目的地。
虚拟服务将服务使用者与服务提供商之间的耦合程度降到了传统的Kubernetes服务所无法达到的程度,而传统的Kubernetes服务则是基本的负载平衡器。虚拟服务的典型用例包括将请求流量分区到服务的不同版本,这些版本指定为服务子集。
客户端在不了解底层提供程序实现的情况下将请求发送到虚拟服务,然后Envoy根据虚拟服务配置中定义的规则将流量转发到不同版本。例如,“ X%的呼叫转到新版本”或“这些用户的呼叫转到Y版本”。精明的读者将把这些用例视为“canary”的部署,其中逐步增加了对新服务版本的访问量,以缓解大爆炸版本的风险。
除了将流量分配给多个基础提供商实现之外,虚拟服务还允许您做相反的工作-将多个完全不同的服务组合到一个虚拟服务中。换句话说,虚拟服务可以充当基本的聚合层,从而消除了对专用反向代理(例如NGINX)的需求。
精心计划的虚拟服务还可以促进Strangler Pattern。假设服务合同不变,细粒度的服务路由规则可以针对各个端点,一旦原始端点在整体上已过时,就将请求流量转移到微服务风格的实现中。将匹配规则与基于百分比的流量策略相结合,可以透明地在提供方安排逐步迁移,而服务使用者则不需要做什么。
下面的代码片段提供了虚拟服务的简单示例。
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - shoppingcart http: - match: - headers: end-user: exact: emil.koutanov route: - destination: host: shoppingcart subset: v3 - route: - destination: host: shoppingcart subset: v3 weight: 25 - destination: host: shoppingcart subset: v2 weight: 75
hosts
将虚拟服务绑定到用户可寻址的目的地。换句话说,它充当调用虚拟服务的触发器。这些目标可以是固定的IP地址,DNS名称或服务名称-后者可以是简短的Kubernetes名称或FQDN(完全合格的域名)。*
还支持通配符前缀(由字符表示)-在子域上触发虚拟服务。指定的主机实际上不需要解析为任何现有服务名称;您可以指定要匹配的任意主机。
注意:*主机匹配模型的灵活性是一把双刃剑。因为主机名可以是任意的,所以Istio不会进行任何形式的健全性检查。例如,如果上例中的主机名拼写错误为“ shopingcart”,则Istio将很乐意应用该配置。稍后,当客户端尝试调用正确命名的
*shoppingcart*
主机时,请求将被直接路由到服务-我们的虚拟服务规则将无效。*
http
介绍了路由规则-匹配条件和用于路由发送到主机字段中指定的目标的HTTP / 1.1,HTTP / 2和gRPC通信的条件和操作。(您也可以使用tcp
和tls
部分配置TCP和未终止TLS流量的路由规则。)
路由规则由要转发流量的目的地以及零个或多个匹配条件组成。在上面的示例中,第一个规则仅匹配来自user的请求emil.koutanov
。路由规则是按照严格的从上到下的顺序进行评估的:第一个匹配的规则将被执行,如果不匹配,则进入下一个规则。
因此,在我们的示例中,emil.koutanov
将始终将用户定向到v3
该shoppingcart
服务。v3
在25%的情况下,将为所有其他用户提供服务。由于带有match
谓词的规则可能不会被剔除,因此最好的做法是保留最后一个没有match
谓词的规则-有效地构成“包罗万象”。
与hosts
中指定的虚拟名称不同,目标主机必须可解析为真实地址。为目标主机提供简称时,Istio将根据虚拟服务的名称空间添加域后缀。如果目的地位于其他名称空间中,则主机应指定标准服务名称。Istio维护人员建议在生产中使用标准名称,因为这样可以避免潜在的歧义和配置错误。
到目前为止,我们没有看到目的地和子集,而没有过多地关注它们的定义位置。一个目的地规则是一个可选的细粒度政策控制特定目的地的交通。在评估了虚拟服务路由规则之后,将应用目标规则,换句话说,它们将应用到流量的“真实”目标。
目标规则定义为 DestinationRule
这种类型的CRD。以下是目标规则的示例。
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: shoppingcart-destinationrule spec: host: shoppingcart trafficPolicy: loadBalancer: simple: RANDOM subsets: - name: v2 labels: version: v2 trafficPolicy: loadBalancer: simple: ROUND_ROBIN - name: v3 labels: version: v3
这个例子应该开始变得更有意义了,实际上它已经完成了。我们在定义中使用的v2
和v3
子集VirtualService
仅是引用shoppingcart
服务的标记版本。标签是标准的Kubernetes概念-可用于注释Kubernetes资源的自由格式键值对。该version
标签将可能出现在metadata
该服务的部分Deployment
资源定义,并调用相应的目标规则集时会在运行时匹配。
Gateways
网关控制服务网格流量的流入和流出。在幕后,网关是一个Envoy代理实例,该实例以独立配置(未附加到应用程序容器)部署在数据平面的概念边界上。
网关的绝大多数用例都围绕入站流量的管理。以这种能力,网关的行为类似于常规的Kubernetes ingress 资源。网关和Kubernetes ingress 之间的主要区别在于,前者被设计为专门与Istio一起使用,而后者则是设计为处理外部流量的标准API。
ingress和 ingress控制器通常独立于服务网格,并且无需任何一个即可运行。从理论上讲,可以部署 ingress控制器并配置 ingress以在流量到达Istio网关之前对其进行路由。此策略对于聚合服务可能很有用,其中某些服务可能使用服务网格进行有线连接,而其他服务可能会按常规部署。
以下代码段是网关定义的示例。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: shoppingcart-gateway spec: selector: istio: ingressgateway servers: - port: number: 443 name: https protocol: HTTPS hosts: - shoppingcart.example.com tls: mode: SIMPLE serverCertificate: /tmp/tls.crt privateKey: /tmp/tls.key
这个简单的示例接受发往shoppingcart.example.com
port的HTTPS流量443
。在我们首先将网关绑定到虚拟服务之前,流量无法流入服务网格。扩展我们之前的虚拟服务示例:
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - shoppingcart gateways: - shoppingcart-gateway http: - match: - headers: end-user: exact: emil.koutanov route: - destination: host: shoppingcart subset: v3 - route: - destination: host: shoppingcart subset: v3 weight: 25 - destination: host: shoppingcart subset: v2 weight: 75
我们添加到上一个示例中的就是该gateways
部分,通过其名称指定了我们的网关。shoppingcart-gateway
现在将允许网关上的入口流量流入shoppingcart
虚拟服务。
除了处理入口流量外,网关还可以充当离开网状网络的流量的受控出口点。网关将使您限制哪些服务可以访问外部网络并监视允许离开的流量。有几个原因可能导致人们想要在基础结构级别上限制出口,而与底层应用程序无关。
其中主要是合规性-例如,PCI DSS标准要求将来自持卡人数据环境(CDE)的出站流量限制为经过授权的通信,要求采用默认拒绝规则来阻止未指定的流量。另一个常见的原因是减轻攻击,系统中的一个或多个组件可能会受到攻击,并可能试图泄漏敏感数据。
Service entries
Service entry是Istio在其专用服务注册表中维护的服务的内部定义。Service entries并不是经常遇到的事情;您可以在Istio中部署完整的分布式系统,而无需触碰这个概念。尽管如此,它仍被视为Istio的核心概念,至少应该意识到这一点。
Service entries的主要用例集分为以下几大类:
- 传统应用程序集成:与未部署在Kubernetes中或无法从Istio数据平面直接访问的服务进行通信。
- 多集群合成:来自多个物理Kubernetes集群的服务的逻辑聚合。
- 将网格扩展到Kubernetes之外:将部署在物理硬件和VM上的工作负载添加到现有服务网格。
- 检测外部服务:例如,对外部服务的调用的重试,百分比路由,跟踪等。
注意:*您不需要仅配置服务条目即可访问外部服务,例如
*maps.googleapis.com*
。默认情况下,Istio egress Envoy代理配置为将请求传递给未知服务。但是,未注册的目的地将无法从适用于Istio增强服务的细粒度流量策略中受益。*
对于服务进入场景的实际示例,请考虑由托管的外部服务,someprovider.com
该服务需要相互TLS(mTLS)进行身份验证。一种选择是将X.509证书和签名的PEM密钥部署到我们Kubernetes集群中部署的每个使用者。这带来了后勤方面的挑战:可能会有几个这样的使用者,并且每个使用者都需要对应用程序代码进行潜在的侵入性更改以支持mTLS的使用。
最重要的是,我们必须分发和旋转关键材料。通过配置Istio作为前向代理,将来自我们数据平面的流量透明封装到TLS隧道中,充当mTLS终结器,可以解决此难题。请参阅下面的资源定义。
apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: external-serviceentry spec: hosts: - someprovider.com ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: external-destinationrule spec: host: someprovider.com trafficPolicy: tls: mode: MUTUAL clientCertificate: /etc/certs/myclientcert.pem privateKey: /etc/certs/client_private_key.pem caCertificates: /etc/certs/rootcacerts.pem
已经定义了两个资源:服务条目和相应的目标规则。前者的作用相对较小-它someprovider.com
作为服务条目注册,并将其置于Istio的范围内。后者承担了繁重的工作-指定身份验证模式,用于验证提供者的CA证书以及用于验证客户端的私钥和证书。
Sidecars
我们之前曾提到过边车是Istio服务网格体系结构的定义元素。确实,不起眼的Envoy代理小工具是Istio生态系统中众所周知的主力军,由控制平面自行决定在数据平面中移动流量。
sidecars与服务入口在同一条船上,因为您可能会在构建过程中获得公平的方式,而无需直接处理它们。常规将Sidecar注入到应用程序Pod中,而用户的参与很少,并且将继承默认配置,该配置可以直接使用。默认情况下,Istio将配置网格中的所有Sidecar代理以到达每个工作负载实例,并接受与工作负载关联的所有端口上的流量。
出于以下原因,可以明确修改Sidecar配置:
- 限制Envoy代理接受的端口和协议集。
- 限制Envoy代理可以访问的服务集。
通过使用,可以将Sidecar配置应用于整个名称空间或特定的工作负载workloadSelector
。这产生了范围界定规则。当确定要应用到特定工作负载实例的Sidecar配置时,将优先使用配置无效的Sidecar
资源。此外,名称空间最多可以具有一个没有的sidecar配置。如果本地名称空间中没有任何sidecar配置匹配,则在名称空间(或配置的根Istio名称空间)中定义的全局默认配置将生效。
为了使事情变得更加复杂,如果两个或更多带有选择相同工作负载实例的Sidecar配置,则系统的行为是不确定的。workloadSelector``workloadSelector``workloadSelector``istio-system``workloadSelector
全局默认配置是相当宽松的。您可以通过运行以下命令查看默认值:
kubectl get sidecar default -n istio-system -o yaml
导致:
apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | *** omitted for brevity *** creationTimestamp: "2019-12-25T01:32:02Z" generation: 1 labels: operator.istio.io/component: IngressGateway operator.istio.io/managed: Reconcile operator.istio.io/version: 1.4.0 release: istio name: default namespace: istio-system resourceVersion: "4527" selfLink: /apis/networking.istio.io/v1alpha3/namespaces/istio-system/sidecars/default uid: 5cee1cb3-1d48-11ea-84b7-025000000001 spec: egress: - hosts: - '*/*'
以下对全局默认配置的示例修订将出口流量仅限制为同一名称空间中的其他工作负载以及名称空间中的服务istio-system
。
apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default namespace: istio-system spec: egress: - hosts: - "./*" - "istio-system/*"