EC2 디스크 Full 장애 추적하기 (feat. 범인은 Docker 로그)

o_z·2026년 1월 18일
post-thumbnail

알림 전송을 위한 FCM 서버를 배포하는 도중 아래 이유로 배포에 실패했다.

no space left on device

docker 이미지를 레포지토리에서 pull 해야하는데 서버에 디스크 공간이 부족하다는 이유이다.
뭐가 이렇게 디스크를 많이 잡아먹나 싶어서 서버를 확인하러 갔다.

[ec2-user@ bin]$ df -h
Filesystem        Size  Used Avail Use% Mounted on
devtmpfs          4.0M     0  4.0M   0% /dev
tmpfs             1.9G     0  1.9G   0% /dev/shm
tmpfs             768M  924K  767M   1% /run
/dev/nvme0n1p1     30G   30G    0M  100% /
tmpfs             1.9G     0  1.9G   0% /tmp
/dev/nvme0n1p128   10M  1.4M  8.7M  14% /boot/efi
tmpfs             384M     0  384M   0% /run/user/0

EBS 볼륨을 30G 할당했는데 다썼다..
당시에는 다른 작업 우선순위가 높아 이에 대한 근본적인 원인을 파악할 시간이 없었다.
그래서 일단 급한 불 끄자는 생각에 임시방편으로 50G까지 볼륨을 올렸다.

저 사태가 있고 며칠 지나지 않아서, 다른 작업들 다 끝내고 지금은 괜찮나 싶어 서버를 확인했는데 이번에도 거의 disk full 상태에 육박했었다.

[ec2-user@ bin]$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        4.0M     0  4.0M   0% /dev
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           768M   73M  695M  10% /run
/dev/nvme0n1p1   50G   47G    3G  97% /
tmpfs           1.9G     0  1.9G   0% /tmp
/dev/nvme0n1p128  10M  1.4M  8.7M  14% /boot/efi
tmpfs           384M     0  384M   0% /run/user/0

EBS를 무작정 늘릴 순 없기에 이젠 근본적인 원인을 파악해서 해결해야했다.


용량을 많이 차지하는 공간 확인하기

현재 디렉토리들 중 어디가 잡아먹는지를 확인하려고 했다.
/var, /home-user, /opt, /usr 디렉토리들을 확인하려고 했다.

/var 디렉토리 공간을 확인해보았다.

sudo du -xhd1 /var | sort -h
[ec2-user@ ~]$ sudo du -xhd1 /var | sort -h
0       /var/account
0       /var/adm
0       /var/db
0       /var/empty
0       /var/ftp
0       /var/games
0       /var/kerberos
0       /var/local
0       /var/nis
0       /var/opt
0       /var/preserve
0       /var/spool
0       /var/yp
16K     /var/tmp
64M     /var/cache
358M    /var/log
36G     /var
36G     /var/lib

/var/lib가 무려 36G나 잡아먹고 있었다. 더 세분화해서 정확히 무엇이 원인인지 파악하고자 파고들었다.

sudo du -xhd1 /var/lib | sort -h
[ec2-user@ ~]$ sudo du -xhd1 /var/lib | sort -h
0       /var/lib/games
0       /var/lib/gssproxy
(생략)   ...
18M     /var/lib/selinux
36G     /var/lib
36G     /var/lib/docker

docker가 /var/lib의 대부분 용량을 차지하고 있었다.
/var/lib/docker 용량을 파고들어보자.

[ec2-user@ip-10-0-5-182 ~]$ sudo du -xhd1 /var/lib/docker | sort -h
0       /var/lib/docker/plugins
(생략)   ...
5.8M    /var/lib/docker/image
30M     /var/lib/docker/volumes
3.1G    /var/lib/docker/overlay2
32.7G   /var/lib/docker/containers
36G     /var/lib/docker

36GB 중 32.7GB를 먹는 /containers 디렉토리..

처음엔 /docker/containers에 이미지나 볼륨이 저장되어서
이렇게까지 큰 용량을 차지하는 것이라고 예상했다.

그런데 오히려 /image, /volumes 디렉토리는 바깥에 따로 있었고,
찾아보니 /containers의 대부분은 컨테이너 로그가 차지한다는 사실을 알게됐다.

/var/lib/docker/containers에는 어떤 데이터가 저장되어있을까?

  • stdout/stderr로 출력되는 컨테이너 로그 (*-json.log)
  • 컨테이너 메타데이터 (ex: 환경변수, 네트워크 등 상태정보)
