コードを一切変更せずにアプリをクラウドストレージ対応させる方法
先日、所有するサーバーの1台にメディア共有アプリをインストールしました。インストール自体は簡単でしたが、長期的な運用を考えると厄介な落とし穴が隠れていました。
ユーザーがファイルをアップロードするたび、Webアプリはそれをローカルのファイルシステムに保存していました。このままでは、サーバーを作り直すことになれば、すべてのファイルを手作業でバックアップし、復元しなければなりません。本来であれば、アプリがファイルを別のストレージサーバーに書き込む構成にすべきですが、そのために何ヶ月もかけてアプリを書き直す気にはなれませんでした。
DockerとGoogle Cloud Storage、そしてgcsfuseユーティリティを使うことで、アプリのコードを1行も変更することなく、この分離を実現できました。
このチュートリアルでは、ソースコードへの変更を最小限に抑えながら、レガシーなアプリをGoogle Cloud Storageに対応させる方法を紹介します。
用語の定義
Google Cloud Platformに馴染みがない方のために、このチュートリアルで登場する略語をまとめておきます。
| 略語 | 正式名称 | 内容 | 他社サービスでの相当例 |
|---|---|---|---|
| GCS | Google Cloud Storage | Googleのクラウドストレージサービス | Amazon S3 |
| GCE | Google Compute Engine | Googleのオンデマンド仮想マシンサービス | Amazon EC2 |
| GCR | Google Container Registry | GoogleのDockerイメージ用ホスティングサービス | Docker Hub |
| GCP | Google Cloud Platform | Googleのクラウドコンピューティングプラットフォーム(GCS、GCE、GCRはいずれもGCPの一部です) | Amazon Web ServicesやMicrosoft Azure |
Dockerを使うと、どこでも動作する自己完結型のアプリケーション環境を構築できます。
- Dockerイメージは、OSやサードパーティの依存関係を含め、アプリの実行に必要なすべてのファイル一式です。
- Dockerコンテナは、Dockerイメージが実行される実際の環境です。
厳密には違いますが、このチュートリアルではDockerコンテナを軽量な仮想マシンだと考えていただいて構いません(厳密にはそうではないのですが)。
前提条件
本記事の手順を実際にお試しになる場合は、以下のものが必要です。
- Google Cloud SDK
- Docker(無料のCommunity Editionで問題ありません)
サンプルアプリの紹介
このチュートリアルのためにサンプルプロジェクトを用意しました。Flaskフレームワークのアップロードに関するドキュメントをベースにした、ごくシンプルなWebアプリです。
実行するには、次のコマンドを入力します。
git clone https://github.com/mtlynch/flask_upload_demo.git
cd flask_upload_demo
sudo pip install -r requirements.txt
gunicorn \
demo.app:app \
--bind 0.0.0.0:5000このアプリでは、ファイルを選択してアップロードできます。

flask-upload-demoのアップロード画面
アップロードされたファイルは、http://[サーバーアドレス]:5000/uploads/[ファイル名]というURLで恒久的に配信されます。

