天天看點

Istio中流量劫持機制

文章目錄

    • Istio流量劫持機制
    • 環境介紹
      • 注入Sidecar
      • Init container
      • istio-proxy container
    • 用戶端請求
      • Iptables規則
      • Envoy Config
    • 服務端接收請求
    • Service Mesh 涉及的網絡棧

Istio流量劫持機制

在Service Mesh架構中,Istio通過流量劫持機制,讓Pod中的應用通路其他服務時都經過Istio代理,實作對流量的控制。基于Istio可以實作請求路由、服務發現和負載均衡、故障處理、故障注入和規則配置等功能。,因為所有Pod中的進出流量都經過Istio的資料面,是以也可以實作日志記錄和追蹤。如下是Istio的架構圖:

Istio中流量劫持機制

下邊通過一個示例操作,看下Istio是怎麼實作流量劫持的,流量被劫持後又是怎麼轉到目的地的。

環境介紹

部署兩個Pod,分别表示用戶端和服務端,通過用戶端發起請求,然後看請求整個鍊路的轉發過程。

# 建立一個namespace
kubectl create ns sidecar
# 注入sidecar
kubectl label ns sidecar istio-injection=enabled
# 部署nginx pod
kubectl apply -f nginx.yaml -n sidecar
# 部署toolbox pod
kubectl apply -f toolbox.yaml -n sidecar
           
  1. nginx.yaml

部署一個nginx pod,并建立service。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    app: nginx
           
  1. toolbox.yaml

toolbox其實就是一個centos,可以在其中執行curl發送請求。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: toolbox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: toolbox
  template:
    metadata:
      labels:
        app: toolbox
        access: "true"
    spec:
      containers:
        - name: toolbox
          image: centos
          command:
            - tail
            - -f
            - /dev/null
           

執行

kubectl get pod,deployment,svc,endpoints -n sidecar -o wide

看下環境情況:

Istio中流量劫持機制
  1. 可以看到兩個Pod分别為 nginx-deployment-85b98978db-48m47(IP:10.10.1.5) 和 toolbox-78555898fb-b9qxq(IP:10.10.1.6)
  2. 建立的Service的IP為10.102.37.221
  3. Service的Endpoints目前有一個10.10.1.5,就是運作Nginx的那個Pod,當然,也可以建立多個服務,統一提供服務。

    因為我現在登入在Kubernetes叢集的Master節點,其實作在就可以執行

    curl http://10.102.37.221

    通路Nginx服務。
Istio中流量劫持機制

注入Sidecar

剛才建立namespace後,執行了

kubectl label ns sidecar istio-injection=enabled

可以看下sidecar namespace的配置,執行

kubectl get ns sidecar -oyaml

:

apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2022-09-24T08:12:14Z"
  labels:
    istio-injection: enabled
    kubernetes.io/metadata.name: sidecar
  name: sidecar
  resourceVersion: "8032"
  uid: 3fdb6ab1-71b6-44ae-83c8-636dfa266147
spec:
  finalizers:
  - kubernetes
status:
  phase: Active
           

通過把istio-injection設定為enabled,這樣在本namespace中的pod都會注入Sidecar。注入Sidecar後Pod有什麼變化呢,可以看下 toolbox-78555898fb-b9qxq 這個pod的運作狀态:

Istio中流量劫持機制

其中READY中都是2/2,說明Pod中有兩個容器,并且狀态都是running,檢視Pod中都是什麼容器。

檢視Pod裡初始化容器:

kubectl get pods toolbox-78555898fb-b9qxq -n sidecar  -o jsonpath={.spec.initContainers[*].name}
           

輸出為:istio-init

檢視Pod裡業務容器:

kubectl get pods toolbox-78555898fb-b9qxq -n sidecar  -o jsonpath={.spec.containers[*].name}
           

輸出:toolbox 和 istio-proxy

Init container

通過檢視 toolbox-78555898fb-b9qxq 這個pod的配置,在配置中可以看到istio-init的容器配置資訊:

Istio中流量劫持機制

容器中執行了

istio-iptables

配置了iptables規則,也正是因為這個配置劫持了Pod進出的流量,下邊我們再仔細看規則的内容。istio-init 在Pod啟動時執行,執行完就自動退出了。

istio-proxy container

istio-proxy在Pod主要做流量代理,通過運作Envoy,可以根據Envoy配置實作流量的靈活控制。配置内容比較多這裡就不展示了。是以 toolbox-78555898fb-b9qxq 中展示READY的兩個容器就是toolbox 和 istio-proxy。

用戶端請求

在 toolbox-78555898fb-b9qxq 中通路 nginx-deployment-85b98978db-48m47,這樣在用戶端和服務端中都會發生流量劫持。

[email protected]:~$ kubectl exec -it toolbox-78555898fb-b9qxq -n sidecar sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
sh-4.4# curl http://10.102.37.221
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/" target="_blank" rel="external nofollow" >nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/" target="_blank" rel="external nofollow" >nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
           

執行上邊操作後,請求從用戶端到服務端的大概流程如下:

Istio中流量劫持機制

其中有兩個地方被iptables規則劫持:

  1. 發起curl請求預設請求的是80端口,但是會被iptables OUTPUT劫持轉發到envoy監聽的15001端口
  2. 請求到達Nginx時,目标端口為80,但是會被iptables PREROUTING劫持轉到envoy監聽的15006端口。

Iptables規則

