天天看點

Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群

作者|何淋波(新勝)

Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群

背景

伴随着 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 整體解決方案,具備安全,非侵入、可擴充、傳輸高效等優點。

Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群

實作方式

在 K8s 雲邊一體化架構中建構一個安全、非侵入、可擴充的反向通道解決方案,方案中至少需要包括如下能力。

  • 雲邊隧道建構
  • 隧道兩端證書的自管理
  • 雲端元件請求被無縫倒流到隧道

Yurt-tunnel 的架構子產品如下圖:

Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群

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 用于轉發鍊路資源回收。具體可以參照下列時序圖:
Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群
  • 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: 使用域名通路, 如: {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 OUTPUT
TUNNEL-PORT  tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* edge tunnel server port */

[root@xxx /]# iptables -nv -t nat -L TUNNEL-PORT
TUNNEL-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-10255
RETURN     tcp  --  *      *       0.0.0.0/0            127.0.0.1            /* return request to access node directly */ tcp dpt:10255
RETURN     tcp  --  *      *       0.0.0.0/0            172.16.6.156         /* return request to access node directly */ tcp dpt:10255
DNAT       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 records
3. 同時在x-tunnel-server-internal-svc中配置端口映射,10250映射到10263,10255映射到10264
4. 通過上述的配置,可以實作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-PORT
TUNNEL-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-9051
RETURN     tcp  --  *      *       0.0.0.0/0            127.0.0.1            /* return request to access node directly */ tcp dpt:9051
RETURN     tcp  --  *      *       0.0.0.0/0            172.16.6.156         /* return request to access node directly */ tcp dpt:9051
DNAT       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: v1
data:
  dnat-ports-pair: 9051=10264 # 新增端口=10264(非10264轉發不支援)
kind: ConfigMap
metadata:
  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 等

歡迎加入 OpenYurt 社群

作為阿裡雲邊緣容器服務 ACK@Edge 的核心,OpenYurt 已經在 CDN、音視訊直播、物聯網、物流、工業大腦、城市大腦等數十個行業中得到商業化實踐、服務規模達數百萬 CPU 核。我們可喜地看到,現在有越來越多的開發者、開源社群、企業和學信機構認可 OpenYurt 的理念,并且正在加入到共同建設 OpenYurt 的隊伍中,比如 VMware、Intel、深信服、招商局、浙大、EdgeX Foundry 社群、eKuiper 社群等。我們也歡迎更多的朋友共建 OpenYurt 社群,繁榮雲原生邊緣計算生态,讓真正意義上的雲原生在更多邊緣場景中創造價值。

歡迎加入 OpenYurt 社群釘釘群:

Yurt-Tunnel 詳解|如何解決 K8s 在雲邊協同下的運維監控挑戰背景Yurt-Tunnel 設計思路實作方式 近期規劃歡迎加入 OpenYurt 社群

點選

https://openyurt.io/en-us/

,直通 OpenYurt 官網!