Service Mesh · Istio · 以實踐入門
- 2020 年 2 月 25 日
- 筆記

Photo @ Jez Timms
文 | 三辰
前言
本文是筆者在學習官方文檔、相關部落格文章和實踐過程中,整理了一些知識概念和自己的思考,主要在探索 lstio 的實際應用場景, Sidecar 原理, Service Mesh 為什麼出現、要解決什麼問題等,幫助我們思考微服務技術架構的升級和落地的可行性。
本文不是 Istio 的全部,但是希望入門僅此一篇就夠。
概念
圍繞雲原生(CN)的概念,給人一種知識大爆炸的感覺,但假如你深入了解每一個概念的細節,你會發現它和你很近,甚至就是你手裡每天做的事情。

圖片來源:https://landscape.cncf.io/
關鍵詞:Service Mesh、Istio、Sidecar、Envoy 等。
服務網格
服務網格( Service Mesh )是一個新瓶裝舊酒的概念,它的發展隨著微服務興起,必然是早於 Kubernates 出現了。但 Kubernates 和 Istio 的出現,促使它成為了一種更火更標準化的概念。
Sidecar 是服務網格技術中常用的(其中)一種設計架構,在 Kubernates 中,不同的容器允許被運行在同一個 Pod 中(即多個進程運行在同一個 cgroup 下),這在很大程度上給 Sidecar 模式提供了良好的土壤。
首先看看 Sidecar 的設計:

圖片來源於網路
為什麼是新瓶舊酒?任何技術的發展都不是憑空地跳躍式發展的。
歷史

原始的應用程式–圖片來源於網路

獨立的網路層–圖片來源於網路

出現網路層(4層協議)控制的需求–圖片來源於網路

控制邏輯下移到網路層–圖片來源於網路
早期,應用程式隨著功能迭代發展,尤其是一個大型項目,程式堆積了越來越多的功能,功能之間緊密耦合在一起,變得越來越難以維護(因為模組耦合度較高,沒有人敢動古老的模組程式碼),迭代周期變長(工程複雜度成幾何增長)。
於是,人們提出,將不同的功能分離到不同的程式(進程)中,減低模組的耦合度,敏捷開發迭代,這就是微服務概念的興起。

出現新的應用層(7層協議)需求(服務發現、熔斷、超時重試等)–圖片來源於網路

封裝成三方庫(服務發現:Dubbo/HSF)–圖片來源於網路
困難:
服務被拆分成眾多的微服務,最困難的問題就是——調用它自己:
1、原本在進程中互相調用那麼簡單的事情,都要變成一次在 7 層網路上的遠程調用。
2、原本公共工具類做的事情,現在需要寫成二方庫 SDK 等,在每一個進程中使用,版本迭代成為了災難。
3、原本是內部透明調用的不需要做任何防護,分離後卻要額外增加安全防護和隔離的工作。
4、不再是程式碼即文檔,需要維護大量的 API 定義和版本管理。

封裝到隔離的進程中代理–圖片來源於網路
到這裡,獨立進程的方式基本成型,即為Sidecar模式。
Sidecar 解決什麼問題?
這裡有個服務網格里的概念:微服務之間的調用,一般在架構圖中是橫向的,被稱為東西流量。服務暴露到外部被公網可見的外部調用,被稱為南北流量。
Sidecar 的設計就是為了解決微服務互相調用(東西流量)的問題。
先來一張我們比較熟悉的圖:

圖片來源於網路
Consumer 與 Provider 就是微服務互相調用的一種解決方案。
毫無疑問,我們熟知的一整套中間件解決方案,解決的正是東西流量的問題,圖為Dubbo 架構。
只不過, Dubbo 中間件一整套組件是基於 SPI 機制以一種較為隔離的方式侵入到運行時的程式碼中。並且,這隻能限定 Java 這樣被官方支援的語言來開發服務應用。
小結
歸納一下與東西流量有關的問題:
流量管理(服務發現、負載均衡、路由、限流、熔斷、容錯等)、可觀測性(監控、日誌聚合、計量、跟蹤)、安全(認證、授權),再甚至更高級的動態配置、故障注入、鏡像流量等
相比來說, Sidecar 的模式更為巧妙並更進一步。通過容器機制,在進程上是隔離的,基於 L7 代理進行通訊,允許微服務是由任何語言進行開發的。

