logrotate 서비스

강정우·약 17시간 전

Dev_Ops

목록 보기
28/28
post-thumbnail

Gunicorn + Logrotate + 블록 스토리지 아카이브 완전 가이드

Django + Gunicorn 환경에서 로그를 파일로 기록하고, logrotate로 자동 관리하며,
쌓인 데이터를 별도 블록 스토리지에 연/월 구조로 보관하는 전 과정을 다룬다.


목차

  1. 블록 스토리지 연결 및 마운트
  2. Gunicorn 로그 출력 설정
  3. Logrotate 옵션 상세 정리
  4. Logrotate 설정 작성
  5. 아카이브 스크립트 설계
  6. 디스크 사용량 계산
  7. 기존 거대 로그 정리
  8. 요약

1. 블록 스토리지 연결 및 마운트

1-1. 현재 디스크 상태 확인

lsblk

마운트 여부와 관계없이 연결된 블록 디바이스를 모두 트리 형태로 보여준다.
파일시스템 타입과 UUID까지 함께 확인하려면 -f 옵션을 붙인다.

lsblk -f

NHN Cloud 등 클라우드 환경에서 추가 볼륨을 attach하면 vdb와 같은 이름으로 나타난다.

1-2. 파티션 분리 여부 판단

클라우드 블록 스토리지를 단순 용량 확장 목적으로 쓴다면, 파티션 없이 디스크 전체를 그대로 사용하는 것이 깔끔하다.
파티션이 유용한 경우는 다음과 같다.

  • 용도별(/data, /logs)로 용량을 강제 제한하고 싶을 때
  • 한 영역이 꽉 차도 나머지에 영향이 없도록 격리하고 싶을 때

1-3. 포맷 및 마운트

# ext4로 포맷
sudo mkfs.ext4 /dev/vdb

# 마운트 포인트 생성
sudo mkdir -p /mnt/data

# 마운트
sudo mount /dev/vdb /mnt/data

# 확인
df -h

1-4. 재부팅 후 자동 마운트 (fstab 등록)

장치명(vdb)은 재부팅 시 바뀔 수 있어 UUID 기반으로 등록해야 안전하다.

# UUID 확인
sudo blkid /dev/vdb

출력 예시:

/dev/vdb: UUID="3de85581-2e2a-43f5-878e-b6a87d2fd905" BLOCK_SIZE="4096" TYPE="ext4"
# fstab에 추가
echo "UUID=3de85581-2e2a-43f5-878e-b6a87d2fd905  /mnt/data  ext4  defaults  0  2" | sudo tee -a /etc/fstab

# 재부팅 없이 검증
sudo mount -a

# 최종 확인
df -h

2. Gunicorn 로그 출력 설정

2-1. 문제: stdout으로 흘러가는 기본 구성

일반적인 gunicorn.service 파일은 이렇게 되어 있다.

ExecStart=/path/to/.venv/bin/gunicorn \
          --access-logfile - \
          --error-logfile - \
          ...

여기서 -는 stdout/stderr로 출력한다는 의미다.
journald로만 데이터가 흘러가 별도 경로에 기록되지 않는다.
journald는 용량 제한이 있고 검색도 불편하기 때문에, 운영 환경에서는 직접 경로를 지정하는 것이 일반적이다.

2-2. 로그 디렉토리 생성

sudo mkdir -p /var/log/mch_api
sudo chown ubuntu:www-data /var/log/mch_api
sudo chmod 755 /var/log/mch_api
  • ubuntu:www-data: gunicorn이 ubuntu 계정으로 구동되므로 해당 소유자에게 쓰기 권한을 부여한다.
  • 디렉토리명은 서비스에 맞게 자유롭게 지정(ssc_api, mch_api 등).

2-3. gunicorn.service 수정

[Unit]
Description=gunicorn daemon for MCH
Requires=gunicorn.socket
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/workspace/smart-silver-center/ssc-api

