一次K8s中的Pod解析外网域名错误的问题排查

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 一次K8s中的Pod解析外网域名错误的问题排查

1、故障现象


我们一个agent代理服务,发布到k8s集群之后,pod状态是Running,但是server一直无法收到心跳信号,因此到集群内部去排查日志,发现该服务日志中出现大量的连接某一个ip地址tcp timeout


640.png


2、故障排查过程


通过查看日志发现是大量的错误日志,连接某个ip地址产生i/o timeout,因此排查服务的业务逻辑,该服务只会去连接server端,在服务的环境变量里配置了server端的域名,怀疑是不是有可能server端挂掉,在本地和集群宿主机上调用server的地址,发现是可以通的,因此排除掉了server端本身的问题


因为server端连接地址在我本地和集群宿主机上是可以正常调用,因此怀疑服务pod到server端地址不通,进入到pod中进行测试,发现的确不能调用,使用ping域名也是可以通的,但是发现ping解析出来的ip地址并不是我们server端的外网ip地址;因此怀疑到了dns解析的问题上,使用nsloopup命令进行排除(通常服务都没有该命令需要手动安装apt-get install dnsutils,yum install bind-utils,或者使用kubectl-debug工具来共享容器排查),解析出来的发现很诡异的name,域名最后面带了一个HOST


640.png


进一步查看/etc/resolv.conf,发现搜索域中有一个HOST搜索域,因此解析域名会带上HOST


640.png


又测试了几个域名,只要最后带HOST,都会解析到一个ip地址上,上网一搜,才知道这个HOST是个顶级域名,还会泛解析到某个ip上


640.png

640.png


至此,导致本次故障的原因,已定位到,是由于pod中的搜索域中带了一个顶级域名HOST,产生的泛解析到了一个不是我们server端的地址上


3、故障原因分析


首先我们需要知道在k8s中的pod是如何进行服务之间域名调用,是如何解析的?


Kubernetes 中的域名解析分析


  • 集群内部域名解析


在 Kubernetes 中,比如服务 a 访问服务 b,对于同一个 Namespace下,可以直接在 pod 中,通过 curl b 来访问。对于跨 Namespace 的情况,服务名后边对应 Namespace即可。比如 curl b.devops。那么,使用者这里边会有几个问题:


①:服务名是什么?②:为什么同一个 Namespace 下,直接访问服务名即可?不同 Namespace 下,需要带上 Namespace 才行?③:为什么内部的域名可以做解析,原理是什么?


DNS 如何解析,依赖容器内 resolv 文件的配置


cat /etc/resolv.conf
nameserver 10.68.0.2
search devops.svc.cluster.local. svc.cluster.local. cluster.local.


这个文件中,配置的 DNS Server,一般就是 K8S 中,kubedns 的 Service 的 ClusterIP,这个IP是虚拟IP,无法ping,但可以访问。


root@other-8-67:~# kubectl get svc -n kube-system |grep dns
kube-dns                    ClusterIP   10.68.0.2       <none>        53/UDP,53/TCP,9153/TCP   106d


所以,所有域名的解析,其实都要经过 kubedns 的虚拟IP 10.68.0.2 进行解析,不论是 Kubernetes 内部域名还是外部的域名。Kubernetes 中,域名的全称,必须是 service-name.namespace.svc.cluster.local 这种模式,服务名,就是Kubernetes中 Service 的名称,所以,当我们执行下面的命令时:


curl b


必须得有一个 Service 名称为 b,这是前提。


在容器内,会根据 /etc/resolve.conf 进行解析流程。选择 nameserver 10.68.0.2 进行解析,然后,用字符串 “b”,依次带入 /etc/resolve.conf 中的 search 域,进行DNS查找,分别是:


// search 内容类似如下(不同的pod,第一个域会有所不同)
search devops.svc.cluster.local svc.cluster.local cluster.local


b.devops.svc.cluster.local -> b.svc.cluster.local -> b.cluster.local ,直到找到为止。


所以,我们执行 curl b,或者执行 curl b.devops,都可以完成DNS请求,这2个不同的操作,会分别进行不同的DNS


// curl b,可以一次性找到(b +devops.svc.cluster.local)
b.devops.svc.cluster.local
// curl b.devops,第一次找不到( b.devops + devops.svc.cluster.local)
b.devops.devops.svc.cluster.local
// 第二次查找( b.devops + svc.cluster.local),可以找到
b.devops.svc.cluster.local


