중소기업 쿠버네티스 구축 – 08. 오브젝트 스토리지: S3 호환 구축 (MinIO·TrueNAS NFS·PV/PVC)

2026.07.30

·

시리즈 안내
이 글은 “중소기업 쿠버네티스 구축” 시리즈의 8편입니다. 7편에서 GitOps 배포 자동화를 갖췄습니다. 이번 편에서는 실제 인프라 서비스 — S3 호환 오브젝트 스토리지 MinIO — 를 TrueNAS NFS 백엔드로 구축합니다. 파일·이미지·백업 같은 비정형 데이터를 담을 저장소를 만듭니다.


들어가며

웹 서비스를 운영하면 정형 데이터(DB)만으로는 부족합니다. 사용자가 올린 이미지, 첨부 파일, 백업 아카이브 같은 비정형 데이터를 담을 곳이 필요하죠. 클라우드라면 AWS S3를 쓰겠지만, 자체 인프라에서는 MinIO가 그 역할을 합니다.

MinIO는 S3 API와 완전히 호환되는 오브젝트 스토리지입니다. 애플리케이션 입장에서는 S3에 연결하듯 MinIO에 연결하면 되니, 나중에 클라우드로 옮기거나 그 반대로 와도 코드를 거의 안 바꿔도 됩니다.

Sponsored

이번 편의 핵심 결정은 데이터를 어디에 둘 것인가입니다. 우리는 NAS(TrueNAS)의 NFS를 백엔드로 씁니다. 그 이유와 주의점을 함께 다룹니다.


1. 왜 NFS 백엔드인가

4편에서 local-path(로컬 디스크) 스토리지를 깔았는데, MinIO는 왜 NFS를 쓸까요?

local-path는 데이터가 특정 노드의 로컬 디스크에 묶입니다. 그 노드가 죽으면 데이터에 접근할 수 없죠. 캐시처럼 날아가도 되는 데이터라면 괜찮지만, 사용자 파일이나 백업처럼 잃으면 안 되는 데이터에는 위험합니다.

그래서 MinIO 데이터는 노드와 분리된 NAS의 NFS 공유에 둡니다.

  • MinIO Pod가 어느 노드에서 뜨든, 같은 NAS 경로를 바라봅니다.
  • Pod가 죽어 다른 노드에서 재시작돼도 데이터는 그대로입니다.
  • NAS 자체의 RAID·스냅샷으로 데이터를 보호할 수 있습니다.

우리 환경에서는 TrueNAS(10.0.31.30)가 NFS를 제공합니다.

트레이드오프: NFS는 노드 독립성과 데이터 보호를 주지만, 대신 NAS가 단일 장애점(SPOF)이 됩니다. NAS가 죽으면 MinIO도 멈춥니다. 그래서 NAS 자체의 RAID·UPS·스냅샷이 핵심 방어선이고, 정말 중요한 데이터는 NAS 외부로 한 부 더 백업하는 3-2-1 원칙을 따릅니다.


2. TrueNAS NFS 준비

먼저 NAS 쪽에서 NFS 공유가 준비돼 있어야 합니다. TrueNAS UI의 Sharing → Unix Shares (NFS)에서 다음을 확인합니다.

  • 공유 경로: MinIO 데이터를 저장할 경로 (예: /mnt/pool/k8s/minio)
  • Authorized Networks: 클러스터 노드 대역(10.0.12.0/24)만 접근 허용 — 보안상 좁히는 게 좋습니다.
  • Maproot user: 보통 root로 설정해야 Pod가 정상적으로 읽고 씁니다(no_root_squash 효과).

no_root_squash(maproot=root)는 편의를 주지만 보안상 느슨합니다. 반드시 접근 가능 네트워크를 노드 대역으로 좁혀 위험을 제한하세요.


3. 노드에서 NFS 마운트 검증

매니페스트를 적용하기 전에, 노드에서 실제로 NFS가 마운트되는지 먼저 확인하는 게 안전합니다. MinIO가 뜰 플랫폼 노드에서 테스트합니다. (2편에서 nfs-common을 이미 설치해뒀습니다.)

# 임시 마운트 테스트
sudo mkdir -p /mnt/nfs-test
sudo mount -t nfs 10.0.31.30:/mnt/pool/k8s/minio /mnt/nfs-test

