CUBE SUGAR CONTAINER

技術系のこと書きます。

S3 互換オブジェクトストレージの OSS - MinIO を試す

MinIO は Amazon S3 互換のオブジェクトストレージを提供する OSS のひとつ。 たとえばオンプレ環境でオブジェクトストレージを構築したいときや、手元で S3 を扱うアプリケーションの動作確認をするときなんかに使える。 今回はそんな MinIO を AWS CLI と Python クライアントの boto3 から使ってみる。

使った環境は次のとおり。

$ sw_vers
ProductName:    macOS
ProductVersion: 11.4
BuildVersion:   20F71
$ minio -v    
minio version RELEASE.2021-05-26T00-22-46Z
$ python -V
Python 3.9.5
$ aws --version
aws-cli/2.2.6 Python/3.9.5 Darwin/20.5.0 source/x86_64 prompt/off
$ pip list | grep -i boto3  
boto3                     1.17.83

もくじ

下準備

今回は Homebrew から MinIO をインストールして使う。 クライアントとして awscli と boto3 も入れておく。

$ brew install minio awscli
$ pip install boto3

インストールできたら作業用のディレクトリを指定して minio server コマンドを実行する。 これで MinIO のサーバが立ち上がる。

$ mkdir -p /tmp/minio
$ minio server /tmp/minio

立ち上がると 9000 番ポートを Listen し始める。

$ lsof -i:9000
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
minio   13739 amedama   14u  IPv6 0x62631bfc177de1b3      0t0  TCP *:cslistener (LISTEN)

ブラウザでローカルホストの 9000 番ポートにアクセスすると管理用の Web UI が見える。

$ open http://localhost:9000/

f:id:momijiame:20210529145812p:plain
MinIO の管理用 Web UI

アカウントはデフォルトだと Access Key と Secret Key がどちらも minioadmin でログインできる。 デフォルトのアカウントを変更したいときはサーバを立ち上げるときに以下の環境変数で指定する。

  • Access Key

    • MINIO_ROOT_USER または MINIO_ACCESS_KEY
  • Secret Key

    • MINIO_ROOT_PASSWORD または MINIO_SECRET_KEY

AWS CLI から操作する

はじめに AWS CLI から操作してみよう。 まずは認証情報を環境変数で設定しておく。

$ export AWS_ACCESS_KEY_ID=minioadmin
$ export AWS_SECRET_ACCESS_KEY=minioadmin

あとは aws コマンドのオプションとして --endpoint-url に MinIO が動作してる http://localhost:9000 を指定するだけ。

$ aws --endpoint-url http://localhost:9000 s3 ls

特にエラーにならず上記が実行できれば大丈夫。

サンプルとなるバケットを example-bucket という名前で作成してみる。

$ aws s3 --endpoint-url http://localhost:9000 mb s3://example-bucket
make_bucket: example-bucket

作成すると、ちゃんと ls でバケットが見えるようになった。

$ aws --endpoint-url http://localhost:9000 s3 ls
2021-05-29 15:25:34 example-bucket

続いてはファイルをバケットにコピーしてみる。

$ echo "Hello, World" > /tmp/greet.txt
$ aws --endpoint-url http://localhost:9000 s3 cp /tmp/greet.txt s3://example-bucket
upload: ../../tmp/greet.txt to s3://example-bucket/greet.txt

ちゃんとアップロードできた。

$ aws --endpoint-url http://localhost:9000 s3 ls s3://example-bucket
2021-05-29 15:26:29         13 greet.txt

ファイルに深いプレフィックスをつけてコピーしたいときは ls--recursive オプションをつけると再帰的に確認できる。

$ aws --endpoint-url http://localhost:9000 s3 cp /tmp/greet.txt s3://example-bucket/folder/subfolder/ 
upload: ../../tmp/greet.txt to s3://example-bucket/folder/subfolder/greet.txt
$ aws --endpoint-url http://localhost:9000 s3 ls s3://example-bucket --recursive
2021-05-29 15:29:37         13 folder/subfolder/greet.txt
2021-05-29 15:26:29         13 greet.txt

