프로젝트에서 Nginx나 Public Cloud에서 제공해주는 Load Balancer 제품을 간단하게 사용만 해봤지 제대로 알고있는지에 대해 자문하다보니 아는게 없어서 정리해보고자 한다.
이번 포스팅에선 Nginx에 대한 개념부터 짚어보자.
(추 후 실 서버에서 Nginx를 구성한 뒤 스크린샷 첨부 예정)
Nginx란 트래픽이 많은 웹 사이트의 서버(WAS)를 도와주는 비동기 이벤트 기반 구조의 경량화 웹 서버 프로그램이다.
먼저, Nginx 및 모듈이 작동하는 방식은 구성 파일에서 정의된다. 기본적으로 구성 파일 이름은 nginx.conf
로 지정되고, /usr/local/nginx/conf
, /etc/nginx
또는 /usr/local/etc/nginx
디렉터리에 저장된다.
conf 파일의 내부를 보며 어떻게 구성하는지 간단하게 알아보자.
중요한 웹 서버 작업은 파일(예: 이미지,정적 HTML 페이지)을 서비스한다. 요청에 따라 각 로컬 디렉터리에서 파일을 서비스하는 /data/www(HTML 파일 포함)
와 /data/images(이미지 포함)
의 예시를 구현해보자. 구성 파일을 편집하고, http 블록 내의 server 블록을 두 개의 location 블록으로 설정해야 한다.
먼저 /data/www 디렉터리를 만들고 index.html 파일에 아무 텍스트를 넣은 다음, /data/images 디렉터리를 만들어 몇 개의 이미지를 넣는다.
그런 다음 구성 파일을 연다. 기본 구성 파일에는 이미 server 블록의 여러 예시가 포함되어 있으며, 대부분은 주석 처리되어 있다. 이제 모든 해당 블록을 주석처리하고 새 server 블록을 시작한다.
http {
server {
location / {
root /data/www;
}
location /images/ {
root /data;
}
}
}
위 예제는 표준 포트 80을 수신하는 서버에서 이미 작동하는 구성이며, http://localhost/
에서 로컬 PC에 액세스할 수 있다.
/images/
로 시작하는 URI를 포함하는 요청을 받으면 서버는 /data/images
디렉터리에서 파일을 전송한다.http://localhost/images/example.png
요청에 응답하여 /data/images/example.png
파일을 전송한다./images/
로 시작하지 않는 URI를 포함한 요청은 /data/www
디렉터리에 매핑된다.http://localhost/some/example.html
요청에 응답하여 /data/www/some/example.html
파일을 전송한다.Proxy Pattern, Proxy Server 등 다양한 용어가 많은데 여기서 말하는 프록시의 사전적 의미는 '대리', '대신'이다. 보안상의 문제로 직접 통신을 주고받을 수 없는 두 PC 사이에서 통신을 할 때 직접하지 않고 중간에서 대리로 중계를 하는 개념을 프록시라 한다.
이렇게 중계 기능을 하는 것을 프록시 서버라고 한다.
프록시 서버는 서버가 어디에 위치하느냐에 따라 Forward Proxy와 Reverse Proxy로 나뉜다. 각각의 경우 용도와 역할도 다르다.
앞서, 클라이언트에서 서버로 리소스를 요청할 때 직접 요청하지 않고 프록시 서버를 거쳐서 요청한다고 했다.
서버에서 받는 IP는 클라이언트의 IP가 아닌 프록시 서버의 IP기 때문에 서버는 클라이언트가 누군지 알 수 없다. 따라서 서버에게 클라이언트가 누구인지 감춰주는 역할을 한다.
Forward Proxy는 캐싱 기능이 있으므로 자주 사용되는 컨텐츠라면 월등한 성능 향상을 가져올 수 있으며 정해진 사이트만 연결하게 설정하는 등 웹 사용 환경을 제한할 수 있으므로 보안이 중요한 환경에서 많이 사용된다.
Reverse Proxy는 Forward Proxy와 반대 개념이다. 애플리케이션 서버의 앞에 위치하여 클라이언트가 서버를 요청할 때 Reverse Proxy를 호출하고 이 프록시가 서버로부터 응답을 전달받아 다시 클라이언트에게 전송하는 역할을 한다.
클라이언트는 애플리케이션 서버를 직접 호출하는 것이 아니라 서버를 통해 호출하기 때문에 Reverse Proxy는 애플리케이션 서버를 감춰주는 역할을 한다.
로컬 디렉터리에 있는 파일을 포함한 이미지에 대한 요청을 서비스하고, 그 외에 다른 요청은 프록시된 서버로 전송한다. 이 예제에서는 두 서버를 하나의 nginx 인스턴스에서 정의할 것이다.
먼저 server 블록을 하나 더 nginx 구성 파일에 추가하여 다음과 같은 내용으로 프록시된 서버를 정의한다.
http {
server {
location / {
proxy_pass http://localhost:8080/;
}
location ~ \.(gif/jpg/png)$ {
root /data/images;
}
}
}
이 서버는 .gif
, .jpg
또는 .png
로 끝나는 요청을 필터링하고 (URI를 root 명령의 매개변수에 추가하여) /data/images
디렉터리에 매핑한다. 그 외에 다른 요청은 위에서 구성한 프록시된 서버로 보낸다.
1995년, UNIX 기반의 NCSA httpd가 나왔다. 이 프로그램은 버그가 굉장히 많아 개발자들이 사용할 때 불편함이 많았다.
httpd의 문제를 해결하기 위해 탄생한 것이 Apache Server이다.
요청이 들어오면 connection을 생성하는 방식이다. 새로운 클라이언트의 요청이 올 때마다 새로운 process를 생성하게 된다.
프로세스를 생성하는 과정은 3-Way-Handshake에 따라 시간이 오래걸린다. 이 때문에 프로세스를 미리 만들어두는 PREFORK라는 방식을 이용하게 된다. 새로운 클라이언트의 요청이 오면 미리 만들어 둔 프로세스를 할당시키고, 만들어둔 프로세스가 없다면 추가로 프로세스를 생성한다.
이런 구조 덕에 개발자는 다양한 모듈을 만들어 서버에 빠르게 기능을 추가할 수 있었다. 즉, 확장성이 높고 동적 컨텐츠를 처리할 수 있게 되었다. 이는 요청을 받고 응답을 처리하는 과정을 하나의 서버에서 해결하기 좋은 구조이다.
1999년에 들어 컴퓨터의 보급률이 높아지자 요청 또한 많아져서 서버에 동시에 연결된 connection 수가 자연스럽게 늘게 되었다. C10K(connection 10000 problem) 문제가 발생하는 한계가 생겨났다.
즉, Apache Server는 현대로 올수록 사용하기 꺼려졌고, 이러한 구조적 문제점을 해결한 Nginx가 2004년에 나왔다.
초창기 Nginx는 Apache와 함께 사용하기 위해 만들어졌다. 웹 서버이기는 하지만 아파치 서버를 완전히 대체할 목적은 아니었다. 아파치 서버가 지닌 구조적 한계를 Nginx를 사용하면서 극복하려고 했다.
위 그림과 같이 수많은 동시 connection을 Nginx가 유지하고, Nginx도 웹 서버이기 때문에 정적 파일에 대한 요청은 스스로 처리하고, 클라이언트로부터 동적 파일의 요청을 받았을 때만 아파치 서버의 connection을 형성하여 아파치 서버의 부하를 줄이게 된다.
Nginx가 많은 동시 connection 유지를 할 수 있는 그 기반은 무엇일까?
Nginx에서의 이러한 connection 형성과 제거, 새로운 요청을 처리하는 일을 이벤트(event)라고 한다.
이 이벤트들은 OS Kernel이 queue 형식으로 worker process에게 전달한다. 해당 이벤트들은 큐에 담긴 상태에서 비동기 상태로 대기한다. worker process는 하나의 스레드로 이벤트를 꺼내서 처리해간다.
이런 방식은 worker process가 쉬지 않고 일을 하기에, 요청이 없을 때 유휴 상태의 프로세스를 두는 Apache Server보다 훨씬 효율적으로 자원을 사용할 수 있다.
동시 connection 수 당 메모리 사용률을 나타내는 그래프이다. Nginx는 Apache에 비해 커넥션 수가 늘어나도 메모리 사용률이 낮고 일정하다.
동시 connection 수가 많아졌을 때 처리하는 초당 요청 수는 Nginx가 Apache에 비해 압도적으로 높다는 것을 나타낸 그래프이다.
Apache는 클라이언트 접속마다 Process 혹은 Thread를 생성하는 구조이다. 10,000 클라이언트로부터 동시 접속 요청이 들어온다면 CPU와 메모리 사용률이 증가하고 추가적인 Process 생성 비용이 드는 등 대용량 요청에서 한계가 나타난다.
Apache 서버의 프로세스가 blocking 될 때 요청을 처리하지 못하고 처리가 완료될 때까지 대기상태로 남는다. 이는 Keep Aliver로 해결이 가능하나 효율이 좋지 않다.
Nginx는 Event Driven 방식으로 동작하기에 한 개 또는 고정된 프로세스만 생성하고, 그 내부에서 비동기로 효율적인 방식으로 task를 처리한다. Apache와 달리 동시 접속자 수가 많아져도 추가적인 생성 비용이 들지 않는다.
CPU 소모가 적고 CPU와 관계 없이 I/O를 전부 이벤트 리스너로 미루기 때문에 흐름이 끊기지 않는다. 또한 context switching 비용이 적다.