Isito 入门(八):金丝雀发布

2023年7月9日 1094点热度 1人点赞 0条评论
内容纲要

本教程已加入 Istio 系列:https://istio.whuanle.cn

可观测性

Istio 集成了 Jaeger、Zipkin 和 Skywalking 等链路追踪应用,能够有效地捕获服务网格的结构,展示网络拓扑结构,并分析网格的健康状况。

这一切都得益于 Envoy 代理的实现。由于所有进出流量都需要经过 Envoy 代理,Envoy 可以捕获这些流量记录,并将其推送到相应的链路追踪系统中。这样一来,可以链路追踪系统轻松地监控和分析服务网格内的流量情况。

另外 Istio 还支持 Prometheus、 Grafana 收集指标数据。

下面我们将使用官方的模板部署 Kiali 、 还有 Jaeger,然后通过 Kiali 统一查看集群的指标信息。

Kiali 界面示例:

image-20230523113334090

拉取 Istio 官方的仓库:

git clone https://github.com/istio/istio.git

samples/addons/ 目录中有以下目录或文件:

samples/addons/
├── extras
│   ├── prometheus-operator.yaml
│   ├── prometheus_vm_tls.yaml
│   ├── prometheus_vm.yaml
│   ├── skywalking.yaml
│   └── zipkin.yaml
├── grafana.yaml
├── jaeger.yaml
├── kiali.yaml
├── prometheus.yaml
└── README.md

我们启用 grafana.yamljaeger.yamlkiali.yamlprometheus.yaml 四个文件。

 kubectl apply -f samples/addons

这些服务默认安装在 istio-system 命名空间下,因此不需要自行设置。

Istio 默认使用 Jaeger 做链路追踪,我们也可以使用 Skywalking 来做追踪。extras 目录中的配置我们可以自行部署。

执行命令查看其 Service 对应的 IP 和端口:

kubectl get svc  -n istio-system

1683096637998

现在,我们有两种方式让 kiali 在外部访问,一种是修改 Service 配置,将其访问类型修改为 NodePort,另一种是使用 istio-ingressgateway 配置流量入口。

第二种方式比较麻烦,但是为了验证我们的学习成果,我们不妨使用 Gateway 的方式暴露服务。

通过 Gateway 访问 Kiali

首先,创建一个 Gateway 。

kiali_gateway.yaml

apiVersion: networking.istio.io/v1alpha3  
kind: Gateway  
metadata:  
  name: kiali-gateway  
spec:  
  selector:  
    istio: ingressgateway  
  servers:  
  - port:  
      number: 15029  
      name: http-kiali  
      protocol: HTTP  
    hosts:  
    - "*"  
kubectl -n istio-system apply -f kiali_gateway.yaml  

接下来,创建一个 VirtualService 资源,将 Gateway 路由到 Kiali 服务.

kiali_vs.yaml

apiVersion: networking.istio.io/v1alpha3  
kind: VirtualService  
metadata:  
  name: kiali  
spec:  
  hosts:  
  - "*"  
  gateways:  
  - kiali-gateway  
  http:  
  - match:  
    - uri:  
        prefix: /kiali  
    route:  
    - destination:  
        host: kiali.istio-system.svc.cluster.local  
        port:  
          number: 20001  
kubectl -n istio-system apply -f kiali_vs.yaml  

然后修改 istio-ingressgateway,新增加一个配置为 kiali 暴露服务。

kubectl edit svc istio-ingressgateway -n istio-system  
  - name: kiali
    nodePort: 32667
    port: 15029
    protocol: TCP
    targetPort: 15029

然后访问:http://192.168.3.150:32667/kiali

image-20230503151439751

查看链路追踪数据

现在我们在 Shell 执行命令轮询一段时间前面部署的微服务,以便给集群创造访问流量。

for i in <code>seq 1 1000</code>; do curl -s -o /dev/null http://192.168.3.150:30666/productpage; done

image-20230507200031563

因为默认链路追踪采样率是 1%,所以可以将请求次数设置大一些。

最终会得到一张类似的图片。

image-20230507201431282

image-20230507202344997

image-20230510162717275

Kiali 的 Graph 数据主要来自两个来源:Prometheus 和 Istio 本身的遥测数据。

Prometheus:Prometheus 是一个开源监控和警报工具,它用于收集和存储 Istio 服务网格中的指标数据。Istio 使用 Envoy 代理收集遥测数据,这些数据随后被 Prometheus 抓取和存储。Kiali 使用这些 Prometheus 数据来生成服务之间的流量、错误率、延迟等指标。

