全部產品
Search
文件中心

Alibaba Cloud Service Mesh:在Service Mesh環境下如何保持服務訪問時的用戶端源IP

更新時間:Jun 30, 2024

本文介紹在Service Mesh環境下如何保持服務訪問時的用戶端源IP。

前提條件

背景資訊

原始IP在很多情況下被廣泛使用,典型的使用情境包括:

  • 應用的存取控制:例如,許多應用程式在檢測到使用者從不同地區登入時強制執行額外的身分識別驗證,可以通過擷取原始IP來完成。

  • 簡單的會話保持:可以根據用戶端的地址,基於源IP做負載平衡,將來自同一個用戶端的請求,轉寄到同一個服務執行個體。

  • 訪問日誌和監控統計:訪問日誌和監控指標中包含真實的源地址,有助於開發人員進行分析統計。

雲上的負載平衡器也支援將用戶端源IP傳遞到後端服務。Istio應提供允許應用程式擷取原始源IP的能力。但在使用Istio時,Pod注入了Sidecar代理之後,所有入站流量都是從Envoy重新導向。目前,Envoy將流量發送到綁定了本地地址(127.0.0.1)的應用程式,所以應用看不到真正的原始IP。

部署應用樣本

  1. 部署sleep應用。

    1. 使用以下內容,建立sleep.yaml

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: sleep
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: sleep
        labels:
          app: sleep
          service: sleep
      spec:
        ports:
        - port: 80
          name: http
        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:
              - mountPath: /etc/sleep/tls
                name: secret-volume
            volumes:
            - name: secret-volume
              secret:
                secretName: sleep-secret
                optional: true
    2. 執行以下命令,部署sleep應用。

      kubectl -n default apply -f  sleep.yaml
  2. 部署httpbin應用。

    1. 使用以下內容,建立httpbin.yaml

      apiVersion: v1
      kind: Service
      metadata:
        name: httpbin
        labels:
          app: httpbin
      spec:
        ports:
        - name: http
          port: 8000
        selector:
          app: httpbin
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: httpbin
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: httpbin
            version: v1
        template:
          metadata:
            labels:
              app: httpbin
              version: v1
          spec:
            containers:
            - image: docker.io/citizenstig/httpbin
              imagePullPolicy: IfNotPresent
              name: httpbin
              ports:
              - containerPort: 8000
    2. 執行以下命令,部署httpbin應用。

      kubectl -n default apply -f  httpbin.yaml

情境一:東西向流量

未開啟TPROXY

在Istio中,東西向服務訪問時,由於Sidecar的注入,所有進出服務的流量均被Envoy攔截代理,然後再由Envoy將請求轉給應用。因此,應用收到的請求的源地址,是Envoy的訪問地址127.0.0.6

  1. 執行以下命令,查看Pod狀態。

    kubectl -n default get pods -o wide

    預期輸出:

    NAME                            READY   STATUS        RESTARTS   AGE     IP             NODE                     NOMINATED NODE   READINESS GATES
    httpbin-c85bdb469-4ll2m         2/2     Running       0          3m22s   172.17.X.XXX   cn-hongkong.10.0.0.XX    <none>           <none>
    sleep-8f764df66-q7dr2           2/2     Running       0          3m9s    172.17.X.XXX   cn-hongkong.10.0.0.XX    <none>           <none>

    由預期輸出得到,sleep應用的地址為172.17.X.XXX

  2. 執行以下命令,從sleep容器發起請求。

    kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip

    預期輸出:

    {
      "origin": "127.0.0.6"
    }

    由預期輸出得到,httpbin應用收到請求的源地址是Envoy的訪問地址127.0.0.6,而不是sleep應用的地址。

  3. 從Socket資訊中確認源IP地址是否為127.0.0.6

    1. 登入到httpbin容器,執行以下命令,安裝netstat。

      apt update & apt install net-tools
    2. 退出httpbin容器,執行以下命令,根據80連接埠查看運行相關資訊。

      kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80

      預期輸出:

      tcp        0      0 172.17.X.XXX:80         127.0.0.6:42691         TIME_WAIT   -

      由預期輸出得到,源IP地址為127.0.0.6

  4. 查看httpbin Pod中的代理日誌內容。

    格式化處理之後的日誌樣本如下:

    {
      "trace_id":null,
      "bytes_received":0,
      "upstream_host":"172.17.X.XXX:80",
      "authority":"httpbin:8000",
      "downstream_remote_address":"172.17.X.XXX:56160",
      "upstream_service_time":"1",
      "upstream_transport_failure_reason":null,
      "istio_policy_status":null,
      "path":"/ip",
      "bytes_sent":28,
      "request_id":"4501a50a-dab0-44c9-b52c-2a4f425a****",
      "protocol":"HTTP/1.1",
      "method":"GET",
      "duration":1,
      "start_time":"2022-11-22T16:09:30.394Z",
      "user_agent":"curl/7.86.0-DEV",
      "upstream_local_address":"127.0.0.6:42169",
      "response_flags":"-",
      "route_name":"default",
      "response_code":200,
      "upstream_cluster":"inbound|80||",
      "x_forwarded_for":null,
      "downstream_local_address":"172.17.X.XXX:80",
      "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local"
    }

    由日誌得到:

    • "downstream_remote_address":"172.17.X.XXX:56160":sleep的地址。

    • "downstream_local_address":"172.17.X.XXX:80":sleep訪問的目標地址。

    • "upstream_local_address":"127.0.0.6:42169":httpbin Envoy串連httpbin的local address(此時獲得的源IP地址是127.0.0.6)。

    • "upstream_host":"172.17.X.XXX:80":httpbin Envoy訪問的目標地址。

