본문 바로가기
Spring

Spring, BE | Apache Tomcat 바로 알기 + 웹서버(Nginx와 Apache 비교)를 두는 이유

by Hoya324 2024. 8. 13.

들어가기 전..

프로젝트를 배포하면서 nginx라는 단어가 많이 보였고, github 프로젝트를 염탐(?) 했을 때도 심심치 않게 보였던 단어라 한번 찾아보고 공부하는 시간을 가졌습니다.

nginx가 뭐지?

간단히 nginx는 Apache와 같은 웹서버 정도로 알고 있었는데, 어떤 기능을 가지고 있고 어떤 차이점을 가지고 있는지 자세히 알아보고자 합니다.

먼저 공식 문서(https://nginx.org/en/) 참고했습니다. 공식 문서에서는 다음과 같이 nginx를 소개했습니다.

nginx [engine x]는 HTTP 및 역방향 프록시 서버, 메일 프록시 서버, 그리고 Igor Sysoev 가 원래 작성한 일반 TCP/UDP 프록시 서버 입니다.

 

여기서 알 수 있는 점은 nginx가 프록시 서버의 역할을 한다는 점입니다. 즉, 서버와 클라이언트 사이에 중간 다리 역할을 하면서 통신을 수

행하도록 해주는 응용프로그램이 nginx의 정의라고 이해하면 될거 같습니다.

 

Apache Tomcat가 있는데 왜 Nginx와 같은 Web Server를 쓸까?

이에 대해 알려면 Spring Boot 를 써본 사람들이라면 익숙한 Apache Tomcat이 무엇인지, 왜 Apache Tomcat이 Spring Boot에 내장되어있는지 한번 정리해볼 필요가 있습니다.

Apache Tomcat을 알아보기 전 비슷한 이름의 Apache Server와 Apache Tomcat은 어떤 차이가 있는지 먼저 알아보겠습니다.

Web Server, WAS 그리고 Apache Tomcat

Apache Server와 Apache Tomcat

Apache ServerApache HTTP Server라고도 부르는 Web Server입니다.

Web Server는 정적인 컨텐츠를 제공하고, WAS를 거치지 않고 자원을 바로 전달합니다. 또한 동적인 컨텐츠를 WAS에 전달하고, WAS가 처리한 결과를 받아서 클라이언트에게 응답 메시지를 전송합니다.

Apache HTTP Server는 빠른 속도와 높은 트래픽 처리 능력을 가지고 있습니다.

아래에서 Apache Server와 Nginx를 비교해보도록 하겠습니다.

 

이때 동적인 데이터를 처리할 수 있도록 웹 애플리케이션 로직을 수행해야하는 경우 WAS(Web Application Server)가 등장하게 됩니다.

 

WAS는 일부 Web Server의 기능과 Web Container로 구성됩니다.

Web ServerHTTP 요청을 받아 Web Container로 넘겨줍니다.

Web Container 는 내부 프로그램 로직 처리에 따라 데이터를 만들어 Web Server로 다시 전달하게 됩니다.

 

Java에서는 이를 Servlet을 통해 처리하기 때문에 Web Container를 Servlet Container라고 부르기도 합니다.

 

Spring Boot에서 요청에 따라 데이터를 처리 및 DB에 영향을 줄 수 있었던 이유는 바로 Apache Tomcat이라는 Servlet Container가 내장되어있기 때문입니다.

아래 사진처럼 Spring Boot는 보통 Apache Tomcat이라는 내장 톰캣을 가지고 있습니다. (찾아보면서 알았는데 변경할 수 있다고 하네요.! 링크)

 

 

 

Apache TomcatServlet Container 개념으로 개발되었고, 여느 Web Container와 같이 데이터의 동적인 처리를 할 수 있습니다.

Spring Boot docs를 살펴보면 내장된 Servlet Container에 대해 알 수 있습니다.

https://docs.spring.io/spring-boot/docs/3.0.12-SNAPSHOT/reference/htmlsingle/#web.servlet.embedded-container

이때, Web Server와 같이 외부 요청을 처리하는 기능을 일부 내장하고 있기에 종종 블로그나 정보글에서처럼 WAS라고 간주하기도 합니다.

 

그럼 WAS 역할을 하는 Apache Tomcat이 있으면 Nginx와 같은 Web Server를 따로 둘 이유가 없는 것 아닐까요?

 

아쉽게도 Apache Tomcat은 완전한 WAS의 역할을 하고 있지 않다고 합니다.

이번엔 이에 대해 좀더 알아보겠습니다.

왜 Apache Tomcat은 완전한 WAS라고 할 수 없을까?

Spring Boot는 Client 요청을 Servlet의 단위로 처리하게 됩니다.

여기서 Servlet은 “클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술” 이라고 정의할 수 있습니다. 즉, 웹을 만드는데 사용되는 자바로 코딩하듯 웹 브라우저용 출력 화면을 만드는 기술입니다.

 

자바로 구현된 CGI(Common Gateway Interface)라고 한다네요!
출처: 망나니개발자님의 블로그 https://mangkyu.tistory.com/14

 

이때, Servlet Container가 이 Servlet 을 Client 요청에 연결하고, 생명 주기를 관리해주게 됩니다.

출처 :  https://medium.com/@hpdeshmukh/understanding-the-servlet-life-cycle-and-types-of-servlets-in-java-web-development-d433a8fe21e7

 

Apache Tomcat은 기본적으로 Servlet Container 이긴 하지만, Web Server가 자체적으로 내장되어있으므로 WAS로 취급받는다고 했습니다. WAS처럼 외부 HTTP 요청을 받을 수 있는거죠.

그럼에도 Apache Tomcat이 WAS라 불릴 수 없는 이유는 보통의 WAS가 가지는 분산 트랜잭션과 같은 기능이 제외되었기 때문입니다.

이는 Apache Tomcat이 가볍고 빠른 웹 애플리케이션 서버를 목표로 설계되었고, Tomcat은 Java EE 전체 스택을 구현한 서버가 아니기 때문입니다.

분산 트랜잭션 관리와 같은 기능은 일반적으로 Java EE 전체 스택 서버(JBoss, WebLogic, WebSphere 등)에서 제공하는 기능입니다.

 

만약 이런 기능을 원한다면, JTA(외부 트랜잭션 관리자)와의 결합을 통해 확장할 수 있습니다.

분산 트랜잭션(Distributed transaction)이란?

여러 시스템이나 데이터베이스에 걸쳐 발생하는 트랜잭션을 일관되게 처리하기 위한 메커니즘을 의미합니다.
하나의 트랜잭션이 여러 분산된 리소스를 사용할 때, 이 트랜잭션이 모든 리소스에서 성공적으로 완료되거나 전부 실패하여 시스템의 일관성을 유지해야 합니다. 분산 트랜잭션의 주요 목표는 ACID 특성을 보장하는 것입니다.
이는 2단계 커밋(2PC, Two-Phase Commit) 과정을 거칩니다.

 

Web ServerWAS의 역할, Spring Boot에 내장된 Apache Tomcat의 역할을 모두 이해했으니 이제 본격적으로 그럼에도 Web Server를 사용하는 이유에 대해 알아보겠습니다.

 

Web Server(Nginx, Apache)를 쓰는 이유

사실 저는 WAS나 Apache Tomcat과 같이 Web Server의 역할을 같이 수행한다면, Web Server가 필요없는 것이 아닌가? 라는 생각이 들었습니다.

 

자료를 찾아보며 Web Server를 Web Application server 앞단에 두는 이유에 대해 제가 이해한 내용을 바탕으로 한번 정리해보겠습니다.

 

WAS만 사용한 경우

먼저, WAS만 사용한 경우 어떻게 되는지 알아보겠습니다.

우리는 Spring Boot를 사용하면서 별다른 세팅을 하지 않아도 (위에서도 알아봤듯) HTTP 요청을 받아 DB에서 데이터를 동적으로 처리하여 결과를 반환할 수 있다는 것을 알 수 있습니다.

JSP나 Thymeleaf와 같은 템플릿을 사용해 SSR까지 해봤다면 정적인 데이터와 동적인 데이터 모두 처리할 수 있다는 것을 알 수 있습니다.

 

그러나 이 만능인 것 같은 WAS가 정적인 데이터까지 모두 처리하게 된다면, 서버에 부하가 커지게 되고, 동적인 컨텐츠의 처리가 지연될 수 있습니다. 이는 서비스 전반에 영향을 미칠 수 있을겁니다.

또한, WAS가 장애가 생기는 경우에는 하나의 서버로 돌아가기 때문에, WAS에서는 순간적으로 비지니스 로직을 처리하지 못하게 되고, 심지어는 정적 화면으로 대처하는 것조차 불가능하게 됩니다.

 

물론, 적은 트래픽이 요구되는 경우에는 WAS만으로 충분할 수 있지만, 트래픽이 몰리거나, 많은 양의 트래픽을 처리해야하는 경우 Web Server와 WAS를 사용한다면 해결이 가능하게 됩니다.

 

Web Server와 WAS를 분리해서 쓰면 가질 수 있는 이점(아키텍쳐 선정 이유)

이제 Web Server를 써야하는 이유를 알았으니, 그로 인해 어떤 이점을 가질 수 있는지 알아보겠습니다.

프로젝트는 어떤 방향으로든 확장될 수 있다는 것을 가정해야합니다. (대박날지도 모르니..ㅎㅎ)

 

만약 Web Server와 WAS를 분리하면, 정적 자원이 더 필요한 경우엔 Web Server를, WAS의 자원이 부족하다면 WAS 서버를 증설하면 됩니다. 즉, 유연하게 필요한 부분을 확장시킬 수 있게 되는 것이죠.

 

이러한 확장성이 아니더라도 Web Server를 사용하게 되면 여러 이점을 챙길 수 있습니다.

 

1. 로드 밸런싱 및 트래픽 관리

로드 밸런싱: 웹 서버는 여러 WAS 인스턴스 간에 트래픽을 분산시켜서 로드 밸런싱을 할 수 있습니다. 이를 통해 WAS의 부하를 고르게 분산시켜 성능을 최적화하고, 단일 WAS 인스턴스에 과부하가 걸리는 것을 방지할 수 있습니다.

트래픽 관리: 웹 서버는 정적 콘텐츠(HTML, CSS, 이미지 등)를 직접 제공하고, 동적 콘텐츠에 대한 요청만 WAS로 전달합니다. 이로 인해 WAS의 부담이 줄어들어 성능이 향상됩니다.

 

2. 보안 향상

방화벽 역할: 웹 서버는 WAS 앞단에 위치하여 방화벽 역할을 수행할 수 있습니다. 이를 통해 클라이언트의 직접적인 접근을 차단하고, WAS를 외부 공격으로부터 보호할 수 있습니다.

SSL 종료: 웹 서버에서 SSL/TLS 암호화를 처리하고, 암호화되지 않은 트래픽을 내부 네트워크에서 WAS로 전달할 수 있습니다. 이로 인해 WAS의 암호화 처리 부담이 줄어들어 성능이 향상됩니다.

 

3. 정적 콘텐츠의 효율적 제공

웹 서버는 정적 콘텐츠를 매우 효율적으로 제공할 수 있습니다. WAS는 주로 동적 콘텐츠 생성을 담당하기 때문에, 정적 콘텐츠와 동적 콘텐츠를 분리하여 처리하는 것이 성능 면에서 유리합니다.

이러한 분리는 정적 콘텐츠에 대한 요청을 웹 서버에서 처리하게 하여 WAS의 리소스를 절약하고, WAS가 동적 요청 처리에 집중할 수 있게 합니다.

 

4. 확장성 및 유지보수성 향상

서비스 확장성: 웹 서버를 사용하면 여러 WAS 인스턴스를 쉽게 추가하거나 제거할 수 있습니다. 이는 웹 애플리케이션의 확장성을 높여주며, 트래픽이 급증할 때 시스템을 유연하게 대응할 수 있게 합니다.

로드 밸런싱이란?

만약 트래픽이 엄청나게 대규모인 경우에는 수직 확장(Scale up)이 아닌 수평 확장(Scale out)을 하게 되는데 이때 필수인 작업이 로드밸런싱입니다.
즉, 클라이언트의 요청을 여러 서버에 분산시켜 각 서버에 균등하게 부하를 나누는 기술입니다.
로드 밸런싱은 네트워크 계층의 7 Layer 를 기준으로 나눠져있는데 L4와 L7만 보면 다음과 같습니다.
L4 : Transport Layer, 그러니까 IP와 Port 레벨에서 로드 밸런싱하는 것
L7 : 어플리케이션 레벨에서 로드밸런싱을 하는것

 

유지보수 용이성: 웹 서버에서 WAS로의 요청 경로를 설정할 수 있기 때문에, WAS의 유지보수나 업그레이드 작업을 할 때도 트래픽을 다른 WAS로 쉽게 리디렉션할 수 있습니다.

 

5. 캐싱 및 성능 최적화

웹 서버는 자주 요청되는 정적 파일이나 동적 페이지의 결과를 캐싱하여 클라이언트 요청에 대해 더 빠르게 응답할 수 있습니다. 캐싱을 통해 WAS로 전달되는 요청 수를 줄일 수 있고, 이를 통해 전체적인 응답 속도가 빨라질 수 있습니다.

 

6. 애플리케이션 분리 및 관리

웹 서버는 여러 웹 애플리케이션을 동일한 도메인에서 분리된 경로로 제공하거나, 서로 다른 서브도메인으로 제공할 수 있습니다. 이는 하나의 WAS에서 여러 애플리케이션을 실행할 때의 충돌이나 복잡성을 줄여줍니다.

 

7. 유연한 아키텍처

웹 서버와 WAS를 분리함으로써, 각각의 역할과 기능에 따라 최적화된 설정을 할 수 있습니다. 예를 들어, 웹 서버는 가벼운 트래픽 처리와 보안 설정에 집중하고, WAS는 비즈니스 로직과 데이터 처리에 집중하는 등 역할을 분담할 수 있습니다.

 

 

이렇듯 Web Server를 설정하면 여러 장점을 가질 수 있게 됩니다.

 

가끔 Web Server를 설정해야만 HTTPS를 설정할 수 있는 것 아닌가? 할 수도 있습니다만(제가 그랬습니다..ㅎㅎ) Spring을 예로 들면 Spring Security를 통해 인증서를 발급받아 간단하게 설정이 가능합니다.

하지만, 만약 HTTPSWAS로만 설정하게 된다면 간단하고 관리가 쉽겠지만, SSL/TLS 암호화 및 복호화 작업은 서버에 부담을 줄 수 있습니다.

WAS가 이 작업을 직접 처리하면 성능에 영향을 줄 수 있고, WAS가 외부 네트워크에 바로 노출되기 때문에 보안적으로 많은 취약점을 가지게 된다고 합니다.

 

이런 이유로 프로젝트에서 Web Server를 WAS에서 분리한 아키텍쳐를 사용하기로 결정했습니다.

Client - Web Server - WAS - DB 아키텍쳐

 

Apache VS Nginx

아키텍쳐를 선정했으니, Web Server로 Apache Server를 사용할지, Nginx를 사용할지 결정해보겠습니다.

Apache 설계 아키텍쳐

  • 프로세스 기반 접근 방식(네이티브 스레드)
  • 각 연결을 하나의 프로세스 또는 네이티브 스레드로 처리하는 방식
  • 매 요청마다 스레드를 생성, 할당해야하므로 리소스를 많이 잡아먹음
  • 장점: 명확한 코드를 작성할 수 있는 장점이 있으며, 커널이 프로세스와 스레드를 코어에 할당하므로 처리 균형을 유지할 수 있습니다.
  • 단점: 많은 커널 및 프로세스 컨텍스트 전환이 발생해 성능이 저하되는 단점이 있습니다.

출처: https://www.iij.ad.jp/en/dev/tech/mighttpd/

 

Apache 특징

출처:  https://anggeum.tistory.com/entry/Apache-MPM-설정-Prefork-Worker-Event

 

 

 

Apache는 각 요청당 하나의 스레드가 처리하는 구조이므로 OS가 thread pool을 관리하듯, 프로세스를 미리 생성해놓는 PreFork 방식을 선택했습니다.

이를통해 요청마다 스레드가 생성되는 비용을 줄였고, 일정량의 생성된 자식 프로세스를 사용하다가 생성한 모든 자식 프로세스가 모두 할당되면 새로이 프로세스를 만들어서 할당해주어서 확장성이 매우 용이했습니다.

이로 인한 장점과 작동원리는 링크로 남겨두겠습니다 관심있으신 분들은 더 찾아보면 좋을거 같아요!
https://httpd.apache.org/docs/2.4/mod/prefork.html

Apache 문제점

PreFork 방식은 프로세스를 미리 만들어두어 생성 비용을 줄일 수 있었지만, 각 프로세스는 메모리를 공유하지 않는 방식을 택하고 있었습니다. 이로 인해 트래픽이 많이 몰리면 메모리가 부족하게 되고 이런 문제를 C10K 문제(Connection 10000 Problem)이라고 불렀습니다.

Apache는 설계의 문제로 많은 커넥션에서 요청이 들어오게 되면 CPU 부하가 높아지게 되고, 이로 인해 Contex Switching이 발생합니다. 즉, 결국 생성해둔 프로세스에 요청이 많이 몰린 경우에 오버헤드가 더 많이 발생할 수 밖에 없습니다.

이제 Apache의 문제를 해결하기 위한 Nginx가 등장합니다. Nginx에 대해 알아보겠습니다.

 

Nginx 설계 아키텍쳐

  • 이벤트 중심 접근 방식
  • 하나의 스레드 내에서 여러 요청을 처리하는 구조
  • 장점: 프로세스 전환이 없어서 성능이 향상됩니다.
  • 단점: 하나의 프로세스만 사용하기 때문에 하나의 코어만 활용할 수 있고, 비동기 프로그래밍으로 인해 코드가 조각나고 명확성이 떨어집니다
  • 비동기 Event-Driven 구조: Event Handler에서 비동기 방식으로 먼저 처리되는 요청을 진행

출처:  https://www.iij.ad.jp/en/dev/tech/mighttpd/

 

Nginx 동작원리

Nginx는 C10K 문제를 해결하기 위해 만들어졌고, 이를 위해 Event-Driven 구조를 가지게 됩니다.

 

 

이벤트 기반의 아키텍쳐를 가지면서 Nginx는 단일 스레드에서 다수의 요청을 비동기적으로 처리할 수 있게 됐습니다. 즉, 여러 요청이 동시에 들어오더라도, 각 요청이 독립적으로 처리되어 Apache와 달리 성능이 크게 향상됩니다.

 

또한, Nginx는 비동기 처리가 가능하기 때문에 요청이 완료될 때까지 기다릴 필요 없이 다른 요청을 동시에 처리할 수 있습니다.

 

Nginx는 하나의 스레드가 여러 요청을 처리하는 반면, 여러 프로세스를 사용할 수 있습니다. 이로인해 적은 메모리 사용과 높은 처리 성능이 가능하게 되었습니다.

 

간단하게 정리한 동작 방식

  1. 마스터 프로세스와 워커 프로세스
    • 마스터 프로세스: NGINX는 시작할 때, 마스터 프로세스가 실행됩니다. 마스터 프로세스는 설정 파일을 읽고, 워커 프로세스를 생성하는 역할을 합니다.
    • 워커 프로세스: 마스터 프로세스가 설정 파일을 기반으로 여러 워커 프로세스를 생성합니다. 각 워커 프로세스는 클라이언트의 요청을 처리합니다.
  2. 커넥션 처리
    • 워커 프로세스는 각자 지정된 listen 소켓을 할당받습니다. 클라이언트가 요청을 보내면, 워커 프로세스는 해당 소켓을 통해 커넥션을 형성하고 요청을 처리합니다.
    • 각 커넥션은 설정된 Keep Alive 시간만큼 유지됩니다. 워커 프로세스는 하나의 커넥션에만 국한되지 않고, 여러 커넥션을 관리합니다. 요청이 없으면 새로운 커넥션을 형성하거나 기존의 다른 커넥션으로부터 들어온 요청을 처리합니다.
  3. 이벤트 처리
    • 워커 프로세스는 OS 커널로부터 큐 형식으로 이벤트를 전달받습니다. 이벤트는 비동기 방식으로 대기하면서 처리됩니다.
    • 요청이 오래 걸려서 다른 요청 처리에 영향을 미치는 상황을 방지하기 위해, NGINX는 스레드 풀을 사용하여 긴 작업을 별도로 처리합니다. 이로 인해 다른 이벤트의 처리가 지연되지 않도록 합니다.
  4. 성능 최적화
    • NGINX는 하나의 프로세스 내에서 여러 스레드를 사용하여 요청을 처리합니다. 스레드는 메모리를 공유하므로, 컨텍스트 스위칭 비용이 줄어들어 성능이 향상됩니다.

이렇듯 메모리를 적게 사용하면서 여러 요청을 처리하는데 적합한 Nginx는 AWS 프리티어 EC2(t2.micro)를 사용하는 이 프로젝트에 적합하다고 판단했습니다.

 

마치면서..

프로젝트의 아키텍쳐를 선정하고, Web Server 종류를 선정하면서 읽었던 글들과 생각을 정리해보았습니다.

 

Nginx를 왜 해야하는가? 라고 한다면 저는 Spring 내부 Apache Tomcat의 부담을 줄이고, 프로젝트의 확장으로 인한 로드 밸런싱(Scale out)을 감안했고, 프론트의 정적 자원과 Spring-DB를 거치는 동적 자원간의 분리를 위해서라고 할 것 같습니다.

Apache도 선택지 중에 있었지만 프리티어처럼 메모리가 적은 CPU를 사용하는 프로젝트였기에 최대한 줄이고자 했다는 말도 덧붙일 것 같네요!

아래는 Nginx를 설정하다가 마주한 cors 오류 중 하나인데, 해당 요청에 대한 Origin 설정을 Nginx와 Spring에서 모두 해두었기에 생긴 오류였습니다.

Access to XMLHttpRequest at 'https://domain.com/api/~' from origin 'https://fe.app' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:3000, ...', but only one is allowed.

 

Nginx와 Spring 간의 소통은 사실상 local처럼 돌아가기 때문에 Spring과 Nginx 중 뒷단에 위치한 Spring의 cors 설정을 없애면 되는 작업이었습니다.

 

만약 Nginx에 대한 이해도가 부족했다면 한참 고생하지 않았을까~ 하는 마음에 마치는 글에 적어보았습니다.

적용하는 기술에 대한 이유와 이해가 필수라는 사실을 좀더 깨달았던 작업이었습니다.


공부하며 정리한 글이기에 부족한 부분이 있을 수 있습니다! 많은 응원과 피드백 부탁드립니다.☺️

Reference

https://tecoble.techcourse.co.kr/post/2021-05-24-apache-tomcat/

https://velog.io/@cjy9306/Spring-Boot의-Embedded-Tomcat

https://velog.io/@jjonggang/Spring-Boot-Nginx를-이용하여-Spring-Boot를-80번-포트로-프록시하기

https://bombo96.tistory.com/65#Apache VS NGINX -1

https://mangkyu.tistory.com/14

https://www.iij.ad.jp/en/dev/tech/mighttpd/

https://anggeum.tistory.com/entry/Apache-MPM-설정-Prefork-Worker-Event

https://httpd.apache.org/docs/2.4/mod/prefork.html