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

Istio 服务网格

代理装上之后会发生什么——资源、启动顺序和结束不了的 Job

在 TT Lab 中继续学习

目标

离线运行 istioctl kube-inject,确认每一个注解会落到注入结果的哪个字段。按字段逐一查看资源请求与上限、代理配置(concurrency、排空时间)、启动顺序保证、排除出站端口、排除注入,以及批处理任务不会结束的经典事故及其解决办法。

为什么重要

接入边车只需一行,但接入之后出现的问题,大多来自某一处配置。默认的代理会请求 100m CPU 和 128Mi 内存——有 2000 个 Pod 的话,仅此一项就是 200 个 CPU 核。启动顺序也是个问题。如果应用比代理先启动,最初几秒的出站请求就会直接失败。最古老的陷阱是批处理任务。代理以普通容器的形式存在时,即使任务结束了,Pod 也无法转为完成状态;以前的绕过办法,是应用结束时请求代理退出。现在 Kubernetes 支持带有重启策略的初始化容器,所以把代理移到那里,从根本上解决问题。本实验就是学习如何直接在注入结果清单中确认所有这些旋钮。如果能在上传到集群之前确认,配置变更就成了评审对象。

步骤

  1. 创建 /root/ist-lifecycle,并在 /root/ist-lifecycle/dep.yaml 中写入 lifecycle 命名空间的 Deployment orders——replicas 为 2,标签 app: orders,一个容器(app,镜像 nginx:1.27,容器端口 8080),没有注解。对该文件执行注入,把结果保存到 /root/ist-lifecycle/baseline.yaml,并把从中读取的四项内容,按 containers、initContainers、proxyCpuRequest、proxyMemRequest 四行写入 /root/ist-lifecycle/baseline.tsv(容器名称按出现的顺序用逗号连接)。
  2. /root/ist-lifecycle/dep-resources.yaml 是把第 1 步的 Deployment 复制为名称 orders-res,并在 Pod 模板上加了四个注解——sidecar.istio.io/proxyCPU 为 250m,sidecar.istio.io/proxyMemory 为 192Mi,sidecar.istio.io/proxyCPULimit 为 1500m,sidecar.istio.io/proxyMemoryLimit 为 512Mi。把注入结果保存到 /root/ist-lifecycle/resources.yaml。
  3. /root/ist-lifecycle/dep-proxyconfig.yaml 是复制为名称 orders-pc 的版本,其中加入了一个 proxy.istio.io/config 注解——值是多行 YAML,包含 concurrency: 3 和 terminationDrainDuration: 45s 两项。把注入结果保存到 /root/ist-lifecycle/proxyconfig.yaml,并把结果中 istio-proxy 的环境变量 PROXY_CONFIG 的值保存到 /root/ist-lifecycle/proxy-config.json。
  4. /root/ist-lifecycle/dep-hold.yaml 是复制为名称 orders-hold 的版本,其中通过 proxy.istio.io/config 注解只加入了 holdApplicationUntilProxyStarts: true。把注入结果保存到 /root/ist-lifecycle/hold.yaml,并把容器顺序和代理上出现的生命周期钩子,按 containerOrder 和 postStart 两行写入 /root/ist-lifecycle/hold.tsv(postStart 一栏写钩子所执行的命令,用空格连接)。
  5. /root/ist-lifecycle/dep-exclude.yaml 是复制为名称 orders-exc 的版本,其中把注解 traffic.sidecar.istio.io/excludeOutboundPorts 设为 "3306,5432"。把注入结果保存到 /root/ist-lifecycle/exclude.yaml,并把 istio-init 容器的参数用空格连接,写成一行存入 /root/ist-lifecycle/init-args.txt。
  6. /root/ist-lifecycle/dep-nosidecar.yaml 是复制为名称 orders-off 的版本,其中把注解 sidecar.istio.io/inject 设为 "false"。把注入结果保存到 /root/ist-lifecycle/nosidecar.yaml——结果中不应有代理。
  7. 在 /root/ist-lifecycle/job.yaml 中写入 lifecycle 命名空间的 Job nightly-settle——Pod 标签 app: nightly-settle,restartPolicy: Never,一个容器(worker,镜像 busybox:1.36)。/root/ist-lifecycle/job-native.yaml 是只把名称改为 nightly-settle-native,并加上 Pod 注解 sidecar.istio.io/nativeSidecar 为 "true" 的版本。分别对两者执行注入,保存到 /root/ist-lifecycle/job-injected.yaml 和 /root/ist-lifecycle/job-native-injected.yaml,并把差异按 plain 和 native 两行,以 <이름>\t<containers>\t<initContainers>\t<프록시의 restartPolicy>(占位符依次为名称、containers、initContainers 和代理的 restartPolicy)的格式写入 /root/ist-lifecycle/job-compare.tsv(没有 restartPolicy 时写 -)。
  8. 创建 /root/ist-lifecycle/inject-summary.sh,让它依次重新注入该目录中的八份原始清单(dep.yaml、dep-resources.yaml、dep-proxyconfig.yaml、dep-hold.yaml、dep-exclude.yaml、dep-nosidecar.yaml、job.yaml、job-native.yaml),并对每个文件按该顺序只向标准输出打印 <파일이름>\t<containers>\t<initContainers>\t<프록시 CPU 요청>(占位符依次为文件名、containers、initContainers 和代理的 CPU 请求)(没有的栏位写 -)。把输出保存到 /root/ist-lifecycle/inject-summary.tsv。

