はじめに
この記事は個人の学習用として作成しています。記事の内容で間違いなどあればご指摘ください。
AWSのネットワークとEC2を理解するため、WebサーバーとAPIサーバーを別サブネットに配置した構成を作成した。
単にEC2を起動するのではなく、VPC、サブネット、ルートテーブル、Internet Gateway、NAT Gateway、Security Group、Nginxまで一通り設定し、ブラウザからAPIへ到達するところまで確認した。
今回の目的は「EC2を起動できる」だけではなく、ブラウザからの通信がどのAWSリソースを通り、最終的にAPIサーバーへ届くのかを理解すること。
作成した構成
Internet
|
Internet Gateway
|
Public Subnet 10.0.1.0/24
|
Web Server EC2
Nginx
|
| Private IP通信
v
Private Subnet 10.0.2.0/24
|
API Server EC2
:8080
|
/api/health
- VPC:WebサーバーとAPIサーバーを配置するネットワーク領域
- Public Subnet:インターネットからアクセスされるWebサーバーを配置
- Private Subnet:外部から直接アクセスさせないAPIサーバーを配置
- Web Server:Nginxで画面配信とAPIへのリバースプロキシを担当
- API Server:8080番ポートでAPIを公開
- NAT Gateway:Private Subnetからインターネットへ出るための経路
- Elastic IP:NAT Gatewayが利用する固定のグローバルIP
- Security Group:Webサーバー、APIサーバー単位で通信を制御
作業全体の流れ
- VPCを作成する
- Public SubnetとPrivate Subnetを作成する
- Internet GatewayをVPCへ接続する
- Public Subnet用ルートテーブルを設定する
- Elastic IPとNAT Gatewayを作成する
- Private Subnet用ルートテーブルを設定する
- Security Groupを作成する
- Webサーバー用EC2をPublic Subnetに作成する
- APIサーバー用EC2をPrivate Subnetに作成する
- SSHでWebサーバーへ接続する
- Webサーバー経由でAPIサーバーへ接続する
- WebサーバーにNginxを設定する
- APIサーバーを起動する
- Web → API → ブラウザの順番で疎通確認を行う
1. VPCを作成する
目的
- AWS上に今回のWebシステム専用のネットワーク領域を作る
- サブネットやルーティング、Security Groupなどのネットワーク設定をVPC内で管理する
今回の構成では 10.0.0.0/16 のようなプライベートIPアドレス範囲を利用した。
学んだこと
- CIDRはネットワークで使用できるIPアドレス範囲を表す
-
/24の場合は256個のIPアドレスを持つ - AWSのサブネットでは5個のIPアドレスが予約されるため、
/24では251個を利用できる
2. Public Subnetを作成する
目的
インターネットからアクセスされるWebサーバーを配置するために作成した。
- CIDR:
10.0.1.0/24 - Web Server EC2を配置
- Internet Gatewayへの経路を持たせる
サブネットを作っただけではPublic Subnetにはならない。ルートテーブルに 0.0.0.0/0 → Internet Gateway を設定することで、インターネットへの経路を持つサブネットになる。
3. Private Subnetを作成する
目的
APIサーバーをインターネットへ直接公開しないために作成した。
- CIDR:
10.0.2.0/24 - API Server EC2を配置
- API ServerにはPublic IPを持たせない
WebサーバーからAPIサーバーへの通信には、APIサーバーのPrivate IPを利用した。
今回のAPI Serverは、
10.0.2.217:8080
で待ち受ける構成にした。
4. Internet Gatewayを設定する
目的
VPCとインターネットを接続するために作成した。
作成したInternet GatewayをVPCへアタッチし、Public Subnetのルートテーブルに次の経路を設定した。
10.0.0.0/16 → local0.0.0.0/0 → Internet Gateway
local はVPC内部の通信に使用し、0.0.0.0/0 はそれ以外の宛先をInternet Gatewayへ送る設定になる。
5. Elastic IPとNAT Gatewayを作成する
NAT Gatewayを作成する目的
Private SubnetにあるAPIサーバーから、インターネットへ通信できるようにするために作成した。
APIサーバー自身を外部公開する必要はないが、OS更新やパッケージ取得では外部通信が必要になる。
API Server
|
Private Subnet
|
NAT Gateway
|
Internet Gateway
|
Internet
Elastic IPを関連付ける目的
NAT Gatewayがインターネットへ通信するときの固定グローバルIPとして利用する。
- Elastic IPを取得する
- Public SubnetにNAT Gatewayを作成する
- NAT GatewayへElastic IPを割り当てる
NAT Gatewayは作成しているだけでも料金が発生する。学習終了後は削除漏れがないか確認する。
6. Private Subnetのルートを設定する
目的
Private Subnetから直接Internet Gatewayへ出さず、NAT Gateway経由で外部通信させる。
Private Subnetのルートテーブルでは次のように設定した。
10.0.0.0/16 → local0.0.0.0/0 → NAT Gateway
これによりAPIサーバーからインターネットへの通信はできるが、インターネットからAPIサーバーへ直接接続する構成にはならない。
7. Security Groupを設定する
Web Server
Web Serverはブラウザからアクセスするため、HTTPと管理用SSHを許可した。
- TCP 80:Webアクセス用
- TCP 22:SSH接続用
- SSHは可能な限り自分の接続元IPに限定する
API Server
API Serverの8080番ポートはWeb Serverからのみアクセス可能にする。
- TCP 8080
- 接続元:Web ServerのSecurity Group
API Serverの8080番を 0.0.0.0/0 に公開するのではなく、Web Serverからの通信だけを許可することで、サーバー間通信を必要な範囲に限定する。
8. EC2インスタンスを作成する
Web Server
Public SubnetにAmazon Linux 2023のEC2を作成した。
目的は次の2つ。
- ブラウザへHTMLを返す
-
/api/へのアクセスをPrivate SubnetのAPI Serverへ転送する
API Server
Private SubnetにEC2を作成した。
- Public IPは持たせない
- Web ServerからPrivate IPで接続する
- 8080番ポートでAPIを起動する
これにより、インターネット利用者がAPI Serverへ直接アクセスせず、Web Serverを経由する構成にした。
9. SSHとSCPでサーバーへ接続する
SSH
MacからWeb Serverへ接続した。
ssh -i ./my-key.pem ec2-user@<Web ServerのPublic IP>
-
ssh:別サーバーへログインする -
-i:認証に利用する秘密鍵を指定する -
ec2-user:Amazon Linuxのログインユーザー
SCP
セットアップシェルをMacからWeb Serverへ転送した。
scp -i ./my-key.pem ./web-setup.sh ec2-user@<Web ServerのPublic IP>:/home/ec2-user/
-
scp:SSHを利用してファイルを転送する - 最初のパス:転送元
-
user@host:path:転送先
scp の転送元ファイルは、コマンドを実行している端末上に存在する必要がある。Mac上のファイルを送りたい場合は、EC2へログインした後ではなくMac側から実行する。
10. Web ServerにNginxを構築する
web-setup.sh をWeb Serverへ転送して実行した。
sudo bash ./web-setup.sh
sudo を利用した理由は、Nginxのインストール、/etc/nginx の編集、systemdによるサービス操作に管理者権限が必要になるため。
セットアップでは主に次の処理を行った。
-
dnf update -y:Amazon Linuxのパッケージを更新 -
dnf install -y nginx:Nginxをインストール -
systemctl start nginx:Nginxを起動 -
systemctl enable nginx:OS起動時にNginxを自動起動 - HTMLファイルを
/usr/share/nginx/htmlに配置 - Nginxのリバースプロキシを設定
- Nginxを再起動
11. NginxからAPI Serverへ転送する
ブラウザから /api/ にアクセスされた場合、NginxからPrivate SubnetのAPI Serverへ転送する。
location /api/ {
proxy_pass http://10.0.2.217:8080/api/;
}
通信の流れは次のようになる。
Browser
|
HTTP :80
v
Web Server / Nginx
|
Private IP :8080
v
API Server
フロント側では /api/health を呼び出し、API Serverが返したJSONを画面へ表示する構成にした。
12. 疎通確認を行う
障害箇所を切り分けるため、通信経路を一気に確認せず段階的に確認した。
API Server単体
API Server上で確認した。
curl http://localhost:8080/api/health
結果:
{"message":"API接続に成功しました"}
API Server自身は正常に動作していることを確認できた。
Web ServerからAPI Server
Web ServerからPrivate IPへ直接アクセスして確認する。
curl http://10.0.2.217:8080/api/health
ここが成功すれば、Security GroupやVPC内通信まで正常と判断できる。
Nginx経由
Web Server上で次を確認する。
curl http://localhost/api/health
ここが成功すれば、Nginxのリバースプロキシ設定まで正常と判断できる。
ブラウザ
最後にWeb ServerのPublic IPへアクセスする。
http://<Web ServerのPublic IP>
API接続テストを実行し、ブラウザ → Nginx → API Serverまでの一連の通信を確認した。
Elastic IPでWebサーバにアクセスできるか確認してAPIの接続テストも完了した
ハンズオンでつまずいたところ
1. SCPの接続先を書き間違えた
誤ってユーザー名とIPアドレスを連結して入力した。
ec2-user52.xxx.xxx.xxx
正しくは @ が必要だった。
ec2-user@52.xxx.xxx.xxx
Could not resolve hostname が出た場合、まず接続先の書式を確認する。
2. SCPを実行する場所を間違えた
Web ServerへSSHした後に、
scp ./web-setup.sh ...
を実行したため、
No such file or directory
になった。
web-setup.sh はMacに存在していたため、一度Web Serverからログアウトし、Mac側からSCPを実行して解決した。
コマンド実行時に「今どのサーバーにいるのか」を意識する必要がある。プロンプト、pwd、hostname、ls を利用すると現在位置を確認しやすい。
3. Macで修正したシェルがEC2へ反映されていなかった
Mac上の web-setup.sh を修正したが、一度転送済みのEC2上のファイルは自動更新されなかった。
再度SCPを行い、
grep API_SERVER_IP ~/web-setup.sh
でEC2上のファイルが更新されていることを確認した。
4. API ServerのPrivate IPを設定していなかった
Nginxの設定に次のプレースホルダーが残っていた。
<api_server_private_ip>
そのため、
host not found in upstream
が発生し、Nginxが起動できなかった。
10.0.2.217 に変更してから、
sudo nginx -t
を実行し、設定ファイルの構文チェックが成功することを確認した。
5. ブラウザでJSON解析エラーが発生した
ブラウザでは、
Unexpected token '<'
というエラーが発生した。
フロント側はJSONを期待している一方、NginxなどがHTMLのエラーページを返している場合、HTMLの先頭文字 < をJSONとして解析してこのエラーになる。
原因調査では、ブラウザだけを見るのではなく、
- API Server自身
- Web Server → API Server
- Nginx → API Server
- Browser → Web Server
の順番で curl を利用して切り分けた。
6. Nginxの設定警告
nginx -t で次の警告が出た。
conflicting server name "_" on 0.0.0.0:80
構文チェック自体は成功しており起動できたが、同じ server_name _ を持つserverブロックが複数存在する可能性がある。
動作していることと設定が整理されていることは別なので、警告が残っている場合は /etc/nginx 配下の設定重複も確認する。
今回学んだこと
- VPCはAWS上のネットワーク領域として利用する
- Public / Private Subnetはルートテーブルの構成によって役割が変わる
- Internet GatewayはVPCとインターネットを接続する
- NAT GatewayはPrivate Subnetから外へ出る通信に利用する
- Elastic IPはNAT Gatewayの固定グローバルIPとして利用できる
- Web ServerとAPI Serverを別サブネットに分けることで直接公開する範囲を限定できる
- Security Groupでは必要な通信だけを許可する
- EC2間の通信ではPrivate IPを利用できる
- Nginxをリバースプロキシとして利用し、WebとAPIを分離できる
- SSHとSCPでは現在操作している端末と接続先を意識する
- 障害調査では通信経路を段階的に切り分ける
考えたこと
今回の構成はネットワークとサーバー間通信を理解するための最小構成。実務では可用性、運用性、セキュリティを考えて構成を追加する必要がある。
- Web Serverを直接公開する代わりにALBを入口にする
- EC2を複数AZへ配置して単一障害点を減らす
- API ServerをPrivate Subnetに維持し、外部から直接アクセスさせない
- SSH用の秘密鍵をWeb Serverへ置く方法は学習用途に留める
- 実務ではAWS Systems Manager Session Managerなどを利用し、秘密鍵をサーバーへ配布しない構成を検討する
- NAT Gatewayはコストが発生するため、本当に必要な外部通信かを確認する
- 学習後はEC2、NAT Gateway、Elastic IPなどの不要なリソースを削除する
まとめ
今回のハンズオンでは、EC2を起動するだけでなく、VPC内にPublic SubnetとPrivate Subnetを作成し、Web ServerとAPI Serverを分離した。
最も大きかった学びは、AWSの各サービスを単独で覚えるのではなく、
通信元
↓
ルート
↓
Security Group
↓
Web Server
↓
Nginx
↓
API Server
という通信経路として考えることだった。
設定ミスが発生した際も、エラーメッセージ、ls、grep、nginx -t、systemctl、curl を利用して一つずつ原因を切り分け、最終的にブラウザからPrivate SubnetのAPI Serverまで接続できることを確認した。
