前言

习惯了 laravel/valet 的方便后,在使用 Docker 搭建的开发环境中,经常需要再本地 /etc/hosts 文件中给项目设置本地域名。这样会导致一个问题,那就是每个人自己配置的域名可能不同,而很多时候成员间需要通过 URL 来交流和复现问题。

比如 A 开发给 CRM 项目的域名是 crm.localhost,B 开发给 CRM 项目的域名是 crm.dev 这样导致我们公用的 API 调试器中需要为每个人定义一套环境,与此同时在复现问题时,也增加了一定的沟通成本。

那么为了尽可能的保持环境的一致性,我们将服务的访问域名也通过配置文件内置于 Docker 的 Compose 编排中,这样一来,每个人不需要做什么,只需要执行 docker compose up -d 就能够得到一致的开发环境,包括域名和 HTTPS 证书等!

选型

为了实现高度统一的愿景,我调研了如下一些开源项目:

  • dnsmasq
  • CoreDNS
  • AdGuardDNS

因为 laravel/valet 使用的也是 dnsmasq,所以我优先考虑选择 dnsmasq,但是扒了一圈,没有找到官方维护的 Docker Image,有一些个人开发者维护的版本,想想还是算了!

后来发现 CoreDNS 基于 Go 语言开发,且是 Cloud Native 孵化的毕业项目,后期的社区肯定是没的说。简单试用了了一下发现基本符合我们的需求,支持通配符解析。

AdGuard Home 算是一个意料之外的收货,为什么这么说呢,因为它面向的是普通用户,而不是开发者,提供了简洁美观的管理界面。但是不太适合我们的地方在于,它主要是用来拦截广告的,并且配置需要登录管理后台配置自定义域名的重写,也支持通配符解析。

经过对比我们最终选择了 CoreDNS,一方面是更强大的社区、丰富的插件以及可通过配置文件保证一致性。

获取镜像

因为我们团队使用的都是基于 ARM 架构的 M1 Chip,而官方目前并未提供 ARM 架构的 Docker Image,所以我们使用的是自己构建了一个 ARM 架构的 Docker Image。

docker pull betterde/coredns

其他架构 CPU 可以拉取 CoreDNS 官方 Docker Image

配置

首先创建 docker-compose.yml 容器编排配置文件:

services:
  coredns:
    image: betterde/coredns:latest
    ports:
      - 53:53/udp
    restart: always
    command: -conf /root/Corefile
    volumes:
      - ./config:/root
    hostname: coredns
    container_name: coredns

新版的 Docker Compose 不再需要定义 version 了。

接着创建 CoreDNS 的配置文件夹:

mkdir config

例如我我们将 betterde.it 这个 TLD 作为本地开发环境的域名,并在文件夹下创建 Corefile 配置文件:

.:53 {
    forward . 223.5.5.5 223.6.6.6
    log
    errors
}

betterde.it:53 {
    file /root/betterde.it.db
    log
    loop
    reload
    errors
}

最后创建一个用于定义域名解析的数据文件 betterde.it.db

betterde.it.        IN  SOA dns.betterde.it. george.example.com. 2017042745 7200 3600 1209600 3600
*.betterde.it.      IN  A   127.0.0.1
dns.betterde.it.    IN  A   127.0.0.1
host.betterde.it.   IN  A   192.168.1.10
server.betterde.it. IN  CNAME   host.betterde.it.

如果你配置过 BIND 的域名服务器 zone,对上面的 betterde.it.db 配置文件应该并不陌生,其中 george.example.com. 是管理者邮箱,因为 @zone 配置文件中有其他用户,所以,这里用 . 替代。

启动服务

docker compose up -d

验证结果

dig @127.0.0.1 dns.betterde.it

; <<>> DiG 9.10.6 <<>> @127.0.0.1 dns.betterde.it
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37167
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;dns.betterde.it.			IN	A

;; ANSWER SECTION:
dns.betterde.it.		0	IN	A	127.0.0.1

;; Query time: 21 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 15:49:28 CST 2022
;; MSG SIZE  rcvd: 67

