1. 程式人生 > >第二部分:以DaemonSet方式執行linkerd_Kubernetes中文社群

第二部分:以DaemonSet方式執行linkerd_Kubernetes中文社群

導言

在我們發表的上一篇關於linkerd的文章中提到過,linkerd是使用DaemonSet而非sidecar來安裝的。在本文中,我們將解釋我們為什麼(怎麼樣)這麼做。
注意:這是關於Linkerd、Kubernetes和service mesh的系列文章其中一篇,其餘部分包括:

  1. Top-line service metrics
  2. Pods are great, until they’re not(本文)
  3. Encrypting all the things
  4. Continuous deployment via traffic shifting
  5. Dogfood environments, ingress, and edge routing
  6. Staging microservices without the tears
  7. Distributed tracing made easy
  8. Linkerd as an ingress controller
  9. gRPC for fun and profit
  10. The Service Mesh API
  11. Egress
  12. Retry budgets, deadline propagation, and failing gracefully
  13. 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。

本文譯者:平凡,歡迎關注微信公眾號