nginx 장애 대응 · 502 와 504 를 증거로 가른다 · 실습
502 와 504 를 직접 만들어 놓고 증거로 가르기
목표
502 와 504 를 직접 만들어 낸 다음, 상태 코드가 아니라 error.log 의 구절로
원인을 확정할 수 있게 됩니다. 특히 같은 504 인데 걸린 타임아웃이 다른 두 경우를
구분하게 됩니다.
왜 중요한가
장애 대응에서 시간을 가장 많이 잡아먹는 것은 틀린 원인을 확신하는 것입니다.
502 를 "백엔드가 죽었다" 로 단정하고 타임아웃을 올리면 아무 일도 일어나지 않습니다.
연결이 거부된 실패에는 기다릴 시간 자체가 없었기 때문입니다.
반대로 504 라고 해서 proxy_read_timeout 을 올리면 되는 것도 아닙니다.
연결 단계에서 걸렸다면 읽기 타임아웃을 30 초로 올려도 요청은 여전히 2 초 만에
끊깁니다. 이 실습의 5 단계가 정확히 그 장면입니다.
그 차이를 만드는 것은 로그 한 줄의 while 뒤 구절 하나뿐입니다.
단계
1. /root/ngi/upstream.py 를 저장하고 백그라운드로 띄웁니다.
9101 번에 앱이 뜨고 9109 번은 연결을 받지 않는 포트가 됩니다.
9999 번에는 아무것도 띄우지 않습니다.
확인: 9101 번의 / 가 200, /slow?d=2 가 2초 뒤 200,/bighdr?n=8000 응답에 X-Session-Blob 헤더가 있어야 합니다.
2. /root/ngi/nginx.conf 를 작성하고 nginx 를 기동합니다.
8088 포트로 listen 하고 세 경로를 둡니다 — /app/ 은 9101 번,/dead/ 는 9999 번, /hang/ 은 9109 번으로 프록시합니다.error_log 는 /root/ngi/logs/error.log 에 warn 수준으로,
액세스 로그는 /root/ngi/logs/access.log 에 남기고 포맷에$upstream_response_time, $request_time, $upstream_status 를 모두 넣습니다.proxy_connect_timeout 2s; 와 proxy_read_timeout 3s; 를 설정합니다.
확인: http://127.0.0.1:8088/app/ 가 200 이어야 합니다.
(브라우저 미리보기로 열어 볼 수 있습니다.)
3. http://127.0.0.1:8088/dead/ 를 호출하고 /root/ngi/case-refused.txt 를 만듭니다.
세 줄입니다.
status=<받은 상태 코드> phrase=<error.log 에서 원인을 지목하는 구절 그대로> timeout=<실제로 발동한 타임아웃 이름, 없으면 none>4. http://127.0.0.1:8088/app/slow?d=10 을 호출하고 몇 초 만에 끊기는지 재서/root/ngi/case-read.txt 를 만듭니다. 네 줄입니다.
status=<상태 코드> phrase=<error.log 의 while 로 시작하는 구절 그대로> timeout=<발동한 타임아웃 지시자 이름> elapsed=<끊기기까지 걸린 초, 정수>5. /hang/ 블록 안에 proxy_read_timeout 30s; 를 넣고 reload 한 뒤http://127.0.0.1:8088/hang/ 를 호출합니다. 읽기에 30 초를 줬는데도
요청이 훨씬 빨리 끊깁니다. 시간을 재서 /root/ngi/case-connect.txt 를
4 단계와 같은 형식으로 만듭니다.
상태 코드는 4 단계와 같지만 timeout 은 달라야 합니다.
6. http://127.0.0.1:8088/app/bighdr?n=8000 을 호출하면 502 가 납니다.
원인 구절을 확인한 뒤 프록시 버퍼를 키워 200 이 나오게 고칩니다.
지시자 하나만 키우면 nginx 가 뜨지 않으니 emerg 메시지를 읽고 함께 고치세요./root/ngi/case-header.txt 를 만듭니다. 네 줄입니다.
before=<고치기 전 코드> after=<고친 뒤 코드> phrase=<error.log 에서 원인을 지목하는 구절 그대로> fix=<헤더 크기를 직접 정하는 지시자 이름>고친 뒤에도 3~5 단계의 실패는 그대로 재현돼야 합니다.
7. /root/ngi/verdict.csv 를 만듭니다. 첫 줄은 case,status,fix 이고
네 줄이 이어집니다. case 는 refused, read-timeout, connect-timeout,big-header 이고 fix 는 none, proxy_connect_timeout,proxy_read_timeout, proxy_buffer_size 중 하나입니다.
어느 것이 어느 것과 짝인지는 3~6 단계에서 본 것으로 정하세요.
8. /root/ngi/rca.md 를 작성합니다. ## 현상, ## 증거, ## 원인,## 조치, ## 재발방지 다섯 개의 h2 제목이 있어야 하고,
4 단계와 5 단계의 elapsed 숫자와 두 타임아웃 지시자 이름이 본문에
그대로 인용돼야 합니다.
참고
- 기동:
nginx -c /root/ngi/nginx.conf -p /root/ngi - 문법 검사:
nginx -t -c /root/ngi/nginx.conf -p /root/ngi - 재적용:
nginx -s reload -c /root/ngi/nginx.conf -p /root/ngi - 시간 재기:
curl -s -o /dev/null -w '%{http_code} %{time_total}\n' <URL> - 업스트림 띄우기:
cd /root/ngi && nohup python3 upstream.py > up.err 2>&1 & - 흔한 실수 1:
error_log를error수준으로 두는 것. - 흔한 실수 2: 502 를 보고 타임아웃부터 올리는 것.
- 흔한 실수 3:
proxy_pass뒤 슬래시를 빼먹어 업스트림 경로가/app/slow로
이 코스의 경고 줄이 전부 사라집니다.
연결이 거부된 실패에는 기다릴 시간 자체가 없었습니다.
가는 것. 위 예시처럼 주소를 뿌리 슬래시로 끝내야 /slow 로 갑니다.
단계 8개
- 장애 발생기 띄우기
- 증거가 남는 프록시 세우기
- 죽은 업스트림 — 502
- 느린 업스트림 — 504, 읽기 타임아웃
- 올린 타임아웃은 발동한 타임아웃이 아니었다
- 서버는 멀쩡한데 특정 사용자만 502
- 네 줄짜리 판정표
- 숫자가 들어간 장애 보고서