NGINX JavaScript 코드 맛보기

NGINX JavaScript 모듈의 주요 이점 중 하나는 NGINX 구성 변수를 읽고 설정할 수 있는 능력입니다. 이 변수들은 환경의 요구에 맞게 사용자 정의 라우팅 결정을 만드는 데 사용될 수 있습니다. 이는 JavaScript 의 강력한 기능을 사용하여 요청 처리에 직접적으로 영향을 미치는 복잡한 기능을 구현할 수 있다는 것을 의미합니다.

이 포스트는 NGINX 오픈 소스와 NGINX Plus에 모두 적용되며 NGINX 만 표기됐다면 둘 다를 의미합니다.

목차

1. 사용 사례 – 새 애플리케이션 서버로 전환
2. HTTP 애플리케이션을 위한 NGINX 구성
3. HTTP 애플리케이션을 위한 NGINX JavaScript 코드
4. TCP/UDP 애플리케이션을 위한 NGINX 구성
5. 요약

1. 사용 사례 – 새 애플리케이션 서버로 전환

이 포스트에서는 NGINX JavaScript 모듈을 사용하여 새로운 애플리케이션 서버로의 원활한 전환을 구현하는 방법을 보여줍니다. 모든 것이 동시에 전환되는 방식인 극단적인 “big-bang 접근법”이 아닌, 모든 클라이언트가 점진적으로 새로운 애플리케이션 서버로 전환되는 시간 창을 정의합니다. 이렇게 하면 우리는 새로운 애플리케이션 서버로의 트래픽을 점진적으로 – 그리고 자동으로 – 증가시킬 수 있습니다.

우리는 이 예시에서 5시부터 7시까지 점진적 전환이 이루어지기를 원하는 2시간 창을 정의하고 있습니다. 첫 12분 후에는 클라이언트의 10%가 새로운 애플리케이션 서버로 전달되었을 것으로 예상되며, 24분 후에는 클라이언트의 20%가 전달되는 것으로 예상합니다. 다음 그래프는 전환을 나타냅니다.

2시간 동안 이전 서버에서 새 서버로 클라이언트를 원하는 대로 전환 javascript

“점진적 전환” 구성의 중요한 요구 사항 중 하나는 전환된 클라이언트가 원래의 서버로 되돌아가지 않는 것입니다. 한 번 클라이언트가 새로운 애플리케이션 서버로 전달되면, 전환 창의 나머지 기간 동안(물론 그 이후에도) 계속해서 해당 서버로 전달됩니다.

아래에 전체 구성을 설명하겠지만 간단히 말하자면, NGINX가 전환 중인 애플리케이션과 일치하는 새로운 요청을 처리할 때 다음 규칙을 따릅니다:

  • 만약 전환 기간이 시작되지 않았다면, 요청을 이전 애플리케이션 서버로 전달합니다.
  • 만약 전환 기간이 지났다면, 요청을 새로운 애플리케이션 서버로 전달합니다.
  • 만약 전환이 진행 중이라면
    • 전환 창에서 현재(시간) 위치를 계산합니다.
    • 클라이언트 IP 주소에 대해 해시값을 계산합니다.
    • 모든 가능한 해시값 범위에서 해시의 위치를 계산합니다.
    • 해시 위치가 전환 창에서 현재 위치보다 크다면, 요청을 새로운 애플리케이션 서버로 전달합니다. 그렇지 않다면 요청을 이전 애플리케이션 서버로 보냅니다.

2. HTTP 애플리케이션을 위한 NGINX 구성

이 예시에서는 NGINX를 웹 애플리케이션 서버로의 리버스 프록시로 사용하므로 모든 구성은 http 컨텍스트 안에 있습니다. stream 컨텍스트에서 TCP 및 UDP 애플리케이션의 구성에 대한 자세한 내용은 아래를 참조하십시오.

먼저, 이전 애플리케이션 코드와 새로운 애플리케이션 코드를 각각 호스팅 하는 서버 집합에 별도의 upstream 블록을 정의합니다. 점진적 전환 구성에도 불구하고, NGINX는 전환 기간 동안에도 사용 가능한 서버들 사이에서 로드 밸런싱을 수행합니다.

upstream old {
    server 10.0.0.1;
    server 10.0.0.2;
}

upstream new {
    server 10.0.0.9;
    server 10.0.0.10;
}

다음으로 NGINX가 클라이언트에게 제공하는 프론트엔드 서비스를 정의합니다.

js_import /etc/nginx/progressive_transition.js;
js_set $upstream progressive_transition.transitionStatus; # Returns "old|new" based on window pos

