LabHub

nginx 장애 대응 · 앱 잘못이 아닌 실패 · 실습

앱 잘못이 아닌 실패 — 버퍼·본문 크기·커넥션 재사용

LabHub 에서 이어서 보기

목표

상태 코드가 200 인데 사용자는 느리다고 하는 장애를 nginx 의 버퍼·본문 크기·
커넥션 설정에서 찾아낼 수 있게 됩니다. 그리고 각 설정이 무엇을 대가로
치르는지 숫자로 말할 수 있게 됩니다.

왜 중요한가

5xx 는 대시보드가 알려 줍니다. 정말 오래 끄는 것은 **아무도 잘못한 사람이
없어 보이는 장애**입니다. 개발팀은 "우리는 0.05 초에 응답했다" 고 하고
인프라팀은 "서버는 한가하다" 고 하는데 사용자는 7 초를 기다립니다.
셋 다 사실이고, 빠진 조각은 nginx 안에 있습니다.
응답이 디스크로 샜거나, 요청 본문이 임시 파일로 떨어졌거나, 매 요청마다
새 TCP 커넥션이 열리고 있습니다. 이 셋은 전부 상태 코드에 나타나지 않고,
[warn] 수준의 로그 한 줄에만 흔적을 남깁니다.
그래서 error_logwarn 으로 열어 두는 것이 이 장애를 볼 수 있는
유일한 방법입니다.

단계

1. /root/ngb/upstream.py 를 저장해 띄우고 /root/ngb/nginx.conf 를 작성해
nginx 를 기동합니다. 8088 포트로 listen 하고 /app/upstream app
그룹(9101 번)으로 프록시합니다. error_log
/root/ngb/logs/error.logwarn 수준, 액세스 로그는
/root/ngb/logs/access.log 에 남기고 포맷에 $upstream_response_time,
$request_time, $upstream_status 를 모두 넣습니다.
client_max_body_size아직 넣지 않습니다.
확인: http://127.0.0.1:8088/app/ 가 200 이어야 합니다.
(브라우저 미리보기로 열어 볼 수 있습니다.)
2. 두 가지 느린 요청을 각각 한 번씩 보냅니다.

   level=<대괄호 안 수준>   path=<로그에 적힌 임시 파일 경로 그대로>   status=<같은 요청의 액세스 로그 상태 코드>   tradeoff=<disk-spill 또는 upstream-held>

4. /nobuf/ 경로를 추가합니다. 같은 upstream app 으로 프록시하되
그 블록 안에 proxy_buffering off; 를 넣습니다. reload 한 뒤
http://127.0.0.1:8088/nobuf/big?n=8000000 을 같은 --limit-rate 1000k
받고 /root/ngb/case-nobuf.txt 를 만듭니다. 네 줄입니다.

   upstream_time=<us 칸이 아니라 ut 칸 값 그대로>   request_time=<rt 칸 값 그대로>   warn=<none 또는 spilled>   tradeoff=<disk-spill 또는 upstream-held>

5. 3MB 본문을 http://127.0.0.1:8088/app/toobig 로 POST 합니다.
상태 코드, 액세스 로그의 us 칸, 그리고 /root/ngb/upstream.log
그 요청이 몇 건 남았는지를 확인하고 /root/ngb/case-413.txt 를 만듭니다.
네 줄입니다.

   status=<상태 코드>   upstream_status=<액세스 로그 us 칸 값>   app_saw=<업스트림 로그에 남은 건수>   fix=<이 실패를 푸는 지시자 이름>

그런 다음 그 지시자를 10m 으로 설정하고 reload 해서
http://127.0.0.1:8088/app/upload 로 같은 3MB 를 POST 하면
200 이 나오게 만듭니다.
6. 500KB 본문을 http://127.0.0.1:8088/app/spill 로 POST 하면
error.log 에 임시 파일 경고가 하나 더 나옵니다.
/root/ngb/case-bodyspill.txt 를 만듭니다. 세 줄입니다.

   level=<대괄호 안 수준>   path=<로그에 적힌 임시 파일 경로 그대로>   fix=<본문을 메모리에 담는 크기를 정하는 지시자 이름>

그런 다음 그 지시자를 1m 으로 설정하고 reload 한 뒤
http://127.0.0.1:8088/app/nospill 로 같은 500KB 를 POST 해서
이번에는 경고가 나오지 않는 것을 확인합니다.
7. 커넥션 재사용을 실측합니다. 먼저 지금 상태에서
http://127.0.0.1:8088/app/ 를 10 회 호출하고 upstream.log
conn 번호가 몇 종류인지 셉니다. 그다음 upstream app 블록에
keepalive 16; 을, /app/ 블록에 proxy_http_version 1.1;
proxy_set_header Connection ""; 를 넣고 reload 한 뒤 다시 10 회를
호출해 같은 방식으로 셉니다. /root/ngb/keepalive.txt 를 만듭니다.

   before=<설정 전 커넥션 수>   after=<설정 뒤 커넥션 수>

8. /root/ngb/runbook.md 를 작성합니다. ## 증상별 첫 확인, ## 버퍼링,
## 본문 크기, ## 커넥션 재사용 네 개의 h2 제목이 있어야 하고,
proxy_buffering, client_max_body_size, client_body_buffer_size,
keepalive, upstream_response_time 다섯 낱말이 모두 나와야 하며,
2 단계에서 잰 두 시간 값이 숫자로 인용돼야 합니다.

참고

단계 8개

  1. 측정할 수 있는 프록시 세우기
  2. 앱이 느린가 전송이 느린가
  3. 디스크로 새는 응답
  4. 버퍼링을 끄면 무엇이 바뀌는가
  5. 애플리케이션이 영영 못 보는 413
  6. 요청 본문도 디스크로 샌다
  7. 세 줄이 다 있어야 커넥션이 재사용된다
  8. 운영 판단 기준서