個人筆記,觀點不一定正確. 適合對 Kubernetes 有一定了解的同學。
前言
最近一直在學習 Kubernetes,但是手頭沒有個自有域名,要測試 ingress 就比較麻煩,每次都是手動改 hosts 文件。。
今天突然想到——K8s 內部就是用 DNS 做的服務發現,我為啥不自己弄一個 DNS 服務器呢?然后所有節點的 DNS 都配成它,這樣有需要時直接改這個 DNS 服務器的配置就行, 一勞永逸。
我首先想到的是 群暉/Windows Server 自帶的那種自帶圖形化界面的 DNS 服務器,但是這倆都是平台特定的。
網上搜一圈沒找到類似帶 UI 的 DNS 工具,搜到的 powerdns/bind 相比 coredns 也沒看出啥優勢來,所以決定就用 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
頂級域名服務器提供。 - 目前國際 DNS 系統中已有上千個 TLD,包括中文「.我愛你」甚至藏文域名,詳細列表參見 IANA TLD 數據庫
- 除了國際可用的 TLD,還有一類類似「內網 IP 地址」的“私有 TLD”,最常見的比如 xxx.local xxx.lan,被廣泛用在集群通信中。后面詳細介紹
- 頂級域名服務器負責解析
- 次級域(Second Level Domains):這個才是個人/企業能夠買到的域名,比如
baidu.com
- 每個次級域名都有一到多個權威 DNS 服務器,這些 DNS 服務器會以 NS 記錄的形式保存在對應的頂級域名(TLD)服務器中。
- 權威域名服務器則負責給出最終的解析結果:ip 地址(A 記錄 ),另一個域名(CNAME 記錄)、另一個 DNS 服務器(NS 記錄)等。
- 子域(Sub Domians):
*.baidu.com
統統都是baidu.com
的子域。- 每一個子域都可以有自己獨立的權威 DNS 服務器,這通過在子域中添加 NS 記錄實現。
普通用戶通常是通過域名提供商如阿里雲購買的次級域名,接下來我們以 rea.ink
為例介紹域名的購買到可用的整個流程。
域名的購買與使用流程:
- 你在某域名提供商處購買了一個域名
rea.ink
- 域名提供商向
.ink
對應的頂級域名服務器中插入一條以上的 NS 記錄,指向它自己的次級 DNS 服務器,如dns25.hichina.com.
- 阿里雲會向 TLD 中插入幾條 NS 記錄,指向阿里雲的次級 DNS 服務器(如
vip1.alidns.com
)。
- 阿里雲會向 TLD 中插入幾條 NS 記錄,指向阿里雲的次級 DNS 服務器(如
- 你在該域名提供商的 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 的一個郵件服務器進行測試:
dig 工具未來可能會被 drill 取代。
DNS 泛解析通配符 *
DNS 記錄允許使用通配符 *
,並且該通配符可匹配任意級數的子域!!!比如 *.example.com
就可以匹配所有的一二三四級域名等等,但是無法匹配 example.com
本身!
TTL(Time To Live)
上面講了公共 DNS 服務器通過緩存技術,降低了上游 DNS 服務器的壓力,也加快了網絡上的 DNS 查詢速度。
可緩存總得有個過期時間吧!為了精確地控制 DNS 記錄的過期時間,每條 DNS 記錄都要求設置一個時間屬性——TTL,單位為秒。這個時間可以自定義。
任何一條 DNS 緩存,在超過過期時間后都必須丟棄!
另外在沒超時的時候,DNS 緩存也可以被主動或者被動地刷新。
四、本地 DNS 服務器與私有 DNS 域
這類服務器只在當前局域網內有效,是一個私有的 DNS 服務器,企業常用。一般通過 DHCP 或者手動配置的方式,使內網的服務器都默認使用局域網 DNS 服務器進行解析。該服務器可以只解析自己的私有 DNS 域,而將其他 DNS 域的解析 forward 到公網 DNS 解析器去。
這個私有 DNS 域,會覆蓋掉公網的同名域(如果公網上有這個域的話)。
私有 dns 域也可使用公網不存在的 TLD,比如 xxx.local xxx.lan 等。vmware vcenter 就默認使用 vsphere.local 作為它的 sso (單點登錄)系統的域名。kubernetes 默認使用 svc.cluster.local
作為集群內部域名。
私有 DNS 域的選擇,參見 DNS 私有域的選擇:internal.xxx.com xxx.local 還是 xxx.zone?
局域網 DNS 服務器的規模與層級,視局域網的大小而定。一般小公司一個就行,要容災設三個副本也夠了。
以 CoreDNS 為例,局域網 DNS 服務器也可以被設置成一個 DNS Resolver,可以設置只轉發特定域名的 DNS 解析。這叫將某個域設為「轉發區域」。
五、操作系統的 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 遞歸解析器。好處有二:
- 權威 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 進行查看與修改。