본문 바로가기
공부

Apache vs NGINX, 그리고 NGINX 설정

by 실패전문개발자 2022. 1. 26.

소개

  • Apache와 NGINX는 현재 가장 폭넓게 사용되는 2가지 오픈소스 웹서버

  • 현재 Apache와 NGINX의 점유율은 합쳐서 50%정도 됩니다. (NGINX가 무섭게 추격하고 있네요)
    • 사실 전 Apache와 NGINX 점유율을 빼면 거의 안남을 줄 알았는데 50%는 다른 것을 쓴다고 하니 놀랐습니다.
  • 이렇게 다양한 웹서버 중에서 왜 유독 Apache랑 NGINX가 많이 사용되는지 한번 알아보려고 합니다.

웹서버

  • Apache랑 NGINX는 결국 웹서버입니다.
  • 웹서버에 대해 대부분 잘 알고 계시겠지만 정리하는 겸 한번 더 상기하려 합니다.
  • 웹서버의 사전적 정의는 다음과 같습니다
  • 웹 콘텐츠를 저장하거나 처리하는 컴퓨터 또는 소프트웨어. 일반적으로 웹서버가 되는 컴퓨터에 설치 되는 소프트웨어를 의미. 웹 서버 소프트웨어는 HTTP 프로토콜을 통해 클라이언트(웹 브라우저)의 요청 정보를 받아 처리하고 그 결과를 다시 클라이언트에 보낸다. 클라이언트가 요청하는 자원(리소스)을 URL(Uniform Resource Locator) 형태로 받아 내부 파일 시스템과 매핑하여 처리하거나, URL과 입력 값(예: 로그인 화면의 아이디, 패스워드 등)을 함께 받으면 사전에 약속된 처리를 한 후 그 결과를 클라이언트에 전달한다. 대표적인 웹 서버로는 아파치(Apache), 엔진엑스(nginx), 마이크로소프트사의 IIS(Internet Information Services) 등이 있다. [네이버 지식백과] 웹 서버 [web server] (IT용어사전, 한국정보통신기술협회)
  • 쉽게 말하자면 클라이언트의 요청을 받아 요청에 대한 서버의 데이터를 반환하는 것입니다.
  • 이때 서버의 데이터는 정적 데이터들 입니다. (html, css, js, img, ...)
    • 동적 데이터(직접 데이터를 만들어야 하는 경우)는 application이 포함된 WAS의 영역이므로 여기에선 다루지 않습니다.

역할

  • HTTP 통신: HTTP 통신을 통해 사용자와 서버간 데이터 교류
  • 정적 데이터 제공: HTTP 프로토콜을 통해 정적 콘텐츠를 클라이언트에게 제공
  • 로깅: 보안 및 통계 목적으로 로그파일에 클라이언트의 요청 및 서버 응답 정보 로깅
  • 가상호스팅: 하나의 IP주소를 사용해 많은 웹사이트(DNS)를 제공 가능
  • 권한 부여: 웹 리소스 일부에 대해 접근 제어 가능
  • 캐싱: 서버 응답 속도를 높이기 위해 정적 및 동적 컨텐츠 캐시 가능
    • 정적 데이터는 파일을 복사한 후 캐시서버에 저장해뒀다 사용
    • 동적 데이터는 요청에 대한 결과를 짧은 시간동안 캐시했다가 사용
  • 대역폭 조절: 네트워크 포화를 방지하고 서버 다운을 방지하기 위해 응답 속도 제한
  • ...

WAS(Web Application Server)

  • 일반적으로 웹 서비스에서 웹서버 하나만 사용하는 경우는 드뭅니다.
  • 동적 데이터를 같이 처리하기 위해 WAS를 함께 사용하는데 WAS에 웹서버가 같이 있기도 합니다.
  • 주로 정적 데이터는 웹서버가 동적 데이터는 WAS가 처리를 합니다.
    • WAS가 정적데이터도 함께 처리할 수 있지만, 그럴 경우 WAS에게 큰 부담을 주기 때문에 웹서버도 함께 사용합니다.

Apache

  • Apache 웹서버는 Apache HTTP Server를 줄여서 부르는 말입니다.
  • Apache HTTP Server는 오픈소스 소프트웨어 그룹인 아파치 소프트웨어 재단에서 만든 웹서버입니다.

