Route53のホストゾーンの情報とレコードの一覧を取得する

今回はRoute53に作成されたホストゾーンの情報とそれに設定されたレコードの一覧を取得するスクリプトを解説します。 Boto3ではPaginatorを使って書くとコードがきれいになるので、今回はPaginatorについても解説をしています。
2023.01.20

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Route53に存在するホストゾーンの情報やそれに設定されたレコードの情報を一括で取得したいときに便利なスクリプトを書いてみました。

はじめにスクリプトの紹介を行って、その後スクリプト内で使用しているPaginatorの説明を行います。

すべてのゾーンからレコードを取得するスクリプト

main.py

import boto3
import json

def get_hosted_zones():
    paginator = route53.get_paginator('list_hosted_zones')
    for page in paginator.paginate():
        for hz in page['HostedZones']:
            yield hz

def get_record_sets(hz_id):
    paginator = route53.get_paginator('list_resource_record_sets')
    pages = paginator.paginate(HostedZoneId=hz_id)
    return [
        page['ResourceRecordSets']
        for page in pages
    ]
        

def main():
    hz_info = [
        {
            "HZInfo": route53.get_hosted_zone(Id=hosted_zone['Id']),
            "RecordSets": get_record_sets(hosted_zone['Id'])
        }
        for hosted_zone in get_hosted_zones()
    ]

    with open('hz_info.json', 'w') as f:
        json.dump(hz_info, f, indent=2)
    
main()

上記がアカウント内のゾーン情報とレコードの情報をすべて取得するスクリプトになります。

上記のスクリプトで出力されるのは以下のような構造のJSONファイルです。

hz_info.jsonの構造

[
    {
        "HZInfo": {...},
        "RecordSets": [{...}, ...],
    }
]

それぞれのフィールドの中身は以下のようになっています。

以下は実際に出力されるファイルの例です。

hz_info.json

[
  {
    "HZInfo": {
      "Id": "/hostedzone/XXXXXXXXXXXXXXXXXXXXX", // ホストゾーンのID
      "Name": "sample.xxx.", // 架空のドメイン名です
      "CallerReference": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // 2重作成を防ぐためのランダムな値
      "Config": {
        "Comment": "this is sample zone",
        "PrivateZone": false // プライベートホストゾーンか否か
      },
      "ResourceRecordSetCount": 3 // レコード数
    },
    "RecordSets": [ // 以下がレコード
      [
        {
          "Name": "sample.xxx.", // レコード名
          "Type": "NS", // レコードタイプ 
          "TTL": 172800, // Time To Live
          "ResourceRecords": [ // レコードの設定値
            {
              "Value": "ns-0000.awsdns-00.co.uk."
            },
            {
              "Value": "ns-1111.awsdns-11.org."
            },
            {
              "Value": "ns-2222.awsdns-22.net."
            },
            {
              "Value": "ns-3333.awsdns-33.com."
            }
          ]
        },
        {
          "Name": "sample.xxx.",
          "Type": "SOA",
          "TTL": 900,
          "ResourceRecords": [
            {
              "Value": "ns-0000.awsdns-00.co.uk. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"
            }
          ]
        },
        {
          "Name": "sub.sample.xxx.",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "10.0.0.10"
            }
          ]
        }
      ]
    ]
  },
]

スクリプトの解説

スクリプトの大まかな流れは以下の通りです。

  1. ホストゾーンの一覧を取得(list_hosted_zonesメソッド)
  2. ホストゾーンの詳細を取得(get_hosted_zoneメソッド)
  3. ゾーン内のレコードのリストを取得(list_resource_record_setsメソッド)

コード自体はシンプルなので説明を割愛しますが、Boto3のPaginatorについては解説しておきます。

Paginator

Paginatorはlist系のメソッドにおいて、ページネーションを行いながらデータを取得するBoto3の便利機能です。

ここでは以下で使用するlist_hosted_zonesメソッドを例に上げ説明します。

以下はlist_hosted_zonesメソッドの返り値の例です。

list_hosted_zonesメソッドの返り値の例

{
    'HostedZones': [...],
    'Marker': 'xxxxxx',
    'IsTruncated': True,
    'NextMarker': 'yyyyyy',
    'MaxItems': '100'
}

注目すべきは以下の3つです。

  • Marker
  • NextMarker
  • MaxItems

