가장 많이 실수하는 NGINX 설정 에러 10가지

문제가 있는 NGINX 사용자를 도울 때 다른 사용자의 구성에서 반복해서 본 것과 동일한 설정 실수를 종종 보게 됩니다. 때로는 동료 NGINX 엔지니어가 작성한 설정에서도 마찬가지입니다! 이번 가이드에서는 가장 많이 실수하는 NGINX 설정 에러 10가지를 살펴보고 무엇이 잘못되었고 어떻게 수정하는지 설명합니다.

목차

1. NGINX 설정 오류: Worker당 파일 설명자가 충분하지 않음
2. NGINX 설정 오류: error_log off 지시문
3. NGINX 설정 오류: 업스트림 서버에 대한 Keepalive 연결을 활성화하지 않음
4. NGINX 설정 오류: 지시적 상속이 작동하는 방식을 잊어버림
5. NGINX 설정 오류: proxy_buffering off 지시문
6. NGINX 설정 오류: if 지시문의 부적절한 사용
7. NGINX 설정 오류: 과도한 Health Checks
8. NGINX 설정 오류: 측정항목(Metrics)에 대한 보안되지 않은 액세스
9. NGINX 설정 오류: 모든 트래픽이 동일한 /24 CIDR 블록에서 올 때 ip_hash 사용
10. NGINX 설정 오류: 업스트림 그룹을 활용하지 않음

1. NGINX 설정 오류: Worker당 파일 설명자가 충분하지 않음

worker_connections 지시문은 NGINX Worker 프로세스가 열 수 있는 최대 동시 연결 수를 설정합니다(기본값은 512). 모든 유형의 연결(예: 프록시 서버와의 연결)은 클라이언트 연결뿐만 아니라 최대값에 포함됩니다. 그러나 궁극적으로 Worker당 동시 연결 수에는 또 다른 제한이 있음을 명심하는 것이 중요합니다. 즉, 각 프로세스에 할당되는 최대 파일 설명자(FD) 수에 대한 운영 체제 제한이 있습니다. 최신 UNIX 배포에서 기본 제한은 1024입니다.

가장 작은 NGINX 배포를 제외한 모든 배포의 경우 Worker당 512개의 연결 제한은 너무 작습니다. 실제로 NGINX 오픈소스 바이너리 및 NGINX Plus와 함께 배포하는 기본 nginx.conf 파일은 파일을 1024로 늘립니다.

일반적인 구성 실수는 FD에 대한 제한을 worker_connections 값의 두 배 이상으로 늘리지 않는 것입니다. 수정 사항은 기본 구성 컨텍스트에서 worker_rlimit_nofile 지시문을 사용하여 해당 값을 설정하는 것입니다.

더 많은 FD가 필요한 이유는 다음과 같습니다. NGINX Worker 프로세스에서 클라이언트 또는 업스트림 서버로의 각 연결은 FD를 소비합니다. NGINX가 웹 서버로 작동할 때 클라이언트 연결을 위해 하나의 FD를 사용하고 클라이언트당 최소 두 개의 FD를 위해 제공된 파일당 하나의 FD를 사용합니다(그러나 대부분의 웹 페이지는 많은 파일로 구성됨). 프록시 서버로 작동할 때 NGINX는 클라이언트 및 업스트림 서버에 대한 연결에 각각 하나의 FD를 사용하고, 잠재적으로 서버의 응답을 임시로 저장하는 데 사용되는 파일에 세 번째 FD를 사용합니다. 캐싱 서버로서 NGINX는 캐시된 응답의 경우 웹 서버처럼 작동하고 캐시가 비어 있거나 만료된 경우 프록시 서버처럼 작동합니다.

NGINX는 또한 로그 파일당 FD를 사용하고 Master 프로세스와 통신하기 위해 몇 개의 FD를 사용하지만 일반적으로 이러한 숫자는 연결 및 파일에 사용되는 FD의 수에 비해 작습니다.

UNIX는 프로세스당 FD 수를 설정하는 여러 가지 방법을 제공합니다.

  • 쉘에서 NGINX를 시작하는 경우 ulimit 명령
  • NGINX를 서비스로 시작하는 경우 초기화 스크립트 또는 시스템 서비스 매니페스트 변수
  • /etc/security/limits.conf 파일

그러나 사용 방법은 NGINX를 시작하는 방법에 따라 다르지만 worker_rlimit_nofile은 NGINX를 시작하는 방법에 관계없이 작동합니다.

또한 OS의 sysctl fs.file-max 명령으로 설정할 수 있는 FD 수에 대한 시스템 전체 제한이 있습니다. 일반적으로 충분히 크지만 모든 NGINX worker 프로세스가 사용할 수 있는 최대 파일 설명자 수(worker_rlimit_nofile * worker_processes)가 fs.file-max보다 훨씬 적은지 확인하는 것이 좋습니다. NGINX가 사용 가능한 모든 FD를 어떻게든 사용하는 경우(예: DoS 공격 중) 문제를 해결하기 위해 시스템에 로그인하는 것조차 불가능하게 됩니다.

2. NGINX 설정 오류: error_log off 지시문

일반적인 실수는 error_log off 지시문이 로깅을 비활성화한다고 생각하는 것입니다. 실제로 access_log 지시문과 달리 error_log는 매개변수를 해제하지 않습니다. 구성에 error_log off 지시문을 포함하면 NGINX는 NGINX 구성 파일의 기본 디렉토리(일반적으로 /etc/nginx)에 off라는 오류 로그 파일을 생성합니다.

NGINX와 관련된 문제를 디버깅할 때 중요한 정보 소스이기 때문에 오류 로그를 비활성화하지 않는 것이 좋습니다. 그러나 저장 공간이 너무 제한되어 사용 가능한 디스크 공간을 고갈시키기에 충분한 데이터를 기록할 수 있는 경우 오류 기록을 비활성화하는 것이 합리적일 수 있습니다. 기본 구성 컨텍스트에 다음 지시문을 포함합니다.

error_log /dev/null emerg;

이 지시문은 NGINX가 구성을 읽고 검증할 때까지 적용되지 않습니다. 따라서 NGINX가 시작되거나 구성이 다시 로드될 때마다 구성이 검증될 때까지 기본 오류 로그 위치(일반적으로 /var/log/nginx/error.log)에 기록될 수 있습니다. 로그 디렉토리를 변경하려면 nginx 명령에 -e 매개변수를 포함합니다.

3. NGINX 설정 오류: 업스트림 서버에 대한 Keepalive 연결을 활성화하지 않음

기본적으로 NGINX는 들어오는 모든 새 요청에 대해 업스트림(백엔드) 서버에 대한 새 연결을 엽니다. 이것은 NGINX와 서버가 연결을 설정하기 위해 3개의 패킷을 교환하고 연결을 종료하기 위해 3~4개의 패킷을 교환해야 하기 때문에 안전하지만 비효율적입니다.

높은 트래픽 볼륨에서 모든 요청에 대해 새 연결을 열면 시스템 리소스가 고갈되고 연결을 전혀 열 수 없습니다. 이유는 다음과 같습니다. 각 연결에 대해 source address, source port, destination address 및 destination port의 4-tuple이 고유해야 합니다. NGINX에서 업스트림 서버로 연결하는 경우 요소 중 3개(첫 번째, 세 번째, 네 번째)가 고정되어 소스 포트만 변수로 남습니다. 연결이 닫히면 Linux 소켓이 2분 동안 TIME‑WAIT 상태에 있게 되며, 트래픽 볼륨이 높을 경우 사용 가능한 source port 풀이 고갈될 가능성이 높아집니다. 이 경우 NGINX는 업스트림 서버에 대한 새 연결을 열 수 없습니다.

수정 사항은 NGINX와 업스트림 서버 간의 keepalive connections을 활성화하는 것입니다. 요청이 완료될 때 닫히는 대신 연결이 열린 상태로 유지되어 추가 요청에 사용됩니다. 이렇게 하면 소스 포트가 부족할 가능성이 줄어들고 성능이 향상됩니다.

