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 请求
$ 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-instance1
和 mysql-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 服务的流量。
更新在 入门 部分中指定的 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
部署第一个外部示例 TCP 应用程序
$ kubectl create ns external-1 $ kubectl -n external-1 apply -f samples/tcp-echo/tcp-echo.yaml
部署第二个外部示例 TCP 应用程序
$ kubectl create ns external-2 $ kubectl -n external-2 apply -f samples/tcp-echo/tcp-echo.yaml
配置
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
验证监听器是否在客户端侧为每个服务单独配置
$ 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-