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-RegionalかRegionalになります。この2つの違いは想定するアクセス元の地域です。Multi-Regionalは世界中からのアクセスを想定しています。
NearlineとColdlineはバックアップ用です。
前提知識
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と違って、手動でライブラリをダウンロードする必要があります。
方法はpip
かgit
、もしくは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デフォルトのバケットを指定する場合は、自動でバケット名を取得できます。...とはいえ平文で指定した方が早い気がするし、使いどころはよくわからないですが...
この場合はos
とapp_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段階になります。
cloudstorage.open()
でインスタンスを作成- 作成したインスタンスに
.write()
で書き込み・.read()
で読み込み .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-acl
はACLを設定します。これは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リファレンス(日本語!)
Cloud Storage ドキュメント | Cloud Storage | Google Cloud