« ^ »

オブジェクトストレージ

所要時間: 約 5分

最近ではファイルサーバを用意するのではなく、オブジェクトストレージを使用する方が多くなってきたように思う。私が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/ にすることはできない
  • 「.」や「..」にすることはできない

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

https://blog.symdon.info/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に移行する

WIP

Azure Storage Blob

エミュレーター

Azurite

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

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

Port用途
10000Blob用
10001Queue用