🐳

CloudRun & Docker 入門

2022/11/30に公開

Cloud Run & Docker 入門

今まで Web アプリケーション を実装したら全て Heroku にデプロイしていました.ところがHeroku が無料プランを廃止するということなので別のデプロイ先を考えます.Heroku にはお世話になったので恩返ししたい気持ちもありますが,やはり課金対象が増えるのは少し痛い...ということで,いい機会なので今さらながら Cloud Run と Docker に同時入門します.

前提

  • Cloud Run の知識ほぼゼロ
  • Docker の知識ほぼゼロ

なんとなく知っているという程度なので,記事内容は勉強がてらの備忘録.

開発環境

  • macOS Monteray 12.6
  • Python 3.10.4

概要

Cloud RunGoogle Cloud で提供されているコンテナイメージをビルド・実行するためのサーバレスのプラットフォームです.コンテナ化された Web アプリを実行できます.単に API を公開・実行したいのであれば Cloud Functions という選択肢もありですが,今回はコンテナにも入門したいので Cloud Run を採用します.開発は 公式ドキュメントを参考にすすめていきます.

1. Croud Run の準備

  • Google Cloud にプロジェクトを作成する
    1. Google Cloud の Console にアクセス
    2. 「プロジェクトの作成」をクリック
    3. 任意のプロジェクト名をつける
    4. プロジェクト ID をコピーしておく (あとで使う)
  • Google Cloud CLI をインストール
    1. [gcloud CLI インストール] のページにアクセス
    2. 自分の環境にあったファイルをダウンロード
    3. アーカイブされたファイルがダウンロードされるのでそれをホームディレクトリに移動
    4. ファイルを開く
    5. ./google-cloud-sdk/install.sh を実行してインストール
    6. いくつか質問されるので適宜 Yes か No で進めていく
    7. インストールが完了したら ./google-cloud-sdk/bin/gcloud init で初期化を開始
    8. 設定方法を聞かれるのでとりあえずデフォルトの [1] を選択
    9. 利用するアカウントを選択
    10. プロジェクトをどれにするか聞かれるのではじめに作成したプロジェクトの ID を入力
  • gcloud CLI のアップデート
    1. gcloud components update でアップデート
    2. デフォルトで設定されているプロジェクトを変更するには gcloud config set project PROJECT_ID を実行

2. Docker の準備

アプリケーションをコンテナ化するにあたっては Docker を利用します.Docker を使うのは完全に初めてなので公式ドキュメントとチュートリアルを参考にキャッチアップしながら進めます.

2.1. Docker の概要

Docker はアプリケーションのコンテナ化や実行を行うためのプラットフォームです.アプリの開発環境や実行環境そのものをコンテナに隔離して独立した環境を構築できます.詳しい解説は全て公式ドキュメントのチュートリアルに丁寧に書いてあります.

2.2. 環境構築

  • Docker のダウンロードとインストール
    1. 自分の環境にあったバージョンをダウンロード
    2. Docker.dmg を開く
    3. Docker.app をアプリケーションフォルダに移動して開く
    4. Sign in で Web 版の Docker Hub が開くのでアカウントを作成する
    5. Docker.app (Docker ダッシュボード) に戻ってサインインする (利用が推奨されているのはデスクトップアプリ?)
    6. ターミナルを開き docker run -d -p 80:80 docker/getting-started を実行してセットアップ
      • コマンドの詳しい説明は公式ドキュメント参照
    7. ダッシュボードを見るとサンプルのコンテナが作成されている

これで自分の開発環境と Docker アカウントとが紐付いたはず...!

3. Flask アプリの準備

Docker のチュートリアルでは Node.js のサンプルアプリを使っていますが,ここでは過去に Flask で開発した LINE ChatBot をコンテナ化してデプロイします.作業開始時点でのディレクトリ構成は以下のようになっています.

chatbot-backend
    L libs
        L hoge.py
        L huga.py
    L app.py
    L README.md
    L requirements.txt
  • app.py の中身は以下 (コメントは割愛)
