지나공 : 지식을 나누는 공간

Nginx와 Apache의 등장과 구조, Nginx.conf 파일 [1] 본문

Tech

Nginx와 Apache의 등장과 구조, Nginx.conf 파일 [1]

해리리_ 2022. 7. 23. 23:26

지난번엔 웹서버와 WAS를 간단히 알아보고, Nginx도 간단히 알아봤다. 오늘은 그것들이 등장한 배경과 구조를 알아본다.

우아한Tech 채널에서 너무 잘 정리해 주셔서 nginx.conf 파일과 같이 글로 정리해 봤다.

Nginx와 Apache의 등장배경

1995년 Apache

매 요청마다 프로세스를 만들고, 미리 만들어 놓는 prefork 방식의 1995년 Apache

아파치는 본래 정적 컨텐츠만 처리하는 웹서버이다. 초기 구조를 보면 클라이언트로부터 요청이 들어올 때마다 process를 할당한다. 근데 process를 매번 만들기는 부담이 있으니 prefork 방식을 사용하는데, 미리 process들을 만들어두고 새 요청이 들어올 때마다 꺼내서 할당하는 것이다. 그리고 만들어뒀던 프로세스가 모두 할당되면 추가로 프로세스를 만든다.

 

장점 :

- 개발하기가 쉽다.

- 새 모듈마다 새 프로세스를 할당하니까 원하는 모듈 만들어서 바로 배포할 수 있으니 확장하기가 편하다.

- 요청을 받고 응답을 받는 일련의 과정들을 하나의 서버에서 해결하기 좋다.

 

단점 :

- (아래에서 언급할 거지만) 새 커넥션도 계속해서 생기고, 동시에 keep-alive 설정을 통해 한번 얻은 커넥션을 길게 유지하여 재사용하는 기능도 쓰다보니 커넥션이 점점 많아진다.

- 커넥션이 너무 많아져서 더이상 새 커넥션을 만들 수 없는 문제가 발생한다. 'C10K' 문제

 

1999년 Apache : 서버 구조의 한계

이 시기는 인터넷 트래픽이 증가하면서 클라이언트로부터 더 많은 요청이 한번에 들어오는 시기였다. 그러면서 서버에 동시에 연결된 커넥션이 많아졌을 때 더 이상 커넥션을 형성하지 못하는 문제가 발생했다. 이 문제를 'C10K' 문제라고 하는데, 커넥션 10000개의 problem이라는 말을 줄인 표현이다.

 

* 동시에 연결된 커넥션 수 vs  초당 요청 처리 수

동시에 연결된 커넥션 수는 요청을 처리하기 위해 한 시점에 서버가 얼마나 많은 클라이언트와 동시에 커넥션을 맺는 지의 지표이고, 초당 요청 처리 수는 서버가 얼마나 빨리 요청들을 처리할 수 있는지를 나타내는 지표다. 한 클라이언트는 하나의 커넥션을 이용해서 여러 요청을 보낼 수 있다. 그리고 커넥션은 긴 시간동안 유지될 수도 있기 때문에 위 두 지표는 서로 다르다.

 

하나의 커넥션을 위해서는 3-way handshake처럼 여러 절차를 거쳐야 하니까 매번 커넥션을 새로 만들기엔 부담스럽다. 그래서 기존에 만들어진 커넥션을 다시 사용하기 위해서 keep-alive라는 헤더의 timeout을 설정하면 기존에 만든 커넥션을 계속 유지할 수 있다.

그런데 위에 그림에서 보이듯이, 여기서 클라이언트 수가 많아지면 동시에 연결되어 있는 커넥션 수가 더 많아진다. 게다가 keep-alive를 설정해놔서 커넥션이 끊기지 않으니 동시 커넥션 수는 더 많아진다. 그러면 어느 순간 더 이상 새로운 커넥션을 만들 수가 없어진다.

 

1. 커넥션이 많으면 매 커넥션마다 프로세스를 만들었을테니 메모리가 부족하고,

2. 아파치 서버의 여러가지 기능을 쉽게 추가할 수 있는 기능으로 인해 많은 기능이 추가되면서 프로세스가 무겁고,

3. 많은 양의 요청이 들어오면 각 요청마다 계속 프로세스가 바뀌니까 context switching이 많이 일어나고 그만큼 CPU 부하가 커진다.

 

2004년 Apache Server를 보완한 새로운 구조의 등장

