TT Lab
开始
学习 学习路径 课程

Istio 进阶 — 为什么会那样流

把子集搭建成集群并按名称追踪

在 TT Lab 中继续学习

目标

从 DestinationRule 按规则推导出集群名称,并按这些名称原样配置 Envoy,亲自确认按子集路由、统计名称以及不存在的子集产生的 503。

为什么重要

Istio 故障排查有一半是在读 proxy-config clusters 和统计数据。集群名称是按规则生成的,所以了解规则,只看名称就能认出服务、端口和子集,并在几秒内区分“子集不存在”“端点为空”“统计名称变了”。

步骤

  1. 在 /root/ist2-name/dr.yaml 中编写 DestinationRule——名称 reviews,命名空间 default,host 为 reviews.default.svc.cluster.local,子集 v1(标签 version: v1)和 v2(标签 version: v2)。把 istioctl validate -f /root/ist2-name/dr.yaml 的输出和退出码写入 /root/ist2-name/01-validate.txt(最后一行 rc=0)。
  2. 服务 reviews 使用端口 9080。根据 /root/ist2-name/dr.yaml,把 sidecar 们将会拥有的四个集群名称,每行一个写入 /root/ist2-name/02-names.txt——其他 Pod 发往 reviews 时使用的三个(没有子集的一个,加上每个子集一个),以及 reviews 这个 Pod 自身把入站请求交给应用时使用的一个。
  3. 在 /root/ist2-name/name.yaml 中编写 Envoy 配置——管理端口 9983,监听器 127.0.0.1:10083(HTTP),route_config 名称 9080,把所有路径发往 outbound|9080|v1|reviews.default.svc.cluster.local。集群是三个出站集群——outbound|9080||reviews.default.svc.cluster.local(端点为 127.0.0.1:8103 和 127.0.0.1:8104 两个)、outbound|9080|v1|reviews.default.svc.cluster.local(只有 8103)、outbound|9080|v2|reviews.default.svc.cluster.local(只有 8104)。在 8103、8104 上以 ok 启动两个上游并启动 Envoy 之后,把 curl localhost:10083/ 的结果以 status= 和 body= 两行写入 /root/ist2-name/03-v1.txt。
  4. 把 /root/ist2-name/name.yaml 复制为 /root/ist2-name/split.yaml,并把路由改为加权路由——outbound|9080|v1|reviews.default.svc.cluster.local 为 75,outbound|9080|v2|reviews.default.svc.cluster.local 为 25。用 split.yaml 并加上 --concurrency 1 重新启动 Envoy,然后恰好发送 40 次 curl localhost:10083/,根据响应正文统计去往了哪个子集,以 v1=、v2=、total= 三行写入 /root/ist2-name/04-split.txt。
  5. 在 /root/ist2-name/split.yaml 的 outbound|9080|v2|reviews.default.svc.cluster.local 集群中添加 alt_stat_name: outbound_9080_v2_reviews(其余保持不变)并重新启动,然后发送 20 次以上请求。从管理端口的 /stats 中把两行原样抄下来,保存到 /root/ist2-name/05-stats.txt——v1 集群的 upstream_rq_200 行,以及 v2 集群的 upstream_rq_200 行(现在会以改变后的名称出现)。
  6. 把 /root/ist2-name/split.yaml 复制为 /root/ist2-name/missing.yaml,并修改两处——在 route_config 中添加 validate_clusters: false,并在原有路由前面添加一条把路径前缀 /v3 发往 outbound|9080|v3|reviews.default.svc.cluster.local 的路由(不创建这个集群)。然后再创建一份把 missing.yaml 中的 validate_clusters: false 那一行去掉的副本 /root/ist2-name/missing-strict.yaml。用 missing.yaml 启动后,发送一次 curl localhost:10083/v3,并在 /root/ist2-name/06-missing.txt 中写入三行——status=(HTTP 状态码)、no_cluster=(统计 http.outbound_0.0.0.0_9080.no_cluster 的值)、strict_rc=(对 missing-strict.yaml 执行 envoy --mode validate 的退出码)。
  7. 从正在运行的 Envoy 的 localhost:9983/clusters 中,统计 reviews 的三个集群各有几个端点,以 <클러스터 이름> <개수>(占位符依次为集群名称与个数)的形式写三行到 /root/ist2-name/07-endpoints.txt。
  8. 在 /root/ist2-name/08-report.md 中写入 default_cluster=、inbound_cluster=、v2_stat_name=、missing_subset_code= 四行(依次为没有子集的出站集群名称、入站集群名称、第 5 步中改变后的 v2 统计名称、第 6 步中收到的 HTTP 状态码),并在下面至少写四行以 - 开头的说明。

