Files

834 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MinIO 예시
MinIO Operator + Tenant CRD (`minio.min.io/v2`) + KES + Vault transit 기준. 모든 manifest는 `kubectl apply` 적용 가능한 완전한 형태다.
---
## 좋은 예시 1: Namespace + Tenant configuration Secret
```yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: minio-prod
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
app.kubernetes.io/part-of: storage-platform
---
apiVersion: v1
kind: Secret
metadata:
name: minio-tenant-env
namespace: minio-prod
type: Opaque
stringData:
config.env: |
export MINIO_ROOT_USER="REPLACE_VIA_VSO"
export MINIO_ROOT_PASSWORD="REPLACE_VIA_VSO"
export MINIO_STORAGE_CLASS_STANDARD="EC:4"
export MINIO_STORAGE_CLASS_RRS="EC:2"
export MINIO_BROWSER_REDIRECT_URL="https://minio-console.internal.example.com"
export MINIO_SERVER_URL="https://s3.example.com"
export MINIO_IDENTITY_OPENID_CONFIG_URL="https://auth.example.com/realms/platform/.well-known/openid-configuration"
export MINIO_IDENTITY_OPENID_CLIENT_ID="minio"
export MINIO_IDENTITY_OPENID_CLAIM_NAME="policy"
export MINIO_IDENTITY_OPENID_SCOPES="openid,profile,email"
export MINIO_PROMETHEUS_AUTH_TYPE="jwt"
---
# 실 운영에서는 VSO가 이 Secret을 채움
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: minio-root-creds
namespace: minio-prod
spec:
vaultAuthRef: default
mount: kv
path: minio/prod/root
type: kv-v2
refreshAfter: 24h
destination:
name: minio-tenant-env
create: false
overwrite: true
transformation:
excludeRaw: true
templates:
config.env:
text: |
export MINIO_ROOT_USER="{{ .Secrets.username }}"
export MINIO_ROOT_PASSWORD="{{ .Secrets.password }}"
export MINIO_STORAGE_CLASS_STANDARD="EC:4"
export MINIO_BROWSER_REDIRECT_URL="https://minio-console.internal.example.com"
export MINIO_SERVER_URL="https://s3.example.com"
export MINIO_IDENTITY_OPENID_CONFIG_URL="https://auth.example.com/realms/platform/.well-known/openid-configuration"
export MINIO_IDENTITY_OPENID_CLIENT_ID="minio"
export MINIO_IDENTITY_OPENID_CLIENT_SECRET="{{ .Secrets.oidc_client_secret }}"
export MINIO_IDENTITY_OPENID_CLAIM_NAME="policy"
export MINIO_IDENTITY_OPENID_SCOPES="openid,profile,email"
export MINIO_PROMETHEUS_AUTH_TYPE="jwt"
```
**왜 좋은가:**
- Tenant configuration은 **shell-source 형식**(`export KEY=VALUE`) Secret으로 전달 (Operator 규약)
- Root credential을 Vault KV에서 VSO가 주입 — Git에 평문 없음
- OIDC 통합 (Keycloak), storage class EC:4, Prometheus JWT auth 한 파일에 고정
- `MINIO_SERVER_URL`로 외부 S3 endpoint 명시 (presigned URL 생성 시 사용)
---
## 좋은 예시 2: Tenant CR — 4 server × 4 volume + KES + TLS
```yaml
apiVersion: minio.min.io/v2
kind: Tenant
metadata:
name: minio
namespace: minio-prod
labels:
app.kubernetes.io/name: minio
app.kubernetes.io/instance: minio-prod
app.kubernetes.io/part-of: storage-platform
app.kubernetes.io/managed-by: minio-operator
annotations:
prometheus.io/path: /minio/v2/metrics/cluster
prometheus.io/port: "9000"
prometheus.io/scrape: "true"
spec:
image: quay.io/minio/minio:RELEASE.2025-01-20T14-49-07Z
imagePullPolicy: IfNotPresent
mountPath: /export
configuration:
name: minio-tenant-env
requestAutoCert: true
certConfig:
commonName: minio.minio-prod.svc.cluster.local
organizationName:
- example.com
dnsNames:
- minio.minio-prod.svc.cluster.local
- "*.minio-hl.minio-prod.svc.cluster.local"
- s3.example.com
pools:
- name: pool-0
servers: 4
volumesPerServer: 4
volumeClaimTemplate:
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi
storageClassName: local-xfs-retain
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: "4"
memory: 8Gi
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
containerSecurityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
v1.min.io/tenant: minio
v1.min.io/pool: pool-0
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
v1.min.io/tenant: minio
tolerations:
- key: storage
operator: Equal
value: dedicated
effect: NoSchedule
features:
bucketDNS: false
domains:
console: https://minio-console.internal.example.com
minio:
- https://s3.example.com
kes:
image: quay.io/minio/kes:2025-01-16T16-24-39Z
replicas: 2
kesSecret:
name: kes-configuration
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
fsGroup: 1000
containerSecurityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
prometheusOperator: true
podManagementPolicy: Parallel
exposeServices:
minio: true
console: false
logging:
anonymous: false
json: true
quiet: false
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: minio
namespace: minio-prod
spec:
minAvailable: 3
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
v1.min.io/tenant: minio
```
**왜 좋은가:**
- `servers × volumesPerServer = 4 × 4 = 16` drive → erasure coding 최소 요건 충족, `EC:4` 기본 parity (4 drive 장애 허용)
- `requestAutoCert: true` + `certConfig.dnsNames`로 Operator가 API/Console TLS 자동 발급
- `podAntiAffinity` hostname required → 한 node에 MinIO pod 복수 배치 금지 (EC 의미 보존)
- KES가 별도 2 replica로 사이드카 없이 Deployment로 분리 (Tenant CR에서 관리됨)
- `exposeServices.console: false` → Console은 Tenant Service에서 Ingress로 별도 처리만 허용
- `minAvailable: 3` → 4 server 중 1 동시 drain까지 허용 (write quorum 보존)
---
## 나쁜 예시 1: 단일 Deployment로 MinIO
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
spec:
replicas: 1
template:
spec:
containers:
- name: minio
image: minio/minio
args: ["server", "/data"]
env:
- name: MINIO_ROOT_USER
value: minioadmin
- name: MINIO_ROOT_PASSWORD
value: minioadmin
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
```
**문제:**
- Single-drive MinIO → erasure coding 없음, 1 drive 장애 = 전체 data loss
- Deployment = 재시작 시 PVC binding 보장 없음, 복수 replica 시 동일 volume 충돌
- emptyDir → pod 재시작 시 모든 object 사라짐
- 기본 `minioadmin/minioadmin` credential → 공개 인터넷 스캐너가 수 분 내 탈취
- Operator + Tenant가 자동화하는 인증서, 서비스, headless, auto-restart를 전부 수제로 다시 만들어야 함
---
## 좋은 예시 3: KES configuration + Vault transit
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: kes-configuration
namespace: minio-prod
type: Opaque
stringData:
server-config.yaml: |
version: v1
address: 0.0.0.0:7373
admin:
identity: disabled
tls:
key: /tmp/kes/server.key
cert: /tmp/kes/server.cert
policy:
minio-app:
allow:
- /v1/key/create/minio-*
- /v1/key/generate/minio-*
- /v1/key/decrypt/minio-*
- /v1/key/bulk/decrypt/minio-*
- /v1/key/list/minio-*
- /v1/status
- /v1/metrics
- /v1/api
identities:
- ${MINIO_KES_IDENTITY}
keystore:
vault:
endpoint: https://vault.vault.svc.cluster.local:8200
engine: transit
version: v1
namespace: ""
prefix: minio
approle:
id: ${VAULT_APPROLE_ID}
secret: ${VAULT_APPROLE_SECRET}
retry: 15s
tls:
ca: /tmp/kes/vault-ca.crt
status:
ping: 10s
---
# Vault AppRole credential은 VSO 또는 별도 Secret으로 주입
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: kes-vault-approle
namespace: minio-prod
spec:
vaultAuthRef: default
mount: kv
path: minio/kes/approle
type: kv-v2
refreshAfter: 24h
destination:
name: kes-vault-approle
create: true
overwrite: true
```
그리고 bucket에 SSE-KMS 적용:
```bash
mc alias set minio https://s3.example.com $ROOT_USER $ROOT_PASS
# Vault transit에 key 생성
mc admin kms key create minio minio-critical
# bucket에 SSE-KMS 기본 적용
mc encrypt set sse-kms minio-critical minio/critical-bucket
```
**왜 좋은가:**
- KES가 Vault transit을 key store로 사용 → master key는 Vault가 관리, MinIO는 DEK만 캐시
- KES policy로 `minio-*` prefix key만 access 허용 (최소 권한)
- AppRole credential은 Vault → VSO → Secret 경로
- bucket level SSE-KMS → 업로드되는 모든 object가 per-object DEK로 자동 암호화
---
## 나쁜 예시 2: KES 없이 평문 저장
```yaml
# Tenant CR
spec:
kes: {} # 미설정
# ... SSE 설정 없음
```
```bash
mc cp secret.pdf minio/bucket/secret.pdf
# object가 disk에 평문 저장
```
**문제:**
- PVC가 탈취되거나 물리 drive가 반출되면 평문 유출
- 감사/규제 요구(GDPR, PCI-DSS, ISO 27001) 위반
- SSE-S3를 대신 쓰더라도 master key가 MinIO 자체에 있어 키 라이프사이클 관리 불가
---
## 좋은 예시 4: Probe — live + cluster-read
Tenant CR이 자동으로 probe를 구성하지만, 커스텀 오버라이드가 필요할 때:
```yaml
spec:
pools:
- name: pool-0
# ...
containers:
- name: minio
livenessProbe:
httpGet:
path: /minio/health/live
port: 9000
scheme: HTTPS
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /minio/health/cluster/read
port: 9000
scheme: HTTPS
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /minio/health/live
port: 9000
scheme: HTTPS
periodSeconds: 5
failureThreshold: 60
timeoutSeconds: 5
```
**왜 좋은가:**
- `readinessProbe``/minio/health/cluster/read`**read quorum** 검사. rolling update 중에도 read가 가능하면 Service에 남아있음
- `/minio/health/cluster` (write quorum)을 readiness로 쓰면 rolling 재시작 시 pod가 전부 빠져 완전 unavailable
- `livenessProbe`는 단순 프로세스 생존만 확인 → 일시적 quorum 상실로 pod 강제 재시작 방지
- HTTPS scheme (requestAutoCert과 일치)
---
## 나쁜 예시 3: readiness를 write quorum으로
```yaml
readinessProbe:
httpGet:
path: /minio/health/cluster
port: 9000
periodSeconds: 5
failureThreshold: 1
```
**문제:**
- rolling update로 pod 1개를 재시작하면 write quorum이 일시적으로 무너져 살아있는 pod들도 NotReady
- Service가 endpoint를 전부 제거 → **읽기도 불가능**
- `failureThreshold: 1` + 5초 주기 → 한 번 느린 응답으로 pod 빠짐
---
## 좋은 예시 5: Ingress — API는 공개, Console은 내부
```yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-api
namespace: minio-prod
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/backend-protocol: HTTPS
spec:
ingressClassName: nginx
tls:
- hosts:
- s3.example.com
secretName: minio-api-ingress-tls
rules:
- host: s3.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio
port:
number: 443
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-console
namespace: minio-prod
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"
nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
nginx.ingress.kubernetes.io/backend-protocol: HTTPS
spec:
ingressClassName: nginx-internal
tls:
- hosts:
- minio-console.internal.example.com
secretName: minio-console-ingress-tls
rules:
- host: minio-console.internal.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio-console
port:
number: 9443
```
**왜 좋은가:**
- API Ingress는 `proxy-body-size: 0` + request/response buffering off → 대용량 multipart upload 지원
- Console은 내부 ingress class + IP whitelist + OIDC forward-auth 2중 보호
- `backend-protocol: HTTPS` → MinIO의 auto-cert TLS를 TLS passthrough 형태로 전달 (인증서 SAN 보존)
---
## 나쁜 예시 4: Console 외부 공개
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minio-all
spec:
rules:
- host: minio.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: minio
port:
number: 9090
```
**문제:**
- Console이 인터넷에 그대로 노출 → root/admin credential brute force 표면 확장
- OIDC forward-auth 없음 → 기본 login 페이지가 공격자에게 노출
- IP 제한 없음
- Bucket 목록, access key 관리, 사용자 관리가 모두 공개 domain에 위치
---
## 좋은 예시 6: ServiceMonitor (Prometheus bearer-token)
먼저 MinIO 내부에서 scrape token 발급:
```bash
mc admin prometheus generate minio cluster
# 출력에 bearer token과 scrape config가 나옴
```
그 결과 token을 Secret로 저장:
```yaml
---
apiVersion: v1
kind: Secret
metadata:
name: minio-prometheus-token
namespace: minio-prod
type: Opaque
stringData:
token: "<minio-prometheus-token>"
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: minio
namespace: minio-prod
labels:
app.kubernetes.io/name: minio
release: kube-prometheus-stack
spec:
selector:
matchLabels:
v1.min.io/tenant: minio
endpoints:
- port: https-minio
scheme: https
path: /minio/v2/metrics/cluster
interval: 30s
scrapeTimeout: 10s
bearerTokenSecret:
name: minio-prometheus-token
key: token
tlsConfig:
insecureSkipVerify: false
ca:
secret:
name: minio-tls
key: ca.crt
serverName: minio.minio-prod.svc.cluster.local
```
**왜 좋은가:**
- `MINIO_PROMETHEUS_AUTH_TYPE=jwt` 와 매칭 (기본값)
- `/minio/v2/metrics/cluster`는 cluster-wide view (replication lag, bucket 사용량, API latency)
- TLS 검증 유지 (`insecureSkipVerify: false`, CA bundle 제공)
- 외부 노출 없이 내부 scrape만
---
## 좋은 예시 7: Bucket 초기화 (Job) — versioning + Object Lock + lifecycle
```yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: minio-bootstrap
namespace: minio-prod
data:
init.sh: |
#!/bin/sh
set -eu
mc --config-dir /tmp/mc alias import minio <<EOF
{
"url": "https://minio.minio-prod.svc.cluster.local",
"accessKey": "$MINIO_ROOT_USER",
"secretKey": "$MINIO_ROOT_PASSWORD",
"api": "s3v4",
"path": "auto"
}
EOF
# Object Lock은 bucket 생성 시점에만 활성화 가능
mc --config-dir /tmp/mc mb --with-lock minio/critical-audit || true
mc --config-dir /tmp/mc retention set --default COMPLIANCE 2555d minio/critical-audit
# Versioning + lifecycle
mc --config-dir /tmp/mc mb minio/app-data || true
mc --config-dir /tmp/mc version enable minio/app-data
mc --config-dir /tmp/mc ilm add --expire-noncurrent-days 90 minio/app-data
mc --config-dir /tmp/mc ilm add --expire-incomplete-upload-days 7 minio/app-data
# SSE-KMS 기본 적용
mc --config-dir /tmp/mc encrypt set sse-kms minio-app-key minio/app-data
mc --config-dir /tmp/mc encrypt set sse-kms minio-critical-key minio/critical-audit
echo "bootstrap complete"
---
apiVersion: batch/v1
kind: Job
metadata:
name: minio-bootstrap
namespace: minio-prod
spec:
backoffLimit: 3
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
serviceAccountName: minio-bootstrap
containers:
- name: mc
image: quay.io/minio/mc:RELEASE.2025-01-17T23-25-50Z
command: ["/bin/sh", "/scripts/init.sh"]
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { memory: 256Mi }
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-root-creds
key: username
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-root-creds
key: password
volumeMounts:
- name: scripts
mountPath: /scripts
- name: tmp
mountPath: /tmp
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumes:
- name: scripts
configMap:
name: minio-bootstrap
defaultMode: 0755
items:
- key: init.sh
path: init.sh
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 64Mi
```
**왜 좋은가:**
- `mc mb --with-lock`은 bucket 생성 시점에만 Object Lock 활성화 가능 — Job이 그 타이밍을 보장
- COMPLIANCE 모드 7년 retention = 감사/규제 요구 충족 (root도 bypass 불가)
- `app-data` bucket은 versioning + lifecycle (90일 noncurrent expire + 7일 incomplete abort)
- 애플리케이션 access key 발급은 별도 bootstrap에서 inline 최소 권한 policy와
함께 수행하고, 생성 secret은 Vault로 직접 전달
- `backoffLimit: 3` + idempotent 명령 (`|| true`) → 재실행 안전
---
## 나쁜 예시 5: mc mirror만으로 DR
```bash
# 매일 자정 crontab
mc mirror minio/critical remote-minio/critical-backup
```
**문제:**
- `mc mirror`**현재 object만** 동기화 — 버전 히스토리 유실
- Object Lock 상태, bucket policy, IAM 설정 미복제
- 메타데이터 중 일부(tag, legal hold) 누락
- RPO = 1일 (하루 단위 손실), replication은 async ms 단위 RPO
- DR 연습(resync 절차) 불가
대안: `mc admin replicate add` (site replication, IAM + bucket + object 전부 async 동기화).
---
## 좋은 예시 8: Bucket replication
```bash
# source alias 설정
mc alias set source https://minio.minio-prod.svc.cluster.local $SRC_USER $SRC_PASS
mc alias set target https://minio.minio-dr.svc.cluster.local $TGT_USER $TGT_PASS
# target에 replication 전용 user + policy
mc admin policy create target replication-target /policies/replication.json
mc admin user add target replication-bot $(openssl rand -hex 16)
mc admin policy attach target replication-target --user replication-bot
# source에서 remote target 등록
mc replicate add source/app-data \
--remote-bucket https://replication-bot:PASS@minio.minio-dr.svc.cluster.local/app-data \
--replicate "delete,delete-marker,existing-objects,metadata-sync" \
--priority 1
```
`replication.json`:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketVersioning",
"s3:PutBucketVersioning",
"s3:GetReplicationConfiguration",
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
"s3:GetObjectVersion",
"s3:GetObjectVersionTagging",
"s3:GetObjectVersionForReplication"
],
"Resource": ["arn:aws:s3:::app-data/*", "arn:aws:s3:::app-data"]
}
]
}
```
**왜 좋은가:**
- `existing-objects` 옵션으로 기존 데이터 backfill
- `delete` + `delete-marker`로 삭제도 복제 (true mirror)
- 전용 replication user + 최소 권한 policy
- async 복제, bucket versioning 전제
---
## 좋은 예시 9: Keycloak OIDC STS 로그인
Keycloak에 `minio` client 생성 후:
```bash
# 앱에서 JWT를 받은 다음
curl -X POST https://s3.example.com/ \
-d "Action=AssumeRoleWithWebIdentity" \
-d "Version=2011-06-15" \
-d "WebIdentityToken=${KEYCLOAK_ID_TOKEN}" \
-d "DurationSeconds=3600"
```
응답의 `AccessKeyId`, `SecretAccessKey`, `SessionToken`을 S3 SDK에 주입.
```bash
# AWS CLI 예시
aws configure set aws_access_key_id "$STS_ACCESS_KEY"
aws configure set aws_secret_access_key "$STS_SECRET_KEY"
aws configure set aws_session_token "$STS_SESSION_TOKEN"
aws s3 ls s3://app-data/ --endpoint-url https://s3.example.com
```
**왜 좋은가:**
- 앱/사용자는 Keycloak에 로그인만 하면 됨 — MinIO에 user 등록 불필요
- 임시 credential (1시간 TTL) → 유출 시 피해 제한
- JWT의 `policy` claim이 MinIO 정책과 자동 매핑
- 장기 access key 배포 없음