因此curl b,要比 curl b.devops 效率高,因为 curl b.devops,多经过了一次 DNS 查询。


  • 集群外部域名解析


访问外部域名走 search 域吗,看情况,可以说,大部分情况要走 search 域。我们以请求 baidu.com 为例,通过抓包的方式,看一看在某个容器中访问 baidu.com,进行的DNS查找的过程,都产生了什么样的数据包。注意:我们要抓DNS容器的包,就得先进入到DNS容器的网络中(而不是发起DNS请求的那个容器)。


由于DNS容器往往不具备bash,所以无法通过 docker exec 的方式进入容器内抓包,我们采用其他的方式:


// 1、找到容器ID,并打印它的NS ID
docker inspect --format "{{.State.Pid}}"  16938de418ac
// 2、进入此容器的网络Namespace
nsenter -n -t  54438
// 3、抓DNS包
tcpdump -i eth0 udp dst port 53|grep baidu.com


在其他的容器中,进行 baidu.com 域名查找


nslookup  baidu.com 114.114.114.114


注意:nslookup命令的最后指定DNS服务容器的IP,是因为,如果不指定,且DNS服务的容器存在多个的话,那么DNS请求,可能会均分到所有DNS服务的容器上,我们如果只抓某单个DNS服务容器抓到的包,可能就不全了,指定IP后,DNS的请求,就必然只会打到单个的DNS容器。抓包的数据才完整。


可以看到类似如下结果:


11:46:26.843118 IP srv-device-manager-7595d6795c-8rq6n.60857 > kube-dns.kube-system.svc.cluster.local.domain: 19198+ A? baidu.com.devops.svc.cluster.local. (49)
11:46:26.843714 IP srv-device-manager-7595d6795c-8rq6n.35998 > kube-dns.kube-system.svc.cluster.local.domain: 53768+ AAAA? baidu.com.devops.svc.cluster.local. (49)
11:46:26.844260 IP srv-device-manager-7595d6795c-8rq6n.57939 > kube-dns.kube-system.svc.cluster.local.domain: 48864+ A? baidu.com.svc.cluster.local. (45)
11:46:26.844666 IP srv-device-manager-7595d6795c-8rq6n.35990 > kube-dns.kube-system.svc.cluster.local.domain: 43238+ AAAA? baidu.com.svc.cluster.local. (45)
11:46:26.845153 IP srv-device-manager-7595d6795c-8rq6n.58745 > kube-dns.kube-system.svc.cluster.local.domain: 59086+ A? baidu.com.cluster.local. (41)
11:46:26.845543 IP srv-device-manager-7595d6795c-8rq6n.32910 > kube-dns.kube-system.svc.cluster.local.domain: 30930+ AAAA? baidu.com.cluster.local. (41)
11:46:26.845907 IP srv-device-manager-7595d6795c-8rq6n.55367 > kube-dns.kube-system.svc.cluster.local.domain: 58903+ A? baidu.com. (27)
11:46:26.861714 IP srv-device-manager-7595d6795c-8rq6n.32900 > kube-dns.kube-system.svc.cluster.local.domain: 58394+ AAAA? baidu.com. (27)


我们可以看到,在真正解析 baidu.com 之前,经历了 baidu.com.devops.svc.cluster.local. -> baidu.com.svc.cluster.local. -> baidu.com.cluster.local. -> baidu.com.


这也就意味着有3次DNS请求,是浪费的无意义的请求。这是因为,在 Kubernetes 中,其实 /etc/resolv.conf 这个文件,并不止包含 nameserver 和 search 域,还包含了非常重要的一项:ndots。


/prometheus $ cat /etc/resolv.conf
nameserver 10.66.0.2
search monitor.svc.cluster.local. svc.cluster.local. cluster.local. 
options ndots:5


ndots:5,表示:如果查询的域名包含的点“.”,不到5个,那么进行DNS查找,将使用非完全限定名称(或者叫绝对域名),如果你查询的域名包含点数大于等于5,那么DNS查询,默认会使用绝对域名进行查询。举例来说:


如果我们请求的域名是,a.b.c.d.e,这个域名中有4个点,那么容器中进行DNS请求时,会使用非绝对域名进行查找,使用非绝对域名,会按照 /etc/resolv.conf 中的 search 域,走一遍追加匹配:


a.b.c.d.e.devops.svc.cluster.local. ->
a.b.c.d.e.svc.cluster.local. ->
a.b.c.d.e.cluster.local.


