클라우드 엔지니어 양성/프로젝트

[개인 프로젝트] 트래픽 폭증 대응 티켓 예매 서비스

gamjadori 2026. 8. 1. 22:35
728x90

[개인 프로젝트] 트래픽 폭증 대응 티켓 예매 서비스

 

콘서트·스포츠 티켓 예매처럼 순간적으로 트래픽이 폭증하는 서비스를 가정하고, 좌석 동시성 제어 · 대기열 설계 · DB 읽기/쓰기 분리 · 실시간 이상탐지를 설계·구현하고, K6 부하테스트와 자체 모의해킹으로 직접 검증한 개인 프로젝트입니다.

GitHubhttps://github.com/mojodori/ticket-booking-security 환경: VMware Fusion, VM 8대, 172.16.60.0/24 작성자: 최시은 · 2026.07


핵심 결과 요약

검증 항목결과

좌석 동시성 제어동일 좌석 100건 동시요청 → 성공 정확히 1건
트래픽 폭증 대응 (대기열)최대 응답지연 60,000ms → 108.8ms (약 99.8% 감소)
DB 복제Primary→Replica 실시간 동기화 확인
실시간 이상탐지Suricata가 공격 시뮬레이션 트래픽 실시간 탐지
자체 모의해킹설계한 포트 매트릭스와 실제 nmap 결과 100% 일치
회원 인증 검증bcrypt 해시 저장, 로그인 5회 실패 시 423 잠금

1. 프로젝트 개요

일반적인 웹 서비스 포트폴리오는 "기능이 동작하는가"에 초점을 맞추지만, 티켓 예매 서비스는 그보다 중요한 질문이 걸려있다 — "공격받거나 트래픽이 폭증해도 안전하게 버티는가." 이 프로젝트 하나에 정보보안 3요소(CIA)가 모두 걸려 있다.
CIA 요소위협대응

기밀성회원정보 유출, 내부망 스니핑bcrypt 해시, 네트워크 계층 분리, DB 최소권한
무결성좌석 동시클릭 중복판매Redis 원자연산(SETNX)으로 구조적 방지
가용성예매 오픈 트래픽 폭증, DoS대기열(Rate Limiting), 로드밸런싱, 이상탐지

2. 인프라 구성

사용자 → queue-gate(80) → web-lb → was-01/02 → redis / db-01(→replica)
관리자 → bastion(22, SSH전용) → 전 서버 ProxyJump

서버IP역할

bastion172.16.60.142SSH 유일 진입점
db-01172.16.60.143MariaDB Primary
redis172.16.60.144좌석 재고 원자연산
was-01172.16.60.145예약 API #1
queue-gate172.16.60.146대기열 + Suricata IDS
web-lb172.16.60.147로드밸런서 + 정적 페이지
db-01-replica172.16.60.148MariaDB Replica
was-02172.16.60.151예약 API #2

방화벽 원칙

전 서버 ufw default-deny + 출발지 IP 화이트리스트만 허용. 허용되지 않은 것은 전부 차단이 기본 상태.


3. 핵심 설계 결정

Redis 원자연산 (DB 트랜잭션 락 대신)

DB 락은 동시요청이 몰릴수록 대기열이 길어져 응답이 느려진다. Redis는 단일 스레드 원자적 명령으로 즉시 처리 — 100건 동시요청에서도 평균 30ms대 유지.

대기열 (느리게 처리 아닌 즉시 거절)

초과 요청을 큐에 쌓으면 서버 자원이 고갈된다(실측 응답 60초). 429로 즉시 거절하면 서버는 항상 감당 가능한 용량 안에서만 일한다.

DB Read Replica

예매 오픈 시 좌석 조회가 예약 확정보다 수십~수백 배 많다. 조회를 Replica로 분리해 쓰기 트랜잭션 지연을 방지한다.


4. 검증 결과

좌석 동시성 제어

  • 순차 curl 2회 / K6 내부 VU 100 / K6 외부 VU 100 — 3가지 조건 모두 DB에 정확히 1건만 저장

트래픽 폭증 대응 (대기열 ON/OFF 비교)

지표대기열 OFF대기열 ON

