Google App Engine (GAE)でウェブサイトを公開してみた

私はAWSのEC2にSSH接続してローカルで構築したウェブサイト（技術ブログ）をアップロードし、公開している。しかし、AWSでは料金の上限を設けることができないため、GAEにウェブサイトを移行しようと思う。GAEだと、料金の上限を設けて上限になるとwebサイトを翌日まで非公開する機能がある。

まずはじめに、ChatGPTからFlaskの使い方を学んだ。その内容は以下の記事に記載している。

https://qiita.com/kaiyo_yume/items/3138e8f9dd776414f987

# App Engine用にローカルなウェブサイトを作成

次に、Google CloudのApp Engineのガイドを読みながら、App Engine用のローカルな技術ブログを作成し、ローカルでテストした。

https://cloud.google.com/appengine/docs/standard/python3/building-app?hl=ja

**※ここからは、ガイドの通りに進めて躓いた所と、自分のウェブサイトに合わせた変更箇所を書いていく。**

## EC2上で公開していた技術ブログのファイル構造を変更
技術ブログのファイルのコードも載せるが、必要なければファイル構造のスクリーンショットまでスキップしてほしい。

EC2で公開中の技術ブログとほぼ同じコードだが、パスだけファイル構造の変更に合わせて書き換えた。
### index.html:
```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>

        function setfiletomain(fname){
            const el = document.getElementById("mainframe");
            el.setAttribute("src", fname);

        }

        document.addEventListener("DOMContentLoaded", function () {
            document.getElementById("backButton").addEventListener("click", function () {
                var iframe = document.getElementById("mainframe");
                iframe.contentWindow.history.back();
            });

            document.getElementById("forwardButton").addEventListener("click", function () {
                var iframe = document.getElementById("mainframe");
                iframe.contentWindow.history.forward();
            });
        });
    </script>
    <style>
        body, html {
            height: 100%;
            margin: 0;
        }
        #grid-container {
            display: grid;
            grid-template-columns: 300px 1fr; /*2列*/
            grid-template-rows: 80px 1fr 60px; /*３行*/
            height: 100%;
        }
        .grid-item {
            padding:5px 20px;
            box-sizing: border-box;
        }
        #header {
            background-color: lightblue;
            grid-column: 1 / span 2;
            display: flex;
        }
        button {
            background-color: lightblue;
            border: none;
            padding: 10px 20px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
        }
        #file-tree {
            background-color: #d3f6d3;
        }
        #main {
            padding:0px 20px;
            background-color: #ffffff;
        }
        #footer {
            background-color: lightblue;
            grid-column: 1 / span 2;
        }
        iframe {
            /*position: absolute;*/
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border: none;
        }
    </style>
</head>
<body>
    <div id="grid-container">
        <div id="header" class="grid-item">
            <button id="backButton">←戻る</button>
            <button id="forwardButton">進む→</button>
            <h2>kaiyo-yumeの技術ブログ</h2>
            <!-- ヘッダーの内容をここに記述 -->
        </div>
        <div id="file-tree" class="grid-item">
            <iframe src="/static/output.html"></iframe>
        </div>
        <div id="main" class="grid-item">
            <iframe id="mainframe" src="/static/はじめに.html"></iframe>
        </div>
        <div id="footer" class="grid-item">
            <p>フッター</p>
            <!-- フッターの内容をここに記述 -->
        </div>
    </div>
</body>
</html>
```
index.htmlファイルの説明
- 画面分割を3✖️2にして、それぞれのグリッドアイテムのidを”header”(上)、”footer”（下）、”file-tree”（中央左）、”main"（中央右）にする。
- id="main"にiframeを使い"はじめに.html"を読み込む。"はじめに.html"は初期状で表示する文章である。
- id="file-tree"にiframeを使い”output.file”を読みこむ。
- ”output.file”はファイルツリーで、ファイルツリー内のフォルダーをクリックするとドロップダウンリストの表示/表示の切り替え、ファイル名をクリックするとid="main"にファイルの中身を表示する。

### html-folder :
htmlファイルの記事をフォルダにまとめ、名前を"html-folder"とする。
### filetree.pyとoutput.html :
"html-folder"から"filetree.py"というPythonスクリプトを使い、ファイルツリー("output.html")を作成する。
下の記事で詳しく説明している。

https://qiita.com/kaiyo_yume/items/a4eb2c08f64b5d8c0ba0

ガイドのファイル構造を見ても配置場所がわからなかった技術ブログのパーツは、chatGPT4にコードを見せて教えてもらった。chatGPT3.5だと不正確だった。

## 完成したファイル構造
これは、デプロイして一般公開が完了した後に撮ったスクリーンショットである。.gcloudignoreはローカルでの構築の現段階では関係ないので無視してほしい。

![スクリーンショット 2023-05-22 15.39.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3268288/d5a03e2b-612a-1c0a-722e-2fe22440481c.png)


## app.yaml、main.py、requirements.txtを追加する。
app.yamlとrequirements.txtはガイドのコードをコピーした。main.pyは、自分のサイトに合わせて不要なコードを削った。
### main.py:
```Python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def root():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)
```
自分の技術ブログのファイル構造をどう変更したらいいが分からずchatGPTに頼ったが、他はガイド通りに進めればローカルのテストのクリアまで概ねスムーズに進む。

ローカルでテストして問題がなかったので、GAEにデプロイする。

# GAEにデプロイ
FlaskアプリをGAEにデプロイするためには、本番用のWSGIサーバー、例えばGunicornを使用する必要がある。ここで問題が生じ、技術ブログのデプロイに時間がかかった。

具体的には下記のようなエラーが出ていた。
- gcloud app browseコマンドを実行してwebサイトを開くと、下のサーバーエラーが表示される。
「Error: Server Error
The server encountered an error and could not complete your request.
Please try again in 30 seconds.　　」

- gcloud app logs tail -s defaultコマンドを実行してログを確認するとサーバーエラーが出る。
 「WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.」

## main.pyの書き換え
GAEにデプロイする場合、if __name__ == '__main__':ブロックは不要である。Google App Engineは自動的にアプリケーションをホストし、適切なポートを使用する。app.run()は通常、ローカル環境でアプリケーションをテストするために使用される。
```Python
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def root():
    return render_template('index.html')
"""
ここは消す
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080)
"""
```
## gunicornをインストール
gunicornをインストールする。gunicornはPythonのWSGI HTTPサーバーであり、Flaskアプリケーションを本番環境で実行するためには必要なパッケージである。
```bash
pip3 install gunicorn
```
## requirements.txtの変更
gunicornをプロジェクトにインストールするには、requirements.txtファイルに"gunicorn"を追加する必要がある。
```text
Flask==2.1.0
gunicorn
```
この行を追加すると、GAEはgunicornをプロジェクトの依存関係として認識し、アプリケーションをデプロイする際にインストールする。

## app.yamlの変更
gunicornを起動する際には、モジュール名とFlaskアプリケーションオブジェクトの名前を指定する。
app.yamlファイルのエントリーポイントがmain:appを指すようにする。これはFlaskアプリケーションオブジェクトappがmain.pyファイルに存在することを意味する。

```yaml
runtime: python39
entrypoint: gunicorn -b :$PORT main:app  #変更箇所

handlers:
- url: /static  # 静的ファイルのルート
  static_dir: static
- url: /.*  # それ以外の全てのURL
  script: auto
```
gcloud app deployコマンドを使用してデプロイを行い、その後gcloud app browseコマンドを使用してアプリケーションを表示してみる。

下に完成した技術ブログのURLを載せる。

https://kaiyoyume.de.r.appspot.com/