CloudRun & Docker 入門
Cloud Run & Docker 入門
今まで Web アプリケーション を実装したら全て Heroku にデプロイしていました.ところがHeroku が無料プランを廃止するということなので別のデプロイ先を考えます.Heroku にはお世話になったので恩返ししたい気持ちもありますが,やはり課金対象が増えるのは少し痛い...ということで,いい機会なので今さらながら Cloud Run と Docker に同時入門します.
前提
- Cloud Run の知識ほぼゼロ
- Docker の知識ほぼゼロ
なんとなく知っているという程度なので,記事内容は勉強がてらの備忘録.
開発環境
- macOS Monteray 12.6
- Python 3.10.4
概要
Cloud Run は Google Cloud で提供されているコンテナイメージをビルド・実行するためのサーバレスのプラットフォームです.コンテナ化された Web アプリを実行できます.単に API を公開・実行したいのであれば Cloud Functions という選択肢もありですが,今回はコンテナにも入門したいので Cloud Run を採用します.開発は 公式ドキュメントを参考にすすめていきます.
1. Croud Run の準備
- Google Cloud にプロジェクトを作成する
- Google Cloud の Console にアクセス
- 「プロジェクトの作成」をクリック
- 任意のプロジェクト名をつける
- プロジェクト ID をコピーしておく (あとで使う)
- Google Cloud CLI をインストール
- [gcloud CLI インストール] のページにアクセス
- 自分の環境にあったファイルをダウンロード
- アーカイブされたファイルがダウンロードされるのでそれをホームディレクトリに移動
- ファイルを開く
-
./google-cloud-sdk/install.sh
を実行してインストール - いくつか質問されるので適宜 Yes か No で進めていく
- インストールが完了したら
./google-cloud-sdk/bin/gcloud init
で初期化を開始 - 設定方法を聞かれるのでとりあえずデフォルトの
[1]
を選択 - 利用するアカウントを選択
- プロジェクトをどれにするか聞かれるのではじめに作成したプロジェクトの ID を入力
-
gcloud CLI
のアップデート-
gcloud components update
でアップデート - デフォルトで設定されているプロジェクトを変更するには
gcloud config set project PROJECT_ID
を実行
-
2. Docker の準備
アプリケーションをコンテナ化するにあたっては Docker を利用します.Docker を使うのは完全に初めてなので公式ドキュメントとチュートリアルを参考にキャッチアップしながら進めます.
2.1. Docker の概要
Docker はアプリケーションのコンテナ化や実行を行うためのプラットフォームです.アプリの開発環境や実行環境そのものをコンテナに隔離して独立した環境を構築できます.詳しい解説は全て公式ドキュメントのチュートリアルに丁寧に書いてあります.
2.2. 環境構築
-
Docker のダウンロードとインストール
- 自分の環境にあったバージョンをダウンロード
-
Docker.dmg
を開く -
Docker.app
をアプリケーションフォルダに移動して開く -
Sign in
で Web 版のDocker Hub
が開くのでアカウントを作成する -
Docker.app
(Docker ダッシュボード) に戻ってサインインする (利用が推奨されているのはデスクトップアプリ?) - ターミナルを開き
docker run -d -p 80:80 docker/getting-started
を実行してセットアップ- コマンドの詳しい説明は公式ドキュメント参照
- ダッシュボードを見るとサンプルのコンテナが作成されている
これで自分の開発環境と 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 にアクセスすると...
成功しました.
まとめ
Cloud Run と Docker に入門しました.Docker が使えるなら Cloud Run へのデプロイはとても簡単なので今後も使っていこうと思います.Docker については Dockerfile の書き方や仕組みについてもう少し理解を深める必要がありますが,とりあえずチュートリアルをやってデプロイはできました.
サンプルとしてデプロイしたアプリは過去に作った Flask 製のチャットボットですが,入門するだけなら Cloud Run の公式が出しているサンプルコードを使うほうが楽です.ただ,今回は環境変数が取れるかも試せたので良しとします.
引き続き Cloud Run と Docker を活用していく予定なので,今後もアップデートがあれば追記していきます.
Discussion