import os
from linebot import (LineBotApi, WebhookHandler)
from linebot.exceptions import (InvalidSignatureError)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage,)

app = Flask(__name__)

ACCESS_TOKEN = os.environ["ACCESS_TOKEN"]
CHANNEL_SECRET = os.environ["CHANNEL_SECRET"]
line_bot_api = LineBotApi(ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)

@app.route("/")
    def test(): return "<h1>It Works!</h1>"

@app.route("/callback", methods=['POST'])
    def callback():
        signature = request.headers['X-Line-Signature']
        body = request.get_data(as_text=True)
        app.logger.info("Request body: " + body)
        try:
            handler.handle(body, signature)
        except
            InvalidSignatureError:
                print("Invalid signature. Please check your channel access token/channel secret.")
                abort(400)
                return 'OK'

@handler.add(MessageEvent, message=TextMessage)
    def handle_message(event):
        line_bot_api.reply_message(
            event.reply_token,
            TextSendMessage(text=event.message.text))

if __name__ == "__main__":
    app.run()
  • requirements.txt の中身は以下 (ここは各自の環境に合わせて)
Flask==2.1.2
Jinja2==3.1.2
gunicorn==20.1.0
line-bot-sdk==2.2.1

4. Docker でアプリをコンテナ化

4.1. Docker 用語

Docker を用いた開発において重要なキーワードをざっくりと整理します.

  • Dockerfile : Docker において,イメージを構築するための設定を書き込むファイルで,イメージをビルドするためのコマンドが詰まったファイル
  • イメージ(Image) : OS やアプリケーションなどの環境情報が詰まったテンプレートファイル
  • コンテナ(Container) : イメージをもとに起動される独立したアプリケーション実行環境

4.2. Dockerfile の作成

Docker の Flask チュートリアルを参考に進めますが,自分の環境に合わせて変えていきます.

  • Flask アプリのプロジェクトルートに移動
  • Dockerfile という名前のファイルを作成
  • Dockerfile の中身は以下のようにする (詳細はチュートリアルのページへ)
    • この段階ではローカルで動かす設定になっている
# syntax=docker/dockerfile:1
FROM python:3.10-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
  • docker-compose.yml という名前のファイルを作成
  • docker-compose.yml の中身は以下のようにする
version: '3.9'
services:
  web:
    build: .
    ports:
      - '8000:5000'
  • このファイルでは 1 つの Web サービスが定義されている

最終的なフォルダの構成は以下のようになります

chatbot-backend
    L libs
        L hoge.py
        L huga.py
    L app.py
    L docker-compose.yml
    L Dockerfile
    L README.md
    L requirements.txt

4.3. アプリの構築と実行

