K8s v1.17新特性預告:拓撲感知服務路由

  • 2019 年 12 月 4 日
  • 筆記

大家好,我是 roc,來自騰訊雲容器服務(TKE)團隊,今天給大家介紹下我參與開發的一個 k8s v1.17 新特性: 拓撲感知服務路由。

No.1

名詞解釋

  • 拓撲域: 表示在集群中的某一類 「地方」,比如某節點、某機架、某可用區或某地域等,這些都可以作為某種拓撲域。
  • endpoint: K8s 某個服務的某個 ip+port,通常是 pod 的 ip+port。
  • service: K8s 的 service 資源(服務),關聯一組 endpoint ,訪問 service 會被轉發到關聯的某個 endpoint 上。

No.2

背景

拓撲感知服務路由,此特性最初由杜軍大佬提出並設計。為什麼要設計此特性呢?想像一下,k8s 集群節點分佈在不同的地方,service 對應的 endpoints 分佈在不同節點,傳統轉發策略會對所有 endpoint 做負載均衡,通常會等概率轉發,當訪問 service 時,流量就可能被分散打到這些不同的地方。雖然 service 轉發做了負載均衡,但如果 endpoint 距離比較遠,流量轉發過去網絡時延就相對比較高,會影響網絡性能,在某些情況下甚至還可能會付出額外的流量費用。要是如能實現 service 就近轉發 endpoint,是不是就可以實現降低網絡時延,提升網絡性能了呢?是的!這也正是該特性所提出的目的和意義。

No.3

k8s 親和性

service 的就近轉發實際就是一種網絡的親和性,傾向於轉發到離自己比較近的 endpoint。在此特性之前,已經在調度和存儲方面有一些親和性的設計與實現:

  • 節點親和性 (Node Affinity): 讓 Pod 被調度到符合一些期望條件的 Node 上,比如限制調度到某一可用區,或者要求節點支持 GPU,這算是調度親和,調度結果取決於節點屬性。
  • Pod 親和性與反親和性 (Pod Affinity/AntiAffinity): 讓一組 Pod 調度到同一拓撲域的節點上,或者打散到不同拓撲域的節點, 這也算是調度親和,調度結果取決於其它 Pod。
  • 數據卷拓撲感知調度 (Volume Topology-aware Scheduling): 讓 Pod 只被調度到符合其綁定的存儲所在拓撲域的節點上,這算是調度與存儲的親和,調度結果取決於存儲的拓撲域。
  • 本地數據卷 (Local Persistent Volume): 讓 Pod 使用本地數據卷,比如高性能 SSD,在某些需要高 IOPS 低時延的場景很有用,它還會保證 Pod 始終被調度到同一節點,數據就不會不丟失,這也算是調度與存儲的親和,調度結果取決於存儲所在節點。
  • 數據卷拓撲感知動態創建 (Topology-Aware Volume Dynamic Provisioning): 先調度 Pod,再根據 Pod 所在節點的拓撲域來創建存儲,這算是存儲與調度的親和,存儲的創建取決於調度的結果。

而 k8s 目前在網絡方面還沒有親和性能力,拓撲感知服務路由這個新特性恰好可以補齊這個的空缺,此特性使得 service 可以實現就近轉發而不是所有 endpoint 等概率轉發。

No.4

如何實現

我們知道,service 轉發主要是 node 上的 kube-proxy 進程通過 watch apiserver 獲取 service 對應的 endpoint,再寫入 iptables 或 ipvs 規則來實現的; 對於 headless service,主要是通過 kube-dns 或 coredns 動態解析到不同 endpoint ip 來實現的。實現 service 就近轉發的關鍵點就在於如何將流量轉發到跟當前節點在同一拓撲域的 endpoint 上,也就是會進行一次 endpoint 篩選,選出一部分符合當前節點拓撲域的 endpoint 進行轉發。

那麼如何判斷 endpoint 跟當前節點是否在同一拓撲域里呢?只要能獲取到 endpoint 的拓撲信息,用它跟當前節點拓撲對比下就可以知道了。那又如何獲取 endpoint 的拓撲信息呢?答案是通過 endpoint 所在節點的 label,我們可以使用 node label 來描述拓撲域。

通常在節點初始化的時候,controller-manager 就會為節點打上許多 label,比如 kubernetes.io/hostname 表示節點的 hostname 來區分節點;另外,在雲廠商提供的 k8s 服務,或者使用 cloud-controller-manager 的自建集群,通常還會給節點打上 failure-domain.beta.kubernetes.io/zone 和 failure-domain.beta.kubernetes.io/region 以區分節點所在可用區和所在地域,但自 v1.17 開始將會改名成 topology.kubernetes.io/zone 和 topology.kubernetes.io/region,參見 PR 81431。

