NGINX JavaScript 모듈을 사용하여 Request에 대해 JavaScript의 강력함과 편의성 활용
NGINX JavaScript 모듈(njs)은 NGINX OSS 1.11.10 및 NGINX Plus R12에서 안정적인 모듈로 일반적으로 사용 가능하게 되었습니다.
NGINX JavaScript는 서버 측 사용 사례 및 요청별 처리를 위해 특별히 설계된 NGINX 및 NGINX Plus를 위한 고유한 JavaScript 구현입니다. 정교한 구성 솔루션을 구현하기 위해 JavaScript 코드로 NGINX 구성 구문을 확장합니다.
사용 사례는 특히 NGINX JavaScript 모듈이 HTTP 및 TCP/UDP 프로토콜 모두에 사용 가능하기 때문에 광범위합니다. NGINX JavaScript의 샘플 사용 사례는 다음과 같습니다.
- 일반 NGINX 변수에서 사용할 수 없는 값으로 사용자 정의 로그 형식 생성(예: diagnostic logging)
- 프록시 서버의 응답 수정(response filtering)
- 사용자 정의 인증 체계 구현(예: OAuth 2.0 token introspection)
- 애플리케이션 수준 고정 세션에 대한 TCP/UDP 프로토콜 구문 분석(예: MQTT load balancing)
목차
1. NGINX JavaScript는 Lua가 아닙니다.
2. NGINX JavaScript는 Node.js가 아닙니다.
3. Server-Side 언어로서의 JavaScript
4. NGINX JavaScript 시작하기 – 실제 사례
5. NGINX용 NGINX JavaScript 활성화
1. NGINX JavaScript는 Lua가 아닙니다.
NGINX 커뮤니티는 수년 동안 여러 프로그래밍 확장 방식을 만들었습니다. 글을 쓰는 시점에서 Lua는 이들 중 가장 인기가 있습니다. NGINX용 모듈과 NGINX Plus용으로 사전 구축된 지원되는 third-party dynamic module로 사용할 수 있습니다. Lua 모듈 및 추가 기능 라이브러리는 NGINX core 및 Redis용 드라이버를 비롯한 풍부한 기능 세트와의 긴밀한 통합을 제공합니다.
Lua는 강력한 스크립트 언어입니다. 그러나 채택 측면에서 상당히 틈새 시장으로 남아 있으며 일반적으로 Frontend 개발자 또는 DevOps 엔지니어의 “기술 도구 상자”에서 찾을 수 없습니다.
NGINX JavaScript는 Lua를 대체하려고 하지 않으며 NGINX JavaScript가 비슷한 수준의 기능을 갖기까지는 시간이 걸립니다. NGINX JavaScript의 목표는 널리 사용되는 프로그래밍 언어를 사용하여 가장 광범위한 커뮤니티에 프로그래밍 방식 구성 솔루션을 제공하는 것입니다.
2. NGINX JavaScript는 Node.js가 아닙니다.
NGINX JavaScript는 NGINX 또는 NGINX Plus를 애플리케이션 서버로 전환하는 것을 목표로 하지 않습니다. 간단히 말해서 NGINX JavaScript의 사용 사례는 JavaScript 코드 실행이 클라이언트와 콘텐츠 사이에서 발생하기 때문에 미들웨어와 유사합니다. 기술적으로 말하면 Node.js는 NGINX JavaScript와 NGINX 또는 NGINX Plus(event-driven 아키텍처 및 JavaScript 프로그래밍 언어)의 조합으로 두 가지를 공유하지만 유사점은 거기서 끝입니다.
Node.js는 Google V8 JavaScript 엔진을 사용하는 반면 NGINX JavaScript는 NGINX 및 NGINX Plus용으로 특별히 설계된 ECMAScript 표준의 맞춤형 구현입니다. Node.js는 메모리에 영구 JavaScript 가상 머신(VM)이 있고 메모리 관리를 위해 일상적인 가비지 수집을 수행하는 반면 NGINX JavaScript는 새 JavaScript VM과 각 요청에 필요한 메모리를 초기화하고 요청이 완료되면 메모리를 해제합니다.
3. Server-Side 언어로서의 JavaScript
위에서 언급했듯이 NGINX JavaScript는 JavaScript 언어의 맞춤형 구현입니다. 다른 모든 기존 JavaScript 런타임 엔진은 웹 브라우저 내에서 실행되도록 설계되었습니다. 클라이언트 측 코드 실행의 특성은 시스템 리소스의 가용성에서 가능한 동시 런타임 수에 이르기까지 여러 면에서 Server-Side 코드 실행과 다릅니다.
우리는 Server-Side 코드 실행 요구 사항을 충족하고 NGINX의 요청 처리 아키텍처에 적합하도록 자체 JavaScript 런타임을 구현하기로 결정했습니다. NGINX JavaScript에 대한 설계 원칙은 다음과 같습니다.
런타임 환경은 request과 함께 살고 죽습니다.
NGINX JavaScript 모듈은 빠른 초기화 및 폐기를 위해 설계된 single-thread 바이트코드 실행을 사용합니다. 런타임 환경은 요청별로 초기화됩니다. 복잡한 상태나 초기화할 도우미가 없기 때문에 시작이 매우 빠릅니다. 메모리는 실행 중에 풀에 누적되고 완료 시 풀을 해제하여 해제됩니다. 이 메모리 관리 체계를 사용하면 개별 개체를 추적하고 해제하거나 가비지 수집기를 사용할 필요가 없습니다.
Non-blocking 코드 실행
NGINX 및 NGINX Plus의 event-driven 모델은 개별 NGINX JavaScript 런타임 환경의 실행을 예약합니다. NGINX JavaScript 규칙이 차단 작업(예: 네트워크 데이터 읽기 또는 외부 하위 요청 발행)을 수행하면 NGINX 및 NGINX Plus는 연결된 NGINX JavaScript VM의 실행을 투명하게 일시 중단하고 이벤트가 완료되면 다시 예약합니다. 즉, 단순하고 선형적인 방식으로 규칙을 작성할 수 있으며 NGINX 및 NGINX Plus는 내부 차단 없이 규칙을 예약합니다.
필요한 언어 지원만 구현
JavaScript에 대한 사양은 ECMAScript 표준에 의해 정의됩니다. NGINX JavaScript는 수학 함수를 위한 일부 ECMAScript 6과 함께 ECMAScript 5.1을 따릅니다. 자체 JavaScript 런타임을 구현하면 Server-Side 사용 사례에 대한 언어 지원의 우선 순위를 지정하고 필요하지 않은 것은 무시할 수 있습니다. 현재 지원되는 언어 요소 목록을 유지 관리합니다.
요청 처리 단계(request-processing phases)와의 긴밀한 통합
NGINX 및 NGINX Plus는 별도의 단계에서 요청을 처리합니다. 구성 지시문은 일반적으로 특정 단계에서 작동하며 기본 NGINX 모듈은 종종 특정 단계에서 요청을 검사하거나 수정하는 기능을 활용합니다. NGINX JavaScript는 JavaScript 코드가 실행되는 시기를 제어하기 위해 구성 지시문을 통해 일부 처리 단계를 노출합니다. 구성 구문과의 이러한 통합은 JavaScript 코드의 단순성과 함께 기본 NGINX 모듈의 강력함과 유연성을 약속합니다.
아래 표는 작성 시점에 NGINX JavaScript를 통해 액세스할 수 있는 처리 단계와 이를 제공하는 구성 지시자를 나타냅니다.
처리 단계 | HTTP 모듈 | Stream 모듈 |
Access – 인증 및 액세스 제어 | auth_request 및 js_content | js_access |
Pre-read – 페이로드 읽기/쓰기 | N/A | js_preread |
Filter – 프록시 중 읽기/쓰기 응답 | js_body_filter js_header_filter | js_filter |
Content – 클라이언트에 응답 보내기 | js_content | N/A |
Log / Variables – 요청 시 평가 | js_set | js_set |
4. NGINX JavaScript 시작하기 – 실제 사례
NGINX JavaScript는 NGINX OSS 바이너리로 컴파일하거나 NGINX 또는 NGINX Plus에 동적으로 로드할 수 있는 모듈로 구현됩니다. NGINX 및 NGINX Plus와 함께 NGINX JavaScript를 활성화하기 위한 지침은 이 기사의 끝에 나와 있습니다.
이 예에서는 NGINX 또는 NGINX Plus를 간단한 리버스 프록시로 사용하고 NGINX JavaScript를 사용하여 다음과 같은 특수 형식의 액세스 로그 항목을 구성합니다.
- 클라이언트가 보낸 요청 헤더를 포함합니다.
- Backend에서 반환된 응답 헤더를 포함합니다.
- ELK Stack(현재 Elastic Stack이라고 함), Graylog 및 Splunk와 같은 로그 처리 도구로의 효율적인 수집 및 검색을 위해 키-값 쌍 사용
이 예의 NGINX 구성은 매우 간단합니다.
js_import conf.d/logging.js; # Load JavaScript code from here
js_set $access_log_headers logging.kvAccess; # Fill variable from JS function
log_format kvpairs $access_log_headers; # Define special log format
server {
listen 80;
root /usr/share/nginx/html;
access_log /var/log/nginx/access.log kvpairs;
}
보시다시피 NGINX JavaScript 코드는 구성 구문과 함께 인라인되지 않습니다. 대신 js_import 지시문을 사용하여 모든 JavaScript 코드가 포함된 파일을 지정합니다. js_set 지시문은 새로운 NGINX 변수인 $access_log_headers와 이를 채우는 JavaScript 함수를 정의합니다. log_format 지시문은 $access_log_headers 값으로 각 로그 라인을 작성하는 kvpairs라는 새로운 형식을 정의합니다.
서버 블록은 모든 요청을 https://www.example.com으로 전달하는 간단한 HTTP 리버스 프록시를 정의합니다. access_log 지시문은 모든 요청이 kvpairs 형식으로 기록되도록 지정합니다.
이제 로그 항목을 준비하는 JavaScript 코드를 살펴보겠습니다.
function kvAccess(r) {
var log = `${r.variables.time_iso8601} client=${r.remoteAddress} method=${r.method} uri=${r.uri} status=${r.status}`;
r.rawHeadersIn.forEach(h => log += ` in.${h[0]}=${h[1]}`);
r.rawHeadersOut.forEach(h => log += ` out.${h[0]}=${h[1]}`);
return log;
}
export default { kvAccess }
로그 항목인 kvAccess 함수의 반환 값은 rawheader_logging.conf의 js_set 구성 지시문으로 전달됩니다. NGINX 변수는 요청 시 평가되며 이는 변수 값이 필요할 때 js_set에 의해 정의된 JavaScript 함수가 실행됨을 의미합니다. 이 예에서 $access_log_headers는 log_format 지시문에 사용되므로 로그 시간에 kvAccess()가 실행됩니다. map 또는 rewrite 지시문의 일부로 사용되는 변수(이 예에서는 표시되지 않음)는 이전 처리 단계에서 해당 JavaScript 실행을 트리거합니다.
리버스 프록시를 통해 요청을 전달하고 in. 접두사가 있는 요청 헤더와 out이 있는 응답 헤더를 포함하는 결과 로그 파일 항목을 관찰하여 NGINX JavaScript 로깅 솔루션이 작동하는 것을 볼 수 있습니다. prefix.
$ curl http://127.0.0.1/
$ tail --lines=1 /var/log/nginx/access_headers.log
2021-04-23T10:08:15+00:00 client=172.17.0.1 method=GET uri=/index.html status=200 in.Host=localhost:55081 in.User-Agent=curl/7.64.1 in.Accept=*/* out.Content-Type=text/html out.Content-Length=612 out.ETag=\x22606339ef-264\x22 out.Accept-Ranges=bytes
NGINX JavaScript의 많은 유틸리티는 NGINX 내부에 대한 액세스의 결과입니다. 이 예에서는 요청(r) 객체의 여러 속성을 활용합니다. Stream NGINX JavaScript 모듈(TCP 및 UDP 애플리케이션용)은 고유한 속성 집합이 있는 세션 개체를 사용합니다. HTTP 및 TCP/UDP 모두에 대한 NGINX JavaScript 솔루션의 다른 예는 NGINX JavaScript 모듈의 사용 사례를 참조하세요.
5. NGINX용 NGINX JavaScript 활성화
NGINX JavaScript 모듈은 기본적으로 공식 NGINX Docker 이미지에 포함되어 있습니다. 시스템이 NGINX 오픈소스용으로 사전 구축된 공식 패키지를 사용하도록 구성되어 있고 설치된 버전이 1.9.11 이상인 경우 플랫폼용 사전 구축된 패키지로 NGINX JavaScript를 설치할 수 있습니다.
1. 미리 빌드된 패키지를 설치합니다.
Ubuntu 및 Debian 시스템의 경우:
$ sudo apt-get install nginx-module-njs
RedHat, CentOS 및 Oracle Linux 시스템의 경우:
$ sudo yum install nginx-module-njs
2. nginx.conf 구성 파일의 최상위(“main”) 컨텍스트(http 또는 스트림 컨텍스트가 아님)에 load_module 지시문을 포함하여 모듈을 활성화합니다. 이 예는 HTTP 및 TCP/UDP 트래픽 모두에 대해 NGINX JavaScript 모듈을 로드합니다.
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
3. NGINX를 다시 로드하여 NGINX JavaScript 모듈을 실행 중인 인스턴스에 로드합니다.
$ sudo nginx -s reload