339 lines
14 KiB
Markdown
339 lines
14 KiB
Markdown
---
|
||
title: NAS StorageClass (NFS + iSCSI)
|
||
updated: 2026-04-14 문서 정확성 보정 (hp2 연결 상태, nodeAffinity, iSCSI on-demand 동작)
|
||
tags: [infra, k3s, storage, nfs, iscsi, synology]
|
||
---
|
||
|
||
## 개요
|
||
|
||
Synology NAS(DS916+)를 K3s NFS/iSCSI StorageClass로 사용. Longhorn(로컬 블록)과 병행하여 파일 저장소, 웹소스 등 대용량/RWX 워크로드에 사용.
|
||
|
||
## NAS 정보
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| 모델 | Synology DS916+ (Braswell, x86_64, Linux 3.10.108) |
|
||
| bond0 (eth0+eth1, 1G×2) | **192.168.9.100** — 관리/SMB/DSM 웹 |
|
||
| eth2 (USB 2.5G, RTL8157, r8152) | **192.168.205.100** — NFS/iSCSI 데이터 플레인, MTU 9000 |
|
||
| 디스크 | 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 노드는 모두 192.168.205.0/24 서브넷에 참여하고 있음 (2026-04-14 검증):
|
||
|
||
| 노드 | 출구 NIC | src IP |
|
||
|------|---------|--------|
|
||
| incus-kr1 | enp7s0 (PCIe 2.5G) | 192.168.205.214 |
|
||
| incus-kr2 | enx803f5dd34c9f (USB 2.5G) | 192.168.205.135 |
|
||
| incus-hp2 | ens2 | 192.168.205.134 |
|
||
|
||
## 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 | **없음 (제약 없이 배포)** — 2026-04-14 기준 Deployment에 nodeSelector/affinity 미설정. 3 노드(kr1/kr2/hp2) 전부 192.168.205.0/24 도달 가능해 스케줄 자유 |
|
||
|
||
### 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` 필수. 인시던트 이력: [[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
|
||
```
|
||
|
||
### ScsiTarget 서비스의 on-demand 동작 (중요)
|
||
|
||
DSM의 `ScsiTarget` 패키지는 LIO(kernel configfs) 기반이며, **Target이 0개이면 3260 포트를 LISTEN하지 않는다.** 이건 고장이 아니라 LIO의 정상 동작.
|
||
|
||
- PVC 없는 상태 → `netstat -lnt | grep 3260` 비어있음
|
||
- PVC 생성 → democratic-csi가 Synology API로 Target+LUN 자동 생성 → configfs에 Target 등록 → 3260 자동 LISTEN
|
||
- PVC 삭제 → Target/LUN 자동 제거 → Target 0개 되면 3260 닫힘
|
||
|
||
따라서 "3260 포트가 안 열려있다"만으로 장애 판단 금지. **Target 존재 여부 먼저 확인:**
|
||
|
||
```bash
|
||
# NAS에서
|
||
find /sys/kernel/config/target/iscsi -maxdepth 2 -type d
|
||
sudo synopkg status ScsiTarget # "running"이어야 함
|
||
```
|
||
|
||
### 2026-04-14 smoke test 결과
|
||
|
||
| 단계 | 결과 |
|
||
|------|------|
|
||
| PVC 생성 (1Gi, synology-iscsi) → Bound | 30초 |
|
||
| Target + LUN 자동 생성 | OK (democratic-csi → Synology HTTPS API) |
|
||
| 3260 LISTEN | OK (Target 등록과 동시에) |
|
||
| discovery (kr1/kr2/hp2 전부) | OK (양 포털 192.168.205.100 + 192.168.9.100) |
|
||
| PVC 삭제 → PV Released → DeleteVolume → PV gone | OK |
|
||
|
||
2026-04-14 이전 며칠간 ScsiTarget이 stop 상태로 방치되어 있었음. 원인 미확인(`/var/log/synopkg.log` 로그 기준 3/24 부팅 이후 stop 기록 없지만 실제 상태는 stop). 수동 `sudo synopkg start ScsiTarget`로 복구. 실제 소비자 PVC는 계속 0건이라 서비스 영향 없었음.
|
||
|
||
### 전체 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` |
|
||
| hp2 | ens2 | 9000 | (설정 위치 확인 필요) |
|
||
| NAS | eth2 (USB 2.5GbE) | 9000 | Synology DSM 네트워크 설정 |
|
||
|
||
### 2026-04-14 end-to-end JF 검증
|
||
|
||
`ping -M do -s 8972`(DF bit, 9000B 프레임) 전 구간 통과:
|
||
|
||
| 경로 | 결과 | avg RTT |
|
||
|------|------|---------|
|
||
| kr1 → NAS | 2/2 | 0.46ms |
|
||
| kr2 → NAS | 2/2 | 0.44ms |
|
||
| hp2 → NAS | 2/2 | 0.47ms |
|
||
| kr1 → kr2 | 2/2 | 1.09ms |
|
||
| kr1 → hp2 | 2/2 | 0.54ms |
|
||
| hp2 → kr2 | 2/2 | 0.46ms |
|
||
|
||
스위치 포함 전 경로 9000B fragmentation 없이 통과. 노드 간(kr1↔kr2, kr1↔hp2 등)도 JF 동작 — storage 전용망 외 노드 간 통신에도 활용 가능.
|
||
|
||
## NAS eth2 USB NIC watchdog (2026-04-14)
|
||
|
||
NAS의 eth2(USB 2.5G, RTL8157)가 USB 3.0 LPM exit latency 미보고 이슈로 간헐적 disconnect 발생 (21일 uptime 기준 2회). 드라이버는 자동 reconnect하지만 인터페이스가 DOWN 상태로 남아 네트워크 서비스 중단.
|
||
|
||
근본 원인: DSM 3.10.108 커널의 xHCI 호스트 컨트롤러가 LPM `U1/U2 exit latency` descriptor를 OS에 노출하지 않음 → 링크 절전 복귀 타이밍 불일치 → `-71 EPROTO` → USB disconnect. DS916+ 하드웨어/커널 조합의 근본적 한계로 드라이버 업데이트·커널 교체로 해결 불가.
|
||
|
||
### 대응: cron watchdog
|
||
|
||
| 항목 | 값 |
|
||
|------|-----|
|
||
| 스크립트 | `/usr/local/bin/eth2-watchdog.sh` (root:755) |
|
||
| 주기 | 1분 (root cron, `/etc/crontab`) |
|
||
| 로그 | `/var/log/eth2-watchdog.log` |
|
||
| 영속성 | `/dev/md0` ext4 root, 재부팅 유지 (DSM 메이저 업그레이드 시 재확인 필요) |
|
||
|
||
동작:
|
||
1. `/sys/class/net/eth2/operstate` 확인
|
||
2. `up`이 아니면 `ip link set eth2 up` (IP는 DSM ifcfg-eth2가 자동 할당)
|
||
3. UP 후 IP 없으면 `ip addr add 192.168.205.100/24 dev eth2`
|
||
4. MTU ≠ 9000 이면 보정
|
||
|
||
### 검증 기록
|
||
|
||
- 2026-04-14 강제 down 테스트: 1분 내 watchdog 실행 → `ip link up` → DSM ifcfg 자동 IP 할당 → 정상 복귀 (MTU 9000 유지)
|
||
- Outline 상세 기록: `2026-04-14 / NAS eth2 watchdog 구축 / kappa` (id `93baf66b-f003-47a9-9d14-7a66e3dbfde0`)
|
||
- RCA 문서: `2026-04-14 / NAS eth2 USB NIC 링크 드롭 원인 조사 / kappa` (id `db923170-8c16-459d-82ce-46fdc1f0f0d0`)
|
||
|
||
### 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
|
||
# <title>NAS - Synology DiskStation</title>
|
||
```
|
||
|
||
### 주의사항
|
||
|
||
- **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 이력
|