초창기의 Nginx는 Apache server와 같이 사용하기 위해 등장했다. 아파치 서버가 가진 구조적 한계를 Nginx가 보완하는 구조로 많이 사용됐다.

Nginx는 이 자체가 웹 서버이므로 정적 파일에 대해서는 스스로 처리하면서, 아파치 서버의 리소스를 커넥션을 처리하는 데에 쓰지 않고 개발자가 원하는 로직을 처리하는 데에 사용되도록 했다. Nginx의 구조는 다음과 같다.

 

Nginx의 구조

1. master process가 있고 이는 설정 파일을 읽어서 설정에 맞게 worker process를 설정한다.

2. worker process가 실제로 일을 하는 프로세스이고, 각각 지정된 listen 소켓을 배정받는다. 

3. 그 소켓에 새로운 클라이언트로부터 요청이 들어오면 커넥션을 형성하고 요청을 처리한다. 그러면 그 커넥션은 keep-alive 시간만큼 유지된다.

4. 하지만 worker process가 하나의 커넥션만 한정해서 담당하지 않고, 기존 커넥션에서 아무런 요청이 없다면 새로운 커넥션을 형성하거나 이미 만들어진 다른 커넥션으로부터 들어온 요청을 처리한다.

 

Nginx에서 이렇게 커넥션을 형성하고 커넥션을 제거하고 새로운 요청을 처리하는 것을 '이벤트'라고 하고, 이 이벤트들은 OS 커널에 의해  큐 형식으로 worker process에게 전달된다. 이 이벤트는 큐에서 worker process에 의해 처리될 때까지 비동기 방식으로 대기한다. 또한 worker process는 하나의 스레드로 이벤트를 꺼내서 처리해 나간다. 

 

Nginx의 장단점

이러면 워커프로세스가 쉬지 않고 계속해서 일한다는 장점이 있다. 아파치 서버는 한 요청 당 한 프로세스가 할당되어 있기 때문에 요청이 안 오면 해당 프로세스는 방치되는데, 이에 비해 Nginx는 방치되는 프로세스 없이 리소스를 효율적으로 사용할 수 있다.

 

그런데 만약 이 요청들 중 하나가 아래 그림처럼 시간이 오래 걸리는 작업이라면, 뒤에 있는 이벤트는 그 요청이 처리되는 동안 계속 블로킹된다. 그래서 이걸 방지하기 위해 시간이 오래 걸리는 작업을 따로 수행하는 thread pool이 있다. worker process는 현재 작업이 오래 걸릴 것 같으면 이 이벤트를 thread pool에 위임해두고 큐 내의 다른 이벤트를 처리하러 간다.

오래 걸리는 작업은 thread pool로 위임하고 다른 작업을 처리함.

그리고 Nginx에서는 이런 worker process를 보통 CPU의 코어 수만큼 생성한다.

코어 수 만큼 워커 프로세스가 있으니 코어가 담당하는 프로세스 개수가 대폭 줄어든다. 각 코어당 하나의 워커 프로세스가 있고 각자 이걸 사용하면 되니까 CPU의 컨텍스트 스위칭 사용을 줄일 수 있다. 이게 Nginx의 Event-Driven Model로서 Apache 서버와 다른 점이다. 이런 구조로 인해 처리 가능한 동시 커넥션 수가 크게 증가했고, Apache와 동일한 커넥션 수로 비교하더라도 처리 속도가 향상된다.

 

하지만 개발자가 기능 추가를 시도했다가 잘 돌아가고 있는 워커프로세스를 종료해버리는 사고가 발생할 수도 있다는게 단점이다. 이러면 해당 워커프로세스가 처리하기로 한 여러 커넥션들과 관련된 모든 요청들을 처리할 수가 없게 된다. 그래서 Nginx는 개발자가 직접 모듈을 만들기가 까다롭다.

 

대신 Nginx 구조에서는 앞서 말한 것처럼 프로세스 수 자체가 대폭 줄어들었고, 이에 대해 동적으로 설정을 변경할 수도 있다. 개발자가 설정 파일을 변경하고 이걸 nginx에 적용하면 master process는 그 설정 파일에 맞는 worker process를 따로 만들고, 기존의 워커프로세스가 더이상 커넥션을 형성하지 않도록 한다. 그리고 담당하던 이벤트 처리가 모두 끝나면 해당 워커프로세스를 종료한다.

 

로드밸런서로서의 Nginx

로드밸런서로의 Nginx

