로그 파이프라인 설계 · 어디까지 읽었는지 기억하기 · 실습
자정에 로그가 끊겼고 재시작하니 다시 흘렀다
목표
파일을 이어 읽는 수집기를 직접 만들고, 덧붙임·회전·절단을 차례로 일으키며 한 줄도 잃거나 겹치지 않는 것을 번호로 확인합니다.
왜 중요한가
수집기의 첫 일은 '어디까지 읽었는지' 를 기억하는 것이다. 그런데 바이트 위치 하나만 기억하면 자정의 로그 회전에서 조용히 멈춘다 — 새 파일은 작은데 기억해 둔 위치는 크기 때문이다. 오류도 경보도 없이 몇 시간이 비고, 재시작하면 다시 흐르기 때문에 원인도 안 남는다. 회전은 inode 가 달라지는 것으로, 절단은 크기가 읽은 곳보다 작아지는 것으로 알아챈다. 두 가지를 모두 봐야 하는 이유는 로테이션 방식이 두 가지이기 때문이다. 위치 기록이 사라졌을 때의 정책도 미리 정해 두어야 한다 — 중복과 유실 중 무엇을 감수할지는 공짜로 정해지지 않는다.
단계
1. /root/lp-tail 에서 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 200 "$(date +%s)" 1 로 seq 1부터 200까지의 로그를 만드세요. 그리고 app.log 를 이어 읽어 /root/lp-tail/collected.log 에 덧붙이고 읽은 위치를 /root/lp-tail/pos.txt 에 남기는 수집기를 만들어 한 번 돌리세요. pos.txt 에는 inode=<정수> 와 offset=<정수> 두 줄이 들어갑니다.
2. python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 201 로 seq 201부터 300까지를 덧붙이고 수집기를 다시 돌리세요. 수집이 끝나면 collected.log 의 줄 수가 300이어야 하고 같은 seq 가 두 번 들어 있으면 안 됩니다.
3. mv /root/lp-tail/app.log /root/lp-tail/app.log.1 로 회전을 일으키고 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 301 로 새 파일에 seq 301부터 400까지를 쓰세요. 그리고 수집기를 돌려 collected.log 가 400줄이 되게 하세요.
4. copytruncate 방식을 흉내 내세요 — cp /root/lp-tail/app.log /root/lp-tail/app.log.2 로 복사하고 : > /root/lp-tail/app.log 로 원본을 0으로 자른 다음 수집기를 한 번 돌립니다. 그리고 python3 /opt/lab/d5/applog.py logfmt /root/lp-tail/app.log 100 "$(date +%s)" 401 로 seq 401부터 500까지를 쓰고 수집기를 다시 돌려 그 구간이 빠짐없이 들어오게 하세요.
5. 지금 pos.txt 의 inode 가 실제 app.log 의 inode 와 같고 offset 이 파일 크기와 같은지 확인하세요. 그리고 /root/lp-tail/05-pos.txt 에 세 줄을 적습니다 — pos_inode=<정수>, file_inode=<정수>, offset_equals_size=<yes|no>.
6. 현재 디렉터리를 /root/lp-tail-nopos 로 통째로 복사하고, 거기서 pos.txt 를 지운 뒤 수집기를 한 번 돌리세요. 그리고 /root/lp-tail/06-nopos.txt 에 세 줄을 적습니다 — before=<복사 시점의 collected.log 줄 수>, after=<다시 돌린 뒤 줄 수>, duplicated=<두 값의 차>. 원래 디렉터리는 건드리지 마세요.
7. 원본 파일 전체(app.log 와 회전본들)에 들어 있는 seq 의 집합과 collected.log 의 seq 집합을 견주어 /root/lp-tail/tally.txt 에 네 줄을 적으세요 — source=<원본 줄 수>, collected=<수집본 줄 수>, missing=<원본에만 있는 seq 수>, duplicated=<수집본에서 두 번 이상 나온 seq 수>.
8. /root/lp-tail/chaos.sh 를 만들어 아래를 순서대로 하게 하세요 — (1) 회전(app.log 를 app.log.3 으로 옮기고 새 파일에 seq 501부터 550까지), (2) 수집, (3) 복사 후 절단(cp app.log app.log.4 다음 : > app.log)과 그 직후의 수집, (4) seq 551부터 600까지를 쓰고 다시 수집. 스크립트를 돌린 뒤 seq 1부터 600까지가 collected.log 에 빠짐없이 한 번씩 들어 있어야 합니다. 결과를 /root/lp-tail/08-chaos.txt 에 collected=<정수>, missing=0, duplicated=0 세 줄로 적으세요.
참고
- 작업 디렉터리는
/root/lp-tail입니다. 이 실습은 목적지를 쓰지 않습니다 — 읽는 쪽만 다룹니다. - 로그를 만드는 '애플리케이션' 은
/opt/lab/d5/applog.py입니다. 줄마다seq=번호가 들어 있어 나중에 유실과 중복을 정확히 셀 수 있습니다. 채점기는 이 파일을 읽지 않습니다. - 수집기는 어떤 언어로 써도 됩니다. 채점기는
collected.log와pos.txt의 내용만 봅니다. stat -c %i <파일>이 inode 를,wc -c < <파일>이 크기를 알려 줍니다.- 흔한 실수: 매번 파일을 통째로 다시 읽는 것. 2단계에서 줄 수가 두 배가 되며 드러납니다.
- 흔한 실수: 위치를 갱신하기 전에 죽는 것. 이 실습에서는 다루지 않지만, 실제 수집기는 보낸 뒤에 위치를 적어 최소 한 번 전달을 보장합니다.
- 이 실습의 한계: 잘린 뒤 새 줄이 쌓여 크기가 옛 위치와 정확히 같아지면 크기 비교로는 절단을 알아낼 수 없습니다. 그래서 수집기는 자주 돌아야 하고, 실제 수집기들은 파일 앞부분의 내용을 함께 기억해 이 경우까지 가려냅니다. 여기서는 자른 직후에 한 번 도는 것으로 그 창을 피합니다.
- [Fluent Bit — tail 입력](https://docs.fluentbit.io/manual/data-pipeline/inputs/tail) · [Fluentd — 설정 파일](https://docs.fluentd.org/configuration/config-file) · [Kubernetes — 클러스터 로깅 구조](https://kubernetes.io/docs/concepts/cluster-administration/logging/) · [Vector — 개념](https://vector.dev/docs/introduction/concepts/)
단계 8개
- 앱 로그를 만들고 처음 한 번 수집한다
- 덧붙은 줄만 이어 읽는다
- 회전 — 같은 이름의 다른 파일
- 절단 — inode 는 그대로인데 작아졌다
- 기억에 무엇이 들어 있어야 하는가
- 위치 기록이 사라지면 무슨 일이 생기나
- 응용 ① — 번호로 유실과 중복을 센다
- 응용 ② — 회전과 절단이 섞여도 한 줄도 잃지 않는다