直到找到为止。如果走完了search域还找不到,则使用 a.b.c.d.e. ,作为绝对域名进行DNS查找。


我们通过抓包分析一个具体案例:域名中点数少于5个的情况:


// 对域名 a.b.c.d.com 进行DNS解析请求 
root@srv-xxx-7595d6795c-8rq6n:/go/bin# nslookup  a.b.c.d.com
Server:  10.68.0.2
Address: 10.68.0.2#53
** server can't find a.b.c.d.com: NXDOMAIN
// 抓包数据如下:
root@srv-device-manager-7595d6795c-8rq6n:/go/bin# tcpdump -i eth0 udp dst port 53  -c 20 |grep a.b.c.d.com
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20 packets captured16:14:40.053575 IP srv-device-manager-7595d6795c-8rq6n.37359 > kube-dns.kube-system.svc.cluster.local.domain: 29842+ A? a.b.c.d.com.cluster.local. (43)
16:14:40.054083 IP srv-device-manager-7595d6795c-8rq6n.34813 > kube-dns.kube-system.svc.cluster.local.domain: 19104+ AAAA? a.b.c.d.com.cluster.local. (43)
25 packets received by filter16:14:40.054983 IP srv-device-manager-7595d6795c-8rq6n.37303 > kube-dns.kube-system.svc.cluster.local.domain: 53902+ A? a.b.c.d.com.devops.svc.cluster.local. (51)
16:14:40.055465 IP srv-device-manager-7595d6795c-8rq6n.40766 > kube-dns.kube-system.svc.cluster.local.domain: 34453+ AAAA? a.b.c.d.com.devops.svc.cluster.local. (51)
0 packets dropped by kernel
16:14:40.055946 IP srv-device-manager-7595d6795c-8rq6n.35443 > kube-dns.kube-system.svc.cluster.local.domain: 24829+ A? a.b.c.d.com.svc.cluster.local. (47)
16:14:40.057698 IP srv-device-manager-7595d6795c-8rq6n.44180 > kube-dns.kube-system.svc.cluster.local.domain: 23046+ AAAA? a.b.c.d.com.svc.cluster.local. (47)
16:14:40.058062 IP srv-device-manager-7595d6795c-8rq6n.56986 > kube-dns.kube-system.svc.cluster.local.domain: 42008+ A? a.b.c.d.com. (29)
16:14:40.075579 IP srv-device-manager-7595d6795c-8rq6n.55738 > kube-dns.kube-system.svc.cluster.local.domain: 32284+ AAAA? a.b.c.d.com. (29)
// 结论:
// 点数少于5个,先走search域,最后将其视为绝对域名进行查询


域名中点数>=5个的情况:


// 对域名 a.b.c.d.e.com 进行DNS解析请求 
root@srv-xxx-7595d6795c-8rq6n:/go/bin# nslookup  a.b.c.d.e.com
Server:  10.68.0.2
Address: 10.68.0.2#53
** server can't find a.b.c.d.e.com: NXDOMAIN
// 抓包数据如下:
root@srv-device-manager-7595d6795c-8rq6n:/go/bin# tcpdump -i eth0 udp dst port 53  -c 20 |grep a.b.c.d.e.com
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:32:39.624305 IP srv-device-manager-7595d6795c-8rq6n.56274 > kube-dns.kube-system.svc.cluster.local.domain: 43582+ A? a.b.c.d.e.com. (31)
20 packets captured16:32:39.805470 IP srv-device-manager-7595d6795c-8rq6n.56909 > kube-dns.kube-system.svc.cluster.local.domain: 27206+ AAAA? a.b.c.d.e.com. (31)
16:32:39.833203 IP srv-device-manager-7595d6795c-8rq6n.33370 > kube-dns.kube-system.svc.cluster.local.domain: 14881+ A? a.b.c.d.e.com.cluster.local. (45)
21 packets received by filter16:32:39.833779 IP srv-device-manager-7595d6795c-8rq6n.40814 > kube-dns.kube-system.svc.cluster.local.domain: 43047+ AAAA? a.b.c.d.e.com.cluster.local. (45)
16:32:39.834363 IP srv-device-manager-7595d6795c-8rq6n.53053 > kube-dns.kube-system.svc.cluster.local.domain: 17994+ A? a.b.c.d.e.com.iot.svc.cluster.local. (53)
0 packets dropped by kernel16:32:39.834740 IP srv-device-manager-7595d6795c-8rq6n.47803 > kube-dns.kube-system.svc.cluster.local.domain: 15951+ AAAA? a.b.c.d.e.com.iot.svc.cluster.local. (53)
16:32:39.835177 IP srv-device-manager-7595d6795c-8rq6n.60845 > kube-dns.kube-system.svc.cluster.local.domain: 38541+ A? a.b.c.d.e.com.svc.cluster.local. (49)
16:32:39.835611 IP srv-device-manager-7595d6795c-8rq6n.36086 > kube-dns.kube-system.svc.cluster.local.domain: 49809+ AAAA? a.b.c.d.e.com.svc.cluster.local. (49)
// 结论:
// 点数>=5个,直接视为绝对域名进行查找,只有当查询不到的时候,才继续走 search 域。