ここまでで 準備完了です.次にアプリケーションを構築して実行します.

  • プロジェクトルートで docker-compose up を実行
  • Container chatbot-backend-web-1 Started と表示されれば OK
  • ブラウザからローカル (http://0.0.0.0:8000/) にアクセスして正常な画面がでれば OK

docker-compose のエラー

docker-compose up を実行した際にソースコードに凡ミスがあり,エラーがでました.その際,ただファイルを修正しただけでは反映されなかったので Docker におけるファイル修正後の手順をメモします.「Docker Compose における各種ファイル変更時の反映」の記事を参考にしました.

  • ソースコードを修正したら
    • docker-compose build でイメージを再構築
    • docker-compose up -d でコンテナを再構築
  • docker-compose.yml を修正したら
    • docker-compose up -d でコンテナを再構築
  • Dockerfile を修正したら
    • docker-compose buildでイメージを再構築
    • docker-compose up -d でコンテナを再構築

そのほかの docker-compose コマンド

  • docker-compose stop : 起動中のコンテナを停止する (削除ではない)
  • docker-compose start : 停止中のコンテナを再起動する
  • docker-compose down : コンテナを停止し,up で作成したイメージとコンテナを削除する

ここまでで,プロジェクトの Docker コンテナ化はできました.チュートリアル通りなのでまだ理解不十分なところはありますが,追々勉強することにして先に進みます.

5. Cloud Run へのデプロイ

Cloud Run のクイックスタートのページにある「Python サービスをビルドしてデプロイする」を参考に進めていきます.

5.1. 各設定ファイルの書き換え

Flask のアプリはすでにあるので設定ファイルを修正する部分からすすめていきます.

  • Dockerfile を以下のように修正 (ローカルで動かすこともあると思うので古い設定はコメントアウトしてあります)
FROM python:3.10-slim
ENV PYTHONNUNBUFFERED True
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
RUN pip install --no-cache-dir -r requirements.txt
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
  • .dockerignore ファイルを作成してコンテナに必要のないファイルを指定
Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache

5.2. デプロイ

  • プロジェクトルートで gcloud auth login
  • アカウントを選択してログイン
    • プロジェクトをデフォルト以外に変更する場合は gcloud config set project PROJECT_ID
  • gcloud run deploy でデプロイを実行
  • いろいろ質問されるので全部そのまま return
  • リージョンは [1] asia-east1 を選択
  • 設定は全部 yes ですすめていく
  • しばらくするとデプロイとコンテナのビルドが走っていることがわかる (けっこう待たされる)
  • 完了すると URL が払い出される
  • アクセスして正常に画面が表示されれば完了!

と,思いきや Service Unavailable の表示が...

5.3. トラブルシューティング

ということでトラブルシューティングを行います.

  • Google Cloud のダッシュボードから今回のプロジェクトを選択する
  • Cloud Run はデプロイ時に有効になっているので
  • Cloud Run で該当アプリ名をクリック
  • 「ログ」にアクセス
  • ログの最新(一番下)を見るとエラーメッセージが
    • 503 のエラーで no module name 'main'

503 のエラーで no module name 'main'500 番台のエラーが出ているのでサーバ側のエラー.サンプルのアプリとして過去に作ったチャットボットのコードをそのまま使っている (ここがチュートリアルどおりではない) ので十中八九ソースコードにおかしい部分があるはず...

  • Dockerfile の最終行を修正する
# 修正前
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

# 修正後
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app

チュートリアルでは main.py を作成するよう言われているのですが,自分のアプリだと app.pyという名前なので,まずここが違います.次に app.py のソースコードも修正します.

  • app.py の最終行をチュートリアル通りに環境変数 PORT を利用するような設定に変更
    • この PORT の環境変数は Cloud Run によってコンテナに挿入されるため,自分で設定してはいけないようです
# 修正前
if __name__ == "__main__":
    app.run()

# 修正後
if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))

今回のサンプルプロジェクトは LINE のチャットボットとして開発したものなのでアクセストークンが必要です. app.py では設定した環境変数を取得している部分がありますが,Cloud Run で設定するのを忘れていました.環境変数についての公式ページはこちら

  • Cloud Run のサービスページへ移動
  • 「新しいリビジョンの編集とデプロイ」に移動
  • 「コンテナ」タブを開く
  • ページ下部「環境変数」で「変数を追加」
  • トークンを追加
  • 「デプロイ」

これで環境変数の設定は完了したので,あらためて

  • docker-compose build
  • docker-compose up -d
  • gcloud run deploy

でデプロイ.URL にアクセスすると...

img1

成功しました.

まとめ

Cloud Run と Docker に入門しました.Docker が使えるなら Cloud Run へのデプロイはとても簡単なので今後も使っていこうと思います.Docker については Dockerfile の書き方や仕組みについてもう少し理解を深める必要がありますが,とりあえずチュートリアルをやってデプロイはできました.

サンプルとしてデプロイしたアプリは過去に作った Flask 製のチャットボットですが,入門するだけなら Cloud Run の公式が出しているサンプルコードを使うほうが楽です.ただ,今回は環境変数が取れるかも試せたので良しとします.

引き続き Cloud Run と Docker を活用していく予定なので,今後もアップデートがあれば追記していきます.


Discussion