雲端設計平臺Coohom在生產環境中使用istio的經驗與實踐

ServiceMesher2018-11-12 03:32:03

自從istio-1.0.0在今年發佈了正式版以後,Coohom項目在生產環境中也開啟了使用istio來作為服務網格。

本文將會介紹與分享在Coohom項目在使用istio中的一些實踐與經驗。

Coohom項目

杭州群核信息技術有限公司成立於2011年,公司總部位於浙江杭州,佔地面積超過5000平方米。酷家樂是公司以分佈式並行計算和多媒體數據挖掘為技術核心,推出的家居雲設計平臺,致力於雲渲染、雲設計、BIM、VR、AR、AI等技術的研發,實現“所見即所得”體驗,5分鐘生成裝修方案,10秒生成效果圖,一鍵生成VR方案,於2013年正式上線。作為“設計入口”,酷家樂致力於打造一個連接設計師、家居品牌商、裝修公司以及業主的強生態平臺。

依託於酷家樂快速的雲端渲染能力與先進的3D設計工具經驗,Coohom致力於打造一個讓用戶擁有自由編輯體驗、極致可視化設計的雲端設計平臺。Coohom項目作為一個新興的產品,在架構技術上沒有歷史包袱,同時Coohom自從項目開啟時就一直部署運行在Kubernetes平臺。作為Coohom項目的技術積累,我們決定使用服務網格來作為Coohom項目的服務治理。

為什麼使用istio

由於istio是由Google所主導的產品,使用istio必須在Kubernetes平臺上。所以對於Coohom項目而言,在生產環境使用istio之前,Coohom已經在Kubernetes平臺上穩定運行了。我們先列一下istio提供的功能(服務發現與負載均衡這些Kubernetes就已經提供了):

  1. 流量管理: 控制服務之間的流量和API調用的流向、熔斷、、A/BTest都可以在這個功能下完成;

  2. 可觀察性: istio可以通過流量梳理出服務間依賴關係,並且進行無侵入的監控(Prometheus)和追蹤(Zipkin);

  3. 策略執行: 這是Ops關心的點, 諸如Quota、限流乃至計費這些策略都可以通過網格來做,與應用代碼完全解耦;

  4. 服務身份和安全:為網格中的服務提供身份驗證, 這點在小規模下毫無作用, 但在一個巨大的集群上是不可或缺的。

但是, 這些功能並不是決定使用istio的根本原因, 基於Dubbo或Spring-Cloud這兩個國內最火的微服務框架不斷進行定製開發,同樣能夠實現上面的功能,真正驅動我們嘗試istio的原因是:

  • 第一:它使用了一種全新的模式(SideCar)進行微服務的管控治理,完全解耦了服務框架與應用代碼。業務開發人員不需要對服務框架進行額外的學習,只需要專注於自己的業務。而istio這一層則由可以由專門的人或團隊深入並管理,這將極大地降低"做好"微服務的成本。

  • 第二: istio來自GCP(Google Cloud Platform),是Kubernetes上的“官方”Service Mesh解決方案,在Kubernetes上一切功能都是開箱即用,不需要改造適配的,深入istio並跟進它的社區發展能夠大大降低我們重複造輪子的成本。

Coohom在istio的使用進度

目前Coohom在多個地區的生產環境集群內都已經使用了istio作為服務網格,對於istio目前所提供的功能,Coohom項目的網絡流量管理已經完全交給istio,並且已經通過istio進行灰度發佈。對於從K8S集群內流出的流量,目前也已經通過istio進行管理。

從單一Kubenertes切換為Kubernetes+istio

在使用istio之前,Coohom項目就已經一直在Kubernetes平臺穩定運行了。關於Coohom的架構,從技術棧的角度可以簡單的分為:

  • Node.js egg應用

  • Java Springboot應用

從網絡流量管理的角度去分類,可以分為三類:

  • 只接受集群外部流量;

  • 只接受集群內部流量;

  • 既接受集群外部流量,也接受集群內部流量

在我們的場景裡,基本上所有的Node應用屬於第一類,一部分Java應用屬於第二類,一部分Java應用屬於第三類。 為了更清楚的表達,我們這裡可以想象一個簡單的場景:

從上面的場景我們可以看到,我們有一個負責渲染併發頁面內容到用戶的瀏覽器,用戶會從瀏覽器訪問到頁面服務和。 賬戶服務負責記錄用戶名,用戶密碼等相關信息。賬戶服務同時還會在權限服務內查看用戶是否具有相應的權限,並且頁面服務同樣也會請求賬戶服務的某些接口。 所以按照我們上面的流量管理的分類法,頁面服務屬於第一類服務,權限服務屬於第二類服務,賬戶服務則屬於第三類服務。 同時,賬戶服務和權限服務也接了外部的RDS作為存儲,需要注意的是RDS並非在Kubernetes集群內部。

那麼在過去只用Kubenretes時,為了讓用戶能正確訪問到對應的服務,我們需要編寫Kubernetes Ingress: 值得注意的是,由於只有賬戶服務和頁面服務需要暴露給外部訪問,所以Ingress中只編寫了這兩個服務的規則。

  1. apiVersion: extensions/v1beta1

  2. kind: Ingress

  3. metadata:

  4.  name: example-ingress

  5. spec:

  6.  rules:

  7.  - host: www.example.com

  8.    http:

  9.      paths:

  10.      - backend:

  11.          serviceName: page-service

  12.          servicePort: 80

  13.        path: /

  14.  - host: www.example.com

  15.    http:

  16.      paths:

  17.      - backend:

  18.          serviceName: account-service

  19.          servicePort: 80

  20.        path: /api/account

在接入istio體系後,雖然這三個服務在所有POD都帶有istio-proxy作為sidecar的情況下依舊可以沿用上面Kubernetes Ingress將流量導入到對應的服務。 不過既然用了istio,我們希望充分利用istio的流量管理能力,所以我們先將流量導入到服務這一職責交給istio VirtualService去完成。所以在我一開始接入istio時,我們將上述Kubernetes方案改造成了通過下述方案:

入口Ingress

首先,我們在istio-system這個namespace下建立Ingress,將所有www.example.com這個host下的流量導入到istio-ingressgateway中。 這樣我們就從集群的流量入口開始將流量管理交付給istio來進行管理。

  1. apiVersion: extensions/v1beta1

  2. kind: Ingress

  3. metadata:

  4.  name: istio-ingress

  5.  namespace: istio-system

  6. spec:

  7.  rules:

  8.  - host: www.example.com

  9.    http:

  10.      paths:

  11.      - backend:

  12.          serviceName: istio-ingressgateway

  13.          servicePort: 80

  14.        path: /

在交付給istio進行管理以後,我們需要將具體的路由-服務匹配規則告訴給istio,這一點可以通過Gateway+VirtualService實現。 需要注意的是,下面的服務名都是用的簡寫,所以必須將這兩個文件和對應的服務部署在同一個Kubernetes namespace下才行。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: Gateway

  3. metadata:

  4.  name: example-gateway

  5. spec:

  6.  selector:

  7.    istio: ingressgateway

  8.  servers:

  9.  - port:

  10.      number: 80

  11.      name: example-http

  12.      protocol: HTTP

  13.    hosts:

  14.    - "www.example.com"

  15. ---

  16. apiVersion: networking.istio.io/v1alpha3

  17. kind: VirtualService

  18. metadata:

  19.  name: example-virtualservice

  20. spec:

  21.  hosts:

  22.  - "www.example.com"

  23.  gateways:

  24.  - example-gateway

  25.  http:

  26.  - match:

  27.    - uri:

  28.        prefix: /

  29.    route:

  30.    - destination:

  31.        port:

  32.          number: 80

  33.        host: page

  34.  - match:

  35.    - uri:

  36.        prefix: /api/account

  37.    route:

  38.    - destination:

  39.        port:

  40.          number: 80

  41.        host: account-service

外部服務註冊

