NFS 의 접근 제어는 두 층으로 나뉜다. /etc/exports 가 어느 호스트가 마운트할 수 있는지를 정하고, 파일시스템 권한과 UID · GID 가 마운트한 뒤 무엇을 할 수 있는지를 정한다. 여기서 헷갈리는 지점은 sec=sys 인 기본 구성에서 NFS 가 사용자 이름을 전혀 보지 않는다는 것이다. 클라이언트가 보내는 것은 숫자 UID · GID 뿐이고 서버는 그 숫자를 그대로 믿는다.
기본 설치와 마운트 절차는 NFS 서버 · 클라이언트 설정과 상태 · 버전 확인 에 있다.
# /etc/exports
# <공유 경로> <클라이언트>(옵션)
/srv/share 192.168.1.100(rw,sync,no_subtree_check)
/srv/share 192.168.0.0/24(rw,sync,no_subtree_check) 192.168.1.0/24(rw,sync,no_subtree_check)
/srv/ro 192.168.1.0/24(ro,sync,no_subtree_check)
/srv/mixed 192.168.1.100(rw,sync) 192.168.1.101(ro,sync)
클라이언트 지정에 쓸 수 있는 형태는 단일 IP, CIDR, 와일드카드를 포함한 호스트명(*.example.com), 넷그룹(@group) 이다. 호스트명을 쓰면 DNS 에 의존하게 되므로 DNS 가 흔들리면 마운트가 실패한다. 운영에서는 IP 나 CIDR 을 쓰는 편이 안전하다.
클라이언트 지정과 괄호 사이에 공백을 넣지 않는다. 192.168.1.0/24 (rw) 라고 쓰면 "192.168.1.0/24 에는 기본 옵션, 그리고 모든 호스트에 rw" 라는 뜻이 되어 전 세계에 열린다. 이 오타가 실제로 사고를 낸다.
exportfs -ra # /etc/exports 를 다시 읽는다
exportfs -v # 현재 적용된 내용과 실제 옵션을 본다
exportfs -v 는 명시하지 않은 기본 옵션까지 전부 펼쳐 보여 주므로, 의도한 대로 적용됐는지는 이 출력으로 확인한다. 서비스를 재시작할 필요는 없다.
| 옵션 | 의미 |
|---|---|
rw / ro |
읽기 · 쓰기 / 읽기 전용 |
sync |
쓰기를 디스크에 반영한 뒤 응답한다. 기본값이자 권장값 |
async |
응답을 먼저 보낸다. 빠르지만 서버가 갑자기 죽으면 데이터를 잃는다 |
no_subtree_check |
서브트리 검사를 끈다. 현재 기본값이며 안정성과 성능 모두에 낫다 |
root_squash |
클라이언트의 root(UID 0) 를 익명 사용자로 낮춘다. 기본값 |
no_root_squash |
클라이언트 root 를 서버 root 로 그대로 통과시킨다 |
all_squash |
모든 사용자를 익명 사용자로 낮춘다 |
anonuid / anongid |
익명 사용자로 쓸 UID · GID 를 지정한다 |
sec=sys / sec=krb5p |
인증 방식. 기본은 sys(UID 신뢰), Kerberos 를 쓰면 실제 사용자 인증이 된다 |
no_root_squash 는 그 클라이언트에서 root 를 얻은 사람이 공유 데이터 전체를 다룰 수 있게 만든다. 컨테이너 런타임이나 백업 도구가 요구하는 경우에만, 그 호스트에만 준다.
/etc/exports 에는 사용자를 적는 자리가 없다. sec=sys 에서 접근 주체는 숫자 UID 이므로 다음 세 가지 중 하나를 쓴다.
서버와 클라이언트에서 같은 계정이 같은 숫자를 갖게 한다.
# 서버
id nfsuser # uid=1001(nfsuser) gid=1001(nfsuser)
# 클라이언트
groupadd -g 1001 nfsuser
useradd -u 1001 -g 1001 nfsuser
숫자가 어긋나면 클라이언트의 다른 사용자가 서버의 파일을 소유한 것처럼 보인다. 여러 대를 다룬다면 LDAP · SSSD 로 계정을 한곳에서 관리하는 편이 낫다.
누가 접근하든 서버에서는 하나의 계정으로 동작하게 만든다.
/srv/share 192.168.1.0/24(rw,sync,all_squash,anonuid=1001,anongid=1001)
chown -R 1001:1001 /srv/share
chmod -R 2775 /srv/share
sec=krb5(인증) · krb5i(무결성) · krb5p(암호화) 를 쓰면 UID 를 신뢰하는 대신 KDC 가 발급한 티켓으로 주체를 확인한다. 이것만이 NFS 에서 "사용자 haedong 만 허용" 을 실제로 구현하는 방법이다. KDC 와 서비스 주체(principal), 클라이언트 키탭이 필요하므로 도입 비용이 있다.
mount -t nfs -o vers=4.2 <NAS_IP>:/srv/share /mnt/share
mount -t nfs -o vers=3 <NAS_IP>:/srv/share /mnt/share
버전은 vers= 또는 nfsvers= 로 지정한다. 지정하지 않으면 클라이언트가 4.2 → 4.1 → 4.0 → 3 순으로 내려가며 협상한다. 서버가 지원하는 버전은 rpcinfo -p <서버IP> 로 확인한다.
/etc/fstab 에 넣을 때는 _netdev 를 붙인다. 네트워크가 준비되기 전에 마운트를 시도해 부팅이 지연되는 것을 막는다.
<NAS_IP>:/srv/share /mnt/share nfs vers=4.2,rw,hard,_netdev 0 0
일반 사용자가 직접 마운트하게 하려면 user(그 사용자만) 또는 users(누구나) 옵션을 준다. noauto 와 같이 쓰면 부팅 시에는 붙지 않고 필요할 때만 마운트한다.
<NAS_IP>:/srv/share /home/haedong/nfs nfs vers=4.2,rw,noauto,user,_netdev 0 0
NFS 마운트로 파일을 옮길 때 소유자와 권한까지 보존하려면 복사 방식과 서버 옵션이 둘 다 맞아야 한다.
cp -a /src/. /mnt/share/
rsync -aAX --numeric-ids /src/ /mnt/share/
--numeric-ids 는 이름 대신 숫자 UID · GID 를 그대로 쓴다. 양쪽 계정 데이터베이스가 다를 때 의도치 않은 매핑을 막는다. 소유자를 바꾸는 작업은 root 권한이 필요하므로, root_squash 가 걸린 export 에서는 root 로 복사해도 소유자가 익명 사용자로 바뀐다. 소유자 보존이 필요하면 해당 export 를 no_root_squash 로 두거나, 서버에서 직접 복사한다.
showmount -e <NAS_IP> # 서버가 내보내는 목록
exportfs -v # 서버에서 실제 적용된 옵션
ss -lntp | grep 2049
getenforce
| 증상 | 원인 |
|---|---|
access denied by server while mounting |
클라이언트 IP 가 export 목록에 없다. NAT 를 거쳐 오면 서버가 보는 주소가 다르다 |
showmount -e 에 경로가 안 보임 |
/etc/exports 에 없거나 exportfs -ra 를 안 했다 |
| 연결 자체가 안 됨 | 방화벽. NFSv4 는 2049 만 있으면 되지만 v3 는 포트가 흩어진다 |
| 마운트는 되는데 쓸 수 없음 | 디렉터리 권한, ro 옵션, root_squash |
소유자가 nobody 로 보임 |
NFSv4 의 idmap 도메인 불일치. 양쪽 /etc/idmapd.conf 의 Domain 을 맞춘다 |
방화벽은 서비스 단위로 연다.
firewall-cmd --permanent --add-service=nfs
firewall-cmd --permanent --add-service=mountd
firewall-cmd --permanent --add-service=rpc-bind
firewall-cmd --reload
NFSv4 만 쓴다면 nfs 서비스(2049/tcp) 만으로 충분하다. mountd 와 rpc-bind 는 v3 를 함께 제공할 때 필요하다.
하드닝된 이미지나 클라우드 기본 이미지에서 NFS 관련 유닛이 masked 로 봉인돼 있는 경우가 있다.
# systemctl status nfs-server
○ nfs-server.service
Loaded: masked (Reason: Unit nfs-server.service is masked.)
Active: inactive (dead)
masked 는 유닛 파일이 /dev/null 로 심볼릭 링크된 상태다. enable 도 start 도 통하지 않는다.
ls -l /etc/systemd/system/nfs-server.service
ls -l /etc/systemd/system/rpcbind.service
-> /dev/null 이면 봉인된 것이다. 의존 관계 때문에 rpcbind 를 먼저 풀어야 한다. nfs-server 만 풀고 시작하면 Job for nfs-server.service canceled 로 취소된다.
systemctl unmask rpcbind rpcbind.socket
systemctl enable --now rpcbind
systemctl unmask nfs-server
systemctl enable --now nfs-server
systemctl status rpcbind nfs-server
ss -lntp | grep 2049
풀기 전에 왜 봉인됐는지 확인한다. 보안 기준에 따라 의도적으로 막아 둔 것이라면 담당자와 합의하고 푼다.
패키지가 아예 없는 경우도 있다.
rpm -qa | grep nfs
dnf install -y nfs-utils