[ec2-user@ ~]$ sudo du -h /var/lib/docker/containers | sort -h | tail
0       /var/lib/docker/containers/8e10d3f0ca6e74807a4e66bfde.../mounts
0       /var/lib/docker/containers/931f313c2b036a41cb39a5ce6b.../checkpoints
0       /var/lib/docker/containers/555e26892d87ae65ce7e4b4a7a2...
(생략)   ...
241M    /var/lib/docker/containers/8e10d3f0ca6e74807a4e66bfde2...
31G     /var/lib/docker/containers/11ff45adf6149a269721ec345c1...
32G     /var/lib/docker/containers

특정 컨테이너에 대한 로그가 31G 쌓인 것이었다.
어떤 컨테이너에서 이렇게 로그를 많이 쌓나 확인했더니 컨테이너 ID가 FCM 애플리케이션 컨테이너였다.

왜이렇게까지 docker 컨테이너의 로그가 폭증했을까?


Docker는 default log rotation이 없다

Docker처럼 잘 만들어진 플랫폼은 log rotation이나 retention 정책에 대해
당연히 default 값이 있을 것이라고 생각했다.

근데 알고보니 Docker는 내가 직접 설정해주지 않는 이상, 로그 보관과 관련된 default 설정이 없다고 한다.

Docker의 logging driver

Docker는 logging driver가 탑재되어있다.
흔히 우리가 docker logs로 로그를 가져올 수 있도록 해주는 로그 메커니즘이다.
각 Docker에는 이 logging driver가 데몬 프로세스로 탑재되어 있다.

"As a default, Docker uses the json-file logging driver, which caches container logs as JSON internally."

"기본적으로 Docker는 컨테이너 로그를 JSON 형태로 내부적으로 캐싱하는 json-file 로깅 드라이버를 사용합니다."

공식문서를 참고해보면 Docker의 default driver는 json-file 로깅 드라이버라고 한다.

그 아래로 공식문서를 읽다보면 logging driver 설정에 대한 Tip 부분이 있다.

"Use the local logging driver to prevent disk-exhaustion.
By default, no log-rotation is performed.
As a result, log-files stored by the default json-file logging driver logging driver can cause a significant amount of disk space to be used for containers that generate much output, which can lead to disk space exhaustion.
"

"디스크 공간 부족을 방지하려면 local 로깅 드라이버를 사용하세요.
기본적으로 로그 rotation은 수행되지 않습니다.
따라서 기본 json-file 로깅 드라이버가 저장하는 로그 파일은 출력을 많이 생성하는 컨테이너에서 상당한 디스크 공간을 차지하여 디스크 공간 부족으로 이어질 수 있습니다."

Docker에서도 공식적으로 디스크 공간 차지 문제 때문에 logging driver를
json-file보다 local로 사용하도록 권장하고 있다.

json-filelocal은 무슨 차이일까?

json-file

  • 컨테이너 stdout/stderr를 JSON 라인 포맷으로 파일에 그대로 저장
  • default log rotation / retention 없음

local

  • stdout/stderr를 Docker가 관리하는 내부 바이너리/최적화 포맷으로 저장하고 자동 압축
  • default log rotation / retention 있음

❓ Docker는 왜 default logging driver를 json-file로 했을까?

두 개의 드라이버 설명을 보면 json-file보다 local의 이점이 훨씬 커보였다.
그럼 Docker는 왜 default로 local을 안두고 json-file로 두었을까?

아래는 Docker 공식문서 내용이다.

"Docker는 이전 버전의 Docker와의 하위 호환성을 유지하고
Docker가 Kubernetes의 런타임으로 사용되는 상황을 위해 (로그 순환 기능이 없는)
json-file 로깅 드라이버를 기본값으로 유지합니다.
"

Docker 이전 버전의 하위호환성 문제와 쿠버네티스간 호환성 문제였다.
(옛날에는 쿠버네티스에서 Docker의 컨테이너 로그가 json-file로 쌓인다는 전제가 있었다고 한다.)

우리 서버는 json-file을 고집해야 하는가?

이 문제를 위한 근본적인 해결책은 로깅의 rotation 지정이다.
따라서 options를 통해 rotation 설정만 켜주면 괜찮을 수 있다.
하지만 공식 문서가 권장하듯, json-file보다 local이 더 좋은 옵션이라고 판단해서
'local이 지금 우리 서버에 적절한가?'를 고려하게 되었다.