如何根據 endpoint 查到它所在節點的這些 label 呢?答案是通過 Endpoint Slice,該特性在 v1.16 發佈了 alpha,在 v1.17 將會進入 beta,它相當於 Endpoint API 增強版,通過將 endpoint 做數據分片來解決大規模 endpoint 的性能問題,並且可以攜帶更多的信息,包括 endpoint 所在節點的拓撲信息,拓撲感知服務路由特性會通過 Endpoint Slice 獲取這些拓撲信息實現 endpoint 篩選 (過濾出在同一拓撲域的 endpoint),然後再轉換為 iptables 或 ipvs 規則寫入節點以實現拓撲感知的路由轉發。

細心的你可能已經發現,之前每個節點上轉發 service 的 iptables/ipvs 規則基本是一樣的,但啟用了拓撲感知服務路由特性之後,每個節點上的轉發規則就可能不一樣了,因為不同節點的拓撲信息不一樣,導致過濾出的 endpoint 就不一樣,也正是因為這樣,service 轉發變得不再等概率,靈活的就近轉發才得以實現。

當前還不支持 headless service 的拓撲路由,計劃在 beta 階段支持。由於 headless service 不是通過 kube-proxy 生成轉發規則,而是通過 dns 動態解析實現的,所以需要改 kube-dns/coredns 來支持這個特性。

No.5

前提條件

啟用當前 alpha 實現的拓撲感知服務路由特性需要滿足以下前提條件:

  • 集群版本在 v1.17 及其以上。
  • Kube-proxy 以 iptables 或 IPVS 模式運行 (alpha 階段暫時只實現了這兩種模式)。
  • service: k8s 的 service 資源(服務),關聯一組 endpoint ,訪問 service 會被轉發到關聯的某個 endpoint 上。
  • 啟用了 Endpoint Slices (此特性雖然在 v1.17 進入 beta,但沒有默認開啟)。

No.6

如何啟用此特性

給所有 k8s 組件打開 ServiceTopology 和 EndpointSlice 這兩個 feature gate:

–feature-gates="ServiceTopology=true,EndpointSlice=true"

No.7

如何使用

在 Service spec 里加上 topologyKeys 字段,表示該 Service 優先順序選用的拓撲域列表,對應節點標籤的 key;當訪問此 Service 時,會找是否有 endpoint 有對應 topology key 的拓撲信息並且 value 跟當前節點也一樣,如果是,那就選定此 topology key 作為當前轉發的拓撲域,並且篩選出其餘所有在這個拓撲域的 endpoint 來進行轉發;如果沒有找到任何 endpoint 在當前 topology key 對應拓撲域,就會嘗試第二個 topology key,依此類推;如果遍歷完所有 topology key 也沒有匹配到 endpoint 就會拒絕轉發,就像此 service 沒有後端 endpoint 一樣。

有一個特殊的 topology key 「*「,它可以匹配所有 endpoint,如果 topologyKeys 包含了 *,它必須在列表末尾,通常是在沒有匹配到合適的拓撲域來實現就近轉發時,就打消就近轉發的念頭,可以轉發到任意 endpoint 上。

當前 topology key 支持以下可能的值(未來會增加更多):

  • kubernetes.io/hostname: 節點的 hostname,通常將它放列表中第一個,表示如果本機有 endpoint 就直接轉發到本機的 endpoint。
  • topology.kubernetes.io/zone: 節點所在的可用區,通常將它放在 kubernetes.io/hostname 後面,表示如果本機沒有對應 endpoint,就轉發到當前可用區其它節點上的 endpoint(部分雲廠商跨可用區通信會收取額外的流量費用)。
  • topology.kubernetes.io/region: 表示節點所在的地域,表示轉發到當前地域的 endpoint,這個用的應該會比較少,因為通常集群所有節點都只會在同一個地域,如果節點跨地域了,節點之間通信延時將會很高。
  • *: 忽略拓撲域,匹配所有 endpoint,相當於一個保底策略,避免丟包,只能放在列表末尾。

除此之外,還有以下約束:

  • topologyKeys 與 externalTrafficPolicy=Local 不兼容,是互斥的,如果 externalTrafficPolicy 為 Local,就不能定義 topologyKeys,反之亦然。
  • topology key 必須是合法的 label 格式,並且最多定義 16 個 key。

這裡給出一個簡單的 Service 示例:

apiVersion: v1  kind: Service  metadata:    name: nginx  spec:    type: ClusterIP    ports:    - name: http      port: 80      protocol: TCP      targetPort: 80    selector:      app: nginx    topologyKeys: ["kubernetes.io/hostname", "topology.kubernetes.io/zone", "*"]

