소개
Nginx는 리버스 프록시, 메일 프록시, 로드 밸런서 및 HTTP 캐시로도 사용되는 고성능 웹 서버입니다. Nginx는 무료 오픈 소스이므로 누구나 다운로드하여 자신의 서버 환경에서 사용할 수 있습니다.
이미 웹사이트를 서비스하기 위해 Nginx를 사용해 보셨을 수도 있습니다. 이 튜토리얼에서는 Nginx의 다른 기능들에 대해 논의할 것입니다. Nginx의 HTTP 프록시 처리 기능은 요청을 백엔드 HTTP 서버로 전달하여 처리할 수 있도록 합니다. 이 기능을 사용하면 여러 백엔드 서버를 구성할 수 있습니다. 이를 통해 클라이언트 요청이 급증할 때 필요에 따라 인프라를 확장할 수 있습니다.
튜토리얼을 진행하면서 Nginx의 로드 밸런싱 속성, 버퍼링, 그리고 캐싱 응답을 사용하여 서버 성능을 향상시키고 클라이언트에게 더 나은 경험을 보장하는 방식으로 인프라를 확장하는 방법에 대해 배우게 됩니다. 시작해 봅시다!
가장 먼저, Nginx를 시작하려면 당사의 Ubuntu 서버에 Nginx를 설치하는 방법에 대한 튜토리얼을 확인해 보십시오..
프록시 처리에 대한 일반 정보
웹 서버에 대한 지식이 웹사이트 요청을 처리하고 웹 페이지를 제공하는 것에만 국한되어 있다면, 왜 요청을 프록시 처리해야 하는지 궁금할 수 있습니다. 아래에서 그 이유를 설명해 드리겠습니다.
Nginx에서 다른 서버로 요청을 프록시 처리하는 한 가지 이유는 인프라의 확장성을 지원하기 위해서입니다. Nginx는 기본적으로 많은 연결을 동시에 처리합니다. 이 덕분에 클라이언트의 첫 번째 접점이 되기에 완벽합니다. 그런 다음 요청을 다양한 백엔드 서버로 전달하여 클라이언트 요청의 실제 처리를 담당하게 할 수 있습니다. 이것이 부하를 분산시키는 방법입니다. 따라서 인프라를 최대한 확장할 수 있도록 보장합니다. 또한 다른 서버가 계속해서 요청을 처리하는 동안 유지 관리를 위해 특정 서버를 내릴 수도 있습니다.
다른 서버로 요청을 프록시 처리하려는 두 번째 이유는 실제 프로덕션 환경에서 클라이언트의 요청을 직접 처리하기에 적합하지 않은 애플리케이션 서버를 사용하는 경우입니다. 웹 서버를 포함한 여러 프레임워크는 Nginx만큼 높은 성능을 내기에 적합하지 않습니다. Nginx를 진입점으로 두고 이러한 저성능 서버로 요청을 프록시 처리하면 사용자가 더 나은 경험을 하도록 보장할 수 있습니다. 또한 애플리케이션의 보안을 강화할 수 있습니다.
Nginx에서 요청을 프록시 처리하는 과정은 Nginx 서버로부터의 요청을 조작하여 실제 처리를 위해 다른 백엔드 서버로 전달하는 것을 포함합니다. 다른 백엔드 서버가 요청을 처리하면 그 결과를 다시 Nginx로 전달합니다. 그런 다음 Nginx는 그 결과를 클라이언트에 응답으로 보냅니다. 이 경우 클라이언트는 웹 브라우저이거나 모바일 웹 앱일 수도 있습니다. 다른 백엔드 서버는 인터넷을 통해 공개적으로 액세스할 수 없는 로컬 서버, 원격 서버 또는 Nginx 서버 블록 구성 내의 다른 가상 서버일 수도 있습니다. Nginx가 요청을 프록시 처리하는 이러한 다른 서버들을 업스트림 서버.
Nginx는 HTTP(S), Memcached, SCGI, FastCGI, 그리고 uWSGI를 포함한 여러 프로토콜을 사용하여 통신하는 서버로 요청을 프록시 처리할 수 있습니다. 각 프로토콜 유형에 대해 일련의 지시어가 있습니다. 이 튜토리얼의 초점은 HTTP 프로토콜입니다. Nginx는 요청과 메시지 구성 요소를 업스트림 서버가 해석하고 처리할 수 있는 형식으로 파싱합니다.
기본 HTTP 프록시 패스 분석
가장 단순한 유형의 프록시는 HTTP를 통해 통신하는 단일 서버로 요청을 전달하는 것입니다. 이러한 유형의 프록시는 일반적으로 "프록시 패스(proxy pass)"라고 하며, Nginx 설정 파일 내에서 적절하게 명명된 proxy_pass 지시어에 의해 처리됩니다.
proxy_pass 지시어는 location 블록 내에 나타납니다. 또한 location 컨텍스트의 블록 내부 및 limit_except 컨텍스트 내에도 존재합니다. 요청이 내부에 proxy_pass 지시어가 있는 location과 일치하면, 요청은 지시어가 지정하는 URL로 이동합니다. 아래는 설정 스니펫의 예시입니다:
|
1 2 3 4 |
listen 80; location / { proxy_pass http://127.0.0.1:3000; } |

위의 예에서 80번 포트로의 요청은 localhost:3000으로 전달됩니다.

위의 스크린샷은 localhost에 접속하려고 할 때 표시되는 기본 Nginx 페이지를 보여줍니다. proxy pass 지시문이 적용된 상태에서 Nginx 서버를 재시작하면 모든 요청이 3000번 포트로 전달됩니다. 아래 이미지에서 볼 수 있듯이 3000번 포트에서 데모 애플리케이션이 실행 중이며, 포트를 지정하지 않고 localhost에서 직접 접속할 수 있습니다.

다음 예에서는 proxy_pass 정의의 서버 끝에 URI가 지정되지 않았습니다. 이 패턴에 부합하는 정의의 경우, 클라이언트가 요청한 URI가 그대로 업스트림 서버로 전달됩니다.
|
1 2 3 |
location /match/url/here { proxy_pass http://example.com; } |
예를 들어, 이 블록이 /match/url/here에 대한 요청을 처리할 때, 요청 URI는 http://example.com/match/url/here로 example.com 서버에 전달됩니다.
아래는 대체 구성 스니펫의 예입니다.
|
1 2 3 |
location /match/url/here { proxy_pass http://example.com/new/url/prefix; } |
위의 스니펫에서 볼 수 있듯이, 프록시 서버 끝에 URI 세그먼트를 new/url/prefix로 정의했습니다. proxy_pass 정의에 URI를 정의하면, 업스트림 서버로 처리를 요청할 때 location 정의와 일치하는 요청 부분이 이 URI로 대체됩니다.
예를 들어, Nginx 서버에서 /match/url/here에 대한 요청은 업스트림 서버에 http://example.com/new/url/here로 전달됩니다. /match/url이 /new/url로 대체됩니다. 이 점을 염두에 두시기 바랍니다.
일부 경우에는 위와 같이 URI를 전달하는 것이 불가능합니다. 이러한 경우 Nginx는 proxy_pass 정의 끝에 있는 URI를 무시합니다. 결과적으로 클라이언트의 원래 URI 또는 다른 지시문이 수정한 URI가 업스트림 서버로 전달됩니다.
정규 표현식이 location과 일치하는 경우가 그 예입니다. Nginx는 URI의 어느 부분이 표현식과 일치하는지 확인하지 못할 수 있습니다. 따라서 원래의 클라이언트 요청 URI를 보냅니다. 이로 인해 동일한 블록 내에서 클라이언트 URI의 재작성(rewriting) 및 처리가 발생합니다. 이러한 경우 재작성된 URI가 전달됩니다.
Nginx는 헤더를 어떻게 처리하나요?
헤더는 서버가 요청을 처리하는 방식에 있어 매우 중요합니다. 일부 헤더에는 인증 정보가 포함되어 있을 수 있습니다. 따라서 Nginx 프록시가 헤더를 어떻게 처리하는지 이해해야 합니다. Nginx에서 업스트림 서버로 보내는 프록시 요청은 클라이언트로부터 직접 들어온 요청과는 다르게 보입니다. 이러한 차이 중 일부는 프록시 요청과 함께 전달되는 헤더로 인해 발생합니다.
요청을 프록시하는 동안 Nginx는 클라이언트로부터 받은 요청 헤더를 조정합니다. 이러한 조정 사항에는 다음이 포함됩니다.
-
모든 빈 헤더를 제거합니다. 빈 헤더는 요청의 크기만 키울 뿐이므로 업스트림 서버로 전달할 필요가 없습니다.
-
밑줄(_)이 포함된 헤더는 기본적으로 유효하지 않은 것으로 간주되어 요청에서 제거됩니다. 이 동작을 변경하여 Nginx가 밑줄이 포함된 헤더를 유효한 것으로 해석하도록 허용하려면 underscores_in_headers 지시문을 “on”으로 지정할 수 있습니다. 그렇지 않으면 클라이언트의 이러한 헤더는 업스트림 서버에 도달하지 못합니다.
-
“Host” 헤더는 $proxy_host 변수에 지정된 값으로 재작성됩니다. 이는 proxy_pass 지시문에 지정된 업스트림 서버의 IP 주소 또는 이름과 포트 번호입니다.
-
“Connection” 헤더 값이 “close”로 변경됩니다. Connection 헤더는 두 당사자 간에 설정된 특정 연결에 대한 정보를 보유합니다. Nginx가 이 값을 close로 설정하면, 원래 요청에 대한 응답이 완료되면 연결이 닫힐 것임을 업스트림 서버에 알리는 것이므로 지속적인 연결(persistent connection)을 기대해서는 안 됩니다.
위에서 설명한 프록시 요청 헤더 조정 사항에서 다음과 같은 점들을 확인할 수 있습니다.
-
업스트림 서버로 헤더를 전달하지 않으려면, 해당 헤더를 빈 문자열로 설정하여 요청에서 완전히 제거할 수 있습니다.
-
업스트림 서버의 애플리케이션이 비표준 헤더를 처리하는 경우, 헤더에 언더스코어(_)가 포함되지 않도록 하십시오. 필요에 따라 구성에서 underscores_in_headers 지시문을 “on”으로 설정할 수 있습니다(IP 주소/포트 조합에 대한 기본 서버 선언 컨텍스트 또는 HTTP 컨텍스트에서 유효함). 이렇게 하면 헤더가 유효하지 않은 것으로 플래그 지정되지 않으므로 실제로 업스트림 서버로 전달됩니다.
-
“Host” 헤더는 대부분의 프록시 상황에서 매우 중요합니다. 기본적으로 proxy_pass 사양에서 가져온 도메인 이름 또는 IP 주소와 포트를 포함하는 변수인 $proxy_host 값으로 설정됩니다. 이 주소는 기본적으로 선택되며 연결 정보에서 직접 가져옵니다. 이는 Nginx가 업스트림 서버가 응답할 것이라고 보장할 수 있는 유일한 주소입니다.
다음은 “Host” 헤더에 가장 흔히 사용되는 값입니다.
-
$host – 요청 라인 자체의 호스트 이름, 클라이언트 요청의 “Host” 헤더, 또는 요청과 일치하는 서버 이름 순으로 우선순위에 따라 설정되는 변수입니다.
-
$http_host – “Host” 헤더를 클라이언트 요청의 “Host” 헤더로 설정하는 변수입니다. 클라이언트 요청의 헤더는 항상 Nginx에서 변수로 사용할 수 있습니다. 이러한 변수는 $http_ 접두사로 시작하고 그 뒤에 소문자로 된 헤더 이름이 옵니다. $http_host 변수는 대부분 잘 작동하지만, 클라이언트 요청에 유효한 “Host” 헤더가 없는 경우 전달이 실패할 수 있습니다.
-
$proxy_host – “Host” 헤더를 proxy_pass 사양에서 가져온 도메인 이름 또는 IP 주소와 포트 조합으로 설정하는 변수입니다. 이는 Nginx 관점에서의 기본 동작이므로 안전한 것으로 간주됩니다. 그러나 서버가 요청을 올바르게 처리하는 데 필요한 값이 아닐 수도 있습니다.
대부분의 구성에서는 “Host” 헤더를 $host 변수로 설정합니다. 이는 매우 유연하며 업스트림 서버에 정확하게 채워진 헤더를 제공합니다.
헤더 설정 및 수정
proxy_set_header 지시문을 사용하면 프록시 연결에 대한 헤더를 설정하거나 수정할 수 있습니다. 앞서 논의한 “Host” 헤더에서 프록시 요청에 흔히 사용되는 추가 헤더를 수정하고 추가하기 위해 다음과 같이 할 수 있습니다.
|
1 2 3 4 5 6 7 8 |
location /match/here { proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $schema; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://example.com/new/prefix; } |
위의 구성 스니펫에서는 “Host” 헤더를 요청된 원래 호스트에 대한 정보가 포함된 $host 변수로 설정합니다. 클라이언트의 원래 요청 스키마(HTTP 또는 HTTPS 요청일 수 있음)에 대한 정보로 X-Forwarded-Proto 헤더를 설정합니다.
클라이언트의 실제 IP 주소를 X-Real-IP로 전달합니다. 이를 통해 업스트림 서버는 클라이언트의 IP 발신지를 기반으로 적절한 결정을 내리거나 로그를 저장할 수 있습니다. X-Forwarded-For 헤더에는 클라이언트가 이 지점에 도달하기 전에 거쳐간 프록시 서버들의 모든 IP 주소 목록이 포함되어 있습니다. 위의 코드 스니펫에서는 이를 $proxy_add_x_forwarded_for 변수로 설정했습니다. 이 변수는 클라이언트로부터 가져온 원래 X-Forwarded-For 헤더의 값을 취하고 끝에 Nginx 프록시 서버의 IP 주소를 추가합니다.
proxy_set_header 지시문이 둘 이상의 location에서 참조되도록 하려면, 이를 server 또는 http 컨텍스트로 이동할 수 있습니다. 아래의 구성 스니펫을 참고하십시오:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location /match/here { proxy_pass http://example.com/new/prefix; } location /different/match { proxy_pass http://example.com; } |
프록시 연결의 부하 분산을 위한 업스트림 컨텍스트 정의
여기까지 단일 백엔드 업스트림 서버에 대한 간단한 http 프록시를 수행하는 방법을 이해하셨을 것입니다. 다행히도 Nginx를 사용하면 요청을 전달하여 처리할 백엔드 서버 풀을 정의함으로써 이러한 구성을 확장할 수 있습니다.
Nginx는 서버 풀을 정의하는 데 사용되는 upstream이라는 지시어를 제공합니다. 지시어의 구성 내에서는 클라이언트의 요청을 처리할 수 있는 서버만 지정해야 합니다. 프록시 서버로서의 Nginx는 최소한의 노력으로 인프라를 확장할 수 있게 해줍니다. upstream 지시어는 Nginx 구성의 http 컨텍스트 내에 지정되어야 합니다.
다음은 upstream 지시어를 보여주는 예시입니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
upstream several_backend_hosts { server host1.example.com; server host2.example.com; server host3.example.com; } server { listen 80; server_name example.com; location /proxy-me { proxy_pass http://several_backend_hosts; } } |
위의 구성 코드 스니펫에서는 several_backend_hosts라는 업스트림 컨텍스트를 정의했습니다. 정의된 컨텍스트 이름은 이제 proxy_pass 내에서 사용할 수 있습니다. 예시에서 보듯이 일반 도메인처럼 사용할 수 있습니다. server 블록 내에서 우리는 example.com/proxy-me/…로 들어오는 모든 요청을 upstream 지시어를 사용하여 정의한 풀(이 경우 several_backend_hosts)로 전달합니다. 풀 내에서 수신 요청을 처리할 호스트는 구성 가능한 알고리즘을 적용하여 선택됩니다. 기본적으로 이 선택은 라운드 로빈 (순환) 프로세스를 따릅니다 – 각 요청은 차례대로 다른 호스트로 라우팅됩니다.
업스트림 부하 분산 알고리즘을 변경하는 방법
위에서 강조했듯이, 선택 프로세스는 라운드 로빈 프로세스를 따릅니다. 이 섹션에서는 업스트림 풀에서 사용하는 부하 분산 알고리즘을 수정하는 방법을 살펴보겠습니다. 알고리즘을 수정하려면 아래에 정의된 대로 upstream 컨텍스트 내에 다른 지시어나 플래그를 포함합니다:
-
(라운드 로빈) – 다른 업스트림 부하 분산 지시어가 지정되지 않은 경우, 기본적으로 upstream 컨텍스트에 정의된 각 서버에 요청이 순차적으로 차례대로 전달됩니다.
-
least_conn – 이 지시어는 활성 연결 수가 가장 적은 백엔드 서버를 선택하도록 업스트림에 지시합니다. 이는 한 백엔드 서버에 대한 연결이 한동안 유지될 수 있는 상황에 적용할 수 있습니다.
-
hash – 이 지시어는 memcached 프록시에서 흔히 사용됩니다. 무작위로 제공되는 해시 키 값에 따라 연결이 백엔드 서버로 전달됩니다. 해시 키 값은 변수, 텍스트 또는 이 둘의 조합일 수 있습니다. hash는 해시에 사용할 키 역할을 하기 위해 사용자의 입력을 요구하는 유일한 부하 분산 방법입니다.
-
ip_hash – 이 지시어는 클라이언트의 IP 주소를 기반으로 요청을 다른 서버에 분배하도록 업스트림에 지시합니다. IP 주소의 처음 세 옥텟이 요청을 처리할 서버를 결정하는 키가 됩니다. 이 지시어의 장점은 클라이언트가 매번 동일한 서버로 연결되는 경향이 있어 세션 일관성이 보장된다는 것입니다.
다음은 upstream 컨텍스트에 부하 분산 알고리즘 지시어를 추가하는 방법의 예시입니다:
|
1 2 3 4 5 6 7 8 |
upstream several_backend_hosts { least_conn; server host1.example.com; server host2.example.com; server host3.example.com; } |
위의 코드 스니펫에서 Nginx는 들어오는 요청을 처리하기 위해 연결 수가 가장 적은 서버 중 하나를 선택합니다. ip_hash 지시어도 동일한 구문을 따릅니다. hash 지시어의 경우 해싱할 키를 직접 제공해야 하며, 다음은 그 예시입니다:
|
1 2 3 4 5 6 7 8 |
upstream several_backend_hosts { hash $remote_addr$remote_port consistent; server host1.example.com; server host2.example.com; server host3.example.com; } |
여기서 사용되는 해시는 클라이언트’의 IP 주소와 포트의 결과가 됩니다. 선택적 매개변수인 consistent는 ketama 일관된 해싱 알고리즘을 구현합니다. 이를 통해 업스트림 서버를 변경하더라도 캐시에 미치는 영향을 최소화할 수 있습니다.
부하 분산을 위한 서버 가중치 지정 방법
기본적으로 백엔드 서버를 선언할 때 각 서버의 가중치는 동일하게 설정됩니다. 이는 각 서버가 동일한 양의 부하를 처리할 수 있는 리소스와 기능을 가지고 있다고 가정하는 것이며, 물론 이는 upstream 컨텍스트에서 지정한 부하 분산 알고리즘을 고려한 것입니다. 이 기본 동작을 변경하려면 선언 중에 각 서버에 다른 가중치를 설정할 수 있습니다. 다음 예시를 살펴보겠습니다:
|
1 2 3 4 5 |
upstream backend_hosts { server host1.example.com weight=2; server host2.example.com; server host3.example.com; } |
이 예시에서 host1.example.com은 다른 두 서버보다 두 배 많은 트래픽을 수신하게 됩니다. 각 서버의 가중치는 기본적으로 1입니다.
버퍼를 사용하여 백엔드 서버 부하 줄이기
서버 구성에서 프록시를 설정할 때, 프로세스에 서버를 더 추가하는 것이 성능에 미칠 영향에 대해 우려할 수 있습니다. 다행히도 Nginx에는 이러한 성능 문제를 완화하는 데 도움이 되는 버퍼링 및 캐싱 기능이 제공됩니다.
다른 서버로 프록시할 때 서로 다른 두 연결의 속도는 클라이언트의 경험에 확실히 영향을 미칩니다:
-
첫 번째 연결은 클라이언트에서 Nginx 프록시로의 연결입니다.
-
두 번째 연결은 Nginx 프록시에서 백엔드 업스트림 서버로의 연결입니다.
Nginx는 필요에 따라 두 연결 중 하나를 최적화하는 데 도움이 되도록 동작을 조정할 수 있습니다.
버퍼를 제거하면 업스트림 백엔드의 데이터가 Nginx 프록시에서 즉시 클라이언트로 전송되기 시작합니다. 클라이언트의 속도가 빠르다는 것을 알고 있다면, 버퍼링을 완전히 꺼서 데이터가 클라이언트에 충분히 빠르게 도달하도록 할 수 있습니다. 버퍼를 켜두면 Nginx 프록시는 백엔드 업스트림 서버로부터 받은 응답 데이터를 임시로 저장합니다. 그런 다음 클라이언트의 속도에 맞춰 데이터를 전송합니다. Nginx가 버퍼에 응답을 저장하면 백엔드 서버와의 연결을 닫을 수 있습니다. 그런 다음 클라이언트가 지원하는 속도로 클라이언트에 데이터를 배포합니다. 동시에 백엔드 서버는 들어오는 다른 요청을 계속 처리할 수 있게 됩니다.
기본적으로 Nginx는 버퍼링이 켜져 있습니다. 이는 클라이언트의 연결 속도를 알 수 없기 때문입니다. 클라이언트는 더 느릴 수 있는 다양한 연결을 사용하는 경향이 있습니다. 아래에서는 Nginx의 버퍼링 동작을 조정하기 위해 지정할 수 있는 다양한 지시어를 정의합니다. 지시어는 http, server 또는 location 컨텍스트에서 정의할 수 있지만, 크기 조정 지시어는 요청당 구성된다는 점에 유의해야 합니다. 따라서 절대적으로 필요한 수준 이상으로 늘리면 들어오는 클라이언트 요청이 너무 많을 때 서버 성능에 영향을 미칠 수 있습니다. 지시어는 다음과 같습니다:
-
proxy_buffering – 특정 컨텍스트 및 하위 컨텍스트에 대해 버퍼링 활성화 여부를 제어하는 지시어입니다. proxy_buffering의 기본 설정은 “on”입니다.
-
proxy_buffer_size – 백엔드 서버의 응답에서 발견된 헤더를 저장하기 위한 버퍼 크기를 지정하는 지시어입니다. 헤더는 백엔드 서버 응답의 첫 번째 부분을 구성합니다. 이러한 헤더의 버퍼링은 나머지 응답과 별개로 처리됩니다. 기본적으로 이 버퍼의 설정 크기는 proxy_buffers의 크기와 동일합니다. 그러나 헤더 정보가 작은 경우 크기를 더 낮은 값으로 설정할 수 있습니다.
-
proxy_buffers – 프록시된 응답에 대한 버퍼의 개수(첫 번째 인수) 및 크기(두 번째 인수)를 제어하는 지시어입니다. 기본 설정은 하나의 메모리 페이지(4k 또는 8k)와 동일한 크기의 버퍼 8개를 지정합니다. 버퍼 개수를 늘려 더 많은 정보의 버퍼링을 허용할 수 있습니다.
-
proxy_max_temp_file_size – 디스크의 임시 파일에 대해 요청당 최대 크기를 지정하는 지시어입니다. 업스트림 응답이 너무 커서 버퍼에 들어가지 않을 때 임시 파일이 생성됩니다.
-
proxy_busy_buffers_size – “클라이언트 준비 완료(client-ready)” 상태로 전달되어 사용 중(busy)으로 분류될 수 있는 버퍼의 최대 크기를 지정하는 지시어입니다. 클라이언트는 한 번에 하나의 버퍼에서만 데이터를 읽을 수 있습니다. 그러나 버퍼는 클라이언트에 일괄 전송하기 위해 대기열에 있습니다. 이 지시어를 수정하여 이 상태가 될 수 있도록 허용된 버퍼 공간의 크기를 지정할 수 있습니다.
-
proxy_temp_file_write_size – 백엔드 업스트림 서버의 응답이 너무 커서 구성된 버퍼에 들어가지 않을 때 Nginx가 임시 파일에 한 번에 기록할 데이터 양을 지정하는 지시어입니다.
-
proxy_temp_path – 업스트림 백엔드 서버의 응답이 너무 커서 구성된 버퍼에 들어가지 않을 때 Nginx가 임시 파일을 저장해야 하는 디스크 상의 경로를 지정하는 지시어입니다.
Nginx는 사용자 정의가 매우 용이하며, 버퍼링 동작을 미세 조정할 수 있는 여러 지시어를 제공합니다. 대부분의 경우 기본값으로도 잘 작동합니다. 동시에 맞춤형 구현을 위해 이러한 값 중 일부를 조정할 수 있다는 점을 알아두면 좋습니다. 주로 proxy_buffers 및 proxy_buffer_size 지시어를 조정하게 될 것입니다.
다음은 각 업스트림 요청에 대해 사용 가능한 프록시 버퍼 수를 늘리는 동시에 헤더를 저장하는 버퍼의 크기를 줄이는 예시입니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
server { proxy_buffering on; proxy_buffer_size 1k; proxy_buffers 24 4k; proxy_busy_buffers_size 8k; proxy_max_temp_file_size 2048m; proxy_temp_file_write_size 32k; location / { proxy_pass http://example.com; } } |
버퍼링을 완전히 비활성화하여 빠른 클라이언트에게 데이터를 더 빠르게 제공하는 방법을 살펴보겠습니다. 클라이언트가 충분히 빠르지 않은 경우 Nginx는 자동으로 버퍼를 사용합니다. 그러나 버퍼 풀을 기다리는 대신 먼저 데이터를 클라이언트로 플러시합니다. 이 구성에는 단점이 있습니다. 이 구성은 느린 클라이언트가 모든 응답 데이터를 수신할 때까지 업스트림 서버 연결이 열려 있는 상태를 유지하게 만듭니다. 버퍼링이 “off”로 설정되면 proxy_buffer_size 지시어에 의해 정의된 버퍼만 사용됩니다. 다음은 버퍼링을 off로 지정하는 방법을 보여주는 코드 조각입니다.
|
1 2 3 4 5 6 7 8 9 |
server { proxy_buffering off; proxy_buffer_size 4k; location / { proxy_pass http://example.com; } } |
-
고가용성(HA) 인프라 구성 (선택적 설정)
Nginx 프록시 구성에 이중화된 로드 밸런서 세트를 추가하여 구성을 더 견고하게 만들고 결과적으로 고가용성을 확보할 수 있습니다. 고가용성(HA) 설정은 단일 장애점(SPOF)이 없는 인프라입니다. 로드 밸런서는 이 구성의 일부입니다. 둘 이상의 로드 밸런서를 사용하면 하나의 로드 밸런서가 고장 나거나 유지 관리를 위해 오프라인 상태가 되더라도 발생할 수 있는 다운타임을 방지할 수 있습니다.
응답 시간을 단축하기 위해 Nginx 프록시 캐싱을 구현하는 방법
이전 섹션에서는 버퍼링을 사용하여 백엔드 서버가 더 많은 요청을 처리할 수 있도록 여유를 주는 방법에 대해 논의했습니다. Nginx에는 백엔드의 응답 데이터를 캐시할 수 있는 또 다른 기능이 있습니다. 이 기능을 사용하면 들어오는 모든 요청에 대해 업스트림에 연결할 필요가 완전히 없어집니다.
프록시 캐시 구현하기
proxy_cache_path 지시문을 사용하면 프록시된 콘텐츠를 저장하는 데 사용할 디스크 영역을 지정하여 캐시를 설정할 수 있습니다. proxy_cache_path 지시문은 http 컨텍스트에서 정의됩니다.
아래의 구성 코드 스니펫은 캐싱 시스템을 구현하는 방법의 예입니다.
|
1 2 3 4 5 6 |
http { proxy_cache_path /var/lib/nginx/cache levels=1:2 keys_zone=backendcache:8m max_size=50m; proxy_cache_key "$scheme$request_method$host$request_uri$is_args$args"; proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; } |
이 코드 스니펫에서는 proxy_cache_path 지시문을 사용하여 캐시를 보관할 파일 시스템의 디렉터리를 정의했습니다. 이 경우 설정한 디렉터리는 /var/lib/nginx/cache입니다. 원하는 디렉터리 경로를 자유롭게 정의할 수 있습니다. 다음 명령을 사용하여 올바른 권한과 소유권을 가진 선택한 디렉터리를 생성하십시오.
|
1 2 3 |
sudo mkdir -p /var/lib/nginx/cache sudo chown www-data /var/lib/nginx/cache sudo chmod 700 /var/lib/nginx/cache |
코드 스니펫에서 levels= 매개변수는 캐시의 구성을 지정합니다. Nginx는 키의 값(proxy_cache_key 지시문을 사용하여 지정됨)을 해싱하여 캐시 키를 생성합니다. 우리가 지정한 레벨(1:2)은 단일 문자 디렉터리(즉, 해시된 값의 마지막 문자)와 두 문자 하위 디렉터리(해시된 값의 끝에서 다음 두 문자에서 가져옴)가 생성됨을 나타냅니다. 대부분의 경우 이 부분은 신경 쓰지 않아도 됩니다. 하지만 이것이 Nginx가 관련 값을 빠르게 찾는 데 어떻게 도움이 되는지 알아두면 좋습니다.
keys_zone= 매개변수는 캐시 영역의 이름을 정의하며, 이 예제에서는 backendcache로 지정했습니다. 여기서는 저장할 메타데이터의 양도 정의합니다. 이 예제에서는 8MB의 키를 저장하고 있습니다. Nginx는 메가바이트당 대략 8000개의 항목을 저장할 수 있습니다. max_size 매개변수는 실제 캐시된 데이터의 최대 크기를 지정하며, 이 예제에서는 50MB입니다.
사용된 proxy_cache_key 지시문도 확인해야 합니다. 이 지시문은 캐시된 값을 저장하는 데 사용할 키를 지정합니다. 요청이 캐시 내에 존재하는지 확인하기 위해 동일한 키를 사용합니다. 이 키는 스킴(http 또는 https), HTTP 요청 메서드, 요청된 호스트 및 URI의 조합으로 지정되었습니다.
또한 proxy_cache_valid 지시문을 사용했습니다. 이 지시문은 다양한 상태 코드에 대해 여러 번 지정할 수 있습니다. 상태 코드에 따라 값을 저장할 기간을 지정할 수 있습니다. 코드 스니펫에서는 성공 코드의 경우 10분, 404 응답의 경우 1분을 지정했습니다.
캐시 영역을 구성했으므로 다음 단계는 Nginx에 캐시를 언제 사용할지 알려주어 구성을 적용하는 것입니다. 아래는 이 캐시의 사용을 구현하는 방법을 보여주는 구성 스니펫입니다:
|
1 2 3 4 5 6 7 8 9 |
server { location /proxy-me { proxy_cache backendcache; proxy_cache_bypass $http_cache_control; add_header X-Proxy-Cache $upstream_cache_status; proxy_pass http://backend; } } |
proxy_cache 지시문에서 이 컨텍스트에 backendcache 캐시 영역을 사용하도록 지정했습니다. 캐시 구성에서 다른 이름을 선택한 경우 여기에서 해당 이름을 변경하면 됩니다. 모든 유효한 항목에 대해 Nginx는 백엔드 업스트림 서버로 요청을 전달하기 전에 캐시를 확인합니다.
우리는 $http_cache_control 변수를 사용하도록 proxy_cache_bypass 지시문을 정의합니다. 이 변수는 서버가 캐시된 응답으로 응답할지, 아니면 리소스의 캐시되지 않은 최신 버전으로 응답할지 여부를 알려줍니다. 이 지시문을 적절하게 설정하면 Nginx가 클라이언트의 다양한 유형의 수신 요청을 올바르게 처리할 수 있습니다.
X-Proxy-Cache라는 추가 헤더도 지정됩니다. 이 헤더는 $upstream_cache_status 변수의 값을 가집니다. 이는 요청 결과가 캐시 히트(hit)인지, 캐시 미스(miss)인지, 아니면 캐시가 명시적으로 우회(bypass)되었는지에 대한 정보를 제공합니다. 이러한 정보는 클라이언트에게 유용할 수 있으며 애플리케이션 디버깅 중에 매우 중요합니다.
캐싱 결과에 관한 중요한 사항
캐싱은 프록시 서버의 성능을 크게 향상시키지만, 캐싱을 구현할 때는 다음 사항에 유의해야 합니다.
한 사용자’의 개인 정보와 관련된 데이터는 다른 사용자가 해당 데이터를 볼 수 있는 상황을 방지하기 위해 캐싱되지 않아야 합니다.
백엔드 서버는 웹사이트의 모든 동적 요소를 고려해야 합니다. 다양한 목적을 달성하기 위해 응답에 지정할 수 있는 여러 Cache-Control 헤더가 있습니다. 이에 대해 알아보겠습니다:
-
no-cache – 응답을 제공하기 전에 백엔드에서 데이터가 변경되었는지 프록시가 확인해야 함을 지정합니다. 이는 동적이고 중요한 데이터에 적용됩니다. 각 요청마다 ETag 해시 메타데이터 헤더가 확인되며, 백엔드가 동일한 해시 값을 반환하면 이전 값이 제공됩니다.
-
no-store – 수신된 데이터에 대해 캐싱을 수행하지 않도록 지정하므로, 모든 요청이 최신 데이터를 얻기 위해 서버로 전달됩니다. 이는 민감한 데이터에 가장 안전합니다.
-
private – 공유 캐시 공간이 데이터를 캐싱하지 않도록 지정합니다. 이 헤더를 사용하여 사용자의 브라우저에 캐싱하도록 지정할 수 있지만, 프록시 서버에는 후속 요청에 대해 해당 데이터를 무효로 간주하도록 알릴 수도 있습니다.
-
public – 공개 응답을 지정하며 연결의 어느 지점에서나 캐싱을 허용합니다.
max-age 헤더를 사용하여 캐시가 유지되기를 원하는 시간을 초 단위로 지정할 수 있습니다. 위에서 정의한 다양한 헤더는 민감한 데이터를 안전하게 보호하고 동적 데이터를 최신 상태로 유지하며, 가장 중요하게는 서버’의 성능을 향상시키면서 캐싱을 구현하는 데 도움이 될 수 있습니다.
백엔드 서버가 Nginx 서버를 실행 중인 경우, 서버 블록 내에서 캐시의 유효 기간을 지정할 수 있습니다. 아래와 같이 구성에 expires 지시문을 추가하여 이 작업을 수행할 수 있습니다:
|
1 2 3 4 5 6 7 |
location / { expires 59m; } location /check-me { expires -1; } |
첫 번째 블록은 59분 동안 콘텐츠 캐싱을 허용하는 반면, 두 번째 블록은 캐싱하지 않음을 나타냅니다. 이러한 설정은 Cache-Control 헤더 옵션에 적용되며, 예를 들어 두 번째 블록의 경우 “no-cache”가 적용됩니다.
add-header 지시문을 사용하여 추가 값을 설정할 수 있습니다:
|
1 2 3 4 |
location /private { expires -1; add_header Cache-Control "no-store"; } |
결론
이 튜토리얼에서는 Nginx의 강력한 기능에 대해 배웠습니다. Nginx는 웹 서버이자 가장 중요하게는 역방향 프록시(reverse proxy)입니다. Nginx의 설계 덕분에 수천 개의 동시 연결을 처리할 수 있습니다. 이로 인해 로드 밸런싱에 완벽합니다. 이러한 설계 덕분에 처리를 위해 다른 백엔드 서버로 요청을 프록시하는 것이 매우 간단합니다.
이 튜토리얼에서 얻은 지식을 바탕으로, Nginx의 유연성 덕분에 복잡한 프록시와 로드 밸런서를 구현할 수 있을 것입니다.
다음은 저희 블로그에서 찾을 수 있는 Nginx와 더 친숙해질 수 있는 몇 가지 리소스입니다:
즐거운 컴퓨팅 되세요!
댓글
아직 댓글이 없습니다. 첫 번째로 작성해 보세요.