圖片來源於網路
以下是微服務集群基於Sidecar互相通訊的簡化場景:

圖片來源於網路
所以說,回到服務網格的概念上來,雖然概念是不同的,但是邏輯上我們可以理解成:所有使用中間件的服務組成了一個大的服務網格,這樣可以幫助我們理解。服務網格基於 Kubernates 這樣的容器技術,將東西流量的問題解決得更加透明無感。
一句話總結,服務網格( Service Mesh )是解決微服務之間的網路問題和可觀測性問題的(事實)標準,並且正在走向標準化。
Service Mesh 是 Kubernetes 支撐微服務能力拚圖的最後一塊
Istio 和 Envoy
Istio,第一個字母是(ai)。
Istio 實現的服務網格分為數據平面和控制平面。核心能力包括4大塊:
1、流量控制(Traffic Management)。
2、安全(Security)。
3、動態規則(Policy)。
4、可觀測能力(Observability)。
Envoy 面向數據平面,也就是服務之間調用的代理。
Envoy 是 Istio Service Mesh 中默認的 Sidecar 方案。
Istio 在 Enovy 的基礎上按照 Envoy 的 xDS 協議擴展了其控制平面。


Istio基於Envoy實現Service Mesh數據平面–圖片來源於網路

Envoy角色–圖片來源於網路
Envoy 是一個由 C++ 實現的高性能代理,與其等價的,還有 Nginx、Traefik ,這就不難理解了。
也就是下圖中的 Proxy :

圖片來源於Istio官網
Istio 在控制平面上主要解決流量管理、安全、可觀測性三個方面的問題,也就是前面提到的東西流量相關的問題。類似一個有配置中心的微服務集群架構。具體細節不在這裡贅述。
Sidecar注入
前面在介紹服務網格時,只是簡單地提到Sidecar設計在其中的作用和特性,這裡詳細展開介紹其中的原理。
首先是一些預備概念:
1、Sidecar 模式:容器應用模式之一,Service Mesh 架構的一種實現方式
2、Init 容器:Pod 中的一種專用的容器,在應用程式容器啟動之前運行,用來包含一些應用鏡像中不存在的實用工具或安裝腳本。
3、iptables:流量劫持是通過 iptables 轉發實現的。
Sidecar 模式解決微服務之間的網路通訊(遠程調用),通常通訊層的實現方式,有以下選擇:
1、在微服務應用程式中導入 SDK 類庫。
2、節點代理(使用縱向的API網關或者是本地 Agent ),代理介面的調用路由規則,轉發到特定的機器。
3、用 Sidecar 容器的形式運行,和應用容器一同運行,透明地劫持所有應用容器的出入流量。
SDK 庫的方式是很自然的,並且調用方式是進程內的,沒有安全隔離的包袱。但是隨著程式語言的發展,很多新的語言為特定的場景而生,而SDK庫的方式限制了使用方必須用支援列表中的語言。
節點代理的方式,是使用一個特定的服務專門代理微服務中的請求,是一個中間人的角色。但這個代理人的安全性要求非常高,因為它需要處理來自不同微服務的請求,並鑒別它們各自的身份。
Sidecar 模型是介於 SDK 庫和節點代理中間的一種形式,相當於是給每個微服務都配上一個自己獨有的代理。這樣,每個微服務自己的 Sidecar 就代表了自己特定的身份,有利於調用的安全審計。因此,從外部看, Sidecar 與其附屬的應用程式具有相同的許可權。

圖片來源:https://toutiao.io/posts/uva4uy/preview
以 Istio 為例:
在 Istio 中, Sidecar 模式啟動時會首先執行一個init 容器 istio-init ,容器只做一件事情,通過 iptables 命令配置 Pod 的網路路由規則,讓 Envoy 代理可以攔截所有的進出 Pod 的流量。
之後,微服務應用通過 Pod 中共享的網路命名空間內的 loopback ( localhost )與 Sidecar 通訊。而外部流量也會通過 Sidecar 處理後,傳入到微服務。
因為它們共享一個 Pod ,對其他 Pod 和節點代理都是不可見的,可以理解為兩個容器共享存儲、網路等資源,可以廣義的將這個注入了 Sidecar 容器的 Pod 理解為一台主機,兩個容器共享主機資源。
下圖是具體 iptables 與 Sidecar 之間互作用原理,來源:
https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/