参考

没有任何注解时,代理会以什么形态放入

创建 /root/ist-lifecycle,并在 /root/ist-lifecycle/dep.yaml 中写入 lifecycle 命名空间的 Deployment orders——replicas 为 2,标签 app: orders,一个容器(app,镜像 nginx:1.27,容器端口 8080),没有注解。对该文件执行注入,把结果保存到 /root/ist-lifecycle/baseline.yaml,并把从中读取的四项内容,按 containers、initContainers、proxyCpuRequest、proxyMemRequest 四行写入 /root/ist-lifecycle/baseline.tsv(容器名称按出现的顺序用逗号连接)。

没有 istiod,所以必须自己给出三个注入配置——--injectConfigFile /opt/istio/inject-config.yaml --meshConfigFile /opt/istio/mesh-config.yaml --valuesFile /opt/istio/values-config.yaml。查看结果中的顺序时,yq -N e '.spec.template.spec.containers[].name' 比较方便。

用注解调整代理的资源

/root/ist-lifecycle/dep-resources.yaml 是把第 1 步的 Deployment 复制为名称 orders-res,并在 Pod 模板上加了四个注解——sidecar.istio.io/proxyCPU 为 250m,sidecar.istio.io/proxyMemory 为 192Mi,sidecar.istio.io/proxyCPULimit 为 1500m,sidecar.istio.io/proxyMemoryLimit 为 512Mi。把注入结果保存到 /root/ist-lifecycle/resources.yaml。

注解必须加在 Pod 模板(spec.template.metadata.annotations)上。加在 Deployment 的元数据上,什么都不会发生——因为注入的对象是 Pod。请把它与第 1 步的结果并排放在一起,看与默认值相比有多大变化。

代理自身的配置用一个注解以 YAML 形式放入

/root/ist-lifecycle/dep-proxyconfig.yaml 是复制为名称 orders-pc 的版本,其中加入了一个 proxy.istio.io/config 注解——值是多行 YAML,包含 concurrency: 3 和 terminationDrainDuration: 45s 两项。把注入结果保存到 /root/ist-lifecycle/proxyconfig.yaml,并把结果中 istio-proxy 的环境变量 PROXY_CONFIG 的值保存到 /root/ist-lifecycle/proxy-config.json。

这个注解的值是字符串,但里面装的是 YAML。请写成多行块(|)。注入器会把它转换成 JSON,放进一个容器环境变量中——关键在于:人写的格式与代理读取的格式不同。

防止应用先于代理启动的竞争

