Kubernetes網路一年發展動態與未來趨勢(下)

華為雲發表於2019-03-01

前言

書接上回,上回講到K8S網路模型,CNI和Service,並留了一個開放性問題:如何從叢集外訪問K8S叢集內的服務?這時候就輪到Ingress粉墨登場了。

K8S Ingress

何謂 Ingress?從字面意思解讀,就是“入站流量”。K8S 的 Ingress 資源物件是指授權入站連線到達叢集內服務的規則集合。具體含義看下面這個例子便一目瞭然:

Kubernetes網路一年發展動態與未來趨勢(下)

通常情況下,Service 和 Pod 僅可在叢集內部網路中通過 IP 地址訪問。所有到達邊界路由的流量或被丟棄或被轉發到其他地方。Ingress 就是在邊界路由處開個口子,放你進來。因此,Ingress 是建立在 Service 之上的 L7 訪問入口,它支援通過 URL 方式將 Service 暴露到 K8S 叢集外;支援自定義 Service 的訪問策略;提供按域名訪問的虛擬主機功能;支援 TLS 通訊。

Kubernetes網路一年發展動態與未來趨勢(下)

在上面這個例子,Ingress 就可以基於客戶端請求的 URL 來做流量分發,轉發給不同的 Service 後端。
我們來看下Ingress資源物件的API定義:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
spec:
  tls:
  - secretName: testsecret
  backend:
    serviceName: testsvc
    servicePort: 80
複製程式碼

把上面這個 Ingress 物件建立起來後,kubectl ge 一把,會看到:

Kubernetes網路一年發展動態與未來趨勢(下)

其中,ADDRESS 即 Ingress 的訪問入口地址,由 Ingress Controller 分配,一般是 Ingress 的底層實現 LB 的 IP 地址,例如:Ingress,GCE LB,F5 等;BACKEND 是 Ingress 對接的後端 K8S Service IP + Port;RULE 是自定義的訪問策略,主要是基於 URL 的轉發策略,若為空,則訪問 ADDRESS 的所有流量都轉發給 BACKEND。

下面給出一個Ingress的rules不為空的例子

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80
複製程式碼

這個例子和上面那個最明顯的區別在於,rules 定義了 path 分別為 /foo/bar 的分發規則,分別轉發給 s1:80 和 s2:80。Kubectl get 一把一目瞭然:

Kubernetes網路一年發展動態與未來趨勢(下)

需要注意的是,當底層 LB 準備就緒時,Ingress Controller 把 LB 的 IP 填充到 ADDRESS 欄位。而在我們的例子中,這個 LB 顯然還未 ready。

Ingress 是一個非常“極客”和需要 DIY 的產物,K8S 只負責提供一個 API 定義,具體的 Ingress Controller 需要使用者自己實現!官方倒是提供了 Nginx 和 GCE 的 Ingress Controller 示例供開發者參考。實現一個 Ingress Controller 的大致框架無非是,List/Watch K8S 的 Service,Endpoints,Ingress 物件,重新整理外部LB的規則和配置。

這還不算,如果想要通過域名訪問 Ingress?需要使用者自己配置域名和 Ingress IP 的對映關係,比如:host 檔案,自己的 DNS(不是 kube-dns)。下文會講到,“高冷”的 kube-dns 只會負責叢集內的域名解析,叢集外的一概不管。
如果嫌麻煩,懶得開發/配置 Ingress?Huawei CCE瞭解一下? Ingress + 高效能ELB :)

K8S DNS

K8S DN S說,剛剛誰唸叨起本宮了?

一言以蔽之,K8S 的 DNS,就是用來解析 K8S 叢集內的 Pod 和 Service 域名的,而且一般是供 Pod 內的程式使用的!血統高貴,一般不給外人使用。那可能會有人好奇問一句,Pod 到底怎麼使用K8S DNS呢?原來,kubelet配置–cluster-dns把DNS的靜態IP傳遞給每個容器。K8S DNS 一般通過外掛方式部署到 K8S 上,併為之繫結一個 Service,而 Service 的 Cluster IP往往是固定的。

K8S DNS 目前有兩個實現,分別是 kube-dns 和 CoreDNS。

對於 Service,K8S DNS 伺服器會生成兩類 DNS 記錄,分別是:A 記錄和SRV記錄。而 A 記錄又對普通 Service 和 headless Service有所區別。

普通 Service 的 A 記錄是:

{service name}.{service namespace}.svc.cluster.local -> Cluster IP
的對映關係。後面域名後面一串子域名:svc.cluster.local 是 Kubelet 通過 --cluster-domain 配置的偽域名。

Headless Service的A記錄是:

{service name}.{service namespace}.svc.cluster.local -> 後端 Pod IP 列表
的對映關係。

至於SRV記錄,則是按照一個約定俗稱的規定:

_{port name}._{port protocol}.{service name}.{service namespace}.svc.cluster.local –> Service Port
實現了對服務埠的查詢。

