NGINX Gateway Fabric 이 복잡한 라우팅 규칙을 구현하는 방법

NGINX Gateway Fabric 은 NGINX를 Data Plane으로 사용하는 Kubernetes Gateway API 사양의 구현입니다. GatewayClass, Gateway, ReferenceGrant 및 HTTPRoute와 같은 Gateway API 리소스를 처리하여 NGINX를 클러스터 외부에 Kubernetes에서 실행 중인 애플리케이션을 노출하는 HTTP Load Balancer로 구성합니다.

이 포스트에서는 NGINX Gateway Fabric 이 NGINX JavaScript 스크립팅 언어(njs)를 사용하여 요청의 헤더, 쿼리 매개변수 및 메서드를 기반으로 HTTP 요청 일치 구현을 간소화하는 방법을 살펴봅니다.

NGINX JavaScript에 대해 자세히 알아보기 전에 NGINX Gateway Fabric 이 Data Plane을 구성하는 방법을 살펴보겠습니다.

목차

1. Go 템플릿을 사용하여 Gateway API 리소스에서 NGINX 구성하기
2. NGINX JavaScript 란 무엇입니까?
3. HTTP 요청 일치
4. NGINX JavaScript 솔루션
5. 왜 NGINX JavaScript를 사용하나요?
6. NGINX Gateway Fabric 다음 단계

1. Go 템플릿을 사용하여 Gateway API 리소스에서 NGINX 구성하기

NGINX Data Plane을 구성하기 위해, 우리는 Kubernetes 클러스터에서 생성된 Gateway API 리소스를 기반으로 구성 파일을 생성합니다. 이러한 파일은 Go 템플릿에서 생성됩니다. 파일을 생성하기 위해, 우리는 Gateway API 리소스를 처리하고, 이를 NGINX 구성을 나타내는 데이터 구조로 변환한 다음, 이를 NGINX 데이터 구조에 적용하여 NGINX 구성 템플릿을 실행합니다. NGINX 데이터 구조에는 NGINX 지시문에 매핑되는 필드가 포함되어 있습니다.

대부분의 경우 이 방법은 매우 잘 작동합니다. Gateway API 리소스에 있는 대부분의 필드는 NGINX 지시문로 쉽게 변환할 수 있습니다. 트래픽 분할을 예로 들어보겠습니다. Gateway API에서 트래픽 분할은 여러 서비스와 해당 가중치를 HTTPRouteRule의 backendRefs 필드에 나열하여 구성합니다.

이 구성 스니펫은 트래픽의 50%를 server-v1으로, 나머지 50%를 service-v2로 분할합니다:

backendRefs: 
- name: service-v1 
   port: 80 
   weight: 50 
- name: service-v2 
   port: 80 
   weight: 50 

트래픽 분할은 NGINX HTTP split clients 모듈에서 기본적으로 지원되므로 템플릿을 사용하여 이를 NGINX 구성으로 변환하는 것은 간단합니다.

생성된 구성은 다음과 같습니다:

split_clients $request_id $variant { 
    50% upstream-service-v1; 
    50% upstream-service-v2; 
}  

트래픽 분할의 경우, Go 템플릿은 사용자가 Gateway API 리소스를 통해 구성한 트래픽 규칙을 반영하는 NGINX 구성을 생성할 수 있는 간단하면서도 강력한 도구입니다.

하지만 Gateway API 사양에 정의된 보다 복잡한 라우팅 규칙은 Go 템플릿을 사용하여 NGINX 지시문에 쉽게 매핑할 수 없으며, 이러한 규칙을 평가하기 위해 더 높은 수준의 언어가 필요하다는 것을 알게 되었습니다. 그래서 NGINX JavaScript를 사용하게 되었습니다.

2. NGINX JavaScript 란 무엇입니까?

NGINX JavaScript는 stream 및 HTTP NGINX 모듈로 구현된 NGINX 및 NGINX Plus 용 범용 스크립팅 프레임워크입니다. NGINX JavaScript 모듈을 사용하면 NGINX 런타임에 맞게 설계된 현대적이고 빠르며 강력한 고급 스크립팅으로 설계된 JavaScript 언어의 하위 집합인 njs 코드를 사용하여 NGINX의 구성 구문을 확장할 수 있습니다. 주로 웹 브라우저를 위해 고안된 표준 JavaScript와 달리 njs는 서버 측 언어입니다. 이 접근 방식은 서버 측 코드 실행의 요구 사항을 충족하고 NGINX의 요청 처리 아키텍처와 통합하기 위해 사용되었습니다.

응답 필터링, 진단 로깅, 하위 요청 조인 등 다양한 사용 사례가 있지만, 이 블로그에서는 NGINX Gateway Fabric 이 njs를 사용하여 HTTP 요청 일치를 수행하는 방법을 구체적으로 살펴봅니다.

3. HTTP 요청 일치

