はじめに
この記事は自分の学習用、備忘録として書いてるものです。
もし内容に間違いなどがあればご指摘ください。
AWSのVPCとEC2を使って、WebサーバとAPIサーバを分けた構成を作成した。
今回の学習で一番大事だったのは、EC2を単体で立てることではなく、AWS上でサーバ同士がどのように通信するのかを理解することだった。
AWSでは、EC2を起動するだけではシステムは成立しない。
VPC、サブネット、ルートテーブル、インターネットゲートウェイ、NATゲートウェイ、セキュリティグループなどが組み合わさって、初めて外部公開や内部通信ができる。
今回の記事では、学習した内容を自分の備忘録として整理する。
今回作った構成
今回作成した構成は以下。
インターネット
|
| HTTP
v
Public Subnet
|
| Webサーバ EC2
| - Nginx
| - HTML配信
| - APIサーバへのリバースプロキシ
v
Private Subnet
|
| APIサーバ EC2
| - APIアプリケーション
| - 8080番ポートで起動
構成のポイントは、Webサーバは外部公開し、APIサーバは外部公開しないという点。
Webサーバはユーザーのブラウザからアクセスされるため、Public Subnetに配置する。
一方で、APIサーバは直接インターネットに公開する必要がないため、Private Subnetに配置する。
外部公開するもの : Webサーバ
外部公開しないもの : APIサーバ、DBなど
この構成にすることで、ユーザーはWebサーバにだけアクセスし、Webサーバが内部的にAPIサーバへ通信する形になる。
VPCとは
VPC(Virtual Private Cloud)は、AWS上に作る仮想ネットワーク。
EC2やRDSなどのリソースは、基本的にVPCの中に配置する。
そのため、VPCはAWS上でシステムを構築するための土台になる。
イメージとしては以下。
VPC = AWS上に作る自分専用のネットワーク空間
VPCを作るときは、IPアドレスの範囲をCIDRで指定する。
今回のような構成では、以下のようなCIDRを使う。
VPC: 10.0.0.0/16
/16 はVPC全体でよく使われる範囲。
この範囲の中に、さらにサブネットを作っていく。
サブネットとは
サブネットは、VPCの中をさらに分割したネットワーク領域。
今回作成したサブネットは以下。
VPC: 10.0.0.0/16
Public Subnet:
10.0.1.0/24
Private Subnet:
10.0.2.0/24
サブネットを分ける理由は、リソースの役割ごとにネットワークを分離するため。
例えば、Webサーバは外部からアクセスされるためPublic Subnetに配置する。
APIサーバやDBは外部から直接アクセスさせたくないためPrivate Subnetに配置する。
Public Subnet : インターネットからアクセスされるリソースを置く
Private Subnet : 外部公開しないリソースを置く
ここで重要なのは、サブネット自体にPublic/Privateという種類があるわけではないこと。
Public SubnetかPrivate Subnetかは、主にルートテーブルの設定で決まる。
CIDRの考え方
CIDRは、IPアドレスの範囲を表す書き方。
例:
10.0.0.0/16
10.0.1.0/24
最初は細かい計算まで理解しようとすると難しい。
まずは以下のパターンを覚えれば十分。
VPC : /16
Subnet : /24
/16 の例
10.0.0.0/16
→ 10.0.0.0 〜 10.0.255.255
VPC全体の範囲として使う。
/24 の例
10.0.1.0/24
→ 10.0.1.0 〜 10.0.1.255
サブネットの範囲として使う。
VPCの中にサブネットを作るため、サブネットのCIDRはVPCの範囲内である必要がある。
良い例:
VPC: 10.0.0.0/16
Subnet A: 10.0.1.0/24
Subnet B: 10.0.2.0/24
悪い例:
VPC: 10.0.0.0/16
Subnet: 192.168.1.0/24
これはVPCの範囲外なので使えない。
Public Subnetとは
Public Subnetは、インターネットと直接通信できるサブネット。
Public Subnetにするには、ルートテーブルでインターネットゲートウェイへの経路を設定する必要がある。
送信先: 0.0.0.0/0
ターゲット: Internet Gateway
0.0.0.0/0 は「すべての宛先」を意味する。
つまり、VPC内部以外への通信はインターネットゲートウェイに流す、という意味になる。
今回、Webサーバはインターネットからアクセスさせる必要があるため、Public Subnetに配置した。
ブラウザ
↓
Internet Gateway
↓
Public Subnet
↓
Webサーバ EC2
Private Subnetとは
Private Subnetは、インターネットから直接アクセスできないサブネット。
APIサーバやDBサーバなど、外部公開したくないリソースを置く。
今回、APIサーバはPrivate Subnetに配置した。
Private Subnet
↓
APIサーバ EC2
Private Subnetに配置したEC2は、基本的に外部から直接アクセスできない。
そのため、ローカルPCから直接SSH接続することもできない。
APIサーバへ接続するときは、Public Subnet上のWebサーバを踏み台として経由する。
ローカルPC
↓ SSH
Webサーバ
↓ SSH
APIサーバ
インターネットゲートウェイとは
インターネットゲートウェイは、VPCとインターネットをつなぐ出入り口。
作成しただけでは使えない。
VPCにアタッチして、ルートテーブルで通信経路を向ける必要がある。
作業の流れは以下。
1. インターネットゲートウェイを作成
2. VPCにアタッチ
3. Public Subnet用のルートテーブルに設定
ルートテーブルの設定例:
送信先: 0.0.0.0/0
ターゲット: Internet Gateway
これでPublic SubnetにあるEC2がインターネットと通信できるようになる。
NATゲートウェイとは
NATゲートウェイは、Private Subnet内のリソースがインターネットへ出ていくための仕組み。
Private Subnetのリソースは外部から直接アクセスさせたくない。
しかし、OSアップデートやパッケージインストールなどで外部通信が必要になる場合がある。
そのときにNATゲートウェイを使う。
Private Subnet
↓
NAT Gateway
↓
Internet Gateway
↓
Internet
ポイントは以下。
- NATゲートウェイはPublic Subnetに配置する
- NATゲートウェイにはElastic IPが必要
- Private SubnetのルートテーブルでNATゲートウェイを指定する
- Private Subnetから外へ出る通信はできる
- インターネット側からPrivate Subnetへ直接入る通信はできない
この「外へは出られるが、外からは入れない」という性質が重要。
Private Subnet用ルートテーブルの例:
送信先: 0.0.0.0/0
ターゲット: NAT Gateway
ルートテーブルとは
ルートテーブルは、通信の行き先を決める設定。
サブネットに関連付けることで、そのサブネット内のEC2がどこへ通信を流すかを決める。
今回作成したルートテーブルは2つ。
Webサーバ用ルートテーブル
Webサーバはインターネットと通信するため、Internet Gatewayへのルートを設定する。
関連付けるサブネット:
web-subnet
ルート:
10.0.0.0/16 → local
0.0.0.0/0 → Internet Gateway
local はVPC内部通信のためのルート。
これはデフォルトで存在する。
0.0.0.0/0 → Internet Gateway があることで、インターネットと通信できる。
APIサーバ用ルートテーブル
APIサーバは外部公開しない。
ただし、必要に応じて外部へ通信できるようにNAT Gatewayへのルートを設定する。
関連付けるサブネット:
api-subnet
ルート:
10.0.0.0/16 → local
0.0.0.0/0 → NAT Gateway
この設定により、APIサーバは直接外部公開されないが、外部への通信はNAT Gateway経由で可能になる。
セキュリティグループとは
セキュリティグループは、EC2に設定する仮想ファイアウォール。
どの通信を許可するかを設定する。
基本は「許可した通信だけ通す」ホワイトリスト方式。
今回作成したセキュリティグループは以下。
web-sg
api-sg
Webサーバ用セキュリティグループ
Webサーバはブラウザからアクセスされるため、HTTPを許可する必要がある。
設定イメージ:
セキュリティグループ: web-sg
インバウンド:
HTTP 80 0.0.0.0/0
SSH 22 自分のIPアドレスなど
HTTPの 0.0.0.0/0 は、インターネット上の誰でもWebページを見られるようにするための設定。
一方、SSHの 0.0.0.0/0 は危険。
学習目的では一時的に使う場合もあるが、本番環境では自分のIPアドレスや社内ネットワークだけに制限するべき。
HTTP: 全世界に公開してよい
SSH : 管理者だけに限定するべき
APIサーバ用セキュリティグループ
APIサーバは外部から直接アクセスさせない。
そのため、Webサーバからの通信だけ許可する。
設定イメージ:
セキュリティグループ: api-sg
インバウンド:
TCP 8080 web-sg
SSH 22 web-sg
ここで重要なのは、送信元に web-sg を指定している点。
これは「web-sgが付いているEC2からの通信だけ許可する」という意味。
つまり、外部のIPアドレスから直接APIサーバへアクセスすることはできない。
ブラウザ
↓
Webサーバ
↓ 8080番
APIサーバ
APIサーバはWebサーバからだけ呼び出される。
セキュリティグループとネットワークACLの違い
セキュリティグループと似たものにネットワークACLがある。
違いは以下。
| 項目 | セキュリティグループ | ネットワークACL |
|---|---|---|
| 適用単位 | EC2単位 | サブネット単位 |
| 設定内容 | 許可のみ | 許可と拒否 |
| 評価方法 | すべてのルールを評価 | ルール番号順に評価 |
| 状態 | ステートフル | ステートレス |
| 戻り通信 | 自動で許可 | 明示的に許可が必要 |
| 主な用途 | EC2単位の通信制御 | サブネット単位の通信制御 |
最初はセキュリティグループを中心に理解するのがよさそう。
ネットワークACLは、特定のIPアドレスを拒否したい場合や、サブネット単位でより細かく制御したい場合に使うイメージ。
EC2とは
EC2は、AWS上で仮想サーバを作成できるサービス。
正確には、EC2はサービス名で、実際に作成される仮想サーバは「インスタンス」と呼ぶ。
EC2を作るときは、以下を指定する。
- AMI
- インスタンスタイプ
- キーペア
- VPC
- サブネット
- セキュリティグループ
- ストレージ
- パブリックIPの有無
EC2は必要なときに作成でき、不要になったら停止・削除できる。
オンプレミスの物理サーバと違い、柔軟に増減できるのが特徴。
AMIとは
AMIは、EC2を起動するためのテンプレート。
OSや初期状態を決めるもの。
例:
Amazon Linux 2023
Ubuntu
Windows Server
AMIが「中身」を決めるもので、インスタンスタイプが「性能」を決めるもの。
AMI : OSやソフトウェア構成
インスタンスタイプ : CPUやメモリなどの性能
今回のハンズオンでは、Amazon Linux 2023を使用した。
インスタンスタイプとは
インスタンスタイプは、EC2の性能を決める設定。
例:
t3.micro
m7i.large
c7i.large
r7i.large
インスタンスタイプは、ファミリー、世代、サイズで構成される。
t3.micro
t : ファミリー
3 : 世代
micro : サイズ
ざっくりした分類は以下。
| ファミリー | 特徴 | 主な用途 |
|---|---|---|
| t系 | 安価、バースト可能 | 学習環境、開発環境、小規模Web |
| m系 | CPUとメモリのバランス型 | 一般的なWeb/APIサーバ |
| c系 | CPU重視 | 高負荷処理、計算処理 |
| r系 | メモリ重視 | DB、キャッシュ、大量データ処理 |
今回のような学習用途では t3.micro を使った。
キーペアとSSH接続
EC2にSSH接続するにはキーペアを使う。
キーペアは以下の2つで構成される。
公開鍵: AWS側に保存
秘密鍵: ローカルPCに保存
SSH接続時には秘密鍵を指定する。
Macの場合の例:
ssh -i ./my-key.pem ec2-user@<WebサーバのパブリックIP>
秘密鍵ファイルの権限が緩いと、以下のようなエラーが出る。
WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions 0644 for './my-key.pem' are too open.
この場合は、秘密鍵の権限を変更する。
chmod 400 ./my-key.pem
秘密鍵は、キーペア作成時にしかダウンロードできない。
紛失すると再ダウンロードできないため、管理に注意が必要。
EBSとは
EBSは、EC2に接続するブロックストレージ。
PCでいうHDDやSSDのようなもの。
EC2のOSやミドルウェア、アプリケーションファイルなどはEBSに保存される。
ただし、永続的に残すべき重要なデータをEC2のEBSに置くのは避けた方がよい。
理由は、EC2は増減・再作成される前提のリソースだから。
画像ファイルなど : S3
DBデータ : RDS
一時ファイル : EBSでも可
クラウドでは、EC2自体はいつでも作り直せるようにして、重要なデータは外部の永続化サービスに置く考え方が重要。
Elastic IPとは
EC2に自動で割り当てられるパブリックIPは、停止・開始すると変わることがある。
固定のパブリックIPが必要な場合は、Elastic IPを使う。
今回、WebサーバにElastic IPを割り当てた。
Elastic IP
↓
web-server
これにより、WebサーバのIPアドレスを固定できる。
ただし、Elastic IPは課金対象になるため、不要になったら解放する必要がある。
ユーザデータとは
ユーザデータは、EC2起動時に自動実行するスクリプト。
例えば、EC2起動時に以下のような処理を自動化できる。
- OSアップデート
- Nginxのインストール
- HTMLファイルの配置
- サービスの起動
毎回SSH接続して手作業でセットアップすると、作業ミスが起きやすい。
ユーザデータを使うと、EC2起動時に初期構築を自動化できる。
これはIaCや自動構築の入口になる考え方。
Webサーバの作成
Webサーバ用のEC2は、Public Subnetに配置した。
設定イメージ:
名前: web-server
AMI: Amazon Linux 2023
インスタンスタイプ: t3.micro
VPC: my-vpc
サブネット: web-subnet
パブリックIP: 有効
セキュリティグループ: web-sg
WebサーバではNginxを使った。
Nginxの役割は以下。
- ブラウザからのHTTPリクエストを受ける
- HTMLページを返す
- /api/ のリクエストをAPIサーバへ転送する
つまり、Webサーバはフロント側の入口になる。
Nginxのリバースプロキシ
Webサーバでは、Nginxを使ってAPIサーバへのリバースプロキシを設定した。
設定イメージ:
location /api/ {
proxy_pass http://<APIサーバのプライベートIP>:8080/api/;
}
これにより、ブラウザからWebサーバに送られた /api/ のリクエストを、内部のAPIサーバへ転送できる。
ブラウザ
↓
Webサーバ(Nginx)
↓ /api/
APIサーバ
ユーザーから見るとWebサーバにアクセスしているだけ。
しかし、内部的にはWebサーバがAPIサーバへ通信している。
この構成にすることで、APIサーバを直接外部公開しなくてもAPIを利用できる。
APIサーバの作成
APIサーバ用のEC2は、Private Subnetに配置した。
設定イメージ:
名前: api-server
AMI: Amazon Linux 2023
インスタンスタイプ: t3.micro
VPC: my-vpc
サブネット: api-subnet
パブリックIP: 無効
セキュリティグループ: api-sg
APIサーバは外部公開しない。
そのため、パブリックIPは付与しない。
APIサーバでは8080番ポートでアプリケーションを起動した。
WebサーバからAPIサーバへ疎通確認するときは以下。
curl http://<APIサーバのプライベートIP>:8080/api/health
成功すると以下のようなJSONが返る。
{ "message": "API接続に成功しました" }
踏み台接続
APIサーバはPrivate Subnetにあるため、ローカルPCから直接SSH接続できない。
そのため、Webサーバを経由してAPIサーバへ接続する。
ローカルPC
↓ SSH
Webサーバ
↓ SSH
APIサーバ
ローカルPCからWebサーバへ接続:
ssh -i ./my-key.pem ec2-user@<WebサーバのパブリックIP>
WebサーバからAPIサーバへ接続:
ssh -i ./my-key.pem ec2-user@<APIサーバのプライベートIP>
ただし、実運用でWebサーバに秘密鍵を置くのは危険。
本番ではSession Managerなどを使う方が望ましい。
動作確認の流れ
最終的な動作確認は、通信経路を順番に分けて確認した。
1. WebサーバへSSH接続できるか
ssh -i ./my-key.pem ec2-user@<WebサーバのパブリックIP>
ここで接続できない場合は、以下を確認する。
- WebサーバにパブリックIPがあるか
- web-sgでSSHが許可されているか
- 秘密鍵の指定が正しいか
- 秘密鍵の権限が正しいか
2. APIサーバへ踏み台経由でSSH接続できるか
ssh -i ./my-key.pem ec2-user@<APIサーバのプライベートIP>
ここで接続できない場合は、以下を確認する。
- APIサーバがPrivate Subnetにいるか
- api-sgでweb-sgからのSSHが許可されているか
- APIサーバのプライベートIPが正しいか
3. APIサーバ自身でAPIが動いているか
APIサーバ上で確認する。
curl http://localhost:8080/api/health
成功例:
{ "message": "API接続に成功しました" }
ここで失敗する場合は、APIアプリ自体が起動していない可能性がある。
確認観点:
- アプリケーションが起動しているか
- 8080番ポートで待ち受けているか
- systemdサービスが起動しているか
4. WebサーバからAPIサーバへ通信できるか
Webサーバ上で確認する。
curl http://<APIサーバのプライベートIP>:8080/api/health
ここで失敗する場合は、WebサーバからAPIサーバへの通信が通っていない。
確認観点:
- api-sgで8080番が許可されているか
- 送信元がweb-sgになっているか
- APIサーバのプライベートIPが正しいか
- APIアプリが起動しているか
5. ブラウザからWebサーバにアクセスできるか
ブラウザで以下にアクセスする。
http://<WebサーバのパブリックIP>
またはElastic IPを割り当てている場合:
http://<Elastic IP>
ここで画面が表示されない場合は、以下を確認する。
- Nginxが起動しているか
- web-sgでHTTP 80番が許可されているか
- Public SubnetのルートテーブルがInternet Gatewayを向いているか
- WebサーバにパブリックIPがあるか
6. 画面からAPIを呼び出せるか
ブラウザ上のボタンを押して、APIのレスポンスが表示されれば成功。
API接続に成功しました
ここで失敗する場合は、Nginxのリバースプロキシ設定や、WebサーバからAPIサーバへの通信を確認する。
通信エラー時の切り分け方
AWSのネットワーク設定で詰まったときは、闇雲に設定を変えない。
通信経路を分解して、どこまで到達できているか確認する。
今回の構成なら、以下の順番で見る。
1. ブラウザ → Webサーバ
2. Webサーバ → Nginx
3. Nginx → APIサーバ
4. APIサーバ → アプリケーション
5. セキュリティグループ
6. ルートテーブル
7. サブネット
8. Internet Gateway / NAT Gateway
特に見るべきポイントは以下。
- そもそもEC2は起動しているか
- パブリックIPはあるか
- セキュリティグループでポートが開いているか
- 送信元の指定は正しいか
- ルートテーブルは正しいゲートウェイを向いているか
- アプリケーションは起動しているか
- Nginxの設定は正しいか
通信できない場合、原因は大きく3つに分けられる。
1. ネットワーク経路の問題
2. セキュリティグループの問題
3. アプリケーション起動・設定の問題
この3つに分けると切り分けしやすい。
今回理解した重要ポイント
Public/Privateはサブネット名では決まらない
サブネットに public という名前を付けただけではPublic Subnetにはならない。
Public Subnetになる条件は、ルートテーブルでInternet Gatewayへのルートがあること。
0.0.0.0/0 → Internet Gateway
Private Subnetの場合は、Internet Gatewayへ直接向けない。
必要に応じてNAT Gatewayへ向ける。
0.0.0.0/0 → NAT Gateway
APIサーバは外部公開しない方が安全
APIサーバにパブリックIPを付ければ、外部から直接アクセスできる。
しかし、それは攻撃対象を増やすことにもなる。
今回のように、外部から直接アクセスする必要がないAPIサーバはPrivate Subnetに置く。
外部ユーザー
↓
Webサーバ
↓
APIサーバ
このようにWebサーバを入口にして、APIサーバは内部通信だけにする。
セキュリティグループは最小限にする
セキュリティグループでは、必要な通信だけを許可する。
悪い例:
すべてのポートを 0.0.0.0/0 に開放する
良い例:
Webサーバ:
HTTP 80 は 0.0.0.0/0
SSH 22 は自分のIPのみ
APIサーバ:
8080 は web-sg からのみ
SSH 22 は web-sg からのみ
特にSSHは管理用の入口なので、安易に全開放しない。
ルートテーブルとセキュリティグループは役割が違う
通信できないときに混同しやすいが、役割が違う。
ルートテーブル:
通信をどこへ流すか
セキュリティグループ:
その通信を通してよいか
つまり、ルートテーブルが正しくてもセキュリティグループで拒否されれば通信できない。
逆に、セキュリティグループで許可していてもルートがなければ通信できない。
両方必要。
EC2は作って終わりではない
EC2は起動するだけなら簡単。
しかし、実際に使えるサーバにするには以下が必要。
- 適切なサブネットに配置する
- セキュリティグループを設定する
- SSH接続できるようにする
- 必要なミドルウェアを入れる
- アプリを配置する
- サービスとして起動する
- ブラウザやcurlで動作確認する
EC2を作る作業と、サーバとして使える状態にする作業は別。
実務で意識すべきこと
今回の構成は学習用としては理解しやすい。
ただし、実務ではより慎重に考える必要がある。
SSHを全開放しない
SSHの22番を 0.0.0.0/0 に開けるのは危険。
実務では以下のどちらかを使う。
- 接続元IPを限定する
- AWS Systems Manager Session Managerを使う
Session Managerを使えば、SSHポートを開けずにサーバへ接続できる。
秘密鍵をサーバに置かない
今回の学習では、APIサーバへ接続するためにWebサーバを踏み台にした。
ただし、Webサーバ上に秘密鍵を置くのはセキュリティリスクがある。
実務では、秘密鍵をサーバ上に置かない運用を考える必要がある。
NATゲートウェイは削除忘れに注意
NATゲートウェイは課金が発生しやすいリソース。
学習用に作った場合、使い終わったら削除する。
削除対象の例:
- EC2インスタンス
- NAT Gateway
- Elastic IP
- VPC
削除順も重要。
依存関係があるため、NAT GatewayやElastic IPが残っているとVPCを削除できないことがある。
リソース削除の流れ
学習後は不要なリソースを削除する。
削除の流れは以下。
1. EC2インスタンスを終了
2. NAT Gatewayを削除
3. Elastic IPを解放
4. VPCを削除
VPCを削除すると、サブネット、ルートテーブル、セキュリティグループ、インターネットゲートウェイなどもまとめて削除される場合がある。
ただし、関連リソースが残っていると削除できないことがあるため、依存関係を確認する必要がある。
まとめ
今回、VPCとEC2を使って、WebサーバとAPIサーバを分離した構成を作った。
学んだことは以下。
- VPCはAWS上の仮想ネットワーク
- サブネットでネットワークを分割する
- Public Subnetは外部公開するリソースを置く
- Private Subnetは外部公開しないリソースを置く
- Public/Privateはルートテーブルで決まる
- Internet GatewayはVPCとインターネットをつなぐ
- NAT GatewayはPrivate Subnetから外部へ出るために使う
- セキュリティグループはEC2単位の通信制御
- ネットワークACLはサブネット単位の通信制御
- EC2はAMI、インスタンスタイプ、キーペア、SG、サブネットを指定して作る
- WebサーバはPublic Subnetに置く
- APIサーバはPrivate Subnetに置く
- NginxのリバースプロキシでAPIサーバへ通信する
- 通信エラーは経路ごとに分解して切り分ける