AWS - 開発時のWebアプリケーション実行環境を考える 〜 「NAT Gateway が高くて辛い」対策 〜

## はじめに

サーバ側のアプリケーションを作る際によく AWS を使用するのですが、 NAT Gateway が占める費用が高くてなんとかしたいと考えていました。

[前回の記事](https://qiita.com/anfangd/items/7fab147e45f54e691298) でアプリケーションの環境定義についてまとめたのですが、この環境ごとに VPC を作ってしまうと益々、 NAT Gateway の費用が上がってしまうので、この機会にベターなシステム構成について考えてみました。

## システムの構成

### 一般的な Webアプリケーション の構成

2019年09月現在の一般的な Mobile と Web のアプリケーション・アーキテクチャは次のような構成が一般的だと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/6cfd590e-1a36-1ed3-a1e6-c1278041f811.png)

### システム開発プロジェクトにおける 環境定義

[前回の記事](https://qiita.com/anfangd/items/7fab147e45f54e691298)では、サービス開発において十分な体制で臨んだ場合に必要な環境は次のようになるはずだと記載しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/68894641-8b35-8740-9138-75bcc5626f06.png)

### 単位あたりの環境の構成

AWS を使用して1つの環境を作る場合、ざっくりと次のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/7857d9a5-61e9-e0f6-d545-a46e390d499e.png)

#### NAT Gateway

前述の構成で環境を構築した際、インスタンスに多少のコストが発生するのは仕方がないですが、 実際のところは NAT Gateway の維持に掛かる費用が少なくないのが現実です。

> VPC 内に NAT ゲートウェイを作成することを選択した場合は、NAT ゲートウェイがプロビジョニングされ利用可能であった "NAT ゲートウェイ時間" に対して料金が請求されます。データ処理料金は、トラフィックの送信元か送信先にかかわらず、NAT ゲートウェイで処理されたギガバイト単位で適用されます。1 時間未満の NAT ゲートウェイ時間は、1 時間分として請求されます。また、NAT ゲートウェイを介して転送されるすべてのデータに対して標準的な AWS データ転送料金が発生します。NAT ゲートウェイへの課金を止めたい場合は、AWS マネジメントコンソール、コマンドラインインターフェイス、API を使用して NAT ゲートウェイを削除します。

「アジアパシフィック (東京)」リージョンの 2019年9月23日 現在の料金は次の通りです。

| 課金対象 | 基準料金 |
|:--|:--|
| NAT ゲートウェイあたりの料金 (USD/時) | 0.062 USD |
| 処理データ 1 GB あたりの料金 (USD) | 0.062 USD |

データ処理量を考慮せず、単純にサービスを維持した場合の費用は次のようになります。

```math
0.062(USD/hour) \times 24(hour) \times 30(days) = 44.64(USD/Month)\\
1.00(USD) = 107.70(円)\\

44.64(USD/Month) \times 107.70(円) = 4807.728(円/Month)
```

※1.00(USD) = 107.70(円) *2019/09/23 12:22

1つの環境に2台の NAT Gateway を起動すると、常時起動させただけで突き当り **約5,000 × ２台 = 約10,000円** の費用が発生します。

本番環境は仕方ないにしても、Staging〜Develp まですべて VPC を用意してしまうと、その分の NAT Gateway の費用が発生してしまい、開発期間はローコストで運用したい場合は多少つらいところです。

- 引用：[Amazon VPC の料金 - AWS](https://aws.amazon.com/jp/vpc/pricing/)

## 環境構成を考える

### ほどよい環境定義

本番（Production） と 準本番（Staging） は「ほぼ同じ」という環境にしたいので、それ以外の環境（Testing、Integration）を同じ VPC 内に構築します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/b0ec70d5-16c3-5fd2-ffa0-47f1eb0d209b.png)

### 環境構成のイメージ

前述の環境定義を実現する際に必要となる基本的な構成を [[AWS初心者向けWebinar] 利用者が実施するAWS上でのセキュリティ対策](https://www.slideshare.net/AmazonWebServicesJapan/awswebinar-aws-56260969) にて紹介されているものをベースに考えてみます。

#### 単位あたりの環境のネットワーク構成

VPC の中に用途に応じた４種類のサブネットを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/082bdb80-e24d-1019-37e0-e5661fa29ea9.png)

#### Route Table

それぞれのサブネットの Route Table は次の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/feaad9b1-fd0e-5b62-d31a-331170d49efa.png)

この図が示す通り、 Private Subnet から インターネットへのアクセスが必要な場合は、 NAT Gateway を経由することになります。

