웹 서비스의 구조 - django, wsgi, 웹서버부터 클라우드와 https까지

이 포스트는 django를 공부하면서 배운 기본 구조를 정리한 포스트입니다.

django 기반 웹 어플리케이션의 기본 구조

웹 어플리케이션의 구조를 설명하는 간단한 도식도를 정리해 봤다. 먼저 웹 어플리케이션이란, 브라우저에서 사용하는 웹 서비스를 말한다. chrome, safari, firefox, edge 등 다양한 브라우저를 통해서 접속할 수 있다.

Python & Web Framework

가장 먼저, 우리는 django로 백엔드 파트를 구성한다. django는 파이썬으로 만든 웹 프레임워크이다. 사실 파이썬은 socket을 지원하기에, 이론상 socket 하나하나를 다루며 서버 프로그래밍을 할 수 있다. 당연히 이는 별로 하고 싶지 않은 경험일 거다. 그래서 이를 쉽고 편리하게 해주는 게 django와 같은 웹 프레임워크이다.

django VS flask

Python의 대표적인 웹 프레임워크는 크게 세 가지가 있다. django, flask, fastAPI. 그런데 fastAPI는 신생 프레임워크이고, 잘 몰라서 제외하면, 대부분은 django 아니면 flask 중에서 선택한다.

django는 공식 홈페이지에서, 스스로를 Battery powered 웹 프레임워크라고 소개한다. 이는 다시 말해 Full featured, 즉 왠만한 모든 기능을 django가 자체적으로 제공한다는 의미이다. django를 설치하는 순간 알아서 auth, ORM, security 등이 알아서 따라온다.

반면 flask는 micro 웹 프레임워크이다. 이 말은 가벼운 대신 최소한의 것만 담겨 있어서, security, DB, ORM 등은 사용자가 직접 pip install 등으로 설치해서 써야 한다.

보통 flask는 microservice, 혹은 웹 공부용으로 많이 사용한다고 한다. 반면 django는 monolitic한 서비스를 구성할 때, 실제 프로덕션 레벨에서 많이 사용된다고 한다.

구분 설명 사용처
django 모든 기능이 내장되어 있음(battery powered)
  • Monolitic
  • Production 용
flask 최소한의 기능만 내장되어 있음(micro)
  • Microservice

물론 당연히 절대적인 기준은 아니다. Flask도 프로덕션 용에서 많이 사용되고, 모놀리스 아키텍처에서 사용될 수 있다. 위의 표는 일반적으로 이렇다 하는 참고용이다.

WSGI

django는 PostgreSQL, MySQL 등 DB 통신을 하거나, 내부적으로 어떤 통신을 하는 등 연결을 주고 받을 수 있다. 중요한 건 어찌 됐든 그렇게 해서 어떻게 사용자의 브라우저와 정보를 주고받을지이다. 이때 django와 같은 python 웹 프레임워크들은, WSGI라는 인터페이스로 정보를 주고 받는다. Python 문서의 설명에 의하면 WSGI(Web Server Gateway Interface)는 웹 서버 소프트웨어와 파이썬으로 작성된 웹 응용 프로그램 간의 표준 인터페이스라 한다. 표준 인터페이스는 여러 웹 서버에서 WSGI를 지원하는 응용 프로그램을 쉽게 사용할 수 있도록 한다고 설명한다. 참고로 '위스기'(wɪski)라고 많이 읽는 것 같다.

기본적으로 django는 내부적으로 WSGI 인터페이스를 지원하는 서버를 내장하고 있다. 그래서 우리는 별도의 설정 없이 개발 혹은 디버그 과정 중에 WSGI를 사용할 수 있는 것이다. 그러나 실제로 웹 서비스를 배포를 할 때에는, 내장 WSGI 서버로는 부족하고, gunicorn(G-유니콘이라고 읽는다) 등의 WSGI 서버를 별도로 두는 게 좋다.

