理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

ServiceMesher發表於2018-09-11

以往有很多文章講解 Istio 是如何做 Sidecar 注入的,但是沒有講解注入之後 Sidecar 工作的細節。本文將帶大家詳細瞭解 Istio 是如何將 Envoy 作為 Sidecar 的方式注入到應用程式 Pod 中,及 Sidecar 是如何做劫持流量的。

本文轉載自:jimmysong.io/posts/envoy…

在講解 Istio 如何將 Envoy 代理注入到應用程式 Pod 中之前,我們需要先了解以下幾個概念:

  • Sidecar 模式:容器應用模式之一,Service Mesh 架構的一種實現方式。

  • Init 容器:Pod 中的一種專用的容器,在應用程式容器啟動之前執行,用來包含一些應用映象中不存在的實用工具或安裝指令碼。

  • iptables:流量劫持是通過 iptables 轉發實現的。

檢視目前 productpage-v1-745ffc55b7-2l2lw Pod 中執行的容器:

$ kubectl -n default get pod productpage-v1-745ffc55b7-2l2lw -o=jsonpath='{..spec.containers[*].name}'
productpage istio-proxy複製程式碼

productpage 即應用容器,istio-proxy 即 Envoy 代理的 sidecar 容器。另外該 Pod 中實際上還執行過一個 Init 容器,因為它執行結束就自動終止了,所以我們看不到該容器的存在。關注 jsonpath 的用法請參考 JSONPath Support

Sidecar 模式

在瞭解 Istio 使用 Sidecar 注入之前,需要先說明下什麼是 Sidecar 模式。Sidecar 是容器應用模式的一種,也是在 Service Mesh 中發揚光大的一種模式,詳見 Service Mesh 架構解析,其中詳細描述了節點代理Sidecar 模式的 Service Mesh 架構。

使用 Sidecar 模式部署服務網格時,無需在節點上執行代理(因此您不需要基礎結構的協作),但是叢集中將執行多個相同的 Sidecar 副本。從另一個角度看:我可以為一組微服務部署到一個服務網格中,你也可以部署一個有特定實現的服務網格。在 Sidecar 部署方式中,你會為每個應用的容器部署一個伴生容器。Sidecar 接管進出應用容器的所有流量。在 Kubernetes 的 Pod 中,在原有的應用容器旁邊執行一個 Sidecar 容器,可以理解為兩個容器共享儲存、網路等資源,可以廣義的將這個注入了 Sidecar 容器的 Pod 理解為一臺主機,兩個容器共享主機資源。

例如下圖 SOFAMesh & SOFA MOSN—基於Istio構建的用於應對大規模流量的Service Mesh解決方案的架構圖中描述的,MOSN 作為 Sidecar 的方式和應用執行在同一個 Pod 中,攔截所有進出應用容器的流量,SOFAMesh 相容 Istio,其中使用 Go 語言開發的 SOFAMosn 替換了 Envoy。

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

注意:下文中所指的 Sidecar 都是指的 Envoy 代理容器。

Init 容器

Init 容器是一種專用容器,它在應用程式容器啟動之前執行,用來包含一些應用映象中不存在的實用工具或安裝指令碼。

一個 Pod 中可以指定多個 Init 容器,如果指定了多個,那麼 Init 容器將會按順序依次執行。只有當前面的 Init 容器必須執行成功後,才可以執行下一個 Init 容器。當所有的 Init 容器執行完成後,Kubernetes 才初始化 Pod 和執行應用容器。

Init 容器使用 Linux Namespace,所以相對應用程式容器來說具有不同的檔案系統檢視。因此,它們能夠具有訪問 Secret 的許可權,而應用程式容器則不能。

在 Pod 啟動過程中,Init 容器會按順序在網路和資料卷初始化之後啟動。每個容器必須在下一個容器啟動之前成功退出。如果由於執行時或失敗退出,將導致容器啟動失敗,它會根據 Pod 的 restartPolicy 指定的策略進行重試。然而,如果 Pod 的 restartPolicy 設定為 Always,Init 容器失敗時會使用 RestartPolicy 策略。

在所有的 Init 容器沒有成功之前,Pod 將不會變成 Ready 狀態。Init 容器的埠將不會在 Service 中進行聚集。 正在初始化中的 Pod 處於 Pending 狀態,但應該會將 Initializing 狀態設定為 true。Init 容器執行完成以後就會自動終止。

關於 Init 容器的詳細資訊請參考 Init 容器 - Kubernetes 中文指南/雲原生應用架構實踐手冊

