IaC 实践:用代码管理 DNS 记录

  • ~5.74K 字
  1. 1.
  2. 2. 调研
    1. 2.1. Terraform
    2. 2.2. Pulumi
    3. 2.3. OctoDNS
    4. 2.4. DNSControl
  3. 3. 实践
    1. 3.1. 0. 准备工具
    2. 3.2. 1. 初始化仓库
    3. 3.3. 2. 配置凭据加密(可跳过)
    4. 3.4. 3. 配置凭据文件
    5. 3.5. 4. 迁移原有配置
    6. 3.6. 5. 手动推送配置
    7. 3.7. 6. 配置 GitHub Actions

随着自部署的服务越来越复杂且服务架构奇怪(为了在节约成本的同时满足一定的灵活性和可扩展性),DNS 记录越来越多,管理这些记录的难度也在增加。

橙橙子的 DNS 记录已经有 50+ 条,大部分记录可能是这样的风格:

service0.example.com {
  # host on server1 & server2
  A 1.2.3.4
  A 1.2.3.5
  AAAA 2001:db8::1
  AAAA 2001:db8::2
}

service1.example.com {
  # host on server1
  A 1.2.3.4
  AAAA 2001:db8::1
}

service2.example.com {
  # host on server2
  A 1.2.3.5
  AAAA 2001:db8::2
}

使用 Web 界面管理这些记录已经变得十分不便:

  • 不便于分类整理、添加注释等
  • 难以批量替换(如某台服务器 IP 变更时,需手动修改多个服务的记录)
  • 缺乏备份和版本管理机制

因此,考虑使用 基础设施即代码(IaC) 工具来管理 DNS 记录。

调研

(特性/工具)TerraformPulumiOctoDNSDNSControl
适用范围多种资源多种资源专注 DNS专注 DNS
描述文件语言HCL多种编程语言YAMLJavaScript
资源配置简洁性较复杂较复杂较简单较简单
跨服务商统一配置困难有限支持原生支持原生支持
状态管理需要存储需要存储无状态无状态

Terraform

可能是最负盛名的 IaC 工具之一,支持管理各种服务,如 DNS 记录、VPC 网络、EC2 实例等。Terraform 采用 HCL 声明语言(具有变量和一定的逻辑运算能力)来定义基础设施的状态。其最大的优势在于可以同时使用 IaC 管理多种资源,并且和 DNS 资源联动(比如通过资源名直接引用 EC2 实例的 IP 地址)

使用 Terraform 工具管理 DNS 记录可以参考 @Candinya 的相关博文,其配置文件风格如下:

resource "cloudflare_record" "alampy_com_root" {
  zone_id = cloudflare_zone.alampy_com.id
  name    = "@"
  value   = "example.com"
  type    = "CNAME"
  proxied = true
}

Terraform 在 DNS 管理方面的不足主要在于,缺乏原生的跨服务商 DNS 记录同步功能,但这对于个人用户可能并不紧要。

然而 Terraform 要求一个存储后端来保存状态信息,这可能会增加额外的复杂性。同时如果已有 DNS 记录配置存在,迁移到 Terraform 需要较复杂的 import 过程。

Pulumi

Pulumi 也是著名的通用 IaC 工具,与 Terraform 的声明式配置不同,Pulumi 采用更灵活的编程式配置,支持多种编程语言(如 JavaScript、TypeScript、Python、Go 等),让开发者可以用熟悉的语言和工具管理基础设施。

其 DNS 管理能力可参考 Cloudflare 官方文档

和 Terraform 类似,Pulumi 需要一个存储后端来保存状态信息,初次迁移需要较复杂的 import 过程。

OctoDNS

OctoDNS 是专注于 DNS 记录管理的工具,最大特点是支持将同一份配置同时应用到多个 DNS 提供商(大型互联网服务常用多家 DNS 提供商,通过多 NS 记录提升冗余和可靠性),极大简化了跨平台 DNS 管理。

OctoDNS 的实现逻辑基于“同步”,即将配置从 sources 同步到 targets,确保各 DNS 提供商(本地文件可视为一种特殊 Provider)之间的一致性。这种逻辑使得 OctoDNS 不需要自行维护状态,而是基于实际情况进行 Diff 和 Apply,对于迁移和部署来说成本更低。

典型配置如下:

providers:
  config:
    class: octodns.provider.yaml.YamlProvider
    directory: ./config
    default_ttl: 3600
    enforce_order: True
  ns1:
    class: octodns_ns1.Ns1Provider
    api_key: env/NS1_API_KEY
  route53:
    class: octodns_route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY

zones:
  example.com.:
    sources:
      - config
    targets:
      - ns1
      - route53

OctoDNS 使用 YAML 作为本地记录格式,其配置复用能力依赖于 YAML 的引用、锚点、合并等特性:

www:
  - &homepage_a_record
    type: A
    values:
    - 1.2.3.4
    - 1.2.3.5
'': # domain's apex
    - *homepage_a_record
    - type: MX
      value:
        exchange: mx.example.com.
        preference: 10

DNSControl

DNSControl 是由 StackExchange 开发的一个开源 DNS 记录管理工具,专注于通过代码管理 DNS 记录。它支持多种 DNS 提供商。其使用基于 JavaScript 的 DSL 来定义 DNS 记录,允许用户以编程方式创建和管理 DNS 记录,风格大致如下:

var REG_NONE = NewRegistrar("none");
var DNS_BIND = NewDnsProvider("bind");

D("example.com", REG_NONE, DnsProvider(DNS_BIND),
    A("@", "1.2.3.4"),    // "@" means the domain's apex.
);

DNSControl 也是无状态的,基于 Diff 和 Apply 完成配置,对于迁移和部署来说成本较低。

实践

0. 准备工具

基于已有情况:

  • 橙橙子暂时还没有使用 IaC 管理多项服务的打算(考虑到 self-hosted 场景下会使用不少小厂的产品,这么做可能并不实际),目前只考虑在 DNS 记录管理上使用 IaC 工具
  • 橙橙子希望配置文件具有较好的灵活性,方便复用逻辑
  • 橙橙子已有一定的 DNS 记录,希望迁移较为方便
  • 橙橙子希望在满足需求的情况下尽可能保持简单易部署

橙橙子选择了使用 DNSControl 作为 DNS 记录管理的 IaC 工具。

  • 对于 Arch Linux 用户,可以安装 dnscontrol-bin [AUR] 包。
  • 对于 macOS 用户,可以使用 brew install dnscontrol 来安装。
  • 对于 Golang 用户,可以使用 go install github.com/StackExchange/dnscontrol/v4@latest 来安装。
  • 对于 Docker 用户,可以使用 docker run --rm -it -v "$(pwd):/dns" ghcr.io/stackexchange/dnscontrol ${command} 来运行。

1. 初始化仓库

准备一个仓库来存放 DNSControl 的配置文件。在这里我们使用 Git 进行管理(你也可以只是简单创建一个文件夹)。

mkdir dnscontrol_alampy
cd dnscontrol_alampy
git init

2. 配置凭据加密(可跳过)

DNSControl 需要一个 creds.json 文件来存储 API 凭据信息,为了加强安全性,我们使用 git-crypt 对该文件进行加密。具体步骤如下:

git-crypt init
echo "creds.json filter=git-crypt diff=git-crypt" >> .gitattributes

git-crypt 会生成一个对称密钥用于加解密数据,如果你有 GPG 密钥,可以使用:

git-crypt add-gpg-user $YOUR_GPG_UID

此时会使用你的公钥保存对称密钥,并自动生成一个 commit。之后,你只需要维护你的私钥不丢失,便可以解密 creds.json 文件。

如果你平时不使用 GPG 工具,也可以直接导出对称密钥并保存在安全的地方。

git-crypt export-key /path/to/keyfile

3. 配置凭据文件

创建 creds.json 文件,内容如下(请替换为适合你自己的配置):

{
  "cloudflare:alampy.com": {
    "TYPE": "CLOUDFLAREAPI",
    "accountid": "your-cloudflare-account-id",
    "apitoken": "your-cloudflare-api-token"
  },
  "none": { "TYPE": "NONE" }
}

对于 Cloudflare 用户,参照 DNSControl 文档,需要的权限如下:

  • Zone → Zone → Read
  • Zone → DNS → Edit
  • Zone → SSL and Certificates → Edit
  • Zone → Page Rules → Edit
  • Zone → Single Redirect → Edit
  • Account → Workers Scripts → Edit
  • Zone → Workers Routes → Edit

4. 迁移原有配置

由于我原本已有配置,因此需要将其迁移到 DNSControl 中。

dnscontrol get-zones --format=js --out=dnsconfig.js cloudflare:alampy.com - alampy.com

迁移工具并不完美,可能需要手动调整生成的配置文件以满足实际需求。请务必仔细检查并测试迁移后的配置。

DNSControl 使用 robertkrimen/otto 作为 JavaScript 解释器,支持绝大多数 ES5 语法,但对于 ES6 支持较差

在手动修正完成后,使用 dnscontrol preview 命令查看预览效果。

5. 手动推送配置

首次推送前请务必执行一次 preview,以防止意外错误。验证无误后,使用如下命令将配置推送到 DNS 提供商。

dnscontrol push

6. 配置 GitHub Actions

首先获取 git crypt 密钥:

git-crypt export-key - | base64

写入到 secret GIT_CRYPT_KEY 中。

然后编写一个如下的 Workflow:

name: "Deploy"
on:
  push:
    branches: [ main ]
  workflow_dispatch:
jobs:
  deploy:
    name: Deploy DNS Config
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v5
      - name: Unlock Secrets
        run: |
          # Skip installing package docs
          echo "path-exclude /usr/share/man/*" | sudo tee /etc/dpkg/dpkg.cfg.d/01_nodoc
          sudo apt-get update
          sudo apt-get install git-crypt
          echo "$GIT_CRYPT_KEY" | base64 -d | git-crypt unlock -
        env:
          GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY }}
      - name: Deploy DNS Config
        run: |
          docker run --rm -i -v "$(pwd):/dns" ghcr.io/stackexchange/dnscontrol push
赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!