checks_failed1.27%0.00%
최대 응답지연60,000ms108.8ms
처리량91,801건864,696건 (9.4배)

DB 복제

Primary INSERT가 Replica에 실시간 반영 확인 (Seconds_Behind_Master: 0)

실시간 이상탐지 (Suricata)

최초 별도 monitor 서버에 배치 → 트래픽 경로 밖이라 탐지 실패 → queue-gate에 직접 재배치하여 K6 공격 시뮬레이션 실시간 탐지 성공.

교훈: IDS는 탐지 룰의 정교함 이전에 "트래픽 경로 위에 있는가"가 우선한다.


5. 자체 모의해킹

nmap 포트 스캔 (외부 관점)

서버22804433000/3306/6379

queue-gatefilteredopenclosedfiltered
web-lbfilteredfilteredfilteredfiltered
was-01filteredfilteredfilteredfiltered

→ 설계한 포트 매트릭스와 실제 스캔 결과 100% 일치

Nikto 웹 취약점 스캔

헤더1차조치 후

Content-Security-Policy누락반영됨
Referrer-Policy누락반영됨
Permissions-Policy누락반영됨
X-Content-Type-Options누락미반영 (재조사 필요)
Strict-Transport-Security누락TLS 적용 후 예정

6. 웹 애플리케이션 취약점 분석 (OWASP Top 10 자가점검)

항목상태

A02 Cryptographic Failures✅ 구현·검증완료 (bcrypt)
A03 Injection✅ 구현 (파라미터 바인딩), 침투테스트는 보류
A04 Insecure Design✅ 구현·검증완료 (Redis 원자연산)
A05 Security Misconfiguration🟡 부분 구현 (헤더 2개 미해결)
A07 Authentication Failures🟡 부분 구현·검증완료 (bcrypt+잠금, MFA/세션쿠키 남음)
A09 Logging and Monitoring✅ 부분 구현·검증완료 (Suricata)
A01, A06, A08, A10부분구현/평가범위밖

7. 회원 인증 검증

was-01에 회원가입·로그인·비밀번호 변경 API를 구현하고 실증했다.

  • 회원가입 → DB 저장: bcrypt(cost 12) 해시 확인, 평문 없음
  • 로그인 실패 잠금: Redis 카운터로 5회 초과 시 423 응답
  • 비밀번호 변경: 변경 전/후 해시값이 다름을 DB 재조회로 확인
  • 관리자 계정: chage로 90일 만료 정책 적용, /etc/login.defs 기본값도 반영

법적 근거 정정: 「개인정보의 안전성 확보조치 기준」의 비밀번호 주기적 변경 의무 조항은 개정으로 삭제되어 현재 법적 의무가 아님. 현재 법이 요구하는 것은 (1) 비밀번호 일방향 암호화 저장, (2) 인증 실패 시 접근 제한 두 가지이며, 본 프로젝트가 이를 충족함. 90일 정책은 조직 자율 정책으로 문서화.


8. 트러블슈팅 하이라이트

문제원인해결

mysql_secure_installation not found최신 MariaDB에서 명령어 개명mariadb-secure-installation 사용
DB 복제 SQL_Running: No계정 재생성 이력이 binlog에 남음sql_slave_skip_counter로 스킵
Suricata 서비스 실패eth0/KiB 등 설정 문법 오류ens160/kb로 수정
IDS 공격 트래픽 미탐지monitor 서버가 트래픽 경로 밖queue-gate로 재배치
웹페이지 CSS 전체 깨짐CSP 헤더가 인라인 스타일 차단style-src/font-src 세분화

전체 목록은 troubleshooting-full.pdf 참고.


9. 산출물 목록

  • ticket-portfolio-full.pdf — 통합 계획+실행 문서 (23p)
  • session-report.pdf / as-built.pdf
  • loadtest-report.pdf / concurrency-report.pdf
  • pentest-report.pdf / webapp-vuln-report.pdf
  • auth-verification-report.pdf
  • db-replication-log.pdf / suricata-report.pdf
  • troubleshooting-full.pdf
  • ticket-project-v2.pptx (17장 발표자료)
  • ticket-scripts.zip (서버/API/K6 코드)