NFS (Network File System)
분산 환경에서 널리 쓰이는 NFS를 커널 관점으로 분석합니다. NFSv3와 상태 기반 NFSv4 계열의 차이, RPC/XDR 인코딩과 전송 경로, knfsd 파일핸들·세션·DRC 처리, 클라이언트 페이지 캐시(Page Cache)와 close-to-open 일관성, 위임과 잠금(Lock) 복구, Kerberos 인증 및 ID 매핑(Mapping), pNFS 데이터 경로 분산, 장애 전환과 성능 튜닝까지 실무에 필요한 내용을 상세히 다룹니다.
핵심 요약
- 계층 이해 — VFS, 캐시(Cache), 하위 FS 경계를 구분합니다.
- 메타데이터 우선 — inode/dentry 일관성을 먼저 확인합니다.
- 저장 정책 — 저널링(Journaling)/압축/할당 정책 차이를 비교합니다.
- 일관성 모델 — 로컬/원격/합성 FS의 반영 시점을 구분합니다.
- 복구 관점 — 장애 시 재구성 경로를 함께 점검합니다.
단계별 이해
- 경계 계층 파악
요청이 VFS에서 어디로 내려가는지 확인합니다. - 메타/데이터 분리
어느 경로에서 무엇이 갱신되는지 나눠 봅니다. - 동기화/플러시(Flush) 확인
쓰기 반영 시점과 순서를 검증합니다. - 복구 시나리오 점검
비정상 종료 후 일관성 회복을 확인합니다.
NFS 개요
NFS(Network File System)는 Sun Microsystems가 1984년에 개발한 분산 파일시스템 프로토콜로, 네트워크를 통해 원격 파일시스템을 마치 로컬 파일시스템처럼 접근할 수 있게 합니다. 리눅스 커널은 NFS 서버(knfsd)와 클라이언트를 모두 커널 내부에 구현하여 높은 성능을 제공합니다.
NFS 버전 발전
| 버전 | 연도 | RFC | 핵심 특징 | 상태 모델 |
|---|---|---|---|---|
| NFSv2 | 1989 | RFC 1094 | 최초 공식 표준, 32비트 파일 오프셋(Offset), UDP 전용 | Stateless |
| NFSv3 | 1995 | RFC 1813 | 64비트 오프셋, 비동기 쓰기(WRITE UNSTABLE), TCP 지원, readdirplus | Stateless |
| NFSv4.0 | 2003 | RFC 7530 | Stateful, compound ops, delegation, ACL, 방화벽(Firewall) 친화(단일 포트 2049) | Stateful |
| NFSv4.1 | 2010 | RFC 8881 | 세션, pNFS(병렬 NFS), 트렁킹, 디렉토리 위임 | Stateful |
| NFSv4.2 | 2016 | RFC 7862 | 서버 사이드 복사, 희소(sparse) 파일, 레이블 NFS(SELinux) | Stateful |
NFS 프로토콜 - RPC/XDR
NFS는 ONC RPC(Open Network Computing Remote Procedure Call) 위에 구축됩니다. 클라이언트가 원격 프로시저를 호출하면, 인자와 반환값은 XDR(eXternal Data Representation) 형식으로 직렬화(Serialization)되어 네트워크를 통해 전달됩니다.
ONC RPC 구조
리눅스 커널의 SUNRPC 구현은 net/sunrpc/ 디렉토리에 위치합니다. 핵심 구조체(Struct)는 다음과 같습니다.
/* net/sunrpc/clnt.c - RPC 클라이언트 핵심 구조체 */
struct rpc_clnt {
struct rpc_xprt __rcu *cl_xprt; /* 전송 계층 */
const struct rpc_procinfo *cl_procinfo; /* 프로시저 정보 테이블 */
u32 cl_prog; /* RPC 프로그램 번호 */
u32 cl_vers; /* RPC 프로그램 버전 */
u32 cl_maxproc; /* 최대 프로시저 수 */
struct rpc_auth *cl_auth; /* 인증 핸들 */
struct rpc_stat *cl_stats; /* RPC 통계 */
struct rpc_iostats *cl_metrics; /* I/O 성능 메트릭 */
/* ... */
};
코드 설명
- rpc_clntSun RPC 클라이언트의 핵심 구조체로,
net/sunrpc/clnt.c에 정의됩니다. NFS 클라이언트가 서버와 통신할 때 이 구조체를 통해 RPC 호출을 수행합니다. - cl_xprtRCU로 보호되는 전송 계층(
rpc_xprt) 포인터입니다. TCP, UDP, RDMA 등 다양한 전송 프로토콜을 추상화하며,nconnect옵션 사용 시 여러 전송 객체가 연결됩니다. - cl_procinfoRPC 프로시저 정보 테이블로, 각 NFS 연산(READ, WRITE, GETATTR 등)의 XDR 인코딩/디코딩 함수와 타임아웃 정책을 정의합니다.
- cl_prog / cl_versRPC 프로그램 번호(NFS=100003)와 버전(3 또는 4)을 지정합니다. 서버는 이 값으로 요청을 적절한 핸들러에 라우팅합니다.
- cl_auth인증 핸들로,
AUTH_SYS(UID/GID 기반) 또는RPCSEC_GSS(Kerberos) 등의 인증 방식을 관리합니다.
XDR 인코딩
XDR은 플랫폼 독립적 데이터 직렬화 형식입니다. 모든 데이터는 4바이트 경계로 정렬되며, 빅 엔디안(big-endian)을 사용합니다.
/* XDR 기본 타입 인코딩/디코딩 */
static inline __be32 *xdr_encode_hyper(__be32 *p, __u64 val)
{
put_unaligned_be64(val, p);
return p + 2; /* 64비트 = 2 x 32비트 워드 */
}
static inline __be32 *xdr_encode_opaque_fixed(__be32 *p,
const void *ptr, unsigned int nbytes)
{
memcpy(p, ptr, nbytes);
if (nbytes & 3) /* 4바이트 패딩 */
memset((char *)p + nbytes, 0, 4 - (nbytes & 3));
return p + XDR_QUADLEN(nbytes);
}
portmap / rpcbind
NFSv2/v3에서 클라이언트는 여러 포트를 사용합니다. rpcbind(포트 111)를 통해 NFS(프로그램 번호 100003), mountd(100005), NLM(100021), NSM(100024) 서비스의 포트를 조회합니다.
| RPC 프로그램 | 번호 | 기능 | NFSv4 필요 여부 |
|---|---|---|---|
| portmapper/rpcbind | 100000 | RPC 포트 매핑 | 불필요 |
| nfs | 100003 | NFS 프로토콜 | 포트 2049 고정 |
| mountd | 100005 | 마운트(Mount) 프로토콜 | 불필요 (pseudo-fs) |
| nlockmgr (NLM) | 100021 | 파일 잠금(File Lock) | 불필요 (프로토콜 내장) |
| status (NSM) | 100024 | 상태 모니터링 | 불필요 |
sunrpc 전송 계층 (xprt)
SUNRPC는 전송 계층을 추상화하여 TCP, UDP, RDMA를 동일한 인터페이스로 사용합니다. rpc_xprt 구조체가 전송 독립적 인터페이스를 제공하며, 각 전송 방식은 rpc_xprt_ops 콜백(Callback)을 구현합니다.
/* include/linux/sunrpc/xprt.h - 전송 계층 추상화 */
struct rpc_xprt {
const struct rpc_xprt_ops *ops; /* 전송 방식별 콜백 */
const struct rpc_timeout *timeout; /* 타임아웃 정책 */
struct rpc_wait_queue sending; /* 송신 대기 큐 */
struct rpc_wait_queue pending; /* 응답 대기 큐 */
struct rpc_wait_queue backlog; /* 슬롯 대기 큐 */
unsigned int max_reqs; /* 최대 동시 요청 (슬롯 수) */
unsigned int min_reqs; /* 최소 동시 요청 */
unsigned int num_reqs; /* 현재 활성 요청 */
unsigned long bind_timeout;
unsigned long reestablish_timeout;
struct sockaddr_storage addr; /* 서버 주소 */
size_t addrlen;
int prot; /* IPPROTO_TCP/UDP */
};
/* 전송 방식별 연산 테이블 */
struct rpc_xprt_ops {
void (*set_buffer_size)(struct rpc_xprt *, size_t, size_t);
int (*reserve_xprt)(struct rpc_xprt *, struct rpc_task *);
void (*release_xprt)(struct rpc_xprt *, struct rpc_task *);
void (*connect)(struct rpc_xprt *, struct rpc_task *);
int (*send_request)(struct rpc_rqst *);
void (*close)(struct rpc_xprt *);
void (*destroy)(struct rpc_xprt *);
void (*inject_disconnect)(struct rpc_xprt *);
};
| 전송 모듈 | 소스 | 프로토콜 | 특징 |
|---|---|---|---|
| xprtsock | net/sunrpc/xprtsock.c | TCP/UDP | 기본 전송, 소켓(Socket) 기반 |
| xprtrdma | net/sunrpc/xprtrdma/ | RDMA | 제로카피, 커널 바이패스 |
| xprtmultipath | net/sunrpc/xprtmultipath.c | 다중 경로 | nconnect, 트렁킹용 다중 xprt 관리 |
RPC 태스크(Task) 스케줄링
각 NFS RPC 호출은 rpc_task로 표현되며, 유한 상태 머신(FSM)으로 실행됩니다. RPC 태스크는 비동기적으로 처리되어 여러 요청을 동시에 진행할 수 있습니다.
/* include/linux/sunrpc/sched.h - RPC 태스크 */
struct rpc_task {
struct rpc_clnt *tk_client; /* RPC 클라이언트 */
struct rpc_xprt *tk_xprt; /* 전송 핸들 */
struct rpc_rqst *tk_rqstp; /* 요청/응답 버퍼 */
/* 상태 머신 콜백 */
const struct rpc_call_ops *tk_ops;
void *tk_calldata; /* 콜백 인자 */
rpc_action tk_action; /* 현재 상태 함수 */
unsigned short tk_flags; /* RPC_TASK_* 플래그 */
unsigned short tk_timeouts; /* 재시도 횟수 */
int tk_status; /* 결과 상태 코드 */
};
/* RPC 태스크 상태 머신 흐름:
* call_start → call_reserve → call_refreshauth → call_allocate
* → call_bind → call_connect → call_transmit → call_status
* → call_decode → 완료 또는 재시도 */
NFSv3 프로토콜
NFSv3 프로시저
NFSv3는 22개의 프로시저를 정의합니다. 각 프로시저는 독립적인 RPC 호출이며, 서버는 요청 간 상태를 유지하지 않습니다(stateless).
/* NFSv3 주요 프로시저 (RFC 1813) */
enum nfs3_procedure {
NFS3PROC_NULL = 0, /* ping */
NFS3PROC_GETATTR = 1, /* 파일 속성 조회 */
NFS3PROC_SETATTR = 2, /* 파일 속성 설정 */
NFS3PROC_LOOKUP = 3, /* 이름으로 파일핸들 조회 */
NFS3PROC_ACCESS = 4, /* 접근 권한 확인 */
NFS3PROC_READLINK = 5, /* 심볼릭 링크 읽기 */
NFS3PROC_READ = 6, /* 파일 데이터 읽기 */
NFS3PROC_WRITE = 7, /* 파일 데이터 쓰기 */
NFS3PROC_CREATE = 8, /* 파일 생성 */
NFS3PROC_MKDIR = 9, /* 디렉토리 생성 */
NFS3PROC_REMOVE = 12, /* 파일 삭제 */
NFS3PROC_READDIR = 16, /* 디렉토리 목록 읽기 */
NFS3PROC_READDIRPLUS= 17, /* 디렉토리 + 속성 읽기 */
NFS3PROC_FSSTAT = 18, /* 파일시스템 통계 */
NFS3PROC_FSINFO = 19, /* 파일시스템 정보 */
NFS3PROC_COMMIT = 21, /* 비동기 쓰기 커밋 */
};
마운트 프로토콜
NFSv3 클라이언트는 mountd 데몬에 MOUNT 요청을 보내 초기 파일핸들(root filehandle)을 얻습니다. 이후 모든 NFS 요청은 이 파일핸들 기반으로 동작합니다.
# NFSv3 마운트 예시
mount -t nfs -o vers=3,tcp,rsize=65536,wsize=65536 \
server:/export/data /mnt/nfs
# rpcinfo로 NFS 관련 서비스 확인
rpcinfo -p server
# program vers proto port service
# 100000 4 tcp 111 portmapper
# 100003 3 tcp 2049 nfs
# 100005 3 tcp 20048 mountd
# 100021 4 tcp 44629 nlockmgr
NLM (Network Lock Manager)
NFSv3의 파일 잠금은 NLM(Network Lock Manager) 프로토콜로 처리됩니다. NLM은 stateful 프로토콜이므로 NSM(Network Status Monitor)과 함께 동작하여 서버/클라이언트 재시작(Reboot) 시 잠금을 복구합니다.
/* fs/lockd/svc.c - NLM 서비스 커널 구현 */
static const struct svc_version nlmsvc_version4 = {
.vs_vers = 4,
.vs_nproc = 24,
.vs_proc = nlmsvc_procedures4,
.vs_dispatch = nlmsvc_dispatch,
.vs_xdrsize = NLMSVC_XDRSIZE,
};
/* NLM 잠금 요청 처리 */
static __be32
nlmsvc_proc_lock(struct svc_rqst *rqstp)
{
struct nlm_args *argp = rqstp->rq_argp;
struct nlm_host *host;
struct nlm_file *file;
/* 파일 핸들 → 잠금 설정 */
return cast_status(nlmsvc_lock(rqstp, file,
host, &argp->lock, argp->block,
&argp->cookie, argp->reclaim));
}
NFSv4 프로토콜
Compound Operations
NFSv4의 가장 큰 프로토콜 변화는 compound 연산입니다. 여러 개의 작은 연산(operation)을 하나의 RPC 요청에 묶어 보낼 수 있어 네트워크 왕복 횟수를 크게 줄입니다.
/* NFSv4 compound 연산 예: 파일 읽기 */
/* 하나의 RPC 요청에 4개 연산을 묶음 */
COMPOUND {
SEQUENCE(session, slot, seq) /* v4.1: 세션 시퀀싱 */
PUTFH(parent_filehandle) /* 현재 파일핸들 설정 */
LOOKUP("filename") /* 이름 → 파일핸들 */
GETATTR(size, mtime, mode) /* 속성 조회 */
READ(offset=0, count=65536) /* 데이터 읽기 */
}
/* NFSv3였다면 MOUNT + LOOKUP + GETATTR + READ = 4번 왕복 */
커널에서 compound 연산 처리는 fs/nfsd/nfs4proc.c의 nfsd4_proc_compound()에서 수행됩니다.
/* fs/nfsd/nfs4proc.c */
static __be32
nfsd4_proc_compound(struct svc_rqst *rqstp)
{
struct nfsd4_compoundargs *args = rqstp->rq_argp;
struct nfsd4_compoundres *resp = rqstp->rq_resp;
struct nfsd4_op *op;
while (resp->opcnt < args->opcnt) {
op = &args->ops[resp->opcnt++];
/* 각 연산을 순차적으로 실행 */
op->status = nfsd4_enc_ops[op->opnum](rqstp, &xdr, op);
if (op->status)
break; /* 에러 시 나머지 연산 중단 */
}
return rpc_success;
}
코드 설명
- nfsd4_proc_compound()
fs/nfsd/nfs4proc.c에 위치한 NFSv4 compound 연산 처리 함수입니다. 하나의 RPC 요청에 포함된 여러 연산(SEQUENCE, PUTFH, LOOKUP, READ 등)을 순차적으로 실행합니다. - args->ops[resp->opcnt++]compound 요청에 포함된 연산 배열을 순회하며,
opcnt를 증가시키면서 각 연산을 하나씩 처리합니다. - nfsd4_enc_ops[op->opnum]연산 번호(
opnum)를 인덱스로 사용하여 해당 연산의 인코딩/실행 함수를 호출합니다. NFSv4.x에서는 SEQUENCE가 항상 첫 번째 연산입니다. - if (op->status) break연산 중 하나라도 오류가 발생하면 나머지 연산을 중단합니다. 이는 NFSv4 compound의 원자적 실패 시맨틱으로, 클라이언트가 어디서 실패했는지 정확히 파악할 수 있습니다.
OPEN/CLOSE 상태 관리
NFSv4에서 파일 열기는 OPEN 연산으로 수행되며, 서버에 상태(stateid)를 생성합니다. 이 stateid는 이후 READ/WRITE/LOCK 연산에 사용됩니다.
/* fs/nfsd/nfs4state.c - NFSv4 open 상태 구조체 */
struct nfs4_ol_stateid {
struct nfs4_stid st_stid;
struct list_head st_locks; /* 이 open에 연결된 잠금 목록 */
struct nfs4_openowner *st_openowner;
struct nfs4_file *st_stateowner;
unsigned char st_access_bmap; /* READ/WRITE 접근 비트맵 */
unsigned char st_deny_bmap; /* 거부 공유 비트맵 */
};
/* stateid: 128비트 = 4바이트 seqid + 12바이트 other */
typedef struct {
u32 si_generation; /* 시퀀스 번호 */
opaque_t si_opaque; /* 서버 고유 식별자 */
} stateid4;
코드 설명
- nfs4_ol_stateid
fs/nfsd/nfs4state.c에 정의된 NFSv4 open/lock 상태 구조체입니다. 서버가 OPEN 연산을 처리할 때 생성되며, 이후 READ/WRITE/LOCK 연산에서 이 상태를 참조합니다. - st_stid상태 식별자(
nfs4_stid)를 내장하며, 128비트 stateid를 포함합니다. 클라이언트는 이 stateid를 모든 상태 관련 연산에 첨부합니다. - st_locks이 open 상태에 연결된 바이트 범위 잠금(byte-range lock) 목록입니다. NFSv4에서는 잠금이 반드시 open 상태에 종속됩니다.
- st_access_bmap / st_deny_bmapWindows 공유 모드와 유사한 접근/거부 비트맵입니다. 여러 클라이언트가 같은 파일을 열 때 충돌을 감지하는 데 사용됩니다.
- stateid4128비트 상태 식별자로, 4바이트 시퀀스 번호(
si_generation)와 12바이트 불투명 식별자로 구성됩니다. 시퀀스 번호는 상태 변경 시마다 증가하여 재전송과 구분합니다.
위임 (Delegation)
NFSv4 위임(delegation)은 서버가 클라이언트에게 파일에 대한 특정 권한을 위임하는 메커니즘입니다. 클라이언트가 위임을 받으면, 서버에 매번 확인하지 않고 로컬에서 캐시된 데이터를 사용할 수 있습니다.
| 위임 유형 | 권한 | 설명 |
|---|---|---|
| OPEN_DELEGATE_READ | 읽기 | 다른 클라이언트가 쓰기를 시도하기 전까지 로컬 캐시 유효 |
| OPEN_DELEGATE_WRITE | 읽기+쓰기 | 독점적 쓰기 권한, 잠금 없이 쓰기 가능 |
/* fs/nfs/delegation.c - 클라이언트 측 위임 처리 */
struct nfs_delegation {
struct list_head super_list;
const struct cred *cred;
struct inode *inode;
nfs4_stateid stateid;
fmode_t type; /* READ or WRITE */
unsigned long flags;
loff_t maxsize;
__u64 change_attr;
struct rcu_head rcu;
};
/* 위임 반환: 서버가 CB_RECALL 콜백 시 호출 */
int nfs4_proc_delegreturn(struct inode *inode,
const struct cred *cred,
const nfs4_stateid *stateid, int issync)
{
/* DELEGRETURN compound 전송 */
struct nfs4_delegreturnargs args = {
.fhandle = NFS_FH(inode),
.stateid = stateid,
};
return nfs4_call_sync(clp->cl_rpcclient, &msg, &args, &res, 0);
}
코드 설명
- nfs_delegation
fs/nfs/delegation.c에 정의된 클라이언트 측 위임 구조체입니다. 서버가 OPEN 응답에 위임을 포함하면 이 구조체가 할당되어 inode에 연결됩니다. - stateid위임 고유의 stateid로, 서버가 부여합니다. DELEGRETURN 시 이 stateid를 서버에 반환하여 어떤 위임을 반납하는지 식별합니다.
- type (READ or WRITE)READ 위임은 다른 클라이언트가 쓰기를 시도하지 않는 한 캐시가 유효함을 보장합니다. WRITE 위임은 더 강력하여 로컬 잠금 처리와 캐시 쓰기까지 허용합니다.
- change_attr위임 획득 시점의 change attribute 값으로, 위임 반환 시 서버의 현재 값과 비교하여 충돌을 감지합니다.
- nfs4_proc_delegreturn()서버의 CB_RECALL 콜백을 받거나 위임이 더 이상 필요 없을 때 호출됩니다. DELEGRETURN compound를 서버에 전송하여 위임을 반납합니다.
NFSv4.1 / pNFS
세션 (Sessions)
NFSv4.1은 세션 개념을 도입해 중복 요청 식별과 재전송(Retransmission) 처리를 강화하며, exactly-once 시맨틱(EOS)에 가까운 동작을 제공합니다. 각 세션은 여러 슬롯을 가진 채널로 구성되며, 클라이언트는 슬롯별로 시퀀스 번호를 관리합니다.
/* include/linux/nfs4.h - NFSv4.1 세션 구조 */
struct nfs4_session {
struct nfs4_sessionid sess_id; /* 16바이트 세션 ID */
u32 flags;
struct nfs4_channel_attrs fc_attrs; /* 전방 채널 속성 */
struct nfs4_channel_attrs bc_attrs; /* 후방 채널 (콜백) */
struct nfs4_slot_table fc_slot_table; /* 전방 슬롯 테이블 */
struct nfs4_slot_table bc_slot_table; /* 후방 슬롯 테이블 */
struct nfs_client *clp;
};
/* SEQUENCE 연산: 모든 compound 요청의 첫 번째 연산 */
/* 슬롯 + 시퀀스 번호로 재전송 감지 (exactly-once) */
struct nfs4_slot {
struct nfs4_slot_table *table;
u32 sl_seqid; /* 시퀀스 번호 */
u32 sl_slot; /* 슬롯 인덱스 */
unsigned long sl_flags; /* NFS4_SLOT_* */
struct nfs4_slot_seqid_failed *sl_lru;
};
pNFS (Parallel NFS)
pNFS는 NFSv4.1의 핵심 확장으로, 데이터 접근을 메타데이터 서버(MDS)와 데이터 서버(DS)로 분리하여 병렬 I/O를 가능하게 합니다. 클라이언트는 MDS에서 레이아웃(layout)을 받아 DS에 직접 데이터를 읽고 쓸 수 있습니다.
레이아웃 유형 (Layout Types)
| 레이아웃 | 커널 모듈(Kernel Module) | 데이터 접근 | 설명 |
|---|---|---|---|
| Files Layout | nfs_layout_nfsv41_files | NFSv4.1 DS | NFS 프로토콜로 DS 접근, 가장 일반적 |
| Blocks Layout | blocklayoutdriver | SAN (iSCSI/FC) | 블록 디바이스 직접 접근 |
| Objects Layout | objlayoutdriver | OSD | 객체 스토리지 접근 (사용 감소) |
| FlexFiles Layout | nfs_layout_flexfiles | NFSv3/v4 DS | 미러링/스트라이핑, 유연한 DS 프로토콜 |
| SCSI Layout | blocklayoutdriver | SCSI PR | SCSI Persistent Reservation 기반 |
/* fs/nfs/pnfs.h - pNFS 레이아웃 드라이버 인터페이스 */
struct pnfs_layoutdriver_type {
struct list_head pnfs_tblid;
u32 id; /* LAYOUT4_* 유형 */
const char *name;
/* 레이아웃 획득/반환 */
struct pnfs_layout_segment *
(*alloc_lseg)(struct pnfs_layout_hdr *layoutid,
struct nfs4_layoutget_res *lgr, gfp_t gfp);
void (*free_lseg)(struct pnfs_layout_segment *lseg);
/* 데이터 I/O */
enum pnfs_try_status
(*read_pagelist)(struct nfs_pgio_header *);
enum pnfs_try_status
(*write_pagelist)(struct nfs_pgio_header *, int sync);
enum pnfs_try_status
(*commit_pagelist)(struct inode *,
struct list_head *, int, struct nfs_commit_info *);
};
코드 설명
- pnfs_layoutdriver_type
fs/nfs/pnfs.h에 정의된 pNFS 레이아웃 드라이버 인터페이스입니다. Files, Blocks, FlexFiles 등 각 레이아웃 유형은 이 구조체의 콜백 함수들을 구현하여 커널에 등록합니다. - id (LAYOUT4_*)레이아웃 유형 식별자로,
LAYOUT4_NFSV4_1_FILES,LAYOUT4_BLOCK_VOLUME,LAYOUT4_FLEX_FILES등의 값을 가집니다. 서버가 LAYOUTGET 응답에 이 유형을 명시합니다. - alloc_lseg / free_lseg레이아웃 세그먼트의 할당과 해제를 담당합니다.
alloc_lseg는 서버로부터 받은 LAYOUTGET 응답을 파싱하여 데이터 서버(DS) 주소와 스트라이프 정보를 추출합니다. - read_pagelist / write_pagelist실제 데이터 I/O를 수행하는 콜백입니다. 레이아웃 정보를 기반으로 MDS를 거치지 않고 데이터 서버에 직접 READ/WRITE RPC를 전송하여 병렬 I/O를 구현합니다.
- commit_pagelistUNSTABLE 모드로 데이터 서버에 쓴 데이터를 커밋합니다. pNFS에서는 각 데이터 서버에 개별적으로 COMMIT을 보내야 하므로 일반 NFS보다 복잡합니다.
NFSv4.2 프로토콜
NFSv4.2(RFC 7862)는 NFSv4.1의 마이너 확장으로, 서버 사이드 복사, 희소 파일, 레이블 NFS 등 현대적 스토리지 기능을 추가합니다. 리눅스 커널은 4.x 시리즈부터 점진적으로 NFSv4.2 기능을 구현했습니다.
서버 사이드 복사 (Server-Side Copy)
NFSv4.2의 가장 주목할 만한 기능은 서버 사이드 복사입니다. 클라이언트가 copy_file_range() 시스템 콜(System Call)을 호출하면, 데이터가 클라이언트를 경유하지 않고 서버 내부(intra-server) 또는 서버 간(inter-server)에서 직접 복사됩니다.
/* fs/nfs/nfs42proc.c - NFSv4.2 서버 사이드 복사 */
static ssize_t _nfs42_proc_copy(
struct file *src, struct nfs_lock_context *src_lock,
struct file *dst, struct nfs_lock_context *dst_lock,
struct nfs42_copy_args *args,
struct nfs42_copy_res *res)
{
/* COPY compound 구성:
* SEQUENCE + PUTFH(src) + SAVEFH + PUTFH(dst) + COPY
*
* 동기(synchronous) 복사: 작은 파일
* 비동기(asynchronous) 복사: 큰 파일
* → CB_OFFLOAD 콜백으로 완료 통보 */
args->cp_src = 1; /* intra-server copy */
args->sync = nss->sync;
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_COPY],
.rpc_argp = args,
.rpc_resp = res,
};
return nfs4_call_sync(server->client, &msg, &args->seq_args,
&res->seq_res, 0);
}
/* copy_file_range() → NFS COPY 연산 매핑 */
static ssize_t nfs4_copy_file_range(
struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
size_t count, unsigned int flags)
{
/* 같은 서버 → intra-server copy (COPY) */
/* 다른 서버 → inter-server copy (COPY_NOTIFY + COPY) */
if (!nfs42_ssc_enabled(server))
return -EOPNOTSUPP;
return nfs42_proc_copy(file_in, pos_in,
file_out, pos_out, count,
nss, nss_count, sync);
}
희소 파일 (Sparse File) 지원
NFSv4.2는 SEEK, ALLOCATE, DEALLOCATE 연산을 도입하여 희소 파일을 효율적으로 처리합니다. lseek(SEEK_HOLE/SEEK_DATA)가 서버에서 직접 처리되어 클라이언트가 빈 영역을 건너뛸 수 있습니다.
/* fs/nfs/nfs42proc.c - SEEK 연산 */
loff_t nfs42_proc_llseek(struct file *filep,
loff_t offset, int whence)
{
struct nfs42_seek_args args = {
.sa_fh = NFS_FH(inode),
.sa_offset = offset,
.sa_what = (whence == SEEK_HOLE) ?
NFS4_CONTENT_HOLE : NFS4_CONTENT_DATA,
};
struct nfs42_seek_res res;
/* COMPOUND: SEQUENCE + PUTFH + SEEK */
status = nfs4_call_sync(server->client, &msg,
&args.seq_args, &res.seq_res, 0);
if (res.sr_eof)
return -ENXIO;
return vfs_setpos(filep, res.sr_offset, inode->i_sb->s_maxbytes);
}
/* ALLOCATE: 지정 범위에 공간 사전 할당 (fallocate 연동) */
static int nfs42_proc_allocate(struct file *filep,
loff_t offset, loff_t len)
{
/* COMPOUND: SEQUENCE + PUTFH + ALLOCATE(offset, length) */
/* 서버가 지정 범위의 디스크 블록을 미리 할당 */
}
/* DEALLOCATE: 지정 범위의 블록 해제 (hole punching) */
static int nfs42_proc_deallocate(struct file *filep,
loff_t offset, loff_t len)
{
/* COMPOUND: SEQUENCE + PUTFH + DEALLOCATE(offset, length) */
/* fallocate(FALLOC_FL_PUNCH_HOLE) 매핑 */
}
레이블 NFS (Labeled NFS)
NFSv4.2의 레이블 NFS는 SELinux 등 MAC(Mandatory Access Control) 보안 레이블을 NFS를 통해 전파합니다. 서버와 클라이언트가 동일한 보안 정책을 공유하면, 원격 파일에도 일관된 보안 레이블이 적용됩니다.
# 레이블 NFS 설정
# 서버 exports (security_label 옵션)
/export *(rw,sync,no_subtree_check,security_label)
# 클라이언트 마운트
mount -t nfs -o vers=4.2,context="system_u:object_r:nfs_t:s0" \
server:/export /mnt/nfs
# 또는 서버 레이블 그대로 사용
mount -t nfs -o vers=4.2 server:/export /mnt/nfs
# 레이블 확인
ls -Z /mnt/nfs/
# -rw-r--r--. user group system_u:object_r:nfs_t:s0 file.txt
클론/리플링크 (CLONE)
NFSv4.2 CLONE 연산은 서버의 COW(Copy-on-Write) 파일시스템(Btrfs, XFS reflink)의 블록 공유를 NFS를 통해 노출합니다. cp --reflink이 NFS 마운트에서도 동작합니다.
/* fs/nfs/nfs42proc.c - CLONE 연산 */
static int nfs42_proc_clone(struct file *src_f,
struct file *dst_f, loff_t src_offset,
loff_t dst_offset, loff_t count)
{
/* COMPOUND: SEQUENCE + PUTFH(src) + SAVEFH +
* PUTFH(dst) + CLONE(src_offset, dst_offset, count)
*
* 서버 파일시스템이 reflink를 지원해야 함
* (Btrfs, XFS with reflink, OCFS2) */
struct nfs42_clone_args args = {
.src_fh = NFS_FH(src_inode),
.dst_fh = NFS_FH(dst_inode),
.src_offset = src_offset,
.dst_offset = dst_offset,
.count = count,
};
return nfs4_call_sync(server->client, &msg,
&args.seq_args, &res.seq_res, 0);
}
I/O 어드바이스 (IO_ADVISE)
NFSv4.2 IO_ADVISE 연산은 클라이언트의 posix_fadvise() 힌트를 서버에 전달하여 서버 측 캐시 전략을 최적화합니다.
| 어드바이스 | POSIX 매핑 | 서버 동작 |
|---|---|---|
| IO_ADVISE4_NORMAL | POSIX_FADV_NORMAL | 기본 캐시 정책 |
| IO_ADVISE4_SEQUENTIAL | POSIX_FADV_SEQUENTIAL | readahead 증가 |
| IO_ADVISE4_RANDOM | POSIX_FADV_RANDOM | readahead 비활성화 |
| IO_ADVISE4_WILLNEED | POSIX_FADV_WILLNEED | 데이터 선제 로드 |
| IO_ADVISE4_DONTNEED | POSIX_FADV_DONTNEED | 캐시에서 제거 |
| IO_ADVISE4_NOREUSE | POSIX_FADV_NOREUSE | 재사용 없음 힌트 |
NFSv4.2 기능 요약
| 기능 | 연산 | 시스템 콜 매핑 | 커널 버전 |
|---|---|---|---|
| 서버 사이드 복사 | COPY, OFFLOAD_CANCEL, CB_OFFLOAD | copy_file_range() | 4.11+ |
| 희소 파일 | SEEK, ALLOCATE, DEALLOCATE | lseek(SEEK_HOLE/DATA), fallocate() | 4.2+ |
| 레이블 NFS | GETATTR/SETATTR(security_label) | SELinux xattr | 3.14+ |
| 클론/리플링크 | CLONE | ioctl(FICLONE) | 4.5+ |
| I/O 어드바이스 | IO_ADVISE | posix_fadvise() | 4.5+ |
Linux NFS 서버 (knfsd)
knfsd 아키텍처
리눅스 NFS 서버(knfsd)는 커널 공간(Kernel Space)에서 동작합니다. fs/nfsd/ 디렉토리에 구현되어 있으며, 유저스페이스 nfs-utils 패키지가 설정과 관리를 담당합니다.
/* fs/nfsd/nfssvc.c - nfsd 커널 스레드 */
static int nfsd(void *vrqstp)
{
struct svc_rqst *rqstp = vrqstp;
for (;;) {
/* RPC 요청 대기 */
int err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT);
if (err == -EAGAIN || err == -EINTR)
continue;
validate_process_creds();
/* RPC 요청 처리 (디스패치) */
svc_process(rqstp);
validate_process_creds();
}
svc_exit_thread(rqstp);
return 0;
}
NFS 파일핸들 (Filehandle)
NFS에서 모든 파일/디렉토리는 파일핸들(filehandle)로 식별됩니다. 파일핸들은 서버가 생성하는 불투명(opaque) 바이트 배열로, 클라이언트는 이를 해석하지 않고 서버에 그대로 전달합니다. 리눅스 knfsd는 파일핸들에 export ID, inode 번호, 생성 시간을 인코딩합니다.
/* include/linux/nfsd/nfsfh.h - NFS 파일핸들 구조 */
struct knfsd_fh {
unsigned int fh_size; /* 파일핸들 크기 (바이트) */
union {
struct {
__u8 fh_version; /* 파일핸들 버전 (1) */
__u8 fh_auth_type; /* 인증 유형 */
__u8 fh_fsid_type; /* fsid 인코딩 방식 */
__u8 fh_fileid_type; /* fileid 인코딩 방식 */
__u32 fh_fsid[]; /* 파일시스템 ID (export 식별) */
} fh_new;
__u8 fh_raw[NFS4_FHSIZE]; /* NFSv4: 최대 128바이트 */
} fh_base;
};
/* fs/nfsd/nfsfh.c - 파일핸들 → dentry 변환 */
__be32 fh_verify(struct svc_rqst *rqstp,
struct svc_fh *fhp, umode_t type, int access)
{
/* 1. fh_fsid → export 조회 */
/* 2. export의 ops->fh_to_dentry() 호출 */
/* 3. inode 번호 + generation 검증 */
/* 4. 접근 권한 확인 (export 옵션 + UNIX perms) */
/* Stale filehandle: inode가 삭제된 경우 */
if (IS_ERR(dentry))
return nfserr_stale; /* ESTALE → 클라이언트에 통보 */
}
/* NFSv3: 최대 64바이트, NFSv4: 최대 128바이트 */
#define NFS3_FHSIZE 64
#define NFS4_FHSIZE 128
no_subtree_check 옵션은 서브트리 이동 시 stale 발생을 줄여줍니다.
Duplicate Request Cache (DRC)
NFS 서버는 DRC(Duplicate Request Cache, 중복 요청 캐시)를 유지하여 동일한 RPC 요청이 재전송될 경우 연산을 반복 수행하지 않고 캐시된 응답을 반환합니다. 이는 비멱등(non-idempotent) 연산(CREATE, REMOVE, RENAME 등)의 안전성을 보장하는 핵심 메커니즘입니다.
/* fs/nfsd/nfscache.c - 중복 요청 캐시 */
struct nfsd_drc_bucket {
struct list_head lru;
spinlock_t lock;
};
struct svc_cacherep {
struct list_head c_lru;
unsigned char c_state; /* RC_UNUSED/INPROG/DONE */
unsigned char c_type; /* RC_NOCACHE/REPLSTAT/REPLBUFF */
struct svc_opaque_auth c_cred; /* 요청자 자격증명 */
__be32 c_xid; /* RPC XID (요청 식별) */
u32 c_prot; /* 프로토콜 (TCP/UDP) */
u32 c_proc; /* 프로시저 번호 */
u32 c_vers; /* 프로그램 버전 */
unsigned long c_timestamp;
union {
struct kvec u_vec; /* 캐시된 응답 데이터 */
__be32 u_status; /* 캐시된 상태 코드 */
} c_u;
};
/* 중복 요청 확인: XID + 소스 주소 + 프로시저로 매칭 */
int nfsd_cache_lookup(struct svc_rqst *rqstp)
{
/* XID 해시 → 버킷 탐색 → 일치 항목 검색
* - 일치: 캐시된 응답 반환 (RC_REPLSTAT/RC_REPLBUFF)
* - 미일치: RC_DOIT → 요청 처리 후 응답 캐시
* - 처리 중(RC_INPROG): 재전송 드롭 */
}
# DRC 관련 커널 파라미터
# DRC 항목 수 (기본 1024, 대규모 서버는 증가)
# /etc/nfs.conf
[nfsd]
drc-tcp-size=4096
# DRC 통계 확인
cat /proc/net/rpc/nfsd
# rc 항목: hits misses nocache
# → hits: DRC 히트 (중복 요청 캐시 응답)
# → misses: DRC 미스 (신규 요청)
# → nocache: 캐시 불필요 (멱등 연산)
exports 설정
/etc/exports 파일에서 NFS로 공유할 디렉토리와 접근 권한을 정의합니다. exportfs 명령으로 커널에 반영합니다.
# /etc/exports 설정 예시
# 기본 읽기/쓰기 공유
/export/data 192.168.1.0/24(rw,sync,no_subtree_check)
# 읽기 전용 + root squash (보안)
/export/public *(ro,root_squash,all_squash,anonuid=65534,anongid=65534)
# NFSv4용 pseudo filesystem root
/export *(fsid=0,ro,no_subtree_check)
# Kerberos 보안
/export/secure gss/krb5p(rw,sync,no_subtree_check)
# crossmnt: 하위 마운트 자동 traversal
/export 192.168.1.0/24(rw,sync,no_subtree_check,crossmnt)
| 옵션 | 설명 |
|---|---|
rw / ro | 읽기/쓰기 또는 읽기 전용(Read-Only) |
sync / async | 동기/비동기 쓰기 (sync 권장, async는 데이터 손실 위험) |
no_subtree_check | 서브트리 검사 비활성화 (성능 향상, 기본 권장) |
root_squash | root(UID 0) 접근을 anonymous 사용자로 매핑 |
all_squash | 모든 사용자를 anonymous로 매핑 |
fsid=0 | NFSv4 pseudo filesystem root 지정 |
crossmnt | 하위 파일시스템 자동 내보내기 |
sec=krb5/krb5i/krb5p | Kerberos 보안 수준 (인증/무결성(Integrity)/프라이버시) |
nfs-utils 데몬
# nfsd 서버 시작 (8개 커널 스레드)
systemctl start nfs-server
# 또는 수동
rpc.nfsd 8
# exports 변경 반영
exportfs -ra
# 현재 export 목록 확인
exportfs -v
# 커널 nfsd 스레드 수 확인/변경
cat /proc/fs/nfsd/threads
echo 16 > /proc/fs/nfsd/threads
# NFS 서버 통계
nfsstat -s
# NFS 클라이언트 통계
nfsstat -c
# NFSv4 지원 버전 확인
cat /proc/fs/nfsd/versions
# 출력 예: -2 +3 +4 +4.1 +4.2
/proc/net/rpc/nfsd의 th 행에서 스레드 사용률을 확인할 수 있습니다.
Linux NFS 클라이언트
클라이언트 아키텍처
NFS 클라이언트는 VFS 계층에 nfs 파일시스템 유형으로 등록됩니다. 커널 소스의 fs/nfs/ 디렉토리에 구현되어 있습니다.
/* fs/nfs/super.c - NFS 파일시스템 등록 */
struct file_system_type nfs_fs_type = {
.owner = THIS_MODULE,
.name = "nfs",
.init_fs_context = nfs_init_fs_context,
.parameters = nfs_fs_parameters,
.kill_sb = nfs_kill_super,
.fs_flags = FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA,
};
struct file_system_type nfs4_fs_type = {
.owner = THIS_MODULE,
.name = "nfs4",
.init_fs_context = nfs_init_fs_context,
.parameters = nfs_fs_parameters,
.kill_sb = nfs_kill_super,
.fs_flags = FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA,
};
nfs_client 구조체
/* include/linux/nfs_fs_sb.h */
struct nfs_client {
refcount_t cl_count;
atomic_t cl_mds_count;
int cl_cons_state; /* 연결 상태 */
unsigned long cl_res_expire; /* lease 만료 */
unsigned long cl_flags;
struct __kernel_sockaddr_storage cl_addr;
const char *cl_hostname; /* 서버 호스트명 */
struct list_head cl_share_link; /* 공유 전송 */
struct list_head cl_superblocks; /* superblock 목록 */
struct rpc_clnt *cl_rpcclient; /* RPC 클라이언트 */
const struct nfs_rpc_ops *rpc_ops; /* v3/v4 분기 */
int cl_proto; /* XPRT_TRANSPORT_* */
int cl_nfs_mod_state;
#if IS_ENABLED(CONFIG_NFS_V4)
u64 cl_clientid; /* NFSv4 client ID */
nfs4_verifier cl_confirm;
struct nfs4_session *cl_session; /* v4.1 세션 */
unsigned long cl_lease_time;
unsigned long cl_last_renewal;
#endif
};
코드 설명
- nfs_client
include/linux/nfs_fs_sb.h에 정의된 NFS 클라이언트의 최상위 구조체입니다. 하나의 NFS 서버 연결을 나타내며, 같은 서버에 여러 마운트가 있어도nfs_client는 공유됩니다. - cl_cons_state연결 상태를 추적합니다. NFS 클라이언트 초기화는 비동기적으로 진행되며, 다른 마운트가 같은 서버를 사용하려 할 때 이 상태를 확인하여 초기화 완료를 대기합니다.
- cl_rpcclientSun RPC 클라이언트(
rpc_clnt)에 대한 포인터입니다. 모든 NFS 프로시저 호출이 이 RPC 클라이언트를 통해 전송됩니다. - rpc_opsNFSv3와 NFSv4의 연산 분기 테이블입니다.
nfs_v3_clientops또는nfs_v4_clientops를 가리키며, 버전별로 다른 RPC 프로시저를 호출합니다. - cl_clientid / cl_sessionNFSv4 전용 필드로,
cl_clientid는 EXCHANGE_ID로 획득한 64비트 클라이언트 식별자이고,cl_session은 NFSv4.1 세션 객체를 가리킵니다. - cl_lease_time / cl_last_renewal서버의 lease 시간과 마지막 갱신 시점입니다. 클라이언트는 lease 만료 전에 주기적으로 SEQUENCE 연산을 보내 lease를 갱신해야 합니다.
마운트 옵션
# NFSv4.2 마운트 (권장 설정)
mount -t nfs -o vers=4.2,tcp,rsize=1048576,wsize=1048576,\
hard,timeo=600,retrans=2,nconnect=8 \
server:/export/data /mnt/nfs
# /etc/fstab 설정
server:/export/data /mnt/nfs nfs vers=4.2,hard,timeo=600,\
retrans=2,rsize=1048576,wsize=1048576,nconnect=8,_netdev 0 0
# autofs 자동 마운트
# /etc/auto.master
/mnt/auto /etc/auto.nfs --timeout=300
# /etc/auto.nfs
data -rw,vers=4.2,hard,nconnect=4 server:/export/data
| 마운트 옵션 | 기본값 | 설명 |
|---|---|---|
vers= | 협상 | NFS 프로토콜 버전 (3, 4, 4.1, 4.2) |
rsize= / wsize= | 협상 | 읽기/쓰기 전송 크기 (최대 1MB) |
hard / soft | hard | hard: 서버 복구까지 무한 재시도, soft: 타임아웃 후 에러 |
timeo= | 600 | RPC 타임아웃 (1/10초 단위) |
retrans= | 2 | 타임아웃 시 재전송 횟수 |
nconnect= | 1 | 서버당 TCP 연결 수 (최대 16, 병렬 성능 향상) |
proto= | tcp | 전송 프로토콜 (tcp, udp, rdma) |
sec= | sys | 보안 방식 (sys, krb5, krb5i, krb5p) |
_netdev | - | 네트워크 가용 후 마운트 (fstab용) |
hard를 사용하세요. soft는 서버 일시 장애 시 데이터 손상을 유발할 수 있습니다. soft+timeo 만료 시 프로세스(Process)에 EIO가 반환되어 쓰기 데이터가 유실됩니다.
NFSv4 ID 매핑 (idmapd)
NFSv4는 UID/GID 숫자 대신 user@domain 형식의 문자열 식별자를 사용합니다. 이를 통해 서로 다른 UID 체계를 가진 시스템 간에도 일관된 소유권 매핑이 가능합니다. rpc.idmapd 데몬 또는 커널의 NFSv4 idmapper가 문자열 ↔ UID/GID 변환을 수행합니다.
/* fs/nfsd/nfs4idmap.c - 서버 측 ID 매핑 */
struct ent {
struct cache_head h;
u32 id; /* UID 또는 GID */
char name[IDMAP_NAMESZ]; /* "user@domain" */
char authname[IDMAP_NAMESZ]; /* GSS 인증 이름 */
int type; /* IDMAP_TYPE_USER/GROUP */
};
/* 이름 → ID 변환 (GETATTR 응답 시) */
int nfsd_map_name_to_uid(
struct svc_rqst *rqstp,
const char *name, size_t namelen,
uid_t *id)
{
/* 1. 커널 키링에서 조회 (request_key) */
/* 2. 캐시 미스 시 유저스페이스 upcall (rpc.idmapd) */
/* 3. /etc/passwd, LDAP, NIS 등에서 매핑 확인 */
}
# NFSv4 ID 매핑 설정
# /etc/idmapd.conf - 서버/클라이언트 공통
[General]
Verbosity = 0
Domain = example.com # NFSv4 도메인 (양쪽 동일해야 함!)
[Mapping]
Nobody-User = nobody
Nobody-Group = nogroup
[Translation]
Method = nsswitch # nsswitch, umich_ldap, static */
# 커널 ID 매핑 키링 확인
cat /proc/net/rpc/nfs4.nametoid/content
cat /proc/net/rpc/nfs4.idtoname/content
# 커널 키링 기반 ID 매핑 (rpc.idmapd 대체, 최신 배포판 기본)
# /etc/request-key.conf에 id_resolver 설정
nfsidmap -c # 키링 캐시 클리어
nfsidmap -l # 키링 내용 조회
# 숫자 ID 직접 사용 (ID 매핑 우회)
# /etc/modprobe.d/nfs.conf
options nfs nfs4_disable_idmapping=1
options nfsd nfs4_disable_idmapping=1
/etc/idmapd.conf Domain 값이 다르면, 모든 파일의 소유자가 nobody:nogroup으로 표시됩니다. 도메인이 일치해야 user@domain 문자열이 올바르게 UID/GID로 변환됩니다. nfs4_disable_idmapping=1 설정 시 숫자 UID/GID를 직접 사용하여 이 문제를 우회할 수 있습니다.
NFSv4.1 클라이언트 ID 설정
NFSv4.1 클라이언트는 EXCHANGE_ID → CREATE_SESSION 과정을 통해 서버와 세션을 수립합니다. 클라이언트 ID는 클라이언트 재시작 시 이전 상태를 복구하는 데 핵심적 역할을 합니다.
/* fs/nfs/nfs4proc.c - EXCHANGE_ID: 클라이언트 ID 설정 */
static int _nfs4_proc_exchange_id(
struct nfs_client *clp,
const struct cred *cred,
u32 sp4_how)
{
struct nfs41_exchange_id_args args = {
.verifier = &verifier, /* 부팅 시 생성한 고유 verifier */
.client = clp,
/* co_ownerid: 클라이언트 식별 문자열
* "Linux NFSv4.1 <hostname>/<uniquifier>" */
};
/* 서버 응답:
* clientid: 64비트 클라이언트 ID
* sequence_id: 세션 생성용 시퀀스
* server_owner/scope: 트렁킹 판단용 */
return nfs4_call_sync(clp->cl_rpcclient, &msg,
&args.seq_args, &res.seq_res, 0);
}
/* CREATE_SESSION: 세션 수립 (EXCHANGE_ID 이후) */
static int _nfs4_proc_create_session(
struct nfs_client *clp,
const struct cred *cred)
{
/* 전방 채널: 클라이언트 → 서버 (NFS 연산)
* 후방 채널: 서버 → 클라이언트 (CB_RECALL 등 콜백)
*
* 채널별 속성:
* ca_maxrequests: 최대 동시 요청 (슬롯 수)
* ca_maxops: compound당 최대 연산 수
* ca_maxresp_sz: 최대 응답 크기 */
}
캐싱 메커니즘
속성 캐시 (Attribute Cache)
NFS 클라이언트는 파일 속성(크기, 수정 시간, 권한)을 로컬에 캐시하여 GETATTR RPC 호출을 줄입니다. 캐시 유효 시간은 acregmin/acregmax(일반 파일), acdirmin/acdirmax(디렉토리)로 제어합니다.
/* fs/nfs/inode.c - 속성 캐시 유효성 검사 */
static int nfs_attribute_cache_expired(struct inode *inode)
{
struct nfs_inode *nfsi = NFS_I(inode);
if (nfs_have_delegated_attributes(inode))
return 0; /* 위임이 있으면 캐시 항상 유효 */
return !time_in_range_open(jiffies,
nfsi->read_cache_jiffies,
nfsi->read_cache_jiffies + nfsi->attrtimeo);
}
/* 캐시 타임아웃은 적응적으로 증가:
* 파일이 변경되지 않으면 acregmin → acregmax까지 2배씩 증가
* 파일이 변경되면 acregmin으로 리셋 */
static void nfs_update_attrtimeo(struct nfs_inode *nfsi)
{
if (time_after(jiffies, nfsi->last_updated + nfsi->attrtimeo))
nfsi->attrtimeo = min(nfsi->attrtimeo << 1, nfsi->attrtimeo_max);
}
코드 설명
- nfs_attribute_cache_expired()
fs/nfs/inode.c에 위치한 속성 캐시 유효성 검사 함수입니다. NFS 클라이언트가 inode 속성(크기, 수정 시간 등)을 참조할 때마다 호출되어 서버에 GETATTR을 보내야 하는지 판단합니다. - nfs_have_delegated_attributes()해당 inode에 유효한 위임(delegation)이 있는지 확인합니다. 위임이 있으면 서버가 다른 클라이언트의 변경을 차단하므로, 로컬 캐시가 항상 유효하여 0(만료되지 않음)을 반환합니다.
- time_in_range_open()현재 시간(
jiffies)이 캐시 갱신 시점(read_cache_jiffies)부터 타임아웃(attrtimeo) 범위 안에 있는지 검사합니다. 범위를 벗어나면 캐시가 만료된 것으로 판단합니다. - nfs_update_attrtimeo()적응적 캐시 타임아웃 알고리즘입니다. 파일이 변경되지 않으면 타임아웃을 2배씩 증가시켜(
attrtimeo << 1) 서버 부하를 줄이고, 변경이 감지되면acregmin으로 리셋합니다. - attrtimeo_max타임아웃의 상한값으로, 마운트 옵션
acregmax(기본 60초) 또는acdirmax(기본 60초)에 의해 설정됩니다.
| 마운트 옵션 | 기본값(초) | 설명 |
|---|---|---|
acregmin | 3 | 일반 파일 속성 캐시 최소 시간 |
acregmax | 60 | 일반 파일 속성 캐시 최대 시간 |
acdirmin | 30 | 디렉토리 속성 캐시 최소 시간 |
acdirmax | 60 | 디렉토리 속성 캐시 최대 시간 |
actimeo= | - | 모든 속성 캐시 시간을 동일 값으로 설정 |
noac | - | 속성 캐시 완전 비활성화 (성능 저하 주의) |
데이터 캐시
NFS 클라이언트는 페이지 캐시를 사용하여 읽기/쓰기 데이터를 캐싱합니다. 일반적으로 close-to-open(CTO) 시맨틱을 기준으로 일관성을 유지하도록 동작하며, 마운트 옵션과 서버 정책에 따라 관찰 시점에는 차이가 날 수 있습니다.
- close() 시: 수정된 데이터를 서버에 플러시하고, 속성을 서버와 동기화
- open() 시: 서버에 GETATTR을 보내 change attribute를 확인하고, 변경되었으면 캐시 무효화(Invalidation)
/* fs/nfs/file.c - open 시 캐시 검증 */
static int nfs_file_open(struct inode *inode,
struct file *filp)
{
int res;
/* inode 속성 재검증 (CTO) */
res = nfs_check_flags(filp->f_flags);
if (res)
return res;
res = nfs_open(inode, filp);
return res;
}
/* fs/nfs/inode.c - close 시 데이터 플러시 */
static void nfs_file_clear_open_context(struct file *filp)
{
struct nfs_open_context *ctx = nfs_file_open_context(filp);
if (ctx) {
filp->private_data = NULL;
put_nfs_open_context_sync(ctx);
}
}
NFS 읽기/쓰기 I/O 경로
NFS 클라이언트의 데이터 I/O는 페이지 캐시를 통해 처리됩니다. 읽기는 readahead 알고리즘에 의해 사전 페칭되고, 쓰기는 writeback 메커니즘에 의해 비동기적으로 서버에 플러시됩니다.
/* fs/nfs/read.c - NFS 읽기 I/O */
static void nfs_readahead(struct readahead_control *ractl)
{
struct nfs_pageio_descriptor pgio;
/* readahead 윈도우 내 페이지들을 모아서 */
nfs_pageio_init_read(&pgio, ractl->mapping->host,
0, &nfs_async_read_completion_ops);
/* 연속 페이지를 하나의 READ RPC로 병합 */
readahead_expand(ractl, readahead_pos(ractl),
readahead_length(ractl));
nfs_pageio_complete_read(&pgio);
/* RPC 완료 시 콜백으로 페이지 unlock */
}
/* fs/nfs/write.c - NFS 쓰기 I/O */
static int nfs_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
struct nfs_pageio_descriptor pgio;
nfs_pageio_init_write(&pgio, mapping->host,
0, 0, &nfs_async_write_completion_ops);
/* dirty 페이지를 수집하여 WRITE RPC 전송
* wsize 크기로 분할하여 전송
*
* 안정성 모드:
* UNSTABLE → 서버 캐시만 (빠름, COMMIT 필요)
* FILE_SYNC → 서버 디스크 동기화 (느림, COMMIT 불필요) */
nfs_pageio_complete(&pgio);
return pgio.pg_error;
}
/* COMMIT: UNSTABLE 쓰기 후 서버 디스크 동기화 */
static int nfs_commit_inode(struct inode *inode, int how)
{
/* close() 또는 fsync() 시 호출
* COMPOUND: SEQUENCE + PUTFH + COMMIT(offset=0, count=0)
*
* 서버 verifier가 이전과 다르면
* → 서버 재시작 감지 → dirty 페이지 재전송 */
}
코드 설명
- nfs_readahead()
fs/nfs/read.c의 NFS 선행 읽기 함수입니다. VFS의 readahead 프레임워크가 호출하며, 연속된 페이지들을 모아 하나의 READ RPC로 병합하여 네트워크 왕복 횟수를 최소화합니다. - nfs_pageio_init_read()읽기용 페이지 I/O 디스크립터를 초기화합니다. pNFS 환경에서는 레이아웃에 따라 데이터 서버로 직접 읽기가 수행되고, 일반 NFS에서는 MDS에 READ RPC를 보냅니다.
- nfs_writepages()
fs/nfs/write.c의 NFS 쓰기 플러시 함수입니다. 커널의 writeback 스레드가 dirty 페이지를 서버에 보낼 때 호출되며,wsize크기로 분할하여 WRITE RPC를 전송합니다. - UNSTABLE / FILE_SYNCUNSTABLE 모드는 서버 메모리 캐시까지만 쓰기하여 빠르지만, 이후 COMMIT이 필요합니다. FILE_SYNC는 서버 디스크까지 동기화하여 느리지만 COMMIT이 불필요합니다.
- nfs_commit_inode()
close()또는fsync()시 호출되어 UNSTABLE로 쓴 데이터를 서버 디스크에 커밋합니다. 서버의 write verifier가 변경되면 서버 재시작을 감지하여 dirty 페이지를 재전송합니다.
wsize=1048576(1MB)이면 최대 256개의 4K 페이지를 하나의 RPC로 전송할 수 있어 오버헤드(Overhead)가 크게 줄어듭니다. pNFS 환경에서는 여러 데이터 서버에 병렬 WRITE가 수행됩니다.
FS-Cache / CacheFiles
FS-Cache는 NFS 데이터를 로컬 디스크에 영구적으로 캐싱하는 커널 프레임워크입니다. 네트워크 대역폭 절약과 읽기 성능 향상에 효과적입니다.
# FS-Cache 설정 (CacheFiles 백엔드)
# 1. 캐시 디렉토리 생성
mkdir -p /var/cache/fscache
# 2. /etc/cachefilesd.conf
dir /var/cache/fscache
tag mycache
brun 10%
bcull 7%
bstop 3%
frun 10%
fcull 7%
fstop 3%
# 3. cachefilesd 시작
systemctl start cachefilesd
# 4. NFS 마운트 시 fsc 옵션 추가
mount -t nfs -o vers=4.2,fsc server:/export/data /mnt/nfs
# 캐시 상태 확인
cat /proc/fs/fscache/stats
잠금과 위임
바이트 범위 잠금 (Byte-Range Locking)
NFSv4에서 파일 잠금은 프로토콜에 내장되어 있습니다. POSIX 호환 fcntl() 잠금이 네트워크를 통해 서버에 전달됩니다.
/* fs/nfs/nfs4proc.c - NFSv4 LOCK 연산 */
static int nfs4_proc_lock(struct file *filp,
int cmd, struct file_lock *request)
{
struct nfs_open_context *ctx;
struct nfs4_state *state;
int status;
/* NFSv4 LOCK compound:
* SEQUENCE + LOCK(stateid, offset, length, type)
*
* 잠금 유형:
* READ_LT - 공유 잠금 (F_RDLCK)
* WRITE_LT - 배타 잠금 (F_WRLCK)
* READW_LT / WRITEW_LT - 대기형 잠금 (F_SETLKW) */
ctx = nfs_file_open_context(filp);
state = ctx->state;
if (request->fl_type == F_UNLCK)
status = nfs4_proc_unlck(state, cmd, request);
else
status = nfs4_proc_setlk(state, cmd, request);
return status;
}
위임 상세
위임은 잠금과 밀접한 관련이 있습니다. WRITE 위임을 가진 클라이언트는 서버에 LOCK 요청을 보내지 않고 로컬에서 잠금을 처리할 수 있습니다.
/* fs/nfs/delegation.c - 위임 기반 잠금 최적화 */
int nfs4_lock_delegation_recall(
struct file_lock *fl,
struct nfs4_state *state,
const nfs4_stateid *stateid)
{
struct nfs_inode *nfsi = NFS_I(state->inode);
struct nfs_delegation *delegation;
delegation = rcu_dereference(nfsi->delegation);
if (delegation == NULL)
return 0;
/* WRITE 위임이면 로컬 잠금으로 처리 */
if (delegation->type == FMODE_WRITE)
return locks_lock_file_wait(fl);
return 0;
}
상태 복구 (State Recovery)
NFSv4 서버가 재시작되면, 클라이언트는 grace period 동안 이전 상태(open, lock, delegation)를 복구해야 합니다. 리눅스 NFS 클라이언트는 nfs4_state_manager() 워크큐에서 이를 자동 처리합니다.
/* fs/nfs/nfs4state.c - NFSv4 상태 관리자 */
static void nfs4_state_manager(struct nfs_client *clp)
{
int status;
/* 상태 복구 순서 */
do {
/* 1. 클라이언트 ID 재설정 (EXCHANGE_ID) */
if (test_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state)) {
status = nfs4_reclaim_lease(clp);
if (status)
continue;
}
/* 2. 세션 복구 (CREATE_SESSION) */
if (test_bit(NFS4CLNT_SESSION_RESET, &clp->cl_state)) {
status = nfs4_reset_session(clp);
}
/* 3. open/lock 상태 복구 */
if (test_bit(NFS4CLNT_RECLAIM_REBOOT, &clp->cl_state)) {
status = nfs4_do_reclaim(clp,
clp->cl_mvops->reboot_recovery_ops);
}
/* 4. 위임 복구 */
nfs_delegation_reap_unclaimed(clp);
} while (test_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state));
}
/proc/fs/nfsd/nfsv4leasetime에서 lease 시간을 설정합니다.
보안
인증 방식
| 보안 방식 | 인증 | 무결성 | 암호화(Encryption) | 설명 |
|---|---|---|---|---|
AUTH_SYS (sec=sys) | UID/GID | 없음 | 없음 | 기본값. 클라이언트가 보낸 UID를 신뢰 |
AUTH_NONE (sec=none) | 없음 | 없음 | 없음 | 인증 없음 (anonymous) |
RPCSEC_GSS/krb5 | Kerberos | 없음 | 없음 | Kerberos 인증만 |
RPCSEC_GSS/krb5i | Kerberos | HMAC | 없음 | 인증 + 메시지 무결성 |
RPCSEC_GSS/krb5p | Kerberos | HMAC | AES | 인증 + 무결성 + 데이터 암호화 |
Kerberos 설정
# Kerberos NFS 설정 (서버 측)
# 1. Kerberos keytab 생성 (KDC에서)
kadmin.local
addprinc -randkey nfs/server.example.com@EXAMPLE.COM
ktadd -k /etc/krb5.keytab nfs/server.example.com@EXAMPLE.COM
# 2. /etc/exports 보안 설정
/export/secure *(sec=krb5p,rw,sync,no_subtree_check)
# 3. rpc.gssd (클라이언트) / rpc.svcgssd (서버) 시작
systemctl enable --now rpc-gssd # 클라이언트
systemctl enable --now rpc-svcgssd # 서버 (선택적)
# 4. 마운트
mount -t nfs -o vers=4.2,sec=krb5p server:/export/secure /mnt/secure
/* net/sunrpc/auth_gss/auth_gss.c - RPCSEC_GSS 인증 */
static const struct rpc_authops authgss_ops = {
.owner = THIS_MODULE,
.au_flavor = RPC_AUTH_GSS,
.au_name = "RPCSEC_GSS",
.create = gss_create,
.destroy = gss_destroy,
.hash_cred = gss_hash_cred,
.lookup_cred = gss_lookup_cred,
.crcreate = gss_create_cred,
.info2flavor = gss_mech_info2flavor,
};
/* GSS 컨텍스트 구조: 세션 키, 시퀀스 번호, 알고리즘 */
struct gss_cl_ctx {
refcount_t count;
enum rpc_gss_svc gc_service; /* none/integrity/privacy */
struct gss_ctx *gc_gss_ctx; /* GSS 메커니즘 컨텍스트 */
struct xdr_netobj gc_wire_ctx; /* 와이어 상의 컨텍스트 핸들 */
atomic_t gc_seq; /* 시퀀스 번호 (재전송 방지) */
};
NFSv4 ACL
NFSv4는 Windows ACL과 유사한 풍부한 ACL 체계를 지원합니다. POSIX 모드 비트와 별개로, 세밀한 접근 제어(Access Control)가 가능합니다.
# NFSv4 ACL 설정 (nfs4-acl-tools 패키지)
# ACL 조회
nfs4_getfacl /mnt/nfs/file.txt
# 출력 예:
# A::OWNER@:rwatTnNcCoy
# A::GROUP@:rtncy
# A::EVERYONE@:rtncy
# 특정 사용자에게 읽기/쓰기 권한 부여
nfs4_setfacl -a "A::user@EXAMPLE.COM:rwatTnNcCoy" /mnt/nfs/file.txt
# ACL 형식: type:flags:principal:permissions
# type: A(Allow), D(Deny), U(aUdit), L(aLarm)
# flags: f(file-inherit), d(directory-inherit), g(group)
# principal: OWNER@, GROUP@, EVERYONE@, user@domain
NFS over RDMA (NFS/RDMA)
NFS/RDMA는 TCP 대신 RDMA(Remote Direct Memory Access) 전송을 사용하여 CPU 오버헤드를 최소화하고 극도로 낮은 지연 시간을 달성합니다. InfiniBand, RoCE(RDMA over Converged Ethernet), iWARP 네트워크에서 동작합니다.
RDMA 전송 아키텍처
/* net/sunrpc/xprtrdma/ - 클라이언트 RDMA 전송 */
/* xprtrdma: RPC/RDMA 클라이언트 전송 모듈 */
struct rpcrdma_xprt {
struct rpc_xprt rx_xprt;
struct rpcrdma_ia rx_ia; /* RDMA 어댑터 */
struct rpcrdma_ep rx_ep; /* RDMA 엔드포인트 */
struct rpcrdma_buffer rx_buf; /* 버퍼 풀 */
};
/* net/sunrpc/svcrdma/ - 서버 RDMA 전송 */
/* svcrdma: RPC/RDMA 서버 전송 모듈 */
struct svcxprt_rdma {
struct svc_xprt sc_xprt;
struct rdma_cm_id *sc_cm_id; /* CM 연결 ID */
struct ib_pd *sc_pd; /* Protection Domain */
struct ib_cq *sc_rq_cq; /* 수신 CQ */
struct ib_cq *sc_sq_cq; /* 송신 CQ */
u32 sc_max_requests; /* 최대 동시 요청 */
};
# NFS/RDMA 마운트
mount -t nfs -o vers=4.2,proto=rdma,port=20049 \
server:/export/data /mnt/nfs_rdma
# 서버 측: RDMA 전송 활성화
# /etc/nfs.conf
[nfsd]
rdma=y
rdma-port=20049
# 커널 모듈 로드
modprobe svcrdma # 서버
modprobe xprtrdma # 클라이언트
# RDMA 포트 활성화 (서버)
echo rdma 20049 > /proc/fs/nfsd/portlist
성능 튜닝
rsize/wsize 최적화
읽기/쓰기 전송 크기는 NFS 성능에 가장 큰 영향을 미치는 파라미터입니다. NFSv3는 최대 64KB, NFSv4는 최대 1MB까지 지원합니다.
# rsize/wsize 테스트
# 작은 크기 (비효율)
mount -t nfs -o vers=4.2,rsize=4096,wsize=4096 server:/data /mnt/test
dd if=/mnt/test/largefile of=/dev/null bs=1M count=1024
# 100 MB/s (4K RPC 요청 = 높은 오버헤드)
# 최적 크기 (기본 협상)
mount -t nfs -o vers=4.2,rsize=1048576,wsize=1048576 server:/data /mnt/test
dd if=/mnt/test/largefile of=/dev/null bs=1M count=1024
# 800+ MB/s (1M RPC 요청 = 낮은 오버헤드)
nconnect 병렬 연결
커널 5.3+에서 도입된 nconnect 옵션은 단일 NFS 마운트에서 여러 TCP 연결을 사용하여 대역폭을 극대화합니다. 단일 TCP 연결의 병목(헤드오브라인 블로킹, 혼잡 윈도우 제한)을 회피합니다.
# nconnect=8: 8개 TCP 연결로 병렬 I/O
mount -t nfs -o vers=4.2,nconnect=8 server:/data /mnt/nfs
# 연결 확인
ss -tn | grep :2049 | wc -l
# 출력: 8
# max_connect (v5.14+): 다중 서버 IP 트렁킹
mount -t nfs -o vers=4.1,max_connect=4 server:/data /mnt/nfs
async vs sync
/etc/exports에서 async 옵션은 서버가 데이터를 디스크에 쓰기 전에 클라이언트에 완료 응답을 보냅니다. 서버 장애 시 데이터 유실이 발생할 수 있으므로, 프로덕션 환경에서는 반드시 sync를 사용하세요.
/* fs/nfsd/vfs.c - sync vs async 쓰기 처리 */
static __be32
nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset,
struct kvec *vec, int vlen,
unsigned long *cnt, int stable,
__be32 *verf)
{
/* stable 모드:
* NFS_UNSTABLE (0) - 비동기 (서버 캐시만)
* NFS_DATA_SYNC (1) - 데이터 디스크 동기화
* NFS_FILE_SYNC (2) - 데이터 + 메타데이터 동기화 */
if (stable != NFS_UNSTABLE) {
flags |= RWF_SYNC; /* O_SYNC 쓰기 */
}
host_err = vfs_writev(nf->nf_file, vec, vlen, &offset, flags);
/* ... */
}
sunrpc 디버깅(Debugging)
# sunrpc 디버그 플래그 활성화
# 클라이언트 RPC 디버그
echo 0xFFFF > /proc/sys/sunrpc/rpc_debug
# NFS 클라이언트 디버그
echo 0xFFFF > /proc/sys/sunrpc/nfs_debug
# NFS 서버 디버그
echo 0xFFFF > /proc/sys/sunrpc/nfsd_debug
# rpcdebug 유틸리티 (세분화된 제어)
rpcdebug -m rpc -s all # 모든 RPC 디버그
rpcdebug -m nfs -s vfs fh # NFS VFS + 파일핸들 디버그
rpcdebug -m nfsd -s proc # nfsd 프로시저 디버그
rpcdebug -m rpc -c all # 디버그 해제
# dmesg에서 NFS 관련 로그 확인
dmesg | grep -i nfs
nfsstat 모니터링
# 서버 통계 (주요 항목)
nfsstat -s
# Server nfs v4:
# null getattr setattr lookup access readlink
# 0 152634 8432 45231 89012 1234
# read write create remove rename ...
# 234567 123456 5678 2345 678 ...
# 클라이언트 통계
nfsstat -c
# 마운트별 상세 통계
nfsstat -m
# 또는
cat /proc/self/mountstats
# 실시간 모니터링 (1초 간격)
nfsstat -l
# 주요 지표 해석:
# - retrans: 재전송 횟수 (높으면 네트워크 문제)
# - jitter: RPC 응답 시간 편차 (높으면 서버 부하)
# - backlog: 대기 큐 깊이 (높으면 nconnect 필요)
커널 파라미터 튜닝
# /etc/sysctl.conf - NFS 관련 커널 파라미터
# sunrpc 전송 버퍼 크기
sunrpc.tcp_slot_table_entries = 128 # 기본 2, 동시 RPC 요청 수
sunrpc.udp_slot_table_entries = 128
# TCP 버퍼 크기 (NFS 대역폭에 영향)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# NFS 서버 스레드 수 (높은 동시 접속 시)
# /etc/nfs.conf
[nfsd]
threads=64
grace-time=90
lease-time=90
# NFS 서버 TCP window 크기
# 적용
sysctl -p
성능 최적화 체크리스트
| 항목 | 권장값 | 영향 |
|---|---|---|
| NFS 버전 | v4.2 | compound ops, 서버 사이드 복사, 단일 포트 |
| rsize/wsize | 1048576 (1MB) | 대용량 전송 시 오버헤드 최소화 |
| nconnect | 4~16 | 병렬 TCP 연결로 대역폭 극대화 |
| nfsd threads | CPU 코어 x 2~4 | 서버 동시 처리 능력 |
| sunrpc slot table | 128+ | 동시 RPC 요청 수 |
| TCP buffer | 16MB+ | WAN/고지연 환경 처리량 |
| 전송 프로토콜 | TCP (LAN), RDMA (HPC) | 지연 시간, CPU 사용률 |
| FS-Cache | 활성화 (읽기 위주) | 반복 읽기 시 네트워크 절감 |
| noatime | 서버/클라이언트 모두 | 불필요한 SETATTR 감소 |
커널 소스 구조
Linux NFS 관련 코드의 디렉토리 구조를 정리합니다.
커널 설정 (Kconfig)
# NFS 관련 주요 Kconfig 옵션
CONFIG_NFS_FS=m # NFS 클라이언트
CONFIG_NFS_V3=y # NFSv3 지원
CONFIG_NFS_V3_ACL=y # NFSv3 POSIX ACL
CONFIG_NFS_V4=y # NFSv4 지원
CONFIG_NFS_V4_1=y # NFSv4.1 (pNFS 필수)
CONFIG_NFS_V4_2=y # NFSv4.2
CONFIG_NFS_FSCACHE=y # FS-Cache 통합
CONFIG_NFS_USE_KERNEL_DNS=y # 커널 DNS 확인
CONFIG_NFSD=m # NFS 서버 (knfsd)
CONFIG_NFSD_V3=y # 서버 NFSv3
CONFIG_NFSD_V3_ACL=y # 서버 NFSv3 ACL
CONFIG_NFSD_V4=y # 서버 NFSv4
CONFIG_NFSD_PNFS=y # 서버 pNFS
CONFIG_NFSD_BLOCKLAYOUT=y # pNFS 블록 레이아웃
CONFIG_NFSD_SCSILAYOUT=y # pNFS SCSI 레이아웃
CONFIG_NFSD_FLEXFILELAYOUT=y # pNFS FlexFiles
CONFIG_SUNRPC=m # SUNRPC 프레임워크
CONFIG_SUNRPC_GSS=m # RPCSEC_GSS 인증
CONFIG_SUNRPC_XPRT_RDMA=m # RPC/RDMA 전송
CONFIG_PNFS_FILE_LAYOUT=m # pNFS 파일 레이아웃
CONFIG_PNFS_BLOCK=m # pNFS 블록 레이아웃
CONFIG_PNFS_FLEXFILE_LAYOUT=m # pNFS FlexFiles 레이아웃
NFS 실무 활용
고가용성 NFS (HA-NFS)
프로덕션 환경에서 NFS 서버 단일 장애점을 해소하기 위해 고가용성(HA) 구성은 매우 중요합니다. Linux에서는 Pacemaker/Corosync 클러스터 프레임워크와 공유 스토리지(DRBD, SAN)를 조합하여 active/passive NFS HA를 구현합니다.
# HA-NFS 구성 요소
# 1. 공유 스토리지: DRBD 또는 SAN LUN
# 2. 클러스터: Pacemaker + Corosync
# 3. 가상 IP (VIP): 클라이언트 접속용
# 4. NFSv4 grace 관리: v4recoverydir 공유
# Pacemaker NFS 리소스 설정 예시
pcs resource create nfs_vip ocf:heartbeat:IPaddr2 \
ip=192.168.1.100 cidr_netmask=24 \
op monitor interval=10s
pcs resource create nfs_share ocf:heartbeat:Filesystem \
device="/dev/drbd0" directory="/export" \
fstype="xfs" \
op monitor interval=20s
pcs resource create nfs_daemon ocf:heartbeat:nfsserver \
nfs_shared_infodir="/export/nfsinfo" \
nfs_ip=192.168.1.100 \
op monitor interval=30s
pcs resource create nfs_root exportfs \
clientspec="192.168.1.0/24" \
options="rw,sync,no_root_squash" \
directory="/export/data" \
fsid=0 \
op monitor interval=30s
# 리소스 순서/배치 제약 (반드시 순서대로 시작)
pcs constraint order nfs_share then nfs_daemon
pcs constraint order nfs_daemon then nfs_root
pcs constraint colocation add nfs_root with nfs_daemon
pcs constraint colocation add nfs_daemon with nfs_share
pcs constraint colocation add nfs_share with nfs_vip
/var/lib/nfs/v4recovery/ 디렉토리를 공유 스토리지에 두어 클라이언트 상태 복구(reclaim)가 가능하도록 합니다. nfs_ip 설정으로 VIP를 NFS에 바인딩하여 클라이언트가 grace period 내에 상태를 복구할 수 있습니다.
NFS와 컨테이너(Container)/Kubernetes
NFS는 Kubernetes의 PersistentVolume(PV) 백엔드로 널리 사용됩니다. 여러 Pod에서 동시 읽기/쓰기(ReadWriteMany)가 가능한 몇 안 되는 스토리지 유형입니다.
# Kubernetes NFS PersistentVolume 정의
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany # NFS는 RWX 지원
persistentVolumeReclaimPolicy: Retain
nfs:
server: nfs-server.example.com
path: /export/k8s-data
mountOptions:
- vers=4.2
- hard
- nconnect=4
- rsize=1048576
- wsize=1048576
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
volumeName: nfs-pv
# NFS CSI 드라이버 (동적 프로비저닝)
# csi-driver-nfs: Kubernetes SIG Storage 프로젝트
# 서버에 PVC별 서브디렉토리 자동 생성/삭제
# Helm으로 설치
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs \
--namespace kube-system
# StorageClass 정의
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: nfs-server.example.com
share: /export/k8s-dynamic
mountPermissions: "0775"
mountOptions:
- vers=4.2
- hard
- nconnect=4
reclaimPolicy: Delete
volumeBindingMode: Immediate
docker volume create --driver local --opt type=nfs --opt o=addr=server,vers=4.2,hard --opt device=:/export/data nfs_vol
NFSv4.1 트렁킹 (Trunking)
NFSv4.1 트렁킹은 클라이언트가 서버의 여러 네트워크 인터페이스를 통해 단일 세션의 성능을 확장하는 메커니즘입니다. EXCHANGE_ID 응답의 server_owner가 동일하면 같은 서버로 인식하여 연결을 병합합니다.
| 트렁킹 유형 | 설명 | 요구사항 |
|---|---|---|
| clientid 트렁킹 | 동일 client ID로 여러 세션 → 각 세션이 독립 네트워크 경로 사용 | server_owner 일치 |
| 세션 트렁킹 | 하나의 세션에 여러 연결 바인딩 → 세션 내 요청이 여러 경로로 분산 | server_owner + server_scope 일치 |
# 트렁킹 설정: 서버의 여러 IP에 연결
# 첫 번째 마운트 (기본 연결)
mount -t nfs -o vers=4.1 server-ip1:/export /mnt/nfs
# 추가 연결 경로 바인딩 (max_connect, 커널 5.14+)
mount -t nfs -o vers=4.1,max_connect=4 server-ip2:/export /mnt/nfs2
# nconnect는 단일 IP에 여러 TCP 연결 (트렁킹과 별개)
mount -t nfs -o vers=4.1,nconnect=8 server:/export /mnt/nfs
# 트렁킹 상태 확인
cat /proc/self/mountstats | grep -A 20 "nfs4"
NFSv4 마이그레이션과 리퍼럴
NFSv4는 서버 간 파일시스템 마이그레이션과 리퍼럴(referral)을 프로토콜 수준에서 지원합니다. 클라이언트에게 투명하게 스토리지를 이동하거나 다른 서버로 리다이렉트할 수 있습니다.
| 메커니즘 | 에러 코드 | 용도 | 설명 |
|---|---|---|---|
| 마이그레이션 | NFS4ERR_MOVED | 서버 교체/로드밸런싱 | 파일시스템이 다른 서버로 이동됨. 클라이언트가 새 위치를 자동으로 따라감 |
| 리퍼럴 | NFS4ERR_MOVED | 분산 네임스페이스(Namespace) | pseudo-fs의 특정 경로가 다른 서버를 가리킴. 클라이언트가 자동 리다이렉트 |
/* fs/nfs/nfs4state.c - 마이그레이션 감지 및 처리 */
static int nfs4_handle_migration(struct nfs_client *clp)
{
/* NFS4ERR_MOVED 수신 시:
* 1. fs_locations 속성 조회 (GETATTR)
* → 새 서버 주소 + 경로 획득
* 2. 새 서버에 EXCHANGE_ID + CREATE_SESSION
* 3. 기존 마운트를 새 서버로 전환
* 4. open/lock 상태 복구 (reclaim)
*
* 클라이언트 프로세스에게는 투명:
* 잠시 지연 후 정상 I/O 재개 */
}
/* fs/nfs/nfs4namespace.c - 리퍼럴 처리 */
static struct vfsmount *nfs4_submount(
struct nfs_server *server,
struct dentry *dentry,
struct nfs_fh *fh,
struct nfs_fattr *fattr)
{
/* 리퍼럴: pseudo-fs 교차점(junction)에서 자동 마운트
*
* 예: 서버 A의 /export/project가 서버 B를 가리킴
* → 클라이언트가 /mnt/nfs/project 접근 시
* → 자동으로 서버 B에 연결하여 서브마운트 */
struct nfs4_fs_locations *locations;
locations = nfs4_proc_fs_locations(server, fh, fattr);
return nfs_follow_referral(dentry, locations);
}
# NFSv4 리퍼럴 설정 (서버 측)
# 서버 A: pseudo-fs root 설정
# /etc/exports
/export *(fsid=0,ro,no_subtree_check,crossmnt)
/export/local *(rw,sync,no_subtree_check)
# 리퍼럴 지점 생성 (서버 B의 /data를 가리킴)
mkdir -p /export/remote
mount -t nfs4 -o vers=4.2 serverB:/data /export/remote
# 또는 nfs-utils의 junction 도구 사용
nfsref add /export/remote serverB:/data
# 클라이언트에서 투명하게 접근
mount -t nfs -o vers=4.2 serverA:/ /mnt/nfs
ls /mnt/nfs/local # → 서버 A
ls /mnt/nfs/remote # → 자동으로 서버 B로 리다이렉트
CTDB 통합 NFS 클러스터링
CTDB(Clustered Trivial Database)는 Samba 프로젝트의 분산 잠금 및 IP 관리 레이어입니다. 여러 노드에서 NFS를 동시에 서비스하면서 IP 페일오버와 세션 일관성을 제공합니다.
CTDB 아키텍처
| 구성 요소 | 역할 |
|---|---|
| CTDB 데몬 | 각 노드에서 실행, 분산 TDB(Trivial Database) 동기화 |
| 공유 스토리지 | DRBD 또는 GFS2/OCFS2 위에 NFS export |
| 공개 IP (VIP) | 클라이언트가 연결하는 가상 IP. 장애 시 다른 노드로 이전 |
| 락 매니저 | NLM(NFS Lock Manager) 상태를 CTDB로 클러스터 전반에 공유 |
IP 페일오버 흐름
# CTDB 상태 확인
$ ctdb status
$ ctdb ip
# 노드 장애 시 자동 IP 이전 (takeover)
# 1. node1이 응답 없음 → CTDB 감지 (recovery election)
# 2. node2가 VIP(192.168.1.100)를 자신에게 가져옴
# 3. NFS 클라이언트: 재연결 or soft 마운트 타임아웃 후 재시도
# CTDB 기본 설정 (/etc/ctdb/ctdb.conf)
# CTDB_NODES=/etc/ctdb/nodes
# CTDB_PUBLIC_ADDRESSES=/etc/ctdb/public_addresses
# CTDB_RECOVERY_LOCK=/shared/ctdb/recovery.lck
# /etc/ctdb/nodes (모든 노드 IP)
192.168.10.1
192.168.10.2
# /etc/ctdb/public_addresses (공개 VIP)
192.168.1.100/24 eth0
192.168.1.101/24 eth0
# systemd로 CTDB 시작
$ systemctl enable --now ctdb
NFS 클러스터링: knfsd vs Ganesha
| 항목 | knfsd + CTDB | NFS Ganesha + CTDB |
|---|---|---|
| NFS 서버 | 커널 nfsd | 사용자 공간(User Space) ganesha |
| 클러스터링 | CTDB (IP 페일오버) | CTDB + FSAL 플러그인 |
| 백엔드 지원 | POSIX 파일시스템 | FSAL_VFS/FSAL_CEPH/FSAL_GLUSTER |
| NFSv4.1 pNFS | 제한적 | 완전 지원 |
| 성능 | 높음 (커널) | 낮음 (사용자 공간) |
| 기능 유연성 | 낮음 | 높음 (FSAL 플러그인) |
| 적합 용도 | 단순 HA NAS | 분산 스토리지 NAS |
NFS Ganesha — 사용자 공간 NFS 서버
NFS Ganesha는 사용자 공간에서 동작하는 NFS 서버입니다. FSAL(File System Abstraction Layer) 플러그인 아키텍처로 CephFS, GlusterFS, POSIX 파일시스템 등 다양한 백엔드를 지원합니다.
FSAL 플러그인 아키텍처
| FSAL 플러그인 | 백엔드 | 특징 |
|---|---|---|
FSAL_VFS | POSIX 파일시스템 (ext4, XFS 등) | 일반 로컬 파일시스템 |
FSAL_CEPH | CephFS | libcephfs 라이브러리 사용 |
FSAL_GLUSTER | GlusterFS | libgfapi 라이브러리 사용 |
FSAL_LUSTRE | Lustre | pNFS striping 지원 |
FSAL_RGW | Ceph RADOS Gateway (S3) | 오브젝트 스토리지를 파일처럼 |
NFS Ganesha 설정 예제
# /etc/ganesha/ganesha.conf
GLOBAL {
NFS_Port = 2049;
MNT_Port = 20048;
}
LOG {
Default_Log_Level = WARN;
Components { NFS = INFO; }
}
EXPORT {
Export_Id = 1;
Path = /;
FSAL { Name = CEPH; } # CephFS FSAL 사용
Access_Type = RW;
Squash = No_Root_Squash;
Protocols = 4;
Transports = TCP;
SecType = sys; # 또는 krb5 Kerberos
}
# CephFS 접근을 위한 keyring 설정
# /etc/ceph/ceph.client.ganesha.keyring
[client.ganesha]
key = AQCx...==
# Ganesha 서비스 시작
$ systemctl enable --now nfs-ganesha
knfsd vs Ganesha 성능 트레이드오프
| 항목 | knfsd | NFS Ganesha |
|---|---|---|
| 아키텍처 | 커널 공간 | 사용자 공간 |
| 메모리 복사 | 최소 (zero-copy 경로 존재) | 커널↔사용자 복사 발생 |
| CPU 오버헤드 | 낮음 | 높음 (context switch) |
| 처리량 (대파일) | 높음 | 낮음 (10~30% 손실) |
| 소규모 메타데이터 | 일반 | 플러그인에 따라 다름 |
| pNFS | 제한적 | 완전 지원 |
| CephFS 통합 | ❌ (POSIX 레이어 필요) | ✅ FSAL_CEPH 직접 |
문제 해결
자주 발생하는 문제
| 증상 | 원인 | 해결 |
|---|---|---|
| mount: RPC: Program not registered | nfsd 미실행 | systemctl start nfs-server |
| mount: access denied | exports 설정 오류 | /etc/exports 확인 + exportfs -ra |
| Stale file handle | 서버 재export/재시작 | umount -f + 재마운트 |
| NFS: nfs4_reclaim_open_state | 서버 lease 만료 | 네트워크/서버 상태 확인 |
| nfs: server not responding | 네트워크 단절/서버 과부하 | 네트워크 확인, nfsd 스레드 증가 |
| Permission denied (UID mismatch) | UID/GID 불일치 | ID 매핑 또는 sec=krb5 사용 |
| 느린 ls (디렉토리 리스팅) | readdirplus 비활성화 | nordirplus 옵션 제거 확인 |
디버깅 명령어
# NFS 마운트 상세 정보
cat /proc/mounts | grep nfs
mount -l | grep nfs
# 마운트별 상세 통계 (RPC 지연, 재전송)
cat /proc/self/mountstats
# NFSv4 open 파일/위임 상태
cat /proc/fs/nfsd/clients/*/states
# RPC 전송 상태
cat /proc/net/rpc/nfs # 클라이언트
cat /proc/net/rpc/nfsd # 서버
# NFSv4 lease 시간 확인/설정
cat /proc/fs/nfsd/nfsv4leasetime
echo 90 > /proc/fs/nfsd/nfsv4leasetime
# 네트워크 패킷 캡처 (NFS 분석)
tcpdump -i eth0 -s 0 -w nfs.pcap port 2049
# Wireshark에서 nfs/rpc 프로토콜로 분석
# systemd 기반 NFS 서비스 상태
systemctl status nfs-server nfs-mountd rpc-statd rpcbind
tshark -r nfs.pcap -Y "nfs" -T fields -e frame.time -e nfs.procedure_v4 -e nfs.status 명령으로 프로시저별 응답 시간과 에러를 추출할 수 있습니다.
ftrace/tracepoint를 이용한 NFS 추적
커널의 NFS 서브시스템에는 다양한 tracepoint가 내장되어 있어 런타임 성능 분석과 디버깅에 활용할 수 있습니다.
# NFS 관련 tracepoint 목록 확인
cat /sys/kernel/debug/tracing/available_events | grep nfs
# nfs:nfs_readpage_done
# nfs:nfs_writeback_done
# nfs:nfs_commit_done
# nfs:nfs4_open_file
# nfs:nfs4_close
# nfs:nfs4_lock
# nfs:nfs4_delegreturn
# nfs:nfs4_state_mgr
# ...
# NFS READ 성능 추적
echo 1 > /sys/kernel/debug/tracing/events/nfs/nfs_readpage_done/enable
echo 1 > /sys/kernel/debug/tracing/events/nfs/nfs_initiate_read/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 출력 예:
# nfs-1234 [002] .... nfs_initiate_read: fileid=0x3f offset=0 count=131072
# nfs-1234 [002] .... nfs_readpage_done: fileid=0x3f offset=0 status=0
# SUNRPC 레벨 tracepoint (RPC 지연 분석)
echo 1 > /sys/kernel/debug/tracing/events/sunrpc/rpc_task_begin/enable
echo 1 > /sys/kernel/debug/tracing/events/sunrpc/rpc_task_complete/enable
# bpftrace를 이용한 NFS 읽기 지연 히스토그램
bpftrace -e '
tracepoint:nfs:nfs_initiate_read {
@start[tid] = nsecs;
}
tracepoint:nfs:nfs_readpage_done /@start[tid]/ {
@usecs = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
Stale Filehandle 심층 분석
ESTALE(Stale file handle) 에러는 NFS에서 가장 빈번하게 겪는 문제 중 하나입니다. 발생 원인과 해결법을 상세히 정리합니다.
| 원인 | 상세 | 해결 |
|---|---|---|
| 서버 파일 삭제 | 클라이언트가 캐시한 파일핸들의 inode가 서버에서 삭제됨 | 클라이언트에서 해당 파일 참조 제거 후 재접근 |
| 서버 re-export | exportfs -r 후 파일시스템 ID 변경 | fsid= 옵션으로 고정 fsid 사용 |
| 서버 파일시스템 재생성 | mkfs 후 동일 경로로 마운트 → inode 번호 불일치 | 클라이언트 umount + 재마운트 |
| subtree_check | 파일이 export 경로 내에서 이동됨 | no_subtree_check 사용 (기본 권장) |
| 서버 inode 재사용 | 삭제 후 같은 inode 번호에 새 파일 생성 | 파일핸들의 generation 필드로 감지 |
# Stale filehandle 발생 시 복구 절차
# 1. 영향 받는 프로세스 확인
lsof /mnt/nfs 2>&1 | grep -i stale
# 2. 강제 umount (프로세스가 남아있어도)
umount -f /mnt/nfs
# 또는 지연 umount (나중에 정리)
umount -l /mnt/nfs
# 3. 재마운트
mount -t nfs -o vers=4.2 server:/export /mnt/nfs
# 예방: exports에서 fsid 고정 (재export 시 안정성)
# /etc/exports
/export/data 192.168.1.0/24(rw,sync,no_subtree_check,fsid=100)
성능 저하 진단 체크리스트
# NFS 성능 저하 단계별 진단
# 1단계: 기본 네트워크 확인
ping -c 10 nfs-server
iperf3 -c nfs-server -t 10
# 대역폭이 예상보다 낮으면 네트워크 문제
# 2단계: NFS 마운트 옵션 확인
cat /proc/mounts | grep nfs
# rsize/wsize, nconnect, vers 확인
# 3단계: RPC 재전송/타임아웃 확인
nfsstat -rc
# retrans가 높으면 네트워크 불안정 또는 서버 과부하
# 4단계: 서버 스레드 사용률 확인
cat /proc/net/rpc/nfsd | grep th
# "th <threads> <fullcnt> <10% 20% ... 100%>"
# 높은 비율이 90-100%에 몰리면 스레드 부족
# 5단계: 클라이언트 RPC backlog 확인
cat /proc/self/mountstats | grep -A 5 "backlog"
# backlog가 높으면 sunrpc.tcp_slot_table_entries 증가 필요
# 6단계: 서버 디스크 I/O 확인
iostat -x 1 5
# await가 높으면 서버 측 스토리지 병목
# 7단계: 속성 캐시 확인 (많은 GETATTR는 acregmin/max 조정)
nfsstat -c | grep -i getattr
# GETATTR 비율이 과도하면 actimeo 값 증가 고려
RPC 호출 흐름
NFS 작업은 모두 ONC RPC(Remote Procedure Call) 위에서 동작합니다. 클라이언트 VFS 요청이 rpc_clnt를 거쳐 xprt(전송 추상화)를 통해 네트워크로 나가고, 서버 측 sunrpc 디스패처가 nfsd로 전달하는 전체 경로를 분석합니다.
rpc_clnt 생성과 RPC 호출
rpc_clnt는 NFS 클라이언트가 서버와 통신하기 위한 핵심 구조체입니다. 마운트 시점에 생성되며, 모든 NFS 프로시저 호출의 진입점(Entry Point) 역할을 합니다.
/* net/sunrpc/clnt.c — rpc_clnt 생성 및 RPC 호출 핵심 */
struct rpc_create_args {
struct net *net;
int protocol; /* IPPROTO_TCP, IPPROTO_RDMA */
struct sockaddr *address;
const struct rpc_program *program;
u32 version;
rpc_authflavor_t authflavor; /* AUTH_UNIX, RPCSEC_GSS_KRB5 */
};
/* RPC 호출 흐름: rpc_run_task → rpc_execute → xprt_transmit */
struct rpc_clnt *rpc_create(struct rpc_create_args *args);
int rpc_call_sync(struct rpc_clnt *clnt,
const struct rpc_message *msg,
int flags);
/*
* rpc_call_sync 내부 흐름:
* 1. rpc_run_task() → rpc_task 생성/큐잉
* 2. rpc_execute() → 상태 머신 (call_start → call_encode → ...)
* 3. xprt_transmit() → 실제 TCP/RDMA 전송
* 4. xprt_complete_rqst() → 응답 수신, XDR 디코딩
*/
XDR 인코딩/디코딩 구조
/* fs/nfs/nfs4xdr.c — XDR 인코딩/디코딩 매크로 예시 */
static void encode_open(struct xdr_stream *xdr,
const struct nfs_openargs *arg,
struct compound_hdr *hdr)
{
encode_op_hdr(xdr, OP_OPEN, decode_open_maxsz, hdr);
encode_openflag(xdr, arg);
/* seqid → 4바이트 빅엔디안 인코딩 */
encode_nfs4_seqid(xdr, arg->seqid);
/* share_access, share_deny → 각 4바이트 */
encode_share_access(xdr, arg->open_flags);
/* owner: clientid(8B) + owner_len(4B) + owner_val(가변) */
encode_open_owner4(xdr, arg);
}
/* XDR 디코딩: 응답에서 stateid 추출 */
static int decode_open(struct xdr_stream *xdr,
struct nfs_openres *res)
{
__be32 *p;
int status;
status = decode_op_hdr(xdr, OP_OPEN);
if (status)
return status;
/* stateid: seqid(4B) + other(12B) = 16바이트 */
decode_open_stateid(xdr, &res->stateid);
decode_change_info(xdr, &res->cinfo);
decode_delegation(xdr, res);
return 0;
}
xprt_timer()가 타임아웃을 감지하고 call_status 상태 머신이 call_encode로 돌아가 재전송합니다. timeo/retrans 마운트 옵션으로 조절 가능합니다.
NFSv4 상태 관리
NFSv4는 NFSv3의 무상태 모델과 달리 상태 기반(stateful) 프로토콜입니다. 클라이언트가 서버에 세션을 설정하고, clientid → session → stateid 계층으로 열린 파일·잠금·위임 상태를 관리합니다.
/* fs/nfs/nfs4state.c — nfs4_state 핵심 구조체 */
struct nfs4_state {
struct list_head open_states; /* nfs4_state_owner의 열린 상태 목록 */
struct nfs4_state_owner *owner;
struct inode *inode;
unsigned long flags;
spinlock_t state_lock;
/* 서버에서 발급한 stateid — OPEN/LOCK/DELEGATION 각각 별도 */
nfs4_stateid stateid; /* open stateid */
nfs4_stateid open_stateid; /* 최신 open stateid 사본 */
unsigned int n_rdonly; /* 읽기 전용 오픈 수 */
unsigned int n_wronly; /* 쓰기 전용 오픈 수 */
unsigned int n_rdwr; /* 읽기/쓰기 오픈 수 */
struct list_head lock_states; /* 이 상태에 연결된 lock_state 목록 */
};
/* NFSv4 OPEN/CLOSE 시퀀스 (fs/nfs/nfs4proc.c) */
static int _nfs4_proc_open(struct nfs4_opendata *data)
{
/* 1. SEQUENCE — 세션 슬롯 예약 */
/* 2. PUTFH — 대상 디렉토리 파일핸들 설정 */
/* 3. OPEN — share_access/share_deny, claim 설정 */
/* 4. GETFH — 열린 파일의 파일핸들 획득 */
/* 5. GETATTR — 파일 속성 캐시 갱신 */
/* 결과: open stateid, change_info, delegation 수신 */
status = nfs4_call_sync(server->client, server, &msg, ...);
if (!status)
nfs4_opendata_to_nfs4_state(data);
return status;
}
/* CLOSE: open stateid를 서버에 반환 */
static int _nfs4_proc_close(struct nfs4_state *state, ...)
{
/* SEQUENCE + PUTFH + CLOSE(stateid) */
/* 서버가 stateid를 무효화하고 자원 해제 */
}
nfs4_state_manager 워크큐가 자동으로 EXCHANGE_CLIENT_ID → CREATE_SESSION → RECLAIM_COMPLETE 시퀀스를 실행하여 기존 상태를 복원합니다. 유예 기간(grace period) 동안 새로운 잠금은 거부됩니다.
Delegation
NFSv4 Delegation은 서버가 클라이언트에게 파일에 대한 독점적 캐싱 권한을 부여하는 메커니즘입니다. 읽기 위임(READ)이면 속성·데이터 캐시를 신뢰할 수 있고, 쓰기 위임(WRITE)이면 로컬에서 자유롭게 수정 후 나중에 서버로 플러시할 수 있습니다.
/* fs/nfs/delegation.c — delegation 수락/반환 */
void nfs_inode_set_delegation(struct inode *inode,
const nfs4_stateid *stateid,
fmode_t type,
...)
{
struct nfs_delegation *delegation;
delegation = kmalloc(sizeof(*delegation), GFP_NOFS);
nfs4_stateid_copy(&delegation->stateid, stateid);
delegation->type = type; /* FMODE_READ 또는 FMODE_WRITE */
delegation->change_attr = inode_peek_iversion_raw(inode);
/* 기존 delegation이 있으면 교체 */
rcu_assign_pointer(NFS_I(inode)->delegation, delegation);
/* delegation 있으면 GETATTR를 건너뛸 수 있음 */
}
/* delegation 반환: 더티 데이터 먼저 플러시 */
int nfs4_proc_delegreturn(struct inode *inode,
const struct cred *cred,
const nfs4_stateid *stateid, ...)
{
/* SEQUENCE + PUTFH + DELEGRETURN(stateid) */
/* 서버가 delegation 제거, 다른 클라이언트 진행 가능 */
}
/* fs/nfs/callback_proc.c — CB_RECALL 콜백 처리 */
__be32 nfs4_callback_recall(void *argp, void *resp,
struct cb_process_state *cps)
{
struct cb_recallargs *args = argp;
struct inode *inode;
/* 파일핸들로 inode 검색 */
inode = nfs_delegation_find_inode(cps->clp, &args->fh);
/* delegation 반환 예약 — 비동기 작업으로 처리 */
nfs_async_inode_return_delegation(inode, &args->stateid);
/* CB_RECALL 성공 응답 (실제 반환은 비동기) */
return htonl(NFS4_OK);
}
pNFS 레이아웃
pNFS(parallel NFS)는 NFSv4.1에서 도입된 데이터 경로 분산 메커니즘입니다. 메타데이터(MDS)와 데이터(DS)를 분리하여, 클라이언트가 데이터 서버에 직접 I/O를 수행합니다. 레이아웃 유형에 따라 file, block, object, flexfiles 네 가지 드라이버를 제공합니다.
/* fs/nfs/pnfs.h — pnfs_layout_segment 핵심 구조체 */
struct pnfs_layout_segment {
struct list_head pls_list; /* layout의 세그먼트 목록 */
struct pnfs_layout_range pls_range; /* offset, length, iomode */
refcount_t pls_refcount;
u32 pls_seq; /* layout stateid seqid */
bool pls_retry;
enum pnfs_try_status pls_lc_status;
struct pnfs_layout_hdr *pls_layout;
};
struct pnfs_layout_range {
u32 iomode; /* IOMODE_READ 또는 IOMODE_RW */
u64 offset; /* 시작 오프셋 */
u64 length; /* NFS4_MAX_UINT64이면 파일 끝까지 */
};
/* fs/nfs/pnfs.c — LAYOUTGET/LAYOUTRETURN 흐름 */
static struct pnfs_layout_segment *
send_layoutget(struct pnfs_layout_hdr *lo,
struct nfs_open_context *ctx,
nfs4_stateid *stateid,
const struct pnfs_layout_range *range, ...)
{
/* COMPOUND: SEQUENCE + LAYOUTGET */
/* 서버가 DS 주소 목록 + stripe 정보를 반환 */
/* 레이아웃 드라이버가 alloc_lseg()로 디코딩 */
}
int pnfs_layoutreturn_no_reclaim(struct pnfs_layout_hdr *lo, ...)
{
/* COMPOUND: SEQUENCE + LAYOUTRETURN */
/* 쓰기 완료 후 레이아웃을 MDS에 반환 */
/* CB_LAYOUTRECALL에 대한 응답으로도 호출됨 */
}
fs/nfs/flexfilelayout/에서 구현을 확인할 수 있습니다.
캐시 일관성(Cache Coherency) 모델
NFS는 close-to-open 일관성 모델을 사용합니다. 파일을 열 때(open) 서버에 속성을 확인하고, 닫을 때(close) 변경 사항을 서버에 반영합니다. 이 모델은 완전한 일관성과 성능 사이의 실용적인 타협점입니다.
# 속성 캐시 타임아웃 조정 예시
# 기본 설정 (일반적인 사용)
mount -t nfs4 server:/export /mnt/nfs
# 높은 일관성이 필요한 경우 (성능 희생)
mount -t nfs4 -o actimeo=0 server:/export /mnt/nfs
# 읽기 위주 워크로드 — 캐시 시간 연장
mount -t nfs4 -o acregmin=30,acregmax=120,acdirmin=60,acdirmax=120 \
server:/export /mnt/nfs
# 속성 캐시 완전 비활성화 (매 접근마다 GETATTR)
mount -t nfs4 -o noac server:/export /mnt/nfs
# Delegation이 있으면 actimeo와 무관하게 로컬 캐시 신뢰
# → delegation + 기본 actimeo가 최적 조합
nfs_invalidate_mapping()이 페이지 캐시를 무효화합니다. Delegation이 유효한 동안에는 이 검사를 건너뜁니다.
HA/Failover 구성
프로덕션 환경에서 NFS 서버의 가용성을 보장하려면 HA(High Availability) 구성이 필수입니다. 리눅스에서는 Pacemaker/Corosync, CTDB, keepalived 등을 활용하여 VIP 기반 장애 전환을 구현합니다.
# Pacemaker NFS HA 리소스 설정
# 1. VIP 리소스
pcs resource create nfs-vip ocf:heartbeat:IPaddr2 \
ip=192.168.1.100 cidr_netmask=24 \
op monitor interval=10s
# 2. 공유 스토리지 마운트
pcs resource create nfs-fs ocf:heartbeat:Filesystem \
device=/dev/drbd0 directory=/srv/nfs fstype=ext4 \
op monitor interval=20s
# 3. NFS 서버 데몬
pcs resource create nfs-server ocf:heartbeat:nfsserver \
nfs_shared_infodir=/srv/nfs/nfsinfo \
nfs_ip=192.168.1.100
# 4. NFS Export
pcs resource create nfs-export ocf:heartbeat:exportfs \
clientspec="192.168.1.0/24" \
directory=/srv/nfs/data \
fsid=1 \
options="rw,sync,no_root_squash" \
op monitor interval=30s
# 5. 리소스 그룹 + 순서 제약
pcs resource group add nfs-group nfs-fs nfs-server nfs-export nfs-vip
pcs constraint order nfs-fs then nfs-server
pcs constraint colocation add nfs-group with nfs-fs INFINITY
# CTDB 기반 NFS HA (Samba/CTDB 클러스터와 공유)
# /etc/ctdb/nodes — 클러스터 노드 목록
192.168.1.11
192.168.1.12
192.168.1.13
# /etc/ctdb/public_addresses — 공용 VIP
192.168.1.100/24 eth0
192.168.1.101/24 eth0
# /etc/ctdb/ctdb.conf
[cluster]
recovery lock = /gluster/lock
[legacy]
realtime scheduling = false
# NFS 이벤트 스크립트 활성화
ctdb event script enable legacy 60.nfs
# 상태 확인
ctdb status
ctdb ip
ctdb getrecmaster
RECLAIM_COMPLETE로 상태를 복원할 수 있습니다. 새로운 LOCK/OPEN 요청은 거부됩니다. /proc/fs/nfsd/nfsv4leasetime으로 lease 시간을 조절할 수 있습니다.
Kerberos 보안 설정 실전
NFS의 기본 인증(sec=sys)은 UID/GID를 신뢰하므로 보안에 취약합니다. 프로덕션 환경에서는 RPCSEC_GSS Kerberos를 사용하여 상호 인증, 무결성 검증(krb5i), 암호화(krb5p)를 적용합니다.
# 1. /etc/exports — Kerberos 보안 설정
# sec= 옵션으로 허용할 보안 수준 지정
# krb5p (암호화) 전용 — 가장 높은 보안
/srv/secure *(rw,sync,sec=krb5p,no_root_squash)
# krb5i 이상 허용 (무결성 + 암호화)
/srv/data *(rw,sync,sec=krb5i:krb5p,root_squash)
# 여러 보안 수준 동시 허용 (호환성)
/srv/compat *(rw,sync,sec=krb5:krb5i:krb5p)
# 서버 reload
exportfs -ra
# 2. NFS 서비스 키탭 생성 (KDC 관리자 권한)
# MIT Kerberos에서 NFS 서비스 주체 생성
kadmin.local -q "addprinc -randkey nfs/nfsserver.example.com@EXAMPLE.COM"
# 키탭 추출
kadmin.local -q "ktadd -k /etc/krb5.keytab nfs/nfsserver.example.com@EXAMPLE.COM"
# 클라이언트용 호스트 주체 (머신 인증)
kadmin.local -q "addprinc -randkey host/client1.example.com@EXAMPLE.COM"
kadmin.local -q "ktadd -k /tmp/client1.keytab host/client1.example.com@EXAMPLE.COM"
# 키탭 확인
klist -kte /etc/krb5.keytab
# KVNO Timestamp Principal
# ---- ----------------- -----------------------------------------------
# 2 03/17/26 09:00:00 nfs/nfsserver.example.com@EXAMPLE.COM (aes256)
# 3. gssproxy 설정 (rpc.gssd 대체, 권장)
# /etc/gssproxy/24-nfs-server.conf
[service/nfs-server]
mechs = krb5
cred_store = keytab:/etc/krb5.keytab
cred_store = ccache:FILE:/var/lib/gssproxy/clients/krb5cc_%U
trusted = yes
kernel_nfsd = yes
euid = 0
# /etc/gssproxy/99-nfs-client.conf
[service/nfs-client]
mechs = krb5
cred_store = keytab:/etc/krb5.keytab
cred_usage = initiate
allow_any_uid = yes
euid = 0
# gssproxy 시작
systemctl enable --now gssproxy
# 서버: nfsd가 gssproxy 사용하도록 설정
echo 1 > /proc/sys/sunrpc/use_gss_proxy
# 클라이언트: Kerberos로 마운트
mount -t nfs4 -o sec=krb5p nfsserver.example.com:/srv/secure /mnt/secure
rpc.idmapd가 Kerberos principal을 로컬 UID/GID로 변환합니다. /etc/idmapd.conf의 [Mapping] 섹션에서 도메인과 방식을 설정합니다. NFSv4에서는 UID 대신 user@domain 형식을 사용합니다.
NFS 디버깅/추적
NFS 문제를 진단하려면 커널 측 디버그 플래그(rpcdebug), 네트워크 캡처(Wireshark), 통계 분석(nfsstat/mountstats), 그리고 ftrace tracepoint를 조합해서 사용합니다.
# rpcdebug — 커널 NFS/RPC 디버그 플래그 제어
# NFS 클라이언트 전체 디버그 활성화
rpcdebug -m nfs -s all
# RPC 전송 디버그
rpcdebug -m rpc -s call xprt
# NFS 서버(nfsd) 디버그
rpcdebug -m nfsd -s proc fileop
# 개별 플래그 (nfs 모듈)
# vfs — VFS 연산 추적
# dircache — readdir 캐시
# lookupcache — name lookup 캐시
# pagecache — 페이지 캐시 I/O
# proc — NFS 프로시저 호출
# xdr — XDR 인코딩/디코딩
# 디버그 해제
rpcdebug -m nfs -c all
rpcdebug -m rpc -c all
# 직접 sysctl로 제어 (비트마스크)
echo 0xFFFF > /proc/sys/sunrpc/nfs_debug
echo 0 > /proc/sys/sunrpc/nfs_debug
# nfsstat -m — 마운트 포인트별 상세 통계 분석
nfsstat -m
# /mnt/nfs from server:/export
# Flags: rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,
# namlen=255,hard,proto=tcp,timeo=600,retrans=2,
# sec=krb5p,...
# mountstats — I/O 지연 시간 상세 분석
mountstats /mnt/nfs
# READ: 50000 ops, 1024000 bytes sent, 51200000 bytes received
# avg bytes sent per op: 20 avg bytes received per op: 1024
# backlog wait: 0.010ms RTT: 0.450ms total execute: 0.520ms
# ↑ 큐 대기 ↑ 네트워크 왕복 ↑ 전체 실행 시간
# 핵심 분석 포인트:
# - RTT 높음 → 네트워크 지연 (MTU/대역폭 확인)
# - backlog 높음 → RPC 슬롯 부족 (max_tcp_slot_table_entries 증가)
# - execute - RTT 차이 큼 → 서버 처리 지연
# - retransmit 높음 → 네트워크 불안정 또는 서버 과부하
# ftrace NFS tracepoint 활용
# 사용 가능한 NFS tracepoint 목록
ls /sys/kernel/debug/tracing/events/nfs/
# nfs_readpage_done nfs_writeback_done nfs_commit_done
# nfs_initiate_read nfs_initiate_write nfs_initiate_commit
# nfs_access_enter nfs_lookup_enter nfs_open_file_enter
# sunrpc tracepoint
ls /sys/kernel/debug/tracing/events/sunrpc/
# rpc_call_status rpc_connect_status xprt_timer
# svc_process svc_recv svc_send
# 읽기/쓰기 지연 추적 활성화
echo 1 > /sys/kernel/debug/tracing/events/nfs/nfs_readpage_done/enable
echo 1 > /sys/kernel/debug/tracing/events/nfs/nfs_writeback_done/enable
echo 1 > /sys/kernel/debug/tracing/events/sunrpc/rpc_call_status/enable
# 추적 결과 확인
cat /sys/kernel/debug/tracing/trace_pipe | head -20
# kworker-123 nfs_readpage_done: fileid=0x1234 offset=0 count=4096 status=0
# kworker-123 rpc_call_status: task=5678 status=0
# bpftrace로 NFS 지연 히스토그램
bpftrace -e 'tracepoint:nfs:nfs_readpage_done { @us = hist(args->status == 0 ? 1 : 0); }'
# 정리
echo 0 > /sys/kernel/debug/tracing/events/nfs/enable
echo 0 > /sys/kernel/debug/tracing/events/sunrpc/enable
nfsstat -c로 재전송(retrans) 비율 확인 →
(2) mountstats로 RTT/execute 비교 →
(3) rpcdebug -m rpc -s xprt로 전송 계층 추적 →
(4) 필요 시 tcpdump -i eth0 -s0 port 2049 -w nfs.pcap으로 패킷(Packet) 캡처 후 Wireshark 분석.
참고자료
공식 문서
- NFS — Linux Kernel Documentation (Admin Guide) — NFS 관리자 가이드 공식 문서입니다.
- NFS — Linux Kernel Documentation (Filesystems) — NFS 파일시스템 커널 내부 문서입니다.
- pNFS — Linux Kernel Documentation — pNFS(Parallel NFS) 레이아웃 드라이버 커널 문서입니다.
- RPC Cache — Linux Kernel Documentation — SunRPC 캐시 인터페이스 문서입니다.
- Linux NFS Wiki — 리눅스 NFS 프로젝트 공식 위키입니다.
- nfs-utils (SourceForge) — nfs-utils 패키지 공식 사이트입니다.
RFC 표준 규격
- RFC 1813 — NFS Version 3 Protocol Specification — NFSv3 프로토콜 규격입니다.
- RFC 7530 — NFS Version 4 Protocol — NFSv4.0 프로토콜 규격입니다.
- RFC 8881 — NFS Version 4 Minor Version 1 Protocol — NFSv4.1 프로토콜 규격입니다.
- RFC 7862 — NFS Version 4 Minor Version 2 Protocol — NFSv4.2 프로토콜 규격입니다.
- RFC 5531 — ONC RPC Version 2 — NFS가 사용하는 ONC RPC 프로토콜 규격입니다.
- RFC 4506 — XDR: External Data Representation Standard — NFS 데이터 직렬화에 사용되는 XDR 규격입니다.
- RFC 5661 — NFS Version 4.1 (Original) — NFSv4.1 최초 규격으로 세션/pNFS 개념이 도입되었습니다.
- RFC 5664 — Object-Based pNFS Operations — pNFS 오브젝트 기반 레이아웃 규격입니다.
- RFC 5663 — pNFS Block/Volume Layout — pNFS 블록/볼륨 레이아웃 규격입니다.
- RFC 8267 — NFS Upper-Layer Binding to RPC-over-RDMA — NFS over RDMA 바인딩 규격입니다.
- RFC 7861 — RPCSEC_GSSv3 — NFS 보안을 위한 RPCSEC_GSS 버전 3 규격입니다.
LWN.net 주요 기사
- The Linux NFS server at 25 (LWN, 2022) — 리눅스 NFS 서버(knfsd) 25주년 역사와 발전을 다룹니다.
- A new API for mounting filesystems (LWN, 2016) — fsopen/fsconfig 기반 새로운 마운트 API가 NFS에 미치는 영향을 설명합니다.
- NFS client performance improvements (LWN, 2011) — NFSv4.1 세션 기반 병렬 I/O 성능 개선을 다룹니다.
- pNFS comes to Linux (LWN, 2010) — 리눅스 커널에 pNFS 지원이 추가된 과정을 설명합니다.
- NFS/RDMA (LWN, 2007) — NFS over RDMA 전송 계층 구현을 다룹니다.
- NFS readdir improvements (LWN, 2022) — NFS readdir 성능 개선 패치를 분석합니다.
커널 소스 경로
- fs/nfs/ — NFS 클라이언트 핵심 구현입니다.
- fs/nfsd/ — NFS 서버(knfsd) 구현입니다.
- fs/nfs/nfs4proc.c — NFSv4 프로시저 구현 코드입니다.
- fs/nfs/pnfs.c — pNFS 클라이언트 레이아웃 드라이버 코어입니다.
- fs/nfsd/nfs4state.c — NFSv4 서버 상태 관리(잠금, 위임, 세션)입니다.
- fs/nfsd/vfs.c — knfsd와 VFS 계층 간 인터페이스입니다.
- net/sunrpc/ — SunRPC(ONC RPC) 전송 계층 구현입니다.
- net/sunrpc/xprtrdma/ — RPC-over-RDMA 전송 구현입니다.
- include/linux/nfs_fs.h — NFS 클라이언트 핵심 헤더입니다.
- include/uapi/linux/nfs.h — NFS UAPI 상수 및 매크로 정의입니다.
서적 및 심화 자료
- NFS Illustrated (Brent Callaghan, Addison-Wesley) — NFS 프로토콜 내부를 체계적으로 설명하는 참고 서적입니다.
- Linux Kernel Development, 3rd Edition (Robert Love, Addison-Wesley) — VFS와 NFS 파일시스템 통합을 다루는 챕터가 포함되어 있습니다.
- Understanding the Linux Kernel, 3rd Edition (Bovet & Cesati, O'Reilly) — NFS 클라이언트와 RPC 계층의 커널 구현을 설명합니다.
- Linux System Programming, 2nd Edition (Robert Love, O'Reilly) — NFS 마운트 옵션과 시스템 호출 연동을 다룹니다.
- USENIX FAST Conference — 스토리지/파일시스템 분야 학술 대회로 NFS 관련 논문이 발표됩니다.
관련 문서
NFS와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.