diff --git a/.gitignore b/.gitignore index 993917a..6444092 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .obsidian/ .claude/ -.claudian/ \ No newline at end of file +.claudian/ +.DS_Store \ No newline at end of file diff --git a/technical/xinfra-preview.md b/technical/xinfra-preview.md index 4328bda..f80555d 100644 --- a/technical/xinfra-preview.md +++ b/technical/xinfra-preview.md @@ -45,3 +45,4 @@ graph LR | 3 | [[technical/xinfra-preview/cloud-dm-overview]] | 数据库 SQL 审核平台 | | 4 | [[technical/xinfra-preview/cachecloud-overview]] | Redis 实例管理平台 | | 5 | [[technical/xinfra-preview/ansible-playbook-basics]] | 服务自动化部署工具 | +| 6 | [[technical/xinfra-preview/multi-host-virtual-ports]] | Service 虚拟端口与 kube-proxy 转发机制 | diff --git a/technical/xinfra-preview/multi-host-virtual-ports.md b/technical/xinfra-preview/multi-host-virtual-ports.md new file mode 100644 index 0000000..6f7602f --- /dev/null +++ b/technical/xinfra-preview/multi-host-virtual-ports.md @@ -0,0 +1,241 @@ +--- +tags: [k8s, networking, kube-proxy, service] +create time: 2026-07-07 14:47 +--- + +# K8s Service 与 kube-proxy 虚拟端口 + +## 概述 + +Kubernetes Service 通过虚拟 IP(ClusterIP)和 kube-proxy 的流量转发机制,为一组动态变化的 Pod 提供稳定的网络访问入口——这是理解集群内服务间通信的关键。 + +## 核心概念 + +### ClusterIP 的本质:一个"不存在"的 IP + +当创建一个 ClusterIP 类型的 Service 时,K8s 做了两件事: + +1. **在 etcd 中分配一个虚拟 IP**(来自 `service-cidr`,如 `10.43.0.0/16`),这个 IP **不绑定在任何网卡上**——你在任何节点执行 `ip addr` 都看不到它 +2. **kube-proxy 在每个节点上写入转发规则**,将发往该虚拟 IP + 端口的流量透明地分发到后端 Pod + +> [!info] 虚拟端口 vs 真实端口 +> Service 的 `port`(如 `80`)是虚拟端口——它不存在于任何进程监听列表中。真正监听的是 Pod 内的 `targetPort`(如 `8080`)。kube-proxy 负责在中间做"端口翻译"。 + +```mermaid +graph LR + C[Client Pod] -->|"TCP to 10.43.0.100:80"| VIP["ClusterIP
10.43.0.100:80"] + VIP -->|"iptables/IPVS DNAT"| P1["Pod-A 10.42.1.5:8080"] + VIP -->|"iptables/IPVS DNAT"| P2["Pod-B 10.42.2.8:8080"] + VIP -->|"iptables/IPVS DNAT"| P3["Pod-C 10.42.3.2:8080"] +``` + +### kube-proxy 三种模式对比 + +kube-proxy 是运行在每个节点上的网络代理,它 watch kube-apiserver 上 Service/Endpoints 的变化,然后在本机维护转发规则。 + +| 维度 | iptables | IPVS | nftables(K8s 1.29+ alpha) | +|------|----------|------|---------------------------| +| **实现方式** | 内核 iptables 链规则 | 内核 IPVS 模块(LVS) | nftables(iptables 的继任者) | +| **匹配复杂度** | O(n) 链表遍历 | O(1) 哈希查找 | O(n) → 未来可优化 | +| **大规模性能** | Service 多时规则膨胀,延迟线性增长 | 万级 Service 仍保持稳定 | 介于两者之间,持续演进中 | +| **负载均衡算法** | 随机 / 概率(`nth`) | rr / wrr / lc / sh 等丰富算法 | 随机 / 概率 | +| **连接跟踪** | conntrack 表 | IPVS 自己的连接表 | conntrack | +| **RKE2 默认** | ✅ 是(传统默认) | 需手动启用 | 实验阶段 | + +> **启发问题**:为什么 IPVS 用哈希表而非链表?——iptables 对每条规则做线性匹配,当 Service 数量上千时,一个新连接可能要遍历数千条规则才能命中;IPVS 在内核中维护哈希表,一次查找直接定位,这是本质区别。 + +### 一条请求的完整路径:Pod → Service → 目标 Pod + +```mermaid +sequenceDiagram + participant Client as Client Pod (10.42.1.3) + participant KProxy as 本机 kube-proxy + participant Kernel as 内核网络栈 + participant Target as Target Pod (10.42.2.8:8080) + + Client->>Kernel: ① connect(10.43.0.100:80) + Note over Kernel: CNI 插件已配置路由
10.43.0.0/16 → kube-proxy + KProxy-->>Kernel: ② iptables/IPVS 规则持续 watch + Note over Kernel: ③ DNAT: dst 10.43.0.100:80 → 10.42.2.8:8080 + Note over Kernel: ④ 如果目标 Pod 在其他节点:
通过 CNI overlay 或 host-gw 封装转发 + Kernel->>Target: ⑤ 10.42.2.8:8080 + Target-->>Client: ⑥ 响应原路返回(reverse NAT) +``` + +关键步骤拆解: + +1. **客户端发出请求**:Pod 内的应用调用 `connect(10.43.0.100:80)`,对应用完全透明 +2. **kube-proxy 规则生效**:通过 iptables DNAT 或 IPVS 将目标地址改写为真实 Pod IP + 端口 +3. **跨节点转发**:如果目标 Pod 在另一台机器上,由 CNI 插件(Flannel VXLAN / Calico BGP)封装后送达 +4. **响应返回**:conntrack 记录了 NAT 映射关系,反向包自动做 SNAT 回到 ClusterIP + +### Service 类型扩展 + +| 类型 | 流量来源 | kube-proxy 行为 | 典型场景 | +|------|---------|----------------|---------| +| **ClusterIP**(默认) | 集群内 Pod | 仅在本机 iptables/IPVS 添加规则 | 服务间调用 | +| **NodePort** | 外部 → 任意节点 IP:Port | 在 ClusterIP 基础上额外开放 30000-32767 端口 | 开发测试、快速验证 | +| **LoadBalancer** | 外部 → 云厂商 LB → NodePort → Pod | 依赖云 controller 创建外部 LB | 云上生产环境 | +| **ExternalName** | 集群内 DNS 查询 | kube-proxy 不介入,CoreDNS 返回 CNAME | 引用集群外服务(如 RDS) | + +> [!tip] NodePort 的"隐藏代价" +> NodePort 会在**集群所有节点**上开放端口(即使是没有运行该 Service Pod 的节点)。这意味着外部流量可能先到达一个"空"节点,再经过一次内部转发才能到达目标 Pod——多了一跳延迟。用 `externalTrafficPolicy: Local` 可避免这个问题(但要注意流量不均)。 + +## iptables vs IPVS 深入 + +### iptables 的规则膨胀问题 + +每个 Service 会产生 **N 条 iptables 规则**(N = 后端 Pod 数): + +```bash +# 查看一个 3-Pod Service 生成的 iptables 规则 +iptables -t nat -L KUBE-SERVICES -n | grep my-service +# KUBE-SVC-XXXX tcp -- 0.0.0.0/0 10.43.0.100 tcp dpt:80 +iptables -t nat -L KUBE-SVC-XXXX -n +# KUBE-SEP-AAA statistic mode random probability 0.33333 → Pod-A +# KUBE-SEP-BBB statistic mode random probability 0.50000 → Pod-B +# KUBE-SEP-CCC /* default */ → Pod-C +``` + +> [!warning] 1000 个 Service × 3 Pod = 3000+ 规则 +> 每次新建连接,内核从第一条开始逐条匹配。实测表明:当规则超过 5000 条时,单次连接的 DNAT 延迟会从微秒级劣化到毫秒级,对在线服务影响显著。 + +### IPVS 的优势 + +```bash +# 查看 IPVS 虚拟服务 +ipvsadm -Ln +# TCP 10.43.0.100:80 rr +# → 10.42.1.5:8080 Masq 1 0 +# → 10.42.2.8:8080 Masq 1 0 +# → 10.42.3.2:8080 Masq 1 0 +``` + +| 场景 | iptables | IPVS | +|------|----------|------| +| 100 Service | ~300 条规则,性能无感知差异 | ~100 个 hash entry,无感知差异 | +| 1000 Service | ~3000+ 条规则,连接建立延迟可见劣化 | ~1000 entry,几乎无变化 | +| 10000 Service | 规则更新耗时数秒,`iptables-restore` 阻塞 | 独立于规则数量,毫秒级更新 | +| 负载均衡策略 | 随机/概率 | 支持 rr / wrr / lc / sh / sed / nq | + +> [!tip] 如何在 RKE2 启用 IPVS +> 在 `/etc/rancher/rke2/config.yaml` 中配置: +> ```yaml +> kube-proxy-arg: +> - "proxy-mode=ipvs" +> - "ipvs-scheduler=rr" # 轮询调度 +> ``` +> 需确保节点内核已加载 `ip_vs` 模块:`modprobe ip_vs && lsmod | grep ip_vs` + +## RKE2 中的实践 + +xinfra 基于 RKE2 集群,以下是与 Service / kube-proxy 相关的关键配置: + +```mermaid +graph TB + subgraph "RKE2 集群(xinfra)" + subgraph "网络栈" + Canal["Canal (Calico + Flannel)"] + KProxy["kube-proxy (iptables 模式)"] + CoreDNS["CoreDNS"] + end + subgraph "Service 层" + SVC1["ClusterIP Service"] + SVC2["NodePort Service"] + end + Canal -->|"Pod-to-Pod 网络"| KProxy + KProxy -->|"Service 转发"| SVC1 + KProxy -->|"NodePort 转发"| SVC2 + CoreDNS -->|"DNS 解析"| SVC1 + end +``` + +**默认配置要点**: + +| 配置项 | RKE2 默认值 | 说明 | +|--------|------------|------| +| `cluster-cidr` | `10.42.0.0/16` | Pod IP 地址范围 | +| `service-cidr` | `10.43.0.0/16` | ClusterIP 地址范围 | +| kube-proxy 模式 | `iptables` | 小规模集群足够 | +| CNI 插件 | Canal(Calico + Flannel) | 提供 Pod 网络 + NetworkPolicy | +| CoreDNS | 内置 | Service DNS 自动解析 | + +> [!info] xinfra 七机房部署 +> xinfra 管理七机房的 RKE2 集群。每个集群独立的 `service-cidr`,跨集群服务访问不通过 ClusterIP,而是通过 Wayne 多集群管理平台统一调度——理解这点很重要:**ClusterIP 的作用域是单集群内**。 + +## 常见陷阱与最佳实践 + +### 1. Session Affinity(会话保持) + +默认情况下,kube-proxy 对每个连接做随机/轮询分发。如果你的应用有状态(如 Session 存储在内存),需要开启: + +```yaml +spec: + sessionAffinity: ClientIP # 基于客户端 IP 做会话保持 + sessionAffinityConfig: + clientIP: + timeoutSeconds: 10800 # 3 小时超时 +``` + +> [!warning] Session Affinity 不是银弹 +> 如果后端 Pod 重启,连接会中断。有状态应用更推荐用 StatefulSet + Headless Service 做稳定的网络标识。 + +### 2. Headless Service(无头 Service) + +当你需要**直接获取 Pod IP 而不经过负载均衡**时,使用 Headless Service: + +```yaml +spec: + clusterIP: None # 关键:不分配 ClusterIP + ports: + - port: 80 +``` + +DNS 查询返回的是**所有 Pod 的 IP 列表**,而非单个虚拟 IP。适用于: +- StatefulSet(如 MySQL 主从、Redis Cluster) +- 客户端自己实现负载均衡的场景 + +### 3. externalTrafficPolicy 的选择 + +```yaml +spec: + externalTrafficPolicy: Cluster # 默认:跨节点转发,可能多一跳 + # externalTrafficPolicy: Local # 只转发到本节点 Pod,保留源 IP +``` + +| 策略 | 优点 | 缺点 | +|------|------|------| +| `Cluster`(默认) | 流量均匀分布到所有 Pod | 多一跳延迟,丢失源 IP | +| `Local` | 保留源 IP,低延迟 | 流量分布不均(取决于 LB 分发) | + +### 4. Service 无法访问排查清单 + +当 `curl :` 超时或拒绝连接时: + +```mermaid +flowchart TD + A["Service 不通"] --> B{"kubectl get endpoints
有 IP 吗?"} + B -->|无| C["检查 selector 是否匹配 Pod label
检查 Pod 是否 Ready"] + B -->|有| D{"Pod 内 curl targetPort
通吗?"} + D -->|不通| E["应用未监听 / 端口错误 / readinessProbe 失败"] + D -->|通| F{"CoreDNS 解析正确吗?
nslookup service-name"} + F -->|解析失败| G["检查 CoreDNS Pod 状态
检查 resolv.conf"] + F -->|解析成功| H["检查 kube-proxy 日志
检查 iptables/IPVS 规则是否写入"] +``` + +### 5. 避免的常见错误 + +- **selector typo**:Service 的 `spec.selector` 与 Pod 的 `metadata.labels` 不匹配,导致 Endpoints 为空 +- **端口不匹配**:Service 的 `targetPort` 与容器实际监听端口不一致 +- **NetworkPolicy 阻断**:Calico NetworkPolicy 可能阻止了 Service 到 Pod 的流量 + +## 延伸阅读 + +- [Kubernetes 官方文档 — Service](https://kubernetes.io/docs/concepts/services-networking/service/) +- [Kubernetes 官方文档 — IPVS-based service proxy](https://kubernetes.io/docs/reference/networking/virtual-ips/) +- [深入理解 kube-proxy 的三种模式(推荐)](https://www.qikqiak.com/post/kube-proxy-iptables-ipvs/) + +## 关联笔记 + +- [[technical/xinfra-preview/k8s-rke2-fundamentals]] — K8s 基础概念,包含 Service 类型概览和 Ingress 示例 +- [[technical/xinfra-preview]] — xinfra 预习总览,本笔记的父文档