如何解決 K8s 在云邊協同下的運維監控挑戰
背景
伴隨著 5G、IoT 等技術的快速發展,邊緣計算被越來越廣泛地應用于電信、媒體、運輸、物流、農業、零售等行業和場景中,成為解決這些領域數據傳輸效率的關鍵方式。與此同時,邊緣計算形態、規模、復雜度的日益增長,邊緣計算領域的運維手段、運維能力對邊緣業務創新速度的支撐日趨乏力。于是,Kubernetes 迅速成為邊緣計算的關鍵要素,幫助企業在邊緣更好地運行容器,最大化利用資源、縮短研發周期。
但是,如果將原生 Kubernetes 直接應用到邊緣計算場景下,仍然需要解決諸多問題,比如云與邊一般位于不同網絡平面,同時邊緣節點普遍位于防火墻內部,采用云(中心)邊協同架構,將導致原生 K8s 系統的運維監控能力面臨如下挑戰:
K8s 原生運維能力缺失(如 kubectl logs/exec 等無法執行)
社區主流監控運維組件無法工作(如 Prometheus/metrics-server )
為了幫助企業解決原生 Kubernetes 在邊緣場景下關于應用生命周期管理、云邊網絡連接、云邊端運維協同、異構資源支持等方方面面的挑戰,基于 K8s 實現的邊緣計算云原生開源平臺 OpenYurt 應運而生,其也是 CNCF 在邊緣云原生版圖中的重要組成部分。本文將詳細介紹,作為 OpenYurt 核心組件之一的 Yurt-Tunnel 如何是擴展原生 K8s 系統在邊緣場景下相關能力的。
Yurt-Tunnel 設計思路
由于邊緣可以訪問云端,因此可以考慮在云邊構建可以反向穿透的隧道,從而保證云(中心)可以基于隧道主動訪問邊緣。當時我們也調查了很多開源的隧道方案,從能力以及生態兼容性等方面,最后我們選擇基于 ANP設計并實現了 Yurt-Tunnel 整體解決方案,具備安全,非侵入、可擴展、傳輸高效等優點。
實現方式
在 K8s 云邊一體化架構中構建一個安全、非侵入、可擴展的反向通道解決方案,方案中至少需要包括如下能力。
云邊隧道構建
隧道兩端證書的自管理
云端組件請求被無縫倒流到隧道
Yurt-tunnel 的架構模塊如下圖:
3.1 云邊隧道構建
當邊緣的 yurt-tunnel-agent 啟動時,會根據訪問地址與 yurt-tunnel-server 建立連接并注冊,并周期性檢測連接的健康狀態以及重建連接等。
- # https://github.com/openyurtio/apiserver-network-proxy/blob/master/pkg/agent/client.go#L189# yurt-tunnel-agent的注冊信息:"agentID": {nodeName}"agentIdentifiers": ipv4={nodeIP}&host={nodeName}"
當 yurt-tunnel-server 收到云端組件的請求時,需要把請求轉發給對應的 yurt-tunnel-agent 。因為除了轉發初始請求之外,該請求 session 后續還有數據返回或者數據的持續轉發(如 kubectl exec )。因此需要雙向轉發數據。同時需要支持并發轉發云端組件的請求,意味需要為每個請求生命周期建立一個獨立的標識。所以設計上一般會有兩種方案。
方案 1: 初始云邊連接僅通知轉發請求,tunnel-agent 會和云端建立新連接來處理這個請求。通過新連接可以很好的解決請求獨立標識的問題,同時并發也可以很好的解決。但是為每個請求都需要建立一個連接,將消耗大量的資源。
方案 2: 僅利用初始云邊連接來轉發請求,大量請求為了復用同一條連接,所以需要為每個請求進行封裝,并增加獨立標識,從而解決并發轉發的訴求。同時由于需要復用一條連接,所以需要解耦連接管理和請求生命周期管理,即需要對請求轉發的狀態遷移進行獨立管理。該方案涉及到封包解包,請求處理狀態機等,方案會復雜一些。
OpenYurt 選擇的 ANP 組件,采用的是上述方案2,這個和我們的設計初衷也是一致的。
- # https://github.com/openyurtio/apiserver-network-proxy/blob/master/konnectivity-client/proto/client/client.pb.go#L98# 云邊通信的數據格式以及數據類型type Packet struct { Type PacketType `protobuf:"varint,1,opt,name=type,proto3,enum=PacketType" json:"type,omitempty"` // Types that are valid to be assigned to Payload: // *Packet_DialRequest // *Packet_DialResponse // *Packet_Data // *Packet_CloseRequest // *Packet_CloseResponse Payload isPacket_Payload `protobuf_oneof:"payload"`}
請求轉發鏈路構建封裝在 Packet_DialRequest 和 Packet_DialResponse 中,其中 Packet_DialResponse.ConnectID 用于標識 request ,相當于 tunnel 中的 requestID。請求以及關聯數據封裝在 Packet_Data 中。Packet_CloseRequest 和 Packet_CloseResponse 用于轉發鏈路資源回收。具體可以參照下列時序圖:
RequestInterceptor 模塊的作用
從上述分析可以看出,yurt-tunnel-server 轉發請求之前,需要請求端先發起一個Http Connect 請求來構建轉發鏈路。但是為 Prometheus、metrics-server 等開源組件增加相應處理會比較困難,因此在 Yurt-tunnel-server 中增加請求劫持模塊 Interceptor ,用來發起 Http Connect 請求。相關代碼如下:
- # https://github.com/openyurtio/openyurt/blob/master/pkg/yurttunnel/server/interceptor.go#L58-82 proxyConn, err := net.Dial("unix", udsSockFile) if err != nil { return nil, fmt.Errorf("dialing proxy %q failed: %v", udsSockFile, err) } var connectHeaders string for _, h := range supportedHeaders { if v := header.Get(h); len(v) != 0 { connectHeaders = fmt.Sprintf("%s\r\n%s: %s", connectHeaders, h, v) } } fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s%s\r\n\r\n", addr, "127.0.0.1", connectHeaders) br := bufio.NewReader(proxyConn) res, err := http.ReadResponse(br, nil) if err != nil { proxyConn.Close() return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v", addr, udsSockFile, err) } if res.StatusCode != 200 { proxyConn.Close() return nil, fmt.Errorf("proxy error from %s while dialing %s, code %d: %v", udsSockFile, addr, res.StatusCode, res.Status) }
3.2 證書管理
為了保證云邊通道的長期安全通信,同時也為了支持 https 請求轉發,yurt-tunnel 需要自行生成證書并且保持證書的自動輪替。具體實現如下:
- # 1. yurt-tunnel-server證書:# https://github.com/openyurtio/openyurt/blob/master/pkg/yurttunnel/pki/certmanager/certmanager.go#L45-90- 證書存儲位置: /var/lib/yurt-tunnel-server/pki- CommonName: "kube-apiserver-kubelet-client" // 用于kubelet server的webhook校驗- Organization: {"system:masters", "openyurt:yurttunnel"} // 用于kubelet server的webhook校驗和yurt-tunnel-server證書的auto approve- Subject Alternate Name values: {x-tunnel-server-svc, x-tunnel-server-internal-svc的ips和dns names}- KeyUsage: "any"# 2. yurt-tunnel-agent證書:# https://github.com/openyurtio/openyurt/blob/master/pkg/yurttunnel/pki/certmanager/certmanager.go#L94-112- 證書存儲位置: /var/lib/yurt-tunnel-agent/pki- CommonName: "yurttunnel-agent"- Organization: {"openyurt:yurttunnel"} // 用于yurt-tunnel-agent證書的auto approve- Subject Alternate Name values: {nodeName, nodeIP}- KeyUsage: "any"# 3. yurt-tunnel證書申請(CSR)均由yurt-tunnel-server來approve# https://github.com/openyurtio/openyurt/blob/master/pkg/yurttunnel/pki/certmanager/csrapprover.go#L115- 監聽csr資源- 過濾非yurt-tunnel的csr(Organization中沒有"openyurt:yurttunnel")- approve還未Approved的csr# 4. 證書自動輪替處理# https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/client-go/util/certificate/certificate_manager.go#L224
3.3 無縫導流云端組件請求到隧道
因為需要無縫把云端組件的請求轉發到 yurt-tunnel-server ,也意味不需要對云端組件進行任何修改。因此需要對云端組件的請求進行分析,目前組件的運維請求主要有以下兩種類型:
類型1: 直接使用 IP 地址訪問,如: http://{nodeIP}:{port}/{path}
類型2: 使用域名訪問, 如: http://{nodeName}:{port}/{path}
針對不同類型請求的導流,需要采用不同方案。
方案1: 使用 iptables dnat rules 來保證類型1的請求無縫轉發到 yurt-tunnel-server
- # 相關iptables rules維護代碼: https://github.com/openyurtio/openyurt/blob/master/pkg/yurttunnel/iptables/iptables.go# yurt-tunnel-server維護的iptables dnat rules如下:[root@xxx /]# iptables -nv -t nat -L OUTPUTTUNNEL-PORT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* edge tunnel server port */[root@xxx /]# iptables -nv -t nat -L TUNNEL-PORTTUNNEL-PORT-10255 tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10255 /* jump to port 10255 */TUNNEL-PORT-10250 tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:10250 /* jump to port 10250 */[root@xxx /]# iptables -nv -t nat -L TUNNEL-PORT-10255RETURN tcp -- * * 0.0.0.0/0 127.0.0.1 /* return request to access node directly */ tcp dpt:10255RETURN tcp -- * * 0.0.0.0/0 172.16.6.156 /* return request to access node directly */ tcp dpt:10255DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* dnat to tunnel for access node */ tcp dpt:10255 to:172.16.6.156:10264
方案2: 使用 dns 域名解析 nodeName 為 yurt-tunnel-server 的訪問地址,從而使類型 2 請求無縫轉發到 yurt-tunnel
- # x-tunnel-server-svc和x-tunnel-server-internal-svc的不同用途: - x-tunnel-server-svc: 主要expose 10262/10263端口,用于從公網訪問yurt-tunnel-server。如yurt-tunnel-agent - x-tunnel-server-internal-svc: 主要用于云端組件從內部網絡訪問,如prometheus,metrics-server等# dns域名解析原理:1. yurt-tunnel-server向kube-apiserver創建或更新yurt-tunnel-nodes configmap, 其中tunnel-nodes字段格式為: {x-tunnel-server-internal-svc clusterIP} {nodeName},確保記錄了所有nodeName和yurt-tunnel-server的service的映射關系2. coredns pod中掛載yurt-tunnel-nodes configmap,同時使用host插件使用configmap的dns records3. 同時在x-tunnel-server-internal-svc中配置端口映射,10250映射到10263,10255映射到102644. 通過上述的配置,可以實現http://{nodeName}:{port}/{path}請求無縫轉發到yurt-tunnel-servers
云端請求擴展:
如果用戶需要訪問邊緣的其他端口(10250 和 10255 之外),那么需要在 iptables 中增加相應的 dnat rules 或者 x-tunnel-server-internal-svc 中增加相應的端口映射,如下所示:
- # 例如需要訪問邊緣的9051端口# 新增iptables dnat rule:[root@xxx /]# iptables -nv -t nat -L TUNNEL-PORTTUNNEL-PORT-9051 tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:9051 /* jump to port 9051 */[root@xxx /]# iptables -nv -t nat -L TUNNEL-PORT-9051RETURN tcp -- * * 0.0.0.0/0 127.0.0.1 /* return request to access node directly */ tcp dpt:9051RETURN tcp -- * * 0.0.0.0/0 172.16.6.156 /* return request to access node directly */ tcp dpt:9051DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* dnat to tunnel for access node */ tcp dpt:9051 to:172.16.6.156:10264# x-tunnel-server-internal-svc中新增端口映射spec: ports: - name: https port: 10250 protocol: TCP targetPort: 10263 - name: http port: 10255 protocol: TCP targetPort: 10264 - name: dnat-9051 # 新增映射 port: 9051 protocol: TCP targetPort: 10264
當然上述的 iptables dnat rules 和 service 端口映射,都是由 yurt-tunnel-server 自動更新。用戶只需要在 yurt-tunnel-server-cfg configmap 中增加端口配置即可。具體如下:
- # 注意:由于證書不可控因素,目前新增端口只支持從yurt-tunnel-server的10264轉發apiVersion: v1data: dnat-ports-pair: 9051=10264 # 新增端口=10264(非10264轉發不支持)kind: ConfigMapmetadata: name: yurt-tunnel-server-cfg namespace: kube-system
近期規劃
支持 kube-apiserver 的 EgressSelector 功能
驗證 yurt-tunnel-server 多實例部署驗證
支持 yurt-tunnel-agent 配置多個 yurt-tunnel-server 地址
支持證書存儲目錄自定義
支持證書 Usage 定義更精細化,保證證書使用范圍可控
支持 yurt-tunnel-server 訪問地址變化后,yurt-tunnel-server 證書可自動更新
支持 yurt-tunnel-agent 對 yurt-tunnel-server 訪問地址的自動刷新
支持非 NodeIP/NodeName 類型的請求轉發(如非主機網絡 Pod 的云訪問邊)
支持通過 Tunnel 由邊緣 Pod 訪問云端 Pod
支持 yurt-tunnel 的獨立部署(非綁定 k8s )
支持更多協議轉發,如 gRPC, websocket, ssh 等