백엔드 개발을 공부하다 보면 기능을 구현하는 것만큼이나 만든 서비스를 실제로 어디에서 실행할지도 중요합니다.
Spring Boot 프로젝트를 실행하면 localhost:8080에서 잘 동작하니까
“이제 완성된 거 아닌가?”라고 생각할 수도 있습니다.
그런데 곰곰이 생각해보면
내 컴퓨터에서만 실행되는 프로젝트는 아직 혼자만 볼 수 있는 프로그램에 가깝습니다.
다른 사람이 접속할 수 있게 하려면
서버가 필요하고,
그 서버가 인터넷과 어떻게 연결되는지도 알아야 하고,
코드를 수정할 때마다 매번 직접 올리는 대신 자동으로 배포되는 흐름도 필요하게 됩니다.
그래서 이번 글에서는
클라우드 컴퓨팅이 무엇인지,
AWS에서 VPC와 EC2는 어떤 역할을 하는지,
그리고 GitHub Actions를 이용해 어떻게 CI/CD 파이프라인을 구성할 수 있는지를 정리해보려고 합니다.
이번 글에서 다룰 내용은 다음과 같습니다.
- 클라우드 컴퓨팅이란 무엇인가
- 왜 AWS 같은 클라우드 서비스를 사용할까
- 리전과 가용 영역은 무엇인가
- EC2와 VPC는 어떤 역할을 하는가
- Public 서브넷과 Private 서브넷은 어떻게 다른가
- Private 서브넷은 왜 필요한가
- CI/CD 파이프라인이란 무엇인가
- GitHub Actions와 Secrets는 어떻게 사용하는가
- Spring Boot 프로젝트를 AWS에 어떻게 배포할 수 있는가
왜 배포를 배워야 할까?
지금까지는 스프링, JPA, 시큐리티, JWT, OAuth 같은 기술들을 이용해서
기능이 동작하는 백엔드 애플리케이션을 만드는 데 집중했다면,
배포는 그 결과물을 실제 서비스처럼 외부에 공개하는 단계라고 볼 수 있습니다.
즉, 배포는 단순히 프로그램을 켜 두는 작업이 아니라
- 어디에서 실행할지 정하고
- 외부 요청을 받을 수 있게 네트워크를 구성하고
- 서버 환경을 준비하고
- 코드를 올리고
- 다시 수정했을 때 자동으로 반영되게 만드는
전체 흐름을 포함합니다.
그래서 배포를 공부하다 보니
백엔드 개발은 단순히 API만 만드는 일이 아니라
그 API가 실제로 돌아갈 환경까지 이해하는 일이라는 점이 더 크게 느껴졌습니다.
클라우드 컴퓨팅이란?
클라우드 컴퓨팅은 쉽게 말하면
다른 회사가 제공하는 서버, 스토리지, 네트워크 같은 컴퓨팅 자원을 빌려서 사용하는 방식입니다.
예전에는 회사가 직접 서버를 사고, 직접 네트워크를 구성하고, 직접 운영하는 경우가 많았습니다.
이런 방식을 온프레미스라고 부릅니다.
그런데 이 방식은 초기 비용이 크고,
프로젝트가 잘 안 되더라도 이미 들어간 서버 비용을 회수하기 어렵다는 문제가 있습니다.
반면 클라우드 컴퓨팅은 필요한 만큼만 자원을 빌려서 쓰고,
규모가 커지면 늘리고 줄어들면 다시 줄일 수 있기 때문에 훨씬 유연합니다.
즉, 클라우드 컴퓨팅은 단순히 “남의 서버를 쓰는 것”이 아니라
필요한 인프라를 필요할 때 빠르게 확보할 수 있게 해주는 방식이라고 이해하면 편했습니다.
왜 AWS 같은 클라우드 서비스를 사용할까?
클라우드 서비스를 사용하는 가장 큰 이유는 결국
비용과 운영 부담을 줄일 수 있기 때문입니다.
만약 프로젝트를 시작할 때마다 직접 서버를 준비해야 한다면
- 초기 비용이 많이 들고
- 확장하기도 어렵고
- 장애나 운영도 직접 감당해야 합니다
반면 AWS 같은 클라우드 서비스는
- 필요한 서버를 빠르게 만들 수 있고
- 사용량에 따라 자원을 조절할 수 있고
- 글로벌 인프라를 이미 갖추고 있어서
- 배포와 운영을 훨씬 유연하게 할 수 있습니다
특히 개인 프로젝트나 사이드 프로젝트 입장에서는
처음부터 큰 서버를 운영하기보다
작게 시작해서 필요할 때 늘릴 수 있다는 점이 꽤 큰 장점처럼 느껴졌습니다.
리전과 가용 영역은 무엇일까?
AWS를 처음 보면 리전(Region), 가용 영역(Availability Zone)이라는 말이 자주 나옵니다.
처음에는 저도 그냥 서버 위치 정도로만 생각했는데,
정리해보니 생각보다 중요한 개념이었습니다.
리전(Region)
리전은 AWS가 전 세계 여러 지역에 만들어둔 큰 단위의 물리적 인프라 위치입니다.
예를 들어 서울 리전, 도쿄 리전, 버지니아 리전처럼
지리적으로 떨어진 여러 위치가 존재합니다.
이렇게 리전을 나누는 이유는 단순합니다.
- 한 지역에 문제가 생겨도 전체 서비스가 멈추지 않게 하기 위해
- 사용자와 가까운 위치에서 서비스를 제공해 속도를 높이기 위해
즉, 리전은 AWS 자원이 모여 있는 큰 지역 단위라고 볼 수 있습니다.
가용 영역(Availability Zone)
가용 영역은 하나의 리전 안에 존재하는 서로 독립된 물리적 데이터 센터 단위입니다.
서울 리전 안에도 여러 가용 영역이 있고,
서로 물리적으로 분리되어 있습니다.
이렇게 하는 이유는 하나의 데이터 센터에 문제가 생겨도
같은 리전 안의 다른 가용 영역을 사용할 수 있게 하기 위해서입니다.
즉,
- 리전은 큰 지역 단위
- 가용 영역은 그 안의 독립된 센터 단위
라고 이해하면 훨씬 정리가 잘 됩니다.
EC2란?
AWS에서 가장 대표적으로 보는 서비스 중 하나가 바로 EC2입니다.
EC2는 쉽게 말하면 AWS에서 빌려 쓰는 가상 서버입니다.
우리가 Spring Boot 프로젝트를 배포할 때는
결국 어딘가에서 자바 애플리케이션을 실행해야 하는데,
그 실행 환경이 되는 컴퓨터 역할을 EC2가 해줍니다.
즉, EC2는 실제 물리 서버 그 자체라기보다
AWS 위에서 실행되는 가상 인스턴스라고 보는 것이 더 자연스럽습니다.
EC2를 만들 때는 보통 다음과 같은 것들을 정하게 됩니다.
- 어떤 OS를 사용할지(AMI)
- 어떤 성능의 인스턴스를 사용할지
- 어느 VPC와 서브넷에 둘지
- 어떤 보안 규칙을 적용할지
- 얼마나 큰 스토리지를 붙일지
즉, EC2를 만든다는 것은 단순히 서버 하나를 클릭해서 생성하는 것이 아니라
실행 환경, 네트워크, 보안까지 함께 구성하는 일이라고 느꼈습니다.
EC2 인스턴스 타입은 왜 중요할까?
EC2는 종류가 다양합니다.
어떤 인스턴스는 범용적으로 쓰기 좋고,
어떤 인스턴스는 메모리나 GPU에 특화되어 있습니다.
또 같은 계열 안에서도 nano, micro, small, large처럼
크기가 다르게 나뉘어 있습니다.
이 의미는 결국 프로젝트 규모에 맞게 서버 자원을 조절할 수 있다는 것입니다.
예를 들어 처음에는 작은 인스턴스로 시작하다가
트래픽이 많아지면 더 큰 인스턴스로 바꾸거나,
필요하다면 여러 대로 늘리는 방식도 가능합니다.
즉, EC2의 장점은 단순히 서버를 만들 수 있다는 것보다
상황에 맞게 유연하게 조절할 수 있다는 점에 있다고 느꼈습니다.
VPC란?
EC2가 “서버”라면,
VPC는 그 서버들이 들어갈 가상의 네트워크 공간입니다.
VPC는 Virtual Private Cloud의 약자로,
AWS 안에서 내가 사용하는 리소스들을 배치할 수 있는
나만의 가상 네트워크라고 볼 수 있습니다.
쉽게 말하면
- EC2는 그 안에 놓이는 컴퓨터
- VPC는 그 컴퓨터들이 연결되는 네트워크 공간
입니다.
즉, 서버만 만드는 것으로 끝나는 것이 아니라
그 서버가 어느 네트워크 안에 있을지까지 정해야
비로소 외부 요청을 받고, 다른 리소스와 통신할 수 있게 됩니다.
서브넷은 왜 나눌까?
VPC 안에는 여러 개의 서브넷을 만들 수 있습니다.
서브넷은 VPC를 더 작은 네트워크 단위로 나눈 것이라고 보면 됩니다.
이렇게 나누는 이유는 모든 서버를 똑같이 외부에 노출시키지 않고,
역할에 따라 위치를 구분하기 위해서입니다.
예를 들어
- 외부 요청을 직접 받아야 하는 서버
- 내부에서만 동작해야 하는 DB 서버
를 같은 위치에 두는 것은 위험할 수 있습니다.
그래서 네트워크를 더 잘게 나눠 각 리소스의 역할에 따라 접근 범위를 다르게 설정하게 됩니다.
즉, 서브넷은 단순한 분할이 아니라
보안과 구조를 더 명확하게 만들기 위한 네트워크 분리 방식입니다.
Public 서브넷과 Private 서브넷은 어떻게 다를까?
서브넷은 크게 Public 서브넷과 Private 서브넷으로 나눌 수 있습니다.
Public 서브넷
Public 서브넷은 인터넷 게이트웨이와 라우팅 테이블이 연결되어 있어서
외부 인터넷과 직접 통신할 수 있는 서브넷입니다.
즉, 외부 사용자의 요청을 받아야 하는 EC2라면
보통 Public 서브넷에 두게 됩니다.
Private 서브넷
Private 서브넷은 외부 인터넷과 직접 연결되지 않는 서브넷입니다.
즉, 외부 사용자가 바로 접근하면 안 되는 리소스,
예를 들어 DB 서버 같은 것들을 Private 서브넷에 두는 경우가 많습니다.
정리하면,
- Public 서브넷은 외부와 연결되는 공간
- Private 서브넷은 내부에서만 사용하는 공간
이라고 볼 수 있습니다.
Private 서브넷은 왜 필요할까?
처음에는 저도 “인터넷이 안 되면 불편한 거 아닌가?”라는 생각이 들었습니다.
그런데 오히려 Private 서브넷의 핵심은
외부에서 직접 접근할 수 없게 만드는 것 자체가 장점이라는 점이었습니다.
Private 서브넷의 장점은 크게 두 가지로 볼 수 있습니다.
1. 보안성이 높다
외부에서 직접 접근할 수 없기 때문에
불필요한 공격 표면을 줄일 수 있습니다.
즉, 모든 서버를 다 인터넷에 노출시키는 것보다
정말 외부와 연결돼야 하는 서버만 Public에 두고,
나머지는 Private에 두는 것이 훨씬 안전합니다.
2. 내부 자원을 더 효율적으로 분리할 수 있다
DB처럼 외부 사용자가 직접 접근할 필요가 없는 리소스는
Public에 둘 이유가 없습니다.
오히려 애플리케이션 서버만 Public에 두고
DB는 Private에 두는 구조가 더 일반적입니다.
즉, Private 서브넷은 “인터넷이 안 되는 불편한 공간”이 아니라
인터넷에 노출시키지 않기 위해 일부러 분리한 공간이라고 이해하는 것이 더 맞았습니다.
그럼 Private 서브넷의 서버는 어떻게 접속할까?
이 부분이 처음에는 가장 헷갈렸습니다.
외부에서 직접 접근할 수 없다면
Private 서브넷 안의 서버는 어떻게 관리할 수 있을까요?
대표적으로 많이 사용하는 방식이 Bastion Host입니다.
Bastion Host는 Public 서브넷에 있는 서버를 거쳐서
Private 서브넷 내부 서버에 접속하는 방식입니다.
즉,
- 먼저 Public 서브넷의 EC2에 접속하고
- 그 서버를 통해 Private 서브넷 내부로 들어가는 구조
입니다.
이렇게 하면 Private 서브넷 내부의 서버는 여전히 외부에 직접 노출되지 않으면서도
필요할 때 관리자는 접근할 수 있게 됩니다.
즉, Bastion Host는 보안을 유지하면서도 내부 서버를 관리하기 위한 중간 접점이라고 볼 수 있습니다.
보안 그룹은 어떤 역할을 할까?
AWS에서 네트워크를 구성할 때 빠질 수 없는 것이 보안 그룹(Security Group) 입니다.
보안 그룹은 쉽게 말해 EC2에 적용하는 가상 방화벽입니다.
즉, 어떤 포트를 열어둘지, 어떤 요청은 허용하고 어떤 요청은 막을지를 정하는 규칙입니다.
예를 들어 Spring Boot 프로젝트를 직접 실행한다면 보통 이런 인바운드 규칙을 생각할 수 있습니다.
- SSH(22): 서버에 터미널로 접속하기 위해
- MySQL(3306): 외부에서 DB에 접속해야 하는 경우
- TCP 8080: Spring Boot 애플리케이션 요청을 받기 위해
즉, 보안 그룹은 단순한 옵션이 아니라 이 서버가 어떤 요청을 받을 수 있을지 결정하는 핵심 설정입니다.
CI/CD 파이프라인이란?
배포를 수동으로 하다 보면
코드를 수정할 때마다 직접 서버에 들어가서 파일을 복사하고,
기존 프로세스를 끄고, 다시 실행하는 일을 반복하게 됩니다.
처음에는 할 수 있어도 프로젝트가 커질수록 이런 방식은 꽤 번거롭고 실수하기도 쉽습니다.
그래서 등장하는 것이 CI/CD 파이프라인입니다.
CI(Continuous Integration)
CI는 개발자가 작업한 코드를 자주 통합하고,
그 코드가 정상적으로 빌드되는지, 테스트는 통과하는지 확인하는 과정입니다.
예를 들어 PR을 만들거나 main 브랜치에 머지할 때
자동으로 빌드가 실행되는 것이 CI 흐름에 가깝습니다.
CD(Continuous Delivery / Deployment)
CD는 빌드된 결과물을 실제 실행 환경에 배포하는 과정입니다.
즉, 빌드까지 끝난 애플리케이션을 실제 서버에 전달하고 실행되게 만드는 단계입니다.
정리하면,
- CI는 코드가 잘 합쳐지고 잘 빌드되는지 확인하는 과정
- CD는 그 결과물을 실제 서버에 배포하는 과정
이라고 볼 수 있습니다.
GitHub Actions는 무엇일까?
GitHub Actions는 GitHub에서 제공하는 CI/CD 자동화 도구입니다.
처음에는 저도 그냥 “GitHub에 있는 기능” 정도로만 생각했는데, 정리해보니 꽤 구조적으로 동작합니다.
주요 개념은 다음처럼 볼 수 있습니다.
- Event: push, pull request 같은 깃허브에서 발생한 이벤트
- Workflow: 이벤트가 발생했을 때 실행할 전체 작업 흐름
- Job: 워크플로우 안에서 수행되는 작업 단위
- Step: 각 Job 안에서 실행되는 세부 명령
즉, GitHub Actions는
“특정 이벤트가 발생했을 때, 정해둔 순서대로 자동 작업을 실행해주는 도구”라고 이해하면 편했습니다.
GitHub Actions Secrets는 왜 필요할까?
배포 자동화를 하다 보면
SSH 키, 서버 주소, 환경 변수 같은 민감한 정보를 워크플로우에서 사용해야 할 때가 많습니다.
그런데 이런 값을 워크플로우 파일에 그대로 적어두면
저장소에 노출될 위험이 있습니다.
그래서 사용하는 것이 GitHub Actions Secrets입니다.
Secrets는 깃허브 저장소 안에서
민감한 값을 안전하게 저장해두고,
워크플로우 실행 시점에만 꺼내 쓸 수 있게 해주는 기능입니다.
예를 들어 이런 값들을 Secrets로 넣을 수 있습니다.
- EC2_HOST
- EC2_USERNAME
- EC2_SSH_KEY
- ENV
즉, Secrets는
배포 자동화 과정에서 민감한 정보를 코드에 직접 노출하지 않기 위한 필수 장치라고 할 수 있습니다.
배포 흐름은 어떻게 구성할 수 있을까?
전체 배포 과정은 크게 이렇게 정리할 수 있습니다.
1. VPC 생성
먼저 네트워크 공간을 만들고,
퍼블릭/프라이빗 서브넷, 라우팅 테이블, 인터넷 게이트웨이 등을 구성합니다.
2. 보안 그룹 생성
SSH, 8080 포트 같은 필요한 인바운드 규칙을 설정합니다.
3. EC2 생성
VPC와 Public 서브넷을 선택하고, 키 페어와 보안 그룹을 연결해서 EC2를 생성합니다.
4. EC2 초기 설정
SSH로 접속한 뒤 Java, MySQL 등을 설치하고,
필요하다면 스왑 메모리도 설정합니다.
5. GitHub Secrets 설정
서버 접속 정보와 환경 변수를 GitHub Secrets에 등록합니다.
6. GitHub Actions 워크플로우 작성
main 브랜치에 push되면 자동으로 빌드하고,
빌드 결과물을 EC2에 전송한 뒤 서버에서 실행되도록 구성합니다.
즉, 배포는 단순히 jar 파일만 복사하는 작업이 아니라
네트워크, 보안, 서버 환경, 자동화까지 이어지는 전체 흐름이라고 볼 수 있습니다.
EC2에서는 무엇을 설치해야 할까?
Spring Boot 프로젝트를 실행하려면
당연히 자바 런타임이 필요합니다.
예를 들어 Java 21을 사용한 프로젝트라면
EC2에도 같은 버전의 JDK를 설치해야 합니다.
그리고 MySQL을 EC2 내부에서 함께 운영한다면
MySQL도 설치해야 합니다.
또 프리티어처럼 메모리가 작은 인스턴스를 쓴다면
스왑 메모리를 설정해서 메모리 부족 상황을 조금 보완할 수도 있습니다.
즉, EC2를 만든다고 끝나는 것이 아니라
내 프로젝트가 실제로 실행될 수 있는 환경으로 세팅하는 단계가 추가로 필요합니다.
GitHub Actions 워크플로우는 어떤 식으로 작성할까?
워크북에서는 .github/workflows 아래에 배포 워크플로우를 두고,
main 브랜치에 push가 발생했을 때 자동으로 빌드와 배포가 이뤄지도록 구성했습니다.
예시 흐름은 대략 이런 식입니다.
name: CI/CD Pipeline (.jar)
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: main 체크아웃
uses: actions/checkout@v4
- name: 자바 설정
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: gradlew 실행 권한 부여
run: chmod +x gradlew
- name: gradle 세팅
uses: gradle/actions/setup-gradle@v4
- name: gradle 빌드
run: ./gradlew build -x test
- name: 빌드된 아티팩트 업로드
uses: actions/upload-artifact@v4
with:
name: BackEnd
path: build/libs/*.jar
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: 아티팩트 다운로드
uses: actions/download-artifact@v4
with:
name: BackEnd
path: build/libs/
이 흐름을 보면 먼저 build Job에서 프로젝트를 빌드하고,
그 결과물을 아티팩트로 저장한 뒤,
deploy Job에서 다시 다운로드해서 서버에 전송하게 됩니다.
즉, 워크플로우도 그냥 한 번에 다 처리하는 것이 아니라
빌드 단계와 배포 단계를 분리해서 더 명확하게 구성할 수 있다는 점이 인상적이었습니다.
배포 단계에서는 실제로 무엇을 할까?
deploy 단계에서는 보통 다음 작업이 필요합니다.
- SSH 키 파일 생성
- jar 파일 찾기
- scp로 EC2에 jar 업로드
- 기존 자바 프로세스 종료
- 새 jar 실행
- 민감한 파일 삭제
예를 들어 워크북에서는 이런 흐름으로 구성했습니다.
- name: EC2 배포
env:
EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}
EC2_USERNAME: ${{ secrets.EC2_USERNAME }}
EC2_HOST: ${{ secrets.EC2_HOST }}
ENV: ${{ secrets.ENV }}
run: |
echo "$EC2_SSH_KEY" > private_key.pem
chmod 600 private_key.pem
jar_file=$(find build/libs -name '*.jar' ! -name '*plain.jar' | head -n 1)
scp -i private_key.pem -o StrictHostKeyChecking=no "$jar_file" $EC2_USERNAME@$EC2_HOST:/home/$EC2_USERNAME/BackEnd.jar
echo "$ENV" | ssh -i private_key.pem -o StrictHostKeyChecking=no $EC2_USERNAME@$EC2_HOST "
cat > .env
pgrep java | xargs -r kill -15
sleep 10
nohup java -jar /home/$EC2_USERNAME/BackEnd.jar > app.log 2>&1 &
"
rm -f private_key.pem
이 흐름을 보면서 느낀 점은
배포 자동화란 결국 “사람이 하던 작업을 순서대로 스크립트로 옮기는 것”에 가깝다는 점이었습니다.
즉, 수동으로 서버에 접속해서 하던 일을
GitHub Actions가 대신 수행하도록 만든 것이 CI/CD 파이프라인이라고 볼 수 있습니다.
.env 파일은 왜 사용할까?
배포할 때 환경 변수 처리는 꽤 중요합니다.
예를 들어 DB 비밀번호, JWT 시크릿, OAuth 키 같은 값들은
소스 코드에 직접 넣어두면 위험할 수 있습니다.
그래서 .env 파일을 이용해 민감한 값을 서버에 전달하고,
Spring Boot가 이를 읽어오도록 구성했습니다.
예를 들면 application.yml에 아래처럼 import 설정을 둘 수 있습니다.
spring:
config:
import: optional:file:.env[.properties]
이렇게 해두면 .env 파일에 들어 있는 값을
애플리케이션 실행 시 불러올 수 있습니다.
즉, 환경 변수 분리는 단순히 깔끔함의 문제가 아니라
민감한 정보를 코드와 분리해서 더 안전하게 관리하기 위한 방식이라고 느꼈습니다.
배포를 해보면서 느낀 점
이번 내용을 정리하면서 느낀 것은
배포는 단순히 “AWS에 올렸다”로 끝나는 개념이 아니라는 점이었습니다.
그 안에는
- 네트워크를 어떻게 나눌지
- 어떤 서버를 어느 서브넷에 둘지
- 어떤 포트를 열어야 할지
- 환경 변수는 어떻게 숨길지
- 배포를 어떻게 자동화할지
같은 선택들이 전부 들어 있었습니다.
특히 VPC, EC2, Public/Private 서브넷 개념을 같이 보면서
서버를 만든다는 것이 단순히 컴퓨터 한 대를 띄우는 것이 아니라
그 서버가 어떤 네트워크 구조 안에서 어떤 역할을 할지 정하는 일이라는 점이 더 크게 와닿았습니다.
또 GitHub Actions를 이용해 CI/CD를 구성해보면서는
코드를 올렸을 때 자동으로 빌드되고 배포되는 흐름이
생각보다 강력한 개발 생산성 도구라는 점도 느낄 수 있었습니다.
마무리
처음에는 저도 배포를
“프로젝트를 AWS에 올리는 것” 정도로만 생각했습니다.
그런데 정리해보니 배포는
단순히 jar 파일을 서버에 복사하는 작업이 아니라
- 클라우드 환경을 이해하고
- 네트워크를 구성하고
- 서버를 준비하고
- 보안을 설정하고
- 자동화된 배포 파이프라인까지 연결하는
꽤 넓은 영역의 작업이었습니다.
특히 이번 내용을 공부하면서
VPC와 EC2가 각각 어떤 역할을 하는지,
왜 Public 서브넷과 Private 서브넷을 나누는지,
GitHub Actions와 Secrets를 왜 함께 써야 하는지가 조금 더 분명해졌습니다.
앞으로 배포를 할 때는 단순히 “실행만 되면 된다”가 아니라
이 서버는 어디에 놓여야 할까,
어떤 요청만 허용해야 할까,
환경 변수는 어떻게 안전하게 관리할까,
배포는 어떻게 반복 가능하게 자동화할까를 함께 고민해야겠다고 느꼈습니다.