使用 CoreDNS 來應對 DNS 汙染

米開朗基楊發表於2020-12-29

原文連結:https://fuckcloudnative.io/posts/install-coredns-on-macos/

CoreDNS 是 Golang 編寫的一個外掛式 DNS 伺服器,是 Kubernetes 1.13 後所內建的預設 DNS 伺服器。CoreDNS 的目標是成為 cloud-native 環境下的 DNS 伺服器和服務發現解決方案,即:

Our goal is to make CoreDNS the cloud-native DNS server and service discovery solution.

它有以下幾個特性:

  • 外掛化(Plugins)

    基於 Caddy 伺服器框架,CoreDNS 實現了一個外掛鏈的架構,將大量應用端的邏輯抽象成 plugin 的形式(如 Kubernetes 的 DNS 服務發現,Prometheus 監控等)暴露給使用者。CoreDNS 以預配置的方式將不同的 plugin 串成一條鏈,按序執行 plugin 的邏輯。從編譯層面,使用者選擇所需的 plugin 編譯到最終的可執行檔案中,使得執行效率更高。CoreDNS 採用 Go 編寫,所以從具體程式碼層面來看,每個 plugin 其實都是實現了其定義的 interface 的元件而已。第三方只要按照 CoreDNS Plugin API 去編寫自定義外掛,就可以很方便地整合於 CoreDNS。

  • 配置簡單化

    引入表達力更強的 DSL,即 Corefile 形式的配置檔案(也是基於 Caddy 框架開發)。

  • 一體化的解決方案

    區別於 kube-dns,CoreDNS 編譯出來就是一個單獨的二進位制可執行檔案,內建了 cache,backend storage,health check 等功能,無需第三方元件來輔助實現其他功能,從而使得部署更方便,記憶體管理更為安全。

其實從功能角度來看,CoreDNS 更像是一個通用 DNS 方案(類似於 BIND),然後通過外掛模式來極大地擴充套件自身功能,從而可以適用於不同的場景(比如 Kubernetes)。正如官方部落格所說:

CoreDNS is powered by plugins.

1. Corefile 介紹


Corefile 是 CoreDNS 的配置檔案(源於 Caddy 框架的配置檔案 Caddyfile),它定義了:

  • server 以什麼協議監聽在哪個埠(可以同時定義多個 server 監聽不同埠)
  • server 負責哪個 zone 的權威(authoritative)DNS 解析
  • server 將載入哪些外掛

常見地,一個典型的 Corefile 格式如下所示:

ZONE:[PORT] {
	[PLUGIN] ...
}
  • ZONE : 定義 server 負責的 zone,PORT 是可選項,預設為 53;
  • PLUGIN : 定義 server 所要載入的 plugin。每個 plugin 可以有多個引數;

比如:

. {
    chaos CoreDNS-001
}

上述配置檔案表達的是:server 負責根域 . 的解析,其中 plugin 是 chaos 且沒有引數。

定義 server

一個最簡單的配置檔案可以為:

.{}

即 server 監聽 53 埠並不使用外掛。如果此時在定義其他 server,要保證監聽埠不衝突;如果是在原來 server 增加 zone,則要保證 zone 之間不衝突,如:

.    {}
.:54 {}

另一個 server 執行於 54 埠並負責根域 . 的解析。

又如:

example.org {
    whoami
}
org {
    whoami
}

同一個 server 但是負責不同 zone 的解析,有不同外掛鏈。

定義 Reverse Zone

跟其他 DNS 伺服器類似,Corefile 也可以定義 Reverse Zone(反向解析 IP 地址對應的域名):

0.0.10.in-addr.arpa {
    whoami
}

或者簡化版本:

10.0.0.0/24 {
    whoami
}

可以通過 dig 進行反向查詢:

$ dig -x 10.0.0.1

使用不同的通訊協議

CoreDNS 除了支援 DNS 協議,也支援 TLSgRPC,即 DNS-over-TLS 和 DNS-over-gRPC 模式:

tls://example.org:1443 {
#...
}

2. 外掛的工作模式


