注意
練習のためにウェブサイトを構築する場合、使い終わったら、EC2インスタンスなどは削除しておきましょう。
環境
- EC2, RDS, ALB, Multi AZ
- ubuntu 22.04
- Flask 2.1.2
- Docker-compose v2.22.0
背景
典型的なウェブアプリのVPCを構築する練習をしたい。
結論
図のようなアーキテクチャを構築する。Multi AZ構成にすることで、可用性を高めることができる。
EC2, RDS, ALBを使って動的ウェブアプリをホストする
今回のウェブアプリの概要
初回アクセスの時に、web(Flask)コンテナが、DBにアクセスしてテーブルを作成します。
ウェブサイトへのアクセスがあったら、DBから適当なデータを取得して、それをブラウザに表示する。
docker-composeを用いることで、EC2インスタンスにnginxコンテナとwebコンテナを構築して、webコンテナからRDS(DB)にアクセスする。
注意
以下で説明する手順について、AWSコンソールからGUIの操作で行う部分については、細かい説明は省略してあります。
手順(GUIでアーキテクチャを構築)
東京リージョンで全ての作業を行うので、東京リージョンを選択してください。
(1) VPCを作る
VPCを作るときに、上のアーキテクチャの図のように、二つのAZそれぞれにpublic subnet 1つとprivate subnet 1つが作られるように設定する。今回はS3は使ってないので、作成しなくてもいいです。
(2) EC2インスタンスを立てる
二つのパブリックサブネットそれぞれに、一台ずつのEC2インスタンスを立てる。EC2作成時に、先ほど作ったVPCを選択すると、そのVPC内のパブリックサブネットが選択できるようになります。Auto-assign public IPをEnableにすることによって、グローバルIPアドレスが割り当てられるようにしてください。
セキュリティグループについて、全てのIPアドレス(0.0.0.0/0)からのhttp, https通信を許可してください。また、My IPからのssh通信を許可してください。
(3) ALBを立てる
ALBを作成する時、今回使用するVPCを選択する。サブネットのMappingsとして、VPC内の二つのパブリックサブネットを選択する。
セキュリティグループは、EC2インスタンスと同じでいいと思います。
そして、先ほど作った二つのEC2インスタンスをルーティングのターゲットとします。ルーティングのターゲットとなるTarget Groupは、あらかじめ作成しておいてください。Target Group作成時に選択するTarget Typeは、instancesでいいと思います。二つのEC2インスタンスを選択して、Target Groupを作成してください。
(4) RDSを立てる
Engine TypeはMySQLでいいと思います。
今回は、Multi-AZ DB instanceを選択します。
ユーザ名とマスターパスワードは、EC2インスタンスからRDSにアクセスする時に必要になります。
Connectivityのcompute resourceの選択で、EC2インスタンスを一つ選択します。EC2インスタンス二つと接続したいですが、一つしか選べないので、とりあえず、一つ目のEC2インスタンスを選択します。後で、もう一つのEC2インスタンスがRDSへ接続できるように、一つ目のEC2インスタンスが持っているセキュリティグループを加えます。
手順(EC2インスタンスにSSH接続をして、ウェブAPIを構築する)
(1) EC2インスタンスにSSH接続する
下記のコマンドで、SSH接続をする。
yourkey.pemは、鍵のパスです。xxx.xxx.xxx.xxxは、EC2インスタンスのグローバルIPアドレスです。
sudo ssh -i yourkey.pem ubuntu@xxx.xxx.xxx.xxx
(2) dockerをインストールする。
下記のように、dockerをインストールする。上記の公式ドキュメントに書いてあるコマンドをそのまま使った。
Add Docker's official GPG key:
$ sudo apt-get update
$ sudo apt-get install ca-certificates curl gnupg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
Add the repository to Apt sources:
$ echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
下記で正常にインストールされたことを確認する。
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
...
Hello from Docker!
This message shows that your installation appears to be working correctly.
(3) docker-composeをインストールする。
下記のように、docker-composeをインストールする。上記のリンクに書いてあるコマンドと基本的には同じで、バージョンだけ変更した。今回は、v2.22.0をインストールする。
下記がうまくいかなかったら、下記を実行する前に、「sudo -i」を実行して、次に下記からsudoを抜いたコマンドを実行してください。
$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
下記で、docker-composeが正常にインストールされているかどうかを確認する。
$ docker-compose --version
Docker Compose version v2.22.0
(4) プロジェクトをgit cloneする
docker-composeのプロジェクトがGitHubにある前提で話を進める。下記のようにgit cloneする。
今回は、「/var/www」フォルダを作り、そこにプロジェクトを配置する。
「YourName」はあなたのGithubアカウントの名前、「VPC-RDS-Test」は今回使うプロジェクトのレポジトリの名前を書いてください。
/var$ sudo mkdir www
/var$ cd www
/var/www$ sudo git clone https://github.com/YourName/VPC-RDS-Test
...
(5) プロジェクトの中身を確認
今回、プロジェクトの階層構造や、ファイルの中身は下記のようになっている
ubuntu@ip-10-0-9-22:/var/www/VPC-RDS-Test$ ls -l
total 20
... Dockerfile
... app.py
... docker-compose.yml
... nginx.conf
... requirements.txt
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
# RDSデータベース設定
db_user = 'YourName'
db_password = 'YourPassword'
db_host = 'YourRdsEndpoint'
db_name = 'test_db'
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{db_user}:{db_password}@{db_host}/{db_name}'
db = SQLAlchemy(app)
# モデル定義
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
def __repr__(self):
return '<Item %r>' % self.name
# データベース初期化とサンプルデータの追加
@app.before_first_request
def create_tables():
db.create_all()
# データが存在しない場合のみ追加
if not Item.query.first():
db.session.add(Item(name="Sample Item 1"))
db.session.add(Item(name="Sample Item 2"))
db.session.commit()
# ルートエンドポイント
@app.route('/')
def index():
items = Item.query.all()
return jsonify([item.name for item in items])
if __name__ == '__main__':
app.run(host='0.0.0.0')
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
# Dockerfile
FROM python:3.8-slim
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
CMD ["flask", "run"]
events {}
http {
server {
listen 80;
location / {
proxy_pass http://app:5000;
}
}
}
Flask==2.1.2
pymysql==1.0.2
SQLAlchemy==1.4
flask_sqlalchemy==2.5.1
Werkzeug==2.2.2
cryptography==3.4.7
(6) RDSに今回使うDBを作成する
今回使うDBの名前は「test_db」です。EC2インスタンスからRDSにアクセスして、DBを作成します。下記はEC2インスタンスで実行するコマンドです。
「YourRdsEndpoint」は、RDSのドメイン名(Endpoint)を書いてください。「YourName」には、RDSを作成したときに決めた名前を書いてください。パスワードの入力が求められますが、RDSを作成したときに決めたパスワードを書いてください。
$ sudo apt update
$ sudo apt install mysql-server
$ systemctl status mysql
...
Active: active (running)
...
$ mysql -h YourRdsEndpoint -P 3306 -u YourName -p
Enter password:
mysql> create database test_db;
Query OK, 1 row affected (0.04 sec)
...
MySQLから抜けるには、exitコマンドを使います。
(7) docker-composeでビルドして、ウェブサイトにアクセスする
下記でwebコンテナを起動させている間、このEC2インスタンスのグローバルIPアドレスにアクセスすると、ブラウザに["Sample Item 1", "Sample Item 2"]という文字列が表示されると思います。これは、webコンテナがDBにアクセスして、データを取得できていることを示します。
/var/www/VPC-RDS-Test$ sudo docker-compose build
...
/var/www/VPC-RDS-Test$ sudo docker-compose up
...
vpc-rds-test-app-1 | * Running on all addresses (0.0.0.0)
vpc-rds-test-app-1 | * Running on http://127.0.0.1:5000
vpc-rds-test-app-1 | * Running on http://172.18.0.2:5000
vpc-rds-test-app-1 | Press CTRL+C to quit
...
下記のようなウェブ画面が表示されると思います。下記は、EC2インスタンスに直接アクセスしています。
ctrl+cでwebコンテナの実行を止めて、下記を改めて実行すると、バックグラウンドでの実行ができます
/var/www/VPC-RDS-Test$ sudo docker-compose up -d
...
(8) もう一つのEC2インスタンスをセットアップ
次に、もう一つのEC2インスタンスに対して同じことを実行します。セキュリティグループが、一つ目のEC2インスタンスと同じものを持っているようにしてください。これにより、もう一つのEC2インスタンスがRDSにアクセスすることが可能になるはずです。
ALBのDNS NAME(ドメイン名)にアクセスすると、EC2インスタンスにルーティングされて、上記の画像と同じ表示になるはずです。
(9) ALBがロードバランスしていることを確かめる
Multi AZ構成を採用しているので、ALBを使っているが、ALBが正常に機能していることを確かめたい。ALBのルールに、「/path1」でアクセスされたら1つ目のEC2インスタンスにルーティングして、「/path2」でアクセスされたら2つ目のEC2インスタンスにルーティングするというルールを加える。GUIでALBの設定を変更してください。ALBのAdd listenerから条件を加えることができます。
スクリプトは以下のように変更します。
1つ目のEC2インスタンスにあるapp.pyに下記を追加して再ビルドする。/path1でアクセスされたら、ALBによって1つ目のEC2インスタンスにルーティングされて、そのwebコンテナはDBの1行目を取得して表示する。
@app.route('/path1')
def path1():
item = Item.query.filter_by(id=1).first()
return item.name
2つ目のEC2インスタンスにあるapp.pyに下記を追加して再ビルドする。/path2でアクセスされたら、ALBによって2つ目のEC2インスタンスにルーティングされて、そのwebコンテナはDBの2行目を取得して表示する。
@app.route('/path2')
def path2():
item = Item.query.filter_by(id=2).first()
return item.name
"/path1", "/path2"にアクセスして、意図通りのデータがブラウザに表示されていれば、ALBのルーティングが機能していることが分かる。"/path1"にアクセスすると、"Sample Item 1"が表示されて、"/path2"にアクセスすると、"Sample Item 2"が表示されるようになっているはず。
①"{ALBのドメイン名}" にアクセスしたときのウェブ画面は下図です。この時、ALBを介して、どちらかのEC2インスタンスに到達している。
②"{ALBのドメイン名}/path1" にアクセスしたときのウェブ画面は下図です。この時、ALBを介して、一つ目のEC2インスタンスに到達している。
③"{ALBのドメイン名}/path2" にアクセスしたときのウェブ画面は下図です。この時、ALBを介して、二つ目のEC2インスタンスに到達している。
今後の展望
典型的なウェブアプリのVPC構造を構築できたと思うので、次は、Terraformとかを使ってみる。
参考
- https://www.rworks.jp/cloud/aws/aws-column/aws-entry/22067/