建设网站服务器自营方式,网站建设中外链与内链的技巧,化妆品网站建设目标,什么是网络营销网络营销的目的有哪些内容K8S如何基于Istio重新实现微服务 认识 Istio前言Istio 的理念Istio 的架构数据平面控制平面服务与 Istio 的关系 Istio 实践环境准备安装 Helm安装Istio 使用 Istio 代理运行应用情感分析应用的架构使用 Istio 代理运行应用Sidecar 注入Ingress 网关网关资源VirtualService 资源… K8S如何基于Istio重新实现微服务 认识 Istio前言Istio 的理念Istio 的架构数据平面控制平面服务与 Istio 的关系 Istio 实践环境准备安装 Helm安装Istio 使用 Istio 代理运行应用情感分析应用的架构使用 Istio 代理运行应用Sidecar 注入Ingress 网关网关资源VirtualService 资源 跨服务跟踪和流量管理内置的特性Kiali——可观察性Grafana——指标可视化Jaeger——跟踪 流量管理A/B 测试——Destination Rule 实践DestinationRulesShadowing———Virtual Services 服务实践通过 Destination Rule 声明子集金丝雀部署超时和重试 认证和授权断路器和舱壁模式Istio 中的认证和授权使用 Auth0 进行认证使用 Auth0 认证请求更新前端 使用 Auth0 进行授权安装和配置 Auth0 授权1. 创建Group2. 添加 Group Claim 到 Access Token3. 在 Istio 中配置授权4. 配置常规的用户访问5. 配置 Moderator 用户访问 结论参考链接 认识 Istio
前言
从单体架构迁移到基于微服务的应用时会带来很多的挑战
流量管理Traffic management超时、重试、负载均衡
安全性Security终端用户的认证和授权
可观察性Observability跟踪、监控和日志。 这些问题都可以在应用层解决但不符合微服务的初衷针对每个服务所有的过程和努力必须都要再来一遍每实现这些功能都会对公司的资源带来一定的压力。而且对我们来说添加一个服务是一项巨大的任务。
Istio 的理念
Istio 是一个开源项目由来自 Google、IBM 和 Lyft 团队协作开发它为一组特定的问题提供了解决方案。
在没有 Istio 的时候某个服务会直接发起对另一个服务的访问如果出现失败这个服务需要作出的处理包括重试、超时以及熔断等等。 Istio 提供了一个非常巧妙的方案它能够从服务中完全分离出来通过只拦截所有的网络通信来发挥作用。能够实现如下功能
容错当请求失败和重试的时候使用它能够理解的响应状态码
金丝雀发布只将特定百分比的请求转发到服务的新版本上
监控和指标统计服务响应所耗费的时间
跟踪和可观察性它在每个请求上添加了一个特殊的头部信息并在集群中跟踪它们
安全性抽取 JWT Token 并对用户进行认证和授权。
Istio 的架构
Istio 会拦截所有的网络流量并且会在每个 pod 上以 sidecar 的形式注入一个智能代理从而能够应用一组特定的规则。让所有特性生效的代理是由**数据平面Data Plane组成的而数据平面是由控制平面Control Plane**动态配置的。
数据平面
注入的代理能够让 Istio 很容易地满足我们的需求 简单总结一下
Envoy(数据平面) 发送请求给服务 B 的第一个实例不过调用失败了
Envoy Sidecar 会进行重试1
失败的请求返回发起调用的代理
开启断路器并在随后的请求中调用下一个服务。2
这意味着我们不需要使用额外的重试库也不需要使用各种各样的语言实现断路器和服务发现功能。这些特性都是由 Istio 所提供的我们不需要任何的代码修改。
它是一个适用于所有场景的解决方案吗通常来讲适用所有场景意味着难以非常好地适用任何场景。
控制平面
控制平面由三个组件组成Pilot、Mixer 和 Citadel它们组合配置 Envoy 以便于路由流量、执行策略和收集遥测数据。 Envoy也就是数据平面会由 Istio 规定的Kubernetes自定义资源定义Kubernetes Custom Resource Definition来进行配置这些定义是专门为了实现该目的的。这就是说对你而言它只是具有熟悉语法的 Kubernetes 资源而已。在创建之后控制平面会获取到它并将其用到 Envoy 上。
服务与 Istio 的关系
服务Service是 Kubernetes 中基础的网络抽象而 Istio 作为服务网格增强了服务的流量管理、安全、监控等能力使得微服务架构更加可靠和可管理。
我们可以选取一个可运行的集群在安装 Istio 组件之后集群中的服务依然能够继续运行我们可以按照相同的方式将组件移除掉所有的事情都能正常运行只不多我们失去了 Istio 所提供的功能而已。
Istio 实践
环境准备
我们需要搭建一个至少 4 vCPU 和 8 GB RAM 的集群。本文已经在如下的 Kubernetes 实现中测试通过
Google Container Engine (GKE)
Azure Kubernetes Service (AKS)
Digital Ocean (20天免费的Kubernetes链接)
创建完集群并使用 Kubernetes 命令行配置完访问权限后我们就可以使用 Helm 包管理器安装 Istio 了。
安装 Helm
按照官方文档的描述在你的机器上安装 Helm 客户端。在下一节中我们将会使用它来生成 Istio 安装模板。
安装Istio
从最新的发布版本下载 Istio 的资源将其抽取到一个目录中我们会将其称为[istio-resources]。
为了更容易地识别 Istio 资源我们在 Kubernetes 集群中创建命名空间istio-system $ kubectl create namespace istio-system
切换至[istio-resources]并执行如下命令完成安装
$ helm template install/kubernetes/helm/istio \ --set global.mtls.enabledfalse \--set tracing.enabledtrue \--set kiali.enabledtrue \--set grafana.enabledtrue \ --namespace istio-system istio.yaml
上述的命令会将 Istio 的核心组件打印到istio.yaml文件中。我们使用如下的参数对模板进行自定义
global.mtls.enabled将这个值设置为 false保证我们这篇介绍文章能够只关注重点
tracing.enabled启用 jaeger 的请求跟踪功能
kiali.enabled在我们的集群中安装 Kiali实现服务和流量的可视化
grafana.enabled安装 Grafana实现收集指标的可视化。
注意从 Istio 1.5 版本开始global.mtls.enabled 被弃用Istio 引入了更为灵活和细粒度的 mTLS 控制机制使用 PeerAuthentication 和 DestinationRule 来配置不同命名空间或服务的 mTLS 策略。通过这些新机制用户可以更好地控制某些服务或命名空间的加密策略而不是全局统一开启或关闭。 创建istio资源 kubectl apply -f istio.yaml
执行如下的命令然后等待istio- system命名空间中的所有 pod 均处于 Running 或 Completed 状态 kubectl get pods -n istio-system
使用 Istio 代理运行应用
情感分析应用的架构
继续使用Kubernetes入门文章中的样例它较为复杂足以通过实践展示 Istio 的特性
这个应用由四个微服务组成
SA-Frontend 服务前端的 Reactjs 应用
SA-WebApp 服务处理情感分析的请求
SA-Logic 服务执行情感分析
SA-Feedback 服务接收用户关于分析精确性的反馈。 除了服务之外我们还看到了 Ingress Controller在 Kubernetes 中它会将传入的请求路由至对应的服务Istio 采用了类似的概念名为 Ingress Gateway在本文后续的内容中我们将会对其进行介绍。
使用 Istio 代理运行应用
如果要要跟着本文一起练习的话读者可以 clone 该 GitHub 仓库 istio-mastery其中包含了适用于 Kubernetes 和 Istio 的应用程序与 manifest。
Sidecar 注入
注入可以自动或手动完成。如果要启用自动化的 Sidecar 注入我们需要使用istio-injectionenabled来标记命名空间这可以通过执行如下的命令来实现
$ kubectl label namespace default istio-injectionenablednamespace/default labeled
现在默认命名空间中部署的所有 pod 都将会被注入 sidecar。切换至[istio-mastery]仓库的根目录并执行如下的命令 $ kubectl apply -f resource-manifests/kube
persistentvolumeclaim/sqlite-pvc created
deployment.extensions/sa-feedback created
service/sa-feedback created
deployment.extensions/sa-frontend created
service/sa-frontend created
deployment.extensions/sa-logic created
service/sa-logic created
deployment.extensions/sa-web-app created
service/sa-web-app created
执行如下的命令并检查 Ready 列我们会看到“2/2”这表明第二个容器已经注入进来了。 $ kubectl get pods
NAME READY STATUS RESTARTS AGE
sa-feedback-55f5dc4d9c-c9wfv 2/2 Running 0 12m
sa-frontend-558f8986-hhkj9 2/2 Running 0 12m
sa-logic-568498cb4d-s9ngt 2/2 Running 0 12m
sa-web-app-599cf47c7c-s7cvd 2/2 Running 0 12m
服务已经启动并运行了每个容器都包含了 sidecar 代理。 但是要访问服务我们需要允许传入的流量进入集群也就是所谓的 Ingress 流量。
Ingress 网关
允许流量进入集群的一个最佳实践就是使用 Istio 的 Ingress 网关它处于集群的边缘并且靠近传入的流量它能够实现 Istio 的多项特性比如路由、安全和监控。
在 Istio 安装的时候Ingress 网关组件以及将其暴露给外部的服务已经安装到了集群中可以通过如下的命令获取它的外部 IP $ kubectl get svc -n istio-system -l istioingressgateway
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
istio-ingressgateway LoadBalancer 10.0.132.127 13.93.30.120 80:31380/TCP,443[...]
在本文后续的内容中我们将会通过该 IP将其称为EXTERNAL-IP访问应用程序为了便利起见我们通过下面的命令将其保存到变量中
$ EXTERNAL_IP$(kubectl get svc -n istio-system \-l appistio-ingressgateway \-o jsonpath{.items[0].status.loadBalancer.ingress[0].ip})
如果你在浏览器中访问该 IP 的话将会看到服务不可用的错误默认情况下在我们定义网关之前Istio 会阻止所有传入的流量。
网关资源
网关是一种 Kubernetes 自定义资源定义Kubernetes Custom Resource Definition它是我们在集群中安装 Istio 时所定义的借助它我们能够指定允许传入流量的端口、协议和主机。
在我们的场景中我们想要为所有主机开放 80 端口。我们可以通过如下的定义来进行配置 apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:name: http-gateway
spec:selector:istio: ingressgatewayservers:- port:number: 80name: httpprotocol: HTTPhosts: - *
除了istio: ingressgateway选择器之外所有配置项的含义均不言自明。通过使用这个选择器我们可以指定哪个 Ingress 网关使用该配置在我们的场景中也就是在 Istio 安装时的默认 Ingress 网关控制器。
通过执行如下的命令应用该配置 $ kubectl apply -f resource-manifests/istio/http-gateway.yaml
gateway.networking.istio.io http-gateway created
网关允许我们访问 80 端口但是它还不知道要将请求路由至何处而这一功能是通过 Virtual Service 来实现的。
VirtualService 资源
VirtualService 能够指导 Ingress 网关如何路由允许进入集群的请求。
对于我们的应用来说通过 http-gateway 的请求必须要路由至** sa-frontend、sa-web-app 和 sa-feedback 服务**。 现在我们拆分一下应该路由至 SA-Frontend 的请求 精确的路径“/”应该路由至 SA-Frontend 以便于获取 Index.html 带有“/static/*”前缀的路径应该路由至 SA-Frontend以便于获取前端所需的静态文件比如级联样式表和 JavaScript 文件 匹配正则表达式“^.*.(ico|png|jpg)$”的路径应该路由至 SA-Frontend因为它代表的是页面展现所需的图片。
这样我们就会得到如下的配置
kind: VirtualService
metadata:name: sa-external-services
spec:hosts:- *gateways:- http-gateway # 1http:- match:- uri:exact: /- uri:exact: /callback- uri:prefix: /static- uri:regex: ^.*\.(ico|png|jpg)$ route:- destination:host: sa-frontend # 2port:number: 80
这里的重点在于
VirtualService 将会应用于通过 http-gateway 的请求
destination 定义了请求要路由至哪个服务。
注意上面的配置在sa-virtualservice-external.yaml文件中它还包含了路由至 SA-WebApp 和 SA-Feedback 的配置但是简洁期间我们将其省略了。
通过执行如下的命令应用 VirtualService $ kubectl apply -f resource-manifests/istio/sa-virtualservice-external.yaml
virtualservice.networking.istio.io sa-external-services created
注意当我们应用该资源时其实所有的 Istio 资源均如此Kubernetes API Server 会创建一个新的事件该事件会被 Istio 的控制平面接收到然后会将新的配置应用到每个 pod 的 envoy 代理上。Ingress Gateway 控制器是 Control Plane 配置的另外一个 Envoy如图所示。 现在我们可以通过http://{{EXTERNAL-IP}}/访问情感分析应用了。如果你遇到 Not Found 状态的话请不要担心有时候配置生效并更新 envoy 缓存会耗费一点时间。
跨服务跟踪和流量管理
内置的特性
通过拦截所有的网络通信Istio 能够得到一些指标和数据这些指标和数据能够用来实现整个应用的可观察性。Kiali是一个开源的项目它能够利用这些数据回答这样的问题微服务是如何成为 Istio 服务网格的一部分的它们是如何连接在一起的
Kiali——可观察性
在安装 Istio 到我们的集群中之前我们创建了一个用于 kiali 的 secret还有另外一个用于 grafana其中我们将 user 和 password 都设置为 admin。要访问 Kiali 的 Admin UI需要执行如下的命令 # 端口转发在本地通过指定的端口访问运行在集群内的 Pod 上的服务 kiali
$ kubectl port-forward \$(kubectl get pod -n istio-system -l appkiali -o jsonpath{.items[0].metadata.name}) \-n istio-system 20001
打开http://localhost:20001/并使用“admin”不含引号作为 user 和 password 进行登录。这里很有多有用的特性比如检查 Istio 组件的配置、拦截网络请求和响应所收集的信息构建的可视化服务比如“谁调用了谁”“哪个版本的服务出现了故障”等等。在进入下一步章节学习使用 Grafana 可视化指标之前请花一些时间检验 Kiali 的功能。 Grafana——指标可视化
Istio 收集到的指标被放到了 Prometheus 中并使用 Grafana 进行可视化。要访问 Grafana 的 Admin UI请执行如下的命令并打开http://localhost:3000。 $ kubectl -n istio-system port-forward \$(kubectl -n istio-system get pod -l appgrafana -o jsonpath{.items[0].metadata.name}) 3000
在左上角点击 Home 菜单并选择 Istio Service Dashboard然后选择 sa-web-app 打头的服务这样我们就会看到收集到的指标如下图所示 通过如下的命令我们可以造一些负载出来 $ while true; do \curl -i http://$EXTERNAL_IP/sentiment -H Content-type: application/json \ -d {sentence: I love yogobella} \sleep .8; done
Prometheus 能够用来监控Grafana 能用来对指标进行可视化有了它们之后我们就能知道随着时间的推移服务的性能、健康状况以及是否有升级和降级。
Jaeger——跟踪
我们需要跟踪系统的原因在于服务越多就越难以定位失败的原因。以下面图中这个简单的场景为例 请求调用进来了但是出现了失败失败的原因是什么呢是第一个服务失败还是第二个服务失败两者都发生了异常我们需要探查它们的日志。
这是微服务中一个非常普遍的问题借助分布式的跟踪系统可以解决该问题这种跟踪系统会在服务间传递一个唯一的 header 信息然后这个信息会发送至分布式跟踪系统中这样请求的跟踪信息就能汇总在一起了。 Istio 使用了 Jaeger Tracer后者实现了 OpenTracing API这是一个独立于供应商的框架。要访问 Jaegers UI请执行如下的命令 $ kubectl port-forward -n istio-system \$(kubectl get pod -n istio-system -l appjaeger -o jsonpath{.items[0].metadata.name}) 16686
然后通过http://localhost:16686打开 UI选择 sa-web-app 服务如果服务没有出现在下拉列表中的话那么在页面进行一些活动并点击刷新。随后点击 Find Traces 按钮它只会展现最近的 trace选择任意一条记录所有的跟踪信息将会分别展示如图所示 trace 展示了如下所述的信息 传入 istio-ingressgateway 的请求这是第一次与某个服务产生关联所以会生成 Trace ID随后网关将请求转发至 sa-web-app 服务 在 sa-web-app 服务中请求会被 Envoy 容器捕获并生成一个 span child这就是我们能够在 trace 中看到它的原因然后会被转发至 sa-web-app 应用。 在这里sentimentAnalysis 方法会处理请求。这些 trace 是由应用生成的这意味着需要对代码进行一些修改 在此之后会发起针对 sa-logic 的 POST 请求。sa-web-app 需要将 Trace ID 传播下去
……
注意在第 4 步我们的应用需要获取 Istio 生成的 header 信息并将其向下传递给下一个请求如下面的图片所示。 AIstio 传播 header 信息; B服务传播 header 信息
Istio 承担了主要的工作它会为传入的请求生成 header 信息、为每个 sidecar 创建新的 span 并传递 span但是如果我们的服务不同时传播这些 header 信息的话那么我们就会丢失请求的完整跟踪信息。
要传播的 header 信息如下所示
x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context
尽管这是一个很简单的任务但依然有很多的库在简化这个过程例如在 sa-web-app 服务中RestTemplate 客户端就进行了 instrument 操作这样的话我们只需要在依赖中添加 Jaeger 和 OpenTracing 就能传播 header 信息了。
流量管理
借助 EnvoyIstio 能够提供为集群带来很多新功能
动态请求路由金丝雀发布、A/B 测试 负载均衡简单一致的 Hash 均衡 故障恢复超时、重试、断路器 故障注入延时、请求中断等。
在本文后面的内容中我们将会在自己的应用中展示这些功能并在这个过程中介绍一些新的概念。我们要介绍的第一个概念就是 DestinationRules并使用它们来实现 A/B 测试。
A/B 测试——Destination Rule 实践
A/B 测试适用于应用有两个版本的场景中通常这些版本在视觉上有所差异我们无法 100%确定哪个版本能够增加用户的交互所以我们同时尝试这两个版本并收集指标。
$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions/sa-frontend-green created
绿色版本的部署 manifest 有两点差异 镜像基于不同的标签istio-green pod 添加了version: green标记。
因为两个 deployment 都有app: sa-frontend标记所以 virtual service sa-external-services在将请求路由至sa-frontend服务时会转发到这两者上面并且会使用 round robin 算法进行负载均衡这样会导致下图所示的问题。 这里有文件没有找到的错误这是因为在应用的不同版本中它们的名称不相同。我们验证一下
$ curl --silent http://$EXTERNAL_IP/ | tr \n | grep main
/static/css/main.c7071b22.css
/static/js/main.059f8e9c.js
$ curl --silent http://$EXTERNAL_IP/ | tr \n | grep main
/static/css/main.f87cd8c9.css
/static/js/main.f7659dbb.js
也就是说index.html在请求某个版本的静态文件时被负载均衡到了另外一个版本的 pod 上了这样的话就会理解为其他文件不存在。这意味着对我们的应用来说如果想要正常运行的话就要引入一种限制“为 index.html 提供服务的应用版本必须也要为后续的请求提供服务”。
我们会使用一致性 Hash 负载均衡Consistent Hash Loadbalancing来达成这种效果这个过程会将同一个客户端的请求转发至相同的后端实例中在实现时会使用一个预先定义的属性如 HTTP header 信息。使用 DestionatioRules 就能够让这一切变成现实。
DestinationRules
在 VirtualService 将请求路由至正确的服务后借助 DestinationRules 我们能够为面向该服务实例的流量指定策略如图所示。 注意 上图用非常易于理解的方式可视化地展现了 Istio 资源是如何影响网络流量的。但是精确决定请求要发送至哪个实例是 Ingress Gateway 的 Envoy 完成的它需要使用 CRD 来进行配置。
借助 Destination Rules 我们可以配置负载均衡使用一致哈希算法从而确保相同的用户会由同一个服务实例提供响应。我们可以通过如下的配置实现这一点 apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:name: sa-frontend
spec:host: sa-frontendtrafficPolicy:loadBalancer:consistentHash:httpHeaderName: version # 1
1.根据“version”头信息的内容生成 consistentHash
执行如下的命令应用该配置
$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yamldestinationrule.networking.istio.io/sa-frontend created
执行如下命令并校验在指定 version header 信息时会得到相同的文件 $ curl --silent -H version: yogo http://$EXTERNAL_IP/ | tr \n | grep main
注意在浏览器中你可以使用chrome扩展为 version 设置不同的值。
DestinationRules 有很多其他负载均衡的功能关于其细节请参考官方文档。
在更详细地探索 VirtualService 之前通过如下的命令移除应用的 green 版本和 DestinationRules $ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions sa-frontend-green deleted$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io sa-frontend deleted
Shadowing———Virtual Services 服务实践
当我们想要在生产环境测试某项变更但是不想影响终端用户的时候可以使用影子Shadowing或镜像Mirroring技术这样的话我们能够将请求镜像至具有变更的第二个实例并对其进行评估。或者说更简单的场景你的同事解决了一个最重要的缺陷并提交了包含大量内容的 Pull Request没人能够对其进行真正的审查。
为了测试这项特性我们通过如下的命令创建 SA-Logic 的第二个实例它是有缺陷的 $ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service.buggy.yaml
执行下面的命令校验所有的版本除了appsa-logic之外都带有对应版本的标记 $ kubectl get pods -l appsa-logic --show-labels
NAME READY LABELS
sa-logic-568498cb4d-2sjwj 2/2 appsa-logic,versionv1
sa-logic-568498cb4d-p4f8c 2/2 appsa-logic,versionv1
sa-logic-buggy-76dff55847-2fl66 2/2 appsa-logic,versionv2
sa-logic-buggy-76dff55847-kx8zz 2/2 appsa-logic,versionv2
因为sa-logic服务的目标是带有appsa-logic标记的 pod所以传入的请求会在所有的实例间进行负载均衡如图所示 但是我们想要将请求路由至 version v1 的实例并镜像至 version v2如图所示 这可以通过组合使用 VirtualService 和 DestinationRule 来实现Destination Rule 声明子集而 VirtualService 路由至特定的子集。
通过 Destination Rule 声明子集
通过如下的配置定义子集
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:name: sa-logic
spec:host: sa-logic # 1subsets:- name: v1 # 2labels:version: v1 # 3- name: v2labels:version: v2 host 定义这个规则只适用于路由至 sa-logic 服务的请求 当路由至子集实例时所使用子集名称 以键值对形式定义的标记将实例定义为子集的一部分。
通过执行如下的命令应用该配置 $ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic created
在子集定义完之后我们可以继续配置 VirtualService让针对 sa-logic 的请求遵循如下的规则 路由至名为 v1 的子集 镜像至名为 v2 的子集。
可以通过如下的 VirtualService 实现 apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1mirror:host: sa-logicsubset: v2
所有的配置都很具有表述性我们不再赘述接下来看一下它的实际效果 $ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic created
通过执行如下命令生成一些负载
$ while true; do curl -v http://$EXTERNAL_IP/sentiment \ -H Content-type: application/json \-d {sentence: I love yogobella}; \sleep .8; done
在 Grafana 检查结果你会发现有缺陷的版本会出现 60%的请求失败但是这些失败都不会影响终端用户因为他们是由当前活跃的服务来提供响应的。 在本节中我们第一次看到如何将 VirtualService 用到服务的 envoy 上当 sa-web-app 发起对 sa-logic 的请求时会经过 sidecar envoy借助我们配置的 VirtualService将会路由至 sa-logic 的 v1 子集并镜像至 v2 子集。
金丝雀部署
金丝雀Canary部署指的是让少量用户使用应用新版本的一个过程借助这一过程能够验证新版本是否存在问题然后能够确保以更高的质量发布给更多的受众。
继续使用sa-logic带有缺陷的子集来阐述金丝雀发布。
首先我们大胆地将 20%的用户发送至带有缺陷的版本这就是金丝雀发布并将 80%的用户发送至正常的服务这可以通过如下的 VirtualService 来实现
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1weight: 80 # 1- destination:host: sa-logicsubset: v2weight: 20 # 1
权重声明了请求要转发到目的地或目的地子集的百分比。
通过下面的命令更新上述的sa-logic virtual service
$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configured
我们马上可以看到有些请求失败了 $ while true; do \curl -i http://$EXTERNAL_IP/sentiment -H Content-type: application/json \-d {sentence: I love yogobella} \--silent -w Time: %{time_total}s \t Status: %{http_code}\n -o /dev/null; \ sleep .1; done
Time: 0.153075s Status: 200
Time: 0.137581s Status: 200
Time: 0.139345s Status: 200
Time: 30.291806s Status: 500
VirtualServices 实现了金丝雀发布借助这种方法我们将潜在的损失降低到了用户群的 20%。非常好现在当我们对代码没有确切把握的时候就可以使用 Shadowing 技术和金丝雀发布。
超时和重试
代码不仅仅会有缺陷在“分布式计算的8个谬误”中排名第一的就是“网络是可靠的”。网络实际上是不可靠的这也是我们需要超时和重试的原因。
为了便于阐述我们将会继续使用有缺陷版本的sa-logic其中随机的失败模拟了网络的不可靠性。
带有缺陷的服务版本会有三分之一的概率在生成响应时耗费过长的时间三分之一的概率遇到服务器内部错误其余的请求均能正常完成。
为了降低这些缺陷的影响并给用户带来更好的用户体验我们会采取如下的措施 如果服务耗时超过了 8 秒钟将会超时 对于失败的请求进行重试。
可以通过如下的资源定义来实现
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1weight: 50- destination:host: sa-logicsubset: v2weight: 50timeout: 8s # 请求有 8 秒钟的超时retries:attempts: 3 # 它会尝试 3 次perTryTimeout: 3s # 如果耗时超过 3 秒钟就将本次尝试标记为失败。
这是一种优化用户的等待不会超过 8 秒钟如果失败的话我们将会重试三次这样的话增加了获取成功响应的概率。
通过如下命令应用更新后的配置 $ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts-vs.yaml
virtualservice.networking.istio.io/sa-logic configured
查阅 Grafana 中的图表看成功率是否有所提升 在进入下一节之前通过如下命令移除掉sa-logic-buggy和 VirtualService
$ kubectl delete deployment sa-logic-buggy
deployment.extensions sa-logic-buggy deleted
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io sa-logic deleted
认证和授权
断路器和舱壁模式
在微服务架构中有两个重要的模式它们能够让服务实现自愈的效果。
断路器模式(Circuit Breake)
能够阻止请求发送到不健康的服务实例上这样的话服务能够进行恢复同时客户端的请求将会转发到服务的健康实例上增加了成功率。
舱壁模式(Bulkhead Pattern)
会隔离失败避免整个系统宕机举例来说服务 B 处于有问题的状态另外一个服务服务 B 的客户端往服务 B 发送请求将会导致客户端耗尽线程池从而不能为其他请求提供服务即便其他的请求与服务 B 毫无关系也是如此。
这里我不再展示这些模式的实现了因为我迫不及待想要分享认证和授权的功能这些模式的实现你可以参考官方文档。
Istio 中的认证和授权
Istio 将认证和授权从我们的服务中剥离了出去并将其委托给了 Envoy 代理这意味着当请求抵达我们的服务时它们已经经过了认证和授权我们只需要编写提供业务价值的代码就可以了。
使用 Auth0 进行认证
我们会使用 Auth0 作为身份识别和访问管理的服务器它有一个试用方案对我来说非常便利我非常喜欢它这也就是说相同的理念可以用于任意的OpenID Connect实现比如 KeyCloak、IdentityServer 等等。
首先使用预设账号导航至Auth0 Portal在 Applications Default App 下创建一个租户并记住 Domain如下所示 更新resource-manifests/istio/security/auth-policy.yaml文件以便于使用该 Domain apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:name: auth-policy
spec:targets:- name: sa-web-app- name: sa-feedbackorigins:- jwt:issuer: https://{YOUR_DOMAIN}/jwksUri: https://{YOUR_DOMAIN}/.well-known/jwks.jsonprincipalBinding: USE_ORIGIN
借助该资源Pilot 将会配置 envoy 在将请求转发至 sa-web-app 和 sa-feedback 服务之前进行认证。同时它不会将该规则应用于服务sa-frontend的 envoy这样的话前端能够不用进行认证。为了应用该策略我们需要执行如下命令 $ kubectl apply -f resource-manifests/istio/security/auth-policy.yaml
policy.authentication.istio.io auth-policy created
回到页面并发送一个请求我们将会看到 401 Unauthorized现在我们让来自前端的用户使用 Auth0 进行认证。
使用 Auth0 认证请求
要认证终端用户的请求我们需要在 Auth0 中创建一个 API它代表了经过认证的服务比如评论、详情和评级。为了创建这样的 API我们需要导航至 Auth0 Portal APIs Create API如下图所示 这里重要的信息是 Identifier在随后的脚本中我们会以如下的形式进行使用
Audience: {YOUR_AUDIENCE}
其余所需的细节在 Auth0 Portal 的 Applications 下进行配置选择自动创建的 Test Application其名称与 API 相同。
这里需要注意
Domain: {YOUR_DOMAIN}
Client Id: {YOUR_CLIENT_ID}
在 Test Application 中向下滚动至 Allowed Callback URLs 文本域在这里我们可以指定在认证完成后要转发到哪个 URL在我们的场景中应该是 http://{EXTERNAL_IP}/callback
在 Allowed Logout URLs 中添加如下的 URL http://{EXTERNAL_IP}/logout
接下来我们更新前端。
更新前端
切换至仓库的 auth0 分支。在这个分支中前端包含了将用户转发至 Auth0 进行认证的代码变更并在发送至其他服务的请求中包含了 JWT Token如下所示 analyzeSentence() {fetch(/sentiment, {method: POST,headers: {Content-Type: application/json,Authorization: Bearer ${auth.getAccessToken()} // Access Token },body: JSON.stringify({ sentence: this.textField.getValue() }) }).then(response response.json()) .then(data this.setState(data));
}
为了更新前端以便于使用租户的详细信息我们导航至sa-frontend/src/services/Auth.js文件并替换如下的值也就是在前文中我提醒注意的地方 const Config {clientID: {YOUR_CLIENT_ID},domain:{YOUR_DOMAIN},audience: {YOUR_AUDIENCE},ingressIP: {EXTERNAL_IP} // Used to redirect after authentication
}
应用已经就绪了在如下的命令指定 docker user id然后构建和部署变更 $ docker build -f sa-frontend/Dockerfile \-t {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0 sa-frontend$ docker push {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
$ kubectl set image deployment/sa-frontend \ sa-frontend{DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
我们可以尝试运行一下应用。访问的时候你会被导航至 Auth0在这里必要要登录或注册再次之后会重新转发会页面这时候就可以发送认证后的请求了。如果你使用之前的 curl 命令的话将会遇到 401 状态码表明请求没有经过授权。
接下来我们更进一步对请求进行授权。
使用 Auth0 进行授权
认证能够让我们知道用户是谁但是我们需要授权信息才能得知他们能够访问哪些内容。Istio 也提供了这样的工具。
作为样例我们会创建两组用户如图所示
Users只能访问 SA-WebApp 和 SA-Frontend 服务
Moderators能够访问所有的三个服务。 为了创建用户组我们需要使用 Auth0 Authorization 扩展随后借助 Istio我们为它们提供不同级别的访问权限。
安装和配置 Auth0 授权
在 Auth0 Portal 中切换至 Extensions并安装“Auth0 Authorization”扩展。安装之后切换至 Authorization Extension 并点击右上角你的租户并选择“Configuration”选项进行配置。启用组并点击“Publish rule”按钮。 1. 创建Group
在 Authorization Extension 中导航至 Groups 并创建 Moderators 组。我们会将所有已认证过的用户视为普通的 User这样的话就没有必要额外再建立一个组了。
选择 Moderators 组并点击 Add Members然后添加你的主账号。请将一些用户不放到任何的组中以便于检验禁止他们访问你可以通过 Auth0 Portal Users Create User 手动注册新用户。
2. 添加 Group Claim 到 Access Token
用户现在已经添加到组中了但是这些信息还没有反映到 Access Token 中。为了确保 OpenID Connect 的一致性同时又能返回组我们需要在 token 中添加自定义的命名空间声明。这可以通过 Auth0 规则来实现。
要在 Auth0 Portal 中创建规则我们需要导航至 Rules点击“Create Rule”并从模板中选择一个 empty rule。 粘贴如下的代码并将规则命名为“Add Group Claim”。 function (user, context, callback) { context.accessToken[https://sa.io/group] user.groups[0]; return callback(null, user, context);
}
注意 这个规则会获取 Authorization Extension 中定义的第一个用户组并将其添加到 access token 中作为自定义命名空间的声明。
回到 Rules 页面确保我们按照如下顺序有了两个角色 auth0-authorization-extension Add Group Claim
这里的顺序是非常重要的因为组的字段会由auth0-authorization-extension异步获取然后由第二个规则将其添加为命名空间声明这将会形成如下的 access token
{
https://sa.io/group: Moderators,
iss: https://bookinfo.eu.auth0.com/,
sub: google-oauth2|196405271625531691872
// [shortened for brevity]
}
现在我们必须配置 Envoy 代理从而能够在返回的 access token 中抽取https://sa.io/group声明的组。这是下一部分的主题我们马上来看一下。
3. 在 Istio 中配置授权
为了实现授权我们为 Istio 启用 RBAC。为了实现这一点要将如下的配置用到 Mesh 中
apiVersion: rbac.istio.io/v1alpha1
kind: RbacConfig
metadata:name: default
spec:mode: ON_WITH_INCLUSION # 仅为“Inclusion”字段中的服务和命名空间启用 RBACinclusion:services: # 包含如下服务。- sa-frontend.default.svc.cluster.local- sa-web-app.default.svc.cluster.local- sa-feedback.default.svc.cluster.local
执行如下命令应用该配置 $ kubectl apply -f resource-manifests\istio\security\enable-rbac.yaml
rbacconfig.rbac.istio.io default created
现在所有的服务都需要基于角色的访问控制Role-Based Access Control换句话说访问所有的服务都会遭到拒绝并且响应中会包含“RBAC: access denied”。我们下一节的主题就是启用对授权用户的访问。
4. 配置常规的用户访问
所有的用户应该都能访问 SA-Frontend 和 SA-WebApp 服务这是通过如下的 Istio 资源实现的
ServiceRole指定用户具有的权限
ServiceRoleBinding指定 ServiceRole 应用到哪些用户上。
对于常规用户我们允许访问指定的服务 apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:name: regular-user namespace: default
spec: rules:- services:- sa-frontend.default.svc.cluster.local- sa-web-app.default.svc.cluster.local paths: [*]methods: [*]
然后借助 regular-user-binding我们将该 ServiceRole 应用到页面的所有访问者上
apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:name: regular-user-bindingnamespace: default
spec:subjects:- user: *roleRef:kind: ServiceRolename: regular-user
这里所说的所有用户是不是意味着未认证的用户也能访问 SA WebApp 呢并非如此该策略依然会检查 JWT token 的合法性。
通过如下命令应用该配置
$ kubectl apply -f resource-manifests/istio/security/user-role.yaml
servicerole.rbac.istio.io/regular-user created
servicerolebinding.rbac.istio.io/regular-user-binding created
5. 配置 Moderator 用户访问
对于 Moderator我们想要启用对所有服务的访问 apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:name: mod-usernamespace: default
spec:rules:- services: [*]paths: [*]methods: [*]
但是我们只想要绑定 Access Token 中https://sa.io/group声明的值为 Moderators 的用户。
apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRoleBinding
metadata:name: mod-user-bindingnamespace: default
spec:subjects:- properties:request.auth.claims[https://sa.io/group]: ModeratorsroleRef:kind: ServiceRolename: mod-user
要应用该配置需要执行下述命令 $ kubectl apply -f resource-manifests/istio/security/mod-role.yaml
servicerole.rbac.istio.io/mod-user unchanged servicerolebinding.rbac.istio.io/mod-user-binding unchanged
Envoy 中会有缓存所以授权规则可能需要几分钟之后才能生效在此之后我们就可以校验 Users 和 Moderators 会有不同级别的访问权限。
坦白讲你见过像这样毫不费力就能构建可扩展的认证和授权吗还有比这更简单的过程吗反正我是没有见过接下来我们总结一下得到的结论。
结论
想象不到吧我们将其称为怪兽Beast-io因为它确实太强大了。借助它我们能够 实现服务的可观察性借助 Kiali我们能够回答哪个服务在运行、它们的表现如何以及它们是如何关联在一起的 借助 Prometheus 和 Grafana能够实现指标收集和可视化完全是开箱即用的 使用 Jaeger德语中猎人的意思实现请求跟踪 对网络流量的完整且细粒度控制能够实现金丝雀部署、A/B 测试和影子镜像 很容易实现重试、超时和断路器 极大地减少了为集群引入新服务的开销 为各种语言编写的微服务添加了认证和授权功能服务端代码无需任何修改。
有个这个 Beastio我们就能让团队真正地提供业务价值并将资源聚焦在领域问题上不用再担心服务的开销让服务真正做到“微型化”。
参考链接 istio 服务网格和Istio Kubernetes 中快速搭建 istio 通过 Istio 重新实现微服务