《京東618如何支持容器百萬(wàn)級(jí)域名解析服務(wù)?》要點(diǎn):
本文介紹了京東618如何支持容器百萬(wàn)級(jí)域名解析服務(wù)?,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
作者:鮑永成、陳書(shū)剛
編輯:木環(huán)
InfoQ 報(bào)道京東大促技術(shù)已有三年的時(shí)間,這三年的陪伴見(jiàn)證了京東技術(shù)的成長(zhǎng).2015 年京東開(kāi)啟容器技術(shù),擁有數(shù)千實(shí)例;2016 年,該數(shù)字上升至 15 萬(wàn);2017 年,容器實(shí)例規(guī)模繼續(xù)穩(wěn)步上升并開(kāi)源數(shù)個(gè)項(xiàng)目.
隨著京東業(yè)務(wù)的高速增長(zhǎng),以及 JDOS2.0 的線上大規(guī)模運(yùn)營(yíng),進(jìn)而容器集群的編排成為常態(tài),Pod 失效也成為常態(tài),RS(Replication Set)在處理失效 Pod 時(shí)候會(huì)帶來(lái) IP 的變化.這樣容器之間基于 IP 相互訪問(wèn)就有可能存在問(wèn)題.所以一個(gè)強(qiáng)大的能支持百萬(wàn)級(jí) hostname 域名解析服務(wù),可以很好地解決這個(gè)問(wèn)題.
本文介紹的 DNS 命名為 ContainerDNS,作為京東商城軟件定義數(shù)據(jù)中心的關(guān)鍵基礎(chǔ)服務(wù)之一,具有以下特點(diǎn):
圖一 ContainerDNS 架構(gòu)圖
ContainerDNS 包括四大組件 DNS Server、Service to DNS 、User API 、IP status check.這四個(gè)組件通過(guò) etcd 集群結(jié)合在一起,彼此獨(dú)立,完全解耦,每個(gè)模塊可以單獨(dú)部署和橫向擴(kuò)展.? ? ?DNS Server 用于提供 DNS 查詢服務(wù)的主體,目前支持了大部分常用的查詢類型(A、AAAA、SRV、NS、TXT、MX、CNAME 等).? ? ?Service to DNS 組件是 JDOS 集群與 DNS Server 的中間環(huán)節(jié),會(huì)實(shí)時(shí)監(jiān)控 JDOS 集群的服務(wù)的創(chuàng)建,將服務(wù)轉(zhuǎn)化為域名信息,存入 etcd 數(shù)據(jù)庫(kù)中.? ? ?User API 組件提供 restful API,用戶可以創(chuàng)建自己的域名信息,數(shù)據(jù)同樣保持到 etcd 數(shù)據(jù)庫(kù)中.? ? ?IP status check 模塊用于對(duì)系統(tǒng)中域名所對(duì)應(yīng)的 IP 做探活處理,數(shù)據(jù)狀態(tài)也會(huì)存入到 etcd 數(shù)據(jù)庫(kù)中.如果某一個(gè)域名對(duì)應(yīng)的某一個(gè) IP 地址不能對(duì)外提供服務(wù),DNS Server 會(huì)在查詢這個(gè)域名的時(shí)候,將這個(gè)不能提供服務(wù)的 IP 地址自動(dòng)過(guò)濾掉.
DNS Server 是提供 DNS 的主體模塊,系統(tǒng)中是掛載在項(xiàng)目 ContainerLB(一種基于 DPDK 平臺(tái)實(shí)現(xiàn)的快速可靠的軟件網(wǎng)絡(luò)負(fù)載均衡系統(tǒng))之后,通過(guò) VIP 對(duì)外提供服務(wù).結(jié)構(gòu)如下:
圖二 DNS Server 與 ContainerLB
如上圖所示,DNS Server 通過(guò) VIP 對(duì)外提供服務(wù),通過(guò)這層 LB 可以對(duì) DNS Server 做負(fù)載均衡,DNS Server 的高可用、動(dòng)態(tài)擴(kuò)展都變得很容易.同時(shí) DNS Server 的數(shù)據(jù)源依賴于 etcd 數(shù)據(jù)庫(kù),所以對(duì) DNS Server 的擴(kuò)展部署十分簡(jiǎn)單.由于 etcd 是一種強(qiáng)一致性的數(shù)據(jù)庫(kù),這也有效保障掛在 LB 后面的 DNS Server 對(duì)外提供的數(shù)據(jù)一致性.
DNS Server 作為 JDOS 集群的 DNS 服務(wù),所以需要把服務(wù)器的地址傳給容器.我們知道 JDOS 的 POD 都是由 JDOS Node 節(jié)點(diǎn)創(chuàng)建的,而 POD 指定 DNS 服務(wù)的地址和域名后綴.最終體現(xiàn)為 Docker 容器的 /etc/resolv.conf 中.
DNS Server 的啟動(dòng)過(guò)程
DNS Server 首先根據(jù)用戶的配置,鏈接 etcd 數(shù)據(jù)庫(kù),并讀取對(duì)應(yīng)的域名信息放在程序的緩存中.然后啟動(dòng) watch 監(jiān)聽(tīng) etcd 的變化,同步數(shù)據(jù)庫(kù)與緩存中的數(shù)據(jù).新的 DNS 請(qǐng)求不用在查詢 etcd 數(shù)據(jù)庫(kù)直接使用緩存中的數(shù)據(jù),從而提高響應(yīng)的速度.啟動(dòng)后監(jiān)聽(tīng)用戶配置的端口(默認(rèn) 53 號(hào)),對(duì)收到的數(shù)據(jù)包進(jìn)行處理.同時(shí)查出過(guò)得結(jié)果會(huì)緩存的 DNS-Server 的內(nèi)存緩存中,對(duì)于緩存的數(shù)據(jù)不老化刪除,就是說(shuō)查詢過(guò)的域名會(huì)一直在緩存中以提高查詢的速度,從而達(dá)到很高的響應(yīng)性能.如果域名信息發(fā)生變化,DNS Server 通過(guò)監(jiān)聽(tīng) etcd 隨時(shí)感知這種變化,從而更新緩存中的數(shù)據(jù),從而提供很好的實(shí)時(shí)性.測(cè)試發(fā)現(xiàn),從發(fā)生變化到能查出變更預(yù)期的結(jié)果一般在 20ms 以內(nèi),壞的情況不超過(guò) 50-60ms.
上圖是 DNS Server 響應(yīng)一次查詢的過(guò)程.首先根據(jù)域名和查詢的類型生成一個(gè)數(shù)據(jù)緩存的索引,然后查詢 DNS 數(shù)據(jù)緩存如果命中,簡(jiǎn)單處理返回給用戶.沒(méi)有命中從數(shù)據(jù)庫(kù)查詢結(jié)果,并將返回的結(jié)果插入到數(shù)據(jù)緩存中,下次查詢直接從緩存中取得,提高響應(yīng)速度.為了進(jìn)一步提高性能,緩存的數(shù)據(jù)不會(huì)老化刪除,只有到了緩存的數(shù)量限制才會(huì)隨機(jī)刪除一些釋放空間.不刪除緩存,緩存中的數(shù)據(jù)和實(shí)際的域名數(shù)據(jù)的一致性就是一個(gè)關(guān)鍵的問(wèn)題.我們采用 etcd 監(jiān)控功能實(shí)時(shí)抓取變更,從而更新緩存的數(shù)據(jù),經(jīng)過(guò)幾個(gè)星期的不停地循環(huán),增、刪、改、查域名,近 10 億次測(cè)試,未出現(xiàn)數(shù)據(jù)不一致的情況.下面是 DNS Server 監(jiān)控到域名信息變化的處理流程.
下面是 DNS Server 的配置文件:
其中 DNS 域主要是對(duì) DNS 的配置,DNS-domains 提供可查詢的域名的 zone,支持多組用 % 分隔.ex-nameServers 如果不是配置的域名,DNS Server 會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到這個(gè)地址進(jìn)行解析.解析的結(jié)果再通過(guò) DNS Server 轉(zhuǎn)給用戶.inDomainServers 選擇做已知域名 zone 的轉(zhuǎn)發(fā)功能.首先如果訪問(wèn)的域名匹配到 inDomainServers, 則交給 inDomainServers 指定的服務(wù)器處理,其次如果匹配到 DNS-domains 則查詢本地?cái)?shù)據(jù),最后如果都不匹配則交給 ex-nameServers 配置的 DNS 服務(wù)器處理.IP-monitor-path 是用于和探活模塊做數(shù)據(jù)交互的,系統(tǒng)中的 IP 狀態(tài)會(huì)存在 etcd 此目錄下.DNS Server 讀取其中的數(shù)據(jù),并監(jiān)控?cái)?shù)據(jù)的變化,從而更新自己緩存中的數(shù)據(jù).
DNS Server 另外提供兩個(gè)附加的功能,可以根據(jù)訪問(wèn)端的 IP 地址做不同的處理.Hold-one 如果使能,同一個(gè)客戶端訪問(wèn)同一個(gè)域名會(huì)返回一個(gè)固定的 IP.而 random-one 相反,每次訪問(wèn)返回一個(gè)不同的 IP.當(dāng)然這兩個(gè)功能在一個(gè)域名對(duì)應(yīng)多個(gè) IP 的時(shí)候才能體現(xiàn)出來(lái).為了提高查詢速度,查詢的域名會(huì)放在緩存中,cacheSize 用于控制緩存的大小,以防止內(nèi)存的無(wú)限之?dāng)U張.DNS Server 由于采用的是 Go 語(yǔ)言,cache 被設(shè)計(jì)為普通的字典,字典的 key 就是域名和訪問(wèn)類型的組合生成的結(jié)果.
DNS Server 提供統(tǒng)計(jì)數(shù)據(jù)的監(jiān)控,通過(guò) restful API 用戶可以讀取 DNS 的歷史數(shù)據(jù),訪問(wèn)采用了簡(jiǎn)單的認(rèn)證,密碼通過(guò)配置文件配置.用戶可以訪問(wèn)得到 DNS Server 啟動(dòng)后查詢域名的總的次數(shù)、成功的次數(shù)、查詢不到次數(shù)等信息.用戶同樣可以得到某一個(gè)域名的查詢次數(shù)和最后一次訪問(wèn)的時(shí)間等有效信息.通過(guò) DNS Server 統(tǒng)計(jì)信息,方便做集群的數(shù)據(jù)統(tǒng)計(jì).效果如下:
這個(gè)組件的主要功能是通過(guò) JDOS 的 JDOS-APIServer 的 watch-list 接口監(jiān)控用戶創(chuàng)建的 Service 和以及 endpoint 的變化,從而生成一條域名記錄,并將域名記錄導(dǎo)入到 etcd 數(shù)據(jù)庫(kù)中.簡(jiǎn)單的結(jié)構(gòu)如下圖.Service to DNS 進(jìn)程,支持多點(diǎn)冗余,防止單點(diǎn)故障.
Service to DNS 生成的域名主要目的是給 Docker 容器內(nèi)部訪問(wèn),域名的格式是 ServiceName.nameSpace.svc. clusterDomain.這個(gè)格式的要求和 JDOS 有密切的關(guān)系,我們知道 JDOS 創(chuàng)建 POD 的時(shí)候,傳遞數(shù)據(jù)生成容器的 resolv.conf 文件.下面是 JDOS 的代碼片段及 Docker 容器的 resolv.conf 文件的內(nèi)容.
可以看到域名采用的是 ServiceName.NameSpace.svc.clusterDomain 的命名格式,故而Service to DNS 需要監(jiān)控 JDOS 集群的 Service 的變化,以這種格式生成相關(guān)的域名.由于系統(tǒng)對(duì)用戶創(chuàng)建的服務(wù)會(huì)自動(dòng)的創(chuàng)建 load-balance 的服務(wù),所以域名的 IP 對(duì)應(yīng)的是這個(gè)服務(wù)關(guān)聯(lián)的 lb 的 IP,而 lb 的后端才是對(duì)應(yīng)著的是真正提供服務(wù)的 POD.
Service to DNS 進(jìn)程有兩種任務(wù):分別做數(shù)據(jù)增量同步和數(shù)據(jù)全量同步.
增量同步調(diào)用 JDOS-API 提供的 watch 接口,實(shí)時(shí)監(jiān)控 JDOS 集群 Service 和 endpoint 數(shù)據(jù)的變化,將變化的結(jié)果同步到 etcd 數(shù)據(jù)庫(kù)中,從而得到域名的信息.由于各種原因,增量同步有可能失敗,比如操作 etcd 數(shù)據(jù)庫(kù),由于網(wǎng)絡(luò)原因發(fā)生失敗.正如此全量同步才顯得有必要.全量同步是個(gè)周期性的任務(wù),這個(gè)任務(wù)首先同步 JDOS-API 的 list 接口得到,集群中的 Service 信息,然后調(diào)用 etcd 的 get 接口得到 etcd 中存儲(chǔ)域名數(shù)據(jù)信息,然后將兩邊的數(shù)據(jù)左匹配,從而保證 JDOS 集群中的 Service 數(shù)據(jù)和 etcd 的域名數(shù)據(jù)完全匹配起來(lái).
另外,Service to DNS 支持多點(diǎn)部署的特性,所以有可能同時(shí)多個(gè) Service to DNS 服務(wù)監(jiān)聽(tīng)到 JDOS 集群數(shù)據(jù)的變化,從而引起了同時(shí)操作 etcd 的問(wèn)題.這樣不利于數(shù)據(jù)的一致性,同時(shí)對(duì)相同的數(shù)據(jù),多次操作 etcd,會(huì)多次觸發(fā) etcd 的變更通知,從而使得 DNS Server 監(jiān)聽(tīng)到一些無(wú)意義的變更.為此 etcd 的讀寫(xiě)接口采用了 Golang 的 Context 庫(kù)管理上下文,可以有效地實(shí)現(xiàn)多個(gè)任務(wù)對(duì) etcd 的同步操作.比如插入一條數(shù)據(jù),會(huì)首先判斷數(shù)據(jù)是否存在,對(duì)于已經(jīng)存在的數(shù)據(jù),插入操作失敗.同時(shí)支持對(duì)過(guò)個(gè)數(shù)據(jù)的插入操作,其中有一個(gè)失敗,本次操作失敗.配置文件如下:
其中 etcd-Server 為 etcd 集群信息,這個(gè)要與 DNS Server 的配置文件要一致.Host 字段用于區(qū)別 Service to DNS 的運(yùn)行環(huán)境的地址,此數(shù)據(jù)會(huì)寫(xiě)到 etcd 數(shù)據(jù)庫(kù)中,可以很方便看到系統(tǒng)運(yùn)行了多少個(gè)冗余服務(wù).IP-monitor-path 寫(xiě)入原始的 IP 數(shù)據(jù)供探活模塊使用.JDOS-domain 域名信息,這個(gè)要和 DNS Server 保持一致,同時(shí)要和 JDOS 啟動(dòng)的 –cluster-domain 選項(xiàng)保持一致,數(shù)據(jù)才能被 Docker 容器正常的訪問(wèn).JDOS-config-file 文件是 JDOS-API 的訪問(wèn)配置信息,包括認(rèn)證信息等.
User API 提供 restful API,用戶可以配置自己域名信息.用戶可以對(duì)自己的域名信息進(jìn)行增、刪、改、查.數(shù)據(jù)結(jié)果會(huì)同步到 etcd 數(shù)據(jù)庫(kù)中,DNS Server 會(huì)通過(guò)監(jiān)聽(tīng) etcd 的變化將用戶的域名信息及時(shí)同步到 DNS Server 的緩存中.從而使得用戶域名數(shù)據(jù)被查詢.簡(jiǎn)單的配置如下:
API-domains 支持多個(gè)域名后綴的操作,API-auth 用于 API 認(rèn)證信息.其他信息 IP-monitor-path 等和 Service to DNS 模塊的功能相同.具體的 API 的使用見(jiàn)
IP status check 組件對(duì)域名的 IP 進(jìn)行探活,包括 DNS-scheduler 和 DNS-scanner 兩個(gè)模塊.DNS-scheduler 模塊監(jiān)控 Service to DNS 和 uer API 組件輸入的域名 IP 的信息,并將相關(guān)的 IP 探活合理地分配給不用的 DNS-scanner 任務(wù);DNS-scanner 模塊負(fù)責(zé)對(duì) IP 的具體的周期探活工作,并將實(shí)際的結(jié)果寫(xiě)到指定的 etcd 數(shù)據(jù)庫(kù)指定的目錄.DNS Server 組件會(huì)監(jiān)聽(tīng) etcd IP 狀態(tài)的結(jié)果,并將結(jié)果及時(shí)同步到自己的緩存中.
Docker 容器中驗(yàn)證
服務(wù)器驗(yàn)證:typeA
SRV 格式:
API 驗(yàn)證:
IP status check ?驗(yàn)證:
可以當(dāng) 192.168.10.1 的狀態(tài)變成 DOWN 后,查詢 DNS Server,192.168.10.1 的地址不會(huì)再出現(xiàn)在返回結(jié)果中.
性能優(yōu)化ContainerDNS 的組件的交互依賴于 etcd,etcd 是由 Go 語(yǔ)言開(kāi)發(fā)了.ContainerDNS 也采用 Go 語(yǔ)言.
測(cè)試環(huán)境:CPU: Intel(R) Xeon(R) CPU E5-2640 v3 @ 2.60GHzNIC: ?Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)測(cè)試工具:queryperf域名數(shù)據(jù):1000W 條域名記錄
性能數(shù)據(jù):
從上面三個(gè)表中可以清晰地看出,走 etcd 查詢速度最慢,走緩存查詢速度提升很多.同樣,不存在緩存老化.所以程序優(yōu)化的第一步,就是采用了全緩存,不老化的實(shí)現(xiàn)機(jī)制.就是說(shuō) DNS Server 啟動(dòng)的時(shí)候,將 etcd 中的數(shù)據(jù)全量讀取到內(nèi)存中,后期 watch 到 etcd 數(shù)據(jù)的變更,實(shí)時(shí)更新內(nèi)存中的數(shù)據(jù).全緩存一個(gè)最大的挑戰(zhàn)就是 etcd 的數(shù)據(jù)要和緩存中的數(shù)據(jù)的一致性.為此代碼中增加了很多對(duì)域名變更時(shí),對(duì)緩存的處理流程.同時(shí)為了防止有 watch 不到的變更(一周穩(wěn)定性測(cè)試 10 億次變更,出現(xiàn)過(guò)一次異常),增加了周期性全量同步數(shù)據(jù)的過(guò)程,這個(gè)同步粒度很細(xì),是基于域名的,程序中會(huì)記錄每次域名變更的時(shí)間,如果發(fā)現(xiàn)同步的過(guò)程中這個(gè)域名的數(shù)據(jù)發(fā)生變化,這個(gè)域名本次不會(huì)同步,從而保證了緩存數(shù)據(jù)的實(shí)時(shí)性,不會(huì)因?yàn)橥綄?dǎo)致新的變更丟失.
同時(shí)我們采集了每一秒的響應(yīng)情況,發(fā)現(xiàn)抖動(dòng)很大.而且全緩存情況下 queryperf 測(cè)試雖然平均能達(dá)到 10W TPS,但是抖動(dòng)從 2W-14W 區(qū)間較大.
通過(guò)實(shí)驗(yàn)測(cè)試進(jìn)程 CPU 損耗,我們發(fā)現(xiàn) golang GC 對(duì) CPU 的占用很大.
同時(shí)我們采集了 10 分鐘內(nèi)存的情況,如下
可以發(fā)現(xiàn),系統(tǒng)動(dòng)態(tài)申請(qǐng)了好多內(nèi)存大概 200 多個(gè) G,而 golang GC 會(huì)動(dòng)態(tài)回收內(nèi)存.
gc 18 @460.002s 0%: 0.030+44+0.21 ms clock, 0.97+1.8/307/503+6.9 ms cpu, 477->482->260 MB, 489 MB goal, 32 P
gc 19 @462.801s 0%: 0.046+50+0.19 ms clock, 1.4+25/352/471+6.3 ms cpu, 508->512->275 MB, 521 MB goal, 32 P
gc 20 @465.164s 0%: 0.067+50+0.41 ms clock, 2.1+64/351/539+13 ms cpu, 536->541->287 MB, 550 MB goal, 32 P
gc 21 @467.624s 0%: 0.10+54+0.20 ms clock, 3.2+65/388/568+6.2 ms cpu, 560->566->302 MB, 574 MB goal, 32 P
gc 22 @470.277s 0%: 0.050+57+0.23 ms clock, 1.6+73/401/633+7.3 ms cpu, 590->596->313 MB, 605 MB goal, 32 P
…
由于 golang GC 會(huì) STW(Stop The World),導(dǎo)致 GC 處理的時(shí)候有一段時(shí)間所有的協(xié)程停止響應(yīng).這也會(huì)引起程序的抖動(dòng).高級(jí)語(yǔ)言都帶有 GC 功能,只要是有內(nèi)存的動(dòng)態(tài)使用,最終會(huì)觸發(fā) GC,而我們可以做的事是想辦法減少內(nèi)存的動(dòng)態(tài)申請(qǐng).為此基于 pprof 工具采集的內(nèi)存使用的結(jié)果,將一些占用大的固定 size 的內(nèi)存放入緩存隊(duì)列中,申請(qǐng)內(nèi)存首先從緩存重申請(qǐng),如果緩存中沒(méi)有才動(dòng)態(tài)申請(qǐng)內(nèi)存,當(dāng)這塊內(nèi)存使用完后,主動(dòng)放在緩存中,這樣后續(xù)的申請(qǐng)就可以從緩存中取得.從而大大減少對(duì)內(nèi)存動(dòng)態(tài)申請(qǐng)的需求.由于各個(gè)協(xié)程都可能會(huì)操作這個(gè)數(shù)據(jù)緩存,從而這個(gè)緩存隊(duì)列的設(shè)計(jì)就要求其安全和高效.為此我們實(shí)現(xiàn)了一個(gè)無(wú)鎖隊(duì)列的設(shè)計(jì),下面是入隊(duì)的代碼片段.
目前對(duì) 512 字節(jié)的 msg 數(shù)據(jù)結(jié)構(gòu)做了緩存.用 pprof 采集內(nèi)存使用情況如下:
可以看到內(nèi)存由原來(lái)的 200G 減少到 120G,動(dòng)態(tài)申請(qǐng)內(nèi)存的數(shù)量大大減小.
同時(shí)性能也有所提升:
10 分鐘內(nèi)的采集結(jié)果可以看出,抖動(dòng)從原來(lái)的 2-10W 變成現(xiàn)在的 10-16W,抖動(dòng)相對(duì)變小.同時(shí) queryperf 測(cè)試每秒大概 14W TPS,比原來(lái)提高了 4W.
本文主要介紹了 ContainerDNS 在實(shí)際環(huán)境中的實(shí)踐、應(yīng)用和一些設(shè)計(jì)的思路.全部的代碼已經(jīng)開(kāi)源在 GitHub 上(詳見(jiàn) https://github.com/ipdcode/skydns ).我們也正在做一些后續(xù)的優(yōu)化和持續(xù)的改進(jìn).
陳書(shū)剛,京東商城基礎(chǔ)平臺(tái)部軟件工程師,有著多年從事數(shù)通產(chǎn)品的開(kāi)發(fā)、協(xié)議報(bào)文解析的工作的經(jīng)驗(yàn),目前主要從事基礎(chǔ)網(wǎng)絡(luò)功能的開(kāi)發(fā)與維護(hù).
文章來(lái)自微信公眾號(hào):高效運(yùn)維開(kāi)發(fā)
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.snjht.com/jiaocheng/2701.html