EKS 워커 노드의 커널 로그에 아래 메시지가 주기적으로 찍히고, 같은 시간대에 해당 볼륨을 쓰는 스테이트풀 파드가 느려지거나 재시작한다.
kernel: NFS: fs-08fc12f0ae4478242.efs.ap-northeast-2.amazonaws.com: lost 66 locks
kubelet: Trace[...]: "Calculate volume metrics of postgres-data for pod ..." (total time: 2347ms)
kubelet: "SyncLoop UPDATE" source="api" pods=["<ns>/<postgres-pod>"]
lost N locks 는 리눅스 NFS 클라이언트가 내보내는 메시지로, 서버와의 상태(state) 를 되찾지 못해 이 클라이언트가 잡고 있던 잠금 N 개를 포기했다는 뜻이다. NFSv4 는 클라이언트가 리스(lease) 를 주기적으로 갱신하며 잠금 상태를 유지하는데, 네트워크 단절이나 서버 측 재시작으로 리스가 만료되면 클라이언트는 복구(reclaim) 를 시도하고, 복구에 실패한 잠금을 버리면서 이 메시지를 남긴다.
문제는 잠금이 사라졌다는 사실을 애플리케이션이 알지 못한다는 점이다. fcntl 잠금으로 상호배제를 하던 프로세스는 자기가 여전히 잠금을 쥐고 있다고 믿는다. 두 프로세스가 동시에 같은 파일을 쓰게 되면 데이터가 깨진다.
Calculate volume metrics ... 2347ms 는 kubelet 이 볼륨 사용량을 재는 데 2.3초가 걸렸다는 뜻으로, 그 자체가 오류는 아니지만 EFS 응답이 느려져 있다는 신호다. SyncLoop UPDATE 가 10초 간격으로 반복되는 것은 정상 폴링이므로 단독으로는 문제의 증거가 아니다.
# EFS 측 지표 — 버스트 크레딧 소진, IO 제한 도달 여부
aws efs describe-file-systems --file-system-id fs-xxxxxxxx
aws efs describe-mount-targets --file-system-id fs-xxxxxxxx
CloudWatch 의 BurstCreditBalance · PercentIOLimit · PermittedThroughput · ClientConnections 를 본다. 버스트 모드에서 크레딧이 0 에 가까워지면 처리량이 기준선으로 떨어지면서 응답 지연과 타임아웃이 함께 늘어난다.
노드에서 실제 마운트 옵션을 본다.
mount | grep nfs4
cat /proc/mounts | grep efs
파드가 잠금을 잃은 시점 전후로 노드의 네트워크 단절, EFS 마운트 타깃 교체, 파드 재스케줄이 있었는지 맞춰 본다.
AWS 가 EFS 에 권장하는 옵션이다.
nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport,_netdev
hard — 서버가 응답하지 않으면 무한정 재시도한다. soft 는 I/O 를 오류로 되돌려 데이터 손상 위험이 있으므로 쓰지 않는다.timeo=600 은 60초다. 단위가 0.1초라는 점을 자주 헷갈린다.noresvport — 재연결 때 새 소스 포트를 쓰게 해서, 끊겼다 붙는 구간에서 세션 복구가 잘 되게 한다. EFS 에서는 사실상 필수다.nolock 은 넣지 않는다. 잠금 자체를 쓰지 않게 만들어 메시지는 사라지지만 상호배제가 없어진다.쿠버네티스에서는 StorageClass 의 mountOptions 에 적는다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: efs-rwx
provisioner: efs.csi.aws.com
mountOptions:
- nfsvers=4.1
- hard
- timeo=600
- retrans=2
- noresvport
StorageClass 의 mountOptions 는 PV 가 만들어질 때만 반영된다. 이미 Bound 된 PVC 에는 소급 적용되지 않는다. 적용 여부는 PV 를 직접 봐야 한다.
kubectl get pv <PV_NAME> -o jsonpath='{.spec.mountOptions}{"\n"}'
기존 PV 에 kubectl patch 로 mountOptions 를 밀어 넣어도 이미 마운트된 볼륨은 다시 마운트되지 않으므로 효과가 없다. 옵션을 바꾸려면 새 PVC 를 만들어 데이터를 옮기거나, 최소한 파드를 내렸다 올려 재마운트가 일어나게 해야 한다.
PostgreSQL 은 데이터 디렉터리에 대해 파일 잠금과 순서 보장된 쓰기를 전제로 동작한다. PostgreSQL 공식 문서도 NFS 위에 데이터 디렉터리를 두는 경우 hard 마운트가 필수이며 잠금 동작이 보장되어야 한다고 못 박는다. EFS 처럼 지연이 크고 세션이 끊겼다 붙는 공유 파일시스템은 이 조건을 만족시키기 어렵다.
| 용도 | 권장 |
|---|---|
| 데이터베이스 데이터 디렉터리 | EBS(gp3 이상) 기반 블록 스토리지. ReadWriteOnce |
| 여러 파드가 같이 읽고 쓰는 공유 디렉터리 | EFS. ReadWriteMany 가 필요한 자리 |
| 로그 · 백업 적재 | S3 또는 EFS |
nfs-subdir-external-provisioner 로 EFS 를 다시 NFS 로 재공유하는 구조라면 경로가 한 단계 더 늘어난다. 프로비저너 파드가 재시작하거나 다른 노드로 옮겨 갈 때마다 세션이 끊기므로 잠금 손실 빈도가 올라간다. EFS 를 쓸 것이라면 efs.csi.aws.com 드라이버로 직접 붙이는 편이 낫다.livenessProbe 타임아웃을 EFS 지연보다 넉넉히 잡아 두어 지연이 곧바로 재시작으로 이어지지 않게 한다.