들어가며
안녕하세요. LINE NEXT DevOps 팀에서 일하고 있는 이동원이라고 합니다. 현재 쿠버네티스 기반의 인프라 운영과 유지 보수, CI/CD 구축, 로그 수집 및 모니터링 시스템 구성, 장애 대응 등 인프라의 전반적인 업 무를 담당하고 있습니다. 이번 글에서는 LINE NEXT의 공통 인프라 중 Nginx 레이어 통합 및 활용 사례를 소개하려고 합니다.
저는 다수의 서비스를 론칭해 온 노하우에 기반해 2022년 DOSI 서비스를 시작으로 2025년에는 현재까지 Dapp Portal과 Molts, Next Market 등 다수의 서비스에 빠르게 웹 서버 인프라를 제공해 왔습니다.
인프라를 제공할 때는 애플리케이션 환경을 구성하기 전에 반드시 웹 서버(Nginx) 레이어를 반복적으로 구성해야 합니다. 그런데 기존에는 이 과정에 꽤나 많은 리드 타임이 필요하다는 문제가 있었습니다. LINE NEXT의 인프라는 초기 구성 방식의 특성상 새로운 서버와 로드밸런서를 발급받는 데에만 최소 일주일이 소요됐고, 이후 Nginx 설치 및 설정과 배포, 앞단의 로드 밸런서 세팅까지 포함하면 최소 2주 정도의 리드 타임이 필요했습니다.
또한 현재 LINE NEXT는 글로벌 대상의 다양한 브랜드 및 서비스 도메인을 운영하고 있으며, 각각의 서브 도메인까지 포함하면 약 100여 개에 달하는 도메인을 관리하고 있는데요. 이처럼 빠르게 확장되는 서비스 환경에서 매번 수동으로 웹 서버를 구성하는 방식은 점점 한계에 다다랐습니다.
저는 이런 문제들을 해결하기 위해 3단계에 걸쳐 웹 서버 인프라의 아키텍처를 개선했습니다. 그 과정에서 기존 단일 구성에서 벗어나 공통 기능을 하나로 통합하면서도 새로운 서비스에 영향을 주지 않도록 신경 써야 했으며, 아래와 같은 기능 요소들도 고려해야 했습니다.
- 사용자 IP 정보 수집을 위한 원격 주소(remote address) 구성
- 로그 수집을 위한 Loki 연동
- GeoIP 모듈 탑재
- 유지 보수를 위한 공통 점검(maintenance) 프로세스 구성
- SSL 인증서 공통 관리 및 자동화
이번 글에서는 수동으로 웹 서버를 구성해야 했던 기존 구조를 개선하기 위해 'Nginx 설정을 통합하고 멀티사이트 구조를 도입한 뒤 안정적이고 확장 가능한 로그 수집 체계를 구축한 과정'을 차례대로 살펴보겠습니다. 또한 이렇게 완성해 나간 구조에서 위에서 나열한 각 기능 요소를 어떻게 구현 및 구성했는지 하나씩 소개하겠습니다.
시스템 아키텍처 변경 과정
LINE NEXT에서는 점점 잦아지는 서비스 론칭 요구에 대응하기 위해 웹 서버(Nginx) 구성을 중심으로 배포 구조와 아키텍처를 지속적으로 개선해 왔습니다. 특히 설정 과정에서의 반복 작업과 리드 타임을 줄이기 위해 기존의 파편화된 구조를 총 3단계에 걸쳐 점진적으로 중앙 집중화된 통합 구조로 전환했는데요. 각 구조의 특징과 다음 구조로 전환한 이유를 살펴보겠습니다.
PMC 기반의 인프라 구조
초기에는 사내에서 제공하는 배포 도구인 PMC(Project Management Console)를 이용해 각 서비스의 Nginx 설정을 관리했습니다.
PMC는 설정 파일과 Nginx 대상 서버를 정의한 후 rsync를 이용해 타깃 서버에 설정을 배포하는 방식으로 작동합니다. 이 구조에서는 Nginx 대상 서버에 SSH를 통해 직접 접근해야 하기 때문에 22번 포트(SSH) 를 열어두어야 하는데요. 사내 시스템이긴 하지만 보안망으로 22번 포트를 허용해야 하기 때문에 보안 관점에서 리스크가 있었습니다.
또한 초기 구성에는 다음과 같은 한계도 있었습니다.
- 서비스별로 별도로 Nginx 서버와 로드밸런서를 운영해야 했기에 구조가 파편화됐습니다.
- 새로운 서비스를 론칭할 때마다 서버를 새로 요청한 뒤 Nginx를 설치하고 PMC 설정을 구성하는 작업을 반복해야 했습니다.
- Nginx 설정을 개별 리포지터리에서 운영했기에 각 팀별로 설정이 달라지면서 유지 보수 측면에서 어려움이 발생했습니다.
초기에는 서비스별로 독립적으로 설정할 수 있다는 장점으로 작용했던 구조의 특성이 서비스가 많아지면서 유지 보수와 확장성 측면에서 부담이 커진다는 단점으로 바뀌었고, 이에 Ingress Nginx 기반의 인프라 구조로 변경하기로 결정했습니다.
Ingress Nginx 기반 인프라 구조
LINE NEXT에서는 애플리케이션의 기본 인프라로 쿠버네티스를 활용하고 있었습니다. 저희는 Nginx도 쿠버네티스로 운영하기 위해 Ingress Nginx를 도입했고, 이에 따라 도메인 설정 및 Nginx 설정 프로세스에 큰 변화가 발생했습니다.
Ingress Nginx 구조의 핵심은 다음과 같습니다.
- 로드 밸런서를 고정으로 두고(프로덕션 기준 4개의 하드웨어 로드밸런서 사용), Ingress Nginx의 NodePort 연결을 통해 트래픽을 분산
- 헬름 차트의 values.yaml 파일을 이용해 도메인, 인증서, 타깃 서비스(애플리케이션) 이름, 타깃 포트 등을 정의하면 바로 쿠버네티스의 Ingress 리소스를 통해 연결할 수 있도록 구성
- 별도의 로드 밸런서나 Nginx 서버를 구성할 필요 없이 헬름 차트 설정만으로 도메인과 웹 서버 설정 가능
이 구조는 PMC 기반의 인프라 구조 대비 다음과 같은 장점이 있습니다.
- 서비스 론칭 속도가 비약적으로 향상되고, 반복 수작업이 줄어들었습니다.
- 인증서 적용과 도메인 설정 등이 설정 중심의 방식으로 추상화됐습니다.
다만 이 구조에는 한 가지 중요한 단점이 존재했는데요. 외부 요청이 로드밸런서(프락시 모드)에서 Ingress Nginx(NodePort)를 통해 전달될 때 클라이언트의 실제 IP(원격 주소)를 확인하기 어렵다는 것입니다. 또한 Nginx의 세부 기능(예: GeoIP 모듈, 정교한 위치 설정 등)을 활용하기 어렵다는 제약이 있었습니다.
네이티브 Nginx 기반 멀티사이트 구조 - 현재 구조
위와 같은 Ingress Nginx 구조의 한계를 극복하고 공통 설정과 고급 기능을 새로운 서비스에 유연하게 반영할 수 있도록 최종적으로 네이티브 Nginx를 직접 운영하는 구조로 전환했습니다. 다만 단순히 서버를 직접 운영하는 방식이 아니라, Ingress Nginx처럼 설정 중심의 방식은 유지하면서도 Nginx의 기본 기능을 충분히 활용할 수 있는 하이브리드 구조로 설계했습니다.
주요 변화 요소는 다음과 같습니 다.
- 기존 Ingress Nginx의 설정 중심 방식 유지
- GeoIP 모듈, Loki 기반 로그 수집, 인증서 관리, 점검(maintenance) 모드 설정, 유연한 위치 설정 등 다양한 기능을 공통으로 탑재할 수 있게 구성
- Nginx 설정만 추가하면 기존 공통 설정과 함께 바로 배포돼 설정될 수 있도록 구성
기존 PMC의 기능을 대체하기 위해 LINE NEXT 인프라망에 별도로 Ansible 배포 서버를 구성했습니다. 이를 통해 설정 파일을 구조적으로 나눠 관리하고, 각 Nginx 타깃 서버에 필요한 설정만 배포하는 형태로 구성했습니다.
각 구조의 특성을 정리하면 다음과 같습니다.
항목 | PMC 기반 인프라 구조 | Ingress Nginx 기반 인프라 구조 | 네이티브 Nginx 기반 인프라 구조 |
---|---|---|---|
배포 방식 | rsync + 파편화된 설정 템플릿 | 헬름 차트 기반 설정 자동화 | Ansible + 공통 설정 템플릿 |
확장성 | 낮음 | 높음 | 높음 |
원격 주소 확인 | 가능 | 불가 | 가능 |
고급 기능 (GeoIP 모듈, Loki 기반 로그 수집) | 수동 구성 | 제약 있음 | 유연하게 가능 |
설정 자유도 | 높음 | 낮음(Ingress 설정 제한) | 높음 |
PMC 기반 인프라 구조 | ![]() |
---|---|
Ingress Nginx 기반 인프라 구조 | ![]() |
네이티브 Nginx 기반 인프라 구조 | ![]() |
이와 같이 3단계 과정을 거치며 반복적인 구성 및 설정 작업을 줄이고, 서비스 론칭 속도를 높이며, 동시에 고급 기능을 유연하게 활용할 수 있는 구조로 전환했습니다.
Nginx 설정 통합
앞서 설명한 아키텍처 전환 과정 중 가장 핵심적인 개선 사항은 바로 'Nginx 설정 통합과 멀티사이트 구조 도입'이었습니다. 이번 섹션에서는 실제로 어떻게 설정을 통합했고, 어떤 방식으로 효율적인 Nginx 설정 배포 구조를 설계했는지 소개하겠습니다.
Nginx 설정 통합 작업
기존의 Nginx 구성은 서비스별로 각각 설정 파일과 서버를 운영하고 있었기 때문에 동일한 설정을 반복적으로 작성해야 했고, 설정 관리도 매우 어려운 상태였습니다. 특히 events
블록이나 http
블록 내의 기본 설정(예: timeout
, keep-alive
, log format
등)은 대부분 내용이 유사했음에도 서비스마다 중복 선언되는 경우가 많았습니다.
이에 저희는 다음과 같은 방향으로 설정을 정리했습니다.
- 공통 설정은 마스터 Nginx 설정으로 추출
events
와http
블록 내 공통 디렉티브(timeout,keep-alive
,log format
설정 등)를 하나의 설정 파일로 분리
- 서비스별
server
블록은 개별 파일로 분리- 실제 가상 호스트를 담당하는
server
블록은 각 서비스 및 배포 환경(알파, 베타, RC(release candidate), 프로덕션 등)에 따라 독립적으로 관리
- 실제 가상 호스트를 담당하는
sites-avaliable
디렉터리 구조로 통합 관리- Apache 방식에서 영감을 받아 Nginx 설정도 아래와 같이 명시적으로 분리 구성
- 실제 서버에는 /etc/nginx/config 내에 ngixn.conf(마스터 설정) 존재
- 각 서비스별 설정은 /etc/nginx/config/sites-avaliable 내 sample1-web-nginx-prod.conf(서버 설정)에 존재
설정 리포지터리 구성 | 다이어그램 |
---|---|
devops-web-nginx/ ├── sample1-web-nginx/ (서버 설정 1) │ ├── server-alpha.conf │ ├── server-beta.conf │ ├── server-rc.conf │ └── server-prod.conf ├── sample2-web-nginx/ (서버 설정 2) │ ├── server-alpha.conf │ ├── server-beta.conf │ ├── server-rc.conf │ └── server-prod.conf ├── nginx-admin/ (관리자 페이지용 마스터 설정) │ ├── nginx-alpha.conf │ ├── nginx-beta.conf │ └── nginx-prod.conf └── nginx-web/ (웹 페이지용 마스터 설정) ├── nginx-alpha.conf ├── nginx-beta.conf └── nginx-prod.conf | ![]() |
설정을 통합하면서 결과적으로 아래와 같은 장점을 얻었습니다.
- Nginx 하나로 다수의 사이트를 동시에 서빙할 수 있는 멀티사이트 구조 구현
- 설정을 추가하고 배포하는 데 걸리는 시간이 비약적으로 단축
- 마스터 설정만 유지 보수하면 전체 구조에 반영되기 때문에 운영 안정성 향상
Nginx 배포 자동화 - Ansible 도입
설정을 구조화한 뒤에는 '이를 어떻게 효율적으로 배포할 것인가'를 고민했습니다. 기존에는 각 제품 단위의 Git 저장소를 기준으로 수작업 으로 배포했는데요. 이 방식은 설정 변경이나 신규 배포 시 매 배포마다 많은 시간이 걸렸고 오류 발생 가능성도 높았습니다.
이를 개선하기 위해 선택한 도구는 Ansible입니다. 저희는 Ansible을 기반으로 다음과 같이 배포 흐름을 구성했습니다.
- 사용자가 배포하고자 하는 환경(알파, 베타, 프로덕션 등)과 Nginx 타깃 서버 목록을 지정
- Ansible 배포 서버에서 Nginx 설정 Git 리포지터리를 최신 상태로 클론
- 지정된 환경에 따라, 해당하는 마스터 설정과 서버 설정 파일을 추출 및 복사
- 타깃 서버에 설정 배포 설정 파일 문법 오류 유무 확인(
Nginx Verify
명령어 활용) - 타깃 서버에 Nginx 프로세스가 정상 구동되었는지 확인(
Nginx status check
명령어 활용) - 각 배포는 롤링 방식(rolling deployment)으로 순차 배포하며, 만약 문제 발생 시 발생한 시점에 배포 중단(서비스 영향 최소화)
이 구조를 통해 얻은 이점은 다음과 같습니다.
- 설정 배포를 오류 없이 반복해서 작업할 수 있는 신뢰성과 반복성 확보
- 기존 수동 작업 대비 배포 시간 80% 이상 단축
- 설정 이후 Jenkins를 통해 Ansible 스크립트 실행
- Git 기반 통합 관리로 설정 변경 이력 관리 추적 용이
- 배포와 롤백에 용이한 환경 확보
저희는 '설정은 표준화하고, 배포는 자동화하자'라는 철학 아래 위와 같은 구조를 만들어 냈고, 이를 통해 Nginx는 단순한 웹 서버의 역할을 넘어 서비스 배포 속도를 높이고 운영 효율성을 확보하는 핵심 인프라 계층으로 진화했습니다.
Promtail과 Loki를 이용한 로그 수집 설정
LINE NEXT에서는 다양한 서비스가 빠르게 론칭되고 있으며 이에 따라 실시간 로그 수집과 분석이 점점 중요해지고 있습니다. 특히 트래픽이 급격히 증가하는 상황에서 문제를 신속하게 파악하고 대응하기 위해서는 안정적이고 확장 가능한 로그 수집 구조가 필요했습니다. 이번 섹션에서는 'Nginx 로그를 JSON 포맷으로 표준화하고 Promtail과 Loki 기반으로 로그 수집 및 분산 저장 환경을 구축한 과정'을 공유하겠습니다.
Promtail 환경 구성
우선 로그를 체계적으로 수집하기 위해 Nginx의 접속 로그 형식을 JSON 형태로 통일했습니다. 이는 Promtail이 로그를 수집하고 파싱하기 위해 꼭 필요한 구조 전환이었습니다. 로그에는 운영 가시성 확보 및 보안 대응, 트래픽 분석 기반 정책 수립을 위해 꼭 필요한 최소 필드를 엄선해 포함시켰습니다.
remote_addr
: 클라이언트 IP- 클라이언트 IP 확인을 통해 요청자의 위치나 공격 원천(IP 기반 차단 등)을 추적 가능
geoip_country_code
,geoip_city_name
: GeoIP 기반 국가, 도시 정보- 국가 및 도시 정보를 바탕으로 국가별 접근 제어 정책에 활용 가능(예: 특정 국가 접 근 제한, 지역별 트래픽 분석)
request_uri
,http_user_agent
: 요청 정보- 요청 URL과 사용자 에이전트를 통해 어떤 요청이 어디서, 어떤 클라이언트에서 발생했는지 파악 가능 (예: 봇 탐지, 특정 URI 집중 요청 분석)
- 기타 요청에 대한 메타 정보(요청에 소요된 시간, 업스트림 정보, 기타 메타 정보 등)
- 성능 이슈, 지연, 장애 상황 발생 시 원인 분석을 위한 핵심 데이터로 활용(예: 특정 서비스의 업스트림 타임아웃 확인 등)
실제 로그 형식은 아래와 같습니다.
log_format json_analytics escape=json '{'
...내용 생략..
'"remote_addr": "$remote_addr", ' # client IP
'"request_uri": "$request_uri", ' # full path and arguments if the request
'"args": "$args", ' # args
'"status": "$status", ' # response status code
'"http_user_agent": "$http_user_agent", ' # user agent
'"http_host": "$http_host", ' # the request Host: header
'"server_name": "$server_name", ' # the name of the vhost serving the request
'"request_method": "$request_method", ' # request method
'"geoip_country_code": "$geoip2_data_country_code", '
'"geoip_city_name": "$geoip2_data_city_name"'
...내용 생략..
'}';
Nginx 로그 수집을 위해 각 서버에 Promtail을 설치했습니다. Promtail은 Loki의 공식 로그 수집 에이전트로, tail
명령어를 이용해 로컬 로그 파일을 실시간으로 읽어서 출력하며 구조화된 라벨 정보를 붙여 Loki로 전송하는 역할을 수행합니다.
Nginx는 JSON 형식으로 상태 코드(status
), 원본 도메인(http_host
), 국가 코드(geoip_country_code
) 등의 필드를 포함한 접속 로그를 남깁니다. 이를 기반으로 Promtail 설정에서 라벨을 추출하는 파이프라인을 구성했고, 이때 라벨은 이후 Grafana 대시보드에서 라벨에 따라 필터링하고 분석할 수 있도록 통일된 방식으로 지정했습니다.
Loki 환경 구성
Loki는 Grafana에서 제공하는 로그 수집 및 조회 시스템으로 쿠버네티스 환경에서는 헬름 차트를 통해 손쉽게 배포할 수 있습니다. 저는 운영의 간결함을 위해 공식 Loki 헬름 차트를 활용했습니다. 공식 Loki 헬름 차트를 이용하면 values.yaml
파일에서 기본 설정을 조정하는 것만으로 Loki를 클러스터에 바로 배포할 수 있고, 이후 설정 변경이나 업그레이드도 간단하게 진행할 수 있어 운영 효율이 매우 높아집니다.
Loki는 다음과 같은 컴포넌트로 구성됩니다.
Distributor
: 로그 수신 역할Ingester
: 로그를 청크로 가공하고 저장Querier
: 사용자 쿼리 처리Query-frontend
: 쿼리 캐싱 및 조율Compactor
: 저장된 데이터 최적화Ruler
: 쿼리 조건을 만족하면 알림을 발생
또한 Loki는 크게 아래 세 가지 배포 방식을 제공합니다.
- Monolithic 모드
- Simple Scalable 모드
- Microservices 모드
저는 위 세 모드 중 Monolithic과 Simple Scalable 모드를 사용해 봤습니다. 아래는 각 모드의 작동 방식을 다이어그램으로 표현한 것입니다.
초기에는 Monolithic 모드를 선택했습니다. 이 모드는 Loki의 주요 컴포넌트를 하나의 프로세스로 통합한 모드로, 모든 컴포넌트가 통합돼 있어서 수평 확장이 불가능합니다. 따라서 테스트나 소규모 환경 혹은 개발 환경 등에 적합한데요. 최초에는 서비스의 규모가 그리 크지 않을 것이라고 예측했기 때문에 운영 부담을 줄이기 위해 Monolithic 모드를 선택했고, 저장소는 로컬 디스크를 사용하도록 구성했습니다.
그런데 이 구조는 2025년 1월 DApp Portal 서비스 오픈 후 예상보다 트래픽이 빠르게 증가하면서 병목 현상이 발생했습니다. 하루 수만 건의 동시 접속 요청이 발생하면서 로그 양도 급증해 내부 디스크는 빠르게 가득 찼지만, 파드를 쉽게 증설할 수가 없어 로그가 유실되고 Loki 시스템이 다운될 위험에 직면했습니다.
이 문제를 해결하기 위해 Loki를 다른 모드로 전환하기로 결정했습니다. 기존의 Monolithic 모드는 설치는 간단하지만 특정 구성 요소에 발생한 병목 현상이 전체 시스템에 영향을 미칠 수 있기 때문에 트래픽이 증가하는 환경에서는 한계가 분명했습니다. 반면 Microservices 모드는 각 구성 요소를 완전히 분리해 운영할 수 있어 유연성이 높지만 운영 복잡도가 증가합니다.
Simple Scalable 모드는 이 두 구조의 중간 지점에 위치합니다. Loki의 핵심 로직을 읽기(read), 쓰기(write), 백엔드(backend)의 세 가지 실행 경로로 나눠 각각을 독립적인 컴포넌트로 구성해서 유연하게 확장할 수 있는 구조입니다. 저는 Loki를 Simple Scalable 모드로 전환했고, 저장소도 S3 호환 오브젝트 스토리지(VOS)로 변경했습니다. 이 구조에서는 각 컴포넌트를 독립적으로 배포할 수 있어서 수평 확장(scale-out)이 용이합니다.
결과적으로 Simple Scalable 모드 Loki를 기반으로 서비스 트래픽이 폭증해도 안정적으로 로그를 수집하고 분석할 수 있는 유연한 분산 환경을 갖출 수 있었습니다. 다음은 Promtail과 Loki로 구성한 로그 수집 및 분석 시스템의 최종 모습을 간략히 나타낸 그림입니다.
Nginx에서 수집하는 로그에는 Remote IP
, 국가 코드
, 도시 정보
등이 포함돼 있어 로그만으로 사용자 위치 기반의 분석이 가능하도록 구성했습니다. 이를 위해 MaxMind의 GeoIP2 City DB를 사용하고 있으며, 일주일에 한 번 최신 DB를 자동으로 다운로드해 갱신하도록 구성해 두었습니다. 이를 통해 변동이 잦은 IP 정보도 항상 최신 상태로 유지해 국가별 접속 분포나 지역 기반 이상 탐지 등의 작업에서 정확한 데이터를 확보할 수 있게 되었습니다.
실시간 로그 수집을 통한 대시보드 구성
현재는 Promtail과 Loki를 통해 안정적으로 로그를 수집하고 있고, Grafana로 실시간 대시보드를 구성해 다음과 같은 지표를 모니터링하고 있습니다
- 실시간 방문자 수(동시 접속 IP 수)
- 초당 HTTP 요청 수
- HTTP 상태 코드별 요청량
- 국가별 접속자 수 현황
- 요청 시간 통계
- 상위 접속 정보(Referrers, IPs, User Agents, Pages)
이와 같이 대시보드까지 구성하면 신규 서비스가 들어오더라도 간단한 설정 작업만으로 주요 지표를 수집해 시각화해서 볼 수 있습니다. NGINX만 통합 구조에 얹으면 Promtail이 자동으로 로그를 수집하고 Loki가 이를 받아 저장하므로 로그 수집과 분석까지의 파이프라인이 자동으로 연결됩니다.
또한 PromQL을 통해 데이터를 질의(쿼리)할 수 있고 실제 문제 대응 시 유용하게 활용하고 있습니다.
- 국가별 요청 수 집계
sum by(geoip_country_code) ( count_over_time({application="www.dappportal.io"} | json | geoip_country_code != "" [$__interval]) )
- User-Agent별 요청 수 상위 5개
topk(5, sum by(user_agent) ( count_over_time({application="www.dappportal.io"} | json | user_agent != "" [$__interval]) ) )
- HTTP 상태 코드별 요청량
sum by(status) ( count_over_time({application="www.dappportal.io"} | json | status != "" [$__interval]) )
Nginx 공통화 기능: 유연하고 확장 가능한 운영을 위한 기반
지금까지 설명한 Nginx 통합 및 로그 수집 구조는 신규 서비스가 유입되더라도 최소한의 작업으로 자동 연계될 수 있는 유연한 아키텍처를 목표로 설계한 것입니다. Nginx를 단일 진입점으로 통합한 아키텍처 덕분에 다양한 공통 기능을 한 곳에서 일관적으로 관리하고, 이를 통해 운영 효율을 높이고 가시성을 극대화할 수 있었습니다.
이 구조는 특히 멀티 사이트나 멀티 도메인 환경에서 진가를 발휘합니다. 한 번의 설정으로 전체 서비스에 영향을 줄 수 있다는 점이 높은 생산성을 제공하기 때문인데요. 현재 LINE NEXT에서는 다양한 브랜드와 글로벌 서비스를 동시에 운영하고 있으며, 주요 TLD(top-Level domain)는 다음과 같습니다.
*.dosi.world *.store.dosi.world *.dappportal.io *.supermates.io *.kaiawallet.io *.nxtgame.net *.nextmarket.games *.molts.co.jp *.line-apps.com *.line.biz *.blockchain.line.me |
여기에 각각의 서브 도메인까지 포함하면 실제로는 100여 개가 넘는 도메인 구성이 존재합니다. 과거에는 이를 개별로 설정하고 관리했기에 이 작업에 상당한 공수가 필요했지만, 현재의 통합 Nginx 구조에서는 서비스 도메인을 설정에 추가하기만 하면 다음과 같은 기능이 자동으로 적용됩니다.
- SSL 인증서 공통 설정 및 업데이트
- 점검(maintenance) 페이지 응답 처리
- GeoIP 기반 국가 차단
- Loki 연동을 통한 로그 수집
즉, 별도로 서버나 로드 밸런서를 요청하지 않아도 공통 인프라에서 필요한 기능을 즉시 활용하고, 전체 서비스에 일관된 정책을 적용할 수 있습니다. 이를 통해 운영 효율과 출시 및 유지 보수 속도를 높일 수 있었는데요. 이 구조를 기반으로 구성한 핵심 공통 기능을 하나씩 소개하겠습니다.
인증서 공통 설정: 반복 작업의 자동화
기존에는 서비스별로 개별 Nginx 서버를 운영하면서 인증서 갱신 시 매번 SSH로 접속해 수동으로 교체하는 방식을 사용했습니다. 이때 동일 TLD 하위의 여러 서브 도메인을 사용하는 경우 서비스 수만큼 인증서 갱신 작업을 반복해야 했는데요. 이는 명백히 휴먼 에러가 발생할 수 있는 운영 리스크였습니다.
구체적인 사례와 함께 살펴보겠습니다. 기존 구조에서는 Nginx의 server
블록 내에서 도메인별로 직접 인증서 경로를 지정했습니다. 예를 들어 www.dappportal.io 도메인의 설정은 다음과 같았습니다.
server {
listen 443 ssl http2;
server_name www.dappportal.io;
ssl_certificate /apps/cert/dappportal.io-prod-20241204/*.dappportal.io_fullchain.crt; → 인증서 참조 경로
ssl_certificate_key apps/cert/dappportal.io-prod-20241204/*.dappportal.io.key; → 인증서 참조 경로
... 내용 생략...
}
위 구조에서 통합 Nginx 구조로 전환하면서 다음과 같이 개선했습니다.
- 도메인별 공통 참조 경로 체계: 인증서 파일을
/apps/cert/{도메인명-갱신날짜}/
하위에 저장하고, Nginxserver
블록에서는 해당 경로만 변경해 참조하도록 표준화 - 갱신 자동화 및 배포 프로세스 구성: 인증서 갱신에서 배포까지의 전 과정을 Ansible과 S3 기반 자동화로 구성
통합 Nginx 구조에서는 전체 인증서 갱신 및 배포를 다음과 같은 흐름으로 진행합니다.
- 인증서 갱신 및 S3 업로드: Voyager(사내 인증서 관리 시스템)를 통해 갱신된 인증서를 S3에 업로드합니다. 디렉터리는
{도메인명-YYYYMMDD}
형태로 구분됩니다. - Ansible로 타깃 서버에 인증서 배포:
ansible-playbook
실행 시, 해당 도메인에 필요한 인증서만 추출하여/apps/cert/{도메인명-날짜}/
경로에 자동으로 배포합니다. - Nginx 설정 파일 경로 변경:
nginx.conf
또는 도메인별include
설정에서ssl_certificate
와ssl_certificate_key
경로를 새로운 디렉터리로 교체합니다.
[과거 인증서 참조] - ssl_certificate /apps/cert/dappportal.io-prod-20231204/*.dappportal.io_fullchain.crt; - ssl_certificate_key /apps/cert/dappportal.io-prod-20231204/*.dappportal.io.key; [신규 인증서 참조] + ssl_certificate /apps/cert/dappportal.io-prod-20241204/*.dappportal.io_fullchain.crt; + ssl_certificate_key /apps/cert/dappportal.io-prod-20241204/*.dappportal.io.key;
- Ansible로 Nginx 설정 전체 배포 및 다시 로드: 인증서 경로가 변경된 설정 파일을 타깃 서버에 배포한 뒤 모든 서버에서
nginx -t && nginx -s reload
명령으로 무중단 재시작을 수행합니다. 이때 안정적으로 작업을 수행하기 위해 각 서버를 순차적으로 진행합니다.
이와 같이 개선한 뒤 다음과 같은 효과를 얻을 수 있었습니다.
- 수작업 제거: 다수의 인증서를 갱신하더라도 Nginx 설정 수정 한 번으로 전체 서비스에 반영 가능
- 안정성 향상: 인증서 경로를 일관적으로 만들어 설정 실수를 줄여서 장애 가능성을 감소시킴
- 운영 자동화: Ansible과 S3 기반의 자동화 구성으로 인증서 관리에 필요한 인력 최소화
점검 공통 설정: Nginx에서 점검 응답 처리
서비스 운영 중에는 종종 점검이 필요한 경우나 특정 시간 동안 일부 트래픽을 제한하고 싶은 경우가 있습니다. 이런 경우 기존에는 백엔드 서버에서 API 응답을 직접 수정해 점검 상태를 처리했는데 이 방식에는 다음과 같은 한계가 있었습니다.
- 백엔드에 불필요한 부하 발생(백엔드까지 트래픽이 들어온 뒤 응답)
- 점검 처리 방식이 서비스별로 상이해 일관적인 운영이 어려움
이를 개선하기 위해 트래픽의 첫 진입점인 Nginx 레이어에서 점검 모드를 처리하도록 구조를 전환했습니다. 이렇게 하면 요청이 백엔드까지 전달되지 않기 때문에 불필요한 리소스 낭비를 줄일 수 있고, 점검 처리 또한 통합 제어할 수 있습니다.
이 구조의 전체 흐름은 다음과 같습니다.
위 구현 방식을 보다 상세히 살펴보겠습니다.
Include 파일 기반 점검 제어 구조
점검 제어는 각 서비스의 nginx.conf
내 location /
블록에 아래와 같이 include
를 삽입해 해당 파일을 제어해서 응답이 변경되도록 설정했습니다. 다음은 Nginx 설정의 일부를 발췌한 내용입니다.
- Nginx 설정 파일
location / { ... include /etc/nginx/blockips_market-web-nginx.conf; ... }
점검 모드는 deny all;
명령을 사용해 외부 요청을 즉시 차단하는 방식으로 작동합니다. 이 설정은 별도 include
파일에 간단한 형태로 작성돼 있어서 필요시 빠르게 적용하거나 해제할 수 있습니다. 또한 전체 흐름은 Ansible과 Jenkins를 통해 자동화했으며, 운영자가 직접 서버에 접속하지 않고도 원하는 점검 설정을 선택해 손쉽게 반영할 수 있도록 구성했습니다.
- blockips 설정 파일
deny all; error_page 403 = @maintenance;
점검 모드에서는 외부에서 들어오는 API 요청에 JSON 형식의 응답을 반환하도록 설정했습니다. 외부 시스템에서도 API를 호출할 수 있기 때문에 CORS(cross-origin resource sharing) 관련 헤더를 함께 포함해 처리하고 있으며, 응답 데이터는 개발 팀과 사전에 협의한 규약에 따 라 구성했습니다. 실제 점검 모드가 작동할 때 적용되는 Nginx의 location
설정은 아래와 같습니다.
- 점검
location
파일
location @maintenance { allow all; # CORS 헤더 추가 add_header 'Access-Control-Allow-Origin' "$http_origin" always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, DELETE, PUT, PATCH' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Headers' 'User-Agent,Keep-Alive,Content-Type' always; add_header 'Access-Control-Expose-Headers' 'X-GeoIP-Country-Code,Date' always; # 점검 응답 JSON default_type application/json; return 403 '{"code": "5030", "message": "Service under maintenance."}'; }
점검 여부는 해당 include
파일 내 deny all;
존재 유무에 따라 결정됩니다. 이 구조는 Ansible 자동화와 결합해 다음과 같은 방식으로 운영됩니다.
작업 유형 | 처리 내용 |
---|---|
점검 시작(on ) | Ansible이 blockips_xxx.conf 파일에 deny all; 삽입 |
점검 해제(off ) | Ansible이 해당 파일의 내용을 비우거나 주석 처리 |
이렇게 구성하면 다음과 같은 장점을 얻을 수 있습니다.
- 점검 온/오프 즉시 제어 가능: Jenkins, Ansible CLI 등을 통해 실시간으로 전환 가능
- 구조적 일관성 확보: 서비스마다 다른 방식이 아닌, 통합된 방식으로 점검 처리
- 안정성 강화: Nginx에서 점검 응답을 처리하기 때문에 백엔드 코드에 영향을 받지 않는 안정성 확보
국가별 접근 제한 공통 설정: GeoIP 기반 접근 제어 표준화
운영하는 서비스에 특정 국가에서 접근하는 것을 제한하는 정책은 보안 및 서비스 안정성 측면에서 매우 중요합니다. 과거에는 각 서비스별로 코드 레벨에서 국가 차단 로직을 직접 구현했기 때문에 중복 작업이 발생했고 관리도 어려웠습니다. 이를 개선하기 위해 저는 Nginx 레벨에서 GeoIP 기반으로 국가별 접근 제한 로직을 공통화했습니다.
우선 상위 Nginx 설정에서는 GeoIP2
모듈을 이용해 클라이언트의 IP로 국가 코 드를 파악하고 이를 map
디렉티브를 통해 관리 가능한 형태로 구성합니다
- 마스터 Nginx 설정 파일
map $geoip2_data_country_code $blocked_country { default 0; AA 1; # 국가 1 BB 1; # 국가 2 CC 1; # 국가 3 ... }
위 설정 구문은 특정 국가 코드가 입력됐을 때 하위 Nginx 설정 파일에서 사용하는 blocked_country
변수를 1
로 설정하는 역할을 합니다. 이 로직을 위와 같이 상위 공통 설정에서 관리하기 때문에 국가별 접근 제어 정책을 한 곳에서 통제할 수 있습니다.
하위 서비스별 Nginx 설정에서는 이 blocked_country
값을 기준으로 조건문과 대응되는 location
을 설정합니다.
- 하위 Nginx 설정 파일
# 국가별 접근 제한 조건문 if ($blocked_country) { return 403; } # 국가 접근 제한 내부 리다이렉트 error_page 403 @blocking; # 국가 접근 제한 시 응답 설정 location @blocking { allow all; if ($request_uri ~ "^/api/") { add_header Content-Type application/json; return 403 '{"code": "4030", "message": "Access denied due to country restrictions."}'; } return 403; }
- 국가별 접근 제한 조건문에서는 해당 요청이 접근 제한 대상 국가에서 온 경우 403 응답을 반환하게 합니다.
- 국가 접근 제한 시 응답 설정 구문에서는 API 요청에는 JSON 형태의 메시지를 응답하고, 그 외 요청에는 기본적인 403 응답으로 처리합니다.
이 구조의 장점은 다음과 같습니다.
- 중앙 집중식 정책 관리: 국가별 접근 제어 목록을 상위 설정의
map
블록에서 통합 관리하기 때문에 모든 서비스에 동일하게 적용할 수 있습니다. - 유지 보수 효율 향상: 새로운 국가를 접근 제어 처리하고 싶을 때 한 곳(
map
블록)만 수정하면 되기 때문에 반복 작업이 필요 없습니다. - 일관적인 서비스 보장: API 및 웹 요청에 표준화한 응답 형식을 제공해 사용자 경험을 예측 가능하게 유지합니다.
이와 같이 각 서비스 팀이 따로 개발할 필요 없이 단일 설정으로 모든 서비스에 동일한 국가별 접근 제어 정책을 적용할 수 있게 되었으며, 설정 변경 시에도 공통 파일만 수정하면 전체에 반영할 수 있습니다.
마치며
이번 Nginx 멀티사이트 통합 구조는 단순한 설정 통합을 넘어 운영 효율 향상과 인프라 안정성 확보라는 두 가지 중요한 목표를 동시에 달성한 사례였습니다.
먼저 접속 로그 수집 및 분석 체계를 정립함으로써 모든 서비스의 요청 흐름을 실시간으로 가시화했습니다. 요청량과 응답 속도, 에러율 등을 일관된 형식으로 분석할 수 있게 돼 이상 징후를 조기에 감지하고 신속하게 대응할 수 있는 기반을 마련할 수 있었습니다. 이는 곧 서비스 신뢰도 향상으로 이어졌습니다.
이와 함께 인증서 갱신과 점검 모드, 국가별 접근 제한 같은 운영 반복 작업을 공통 설정화하고, Ansible 및 Jenkins와 연계해 완전 자동화한 배포 흐름을 구축했습니다. 이를 통해 이후 서비스가 늘어나더라도 운영 부담이 거의 증가하지 않는 구조를 갖췄습니다.
무엇보다 중요한 점은, 이 구조가 서비스 코드와는 독립적으로 작동하는 미들웨어 계층에서 일관적으로 정책을 적용할 수 있게 만들 었다는 것입니다. 이를 통해 개발 팀의 반복 작업을 줄일 수 있었고, DevOps 팀은 더욱 전략적인 역할에 집중할 수 있도록 만들었습니다.
결국 이번 통합은 단순한 구성 개선이 아니라 '인프라 안정성에 실질적으로 기여한 핵심 성공 사례'입니다. 이 글이 유사한 고민을 안고 있는 독자 여러분들께 실질적인 방향을 제시하고 도움을 주는 글이 되기를 바라며 이만 마치겠습니다. 긴 글 읽어주셔서 감사합니다.