이번 글에서는 로컬 쿠버네티스 환경을 KinD로 구성하고, MinIO 를 설치하고 오브젝트 스토리지를 간단하게 올려서 기본적인 동작을 확인하고 정리해보려고 합니다. MinIO의 핵심 컨셉도 정리하려했으나 내용이 길어져 별도 글로로 작성했습니다.
https://ryusstory.tistory.com/entry/minio-architecture-key-concepts
아래 스크립트를 통해 컨트롤 플레인 1개와 워커 3개로 구성된 클러스터를 생성합니다.
추가로 NodePort를 사용하기 위해 30000-30003 포트를 열어놨습니다.
kind create cluster --image kindest/node:v1.33.4 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
- role: worker
- role: worker
- role: worker
EOF
MinIO Operator는 MinIO를 관리하기 위한 CRD를 설치합니다. 이 CRD를 통해 MinIO Tenent를 쿠버네티스 오브젝트로 정의하고 관리할 수 있습니다.
Operator는 기본적으로 네임스페이스 안에서 동작하며, 기본적으로 2개의 파드를 실행합니다.
helm repo add minio-operator https://operator.min.io
helm install \
--namespace minio-operator \
--create-namespace \
--set operator.replicaCount=1 \
operator minio-operator/operator
설치후에 crd를 확인해보면 policybindings.sts.min.io
와 tenants.minio.min.io
라는 CRD가 생성된 것을 확인할 수 있습니다.
공식문서
각 Tenant는 서버 파드, PVC, Service, Secret 등을 포함하며, 이를 통해 여러 개의 MinIO 클러스터를 한 Kubernetes 환경 안에서 네임스페이스 별로 서로 분리해 운영할 수 있습니다.
MinIO 공식 문서에서는 안정성과 성능을 위해 다음을 권장하고 있습니다
각 테넌트에는 MinIO 파드가 배포되는데, 각 파드는 3개의 컨테이너로 구성됩니다.
공식 문서를 참고하여 한번 테넌트를 배포해보겠습니다.
먼저 kustomize를 통해 배포할 매니페스트를 생성합니다.
kubectl kustomize https://github.com/minio/operator/examples/kustomization/base/ > tenant-base.yaml
해당 파일은 편집기로 열어서 아래 부분들을 수정해 봅니다.
Secret 중 storage-configuration
EC:2
는 4개의 디스크 중 2개의 디스크가 장애가 나더라도 데이터를 복구할 수 있는 인코딩 방식입니다. (Erasure Coding)
테스트목적이기 때문에 EC:1
로 설정해서 2개의 디스크 중 1개의 디스크가 장애가 나더라도 데이터를 복구할 수 있는 인코딩 방식으로 설정합니다.
유저/패스워드는 편의상 admin/adminadmin으로 설정했습니다. (MINIO_ROOT_USER length should be at least 3, and MINIO_ROOT_PASSWORD length at least 8 characters
)
export MINIO_STORAGE_CLASS_STANDARD="EC:1"
export MINIO_ROOT_USER="admin"
export MINIO_ROOT_PASSWORD="adminadmin"
S3 API를 통해 접속할 수 있는 콘솔의 유저/패스워드도 admin/adminadmin으로 설정합니다. YWRtaW4=
는 base64로 인코딩된 admin 문자열입니다. ( echo -n "admin" | base64
)
CONSOLE_ACCESS_KEY: YWRtaW4=
CONSOLE_SECRET_KEY: YWRtaW5hZG1pbg==
kind: Tenant
오브젝트의 spec.pools[0]
부분을 보면 서버 수를 설정하는 servers
항목과 서버당 볼륨 수를 설정하는 volumesPerServer
항목이 있습니다.
servers는 4로 설정하고, volumesPerServer는 2로 설정합니다.
volumesPerServer: 2
pools[0].volumeClaimTemplate.spec.resources.requests.storage
항목은 각 볼륨의 크기를 지정합니다. 1Ti로 설정되어 있는데, 테스트 목적이기 때문에 1Gi로 수정합니다.
storage: 1Gi
그리고 마지막으로 각 yaml에 namespace가 minio-tenent
로 설정되어 있는데, 그대로 배포하겠습니다.
kubectl apply -f tenant-base.yaml
배포하고 나면kubectl get all -n minio-tenant
명령어로 리소스들이 잘 생성되기를 기다립니다
완료되고 나면 kubectl get pvc -n minio-tenant
명령어로 PVC들이 잘 생성되었는지 확인합니다. 확인해보면 4개 노드 x 2개 볼륨 으로 8개의 PVC가 생성된 것을 확인할 수 있습니다.
서비스를 확인해보면 3개의 서비스가 생성된 것을 확인할 수 있습니다.
minio와 minio-console만
$ kubectl get svc -n minio-tenant
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
minio ClusterIP 10.96.133.240 <none> 443/TCP 29s
myminio-console ClusterIP 10.96.35.196 <none> 9443/TCP 29s
myminio-hl ClusterIP None <none> 9000/TCP 29s
기존의 ClusterIP 서비스를 NodePort로 패치해서 kind에서 열어둔 포트로 접속할 수 있도록 합니다.
kubectl patch svc minio -n minio-tenant -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "nodePort": 30001}]}}'
kubectl patch svc myminio-console -n minio-tenant -p '{"spec": {"type": "NodePort", "ports": [{"port": 9443, "nodePort": 30002}]}}'
https://localhost:30001/ 로 접속하면 콘솔 로그인 화면이 나옵니다.
앞서 설정한 admin/admin으로 로그인하면 아래와 같이 콘솔 화면이 나옵니다.
공식 문서의 방법대로 MinIO Client(mc)를 설치합니다.
설치하고 mc alias set 명령어로 앞서 설정한 minio 서비스에 접속할 수 있도록 설정합니다.
mc alias set local https://localhost:30001 admin adminadmin --insecure
테스트를 위해 간단한 30줄짜리 텍스트를 업로드하고, 워커 노드 한대를 종료 시켜 EC:1 설정이 잘 동작하는지 확인해보겠습니다.
seq 명령어를 통해 30줄짜리 텍스트 파일을 생성합니다. 넣고 나서 확인하기 편하도록 숫자 앞에 이모지를 추가합니다.
seq 30 | sed 's/^/🟩🟩 /' > test.txt
mc 명령어로 테스트 버킷을 생성하고 파일을 업로드 합니다.
mc mb local/test-bucket --insecure
mc cp test.txt local/test-bucket/ --insecure
업로드된 파일을 확인합니다.
mc ls local/test-bucket --insecure
원본 파일과 업로드된 객체를 md5sum으로 비교하면 일치하는 것을 확인할 수 있습니다.
cat test.txt | md5sum
mc cat local/test-bucket/test.txt --insecure | md5sum
kubectl describe pod -n minio-tenant -l v1.min.io/tenant=myminio
명령어로 확인해보면 MinIO 파드는 /export0
과 /export1
경로에 PV가 마운트되어 있습니다.
아래처럼 실행하면 모든 파드에서 /export0
과 /export1
경로에 test-bucket/test.txt/xl.meta
파일이 존재하는 것을 확인할 수 있습니다.
즉, 객체가 업로드 되면 각 PV에 디렉토리/파일 단위로 저장되고, 확인해보면 실제 파일은 파일명 폴더 안에 xl.meta
라는 메타데이터 파일로 저장되는 것을 알 수 있습니다.
kubectl get pod -n minio-tenant -l v1.min.io/tenant=myminio -oname \
| xargs -I {} kubectl exec -n minio-tenant -c minio {} -- \
sh -c "echo _____ {}; ls /export0/test-bucket/test.txt/xl.meta /export1/test-bucket/test.txt/xl.meta"
그리고 실제 어떻게 나눠서 저장되어 있는지 어느정도는 구경해 볼 수 있습니다. 잘 찾아보면 1부터 30까지 모두 들어가 있는 것을 확인할 수 있습니다.
kubectl get pod -n minio-tenant -l v1.min.io/tenant=myminio -oname | xargs -I {} kubectl exec -n minio-tenant -c minio {} -- sh -c "echo; echo _____ {} export0; cat /export0/test-bucket/test.txt/xl.meta; echo; echo _____ {} export1; cat /export1/test-bucket/test.txt/xl.meta"
xl-meta 툴은 minio 디버깅 툴로 golang을 설치하고 아래 명령어로 xl-meta 툴을 설치합니다.
wget https://go.dev/dl/go1.25.1.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.1.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin:~/go/bin/' >> ~/.bashrc
source ~/.bashrc
go install github.com/minio/minio/docs/debugging/xl-meta@latest
이제 xl.meta 파일을 직접 보기 위해 먼저 pv를 확인합니다.
ryuss@R250822:~$ kc get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-0c903892-36ac-40bf-aabb-32f6efa6bfaf 1Gi RWO Delete Bound minio-tenant/1-myminio-pool-0-0 standard <unset> 67m
해당 pv의 실제 위치도 확인합니다.
kc describe pv pvc-0c903892-36ac-40bf-aabb-32f6efa6bfaf
Source:
Type: HostPath (bare host directory volume)
Path: /var/local-path-provisioner/pvc-0c903892-36ac-40bf-aabb-32f6efa6bfaf_minio-tenant_1-myminio-pool-0-0
해당 파드가 어느 노드에 있는지 확인합니다.
kc get pods -nminio-tenant -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myminio-pool-0-0 2/2 Running 0 69m 10.244.2.4 kind-worker <none> <none>
이제 노드에서 직접 xl.meta 파일을 확인해봅니다.
docker exec -ti kind-worker bash -c "cat /var/local-path-provisioner/pvc-0c903892-36ac-40bf-aabb-32f6efa6bfaf_minio-tenant_1-myminio-pool-0-0/test-bucket"
이제 xl.meta 파일을 받아옵니다.
docker cp kind-worker:/var/local-path-provisioner/pvc-0c903892-36ac-40bf-aabb-32f6efa6bfaf_minio-tenant_1-myminio-pool-0-0/test-bucket/test.txt/xl.meta .
xl-meta 를 사용하면 메타데이터 파일의 내용을 json 형태로 볼 수 있고, --data 옵션을 사용하면 base64로 인코딩된 실제 데이터를 볼 수 있습니다. 앞의 minio 아키텍처와 핵심정리에서 나온 내용 대부분이 나와 좀 이해가 되기도 했습니다. Part는 여기서는 단일 파트로만 이뤄줬는데,아마 multipart로 업로드 했을 경우 파트가 나눠질 것으로 보입니다. 아래는 메타데이터 중 의미있는 몇개 데이터만 정리해봤습니다.
$ xl-meta --export
{
"Versions": [
{
"Header": {
"EcM": 6,
"EcN": 2,
"Flags": 6,
"ModTime": "2025-09-12T02:45:14.531206152+09:00",
"Signature": "c5313dfb",
"Type": 1,
"VersionID": "00000000000000000000000000000000"
},
"Idx": 0,
"Metadata": {
"Type": 1,
"V2Obj": {
"CSumAlgo": 1,
"DDir": "z8yLlyKxSnaPjfPKqCsAcA==",
"EcAlgo": 1,
"EcBSize": 1048576,
"EcDist": [
6,
7,
8,
1,
2,
3,
4,
5
],
"EcIndex": 2,
"EcM": 6,
"EcN": 2,
"ID": "AAAAAAAAAAAAAAAAAAAAAA==",
"MTime": 1757612714531206152,
"MetaSys": {
"x-minio-internal-inline-data": "dHJ1ZQ=="
},
"MetaUsr": {
"content-type": "text/plain",
"etag": "ae3977a0ed8736953c6ce5e5c03b6ca7"
},
"PartASizes": [
351
],
"PartETags": null,
"PartNums": [
1
],
"PartSizes": [
351
],
"Size": 351
},
"v": 1744126884
}
}
]
}
$ xl-meta --data
{
"null": {
"bitrot_valid": true,
"bytes": 91,
"data_base64": "8J+fqSA2CvCfn6nwn5+pIDcK8J+fqfCfn6kgOArwn5+p8J+fqSA5CvCfn6nwn5+pIDEwCvCfn6nwn58="
}
$ echo -n "8J+fqSA2CvCfn6nwn5+pIDcK8J+fqfCfn6kgOArwn5+p8J+fqSA5CvCfn6nwn5+pIDEwCvCfn6nwn58=" | base64 -d
🟩 6
🟩🟩 7
🟩🟩 8
🟩🟩 9
🟩🟩 10
🟩
MinIO 아키텍처와 핵심 개념 정리 (0) | 2025.09.13 |
---|---|
MinIO 오브젝트 스토리지 에센셜 강의 자료 정리 (0) | 2025.09.11 |
Cilium Policy Audit Mode로 네트워크 정책 만들기 (0) | 2025.09.06 |