역사

  • 팀 버너스 리가 만든 최초의 웹 서버 프로그램인 NCSA HTTPd를 기반으로 만들어졌습니다.
  • Apache HTTP Server는 NCSA HTTPd를 리눅스에서도 도리는 것을 목표로 만들어진 프로그램입니다.
  • 리눅스가 서버 OS의 최다 점유율을 차지하자 Apache HTTP Server 또한 최다 점유율을 차지하게 되었습니다.
  • 초창기에는 Perl언어와 많이 사용했는데 이후 PHP와 많이 사용하게 되었습니다.
  • 오픈소스 DB인 MySQL이 나오자 Apache+PHP+MySQL(APM)과 함께 웹서버를 구축하는 케이스가 많아졌습니다.
  • 2.2 버전대에서 무겁다는 평이 많아지고, 경량 웹서버인 Nginx가 나오면서 위기가 시작됐습니다.
  • 2.4 버전에서 Event MPM을 탑재해 속도를 크게 개선했습니다.
  • 윈도우용도 나와 윈도우에서 APM을 통해 설치할 수 있는 패키지가 나왔습니다. (하지만 리눅스 속도는 못따라감)

특징

  • 클라이언트 요청을 처리하기 위해 멀티 스레드 방식을 따릅니다.
  • 모듈을 동적으로 Load&Unload 할 수 있습니다.
  • 정적 컨텐츠는 파일 기반 방식의 처리 방식을 제공합니다.
  • 웹 서버 자체 내에서 동적 컨텐츠를 처리합니다.
    • 웹서버가 왠 동적 컨텐츠?라고 생각했는데 CGI(Common Gateway Inteface)를 사용해서 동적 페이지를 만든다고 하네요.
  • 60개 이상의 다양한 모듈을 지원하며, 필요에 따라 활성화 비활성화 할 수 있습니다.
    • 그런데 대다수는 이 모듈들 사용안하는듯
  • 보안 측면에서 Web 기반 DDoS 방어에 관한 기술을 제공합니다.
  • ...

구조

스레드 / 프로세스 기반 구조

 

  • Apache는 멀티 프로세스 + 멀티 스레드 방식을 사용합니다.
  • 항상 여유로운 수의 프로세스/스레드를 생성해두기 때문에 요청이 들어왔을 때 프로세스/스레드가 생성되는 것을 기다리지 않아도 됩니다.
  • 서버를 최초 실행할 때 몇 개의 프로세스를 생성할지 전달받습니다.
  • 서버는 모든 프로세스에 속해있는 유휴 스레드의 수를 파악하고, 이 값이 설정된 범위내에 있도록 프로세스를 fork 혹은 kill 합니다.
  • 평소에는 요청 하나에 스레드 하나가 대응하는 구조입니다.
  • 하나의 프로세스에서 관리할 수 있는 스레드 수가 정해져있기 때문에, 사용자의 접속이 증가하면 프로세스를 fork 합니다.

MPM(Multi Processing Module)방식 처리

Perfork MPM 방식

  • 하나의 자식 프로세스가 하나의 스레드를 갖는 구조로, 자식 프로세스는 최대 1024개까지 가능합니다.
  • 스레드 간 메모리 공유를 하지 않습니다. (독립적이지만 메모리를 많이 사용)
  • 실행 중인 프로세스를 복제하여 실행합니다. (메모리 영역까지 복제)
  • 응답 프로세스를 미리 띄워놓고 클라이언트 요청 시 자식 프로세스가 반응하게 되는 방식입니다.
  • 일반적으로 Single CPU 또는 Dual CPU에서 성능이 좋습니다.

Worker MPM 방식

  • Perfork보다 메모리 사용량이 적고 통신량이 많거나 동시 접속자가 많은 사이트에 적합합니다.
  • 프로세스당 최대 64개의 스레드 처리가 가능하고 각 스레드는 한번에 한 연결을 담당합니다.
  • 스레드 간에 메모리를 공유합니다.
  • 일반적으로 Multi CPU 시스템에서 성능이 좋습니다.

NGINX

  • NGINX, Inc. 가 운영 중인 오픈소스 웹서버입니다.
  • 경량화된 소프트웨어 웹 서버입니다.

