NTC 시스템 아키텍처 구성도
NCP · 3-Tier Architecture

NAVER TENNIS CLUB — 시스템 구성도

NCP 에 구성한 웹·앱·DB 3계층 아키텍처. 로드밸런서가 트래픽을 분산하고, 웹 계층(nginx)이 앱 계층(Flask)으로 프록시하며, 모든 데이터는 관리형 Cloud DB에 저장됩니다.

도메인 https://navertennis.duckdns.org 🔒 VPC 10.0.0.0/16 리전 KR (한국) 앱 계층 이중화 ×2
① 사용자 웹 브라우저 · 모바일
👥 클럽 회원 (197명)
HTTP 요청 → navertennis.duckdns.org 랭킹 · 경기입력 · 대진편성 · 클럽현황 조회
🎨 외부 CDN
DiceBear · Twemoji 아바타 SVG를 브라우저가 직접 로드
HTTPS :443 (HTTP는 301 자동 이동)
② 로드밸런서 Network LB · Public · TCP 통과
⚖️ lab-nlb
Public IP 101.79.18.101 TCP:443 · TCP:80 그대로 통과 — TLS는 서버의 nginx가 종료 클라이언트 실제 IP 보존 · 라운드로빈 · 헬스체크 TCP (10초 주기) Target Group → lnxsvr1 · lnxsvr2
분산
☁️ VPC · 10.0.0.0/16
web-subnet · 10.0.1.0/24 🛡️ NACL (서브넷 방화벽) + ACG (서버 방화벽)
③ 웹 계층 — nginx (🔒 TLS 종료 + 리버스 프록시)
🖥️ lnxsvr1 WEB+APP
nginx :443(TLS)·:80 · 사설 10.0.1.101 공인 101.79.26.198 proxy_pass → 127.0.0.1:5000
🖥️ lnxsvr2 WEB+APP
nginx :443(TLS)·:80 · 사설 10.0.1.102 공인 101.79.27.121 proxy_pass → 127.0.0.1:5000
↓ 각 서버의 nginx가 자기 서버의 Flask :5000으로 프록시 — 한 대가 죽어도 서비스 유지 ↓
④ 앱 계층 — Flask 애플리케이션
🎾 guestbook (Flask) 이중화 ×2 (Active-Active)
lnxsvr1 · lnxsvr2 각각 :5000 · systemd (Restart=always, enabled) /opt/guestbook/app.py — 라우트·NTR 알고리즘·HTML 렌더링 일체형 📷 프로필 사진 업로드 → Object Storage ntc-uploads (public-read)
MySQL :3306
db-subnet · VPC Cloud DB 🛡️ ACG cloud-mysql-2dfqr5
⑤ 데이터 계층 — 관리형 DB
🗄️ Cloud DB for MySQL
db-48rail.vpc-cdb.ntruss.com : 3306 DB guestbook · 계정 appuser 테이블: players · matches · entries · settings · attendance
backup.sh
⑥ 백업 Object Storage (S3 호환)
💾 s3://ntc-backup
kr.object.ncloudstorage.com DB 덤프(.sql.gz) + 앱 소스(.tar.gz)
🖼️ s3://ntc-uploads
선수 프로필 사진 (public-read) 앱이 boto3로 업로드 · 브라우저가 직접 로드

요청 처리 흐름

회원이 https://navertennis.duckdns.org 접속 → DuckDNS가 Network LB 공인 IP로 연결 아바타 이미지(DiceBear·Twemoji)는 서버를 거치지 않고 브라우저가 CDN에서 직접 로드
Network LB가 TCP를 그대로 통과시키며 정상 서버로 라운드로빈 분산 — 클라이언트 IP가 서버까지 보존됨
선택된 서버의 nginx(:443)가 TLS를 종료(암호화 해제)하고 proxy_pass로 앱 계층에 전달 HTTP(:80)로 오면 nginx가 301로 HTTPS 이동 · 각 서버가 자기 Flask(127.0.0.1:5000)로 프록시 — Active-Active 이중화
Flask(:5000)가 라우팅·NTR 계산 후 서버사이드 HTML 렌더링, 프로필 사진은 Object Storage에서 직접 로드
데이터 읽기/쓰기는 Cloud DB for MySQL(:3306)에 질의 → 결과를 페이지에 반영해 응답
backup.sh가 DB 덤프와 파일을 Object Storage에 주기 백업

Flask 앱 내부 구조 — 요청 한 건이 처리되는 과정

