本系列文章将分为两部分,在此我们将探讨Istio服务网格中一部分,即可观察性工具集。这些工具包含了Jaeger,Kiali,Prometheus以及Grafana。为辅助我们此行探索,我们将在GCP上部署基于Go的微服务参考平台到GKE上去。
与区块链,无服务器,AI和机器学习,聊天机器人,网络安全和服务网格类似,可观察性现在是IT行业中的一大热门话题。根据维基百科记录,可观察性指的是如何从外部输出来推断衡量系统内部状态。日志,指标和跟踪通常被成为可观察性的三大支柱。这些是我们可观察到的系统的外部输出。
由Cindy Sridharan撰写,O’Reilly出版的 分布式系统可观察性 一书在 第四章节 中详细地介绍了“可观察性的三大支柱”。在继续下文之前,我强烈建议你阅读下这篇在线摘录。关于可观察性资讯的另一大来源是 honeycomb.io ,一个生产系统可观察性工具的开发商,由知名的行业思想领袖 Charity Majors 领导。这个站点上面发布了很多关于可观察性的文章,博文,白皮书和播客。
随着现代分布式系统变得越发复杂,想具备观察这些系统的能力需要同样在设计之初就考虑到适配如此复杂环境的现代化的工具。传统的日志记录和监控系统通常与当今的混合和多云,基于多种语言,事件驱动,基于容器和无服务器,可无限扩展的,临时计算的平台相竞争。
像 Istio服务网格 这类工具试图通过提供与几种同类最佳的开源遥测工具的原生集成方案来解决可观察性带来的挑战。Istio的集成包括 Jaeger 用于分布式跟踪, Kiali 用于分布式系统可视化, Prometheus 和 Grafana 用于度量指标收集,监控和告警。同时结合云平台原生的监控和日志服务,例如 Google Kubernetes Engine (GKE) 上的 Stackdriver ,我们可为现代化的分布式应用打造完整的可观测平台。
为了演示与最新版本的Istio服务网格集成的可观察性工具,我们将部署一个具备参考意义的,使用Go编写的微服务平台到GKE上面去。我开发了相关参考服务平台来演示涉及到的概念例如API管理,服务网格,可观察性,DevOps和 混沌工程 等。该平台由14个组件组成,包含了8个基于Go的微服务,以服务A-H来标记,一个Augular 7,基于 TypeScript 的前端,4个MongoDB数据库实例,一个RabbitMQ队列用于基于事件队列的通信。该平台以及所有代码都已开源。
该参考平台旨在生成基于HTTP的服务到服务,基于TCP的服务到数据库以及基于TCP的服务到队列再到服务(RabbitMQ)的IPC(进程间通信)。服务A调用服务B和C,服务B调用服务D和E,服务D生产消息到RabbitMQ队列供服务F来消费并写入到MongoDB,依此类推。当这个系统部署到运行Istio服务网格的Kubernetes集群中时,可以使用Istio的可观察性工具来观察这些分布式通信。
在该平台上,每个上游服务通过返回一个小的JSON信息负载(在源码中称为问候语)来响应下游服务的请求。
这些请求响应在调用链中聚合,进而产生给边缘服务的一系列服务响应,并返回给UI界面,最终展示在用户的浏览器中。响应聚合功能只是简单验证服务到服务间通信,Istio组件和遥测工具都能否正常工作。
所有的Go微服务都包含一个 /ping
和 /health
端点。 /health
端点用于配置Kubernetes的存活以及就绪探针。此外,边缘服务A使用响应头 access-control-allow-origin: *
用来配置 Cross-Origin Resource Sharing (CORS) 。CORS允许用于浏览器来调用位于与UI不同子域的 /ping
端点。相关服务A的源码可以在 此 查看。
在本次演示中,MongoDB数据库将被托管到GCP的外部,即 MongoDB Atlas ,它是一个基于云的MongoDB即服务平台。同样的,RabbitMQ队列服务将被托管到 CloudAMQP ,这是一个基于云的RabbitMQ即服务平台。我曾在之前的几篇文章中使用过这两种Saas供应商。使用外部服务将帮助我们了解Istio和它的可观察性工具是如果为位于Kubernetes集群中和外部系统的通信提供遥测的。
服务 F 消费来自RabbitMQ队列的消息,服务 D 将消息写入队列并写入到MongoDB中,这些服务都能通过对应链接打开直接查看源码。
所有本文涉及到的源码都可以在Github的两个项目中找到。基于Go的微服务的源码,所有的Kubernetes资源和部署脚本都位于 k8s-istio-observe-backend 这个项目仓库中。Angular UI前端项目源码位于 k8s-istio-observe-frontend 中。此次演示中你将不需要下载前端项目。
git clone --branch master --single-branch --depth 1 --no-tags / https://github.com/garystafford/k8s-istio-observe-backend.git
Kubernetes资源中引用到的相关Go服务和UI界面的容器镜像,都可以在 Docker Hub 上找到。Go服务镜像是使用官方的 Golang Alpine 来作为基础镜像,包含了Go 1.12.0版本。使用Alpine镜像来编译源码将能保证容器镜像尽可能地小并且包含更小的可攻击面。
文章接下来的部分,你需要将最新版本的 gcloud
的客户端工具(最小版本要求为239.0.0), Helm 和刚刚发布的 Istio 1.1.0版本 ,安装并配置在你的本地或者是构建机器上。
为了将该微服务平台部署到GKE,我们将按以下步骤进行。
MongoDB Atlas 是一个完全托管的MongoDB即服务,可在AWS,Azure和GCP上使用。Atlas是一款成熟的SaaS产品,提供高可用性,有保证的正常运行时间SLA,弹性可扩展性,跨区域复制,企业级安全性,LDAP集成,BI连接器等等。
MongoDB Atlas目前提供有四种 定价方案 ,包含免费版,基础版,高级版和企业版。这些方案范围从最小的共享内存和512MB存储的M0规模的MongoDB集群,到最高具有488GB内存和3TB存储的大型M400 MongoDB集群。
在本文中,我在GCP的 us-central1 地区创建了一个M2大小的MongoDB集群,并为该演示创建了一个数据库账户,该帐户将用于连接GKE上运行的八个基于Go的微服务的其中四个。
最初我使用了一个M0大小的集群,但发现计算资源不足以支撑来自这些Go语言的微服务。在此我建议至少使用M2大小乃至更大的集群。
CloudAMQP 在所有主流云供应商和应用程序平台上提供完全托管的RabbitMQ集群。RabbitMQ将支持一部分基于Go的微服务的解耦,最终一致的基于消息的架构。在本文中,我同样在GCP的 us-central1 区域创建了一个RabbitMQ集群,与我们的GKE集群和MongoDB Atlas集群相同,我选择了一个最低配置的RabbitMQ免费版本。CloudAMQP还提供强大的多节点RabbitMQ集群以供生产级别环境使用。
在GitHub项目中的Kubernetes资源文件和Bash部署脚本中你需要对应调整部分配置设置。
在 external-mesh-mongodb-atlas.yaml 文件中添加MongoDB Atlas主机地址。此文件允许从GKE上的四个微服务到外部MongoDB Atlas集群的出口流量。
apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: mongodb-atlas-external-mesh spec: hosts: - {{ your_host_goes_here }} ports: - name: mongo number: 27017 protocol: MONGO location: MESH_EXTERNAL resolution: NONE
在 external-mesh-cloudamqp.yaml 文件中添加CloudAMQP主机地址,此文件允许从两个微服务到CloudAMQP群集的出口流量。
apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: cloudamqp-external-mesh spec: hosts: - {{ your_host_goes_here }} ports: - name: rabbitmq number: 5672 protocol: TCP location: MESH_EXTERNAL resolution: NONE
通过Istio你可以有许多策略用于配置路由流量。此文中我使用了 example-api.com 这个域名,还有4组子域名。一组子域用于Angular UI,分别是 dev 命名空间( ui.dev.example-api.com )和 test 命名空间( ui.test.example-api.com )。另一组子域用于边缘API微服务,即被UI界面调用的服务A( api.dev.example-api.com 和 api.test.example-api.com )。流量将根据URL路由到特定的Kubernetes服务去。
Istio中所定义的 Gateway 对象描述了一个在网格边缘操作的负载均衡器,它负责接收传入或传出的HTTP/TCP连接。修改Istio ingress Gateway ,在该hosts部分中插入你自己的域名或子域,这些是端口80上允许进入网格的主机。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: demo-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - ui.dev.example-api.com - ui.test.example-api.com - api.dev.example-api.com - api.test.example-api.com
Istio中的 VirtualService 定义了一组主机域名被寻址时应用的流量路由规则。一个 VirtualService 与 Gateway 绑定来控制到达特定主机和端口的流量的转发。修改项目中的4个 VirtualServices ,将你的域名或子域添加进去。以下是在 istio-gateway.yaml 中一个VirtualService的例子。
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: angular-ui-dev spec: hosts: - ui.dev.example-api.com gateways: - demo-gateway http: - match: - uri: prefix: / route: - destination: port: number: 80 host: angular-ui.dev.svc.cluster.local
该项目中包含了一个Kubernetes Secret 对象, go-srv-demo.yaml 文件中指定了两个键值,一个用于MongoDB Atlas的连接,一个用于CloudAMQP,Kubernetes中的 Secret 的值需要经过 base64
加密。
apiVersion: v1 kind: Secret metadata: name: go-srv-config type: Opaque data: mongodb.conn: {{ your_base64_encoded_secret }} rabbitmq.conn: {{ your_base64_encoded_secret }}
在Linux和Mac上,你能直接使用 base64
程序来对此连接字符串进行编码。
> echo -n "mongodb+srv://username:password@atlas-cluster.gcp.mongodb.net/test?retryWrites=true" | base64 bW9uZ29kYitzcnY6Ly91c2VybmFtZTpwYXNzd29yZEBhdGxhcy1jbHVzdGVyLmdjcC5tb25nb2RiLm5ldC90ZXN0P3JldHJ5V3JpdGVzPXRydWU= > echo -n "amqp://username:password@rmq.cloudamqp.com/cluster" | base64 YW1xcDovL3VzZXJuYW1lOnBhc3N3b3JkQHJtcS5jbG91ZGFtcXAuY29tL2NsdXN0ZXI=
脚本 part3_create_gke_cluster.sh 包含了一系列环境变量。你至少需要更改所有脚本中的 PROJECT 变量为你的GCP项目名。
readonly PROJECT='{{ your_gcp_project_goes_here }}'
readonly CLUSTER='go-srv-demo-cluster'
readonly REGION='us-central1'
readonly MASTER_AUTH_NETS='72.231.208.0/24'
readonly GKE_VERSION='1.12.5-gke.5'
readonly MACHINE_TYPE='n1-standard-2'
脚本 part4_install_istio.sh 中包含了 ISTIO_HOME 这个变量,该值应与你本地的Istio 1.1.0的路径一致。下面是在我本地的Mac上的值
readonly ISTIO_HOME='/Applications/istio-1.1.0'
接下来使用脚本 part3_create_gke_cluster.sh 来部署GKE集群。这一步将创建一个区域,多可用区的3节点的GKE集群。该集群将会被部署到与MongoDB Atlas和CloudAMQP集群相同的区域,即us-central1(Iowa)。规划云资源所处的位置,对于Saas供应商和主流云供应商而言,能最大限度地减少网络I/O密集型应用程序的延迟。
等待GKE集群和相关的基础设施就绪后,我们将部署Istio。此文中,我推荐选择 使用Helm部署Istio 该种部署方式。要使用该种方式,请使用脚本 part4_install_istio.sh 。
该脚本使用本地Istio 1.1.0中 install/kubernetes/helm/istio
目录中的Helm Chart安装Istio。该安装脚本使用 --set
命令行标志覆写了 Istio Helm Chart 中的多个默认值。相关可用的配置项在 GitHub project 中有详细说明。这些选项启用了Istio的可观察性功能,包含Kiali,Grafana,Prometheus和Jaeger,这些我们将在本系列的第二篇中探索。
helm install ${ISTIO_HOME}/install/kubernetes/helm/istio-init / --name istio-init / --namespace istio-system helm install ${ISTIO_HOME}/install/kubernetes/helm/istio / --name istio / --namespace istio-system / --set prometheus.enabled=true / --set grafana.enabled=true / --set kiali.enabled=true / --set tracing.enabled=true kubectl apply --namespace istio-system / -f ./resources/secrets/kiali.yaml
如下图,我们可以看到与Istio相关联的负载已经运行在集群中,包含了可观察性工具集。
同样,我们能看到集群中相应的Istio相关的Service资源。
我们将使用DNS,而非使用IP地址来路由GKE集群及其应用程序的流量。如上所述,我使用了 example-api.com 这个域名和四个子域。一个子域用于 dev 和 test 命名空间的Angular UI服务,其他被用于API调用的边缘微服务A。流量基于URL路由到特定的 Kubernetes Service 资源。
部署GKE集群和Istio会触发创建一个 Google Load Balancer ,四个IP地址和所有必要的防火墙规则。与转发规则相关联的四个IP地址之一(如下所示)将与负载均衡器的前端相关联。
如下所示,我们能看到一个新的负载均衡器,包含了前端IP地址和三个GKE集群工作节点的后端VM池。每个节点都被分配了一个IP地址。
接着我使用了 Google Cloud DNS 创建了四个子域并将所有子域与负载均衡器前端的IP绑定。到这些地址的入口流量将通过Istio ingress Gateway 和四个Istio VirtualService 然后路由到适当的 Kubernetes Service 资源去。使用你选定的DNS管理工具来创建四个A类型的DNS记录。
接下来,将八个基于Go的微服务,Angular UI以及相关的Kubernetes和Istio资源部署到GKE集群。请使用脚本 part5a_deploy_resources.sh 来部署平台。如果任何操作失败并且在不破坏GKE集群或Istio的情况下,你想要删除现有资源并重新部署,则可以使用 part5b_delete_resources.sh 删除脚本。
部署脚本将所有资源部署到两个Kubernetes命名空间,分别是 dev 和 test 。这将使我们能够在使用可观察性工具时区分不同的命名空间。
下图展示刚刚部署的与Istio相关的资源,它们包括Istio Gateway ,四个Istio VirtualService 和两个Istio ServiceEntry 资源。
接着是在集群上运行的此平台的工作负载(Kubernetes Deployment 资源)。在这我们可以看到每个工作负载有两个Pod,共有18个Pod,在dev命名空间中运行。每个Pod都包含已部署的微服务或UI组件,以及Istio的 Envoy Proxy 的副本。
下图我们看到dev命名空间中创建了对应的Kubernetes Service 资源。
下图 test 命令空间中的资源与 dev 命名空间的类似。
我们想确保平台中的八个基于Go的微服务和Angular UI都能正常工作,相互通信,并与外部的MongoDB Atlas和CloudAMQP RabbitMQ集群进行通信。最简单的方式就是直接在浏览器查看Augular UI。
UI界面要求你输入服务A的主机名,即API的边缘服务。由于你无法使用我的子域,并且JavaScript代码是在你的Web浏览器本地运行,因此该选项允许你提供自己的主机域。这与你在 VirtualService 中为UI配置的域名相同。此域名将API调用路由到 dev 命名空间中运行的Service A服务的FQDN service-a.dev.svc.cluster.local ,或 test 命名空间 service-a.test.svc.cluster.local 。
你还可以使用性能测试工具对平台进行负载测试。很多问题在平台出现负载前都很难发现。最近我开始使用 hey
,一个现代化的负载生成工具,作为Apache Bench的替代品,它不像ab,hey支持HTTP/2端点,这是测试运行在包含了Istio组件的GKE集群上的平台工作所必需的。下面我直接在 Google Cloud Shell 使用 hey
工具,将模拟25个并发用户,为服务A生成总共1000个基于HTTP/2的GET请求。
如果由于某些原因导致UI无法显示或者是从UI发起的调用请求失败,并且假设所有Kubernetes和Istio资源都运行在GKE集群上,则最常见的解释通常就是以下的资源存在错误配置:
我建议通过使用cURL或Postman工具直接调用服务A(API的边缘服务)来开始排障。如果你能看到类似下图的JSON格式的响应,那则表明问题在于UI而非API。
接下来,确认为服务D,F,G和H创建了四个MongoDB数据库。另外确保新文档被正确写入数据库。
另外使用CloudAMQP RabbitMQ管理控制台确认已创建新的RabbitMQ队列。服务D产生信息,服务F从队列中取出并消费。
最后查看Stackdriver日志以查看是否存在任何明显错误。
在此系列的 第二部分 中,我们将详细探索每个可观察性工具,并了解它们如何帮助我们管理GKE集群以及运行在上面的相关平台。
由于集群只需几分钟即可完成创建和部署资源,如果需要清除集群请运行 part6_tear_down.sh 脚本。
以上所有仅代表我个人观点,与现任,前任雇主和客户无关。
【原文链接】: Kubernetes-based Microservice Observability with Istio Service Mesh: Part 1 翻译:冯旭松