--- title: NAS StorageClass (NFS + iSCSI) updated: 2026-04-10 tags: [infra, k3s, storage, nfs, iscsi, synology] --- ## 개요 Synology NAS를 K3s NFS StorageClass로 사용. Longhorn(블록)과 병행하여 파일 저장소, 웹소스 등 대용량/RWX 워크로드에 사용. ## NAS 정보 | 항목 | 값 | |------|-----| | 장비 | Synology NAS | | IP (kr1/kr2) | 192.168.205.100 | | IP (hp2) | 192.168.9.100 (현재 미연결, 해결 예정) | | 디스크 | 11TB (사용 2%) | | NFS export | `/volume1/k3s-nfs` | | NFS 옵션 | `rw,async,no_wdelay,crossmnt,no_root_squash,insecure_locks,sec=sys,anonuid=1025,anongid=100` | ## K3s 설정 | 항목 | 값 | |------|-----| | StorageClass | `nfs` (default 아님, 명시 지정 필요) | | Provisioner | nfs-subdir-external-provisioner (Helm) | | Namespace | `nfs-provisioner` | | NFS 경로 | `/volume1/k3s-nfs` | | 마운트 옵션 | `soft,timeo=50,retrans=3` | | archiveOnDelete | true (PVC 삭제 시 데이터 archived- 접두사로 보존) | | nodeAffinity | incus-kr1, incus-kr2만 (hp2 NAS 연결 해결 전까지) | ### Helm 설치 명령 ```bash export KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ --namespace nfs-provisioner --create-namespace \ --set nfs.server=192.168.205.100 \ --set nfs.path=/volume1/k3s-nfs \ --set storageClass.name=nfs \ --set storageClass.defaultClass=false \ --set storageClass.reclaimPolicy=Delete \ --set storageClass.archiveOnDelete=true \ --set nfs.mountOptions[0]=soft \ --set nfs.mountOptions[1]=timeo=50 \ --set nfs.mountOptions[2]=retrans=3 ``` ### PVC 사용 예시 ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-data spec: storageClassName: nfs accessModes: [ReadWriteMany] resources: requests: storage: 10Gi ``` ## 성능 (fio 벤치마크, 2026-04-05) | 테스트 | IOPS | 대역폭 | 레이턴시 | |--------|------|--------|---------| | Random Read 4K | 20.6K | 84 MB/s | 1.5 ms | | Random Write 4K | 18.1K | 74 MB/s | 1.8 ms | | Seq Read 1M | 263 | 276 MB/s | 30 ms | | Seq Write 1M | 275 | 288 MB/s | 29 ms | ## 용도별 StorageClass 선택 | 용도 | StorageClass | 이유 | |------|-------------|------| | DB (PostgreSQL, etcd, Redis) | longhorn | 저레이턴시 블록 필요 | | 파일 업로드, 사용자 데이터 | nfs | 대용량, RWX 지원 | | 웹소스, 정적 파일 | nfs | 대용량, 여러 Pod 공유 | | 로그, 임시 데이터 | longhorn/local-path | 빠른 쓰기 | ## 파일 소유권 - `no_root_squash` 설정으로 root 컨테이너는 소유권 문제 없음 - 비-root 컨테이너는 Pod `securityContext.fsGroup`으로 제어 ## NFS hard vs soft - **hard**: NAS 끊기면 무한 대기 → 서버 먹통 - **soft**: 타임아웃 후 에러 반환 → 서버 생존 모든 NFS 마운트는 `soft,timeo=50,retrans=3` 필수. 인시던트 이력: [[../history/2026-04-04-usb-25g-hang|history]] ## iSCSI StorageClass (democratic-csi) Synology NAS의 iSCSI를 K3s 블록 스토리지로 사용. democratic-csi가 PVC 생성/삭제 시 자동으로 iSCSI Target + LUN을 관리. | 항목 | 값 | |------|-----| | StorageClass | `synology-iscsi` | | CSI Driver | democratic-csi (Helm) | | Namespace | `democratic-csi` | | iSCSI Portal | 192.168.205.100:3260 | | Base IQN | `iqn.2000-01.com.synology:NAS.k3s.` | | LUN 타입 | BLUN (Btrfs thin provisioning) | | Volume | /volume1 | | 인증 | kaffa 계정 (HTTPS API) | | 자동 관리 | PVC 생성 → Target+LUN 생성, PVC 삭제 → Target+LUN 삭제 | ### Helm 설치 values 파일: `/tmp/democratic-csi-values.yaml` (kr1) ```bash export KUBECONFIG=/etc/rancher/k3s/k3s.yaml helm repo add democratic-csi https://democratic-csi.github.io/charts/ helm install synology-iscsi democratic-csi/democratic-csi \ --namespace democratic-csi --create-namespace \ -f /tmp/democratic-csi-values.yaml ``` ### 전체 StorageClass 요약 | StorageClass | 방식 | 용도 | HA | |---|---|---|---| | longhorn | 로컬 NVMe 블록 | DB, 고성능 블록 | 노드 간 레플리카 | | synology-iscsi | NAS iSCSI 블록 | 블록 스토리지 (NAS) | RAID5 | | nfs | NAS NFS 파일 | 파일, 웹소스, RWX | RAID5 | | local-path | 로컬 디스크 | 캐시, 임시 | 없음 | ## Jumbo Frame (MTU 9000) 설정 (2026-04-05) 모든 NAS 전용 경로에 MTU 9000 적용. 스위치 포함 전체 경로가 JF 지원. | 장비 | 인터페이스 | MTU | 설정 위치 | |------|-----------|-----|----------| | kr1 | enp7s0 (PCIe 2.5GbE) | 9000 | `/etc/systemd/network/20-enp7s0.network` | | kr2 | enx803f5dd34c9f (USB 2.5GbE) | 9000 | `/etc/systemd/network/30-usb-2g5.network` | | NAS | eth2 (2.5GbE) | 9000 | Synology DSM 네트워크 설정 | ### kr2 USB NIC 드라이버 (r8152 DKMS) kr2의 USB 2.5GbE 어댑터(Realtek RTL8157, 0bda:8157)는 커널 기본 `cdc_ncm` 드라이버로 잡히면 Half Duplex + MTU 1500 제한. DKMS r8152 드라이버(v2.21.4)를 설치하여 Full Duplex + Jumbo Frame 지원. - DKMS 패키지: `linux-headers-$(uname -r)` 설치 시 자동 빌드 - udev 규칙: `/etc/udev/rules.d/50-usb-realtek-net.rules` — USB config를 1로 설정하여 r8152가 바인딩 - 커널 업데이트 시 DKMS가 자동으로 재빌드 ### 주의사항 - Jumbo Frame은 경로 전체(NIC → 스위치 → NIC)가 지원해야 함. 미지원 스위치가 중간에 있으면 프레임 드롭 - `ping -M do -s 8972` 로 end-to-end JF 동작 확인 가능 ## DSM Reverse Proxy — `nas.inouter.com` LAN 전용으로 Synology DSM 웹 UI 접근. `nas.inouter.com` 은 `*.inouter.com` 와일드카드 폴백으로 `k3s.inouter.com (192.168.9.53, Traefik MetalLB VIP)` 로 해석되고, Traefik 에서 이 호스트를 Synology NAS 로 reverse proxy 한다. | 항목 | 값 | |---|---| | Namespace | `lan-proxies` (신규 — LAN-only 리버스 프록시 전용) | | Service | `nas` (selector 없는 ClusterIP) + EndpointSlice → `192.168.9.100:5000` | | Backend | Synology DSM HTTP (포트 5000, 평문) | | Traefik IngressRoute | `nas` (web entrypoint, redirect → https), `nas-tls` (websecure entrypoint) | | Middleware | `redirect-https` (`redirectScheme.scheme=https permanent=true`) | | TLS | 기존 wildcard `wildcard-inouter-tls` (cert-manager + emberstack reflector 자동 복제) | | 외부 노출 | LAN-only — 와일드카드 `*.inouter.com` 가 사설 IP `192.168.9.53` 로 폴백되므로 인터넷 라우팅 없음 | ### ExternalName 대신 Service+EndpointSlice 사용 이유 요구사항은 "ExternalName Service" 였으나 Kubernetes ExternalName 은 RFC1123 DNS 호스트네임만 허용하고 IP(`192.168.9.100`)는 거부. 동등한 K8s-native 패턴인 **selector-less Service + 수동 EndpointSlice** 로 구현. CoreDNS 가 ClusterIP 를 정상 발행하고 kube-proxy 가 EndpointSlice 의 외부 IP 로 분산. ### 매니페스트 원본: `/tmp/nas-reverse-proxy.yaml` (heimdall). 핵심 발췌 — 전체 6 객체. ```yaml apiVersion: v1 kind: Namespace metadata: name: lan-proxies labels: purpose: lan-only-reverse-proxy --- apiVersion: v1 kind: Service metadata: { name: nas, namespace: lan-proxies } spec: type: ClusterIP ports: - { name: dsm-http, port: 5000, targetPort: 5000, protocol: TCP } --- apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: nas namespace: lan-proxies labels: { kubernetes.io/service-name: nas } addressType: IPv4 ports: - { name: dsm-http, port: 5000, protocol: TCP } endpoints: - addresses: [192.168.9.100] conditions: { ready: true } --- apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: { name: redirect-https, namespace: lan-proxies } spec: redirectScheme: { scheme: https, permanent: true } --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: { name: nas, namespace: lan-proxies } spec: entryPoints: [web] routes: - kind: Rule match: Host(`nas.inouter.com`) middlewares: [{ name: redirect-https }] services: [{ name: nas, port: 5000 }] --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: { name: nas-tls, namespace: lan-proxies } spec: entryPoints: [websecure] routes: - kind: Rule match: Host(`nas.inouter.com`) services: [{ name: nas, port: 5000 }] tls: secretName: wildcard-inouter-tls ``` ### 동작 검증 ```bash curl -sk --resolve nas.inouter.com:80:192.168.9.53 http://nas.inouter.com/ # → 301 https://nas.inouter.com/ curl -sk --resolve nas.inouter.com:443:192.168.9.53 https://nas.inouter.com/ # → 200, 12094B # NAS - Synology DiskStation ``` ### 주의사항 - **LAN-only**. `*.inouter.com` 가 사설 192.168.9.53 으로 가는 한 외부 인터넷에서는 도달 불가. 외부 노출 시 보안/인증 재검토 필요 (Synology DSM 직노출은 CVE 노출면 큼). - DSM HTTPS(5001) 도 동일 호스트에서 200 응답하지만 자기서명 인증서 + 이중 TLS 종료라 백엔드는 평문 5000 사용. - Traefik 리버스 프록시 패턴은 `lan-proxies` ns 에 추가 LAN 서비스(예: 스위치/라우터/NAS 부속 UI) 적층 가능. ## 관련 문서 - [[backup]] — 백업 파이프라인 (NAS 활용) - [[storage-plan]] — NVMe NAS + iSCSI 기획, 벤치마크 결과 - [[postgresql-ha]] — PostgreSQL HA (Patroni + etcd) - [[infra-hosts]] — 서버 목록 - [[cert-manager]] — `wildcard-inouter-tls` 발급/복제 - [[cloudflare]] — `nas.inouter.com` DNS 이력