환경  Ubuntu 22.04 · Linux 6.8 · ext4 · e2fsprogs · 2026-10-26 확인

배포 스크립트가 임시 파일을 못 만들었다며 멈췄습니다. 메시지는 No space left on device. 그런데 df -h 를 쳐 보면 사용률은 30퍼센트도 안 되고, 지울 만한 큰 파일도 보이지 않습니다. 디스크에는 용량 말고 세는 칸이 하나 더 있고, 이번에 바닥난 쪽은 그 칸입니다.

inode 는 파일 하나마다 붙는 신상 카드다

리눅스 파일 시스템에서 파일 하나는 두 부분으로 나뉩니다. 내용이 들어가는 데이터 블록, 그리고 그 파일이 누구 것이고 권한이 어떻고 크기가 얼마이며 내용이 어느 블록에 있는지를 적어 두는 inode 입니다. 파일 이름은 inode 안에 없습니다. 이름은 디렉터리가 들고 있고, 디렉터리는 이름과 inode 번호를 짝지어 놓은 목록일 뿐입니다.

그래서 파일을 하나 만들려면 블록만 남아서는 안 됩니다. 빈 inode 도 하나 있어야 합니다. 1바이트짜리 파일도, 빈 파일도, 디렉터리도, 심볼릭 링크도 전부 inode 를 하나씩 차지합니다. 둘 중 어느 쪽이 먼저 떨어져도 커널은 같은 오류 ENOSPC 를 돌려주고, 화면에는 똑같이 No space left on device 가 찍힙니다. 메시지만으로는 어느 칸이 찬 건지 구분이 안 되는 셈입니다.

inode 번호는 ls -i 나 stat 으로 바로 볼 수 있습니다. 하드 링크가 두 이름이 같은 파일을 가리킬 수 있는 것도 이 구조 덕분입니다. 이름 두 개가 같은 inode 번호를 적어 두면 그만이거든요.

stat -c '%i %h %n' /etc/hostname
# 646 1 /etc/hostname     ← inode 번호, 링크 수, 이름

df -h /
# Filesystem  Size  Used Avail Use% Mounted on
# /dev/root    58G   16G   43G  27% /

df -i /
# Filesystem  Inodes   IUsed   IFree IUse% Mounted on
# /dev/root  7741440  304477 7436963    4% /

같은 디스크를 두 번 봅니다. 용량 칸과 inode 칸은 따로 찹니다

파일 하나를 만들려면 빈 블록과 빈 inode 가 둘 다 있어야 합니다.

ext4 는 inode 자리를 포맷할 때 미리 깔아 둔다

ext4 는 inode 를 쓸 때마다 만들지 않습니다. mke2fs 로 포맷하는 순간 디스크를 블록 그룹으로 나누고, 그룹마다 inode 표를 정해진 크기로 깔아 둡니다. 몇 개를 깔지는 bytes-per-inode 비율로 정합니다. 디스크 공간 몇 바이트마다 inode 를 하나씩 둘지를 정하는 값이고, /etc/mke2fs.conf 의 기본은 16384, 즉 16KB 에 하나꼴입니다. inode 하나의 크기는 기본 256바이트입니다.

이렇게 미리 깔아 두는 데는 사정이 있습니다. inode 의 위치가 번호만으로 계산되니 찾는 데 따로 색인이 필요 없고, 파일 시스템이 망가졌을 때도 fsck 가 어디를 뒤져야 할지 압니다. 대신 개수가 고정됩니다. 비율은 포맷한 뒤에는 바꿀 수 없고, 디스크를 늘리면 그 비율을 지키는 만큼만 inode 가 같이 늘어납니다.

