ユーザーデータを用いてAmazon Linux 2のNATインスタンスを手軽に作成する方法

こんにちは、Insight EdgeのLead Engineerの日下です。 Insight Edgeでは、技術検証から実証実験、商用サービス開発に至るまで常日頃からAWSやGoogle Cloudなどのクラウドサービスを活用しており、開発サイクルのスピードアップに欠かせないツールとなっています。 一方、オンプレミスのハードウェアとは異なり、クラウドサービス利用時には開発者それぞれがコストを意識して使い方に気をつけないと、思わぬ課金が発生しまうこともあります。 本記事では、AWSでVPCネットワークを構築するときに思わぬコスト増の要因となりやすいNATゲートウェイの節約手段のひとつとして、NATインスタンスを手軽に作成する方法を紹介します。

要約

  • NATゲートウェイは料金が高く、用途によってはNATインスタンスの方がコスト最適
  • Amazon Linux 2のEC2インスタンス作成時にユーザーデータを指定することで、手軽にNATインスタスを作成可能
  • 本番環境はNATゲートウェイ、開発環境はNATインスタンスなど、必要な性能や可用性に応じて使い分けるとよい

NATゲートウェイはお高い

AWSにおいて、VPCを構築しサーバやデータベースを配置するのは典型的なインフラ構築パターンのひとつであり、セキュリティを向上させるために各種サーバはプライベートサブネットに配置し、インターネットアクセスが必要な場合にはNATを経由するといった使い方がよくされます。 現在のAWSの公式ドキュメントによるとNATの実装にはNATゲートウェイの利用を推奨していますが、実はNATゲートウェイは置いておくだけで月額約45USDかかるリソースであり、性能や可用性を求められない用途で使うにはちょっと高いなと感じてしまいます。

特に、開発や検証用など利用者が内部に限られている環境では、低スペックなNATインスタンスで十分なケースも多々あります。 プライベートサブネット内からたまにインターネットアクセスする程度、例えばCodeBuildでDockerイメージをPullしたり、依存ライブラリをインストールしたりなど開発ワークフローの中で時々通信が発生する程度であれば、最安のt4g.nanoで運用することも可能だったりします。 仮にt4g.nanoを利用してNATインスタンスを作成した場合、下表のようにEBSを含めてもNATゲートウェイの約1/10のコストでNAT機能を実現できます。

NATゲートウェイ 月額 NATインスタンス 月額
プロビジョニング (0.062USD/時) 44.64USD/月 コンピューティング (t4g.nano 0.0054USD/時) 3.888USD/月
データ処理(0.062USD/GB) データ量による EBSストレージ (gp3 8GB 0.096USD/GB月) 0.768USD/月
合計 44.64USD〜/月 合計 4.656USD/月

(2022年11月時点、東京リージョンでの比較。月額は30日で計算。VPCリソースにかかるアウトバウンド通信料金は同じ条件なので省略)

また、NATインスタンスを利用する場合、必要な性能に応じてインスタンスタイプを調節したり、使わない時間帯はインスタンスを停止しておく、Saving Plansを適用するなど、通常のEC2インスタンスと同様のコスト最適化手段をとることも可能になります。

NATインスタンスを手軽に作成するには?

以前はNATインスタンス用のAMIがAWSから提供されていましたがすでにサポートが切れており、現在はNATゲートウェイの利用が推奨されているためか、最新のAmazon Linux 2向けのAMIは公式には提供されていません。 Amazon Linux 2ベースでも必要なサービスをインストールして設定すればNATインスタンス化は可能であり、NATインスタンスの公式ドキュメントを見るとNATインスタンスがユースケースに合致している場合には独自のNAT AMIを作成できると書かれていますが、AMIをベースにすると、AMIの管理、共有、更新などが必要になってしまうため扱いが面倒です。 そこで、EC2インスタンス作成時のオプション設定であるユーザーデータを用いてAmazon Linux 2ベースのNATインスタンスを作成する方法を試してみました。 ユーザーデータによってEC2インスタンス作成時にNAT機能を有効化するスクリプトを実行することで、あらかじめ設定したAMIイメージを保存や共有したりすることなく、任意のAWSアカウント及びリージョンで、その時の最新バージョンの公式AMIをベースにNATインスタンスを手軽に作成できます。

さて、ここまでを読んで完全に理解した方は、ユーザーデータのスクリプトまたはCloudFormationテンプレート例までスキップしていただいても大丈夫です。 以下の節では、Amazon Linux 2のNATインスタンスを作成する具体的な手順を説明します。

Amazon Linux 2 で NATインスタンスを作成する手順

前提VPC環境

前提として下図のようなVPC環境があるものとします。

前提VPC環境

本記事の手順で新たに作成する部分は以下の赤文字の部分です。

本記事で作成する部分

AWSマネジメントコンソールからEC2インスタンスを作成

AWSマネジメントコンソールのGUIからEC2インスタンスを作成します。 本記事ではNAT料金の節約をテーマにしているため、最安となるt4g.nanoを利用する場合の手順を示します。

EC2の画面からインスタンスの起動を選択し、以下の通り設定していきます。

  • ベースとなるAMIは Amazon Linux 2 を選択
  • アーキテクチャは 64ビット(Arm) を選択
  • インスタンスタイプは t4g.nano を選択(Armアーキテクチャを選択していないと選択肢が現れないので注意)

次に、インスタンスに割り当てるキーペアを選択します。 本記事の手順ではインスタンスにログインしないため、キーペアなしでも問題ありません。

