基于Kubernetes的微服务可观测性与Istio服务网格(一)


本系列文章将分为两部分,在此我们将探讨Istio服务网格中一部分,即可观测性工具集。这些工具包含了Jaeger、Kiali、Prometheus以及Grafana。为辅助我们此行探索,我们将在GCP上部署基于Go的微服务参考平台到GKE上去。
1.png

什么是可观测性

与区块链、无服务器、AI和机器学习、聊天机器人、网络安全和服务网格类似,可观测性现在是IT行业中的一大热门话题。根据维基百科记录,可观测性指的是如何从外部输出来推断衡量系统内部状态。日志、指标和跟踪通常被成为可观测性的三大核心支柱。这些是我们可观察到的系统的外部输出。

由Cindy Sridharan撰写,O’Reilly出版的分布式系统可观测性一书在第四章节中详细地介绍了“可观测性的三大支柱”。在继续下文之前,我强烈建议你阅读下这篇在线摘录。关于可观测性资讯的另一大来源是honeycomb.io,一个生产系统可观测性工具的开发商,由知名的行业思想领袖Charity Majors领导。这个站点上面发布了很多关于可观测性的文章、博文、白皮书和博客。

随着现代分布式系统变得越发复杂,想具备观察这些系统的能力需要同样在设计之初就考虑到适配如此复杂环境的现代化的工具。传统的日志记录和监控系统通常与当今的混合和多云、基于多种语言、事件驱动、基于容器和无服务器、可无限扩展的、临时计算的平台相竞争。

Istio服务网格这类工具试图通过提供与几种同类最佳的开源遥测工具的原生集成方案来解决可观测性带来的挑战。Istio的集成包括Jaeger用于分布式跟踪,Kiali用于分布式系统可视化,PrometheusGrafana用于度量指标收集、监控和警报。同时结合云平台原生的监控和日志服务,例如谷歌云平台上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信息负载(在源码中称为问候语)来响应下游服务的请求。
2.png

这些请求响应在调用链中聚合,进而产生给边缘服务的一系列服务响应,并返回给UI界面,最终展示在用户的浏览器中。响应聚合功能只是简单验证服务到服务间通信,Istio组件和遥测工具都能否正常工作。
3.png

所有的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版本安装并配置在你的本地或者是构建机器上。
4.png

配置和安装

为了将该微服务平台部署到GKE,我们将按以下步骤进行:
  1. 创建MongoDB Atlas数据库集群
  2. 创建CloudAMQP RabbitMQ集群
  3. 根据自己的环境调整Kubernetes的资源文件和脚本
  4. 在GCP上创建GKE集群
  5. 使用Helm在GKE集群中部署Istio 1.1.0版本
  6. 为平台中需要暴露访问的资源创建DNS记录
  7. 部署所有Go微服务,Angular UI和与GKE相关联的资源
  8. 测试或为你的平台排障
  9. 在第二篇中查看观察的结果


MongoDB Atlas集群

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的微服务的其中四个。
5.png

最初我使用了一个M0大小的集群,但发现计算资源不足以支撑来自这些Go语言的微服务。在此我建议至少使用M2大小乃至更大的集群。

CloudAMQP RabbitMQ集群

CloudAMQP在所有主流云供应商和应用程序平台上提供完全托管的RabbitMQ集群。RabbitMQ将支持一部分基于Go的微服务的解耦,最终一致的基于消息的架构。在本文中,我同样在GCP的us-central1区域创建了一个RabbitMQ集群,与我们的GKE集群和MongoDB Atlas集群相同,我选择了一个最低配置的RabbitMQ免费版本。CloudAMQP还提供强大的多节点RabbitMQ集群以供生产级别环境使用。

修改配置

在GitHub项目中的Kubernetes资源文件和Bash部署脚本中你需要对应调整部分配置设置。

配置MongoDB Atlas的Istio ServiceEntry

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

配置CloudAMQP RabbitMQ的Istio ServiceEntry

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 Gateway和VirtualService

通过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.comapi.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定义了一组主机域名被寻址时应用的流量路由规则。一个VirtualServiceGateway绑定来控制到达特定主机和端口的流量的转发。修改项目中的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

该项目中包含了一个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=

Bash脚本变量

脚本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'

部署GKE集群

接下来使用脚本part3_create_gke_cluster.sh来部署GKE集群。这一步将创建一个区域,多可用区的3节点的GKE集群。该集群将会被部署到与MongoDB Atlas和CloudAMQP集群相同的区域,即us-central1(Iowa)。规划云资源所处的位置,对于Saas供应商和主流云供应商而言,能最大限度地减少网络I/O密集型应用程序的延迟。
6.png

使用Helm部署Istio

等待GKE集群和相关的基础设施就绪后,我们将部署Istio。此文中,我推荐选择使用Helm部署Istio的部署方式。要使用该种方式,请使用脚本part4_install_istio.sh
7.png

