詳解 DNS 與 CoreDNS 的實現(xiàn)原理
域名系統(tǒng)(Domain Name System)是整個互聯(lián)網(wǎng)的電話簿,它能夠?qū)⒖杀蝗死斫獾挠蛎g成可被機器理解 IP 地址,使得互聯(lián)網(wǎng)的使用者不再需要直接接觸很難閱讀和理解的 IP 地址。
我們在這篇文章中的第一部分會介紹 DNS 的工作原理以及一些常見的 DNS 問題,而第二部分我們會介紹 DNS 服務(wù)?CoreDNS?的架構(gòu)和實現(xiàn)原理。
DNS
域名系統(tǒng)在現(xiàn)在的互聯(lián)網(wǎng)中非常重要,因為服務(wù)器的 IP 地址可能會經(jīng)常變動,如果沒有了 DNS,那么可能 IP 地址一旦發(fā)生了更改,當前服務(wù)器的客戶端就沒有辦法連接到目標的服務(wù)器了,如果我們?yōu)?IP 地址提供一個『別名』并在其發(fā)生變動時修改別名和 IP 地址的關(guān)系,那么我們就可以保證集群對外提供的服務(wù)能夠相對穩(wěn)定地被其他客戶端訪問。

DNS 其實就是一個分布式的樹狀命名系統(tǒng),它就像一個去中心化的分布式數(shù)據(jù)庫,存儲著從域名到 IP 地址的映射。
工作原理
在我們對 DNS 有了簡單的了解之后,接下來我們就可以進入 DNS 工作原理的部分了,作為用戶訪問互聯(lián)網(wǎng)的第一站,當一臺主機想要通過域名訪問某個服務(wù)的內(nèi)容時,需要先通過當前域名獲取對應(yīng)的 IP 地址。這時就需要通過一個 DNS 解析器負責域名的解析,下面的圖片展示了 DNS 查詢的執(zhí)行過程:

本地的 DNS 客戶端向 DNS 解析器發(fā)出解析 draveness.me 域名的請求;
DNS 解析器首先會向就近的根 DNS 服務(wù)器?
.?請求頂級域名 DNS 服務(wù)的地址;拿到頂級域名 DNS 服務(wù)?
me.?的地址之后會向頂級域名服務(wù)請求負責?dravenss.me.域名解析的命名服務(wù);得到授權(quán)的 DNS 命名服務(wù)時,就可以根據(jù)請求的具體的主機記錄直接向該服務(wù)請求域名對應(yīng)的 IP 地址;
DNS 客戶端接受到 IP 地址之后,整個 DNS 解析的過程就結(jié)束了,客戶端接下來就會通過當前的 IP 地址直接向服務(wù)器發(fā)送請求。
對于 DNS 解析器,這里使用的 DNS 查詢方式是迭代查詢,每個 DNS 服務(wù)并不會直接返回 DNS 信息,而是會返回另一臺 DNS 服務(wù)器的位置,由客戶端依次詢問不同級別的 DNS 服務(wù)直到查詢得到了預(yù)期的結(jié)果;另一種查詢方式叫做遞歸查詢,也就是 DNS 服務(wù)器收到客戶端的請求之后會直接返回準確的結(jié)果,如果當前服務(wù)器沒有存儲 DNS 信息,就會訪問其他的服務(wù)器并將結(jié)果返回給客戶端。
域名層級
域名層級是一個層級的樹形結(jié)構(gòu),樹的最頂層是根域名,一般使用?.?來表示,這篇文章所在的域名一般寫作?draveness.me,但是這里的寫法其實省略了最后的?.,也就是全稱域名(FQDN)dravenss.me.。

