kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

roadworkerでいろいろやってみる

f:id:kun432:20210519005723p:plain

何周遅れかわかりませんが、Route53のDNSレコードをコードでできるroadworkerでいろいろやってみたのでメモ。

Rubyとroadworkerのバージョンは以下のとおりです。

$ ruby -v
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

$ roadwork -v
roadwork 0.5.14

目次

変数

単純に変数を定義して参照するだけです。文字列としてクォート内で参照する場合には#{変数名}で。

domain = "example.com"
default_ttl = 100

hosted_zone domain do
  rrset "www.#{domain}", "A" do
    ttl default_ttl
    resource_records(
      "192.168.0.1"
    )
  end
end

結果

レコード名 タイプ TTL
www.example.com A 192.168.0.1 100

ループ

eachを使って単純なループをさせる場合。数値計算も普通にできますね。

domain = "example.com"
default_ttl = 100

hosted_zone domain do
  (1..9).each do |n|
    rrset "www#{n}.#{domain}", "A" do
      ttl default_ttl + 200
      resource_records(
        "192.168.1.#{n}"
      )
    end
  end
end

結果

レコード名 タイプ TTL
www1.example.com A 192.168.1.1 300
www2.example.com A 192.168.1.2 300
www3.example.com A 192.168.1.3 300
www4.example.com A 192.168.1.4 300
www5.example.com A 192.168.1.5 300
www6.example.com A 192.168.1.6 300
www7.example.com A 192.168.1.7 300
www8.example.com A 192.168.1.8 300
www9.example.com A 192.168.1.9 300

配列/ハッシュ

配列・ハッシュを組み合わせるこんなこともできます。

domain = "example.com"
default_ttl = 100
subnet = "192.168.2"

servers = [
  { name: "www",  record: "#{subnet}.1" },
  { name: "mail", record: "#{subnet}.2" },
  { name: "app",  record: "#{subnet}.3" }
]

hosted_zone domain do
  servers.each do |server|
    rrset "#{server[:name]}.#{domain}", "A" do
      ttl default_ttl
      resource_records(
        server[:record]
      )
    end
  end
end

結果

レコード名 タイプ TTL
www.example.com A 192.168.2.1 100
mail.example.com A 192.168.2.2 100
app.example.com A 192.168.2.3 100

文字列の結合

普通にrubyの関数が使えるので、文字列結合させて、こんなこともできます。

domain = "example.com"
default_ttl = 100
sender_ips = [
  "192.168.3.1/32",
  "192.168.3.2/32",
  "192.168.3.3/32",
  "192.168.3.4/32",
]
str_txt_record = sender_ips.map{|r| "ip4:" + r}.join(" ")

hosted_zone domain do
  rrset "#{domain}", "TXT" do
    ttl default_ttl
    resource_records(
      "\"v=spf1 #{str_txt_record} ~all\""
    )
  end
end

結果

レコード名 タイプ TTL
example.com TXT "v=spf1 ip4:192.168.3.1/32 ip4:192.168.3.2/32 ip4:192.168.3.3/32 ip4:192.168.3.4/32 ~all" 100

テンプレート

templateを使うと、よく使う設定を使いまわすことができます。

domain = "example.com"
default_ttl = 100

template "default_rrset" do
  rrset context.name + "." + context.hosted_zone_name, "A" do
    ttl context.ttl
    resource_records(
      "192.168.4.1"
    )
  end
end

hosted_zone domain do
  context.ttl = default_ttl
  include_template "default_rrset", :name => "customer1"
  include_template "default_rrset", :name => "customer2"
  include_template "default_rrset", :name => "customer3"
end

結果

レコード名 タイプ TTL
customer1.example.com A 192.168.4.1 100
customer2.example.com A 192.168.4.1 100
customer3.example.com A 192.168.4.1 100

hosted_zoneのゾーン名やtemplateに渡す変数に対してcontext.*でアクセスできるようです。ブロック内の定義はtemplateの外で明示的にcontext.*と指定しないとtemplateからアクセスできないみたいです。contextがhostedゾーンブロック内のローカル変数って感じなんでしょうか?試しにこう書いてみるといけましたので、多分あってますね。

hosted_zone domain do
  local_ttl = default_ttl
  include_template "default_rrset", :name => "customer1", :ttl => local_ttl
  include_template "default_rrset", :name => "customer2", :ttl => local_ttl
  include_template "default_rrset", :name => "customer3", :ttl => local_ttl
end

テスト

-tでテストができますが、更新時とかは特に失敗することが多いと思います。

$ roadwork -a
Apply `Routefile` to Route53
=== Change batch: example.com. | /hostedzone/XXXXXXXXXXXXXX
Create ResourceRecordSet: customer1.example.com. A
Create ResourceRecordSet: customer2.example.com. A
Create ResourceRecordSet: customer3.example.com. A
--> Change submitted: /change/XXXXXXXXXXXXXXXXXXXXX

$ roadwork -t
FFF
customer1.example.com. A: Query timed out
customer2.example.com. A: Dnsruby::NXDomain
customer3.example.com. A: Dnsruby::NXDomain
3 examples, 3 failures

通常は(プロバイダ等の)キャッシュDNSサーバに聞きに行くと思いますが、変更前のキャッシュがまだ残っていたりする場合などですね(上記の例だとネガティブキャッシュが残ってるのだと思います)。こういう場合は権威DNSサーバ、つまりRoute53で割り当てられたNSレコードのDNSサーバに直接聞けばよいです。

$ dig example.com ns +short
ns-XXXX.awsdns-XX.org.
ns-XXXX.awsdns-XX.co.uk.
ns-XXX.awsdns-XX.com.
ns-XXX.awsdns-XX.net.

$ roadwork -t --nameservers ns-XXXX.awsdns-XX.org
...
3 examples, 0 failure

あと設定があっている場合は上記のようにあまり出力されないようなのですが、--debugをつけるとテストっぽい出力が追加されるのでこっちのほうが良い感じがします。

$ roadwork -t --nameservers ns-XXXX.awsdns-XX.org --debug
opening connection to route53.amazonaws.com:443...
opened
starting SSL for route53.amazonaws.com:443...

(...いろいろ内部処理が表示される。割愛...)

Check DNS: customer1.example.com. A
  expected=192.168.4.1(100)
  actual=192.168.4.1(100)
Check DNS: customer2.example.com. A
  expected=192.168.4.1(100)
  actual=192.168.4.1(100)
Check DNS: customer3.example.com. A
  expected=192.168.4.1(100)
  actual=192.168.4.1(100)
3 examples, 0 failure

roadwork -tは事後のテストですけど、事前のテストはどうするのがよいんでしょうか?RubyだとRSpecとかになるのかな?

まとめ

Rubyが書ければいろいろ凝ったこともできそうですが、やりすぎると何やってるかパッと見でわかりにくくなったり、DSLの意味が薄れるかなという気がします。CI/CD回してテストで担保できるようにすればいいかもしれませんね。

参考)

dev.classmethod.jp