그래서 우리 서버가 json-file 드라이버를 고집해야 하는 필수적인 이유가 있지 않다면 local로 변경해도 무방할 것이라고 생각했다.
우리 서버가 json-file을 고집해야 하는 이유가 있을까?

서버는 Promtail을 통해 애플리케이션 로그를 수집하고, 이를 loki에게 push 해주는 방식이다.
이 때 Promtail의 Docker service discovery(docker_sd_configs)를 쓰는 구성일 경우,
즉, Promtail이 docker log를 스크랩 하는 경우 공식 문서에 컨테이너는 json-file 또는 journald 로깅 드라이버여야 한다고 명시돼 있다.

이 경우 local로 바꾸면 Promtail이 기대하는 방식으로 못 읽을 수 있는 것이다.

하지만 지금 알림 서버는 logback 기반으로 생성된 로그를 스크래핑 하고 있었으므로,
docker log는 json format을 유지할 필요가 없었다.

따라서 local 드라이버를 적용해도 무방하겠다는 결론을 지었다.

FCM 서버에 적용하기

그래서 Docker 공식 문서에서도 권장함에 따라, 우리도 json-file에서 local로 logging driver를 변경했다.
docker-compose.yml 파일에서 설정할 수 있다.

services:
  fcm-app:
  	...
	logging:
      driver: "local"
      options:
          max-size: "50m"
          max-file: "5"
  • max-size: 로그 파일 1개가 커질 수 있는 최대 크기이다. (default: 100m)
    • 이 크기에 도달할 시 Docker가 로그 rotation을 시켜 새 파일로 넘긴다.
  • max-file: rotation 된 로그 파일을 유지할 최대 개수이다. (default: 5)
    • 파일 개수가 max-file개를 넘으면 가장 오래된 파일부터 삭제한다.

위 설정들은 json-file 드라이버를 사용해도 위처럼 설정할 수 있다.
default 값이 없을 뿐이다.

따라서 실제 컨테이너당 로그 상한은 max-size * max-file 이 될 것이다.
두 설정 뿐 만 아니라 logging driver도 local로 지정했으므로 압축까지 발생해서
사실상 이보다 더 작은 용량을 차지할 것이다.

무난한 운영 기본 값으로 max-size: 50m, max-file: 5로 지정해,
FCM 서버 컨테이너 당 약 최대 250MB 수준으로 유지되도록 지정했다.


Docker log rotation 적용 후 디스크 용량

적용 후 docker의 디스크 용량을 확인해보았다.
확실하게 /container 용량이 완전히 줄어들었다.

[ec2-user@ ~]$ sudo du -xhd1 /var/lib/docker | sort -h
0       /var/lib/docker/plugins
0       /var/lib/docker/runtimes
0       /var/lib/docker/swarm
0       /var/lib/docker/tmp
100K    /var/lib/docker/buildkit
108K    /var/lib/docker/network
5.8M    /var/lib/docker/image
26M     /var/lib/docker/volumes
987M    /var/lib/docker/containers
3.1G    /var/lib/docker/overlay2
4.1G    /var/lib/docker

전체 디스크 사용량도 확인해보았다.
97% 사용하던 디스크가 35%까지 내려간 모습이다.

[ec2-user@ bin]$ df -h
Filesystem        Size  Used Avail Use% Mounted on
devtmpfs          4.0M     0  4.0M   0% /dev
tmpfs             1.9G     0  1.9G   0% /dev/shm
tmpfs             768M  1.3M  766M   1% /run
/dev/nvme0n1p1     50G   18G   33G  35% /
tmpfs             1.9G     0  1.9G   0% /tmp
/dev/nvme0n1p128   10M  1.4M  8.7M  14% /boot/efi
tmpfs             384M     0  384M   0% /run/user/0

(2월 5일 기준, 여전히 35~36%의 사용률을 유지하고 있다.)


위에서 설정한 이 값들이 정답이라는건 아니다.

로그 보관 정책은 서비스 특성과 내부 정책에 따라 다양할 수 있으며,
디스크 사용량도 마냥 적다고 좋아할 것은 아니다.

로그를 운영상 얼마나 보관하고 있는 것이 적절할지, 안전한 목표 디스크 사용량도 고민해봐야한다.
꾸준히 모니터링하고 고민해봐야 할 사항인 것 같다.


참고
https://docs.docker.com/engine/logging/configure/

profile
트러블슈팅과 구현기를 위주로 기록합니다-

0개의 댓글