ServiceMesher2018-11-12 03:32:03
自從istio-1.0.0在今年發佈了正式版以後,Coohom項目在生產環境中也開啟了使用istio來作為服務網格。
本文將會介紹與分享在Coohom項目在使用istio中的一些實踐與經驗。
杭州群核信息技術有限公司成立於2011年,公司總部位於浙江杭州,佔地面積超過5000平方米。酷家樂是公司以分佈式並行計算和多媒體數據挖掘為技術核心,推出的家居雲設計平臺,致力於雲渲染、雲設計、BIM、VR、AR、AI等技術的研發,實現“所見即所得”體驗,5分鐘生成裝修方案,10秒生成效果圖,一鍵生成VR方案,於2013年正式上線。作為“設計入口”,酷家樂致力於打造一個連接設計師、家居品牌商、裝修公司以及業主的強生態平臺。
依託於酷家樂快速的雲端渲染能力與先進的3D設計工具經驗,Coohom致力於打造一個讓用戶擁有自由編輯體驗、極致可視化設計的雲端設計平臺。Coohom項目作為一個新興的產品,在架構技術上沒有歷史包袱,同時Coohom自從項目開啟時就一直部署運行在Kubernetes平臺。作為Coohom項目的技術積累,我們決定使用服務網格來作為Coohom項目的服務治理。
由於istio是由Google所主導的產品,使用istio必須在Kubernetes平臺上。所以對於Coohom項目而言,在生產環境使用istio之前,Coohom已經在Kubernetes平臺上穩定運行了。我們先列一下istio提供的功能(服務發現與負載均衡這些Kubernetes就已經提供了):
流量管理: 控制服務之間的流量和API調用的流向、熔斷、灰度發佈、A/BTest都可以在這個功能下完成;
可觀察性: istio可以通過流量梳理出服務間依賴關係,並且進行無侵入的監控(Prometheus)和追蹤(Zipkin);
策略執行: 這是Ops關心的點, 諸如Quota、限流乃至計費這些策略都可以通過網格來做,與應用代碼完全解耦;
服務身份和安全:為網格中的服務提供身份驗證, 這點在小規模下毫無作用, 但在一個巨大的集群上是不可或缺的。
但是, 這些功能並不是決定使用istio的根本原因, 基於Dubbo或Spring-Cloud這兩個國內最火的微服務框架不斷進行定製開發,同樣能夠實現上面的功能,真正驅動我們嘗試istio的原因是:
第一:它使用了一種全新的模式(SideCar)進行微服務的管控治理,完全解耦了服務框架與應用代碼。業務開發人員不需要對服務框架進行額外的學習,只需要專注於自己的業務。而istio這一層則由可以由專門的人或團隊深入並管理,這將極大地降低"做好"微服務的成本。
第二: istio來自GCP(Google Cloud Platform),是Kubernetes上的“官方”Service Mesh解決方案,在Kubernetes上一切功能都是開箱即用,不需要改造適配的,深入istio並跟進它的社區發展能夠大大降低我們重複造輪子的成本。
目前Coohom在多個地區的生產環境集群內都已經使用了istio作為服務網格,對於istio目前所提供的功能,Coohom項目的網絡流量管理已經完全交給istio,並且已經通過istio進行灰度發佈。對於從K8S集群內流出的流量,目前也已經通過istio進行管理。
在使用istio之前,Coohom項目就已經一直在Kubernetes平臺穩定運行了。關於Coohom的架構,從技術棧的角度可以簡單的分為:
Node.js egg應用
Java Springboot應用
從網絡流量管理的角度去分類,可以分為三類:
只接受集群外部流量;
只接受集群內部流量;
既接受集群外部流量,也接受集群內部流量
在我們的場景裡,基本上所有的Node應用屬於第一類,一部分Java應用屬於第二類,一部分Java應用屬於第三類。 為了更清楚的表達,我們這裡可以想象一個簡單的場景:
從上面的場景我們可以看到,我們有一個頁面服務負責渲染併發頁面內容到用戶的瀏覽器,用戶會從瀏覽器訪問到頁面服務和賬戶服務。 賬戶服務負責記錄用戶名,用戶密碼等相關信息。賬戶服務同時還會在權限服務內查看用戶是否具有相應的權限,並且頁面服務同樣也會請求賬戶服務的某些接口。 所以按照我們上面的流量管理的分類法,頁面服務屬於第一類服務,權限服務屬於第二類服務,賬戶服務則屬於第三類服務。 同時,賬戶服務和權限服務也接了外部的RDS作為存儲,需要注意的是RDS並非在Kubernetes集群內部。
那麼在過去只用Kubenretes時,為了讓用戶能正確訪問到對應的服務,我們需要編寫Kubernetes Ingress: 值得注意的是,由於只有賬戶服務和頁面服務需要暴露給外部訪問,所以Ingress中只編寫了這兩個服務的規則。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: www.example.com
http:
paths:
- backend:
serviceName: page-service
servicePort: 80
path: /
- host: www.example.com
http:
paths:
- backend:
serviceName: account-service
servicePort: 80
path: /api/account
在接入istio體系後,雖然這三個服務在所有POD都帶有istio-proxy作為sidecar的情況下依舊可以沿用上面Kubernetes Ingress將流量導入到對應的服務。 不過既然用了istio,我們希望充分利用istio的流量管理能力,所以我們先將流量導入到服務這一職責交給istio VirtualService去完成。所以在我一開始接入istio時,我們將上述Kubernetes方案改造成了通過下述方案:
首先,我們在istio-system這個namespace下建立Ingress,將所有www.example.com這個host下的流量導入到istio-ingressgateway中。 這樣我們就從集群的流量入口開始將流量管理交付給istio來進行管理。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: istio-ingress
namespace: istio-system
spec:
rules:
- host: www.example.com
http:
paths:
- backend:
serviceName: istio-ingressgateway
servicePort: 80
path: /
在交付給istio進行管理以後,我們需要將具體的路由-服務匹配規則告訴給istio,這一點可以通過Gateway+VirtualService實現。 需要注意的是,下面的服務名都是用的簡寫,所以必須將這兩個文件和對應的服務部署在同一個Kubernetes namespace下才行。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: example-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: example-http
protocol: HTTP
hosts:
- "www.example.com"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: example-virtualservice
spec:
hosts:
- "www.example.com"
gateways:
- example-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 80
host: page
- match:
- uri:
prefix: /api/account
route:
- destination:
port:
number: 80
host: account-service
在經過上述的操作以後,重新啟動服務實例並且自動注入istio-proxy後,我們會發現兩個後端的Java應用並不能正常啟動。經過查詢啟動日誌後發現,無法啟動的原因則是因為不能連接到外部RDS。這是因為我們的所有網絡流量都經過istio的管控後,所有需要集群外部服務都需要先向istio進行註冊以後才能被順利的轉發過去。一個非常常見的場景則是通過TCP連接的外部RDS。當然,外部的HTTP服務也是同理。
以下是一個向istio註冊外部RDS的例子。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: mysql-external
namespace: istio-system
spec:
hosts:
- xxxxxxxxxxxx1.mysql.rds.xxxxxx.com
- xxxxxxxxxxxx2.mysql.rds.xxxxxx.com
addresses:
- xxx.xxx.xxx.xxx/24
ports:
- name: tcp
number: 3306
protocol: tcp
location: MESH_EXTERNAL
上面istio-ingress+Gateway+VirtualService的方案可以替代我們之前只使用Kubernetes Ingress的方案,但如果只是停留在這一步的話那麼對於istio給我們帶來的好處可能就不能完全體現。值得一提的是,在上文的istio-ingress中我們將www.example.com的所有流量導入到了istio-ingressGateway,通過這一步我們可以在istio-ingressGateway的log當中查看到所有被轉發過來的流量的網絡情況,這一點在我們日常的debug中非常有用。然而在上述所說的方案中istio的能力還並未被完全利用,接下來我將介紹我是如何基於上述方案進行改造以後來進行Coohom日常的灰度發佈。
還是以上文為例,假設需要同時發佈三個服務,並且三個服務都需要進行灰度發佈,並且我們對灰度發佈有著以下幾個需求:
最初的灰度發佈希望只有內部開發者才能查看,外部用戶無法進入灰度。
當內部開發者驗證完灰度以後,逐漸開發切換新老服務流量的比例。
當某個外部用戶進入新/老服務,我希望他背後的整個服務鏈路都是新/老服務
為了支持以上灰度發佈的需求,我們有如下工作需要完成:
定義規則告訴istio,對於一個Kubernetes service而言,後續的Deployment實例哪些是新服務,哪些是老服務。
重新設計VirtualService結構策略,使得整個路由管理滿足上述第二第三點需求。
需要設計一個合理的流程,使得當灰度發佈完成以後,最終狀態能恢復成與初始一致。
為了使得istio可以知道對於某個服務而言新老實例的規則,我們需要用到DestinationRule,以賬戶服務為例:
從下文的例子我們可以看到,對於賬戶服務而言,所有Pod中帶有type為normal的標籤被分為了normal組,所有type為grey的標籤則被分為了grey組, 這是用來在後面幫助我們讓istio知道新老服務的規則,即帶有type:normal標籤的POD為老實例,帶有type:grey標籤的POD為新實例。這裡所有三個服務分類都可以套用該規則,就不再贅述。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: account-service-dest
spec:
host: account-service
subsets:
- name: normal
labels:
type: normal
- name: grey
labels:
type: grey
前文我們提到,我們在Kubernetes平臺內將在網絡流量所有服務分為三類。之所以這麼分,就是因為在這裡每一類服務的VirtualService的設計不同。 我們先從第一類,只有外部連接的服務說起,即頁面服務,下面是頁面服務VirtualService的例子:
從下文這個例子,我們可以看到對於頁面服務而言,他定義了兩種規則,對於headers帶有 end-user:test
的請求,istio則會將該請求導入到上文我們所提到的
grey分組,即特定請求進入灰度,而所有其他請求則像之前導入到normal分組,即老實例。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: page-service-external-vsc
spec:
hosts:
- "www.example.com"
gateways:
- example-gateway
http:
- match:
- headers:
end-user:
exact: test
uri:
prefix: /
route:
- destination:
port:
number: 80
host: page-service
subset: grey
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 80
host: page-service
subset: normal
然後我們再看第二類服務,即權限服務,下面是權限服務的virtualService例子:
從下面這個例子我們可以看到,首先在取名方面,上面的page-service的virtualService name為xxx-external-vsc,而這裡權限服務則名為xxx-internal-service。這裡的name對實際效果其實並沒有影響,只是我個人對取名的習慣,用來提醒自己這條規則是適用於外部流量還是集群內部流量。 在這裡我們定義了一個內部服務的規則,即只有是帶有type:grey的POD實例流過來的流量,才能進入grey分組。即滿足了我們上述的第三個需求,整個服務鏈路要麼是全部新實例,要麼是全部老實例。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: auth-service-internal-vsc
spec:
hosts:
- auth-service
http:
- match:
- sourceLabels:
type: grey
route:
- destination:
host: auth-service
subset: grey
- route:
- destination:
host: auth-service
subset: normal
對於我們的第三類服務,即既接收外部流量,同樣也接受內部流量的賬戶服務來說,我們只需要將上文提到的兩個virtualService結合起來即可:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: account-service-external-vsc
spec:
hosts:
- "www.example.com"
gateways:
- example-gateway
http:
- match:
- headers:
end-user:
exact: test
uri:
prefix: /api/account
route:
- destination:
port:
number: 80
host: account-service
subset: grey
- match:
- uri:
prefix: /api/account
route:
- destination:
port:
number: 80
host: account-service
subset: normal
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: account-service-internal-vsc
spec:
hosts:
- "account-service"
http:
- match:
- sourceLabels:
type: grey
route:
- destination:
host: account-service
subset: grey
- route:
- destination:
host: account-service
subset: normal
至此,我們就已經完成了灰度發佈準備的第一步,也是一大步。當新服務實例發佈上去以後,我們在最初通過添加特定的header進入新服務,同時保證所有的外部服務只會進入老服務。當內部人員驗證完新服務實例在生產環境的表現後,我們需要逐漸開放流量比例將外部的用戶流量導入到新服務實例,這一塊可以通過更改第一類和第三類服務的external-vsc來達到,下面給出一個例子:
下面這個例子則是表現為對於外部流量而言,將會一半進入grey分組,一般進入normal分組。最終我們可以將grey分組的weigth變更為100,而normal分組的weight變更為0,即將所有流量導入到grey分組,灰度發佈完成。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: page-service-external-vsc
spec:
hosts:
- "www.example.com"
http:
- route:
- destination:
host: page-service
subset: grey
weight: 50
- destination:
host: page-service
subset: normal
weight: 50
從上述的方案當中,我們將所有服務根據網絡流量來源分為三類,並且通過istio實現了整個業務的灰度發佈。然而整個灰度發佈還並沒有完全結束,我們還需要一點收尾工作。
考慮整個業務剛開始的狀態我們有3個Kubernetes service,3個Kubernetes Deployment,每個Deployment的POD都帶有了type:normal的標籤。 然而現在經過上述方案以後,我們同樣有3個Kubernetes service,3個Kubernetes Deployment,但是這裡每個Deployment的POD卻都帶有了type:grey的標籤。
所以在經過上述灰度發佈以後,我們還要狀態恢復為初始值,這有利於我們下一次進行灰度發佈。由於對於Coohom項目,在CICD上使用的是Gitlab-ci,所以我們的自動化灰度發佈收尾工作深度綁定了Gitlab-ci的腳本,所以這裡就不做介紹,各位讀者可以根據自身情況量身定製。
以上就是目前Coohom在istio使用上關於灰度發佈的一些實踐和經驗。對於Coohom項目而言,在生產環境中使用istio是從istio正式發佈1.0.0版本以後才開始的。但是在這之前,我們在內網環境使用istio已經將近有半年的時間了,Coohom在內網中從istio0.7.1版本開始使用。內網環境在中長期時間內與生產環境環境架構不一致是反直覺的,一聽就不靠譜。然而,恰恰istio 是對業務完全透明的, 它可以看作是基礎設施的一部分,所以我們在生產環境使用istio之前,在內網環境下先上了istio,積累了不少經驗。
點擊【閱讀原文】跳轉到ServiceMesher網站上瀏覽可以查看文中的鏈接。
SOFAMesh(https://github.com/alipay/sofa-mesh)基於Istio的大規模服務網格解決方案
SOFAMosn(https://github.com/alipay/sofa-mosn)使用Go語言開發的高性能Sidecar代理
合作社區
參與社區
以下是參與ServiceMesher社區的方式,最簡單的方式是聯繫我!
加入微信交流群:關注本微信公眾號後訪問主頁右下角有獲取聯繫方式按鈕,添加好友時請註明姓名-公司
社區網址:http://www.servicemesher.com
Slack:https://servicemesher.slack.com (需要邀請才能加入)
GitHub:https://github.com/servicemesher
Istio中文文檔進度追蹤:https://github.com/servicemesher/istio-official-translation
Twitter: https://twitter.com/servicemesher
提供文章線索與投稿:https://github.com/servicemesher/trans