本文的源码基于 Kubernetes v1.24.0,容器运行时应用 Containerd 1.5,从源码来剖析 kubectl port-forward 的工作原理。
通过 port-forward 流程的剖析,梳理出 kubectl -> api-server -> kubelet -> 容器运行时 的交互,理解 cri 的工作形式。
kubectl
简略创立个 pod:
kubectl run pipy --image flomesh/pipy:latest -n default
在执行 kubectl forward
时增加参数 -v 9
打印日志。
kubectl port-forward pipy 8080 -v 9
...
I0807 21:45:58.457986 14495 round_trippers.go:466] curl -v -XPOST -H "User-Agent: kubectl/v1.24.3 (darwin/arm64) kubernetes/aef86a9" -H "X-Stream-Protocol-Version: portforward.k8s.io" 'https://192.168.1.12:6443/api/v1/namespaces/default/pods/pipy/portforward'
I0807 21:45:58.484013 14495 round_trippers.go:553] POST https://192.168.1.12:6443/api/v1/namespaces/default/pods/pipy/portforward 101 Switching Protocols in 26 milliseconds
I0807 21:45:58.484029 14495 round_trippers.go:570] HTTP Statistics: DNSLookup 0 ms Dial 0 ms TLSHandshake 0 ms Duration 26 ms
I0807 21:45:58.484035 14495 round_trippers.go:577] Response Headers:
I0807 21:45:58.484040 14495 round_trippers.go:580] Upgrade: SPDY/3.1
I0807 21:45:58.484044 14495 round_trippers.go:580] X-Stream-Protocol-Version: portforward.k8s.io
I0807 21:45:58.484047 14495 round_trippers.go:580] Date: Sun, 07 Aug 2022 13:45:58 GMT
I0807 21:45:58.484051 14495 round_trippers.go:580] Connection: Upgrade
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
从日志能够看到申请的地址为 /api/v1/namespaces/default/pods/pipy/portforward
,其中 portforward
为 pod 资源的子资源。
这里应用的协定是 spdy。
kubectl
此时会监听本地端口,同时应用 pod 子资源 portforward 的 url 创立到 api-server 的连贯。
当本地端口有连贯接入时,kubectl
会 一直地在两个连贯间拷贝数据。
参考源码:
- staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go:389
- staging/src/k8s.io/client-go/tools/portforward/portforward.go:242
- staging/src/k8s.io/client-go/tools/portforward/portforward.go:330
api-server
pod 的三个子资源 exec、attach 和 portforward,对这三个资源的操作都会代理有对应 node 的 kubetlet server 进行解决。
api-server 在接管到拜访 pod 子资源 portforward 的申请后,通过 pod 及其所在 node 的信息,获取拜访该 node 上 kubelet server 的 url。
而后将拜访 pod 的 portforward 的申请,代理到 kubelet server。
参考源码
- pkg/registry/core/pod/rest/subresources.go:185
kubelet
portforward 申请来到了 pod 所在节点的 kubelet server,在 kubelet server 中,有几个用于调试的 endpoint,portforward 便是其中之一:
/run/{podNamespace}/{podID}/{containerName}
/exec/{podNamespace}/{podID}/{containerName}
/attach/{podNamespace}/{podID}/{containerName}
/portforward/{podNamespace}/{podID}
/containerLogs/{podNamespace}/{podID}/{containerName}
/runningpods/
kubelet server 收到申请后,首先会通过 RuntimeServiceClient
发送 gRCP 申请到容器运行时的接口(/runtime.v1alpha2.RuntimeService/PortForward
)获取容器运行时 streaming server 解决 pordforward 申请的 url。
拿到 portforward streaming 的 url 之后,kubelet server 将申请代理到该 url。
参考源码
- pkg/kubelet/server/server.go:463
- pkg/kubelet/server/server.go:873
- pkg/kubelet/cri/streaming/portforward/portforward.go:46
- pkg/kubelet/cri/streaming/server.go:111
cri
这里以 Containerd 为例。
Containerd 在启动时会启动 runtime service 和 image service。前者是负责容器相干的操作,后者负责镜像相干的操作。
kubelet 获取用于端口转发的 streaming url,就是调用了 runtime service 的 gRPC 接口实现的。
除了两个 gRPC service 以外,还加载了一系列插件。这些插件中,其中有一个是 cri service。
cri service 会启动 streaming server。这个 server 会响应 /exec
、/attach
和 /portforward
的 stream 申请。
portforward 反对两种操作系统 linux 和 windows:sandbox_portforward_linux.go
和 sandbox_portforward_windows.go
。
在 linux 上,在 pod 所在的 network namespace 中应用地址 localhost
创立到指标端口的连贯。而后在 streaming server 的连贯和该连贯之间拷贝数据,实现数据的传递。
在 windows 上,是通过 wincat.exe
应用地址 127.0.0.1
创立到指标端口的连贯。
参考源码
- pkg/cri/streaming/server.go:149
- pkg/cri/server/streaming.go:69
- pkg/cri/server/service.go:138
- pkg/cri/server/sandbox_portforward_linux.go:34
总结
联合源码剖析对 port-foward 工作原理的梳理,置信对 cri 的工作形式也有了肯定的理解。本文是以容器运行时 Containerd 为例,不同的容器运行时尽管实现了 cri,然而实现的细节上也会有所差别。
比方在 port-forward 的实现上,Kubernetes v1.23.0 版本中的 docker shim(1.24 中被移除)中,是应用nsenter
进入 pod 所在的 network namespace 中通过 socat
实现的端口转发。
文章对立公布在公众号
云原生指北