前言

在k8s中,我们可以使用..svc.cluster.local的方式对服务直接访问,该原理是在集群中部署一个kubeDNS,然后修改为默认的DNS服务器!
而kubeDNS的原理就是获取所有的svc然后构建DNS表(这里应该有一些优化比如使用散列表而不是顺序表等等),当发起一个服务请求时,首选进行域名解析,然后使用KubeDNS将解析后的ip地址进行访问,这里扩展一下,在对ip进行访问时只是访问的svc的ip并不是endpoint的ip,而svcip -> endpointip这一步是集群帮我们完成的,其原理是 安装kube-proxy,kube-proxy使用iptabels将svc-ip与endpoint-ip创建映射关系,这样我们在访问svc-ip时,就会自动转发到endpoint-ip上(采用轮询方式)。

Istio的DNS

为什么

现在我们思考一个场景,一个集群外部应用(与k8s内部网络必须在同一个网段)想要通过kubeDNS的形式访问内部服务,一般我们的操作就是将kubeDNS使用nodeport(或者通过网关)暴露出去,这种方式虽然可行,但是也增加了一些安全隐患。
istio分为内部应用于外部应用,外部应用无法直接访问内部应用(默认情况下,如果访问一个未知的服务istio代理是使用的透传的方式进行处理,但是为了安全起见,一般将其设置为拦截,也就是访问一个未知的服务istio代理会直接拒绝)。
istio的内部应用限定大于k8s内部应用限定,也就是说属于k8s内的应用一定属于istio内部应用(默认配置情况下),属于istio内部的应用不一定属于k8s应用服务。

内部访问外部

这句话怎么理解那?
我们还是考虑刚才那个场景,我们在集群外面的物理机上部署了一个应用(与k8s内部网络必须在同一个网段),它不属于集群内部应用,然后我们在集群中为其创建serviceentry,那么它就属于istio的内部服务(其他内部服务可以直接进行通讯,因为现在它是一个可知的服务。) 注意: 这个外部服务一定要使用istio-proxy 进程,否则它将不会使用限流,熔断等规则!其实也就是把之前以容器形式部署的应用,扩大到虚拟机或者物理机上部署!

外部访问内部

现在我们在上面那个场景的基础上进行另一步操作,也就是让外面部署的这个应用访问内部应用。
下面会有两种情况,第一种直接通过ip进行访问,第二种通过服务名进行通讯。
直接通过ip进行访问时可以访问成功的,这里不再赘述,其原理与原生k8s请求一致。
下面我们考虑第二种情况,既然通过域名访问,那么首先就需要访问域名解析服务器,但是如果使用上面所说的直接将DNS暴露出来又不安全,那么要怎么办那?下面就引出来本文的重点——istioDNS

怎么做

那么istio是怎么做的,才能让kubeDNS在不暴露的情况下,供外部应用使用的那?
pilot-agent除了提供优雅开关envoy外,还提供了一个DNS服务器的功能,没错istio实现了一个DNS解析服务器,这样既能保证kubeDNS不暴露,也能保证外部应用能够通过域名的形式进行访问!

使用

$ cat <<EOF | istioctl install -y -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
  meshConfig:
    defaultConfig:
      proxyMetadata:
        # 开启智能DNS
        ISTIO_META_DNS_CAPTURE: "true"
        # 使用自动分配Vip
        ISTIO_META_DNS_AUTO_ALLOCATE: "true"
EOF

原理

那么具体原理是什么那?
DNS解析服务器分为两部分首先获取host与ip映射信息,其次创建服务器供应用进行访问,这里我们还需要考虑一点,如何在代码无侵入的情况下完成这一步操作!并且在当前映射表中没有查找到信息后使用原有的DNS解析器进行解析!
让我们一一列出

  1. isito根据service获取endpoint(这里有原生svc资源与istio的资源)构建DNStables然后发送给pilot-agent。pilot-agent获取到信息后更新当前缓存表。
  2. pilot-agent 创建服务器,地址为localhost:15053
  3. iptables可以拦截DNS操作,所以使用iptables拦截所有DNS操作然后转发到 15053端口上。
  4. 读取/etc/resolv.conf 文件,当缓存表没有命中是遍历文件中的ip进行域名解析转发。

优化

