容器化dns服务

容器化dns服务

之前将一个小应用使用docker compose部署之后,遇到一个问题,修改域名解析的IP。之前在虚拟机上可以直接修改hosts文件,
在docker容器中修改就比较麻烦,修改主机hosts文件也没有效果。

为了解决这个问题,引入了dnsmasq作为dns服务器。由于主机上本身已经有docker环境,因此也打算把dnsmasq放到容器中去运行。
首先是选择容器,在docker hub上下载量最多的是 andyshinn/dnsmasq 镜像。这个镜像的最大优势就是简单,整个镜像只有6M多。
对于无法访问外网的docker主机来说,部署比较方便。当然,这个镜像最大的劣势也是简单,没有提供配置文件,也无法可视化配置,使用时需要对dnsmasq有一定的了解。本文后面会介绍到刚开始使用遇到的不方便的地方。

安装

由于真正部署这个镜像的服务器无法访问外网,因此不能直接拉去docker镜像,暂时在一台可以访问外网的机器上拉取,然后导出,最后在机器上重新加载的方式来进行安装。也就是

  1. docker pull andyshinn/dnsmasq
  2. docker save andyshinn/dnsmasq > dnsmasq.tar
  3. scp dnsmasq.tar admin@xxx:.
  4. docker load < dnsmasq.tar

这样镜像就算是安装完成了。

启动

启动镜像前,参考了该镜像文档中的启动命令:

docker run -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN andyshinn/dnsmasq -S /consul/10.17.0.2

同时,看了下这个镜像的Dockerfile:

FROM alpine:3.2
RUN apk -U add dnsmasq
EXPOSE 53 53/udp
ENTRYPOINT ["dnsmasq", "-k"]

可以发现,这个镜像真是简单至极。默认就是运行了dnsmasq -k,即强制让dnsmasq运行在前端。然后dnsmasq没有任何特殊的配置,都需要启动过的时候手工指定。

而示例中的启动命令,有这几个含义:

  • 将容器的53端口(TCP和UDP)映射到主机上,这是dns的标准端口
  • 增加NET_ADMIN参数,使得容器中的进程可以修改网络配置
  • 给dnsmasq传递参数,针对.consul后缀的域名,使用10.17.0.2这个上游dns服务器进行解析

这样的启动方式,当然是无法满足需求的。dnsmasq命令的参数,可以在这个文档中进行查询。如果仅仅是解析一个域名,可以通过--address参数指定,域名和IP格式和上面类似:

docker run -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN andyshinn/dnsmasq --address /api.test.com/127.0.0.1

这样容器启动之后,就可以解析api.test.com这个域名到127.0.0.1。

应用

启动容器还是比较方便,然后就是将这个dns服务提供给其他docker容器使用。docker在启动参数中支持通过--dns指定dns服务器地址(指定后,会修改启动容器的/etc/resolv.conf文件)。如果在docker-compose中使用,可以在yml文件中增加dns项。

这样,容器启动之后,就可以使用自己搭建的dns服务了。然而,并没有~

NAT模式下dns服务的失效

首先遇到的问题,是容器中的服务仍然无法解析域名。使用docker exec命令进入容器,ping了域名还是提示无法解析。排查dnsmasq的日志没有输出。

由于dnsmasq解析日志不太好排查,查询了手册之后发现,可以通过--log-facility参数指定日志输出,将容器启动参数改成:

docker run -p 53:53/tcp -p 53:53/udp --cap-add=NET_ADMIN andyshinn/dnsmasq --address /api.test.com/127.0.0.1 -q --log-facility=-

这样可以将dnsmasq的日志输出到标准错误,也就能够在当前命令行中查看到。(如果在docker run的时候使用了-d参数放到了后台执行,可以直接通过docker logs来查看输出)

再次到应用容器中ping域名,会发现dnsmasq实际上已经完成了域名解析,但是域名解析的来源IP都是docker0设备的IP。如果将应用容器的dns服务器地址,修改成dnsmasq容器的内网地址,则解析正常。因此判断这个解析错误,可能和两个容器都运行在NAT模式中有关。

将dns容器改成host模式:

docker run --net=host --cap-add=NET_ADMIN andyshinn/dnsmasq --address /api.test.com/127.0.0.1 -q --log-facility=-

在将应用容器的dns服务器地址修改成主机的IP,可以发现能够正常解析域名。

dns解析自动更新

按照上面方式启动dns容器,可以正常工作。但是如果要增加解析记录,需要重新启动这个容器。文档里面有提到dnsmasq可以通过向进程发送SIGHUP信号,重新加载文件:

When it receives a SIGHUP, dnsmasq clears its cache and then re-loads /etc/hosts and /etc/ethers and any file given by --dhcp-hostsfile, --dhcp-hostsdir, --dhcp-optsfile, --dhcp-optsdir, --addn-hosts or --hostsdir. The dhcp lease change script is called for all existing DHCP leases. If --no-poll is set SIGHUP also re-reads /etc/resolv.conf. SIGHUP does NOT re-read the configuration file.

也就是说,如果通过--addn-hosts或者--hostsdir参数指定的文件(夹),可以通过信号来触发dnsmasq重新加载。为了方便区分,最终采用了后者,既--hostsdir指定hosts文件夹。

首先,在主机上创建一个文件夹和一个文件,写入需要解析的域名:

mkdir hosts
echo '127.0.0.1 a.test.com' > hosts/common

然后启动容器的时候,设置挂载卷,将这个目录共享给容器,同时指定这个目录未hostsdir:

docker run --net=host --cap-add=NET_ADMIN -v /home/admin/hosts:/media andyshinn/dnsmasq --addn-hosts=/media/common -q --log-facility=-

这样,如果需要新增解析记录,需要两步:

  1. 在主机hosts目录中,新增一条记录:echo 'b.test.com' >> hosts/common
  2. 向dns容器发送信号,使其重新加载文件:docker kill -s HUP 2beb1d9f3554

这样虽然还需要手工操作,但是无须再重新运行dns容器。

One Reply to “容器化dns服务”

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据