프로젝트/캡스톤 졸프

[HandDoc] 한 대의 EC2로 Spring BE + FastAPI(AI) + Nginx 프록시 배포 (MongoDB Atlas)

rngPwns 2025. 8. 26. 15:41

우리의 로고!

 

HandDoc 졸업프로젝트에서 평소 건드려보고 싶었던 배포를 맡았다 !! 

우선 FastAPI 레포, BE 배포를 진행했다. (가능하면 UMC 경험을 살려 FE 배포도 맡을 예정~!)

 

일단 최대한 서버 한 대로 버텨보자는 생각에  EC2 t3.micro 한 대에 Nginx 프록시로 라우팅만 잡아 /api → Spring(8080), /ai(/ws) → FastAPI(8000) 구조로 가기로 했다. DB는 MongoDB Atlas로 분리했다.

 

 

🎈깃허브 : https://github.com/3-NoPainNoGain

레포 구조입니둥

 

 

[폴더구조]

/home/ec2-user/ 
 └── apps/ 
		  └── .env  # Spring에서 사용할 환경 변수 모음 
		  └── deploy.sh  # 블루/그린 무중단 배포를 위한 스크립트 
		  └── docker-compose.spring-blue.yml # 블루 컨테이너
		  └── docker-compose.spring-green.yml  # 그린 컨테이너
		  └── docker-compose.fastapi.yml # fastapi 서비스 컨테이너 

/etc/nginx/conf.d # 리버스 프록시 설정 
 └── handdoc.conf 
 └── active-upstream.conf # deploy.sh에서 배포 성공 시에 포트 교체하고 nginx reload 

 

 

 

처음 AWS배포를 진행해보는 분들께도 도움을 드릴 수 있도록 과정을 최대한 자세히 써 보겠다.

 

 

1. AWS 콘솔 설정 

 

먼저 Google에 AWS 콘솔을 검색하여 들어간다. 

자세한 aws 콘솔 가입 및 설정 과정은 이미 다른 블로그에 많으니 참고하시면 되어요~

 

1) AWS 콘솔 회원가입 & 비용 알람

  • 계정 만들고 결제카드 등록까지 했다. (루트 계정으로 로그인)
  • 바로 Billing → Budgets에서 월 사용액 알람 만들어두었다. 잘못되어 과금되면 안 되니까..

내 계정은 t3.micro가 프리티어였다. (t2는 유료였음!!)

 

2) IAM 사용자 만들기 (루트 로그인 막기)

루트로 계속 작업하면 위험하기 때문에

  • IAM 사용자를 만들고 EC2·VPC 필요한 권한만 부여.
  • 앞으로는 이 사용자로 콘솔/CLI 에 들어가기로 한다. (루트는 결제/보안만)

3) 리전 고정

서울( ap-northeast-2 )로 고정했다. 리전이 달라지면 EIP/EC2가 달라서 헷갈릴 수도 있다!

 

4) EC2 만들기 & EIP 연결

  • 인스턴스: t3.micro, AMI: Amazon Linux 2023
  • 키 페어 생성: handdoc-key.pem (로컬 보관)
  • 탄력적 IP(EIP) 할당 및 인스턴스에 연결(Associate)
  • (확인) 인스턴스 내부에서 IMDSv2로 공인 IP 조회:
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/public-ipv4
# 결과: 43.200.110.36

 

트러블슈팅: SG ID 404

  • 처음에 SG ID를 /latest/meta-data/security-group-ids로 치다가 404 받음 !! (경로 틀림)
  • MAC 경로로 수정해서 해결:
MAC=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/network/interfaces/macs/ | head -n1)
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/network/interfaces/macs/${MAC}security-group-ids
# 결과 예: sg-0850234c0d72f2d0e

 

나중에 8000, 8080은 닫음. 외부에 오픈하는 주소가 아니라 한다.

2. Windows에서 SSH 접속(권한 문제/타임아웃 대응)

pem 권한

