DNS 代理

除了擷取應用程式流量外,Istio 還可以擷取 DNS 請求,以提高網格的效能和可用性。在代理 DNS 時,應用程式的所有 DNS 請求都將重新導向至 sidecar,後者會儲存網域名稱到 IP 位址的本機對應。如果 sidecar 可以處理請求,它會直接將回應傳回應用程式,避免往返上游 DNS 伺服器。否則,請求會按照標準 /etc/resolv.conf DNS 設定向上游轉送。

雖然 Kubernetes 為 Kubernetes Service 提供開箱即用的 DNS 解析,但任何自訂的 ServiceEntry 都無法辨識。使用此功能,可以解析 ServiceEntry 位址,而無需自訂設定 DNS 伺服器。對於 Kubernetes Service,DNS 回應會相同,但 kube-dns 上的負載會降低,且效能會提高。

此功能也適用於在 Kubernetes 外部執行的服務。這表示可以解析所有內部服務,而無需使用笨拙的解決方案將 Kubernetes DNS 項目公開到叢集外部。

開始使用

此功能目前預設為停用。若要啟用它,請使用下列設定安裝 Istio

$ cat <<EOF | istioctl install -y -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      proxyMetadata:
        # Enable basic DNS proxying
        ISTIO_META_DNS_CAPTURE: "true"
        # Enable automatic address allocation, optional
        ISTIO_META_DNS_AUTO_ALLOCATE: "true"
EOF

這也可以在每個 Pod 的基礎上啟用,使用 proxy.istio.io/config 註解

kind: Deployment
metadata:
  name: curl
spec:
...
  template:
    metadata:
      annotations:
        proxy.istio.io/config: |
          proxyMetadata:
            ISTIO_META_DNS_CAPTURE: "true"
            ISTIO_META_DNS_AUTO_ALLOCATE: "true"
...

DNS 擷取實際運作

要嘗試 DNS 擷取功能,首先為某些外部服務設定一個 ServiceEntry

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: external-address
spec:
  addresses:
  - 198.51.100.1
  hosts:
  - address.internal
  ports:
  - name: http
    number: 80
    protocol: HTTP
EOF

啟動一個用戶端應用程式來發起 DNS 請求。

壓縮。
$ kubectl label namespace default istio-injection=enabled --overwrite
$ kubectl apply -f @samples/curl/curl.yaml@

在沒有 DNS 擷取的情況下,對 address.internal 的請求可能會無法解析。一旦啟用此功能,您應該會根據設定的 address 得到回應。

$ kubectl exec deploy/curl -- curl -sS -v address.internal
*   Trying 198.51.100.1:80...

位址自動分配

在上述範例中,您有一個預先定義的 IP 位址,用於您發送請求的服務。然而,存取沒有穩定位址,而是依賴 DNS 的外部服務是很常見的。在這種情況下,DNS 代理將沒有足夠的資訊來返回回應,並且需要將 DNS 請求轉發到上游。

這對於 TCP 流量尤其麻煩。與基於 Host 標頭路由的 HTTP 請求不同,TCP 攜帶的資訊少得多;您只能根據目標 IP 和埠號進行路由。因為您沒有後端的穩定 IP,所以您也無法根據該 IP 進行路由,只剩下埠號,這會導致多個 TCP 服務的 ServiceEntry 共享相同埠時產生衝突。請參閱以下章節以了解更多詳細資訊。

為了解決這些問題,DNS 代理還支援為未明確定義位址的 ServiceEntry 自動分配位址。這由 ISTIO_META_DNS_AUTO_ALLOCATE 選項配置。

啟用此功能時,DNS 回應將包含每個 ServiceEntry 的不同且自動分配的位址。然後,代理會被配置為匹配此 IP 位址的請求,並將請求轉發到對應的 ServiceEntry。當使用 ISTIO_META_DNS_AUTO_ALLOCATE 時,只要服務不使用萬用字元主機,Istio 就會自動將不可路由的 VIP(來自 E 類子網路)分配給這些服務。Sidecar 上的 Istio 代理將使用 VIP 作為來自應用程式的 DNS 查詢的回應。Envoy 現在可以清楚地區分每個外部 TCP 服務的流量,並將其轉發到正確的目標。有關更多資訊,請查看有關智慧 DNS 代理的相關 Istio 部落格文章

要嘗試此功能,請設定另一個 ServiceEntry

$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: external-auto
spec:
  hosts:
  - auto.internal
  ports:
  - name: http
    number: 80
    protocol: HTTP
  resolution: DNS