對於Pod,A記錄是:

{pod-ip}.{pod namespace}.pod.cluster.local -> Pod IP
如果Pod IP 是 1.2.3.4,上面的 {pod-ip} 即 1-2-3-4。Pod的A記錄其實沒什麼用,因為如果都知道 Pod IP 了,還用查 DNS 嗎?
如果在 Pod Spec 指定 hostname 和 subdomain,那麼 K8S DNS 會額外生成Pod的A記錄就是:
{hostname}.{subdomain}.{pod namespace}.pod.cluster.local –> Pod IP
同樣,後面那一串子域名 pod.cluster.local 是 kubelet 配置的偽域名。

讓我們看下kube-dns的架構吧。

Kubernetes網路一年發展動態與未來趨勢(下)

如上圖所示,kube-dns是“三程式”架構。

  • kubedns:/Watch K8S Service 和 Endpoints 變化。接入 SkyDNS,在記憶體中維護 DNS 記錄,是 dnsmasq 的上游。
  • dnsmasq: DNS 配置工具,監聽 53 埠,為叢集提供 DNS 查詢服務。提供 DNS 快取,降低 kubedns 壓力。
  • exechealthz: 健康檢查,檢查 kube-dns 和 dnsmasq 的健康
    需要注意的是,dnsmasq 是個 C++ 寫的一個小程式,記憶體洩露的“老毛病”。
    雖然 kube-dns 血統純正,而且早早地進入到 K8S 的“後宮”,也早有“名分”,但近來 CoreDNS 卻獨得 K8S SIG Network 的聖寵。CoreDNS 是個 DNS 伺服器,原生支援 K8S,而且居然還是一個 CNCF 的專案!

與 kube-dns 的三程式架構不同,CoreDNS 就一個程式,運維起來更加簡單。而且採用 Go 語言編寫,記憶體安全,高效能。值得稱道的是,CoreDNS 採用的是“外掛鏈”架構,每個外掛掛載一個 DNS 功能,保證了功能的靈活、易擴充套件。儘管資歷不深,但卻“集萬千寵愛於一身”,自然是有兩把刷子的。

值得一提的是,以上效能測試資料是不帶 cache 情況下取得的,明顯要高於 kube-dns。那麼為什麼建議使用 CoreDNS 呢?

K8S 官方已經將 CoreDNS 扶正,成為了預設模式。除了效能好以外,還有什麼其他優勢嗎?Core DNS修復了 kube-dns 的一些“令人討厭”的“老生常談”的問題:

  • dns#55 – Allow custom DNS entries for kube-dns
  • dns#116 – Missing ‘A’ records for headless service with pods sharing * hostname
  • dns#131 – ExternalName not using stubDomains settings
  • dns#167 – Enable round robin A/AAAA records
  • dns#190 – kube-dns cannot run as non-root user
  • dns#232 – Use pod’s name instead of pod’s hostname in DNS SRV records

同時,還有一些吸引人的特性:

  • Zone transfers – list all records, or copy records to another server
  • Namespace and label filtering – expose a limited set of services
  • Adjustable TTL – adjust up/down default service record TTL
  • Negative Caching – By default caches negative responses (e.g. NXDOMAIN)

其中,原生支援基於 namespace 隔離和過濾 Service 和 Pod 的 DNS 記錄這一條特性,在多租戶場景下格外有用。

Network Policy

K8S預設情況下,底層網路是“全連通”的。但如果我們需要實現以下願景:

Kubernetes網路一年發展動態與未來趨勢(下)

即,只允許訪問 default namespace 的 Label 是 app = web 的 Pod,default namespace 的其他 Pod 都不允許外面訪問。這個隔離需求在多租戶的場景下十分普遍。K8S 的解決方案是 Network Policy。

Network Policy 說白了就是基於 Pod 源 IP(所以 K8S 網路不能隨隨便便做SNAT啊!)的訪問控制列表,限制 Pod 的進/出流量,用白名單實現了一個訪問控制列表(ACL)。Network Policy 作為 Pod 網路隔離的一層抽象,允許使用 Label Selector,namespace selector,埠,CIDR 這四個維度限制 Pod 的流量進出。和I ngress 一副德行的是,K8S 對 Netowrk Policy 還是隻提供了 API 定義,不負責實現!

Kubernetes網路一年發展動態與未來趨勢(下)

一般情況下,Policy Controller 是由網路外掛提供的。支援 Network Policy 的網路外掛有 Calico,Cilium,Weave Net,Kube-router,Romana。需要注意的是,flannel 不在這個名單之列,似乎又找到了一個不用 flannel 的理由?

讓我們先來見識幾個預設網路策略:

Kubernetes網路一年發展動態與未來趨勢(下)

注:{}代表允許所有,[]代表拒絕所有。

如果要拒絕所有流量進入呢?比如,場景長這樣:

Kubernetes網路一年發展動態與未來趨勢(下)

