LabHub

스토리지와 마운트 · NFS 개념 · 이론

NFS 는 왜 로컬 디스크처럼 굴지 않나

LabHub 에서 이어서 보기

한 줄 요약

NFS 는 파일시스템처럼 보이지만 네트워크 위의 RPC 호출이다. 그래서 로컬 디스크에는 없는 실패 모드(멈춤, 부분 실패, 캐시 불일치)가 생긴다.

왜 이게 필요했나

홈랩이든 회사든, 여러 서버가 같은 데이터를 봐야 하는 순간이 온다. 쿠버네티스에서 여러 노드의 파드가 같은 PVC 를 쓰려면 ReadWriteMany 가 필요하고, 가장 손쉬운 구현이 NFS 다. 그런데 NFS 를 붙이고 나면 이상한 일들이 생긴다. ls 가 30초씩 멈추고, 프로세스가 D 상태로 죽지 않고, 어떤 노드에서는 파일이 보이는데 다른 노드에서는 안 보인다.

어떻게 동작하나

서버 쪽 — exports

# /etc/exports/export/share  10.0.0.0/24(rw,sync,no_subtree_check,root_squash)/export/ro     10.0.0.0/24(ro,sync,no_subtree_check)

| 옵션 | 의미 |
| --- | --- |
| rw / ro | 쓰기 허용 / 읽기 전용 |
| sync | 쓰기를 디스크에 반영한 뒤 응답. 느리지만 안전 |
| async | 메모리에만 쓰고 즉시 응답. 서버가 죽으면 데이터 손실 |
| root_squash | 클라이언트의 root 를 nobody 로 매핑 (기본값) |
| no_root_squash | root 를 그대로 인정. 사실상 서버를 내주는 것 |
| no_subtree_check | 서브트리 검사 생략. 현재 권장 기본 |

root_squash 를 이해하지 못해 생기는 혼란이 많다. 클라이언트에서 root 로 파일을 만들었는데 소유자가 nobody 로 보인다. 이건 버그가 아니라 보안 기능이다. NFS 는 UID 를 그대로 신뢰하기 때문에, squash 가 없으면 클라이언트에서 sudo 한 번으로 서버의 아무 파일이나 건드릴 수 있다.

그리고 UID/GID 는 이름이 아니라 숫자로 전달된다. 서버의 appuser 가 1001 인데 클라이언트의 1001 이 webuser 라면, 파일 소유권이 엉뚱하게 보인다. 그래서 NFS 를 쓰는 환경은 UID 를 중앙에서 통일하거나(LDAP), NFSv4 의 idmapd 를 설정한다.

클라이언트 쪽 — 마운트 옵션

nfs01:/export/share  /mnt/share  nfs4  _netdev,rw,soft,timeo=600,retrans=2,noatime  0  0

가장 중요한 선택이 hardsoft다.

| 옵션 | 서버가 응답하지 않을 때 |
| --- | --- |
| hard (기본) | 영원히 재시도한다. 프로세스가 D 상태로 멈추고 kill 도 안 먹는다 |
| soft | timeo×retrans 후 I/O 오류를 반환한다 |
| intr | (구버전) hard 여도 시그널로 중단 가능. 최신 커널에서는 기본 동작에 흡수 |

hard 는 데이터 정합성 면에서 안전하다 — 쓰기가 실패로 처리되어 애플리케이션이 잘못된 상태로 진행하는 일이 없다. 대신 서버가 죽으면 클라이언트가 통째로 멈춘다. soft 는 반대다. 응답이 없으면 EIO 를 돌려주므로 시스템은 살아 있지만, 애플리케이션이 그 오류를 제대로 처리하지 못하면 데이터가 깨진다.

실무 판단은 대개 이렇다. 데이터베이스나 쓰기가 중요한 워크로드는 hard, 읽기 위주이거나 없어도 되는 캐시성 데이터는 soft.

캐시와 일관성

NFS 클라이언트는 성능을 위해 속성(attribute)을 캐시한다. 기본 acregmin/acregmax 는 3~60초다. 그래서 서버에서 바뀐 파일이 클라이언트에 즉시 보이지 않을 수 있다. 여러 노드가 같은 파일을 동시에 쓰는 설계는 NFS 에서 위험하다. noac 로 캐시를 끄면 일관성은 좋아지지만 성능이 크게 떨어진다.

파일 잠금(flock, fcntl)은 NFSv4 에서 지원되지만, 서버 재시작이나 네트워크 단절 시 잠금 복구가 완벽하지 않다. NFS 위에서 잠금에 의존하는 설계는 피하는 편이 좋다.

현장에서 만나는 모습

df 가 멈춘다. NFS 서버 하나가 죽었는데 hard 로 마운트돼 있으면 df 조차 응답하지 않는다. 이때 df -l(로컬만) 이나 timeout 5 df 로 우회한다. 모니터링 스크립트가 df 를 쓰다가 이 상황에서 함께 멈추는 사고도 흔하다.

부팅이 멈춘다. fstab 에 _netdev 를 빼먹으면 네트워크가 준비되기 전에 마운트를 시도한다. hard 와 겹치면 부팅이 사실상 무한 대기에 들어간다.

쿠버네티스에서 파드가 Terminating 에서 안 나온다. NFS 볼륨이 응답하지 않으면 kubelet 이 언마운트를 못 해 파드가 영원히 종료되지 않는다. 이때는 NFS 서버를 살리거나 노드에서 강제 언마운트(umount -f -l)를 해야 한다.

다음에서 할 것

이 모듈은 개념만 다룬다. 다만 앞의 fstab 실습에서 작성한 NFS 항목이 왜 그런 옵션 조합이었는지 이제 설명할 수 있어야 한다.