특징

  • 이벤트 처리방식, 비동기식 처리, 논블로킹 처리 방식을 통해 정적 컨텐츠를 고속으로 처리합니다.
  • 동적 컨텐츠는 처리하지 않습니다. (다른 WAS의 도움을 받아야 함)
  • 정적 컨텐츠를 빠르게 처리하여 동적 처리를 별도로 하는 소프트웨어 스택과 연계해서 고성능 서버를 제공하는데 적합합니다.
  • Windows 부분을 다소 아쉽습니다.
  • 모듈 및 확장성, 보안과 관련해서는 기본적으로 다른 코어 모듈을 로딩할 수 없습니다.
  • ...

구조

Event-Driven 처리 기반 구조

  • NGINX는 Event-Driven 구조로 동작하기 때문에 한 개 혹은 고정된 프로세스만 생성해서 사용합니다.
  • 비동기 방식으로 요청들을 Concurrency 하게 처리할 수 있습니다.
  • 위 그림과 같이 NGINX는 새로운 요청이 들어오더라도 새로운 프로세스와 스레드를 생성하지 않습니다
  • 새로운 프로세스와 스레드를 생성하지 않기 때문에 생성 비용이 존재하지 않고, 적은 자원으로도 효율적인 운용이 가능합니다.

Event-Driven 처리

  • 여러개의 커넥션을 모두 Event-Handler를 통해 비동기식 방식으로 처리합니다.
  • 먼저 처리되는 Event부터 로직이 진행되도록 합니다.

Master-Worker 프로세스 구조

  • NGINX는 하나의 Master Process와 다수의 Worker Process로 구성되어 실행됩니다.
  • Master Process는 설정 파일을 읽고, 유효성을 검사 및 Worker Process를 관리합니다.
  • 모든 요청은 Worker Process에서 처리합니다.
  • NGINX는 이벤트 기반 모델을 사용하고 Worker Process 사이에 요청을 효율적으로 분배하기 위해 OS에 의존적인 메커니즘을 사용합니다.
  • Worker Process의 개수는 설정 파일에서 정의되며, 정의된 프로세스 개수와 사용 가능한 CPU 코어 숫자에 맞게 자동으로 조정됩니다.

Apache vs NGINX

Apache와 Nginx가 점유율이 높은 이유?

Apache

  • Apache는 최초의 웹서버 프로그램인 NCSA HTTPd를 기반으로 만들어 졌습니다.
    • 최초라는 것에서 이미 선점하고 간다는 메리트가 있습니다.
  • Apache는 NCSA HTTPd를 리눅스에서 사용할 수 있게 만들었습니다.
    • 리눅스가 서버 OS의 점유율 대부분을 차지했습니다. 즉 아파치에 날개를 달아줬죠.
  • Apache+PHP+MySQL 을 통해 쉽게 공짜로 서버를 구축할 수 있게 만들었습니다.
    • 그렇기 때문에 자원이 적은 중소기업에서 아파치를 많이 사용하게 되었죠

NGINX

  • 무엇보다 성능이 뛰어납니다.
    • 다른 이유를 찾아보려고 했는데 그냥 성능이 너무 좋아서 점유율이 높아진 것 같습니다.
    • NGINX가 오픈소스이긴 한데 기업용 NGINX PLUS는 유료입니다. - health check는 NGINX PLUS만 이용 가능하다네요.

NGINX가 아파치의 점유율을 따라잡는 이유

Apache의 한계

  • 클라이언트 접속마다 프로세스 혹은 스레드를 생성하는 구조입니다.
    • C10K라는 문제가 있습니다.
      • C10K: 하드웨어 자원이 부족하지 않음에도 불구하고 IO 처리방식의 문제 때문에 프로세스가 제대로 처리못하는 문제
      • 처음 설계했을 때는 하드웨어가 10K 클라이언트를 처리할 여력이 되지 않아서 이를 고려하지 않고 설계했죠.
      • 성능이 아무리 좋아도 코어에서 사용할 수 있는 스레드가 제한적이기 때문에 10K를 해결해도 근본적인 해결법은 아니죠.
    • 프로세스와 스레드의 생성비용이 들어갑니다.
    • 그리고 프로세스가 blocking되면 요청을 처리하지 못하고 완료될 때까지 대기상태에 있습니다.
      • Keep Alive(접속대기)로 해결이 가능하지만, 효율이 떨어집니다.

