AWSでNATインスタンスを構築してみよう！


# はじめに
AWSではVPCとサブネットを上手く使い分けることでネットワーク公開領域と非公開領域を分けることができる。ネットワーク公開領域をパブリックサブネット、非公開領域をプライベートサブネットとそれぞれ呼び、プライベートサブネットにサーバーを立てることでセキュアなサーバーを構築することが可能。

この性質とNATインスタンスを利用することで、
プライベートサブネットに配置されたサーバーに対して以下のネットワーク要件が可能となる。

- ロードバランサー経由でのみアクセス可能
- 外部からサーバーに直接アクセス不可能(HTTP, HTTPS, SSHなど全て)
- サーバーからの外部サイトはアクセス可能

## NATインスタンスとは？
簡単に言うとプライベートサブネットに配置されたサーバーから外部サイトへアクセスするときに踏み台となるサーバーのこと。

### 例

データベースに接続してJSONを返す内部APIサーバーを構築したい要件。
このAPIサーバーは外部決済機能を持っていてStripeと通信を行う必要があり、今後も要件次第ではFacebookやSendgridなどの外部サイトと通信が増える可能性がある。

APIサーバーをパブリックサブネットに配置すると誰でもサーバーに接続することができてしまい、認証情報さえあればAPIサーバー経由で情報が取得できてしまうかもしれないので危険なので、セキュアなプライベートサブネットにAPIを立てることにした。
この場合だと、ネットワーク設定されていないプライベートサブネットにサーバーを配置するのでStripeなどの外部へ通信を行うことができない。

そんな要件の時に活用できる救世主がNATインスタンスである！！！！！

APIサーバーが外部通信を行いたい場合、NATインスタンスを通してネットワークにアクセスすること可能。
APIサーバーの代わりにNATインスタンスがStripeへ通信を行い決済完了の通知を受け取る。
そして決済完了の通知をAPIサーバーに伝達するという仕組み。

