DNS 代理

除了捕获应用程序流量外,Istio 还可以捕获 DNS 请求以提高您的网格的性能和可用性。当代理 DNS 时,来自应用程序的所有 DNS 请求都将被重定向到 sidecar,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

这也可以通过 proxy.istio.io/config 注解 在每个 Pod 的基础上启用。

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 请求

Zip
$ 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,所以也不能基于它进行路由,只留下端口号,当多个 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} 上有一个单一的监听器,它只查找 mysql-instance1 的 IP 地址(来自公共 DNS 服务器),并将流量转发到它。它无法将流量路由到 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
    

清理

ZipZipZip
$ 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-
这些信息对您有帮助吗?
您是否有任何改进建议?

感谢您的反馈!