【Google Cloud Platformの使い方】GCP Storage にファイルの出し入れ(python3/Web API)


今日は、Google Cloud Storage にファイルをupload するところを復習したので、そのメモを書きます。

準備

データはBucket というラベルの単位で管理します。ここにファイルをホイホイ入れたり出したりします。

簡単に使う分にはCloud Storage のクイックスタートをフォローするだけで十分です。

bucket の作成

コマンドラインからも作れますが、console.cloud.google.com から行ったほうが便利です。何度も作るものではないと思うので。

コンソールのStorage のブラウザから、「バケット作成」にたどり着けます。下記の画面では、私が作った(塗りつぶした)bucket のほかに、最初から xxxx.artifacts.projectid.appspot.com という名前のbucket がありました。これは開くと分かりますが、Cloud Build で作成したdocker image が置かれていました。(今回は関係ありません)

アクセス権の設定

どうやらbucket 単位でアクセス権を設定することができるようです。
全てのユーザに与えたり、特定にユーザにしたりできます。
個別のユーザだけでなく、allUsers, allAuthentificatedUsers が選べます。
おそらく入門ガイドにある「機能の使用」や「アクセスの制御」をじっくり理解しなければならないのだと思いますが、今回は精読をスキップして、以下のようにデフォルトのサービスアカウントに権限を割り当てて、そのアカウントのキーを使用しました。

サービスアカウントとキー

CREDENTIAL の扱い方については、いろいろな方法が用意されていますが、私はもともとこのプロジェクトにデフォルトで作成されているサービスアカウントを使うことにしました。
「IAMと管理」の「サービスアカウント」をみると、[email protected] のようなCompute ENgine default service account がありました。

  • GCPで動かすときはこのアカウントが使われるらしい。ので、これに「ストレージオブジェクト管理者」のロールを与えます。さらに、作成したbucket にも権限を与えます。 (←これは違うかも。今は、キーファイルを使って運用しています。。。)
  • ローカルで動かすときは、このサービスアカウントの鍵を作成し、これを環境変数GOOGLE_APPLICATION_CREDENTIALSに設定して利用します。

鍵は「サービスアカウント」のページの編集から鍵生成の画面に入れます。流れに従って鍵を生成すると myproject-0123456789012345.json のようなファイルが生成されるので、これを保存します。環境変数に設定します。

export GOOGLE_APPLICATION_CREDENTIALS=/...your_dir/myprojectid-0123456.json

サービスアカウントにbucket へのアクセス権を付与

bucket へのアクセスするには、使用するサービスアカウントがその権限を持っているかで制御します。今は、自分が作ったbucket に使用するサービスアカウントを追加しました。

先ほどと同じストレージブラウザで対象となるbucket を選択し、「バケットの権限を編集」を起動します。「ユーザの追加」から先ほどのサービスアカウントを選択し、「ロールの追加」でCloud Storage の「ストレージオブジェクト管理者」を選択して保存しました。

その他いろいろごちゃごちゃやったので、いまいちクリアにはなっていないので、もしかしたら少し違うかもしれませんが、私はこれで動いています。

サーバー間での本番環境アプリケーションの認証の設定を参考にしました。

python によるファイルの出し入れの実装

google-cloud-storage を pip でインストールします。
https://pypi.org/project/google-cloud-storage/
ここでは全てpython3 で実装と動作確認を行っています。

Storage にアクセスするClient

pythonの実装では、まず、Storage へのClient を作成します。
CREDENTIAL の設定ができていれば、以下でclient が作成されるはずです。直接にkeyファイルを指定するにはstorage.Client.from_service_account_json()も使えますが、最近は上記のように環境変数を利用するほうがサーバとローカルとで同じ関数で書けるので使わなくなりました。

import google.cloud.storage as storage
from os import environ
def test_bucket():
    print("GOOGLE_APPLICATION_CREDENTIALS={}".format(environ.get("GOOGLE_APPLICATION_CREDENTIALS")))
    client = storage.Client()
    for bucket in client.list_buckets():
        print(bucket)

今一つbucket を作っていたので、以下のようになります。

GOOGLE_APPLICATION_CREDENTIALS=myserveraccountkey.json
<Bucket: asia.artifacts.my-project.appspot.com>
<Bucket: my-bucket>

file のupload

あとは、例文にあるように実装するだけです。が、APIの解説ではupload_from_file の引数がfile_obj ですが、私が動かしている範囲ではfile のpath を引数にしないとダメなようです。また、この状態だと、同じファイル名だと上書きを行っています。

import google.cloud.storage as storage
from os import path