Sidecar 注入示例分析

我們看下 Istio 官方示例 bookinfoproductpage 的 YAML 配置,關於 bookinfo 應用的詳細 YAML 配置請參考 bookinfo.yaml

apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: productpage-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: productpage
        version: v1
    spec:
      containers:
      - name: productpage
        image: istio/examples-bookinfo-productpage-v1:1.8.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080複製程式碼

再檢視下 productpage 容器的 Dockerfile

FROM python:2.7-slim
​
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY productpage.py /opt/microservices/
COPY templates /opt/microservices/templates
COPY requirements.txt /opt/microservices/
EXPOSE 9080
WORKDIR /opt/microservices
CMD python productpage.py 9080複製程式碼

我們看到 Dockerfile 中沒有配置 ENTRYPOINT,所以 CMD 的配置 python productpage.py 9080 將作為預設的 ENTRYPOINT,記住這一點,再看下注入 sidecar 之後的配置。

$ istioctl kube-inject -f yaml/istio-bookinfo/bookinfo.yaml複製程式碼

我們只擷取其中與 productpage 相關的 ServiceDeployment 配置部分。

apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  creationTimestamp: null
  name: productpage-v1
spec:
  replicas: 1
  strategy: {}
  template:
    metadata:
      annotations:
        sidecar.istio.io/status: '{"version":"fde14299e2ae804b95be08e0f2d171d466f47983391c00519bbf01392d9ad6bb","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
      creationTimestamp: null
      labels:
        app: productpage
        version: v1
    spec:
      containers:
      - image: istio/examples-bookinfo-productpage-v1:1.8.0
        imagePullPolicy: IfNotPresent
        name: productpage
        ports:
        - containerPort: 9080
        resources: {}
      - args:
        - proxy
        - sidecar
        - --configPath
        - /etc/istio/proxy
        - --binaryPath
        - /usr/local/bin/envoy
        - --serviceCluster
        - productpage
        - --drainDuration
        - 45s
        - --parentShutdownDuration
        - 1m0s
        - --discoveryAddress
        - istio-pilot.istio-system:15007
        - --discoveryRefreshDelay
        - 1s
        - --zipkinAddress
        - zipkin.istio-system:9411
        - --connectTimeout
        - 10s
        - --statsdUdpAddress
        - istio-statsd-prom-bridge.istio-system:9125
        - --proxyAdminPort
        - "15000"
        - --controlPlaneAuthPolicy
        - NONE
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: INSTANCE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: ISTIO_META_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: ISTIO_META_INTERCEPTION_MODE
          value: REDIRECT
        image: jimmysong/istio-release-proxyv2:1.0.0
        imagePullPolicy: IfNotPresent
        name: istio-proxy
        resources:
          requests:
            cpu: 10m
        securityContext:
          privileged: false
          readOnlyRootFilesystem: true
          runAsUser: 1337
        volumeMounts:
        - mountPath: /etc/istio/proxy
          name: istio-envoy
        - mountPath: /etc/certs/
          name: istio-certs
          readOnly: true
      initContainers:
      - args:
        - -p
        - "15001"
        - -u
        - "1337"
        - -m
        - REDIRECT
        - -i
        - '*'
        - -x
        - ""
        - -b
        - 9080,
        - -d
        - ""
        image: jimmysong/istio-release-proxy_init:1.0.0
        imagePullPolicy: IfNotPresent
        name: istio-init
        resources: {}
        securityContext:
          capabilities:
            add:
            - NET_ADMIN
          privileged: true
      volumes:
      - emptyDir:
          medium: Memory
        name: istio-envoy
      - name: istio-certs
        secret:
          optional: true
          secretName: istio.default
status: {}複製程式碼

我們看到 Service 的配置沒有變化,所有的變化都在 Deployment 裡,Istio 給應用 Pod 注入的配置主要包括:

  • Init 容器 istio-init:用於給 Sidecar 容器即 Envoy 代理做初始化,設定 iptables 埠轉發

  • Envoy sidecar 容器 istio-proxy:執行 Envoy 代理

接下來將分別解析下這兩個容器。

Init 容器解析

Istio 在 Pod 中注入的 Init 容器名為 istio-init,我們在上面 Istio 注入完成後的 YAML 檔案中看到了該容器的啟動引數:

-p 15001 -u 1337 -m REDIRECT -i '*' -x "" -b 9080 -d ""複製程式碼

