前言
要做好運維自動化,內網域名的自動化管理不可少,得提供豐富的API 供其他內部系統調用。
之前在網易杭研實習,見過他們管理內部的域名,還是手工編輯bind 的zone 文件,苦不堪言,改一個得反覆檢查和找人Review,因爲zone 如果出現一絲錯誤,會導致大量域名解析出錯。一個方式是用DNSPod,CloudXNS等,這些服務商一般都有完善的API 支持,但是這樣內網域名就暴露在公網,可被人暴力遍歷,爲滲透之類的提供信息,所以內網自己搭建一個權威DNS 服務器是更好的選擇。
之前在網易杭研實習,見過他們管理內部的域名,還是手工編輯bind 的zone 文件,苦不堪言,改一個得反覆檢查和找人Review,因爲zone 如果出現一絲錯誤,會導致大量域名解析出錯。一個方式是用DNSPod,CloudXNS等,這些服務商一般都有完善的API 支持,但是這樣內網域名就暴露在公網,可被人暴力遍歷,爲滲透之類的提供信息,所以內網自己搭建一個權威DNS 服務器是更好的選擇。
Name server 的選擇上,有很多選擇,如Bind, PowerDNS, NSD 等。PowerDNS 對MySQL 有原生支持, 性能上相對Bind DLZ MySQL 會強很多,並且有非常多開源的WebFrontend 支持。不過我最終還是選擇了Bind DLZ,性能的問題可以通過後文提到的架構解決掉,並且還可以省去對 MySQL 之類的優化工作,另外自己寫一個對數據庫的CRUD 操作的Web 界面並不困難,工作量也並不大。
性能與架構
Bind DLZ 官方提供了一份Performance Tests,雖然是比較老的測試了,但是還是有不少參考價值。
我自己使用queryperf 壓測,結果與上圖中類似,基本來說Bind DLZ MySQL 相比Bind 原生的方式,QPS 差20倍左右,因爲MySQL 只能跑在單線程下。由於這個原因,建議採用Bind DLZ 作爲Master,Bind 作爲Slave的方式。多個Slave 結合LVS 做高可用和負載均衡, Master可以另外針對Bind DLZ 和MySQL 做高可用,這樣的設計,可以發揮Bind 原生的高性能,也可以利用Bind DLZ 的靈活性。
但是這樣會帶來一個問題,在Master 上修改完記錄之後,可能不會立即同步到Slave,會帶來不一致的問題。不過這個問題可以通過自動發送Notify 來解決掉。
但是這樣會帶來一個問題,在Master 上修改完記錄之後,可能不會立即同步到Slave,會帶來不一致的問題。不過這個問題可以通過自動發送Notify 來解決掉。
另外可參考:
DNS 查詢走UDP 協議,前端轉發這塊一般用LVS + Keepalived 之類的做高可用,當然用最近出的Nginx UDP Stream 也可以,不過性能上差很多。
同時也需要優化一下服務器網絡UDP 相關的參數。
同時也需要優化一下服務器網絡UDP 相關的參數。
pnotify.pl
pnotify - A simple, portable Perl script for sending DNS NOTIFY packets with TSIG support.
使用這個腳本,在每次對域名記錄做更改之後都對幾臺Slave發送一下notify 請求。
/etc/resolv.conf
把內網NS 加入到
另外需要設置一下一次查詢的超時時間,默認是5s。如果某個服務處理過程中涉及到大量的域名查詢,如果resolv.conf 中某一個nameserver 異常,默認的30s 超時將會導致請求大量堆積。建議改小timeout 的值,特別NS 在同一個內網的情況下。
更多配置細節請參考:resolv.conf - resolver configuration file
/etc/resolv.conf
之前需要注意一些事情,一般來說resolv.conf 會有多個nameserver,默認情況下會從上到下發送域名解析請求,當然可以配置成輪詢(options:rotate)。建議是多個ns slave 爲一組,一組有一個lvs 做高可用,將多個lvs的vip 分成多行寫到resolv.conf 中,然後配置options: rotate,開啓輪詢。另外需要設置一下一次查詢的超時時間,默認是5s。如果某個服務處理過程中涉及到大量的域名查詢,如果resolv.conf 中某一個nameserver 異常,默認的30s 超時將會導致請求大量堆積。建議改小timeout 的值,特別NS 在同一個內網的情況下。
更多配置細節請參考:resolv.conf - resolver configuration file
安裝配置
Bind 的版本選擇上,建議使用官方推薦的stable 版本。DLZ 需要自行編譯安裝,官方的Bind 源碼包裏已帶DLZ 的相關代碼,編譯時開啓對應選項即可。MySQL 的表設計直接參考官網內容,和MySQL_Example。
建議設置一下MySQL 的trigger,自動更新SOA 記錄的serial 字段的值。
建議設置一下MySQL 的trigger,自動更新SOA 記錄的serial 字段的值。
作爲Slave 的Bind 強烈建議使用包管理系統直接安裝。如果出現安全問題,Slave 是直接對外提供服務的,需要快速修復,直接 aptitude|yum 更新會方便和快速很多(相信上游倉庫打包者的速度)。
另外在投入使用之前,搭建者應該已經閱讀過官方的手冊,BIND 9 Documentation。
開發的Web 前端在數據輸入上必須做好校驗,空格和異常字符等要檢測和處理掉,域名需要符合規範(只能包含數字,字母,連接符,點號等,細節可以Google下),不然某條記錄出錯,依然可能導致大量域名無法解析。
安全
- 關注官方的 Security Advisories,RSS訂閱;
- 隱藏版本:options 中自定義 version;
- chroot,另外使用非root 賬戶跑bind 服務;
- 限制請求,利用ACL 限制查詢來源,如果開放在公網最好關閉遞歸查詢,防止被用於DNS 放大攻擊;
- 控制好域傳送,配置allow-transfer;
- 控制好allow-notify;
- 控制好allow-update;
- 使用DNSSEC;
- 等等等。
備份
- MySQL 備份;
- Slave 的zone 文件備份,方便快速恢復;
- 全部域名記錄可以選擇定期備份到DNSPod 或者CloudXN 之類的,以防萬一。
監控
- Bind 進程監控;
- Bind 端口監控;
- Bind 解析功能監控;
- Bind 各類請求量和響應監控等。
Nagios 有一個開源的插件可以使用:check_bind.sh,不過很老了,可能需要自己改改,使用rndc 這個命令來獲取Bind 的狀態,採點繪圖。
上面的腳本簡單改改,可用於OpenFalcon,Bind9.9 版本:
上面的腳本簡單改改,可用於OpenFalcon,Bind9.9 版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | #!/usr/bin/env bash
#check bind status for falcon agent
#fork from https://exchange.nagios.org/directory/Plugins/Network-Protocols/DNS/check_bind-2Esh/details
ST_OK=0
ST_WR=1
ST_CR=2
ST_UK=3
path_pid=""
name_pid="named.pid"
path_rndc=""
path_stats=""
path_tmp=""
pid_check=1
HOST=`hostname`
DATE=`date +%s`
check_pid() {
if [ -f "$path_pid/$name_pid" ]
then
retval=0
else
retval=1
fi
}
trigger_stats() {
if [ -n "$path_chroot" ]
then
sudo chroot $path_chroot $path_rndc/rndc stats
else
sudo $path_rndc/rndc -c /named/etc/rndc.conf stats
fi
}
copy_to_tmp() {
tac $path_stats/named_stats.txt | awk '/--- \([0-9]*\)/{p=1} p{print} /\+\+\+ \([0-9]*\)/{p=0;if (count++==1) exit}' > $path_tmp/named.stats.tmp
}
get_vals() {
succ_1st=`grep 'resulted in successful answer' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
succ_2nd=`grep 'resulted in successful answer' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
ref_1st=`grep 'resulted in referral' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
ref_2nd=`grep 'resulted in referral' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
nxrr_1st=`grep 'resulted in nxrrset' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
nxrr_2nd=`grep 'resulted in nxrrset' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
nxdom_1st=`grep 'resulted in NXDOMAIN' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
nxdom_2nd=`grep 'resulted in NXDOMAIN' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
rec_1st=`grep 'caused recursion' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
rec_2nd=`grep 'caused recursion' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
fail_1st=`grep 'resulted in SERVFAIL' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
fail_2nd=`grep 'resulted in SERVFAIL' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
dup_1st=`grep 'duplicate queries received' $path_tmp/named.stats.tmp | awk '{ print $1 }' | grep -m1 ''`
dup_2nd=`grep 'duplicate queries received' $path_tmp/named.stats.tmp | awk '{ print $1 }' | sort -n | grep -m1 ''`
if [ "$succ_1st" == '' ]
then
success=0
else
success=`expr $succ_1st - $succ_2nd`
fi
if [ "$ref_1st" == '' ]
then
referral=0
else
referral=`expr $ref_1st - $ref_2nd`
fi
if [ "$nxrr_1st" == '' ]
then
nxrrset=0
else
nxrrset=`expr $nxrr_1st - $nxrr_2nd`
fi
if [ "$nxdom_1st" == '' ]
then
nxdomain=0
else
nxdomain=`expr $nxdom_1st - $nxdom_2nd`
fi
if [ "$rec_1st" == '' ]
then
recursion=0
else
recursion=`expr $rec_1st - $rec_2nd`
fi
if [ "$fail_1st" == '' ]
then
failure=0
else
failure=`expr $fail_1st - $fail_2nd`
fi
if [ "$dup_1st" == '' ]
then
duplicate=0
else
duplicate=`expr $dup_1st - $dup_2nd`
fi
if [ "$drop_1st" == '' ]
then
dropped=0
else
dropped=`expr $drop_1st - $drop_2nd`
fi
}
falcon() {
echo -n "{\"metric\": \"$1\", \"endpoint\": \"$HOST\", \"tags\": \"\", "
echo -n "\"value\": $2,"
echo -n " \"timestamp\": $DATE, \"counterType\": \"GAUGE\", \"step\": 60}"
}
get_perfdata() {
echo -n "["
falcon "caused_recursion" $recursion
echo -n ","
falcon "duplicate_queries_received" $duplicate
echo -n ","
falcon "failure_responses" $failure
echo -n ","
falcon "nxdomain_responses" $nxdomain
echo -n ","
falcon "nxrrset_responses" $nxrrset
echo -n ","
falcon "referral_responses" $referral
echo -n ","
falcon "success_responses" $success
}
if [ ${pid_check} == 1 ]
then
check_pid
if [ "$retval" = 1 ]
then
echo -n "["
falcon "check_bind" 1
echo -n "]"
exit $ST_CR
fi
fi
trigger_stats
copy_to_tmp
get_vals
get_perfdata
echo -n ","
falcon "check_bind" 0
echo -n "]"
Exit $ST_OK
|
作為indns和DNS間諜的替代方法,您還可以參考
ReplyDeletednschecker.org