Istio 遥测数据:Istio 服务网格生成的遥测数据包括请求、响应、延迟以及 Envoy 代理的其他性能指标。这些数据由 Istio 组件(例如 Mixer 和 Pilot)以及 Envoy 代理本身生成。Kiali 从这些遥测数据中获取服务拓扑信息,以创建服务之间的依赖关系图。

Kiali 将这两个数据源的信息整合在一起,生成 Graph,它展示了服务网格的拓扑结构、服务之间的流量以及其他性能指标。这有助于用户更好地理解服务之间的依赖关系,发现潜在的性能问题,并优化服务网格配置。

可能失败的原因

如果你的 Kiali 一直显示 Empty Graph。请关注以下几种可能的情况:

  • 集群版本低于 1.23 ,需要升级 Kubernetes 集群。
  • 安装了 Kubesphere,说多了都是泪,Kubesphere 太重了,笔者花了一晚上时间重新安装集群。
  • 访问的地址不正确,没有配置对 /productpage 的访问地址,请求流量没有打入集群。
  • Pod 没有被注入 istio-proxy。

你可以在 Kiali 的 Workloads 查看每个负载的 Pod 信息,正常情况应当如下所示:

1683461900117

修复 Kiali Grafana 问题

点击右上角的消息,可能会提示配置不正确,因为 kiali 需要从 Grafana 拉取数据。

image-20230503153541163

编辑 configmap 。

 kubectl edit configmap kiali -n istio-system

在里面添加如下两行内容。

  grafana:  \n    enabled: true  \n    url: \"http://grafana.istio-system.svc.cluster.local:3000\"
    \ \n    in_cluster_url: \"http://grafana.istio-system.svc.cluster.local:3000\"\n

1683210817222

如果使用的是可视化工具,添加就简单了。

      grafana:  
        enabled: true  
        url: "http://grafana.istio-system.svc.cluster.local:3000"  
        in_cluster_url: "http://grafana.istio-system>.svc.cluster.local:3000"

image-20230504223422031

然后使用 kubectl describe configmap kiali -n istio-system 查看配置是否正确。

5,出入口网关

Istio 可以管理集群的出入口流量,当客户端访问集群内的应用时, Istio 可以将经过 istio-ingressgateway 的流量实现负载均衡和熔断等一系列功能。

可是,如果集群内的一个应用要访问 google.com ,那么我们可以给内部所有请求了 google.com 的流量设置负载均衡吗?答案是可以,Istio 提供了 istio-egressgateway 实现这种功能。因为 Pod 中的容器要访问网络时,会被 Envoy 拦截,Envoy 可以很容易地分析这些请求,然后通过一系列手段影响着请求的行为。

在本章中,将会简单说一下 istio-ingressgateway 和 istio-egressgateway。

istio-ingressgateway

入口网关指的是从外部经过 istio-ingressgateway 流入集群的流量,需要创建 Gateway 绑定流量。

关于 istio-ingressgateway 经过前面几章的学习,大家应该不陌生了。

istio-ingressgateway 由 Pod 和 Service 组成。 istio-ingressgateway 本身就是一个网关应用,你可以把它当作 Nginx、Apisix、Kong ,你可以从各种各种网关应用中找到与 istio-ingressgateway 类似的概念。

image-20230526194223740

image-20230526194154234

作为一个应用,它需要对外开放一些端口,只有当流量经过这些端口时, istio-ingressgateway 才会起作用。为了在 Kubernetes 中暴露端口, istio-ingressgateway 还有一个 Service 对象。

image-20230526194139225

有了 istio-ingressgateway 之后,我们可以通过 Istio Gateway 监控一些域名或IP,然后暴露集群内部的服务 。

Gateway 的概念跟 Nginx 有很多相似之处。

比如从配置上看, Gateway 跟 Nginx 如果要监控某个入口流量,它们的配置如下:

Nginx:

server {
    listen      80;
    server_name example.org www.example.org;
    #...
}

Gateway:

  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - example.org
    - www.example.org

这些配置指定了 Gateway 和 Nginx 只监控哪些流量。

紧接着,监控到指定入口的流量之后,需要将流量转发到集群内的应用中。

Nginx 可以直接在同一个配置文件里面设置:

server {
    listen      80;
    server_name example.org www.example.org;
    #...
}

location /some/path/ {
    proxy_pass http:/bookinfo:9080/;
}

而 Gateway 需要使用 VirtualService 指定流量转发到哪里,并且 VirtualService 还可以进一步筛选入口地址。

spec:
  hosts:
  - "www.example.org"
  gateways:
  # 绑定 Gateway
  - mygateway
  http:
    route:
    - destination:
        host: bookinfo
        port:
          number: 9080