위 그림의 상황처럼 많은 커넥션을 Nginx가 받는 와중에, 뒷단의 서버(그림의 Apaache)를 추가해야 하는 경우가 있다. Nginx는 이때 로드밸런서의 역할을 담당해서, 요청을 여러 서버로 분산하는 작업을 한다. 여기서 Nginx가 이미 여러 커넥션을 대하고 있으므로 중간에 Nginx의 설정을 바꾸는 건 어렵다. 그런데 동적으로 설정을 변경할 수 있다면 Nginx의 동시 커넥션들을 유지한 채, 요청을 처리하면서 뒷단에 서버를 추가할 수 있다. Nginx는 이러한 설정 변경을 초당 수십번씩 하더라도 무리 없이 커넥션을 관리하고 요청을 서버에 전달할 수 있다.

 

Nginx와 Apache

Nginx와 Apache 각각의 장점

Apache : 안정성과 확장성에 좋음.

Nginx : 동시 커넥션 문제 해결에 좋음.

 

2021년 : Nginx를 어떻게 사용해야 할까?

Nginx를 활용할 수 있는 방법은 다양하다.

 

1. 웹 서버로서의 기능 :

정적 콘텐츠를 전달하고, 동적 컨텐츠를 아파치에게 전달함

2. 로드밸런서로의 기능 :

nginx는 동시커넥션을 많이 유지할 수 있다는 장점이 있으니, 커넥션을 관리해서 여러 서버로 분산하는 작업함.

3. SSL 터미네이션 :

Nginx가 클라이언트와는 https 통신을 하고, 서버와는 http 통신을 하게 하면 뒷단 서버가 복호화 과정을 감당하지 않아도 된다. 뒷단 서버가 비즈니스 로직에만 리소스를 사용하도록 할 수 있다. 보통 Nginx와 뒷단 서버는 같은 네트워크 안에 있는 경우가 많으니 http 통신을 해도 보안 이슈가 덜하다.

 

4. 캐싱 :

Nginx는 http 프로토콜을 통해 캐싱을 할 수 있는데, SSL 터미네이션에서는 서버와 Nginx가 같은 네트워크 안에 있던 것과 달리, 캐싱에서는 Nginx를 클라이언트에 더 가깝게 배치한다. 그리고 한번 서버로부터 받은 응답을 스스로 보관하고 클라이언트에게 전달한다.

 

 

Nginx conf 파일을 알아보자

nginx.conf 파일은 메인 설정 파일이다. 디렉티브로 이루어져 있고, 각 디렉티브는 블록과 콘텐츠로 이루어져 있다.

그리고 디렉티브를 끝내는 방법은 세미클론과 중괄호 블록이다. 주석 처리는 #을 통해 한다.

 

디렉티브

user nginx;
worker_processes 1;

error_log    /var/log/nginx/error.log warn;
pid    /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    upstream tour.co.kr {
    	server localhost:11111;
    }
    server {
    	listen 80;
        server name tour.co.kr;
        location / {
        	proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Server $host;
        }
        error_page 500 502 503 /50x.html
        location = /50x.html {
        	root html;
        }
    }
    server {
    	listen 43 ssl;
        server name localhost:11111;
        location / {
        	proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Server $host;
        }
        error_page 500 502 503 /50x.html
        location = /50x.html {
        	root html;
        }
    }
}
  • user
  • worker_process : 몇 개의 워커 프로세스를 생성할 것인지
  • error_log
  • pid

이것들은 특정 블록이나 컨텍스트에 포함되지 않는 메인 컨텍스트다. user가 root로 지정되어 있으면 워커 프로세스가 root로 동작하게 되는데, 사용자가 워커 프로세스를 악의적으로 사용할 수도 있으니 이런 상황은 보안상 위험하다.

 