NGINX JavaScript 솔루션에 대해 자세히 알아보기 전에 구현 중인 Gateway API 기능에 대해 설명하겠습니다.

HTTP 요청 일치 요청의 헤더, 쿼리 매개변수 및/또는 메서드 등 특정 조건(일치)을 기반으로 요청을 라우팅 규칙에 일치시키는 프로세스입니다. Gateway API를 사용하면 규칙에 정의된 일치 조건에 따라 클라이언트 요청이 특정 백엔드로 전송되도록 하는 HTTPRouteRules 집합을 지정할 수 있습니다.

예를 들어, 애플리케이션의 버전이 두 가지이고 헤더 버전이 v2인 요청은 애플리케이션 버전 2로, 다른 모든 요청은 버전 1로 라우팅하려는 경우, 다음 라우팅 규칙을 사용하여 이를 수행할 수 있습니다:

rules: 
  - matches: 
      - path: 
          type: PathPrefix 
          value: / 
    backendRefs: 
      - name: v1-app 
        port: 80 
  - matches: 
      - path: 
          type: PathPrefix 
          value: / 
        headers: 
          - name: version 
            value: v2 
    backendRefs: 
      - name: v2-app 
        port: 80 

이제 쿼리 매개변수 TEST=v2가 포함된 트래픽을 애플리케이션 버전 2로 전송하려는 경우 해당 쿼리 매개변수와 일치하는 다른 규칙을 추가할 수 있습니다:

- matches 
  - path: 
      type: PathPrefix 
      value: /coffee 
    queryParams: 
      - name: TEST 
        value: v2 

위의 예제에서 정의한 세 가지 라우팅 규칙입니다:

  1. 경로 /와 요청을 일치시켜 백엔드 v1-app로 라우팅합니다.
  2. 경로 / 및 헤더 version: v2가 있는 요청을 일치시켜 백엔드 v2-app로 라우팅합니다.
  3. 경로 / 및 쿼리 매개변수 TEST=v2와 요청을 일치시켜 백엔드 v2-app로 라우팅합니다.

NGINX Gateway Fabric 은 이러한 라우팅 규칙을 처리하고 그에 따라 요청을 라우팅하도록 NGINX를 구성해야 합니다. 다음 섹션에서는 이 라우팅을 처리하기 위해 NGINX JavaScript를 사용하겠습니다.

4. NGINX JavaScript 솔루션 (NGINX Gateway Fabric)

일치하는 항복이 정의되었을 때 요청을 라우팅할 위치를 결정하기 위해, 요청의 헤더, 인수, 메서드에 따라 요청을 내부 location 블록으로 리다이렉션하는 redirect라는 location 핸들러 함수를 njs에 작성했습니다.

위에서 정의한 세 가지 라우팅 규칙에 대해 NGINX Gateway Fabric 에서 생성한 NGINX 구성을 살펴보겠습니다.

참고: 이 구성은 이 포스트의 목적을 위해 단순화되었습니다.

# nginx.conf 
load_module /usr/lib/nginx/modules/ngx_http_js_module.so; # load NGINX JavaScript Module 
events {}  
http {  
    js_import /usr/lib/nginx/modules/httpmatches.js; # Import the njs script 
    server {  
        listen 80; 
        location /_rule1 {  
            internal; # Internal location block that corresponds to rule 1 
            proxy_pass http://upstream-v1-app$request_uri;  
         }  
        location /_rule2{  
            internal; # Internal location block that corresponds to rule 2 
            proxy_pass http://upstream-v2-app$request_uri; 
        } 
  location /_rule3{ 
internal; # Internal location block that corresponds to rule 3 
proxy_pass http://upstream-v2-app$request_uri; 
  } 
        location / {  
            # This is the location block that handles the client requests to the path / 
           set $http_matches "[{\"redirectPath\":\"/_rule2\",\"headers\":[\"version:v2\"]},{\"redirectPath\":\"/_rule3\",\"params\":[\"TEST=v2\"]},{\"redirectPath\":\"/_rule1\",\"any\":true}]"; 
             js_content httpmatches.redirect; # Executes redirect njs function 
        } 
     }  
} 

js_import 지시문은 리다이렉션 함수가 포함된 파일을 지정하는 데 사용되며, js_content 지시문은 리다이렉션 함수를 실행하는 데 사용됩니다.

리다이렉션 함수는 http_matches 변수에 따라 달라집니다. http_matches 변수에는 라우팅 규칙에 정의된 일치 항목의 JSON 인코딩된 목록이 포함됩니다. JSON 일치 항목에는 필수 헤더, 쿼리 매개변수, 메서드뿐만 아니라 요청을 일치 항목으로 리다이렉션 하는 경로인 redirectPath가 포함됩니다. 모든 redirectPath는 내부 location 블록에 해당해야 합니다.