그러니 16KB 에 하나라는 기본값은 평균 파일 크기가 대략 그쯤이라고 가정한 숫자입니다. 그보다 훨씬 작은 파일이 수백만 개 쌓이면 블록은 남아도 inode 가 먼저 떨어집니다. 지금 쓰는 서버를 tune2fs 로 열어 보면 inode 774만 개에 블록 1570만 개, 8KB 남짓에 하나꼴로 만들어져 있습니다. 기본값과 다른 비율인데, 클라우드 이미지가 어떤 값으로 만들어졌는지에 따라 이렇게 달라집니다. 내 디스크의 숫자는 짐작하지 말고 직접 찍어 보는 편이 맞습니다.

sudo tune2fs -l "$(findmnt -no SOURCE /)" | grep -E 'Inode count|Block count|Block size|Inode size|Inodes per group'
# Inode count:              7741440
# Block count:              15700219
# Block size:               4096
# Inodes per group:         16128
# Inode size:               256

grep -A6 '\[defaults\]' /etc/mke2fs.conf | grep -E 'inode_(size|ratio)'
#  inode_size = 256
#  inode_ratio = 16384

포맷 기본값과 실제 디스크의 비율이 다를 수 있습니다

8메가짜리 디스크로 칸이 먼저 차는 장면을 만들어 보면

말로만 들으면 감이 잘 안 오니, 파일 하나를 디스크 이미지로 삼아 inode 를 일부러 아주 적게 깔아 봤습니다. mke2fs 에 -N 32 를 주면 inode 를 32개만 만듭니다. 이 중 앞 번호 10개는 루트 디렉터리와 저널 같은 예약용이라 First inode 가 11 로 잡히고, 실제로 남는 건 21개입니다.

여기에 몇 바이트짜리 파일을 계속 써 넣으면 21개째에서 멈춥니다. 그 순간 dumpe2fs 를 다시 보면 Free inodes 는 0 인데 Free blocks 는 991개, 4KB 블록이니 4메가 가까이 비어 있습니다. 용량은 반이나 남았는데 파일은 한 개도 더 못 만드는 상태가 그대로 재현됩니다.

운영 서버에서 벌어지는 일도 크기만 다를 뿐 똑같습니다. 정리되지 않는 세션 파일, 캐시 조각, 메일 큐, 빌드 산출물처럼 작고 많은 파일이 한 디렉터리에 쌓이면 이렇게 됩니다. 마운트를 거치지 않고 이미지 파일에 debugfs 로 쓰는 방식이라 root 권한도 필요 없고, 끝나면 이미지 파일만 지우면 됩니다.

d=$(mktemp -d); cd "$d"
truncate -s 8M img
/sbin/mke2fs -q -t ext4 -N 32 img
/sbin/dumpe2fs -h img 2>/dev/null | grep -E '^Inode count|First inode|Free inodes|Free blocks'
# Inode count:   32
# Free blocks:   1012
# Free inodes:   21
# First inode:   11

for i in $(seq 1 40); do echo x > f; /sbin/debugfs -w -R "write f f$i" img; done
# ... 21개까지 Allocated inode, 그 뒤로는
# write: Could not allocate inode in ext2 filesystem

/sbin/dumpe2fs -h img 2>/dev/null | grep -E 'Free inodes|Free blocks'
# Free blocks:   991
# Free inodes:   0

cd /; rm -rf "$d"

블록은 991개가 남았는데 inode 는 0개입니다

inode 가 바닥나면 용량이 아무리 남아도 빈 파일 하나 만들지 못합니다.

범인은 큰 파일이 아니라 수가 많은 디렉터리다

용량이 찼을 때는 du -h 로 큰 것부터 찾지만, inode 가 찼을 때는 그 방법이 통하지 않습니다. 몇 기가짜리 파일 하나는 inode 를 하나만 씁니다. 찾아야 할 건 개수가 많은 곳입니다. GNU du 에는 --inodes 옵션이 있어서, 크기 대신 그 아래 inode 개수를 세어 줍니다. 최상위에서 시작해 숫자가 큰 디렉터리로 한 단계씩 내려가면 금방 좁혀집니다.