优化方式1:使用全限定域名


其实最直接,最有效的优化方式,就是使用 “fully qualified name”,简单来说,使用“完全限定域名”(也叫绝对域名),你访问的域名,必须要以 “.” 为后缀,这样就会避免走 search 域进行匹配,我们抓包再试一次:


nslookup  a.b.c.com.


在DNS服务容器上抓到的包如下


root@srv-device-manager-7595d6795c-8rq6n:/go/bin# tcpdump -i eth0 udp dst port 53  -c 20 |grep a.b.c.com.
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:39:31.771615 IP srv-device-manager-7595d6795c-8rq6n.50332 > kube-dns.kube-system.svc.cluster.local.domain: 50829+ A? a.b.c.com. (27)
20 packets captured16:39:31.793579 IP srv-device-manager-7595d6795c-8rq6n.51946 > kube-dns.kube-system.svc.cluster.local.domain: 25235+ AAAA? a.b.c.com. (27)


并没有多余的DNS请求


优化方式2:具体应用配置特定的 ndots


其实,往往我们还真不太好用这种绝对域名的方式,有谁请求baidu.com的时候,还写成 baidu.com. 呢?


在 Kubernetes 中,默认设置了 ndots 值为5,是因为,Kubernetes 认为,内部域名,最长为5,要保证内部域名的请求,优先走集群内部的DNS,而不是将内部域名的DNS解析请求,有打到外网的机会,Kubernetes 设置 ndots 为5是一个比较合理的行为。


如果你需要定制这个长度,最好是为自己的业务,单独配置 ndots 即可(deployment为例)。


...
    spec:
      containers:
      - env:
        - name: GOENV
          value: DEV
        image: xxx/devops/srv-inner-proxy
        imagePullPolicy: IfNotPresent
        lifecycle: {}
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /health
            port: 8000
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          timeoutSeconds: 1
        name: srv-inner-proxy
        ports:
        - containerPort: 80
          protocol: TCP
        - containerPort: 8000
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /health
            port: 8000
            scheme: HTTP
          initialDelaySeconds: 5
          periodSeconds: 5
          successThreshold: 1
          timeoutSeconds: 1
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsConfig:
        options:
        - name: timeout
          value: "2"
        - name: ndots
          value: "2"
        - name: single-request-reopen
      dnsPolicy: ClusterFirst
      ...


在Kubernetes 中,有4种 DNS 策略


具体来说:


  • None

表示空的DNS设置


这种方式一般用于想要自定义 DNS 配置的场景,而且,往往需要和 dnsConfig 配合一起使用达到自定义 DNS 的目的。


  • Default

有人说 Default 的方式,是使用宿主机的方式,这种说法并不准确。


这种方式,其实是,让 kubelet 来决定使用何种 DNS 策略。而 kubelet 默认的方式,就是使用宿主机的 /etc/resolv.conf(可能这就是有人说使用宿主机的DNS策略的方式吧),但是,kubelet 是可以灵活来配置使用什么文件来进行DNS策略的,我们完全可以使用 kubelet 的参数:–resolv-conf=/etc/resolv.conf 来决定你的DNS解析文件地址。


  • ClusterFirst

这种方式,表示 POD 内的 DNS 使用集群中配置的 DNS 服务,简单来说,就是使用 Kubernetes 中 kubedns 或 coredns 服务进行域名解析。如果解析不成功,才会使用宿主机的 DNS 配置进行解析。


  • ClusterFirstWithHostNet

