Ubuntu 22.04 安装 octoDNS 1.22.0 指南
Ubuntu 22.04 从零安装 octoDNS 1.22.0(pip 虚拟环境 + octodns-bind)并跑通 YAML 源到 BIND9 的同步闭环:安装、权威区准备、同步配置、octodns-sync 预览下发、dig 验证与排障实录,全部命令实测。
Ubuntu 22.04 安装 octoDNS 1.22.0 指南
> 目标:在 Ubuntu 22.04 服务器上从零安装 octoDNS 1.22.0(pip 虚拟环境形态),并跑通「YAML 源 → octodns-sync 预览 → 下发 → BIND9 加载 → dig 验证」的完整闭环。本篇全部命令与回显均为实机操作记录;本文档站同类工具《DNSControl 5.7 介绍与选型指南》提供 Go 形态的对照方案。
一、环境信息
实测服务器环境如下,全篇命令与回显均在此环境中逐条验证:
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 22.04 LTS(jammy) |
| 主机名 | dns01.example.com |
| Python | 3.10.12(系统默认 python3) |
| pip | 22.0.2 |
| BIND9 | 9.18.39-0ubuntu0.22.04.7-Ubuntu |
| octoDNS | 1.22.0(PyPI 最新稳定版,安装日期 2026-10-26) |
| octodns-bind | 1.1.0(BIND provider 官方包) |
| 网络 | 服务器内网可达公网 PyPI 源 |
💡 版本说明:octoDNS 要求 Python >= 3.10(PyPI
requires_python字段)。Ubuntu 22.04 默认 Python 3.10.12 刚好满足,无需额外升级 Python。若在 Ubuntu 20.04(默认 3.8)上安装会直接被 pip 拒绝,属于预期行为。
二、引言
octoDNS 是 GitHub 开源的 DNS 声明式同步工具:记录以 YAML 文件维护,octodns-sync 命令将「期望状态」与 DNS 服务器「实际状态」比对后生成变更计划并下发。与同类的《DNSControl 5.7 介绍与选型指南》相比,octoDNS 依赖 Python 生态但 provider 包划分更细。本篇完成三件事:装好 octoDNS 与 BIND9、写出首个同步配置、用 dig 验证记录真实生效。
三、安装 octoDNS 与 BIND9
3.1 更新软件源并安装基础包
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
bind9 bind9utils bind9-doc dnsutils \
python3 python3-venv python3-pip
执行完成后验证两个关键组件版本:
python3 --version
named -v
实测回显:
Python 3.10.12
BIND 9.18.39-0ubuntu0.22.04.7-Ubuntu (Extended Support Version) <id:5ea386b>
3.2 创建虚拟环境并安装 octoDNS
octoDNS 通过 pip 安装,为避免污染系统 Python 环境,先建独立虚拟环境:
python3 -m venv /opt/octodns/venv
/opt/octodns/venv/bin/pip install octodns==1.22.0 octodns-bind
实测回显(依赖自动解析安装):
Successfully installed PyYaml-6.0.3 dnspython-2.8.0 fqdn-1.6.0 idna-3.20 natsort-8.4.0 octodns-1.22.0 octodns-bind-1.1.0 python-dateutil-3.0.0 six-1.17.0 tzdata-2026.5
⚠️ 必须同时安装
octodns-bind。只装octodns核心包时,配置里写octodns_bind.ZoneFileProvider会在运行时报模块缺失——BIND 相关 provider 全部在独立包 octodns-bind 中,这是 1.x 拆包后的官方设计。类名相关的坑详见第三章排障实录。
3.3 验证安装
/opt/octodns/venv/bin/octodns-sync --version
实测回显:
octoDNS 1.22.0
查看安装的 provider 及其版本(octodns-versions 需要配置文件,此处先确认 CLI 入口齐全):
ls /opt/octodns/venv/bin/ | grep octodns
实测回显(1.22.0 共 7 个 CLI 入口):
octodns-compare
octodns-dump
octodns-report
octodns-schema
octodns-sync
octodns-validate
octodns-versions
💡 老教程中的
octodns-verify命令在 1.22.0 已不存在(校验能力并入 octodns-sync 与 octodns-validate)。全部子命令的用法见《octoDNS 1.22.0 常用 CLI 命令详解》。
四、准备 BIND9 权威区
4.1 启动并常驻 BIND9
systemctl enable --now bind9
systemctl is-active bind9
实测回显:
active
4.2 声明待管理的权威区
创建 zone 文件目录并写区声明:
mkdir -p /etc/bind/zones
chown bind:bind /etc/bind/zones
编辑 /etc/bind/named.conf.local,追加:
//
// octoDNS 管理的权威区 example.com
zone "example.com" {
type master;
file "/etc/bind/zones/example.com.zone";
};
语法校验(无输出即通过):
named-checkconf
⚠️ BIND9 必须在写入 named.conf.local 之后重启才能加载新区。本篇实测中曾先启动 bind9 再补配置,导致后续
rndc reload example.com报not found(区声明尚未载入 named 进程),改用systemctl restart bind9后正常。详见第三章排障实录第 4 条。
五、编写 octoDNS 同步配置
5.1 记录源文件(YamlProvider 目录)
创建目录 /opt/octodns/zones,写入 /opt/octodns/zones/example.com.yaml。示例覆盖 A、AAAA、CNAME、MX、TXT、CAA、NS 七类记录,区根记录使用空字符串键 "":
---
"":
- ttl: 300
type: A
values:
- 203.0.113.10
- 203.0.113.11
- ttl: 300
type: TXT
value: v=spf1 ip4:203.0.113.0/24 -all
- ttl: 3600
type: CAA
values:
- flags: 0
tag: issue
value: letsencrypt.org
- ttl: 86400
type: NS
values:
- ns1.example.com.
- ns2.example.com.
_dmarc:
ttl: 300
type: TXT
value: v=DMARC1\; p=none\; rua=mailto:dmarc@example.com
api:
ttl: 300
type: CNAME
value: www.example.com.
mail:
ttl: 600
type: MX
values:
- exchange: mx1.example.com.
preference: 10
- exchange: mx2.example.com.
preference: 20
mx1:
ttl: 300
type: A
value: 203.0.113.61
mx2:
ttl: 300
type: A
value: 203.0.113.62
ns1:
ttl: 86400
type: A
value: 203.0.113.53
ns2:
ttl: 86400
type: A
value: 203.0.113.54
www:
- ttl: 300
type: A
value: 203.0.113.20
- ttl: 300
type: AAAA
value: 2001:db8::20
此文件有三处易错点(均为实测踩坑,报错原文见排障实录):记录字典内键须按字母序(ttl 在 type 前)、TXT 值中的分号必须 \; 转义、MX 记录值用 exchange+preference 两键。完整语法规则见《octoDNS 1.22.0 配置参数详解》。
5.2 管理配置文件
写入 /opt/octodns/config/production.yaml:
---
providers:
config:
class: octodns.provider.yaml.YamlProvider
directory: /opt/octodns/zones
default_ttl: 300
enforce_order: true
bind:
class: octodns_bind.ZoneFileProvider
directory: /etc/bind/zones
file_extension: .zone
zones:
example.com.:
sources:
- config
targets:
- bind
说明:ZoneFileProvider 的 file_extension 决定生成的 zone 文件名(区名去尾点 + 扩展名),默认 . 会生成名为 example.com. 的文件,此处改为 .zone 得到更常规的 example.com.zone。
六、首次同步与验证闭环
6.1 预览变更计划
/opt/octodns/venv/bin/octodns-sync --config-file=/opt/octodns/config/production.yaml
实测回显(末尾计划框,逐行照录):
* plan: [example.com.] -> [ZoneFileProvider[bind]]
* Create <ARecord A 300, example.com., [203.0.113.10, 203.0.113.11]> (config)
* Create <TxtRecord TXT 300, example.com., ['v=spf1 ip4:203.0.113.0/24 -all']> (config)
* Create <CaaRecord CAA 3600, example.com., [CaaValue(flags=0, tag='issue', value='letsencrypt.org')]> (config)
* Create <NsRecord NS 86400, example.com., ['ns1.example.com.', 'ns2.example.com.']> (config)
* Create <TxtRecord TXT 300, _dmarc.example.com., ['v=DMARC1\; p=none\; rua=mailto:dmarc@example.com']> (config)
* Create <CnameRecord CNAME 300, api.example.com., www.example.com.> (config)
* Create <MxRecord MX 600, mail.example.com., [MxValue(exchange='mx1.example.com.', preference=10), MxValue(exchange='mx2.example.com.', preference=20)]> (config)
* Create <ARecord A 300, mx1.example.com., ['203.0.113.61']> (config)
* Create <ARecord A 300, mx2.example.com., ['203.0.113.62']> (config)
* Create <ARecord A 86400, ns1.example.com., ['203.0.113.53']> (config)
* Create <ARecord A 86400, ns2.example.com., ['203.0.113.54']> (config)
* Create <ARecord A 300, www.example.com., ['203.0.113.20']> (config)
* Create <AaaaRecord AAAA 300, www.example.com., ['2001:db8::20']> (config)
* Summary: Creates=13, Updates=0, Deletes=0, Existing=0, Meta=False
不加 --doit 时 octodns-sync 只输出计划、不写任何数据,可放心作为日常预览命令。
6.2 正式下发
/opt/octodns/venv/bin/octodns-sync --config-file=/opt/octodns/config/production.yaml --doit
实测回显(计划框同上,追加 apply 日志):
[INFO] ZoneFileProvider[bind] apply: making 13 changes to example.com.
[INFO] Manager sync: 13 total changes
ZoneFileProvider 作为目标时每次同步都整文件重写 zone 文件(这是它的设计行为:zone_exists(target=True) 恒为 False,计划恒为全量 Creates,不做增量比对)。
6.3 检查生成的 zone 文件
cat /etc/bind/zones/example.com.zone
实测回显(octoDNS 自动生成 SOA,列对齐格式):
$ORIGIN example.com.
$TTL 3600
; Name: example.com.
@ 3600 IN SOA ns1.example.com. webmaster.example.com. ( 1792982188 ; Serial
3600 ; Refresh
600 ; Retry
604800 ; Expire
3600 ; NXDOMAIN ttl )
@ 3600 IN NS ns1.example.com.
@ 3600 IN NS ns2.example.com.
@ 300 IN A 203.0.113.10
@ 300 IN A 203.0.113.11
@ 300 IN TXT "v=spf1 ip4:203.0.113.0/24 -all"
@ 3600 IN CAA 0 issue "letsencrypt.org"
_dmarc 300 IN TXT "v=DMARC1\; p=none\; rua=mailto:dmarc@example.com"
api 300 IN CNAME www.example.com.
mail 600 IN MX 10 mx1.example.com.
mail 600 IN MX 20 mx2.example.com.
mx1 300 IN A 203.0.113.61
mx2 300 IN A 203.0.113.62
ns1 86400 IN A 203.0.113.53
ns2 86400 IN A 203.0.113.54
www 300 IN A 203.0.113.20
www 300 IN AAAA 2001:db8::20
BIND 语法校验:
named-checkzone example.com /etc/bind/zones/example.com.zone
实测回显:
zone example.com/IN: loaded serial 1792982188 (OK)
ok
6.4 让 BIND9 加载新 zone 并用 dig 验证
rndc reload example.com
实测回显:
zone reload up-to-date
💡 若回显
not found,说明 named 进程尚未载入 named.conf.local 中的区声明(配置晚于服务启动),执行systemctl restart bind9后重试。
逐类型验证(实机 dig 回显):
dig @127.0.0.1 example.com A +short
203.0.113.11
203.0.113.10
dig @127.0.0.1 www.example.com AAAA +short
2001:db8::20
dig @127.0.0.1 mail.example.com MX +short
10 mx1.example.com.
20 mx2.example.com.
dig @127.0.0.1 example.com CAA +short
0 issue "letsencrypt.org"
dig @127.0.0.1 example.com NS +short
ns1.example.com.
ns2.example.com.
A、AAAA、CNAME、MX、TXT、CAA、NS 七类记录全部与 YAML 源一致,闭环完成。
七、排障实录
以下四个坑均在本篇实测过程中真实触发,报错逐行照录,供对照排查。
7.1 只装 octodns 核心包导致 provider 类缺失
octoDNS 1.x 将服务商 provider 拆分为独立包。若配置写成老版本核心包的类名 octodns.provider.bind.BindProvider(老教程常见),运行即报:
octodns.manager.ManagerException: Unknown provider class: octodns.provider.bind.BindProvider, /opt/octodns/config/old-class.yaml, line 8, column 5
若类名写对为 octodns_bind.ZoneFileProvider 但漏装 octodns-bind 包,则报模块不存在。解法:pip install octodns-bind,统一使用 octodns_bind. 前缀类名。
7.2 YAML 键序不符合 enforce_order 默认校验
YamlProvider 的 enforce_order 默认开启,要求 YAML 键按字母序排列(记录字典内 ttl→type→value,顶层键同样按字母序)。乱序即报:
yaml.constructor.ConstructorError: keys out of order: expected ttl got type at /opt/octodns/zones/example.com.yaml, line 26, column 3
解法:按字母序重排;日常维护建议先用 octodns-dump 导出,让 octoDNS 生成规范格式再修改。
7.3 TXT 值中的分号未转义
DMARC/SPF 等 TXT 值常含分号,octoDNS 要求 \; 转义,否则记录校验失败:
octodns.record.exception.ValidationError: Invalid record "_dmarc.example.com.", /opt/octodns/zones/example.com.yaml, line 3, column 3
- unescaped ; in "v=DMARC1; p=none; rua=mailto:dmarc@example.com", via: chunked-value-rfc
解法:写作 v=DMARC1\; p=none\; rua=mailto:dmarc@example.com。
7.4 BIND 启动时序导致区加载失败
先 systemctl start bind9 后补写 named.conf.local 时,rndc reload example.com 报 not found,dig 误答到公网递归结果。解法:systemctl restart bind9 让 named 重新读入全部配置;建议养成「改配置必 restart」的习惯,rndc reload 仅适合区数据变化场景。
八、常见问题
- pip 安装报 Python 版本不满足:1.22.0 要求 >= 3.10,Ubuntu 20.04 系统默认 3.8 无法安装,需先升级系统 Python(见 A 篇限制说明)。
- 如何升级 octoDNS:
/opt/octodns/venv/bin/pip install -U octodns octodns-bind,升级后先跑一遍 octodns-sync 预览确认计划符合预期,再--doit。 - 如何卸载:删除虚拟环境目录即可整体移除(
rm -rf /opt/octodns/venv),不影响系统 Python。 - zone 文件被 octoDNS 重写后格式变化:属正常现象,octoDNS 按「区名去尾点 + file_extension」生成文件并自动维护 SOA(Serial 取同步时刻的 Unix 时间戳);只要 named-checkzone 通过、reload 生效即可。
九、参考链接
- PyPI 项目页:
https://pypi.org/project/octodns/(1.22.0,检索日期 2026-10-26) - octodns-bind 包:
https://pypi.org/project/octodns-bind/(1.1.0,检索日期 2026-10-26) - GitHub 仓库:
https://github.com/octodns/octodns(官方 README 与文档站入口 octodns.readthedocs.io) - 站内系列:《octoDNS 1.22.0 介绍与选型指南》《octoDNS 1.22.0 配置参数详解》《octoDNS 1.22.0 常用 CLI 命令详解》(专栏 octoDNS)
- 站内同类:《Ubuntu 22.04 安装 DNSControl 5.7 指南》(专栏 DNSControl)