Google App EngineからGoogle Cloud Storageを利用する

Google App Engine(Pythonスタンダード環境)からAPIでGoogle Cloud Storageにファイルを保存・取り出しをします。

Cloud Storageは写真や動画などリッチメディア用です。文字列などの通常のデータベースはCloud Datastoreを利用しましょう。
Google Cloud DatastoreをGoogle App Engineから利用する - simon's note
Google Cloud Datastoreを利用したサーバーをFlaskでつくる [GAE] - simon's note

このページの内容はこちらの公式リファレンスを噛み砕いたものです。
Reading and Writing to Google Cloud Storage  |  App Engine standard environment for Python  |  Google Cloud

上のリファレンスはwebapp2前提でわかりにくいです。GitHubに上がっているサンプルコードをみて理解しました。

Google Cloud Storageとは

Google Cloud Platformのストレージサービスです。Google Driveみたいに物置としても使えるし、ブログの写真やスクリプトファイル置き場としても使えます。もちろんAPIを利用してWebアプリからアクセスできます(それが主用途ですよね)。

App Enigineから利用可能なのはBlobstoreというのもありますが、現在はCloud Storageの方が推奨されているようです。今から使うならCloud Storageにしましょう。

最初の5GBのみ無料で利用できます。

バケット

Cloud Storageはバケットごとに管理できます。バケットとはフォルダの上位のようなもので、基本的に用途ごとにバケットを使い分けます。(例:ブログA用のバケット、App Engine用のバケット、バックアップ用のバケットなど)

バケットごとにストレージクラスを選択できます。ストレージクラスの違いはおおまかにいえば想定しているアクセス頻度です。
ストレージ クラス  |  Cloud Storage ドキュメント  |  Google Cloud

2018/6/13現在4種類のストレージクラスがありますが、通常の利用であればMulti-RegionalRegionalになります。この2つの違いは想定するアクセス元の地域です。Multi-Regionalは世界中からのアクセスを想定しています。

NearlineColdlineはバックアップ用です。

前提知識

Google App EngineでHello Worldくらいはできるものとします。できない場合はまずこちらを読んできてください。
GAEではじめてのFlaskアプリをつくる[Progate修了レベル] - simon's note

Cloud Storageを有効化する

バケットの作成

まずはApp Engine用のバケットを作成しましょう。

GCPコンソール→App Engine→Settingsのページで、'Default Cloud Storage Bucket'の下にある'Create'をクリックしましょう。

'Create'のかわりにApp EngineアプリのURLが表示されていれば既に有効化されています。それをクリックしてください。

これでApp Engine用のCloud Storageバケットが作成されました。App Engineからのファイルの読み書きはそのバケットに行うことになります。

デフォルトではアプリからそのバケットへの読み書きは100%許可されています。変更する場合はGCPコンソールのIAMから行います。
Google Cloud Platform Console  |  Cloud Storage ドキュメント  |  Google Cloud

ライブラリをダウンロードする

Cloud Datastoreと違って、手動でライブラリをダウンロードする必要があります。

方法はpipgit、もしくはGitHubからのダウンロード(クリックするとzipファイルがダウンロードされます)のいずれかです。
Setting Up Google Cloud Storage  |  App Engine standard environment for Python  |  Google Cloud

まあ、App Engineアプリのフォルダのlibフォルダ内に置くだけなので。

pipの場合はまずはApp Engineアプリのフォルダに移動

cd AppEngineアプリのフォルダのパス

lib/にライブラリをインストール

pip install GoogleAppEngineCloudStorageClient --target lib

Pythonファイルでのコード

ここからはPythonファイルでのコーディングのみとなります。

準備

まずはCloud Storageを利用できるように準備をします。

import

先ほどダウンロードしたcloudstorageをimportします。

import cloudstorage
バケット名の自動取得

Cloud Storageへのアクセスは常に/バケット名/ファイル名のようにパスを渡す必要があります。

このバケット名は普通にGCPコンソール→Storage→Browserで見られるので、平文での指定でも全く問題ありません。

上の「Cloud Storageを有効化する」のくだりで有効化したApp Engineデフォルトのバケットを指定する場合は、自動でバケット名を取得できます。...とはいえ平文で指定した方が早い気がするし、使いどころはよくわからないですが...

この場合はosapp_identity (from google.appengine.api)をimportしておきます。