# 쓰기 테스트
echo "test" | sudo tee /mnt/nfs-test/test.txt
cat /mnt/nfs-test/test.txt

# 정리
sudo rm /mnt/nfs-test/test.txt
sudo umount /mnt/nfs-test

쓰기·읽기가 되면 NFS 준비는 끝입니다. 여기서 막히면 NAS의 경로·권한·허용 네트워크를 다시 점검하세요. 이 검증을 건너뛰고 매니페스트부터 적용하면, MinIO Pod가 마운트 실패로 안 뜰 때 원인 찾기가 훨씬 어렵습니다.


4. 네임스페이스와 시크릿

MinIO용 네임스페이스를 만들고, 관리자 자격 증명을 시크릿으로 둡니다.

kubectl create namespace minio

루트 사용자/비밀번호를 시크릿으로 생성합니다. (값은 base64로 인코딩해 넣습니다.)

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: minio-creds
  namespace: minio
type: Opaque
stringData:
  rootUser: "myadmin"
  rootPassword: "<강력한_비밀번호>"
EOF

stringData를 쓰면 base64 인코딩을 쿠버네티스가 알아서 해주므로 평문으로 적을 수 있습니다. 실제 운영에서는 이 매니페스트를 Git에 평문으로 올리지 말고, Sealed Secrets나 외부 시크릿 관리 도구를 쓰는 게 안전합니다.


5. PV/PVC: NFS 연결

4편에서 배운 PV/PVC 개념을 여기서 NFS로 적용합니다. NFS 공유를 가리키는 PV를 만들고, MinIO가 쓸 PVC로 바인딩합니다.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  name: minio-data
  labels:
    app: minio
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Ti
  mountOptions:
    - nfsvers=4.2
    - hard
    - noatime
  nfs:
    path: /mnt/pool/k8s/minio
    server: 10.0.31.30
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: minio-data
  namespace: minio
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Ti
  storageClassName: ""
  volumeName: minio-data
EOF

마운트 옵션의 의미:

  • nfsvers=4.2: NFSv4 사용. 락·보안 동작이 v3보다 안정적입니다.
  • hard: NAS가 일시적으로 응답이 없으면 무한 재시도합니다. Pod가 잠시 IO 대기로 멈출 순 있어도 데이터 손상은 없습니다. (soft는 빠르게 실패하지만 데이터 손실 위험이 있어 비권장.)
  • noatime: 파일 접근 시각 기록을 생략해 불필요한 쓰기를 줄입니다.
  • persistentVolumeReclaimPolicy: Retain: PVC를 지워도 PV·데이터는 보존. 실수로 데이터가 날아가는 걸 막습니다.

바인딩을 확인합니다.

kubectl get pv minio-data
kubectl get pvc -n minio

PVC가 Bound 상태가 되면 성공입니다.


6. MinIO 배포

이제 MinIO 자체를 배포합니다. Deployment와 Service를 만듭니다.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: minio
  namespace: minio
spec:
  replicas: 1
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      nodeSelector:
        role: platform
      containers:
        - name: minio
          image: minio/minio:latest
          args: ["server", "/data", "--console-address", ":9001"]
          env:
            - name: MINIO_ROOT_USER
              valueFrom:
                secretKeyRef:
                  name: minio-creds
                  key: rootUser
            - name: MINIO_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: minio-creds
                  key: rootPassword
          ports:
            - containerPort: 9000   # S3 API
            - containerPort: 9001   # 웹 콘솔
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: minio-data
---
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: minio
spec:
  selector:
    app: minio
  ports:
    - name: api
      port: 9000
      targetPort: 9000
    - name: console
      port: 9001
      targetPort: 9001
EOF

포트가 두 개인 점에 주목하세요.

  • 9000 (S3 API): 애플리케이션이 데이터를 읽고 쓰는 포트
  • 9001 (콘솔): 사람이 브라우저로 관리하는 웹 UI

배포를 확인합니다.

kubectl get pods -n minio

Running이 되면 MinIO가 NFS 위에서 동작합니다.


7. Ingress로 외부 노출

5편 경로에 MinIO의 두 포트를 각각 도메인으로 노출합니다.

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minio-ingress
  namespace: minio
