콘텐츠로 이동

NGINX 구성

cloud-init 프로비저닝은 완전히 성능 최적화된 NGINX CDN 엣지 구성을 배포합니다. 이 페이지는 커널 튜닝부터 캐시 동작, 벤더 헤더 주입까지 모든 구성 레이어를 문서화합니다. 모든 설정은 172,540 req/s 피크에서 48시간 연속 부하 테스트를 통해 검증되었습니다.

파일목적
/etc/sysctl.d/99-cdn-tuning.confLinux 커널 네트워크 튜닝
/etc/systemd/system/nginx.service.d/override.confNGINX 파일 디스크립터 한도
/etc/security/limits.d/99-nginx.confwww-data 사용자를 위한 OS 수준 한도
/etc/nginx/nginx.confNGINX 메인 구성 (워커, 버퍼, gzip, 로깅)
/etc/nginx/conf.d/cdn-edge.confCDN 프록시 구성 (캐시, 업스트림, 헤더)

부팅 시 /etc/sysctl.d/99-cdn-tuning.conf를 통해 적용됩니다.

매개변수기본값목적
net.core.somaxconn655354096수신 대기 백로그 큐 크기
net.core.netdev_max_backlog655351000CPU당 수신 패킷 백로그
net.ipv4.tcp_max_syn_backlog65535256SYN 요청 큐 (버스트 시 드롭 방지)
net.ipv4.tcp_tw_reuse12아웃바운드 연결에 TIME_WAIT 소켓 재사용
net.ipv4.ip_local_port_range1024-6553532768-60999임시 포트 범위 (64K vs 28K)
net.core.rmem_max16 MB212 KB최대 수신 소켓 버퍼
net.core.wmem_max16 MB212 KB최대 송신 소켓 버퍼
net.ipv4.tcp_rmem4K/87K/16M4K/131K/6M소켓당 수신 버퍼 (최소/기본/최대)
net.ipv4.tcp_wmem4K/65K/16M4K/16K/4M소켓당 송신 버퍼 (최소/기본/최대)
net.ipv4.tcp_fin_timeout1560FIN_WAIT_2 타임아웃 (빠른 소켓 정리)
net.ipv4.tcp_keepalive_time30072005분 유휴 후 keepalive 프로브 시작
net.ipv4.tcp_slow_start_after_idle01유휴 연결에서 혼잡 윈도우 웜 유지
net.ipv4.tcp_max_tw_buckets2000000~65536최대 TIME_WAIT 소켓 수
fs.file-max2097152다양시스템 전체 파일 디스크립터 한도
vm.swappiness1060캐시 데이터에 스왑보다 RAM 선호
worker_processes auto; # D4s_v5에서 4개 워커 (vCPU당 1개)
worker_rlimit_nofile 65535; # 워커당 파일 디스크립터 한도
events {
use epoll; # Linux 최적화 이벤트 모델
worker_connections 8192; # 4 워커 x 8192 = 32,768 최대 동시 연결
multi_accept on; # 이벤트 루프당 모든 대기 연결 수락
accept_mutex off; # epoll + reuseport와 함께 불필요
}

/etc/systemd/system/nginx.service.d/override.conf의 Systemd 오버라이드는 일치하도록 LimitNOFILE=65535를 설정합니다.

proxy_buffering on;
proxy_buffer_size 16k; # 헤더 버퍼 (대형 CDN 헤더 처리)
proxy_buffers 64 16k; # 연결당 1 MB (64 x 16k)
proxy_busy_buffers_size 256k; # 읽는 중에도 256k를 클라이언트에 전송 가능

256k 바쁜 버퍼 크기는 중요합니다 — 가장 큰 캐시된 응답 크기(Juice Shop은 75KB)를 초과해야 합니다. 원래 64k 설정은 부하 시 직렬화를 유발했습니다.

gzip on;
gzip_comp_level 4; # 균형: 최대 압축의 ~85%에서 ~40% CPU
gzip_min_length 256; # 작은 응답 건너뜀 (gzip 헤더 오버헤드)
gzip_vary on; # 올바른 캐싱을 위한 Vary: Accept-Encoding
gzip_proxied any; # 모든 프록시 응답 압축
gzip_types text/plain text/css text/javascript text/xml
application/json application/javascript application/xml
application/xml+rss application/atom+xml
application/ld+json application/manifest+json
image/svg+xml;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

