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/
アカウントはデフォルトだと 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'
いじょう。