위의 라우팅 규칙과 동일한 순서로 표시된 http_matches 변수에 있는 각 JSON 일치 항목을 자세히 살펴봅시다:

  1. {“redirectPath”:”/_rule1″,”any”:true} – “any” boolean은 모든 요청이 이 규칙과 일치하며 /_rule1 경로가 있는 내부 location 블록으로 리다이렉션되어야 함을 의미합니다.
  2. {“redirectPath”:”/_rule2″,”headers”[“version:v2”]} – 헤더 버전이 v2인 요청은 이 규칙과 일치하며 /_rule2 경로가 있는 내부 location 블록으로 리다이렉션되어야 합니다.
  3. {“redirectPath”:”/_rule3″,”params”[“TEST:v2”]} – 쿼리 매개변수 TEST=v2가 있는 요청은 이 규칙과 일치하며 /_rule3 경로가 있는 내부 location 블록으로 리다이렉션되어야 합니다.

http_matches 변수에 대해 마지막으로 주의해야 할 점은 일치하는 순서가 중요하다는 것입니다. 리다이렉션 함수는 요청이 만족하는 첫 번째 일치 항목을 수락합니다. NGINX Gateway Fabric 은 Gateway API에 정의된 알고리즘에 따라 일치 항목을 정렬하여 올바른 일치 항목이 선택되었는지 확인합니다.

이제 리다이렉션 함수에 대한 JavaScript 코드를 살펴보겠습니다(전체 코드는 여기에서 확인할 수 있습니다):

// httpmatches.js 
function redirect(r) { 
  let matches; 

  try { 
    matches = extractMatchesFromRequest(r); 
  } catch (e) { 
    r.error(e.message); 
    r.return(HTTP_CODES.internalServerError); 
    return; 
  } 

  // Matches is a list of http matches in order of precedence. 
  // We will accept the first match that the request satisfies. 
  // If there's a match, redirect request to internal location block. 
  // If an exception occurs, return 500. 
  // If no matches are found, return 404. 
  let match; 
  try { 
    match = findWinningMatch(r, matches); 
  } catch (e) { 
    r.error(e.message); 
    r.return(HTTP_CODES.internalServerError); 
    return; 
  } 

  if (!match) { 
    r.return(HTTP_CODES.notFound); 
    return; 
  } 

  if (!match.redirectPath) { 
    r.error( 
      `cannot redirect the request; the match ${JSON.stringify( 
        match, 
      )} does not have a redirectPath set`, 
    ); 
    r.return(HTTP_CODES.internalServerError); 
    return; 
  } 

  r.internalRedirect(match.redirectPath); 
} 

리다이렉션 함수는 NGINX HTTP 요청 객체를 인수로 받아들이고 이 객체에서 http_matches 변수를 추출합니다. 그런 다음 요청 객체에서 찾은 요청의 속성을 일치 목록과 비교하여 일치하는 항목을 찾고 내부적으로 일치하는 항목의 리다이렉션 경로로 요청을 리다이렉션합니다.

5. 왜 NGINX JavaScript를 사용하나요?

Go 템플릿을 사용하여 HTTP 요청 일치를 구현하여 NGINX 구성을 생성할 수는 있지만, 트래픽 분할과 같은 간단한 사용 사례와 비교할 때 간단하지 않습니다. split_clients 지시문과 달리, 요청의 속성을 저수준 NGINX 구성의 일치 목록과 비교할 수 있는 기본 방법이 없습니다.

이러한 이유로 NGINX Gateway Fabric 에서 njs 대 HTTP 요청 일치를 사용하기로 결정했습니다:

  • 단순성 – 복잡한 HTTP 요청 일치를 쉽게 구현할 수 있어 코드 가독성과 개발 효율성이 향상됩니다.
  • 디버깅 – 설명형 오류 메시지를 허용하여 디버깅을 간소화하여 문제 해결 속도를 높입니다.
  • 단위 테스트 – 코드를 철저하게 단위 테스트할 수 있어 강력하고 안정적인 기능을 보장합니다.
  • 확장성 – 높은 수준의 스크립팅 특성으로 쉽게 확장 및 수정할 수 있어 복잡한 수동 구성 변경 없이 진화하는 프로젝트 요구 사항을 수용할 수 있습니다.
  • 성능 – NGINX를 위해 특별히 제작되었으며 속도가 빠르도록 설계되었습니다.

6. NGINX Gateway Fabric 다음 단계

NGINX Data Plane을 사용한 Gateway API 구현에 관심이 있으시다면 GitHub의 NGINX Gateway Fabric 프로젝트를 방문하여 참여하세요:

  • 프로젝트 기여자로 참여하기
  • Lab에서 구현을 시도해 보세요
  • 테스트 및 피드백 제공

njs에 대해 자세히 알아보려면 추가 예제를 확인하거나 이 포스트를 읽어보십시오.

NGINX Plus를 직접 사용해 보시려면 30일 무료 평가판을 신청하거나 NGINX STORE에 연락하여 문의하십시오.

NGINX에 대한 최신 정보들을 빠르게 전달받고 싶으시다면, 아래의 뉴스레터를 구독하세요.