Istio 服務網格:深入學習網絡流量和架構

作者|Kasun Talwatta   譯者|張衛濱

來源公衆號 | InfoQ 架構頭條

本文首先介紹了 Istio 的基礎知識,然後結合實際的樣例闡釋了 Istio 是如何將 sidecar 容器注入到 Kubernetes 集羣中,並實現流量攔截的。

本文最初發表於 Solo 官方博客,經原作者 Kasun Talwatta 授權,由 InfoQ 中文站翻譯分享。

像 Istio 這樣的服務網格項目會爲我們的架構引入很多的特性和收益,包括更安全地管理集羣中微服務之間的流量、服務發現、請求路由以及服務之前可靠的通信。

儘管 Istio 是平臺中立的服務網格,但是它更受歡迎的使用場景是與 Kubernetes 協作。雖然它如此流行,但對於剛接觸服務網格的人來說,理解 Istio 的網絡和核心機制可能會很複雜和困難,例如:

在本系列的博客文章的第一篇中,我們將會分析 Istio 的架構和實現原理,從而解釋這些機制是如何運行的,我們將會介紹 Istio 的網絡基礎知識、數據平面和控制平面、網絡、以及使用 Envoy 代理的 sidecar 注入。藉助一個演示環境,我們將會看到 Istio 如何注入 init 和 sidecar 容器,以及這些容器在 pod 模板中的配置。

01 Istio 的網絡基礎

Istio 概覽已在 官方文檔 中進行了詳盡的介紹,但在繼續後面的內容之前,我們着重再看一下它的幾個核心組件。

Istio 主要由兩部分組成,分別是數據平面和控制平面。

02 樣例環境中的 Istio 網絡

在介紹下面的內容之前,我們創建一個本地的沙箱環境。這能確保我們會有一個部署在 Kubernetes 中的 Istio 服務網格以及運行在網格中的示例應用。

所需的工具包括:

部署 Istio 服務網格的步驟如下:

  1. 使用 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
  1. 集羣啓動之後,執行如下的命令來搭建 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
  1. 部署示例應用
# 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
  1. 驗證 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 模板的形式添加和運行額外容器的過程。在這個注入過程中,會提供兩個額外的容器,分別是:

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

在這些片段中有一些有意思的事情:

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
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
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 注入時的處理過程:

關於完整的配置,請使用如下的命令 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 代理的變更。下面是對匹配規則的小結。

fXwxBF

在注入 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