#### サービス / インスタンスの構成

作成したサブネットには次のような インスタンス を配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/4714381b-4aed-d5ba-0cf2-e1f24cc1040c.png)

#### 通信の経路：インターネット からの HTTP アクセス

アプリケーションが稼働するときの基本的なアクセスの経路は次の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/e998cd04-a0ec-fa81-ac6e-4818c1efbe50.png)

#### 通信の経路：内部からの インターヘットへのアクセス（NTP、バッチ など）

例えば、 Application Layer に配置した EC2 インスタンス からインターネットの外へ通信したい場合は次のような経路になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/b5548453-cd29-7059-2d42-c2ab030771ef.png)

#### 通信の経路：踏み台サーバからの保守作業アクセス

開発作業の実施時に Applicaiton Layer のサーバや、 Datasource Layer のデータストアにアクセスする場合は次の経路になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/6f6cabf2-85d3-6cbe-cb82-80267f8cb433.png)

- [[5分で]AWSベストプラクティスにのっとって踏み台(Bastion)サーバを構築する – Linux編 – Developers.IO](https://dev.classmethod.jp/cloud/aws/20160929-bastion/)

#### 環境別アプリケーション の範囲

１つのVPC内に複数の環境のアプリケーション・リソースを配置する際に、`Testing` や `Integration` といった環境の種類ごとに構築が必要なのは次の星印で示した箇所です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/64995afd-969b-0bbe-5b58-cf7ae95ae60d.png)

この他の要素については、ネットワーク・インフラに関連する箇所や、保守専門の箇所だったりするので、環境ごとに個別に作成する必要はないはずです。

こうすることで、システム開発プロジェクトにおいて Webアプリケーションの開発時に まずは１つのVPCだけを用意し、顧客の動作確認時に Staging環境を、 リリース時に Production環境を という風に使い分け、 NAT Gateway の費用を多少抑えることができます。

## もう少し掘り下げる

### アカウント

「環境ごとにアカウントを分割するか」、「VPCを分割するか」といった話は以下の記事がとても参考になります。

- [AWSアカウントとVPC、分ける？ 分けない？: 分割パターンのメリット・デメリット - Developers.IO](https://dev.classmethod.jp/cloud/aws/account-and-vpc-dividing-pattern/)

### Elastic IP

**１つの環境に最低３つの Elastic IP が必要** となります。2環境以上作成する場合は事前に AWS へ上限緩和申請が必要となるので事前に準備しましょう。

Git のホスティングツールや CI サービス、連携システムなどで接続元IPで制限したい場合は、 NAT Gateway にアタッチした Elastic IP で制限する必要があります。途中で変更したりすると接続できなくなったりするので、ある段階からは IP アドレスは固定としておく必要が出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/6d0079df-8523-8f82-2934-2ddf5a05855a.png)

Elastic IP はアタッチしていれば課金対象にはなりませんが、踏み台サーバについては NAT Gateway 同様に起動している時間だけ費用が発生するので、この記事に書いた構成にすることで、そこの インスタンス稼働費の削減にも繋がります。

### Route 53 & Certificate Manager

Elastic Load Balancing が持つ デフォルトのドメインを使用することはまずなく、任意のドメインを使用して https 通信をすることが通常だと思います。
環境ごとにサブドメインを作成するなどして、 Elastic Load Balancing の インスタンスに紐付けます。

また、 RDS などについてもそのまま Endpoint を指定するのではなく、 Private な Host zone を作成し、そこで名前解決できるように設定を行います。
なお、この設定を有効にするためには、 **VPCの「DNS解決」と「DNSホスト名」 を True に設定しておく** 必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/276b304f-123b-9741-4bb5-aee800b4220d.png)


#### memo

1. ドメインは最初に取得し、ACMで証明書の取得も行います。
2. Private Hosted Zone は VPC ごとに作成します。

