背景
在邊緣集群的場景下邊緣節點分布在不同的區域,且邊緣節點和雲端之間是單向網絡,邊緣節點可以訪問雲端節點,雲端節點無法直接訪問邊緣節點,給邊緣節點的運維帶來很大不便,如果可以從雲端SSH登錄到邊緣節點可以簡化節點的運維工作。針對這一需求,SuperEdge 項目擴展了 tunnel 組件的能力,新增了 SSH 模塊,讓用戶可以從雲端 SSH 登錄到邊緣節點。
需求分析
邊緣集群的節點分布在不同的區域,邊緣節點能夠訪問雲端 master 節點,但 master 節點無法訪問邊端區域內的節點,用戶希望通過訪問 master 節點的端口 SSH 登錄到節點實施運維工作。
常規方案
使用 SSH 端口轉發技術可以實現 SSH 運維邊緣節點功能,具體內容如下圖所示:
- 邊緣節點 node-A 和 node-B 通過 SSH 的遠程轉發(ssh -R)將雲端 master-A 節點的 port-A 和 port-B 端口與本地22端口(SSH Server 的端口)綁定
- user 通過SSH的動態轉發(ssh -D)與 master-A 建立 SSH 隧道同時在本地監聽 local-port 端口
- local-port 的請求都會通過 SSH 連接傳到 master-A,由master-A 轉發,例如 SSH 登錄 node-A:ssh -o ProxyCommand=”nc -X 5 -x 127.0.0.1:local-port 127.0.0.1 port-A” root@127.0.0.1 ,127.0.0.1 port-A 就是 master-A 轉發時請求的目標地址。
常規方案的缺點:
- 邊緣節點映射端口管理復雜
如圖2所示,node-A 和node-B 將本地的22端口在 master-A 上映射為不同的端口,SSH 登錄目標節點需要指定其在master-A映射的端口,當邊緣節點數量很多時端口映射管理非常復雜,直接導致方案不可行。 - 請求涉及的多個連接,增加了出錯的概率
以 SSH 登錄 node-A為例,如圖1所示,sshClient->local-port,user->master-A,master-A->port-A,master-A->node-A,node-A->sshServer,共計需要建立5條連接。 - 雲端和邊端的 SSH 維護麻煩
邊端節點和雲端節點的 SSH 連接,需要在邊端節點上執行建立,且連接不具備斷開重連的能力,維護起來比較麻煩。
tunnel 方案
架構設計
- SSH Client 請求 tunnel-cloud service 暴漏到外網的 NodePort-1 端口,service 根據負載均衡策略將請求轉發 tunnel-cloud pod-A 或 tunnel-cloud pod-B。
- 如果請求轉發到 pod-A(R.1.1),由於 node-A 沒有和 pod-A 建立隧道,pod-A 會查詢 coredns 獲取與 node-A 建立隧道的 pod-B 的 podIp,然后將請求轉發到 pod-B(R.1.2)
- pod-B 收到請求后將請求通過隧道轉發到 node-A 的 tunnel-edge,tunnel-edge 將請求轉發到 SSH Server。
本方案的優勢
- 在雲端和邊緣節點間的隧道不會映射端口,避開端口管理。
- 減少了請求過程中的建立連接數,減少了出錯的概率
- 雲端和邊端的隧道具備斷開重連機制,降低維護成本
雲邊隧道的建立
使用 gRPC 開源項目搭建長連接隧道,gRPC 實現斷開重連機制。
- tunnel-edge 向 tunnel-cloud 發送建立 gRPC 連接的請求
tunnel-edge 在向 tunnel-cloud 的 NodePort-2 發送建立連接的請求,請求中攜帶了所在節點的節點名和 token 信息,tunnel-cloud service 根據負載均衡策略將請求轉發到 tunnel-cloud pod,如圖3所示,將請求轉發到 tunnel-cloud pod-B - tunnel-cloud 向 coredns 注冊本 pod 的 podIp 和 tunnel-edge 所在的節點的節點名信息
tunnel-cloud 驗證 tunnel-edge 請求信息中的 token 信息,驗證通過后,節點請求信息中的節點名和本 pod的 podIp 寫入到 coredns - tunnel-cloud 返回 gRPC 連接建立成功的消息
- tunnel-edge 和 tunnel-cloud 之間通過 gRPC 長連接發送自定義協議消息 StreamMsg
關於自定義協議消息的字段定義請參考 一文讀懂SuperEdge雲邊隧道
tunnel-cloud 轉發的實現
-
SSH Client 請求 tunnel-cloud service 的 NodePort-1端口,發送 method 為 Connect的http 請求
SSH Client 通過工具 netcat 或 crokscrew 發送的 method 為 CONNECT HTTP 請求,即 ProxyCommand 的內容(ProxyCommand= "nc -X connect -x tunnel-cloudIp:NodePort-1 node-A 22" ),參數的定義如下:- -X: 參數為協議的類型,這里指定的 connect 是 HTTP CONNECT
- -x: HTTP Server 的ip地址和端口,這里指定的 tunnel-cloudIp:NodePort-1 是 tunnel-cloud service 暴漏到外網的ip和端口
- node-A 為邊端節點的節點名
- 22 為邊端節點 SSH Server 的監聽的端口
tunnel-cloud service 根據負載均衡策略將 SSH Client 的請求轉發到 tunnel-cloud pod,如架構設計圖3所示如果轉發到 tunnel-cloud pod-A,tunnel-cloud 的 HTTP Server 收到的消息為 CONNECT node-A:22 HTTP/1.0 ,其中 node-A 為邊端節點的節點名,22為邊端節點 SSH Server 監聽的端口,由於node-A沒有與tunnel-cloud pod-A 建立雲邊隧道,因此 HTTP Server 會請求 coredns 獲取 node-A 節點名對應的 tunnel-cloud 的 podIp,即為 tunnel-cloud pod-B , pod-A 會把請求轉發給 pod-B
-
tunnel-cloud 向 tunnel-edge 發送自定義協議消息(StreamMsg.Type 為 connecting)
tunnel-cloud 會根據 HTTP CONNECT 的請求信息中獲取雲端和邊端節點的隧道,並通過雲邊隧道向 tunnel-edge 發送自定義協議消息用於與 SSH Server 建立 TCP 連接,類型為 connecting -
tunnel-edge 向 SSH Server 發送建立 TCP 連接的消息
tunnel-edge 在接收到 connecting 類型的自定義協議消息之后根據消息中 SSH Server 的ip和 port 發送建立TCP連接的請求 -
SSH Server 返回 TCP 連接建立成功的消息給 tunnel-edge
-
Tunnel-edge 返回自定義協議消息(StreamMsg.Type 為 conneted)給 tunnel-cloud
tunnel-edge 在收到連接建立成功的消息后向 tunnel-cloud 發送一個 TCP 連接已建立的類型 connected 的自定義協議的消息 -
tunnel-cloud 返回 SSH Client 狀態為 200 的 reponse 消息
tunnel-cloud 在接收到 connected 的自定協議消息后會 SSH Client 返回一個狀態碼為 200的消息: HTTP/1.1 200 Connection established -
SSH Client 向 tunnel-cloud 發送 SSH 協議的消息
SSH Client在接收到狀態碼為200的響應消息后,使用和 tunnel-cloud 已經建立的隧道發送 SSH 協議數據 -
tunnel-cloud 將 SSH 協議的消息封裝為自定義協議消息(StreamMsg.Type 為 content)發送給 tunel-edge
-
tunnel-edge 將 SSH 協議消息通過TCP連接發送給 SSH Server
-
SSH Server 將 SSH 協議消息發送給 tunnel-edge
-
tunnel-edge 將 SSH 協議消息封裝為 content 類型的自定義消息發送給 tunnel-cloud
-
tunnel-cloud 將 SSH 協議消息返回給 SSH Client
SSH登錄節點
前置條件
安裝 corkscrew,Mac 直接使用 brew 安裝
brew install corkscrew
或者,安裝netcat(centos)
yum install -y netcat
SSH 登錄節點
SSH 登錄邊緣節點 node-A-1,可以使用下面的命令:
ssh -o ProxyCommand= "corkscrew masterIP cloud-port node-A-1 22" root@127.0.0.1
或者
ssh -o ProxyCommand= "nc -X connect -x masterIP:cloud-port node-A-1 22" root@127.0.0.1
- materIP: master 節點所在節點的外網ip
- cloud-port: NodePort 端口,對應的 SSH 模塊的 Server 的端口
獲取 cloud-port
kubectl -n edge-system get svc tunnel-cloud -o=jsonpath='{range .spec.ports[*]}{.name}{"\t"}{.nodePort}{"\n"}{end}' | grep ssh | awk '{print $2}'
總結
使用該方案,用戶無需手動搭建,可以快速SSH登錄邊端節點,實施運維工作。同時本方案提供的隧道相對傳統方案更便於維護且提高了穩定性,用戶體驗得到提升。
未來展望
支持從雲端統一SSH 運維邊緣節點是tunnel組件的增強功能,也是對 一文讀懂SuperEdge雲邊隧道 的展望的實現,SuperEdge 開源之后社區小伙伴也對 tunnel 組件提了新的需求,大致如下:
- 支持雲端 apiserver 訪問邊緣端的webhook server
- 支持服務之間的跨區域互訪
合作和開源
雲邊隧道的SSH運維邊緣節點的新特性已經在 SuperEdge release 0.4.0 開源,歡迎大家體驗。我們也會持續提升 Tunnel 的能力,適用更加復雜的邊緣網絡場景,也歡迎對邊緣計算感興趣的公司、組織及個人一起共建 SuperEdge 邊緣容器項目。
