サービス

S3 - Amazon Simple Storage Service

GCS - Google Cloud Storage

オブジェクトの命名ガイドライン

  • Unicode文字で任意に設定できる

  • UTF-8 エンコード時の長さが 1~1,024 バイト

  • 改行やラインフィード文字を含めることはできない

  • 先頭を .well-known/acme-challenge/ にすることはできない

  • 「.」や「..」にすることはできない

https://cloud.google.com/storage/docs/naming-objects?hl=ja

https://symdon.ga/posts/1612947902/

クライアント

gsutil

Google Cloud StorageのためのCLIツール。

gcsfuse

https://github.com/GoogleCloudPlatform/gcsfuse

GCSをfuseでマウントすることで操作できる。

fake-gcs-serverにAPI endpointを向ける方法が不明。 credentials.jsonで行える?Googleの認証もfakeする必要がある。

UbuntuにgcsfiseをインストールするDockerfile
FROM php:5.6-apache
LABEL maintainer=TakesxiSximada
RUN apt update
RUN apt install -y gnupg2 sudo
RUN curl 'https://packages.cloud.google.com/apt/doc/apt-key.gpg' -o apt-key.gpg
RUN cat apt-key.gpg | apt-key add -

RUN echo "deb http://packages.cloud.google.com/apt gcsfuse-jessie main" | tee /etc/apt/sources.list.d/gcsfuse.list
RUN apt update
RUN apt install -y gcsfuse
google-cloud-storage

https://pypi.org/project/google-cloud-storage/

リクエストの送信先の変更

環境変数 STORAGE_EMULATOR_HOST が設定されているとクライアントはこの ホストに対してリクエストを送信する。

STORAGE_EMULATOR_HOST=http://host.docker.internal:4443

エミュレーターを用いるように設定を行った。 この状態であってもgoogle-cloud-storageはGoogleに認証を試みる。 ローカルでダミーサーバーを相手に動作させたいときにはその認証もさせたくない。 google.cloud.storage.Client のproject引数とcredential引数を渡すことで認証を回避できる。

from google.auth.credentials import AnonymousCredentials
from google.cloud.storage import Client

credentials = AnonymousCredentials()
client = Client(credentials=credentials, project=GCS_PROJECT_NAME)
発生したエラー
import io
import os

import requests

from google.cloud.storage import Client
from google.auth.credentials import AnonymousCredentials

GCS_BUCKET_NAME = os.environ["GCS_BUCKET_NAME"]

# クライアントの作成
# localではAnonymousCredentialsを認証情報の代わりとして使うとGoogleへ認証しないようになる。
# _httpをrequests.SessionにしないとGoogleへのAPI呼び出しをしようとする。
client = Client(credentials=AnonymousCredentials(), _http=requests.Session())

# bucketの作成
client.create_bucket(GCS_BUCKET_NAME)

# bucketの取得
bucket = client.bucket(GCS_BUCKET_NAME)

# blobの取得
blob = bucket.blob("/foo/bar/baz")

# コンテンツのアップロード
fp = io.BytesIO(b"AAAAAAAAAAAAAA")
fp.seek(0)
blob.upload_from_file(fp, content_type="application/octet-stream")

# コンテンツのダウンロード
# FIXME: この処理で5404 Not Foundが発生する。fake-gcs-serverと権限の設定により発生していると思われる。
resp = blob.download_as_text()
print(resp)

ただしこの状態ではblobの取得ができない。

