1. 程式人生 > >Kubernetes,微服務以及 Service Mesh_Kubernetes中文社群

Kubernetes,微服務以及 Service Mesh_Kubernetes中文社群

作者:Jolestar ,2015年初開始創業,作為技術合夥人,專注於打造一款團隊通訊協作工具-Grouk

這是前一段時間在一個微服務的 meetup 上的分享,整理成文章釋出出來。

談微服務之前,先澄清一下概念。微服務這個詞的準確定義很難,不同的人有不同的人的看法。比如一個朋友是『微服務原教旨主義者』,堅持微服務一定是無狀態的 http API 服務,其他的都是『邪魔歪道』,它和 SOA,RPC,分散式系統之間有明顯的分界。而另外也有人認為微服務本身就要求把整體系統當做一個完整的分散式應用來對待,而不是原來那種把各種元件堆積在一起,『拼接』系統的做法。兩種說法都有道理,因為如果微服務沒有個明確的邊界的話,你可能會發現微服務囊括了一切,但如果只是堅持無狀態,那微服務相關的一些領域又無法涵蓋。我個人對這個問題持開放式的看法,微服務本身代表了一種軟體交付以及複用模式的變化,從依賴庫到依賴服務,和 SOA 有相通之處,同時它也帶來了新的挑戰,對研發運維都有影響,所有因為和微服務相關而產生的變化,都可以囊括在大微服務主題下。

微服務帶來的變化

微服務主要給我們帶來的變化有三點,

  1. 部署單元 越來越小的粒度,加快交付效率,同時增加運維的複雜度。
  2. 依賴方式 從依賴庫到依賴服務,增加了開發者選擇的自由(語言,框架,庫),提高了複用效率,同時增加了治理的複雜度。
  3. 架構模式 從單體應用到微服務架構,架構設計的關注點從分層轉向了服務拆分。

##微服務涉及的技術點

  • 服務註冊與發現 服務目錄 服務列表 配置中心
  • 程序間通訊 負載均衡
  • 服務生命週期管理 部署, 變更,升級,自動化運維
  • 服務依賴關係
  • 鏈路跟蹤,限流,降級,熔斷
  • 訪問控制
  • 日誌與監控
  • 微服務應用框架

這個是我瀏覽了眾多微服務的話題之後摘要出來的一些技術點,不全面也不權威,不過可以看出,微服務主題涉及的面非常廣,那這些問題在微服務之前就不存在麼?為什麼大家談到微服務的時候才把這些東西拿出來說。這個需要從軟體開發的歷史說來。軟體開發行業從十多年前開始,出現了一個分流,一部分是企業應用開發,軟體要安裝到企業客戶自己的資源上,客戶負責運維(或者通過技術支援),一部分是網際網路軟體,自己開發運維一套軟體通過網路給終端使用者使用。這兩個領域使用的技術棧也逐漸分化,前者主要關注標準化,框架化(複用),易安裝,易運維,後者主要關注高可用,高效能,縱向伸縮。而這兩個領域到微服務時代,形成了一個合流,都在搞微服務化。主要原因我認為有兩點:

  1. SaaS 的興起,使得一些企業應用廠商也開始採用網際網路模式,遇到使用者規模的問題。同時企業應用很難純 SaaS 化,面對大客戶的時候,勢必面臨私有部署的問題,所以必須探索一種既能支撐使用者規模,同時要能方便私有化部署的架構。
  2. 隨著網際網路技術領域為了應對新的業務變化,需要將原來的技術經驗沉澱繼承過來,開始關注標準化和框架化。

也就是說,這些技術點並不是新問題,但如何將這些技術點從業務邏輯中抽取出來,作為獨立的,可複用的框架或者服務,這個是大家探尋的新問題。

為微服務而生的 Kubernetes

  • Kubernetes Pod – Sidecar 模式
  • Kubernetes 支援微服務的一些特性
  • Service Mesh 微服務的中介軟體層

我這樣說,不僅僅是因為這是一個微服務的 meetup,微服務和 Kubernetes 確實是相輔相成的。一方面 Kubernetes 幫助微服務落地,另外一方面微服務促進了對容器和 Kubernetes 的需求。

Kubernetes 的 Pod – Sidecar 模式

