最近、仕事でRoute 53に対して数十件のレコードをAWSマネジメントコンソールの画面から手動で登録したんですが、登録したレコードの中にはMXレコードやSPFレコードが含まれていたため、ミスってたら怖いなぁと思い、以前から気になっていたawspecを触ってみるよい機会だと思い使ってみました。
ということで、ここではawspecを使ってRoute 53のレコードが正しく設定されているかを確認する手順を示します。
awspecとは?
awspecのGitHubのページを見ると「RSpec tests for your AWS resources.」とのこと。
RSpecの記法で書けるServerspecのAWS版といったところでしょうか。
今回の実行環境
- macOS Catalina バージョン 10.15.5
必要なパッケージ
- Ruby
- Bundler
RubyとBundlerを利用するため、実行環境にインストール済みか確認します。
$ ruby -v
ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-darwin19]
$ bundler -v
Bundler version 2.2.16
事前準備
awspecではAWSの各リソースの読み取り権限のポリシーが付与されたIAMユーザーが必要です。
別の記事で紹介した「SecurityAudit」というIAMポリシーを利用することでAWSの多くのリソースにアクセスすることが可能ですが、今回はRoute 53に対してのみ読み取り権限があれば十分なので、「AmazonRoute53ReadOnlyAccess」のポリシーを付与してIAMユーザーを作成しました。
ユーザー名は「Route53ReadOnly」としました。マネジメントコンソールでは利用しないため、「プログラムによるアクセス」にのみチェックしておきます。
「既存のポリシーを直接アタッチ」を選択して、「AmazonRoute53ReadOnlyAccess」にチェックします。
「Name」というキー名に「Route53ReadOnly」を設定します。(このステップはスキップしてもOKです)
上記手順で作成したIAMユーザーの認証情報はあとの手順で利用するので控えておきます。
$ cat ~/Downloads/new_user_credentials.csv
User name,Password,Access key ID,Secret access key,Console login link
Route53ReadOnly,,XXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,https://XXXXXXXXXXXX.signin.aws.amazon.com/console
awspecのインストール
インストール作業用のディレクトリを作成し、そのディレクトリ配下にGemfileを作成します。
$ mkdir awspec_r53
$ cd awspec_r53
$ bundle init
Writing new Gemfile to /PATH_TO/awspec_r53/Gemfile
$ cat <<EOF >> Gemfile
gem "awspec"
gem "rake"
EOF
カレントディレクトリ配下にインストールするために「bundle config set --local path」を実行した後に「 bundle install」を行います。
$ bundle config set --local path 'vendor/bundle'
$ cat .bundle/config
---
BUNDLE_PATH: "vendor/bundle"
$ bundle install
Fetching rake 13.0.3
Installing rake 13.0.3
・
・
・
Bundle complete! 2 Gemfile dependencies, 290 gems now installed.
Bundled gems are installed into `./vendor/bundle`
以前は「bundle install --path」のように実行していたと思いますが、この手順は非推奨になっているようです。
$ bundle install --path vendor/bundle
[DEPRECATED] The `--path` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set --local path 'vendor/bundle'`, and stop using this flag
awspecの初期設定
awspecの初期設定を行うために、「bundle exec awspec init」でawspecのファイル群を生成します。
今回はawspecをローカル環境にインストールしたので、「bundle exec awspec init」を実行します。グローバルにインストールした場合は「awspec init」でよいはずです。
$ bundle exec awspec init
+ spec/
+ spec/spec_helper.rb
+ Rakefile
+ spec/.gitignore
+ .rspec
事前に控えておいたIAMユーザーの認証情報を以下のように入力し、「spec/secrets.yml」ファイルを作成します。
$ cat <<EOF > spec/secrets.yml
region: ap-northeast-1
aws_access_key_id: XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
EOF
これで準備が整いました。
specファイルの書き方について
Route 53のホストゾーンに対応するspecファイルの書き方については、ドキュメントがありますので事前に目を通しておきましょう。
Route 53で登録可能なレコードタイプには以下のようなものがあります。
「awspec/lib/awspec/matcher/have_record_set.rb」の以下のコードを見ると、対応しているレコードタイプを確認できます。
%w(soa a txt ns cname mx ptr srv spf aaaa caa).each do |type|
chain type do |value|
@type = type
@value = value
@options = {} if @options.nil?
end
end
ホストゾーンの作成
本番環境で実際に登録したレコード群をここで例示することはできないので、今回は個人で管理しているドメインを元に一時的にRoute 53のホストゾーンとして登録し、テストを書いてみようと思います。
Route 53の画面で「fifty-four.rocks」をホストゾーンとして登録します。
初期状態ではNSレコードとSOAレコードが作成されています。
specファイルの作成と実行
上記の初期状態のレコードに対してテストを作成します。
まず、「fifty-four.rocks」のホストゾーンが存在しているかを確認します。
次に初期状態のレコード数は2レコードなので、カウントが合致しているかを確認します。
NSレコードはデフォルトで4行登録されているので、ここでは配列の形式で定義し、引数に渡す際に改行を区切り文字として指定しています。
SOAレコードは文字列なので、そのまま引数に渡します。
$ cat spec/route53_spec.rb
require 'spec_helper'
describe route53_hosted_zone('fifty-four.rocks.') do
it { should exist }
its(:resource_record_set_count) { should eq 2 }
ns = [
'ns-1931.awsdns-49.co.uk.',
'ns-296.awsdns-37.com.',
'ns-902.awsdns-48.net.',
'ns-1286.awsdns-32.org.',
]
it { should have_record_set('fifty-four.rocks.').ns(ns.join("\n")).ttl(172800) }
soa = 'ns-1931.awsdns-49.co.uk. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400'
it { should have_record_set('fifty-four.rocks.').soa(soa).ttl(900) }
end
それではテストを実行してみます。
今回はrakeをローカル環境にインストールしたので、「bundle exec rake spec」を実行します。グローバルにインストールした場合は「rake spec」でよいはずです。
$ bundle exec rake spec
route53_hosted_zone 'fifty-four.rocks.'
is expected to exist
is expected to have record set "fifty-four.rocks."
is expected to have record set "fifty-four.rocks."
resource_record_set_count
is expected to eq 2
Finished in 1.8 seconds (files took 2.95 seconds to load)
4 examples, 0 failures
4件のテストがすべて成功しました。
次にfifty-four.rocksのAレコードをRoute 53に登録します。
その前に、現状のAレコードをdigコマンドで確認してみましょう。
$ dig fifty-four.rocks a +short
3.0.239.142
104.248.158.121
上記の2レコードが返ってきましたので、このテストをspecファイルに追加します。変更箇所は以下の通りです。
cat spec/route53_spec.rb
require 'spec_helper'
describe route53_hosted_zone('fifty-four.rocks.') do
・
・
・
its(:resource_record_set_count) { should eq 3 }
・
・
・
a = [
'3.0.239.142',
'104.248.158.121',
]
it { should have_record_set('fifty-four.rocks.').a(a.join("\n")).ttl(300) }
end
テストを実行して、失敗することを確認します。
$ bundle exec rake spec
route53_hosted_zone 'fifty-four.rocks.'
is expected to exist
is expected to have record set "fifty-four.rocks."
is expected to have record set "fifty-four.rocks."
is expected to have record set "fifty-four.rocks."
resource_record_set_count
is expected to eq 3 (FAILED - 1)
Failures:
1) route53_hosted_zone 'fifty-four.rocks.' resource_record_set_count is expected to eq 3
Failure/Error: its(:resource_record_set_count) { should eq 3 }
expected: 3
got: 2
(compared using ==)
# ./spec/route53_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 1.76 seconds (files took 4.06 seconds to load)
5 examples, 1 failure
Failed examples:
rspec ./spec/route53_spec.rb:6 # route53_hosted_zone 'fifty-four.rocks.' resource_record_set_count is expected to eq 3
テストを再度実行します。
$ bundle exec rake spec
route53_hosted_zone 'fifty-four.rocks.'
is expected to exist
is expected to have record set "fifty-four.rocks."
is expected to have record set "fifty-four.rocks."
is expected to have record set "fifty-four.rocks."
resource_record_set_count
is expected to eq 3
Finished in 1.56 seconds (files took 4.11 seconds to load)
5 examples, 0 failures
5件のテストがすべて成功しました。
こういった感じで、後は「テストを作成 → レコードを登録 → テストを実行」を繰り返すことで安心してDNSの設定変更作業を行うことができるのではないでしょうか。