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

Istio 进阶 — 为什么会那样流

编写引导配置,区分两种无法启动的情况

在 TT Lab 中继续学习

目标

从注入产物中提取 pilot-agent 的材料,亲手编写 istiod 风格的 bootstrap 并进行筛查,再亲手制造 sidecar 无法启动的两种情况(配置被拒绝、没有配置服务器),并加以区分。

为什么重要

sidecar 故障有一半是以两种症状出现的——容器 CrashLoop,或者在运行却不是 Ready。前者是 bootstrap 有误,后者是没有从 istiod 收到配置。了解 bootstrap 是由什么生成的,只凭一行日志和一次管理端口查询,就能把两者区分开。

步骤

  1. 在 /root/ist2-boot 中用 istioctl kube-inject 给 /opt/lab/fixtures/istio/inject-target.yaml 注入 sidecar,保存为 /root/ist2-boot/inject.yaml(要把三个注入配置文件全部传入)。然后读取 istio-proxy 容器的 args,在 /root/ist2-boot/01-args.txt 中写入四行——role=(第二个参数)、domain=(--domain 之后的参数,保持原样)、proxy_log_level=(--proxyLogLevel= 的值)、component_log_level=(--proxyComponentLogLevel= 的值)。
  2. 从 /root/ist2-boot/inject.yaml 中 istio-proxy 的环境变量里挑出七个,以 이름=값(占位符依次为名称与值)的形式写入 /root/ist2-boot/02-env.txt——CA_ADDR、PILOT_CERT_PROVIDER、ISTIO_META_CLUSTER_ID、TRUST_DOMAIN、ISTIO_META_WORKLOAD_NAME 原样写 value,POD_NAMESPACE、SERVICE_ACCOUNT 的值来自 Pod,所以写成 fieldRef:<fieldPath> 的形式(例如:fieldRef:metadata.name)。
  3. 读取 /opt/istio/mesh-config.yaml,在 /root/ist2-boot/03-mesh.txt 中写入四行——discovery_address=(defaultConfig.discoveryAddress)、root_namespace=、trust_domain=,以及 ca_addr_same=:如果该 discoveryAddress 与第 2 步的 CA_ADDR 逐字相同则写 yes,否则写 no。
  4. 在 /root/ist2-boot/bootstrap.yaml 中编写 bootstrap——管理端口 9982;node.id 是四个字段 sidecar、10.0.0.7、payments-6d5f9.default、default.svc.cluster.local 按此顺序用波浪号连接而成,node.cluster 为 payments.default,node.metadata 中有 CLUSTER_ID、MESH_ID、NAMESPACE、WORKLOAD_NAME 四个键(值取自第 2 步的 ISTIO_META_ 变量和命名空间 default);dynamic_resources 通过 ADS(gRPC)接收 cds_config、lds_config,并设置 initial_fetch_timeout: 0s;ADS 所指向的静态集群名称为 xds-grpc,以 STRICT_DNS 指向第 3 步的 discovery_address,并使用 HTTP/2。把 envoy --mode validate 的输出和退出码写入 /root/ist2-boot/04-validate.txt(最后一行 rc=0)。
  5. 把 /root/ist2-boot/bootstrap.yaml 复制为 /root/ist2-boot/boot-typo.yaml,然后只把 ADS 一侧(ads_config 的 cluster_name)改为 istiod-xds(静态集群名称 xds-grpc 保持不变)。用 envoy --mode validate 检查,并把输出和退出码写入 /root/ist2-boot/05-typo.txt(最后一行为 rc=)。
  6. 用 /root/ist2-boot/bootstrap.yaml 启动 Envoy(这个 Pod 中没有 istiod)。从管理端口的 /config_dump 中,只提取 bootstrap 的 node 里的 id、cluster、metadata 三个字段,保存到 /root/ist2-boot/06-node.json,并在 /root/ist2-boot/06-ready.txt 中写入三行——ready_code=(/ready 的 HTTP 状态码)、ready_body=(其正文)、connected_state=(统计 control_plane.connected_state 的值)。
  7. 在 /root/ist2-boot/inject.yaml 中找到身份的材料,在 /root/ist2-boot/07-identity.txt 中写入五行——token_volume=(投射 serviceAccountToken 的卷名称)、token_audience=(该令牌的 audience)、token_mount=(该卷挂载到 istio-proxy 的路径)、ca_configmap=(卷 istiod-ca-cert 所读取的 ConfigMap 名称)、ca_mount=(istiod-ca-cert 挂载到 istio-proxy 的路径)。
  8. 在 /root/ist2-boot/08-report.md 中写入 xds_address=、xds_cluster=、typo_rc=、ready_without_istiod= 四行(依次为接收 xDS 的地址、ADS 应该指向的静态集群名称、第 5 步的退出码、第 6 步中看到的 /ready 正文),并在下面至少写四行以 - 开头的说明。