server {
    listen 80;

    location / {
        set $transition_window_start "Wed, 31 Aug 2016 17:00:00 +0100";
        set $transition_window_end   "Wed, 31 Aug 2016 19:00:00 +0100";

        proxy_pass http://$upstream;
        error_log /var/log/nginx/transition.log info; # Enable NJS logging
    }
}

사용할 업스트림 그룹을 결정하기 위해 NGINX JavaScript를 사용하고 있으므로 NGINX JavaScript 코드가 위치할 곳을 지정해야 합니다. NGINX Plus R11 이상에서는 모든 NGINX JavaScript 코드를 별도의 파일에 위치시키고, js_import 지시문을 사용하여 해당 위치를 지정해야 합니다.

js_set 지시문은 $upstream 변수의 값을 설정합니다. 이 지시문은 NGINX에게 NGINX JavaScript 함수인 transitionStatus를 호출하도록 지시하지 않습니다. NGINX 변수는 필요한 시점에 평가되며, 즉, 요청 처리 중에 사용되는 시점에서 평가됩니다. 따라서 js_set 지시문은 $upstream 변수를 필요한 시점에 어떻게 평가할지 NGINX에게 알려줍니다.

server 블록은 NGINX가 수신하는 HTTP 요청을 처리하는 방식을 정의합니다. listen 지시문은 NGINX가 80 포트에서 수신하도록 지시합니다. 실제 운영 구성에서는 이동 중인 데이터를 보호하기 위해 SSL/TLS를 사용하는 것이 일반적입니다.

location 블록은 전체 애플리케이션 공간(/)에 적용됩니다. 이 블록 내에서는 set 지시문을 사용하여 $transition_window_start와 $transition_window_end라는 두 개의 새로운 변수로 전환 창을 정의합니다. 날짜는 RFC 2822 형식(스니펫에서와 같이) 또는 ISO 8601 형식(밀리초(ms) 포함)으로 지정할 수 있습니다. 두 형식 모두 해당하는 지역 시간대 표시자를 포함해야 합니다. 이는 JavaScript Date.now 함수가 항상 UTC 날짜와 시간을 반환하므로 현지 시간대가 지정된 경우에만 정확한 시간 비교가 가능하기 때문입니다.

proxy_pass 지시문은 transitionStatus 함수에 의해 계산된 업스트림 그룹으로 요청을 전달합니다.

마지막으로, error_log 지시문은 info 및 그 이상 심각도 수준의 이벤트를 NGINX JavaScript 로깅할 수 있도록 합니다(기본적으로는 warn 및 그 이상의 수준의 이벤트만 로깅 됩니다). 이 지시문을 location 블록 내에 배치하고 별도의 로그 파일 이름을 지정함으로써, 모든 info 메시지에 주된 error log를 난잡하게 만드는 것을 피합니다.

3. HTTP 애플리케이션을 위한 NGINX JavaScript 코드

NGINX JavaScript 모듈을 이미 활성화했다고 가정합니다.

NGINX JavaScript 코드를 js_import 지시문에 지정된 대로 /etc/nginx/progressive_transition.js 파일에 위치시켰습니다. 모든 함수는 이 파일에 포함되어 있습니다.

종속 함수는 호출하는 함수보다 먼저 정의되어야 하므로, 먼저 클라이언트의 IP 주소의 해시값을 반환하는 함수를 정의합니다. 만약 애플리케이션 서버가 대부분 같은 LAN의 사용자에 의해 사용된다면, 모든 클라이언트는 매우 유사한 IP 주소를 가지고 있으므로, 작은 범위의 입력값에 대해서도 균등한 값 분포를 반환하는 해시 함수가 필요합니다.

이 예시에서는 FNV-1a 해시 알고리즘을 사용합니다. 이 알고리즘은 간결하고 빠르며 분포 특성이 상당히 좋습니다. 또한 양수인 32bit 정수를 반환하기 때문에 각 클라이언트 IP 주소의 위치를 쉽게 계산할 수 있습니다. 다음은 FNV-1a 알고리즘의 JavaScript 구현 코드입니다.

function fnv32a(str) {
  var hval = 2166136261;
  for (var i = 0; i < str.length; ++i ) {
    hval ^= str.charCodeAt(i);
    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
  }
  return hval >>> 0;
}

다음으로 NGINX 구성의 js_set 지시문에서 $upstream 변수를 설정하는 transitionStatus 함수를 정의합니다.

