1. 程式人生 > >Linux網路學習筆記(二):域名解析(DNS)——以 CoreDNS 為例

Linux網路學習筆記(二):域名解析(DNS)——以 CoreDNS 為例

>個人筆記,觀點不一定正確. 適合對 Kubernetes 有一定了解的同學。 ## 前言 最近一直在學習 Kubernetes,但是手頭沒有個自有域名,要測試 ingress 就比較麻煩,每次都是手動改 hosts 檔案。。 今天突然想到——K8s 內部就是用 DNS 做的服務發現,我為啥不自己弄一個 DNS 伺服器呢?然後所有節點的 DNS 都配成它,這樣有需要時直接改這個 DNS 伺服器的配置就行, 一勞永逸。 我首先想到的是 群暉/Windows Server 自帶的那種自帶圖形化介面的 DNS 伺服器,但是這倆都是平臺特定的。 網上搜一圈沒找到類似帶 UI 的 DNS 工具,所以決定就用 CoreDNS,剛好熟悉一下它的具體使用。 不過講 CoreDNS 前,我們還是先來熟悉一下 DNS 吧。 ## 一、DNS 是個啥? >沒有寫得很清楚,不適合初學。建議先通過別的資料熟悉下 DNS。 DNS,即域名系統(Domain Name System),是一項負責將一個 human readable 的所謂域名,轉換成一個 ip 地址的協議。 而域名的好處,有如下幾項: 1. 域名對人類更友好,可讀的字串總比一串 ip 數字好記。 1. 一個域名可以對應多個 ip,可實現所謂的負載均衡。 1. 多個域名可以對應同一個 ip,以不同的域名訪問該 ip,能訪問不同的應用。(通過 nginx 做代理實現) DNS 協議是一個基於 UDP 的應用層協議,它預設使用 53 埠進行通訊。 應用程式通常將 DNS 解析委派給作業系統的 DNS Resolver 來執行,程式設計師對它幾乎無感知。 DNS 雖然說一般只用來查個 ip 地址,但是它提供的記錄型別還蠻多的,有如下幾種: 1. `A` 記錄:它記錄域名與 IPv4 地址的對應關係。目前用的最多的 DNS 記錄就是這個。 1. `AAAA` 記錄:它對應的是 IPv6,可以理解成新一代的 `A` 記錄。以後會用的越來越多的。 1. `NS` 記錄:記錄 DNS 域對應的權威伺服器**域名**,權威伺服器域名必須要有對應的 `A` 記錄。 - 通過這個記錄,可以將子域名的解析分配給別的 DNS 伺服器。 1. `CNAME` 記錄: 記錄域名與另一個域名的對應關係,用於給域名起別名。這個用得也挺多的。 1. `MX` 記錄:記錄域名對應的郵件伺服器域名,郵件伺服器的域名必須要有對應的 `A` 記錄。 1. `SRV` 記錄:SRV 記錄用於提供服務發現,看名字也能知道它和 SERVICE 有關。 - SRV 記錄的內容有固定格式:`優先順序 權重 埠 目標地址`,例如 `0 5 5060 sipserver.example.com` - 主要用於企業域控(AD)、微服務發現(Kubernetes) 上述的所有 DNS 記錄,都是屬於將域名解析為 IP 地址,或者另一個域名,這被稱做** DNS 正向解析**。 除了這個正向解析外,還有個非常冷門的**反向解析**,基本上只在設定郵件伺服器時才會用到。(Kubernetes 可能也有用到) 反向解析主要的記錄型別是:**`PTR` 記錄**,它提供將 IP 地址反向解析為域名的功能。 而且因為域名是從右往左讀的(最右側是根, `www.baidu.com.`),而 IP 的網段(如 `192.168.0.0/16`)剛好相反,是左邊優先。 因此 PTR 記錄的“域名”必須將 IP 地址反著寫,末尾再加上 `.in-addr.arpa.` 表示這是一個反向解析的域名。(ipv6 使用 `ip6.arpa.`) 拿 baidu.com 的郵件伺服器測試一下: ![](https://img2018.cnblogs.com/blog/968138/202002/968138-20200207101308247-1165339332.png) 其他還有些 `TXT`、`CAA` 等奇奇怪怪的記錄,就用到的時候自己再查了。 ## 二、域名的分層結構 國際域名系統被分成四層: 1. **根域(Root Zone)**:所有域名的根。 - 根域名伺服器負責解析`頂級域名`,給出頂級域名的 DNS 伺服器地址。 - 全世界僅有十三組根域名伺服器,這些伺服器的 ip 地址基本不會變動。 - 它的域名是 "",空字串。而它的**全限定域名(FQDN)**是 `.`,因為 FQDN 總是以 `.` 結尾。(FQDN 在後面解釋,可暫時忽略) 1. **頂級域(Top Level Domains, TLD)**:`.com` `.cn` 等國際、國家級的域名 - 頂級域名伺服器負責解析`次級域名`,給出次級域名的 DNS 伺服器地址。 - 每個頂級域名都對應各自的伺服器,它們之間是完全獨立的。`.cn` 的域名解析僅由 `.cn` 頂級域名伺服器提供。 1. **次級域(Second Level Domains)**:這個才是個人/企業能夠買到的域名,比如 `baidu.com` - 每個次級域名都有一到多個權威 DNS 伺服器,這些 DNS 伺服器會以 NS 記錄的形式儲存在對應的頂級域名(TLD)伺服器中。 - 權威域名伺服器則負責給出最終的解析結果:ip 地址(A 記錄 ),另一個域名(CNAME 記錄)、另一個 DNS 伺服器(NS 記錄)等。 1. **子域(Sub Domians)**:`*.baidu.com` 統統都是 `baidu.com` 的子域。 - 每一個子域都可以有自己獨立的權威 DNS 伺服器,這通過在子域中新增 NS 記錄實現。 普通使用者通常是通過域名提供商如阿里雲購買的次級域名,接下來我們以 `rea.ink` 為例介紹域名的購買到可用的整個流程。 這時阿里雲會向該中插入幾條 NS 記錄,指向阿里雲的次級 DNS 伺服器(`vip1.alidns.com`)。 1. 你在某域名提供商處購買了一個域名 `rea.ink` 1. 域名提供商向 `.ink` 對應的頂級域名伺服器中插入一條以上的 NS 記錄,指向它自己的次級 DNS 伺服器,如 `dns25.hichina.com.` 1. 你在該域名提供商的 DNS 管理介面中新增 `A` 記錄,值為你的伺服器 IP。 1. OK 現在 ping 一下 `rea.ink`,就會發現它已經解析到你自己的伺服器了。 上述流程中忽略了我大天朝的特殊國情——備案,勿介意。 ## 三、DNS 遞迴解析器:在瀏覽器中輸入域名後發生了什麼? 下面的圖片拷貝自 Amazon Aws 文件,它展示了在不考慮任何 DNS 快取的情況下,一次 Web 請求的經過,詳細描繪了 DNS 解析的部分。 ![](https://img2018.cnblogs.com/blog/968138/202002/968138-20200205165225054-57338322.png) 其中的第 3 4 5 步按順序向前面講過的根域名伺服器、頂級域名伺服器、權威域名伺服器發起請求,以獲得下一個 DNS 伺服器的資訊。這很清晰。 圖中當前還沒介紹的部分,是紫色的 `DNS Resolver`(域名解析器),也叫 `Recursive DNS resolver`(**DNS 遞迴解析器**)。 它本身只負責遞迴地請求 3 4 5 步中的上游伺服器,然後把獲取的最終結果返回給客戶端,同時將記錄快取到本地以加快解析速度。 這個 DNS 解析器,其實就是所謂的**公共 DNS 伺服器**:Google 的 `8.8.8.8`,國內著名的 `114.114.114.114`。 這些公共 DNS 使用者量大,快取了大量的 DNS 記錄,有效地降低了上游 DNS 伺服器的壓力,也加快了網路上的 DNS 查詢速度。 接下來使用 `dig +trace baidu.com` 復現一下上述的查詢流程(這種情況下 dig 自己就是一個 DNS 遞迴解析器): ![](https://img2018.cnblogs.com/blog/968138/202002/968138-20200207101817456-1276700345.png) 另外前面有講過 DNS 的反向解析,也是同樣的層級結構,是從根伺服器開始往下查詢的,下面拿 baidu 的一個郵件伺服器進行測試: ![](https://img2018.cnblogs.com/blog/968138/202002/968138-20200207102331250-71048478.png) ### TTL(Time To Live) 上面講了**公共 DNS 伺服器**通過快取技術,降低了上游 DNS 伺服器的壓力,也加快了網路上的 DNS 查詢速度。 可快取總得有個過期時間吧!為了精確地控制 DNS 記錄的過期時間,每條 DNS 記錄都要求設定一個時間屬性——TTL,單位為秒。這個時間可以自定義。 任何一條 DNS 快取,在超過過期時間後都必須丟棄! ## 四、本地 DNS 伺服器 這類伺服器只在當前區域網內有效,是一個私有的 DNS 伺服器,企業常用。一般通過 DHCP 或者手動配置的方式,使內網的伺服器都預設使用區域網 DNS 伺服器進行解析。 區域網 DNS 伺服器的規模與層級,視區域網的大小而定。一般小公司一個就行,要容災設三個副本也夠了。 以 CoreDNS 為例,區域網 DNS 伺服器也可以被設定成一個 DNS Resolver,可以設定只轉發特定域名的 DNS 解析。這叫將某個域設為轉發區域。 著名的 Kubernetes 容器集群系統(它內部使用的是自己的虛擬區域網),就是使用的 CoreDNS 進行區域網的域名解析,以實現服務發現功能。 ## 五、作業系統的 DNS 解析器 應用程式實際上都是呼叫的作業系統的 DNS Resolver 進行域名解析的。在 Linux 中 DNS Resolver 由 glibc/musl 提供,配置檔案為 `/etc/resolv.conf`。 比如 Python 的 DNS 解析,就來自於標準庫的 socket 包,這個包只是對底層 c 語言庫的一個簡單封裝。 基本上只有專門用於網路診斷的 DNS 工具包,才會自己實現 DNS 協議。 ### 1. hosts 檔案 作業系統中還有一個特殊檔案:Linux 中的 `/etc/hosts` 和 Windows 中的 `C:\Windows\System32\drivers\etc\hosts` 系統中的 DNS resolver 會首先檢視這個 `hosts` 檔案中有沒有該域名的記錄,如果有就直接返回了。沒找到才會去查詢本地 DNS 快取、別的 DNS 伺服器。 只有部分專門用於網路診斷的應用程式(e.g. dig)不會依賴 OS 的 DNS 解析器,因此這個 `hosts` 會失效。`hosts` 對於絕大部分程式都有效。 >移動裝置上 hosts 可能會失效,部分 app 會繞過系統,使用新興的 HTTPDNS 協議進行 DNS 解析。 ### 2. HTTPDNS 傳統的 DNS 協議因為使用了明文的 UDP 協議,很容易被劫持。順應移動網際網路的興起,目前一種新型的 DNS 協議——HTTPDNS 應用越來越廣泛,國內的阿里雲騰訊雲都提供了這項功能。 HTTPDNS 通過 HTTP 協議直接向權威 DNS 伺服器發起請求,繞過了一堆中間的 DNS 遞迴解析器。好處有二: 1. 權威 DNS 伺服器能直接獲取到客戶端的真實 IP(而不是某個中間 DNS 遞迴解析器的 IP),能實現就近排程。 1. 因為是直接與權威 DNS 伺服器連線,避免了 DNS 快取汙染的問題。 HTTPDNS 協議需要程式自己引入 SDK,或者直接請求 HTTP API。 ### 3. 預設 DNS 伺服器 作業系統的 DNS 解析器通常會允許我們配置多個上游 Name Servers,比如 Linux 就是通過 `/etc/resolv.conf` 配置 DNS 伺服器的。 ```conf $ cat /etc/resolv.conf nameserver 8.8.8.8 nameserver 8.8.4.4 search lan ``` >不過現在這個檔案基本不會手動修改了,各 Linux 發行版都推出了自己的網路配置工具,由這些工具自動生成 Linux 的各種網路配置,更方便。 比如 Ubuntu 就推薦使用 netplan 工具進行網路設定。 >Kubernetes 就是通過使用容器卷對映的功能,修改 /etc/resolv.conf,使叢集的所有容器都使用叢集 DNS 伺服器(CoreDNS)進行 DNS 解析。 通過重複使用 `nameserver` 欄位,可以指定多個 DNS 伺服器(Linux 最多三個)。DNS 查詢會按配置中的順序選用 DNS 伺服器。 **僅在靠前的 DNS 伺服器沒有響應(timeout)時,才會使用後續的 DNS 伺服器!所以指定的伺服器中的 DNS 記錄最好完全一致!!!**不要把第一個配內網 DNS,第二個配外網!!! ### 4. DNS 搜尋域 上一小節給出的 `/etc/resolv.conf` 檔案內容的末尾,有這樣一行: `search lan`,它指定的,是所謂的 DNS 搜尋域。 講到 `DNS 搜尋域`,就不得不提到一個名詞:全限定域名(Full Qulified Domain Name, FQDN),即一個域名的完整名稱,`www.baidu.com`。 一個普通的域名,有下列四種可能: 1. `www.baidu.com.`: 末尾的 `.` 表示根域,說明 `www.baidu.com` 是一個 FQDN,因此不會使用搜索域! 1. `www.baidu.com`: 末尾沒 `.`,但是域名包含不止一個 `.`。首先當作 FQDN 進行查詢,沒查詢再按順序在各搜尋域中查詢。 - `/etc/resolv.conf` 的 `options` 引數中,可以指定域名中包含 `.` 的臨界個數,預設是 1. 1. `local`: 不包含 `.`,被當作 `host` 名稱,非 FQDN。首先在 `/etc/hosts` 中查詢,沒找到的話,再按順序在各搜尋域中查詢。 >上述搜尋順序可以通過 `host -v ` 進行測試,該命令會輸出它嘗試過的所有 FQDN。 修改 `/etc/resolv.conf` 中的 `search` 屬性並測試,然後檢視輸出。 就如上面說例舉的,在沒有 `DNS 搜尋域` 這個東西的條件下,我們訪問任何域名,都必須輸入一個全限定域名 FQDN。 有了搜尋域我們就可以稍微偷點懶,省略掉域名的一部分字尾,讓 DNS Resolver 自己去在各搜尋域中搜索。 在 Kubernetes 中就使用到了搜尋域,k8s 中預設的域名 FQDN 是 `service.namespace.svc.cluster.local`, 但是對於 default namespace 中的 service,我們可以直接通過 `service` 名稱查詢到它的 IP。 對於其他名字空間中的 service,也可以通過 `service.namespace` 查詢到它們的 IP,不需要給出 FQDN。 Kubernetes 中 `/etc/resolv.conf` 的示例如下: ```conf nameserver 10.43.0.10 search default.svc.cluster.local svc.cluster.local cluster.local options ndots:5 ``` 可以看到 k8s 設定了一系列的搜尋域,並且將 `.` 的臨界值設為了 5。 也就是少於 5 個 dots 的域名,都首先當作非 FQDN 看待,優先在搜尋域裡面查詢。 該配置檔案的詳細描述參見 [manpage - resolv.conf](http://man7.org/linux/man-pages/man5/resolv.conf.5.html),或者在 Linux 中使用 `man resolv.conf` 命令檢視。 ## 六、DNS 診斷的命令列工具 ```shell dig +trace baidu.com # 診斷 dns 的主要工具,非常強大 host -a baidu.com # host 基本就是 dig 的弱化版,不過 host 有個有點就是能打印出它測試過的所有 FQDN nslookup baidu.com # 和 host 沒啥大差別,多個互動式查詢不過一般用不到 whois baidu.com # 查詢域名註冊資訊,內網診斷用不到 ``` 詳細的使用請 `man dig` ## 七、CoreDNS 的使用 主流的本地 DNS 伺服器中,提供 UI 介面的有 Windows DNS Server 和群暉 DNS Server,很方便,不過這兩個都是作業系統繫結的。 開源的 DNS 伺服器裡邊兒,BIND 好像是最有名的,各大 Linux 發行版自帶的 `dig/host/nslookup`,最初都是 Bind 提供的命令列工具。 不過為了一舉兩得(DNS+K8s),咱還是直接學習 CoreDNS 的使用。 CoreDNS 最大的特點是靈活,可以很方便地給它編寫外掛以提供新功能。功能非常強大,相比傳統 DNS 伺服器,它非常“現代化”。在 K8s 中它被用於提供服務發現功能。 接下來以 CoreDNS 為例,講述如何配置一個 DNS 伺服器,新增私有的 DNS 記錄,並設定轉發規則以解析公網域名。 ### 1. 配置檔案:Corefile CoreDNS 因為是 Go 語言寫的,編譯結果是單個可執行檔案,它預設以當前資料夾下的 Corefile 為配置檔案。以 kubernetes 中的 Corefile 為例: ```corefile .:53 { errors # 啟用錯誤日誌 health # 啟用健康檢查 api ready # 啟用 readiness 就緒 api # 啟用 kubernetes 叢集支援,詳見 https://coredns.io/plugins/kubernetes/ # 此外掛只處理 cluster.local 域,以及 PTR 解析 kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure upstream # fallthrough in-addr.arpa ip6.arpa # 向下傳遞 DNS 反向查詢 ttl 30 # 過期時間 } prometheus :9153 # 啟用 prometheus metrics 支援 forward . 114.114.114.114 19.29.29.29 # 將非叢集域名的 DNS 請求,轉發給公網 DNS 伺服器。 cache 30 # 啟用前端快取,快取的 TTL 設為 30 loop # 檢測並停止死迴圈解析 reload # 支援動態更新 Corefile # 隨機化 A/AAAA/MX 記錄的順序以實現負載均衡。 # 因為 DNS resolver 通常使用第一條記錄,而第一條記錄是隨機的。這樣客戶端的請求就能被隨機分配到多個後端。 loadbalance } ``` Corefile 首先定義 DNS 域,域後的程式碼塊內定義需要使用的各種外掛。**注意這裡的外掛順序是沒有任何意義的!**外掛的呼叫鏈是在 CoreDNS 編譯時就定義好的,不能在執行時更改。 通過上述配置啟動的 CoreDNS 是無狀態的,它以 Kubernetes ApiServer 為資料來源,CoreDNS 本身只相當於一個查詢器/快取,因此它可以很方便地擴縮容。 ### 2. 將 CoreDNS 設定成一個私有 DNS 伺服器 現在清楚了 Corefile 的結構,讓我們來設計一個通過檔案配置 DNS 條目的 Corefile 配置: ```corefile # 定義可複用 Block (common) { log errors cache loop # 檢測並停止死迴圈解析 } # 本地開發環境的 DNS 解析 dev-env.local:53 { import common # 匯入 Block file dev-env.local { # 從檔案 `dev-env.local` 中讀取 DNS 資料 reload 30s # 每 30s 檢查一次配置的 Serial,若該值有變更則過載整個 Zone 的配置。 } } # 本地測試環境 test-env.local:53 { import common file test-env.local { reload 30s } } # 其他 .:53 { forward . 114.114.114.114 # 解析公網域名 log errors cache } ``` 上面的 Corefile 定義了兩個本地域名 `dev-env.local` 和 `test-env.local`,它們的 DNS 資料分別儲存在 `file` 指定的檔案中。 這個 `file` 指定的檔案和 `bind9` 一樣,都是使用在 [rfc1035](https://tools.ietf.org/html/rfc1035#section-5.3) 中定義的 Master File 格式,`dig` 命令輸出的就是這種格式的內容。示例如下: ``` ;; 與整個領域相關性較高的設定包括 NS, A, MX, SOA 等標誌的設定處! $TTL 30 @ IN SOA dev-env.local. devops.dev-env.local. ( 20200202 ; SERIAL,每次修改此檔案,都應該同步修改這個“版本號”,可將它設為修改時間。 7200 ; REFRESH 600 ; RETRY 3600000 ; EXPIRE 60) ; MINIMUM @ IN NS dns1.dev-env.local. ; DNS 伺服器名稱 dns1.dev-env.local. IN A 192.168.23.2 ; DNS 伺服器 IP redis.dev-env.local. IN A 192.168.23.21 mysql.dev-env.local. IN A 192.168.23.22 elasticsearch.dev-env.local. IN A 192.168.23.23 ftp IN A 192.168.23.25 ; 這是簡化的寫法! ``` 詳細的格式說明參見 [鳥哥的 Linux 私房菜 - DNS 正解資料庫檔案的設定](http://linux.vbird.org/linux_server/0350dns.php#DNS_master_name) `test-env.local` 也是一樣的格式,根據上面的模板修改就行。這兩個配置檔案和 Corefile 放在同一個目錄下: ``` root@test-ubuntu:~/dns-server# tree . ├── coredns # coredns binary ├── Corefile ├── dev-env.local └── test-env.local ``` 然後通過 `./coredns` 啟動 coredns。通過 dig 檢驗: ![](https://img2018.cnblogs.com/blog/968138/202002/968138-20200211182339141-1135337933.png) 可以看到 `ftp.dev-env.local` 已經被成功解析了。 ### 3. [可選外掛(External Plugins)](https://coredns.io/explugins/) CoreDNS 提供的預編譯版本,不包含 [External Plugins](https://coredns.io/explugins/) 中列出的部分,如果你需要,可以自行修改 `plugin.cfg`,然後手動編譯。 不得不說 Go 語言的編譯,比 C 語言是方便太多了。自動拉取依賴,一行命令編譯!只要配好 [GOPROXY](https://github.com/goproxy/goproxy.cn),啟用可選外掛其實相當簡單。 ### 4. 設定 DNS 叢集 單臺 DNS 伺服器的效能是有限的,而且存在單點故障問題。因此在要求高可用或者高效能的情況下,就需要設定 DNS 叢集。 雖然說 CoreDNS 本身也支援各種 DNS Zone 傳輸,主從 DNS 伺服器等功能,不過我想最簡單的,可能還是直接用 K8s。 直接用 ConfigMap 存配置,通過 Deployment 擴容就行,多方便。 要修改起來更方便,還可以啟用可選外掛:redis,直接把配置以 json 的形式存在 redis 裡,通過 redis-desktop-manager 進行檢視與修改。 ## 參考 - [DNS 原理入門](http://www.ruanyifeng.com/blog/2016/06/dns.html) - [What Is DNS? | How DNS Works - Cloudflare](https://www.cloudflare.com/learning/dns/what-is-dns/) - [What is DNS? - Amazon AWS](https://aws.amazon.com/cn/route53/what-is-dns/?nc1=h_ls) - [鳥哥的 Linux 私房菜——主機名稱控制者: DNS 伺服器](http://linux.vbird.org/linux_server/0350dns.php#DNS_resolver_whois) - [CoreDNS - Manual](https://coredns.io/manual/toc/) - [Kubernetes - DNS for Services and Pods](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) - [Kubernetes - Customizing DNS Service](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-names