DNS 的原理相信大家都了解。树形结构,根服务器,递归溯源,UDP 协议(现在也有 TCP 协议甚至 http 协议的)。搭建一台自己的 DNS 也是稀松平常的事情。

我遇到的场景是这样的。

  1. 公司有内网机房,研发用,研发环境和测试环境都在内网机房;
  2. 公司的域名是『company.com』,在公司内网有专门的 DNS(bind 搭建)做解析;
  3. 研发/测试环境的服务器也用顶级域名指向,例如:test1.mod-a.company.com。这类解析都是通过 bind 实现的。公司外网解析不到这个地址;
  4. 我自己需要一个安全的 DNS 环境,对 DNS 服务器溯源这个细节,优选 TCP 协议;
  5. 对『company.com』顶级域名的解析还是走公司内部的 DNS 服务器,即 bind;
  6. 之前用 ss 的 chinadns,可以实现第 4 条,但是无法实现第 5 条。

找了一圈,发现 CoreDNS 挺好的。推荐之。

一、安装

CoreDNS 是 golang 写的,所以只需要下载对应操作系统的二进制文件,到处拷贝,就可以运行了。

下面统统以 MacOS 为例作讲解。

cd ~/Downloads
curl -LO "https://github.com/coredns/coredns/releases/download/v1.1.2/coredns_1.1.2_darwin_amd64.tgz" && \
tar zxf coredns_1.1.2_darwin_amd64.tgz && \
mv ./coredns /usr/local/bin/

这里补充一句,CoreDNS 的二进制版本已经安装了所有的插件(plugins),不需要你自己编译。推荐下载二进制版本。

二、配置

要深入了解 CoreDNS,请查看其文档,及 plugins 的介绍

cat <<EOF > /usr/local/etc/Corefile
. {
  hosts {
    fallthrough
  }

  forward . 1.1.1.1 8.8.8.8 119.29.29.29 223.5.5.5 {
    force_tcp
    max_fails 3
    expire 10s
    health_check 5s
    policy sequential
    except company.com
  }

  cache 120
  reload 6s
  log
  errors
}

company.com {
  hosts {
    fallthrough
  }

  forward . 192.168.88.101 119.29.29.29 114.114.114.114 {
    max_fails 3
    expire 5s
    health_check 3s
    policy sequential
  }
}
EOF

其中 192.168.88.101 是公司内网 DNS 服务器(bind)的 IP 地址。

对配置中的一些选项稍作解释。

  • hostshosts 是 CoreDNS 的一个 plugin,这一节的意思是加载 /etc/hosts 文件里面的解析信息。hosts 在最前面,则如果一个域名在 hosts 文件中存在,则优先使用这个信息返回;
  • fallthrough:如果 hosts 中找不到,则进入下一个 plugin 继续。缺少这一个指令,后面的 plugins 配置就无意义了;
  • forward:这是另外一个 plugin。. 代表所有域名,后面的 IP 代表上游 DNS 服务器的列表。按照什么顺序溯源,由下面的 policy 指令决定;
  • force_tcp:强制使用 TCP 协议溯源。这要求上游 DNS 必须支持 TCP 协议;
  • expect:指定哪些域名不按照本 plugin 配置溯源;
  • cache:溯源得到的结果,缓存指定时间。类似 TTL 的概念;
  • reload:多久扫描配置文件一次。如有变更,自动加载;
  • log:打印/存储访问日志;
  • errors:打印/存储错误日志;
  • company.com { }:另外一个『服务』,只服务针对『company.com』的域名解析;

我讲一下我自己的理解。

  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 的性能指标看,适合做大型服务。

三、运行

# 前台运行方式
/usr/local/bin/coredns -conf /usr/local/etc/Corefile

NOTE(simon): 新增 -log 参数,将日志打到 stdout,日志集中处理。

UPDATE(simon): 更新 coredns 到最新的 1.2.2 版本,已经没有 -log 这个参数了。日志默认也是直接打到 stdout 上面。

如果没有问题,这时候应该看到 CoreDNS 持续运行。

四、部署

在用 chinadns 的时候,遇到过 chinadns 崩掉的情况。作为基础服务,DNS 还是要能稳定持续提供服务的。此外,开机自动启动也是个必要的功能。

综合考虑,熟悉的 supervisor 是个好的选择。

设置服务配置目录

mkdir -p /usr/local/etc/supervisord.ini.d

修改 supervisor 配置

sed -i .bak ‘s@files = .@files = /usr/local/etc/supervisord.ini.d/.ini@g’ /usr/local/etc/supervisord.ini

为 supervisorctl 做映射