所以总结起来,Istio 的做法是 Gateway 监控入口流量,通过 VirtualService 设置流量进入的策略,并指向 Service。而 DestinationRule 则定义了流量流向 Pod 的策略

部署服务

下面我们将使用 httpbin 服务作为示例,如何一步步配置在外部访问 httpbin 服务。

首先部署一个 httpbin 服务,这个 httpbin 服务很简单,包含了 Service 和 Deployment 。

httpbin.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: httpbin
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
  labels:
    app: httpbin
    service: httpbin
spec:
  ports:
  - name: http
    port: 8000
    targetPort: 80
  selector:
    app: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpbin
      version: v1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      serviceAccountName: httpbin
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        ports:
        - containerPort: 80
kubectl -n bookinfo apply -f httpbin.yaml

配置 Gateway

然后创建一个 Gateway ,指定监听哪些入口流量。

httpbin_gw.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use Istio default gateway implementation
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "httpbin.s1.whuanle.cn"
    - "*"

这一步为了大家能够通过域名更加直观地了解 Gateway,大家可以修改 httpbin.s1.whuanle.cn 替换为自己的域名。

然后在自己的电脑中打开 C:\Windows\System32\drivers\etc\hosts 增加一条记录 ,将 IP 指向自己的服务器。

image-20230515193213923

kubectl -n bookinfo apply -f httpbin_gw.yaml

现在,我们已经让 istio-ingressgateway 帮我们关注 httpbin.s1.whuanle.cn 这个地址,如果有人访问了 httpbin.s1.whuanle.cn,那么这个流量将会流入到 httpbin-gateway。

image-20230515190518804

接下来我们将要为 Gateway 配置服务地址,并配置外部允许访问的地址后缀。

配置 VistualService:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*"
  gateways:
  - httpbin-gateway
  http:
  - match: 
    - uri:
        prefix: /status
    - uri:
        prefix: /delay 
    route:
    - destination:
        port:
          number: 8000
        host: httpbin

当 Gateway 和 VirtualService 端口只有一个时,不需要配置端口绑定。

kubectl -n bookinfo apply -f httpbin_vs.yaml

找到 istio-ingressgateway 对外暴露的端口。

kubectl get svc istio-ingressgateway  -n istio-system

1683287785674

httpbin 是一个 http 测试程序,我们可以通过使用 /status/{状态码} 获取对应的 http 请求状态。

例如:

image-20230505200437890

image-20230505200444999

image-20230515193314909image-20230515193301641

如果我们不希望这个服务被外界访问到,我们可以先把 /status 删除。

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /delay
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
EOF

此时你将无法访问 status 路径。但是我们还可以访问 /delay 路径。

httpbin 的 /delay 路径用于测试延迟 http 请求响应使用,/delay/{秒数} 可以指定服务器在多久之后才会返回响应。

例如 http://192.168.3.150:32309/delay/5 将在 5 秒后响应。

httpbin 还有很多路由接口,我们可以通过 VirtualService 配置放通哪些路径。

image-20230505201156220

如果需要全部放通,可以使用:

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
  - "*"
  gateways:
  - httpbin-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        port:
          number: 8000
        host: httpbin
        subset: v1
EOF

子版本

第四章中进行版本路由实验时使用到,可以将流量导入到不同的版本之中。

kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  subsets:
  - name: v1
    labels:
      version: v1
EOF

首先是使用 DestinationRule 指向一个 Service:

  host: httpbin

当然,我们也可以写成

  host: httpbin.bookinfo.svc.cluster.local

通过 host 可以识别到对应的 Kubernetes Service,然后从 Service 对应的 Endpoints 中获得所有 Pod 列表。

image-20230515194105645

1684151025350

通过 Endpoints 获得所有 Pod 之后,查看每个 Pod 的描述信息。当有一个请求到达时,根据 DestinationRule 中的标签选择器,选择合适的 Pod 进行访问。

  - name: v1
    labels:
      version: v1

1684151265603

istio-egressgateway

istio-egressgateway 也是 Istio 中的一种组件,需要自行安装。安装 istio-egressgateway 命令:

helm install istio-egressgateway istio/gateway -n istio-system

在集群中,如果 A 应用访问的地址属于集群中的应用,那么 Istio 可以给这些请求注入各种行为,实现负载均衡和熔断等。

可是,如果集群内部要访问外部的一个服务时,需要配置访问地址,如 aaa.com,我们应该如何实现负载均衡和熔断这些功能呢?

image-20230515195151940

