前言
本文是筆者在學習官方文檔、相關部落格文章和實踐過程中,整理了一些知識概念和自己的思考,主要在探索 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 提供包含了南北流量和東西流量兩部分的防禦機制:
1、Security by default:微服務應用不需要提供任何代碼或引入三方庫來保證自身安全。
2、Defense in depth:能夠與已有的安全體系融合,深度定制安全能力。
3、Zero-trust Network:提供安全的方案都是假定服務網格的内部和外部都是0信任(不安全)網絡。
下圖是 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做二次開發擴充,等等問題都是在生産實踐當中需要關注的點,以後有機會再分享整理。
參考文檔
作者資訊:
袁赓拓,花名三辰,阿裡雲智能-計算平台事業部技術專家,負責數加平台 &DataWorks 的微服務生态建設,目前主要關注微服務、Service Mesh 等相關技術方向。