具體原理上的細節,我們可以通過實踐,慢慢挖掘。
小結
最後給概念章節有個階段性的總結:

圖片來源於網路
所以我們打算賣什麼?
實踐
鋪墊這麼多概念,我們可以實操起來了。具體怎麼做?從安裝 Istio 開始。
準備工作
首先,預備一個Kubernates集群,這裡不贅述。
如果是本地測試,Docker-Desktop也可以啟動一個單機的k8s集群
裝 Istio 的命令行工具 istioctl :
下載 istio-release(包括 istioctl 、示例文件和安裝配置)。
curl -sL "https://github.com/istio/istio/releases/download/1.4.2/istio-1.4.2-osx.tar.gz" | tar xz
安裝 helm (可選):
從 1.4.0 版本開始,不再使用 helm 來安裝 Istio
# helm工具 $ brew install kubernetes-helm
安裝Istio
進入到安裝文件的目錄,開始將 Istio 安裝到 k8s 上。
首先確認 kubectl 連接的正確的 k8s 集群。
選擇以下其中一種方式:
方式1、使用 istioctl
安裝
cd istio-1.4.2 # 安裝istioctl cp bin/istioctl /usr/local/bin/ # 也可以加一下PATH # (可選)先查看配置文件 istioctl manifest generate --set profile=demo > istio.demo.yaml # 安裝istio istioctl manifest apply --set profile=demo ## 以下是舊版本istio的helm安裝方式 ## # 創建istio專屬的namespace kubectl create namespace istio-system # 通過helm初始化istio helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f - # 通過helm安裝istio的所有組件 helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
方式2、使用 helm 安裝
## 以下是舊版本istio的helm安裝方式 ## # 創建istio專屬的namespace kubectl create namespace istio-system # 通過helm初始化istio helm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f - # 通過helm安裝istio的所有組件 helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
等待所有的 Istio 組件的容器啟動,直到:
$ kubectl get crds | grep 'istio.io' | wc -l 23
如果是阿里雲ACS集群,安裝完Istio後,會有對應的一個SLB被創建出來,轉發到Istio提供的虛擬伺服器組
示例:Hello World
示例程式碼在源碼的 samples 目錄中
cd samples/hello-world
注入
Istio Sidecar 的注入有兩種方式:自動、手動。
這裡先通過 istioctl 命令直接手工inject:
istioctl kube-inject -f helloworld.yaml -o helloworld-istio.yaml
實際上就是通過腳本修改了原文件,增加了:
1、sidecar init容器。
2、istio proxy sidecar容器。
分析
我們可以簡單對比一下注入的配置,原文件:
apiVersion: v1 kind: Service metadata: name: helloworld labels: app: helloworld spec: ports: - port: 5000 name: http selector: app: helloworld --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: version: v1 name: helloworld-v1 spec: replicas: 1 selector: matchLabels: app: helloworld version: v1 strategy: {} template: metadata: labels: app: helloworld version: v1 spec: containers: - image: docker.io/istio/examples-helloworld-v1 imagePullPolicy: IfNotPresent name: helloworld ports: - containerPort: 5000 resources: requests: cpu: 100m --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: version: v2 name: helloworld-v2 spec: replicas: 1 selector: matchLabels: app: helloworld version: v2 strategy: {} template: metadata: labels: app: helloworld version: v2 spec: containers: - image: docker.io/istio/examples-helloworld-v2 imagePullPolicy: IfNotPresent name: helloworld ports: - containerPort: 5000 resources: requests: cpu: 100m
可以看到,需要部署兩個版本 helloworld-v1/v2 的容器,掛載在同一個服務下。
這是一個典型的藍綠部署方式,後續我們可以通過配置 Istio ,來調整它們的流量權重,這是真實生產環境版本升級的場景。
再來看增加的部分:

這裡增加了一部分 Istio 的配置,是 K8s 中的標準做法 annotations 。

