문제가 있는 NGINX 사용자를 도울 때, 우리는 종종 다른 사용자의 구성에서 반복적으로 보았던 것과 동일한 구성 실수를 봅니다. 심지어 NGINX 엔지니어 동료가 작성한 구성에서도 볼 수 있습니다! 이 블로그에서는 가장 흔한 오류 10가지를 살펴보고 무엇이 잘못되었는지, 어떻게 수정하는지 설명합니다.
- 작업자당 파일 설명자가 충분하지 않습니다.
- 지시 error_log off사항
- 업스트림 서버에 대한 keepalive 연결을 활성화하지 않음
- 지시어 상속의 작동 방식을 잊어버림
- 지시 proxy_buffering off사항
- if지시문 의 부적절한 사용
- 과도한 건강 검진
- 메트릭에 대한 보안되지 않은 액세스
- ip_hash모든 트래픽이 동일한 /24 CIDR 블록에서 오는 경우 사용
- 상류 그룹을 이용하지 않음
실수 1: 작업자당 파일 설명자가 충분하지 않음
이 worker_connections지시문은 NGINX 워커 프로세스가 열 수 있는 최대 동시 연결 수를 설정합니다(기본값은 512). 모든 유형의 연결(예: 프록시 서버와의 연결)은 클라이언트 연결뿐만 아니라 최대값에 포함됩니다. 하지만 궁극적으로 워커당 동시 연결 수에는 또 다른 제한이 있다는 점을 명심하는 것이 중요합니다. 각 프로세스에 할당된 최대 파일 설명자(FD) 수에 대한 운영 체제 제한입니다. 최신 UNIX 배포판에서 기본 제한은 1024입니다.
가장 작은 NGINX 배포를 제외한 모든 경우, 작업자당 512개의 연결 제한은 아마도 너무 작을 것입니다. 실제로 NGINX Open Source 바이너리와 NGINX Plus와 함께 배포하는 기본 nginx.conf 파일은 이를 1024개로 늘립니다.
일반적인 구성 실수는 FD에 대한 제한을 .의 값의 최소 두 배로 늘리지 않는 것입니다 worker_connections. 해결책은 worker_rlimit_nofile주 구성 컨텍스트에서 지시문으로 해당 값을 설정하는 것입니다.
더 많은 FD가 필요한 이유는 다음과 같습니다. NGINX 워커 프로세스에서 클라이언트 또는 업스트림 서버로의 각 연결은 FD를 사용합니다. NGINX가 웹 서버로 작동할 때 클라이언트 연결에 FD 하나를 사용하고 제공된 파일당 FD 하나를 사용하여 클라이언트당 최소 FD 두 개를 사용합니다(하지만 대부분의 웹 페이지는 여러 파일로 구성됩니다). 프록시 서버로 작동할 때 NGINX는 클라이언트와 업스트림 서버에 대한 연결에 각각 FD 하나를 사용하고 잠재적으로 서버의 응답을 일시적으로 저장하는 데 사용되는 파일에 세 번째 FD를 사용합니다. 캐싱 서버로서 NGINX는 캐시된 응답에 대해 웹 서버처럼 동작하고 캐시가 비어 있거나 만료된 경우 프록시 서버처럼 동작합니다.
NGINX도 로그 파일당 하나의 FD를 사용하고, 마스터 프로세스와 통신하기 위해 여러 개의 FD를 사용하지만, 일반적으로 이 숫자는 연결 및 파일에 사용되는 FD 수에 비하면 적습니다.
UNIX는 프로세스당 FD 수를 설정하는 여러 가지 방법을 제공합니다.
- ulimit쉘에서 NGINX를 시작하는 경우 명령
- NGINX를 서비스로 시작하는 경우 스크립트 또는 서비스 매니페스트 init변수systemd
- /etc/security/limits.conf 파일
그러나 사용할 방법은 NGINX를 시작하는 방법에 따라 달라지지만, worker_rlimit_nofileNGINX를 시작하는 방법과 상관없이 작동합니다.
FD 수에 대한 시스템 전체 제한도 있으며, 이는 OS sysctl fs.file-max명령으로 설정할 수 있습니다. 일반적으로 충분히 크지만 모든 NGINX 작업자 프로세스가 사용할 수 있는 최대 파일 설명자 수( worker_rlimit_nofile * worker_processes)가 보다 상당히 적은지 확인하는 것이 좋습니다 fs.file‑max. NGINX가 사용 가능한 모든 FD를 사용하는 경우(예: DoS 공격 중) 문제를 해결하기 위해 머신에 로그인하는 것조차 불가능해집니다.
실수 2: error_log off지시
일반적인 실수는 error_log off지시어가 로깅을 비활성화한다고 생각하는 것입니다. 사실, access_log지시어 와 달리 매개변수를 error_log사용하지 않습니다 . 구성에 지시어를 off포함하면 NGINX는 NGINX 구성 파일의 기본 디렉토리(일반적으로 /etc/nginx )에 off 라는 이름의 오류 로그 파일을 만듭니다 .error_log off
오류 로그를 비활성화하는 것은 권장하지 않습니다. NGINX에서 문제를 디버깅할 때 중요한 정보 소스이기 때문입니다. 그러나 저장소가 너무 제한되어 사용 가능한 디스크 공간을 모두 소모할 만큼 충분한 데이터를 로깅할 수 있는 경우 오류 로깅을 비활성화하는 것이 합리적일 수 있습니다. 이 지시문을 기본 구성 컨텍스트에 포함합니다.
error_log /dev/null emerg;
이 지시문은 NGINX가 구성을 읽고 검증할 때까지 적용되지 않습니다. 따라서 NGINX가 시작되거나 구성이 다시 로드될 때마다 구성이 검증될 때까지 기본 오류 로그 위치(일반적으로 /var/log/nginx/error.log )에 기록될 수 있습니다. 로그 디렉토리를 변경하려면 명령 에 매개변수를 포함합니다 .-e <error_log_location>nginx
실수 3: 업스트림 서버에 대한 Keepalive 연결을 활성화하지 않음
기본적으로 NGINX는 들어오는 모든 새로운 요청에 대해 업스트림(백엔드) 서버에 대한 새 연결을 엽니다. 이는 안전하지만 비효율적입니다. NGINX와 서버는 연결을 설정하기 위해 3개의 패킷을 교환해야 하고 연결을 종료하기 위해 3개 또는 4개의 패킷을 교환해야 하기 때문입니다.
트래픽 양이 많을 때 모든 요청에 대해 새 연결을 열면 시스템 리소스가 고갈되어 연결을 전혀 열 수 없게 될 수 있습니다. 그 이유는 다음과 같습니다. 각 연결에 대해 소스 주소, 소스 포트, 대상 주소, 대상 포트의 4-튜플은 고유해야 합니다. NGINX에서 업스트림 서버로 연결하는 경우 세 가지 요소(첫 번째, 세 번째, 네 번째)가 고정되어 소스 포트만 변수로 남습니다. 연결이 닫히면 Linux 소켓은 TIME‑WAIT2분 동안 그 상태에 머무르므로 트래픽 양이 많을 때 사용 가능한 소스 포트 풀이 고갈될 가능성이 커집니다. 그런 경우 NGINX는 업스트림 서버에 대한 새 연결을 열 수 없습니다.
해결책은 NGINX와 업스트림 서버 간에 keepalive 연결을 활성화하는 것입니다 . 요청이 완료될 때 닫히는 대신, 연결은 추가 요청에 사용될 수 있도록 열려 있습니다. 이렇게 하면 소스 포트가 부족해질 가능성이 줄어들고 성능이 향상됩니다 .
Keepalive 연결을 활성화하려면:
keepalive각 블록에 지침을 포함하여 upstream{}각 작업자 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 keepalive 연결 수를 설정합니다.
지시문은 keepaliveNGINX 작업자 프로세스가 열 수 있는 업스트림 서버에 대한 총 연결 수를 제한하지 않는다는 점에 유의하세요. 이는 일반적인 오해입니다. 따라서 매개변수는 keepalive생각보다 클 필요가 없습니다.
매개 변수를 블록에 나열된 서버 수의 두 배로 설정하는 것이 좋습니다 upstream{}. 이는 NGINX가 모든 서버와 keepalive 연결을 유지하기에 충분히 크지만, 업스트림 서버도 새로운 수신 연결을 처리할 수 있을 만큼 작습니다.
또한 블록에서 로드 밸런싱 알고리즘을 지정할 때 upstream{}( hash, ip_hash, least_conn, least_time, 또는 random지시어 사용) 지시어가 keepalive지시어 위에 나타나야 합니다. 이는 NGINX 구성에서 지시어 순서가 중요하지 않다는 일반적인 규칙에 대한 드문 예외 중 하나입니다.
요청을 업스트림 그룹에 전달하는 블록 에서 location{}다음 지침을 proxy_pass지침과 함께 포함합니다.
proxy_http_version 1.1;
proxy_set_header "Connection" "";
기본적으로 NGINX는 업스트림 서버에 대한 연결에 HTTP/1.0을 사용하고 그에 따라 Connection: close서버로 전달하는 요청에 헤더를 추가합니다. 결과적으로 블록 keepalive에 지시문이 있음에도 불구하고 요청이 완료되면 각 연결이 닫힙니다 upstream{}.
해당 proxy_http_version지시문은 NGINX에게 대신 HTTP/1.1을 사용하도록 지시하고, 헤더 에서 proxy_set_header해당 값을 제거합니다 .closeConnection
실수 4: 지시어 상속의 작동 방식을 잊음
NGINX 지시문은 아래로 상속되거나 "외부에서 내부로" 상속됩니다. 자식 컨텍스트(다른 컨텍스트( 부모 ) 내에 중첩된 컨텍스트)는 부모 수준에 포함된 지시문의 설정을 상속합니다. 예를 들어 컨텍스트 의 모든 server{}및 블록은 수준 에 포함된 지시문의 값을 상속 하고 블록의 지시문은 해당 블록의 모든 자식 블록에 상속됩니다. 그러나 동일한 지시문이 부모 컨텍스트와 해당 자식 컨텍스트에 모두 포함된 경우 값이 합쳐지지 않습니다. 대신 자식 컨텍스트의 값이 부모 값을 재정의합니다.location{}http{}httpserver{}location{}
실수는 배열 지시문 에 대한 이 "오버라이드 규칙"을 잊는 것입니다 . 배열 지시문은 여러 컨텍스트에 포함될 수 있을 뿐만 아니라 주어진 컨텍스트 내에서 여러 번 포함될 수도 있습니다. 예로는 proxy_set_headerand가 있습니다. add_header second의 이름에 "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에서 수신하는 서버의 경우, 또는 블록 add_header에 지시문이 없습니다 . 따라서 상속은 간단하고 컨텍스트에 정의된 두 헤더를 볼 수 있습니다 .server{}location{}http{}
% curl -i 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에서 수신하는 서버의 경우 블록 add_header에는 지시문이 있지만 server{}자식 location /블록에는 없습니다. 블록에 정의된 헤더는 컨텍스트 server{}에 정의된 두 헤더를 재정의합니다 http{}.
% curl -i 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 -i 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 -i 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: proxy_buffering off지시
프록시 버퍼링은 NGINX에서 기본적으로 활성화됩니다( proxy_buffering지시문은 로 설정됨 on). 프록시 버퍼링은 NGINX가 서버에서 들어오는 응답을 내부 버퍼에 저장하고 전체 응답이 버퍼링될 때까지 클라이언트에 데이터를 전송하지 않는다는 것을 의미합니다. 버퍼링은 느린 클라이언트의 성능을 최적화하는 데 도움이 됩니다. NGINX는 클라이언트가 모든 응답을 검색하는 데 걸리는 시간 동안 응답을 버퍼링하기 때문에 프록시된 서버는 가능한 한 빨리 응답을 반환하고 다른 요청을 처리할 수 있습니다.
프록시 버퍼링이 비활성화되면 NGINX는 클라이언트로 전송하기 시작하기 전에 서버 응답의 첫 번째 부분만 버퍼링합니다. 기본적으로 한 메모리 페이지 크기(운영 체제에 따라 4KB 또는 8KB )의 버퍼에 있습니다. 이는 일반적으로 응답 헤더에 충분한 공간입니다. 그런 다음 NGINX는 클라이언트가 응답을 수신하는 대로 동기적으로 클라이언트에 응답을 전송하여 NGINX가 다음 응답 세그먼트를 수락할 때까지 서버가 유휴 상태로 대기하도록 합니다.
그래서 우리는 구성에서 얼마나 자주 볼 수 있는지에 놀랐습니다 proxy_buffering off. 아마도 클라이언트가 경험하는 대기 시간을 줄이기 위한 것일 수 있지만, 그 효과는 무시할 만하고 부작용은 많습니다. 프록시 버퍼링이 비활성화되면 속도 제한 및 캐싱이 구성하더라도 작동하지 않고 성능이 저하되는 등입니다.
프록시 버퍼링을 비활성화하는 것이 타당한 사용 사례는 소수에 불과합니다(예: 롱 폴링). 따라서 기본값을 변경하지 않는 것이 좋습니다. 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
실수 6: if지침 의 부적절한 사용
이 if지시어는 사용하기 까다롭습니다. 특히 location{}블록에서 그렇습니다. 종종 예상한 대로 작동하지 않고 세그폴트를 일으킬 수도 있습니다. 사실 너무 까다로워서 NGINX 위키에 If is Evil 이라는 제목의 기사가 있는데 , 저희는 문제에 대한 자세한 논의와 이를 피하는 방법을 알려드리기 위해 그곳으로 안내합니다.
일반적으로 블록 내에서 항상 안전하게 사용할 수 있는 유일한 지시문은 및 if{}입니다 . 다음 예에서는 헤더를 포함하는 요청을 감지하는 데 사용됩니다 (하지만 이는 테스트하려는 모든 조건일 수 있음). NGINX는 오류를 반환하고 @ error_430 이라는 명명된 위치에서 가로채고 요청을 b 라는 업스트림 그룹으로 프록시합니다 .returnrewriteifX‑Test430 (Request Header Fields Too Large)
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_nameb
map $http_x_test $upstream_name {
default "b";
"" "a";
}
# ...
location / {
proxy_pass http://$upstream_name;
}
실수 7: 과도한 Health Check
여러 가상 서버를 구성하여 동일한 업스트림 그룹에 요청을 프록시하는 것은 매우 일반적입니다(즉, proxy_pass여러 server{}블록에 동일한 지시문을 포함하는 것입니다). 이 상황에서 실수는 health_check모든 server{}블록에 지시문을 포함하는 것입니다. 이는 추가 정보를 제공하지 않고 업스트림 서버에 더 많은 부하를 발생시킬 뿐입니다.
당연한 말이지만, 해결책은 블록당 하나의 상태 점검만 정의하는 것입니다 . 여기서는 bupstream{} 라는 이름의 업스트림 그룹에 대한 상태 점검을 특별한 명명된 위치에 정의하고, 적절한 타임아웃과 헤더 설정을 완료합니다.
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;
}
}HTTP, TCP, UDP 및 gRPC 서버의 상태 점검에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
실수 8: 메트릭에 대한 보안되지 않은 액세스
NGINX 작업에 대한 기본 메트릭은 Stub Status 모듈 에서 사용할 수 있습니다 . NGINX Plus의 경우 NGINX Plus API를 사용하여 훨씬 더 광범위한 메트릭 세트를 수집할 수도 있습니다. 각각 or 블록에 stub_statusor 지시문을 포함하여 메트릭 수집을 활성화하면 해당 URL이 되어 메트릭을 볼 수 있습니다. ( NGINX Plus API 의 경우 메트릭을 수집하려는 NGINX 엔터티(가상 서버, 업스트림 그룹, 캐시 등)에 대한 공유 메모리 영역도 구성해야 합니다. NGINX Plus 관리자 가이드 의 지침을 참조하세요 .)apiserver{}location{}
일부 지표는 NGINX에서 프록시된 웹사이트나 앱을 공격하는 데 사용할 수 있는 민감한 정보이며, 사용자 구성에서 가끔 볼 수 있는 실수는 해당 URL에 대한 액세스를 제한하지 못하는 것입니다. 여기서는 지표를 보호할 수 있는 몇 가지 방법을 살펴보겠습니다. stub_status첫 번째 예에서 사용하겠습니다.
다음 구성을 사용하면 인터넷에 있는 모든 사람이 http://example.com/basic_status 에서 메트릭에 액세스할 수 있습니다 .
server {
listen 80;
server_name example.com;
location = /basic_status {
stub_status;
}
}HTTP 기본 인증을 사용하여 메트릭 보호
HTTP 기본 인증 으로 메트릭을 암호로 보호하려면 auth_basic및auth_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 all
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;
}
}두 가지 방법을 결합하다
두 방법을 결합하고 싶다면 어떨까요? 클라이언트가 비밀번호 없이 특정 주소에서 메트릭에 액세스하도록 허용하면서도 다른 주소에서 오는 클라이언트에 로그인을 요구할 수 있습니다. 이를 위해 지시문을 사용합니다 . NGINX에 HTTP 기본 인증 자격 증명으로 로그인하거나 사전 승인된 IP 주소를 사용하는 클라이언트에 액세스를 허용하도록 지시합니다. 추가 보안을 위해 특정 주소에서 오는 사람에게도 로그인을 요구하도록 설정할 수 있습니다 .satisfy anysatisfyall
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;
}
}NGINX Plus를 사용하면 동일한 기술을 사용하여 NGINX Plus API 엔드포인트( 다음 예에서는 http://monitor.example.com:8080/api/ )에 대한 액세스를 제한할 수 있고, http://monitor.example.com/dashboard.html 에 있는 라이브 활동 모니터링 대시보드에도 액세스할 수 있습니다 .
이 구성은 96.1.2.23/32 네트워크 또는 로컬호스트에서 오는 클라이언트에게만 암호 없이 액세스를 허용합니다. 지시문이 수준에서 정의되므로 server{}API와 대시보드에 동일한 제한이 적용됩니다. 참고로 매개 write=on변수는 api이러한 클라이언트도 API를 사용하여 구성을 변경할 수 있음을 의미합니다.
API와 대시보드 구성에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
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: ip_hash모든 트래픽이 동일한 /24 CIDR 블록에서 오는 경우 사용
이 ip_hash알고리즘은 클라이언트 IP 주소의 해시를 기반으로 블록의 서버 간에 트래픽을 로드 밸런싱합니다 upstream{}. 해싱 키는 IPv4 주소의 처음 세 옥텟 또는 전체 IPv6 주소입니다. 이 방법은 세션 지속성을 확립하는데, 이는 클라이언트의 요청이 서버를 사용할 수 없는 경우를 제외하고 항상 동일한 서버로 전달된다는 것을 의미합니다.
고가용성을 위해 구성된 가상 사설망에서 역방향 프록시로 NGINX를 배포했다고 가정해 보겠습니다. 다양한 방화벽, 라우터, 계층 4 로드 밸런서 및 게이트웨이를 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 {# …}
}하지만 문제가 하나 있습니다. 모든 "차단" 장치가 동일한 10.10.0.0/24 네트워크에 있으므로 NGINX에서는 모든 트래픽이 해당 CIDR 범위의 주소에서 오는 것처럼 보입니다. 알고리즘이 ip_hashIPv4 주소의 처음 세 옥텟을 해시한다는 점을 기억하세요. 저희 배포에서 처음 세 옥텟은 모든 클라이언트에서 동일합니다(10.10.0). 따라서 해시는 모든 클라이언트에서 동일하고 트래픽을 다른 서버로 분산할 근거가 없습니다.
해결책은 해시 키로 변수를 사용 hash하는 대신 알고리즘을 사용하는 것입니다. 해당 변수는 전체 클라이언트 주소를 캡처하여 IPv4 주소의 경우 4바이트 , IPv6 주소의 경우 16바이트인 이진 표현으로 변환합니다. 이제 해시는 각 가로채기 장치에 따라 다르고 로드 밸런싱은 예상대로 작동합니다.$binary_remote_addr
또한 기본값 대신 케타마consistent 해싱 방법을 사용하는 매개변수를 포함합니다 . 이렇게 하면 서버 세트가 변경될 때 다른 업스트림 서버로 다시 매핑되는 키 수가 크게 줄어들어 캐싱 서버의 캐시 적중률이 높아집니다.
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: Upstream 그룹의 이점을 활용하지 못함
포트 3000에서 수신하는 단일 NodeJS 기반 백엔드 애플리케이션의 역방향 프록시로서 가장 간단한 사용 사례 중 하나에 NGINX를 사용한다고 가정해 보겠습니다. 일반적인 구성은 다음과 같습니다.
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 워커 프로세스가 업스트림 서버에 대한 구성 및 상태 정보에 액세스할 수 있는 공유 메모리 영역을 설정합니다. 여러 업스트림 그룹이 영역을 공유할 수 있습니다. NGINX Plus를 사용하면 영역을 통해 NGINX Plus API를 사용하여 업스트림 그룹의 서버와 NGINX를 다시 시작하지 않고도 개별 서버의 설정을 변경할 수도 있습니다. 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
이 server지시문에는 서버 동작을 조정하는 데 사용할 수 있는 여러 매개변수가 있습니다. 이 예에서 NGINX가 서버가 건강하지 않고 따라서 요청을 수락할 수 없음을 결정하는 데 사용하는 조건을 변경했습니다. 여기서는 통신 시도가 2초마다 한 번이라도 실패하면 (기본값인 10초 마다 한 번 ) 서버가 건강하지 않은 것으로 간주합니다.
이 설정을 지시문과 결합하여 proxy_next_upstreamNGINX가 실패한 통신 시도로 간주하는 것을 구성합니다. 이 경우 요청을 업스트림 그룹의 다음 서버로 전달합니다. 기본 오류 및 시간 초과 조건에 NGINX가 업스트림 서버의 http_500HTTP 500 (Internal Server Error)코드를 실패한 시도로 간주하도록 추가합니다.
이 keepalive지시문은 각 작업자 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 keepalive 연결 수를 설정합니다. 우리는 이미 실수 3: 업스트림 서버에 대한 keepalive 연결을 활성화하지 않음 에서 이점에 대해 논의했습니다 .
NGINX Plus를 사용하면 업스트림 그룹으로 추가 기능을 구성할 수 있습니다.
위에서 언급했듯이 NGINX Open Source는 시작 중에 서버 호스트 이름을 IP 주소로 단 한 번만 확인합니다. resolveserver 지시문에 대한 매개변수를 사용하면 NGINX Plus가 업스트림 서버의 도메인 이름에 해당하는 IP 주소의 변경 사항을 모니터링하고 재시작할 필요 없이 업스트림 구성을 자동으로 수정할 수 있습니다.
이 service매개변수는 NGINX Plus가 포트 번호, 가중치 및 우선순위에 대한 정보를 포함하는 DNS 레코드를 사용할 수 있도록 합니다 SRV. 이는 서비스의 포트 번호가 종종 동적으로 할당되는 마이크로서비스 환경에서 중요합니다.
서버 주소를 확인하는 방법에 대한 자세한 내용은 블로그의 NGINX 및 NGINX Plus에서 서비스 검색에 DNS 사용을 참조하세요.
서버 지시문에 대한 매개 변수 slow_start를 사용하면 NGINX Plus가 새로이 건강하다고 간주되고 요청을 수락할 수 있는 서버로 보내는 요청의 양을 점진적으로 늘릴 수 있습니다. 이를 통해 서버를 압도하고 다시 실패하게 만들 수 있는 갑작스러운 요청 홍수를 방지할 수 있습니다.
이 queue지시어를 사용하면 NGINX Plus에서 요청을 처리할 상위 서버를 선택할 수 없는 경우 클라이언트에 즉시 오류를 반환하는 대신 요청을 대기열에 넣을 수 있습니다.
문제가 있는 NGINX 사용자를 도울 때, 우리는 종종 다른 사용자의 구성에서 반복적으로 보았던 것과 동일한 구성 실수를 봅니다. 심지어 NGINX 엔지니어 동료가 작성한 구성에서도 볼 수 있습니다! 이 블로그에서는 가장 흔한 오류 10가지를 살펴보고 무엇이 잘못되었는지, 어떻게 수정하는지 설명합니다.
실수 1: 작업자당 파일 설명자가 충분하지 않음
이 worker_connections지시문은 NGINX 워커 프로세스가 열 수 있는 최대 동시 연결 수를 설정합니다(기본값은 512). 모든 유형의 연결(예: 프록시 서버와의 연결)은 클라이언트 연결뿐만 아니라 최대값에 포함됩니다. 하지만 궁극적으로 워커당 동시 연결 수에는 또 다른 제한이 있다는 점을 명심하는 것이 중요합니다. 각 프로세스에 할당된 최대 파일 설명자(FD) 수에 대한 운영 체제 제한입니다. 최신 UNIX 배포판에서 기본 제한은 1024입니다.
가장 작은 NGINX 배포를 제외한 모든 경우, 작업자당 512개의 연결 제한은 아마도 너무 작을 것입니다. 실제로 NGINX Open Source 바이너리와 NGINX Plus와 함께 배포하는 기본 nginx.conf 파일은 이를 1024개로 늘립니다.
일반적인 구성 실수는 FD에 대한 제한을 .의 값의 최소 두 배로 늘리지 않는 것입니다 worker_connections. 해결책은 worker_rlimit_nofile주 구성 컨텍스트에서 지시문으로 해당 값을 설정하는 것입니다.
더 많은 FD가 필요한 이유는 다음과 같습니다. NGINX 워커 프로세스에서 클라이언트 또는 업스트림 서버로의 각 연결은 FD를 사용합니다. NGINX가 웹 서버로 작동할 때 클라이언트 연결에 FD 하나를 사용하고 제공된 파일당 FD 하나를 사용하여 클라이언트당 최소 FD 두 개를 사용합니다(하지만 대부분의 웹 페이지는 여러 파일로 구성됩니다). 프록시 서버로 작동할 때 NGINX는 클라이언트와 업스트림 서버에 대한 연결에 각각 FD 하나를 사용하고 잠재적으로 서버의 응답을 일시적으로 저장하는 데 사용되는 파일에 세 번째 FD를 사용합니다. 캐싱 서버로서 NGINX는 캐시된 응답에 대해 웹 서버처럼 동작하고 캐시가 비어 있거나 만료된 경우 프록시 서버처럼 동작합니다.
NGINX도 로그 파일당 하나의 FD를 사용하고, 마스터 프로세스와 통신하기 위해 여러 개의 FD를 사용하지만, 일반적으로 이 숫자는 연결 및 파일에 사용되는 FD 수에 비하면 적습니다.
UNIX는 프로세스당 FD 수를 설정하는 여러 가지 방법을 제공합니다.
그러나 사용할 방법은 NGINX를 시작하는 방법에 따라 달라지지만, worker_rlimit_nofileNGINX를 시작하는 방법과 상관없이 작동합니다.
FD 수에 대한 시스템 전체 제한도 있으며, 이는 OS sysctl fs.file-max명령으로 설정할 수 있습니다. 일반적으로 충분히 크지만 모든 NGINX 작업자 프로세스가 사용할 수 있는 최대 파일 설명자 수( worker_rlimit_nofile * worker_processes)가 보다 상당히 적은지 확인하는 것이 좋습니다 fs.file‑max. NGINX가 사용 가능한 모든 FD를 사용하는 경우(예: DoS 공격 중) 문제를 해결하기 위해 머신에 로그인하는 것조차 불가능해집니다.
실수 2: error_log off지시
일반적인 실수는 error_log off지시어가 로깅을 비활성화한다고 생각하는 것입니다. 사실, access_log지시어 와 달리 매개변수를 error_log사용하지 않습니다 . 구성에 지시어를 off포함하면 NGINX는 NGINX 구성 파일의 기본 디렉토리(일반적으로 /etc/nginx )에 off 라는 이름의 오류 로그 파일을 만듭니다 .error_log off
오류 로그를 비활성화하는 것은 권장하지 않습니다. NGINX에서 문제를 디버깅할 때 중요한 정보 소스이기 때문입니다. 그러나 저장소가 너무 제한되어 사용 가능한 디스크 공간을 모두 소모할 만큼 충분한 데이터를 로깅할 수 있는 경우 오류 로깅을 비활성화하는 것이 합리적일 수 있습니다. 이 지시문을 기본 구성 컨텍스트에 포함합니다.
error_log /dev/null emerg;이 지시문은 NGINX가 구성을 읽고 검증할 때까지 적용되지 않습니다. 따라서 NGINX가 시작되거나 구성이 다시 로드될 때마다 구성이 검증될 때까지 기본 오류 로그 위치(일반적으로 /var/log/nginx/error.log )에 기록될 수 있습니다. 로그 디렉토리를 변경하려면 명령 에 매개변수를 포함합니다 .-e <error_log_location>nginx
실수 3: 업스트림 서버에 대한 Keepalive 연결을 활성화하지 않음
기본적으로 NGINX는 들어오는 모든 새로운 요청에 대해 업스트림(백엔드) 서버에 대한 새 연결을 엽니다. 이는 안전하지만 비효율적입니다. NGINX와 서버는 연결을 설정하기 위해 3개의 패킷을 교환해야 하고 연결을 종료하기 위해 3개 또는 4개의 패킷을 교환해야 하기 때문입니다.
트래픽 양이 많을 때 모든 요청에 대해 새 연결을 열면 시스템 리소스가 고갈되어 연결을 전혀 열 수 없게 될 수 있습니다. 그 이유는 다음과 같습니다. 각 연결에 대해 소스 주소, 소스 포트, 대상 주소, 대상 포트의 4-튜플은 고유해야 합니다. NGINX에서 업스트림 서버로 연결하는 경우 세 가지 요소(첫 번째, 세 번째, 네 번째)가 고정되어 소스 포트만 변수로 남습니다. 연결이 닫히면 Linux 소켓은 TIME‑WAIT2분 동안 그 상태에 머무르므로 트래픽 양이 많을 때 사용 가능한 소스 포트 풀이 고갈될 가능성이 커집니다. 그런 경우 NGINX는 업스트림 서버에 대한 새 연결을 열 수 없습니다.
해결책은 NGINX와 업스트림 서버 간에 keepalive 연결을 활성화하는 것입니다 . 요청이 완료될 때 닫히는 대신, 연결은 추가 요청에 사용될 수 있도록 열려 있습니다. 이렇게 하면 소스 포트가 부족해질 가능성이 줄어들고 성능이 향상됩니다 .
Keepalive 연결을 활성화하려면:
keepalive각 블록에 지침을 포함하여 upstream{}각 작업자 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 keepalive 연결 수를 설정합니다.
지시문은 keepaliveNGINX 작업자 프로세스가 열 수 있는 업스트림 서버에 대한 총 연결 수를 제한하지 않는다는 점에 유의하세요. 이는 일반적인 오해입니다. 따라서 매개변수는 keepalive생각보다 클 필요가 없습니다.
매개 변수를 블록에 나열된 서버 수의 두 배로 설정하는 것이 좋습니다 upstream{}. 이는 NGINX가 모든 서버와 keepalive 연결을 유지하기에 충분히 크지만, 업스트림 서버도 새로운 수신 연결을 처리할 수 있을 만큼 작습니다.
또한 블록에서 로드 밸런싱 알고리즘을 지정할 때 upstream{}( hash, ip_hash, least_conn, least_time, 또는 random지시어 사용) 지시어가 keepalive지시어 위에 나타나야 합니다. 이는 NGINX 구성에서 지시어 순서가 중요하지 않다는 일반적인 규칙에 대한 드문 예외 중 하나입니다.
요청을 업스트림 그룹에 전달하는 블록 에서 location{}다음 지침을 proxy_pass지침과 함께 포함합니다.
proxy_http_version 1.1; proxy_set_header "Connection" "";기본적으로 NGINX는 업스트림 서버에 대한 연결에 HTTP/1.0을 사용하고 그에 따라 Connection: close서버로 전달하는 요청에 헤더를 추가합니다. 결과적으로 블록 keepalive에 지시문이 있음에도 불구하고 요청이 완료되면 각 연결이 닫힙니다 upstream{}.
해당 proxy_http_version지시문은 NGINX에게 대신 HTTP/1.1을 사용하도록 지시하고, 헤더 에서 proxy_set_header해당 값을 제거합니다 .closeConnection
실수 4: 지시어 상속의 작동 방식을 잊음
NGINX 지시문은 아래로 상속되거나 "외부에서 내부로" 상속됩니다. 자식 컨텍스트(다른 컨텍스트( 부모 ) 내에 중첩된 컨텍스트)는 부모 수준에 포함된 지시문의 설정을 상속합니다. 예를 들어 컨텍스트 의 모든 server{}및 블록은 수준 에 포함된 지시문의 값을 상속 하고 블록의 지시문은 해당 블록의 모든 자식 블록에 상속됩니다. 그러나 동일한 지시문이 부모 컨텍스트와 해당 자식 컨텍스트에 모두 포함된 경우 값이 합쳐지지 않습니다. 대신 자식 컨텍스트의 값이 부모 값을 재정의합니다.location{}http{}httpserver{}location{}
실수는 배열 지시문 에 대한 이 "오버라이드 규칙"을 잊는 것입니다 . 배열 지시문은 여러 컨텍스트에 포함될 수 있을 뿐만 아니라 주어진 컨텍스트 내에서 여러 번 포함될 수도 있습니다. 예로는 proxy_set_headerand가 있습니다. add_header second의 이름에 "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에서 수신하는 서버의 경우, 또는 블록 add_header에 지시문이 없습니다 . 따라서 상속은 간단하고 컨텍스트에 정의된 두 헤더를 볼 수 있습니다 .server{}location{}http{}
% curl -i 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에서 수신하는 서버의 경우 블록 add_header에는 지시문이 있지만 server{}자식 location /블록에는 없습니다. 블록에 정의된 헤더는 컨텍스트 server{}에 정의된 두 헤더를 재정의합니다 http{}.
% curl -i 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 -i 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 OKlocation{}블록이 로컬에서 정의된 헤더와 함께 부모 컨텍스트에서 정의된 헤더를 보존하도록 하려면 블록 내에서 부모 헤더를 다시 정의해야 합니다 . 블록 location{}에서 한 일은 다음과 같습니다 location /correct.
% curl -i 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: proxy_buffering off지시
프록시 버퍼링은 NGINX에서 기본적으로 활성화됩니다( proxy_buffering지시문은 로 설정됨 on). 프록시 버퍼링은 NGINX가 서버에서 들어오는 응답을 내부 버퍼에 저장하고 전체 응답이 버퍼링될 때까지 클라이언트에 데이터를 전송하지 않는다는 것을 의미합니다. 버퍼링은 느린 클라이언트의 성능을 최적화하는 데 도움이 됩니다. NGINX는 클라이언트가 모든 응답을 검색하는 데 걸리는 시간 동안 응답을 버퍼링하기 때문에 프록시된 서버는 가능한 한 빨리 응답을 반환하고 다른 요청을 처리할 수 있습니다.
프록시 버퍼링이 비활성화되면 NGINX는 클라이언트로 전송하기 시작하기 전에 서버 응답의 첫 번째 부분만 버퍼링합니다. 기본적으로 한 메모리 페이지 크기(운영 체제에 따라 4KB 또는 8KB )의 버퍼에 있습니다. 이는 일반적으로 응답 헤더에 충분한 공간입니다. 그런 다음 NGINX는 클라이언트가 응답을 수신하는 대로 동기적으로 클라이언트에 응답을 전송하여 NGINX가 다음 응답 세그먼트를 수락할 때까지 서버가 유휴 상태로 대기하도록 합니다.
그래서 우리는 구성에서 얼마나 자주 볼 수 있는지에 놀랐습니다 proxy_buffering off. 아마도 클라이언트가 경험하는 대기 시간을 줄이기 위한 것일 수 있지만, 그 효과는 무시할 만하고 부작용은 많습니다. 프록시 버퍼링이 비활성화되면 속도 제한 및 캐싱이 구성하더라도 작동하지 않고 성능이 저하되는 등입니다.
프록시 버퍼링을 비활성화하는 것이 타당한 사용 사례는 소수에 불과합니다(예: 롱 폴링). 따라서 기본값을 변경하지 않는 것이 좋습니다. 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
실수 6: if지침 의 부적절한 사용
이 if지시어는 사용하기 까다롭습니다. 특히 location{}블록에서 그렇습니다. 종종 예상한 대로 작동하지 않고 세그폴트를 일으킬 수도 있습니다. 사실 너무 까다로워서 NGINX 위키에 If is Evil 이라는 제목의 기사가 있는데 , 저희는 문제에 대한 자세한 논의와 이를 피하는 방법을 알려드리기 위해 그곳으로 안내합니다.
일반적으로 블록 내에서 항상 안전하게 사용할 수 있는 유일한 지시문은 및 if{}입니다 . 다음 예에서는 헤더를 포함하는 요청을 감지하는 데 사용됩니다 (하지만 이는 테스트하려는 모든 조건일 수 있음). NGINX는 오류를 반환하고 @ error_430 이라는 명명된 위치에서 가로채고 요청을 b 라는 업스트림 그룹으로 프록시합니다 .returnrewriteifX‑Test430 (Request Header Fields Too Large)
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_nameb
map $http_x_test $upstream_name { default "b"; "" "a"; } # ... location / { proxy_pass http://$upstream_name; }실수 7: 과도한 Health Check
여러 가상 서버를 구성하여 동일한 업스트림 그룹에 요청을 프록시하는 것은 매우 일반적입니다(즉, proxy_pass여러 server{}블록에 동일한 지시문을 포함하는 것입니다). 이 상황에서 실수는 health_check모든 server{}블록에 지시문을 포함하는 것입니다. 이는 추가 정보를 제공하지 않고 업스트림 서버에 더 많은 부하를 발생시킬 뿐입니다.
당연한 말이지만, 해결책은 블록당 하나의 상태 점검만 정의하는 것입니다 . 여기서는 bupstream{} 라는 이름의 업스트림 그룹에 대한 상태 점검을 특별한 명명된 위치에 정의하고, 적절한 타임아웃과 헤더 설정을 완료합니다.
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; } }HTTP, TCP, UDP 및 gRPC 서버의 상태 점검에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
실수 8: 메트릭에 대한 보안되지 않은 액세스
NGINX 작업에 대한 기본 메트릭은 Stub Status 모듈 에서 사용할 수 있습니다 . NGINX Plus의 경우 NGINX Plus API를 사용하여 훨씬 더 광범위한 메트릭 세트를 수집할 수도 있습니다. 각각 or 블록에 stub_statusor 지시문을 포함하여 메트릭 수집을 활성화하면 해당 URL이 되어 메트릭을 볼 수 있습니다. ( NGINX Plus API 의 경우 메트릭을 수집하려는 NGINX 엔터티(가상 서버, 업스트림 그룹, 캐시 등)에 대한 공유 메모리 영역도 구성해야 합니다. NGINX Plus 관리자 가이드 의 지침을 참조하세요 .)apiserver{}location{}
일부 지표는 NGINX에서 프록시된 웹사이트나 앱을 공격하는 데 사용할 수 있는 민감한 정보이며, 사용자 구성에서 가끔 볼 수 있는 실수는 해당 URL에 대한 액세스를 제한하지 못하는 것입니다. 여기서는 지표를 보호할 수 있는 몇 가지 방법을 살펴보겠습니다. stub_status첫 번째 예에서 사용하겠습니다.
다음 구성을 사용하면 인터넷에 있는 모든 사람이 http://example.com/basic_status 에서 메트릭에 액세스할 수 있습니다 .
server { listen 80; server_name example.com; location = /basic_status { stub_status; } }HTTP 기본 인증을 사용하여 메트릭 보호
HTTP 기본 인증 으로 메트릭을 암호로 보호하려면 auth_basic및auth_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 all
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; } }두 가지 방법을 결합하다
두 방법을 결합하고 싶다면 어떨까요? 클라이언트가 비밀번호 없이 특정 주소에서 메트릭에 액세스하도록 허용하면서도 다른 주소에서 오는 클라이언트에 로그인을 요구할 수 있습니다. 이를 위해 지시문을 사용합니다 . NGINX에 HTTP 기본 인증 자격 증명으로 로그인하거나 사전 승인된 IP 주소를 사용하는 클라이언트에 액세스를 허용하도록 지시합니다. 추가 보안을 위해 특정 주소에서 오는 사람에게도 로그인을 요구하도록 설정할 수 있습니다 .satisfy anysatisfyall
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; } }NGINX Plus를 사용하면 동일한 기술을 사용하여 NGINX Plus API 엔드포인트( 다음 예에서는 http://monitor.example.com:8080/api/ )에 대한 액세스를 제한할 수 있고, http://monitor.example.com/dashboard.html 에 있는 라이브 활동 모니터링 대시보드에도 액세스할 수 있습니다 .
이 구성은 96.1.2.23/32 네트워크 또는 로컬호스트에서 오는 클라이언트에게만 암호 없이 액세스를 허용합니다. 지시문이 수준에서 정의되므로 server{}API와 대시보드에 동일한 제한이 적용됩니다. 참고로 매개 write=on변수는 api이러한 클라이언트도 API를 사용하여 구성을 변경할 수 있음을 의미합니다.
API와 대시보드 구성에 대한 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
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: ip_hash모든 트래픽이 동일한 /24 CIDR 블록에서 오는 경우 사용
이 ip_hash알고리즘은 클라이언트 IP 주소의 해시를 기반으로 블록의 서버 간에 트래픽을 로드 밸런싱합니다 upstream{}. 해싱 키는 IPv4 주소의 처음 세 옥텟 또는 전체 IPv6 주소입니다. 이 방법은 세션 지속성을 확립하는데, 이는 클라이언트의 요청이 서버를 사용할 수 없는 경우를 제외하고 항상 동일한 서버로 전달된다는 것을 의미합니다.
고가용성을 위해 구성된 가상 사설망에서 역방향 프록시로 NGINX를 배포했다고 가정해 보겠습니다. 다양한 방화벽, 라우터, 계층 4 로드 밸런서 및 게이트웨이를 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 {# …} }하지만 문제가 하나 있습니다. 모든 "차단" 장치가 동일한 10.10.0.0/24 네트워크에 있으므로 NGINX에서는 모든 트래픽이 해당 CIDR 범위의 주소에서 오는 것처럼 보입니다. 알고리즘이 ip_hashIPv4 주소의 처음 세 옥텟을 해시한다는 점을 기억하세요. 저희 배포에서 처음 세 옥텟은 모든 클라이언트에서 동일합니다(10.10.0). 따라서 해시는 모든 클라이언트에서 동일하고 트래픽을 다른 서버로 분산할 근거가 없습니다.
해결책은 해시 키로 변수를 사용 hash하는 대신 알고리즘을 사용하는 것입니다. 해당 변수는 전체 클라이언트 주소를 캡처하여 IPv4 주소의 경우 4바이트 , IPv6 주소의 경우 16바이트인 이진 표현으로 변환합니다. 이제 해시는 각 가로채기 장치에 따라 다르고 로드 밸런싱은 예상대로 작동합니다.$binary_remote_addr
또한 기본값 대신 케타마consistent 해싱 방법을 사용하는 매개변수를 포함합니다 . 이렇게 하면 서버 세트가 변경될 때 다른 업스트림 서버로 다시 매핑되는 키 수가 크게 줄어들어 캐싱 서버의 캐시 적중률이 높아집니다.
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: Upstream 그룹의 이점을 활용하지 못함
포트 3000에서 수신하는 단일 NodeJS 기반 백엔드 애플리케이션의 역방향 프록시로서 가장 간단한 사용 사례 중 하나에 NGINX를 사용한다고 가정해 보겠습니다. 일반적인 구성은 다음과 같습니다.
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 워커 프로세스가 업스트림 서버에 대한 구성 및 상태 정보에 액세스할 수 있는 공유 메모리 영역을 설정합니다. 여러 업스트림 그룹이 영역을 공유할 수 있습니다. NGINX Plus를 사용하면 영역을 통해 NGINX Plus API를 사용하여 업스트림 그룹의 서버와 NGINX를 다시 시작하지 않고도 개별 서버의 설정을 변경할 수도 있습니다. 자세한 내용은 NGINX Plus 관리자 가이드를 참조하세요 .
이 server지시문에는 서버 동작을 조정하는 데 사용할 수 있는 여러 매개변수가 있습니다. 이 예에서 NGINX가 서버가 건강하지 않고 따라서 요청을 수락할 수 없음을 결정하는 데 사용하는 조건을 변경했습니다. 여기서는 통신 시도가 2초마다 한 번이라도 실패하면 (기본값인 10초 마다 한 번 ) 서버가 건강하지 않은 것으로 간주합니다.
이 설정을 지시문과 결합하여 proxy_next_upstreamNGINX가 실패한 통신 시도로 간주하는 것을 구성합니다. 이 경우 요청을 업스트림 그룹의 다음 서버로 전달합니다. 기본 오류 및 시간 초과 조건에 NGINX가 업스트림 서버의 http_500HTTP 500 (Internal Server Error)코드를 실패한 시도로 간주하도록 추가합니다.
이 keepalive지시문은 각 작업자 프로세스의 캐시에 보존된 업스트림 서버에 대한 유휴 keepalive 연결 수를 설정합니다. 우리는 이미 실수 3: 업스트림 서버에 대한 keepalive 연결을 활성화하지 않음 에서 이점에 대해 논의했습니다 .
NGINX Plus를 사용하면 업스트림 그룹으로 추가 기능을 구성할 수 있습니다.
위에서 언급했듯이 NGINX Open Source는 시작 중에 서버 호스트 이름을 IP 주소로 단 한 번만 확인합니다. resolveserver 지시문에 대한 매개변수를 사용하면 NGINX Plus가 업스트림 서버의 도메인 이름에 해당하는 IP 주소의 변경 사항을 모니터링하고 재시작할 필요 없이 업스트림 구성을 자동으로 수정할 수 있습니다.
이 service매개변수는 NGINX Plus가 포트 번호, 가중치 및 우선순위에 대한 정보를 포함하는 DNS 레코드를 사용할 수 있도록 합니다 SRV. 이는 서비스의 포트 번호가 종종 동적으로 할당되는 마이크로서비스 환경에서 중요합니다.
서버 주소를 확인하는 방법에 대한 자세한 내용은 블로그의 NGINX 및 NGINX Plus에서 서비스 검색에 DNS 사용을 참조하세요.
서버 지시문에 대한 매개 변수 slow_start를 사용하면 NGINX Plus가 새로이 건강하다고 간주되고 요청을 수락할 수 있는 서버로 보내는 요청의 양을 점진적으로 늘릴 수 있습니다. 이를 통해 서버를 압도하고 다시 실패하게 만들 수 있는 갑작스러운 요청 홍수를 방지할 수 있습니다.
이 queue지시어를 사용하면 NGINX Plus에서 요청을 처리할 상위 서버를 선택할 수 없는 경우 클라이언트에 즉시 오류를 반환하는 대신 요청을 대기열에 넣을 수 있습니다.
위 내용과 같이 NGINX Plus 를 활용하여 Demo 가 필요하시면 하단의 전문가에게 상담받기 버튼을 클릭해주세요
전문가에게 상담받기