캐시된 객체에 대한 파일 디스크립터와 메타데이터를 캐싱하여 핫 파일에 대한 stat()open() 시스템 호출을 제거합니다.

keepalive_timeout 65;
keepalive_requests 100000; # 1000이었음 — 90K req/s에서 연결 재활용 유발

keepalive_requests 1000으로 90K req/s에서 연결이 11초마다 재활용되어 TIME_WAIT 소켓이 생성되었습니다. 100,000으로 늘리면 이 문제가 완전히 해소됩니다.

log_format cdn '$remote_addr [$time_local] "$request" $status $body_bytes_sent $upstream_cache_status $request_time';
access_log /var/log/nginx/access.log cdn buffer=256k flush=5s;

cdn 로그 형식은 지연 시간 분석을 위한 $upstream_cache_status (HIT/MISS)와 $request_time을 포함합니다. 버퍼링된 로깅 (256k/5초 플러시)은 높은 부하에서 I/O 오버헤드를 줄입니다.

upstream origin_backend {
server 20.12.78.159:80;
keepalive 256; # 워커당 오리진으로의 영구 연결
keepalive_timeout 60s;
keepalive_requests 1000;
}

4 워커 x 256 keepalive = 오리진에 대한 1,024개의 웜 연결. proxy_http_version 1.1proxy_set_header Connection ""과 결합하여 모든 캐시 미스에서 TCP 핸드셰이크 오버헤드를 제거합니다.

proxy_cache_path /var/cache/nginx/cdn
levels=1:2
keys_zone=cdn_cache:32m
max_size=25g
inactive=24h
use_temp_path=off;
매개변수목적
keys_zone=cdn_cache:32m32MB 공유 메모리~256,000개의 캐시 키와 메타데이터 저장
max_size=25g25GB 디스크 한도30GB OS 디스크의 대부분 사용; 한도에서 LRU 제거
inactive=24h24시간 비활성 타임아웃24시간 동안 액세스하지 않은 콘텐츠 제거 (TTL과 다름)
use_temp_path=off캐시 디렉토리에 직접 쓰기파일 시스템 간 파일 이동 방지
proxy_cache cdn_cache;
proxy_cache_valid 200 301 302 4h;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_lock on;
proxy_cache_lock_age 3s;
proxy_cache_lock_timeout 3s;
proxy_cache_background_update on;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
proxy_ignore_headers Set-Cookie Cache-Control Expires Vary;
proxy_hide_header X-Cache-Status;
proxy_hide_header Vary;
지시문목적
proxy_cache_valid 200 301 302 4h4시간 TTL캐시된 콘텐츠 4시간 유효 (새로 고침 웨이브 처리량 저하 감소를 위해 증가)
proxy_cache_lock on썬더링 허드 방지URL당 캐시되지 않은 경우 오리진에 1개의 요청만 전송; 나머지는 캐시 채우기 대기
proxy_cache_lock_age 3s잠금 타임아웃첫 번째 요청이 3초 내에 완료되지 않으면 다른 요청 허용
proxy_cache_background_update on제로 지연 새로 고침백그라운드에서 새로 고침하는 동안 즉시 오래된 콘텐츠 제공
proxy_cache_use_stale복원력오리진 오류 (500/502/503/504) 또는 업데이트 중에 오래된 콘텐츠 제공
proxy_ignore_headers강제 캐싱오리진 Set-Cookie, Cache-Control, Expires, Vary 무시 — CDN이 TTL 및 Vary 동작 결정 (Juice Shop은 효과적인 캐싱을 방해하는 max-age=0 및 삼중 Vary 헤더를 전송함)
proxy_hide_header X-Cache-Status오리진 헤더 제거오리진 NGINX가 자체 X-Cache-Status를 추가함 — CDN의 캐시 상태만 표시되도록 제거
proxy_hide_header Vary캐시 단편화 방지오리진이 여러 Vary: Accept-Encoding 헤더를 전송. 제거하면 Accept-Encoding 순열에 따른 캐시 키 단편화 방지. NGINX의 gzip_vary on이 올바른 단일 Vary 헤더를 자동으로 추가
proxy_read_timeout 30s;
proxy_connect_timeout 10s;
proxy_send_timeout 15s;

연결 문제에서 빠르게 실패하면서도 부하 시 오리진에 더 많은 응답 시간을 부여합니다.

server {
listen 80 reuseport; # 커널이 모든 4개 워커에 연결 분산
server_name _;
}