根域名下面的就是?com、net?和?me?等頂級域名以及次級域名?draveness.me,我們一般在各個域名網(wǎng)站中購買和使用的都是次級域名、子域名和主機名了。
域名服務(wù)器
既然域名的命名空間是樹形的,那么用于處理域名解析的 DNS 服務(wù)器也是樹形的,只是在樹的組織和每一層的職責上有一些不同。DNS 解析器從根域名服務(wù)器查找到頂級域名服務(wù)器的 IP 地址,又從頂級域名服務(wù)器查找到權(quán)威域名服務(wù)器的 IP 地址,最終從權(quán)威域名服務(wù)器查出了對應(yīng)服務(wù)的 IP 地址。
$?dig?-t?A?draveness.me?+trace
我們可以使用 dig 命令追蹤?draveness.me?域名對應(yīng) IP 地址是如何被解析出來的,首先會向預(yù)置的 13 組根域名服務(wù)器發(fā)出請求獲取頂級域名的地址:
.????????????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 中最高級別的域名服務(wù)器,這些服務(wù)器負責返回頂級域的權(quán)威域名服務(wù)器地址,這些域名服務(wù)器的數(shù)量總共有 13 組,域名的格式從上面返回的結(jié)果可以看到是?
.root-servers.net,每個根域名服務(wù)器中只存儲了頂級域服務(wù)器的 IP 地址,大小其實也只有 2MB 左右,雖然域名服務(wù)器總共只有 13 組,但是每一組服務(wù)器都通過提供了鏡像服務(wù),全球大概也有幾百臺的根域名服務(wù)器在運行。
在這里,我們獲取到了以下的 5 條 NS 記錄,也就是 5 臺?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
當 DNS 解析器從根域名服務(wù)器中查詢到了頂級域名?.me?服務(wù)器的地址之后,就可以訪問這些頂級域名服務(wù)器其中的一臺?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ù)是作者在域名提供商進行配置的,當有客戶端請求?draveness.me?域名對應(yīng)的 IP 地址時,其實會從作者使用的 DNS 服務(wù)商 DNSPod 處請求服務(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ù)中獲取了當前博客的 IP 地址?123.56.94.228,瀏覽器或者其他設(shè)備就能夠通過 IP 向服務(wù)器獲取請求的內(nèi)容了。
從整個解析過程,我們可以看出 DNS 域名服務(wù)器大體分成三類,根域名服務(wù)、頂級域名服務(wù)以及權(quán)威域名服務(wù)三種,獲取域名對應(yīng)的 IP 地址時,也會像遍歷一棵樹一樣按照從頂層到底層的順序依次請求不同的服務(wù)器。
膠水記錄
在通過服務(wù)器解析域名的過程中,我們看到當請求?me.?頂級域名服務(wù)器的時候,其實返回了?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)中想要請求服務(wù),最終一定需要獲取 IP 提供服務(wù)的服務(wù)器的 IP 地址;同理,作為?b0.nic.me?作為一個 DNS 服務(wù)器,我也必須獲取它的 IP 地址才能獲得次級域名的 DNS 信息,但是這里就陷入了一種循環(huán):
如果想要獲取?
dravenss.me?的 IP 地址,就需要訪問?me?頂級域名服務(wù)器?b0.nic.me如果想要獲取?
b0.nic.me?的 IP 地址,就需要訪問?me?頂級域名服務(wù)器?b0.nic.me如果想要獲取?
b0.nic.me?的 IP 地址,就需要訪問?me?頂級域名服務(wù)器?b0.nic.me…
為了解決這一個問題,我們引入了膠水記錄(Glue Record)這一概念,也就是在出現(xiàn)循環(huán)依賴時,直接在上一級作用域返回 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
...
也就是同時返回 NS 記錄和 A(或 AAAA) 記錄,這樣就能夠解決域名解析出現(xiàn)的循環(huán)依賴問題。
服務(wù)發(fā)現(xiàn)
講到現(xiàn)在,我們其實能夠發(fā)現(xiàn) DNS 就是一種最早的服務(wù)發(fā)現(xiàn)的手段,通過雖然服務(wù)器的 IP 地址可能會經(jīng)常變動,但是通過相對不會變動的域名,我們總是可以找到提供對應(yīng)服務(wù)的服務(wù)器。
在微服務(wù)架構(gòu)中,服務(wù)注冊的方式其實大體上也只有兩種,一種是使用 Zookeeper 和 etcd 等配置管理中心,另一種是使用 DNS 服務(wù),比如說 Kubernetes 中的 CoreDNS 服務(wù)。
使用 DNS 在集群中做服務(wù)發(fā)現(xiàn)其實是一件比較容易的事情,這主要是因為絕大多數(shù)的計算機上都會安裝 DNS 服務(wù),所以這其實就是一種內(nèi)置的、默認的服務(wù)發(fā)現(xiàn)方式,不過使用 DNS 做服務(wù)發(fā)現(xiàn)也會有一些問題,因為在默認情況下 DNS 記錄的失效時間是 600s,這對于集群來講其實并不是一個可以接受的時間,在實踐中我們往往會啟動單獨的 DNS 服務(wù)滿足服務(wù)發(fā)現(xiàn)的需求。
CoreDNS
CoreDNS 其實就是一個 DNS 服務(wù),而 DNS 作為一種常見的服務(wù)發(fā)現(xiàn)手段,所以很多開源項目以及工程師都會使用 CoreDNS 為集群提供服務(wù)發(fā)現(xiàn)的功能,Kubernetes 就在集群中使用 CoreDNS 解決服務(wù)發(fā)現(xiàn)的問題。