参考

代理容器启动的不是 Envoy,而是 pilot-agent

在 /root/ist2-boot 中用 istioctl kube-inject 给 /opt/lab/fixtures/istio/inject-target.yaml 注入 sidecar,保存为 /root/ist2-boot/inject.yaml(要把三个注入配置文件全部传入)。然后读取 istio-proxy 容器的 args,在 /root/ist2-boot/01-args.txt 中写入四行——role=(第二个参数)、domain=(--domain 之后的参数,保持原样)、proxy_log_level=(--proxyLogLevel= 的值)、component_log_level=(--proxyComponentLogLevel= 的值)。

镜像的入口点是 pilot-agent,参数的前两个单词 proxy sidecar 是“启动 sidecar 角色的代理”这个子命令。--domain 的值中包含的 $(POD_NAMESPACE),不是 shell 语法,而是 kubelet 在启动容器时用同名环境变量替换进去的 Kubernetes 语法——清单中会保留其原样,所以请原样抄下来。--x=값(占位符为值)的形式,用 sed 's/^--x=//' 去掉前缀只取值即可。

环境变量成为代理的标识和通讯录

从 /root/ist2-boot/inject.yaml 中 istio-proxy 的环境变量里挑出七个,以 이름=값(占位符依次为名称与值)的形式写入 /root/ist2-boot/02-env.txt——CA_ADDR、PILOT_CERT_PROVIDER、ISTIO_META_CLUSTER_ID、TRUST_DOMAIN、ISTIO_META_WORKLOAD_NAME 原样写 value,POD_NAMESPACE、SERVICE_ACCOUNT 的值来自 Pod,所以写成 fieldRef:<fieldPath> 的形式(例如:fieldRef:metadata.name)。

环境变量有两类。一类是注入时已经确定的值(value),另一类是 Pod 启动时由 kubelet 填入的值(valueFrom.fieldRef)。Pod 名称、命名空间、服务账号在注入的那一刻还无法知道,所以属于后一类。以 ISTIO_META_ 开头的变量,pilot-agent 会去掉前缀后移入 Envoy 的 node.metadata——这是 istiod 用来认出这个代理的标识。在 yq 中用 .env[] | select(.name=="CA_ADDR") 逐个挑选。

把网格配置的默认值与 CA 地址对照

读取 /opt/istio/mesh-config.yaml,在 /root/ist2-boot/03-mesh.txt 中写入四行——discovery_address=(defaultConfig.discoveryAddress)、root_namespace=、trust_domain=,以及 ca_addr_same=:如果该 discoveryAddress 与第 2 步的 CA_ADDR 逐字相同则写 yes,否则写 no。

defaultConfig 是整个网格的代理默认配置,其中的 discoveryAddress 是获取 xDS 的地址。istiod 既是配置服务器,也是证书签发机构(CA),所以在默认安装中,两个地址指向同一个 15012 端口。两者变得不同,是在接入外部 CA 的时候。值用 yq 提取,比较用 shell 的 [ "$a" = "$b" ]。

亲手编写 istiod 风格的 bootstrap 并进行筛查

在 /root/ist2-boot/bootstrap.yaml 中编写 bootstrap——管理端口 9982;node.id 是四个字段 sidecar、10.0.0.7、payments-6d5f9.default、default.svc.cluster.local 按此顺序用波浪号连接而成,node.cluster 为 payments.default,node.metadata 中有 CLUSTER_ID、MESH_ID、NAMESPACE、WORKLOAD_NAME 四个键(值取自第 2 步的 ISTIO_META_ 变量和命名空间 default);dynamic_resources 通过 ADS(gRPC)接收 cds_config、lds_config,并设置 initial_fetch_timeout: 0s;ADS 所指向的静态集群名称为 xds-grpc,以 STRICT_DNS 指向第 3 步的 discovery_address,并使用 HTTP/2。把 envoy --mode validate 的输出和退出码写入 /root/ist2-boot/04-validate.txt(最后一行 rc=0)。

