Istio 服務網格:深入學習網絡流量和架構
作者|Kasun Talwatta 譯者|張衛濱
來源公衆號 | InfoQ 架構頭條
本文首先介紹了 Istio 的基礎知識,然後結合實際的樣例闡釋了 Istio 是如何將 sidecar 容器注入到 Kubernetes 集羣中,並實現流量攔截的。
本文最初發表於 Solo 官方博客,經原作者 Kasun Talwatta 授權,由 InfoQ 中文站翻譯分享。
像 Istio 這樣的服務網格項目會爲我們的架構引入很多的特性和收益,包括更安全地管理集羣中微服務之間的流量、服務發現、請求路由以及服務之前可靠的通信。
儘管 Istio 是平臺中立的服務網格,但是它更受歡迎的使用場景是與 Kubernetes 協作。雖然它如此流行,但對於剛接觸服務網格的人來說,理解 Istio 的網絡和核心機制可能會很複雜和困難,例如:
-
Envoy sidecar 代理的注入
-
sidecar 是如何攔截和路由流量的
-
流量管理配置的發佈
-
流量規則如何在數據平面上生效
在本系列的博客文章的第一篇中,我們將會分析 Istio 的架構和實現原理,從而解釋這些機制是如何運行的,我們將會介紹 Istio 的網絡基礎知識、數據平面和控制平面、網絡、以及使用 Envoy 代理的 sidecar 注入。藉助一個演示環境,我們將會看到 Istio 如何注入 init 和 sidecar 容器,以及這些容器在 pod 模板中的配置。
01 Istio 的網絡基礎
Istio 概覽已在 官方文檔 中進行了詳盡的介紹,但在繼續後面的內容之前,我們着重再看一下它的幾個核心組件。
Istio 主要由兩部分組成,分別是數據平面和控制平面。
-
數據平面:數據平面或者數據層是由代理服務的集合所組成的,它們會使用擴展的 Envoy 代理服務器,表現形式是每個 Kubernetes pod 中的 sidecar 容器。這些 sidecar 會協調和控制所有微服務之間的網絡通信,同時還會收集和報告有用的遙測數據。
-
控制平面:控制平面或者控制層由一個名爲 istiod 的二進制文件組成,負責將高層級的路由規則和流量控制行爲轉換成 Envoy 的特定配置,然後在運行時將它們傳播到 sidecar 中。除此之外,控制平面還提供安全措施,通過內置的身份標識和證書管理,實現強大的服務間和終端用戶認證,同時根據服務的身份標識執行安全策略。
02 樣例環境中的 Istio 網絡
在介紹下面的內容之前,我們創建一個本地的沙箱環境。這能確保我們會有一個部署在 Kubernetes 中的 Istio 服務網格以及運行在網格中的示例應用。
所需的工具包括:
-
minikube
-
istioctl(可以通過
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.11.4 sh
安裝)
部署 Istio 服務網格的步驟如下:
- 使用 hyperkit 驅動在本地創建一個 1.22.2 版本的 Kubernetes 集羣,如果你使用非 Mac OS X 的機器的話,那麼需要使用 virtualbox 來代替。
minikube start --memory=4096 --cpus=2 --disk-size='20gb' --kubernetes-version=1.22.2 --driver=hyperkit -p istio-demo
- 集羣啓動之後,執行如下的命令來搭建 Istio
# Deploy Istio operator
istioctl operator init
# Inject operator configuration
cat << EOF | kubectl apply -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: istio-control-plane
namespace: istio-system
spec:
profile: minimal
meshConfig:
accessLogFile: /dev/stdout
enableAutoMtls: true
defaultConfig:
proxyMetadata:
# Enable basic DNS proxying
ISTIO_META_DNS_CAPTURE: 'true'
# Enable automatic address allocation
ISTIO_META_DNS_AUTO_ALLOCATE: 'true'
EOF
- 部署示例應用
# Create apps namespace
kubectl create ns apps
# Label apps namespace for sidecar auto injection
kubectl label ns apps istio-injection=enabled
# Deploy a unprivileged sleep application
cat << EOF | kubectl apply -n apps -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
---
apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
service: sleep
spec:
ports:
- name: http
port: 80
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
terminationGracePeriodSeconds: 0
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: secret-volume
mountPath: /etc/sleep/tls
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
EOF
- 驗證 istio-init 和 istio-proxy 容器已經就緒並處於運行狀態
kubectl get po -l app=sleep -n apps -o jsonpath='{range .items[*]}{range @.status.containerStatuses[*]}{.name},{"ready="}{.ready},{"started="}{.started}{"\n"}{end}{range @.status.initContainerStatuses[*]}{.name},{"ready="}{.ready},{"terminated="}{.state.terminated.reason}{end}' | sort
該命令將會輸出:
istio-init,ready=true,terminated=Completed
istio-proxy,ready=true,started=true
03 Istio sidecar 容器和 Envoy 代理
在 Istio 中,sidecar 注入是關鍵功能之一,它簡化了以 pod 模板的形式添加和運行額外容器的過程。在這個注入過程中,會提供兩個額外的容器,分別是:
-
istio-init:這個容器會配置應用 pod 中的 iptables,這樣 Envoy 代理(以另外一個容器的形式運行)就能攔截入站和出站流量。在所有其他的容器啓動之前,Kubernetes 會將其以 Init 容器 的形式運行,以初始化 pod 中的網絡。請注意,允許 istio-init 在內核空間操縱 iptables,需要升級 Kubernetes 的權限。這個容器成功完成任務之後就會自動終止。在此之前,pod 將不會進入就緒狀態。請注意,爲了消除部署該容器時產生安全問題和運維方面的麻煩,Istio 引入了 CNI 插件,因此它會直接與底層的 Kubernetes CNI 集成,而不需要操作 iptables。
-
istio-proxy:它會打包成上游 Envoy 代理的擴展版本。請參閱 官方文檔 以瞭解所支持的擴展列表。
04 深入研究 sidecar 的清單
我們首先看一下在之前部署的應用 pod 中,這兩個容器的 YAML 清單(manifest)。
kubectl get po -l app=sleep -n apps -o yaml
我們來看一下 istio-init 和 istio-proxy 容器的片段。
istio-init 容器:
initContainers:
- name: istio-init
image: docker.io/istio/proxyv2:1.11.4
imagePullPolicy: IfNotPresent
args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
env:
- name: ISTIO_META_DNS_AUTO_ALLOCATE
value: "true"
- name: ISTIO_META_DNS_CAPTURE
value: "true"
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
istio-proxy 容器:
containers:
- name: istio-proxy
image: docker.io/istio/proxyv2:1.11.4
imagePullPolicy: IfNotPresent
args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --proxyLogLevel=warning
- --proxyComponentLogLevel=misc:error
- --log_output_level=default:info
- --concurrency
- "2"
ports:
- name: http-envoy-prom
containerPort: 15090
protocol: TCP
readinessProbe:
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
failureThreshold: 30
initialDelaySeconds: 1
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
env:
- name: PROXY_CONFIG
value: |
{"proxyMetadata":{"ISTIO_META_DNS_AUTO_ALLOCATE":"true","ISTIO_META_DNS_CAPTURE":"true"}}
- name: ISTIO_META_DNS_AUTO_ALLOCATE
value: "true"
- name: ISTIO_META_DNS_CAPTURE
value: "true"
...
在這些片段中有一些有意思的事情:
- 這兩個容器都是由同一個鏡像提供的,也就是 docker.io/istio/proxyv2:1.11。這意味着什麼,它又是如何運行的呢?istio-iptables 和 proxy(在 args 部分下面)命令被封裝到了鏡像中的 pilot-agent 二進制文件中。所以,如果在 istio-proxy 容器中運行 pilot-agent 二進制文件的話
kubectl exec $(kubectl get po -l app=sleep -n apps -o jsonpath="{.items[0].metadata.name}") -n apps -c istio-proxy -- pilot-agent
輸出如下所示:
Istio Pilot agent runs in the sidecar or gateway container and bootstraps Envoy.
Usage:
pilot-agent [command]
Available Commands:
completion generate the autocompletion script for the specified shell
help Help about any command
istio-clean-iptables Clean up iptables rules for Istio Sidecar
istio-iptables Set up iptables rules for Istio Sidecar
proxy XDS proxy agent
request Makes an HTTP request to the Envoy admin API
version Prints out build version information
wait Waits until the Envoy proxy is ready
- 爲了儘可能減少攻擊面,istio-init 容器中的 securityContext 小節(它是 PodSecurityContext 對象 的一部分)標記該容器將以 root 權限運行(runAsUser: 0),但是除了 NET_ADMIN 和 NET_RAW 能力之外,其他的 Linux 能力都被禁用了。這些能力爲 istio-init init 容器提供了運行時的權限,以重寫應用 pod 的 iptables。更多細節,請參閱 Istio 的文檔。
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
另一方面,istio-proxy 容器以 1337 用戶在限制權限下運行。因爲這是一個保留用戶,所以應用工作負載的 UID(User ID)必須要與之不同,不能與 1337 衝突。1337 UID 是由 Istio 團隊任意選擇的,以便於將流量重定向到 istio-proxy 容器。我們可以看到在初始化 iptables 的時候,1337 也作爲了 istio-iptables 的參數。由於這個容器會與應用工作負載一起運行,Istio 還確保它對根文件系統只有讀的權限。
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
- istio-proxy 容器運行時會使用如下所示的就緒性(readiness)探針。在 Kubernetes 中,kubelet 使用這個探針確定 istio-proxy 是否已經準備好接收流量。只有當 istio-proxy 容器以及所有相關的應用容器均處於運行狀態並且健康探針成功執行的情況下,kubelet 纔會將 Pod 視爲達到就緒狀態。如果針對服務器路徑 “/healthz/ready”(在 pilot-agent 源碼中定義的)的處理器返回成功的狀態碼,kubelet 就會認定容器處於健康狀態。failureThreshold 配置指定了在將容器視爲未就緒之前,這個就緒性探針允許連續失敗的次數。
readinessProbe:
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
initialDelaySeconds: 1
failureThreshold: 30
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
05 sidecar 注入分析
Istio 採用了兩種不同的方式將 sidecar 代理注入應用的工作負載中,分別是手動和自動方式。這兩種方法都遵循相同的注入原則,那就是指定的 “某些” 應用工作負載(這能夠以更高級的 Kubernetes 資源的形式來進行定義,如 Deployment、Statefulset、DaemonSet,甚至可以作爲 Pod)允許 Kubernetes 使用 sidecar 注入模板和配置參數(istio-sidecar-injector configmap)注入 sidecar 容器。
Istio 中的手動 sidecar 注入
在這兩種方法中,手動方式更易於理解。手動注入是通過 istioctl 命令並藉助 kube-inject 參數完成的。你可以使用下面的任何一種格式來注入:
istioctl kube-inject -f application.yaml | kubectl apply -f -
或者
kubectl apply -f <(istioctl kube-inject -f application.yaml)
當使用 istioctl kube-inject 來注入 sidecar 的時候,默認它會使用集羣中名爲 istio-sidecar-injector Kubernetes configmap。它是以一組標記的形式提供的,我們可以聲明它們以自定義這種行爲:
--injectConfigFile string Injection configuration filename. Cannot be used with --injectConfigMapName
--meshConfigFile string Mesh configuration filename. Takes precedence over --meshConfigMapName if set
--meshConfigMapName string ConfigMap name for Istio mesh configuration, key should be "mesh" (default "istio")
--injectConfigMapNam string ConfigMap name for Istio sidecar injection, key should be "config" (default "istio-sidecar-injector")
注意,在 istioctl kube-inject 中,--injectConfigMapNam 是一個 隱藏標記,它允許我們重寫集羣中 sidecar 的注入配置。
另外,注入也可以通過配置的本地副本和上述標記來實現:
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
istioctl kube-inject \
--injectConfigFile inject-config.yaml \
--meshConfigFile mesh-config.yaml \
--valuesFile inject-values.yaml \
--filename application.yaml \
| kubectl apply -f -
必須要注意的是,在手動注入的時候,不要破壞 sidecar,尤其是使用自定義配置的時候。
Istio 中的自動 sidecar 注入
這種方式被認爲是 Istio 中注入 sidecar 的標準方法。與手動方式相比,它涉及的配置步驟更少,但是它取決於底層的 Kubernetes 分發版本是否啓用了對 admission 控制器 的支持。Istio 使用了一個 mutating webhook admission 控制器 來實現這一點。
如下是 Kubernetes mutating admission 在 sidecar 注入時的處理過程:
-
首先,istio-sidecar-injector mutating 配置會在 Istio 安裝過程中注入進來,並且會發送一個包含了所有 pod 信息的 webhook 請求到 istiod 控制器。
-
接下來,控制器會在運行時修改 pod 規範,將一個 init 和 sidecar 容器代理引入到實際的 pod 規範中。
-
然後,控制器將修改後的對象返回給 admission webhook 進行對象校驗。
-
在檢驗完成後,修改後包含所有 sidecar 容器的 pod 規範會進行部署。
關於完整的配置,請使用如下的命令 kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml 進行查閱。爲了簡潔起見,下面的片段中僅包含了四個 webhook 配置中的兩個:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: namespace.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: namespace.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: object.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: DoesNotExist
- key: istio.io/rev
operator: DoesNotExist
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: In
values:
- "true"
- key: istio.io/rev
operator: DoesNotExist
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
這個配置會告訴 Kubernetes mutating 控制器在 HTTPS 端口上安全地將請求發送到 istiod 服務的 “/inject” 端點。在調用 mutating webhook 之前,Kubernetes 會檢查發送請求的用戶是否允許發起該請求。在 Istio 中,webhook 是作爲 istiod 二進制文件的一部分實現的。
注入可以使用命名空間級別的標籤(istio-injection=enabled),也可以使用對象級別的註解(sidecar.istio.io/inject="true")來觸發。每個 webhook 配置在 namespaceSelector 和 objectSelector 中爲這些觸發器定義了匹配規則。當注入基於命名空間級別定義的標籤觸發時,在命名空間中創建的任何部署對象(Deployment、StatefulSet、DaemonSet)都將注入 sidecar 代理的變更。下面是對匹配規則的小結。
在注入 Pod 清單時,也可以直接變更 pod 對象(如果命名空間還沒有標籤的話)。Pod 清單必須要有一個 sidecar.istio.io/inject="true" 標籤。舉例來說:
apiVersion: v1
kind: Pod
metadata:
name: sleep
namespace: apps
labels:
app: sleep
sidecar.istio.io/inject: "true"
...
到目前爲止,我們已經瞭解了 Istio 的基礎知識、數據平面和控制平面、網絡,以及 Envoy 代理的 sidecar 注入,並且展示了 Istio 如何使用演示環境在 pod 模板中注入 init 和 sidecar 容器以及這些容器的配置。在後續的博客文章中,我們將分析如何配置和管理 iptables。
原文鏈接: https://www.solo.io/blog/istios-networking-in-depth/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/rwT_5fSdIDqkmcg0JMDV-g