當 CoreDNS 啟動後,它將根據配置檔案啟動不同 server ,每臺 server 都擁有自己的外掛鏈。當有 DNS 請求時,它將依次經歷如下 3 步邏輯:

  1. 如果有當前請求的 server 有多個 zone,將採用貪心原則選擇最匹配的 zone;
  2. 一旦找到匹配的 server,按照 plugin.cfg 定義的順序執行外掛鏈上的外掛;
  3. 每個外掛將判斷當前請求是否應該處理,將有以下幾種可能:
  • 請求被當前外掛處理

    外掛將生成對應的響應並回給客戶端,此時請求結束,下一個外掛將不會被呼叫,如 whoami 外掛;

  • 請求被當前外掛以 Fallthrough 形式處理

    如果請求在該外掛處理過程中有可能將跳轉至下一個外掛,該過程稱為 fallthrough,並以關鍵字 fallthrough 來決定是否允許此項操作,例如 host 外掛,當查詢域名未位於 /etc/hosts,則呼叫下一個外掛;

  • 請求在處理過程被攜帶 Hint

    請求被外掛處理,並在其響應中新增了某些資訊(hint)後繼續交由下一個外掛處理。這些額外的資訊將組成對客戶端的最終響應,如 metric 外掛;

3. CoreDNS 如何處理 DNS 請求


如果 Corefile 為:

coredns.io:5300 {
    file db.coredns.io
}

example.io:53 {
    log
    errors
    file db.example.io
}

example.net:53 {
    file db.example.net
}

.:53 {
    kubernetes
    proxy . 8.8.8.8
    log
    health
    errors
    cache
}

從配置檔案來看,我們定義了兩個 server(儘管有 4 個區塊),分別監聽在 530053 埠。其邏輯圖可如下所示:

每個進入到某個 server 的請求將按照 plugin.cfg 定義順序執行其已經載入的外掛。

從上圖,我們需要注意以下幾點:

  • 儘管在 .:53 配置了 health 外掛,但是它併為在上面的邏輯圖中出現,原因是:該外掛並未參與請求相關的邏輯(即並沒有在外掛鏈上),只是修改了 server 配置。更一般地,我們可以將外掛分為兩種:
    • Normal 外掛:參與請求相關的邏輯,且插入到外掛鏈中;
    • 其他外掛:不參與請求相關的邏輯,也不出現在外掛鏈中,只是用於修改 server 的配置,如 healthtls 等外掛;

4. 配置 CoreDNS


既然 CoreDNS 如此優秀,我用它來抵禦偉大的防火長城豈不美哉?研究了一圈,發現技術上還是可行的,唯一的一個缺點是不支援使用代理,不過你可以通過 proxychians-ngproxifier 來強制使用代理。下面開始折騰。

具體的思路其實非常簡單,就是將國內的域名查詢請求轉發到 114 等國內的公共 DNS 伺服器,將國外的域名查詢請求轉發到 8.8.8.8 等國外的公共 DNS 伺服器。然而 CoreDNS 的外掛鏈有點反直覺,同一個外掛鏈上的每一個外掛只能出現一次,如果只使用 forward 外掛是滿足不了需求的。

CoreDNS 原來還有個外掛叫 proxy,功能和 forward 類似,目測好像同時利用 proxyforward 外掛就可以實現我們的需求了。但理想與現實的差距總是很大,不知道從什麼時候開始,CoreDNS 官方編譯的二進位制檔案已經沒有 proxy 外掛了,真是氣人。

dnsredir

偶然間發現了一個第三方外掛 dnsredir,目測可以解決我的所有問題。該外掛綜合了 proxyforward 外掛的所有優點,支援 UDP、TCP、DNS-over-TLS 和 DNS-over-HTTPS,也支援多個後端,還具備健康檢查和故障轉移的功能,真是太香了!

它的語法是這樣的:

