LabHub
배우기 러닝패스 코스

로그 파이프라인 설계 · 어디까지 읽었는지 기억하기 · 이론

바이트 위치 하나로는 부족하다

LabHub 에서 이어서 보기

한 줄 요약

파일을 따라 읽는 수집기는 어디까지 읽었는지를 디스크에 적어 두어야 하고, 그 기억은 바이트 위치 하나로는 부족하다. 같은 이름의 다른 파일이 되었는지(회전), 읽은 곳보다 작아졌는지(절단)를 함께 알아야 한다.

왜 이게 필요했나

야간 배포 뒤 대시보드의 로그가 뚝 끊겼다. 수집기 파드는 살아 있고 오류 로그도 없었다. 재시작하니 다시 흘렀다.

원인은 로그 회전이었다. 자정에 logrotateapp.logapp.log.1 로 옮기고 새 app.log 를 만들었다. 수집기는 "바이트 3억 2천만까지 읽었다" 는 기억만 들고 있었고, 새 파일은 겨우 몇 킬로바이트였다. 그 위치에서 읽으려 하니 읽을 것이 없었고, 새 파일이 그 위치를 넘어설 때까지 몇 시간 동안 아무것도 수집되지 않았다.

어떻게 동작하나

파일을 따라 읽는 일은 세 가지 상태 변화에 대응해야 한다.

| 상황 | 파일에서 보이는 것 | 해야 할 일 |
| --- | --- | --- |
| 덧붙임 | 크기만 커짐, inode 그대로 | 이어서 읽는다 |
| 회전(rename) | 같은 이름에 다른 inode | 새 파일을 처음부터 읽는다 |
| 절단(truncate) | inode 그대로인데 크기가 줄어듦 | 그 파일을 처음부터 읽는다 |

그래서 위치 기록에는 최소한 inode 와 offset 이 함께 들어간다. Fluent Bit 는 이 상태를 SQLite 데이터베이스(DB 설정)에 넣고, Fluentd 는 pos_file 에, Promtail 은 positions.yaml 에 넣었다. 이름은 달라도 하는 일은 같다.

여기서 한 가지가 더 남는다 — 회전된 파일에 아직 안 읽은 꼬리가 남아 있을 수 있다. 수집기가 잠깐 멈춘 사이에 회전이 일어나면 옛 파일의 마지막 몇 줄을 못 읽는다. 진짜 수집기들은 열어 둔 파일 서술자를 바로 놓지 않고 잠시 더 붙들어 이 꼬리를 읽는다(Rotate_Wait 같은 설정이 그것이다). 컨테이너 환경에서는 회전 주기가 짧고 파일이 작아서 이 창이 더 위험하다.

위치 파일이 없을 때의 정책도 정해야 한다. 처음부터 읽으면 이미 보낸 줄을 다시 보내 중복이 생기고, 끝부터 읽으면 그때까지 쌓인 줄을 통째로 잃는다. 어느 쪽도 공짜가 아니다. 그래서 위치 파일은 파드가 재시작해도 남는 자리(볼륨)에 두어야 하고, 그러지 못하면 중복을 감수하고 처음부터 읽는 쪽이 대개 안전하다 — 중복은 나중에 접을 수 있지만 잃은 줄은 되돌릴 수 없다.

현장에서 만나는 모습

가장 흔한 사고가 위의 "회전 뒤 조용한 중단" 이다. 증상이 조용해서 대시보드를 들여다보는 사람이 없으면 며칠도 간다. 수집기 자신의 지표(읽은 바이트, 열린 파일 수)를 재고 0 이 되면 경보를 거는 것이 유일한 방어다.

두 번째는 위치 파일을 emptyDir 에 두는 것이다. 파드가 재시작하면 기억이 사라지고, 정책에 따라 중복이 쏟아지거나 구멍이 생긴다.

세 번째는 copytruncate 방식이다. 원본을 복사한 뒤 원본을 0으로 잘라 버리므로 inode 는 그대로인데 크기가 줄어든다. inode 만 보는 수집기는 이 변화를 놓치고, 파일이 다시 자라 옛 위치를 넘어설 때까지 아무것도 못 읽는다.

다음 실습에서 할 것

결정적으로 번호가 붙은 로그를 쓰는 '애플리케이션' 을 돌려 두고, 이어 읽는 수집기를 직접 만든다. 덧붙임·회전·절단을 차례로 일으키며 매번 유실과 중복이 0인지 번호로 확인하고, 위치 파일이 없을 때 무슨 일이 생기는지 사본에서 실험해 숫자로 남긴다. 마지막에는 회전과 절단이 섞인 상황을 한 번에 통과시킨다.