在經過上述的操作以後,重新啟動服務實例並且自動注入istio-proxy後,我們會發現兩個後端的Java應用並不能正常啟動。經過查詢啟動日誌後發現,無法啟動的原因則是因為不能連接到外部RDS。這是因為我們的所有網絡流量都經過istio的管控後,所有需要集群外部服務都需要先向istio進行註冊以後才能被順利的轉發過去。一個非常常見的場景則是通過TCP連接的外部RDS。當然,外部的HTTP服務也是同理。

以下是一個向istio註冊外部RDS的例子。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: ServiceEntry

  3. metadata:

  4.  name: mysql-external

  5.  namespace: istio-system

  6. spec:

  7.  hosts:

  8.  - xxxxxxxxxxxx1.mysql.rds.xxxxxx.com

  9.  - xxxxxxxxxxxx2.mysql.rds.xxxxxx.com

  10.  addresses:

  11.  - xxx.xxx.xxx.xxx/24

  12.  ports:

  13.  - name: tcp

  14.    number: 3306

  15.    protocol: tcp

  16.  location: MESH_EXTERNAL

支持灰度發佈

上面istio-ingress+Gateway+VirtualService的方案可以替代我們之前只使用Kubernetes Ingress的方案,但如果只是停留在這一步的話那麼對於istio給我們帶來的好處可能就不能完全體現。值得一提的是,在上文的istio-ingress中我們將www.example.com的所有流量導入到了istio-ingressGateway,通過這一步我們可以在istio-ingressGateway的log當中查看到所有被轉發過來的流量的網絡情況,這一點在我們日常的debug中非常有用。然而在上述所說的方案中istio的能力還並未被完全利用,接下來我將介紹我是如何基於上述方案進行改造以後來進行Coohom日常的灰度發佈。

還是以上文為例,假設需要同時發佈三個服務,並且三個服務都需要進行灰度發佈,並且我們對灰度發佈有著以下幾個需求:

  • 最初的灰度發佈希望只有內部開發者才能查看,外部用戶無法進入灰度。

  • 當內部開發者驗證完灰度以後,逐漸開發切換新老服務流量的比例。

  • 當某個外部用戶進入新/老服務,我希望他背後的整個服務鏈路都是新/老服務

為了支持以上灰度發佈的需求,我們有如下工作需要完成:

  1. 定義規則告訴istio,對於一個Kubernetes service而言,後續的Deployment實例哪些是新服務,哪些是老服務。

  2. 重新設計VirtualService結構策略,使得整個路由管理滿足上述第二第三點需求。

  3. 需要設計一個合理的流程,使得當灰度發佈完成以後,最終狀態能恢復成與初始一致。

定義規則

為了使得istio可以知道對於某個服務而言新老實例的規則,我們需要用到DestinationRule,以賬戶服務為例:

從下文的例子我們可以看到,對於賬戶服務而言,所有Pod中帶有type為normal的標籤被分為了normal組,所有type為grey的標籤則被分為了grey組, 這是用來在後面幫助我們讓istio知道新老服務的規則,即帶有type:normal標籤的POD為老實例,帶有type:grey標籤的POD為新實例。這裡所有三個服務分類都可以套用該規則,就不再贅述。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: DestinationRule

  3. metadata:

  4.  name: account-service-dest

  5. spec:

  6.  host: account-service

  7.  subsets:

  8.  - name: normal

  9.    labels:

  10.      type: normal

  11.  - name: grey

  12.    labels:

  13.      type: grey

重構VirtualService

前文我們提到,我們在Kubernetes平臺內將在網絡流量所有服務分為三類。之所以這麼分,就是因為在這裡每一類服務的VirtualService的設計不同。 我們先從第一類,只有外部連接的服務說起,即頁面服務,下面是頁面服務VirtualService的例子:

從下文這個例子,我們可以看到對於頁面服務而言,他定義了兩種規則,對於headers帶有 end-user:test的請求,istio則會將該請求導入到上文我們所提到的 grey分組,即特定請求進入灰度,而所有其他請求則像之前導入到normal分組,即老實例。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: VirtualService

  3. metadata:

  4.  name: page-service-external-vsc

  5. spec:

  6.  hosts:

  7.    - "www.example.com"

  8.  gateways:

  9.  - example-gateway

  10.  http:

  11.  - match:

  12.    - headers:

  13.        end-user:

  14.          exact: test

  15.      uri:

  16.        prefix: /

  17.    route:

  18.    - destination:

  19.        port:

  20.          number: 80

  21.        host: page-service

  22.        subset: grey

  23.  - match:

  24.    - uri:

  25.        prefix: /

  26.    route:

  27.    - destination:

  28.        port:

  29.          number: 80

  30.        host: page-service

  31.        subset: normal