Istio ServiceEntry 是一种资源,允许将外部服务(即不在 Istio 服务网格中的服务)纳入Istio服务网格。通过将外部服务添加到网格,可以使用 Istio 的流量管理和策略功能来控制与这些外部服务的交互。

以下是一个ServiceEntry示例,将外部HTTP服务 www.google.com添加到Istio服务网格:

apiVersion: networking.istio.io/v1alpha3  
kind: ServiceEntry  
metadata:  
  name: google
spec:  
  hosts:  
  - www.google.com  
  addresses:  
  - 192.168.1.1  
  ports:  
  - number: 80  
    name: http  
    protocol: HTTP  
  location: MESH_EXTERNAL  
  resolution: DNS  
  endpoints:  
  - address: "www.google.com"  
    ports:  
      http: 80  
    locality: "us-west1/zone1"  
  exportTo:  
  - "*"  

在此示例中,我们创建了一个名为httpbin-ext的ServiceEntry资源。指定的主机为httpbin.org,端口号为80,协议为HTTP。此外,我们将resolution设置为DNS,将location设置为MESH_EXTERNAL,表示该服务位于网格之外。

要将此ServiceEntry应用到集群,请将其保存到一个YAML文件(例如:httpbin-ext.yaml),然后运行以下命令:

kubectl apply -f httpbin-ext.yaml  

现在,Istio 服务网格中的服务访问 www.google.com 时仍受Istio策略的控制。例如,可以为此 ServiceEntry 创建 VirtualService 以应用流量管理规则,或者为其创建 DestinationRule 以配置负载均衡和连接池设置。

spec: 包含ServiceEntry的具体配置的对象。

  • hosts: 一个包含要导入的外部服务的主机名(FQDN)的列表。例如:["httpbin.org"]
  • addresses: (可选)与外部服务关联的虚拟IP地址的列表。例如:["192.168.1.1"]
  • ports: 一个描述外部服务使用的端口的列表。每个端口都有以下属性:
    • number: 端口号,例如:80。
    • name: 端口的名称,例如:http
    • protocol: 使用的协议,例如:HTTPTCPHTTPS等。
  • location: 服务的位置。可以是MESH_EXTERNAL(表示服务在网格外部)或MESH_INTERNAL(表示服务在网格内部,但不属于任何已知服务)。
  • resolution: 用于确定服务实例地址的解析方法。可以是NONE(默认值,表示不解析地址),STATIC(表示使用addresses字段中的IP地址),DNS(表示使用DNS解析主机名)或MESH_EXTERNAL
  • endpoints: (可选)外部服务的端点列表。每个端点都有以下属性:
    • address: 端点的IP地址或主机名。
    • ports: 一个包含端口名称和端口号的映射,例如:"http": 8080
    • labels: (可选)应用于端点的标签。
    • locality: (可选)端点的地理位置,例如:us-west1/zone1
  • exportTo: (可选)一个包含命名空间名称的列表,指定可以访问此ServiceEntry的命名空间。可以使用星号(*)表示所有命名空间。默认值为*
  • subjectAltNames: (可选)用于验证服务器证书主题替代名(SANs)的列表。

读者可以从官方文档中了解更多:

https://istio.io/latest/zh/docs/tasks/traffic-management/egress/egress-control/

6,金丝雀发布

项目总是处于不断变化之中,每次发布新的版本时,都考验了团队的运维能力。

e7f1650a0d55dfa0cb2a48ed90c615f6_opswork

【图源:互联网】

新版本上线之前,经历过开发和测试人员的验证,也经过产品经理的验收。可是当要上线到生产环境时,谁也保证不了上线一定就能跑起来。所以往往需要在上线时保持新版本和旧版本同时在用,测试人员或内测用户可以访问新版本,其他人继续使用旧版本。再有就是上线时新旧系统能够丝滑切换,用户完全感知不到这种变化。

并且新版本上线时,不应该影响旧版本的运行,要求实现不停止更新。但是除了应用本身,还涉及到新旧版本共有同一个数据库,新旧版本应用对应的数据库表结构不一样。

新旧版本切换带来的问题很多,但是,在本系列教程中我们只考虑外部访问效果即可。

很多项目的版本更新做得并不好。很多初级阶段的 Web 服务团队,每次更新都需要中断当前的应用,后端服务有一段时间内不可用。如果新版本有问题,那么还需要重新发布旧版本。如果只是内部服务,问题不大,可是如果系统是给客户使用的,那么很容易让客户给出一个低分的评价。

5de795c65e3f932d6519aaa8a875b4e7_20200613221523788

【图源:互联网】