>>> import codecs, os;__pyfile = codecs.open('''/var/folders/7c/h41k49q1301dz2j4zhzyzsp80000gn/T/pycGxk8M''', encoding='''utf-8''');__code = __pyfile.read().encode('''utf-8''');__pyfile.close();os.remove('''/var/folders/7c/h41k49q1301dz2j4zhzyzsp80000gn/T/pycGxk8M''');exec(compile(__code, '''/ng/symdon/pages/posts/1612772644/gcs_create_bucket.py''', 'exec'));
Traceback (most recent call last):
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/client.py", line 712, in download_blob_to_file
    blob_or_uri._do_download(
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/blob.py", line 956, in _do_download
    response = download.consume(transport, timeout=timeout)
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/resumable_media/requests/download.py", line 168, in consume
    self._process_response(result)
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/resumable_media/_download.py", line 185, in _process_response
    _helpers.require_status_code(
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/resumable_media/_helpers.py", line 99, in require_status_code
    raise common.BaralidResponse(
google.resumable_media.common.BaralidResponse: ('Request failed with status code', 404, 'Expected one of', <HTTPStatus.OK: 200>, <HTTPStatus.PARTIAL_CONTENT: 206>)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/ng/symdon/pages/posts/1612772644/gcs_create_bucket.py", line 31, in <module>
    resp = blob.download_as_text()
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/blob.py", line 1464, in download_as_text
    data = self.download_as_bytes(
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/blob.py", line 1289, in download_as_bytes
    self.download_to_file(
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/blob.py", line 1087, in download_to_file
    client.download_blob_to_file(
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/client.py", line 724, in download_blob_to_file
    _raise_from_baralid_response(exc)
  File "/Users/foo/.virtualenvs/bar/lib/python3.8/site-packages/google/cloud/storage/blob.py", line 3886, in _raise_from_baralid_response
    raise exceptions.from_http_status(response.status_code, message, response=response)
google.api_core.exceptions.NotFound: 404 GET http://host.docker.internal:4443/download/storage/v1/b/testing/o/foo/bar/baz?alt=media: Not Found
: ('Request failed with status code', 404, 'Expected one of', <HTTPStatus.OK: 200>, <HTTPStatus.PARTIAL_CONTENT: 206>)
>>>

これは先頭に設定した / が邪魔をしていただけだった。

fake-gcs-serverを相手にgoogle-cloud-storageを用いる場合

fake-gcs-serverを相手にgoogle-cloud-storageを用いる場合、 fake-gcs-serverの返すレスポンスとgoogle-cloud-storageが期待しているレ スポンスに一部差があり、動作しない箇所がある。 以下にその箇所とそれらに対する対策を示す。

Blobのmake_public()で権限の処理ができずエラーとなる

期待しているレスポンスと形が異なるようだ。 google.cloud.storage.acl.ACL.entity_from_dict に以下のモンキーパッチを当てて回避した。

import google.cloud.storage.acl

def entity_from_dict(self, entity_dict: Dict):
    role = entity_dict["role"]
    entity = self.all()
    entity.grant(role)
    return entity

google.cloud.storage.acl.ACL.entity_from_dict = entity_from_dict  # noqa
Blobのpublic_urlがgoogleのURLになってしまう

使われているのは google.cloud.storage.blob._API_ACCESS_ENDPOINT なので ここをモンキーパッチする。

import google.cloud.storage.blob
google.cloud.storage.blob._API_ACCESS_ENDPOINT = "http://host.docker.internal:4443"  # noqa

エミュレーター

fake-gcs-server

STORAGE_EMULATOR_ENV_VAR

public host

アップロードしたファイルを公開URLからアクセスできるようにするには --public-host オプションを指定する。オプションにはドメイン名(又はIP アドレス)を渡す。ポート番号やスキーマは --port--scheme で渡し た値が用いられる。

例えば localhost でアクセスしたければ --public-host localhost と指定する。例えば host.docker.internal で アクセスしたければ --public-host host.docker.internal と指定する。

host.docker.internalの4443ポートにhttpでアクセスできるようにした起動例を以下に示す。

docker run -p '4443:4443' fsouza/fake-gcs-server:v1.21.2 --scheme http --port 4443 --public-host host.docker.internal

他のオブジェクトストレージからGoogle Cloud Storageへ移行する

Google Cloud Storageは他のオブジェクトからのデータ移行をGoogle Cloud Consoleから確認できるようになっている。対応している転送元のサービスを 以下に示す。

  • Google Cloud Storage バケット

  • Amazon S3 バケット

  • Microsoft Azure ストレージ コンテナ (ベータ版)

− オブジェクト URL のリスト

Microsoft Azure ストレージ コンテナはβ版として提供されている。実際に試 してみたが正しく転送できた。

https://cloud.google.com/storage-transfer/docs/create-manage-transfer-console?hl=ja#microsoft-azure-blob-storage

Azure Storage BlobからGoogle Cloud Storageに移行する

Azure Storage Blob

エミュレーター

Azurite

Azuriteは現在V3が提供されている。

https://hub.docker.com/_/microsoft-azure-storage-azurite

Port用途
10000Blob用
10001Queue用