作為一個加入 CNCF(Cloud Native Computing Foundation) 的服務(wù) CoreDNS 的實現(xiàn)可以說的非常的簡單。
架構(gòu)
整個 CoreDNS 服務(wù)都建立在一個使用 Go 編寫的 HTTP/2 Web 服務(wù)器?Caddy · GitHub?上,CoreDNS 整個項目可以作為一個 Caddy 的教科書用法。

CoreDNS 的大多數(shù)功能都是由插件來實現(xiàn)的,插件和服務(wù)本身都使用了 Caddy 提供的一些功能,所以項目本身也不是特別的復(fù)雜。
插件
作為基于 Caddy 的 Web 服務(wù)器,CoreDNS 實現(xiàn)了一個插件鏈的架構(gòu),將很多 DNS 相關(guān)的邏輯都抽象層了一層一層的插件,包括 Kubernetes 等功能,每一個插件都是一個遵循如下協(xié)議的結(jié)構(gòu)體:
type?(
????Plugin?func(Handler)?Handler
????Handler?interface?{
????????ServeDNS(context.Context,?dns.ResponseWriter,?*dns.Msg)?(int,?error)
????????Name()?string
????}
)
所以只需要為插件實現(xiàn)?ServeDNS?以及?Name?這兩個接口并且寫一些用于配置的代碼就可以將插件集成到 CoreDNS 中。
Corefile
另一個 CoreDNS 的特點就是它能夠通過簡單易懂的 DSL 定義 DNS 服務(wù),在 Corefile 中就可以組合多個插件對外提供服務(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
}
對于以上的配置文件,CoreDNS 會根據(jù)每一個代碼塊前面的區(qū)和端點對外暴露兩個端點提供服務(wù):

該配置文件對外暴露了兩個 DNS 服務(wù),其中一個監(jiān)聽在 5300 端口,另一個在 53 端口,請求這兩個服務(wù)時會根據(jù)不同的域名選擇不同區(qū)中的插件進行處理。
原理
CoreDNS 可以通過四種方式對外直接提供 DNS 服務(wù),分別是 UDP、gRPC、HTTPS 和 TLS:

