午前3時、引き継ぎ文書のコマンドが間違っていた
한국어 원문으로 표시합니다.
목표
인계받을 운영팀을 위해 지표 → 경보 조건 → 런북 항목 → 실행 가능한 확인 명령의 사슬을 만들고, 채점기가 가짜 서비스를 여러 장애 모드로 띄워 그 사슬이 정확한 항목을 가리키는지 훈련으로 확인한다.
왜 중요한가
인계 문서는 쓴 날에만 맞다. 경보가 어떤 조건에서 울리는지, 울렸을 때 무엇으로 확인하는지가 코드와 따로 적혀 있으면 서비스가 바뀌는 순간 문서가 낡고, 그 사실은 새벽의 당번이 명령을 칠 때 드러난다. 그래서 경보 규칙은 스크립트가 읽는 데이터로 두고, 런북의 확인 줄은 종료 코드로 답하는 명령으로 쓰고, 그 명령이 실제로 도는지 점검기와 훈련으로 되풀이해 확인한다. 조용한 대시보드(지표를 못 긁음)를 정상으로 읽지 않는 것도 같은 사슬의 일부다.
예상 75분이다. 기본 60분 세션이 끝나기 전에 +시간으로 연장하자(최대 180분). 세션이 끝나면 /root/drill 의 파일은 사라지니, 남기고 싶은 코드는 종료 전에 따로 보관한다.
재료
- 인계 메모(경보 표·런북 규칙):
/opt/lab/drill/brief.md - 가짜 서비스:
python3 /opt/lab/drill/fakesvc.py --port 9311 --mode normal(--help로 장애 모드 확인) - 벤더가 남긴 옛 런북:
/opt/lab/drill/vendor-runbook.md - 채점기는 여러분이 띄운 서버를 쓰지 않는다. 빈 포트에 가짜 서비스를 여러 모드·여러 시드로 직접 띄워 여러분의 스크립트를 실행해 본다. 제공된 숫자를 외워 넣으면 통과하지 않는다.
단계
- 가짜 서비스를 정상 모드로 띄우고
/metrics응답을 가공 없이/root/drill/normal.prom에 저장한다. 원문의# TYPE줄에서이름 형식을 한 줄에 하나씩/root/drill/families.txt에 적는다. /root/drill/promtext.py를 쓴다.python3 promtext.py FILE이 샘플마다{"name","labels","value","timestamp"}객체의 JSON 배열을 출력한다. 값은 숫자, 무한대·NaN 은 문자열"+Inf"·"-Inf"·"NaN", 타임스탬프가 없으면 null. 주석·빈 줄은 건너뛰고, 레이블 값 안의 쉼표·중괄호와\\·\"·\n이스케이프를 올바르게 읽는다. 잘못된 샘플 줄이 하나라도 있으면 종료 코드 2.- 인계 메모의 여섯 경보를
/root/drill/alerts.json에 옮긴다.{"alerts": [{"alert","severity","runbook","rule"}]}형식이고 rule.kind 는threshold(metric·op·value),ratio(metric·denominator·op·value),expires_within(metric·seconds),scrape,absent(metrics) 중 하나다. 채점기는 경계값을 포함한 여러 상황에서 여러분의 규칙을 판정해 본다. /root/drill/diagnose.py를 쓴다.python3 diagnose.py --url URL [--rules 경로](기본/root/drill/alerts.json)가URL/metrics를 한 번 긁어 규칙으로 판정하고{"target": URL, "firing": [{"alert","severity","runbook","labels"}]}를 출력한다. labels 는 그 샘플의 레이블이다. 종료 코드는 경보가 없으면 0, 있으면 1. 문턱은 규칙 파일에서 읽는다./root/drill/diagnose.py가 못 긁은 상황을 경보로 올리게 고친다. 연결 실패·HTTP 200 아님·형식 오류·orders_up없음이면OrdersTargetDown(labels{}) 하나만 내고 종료 코드 2. 필수 지표가 빠지면 빠진 지표마다OrdersMetricAbsent(labels{"metric": 이름})를 낸다.- 여섯 경보의 런북 항목을
/root/drill/runbook.md에 쓴다. 항목은## 런북이름제목과- 경보:·- 확인:·- 판단:·- 조치:·- 에스컬레이션:다섯 줄이다. 확인 줄은 백틱으로 감싼 한 줄 명령이고$ORDERS_URL로 서비스를 가리키며,bash -o pipefail -c로 돌렸을 때 정상이면 0, 그 장애면 0 이 아닌 값으로 곧바로 끝나야 한다. /root/drill/check_runbook.py를 쓴다.python3 check_runbook.py 런북.md --url URL이 항목마다 확인 명령을ORDERS_URL환경 변수와 함께bash -o pipefail -c로 5초 제한 안에 돌리고, 런북 순서대로[{"id","check","exit","status","reason"}]를 출력한다. status 는 종료 코드 0 이면ok, 아니면broken. reason 은ok·timeout·not-found(127)·exit N·no-check(확인 줄 없음). 하나라도 broken 이면 종료 코드 1. 벤더 런북에 돌려 고장 난 줄을 찾는다.- 당번 훈련 스크립트
/root/drill/oncall.sh를 쓴다.bash oncall.sh URL이 diagnose.py 로 경보를 얻고, 경보마다 runbook.md 의 항목을 찾아 확인 명령을 돌려{"target": URL, "incidents": [{"alert","severity","runbook","labels","check_exit","confirmed","escalation"}]}를 출력한다. confirmed 는 확인 명령이 0 이 아닌 값으로 끝났을 때(장애 확증) true, escalation 은 런북의 에스컬레이션 줄 그대로. 사건이 있으면 종료 코드 1, 없으면 0.
참고
- 원문 저장:
curl -fsS http://127.0.0.1:9311/metrics -o 파일— 파이프로 가공하면 마지막 줄바꿈이나 주석이 사라질 수 있다. - 서비스를 여러 개 띄워 비교할 때는 포트를 바꾼다(9312, 9313…). 끝난 서비스는
pkill -f 'fakesvc.py --port 9312'처럼 포트까지 지정해 내린다. - 흔한 실수 1: 레이블을 쉼표로 자르기.
note="handoff \"v2\", see RB"에서 깨진다. - 흔한 실수 2: 긁기 실패를 빈 목록으로 삼키기. 그러면 서비스가 죽은 밤에 경보가 0개로 나온다.
- 흔한 실수 3: 확인 명령에서
curl ... | grep -q. 첫 일치에서 grep 이 끝나 curl 이 SIGPIPE(종료 코드 23)를 받고, pipefail 아래에서 정상인데도 실패로 보일 수 있다.grep -c ... >/dev/null이나jq -e처럼 입력을 끝까지 읽는 형태를 쓴다. - 흔한 실수 4: 시간 제한을 bash 에만 걸기. 파이프 뒤의 curl 이 살아남아 점검기가 함께 멈춘다. 새 세션으로 띄우고 프로세스 그룹째 끊는다.
지표 원문을 그대로 긁어 두기
정상 모드 가짜 서비스의 /metrics 응답을 /root/drill/normal.prom 에, TYPE 줄의 이름 형식 목록을 /root/drill/families.txt 에 저장한다.
# TYPE <이름> <형식> 줄은 지표 계열마다 하나뿐입니다. histogram 계열은 TYPE 줄의 이름이 기준이고, 샘플에는 _bucket·_sum·_count 가 붙어 나옵니다. awk 로 첫 두 칸이 # 와 TYPE 인 줄만 고르면 됩니다.
쉼표와 따옴표에 속지 않는 파서
Prometheus 텍스트 노출 형식을 샘플 객체 JSON 배열로 바꾸는 /root/drill/promtext.py 를 쓴다. 잘못된 샘플 줄이 있으면 종료 코드 2.
레이블 부분은 split(',') 로 자를 수 없습니다. 따옴표 안에서는 쉼표와 } 가 값의 일부이고, 역슬래시 다음 글자는 이스케이프입니다. 문자를 하나씩 읽는 작은 상태 기계(따옴표 안인가·역슬래시 직후인가)를 만드세요. 값은 float() 로 읽되 JSON 에 없는 무한대·NaN 은 문자열로 바꿉니다.
인계 메모의 경보 여섯 개를 데이터로
/opt/lab/drill/brief.md 의 경보 표를 /root/drill/alerts.json 규칙으로 옮긴다.
'미만'과 '이하', '초과'와 '이상'은 서로 다른 op 입니다. 인증서 지표는 만료 시각이라 expires_within 을, 큐는 깊이가 아니라 가장 오래된 메시지의 나이를 씁니다. 못 긁음은 scrape, 필수 지표 목록은 absent 의 metrics 에 적습니다.
한 번 긁어 규칙으로 판정하기
지표를 긁어 alerts.json 으로 판정하고 울린 경보를 JSON 으로 내는 /root/drill/diagnose.py 를 쓴다(경보 없음 0 · 있음 1).
샘플을 이름별로 묶어 두면 규칙 종류마다 필요한 것만 꺼낼 수 있습니다. ratio 는 분자와 분모를 같은 레이블끼리 짝지어야 합니다. expires_within 은 '지표값 − 지금 시각' 을 비교합니다. 앞 단계의 promtext.py 를 import 하면 파서를 다시 쓸 필요가 없습니다.
조용한 대시보드를 정상으로 읽지 않기
/root/drill/diagnose.py 가 못 긁음은 OrdersTargetDown·종료 코드 2 로, 빠진 필수 지표는 OrdersMetricAbsent 로 올리게 고친다.
urlopen 은 연결 실패에 URLError, 200 이 아닌 응답에 HTTPError 를 던집니다. 파서의 ParseError 도 못 긁음입니다. 이 셋을 빈 샘플 목록으로 바꾸면 모든 비교가 거짓이 되어 경보가 0개로 나옵니다 — 그 자체가 경보여야 합니다. absent 는 '이 이름의 샘플이 하나도 없는가' 로 판정합니다.
종료 코드로 답하는 런북
여섯 경보의 항목을 /root/drill/runbook.md 에 쓴다. 확인 명령은 정상 0 · 해당 장애 비0 으로 실제로 동작해야 한다.
진단 엔드포인트(/debug/deps·/debug/disk·/debug/tls·/debug/queue)는 JSON 이라 jq -e 가 잘 맞습니다. -e 는 결과가 false·null 이면 종료 코드 1 을 냅니다. curl 에는 -f(4xx·5xx 를 실패로)와 -m(시간 제한)을 붙이세요. target-down 은 200 인 점검 페이지도 잡아야 하므로 응답 안에 orders_up 샘플이 있는지까지 봅니다.
벤더 런북을 돌려 보고 고장 난 줄 찾기
런북의 확인 명령을 실제로 돌려 판정하는 /root/drill/check_runbook.py 를 쓰고, /opt/lab/drill/vendor-runbook.md 에 돌려 본다.
subprocess.run(timeout=…) 은 시간이 지나면 bash 만 죽이고, 파이프 뒤에 남은 curl 이 출력 파이프를 붙잡아 기다림이 끝나지 않을 수 있습니다. Popen(start_new_session=True) 로 띄우고 시간이 지나면 os.killpg 로 그룹째 끊으세요. 종료 코드 127 은 '명령을 찾을 수 없음' 입니다.
경보부터 에스컬레이션까지 당번 훈련
diagnose.py → runbook.md → 확인 명령 → 에스컬레이션을 한 번에 도는 /root/drill/oncall.sh 를 쓴다.
diagnose.py 의 종료 코드 1·2 는 오류가 아니라 판정 결과이니 set -e 로 스크립트를 끊지 마세요. 런북 파싱과 시간 제한 실행은 7단계 check_runbook.py 의 함수를 import 해 다시 쓰면 됩니다. confirmed 는 '확인 명령이 장애를 확증했는가' 라서 종료 코드가 0 이 아닐 때 true 입니다.