Skip to content

필사 모드: Spring Boot Embedded Tomcat 완전 가이드: 설정, 최적화, 운영 환경 튜닝

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

목차

1. [Embedded Tomcat 기본 구조](#1-embedded-tomcat-기본-구조)

2. [핵심 설정 옵션](#2-핵심-설정-옵션)

3. [SSL/TLS 설정](#3-ssltls-설정)

4. [HTTP/HTTPS 동시 운영](#4-httphttps-동시-운영)

5. [스레드 풀 튜닝](#5-스레드-풀-튜닝)

6. [Undertow로 교체](#6-undertow로-교체)

7. [커스텀 ErrorPage와 FilterRegistration](#7-커스텀-errorpage와-filterregistration)

8. [Graceful Shutdown](#8-graceful-shutdown)

9. [퀴즈](#9-퀴즈)

1. Embedded Tomcat 기본 구조

Spring Boot는 기본적으로 **Embedded Tomcat**을 내장하고 있어 별도의 WAS 설치 없이 독립 실행 가능한 JAR 파일을 만들 수 있습니다. `spring-boot-starter-web` 의존성을 추가하면 자동으로 Tomcat이 포함됩니다.

spring-boot-starter-web 의존성 트리

spring-boot-starter-web

└── spring-boot-starter-tomcat

├── tomcat-embed-core

├── tomcat-embed-el

└── tomcat-embed-websocket

내장 서버 비교표

| 항목 | Tomcat | Undertow | Jetty |

| ------------- | --------- | --------- | ----- |

| 기본 탑재 | O | X | X |

| 메모리 사용량 | 중간 | 낮음 | 낮음 |

| HTTP/2 지원 | O | O | O |

| WebSocket | O | O | O |

| 성능 (처리량) | 높음 | 매우 높음 | 높음 |

| 안정성/성숙도 | 매우 높음 | 높음 | 높음 |

| 커뮤니티 | 매우 큰 | 중간 | 큰 |

Tomcat은 가장 오래되고 안정적인 서블릿 컨테이너로, 대부분의 엔터프라이즈 환경에서 검증된 선택입니다. Undertow는 낮은 메모리 사용량과 높은 처리량이 필요한 마이크로서비스에 적합합니다.

2. 핵심 설정 옵션

`application.yml`에서 Embedded Tomcat의 거의 모든 설정을 제어할 수 있습니다.

전체 설정 예시

server:

port: 8080

servlet:

context-path: /api

session:

timeout: 30m

cookie:

http-only: true

secure: true

tomcat:

threads:

max: 200 # 최대 스레드 수 (기본값: 200)

min-spare: 10 # 최소 유휴 스레드 (기본값: 10)

max-connections: 8192 # 동시 처리 가능한 최대 연결 수

accept-count: 100 # 연결 대기 큐 크기

connection-timeout: 20000 # 연결 타임아웃 (ms)

max-http-form-post-size: 2MB

max-swallow-size: 2MB

uri-encoding: UTF-8

accesslog:

enabled: true

directory: logs

prefix: access_log

suffix: .txt

pattern: combined

rotate: true

shutdown: graceful

http2:

enabled: true

compression:

enabled: true

mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json

min-response-size: 1024

error:

include-message: always

include-binding-errors: always

주요 파라미터 상세 설명

**threads.max (최대 스레드 수)**

- 기본값: 200

- 동시에 처리할 수 있는 최대 요청 수

- CPU 코어 수와 애플리케이션 특성에 따라 조정 필요

**max-connections**

- 기본값: 8192

- NIO 커넥터에서 동시에 유지할 수 있는 최대 연결 수

- threads.max보다 훨씬 크게 설정하여 Keep-Alive 연결을 효율적으로 관리

**accept-count**

- 기본값: 100

- max-connections 초과 시 OS 레벨 소켓 대기 큐 크기

- 이 값을 초과하면 클라이언트에게 Connection refused 반환

**connection-timeout**

- 기본값: 20000ms (20초)

- 클라이언트가 요청 데이터를 전송하는 데 허용된 최대 시간

3. SSL/TLS 설정

자체 서명 인증서로 테스트

keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 \

-storetype PKCS12 -keystore keystore.p12 -validity 3650 \

-storepass changeit -dname "CN=localhost, OU=Dev, O=Example, L=Seoul, S=Seoul, C=KR"

application.yml SSL 설정

server:

port: 8443

ssl:

key-store: classpath:keystore.p12

key-store-password: changeit

key-store-type: PKCS12

key-alias: tomcat

enabled: true

protocol: TLS

enabled-protocols: TLSv1.2,TLSv1.3

ciphers: TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256

Let's Encrypt 인증서 연동

Let's Encrypt에서 발급받은 PEM 형식의 인증서를 PKCS12로 변환하여 사용합니다.

PEM을 PKCS12로 변환

openssl pkcs12 -export \

-in /etc/letsencrypt/live/yourdomain.com/fullchain.pem \

-inkey /etc/letsencrypt/live/yourdomain.com/privkey.pem \

-out keystore.p12 \

-name tomcat \

-passout pass:yourpassword

운영 환경에서는 인증서 비밀번호를 환경변수로 주입하는 것이 안전합니다.

server:

ssl:

key-store: file:/etc/ssl/keystore.p12

key-store-password: ${SSL_KEYSTORE_PASSWORD}

4. HTTP/HTTPS 동시 운영

Spring Boot는 기본적으로 하나의 포트만 지원하지만, `TomcatServletWebServerFactory`를 커스터마이징하여 HTTP와 HTTPS를 동시에 열 수 있습니다.

두 포트 동시 열기

@Configuration

public class TomcatConfig {

@Bean

public TomcatServletWebServerFactory tomcatServletWebServerFactory() {

TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();

factory.addAdditionalTomcatConnectors(createHttpConnector());

return factory;

}

private Connector createHttpConnector() {

Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);

connector.setScheme("http");

connector.setPort(8080);

connector.setSecure(false);

connector.setRedirectPort(8443);

return connector;

}

}

HTTP to HTTPS 자동 리다이렉트

@Configuration

public class SecurityConfig {

@Bean

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

http

.requiresChannel(channel ->

channel.anyRequest().requiresSecure()

);

return http.build();

}

}

5. 스레드 풀 튜닝

기본 공식

**I/O Bound 애플리케이션 (DB, 외부 API 호출)**

적정 스레드 수 = CPU 코어 수 × (1 + 대기 시간 / 처리 시간)

**CPU Bound 애플리케이션 (이미지 처리, 암호화)**

적정 스레드 수 = CPU 코어 수 + 1

일반적인 웹 애플리케이션은 I/O Bound이므로 CPU 코어 수의 10~20배 정도가 출발점입니다. 부하 테스트를 통해 최적값을 찾아야 합니다.

Java 21 Virtual Thread 적용

Java 21의 Virtual Thread를 활용하면 스레드 풀 크기에 대한 고민을 크게 줄일 수 있습니다.

spring:

threads:

virtual:

enabled: true

또는 직접 설정:

@Bean

public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {

return protocolHandler -> {

protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());

};

}

Actuator로 스레드 모니터링

management:

endpoints:

web:

exposure:

include: metrics,health,threaddump

`/actuator/metrics/tomcat.threads.busy`와 `/actuator/metrics/tomcat.threads.config.max`를 통해 실시간 스레드 사용량을 확인할 수 있습니다.

6. Undertow로 교체

의존성 변경

Undertow 설정 옵션

server:

undertow:

threads:

worker: 200 # 워커 스레드 수 (기본: CPU 코어 × 8)

io: 4 # I/O 스레드 수 (기본: CPU 코어 × 2)

buffer-size: 16384 # 버퍼 크기 (bytes)

direct-buffers: true

max-http-post-size: 10MB

accesslog:

enabled: true

dir: logs

pattern: combined

Tomcat vs Undertow 성능 비교

Undertow는 논블로킹 I/O 기반으로 동시 접속이 많은 환경에서 Tomcat 대비 더 낮은 메모리 사용량과 높은 처리량을 보입니다. 특히 서버 푸시나 WebSocket 집약적인 애플리케이션에서 차이가 두드러집니다. 단순 REST API 서버의 경우 두 서버의 성능 차이는 크지 않습니다.

7. 커스텀 ErrorPage와 FilterRegistration

TomcatServletWebServerFactory Bean 커스터마이징

@Configuration

public class WebServerConfig {

@Bean

public ConfigurableServletWebServerFactory webServerFactory() {

TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();

// 커스텀 에러 페이지 등록

factory.addErrorPages(

new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"),

new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"),

new ErrorPage(Exception.class, "/error/general")

);

// Tomcat 컨텍스트 커스터마이징

factory.addContextCustomizers(context -> {

context.setSessionTimeout(30);

context.setUseHttpOnly(true);

});

return factory;

}

}

커스텀 Filter 등록

@Bean

public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {

FilterRegistrationBean<RequestLoggingFilter> registrationBean = new FilterRegistrationBean<>();

registrationBean.setFilter(new RequestLoggingFilter());

registrationBean.addUrlPatterns("/api/*");

registrationBean.setOrder(1);

return registrationBean;

}

Custom Connector 추가 (AJP 비활성화 예시)

Spring Boot 2.3 이후 AJP 커넥터는 기본적으로 비활성화되어 있으며, 보안상 활성화하지 않는 것을 권장합니다. 필요한 경우에만 아래와 같이 설정합니다.

server:

tomcat:

remoteip:

remote-ip-header: x-forwarded-for

protocol-header: x-forwarded-proto

8. Graceful Shutdown

설정

server:

shutdown: graceful

spring:

lifecycle:

timeout-per-shutdown-phase: 30s

Graceful Shutdown이 활성화되면 종료 신호(SIGTERM)를 받은 후 새 요청은 거부하고, 진행 중인 요청이 완료될 때까지 최대 30초를 기다린 뒤 종료합니다.

쿠버네티스 연동

kubernetes deployment.yaml

spec:

template:

spec:

terminationGracePeriodSeconds: 60 # Kubernetes 레벨 종료 대기

containers:

- name: app

lifecycle:

preStop:

exec:

command: ['/bin/sh', '-c', 'sleep 5'] # 로드밸런서 제거 대기

쿠버네티스의 `terminationGracePeriodSeconds`는 Spring Boot의 `timeout-per-shutdown-phase`보다 5~10초 여유 있게 설정해야 합니다.

Health Check 연동

management:

endpoint:

health:

probes:

enabled: true

health:

livenessstate:

enabled: true

readinessstate:

enabled: true

Readiness Probe가 `OUT_OF_SERVICE`로 전환되어 로드밸런서에서 제외된 후에 실제 종료가 진행되도록 구성합니다.

9. 퀴즈

**정답:** `server.tomcat.max-connections`

**설명:** `max-connections`는 Tomcat이 동시에 유지할 수 있는 최대 연결 수를 제어합니다. NIO 커넥터 기본값은 8192입니다. 이 값은 `threads.max`보다 훨씬 크게 설정하여 Keep-Alive 연결을 효율적으로 처리합니다. `accept-count`는 `max-connections` 초과 시 OS 소켓 레벨의 대기 큐 크기를 의미합니다.

**정답:** 새 요청 수신을 거부하고, 진행 중인 요청이 완료될 때까지 설정된 시간(timeout-per-shutdown-phase) 동안 대기한 후 종료합니다.

**설명:** `server.shutdown=graceful`로 설정하면 SIGTERM 신호를 받은 후 새 연결을 거부하고, 기존에 처리 중인 요청이 완료되길 기다립니다. `spring.lifecycle.timeout-per-shutdown-phase`로 최대 대기 시간을 설정합니다. 쿠버네티스 환경에서는 `terminationGracePeriodSeconds`를 Spring Boot의 타임아웃보다 여유 있게 설정해야 합니다.

**정답:** `spring.threads.virtual.enabled=true` 설정 또는 `TomcatProtocolHandlerCustomizer`를 통해 `Executors.newVirtualThreadPerTaskExecutor()`를 executor로 설정합니다.

**설명:** Virtual Thread는 JDK 21에서 정식 도입된 경량 스레드로, 블로킹 I/O 작업에서도 OS 스레드를 점유하지 않아 대규모 동시 요청 처리에 유리합니다. Spring Boot 3.2 이상에서는 `spring.threads.virtual.enabled=true` 설정만으로 간편하게 활성화할 수 있습니다.

**정답:** `spring-boot-starter-web`에서 `spring-boot-starter-tomcat`을 exclusion으로 제외하고, `spring-boot-starter-undertow`를 추가해야 합니다.

**설명:** Spring Boot의 자동 설정은 클래스패스에 있는 서버 구현체를 자동으로 감지합니다. 따라서 단순히 Undertow를 추가하면 두 서버가 동시에 존재하는 충돌이 발생합니다. Tomcat을 명시적으로 exclusion으로 제거한 후 Undertow를 추가해야 올바르게 교체됩니다.

**정답:** `threads.max`는 실제 요청을 처리하는 스레드의 최대 수이고, `max-connections`는 동시에 유지할 수 있는 TCP 연결의 최대 수입니다.

**설명:** HTTP/1.1의 Keep-Alive 덕분에 하나의 TCP 연결로 여러 요청을 순차적으로 처리할 수 있습니다. 따라서 `max-connections`는 `threads.max`보다 훨씬 크게 설정하는 것이 일반적입니다. 예를 들어 스레드 200개, 연결 8192개로 설정하면 8192개의 연결을 유지하면서 그 중 200개를 동시에 처리할 수 있습니다. `accept-count`는 `max-connections`가 모두 소진되었을 때 추가로 대기시킬 수 있는 요청 수입니다.

현재 단락 (1/239)

1. [Embedded Tomcat 기본 구조](#1-embedded-tomcat-기본-구조)

작성 글자: 0원문 글자: 8,028작성 단락: 0/239