def upload_file(local_fpath:str, bucket_name:str, remote_fpath:str):
    client = storage.Client()

    bucket = client.lookup_bucket(bucket_name)
    assert isinstance(bucket, storage.bucket.Bucket)

    bucket = client.get_bucket(bucket_name)

    if not path.isfile(local_fpath):
        return ""
    blob = bucket.blob(remote_fpath)
    if blob is not None:
        blob.upload_from_filename(local_fpath)
        url = "https://console.cloud.google.com/storage/browser/{}/{}".format(bucket_name, remote_fpath)
        return url
    return ""

file の download

同様にしてdownload もできます。ここでもdownload_to_fileの引数はファイルのパスで動いています。

import google.cloud.storage as storage
def download_file(bucket_name:str, remote_fpath:str, local_fpath:str):
    client = storage.Client()
    bucket = client.lookup_bucket(bucket_name)
    blob = bucket.blob(remote_fpath)
    if blob is not None:
        blob.download_to_filename(local_fpath)

最低限のことはできるようになりました。

WEB APIの利用

実はpython API で実装している中身はREST APIを使って通信をしているらしいです。今回は、ファイルの存在だけ確認しました。使い方は、APIリファレンスのobject/listに書かれています。

GET https://storage.googleapis.com/storage/v1/b/<bucket-name>/o

解説ページでパラメータの動作確認ができる

この解説ページのある"try it"で簡単に試すことができます。

ここでは簡単に動きました。Storage のbucket の所定のprefix (フォルダ)にあるはずのファイル名が返ってきました。

OAUth 2.0 Playground (開発者向けの動確ページ)

ですが、実際にターミナルで動かすにはまた一苦労です。OAuth のaccess token が必要でした。

OAuthを利用したリクエストは、OAUth 2.0 Playgroundで試すことができます。token を作成し、http request を試すことができます。token 発行までの流れはここの手順に従っていけば良く、gcloud auth login と同じようにアカウントが認証します。

WEB APIで確認

ここで実行したリクエストをcurl でも動作することを確認しました。my-bucket-name というbucket にあるtest/dir01 以下にあるファイル名を取得します。access token は先に取得したもので、ya29.で始まる文字列でした。

curl 'https://storage.googleapis.com/storage/v1/b/my-bucket-name/o?prefix=test%2Fdir01' \
    --header 'Authorization: Bearer ya29.accesstokenislooooooooooooooooong' \
    --header 'Accept: application/json' \
    --compressed

{
  "kind": "storage#objects",
  "items": [
    {
      "kind": "storage#object",
      "id": "mqtt-log-test/test/dir01/my-filename/1234567890123456",
      "selfLink": "https://www.googleapis.com/storage/v1/b/my-bucket-name/o/test%2Fdir01%2Fmy_filename",
      "mediaLink": "https://storage.googleapis.com/download/storage/v1/b/my-bucket-name/o/test%2Fdir01%2Fmy_filename?generation=1588578408260925&alt=media",
      "name": "test/dir01/my-filename",
      "bucket": "my-bucket-name",
      "generation": "1234567890123456",
      "metageneration": "1",
      "contentType": "application/x-tar",
      "storageClass": "STANDARD",
      "size": "192852",
      "md5Hash": "vLES9qETsDyglngtaBsrdw==",
      "crc32c": "XxXxXx==",
      "etag": "CL3K493bmekCEAE=",
      "timeCreated": "2020-05-04T07:46:48.260Z",
      "updated": "2020-05-04T07:46:48.260Z",
      "timeStorageClassUpdated": "2020-05-04T07:46:48.260Z"
    }
  ]
}

便利なgsutil

実は急いでダウンロードしたときはgsutil を使っています。早くて便利だ。

gsutil -m cp gs://bucket_name/......./* .

所感

とりあえずファイルの出し入れはできるようになりました。また、権限の問題も最低限のところはクリアできた感じです。自分はpython の実装も十分に楽ですが、REST APIの方がもっと楽だという話も聞きます。

ぱらぱら見ていると、
- データを暗号化できる
- ユーザごとにアクセス権や暗号化キーを設定できる
など、APIドキュメントや解説を見ていても、いろいろ使い方あるようです。

ふー。
(2020/05/04)

追記

  • (2020/05/05) 実際にbucket にファイルをupload するとき、"dir01/dir02/file"ならOKですが、"dir01/dir02//file" はNGでした。
  • (2020/05/06) サービスアカウントにあった長い数字はPROJECT NUMBER らしい。これはシェルからは gcloud projects describe ${PROJECT_ID} --format='get(projectNumber)' で取得できる。
  • (2020/05/06) 権限のところ、勘違いがあったかもしれないので、取り消し線を入れました。
  • (20200615) gsutil を追加