참고로 python이 아닌 Spring이나 닷넷 진영에서는 WSGI 대신 WAS라는 용어를 사용한다. 기본적으로 WSGI나 WAS나 역할은 같다. django 등의 서비스 로직과 통신하며 동적인 처리를 제공하는 것이다. 여기서 동적 사이트란, 블로그나 게시판과 같이 계속해서 변화하는 사이트를 의미한다. 게시판을 생각해보자. 우리가 글을 하나 작성하면, 게시판에는 새로운 글이 추가된다. 동적으로 변화하는 사이트이다.
동적 사이트의 반대는 정적 사이트이다. 포트폴리오 등을 정적 사이트로 만들 수 있는데, 이 경우에는 소스 코드 자체를 수정하지 않는 한 변화하지 않는다. 정적 사이트는 사실 굳이 django와 같은 웹 프레임워크를 쓸 필요도, WSGI같은 서버를 이용할 필요도 없다.

그러나 django로 만들려는 웹 서비스는 동적 사이트가 대부분이기에, WSGI에 대해서 알아두어야 한다.

Web Server

사실 gunicorn 만으로도 http 프로토콜을 사용하며 웹 사이트를 서비스할 수 있다. 그러나 규모가 커지게 된다면, gunicorn 등의 WSGI 서버로만으로 사용자의 요청을 처리하기에는 한계가 있다. 그래서 우리는 웹 서버를 사용한다. 웹 서버는 기본적으로는 정적 사이트를 제공하는 서버이다. 우리는 사용자의 http 요청을 WSGI로 직접적으로 받지 않고, 웹 서버를 중간에 두어서, gunicorn 등의 WSGI 서버의 부담을 줄이려고 한다. 기본적으로 사용자, 즉 브라우저의 http request를 웹서버가 받아서 처리하고, 이를 WSGI 서버로 보내준다. WSGI 서버에서는 WSGI 프로토콜로 정보를 처리해서 django와 같은 파이썬 코드로 보내준다. response는 그 반대이다.

그 외에도 웹 서버를 이용하는 이유는 여러 가지가 있다. 일단 사용자의 수가 많아졌을 때, 웹서버가 사용자 요청을 분산하는 로드 밸런서(Load Balancer) 역할을 한다. 또한 웹 서버는 리버스 프록시 서버(Reverse Proxy Server)로써, 우리가 만든 백엔드 로직을 숨겨주는 역할을 한다. 프록시 서버의 역할을 생각해 보자. 프록시 서버는 클라이언트가 자신을 숨기고 요청을 전달하기 위해서 중간에 프록시 서버를 둔다. 즉, 클라이언트 -> 프록시 -> 웹 서비스 순서로 요청이 전달되며, 웹 서비스는 클라이언트의 정보를 알지 못한다.

리버스 프록시 서버는 말 그대로 이 과정의 반대이다. 우리가 굳이 웹 서비스의 백엔드 내부 구조를 공개하며 보안상 취약점을 드러낼 필요는 없다. 그래서 웹서버를 중간에 두어 클라이언트가 내부 구조를 알지 못하도록 숨기는 패턴이다.

그 외에도 CDN, Static File Delivery, SSL과 같은 기능도 제공한다. 다만 이후 후술하게 될 클라우드 서비스를 이용하게 되면, Load Balancing, CDN과 같은 기능을 사용해 볼 필요는 없는데, 다만 사용자가 많아졌을 때 이를 핸들링할 Request Buffer의 역할을 할 것이다.

Apache vs Nginx

웹 서버 중 가장 대표적인 양대 산맥은 apache와 nginx이다. 윈도우 서버 기반의 IIS도 있는데, 거의 95% 확률로 리눅스/유닉스 기반 서버를 쓰지 윈도우 서버를 쓰지는 않을 것이므로 IIS는 제외하자.

Apache는 1995년에 처음 릴리즈된, 역사가 매우 오래된 웹서버이다. 순수한 C 언어로 개발됐고, 역사가 오래된 만큼 전세계에서 가장 널리 쓰이는 웹서버이다. 그래서 Apache의 안정성은 이미 수많은 개발자들에 의해서 검증됐다.

NginX 2004년 러시아의 Igor Sysoev가 개발한 차세대 웹서버 엔진이다. Apache와 마찬가지로 C 언어로 만들어졌다. Apache에 비해서 후발 주자인 만큼 점유율은 떨어지지만 이를 NginX가 따라잡는 추세이다. NginX는 기본적으로 apache에 비해서 더 최신의 웹서버인 만큼 성능적인 이점이 있다고 한다.

