AKS 안의 파드가 클러스터 밖의 리눅스 VM 을 NFS 서버로 삼아 PV 를 붙이는 구성이다. 관리형 스토리지(Azure Files NFS, Azure NetApp Files)를 쓰지 않고 직접 띄운 NFS 서버를 쓰는 이유는 보통 기존 데이터가 그 서버에 이미 있거나, POSIX 권한을 그대로 유지해야 하기 때문이다.
이 구성에서 PVC 가 Pending 에 머물거나 파드가 마운트에서 멈추는 원인은 계층이 둘로 나뉜다. 네트워크 경로가 먼저고, 그것이 풀린 뒤에 NFS 서버 쪽 권한(SELinux · exports) 이 드러난다. 순서를 바꿔 진단하면 원인을 잘못 짚는다.
증상이 No route to host 면 순수 네트워크 문제다. AKS 노드는 노드 서브넷 IP 를 쓰지만, Azure CNI Overlay 를 쓰면 파드는 노드와 다른 오버레이 대역을 쓴다. 이 경우 파드에서 나가는 트래픽은 노드 IP 로 SNAT 되어 나가야 하는데, 대상 VM 이 다른 서브넷에 있고 NSG 가 그 트래픽을 막고 있으면 조용히 끊긴다.
확인 순서는 아래와 같다.
# 1. 노드에서 되는지
kubectl debug node/<node> -it --image=busybox -- sh -c 'nc -vz ${NFS_IP} 2049'
# 2. 파드에서 되는지
kubectl run nettest --rm -it --image=busybox --restart=Never -- sh -c 'nc -vz ${NFS_IP} 2049'
# 3. 어느 서브넷에 붙어 있는지
az network nic show --ids $(az vm show -g ${RG} -n ${NFS_VM} --query 'networkProfile.networkInterfaces[0].id' -o tsv) \
--query 'ipConfigurations[].subnet.id' -o tsv
# 4. NSG 규칙 확인
az network nsg rule list -g ${RG} --nsg-name ${NSG} -o table
NFS 서버가 열려 있어야 하는 포트는 NFSv4 기준 TCP 2049 하나다. NFSv3 를 쓰면 여기에 portmapper(111)와 mountd · statd · lockd 포트가 추가되며, 이들은 기본적으로 임의 포트라 방화벽을 열기 까다롭다. 가능하면 NFSv4 로 고정한다.
서브넷을 옮기는 것은 마지막 수단이다. Azure 에서 VM 의 서브넷을 바꾸려면 NIC 를 새로 만들어 붙이는 방식이 되는데, 기존 NIC 를 떼면 그 NIC 에 붙어 있던 공용 IP 와 NSG 연결도 함께 끊긴다. 서브넷을 나눈 채로 NSG 를 여는 편이 안전하고, AKS 삭제가 막히는 문제도 피할 수 있다.
네트워크가 뚫리고 나서야 CSI 드라이버가 실제 마운트를 시도하고, 그때 서버 쪽 권한이 드러난다. 여기서 흔한 함정이 SELinux Enforcing 이다. RHEL 계열 NFS 서버에서 SELinux 가 켜져 있으면 exports 설정이 맞아도 원격 쓰기가 거부되는데, 거부가 클라이언트 쪽에 명확한 메시지로 전달되지 않아 "마운트는 됐는데 쓰기가 안 되는" 모호한 상태가 된다.
먼저 원인을 확정한다.
getenforce
ausearch -m avc -ts recent
ausearch 에 NFS 관련 AVC 거부가 찍히면 SELinux 가 원인이다. 임시로 확인만 하려면 setenforce 0 으로 Permissive 로 내려 본다. 재부팅하면 원래대로 돌아가므로 테스트에 적합하다.
Enforcing 을 유지한 채 쓰려면 NFS 서버에서 export 관련 불리언을 켠다.
setsebool -P nfs_export_all_rw on
setsebool -P nfs_export_all_ro on
getsebool -a | grep nfs
확인 필요 — 대화 기록에는 chcon -Rt svirt_sandbox_file_t <export 경로> 도 함께 제시돼 있었다. svirt_sandbox_file_t(현재 이름은 container_file_t)는 같은 호스트에서 컨테이너가 로컬 디렉터리를 쓸 때 필요한 레이블이고, 원격 NFS export 에 붙이는 레이블로는 맞지 않는다. NFS 서버 쪽은 불리언으로 해결하는 것이 정석이므로, 레이블 변경은 필요할 때만 ausearch 근거를 보고 판단한다.
exports 는 다음 형태로 둔다.
/data 10.224.0.0/16(rw,sync,no_subtree_check,no_root_squash)
no_root_squash 는 컨테이너가 root 로 파일을 만들어야 할 때 필요하지만 보안상 부담이 크다. 애플리케이션이 특정 UID 로 돌게 만들 수 있다면 root_squash 를 유지하고 디렉터리 소유자를 그 UID 로 맞추는 편이 낫다.
동적 프로비저닝 없이 정적 PV 로 붙이는 형태다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-data
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: ""
mountOptions:
- nfsvers=4.1
- hard
- noatime
nfs:
server: 10.224.1.10
path: /data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-data
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
storageClassName: ""
volumeName: nfs-data
storageClassName: "" 을 양쪽에 명시해 기본 StorageClass 가 끼어들지 않게 한다. 비워 두면 AKS 기본 StorageClass 가 잡혀 엉뚱한 디스크가 붙는다.
| 증상 | 의심 계층 | 확인 |
|---|---|---|
No route to host |
네트워크 | 서브넷 · NSG · 라우팅 |
| 연결은 되는데 마운트 hang | NFS 버전 · 포트 | nfsvers 옵션, portmapper 포트 |
| 마운트는 되는데 쓰기 실패 | 서버 권한 | exports 옵션, SELinux AVC |
| PVC 가 계속 Pending | PV 매칭 | storageClassName, accessModes, capacity |