NGINX는 처리하는 모든 트랜잭션에 대해 매우 상세한 로그를 기록할 수 있습니다. 이러한 로그를 액세스 로그라고 하며, 사용자 지정 가능한 로그 파일 형식을 사용하여 서비스나 위치별로 기록되는 세부 정보를 정밀하게 조정할 수 있습니다.
기본적으로 NGINX는 처리하는 모든 트랜잭션을 로그에 기록합니다. 규정 준수나 보안을 위해 필요할 수 있지만, 트래픽이 많은 웹사이트에서는 생성되는 데이터의 양이 감당하기 어려울 정도로 많아질 수 있습니다. 이 글에서는 다양한 기준에 따라 트랜잭션을 선택적으로 기록하는 방법과, 이를 활용해 요청에 관한 데이터 포인트를 빠르고 가볍게 샘플링하는 방법을 설명합니다.
별도로 명시된 경우를 제외하면 이 게시물은 NGINX Open Source와 NGINX Plus 모두에 적용됩니다. 읽기 쉽도록 이 글에서는 모두 NGINX라고 지칭하겠습니다.
배경 – NGINX 액세스 로그 구성 간략히 살펴보기
NGINX 액세스 로그는 log_format 지시어를 사용하여 정의합니다. 서로 다른 이름의 로그 형식을 여러 개 정의할 수 있습니다. 예를 들어, 전체 로그 형식에는 main이라는 이름을, 요청에 관한 세 가지 데이터 항목을 기록하는 축약 로그 형식에는 notes라는 이름을 지정할 수 있습니다:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format notes '$remote_addr "$request" $status';로그 형식에서는 NGINX 변수 및 로깅 시점에 계산되는 기타 값을 참조할 수 있습니다.
그런 다음 access_log 지시어를 사용하여 트랜잭션이 완료되면 NGINX가 이를 로그에 기록하도록 합니다. 이 지시어는 로그 파일의 위치와 사용할 로그 형식을 지정합니다:
access_log /var/log/nginx/access.log main;기본적으로 NGINX는 다음 구성을 사용하여 모든 트랜잭션을 기록합니다:
access_log logs/access.log combined;자체 access_log를 정의하면 기본 액세스 로그를 재정의(대체)합니다.
조건부 로깅
경우에 따라 특정 요청만 로그에 기록하고 싶을 수 있습니다. 이때는 다음과 같이 조건부 로깅을 사용합니다:
server {
listen 80;
set $logme 0;
if ( $uri ~ ^/secure ) {
set $logme 1;
}
# Auditors require an additional log for requests to /secure
access_log /var/log/nginx/secure.log notes if=$logme;
# If we have a global access log, we need to re-declare it here
access_log /var/log/nginx/access.log main;
location / {
# ...
}
}액세스 로그는 상속되지 않습니다
액세스 로그 설정은 누적되거나 상속되지 않습니다. 한 컨텍스트의 access_log 지시어는 상위 컨텍스트에 선언된 액세스 로그를 재정의(대체)합니다.
예를 들어 URI /secure로 향하는 트래픽에 대한 추가 정보를 기록하려면 location /secure {...} 블록에 액세스 로그를 정의할 수 있습니다. 이 액세스 로그는 구성의 다른 위치에 정의된 일반 액세스 로그를 대체합니다.
The example in the 이전 섹션의 예에서는 이 문제를 해결합니다. 동일한 컨텍스트에서 두 개의 액세스 로그를 사용하며, 조건부 로깅을 통해 /secure에 대한 요청을 전용 로그 파일에 기록합니다.
액세스 로그의 과제
웹사이트 트래픽에 관한 몇 가지 통계 정보를 파악하려고 한다고 가정해 보겠습니다:
- 일반적으로 사용자의 지역별 분포는 어떻게 되나요?
- 내 사용자들은 어떤 SSL/TLS 암호화 스위트와 프로토콜을 사용하나요?
- 웹 브라우저별 비율은 어떻게 되나요?
일반 액세스 로그는 이러한 정보를 기록하기에 적합하지 않은 경우가 많습니다. 분석에 필요한 추가 필드로 액세스 로그가 불필요하게 복잡해지는 것을 원치 않을 수 있으며, 트래픽이 많은 사이트에서는 모든 트랜잭션을 기록하는 데 따른 오버헤드가 지나치게 커질 수 있습니다.
이 경우 제한된 필드 집합을 별도의 로그에 기록할 수 있습니다. 시스템 부하를 줄이기 위해 요청 중 일부만 샘플링할 수도 있습니다.
샘플링 기법
요청의 1% 샘플링
다음 구성에서는 $request_id 변수를 각 요청의 고유 식별자로 사용합니다. 또한 split_clients 블록을 사용하여 요청의 1%만 로깅하는 방식으로 데이터를 샘플링합니다:
split_clients $request_id $logme {
1% 1;
* 0;
}
server {
listen 80;
access_log /var/log/nginx/secure.log notes if=$logme;
# ...
}고유 사용자 중 1% 샘플링
각 사용자(또는 사용자 중 1%)로부터 User-Agent 헤더와 같은 데이터 포인트 하나를 샘플링한다고 가정해 보겠습니다. 모든 요청에서 단순히 샘플링하면 요청을 많이 생성하는 사용자가 데이터에 과도하게 반영되므로 그렇게 할 수 없습니다.
세션 쿠키가 있는지 감지하기 위해 map 블록을 사용합니다. 이를 통해 요청이 신규 사용자에게서 온 것인지, 이전에 방문한 사용자에게서 온 것인지 알 수 있습니다. 그런 다음 신규 사용자의 요청만 샘플링합니다:
map $cookie_SESSION $logme {
"" $perhaps; # If the cookie is missing, we log if $perhaps
default 0;
}
split_clients $request_id $perhaps {
1% 1; # $perhaps is true 1% of the time
* 0;
}
server {
listen 80;
access_log /var/log/nginx/secure.log notes if=$logme;
# Optional: If the application does not generate a session cookie, we
# generate our own
add_header Set-Cookie SESSION=1;
# ...
}고유한 항목 샘플링
하지만 모든 클라이언트가 세션 쿠키를 따르는 것은 아닙니다. 예를 들어 웹 스파이더는 쿠키를 무시할 수 있으므로, 웹 스파이더가 보내는 모든 요청이 새로운 사용자의 요청으로 식별되어 결과가 왜곡될 수 있습니다.
새로운 대상을 처음 발견했을 때 해당 요청을 샘플링할 수 있다면 좋지 않을까요? 여기서 대상은 새로운 IP 주소, 새로운 세션 쿠키 값, 새로운 User-Agent 헤더, 이전에 본 적 없는 호스트 헤더 또는 이들의 조합일 수도 있습니다. 이렇게 하면 각 대상의 데이터를 한 번만 샘플링할 수 있습니다.
분명히 상태(지금까지 확인한 대상의 목록)를 저장해야 하며, 이를 위해 NGINX Plus의 키-값 저장소를 사용합니다. 키-값 저장소는 변수를 사용하여 NGINX Plus 구성에서 액세스할 수 있는 인메모리 키-값 데이터베이스를 유지합니다. 이 데이터베이스는 선택적으로 항목 자동 만료(timeout 매개변수), 영구 저장소(state), 클러스터 동기화(sync)를 지원합니다. 아직 저장소에 없는 각 대상에 대해 요청을 기록하고 해당 대상을 저장소에 추가하여 다시 기록되지 않도록 합니다.
NGINX Plus R18 이상에서는 트랜잭션을 처리하는 동안 키-값 쌍을 설정하는 것이 매우 간단합니다:
# Define a keyval zone with appropriate parameterskeyval_zone zone=clients:80m timeout=3600s;
# Create a variable $seen for each unique $remote_addr
keyval $remote_addr $seen zone=clients;
log_format notes '$remote_addr "$request" $status';
server {
listen 80;
# if $seen is empty, update the keyval (set $seen 1;) and log this
# request (set $logme 1;)
# Otherwise, $logme is not set and we don't log the request
# Note that $seen resets to "" after the configured timeout
if ($seen = "") {
set $seen 1;
set $logme 1;
}
access_log /var/log/nginx/secure.log notes if=$logme;
location / {
return 200 "All OK: -$seen-$logme-\n";
}
location /api {
api;
}
}실제 사례 – TLS 매개변수 샘플링
이 글은 현실에서 마주한 한 가지 문제에서 출발했습니다. 레거시 기기를 사용하는 이용자를 배제하지 않으면서 모범 사례에 따라 TLS를 구성하려면 어떻게 해야 할까요?
TLS 모범 사례는 끊임없이 변화합니다. TLS 1.3은 1년 전에 표준으로 승인되었지만, 여전히 많은 클라이언트가 이전 버전의 TLS만 지원합니다. 암호화 스위트가 ‘안전하지 않다’고 판정되어 폐기되더라도 구형 구현에서는 여전히 이에 의존하며, ECC 인증서는 RSA보다 뛰어난 성능을 제공하지만 모든 클라이언트가 ECC를 지원하는 것은 아닙니다. 많은 TLS 공격은 암호화 스위트 협상 핸드셰이크를 가로채 클라이언트와 서버가 보안 수준이 낮은 암호화 스위트를 선택하도록 강제하는 ‘중간자’ 공격에 의존합니다. 따라서 취약하거나 오래된 암호화 스위트를 지원하지 않도록 NGINX Plus를 구성하는 것이 중요하지만, 그렇게 하면 레거시 클라이언트가 제외될 수 있습니다.
다음 구성 예에서는 각 TLS 클라이언트를 샘플링하여 SSL 프로토콜, 암호화 스위트 및 User-Agent 헤더를 기록합니다. 각 클라이언트가 지원하는 최신 프로토콜과 가장 안전한 암호화 스위트를 선택한다고 가정하면, 샘플링된 데이터를 평가하여 이전 프로토콜과 암호화 스위트에 대한 지원을 제거할 경우 제외되는 클라이언트의 비율을 파악할 수 있습니다.
각 클라이언트는 IP 주소와 User-Agent의 고유한 조합으로 식별하지만, 세션 쿠키나 다른 방법으로 식별해도 동일하게 잘 작동합니다.
log_format sslparams '$ssl_protocol $ssl_cipher '
'$remote_addr "$http_user_agent"';
# Define a keyval zone with appropriate parameters
keyval_zone zone=clients:80m timeout=3600s;
# Create a variable $seen for each unique combination of $remote_addr and
# 'User-Agent' header
keyval $remote_addr:$http_user_agent $seen zone=clients;
server {
listen 443 ssl;
# default NGINX SSL configuration
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
if ($seen = "") {
set $seen 1;
set $logme 1;
}
access_log /tmp/sslparams.log sslparams if=$logme;
# ...
}그러면 다음과 같은 항목이 포함된 로그 파일이 생성됩니다:
TLSv1.2 AES128-SHA 1.1.1.1 "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 2.2.2.2 "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 3.3.3.3 "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:58.0) Gecko/20100101 Firefox/58.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 4.4.4.4 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1 AES128-SHA 5.5.5.5 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305 6.6.6.6 "Mozilla/5.0 (Linux; U; Android 5.0.2; en-US; XT1068 Build/LXB22.46-28) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36"그런 다음 다양한 방법으로 파일을 처리하여 데이터의 분포를 확인할 수 있습니다:
$ cat /tmp/sslparams.log | cut -d ' ' -f 2,2 | sort | uniq -c | sort -rn | perl -ane 'printf "%30s %s\n", $F[1], "="x$F[0];' ECDHE-RSA-AES128-GCM-SHA256 =========================
ECDHE-RSA-AES256-GCM-SHA384 ========
AES128-SHA ====
ECDHE-RSA-CHACHA20-POLY1305 ==
ECDHE-RSA-AES256-SHA384 ==사용 빈도가 낮고 보안성이 떨어지는 암호화 스위트를 식별하고, 로그를 확인하여 어떤 클라이언트가 이를 사용하고 있는지 파악한 다음, NGINX Plus 구성에서 해당 암호화 스위트를 제거할지 여부를 신중하게 결정합니다.
결론
NGINX의 조건부 로깅을 사용하면 NGINX가 처리하는 요청 중 일부를 샘플링하여 표준 로그나 특수 목적 로그에 기록할 수 있습니다. 이 기법은 SSL 매개변수의 분포를 파악하는 등 통계 분석을 위해 트래픽을 신속하게 샘플링해야 할 때 유용합니다.
활동량이 많은 사용자나 스파이더의 데이터가 지나치게 많이 반영되지 않도록 데이터 샘플링 방식을 신중하게 고려해야 합니다. NGINX 구성에서 변수와 map 및 split_clients 지시어를 사용하여 요청을 선택하고 필터링할 수 있습니다.
결정 과정이 더 복잡하거나 높은 수준의 정확성이 필요한 경우에는 NGINX 구성에서 정교한 선택기를 만들 수 있습니다. NGINX Plus 키-값 저장소를 사용하면 상태를 누적하고, 필요한 경우 클러스터 내 NGINX Plus 인스턴스 간에 이를 공유할 수 있습니다.
NGINX Plus의 요청 샘플링을 직접 사용해 보세요. 지금 30일 무료 평가판을 시작하거나 문의하여 사용 사례에 대해 상담해 보세요.
작성자 소개