開啟TPROXY保持源IP

修改httpbin應用的deployment,使用TPROXY作為入流量攔截模式。

  1. 執行以下命令,修改httpbin應用的deployment。

    kubectl patch deployment -n default httpbin -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/interceptionMode":"TPROXY"}}}}}'                       
  2. 執行以下命令,從sleep容器發起請求。

    kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip

    預期輸出:

    {
      "origin": "172.17.X.XXX"
    }

    由預期輸出得到,httpbin可以得到sleep端的真實IP。

  3. 執行以下命令,根據80連接埠查看運行相關資訊。

    說明

    Pod重啟之後,需要重新安裝netstat。

    kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80

    預期輸出:

    tcp        0      0 172.17.X.XXX:80         172.17.X.XXX:36728      ESTABLISHED -

    由預期輸出得到,源IP地址為172.17.X.XXX

  4. 查看httpbin Pod中的代理日誌內容。

    格式化處理之後的日誌樣本如下:

    {
      "route_name":"default",
      "bytes_received":0,
      "trace_id":null,
      "request_id":"1ccabe60-63cf-469b-8565-99cac546****",
      "upstream_cluster":"inbound|80||",
      "response_flags":"-",
      "protocol":"HTTP/1.1",
      "upstream_transport_failure_reason":null,
      "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local",
      "response_code":200,
      "user_agent":"curl/7.86.0-DEV",
      "start_time":"2022-11-22T16:03:32.803Z",
      "path":"/ip",
      "authority":"httpbin:8000",
      "bytes_sent":31,
      "downstream_remote_address":"172.17.X.XXX:39058",
      "upstream_service_time":"1",
      "method":"GET",
      "downstream_local_address":"172.17.X.XXX:80",
      "duration":1,
      "upstream_host":"172.17.X.XXX:80",
      "istio_policy_status":null,
      "upstream_local_address":"172.17.X.XXX:46129",
      "x_forwarded_for":null
    }

    由日誌得到:

    • "downstream_remote_address":"172.17.X.XXX:39058":sleep的地址。

    • "downstream_local_address":"172.17.X.XXX:80":sleep訪問的目標地址。

    • "upstream_local_address":"172.17.X.XXX:46129":httpbin Envoy串連httpbin的local address(即sleep的IP地址)。

    • "upstream_host":"172.17.X.XXX:80":httpbin Envoy訪問的目標地址。

情境二:南北向流量

對於南北向流量,用戶端先請求負載平衡,然後將請求轉給Istio ingressgateway,再轉到後端服務。由於中間多了ingressgateway,使得擷取用戶端源IP地址變得更加複雜。下文介紹如何設定及驗證HTTP和HTTPS協議的請求保留源IP地址。

HTTP協議的請求