연결 유지 연결을 활성화하려면:

각 Worker 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 연결 유지 연결 수를 설정하려면 모든 upstream{} 블록에 keepalive 지시문을 포함합니다.

keepalive 지시문은 NGINX Worker 프로세스가 열 수 있는 업스트림 서버에 대한 총 연결 수를 제한하지 않습니다. 이는 일반적인 오해입니다. 따라서 keepalive에 대한 매개변수는 생각만큼 클 필요가 없습니다.

매개변수를 upstream{} 블록에 나열된 서버 수의 두 배로 설정하는 것이 좋습니다. 이것은 NGINX가 모든 서버와의 연결 유지 연결을 유지하기에 충분히 크지만 업스트림 서버가 새로운 수신 연결도 처리할 수 있을 만큼 충분히 작습니다.

또한 hash, ip_hash, least_conn, least_time 또는 random 지시문을 사용하여 upstream{} 블록에서 부하 분산 알고리즘을 지정할 때 이 지시문이 keepalive 지시문 위에 나타나야 합니다. 이것은 NGINX 구성에서 지시문 순서가 중요하지 않다는 일반적인 규칙에 대한 드문 예외 중 하나입니다.

요청을 업스트림 그룹으로 전달하는 location{} 블록에서 proxy_pass 지시문과 함께 다음 지시문을 포함합니다.

proxy_http_version 1.1;
proxy_set_header   "Connection" "";

기본적으로 NGINX는 업스트림 서버에 대한 연결에 HTTP/1.0을 사용하므로 서버에 전달하는 요청에 Connection: close 헤더를 추가합니다. 그 결과 upstream{} 블록에 keepalive 지시문이 있음에도 불구하고 요청이 완료되면 각 연결이 닫힙니다.

proxy_http_version 지시문은 NGINX에게 대신 HTTP/1.1을 사용하도록 지시하고 proxy_set_header 지시문은 Connection 헤더에서 닫기 값을 제거합니다.

4. NGINX 설정 오류: 지시적 상속이 작동하는 방식을 잊어버림

NGINX 지시문은 아래쪽으로 상속되거나 “outside-in”: 자식 컨텍스트(다른 컨텍스트(상위) 내에 중첩됨)는 부모 수준에 포함된 지시문 설정을 상속합니다. 예를 들어, http{} 컨텍스트의 모든 server{}location{} 블록은 http 수준에 포함된 지시문 값을 상속하고, server{} 블록의 지시문은 그 안에 있는 모든 하위 location{} 블록이 상속합니다. . 그러나 동일한 지시문이 부모 컨텍스트와 자식 컨텍스트 모두에 포함된 경우 값이 함께 추가되지 않고 대신 자식 컨텍스트의 값이 부모 값을 재정의합니다.

실수는 배열 지시문에 대한 이 “재정의 규칙”을 잊어버리는 것입니다. 이는 여러 컨텍스트에서 뿐만 아니라 주어진 컨텍스트 내에서 여러 번 포함될 수 있습니다. 예를 들면 proxy_set_headeradd_header가 있습니다. 두 번째 이름에 “add”가 있으면 재정의 규칙을 잊어버리기 쉽습니다.

add_header에 대한 이 예에서 상속이 작동하는 방식을 설명할 수 있습니다.

http {
    add_header X-HTTP-LEVEL-HEADER 1;
    add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;

    server {
        listen 8080;
        location / {
            return 200 "OK";
        } 
    }

    server {
        listen 8081;
        add_header X-SERVER-LEVEL-HEADER 1;

        location / {
            return 200 "OK";
        }

        location /test {
            add_header X-LOCATION-LEVEL-HEADER 1;
            return 200 "OK";
        }

        location /correct {
            add_header X-HTTP-LEVEL-HEADER 1;
            add_header X-ANOTHER-HTTP-LEVEL-HEADER 1;

            add_header X-SERVER-LEVEL-HEADER 1;
            add_header X-LOCATION-LEVEL-HEADER 1;
            return 200 "OK";
        } 
    }
}

포트 8080에서 수신 대기하는 서버의 경우 server{} 또는 location{} 블록에 add_header 지시문이 없습니다. 따라서 상속은 간단하며 http{} 컨텍스트에 정의된 두 개의 헤더를 볼 수 있습니다.

% curl -is localhost:8080
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:15 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-HTTP-LEVEL-HEADER: 1
X-ANOTHER-HTTP-LEVEL-HEADER: 1
OK

포트 8081에서 수신 대기하는 서버의 경우 server{} 블록에는 add_header 지시문이 있지만 자식 location / 블록에는 없습니다. server{} 블록에 정의된 헤더는 http{} 컨텍스트에 정의된 두 개의 헤더를 재정의합니다.

% curl -is localhost:8081
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:20 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-SERVER-LEVEL-HEADER: 1
OK

하위 location /test 블록에는 add_header 지시문이 있으며 이는 상위 server{} 블록의 헤더와 http{} 컨텍스트의 두 헤더를 모두 재정의합니다.

% curl -is localhost:8081/test
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:25 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-LOCATION-LEVEL-HEADER: 1
OK

location{} 블록이 상위 컨텍스트에 정의된 헤더와 로컬로 정의된 헤더를 보존하도록 하려면 location{} 블록 내에서 상위 헤더를 재정의해야 합니다. 이것이 location /correct 블록에서 수행한 작업입니다.

% curl -is localhost:8081/correct
HTTP/1.1 200 OK
Server: nginx/1.21.5
Date: Mon, 21 Feb 2022 10:12:30 GMT
Content-Type: text/plain
Content-Length: 2
Connection: keep-alive
X-HTTP-LEVEL-HEADER: 1
X-ANOTHER-HTTP-LEVEL-HEADER: 1
X-SERVER-LEVEL-HEADER: 1
X-LOCATION-LEVEL-HEADER: 1
OK

5. NGINX 설정 오류: proxy_buffering off 지시문

프록시 버퍼링은 NGINX에서 기본적으로 활성화되어 있습니다(proxy_buffering 지시문이 켜짐으로 설정됨). 프록시 버퍼링은 NGINX가 서버로부터 받은 응답을 내부 버퍼에 저장하고 전체 응답이 버퍼링될 때까지 클라이언트에 데이터 전송을 시작하지 않는다는 것을 의미합니다. 버퍼링은 느린 클라이언트에서 성능을 최적화하는 데 도움이 됩니다. NGINX는 클라이언트가 모든 응답을 검색하는 데 걸리는 시간 동안 응답을 버퍼링하기 때문에 프록시 서버는 가능한 한 빨리 응답을 반환하고 다른 요청을 처리하는 데 사용할 수 있는 상태로 돌아갈 수 있습니다.

프록시 버퍼링이 비활성화되면 NGINX는 기본적으로 하나의 메모리 페이지 크기(운영 체제에 따라 4KB 또는 8KB)인 버퍼에서 클라이언트로 보내기 시작하기 전에 서버 응답의 첫 번째 부분만 버퍼링합니다. 이것은 일반적으로 응답 헤더를 위한 충분한 공간입니다. 그런 다음 NGINX는 응답을 수신할 때 동기적으로 클라이언트에 응답을 전송하여 NGINX가 다음 응답 세그먼트를 수락할 수 있을 때까지 기다리는 동안 서버를 유휴 상태로 유지합니다.

그래서 우리는 구성에서 proxy_buffering이 꺼진 것(off)을 자주 보고 놀랐습니다. 아마도 클라이언트가 경험하는 대기 시간을 줄이기 위한 것일 수 있지만 프록시 버퍼링이 비활성화된 경우 속도 제한 및 캐싱이 구성되어도 작동하지 않고 성능이 저하되는 등 부작용이 많은 반면 효과는 무시할 수 있습니다.

프록시 버퍼링을 비활성화하는 것이 합리적일 수 있는 사용 사례는 소수에 불과하므로(예: 긴 폴링) 기본값을 변경하지 않는 것이 좋습니다.

6. NGINX 설정 오류: if 지시문의 부적절한 사용

if 지시문은 특히 location{} 블록에서 사용하기 까다롭습니다. 종종 예상대로 작동하지 않으며 segfault를 일으킬 수도 있습니다. 사실 NGINX Wiki에 If is Evil이라는 기사가 있을 정도로 까다롭습니다. 문제와 이를 피하는 방법에 대한 자세한 논의를 위해 그곳으로 안내합니다.

일반적으로 if{} 블록 내에서 항상 안전하게 사용할 수 있는 유일한 지시문은 returnrewrite입니다. 다음 예에서는 if를 사용하여 X‑Test 헤더가 포함된 요청을 감지합니다(그러나 이것은 테스트하려는 모든 조건일 수 있음). NGINX는 430(요청 헤더 필드가 너무 큼) 오류를 반환하고 @error_430이라는 이름의 위치에서 이를 가로채고 b라는 업스트림 그룹에 요청을 프록시합니다.

location / {
    error_page 430 = @error_430;
    if ($http_x_test) {
        return 430; 
    }

    proxy_pass http://a;
}

location @error_430 {
    proxy_pass b;
}

이것과 if의 다른 많은 용도의 경우 지시문을 완전히 피할 수 있는 경우가 많습니다. 다음 예시에서 요청에 X‑Test 헤더가 포함된 경우 map{} 블록은 $upstream_name 변수를 b로 설정하고 요청은 해당 이름을 가진 업스트림 그룹으로 프록시됩니다.

map $http_x_test $upstream_name {
    default "b";
    ""      "a";
}

# ...

location / {
    proxy_pass http://$upstream_name;
}

7. NGINX 설정 오류: 과도한 Health Checks

동일한 업스트림 그룹에 대한 요청을 프록시하도록(즉, 여러 server{} 블록에 동일한 proxy_pass 지시문을 포함하도록) 여러 가상 서버를 구성하는 것은 매우 일반적입니다. 이 상황에서 실수는 모든 server{} 블록에 health_check 지시문을 포함하는 것입니다. 이것은 추가 정보를 제공하지 않고 업스트림 서버에 더 많은 부하를 생성합니다.

명백한 위험을 감수하고 수정은 upstream{} 블록당 하나의 상태 확인만 정의하는 것입니다. 여기서 우리는 적절한 시간 초과 및 헤더 설정이 포함된 특별한 명명된 위치에서 b라는 업스트림 그룹에 대한 상태 확인을 정의합니다.

location / {
    proxy_set_header Host $host;
    proxy_set_header "Connection" "";
    proxy_http_version 1.1;
    proxy_pass http://b;
}

location @health_check {
    health_check;
    proxy_connect_timeout 2s;
    proxy_read_timeout 3s;
    proxy_set_header Host example.com;
    proxy_pass http://b;
}

복잡한 구성에서는 이 예에서와 같이 NGINX Plus API 및 대시보드와 함께 단일 가상 서버의 모든 상태 확인 위치를 그룹화하여 관리를 더욱 단순화할 수 있습니다.

server {
	listen 8080;
 
	location / {
	    # …
 	}
 
	location @health_check_b {
	    health_check;
	    proxy_connect_timeout 2s;
	    proxy_read_timeout 3s;
	    proxy_set_header Host example.com;
	    proxy_pass http://b;
	}
 
	location @health_check_c {
	    health_check;
	    proxy_connect_timeout 2s;
	    proxy_read_timeout 3s;
	    proxy_set_header Host api.example.com;
	    proxy_pass http://c;
	}
 
	location /api {
	    api write=on;
	    # directives limiting access to the API (see 'Mistake 8' below)
	}
 
	location = /dashboard.html {
	    root   /usr/share/nginx/html;
	}
}

8. NGINX 설정 오류: 측정항목(Metrics)에 대한 보안되지 않은 액세스