NginX가 성능적 우위를 차지할 수 있는 가장 큰 이유는 Apache는 다중 프로세스 방식으로 작업을 처리하는데 비해 NginX는 event-driven 방식의 처리 방식으로 작업을 처리하기 때문이다.

Apache는 MPM(Multi Process Module) 방식을 사용한다. Apache의 MPM 모델은 또 프로세스 기반의 prefork, 쓰레드 기반의 worker로 나뉜다.

  • Prefork 방식은 한 요청이 들어올 때마다 새로운 프로세스를 생성하는 방식이다. 응답 프로세스를 미리 띄워놓고 클라이언트 요청이 들어오면 이 전용 응답 프로세스가 요청을 받아서 처리하는 방식이다. 구현이 쉽고 간단하다. 그러나 worker 방식에 비해서 메모리 사용량이 많고 느리다는 단점이 있다(프로세스를 생성하고 관리하는 데 드는 비용을 생각해 보면 된다).
  • Worker 방식은 매번 요청이 들어올 때마다 프로세스를 생성하는 것이 아니라 한 프로세스 내에 여러 개의 쓰레드를 생성하는 방식이다. 메모리를 적게 사용한다는 장점이 있고, Prefork 방식에 비해서 약간 더 빠르다고 한다.

그러나 Prefork가 됐든 Worker 방식이 됐든, 여전히 컴퓨터 자원을 많이 소모하는 것과, 스레드 혹은 프로세스의 작업이 전환될 때 오버헤드가 발생하는 컨텍스트 스위칭 문제가 발생하는 것은 동일하다. NginX는 이를 개선하여 Event Driven 방식을 사용한다.


먼저 NginX는 미리 준비해둔 스레드 풀(Thread Pool)을 사용한다. Event Queue에 새로운 이벤트가 도착하면, Event Loop가 돌면서 큐에 있는 이벤트를 하나씩 꺼내서 스레드 풀에 집어넣는 방식이다. 이 방식으로 NginX의 성능이 입증되자 점차 NginX의 점유율이 높아지고 있다.

NginX의 자세한 작동 방식은 (Lou Park, 2021)의 글을 참고하자. 위 두 사진 출처도 이 블로그에서 가져왔다.

개발 환경

Cloud Computing

기본적인 웹 서비스 환경의 구성이 끝났다면, 이를 호스팅해야 한다. 그런데 이 웹 서버, WSGI 서버 등을 돌릴 물리적인 머신이 필요한데, 우리가 직접 서버를 구축하는 것은 비용상으로도, 관리 면에서도 비효율적이다. 그래서 클라우드 서비스를 사용하면 좋다.

클라우드 서비스의 3대장에는 AWS, Azure, GCP가 있다. 그리고 국내에는 네이버에서 제공하는 NCP도 있다.

Docker와 개발 & 배포 환경 구성

위에서 AWS, Azure, GCP 등의 클라우드 서비스를 이야기했다. 무엇을 이용하든지, 프로그래머는 스스로 어느 한 서비스에 종속적인 프로그램을 작성하는 것을 경계해야 한다. 그래서 Docker를 이용한다.

Docker는 격리된 컨테이너 속에서 환경을 구성할 수 있게 도와주는 프로그램이다. Docker를 이용하면, 컨테이너의 환경이 어디에서나 동일함을 보장받을 수 있다. 컨테이너 이미지만 있으면, 로컬 개발 환경에서든 클라우드 환경에서든 동일한 환경을 구성할 수 있다. 그래서 우리는 맨 처음 그림 1에서 각각 환경을 도커로 감싸준다 (이후에 클라우드 배포 시에는 도커 째로 올린다).

Docker는 VM과 다르게, 컨테이너 기반이기에 매우 효율적으로 독립된 격리 가상 환경을 운영할 수 있다. 예를 들어 한 집 A 안에 독립된 한 집 B를 만든다고 해보자. 그런데 B는 A의 복제된 집인데, 그 안에 화장실이라든지 수도관 등도 똑같이 존재한다. 한 집 안에 이런 것까지 똑같이 복제할 필요가 있겠는가. 화장실이나 수도관 이런 건 공용으로 쓸 수 있지 않을까. 그래서 집을 하나 복제하는 대신, 일부 운영 체제 자원은 공유하고, 필요한 것만 격리시키는 '방'을 하나 만드는 것이 Docker이다(얄팍한 코딩사전, 2020). 이렇게 하면 또 장점이, VM은 미리 컴퓨팅 자원을 할당해서 배정해야 하고, 그 이상으로 자원을 사용할 수가 없는데, Docker는 유동적으로 자원을 조정해서 할당받을 수 있다.

