kubectl port-forward

22 阅读5分钟

kubectl port-forward -n ns7 pod/ns7-netshoot-http-t58zl 5000:8080

企业微信截图_72fe3cd8-6e6f-419a-962f-2ffd2a9e5c84.png

好——直奔主题,kubectl port-forward 的工作原理从 L7 到 L2 用一句话到细节全讲清楚,并且把你之前抓到的 lo(localhost)流量和 ARP 日志对应起来,方便你马上验证和排查。


一句结论(最重要)

kubectl port-forward 在本地打开一个监听端口(默认 127.0.0.1:localport),把到这个监听端口的 TCP 流量通过与 Kubernetes API Server 建立的**升级流(SPDY / websocket)**通道,代理到目标 Pod 所在节点的 kubelet,kubelet 再把流量注入到该 Pod 的网络命名空间和目标端口。也就是说:流量走的是:你的程序 ←→ kubectl(本地) ←→ apiserver ←→ kubelet ←→ pod。


分步细节(按实际数据流)

  1. 本地监听(local kubectl)

    • 你运行 kubectl port-forward pod/X 5000:8080 时,kubectl 在本地启动一个 TCP listening socket(默认 127.0.0.1:5000,除非用 --address 指定)。
    • 客户端(浏览器 / curl / 程序)向本地 127.0.0.1:5000 建立 TCP 连接 —— 这正是你在 pod 内用 tcpdump -i anylo 上看到的 127.0.0.1:50806 -> 127.0.0.1:8080(kubectl 作为客户端连接到本地映射的 8080 端口,显示为 loopback 事务)。
  2. 创建到 API Server 的流式通道

    • kubectlapiserver 请求 Pod 的 portforward 子资源(HTTP path /api/v1/namespaces/{ns}/pods/{pod}/portforward),并通过 HTTP 升级(SPDY 或 websocket)建立若干双向数据流(stream)。
    • 每个要转发的端口通常会对应一个或两个 SPDY stream(一个用于数据,一个用于 error/control),协议负责在这些 stream 里封装原始 TCP 字节。
  3. apiserver → kubelet 转发

    • API Server 接到这个 upgrade 请求后,会把流量转发到目标 Pod 所在节点的 kubelet(apiserver 知道 Pod 在哪台 node)。内部它会把这个 portforward 请求 proxy/upgrade 到 kubelet 的对应 handler(有时 apiserver 直接充当中继,实际实现可能与版本有关,但逻辑是一致:apiserver 对外接入,向 kubelet 建立流式连接并中继数据)。
    • 关键点:数据不会直接 TCP 直连 Pod,而是走 apiserver ↔ kubelet 的流式通道。
  4. kubelet 注入 Pod 网络命名空间

    • kubelet 在 node 上接收这个升级流,将收到的字节写入到对应 Pod 在该 node 的网络命名空间中的目标端口(相当于把字节放到 pod 的 socket)。同样,从 Pod 返回的字节会被 kubelet 读取,并通过流式通道回传给 apiserver,再到 kubectl,最后写到本地的 socket 给客户端。

回到你看到的抓包(和说明)

  • 你在 kubectl port-forward 的终端里看到大量 lo(127.0.0.1)TCP 报文 —— 这是本地进程(curl) ↔ kubectl 本地 listener的往返。显示 127.0.0.1:5000(本地监听)和 127.0.0.1:8080(kubectl 内部映射/表示)是正常的:port-forward 把远端 Pod 的端口“呈现”为本地 loopback 端口
  • 你不会在 pod 的网络接口直接看到来自客户端的原始 IP(因为流量被代理并封装/解封装在 apiserver↔kubelet 链路上)。
  • 你在主机 eth0 上看到的 ARP 请求与 port-forward 无直接关系,那是宿主机与其它网络的 ARP(例如默认网关/外网访问),不是 port-forward 的流量。

常见误解与限制(一句话总结每点)

  • 不是直连:port-forward 不是把你的本地端口直接 NAT 到 Pod;流量会经过 apiserver 和 kubelet(中转)。
  • 延迟与吞吐:因为两次中转(本地→apiserver、apiserver→kubelet)和编码开销,高吞吐或低延迟场景不适合长期用 port-forward
  • 默认只绑定 localhost:出于安全性,默认 kubectl 只监听 127.0.0.1,如果需要外部访问可用 --address 0.0.0.0(注意风险)。
  • 仅 TCP(通常) :port-forward 传输原始 TCP 字节流;UDP 不被直接支持(因为需要连接语义)。
  • 身份与授权:apiserver 会验证 kubectl 的凭证与 Pod 的权限(RBAC)。
  • 多端口/多连接:可以同时转发多个端口(为每个端口建立不同的 stream)。

如何抓到“apiserver ↔ kubelet”的真实封包(如果你想证实中转)

  1. 抓 kubectl ↔ apiserver(在运行 kubectl 的机器上)

    sudo tcpdump -i <iface> host <apiserver-ip> and tcp port <apiserver-port>
    

    你会看到 TLS 流量(HTTPS 升级),里面封装着 SPDY/websocket 数据流。内容看不出 TCP payload,但能看到连接和流量存在。

  2. 抓 apiserver ↔ kubelet(在 apiserver 或 node 上)
    在 apiserver 节点或目标 node 上抓到对应端口(kubelet 可能是 10250)会看到升级流和数据包。把流量解码成 SPDY/Geneve 需要专门工具,但你能确认存在中继链接与流量字节量。

  3. 抓 pod 真实流量(在 pod node 上)
    在 pod 所在 node,抓 pod 的 veth/bridge 接口可以看到 kubelet 注入后的原始 TCP 流量(由 kubelet 在 node 内部与容器网络命名空间交互生成)。


实务建议(你要排查/验证时直接用这些命令)

  • 验证本地 loopback 流量(你已经做,对):

    tcpdump -i lo -nn tcp port 5000 or tcp port 8080
    
  • 在运行 kubectl 的机器上看到到 apiserver 的 TLS 流:

    sudo tcpdump -i any host <APISERVER_IP> and tcp port 6443
    
  • 在 pod node 抓 pod 端口流量:

    sudo tcpdump -i <veth-or-bridge> tcp port 8080
    
  • 查看 kubectl port-forward 输出日志(增加 -v=9 可见更多 debug):

    kubectl --v=9 port-forward pod/ns7-netshoot-http-t58zl 5000:8080 -n ns7
    

最后,针对你现有抓包的要点

  • 你抓到的 lo 流量(127.0.0.1 段)就是本地客户端 ↔ kubectl 的往返,表现为标准的 TCP 三次握手、HTTP GET/response(你看见 Python 的 SimpleHTTP 返回 200)。完全正常。
  • 如果想看到“远端 Pod 实际接收到的数据”,在 pod 所在 node 上抓 pod 的接口(或在 pod 内 tcpdump)可以看到 kubelet 注入的那端真实 TCP 流量(会有源/目的为容器 IP)。
  • ARP 报文和 eth0 上看见的请求与 port-forward 的 loopback TCP 无关系,是宿主机层面的 L2 活动。