然後我們再看第二類服務,即權限服務,下面是權限服務的virtualService例子:

從下面這個例子我們可以看到,首先在取名方面,上面的page-service的virtualService name為xxx-external-vsc,而這裡權限服務則名為xxx-internal-service。這裡的name對實際效果其實並沒有影響,只是我個人對取名的習慣,用來提醒自己這條規則是適用於外部流量還是集群內部流量。 在這裡我們定義了一個內部服務的規則,即只有是帶有type:grey的POD實例流過來的流量,才能進入grey分組。即滿足了我們上述的第三個需求,整個服務鏈路要麼是全部新實例,要麼是全部老實例。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: VirtualService

  3. metadata:

  4.  name: auth-service-internal-vsc

  5. spec:

  6.  hosts:

  7.  - auth-service

  8.  http:

  9.  - match:

  10.    - sourceLabels:

  11.        type: grey

  12.    route:

  13.    - destination:

  14.        host: auth-service

  15.        subset: grey

  16.  - route:

  17.    - destination:

  18.        host: auth-service

  19.        subset: normal

對於我們的第三類服務,即既接收外部流量,同樣也接受內部流量的賬戶服務來說,我們只需要將上文提到的兩個virtualService結合起來即可:

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: VirtualService

  3. metadata:

  4.  name: account-service-external-vsc

  5. spec:

  6.  hosts:

  7.    - "www.example.com"

  8.  gateways:

  9.  - example-gateway

  10.  http:

  11.  - match:

  12.    - headers:

  13.        end-user:

  14.          exact: test

  15.      uri:

  16.        prefix: /api/account

  17.    route:

  18.    - destination:

  19.        port:

  20.          number: 80

  21.        host: account-service

  22.        subset: grey

  23.  - match:

  24.    - uri:

  25.        prefix: /api/account

  26.    route:

  27.    - destination:

  28.        port:

  29.          number: 80

  30.        host: account-service

  31.        subset: normal

  32. ---

  33. apiVersion: networking.istio.io/v1alpha3

  34. kind: VirtualService

  35. metadata:

  36.  name: account-service-internal-vsc

  37. spec:

  38.  hosts:

  39.  - "account-service"

  40.  http:

  41.  - match:

  42.    - sourceLabels:

  43.        type: grey

  44.    route:

  45.    - destination:

  46.        host: account-service

  47.        subset: grey

  48.  - route:

  49.    - destination:

  50.        host: account-service

  51.        subset: normal

至此,我們就已經完成了灰度發佈準備的第一步,也是一大步。當新服務實例發佈上去以後,我們在最初通過添加特定的header進入新服務,同時保證所有的外部服務只會進入老服務。當內部人員驗證完新服務實例在生產環境的表現後,我們需要逐漸開放流量比例將外部的用戶流量導入到新服務實例,這一塊可以通過更改第一類和第三類服務的external-vsc來達到,下面給出一個例子:

下面這個例子則是表現為對於外部流量而言,將會一半進入grey分組,一般進入normal分組。最終我們可以將grey分組的weigth變更為100,而normal分組的weight變更為0,即將所有流量導入到grey分組,灰度發佈完成。

  1. apiVersion: networking.istio.io/v1alpha3

  2. kind: VirtualService

  3. metadata:

  4.  name: page-service-external-vsc

  5. spec:

  6.  hosts:

  7.  - "www.example.com"

  8.  http:

  9.  - route:

  10.    - destination:

  11.        host: page-service

  12.        subset: grey

  13.      weight: 50

  14.    - destination:

  15.        host: page-service

  16.        subset: normal

  17.      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


閱讀原文

TAGS: