PowerDNSのZoneとレコードをAnsibleで設定する

毎度、ググっても出てこない小ネタを取り扱っております。
本記事は個人的な見解であり、筆者の所属するいかなる団体にも関係ございません。

# 0. はじめに

PowerDNSでAuthorityサーバーを起動するのはできましたが、それだけではDNSサーバーとしては役に立ちません。Zoneとレコードを設定する必要があります。

curlでポチポチ入れてもいいのですが、どうせならAnsibleでYAMLファイルにより設定できた方がいいですよね。

って事で、AnsibleのPlaybookでPowerDNSのゾーンとレコードを管理してみました。

# 1. 前提条件

1. PowerDNS Authorityサーバーが設定されている
2. APIが有効になっている
2. Ansibleがインストールされている
2. AnsibleはLocal接続

# 2. PowerDNSのゾーンとレコードを管理するAnsible

まず、PowerDNSのゾーンとレコードを管理するAnsibleのRoleですが、探してみると実は多くありません。3つぐらいあるようですが、それからさらに、まだメンテナンスされているものとなると1つだけでした。

以下のAnsible Collectionです。

kpfleming/ansible\-powerdns\-auth: Ansible modules which manage content in a PowerDNS Authoritative server\. 

https://github.com/kpfleming/ansible-powerdns-auth/tree/main

現状では一番メンテナンスが継続しているものです。

こちらはAnsibleのRoleではなくて、Collectionとなっています(違いはよく分かっていません)

# 3. kpfleming.powerdns_authのインストール

AnsibleのCollectionとなっているので、インストールも簡単です。

## 3-1. Ansibleなどのインストール

事前にAnsible等の以下をインストールしておきましょう。

```bash
sudo apt update
sudo apt install -y ansible jq curl python3-pip
```

## 3-2. Ansible Galaxyで`kpfleming.powerdns_auth`をインストール

requirements.ymlを作成します。

```yaml:requirements.yml
collections:
  # Install a collection from Ansible Galaxy.
  - name: kpfleming.powerdns_auth
```

以下のコマンドでインストールします。
```
ansible-galaxy collection install -r requirements.yml 
```

## 3-3. インベントリーファイルを用意する

今回はlocalhostに接続するだけなので、インベントリーファイルも簡単です。

```file:hosts
[localhost]
127.0.0.1
```

## 3-3. Zone設定ファイルを用意する

以下のようなZone設定ファイルを用意します。
api_keyは、PowerDNSのAPI接続時に利用するAPIのシークレットです。
皆様の環境に合わせてください。
api_urlは、API接続のURLです。ローカルになっていますがリモートの場合は正しいURLを指定してください。
`gather_facts: true`にするとエラーになるので、falseにしてあります。

```yaml:playbook-manage_zone.yml
- hosts: localhost
  name: PowerDNS
  connection: local
  gather_facts: false

  vars:
    ansible_python_interpreter: python3
    pdns_version: "{{ lookup('ansible.builtin.env', 'pdns_version') }}"
    common_args: &common
      api_key: password
      api_url: http://localhost:8081
  tasks:
    - name: manage dependencies needed for powerdns_auth modules
      ansible.builtin.pip:
        name:
          - bravado
          - jsonschema<4
          - swagger-spec-validator==2.6.0
    - name: delete zone example.com
      kpfleming.powerdns_auth.zone:
        <<: *common
        name: example.com.
        state: absent
    - name: create zone example.com
      kpfleming.powerdns_auth.zone:
        <<: *common
        name: example.com.
        state: present
        properties:
          kind: 'Master'
          nameservers:
            - 'ns1.example.com.'
            - 'ns2.example.com.'
          soa:
            mname: 'localhost.'
            rname: 'hostmaster.localhost.'
          rrsets:
            - name: www.example.com.
              type: A
              ttl: 900
              records:
                - content: 192.0.2.1
            - name: mail.example.com.
              type: A
              ttl: 900
              records:
                - content: 192.0.2.3
                - content: 192.0.2.4
```

### 3-4. `playbook-manage_zone.yml`ファイルの内容の解説

vars: は各種変数を宣言しています。
tasks: には3つタスクがあります。
1. name: manage dependencies needed for powerdns_auth modules
2. name: delete zone example.com
3. name: create zone example.com

1つ目は、このCollectionを実行するため必要なPythonのパッケージをインストールしています。1回実行したらインストールされるでしょう。
2つ目は、Zoneを削除します。
3つ目は、Zoneを作成します。

2つ目のZoneの削除について説明します。

Ansibleは冪等性を確保する事ができるスクリプトです。このコレクションは既にZoneが存在すると設定を行わないようになっています。

例えば、Zoneを1回作成した後に、Aレコードを追加したいとします。
このスクリプトで1回実行したZoneファイルにAレコードを追加して、再度Ansibleを実行しても、既に設定されているZoneにレコードを追加するようにできていません。

つまり、冪等性チェックは`Zoneがあるかないか`までしかチェックしておらず、
`レコードがあるかないか`はチェックしていません。

となると、このPlaybookは1回しか実行できないということになります。
それでは困るので、Zoneを追加する前にZoneを削除しています。
(冪等性チェックがレコード単位になると便利なのですが...)

# 4. 実行する

Ansible Playbookコマンドを実行します。

```bash
ansible-playbook -i hosts ./playbook-manage_zone.yml
```

# 5. まとめ

ちょっと微妙なところはありますが、ひとまず動かす事はできるようになりました。