[엉박사] 2.4 Nginx

impala·2023년 1월 12일
0
post-thumbnail

2.4 Nginx

프로젝트를 배포하는 과정 중 가장 골치아팠던게 Nginx였다. 장고쪽 컨테이너는 Gunicorn을 통해 WAS(Web Application Server)로 만들었는데 리액트로 만든 정적파일을 어디에서 호스팅해줘야 하는지 감이 잡히지 않아 애를 먹었다. 웹서버에 대한 개념이 부족해서 지금보면 당연한 걸 이해하기까지 오랜 시간이 걸렸다. 그 당시 내가 헷갈렸던 개념들과 해결책을 아래에 정리해 보았다.

2.4.1 웹 서버와 WAS

Nginx에 대해 알아보기 전에 먼저 웹 서버가 무엇인가에 대해 말하자면,

웹 서버는 HTTP또는 HTTPS를 통해 웹 브라우저에서 요청하는 문서나 오브젝트를 전송해주는 서비스 프로그램이다 - 위키백과

라고 나와있다. 또한 웹 어플리케이션 서버에 대한 정의는 다음과 같다.

웹 어플리케이션 서버는 웹 어플리케이션과 서버 환경을 만들어 동작시키는 기능을 제공하는 소프트웨어 프레임워크이다 - 위키백과

즉, 웹서버는 클라이언트에서 요청하는 정적파일을 호스팅해주고, 웹 어플리케이션 서버는 데이터베이스와 연동되어 비즈니스 로직이나 데이터베이스 조회 등 동적인 컨텐츠를 호스팅해주는 역할을 한다. 쉽게 말해서(정확한 건 아니지만) 웹 서버는 흔히 말하는 프론트엔드, 웹 어플리케이션 서버는 백엔드서버라고 볼 수 있다.

대표적인 웹 서버 소프트웨어로는 아파치, 톰캣, Nginx등을 들 수 있고, 장고와 같은 파이썬 어플리케이션이 WAS의 역할을 하기 위해서는 Gunicorn, uWSGI와 같은 WSGI(Web Server Gateway Interface)가 필요하다. WSGI란 간단히 말하자면 웹서버(Nginx 등)와 웹 어플리케이션(Django 등)을 이어주는 인터페이스이다.

2.4.2 Nginx

Nginx란 비동기 이벤트 기반 웹 서버 소프트웨어로 쓰레드/프로세스 기반으로 요청을 처리하는 아파치에 비해 동시접속에 특화된 서버로 평가받는다.

Nginx의 가장 큰 역할은

  • 정적파일을 호스팅하는 HTTP서버
  • WAS서버에 요청을 보내는 리버스 프록시 서버

로, 내가 가장 헷갈렸던 부분이 두번째 리버스 프록시 서버라는 개념이었다.

정적파일을 호스팅한다는 것은 우리가 "www.naver.com"에 접속하면 네이버의 웹 서버에서 index.html과 style.css등의 정적파일을 우리에게 보내주는 것을 뜻한다.

리버스 프록시 서버란 클라이언트가 WAS에 직접 요청을 보내지 않고, 웹 서버에 API요청을 보내면 이를 WAS로 전달해주는 역할을 하는 중계서버이다. 즉, 우리가 "www.naver.com"에서 로그인을 하면 데이터베이스에서 내 이메일과 닉네임등의 정보를 가져와야 하는데, 이때 데이터베이스로 직접 요청을 보내는 것이 아니라 다시 웹서버로 요청을 보내면 알아서 WAS서버로 이 요청을 중계하여 데이터를 받아와 우리에게 넘겨주는 것이다.

Nginx 리버스 프록시 서버의 장점은 여러가지가 있지만, 클라이언트의 요청을 여러대의 WAS로 분산하여 보내주는 로드밸런싱과, 클라이언트는 WAS서버의 IP주소로 직접 요청을 보내는 것이 아닌 웹 서버의 IP주소로만 요청을 보내기 때문에 보안을 강화할 수 있다는 것이 가장 큰 장점으로 꼽힌다.

따라서 프로젝트를 배포하기 위해 EC2 인스턴스에 두 개의 도커 컨테이너를 두고 하나는 장고와 Gunicorn을 통해 WAS를 구축하여 데이터베이스(RDS)와 연결하고, 다른 하나는 Nginx로 웹 서버를 구축하여 WAS 컨테이너와 연결하면 되겠다는 결론이 나왔다.

그런데 당시 리버스 프록시 서버의 개념을 이해하지 못해서 정적파일에서 어떤 주소로 API요청을 보내야 하는지 몰라 한참을 찾아보고 나서야 답을 알게 되었다. 위에서 설명한 대로 클라이언트는 웹서버에게 API요청을 보내면 웹 서버가 알아서 요청을 전달해주기 때문에 그냥 웹서버의 주소로 API요청을 보내면 되는 것이었다.