我們先從 Kubernetes 的 Pod 機制帶來的一種架構模式 — Sidecar 來說。下面這段配置檔案是我從 Kubernetes 內建的 dns 的 deployment 中抽取出來的 Pod 描述檔案,簡化掉了資源限制,埠設定以及健康檢查等內容。

apiVersion: v1
kind: Pod
metadata:
  name: dnspod
spec:
  containers:
  - name: kubedns
    image: gcr.io/google_containers/k8s-dns-kube-dns-amd64:1.14.4
    args:
    - --domain=cluster.local.
    - --dns-port=10053
    - --config-dir=/kube-dns-config
    - --v=2
    ports:
    - containerPort: 10053
      name: dns-local
      protocol: UDP
    - containerPort: 10053
      name: dns-tcp-local
      protocol: TCP
  - name: dnsmasq
    image: gcr.io/google_containers/k8s-dns-dnsmasq-nanny-amd64:1.14.4
    args:
    - -v=2
    - -logtostderr
    - -configDir=/etc/k8s/dns/dnsmasq-nanny
    - -restartDnsmasq=true
    - --
    - -k
    - --cache-size=1000
    - --log-facility=-
    - --server=/cluster.local/127.0.0.1#10053
    - --server=/in-addr.arpa/127.0.0.1#10053
    - --server=/ip6.arpa/127.0.0.1#10053
    ports:
    - containerPort: 53
      name: dns
      protocol: UDP
    - containerPort: 53
      name: dns-tcp
      protocol: TCP
  - name: sidecar
    image: gcr.io/google_containers/k8s-dns-sidecar-amd64:1.14.4
    args:
    - --v=2
    - --logtostderr
    - --probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local,5,A
    - --probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local,5,A

從這個配置檔案可以看出,Kubernetes 的 Pod 裡可以包含多個容器,同一個 Pod 的多個容器之間共享網路,Volume,IPC(需要設定),生命週期和 Pod 保持一致。上例中的 kube-dns 的作用是通過 Kubernetes 的 API 監聽叢集中的 Service 和 Endpoint 的變化,生成 dns 記錄,通過 dns 協議暴露出來。dnsmasq 的作用是作為 dns 快取,cluster 內部域名解析代理到 kube-dns,其他域名通過上游 dns server 解析然後快取,供叢集中的應用使用。sidecar 容器的作用是進行 dns 解析探測,然後輸出監控資料。

通過這個例子可以看出來,Kubernetes 的 Pod 機制給我們提供了一種能力,就是將一個本來要捆綁在一起的服務,拆成多個,分為主容器和副容器(sidecar),是一種更細粒度的服務拆分能力。當然,沒有 Kubernetes 的時候你也可以這麼幹,但如果你的程式需要幾個程序捆綁在一起,要一起部署遷移,運維肯定想來打你。有了這種能力,我們就可以用一種非侵入的方式來擴充套件我們的服務能力,並且幾乎沒有增加運維複雜度。這個在我們後面的例子中也可以看到。

Kubernetes 上的服務發現

服務發現其實包含了兩個方面的內容,一種是要發現應用依賴的服務,這個 Kubernetes 提供了內建的 dns 機制和 ClusterIP 機制,每個 Service 都自動註冊域名,分配 ClusterIP,這樣服務間的依賴可以從 IP 變為 name,這樣可以實現不同環境下的配置的一致性。

apiVersion: v1
kind: Service
metadata:
  name: myservice
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 9376
  selector:
    app: my-app
  clusterIP: 10.96.0.11
curl http://myservice

如上面的 Service 的例子,依賴方只需要通過 myservice 這個名字呼叫,具體這個 Service 後面的後端例項在哪,有多少個,使用者不用關心,交由 Kubernetes 接管,相當於一種基於虛 IP(通過 iptables 實現) 的內部負載均衡器。(具體的 clusterIP 實現這裡不再詳述,可查閱相關資料)。

另外一種服務發現是需要知道同一個服務的其他容器例項,節點之間需要互相連線,比如一些分散式應用。這種需求可以通過 Kubernetes 的 API 來實現,比如以下例項程式碼:

