Create 502 and 504 Yourself and Tell Them Apart by Evidence
한국어 원문으로 표시합니다.
목표
502 와 504 를 직접 만들어 낸 다음, 상태 코드가 아니라 error.log 의 구절로
원인을 확정할 수 있게 됩니다. 특히 같은 504 인데 걸린 타임아웃이 다른 두 경우를
구분하게 됩니다.
왜 중요한가
장애 대응에서 시간을 가장 많이 잡아먹는 것은 틀린 원인을 확신하는 것입니다.
502 를 "백엔드가 죽었다" 로 단정하고 타임아웃을 올리면 아무 일도 일어나지 않습니다.
연결이 거부된 실패에는 기다릴 시간 자체가 없었기 때문입니다.
반대로 504 라고 해서 proxy_read_timeout 을 올리면 되는 것도 아닙니다.
연결 단계에서 걸렸다면 읽기 타임아웃을 30 초로 올려도 요청은 여전히 2 초 만에
끊깁니다. 이 실습의 5 단계가 정확히 그 장면입니다.
그 차이를 만드는 것은 로그 한 줄의 while 뒤 구절 하나뿐입니다.
단계
/root/ngi/upstream.py를 저장하고 백그라운드로 띄웁니다. 9101 번에 앱이 뜨고 9109 번은 연결을 받지 않는 포트가 됩니다. 9999 번에는 아무것도 띄우지 않습니다. 확인: 9101 번의/가 200,/slow?d=2가 2초 뒤 200,/bighdr?n=8000응답에X-Session-Blob헤더가 있어야 합니다./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 이어야 합니다. (브라우저 미리보기로 열어 볼 수 있습니다.)http://127.0.0.1:8088/dead/를 호출하고/root/ngi/case-refused.txt를 만듭니다. 세 줄입니다.status=<받은 상태 코드> phrase=<error.log 에서 원인을 지목하는 구절 그대로> timeout=<실제로 발동한 타임아웃 이름, 없으면 none>http://127.0.0.1:8088/app/slow?d=10을 호출하고 몇 초 만에 끊기는지 재서/root/ngi/case-read.txt를 만듭니다. 네 줄입니다.status=<상태 코드> phrase=<error.log 의 while 로 시작하는 구절 그대로> timeout=<발동한 타임아웃 지시자 이름> elapsed=<끊기기까지 걸린 초, 정수>/hang/블록 안에proxy_read_timeout 30s;를 넣고 reload 한 뒤http://127.0.0.1:8088/hang/를 호출합니다. 읽기에 30 초를 줬는데도 요청이 훨씬 빨리 끊깁니다. 시간을 재서/root/ngi/case-connect.txt를 4 단계와 같은 형식으로 만듭니다. 상태 코드는 4 단계와 같지만timeout은 달라야 합니다.http://127.0.0.1:8088/app/bighdr?n=8000을 호출하면 502 가 납니다. 원인 구절을 확인한 뒤 프록시 버퍼를 키워 200 이 나오게 고칩니다. 지시자 하나만 키우면 nginx 가 뜨지 않으니 emerg 메시지를 읽고 함께 고치세요./root/ngi/case-header.txt를 만듭니다. 네 줄입니다.
고친 뒤에도 3~5 단계의 실패는 그대로 재현돼야 합니다.before=<고치기 전 코드> after=<고친 뒤 코드> phrase=<error.log 에서 원인을 지목하는 구절 그대로> fix=<헤더 크기를 직접 정하는 지시자 이름>/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 단계에서 본 것으로 정하세요./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로 갑니다.
장애 발생기 띄우기
/root/ngi/upstream.py 를 저장하고 백그라운드로 띄웁니다.
9101 번에 앱이 뜨고 9109 번은 연결을 받지 않는 포트가 됩니다.
9999 번에는 아무것도 띄우지 않습니다.
확인: 9101 번의 / 가 200, /slow?d=2 가 2초 뒤 200,
/bighdr?n=8000 응답에 X-Session-Blob 헤더가 있어야 합니다.
업스트림 셋이 필요합니다 — 정상적으로 답하는 앱, 연결을 절대 받지 않는 포트, 그리고 아무도 듣지 않는 포트. 앞의 둘은 파이썬 한 파일로 함께 띄웁니다. 기동 뒤에는 앱이 200 을 주는지, 연결을 받지 않는 포트가 정말로 매달리는지 각각 확인하세요.
증거가 남는 프록시 세우기
/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 이어야 합니다.
(브라우저 미리보기로 열어 볼 수 있습니다.)
포트는 8088 입니다. 로그 두 개가 이 실습의 전부이니 경로와 수준을 정확히 잡으세요 — error_log 를 error 수준으로 두면 뒤 실습의 경고가 통째로 사라집니다. 액세스 로그 포맷에는 업스트림이 쓴 시간, 전체 시간, 업스트림 상태 코드 세 가지가 들어가야 합니다.
죽은 업스트림 — 502
http://127.0.0.1:8088/dead/ 를 호출하고 /root/ngi/case-refused.txt 를 만듭니다.
세 줄입니다.
status=<받은 상태 코드>
phrase=<error.log 에서 원인을 지목하는 구절 그대로>
timeout=<실제로 발동한 타임아웃 이름, 없으면 none>
죽은 포트로 프록시하면 어떤 코드가 나오는지, 그리고 그때 error.log 첫 낱말이 무엇인지 보세요. 여기서 타임아웃 이야기가 나올 자리가 있는지 스스로에게 물어보면 timeout 칸에 무엇을 적을지 정해집니다.
느린 업스트림 — 504, 읽기 타임아웃
http://127.0.0.1:8088/app/slow?d=10 을 호출하고 몇 초 만에 끊기는지 재서
/root/ngi/case-read.txt 를 만듭니다. 네 줄입니다.
status=<상태 코드>
phrase=<error.log 의 while 로 시작하는 구절 그대로>
timeout=<발동한 타임아웃 지시자 이름>
elapsed=<끊기기까지 걸린 초, 정수>
응답까지 10 초 걸리는 경로를 호출하되, 몇 초 만에 끊기는지 시간을 재세요. 그 초 값은 설정 파일 어딘가에 그대로 적혀 있습니다. error.log 의 구절에서 while 뒤를 읽으면 어느 타임아웃인지 확정됩니다.
올린 타임아웃은 발동한 타임아웃이 아니었다
/hang/ 블록 안에 proxy_read_timeout 30s; 를 넣고 reload 한 뒤
http://127.0.0.1:8088/hang/ 를 호출합니다. 읽기에 30 초를 줬는데도
요청이 훨씬 빨리 끊깁니다. 시간을 재서 /root/ngi/case-connect.txt 를
4 단계와 같은 형식으로 만듭니다.
상태 코드는 4 단계와 같지만 timeout 은 달라야 합니다.
먼저 /hang/ 블록 안에 proxy_read_timeout 30s 를 넣고 reload 하세요. 읽기에 30 초를 주겠다고 선언한 겁니다. 그런데도 요청이 몇 초 만에 끊기는지 재 보고, error.log 의 while 뒤 구절이 4 단계와 어떻게 다른지 보세요. 상태 코드와 오류 번호는 같습니다.
서버는 멀쩡한데 특정 사용자만 502
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 단계의 실패는 그대로 재현돼야 합니다.
응답 헤더를 8000 바이트로 부풀리는 경로가 있습니다. 헤더가 커졌을 뿐인데 코드가 어떻게 나오는지 보세요. 고칠 때 버퍼 지시자를 하나만 키우면 nginx 가 아예 뜨지 않습니다 — 그 emerg 메시지가 무엇을 함께 키우라고 하는지 읽으세요.
네 줄짜리 판정표
/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 단계에서 본 것으로 정하세요.
네 가지 실패를 한 표로 모읍니다. fix 칸에는 nginx 설정에서 이 실패를 실제로 푸는 지시자 이름을 적되, nginx 설정으로는 풀 수 없는 것이 하나 있습니다 — 그 줄에는 none 을 적습니다.
숫자가 들어간 장애 보고서
/root/ngi/rca.md 를 작성합니다. ## 현상, ## 증거, ## 원인,
## 조치, ## 재발방지 다섯 개의 h2 제목이 있어야 하고,
4 단계와 5 단계의 elapsed 숫자와 두 타임아웃 지시자 이름이 본문에
그대로 인용돼야 합니다.
보고서의 가치는 숫자에 있습니다. 앞 단계에서 재 둔 초 값을 그대로 인용하세요. 재발방지에는 '주의한다' 대신 어떤 지시자를 어떤 값으로 두고 어떤 로그 문구에 알림을 걸 것인지를 적습니다.