介绍
Kubernetes 从 1.3 版本起, DNS 是内置的服务,通过插件管理器 集群插件 自动被启动。
Kubernetes DNS 在集群中调度 DNS Pod 和 Service ,配置 kubelet 以通知个别容器使用 DNS Service 的 IP 解析 DNS 名字。
怎样获取 DNS 名字?
在集群中定义的每个 Service(包括 DNS 服务器自身)都会被指派一个 DNS 名称。默认,一个客户端 Pod 的 DNS 搜索列表将包含该 Pod 自己的 Namespace 和集群默认域。可以通过如下示例进行说明:
假设在 Kubernetes 集群的 Namespace bar 中,定义了一个Service foo。运行在Namespace bar 中的一个 Pod,可以简单地通过 DNS 查询 foo 来找到该 Service。运行在 Namespace quux 中的一个 Pod 可以通过 DNS 查询 foo.bar 找到该 Service。
支持的 DNS 模式
下面各段详细说明支持的记录类型和布局。如果任何其它的布局、名称或查询,碰巧也能够使用,这就需要研究下它们的实现细节,以免后续修改它们又不能使用了。
Service
A 记录
“正常” Service(除了Headless Service)会以 my-svc.my-namespace.svc.cluster.local 这种名字的形式被指派一个 DNS A 记录。这会解析成该 Service 的 Cluster IP。
“Headless” Service(没有Cluster IP)也会以 my-svc.my-namespace.svc.cluster.local 这种名字的形式被指派一个 DNS A 记录。不像正常 Service,它会解析成该 Service 选择的一组 Pod 的 IP。希望客户端能够使用这一组 IP,否则就使用标准的 round-robin 策略从这一组 IP 中进行选择。
SRV 记录
命名端口需要创建 SRV 记录,这些端口是正常 Service或 Headless Services 的一部分。 对每个命名端口,SRV 记录具有 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local 这种形式。 对普通 Service,这会被解析成端口号和 CNAME:my-svc.my-namespace.svc.cluster.local。 对 Headless Service,这会被解析成多个结果,Service 对应的每个 backend Pod各一个,包含 auto-generated-name.my-svc.my-namespace.svc.cluster.local 这种形式 Pod 的端口号和 CNAME。
后向兼容性
上一版本的 kube-dns 使用 my-svc.my-namespace.cluster.local 这种形式的名字(后续会增加 ‘svc’ 这一级),以后这将不再被支持。
Pod
A 记录
如果启用,Pod 会以 pod-ip-address.my-namespace.pod.cluster.local 这种形式被指派一个 DNS A 记录。
例如,default Namespace 具有 DNS 名字 cluster.local,在该 Namespace 中一个 IP 为 1.2.3.4 的 Pod 将具有一个条目:1-2-3-4.default.pod.cluster.local。
基于 Pod hostname、subdomain 字段的 A 记录和主机名
当前,创建 Pod 后,它的主机名是该 Pod 的 metadata.name 值。
在 v1.2 版本中,用户可以配置 Pod annotation, 通过 pod.beta.kubernetes.io/hostname 来设置 Pod 的主机名。 如果为 Pod 配置了 annotation,会优先使用 Pod 的名称作为主机名。 例如,给定一个 Pod,它具有 annotation pod.beta.kubernetes.io/hostname: my-pod-name,该 Pod 的主机名被设置为 “my-pod-name”。
在 v1.3 版本中,PodSpec 具有 hostname 字段,可以用来指定 Pod 的主机名。这个字段的值优先于 annotation pod.beta.kubernetes.io/hostname。 在 v1.2 版本中引入了 beta 特性,用户可以为 Pod 指定 annotation,其中 pod.beta.kubernetes.io/subdomain 指定了 Pod 的子域名。 最终的域名将是 “ ...svc.”。 举个例子,Pod 的主机名 annotation 设置为 “foo”,子域名 annotation 设置为 “bar”,在 Namespace “my-namespace” 中对应的 FQDN 为 “foo.bar.my-namespace.svc.cluster.local”。
在 v1.3 版本中,PodSpec 具有 subdomain 字段,可以用来指定 Pod 的子域名。这个字段的值优先于 annotation pod.beta.kubernetes.io/subdomain 的值。
apiVersion: v1 kind: Service metadata: name: default-subdomain spec: selector: name: busybox clusterIP: None ports: - name: foo # Actually, no port is needed. port: 1234 targetPort: 1234 --- apiVersion: v1 kind: Pod metadata: name: busybox1 labels: name: busybox spec: hostname: busybox-1 subdomain: default-subdomain containers: - image: busybox command: - sleep - "3600" name: busybox --- apiVersion: v1 kind: Pod metadata: name: busybox2 labels: name: busybox spec: hostname: busybox-2 subdomain: default-subdomain containers: - image: busybox command: - sleep - "3600" name: busybox
如果 Headless Service 与 Pod 在同一个 Namespace 中,它们具有相同的子域名,集群的 KubeDNS 服务器也会为该 Pod 的完整合法主机名返回 A 记录。在同一个 Namespace 中,给定一个主机名为 “busybox-1” 的 Pod,子域名设置为 “default-subdomain”,名称为 “default-subdomain” 的 Headless Service ,Pod 将看到自己的 FQDN 为 “busybox-1.default-subdomain.my-namespace.svc.cluster.local”。DNS 会为那个名字提供一个 A 记录,指向该 Pod 的 IP。“busybox1” 和 “busybox2” 这两个 Pod 分别具有它们自己的 A 记录。
在Kubernetes v1.2 版本中,Endpoints 对象也具有 annotation endpoints.beta.kubernetes.io/hostnames-map。它的值是 map[string(IP)][endpoints.HostRecord] 的 JSON 格式,例如: ‘{“10.245.1.6”:{HostName: “my-webserver”}}’。
如果是 Headless Service 的 Endpoints,会以 ...svc. 的格式创建 A 记录。对示例中的 JSON 字符串,如果 `Endpoints` 是为名称为 “bar” 的 Headless Service 而创建的,其中一个 `Endpoints` 的 IP 是 “10.245.1.6”,则会创建一个名称为 “my-webserver.bar.my-namespace.svc.cluster.local” 的 A 记录,该 A 记录查询将返回 “10.245.1.6”。
Endpoints annotation 通常没必要由最终用户指定,但可以被内部的 Service Controller 用来提供上述功能。
在 v1.3 版本中,Endpoints 对象可以为任何 endpoint 指定 hostname 和 IP。hostname 字段优先于通过 endpoints.beta.kubernetes.io/hostnames-map annotation 指定的主机名。
在 v1.3 版本中,下面的 annotation 是过时的:pod.beta.kubernetes.io/hostname、pod.beta.kubernetes.io/subdomain、endpoints.beta.kubernetes.io/hostnames-map。
如何测试它是否可以使用?
创建一个简单的 Pod 作为测试环境
创建 busybox.yaml 文件,内容如下:
apiVersion: v1 kind: Pod metadata: name: busybox namespace: default spec: containers: - image: busybox command: - sleep - "3600" imagePullPolicy: IfNotPresent name: busybox restartPolicy: Always
然后,用该文件创建一个 Pod:
kubectl create -f busybox.yaml
等待这个 Pod 变成运行状态
获取它的状态,执行如下命令:
kubectl get pods busybox
可以看到如下内容:
NAME READY STATUS RESTARTS AGE busybox 1/1 Running 0 <some-time>
验证 DNS 已经生效
一旦 Pod 处于运行中状态,可以在测试环境中执行如下 nslookup 查询:
kubectl exec -ti busybox -- nslookup kubernetes.default
可以看到类似如下的内容:
Server: 10.0.0.10 Address 1: 10.0.0.10 Name: kubernetes.default Address 1: 10.0.0.1
如果看到了,说明 DNS 已经可以正确工作了。
问题排查技巧
如果执行 nslookup 命令失败,检查如下内容:
先检查本地 DNS 配置
查看配置文件 resolv.conf。(关于更多信息,参考下面的 “从 Node 继承 DNS” 和 “已知问题”。)
kubectl exec busybox cat /etc/resolv.conf
按照如下方法(注意搜索路径可能会因为云提供商不同而变化)验证搜索路径和 Name Server 的建立:
search default.svc.cluster.local svc.cluster.local cluster.local google.internal c.gce_project_id.internal nameserver 10.0.0.10 options ndots:5
快速诊断
出现类似如下指示的错误,说明 kube-dns 插件或相关 Service 存在问题:
$ kubectl exec -ti busybox -- nslookup kubernetes.default Server: 10.0.0.10 Address 1: 10.0.0.10 nslookup: can't resolve 'kubernetes.default'
或者
$ kubectl exec -ti busybox -- nslookup kubernetes.default Server: 10.0.0.10 Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local nslookup: can't resolve 'kubernetes.default'
检查是否 DNS Pod 正在运行
使用 kubectl get pods 命令验证 DNS Pod 正在运行:
kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
应该能够看到类似如下信息:
NAME READY STATUS RESTARTS AGE ... kube-dns-v19-ezo1y 3/3 Running 0 1h ...
如果看到没有 Pod 运行,或 Pod 失败/结束,DNS 插件不能默认部署到当前的环境,必须手动部署。
检查 DNS Pod 中的错误信息
使用 kubectl logs 命令查看 DNS 后台进程的日志:
kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c kubedns kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c dnsmasq kubectl logs --namespace=kube-system $(kubectl get pods --namespace=kube-system -l k8s-app=kube-dns -o name) -c healthz
查看是否有任何可疑的日志。在行开头的字母 W、E、F 分别表示 警告、错误、失败。请搜索具有这些日志级别的日志行,通过 Kubernetes 问题 报告意外的错误。
DNS 服务是否运行?
通过使用 kubectl get service 命令,验证 DNS 服务是否运行:
kubectl get svc --namespace=kube-system
应该能够看到:
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... kube-dns 10.0.0.10 <none> 53/UDP,53/TCP 1h ...
如果服务已经创建,或在这个例子中默认被创建,但是并没有看到,可以查看 调试 Service 页面 获取更多信息。
kubectl get ep kube-dns --namespace=kube-system
应该能够看到类似如下信息:
NAME ENDPOINTS AGE kube-dns 10.180.3.17:53,10.180.3.17:53 1h
如果没有看到 Endpoint,查看 调试 Service 文档 中的 Endpoint 段内容。
关于更多 Kubernetes DNS 的示例,参考 Kubernetes GitHub 仓库中 集群 DNS 示例。
Kubernetes Federation(多 Zone 支持)
在1.3 发行版本中,为多站点 Kubernetes 安装引入了集群 Federation 支持。这需要对 Kubernetes 集群 DNS 服务器处理 DNS 查询的方式,做出一些微小(后向兼容)改变,从而便利了对联合 Service 的查询(跨多个 Kubernetes 集群)。参考 集群 Federation 管理员指南 获取更多关于集群 Federation 和多站点支持的细节。
工作原理
运行的 Kubernetes DNS Pod 包含 3 个容器 —— kubedns、dnsmasq 和负责健康检查的 healthz。 kubedns 进程监视 Kubernetes master 对 Service 和 Endpoint 操作的变更,并维护一个内存查询结构去处理 DNS 请求。dnsmasq 容器增加了一个 DNS 缓存来改善性能。为执行对 dnsmasq 和 kubedns 的健康检查,healthz 容器提供了一个单独的健康检查 Endpoint。
DNS Pod 通过一个静态 IP 暴露为一个 Service。一旦 IP 被分配,kubelet 会通过 --cluster-dns=10.0.0.10 标志将配置的 DNS 传递给每一个容器。
DNS 名字也需要域名,本地域名是可配置的,在 kubelet 中使用 --cluster-domain=<default local domain> 标志。
Kubernetes 集群 DNS 服务器(根据 SkyDNS 库)支持正向查询(A 记录),Service 查询(SRV 记录)和反向 IP 地址查询(PTR 记录)。
从 Node 继承 DNS
当运行 Pod 时,kubelet 将集群 DNS 服务器和搜索路径追加到 Node 自己的 DNS 设置中。如果 Node 能够在大型环境中解析 DNS 名字,Pod 也应该没问题。参考下面 “已知问题” 中给出的更多说明。
如果不想这样,或者希望 Pod 有一个不同的 DNS 配置,可以使用 kubelet 的 --resolv-conf 标志。设置为 “” 表示 Pod 将不继承自 DNS。设置为一个合法的文件路径,表示 kubelet 将使用这个文件而不是 /etc/resolv.conf 。
已知问题
Kubernetes 安装但并不配置 Node 的 resolv.conf 文件,而是默认使用集群 DNS的,因为那个过程本质上就是和特定的发行版本相关的。最终应该会被实现。
Linux libc 在限制为3个 DNS nameserver 记录和3个 DNS search 记录是不可能卡住的(查看 2005 年的一个 Bug)。Kubernetes 需要使用1个 nameserver 记录和3个 search 记录。这意味着如果本地安装已经使用了3个 nameserver 或使用了3个以上 search,那些设置将会丢失。作为部分解决方法, Node 可以运行 dnsmasq ,它能提供更多 nameserver 条目,但不能运行更多 search 条目。可以使用 kubelet 的 --resolv-conf 标志。
如果使用 3.3 版本的 Alpine 或更早版本作为 base 镜像,由于 Alpine 的一个已知问题,DNS 可能不会正确工作。查看 这里 获取更多信息。