reuseportSO_REUSEPORT를 활성화합니다 — 커널이 수신 연결을 워커 프로세스에 직접 분산하여 accept mutex 경합을 제거합니다.

시뮬레이터는 5개의 주요 CDN 벤더 헤더를 동시에 모두 주입합니다. 이를 통해 F5 XC를 어떤 벤더의 “신뢰할 수 있는 클라이언트 IP 헤더”로도 구성하고, 어떤 CDN이 시뮬레이션되든 현실적인 헤더 페이로드를 볼 수 있습니다.

헤더목적
X-Forwarded-For클라이언트 IP 체인표준 포워딩 IP
X-Forwarded-Protohttp 또는 https원래 클라이언트 프로토콜
X-Forwarded-Host원래 호스트명원래 Host 헤더
X-Forwarded-Port서버 포트원래 포트
X-Real-IP클라이언트 IP단일 클라이언트 IP (nginx 관례)
Via1.1 cdn-simulator프록시 식별
ForwardedRFC 7239 형식표준화된 포워딩 헤더
CDN-Loopcdn-simulator루프 감지
헤더목적
True-Client-IP클라이언트 IP원래 최종 사용자 IP 주소
X-Akamai-Edgescape복합 지오 문자열georegion, country_code, region_code, city, dma, pmsa, msa, areacode, county, fips, lat, long, timezone, zip, continent, throughput, bw, network, asnum, network_type
X-Akamai-Device-Characteristics디바이스 속성brand_name, model_name, is_mobile, is_tablet, is_wireless_device, device_os, device_os_version, resolution_width, resolution_height
X-Akamai-Request-ID요청 UUID고유 요청 식별자
헤더목적
CF-Connecting-IP클라이언트 IP실제 클라이언트 IP (항상 존재)
CF-IPCountryUS두 글자 국가 코드
cf-ipcitySan Jose클라이언트 도시
cf-ipcontinentNA대륙 코드
cf-iplatitude / cf-iplongitude좌표지리 위치
cf-region / cf-region-codeCalifornia / CA지역 정보
cf-metro-code807미국 메트로 코드
cf-postal-code95113우편번호
cf-timezoneAmerica/Los_AngelesIANA 시간대
Cf-Ray{request_id}-SJCPOP IATA 코드가 포함된 고유 레이 ID
CF-Visitor{"scheme":"https"}방문자 프로토콜 정보
cf-bot-score85봇 점수 (1=봇, 99=사람)
cf-verified-botfalse알려진 정상 봇 플래그
cf-ja3-hashe7d705a3286e19ea42f587b344ee6865JA3 TLS 지문
cf-ja4t13d1516h2_8daaf6152771_b0da82dd1658JA4 TLS 지문
헤더목적
CloudFront-Viewer-AddressIP:port클라이언트 IP 및 소스 포트
CloudFront-Viewer-CountryUS국가 코드
CloudFront-Viewer-Country-NameUnited States전체 국가명
CloudFront-Viewer-Country-RegionCA지역 코드
CloudFront-Viewer-Country-Region-NameCalifornia전체 지역명
CloudFront-Viewer-CitySan Jose클라이언트 도시
CloudFront-Viewer-Postal-Code95113우편번호
CloudFront-Viewer-Latitude / Longitude37.33530 / -121.89300지리 위치
CloudFront-Viewer-Time-ZoneAmerica/Los_AngelesIANA 시간대
CloudFront-Viewer-Metro-Code807미국 메트로 코드
CloudFront-Viewer-ASN7018자율 시스템 번호
CloudFront-Viewer-Http-Version2.0클라이언트 HTTP 버전
CloudFront-Forwarded-Protohttps원래 프로토콜
CloudFront-Viewer-TLSTLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumedTLS 세부 정보
CloudFront-Viewer-JA3-Fingerprinte7d705a3286e19ea42f587b344ee6865JA3 TLS 지문
CloudFront-Is-Desktop-Viewertrue/false디바이스 감지
CloudFront-Is-Mobile-Viewertrue/false디바이스 감지
CloudFront-Is-Tablet-Viewertrue/false디바이스 감지
CloudFront-Is-SmartTV-Viewerfalse디바이스 감지
X-Amz-Cf-Id인코딩된 IDCloudFront 요청 식별자
헤더목적
Fastly-Client-IP클라이언트 IP실제 클라이언트 IP
Fastly-SSL1TLS를 통한 연결
Fastly-Client1클라이언트 대면 요청 (쉴드 아님)
Fastly-FFcache-sjc3120-SJC캐시 노드 식별
X-Geo-Country-CodeUS국가 (VCL 변수 규칙)
X-Geo-Country-Code3USA세 글자 국가 코드
X-Geo-Country-NameUnited States전체 국가명
X-Geo-CitySan Jose클라이언트 도시
X-Geo-RegionCA지역 코드
X-Geo-Continent-CodeNA대륙
X-Geo-Latitude / X-Geo-Longitude37.3353 / -121.8938지리 위치
X-Geo-Postal-Code95113우편번호
X-Geo-Metro-Code807미국 메트로 코드
X-Geo-ASN7018자율 시스템 번호
X-Geo-Conn-Speedbroadband연결 속도 클래스
X-Geo-Conn-Typewired연결 유형
헤더목적
X-Azure-ClientIP클라이언트 IP클라이언트 IP 주소
X-Azure-SocketIP클라이언트 IPTCP 소켓 소스 IP
X-Azure-Ref인코딩된 참조 문자열문제 해결을 위한 고유 요청 참조
X-Azure-FDIDa0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1Front Door 리소스 식별자
X-Azure-RequestChainhops=1루프 감지 홉 수