NGINX가 점유율이 올라가는 이유

  • NGINX는 Event-Driven 방식으로 동작합니다.
    • 즉 위에서 프로세스-스레드를 이용하는 방식의 문제가 거의 발생하지 않습니다.
    • CPU 자원 소모가 적고 성능 또한 더욱 좋습니다.
  • 아파치가 보유하고 있던 장점인 동적 컨텐츠 처리를 다른 서버로 위임할 수 있습니다.
    • 정적처리를 NGINX가 하고 동적 처리를 아파치에게 시킬 수도 있죠.
  • 그리고 우리가 사용하는 스프링부트의 경우 NGINX는 그저 리버스 프록시 역할만 하면서 점유를 높이고 있죠.
    • 웹서버랑 WAS를 전부 내장 Tomcat에 던져주고 프록시 기능만 쏙 빼서 써도 NGINX 만한게 없죠 ㅎ

Apache와 NGINX 비교

잘 정리된 표가 있어서 이거 하나면 될 것 같습니다.

NGINX 설정

  • NGINX 환경세팅까지 전부 다루기엔 너무 양이 많아질 것 같아서 간단하게 nginx.conf 만 살펴보려 합니다.

기본 nginx.conf 구조

user nginx;
worker_processes auto;
error_log logs/error.log;
pid logs/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
    ...
}

http {
    ...
    server {
        ...
        location / {
            ...
        }
    }
}

코어 모듈 블록

  • 코어 모듈은 대부분 환경 설정 파일의 최상단에 위치하며 한번만 사용할 수 있습니다.
# user: worker process가 실행되는 권한. root를 넣으면 worker process가 root권한을 가지기 때문에 보안상 위험합니다.
# param1: user의 이름을 정의.
# param2: (optional) user의 그룹을 지정. 없다면 param1과 같은 이름.
user usr [group];  ## default: nobody;

# worker_process: worker process의 수를 정의.
# param1: worker process 개수 (default: 1)
# auto값이 자동 감지를 시도합니다. (ver 1.3.8과 ver 1.2.5 부터 지원)
worker_processes auto;  ## default: 1;

# error_log: 로깅을 구성. level이상의 log가 저장. [debug, info, notice, warn, error, crit, alert, emerg]
# param1: 로그가 저장될 위치.
# param2: (optional) 로그의 최소 level.
error_log logs/error.log [level];  ## default: log/error.log error;

# pid: main process의 pid를 저장할 file을 정의.
# param1: main process의 pid가 저장될 파일 위치.
pid logs/nginx.pid;  ## default: logs/nginx.pid

event 블록

  • 주로 네트워크 동작에 관련된 설정하는 영역으로, 일부 매개변수는 성능에 중요한 영향을 미칩니다.
events {
    # worker_connections: worker process에서 열 수 있는 최대 동시 연결 수 설정
    # 클라이언트의 연결 뿐만이 아니라 모든연결(예: 프록시 서버와의 연결)이 포함됨
    # param1: worker process에서 열 수 있는 최대 동시 연결 수
    worker_connections 4096;  ## default: 512;
}

http 블록

  • 웹서버에 대한 동작을 설정하는 영역으로, server 블록과 location 블록의 루트 블록입니다.