上記は / を区切りにした階層構造があるように見えるけど、これはあくまでファイル名に / 区切りのプレフィックスがついているに過ぎない。 つまり、インタフェース的に階層構造があるように見せているだけ、という点には留意する必要がある。 階層構造のように見えたとしても、バケット以下の構造はあくまでもフラットな名前空間になっている。

標準入出力経由でファイルをコピーすることもできる。

$ echo "Hello, World" | aws --endpoint-url http://localhost:9000 s3 cp - s3://example-bucket/stdin/greet.txt
$ aws --endpoint-url http://localhost:9000 s3 cp s3://example-bucket/stdin/greet.txt -
Hello, World

ファイルを削除するときは rm コマンドを使う。

$ aws --endpoint-url http://localhost:9000 s3 rm s3://example-bucket/stdin/greet.txt
delete: s3://example-bucket/stdin/greet.txt

バケットの削除は、入っているファイルをすべて削除すれば rb コマンドからできる。 ただし、今回は後段の boto3 が残っているので省略する。

boto3 から操作する

続いては Python クライアントの boto3 からアクセスしてみる。

まずは Python のインタプリタを起動する。

$ python

boto3 パッケージをインポートする。

>>> import boto3

エンドポイントや認証情報を与えてクライアントを作る。

>>> s3_client = boto3.client('s3',
...                          use_ssl=False,
...                          endpoint_url='http://localhost:9000',
...                          aws_access_key_id='minioadmin',
...                          aws_secret_access_key='minioadmin')

バケットのリストを確認すると、先ほど AWS CLI で作成したものが確認できる。

>>> response = s3_client.list_buckets()
>>> response['Buckets']
[{'Name': 'example-bucket', 'CreationDate': datetime.datetime(2021, 5, 29, 6, 25, 34, 96000, tzinfo=tzutc())}]

試しに新しくバケットを作ってみよう。

>>> s3_client.create_bucket(Bucket='boto3-bucket')
{'ResponseMetadata': {'RequestId': '168376A910A8A588', 'HostId': '', 'HTTPStatusCode': 200, 'HTTPHeaders': {'accept-ranges': 'bytes', 'content-length': '0', 'content-security-policy': 'block-all-mixed-content', 'location': '/boto3-bucket', 'server': 'MinIO', 'vary': 'Origin', 'x-amz-request-id': '168376A910A8A588', 'x-xss-protection': '1; mode=block', 'date': 'Sat, 29 May 2021 06:45:59 GMT'}, 'RetryAttempts': 0}, 'Location': '/boto3-bucket'}

確認すると、新しくバケットができている。

>>> response = s3_client.list_buckets()
>>> from pprint import pprint
>>> pprint(response['Buckets'])
[{'CreationDate': datetime.datetime(2021, 5, 29, 6, 45, 59, 285000, tzinfo=tzutc()),
  'Name': 'boto3-bucket'},
 {'CreationDate': datetime.datetime(2021, 5, 29, 6, 25, 34, 96000, tzinfo=tzutc()),
  'Name': 'example-bucket'}]

いくつかやり方はあるけど、ここでは upload_fileobj() 関数を使ってファイルをアップロードしてみる。

>>> import io
>>> f = io.BytesIO(b'Hello, World')
>>> s3_client.upload_fileobj(f, 'boto3-bucket', 'greet.txt')

ちゃんとアップロードできた。

>>> response = s3_client.list_objects(Bucket='boto3-bucket')
>>> pprint(response['Contents'])
[{'ETag': '"82bb413746aee42f89dea2b59614f9ef"',
  'Key': 'greet.txt',
  'LastModified': datetime.datetime(2021, 5, 29, 6, 47, 55, 783000, tzinfo=tzutc()),
  'Owner': {'DisplayName': 'minio',
            'ID': '02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4'},
  'Size': 12,
  'StorageClass': 'STANDARD'}]

今度は download_fileobj() 関数を使ってファイルをダウンロードしてみよう。

>>> f = io.BytesIO()
>>> s3_client.download_fileobj(Bucket='example-bucket', Key='greet.txt', Fileobj=f)

ちゃんと中身が確認できた。

>>> f.seek(0)
0
>>> f.read()
b'Hello, World\n'

いじょう。