未設定保留源IP

  1. 使用以下內容,建立http-demo.yaml,以HTTP協議訪問httpbin。

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gw-httpprotocol
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - '*'
          port:
            name: http
            number: 80
            protocol: HTTP
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
      namespace: default
    spec:
      gateways:
        - httpbin-gw-httpprotocol
      hosts:
        - '*'
      http:
        - route:
            - destination:
                host: httpbin
                port:
                  number: 8000
  2. 執行以下命令,部署網關和虛擬服務。

    kubectl -n default apply -f  http-demo.yaml
  3. 執行以下命令,通過ingressgateway訪問httpbin。

    export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    curl http://$GATEWAY_URL:80/ip

    預期輸出:

    {
      "origin": "10.0.0.93"
    }

    由預期輸出得到,返回的IP地址為Kubernetes叢集的節點地址。

  4. 查看ingressgateway的訪問日誌。

    日誌樣本如下:

    {
      "upstream_service_time":"1",
      "response_code":200,
      "protocol":"HTTP/1.1",
      "bytes_sent":28,
      "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local",
      "start_time":"2022-11-23T03:29:20.017Z",
      "istio_policy_status":null,
      "upstream_transport_failure_reason":null,
      "trace_id":null,
      "route_name":null,
      "request_id":"292903be-a889-4d5d-83a0-ab1f5d1a****",
      "method":"GET",
      "upstream_host":"172.17.X.XXX:80",
      "duration":1,
      "path":"/ip",
      "downstream_local_address":"172.17.X.XXX:80",
      "authority":"47.242.XXX.XX",
      "user_agent":"curl/7.79.1",
      "downstream_remote_address":"10.0.0.93:5899",
      "upstream_local_address":"172.17.X.XXX:54322",
      "requested_server_name":null,
      "x_forwarded_for":"10.0.0.93",
      "response_flags":"-",
      "bytes_received":0
    }

    由日誌得到:

    • "downstream_remote_address":"10.0.0.93:5899":不是真實的用戶端源地址。

    • "downstream_local_address":"172.17.X.XXX:80":ingressgateway Pod的地址。

    • "upstream_local_address":"172.17.X.XXX:54322":保留了ingressgateway Pod的地址,連接埠值改變。

    • "upstream_host":"172.17.X.XXX:80":httpbin Pod的地址。

設定保留源IP

  1. 設定外部流量策略為Local。(網路模式為Terway的叢集可跳過該步驟。)

    1. 登入ASM控制台,在左側導覽列,選擇服務網格 > 網格管理

    2. 網格管理頁面,單擊目標執行個體名稱,然後在左側導覽列,選擇ASM網關 > 入口網關

    3. 入口網關頁面,單擊目標網關右側的查看YAML

    4. 編輯對話方塊的spec欄位下,將externalTrafficPolicy欄位配置為Local,然後單擊確定設定外部流量策略為Local

  2. 執行以下命令,通過ingressgateway訪問httpbin。

    curl http://$GATEWAY_URL:80/ip

    預期輸出:

    {
      "origin": "120.244.xxx.xxx"
    }

    由預期輸出得到,返回的IP地址為實際的用戶端的源IP地址。

  3. 查看ingressgateway的訪問日誌。

    日誌樣本如下:

    {
      "istio_policy_status":null,
      "upstream_transport_failure_reason":null,
      "path":"/ip",
      "x_forwarded_for":"120.244.XXX.XXX",
      "route_name":null,
      "method":"GET",
      "duration":2,
      "downstream_remote_address":"120.244.XXX.XXX:28504",
      "bytes_received":0,
      "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local",
      "bytes_sent":34,
      "protocol":"HTTP/1.1",
      "response_flags":"-",
      "upstream_local_address":"172.17.X.XXX:57498",
      "upstream_service_time":"2",
      "request_id":"9c0295d4-e77f-4a3a-b292-e5c58d92****",
      "start_time":"2022-11-23T03:24:04.413Z",
      "response_code":200,
      "trace_id":null,
      "authority":"47.242.XXX.XX",
      "user_agent":"curl/7.79.1",
      "downstream_local_address":"172.17.X.XXX:80",
      "upstream_host":"172.17.X.XXX:80",
      "requested_server_name":null
    }

    由日誌得到:

    • "downstream_remote_address":"120.244.XXX.XXX:28504":用戶端源地址,符合預期。

    • "downstream_local_address":"172.17.X.XXX:80":ingressgateway Pod的地址。

    • "upstream_local_address":"172.17.X.XXX:57498":保留了ingressgateway Pod的地址,連接埠值改變。

    • "upstream_host":"172.17.X.XXX:80":httpbin Pod的地址。

HTTPS協議的請求

上文已詳細說明HTTP協議的請求設定保留源IP地址前後的對比,因此,下文僅介紹如何設定及驗證HTTPS協議的請求保留源IP。

  1. 設定保留源IP地址

  2. 使用以下內容,建立https-demo,以HTTPS協議訪問httpbin。

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gw-https
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - '*'
          port:
            name: https
            number: 443
            protocol: HTTPS
          tls:
            credentialName: myexample-credential
            mode: SIMPLE
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin-https
      namespace: default
    spec:
      gateways:
        - httpbin-gw-https
      hosts:
        - '*'
      http:
        - route:
            - destination:
                host: httpbin
                port:
                  number: 8000
  3. 執行以下命令,部署網關和虛擬服務。

    kubectl -n default apply -f  https-demo.yaml
  4. 執行以下命令,通過ingressgateway訪問httpbin。

    export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    curl -k https://$GATEWAY_URL:443/ip

    預期輸出:

    {
      "origin": "120.244.XXX.XXX"
    }

    由預期輸出得到,返回的IP地址為實際的用戶端的源IP地址。