/root/ist-lifecycle/dep-hold.yaml 是复制为名称 orders-hold 的版本,其中通过 proxy.istio.io/config 注解只加入了 holdApplicationUntilProxyStarts: true。把注入结果保存到 /root/ist-lifecycle/hold.yaml,并把容器顺序和代理上出现的生命周期钩子,按 containerOrder 和 postStart 两行写入 /root/ist-lifecycle/hold.tsv(postStart 一栏写钩子所执行的命令,用空格连接)。

开启这个值后,注入器会改变两件事——代理放在容器列表的什么位置,以及给代理附加什么。与第 1 步的结果比较容器顺序,马上就能看出来。钩子位于 .lifecycle.postStart.exec.command。

把不该经过代理的端口排除掉

/root/ist-lifecycle/dep-exclude.yaml 是复制为名称 orders-exc 的版本,其中把注解 traffic.sidecar.istio.io/excludeOutboundPorts 设为 "3306,5432"。把注入结果保存到 /root/ist-lifecycle/exclude.yaml,并把 istio-init 容器的参数用空格连接,写成一行存入 /root/ist-lifecycle/init-args.txt。

把出站流量转向代理的工作,是由初始化容器通过 iptables 规则完成的。注解会传递到它的命令行参数中——请找一找这些端口附在哪个标志上。这是在排除数据库协议这类代理可能解析错误的端口时使用的旋钮。

只让这个 Pod 不接收代理

/root/ist-lifecycle/dep-nosidecar.yaml 是复制为名称 orders-off 的版本,其中把注解 sidecar.istio.io/inject 设为 "false"。把注入结果保存到 /root/ist-lifecycle/nosidecar.yaml——结果中不应有代理。

开启和关闭注入的旋钮位于命名空间标签和 Pod 注解两层,以 Pod 一侧为准。它用于把不应经过代理的工作负载(大流量传输、代理无法理解的协议)设为例外。请确认初始化容器是否也一并消失。

不会结束的批处理任务及其解决办法

在 /root/ist-lifecycle/job.yaml 中写入 lifecycle 命名空间的 Job nightly-settle——Pod 标签 app: nightly-settle,restartPolicy: Never,一个容器(worker,镜像 busybox:1.36)。/root/ist-lifecycle/job-native.yaml 是只把名称改为 nightly-settle-native,并加上 Pod 注解 sidecar.istio.io/nativeSidecar 为 "true" 的版本。分别对两者执行注入,保存到 /root/ist-lifecycle/job-injected.yaml 和 /root/ist-lifecycle/job-native-injected.yaml,并把差异按 plain 和 native 两行,以 <이름>\t<containers>\t<initContainers>\t<프록시의 restartPolicy>(占位符依次为名称、containers、initContainers 和代理的 restartPolicy)的格式写入 /root/ist-lifecycle/job-compare.tsv(没有 restartPolicy 时写 -)。

以普通容器放入的代理,即使任务结束也会一直存活,所以 Job 无法转为完成状态。从 Kubernetes 1.28 起,可以给初始化容器指定重启策略,做成“先启动、一直存活、主体结束后一并清理”的容器。开启注解后,请看代理会移到哪个列表。

一次性重新注入八份清单,并固化成表

创建 /root/ist-lifecycle/inject-summary.sh,让它依次重新注入该目录中的八份原始清单(dep.yaml、dep-resources.yaml、dep-proxyconfig.yaml、dep-hold.yaml、dep-exclude.yaml、dep-nosidecar.yaml、job.yaml、job-native.yaml),并对每个文件按该顺序只向标准输出打印 <파일이름>\t<containers>\t<initContainers>\t<프록시 CPU 요청>(占位符依次为文件名、containers、initContainers 和代理的 CPU 请求)(没有的栏位写 -)。把输出保存到 /root/ist-lifecycle/inject-summary.tsv。

代理位于普通容器中和位于初始化容器中两种情况都要找到——把两个列表连起来遍历,一个表达式就够了。如果把这样的表也放在仓库中,注入配置发生变化的那天,差异就会立刻显现出来。