使用 CoreDNS

在 macOS 上使用自己的 DNS 可以通过如下步骤来管理命令来进行配置:

  • 查看系统 DNS 优先级
scutil --dns
DNS configuration

resolver #1
  nameserver[0] : 223.6.6.6
  nameserver[1] : 202.46.34.75
  if_index : 14 (en0)
  flags    : Request A records
  reach    : 0x00000002 (Reachable)

resolver #2
  domain   : local
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300000

resolver #3
  domain   : 254.169.in-addr.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300200

resolver #4
  domain   : 8.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300400

resolver #5
  domain   : 9.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300600

resolver #6
  domain   : a.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300800

resolver #7
  domain   : b.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 301000

resolver #8
  domain   : it
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

DNS configuration (for scoped queries)

resolver #1
  nameserver[0] : 223.6.6.6
  nameserver[1] : 202.46.34.75
  if_index : 14 (en0)
  flags    : Scoped, Request A records
  reach    : 0x00000002 (Reachable)

可以看到现在使用的是阿里云 DNS 223.6.6.6 和电信 202.46.34.75 作为 DNS 的。

  • 列出所有的网络连接方式:
networksetup -listallnetworkservices
An asterisk (*) denotes that a network service is disabled.
USB-LAN
Wi-Fi
Thunderbolt Bridge
iPhone USB
GAVPN
Tailscale Tunnel
  • 给指定的网络连接方式设定 DNS 服务器
networksetup -setdnsservers Wi-Fi 127.0.0.1

例如我这里是通过 Wi-Fi 联网的,所以我这里给 Wi-Fi 设备设置 DNS 查询走本地 Loopback 地址 127.0.0.1

  • 验证结果
scutil --dns
DNS configuration

resolver #1
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

resolver #2
  domain   : local
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300000

resolver #3
  domain   : 254.169.in-addr.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300200

resolver #4
  domain   : 8.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300400

resolver #5
  domain   : 9.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300600

resolver #6
  domain   : a.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 300800

resolver #7
  domain   : b.e.f.ip6.arpa
  options  : mdns
  timeout  : 5
  flags    : Request A records
  reach    : 0x00000000 (Not Reachable)
  order    : 301000

resolver #8
  domain   : it
  nameserver[0] : 127.0.0.1
  flags    : Request A records, Request AAAA records
  reach    : 0x00030002 (Reachable,Local Address,Directly Reachable Address)

DNS configuration (for scoped queries)

resolver #1
  nameserver[0] : 127.0.0.1
  if_index : 14 (en0)
  flags    : Scoped, Request A records, Request AAAA records
  reach    : 0x00000000 (Not Reachable)

通过上面的结果可以看到,现在使用的是 127.0.0.1 作为 DNS 的。

总结

dig www.baidu.com

; <<>> DiG 9.10.6 <<>> www.baidu.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40287
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.baidu.com.			IN	A

;; ANSWER SECTION:
www.baidu.com.		196	IN	CNAME	www.a.shifen.com.
www.a.shifen.com.	196	IN	A	180.101.49.13
www.a.shifen.com.	196	IN	A	180.101.49.14

;; Query time: 22 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 16:02:29 CST 2022
;; MSG SIZE  rcvd: 149

dig crm.betterde.it

; <<>> DiG 9.10.6 <<>> crm.crm.betterde.it
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 28646
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;crm.crm.betterde.it.			IN	A

;; ANSWER SECTION:
crm.crm.betterde.it.		0	IN	A	127.0.0.1

;; Query time: 5 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Mon Nov 28 16:04:42 CST 2022
;; MSG SIZE  rcvd: 67

到此,一个小而全的 DNS 就搭建完成了,而它将成为我们团队协作的一个必要基础设施!

如果需要取消使用 CoreDNS 可以使用如下命令:

# 清空设备当前的 DNS 设置
networksetup -setdnsservers Wi-Fi empty

# 检查设备当前 DNS 设置
networksetup -getdnsservers Wi-Fi

最后清理 DNS 缓存

dscacheutil -flushcache

I hope this is helpful, Happy hacking…