自從istio-1.0.0在今年發佈了正式版之後,Coohom項目在生產環境中也開啓了使用istio來做爲服務網格。mysql
本文將會介紹與分享在Coohom項目在使用istio中的一些實踐與經驗。git
杭州羣核信息技術有限公司成立於2011年,公司總部位於浙江杭州,佔地面積超過5000平方米。酷家樂是公司以分佈式並行計算和多媒體數據挖掘爲技術核心,推出的家居雲設計平臺,致力於雲渲染、雲設計、BIM、VR、AR、AI等技術的研發,實現「所見即所得」體驗,5分鐘生成裝修方案,10秒生成效果圖,一鍵生成VR方案,於2013年正式上線。做爲「設計入口」,酷家樂致力於打造一個鏈接設計師、家居品牌商、裝修公司以及業主的強生態平臺。github
依託於酷家樂快速的雲端渲染能力與先進的3D設計工具經驗,Coohom致力於打造一個讓用戶擁有自由編輯體驗、極致可視化設計的雲端設計平臺。Coohom項目做爲一個新興的產品,在架構技術上沒有歷史包袱,同時Coohom自從項目開啓時就一直部署運行在Kubernetes平臺。做爲Coohom項目的技術積累,咱們決定使用服務網格來做爲Coohom項目的服務治理。sql
因爲istio是由Google所主導的產品,使用istio必須在Kubernetes平臺上。因此對於Coohom項目而言,在生產環境使用istio以前,Coohom已經在Kubernetes平臺上穩定運行了。咱們先列一下istio提供的功能(服務發現與負載均衡這些Kubernetes就已經提供了):後端
流量管理: 控制服務之間的流量和API調用的流向、熔斷、灰度發佈、A/BTest均可以在這個功能下完成;api
可觀察性: 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,積累了很多經驗。
微信羣:聯繫我入羣
Slack:servicemesher.slack.com 須要邀請才能加入
Twitter: twitter.com/servicemesh…
GitHub:github.com/servicemesh…
更多Service Mesh諮詢請掃碼關注微信公衆號ServiceMesher。