强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

BIND DNS 服务器搭建完全教程 / 第 13 章:Docker 容器化部署

本章概述

容器化部署 BIND 可以简化环境管理、提高可移植性。本章讲解使用 Docker 和 Docker Compose 部署 BIND,包括配置管理、日志收集、健康检查等。


13.1 基础 Dockerfile

13.1.1 使用 Alpine Linux(推荐)

# Dockerfile
FROM alpine:3.19

# 安装 BIND
RUN apk add --no-cache bind bind-tools

# 创建目录
RUN mkdir -p /etc/bind \
    /var/bind/{primary,secondary,dynamic} \
    /var/log/named \
    && chown -R named:named /var/bind /var/log/named

# 暴露端口
EXPOSE 53/udp 53/tcp 953/tcp

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
    CMD dig @127.0.0.1 . NS > /dev/null 2>&1 || exit 1

# 启动命令
CMD ["/usr/sbin/named", "-c", "/etc/bind/named.conf", "-f", "-g", "-u", "named"]

注意-f 参数让 named 在前台运行(Docker 要求),-g 输出日志到 stderr。

13.1.2 使用 Ubuntu 基础镜像

# Dockerfile.ubuntu
FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    bind9 bind9utils dnsutils && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir -p /var/cache/bind/{primary,secondary,dynamic} \
    /var/log/named && \
    chown -R bind:bind /var/cache/bind /var/log/named

EXPOSE 53/udp 53/tcp 953/tcp

HEALTHCHECK --interval=30s --timeout=5s \
    CMD dig @127.0.0.1 . NS > /dev/null 2>&1 || exit 1

CMD ["/usr/sbin/named", "-c", "/etc/bind/named.conf", "-f", "-g", "-u", "bind"]

13.2 Docker Compose 配置

13.2.1 基本配置

# docker-compose.yml
version: '3.8'

services:
  dns:
    build: .
    container_name: bind9-dns
    restart: unless-stopped
    ports:
      - "53:53/udp"
      - "53:53/tcp"
      # rndc 管理端口(仅限主机访问)
      - "127.0.0.1:953:953/tcp"
    volumes:
      # 配置文件
      - ./config/named.conf:/etc/bind/named.conf:ro
      - ./config/zones:/etc/bind/zones:ro
      # 区域文件(可写,支持动态更新)
      - dns-data:/var/cache/bind
      # 日志
      - ./logs:/var/log/named
      # 根提示文件
      - ./config/db.root:/etc/bind/db.root:ro
    environment:
      - TZ=Asia/Shanghai
    networks:
      - dns-network
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: false
    tmpfs:
      - /run
      - /tmp

volumes:
  dns-data:
    driver: local

networks:
  dns-network:
    driver: bridge

13.2.2 目录结构

dns-server/
├── docker-compose.yml
├── Dockerfile
├── config/
│   ├── named.conf
│   ├── db.root
│   ├── zones/
│   │   ├── primary/
│   │   │   └── example.com.zone
│   │   └── secondary/
│   └── keys/
│       └── update.key
└── logs/
    ├── default.log
    ├── query.log
    └── security.log

13.3 配置文件管理

13.3.1 named.conf

// config/named.conf
options {
    directory "/var/cache/bind";
    
    listen-on port 53 { any; };
    listen-on-v6 port 53 { any; };
    
    recursion yes;
    allow-recursion { 192.168.0.0/16; 10.0.0.0/8; 172.16.0.0/12; };
    allow-query { any; };
    
    forwarders {
        8.8.8.8;
        1.1.1.1;
    };
    forward first;
    
    version "not disclosed";
    dnssec-validation auto;
    
    max-cache-size 256m;
    max-cache-ttl 3600;
    max-ncache-ttl 900;
    
    minimal-responses yes;
    
    pid-file "/run/named.pid";
    session-keyfile "/run/session.key";
};

logging {
    channel default_log {
        stderr;
        severity info;
        print-time yes;
        print-severity yes;
        print-category yes;
    };
    channel query_log {
        file "/var/log/named/query.log" versions 3 size 50m;
        severity dynamic;
        print-time yes;
    };
    category default { default_log; };
    category queries { query_log; };
    category lame-servers { null; };
    category edns-disabled { null; };
};

zone "." {
    type hint;
    file "/etc/bind/db.root";
};

zone "example.com" {
    type primary;
    file "/etc/bind/zones/primary/example.com.zone";
    allow-transfer { none; };
};

zone "internal.corp" {
    type primary;
    file "/var/cache/bind/primary/internal.corp.zone";
    allow-update { key update-key; };
};

13.3.2 热重载配置

# 使用 Docker exec 重载配置
docker exec bind9-dns rndc reload

# 使用 Docker Compose
docker compose exec dns rndc reload

# 完全重启
docker compose restart dns

13.4 高可用 Compose 配置

13.4.1 主从架构

# docker-compose.ha.yml
version: '3.8'