dnsredir FROM... {
    to TO...
}
  • FROM... 是一個檔案列表,包含了匹配的域名和解析該域名的伺服器,說白了就是 dnsmasq 所使用的格式,直接看例子:

    server=/0-100.com/114.114.114.114
    server=/0-100.com/114.114.114.114
    

    為什麼要用這種格式呢?當然是為了方便啦。

    為什麼這樣會方便呢?當然是為了可以直接用上 FelixOnMars的大陸區域名列表了。。。FelixOnMars 同時還提供了 GoogleApple 的域名列表,這在某些地區某些ISP可以得到國內映象的 IP,從而加速訪問,想想就刺激。

  • 當然,除了使用檔案列表外,還可以使用 .,類似於上面所說的根域。這個外掛最大的亮點是可以在外掛鏈中重複使用 dnsredir 外掛,只要 FROM... 不重複就行。

  • to TO... 用來將 DNS 解析請求發給上游 DNS 伺服器。支援幾乎所有 DNS 協議,例如:

    dns://1.1.1.1
    8.8.8.8
    tcp://9.9.9.9
    udp://2606:4700:4700::1111
    
    tls://1.1.1.1@one.one.one.one
    tls://8.8.8.8
    tls://dns.quad9.net
    
    doh://cloudflare-dns.com/dns-query
    json-doh://1.1.1.1/dns-query
    json-doh://dns.google/resolve
    ietf-doh://dns.quad9.net/dns-query
    

增強版 CoreDNS

dnsredir 雖香,但大家別忘了,它是第三方外掛,官方預設的二進位制檔案是不包含該外掛的。你可以選擇自己編譯,但如果經常需要升級怎麼辦?總不能每次都手動編譯吧,也太累了。

好在有位大佬已經通過 CI/CD 流程將所需的第三方外掛都整合編譯進去了,並定期更新,簡直就是我等的福音。大佬的專案地址為:

現在只需要下載對應作業系統的二進位制檔案,到處拷貝,就可以執行了。

下面統統以 MacOS 為例作講解。Openwrt 的玩法也一樣,參考本文的方法論即可,具體本文就不展開了。

直接下載二進位制檔案:

$ wget 'https://appveyorcidatav2.blob.core.windows.net/missdeer-15199/coredns-custom-build/1-7-1-514/idbodwxwywg1xgdg/distrib/coredns-linux-amd64.zip?sv=2015-12-11&sr=c&sig=BhMWcOVtDuaETyz2DcjpOr9GdvkpNVOqoIa7iWFpFNQ%3D&st=2020-12-23T15%3A26%3A19Z&se=2020-12-23T15%3A32%3A19Z&sp=r'
$ $ tar zxf coredns-linux-amd64.zip
$ mv coredns-linux-amd64/coredns /usr/local/bin/

配置

要深入瞭解 CoreDNS,請檢視其文件,及 plugins 的介紹。下面是我的配置檔案:

cat > /usr/local/etc/Corefile <<EOF
# https://coredns.io/plugins/cache/
(global_cache) {
    cache {
        # [5, 60]
        success 65536 3600 300
        # [1, 10]
        denial 8192 600 60
        prefetch 1 60m 10%
    }
}

.:7913  {
  ads {
      default-lists
      blacklist https://raw.githubusercontent.com/privacy-protection-tools/anti-AD/master/anti-ad-domains.txt
      whitelist https://files.krnl.eu/whitelist.txt
      log
      auto-update-interval 24h
      list-store ads-cache
  }
  errors
  hosts {
    fallthrough
  }
  health
  prometheus :9153

  import global_cache

  template ANY AAAA {
      rcode NXDOMAIN
  }

  dnsredir accelerated-domains.china.conf google.china.conf apple.china.conf mydns.conf {
      expire 15s
      max_fails 3
      health_check 3s
      policy round_robin
      path_reload 2s

      to 114.114.114.114 223.5.5.5 119.29.29.29
  }

  dnsredir . {
      expire 60s
      max_fails 5
      health_check 5s
      policy random
      spray

      to tls://8.8.8.8@dns.google tls://8.8.4.4@dns.google
      to tls://1.1.1.1@1dot1dot1dot1.cloudflare-dns.com tls://1.0.0.1@1dot1dot1dot1.cloudflare-dns.com
      # Global TLS server name
      # tls_servername cloudflare-dns.com
  }

  log
  loop
  reload 6s
}

