LabHub

로그 파이프라인 설계 · 로그 로테이션 · 이론

create 와 copytruncate

LabHub 에서 이어서 보기

한 줄 요약

create로그를 잃지 않지만 프로세스의 협조가 필요하고, copytruncate협조가 필요 없지만 로그를 조금 잃는다. 둘 다 공짜가 아니다.

왜 이게 필요했나

로그 파일이 커지면 디스크가 찬다. 그런데 프로세스가 파일을 열어 둔 채로 쓰고 있어서 그냥 지울 수 없다. 리눅스에서 rm 은 디렉터리 항목만 지우고, 열려 있는 파일의 블록은 마지막 핸들이 닫힐 때까지 반환되지 않는다. 그래서 df 는 꽉 찼다고 하고 du 는 파일이 없다고 한다.

로테이션은 이 문제를 두 가지 방식으로 푼다.

create — 이름을 바꾸고 새로 만든다

app.log → app.log.1 로 rename새 app.log 생성프로세스에 SIGHUP 을 보내 다시 열게 한다

rename 은 inode 를 그대로 두므로 프로세스는 여전히 옛 파일에 쓴다. 그래서 반드시 다시 열게 해야 한다. 그걸 안 하면 새 app.log 는 영원히 0바이트고 실제 로그는 app.log.1 로 계속 흘러간다. 로그를 잃지는 않지만 아무도 못 찾는다.

copytruncate — 복사하고 잘라 낸다

app.log → app.log.1 로 복사app.log 를 0바이트로 truncate

파일이 그대로이므로 프로세스는 아무것도 몰라도 된다. 대신 복사와 자르기 사이에 쓴 로그는 사라진다. 초당 수천 줄이 쌓이는 로그라면 그 틈이 크다.

어떻게 동작하나

/var/log/app/*.log {    daily    rotate 14              # 14개까지 보관    size 100M              # 또는 100MB 넘으면    compress    delaycompress          # 직전 것은 압축하지 않는다(아직 쓰고 있을 수 있다)    missingok    notifempty    create 0640 app app    postrotate        kill -HUP $(cat /run/app.pid) 2>/dev/null || true    endscript}

delaycompress 가 중요하다. create 방식에서 프로세스가 아직 옛 파일에 쓰고 있을 수 있는데, 그걸 바로 압축하면 쓰기가 깨진다.

흔한 착각

컨테이너에서 logrotate 를 돌리는 것. 컨테이너는 대개 로그를 stdout 으로 낸다. 그러면 컨테이너 런타임(containerd)이 파일로 받아 적고 런타임이 로테이션한다. kubelet 의 containerLogMaxSize(기본 10Mi), containerLogMaxFiles(기본 5)가 그 설정이다. 컨테이너 안에서 logrotate 를 돌릴 이유가 없다.

로테이션이 수집기를 깨뜨리는 것. Fluent Bit 같은 수집기는 inode 를 기준으로 파일을 추적한다. create 로 rename 되면 수집기가 옛 inode 를 계속 읽다가 새 파일을 놓칠 수 있다. Rotate_Wait 설정이 그 틈을 메운다.

실무에서 진짜 중요한 것

보관 기간은 디스크가 아니라 규정과 조사 필요로 정한다. "디스크가 남으니 90일" 이 아니라 "장애 조사에 보통 2주가 필요하고 감사 요건이 1년" 이라면 뜨거운 저장소 2주 + 차가운 저장소 1년으로 나눈다.

그리고 압축은 거의 항상 이득이다. 텍스트 로그는 gzip 으로 10~20배 줄어든다. CPU 를 조금 쓰고 디스크를 크게 아낀다.