前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从 KIND 环境中了解到的有趣的 DNS 事实

从 KIND 环境中了解到的有趣的 DNS 事实

作者头像
DevOps云学堂
发布2023-12-19 16:30:45
2220
发布2023-12-19 16:30:45
举报
文章被收录于专栏:DevOps持续集成DevOps持续集成

本篇文章是「DevOps云学堂」与你共同进步的第 67

前言

Kubernetes in Docker (KIND) 是一个由 Kubernetes SIG 社区维护的开源项目。该项目的目的是使用Docker提供一个简单的Kubernetes环境,主要用于Kubernetes CI测试。 Kubernetes本身是一个容器编排平台,因此使用Docker作为其节点会产生基于容器中容器概念的架构。这种方法的实现过程也引入了与双层容器相关的挑战。本文重点讨论这一过程中出现的与 DNS 相关的一个具体实施问题。

环境

KIND 的架构构建于 Docker 之上。本文的实验设置涉及在 Ubuntu 20.04 上使用 KIND 创建一个三节点 Kubernetes 集群。其中一个节点作为控制平面,另外两个节点作为普通工作人员。 在 Docker 环境中,需要启动三个 Docker 容器来模拟 Kubernetes 节点。这些容器使用 Docker 网络相互通信,以解决网络连接问题。整体架构如下图所示:

在此环境中,将部署三个容器。每个容器在 Docker 网络子网中都将拥有自己的 IP,并且它们都将包含各自容器映像中包含的应用程序和库。 对于Kubernetes来说,为了实现三节点的Kubernetes集群,集群内必须有一个控制平面,提供etcd、调度器、控制器和API服务器等功能。此外,每个节点都需要安装 kubelet 和相关的容器运行时来管理容器的生命周期。该概念如下图所示:

KIND 环境结合了这两个方面。因此,整体架构如下:

Kubernetes 如何在 KIND 中工作 通过 Docker 启动的容器将安装 Containerd 来管理 Kubernetes 容器的生命周期。同时,通过kubelet等组件与控制平面建立连接,形成Kubernetes集群。

Docker DNS

熟悉 Docker Compose 的读者可能知道,为了方便容器之间的通信,可以直接使用容器名称作为 DNS 目标。这样的设计让容器无需担心IP变化。事实上,Docker 在系统中嵌入了一个 DNS 服务器来处理这个问题,DNS 服务器的固定 IP 是 127.0.0.11。 该 DNS 服务器的职责可分类如下:

  1. 如果 DNS 请求是针对容器名称,则返回容器的 IP。
  2. 否则,根据主机配置,将 DNS 请求转发到上游 DNS 服务器。

在下面描述的示例中,两个名为hwchiuhwchiu2的容器正在运行。使用nslookup,可以轻松解析对应的IP地址。还可以观察到这些容器中的 /etc/hosts 文件动态指向 127.0.0.11。这意味着容器内的所有 DNS 请求都将重定向到内置的 Docker DNS 服务器。

然而,Docker 的 DNS 实现提出了一些值得探讨的问题:

  1. Docker DNS 服务器到底在哪里运行?Docker什么时候开始运行?为什么无法通过诸如ps之类的命令在容器内找到其踪迹?
  2. 常见的 DNS 通信通常发生在端口 53 上。如果 Docker DNS 服务器占用端口 53,是否会阻止容器运行使用端口 53 的第二个服务?这种复杂性是否会导致在 Docker 容器中部署其他 DNS 服务器服务变得困难?

为了解决这些不确定性,有必要了解 Docker DNS 的实现。通过深入了解其实施情况,我们可以为上述问题提供准确的答案。

实现

