前言
要做好运维自动化,内网域名的自动化管理不可少,得提供丰富的API 供其他内部系统调用。
之前在网易杭研实习,见过他们管理内部的域名,还是手工编辑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 来解决掉。
另外可参考:
DNS 查询走UDP 协议,前端转发这块一般用LVS + Keepalived 之类的做高可用,当然用最近出的Nginx UDP Stream 也可以,不过性能上差很多。
同时也需要优化一下服务器网络UDP 相关的参数。
pnotify.pl
pnotify - A simple, portable Perl script for sending DNS NOTIFY packets with TSIG support.
使用这个脚本,在每次对域名记录做更改之后都对几台Slave发送一下notify 请求。
/etc/resolv.conf
把内网NS 加入到/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 字段的值。
作为Slave 的Bind 强烈建议使用包管理系统直接安装。如果出现安全问题,Slave 是直接对外提供服务的,需要快速修复,直接 aptitude|yum 更新会方便和快速很多(相信上游仓库打包者的速度)。
另外在投入使用之前,搭建者应该已经阅读过官方的手册,BIND 9 Documentation。
安装配置好之后,如果NS 要开放在公网访问,推荐使用intoDNS 进行检测,可以发现一些细节问题。另外还可以使用DNS Spy 对安全性做一番检查。
开发的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 版本:
#!/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
#author: fangpeishi
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