function transitionStatus(req) {
  var vars, window_start, window_end, time_now, timepos, numhash, hashpos;

  // Get the transition window from NGINX configuration
  vars = req.variables;
  window_start = new Date(vars.transition_window_start);
  window_end = new Date(vars.transition_window_end);

  // Are we in the transition time window?
  time_now = Date.now();
  if ( time_now < window_start ) {
    return "old";
  } else if ( time_now > window_end ) {
    return "new";
  } else { // We are in the transition window
    // Calculate our position in the window (0-1)
    timepos = (time_now - window_start) / (window_end - window_start);

    // Get numeric hash for this client's IP address
    numhash = fnv32a(req.remoteAddress);

    // Calculate the hash's position in the output range (0-1)
    hashpos = numhash / 4294967295; // Upper bound is 32 bits
    req.log("timepos = " + timepos + ", hashpos = " + hashpos); //error_log [info]

    // Should we transition this client?
    if ( timepos > hashpos ) {
      return "new";
    } else {
      return "old";
    }
  }
}

export default { transitionStatus }

transitionStatus 함수는 HTTP 요청을 나타내는 JavaScript 객체인 req를 매개변수로 사용합니다. 이 객체의 variables 속성에는 전환 기간을 정의하기 위해 설정한 두 가지 $transition_window_start 및 $transition_window_end를 포함하여 모든 NGINX 구성 변수가 포함되어 있습니다.

외부 if/else 블록은 전환 창이 시작되었는지, 완료되었는지 또는 진행 중인지를 결정합니다. 만약 진행 중인 경우, req.remoteAddress를 fnv32a 함수에 전달하여 클라이언트의 IP 주소의 해시값을 얻습니다.

해시값을 얻은 후, 코드는 해당 해시값이 가능한 모든 값의 범위 내에서 어디에 위치하는지를 계산합니다. FNV-1a 알고리즘은 32bit 정수를 반환하기 때문에 해시값을 4,294,967,295(32bit의 10진수 표현)로 나누어 위치를 계산할 수 있습니다.

이 시점에서 req.log()를 호출하여 전환 시간 창의 해시 위치와 현재 위치를 로깅합니다. 이 로그는 NGINX 설정에 정의된 error_log에 기록되며 info 레벨로 기록됩니다. 다음 예시와 같이 JavaScript 코드에서 생성된 로그 항목입니다. js: 접두사는 JavaScript 코드로부터 생성된 로그 항목임을 나타냅니다.

2016/09/08 17:44:48 [info] 41325#41325: *84 js: timepos = 0.373333, hashpos = 0.840858

마지막으로 출력 범위 내에서 해시된 값의 위치를 전환 시간 창 내 현재 위치와 비교하고 해당 업스트림 그룹의 이름을 반환합니다.

4. TCP/UDP 애플리케이션을 위한 NGINX 구성

이전 섹션의 HTTP 애플리케이션에 대한 샘플 구성은 NGINX가 HTTP 애플리케이션 서버에 대한 리버스 프록시로 작동할 때 적합합니다. 전체 구성 스티펫을 stream 컨텍스트로 이동하여 TCP/UDP 애플리케이션에 대한 구성을 조정할 수 있습니다.

한 번의 변경과 한 번의 확인만 있으면 됩니다.

  • set 지시문이 stream 컨텍스트에서 아직 지원하지 않기 때문에 NGINX 구성의 set 지시문 대신 transitionStatus 함수에서 $transition_window_start 및 $transition_window_end 변수를 정의합니다.
  • nginx.conf 구성 파일의 최상위 (“main”) 컨텍스트에서 다음 load_module 지시문을 사용하여 Stream 트래픽용 NGINX JavaScript 모듈이 활성화되었는지 확인합니다.
load_module modules/ngx_stream_js_module.so;

그런 다음 NGINX 소프트웨어를 다시 로드합니다.

5. NGINX Javascript 요약

NGINX JavaScript로 달성할 수 있는 복잡한 프로그래밍 구성 유형의 예시로 이 포스트에서 점진적 전환 사용 사례를 제시했습니다. 가장 적합한 업스트림 그룹의 선택을 제어하기 위해 사용자 정의 로직을 배포하는 것은 NGINX JavaScript로 가능한 많은 솔루션 중 하나입니다.

NGINX 및 NGINX Plus를 위한 훨씬 더 유용한 프로그램 구성 솔루션을 지원하기 위해 NGINX JavaScript의 기능을 계속해서 확장하고 향상시킬 것입니다.

NGINX Plus와 함께 NGINX JavaScript를 직접 사용해 보려면 지금 무료 30일 평가판을 시작하거나 NGINX STORE에 연락하여 문의하십시오.

사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.