Linux網路學習筆記(二):域名解析(DNS)——以 CoreDNS 為例
- 2020 年 3 月 29 日
- 筆記
個人筆記,觀點不一定正確. 適合對 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 地址的協議。
而域名的好處,有如下幾項:
- 域名對人類更友好,可讀的字元串總比一串 ip 數字好記。
- 一個域名可以對應多個 ip,可實現所謂的負載均衡。
- 多個域名可以對應同一個 ip,以不同的域名訪問該 ip,能訪問不同的應用。(通過 nginx 做代理實現)
DNS 協議是一個基於 UDP 的應用層協議,它默認使用 53 埠進行通訊。
應用程式通常將 DNS 解析委派給作業系統的 DNS Resolver 來執行,程式設計師對它幾乎無感知。
DNS 雖然說一般只用來查個 ip 地址,但是它提供的記錄類型還蠻多的,有如下幾種:
A
記錄:它記錄域名與 IPv4 地址的對應關係。目前用的最多的 DNS 記錄就是這個。AAAA
記錄:它對應的是 IPv6,可以理解成新一代的A
記錄。以後會用的越來越多的。NS
記錄:記錄 DNS 域對應的權威伺服器域名,權威伺服器域名必須要有對應的A
記錄。- 通過這個記錄,可以將子域名的解析分配給別的 DNS 伺服器。
CNAME
記錄: 記錄域名與另一個域名的對應關係,用於給域名起別名。這個用得也挺多的。MX
記錄:記錄域名對應的郵件伺服器域名,郵件伺服器的域名必須要有對應的A
記錄。SRV
記錄:SRV 記錄用於提供服務發現,看名字也能知道它和 SERVICE 有關。- SRV 記錄的內容有固定格式:
優先順序 權重 埠 目標地址
,例如0 5 5060 sipserver.example.com
- 主要用於企業域控(AD)、微服務發現(Kubernetes)
- SRV 記錄的內容有固定格式:
上述的所有 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 的郵件伺服器測試一下:
其他還有些 TXT
、CAA
等奇奇怪怪的記錄,就用到的時候自己再查了。
二、域名的分層結構
國際域名系統被分成四層:
- 根域(Root Zone):所有域名的根。
- 根域名伺服器負責解析
頂級域名
,給出頂級域名的 DNS 伺服器地址。 - 全世界僅有十三組根域名伺服器,這些伺服器的 ip 地址基本不會變動。
- 它的域名是 "",空字元串。而它的全限定域名(FQDN)是
.
,因為 FQDN 總是以.
結尾。(FQDN 在後面解釋,可暫時忽略)
- 根域名伺服器負責解析
- 頂級域(Top Level Domains, TLD):
.com
.cn
等國際、國家級的域名- 頂級域名伺服器負責解析
次級域名
,給出次級域名的 DNS 伺服器地址。 - 每個頂級域名都對應各自的伺服器,它們之間是完全獨立的。
.cn
的域名解析僅由.cn
頂級域名伺服器提供。
- 頂級域名伺服器負責解析
- 次級域(Second Level Domains):這個才是個人/企業能夠買到的域名,比如
baidu.com
- 每個次級域名都有一到多個權威 DNS 伺服器,這些 DNS 伺服器會以 NS 記錄的形式保存在對應的頂級域名(TLD)伺服器中。
- 權威域名伺服器則負責給出最終的解析結果:ip 地址(A 記錄 ),另一個域名(CNAME 記錄)、另一個 DNS 伺服器(NS 記錄)等。
- 子域(Sub Domians):
*.baidu.com
統統都是baidu.com
的子域。- 每一個子域都可以有自己獨立的權威 DNS 伺服器,這通過在子域中添加 NS 記錄實現。
普通用戶通常是通過域名提供商如阿里雲購買的次級域名,接下來我們以 rea.ink
為例介紹域名的購買到可用的整個流程。
這時阿里雲會向該中插入幾條 NS 記錄,指向阿里雲的次級 DNS 伺服器(vip1.alidns.com
)。
- 你在某域名提供商處購買了一個域名
rea.ink
- 域名提供商向
.ink
對應的頂級域名伺服器中插入一條以上的 NS 記錄,指向它自己的次級 DNS 伺服器,如dns25.hichina.com.
- 你在該域名提供商的 DNS 管理介面中添加
A
記錄,值為你的伺服器 IP。 - OK 現在 ping 一下
rea.ink
,就會發現它已經解析到你自己的伺服器了。
上述流程中忽略了我大天朝的特殊國情——備案,勿介意。
三、DNS 遞歸解析器:在瀏覽器中輸入域名後發生了什麼?
下面的圖片拷貝自 Amazon Aws 文檔,它展示了在不考慮任何 DNS 快取的情況下,一次 Web 請求的經過,詳細描繪了 DNS 解析的部分。
其中的第 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 遞歸解析器):
另外前面有講過 DNS 的反向解析,也是同樣的層級結構,是從根伺服器開始往下查詢的,下面拿 baidu 的一個郵件伺服器進行測試:
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:WindowsSystem32driversetchosts
系統中的 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 遞歸解析器。好處有二:
- 權威 DNS 伺服器能直接獲取到客戶端的真實 IP(而不是某個中間 DNS 遞歸解析器的 IP),能實現就近調度。
- 因為是直接與權威 DNS 伺服器連接,避免了 DNS 快取污染的問題。
HTTPDNS 協議需要程式自己引入 SDK,或者直接請求 HTTP API。
3. 默認 DNS 伺服器
作業系統的 DNS 解析器通常會允許我們配置多個上游 Name Servers,比如 Linux 就是通過 /etc/resolv.conf
配置 DNS 伺服器的。
$ 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
。
一個普通的域名,有下列四種可能:
www.baidu.com.
: 末尾的.
表示根域,說明www.baidu.com
是一個 FQDN,因此不會使用搜索域!www.baidu.com
: 末尾沒.
,但是域名包含不止一個.
。首先當作 FQDN 進行查詢,沒查找再按順序在各搜索域中查詢。/etc/resolv.conf
的options
參數中,可以指定域名中包含.
的臨界個數,默認是 1.
local
: 不包含.
,被當作host
名稱,非 FQDN。首先在/etc/hosts
中查找,沒找到的話,再按順序在各搜索域中查找。
上述搜索順序可以通過
host -v <domain-name>
進行測試,該命令會輸出它嘗試過的所有 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
的示例如下:
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,或者在 Linux 中使用 man resolv.conf
命令查看。
六、DNS 診斷的命令行工具
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 為例:
.: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 配置:
# 定義可復用 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 中定義的 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 正解資料庫檔案的設定
test-env.local
也是一樣的格式,根據上面的模板修改就行。這兩個配置文件和 Corefile 放在同一個目錄下:
root@test-ubuntu:~/dns-server# tree . ├── coredns # coredns binary ├── Corefile ├── dev-env.local └── test-env.local
然後通過 ./coredns
啟動 coredns。通過 dig 檢驗:
可以看到 ftp.dev-env.local
已經被成功解析了。
3. 可選插件(External Plugins)
CoreDNS 提供的預編譯版本,不包含 External Plugins 中列出的部分,如果你需要,可以自行修改 plugin.cfg
,然後手動編譯。
不得不說 Go 語言的編譯,比 C 語言是方便太多了。自動拉取依賴,一行命令編譯!只要配好 GOPROXY,啟用可選插件其實相當簡單。
4. 設置 DNS 集群
單台 DNS 伺服器的性能是有限的,而且存在單點故障問題。因此在要求高可用或者高性能的情況下,就需要設置 DNS 集群。
雖然說 CoreDNS 本身也支援各種 DNS Zone 傳輸,主從 DNS 伺服器等功能,不過我想最簡單的,可能還是直接用 K8s。
直接用 ConfigMap 存配置,通過 Deployment 擴容就行,多方便。
要修改起來更方便,還可以啟用可選插件:redis,直接把配置以 json 的形式存在 redis 里,通過 redis-desktop-manager 進行查看與修改。
參考
- DNS 原理入門
- What Is DNS? | How DNS Works – Cloudflare
- What is DNS? – Amazon AWS
- 鳥哥的 Linux 私房菜——主機名稱控制者: DNS 伺服器
- CoreDNS – Manual
- Kubernetes – DNS for Services and Pods
- Kubernetes – Customizing DNS Service