Chef:自前NATインスタンスを作ってみた(Amazon Linux 2014.03版)

2014.05.09

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

こんにちは、三井田です。

VPCでプライベートサブネットからインターネットにアクセスしたい場合、NATインスタンスを利用することがあります。

参考資料:Amazon VPC ユーザガイド

  • amzn-ami-vpc-nat-pv-2013.03.1.x86_64-ebs - ami-5f840e5e
  • amzn-ami-vpc-nat-pv-2013.09.0.x86_64-ebs - ami-cd43d9cc

このエントリでは、上記のAWSが用意しているNATインスタンスを使うのではなく、自前でNATインスタンスを作ってみました。

自前で作ることで、次のようなメリットがあると思っています。

  • 最新のAmazon Linuxベースで、他のサーバなどと環境を合わせることができる
  • amzn-ami-minimal-pvイメージを利用すると、EBSタイプのインスタンスでは、EBSは2GBと少量であり、コストが節約できる
  • さらに、インスタンスストアタイプのAMIでは、そもそもEBSを利用しないのでさらに節約ができる
    • NATインスタンスには重要な永続的なデータは通常必要ないため、インスタンスストアタイプでも十分実用的かと思います

NATインスタンスのシステム要件

NATインスタンスを端的に描写すると、IPマスカレード機能を提供するLinuxルータです。

  1. Linuxカーネルパラメータ"net.ipv4.ip_forward"の値が「1」である
  2. iptablesのNATテーブルでPOSTROUTINGチェインにMASQUERADE設定がある
  3. EC2の送信元/送信先チェック機能がオフになっている

上記の3は冒頭で紹介した「Amazon VPC ユーザガイド」にあるようにマネジメントコンソールなどで変更できます。 1と2についてはインスタンスのOSレベルでの設定となります。

Let's Cooking

近日中にGithubに公開予定です

下で説明する「NATインスタンスを構築するChefクックブック」で作成したクックブックで、EC2インスタンスを構築してみましょう。

次の4つのAMIで試してみました。(AMI IDは東京リージョンのものです)

  • amzn-ami-pv-2014.03.0.x86_64-ebs - ami-a1bec3a0
  • amzn-ami-minimal-pv-2014.03.0.x86_64-ebs - ami-81bec380
  • amzn-ami-pv-2014.03.0.x86_64-s3 - ami-91bec390
  • amzn-ami-minimal-pv-2014.03.0.x86_64-s3 - ami-b7bfc2b6

インスタンスを立ち上げたら、マネジメントコンソールから「Change Source/Dest. Check」を選び、Source/Dest. CheckをDisabledにします。

その後、以下のコマンドを実行してNATインスタンスを構築します。

$ knife solo prepare ec2-user@***-***-***-***
$ knife solo cook ec2-user@***-***-***-*** -o "recipe[nat-instance]"

動作確認

プライベートサブネットのルーティングテーブルの0.0.0.0/0のターゲットをNATインスタンスに変更します。

NATインスタンスを踏み台にして、プライベートサブネットのインスタンスにログインして、pingを実行してみましょう。NATインスタンスでは、tcpdumpを実行してパケットの流れを見てみます。

NATインスタンス側で、ソースIPが書き変わったパケットが宛先に出ていれば成功ですね。

※プライベートサブネットのインスタンス
[ec2-user@ip-172-31-38-163 ~]$ ping -c 3  www.google.co.jp
PING www.google.co.jp (173.194.126.215) 56(84) bytes of data.
64 bytes from nrt04s07-in-f23.1e100.net (173.194.126.215): icmp_seq=1 ttl=57 time=3.10 ms
64 bytes from nrt04s07-in-f23.1e100.net (173.194.126.215): icmp_seq=2 ttl=57 time=3.24 ms
64 bytes from nrt04s07-in-f23.1e100.net (173.194.126.215): icmp_seq=3 ttl=57 time=3.38 ms