EOF
  • hosts : hosts 是 CoreDNS 的一個 plugin,這一節的意思是載入 /etc/hosts 檔案裡面的解析資訊。hosts 在最前面,則如果一個域名在 hosts 檔案中存在,則優先使用這個資訊返回;
  • fallthrough : 如果 hosts 中找不到,則進入下一個 plugin 繼續。缺少這一個指令,後面的 plugins 配置就無意義了;
  • cache : 溯源得到的結果,快取指定時間。類似 TTL 的概念;
  • reload : 多久掃描配置檔案一次。如有變更,自動載入;
  • errors : 列印/儲存錯誤日誌;
  • dnsredir : 這是重點外掛。第一段 dnsredir 配置使用了 4 個檔案列表,均是 FelixOnMars的大陸區域名列表,這裡我還加了一個自定義的檔案列表 mydns.conf。第二段 dnsredir 配置表示預設的解析配置,可以理解為故障轉移,如果某個域名沒有匹配到任何一個檔案列表,就使用第二段 dnsredir 的上游 DNS 伺服器進行解析。通過這樣的配置方式,就實現了將國內的域名查詢請求轉發到 114 等國內的公共 DNS 伺服器,將國外的域名查詢請求轉發到 8.8.8.8 等國外的公共 DNS 伺服器。

講一下我自己的理解:

  1. 配置檔案類似於 nginx 配置檔案的格式;
  2. 最外面一級的大括號,對應『服務』的概念。多個服務可以共用一個埠;
  3. 往裡面一級的大括號,對應 plugins 的概念,每一個大括號都是一個 plugin。這裡可以看出,plugins 是 CoreDNS 的一等公民;
  4. 服務之間順序有無關聯沒有感覺,但 plugins 之間是嚴重順序相關的。某些 plugin 必須用 fallthrough 關鍵字流向下一個 plugin;
  5. plugin 內部的配置選項是順序無關的;
  6. plugins 頁面的介紹看,CoreDNS 的功能還是很強的,既能輕鬆從 bind 遷移,還能相容 old-style dns server 的運維習慣;
  7. 從 CoreDNS 的效能指標看,適合做大型服務。

注意:該方案的前提是能夠強制讓 CoreDNS 使用代理,或者更精確一點,讓 8.8.8.8 和 8.8.4.4 使用代理。這裡的方法比較複雜一點,本文就不介紹了。如果你實在不知道怎麼辦,可以將 8.8.8.8 這一行刪除,直接使用 Cloudflare 提供的 DNS 服務,雖然響應有點慢,但好在可以訪問。

如果你無法忍受 Cloudflare 的響應速度,可以考慮使用國內的無汙染 DNS:紅魚 DNS。然後直接一勞永逸:

cat > /usr/local/etc/Corefile <<EOF
# https://coredns.io/plugins/cache/
(global_cache) {
    cache {
        # [5, 60]
        success 65536 3600 300
        # [1, 10]
        denial 8192 600 60
        prefetch 1 60m 10%
    }
}

.:7913  {
  ads {
      default-lists
      blacklist https://raw.githubusercontent.com/privacy-protection-tools/anti-AD/master/anti-ad-domains.txt
      whitelist https://files.krnl.eu/whitelist.txt
      log
      auto-update-interval 24h
      list-store ads-cache
  }
  errors
  hosts {
    fallthrough
  }
  health
  prometheus :9153

  import global_cache

  template ANY AAAA {
      rcode NXDOMAIN
  }

  dnsredir accelerated-domains.china.conf google.china.conf apple.china.conf mydns.conf {
      expire 15s
      max_fails 3
      health_check 3s
      policy round_robin
      path_reload 2s

      to 114.114.114.114 223.5.5.5 119.29.29.29
  }
  
  dnsredir . {
      expire 60s
      max_fails 5
      health_check 5s
      policy random
      spray

      to doh://13800000000.rubyfish.cn
  }

  log
  loop
  reload 6s
}

EOF

這樣 CoreDNS 就不用擔心走代理的問題了。

定時更新國內域名列表

大陸域名列表每天都會更新,所以還需要寫個指令碼來更新檔案列表。不用檢查檔案是否存在了,直接簡單粗暴無腦更新:

$ cat > /usr/local/bin/update_coredns.sh <<EOF
#!/bin/bash

rm accelerated-domains.china.conf
wget https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf -O /usr/local/etc/accelerated-domains.china.conf
rm apple.china.conf
wget https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf -O /usr/local/etc/apple.china.conf
rm google.china.conf
wget https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf -O /usr/local/etc/google.china.conf
EOF
$ sudo chmod +x /usr/local/bin/update_coredns.sh

先執行一遍該指令碼,更新 Corefile 的配置:

