源碼解析 kubectl port-forward 工作原理
本文的源碼基於 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[1]
-
staging/src/k8s.io/client-go/tools/portforward/portforward.go:242[2]
-
staging/src/k8s.io/client-go/tools/portforward/portforward.go:330[3]
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[4]
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[5]
-
pkg/kubelet/server/server.go:873[6]
-
pkg/kubelet/cri/streaming/portforward/portforward.go:46[7]
-
pkg/kubelet/cri/streaming/server.go:111[8]
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[9]
-
pkg/cri/server/streaming.go:69[10]
-
pkg/cri/server/service.go:138[11]
-
pkg/cri/server/sandbox_portforward_linux.go:34[12]
總結
結合源碼分析對 port-foward 工作原理的梳理,相信對 cri 的工作方式也有了一定的瞭解。本文是以容器運行時 Containerd 爲例,不同的容器運行時雖然實現了 cri,但是實現的細節上也會有所差異。
比如在 port-forward 的實現上,Kubernetes v1.23.0 版本中的 docker shim(1.24 中被移除)[13] 中,是使用nsenter
進入 pod 所在的 network namespace 中通過 socat
完成的端口轉發。
參考資料
[1]
staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go:389: https://github.com/kubernetes/kubernetes/tree/release-1.24/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go#L389
[2]
staging/src/k8s.io/client-go/tools/portforward/portforward.go:242: https://github.com/kubernetes/kubernetes/tree/release-1.24/staging/src/k8s.io/client-go/tools/portforward/portforward.go#L242
[3]
staging/src/k8s.io/client-go/tools/portforward/portforward.go:330: https://github.com/kubernetes/kubernetes/tree/release-1.24/staging/src/k8s.io/client-go/tools/portforward/portforward.go#L330
[4]
pkg/registry/core/pod/rest/subresources.go:185: https://github.com/kubernetes/kubernetes/tree/release-1.24/pkg/registry/core/pod/rest/subresources.go#L185
[5]
pkg/kubelet/server/server.go:463: https://github.com/kubernetes/kubernetes/tree/release-1.24/pkg/kubelet/server/server.go#L463
[6]
pkg/kubelet/server/server.go:873: https://github.com/kubernetes/kubernetes/tree/release-1.24/pkg/kubelet/server/server.go#L873
[7]
pkg/kubelet/cri/streaming/portforward/portforward.go:46: https://github.com/kubernetes/kubernetes/tree/release-1.24/pkg/kubelet/cri/streaming/portforward/portforward.go#L46
[8]
pkg/kubelet/cri/streaming/server.go:111: https://github.com/kubernetes/kubernetes/tree/release-1.24/pkg/kubelet/cri/streaming/server.go#L111
[9]
pkg/cri/streaming/server.go:149: https://github.com/containerd/containerd/tree/release/1.5/pkg/cri/streaming/server.go#L149
[10]
pkg/cri/server/streaming.go:69: https://github.com/containerd/containerd/tree/release/1.5/pkg/cri/server/streaming.go#L69
[11]
pkg/cri/server/service.go:138: https://github.com/containerd/containerd/tree/release/1.5/pkg/cri/server/service.go#L138
[12]
pkg/cri/server/sandbox_portforward_linux.go:34: https://github.com/containerd/containerd/tree/release/1.5/pkg/cri/server/sandbox_portforward_linux.go#L34
[13]
Kubernetes v1.23.0 版本中的 docker shim(1.24 中被移除): https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/kubelet/dockershim/docker_streaming_others.go#L43
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/oDSTJBUh1B-6mf7DX7g4Zg