把流量策略搬进集群并让它溢出
目标
把 DestinationRule 的流量策略转换为 Envoy 集群的字段,并通过发送请求,统计异常点检测和断路器实际是如何工作的。
为什么重要
流量策略出事故,不是因为配置写错,而是因为对行为的期望错了。限额由每个 sidecar 分别统计,异常点检测是事后措施,子集策略会整块覆盖父策略。亲眼看过转换后的集群和实际行为之后,在配置评审中就能立刻指出这三点。
步骤
- 在
/root/ist2-dr/dr.yaml中编写 DestinationRule——名称ratings,命名空间default,host 为ratings.default.svc.cluster.local。trafficPolicy包含loadBalancer.simple: LEAST_REQUEST、connectionPool中的tcp.maxConnections: 3、http.http1MaxPendingRequests: 2、http.http2MaxRequests: 5,以及outlierDetection中的consecutive5xxErrors: 2、interval: 5s、baseEjectionTime: 30s、maxEjectionPercent: 50。子集只有一个v2(标签version: v2),只给这个子集设置trafficPolicy.connectionPool.tcp.maxConnections: 1。把istioctl validate的输出和退出码保存到/root/ist2-dr/01-validate.txt(最后一行为rc=0)。 - 在
/root/ist2-dr/02-map.txt中分七行写出 DestinationRule 的字段会变成 Envoy 集群的哪个字段。每行的格式为<trafficPolicy 아래 경로>=<클러스터 아래 경로>(占位符依次为 trafficPolicy 之下的路径与集群之下的路径),用点连接的路径中不要写数组下标。左侧是这七个——loadBalancer.simple、connectionPool.tcp.maxConnections、connectionPool.http.http1MaxPendingRequests、connectionPool.http.http2MaxRequests、outlierDetection.consecutive5xxErrors、outlierDetection.baseEjectionTime、outlierDetection.maxEjectionPercent。 - 在
/root/ist2-dr/pool.yaml中编写 Envoy 配置——管理端口9985,把监听器127.0.0.1:10085的所有路径发往集群outbound|9080||ratings.default.svc.cluster.local。该集群包含三个端点(127.0.0.1:8107、127.0.0.1:8108、127.0.0.1:8115),以及把第 1 步父级流量策略按第 2 步对照表转换而来的字段(lb_policy、circuit_breakers.thresholds的一项、outlier_detection——同时包含 interval)。把envoy --mode validate的输出和退出码保存到/root/ist2-dr/03-validate.txt(最后一行为rc=0)。 - 启动三个上游(
8107和8115用ok,8108用fail),用pool.yaml启动 Envoy,然后从localhost:9985/config_dump?resource=static_clusters中取出这个集群的值,写成一行保存到/root/ist2-dr/04-dump.txt——lb=<lb_policy> maxconn=<max_connections> pend=<max_pending_requests> req=<max_requests> c5xx=<consecutive_5xx> base=<base_ejection_time> pct=<max_ejection_percent>。 - 重新启动 Envoy(使用
pool.yaml)之后立即依次发送 30 次请求,并在/root/ist2-dr/05-eject.txt中写入四行——failed_requests=(30 次中收到 503 的数量)、ejected=(/clusters中 health_flags 为/failed_outlier_check的端点的주소:포트(占位符依次为地址与端口))、ejections_enforced_total=(统计outlier_detection.ejections_enforced_total的值)、ejections_active=(统计outlier_detection.ejections_active的值)。 - 把
pool.yaml复制为/root/ist2-dr/pool-subset.yaml,并添加子集 v2 的集群outbound|9080|v2|ratings.default.svc.cluster.local(端点为127.0.0.1:8115)。要与 Istio 根据第 1 步的 DestinationRule 生成的结果一致——lb_policy和outlier_detection与父级相同,而circuit_breakers.thresholds只由子集的 connectionPool 生成:max_connections: 1,以及子集没有指定的max_pending_requests、max_requests、max_retries,取 Istio 的默认值4294967295。路由把前缀/v2发往这个集群,其余保持不变。重新启动之后,从/clusters的default_priority行中读取,并在/root/ist2-dr/06-subset.txt中写入v2_max_connections=、v2_max_pending_requests=、default_max_pending_requests=(父集群的值)三行。 - 把
pool.yaml复制为/root/ist2-dr/pool-cb.yaml,并添加集群outbound|9080|slow|ratings.default.svc.cluster.local——端点为127.0.0.1:8116(需要 3 秒才响应的上游),circuit_breakers.thresholds设为max_connections: 1、max_pending_requests: 1(在 Istio 中对应tcp.maxConnections: 1、http.http1MaxPendingRequests: 1),不设异常点检测。路由把前缀/slow发往这个集群。以slow模式在8116上启动上游,用--concurrency 1重新启动 Envoy,然后同时发送五个/slow请求(用&放到后台,再wait)。在/root/ist2-dr/07-overflow.txt中写入ok=(200 的数量)、overflow_503=(503 的数量)、pending_overflow=(slow 集群统计upstream_rq_pending_overflow的值)三行。 - 在
/root/ist2-dr/08-report.md中写入lb_policy=、failures_before_eject=(第 5 步中被移出之前遭遇的 503 数量)、v2_pending_limit=(第 6 步中看到的 v2 的队列限额)、overflow_status=(第 7 步中溢出的请求收到的状态码)四行,并在下面写至少四行以-开头的说明。
参考
- 这个 Pod 中既没有真正的 istiod,也没有真正的 sidecar。所以无法用
istioctl proxy-config查看实际生成的内容,需要了解转换规则,并亲手编写等价的 Envoy 配置来确认行为。同样的规则会原样出现在生产集群的proxy-config输出中。 - 从第 4 步开始,请用
--concurrency 1启动 Envoy。这样统计断路器和异常点检测的实验不会出现波动。 - 统计数据和异常点状态会在进程存活期间一直累积。需要统计的步骤,请在重新启动之后立即进行。
/clusters和/stats的名称中含有|。请使用grep -F,或者用\|转义。- 启动 Envoy 时,请用
setsid --fork nohup envoy -c <파일> --log-level warn > <로그> 2>&1 </dev/null(占位符依次为配置文件与日志文件)使它与 shell 完全脱离。重新启动之前,用pkill -x envoy清理(pkill -f 'envoy -c'会把包含该字符串的 shell 自己也杀掉)。 - 镜像中有模拟上游的服务器:
python3 /opt/lab/envoy/upstream.py <포트> ok|fail|slow(占位符为端口)。响应正文为<모드>:<포트> <경로>(占位符依次为模式、端口与路径)。 - 修改配置之后,启动之前先用
envoy --mode validate -c <파일>(占位符为配置文件)筛查一遍。集群名称中含有|,所以在 YAML 中必须用引号括起来。
编写带有流量策略的 DestinationRule
在 /root/ist2-dr/dr.yaml 中编写 DestinationRule——名称 ratings,命名空间 default,host 为 ratings.default.svc.cluster.local。trafficPolicy 包含 loadBalancer.simple: LEAST_REQUEST、connectionPool 中的 tcp.maxConnections: 3、http.http1MaxPendingRequests: 2、http.http2MaxRequests: 5,以及 outlierDetection 中的 consecutive5xxErrors: 2、interval: 5s、baseEjectionTime: 30s、maxEjectionPercent: 50。子集只有一个 v2(标签 version: v2),只给这个子集设置 trafficPolicy.connectionPool.tcp.maxConnections: 1。把 istioctl validate 的输出和退出码保存到 /root/ist2-dr/01-validate.txt(最后一行为 rc=0)。
DestinationRule 决定“发送之后如何处理”。流量策略分为三块——发给谁(loadBalancer)、一次能接纳多少(connectionPool)、什么时候把不健康的端点移出(outlierDetection)。在子集之下也可以放置形状相同的 trafficPolicy,它如何与父级合并,正是第 6 步的主题。
做出七个字段的对照表
在 /root/ist2-dr/02-map.txt 中分七行写出 DestinationRule 的字段会变成 Envoy 集群的哪个字段。每行的格式为 <trafficPolicy 아래 경로>=<클러스터 아래 경로>(占位符依次为 trafficPolicy 之下的路径与集群之下的路径),用点连接的路径中不要写数组下标。左侧是这七个——loadBalancer.simple、connectionPool.tcp.maxConnections、connectionPool.http.http1MaxPendingRequests、connectionPool.http.http2MaxRequests、outlierDetection.consecutive5xxErrors、outlierDetection.baseEjectionTime、outlierDetection.maxEjectionPercent。
在 Envoy 的集群配置文档(cluster.proto)中找名称相近的字段即可。连接池的限额在 Envoy 中位于 circuit_breakers.thresholds 之下,按优先级排列的列表中——Istio 所说的“连接池”和 Envoy 所说的“断路器”是同样的数值。异常点检测的字段名称,如 consecutive5xxErrors,从驼峰式命名变成下划线命名时,会略有缩短。
按对照表建立集群
在 /root/ist2-dr/pool.yaml 中编写 Envoy 配置——管理端口 9985,把监听器 127.0.0.1:10085 的所有路径发往集群 outbound|9080||ratings.default.svc.cluster.local。该集群包含三个端点(127.0.0.1:8107、127.0.0.1:8108、127.0.0.1:8115),以及把第 1 步父级流量策略按第 2 步对照表转换而来的字段(lb_policy、circuit_breakers.thresholds 的一项、outlier_detection——同时包含 interval)。把 envoy --mode validate 的输出和退出码保存到 /root/ist2-dr/03-validate.txt(最后一行为 rc=0)。
circuit_breakers.thresholds 是一个列表——因为可以按优先级(DEFAULT、HIGH)分别设置限额。Istio 只使用 DEFAULT 一项。时间值要写成带单位的字符串,如 30s。8108 是用作始终返回 503 的端点(第 5 步)。
从 config_dump 中读回六个值
启动三个上游(8107 和 8115 用 ok,8108 用 fail),用 pool.yaml 启动 Envoy,然后从 localhost:9985/config_dump?resource=static_clusters 中取出这个集群的值,写成一行保存到 /root/ist2-dr/04-dump.txt——lb=<lb_policy> maxconn=<max_connections> pend=<max_pending_requests> req=<max_requests> c5xx=<consecutive_5xx> base=<base_ejection_time> pct=<max_ejection_percent>。
这一步是把写在文件里的内容与 Envoy 实际读入的内容对照。在生产环境中,istioctl proxy-config cluster <파드> --fqdn … -o json(占位符为 Pod)显示的就是这份转储。用 .configs[].cluster | select(.name==…) 选出之后,再用 jq 的字符串插值生成一行。转储会省略取默认值的字段,所以如果看到缺少的值,说明它没有写进配置。
异常点检测是在经历失败之后才移出
重新启动 Envoy(使用 pool.yaml)之后立即依次发送 30 次请求,并在 /root/ist2-dr/05-eject.txt 中写入四行——failed_requests=(30 次中收到 503 的数量)、ejected=(/clusters 中 health_flags 为 /failed_outlier_check 的端点的 주소:포트(占位符依次为地址与端口))、ejections_enforced_total=(统计 outlier_detection.ejections_enforced_total 的值)、ejections_active=(统计 outlier_detection.ejections_active 的值)。
异常点检测是在遭遇之后才移出的装置。当某个端点连续 consecutive5xxErrors 次返回 5xx 时,才会在 baseEjectionTime 期间把它移出负载均衡。所以在达到这个次数之前,用户会先遭遇 503。被移出之后,只剩另外两个端点接收请求,503 就停止了。maxEjectionPercent: 50 的意思是,三个中最多只能移出一个。/clusters 的行格式为 클러스터::주소::health_flags::값(占位符依次为集群名称、地址与值)。
子集策略会整块覆盖
把 pool.yaml 复制为 /root/ist2-dr/pool-subset.yaml,并添加子集 v2 的集群 outbound|9080|v2|ratings.default.svc.cluster.local(端点为 127.0.0.1:8115)。要与 Istio 根据第 1 步的 DestinationRule 生成的结果一致——lb_policy 和 outlier_detection 与父级相同,而 circuit_breakers.thresholds 只由子集的 connectionPool 生成:max_connections: 1,以及子集没有指定的 max_pending_requests、max_requests、max_retries,取 Istio 的默认值 4294967295。路由把前缀 /v2 发往这个集群,其余保持不变。重新启动之后,从 /clusters 的 default_priority 行中读取,并在 /root/ist2-dr/06-subset.txt 中写入 v2_max_connections=、v2_max_pending_requests=、default_max_pending_requests=(父集群的值)三行。
官方文档只写了“子集级别的流量策略会覆盖 DestinationRule 级别的对应设置”。覆盖的单位是 connectionPool、loadBalancer、outlierDetection、tls 这样的整块。所以如果在子集中只写了一个 tcp.maxConnections,整个 connectionPool 块就会被整体替换,父级的 http 限额(2、5)不会延续到 v2。空缺的部分是 Istio 填入的默认值(4294967295,实际上不设上限)——比 Envoy 自身的默认值 1024 还要宽松。请在 /clusters 中找 <이름>::default_priority::max_pending_requests::<값>(占位符依次为名称与值)这一行。
数一数断路器是如何真正打开的
把 pool.yaml 复制为 /root/ist2-dr/pool-cb.yaml,并添加集群 outbound|9080|slow|ratings.default.svc.cluster.local——端点为 127.0.0.1:8116(需要 3 秒才响应的上游),circuit_breakers.thresholds 设为 max_connections: 1、max_pending_requests: 1(在 Istio 中对应 tcp.maxConnections: 1、http.http1MaxPendingRequests: 1),不设异常点检测。路由把前缀 /slow 发往这个集群。以 slow 模式在 8116 上启动上游,用 --concurrency 1 重新启动 Envoy,然后同时发送五个 /slow 请求(用 & 放到后台,再 wait)。在 /root/ist2-dr/07-overflow.txt 中写入 ok=(200 的数量)、overflow_503=(503 的数量)、pending_overflow=(slow 集群统计 upstream_rq_pending_overflow 的值)三行。
如果上游很慢,一个连接会被占用 3 秒。在此期间到来的请求进入等待队列,如果队列也满了,Envoy 连上游都不会去,就立刻返回 503 和 x-envoy-overloaded: true 头。所以能接纳的数量是“连接数 + 队列长度”。如果依次发送,请求会一次完成一个,什么都不会溢出,所以一定要同时发送。统计数据是累积的,所以重新启动之后只统计一次。
整理成 DestinationRule 转换对照表
在 /root/ist2-dr/08-report.md 中写入 lb_policy=、failures_before_eject=(第 5 步中被移出之前遭遇的 503 数量)、v2_pending_limit=(第 6 步中看到的 v2 的队列限额)、overflow_status=(第 7 步中溢出的请求收到的状态码)四行,并在下面写至少四行以 - 开头的说明。
数值请从前面步骤的文件中抄录。说明行中最好写下“修改 DestinationRule 时我要小心什么”——尤其是子集策略覆盖父级的方式,在评审时很容易被忽略。