【AWS VPC】3.4ドルで実践的なネットワークを構築して超感動したって話

# はじめに

※本記事は、社内勉強会用の資料も兼ねています。

「業務をしていて、なんかVPCまわりイマイチわかってないなあ」

「実践的なVPCを構築しながら学びたいけど、適当に触り過ぎてもすごいお金かかりそうで怖いなあ」

そんな折、以下の [@c60evaporator(Kenta Nakamura)](https://qiita.com/c60evaporator) さんの記事を見つけ、

「こ、これだ！」

https://qiita.com/c60evaporator/items/2f24d4796202e8b06a77

https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e

というわけで、今回はこれらの記事を熟読して、実際にやってみたところめちゃくちゃ感動したので、躓いたところなどを含め記事にしてみようと思いました。


# 対象とする読者


- VPCを勉強したいけど何からやればいいかわからない人
- 認定試験に向けて、VPCをとにかく触りながら勉強したい人


# 私の学習の流れ

まずは、こちらの記事を熟読しました。（2時間くらい）

https://qiita.com/c60evaporator/items/2f24d4796202e8b06a77

タイトルに偽りなく、めちゃくちゃわかりやすかったです。

続いて、こちらの記事を見ながら実際に構築しました。（7時間くらい）

https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e

そうして構築できるネットワークがこちら。
<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665927/c951b432-00fb-da41-afd4-317263fa5d4c.png">


出典元：https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e

どうですか？すごくないですか？
冗長構成なwebアプリのネットワーク（VPC1）を構築するところまでなら見たことありましたが、それを開発・運用するためのアクセスは社内ネットワーク（VPC2）からのみ行えるようにするところまでやるのは、非常に実践的だなと個人的には思いました。


# コスト


私は、2日に分けて構築しました。その際、かかった費用はこちらです。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665927/657d701c-30d2-199d-1512-cd4b57bdfba9.png">

合計、$3.40ですね。

シャン〇ス「安いもんだ 3.4ドルくらい」

冗談抜きで、このネットワークを構築できた達成感を考えると、個人的には全然OKです。

とはいえ、私は ~~ケチ~~節約家なので使ってないときは以下を行い多少工夫しました（笑）

- スナップショットを取った上でRDSインスタンスの削除
- EC2インスタンス停止
- ALBの削除
- VPNエンドポイントの削除


# 学習したこと

以下では、上記学習を行う中で自分の中で疑問に思い調べたところなどを、問いかけも交えながら説明します。

## ネットワークACLとセキュリティグループ

両者は、以下のように整理できます。

|                    | セキュリティグループ                                       | ネットワークACL                | 
| ------------------ | ---------------------------------------------------------- | ------------------------------ | 
| **フィルタ対象単位** | インスタンスごと（セキュリティグループIDでグルーピング可） | サブネットごと                 | 
| **フィルタ方式**       | ホワイトリスト                                             | ホワイトリスト＋ブラックリスト | 
| **戻り値の取り扱い**   | ステートフル[^1]                                              | ステートレス[^2]                   | 

[^1]: 通信の起点となる要求パケットが外向きなら、それに付随する応答パケットも全て外向きとして扱う
[^2]: パケットが要求であるか応答であるかは考慮せず、通信の方向のみで外向きか内向きか判定する

**🔍どちらの方がより優先して使うべきでしょうか？理由も考えてみてください。**

<details><summary>📝</summary>

優先して使った方がよいとされているのは、セキュリティグループです。
理由は、ホワイトリスト＆ステートフルにより、設定をシンプルにできるからです。
また、インスタンスごとに設定できるため、より細かい設定ができる点も利点といえます。
参考: [ネットワークACLを使用してサブネットへのアクセスを、セキュリティグループを使用してサブネット内のインスタンスへのトラフィックを制御する](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AFacl%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%82%B5%E3%83%96%E3%83%8D%E3%83%83%E3%83%88%E3%81%B8%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%82%92%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%82%B5%E3%83%96%E3%83%8D%E3%83%83%E3%83%88%E5%86%85%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%B8%E3%81%AE%E3%83%88%E3%83%A9%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E3%82%92%E5%88%B6%E5%BE%A1%E3%81%99%E3%82%8B)
</details>

## ルートテーブル

ルートテーブルは、ロンゲストマッチングアルゴリズムに基づき、宛先ルートを選択する通信振り分けルールです。

AWS VPCの場合、例えば以下のようにルートを設定できます。

| 送信先        | ターゲット                    | 
| ------------- | ----------------------------- | 
| 0.0.0.0/0     | インターネットゲートウェイ    | 
| 172.31.0.0/16 | local（自身のネットワーク内） | 
| 172.31.1.0/24 | VPCピアリング接続             | 

**🔍このとき、`172.31.1.1`宛ての通信はどのターゲットに転送されるでしょうか？**
<details><summary>📝</summary>

転送されるのは、VPCピアリング接続です。
`172.31.1.1`は、上記のいずれにもマッチしますが、ロンゲストマッチングアルゴリズムに基づき、VPCピアリングが転送先として選ばれます。

</details>

参考記事の通り、AWSのマネジメントコンソールから「VPCなど」を選択して構築すると、VPCだけでなく一緒にサブネットやエンドポイントを作成する事ができます。
参考記事通りに操作すると、VPC1は以下のような画面になります。

<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665927/d05985e4-8889-7f69-4cd1-5d2c45335985.png">

この画像に表示されている通り、各サブネットにはルートテーブルが自動作成され関連付けられますが、public1とpublic2は共通のルートテーブルを使用するのに対し、private1とprivate2はそれぞれルートテーブルを作成され関連付けられます。

**🔍このような挙動になるのはなぜでしょうか？**
<details><summary>📝</summary>

NATゲートウェイは各AZに作るのがベストプラクティス[^3]であり、各privateのルートテーブルにはそれぞれ異なるNATゲートウェイへのルートを定義する必要があります。
したがって、それを見込んでprivateのルートテーブルは各AZごとに分けて作成されます。

[^3]: [参考](https://repost.aws/ja/knowledge-center/nat-gateway-vpc-private-subnet
)

</details>

参考記事通りに構築する中で、一時的に以下のような状態になります。

<img width="1200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665927/5a253c90-b159-9c52-5a85-c8dd08442dfa.png">


**🔍このとき、Web1からAP2へはSSHできるでしょうか？**

<details><summary>📝</summary>

Web1からAP2へは、SSHアクセス可能です。
Web1からのAP2宛てのSSHアクセスは、まずPublic subnet route tableに基づきlocalルートに転送され、AP2へ向かいます。ここで、AP2に関連付けられているSecurity group2では、すべてのSSHアクセスを許可しているので問題なくSSHアクセスできます。
**localルートは、VPC内での通信を可能にするもの**で、デフォルトですべてのルートテーブルに追加されます。[^4]

[^4]: [参考](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/subnet-route-tables.html)

</details>

ルートテーブルには、以下のように「伝播済み」という列があります。

<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3665927/d90b530f-e61d-b42b-c18d-a8931dbfbfdb.png">


**🔍この「伝播済み」とはどういう意味でしょうか？**

<details><summary>📝</summary>

ルート伝播とは、仮想プライベートゲートウェイ（VGW）またはトランジットゲートウェイ（TGW）が動的にルートをルートテーブルに追加する機能のことです。すなわち、対象のVPCの外側にTGWやVGWで接続されているネットワークが増えた際に、自動的にサブネットのルートテーブルを追記してくれます。
したがって、「伝播済み」とは、自動的に追記されたルートかどうかを記載している列になります。

参考: [ルート伝播とは](https://zenn.dev/nnydtmg/articles/aws-route-propagation#%E3%83%AB%E3%83%BC%E3%83%88%E4%BC%9D%E6%92%AD%E3%81%A8%E3%81%AF)

</details>




# 構築する際の躓きポイントや工夫まとめ

## 私の環境と構築する際に選択したリソースタイプ

私の環境は以下です。

- Windows 11
- OpenVPN 2.6.1201

参考記事が少しだけ古いので、新しい選択肢が増えてたりしていて、若干迷いました。結論、私は以下を選んで構築してみました。後述する躓きポイントはあったものの、問題なく動作しました。

- EC2
  - Amazon マシンイメージ： Amazon Linux 2023 AMI
  - インスタンスタイプ： t2.micro
  - ストレージタイプ：gp2
- RDS
  - DBエンジン：MySQL 8.0.39
  - インスタンスタイプ： t3.micro
  - ストレージタイプ：gp2


## ホスト名の変更

[参考記事](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e)に従い、ネットワーク構築を進めると、疎通確認の手順が大量にあります。
しかし、EC2インスタンスは、デフォルトでホスト名が`ip-172-16-1-11`のようなプライベートアドレスから自動生成されたものになっているため、SSHログインした際もプロンプトが以下のようになり、自分が今どのサーバーにいるのか、直感的にわかりにくいです。
```
[ec2-user@ip-172-16-1-11 ~]$
```
そこで、以下コマンドでホスト名を変更しておくと便利です。
```
sudo hostnamectl set-hostname --static <設定したいホスト名>
```
上記コマンド実行後には、以下コマンドでインスタンスを再起動してください。
```
reboot
```
これにより、以下のように表示されるようになるので、誤操作を防ぐことができます。
```
[ec2-user@development-server-1 ~]$
```

## /etc/hostsの追記

[参考記事](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#ssh%E3%81%AE%E7%96%8E%E9%80%9A%E7%A2%BA%E8%AA%8D)では、疎通確認用のSSHログイン時のコマンドは以下のように説明されています。
```
ssh ec2-user@<ログインするサーバのプライベートIPアドレス> -i <ローカルのキーペア秘密鍵のパス>
```
これだと、`<ログインするサーバのプライベートIPアドレス>`を毎回AWSのマネジメントコンソールから調べるのが大変です。そこで、SSHコマンドを実行するサーバー上の`/etc/hosts`ファイルにホスト名とプライベートIPアドレスの対応を追記します。例えば、以下のようなコマンドを実行して追記します。
```
echo "<サーバのプライベートアドレス> <サーバー名>" | sudo tee -a /etc/hosts
```
追記内容は、catコマンドやlessコマンドで確認できます。
これを行うと、以下のコマンドでログインできるので、いちいちIPアドレスを調べる必要がなくなります。これはscpコマンドでも同様です。
```
ssh ec2-user@<ログインするサーバー名> -i <ローカルのキーペア秘密鍵のパス>
```


## VPN接続用のサーバー証明書とキーの生成コマンド

[参考記事](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%A8%E3%82%AD%E3%83%BC%E3%81%AE%E7%99%BA%E8%A1%8C-1)では、VPN接続用のサーバー証明書とキーを生成するコマンドは以下のように説明されています。
```
./easyrsa build-server-full server nopass
```
このコマンドも実行はできるのですが、これにより生成されたサーバー証明書はACMにアップロードしても正しく認識されません。[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpn/latest/clientvpn-admin/client-auth-mutual-enable.html)によると、以下が正しいコマンドのようです。
```
./easyrsa --san=DNS:server build-server-full server nopass
```
こちらのコマンドを用いて生成したサーバー証明書とキーを使うと、無事VPN接続することができました。


## mysqlのインストール

mysqlのインストールですが、[参考記事](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#rds%E3%81%B8%E3%81%AE%E7%96%8E%E9%80%9A%E7%A2%BA%E8%AA%8D)に沿ってインストールを行うと、私のAmazon Linux 2023 AMIで起動したEC2では以下のようにエラーとなりました。
```
[ec2-user@ip-172-16-1-11 ~]$ sudo yum install mysql
Last metadata expiration check: 2:40:47 ago on Sat Jan  4 02:34:48 2025.
No match for argument: mysql
Error: Unable to find a match: mysql
```
[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/mysql-install-cli.html)を参照すると、Amazon Linux 2023では、mysqlパッケージではなくmariadb105を使うのが正しいようです。したがって、以下のコマンドを実行すると無事インストールできました。
```
sudo dnf install mariadb105
```
参考までに、dnfはyumの後継コマンドであり、`sudo yum install mariadb105`でも問題なくインストール可能です。

## squidの設定ファイル変更

[参考記事](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A-vpc2)に従いプロキシサーバの設定を完了しても、[⑬VPNからプロキシサーバ経由でのインターネットへのHTTP疎通確認](https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e#vpn%E3%81%8B%E3%82%89%E3%83%97%E3%83%AD%E3%82%AD%E3%82%B7%E3%82%B5%E3%83%BC%E3%83%90%E7%B5%8C%E7%94%B1%E3%81%A7%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%8D%E3%83%83%E3%83%88%E3%81%B8%E3%81%AEhttp%E7%96%8E%E9%80%9A%E7%A2%BA%E8%AA%8D)がうまくいきません。
原因はsquidの設定ファイルでした。以下のコマンドなどでsquidの設定ファイルを開き、
```
sudo vi /etc/squid/squid.conf
```
以下の部分に追記を行います。
```
#
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
#
http_access allow localnet ← ★追記
```
この追記を行った上で`systemctl restart squid`を実行しサービス再起動すると、ローカルネットワークからプロキシサーバにアクセスできるようになり、プロキシサーバ経由でインターネットへHTTP通信できるようになりました。

# おわりに

今回はVPCのベストプラクティスについて実際に構築しながら学んでみました。
ネットワークは、プロジェクトの初期段階にある程度上級エンジニアが構築済みだったりして、あとからプロジェクトに参画してアプリケーションの開発とかだけに専念しているとあまり意識しないこともあるかと思います。しかし、実際に手を動かしながら構築して、思い通りのアクセス制御を実現できたときの達成感は格別です。ぜひ皆さんも本記事でご紹介している記事を参考に構築してみてください。
最後になりましたが、以下の非常にタメになる記事を書いてくださった[@c60evaporator(Kenta Nakamura)](https://qiita.com/c60evaporator)さん、本当にありがとうございました！

https://qiita.com/c60evaporator/items/2f24d4796202e8b06a77

https://qiita.com/c60evaporator/items/b9e645b96afa3a34f41e