该脚本使用本地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相关联的负载已经运行在集群中,包含了可观测性工具集。
8.png

同样,我们能看到集群中相应的Istio相关的Service资源。
9.png

修改DNS记录

我们将使用DNS,而非使用IP地址来路由GKE集群及其应用程序的流量。如上所述,我使用了example-api.com这个域名和四个子域。一个子域用于devtest命名空间的Angular UI服务,其他被用于API调用的边缘微服务A。流量基于URL路由到特定的Kubernetes Service资源。

部署GKE集群和Istio会触发创建一个Google Load Balancer,四个IP地址和所有必要的防火墙规则。与转发规则相关联的四个IP地址之一(如下所示)将与负载均衡器的前端相关联。
10.png

如下所示,我们能看到一个新的负载均衡器,包含了前端IP地址和三个GKE集群工作节点的后端VM池。每个节点都被分配了一个IP地址。
11.png

接着我使用了Google Cloud DNS创建了四个子域并将所有子域与负载均衡器前端的IP绑定。到这些地址的入口流量将通过Istio ingress Gateway和四个Istio VirtualService然后路由到适当的Kubernetes Service资源去。使用你选定的DNS管理工具来创建四个A类型的DNS记录。
12.png

部署整个参考平台

接下来,将八个基于Go的微服务,Angular UI以及相关的Kubernetes和Istio资源部署到GKE集群。请使用脚本part5a_deploy_resources.sh来部署平台。如果任何操作失败并且在不破坏GKE集群或Istio的情况下,你想要删除现有资源并重新部署,则可以使用part5b_delete_resources.sh删除脚本。
14.png

部署脚本将所有资源部署到两个Kubernetes命名空间,分别是devtest。这将使我们能够在使用可观测性工具时区分不同的命名空间。

下图展示刚刚部署的与Istio相关的资源,它们包括Istio Gateway,四个Istio VirtualService和两个Istio ServiceEntry资源。
15.png

接着是在集群上运行此平台的工作负载(Kubernetes Deployment资源)。在这我们可以看到每个工作负载有两个Pod,共有18个Pod,在dev命名空间中运行。每个Pod都包含已部署的微服务或UI组件,以及Istio的Envoy Proxy的副本。
16.png

下图我们看到dev命名空间中创建了对应的Kubernetes Service资源。
17.png

下图test命令空间中的资源与dev命名空间的类似。
18.png

测试该平台

我们想确保平台中的八个基于Go的微服务和Angular UI都能正常工作,相互通信,并与外部的MongoDB Atlas和CloudAMQP RabbitMQ集群进行通信。最简单的方式就是直接在浏览器查看Augular UI。
19.png

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
20.png

你还可以使用性能测试工具对平台进行负载测试。很多问题在平台出现负载前都很难发现。最近我开始使用hey,一个现代化的负载生成工具,作为Apache Bench的替代品,它不像ab,hey支持HTTP/2端点,这是测试运行在包含了Istio组件的GKE集群上的平台工作所必需的。下面我直接在Google Cloud Shell使用hey工具,将模拟25个并发用户,为服务A生成总共1000个基于HTTP/2的GET请求。
21.png

故障排除

如果由于某些原因导致UI无法显示或者是从UI发起的调用请求失败,并且假设所有Kubernetes和Istio资源都运行在GKE集群上,则最常见的解释通常就是以下的资源存在错误配置:
  1. 四个DNS记录中存在错误,它们不是解析到负载均衡器的前端IP地址上
  2. 没有为四个Istio的VirtualService资源配置正确的子域名
  3. 基于Go的微服务无法访问外部MongoDB Atlas和CloudAMQP RabbiqMQ集群。可能是Kuberetes Secret资源结构不对或者是两个ServiceEntry中包含了那些到外部集群的错误的主机信息


我建议通过使用cURL或Postman工具直接调用服务A(API的边缘服务)来开始排障。如果你能看到类似下图的JSON格式的响应,那则表明问题在于UI而非API。
22.png

接下来,确认为服务D、F、G和H创建了四个MongoDB数据库。另外确保新文档被正确写入数据库。
23.png

另外使用CloudAMQP RabbitMQ管理控制台确认已创建新的RabbitMQ队列。服务D产生信息,服务F从队列中取出并消费。
24.png

最后查看Stackdriver日志以查看是否存在任何明显错误。
25.png

未完待续

在此系列的第二部分中,我们将详细探索每个可观测性工具,并了解它们如何帮助我们管理GKE集群以及运行在上面的相关平台。
26.png

由于集群只需几分钟即可完成创建和部署资源,如果需要清除集群请运行part6_tear_down.sh脚本。
27.png

以上所有仅代表我个人观点,与现任、前任雇主和客户无关。

原文链接:Kubernetes-based Microservice Observability with Istio Service Mesh: Part 1(翻译:冯旭松)

0 个评论

要回复文章请先登录注册