那麼 Network Policy 物件應該定義成:

Kubernetes網路一年發展動態與未來趨勢(下)

如果要限制部分流量進入呢?比如,場景長這樣:

Kubernetes網路一年發展動態與未來趨勢(下)

那麼 Network Policy 物件應該定義成:

Kubernetes網路一年發展動態與未來趨勢(下)

如果只允許特定 namespace 的 Pod 流量進入呢?比如,場景長這樣:

Kubernetes網路一年發展動態與未來趨勢(下)

那麼 Network Policy 物件應該定義成:

Kubernetes網路一年發展動態與未來趨勢(下)

如果限制流量從指定埠進入呢?比如,場景長這樣:

Kubernetes網路一年發展動態與未來趨勢(下)

那麼,Network Policy物件應該定義成:

Kubernetes網路一年發展動態與未來趨勢(下)

未來工作

最後,暢想下K8S網路後面的發展趨勢。

  • 首先,kubenet 會被廢棄。 Kubenet 本來就是一個特定時期的產物,那時候 CNI 尚未成熟,讓 K8S 親自去幹底層網路這種“苦差事”,儘管 K8S 是有一萬個不願意,但如果完全沒有網路連通方案,又會讓使用者詬病“過於高冷”,“易用性差”,甚至會讓那時的競爭對手 docker swarm 有機可圖。因此K8S 寫了一個簡單的網路外掛,即 kubenet,一個 bridge 模型的容器連通性解決方案。但隨著 CNI 強勢崛起,以及kubenet並不支援網路策略等硬傷,社群已經沒了繼續維護 kubenet 的熱情,因此廢棄 kubenet 也就被提上了議程。

  • IPv4/IPv6雙棧支援。 經過大半年社群開發者的齊心協力,K8S 總算支援了 IPv6。但目前的支援比較初級,IPv6 還不能和IPv4 混用。IPv4/IPv6 的雙棧支援,勢在必行。
    Pod Ready++。Pod 有 Ready 和非 Ready 狀態之分,為何還要搞出個 Ready++ 這種“量子化”的模糊界限呢?原因在於,一個Pod能否真的對外提供服務,除了依賴容器內程式ready(我們會放置探針,檢查程式狀態)這類內部條件外,還依賴諸如:Ingress,Service,網路策略,外部LB等一系列外部因素。Pod Ready++ 的提出,就是為了將外部因素一齊納入 Pod 狀態的考量。

  • 多網路。 也許是 K8S 的“單 Pod 單 IP”的網路模型過於深入人心了,以至於在實現過程中都謹遵這一“金科玉律”。但我們知道,網路的世界紛繁複雜,一塊網路卡怎麼可能 cover 所有場景呢?據最簡單的例子,一般我們會為一個 IO 密集型的作業配兩塊網路,一塊網路卡作為資料通道,另一塊網路卡則作為控制通道。從單網路到多網路的遷移,道路並不平坦,甚至是處處荊棘和沼澤,且不說網路外掛,Service,DNS,Ingress 等實現要大改,光 API 相容性就讓你頭疼。好訊息是經過整整兩年的拉鋸,社群 Network Plumbing WG終於取得了階段性成果,如不出意外的話,應該是 CRD + 多網路外掛的形式支援K8S的多網路,保持 K8S 原生 API 的穩定。支援多網路的 CNI 外掛有幾個,但真真落到生產的沒幾個,CNI-genie 是其中一個有眾多粉絲基礎和經過生產環境檢驗的 K8S 多網路外掛,瞭解一下?

  • 最後,談下Service Mesh。 嚴格說來,Service Mesh 並不在 K8S 核心的範圍之內。但是,在 K8S 的幫助下,應用上雲後,還面臨著服務治理的難題。現在大多數雲原生的應用都是微服務架構,微服務的註冊,服務之間的相互呼叫關係,服務異常後的熔斷、降級,呼叫鏈的跟蹤、分析等待一系列現實問題擺在各機構面前。Service Mesh(服務網路)就是解決這類微服務發現和治理問題的一個概念。在我看來,Service Mesh 之於微服務架構就像 TCP 協議之於 web 應用。我們大部分人寫web應用,關心的是 RESTful,HTTP 等上層協議,很少需要我們自己操心網路報文超時重傳、分割組裝、內容校驗等底層細節。正是因為有了 Service Mesh,企業在雲原生和微服務架構的實踐道路上只需根據自己業務做適當的微服務拆分即可,無需過多關注底層服務發現和治理的複雜度。而 Istio 的出現,使得有些“學院派”的 Service Mesh 概念真正得到了落地,並且提供了真正可供操作、非侵入式的方案,讓諸如 Spring Cloud,Dubbo 這些“老古董”第一次有了被淘汰的危機感。

如果你想深入學習 Service Mesh 和 Istio,請關注本公眾號,我們會不定期推送相關技術乾貨。

相關文章