Jellyfin_Storage
Jellyfin 미디어 스토리지 구조 변경 정리 (RWX → RWO 전환)
🧾 목적
- Jellyfin의 미디어 저장소를 외장하드를 기반으로 안정적으로 운영하기 위해, 기존 NFS(RWX) 방식에서 Longhorn 기반 RWO 방식으로 구조를 변경함
- Git + Argo CD + Helm으로 배포되는 홈 미디어 서버 인프라의 일환으로 작업함
- 향후에도 이 구조를 유지하고 실수를 반복하지 않도록 문서화함
✅ 작업 배경 및 원인
- 초기에는 여러 Pod에서 동시에 읽고 쓸 수 있도록 하기 위해 RWX 모드(NFS 서버 + PVC) 구조를 시도했음
- 그러나 Helm으로 배포된
nfs-server-provisioner가 자동으로 PVC를 만들면서 수동 생성한 Longhorn PVC와 충돌함 - 또한 PVC 요청 용량이 실제 볼륨보다 커서 바인딩 자체가 실패했음
- 문제를 반복적으로 해결하려 했지만, 결국 안정성을 위해 RWX 대신 RWO 기반 단일 Pod 전용 구조로 변경 결정
✅ 작업 순서 요약
- 기존 NFS 기반 RWX 구성 리소스 삭제 시도 (kubectl)
- Argo CD가 리소스를 다시 살려냄 → Git에서 Helm values 삭제 + Argo CD Sync로 완전 제거
- Longhorn UI에서 외장하드 디스크에
jellyfin-disk태그 부여 - PVC 요청 크기를 470Gi로 줄이고 Longhorn Volume 새로 생성
- PVC를
jellyfin-media-pvc로 정의하고 수동으로 apply - Helm
values.yaml에서 PVC를 참조하도록 설정 변경 - Git 커밋 후 Argo CD Sync로 적용
- 파일 업로드 테스트 (scp, debug pod, UI 업로더 등)
🧹 삭제 및 수정한 항목
❌ 삭제 대상
kubectl delete pod home-media-nfs-server-provisioner-0 -n media # → 삭제했지만 다시 살아남
kubectl delete pvc nfs-media-pvc -n media
kubectl delete pvc media-longhorn-pvc -n media # (선택적으로)
✅ Argo CD 기준으로 실제 제거한 방법
apps/home-media-server/values.yaml에서nfs-server-provisioner항목 제거- Git add/commit/push
- Argo CD UI →
home-media-server앱 → Sync → 'Prune' 체크 → Sync 실행 - 이후 관련 Pod, PVC, Service, StatefulSet 등 완전히 제거됨
✅ 최종 구조 (RWO 기반 Jellyfin 스토리지)
💾 스토리지 흐름
[외장하드 (/mnt/longhorn-disk)]
└─ Longhorn 디스크 (tag: jellyfin-disk)
└─ Volume: jellyfin-media-volume (470Gi)
└─ PVC: jellyfin-media-pvc
└─ Jellyfin Pod의 /media 에 마운트됨
⚙️ PVC YAML (base/jellyfin-media-pvc.yaml)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jellyfin-media-pvc
namespace: media
annotations:
purpose: "jellyfin-only" # jellyfin 전용임을 명시
spec:
accessModes:
- ReadWriteOnce # 외장하드는 RWO만 가능
storageClassName: longhorn
resources:
requests:
storage: 470Gi # 외장하드 용량 고려해서 470Gi로 제한
🧩 Helm values.yaml (apps/home-media-server/values.yaml)
persistence:
config:
enabled: true
existingClaim: jellyfin-config-pvc
media:
enabled: true
existingClaim: jellyfin-media-pvc
🧠 디스크 태그 설정 (Longhorn UI)
- 위치:
Nodes → oldbear → /mnt/longhorn-disk → Edit - Tag 입력:
jellyfin-disk - 효과: 이 디스크는
jellyfin-disk태그가 설정된 볼륨만 저장 가능 → jellyfin 전용 보장
📂 파일 업로드 방법 정리
📌 방법 1: Debug Pod (PVC 마운트된 쉘)
kubectl run -n media jellyfin-media-debug --rm -it --image=ubuntu --overrides='...'
📌 방법 2: 외부 PC → oldbear 노드 직접 접근
scp ~/Videos/Movie.mp4 igotoo@192.168.0.100:/mnt/longhorn-disk/pvc-xxx/data/
⚠️ 주의: 이 방식은 Longhorn 내부 디렉토리(/mnt/.../replicas/...)에 직접 접근하는 것으로, 권장되지 않으며 향후 데이터 무결성에 문제가 생길 수 있음.
→ 이 경로는 Longhorn의 internal replica 영역으로 Jellyfin이 인식하지 않음.
→ 반드시 Pod 내 /media 경로를 사용하거나 Upload API를 통해 복사해야 안전함.
📌 방법 3: 웹 업로더 준비 중
/uploadAPI 제공하는 Node.js 서버(Express)- Jellyfin ingress에
/upload경로로 붙여서 클라이언트에서 직접 업로드 가능 - React UI 앱을 통해 파일 선택 + 전송 예정
🧨 과거 실수 & 교훈
| 실수 | 교훈 |
|---|---|
| RWX PVC 요청 용량이 실제 Longhorn 볼륨보다 큼 | PVC 요청은 항상 실제 볼륨보다 작아야 함 |
| Helm Chart가 StatefulSet과 PVC 자동 생성 | 수동 PVC와 충돌할 수 있음, chart 구조 미리 검토할 것 |
| kubectl delete만 하면 되는 줄 앎 | GitOps 환경에선 반드시 Git 수정 + Argo CD Sync 필요 |
| 디스크 Tag 미지정으로 외장하드에 다른 볼륨 올라감 | 외장하드는 tag로 독점 제한 필요 |
| Longhorn replica 디렉토리에 직접 scp 복사 시도 | 해당 영역은 raw volume 이미지 저장소로 직접 접근 불가. 반드시 Pod 내부 경로 사용 |
🔚 결론
- Jellyfin은 현재 RWO PVC로 외장하드 단독 사용 중이며 매우 안정적
- 과거 RWX 구성은 실패 이유까지 명확히 파악하고 제거함
- upload-server 배포 준비도 GitOps 환경에 맞춰 Helm chart 형태로 설계 중
- 홈 미디어 서버 스토리지 구성은 완전히 통제 가능 상태에 도달함