在某些场景下,我们的 POD 是用 HOST 模式启动的(HOST模式,是共享宿主机网络的),一旦用 HOST 模式,表示这个 POD 中的所有容器,都要使用宿主机的 /etc/resolv.conf 配置进行DNS查询,但如果你想使用了 HOST 模式,还继续使用 Kubernetes 的DNS服务,那就将 dnsPolicy 设置为 ClusterFirstWithHostNet。


这几种DNS策略,需要在Pod,或者Deployment、RC等资源中,设置 dnsPolicy 即可


4、结论


通过故障原因的分析,我们可以知道该故障比较好的解决办法,就是在deployment中去设置dnsPolicy,在不影响集群内服务直接调用的情况下,把ndots从默认的5修改成了2,使代理服务pod在访问server端域名的时候dns解析直接走绝对域名,这样就会避免走 search 域进行匹配,可以正确匹配到ip地址。通过此次故障也让我知其然知其所以然,在排查故障的过程中,需要去了解背后涉及到的知识点和根本原因。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
25天前
|
域名解析 缓存 网络协议
DNS更新后不生效?快速排查攻略
本文介绍了修改DNS后不生效,其主因是DNS传播延迟。TTL值、ISP缓存及服务器位置影响传播速度。提前调小TTL、清除本地缓存、更换公共DNS可加速。通过nslookup、Dig或Myssl工具可检测全球解析状态,确保更新完成。
243 1
备案成功以后,也解析了为什么没办法通过域名收到网站呢
网站备案成功后仍无法通过域名访问,可能涉及解析设置错误、服务器配置问题或网络限制等原因。本文将详细分析常见原因并提供解决方案。
|
7月前
|
Kubernetes Docker 容器
Kubernetes与Docker参数对照:理解Pod中的command、args与Dockerfile中的CMD、ENTRYPOINT。
需要明确的是,理解这些都需要对Docker和Kubernetes有一定深度的理解,才能把握二者的区别和联系。虽然它们都是容器技术的二个重要组成部分,但各有其特性和适用场景,理解它们的本质和工作方式,才能更好的使用这些工具,将各自的优点整合到生产环境中,实现软件的快速开发和部署。
236 25
|
11月前
|
Prometheus Kubernetes 监控
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
|
7月前
|
Kubernetes Shell Windows
【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享
在AKS中遇到复杂网络问题时,可通过以下步骤进入特定POD抓取网络包进行分析:1. 使用`kubectl get pods`确认Pod所在Node;2. 通过`kubectl node-shell`登录Node;3. 使用`crictl ps`找到Pod的Container ID;4. 获取PID并使用`nsenter`进入Pod的网络空间;5. 在`/var/tmp`目录下使用`tcpdump`抓包。完成后按Ctrl+C停止抓包。
242 12
|
11月前
|
存储 Kubernetes Docker
【赵渝强老师】Kubernetes中Pod的基础容器
Pod 是 Kubernetes 中的基本单位,代表集群上运行的一个进程。它由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。基础容器负责维护 Pod 的网络空间,对用户透明。文中附有图片和视频讲解,详细介绍了 Pod 的组成结构及其在网络配置中的作用。
185 1
【赵渝强老师】Kubernetes中Pod的基础容器
|
11月前
|
域名解析 网络协议 安全
反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性
在网络世界中,反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性。它在邮件服务器验证、网络安全等领域至关重要,帮助识别恶意行为,增强网络安全性。尽管存在配置错误等挑战,但正确管理下,反向DNS解析能显著提升网络环境的安全性和可靠性。
668 3
|
11月前
|
运维 Kubernetes Shell
【赵渝强老师】K8s中Pod的临时容器
Pod 是 Kubernetes 中的基本调度单位,由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。临时容器用于故障排查和性能诊断,不适用于构建应用程序。当 Pod 中的容器异常退出或容器镜像不包含调试工具时,临时容器非常有用。文中通过示例展示了如何使用 `kubectl debug` 命令创建临时容器进行调试。
197 1
|
11月前
|
Kubernetes 调度 容器
【赵渝强老师】K8s中Pod中的业务容器
Pod 是 Kubernetes 中的基本调度单元,由一个或多个容器组成。除了业务容器,Pod 还包括基础容器、初始化容器和临时容器。本文通过示例介绍如何创建包含业务容器的 Pod,并提供了一个视频讲解。示例中创建了一个名为 &quot;busybox-container&quot; 的业务容器,并使用 `kubectl create -f firstpod.yaml` 命令部署 Pod。
147 1
|
7月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
683 29

热门文章

最新文章

推荐镜像

更多
  • DNS
  • 下一篇
    oss教程