NGINX Unit – Reverse Proxy 및 주소 기반 라우팅 도입

NGINX Unit 은 일반 라우팅 프레임워크 내에서 리버스 프록시를 가능하게 합니다.
새로운 프록시 옵션은 요청을 지정된 주소로 프록시하는 것을 구성하며, 내부 라우팅 및 정적 파일 서비스에 대한 포스트에서 이미 알고 있는 passshare 옵션과 함께 사용됩니다.
현재 프록시 주소 구성은 IPv4, IPv6 및 Unix 소켓 주소를 지원합니다.

목차

1. NGINX Unit 을 통한 HTTP 리버스 프록싱
2. NGINX Unit 의 Host 기반 라우팅
3. 사용 사례: IP 주소 필터링 및 액세스 관리
4. 결론

1. NGINX Unit 을 통한 HTTP 리버스 프록싱

다음은 proxy 옵션이 활성화된 샘플 routes 객체입니다.

{
    "routes": [
        {
            "match": {
                "host": "v1.example.com"
            },

            "action": {
                "proxy": "http://127.0.0.1:8000"
            }
        }
    ]
}

들어오는 요청이 match 조건을 충족하는 경우, NGINX Unit는 proxy 옵션에 지정된 주소로 프록시 연결을 설정하고 요청을 전달하여 응답(또는 오류 상태)을 클라이언트에 반환합니다.
그렇지 않은 경우, 요청은 route의 후속 일치 조건과의 일치 여부를 확인합니다.

처음에는 구성이 다소 단조롭게 보일 수 있습니다.
그러나 이것은 NGINX Unit 이 리버스 프록시로서의 진화의 시작에 불과하며, 이 기본 형태에서도 NGINX Unit 의 동적 기능이 필요하지 않은 요청을 다른 서버로 offload 하고, NGINX Unit listener와 인스턴스를 연결하여 사용자 정의 요청 처리 시나리오를 설계할 수 있도록 해줍니다.

이전 버전의 NGINX Unit 은 많은 기능을 갖고 있었지만, 들어오는 클라이언트 요청의 endpoint로만 사용할 수 있었습니다.
이제 NGINX Unit은 웹 프레임워크 내에서 중간 노드로서도 사용할 수 있으며, 모든 종류의 트래픽을 수용하고, 동적 구성을 유지하며, 자체적으로 대용량 트래픽을 처리하고, 기존 Backend 솔루션에 대한 리버스 프록시 역할을 수행할 수 있습니다.

그러나, 리버스 프록시된 주소의 가용성은 자동으로 유효성이 검증되지 않습니다.
주소를 잘못 구성하거나 실수로 리디렉션 루프를 생성한 경우, NGINX Unit은 요청이 실패할 때에만 오류를 보고합니다.

2. NGINX Unit 의 주소 기반 라우팅

NGINX Unit 1.14.0에서 추가된 주소 기반 라우팅은 라우팅 메커니즘을 확장하여 두 가지 새로운 match 옵션인 소스와 대상에 대한 주소 일치를 가능하게 합니다.
전자는 연결된 클라이언트의 IP 주소와 일치하고, 후자는 요청의 대상 주소와 일치합니다.

NGINX Unit 의 라우팅 엔진은 이제 주소 값들을 개별 IPv4 또는 IPv6 기반 패턴 및 패턴 배열과 일치시킬 수 있습니다.
유효한 패턴은 포트 번호가 있는 와일드카드, 정확한 주소 또는 CIDR 표기법으로 된 주소 범위일 수 있습니다.

{
    "routes": [
        {
            "match": {
                "source": [
                    "*:8000",
                    "*:0-65535"
                ]
            },
            "action": {
                "share": "/single_wildcards_w/ports_or_port_ranges/"
            }
        },
        {
            "match": {
                "destination": [
                    "127.0.0.1",
                    "[2002::]:8000",
                    "192.168.0.11:8080-8090"
                ]
            },
            "action": {
                "share": "/single_v4_or_v6_IPs_w/ports_or_port_ranges/"
            }
        },
        {
            "match": {
                "source": [
                    "10.0.0.0/8",
                    "[0ff::/64]:8000",
                    "10.0.0.0/32:8080-8090"
                ]
            },
            "action": {
                "share": "/CIDR_v4_or_v6_IP_ranges_w/ports_or_port_ranges/"
            }
        },
        {
            "match": {
                "destination": [
                    "10.0.0.0-11.1.0.30",
                    "[fe08::-feff::]:8000",
                    "192.168.0.0-192.168.1.101:8080-8090"
                ]
            },
            "action": {
                "share": "/v4_or_v6_IP_ranges_w/ports_or_port_ranges/"
            }
        }
    ]
}

이러한 일치 방식은 다른 match 방식과 자유롭게 결합할 수 있습니다.
이전 릴리스에서 소개된 match 조건과 마찬가지로, 주소 기반 패턴을 부정하기 위해 느낌표(!)를 접두사로 붙일 수 있습니다.
다음 예제에서는 대상 옵션은 127.0.0.1을 제외한 모든 대상 주소와 일치합니다.

{
    "routes": [
        {
            "match": {
                "destination": "!127.0.0.1",
                "headers": {
                    "User-Agent": "Mozilla/5.0*"
                },
                "host": "*.example.com"
            }
        }
    ]
}

3. 사용 사례: IP 주소 필터링 및 액세스 관리

