실패를 알림으로 전달
95분 안팎
학습 목표
점검 실패를 stderr·비정상 종료 코드·실습용 로컬 알림 수신기에 전달하고 전송 실패도 기록합니다.
개념
화면에 빨간 글자가 보이는 것으로 끝나지 않습니다
예약 점검의 stdout을 사람이 보고 있지 않을 때도 실패를 담당자에게 전달해야 합니다. 하지만 알림 연결이 실패했다고 점검이 정상이었던 것으로 바꾸면 안 됩니다. 이번 레슨은 판정 결과를 표준 오류와 종료 코드로 호출자에게 전달하고 로컬 수신기에 실패 요약을 보내며 전달 결과까지 남깁니다. 알림 전송 자체는 점검 판정과 다른 결과이므로 두 상태를 한 개 성공 표시로 합치지 않습니다.
출력 통로마다 소비자가 다릅니다
stdout은 status와 reasons로 구성된 JSON만 출력해 다른 프로그램이 읽게 합니다. stderr에는 CHECK_FAIL, CHECK_UNKNOWN, ALERT_DELIVERY_FAILED 같은 고정 코드를 출력합니다. 호출자는 종료 코드로 성공 여부를 확인합니다. 사람에게 보이는 메시지가 있어도 마지막 종료가 0이면 자동화는 정상으로 볼 수 있습니다. 반대로 stdout을 모두 오류 로그로 쓰면 다음 파서가 데이터를 읽지 못하므로 두 통로의 목적을 분리합니다.
종료 코드 표는 팀과의 약속입니다
이 실습은 정상 0, 확정 점검 실패 1, 입력 또는 관측 불명 2, 알림 전달 실패 3을 사용합니다. 증거 파일을 쓰지 못하면 4이고 이미 점검 중이면 75로 생략을 알립니다. 이 숫자가 다른 도구에도 동일하다는 가정은 하지 않습니다. 호출자는 실습 계약을 읽고 대응합니다. 전달 실패의 코드 3이 반환되어도 checks.jsonl에는 원래 FAIL 또는 UNKNOWN과 원인 배열이 남아 점검 문제를 잃지 않습니다.
성공할 때는 수신기를 호출하지 않습니다
모든 정상 실행에 HTTP 요청을 보내면 실제 장애를 찾기 어렵고 수신기의 자원도 낭비합니다. deliver는 status=OK이면 바로 NOT_NEEDED를 반환합니다. FAIL과 UNKNOWN만 전송합니다. 이 구현은 실패가 계속되면 매번 요약을 보내며 장기 중복 억제나 복구 알림 정책은 구현하지 않습니다. 실제 운영에 사용할 때는 누가 언제 응답해야 하는지와 같은 실패를 묶는 기간을 추가로 정해야 합니다.
알림 목적지는 실습 범위로 고정합니다
수신 주소는 http://127.0.0.1:18081/alerts입니다. 사용자 입력에서 목적지를 받아 임의 사이트로 보내지 않습니다. 요청은 target, status, reasons 세 필드만 담고 환경 변수·응답 본문·원본 JSON·예외 문자열은 포함하지 않습니다. 주소가 고정되어도 수신기가 올바른 것인지 확인은 필요합니다. 포트 충돌이 있으면 기존 프로세스를 종료하지 않고 소유자와 실습 경로를 먼저 조사합니다.
수신 성공의 기준을 명시합니다
이 수신기 계약은 POST /alerts 요청을 읽고 허용 필드를 JSONL에 기록한 뒤 HTTP 204를 반환합니다. 송신기는 204일 때 SENT, 다른 응답 또는 접속 오류일 때 DELIVERY_FAILED로 처리합니다. HTTP 응답을 받았다는 사실을 담당자가 읽었다는 뜻으로 해석하지 않습니다. 수신 증거 received.jsonl과 송신 측 checks.jsonl을 대조해야 이 작은 실습에서 전송과 저장을 확인할 수 있습니다.
제한 시간 뒤에는 전달 실패를 남깁니다
urllib.request.urlopen에는 2초 상한을 지정합니다. 접속 거부, 읽기 오류, HTTP 오류 등 예상 전송 실패는 OSError 또는 ValueError 처리로 DELIVERY_FAILED를 반환합니다. 무한 재전송을 넣지 않아 점검 전체가 알림 서버에 매달리지 않게 합니다. 담당자는 원래 대상 장애와 수신기 장애를 각각 조사합니다. 타임아웃 숫자를 늘려 증상을 숨기기보다 요청이 어느 단계에서 막혔는지 관측해야 합니다.
기록은 판정과 전달을 같은 행에 묶습니다
health.py는 UTC 시각 at, 고정 target, manual 또는 timer origin, status, reasons, alert를 JSONL 한 행으로 추가합니다. 한 행은 한 번 끝낸 점검입니다. alert가 SENT인지 DELIVERY_FAILED인지 읽어 대상 실패와 전달 실패를 구분합니다. 같은 파일 잠금 아래 점검과 기록을 수행하므로 두 실행이 결과를 섞어 쓰지 않습니다. 이 기록은 전원 장애에 대한 지속성이나 위변조 방지 저장소를 구현한 것은 아닙니다.
로그 기록 실패는 또 하나의 실패입니다
evidence 디렉터리가 일반 파일로 막혀 있거나 쓸 권한이 없으면 EVIDENCE_WRITE_FAILED와 종료 4를 남깁니다. 알림이 먼저 전달된 뒤 기록에 실패할 수도 있으므로 증거 파일이 없다는 이유로 알림도 없었다고 단정하지 않습니다. stderr와 수신 기록을 추가 확인합니다. root로 실행해 오류를 감추지 않고 bcops의 쓰기 권한과 남은 저장 공간을 확인하는 것이 일관된 실행 조건을 유지하는 방법입니다.
민감한 원문을 예외 해설로 내보내지 않습니다
접속 오류 객체에는 URL이나 요청 관련 값이 섞일 수 있고 손상 입력 자체에도 비밀 문자열이 포함될 수 있습니다. 이번 구현은 고정 코드와 허용 원인만 출력합니다. 테스트는 TOKEN=TEST-ONLY 같은 가짜 값을 예외와 입력에 넣어 로그에 남지 않는지 확인합니다. 실제 비밀을 시험 데이터로 넣는 검사는 하지 않습니다. 허용 목록은 알려진 필드 밖의 정보를 내보내지 않기 위한 경계입니다.
자연 종료하는 수신기로 전달을 관찰합니다
receiver.py는 루프백에만 바인딩하고 15초 동안 요청을 처리한 뒤 종료합니다. 요청 크기를 제한하고 연결 읽기에 상한을 둡니다. VM의 새 전용 경로에서 umask 077로 실행해 수신 기록 접근을 제한합니다. 한 터미널에서 수신기를 실행한 직후 다른 터미널에서 failed.json을 점검합니다. 수신기 종료 뒤 같은 점검을 다시 실행하면 원래 FAIL은 같지만 alert와 반환 코드가 달라져야 합니다.
대역 테스트와 실제 HTTP 확인의 경계
로컬 네 검사는 전송 함수를 대역으로 교체하여 정상 시 미호출, 요청 필드·상한, 접속 실패, 점검과 알림 결과 분리를 확인합니다. demo.py alerts도 전달 함수의 고정 결과를 사용합니다. 이 출력은 네트워크가 실제로 연결된 증거가 아닙니다. VM에서는 15초 수신기를 사용해 received.jsonl의 저장과 HTTP 204를 따로 확인합니다. 실행하지 않은 수신 단계에는 예상 결과를 실제 출력처럼 적지 않습니다.
테스트의 오답에서 오류 처리 분기를 찾습니다
starter는 OSError를 만났을 때 SENT를 반환하도록 잘못 작성되어 있습니다. test_refused_and_http_failure에서 SENT와 DELIVERY_FAILED가 다르다는 실패를 읽고 except 분기를 고칩니다. 요청을 받지 못했는데 완료했다고 기록하는 오류입니다. 수신 실패를 대상 OK로 바꾸거나 예외를 무시하지 않습니다. 함수는 전달 결과만 반환하고 원래 판정과 최종 종료 코드는 health.py가 연결하도록 유지합니다.
담당자에게 넘기는 실패 정보
최종 제출에는 같은 FAIL 판정에 대해 전달 성공·전달 실패의 두 JSONL 행과 종료 코드, 실제 수신 기록 여부를 담습니다. 원인 코드로 대상 서비스·HTTP·저장 지표를 찾아가고 ALERT_DELIVERY_FAILED면 수신기와 포트 상태도 확인하도록 대응 담당자를 씁니다. 예외 처리 문법은 더 읽기에서 보강합니다. 이 레슨의 완료는 실패를 없앤 것이 아니라 점검 결과와 전달 경로의 실패를 서로 잃지 않고 알렸다는 증거입니다.
따라하기
전달 성공과 실패를 같은 점검에 연결
solution ZIP의 전송 대역으로 두 결과를 재현합니다. 실제 HTTP 송신은 아닙니다.
python3 -B demo.py alerts실행 결과
STATUS FAIL ALERT SENT EXIT 1 STATUS FAIL ALERT DELIVERY_FAILED EXIT 3
실패 처리 테스트 완성
starter except 분기를 고치고 네 테스트의 OK를 stderr에서 확인합니다.
bash check.shVM에서 유한 수명 수신기 실행
VM 새 디렉터리의 터미널 A에서 실행합니다. 15초 뒤 자연 종료하며 받은 요약은 received.jsonl에 기록합니다.
umask 077
python3 -B receiver.py실제 전달 및 수신 종료 후 재확인
터미널 B에서 수신기가 켜진 동안 실행합니다. 종료 뒤 다시 실행해 alert와 종료 코드를 비교합니다. JSONL의 FAIL 원인이 유지되고 수신 파일에 허용 필드만 있는지 확인합니다.
bash run-health.sh --snapshot failed.json
rc=$?
printf "EXIT=%s\n" "$rc"
cat evidence/checks.jsonl확인 문제
실습
notify.py except 분기의 전달 실패 결과를 구현합니다. 네 전송 대역 검사를 통과하고 README의 15초 자연 종료 수신기로 실제 수신·전달 실패를 구분해 기록합니다.
실행 명령
bash check.sh
기대 결과
4 tests, OK, 종료 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 점검 스크립트가 실패를 알리는 방식을 설명합니다.