我們再檢查下該容器的 Dockerfile 看看 ENTRYPOINT 是什麼以確定啟動時執行的命令。

FROM ubuntu:xenial
RUN apt-get update && apt-get install -y \
    iproute2 \
    iptables \
 && rm -rf /var/lib/apt/lists/*
ADD istio-iptables.sh /usr/local/bin/
ENTRYPOINT ["/usr/local/bin/istio-iptables.sh"]複製程式碼

我們看到 istio-init 容器的入口是 /usr/local/bin/istio-iptables.sh 指令碼,再按圖索驥看看這個指令碼里到底寫的什麼,該指令碼的位置在 Istio 原始碼倉庫的 tools/deb/istio-iptables.sh,一共 300 多行,就不貼在這裡了。下面我們就來解析下這個啟動指令碼。

Init 容器啟動入口

Init 容器的啟動入口是 /usr/local/bin/istio-iptables.sh 指令碼,該指令碼的用法如下:

$ istio-iptables.sh -p PORT -u UID -g GID [-m mode] [-b ports] [-d ports] [-i CIDR] [-x CIDR] [-h]
  -p: 指定重定向所有 TCP 流量的 Envoy 埠(預設為 $ENVOY_PORT = 15001)
  -u: 指定未應用重定向的使用者的 UID。通常,這是代理容器的 UID(預設為 $ENVOY_USER 的 uid,istio_proxy 的 uid 或 1337)
  -g: 指定未應用重定向的使用者的 GID。(與 -u param 相同的預設值)
  -m: 指定入站連線重定向到 Envoy 的模式,“REDIRECT” 或 “TPROXY”(預設為 $ISTIO_INBOUND_INTERCEPTION_MODE)
  -b: 逗號分隔的入站埠列表,其流量將重定向到 Envoy(可選)。使用萬用字元 “*” 表示重定向所有埠。為空時表示禁用所有入站重定向(預設為 $ISTIO_INBOUND_PORTS-d: 指定要從重定向到 Envoy 中排除(可選)的入站埠列表,以逗號格式分隔。使用萬用字元“*” 表示重定向所有入站流量(預設為 $ISTIO_LOCAL_EXCLUDE_PORTS)
  -i: 指定重定向到 Envoy(可選)的 IP 地址範圍,以逗號分隔的 CIDR 格式列表。使用萬用字元 “*” 表示重定向所有出站流量。空列表將禁用所有出站重定向(預設為 $ISTIO_SERVICE_CIDR)
  -x: 指定將從重定向中排除的 IP 地址範圍,以逗號分隔的 CIDR 格式列表。使用萬用字元 “*” 表示重定向所有出站流量(預設為 $ISTIO_SERVICE_EXCLUDE_CIDR)。
​
環境變數位於 $ISTIO_SIDECAR_CONFIG(預設在:/var/lib/istio/envoy/sidecar.env)複製程式碼

通過檢視該指令碼你將看到,以上傳入的引數都會重新組裝成 iptables 命令的引數。

再參考 istio-init 容器的啟動引數,完整的啟動命令如下:

$ /usr/local/bin/istio-iptables.sh -p 15001 -u 1337 -m REDIRECT -i '*' -x "" -b 9080 -d ""複製程式碼

該容器存在的意義就是讓 Envoy 代理可以攔截所有的進出 Pod 的流量,即將入站流量重定向到 Sidecar,再攔截應用容器的出站流量經過 Sidecar 處理後再出站。

命令解析

這條啟動命令的作用是:

  • 將應用容器的所有流量都轉發到 Envoy 的 15001 埠。

  • 使用 istio-proxy 使用者身份執行, UID 為 1337,即 Envoy 所處的使用者空間,這也是 istio-proxy 容器預設使用的使用者,見 YAML 配置中的 runAsUser 欄位。

  • 使用預設的 REDIRECT 模式來重定向流量。

  • 將所有出站流量都重定向到 Envoy 代理。

  • 將所有訪問 9080 埠(即應用容器 productpage 的埠)的流量重定向到 Envoy 代理。

因為 Init 容器初始化完畢後就會自動終止,因為我們無法登陸到容器中檢視 iptables 資訊,但是 Init 容器初始化結果會保留到應用容器和 Sidecar 容器中。

istio-proxy 容器解析

為了檢視 iptables 配置,我們需要登陸到 Sidecar 容器中使用 root 使用者來檢視,因為 kubectl 無法使用特權模式來遠端操作 docker 容器,所以我們需要登陸到 productpage Pod 所在的主機上使用 docker 命令登陸容器中檢視。

檢視 productpage Pod 所在的主機。

$ kubectl -n default get pod -l app=productpage -o wide
NAME                              READY     STATUS    RESTARTS   AGE       IP             NODE
productpage-v1-745ffc55b7-2l2lw   2/2       Running   0          1d        172.33.78.10   node3複製程式碼

從輸出結果中可以看到該 Pod 執行在 node3 上,使用 vagrant 命令登陸到 node3 主機中並切換為 root 使用者。

$ vagrant ssh node3
$ sudo -i複製程式碼

檢視 iptables 配置,列出 NAT(網路地址轉換)表的所有規則,因為在 Init 容器啟動的時候選擇給 istio-iptables.sh 傳遞的引數中指定將入站流量重定向到 Envoy 的模式為 “REDIRECT”,因此在 iptables 中將只有 NAT 表的規格配置,如果選擇 TPROXY 還會有 mangle 表配置。iptables 命令的詳細用法請參考 iptables,規則配置請參考 iptables 規則配置

理解 iptables

iptables 是 Linux 核心中的防火牆軟體 netfilter 的管理工具,位於使用者空間,同時也是 netfilter 的一部分。Netfilter 位於核心空間,不僅有網路地址轉換的功能,也具備資料包內容修改、以及資料包過濾等防火牆功能。

在瞭解 Init 容器初始化的 iptables 之前,我們先來了解下 iptables 和規則配置。

iptables 中的表

Init 容器中使用的的 iptables 版本是 v1.6.0,共包含 5 張表:

  1. raw 用於配置資料包,raw 中的資料包不會被系統跟蹤。

  2. filter 是用於存放所有與防火牆相關操作的預設表。

  3. nat 用於 網路地址轉換(例如:埠轉發)。

  4. mangle 用於對特定資料包的修改(參考損壞資料包)。

  5. security 用於強制訪問控制 網路規則。

:在本示例中只用到了 nat 表。

不同的表中的具有的鏈型別如下表所示:

規則名稱rawfilternatmanglesecurity
PREROUTING
INPUT
OUTPUT
POSTROUTING
FORWARD

下圖是 iptables 的呼叫鏈順序。

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

關於 iptables 的詳細介紹請參考常見 iptables 使用規則場景整理

iptables 命令

iptables 命令的主要用途是修改這些表中的規則。iptables 命令格式如下:

$ iptables [-t 表名] 命令選項[鏈名][條件匹配][-j 目標動作或跳轉]複製程式碼

Init 容器中的 /istio-iptables.sh 啟動入口指令碼就是執行 iptables 初始化的。

理解 iptables 規則

檢視 istio-proxy 容器中的預設的 iptables 規則,預設檢視的是 filter 表中的規則。

$ iptables -L -v
Chain INPUT (policy ACCEPT 350K packets, 63M bytes)
 pkts bytes target     prot opt in     out     source               destination
​
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
​
Chain OUTPUT (policy ACCEPT 18M packets, 1916M bytes)
 pkts bytes target     prot opt in     out     source               destination複製程式碼

我們看到三個預設的鏈,分別是 INPUT、FORWARD 和 OUTPUT,每個鏈中的第一行輸出表示鏈名稱(在本例中為INPUT/FORWARD/OUTPUT),後跟預設策略(ACCEPT)。

下圖是 iptables 的建議結構圖,流量在經過 INPUT 鏈之後就進入了上層協議棧,比如

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

圖片來自常見 iptables 使用規則場景整理

每條鏈中都可以新增多條規則,規則是按照順序從前到後執行的。我們來看下規則的表頭定義。

  • pkts:處理過的匹配的報文數量

  • bytes:累計處理的報文大小(位元組數)

  • target:如果報文與規則匹配,指定目標就會被執行。

  • prot:協議,例如 tdpudpicmpall

  • opt:很少使用,這一列用於顯示 IP 選項。

  • in:入站網路卡。

  • out:出站網路卡。

  • source:流量的源 IP 地址或子網,後者是 anywhere

  • destination:流量的目的地 IP 地址或子網,或者是 anywhere

還有一列沒有表頭,顯示在最後,表示規則的選項,作為規則的擴充套件匹配條件,用來補充前面的幾列中的配置。protoptinoutsourcedestination 和顯示在 destination 後面的沒有表頭的一列擴充套件條件共同組成匹配規則。當流量匹配這些規則後就會執行 target

關於 iptables 規則請參考常見iptables使用規則場景整理

target 支援的型別

target 型別包括 ACCEPT、REJECTDROPLOGSNATMASQUERADEDNATREDIRECTRETURN 或者跳轉到其他規則等。只要執行到某一條鏈中只有按照順序有一條規則匹配後就可以確定報文的去向了,除了 RETURN 型別,類似程式語言中的 return 語句,返回到它的呼叫點,繼續執行下一條規則。target 支援的配置詳解請參考 iptables 詳解(1):iptables 概念

從輸出結果中可以看到 Init 容器沒有在 iptables 的預設鏈路中建立任何規則,而是建立了新的鏈路。

檢視 iptables nat 表中注入的規則

Init 容器通過向 iptables nat 表中注入轉發規則來劫持流量的,下圖顯示的是 productpage 服務中的 iptables 流量劫持的詳細過程。

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

Init 容器啟動時命令列引數中指定了 REDIRECT 模式,因此只建立了 NAT 表規則,接下來我們檢視下 NAT 表中建立的規則,這是全文中的重點部分,前面講了那麼多都是為它做鋪墊的。下面是檢視 nat 表中的規則,其中鏈的名字中包含 ISTIO 字首的是由 Init 容器注入的,規則匹配是根據下面顯示的順序來執行的,其中會有多次跳轉。

# 檢視 NAT 表中規則配置的詳細資訊
$ iptables -t nat -L -v
# PREROUTING 鏈:用於目標地址轉換(DNAT),將所有入站 TCP 流量跳轉到 ISTIO_INBOUND 鏈上
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    2   120 ISTIO_INBOUND  tcp  --  any    any     anywhere             anywhere
​
# INPUT 鏈:處理輸入資料包,非 TCP 流量將繼續 OUTPUT 鏈
Chain INPUT (policy ACCEPT 2 packets, 120 bytes)
 pkts bytes target     prot opt in     out     source               destination
​
# OUTPUT 鏈:將所有出站資料包跳轉到 ISTIO_OUTPUT 鏈上
Chain OUTPUT (policy ACCEPT 41146 packets, 3845K bytes)
 pkts bytes target     prot opt in     out     source               destination
   93  5580 ISTIO_OUTPUT  tcp  --  any    any     anywhere             anywhere
​
# POSTROUTING 鏈:所有資料包流出網路卡時都要先進入POSTROUTING 鏈,核心根據資料包目的地判斷是否需要轉發出去,我們看到此處未做任何處理
Chain POSTROUTING (policy ACCEPT 41199 packets, 3848K bytes)
 pkts bytes target     prot opt in     out     source               destination
​
# ISTIO_INBOUND 鏈:將所有目的地為 9080 埠的入站流量重定向到 ISTIO_IN_REDIRECT 鏈上
Chain ISTIO_INBOUND (1 references)
 pkts bytes target     prot opt in     out     source               destination
    2   120 ISTIO_IN_REDIRECT  tcp  --  any    any     anywhere             anywhere             tcp dpt:9080
​
# ISTIO_IN_REDIRECT 鏈:將所有的入站流量跳轉到本地的 15001 埠,至此成功的攔截了流量懂啊 Envoy 
Chain ISTIO_IN_REDIRECT (1 references)
 pkts bytes target     prot opt in     out     source               destination
    2   120 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15001
​
# ISTIO_OUTPUT 鏈:選擇需要重定向到 Envoy(即本地) 的出站流量,所有非 localhost 的流量全部轉發到 ISTIO_REDIRECT。為了避免流量在該 Pod 中無限迴圈,所有到 istio-proxy 使用者空間的流量都返回到它的呼叫點中的下一條規則,本例中即 OUTPUT 鏈,因為跳出 ISTIO_OUTPUT 規則之後就進入下一條鏈 POSTROUTING。如果目的地非 localhost 就跳轉到 ISTIO_REDIRECT;如果流量是來自 istio-proxy 使用者空間的,那麼就跳出該鏈,返回它的呼叫鏈繼續執行下一條規則(OUPT 的下一條規則,無需對流量進行處理);所有的非 istio-proxy 使用者空間的目的地是 localhost 的流量就跳轉到 ISTIO_REDIRECT
Chain ISTIO_OUTPUT (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ISTIO_REDIRECT  all  --  any    lo      anywhere            !localhost
   40  2400 RETURN     all  --  any    any     anywhere             anywhere             owner UID match istio-proxy
    0     0 RETURN     all  --  any    any     anywhere             anywhere             owner GID match istio-proxy    
    0     0 RETURN     all  --  any    any     anywhere             localhost
   53  3180 ISTIO_REDIRECT  all  --  any    any     anywhere             anywhere
​
# ISTIO_REDIRECT 鏈:將所有流量重定向到 Envoy(即本地) 的 15001 埠
Chain ISTIO_REDIRECT (2 references)
 pkts bytes target     prot opt in     out     source               destination
   53  3180 REDIRECT   tcp  --  any    any     anywhere             anywhere             redir ports 15001複製程式碼

iptables 顯示的鏈的順序,即流量規則匹配的順序。其中要特別注意 ISTIO_OUTPUT 鏈中的規則配置。為了避免流量一直在 Pod 中無限迴圈,所有到 istio-proxy 使用者空間的流量都返回到它的呼叫點中的下一條規則,本例中即 OUTPUT 鏈,因為跳出 ISTIO_OUTPUT 規則之後就進入下一條鏈 POSTROUTING

ISTIO_OUTPUT 鏈規則匹配的詳細過程如下:

  • 如果目的地非 localhost 就跳轉到 ISTIO_REDIRECT 鏈

  • 所有來自 istio-proxy 使用者空間的流量跳轉到它的呼叫點 OUTPUT 繼續執行 OUTPUT 鏈的下一條規則,因為 OUTPUT 鏈中沒有下一條規則了,所以會繼續執行 POSTROUTING 鏈然後跳出 iptables,直接訪問目的地

  • 如果目的地是 localhost 但是流量又不是來自 istio-proxy 使用者空間的就跳轉到 ISTIO_REDIRECT

以上 iptables 規則都是 Init 容器啟動的時使用 istio-iptables.sh 指令碼生成的,詳細過程可以檢視該指令碼。

檢視 Envoy 執行狀態

首先檢視 proxyv2 映象的 Dockerfile

FROM istionightly/base_debug
ARG proxy_version
ARG istio_version
​
# 安裝 Envoy
ADD envoy /usr/local/bin/envoy
# 使用環境變數的方式明文指定 proxy 的版本/功能
ENV ISTIO_META_ISTIO_PROXY_VERSION "1.1.0"
# 使用環境變數的方式明文指定 proxy 明確的 sha,用於指定版本的配置和除錯
ENV ISTIO_META_ISTIO_PROXY_SHA $proxy_version
# 環境變數,指定明確的構建號,用於除錯
ENV ISTIO_META_ISTIO_VERSION $istio_version
​
ADD pilot-agent /usr/local/bin/pilot-agent
ADD envoy_pilot.yaml.tmpl /etc/istio/proxy/envoy_pilot.yaml.tmpl
ADD envoy_policy.yaml.tmpl /etc/istio/proxy/envoy_policy.yaml.tmpl
ADD envoy_telemetry.yaml.tmpl /etc/istio/proxy/envoy_telemetry.yaml.tmpl
ADD istio-iptables.sh /usr/local/bin/istio-iptables.sh
COPY envoy_bootstrap_v2.json /var/lib/istio/envoy/envoy_bootstrap_tmpl.json
RUN chmod 755 /usr/local/bin/envoy /usr/local/bin/pilot-agent
# 將 istio-proxy 使用者加入 sudo 許可權以允許執行 tcpdump 和其他除錯命令
RUN useradd -m --uid 1337 istio-proxy && \
    echo "istio-proxy ALL=NOPASSWD: ALL" >> /etc/sudoers && \
    chown -R istio-proxy /var/lib/istio
# 使用 pilot-agent 來啟動 Envoy
ENTRYPOINT ["/usr/local/bin/pilot-agent"]複製程式碼

該容器的啟動入口是 pilot-agent 命令,根據 YAML 配置中傳遞的引數,詳細的啟動命令入下:

/usr/local/bin/pilot-agent proxy sidecar --configPath /etc/istio/proxy --binaryPath /usr/local/bin/envoy --serviceCluster productpage --drainDuration 45s --parentShutdownDuration 1m0s --discoveryAddress istio-pilot.istio-system:15007 --discoveryRefreshDelay 1s --zipkinAddress zipkin.istio-system:9411 --connectTimeout 10s --statsdUdpAddress istio-statsd-prom-bridge.istio-system:9125 --proxyAdminPort 15000 --controlPlaneAuthPolicy NONE複製程式碼

主要配置了 Envoy 二進位制檔案的位置、服務發現地址、服務叢集名、監控指標上報地址、Envoy 的管理埠、熱重啟時間等,詳細用法請參考 Istio官方文件 pilot-agent 的用法

pilot-agent 是容器中 PID 為 1 的啟動程式,它啟動時又建立了一個 Envoy 程式,如下:

/usr/local/bin/envoy -c /etc/istio/proxy/envoy-rev0.json --restart-epoch 0 --drain-time-s 45 --parent-shutdown-time-s 60 --service-cluster productpage --service-node sidecar~172.33.78.10~productpage-v1-745ffc55b7-2l2lw.default~default.svc.cluster.local --max-obj-name-len 189 -l warn --v2-config-only複製程式碼

我們分別解釋下以上配置的意義。

  • -c /etc/istio/proxy/envoy-rev0.json:配置檔案,支援 .json.yaml.pb.pb_text 格式,pilot-agent 啟動的時候讀取了容器的環境變數後建立的。

  • --restart-epoch 0:Envoy 熱重啟週期,第一次啟動預設為 0,每熱重啟一次該值加 1。

  • --drain-time-s 45:熱重啟期間 Envoy 將耗盡連線的時間。

  • --parent-shutdown-time-s 60: Envoy 在熱重啟時關閉父程式之前等待的時間。

  • --service-cluster productpage:Envoy 執行的本地服務叢集的名字。

  • --service-node sidecar~172.33.78.10~productpage-v1-745ffc55b7-2l2lw.default~default.svc.cluster.local:定義 Envoy 執行的本地服務節點名稱,其中包含了該 Pod 的名稱、IP、DNS 域等資訊,根據容器的環境變數拼出來的。

  • -max-obj-name-len 189:cluster/route_config/listener 中名稱欄位的最大長度(以位元組為單位)

  • -l warn:日誌級別

  • --v2-config-only:只解析 v2 引導配置檔案

詳細配置請參考 Envoy 的命令列選項

檢視 Envoy 的配置檔案 /etc/istio/proxy/envoy-rev0.json

{
  "node": {
    "id": "sidecar~172.33.78.10~productpage-v1-745ffc55b7-2l2lw.default~default.svc.cluster.local",
    "cluster": "productpage",
​
    "metadata": {
          "INTERCEPTION_MODE": "REDIRECT",
          "ISTIO_PROXY_SHA": "istio-proxy:6166ae7ebac7f630206b2fe4e6767516bf198313",
          "ISTIO_PROXY_VERSION": "1.0.0",
          "ISTIO_VERSION": "1.0.0",
          "POD_NAME": "productpage-v1-745ffc55b7-2l2lw",
      "istio": "sidecar"
    }
  },
  "stats_config": {
    "use_all_default_tags": false
  },
  "admin": {
    "access_log_path": "/dev/stdout",
    "address": {
      "socket_address": {
        "address": "127.0.0.1",
        "port_value": 15000
      }
    }
  },
  "dynamic_resources": {
    "lds_config": {
        "ads": {}
    },
    "cds_config": {
        "ads": {}
    },
    "ads_config": {
      "api_type": "GRPC",
      "refresh_delay": {"seconds": 1, "nanos": 0},
      "grpc_services": [
        {
          "envoy_grpc": {
            "cluster_name": "xds-grpc"
          }
        }
      ]
    }
  },
  "static_resources": {
    "clusters": [
    {
    "name": "xds-grpc",
    "type": "STRICT_DNS",
    "connect_timeout": {"seconds": 10, "nanos": 0},
    "lb_policy": "ROUND_ROBIN",
​
    "hosts": [
    {
    "socket_address": {"address": "istio-pilot.istio-system", "port_value": 15010}
    }
    ],
    "circuit_breakers": {
        "thresholds": [
      {
        "priority": "default",
        "max_connections": "100000",
        "max_pending_requests": "100000",
        "max_requests": "100000"
      },
      {
        "priority": "high",
        "max_connections": "100000",
        "max_pending_requests": "100000",
        "max_requests": "100000"
      }]
    },
    "upstream_connection_options": {
      "tcp_keepalive": {
        "keepalive_time": 300
      }
    },
    "http2_protocol_options": { }
    }
​
​
    ,
      {
        "name": "zipkin",
        "type": "STRICT_DNS",
        "connect_timeout": {
          "seconds": 1
        },
        "lb_policy": "ROUND_ROBIN",
        "hosts": [
          {
            "socket_address": {"address": "zipkin.istio-system", "port_value": 9411}
          }
        ]
      }
​
    ]
  },
​
  "tracing": {
    "http": {
      "name": "envoy.zipkin",
      "config": {
        "collector_cluster": "zipkin"
      }
    }
  },
​
​
  "stats_sinks": [
    {
      "name": "envoy.statsd",
      "config": {
        "address": {
          "socket_address": {"address": "10.254.109.175", "port_value": 9125}
        }
      }
    }
  ]
​
}複製程式碼

下圖是使用 Istio 管理的 bookinfo 示例的訪問請求路徑圖。

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

圖片來自 Istio 官方網站

對照 bookinfo 示例的 productpage 的檢視建立的連線。在 productpage-v1-745ffc55b7-2l2lw Pod 的 istio-proxy 容器中使用 root 使用者檢視開啟的埠。

$ lsof -i
COMMAND PID        USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
envoy    11 istio-proxy    9u  IPv4  73951      0t0  TCP localhost:15000 (LISTEN) # Envoy admin 埠
envoy    11 istio-proxy   17u  IPv4  74320      0t0  TCP productpage-v1-745ffc55b7-2l2lw:46862->istio-pilot.istio-system.svc.cluster.local:15010 (ESTABLISHED) # 15010:istio-pilot 的 grcp-xds 埠
envoy    11 istio-proxy   18u  IPv4  73986      0t0  UDP productpage-v1-745ffc55b7-2l2lw:44332->istio-statsd-prom-bridge.istio-system.svc.cluster.local:9125 # 給 Promethues 傳送 metric 的埠
envoy    11 istio-proxy   52u  IPv4  74599      0t0  TCP *:15001 (LISTEN) # Envoy 的監聽埠
envoy    11 istio-proxy   53u  IPv4  74600      0t0  UDP productpage-v1-745ffc55b7-2l2lw:48011->istio-statsd-prom-bridge.istio-system.svc.cluster.local:9125 # 給 Promethues 傳送 metric 埠
envoy    11 istio-proxy   54u  IPv4 338551      0t0  TCP productpage-v1-745ffc55b7-2l2lw:15001->172.17.8.102:52670 (ESTABLISHED) # 52670:Ingress gateway 埠
envoy    11 istio-proxy   55u  IPv4 338364      0t0  TCP productpage-v1-745ffc55b7-2l2lw:44046->172.33.78.9:9091 (ESTABLISHED) # 9091:istio-telemetry 服務的 grpc-mixer 埠
envoy    11 istio-proxy   56u  IPv4 338473      0t0  TCP productpage-v1-745ffc55b7-2l2lw:47210->zipkin.istio-system.svc.cluster.local:9411 (ESTABLISHED) # 9411: zipkin 埠
envoy    11 istio-proxy   58u  IPv4 338383      0t0  TCP productpage-v1-745ffc55b7-2l2lw:41564->172.33.84.8:9080 (ESTABLISHED) # 9080:details-v1 的 http 埠
envoy    11 istio-proxy   59u  IPv4 338390      0t0  TCP productpage-v1-745ffc55b7-2l2lw:54410->172.33.78.5:9080 (ESTABLISHED) # 9080:reivews-v2 的 http 埠
envoy    11 istio-proxy   60u  IPv4 338411      0t0  TCP productpage-v1-745ffc55b7-2l2lw:35200->172.33.84.5:9091 (ESTABLISHED) # 9091:istio-telemetry 服務的 grpc-mixer 埠
envoy    11 istio-proxy   62u  IPv4 338497      0t0  TCP productpage-v1-745ffc55b7-2l2lw:34402->172.33.84.9:9080 (ESTABLISHED) # reviews-v1 的 http 埠
envoy    11 istio-proxy   63u  IPv4 338525      0t0  TCP productpage-v1-745ffc55b7-2l2lw:50592->172.33.71.5:9080 (ESTABLISHED) # reviews-v3 的 http 埠複製程式碼

從輸出級過上可以驗證 Sidecar 是如何接管流量和與 istio-pilot 通訊,及向 Mixer 做遙測資料匯聚的。感興趣的讀者可以再去看看其他幾個服務的 istio-proxy 容器中的 iptables 和埠資訊。

參考

ServiceMesher社群資訊

微信群:聯絡我入群

社群官網:www.servicemesher.com

Slack:servicemesher.slack.com 需要邀請才能加入

Twitter: twitter.com/servicemesh…

GitHub:github.com/

更多Service Mesh諮詢請掃碼關注微信公眾號ServiceMesher。

理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持


相關文章