Istio網格中訪問外部服務方法

語言: CN / TW / HK

背景

在生產環境使用 Istio 的時候,可能最需要考慮的問題一個是安全問題一個是效能問題,在這裡和大家一起探討下一個安全問題,如何在 Istio 網格中訪問外部服務。Istio 提供了兩種模式來配置對外部請求的訪問策略,並通過配置項 outboundTrafficPolicy.mode 來指定。 預設的模式是 ALLOW_ANY,也就是允許在網格內請求所有外部的未知服務;另外一個模式是 REGISTRY_ONLY,表示只允許請求註冊到服務網格登錄檔中的服務。預設的 ALLOW_ANY 模式雖然使用方便,但是存在一定的安全隱患,建議的做法是切換到 REGISTRY_ONLY 模式。那麼在 REGISTRY_ONLY 模式下如何訪問外部服務?實現機制是什麼呢?在這裡針對這兩個問題和大家一起探討下。

方案調研

目前我們安裝部署 Istio 使用的是helm,可以在安裝中新增相應的配置 --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY 修改 outboundTrafficPolicy.mode 的值;如果 Istio 已經安裝好,通過 kubectl edit cm istio -n istio-system 可以動態修改此值。

apiVersion: v1
data:
  mesh: |-
    defaultConfig:

      discoveryAddress: istiod.istio-system.svc:15012
      proxyMetadata:
        DNS_AGENT: ""
      tracing:
        zipkin:
          address: zipkin.istio-system:9411
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY

配置完 REGISTRY_ONLY 後,我們在 pod 中無法訪問外部服務

那麼在 REGISTRY_ONLY 模式下,如何才能訪問外部服務呢?首先我們通過 下圖 看下 Istio 在什麼時機可以訪問外部服務,在這種時機下怎麼修改配置才能訪問到外部服務?

圖中描述了 product 服務訪問 review 服務,這裡我們假設 review 服務會繼續呼叫 36.152.44.96 這個外部服務。

當 reviews 應用需要訪問 36.152.44.96 這個外部服務時,會在 reviews 的應用容器中往上游傳送請求資訊,下面按照圖中的順序介紹如何訪問外部請求。

  1. reviews 服務訪問外部服務,這一步對 reviews 服務來說屬於出口流量,被 iptables 規則攔截轉發至出口流量 OUTPUT 鏈。
  2. OUTPUT 鏈轉發流量至 ISTIO_OUTPUT 鏈。
  3. 在 ISTIO_OUTPUT 鏈中預設有九個規則,決定 reviews 服務訪問外部服務的流量發往何處,在這裡我們可以自定義一個規則 -A ISTIO_OUTPUT -d 36.152.44.0/24 -j RETURN ,使訪問 36.152.44.96 這個外部服務的流量跳出當前鏈,呼叫 POSTROUTING 鏈 ,直接訪問外部服務;如果使用預設規則,流量被轉發至 ISTIO_REDIRECT 鏈。
  4. ISTIO_REDIRECT鏈直接重定向至 Envoy監聽的15001出口流量埠。
  5. 外部服務的流量策略並不在 Istio 服務網格中,如果不進行相關配置把外部服務註冊到服務網格內,經過 Envoy 一系列出口流量治理動作後將會返回錯誤資訊。因此這裡我們需要配置 ServiceEntry ,把外部服務納入服務網格中,然後通過 Envoy 的流量治理後可以繼續傳送外部請求,訪問外部請求時又會被 iptables 攔截轉發至出口流量 OUTPUT 鏈。
  6. OUTPUT 鏈轉發流量至 ISTIO_OUTPUT 鏈。
  7. 在這裡會匹配到 ISTIO_OUTPUT 鏈的第四條規則 -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN ,流量直接 RETURN 到下一個鏈 POSTROUTING 鏈 ,經 POSTROUTING 鏈流出訪問外部服務。

我們看到通過第11步、第13步兩種方式修改配置都可以使 Istio 內部的服務訪問外部服務,具體應該怎麼做呢?

通過iptables規則訪問

如果想在第11步直接跳出 ISTIO_OUT 鏈呼叫 POSTROUTING 鏈,需要新增一個自定義的 iptables 規則 -A ISTIO_OUTPUT -d 36.152.44.0/24 -j RETURN ,我們知道 iptables 規則是在 istio-init 裡定義的,istio-init 啟動時執行的是 istio-iptables 命令,我們在 manifests 的 injection-template.yaml 檔案裡看到 istio-iptables 預設的部分配置。