마지막으로, 새롭게 도입된 기능들의 실용적인 시너지를 살펴보겠습니다. 192.168.1.100과 192.168.1.101이라는 두 개의 거의 동일한 서버가 있다고 가정해봅시다.
각각은 포트 8080에서 단일 웹 앱 인스턴스를 실행하고 있습니다. NGINX는 내부 및 외부 사용자 모두에게 서비스를 제공하고, 이 과정에서 몇 가지 제한을 강제하고자 합니다.

  • 세계의 일부 지역 사용자는 액세스가 거부됩니다.
  • 지역 사용자 중에서는 관리자만이 앱의 특권 섹션에 액세스할 수 있습니다.
  • 신뢰할 수 있는 파트너는 관리자와 동등한 수준으로 동일한 섹션에 액세스할 수 있습니다.
  • 정적 파일은 앱 서버의 성능을 향상시키기 위해 별도로 제공됩니다.
  • 관리 작업은 보안을 향상시키기 위해 단일 서버로 제한됩니다.

예상치 못한 사건으로, NGINX는 NGINX Unit 을 리버스 프록시로만 사용하여 새로운 능력을 보여줍니다.

NGINX Unit 인스턴스에서 시작해보겠습니다. 외부 및 내부 트래픽을 위해 두 개의 리스너를 설정하고, 추가 보안을 위해 두 개의 별도 인증서 번들을 구성하고 각 리스너를 다른 경로로 지정합니다.

"listeners": {
    "192.168.1.99:8080": {
        "pass": "routes/internal",
        "tls": {
            "certificate": "internal-cert-bundle"
        }
    },
    "203.0.113.1:8080": {
        "pass": "routes/external",
        "tls": {
            "certificate": "external-cert-bundle"
        }
    }

우리는 두 개의 경로 (internalexternal)를 정의하여 들어오는 트래픽에 도메인별 제한을 도입합니다. 두 경로 모두 /admin URI에 대한 관리자 전용 액세스를 강제하는 match 조건을 가지고 있습니다.
내부 경로는 특정 쿠키를 소유한 로컬 사용자만 액세스를 허용하고, 외부 경로는 신뢰할 수 있는 파트너에 속한 특정 IP 주소에서만 액세스를 허용합니다.
각 경로의 다른 일치 조건은 각각 로컬 사용자와 제한된 파트너 네트워크 외부의 모든 외부 IP 주소에서 비 관리자 URI에 대한 액세스를 허용합니다. 두 경로는 모든 유효한 트래픽을 common이라는 경로로 보냅니다.

        "internal": [
            {
                "match": {
                    "cookies": {
                        "userhash": "2475203514"
                    },
                    "uri": "/admin/*"
                },

                "action": {
                    "pass": "routes/common"
                }
            },

            {
                "match": {
                    "uri": "!/admin/*"
                },

                "action": {
                    "pass": "routes/common"
                }
            }
        ],

        "external": [
            {
                "match": {
                    "source": "203.0.113.51",
                    "uri": "/admin/*"
                },

                "action": {
                    "pass": "routes/common"
                }
            },

            {
                "match": {
                    "source": "!203.0.113.100-203.0.113.255",
                    "uri": "!/admin/*"
                },

                "action": {
                    "pass": "routes/common"
                }
            }
        ],
},

다음은 세 가지 match 조건을 가진 common 경로입니다.
이 조건들은 정적 파일 처리를 각각 offload 하고, 보안 및 문제 해결을 위해 관리 작업을 단일 서버 주소(192.168.1.100)로 직접 수행하며, 수신기를 통해 내부 경로로 도착하는 모든 요청을 관리 요청도 처리하는 서버로 할당합니다.
최종 작업 개체는 두 서버 중 두 번째 서버(192.168.1.101)에 나머지 모든 요청을 무조건 전달합니다.
이러한 방식으로 내부 및 비관리 외부 트래픽을 분리하면 액세스 모니터링 및 권한 관리가 간소화됩니다.

"common": [
    {
        "match": {
            "uri": [
                "/js/*",
                "/css/*",
                "/xml/*"
            ]
        },

        "action": {
            "share": "/www/static/assets/"
        }
    },

    {
        "match": {
            "uri": "/admin/*"
        },

        "action": {
            "proxy": "http://192.168.1.100:8080"
        }
    },

    {
        "match": {
            "destination": "192.168.1.99:8080"
        },

        "action": {
            "proxy": "http://192.168.1.100:8080"
        }
    },

    {
        "action": {
            "proxy": "http://192.168.1.101:8080"
        }
    }
]

4. 결론

NGINX는 이미 몇 가지 중요한 개선 사항에 주목하고 있으며 이는 프로젝트의 지속적인 발전에 기여할 것입니다.
이는 Round-robin 로드 밸런싱, 초기 앱 격리를 조금 더 발전시키기 위한 rootfs 지원, 정적 자산 처리를 위한 Advanced 로직, 메모리 성능 개선 및 확장된 네트워킹 기능이 포함됩니다.

NGINX Unit 에서 원하는 다른 기능에 대해 관심이 있습니다. 토론과 개발에 참여하려면 GitHub 리포지터리를 방문해주십시오.

NGINX Plus 구독자는 추가 비용 없이 NGINX Unit에 대한 지원을 받을 수 있습니다.
NGINX Plus의 무료 30일 체험판을 시작해보세요.

아래 뉴스레터를 구독하고 NGINX와 NGINX STORE의 최신 정보들을 빠르게 전달 받아보세요.