Kubernetes 中虽然有滚动升级,能够逐渐使用新版本的 Pod 替换旧版本的 Pod,两个版本共存,但是并不能做到流量自由切分,一个流量进入时,依然会在新旧版本的 Pod 中轮询选择。但是我们还是可以调整 v1 和 v2 的 Pod 数量,实现流量按照比例划分,比如 v1 的 Pod 数量有 40 个,v2 的 Pod 的数量有 60 个,那么按照比例,会有 60% 的流量进入到 v2 中。不过这样并没有什么鸟用。

Kubernetes 滚动升级、伸缩参考资料:https://k8s.whuanle.cn/3.pod/6.scale.html

Istio 中虽然没有名为 金丝雀发布的功能,但是按照之前我们所学到的 Istio 的 VirtualService 、DestinationRule 就可以实现根据请求的 header 等,将流量转发到不同的子版本中,我们使用这些操作方法即可实现金丝雀发布。

金丝雀发布

尴尬,在学习 Istio 之前,我对蓝绿发布、灰度发布、金丝雀发布的概念并不清晰,很多文章都是将它们分成三类发布方式。实际上蓝绿发布和金丝雀发布都是灰度发布的一种。

灰度发布是两个版本共存,将一小部分流量切到新版本中,当新版本经过一段时间验证后,如果没有问题,再将流量逐步切换到新版本中。

所以,可以把蓝绿发布、A/B 测试、金丝雀发布都划分为灰度发布。

当然,它们有一些差别,每个人的划分方法也有差别,有的说法是金丝雀发布才是灰度发布。

蓝绿发布

蓝绿发布的方式是使用另一套资源部署新版本,然后将旧版本的流量都切换到新版本中去,缺点是需要消耗两套资源,而且蓝绿发布是全量切换,如果新版本出现问题则会影响到所有用户。

A/B 测试

A/B 测试是同时部署两个版本对等的版本来接收流量,但它不是指新旧版本。比如,产品经理有一个好主意,做新时代的农场区块链,想知道大家喜欢养猪还是养鸡。于是发布了养猪农场和养鸡农场两个对等的应用,然后邀请不同的用户进行内测,喜欢养猪的用户会被导向养猪农场应用。然后收集用户的意见和各种指标数据,分析用户喜欢养什么动物,最后决定上线什么版本。

b189145e36cb6026213e950a3d5d88b7_9k=

金丝雀发布

先上线一个新版本,然后根据规则将一小部分用户导向到新应用,观察新版本在生产环境的表现,如果达到预期,则逐步将流量切换到新版本中。

按照流量比例划分

在第四章中,我们使用 DestinationRule 为 reviews 定义了三个版本,但是每个版本只有一个 Pod。

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

image-20230515200651788

现在使用命令将 v1 版本增加到 10 个 pod。

kubectl scale deployment reviews-v1 -n bookinfo --replicas=10

1684152463851

在原配置不变的情况下,我们来部署 VirtualService,将流量的 90% 打入到包含 10个 Pod 的 v1 版本。因为 v2 版本只有10% 的流量,所以只需要 1 个 Pod 也可以支撑了叭。

kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:  
  - route:  
    - destination:  
        host: reviews
        subset: v1  
      weight: 90  
    - destination:  
        host: reviews
        subset: v2  
      weight: 10  
EOF

命令之前完毕之后,不断刷新 productpage页面,会发现大部分情况下不会显示评星,少部分情况会出现黑色的星星。

image-20230515202826614

按照 Header 划分

当然,也可以使用 header 的方式,将流量导入到不同的版本中。比如只有测试人员的流量才会打入到新版本中。

  http:
  - match:
    - headers:
        end-user:
          exact: jason
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v1

当我们检查 v2 版本没有问题之后,我们就将 v2 版本扩容。

kubectl scale deployment reviews-v2 -n bookinfo --replicas=10

与此同时,修改 VirtualService,将全部流量路由到 v2 中。

kubectl -n bookinfo apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:  
  - route:  
    - destination:  
        host: reviews
        subset: v1  
      weight: 0  
    - destination:  
        host: reviews
        subset: v2  
      weight: 100  
EOF

当一段时间后,如果一切正常,将 v1 版本的 Pod 数量降低为 0。

kubectl scale deployment reviews-v1 -n bookinfo --replicas=0

1684154086502

但是不要删除 v1 版本,如果新版本出现问题,我们只需要调整 v1版本的 Pod 数量,然后修改 VirtualService ,将理论导入到 v1 即可。

金丝雀发布其实很简单,除了 Istio 之外,我们通过 Nginx、Apisix、Kong 等网关都可以轻松实现。

痴者工良

高级程序员劝退师

文章评论