Docker DNS 的设计非常巧妙,利用 Linux 命名空间的概念无缝地解决了这个问题。它使所有 Docker DNS 服务器能够在主机本身(PID 命名空间内)上运行,而网络方面则在每个容器内(网络命名空间内)进行侦听。 这种架构允许你通过127.0.0.11访问DNS服务器,但是你在容器内找不到这个DNS服务器的进程。 此外,为了防止 Docker DNS 服务器与用户定义的 DNS 服务发生冲突,Docker DNS 避免使用端口 53,而是采用随机端口号。 整体架构如下图所示:

但是,对于容器服务,/etc/hosts 已更改为使用 127.0.0.11 作为默认 DNS 搜索,并且通常依赖于在端口 53 上,Dockerd 依靠 iptables 来动态调整规则。这涉及修改发送到 127.0.0.11:53 的所有数据包的目标端口以处理连接问题。 因此,如果您使用 nsenter 等命令在容器内进行观察,请使用 ss 和 i ptables 命令,您将看到如下图所示的结果:

在此显示中,ss 显示在环境中,127.0.0.11 正在侦听两个端口,分别对应 TCP 和 UDP DNS 请求,进程为dockerd。iptables揭示了相关DNAT规则。 有了这些机制,来自容器的所有 DNS 请求都由 Docker DNS 处理,而不会抢占端口 53。 在这里您可以找到相关的源代码,https://github.com/moby/libnetwork/blob/67e0588f1ddfaf2faf4c8cae8b7ea2876434d91c/resolver_unix.go?WT.mc_id=AZ-MVP-5003331 它演示了Docker动态修改四个规则来满足DNAT + SNAT需求。

