문서 목차
NFS 스토리지
외부 또는 내장 NFS 서버를 기반으로 영구 볼륨(PV)을 동적으로 생성·관리하는 기능입니다.
QUANTUM C&S
외부 또는 내장 NFS 서버를 기반으로 영구 볼륨(PV)을 동적으로 생성 · 관리하는 기능입니다.
roles/qks-nfs-provisioner 존재), 아직 실제로 설치 · 검증하지 않았습니다.
사전 준비 — NFS 서버 (두 방식 공통)
두 방식 모두 실제 NFS 서버가 먼저 필요합니다. 이미 운영 중인 외부 NFS/NAS가 있다면 이 단계는 생략하고 바로 아래 "CSI 방식" 절의 설정에서 그 서버의 주소만 가리키면 됩니다. 없다면 노드 중 하나를 NFS 서버로 직접 구성합니다.
iaas.yaml에 포함되어 있습니다. 다만 아래 CSI 방식(plays/qks-csi-nfs.yaml)은 어떤 번들 플레이북에도 포함되어 있지 않아 직접 실행해야만 설치됩니다.
인벤토리에 서버 노드 배정
inventory/qks/hosts.ubuntu는 Terraform이 재생성하지 않는 수동 관리 그룹입니다(Ceph의 [qks-ceph-admin] 등과 동일한 카테고리):
[qks-nfs-server]
k8s-ubuntu24-w01
Ceph mon처럼 여러 대 · 홀수 개일 필요 없이 노드 하나만 지정하면 됩니다 — NFS는 원래 단일 서버 구조입니다.
플레이북 실행
./install.sh ubuntu plays/qks-nfs-server.yaml
설치 흐름
1. nfs-kernel-server 패키지 설치
2. 공유 폴더 생성 /nfs, /nfs/k8s
3. /etc/exports 설정 "/nfs *(rw,sync,no_root_squash,fsid=0,no_subtree_check)"
4. nfs-server, rpc-statd 서비스 시작
fsid=0은 이 디렉터리(/nfs)를 NFSv4 클라이언트가 보는 가상 루트로 지정하는 표준 옵션입니다. 그래서 클라이언트가 서버:/k8s로 마운트를 요청하면 실제로는 서버의 /nfs/k8s를 가리키게 됩니다 — 아래 CSI 방식의 share: /k8s 설정이 이 원리로 동작합니다.
CSI 방식 (검증 완료)
쿠버네티스 SIG-Storage가 관리하는 정식 CSI 드라이버(csi-driver-nfs)를 사용합니다. Ceph RBD/CephFS CSI와 동일한 아키텍처(nodeplugin DaemonSet + controller Deployment)입니다.
설정값
qks_csi_nfs_server: "{{ hostvars[groups['qks-nfs-server'][0]]['private_ip'] }}" # 서버 IP 자동 참조
qks_csi_nfs_path: /k8s # 마운트 경로 (fsid=0 기준 → 실제로는 /nfs/k8s)
qks_csi_nfs_storageclass_name: qks-nfs-csi
qks_csi_nfs_version: 4.13.4 # "v" 접두어 없이! (v4.10.0까지는 v가 붙지만 4.11.0부터 빠짐, 차트 저장소 기준)
플레이북 실행
./install.sh ubuntu plays/qks-csi-nfs.yaml
설치 내용
- StorageClass
qks-nfs-csi(VOLUMEBINDINGMODE: Immediate— 네트워크 공유라 Local Path처럼 파드 스케줄링을 기다릴 필요 없음) kube-system네임스페이스에csi-nfs-controller(Deployment),csi-nfs-node(DaemonSet, 전 노드) 파드
설치 확인
kubectl get storageclass qks-nfs-csi
kubectl get pods -n kube-system | grep csi-nfs
실제 PVC 생성 + 파드 2개 동시 마운트(RWX) 테스트
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-nfs-pvc
spec:
accessModes: ["ReadWriteMany"]
storageClassName: qks-nfs-csi
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-pod-a # 쓰기 담당
labels: {test-nfs: "true"}
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: {matchExpressions: [{key: test-nfs, operator: In, values: ["true"]}]}
topologyKey: kubernetes.io/hostname # pod-b와 반드시 다른 노드에 배치
containers:
- name: busybox
image: busybox
command: ["sh", "-c", "echo hello-nfs > /mnt/test.txt && sleep 3600"]
volumeMounts:
- {name: data, mountPath: /mnt}
volumes:
- name: data
persistentVolumeClaim: {claimName: test-nfs-pvc}
---
apiVersion: v1
kind: Pod
metadata:
name: test-nfs-pod-b # 읽기 담당 (pod-a와 같은 PVC를 동시에, 다른 노드에서 마운트)
labels: {test-nfs: "true"}
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: {matchExpressions: [{key: test-nfs, operator: In, values: ["true"]}]}
topologyKey: kubernetes.io/hostname
containers:
- name: busybox
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- {name: data, mountPath: /mnt}
volumes:
- name: data
persistentVolumeClaim: {claimName: test-nfs-pvc}
kubectl get pod test-nfs-pod-a test-nfs-pod-b -o wide # 둘 다 Running + 서로 다른 NODE 확인
kubectl exec test-nfs-pod-b -- cat /mnt/test.txt # pod-a가 쓴 hello-nfs가 그대로 읽혀야 함
서로 다른 노드에 있는 두 파드가 동시에 같은 볼륨을 마운트해서 한쪽이 쓴 파일을 다른 쪽이 그대로 읽으면, NFS의 핵심 기능(ReadWriteMany)이 노드 경계를 넘어 실제로 동작하는 것까지 증명된 것입니다(같은 노드에만 뜨면 우연히 로컬 캐시로 통과했을 가능성을 배제 못 하므로, 안티어피니티로 다른 노드 배치를 강제함).
확인 후 정리:
kubectl delete pod test-nfs-pod-a test-nfs-pod-b
kubectl delete pvc test-nfs-pvc
서버 프로비저너 방식 (옵션, 미검증)
nfs-subdir-external-provisioner 기반의 예전 방식입니다. CSI 표준 이전부터 쓰이던, 쿠버네티스 내장 NFS 마운트 기능을 이용하는 구현체로, 이 저장소에서는 roles/qks-nfs-provisioner로 지원됩니다. 생성되는 StorageClass 이름은 qks_nfs_provisioner_storageclass_name(기본값 qks-nfs)입니다.
./install.sh ubuntu plays/qks-nfs-provisioner.yaml
qks_nfs_provisioner_enabled 변수는 iaas.yaml 번들 실행 시에만 의미가 있고, 위 플레이북을 직접 실행할 때는 값과 무관하게 항상 설치됩니다.
CSI 방식과 같은 NFS 서버(/nfs/k8s)를 절대경로로 직접 마운트합니다. 아직 실제로 설치 · 검증하지 않았습니다 — 검증 후 이 섹션에 결과를 반영할 예정입니다.