# K8s 踩坑记录(模式 B)— 部署流程 + 根因排查 > ⚠️ **2026-09-15:模式 B 的 K8s 后端代码已从本仓下线**(`k8s-spawner` / `leader` / `reconcile` / `k8s-user-fs` / `tcp-bridge` / `web/file-service` 六个模块 + 2 个测试 + 1 个脚本)。备份:`D:\github\_dsh_shenxian_K8s后端备份_20260915\`。本文按「**历史设计稿 + 未来可选路线**」保留;文中的 `dshs file-service` / `dshs tcp-bridge` 两条子命令**现已不存在**,照着敲会 `command not found`。 > 🧭 [← 返回 README](../README.md) · 分步教程:[K8s 部署教程](k8s-deployment.md) > 记录模式 B 实机部署踩到的坑与根因:先在**阿里云 2C2G 单机 k3s** 上 PoC 验证,后在 **ACK 智能托管**上完整落地(§5–§8 含部署流程实录)。 > 想直接照着部署,从 [k8s-deployment.md](k8s-deployment.md) 开始;本文按「症状 → 根因 → 修复」组织,用于卡住时对查。 --- ## 1. 环境 / k3s 安装 ### 1.1 cgroup v1 导致 k3s v1.36+ kubelet 拒启 - **症状**:k3s v1.36 启动几秒后退出,`systemctl` 反复 auto-restart。日志: ``` Shutdown request received: "kubelet exited: ... kubelet is configured to not run on a host using cgroup v1 ..." ``` - **根因**:K8s 1.36 移除了 cgroup v1 支持;阿里云 Linux 3(RHEL8 系)默认跑 cgroup v1。 - **修复(二选一)**: - 启用 cgroup v2(改内核参数 + **重启**): ```bash grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1" grubby --info=ALL | grep args # 确认参数已写入 reboot # 重启后验证 stat -fc %T /sys/fs/cgroup/ # 输出 cgroup2fs 才对 ``` - 或降级 k3s v1.31(还支持 cgroup v1)。 ### 1.2 swap 未关 - **症状**:kubelet 起不来(`fail-swap-on` 默认 true)。 - **修复**: ```bash swapoff -a # 持久化(可选):注释 /etc/fstab 里的 swap 行 sed -i '/[[:space:]]swap[[:space:]]/s/^/#/' /etc/fstab ``` ### 1.3 SELinux 依赖缺失(RHEL8 系) - **症状**:k3s 安装脚本报: ``` nothing provides container-selinux >= 3:2.191.0-1 needed by k3s-selinux-... ``` - **修复**:跳过 SELinux RPM(PoC 不需要): ```bash curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | \ INSTALL_K3S_MIRROR=cn INSTALL_K3S_SKIP_SELINUX_RPM=true sh -s - --disable traefik --disable metrics-server --disable servicelb --disable local-storage ``` --- ## 2. 镜像源(中国区) ### 2.1 docker hub 被墙 - **症状**:`registry-1.docker.io` 403 / 超时,busybox/node/socat 镜像拉不动。 - **修复**:给 k3s 的 containerd 配国内镜像 `/etc/rancher/k3s/registries.yaml`: ```yaml mirrors: docker.io: endpoint: - "https://docker.m.daocloud.io" - "https://docker.1ms.run" ``` 然后 `systemctl restart k3s`。 - **注意**:镜像拉取**极慢**(一个 29MB 镜像拉了 13 分钟),批量拉取要有耐心或换更快的镜像源。 ### 2.2 raw.githubusercontent 被墙 - **症状**:`kubectl apply -f https://raw.githubusercontent.com/longhorn/...` 拉不到 manifest。 - **修复**:加 ghproxy 前缀: ```bash curl -sL "https://ghfast.top/https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml" -o /tmp/longhorn.yaml ``` ### 2.3 alpine/socat tag 写错 - **症状**:`alpine/socat:1.8.0.0-r0` 拉取 403。 - **根因**:该 tag 不存在。 - **修复**:用 `alpine/socat:1.8.0.0`(或 `latest`)。 ⚠️ 内部设计稿 §4.3 里写的 `1.8.0.0-r0` 需按此改正;本项目已改用自带的 Node `tcp-bridge`(见 §7.5),不再依赖 socat 镜像。 ### 2.4 ghcr.io 直连被墙(CNPG/Longhorn 镜像) - **症状**:CloudNativePG operator 镜像 `ghcr.io/cloudnative-pg/cloudnative-pg` 卡 "Pulling" 十几分钟不动。 - **修复**:`registries.yaml` 里给 `ghcr.io` 也配镜像: ```yaml ghcr.io: endpoint: - "https://ghcr.m.daocloud.io" ``` ### 2.5 CloudNativePG `poolers` CRD apply 报 annotation 超长 - **症状**:`kubectl apply -f cnpg.yaml` 报 `CRD poolers.postgresql.cnpg.io is invalid: metadata.annotations: Too long`,operator 崩溃 `no matches for kind "Pooler"`。 - **根因**:client-side apply 写的 `last-applied-configuration` annotation 超过 256KB。 - **修复**:`kubectl apply --server-side --force-conflicts -f cnpg.yaml`(server-side 不写那个超长 annotation)。 --- ## 3. Longhorn ### 3.1 磁盘保留比例过高 - **症状**:卷副本创建失败 `No available disk candidates`,卷状态 `faulted`。 - **根因**:Longhorn 默认**保留 30% 磁盘**;磁盘 88% 满时「可用 < 保留量」,拒绝调度副本。 - **修复**:把保留/最小可用降到 5%: ```bash kubectl -n longhorn-system patch settings.longhorn.io storage-reserved-percentage-for-default-disk --type=merge -p '{"value":"5"}' kubectl -n longhorn-system patch settings.longhorn.io storage-minimal-available-percentage --type=merge -p '{"value":"5"}' ``` ### 3.2 iscsid 未启动 - **症状**:卷卡在 `attaching`,消费 Pod 一直 `ContainerCreating`。 - **修复**(装完 `iscsi-initiator-utils` 后记得启动): ```bash dnf install -y iscsi-initiator-utils systemctl enable --now iscsid ``` ### 3.3 单节点 3 副本调度不了 - **症状**:2 个副本 `Failed to schedule replica`,卷卡 `attaching`。 - **根因**:Longhorn 默认 **hard anti-affinity**,同一卷的副本必须在不同节点;单节点只能调度 1 个。 - **修复**:副本降到 1: ```bash kubectl -n longhorn-system patch volumes.longhorn.io --type=merge -p '{"spec":{"numberOfReplicas":1}}' kubectl -n longhorn-system patch settings.longhorn.io default-replica-count --type=merge -p '{"value":"1"}' ``` ### 3.4 RWX 卷需 nfs-utils - **症状**:RWX 卷挂载失败(RWX 走 share-manager / NFS ganesha)。 - **修复**:装 `nfs-utils`(提供 `mount.nfs`): ```bash dnf install -y nfs-utils ``` ### 3.5 内存预算(重要) - Longhorn 自身 ~600M。**2C2G 上 k3s + Longhorn 已占 ~1.4G**,无法再跑 CloudNativePG 3 实例(需 ~1.5G)。 - **PoC item 4(CNPG + pgbench)需 ≥4G 机器**,2G 必 OOM。 --- ## 4. 方案修正(PoC 实测推翻/修正的结论) ### 4.1 flannel 实际强制 NetworkPolicy(§3.5 修正) - 实测 k3s v1.31 flannel 下 NetworkPolicy **开箱即用**(k3s 内嵌 kube-router netpol 控制器,不再以 DaemonSet 形式;attacker 被拒,删策略后立刻恢复连通)。 - **§3.5「k3s 默认 flannel 不强制」不成立**。 - Cilium 的选型理由应改为:**L7 策略 / eBPF 性能 / Hubble 可观测**(这些 kube-router 没有),而不是「基础强制」。 ### 4.2 gid ≠ fsGroup(§6.0 措辞) - 实测 init Job 建目录 `0700` 属主 uid 正确,但 **gid=0**(不是 fsGroup=100001)——因 §4.3 的 Pod 只设 `runAsUser`+`fsGroup`、没设 `runAsGroup`,进程默认 gid=0。 - **不影响 0700 安全边界**(0700 已把 group 权限关成 `---`)。若要对齐 gid,需加 `runAsGroup:`。 ### 4.3 socat tag(§4.3 修正) - `alpine/socat:1.8.0.0-r0` → `alpine/socat:1.8.0.0`。见 §2.3。 --- ## 5. 完整部署流程(ACK 实跑记录,2026-08-23) > 在阿里云 **ACK 智能托管模式** 上跑通控制面 Phase 2 的完整步骤。k8s 方案里的 > 「3 server HA / kube-vip / MetalLB / Cilium」在 ACK 上由托管能力替代(§5.5 映射)。 > 每用户 DSH Pod(Phase 3)、cert-manager、Helm chart 尚未落地,仍待补。 ### 5.1 集群与基础组件 1. 建 ACK 集群(智能托管模式):**网络插件 DataPath V2(eBPF)+ 勾选 NetworkPolicy**;服务转发模式 IPVS。 2. 装 CNPG operator:manifest 用 ghproxy 下载(§2.2),`kubectl apply --server-side --force-conflicts`(§2.5 annotation 超长坑)。 3. **ghcr.io 换源**:CNPG operator 与 Postgres 镜像在阿里云拉不动(§2.4),换 `ghcr.m.daocloud.io`。 ### 5.2 部署步骤 1. `kubectl apply -f deploy/00-namespace.yaml` 2. `kubectl apply -f deploy/01-dsh-pg.yaml`(Postgres 集群;storageClass 用拓扑感知 `alicloud-disk-topology-alltype`,size ≥20GiB) 3. 等 `dsh-pg` 进入 `Cluster in healthy state`;读连接串: `kubectl -n dsh get secret dsh-pg-app -o jsonpath='{.data.uri}' | base64 -d` 4. 推镜像到 ACR:CI master push 自动推(需 `ACR_USERNAME`/`ACR_PASSWORD` secret),或 workflow_dispatch。 5. 建三个 secret(不在 deploy/ YAML 里,因含动态值): - `dsh-acr-pull`(`docker-registry` 类型,ACR 凭证) - `dsh-secret`(共享加密密钥,`key`) - `dsh-pg`(`url` = 上面读到的 URI) 6. `kubectl apply -f deploy/02-control-plane.yaml` 7. bootstrap admin:`kubectl -n dsh exec deploy/dsh-orchestrator -- node lib/cli.js bootstrap-admin --username admin --password '