import os
from google.appengine.api import app_identity
bucket_name = os.environ.get('BUCKET_NAME', app_identity.get_default_gcs_bucket_name())
RetryParamsの設定

RetryParamsとはCloud Storageとのセッションタイムアウトとリトライ(再接続)などの設定です。

以下のように設定します。引数以外はコピペでOK。

retryparams_instance = cloudstorage.RetryParams(引数)
cloudstorage.set_default_retry_params(retryparams_instance)

引数は以下の通り。

initial_delay (default=0.1)
リトライの前に待つ秒数。 Cloud Storageのサーバーが準備できるまで待ってあげると読み込み成功率が上がります。
backoff_factor (default=2.0)
backoffレート。接続に失敗した際に定期的にリトライする間隔を設定します。詳しくはbackoffについて
max_delay (default=10.0)
リトライまでの最大秒数。
min_retries (default=3)
リトライの最小回数。
max_retries (default=6)
リトライの最大回数。リトライをしたくない場合はこれを0にします。
max_retry_period (default=10.0)
リトライまでの最大秒数。
max_delay (default=30.0)
一度のリクエストに対する全てのリトライに使う最大時間。min_retriesを満たした状態でこの秒数が経つとリトライをやめます。
urlfetch_timeout (default=None(=5秒))
UrlFetchでCloud Storageにアクセスするときに待つ最大秒数。これを過ぎるとタイムアウトエラーを返します。デフォルトはNoneですが、この場合は5秒(default UrlFetch deadline)。最大で60秒まで設定可。

どんな具合に設定したらいいかイメージが湧かないと思いますが、サンプルコードではこうなってます。

retryparams_instance = cloudstorage.RetryParams(initial_delay=0.2, max_delay=5.0, backoff_factor=2, max_retry_period=15)
cloudstorage.set_default_retry_params(retryparams_instance)

見ての通り、ここで設定したのはデフォルト値です。Cloud Storageへのアクセスの際にまた設定できます。

...つまり場面によって数値を調整してチューニングしてくれということですね。よくわかんないですがいろいろ試してるうちにコツがつかめてくるのかも?

RetryParamsクラスについて詳しくはこちらのリファレンスをどうぞ。
The RetryParams Class  |  App Engine standard environment for Python  |  Google Cloud

ファイルの読み・書き

Cloud Storageへのアクセスの手順は以下の3段階になります。

  1. cloudstorage.open()でインスタンスを作成
  2. 作成したインスタンスに.write()で書き込み・.read()で読み込み
  3. .close()で完了(ここで変更を保存)

1.インスタンスの作成

まずはcloudstorage.open()でインスタンスを作成します。ここでほとんどの操作をしてしまうし、読み込みか書き込みかもここで決めてしまいます。
引数は以下の通りです。

filename (必須)
/バケット名/beatles/revolver/taxman.mp3のようにバケット名に続けてディレクトリを含むファイルパスを指定します。ここでディレクトリ階層もファイル名も拡張子もすべて指定してしまうんですね。ちなみにこの場合実際のファイルURLはhttps://storage.googleapis.com/バケット名/beatles/revolver/taxman.mp3になります。
mode (default='r')
読み('r')か書き('w')で指定。新規ファイルの作成と既存のファイルの書き換えはどちらも'w'
content_type (default='binary/octet-stream')
mode = 'w'の時のみ有効。ファイルのMIMEタイプを指定。
options :辞書型
mode = 'w'の時のみ有効。ファイルのメタデータを指定。x-goog-acl, x-goog-meta-, cache-control, content-disposition, content-encodingを指定可。

x-goog-aclACLを設定します。これはIAMを補完する追加のアクセス制限です。指定しない場合は誰でもアクセス可能となります。

x-goog-meta-には'x-goog-meta-birthday':'1972-07-04'のように好きなメタパラメータを設定できます。

以下のように辞書型で指定します。
例:options={'x-goog-acl':'private', 'x-goog-meta-john':'lennon', 'x-goog-meta-paul':'mccartney'}
read_buffer_size (default=recommended) :integer型
mode = 'r'の時のみ有効。バッファサイズを指定。指定しないのが推奨されているので、よくわからなければ無視でOK。
retry_params (default=None)
指定しない場合は最初に設定したデフォルトのRetryParamsが適用されます。この処理の間だけ変更点がある場合は新たにRetryParamsインスタンスを作成してここに引数として渡します。
例:retry_params=cloudstorage.RetryParams(backoff_factor=1.1)
ということで、インスタンスの作成はこんな感じになります。