EOF

現在,發送一個請求。

$ kubectl exec deploy/curl -- curl -sS -v auto.internal
*   Trying 240.240.0.1:80...

如您所見,請求會被發送到自動分配的位址 240.240.0.1。這些位址將從 240.240.0.0/16 保留的 IP 位址範圍中選取,以避免與真實服務衝突。

沒有 VIP 的外部 TCP 服務

預設情況下,Istio 在路由外部 TCP 流量時存在限制,因為它無法區分同一埠上的多個 TCP 服務。當使用 AWS Relational Database Service 等第三方資料庫或任何具有地理冗餘設定的資料庫時,此限制尤其明顯。預設情況下,類似但不同的外部 TCP 服務無法單獨處理。為了讓 Sidecar 區分網格外部的兩個不同 TCP 服務之間的流量,這些服務必須位於不同的埠上,或者它們需要具有全域唯一的 VIP。

例如,如果您有兩個外部資料庫服務 mysql-instance1mysql-instance2,並且您為兩者建立服務條目,用戶端 Sidecar 仍然會在 0.0.0.0:{port} 上有一個單一監聽器,它會從公共 DNS 伺服器查找 mysql-instance1 的 IP 位址,並將流量轉發到該位址。它無法將流量路由到 mysql-instance2,因為它無法區分到達 0.0.0.0:{port} 的流量是發往 mysql-instance1 還是 mysql-instance2

以下範例說明如何使用 DNS 代理來解決此問題。每個服務條目都會分配一個虛擬 IP 位址,以便用戶端 Sidecar 可以清楚地區分每個外部 TCP 服務的流量。

  1. 更新 入門指南 章節中指定的 Istio 配置,也配置 discoverySelectors,將網格限制為啟用 istio-injection 的命名空間。這將允許我們在叢集中的任何其他命名空間中執行網格外部的 TCP 服務。

    $ cat <<EOF | istioctl install -y -f -
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    spec:
      meshConfig:
        defaultConfig:
          proxyMetadata:
            # Enable basic DNS proxying
            ISTIO_META_DNS_CAPTURE: "true"
            # Enable automatic address allocation, optional
            ISTIO_META_DNS_AUTO_ALLOCATE: "true"
        # discoverySelectors configuration below is just used for simulating the external service TCP scenario,
        # so that we do not have to use an external site for testing.
        discoverySelectors:
        - matchLabels:
            istio-injection: enabled
    EOF
    
  2. 部署第一個外部範例 TCP 應用程式。

    $ kubectl create ns external-1
    $ kubectl -n external-1 apply -f samples/tcp-echo/tcp-echo.yaml
    
  3. 部署第二個外部範例 TCP 應用程式。

    $ kubectl create ns external-2
    $ kubectl -n external-2 apply -f samples/tcp-echo/tcp-echo.yaml
    
  4. 配置 ServiceEntry 以連線外部服務。

    $ kubectl apply -f - <<EOF
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: external-svc-1
    spec:
      hosts:
      - tcp-echo.external-1.svc.cluster.local
      ports:
      - name: external-svc-1
        number: 9000
        protocol: TCP
      resolution: DNS
    ---
    apiVersion: networking.istio.io/v1
    kind: ServiceEntry
    metadata:
      name: external-svc-2
    spec:
      hosts:
      - tcp-echo.external-2.svc.cluster.local
      ports:
      - name: external-svc-2
        number: 9000
        protocol: TCP
      resolution: DNS
    EOF
    
  5. 驗證用戶端上的每個服務是否已單獨配置監聽器。

    $ istioctl pc listener deploy/curl | grep tcp-echo | awk '{printf "ADDRESS=%s, DESTINATION=%s %s\n", $1, $4, $5}'
    ADDRESS=240.240.105.94, DESTINATION=Cluster: outbound|9000||tcp-echo.external-2.svc.cluster.local
    ADDRESS=240.240.69.138, DESTINATION=Cluster: outbound|9000||tcp-echo.external-1.svc.cluster.local
    

清理

壓縮壓縮壓縮
$ kubectl -n external-1 delete -f @samples/tcp-echo/tcp-echo.yaml@
$ kubectl -n external-2 delete -f @samples/tcp-echo/tcp-echo.yaml@
$ kubectl delete -f @samples/curl/curl.yaml@
$ istioctl uninstall --purge -y
$ kubectl delete ns istio-system external-1 external-2
$ kubectl label namespace default istio-injection-
此資訊是否對您有幫助?
您是否有任何改進建議?

感謝您的回饋!