도커에 대한 개념들은 아래 자료를 참고하면 좋다.

최종적으로 아래 그림처럼 웹 어플리케이션이 구성된다.

보안

https

여기서 조금만 더 신경쓴다면, 보안 프로토콜인 https를 사용할 수 있다. https는 기존 http 통신을 대칭키(혹은 공개키) 알고리즘을 사용해서 암호화한다.

또한 이렇게 통신이 암호화됐더라도, 공격자가 클라이언트와 서버 간 통신에 개입해서, 가짜 서버를 만들어서 통신을 가로채는 중간자 공격에 대해서도 생각해야 한다. 예를 들어서 진짜 amazon.com 이 있고, 해커가 가짜 amazon.com을 만들어서 중간자 공격을 시도했을 때, 이를 구분할 수 있는 차이점이 있어야 한다. 이 차이를 두기 위해서 인증서를 발급받는다.

이를 SSL 인증서라고 하는데, 제 3자 기관에서 발급받는다. 제삼자 기관은 웹사이트를 체크해서, 이 사이트가 믿을 만한지, 해커가 만든 것은 아닌 지 체크한 후 인증서를 발급한다.

Digicert와 Let's Encrypt

SSL 인증서를 발급받는 기관은 여러 곳이 있는데, 대표적인 게 Digicert와 Let's Encrypt이다.

Digicert는 굉장히 유명한 인증 기관이다. 글로벌 웹사이트들이 Digicert를 많이 이용하는데, 대표적인 고객이 amazon.com이다.

amazon.com의 SSL 인증서

그러나 개인이 사용하기에는 너무 비싼 가격이 치명적인 단점이다. 기본 Basic Plan의 가격이 2년에 550달러인데, 개인이 지불하기에는 부담되는 가격임은 분명하다.

그래서 Let's Encrypt라는 Organization이 있다. 더 안전한 인터넷 환경을 만들기 위한 모토로, Microsoft, Amazon, IBM 등 여러 대기업들이 돈을 각출해서 운영하는 곳이다. 가장 큰 장점으로는 SSL 인증서를 받는 가격이 무료이다. 무료로 인증서를 발급받아서 사용할 수 있다.

그러나 이들 사이트에서 직접 인증서를 발급받는 방식은, 매번 인증서가 만료될 때마다 새로운 인증서를 발급받아야 한다는 단점도 존재한다. 그래서 이를 보완해 줄 서비스로 CloudFlare를 사용할 수 있다.

CloudFlare

Cloudflare는 웹 보안 분야로 굉장히 큰 기업이다. Cloudflare에서 제공하는 서비스 중에서 유용한 서비스 하나가 바로 Shared SSL이다.

클라이언트가 내 웹서비스 서버로 직접 접근하는 대신 cloudflare의 사이트를 중간에 두어서 접근하게 해서, 인증서를 발급받는 방식이다. 이렇게 하면 인증서의 갱신 없이 Cloudflare를 이용하는 것만으로도 https를 사용할 수 있다.

그런데 이때 주의할 것은, CloudFlare에서는 flexible strict와 full strict가 있는데, full strict를 사용하는 것이 더 권장된다. flexible strict는 자칫하면 겉으로만 SSL 인증을 받아서 안전한 것처럼 보이지만, 실제로는 안전하지 않을 수도 있기 때문이다. 공식 문서에서도 full strict를 사용할 것을 "strongly recommend"란 표현을 쓰면서 강력하게 권장하기도 했다.

Visitor와 Cloudflare 사이만 SSL 인증서가 적용되면 flexible. Cloudflare와 Origin Server 사이에는 SSL이 적용되지 않는다.

이 외에도 DNS, CDN 등 다양한 서비스를 제공한다고 하니, 한 번 관심 있으면 cloudflare에 들어가서 내가 원하는 서비스가 있는지 찾아보자.

 

📚 참고 자료