深度剖析 K8S DNS 的 Service 与 Pod
技术
作者:小君君
译者:小君君
2019-04-20 03:46

众所周知,Kubernetes master 存储了所有 Service 的定义和更新。但是,要与后端 Pod 通信的客户端 Pod(通过 Service 实现负载均衡)也需要知道这些请求会发送到何处。这些 Pod 可以将网络信息存储在容器环境变量中,但从长远来看这是不可行的。如果网络详细信息和一组后端 Pod 在将来发生更改时,客户端 Pod 将无法与它们通信。今天,我们就来一起看看 Kubernetes DNS 系统是如何解决这个问题的,然后通过一个实际用例加深对该系统设计原理的理解。

Kube-DNS 和 CoreDNS 是两个已建立的 DNS 解决方案,用于定义 DNS 命名规则。它们还可以将 Pod、DNS 服务解析为其相应的集群 IP。使用 DNS,Kubernetes 服务可以被 name 引用(该 name 对应于服务管理的任意数量的后端 Pod)。 

DNS 的命名方案也遵循了可预测的模式,使各种服务的地址更容易被记住。服务不仅可以通过完全限定域名(FQDN)引用,还可以仅通过服务本身的 name 引用。通过本文你将了解到:

  • Kubernetes DNS 如何运作;
  • Service DNS 记录;
  • Pod DNS 记录;
  • Pod 的主机名和子域字段;
  • 教程:如何通过 DNS 命名解决服务问题;
  • 总结。

Kubernetes DNS 如何运作

在 Kubernetes 中,你可以设置一个 DNS 系统,其中包含两个受到良好支持的附加组件:CoreDNS 和 Kube-DNS。CoreDNS 是一个较新的附加组件,从 Kubernetes v1.12 开始成为默认的 DNS 服务器。但,某些 Kubernetes 安装程序工具仍可将 Kube-DNS 安装为默认 DNS 系统。 

 这两个附加组件都可以在集群上调度一个或多个 DNS Pod 以及具有静态 IP 的服务。为了实现互操作性,两者在metadata.name字段都被命名为kube-dns。当管理员或安装工具配置集群时, kubelet会将 DNS 功能传递给每个带有--cluster-dns=<dns-service-ip>标识的容器。配置kubelet时,管理员还可以使用--cluster-domain=<default-local-domain> 指定本地域名。 

目前,Kubernetes DNS 加载项可以支持正向查找(A Record)、端口查找(SRV 记录)、反向 IP 地址查找(PTR 记录)以及一些其他选项。本文将继续讨论,在这些记录类型中 Pod 和 Service 的 Kubernetes 命名方案。 

Service DNS 记录 

通常,Kubernetes 服务支持 A Record、CNAME 和 SRV 记录。 

A Record

A Record 是用于将域或子域指向某个 IP 地址的 DNS 记录的最基本类型。记录包括域名、解析它的 IP 地址和以秒为单位的 TTL。TTL 代表生存时间,是 DNS 记录上的一种到期日期。每个 TTL 都会告诉 DNS 服务器,它应该在其缓存中保留给定记录多长时间。 

Kubernetes 为“normal”和“headless”服务分配不同的 A Record name。“headless”服务与“normal”服务的不同之处在于它们未分配 ClusterIP 且不执行负载均衡。 

“Normal”服务都分配了一个 DNS A Record 作为表单your-svc.your-namespace.svc.cluster.local的 name(根域名可以在kubelet设置中更改)。此 name 解析为服务的集群 IP。“Headless”服务还为表单your-svc.your-namespace.svc.cluster.local的 name 分配一个 DNS A Record。但是,与“normal”服务相反,此 name 解析的是,为服务选择的一组 Pod IP。DNS 不会自动将此设置解析为特定的 IP,因此客户端应该负责好集合中进行的负载均衡或循环选择。 

CNAME 

CNAME 记录用于将域或子域指向另一个主机名。为此,CNAME 使用现有的 A Record 作为其值。反过来,A Record 会解析为指定的 IP 地址。此外,在 Kubernetes 中,CNAME 记录可用于联合服务的跨集群服务发现。在整个场景中会有一个跨多个 Kubernetes 集群的公共服务。所有 Pod 都可以发现这项服务(无论这些 Pod 在哪个集群上)。这是一种跨集群服务发现方法。 

SRV 记录 

SRV 记录是通过描述某些服务协议和地址来促进服务发现的。SRV 记录通常定义一个符号名称和作为域名一部分的传输协议(如,TCP),并定义给定服务的优先级、权重、端口和目标(请参阅下面的示例)。

_sip._tcp.example.com.   3600 IN    SRV 10       70     5060 srvrecord.example.com.
_sip._tcp.example.com.   3600 IN    SRV 10       20     5060 srvrecord2.ex