spec:
  ingressClassName: nginx
  rules:
    - host: s3.prd.dztechwill.com           # S3 API
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: minio
                port:
                  number: 9000
    - host: s3-console.prd.dztechwill.com   # 웹 콘솔
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: minio
                port:
                  number: 9001
EOF
  • s3.prd... → 애플리케이션이 연결하는 S3 엔드포인트
  • s3-console.prd... → 관리자가 접속하는 웹 콘솔

브라우저에서 https://s3-console.prd.dztechwill.com에 접속해, 4절에서 정한 루트 계정으로 로그인하면 MinIO 콘솔이 열립니다. 여기서 버킷을 만들고 파일을 올려볼 수 있습니다.


8. GitOps로 넘기기 (선택)

지금은 kubectl apply로 직접 배포했습니다. 7편에서 ArgoCD를 깔았으니, 이 MinIO 매니페스트들을 Git 레포에 올리고 ArgoCD Application으로 등록하면 GitOps 흐름에 편입됩니다.

실제 구축 때는 “MinIO를 손으로 배포할지, 처음부터 GitOps로 갈지” 갈림길이 있었습니다. 어느 쪽이든 정답은 아니고, 손에 익히는 순서의 문제입니다. 손으로 한 번 배포해 동작을 이해한 뒤 Git으로 옮기면, GitOps가 “마법”이 아니라 “같은 매니페스트를 Git이 적용해주는 것”임이 분명해집니다.


마치며

8편에서는 S3 호환 오브젝트 스토리지를 NFS 위에 구축했습니다.

  • MinIO: S3 API 호환이라 클라우드와 코드 호환성을 유지합니다.
  • NFS 백엔드: 데이터를 노드에서 분리해 Pod가 어디서 뜨든 같은 데이터를 봅니다. 대신 NAS가 SPOF이므로 RAID·스냅샷·외부 백업이 방어선입니다.
  • 마운트 검증 우선: 매니페스트 전에 노드에서 직접 마운트를 테스트해 디버깅 난이도를 낮췄습니다.
  • PV/PVC + 마운트 옵션: hard·Retain 등으로 데이터 안전을 우선했습니다.
  • 두 포트 노출: S3 API(9000)와 콘솔(9001)을 각각 도메인으로.

이제 데이터를 담을 곳까지 갖췄습니다. 인프라 레이어가 거의 완성됐습니다. 마지막 편에서는 그 위에 실제 애플리케이션 워크로드를 올리고, 클러스터 밖 레거시 시스템과 연동합니다. 09편 — 워크로드와 레거시 연동, 실전 트러블슈팅으로 시리즈를 마무리하겠습니다.


이전 글: 중소기업 쿠버네티스 구축 – 07. GitOps 배포 자동화: ArgoCD 도입과 판단
다음 글: 중소기업 쿠버네티스 구축 – 09. 워크로드와 레거시 연동: 실전 트러블슈팅 (Headless Service·SNAT·503 디버깅)


MORE POSTS

다른 글 보기

guniq 인사이트 썸네일 — 이론으로 읽는 PSTA – 03. P-S-T-A 4계층은 WBS인가, 에픽-스토리인가
프로젝트 관리

이론으로 읽는 PSTA – 03. P-S-T-A 4계층은 WBS인가, 에픽-스토리인가

2026.08.06
guniq 인사이트 썸네일 — 이론으로 읽는 PSTA – 02. 애자일이라는 렌즈 — 선언문·스크럼·칸반이 말하는 “덜어내야 할 것들”
프로젝트 관리

이론으로 읽는 PSTA – 02. 애자일이라는 렌즈 — 선언문·스크럼·칸반이 말하는 “덜어내야 할 것들”

2026.08.05
guniq 인사이트 썸네일 — 중소기업 쿠버네티스 구축 – 09. 워크로드와 레거시 연동: 실전 트러블슈팅 (Headless Service·SNAT·503 디버깅)
테크 랩

중소기업 쿠버네티스 구축 – 09. 워크로드와 레거시 연동: 실전 트러블슈팅 (Headless Service·SNAT·503 디버깅)

2026.08.04

프로젝트 문의 환영합니다

기획부터 개발, 운영까지 함께 만들어 드립니다.

무료 3분 자가진단

우리 회사, 자체 클라우드가 답일까?

AWS vs 자체 인프라 · 11개 항목 3분 체크