alias supervisorctl=’/usr/local/bin/supervisorctl -c /usr/local/etc/supervisord.ini’ cat «EOF » ~/.bash_profile alias supervisorctl=’/usr/local/bin/supervisorctl -c /usr/local/etc/supervisord.ini’ EOF

为 CoreDNS 写配置文件

cat «EOF > /usr/local/etc/supervisord.ini.d/coredns.ini [program:coredns] command=/usr/bin/sudo /usr/local/bin/coredns -conf /usr/local/etc/Corefile ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) numprocs=1 ; number of processes copies to start (def 1) directory=/usr/local ; directory to cwd to before exec (def no cwd) ;umask=022 ; umask for process (default None) ;priority=999 ; the relative start priority (default 999) autostart=true ; start at supervisord start (default: true) autorestart=unexpected ; whether/when to restart (default: unexpected) startsecs=1 ; number of secs prog must stay running (def. 1) startretries=9999 ; max # of serial start failures (default 3) ;exitcodes=0,2 ; ‘expected’ exit codes for process (default 0,2) stopsignal=QUIT ; signal used to kill process (default TERM) ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) ;stopasgroup=false ; send stop signal to the UNIX process group (default false) ;killasgroup=false ; SIGKILL the UNIX process group (def false) ;user=chrim ; setuid to this UNIX account to run the program ;redirect_stderr=true ; redirect proc stderr to stdout (default false) stdout_logfile=/usr/local/var/log/supervisor/coredns.stdout.log ; stdout log path, NONE for none; default AUTO stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) stdout_logfile_backups=10 ; # of stdout logfile backups (default 10) stdout_capture_maxbytes=1MB ; number of bytes in ‘capturemode’ (default 0) stdout_events_enabled=false ; emit events on stdout writes (default false) stderr_logfile=/usr/local/var/log/supervisor/coredns.stderr.log ; stderr log path, NONE for none; default AUTO stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) stderr_logfile_backups=10 ; # of stderr logfile backups (default 10) stderr_capture_maxbytes=1MB ; number of bytes in ‘capturemode’ (default 0) stderr_events_enabled=false ; emit events on stderr writes (default false) ;environment=A="1”,B="2” ; process environment additions (def no adds) ;serverurl=AUTO ; override serverurl computation (childutils)

EOF

设置 supervisord 开机自启动

brew service start supervisor

查看 CoreDNS 是否正常运行

supervisorctl status

</strike>

> NOTE(simon, 2018-05-30):
>   放弃使用 supervisor 的原因是,MacOS 作为生产力工具,supervisor 最友好方式的安装还是通过 brew。而 brew 安装的 supervisor 启动时,是当前用户权限,不是 root 身份。试了下,也没办法通过配置,提权到 root。再则,supervisor 的配置中,大量文件写到 `/usr/local` 目录下,如果以 root 身份启动 supervisord 进程,这些文件势必也是 root 权限的。而按照 brew 的指导意见,`/usr/local` 下还是要保留当前用户权限的,否则 brew 可能会挂。
>   所以兜兜转换,又用回到老的土办法。毕竟,稳定就好。

<strike>
```bash
cat <<EOF > ~/.bash_profile
# CoreDNS
COREDNS_PROCESS_COUNT=$(ps aux | grep coredns | grep -v grep | wc -l)
[ ${COREDNS_PROCESS_COUNT} -lt 1 ] && \
    nohup sudo /usr/local/bin/coredns -conf /usr/local/etc/Corefile &
EOF

NOTE(simon, 2018-11-20): 更新一下脚本,实现下面几个功能:

  1. coredns 进程的 stderr 输出转到 stdout;
  2. coredns 进程不绑定到当前 tty 上,运行 jobs 命令不显示 coredns 进程;
  3. 显式指定 nohup 将 coredns 进程的 stdout 输出打到 ~/nohup.out 文件;
COREDNS_PROCESS_COUNT=$(ps aux | grep coredns | grep -v grep | wc -l)
[ ${COREDNS_PROCESS_COUNT} -lt 1 ] && \
  (2>&1 nohup sudo /usr/local/bin/coredns -conf /usr/local/etc/Corefile >> ~/nohup.out &)

五、验证

nslookup www.qq.com 127.0.0.1
nslookup www.facebook.com 127.0.0.1
nslookup test1.mod-a.company.com 127.0.0.1

公司内网 IP 几乎秒出。非死不可也能出。

搞定。

六、系统设置

在『系统偏好设置』->『网络』中,把 DNS 里面添加一行,127.0.0.1,并把新增这项移动到最前面,即可。