在上面的示例中,_sip是服务的符号名称,_tcp是服务的使用传输协议。记录内容代表的意思是:两个记录都定义了 10 的优先级。另外,第一个记录的权重为 70,第二个记录的权重为 20。优先级和权重通常用于建议指定使用某些服务器。记录中的最后两个值定义了要连接的端口和主机名,以便与服务通信。

SRV 记录是为“normal”或“headless”服务的部分指定端口创建的。SRV 记录采用_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local的形式 。对于常规服务,它被解析的端口号和域名是:my-svc.my-namespace.svc.cluster.local。在 “headless” 服务的情况下,此 name 解析为多个 answer,每个 answer 都支持服务。 

每个 answer 都包含auto-generated-name.my-svc.my-namespace.svc.cluster.local表单的 Pod 端口号和域名。 

Pod DNS 记录

 A Record  

如果启用了 DNS,Pod 将以pod-ip-address.my-namespace.pod.cluster.local的形式被分配为一个 DNS 记录。例如,在default命名空间中 IP 为172.12.3.4、DNS 名称为cluster.local的 Pod 将有一个形式为172-12-3-4.default.pod.cluster.local的条目。 

Pod 的主机名和子域字段 

Pod 的默认主机名由 Pod 的metadata.name值定义。然而,用户可以通过在可选的hostname字段中指定一个新值来更改默认主机名。用户还可以在subdomain字段中自定义子域名。例如,在命名空间my-namespace中,将hostname设置为custom-host,将subdomain设置为custom-subdomain的 Pod 将具有完全限定的域名 (FQDN)custom-host.custom-subdomain.my-namespace.svc.cluster.local。 

教程:如何通过 DNS 命名解决服务问题 

接下来,本文将演示如何通过 DNS 命名解决服务问题,检查 DNS 解析并在发生 DNS 问题时对其进行调试。

要完成下面的示例,你需要满足以下先决条件:

  • 一个正在运行的 Kubernetes 集群;
  • 安装与配置 kubectl 命令行工具。

首先,你需要使用三个 Python HTTP 服务器创建一个部署,该服务器会在端口 80 上侦听连接并返回包含 Pod 主机名的自定义问候语。

apiVersion: apps/v1
kind: Deployment
metadata:
 name: test-deployment
spec:
 replicas: 3
 selector:
   matchLabels:
     app: test-pod
 template:
   metadata:
     labels:
       app: test-pod
   spec:
     containers:
     - name: python-http-server
       image: python:2.7
       command: ["/bin/bash"]
       args: ["-c", "echo \" Hello from $(hostname)\" > index.html; python -m SimpleHTTPServer 80"]
       ports:
       - name: http
         containerPort: 80

创建部署:

kubectl create -f deployment.yml 
deployment.extensions "test-deployment" created

接下来,创建一个服务来发现部署的 Pod 并在它们之间分发客户端请求。以下是分配 ClusterIP 的“normal”服务清单。

kind: Service
apiVersion: v1
metadata:
 name: test-service
spec:
 selector:
   app: test-pod
 ports:
 - protocol: TCP
   port: 4000
   targetPort: http

请注意,服务的spec.selector字段应该与部署创建的Pod的spec.template.metadata.labels相匹配。

kubectl create -f service.yml
service "test-service" created

最后,创建一个客户端 Pod,curl 将通过其 name 来提供服务。这样管理员就不需要知道服务端点的 IP,而是依赖于 Kubernetes Pod 的短暂性。

apiVersion: v1
kind: Pod
metadata:
 name: client-pod
spec:
 containers:
 - name: curl
   image: appropriate/curl
   command: ["/bin/sh"]
   args: ["-c","curl test-service:4000 "]

请注意,本实验使用的是服务名称,而不是部署创建的 ClusterIP 或 Pod 的 IP。你可以使用服务的 DNS 名称(“tut-service”),因为本实验设置的 Kubernetes 集群是使用 Kube-DNS 附加组件来监视 Kubernetes API 以获取新服务并为每个服务创建 DNS 记录。如果在集群中启用了 Kube-DNS,则所有 Pod 都可以自动执行服务的名称解析。但是,你也可以继续使用你的服务 ClusterIP。

kubectl create -f client.yml
pod "client-pod" created

创建客户端 Pod 后,检查日志。现在,你验证服务的名称已解析为正确的后端 Pod:


上面的响应表明 Kube-DNS 已正确解析服务的 ClusterIP 服务名称,并且服务已成功将客户端请求转发到以循环方式选择的随机后端 Pod。反过来,选定的 Pod 会返回其自定义问候语,你就可以在上面的响应中看到结果。 