もう少し具体的にNATインスタンスを知りたい場合は[公式サイト](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_NAT_Instance.html)をみてね。
そもそもNATを知りたい人は[こちら](https://qiita.com/tasakii/items/60a3696ec7b86111884d)（図があって個人的に分かりやすかった）

## NATインスタンスとNAT Gatewayの違い
AWSにはNAT Gatewayが存在する。NATインスタントと何が違うの？と疑問に思う人もいるだろう。NATインスタンスの構築を自動でやってAWSコンソールから管理できるのがNAT Gatewayである。

じゃあ、最初から自分で構築せずにNAT Gateway使えばいいのではないか。
と思うかもしれないが、問題なのは料金である。

NATインスタンスはスペックにも寄るが1000円〜3000円くらい、
NAT Gatewayは7000円以上となり結構なコストになってしまう。

私が携わっているプロジェクトではコスト削減のために少々管理が面倒でもNATインスタンスを立てることにしている。

## 補足

プライベートサブネットに配置されるサーバーをECSで管理してコンテナを自動的に起動させたりしたい場合はAWSにアクセスできる必要があるため、NATインスタンスを構築する必要がある。

そうしないとECSからEC2インスタンスの紐付けができない点に注意が必要！

# 構築手順
正直なところ[こちら](https://qiita.com/TK1989/items/ece84500ee87d2d96c4b)の記事の方が説明が詳しい気もするが、今回はアウトプットも兼ねて。

## 今回構築するアーキテクチャイメージ
![NAT Instance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/7cf46804-957c-eb1b-6806-b319673697f5.png)


## ネットワークの構築
### VPC
まずはネットワークの基礎となる、VPCを作成する。
ネットワークのクラスとサブネットについての[参考記事](https://nishinatoshiharu.com/fundamental-ipaddress/#i-7)

![VPC_の作成___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/31f695fd-0146-3840-5855-4f5f576bba7a.png)

### Subnet(public, private)
ネットワーク公開設定を行うサブネット（パブリックサブネット）と、
ネットワークに接続できない設定を行うサブネット（プライベートサブネット）を作成する。

#### パブリックサブネットの作成
サブネットのネットワーク部を`172.16.0.X`に設定する。

今回の場合は、東京リージョンのアベイラビリティゾーン(AZ)は1aを指定する。
可用性を高める構成にしたい場合は1cにも同じようにサブネットを作成する。

![サブネットの作成___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/c895959c-fc02-639e-10e3-7b6066246d77.png)

#### プライベートサブネットの作成
サブネットのネットワーク部を`172.16.1.X`に設定する。

![サブネットの作成___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/098abec1-83fc-1b87-b7db-328ce2ac5902.png)

### ルートテーブルなどの接続経路設定
ネットワークの経路を設定するルートテーブルはサブネット単位で紐づけることが可能。
VPCを作成した時にメインテーブルと呼ばれるルートテーブルが作成されるので、デフォルトでプライベートサブネットの経路に設定する。


#### サブネットとルートテーブルの関連付け
1. パブリックサブネットに紐づけるルートテーブルを作成する。
  ![ルートテーブルの作成___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/48f8d16b-b280-0713-d0bc-cf1f6a5e8937.png)

1. 作成したルートテーブルをパブリックサブネットに紐づける。
  これでパブリックサブネットのネットワーク経路はこのルートテーブルの設定が適用される。
  ![サブネットの関連付けの編集___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/c5a58f0f-3b48-8407-9843-415bcf71c042.png)

1. メインルートテーブルにプライベートサブネットに紐づける。
  メインルートテーブルはすでに作成されているため、新規で作成する必要はない。
  Nameタグにroutetable-xxx-privateと設定すると後々分かりやすい！
  ![サブネットの関連付けの編集___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/560104e8-ae6b-15ba-ce19-832a80f5f309.png)


#### インターネット接続経路を確保する
インターネットに接続するためにはインターネットゲートウェイを作成して、経路を許可するIPアドレス範囲を指定する必要がある。

1. インターネットゲートウェイを作成
  ![インターネットゲートウェイの作成___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/27923013-abdf-628a-0b52-02a8f2c52fe3.png)

1. インターネットゲートウェイとVPCを紐付ける。
  デフォルトではVPCが設定されていないので個別で紐づける必要があることに注意。
  インターネットゲートウェイの状態が`attached`に変化すれば正しく設定されたことになる。
  ![VPC_にアタッチ___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/55f26fed-e305-ad34-c135-4e753cb030de.png)

1. パブリックのルートテーブルに紐付ける。
  特に通信制御せずにインターネットアクセスすると言う意味で`0.0.0.0/0`を設定する。
  ![ルートの編集___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/c6181db2-04bf-8dfd-ccb5-648699129727.png)

これでネットワーク関連の設定は完了。
その他のネットワーク許可設定は後述にあるセキュリティグループで設定する。

## NATインスタンス作成
### セキュリティグループの作成
NATインスタンスに紐づけるセキュリティグループを作成する。([参考]
(https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_NAT_Instance.html#NATSG))
サブネットを1a,1c両方に作成している場合はHTTP,HTTPSそれぞれ許可するCIDRを設定する。
(画像上はセキュリティグループの説明ないですが、適当に設定すること)

- インバウンド
![セキュリティグループ___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/b08178a4-92c3-c41a-82bf-5d36c1f55a36.png)


- アウトバウンド
![セキュリティグループ___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/7c2d93a5-c99d-629a-9cfd-6e9bc754dc5a.png)

### EC2インスタンスの作成とIPアドレスの割り当て
NAT用のEC2インスタンス専用のイメージが提供されている。([参考](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/VPC_NAT_Instance.html#NATInstance))

1. マシンイメージ(AMI)の選択
  Amazonマシンイメージの選択ウィンドウで検索するとNAT用のイメージが表示される。（`amzn-ami-vpc-nat`）今回は最新版っぽい一番上のインスタンスを利用するが、バージョンは常に更新されるのでマシンイメージ名の日付が最新のものを選択するのがおすすめ。
  ![インスタンスウィザードを起動___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/d73a4a00-0494-aa9b-7c2b-fc2740814fc1.png)

1. インスタンスを配置するネットワーク設定
  パブリックインスタンスにNATインスタンスを配置することが必要。
  APIサーバーはプライベートに配置するが、NATインスタンスは代わりに接続を行う必要があるためパブリックサブネットの必要がある、と覚えておく。
  ![インスタンスウィザードを起動___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/9a2f2cf9-8a4a-87f8-2ee3-cce627e4bb2f.png)

1. セキュリティグループの設定
  さっき作ったNATインスタンス用のセキュリティグループを設定する。
  ![インスタンスウィザードを起動___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/0f3d9d9d-68f4-0cc2-dec8-ce995f4db21f.png)


あとはキー情報を作成してEC2インスタンスの起動を待つ。
その間にNATインスタンスがパブリックIPを持つのに必要な固定IPアドレスの設定を。

### 固定IPの紐付け
1. 割り当てボタンを押すと自動的にパブリックIPアドレスを取得してくれる。
  ![新しいアドレスの割り当て___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/fa8557d2-c9e4-b61c-0333-24501724615a.png)

1. 固定IPとNATインスタンスを紐づける。
  ![アドレスの関連付け___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/cd44195e-680d-7cea-9c2e-df8bf93efd90.png)

### 送信元/送信先チェックの無効化
この設定作業はとても大切です！！！（忘れがちなだけ）

送信元/送信先チェックは`NATインスタンスに対して送信しますよ！`
と外部サーバーが応答してくれるかどうかをチェックしている機能です。

しかし、今回はプライベートサブネットに配置されたインスタンスに送信するため、
`あなたのサーバーのxxさんに対して送信するので後はよろしく！`
のようにNATインスタンス宛ての応答ではないです。
そのためこの機能を有効のままにしておくと「自分宛じゃないから無理！！」となるわけです。

![インスタンス___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/c75138e1-cac6-5065-2e81-8a1fa2d6f398.png)

## プライベートサブネットからのインターネット接続をNATインスタンス経由にする
プライベートサブネットに紐づいているルートテーブルに対してネットワーク経路を追加する。

以下の設定を行うことでプライベートサブネットから外部への接続はNATインスタンスを経由するようになります。
![ルートの編集___VPC_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/28b830fb-17e2-3c69-37ba-f9c30afd3cd6.png)

これでNATインスタンスの設定は完了です！！！

## NATインスタンスの接続確認

### サーバーを立てる
プライベートサブネットにEC2インスタンスが配置されていない場合は仮にサーバーを立ててテストを実施する。

Amazon Linux2をパブリックサブネットに配置する。
基本的に設定はデフォルトでOK。
![インスタンス___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/fd3d9c28-d6f0-fb46-c5f0-ffc1fe5226aa.png)


### NATインスタンスのセキュリティグループを変更
セキュリティグループにテスト用IPアドレスの許可設定を行う。

#### インバウンド
今回のテストではpingによる接続確認を行うため`すべてのICMP IPv4`を許可する必要がある。
サーバーからNATインスタンス経由でのpingになるため許可するのはプライベートサブネットに配置されているインスタンスのみ許可。

![セキュリティグループ___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/33b4c343-6d1e-0066-379b-df41a9e29f99.png)


#### アウトバウンド
ping接続した応答を受診する必要があるため`すべてのICMP IPv4`の接続を全て許可する必要がある。
またプライベートサブネットに配置されたインスタンスにSSHの応答を返す必要もあるため設定。

![セキュリティグループ___EC2_Management_Console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248805/432e45f2-2015-562f-e5d4-7b78f4c99c33.png)


# テスト実施

## SSH接続確認
まずはNATインスタンス経由でプライベートサブネットに配置したサーバーにSSH接続を行う。

```bash
ssh -o ProxyCommand='ssh -i [NATインスタンスの公開鍵] ec2-user@[NATインスタンス固定IP] -W [サーバーのプライベートIP]:22' -i [サーバーの公開鍵] ec2-user@[サーバーのプライベートIP]
```

例えば、以下のような接続情報の場合

- NATインスタンス固定IP： `52.198.51.206`
- サーバーのプライベートIP: `172.16.1.68`
- NATインスタンスの公開鍵の場所: (MAC上) `~/.ssh/nat-test.pem`
- サーバーの公開鍵の場所: (MAC上) `~/.ssh/nat-test-server.pem`

実行するコマンドは

```bash
ssh -o ProxyCommand='ssh -i ~/.ssh/nat-test.pem ec2-user@52.198.51.206 -W 172.16.1.68:22' -i ~/.ssh/nat-test-server.pem ec2-user@172.16.1.68
```

## ネットワーク接続確認

## ping確認
プライベートサブネットに配置したサーバーでping接続確認を行う。

```bash
[ec2-user@ip-172-16-1-68 ~]$ ping yahoo.co.jp
PING yahoo.co.jp (183.79.135.206) 56(84) bytes of data.
64 bytes from f1.top.vip.kks.yahoo.co.jp (183.79.135.206): icmp_seq=1 ttl=40 time=17.1 ms
64 bytes from f1.top.vip.kks.yahoo.co.jp (183.79.135.206): icmp_seq=2 ttl=40 time=17.1 ms
64 bytes from f1.top.vip.kks.yahoo.co.jp (183.79.135.206): icmp_seq=3 ttl=40 time=17.1 ms
^C
--- yahoo.co.jp ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 17.168/17.186/17.199/0.107 ms
```

## curl確認
ステータス200が返ることを確認。

```
[ec2-user@ip-172-16-1-68 ~]$ curl -I https://www.yahoo.co.jp/
HTTP/2 200
date: Tue, 27 Aug 2019 11:50:37 GMT
p3p: policyref="http://privacy.yahoo.co.jp/w3c/p3p_jp.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
expires: -1
pragma: no-cache
cache-control: private, no-cache, no-store, must-revalidate
content-type: text/html; charset=UTF-8
age: 0
via: http/1.1 edge1604.img.bbt.yahoo.co.jp (ApacheTrafficServer [c sSf ])
server: ATS

[ec2-user@ip-172-16-1-68 ~]$
```

## フローログの確認
今回はフローログの設定方法まで詳しくは書かないが、VPCフローログを確認すると以下のような経路になっていることも確認できるので調べながら確認するのも良い。

- サーバー(172.16.1.xxx)からNATインスタンス(172.16.0.xxx)
- NATインスタンス(172.16.0.xxx)からyahooサイト(183.79.135.206)
- yahooサイト(183.79.135.206)からNATインスタンス(172.16.0.xxx)
- NATインスタンス(172.16.0.xxx)からサーバー(172.16.1.xxx)

# まとめ
NATインスタンスを構築することで安価でかつセキュアな作りにすることができる。
試す価値大いにあり！！！！