디렉티브 설명

  • wokder_connections : event 내에 사용하는 지시어로, 동시에 얼마나 많은 접속을 처리할지를 나타낸다.
    • 만약 worder_process가 4이면 4개의 워커프로세스가 돌아가고, 여기서 worker_connections가 1024라면 총 4*1024 = 4096만큼의 커넥션을 동시에 처리할 수 있다.
  • http 블록 : http 와 관련된 모듈들이 있다,
    • upstream : upstream 서버에 대한 설정을 하고, upstream 서버의 이름도 넣어준다. 서버들은 TCP든 UNIX든 소켓이든 모두 listen할 수 있다. 위 코드에서는 tour.co.kr이라는 이름으로 localhost 11111에 연결되게 했다.
    • include : 다른 파일을 가져올 수 있다. 
  • server 블록 : 가상 서버를 정의한다. IP 주소 기반도 가능하고 server_name 기반도 가능하다.
    • location : request URI에 따른 설정을 한다.  
    • listen : IP를 위한 address와 port 번호를 설정할 수 이다. port 번호가 안적혀 있으면 80폴트를 listen하게 된다.
    • SSL 관련해서 private key와 timeout도 설정할 수 있다.
    • nginx가 proxy로서 동작할 때 request를 중개받는 서버의 프로토콜과 address, location에 매핑될 URI 등을 설정할 수도 있다.
      • proxy_set_haeder : nginx가 porxy로서 동작할 때 중개받는 서버에 request header를 재정의해서 전달할 때 사용한다.
      • proxy_pass는 nginx가 proxy로서 동작할 때 중개받는 서버의 프로토콜과 address, 매핑될 URI를 설정한다.
  • location 블록 : server 블록 내에 위치하고, 특정 url 을 처리하는 방법을 정의한다. 
    • header도 세팅할 수 있다.

Upstream / Downstream

upsteram은 물을 흘러 내려보내는 강의 상류고, downstream은 흘러 내려가서 물이 받아지는 강의 하류다. 데이터로 따지면, 데이터를 보내는 쪽에서 나가는 흐름이 upstream, 데이터를 받는 쪽에서 받는 흐름이 downstream이 된다. 실제 지형의 높낮이가 정해져 있는 강과 달리, 컴퓨터 세계에서는 어느 하나가 영원히 upstream이고 영원히 downstream일 수는 없다.

 

예를 들어 우리가 파일을 다운로드하면, 유저 입장에서 서버가 upstream이고, 이 데이터를 받는 우리가 downstream이다.

http {
	upstream tour.co.kr{
    	server localhost:12101;
    }
}

위의 경우 localhost 12101번 포트를 upstream 서버로 사용하겠다는 거고, 앞으로 http 프로토콜의 request들을 여기로 보내겠다는 말이다.

 

Nginx.conf 파일 작성하기

먼저 http 블록와 server 블록 아래에 "/"에 대한 location 블록을 설정한다.

http {
	server {
    	location / {
        	root /www;
        }
        location /images/ {
        	root /www'
        }
    }
}

정적 파일의 위치가 /www라고 할 때, request의 URI가 "/"로 시작하면 이를 정적 파일과 연결시키기 위해 root인 "/www"를 URI에 붙인 뒤 정적 파일인 index.html로 연결시켜준다.

 

이미지 파일이 /www/images에 있다고 하면, 마찬가지로 "/images" 로 요청이 들어왔을 때 이미지 파일이 있는 곳으로 연결되게 하기 위해 URI의 root에 해당 경로가 붙어서 연결되도록 설정을 추가할 수 있다.

 

위처럼 해두면 images로 매칭되지 않은 모든 URI에 대해 첫번째 location 설정대로 root에 /www를 붙여서 접근하게 된다.

 

Nginx를 Proxy 서버로 사용해보자.

정적 리소스, DB 요청 등 각각을 구분해서 맞는 서버로 요청을 보내는 Proxy 서버로서 Nginx를 활용할 수도 있다. 

server {
	listen 8000;
    root /data/www;
    
    location / {
    	proxy_pass http://localhost:12101;
    }
    
    location ~ \.(gif|jpg)$ {
    	root /data/images;
    }
}

이 서버는 8080 포트를 listen하고 들어온 모든 request의 URI에 /data/www를 붙여준다는 의미다. 그리고 /로 시작되는 URI에 대해서는 proxy_pass를 통해 proxy 서버로서 nginx가 어디로 연결시켜줄지를 정의해놨다. 그러면 12101 포트를 listen하고 있는 특정 서버가 있고 요청이 들어왔을 때 거기로 보내준다는 의미가 된다. (nginx 설정을 하고 나서는 reload를 해줘야 함)

 

 

내용 출처 :  https://www.youtube.com/watch?v=6FAwAXXj5N0 https://developer88.tistory.com/299

728x90

'Tech' 카테고리의 다른 글

Redis 이모저모  (2) 2023.12.10
Web Server, WAS, Nginx [0]  (0) 2022.07.10
나 보려고 만드는 IntelliJ 단축키 (mac)  (0) 2022.04.07
Comments