参考

编写带有两个子集的 DestinationRule

在 /root/ist2-name/dr.yaml 中编写 DestinationRule——名称 reviews,命名空间 default,host 为 reviews.default.svc.cluster.local,子集 v1(标签 version: v1)和 v2(标签 version: v2)。把 istioctl validate -f /root/ist2-name/dr.yaml 的输出和退出码写入 /root/ist2-name/01-validate.txt(最后一行 rc=0)。

子集是“同一个服务的 Pod 中带有这个标签的那些”这样一个有名字的选择器。它本身不会改变流量,只有当 VirtualService 用 subset: v1 指向它时才会被使用。host 写短名称也可以,但 Istio 最终会把它解析成 FQDN 来使用——这里一开始就写成 FQDN,下一步的名称推导会更容易。istioctl validate 无需集群,仅凭文件检查 schema。

按规则推导出四个集群名称

服务 reviews 使用端口 9080。根据 /root/ist2-name/dr.yaml,把 sidecar 们将会拥有的四个集群名称,每行一个写入 /root/ist2-name/02-names.txt——其他 Pod 发往 reviews 时使用的三个(没有子集的一个,加上每个子集一个),以及 reviews 这个 Pod 自身把入站请求交给应用时使用的一个。

名称由 방향|포트|서브셋|호스트(占位符依次为方向、端口、子集、主机)四个字段组成。方向是 outbound 或 inbound,端口是服务端口,没有子集时,把这个字段留空就成了 ||。入站一侧是发往自己 Pod 的应用,既不需要子集,也不需要主机,所以后两个字段都为空。主机字段是把 DestinationRule 的 host 写成 FQDN。用 yq 提取 .spec.subsets[].name 再用循环输出,就不会有错别字。

按这些名称原样搭建 Envoy,并发往 v1

在 /root/ist2-name/name.yaml 中编写 Envoy 配置——管理端口 9983,监听器 127.0.0.1:10083(HTTP),route_config 名称 9080,把所有路径发往 outbound|9080|v1|reviews.default.svc.cluster.local。集群是三个出站集群——outbound|9080||reviews.default.svc.cluster.local(端点为 127.0.0.1:8103 和 127.0.0.1:8104 两个)、outbound|9080|v1|reviews.default.svc.cluster.local(只有 8103)、outbound|9080|v2|reviews.default.svc.cluster.local(只有 8104)。在 8103、8104 上以 ok 启动两个上游并启动 Envoy 之后,把 curl localhost:10083/ 的结果以 status= 和 body= 两行写入 /root/ist2-name/03-v1.txt。

istiod 总是为一个服务创建“没有子集的集群”,并为 DestinationRule 的每个子集再多创建一个集群。子集集群的端点,是从服务的全部端点中筛选出标签匹配的那些——这里用端口来模拟(8103 是 version v1 的 Pod,8104 是 version v2 的 Pod)。集群名称中有 |,所以要用引号括起来,route_config 的名称看起来像数字,但它是字符串,所以也要加引号。

统计四十次子集权重

把 /root/ist2-name/name.yaml 复制为 /root/ist2-name/split.yaml,并把路由改为加权路由——outbound|9080|v1|reviews.default.svc.cluster.local 为 75,outbound|9080|v2|reviews.default.svc.cluster.local 为 25。用 split.yaml 并加上 --concurrency 1 重新启动 Envoy,然后恰好发送 40 次 curl localhost:10083/,根据响应正文统计去往了哪个子集,以 v1=、v2=、total= 三行写入 /root/ist2-name/04-split.txt。