- "-i"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges` .Values.global.proxy.includeIPRanges }}"
    - "-x"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges` .Values.global.proxy.excludeIPRanges }}"
    - "-b"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` `*` }}"
    - "-d"
  {{- if excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}
    - "15090,15021,{{ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}"
  {{- else }}
    - "15090,15021,15020"
  • -i 預設為 * ,所有出站流量都會被重定向到 Envoy 代理
  • -x 預設為空,當-i引數為 * 時,用來指明哪些地址不用重定向 Envoy 代理,直接進行轉發
  • -b 預設為 * ,逗號分隔的埠,指定埠的流量將重定向到 Envoy
  • -d 預設為15090,15021,15020,逗號分隔的埠,指定哪些埠的流量不用重定向到 Envoy

根據上面配置我們可以在 Istio 服務中通過 global.proxy.* 進行全域性配置,還可以在服務的 deployment 裡通過 traffic.sidecar.istio.io/* 配置相應的引數。我們現在的目的是需要把訪問 36.152.44.96 的請求不重定向到 Envoy 代理,直接進行轉發,因此我們在 deployment 裡新增 traffic.sidecar.istio.io/excludeOutboundIPRanges: 36.152.44.0/24 ,執行完後我們 describe pod 的詳細資訊,獲取到 pod 中的部分配置如下,可以看到設定生效,36.152.44.0/24 不會重定向到 Envoy 中

-i
  *
  -x
  36.152.44.0/24
  -b
  *
  -d
  15090,15021,15020

繼續檢視 pod 中的 iptables 規則,可以看到在原來的 ISTIO_OUTPUT 九條 iptables 規則中插入了一個自定義的規則 A ISTIO_OUTPUT -d 36.152.44.0/24 -j RETURN

-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
 -A ISTIO_OUTPUT -d 36.152.44.0/24 -j RETURN
 -A ISTIO_OUTPUT -j ISTIO_REDIRECT

通過ServiceEntry訪問

Istio 在 15001 埠使用 VirtualOutboundListener 處理出向請求,Iptable 將 Envoy 所在 Pod 的對外請求攔截後發向本地的 15001 埠,該監聽器接收後並不進行業務處理,而是根據請求的目的埠分發給其他獨立的監聽器處理。 我們訪問的外部服務為 36.152.44.96:80,因此 Envoy 根據目的埠匹配到 0.0.0.0_80 這個 Outbound listener,並轉交給該 listener。

當 0.0.0.0_80 接收到出向請求後,並不會直接傳送到目的 cluster,其實通過檢視 0.0.0.0_80 的 listener 的資訊,我們也找不到目的 cluster 或 endpoint,在這個 listener 中配置了一個路由規則80,在該路由規則中會根據不同的請求目的地進行路由匹配處理。

通過 name 為80的路由規則我們沒找到符合 36.152.44.96 的請求,因此會被 listener 裡的 default_filter_match 處理,進入到 BlackHoleCluster 叢集裡,請求被丟棄。 這裡我們簡單介紹下 Envoy 中的兩個特殊 cluster: BlackHoleClusterPassthroughClusterBlackHoleCluster 中沒有配置任何處理請求的host。請求進入該 cluster 後將被丟棄掉,而不是發向一個 host,如果 outboundTrafficPolicy.mode=REGISTRY_ONLY ,預設情況下請求的外部服務都會直接進入 BlackHoleCluster 中丟掉。 PassthroughCluster 的 type 被設定為 ORIGINAL_DST ,表明任何發向該 cluster 的請求都會被直接傳送到其請求中的原始目的地,如果 outboundTrafficPolicy.mode=ALLOW_ANY ,Envoy 不會對請求進行重新路由直接傳送到原始目的地。

outboundTrafficPolicy.mode=REGISTRY_ONLY 模式下,為了流量不進入 BlackHoleCluster 中,我們需要新增 ServiceEntry,把外部請求註冊到服務網格中,以便Envoy可以找到外部服務的 route 進行流量處理。

apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: baidu-ip
spec:
  hosts:
  - www.baidu.com
  addresses:
  - 36.152.44.96
  location: MESH_EXTERNAL
  ports:
  - number: 80
    name: baidu-http
    protocol: HTTP
  resolution: NONE

配置完 ServiceEntry 後我們再次查詢 review 的 route 資訊,可以看到36.152.44.96已經被加入 route 中了,直接在pod中訪問外部服務36.152.44.96可以得到正確的請求。

總結

通過對 Istio 訪問外部服務的方案進行調研,我們學習了 Istio 是如何通過 iptables 處理進入網格中的流量,也大體瞭解了 Envoy 處理 Outbound 流量的流程。以上的調研基於 Istio 1.8 版本,內容可能存在錯誤或不準確的地方,歡迎大家交流指正。