'` 8. 按 §5.3 验收。 ### 5.3 验收清单(Phase 2) - 注册 → 审核 → 登录 → 管理台/域名 API 全通。 - 访问控制:普通用户访问管理台 403、域名 API 200。 - kill 一个控制面副本 → Deployment 自动重建(3/3)、Service 不中断。 - 数据在 Postgres,删副本/重启不丢。 ### 5.4 踩坑记录(ACK 实测新增) | 坑 | 修复 | |---|---| | ghcr.io 被墙:CNPG operator / Postgres 镜像拉不动 | 换 `ghcr.m.daocloud.io`(§2.4) | | ESSD 最小 20GiB:`size: 10Gi` 报 `less than minimum 20GiB` | storage `size: 20Gi` | | CNPG 重建集群密码漂移:`dsh-pg-app` 重新生成密码 | 读最新 URI 重建 `dsh-pg` secret;生产用固定密码 | | 控制面启动依赖 Postgres 就绪:ECONNREFUSED 崩 | 等 Postgres healthy 再部署,或接受 CrashLoopBackOff 重试 | | Auto Mode 节点有 taint,CNPG pod 调度失败 | GOATScaler 自动扩出无 taint 节点 | ### 5.5 ACK 与 k8s 方案的映射 | k8s.md 定案 | ACK 等价 | |---|---| | 3 server HA + kube-vip | 托管控制面(免费) | | MetalLB(L2 ARP 云上不生效) | SLB / ALB | | Cilium | Terway DataPath V2(eBPF + NetworkPolicy) | | Longhorn RWX / RWO | NAS / ESSD 云盘 | | CloudNativePG | CloudNativePG(自建)或 RDS PG 高可用版 | --- ## 6. PoC 实测基线(item 4:CNPG on Longhorn) **机器**:阿里云 4C16G,Ubuntu 24.04,SSD 40G。k3s v1.31 + Longhorn v1.7.2(副本 1)+ CNPG v1.24.1,3 实例 healthy。 **pgbench(scale 5,30s,4 clients / 2 threads)**: | 负载 | tps | latency average | 说明 | |---|---|---|---| | select-only(`-S`) | 19164.7 | 0.209 ms | 纯读,数据 ~70MB 全进内存缓冲,未打到磁盘 | | TPC-B 混合(含写) | 705.3 | 5.671 ms | 写落到 Longhorn 磁盘,**这才是 I/O 基线** | **结论**:CNPG on Longhorn 跑通(3 实例、pgbench 正常)。写路径 latency ~5.6ms / tps ~705,是 Longhorn 单副本的 I/O 成本。要对照「节点本地 NVMe + PG 主备」判断是否可接受,需换 NVMe 盘 + 更大 scale(让数据集超过内存)再压一次。 **另:pgbench 手动跑法**(`kubectl run --rm -i` 在后台/非 tty 会话会卡 stdin,别用): ```bash kubectl run pgbench-init --restart=Never --image=postgres:16 -n \ --env PGHOST=-rw --env PGUSER=dsh --env PGPASSWORD= --env PGDATABASE=dsh \ -- pgbench -i -s 5 # 等 pod phase=Succeeded 后 kubectl logs;跑完 kubectl delete pod ``` --- ## 7. Phase 3 集群联调踩坑(2026-08-23,第二阶段) > 控制面 `deployMode=k8s` + leader election + file sidecar + 每用户 Pod 在 ACK > 上联调的实跑记录。前一个阶段(§5)只把控制面 3 副本 + CNPG 跑通了。 ### 7.1 部署顺序(缺一步就挂,按序执行) 1. `deploy/00-namespace.yaml` → `01-dsh-pg.yaml`(Postgres healthy)→ 读 `dsh-pg-app` URI 建 `dsh-pg`/`dsh-secret`/`dsh-acr-pull` secret。 2. **`deploy/03-rbac.yaml`**(SA + Role + lease 权限)。漏了它,控制面 Deployment 报 `FailedCreate: serviceaccount "dsh-orchestrator" not found`,滚动更新卡死。 3. `deploy/02-control-plane.yaml`(含 `imagePullPolicy: Always`,同名 tag 否则节点 缓存旧镜像)。 4. NAS:控制台建 **CNFS**(容器网络文件系统)→ 应用 `deploy/05-storage.yaml` 引 CNFS → `04-pvc.yaml` → `08-bootstrap.yaml`(chmod 1777 PVC 根)→ **最后** `07-psa.yaml`。 5. 每用户 Pod 依赖 `dsh-acr-pull`(§7.3)。 ### 7.2 NAS:用 CNFS,别手写 `server` 参数 - 手写 `alicloud-nas` StorageClass 的 `server` 必须是**挂载目标域 + `:/`**,且 **不含 FileSystemId 前缀**。我给错成 `.` (带 FSID 前缀)→ provisioner 报 `CreateDir: IllegalCharacters`;去掉 FSID 前缀 后 PVC 才 Bound。 - 但挂载目标还会变(重建 NAS 后后缀漂移),正确姿势是控制台建 **CNFS** (`storage.alibabacloud.com/v1beta1 ContainerNetworkFileSystem`),StorageClass 用 `containerNetworkFileSystem: nas` 参数引用,provisioner 自动拿到当前挂载目标。 - bootstrap Job 第一次卡 `ContainerCreating` 无事件 = NFS 挂载挂起(挂载目标错/VPC 不通); 目标对了会报 `mount.nfs: Connection reset by peer`(通常是安全组/访问组或目标刚就绪)。 ### 7.3 每用户 Pod 镜像拉取与资源 - 控制面镜像在**私有 ACR**,生成的 files Pod / DSH Pod / watchdog Job **必须带 `imagePullSecrets: [dsh-acr-pull]`**——控制面 Deployment 有,但生成的 Pod 不继承。 漏了就是 `Init:ImagePullBackOff insufficient_scope`。代码里 config `imagePullSecret` 默认 `dsh-acr-pull`,已在 K8sSpawner 生成的三类 Pod/Job 全部带上。 - bootstrap Job 也需 `imagePullSecrets` + 用控制面镜像(busybox 从 docker.io 拉不动)。 - files sidecar 内存 64Mi 跑不起 node:22-slim + Fastify(OOMKilled → Pod 不 Ready → Headless DNS 无 A 记录 → 控制面 ENOTFOUND),提到 256Mi。 - files sidecar 以用户 uid 跑且无 home,`homedir()` 落到 `/`,`resolveConfig` 会尝试 `mkdir /.dshs` → EACCES 崩。`runFileService` 已钉 `dataRoot=/tmp` + 占位 `encryptionSecret` 跳过写文件。 ### 7.5 docker.io 被墙 → socat sidecar 换成 Node tcp-bridge - ACK 节点拉不动 docker.io(busybox/alpine/socat 都超时),而 `alpine/socat` 正是 每用户 DSH Pod 的 sidecar 镜像。改为控制面镜像里的 `tcp-bridge` 子命令(`net` 纯转发 8081→8080),DSH Pod 只依赖 ACR 里已有的 `dsh` + `dshs` 两个镜像 + `imagePullSecret`,彻底去掉 docker.io 依赖。 - 测鉴权/带 cookie 的接口用 `--resolve` 走域名,别用 `kubectl port-forward`:设了 `cookieDomain=.dsh.example.com` 后 cookie 不会发到 `127.0.0.1`。 ### 7.6 每用户 Pod 的 uid 与就绪探针 - **uid 错位**:注册/建 admin 若先 `initUserRoot`(建 files Pod)再 `createUser`,此时 用户不存在,`resolveUid` 回退 hash uid,而 `createUser` 给的是 `baseUid+row_id`—— files Pod 用 hash uid 建 0700 目录,DSH Pod 用 row_id uid 访问 → EACCES。已改成先 建用户再 initUserRoot。 - **就绪探针**:DSH 只监听 loopback,`tcpSocket 8080` 探的是 Pod IP、永远不 Ready → Headless 无 A 记录 → 子域 502。改成容器内 `node -e http.get(127.0.0.1:8080)` exec 探针。 - **子域代理端口**:Headless Service(clusterIP: None)没有 kube-proxy 做 DNAT,代理 必须直连 Pod 的 targetPort(sidecar 8081),不能连 Service port 80——否则 502。 `endpointFor` 返回 8081。 - **cookie 跨子域**:`dsh.` 登录后跳 `.dsh.`,`SameSite=Lax` 实测 不带 cookie → 401;改 `SameSite=None; Secure`(App 已 HTTPS-only)+ `secureCookies=true`。 - **代理 502**:控制面 keep-alive 池缓存旧 DSH Pod IP,重建 Pod 后命中旧 IP 副本 → 502; 连接出错时 `agent.destroy()` + `agent:false` 重试一次重新 DNS。 ### 7.4 域名入口被阿里云备案拦截 - 腾讯云入口机 nginx 反代到 ACK 公网 SLB(``)时,`Host: dsh.example.com` 被阿里云返回 `403 Non-compliance ICP Filing`(`Server: Beaver`)——域名在腾讯云备案、 未在阿里云备案,走阿里云公网 SLB 的 80/443 被备案校验拦。直连 SLB IP(不带域名 Host) 则 200 正常。 - 解法:给域名在阿里云做 ICP 备案,或入口走 NodePort/专线绕开公网 SLB 备案层,或 控制面域名解析子域改用「SLB 直连 + 腾讯云 nginx 透传 Host」之外的方案(待定)。 --- ## 8. Phase 4 加固 / 可观测(2026-08-23) ### 8.1 代码加固(已落地,随镜像生效) - `readOnlyRootFilesystem: true` + `emptyDir` `/tmp`:DSH dsh/sidecar、file sidecar、 watchdog、控制面容器全部只读 rootfs,`/tmp` 走 emptyDir(PSA restricted 纵深)。 - 空闲回收:reconcile(仅 leader)对每个期望 main 检查 `hasActiveSession`,会话全过期 → `stop` Pod + 删期望态,不自动拉起(复用 `sessionTtlSeconds`,默认 7 天)。 - egress 收敛:`DSHS_EGRESS_CIDRS`(逗号分隔 CIDR)非空时,每用户 DSH Pod 的 443 egress 从 `0.0.0.0/0` 收敛为白名单(仍 except 内网)。默认空 = 保持现状。 ⚠️ DeepSeek API 走 CDN,IP 会漂移,收敛前先 `dig +short api.deepseek.com` 核对并定期重查。 ### 8.2 etcd encryption-at-rest(ACK 托管,控制台操作) 1. ACK 控制台 → 集群 → 托管控制面 → 开启 **etcd 加密**(encryption-at-rest)。 2. 开启后**必须全量重写存量 Secret**,否则旧 Secret 仍明文: ```bash kubectl get secrets -A -o json | kubectl replace -f - ``` 3. 验证(应只剩 `k8s:enc:aescbc:v1:` 前缀的密文): ```bash kubectl get secrets --all-namespaces -o json | grep -v 'k8s:enc:aescbc' || echo "all encrypted" ``` 覆盖 `dsh-key-*`(每用户 API key)、`dsh-pg`(DB DSN)、`dsh-secret`(共享加密密钥)。 ### 8.3 可观测(Prometheus + Loki + 告警) - **指标**:控制面已可暴露 `/metrics`(未接入则用 prom-client 加一个);ACK 托管 Prometheus (ARMS)在控制台开通后,用 ServiceMonitor 抓 `dsh-orchestrator` 的 3080。 关键指标:控制面副本数、Postgres 连接数、每用户 Pod 数、崩溃计数。 - **日志**:Loki + Promtail(或 ACK SLS)接入控制面与每用户 Pod 日志。 - **告警规则**(Prometheus):控制面副本 < 3、Postgres 不可写、DSH Pod 崩溃率、NAS 容量水位。 示例(自建 Prometheus 时用): ```yaml apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: { name: dsh-alerts, namespace: dsh } spec: groups: - name: dsh rules: - alert: DshControlPlaneDown expr: count(up{app="dsh-orchestrator"}) < 3 for: 5m - alert: DshPostgresUnavailable expr: pg_up == 0 for: 2m ``` 注:托管 Prometheus/Loki 实际接入需在 ACK 控制台开通,本轮交付清单与步骤。 ### 8.4 Web 安全审计(代码审计结论,2026-08-23) - **XSS(已修)**:`desktop.html`/`admin.html` 把文件名 `e.name`、插件名/描述 `plugin.name`/`plugin.description`、密钥名 `k.name`、用户名 `u.username` 直接拼进 `innerHTML`/`insertAdjacentHTML`。文件名与插件描述不受字符集约束 → 存储型 XSS。 已加 `esc()` 转义 `<>&"'` 后再拼接。 - **输入校验(已确认安全)**:用户名 `^[a-zA-Z0-9_-]+$`、域名 `isValidDomain` (小写字母数字+连字符,防 nginx 注入)、密钥名 `^[A-Za-z0-9\-_ .]{1,32}$`、路径 `resolveWithinRoot`/`safeFilename`。 - **CSRF(残余低风险,未修)**:会话 cookie 为 `SameSite=None` 后跨站请求也会带 cookie,但所有状态变更端点都是 `application/json` + 无 CORS 头,跨站 fetch 会触发 preflight 被浏览器拦;真正暴露的是无 body 的 `POST /api/auth/logout`(logout CSRF, 低危)。后续若要加固,给状态变更端点加 CSRF token / 自定义头校验。 - **OWASP ZAP 基线扫描**:未在本轮执行(需 ZAP 工具/容器)。已定位的 XSS 已人工修, ZAP 可作为 CI 门禁后续接入。 ### 8.5 联调后期修的 4 个根因 bug(2026-08-23) | 症状 | 根因 | 修复 | |---|---|---| | leader 不接管,lease 挂旧 pod 数小时 | client-node `patchNamespacedLease` 走 JSON Patch(要数组),传对象被 Go 拒 400 被静默吞 | 改 `replaceNamespacedLease` 全量 PUT + RV 乐观并发 | | admin DSH 反复 502 | `endpointFor` 返回 Service DNS,A 记录 ~30s TTL,Pod 重启后这 30s 内仍解析旧 IP | endpointFor 直接返回 `podIP`,每次请求实时读 Pod | | 模型无法请求(EAI_AGAIN) | ACK DNS 是 `node-local-dns`(label `k8s-app=node-local-dns`),NP 规则却选 `k8s-app=kube-dns` → DNS 全被拦 | NP DNS egress 改 `0.0.0.0/0`(可移植) | | keepAlive 池销毁后不可复用 | `agent.destroy()` 只销毁不替换,后续请求首跳全失败 | destroy 后 `new Agent()` 替换,再短连接重试一次 |