VirtualService 的 route[].weight 会变成 Envoy 的 weighted_clusters。权重是对每个请求掷骰子,所以四十次请求不会恰好是 30 比 10——数字稍有偏差是正常的。--concurrency 1 把工作线程固定为一个,让实验不再波动。根据正文中的端口(是 ok:8103 还是 ok:8104)区分子集即可。

统计名称就是集群名称——用 alt_stat_name 改变它

在 /root/ist2-name/split.yaml 的 outbound|9080|v2|reviews.default.svc.cluster.local 集群中添加 alt_stat_name: outbound_9080_v2_reviews(其余保持不变)并重新启动,然后发送 20 次以上请求。从管理端口的 /stats 中把两行原样抄下来,保存到 /root/ist2-name/05-stats.txt——v1 集群的 upstream_rq_200 行,以及 v2 集群的 upstream_rq_200 行(现在会以改变后的名称出现)。

Envoy 把集群统计累积在 cluster.<클러스터 이름>.<지표>(占位符依次为集群名称与指标)之下。Istio 的集群名称中包含 |,转换为 Prometheus 时不太好处理。所以如果在网格配置的 outboundClusterStatName 中给出模式,istiod 就会为每个集群附加 alt_stat_name,只改变统计名称——路由仍然使用原来的名称。像 /stats?filter=upstream_rq_200 这样过滤,就容易找到这两行。

指向不存在的子集会变成 503

把 /root/ist2-name/split.yaml 复制为 /root/ist2-name/missing.yaml,并修改两处——在 route_config 中添加 validate_clusters: false,并在原有路由前面添加一条把路径前缀 /v3 发往 outbound|9080|v3|reviews.default.svc.cluster.local 的路由(不创建这个集群)。然后再创建一份把 missing.yaml 中的 validate_clusters: false 那一行去掉的副本 /root/ist2-name/missing-strict.yaml。用 missing.yaml 启动后,发送一次 curl localhost:10083/v3,并在 /root/ist2-name/06-missing.txt 中写入三行——status=(HTTP 状态码)、no_cluster=(统计 http.outbound_0.0.0.0_9080.no_cluster 的值)、strict_rc=(对 missing-strict.yaml 执行 envoy --mode validate 的退出码)。

VirtualService 指向 DestinationRule 中没有的子集,是 Istio 中非常常见的错误。istiod 通过 RDS 下发路由,而动态接收的路由,即使所指向的集群不存在,也会被接受(动态情况下 validate_clusters 的默认值是 false)。这样一来,直到请求到来时才会返回 503,访问日志中会打印响应标记 NC(no cluster)。静态配置的默认值是 true,会把同样的配置直接拒绝——这一步就是把两种表现放在一起对照观察。

子集是同一个服务端点的部分集合

从正在运行的 Envoy 的 localhost:9983/clusters 中,统计 reviews 的三个集群各有几个端点,以 <클러스터 이름> <개수>(占位符依次为集群名称与个数)的形式写三行到 /root/ist2-name/07-endpoints.txt。

/clusters 会为每个端点输出多行 이름::주소::지표::값(占位符依次为名称、地址、指标、值)。用每个端点只出现一次的指标(例如 cx_active)选出行,再按集群名称统计即可。关键在于,没有子集的集群同时拥有两个子集的全部端点——子集不是新的服务,而是用标签把同一份端点列表分开。所以没有标签的 Pod 不属于任何子集,只会去往没有子集的集群。

整理成读取名称的方法

在 /root/ist2-name/08-report.md 中写入 default_cluster=、inbound_cluster=、v2_stat_name=、missing_subset_code= 四行(依次为没有子集的出站集群名称、入站集群名称、第 5 步中改变后的 v2 统计名称、第 6 步中收到的 HTTP 状态码),并在下面至少写四行以 - 开头的说明。

请从前面步骤的文件中转写。说明行中最好想象成是在教第一次看到 proxy-config clusters 输出的同事如何读取名称。