LabHub
배우기 러닝패스 코스

Finding the Cause in Logs

The Logs from the Day of the Incident Were Already Deleted

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

회전된 로그 묶음에서 실제 보관 구간을 재고, 사고 구간이 그 범위 밖임을 숫자로 밝히고, 압축률을 실측해 보존 일수와 용량을 계산해 회전 정책으로 적습니다. 마지막으로 속도 제한에 걸려 애초에 기록되지 않는 줄을 셉니다.

왜 중요한가

조사 첫날 가장 자주 만나는 벽은 어려운 질문이 아니라 빈 디렉터리입니다. 그때 필요한 것은 한탄이 아니라 세 개의 숫자입니다 — 며칠이 모자랐는가, 얼마를 더 남겨야 하는가, 그 값이 디스크 예산에 맞는가. 세 숫자는 전부 지금 있는 파일에서 잴 수 있습니다. 그리고 "로그가 없다" 는 두 가지 뜻입니다. 회전으로 지워졌거나, 속도 제한에 걸려 애초에 기록되지 않았거나. 대책이 완전히 다르므로 둘을 가려야 합니다.

단계

  1. /root/keep/gen_keep.py 를 만들어 실행해 /root/keep/var/log/ 아래 보관 상태를 재현하세요.
  2. /root/keep/order.json 에 회전본을 오래된 것부터 세운 목록과 그 규칙을 적으세요.
  3. /root/keep/coverage.json 에 파일마다 줄 수와 첫 줄 · 마지막 줄의 시각을 적으세요.
  4. /root/keep/window.json 에 사고 구간이 보관 범위 안인지, 얼마나 모자랐는지 적으세요.
  5. /root/keep/budget.json 에 압축률 실측값과 보존 일수 · 용량 계산을 적으세요.
  6. /root/keep/logrotate.conf 에 회전 정책을 적으세요. rotate 값은 5단계의 계산과 같아야 합니다.
  7. /root/keep/ratelimit.json 에 속도 제한에 걸려 버려질 줄 수를 세어 적으세요.
  8. /root/keep/keep_report.md 에 네 절로 보고서를 남기세요.

참고

고객사의 보관 상태 재현하기

/root/keep/gen_keep.py 를 만들어 실행해 /root/keep/var/log/ 아래 payments.log(720줄) · payments.log.1(2000줄) · payments.log.2.gz · payments.log.3.gz · old/payments.log-20260407.gz · old/payments.log-20260408.gz · burst.ndjson(360줄)을 만드세요.

회전 이름이 두 가지로 섞여 있는 상태를 만듭니다. 번호가 붙은 것과 날짜가 붙은 것, 압축된 것과 안 된 것이 함께 있어야 이 실습이 성립합니다. 압축본은 파이썬 gzip.GzipFile 로 mtime=0 을 주어 쓰면 다시 만들어도 같은 파일이 나옵니다.

회전본을 오래된 것부터 세우기

/root/keep/order.jsonorder(파일 경로를 오래된 것부터 담은 배열, /root/keep/var/log/ 를 뺀 상대 경로)와 rule(그렇게 세운 규칙 한 문장)을 적으세요.

번호가 붙은 이름과 날짜가 붙은 이름은 정렬 방향이 서로 반대입니다. 한쪽은 숫자가 클수록 과거이고 다른 쪽은 이름이 작을수록 과거입니다. 지금 쓰는 파일이 가장 새 것입니다. 파일을 열지 않고 이름만으로 세워 보고, 다음 단계에서 내용으로 확인합니다.

파일마다 실제로 담긴 구간 재기

/root/keep/coverage.json 에 파일마다 file · lines · first_ts · last_ts 를 담은 객체를 오래된 것부터 배열로 적으세요. 시각은 로그 줄의 첫 칸을 그대로 씁니다.

파일 이름은 거짓말을 할 수 있습니다 — 회전이 실패했거나 사람이 손으로 옮긴 파일이 섞이면 이름 순서와 내용 순서가 어긋납니다. 그래서 내용으로 다시 잽니다. 압축본은 zcat 으로 읽거나 파이썬에서 gzip.open(path, 'rt') 로 엽니다.

사고 구간이 범위 밖임을 숫자로 밝히기

/root/keep/window.json 에 oldest_retained · newest_retained · incident_start · incident_end · incident_covered(참/거짓) · short_by_seconds(정수) · extra_rotations_needed(정수)를 적으세요. extra_rotations_needed 는 모자란 초를 하루로 나눈 뒤 올림한 값입니다.

사고 구간은 지시문의 참고 절에 적혀 있습니다. 모자란 초는 보관된 가장 오래된 시각에서 사고 시작 시각을 뺀 값입니다. 하루 단위로 회전하고 있으므로, 며칠치를 더 남겼어야 했는지는 올림으로 구합니다.

압축률을 실측해 보존 일수 계산하기

/root/keep/budget.json 에 sample_raw_bytes_per_day · sample_gz_bytes_per_day · compression_ratio · prod_raw_bytes_per_day · prod_gz_bytes_per_day · required_bytes_for_30_days · fits_in_budget · max_days_in_budget · rotate_value 를 지시문의 계산 규칙대로 적으세요.

압축률은 어림으로 쓰지 말고 이 고객의 파일로 잽니다 — 접근 로그와 스택트레이스는 줄어드는 정도가 다릅니다. delaycompress 때문에 최근 두 날은 원본 크기로 잡아야 하고, logrotate 의 rotate 는 현재 파일을 뺀 개수라는 점이 이 계산의 함정입니다.

계산한 값으로 회전 정책 적기

/root/keep/logrotate.conf/root/keep/var/log/payments.log 블록을 적으세요. daily · rotate <5단계의 rotate_value> · compress · delaycompress · missingok · notifempty · dateext · create 0640 root adm 이 있어야 하고, copytruncate 는 넣지 않습니다.

rotate 값은 5단계에서 계산한 그 숫자입니다 — 보존 일수와 같은 값이 아닙니다. copytruncate 를 빼는 이유는 매뉴얼에 적혀 있습니다. 복사와 비우기 사이의 짧은 틈에 쓰인 줄이 사라지기 때문이고, 그건 조사에서 가장 아쉬운 순간에 사라집니다.

애초에 기록되지 않는 줄 세기

/root/keep/ratelimit.json 에 interval_sec · burst · windows_over_limit · dropped_total · dropped_by_service(서비스 이름을 키로 한 객체) · worst_window(window_start · service · dropped)를 적으세요. 한 구간에서 버려지는 줄은 count - burst 이고 음수면 0입니다.

journald 의 속도 제한은 서비스별로 따로 적용되고, 한 구간에서 한도를 넘으면 그 구간의 나머지가 전부 버려집니다. 보관 기간을 아무리 늘려도 이 손실은 남습니다. worst_window 는 버려진 줄이 가장 많은 구간 하나입니다.

무엇을 바꿀지 글로 남기기

/root/keep/keep_report.md## 무엇이 없었나 ## 지금 보관 정책은 무엇인가 ## 얼마를 남겨야 하는가 ## 무엇을 바꾸기로 했나 네 절로 적으세요. 모자란 초 · rotate 값 · 예산 안에서 가능한 일수 · 속도 제한으로 버려질 줄 수를 숫자로 포함해야 합니다.

이 보고서를 읽는 사람은 디스크 예산을 쥔 사람입니다. '로그를 더 남겨 주세요' 가 아니라 '하루 N 바이트, 30일에 M 바이트, 예산 안에 들어옵니다' 라고 말해야 결정이 납니다. 속도 제한 손실은 보존과 다른 대책이 필요하다는 점도 함께 적으세요.