응답 헤더 (클라이언트 응답에 추가됨)

섹션 제목: “응답 헤더 (클라이언트 응답에 추가됨)”
헤더목적
X-Cache-StatusHIT, MISS, EXPIRED, STALE, UPDATING이 요청에 대한 캐시 동작
X-CDN-Edgecdn-simulator이 엣지 노드 식별
X-CDN-POPSJC시뮬레이션된 거점(POP) IATA 코드
X-Served-Bycache-sjc3120-SJCFastly 형식의 시뮬레이션된 캐시 노드
X-Request-IDUUID (요청당)고유 요청 식별자

시뮬레이터는 NGINX map 지시문을 사용하여 User-Agent 헤더에서 디바이스 유형을 감지합니다:

  • 모바일: iPhone, Android (태블릿 제외), iPod, BlackBerry, Opera Mini, IEMobile 일치
  • 태블릿: iPad, Android 태블릿, Kindle, PlayBook 일치
  • 데스크톱: 모바일이나 태블릿에 일치하지 않을 경우 기본값

디바이스 유형은 다음에 반영됩니다:

  • CloudFront-Is-Desktop-Viewer / CloudFront-Is-Mobile-Viewer / CloudFront-Is-Tablet-Viewer
  • X-Akamai-Device-Characteristics (is_mobile, is_tablet, is_wireless_device 필드)
Terminal window
ssh azureuser@<PUBLIC_IP>
# 업스트림 서버 업데이트
sudo sed -i 's|server .*;|server NEW_HOST:80;|' /etc/nginx/conf.d/cdn-edge.conf
# 캐시 지우기 및 리로드
sudo rm -rf /var/cache/nginx/cdn/*
sudo nginx -t && sudo systemctl reload nginx

또는 terraform.tfvars에서 origin_host를 업데이트하고 terraform apply를 실행하여 재프로비저닝합니다.

Terminal window
ssh azureuser@<PUBLIC_IP>
sudo rm -rf /var/cache/nginx/cdn/*
sudo systemctl reload nginx
Terminal window
# 객체 수 및 디스크 사용량
sudo find /var/cache/nginx/cdn -type f | wc -l
sudo du -sh /var/cache/nginx/cdn
# 캐시 상태가 포함된 최근 액세스 로그
tail -20 /var/log/nginx/access.log
Terminal window
# 실시간 연결 및 소켓 상태
ss -s
# NGINX 워커 CPU 사용량
top -bn1 | grep nginx
# 업스트림 keepalive 연결
ss -tn state established dst <ORIGIN_IP> | wc -l
# TIME_WAIT 소켓 수
ss -tn state time-wait | wc -l

48시간 연속 부하 테스트에서 검증됨:

지표
피크 처리량 (캐시됨)172,540 req/s
지속 처리량 (캐시됨)85,000-103,000 req/s
피크 연결 수15,000 동시
캐시 적중률100% (웜업 후)
부하 시 메모리1.2GB 안정 (16GB의 8%)
피크 시 CPU100% (4코어 - CPU가 상한선)
48시간 테스트 중 오류0
메모리 누수미감지
연결 누수미감지