2026年10月27日火曜日

EC2にRDSから情報を取得し、FlaskでWebアプリとして表示させる(2026年版)

今回は、RDSに格納したNASAのAPOD(Astronomy Picture of the Day)のデータを、EC2上のFlaskアプリから取得し、Webページとして80番ポートで公開します。途中でつまずいた点(アクセスできない・HTMLタグがそのまま出る・画像が出ない)も、確認しながら解決していきます。

事前知識
・EC2でサーバを立ち上げてクラウドシェルで接続(https://smizunolab.blogspot.com/2023/10/ec2.html)
・Pythonでデータベースに情報を格納する(https://smizunolab.blogspot.com/2024/07/python.html)
・EC2にRDSから情報を取得し、FlaskでWebアプリとして表示させる(2024/7/10の記事)

完成した画面
完成した画面

全体の構成と作業の流れ

Google Colab(NASA APIから取得してRDSへ格納)→ RDS(MySQL、DB:nasa、テーブル:apod)→ EC2(Flask、ポート80)→ ブラウザ(http://EC2のパブリックDNS)という構成です。ブラウザ→EC2は80番(HTTP)、EC2→RDSは3306番(MySQL)で通信するので、どちらもセキュリティグループでの許可が必要です。

作業の流れ:①RDSを起動 → ②データを確認 → ③EC2を作成 → ④EC2に接続 → ⑤インストール → ⑥ファイル作成 → ⑦起動・確認 → ⑧つまずきの解決

1. RDSの準備とデータの確認

(1) RDSを起動する

RDSコンソールのデータベース一覧
RDSコンソールのデータベース一覧

RDSコンソールの「データベース」でdatabase-3を確認します。ステータスが「一時的に停止済み」なら、選択して「アクション」→「開始」を押します。起動には数分かかるので、ステータスが「利用可能」になるまで待ちます。

ポイント RDSは停止しても7日後に自動で起動します。使わないときは停止して料金を抑えましょう。

(2) 前回の復習:ColabからAPODを取得してRDSに格納

ColabでAPOD APIからデータを取得するコード
ColabでAPOD APIからデータを取得するコード

NASAのAPOD APIからデータを取得し、RDSのnasaデータベースのapodテーブルにINSERTしています(詳しくは「Pythonでデータベースに情報を格納する」を参照)。今回はこのデータをFlaskで表示します。

注意 APIキーやパスワードはコードに直書きせず、Colabの「シークレット」に登録して userdata.get() で読みます。

(3) CloudShellからRDSのデータを確認する

$ mysql -h (RDSのエンドポイント) -P 3306 --ssl-ca /certs/global-bundle.pem --ssl-verify-server-cert -u admin -p

MySQL> use nasa;
MySQL> show tables;
MySQL> SELECT date, title FROM apod;
apodテーブルがあることを確認
apodテーブルがあることを確認
ポイント 接続コマンドは、RDSの「接続とセキュリティ」→「クラウドシェル」で表示されるものをコピーすると確実です。apodテーブルにデータがあればOKです。

2. EC2を作成して接続する

(1) インスタンスを起動:名前・AMI・タイプ

インスタンスを起動の画面
インスタンスを起動の画面

EC2の「インスタンスを起動」から、次のように設定します。
・名前:flask-app-01(任意)
・AMI:Amazon Linux 2023
・インスタンスタイプ:t3.micro(無料利用枠)
ユーザー名は ec2-user になります。

(2) キーペアを作成する

キーペアの作成
キーペアの作成

「新しいキーペアの作成」でキーペア名を入力し(例:flask-app-01)、タイプはRSA、形式は.pemにします。ダウンロードした.pemは安全な場所に保存します。

ポイント 今回はブラウザのEC2 Instance Connectで接続するので.pemは使いませんが、SSHで接続するときのために作成しておきます。

(3) ネットワーク設定:HTTPを許可する

ネットワーク設定(SSH・HTTPS・HTTPを許可)
ネットワーク設定(SSH・HTTPS・HTTPを許可)

「セキュリティグループを作成」を選び、SSH・HTTPS・HTTPのトラフィックを許可します。80番で公開するのでHTTPのチェックは必須です。ストレージは8GiBのままで「インスタンスを起動」します。

ポイント ここでHTTPにチェックを入れておくと、後でインバウンドルールを編集しなくて済みます。

(4) EC2 Instance Connectで接続する

インスタンスに接続の画面
インスタンスに接続の画面

インスタンスを選択して「接続」→「EC2 Instance Connect」タブで、ユーザー名 ec2-user のまま「接続」します。ブラウザ上にターミナルが開き、Amazon Linux 2023のロゴが表示されれば接続完了です。

3. Flaskアプリを作る

(1) pip・Flask・MySQLコネクタをインストール

$ sudo yum install python3-pip
(Is this ok [y/N]: と聞かれたら y)

$ sudo pip install Flask mysql-connector-python
python3-pipのインストール
python3-pipのインストール
ポイント 80番ポートはroot権限がないと使えないため、後で sudo python3 app.py で起動します。sudoで動くPythonから見えるように、ライブラリも sudo pip でインストールしておきます。
注意 sudoを付けずに pip install すると ec2-user 用にインストールされるため、sudo python3 で起動したときに「Flaskが見つからない」エラーになります。

(2) アプリのフォルダとファイルを作る:ホームディレクトリに作成

$ mkdir my_flask_app
$ cd my_flask_app
$ touch app.py
$ mkdir templates
$ touch templates/index.html
$ ls -l

できあがる構成は次のとおりです。

my_flask_app/
├── app.py
└── templates/
    └── index.html
ポイント Flaskはtemplatesフォルダの中からHTMLを探します。フォルダ名は templates(複数形)で正確に作ります。

(3) app.pyの作成:RDSからデータを取得してHTMLに渡す

vi app.py で次のコードを書き込みます。

from flask import Flask, render_template
import mysql.connector

app = Flask(__name__)

def get_db_connection():
    conn = mysql.connector.connect(
        host="(RDSのエンドポイント)",
        user="admin",
        passwd="(自分のパスワード)",
        database="nasa"
    )
    return conn

@app.route('/')
def index():
    conn = get_db_connection()
    cursor = conn.cursor(dictionary=True)
    cursor.execute("SELECT * FROM apod")
    apod_data = cursor.fetchall()
    cursor.close()
    conn.close()
    return render_template('index.html', apod_data=apod_data)

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=80)

・host:RDSのエンドポイントを指定します(localhostではありません)。ホストやパスワードは自分の環境に揃えてください。
・dictionary=True で、列名(titleなど)で値を取り出せます。
・render_template で index.html に apod_data を渡します。
・port=80 で起動するので、URLにポート番号を付ける必要がありません。

(4) index.htmlの作成:データを並べて表示する

vi templates/index.html で次のコードを書き込みます(完成版)。

<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>APOD</title>
</head>
<body>
    <h1>Astronomy Picture of the Day</h1>
    {% for apod in apod_data %}
        <div>
            <h2>{{ apod.title }}</h2>
            <p>{{ apod.date }}</p>
            {% if apod.media_type == 'image' %}
                <a href="{{ apod.url }}"><img src="{{ apod.hdurl }}" alt="{{ apod.title }}" width="600"></a>
            {% else %}
                <p><a href="{{ apod.url }}">NASAの記事ページで見る</a></p>
            {% endif %}
            <div>{{ apod.explanation | safe }}</div>
        </div>
        <hr>
    {% endfor %}
</body>
</html>

・{% for %} で1件ずつ繰り返し、{{ apod.title }} で列の値を表示します。
・画像は hdurl を使い、クリックすると記事ページ(url)が開きます。
・| safe で説明文に含まれるHTMLを有効にします。
・width="600" で画像がはみ出さないようにします。

ポイント 最初は2024年の記事どおりのHTMLで動かし、4章のつまずきを直してこの形になりました。

(5) アプリを起動してブラウザで確認する

$ sudo python3 app.py
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:80

(止めるときは Ctrl + C)

ブラウザで http://(パブリック IPv4 DNS) を開きます(例:http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com)。
・パブリック IPv4 DNS(またはパブリックIP)は、EC2のインスタンス詳細で確認できます。
・80番なので「:80」は付けなくて大丈夫です。
・証明書を設定していないので、https:// ではなく http:// で開きます。

解決 ターミナルに "GET / HTTP/1.1" 200 と出れば、ブラウザからのアクセスが届いています。

4. つまずきを確認しながら直す

つまずき①:「このサイトにアクセスできません」

ERR_CONNECTION_TIMED_OUT(応答なし)
ERR_CONNECTION_TIMED_OUT(応答なし)
つまずき ブラウザが待たされた末にタイムアウトする場合は、ポートがセキュリティグループで許可されていません(動画では5000番で起動して発生しました)。
セキュリティグループのインバウンドルールを編集
セキュリティグループのインバウンドルールを編集
解決 EC2 → セキュリティグループ → インバウンドルールに「HTTP / TCP 80 / 0.0.0.0/0」があるか確認します。あわせて、アプリが sudo python3 app.py で起動中かも確認します。

つまずき②:HTMLタグがそのまま表示される

説明文のHTMLタグが文字のまま表示される
説明文のHTMLタグが文字のまま表示される

explanationに<a>や<strong>などのHTMLが含まれていますが、Jinja2は安全のため {{ }} の中身を文字として表示(エスケープ)します。

<!-- 変更前 -->
<p>{{ apod.explanation }}</p>

<!-- 変更後 -->
<div>{{ apod.explanation | safe }}</div>
解決 | safe は信頼できるデータ(今回はNASAのデータ)にだけ使います。誰でも書き込めるデータに使うと、不正なスクリプトが動く危険(XSS)があります。

つまずき③:画像が表示されない

url列とhdurl列の中身を確認
url列とhdurl列の中身を確認

タイトルの横に「壊れた画像」が出るのは、<img>に渡したURLで画像を取得できていないためです。データベースを確認すると、url列は記事ページ(science.nasa.gov/image-article/...)で、画像ファイルはhdurl列に入っていました。

MySQL> SELECT date, url, hdurl FROM apod ORDER BY date DESC;

<img src="{{ apod.hdurl }}" width="600">

完成

RDSのデータがWebページに表示された
RDSのデータがWebページに表示された

タイトル・日付・画像・説明文(リンク付き)が表示されました。データはRDSから毎回読み出しているので、Colabで新しい日のデータを追加すれば、ページを再読み込みするだけで反映されます。

ポイント 同じ記事が2回出るのは、データを2回格納したためです。dateにUNIQUE制約を付けると重複を防げます。

まとめと注意点

今日やったこと:RDSを起動してデータを確認 → EC2を作成(HTTPを許可) → Flask+MySQLコネクタを sudo pip で導入 → app.pyとindex.htmlを作成 → sudo python3 app.py で80番公開

確認のポイント:アクセスできない → セキュリティグループとポート/タグが出る → | safe/画像が出ない → urlとhdurlの中身。うまくいかないときは、DBの中身とターミナルのログを見ます。

注意 debug=True のまま公開し続けない(デバッガから操作される危険があります)。パスワードやAPIキーを資料・GitHubに載せない。使い終わったらEC2とRDSを停止する。

0 件のコメント:

コメントを投稿