VirtualService 与 DestinationRule — 角色分开的原因
一句话总结
VirtualService决定将哪个请求发送到哪里,DestinationRule决定到达那里后如何处理。
为什么需要这个?
容器服务只把请求扔到队列中。没有更多。只有“只有带有此头部的请求作为新版本”,“如果此服务在3秒内没有响应,就放弃”,“连续发出5xx的实例将被扣除30秒”等请求都必须由应用程序或前端网关承担。
Istio将这个要求分解为两个资源。分解的理由很重要。**路由规则经常变化,每个部门都不一样,但目标的性质(哪些标签是哪个版本,连接池留多少)相对稳定。**所以每次部署时只需要修改一个VirtualService,DestinationRule只要服务所有者设定一次就很长一段时间了。
怎么行动
看看两个资源翻译成Envoy的哪个地方,角色分工就变得清晰了。
| Istio资源 | Envoy对应 | 决定 |
|---|---|---|
| VirtualService | RouteConfiguration | 匹配条件、权重、重写、超时、重试、注入故障、映象 |
| DestinationRule | Cluster | subset(按标签划分的对象)、负载平衡、连接池、异常检测、客户端TLS |
Envoy 群集名称是방향|포트|서브셋|FQDN因为是形式outbound|9080|v2|reviews.mesh-lab.svc.cluster.local看起来像这样。只要记住subset的名字就那样粘在原处,就能更容易地读取日志。
匹配是有顺序的。spec.http是数组,Envoy从上面往下走,使用第一个遇到的。所以如果把无条件的catch-all路由放在最上面,下面规则就永远不会执行。实际规则只有一条——**条件越窄的规则越往上走,无条件的基本路径一定要放在最下面。**而且如果根本没有基本路径,匹配失败的请求是NR(no route)和标志一起变成503。
subset不是名字,而是用标签来选择pad。在DestinationRule中name: v2, labels: {version: v2}写上的话,那个subset是version=v2只指着有标签的pad。如果把名字和标签弄混了,丢掉了标签或出错的话,那个subset中就没有端点,流量是UH(no healthy upstream)变成503。在消息中最常见的503的原因正是这个标签不一致。
协议辨别也很容易错过。梅西根据Service的端口名称决定L7处理是否。端口名称http,http-api,grpc,tcp必须像这样开始才能进行HTTP路由。如果名称不存在或不正确,就会被视为TCP,整个头衔匹配被忽略。
恢复力设置必须以数字依据为基础进行确定。
perTryTimeout是对象正常p99延迟的1.5~2倍。timeout(全部)是perTryTimeout × (attempts + 1)以上。否则连重新尝试都不可能开始,就会超出整个截止日期。attempts仅限GET请求时,数量充足。如果没有唯一性键,POST重试将导致重复处理。outlierDetection里面有maxEjectionPercent必须设置。在有3个实例的服务中,如果设置为100%,全部都会丢失,导致全面故障。
在现场相遇的样子
**第一,重试风暴。**在A→B→C链中,如果每个阶段有2次重试,C接收的请求在最坏的情况下会增加9倍。4段的话是27倍。就像在死去的服务上插上棍子一样,重试只放在链的某个地方(最好是外面),里面保持0~1。
**第二,超时叠加。**如果外部超时比内部短,那么内部在认真工作时,外部会中断并重新尝试。内部服务会重复“绝对无法完成的工作”。截止日期应该从外部向内部越来越短。
第三,503从标志开始看。UH没有端点(确认subset标签),UO是连接池超出(电路断路器运行中),UF是连接失败(只有一方是STRICT的mTLS不一致是常客),NR没有路由(确认catch-all),URX重试消耗完毕。只要知道这五个就能将调试时间缩短一半。
下次实习要做的事情
上传图片的reviews v1/v2工作量,定义subset后,将基本路径设为v1,只将带有特定头部的请求发送为v2的VirtualService。ratings中,对路径前缀匹配和重写进行延时和重试,然后加上异常检测和缺陷注入。最后istioctl analyze验证设置,将路由顺序整理为JSON。虽然流量实际上没有流过,但路由数组的顺序和subset的标签一致,即使只通过静态分析也能准确判断。