ディスクとファイルディスクリプタ
한국어 원문으로 표시합니다.
한 줄 요약
df 에 여유가 있는데 쓸 수 없으면 inode 가 떨어졌거나 지워진 파일을 누가 열고 있다. 둘 다 df -h 로는 안 보인다.
왜 이게 필요했나
디스크 관련 신고에서 가장 자주 나오는 두 가지가 이것이다.
inode 고갈. 파일 하나하나가 inode 를 하나씩 쓴다. 작은 파일이 수백만 개면 용량은 남아도 inode 가 먼저 떨어진다. df -i 로 봐야 보인다.
지워졌지만 열려 있는 파일. 로그 파일을 rm 했는데 프로세스가 아직 그 파일을 열고 있으면, 리눅스는 링크만 지우고 실제 블록은 반환하지 않는다. df 는 여전히 꽉 찼다고 하고 du 는 파일이 없다고 한다. 이 불일치가 결정적 단서다.
# du 와 df 가 다르면 이걸 본다
ls -l /proc/*/fd 2>/dev/null | grep deleted
해결은 프로세스에게 파일을 다시 열게 하는 것이다(로그라면 logrotate 의 copytruncate 또는 데몬에 SIGHUP). 프로세스를 재시작해도 된다.
어떻게 동작하나
파일 디스크립터 누수는 서서히 죽는다.
ls /proc/<PID>/fd | wc -l # 지금 몇 개 열었나
cat /proc/<PID>/limits | grep 'open files' # 한도
이 숫자가 시간에 따라 단조 증가하면 닫지 않는 코드가 있다. 한도에 닿는 순간 Too many open files 로 요청이 전부 실패한다. 한도를 올리는 것은 시간을 버는 것이지 고치는 것이 아니다.
흔한 착각
ulimit -n 이 무제한이면 안전하다는 착각. 컨테이너의 기본 RLIMIT_NOFILE 은 사실상 무제한(10억 대)인데, 오래된 데몬 중에는 시작할 때 그 숫자만큼 연결 테이블을 잡으려다 메모리를 다 쓰고 죽는 것이 있다. 무제한이 항상 좋은 것은 아니다.
디스크가 느린지 판정하는 두 숫자
iostat -x 1 의 여러 열 중 실제로 쓰는 것은 둘입니다.
Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util
nvme0n1 120 380 4800 15200 0.31 0.52 1.24 38.2
await(r_await·w_await) — 요청 하나가 큐에서 기다린 시간까지 포함한 응답 시간(ms). NVMe 는 1ms 아래, SATA SSD 는 몇 ms, HDD 는 10ms 대가 정상입니다. 이 값이 평소의 몇 배로 뛰면 디스크가 병목입니다.aqu-sz— 평균 큐 길이. 1을 넘으면 요청이 줄을 서고 있다는 뜻입니다.
%util 은 믿지 마세요. 이 값은 "요청이 하나라도 진행 중이던 시간의 비율" 이라,
병렬 처리가 되는 NVMe 에서는 100% 여도 여유가 남아 있습니다. 회전 디스크 시절의
지표입니다.
어떤 프로세스가 디스크를 쓰는가
# 실시간으로 I/O 상위 프로세스
iotop -oPa # -o: 실제로 I/O 하는 것만, -a: 누적
# iotop 이 없을 때 (컨테이너에서 흔하다)
cat /proc/<PID>/io # read_bytes, write_bytes 를 두 번 읽어 차이를 낸다
/proc/<PID>/io 의 read_bytes 는 실제 디스크에서 읽은 양 이고 rchar 는
읽기 시스템 호출로 넘긴 양입니다. 둘의 차이가 크면 페이지 캐시가 잘 듣고 있다는
뜻이라 좋은 신호입니다. 반대로 둘이 비슷하면 캐시가 안 먹고 매번 디스크를 칩니다.
컨테이너에서 I/O 를 제한하기
CPU·메모리와 달리 쿠버네티스에는 I/O 제한 필드가 없습니다. cgroup v2 에는
io.max 가 있지만 파드 스펙으로 노출되지 않습니다. 그래서 실무의 대응은 다릅니다.
- 시끄러운 이웃을 물리적으로 분리한다. 로그 수집기나 백업처럼 I/O 를 몰아 쓰는 워크로드는 별도 노드나 별도 디스크로 보냅니다.
- 애플리케이션에서 조절한다. 백업 스크립트에
ionice -c3(idle 클래스)를 걸거나,rsync --bwlimit처럼 도구가 주는 제한을 씁니다. - 로컬 디스크 대신 네트워크 스토리지의 IOPS 등급을 산다. 클라우드라면 이쪽이 가장 확실합니다.
ionice 는 CFQ·BFQ 스케줄러에서만 듣습니다. 요즘 NVMe 기본값인 none(멀티큐)
에서는 아무 효과가 없으니 cat /sys/block/nvme0n1/queue/scheduler 로 먼저
확인합니다.
실무에서 진짜 중요한 것
로그 로테이션 설정에서 create 와 copytruncate 의 차이가 여기에 걸린다.
create: 원본을 rename 하고 새 파일을 만든다. 프로세스가 계속 옛 파일(이제 이름이 바뀐)에 쓰므로 데몬에 SIGHUP 을 보내 다시 열게 해야 한다.copytruncate: 내용을 복사한 뒤 원본을 0바이트로 자른다. 프로세스는 그대로 쓰면 되지만, 복사와 자르기 사이에 쓴 로그는 사라진다.
어느 쪽도 공짜가 아니다. 애플리케이션이 SIGHUP 을 처리하면 create, 못 하면 copytruncate 를 쓰되 유실 가능성을 안다.