最近ではファイルサーバを用意するのではなく、オブジェクトストレージを使用する方が多くなってきたように思う。私がWebでのキャリアを始めた頃には既にAWSやGoogle Cloudなどのクラウドサービスが提供されていたため、ファイルや大きなデータを保持する時はファイルサーバよりもオブジェクトストレージを使用する方が身近だったし、今もそうだ。SambaやFTPdなどのファイルサーバを用意しろと言われると、ちょっと身構えてしまう。ではオブジェクトストレージをきちんと理解できているのかと言われると、そうでもない。なんとなく使用していて、そして浅い理解でもそこそこ利用できてしまう。クラウドで提供され、フルマネージドであるため、サーバーを管理するというようなことも考える必要もないし、意識もしない。その浅い理解でも使えてしまう所が良いところの一つなのかもしれない。これまでそのようにしてお茶を濁してきたが、折角なので知識の整理がてら、オブジェクトストレージにどのようなものがあるのかをまとめて行こうと思った。オブジェクトストレージとは何かということよりも、オブジェクトストレージにはどんなものがあり、どうやってアクセスし、使ってみて躓いたところなどを記載していくことにする。
サービス
S3 - Amazon Simple Storage Service
署名付きURLでのファイルのアップロード
GCS - Google Cloud Storage
オブジェクトの命名ガイドライン
- Unicode文字で任意に設定できる
- UTF-8 エンコード時の長さが 1~1,024 バイト
- 改行やラインフィード文字を含めることはできない
- 先頭を .well-known/acme-challenge/ にすることはできない
- 「.」や「..」にすることはできない
クライアント
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が期待しているレスポンスに一部差があり、動作しない箇所がある。以下にその箇所とそれらに対する対策を示す。
期待しているレスポンスと形が異なるようだ。
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
使われているのは 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 ストレージ コンテナはβ版として提供されている。実際に試 してみたが正しく転送できた。
Azure Storage BlobからGoogle Cloud Storageに移行する
WIP
Azure Storage Blob
エミュレーター
Azurite
Azuriteは現在V3が提供されている。
https://hub.docker.com/_/microsoft-azure-storage-azurite
Port | 用途 |
---|---|
10000 | Blob用 |
10001 | Queue用 |