retryparams_w = cloudstorage.RetryParams(backoff_factor=1.1)
cloudstorage_file = cloudstorage.open(filename='/バケット名/beatles/revolver/taxman.mp3', mode='w', content_type='audio/mpeg', options={'x-goog-meta-composer': 'George Harrison', 'x-goog-meta-published': '1966'}, retry_params=retryparams_w)

mode = 'r'で指定したファイルが存在しない場合cloudstorage.NotFoundErrorエラーが返るので、mode = 'r'の時はエラーキャッチが必要です。

2-w.ファイルの書き込み

ファイルの書き込みはさっき作ったインスタンスに.write(書き込み内容)するだけです。

cloudstorage_file.write('abcdefgh')
2-r.ファイルの読み込み

ファイルの読み込みはさっき作ったインスタンスに.read()を行い、返り値を保存します。

file_content = cloudstorage_file.read()
3.close()

処理が終わったら必ず.close()を行います。ここで初めて変更が保存されます。

cloudstorage_file.close()

複数のファイルを一度に読み込む

cloudstorage.open()の戻り値は単一のファイルですが、複数のファイルを一度に読み込みたい場合はcloudstorage.listbucket()を利用します。
引数は以下のようになります。

path_prefix (必須)
呼び出すファイルたちを含む親階層。例えばpath_prefix='/バケット名/beatles/revoler'としたら、そのディレクトリ以下のファイル全てが対象になります。プレフィックスなので'/バケット名/beatles/revoler/tax'としておいて、あとで'/バケット名/beatles/revoler/taxman''/バケット名/beatles/revoler/taxwoman'と繋げることも可。
marker (default=None)
上のpath_prefixで対象に含まれるうち、除外したいプレフィックス。例えばpath_prefix='/バケット名/beatles/revoler/tax'のときにmarker='/バケット名/beatles/revoler/taxman'とすると、'/バケット名/beatles/revoler/taxwoman'は対象に含まれるが'/バケット名/beatles/revoler/taxman〜〜〜'は除外されます。
max_keys (default=None) :integer型
返されるファイルの最大数。
delimiter (default=None) :string型
ここに指定した文字列でパスを区切って階層構造にして返します。
retry_params (default=None)
.write()と同様。
返り値はGCSFileStatオブジェクトのイテレータです。

使い方はリファレンスにサンプルが載っているので参考にしてください。

Cloud Storageのファイルを削除する

cloudstorage.delete()を利用します。引数は以下の通り。

filename (必須)
.write()と同様。
retry_params (default=None)
.write()と同様。

ファイルが存在しない場合cloudstorage.NotFoundErrorエラーが返るので、エラーキャッチをします。

try:
    cloudstorage.delete('/バケット名/beatles/revoler/taxman.mp3')
except cloudstorage.NotFoundError:
    pass

エラーキャッチ

上で紹介したcloudstorage.NotFoundError以外にもタイムアウトエラーや認証エラーなどの可能性が常にあるので、基本的にエラーキャッチは万全にしておきましょう。
Google Cloud Storage Errors and Error Handling  |  App Engine standard environment for Python  |  Google Cloud



テスト

localhost:8080を開いてDevelopment serverでテストをする際は、Datastoreと違ってCloud Storageは仮想環境でのシミュレートができません。そのためテスト時も実際のバケットとのやりとりとなります。

なので、dev_appserver.py app.yamlに以下のようにオプションを付ける必要があります。

dev_appserver.py app.yaml --default_gcs_bucket_name バケット名

リファレンス

Google App Engine公式リファレンス(英語)
Setting Up Google Cloud Storage  |  App Engine standard environment for Python  |  Google Cloud
Reading and Writing to Google Cloud Storage  |  App Engine standard environment for Python  |  Google Cloud

サンプルコード(これをみないとわけわかんないです)
appengine-gcs-client/main.py at master · GoogleCloudPlatform/appengine-gcs-client · GitHub

ドキュメント
Google Cloud Storage Client Library Functions  |  App Engine standard environment for Python  |  Google Cloud

Google Cloud Storageリファレンス(日本語!)
Cloud Storage ドキュメント  |  Cloud Storage  |  Google Cloud