在 pilot-agent 生成的 bootstrap 中,静态集群实际上只有一个 xds-grpc。监听器和服务集群全部通过这个集群以 ADS 方式获取。gRPC 运行在 HTTP/2 之上,所以必须为集群启用 http2_protocol_options。node.id 的四个字段依次为角色、Pod IP、파드이름.네임스페이스(占位符依次为 Pod 名称与命名空间)、네임스페이스.svc.cluster.local(占位符为命名空间)。initial_fetch_timeout: 0s 的意思是“一直等到收到配置为止”,这是 Istio 实际使用的值。

集群名称里的一个字母会造成 CrashLoop

把 /root/ist2-boot/bootstrap.yaml 复制为 /root/ist2-boot/boot-typo.yaml,然后只把 ADS 一侧(ads_config 的 cluster_name)改为 istiod-xds(静态集群名称 xds-grpc 保持不变)。用 envoy --mode validate 检查,并把输出和退出码写入 /root/ist2-boot/05-typo.txt(最后一行为 rc=)。

ADS 的 cluster_name 的意思是“用这个名称的静态集群打开 gRPC 连接”。如果这个名称不在静态集群列表中,Envoy 就没有办法去往配置服务器,所以会在启动之前就拒绝。在 Kubernetes 中,容器会立刻退出,又反复启动——这就是 CrashLoopBackOff。拒绝信息中会原样打印出写错的名称,所以一行日志就能指出原因。

连不上 istiod 的代理不会就绪

用 /root/ist2-boot/bootstrap.yaml 启动 Envoy(这个 Pod 中没有 istiod)。从管理端口的 /config_dump 中,只提取 bootstrap 的 node 里的 id、cluster、metadata 三个字段,保存到 /root/ist2-boot/06-node.json,并在 /root/ist2-boot/06-ready.txt 中写入三行——ready_code=(/ready 的 HTTP 状态码)、ready_body=(其正文)、connected_state=(统计 control_plane.connected_state 的值)。

sidecar 的就绪状态归根结底就是 Envoy 的 /ready(pilot-agent 在 15021 上代为应答)。以 initial_fetch_timeout: 0s 启动的 Envoy,会一直等到收到第一个 CDS、LDS,才会结束初始化。在这种状态下管理端口也是开放的,所以可以通过 config_dump 查看应用了什么。用 jq '.configs[0].bootstrap.node | {id, cluster, metadata}' 只挑出这三个字段。/ready 的状态码用 curl -o /dev/null -w '%{http_code}' 获取。

身份始于两个卷

在 /root/ist2-boot/inject.yaml 中找到身份的材料,在 /root/ist2-boot/07-identity.txt 中写入五行——token_volume=(投射 serviceAccountToken 的卷名称)、token_audience=(该令牌的 audience)、token_mount=(该卷挂载到 istio-proxy 的路径)、ca_configmap=(卷 istiod-ca-cert 所读取的 ConfigMap 名称)、ca_mount=(istiod-ca-cert 挂载到 istio-proxy 的路径)。

pilot-agent 一启动,就会向 istiod(即 CA_ADDR)发送证书签名请求。此时证明“我是这个服务账号”的,是audience 被限定为 istio-ca 的投射令牌,而确认对方是不是真正的 istiod 的,是通过 ConfigMap 分发的根证书。卷在 .volumes[] 中,挂载路径在 istio-proxy 的 .volumeMounts[] 中。投射卷请查看 .projected.sources[].serviceAccountToken 之下。

整理成 sidecar 无法启动时的检查清单

在 /root/ist2-boot/08-report.md 中写入 xds_address=、xds_cluster=、typo_rc=、ready_without_istiod= 四行(依次为接收 xDS 的地址、ADS 应该指向的静态集群名称、第 5 步的退出码、第 6 步中看到的 /ready 正文),并在下面至少写四行以 - 开头的说明。

值请从前面步骤的文件中转写。这张清单是为了整理遇到“sidecar 处于 CrashLoop”“Pod 无法 Ready”时该从哪里看起——说明行中最好把症状和原因配对写下。