はじめに
オンプレなローカルのkubernetesクラスターではRook/Cephを通してBlock StorageやFilesystemを利用しています。Object Storageについては、Rook側に少し問題があった経緯もあってminioを常用しています。
久し振りに新規でk8sクラスターを構築したので、これまで利用していた Helm chart, stable/minio, は削除し、新規にMinio公式のHelm chartを利用してサーバーを構築しました。
以下の環境に合わせて、全面的に内容を見直しています。
- kubernetes v1.25.6 (by kubespray v2.21.0)
- rook v1.11.8 (ceph v17.2.6)
- helm chart, minio/minio v5.0.11
利用スタイル
各利用者はOIDCからTokenを得て、個々のnamespace内で作業をします。
原則として1ユーザーが利用できるObject Storageは1つで、"-bucket"という命名規則に従います。
このObject Storeへは各ユーザーだけがアクセスできるよう、個別にアクセス情報(access-key, secret-access-key)を作成し、Secretオブジェクトとして提供します。
この記事では、各ユーザー毎のBucket・接続情報の登録をバッチ的に処理できるようなスクリプトと、ユーザーが検証できるようmcコマンドやpythonスクリプトからMinioを利用する方法などを紹介しています。
参考資料
- https://qiita.com/iaoiui/items/cf405099ccc483a05db4
- https://helm.sh/blog/new-location-stable-incubator-charts/
- https://hub.docker.com/r/yasuhiroabe/minio-toolbox
- https://github.com/YasuhiroABE/docker-minio-toolbox
準備作業
作業手順の記録のためにMakefileを準備します。
NAME = minio
REL_NAME = my-minio
REPO_URL = https://charts.min.io/
CHART_PATH = minio/minio
VALUES_YAML = values.yaml
init:
sudo kubectl create ns $(NAME)
setup-repo:
sudo helm repo add $(NAME) $(REPO_URL)
list:
sudo helm repo list
update:
sudo helm repo update $(NAME)
fetch:
sudo helm fetch $(CHART_PATH)
install:
(cd $(NAME) ; sudo helm install $(REL_NAME) --debug --namespace $(NAME) -f $(VALUES_YAML) . )
upgrade:
(cd $(NAME) ; sudo helm upgrade $(REL_NAME) --debug --namespace $(NAME) -f $(VALUES_YAML) . )
delete:
sudo helm delete --namespace $(NAME) $(REL_NAME)
delete-ns:
sudo kubectl delete ns $(NAME)
extract-keys:
sudo kubectl -n $(NAME) get secret $(REL_NAME) -o jsonpath='{.data.rootUser}' | base64 -d && echo
sudo kubectl -n $(NAME) get secret $(REL_NAME) -o jsonpath='{.data.rootPassword}' | base64 -d && echo
Makefileを使って、repoの登録、tar.gzファイルの取得・展開などを進めます。
## repoの追加
$ make setup-repo
## namespaceの作成
$ make init
## chartの入手
$ make fetch
## tar.gzファイルの展開
$ ls
Makefile minio-5.0.11.tgz
$ tar xvzf minio-5.0.11.tgz
ここで、./minio/ディレクトリが出来ているはずです。
minio/values.yamlの編集
まず minio/values.yamlを編集します。
作業前にgitで初期状態を記録しておきます。
$ cd minio
$ git init
$ git add .
$ git commit -m 'initial commit'
$ vi values.yaml
minio/values.yamlの編集部分は次のようになっています。
diff --git a/values.yaml b/values.yaml
index 6657662..5a68579 100644
--- a/values.yaml
+++ b/values.yaml
@@ -114,7 +114,7 @@ bucketRoot: ""
# Number of drives attached to a node
drivesPerNode: 1
# Number of MinIO containers running
-replicas: 16
+replicas: 4
# Number of expanded MinIO clusters
pools: 1
@@ -158,7 +158,7 @@ persistence:
##
## Storage class of PV to bind. By default it looks for standard storage class.
## If the PV uses a different storage class, specify that here.
- storageClass: ""
+ storageClass: "rook-ceph-block"
VolumeName: ""
accessMode: ReadWriteOnce
size: 500Gi
@@ -259,7 +259,7 @@ podLabels: {}
##
resources:
requests:
- memory: 16Gi
+ memory: 4Gi
## List of policies to be created after minio install
##
続いて導入作業を実施します。
$ make install
まだ確認できませんが、これでminioが動作しているはずです。
稼動確認
以前の記事では、mcコマンドを使ってネットワーク越しにアクセスしていました。
今回の環境はClusterIPを使っていて、LBでの接続は許可していません。
一時的であればLB用のServiceオブジェクトを定義してブラウザから作業するなどしていますが、基本的にはKubernetes内部からのアセクスしか許可したくないので稼動確認に工夫が必要です。
mcコマンドのdockerイメージは存在するもののコマンドとして利用することが前提でKubernetesのネットワークにアクセスすることはできません。これをサーバーとして稼動させるため、busybox+mcコマンドのようなコンテナがないか探したところ、Toolbox Container Image が公開されていたので、このコンセプトを参考にmcコマンドを実行するためのサーバーコンテナを作成していきます。
mcコマンドを搭載したminio-toolboxコンテナの作成
次のようなDockerfileを現在では利用しています。
FROM alpine:3.18.2
ARG TARGETOS
ARG TARGETARCH
RUN apk add --no-cache tzdata bash ca-certificates busybox openssl git
WORKDIR /app
RUN wget https://dl.min.io/client/mc/release/${TARGETOS:-linux}-${TARGETARCH:-adm64}/mc
RUN chmod +x mc
COPY ./app /app
RUN chmod +x gen-template.sh
ENV DATADIR /root
CMD ["sh"]
最新のコンテナはDocker Hubに登録しています。
コードはGitHubに登録しています。
GitHubにはKubernetes上で実行するYAMLファイルなども含まれています。
最近はmacOSでの稼動機会も増えてきたので、arm64版も登録しています。
namespace/minio-client で minio-toolbox を動かす
helmで構成したminioサーバーは、namespace/minioに存在しています。
NetworkPolicyによる通信制限はしていないため、別のnamespace(minio-client)に配置したminio-toolboxコンテナから接続させています。
まず作成するbucket情報などを保存するPVCを定義します。
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
namespace: minio-client
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: rook-ceph-block
resources:
requests:
storage: 1Gi
次にmcコマンドなどが入っている、minio-toolboxを稼動させます。
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-toolbox
labels:
app: minio-toolbox
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: minio-toolbox
template:
metadata:
labels:
app: minio-toolbox
spec:
containers:
- name: minio-toolbox
image: yasuhiroabe/minio-toolbox:1.0.5
command: ["sh","-c", "tail -f /dev/null"]
imagePullPolicy: "Always"
volumeMounts:
- name: data
mountPath: /root
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
このYAMLファイルを適用して、podを立ち上げます。
$ sudo kubectl create ns minio-client
$ sudo kubectl -n minio-client apply -f pvc-minio-client.yaml
$ sudo kubectl -n minio-client apply -f deploy-minio-client.yaml
$ sudo kubectl -n minio-client get all
NAME READY STATUS RESTARTS AGE
pod/minio-toolbox-6dc6798759-r9dcn 1/1 Running 0 5h17m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/minio-toolbox 1/1 1 1 5h17m
NAME DESIRED CURRENT READY AGE
replicaset.apps/minio-toolbox-6dc6798759 1 1 1 5h17m
ここまでで準備作業は終わりです。
minio-client (container-toolbox)の利用
このPodの内部に入って、mcコマンドを実行していきます。
namespaceなどの条件が同じであれば、次のようなコマンドラインをコピーして実行することで、コンテナに移動することができます。
$ sudo kubectl -n minio-client exec -it $(sudo kubectl -n minio-client get pod -l app=minio-toolbox -o=jsonpath='{.items[0].metadata.name}') -- bash
内部には直下にmcコマンドなどが配置してありますので、次の要領で稼動確認ができます。
minio-toolbox-c65b78d9b-z79d7:/app# ./mc --version
mc version RELEASE.2023-07-07T05-25-51Z (commit-id=1b992864ee0682b8be6a590ccbda080475dcadd3)
Runtime: go1.19.10 linux/amd64
Copyright (c) 2015-2023 MinIO, Inc.
License GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html>
mcコマンドによる初期設定
mcコマンドを利用してminioサーバーを操作する時の基本的な手順は次のとおりです。
- ~/.mc/config.json にminioサーバーへの接続情報を登録 (初回のみ)
- ユーザーの登録
- ポリシーの登録
- Bucketの作成
ここではラベル"my-minio-local"に、minioサーバーへの接続情報を登録します。
これらの情報は ~/.mc/config.json に保存されています。
接続情報の確認
minioサーバーのFQDNはhelmでこの指示どおりに導入した場合 my-minio-svc.minio.svc.cluster.local で、ポート番号は 9000 番です。
この他に必要なaccess-keyとsecret-keyは namespace/minio のSecretオブジェクトを確認します。
別の端末(shell)から、次のようなコマンドを実行し、rootUser, rootPassword の情報を確認します。
$ sudo kubectl -n minio get secret my-minio -o jsonpath='{.data.rootUser}' | base64 -d && echo
$ sudo kubectl -n minio get secret my-minio -o jsonpath='{.data.rootPassword}' | base64 -d && echo
このコマンドはMakefile中に"extract-keys"タスクとして登録しています。
rootUserがaccess-key、 rootPasswordがsecret-keyに対応します。
mcコマンドによる稼動確認
再びPodに戻り、./mcコマンドを実行していきます。
minio-toolbox-c65b78d9b-z79d7:/app# ./mc config host add my-minio-local http://my-minio-svc.minio.svc.cluster.local:9000 <access-key> <secret-key> --api S3v4
Added `my-minio-local` successfully.
以降は~/.mc/config.jsonを参照するため、URL等の指定は不要です。
何かURLを間違ったなどの問題は、直接 ~/.mc/config.json を修正することもできます。
minio-toolbox-c65b78d9b-z79d7:/app# ./mc ls my-minio-local
minio-toolbox-c65b78d9b-z79d7:/app# ./mc mb my-minio-local/test
Bucket created successfully `my-minio-local/test`.
minio-toolbox-c65b78d9b-z79d7:/app# ./mc cp /etc/hosts my-minoi-local/test
/etc/hosts: 340 B / 340 B ━━━━━━━━━━━━ 140.88 KiB/s 0s
minio-toolbox-c65b78d9b-z79d7:/app# ./mc ls my-minio-local/test -r
[2022-09-01 08:51:17 UTC] 227B STANDARD hosts
ユーザー毎のアクセス権限の付与
利用するユーザー毎に利用できるbucketを準備するために、minio-toolboxには gen-template.sh を配置しています。
これは、ユーザー名をキーとして、"${username}-bucket" オブジェクトを作成し、必要な権限を一括して付与するスクリプトです。
スクリプト内部ではユーザー毎に権限を定義するjsonファイルを生成するためのテンプレートを利用しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::_USER_ID_-bucket/*"
],
"Sid": ""
}
]
}
引数にユーザー名のリストを取り、"${username}-bucket"形式のbucketを作成し、そのbucketに接続するためのaccess-key, secret-key情報を登録します。
#!/bin/bash
## Usage: ./gen-template.sh $(cat member.list)
BASEDIR=$(dirname $(readlink -f $0))
OUTPUT_DIRNAME="$(date +%Y%m%d.%H%M%S)"
OUTPUTDIR="${DATADIR:-/root}/${OUTPUT_DIRNAME}"
MC_CMD="${BASEDIR}/mc"
MINIO_TARGET=${MINIO_TARGET:-my-minio-local}
## template filepath from ${OUTPUTDIR}
TEMPLPATH1="${BASEDIR}/user-bucket-policy-_USER_ID_.json"
## check the .mc/config file.
if test ! -f ~/.mc/config.json || test "$(grep ${MINIO_TARGET} ~/.mc/config.json >/dev/null)" == "1" ; then
echo "Usage: Please setup the ~/.mc/config file, using the following command line."
echo " ./mc config host add my-minio-local http://my-minio.minio:9000 <root-access-key> <root-password> --api S3v4"
fi
## prepare outputdir and cd to it.
mkdir -p "${OUTPUTDIR}"
cd "${OUTPUTDIR}"
for username in $@
do
## check existing bucket
${MC_CMD} ls "${MINIO_TARGET}/${username}-bucket"
test "$?" == "0" && echo "[skip] bucket, ${username}-bucket, exists" && continue
## prepare the template file
OUTPUT_FILENAME=$(basename "${TEMPLPATH1}" | sed -e "s/_USER_ID_/${username}/g")
sed -e "s/_USER_ID_/${username}/g" "${TEMPLPATH1}" > "${OUTPUT_FILENAME}"
## adding minio user and policy
ACCESS_KEY="$(openssl rand -hex 8)"
SECRET_KEY="$(openssl rand -hex 16)"
${MC_CMD} admin policy create "${MINIO_TARGET}" "readwrite-${username}" "${OUTPUT_FILENAME}"
test "$?" == "0" && ${MC_CMD} admin user add "${MINIO_TARGET}" "${ACCESS_KEY}" "${SECRET_KEY}"
test "$?" == "0" && ${MC_CMD} admin policy attach "${MINIO_TARGET}" "readwrite-${username}" --user "${ACCESS_KEY}"
echo "${username},${ACCESS_KEY},${SECRET_KEY}" >> "../${OUTPUT_DIRNAME}.txt"
${MC_CMD} mb "${MINIO_TARGET}/${username}-bucket"
done
コマンドを実行すると、環境変数DATADIR(default: /root/)で指定したディレクトリの直下に"日付.時刻"形式のサブディレクトリが作成され、jsonファイルが保存されます。"日付.時刻".txtファイルにユーザー毎のaccess_key, secret_keyを記録したファイルが生成されます。
$ env MINIO_TARGET="my-minio-local" ./gen-template.sh user01 usre02
...
$ cat /root/20230712.050838.txt
user01,a82e1cfd6e6166f7,e53d91c7887e92174408917a553f14bd
user02,5685c31ba389058d,f9fe1aa2eba7d7a7536575e23de987f4
この接続情報を利用してユーザーアプリケーションからの接続確認
$ ./mc config host add my-minio-local http://my-minio-svc.minio.svc.cluster.local:9000 a82e1cfd6e6166f7 e53d91c7887e92174408917a553f14bd --api S3v4
## user01-bucketへはアクセスできる
$ ./mc ls my-minio-local/user01-bucket
## user02-bucketへのアクセスは拒絶
$ ./mc ls my-minio-local/user02-bucket
mc: <ERROR> Unable to list folder. Access Denied.
## user01-bucketへのアクセスは問題なくできる
$ ./mc ls my-minio-local/user01-bucket
$ ./mc cp gen-template.sh my-minio-local/user01-bucket
$ ./mc ls my-minio-local/user01-bucket
[2022-09-01 15:18:53 UTC] 1.2KiB STANDARD gen-template.sh
実際にはユーザーはアプリケーションの中でminioクライアントや、S3互換クライアントを利用して接続すると思いますので、このaccess_key, secret_keyの利用方法が分かれば良いことになります。
各ユーザーにaccess_key, secret_keyを通知する
次に管理者権限でkubectlを実行できる端末に移動します。
前提としている環境では、各ユーザーは同名のnamespaceに所属していて、OIDCによって個別に認証されています。
まずnamespace毎に secret/objectstore を作成し、bucketに接続するために必要な情報を格納していきます。
先ほどのコマンドで、"日付".txt ファイルが生成されているので、その情報を使って、ユーザーの毎namespaceにSecretオブジェクトを配置します。
kubectlが実行できる環境の適当なディレクトリに、次のようなスクリプトを実行します。
#!/bin/bash
## Usage: ./store-passwd-to-namespace.sh < some.keys
## Format: The input literal should be as follows:
##
## namespace,access-key,secret-access-key
##
for line in $(cat -)
do
ns=$(echo $line | cut -d, -f1)
akey=$(echo $line | cut -d, -f2)
skey=$(echo $line | cut -d, -f3)
ip="my-minio-svc.minio.svc.cluster.local:9000"
host="my-minio-svc.minio.svc.cluster.local"
sudo kubectl -n $ns create secret generic objectstore --from-literal=secret-access-key=$skey --from-literal=access-key=$akey --from-literal=aws-host="${host}" --from-literal=aws-ip="${ip}"
done
各ユーザーが自分の情報を知りたい場合には次のようなコマンドで各値を読み出す事が可能です。
$ kubectl -n $ns get secret objectstore -o=jsonpath="{.items[0]}{.data.access-key}" | base64 --decode ; echo
$ kubectl -n $ns get secret objectstore -o=jsonpath="{.items[0]}{.data.secret-access-key}" | base64 --decode ; echo
これらの情報を利用してMinioにデータを保存することが可能になります。
ユーザーによる作成したObject Storeの利用
先ほどはnamespace/minio-clusterにpod/minio-toolboxを作成しましたが、同様に自分のnamespaceで、minio-toolboxを稼動させることもできます。
この場合は先ほどと同じように操作が可能です。
もう少し現実的な、pythonのminioライブラリを使ったサンプルを載せておきます。
Pythonの準備作業
venvを利用して適当な作業用ディレクトリで、次のようなコマンドを実行します。
$ python3 -m venv venv/minio
$ . venv/minio/bin/activate
(minio) $
Promptが変化すれば準備は完了です。
この状態で、次のようなrequirements.txtファイルを準備します。
environs==9.5.0
minio==7.1.15
venv/minio以下にminioライブラリをダウンロードします。
(venv) $ pip install -r requirements.txt
run.pyファイルに次のようなPythonコードを準備しておきます。
#!/usr/bin/python
import io
from minio import Minio
from environs import Env
env = Env()
env.read_env()
MINIO_ENDPOINT_SECURITY = env.bool('MINIO_ENDPOINT_SECURITY', False)
MINIO_ENDPOINT = env.str('MINIO_ENDPOINT', '')
MINIO_ACCESS_KEY = env.str('MINIO_ACCESS_KEY', '')
MINIO_SECRET_KEY = env.str('MINIO_SECRET_KEY', '')
MINIO_BUCKET = env.str('MINIO_BUCKET', 'my-minio-local')
client = Minio(MINIO_ENDPOINT,
access_key=MINIO_ACCESS_KEY,
secret_key=MINIO_SECRET_KEY,
secure=MINIO_ENDPOINT_SECURITY,)
message = "Helo world!"
client.put_object(MINIO_BUCKET, "my/message.txt",
io.BytesIO(message), length=len(message),)
Dockerコンテナの作成と登録
これを次のようなDockerfileを利用してまとめます。
FROM python:3.10.12-alpine3.18
ENV PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off \
PORT=8000
RUN apk add --no-cache tzdata bash ca-certificates
WORKDIR /app
RUN addgroup minio
RUN adduser -S -G minio minio
RUN chown minio:minio /app
USER minio
RUN python -m venv venv
ENV PATH=/app/venv/bin/:$PATH
# Install the project requirements.
COPY requirements.txt /app
RUN pip install -r /app/requirements.txt
COPY run.py /app
ENV MINIO_ENDPOINT "minio.minio.svc.cluster.local:9000"
ENV MINIO_ENDPOINT_SECURITY "False"
ENV MINIO_ACCESS_KEY ""
ENV MINIO_SECRET_KEY ""
ENV MINIO_BUCKET ""
ENTRYPOINT ["python", "run.py"]
DockerHubやharborなどの適当なリポジトリに登録しておきます。
$ sudo docker build . --tag minio-sample:1.0.0 --no-cache
$ sudo docker tag minio-sample:1.0.0 docker.io/yasuhiroabe/minio-sample:1.0.0
$ sudo docker push docker.io/yasuhiroabe/minio-sample:1.0.0
配置したサンプルの実行
ここでは、docker.io/yasuhiroabe/minio-sample:1.0.0 を使って、動作を確認していきます。
自分のリポジトリに登録した場合は、適宜読み替えてください。
Kubernetes上で、minio-sample:1.0.0を動作させるため、Deploymentオブジェクトを作成します。
この時にsecretオブジェクトから環境変数に必要な値をコピーします。
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-sample
labels:
app: minio-sample
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: minio-sample
template:
metadata:
labels:
app: minio-sample
spec:
containers:
- name: minio-sample
image: yasuhiroabe/minio-python-sample:1.0.0
command: ["sh","-c", "tail -f /dev/null"]
imagePullPolicy: "Always"
env:
- name: MINIO_BUCKET
value: "user01-bucket"
- name: MINIO_ACCESS_KEY
valueFrom:
secretKeyRef:
name: objectstore
key: access-key
- name: MINIO_SECRET_KEY
valueFrom:
secretKeyRef:
name: objectstore
key: secret-access-key
- name: MINIO_ENDPOINT
valueFrom:
secretKeyRef:
name: objectstore
key: aws-ip
このPodを作成し、中に入って、run.py を実行します。
作成したmessage.txtの確認
pod/minio-sampleからpod/minio-toolboxに移動し、Bucket上にメッセージが保存されているか確認します。
$ sudo kubectl exec -it minio-toolbox-79d8bf654f-7hkjf -- sh
/app # ./mc ls my-minio-local/user01-bucket/
[2022-09-01 15:18:53 UTC] 1.2KiB STANDARD gen-template.sh
[2022-09-01 22:13:20 UTC] 0B my/
/app # ./mc ls my-minio-local/user01-bucket/my/message.txt
[2022-09-01 22:12:24 UTC] 11B STANDARD message.txt
/app # ./mc cp my-minio-local/user01-bucket/my/message.txt .
.../my/message.txt: 11 B / 11 B ━━━━━━━━━━━━━━━━━━━ 244 B/s 0s
/app # cat message.txt
Hello world!
ここまでで、任意のBucketの作成、個別の接続情報の登録などが行えるようになりました。
作成した情報の削除
作成したuser, policy, bucketを削除する手順は以下のとおり
## bucketの削除
$ ./mc rb my-minio-local/user01-bucket
## policyの削除
$ ./mc admin policy remove my-minio-local readwrite-user01
## access_keyを調べて不要なものを削除する
$ ./mc admin user list my-minio-local
$ ./mc admin user info my-minio-local 833ff7c0ac494849
$ ./mc admin user remove my-minio-local 833ff7c0ac494849
usre01などの情報は、bucket名とnamespaceとして参照していますが、実際に接続に使用されるID, Passwordに相当する情報は乱数なので、./mc admin user list <bucket name>
で接続情報のリストを確認して使われていない接続情報は削除する必要があります。
なお手元の環境では次のようになっていて、検証のために繰り返しスクリプトを実行したタイミングで重複した接続情報が登録されている様子が分かります。
/app # ./mc admin user list my-minio-local
enabled 0443140359ee8cbb readwrite-yasu-abe
enabled 5685c31ba389058d readwrite-user02
enabled 8dca9cae82b2be30 readwrite-user01
enabled a82e1cfd6e6166f7 readwrite-user01
enabled console consoleAdmin
enabled d184179c6a94a34a readwrite-user02
さいごに
2022年頃からmcコマンドのいくつかのオプションがOBSOLETED扱いになっていて、以前のスクリプトが動作しない現象に遭遇しました。
またminioサーバーも、作成したユーザーをpolicyにattachしようとして失敗する事例にも遭遇しました。
システムを更新するタイミングで、Helmで stable/minio repo を使っていた古いクラスターは削除して、公式ドキュメントに掲載されている手順から minio/minio repo を利用して最新のバージョンに切り替えました。
まだObject Storageの利用は限定的なので、一時的にバックアップを取ったりして楽に移行できるので良いですが、長く使ったminioクラスターをメンテナンスしていくかは難しい問題になるかもしれません。
多くのユーザーが共同利用するようなクラスターで、ユーザー毎にBUCKET、ユーザー、ポリシーを個別に作成するこの実装が参考になれば幸いです。
以上