アップロードされた画像を配信するflask-upload-demo
サーバー内部を見ると、アプリがファイルをローカルファイルシステム上のdemo/uploadsフォルダに保存していることがわかります。
$ ls -l demo/uploads/
-rw-rw-r-- 1 mike mike 230720 Nov 24 21:45 Space_Duck_Desktop_RGB_PNG.pngサンプルアプリのDocker化
このアプリは依存関係が少なくシンプルなので、Dockerイメージの作成も簡単です。
Dockerfile
FROM debian:stretch
RUN apt-get update
RUN apt-get install --yes \
git-core \
python \
python-pip \
python-virtualenv
# Create demo user system account.
ARG APP_USER="demo-user"
ARG APP_GROUP="demo-user"
ARG APP_HOME_DIR="/home/demo-user"
RUN set -x && \
groupadd "$APP_GROUP" && \
useradd \
--comment "Demo app system account" \
--home-dir "$APP_HOME_DIR" \
--create-home \
--system \
--gid "$APP_GROUP" \
"$APP_USER"
# Create directory for app source code.
ARG APP_ROOT="/srv/demo-app"
RUN mkdir --parents "$APP_ROOT" && \
chown \
--no-dereference \
--recursive \
"${APP_USER}:${APP_GROUP}" "$APP_ROOT"
USER "$APP_USER"
WORKDIR "$APP_ROOT"
# Install demo app.
ARG DEMO_APP_REPO="https://github.com/mtlynch/flask_upload_demo"
RUN set -x && \
git clone "$DEMO_APP_REPO" . && \
virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
pip install --requirement requirements.txt
EXPOSE 5000
# Run demo app.
ENV FLASK_APP "demo/app.py"
CMD virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
gunicorn \
demo.app:app \
--bind 0.0.0.0:5000 \
--log-level info上記のDockerfileでは、イメージを準備するために大きく次の処理を行っています。
- Git、Pythonおよび関連パッケージをインストールする
- 権限を絞ってアプリを実行するためのシステムアカウント(
demo-user)を作成する - アプリのソースリポジトリをローカルにクローンする
- ポート5000でデモアプリを起動する
CMDを追加する
このDockerfileは、私のリポジトリをクローンしてローカルでDockerコンテナをビルドすることで試せます。
cd ~
git clone https://github.com/mtlynch/docker-flask-upload-demo.git
cd docker-flask-upload-demo
docker build \
--tag demo-app-image \
.
docker run \
--detach \
--publish 80:5000 \
--name demo-app \
demo-app-imageブラウザでhttp://localhost/にアクセスすると、デモアプリが表示されます。
より現実的なDockerイメージ
多くのWebアプリは、ブラウザからのトラフィックを直接受け付けることはありません。代わりにNginxやApacheのようなHTTPサーバーを使い、負荷分散や静的ファイルの配信といった汎用的な処理をそちらに任せ、バックエンドではアプリ固有のロジックを処理します。
より実践的なアプリで使われる構成に近いDockerイメージを示すため、Dockerリポジトリにnginxブランチを追加しました。主な変更点は以下のとおりです。
# Install nginx.
ARG NGINX_GROUP="www-data"
COPY nginx.conf /etc/nginx/sites-enabled/nginx.conf
RUN set -x && \
apt-get install --yes \
nginx \
sudo && \
rm /etc/nginx/sites-enabled/default && \
usermod --append --groups "$NGINX_GROUP" "$APP_USER" && \
echo "$APP_USER ALL=(ALL:ALL) NOPASSWD: /usr/sbin/nginx" >> /etc/sudoersNginxは通常、特権ポートである80番や443番で待ち受けるためにrootとして動作します。そのためDockerfileでは、sudoを使ってデモアプリ用ユーザーがNginxだけをrootとして起動できるようにしつつ、それ以外の処理は権限を絞ったまま実行できるようにしています。
最後に、Nginxの設定ファイルをコンテナにコピーします。
nginx.conf
server {
listen 80;
server_name example.org; # Replace with your server's domain name
client_max_body_size 20m;
# Serve static resources directly (bypass backend).
location /uploads/ {
alias /srv/demo-app/demo/uploads/;
}
# Forward all other requests to the application backend.
location / {
proxy_pass http://127.0.0.1:5000;
}
}location /uploads { ... }ブロックにより、Nginxはリクエストをflask-upload-demoのバックエンドに転送するのではなく、/uploadsフォルダからファイルを直接配信できるようになります。Nginxの方がアプリのバックエンドよりも静的ファイルを高速かつ効率的に扱えるため、よく使われる構成です。
Nginx版のflask-upload-demoは、以下のコマンドで実行できます。
cd ~/docker-flask-upload-demo
git checkout nginx
docker build \
--tag demo-app-image \
.
docker run \
--detach \
--publish 80:80 \
--name demo-app \
demo-app-imageアプリは再びhttp://localhost/で表示されます。動作は前述のバージョンと同じですが、ファイルの配信に必要なリソースが少なくなっています。
GCPプロジェクトの準備
アプリをローカルで動かすのも良いですが、すべてのユーザーがアクセスできるクラウドに公開する方がわくわくします。GCPへデプロイするために、いくつか準備が必要です。
まず、gcloudでGCPプロジェクト名を指定します。
PROJECT_ID="ENTER-YOUR-PROJECT-ID-HERE"
gcloud config set project "$PROJECT_ID"次に、GCPのウェブコンソールでオーナー権限を持つサービスアカウントを作成します。
要注意:GCPの不具合と思われる挙動により、gcr.ioへのDockerイメージプッシュ(次のセクションを参照)が、ルートのGCPアカウント(@gmail.comアカウントなど)やgcloud経由で作成したサービスアカウントを使うと失敗します。