NGINX 작업에 대한 기본 메트릭은 Stub Status 모듈에서 사용할 수 있습니다. NGINX Plus의 경우 NGINX Plus API를 사용하여 훨씬 더 광범위한 메트릭 세트를 수집할 수도 있습니다. server{} 또는 location{} 블록에 각각 stub_status 또는 api 지시문을 포함하여 측정항목 수집을 활성화합니다. 이 블록은 측정항목을 보기 위해 액세스하는 URL이 됩니다. (NGINX Plus API의 경우 메트릭을 수집하려는 NGINX 엔터티(가상 서버, 업스트림 그룹, 캐시 등)에 대한 공유 메모리 영역도 구성해야 합니다.)

일부 측정항목은 웹사이트나 NGINX에 의해 프록시된 앱을 공격하는 데 사용할 수 있는 민감한 정보이며, 사용자 구성에서 종종 볼 수 있는 실수는 해당 URL에 대한 액세스를 제한하지 못하는 것입니다. 여기에서는 지표를 보호할 수 있는 몇 가지 방법을 살펴봅니다. 첫 번째 예에서 stub_status를 사용할 것입니다.

다음 구성을 사용하면 인터넷의 모든 사용자가 http://example.com/basic_status에서 메트릭에 액세스할 수 있습니다.

server {
    listen 80;
    server_name example.com;

    location = /basic_status {
        stub_status;
    }
}

HTTP Basic 인증으로 메트릭 보호

HTTP Basic Authentication으로 메트릭을 암호로 보호하려면 auth_basicauth_basic_user_file 지시문을 포함하십시오. 파일(여기서는 .htpasswd)은 메트릭을 보기 위해 로그인할 수 있는 클라이언트의 사용자 이름과 비밀번호를 나열합니다.

server {
    listen 80;
    server_name example.com;

    location = /basic_status {
        auth_basic “closed site”;
        auth_basic_user_file conf.d/.htpasswd;
        stub_status;
    }
}

allow 및 deny 지시문으로 메트릭 보호

승인된 사용자가 로그인하지 않도록 하고 해당 사용자가 메트릭에 액세스할 IP 주소를 알고 있는 경우 또 다른 옵션은 allow 지시문입니다. 개별 IPv4 및 IPv6 주소와 CIDR 범위를 지정할 수 있습니다. 모두 deny 지시문은 다른 주소로부터의 액세스를 방지합니다.

server {
    listen 80;
    server_name example.com;

    location = /basic_status {
        allow 192.168.1.0/24;
        allow 10.1.1.0/16;
        allow 2001:0db8::/32;
        allow 96.1.2.23/32;
        deny  all;
        stub_status;
    }
}

두 가지 방법 결합

두 가지 방법을 결합하려면 어떻게 해야 합니까? 우리는 클라이언트가 암호 없이 특정 주소의 메트릭에 액세스하도록 허용할 수 있으며 여전히 다른 주소에서 오는 클라이언트에 대한 로그인을 요구할 수 있습니다. 이를 위해 우리는 satisfy 지시문을 사용합니다. HTTP 기본 인증 자격 증명으로 로그인하거나 사전 승인된 IP 주소를 사용하는 클라이언트에 대한 액세스를 허용하도록 NGINX에 지시합니다. 추가 보안을 위해 특정 주소에서 온 사람도 로그인하도록 요구하도록 satisfy에 all을 설정할 수 있습니다.

server {
    listen 80;
    server_name monitor.example.com;

    location = /basic_status {
        satisfy any;

        auth_basic “closed site”;
        auth_basic_user_file conf.d/.htpasswd;
        allow 192.168.1.0/24;
        allow 10.1.1.0/16;
        allow 2001:0db8::/32;
        allow 96.1.2.23/32;
        deny  all;
        stub_status;
    }
}

예에서는 http://monitor.example.com:8080/api/)과 라이브 활동 모니터링 대시보드(http://monitor.example.com:8080/api/)에 대한 액세스를 제한합니다. /monitor.example.com/dashboard.html.

이 구성은 96.1.2.23/32 네트워크 또는 localhost에서 오는 클라이언트에만 암호 없이 액세스를 허용합니다. 지시문이 서버 수준에서 정의되기 때문에{} API와 대시보드 모두에 동일한 제한 사항이 적용됩니다. 참고로 api에 대한 write=on 매개변수는 이러한 클라이언트가 API를 사용하여 구성을 변경할 수도 있음을 의미합니다.