Boto3のlist系のメソッドではMaxItemsで取得するリスト内の要素数を制限できます(ここではHostedZonesリストの中身)。

list_hosted_zonesでは一回で取得できる要素の上限は100個です。 つまりはホストゾーンが100個以上存在する場合は再度データを取得する必要があります。

ここで登場するのがMarkerです。 まだ取得できていない要素があると返り値のNextMarkerに値が入った状態になります(なければフィールド自体が存在しない)。 次に呼び出す際はNextMarkerの値を関数の呼び出し時にMarkerという引数に渡します。

以下はMarkerを使ってすべてのホストゾーンの値を取得する例です。

Markerを使った全ホストゾーンの取得

def get_hosted_zones(marker=None, ini=[]):
    res = None
    if marker is None:
        res = route53.list_hosted_zones()
    else:
        res = route53.list_hosted_zones(Marker=marker)
    if res.get('NextMarker') is None:
        return [*ini, *res['HostedZones']]
    else:
        return get_hosted_zones(res.get('NextMarker'), [*ini, *res['HostedZones']])

ここでは再帰を使用してNextMarkerが取得できなくなるまでループを回しています。 6行目でlist_hosted_zonesを取得する際にMarkerを指定しています。

このようなMarkerを使用したデータ取得の仕組みはlist系の多くのメソッドに採用されています。 共通の仕組みを持っているのでこれをラップするような便利な機能があります。 それがPaginatorです。

以下は先程のget_hosted_zones関数と同じ値を返します。(上述のスクリプトではジェネレータを使っていますが、ここでは説明のためにリストを返すように変更しています)

Paginatorを使った例

def get_hosted_zones():
    paginator = route53.get_paginator('list_hosted_zones')
    hosted_zones = []
    for page in paginator.paginate():
        for hz in page['HostedZones']:
            hosted_zones.append(hz)
    return hosted_zones

注目すべきは以下の2つです。

  • get_paginator
  • paginate

get_paginator

get_paginatorメソッドはページネーションに対応したメソッドのラッパーのようなオブジェクトを返します。 このオブジェクトがPaginatorです。 get_paginatorの第一引数に関数名を指定すれば、ラップしてほしい関数のPaginatorが取得できます(ここではlist_hosted_zonesです)

ちなみにメソッドが対応しているかどうかはcan_paginateメソッドで確認できます。

can_pagenate

route53.can_paginate("list_hosted_zones")
=> True

pagenate

先程作成したPaginatorはlist_hosted_zonesのラッパーなので、関数を呼び出す必要があります。 呼び出しに使用するのがPaginatorオブジェクトのpagenateメソッドです。

pagenateメソッドはPaginatorで指定した関数の値を返すようなイテレータを返します。

pagenate

paginator.paginate()
=> list_hosted_zonesの結果を順に返すイテレータ

もう少し噛み砕くと以下のような値を返すと思ってもらって良いです。

pagenate

paginator.paginate()
=> [
    { // このオブジェクトの中身はlist_hosted_zonesの返り値と同じ
        'HostedZones': [...],
        'IsTruncated': True,
        'NextMarker': 'xxxxxx',
        'MaxItems': '100'
    },
    {
        'HostedZones': [...],
        'Marker': 'xxxxxx',
        'IsTruncated': False,
        'MaxItems': '100'
    },
]

これをFor文で扱った場合には値の取得は一度に行われるわけではなく、ループが一周するごとに取得されます。 要は遅延評価をしているので、これをほしい分だけ繰り返せば良いです。

メソッドへの引数はどうやって渡すかというと、pagenateメソッドを呼び出す際に指定します。

以下は上述のスクリプトのレコードの一覧を取得する関数です。 list_resource_record_setsメソッドではホストゾーンのIDを指定する必要があるのですが、ここではpagenateメソッドを経由して呼び出すことになるので、そこで引数を渡しています。

def get_record_sets(hz_id):
    paginator = route53.get_paginator('list_resource_record_sets')
    pages = paginator.paginate(HostedZoneId=hz_id)
    return [
        page['ResourceRecordSets']
        for page in pages
    ]

終わりに

今回はRoute53のホストゾーンの一覧とそれに設定されているレコードの一覧を取得するスクリプトを書いてみました。 ホストゾーンの数が少なければ手動でも情報を閲覧できますが、数が多くなるとスクリプトを使って情報を整理したくなる局面があると思います。 そんなときに使ってもらえたら嬉しいです。