dns服务器系统架构,详解 DNS 与 CoreDNS 的实现原理
原文鏈接:https://draveness.me/dns-coredns
【編者的話】域名系統(tǒng)(Domain Name System)是整個(gè)互聯(lián)網(wǎng)的電話簿,它能夠?qū)⒖杀蝗死斫獾挠蛎g成可被機(jī)器理解 IP 地址,使得互聯(lián)網(wǎng)的使用者不再需要直接接觸很難閱讀和理解的 IP 地址。
我們?cè)谶@篇文章中的第一部分會(huì)介紹 DNS 的工作原理以及一些常見的 DNS 問題,而第二部分我們會(huì)介紹 DNS 服務(wù) CoreDNS 的架構(gòu)和實(shí)現(xiàn)原理。
DNS域名系統(tǒng)在現(xiàn)在的互聯(lián)網(wǎng)中非常重要,因?yàn)榉?wù)器的 IP 地址可能會(huì)經(jīng)常變動(dòng),如果沒有了 DNS,那么可能 IP 地址一旦發(fā)生了更改,當(dāng)前服務(wù)器的客戶端就沒有辦法連接到目標(biāo)的服務(wù)器了,如果我們?yōu)?IP 地址提供一個(gè)『別名』并在其發(fā)生變動(dòng)時(shí)修改別名和 IP 地址的關(guān)系,那么我們就可以保證集群對(duì)外提供的服務(wù)能夠相對(duì)穩(wěn)定地被其他客戶端訪問。
DNS 其實(shí)就是一個(gè)分布式的樹狀命名系統(tǒng),它就像一個(gè)去中心化的分布式數(shù)據(jù)庫(kù),存儲(chǔ)著從域名到 IP 地址的映射。
工作原理在我們對(duì) DNS 有了簡(jiǎn)單的了解之后,接下來我們就可以進(jìn)入 DNS 工作原理的部分了,作為用戶訪問互聯(lián)網(wǎng)的第一站,當(dāng)一臺(tái)主機(jī)想要通過域名訪問某個(gè)服務(wù)的內(nèi)容時(shí),需要先通過當(dāng)前域名獲取對(duì)應(yīng)的 IP 地址。這時(shí)就需要通過一個(gè) DNS 解析器負(fù)責(zé)域名的解析,下面的圖片展示了 DNS 查詢的執(zhí)行過程:
本地的 DNS 客戶端向 DNS 解析器發(fā)出解析 draveness.me 域名的請(qǐng)求;
DNS 解析器首先會(huì)向就近的根 DNS 服務(wù)器 . 請(qǐng)求頂級(jí)域名 DNS 服務(wù)的地址;
拿到頂級(jí)域名 DNS 服務(wù) me. 的地址之后會(huì)向頂級(jí)域名服務(wù)請(qǐng)求負(fù)責(zé) dravenss.me. 域名解析的命名服務(wù);
得到授權(quán)的 DNS 命名服務(wù)時(shí),就可以根據(jù)請(qǐng)求的具體的主機(jī)記錄直接向該服務(wù)請(qǐng)求域名對(duì)應(yīng)的 IP 地址;
DNS 客戶端接受到 IP 地址之后,整個(gè) DNS 解析的過程就結(jié)束了,客戶端接下來就會(huì)通過當(dāng)前的 IP 地址直接向服務(wù)器發(fā)送請(qǐng)求。
對(duì)于 DNS 解析器,這里使用的 DNS 查詢方式是迭代查詢,每個(gè) DNS 服務(wù)并不會(huì)直接返回 DNS 信息,而是會(huì)返回另一臺(tái) DNS 服務(wù)器的位置,由客戶端依次詢問不同級(jí)別的 DNS 服務(wù)直到查詢得到了預(yù)期的結(jié)果;另一種查詢方式叫做遞歸查詢,也就是 DNS 服務(wù)器收到客戶端的請(qǐng)求之后會(huì)直接返回準(zhǔn)確的結(jié)果,如果當(dāng)前服務(wù)器沒有存儲(chǔ) DNS 信息,就會(huì)訪問其他的服務(wù)器并將結(jié)果返回給客戶端。
域名層級(jí)域名層級(jí)是一個(gè)層級(jí)的樹形結(jié)構(gòu),樹的最頂層是根域名,一般使用.來表示,這篇文章所在的域名一般寫作 draveness.me,但是這里的寫法其實(shí)省略了最后的.,也就是全稱域名(FQDN)dravenss.me.。
根域名下面的就是 com、net 和 me 等頂級(jí)域名以及次級(jí)域名 draveness.me,我們一般在各個(gè)域名網(wǎng)站中購(gòu)買和使用的都是次級(jí)域名、子域名和主機(jī)名了。
域名服務(wù)器既然域名的命名空間是樹形的,那么用于處理域名解析的 DNS 服務(wù)器也是樹形的,只是在樹的組織和每一層的職責(zé)上有一些不同。DNS 解析器從根域名服務(wù)器查找到頂級(jí)域名服務(wù)器的 IP 地址,又從頂級(jí)域名服務(wù)器查找到權(quán)威域名服務(wù)器的 IP 地址,最終從權(quán)威域名服務(wù)器查出了對(duì)應(yīng)服務(wù)的 IP 地址。
$?dig?-t?A?draveness.me?+trace
我們可以使用 dig 命令追蹤 draveness.me 域名對(duì)應(yīng) IP 地址是如何被解析出來的,首先會(huì)向預(yù)置的 13 組根域名服務(wù)器發(fā)出請(qǐng)求獲取頂級(jí)域名的地址:
.????????????56335???IN??NS??m.root-servers.net.
.???????????56335???IN??NS??b.root-servers.net.
.???????????56335???IN??NS??c.root-servers.net.
.???????????56335???IN??NS??d.root-servers.net.
.???????????56335???IN??NS??e.root-servers.net.
.???????????56335???IN??NS??f.root-servers.net.
.???????????56335???IN??NS??g.root-servers.net.
.???????????56335???IN??NS??h.root-servers.net.
.???????????56335???IN??NS??i.root-servers.net.
.???????????56335???IN??NS??a.root-servers.net.
.???????????56335???IN??NS??j.root-servers.net.
.???????????56335???IN??NS??k.root-servers.net.
.???????????56335???IN??NS??l.root-servers.net.
.???????????56335???IN??RRSIG???NS?8?0?518400?20181111050000?20181029040000?2134?.?G4NbgLqsAyin2zZFetV6YhBVVI29Xi3kwikHSSmrgkX+lq3sRgp3UuQ3?JQxpJ+bZY7mwzo3NxZWy4pqdJDJ55s92l+SKRt/ruBv2BCnk9CcnIzK+?OuGheC9/Coz/r/33rpV63CzssMTIAAMQBGHUyFvRSkiKJWFVOps7u3TM?jcQR0Xp+rJSPxA7f4+tDPYohruYm0nVXGdWhO1CSadXPvmWs1xeeIKvb?9sXJ5hReLw6Vs6ZVomq4tbPrN1zycAbZ2tn/RxGSCHMNIeIROQ99kO5N?QL9XgjIJGmNVDDYi4OF1+ki48UyYkFocEZnaUAor0pD3Dtpis37MASBQ?fr6zqQ==
;;?Received?525?bytes?from?8.8.8.8#53(8.8.8.8)?in?247?ms
根域名服務(wù)器是 DNS 中最高級(jí)別的域名服務(wù)器,這些服務(wù)器負(fù)責(zé)返回頂級(jí)域的權(quán)威域名服務(wù)器地址,這些域名服務(wù)器的數(shù)量總共有 13 組,域名的格式從上面返回的結(jié)果可以看到是 .root-servers.net,每個(gè)根域名服務(wù)器中只存儲(chǔ)了頂級(jí)域服務(wù)器的 IP 地址,大小其實(shí)也只有 2MB 左右,雖然域名服務(wù)器總共只有 13 組,但是每一組服務(wù)器都通過提供了鏡像服務(wù),全球大概也有幾百臺(tái)的根域名服務(wù)器在運(yùn)行。在這里,我們獲取到了以下的 5 條 NS 記錄,也就是 5 臺(tái) me. 定義域名 DNS 服務(wù)器:
me.??????????172800??IN??NS??b0.nic.me.
me.?????????172800??IN??NS??a2.nic.me.
me.?????????172800??IN??NS??b2.nic.me.
me.?????????172800??IN??NS??a0.nic.me.
me.?????????172800??IN??NS??c0.nic.me.
me.?????????86400???IN??DS??2569?7?1?09BA1EB4D20402620881FD9848994417800DB26A
me.?????????86400???IN??DS??2569?7?2?94E798106F033500E67567B197AE9132C0E916764DC743C55A9ECA3C?7BF559E2
me.?????????86400???IN??RRSIG???DS?8?1?86400?20181113050000?20181031040000?2134?.?O81bud61Qh+kJJ26XHzUOtKWRPN0GHoVDacDZ+pIvvD6ef0+HQpyT5nV?rhEZXaFwf0YFo08PUzX8g5Pad8bpFj0O//Q5H2awGbjeoJnlMqbwp6Kl?7O9zzp1YCKmB+ARQgEb7koSCogC9pU7E8Kw/o0NnTKzVFmLq0LLQJGGE?Y43ay3Ew6hzpG69lP8dmBHot3TbF8oFrlUzrm5nojE8W5QVTk1QQfrZM?90WBjfe5nm9b4BHLT48unpK3BaqUFPjqYQV19C3xJ32at4OwUyxZuQsa?GWl0w9R5TiCTS5Ieupu+Q9fLZbW5ZMEgVSt8tNKtjYafBKsFox3cSJRn?irGOmg==
;;?Received?721?bytes?from?192.36.148.17#53(i.root-servers.net)?in?59?ms
當(dāng) DNS 解析器從根域名服務(wù)器中查詢到了頂級(jí)域名 .me 服務(wù)器的地址之后,就可以訪問這些頂級(jí)域名服務(wù)器其中的一臺(tái) b2.nic.me 獲取權(quán)威 DNS 的服務(wù)器的地址了:
draveness.me.????????86400???IN??NS??f1g1ns1.dnspod.net.
draveness.me.???????86400???IN??NS??f1g1ns2.dnspod.net.
fsip6fkr2u8cf2kkg7scot4glihao6s1.me.?8400?IN?NSEC3?1?1?1?D399EAAB?FSJJ1I3A2LHPTHN80MA6Q7J64B15AO5K??NS?SOA?RRSIG?DNSKEY?NSEC3PARAM
fsip6fkr2u8cf2kkg7scot4glihao6s1.me.?8400?IN?RRSIG?NSEC3?7?2?8400?20181121151954?20181031141954?2208?me.?eac6+fEuQ6gK70KExV0EdUKnWeqPrzjqGiplqMDPNRpIRD1vkpX7Zd6C?oN+c8b2yLoI3s3oLEoUd0bUi3dhyCrxF5n6Ap+sKtEv4zZ7o7CEz5Fw+?fpXHj7VeL+pI8KffXcgtYQGlPlCM/ylGUGYOcExrB/qPQ6f/62xrPWjb?+r4=
qcolpi5mj0866sefv2jgp4jnbtfrehej.me.?8400?IN?NSEC3?1?1?1?D399EAAB?QD4QM6388QN4UMH78D429R72J1NR0U07??NS?DS?RRSIG
qcolpi5mj0866sefv2jgp4jnbtfrehej.me.?8400?IN?RRSIG?NSEC3?7?2?8400?20181115151844?20181025141844?2208?me.?rPGaTz/LyNRVN3LQL3LO1udby0vy/MhuIvSjNfrNnLaKARsbQwpq2pA9?+jyt4ah8fvxRkGg9aciG1XSt/EVIgdLSKXqE82hB49ZgYDACX6onscgz?naQGaCAbUTSGG385MuyxCGvqJdE9kEZBbCG8iZhcxSuvBksG4msWuo3k?dTg=
;;?Received?586?bytes?from?199.249.127.1#53(b2.nic.me)?in?267?ms
這里的權(quán)威 DNS 服務(wù)是作者在域名提供商進(jìn)行配置的,當(dāng)有客戶端請(qǐng)求 draveness.me 域名對(duì)應(yīng)的 IP 地址時(shí),其實(shí)會(huì)從作者使用的 DNS 服務(wù)商 DNSPod 處請(qǐng)求服務(wù)的 IP 地址:
draveness.me.????????600?IN??A???123.56.94.228
draveness.me.???????86400???IN??NS??f1g1ns2.dnspod.net.
draveness.me.???????86400???IN??NS??f1g1ns1.dnspod.net.
;;?Received?123?bytes?from?58.247.212.36#53(f1g1ns1.dnspod.net)?in?28?ms
最終,DNS 解析器從 f1g1ns1.dnspod.net 服務(wù)中獲取了當(dāng)前博客的 IP 地址 123.56.94.228,瀏覽器或者其他設(shè)備就能夠通過 IP 向服務(wù)器獲取請(qǐng)求的內(nèi)容了。
從整個(gè)解析過程,我們可以看出 DNS 域名服務(wù)器大體分成三類,根域名服務(wù)、頂級(jí)域名服務(wù)以及權(quán)威域名服務(wù)三種,獲取域名對(duì)應(yīng)的 IP 地址時(shí),也會(huì)像遍歷一棵樹一樣按照從頂層到底層的順序依次請(qǐng)求不同的服務(wù)器。
膠水記錄在通過服務(wù)器解析域名的過程中,我們看到當(dāng)請(qǐng)求 me. 頂級(jí)域名服務(wù)器的時(shí)候,其實(shí)返回了 b0.nic.me 等域名:
me.??????????172800??IN??NS??b0.nic.me.
me.?????????172800??IN??NS??a2.nic.me.
me.?????????172800??IN??NS??b2.nic.me.
me.?????????172800??IN??NS??a0.nic.me.
me.?????????172800??IN??NS??c0.nic.me.
...
就像我們最開始說的,在互聯(lián)網(wǎng)中想要請(qǐng)求服務(wù),最終一定需要獲取 IP 提供服務(wù)的服務(wù)器的 IP 地址;同理,作為 b0.nic.me 作為一個(gè) DNS 服務(wù)器,我也必須獲取它的 IP 地址才能獲得次級(jí)域名的 DNS 信息,但是這里就陷入了一種循環(huán):
如果想要獲取 dravenss.me 的 IP 地址,就需要訪問 me 頂級(jí)域名服務(wù)器 b0.nic.me
如果想要獲取 b0.nic.me 的 IP 地址,就需要訪問 me 頂級(jí)域名服務(wù)器 b0.nic.me
如果想要獲取 b0.nic.me 的 IP 地址,就需要訪問 me 頂級(jí)域名服務(wù)器 b0.nic.me
……
為了解決這一個(gè)問題,我們引入了膠水記錄(Glue Record)這一概念,也就是在出現(xiàn)循環(huán)依賴時(shí),直接在上一級(jí)作用域返回 DNS 服務(wù)器的 IP 地址:
$?dig?+trace?+additional?draveness.me
...
me.?????????172800??IN??NS??a2.nic.me.
me.?????????172800??IN??NS??b2.nic.me.
me.?????????172800??IN??NS??b0.nic.me.
me.?????????172800??IN??NS??a0.nic.me.
me.?????????172800??IN??NS??c0.nic.me.
me.?????????86400???IN??DS??2569?7?1?09BA1EB4D20402620881FD9848994417800DB26A
me.?????????86400???IN??DS??2569?7?2?94E798106F033500E67567B197AE9132C0E916764DC743C55A9ECA3C?7BF559E2
me.?????????86400???IN??RRSIG???DS?8?1?86400?20181116050000?20181103040000?2134?.?cT+rcDNiYD9X02M/NoSBombU2ZqW/7WnEi+b/TOPcO7cDbjb923LltFb?ugMIaoU0Yj6k0Ydg++DrQOy6E5eeshughcH/6rYEbVlFcsIkCdbd9gOk?QkOMH+luvDjCRdZ4L3MrdXZe5PJ5Y45C54V/0XUEdfVKel+NnAdJ1gLE?F+aW8LKnVZpEN/Zu88alOBt9+FPAFfCRV9uQ7UmGwGEMU/WXITheRi5L?h8VtV9w82E6Jh9DenhVFe2g82BYu9MvEbLZr3MKII9pxgyUE3pt50wGY?Mhs40REB0v4pMsEU/KHePsgAfeS/mFSXkiPYPqz2fgke6OHFuwq7MgJk?l7RruQ==
a0.nic.me.??????172800??IN??A???199.253.59.1
a2.nic.me.??????172800??IN??A???199.249.119.1
b0.nic.me.??????172800??IN??A???199.253.60.1
b2.nic.me.??????172800??IN??A???199.249.127.1
c0.nic.me.??????172800??IN??A???199.253.61.1
a0.nic.me.??????172800??IN??AAAA????2001:500:53::1
a2.nic.me.??????172800??IN??AAAA????2001:500:47::1
b0.nic.me.??????172800??IN??AAAA????2001:500:54::1
b2.nic.me.??????172800??IN??AAAA????2001:500:4f::1
c0.nic.me.??????172800??IN??AAAA????2001:500:55::1
;;?Received?721?bytes?from?192.112.36.4#53(g.root-servers.net)?in?110?ms
...
也就是同時(shí)返回 NS 記錄和 A(或 AAAA) 記錄,這樣就能夠解決域名解析出現(xiàn)的循環(huán)依賴問題。
服務(wù)發(fā)現(xiàn)講到現(xiàn)在,我們其實(shí)能夠發(fā)現(xiàn) DNS 就是一種最早的服務(wù)發(fā)現(xiàn)的手段,通過雖然服務(wù)器的 IP 地址可能會(huì)經(jīng)常變動(dòng),但是通過相對(duì)不會(huì)變動(dòng)的域名,我們總是可以找到提供對(duì)應(yīng)服務(wù)的服務(wù)器。
在微服務(wù)架構(gòu)中,服務(wù)注冊(cè)的方式其實(shí)大體上也只有兩種,一種是使用 ZooKeeper 和 etcd 等配置管理中心,另一種是使用 DNS 服務(wù),比如說 Kubernetes 中的 CoreDNS 服務(wù)。
使用 DNS 在集群中做服務(wù)發(fā)現(xiàn)其實(shí)是一件比較容易的事情,這主要是因?yàn)榻^大多數(shù)的計(jì)算機(jī)上都會(huì)安裝 DNS 服務(wù),所以這其實(shí)就是一種內(nèi)置的、默認(rèn)的服務(wù)發(fā)現(xiàn)方式,不過使用 DNS 做服務(wù)發(fā)現(xiàn)也會(huì)有一些問題,因?yàn)樵谀J(rèn)情況下 DNS 記錄的失效時(shí)間是 600s,這對(duì)于集群來講其實(shí)并不是一個(gè)可以接受的時(shí)間,在實(shí)踐中我們往往會(huì)啟動(dòng)單獨(dú)的 DNS 服務(wù)滿足服務(wù)發(fā)現(xiàn)的需求。
CoreDNSCoreDNS 其實(shí)就是一個(gè) DNS 服務(wù),而 DNS 作為一種常見的服務(wù)發(fā)現(xiàn)手段,所以很多開源項(xiàng)目以及工程師都會(huì)使用 CoreDNS 為集群提供服務(wù)發(fā)現(xiàn)的功能,Kubernetes 就在集群中使用 CoreDNS 解決服務(wù)發(fā)現(xiàn)的問題。
作為一個(gè)加入 CNCF(Cloud Native Computing Foundation)的服務(wù) CoreDNS 的實(shí)現(xiàn)可以說的非常的簡(jiǎn)單。
架構(gòu)整個(gè) CoreDNS 服務(wù)都建立在一個(gè)使用 Go 編寫的 HTTP/2 Web 服務(wù)器 Caddy · GitHub 上,CoreDNS 整個(gè)項(xiàng)目可以作為一個(gè) Caddy 的教科書用法。
CoreDNS 的大多數(shù)功能都是由插件來實(shí)現(xiàn)的,插件和服務(wù)本身都使用了 Caddy 提供的一些功能,所以項(xiàng)目本身也不是特別的復(fù)雜。
插件作為基于 Caddy 的 Web 服務(wù)器,CoreDNS 實(shí)現(xiàn)了一個(gè)插件鏈的架構(gòu),將很多 DNS 相關(guān)的邏輯都抽象層了一層一層的插件,包括 Kubernetes 等功能,每一個(gè)插件都是一個(gè)遵循如下協(xié)議的結(jié)構(gòu)體:
type?(
Plugin?func(Handler)?Handler
Handler?interface?{
ServeDNS(context.Context,?dns.ResponseWriter,?*dns.Msg)?(int,?error)
Name()?string
}
)
所以只需要為插件實(shí)現(xiàn) ServeDNS 以及 Name 這兩個(gè)接口并且寫一些用于配置的代碼就可以將插件集成到 CoreDNS 中。
Corefile另一個(gè) CoreDNS 的特點(diǎn)就是它能夠通過簡(jiǎn)單易懂的 DSL 定義 DNS 服務(wù),在 Corefile 中就可以組合多個(gè)插件對(duì)外提供服務(wù):
coredns.io:5300?{
file?db.coredns.io
}
example.io:53?{
log
errors
file?db.example.io
}
example.net:53?{
file?db.example.net
}
.:53?{
kubernetes
proxy?.?8.8.8.8
log
errors
cache
}
對(duì)于以上的配置文件,CoreDNS 會(huì)根據(jù)每一個(gè)代碼塊前面的區(qū)和端點(diǎn)對(duì)外暴露兩個(gè)端點(diǎn)提供服務(wù):
該配置文件對(duì)外暴露了兩個(gè) DNS 服務(wù),其中一個(gè)監(jiān)聽在 5300 端口,另一個(gè)在 53 端口,請(qǐng)求這兩個(gè)服務(wù)時(shí)會(huì)根據(jù)不同的域名選擇不同區(qū)中的插件進(jìn)行處理。
原理CoreDNS 可以通過四種方式對(duì)外直接提供 DNS 服務(wù),分別是 UDP、gRPC、HTTPS 和 TLS:
但是無論哪種類型的 DNS 服務(wù),最終隊(duì)會(huì)調(diào)用以下的 ServeDNS 方法,為服務(wù)的調(diào)用者提供 DNS 服務(wù):
func?(s?*Server)?ServeDNS(ctx?context.Context,?w?dns.ResponseWriter,?r?*dns.Msg)?{
m,?_?:=?edns.Version(r)
ctx,?_?:=?incrementDepthAndCheck(ctx)
b?:=?r.Question[0].Name
var?off?int
var?end?bool
var?dshandler?*Config
w?=?request.NewScrubWriter(r,?w)
for?{
if?h,?ok?:=?s.zones[string(b[:l])];?ok?{
ctx?=?context.WithValue(ctx,?plugin.ServerCtx{},?s.Addr)
if?r.Question[0].Qtype?!=?dns.TypeDS?{
rcode,?_?:=?h.pluginChain.ServeDNS(ctx,?w,?r)
dshandler?=?h
}
off,?end?=?dns.NextLabel(q,?off)
if?end?{
break
}
}
if?r.Question[0].Qtype?==?dns.TypeDS?&&?dshandler?!=?nil?&&?dshandler.pluginChain?!=?nil?{
rcode,?_?:=?dshandler.pluginChain.ServeDNS(ctx,?w,?r)
plugin.ClientWrite(rcode)
return
}
if?h,?ok?:=?s.zones["."];?ok?&&?h.pluginChain?!=?nil?{
ctx?=?context.WithValue(ctx,?plugin.ServerCtx{},?s.Addr)
rcode,?_?:=?h.pluginChain.ServeDNS(ctx,?w,?r)
plugin.ClientWrite(rcode)
return
}
}
在上述這個(gè)已經(jīng)被簡(jiǎn)化的復(fù)雜函數(shù)中,最重要的就是調(diào)用了『插件鏈』的 ServeDNS 方法,將來源的請(qǐng)求交給一系列插件進(jìn)行處理,如果我們使用以下的文件作為 Corefile:
example.org?{
file?/usr/local/etc/coredns/example.org
prometheus?????#?enable?metrics
errors?????????#?show?errors
log????????????#?enable?query?logs
}
那么在 CoreDNS 服務(wù)啟動(dòng)時(shí),對(duì)于當(dāng)前的 example.org 這個(gè)組,它會(huì)依次加載 file、log、errors 和 prometheus 幾個(gè)插件,這里的順序是由 zdirectives.go 文件定義的,啟動(dòng)的順序是從下到上:
var?Directives?=?[]string{
//?...
"prometheus",
"errors",
"log",
//?...
"file",
//?...
"whoami",
"on",
}
因?yàn)閱?dòng)的時(shí)候會(huì)按照從下到上的順序依次『包裝』每一個(gè)插件,所以在真正調(diào)用時(shí)就是從上到下執(zhí)行的,這就是因?yàn)?NewServer 方法中對(duì)插件進(jìn)行了組合:
func?NewServer(addr?string,?group?[]*Config)?(*Server,?error)?{
s?:=?&Server{
Addr:????????addr,
zones:???????make(map[string]*Config),
connTimeout:?5?*?time.Second,
}
for?_,?site?:=?range?group?{
s.zones[site.Zone]?=?site
if?site.registry?!=?nil?{
for?name?:=?range?enableChaos?{
if?_,?ok?:=?site.registry[name];?ok?{
s.classChaos?=?true
break
}
}
}
var?stack?plugin.Handler
for?i?:=?len(site.Plugin)?-?1;?i?>=?0;?i--?{
stack?=?site.Plugin[i](stack)
site.registerHandler(stack)
}
site.pluginChain?=?stack
}
return?s,?nil
}
對(duì)于 Corefile 里面的每一個(gè)配置組,NewServer 都會(huì)講配置組中提及的插件按照一定的順序組合起來,原理跟 Rack Middleware 的機(jī)制非常相似,插件 Plugin 其實(shí)就是一個(gè)出入?yún)?shù)都是 Handler 的函數(shù):
type?(
Plugin?func(Handler)?Handler
Handler?interface?{
ServeDNS(context.Context,?dns.ResponseWriter,?*dns.Msg)?(int,?error)
Name()?string
}
)
所以我們可以將它們疊成堆棧的方式對(duì)它們進(jìn)行操作,這樣在最后就會(huì)形成一個(gè)插件的調(diào)用鏈,在每個(gè)插件執(zhí)行方法時(shí)都可以通過 NextOrFailure 函數(shù)調(diào)用下一個(gè)插件的 ServerDNS 方法:
func?NextOrFailure(name?string,?next?Handler,?ctx?context.Context,?w?dns.ResponseWriter,?r?*dns.Msg)?(int,?error)?{
if?next?!=?nil?{
if?span?:=?ot.SpanFromContext(ctx);?span?!=?nil?{
child?:=?span.Tracer().StartSpan(next.Name(),?ot.ChildOf(span.Context()))
defer?child.Finish()
ctx?=?ot.ContextWithSpan(ctx,?child)
}
return?next.ServeDNS(ctx,?w,?r)
}
return?dns.RcodeServerFailure,?Error(name,?errors.New("no?next?plugin?found"))
}
除了通過 ServeDNS 調(diào)用下一個(gè)插件之外,我們也可以調(diào)用 WriteMsg 方法并結(jié)束整個(gè)調(diào)用鏈。
從插件的堆疊到順序調(diào)用以及錯(cuò)誤處理,我們對(duì) CoreDNS 的工作原理已經(jīng)非常清楚了,接下來我們可以簡(jiǎn)單介紹幾個(gè)插件的作用。
LoadBalanceLoadBalance 這個(gè)插件的名字就告訴我們,使用這個(gè)插件能夠提供基于 DNS 的負(fù)載均衡功能,在 setup 中初始化時(shí)傳入了 RoundRobin 結(jié)構(gòu)體:
func?setup(c?*caddy.Controller)?error?{
err?:=?parse(c)
if?err?!=?nil?{
return?plugin.Error("loadbalance",?err)
}
dnsserver.GetConfig(c).AddPlugin(func(next?plugin.Handler)?plugin.Handler?{
return?RoundRobin{Next:?next}
})
return?nil
}
當(dāng)用戶請(qǐng)求 CoreDNS 服務(wù)時(shí),我們會(huì)根據(jù)插件鏈調(diào)用 LoadBalance 這個(gè)包中的 ServeDNS 方法,在方法中會(huì)改變用于返回響應(yīng)的 Writer:
func?(rr?RoundRobin)?ServeDNS(ctx?context.Context,?w?dns.ResponseWriter,?r?*dns.Msg)?(int,?error)?{
wrr?:=?&RoundRobinResponseWriter{w}
return?plugin.NextOrFailure(rr.Name(),?rr.Next,?ctx,?wrr,?r)
}
所以在最終服務(wù)返回響應(yīng)時(shí),會(huì)通過 RoundRobinResponseWriter 的 WriteMsg 方法寫入 DNS 消息:
func?(r?*RoundRobinResponseWriter)?WriteMsg(res?*dns.Msg)?error?{
if?res.Rcode?!=?dns.RcodeSuccess?{
return?r.ResponseWriter.WriteMsg(res)
}
res.Answer?=?roundRobin(res.Answer)
res.Ns?=?roundRobin(res.Ns)
res.Extra?=?roundRobin(res.Extra)
return?r.ResponseWriter.WriteMsg(res)
}
上述方法會(huì)將響應(yīng)中的 Answer、Ns 以及 Extra 幾個(gè)字段中數(shù)組的順序打亂:
func?roundRobin(in?[]dns.RR)?[]dns.RR?{
cname?:=?[]dns.RR{}
address?:=?[]dns.RR{}
mx?:=?[]dns.RR{}
rest?:=?[]dns.RR{}
for?_,?r?:=?range?in?{
switch?r.Header().Rrtype?{
case?dns.TypeCNAME:
cname?=?append(cname,?r)
case?dns.TypeA,?dns.TypeAAAA:
address?=?append(address,?r)
case?dns.TypeMX:
mx?=?append(mx,?r)
default:
rest?=?append(rest,?r)
}
}
roundRobinShuffle(address)
roundRobinShuffle(mx)
out?:=?append(cname,?rest...)
out?=?append(out,?address...)
out?=?append(out,?mx...)
return?out
}
打亂后的 DNS 記錄會(huì)被原始的 ResponseWriter 結(jié)構(gòu)寫回到 DNS 響應(yīng)中。
LoopLoop 插件會(huì)檢測(cè) DNS 解析過程中出現(xiàn)的簡(jiǎn)單循環(huán)依賴,如果我們?cè)?Corefile 中添加如下的內(nèi)容并啟動(dòng) CoreDNS 服務(wù),CoreDNS 會(huì)向自己發(fā)送一個(gè) DNS 查詢,看最終是否會(huì)陷入循環(huán):
.?{
loop
forward?.?127.0.0.1
}
在 CoreDNS 啟動(dòng)時(shí),它會(huì)在 setup 方法中調(diào)用 Loop.exchange 方法向自己查詢一個(gè)隨機(jī)域名的 DNS 記錄:
func?(l?*Loop)?exchange(addr?string)?(*dns.Msg,?error)?{
m?:=?new(dns.Msg)
m.SetQuestion(l.qname,?dns.TypeHINFO)
return?dns.Exchange(m,?addr)
}
如果這個(gè)隨機(jī)域名在 ServeDNS 方法中被查詢了兩次,那么就說明當(dāng)前的 DNS 請(qǐng)求陷入了循環(huán)需要終止:
func?(l?*Loop)?ServeDNS(ctx?context.Context,?w?dns.ResponseWriter,?r?*dns.Msg)?(int,?error)?{
if?r.Question[0].Qtype?!=?dns.TypeHINFO?{
return?plugin.NextOrFailure(l.Name(),?l.Next,?ctx,?w,?r)
}
//?...
if?state.Name()?==?l.qname?{
l.inc()
}
if?l.seen()?>?2?{
log.Fatalf("Forwarding?loop?detected?in?\"%s\"?zone.?Exiting.?See?https://coredns.io/plugins/loop#troubleshooting.?Probe?query:?\"HINFO?%s\".",?l.zone,?l.qname)
}
return?plugin.NextOrFailure(l.Name(),?l.Next,?ctx,?w,?r)
}
就像 Loop 插件的 README 中寫的,這個(gè)插件只能夠檢測(cè)一些簡(jiǎn)單的由于配置造成的循環(huán)問題,復(fù)雜的循環(huán)問題并不能通過當(dāng)前的插件解決。
總結(jié)如果想要在分布式系統(tǒng)實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)的功能,DNS 以及 CoreDNS 其實(shí)是一個(gè)非常好的選擇,CoreDNS 作為一個(gè)已經(jīng)進(jìn)入 CNCF 并且在 Kubernetes 中作為 DNS 服務(wù)使用的應(yīng)用,其本身的穩(wěn)定性和可用性已經(jīng)得到了證明,同時(shí)它基于插件實(shí)現(xiàn)的方式非常輕量并且易于使用,插件鏈的使用也使得第三方插件的定義變得非常的方便。
總結(jié)
以上是生活随笔為你收集整理的dns服务器系统架构,详解 DNS 与 CoreDNS 的实现原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android继承父类的界面,Andro
- 下一篇: java 对象视图框架_Stripes视