你的集群的 Kubernetes DNS 服务器的负载急剧下降,因为几乎所有的 DNS 查询都是由 Istio 在 pod 内解决的。集群上 mesh 的管理的范围越大,你的 DNS 服务器的负载就越小。在 Istio 代理中实现我们自己的 DNS 代理,使我们能够实现诸如 CoreDNS auto-path 等很酷的优化,而不会出现 CoreDNS 目前面临的正确性问题。
为了理解这种优化的影响,让我们以一个简单的 DNS 查找场景为例,在一个标准的 Kubernetes 集群中,没有对 pod 进行任何自定义 DNS 设置 —— 即在 /etc/resolv.conf 中默认设置为 ndots:5。当你的应用程序开始对 productpage.ns1.svc.cluster.local 进行 DNS 查询时,它会将 /etc/resolv.conf 中的 DNS 搜索 namespace(例如,ns1.svc.cluster.local)作为 DNS 查询的一部分,然后再按原样查询主机。因此,实际发出的第一个 DNS 查询将看起来像 productpage.ns1.svc.cluster.local.ns1.svc.cluster.local,当 Istio 不参与时,这将不可避免地导致 DNS 解析失败。如果你的 /etc/resolv.conf 有 5 个搜索 namespace,应用程序将为每个搜索 namespace 发送两个 DNS 查询,一个是 IPv4 A 记录,另一个是 IPv6 AAAA 记录,然后再发送最后一对查询,查询内容是代码中使用的准确主机名。在建立连接之前,应用程序要为每个主机执行 12 次 DNS 查找查询!通过 Istio 实现的 CoreDNS 风格的 auto-path 技术,sidecar 代理将在第一次查询中检测到被查询的真实主机名,并返回一个 cname 记录给 productpage.ns1.svc.cluster.local 作为这个 DNS 响应的一部分,以及 productpage.ns1.svc.cluster.local 的 A/AAAA 记录。接收到这个响应的应用程序现在可以立即提取 IP 地址,并继续建立到该 IP 的 TCP 连接。Istio 代理中的智能 DNS 代理大大减少了 DNS 查询的次数,从 12 次减少到只有 2 次!

nameserver 10.1.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
ndots:5:如果查询的域名包含的点 “.” 不到 5 个,那么进行 DNS 查找,将使用非完全限定名称(或者叫绝对域名),如果你查询的域名包含点数大于等于 5,那么 DNS 查询,默认会使用绝对域名进行查询。

这里注意search 它并不是根据你输入的host进行匹配然后访问nameserver,它是你输入host后要添加的后缀名称,这也是为什么我们只输入服务名与命名空间就可以访问的原因

扩展

自动分配的VIP

apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: db1
  namespace: ns1
spec:
  hosts:
  - mysql–instance1.us-east-1.rds.amazonaws.com
  ports:
  - name: mysql
    number: 3306
    protocol: TCP
  resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
  name: db2
  namespace: ns1
spec:
  hosts:
  - mysql–instance2.us-east-1.rds.amazonaws.com
  ports:
  - name: mysql
    number: 3306
    protocol: TCP
  resolution: DNS

在上面的示例中,对于发送请求的服务,您有一个预定义的IP地址。但是常规情况下,服务访问外部服务时一般没有一个相对固定的地址,因此需要通过 DNS 代理去访问外部服务。如果 DNS 代理没有足够的信息去返回一个响应的情况下,将需要向上游转发 DNS 请求。
这在 TCP 通讯中是一个很严重的问题。它不像 HTTP 请求,基于 Host 头部去路由。TCP 携带的信息更少,只能在目标 IP 和端口号上路由。由于后端没有稳定的 IP,所以也不能基于其他信息进行路由,只剩下端口号,但是这会导致多个 ServiceEntry 使用 TCP 服务会共享同一端口而产生冲突。
为了解决这些问题,DNS 代理还支持为没有明确定义的 ServiceEntry 自动分配地址。这是通过 ISTIO_META_DNS_AUTO_ALLOCATE 选项配置的。
启用此特性后,DNS 响应将为每个 ServiceEntry 自动分配一个不同的独立地址。然后代理能匹配请求与 IP 地址,并将请求转发到相应的 ServiceEntry。

参考

https://cloudnative.to/blog/istio-dns-proxy/#%E5%A4%9A%E9%9B%86%E7%BE%A4-dns-%E6%9F%A5%E8%AF%A2
https://istio.io/latest/zh/docs/ops/configuration/traffic-management/dns-proxy/#address-auto-allocation

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