kjp0411 님의 블로그
내일배움캠프 단기심화 Java - 본 캠프 Day 69 본문
TIL - 2026.05.21
주제
최종 프로젝트 개발 키워드 정리
프로젝트 개요
이번 최종 프로젝트에서는 예매 서비스를 주제로 MSA 기반 티켓팅 시스템을 개발했다.
단순히 기능을 구현하는 것뿐만 아니라, 실제 배포 환경에서 여러 서비스를 분리하고, Gateway를 통한 단일 진입점 구성, Keycloak 기반 인증/인가, Eureka 서비스 디스커버리, Config Server를 통한 중앙 설정 관리, AWS 기반 배포와 ALB 이중화까지 경험했다.
특히 나는 인프라 및 배포 환경 구성, Gateway 연동, 서비스 간 통신 확인, 운영 환경 문제 해결을 중심으로 프로젝트에 참여했다.
개발 키워드 정리
| 키워드 | 사용한 이유 | 설명 가능한 포인트 |
|---|---|---|
| MSA | 서비스별 책임을 분리하고 확장성을 고려하기 위해 사용 | user, match, reservation, payment 등 도메인별 서비스를 분리하여 개발 |
| Spring Cloud Gateway | 외부 요청의 단일 진입점을 만들기 위해 사용 | 클라이언트 요청을 Gateway에서 받고, 내부 서비스로 라우팅 |
| Eureka | 서비스 위치를 동적으로 찾기 위해 사용 | 서비스 IP/Port를 직접 고정하지 않고 서비스 이름 기반으로 통신 |
| Config Server | 서비스별 설정을 중앙에서 관리하기 위해 사용 | Gateway, Eureka, 각 서비스 설정을 config-repo에서 관리 |
| Keycloak / OAuth2 / JWT | 인증/인가를 직접 구현하지 않고 표준 기반으로 처리하기 위해 사용 | 로그인 후 발급된 JWT를 Gateway에서 검증하고 권한 기반 접근 제어 |
| Docker Compose | 여러 서비스를 한 번에 실행하고 환경을 통일하기 위해 사용 | 로컬/EC2 환경에서 서비스, Kafka, Redis, Keycloak 등을 컨테이너로 관리 |
| AWS EC2 / RDS / ALB | 실제 배포 환경을 구성하기 위해 사용 | 애플리케이션 서버 2대와 ALB를 활용해 외부 요청 분산 구조 구성 |
| CI/CD | 수동 배포 부담을 줄이고 안정적인 배포 흐름을 만들기 위해 사용 | GitHub Actions를 통해 이미지 빌드 및 EC2 배포 자동화 |
| Kafka | 서비스 간 비동기 이벤트 처리를 위해 사용 | 결제, 예약 등 서비스 간 결합도를 낮추고 장애 전파를 줄이기 위한 구조 |
| CORS / Security 설정 | 프론트엔드와 백엔드 연동 과정에서 발생한 인증 문제를 해결하기 위해 사용 | OPTIONS 요청, Authorization Header, Gateway 보안 필터 설정을 점검 |
기술 선택 이유 정리
1. Gateway를 단일 진입점으로 구성한 이유
초기에는 각 서비스가 직접 외부 요청을 받을 수도 있었지만, 서비스가 많아질수록 인증/인가, CORS, 라우팅 설정이 분산되는 문제가 생길 수 있다고 판단했다.
그래서 외부 요청은 모두 Gateway로만 들어오도록 구성했다.
이를 통해 인증/인가 검증 위치를 Gateway 중심으로 모을 수 있었고, 클라이언트 입장에서도 여러 서비스 주소를 직접 알 필요 없이 하나의 진입점만 바라보도록 구성할 수 있었다.
2. Eureka를 사용한 이유
MSA 환경에서는 서비스가 여러 개로 분리되어 있고, 각 서비스의 위치가 변경될 수 있다.
서비스 간 통신 시 IP와 Port를 직접 고정하면 배포 환경이 바뀔 때마다 설정을 수정해야 하는 문제가 생긴다.
이를 해결하기 위해 Eureka를 사용했다.
각 서비스가 Eureka에 등록되고, Gateway는 lb://SERVICE-NAME 방식으로 서비스를 찾아 라우팅할 수 있도록 구성했다.
3. Config Server를 사용한 이유
서비스별 설정 파일을 각 프로젝트에 직접 두면 운영 환경에서 설정 변경이 어렵고, 여러 서비스의 설정을 일관되게 관리하기 어렵다.
그래서 Config Server와 config-repo를 사용해 중앙 설정 관리 구조를 적용했다.
이를 통해 Gateway 라우팅 정보, Eureka 주소, JWT 관련 설정 등을 한 곳에서 관리할 수 있었다.
4. Keycloak을 사용한 이유
인증/인가 기능을 직접 구현할 수도 있었지만, 최종 프로젝트에서는 OAuth2와 JWT 기반 인증 흐름을 경험하는 것이 더 의미 있다고 판단했다.
Keycloak을 사용하면서 로그인, 토큰 발급, 역할 기반 권한 관리, JWT 검증 흐름을 표준 방식으로 구성할 수 있었다.
특히 Gateway에서 JWT를 검증하고, 내부 서비스는 필요한 사용자 정보만 전달받는 구조를 만들 수 있었다.
5. ALB와 애플리케이션 서버 2대를 구성한 이유
단일 서버만 사용할 경우 해당 서버에 문제가 생기면 전체 서비스 접근이 어려워질 수 있다.
그래서 애플리케이션 서버를 2대로 구성하고 ALB를 통해 요청을 분산하도록 했다.
이를 통해 한 서버에 장애가 발생하더라도 다른 서버로 요청을 처리할 수 있는 구조를 경험했다.
다만 비용과 운영 부담을 고려해 NAT Gateway를 사용하는 완전한 Private Subnet 구조까지는 적용하지 못했고, RDS만 Private 영역에 두는 방향으로 구성했다.
문제 해결 경험 정리
문제 1. Gateway에서 401 Unauthorized 발생
문제 상황
프론트엔드 또는 API 테스트 도구에서 Gateway로 요청을 보냈을 때 401 Unauthorized가 발생했다.
처음에는 토큰 문제라고 생각했지만, 실제로는 Gateway의 JWT 설정, Keycloak issuer-uri, jwk-set-uri, CORS 설정 등이 함께 영향을 주고 있었다.
검토한 선택지
- 각 서비스에서 인증을 직접 처리한다.
- Gateway에서만 JWT 검증을 수행한다.
- 임시로 전체 permitAll 처리 후 기능 개발을 먼저 진행한다.
선택한 방식
최종적으로는 Gateway에서 JWT를 검증하는 구조를 유지했다.
다만 문제를 분리하기 위해 서비스 내부 보안 설정은 MVP 단계에서 일부 완화하고, Gateway 중심으로 인증 흐름을 점검했다.
결과
Gateway에서 Keycloak 토큰을 검증하고, 서비스로 정상 라우팅되는 흐름을 확인했다.
이를 통해 MSA 환경에서는 인증 문제를 볼 때 단순히 코드만 보는 것이 아니라, Gateway, Keycloak, 환경변수, Docker 네트워크, CORS 설정까지 함께 확인해야 한다는 점을 배웠다.
문제 2. Eureka 등록 정보와 실제 서비스 접근 주소 불일치
문제 상황
서비스 컨테이너는 정상적으로 실행되고 있었지만, Gateway에서 특정 서비스로 라우팅이 되지 않는 문제가 있었다.
확인해보니 Eureka에 등록된 서비스 주소 또는 포트 정보가 실제 Gateway 컨테이너에서 접근 가능한 값과 맞지 않는 문제가 있었다.
검토한 선택지
- Gateway 라우팅 주소를 직접 IP:Port로 고정한다.
- Eureka 등록 정보를 수정해 서비스 이름 기반 라우팅을 유지한다.
- 각 서비스별로 별도의 환경변수를 두어 서버별 IP를 명시한다.
선택한 방식
MSA 구조의 목적에 맞게 Eureka 기반 서비스 디스커버리를 유지하고, 각 서비스의 Eureka 등록 정보와 환경변수를 수정하는 방향으로 해결했다.
결과
Gateway에서 Eureka에 등록된 서비스 이름을 기준으로 정상 라우팅할 수 있게 되었다.
이 과정을 통해 서비스 디스커버리는 단순히 라이브러리를 추가하는 것이 아니라, 배포 환경의 네트워크 구조와 함께 맞춰야 한다는 점을 배웠다.
문제 3. 수동 배포로 인한 운영 부담
문제 상황
초기에는 서비스 코드가 변경될 때마다 인프라 담당자가 직접 EC2에 접속해 이미지를 갱신하고 컨테이너를 재시작해야 했다.
서비스가 많아질수록 이 방식은 실수 가능성이 높고, 배포 속도도 느려지는 문제가 있었다.
검토한 선택지
- 계속 수동 배포를 유지한다.
- 전체 서비스를 한 번에 배포하는 스크립트를 작성한다.
- GitHub Actions 기반 CI/CD를 적용한다.
선택한 방식
GitHub Actions를 활용해 서비스별 이미지 빌드, GHCR Push, EC2 배포 흐름을 자동화했다.
결과
코드 변경 후 배포까지의 반복 작업을 줄일 수 있었고, 팀원이 각자 담당 서비스의 변경 사항을 더 빠르게 반영할 수 있게 되었다.
이를 통해 배포 자동화는 단순한 편의 기능이 아니라, 팀 개발 속도와 운영 안정성에 직접 영향을 주는 요소라는 점을 배웠다.
포트폴리오에 연결할 수 있는 표현
이번 프로젝트에서 나는 단순 기능 구현보다 실제 서비스가 운영 환경에서 동작하기 위한 인프라와 인증/인가 흐름을 중점적으로 담당했다.
Spring Cloud Gateway, Eureka, Config Server를 활용해 MSA 기반 서비스 라우팅 구조를 구성했고, Keycloak과 JWT를 연동해 Gateway 중심의 인증/인가 구조를 설계했다.
또한 AWS EC2, RDS, ALB, Docker Compose를 활용해 실제 배포 환경을 구성하고, 서비스 간 통신 문제, CORS 문제, Eureka 등록 정보 불일치, JWT 검증 오류 등을 직접 추적하며 해결했다.
이 과정에서 단순히 기술을 적용하는 것보다, 문제가 발생했을 때 로그와 네트워크 흐름, 환경변수, 설정 파일을 함께 확인하며 원인을 좁혀가는 과정이 중요하다는 것을 배웠다.
면접에서 설명할 수 있는 포인트
Q. MSA를 사용한 이유는 무엇인가요?
서비스별 책임을 분리하고, 각 도메인을 독립적으로 개발·배포할 수 있는 구조를 경험하기 위해 MSA를 사용했습니다.
다만 MSA는 단순히 서비스를 나누는 것만으로 끝나는 것이 아니라, Gateway, 서비스 디스커버리, 중앙 설정 관리, 인증/인가, 서비스 간 통신 문제까지 함께 고려해야 한다는 점을 프로젝트를 통해 체감했습니다.
Q. Gateway를 사용한 이유는 무엇인가요?
외부 요청의 진입점을 하나로 통일하기 위해 사용했습니다.
각 서비스가 직접 외부 요청을 받게 되면 인증/인가, CORS, 라우팅 설정이 서비스별로 흩어질 수 있습니다.
Gateway를 사용하면 클라이언트는 하나의 주소만 바라보면 되고, 인증/인가와 라우팅을 중앙에서 관리할 수 있다는 장점이 있습니다.
Q. 프로젝트에서 가장 어려웠던 문제는 무엇인가요?
가장 어려웠던 문제는 로컬에서는 정상 동작하던 설정이 실제 EC2와 Docker 환경에서는 다르게 동작하는 문제였습니다.
특히 Gateway, Eureka, Keycloak, 각 서비스 컨테이너가 서로 다른 네트워크 환경에서 동작하다 보니 단순히 코드 문제가 아니라 환경변수, 컨테이너 내부 주소, Eureka 등록 정보, JWT issuer 설정까지 함께 확인해야 했습니다.
이 문제를 해결하면서 운영 환경에서는 애플리케이션 코드뿐만 아니라 인프라와 네트워크 구조를 함께 이해해야 한다는 점을 배웠습니다.
오늘의 회고
이번 최종 프로젝트를 통해 기능 구현뿐만 아니라, 실제 서비스가 배포 환경에서 동작하기 위해 필요한 요소들을 많이 경험했다.
특히 MSA 구조에서는 하나의 기능이 정상 동작하기 위해 여러 서비스, Gateway, 인증 서버, 설정 서버, 네트워크 환경이 함께 맞아야 한다는 점을 체감했다.
아직 대규모 트래픽 처리, 장애 대응 자동화, 모니터링 고도화까지는 부족한 부분이 있지만, 이번 프로젝트를 통해 백엔드 개발자가 단순히 API만 구현하는 것이 아니라 서비스가 안정적으로 운영될 수 있는 구조까지 고민해야 한다는 점을 배웠다.
앞으로는 이번 프로젝트에서 겪은 문제들을 포트폴리오와 면접 답변으로 정리하고, 각 기술을 왜 사용했는지 더 명확하게 설명할 수 있도록 준비할 예정이다.
'TIL' 카테고리의 다른 글
| 내일배움캠프 단기심화 Java - 본 캠프 Day 70 (0) | 2026.05.22 |
|---|---|
| 내일배움캠프 단기심화 Java - 본 캠프 Day 68 (0) | 2026.05.20 |
| 내일배움캠프 단기심화 Java - 본 캠프 Day 67 (0) | 2026.05.19 |
| 내일배움캠프 단기심화 Java - 본 캠프 Day 66 (0) | 2026.05.18 |
| 내일배움캠프 단기심화 Java - 본 캠프 Day 65 (0) | 2026.05.15 |