ExecStart=/workspace/smart-silver-center/ssc-api/.venv/bin/gunicorn \
          --access-logfile /var/log/mch_api/access.log \
          --error-logfile /var/log/mch_api/error.log \
          --workers 4 \
          --threads 2 \
          --worker-class gthread \
          --timeout 60 \
          --graceful-timeout 30 \
          --max-requests 1000 \
          --max-requests-jitter 100 \
          --keep-alive 5 \
          --bind unix:/run/gunicorn.sock \
          --preload \
          --capture-output \
          --enable-stdio-inheritance \
          --log-level info core.michuhol.wsgi:application

Environment="ENV_FILE=.env"
Restart=on-failure
RestartSec=5
LimitNOFILE=4096

[Install]
WantedBy=multi-user.target

변경점은 두 줄이다.

- --access-logfile - \
- --error-logfile - \
+ --access-logfile /var/log/mch_api/access.log \
+ --error-logfile /var/log/mch_api/error.log \

2-4. 적용 및 확인

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

# 경로 생성 확인
ls -la /var/log/mch_api/

# 실시간 확인
tail -f /var/log/mch_api/access.log

기록이 안 된다면 아래 순서로 점검한다.

sudo systemctl status gunicorn.service
sudo journalctl -u gunicorn.service -n 30 --no-pager
ls -la /var/log/ | grep mch_api
cat /etc/systemd/system/gunicorn.service | grep logfile

3. Logrotate 옵션 상세 정리

3-1. 회전 주기

옵션설명
daily매일 회전
weekly매주 회전
monthly매월 회전

API 서버라면 daily가 일반적이다.

3-2. 보관 관련

옵션설명
rotate 14회전된 항목을 최대 14개까지 유지. 초과 시 오래된 것부터 삭제
maxsize 100M주기 무관하게 100MB를 넘으면 강제 회전
minsize 1M1MB 미만이면 주기가 돼도 넘어감
maxage 3030일 지난 항목 삭제 (rotate는 개수 기준, maxage는 날짜 기준)

3-3. 압축 관련

옵션설명
compressgzip으로 압축
delaycompress직전 회전본은 압축하지 않음. 장애 시 zcat 없이 바로 열람 가능
compresscmd bzip2압축 프로그램 변경

3-4. 파일 생성/처리

옵션설명
create 0644 ubuntu www-data회전 후 새 항목을 지정 권한/소유자로 생성
copytruncate원본 복사 후 비움. 디스크립터를 새로 열지 못하는 프로세스에 유용. 복사~truncate 사이 유실 가능
nocreate회전 후 새 항목 자동 생성 안 함

3-5. 예외 처리

옵션설명
missingok대상이 없어도 에러 없이 통과
notifempty비어 있으면 회전하지 않음

3-6. 스크립트 실행

postrotate
    systemctl reload gunicorn 2>/dev/null || true
endscript
옵션설명
postrotate / endscript회전 후 실행할 명령. 보통 서비스에 시그널을 보내 새 경로를 열게 함
prerotate / endscript회전 전 실행
sharedscripts*.log처럼 여러 항목 매칭 시 postrotate를 한 번만 호출

sharedscripts가 없으면 access.log 회전 후 한 번, error.log 회전 후 또 한 번 reload가 중복 호출된다.

2>/dev/null 이란?

리눅스의 모든 프로세스는 세 가지 기본 스트림을 가진다.

번호이름의미
0stdin입력
1stdout일반 출력
2stderr에러 출력

2>/dev/null은 stderr(에러 메시지)를 /dev/null(쓰레기통)로 버리라는 의미다.

systemctl reload gunicorn 2>/dev/null || true
#                          ↑              ↑
#             에러 메시지 무시    실패해도 종료 코드 0으로 처리

gunicorn이 내려가 있거나 reload 자체가 실패해도, logrotate 전체가 오류로 처리되지 않도록 하는 방어 코드다.

3-7. 네이밍