endpoints, _ = client.Core().Endpoints(namespace).Get("myservice", metav1.GetOptions{})
addrs := []string{}
for _, ss := range endpoints {
	for _, addr := range ss.Addresses {
		ips = append(addrs, fmt.Sprintf(`"%s"`, addr.IP))
	}
}
glog.Infof("Endpoints = %s", addrs)

Kubernetes 提供了 ServiceAccount 的機制,自動在容器中注入呼叫 Kubernetes API 需要的 token,應用程式碼中無需關心認證問題,只需要部署的時候在 yaml 中配置好合適的 ServiceAccount 即可。

關於服務發現再多說兩句,沒有 Kubernetes 這樣的統一平臺之前,大家做服務發現還主要依賴一些服務發現開源工具,比如 etcd,zookeeper,consul 等,進行自定義開發註冊規範,在應用中通過 sdk 自己註冊。但當應用部署到 Kubernetes 這樣的平臺上你會發現,應用完全不需要自己註冊,Kubernetes 本身最清楚應用的節點狀態,有需要直接通過 Kubernetes 進行查詢即可,這樣可以降低應用的開發運維成本。

通過 Kubernetes 進行 Leader 選舉

有些分散式需要 Leader 選舉,在之前,大家一般可能依賴 etcd 或者 zookeeper 這樣的服務來實現。如果應用部署在 Kubernetes 中,也可以利用 Kubernetes 來實現選舉,這樣可以減少依賴服務。

Kubernetes 中實現 Leader 選舉主要依賴以下特性:

  1. Kubernetes 中的所有 API 物件型別,都有一個 ResourceVersion,每次變更,版本號都會增加。
  2. Kubernetes 中的所有物件,都支援 Annotation,支援通過 API 修改,這樣可以附加一些自定義的 key-value 資料儲存到 Kubernetes 中。
  3. Kubernetes 的所有 API 物件中,Endpoint/ConfigMap 屬於『無副作用』物件,也就是說,建立後不會帶來額外的影響,所以一般用這兩種物件來儲存選主資訊。
  4. Kubernetes 的 Update/Replace 介面,支援 CAS 機制的更新,也就是說,更新時可以帶上客戶端快取中的 ResourceVersion,如果伺服器端該物件的 ResourceVersion 已經大於客戶端傳遞的 ResourceVersion,則會更新失敗。

這樣,所有的節點都一起競爭更新同一個 Endpoint/ConfigMap,更新成功的,作為 Leader,然後把 Leader 資訊寫到 Annotation 中,其他節點也能獲取到。為了避免競爭過於激烈,會有一個過期機制,過期時間也寫入到 Annotation,Leader 定時 renew 過期時間,其他節點定時查詢,發現過期就發起新一輪的競爭。這樣相當於 Kubernetes 提供了一種以 client 和 API 一起配合實現的 Leader 選舉機制,並且在 client sdk 中提供。當前 Kubernetes 的一些內部元件,比如 controller-manager,也是通過這種方式來實現選舉的。

當然,如果覺得呼叫 client 麻煩,或者該語言的 sdk 尚未支援這個特性(應該只有 go 支援了),可以也可以通過 sidercar 的方式實現,參看 https://github.com/kubernetes/contrib/tree/master/election 。也就是說,通過一個 sidecar 程式去做選主,主程式只需要呼叫 sidecar 的一個 http api 去查詢即可。

$ kubectl exec elector-sidecar -c nodejs -- wget -qO- http://localhost:8080
Master is elector-sidecar

Kubernetes 支援微服務的運維特性

Kubernetes 對微服務的運維特性上的支援,主要體現以下兩方面:

  1. 滾動升級以及自動化伸縮
    kubectl set image deployment <deployment> <container>=<image>
    kubectl rollout status deployment <deployment>
    kubectl rollout pause deployment <deployment>
    kubectl rollout resume deployment <deployment>
    kubectl rollout undo deployment <deployment>
    
    kubectl autoscale deployment php-apache --cpu-percent=50 --min=1 --max=10
    

    滾動升級支援最大不可用節點設定,支援暫停,恢復,一鍵回滾。自動伸縮支援根據監控資料在一個範圍內自動伸縮,很大程度上降低了微服務的運維成本。

  2. 日誌與監控的標準化通過 Kubernetes 可以實現日誌收集以及應用監控的標準化,自動化,這樣應用不用關心日誌和監控資料的收集展示,只需要按照系統標準輸出日誌和監控介面即可。