下邊看下iptables規則的内容是什麼,因為Pod中的指令不全,是以使用nsenter指令在主控端上進入容器檢視。首先擷取容器的PID

Istio中流量劫持機制

我的Kubernetes環境分為master和node節點,容器是運作在node節點,是以就在node節點操作

根據上圖,toolbox的容器Pid為36009:

sudo nsenter -t 36009 -n iptables-legacy-save
           

檢視到的iptables規則如下:

# Generated by iptables-save v1.8.7 on Sat Sep 24 23:53:39 2022
*nat
:PREROUTING ACCEPT [9019:541231]
:INPUT ACCEPT [9012:540720]
:OUTPUT ACCEPT [722:64866]
:POSTROUTING ACCEPT [725:65046]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
# 入流量比對這條,走到ISTIO_INBOUND
-A PREROUTING -p tcp -j ISTIO_INBOUND
# 出流量比對這條,走到ISTIO_OUTPUT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
# 入流量比對這條,走到ISTIO_IN_REDIRECT
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
# 目标端口轉換為 15006,針對curl請求目标端口 80 --> 15006
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
# 以上的都不比對,走到ISTIO_REDIRECT
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
# 目标端口轉換為15001,針對curl請求目标端口 80 --> 15001
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Sat Sep 24 23:53:39 2022
           

在toolbox中發起

curl http://10.102.37.221

,預設的端口為80,經過iptables OUTPUT規則目标端口轉換為15001,請求被轉到envoy,在envoy中有listener監聽15001。

Envoy Config

  • listener 15001

請求到達Envoy,被監聽的15001接收,下邊看看Envoy listener 15001的配置,同樣配置較多,隻展示關鍵部分:

istioctl pc listener -n sidecar toolbox-78555898fb-b9qxq --port 15001 -ojson
           
{
    "name": "virtualOutbound",
    "address": {
        "socketAddress": {
            "address": "0.0.0.0",
            "portValue": 15001
        }
    },
    "useOriginalDst": true
}
           

配置中的useOriginalDst設定為true,表示擷取原始目的端口,就是被iptables規則轉換前的80端口,看到這個listener的配置為virtualOutbound,就像它的名字一樣是個虛拟機,是以擷取出來原始的端口80後,就會再被listener 80配置處理。

  • listener 80

同樣,我們看下listener 80的配置:

istioctl pc listener -n sidecar toolbox-78555898fb-b9qxq --port 80 -ojson
           

隻看關鍵配置:

{
        "name": "0.0.0.0_80",
        "address": {
            "socketAddress": {
                "address": "0.0.0.0",
                "portValue": 80
            }
        },
        "routeConfigName": "80"
}
           

經過listener 80後,比對routeConfigName “80”,繼續看route。

  • route “80”

執行

istioctl pc route -n sidecar toolbox-78555898fb-b9qxq --name=80 -ojson

檢視如下:

{
                "name": "nginx.sidecar.svc.cluster.local:80",
                "domains": [
                    "nginx.sidecar.svc.cluster.local",
                    "nginx.sidecar.svc.cluster.local:80",
                    "nginx",
                    "nginx:80",
                    "nginx.sidecar.svc",
                    "nginx.sidecar.svc:80",
                    "nginx.sidecar",
                    "nginx.sidecar:80",
                    "10.102.37.221",
                    "10.102.37.221:80"
                ],
                "routes": [
                    {
                        "name": "default",
                        "match": {
                            "prefix": "/"
                        },
                        "route": {
                            "cluster": "outbound|80||nginx.sidecar.svc.cluster.local",
           

根據上邊的配置,請求會選擇

"cluster": "outbound|80||nginx.sidecar.svc.cluster.local"

處理。現在我們就看下選擇的cluster正式的endpoint是什麼。

  • endpoints

執行如下指令過濾出我們需要的endpoints:

istioctl pc endpoints -n sidecar toolbox-78555898fb-b9qxq --cluster="outbound|80||nginx.sidecar.svc.cluster.local"  -ojson
           

根據配置可以看到最終選擇後端為10.10.1.5,就是我們部署的nginx Pod的位址。

{
    "name": "outbound|80||nginx.sidecar.svc.cluster.local",
    "addedViaApi": true,
    "hostStatuses": [
        {
            "address": {
                "socketAddress": {
                    "address": "10.10.1.5",
                    "portValue": 80
                }
            },
            "stats": [...],
            "healthStatus": {
                "edsHealthStatus": "HEALTHY"
            },
            "weight": 1,
            "locality": {}
        }
    ],
    "circuitBreakers": {...},
    "observabilityName": "outbound|80||nginx.sidecar.svc.cluster.local"
}
           

經過以上流程,請求就從toolbox Pod中出去了,請求的目标位址變為 10.10.1.5,目标端口為80。

服務端接收請求

請求到達服務端一樣會經過iptables規則和Envoy代理,才能最終到達Nginx。分析過程和用戶端一樣,就不再贅述。

Service Mesh 涉及的網絡棧

上邊的請求流程,在兩個Pod中都經過了多次協定棧的處理。

Istio中流量劫持機制

就像上圖一樣,請求一次要經過多次網絡棧的處理,對性能肯定會有影響,既然有問題,就會出現技術去解決,Cilium就可以實作加速,具體架構圖如下:

Istio中流量劫持機制

Cilium底層基于Linux核心的新技術eBPF,保持好奇心,多多探索吧,哈哈。