GCPウェブコンソールで新しいサービスアカウントを作成する

サービスアカウントにロールを割り当てる
秘密鍵をkey.jsonとしてダウンロードします。

サービスアカウントの秘密鍵をダウンロードする
最後に、作成したサービスアカウントとしてgcloudで認証します。
gcloud auth activate-service-account --key-file key.json認証が完了したら、チュートリアルの続きに進むために必要な準備コマンドをいくつか実行します。
# Enable APIs you'll need for this workflow.
gcloud services enable \
cloudresourcemanager.googleapis.com \
compute.googleapis.com \
containerregistry.googleapis.com \
iam.googleapis.com
# Enable gcloud to provide credentials for Docker image pushes.
gcloud auth configure-docker --quietGoogle Container Registryへのイメージのアップロード
DockerイメージをGCEにデプロイする前に、Dockerイメージホスティングサービスに公開する必要があります。GCRはGoogle Cloudに統合されたDockerイメージホスティングサービスなので、最も手軽な選択肢です。
DockerイメージをGCRにアップロードするには、サンプルリポジトリのnginxブランチをチェックアウトします。
cd ~/docker-flask-upload-demo
git checkout nginx次に、Dockerイメージをローカルでビルドし、GCRにプッシュします。
LOCAL_IMAGE_NAME="flask-upload-demo-image"
docker build --tag "$LOCAL_IMAGE_NAME" .
GCR_HOSTNAME="gcr.io"
GCR_IMAGE_PATH="${GCR_HOSTNAME}/${PROJECT_ID}/flask-demo-app"
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"Dockerコンテナのデプロイ
コンテナのデプロイは少し回りくどい手順が必要です。GCPではDockerイメージを直接デプロイできません。代わりにGCEでフル機能の仮想マシン(VM)を立ち上げ、そのVM上でDockerを実行し、さらにその中でDockerイメージをコンテナとして動かす形になります。幸い、GCPのツールを使えばこのプロセスは簡単に行えます。
GCEのVMはデフォルトで受信HTTPトラフィックを拒否します。許可するには、ポート80(平文HTTPの標準ポート)へのTCP接続を許可するファイアウォールルールを作成します。
簡単な方法は、http-serverタグが付いたすべてのVMにルールを適用することです。
VM_TAGS="http-server"
gcloud compute \
--project="$PROJECT_ID" \
firewall-rules create default-allow-http \
--direction=INGRESS \
--action=ALLOW \
--rules=tcp:80 \
--target-tags="$VM_TAGS"次に、http-serverタグを付けてGCE VMをデプロイします。
VM_NAME="flask-demo-app-vm"
MACHINE_TYPE="n1-standard-1"
ZONE=us-east1-b
gcloud compute \
--project="$PROJECT_ID" \
instances create-with-container "$VM_NAME" \
--zone="$ZONE" \
--machine-type="$MACHINE_TYPE" \
--network-tier=STANDARD \
--metadata=google-logging-enabled=true \
--maintenance-policy=MIGRATE \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--tags="$VM_TAGS" \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=10GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name="$VM_NAME" \
--container-image="$GCR_IMAGE_PATH" \
--container-restart-policy=on-failure注目すべきフラグは次のとおりです。
--tags="$VM_TAGS" \--tagsフラグは、ファイアウォールルール用に作成したタグを付けてVMを起動します。このフラグにより、ポート80でHTTPトラフィックを受信できるようになります。
--image-family=cos-stable \
--image-project=cos-cloud \これらのフラグは、GCEに対してContainer-Optimized OS上でコンテナを実行するよう指示します。これはDockerコンテナ実行用にGoogleが作った最小構成のLinux OSです。--image-family=cos-stableは、Container-Optimized OSの最新の安定版を使うようgcloudに指示します。
--container-image="$GCR_IMAGE_PATH" \上記のフラグは、先ほど作成したGCRのURLを使って、GCE VM内で実行するDockerイメージを指定します。
コマンドが完了すると、次のような出力が表示されます。
Created [https://www.googleapis.com/compute/v1/projects/flask-upload-demo-2018-11-26/zones/us-east1-b/instances/flask-demo-app-vm].
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
flask-demo-app-vm us-east1-b n1-standard-1 10.142.0.2 35.211.106.214 RUNNINGブラウザでEXTERNAL_IPのアドレスを入力すると、ローカル版とまったく同じようにアプリが動作します。

GCE上で動作するflask-upload-demo

GCEから画像ファイルを配信するflask-upload-demo
問題は、そのVMを停止して同じDockerイメージで新しいVMを起動すると、アップロードしたファイルが消えてしまうことです。

新しいGCE VMでは、ファイルが以前のVMに保存されていたため配信できません
もちろん、これこそが本チュートリアルのきっかけとなった問題です。コンテナはファイルを内部のファイルシステムに保存しているため、ホストVMを終了させるとすべてのファイルが失われてしまいます。
これを解決するには、Dockerコンテナが永続データをすべてGoogle Cloud Storage(GCS)バケットに保存するように設定する必要があります。
GCS対応アーキテクチャの設計
この問題を解決する目標のアーキテクチャは次のとおりです。

FlaskアプリをGoogle Cloud Platformにデプロイするためのアーキテクチャ
- Webブラウザが通信するのはWebサーバーであるNginxのみで、Nginxがフロントエンドからのすべてのリクエストを統括します。
- Webブラウザがファイルをリクエストすると、Nginxはgcsfuseというユーティリティを介してGCSからファイルを取得します。gcsfuseはGCSバケットをファイルシステム上のフォルダとしてマウントします。
- それ以外のリクエストについては、Nginxがflask-upload-demoアプリに転送します。
- flask-upload-demoも同じくgcsfuseユーティリティ経由でGCSに新しいファイルを書き込むことができます。
このアーキテクチャは、記事の冒頭で示した目標を満たしています。永続的な状態はすべてGCSに保存されるため、VM内のものはすべて使い捨て可能です。アプリのコードはすべてDockerコンテナに収まっているため、デプロイもシンプルかつアトミックに行えます。
GCSバケットの作成(任意)
まだGCSバケットをお持ちでない場合は、次のgcloudコマンドで作成できます。
STORAGE_LOCATION="us-east1"
GCS_BUCKET="${PROJECT_ID}-storage"
gsutil mb \
-p "$PROJECT_ID" \
-l "$STORAGE_LOCATION" \
"gs://${GCS_BUCKET}"すでにバケットをお持ちの場合は、環境変数GCS_BUCKETにそのバケット名を設定してください。
DockerコンテナにGCSへのアクセス権を付与する
Dockerイメージを修正してgcsfuseユーティリティを組み込む必要があります。幸い、その作業はすでに済ませてあります。完全なDockerfileはGitHubリポジトリのgcsfuseブランチで公開していますが、主な変更点は次のとおりです。
# Install gcsfuse.
ARG GCSFUSE_REPO="gcsfuse-stretch"
ARG GCS_MOUNT_ROOT="/mnt/gcsfuse"
RUN set -x && \
apt-get install --yes --no-install-recommends \
ca-certificates \
curl && \
echo "deb http://packages.cloud.google.com/apt $GCSFUSE_REPO main" \
| tee /etc/apt/sources.list.d/gcsfuse.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| apt-key add -
RUN apt-get update
RUN set -x && \
apt-get install --yes gcsfuse && \
echo 'user_allow_other' > /etc/fuse.conf && \
mkdir --parents "$GCS_MOUNT_ROOT" && \
chown \
--no-dereference \
"${APP_USER}:${NGINX_GROUP}" "$GCS_MOUNT_ROOT"ここで注目すべき点が2つあります。
echo 'user_allow_other' > /etc/fuse.conf上記の行により、gcsfuseの-o allow_otherオプションが使えるようになります。これはアプリ用システムアカウントとNginx用システムアカウントの両方がGCSフォルダにアクセスする必要があるためです。設定ファイルにuser_allow_otherの行がないと、単一のアカウントしかGCSフォルダにアクセスできません。
要注意:複数のシステムアカウントがGCSバケットにアクセスする場合は、/etc/fuse.confファイルにuser_allow_otherという行が必要です。
mkdir --parents "$GCS_MOUNT_ROOT" && \
chown \
--no-dereference \
"${APP_USER}:${NGINX_GROUP}" "$GCS_MOUNT_ROOT"gcsfuseは、起動ユーザーが書き込み権限を持つ既存のディレクトリを必要とします。通常のユーザーには/mntディレクトリへの書き込み権限がないため、Dockerfileではrootユーザーとして/mnt/gcsfuseディレクトリを作成し、chownで所有権をNginxとデモアプリのシステムアカウントに割り当てています。
もう一つの興味深い変更点は、コンテナの実行時の動作を定義するDockerfileのCMD部分にあります。
ENV GCS_BUCKET "REPLACE-WITH-YOUR-GCS-BUCKET-NAME"
ENV GCS_MOUNT_ROOT "$GCS_MOUNT_ROOT"
ENV APP_UPLOADS_DIR "${APP_ROOT}/demo/uploads"
CMD set -x && \
sudo nginx && \
gcsfuse \
-o nonempty \
-o allow_other \
--implicit-dirs \
"$GCS_BUCKET" "$GCS_MOUNT_ROOT" && \
if [ ! -d "$APP_UPLOADS_DIR" ]; then \
ln --symbolic "$GCS_MOUNT_ROOT" "$APP_UPLOADS_DIR"; \
fi && \
virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
gunicorn \
demo.app:app \
--bind 127.0.0.1:5000 \
--log-level infoここでも、注目すべきスニペットごとに分解して見ていきます。
gcsfuse \
-o nonempty \
-o allow_other \
--implicit-dirs \
"$GCS_BUCKET" "$GCS_MOUNT_ROOT"前述のuser_allow_otherオプションと同様、-o allow_otherにより、複数のユーザーがマウントされたフォルダにアクセスできるようになります。nginx(www-dataユーザーとして動作)とデモアプリ(demo-userとして動作)の両方がアクセスする必要があるためです。
要注意:複数のユーザーアカウントがGCSマウント内のファイルにアクセスする場合は、gcsfuseに-o allow_otherフラグが必要です。
--implicit-dirsフラグがないと、gcsfuseはGCSバケットのサブフォルダ内のファイルにアクセスできません。
要注意:GCSバケットにサブフォルダが含まれる場合は、gcsfuseに--implicit-dirsフラグが必要です。
if [ ! -d "$APP_UPLOADS_DIR" ]; then \
ln --symbolic "$GCS_MOUNT_ROOT" "$APP_UPLOADS_DIR"; \
fiアプリはアップロードされたファイルをdemo/uploadsディレクトリに書き込みます。上記のブロックは、demo/uploadsから/mnt/gcsfuseへのシンボリックリンクを作成します。これにより、アプリがdemo/uploadsパスに書き込んでいるつもりでも、実際にはGCSバケットに書き込まれることになります。if/thenブロックは、コンテナ再起動時などにこの処理が二重に実行されるのを防ぎます。
GCSアクセス権を持つサービスアカウントの作成
このイメージをGCEにデプロイする前にもう一手間必要です。デフォルトでは、GCEインスタンスは標準のGCEサービスアカウントの権限で動作します。このアカウントはGCSへのアクセスが読み取り専用のため、アプリはGCSに新しいファイルを書き込めず失敗します。
これを解決するには、次の2つのロールを持つカスタムサービスアカウントを作成します。
storage.objectAdmin:VM内のプロセスがGCSオブジェクトの読み書きを行えるようにします。logging.logWriter:アプリのログ出力をGCPのロギングインターフェースに表示できるようにします。
要注意:カスタムサービスアカウントで起動しない限り、GCEインスタンスはGCSバケットに書き込むことができません。
次のコマンドで、必要な権限を持つサービスアカウントを作成します。
SERVICE_ACCOUNT_NAME=flask-demo-app-service-account
SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create "$SERVICE_ACCOUNT_NAME"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role roles/storage.objectAdmin
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role roles/logging.logWriterGCS対応コンテナのデプロイ
クローンしたdocker-flask-upload-demoリポジトリに戻り、gcsfuseブランチをチェックアウトします。
cd ~/docker-flask-upload-demo
git checkout gcsfuse次に、Dockerイメージを再ビルドしてGCRにプッシュします。
LOCAL_IMAGE_NAME="flask-upload-demo-image"
docker build --tag "$LOCAL_IMAGE_NAME" .
GCR_HOSTNAME="gcr.io"
GCR_IMAGE_PATH="${GCR_HOSTNAME}/${PROJECT_ID}/flask-demo-app"
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"注記:このイメージをローカルで実行しようとすると失敗します。gcsfuse版のDockerfileは、実行環境がすでにgcloudで認証済みであることを前提としています(GCE上で動作するコンテナではこれが当てはまります)。GCEの外部で実行しながらGCSバケットをマウントできるようDockerfileを調整することも可能ですが、ここでは読者の演習課題とします。
新しいカスタムGCEサービスアカウントが用意できたので、GCS対応コンテナをデプロイする準備が整いました。
VM_NAME="flask-demo-app-vm-gcsfuse"
MACHINE_TYPE="n1-standard-1"
ZONE=us-east1-b
gcloud compute \
--project="$PROJECT_ID" \
instances create-with-container "$VM_NAME" \
--zone="$ZONE" \
--machine-type="$MACHINE_TYPE" \
--network-tier=STANDARD \
--metadata=google-logging-enabled=true \
--maintenance-policy=MIGRATE \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--service-account="$SERVICE_ACCOUNT_EMAIL" \
--tags="$VM_TAGS" \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=10GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name="$VM_NAME" \
--container-image="$GCR_IMAGE_PATH" \
--container-restart-policy=on-failure \
--container-privileged \
--container-env="GCS_BUCKET=$GCS_BUCKET"このコマンドは前回のデプロイコマンドと同じですが、2つのフラグが追加されています。
--container-privileged \--container-privilegedフラグは、DockerコンテナがFUSEファイルシステムをマウントできるようにするために必要です(Dockerにはより細かく制御する方法もありますが、GCEではまだサポートされていません)。
要注意:--container-privilegedフラグを付けてVMをデプロイしない限り、gcsfuseはGCE上でGCSバケットをマウントできません。
--container-env="GCS_BUCKET=$GCS_BUCKET"Dockerイメージは、実行時までGCSバケット名に依存しないように意図的に設計しています。--container-envフラグを使うと、デプロイ時に環境変数GCS_BUCKETを指定できます。
永続化の努力は、永続性という形で報われる
これで、状態をGCSに永続化するDockerコンテナが完成しました。デプロイしたアプリにファイルをアップロードして試してみましょう。

GCS上の永続ストレージからファイルを配信するflask-upload-demo
GCSバケットを確認すると、アップロードしたばかりのファイルが保存されています。

GCSバケット内の画像ファイル
本当の試金石は、この状態が異なるVM間でも保持されるかどうかです。VMを完全に削除して再デプロイすることで確認できます。以前のVMでの画像URLが、新しいサーバーでもそのままアクセス可能です。

VMを破棄して再構築した後も、flask-upload-demoはファイルの配信を継続します
おまけ:ロギングインターフェース
このソリューションの嬉しい副次的効果として、GCPがアプリのログを確認できる洗練されたウェブインターフェースを提供してくれることが挙げられます。
いつでもVMにSSHで接続し、docker logsを実行して手動でログを確認できます。
$ docker logs klt-flask-demo-app-vm-gcsfuse-qnnf
...
[2018-11-26 21:28:54 +0000] [33] [INFO] Starting gunicorn 19.9.0
[2018-11-26 21:28:54 +0000] [33] [INFO] Listening at: http://127.0.0.1:5000 (33)
[2018-11-26 21:28:54 +0000] [33] [INFO] Using worker: sync
[2018-11-26 21:28:54 +0000] [37] [INFO] Booting worker with pid: 37
[2018-11-26 21:32:59 +0000] [37] [INFO] Saving uploaded file "zestful-logo.png" to "/srv/demo-app/demo/uploads/zestful-logo.png"より簡単な方法は、GCPのStackDriverロギングインターフェースを開くことです。そこでは、機能豊富なウェブインターフェースですべてのログを確認できます。

GCPのStackDriverインターフェースに表示されたアプリのログ出力。
新しいリリースのプッシュ
このアーキテクチャにより、新しいリリースのプッシュが簡単になります。アプリやその依存関係を更新したいときは、新しいイメージをビルドしてGCRにプッシュするだけです。
docker build --tag "$LOCAL_IMAGE_NAME" .
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"次に、update-containerコマンドを使って、実行中のGCEインスタンス上のDockerイメージを更新します。
gcloud compute \
--project="$PROJECT_ID" \
instances update-container "$VM_NAME" \
--container-image="$GCR_IMAGE_PATH"要注意:静的IPを割り当てていない場合、このコマンドの実行後にVMの外部IPアドレスが変わります。
もし不良なリリースをプッシュしてしまっても、update-containerコマンドを使えば以前の正常なイメージにロールバックできます。
制限事項
このソリューションは、gcsfuseユーティリティやGCS自体と同じ制限を抱えています。gcsfuseはGCSバケットを通常のファイルシステムのフォルダのように見せようと懸命に努力しますが、抽象化は主に2つの点で破綻します。
- GCSはロックをサポートしていないため、アプリがファイルロックを取得しようとすると不具合が生じます。
- レイテンシが高く、特に大きなファイルに対して小さなランダムな読み書きを行う場合に顕著です。
特に、gcsfuseマウント上のデータベースをsqliteに指定すると、すぐに失敗することを確認しています。
まとめ
このチュートリアルでは、アプリ自体に変更を加えることなく、アプリのデータをクラウドストレージにリダイレクトする方法を学びました。サンプルアプリはGoogle Cloud Platformのことをまったく知りませんでしたが、Google Compute Engineにデプロイし、その永続データをGoogle Cloud Storageにリダイレクトすることができました。
ソースコード
- flask-upload-demo:ローカルファイルシステムに状態を保持するFlaskのサンプルアプリです。
- docker-flask-upload-demo:flask-upload-demo用のDocker構成です。3つのバリエーションがあります。
- masterブランチ - アプリの基本的なパッケージングを示します
- nginxブランチ - nginxがアプリのトラフィックをプロキシする、より現実的な構成を示します
- gcsfuseブランチ - Dockerコンテナ内からGoogle Cloud Storageバケットをマウントする方法を示します(コンテナがGoogle Cloud Storageへの読み書き権限を持つGoogle Compute Engine VM上で動作することを前提としています)。
- mediagoblin-docker(gcsfuseブランチ) - 実際のメディア共有アプリ用のDocker構成で、同じ手法を使ってアプリの永続データをGoogle Cloud Storageにリダイレクトした例です。
ソフトウェアアーキテクチャ図:Loraine Yow 作
記事をランダムに読む

