ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • AWS EC2 단일 인스턴스에서 Spring Boot + MySQL + Nginx배포 경험기
    Web 2025. 8. 27. 21:20

     

    서론

    지난 멋쟁이사자처럼 13기 전국연합해커톤에서 백엔드 개발자로 참여했습니다. 이번 해커톤에서는 파이브클라우드에서 제공하는 AWS EC2 서버를 활용하여, 단일 서버 안에서 Spring Boot 애플리케이션과 MySQL 데이터베이스 그리고 Nginx를 이용한 프론트엔드 를 배포하고 운영하는 경험을 쌓았습니다.

    이번 글에서는 제가 EC2 서버를 활용해 백엔드와 DB를 배포하고, 프론트엔드 정적 파일을 Nginx로 서비스한 과정을 상세히 설명하며, 이번 해커톤에서 사용되는 구조와 실제 서비스에서 주로 사용하는 구조를 비교하고자 합니다.

     


     

    1. EC2 서버 환경 구성

    파이브클라우드에서 제공받은 EC2 인스턴스는 Ubuntu 기반이었고, SSH로 접속해 환경을 구성했습니다. (자바 등 필요한 소프트웨어 설치과정은 생략했습니다)

     

    1. 프로젝트 디렉토리 생성 및 파일 업로드

    먼저, 로컬 개발 환경에서 빌드한 Spring Boot JAR 파일을 EC2 서버로 옮겨야 합니다.
    이를 위해 EC2에 SSH로 접속한 뒤, 프로젝트를 담을 디렉토리를 만들어줍니다.

    ssh -i MyKey.pem ec2-user@발급ip
    mkdir oreumi-backend

     

    그다음, scp 명령어를 사용해 로컬에서 생성된 oreumi-backend-0.0.1-SNAPSHOT.jar 파일을 서버에 업로드합니다.

    scp oreumi-backend-0.0.1-SNAPSHOT.jar ec2-user@발급ip:~/oreumi-backend/
    • scp는 SSH 프로토콜을 이용한 파일 복사 명령어입니다.
    • ec2-user@발급ip:~/oreumi-backend/는 서버의 사용자와 디렉토리 경로를 의미합니다.
    • 이렇게 하면 로컬에서 빌드된 JAR 파일이 EC2 서버의 /home/ec2-user/oreumi-backend 디렉토리에 업로드됩니다.

    즉, 이 과정은 개발 환경에서 만든 실행 파일을 배포 서버에 올리는 단계라고 할 수 있습니다.

     

     

     

    2. 환경 변수 설정
    서버에서 애플리케이션이 실행될 때 필요한 API 키, DB 비밀번호 같은 민감한 값들은 직접 코드에 하드코딩하지 않고 환경 변수로 관리합니다.

    이번 프로젝트에서는 OpenAI API 연동을 위해 OPENAI_API_KEY라는 환경 변수를 EC2 서버에 등록했습니다.

    nano ~/.zshrc

     

    파일에 아래 내용을 추가

    export OPENAI_API_KEY='API_KEY'

     

    변경 사항을 적용합니다

    source ~/.zshrc

     

    즉, 이 과정으로 애플리케이션 실행 환경을 서버에 맞게 세팅했습니다.

     


     

     

    2. MySQL 설치 및 외부 접속 설정

    EC2 서버에 MySQL을 설치한 뒤, 이번 프로젝트에서 사용할 데이터베이스와 계정을 직접 생성했습니다.

     

    1. 데이터베이스 생성

    • 데이터베이스 이름: oreumi-db
    • 사용자: oreumi
    • 비밀번호: ****
    CREATE DATABASE IF NOT EXISTS `oreumi-db`
    CHARACTER SET utf8mb4
    COLLATE utf8mb4_general_ci;

     

     

    2. 사용자 및 권한 부여

    Spring Boot 애플리케이션에서 DB에 접근할 수 있도록 전용 사용자 계정을 생성했습니다.

    CREATE USER 'oreumi'@'%' IDENTIFIED BY 'password';
    GRANT ALL PRIVILEGES ON `oreumi-db`.* TO 'oreumi'@'%';
    FLUSH PRIVILEGES;

     

    • 'oreumi'@'%': 어디서든 접속 가능한 계정
    • oreumi-db.*: 해당 데이터베이스 내 모든 테이블 권한 부여

    DBeaver에 연결 후 관리하기 위해 외부 접속 허용 설정을 해주었습니다. 

     

     

     

    3. 외부 접속 가능 여부 확인

    SHOW VARIABLES LIKE 'bind_address';

    * 또는 0.0.0.0 이면 외부 접속 허용

     

     

    4. DBeaver 연결 시 주의점

    외부에서 접속 시 Public Key Retrieval is not allowed 오류가 발생했습니다.

    ▶ DBeaver에서 Driver Properties → allowPublicKeyRetrieval=true 옵션을 추가해야 정상적으로 접속됩니다.

     

     


     

     

    3. Spring Boot 애플리케이션 배포

    JAR 파일을 업로드한 후, nohup을 사용해 백그라운드에서 실행했습니다.

    nohup java -jar oreumi-backend-0.0.1-SNAPSHOT.jar &
    tail -f nohup.out

     

    • 터미널 종료 후에도 애플리케이션 실행 유지
    • 로그 확인 가능

    application.yml에는 DB 정보와 환경 변수를 연결하여 MySQL과 연동했습니다.

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/oreumi-db?serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false
        username: oreumi
        password: ${SPRING_DATASOURCE_PASSWORD}

     

     


     

    4. Nginx를 활용한 프론트엔드 배포

    해커톤에서는 백엔드뿐만 아니라 프론트엔드도 함께 배포해야 했기 때문에, 같은 EC2 인스턴스 안에서 Nginx를 이용해 정적 파일을 서비스했습니다.

     

    Nginx란?

    • Nginx는 웹 서버이자 리버스 프록시 서버입니다.
    • 정적 파일 제공, 로드 밸런싱, 요청 프록시 처리 등 다양한 역할 가능
    • 이번 프로젝트에서는 두 가지 역할을 수행
      1. 정적 파일 제공: 프론트엔드 빌드 파일 제공
      2. 프록시 서버: /api/ 요청을 백엔드로 전달

     

    즉, Nginx가 프론트엔드 요청을 처리하고, 백엔드 요청은 Spring Boot로 전달해 단일 EC2에서 프론트와 백엔드를 모두 서비스할 수 있게 하는 구조입니다.

     

     

    1. 빌드 및 업로드

    먼저 프론트엔드 프로젝트를 EC2 서버에서 서비스할 수 있도록 정적 파일로 빌드합니다.

    cd FE
    npm run build
    • npm run build 명령어를 실행하면, React 또는 Vue 프로젝트는 HTML, CSS, JS 파일 등 정적 리소스로 변환되어 build/ 폴더에 생성됩니다.
    • 이 빌드 결과물은 브라우저가 요청할 때 바로 제공할 수 있는 형태입니다.

     

    이제 빌드된 파일을 EC2 서버로 전송합니다.

    scp -i hackathon_server31.pem -r build/* ec2-user@발급ip:/home/ec2-user/frontend/

     

    • scp는 SSH를 통해 파일을 안전하게 복사하는 명령어입니다.
    • -r 옵션은 디렉토리 전체를 재귀적으로 복사할 때 사용합니다.
    • EC2 서버에서는 /home/ec2-user/frontend/ 디렉토리에 빌드된 정적 파일이 위치하게 됩니다.

     

     

    2. Nginx 루트 디렉토리 반영

    sudo rm -rf /usr/share/nginx/html/*
    sudo cp -r /home/ec2-user/frontend/* /usr/share/nginx/html/
    sudo systemctl restart nginx
    • /usr/share/nginx/html/ : Nginx의 기본 정적 파일 루트 디렉토리
    • rm -rf : 기존 HTML 파일 삭제
    • cp -r : 새로 빌드한 정적 파일을 루트 디렉토리로 복사
    • systemctl restart nginx : Nginx를 재시작해 변경 사항 반영

    결과적으로 Nginx는 브라우저 요청 시 /usr/share/nginx/html에 있는 정적 파일을 제공하게 됩니다.

     

     

    3. Nginx 설정 변경

    server {
        listen 80;
        server_name 발급ip;
    
        root /home/ec2-user/frontend;
        index index.html;
    
        location / {
            try_files $uri /index.html;
        }
    
        location /api/ {
            proxy_pass http://localhost:8080/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
    • listen 80 : 80번 포트로 들어오는 요청을 수신
    • server_name 발급ip : 서버의 공인 IP 또는 도메인을 지정
    • root /usr/share/nginx/html : 정적 파일이 위치한 디렉토리 지정
    • index index.html : 기본 파일로 index.html을 사용
    • location / { try_files $uri /index.html; }
      • 브라우저에서 특정 URL로 접근 시 해당 파일이 존재하면 제공, 없으면 index.html 반환
      • SPA 라우팅을 위해 필요
    • location /api/
      • /api/로 시작하는 요청은 백엔드로 전달
      • proxy_pass http://localhost:8080/; : 로컬에서 실행 중인 Spring Boot 서버로 프록시
      • proxy_set_header : 클라이언트 IP 등 헤더 정보를 백엔드에 전달

     

     


     

     

    4. 현재 아키텍처 (EC2 단일 서버)

    [Client / Frontend]
            |
            | HTTP REST API
            v
    [EC2 Instance]
       ├─ Spring Boot Application (Backend)
       ├─ MySQL Database
       └─ Nginx (Frontend 배포)
     
    • Spring Boot: API 요청 처리, 비즈니스 로직 수행, GPT API 연동
    • MySQL: 사용자, 리뷰, 로그 등 모든 데이터를 단일 DB에서 관리
    • Nginx: 정적 프론트엔드 파일 제공 및 /api 요청을 Spring Boot로 프록시 처리
    • 운영 방식: 단일 서버에서 모든 서비스를 실행 → 터미널 종료 후에도 nohup으로 백그라운드 유지

    장점

    • 간단한 배포와 운영: 단일 서버만 설정하면 끝나므로 해커톤과 초기 개발 단계에 적합
    • 비용 효율적: 서버 한 대만 사용하므로 초기 비용이 낮음

    단점

    • 확장성 부족: 트래픽이 늘어나면 서버 한 대로 처리해야 하므로 성능 한계
    • 신뢰성 문제: 서버가 다운되면 백엔드, DB, 프론트 모두 동시에 중단
    • 보안 취약: 백엔드와 DB가 같은 서버에 있어 외부 공격에 취약

     

     


     

    5. 실제 서비스에서 흔히 사용하는 구조

    실제 운영 환경에서는 단일 서버보다는 서비스 분리와 확장성을 고려한 아키텍처가 선호됩니다.

    [Client / Frontend]
            |
            | HTTP REST API
            v
    [Load Balancer] -> [Spring Boot Backend Instances]
            |
            | JDBC / Connection Pool
            v
    [Database Cluster (RDS or MySQL Cluster)]

    구성 요소 

    1. Frontend: 사용자 브라우저, React/Vue SPA
    2. Load Balancer: 클라이언트 요청을 여러 백엔드 인스턴스로 분산 → 트래픽 분산, 장애 대응
    3. Spring Boot Backend Instances: 필요에 따라 인스턴스 수를 자동으로 조절 → 확장성 
    4. Database Cluster (RDS 또는 MySQL Cluster): 백엔드와 분리, 읽기/쓰기 분리, 장애 복구, 백업 관리

    장점

    • 확장성: 트래픽 증가 시 백엔드 인스턴스 추가 가능
    • 가용성: 한 서버가 다운되어도 다른 서버가 서비스 제공
    • 보안 강화: DB와 백엔드 분리, 외부 접근 제한 가능
    • 운영 효율성: 데이터베이스 클러스터를 이용하면 읽기/쓰기 부하 분산, 스냅샷 및 백업 자동화

    단점

    • 초기 설정과 비용 부담: 여러 서버와 LB, RDS를 구성해야 하므로 단일 서버보다 초기 비용 및 관리가 복잡
    • 운영 난이도 상승: 인프라 관리, 모니터링, 자동 확장, 장애 대응 등 추가 고려 필요

    결론

    • 해커톤 및 초기 배포: 단일 EC2 서버 아키텍처가 효율적
    • 실제 운영 서비스: 트래픽 증가, 안정성, 보안, 확장성을 위해 백엔드/프론트/DB 분리 아키텍처 추천

    ▶ 이번 경험을 통해 단일 서버 배포의 간단함분리 아키텍처의 필요성을 모두 이해할 수 있었습니다.

     

     


     

     

    6. 느낀 점 

    • 단일 EC2 서버에서 백엔드와 DB를 배포하며 발생하는 문제를 직접 해결
    • 외부 DB 접속, 환경 변수 관리, Spring Boot 배포를 실제로 수행
    • 실제 서비스 구조와 단일 서버 구조의 차이를 이해하고, 확장 가능성을 고려한 설계 방법 학습
Designed by Tistory.