🚦
① URL 라우팅
주소를 보고 실행할 기능 연결
랭킹·경기입력·선수관리 등 12개 경로
🧮
② 비즈니스 로직
NTR 레이팅 계산 (Elo 방식)
대진 편성 · 궁합/천적 · 어워드 집계
🗄️
③ 데이터 처리
Cloud DB 읽기/쓰기 (테이블 5개)
사진은 Object Storage 저장
🖼️
④ 화면 생성
서버에서 HTML 완성 후 응답
(서버사이드 렌더링 · 프론트 분리 없음)
✏️ / 게시판 🏆 /tennis 2026 랭킹 📝 /tennis/games 경기입력·대진편성 👥 /tennis/players 선수관리 🎾 /tennis/matches 2025 기록 📊 /tennis/dashboard 클럽현황 👑 /tennis/awards 월간성적 📈 /tennis/p/<id> 선수상세 ⚔️ /tennis/vs 선수비교 🏗️ /architecture 구성도 💓 /health LB 헬스체크

2단계 방화벽 (트래픽이 지나는 순서)

1. NACL — 서브넷 방화벽

서브넷 경계에서 먼저 걸러냅니다. 규칙에 우선순위가 있어 낮은 번호가 먼저 적용되며, ACG보다 앞단에서 차단합니다. (실습 중 22번 SSH가 여기서 막혀 특정 IP만 허용 규칙을 추가한 이력)

2. ACG — 서버 방화벽

서버(NIC) 단위 보안그룹. 웹 서버는 lab1-web-acg, Cloud DB는 cloud-mysql-2dfqr5가 적용되어 앱 계층(10.0.1.0/24)에서 오는 3306 포트만 DB로 통과시킵니다.

리소스 명세

구성요소역할사설 IP공인 IP / 엔드포인트포트·서비스
lab-nlbNetwork LB (Public · TCP 통과)101.79.18.101TCP:443 · TCP:80 · 라운드로빈
lnxsvr1웹 + 앱 (nginx·Flask)10.0.1.101101.79.26.198nginx :80 · Flask :5000
lnxsvr2웹 + 앱 (nginx·Flask)10.0.1.102101.79.27.121nginx :80 · Flask :5000
Cloud DBMySQL (관리형)VPC 내부db-48rail.vpc-cdb.ntruss.com:3306
Object Storage백업 + 사진 저장소kr.object.ncloudstorage.coms3://ntc-backup · s3://ntc-uploads
Auto Scaling미사용 잔여 서버10.0.2.6없음반납 시 AS그룹 먼저 제거

구조상 유의점 & 개선 여지

① 앱 계층 이중화 완료 (2026-07-26). lnxsvr1에도 Flask를 배치해 웹·앱 계층 모두 Active-Active 2중화. 업로드 사진도 Object Storage로 이전해 서버 간 파일 불일치 없음. 한 서버 장애 시 LB 헬스체크(30초×2)가 감지 후 나머지 서버로만 서빙합니다.

② HTTPS + 인증서 완전 자동 갱신 (2026-07-26). Network LB(TCP 통과) 전환으로 TLS를 서버 nginx에서 종료. Let's Encrypt 인증서는 lnxsvr2의 acme.sh가 HTTP 인증으로 60일마다 자동 재발급하고, 갱신 즉시 lnxsvr1로 자동 복사 + 양쪽 nginx 재적용 — 사람 개입 0. HTTP 접속은 nginx가 301로 HTTPS 이동.

무중단 장애조치 테스트 — 2026-07-26 실측

앱 계층 이중화 직후, lnxsvr2의 Flask를 실제로 중지시켜 LB가 장애를 감지하고 lnxsvr1 단독으로 서비스를 유지하는지 검증한 기록입니다.

시각이벤트결과
11:09:56기준 확인 (3회)모두 200
11:09:56🔥 lnxsvr2 Flask 중지 (장애 주입)inactive
11:10:02LB 감지 창502 단 1회
11:10:07 ~ 11:12:24lnxsvr1 단독 서빙 (140초 관찰)연속 200 · 에러 0
장애 중랭킹(DB 조회) · 게시판 실기능 확인모두 200 — 완전한 서비스
11:13:40✅ lnxsvr2 재기동active · health OK
~11:15:10재편입 관찰 (90초)LB·직접 모두 200 · 무결점 복귀

결론: 장애 영향은 감지 창의 502 응답 1건뿐. 한쪽 서버 전체가 죽어도 반대편이 웹+앱 풀스택으로 서비스를 유지하며, 복구 시 LB가 헬스체크 2회 통과 후 자동으로 트래픽을 재분배한다 (사용자 영향 0건).