目录
IP地址是TCP/IP协议中,用来确定通讯双方的一个重要标识,每个IP地址又包括了主机号和网络号两部分,相同网络号的主机组成一个子网,不同子网再通过路由连接,组成一个庞大的网络
但IP地址不方便记忆,于是又了NDS(Domain Name System)域名系统,是互联网中最基础的一项服务,主要提供域名和IP地址之间映射关系的查询服务
DNS不仅方便人们访问不同的互联网服务,更提供了动态服务发现,全局负载均衡(Global Server Load Balance GSLB)的机制吗,这样DNS就可以选择离用户最近的IP来提供服务,即使后端服务的IP地址发生变化,用户依然可以用相同的域名来访问
域名和DNS解析
域名是全球唯一的,需要通过专门的域名注册商才可以申请注册,为了组织全球互联网中众多的计算机,域名同样用点来分开,形成一个分层的结构,而每个被点分割开的字符串,就构成了域名中的一个层级,并且为之越靠后,层级越高
以极客时间 time.geekbang.org 为列
这个字符串中,最后面的org是顶级域名
中间的geekbang是二级域名
最左边的time是三级域名
如下图所示,注意 . 是所有域名的根,也就是说所有域名都以点作为后缀,可以理解为在域名解析过程中,所有域名都以点结束
通过理解这几个概念,可以看出,域名是为了方便人记住,而IP地址是机器间通讯的真正机制,把域名转换为IP地址的服务,也就是开头提到的域名解析服务NDS,对应的服务器就是域名服务器,网络协议为DNS
注意,DNS在TCP/IP中属于应用层,不过实际传输还是基于UDP或TCP协议(UDP居多),并且域名服务器一般在端口53上监听
域名解析也是用递归的方式,从顶层开始,以此类推,发送给每个层级的域名服务器,直到得到解析结果
递归查询过程中,不需要用户操作,DNS服务器会替用户完成
通常来说,每层的NDS服务器,都会有最近解析记录的缓存,当缓冲命中时,直接用缓存中的记录应答就可以了,如果缓存过期或者不存在,才需要用递归的方式查询
系统管理员在配置Linux系统网络时,除了需要配置IP地址,还需要给它配置DNS服务器,这样它才可以通过欲望来访问外部服务
如配置114.114.114.114这个域名服务器
cat /etc/resolv.conf
nameserver 114.114.114.114
DNS服务通过资源记录的方式,来管理所有数据,它支持A记录,CNAME,MX,NS,PTR等多种类型记录
A记录,用来把域名转换成IP地址
CNAME记录,用来创建别名
NS记录,表示该域名对应的域名服务器地址
当访问某个网址时,就需要通过DNS的A记录,查询该域名对应的IP地址,然后再通过IP来访问Web服务
比如,访问极客时间 time.geekbang.org,执行nslookup命令,可以查询到这个域名的A记录,可以看到它的IP地址信息
nslookup time.geekbang.org
Server: 114.114.114.114
Address: 114.114.114.114#53
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
通过debug方式执行nslookup的结果
nslookup -debug time.geekbang.org
Server: 114.114.114.114
Address: 114.114.114.114#53
------------
QUESTIONS:
time.geekbang.org, type = A, class = IN
ANSWERS:
-> time.geekbang.org
internet address = 39.106.233.176
ttl = 324
AUTHORITY RECORDS:
ADDITIONAL RECORDS:
------------
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
如果想知道整个查询过程,可以用另外一个DNS查询工具dig
# +trace表示开启跟踪查询
# +nodnssec 表示禁止DNS安全扩展
dig +trace +nodnssec time.geekbang.org
; <<>> DiG 9.9.4-RedHat-9.9.4-72.el7 <<>> +trace +nodnssec time.geekbang.org
;; global options: +cmd
. 357157 IN NS i.root-servers.net.
. 357157 IN NS c.root-servers.net.
. 357157 IN NS m.root-servers.net.
. 357157 IN NS b.root-servers.net.
. 357157 IN NS a.root-servers.net.
. 357157 IN NS e.root-servers.net.
. 357157 IN NS l.root-servers.net.
. 357157 IN NS j.root-servers.net.
. 357157 IN NS f.root-servers.net.
. 357157 IN NS k.root-servers.net.
. 357157 IN NS g.root-servers.net.
. 357157 IN NS h.root-servers.net.
. 357157 IN NS d.root-servers.net.
;; Received 239 bytes from 114.114.114.114#53(114.114.114.114) in 455 ms
org. 172800 IN NS a0.org.afilias-nst.info.
org. 172800 IN NS a2.org.afilias-nst.info.
org. 172800 IN NS b0.org.afilias-nst.org.
org. 172800 IN NS b2.org.afilias-nst.org.
org. 172800 IN NS c0.org.afilias-nst.info.
org. 172800 IN NS d0.org.afilias-nst.org.
;; Received 448 bytes from 192.203.230.10#53(e.root-servers.net) in 361 ms
geekbang.org. 86400 IN NS dns10.hichina.com.
geekbang.org. 86400 IN NS dns9.hichina.com.
;; Received 96 bytes from 199.19.54.1#53(b0.org.afilias-nst.org) in 368 ms
time.geekbang.org. 600 IN A 39.106.233.176
;; Received 62 bytes from 106.11.211.56#53(dns10.hichina.com) in 6 ms
dig trace的输出,主要包括四部分
- 第一部分,是从114.114.114.114查到的一些根域名服务器 . 的NS记录
- 第二部分,是从NS记录结果中选一个 h.root-server.net,并查询顶级域名org. 的NS记录
- 第三部分,是从org. 的NS记录中选一个b0.org.afilias-nst.org,并查询二级域名geekbang.org的NS服务器
- 第四部分,是从geekbang.org的NS服务器nds10.hichina.com查询最终主机time.geekbang.org的A记录
这个输出里展示的各级域名的NS记录,其实就是各级域名服务器的地址,下面是整个查询的流程图
当然,不仅仅是发布到互联网的服务器需要域名,对局域网内部的主机进行域名解析,即内网域名,大多数情况下为主机名,Linux也支持这种行为
可以把主机名和IP地址的映射关系,写入本机的/etc/hosts文件中,这样指定的主机名就可以在本地直接查找到目标IP,比如
cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
或者,可以在内网中,搭建自定义的DNS服务器,专门用来解析内网中的域名,而内网DNS服务器,一般还会设置一个或多个上游DNS服务器,用来解析网络的域名
案例分析
案例准备
拉取案例中使用的Docker镜像
docker pull feisky/dnsutils
Using default tag: latest
Trying to pull repository docker.io/feisky/dnsutils ...
latest: Pulling from docker.io/feisky/dnsutils
38e2e6cd5626: Already exists
705054bc3f5b: Already exists
c7051e069564: Already exists
7308e914506c: Already exists
9b20820a1a69: Pull complete
8633a2284391: Pull complete
89ab6a8002a6: Pull complete
Digest: sha256:a23534daa60aad8736823219852b6dbd9b51e84ddbaf42e38b54c68954719766
Status: Downloaded newer image for docker.io/feisky/dnsutils:latest
查看当前主机配置的DNS服务器
cat /etc/resolv.conf
options timeout:1 attempts:1 rotate single-request-reopen
search localdomain
nameserver 114.114.114.114
DNS解析失败
首先进入容器环境
# 进入案例的SHELL终端中
docker run -it --rm -v $(mktemp):/etc/resolv.conf feisky/dnsutils bash
接着,在容器中,执行DNS查询命令,还是查询极客时间地址
# root后面的 29e9097050ca 是Docker生成的ID前缀
root@29e9097050ca:/# nslookup time.geekbang.org
;; connection timed out; no servers could be reached
这个命令阻塞了一段时间最后报错,连接超时,服务不可达
检查一下本地到114的连通性
root@29e9097050ca:/# ping -c 3 114.114.114.114
PING 114.114.114.114 (114.114.114.114): 56 data bytes
64 bytes from 114.114.114.114: icmp_seq=0 ttl=64 time=32.986 ms
64 bytes from 114.114.114.114: icmp_seq=1 ttl=71 time=32.972 ms
64 bytes from 114.114.114.114: icmp_seq=2 ttl=88 time=33.018 ms
--- 114.114.114.114 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 32.972/32.992/33.018/0.000 ms
网络是通的,但nslookup执行失败了,这时候开启nslookup的调试输出,看查询过程中的详细步骤,排查异常
root@29e9097050ca:/# nslookup -debug time.geekbang.org
;; Connection to 127.0.0.1#53(127.0.0.1) for time.geekbang.org failed: connection refused.
;; Connection to ::1#53(::1) for time.geekbang.org failed: connection refused.
可以看到nslookup连接环回地址127.0.0.1和::1的53端口失败,正常来说应该是连接114.114.114.114的
但这里却连接到本地了,检查下DNS的本地配置文件
#结果是空的
cat /etc/resolv.conf
重新配置上DNS服务器,再执行nslookup命令,这次就正常了
echo "nameserver 114.114.114.114" > /etc/resolv.conf
root@29e9097050ca:/# nslookup time.geekbang.org
Server: 114.114.114.114
Address: 114.114.114.114#53
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
最后在终端中执行exit命令退出容器,Docker会自动清理刚才运行的容器
DNS解析不稳定
再进入第二个案例,使用下面命令
docker run -it --rm --cap-add=NET_ADMIN --dns 8.8.8.8 feisky/dnsutils bash
再用nslookup查询,并加上time,案例中给出的查询时间是10秒,甚至还有超时,但我这里查询很快,可能是DNS
缓存等其他原因导致的
time nslookup time.geekbang.org
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
real 0m0.245s
user 0m0.006s
sys 0m0.006s
需要245毫秒完成
DNS解析,其实就是客户端与服务器交互的课程,并且这个过程还使用了UDP
对于整个流程,不稳定的情况有很多,比如
- DNS服务器本身有问题,响应慢并且不稳定
- 客户端到DNS服务器的网络延迟比较大
- DNS请求或者响应包,在某些情况下被链路中的网络设备弄丢了
根据nslookup的输出,客户端连接的DNS是8.8.8.8,也就是谷歌提供的DNS服务器
假设谷歌提供的DNS服务器是正常的
那么测试一下本机到DNS服务器的延迟情况
ping -c 3 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=38 time=53.284 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=38 time=53.193 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=38 time=53.228 ms
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 53.193/53.235/53.284/0.037 ms
ping的延迟是50多毫秒,多执行几次ping,偶尔也有超时丢包的情况
nslookup执行失败,慢就是因为网络链路中的丢包延迟导致的
既然延迟大,就换一个DNS服务器,比如使用电信的114.114.114.114
先测试一下 到114的连通情况
ping -c 3 114.114.114.114
PING 114.114.114.114 (114.114.114.114): 56 data bytes
64 bytes from 114.114.114.114: icmp_seq=0 ttl=86 time=32.906 ms
64 bytes from 114.114.114.114: icmp_seq=1 ttl=66 time=32.904 ms
64 bytes from 114.114.114.114: icmp_seq=2 ttl=91 time=34.569 ms
--- 114.114.114.114 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 32.904/33.460/34.569/0.784 ms
根据结果看,稍微好一些,从延迟50毫秒 -> 延迟30毫秒
执行下面命令,更换DNS服务器,再执行nslookup解析命令
cat /etc/resolv.conf
search localdomain
nameserver 8.8.8.8
options timeout:1 attempts:1 rotate single-request-reopen
root@879899eacac7:/# echo "nameserver 114.114.114.114" > /etc/resolv.conf
root@879899eacac7:/# cat /etc/resolv.conf
nameserver 114.114.114.114
time nslookup time.geekbang.org
Server: 114.114.114.114
Address: 114.114.114.114#53
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
real 0m0.083s
user 0m0.008s
sys 0m0.002s
这次只需要83毫秒就可以完成
不过偶尔还是会有一些时间比较长的,可能当时网络比较繁忙所以返回的结果就比较慢了
其实第一次解析完后,可以将结果放到DNS缓存中
想要为系统开启DNS缓存,就需要额外的配置,最简单的方法是使用 dnsmasq
dnsmasq 是最常用的NDS缓存服务之一,还经常作为DHCP服务来使用,它安装和配置都比较简单,性能也可以满足绝大多数应用程序对DNS缓存的需求
执行下面命令
/etc/init.d/dnsmasq start
* Starting DNS forwarder and DHCP server dnsmasq [ OK ]
修改resolv.conf文件,将DNS服务器改为dnsmasq的监听地址,这里是127.0.0.1
再多执行几次nslookup
echo "nameserver 127.0.0.1" > /etc/resolv.conf
time nslookup time.geekbang.org
Server: 127.0.0.1
Address: 127.0.0.1#53
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.176
real 0m0.012s
user 0m0.007s
sys 0m0.003s
多次解析之后发现只需要10毫秒多一点就可以完成了,而且之后的解析时间也都很稳定
案例最后,可以执行exit 退出容器终端,Docker会自动清理案例容器
优化总结
DNS是互联网中最基础的一项服务,提供了域名和IP地址间映射关系的查询服务,很多应用程序再最初开发时,并没有考虑DNS解析的问题,后续出问题排查很久才发现,是DNS解析慢导致的
常见的DNS优化方法如下
- 对DNS解析结果进行缓存,缓存是最有效的方法,不过缓存一旦过期还是要去DNS服务器重新获取新记录,但对大部分应用来说都可以接受
- 对DNS解析的结果进行预取,这是浏览器等Web应用中最常见的方法,不等用户点击页面上的超链接,浏览器会在后台自动解析域名,并把结果缓存起来
- 使用HTTPNDS代替DNS解析,这是很多移动应用会选择的方式,特别是域名劫持普遍存在,使用HTTP协议绕过链路中的DNS服务器,可以避免域名劫持的问题
- 基于DNS的全局负载均衡(GSLB),不仅为服务提供了负载均衡和高可用的功能,还可以根据用户的位置,返回距离最近的IP地址