$KEY="C:\Users\<me>\Desktop\handdoc-key.pem"
icacls $KEY /inheritance:r
icacls $KEY /grant:r "$($env:USERNAME):R"

접속

ssh -i "C:\Users\<me>\Desktop\handdoc-key.pem" ec2-user@43.200.110.36

 

트러블슈팅 1: Connection timed out

  • SG의 22/tcp가 내 IP로 열려 있지 않았음 → 내 현재 IP/32로 열고 해결.
  • 사무실/카페 네트워크에서 막힐 때는 테더링으로 테스트하면 된다고 하네요!

트러블슈팅 2: 호스트 키 경고

  • 첫 접속 시 fingerprint 확인 메시지 → yes 입력 후 정상 접속.

 

3. 기본 OS 설정

sudo timedatectl set-timezone Asia/Seoul
sudo dnf update -y
sudo dnf install -y git unzip htop

 

 

4. Docker & Compose 설치 (AL2023 고생포인트..)

잘못된 시도(실패)

  • docker-ce CentOS 리포를 붙여보니 404 / 패키지 미존재:
    • docker-ce.repo 추가 → Failed to download metadata for repo 'docker-ce-stable'
    • docker-compose-plugin No match 오류

해결

  • Amazon Linux 2023에서는 기본 docker 패키지로 설치:
sudo dnf install -y docker
sudo systemctl enable --now containerd docker
sudo usermod -aG docker ec2-user && newgrp docker
docker version
docker run --rm hello-world   # 성공
docker compose version        # v2.27.0 확인

교훈: AL2023에 docker-ce 리포 붙이지 말기. 기본 repo의 docker 패키지로 충분하다.

 

5. 앱 디렉토리 & .env

mkdir -p ~/apps/handdoc/{be,fastapi,nginx,logs}
cd ~/apps/handdoc

JWT=$(openssl rand -hex 32)
cat > .env <<EOF
SPRING_PROFILES_ACTIVE=prod
ALLOWED_ORIGINS=http://localhost:5173
MONGODB_URI=mongodb+srv://<DB_USER>:<DB_PASS>@<CLUSTER_HOST>/handdoc?retryWrites=true&w=majority
SERVER_PORT=8080
JWT_SECRET=$JWT
AI_PORT=8000
EOF
chmod 600 .env

 

트러블슈팅: .env에 이상한 텍스트 섞임

  • 터미널 붙여넣다가 명령어 조각이 .env 마지막 줄에 붙음.
  • 값만 뽑아서 깨끗하게 재작성:
SPR=$(grep -m1 '^SPRING_PROFILES_ACTIVE=' .env | cut -d= -f2-)
ALLOWED=$(grep -m1 '^ALLOWED_ORIGINS=' .env | cut -d= -f2-)
MONGO=$(grep -m1 '^MONGODB_URI=' .env | cut -d= -f2-)
SRV=$(grep -m1 '^SERVER_PORT=' .env | cut -d= -f2-)
JWT=$(grep -m1 '^JWT_SECRET=' .env | cut -d= -f2-)

cat > .env.clean <<EOF
SPRING_PROFILES_ACTIVE=$SPR
ALLOWED_ORIGINS=$ALLOWED
MONGODB_URI=$MONGO
SERVER_PORT=$SRV
JWT_SECRET=$JWT
AI_PORT=8000
EOF

mv -f .env.clean .env && chmod 600 .env
sed -n '1,200p' .env
# 최종 .env 6개 키만 깔끔히 확인

현재 .env는 정상이며 깨끗하다. MONGODB_URI는 Atlas URI로 교체 필요(아직 placeholder).

 

6. Nginx(호스트) 설치 & 프록시 설정

 

설치 & 기동

sudo dnf install -y nginx
sudo systemctl enable --now nginx

 

프록시 설정 파일 존재

  • /etc/nginx/conf.d/handdoc.conf 존재 확인(본인이 생성)
  • 핵심 라우팅 라인 확인:
location /api/ { proxy_pass http://127.0.0.1:8080/; ... }
location /ai/  { proxy_pass http://127.0.0.1:8000/; ... }
# WebSocket 업그레이드 관련 헤더 라인도 들어 있음

 

문법 테스트:

sudo nginx -t
 

트러블슈팅: conflicting server name "_" on 0.0.0.0:80

  • nginx -t에서 경고 확인.
  • 원인: 기본 server가 어딘가에 하나 더 있어서 _가 중복되었다.
  • 해결:
    • /etc/nginx/conf.d/default.conf 있으면 비활성화
    • /etc/nginx/nginx.conf 안의 기본 server 블록 주석
    • 이후 sudo nginx -t && sudo systemctl reload nginx

 

7. 포트/서비스 상태 확인

# 80만 리슨 중(정상). 8080/8000은 컨테이너 기동 전이라 없음.
sudo ss -ltnp | grep -E ':80|:8080|:8000' || echo "리슨 없음"

# nginx 상태
systemctl is-active nginx && systemctl is-enabled nginx
sudo nginx -t

8. 보안그룹(의도)

  • 22/tcp → 내 IP/32 (팀원 접속 시 그 사람 IP/32 추가)
  • 80/tcp → 0.0.0.0/0
  • 443/tcp → 필요 시(도메인+SSL)
  • 8080/8000은 외부 개방 안 함 (컨테이너는 127.0.0.1 바인딩 예정)

 

내가 맡은 부분은 여기까지이다. 나머지는 백엔드 api를 작성한 팀원이 담당하기로 했다. 

 

 

 

AWS 배포 인프라의 앞부분을 맡으며 얻은 교훈

 

  • AL2023에서는 docker-ce 리포 붙이지 말기
    • 404/No match로 시간 날림..ㅠ 기본 docker 패키지로 가자.
  • .env 붙여넣기 오염 주의
    •  값만 추출해 새 파일로 재작성하는 스니펫이 유용하다고 한다!
  • NGINX server_name _ 충돌 주의.
    • default/welcome 비활성화 + 기본 server 주석처리로 해결
      • NGINX에서 server_name _; 설정이 있으면 기본 서버(default server)처럼 동작하는데, 이게 여러 군데 선언되어 있으면 충돌이 난다. 그래서 기본 server 블록을 주석 처리하거나 welcome page(디폴트 페이지)를 꺼야 합니당!
  • 보안그룹 22/tcp는 내 IP/32만!
    • 회사/카페 방화벽 이슈 있으면 테더링으로 먼저 점검해보기~
  • IMDSv2 메타데이터 경로는 자주 틀린다
    • SG ID는 NIC(MAC) 하위 경로에서 가져와야 한다.

 

[팀원을 위한 최종 핸드오버 문서]

# HandDoc 인프라 핸드오버

[서버]
- EIP: 43.200.110.36
- User: ec2-user
- OS/Spec: Amazon Linux 2023 / t3.micro
- 작업 경로: /home/ec2-user/apps/handdoc

[보안그룹]
- 22/tcp: 팀원 고정 IP/32만 허용
- 80/tcp: 0.0.0.0/0 허용
- 8080/8000: 0.0.0.0/0 허용 - 나중에 닫음

[Nginx(호스트)]
- 파일: /etc/nginx/conf.d/handdoc.conf
- 라우팅:
  /api/* → 127.0.0.1:8080  (Spring BE)
  /ai/*  → 127.0.0.1:8000  (FastAPI)

[환경파일(.env)]
- 위치: /home/ec2-user/apps/handdoc/.env

 

 

BE의 나머지 배포는 타 팀원이 맡았고 FastAPI 배포 및 github action 처리는 내가 했다 !! FE도 내가 배포할 예정!

이 과정도 아래에 기록해보았으니 꼬옥 읽어보시길 바란다.

 

 

 

2025.08.27 - [프로젝트/캡스톤 졸프] - [HandDoc] FastAPI 추론 서버 - Docker+GitHub Actions로 EC2에 배포해보자