http {
    # include: 다른 파일을 구성에 포함.
    # 포함된 파일은 구문상 올바른 지시문과 블록으로 되어 있어야 함
    include    conf/mime.types;
    include    /etc/nginx/proxy.conf;
    include    /etc/nginx/fastcgi.conf;

    # default_type: 이 웹서버의 기본 Content-Type을 설정
    # application/octet-stream은 바이너리 형태의 타입을 의미
    # param1: Content-Type 종류
    default_type application/octet-stream;

    # log_format: 로그의 포맷을 설정
    # 변수 종류는 여기서 확인 -> http://nginx.org/en/docs/varindex.html
    # param1: log_format 이름
    # param2: log_format 메시지 포맷
    log_format   main '$remote_addr - $remote_user [$time_local]  $status '
      '"$request" $body_bytes_sent "$http_referer" '
      '"$http_user_agent" "$http_x_forwarded_for"';

    # access_log: 액세스 로그를 저장할 파일을 지정
    # param1: 액세스 로그 저장 위치
    # param2: (optional) 액세스 로그 포맷 이름
    access_log   logs/access.log  [main];

    # sendfile: sendfile api를 사용할지 말지 결정
    # param1: on | off
    sendfile     on;  ## default: off;

    # tcp_nopush: 뭔지 찾아도 잘 안나옴, 안써도 무방한듯
    # sendfile이 on일때만 사용 가능
    # param1: on | off
    tcp_nopush   on;  ## default: off;

    # server_tokens: 서버의 응답 헤더에 nginx version을 숨기거나 숨기지 않음
    # param1: on | off | build | string
    server_tokens   off;  ## default: on;

    # keepalive_timeout: 서버에서 클라이언트 연결이 열리는 제한 시간
    # param1: keep-alive 시간
    keepalive_timeout   65;  ## default: 75s;

    server { 
        ...
    }

    server {
        ...
    }

    server { 
        ...
    }
}

server 블록

  • 하나의 웹사이트를 선언하는데 사용합니다. 가상 호스팅(Virtual Host)의 개념입니다.

location 블록

  • Server 블록 내에서 특정 URL을 처리하는 방법을 정의합니다.
# php/fastcgi 사용 예시
server { 
    # 80포트로 들어오면
    listen       80;

    # 가상 호스트 이름은 domain1.com 혹은 www.domain1.com 이고
    server_name  domain1.com www.domain1.com;

    # 액세스 로그는 logs/domain1.access.log에 차곡차곡 쌓아두고
    access_log   logs/domain1.access.log;

    # root 디렉토리는 html 디렉토리이고
    # /hello로 들어오면 /html/hello 로 들어감
    root         html;

    # localhost/AAA.php로 들어오면 localhost:1025/AAA.php로 접속
    # localhost/AaA.php로 들어오면 localhost:0125/AaA.php로 접속
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:1025;
    }
}

# reverse-proxy 사용 예시
server {
    # 80포트로 들어오면
    listen       80;

    # 가상 호스트 이름은 domain2.com 혹은 www.domain2.com 이고
    server_name  domain2.com www.domain2.com;

    # 액세스 로그는 logs/domain2.access.log에 main 로그 포맷으로 차곡차곡 쌓아두고
    access_log   logs/domain2.access.log  main;

    # 각종 스태틱 파일들이 들어오면 root directory에서 꺼내쓰고 캐시는 30일 동안 저장해두고
    location ~ ^/(images|javascript|js|css|flash|media|static)/  {
        root    /var/www/virtual/domain2/htdocs;
        expires 30d;
    }

    # localhost/foo/bar 에 접속한다면 127.0.0.1:8080/bar 에 접속하고
    location ^~ /foo/ {
        proxy_pass     http://127.0.0.1:8080/;
    }

    # 그외 localhost:8080에 접속
    location / {
        proxy_pass      http://127.0.0.1:8080;

        # 요청에 param1의 헤더에 param2의 값을 넣어 전달
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
    }
}

# load balancing 예시
## upstream: 정보 공급자
## big_server_com 으로 들어오면 밑의 4개에 가중치에 맞게 보냄 
upstream big_server_com {
    server 127.0.0.3:8000 weight=5;
    server 127.0.0.3:8001 weight=5;
    server 192.168.0.1:8000;
    server 192.168.0.1:8001;
}

server {
    # 80포트로 들어오면
    listen          80;

    # 가상 호스트 이름은 big.server.com 이고
    server_name     big.server.com;

    # 액세스 로그는 logs/big.server.access.log에 main 로그 포맷으로 차곡차곡 쌓아두고
    access_log      logs/big.server.access.log main;

    # 접속되는 것은 upstream big_server_name에 있는 것으로 라운드로빈해서 보내주기
    location / {
        proxy_pass      http://big_server_com;
    }
}

'공부' 카테고리의 다른 글

recoil 소개  (0) 2022.06.22
쿠버네티스 학습  (0) 2022.03.24
RabbitMQ vs Kafka vs Redis  (0) 2022.03.11
Log4j 학습  (0) 2022.01.28