services:
  dns-primary:
    build: .
    container_name: bind9-primary
    restart: unless-stopped
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    volumes:
      - ./config-primary/named.conf:/etc/bind/named.conf:ro
      - ./zones/primary:/var/cache/bind/primary
      - ./logs/primary:/var/log/named
      - ./config/db.root:/etc/bind/db.root:ro
    networks:
      dns-network:
        ipv4_address: 172.20.0.10
    healthcheck:
      test: ["CMD", "dig", "@127.0.0.1", ".", "NS"]
      interval: 30s
      timeout: 5s
      retries: 3

  dns-secondary:
    build: .
    container_name: bind9-secondary
    restart: unless-stopped
    ports:
      - "54:53/udp"
      - "54:53/tcp"
    volumes:
      - ./config-secondary/named.conf:/etc/bind/named.conf:ro
      - ./zones/secondary:/var/cache/bind/secondary
      - ./logs/secondary:/var/log/named
      - ./config/db.root:/etc/bind/db.root:ro
    depends_on:
      - dns-primary
    networks:
      dns-network:
        ipv4_address: 172.20.0.11

networks:
  dns-network:
    ipam:
      config:
        - subnet: 172.20.0.0/24

13.5 日志收集

13.5.1 Docker 原生日志

# docker-compose.yml
services:
  dns:
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

13.5.2 使用 Syslog 驱动

services:
  dns:
    logging:
      driver: "syslog"
      options:
        syslog-address: "tcp://logserver:514"
        tag: "bind9"

13.5.3 集中日志(Loki + Promtail)

# docker-compose.yml
services:
  dns:
    # ... 其他配置 ...
    labels:
      - "logging=loki"
      - "loki.job=bind9"

  promtail:
    image: grafana/promtail:latest
    volumes:
      - ./logs:/var/log/named
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
# promtail-config.yml
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: bind9
    static_configs:
      - targets: [localhost]
        labels:
          job: bind9
          __path__: /var/log/named/*.log

13.6 DNS 服务器编排(Docker Swarm / Kubernetes)

13.6.1 Docker Swarm 部署

# 初始化 Swarm
docker swarm init

# 部署 Stack
docker stack deploy -c docker-compose.yml dns

13.6.2 Kubernetes ConfigMap

# k8s-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: bind-config
data:
  named.conf: |
    options {
        directory "/var/cache/bind";
        recursion yes;
        allow-recursion { 10.0.0.0/8; };
        listen-on port 53 { any; };
    };
    
    zone "example.com" {
        type primary;
        file "/etc/bind/zones/example.com.zone";
    };
  example.com.zone: |
    $TTL 3600
    @   IN  SOA  ns1.example.com. admin.example.com. (
            2026051001 3600 900 1209600 86400
        )
        IN  NS  ns1.example.com.
    ns1 IN  A   10.0.0.10
    www IN  A   10.0.0.20
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bind9
spec:
  replicas: 2
  selector:
    matchLabels:
      app: bind9
  template:
    metadata:
      labels:
        app: bind9
    spec:
      containers:
        - name: bind9
          image: bind9:latest
          ports:
            - containerPort: 53
              protocol: UDP
            - containerPort: 53
              protocol: TCP
          volumeMounts:
            - name: config
              mountPath: /etc/bind/named.conf
              subPath: named.conf
            - name: zones
              mountPath: /etc/bind/zones/example.com.zone
              subPath: example.com.zone
          livenessProbe:
            exec:
              command: ["dig", "@127.0.0.1", ".", "NS"]
            initialDelaySeconds: 10
            periodSeconds: 30
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
      volumes:
        - name: config
          configMap:
            name: bind-config
        - name: zones
          configMap:
            name: bind-config

13.7 最佳实践

13.7.1 镜像构建

实践说明
使用 Alpine镜像小(~15MB)
固定版本apk add bind=9.18.x
多阶段构建减少最终镜像大小
安全扫描docker scan bind9

13.7.2 运行时安全

services:
  dns:
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE    # 允许绑定 53 端口
    read_only: true          # 只读文件系统
    security_opt:
      - no-new-privileges    # 禁止提权
    tmpfs:
      - /run:rw,size=10M
      - /tmp:rw,size=100M

13.7.3 网络优化

services:
  dns:
    sysctls:
      - net.core.rmem_max=8388608
      - net.core.wmem_max=8388608

13.8 常见问题

问题原因解决方案
端口冲突宿主机已有 DNS 服务修改端口映射
权限不足绑定 53 端口需要权限cap_add: NET_BIND_SERVICE
日志丢失容器重启挂载日志卷
配置不生效文件只读挂载后更新重启容器或使用 volume
性能差默认网络模式使用 host 网络模式

13.9 本章小结

部署方式适用场景优点缺点
单容器开发测试简单无高可用
Compose小型生产配置即代码手动扩缩容
Swarm中型生产自动扩缩容配置复杂
Kubernetes大型生产企业级学习成本高

💡 小技巧

  1. 使用 Alpine 基础镜像:体积小,安全更新快。
  2. 日志输出到 stderr:便于 Docker 原生日志收集。
  3. 使用命名卷docker volume create dns-data
  4. 固定镜像版本:避免意外升级导致问题。
  5. 健康检查必须配置:自动检测服务故障。

📖 扩展阅读