- [DNSド素人がdigコマンドとRoute 53を使って、DNSについてあれこれ学んでみた - Developers.IO](https://dev.classmethod.jp/cloud/aws/dig-route53-begginer/)

## 考慮が必要な点

### 環境名 と ネットワーク名

Terraform で環境構築する際には **workspace** などで環境分割を行うことがあります。
単純に１つの環境で １つの VPC であれば、低レイヤーの構成から順次構築すれば問題ありませんが、１つの VPC に複数の環境を構築する場合は、参照する backend の設定情報や 変数の情報に注意する必要があります。

例えば、AWS Cloud 内にリソースを構築する際には次のような情報が識別子の要素の１つとなるでしょう。

- Project Code　・・・　開発プロジェクトのプロジェクトコード
- Environment Name　・・・　環境の名前
- Network Name　・・・　システムが稼働するネットワークの名前
- Service Name　・・・　プロジェクトによって実現するサービスの名前
- Application Name　・・・　サービスを構成するアプリケーション名前
- AWS Service Name　・・・　AWS の任意のサービス名
- AWS Resource Identifier　・・・　AWSで作成するリソースを識別する識別子

これらの要素には、例えば次のような値が入るはずです。

|#|項目|例|
|:--|:--|:--|
|1|Project Code|abc, p001|
|2|Environment Name|production, staging, test, integration|
|3|Network Name|production, staging, development<br>　（→release, demo, making）|
|4|Service Code|Blog Service, S001|
|5|Application Name|Blog Web App, Special Mobile Backend|
|6|AWS Service Name|vpc, sb, sg, rtb|
|6|AWS Resource Identifier|1a, 1c|

１つの環境を１つのVPCで作成する場合は、例えば次のようなことが可能でした。

```bash
# Rule
${Stack}.${Environment}.${Indentifier}.${AWS Service Name}

# Example
- p001.test.vpc
　　〜
- p001.test.pub.rtb
- p001.test.1a-dmz.subnet
- p001.test.1c-dmz.subnet
　　〜
- p001.test.bastion.ec2
　　〜
- p001.test.app.eb    ・・・ ※
- p001.test.env.eb    ・・・ ※
- p001.test.main.rds  ・・・ ※
```

しかし、１つのVPC内に複数の環境のリソースが配置される場合は、「環境」と「ネットワーク・インフラ」という観点でスコープを分割した次のような考え方が必要となります。

```bash
# Rule
${Stack}.${Environment}.${Network_Name}.${Indentifier}.${AWS Service Name}

# Example
- p001.making.vpc
　　〜
- p001.making.pub.rtb
- p001.making.1a-dmz.subnet
- p001.making.1c-dmz.subnet
　　〜
- p001.making.bastion.ec2
　　〜
- p001.making.test.app.eb    ・・・ ※
- p001.making.test.env.eb    ・・・ ※
- p001.making.test.main.rds  ・・・ ※
- p001.making.integration.app.eb    ・・・ ※
- p001.making.integration.env.eb    ・・・ ※
- p001.making.integration.main.rds  ・・・ ※
```

これであとは、 component と workspace の切り分けを適切に行い、命名を行うことで、 1つの VPC 内に複数の環境を構築する際にそれっぽい環境が構築出来るはずです。

- [Terraform - リポジトリ構造と活用範囲を考える #Qiita](https://qiita.com/anfangd/items/1b84f69fa2a4f8a29fbc)

## おわりに

CloudWatch の設定や IAM の設定なども含めようと思いますが、苦手な領域でもあるのでひとまずはまとめられる範囲でまとめてみました。

## 参考

- [Deployment environment - Webアプリケーションの環境定義（本番/検証/開発/...） について考えてみる #Qiita](https://qiita.com/anfangd/items/7fab147e45f54e691298)
- [Amazon AWSでユーザ数1100万以上にスケーリングするためのビギナーズ・ガイド | POSTD](https://postd.cc/a-beginners-guide-to-scaling-to-11-million-users-on-amazons/)
- [[AWS初心者向けWebinar] 利用者が実施するAWS上でのセキュリティ対策 - SlideShare](https://www.slideshare.net/AmazonWebServicesJapan/awswebinar-aws-56260969)
- [サーバレスでNAT Gatewayの起動・停止を管理してみた【押忍！ソフト道場】](https://www.ntt-tx.co.jp/column/dojo_aws_blog/20180411/)

### Tag & Name

- [AWS リソースのタグ規約を考える #Qiita](https://qiita.com/hidekatsu-izuno/items/c1c0ce0b56a822c25e5e)
- [Tagging Best Practices](https://d1.awsstatic.com/whitepapers/aws-tagging-best-practices.pdf)

### AWSのアーキテクチャ

- [Webinar aws 101 a walk through the aws cloud- introduction to cloud computing with aws - feb 2014](https://www.slideshare.net/AmazonWebServices/webinar-aws-101-a-walk-through-the-aws-cloud-introduction-to-cloud-computing-with-aws-feb-2014)
- [AWS Go Through - Alice && Bob](https://morganwu277.github.io/2018/06/25/AWS-Go-Through/)