第二部分:以DaemonSet方式執行linkerd_Kubernetes中文社群
導言
在我們發表的上一篇關於linkerd的文章中提到過,linkerd是使用DaemonSet而非sidecar來安裝的。在本文中,我們將解釋我們為什麼(怎麼樣)這麼做。
注意:這是關於Linkerd、Kubernetes和service mesh的系列文章其中一篇,其餘部分包括:
- Top-line service metrics
- Pods are great, until they’re not(本文)
- Encrypting all the things
- Continuous deployment via traffic shifting
- Dogfood environments, ingress, and edge routing
- Staging microservices without the tears
- Distributed tracing made easy
- Linkerd as an ingress controller
- gRPC for fun and profit
- The Service Mesh API
- Egress
- Retry budgets, deadline propagation, and failing gracefully
- Autoscaling by top-line metrics
linkerd以DaemonSet方式執行
作為service mesh,linkerd設計為與應用程式程式碼一起執行。它管理和監控service的內部通訊,包括服務發現、重試、負載均衡與協議升級。
初次聽聞,都會覺得這非常適合在Kubernetes中以sidecar的方式部署。畢竟,Kubernetes的定義特徵之一就是它的pod模型。作為sidecar部署理論上簡單,有清晰的失敗語義,我們花了大量時間用於針對該用例的linkerd優化。
然而,sidecar模型有其缺陷:部署一個pod就要用掉部署一個pod的資源。如果你的service較輕量並且跑了許多的應用例項,這樣使用sidecar來部署就會代價極高。
我們可以通過每一個host而非每一個pod部署一個linkerd來減少資源的開銷。它對每個主機進行擴充套件而消耗資源,這通常是比pod數量要顯著更慢的增長的指標。並且很幸運,Kubernetes為此目的提供了DaemonSets。
但美中不足的是,對於linkerd來說,按每個host部署比僅僅使用DaemonSets要複雜一些。我們如何解決service mesh這個問題?請繼續閱讀下文。
Kubernetes的service mesh
service mesh的定義特徵之一是它將應用通訊與傳輸通訊分離開的能力。例如,如果service A與B使用HTTP協議通訊,service mesh也許會通過電纜將之轉換為HTTPS,而且應用程式並不知曉。service mesh也可以做連線池、許可權控制,或其它的傳輸層特性,並且是以對應用程式透明的方式。
為了完美的做到這些,linkerd必須作為本地例項的代理處於請求的傳送端與接收端。例如HTTP升級為HTTPS,linkerd一定要在傳輸層協議的開始與結束。在DaemonSet的世界,一個通過linkerd的請求路線看起來如下圖所示:
正如你所見,一個從Host 1中Pod A發起的目標為Host 2中Pod B的請求必須通過Pod A的本地linkerd例項,然後到達Host 2的linkerd例項,最後到達Pod J。這個路徑引入了linkerd必須解決的三個問題:
- 應用如何定位它的本地linkerd
- linkerd如何將傳出請求路由到目標linkerd
- linkerd如何將傳入請求路由到目標應用
接下來就講述我們如何解決這三個問題的技術細節。
應用程式如何定位它的本地linkerd?
因為DaemonSet使用Kubernetes的host埠,我們就知道linkerd運行於host IP的一個固定埠。因此,為了傳送一個請求給運行於同一宿主機的linkerd程序,我們需要確定這個主機的IP地址。
在Kubernetes 1.4及其以後的版本,此資訊可通過底層API直接獲取到。以下是來自hello-world.yaml的摘要,顯示瞭如何將節點名稱傳遞到應用程式中:
env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: http_proxy value: $(NODE_NAME):4140 args: - "-addr=:7777" - "-text=Hello" - "-target=world"
(請注意,此例設定http_proxy環境變數以引導通過本地linkerd例項的所有HTTP呼叫。然而此方法僅適用於大多數的HTTP應用,非HTTP應用需要做一些不同的工作)
Kubernetes 1.4之前的版本這些資訊仍舊可用,但是方式要間接一些。
以下是hello-world-legacy.yml的一部分,顯示如何將主機IP傳遞到應用程式中。
env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NS valueFrom: fieldRef: fieldPath: metadata.namespace command: - "/bin/sh" - "-c" - "http_proxy=`hostIP.sh`:4140 helloworld -addr=:7777 -text=Hello -target=world"
請注意,hostIP.sh指令碼需要將pod名和名稱空間以環境變數的方式注入pod中。
linkerd如何將傳出請求路由到目標linkerd?
在我們的service mesh部署中,傳出請求不應當直接傳送給目標應用,而應當傳送給運行於應用程式所在主機的linkerd。為了做到這點,我們可以利用linkerd 0.8.0引入的被稱為transformer的新特性,它可以對linker路由到的目標地址進行任意的後期處理。在這種情況下,我們可以使用DaemonSet transformer自動的讓運行於目標主機的DaemonSet的地址取代目標地址。例如,這個linkerd傳出路由配置傳送所有的請求到運行於相同主機的作為目標應用的linkerd的輸入埠。
routers: - protocol: http label: outgoing interpreter: kind: default transformers: - kind: io.l5d.k8s.daemonset namespace: default port: incoming service: l5d ...
linkerd如何將輸入請求路由到目標應用?
當一個請求最終到達目標pod的linkerd例項,它一定得正確的路由到該pod。為此,我們使用localnode transformer將路由限制在運行於本地主機的pod。linkerd配置示例如下:
routers: - protocol: http label: incoming interpreter: kind: default transformers: - kind: io.l5d.k8s.localnode ...
結束語
將linkerd作為Kubernetes DaemonSet部署使得兩全其美——它讓我們完成service mesh的全部目標(如透明的TLS、協議升級、延遲感知負載均衡等等),同時按每個host擴縮linkerd例項而非每個pod。