$ /usr/local/bin/update_coredns.sh

然後通過 Crontab 製作定時任務,每隔兩天下午兩點更新域名列表:

$ crontab -l
0 14 */2 * * /usr/local/bin/update_coredns.sh

開機自啟

MacOS 可以使用 launchctl 來管理服務,它可以控制啟動計算機時需要開啟的服務,也可以設定定時執行特定任務的指令碼,就像 Linux crontab 一樣, 通過加裝 *.plist 檔案執行相應命令。Launchd 指令碼儲存在以下位置, 預設需要自己建立個人的 LaunchAgents 目錄:

  • ~/Library/LaunchAgents : 由使用者自己定義的任務項
  • /Library/LaunchAgents : 由管理員為使用者定義的任務項
  • /Library/LaunchDaemons : 由管理員定義的守護程式任務項
  • /System/Library/LaunchAgents : 由 MacOS 為使用者定義的任務項
  • /System/Library/LaunchDaemons : 由 MacOS 定義的守護程式任務項

我們選擇在 /Library/LaunchAgents/ 目錄下建立 coredns.plist 檔案,內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>coredns</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/local/bin/coredns</string>
      <string>-conf</string>
      <string>/usr/local/etc/Corefile</string>
    </array>
    <key>StandardOutPath</key>
    <string>/var/log/coredns.stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/coredns.stderr.log</string>
    <key>KeepAlive</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

設定開機自動啟動 coredns:

$ sudo launchctl load -w /Library/LaunchAgents/coredns.plist

檢視服務:

$ sudo launchctl list|grep coredns

61676	0	coredns
$ sudo launchctl list coredns

{
	"StandardOutPath" = "/var/log/coredns.stdout.log";
	"LimitLoadToSessionType" = "System";
	"StandardErrorPath" = "/var/log/coredns.stderr.log";
	"Label" = "coredns";
	"TimeOut" = 30;
	"OnDemand" = false;
	"LastExitStatus" = 0;
	"PID" = 61676;
	"Program" = "/usr/local/bin/coredns";
	"ProgramArguments" = (
		"/usr/local/bin/coredns";
		"-conf";
		"/usr/local/etc/Corefile";
	);
};

檢視埠號:

$ sudo ps -ef|egrep -v grep|grep coredns

    0 81819     1   0  2:54下午 ??         0:04.70 /usr/local/bin/coredns -conf /usr/local/etc/Corefile
    
$ sudo lsof -P -p 81819|egrep "TCP|UDP"

coredns 81819 root    5u    IPv6 0x1509853aadbdf853      0t0     TCP *:5302 (LISTEN)
coredns 81819 root    6u    IPv6 0x1509853acd2f39ab      0t0     UDP *:5302
coredns 81819 root    7u    IPv6 0x1509853aadbdc493      0t0     TCP *:53 (LISTEN)
coredns 81819 root    8u    IPv6 0x1509853acd2f5a4b      0t0     UDP *:53
coredns 81819 root    9u    IPv6 0x1509853ac63bfed3      0t0     TCP *:5301 (LISTEN)
coredns 81819 root   10u    IPv6 0x1509853acd2f5d03      0t0     UDP *:5301

大功告成,現在你只需要將系統的 DNS IP 設定為 127.0.0.1 就可以了。

驗證

$ doggo www.youtube.com @udp://127.0.0.1

NAME                    	TYPE 	CLASS	TTL 	ADDRESS                 	NAMESERVER
www.youtube.com.        	CNAME	IN   	293s	youtube-ui.l.google.com.	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.14.110          	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.11.174          	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.5.206           	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.5.78            	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.14.78           	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	142.250.72.238          	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	216.58.193.206          	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	142.250.68.110          	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	142.250.68.78           	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	172.217.4.142           	127.0.0.1:53
youtube-ui.l.google.com.	A    	IN   	293s	142.250.68.14           	127.0.0.1:53

搞定。

什麼?你問我 doggo 是個啥?掃描下方二維碼關注公眾號:

公眾號後臺回覆 doggo 即可獲取你想要的東西?

5. 參考資料



Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包釋出地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs載入問題, 修復lvscare社群netlink與3.10核心不相容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經整合sealos的機器人實時可以看到sealos的動態。

相關文章