続いて、ネットワーク設定を編集します。

  • サブネットには、パブリックサブネットを選択
  • パブリックIPの自動割り当てを有効化(NATインスタンスはパブリックIPを持つ必要があるため)
  • セキュリティグループは、(本記事では)VPCのデフォルトセキュリティグループを選択

実際に利用する際には、環境に応じて適切なセキュリティグループを割り当ててください。 NATインスタンスは、プライベートサブネットからの通信を受け取ってインターネットアクセスを行うため、セキュリティグループのインバウンドルールでプライベートサブネットからの通信を受け入れアウトバウンドルールでインターネットへのアクセス許可が必要です。

ストレージ設定はデフォルトの8GBで作成します。 デフォルトのgp2でも良いですがgp3の方がやや安くコスパに優れるので、ここではgp3を選択しています。

高度な詳細 の設定項目を開きます。 設定項目最下部の ユーザーデータ に以下のスクリプトを設定します。

#!/bin/bash

yum install -y iptables-services
iptables -F
echo 1 > /proc/sys/net/ipv4/ip_forward
echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
service iptables save
systemctl start iptables
systemctl enable iptables

ここまで設定したら、 インスタンスを起動 します。

ソース/宛先チェックを無効化

EC2インスタンス作成後、インスタンス一覧画面に戻りインスタンスのネットワーキング設定から、ソース/宛先チェックを変更 を選択し、停止 にチェックを入れて保存します。 これを設定しておかないと、プライベートサブネットから送信されるインターネット向けのパケットをインスタンスが受信できず、NATとして動作しません。

ここまで設定すれば、NATインスタンスの作成は完了です。

ルーティングテーブルを設定

NATインスタンスの作成が完了したら、NATゲートウェイ利用時と同様にルーティングテーブルを編集して、NATインスタンスを経由してインターネットにアクセスできるように設定します。 プライベートサブネットに割り当てられているルーティングテーブルを編集し、送信先 0.0.0.0/0 のターゲットにNATインスタンスを指定します。

動作確認

確認のために、プライベートサブネット内からインターネットアクセスを試してみます。 本記事では、プライベートサブネットに接続したLambda関数を作成して通信してみます。

Lambda関数を作成

Python 3.9でLambda関数を作成します。 アーキテクチャとアクセス権限はデフォルト設定で作成します。

詳細設定を開き、VPCを有効化 にチェックを入れます。 NATインスタンスと同じVPCを選択し、プライベートサブネットを選択します。 セキュリティグループはここではNATインスタンス同様、VPCのデフォルトセキュリティグループを使用します。

上記の設定で関数を作成します。

関数のコードを編集

Insight EdgeのWebサイトにアクセスするプログラムで実験してみます。 以下のようにコードを編集します。

# lambda_function.py

from urllib.request import urlopen

def lambda_handler(event, context):
    with urlopen("https://insightedge.jp/") as f:
        res = f.read().decode()
        print(res)
        return {
            'statusCode': 200,
            'body': res 
        }

Deployボタンを押して、関数をデプロイします。

実行

Testボタンを押して、新しいテスト用イベントを作成し、実行してみます。 以下のように、WebサイトのHTMLを取得できていれば成功です。

失敗するケースの確認

試しに、NATインスタンスを停止して実行してみます。 EC2コンソールからNATインスタンスを停止します(終了ではないので注意)。

Lambda関数の画面に戻り、Testボタンを押して再度実行してみます。 通信経路上のNATインスタンスが停止しているため、タイムアウトエラーが発生します。

CloudFormationでNATインスタンスを作成

CloudFormationでNATインスタンスを作成する場合のテンプレート定義例です。 インスタンス作成、およびソース/宛先チェック無効化を定義しています。 詳細な手順は割愛しますが、パラメータにAMIのID、パブリックサブネットのID、セキュリティグループIDを指定するとNATインスタンスを作成できます。

AWSTemplateFormatVersion: '2010-09-09'
Description: Create NAT Instance.

Parameters:
  amiId:
    Type: String
    Default: ami-082dd9d89994d3690  # Amazon Linux 2 (Arm64) 東京リージョン
  publicSubnetId:
    Type: String
    Default: subnet-xxxxxxxxxxxxxxxxx
  securityGroupId:
    Type: String
    Default: sg-xxxxxxxxxxxxxxxxx

Resources:
  natInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref amiId
      InstanceType: t4g.nano
      NetworkInterfaces:
        - DeviceIndex: 0
          SubnetId: !Ref publicSubnetId
          AssociatePublicIpAddress: true
          GroupSet:
            - !Ref securityGroupId
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 8
            VolumeType: gp3
            Encrypted: true
      UserData: !Base64 |
          #!/bin/bash
          yum install -y iptables-services
          iptables -F
          echo 1 > /proc/sys/net/ipv4/ip_forward
          echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
          iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
          service iptables save
          systemctl start iptables
          systemctl enable iptables
      SourceDestCheck: false  # ソース/宛先チェックを無効化
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-nat-instance"

まとめ

本記事では手軽かつ低コストでVPCにNAT機能を導入する方法として、ユーザーデータを用いたNATインスタンス作成方法を紹介しました。 単一のEC2インスタンスであるため、可用性が低い、自動スケールしない、パッチ当てなどの保守が必要といったデメリットはあるものの、NATゲートウェイに比べてコストを大幅に抑えられるのは魅力です。 そのため、本番環境ではNATゲートウェイを、開発環境ではNATインスタンスを利用するなど、必要な性能や可用性によって使い分けるとよいでしょう。