server {
    listen 8080;
    server_name monitor.example.com;
 
    satisfy any;
    auth_basic “closed site”;
    auth_basic_user_file conf.d/.htpasswd;
    allow 127.0.0.1/32;
    allow 96.1.2.23/32;
    deny  all;

    location = /api/ {    
        api write=on;
    }

    location = /dashboard.html {
        root /usr/share/nginx/html;
    }
}

9. NGINX 설정 오류: 모든 트래픽이 동일한 /24 CIDR 블록에서 올 때 ip_hash 사용

ip_hash 알고리즘은 클라이언트 IP 주소의 해시를 기반으로 upstream{} 블록의 서버 전반에 걸쳐 트래픽 부하를 분산합니다. 해싱 키는 IPv4 주소 또는 전체 IPv6 주소의 처음 세 옥텟입니다. 이 방법은 세션 지속성을 설정합니다. 즉, 클라이언트의 요청은 서버를 사용할 수 없는 경우를 제외하고 항상 동일한 서버로 전달됩니다.

고가용성을 위해 구성된 가상 사설망에서 NGINX를 리버스 프록시로 배포했다고 가정합니다. 다양한 방화벽, 라우터, L4 로드 밸런서 및 게이트웨이를 NGINX 앞에 배치하여 다양한 소스(내부 네트워크, 파트너 네트워크, 인터넷 등)의 트래픽을 수락하고 업스트림 서버로의 리버스 프록시를 위해 NGINX에 전달합니다.초기 NGINX 구성은 다음과 같습니다.

http {

    upstream {
        ip_hash;
        server 10.10.20.105:8080;
        server 10.10.20.106:8080;
        server 10.10.20.108:8080;
    }
 
    server {# …}
}

그러나 문제가 있음이 밝혀졌습니다. 모든 “Intercepting” 장치가 동일한 10.10.0.0/24 네트워크에 있으므로 NGINX에서는 모든 트래픽이 해당 CIDR 범위의 주소에서 오는 것처럼 보입니다. ip_hash 알고리즘은 IPv4 주소의 처음 세 옥텟을 해시한다는 것을 기억하십시오. 우리 배포에서 처음 세 옥텟은 모든 클라이언트에 대해 동일합니다(10.10.0). 따라서 해시는 모든 클라이언트에 대해 동일하며 트래픽을 다른 서버로 분산할 근거가 없습니다.

수정 사항은 $binary_remote_addr 변수를 해시 키로 대신 hash 알고리즘을 사용하는 것입니다. 이 변수는 전체 클라이언트 주소를 캡처하여 IPv4 주소의 경우 4바이트, IPv6 주소의 경우 16바이트인 이진 표현으로 변환합니다. 이제 해시는 가로채는 장치마다 다르며 로드 밸런싱은 예상대로 작동합니다.

또한 기본값 대신 ketama 해싱 방법을 사용하기 위해 일관된 매개변수를 포함합니다. 이렇게 하면 서버 집합이 변경될 때 다른 업스트림 서버에 다시 매핑되는 키 수가 크게 줄어들어 캐싱 서버에 대한 캐시 적중률이 높아집니다.

http {
    upstream {
        hash $binary_remote_addr consistent;
        server 10.10.20.105:8080;
        server 10.10.20.106:8080;
        server 10.10.20.108:8080;
    }

    server {# …}
}

10. NGINX 설정 오류: 업스트림 그룹을 활용하지 않음

가장 간단한 사용 사례 중 하나에 NGINX를 포트 3000에서 수신하는 단일 NodeJS 기반 백엔드 애플리케이션에 대한 리버스 프록시로 사용한다고 가정합니다. 일반적인 구성은 다음과 같습니다.

http {

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_set_header Host $host;
            proxy_pass http://localhost:3000/;
        }
    }
}

