はじめに
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": [{...}, ...],
}
]
それぞれのフィールドの中身は以下のようになっています。
- HZInfo: Boto3のget_hosted_zoneのレスポンスの
HostedZone
以下 - RecordSets: Boto3のlist_resource_record_setsのレスポンスの
ResourceRecordSets
以下
以下は実際に出力されるファイルの例です。
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"
}
]
}
]
]
},
]
スクリプトの解説
スクリプトの大まかな流れは以下の通りです。
- ホストゾーンの一覧を取得(
list_hosted_zones
メソッド) - ホストゾーンの詳細を取得(
get_hosted_zone
メソッド) - ゾーン内のレコードのリストを取得(
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のホストゾーンの一覧とそれに設定されているレコードの一覧を取得するスクリプトを書いてみました。 ホストゾーンの数が少なければ手動でも情報を閲覧できますが、数が多くなるとスクリプトを使って情報を整理したくなる局面があると思います。 そんなときに使ってもらえたら嬉しいです。