[Ubuntu] 07. 홈 서버 운영 - Docker 로그 관리와 디스크 최적화
MSSQL 백업과 복구 테스트까지 끝내고 나니 다음으로 신경 쓰인 건 디스크였다.
홈 서버는 클라우드 서버처럼 스토리지를 쉽게 늘릴 수 있는 환경이 아니다. 특히 Docker는 기본 설정으로 오래 운영하면 로그와 빌드 캐시가 생각보다 많이 쌓인다.
처음에는 컨테이너만 잘 떠 있으면 된다고 생각했는데, 운영 쪽으로 넘어가니 “잘 실행되는 것”만큼 “계속 버틸 수 있는 것”도 중요했다.
오늘 목표
오늘 목표는 아래와 같았다.
Docker 로그 사용량 확인
↓
컨테이너 로그 로테이션 설정
↓
기존 컨테이너에 설정 반영
↓
Docker 이미지 / 빌드 캐시 정리 정책 정리
먼저 현재 로그 사용량 확인
Docker 컨테이너 로그는 보통 아래 경로에 쌓인다.
/var/lib/docker/containers
현재 사용량을 확인했다.
sudo du -sh /var/lib/docker/containers
그리고 컨테이너별 JSON 로그 파일을 확인했다.
sudo find /var/lib/docker/containers -name "*-json.log" -exec ls -lh {} \;
초기 상태에서는 큰 문제가 없었다.
40K ...-json.log
24K ...-json.log
3.1K ...-json.log
지금은 작지만, 이 상태로 몇 달 동안 운영하면 달라질 수 있다.
ASP.NET Core 로그, EF Core SQL 로그, Nginx 로그, SQL Server 로그가 쌓이면 Docker 로그 파일이 몇 GB까지 커지는 것도 이상한 일이 아니다.
Docker 로그가 커지는 이유
Docker의 기본 로그 드라이버는 보통 json-file이다.
컨테이너가 stdout/stderr로 출력하는 로그를 JSON 파일로 저장한다.
컨테이너 출력
↓
Docker logging driver
↓
*-json.log
문제는 기본 설정에서는 로그 파일 크기 제한이 없을 수 있다는 점이다.
즉 앱이 계속 로그를 찍으면 파일도 계속 커진다.
홈 서버에서는 이걸 그냥 두기 어렵다.
Docker daemon.json 설정
Docker 데몬 설정 파일을 열었다.
sudo nano /etc/docker/daemon.json
아래 내용을 넣었다.
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "5"
}
}
의미는 간단하다.
max-size: 로그 파일 하나는 최대 10MB
max-file: 로그 파일은 최대 5개 보관
컨테이너 하나당 최대 약 50MB 정도로 제한되는 구조다.
서버 규모가 크다면 다르게 잡을 수 있겠지만, 지금 홈 서버에는 충분하다고 봤다.
Docker 재시작
설정을 바꾼 뒤 Docker를 재시작했다.
sudo systemctl restart docker
컨테이너들이 다시 올라왔는지 확인했다.
docker ps
정상적으로 컨테이너들이 다시 실행됐다.
survivalgame Up
mssql-db Up
postgres-db Up
portainer Up
처음 확인했을 때 설정이 안 보였다
바로 확인해봤다.
docker inspect survivalgame --format '{{json .HostConfig.LogConfig}}'
처음에는 이렇게 나왔다.
{"Type":"json-file","Config":{}}
분명 Docker daemon 설정을 바꿨는데 컨테이너에는 반영되지 않은 것처럼 보였다.
이유는 단순했다.
Docker daemon의 기본 설정은 새로 생성되는 컨테이너에 적용된다. 기존 컨테이너는 다시 만들어야 한다.
그래서 Compose로 컨테이너를 재생성했다.
cd ~/docker/compose
docker compose up -d --force-recreate
다시 확인했다.
docker inspect survivalgame --format '{{json .HostConfig.LogConfig}}'
이번에는 정상적으로 적용됐다.
{"Type":"json-file","Config":{"max-file":"5","max-size":"10m"}}
이 상태면 컨테이너 로그가 무한히 커지는 문제는 막을 수 있다.
Docker 이미지 정리
Docker는 이미지를 새로 빌드할 때 이전 이미지나 중간 레이어가 남을 수 있다.
현재 사용량은 아래 명령으로 확인할 수 있다.
docker system df
사용하지 않는 이미지는 정리할 수 있다.
docker image prune -f
배포 workflow에도 이 명령을 넣었다.
docker image prune -f
이 명령은 사용하지 않는 dangling image를 정리한다. 실행 중인 컨테이너가 사용하는 이미지는 지우지 않는다.
Docker build cache 정리
Docker build cache도 계속 커질 수 있다.
완전히 지워버리면 다음 빌드가 많이 느려질 수 있다. 그래서 어느 정도는 남기고 제한하는 방식을 사용했다.
docker builder prune -f --keep-storage=10gb
의미는 대략 이렇다.
빌드 캐시를 정리하되,
최대 10GB 정도는 유지한다.
서버 용량이 넉넉하지 않기 때문에 무제한으로 두는 것보다 안전하다.
배포가 자주 일어나면 빌드 캐시가 쌓일 수 있으니, deploy workflow 마지막에 넣어두었다.
echo "Cleaning up old images and excess build cache"
docker image prune -f
docker builder prune -f --keep-storage=10gb
Git에 올라가는 설정과 아닌 설정
여기서 한 번 정리할 필요가 있었다.
/etc/docker/daemon.json은 서버 운영 설정이다. GitHub 프로젝트 소스에 올라가는 파일이 아니다.
구분하면 이렇게 된다.
GitHub repository에 올라가는 것
- 애플리케이션 소스
- Dockerfile
- GitHub Actions workflow
- 테스트 코드
서버에만 있는 것
- /etc/docker/daemon.json
- /etc/nginx 설정
- ~/docker/compose/.env
- SSL 인증서
- DB 데이터
- 백업 파일
- cron 설정
처음에는 “이런 설정도 Git에 올려야 하나?”라는 생각이 들었는데, 지금 구조에서는 서버 전용 운영 설정으로 두는 게 맞다.
다만 나중에 잊어버리지 않게 PROJECT_CONTEXT.md 같은 인계 문서에는 기록해두는 것이 좋다.
현재 디스크 관리 구조
현재 디스크 관리 흐름은 이렇게 정리된다.
Docker container logs
↓
json-file driver
↓
max-size 10m
max-file 5
Docker images
↓
docker image prune
Docker build cache
↓
docker builder prune --keep-storage=10gb
MSSQL backups
↓
주 1회 백업
56일 지난 파일 삭제
이 정도면 당장은 디스크가 갑자기 차는 상황을 어느 정도 막을 수 있다.
운영 중 가끔 볼 명령어
앞으로는 아래 명령어를 가끔 확인하면 된다.
df -h
전체 디스크 사용량 확인.
docker system df
Docker 이미지, 컨테이너, 볼륨, 빌드 캐시 사용량 확인.
sudo du -sh /var/lib/docker/containers
컨테이너 로그 사용량 확인.
ls -lh ~/docker/backups/mssql
DB 백업 파일 확인.
오늘 배운 점
Docker는 컨테이너를 쉽게 띄우게 해주지만, 오래 운영할 때 생기는 파일들까지 자동으로 관리해주지는 않는다.
처음에는 컨테이너가 Up 상태인지, HTTP 200이 나오는지만 봤다. 그런데 운영 쪽으로 넘어오면 로그, 백업, 빌드 캐시 같은 파일들이 계속 쌓인다.
이번에 느낀 건 서버 운영에서 디스크는 조용히 망가지는 부분이라는 점이다. 앱이 잘 돌아가고 있어도, 로그가 계속 쌓이다가 어느 날 디스크가 꽉 차면 DB나 컨테이너가 정상적으로 동작하지 않을 수 있다.
그래서 지금처럼 초기에 제한을 걸어두는 편이 마음이 편하다.
다음 작업
이제 서버 운영의 기본적인 안전장치는 어느 정도 갖춰졌다.
다음에는 테스트 구조를 정리한다.
처음에는 모든 테스트가 로컬 SQL Server나 LocalDB에 의존할 수 있었는데, Codex나 GitHub Actions 같은 환경에서는 LocalDB가 항상 있는 것이 아니다.
그래서 Unit Test와 Integration Test를 나누고, 실제 DB가 필요한 테스트는 GitHub Actions의 MSSQL 컨테이너에서 실행하도록 정리할 예정이다.