간단하죠? proxy_pass 지시문은 NGINX에 클라이언트로부터 요청을 보낼 위치를 알려줍니다. NGINX는 호스트 이름을 IPv4 또는 IPv6 주소로 확인하기만 하면 됩니다. 연결이 설정되면 NGINX는 요청을 해당 서버로 전달합니다.

여기서 실수는 서버가 하나뿐이므로 부하 분산을 구성할 이유가 없기 때문에 upstream{} 블록을 생성하는 것이 무의미하다고 가정하는 것입니다. 실제로, upstream{} 블록은 다음 구성에서 설명하는 것처럼 성능을 향상시키는 여러 기능을 잠금 해제합니다.

http {

    upstream node_backend {
        zone upstreams 64K;
        server 127.0.0.1:3000 max_fails=1 fail_timeout=2s;
        keepalive 2;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_set_header Host $host;
            proxy_pass http://node_backend/;
            proxy_next_upstream error timeout http_500;

        }
    }
}

zone 지시문은 호스트의 모든 NGINX worker 프로세스가 업스트림 서버에 대한 구성 및 상태 정보에 액세스할 수 있는 공유 메모리 영역을 설정합니다. 여러 업스트림 그룹이 영역을 공유할 수 있습니다. NGINX Plus를 사용하면 영역을 통해 NGINX를 다시 시작하지 않고도 NGINX Plus API를 사용하여 업스트림 그룹의 서버와 개별 서버의 설정을 변경할 수 있습니다.

server 지시문에는 서버 동작을 조정하는 데 사용할 수 있는 여러 매개변수가 있습니다. 이 예에서는 NGINX가 서버가 비정상이어서 요청을 수락할 수 없는지 확인하는 데 사용하는 조건을 변경했습니다. 여기에서는 통신 시도가 각 2초 기간 내에 한 번(기본값인 10초 기간에 한 번) 실패하는 경우 서버가 비정상인 것으로 간주합니다.

NGINX가 실패한 통신 시도로 간주하는 항목을 구성하기 위해 이 설정을 proxy_next_upstream 지시문과 결합하고 있습니다. 이 경우 업스트림 그룹의 다음 서버로 요청을 전달합니다. 기본 오류 및 시간 초과 조건에 http_500을 추가하여 NGINX가 실패한 시도를 나타내는 업스트림 서버의 HTTP 500(내부 서버 오류) 코드를 고려하도록 합니다.

keepalive 지시문은 각 worker 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 상태 유지 연결 수를 설정합니다. 우리는 이미 Mistake3: 업스트림 서버에 대한 연결 유지 연결을 활성화하지 않음에서 이점에 대해 논의했습니다.

NGINX Plus를 사용하면 업스트림 그룹으로 추가 기능을 구성할 수 있습니다.

NGINX 오픈소스는 시작하는 동안 서버 호스트 이름을 IP 주소로 한 번만 확인한다고 위에서 언급했습니다. 서버 지시문에 대한 resolve 매개변수를 사용하면 NGINX Plus가 업스트림 서버의 도메인 이름에 해당하는 IP 주소의 변경 사항을 모니터링하고 다시 시작할 필요 없이 업스트림 구성을 자동으로 수정할 수 있습니다.

서비스 매개변수를 통해 NGINX Plus는 포트 번호, 가중치 및 우선 순위에 대한 정보가 포함된 DNS SRV 레코드를 사용할 수 있습니다. 이는 서비스의 포트 번호가 종종 동적으로 할당되는 마이크로서비스 환경에서 중요합니다.

server 지시문에 대한 slow_start 매개변수를 사용하면 NGINX Plus가 새로 정상으로 간주되고 요청을 수락할 수 있는 서버로 보내는 요청의 양을 점진적으로 늘릴 수 있습니다. 이렇게 하면 서버를 압도하고 다시 실패할 수 있는 갑작스러운 요청 홍수를 방지할 수 있습니다.

queue 지시문을 사용하면 NGINX Plus가 요청을 처리할 업스트림 서버를 선택할 수 없을 때 클라이언트에 즉시 오류를 반환하는 대신 대기열에 요청을 배치할 수 있습니다.