解釋: 當訪問 nginx 服務時,首先看本機是否有這個服務的 endpoint,如果有就直接本機路由過去;如果沒有,就看是否有 endpoint 位於當前節點所在可用區,如果有,就轉發過去,如果還是沒有,就轉發給任意 endpoint。

上圖就是其中一次轉發的例子:Pod 訪問 nginx 這個 service 時,發現本機沒有 endpoint,就找當前可用區的,找到了就轉發過去,也就不會考慮轉發給另一可用區的 endpoint。

No.8

背後小故事

此特性的 KEP Proposal 最終被認可(合併)時的設計與當前最終的代碼實現已經有一些差別,實現方案歷經一變再變,但同時也推動了其它特性的發展,我來講下這其中的故事。

一開始設計是在 alpha 時,讓 kube-proxy 直接暴力 watch node,每個節點都有一份全局的 node 的緩存,通過 endpoint 的 nodeName 字段找到對應的 node 緩存,再查 node 包含的 label 就可以知道該 endpoint 的拓撲域了,但在集群節點數量多的情況下,kube-proxy 將會消耗大量資源,不過優點是實現上很簡單,可以作為 alpha 階段的實現,beta 時再從 watch node 切換到 watch 一個新設計的 PodLocator API,作為拓撲信息存儲的中介,避免 watch 龐大的 node。

實際上一開始我也是按照 watch node 的方式,花了九牛二虎之力終於實現了這個特性,後來 v1.15 時 k8s 又支持了 metadata-only watch,參見 PR 71548,利用此特性可以僅僅 watch node 的 metadata,而不用 watch 整個 node,可以極大減小傳輸和緩存的數據量,然後我就將實現切成了 watch node metadata; 即便如此,metadata 還是會更新比較頻繁,主要是 resourceVersion 會經常變 (kubelet 經常上報 node 狀態),所以雖然 watch node metadata 比 watch node 要好,但也還是可能會造成大量不必要的網絡流量,但作為 alpha 實現是可以接受的。

可惜在 v1.16 code freeze 之前沒能將此特性合進去,只因有一點小細節還沒討論清楚。實際在實現 watch node 方案期間,Endpoint Slice 特性就提出來了,在這個特性討論的階段,我們就想到了可以利用它來攜帶拓撲信息,以便讓拓撲感知服務路由這個特性後續可以直接利用 Endpoint Slice 來獲取拓撲信息,也就可以替代之前設計的 PodLocator API,但由於它還處於很早期階段,並且代碼還未合併進去,所以 alpha 階段先不考慮 watch Endpint Slice。後來,Endpoint Slice 特性在 v1.16 發佈了 alpha。

由於 v1.16 沒能將拓撲感知服務路由特性合進去,在 v1.17 周期開始後,有更多時間來討論小細節,並且 Endpoint Slice 代碼已經合併,我就乾脆直接又將實現從 watch node metadata 切成了 watch Endpint Slice,在 alpha 階段就做了打算在 beta 階段做的事情,終於,此特性實現代碼最終合進了主幹。

No.9

結尾

拓撲感知服務路由可以實現 service 就近轉發,減少網絡延時,進一步提升 k8s 的網絡性能,此特性將於 k8s v1.17 發佈 alpha,時間是 12 月上旬,讓我們一起期待吧!k8s 網絡是塊難啃的硬骨頭,感興趣的同學可以看下杜軍的新書 《Kubernetes 網絡權威指南》,整理鞏固一下 k8s 的網絡知識: https://item.jd.com/12724298.html

No.10

參考資料

  • KEP: EndpointSlice – https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/20190603-EndpointSlice-API.md
  • Endpoint Slice 官方文檔 – https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/
  • Proposal: Volume Topology-aware Scheduling – https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/volume-topology-scheduling.md
  • PR: Service Topology implementation for Kubernetes – https://github.com/kubernetes/kubernetes/pull/72046
  • Proposal: Inter-pod topological affinity and anti-affinity – https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/podaffinity.md
  • Topology-Aware Volume Provisioning in Kubernetes – https://kubernetes.io/blog/2018/10/11/topology-aware-volume-provisioning-in-kubernetes/
  • Kubernetes 1.14: Local Persistent Volumes GA – https://kubernetes.io/blog/2019/04/04/kubernetes-1.14-local-persistent-volumes-ga/
  • KubeCon 演講: 面向 k8s 的拓撲感知服務路由即將推出! – https://v.qq.com/x/page/t0893nn9zqa.html
  • 拓撲感知服務路由官方文檔(等v1.17發佈後才能看到) – https://kubernetes.io/docs/concepts/services-networking/service-topology/
  • KEP: Topology-aware service routing – https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/20181024-service-topology.md (此文檔後續會更新,因為實現跟設計已經不一樣了)