NGINX Reload는 어떻게 작동하나요? NGINX가 Hot-Reloading되지 않는 이유는 무엇인가요?
Wei Liu
September 30, 2022
최근 Reddit에서 "왜 NGINX는 핫 리로딩을 지원하지 않나요?"라는 게시물을 보았습니다. 이상하게도, 세계에서 가장 큰 웹 서버인 NGINX가 핫 리로딩을 지원하지 않는다니요? 이는 우리가 모두 nginx -s reload
를 잘못 사용하고 있다는 뜻일까요? 이 질문을 가지고 NGINX 리로드가 어떻게 작동하는지 살펴보겠습니다.
NGINX란 무엇인가
NGINX는 C 언어로 개발된 크로스 플랫폼 오픈 소스 웹 서버입니다. 통계에 따르면, 트래픽이 가장 많은 상위 1000개 웹사이트 중 40% 이상이 NGINX를 사용하여 엄청난 요청을 처리하고 있습니다.
NGINX가 인기 있는 이유
NGINX가 다른 웹 서버를 제치고 높은 사용률을 유지하는 이유는 무엇일까요?
주된 이유는 NGINX가 높은 동시성 문제를 처리하기 위해 설계되었기 때문입니다. 따라서 NGINX는 엄청난 동시 요청을 처리하면서도 안정적이고 효율적인 서비스를 제공할 수 있습니다. 또한, 같은 시대의 경쟁자들인 Apache와 Tomcat에 비해 NGINX는 고급 이벤트 기반 아키텍처, 네트워크 IO를 처리하는 완전 비동기 메커니즘, 그리고 매우 효율적인 메모리 관리 시스템과 같은 탁월한 설계를 가지고 있습니다. 이러한 뛰어난 설계 덕분에 NGINX는 서버의 하드웨어 리소스를 완전히 활용할 수 있으며, NGINX는 웹 서버의 동의어가 되었습니다.
이 외에도 다음과 같은 이유들이 있습니다:
- 고도로 모듈화된 설계로 인해 NGINX는 풍부한 공식 및 확장된 서드파티 모듈을 보유하고 있습니다.
- 가장 자유로운 BSD 라이선스로 인해 개발자들이 NGINX에 기여하려는 의지를 갖게 됩니다.
- 핫 리로딩 지원으로 인해 NGINX는 24/7 서비스를 제공할 수 있습니다.
이 중에서 핫 리로딩이 오늘의 주요 주제입니다.
핫 리로딩이 하는 일
우리가 기대하는 핫 리로딩은 무엇일까요? 첫째, 클라이언트 사용자는 서버가 리로드되고 있다는 것을 알아차리지 못해야 합니다. 둘째, 서버 또는 업스트림 서비스는 다운타임 없이 동적 로딩을 달성하고 모든 사용자 요청을 성공적으로 처리해야 합니다.
어떤 상황에서 핫 리로딩이 필요할까요? 클라우드 네이티브 시대에 마이크로서비스가 매우 인기를 끌면서, 서버 측 수정이 점점 더 빈번해지고 있습니다. 이러한 수정은 도메인의 온라인/오프라인 리버스 프록시, 업스트림 주소 변경, IP 허용 목록/차단 목록 변경 등과 관련이 있으며, 이는 핫 리로딩과 관련이 있습니다.
그렇다면 NGINX는 어떻게 핫 리로딩을 달성할까요?
NGINX 핫 리로딩의 원리
핫 리로드 명령어 nginx -s reload
를 실행하면, 이는 NGINX의 마스터 프로세스에 HUP 신호를 보냅니다. 마스터 프로세스가 HUP 신호를 받으면, 순차적으로 리스닝 포트를 열고 새로운 워커 프로세스를 시작합니다. 따라서 두 개의 워커 프로세스(이전 & 새)가 동시에 존재하게 됩니다. 새 워커 프로세스가 진입한 후, 마스터 프로세스는 이전 워커 프로세스에 QUIT 신호를 보내 정상적으로 종료합니다. 이전 워커 프로세스가 QUIT 신호를 받으면, 먼저 리스닝 핸들러를 닫습니다. 이제 모든 새로운 연결은 새 워커 프로세스로만 들어가며, 서버는 모든 남은 연결을 처리한 후 이전 프로세스를 종료합니다.
이론적으로 NGINX의 핫 리로딩이 우리의 요구 사항을 완벽하게 충족할 수 있을까요? 불행히도, 그렇지 않습니다. 그렇다면 NGINX의 핫 리로딩에는 어떤 단점이 있을까요?
NGINX 리로드로 인한 다운타임
-
너무 빈번한 핫 리로딩은 연결을 불안정하게 만들고 비즈니스 데이터를 손실시킬 수 있습니다.
NGINX가 리로드 명령을 실행할 때, 이전 워커 프로세스는 기존 연결을 계속 처리하고 모든 남은 요청을 처리한 후 자동으로 연결을 끊습니다. 그러나 클라이언트가 모든 요청을 처리하지 못했다면, 남은 요청의 비즈니스 데이터를 영원히 잃게 됩니다. 물론, 이는 클라이언트 사용자의 주의를 끌게 됩니다.
-
어떤 경우에는 이전 워커 프로세스의 재활용 시간이 너무 길어져 정상적인 비즈니스에 영향을 미칠 수 있습니다.
예를 들어, WebSocket 프로토콜을 프록시할 때, NGINX는 헤더 프레임을 파싱하지 않기 때문에 요청이 처리되었는지 알 수 없습니다. 따라서 워커 프로세스가 마스터 프로세스로부터 종료 명령을 받더라도, 이러한 연결이 예외를 발생시키거나 시간 초과되거나 연결이 끊길 때까지 종료할 수 없습니다.
또 다른 예로, NGINX가 TCP 및 UDP의 리버스 프록시로 작동할 때, 요청이 최종적으로 종료되기 전에 얼마나 자주 요청되는지 알 수 없습니다.
따라서 이전 워커 프로세스는 특히 라이브 스트리밍, 미디어, 음성 인식과 같은 산업에서 종종 오랜 시간이 걸립니다. 때로는 이전 워커 프로세스의 재활용 시간이 30분 이상 걸리기도 합니다. 한편, 사용자가 서버를 자주 리로드하면 종료 중인 프로세스가 많이 생성되고 결국 NGINX OOM을 초래하여 비즈니스에 심각한 영향을 미칠 수 있습니다.
# 항상 이전 워커 프로세스에 존재:
nobody 6246 6241 0 10:51 ? 00:00:00 nginx: worker process
nobody 6247 6241 0 10:51 ? 00:00:00 nginx: worker process
nobody 6247 6241 0 10:51 ? 00:00:00 nginx: worker process
nobody 6248 6241 0 10:51 ? 00:00:00 nginx: worker process
nobody 6249 6241 0 10:51 ? 00:00:00 nginx: worker process
nobody 7995 10419 0 10:30 ? 00:20:37 nginx: worker process is shutting down <= 여기
nobody 7995 10419 0 10:30 ? 00:20:37 nginx: worker process is shutting down
nobody 7996 10419 0 10:30 ? 00:20:37 nginx: worker process is shutting down
요약하자면, nginx -s reload
를 실행하여 핫 리로딩을 달성할 수 있었고, 이는 과거에는 충분했습니다. 그러나 마이크로서비스와 클라우드 네이티브의 급속한 발전으로 인해 이 솔루션은 더 이상 사용자의 요구 사항을 충족시키지 못합니다.
비즈니스 업데이트 빈도가 주간 또는 일간이라면, 이 NGINX 리로딩은 여전히 요구 사항을 충족시킬 수 있습니다. 그러나 업데이트 빈도가 시간당 또는 분당으로 바뀐다면 어떻게 될까요? 예를 들어, 100대의 NGINX 서버가 있고 한 시간에 한 번씩 리로드한다면, 하루에 2400번 리로드해야 합니다; 서버가 분당 리로드한다면, 하루에 8,640,000번 리로드해야 하며, 이는 받아들일 수 없습니다.
우리는 프로세스 전환 없이도 즉각적인 콘텐츠 업데이트를 달성할 수 있는 솔루션이 필요합니다.
메모리에서 즉시 적용되는 핫 리로딩
Apache APISIX가 탄생했을 때, 그것은 NGINX 핫 리로딩 문제를 해결하기 위해 설계되었습니다. APISIX는 NGINX와 Lua 기술 스택을 기반으로 개발되었으며, etcd를 핵심 구성 센터로 사용하는 클라우드 네이티브, 고성능, 완전 동적 마이크로서비스 API 게이트웨이입니다. 서버를 재부팅하지 않고도 새로운 서버 구성을 리로드할 필요가 없으며, 이는 업스트림 서비스, 라우트 또는 플러그인 변경이 서버 재부팅을 필요로 하지 않음을 의미합니다. 하지만 APISIX가 NGINX 기술 스택을 기반으로 개발되었다는 사실을 고려할 때, 어떻게 NGINX의 한계를 극복하고 완벽한 핫 리로딩을 달성할 수 있을까요?
먼저, Apache APISIX의 소프트웨어 아키텍처를 살펴보겠습니다:
APISIX는 모든 구성을 APISIX 코어와 플러그인 런타임에 넣어 동적 할당을 사용할 수 있게 함으로써 완벽한 핫 리로딩을 달성할 수 있습니다. 예를 들어, NGINX는 구성 파일 내부에 매개변수를 구성해야 하며, 각 수정은 리로드 후에만 적용됩니다. 동적으로 라우트를 구성하기 위해 Apache APISIX는 하나의 특정 서버와 하나의 위치만 구성합니다. 이 위치를 주요 진입점으로 사용해야 하며, 모든 요청은 먼저 이를 통과한 후 APISIX 코어가 동적으로 특정 업스트림을 할당합니다. Apache APISIX의 라우트 모듈은 서버가 실행 중일 때 라우트를 추가/제거, 수정, 삭제할 수 있습니다. 즉, 동적 리로딩을 달성할 수 있습니다. 이러한 변경 사항은 사용자의 주의를 끌지 않고 정상적인 비즈니스에 영향을 미치지 않습니다.
이제 클래식한 시나리오를 소개하겠습니다; 예를 들어, 새로운 도메인에 대한 리버스 프록시를 추가하려면 APISIX에서 업스트림을 생성하고 새로운 라우트를 추가하기만 하면 됩니다. 이 과정에서 NGINX는 재부팅할 필요가 없습니다. 플러그인 시스템의 또 다른 예로, APISIX는 IP-restriction 플러그인을 사용하여 IP 허용 목록/차단 목록 기능을 달성할 수 있습니다. 이러한 기능 업데이트는 모두 동적이며 서버 재부팅이 필요하지 않습니다. etcd 덕분에 구성 전략은 애드온을 사용하여 즉시 업데이트할 수 있으며, 모든 구성은 즉시 적용되어 최고의 사용자 경험을 제공할 수 있습니다.
결론
NGINX 핫 리로딩은 어떤 경우에는 이전과 새로운 워커 프로세스가 동시에 존재하게 되어 추가적인 리소스 낭비를 초래합니다. 또한, 너무 빈번한 핫 리로딩은 비즈니스 데이터의 완전한 손실 가능성을 초래할 수 있습니다. 클라우드 네이티브와 마이크로서비스의 배경에서 서비스 업데이트가 점점 더 빈번해지고, API 관리 전략도 다양해지면서 핫 리로딩에 대한 새로운 요구 사항이 생겼습니다.
NGINX의 핫 리로딩은 더 이상 비즈니스 요구 사항을 충족시키지 못합니다. 이제는 클라우드 네이티브 시대에 더 발전된 핫 리로딩 전략을 가진 Apache APISIX로 전환할 때입니다. 또한, APISIX로 전환한 후 사용자는 API 서비스에 대한 동적이고 통일된 관리를 할 수 있으며, 이는 관리 효율성을 크게 향상시킬 수 있습니다.