관련 블로그 게시물
시청하기: KubeCon North America 2023에서의 NGINX Gateway Fabric
이 영상에서는 KubeCon의 NGINX 부스를 찾아가, 저희가 공개한 Kubernetes Gateway API 구현을 살펴봅니다.
F5 NGINX로 대규모 WAF 플릿을 확인하고 관리하는 2가지 방법
앱 보안을 강화하는 두 가지 방법을 소개합니다. NGINX Management Suite를 통해 WAF 운영에 대한 가시성을 높이고, NGINX App Protect WAF를 활용해 코드형 보안을 구현하는 것입니다.
Ingress 컨트롤러 선택 가이드, 4부: NGINX Ingress Controller 옵션
개발 주체, 개발 철학, 프로덕션 환경 준비도, 보안 및 지원을 기준으로 어떤 NGINX Ingress 컨트롤러가 가장 적합한지 알아보세요.
진화하는 공격 환경에 NGINX App Protect Denial of Service가 적응하는 방법
NGINX App Protect Denial of Service(DoS)가 계층 7 DoS 공격을 방어하기 위해 완화 방법을 실시간으로 조정하는 방식을 설명합니다. 정상적인 사용자 행동과 서버 상태에 대한 모델을 생성하여 공격을 나타내는 이상 징후를 감지하고 맞춤형 완화 시그니처를 적용할 수 있습니다.
NGINX Instance Manager를 활용한 대규모 구성 업데이트 및 인증서 관리
NGINX Instance Manager 버전 1.0에는 두 가지 새로운 기능이 도입되었습니다. NGINX 인스턴스에 태그와 사용자 역할을 지정하면 전체 그룹에 구성을 한 번에 적용할 수 있습니다. 인증서 검사는 만료 예정인 인증서를 감지하여 서비스 중단을 방지할 수 있도록 인증서를 교체할 수 있게 해줍니다.
Netflix가 자사 CDN의 핵심으로 NGINX를 선택한 이유
Netflix가 글로벌 콘텐츠 전송 네트워크인 Open Connect의 스트리밍 미디어 기술로 NGINX를 선택한 이유를 NGINX 개발자 Gleb Smirnoff에게서 알아보세요.