--- www.google.co.jp ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2006ms
rtt min/avg/max/mdev = 3.101/3.244/3.389/0.117 ms
※NATインスタンス
[root@ip-172-31-15-6 ec2-user]# tcpdump -i eth0 -nn 'not port 22'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
09:23:10.521707 IP 172.31.38.163 > 173.194.126.215: ICMP echo request, id 13317, seq 1, length 64
09:23:10.521748 IP 172.31.15.6 > 173.194.126.215: ICMP echo request, id 13317, seq 1, length 64
09:23:10.524414 IP 173.194.126.215 > 172.31.15.6: ICMP echo reply, id 13317, seq 1, length 64
09:23:10.524431 IP 173.194.126.215 > 172.31.38.163: ICMP echo reply, id 13317, seq 1, length 64
09:23:11.523482 IP 172.31.38.163 > 173.194.126.215: ICMP echo request, id 13317, seq 2, length 64
09:23:11.523535 IP 172.31.15.6 > 173.194.126.215: ICMP echo request, id 13317, seq 2, length 64
09:23:11.526193 IP 173.194.126.215 > 172.31.15.6: ICMP echo reply, id 13317, seq 2, length 64
09:23:11.526209 IP 173.194.126.215 > 172.31.38.163: ICMP echo reply, id 13317, seq 2, length 64
09:23:12.525020 IP 172.31.38.163 > 173.194.126.215: ICMP echo request, id 13317, seq 3, length 64
09:23:12.525081 IP 172.31.15.6 > 173.194.126.215: ICMP echo request, id 13317, seq 3, length 64
09:23:12.527900 IP 173.194.126.215 > 172.31.15.6: ICMP echo reply, id 13317, seq 3, length 64
09:23:12.527915 IP 173.194.126.215 > 172.31.38.163: ICMP echo reply, id 13317, seq 3, length 64
^C
12 packets captured
12 packets received by filter
0 packets dropped by kernel

まとめ

このように、NATインスタンスは手軽に構築することが出来ました。

このレシピで、Source/Dest. Checkの変更まで出来ればさらに自動化を進められそうです。

付録:NATインスタンスを構築するChefクックブック

手許の環境は、Mac OS X Merveriksです。以下の資料を参考にknife soloとberkshelfを実行できる環境を作りました。

また、なるべく公開されている資産を使う方針で、Opscoe Community Cookbooksのクックブックを活用するようにしました。

Chefリポジトリと空のクックブックの作成

まず、Chefリポジトリを作り、クックブックを作成しました。ディレクトリ構成は次のようになっています。

$ knife solo init chef-repo
$ cd chef-repo
$ knife cookbook create nat-instance -o site-cookbooks
$ tree -F
.
├── Berksfile
├── cookbooks/
├── data_bags/
├── environments/
├── nodes/
├── roles/
└── site-cookbooks/
    └── nat-instance/
        ├── CHANGELOG.md
        ├── README.md
        ├── attributes/
        ├── definitions/
        ├── files/
        │   └── default/
        ├── libraries/
        ├── metadata.rb
        ├── providers/
        ├── recipes/
        │   └── default.rb
        ├── resources/
        └── templates/
            └── default/

nat-instanceクックブックの作成

それでは、nat-instanceクックブックを作成していきます。 まず、chef-repo/Berksfileに依存するコミュニティクックブックを記載しておきます。

source "http://api.berkshelf.com"

cookbook 'sysctl'
cookbook 'iptables'

続いて、chef-repo/site-cookbooks/metadata.rbにも同様に追記しておきます。

name             'nat-instance'
<<略>>
version          '0.1.0'

depends          'sysctl'
depends          'iptables'

次にレシピ本体を見てみましょう。chef-repo/site-cookbooks/nat-instance/recipes/default.rbです。

include_recipe 'sysctl'
sysctl_param 'net.ipv4.ip_forward' do
  value 1
end

include_recipe 'iptables'
iptables_rule 'ip_masquerade'

はい!これだけです。2行目のsysctl_paramsysctlクックブックから、7行目のiptables_ruleiptablesクックブックからの借り物です。

iptables_rule 'ip_masquerade'の部分で、iptablesのNATテーブルにルールを追加しています。追加するルールは、次のchef-repo/site-cookbooks/nat-instance/template/default/ip_masquerade.erbファイルに記載してあります。

*nat
-A POSTROUTING -s <%= node.vpc_cidr %> -j MASQUERADE

上記のテンプレートで<%= node.vpc_cidr %>と変数を埋め込んでいます。このデフォルト値は、chef-repo/site-cookbooks/nat-instance/attributes/default.rbに設定してあります。

EC2のmeta-dataからVPCのCIDRを取得しているのですが、取得する際にohaiが収集したノード情報node[:macaddress]を利用しているのがTipです。

default["vpc_cidr"] = `curl -q http://169.254.169.254/latest/meta-data/network/interfaces/macs/#{node[:macaddress].downcase}/vpc-ipv4-cidr-block 2>/dev/null`

以上です、ではまた!