一条竖线省下三十分钟排查
한국어 원문으로 표시합니다.
목표
오류율 패널 하나짜리 대시보드에 배포와 장애를 주석으로 올려, 그래프가 '언제' 뿐 아니라 '그 때 무슨 일이 있었나' 까지 답하게 만듭니다. 주석은 API 로 남기고 다시 읽어 확인하며, 배포 파이프라인이 자동으로 남기는 기록기도 만듭니다.
왜 중요한가
지표는 값이 변한 시각까지만 답한다. 그 시각에 사람이 무엇을 했는지는 지표 바깥의 사건이라 같은 화면에 없고, 그래서 새벽에 호출받은 사람은 배포 기록과 채팅과 설정 저장소를 오가며 시계를 맞춘다. 조사 시간의 상당 부분이 그 왕복이다. 주석은 그 사건을 같은 시간축 위에 올려 왕복을 없앤다. 다만 주석은 사후에 만들 수 없는 기록이라 그때 남겨야 하고, 그러려면 사람의 손이 아니라 파이프라인이 남겨야 한다. 무엇을 적을지도 미리 정해 두어야 한다 — 다음 사람이 그 자리에서 되돌릴 수 있으려면 버전과 사람과 되돌리는 명령이 주석 안에 있어야 한다.
단계
lab-start-grafana로 Grafana 를 띄우고, uid 가gfd-annot인 대시보드를 만드세요. 패널은 shop-api 의 5xx 비율을 그리는timeseries한 장입니다. 그리고/root/gfd-annotations/01-blind.txt에 세 줄을 적으세요 —question=뒤에 이 패널이 답하는 질문,unanswerable=뒤에 이 패널만으로는 답할 수 없는 질문,reason=뒤에 왜 답할 수 없는지를 40자 이상으로 적습니다.POST /api/annotations로 이 대시보드(dashboardUID가gfd-annot)에 배포 주석 하나를 남기세요. 태그에deploy가 있어야 하고, 본문에는version=vX.Y.Z형태의 버전이 들어가야 합니다. 시각은 지금부터 30분 전으로 둡니다(밀리초 에포크). 응답에 돌아온id로GET /api/annotations를 다시 읽어 확인한 뒤,/root/gfd-annotations/02-annot.txt에 두 줄id=<그 id>와version=<적은 버전>을 적으세요.- 시작과 끝이 있는 구간 주석을 하나 남기세요. 태그는
incident이고, 시작은 지금부터 25분 전, 끝은 지금부터 5분 전입니다(따라서 길이는 20분). 본문에는 무엇이 있었는지 한 줄로 적습니다. 그리고/root/gfd-annotations/03-region.txt에 두 줄id=<그 id>와duration_min=<구간 길이(분)>를 적으세요. - 대시보드의
annotations.list에 주석 질의 두 개를 선언하세요. 하나는deploy태그를, 다른 하나는incident태그를 가져옵니다. 두 항목 모두 켜져 있어야 하고(enable), 서로 다른 색(iconColor)을 가지며, 대상은 태그로 거르는 형태(target.type이tags)여야 합니다. 두 태그를 모두 가진 주석만 가져오는 일이 없도록 한 항목에는 태그를 하나씩만 둡니다. /root/gfd-annotations/annotate-deploy.sh를 만드세요. 첫 인자로 버전을 받아gfd-annot대시보드에 주석을 남기고, 태그는deploy와auto두 개, 본문에는version=,by=,rollback=세 값이 들어갑니다. 환경변수DRY_RUN=1이 주어지면 쏘지 않고 보낼 JSON 본문만 표준출력에 찍고 끝나야 합니다(그때 출력은 JSON 하나뿐이어야 합니다). 그리고 그 스크립트로 서로 다른 두 버전을 실제로 기록하세요./root/gfd-annotations/annotation-fields.txt에 배포 주석에 반드시 적을 필드 이름을 한 줄에 하나씩 적으세요 —version,by,rollback셋은 반드시 들어가야 합니다. 그리고 그 형식을 지키는 주석 하나를gfd-annot대시보드에 남기세요. 태그는deploy와runbook두 개이고, 본문에는 적어 둔 모든 필드가필드이름=값형태로 들어가야 합니다.rollback의 값은 그대로 칠 수 있는 명령이어야 하므로 10자 이상입니다.runbook태그가 붙은 주석은 이 하나뿐이어야 합니다./root/gfd-annotations/07-timeline.tsv를 만드세요.gfd-annot대시보드에 달린 모든 주석을 시각 오름차순(같으면 id 오름차순)으로 한 줄씩, 탭으로 나눈 세 칸<id> <태그 하나> <요약>으로 적습니다. 둘째 칸은 그 주석에 실제로 붙어 있는 태그 중 하나여야 하고, 셋째 칸은 4자 이상의 요약입니다./root/gfd-annotations/08-finding.txt에 네 줄을 적으세요 —deploy_id=는 2단계에서 남긴 배포 주석의 id,incident_id=는 3단계에서 남긴 구간 주석의 id,gap_min=은 배포 시각과 장애 시작 시각의 차이를 분으로 반올림한 정수(0 이상),verdict=는 이 두 사건을 이어 내린 결론을 60자 이상으로 적은 문장입니다. 두 시각은 API 에서 다시 읽어 계산하세요.
참고
- 작업 디렉터리는
/root/gfd-annotations입니다. 배포 이력 재료는/opt/lab/gfd/gfd-annotations/releases.tsv이고, 그 파일을 만든 스크립트는/opt/lab/gfd/gfd-annotations/make.sh입니다. - Grafana 는
lab-start-grafana로 켭니다(20~40초). 익명 Admin 이라 토큰 없이 API 를 쓸 수 있고, 터미널 위쪽 웹 미리보기의 3000번 포트로 화면을 열 수 있습니다. - 주석의 시각은 밀리초 에포크 정수입니다. 초 단위로 넣으면 1970년대 어딘가에 찍혀 화면에서 사라집니다.
- 이 환경에서 판정할 수 없는 것: 주석이 화면에 정말 세로줄로 그려지는가. 패널은 브라우저가 그리고 이미지 렌더러 플러그인이 없습니다. 채점은 전부 API 응답과 대시보드 모델로만 합니다 — 질의가 선언돼 있고 그 태그로 주석이 조회된다면 그려질 조건은 갖춘 것이지만, 그것과 '그려졌다' 는 같은 말이 아닙니다.
- 흔한 실수:
dashboardUID없이 만들어 조직 전체 주석이 된 것. 만든 직후에GET으로 다시 읽어 확인하는 습관이 이 실수를 그 자리에서 잡아 줍니다. - Annotate visualizations · Annotations HTTP API · Dashboard HTTP API · Dashboard JSON model
그래프는 '언제' 까지만 답한다
lab-start-grafana 로 Grafana 를 띄우고, uid 가 gfd-annot 인 대시보드를 만드세요. 패널은 shop-api 의 5xx 비율을 그리는 timeseries 한 장입니다. 그리고 /root/gfd-annotations/01-blind.txt 에 세 줄을 적으세요 — question= 뒤에 이 패널이 답하는 질문, unanswerable= 뒤에 이 패널만으로는 답할 수 없는 질문, reason= 뒤에 왜 답할 수 없는지를 40자 이상으로 적습니다.
Grafana 기동에는 20~40초가 걸립니다. curl -s http://127.0.0.1:3000/api/health 가 "database": "ok" 를 줄 때까지 기다리세요.
5xx 비율은 개수가 아니라 비율입니다. 5xx 요청의 초당 건수를 전체 요청의 초당 건수로 나눕니다. promq "<PromQL>" 로 먼저 던져 보고 숫자가 나오는지 확인하세요.
패널 타입이 timeseries 여야 하는 이유가 다음 단계에서 드러납니다. 공식 문서는 주석을 지원하는 시각화가 Time series, State timeline, Candlestick 이라고 적습니다 — 숫자 한 칸짜리 패널에는 사건을 올릴 자리가 없습니다.
배포를 주석으로 남기고 다시 읽어 확인한다
POST /api/annotations 로 이 대시보드(dashboardUID 가 gfd-annot)에 배포 주석 하나를 남기세요. 태그에 deploy 가 있어야 하고, 본문에는 version=vX.Y.Z 형태의 버전이 들어가야 합니다. 시각은 지금부터 30분 전으로 둡니다(밀리초 에포크). 응답에 돌아온 id 로 GET /api/annotations 를 다시 읽어 확인한 뒤, /root/gfd-annotations/02-annot.txt 에 두 줄 id=<그 id> 와 version=<적은 버전> 을 적으세요.
필수 필드는 text 하나입니다. dashboardUID 를 적지 않으면 조직 전체 주석이 되어 이 대시보드로 거를 수 없습니다.
curl -s -X POST -H 'Content-Type: application/json' \
-d '{"dashboardUID":"gfd-annot","time":1700000000000,"tags":["deploy"],"text":"..."}' \
http://127.0.0.1:3000/api/annotations
curl -sG http://127.0.0.1:3000/api/annotations --data-urlencode 'dashboardUID=gfd-annot' | jq
시각 단위를 틀리는 실수가 가장 흔합니다. 초 단위로 넣으면 1970년대 어딘가에 찍혀 화면에서 사라집니다. 30분 전은 $(( ($(date +%s) - 1800) * 1000 )) 입니다.
버전은 다음 단계들에서도 씁니다. 배포 이력은 /opt/lab/gfd/gfd-annotations/releases.tsv 에 있습니다.
구간 주석으로 장애 시간을 표시한다
시작과 끝이 있는 구간 주석을 하나 남기세요. 태그는 incident 이고, 시작은 지금부터 25분 전, 끝은 지금부터 5분 전입니다(따라서 길이는 20분). 본문에는 무엇이 있었는지 한 줄로 적습니다. 그리고 /root/gfd-annotations/03-region.txt 에 두 줄 id=<그 id> 와 duration_min=<구간 길이(분)> 를 적으세요.
구간 주석은 점 주석과 같은 자리에 만듭니다. 다른 점은 timeEnd 를 함께 보낸다는 것뿐입니다. 공식 문서는 Grafana 6.4 부터 구간이 time 과 timeEnd 를 가진 항목 하나로 표현된다고 적습니다.
두 시각을 각각 date 로 따로 계산하면 초가 어긋나 길이가 20분에서 밀립니다. 기준 시각을 한 번만 구해 놓고 거기서 빼세요.
NOW=$(date +%s)
echo $(( (NOW - 1500) * 1000 )) $(( (NOW - 300) * 1000 ))
채점기는 duration_min 을 그대로 믿지 않고 주석의 두 시각에서 다시 계산해 대조합니다.
태그로 갈라 대시보드가 가져오게 한다
대시보드의 annotations.list 에 주석 질의 두 개를 선언하세요. 하나는 deploy 태그를, 다른 하나는 incident 태그를 가져옵니다. 두 항목 모두 켜져 있어야 하고(enable), 서로 다른 색(iconColor)을 가지며, 대상은 태그로 거르는 형태(target.type 이 tags)여야 합니다. 두 태그를 모두 가진 주석만 가져오는 일이 없도록 한 항목에는 태그를 하나씩만 둡니다.
대시보드 JSON 의 annotations.list 는 항목 배열입니다. 항목 하나가 화면 위쪽의 토글 하나가 되고, 이름이 그 토글의 이름입니다.
내장 주석 데이터소스를 가리키려면 datasource 를 {"type": "grafana", "uid": "-- Grafana --"} 로 둡니다.
curl -s http://127.0.0.1:3000/api/dashboards/uid/gfd-annot \
| jq '.dashboard.annotations.list'
종류가 섞이면 토글이 쓸모없어집니다. 배포만 보고 싶을 때 장애 구간까지 함께 켜지면 결국 아무도 토글을 쓰지 않습니다.
파이프라인이 자동으로 남기게 만든다
/root/gfd-annotations/annotate-deploy.sh 를 만드세요. 첫 인자로 버전을 받아 gfd-annot 대시보드에 주석을 남기고, 태그는 deploy 와 auto 두 개, 본문에는 version=, by=, rollback= 세 값이 들어갑니다. 환경변수 DRY_RUN=1 이 주어지면 쏘지 않고 보낼 JSON 본문만 표준출력에 찍고 끝나야 합니다(그때 출력은 JSON 하나뿐이어야 합니다). 그리고 그 스크립트로 서로 다른 두 버전을 실제로 기록하세요.
자동 기록기는 바쁜 날에도 빠지지 않는 것이 전부입니다. 그래서 형식을 고정하고, 사람이 손으로 부르는 일이 없게 만듭니다.
찍어 보는 모드를 두는 이유는 두 가지입니다. 파이프라인 안에서만 도는 스크립트는 고장 났을 때 확인하기 어렵고, 시험이 부작용 없이 돌 수 있어야 하기 때문입니다. 채점기도 이 모드로 본문을 검사합니다 — 그리고 그 전후로 주석 수가 늘지 않는지도 함께 봅니다.
본문을 만들 때 문자열을 손으로 이어 붙이면 따옴표에서 깨집니다. jq -n --arg 로 만드세요. 시각을 숫자로 넣으려면 --argjson 입니다.
배포 이력은 /opt/lab/gfd/gfd-annotations/releases.tsv 에 있습니다.
다음 사람이 그 자리에서 행동할 수 있는가
/root/gfd-annotations/annotation-fields.txt 에 배포 주석에 반드시 적을 필드 이름을 한 줄에 하나씩 적으세요 — version, by, rollback 셋은 반드시 들어가야 합니다. 그리고 그 형식을 지키는 주석 하나를 gfd-annot 대시보드에 남기세요. 태그는 deploy 와 runbook 두 개이고, 본문에는 적어 둔 모든 필드가 필드이름=값 형태로 들어가야 합니다. rollback 의 값은 그대로 칠 수 있는 명령이어야 하므로 10자 이상입니다. runbook 태그가 붙은 주석은 이 하나뿐이어야 합니다.
주석의 내용은 취향이 아니라 계약입니다. 새벽 세 시에 그 주석을 본 사람이 다른 창을 열지 않고 다음 행동을 할 수 있어야 합니다. 커밋 해시 하나만 적힌 주석은 그 사람을 저장소로 보냅니다.
되돌리는 명령을 적어 두는 것이 특히 중요합니다. 배포한 사람이 자고 있어도 다른 사람이 되돌릴 수 있어야 하고, 그러려면 명령이 주석 안에 있어야 합니다. 실제 명령은 /opt/lab/gfd/gfd-annotations/releases.tsv 에 있습니다.
채점기는 여러분이 적어 둔 필드 목록을 읽고, 그 목록대로 주석이 채워졌는지 대조합니다.
주석을 시간순으로 다시 읽어 조사 기록을 만든다
/root/gfd-annotations/07-timeline.tsv 를 만드세요. gfd-annot 대시보드에 달린 모든 주석을 시각 오름차순(같으면 id 오름차순)으로 한 줄씩, 탭으로 나눈 세 칸 <id> <태그 하나> <요약> 으로 적습니다. 둘째 칸은 그 주석에 실제로 붙어 있는 태그 중 하나여야 하고, 셋째 칸은 4자 이상의 요약입니다.
정렬 기준을 채점기와 맞춰야 합니다. jq 의 sort_by(.time, .id) 를 쓰면 같은 밀리초에 찍힌 주석도 순서가 흔들리지 않습니다.
curl -sG http://127.0.0.1:3000/api/annotations --data-urlencode 'dashboardUID=gfd-annot' \
| jq -r 'sort_by(.time, .id)[] | [(.id|tostring), .tags[0], .text] | @tsv'
이 표가 조사 기록의 뼈대입니다. 시간순으로 늘어놓고 나면 '배포 → 오류 → 되돌림' 같은 순서가 눈에 들어오고, 그 순서가 곧 원인 가설입니다.
배포와 장애를 이어 결론을 적는다
/root/gfd-annotations/08-finding.txt 에 네 줄을 적으세요 — deploy_id= 는 2단계에서 남긴 배포 주석의 id, incident_id= 는 3단계에서 남긴 구간 주석의 id, gap_min= 은 배포 시각과 장애 시작 시각의 차이를 분으로 반올림한 정수(0 이상), verdict= 는 이 두 사건을 이어 내린 결론을 60자 이상으로 적은 문장입니다. 두 시각은 API 에서 다시 읽어 계산하세요.
채점기는 두 id 를 API 로 다시 읽어 간격을 스스로 계산하고 여러분이 적은 값과 대조합니다. 그래서 손으로 어림한 숫자는 통과하지 않습니다.
curl -sG http://127.0.0.1:3000/api/annotations --data-urlencode 'dashboardUID=gfd-annot' \
| jq -c '.[] | {id, time, timeEnd, tags}'
결론은 '배포 때문이다' 로 단정할 필요가 없습니다. 간격이 짧다는 것은 의심할 근거이지 증거가 아닙니다. 다음 사람이 무엇을 먼저 확인해야 하는지까지 적으면 그 주석은 조사 기록이 됩니다.