Service Mesh 微服務的中介軟體層

微服務這個話題雖然火了有很長時間了,關於微服務的各種框架也有一些。但這些框架大多是程式語言層面來解決的,需要使用者的業務程式碼中整合框架的類庫,語言的選擇也受限。這種方案很難作為單獨的產品或者服務給使用者使用,升級更新也受限於應用本身的更新與迭代。直到 Service Mesh 的概念的提出。Service Mesh 貌似也沒有比較契合的翻譯(有的譯做服務齒合層,有的翻譯做服務網格),這個概念就是試圖在網路層抽象出一層,來統一接管一些微服務治理的功能。這樣就可以做到跨語言,無侵入,獨立升級。其中前一段時間 Google,IBM,Lyft 聯合開源的 istio 就是這樣一個工具,先看下它的功能簡介:

  • 智慧路由以及負載均衡
  • 跨語言以及平臺
  • 全範圍(Fleet-wide)策略執行
  • 深度監控和報告

是不是聽上去就很厲害?有的還搞不明白是啥意思?我們看 istio 之前先看看 Service Mesh 能在網路層做些什麼。

  • 視覺化 其實本質上微服務治理的許多技術點都包含視覺化要求,比如監控和鏈路追蹤,比如服務依賴
  • 彈性(Resiliency 或者應該叫柔性,因為彈性很容易想到 scale) 就是網路層可以不那麼生硬,比如超時控制,重試策略,錯誤注入,熔斷,延遲注入都屬於這個範圍。
  • 效率(Efficiency) 網路層可以幫應用層多做一些事情,提升效率。比如解除安裝 TLS,協議轉換相容
  • 流量控制 比如根據一定規則分發流量到不同的 Service 後端,但對呼叫方來說是透明的。
  • 安全保護 在網路層對流量加密/解密,增加安全認證機制,而對應用透明。

可以看出,如果接管了應用的進出流量,將網路功能可程式設計化,實際上可做的事情很多。那我們簡單看下 istio 是如何做的。

大致看一下這個架構圖,istio 在業務 Pod 裡部署了一個 sidecar — Envoy,這是一個代理伺服器,前面說的網路層功能基本靠它來實現,然後 Envoy 和上面的控制層元件(Mixer,Pilot,Istio-Auth)互動,實現動態配置,策略執行,安全證書獲取等,對使用者業務透明。實際部署的時候,並不需要開發者在自己的 Pod 宣告檔案裡配置 Envoy 這個 sidecar,istio 提供了一個命令列工具,在部署前注入(解析宣告配置檔案然後自動修改)即可,這樣 istio 的元件就可以和業務應用完全解耦,進行獨立升級。

看到這裡,Java 伺服器端的研發人員可能會感覺到,這個思路和 Java 當初的 AOP(aspect-oriented programming) 有點像,確實很多 Java 微服務框架也是利用 AOP 的能力來儘量減少對應用程式碼的侵入。但程式語言的 AOP 能力受語言限制,有的語言裡就非常難實現非侵入的 AOP,而如果直接在網路層面尋找切面就可以做到跨語言了。

當前其實已經有許多在網路層實現的中介軟體,比如有提供資料庫安全審計功能的,有提供 APM 的,有提供資料庫自動快取的,但這些中介軟體遇到的最大問題是增加了使用者應用的部署複雜度,實施成本比較高,很難做到對使用者應用透明。可以預見,隨著 Kubernetes 的普及,這類中介軟體也會湧現出來。

結語

本質上,微服務的目的是想以一種架構模式,應對軟體所服務的使用者的規模增長。沒有微服務架構之前,大多數應用是以單體模式出現的,只有當規模增長到一定程度,單體架構滿足不了伸縮的需求的時候,才考慮拆分。而微服務的目標是在一開始的時候就按照這種架構實現,是一種面向未來的架構,也就是說用開始的選擇成本降低以後的重構成本。用經濟學的觀點來說,微服務是技術投資,單體應用是技術債務,技術有餘力那可以投資以期待未來收益,沒餘力那就只能借債支援當前業務,等待未來還債。而隨著微服務基礎設施的越來越完善,用很小的投資就可以獲得未來很大的收益,就沒有理由拒絕微服務了。

相關連結