Linux網路學習筆記(二):域名解析(DNS)——以 CoreDNS 為例
阿新 • • 發佈:2020-03-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 地址的協議。
而域名的好處,有如下幾項:
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