使用 nslookup 检查 DNS 解析 

 现在,查找 A Record 定义的 FQDN,验证 DNS 是否正常工作。为此,你需要将 shell 添加到正在运行的 Pod 中,并在其中使用nslookup命令。
首先,找到在部署中创建的 Pod:

kubectl get pods -l app=test-pod
NAME                               READY     STATUS    RESTARTS   AGE
test-deployment-84dc998fc5-772gj   1/1       Running   0          1m
test-deployment-84dc998fc5-fh5pf   1/1       Running   0          1m
test-deployment-84dc998fc5-pkmsc   1/1       Running   0          1m

选择其中一个 Pod 并使用下面的命令获取 shell(使用你的唯一 Pod name):

kubectl exec -ti test-deployment-84dc998fc5-772gj -- /bin/bash

下一步,你需要在 BusyBox 包中安装nslookup命令:

apt-get update
apt-get install busybox

安装 BusyBox 后,请检查服务的 DNS:

busybox nslookup test-service.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      test-service.default.svc.cluster.local
Address 1: 10.109.90.121 test-service.default.svc.cluster.local

在上面的命令中,本实验使用了服务 A Record 的命名方案。现在,你可以通过验证 DNS 查找 DNS 服务的解析是否为正确的 IP(A Record)。

kubectl describe svc test-service
Name:              test-service
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=test-pod
Type:              ClusterIP
IP:                10.109.90.121
Port:              <unset>  4000/TCP
TargetPort:        http/TCP
Endpoints:         172.17.0.11:80,172.17.0.15:80,172.17.0.19:80
Session Affinity:  None
Events:            <none>

看起来很正确!你可以看到该服务的 ClusterIP 是10.109.90.121(与 DNS 查找解析的 IP 相同)。

调试 DNS  

 如果 nslookup 命令由于某种原因失败,你会有几个调试和故障排除的方案。但是,你应该如何得知 DNS 查找失败呢?如果 DNS 失败,你通常会得到如下响应:

kubectl exec -ti busybox -- nslookup kubernetes.default
Server:    10.0.0.10
Address 1: 10.0.0.10
nslookup: can't resolve 'kubernetes.default'

如果出现此错误,你需要做的第一件事是检查 DNS 配置是否正确。查看容器中的resolv.conf文件:

kubectl exec test-deployment-84dc998fc5-772gj cat /etc/resolv.conf

验证是否正确设置了搜索路径和名称服务器,如下例所示(请注意,搜索路径可能因不同的云提供商而异):

nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

如果是/etc/resolve.conf的所有条目都是正确的,你需要检查 kube-dns / coredns 插件是否已启用。在 Minikube 上,运行:

minikube addons list
- addon-manager: enabled
- coredns: disabled
- dashboard: enabled
- default-storageclass: enabled
- efk: disabled
- freshpod: disabled
- heapster: disabled
- ingress: disabled
- kube-dns: enabled
- metrics-server: enabled
- registry: disabled
- registry-creds: disabled
- storage-provisioner: enabled

如你所见,本实验启用了kube-dns。如果你的 DNS 加载项未运行,你可以尝试使用以下命令启用它:

minikube addons enable kube-dns
kube-dns was successfully enabled

或者,你可以检查 kubedns / coredns Pod 是否正在运行:

kubectl get pods --namespace=kube-system
NAME                                    READY     STATUS    RESTARTS   AGE
....
kube-dns-86f4d74b45-2qkfd               3/3       Running   232        133d
kube-proxy-b2frq                        1/1       Running   0          15m
...

如果 Pod 正在运行,则全局 DNS 服务可能存在问题。
检查一下:

$ kubectl get svc --namespace=kube-system
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
kube-dns               ClusterIP   10.96.0.10       <none>        53/UDP,53

你可能还需要检查是否公开了 DNS 端点:

kubectl get ep kube-dns --namespace=kube-system
NAME       ENDPOINTS                     AGE
kube-dns   172.17.0.5:53,172.17.0.5:53   133d

这些调试操作通常会指示 DNS 配置的问题,或者它只是向你显示应在集群配置中启用的 DNS 加载项。

总结

总而言之,Kubernetes 通过其内置的 DNS 附件实现高效的服务发现:Kube-DNS 或 CoreDNS。Kubernetes DNS 系统会将域和子域名分配给 Pod、端口和服务,这使得它们可以被 Kubernetes 集群中的其他组件发现。

基于 DNS 的服务发现是非常强大,因为用户不需要将 IP 和端口等网络参数硬编码到应用程序中。当服务管理一组 Pod 时,你就可以使用服务的 DNS 轻松访问它们了。



 

330 comCount 0
相关文章推荐