但是無論哪種類型的 DNS 服務(wù),最終隊會調(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
????}
}
在上述這個已經(jīng)被簡化的復(fù)雜函數(shù)中,最重要的就是調(diào)用了『插件鏈』的?ServeDNS?方法,將來源的請求交給一系列插件進行處理,如果我們使用以下的文件作為 Corefile:
example.org?{
????file?/usr/local/etc/coredns/example.org
????prometheus?????#?enable?metrics
????errors?????????#?show?errors
????log????????????#?enable?query?logs
}
那么在 CoreDNS 服務(wù)啟動時,對于當前的?example.org?這個組,它會依次加載?file、log、errors?和?prometheus?幾個插件,這里的順序是由 zdirectives.go 文件定義的,啟動的順序是從下到上:
var?Directives?=?[]string{
??//?...
????"prometheus",
????"errors",
????"log",
??//?...
????"file",
??//?...
????"whoami",
????"on",
}
因為啟動的時候會按照從下到上的順序依次『包裝』每一個插件,所以在真正調(diào)用時就是從上到下執(zhí)行的,這就是因為?NewServer?方法中對插件進行了組合:
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
}
對于 Corefile 里面的每一個配置組,NewServer?都會講配置組中提及的插件按照一定的順序組合起來,原理跟 Rack Middleware 的機制非常相似,插件?Plugin?其實就是一個出入?yún)?shù)都是?Handler?的函數(shù):
type?(
????Plugin?func(Handler)?Handler
????Handler?interface?{
????????ServeDNS(context.Context,?dns.ResponseWriter,?*dns.Msg)?(int,?error)
????????Name()?string
????}
)
所以我們可以將它們疊成堆棧的方式對它們進行操作,這樣在最后就會形成一個插件的調(diào)用鏈,在每個插件執(zhí)行方法時都可以通過?NextOrFailure?函數(shù)調(diào)用下一個插件的?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)用下一個插件之外,我們也可以調(diào)用?WriteMsg?方法并結(jié)束整個調(diào)用鏈。

從插件的堆疊到順序調(diào)用以及錯誤處理,我們對 CoreDNS 的工作原理已經(jīng)非常清楚了,接下來我們可以簡單介紹幾個插件的作用。
loadbalance
loadbalance 這個插件的名字就告訴我們,使用這個插件能夠提供基于 DNS 的負載均衡功能,在?setup?中初始化時傳入了?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
}
當用戶請求 CoreDNS 服務(wù)時,我們會根據(jù)插件鏈調(diào)用 loadbalance 這個包中的?ServeDNS?方法,在方法中會改變用于返回響應(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)時,會通過?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)
}
上述方法會將響應(yīng)中的?Answer、Ns?以及?Extra?幾個字段中數(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 記錄會被原始的?ResponseWriter?結(jié)構(gòu)寫回到 DNS 響應(yīng)中。
loop
loop 插件會檢測 DNS 解析過程中出現(xiàn)的簡單循環(huán)依賴,如果我們在 Corefile 中添加如下的內(nèi)容并啟動 CoreDNS 服務(wù),CoreDNS 會向自己發(fā)送一個 DNS 查詢,看最終是否會陷入循環(huán):
.?{
????loop
????forward?.?127.0.0.1
}
在 CoreDNS 啟動時,它會在?setup?方法中調(diào)用?Loop.exchange?方法向自己查詢一個隨機域名的 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)
}
如果這個隨機域名在?ServeDNS?方法中被查詢了兩次,那么就說明當前的 DNS 請求陷入了循環(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 中寫的,這個插件只能夠檢測一些簡單的由于配置造成的循環(huán)問題,復(fù)雜的循環(huán)問題并不能通過當前的插件解決。
總結(jié)
如果想要在分布式系統(tǒng)實現(xiàn)服務(wù)發(fā)現(xiàn)的功能,DNS 以及 CoreDNS 其實是一個非常好的選擇,CoreDNS 作為一個已經(jīng)進入 CNCF 并且在 Kubernetes 中作為 DNS 服務(wù)使用的應(yīng)用,其本身的穩(wěn)定性和可用性已經(jīng)得到了證明,同時它基于插件實現(xiàn)的方式非常輕量并且易于使用,插件鏈的使用也使得第三方插件的定義變得非常的方便。
References
What is DNS? | How DNS works
移動互聯(lián)網(wǎng)時代,如何優(yōu)化你的網(wǎng)絡(luò) —— 域名解析篇
How Queries Are Processed in CoreDNS
Domain Name System
DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION · RFC1035
A fun and colorful explanation of how DNS works.
Root Servers
What is the DNS Protocol?
Root name server · Wikipedia
CoreDNS for Kubernetes Service Discovery, Take 2
Kubernetes DNS-Based Service Discovery
CoreDNS Manual
K8S進階訓練營,點擊下方圖片了解詳情