-x 는 꼭 붙여 둡니다. 안 붙이면 /proc 이나 다른 디스크까지 넘어가 세느라 오래 걸리고 숫자도 섞입니다. 범인을 찾았으면 지울 때도 조심해야 합니다. 파일이 수십만 개인 디렉터리에 rm * 를 치면 셸이 이름을 전부 펼치다가 인자 목록이 너무 길다며 실패합니다. find 로 -delete 를 거는 쪽이 안전하고, 오래된 것만 지우려면 -mtime 으로 범위를 정합니다.

지우고 나서 끝내면 같은 일이 또 납니다. 세션이라면 정리 주기가 실제로 돌고 있는지, 캐시라면 만료가 걸려 있는지, 무엇이 그 디렉터리에 파일을 만드는지까지 확인해 두는 게 진짜 조치입니다.

sudo du --inodes -x -d1 / 2>/dev/null | sort -n | tail -5
#   2795  /etc
#   8395  /var
# 141083  /usr
# 149915  /home
# 304422  /

# 숫자가 큰 쪽으로 내려간다
sudo du --inodes -x -d1 /home 2>/dev/null | sort -n | tail -5

# 지울 때는 셸 확장 대신 find
sudo find /path/to/dir -xdev -type f -mtime +7 -delete

크기가 아니라 개수로 세야 범인이 보입니다

비슷한 증상 셋과는 이렇게 갈린다

No space left on device 와 함께 자주 헷갈리는 상황이 셋 있습니다. 첫째는 지운 파일을 프로세스가 아직 붙들고 있는 경우입니다. 이때는 df -h 가 꽉 찼다고 하는데 du 로 더해 보면 숫자가 모자랍니다. inode 고갈은 반대로 df -h 는 여유롭고 df -i 의 IUse% 만 100퍼센트입니다. 두 명령을 나란히 쳐 보면 바로 갈립니다.

둘째는 Too many open files 입니다. 이름에 파일이 들어가서 비슷해 보이지만, 이건 프로세스가 동시에 열어 둘 수 있는 번호의 한도라 디스크와는 상관이 없습니다. 오류 이름부터 EMFILE 로 다릅니다.

셋째는 파일 시스템 종류가 다른 경우입니다. XFS 는 inode 를 쓸 때마다 만들어서 ext4 처럼 개수가 미리 박혀 있지 않습니다. 대신 전체 공간 가운데 inode 에 쓸 수 있는 비율의 상한(maxpct)이 있고, 1TB 미만 디스크의 기본값은 25퍼센트입니다. 그래서 df -i 의 Inodes 숫자가 고정값이 아니라 남은 공간에 따라 움직입니다. 같은 오류라도 ext4 냐 XFS 냐에 따라 손댈 자리가 달라지니, findmnt 로 종류부터 확인하는 게 순서입니다.

df -h 는 여유롭고 df -i 만 100퍼센트라면, 지울 것은 큰 파일이 아니라 많은 파일입니다.

조치 후 확인할 것

  • No space left on device 가 나오면 df -h 와 df -i 를 둘 다 본다
  • IUse% 가 높으면 du --inodes -x 로 개수가 많은 디렉터리를 찾아 내려간다
  • 파일이 많은 디렉터리는 rm * 대신 find -delete 로 지운다
  • 지운 뒤에는 그 파일을 만드는 쪽의 정리 주기나 만료 설정을 확인한다
  • 새 디스크에 작은 파일이 많이 쌓일 예정이면 포맷 전에 mke2fs -i 나 -N 을 정한다

디스크에는 용량과 개수, 칸이 두 개 있습니다. 평소에는 용량만 보고 살아도 괜찮지만, 작은 파일을 끝없이 만드는 무언가가 하나 생기면 개수 쪽이 먼저 바닥납니다. 모니터링에 df -h 만 걸려 있다면 df -i 도 한 줄 같이 걸어 두는 게 좋습니다. 둘 중 하나만 보고 있으면 남은 하나가 터질 때까지 모릅니다.