這部分可以看到,原有的服務容器沒有任何改動,只是增加了一個sidecar容器,包括啟動參數和環境變數(因為配置排序的問題, args 排在了最前面,整體的定義:
- args: - proxy - sidecar - ... env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - ... image: docker.io/istio/proxyv2:1.3.2 imagePullPolicy: IfNotPresent name: istio-proxy ports: - containerPort: 15090 name: http-envoy-prom protocol: TCP readinessProbe: failureThreshold: 30 httpGet: path: /healthz/ready port: 15020 initialDelaySeconds: 1 periodSeconds: 2 resources: limits: cpu: "2" memory: 1Gi requests: cpu: 100m memory: 128Mi securityContext: readOnlyRootFilesystem: true runAsUser: 1337 volumeMounts: - mountPath: /etc/istio/proxy name: istio-envoy - mountPath: /etc/certs/ name: istio-certs readOnly: true
鏡像名 docker.io/istio/proxyv2:1.3.2 。
另外一部分,就是 initContainer :
initContainers: - args: - -p - "15001" - -z - "15006" - -u - "1337" - -m - REDIRECT - -i - '*' - -x - "" - -b - '*' - -d - "15020" image: docker.io/istio/proxy_init:1.3.2 imagePullPolicy: IfNotPresent name: istio-init resources: limits: cpu: 100m memory: 50Mi requests: cpu: 10m memory: 10Mi securityContext: capabilities: add: - NET_ADMIN runAsNonRoot: false runAsUser: 0 volumes: - emptyDir: medium: Memory name: istio-envoy - name: istio-certs secret: optional: true secretName: istio.default
部署
$ kubectl apply -f helloworld-istio.yaml service/helloworld created deployment.apps/helloworld-v1 created deployment.apps/helloworld-v2 created $ kubectl get deployments.apps -o wide NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR helloworld-v1 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v1,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v1 helloworld-v2 1/1 1 1 20m helloworld,istio-proxy docker.io/istio/examples-helloworld-v2,docker.io/istio/proxyv2:1.3.2 app=helloworld,version=v2 並啟用一個簡單的gateway來監聽,便於我們訪問測試頁面$ kubectl apply -f helloworld-gateway.yaml gateway.networking.istio.io/helloworld-gateway created virtualservice.networking.istio.io/helloworld created 部署完成之後,我們就可以通過gateway訪問hello服務了:$ curl "localhost/hello" Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5 $ curl "localhost/hello" Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5 $ curl "localhost/hello" Hello version: v1, instance: helloworld-v1-57bdc65497-js7cm
兩個版本的 Deployment 都可以隨機被訪問到
深入探索
接著剛才我們部署好的 hello-world ,我們隨著Istio的feature進行探索。
流量控制 – 切流
首先,我們嘗試控制一下流量,比如只走v2。參考Traffic Shifting:
https://istio.io/docs/tasks/traffic-management/traffic-shifting/
我們可以通過 VirtualService 配置控制版本流量,詳情參考:
https://istio.io/docs/reference/config/networking/v1alpha3/virtual-service/
先查看一下當前 Gateway 和 VirtualService 的配置:
$ kubectl get gw helloworld-gateway -o yaml apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: helloworld-gateway spec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - "*" $ kubectl get vs helloworld -o yaml apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: helloworld spec: hosts: - "*" gateways: - helloworld-gateway http: - match: - uri: exact: /hello route: - destination: host: helloworld # short for helloworld.${namespace}.svc.cluster.local port: number: 5000
可以看到,VS 轉發 /hello 路徑的請求到 helloworld:5000 ,不過,這種 short 寫法不推薦。我們可以改成 helloworld.${namespace}.svc.cluster.local 。
我們將其中 VirtualService 的配置修改為:
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: helloworld spec: hosts: - "*" gateways: - helloworld-gateway http: - match: - uri: exact: /hello route: - destination: host: helloworld.default.svc.cluster.local subset: v1 weight: 0 - destination: host: helloworld.default.svc.cluster.local subset: v2 weight: 100
在 http.route 里增加一個 destination ,並將 v2 的 weight 權重配置到100 。
並增加一個 DestinationRule 對 subset 進行定義。
apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: helloworld-destination spec: host: helloworld.default.svc.cluster.local subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2
然後應用更新:
$ kubectl apply -f helloworld-gateway.yaml gateway.networking.istio.io/helloworld-gateway unchanged virtualservice.networking.istio.io/helloworld configured destinationrule.networking.istio.io/helloworld-destination created
測試一下效果:
$ while true;do sleep 0.05 ;curl localhost/hello;done Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6 Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
流量完美切走。不過,到目前為止我們只接觸了Gateway 、VirtualService和DestinationRule。我們來回顧一下:
Gateway
Gateway 用於處理服務網格的邊界,定義了出入負載的域名、埠、協議等規則。
VirtualService
VirtualService 可以控制路由(包括subset/version權重、匹配、重定向等)、故障注入、TLS 。
DestinationRule
DestinationRule 定義確定路由的細節規則,比如 subset 定義、負載均衡的策略,甚至可以針對特定的埠再重新定義規則。
示例:Bookinfo
前面的例子,通過控制流量權重達到版本切流的目的。
下面,我們再通過另外一個 Bookinfo 的例子繼續探索其它Istio的feature。

圖片來源於 Istio 官網
本例是一個多實例微服務架構,並且由不同語言開發。
開始
$ cd samples/bookinfo
注入
這次Pod定義比較多,我們打開auto sidecar-injection
$ kubectl label namespace default istio-injection=enabled
打開之後,每次創建的Pod都會自動注入上istio-proxy和相應的initContainer
部署
$ kubectl apply -f platform/kube/bookinfo.yaml service/details created serviceaccount/bookinfo-details created deployment.apps/details-v1 created service/ratings created serviceaccount/bookinfo-ratings created deployment.apps/ratings-v1 created service/reviews created serviceaccount/bookinfo-reviews created deployment.apps/reviews-v1 created deployment.apps/reviews-v2 created deployment.apps/reviews-v3 created service/productpage created serviceaccount/bookinfo-productpage created deployment.apps/productpage-v1 created
創建一個Gateway用於查看頁面:
$ kubectl apply -f networking/bookinfo-gateway.yaml gateway.networking.istio.io/bookinfo-gateway created virtualservice.networking.istio.io/bookinfo created
訪問 http://localhost/productpage 頁面:

不斷刷新可以看到右側Reviews有三個版本:



流量控制 – 網路可見性
基於前面安裝好的 Bookinfo 應用,起一個 Pod 探索一下網路可見性:
$ kubectl run --image centos:7 -it probe # 請求productpage服務上的介面 [root@probe-5577ddd7b9-rbmh7 /]# curl -sL http://productpage:9080 | grep -o "<title>.*</title>" <title>Simple Bookstore App</title> $ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl www.baidu.com | grep -o "<title>.*</title>" <title>百度一下,你就知道</title>
我們可以看到,默認情況下,所有的微服務(容器)之間都是公開可訪問的,並且微服務可以訪問外部網路。
接下來,介紹 Sidecar 配置對可見性進行控制。
Sidecar
由於每個容器都自動注入了Sidecar容器,託管了所有的出入請求。所以基於這個 Sidecar 容器,我們可以很容易對它進行配置。
Sidecar 配置就是 Istio 中專門用於配置 sidecar 之間的網路可見性。
首先,修改全局配置,使用 blocked-by-default 的方式。
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f - configmap "istio" replaced $ kubectl get configmap istio -n istio-system -o yaml | grep -n1 -m1 "mode: REGISTRY_ONLY" 67- outboundTrafficPolicy: 68: mode: REGISTRY_ONLY
outboundTrafficPolicy.mode=REGISTRY_ONLY 表示默認容器不允許訪問外部網路,只允許訪問已知的ServiceEntry。
然後,我們設置一個全局性的 Sidecar 配置:
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default namespace: istio-system spec: egress: - hosts: - "./*" - "istio-system/*" EOF sidecar.networking.istio.io/default configured
- 每個namespace只允許一個無 workloadSelector 的配置
- rootNamespace中無 workloadSelector 的配置是全局的,影響所有namespace,默認的rootNamespace=istio-system
這個配置的含義是:
所有namespace里的容器出流量(egress)只能訪問自己的namespace或namespace=istio-system 中提供的 services 。
egress
我們先測試一下外網連通性, Sidecar 的出流量被稱作 egress 流量。
這裡需要等待一會生效,或者直接銷毀重新部署一個測試容器
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com * About to connect() to www.baidu.com port 80 (#0) * Trying 220.181.38.150... * Connected to www.baidu.com (220.181.38.150) port 80 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.29.0 > Host: www.baidu.com > Accept: */* > * Recv failure: Connection reset by peer * Closing connection 0 curl: (56) Recv failure: Connection reset by peer command terminated with exit code 56
效果是:外網已經訪問不通。
恢復:這時,我們將需要訪問的域名註冊到 ServiceEntry 中,並且增加一個 Sidecar 的 egress 規則,例如:
apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: baidu spec: hosts: - www.baidu.com ports: - number: 80 name: http protocol: HTTP resolution: DNS location: MESH_EXTERNAL --- apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default spec: egress: - hosts: - "./www.baidu.com" port: number: 80 protocol: HTTP name: http
重新請求,確認恢復了。
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com * About to connect() to www.baidu.com port 80 (#0) * Trying 220.181.38.150... * Connected to www.baidu.com (220.181.38.150) port 80 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.29.0 > Host: www.baidu.com > Accept: */* > < HTTP/1.1 200 OK < accept-ranges: bytes < cache-control: private, no-cache, no-store, proxy-revalidate, no-transform < content-length: 2381 < content-type: text/html < date: Tue, 15 Oct 2019 07:45:33 GMT < etag: "588604c8-94d" < last-modified: Mon, 23 Jan 2017 13:27:36 GMT < pragma: no-cache < server: envoy < set-cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/ < x-envoy-upstream-service-time: 21
同樣地,容器之間的流量同理:
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080 curl: (56) Recv failure: Connection reset by peer command terminated with exit code 56 配置上ServiceEntryapiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default spec: egress: - hosts: - "./www.baidu.com" - "./productpage.default.svc.cluster.local" # 這裡必須用長名稱 --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: baidu spec: hosts: - www.baidu.com resolution: DNS location: MESH_EXTERNAL --- apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: productpage spec: hosts: - productpage resolution: DNS location: MESH_EXTERNAL
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080 | grep -o "<title>.*</title>" <title>Simple Bookstore App</title>
需要留意的是,不帶workloadSelector的(不指定特定容器的)Sidecar配置只能有一個,所以規則都需要寫在一起。
ingress
下面我們探究容器入流量的配置:
apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: productpage-sidecar spec: workloadSelector: labels: app: productpage ingress: - port: number: 9080 protocol: HTTP defaultEndpoint: 127.0.0.1:10080 egress: - hosts: - "*/*"
這個配置的效果是讓 productpage 應用的容器收到 9080 埠的 HTTP 請求時,轉發到容器內的10080埠。
由於容器內沒有監聽 10080 ,所以會訪問失敗。
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -s productpage:9080 upstream connect error or disconnect/reset before headers. reset reason: connection failure
小結
Sidecar 的示例說明就到這裡,這只是一個示例。
egress 配置覆蓋的域名訪問規則沒必要在 ingress 中重複,所以 ingress 配置主要用於配置代理流量的規則。例如,我們可以將所有的入口流量傳入 sidecar 中的監聽進程(做一些訂製開發的許可權檢查等),然後再傳給下游微服務。
egress 的配置更多的是關注在服務網格對外訪問的能力,服務內部如果限制了,應用自身訪問都會需要大量的 ServiceEntry 註冊,所以微服務之間東西流量的信任訪問,需要靠安全機制來解決。
安全機制
概述

圖片來源於Istio官網
Istio 提供包含了南北流量和東西流量兩部分的防禦機制:
1、Security by default:微服務應用不需要提供任何程式碼或引入三方庫來保證自身安全。
2、Defense in depth:能夠與已有的安全體系融合,深度訂製安全能力。
3、Zero-trust Network:提供安全的方案都是假定服務網格的內部和外部都是0信任(不安全)網路。
下圖是 Istio 中每個組件的角色:

圖片來源於Istio官網
1、Citadel,證書(CA)管理
2、Sidecar等Envoy Proxy,提供TLS保障
3、Pilot,策略(Policy)和身份資訊下發
4、Mixer,認證和審計
策略(Policy)
Istio 支援在運行時實時地配置策略(Policy),支援:
1、服務入流量速率控制。
2、服務訪問控制,黑白名單規則等。
3、Header重寫,重定向。
也可以自己訂製 Policy Adapter 來定義業務邏輯。
TLS
在介紹安全機制之前,有必要先科普一下 TLS 。
SSL ( Security Socket Layer ,安全 Socket 層),是一個解決 4 層 TCP 和 7 層HTTPS 中間的協議層,解決安全傳輸的問題。
TLS ( Transport Layer Security ,傳輸層安全協議),是基於 SSL v3 的後續升級版本,可以理解為相當於 SSL v3.1 。
主要提供:
1、認證(Transport Authentication),用戶、伺服器的身份,確保數據發送到正確的目的地。
2、加密數據,防止數據明文泄露。
3、數據一致性,傳輸過程不被串改。
Istio 中的安全傳輸機制都是建立在 TLS 之上的。
更多資訊可以查看官方概念,詳情參考:
https://istio.io/docs/concepts/security
認證(Authentication)與鑒權(Authorization)
這兩個詞很相近,甚至縮寫 auth 都是相同的,所以有時候很容混淆。
在 istioctl 中有兩個命令 authn 和 authz ,這樣就可以區分它們。
認證和鑒權分別做什麼,在後文兩節會具體介紹。這裡先說一下它們的關係。
認證 實際上是 鑒權 的必要條件
認證 實際上是 鑒權 的必要條件
認證 實際上是 鑒權 的必要條件
為什麼?
1、認證是識別身份(Identification)。
2、鑒權是檢查特定身份(Identity)的許可權。
這樣就很好理解了。二者時常相隨,我們常說的比如登錄,就是:
1、基於登錄機制的cookie來識別訪問來源的身份——認證。
2、然後判斷來源的身份是否具備登錄系統的許可權(或者是訪問某一個頁面的具體細節的許可權)——鑒權。
那麼在 Istio 體系中,Authentication 是基於 mTLS 機制來做的,那麼開啟mTLS之後,就可以設置一些 AuthorizationPolicy 來做訪問控制。細節可以看下文。
認證(Authentication)
Istio 中的認證包含兩種:
1、Transport Authentication ,傳輸層認證。基於 mTLS ( Mutual TLS ),檢查東西流量的合法性。
2、Origin Authentication ,客戶端認證。基於 JWT 等校驗南北流量的登錄身份。
示例:配置Policy
這次我們跟著 Task: Authentication Policy 例子走,這裡簡化一下過程不做全程搬運,只分析關鍵點。
準備環境:
這個例子中,創建了 3 個 namespace ,其中兩個 foo 和 bar 注入了Sidecar, legacy 不注入用於對比。
#!/bin/bash kubectl create ns foo kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo kubectl create ns bar kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar kubectl create ns legacy kubectl apply -f samples/httpbin/httpbin.yaml -n legacy kubectl apply -f samples/sleep/sleep.yaml -n legacy
默認情況下,容器之間是互通的(mTLS運行在PRESSIVE_MODE)。
這裡通過一個 check.sh 腳本檢查連通性:
#!/bin/bash for from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}n"; done; done$ ./check.sh sleep.foo to httpbin.foo: 200 sleep.foo to httpbin.bar: 200 sleep.foo to httpbin.legacy: 200 sleep.bar to httpbin.foo: 200 sleep.bar to httpbin.bar: 200 sleep.bar to httpbin.legacy: 200 sleep.legacy to httpbin.foo: 200 sleep.legacy to httpbin.bar: 200 sleep.legacy to httpbin.legacy: 200
打開TLS:
通過全局的 MeshPolicy 配置打開mTLS:
$ kubectl apply -f - <<EOF apiVersion: "authentication.istio.io/v1alpha1" kind: "MeshPolicy" metadata: name: "default" spec: peers: - mtls: {} EOF
這時,原本互通的容器訪問不通了
執行:
$ ./check.sh sleep.foo to httpbin.foo: 503 sleep.foo to httpbin.bar: 503 sleep.foo to httpbin.legacy: 200 sleep.bar to httpbin.foo: 503 sleep.bar to httpbin.bar: 503 sleep.bar to httpbin.legacy: 200 sleep.legacy to httpbin.foo: 000 command terminated with exit code 56 sleep.legacy to httpbin.bar: 000 command terminated with exit code 56 sleep.legacy to httpbin.legacy: 200
Sidecar 注入的 namespace 中,會返回 503. 而沒有注入的 ns 上,連接會直接被重置(connection reset)。
配置託管的 mTLS 能力
接著,通過 DestinationRule ,重新對注入Sidecar的微服務增加 mTLS 能力:
kubectl apply -f - <<EOF apiVersion: "networking.istio.io/v1alpha3" kind: "DestinationRule" metadata: name: "default" namespace: "istio-system" spec: host: "*.local" trafficPolicy: tls: mode: ISTIO_MUTUAL EOF
1、*.local 配置的含義是,對所有 K8s 集群內任意 namespace 之間的東西流量有效
2、tls.mode=ISTIO_MUTUAL :查看文檔,表示完全由 Istio 託管 mTLS 的實現,其它選項失效。具體配置後面再涉及。
重新運行 check.sh :
$ ./check.sh sleep.foo to httpbin.foo: 200 sleep.foo to httpbin.bar: 200 sleep.foo to httpbin.legacy: 503 sleep.bar to httpbin.foo: 200 sleep.bar to httpbin.bar: 200 sleep.bar to httpbin.legacy: 503 sleep.legacy to httpbin.foo: 000 command terminated with exit code 56 sleep.legacy to httpbin.bar: 000 command terminated with exit code 56 sleep.legacy to httpbin.legacy: 200
注意,如果走了前面的例子會有全局 default 的 Sidecar Egress 配置,限制了只能訪問同 namespace 的服務,那麼跨 namespace 的調用仍然會 503 :
sleep.foo to httpbin.bar: 503
可以自己試驗一下,回顧一下配置:
apiVersion: networking.istio.io/v1alpha3 kind: Sidecar metadata: name: default namespace: istio-system spec: egress: - hosts: - ./* # <-- - istio-system/*
分析
對比之前的結果,有兩點變化:
1、同樣注入Sidecar的微服務互相可以訪問了(200)。
2、沒有注入Sidecar(ns=legacy)的微服務不能被已注入Sidecar的微服務訪問(503)。
ns=legacy中的行為仍然不變
變化1:說明微服務之間的 TLS 已經由 Istio 託管,這個期間我們沒有修改任何服務的程式碼,很魔性。
變化2:說明服務網格對外部容器也要求具備 TLS 能力,因為 legacy 中的服務沒有注入 Sidecar ,因此訪問失敗。
鑒權(Authorization)
Istio 的鑒權機制的前提就是打開 mTLS 認證,讓每一個或特定的微服務的 sidecar 互相訪問都基於 mTLS 機制。
不是必要前提 有一部分鑒權規則是不依賴mTLS的,但是很少。
鑒權基於 istio CRD :AuthorizationPolicy
例如,默認拒絕所有微服務互相訪問:
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-all namespace: foo spec:
需要留意的是,如果默認全部拒絕,那麼甚至 istio-system 中的 istio-ingressgateway 流量訪問 foo 這個namespace的服務也都會被拒絕。就無法從外部訪問所有 foo 的服務了。所以我們可以改為:
apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: deny-all namespace: foo spec: rules: - from: - source: namespaces: - "istio-system"
AuthorizationPolicy 的規則文檔里都已經很詳細了,這裡就不再贅述。
應用配置之後,在任意一個微服務中訪問另外一個微服務,就都會遇到 403 錯誤,消息為 RBAC access denied 。
其它
Istio 能力本文僅覆蓋了流量控制(Traffic Management)、安全機制(Security)中比較淺顯的部分,有關高級的 Policy 設置(限流、路由的干預等)、服務觀測(Telemetry)等能力沒有涉及。
此外,如何地高效運維管理(比如升級 istio 版本、管理不同集群的配置等),0 信任基礎下的安全訪問策略配置,基於istio做二次開發擴展,等等問題都是在生產實踐當中需要關注的點,以後有機會再分享整理。
參考文檔
- Istio官方文檔 https://istio.io/docs/
- Istio Handbook https://www.servicemesher.com/istio-handbook/concepts-and-principle/what-is-service-mesh.html
- Pattern Service Mesh https://philcalcado.com/2017/08/03/pattern_service_mesh.html
作者資訊:
袁賡拓,花名三辰,阿里雲智慧-計算平台事業部技術專家,負責數加平台 &DataWorks 的微服務生態建設,目前主要關注微服務、Service Mesh 等相關技術方向。