代码语言:javascript
复制
...
 resolverIP, ipPort, _ := net.SplitHostPort(os.Args[2])
 _, tcpPort, _ := net.SplitHostPort(os.Args[3])
 rules := [][]string{
  {"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "udp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[2]},
  {"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "udp", "--sport", ipPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
  {"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "tcp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[3]},
  {"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "tcp", "--sport", tcpPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
 }
...

到目前为止,您应该对 Docker DNS 有了基本的了解。接下来,让我们看看 Kubernetes 的情况。

Kubernetes DNS

在 Kubernetes 集群中,还有一个 DNS 服务器——从早期的 Kube-DNS 到现在的 CoreDNS。它的功能与Docker DNS非常相似:

  1. 如果 DNS 请求与内部 Kubernetes 服务相关,它将使用内部信息进行响应。
  2. 否则,它将请求转发到上游 DNS 服务器。

与 Docker DNS 类似,两者都旨在处理特定于服务的请求并在必要时进行转发。 对于CoreDNS来说,上游DNS服务器默认是节点使用的DNS服务器,与前面提到的127.0.0.11相同。 该过程展开如下:

CoreDNS 如何处理 DNS 假设“worker2”上的 Pod 想要发出 DNS 请求。该请求被定向到 CoreDNS。当 CoreDNS 无法解析它并尝试将其转发到上游 DNS 服务器时,它最终会转发到 127.0.0.11,这就是问题出现的地方。

问题

由于 127.0.0.11 是 Dockerd 特有的,因此 Docker 容器上 Containerd 管理的 CoreDNS 自然缺乏此功能它不具备相关的 iptables 和 Docker DNS 服务器。 为了解决这个问题,KIND 的方法是阻止 CoreDNS 向 127.0.0.11 发送数据包。相反,CoreDNS 将它们发送到节点的 IP,然后将数据包转发到节点上的 127.0.0.11 服务。 流程概述如下:

解决 CoreDNS 问题的想法 如前所述,CoreDNS 本身使用节点的 /etc/hosts 作为上游服务器。这里的做法是动态修改节点的/etc/hosts,将默认的DNS服务器从127.0.0.11改为节点自己的IP,比如示例图中的 172.18.0.2。 一旦默认请求位置发生更改,Dockerd 设置的 iptables 规则将不再适用。这就需要再次调整iptables规则。 Dockerd 设置的初始 iptables 规则如下:

KIND 更改之前的 iptables 但是,KIND 将它们修改为以下内容(示例来自不同节点,节点 IP 为 172.18.0.1):

KIND 更改后的 iptables 现在,所有发送到 172.18.0.1:53 的数据包将被重定向到 127.0.0.11 :33501/41285,Docker DNS 位置。这还涉及到 SNAT 的更改。 从KIND源码可以观察到KIND的K8s节点每次启动都会修改iptables规则,同时也会更新默认地址在/etc/resolve.conf中。

代码语言:javascript
复制
# well-known docker embedded DNS is at 127.0.0.11:53
local docker_embedded_dns_ip='127.0.0.11'

# first we need to detect an IP to use for reaching the docker host
local docker_host_ip
docker_host_ip="$( (head -n1 <(timeout 5 getent ahostsv4 'host.docker.internal') | cut -d' ' -f1) || true)"
# if the ip doesn't exist or is a loopback address use the default gateway
if [[ -z "${docker_host_ip}" ]] || [[ $docker_host_ip =~ ^127\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  docker_host_ip=$(ip -4 route show default | cut -d' ' -f3)
fi

# patch docker's iptables rules to switch out the DNS IP
iptables-save \
  | sed \
    `# switch docker DNS DNAT rules to our chosen IP` \
    -e "s/-d ${docker_embedded_dns_ip}/-d ${docker_host_ip}/g" \
    `# we need to also apply these rules to non-local traffic (from pods)` \
    -e 's/-A OUTPUT \(.*\) -j DOCKER_OUTPUT/\0\n-A PREROUTING \1 -j DOCKER_OUTPUT/' \
    `# switch docker DNS SNAT rules rules to our chosen IP` \
    -e "s/--to-source :53/--to-source ${docker_host_ip}:53/g"\
    `# nftables incompatibility between 1.8.8 and 1.8.7 omit the --dport flag on DNAT rules` \
    `# ensure --dport on DNS rules, due to https://github.com/kubernetes-sigs/kind/issues/3054` \
    -e "s/p -j DNAT --to-destination ${docker_embedded_dns_ip}/p --dport 53 -j DNAT --to-destination ${docker_embedded_dns_ip}/g" \
  | iptables-restore

# now we can ensure that DNS is configured to use our IP
cp /etc/resolv.conf /etc/resolv.conf.original
replaced="$(sed -e "s/${docker_embedded_dns_ip}/${docker_host_ip}/g" /etc/resolv.conf.original)"
if [[ "${KIND_DNS_SEARCH+x}" == "" ]]; then
  # No DNS search set, just pass through as is
  echo "$replaced" >/etc/resolv.conf
elif [[ -z "$KIND_DNS_SEARCH" ]]; then
  # Empty search - remove all current search clauses
  echo "$replaced" | grep -v "^search" >/etc/resolv.conf
else
  # Search set - remove all current search clauses, and add the configured search
  {
    echo "search $KIND_DNS_SEARCH";
    echo "$replaced" | grep -v "^search";
  } >/etc/resolv.conf
fi

通过这些调整,CoreDNS 向节点发送 DNS 请求,然后通过 iptables 将请求转发到 Docker DNS。如果 Docker DNS 无法处理它们,请求将向上转发到主机上的初始设置,从而创建级联过程。

概括

  1. Docker 有一个集成的 DNS 服务器来处理 Docker 容器之间的 DNS 请求。
  2. Docker 通过 iptables 和命名空间简化了 DNS 的部署和操作。
  3. 由于 /etc/resolve 的修改,Kubernetes 的 CoreDNS 默认受到 Docker DNS 的影响。
  4. KIND 两次修改这些规则以纠正所有路由,保证 DNS 转发。

文章翻译 https://hwchiu.medium.com/fun-dns-facts-learned-from-the-kind-environment-241e0ea8c6d4

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 DevOps云学堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 环境
  • Docker DNS
    • 实现
    • Kubernetes DNS
      • 问题
      • 概括
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档