즉, 우리 서비스의 전반적인 동작 순서는 다음과 같다.

  1. 클라이언트의 웹 브라우저가 웹 서버의 도메인(www.dreung.duckdns.org)에 접속한다.
  2. Nginx 컨테이너는 웹 서버로서 클라이언트에게 정적파일(html, css, js)을 보내준다.
  3. 클라이언트가 정적파일을 렌더링하는 중 동적 데이터에 대한 요청을 웹 서버(www.dreung.duckdns.org/api)에 보낸다.
  4. Nginx 컨테이너는 요청이 들어온 URL를 보고 Django 컨테이너로 요청을 넘겨준다
  5. Django 컨테이너는 요청에 알맞은 응답을 Nginx 컨테이너로 보내준다
  6. Nginx 컨테이너는 Django 컨테이너로부터 들어온 응답을 클라이언트에게 보내준다
  7. 클라이언트는 응답으로 받은 데이터를 포함하여 렌더링을 마친다.

지금부터는 구체적인 Nginx의 사용법을 설명하겠다.

Nginx를 사용하기 위해서는 /etc/nginx/ 경로에 있는 nginx.conf 파일을 수정하여 설정을 변경해야 한다. nginx.conf의 기본값은 다음과 같다.

# /etc/nginx/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 {
    include       /etc/nginx/mime.types;
    
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    
    # /etc/nginx/conf.d/ 경로에 있는 .conf파일의 내용을 이 자리로 불러옴
    include /etc/nginx/conf.d/*.conf
}

위의 설정파일은 로드밸런싱, 프로세스 아이디 등의 서버 자체에 대한 설정을 지정할 수 있고, 설정파일 마지막 줄에 include /etc/nginx/conf.d/*.conf라는 구문은 해당 경로에 있는 .conf확장자를 가진 모든 파일을 불러온다는 의미이기 때문에 라우팅정보나 정적파일 경로등에 대한 설정은 /etc/nginx/conf.d 안에 있는 default.conf를 변경하여 설정할 수 있다.

# /etc/nginx/conf.d/default.conf
server {
    listen       80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

server블록 안에는 서버 설정과 관련된 내용들을 기입한다.

  • listen : 요청을 받을 포트번호
  • location : 라우팅 정보. 요청이 들어오는 url과 호스팅 정보를 적는다

위의 내용을 바탕으로 우리 서비스에 맞게 설정파일의 내용을 변경한 결과는 아래와 같다.

# default.conf

# API요청을 전달할 WAS서버
upstream docker-django {
	server django:8000;	    # django컨테이너의 8000번 포트로 요청을 전달함
}

# HTTP 서버
server {
	listen 80;                          # 80번 포트의 요청을 들을
	server_name dreung.duckdns.org;     # 웹서버의 도메인 주소
	server_tokens off;
	
    # Certbot을 통해 http요청을 https서버로 전달
	location /.well-known/acme-challenge/ {
        	allow all;
        	root /var/www/certbot;
	}

	location / {
        	return 301 https://$host$request_uri;
    	}
}

# HTTPS 서버
server {
	listen 443 ssl;                     # 443번 포트의 요청을 들음
	server_name dreung.duckdns.org;     # 웹서버의 도메인 주소
	server_tokens off;
	
    # SSL 인증정보
	ssl_certificate /etc/letsencrypt/live/dreung.duckdns.org/fullchain.pem;
    	ssl_certificate_key /etc/letsencrypt/live/dreung.duckdns.org/privkey.pem;
    	include /etc/letsencrypt/options-ssl-nginx.conf;
    	ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
	
    # 리버스 프록시 서버 : /api/ 경로로 들어온 요청에 대한 라우팅 설정
	location /api/ {
        # 요청을 전달할 서버의 주소(upstream 블록의 이름)
		proxy_pass		    http://docker-django;
		proxy_redirect		off;
		proxy_set_header	Host $host;
		proxy_set_header	X-Real-IP Sremote_addr;
		proxy_set_header	X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header	X-Forwarded_Host $server_name;
	}
	
    # /static/경로로 들어오는 요청을 아래 경로로 연결
	location /static/ {
        # /frontend/build/static/경로에 있는 정적파일을 호스팅
		alias /frontend/build/static/;      
	}
	
    # root경로로 들어오는 요청을 아래 경로로 연결
	location / {
        # /frontend/build/경로에 있는 정적파일을 호스팅
   		root   /frontend/build/;
    		index  index.html index.htm;
    		try_files $uri $uri/ /index.html;
  	}
}

conf파일을 작성하는 방법을 간단히 정리하자면

  • 정적파일을 호스팅하기 위해서는 location 블럭에 웹 서버로 들어오는 요청의 경로와 서버에서 이 요청을 처리할 경로(정적파일이 저장된 위치)를 연결한다.
  • 리버스 프록시 서버로서의 역할을 구현하기 위해서 upstream 블록에 요청을 전달할 WAS의 주소를 적고, location 블록을 통해 웹 서버로 들어온 요청을 전달할 서버의 정보를 proxy_pass키워드와 함께 작성한다.

위와 같은 방법으로 전체적인 웹 서버와 웹 어플리케이션 서버, 리버스 프록시 서버를 구성하여 서비스를 원활하게 제공할 수 있도록 하였다.

0개의 댓글