关键要点
- 在开发微服务架构时,需要解决一些新的挑战,例如可扩展性、安全性和可观察性。
- 微服务提供了正确实现微服务的横切关注点列表。
- Kubernetes 是实现这些微服务的良好开端,但存在一些差距。
- 服务网格是一个专用的基础设施层,用于使服务到服务的通信安全、快速和可靠。
- Istio 是一个服务网格,以非侵入性的方式实现了一些所需的微服务。
在微服务架构中,一个应用程序由多个相互连接的服务组成,所有这些服务协同工作以产生所需的业务功能。所以一个典型的企业微服务架构是这样的:
一开始,使用微服务架构实现应用程序似乎很容易。但是正确地做到这一点并不是一件容易的事,因为有一些挑战需要解决,而这些挑战是单体架构所没有的。其中一些是容错、服务发现、扩展、日志记录或跟踪,仅举几例。
为了解决这些挑战,每个微服务都应该实施我们在红帽称为“微服务功能”的东西。该术语指的是服务必须在业务逻辑之外实现以解决这些挑战的横切关注点列表。
下图总结了这些担忧:
业务逻辑可以用任何语言(Java、Go、JavaScript)或任何框架(Spring Boot、Quarkus)实现,但围绕业务逻辑,还应实现以下关注点:
API:可以通过一组定义的 API 操作访问该服务。例如,在 RESTful Web API 的情况下,HTTP 被用作协议。此外,可以使用Swagger等工具记录 API 。
发现:服务需要发现其他服务。
调用:发现服务后,需要使用一组参数调用它,并可选择返回响应。
弹性:微服务架构的关键特性之一是每个服务都是弹性的,这意味着它可以根据系统的关键性或当前工作负载等一些参数独立地扩大和/或缩小。
弹性:在微服务架构中,我们应该在开发时考虑到失败,尤其是在与其他服务通信时。在单体应用程序中,应用程序作为一个整体是向上或向下的。但是当这个应用程序被分解成一个微服务架构时,这个应用程序是由几个服务组成的。所有这些都通过网络互连,这意味着应用程序的某些部分可能正在运行,而其他部分可能会失败。包含故障以避免通过其他服务传播错误非常重要。弹性(或应用程序弹性)是应用程序/服务对问题做出反应并仍然提供最佳结果的能力。
管道:服务应该独立部署,没有任何类型的部署编排。因此,每个服务都应该有自己的部署管道。
身份验证:微服务架构的关键方面之一是如何对内部服务之间的调用进行身份验证/授权。Web 令牌(以及一般的令牌)是在内部服务中安全地表示声明的首选方式。
日志记录:日志记录在单体应用程序中很简单,因为所有组件都在同一个节点中运行。组件现在以服务的形式分布在多个节点上;因此,需要一个统一的日志记录系统/数据收集器来完整查看日志记录跟踪。
监控:一种衡量系统性能、了解应用程序整体健康状况或在出现问题时发出警报以保持基于微服务的应用程序正常运行的方法。监控是控制应用程序的一个关键方面。
跟踪:跟踪用于可视化程序的流程和数据进程。当我们作为开发人员/操作员需要检查用户在整个应用程序中的旅程时,这尤其有用。
Kubernetes正在成为部署微服务的事实上的工具。它是一个用于自动化、编排、扩展和管理容器的开源系统。
但是,在使用 Kubernetes 时,仅涵盖了十个微服务中的三个。
发现是通过 Kubernetes 服务的概念实现的。它提供了一种使用稳定的虚拟 IP 和 DNS 名称对 Kubernetes Pod(充当一个)进行分组的方法。发现服务只是使用 Kubernetes 的服务名称作为主机名发出请求的问题。
使用 Kubernetes调用服务很容易,因为该平台提供了调用任何服务所需的网络。
弹性(或可扩展性)是 Kubernetes 从一开始就考虑的东西。例如,执行命令kubectl scale deployment myservice --replicas=5,myservice 部署扩展到五个副本或实例。Kubernetes 平台负责寻找合适的节点、部署服务并始终保持所需数量的副本正常运行。
但是其余的微服务呢?Kubernetes 只涵盖了其中的三个,那么我们如何实现其余的呢?
在本系列的第一部分中,我介绍了通过使用 Java 将它们嵌入到服务中来实现这些关注点。
在同一代码中实现的具有横切关注点的服务如下图所示:
如上一篇文章所述,这种方法有效并且有几个优点,但也有一些缺点。让我们提到主要的:
- 服务的基本代码是业务逻辑(为公司带来价值)和基础设施代码(因为微服务而需要)的混合体。
- 微服务架构中的服务可以用不同的语言开发,例如 Java 中的 Service A 和 Go 中的 Service B。拥有多语言服务的挑战是学习如何为每种语言实现这些微服务。例如,哪个库可用于在 Java、Go 等中实现弹性。
- 对于 Java,我们可以为每个“微服务”添加新的库(及其所有传递依赖项),例如用于弹性的Resiliency4J、用于跟踪的Jaeger或用于监控的Micrometer 。虽然这并没有什么问题,但是当在类路径中添加不同类型的库时,我们增加了类路径冲突的可能性。此外,内存消耗和启动时间也会增加。最后但并非最不重要的一点是,维护所有 Java 服务的库版本一致的问题,因此它们都运行相同的版本。
所以到了这一点,我们可能想知道为什么我们需要实现所有这些微服务?
在微服务架构中,一个应用程序由多个相互连接的服务组成,所有这些服务协同工作以产生所需的业务功能。所有这些服务都使用网络互连,因此我们正在有效地实现分布式计算模型。由于它是分布式的,因此可观察性(监控、跟踪、日志记录)变得更加复杂,因为所有数据都分布在多个服务中。由于网络不可靠或延迟不为零,因此服务需要对这些故障具有弹性。
因此,假设由于在基础设施级别做出决策(分布式服务使用网络进行通信,而不是单体应用)而需要微服务。为什么我们需要在应用程序级别而不是基础架构级别实现这些微服务?问题就在这里。这是一个公平的问题,答案很简单:服务网格。
什么是服务网格和 Istio?
服务网格是一个专用的基础设施层,用于处理安全、快速和可靠的服务到服务通信。
服务网格通常实现为与服务代码一起部署的轻量级网络代理,透明地拦截来自服务的所有入站/出站网络流量。
Istio是 Kubernetes 服务网格的开源实现。Istio 用于将网络流量代理集成到 Kubernetes Pod 的策略是使用sidecar 容器完成的。这是一个与服务容器一起运行在同一个 Pod 中的容器。由于它们在同一个 Pod 中运行,因此两个容器共享 IP、生命周期、资源、网络和存储。
Istio 使用Envoy Proxy作为 sidecar 容器内的网络代理,并配置 Pod 以通过 Envoy 代理(sidecar 容器)发送所有入站/出站流量。
使用 Istio 时,服务之间的通信不是直接的。尽管如此,通过 sidecar 容器(Envoy 代理),当服务 A 请求服务 B 时,请求会使用其 DNS 名称发送到服务 A 的代理容器。然后服务 A 的代理容器将请求发送到服务 B 的代理容器,最终调用真正的服务 B。按照反向路径进行响应。
Envoy 代理 sidecar 容器实现了以下功能:
- 跨服务的智能路由和负载平衡。
- 故障注入。
- 弹性:重试和断路器。
- 可观察性和遥测:指标和跟踪。
- 安全性:加密和授权。
如下图所示,sidecar 容器实现的功能与五个微服务完美匹配:发现、弹性、身份验证、监控和跟踪。
在容器中拥有微服务逻辑有几个优点:
- 业务代码与微服务完全隔离。
- 所有服务都使用完全相同的实现,因为它们使用相同的容器。
- 它的代码是独立的。服务可以用任何语言实现,但这些横切关注点始终相同。
- 所有服务的配置过程和参数都是相同的。
但是 Istio 在内部是如何工作的,为什么我们需要 Istio 而不仅仅是 Envoy 代理?
实践案例
Envoy 代理是一个轻量级的网络代理,可以单独使用,但是当部署数十个服务时,需要配置数十个 Envoy 代理。事情可能会变得有点复杂和麻烦。Istio 简化了这个过程。
从架构上讲,Istio 服务网格由数据平面和控制平面组成。
数据平面由部署为 sidecar 的 Envoy 代理组成。该代理拦截服务之间的所有网络通信。它还收集和报告所有网状流量的遥测数据。
控制平面管理和配置 Envoy 代理。
下图总结了这两个组件:
安装 Istio
我们需要一个 Kubernetes 集群来安装 Istio。对于本文,我们使用Minikube,但任何其他 Kubernetes 集群都可能有效。
运行以下命令启动集群:
minikube start -p istio --kubernetes-version=\'v1.19.0\' --vm-driver=\'virtualbox\' --memory=4096
[istio] minikube v1.17.1 on Darwin 11.3
Kubernetes 1.20.2 is now available. If you would like to upgrade, specify: --kubernetes-version=v1.20.2
minikube 1.19.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.19.0
To disable this notice, run: \'minikube config set WantUpdateNotification false\'
✨ 使用基于现有配置文件的 virtualbox 驱动程序
❗ 您无法更改现有 minikube 集群的内存大小。请先删除集群。
Starting control plane node istio in cluster istio
Restarting existing virtualbox VM for "istio" ...
Preparing Kubernetes v1.19.0 on Docker 19.03.12 ...
Verifying Kubernetes components...
Enabled addons: storage-provisioner, default-storageclass
Done! kubectl is now configured to use "istio" cluster and "" namespace by default
在 Kubernetes 集群启动并运行后,下载istioctl CLI 工具以在集群中安装 Istio。在这种情况下,我们从发布页面下载 Istio 1.9.4 。
安装该istioctl工具后,我们可以继续在集群中部署 Istio。Istio 附带了不同的配置文件,但要开始使用 Istio,演示配置文件是完美的。
istioctl install --set profile=demo -y
Detected that your cluster does not support third party JWT authentication. Falling back to less secure first party JWT. See https://istio.io/docs/ops/best-practices/security/#configure-third-party-service-account-tokens for details.
✔ 已安装 Istio 核心
✔ Istiod 已安装
✔ 安装出口网关
✔ 已安装入口网关
✔ 已安装插件
✔ 安装完成
等到istio-system命名空间中的所有 Pod 都处于运行状态。
kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-b54bb57b9-fj6qk 1/1 Running 2 171d
istio-egressgateway-68587b7b8b-m5b58 1/1 Running 2 171d
istio-ingressgateway-55bdff67f-jrhpk 1/1 Running 2 171d
istio-tracing-9dd6c4f7c-9gcx9 1/1 Running 3 171d
istiod-76bf8475c-xphgd 1/1 Running 2 171d
kiali-d45468dc4-4nbl4 1/1 Running 2 171d
prometheus-74d44d84db-86hdr 2/2 Running 4 171d
要利用 Istio 的所有功能,网格中的 Pod 必须运行 Istio sidecar 代理。
将 Istio sidecar 注入 Pod 有两种方法:手动使用istioctl命令或在将 Pod 部署到为此目的配置的命名空间时自动。
为简单起见,default通过执行以下命令在命名空间中配置自动 sidecar 注入:
kubectl label namespace default istio-injection=enabled
namespace/default labeled
Istio 现在已安装在 Kubernetes 集群中,并且可以在default命名空间中使用。
在下一节中,我们将看到“istioize”应用程序的概述,我们将部署它。
应用程序
该应用程序由两个服务组成,图书服务和评级服务。图书服务返回一本书的信息及其评级。评级服务返回给定书籍的评级。在评分服务的情况下有两个版本:v1 为任何一本书返回一个固定的评分号(1),而 v2 返回一个随机的评分号。
部署
由于启用了自动边车注入,我们不需要更改 Kubernetes 部署文件中的任何内容。让我们将这三个服务部署在“istioized”命名空间上。
例如book 服务的 Kubernetes 部署文件为:
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: book-service
app.kubernetes.io/version: v1.0.0
name: book-service
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app.kubernetes.io/name: book-service
app.kubernetes.io/version: v1.0.0
type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: book-service
app.kubernetes.io/version: v1.0.0
name: book-service
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: book-service
app.kubernetes.io/version: v1.0.0
template:
metadata:
labels:
app.kubernetes.io/name: book-service
app.kubernetes.io/version: v1.0.0
spec:
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/lordofthejars/book-service:v1.0.0
imagePullPolicy: Always
name: book-service
ports:
- containerPort: 8080
name: http
protocol: TCP
正如我们所见,文件中没有任何特定于 Istio 或 sidecar 容器的内容。默认情况下,Istio 功能的注入会自动发生。
让我们将应用程序部署到 Kubernetes 集群:
kubectl apply -f rating-service/src/main/kubernetes/service.yml -n default
kubectl apply -f rating-service/src/main/kubernetes/deployment-v1.yml -n default
kubectl apply -f rating-service/src/main/kubernetes/deployment-v2.yml -n default
kubectl apply -f book-service/src/main/kubernetes/deployment.yml -n default
几秒钟后,应用程序将启动并运行。要进行验证,请运行以下命令并密切关注属于 Pod 的容器数量:
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
book-service-5cc59cdcfd-5qhb2 2/2 Running 0 79m
rating-service-v1-64b67cd8d-5bfpf 2/2 Running 0 63m
rating-service-v2-66b55746d-f4hpl 2/2 Running 0 63m
请注意,每个 Pod 包含两个正在运行的容器,一个容器是服务本身,另一个是 Istio 代理。
如果我们描述 Pod,我们会注意到:
kubectl describe pod rating-service-v2-66b55746d-f4hpl
Name: rating-service-v2-66b55746d-f4hpl
Namespace: default
…
Containers:
rating-service:
Container ID: docker://cda8d72194ee37e146df7bf0a6b23a184b5bfdb36fed00d2cc105daf6f0d6e85
Image: quay.io/lordofthejars/rating-service:v2.0.0
…
istio-proxy:
Container ID: docker://7f4a9c1f425ea3a06ccba58c74b2c9c3c72e58f1d805f86aace3d914781e0372
Image: docker.io/istio/proxyv2:1.6.13
由于我们使用的是 Minikube 并且 Kubernetes 服务是 LoadBalancer 类型,因此需要 Minikube IP 和服务端口来访问应用程序。要找到它们,请执行以下命令:
minikube IP -p istio
192.168.99.116
kubectl get services -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
book-service LoadBalancer 10.106.237.42 <pending> 8080:31304/TCP 111m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 132m
rating LoadBalancer 10.109.106.128 <pending> 8080:31216/TCP 95m
让我们对服务执行一些 curl 命令:
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":3}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":3}
我们可以从输出中看到,对于同一个图书 ID,评分值从 1 变为 3。默认情况下,Istio 使用循环方法跨服务平衡调用。rating:v1在此示例中,请求在(固定评级为 1)和rating:v2(在启动时计算的随机评级;在本例中,图书 ID 1 为 3 )之间平衡。
该应用程序现在已部署并“Istioized”,但尚未启用任何微服务。让我们开始创建一些 Istio 资源来启用和配置 Istio 代理容器上的微服务。
Istio 微服务
发现
Kubernetes Service实现了发现的概念。它提供了一种使用稳定的虚拟 IP 和 DNS 名称对Kubernetes Pod(充当一个)进行分组的方法。Pod 使用Kubernetes 服务名称作为主机名访问其他Pod。然而,这仅允许我们实现基本的发现策略,但是当需要更高级的发现/部署策略时,例如金丝雀发布、暗启动或阴影流量,Kubernetes 服务是不够的。
Istio 允许您使用两个概念轻松控制服务之间的流量:DestinationRule和VirtualService.
ADestinationRule定义了在路由发生后为流量提供服务的策略。我们在目标规则中配置的一些内容是:
- 交通政策。
- 负载均衡策略。
- 连接池设置。
- mTLS。
- 弹性。
- 使用标签指定服务的子集。这些子集用于VirtualService.
让我们创建一个名为destination-rule-v1-v2.yml注册两个子集的文件:一个用于评级服务 v1,另一个用于评级服务 v2:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: rating
spec:
host: rating
subsets:
- labels:
app.kubernetes.io/version: v1.0.0
name: version-v1
- labels:
app.kubernetes.io/version: v2.0.0
name: version-v2
我们将该host字段设置为 rating 因为这是Kubernetes Service中指定的 DNS 名称。然后在本subsets节中,我们使用集合定义子labels集作为 Kubernetes 资源,并将它们分组在“虚拟”下name。例如,在前一种情况下创建了两个组:一个组用于版本 1,另一个组用于对版本 2 的服务进行评级。
kubectl apply -f src/main/kubernetes/destination-rule-v1-v2.yml -n default
destinationrule.networking.istio.io/rating created
AVirtualService允许您配置如何将请求路由到 Istio 服务网格中的服务。借助虚拟服务,可以直接实施 A/B 测试、蓝/绿部署、金丝雀发布或暗启动等策略。
让我们创建一个文件virtual-service-v1.yml,将所有流量发送到 rating v1:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rating
spec:
hosts:
- rating
http:
- route:
- destination:
host: rating
subset: version-v1
weight: 100
在上一个文件中,我们正在配置任何请求以到达评级主机,该请求应发送到属于子集 version-v1 的评级 Pod。请记住,DestinationRule文件创建了这个子集。
kubectl apply -f src/main/kubernetes/virtual-service-v1.yml -n default
virtualservice.networking.istio.io/rating created
现在再次对服务执行一些curl 命令,但现在最显着的区别是输出,因为所有请求都发送到rating v1。
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
显然,我们可以创建另一个指向等级 v2 的虚拟服务文件:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rating
spec:
hosts:
- rating
http:
- route:
- destination:
host: rating
subset: version-v2
weight: 100
kubectl apply -f src/main/kubernetes/virtual-service-v2.yml -n default
virtualservice.networking.istio.io/rating configured
并且流量被发送到等级v2:
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":3}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":3}
该rating字段不再设置为 1,因为请求已由版本 2 处理。
通过更改虚拟服务中的字段来执行金丝雀发布。weight
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rating
spec:
hosts:
- rating
http:
- route:
- destination:
host: rating
subset: version-v1
weight: 75
- destination:
host: rating
subset: version-v2
weight: 25
kubectl apply -f src/main/kubernetes/virtual-service-v1-v2-75-25.yml -n default
virtualservice.networking.istio.io/rating configured
curl 现在对应用程序执行一些命令:
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":3}
weight按照字段中设置的比例,访问评级 v1 的次数比访问评级 v2 的次数多。
让我们删除虚拟服务资源以恢复默认行为(循环策略):
kubectl delete -f src/main/kubernetes/virtual-service-v1-v2-75-25.yml -n default
virtualservice.networking.istio.io "rating" deleted
弹力
在微服务架构中,我们应该在开发时考虑到失败,尤其是在与其他服务通信时。在单体应用程序中,您的应用程序作为一个整体是启动或关闭的,但在微服务架构中,情况并非如此,因为有些可能启动,有些可能停止。弹性(或应用程序弹性)是应用程序/服务对问题做出反应并仍然提供最佳结果的能力。
让我们看看 Istio 如何帮助我们实施弹性策略以及如何配置它们。
失败
评级服务实现了一个特定的端点,该端点导致服务在被访问时开始返回 503 HTTP 错误代码。
执行以下命令(更改 Pod 名称为您的),使 service rating v2 在访问时开始失败:
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
book-service-5cc59cdcfd-5qhb2 2/2 Running 4 47h
rating-service-v1-64b67cd8d-5bfpf 2/2 Running 4 47h
rating-service-v2-66b55746d-f4hpl 2/2 Running 4 47h
kubectl exec -ti rating-service-v2-66b55746d-f4hpl -c rating-service -n default curl localhost:8080/rate/misbehave
Ratings endpoint returns 503 error.
重试
目前,Istio 没有配置虚拟服务,这意味着它可以平衡两个版本之间的请求。
让我们提出一些请求并验证评级 v2 是否失败:
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
一个请求没有产生响应,因为rating v2没有返回有效的响应,而是一个错误。
Istio 支持重试并在VirtualService资源中配置。创建一个virutal-service-retry.yml具有以下内容的新文件:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: rating
spec:
hosts:
- rating
http:
- route:
- destination:
host: rating
retries:
attempts: 2
perTryTimeout: 5s
retryOn: 5xx
5XX如果评级服务(任何版本)返回HTTP 错误代码,我们配置为执行两次自动重试。
kubectl apply -f src/main/kubernetes/virtua-service-retry.yml -n default
virtualservice.networking.istio.io/rating created
让我们提出一些请求并查看输出:
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
curl 192.168.99.116:31304/book/1
{"bookId":1,"name":"Book 1","rating":1}
我们现在看到所有请求都响应等级 v1。原因很简单。当评级服务请求发送到 v1 时,会提供有效响应。但是当请求发送到 v2 时,会发生错误并执行自动重试。
由于调用在两个服务之间是负载平衡的,因此重试请求被发送到 v1,产生一个有效的响应。
因此,之前的每个请求都会返回来自 v1 的响应。
断路器
自动重试是处理网络故障或偶发错误的好方法,但是当多个并发用户通过自动重试向故障系统发送请求时会发生什么?
让我们使用HTTP 负载测试器实用程序Siege来模拟该场景,但首先,让我们使用 kubectl 命令检查 rating v2 日志:
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
book-service-5cc59cdcfd-5qhb2 2/2 Running 4 47h
rating-service-v1-64b67cd8d-5bfpf 2/2 Running 4 47h
rating-service-v2-66b55746d-f4hpl 2/2 Running 4 47h
kubectl logs rating-service-v2-66b55746d-f4hpl -c rating-service -n default
…
Request 31
Request 32
Request 33
Request 34
这些日志行显示此服务处理的请求数。目前,该服务处理了 34 个请求。
模拟四个并发用户,每一个向应用发送十个请求,执行以下围攻命令:
siege -r 10 -c 4 -v -d 1 192.168.99.116:31304/book/1
HTTP/1.1 200 0.04 secs: 39 bytes ==> GET /book/1
HTTP/1.1 200 0.03 secs: 39 bytes ==> GET /book/1
Transactions: 40 hits
Availability: 100.00 %
Elapsed time: 0.51 secs
Data transferred: 0.00 MB
Response time: 0.05 secs
Transaction rate: 78.43 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 3.80
Successful transactions: 40
Failed transactions: 0
Longest transaction: 0.13
Shortest transaction: 0.01
当然,因为有自动重试,所以不会向调用者发送任何失败,但让我们再次检查 rating v2 的日志:
kubectl logs rating-service-v2-66b55746d-f4hpl -c rating-service -n default
…
Request 56
Request 57
Request 58
Request 59
尽管 rating v2 无法生成有效响应,但该服务被访问了 25 次,对应用程序产生了相当大的影响,因为:
- 如果服务过载,发送更多请求让它恢复似乎是个坏主意。可能最好的方法是将服务实例放入隔离区。
- 如果服务只是因为错误而失败,那么重试不会改善这种情况。
- 对于每次重试,都会创建一个套接字,分配一些文件描述符,或者通过网络发送一些数据包,最终以失败告终。此过程会影响在同一节点中运行的其他服务(CPU、内存、文件描述符等)或使用网络(增加无用流量、延迟等)。
为了解决这个问题,我们需要找到一种在执行反复失败时自动快速失败的方法。断路器设计模式和隔板模式是解决这个问题的方法。前者在并发错误发生时提供快速失败策略,后者限制并发执行的数量。
现在创建一个destination-rule-circuit-breaker.yml 具有以下内容的新文件:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: rating
spec:
host: rating
subsets:
- labels:
version: v1
name: version-v1
- labels:
version: v2
name: version-v2
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 3
maxRequestsPerConnection: 3
tcp:
maxConnections: 3
outlierDetection:
baseEjectionTime: 3m
consecutive5xxErrors: 1
interval: 1s
maxEjectionPercent: 100
您应该注意的第一件事是 DestinationRule配置了断路器。除了配置断路器参数外,还需要指定子集。限制并发连接在connectionPool字段中设置。
要配置断路器,请使用该outlierDetection部分。对于此示例,如果在一秒钟的窗口内发生错误,则电路将打开,从而使服务跳闸三分钟。在这段时间之后,电路将半开,这意味着执行真正的逻辑。如果再次失败,则电路保持开路;如果没有,它就关闭了。
kubectl apply -f src/main/kubernetes/destination-rule-circuit-breaker.yml
destinationrule.networking.istio.io/rating configured
我们已经在 Istio 中配置了断路器模式;让我们再次执行siege命令并检查rating v2 的日志。
siege -r 10 -c 4 -v -d 1 192.168.99.116:31304/book/1
HTTP/1.1 200 0.04 secs: 39 bytes ==> GET /book/1
HTTP/1.1 200 0.03 secs: 39 bytes ==> GET /book/1
Transactions: 40 hits
Availability: 100.00 %
再次检查日志。请记住,在上一次运行中,我们在请求 59 处停止。
kubectl 记录 rating-service-v2-66b55746d-f4hpl -c rating-service -n 默认
kubectl logs rating-service-v2-66b55746d-f4hpl -c rating-service -n default
…
Request 56
Request 57
Request 58
Request 59
Request 60
Rating v2仅接收一个请求,因为第一个处理的请求返回错误,电路已打开,并且没有更多请求发送到 rating v2。
我们现在已经看到了使用 Istio 的弹性。Sidecar 容器不是在服务中与业务逻辑一起实现这个逻辑,而是实现它。
最后,执行以下命令将等级 v2 恢复到之前的状态。
kubectl exec -ti rating-service-v2-66b55746d-f4hpl -c rating-service curl localhost:8080/rate/behave
Back to normal
验证
我们在实现微服务架构时可能会发现的问题之一是如何保护内部服务之间的通信。我们应该使用 mTLS 吗?我们应该验证请求吗?我们应该授权他们吗?这些问题的答案是肯定的!我们将一步一步地了解 Istio 如何帮助我们解决这个问题。
验证
Istio 会自动将代理和工作负载之间的所有流量升级到 mTLS,而无需更改服务代码中的任何内容。同时,作为开发者,我们使用 HTTP 协议来实现服务。当服务被“istioized”时,服务之间的通信使用 HTTPS 进行。Istio 负责管理证书、证书颁发机构或撤销/更新证书。
为了验证 mTLS 是否启用,我们可以通过执行以下命令来使用 istioctl 工具:
istioctl experimental authz check book-service-5cc59cdcfd-5qhb2 -a
LISTENER[FilterChain] HTTP ROUTE ALPN mTLS (MODE) AuthZ (RULES)
...
virtualInbound[5] inbound|8080|http|book-service.default.svc.cluster.local istio,istio-http/1.0,istio-http/1.1,istio-h2 noneSDS: default yes (PERMISSIVE) no (none)
…
端口 8080 中的 Book-service 主机配置了使用许可策略的 mTLS。
授权
让我们看看如何使用 JSON Web Token ( JWT ) 格式通过 Istio 对最终用户进行身份验证。
首先要做的是应用资源。此策略确保如果标头包含 JWT 令牌,它必须是有效的、未过期的、由正确的颁发者颁发且未被操纵。RequestAuthenticationAuthorization
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "bookjwt"
namespace: default
spec:
selector:
matchLabels:
app.kubernetes.io/name: book-service
jwtRules:
- issuer: "testing@secure.istio.io"
jwksUri: "https://gist.githubusercontent.com/lordofthejars/7dad589384612d7a6e18398ac0f10065/raw/ea0f8e7b729fb1df25d4dc60bf17dee409aad204/jwks.json"
基本领域是:
- issuer:令牌的有效发行者。如果提供的令牌未在issJWT 字段中指定此颁发者,则令牌无效。
- jwksUrijwks:注册公钥以验证令牌签名的文件的 URL 。
kubectl apply -f src/main/kubernetes/request-authentication-jwt.yml -n default
requestauthentication.security.istio.io/bookjwt created
curl现在再次使用无效令牌运行命令:
curl 192.168.99.116:31304/book/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUU"
Jwt verification fails
由于令牌无效,请求被 HTTP/1.1 401 Unauthorized 代码拒绝。
使用有效令牌重复上一个请求:
curl 192.168.99.116:31304/book/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg"
{"bookId":1,"name":"Book 1","rating":3}
我们现在看到一个有效的响应,因为令牌是正确的。
到目前为止,我们只对请求进行身份验证(只需要有效的令牌),但 Istio 还支持遵循基于角色的访问控制 (RBAC) 模型的授权。让我们创建一个AuthorizationPolicy仅允许具有有效 JSON Web 令牌且声明角色设置为客户的请求。创建一个文件名authorization-policy-jwt.yml:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: default
spec:
selector:
matchLabels:
app.kubernetes.io/name: book-service
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
when:
- key: request.auth.claims[role]
values: ["customer"]
kubectl apply -f src/main/kubernetes/authorization-policy-jwt.yml
authorizationpolicy.security.istio.io/require-jwt created
然后执行与之前完全相同的 curl 命令:
curl 192.168.99.116:31304/book/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg"
RBAC: access denied
这次的反应略有不同。尽管令牌有效,但访问被拒绝,因为令牌没有为客户设置声明的角色。
让我们使用以下令牌:
curl 192.168.99.116:31304/book/1 -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjI1NDkwNTY4ODgsImlhdCI6MTU0OTA1Njg4OSwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJyb2xlIjoiY3VzdG9tZXIiLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.VM9VOHD2NwDjQ6k7tszB3helfAn5wcldxe950BveiFVg43pp7x5MWTjMtWQRmQc7iYul19PXsmGnSSOiQQobxdn2UnhHJeKeccCdX5YVgX68tR0R9xv_wxeYQWquH3roxHh2Xr2SU3gdt6s7gxKHrW7Zc4Z9bT-fnz3ijRUiyrs-HQN7DBc356eiZy2wS7O539lx3mr-pjM9PQtcDCDOGsnmwq1YdKw9o2VgbesfiHDDjJQlNv40wnsfpq2q4BgSmdsofAGwSNKWtqUE6kU7K2hvV2FvgwjzcB19bbRYMWxRG0gHyqgFy-uM5tsC6Cib-gPAIWxCdXDmLEiqIdjM3w"
{"bookId":1,"name":"Book 1","rating":3}
我们现在看到一个有效的响应,因为令牌是正确的并且包含一个有效的角色值。
可观察性
Istio 安装了四个组件以满足可观察性要求:
- Prometheus用于监控。
- 用于可视化的Grafana。
- Jaeger + Zipkin用于追踪。
- Kiali拥有应用程序的全球概览。
从命名空间中获取所有 Pod istio-system:
kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-b54bb57b9-k5qbm 1/1 Running 0 178m
istio-egressgateway-68587b7b8b-vdr67 1/1 Running 0 178m
istio-ingressgateway-55bdff67f-hlnqw 1/1 Running 0 178m
istio-tracing-9dd6c4f7c-44xhk 1/1 Running 0 178m
istiod-76bf8475c-xphgd 1/1 Running 7 177d
kiali-d45468dc4-fl8j4 1/1 Running 0 178m
prometheus-74d44d84db-zmkd7 2/2 Running 0 178m
监控
Istio 与Prometheus集成,用于发送与网络流量和服务相关的各种信息。此外,它提供了一个Grafana实例来可视化所有收集的数据。
要访问 Grafana,让我们使用 port-forward 命令公开 Pod:
kubectl port-forward -n istio-system grafana-b54bb57b9-k5qbm 3000:3000
Forwarding from 127.0.0.1:3000 -> 3000
Forwarding from [::1]:3000 -> 3000
打开浏览器并通过导航到 访问 Grafana 仪表板locahost:3000。
Kiali是在 Istio 中运行的另一个工具,用于管理 Istio 并观察服务网格参数,例如服务如何连接、它们如何执行以及注册了哪些 Istio 资源。
要访问 Kiali,让我们使用 port-forward 命令公开 Pod:
kubectl port-forward -n istio-system kiali-d45468dc4-fl8j4 20001:20001
Forwarding from 127.0.0.1:20001 -> 20001
Forwarding from [::1]:20001 -> 20001
打开浏览器,访问 Istio 仪表板,然后导航到 locahost:20001。
追踪
跟踪用于可视化程序的流程和数据进程。Istio 拦截所有请求/响应并将它们发送给Jaeger。
我们可以不使用 port-forward 命令,而是使用istioctl暴露端口并自动打开页面:
istioctl dashboard jaeger
结论
开发和实现微服务架构比开发单体应用程序更具挑战性。我们相信,微服务可以推动您在应用程序基础架构方面正确创建服务。
Istio 在 sidecar 容器中实现了其中一些微服务,使其可在所有服务中重用,而与应用程序使用的编程语言无关。
此外,Istio 方法允许我们更改服务的行为,而无需重新部署它们。
如果您计划开发微服务并将其部署到 Kubernetes,Istio 是一个可行的解决方案,因为它可以与 Kubernetes 顺利集成。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请通知我们,一经查实,本站将立刻删除。