Nginx 리버스 프록시 구현의 필요성을 느낀 계기가 있었다. 빼곡 서비스의 프로덕션과 스테이징 환경을 분리하면서 발생했던 문제였다.
우선 홈서버를 사용하고 있는 기존 아키텍처를 그림으로 표현하자면 다음과 같다.

한 서버에 각 docker-compose로 서버가 구성되어있고, 서로 다른 포트로 포트포워딩함으로써 접속하도록 만든것이다.
당연히 DNS도 따로 발급받아두긴 했었다. 스테이징 URL에는 staging이 포함되는 url경로를 사용했다.
그러나 이 구조에서 헛점을 발견했는데, 도메인과 별개로 포트번호만 바뀌어도 각자 환경에 접근 할 수 있다는 것이였다. 즉, https://{product-uri}:50443으로 접근하면 product 환경이 아닌 staging환경으로 접속되는것이다.
사실 이건 당연한 결과였다. DNS라는건 결국 IP정보를 연결해주는 기술인데, 내 서버의 분기는 포트를 사용하고 있는것이니까!
그래서 이부분이 구조상 큰 문제라고 판단했고, 이를 해결하기 위해 고민했다.
결과적으로 Nginx를 하나 더 두고 Reverse-Proxy 전용으로 사용하며 도메인 기반 분기 시스템을 만들어 해결할 수 있었다.
Nginx Reverse Proxy
구현은 간단하다. 두 환경과는 별개로 가장 앞단에 위치할 Nginx 컨테이너를 만들고, 해당 Nginx에서 현재 홈서버를 향하는 모든 요청을 받는다. 그리고 요청URI를 확인한 뒤, 해당하는 서버의 Nginx로 요청을 proxy하면 되는것이다.
그림으로 표현하자면 아래와 같다.

따라서 리버스 프록시가 해야할일은 다음과 같아진다.
1. External networks를 설정한다.
2. 요청URI별로 프록시할 Nginx를 매핑한다.
3. 80요청의 경우 한번에 443으로 redirect 처리한다.
특히 1번이 중요하다.
External networks 설정하기
갑자기 웬 네트워크 설정? 이라고 생각할수있지만, 가만 생각해보면 꼭 필요한 설정이다.
왜냐면 결국 같은네트워크 = 같은 docker-compose안에 설정된 네트워크이기 때문에, 이것 없이는 같은 호스트머신에 있더라고 다른 Docker 네트워크에 있는 nginx와는 통신할 수 없다.
따라서 proxy-network라는 external network를 만들었고, 이를 각 Nginx들에 설정함으로써 서로 통신 할 수 있도록 구조화했다.
# reverse-proxy nginx
services:
reverse-proxy-nginx:
image: nginx:latest
container_name: reverse-proxy-nginx
ports:
- "80:80"
- "446:443"
//생략..
networks:
- proxy-network
networks:
proxy-network:
external: true
# prodct nginx / staging nginx
services:
nginx:
image: nginx:latest
ports:
- "443:443"
- "82:80"
networks:
- prod-network
- proxy-network
//..생략
networks:
prod-network:
proxy-network:
external: true
nginx.conf 구현하기
아까 말한대로 모든 80요청은 HTTPS로 redirect 시킨다.
그리고 나머지 요청들은 uri 경로에 따라서 각 네트워크의 nginx로 전달한다.
events {}
http {
server {
listen 80;
server_name ${product-uri} ${staging-uri};
# 모든 HTTP 요청 HTTPS로 리다이렉트
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name ${product-uri};
ssl_certificate /etc/nginx/ssl/prod_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/prod_privkey.pem;
location / {
proxy_pass http://${product-nginx}:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl;
server_name ${staging-uri};
ssl_certificate /etc/nginx/ssl/staging_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/staging_privkey.pem;
location / {
proxy_pass http://${staging-nginx}:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
여기서 포인트는, HTTPS(443)가 아닌 HTTP(80)로 프록시한다는것이다.
그 이유는 우선 리버스 프록시 자체에서 SSL/TLS 핸드셰이크와 암/복호화를 모두 처리된 상태기 때문이다. 따라서 이미 평문 HTTP트래픽이 되었기 때문에 별도의 관리가 필요없어진 것. 또한 리버스프록시와 애플리케이션 서버와 같이 서로 신뢰하고 있는 내부망 안에서는 굳이 CPU 자원이 더 드는 HTTPS를 사용할 이유가 없었다(우린 민감데이터도 없으니까...)
다만 애플리케이션쪽에 원래 요청은 HTTPS였다는걸 알려주기 위해서 다음 설정을 필수로 넣어줬다.
proxy_set_header X-Forwarded-Proto $scheme;
이렇게 Nginx Reverse Proxy를 완성했고, 도메인 기반으로 트래픽을 분기하여 처리해주는 아키텍처를 완성 할 수 있었다.
이를 통해 얻을 수 있는 이점은 다음과 같다.
- 포트번호 기반으로 동작하던 기존 로직이 수정되어 포트 실수로 인한 요청실패를 줄일 수 있게 되었고
- 단 하나의 80/443으로 모든 URI가 처리 가능하니 SSL인증 자동화도 훨씬 편리해졌다.
- 또한 로그/모니터링/로드밸런싱 등 정책을 중앙에서 적용 할 수 있게 되었다.
이뿐만 아니라, 앞으로 서비스 규모가 커져도 새로운 스테이징/테스트 환경을 쉽게 추가 할 수 있을것이다!
이번 포스팅이 비슷한 구조를 고민중인 분들께 도움이 되길 바란다ㅎㅎ
'프로젝트 > 빼곡' 카테고리의 다른 글
| crontab으로 DB 백업 자동화하기 (2) | 2025.07.08 |
|---|---|
| Metabase로 애널리틱스 대시보드 구축하기 (1) | 2025.05.22 |
| Bean일까 Util일까, CookieUtil 리팩토링하기 (1) | 2025.03.08 |
| 빼곡 스토어 출시! 그리고 개선방향(feat.코드리뷰) (1) | 2025.03.08 |
| Spring 명시적 Null값으로 부분 업데이트(PATCH) 구현하기 (9) | 2025.01.02 |