옵션설명
dateextaccess.log.1 대신 access.log-20260304 형식으로 날짜 기반 이름 생성
dateformat -%Y%m%d-%s날짜 포맷 커스터마이징

4. Logrotate 설정 작성

4-1. 설정 파일 생성

/etc/logrotate.d/ 아래에 서비스명으로 생성한다.
파일 이름은 서비스명과 일치할 필요 없다. 중요한 건 안에 적는 경로다.

sudo nano /etc/logrotate.d/ssc_api

4-2. 내용

/var/log/ssc_api/*.log {
    daily
    missingok
    rotate 14
    maxsize 100M
    compress
    delaycompress
    notifempty
    create 0644 ubuntu www-data
    dateext
    sharedscripts
    postrotate
        systemctl reload gunicorn 2>/dev/null || true
        /usr/local/bin/archive_logs.sh >> /var/log/archive_logs.log 2>&1
    endscript
}

4-3. 검증 및 실행

# 문법 검증 (dry-run, 실제 동작 없음)
sudo logrotate -d /etc/logrotate.d/ssc_api

# 강제 실행 + verbose
sudo logrotate -vf /etc/logrotate.d/ssc_api

# 결과 확인
ls -lh /var/log/ssc_api/
옵션설명
-ddry-run. 실제 동작 없이 문제만 점검
-vverbose. 과정을 출력하며 실행
-fforce. 주기 무관하게 강제 회전

logrotate는 데몬이 아니므로 설정 수정 후 별도 재시작이 필요 없다.
cron이 매일 호출하는 구조이기 때문에, 저장 즉시 다음 실행부터 반영된다.

자주 보이는 메시지

glob finding logs to compress failed
glob finding old rotated logs failed

에러가 아니다. "이전에 회전된 항목을 찾으려 했는데 아직 없다"는 정보성 메시지로, 최초 실행 시 정상적으로 출력된다.


5. 아카이브 스크립트 설계

5-1. 전체 구조

/var/log/ssc_api/              ← 현재 활성 로그 (그대로 유지)
    gunicorn_access.log
    gunicorn_error.log
    gunicorn_error.log-20260304  ← delaycompress 대기 중 (미압축)

/mnt/data/logs/ssc_api/        ← 압축 완료된 항목 아카이브
    2026/
        03/
            gunicorn_access.log-20260301.gz
            gunicorn_error.log-20260304.gz
        04/
            ...

5-2. 디렉토리 준비

sudo mkdir -p /mnt/data/logs/ssc_api
sudo chown ubuntu:ubuntu /mnt/data/logs/ssc_api

5-3. 스크립트 작성

sudo nano /usr/local/bin/archive_logs.sh
#!/bin/bash

SRC="/var/log/ssc_api"
DEST="/mnt/data/logs/ssc_api"

# .gz 항목만 대상 (delaycompress로 압축 완료된 것만)
for f in "$SRC"/*.log-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*.gz; do
    [ -f "$f" ] || continue

    filename=$(basename "$f")

    # 파일명에서 날짜 추출 (예: gunicorn_access.log-20260304.gz → 20260304)
    datestr=$(echo "$filename" | grep -oP '\d{8}')
    [ -z "$datestr" ] && continue

    year="${datestr:0:4}"
    month="${datestr:4:2}"

    target_dir="$DEST/$year/$month"
    mkdir -p "$target_dir"

    mv "$f" "$target_dir/"
    echo "[$(date)] Archived: $filename$target_dir/"
done
sudo chmod +x /usr/local/bin/archive_logs.sh

5-4. ⚠️ delaycompress와의 충돌 주의

delaycompress는 직전 회전본을 하루 동안 압축하지 않은 채 둔다.
glob 패턴을 .gz 없이 작성하면 미압축 항목도 매칭되어 /mnt/data로 이동해버린다.
다음 날 logrotate가 압축 대상을 찾지 못해 아카이브에 비압축 항목이 쌓이는 결과로 이어진다.

# ❌ 잘못된 패턴 (미압축도 매칭됨)
"$SRC"/*.log-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*

# ✅ 올바른 패턴 (압축 완료본만 이동)
"$SRC"/*.log-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]*.gz

5-5. 전체 동작 흐름 검증

Day 1 logrotate 실행:
  - access.log → access.log-20260303 (압축 안 됨, delaycompress)
  - postrotate: archive_logs.sh → .gz 없으므로 아무것도 안 함 ✅

Day 2 logrotate 실행:
  - access.log-20260303 → access.log-20260303.gz (압축 완료)
  - access.log → access.log-20260304 (새로 회전)
  - postrotate: archive_logs.sh → access.log-20260303.gz를 /mnt/data/2026/03/ 으로 이동 ✅

/var/log/ssc_api/ 에는:
  - gunicorn_access.log          (현재 활성)
  - gunicorn_access.log-20260304 (어제, 아직 압축 전)
  → 깔끔하게 유지 ✅

5-6. cron 등록 (보조 보험용)

수동 생성 항목 등 누락 케이스를 대비한 추가 트리거다.

sudo crontab -e
# 매일 새벽 2시 실행
0 2 * * * /usr/local/bin/archive_logs.sh >> /var/log/archive_logs.log 2>&1

이미 이동된 항목은 glob 매칭에서 걸리지 않으므로 중복 실행해도 무방하다.


6. 디스크 사용량 계산

6-1. 일일 생성량 파악

gunicorn_access.log   2.6GB / 5일 → 약 520MB/day
gunicorn_error.log    582MB / 5일 → 약 116MB/day

6-2. 회전 빈도 계산

maxsize 100M 적용 시:

  • access: 520MB ÷ 100MB = 하루 약 5회 회전
  • error: 116MB ÷ 100MB = 하루 약 1~2회 회전

6-3. 실제 보관 기간

rotate 14는 회전된 항목 14개를 유지한다는 뜻이다.
하루에 5회 회전한다면 14개는 약 2~3일치에 불과하다.

대상하루 회전 횟수rotate 14 기준 실제 보관 기간
access.log5회약 2~3일
error.log1~2회약 7~14일

14일치 보장이 필요하다면:

rotate 70      # 하루 5회 × 14일 = 70개

6-4. 디스크 사용량 추정

gzip 압축 시 텍스트 로그는 원본의 약 10~15% 수준이 된다.

access.log 기준 (rotate 14 적용):

항목크기
현재 활성 (최대)100MB
delaycompress 대기 (미압축 1개)100MB
압축 완료 12개 (100MB × 0.12 × 12)약 144MB
소계약 344MB

error.log도 동일 구조로 약 344MB.
총 합계: 약 700MB (기존 7GB 대비 1/10 수준)

rotate 70 적용 시 약 1.2GB 수준으로 여전히 기존 대비 대폭 절감된다.


7. 기존 거대 로그 정리

logrotate 도입 전 이미 쌓인 대용량 항목은 수동으로 처리한다.

# 압축 보관
sudo gzip /var/log/ssc_api/gunicorn_access.log
sudo gzip /var/log/ssc_api/gunicorn_error.log

# 불필요하다면 삭제
sudo rm /var/log/ssc_api/gunicorn_access.log

# 여유 공간 확인
df -h /var/log

8. 요약

단계작업
1블록 스토리지 attach 후 ext4 포맷 및 /mnt/data 마운트
2fstab에 UUID 기반으로 등록해 자동 마운트 보장
3gunicorn의 --access-logfile -를 실제 경로로 변경
4/etc/logrotate.d/ 아래에 설정 작성
5archive_logs.sh 작성 시 glob 패턴을 .gz로 한정 (delaycompress 충돌 방지)
6postrotate에서 archive_logs.sh 호출, cron으로 보조 트리거 등록
7sudo logrotate -d로 문법 점검 → sudo logrotate -vf로 강제 실행 확인
8로그 생성량에 따라 rotate 값 조정으로 원하는 보관 기간 확보
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글