RDS 데이터베이스
"SQLite를 진짜 데이터베이스로 교체하자"
학습 목표
- RDS가 무엇이고 왜 필요한지 설명할 수 있다
- RDS용 보안 그룹을 생성하고 EC2 보안 그룹을 소스로 참조할 수 있다
- DB 서브넷 그룹을 생성하여 프라이빗 서브넷에 RDS를 배치할 수 있다
- RDS MySQL 인스턴스를 생성하고 EC2에서 접속할 수 있다
- ShopEasy API 서버의 데이터베이스를 SQLite에서 RDS MySQL로 전환할 수 있다
1. 왜 RDS가 필요한가?
지금까지 ShopEasy API 서버는 SQLite를 사용하고 있습니다. SQLite는 파일 하나에 모든 데이터를 저장하는 가벼운 데이터베이스입니다. 로컬 개발에는 편리하지만, 실제 서비스 운영에는 심각한 문제가 있습니다.
SQLite vs RDS MySQL 비교
| 항목 | SQLite (현재) | RDS MySQL (목표) |
|---|---|---|
| 데이터 저장 위치 | EC2 서버 안의 파일 1개 | 별도의 전용 DB 서버 |
| 서버가 죽으면? | 데이터도 같이 사라짐 | DB는 별도 서버라 안전 |
| 동시 접속 | 1명만 쓰기 가능 | 수천 명 동시 처리 |
| 백업 | 내가 직접 해야 함 | AWS가 자동 백업 |
| 보안 패치 | 내가 직접 해야 함 | AWS가 자동 패치 |
| 장애 복구 | 내가 직접 해야 함 | Multi-AZ로 자동 복구 |
돈(데이터)을 보관하는 방법을 생각해 봅시다.
- SQLite = 집 서랍에 현금을 넣어두는 것. 간편하지만, 도둑이 들거나 불이 나면 돈을 잃습니다.
- RDS = 전문 금고 회사(은행)에 맡기는 것. 24시간 감시, 자동 백업, 재해 복구까지 전문가가 해줍니다.
RDS = AWS가 관리해주는 데이터베이스 서버입니다. 백업, 보안 패치, 장애 복구를 AWS가 자동으로 해주므로 우리는 데이터베이스 관리가 아닌 서비스 개발에 집중할 수 있습니다.
- RDS (Relational Database Service) - AWS가 관리하는 관계형 데이터베이스. MySQL, PostgreSQL, MariaDB, Oracle, SQL Server 등 지원
- DB 서브넷 그룹 - RDS가 사용할 서브넷 목록. 최소 2개 이상의 가용 영역(AZ)에 서브넷이 필요 (Multi-AZ 대비)
- 프라이빗 서브넷에 배치 - DB는 외부에서 직접 접속하지 못하게 프라이빗 서브넷에 둠 (보안)
- 보안 그룹 참조 - RDS 보안 그룹의 소스를 IP가 아닌 EC2 보안 그룹으로 지정하면, 해당 보안 그룹에 속한 EC2만 접근 가능
2. ShopEasy에서 RDS의 역할
ShopEasy 이커머스 앱에서 RDS MySQL은 회원 정보, 상품 목록, 주문 내역 등 핵심 비즈니스 데이터를 저장합니다. API 서버(EC2)가 퍼블릭 서브넷에서 사용자 요청을 받고, 프라이빗 서브넷에 있는 RDS에 데이터를 읽고 씁니다.
포트 5000
SG: ShopEasy-EC2-SG
DB: ecommerce
SG: ShopEasy-RDS-SG
데이터베이스에는 회원의 개인정보, 비밀번호, 주문 내역 등 민감한 데이터가 저장됩니다. 만약 DB를 퍼블릭 서브넷에 두고 퍼블릭 접근을 허용하면, 인터넷 어디서든 DB에 접근을 시도할 수 있습니다. 프라이빗 서브넷에 배치하면 인터넷에서 직접 접근하는 것이 원천적으로 불가능합니다. 오직 같은 VPC 안에 있는 EC2 서버만 DB에 접속할 수 있습니다.
일반적으로 보안 그룹의 소스에 IP 주소를 입력합니다 (예: 10.0.1.50/32).
하지만 EC2의 IP는 재시작하면 바뀔 수 있습니다.
대신 EC2 보안 그룹의 ID (예: sg-0abc1234)를 소스로 지정하면,
해당 보안 그룹이 적용된 모든 EC2 인스턴스가 자동으로 접근을 허용받습니다.
IP가 바뀌어도, EC2가 추가되어도 보안 그룹만 같으면 자동으로 적용됩니다.
3. 실습: RDS MySQL 구축 및 연동
- Chapter 01에서 생성한 VPC, 프라이빗 서브넷 2개 (서로 다른 AZ)
- Chapter 02에서 생성한 EC2 인스턴스와 EC2 보안 그룹 (
ShopEasy-EC2-SG) - EC2에 SSH 접속이 가능한 상태
RDS에 적용할 보안 그룹을 만듭니다. EC2 보안 그룹에서만 MySQL 포트(3306)에 접근할 수 있도록 설정합니다.
다음 설정으로 보안 그룹을 생성하세요.
| 항목 | 값 |
|---|---|
| 보안 그룹 이름 | ShopEasy-RDS-SG |
| 설명 | ShopEasy RDS MySQL Security Group |
| VPC | ShopEasy-VPC |
인바운드 규칙을 추가하세요.
| 유형 | 포트 | 소스 | 설명 |
|---|---|---|---|
| MySQL/Aurora | 3306 | ShopEasy-EC2-SG (보안 그룹 선택) | EC2에서 RDS 접근 허용 |
소스를 지정할 때 IP가 아닌 "사용자 지정"을 선택하고,
ShopEasy-EC2-SG의 보안 그룹 ID (sg-xxxx)를 입력합니다.
보안 그룹 ID를 입력하기 시작하면 자동완성 목록이 나타납니다.
RDS가 사용할 서브넷 목록을 DB 서브넷 그룹으로 묶어야 합니다. 서로 다른 가용 영역(AZ)에 있는 프라이빗 서브넷 2개를 선택합니다.
다음 설정으로 DB 서브넷 그룹을 생성하세요.
| 항목 | 값 |
|---|---|
| 이름 | shopeasy-db-subnet-group |
| 설명 | ShopEasy RDS DB Subnet Group |
| VPC | ShopEasy-VPC |
| 가용 영역 | ap-northeast-2a, ap-northeast-2c |
| 서브넷 | 프라이빗 서브넷 2개 (10.0.100.0/24, 10.0.101.0/24) |
RDS 콘솔 → 서브넷 그룹 → DB 서브넷 그룹 생성에서 만듭니다. VPC를 선택하면 해당 VPC의 서브넷 목록이 나타납니다. 퍼블릭 서브넷이 아닌 프라이빗 서브넷을 선택해야 합니다. 서브넷의 CIDR 블록이나 이름 태그를 확인하세요.
AWS RDS는 DB 서브넷 그룹에 최소 2개 이상의 가용 영역(AZ)을 요구합니다. 이는 나중에 Multi-AZ 배포(자동 장애 복구)를 활성화할 때를 대비한 것입니다. 하나의 AZ에 장애가 발생해도 다른 AZ에 있는 대기 DB로 자동 전환할 수 있습니다. 지금은 비용 절약을 위해 Single-AZ로 생성하지만, DB 서브넷 그룹은 2개 AZ가 필수입니다.
이제 실제 RDS MySQL 데이터베이스 인스턴스를 생성합니다. 비용 절약을 위해 프리 티어 옵션을 사용합니다.
다음 설정으로 RDS 인스턴스를 생성하세요.
엔진 옵션
| 항목 | 값 |
|---|---|
| 엔진 유형 | MySQL |
| 엔진 버전 | MySQL 8.0.x (기본값) |
| 템플릿 | 프리 티어 |
설정
| 항목 | 값 |
|---|---|
| DB 인스턴스 식별자 | shopeasy-db |
| 마스터 사용자 이름 | admin |
| 마스터 암호 | 본인이 설정 (8자 이상, 기억할 것!) |
인스턴스 구성
| 항목 | 값 |
|---|---|
| DB 인스턴스 클래스 | db.t3.micro |
스토리지
| 항목 | 값 |
|---|---|
| 스토리지 유형 | gp2 (범용 SSD) |
| 할당된 스토리지 | 20 GiB |
| 스토리지 자동 조정 | 비활성화 |
연결
| 항목 | 값 |
|---|---|
| VPC | ShopEasy-VPC |
| DB 서브넷 그룹 | shopeasy-db-subnet-group |
| 퍼블릭 액세스 | 아니요 |
| VPC 보안 그룹 | ShopEasy-RDS-SG (기존 항목 선택) |
| 가용 영역 | 기본 설정 유지 |
추가 구성
| 항목 | 값 |
|---|---|
| 초기 데이터베이스 이름 | ecommerce |
| 자동 백업 | 비활성화 (실습용, 비용 절약) |
| 암호화 | 기본값 유지 |
| 모니터링 | 기본값 유지 |
| 삭제 방지 | 비활성화 (실습 후 삭제 편의) |
RDS 인스턴스가 "사용 가능(Available)" 상태가 되면 엔드포인트를 메모합니다. 이 엔드포인트가 DB 접속 주소입니다.
예시: shopeasy-db.cxxxxxxxxx.ap-northeast-2.rds.amazonaws.com
RDS 콘솔 → 데이터베이스 → shopeasy-db를 클릭하면 "연결 & 보안" 탭에서 엔드포인트를 확인할 수 있습니다. 엔드포인트는 RDS 인스턴스의 DNS 주소입니다.
RDS 인스턴스 생성에는 보통 5~15분이 소요됩니다. "생성 중(Creating)" 상태에서 "사용 가능(Available)"이 될 때까지 기다려야 합니다. 이 시간 동안 다음 단계의 내용을 미리 읽어두세요.
- Multi-AZ 활성화 - 자동 장애 복구 (가용성)
- 자동 백업 활성화 - 최대 35일 자동 백업 (데이터 보호)
- 암호화 활성화 - 저장 데이터 암호화 (보안)
- 삭제 방지 활성화 - 실수로 DB 삭제 방지
- 비밀번호는 AWS Secrets Manager에 저장
EC2 인스턴스에 SSH로 접속한 뒤, MySQL 클라이언트를 설치하고 RDS에 접속해 봅니다.
로컬 PC에서 EC2에 SSH로 접속합니다.
ssh -i your-key.pem ec2-user@EC2_PUBLIC_IP
EC2에 MySQL 클라이언트(MariaDB 클라이언트)를 설치합니다.
sudo dnf install mariadb105 -y
Amazon Linux 2023에서는 mariadb105 패키지에 MySQL 호환 클라이언트가 포함되어 있습니다.
mysql 명령어로 MySQL 서버에 접속할 수 있습니다.
MySQL 클라이언트로 RDS에 접속합니다. 엔드포인트를 입력하세요.
mysql -h RDS_ENDPOINT -u admin -p
비밀번호를 입력하면 MySQL 프롬프트가 나타납니다.
접속에 성공하면 데이터베이스 목록을 확인합니다.
SHOW DATABASES;
USE ecommerce;
SHOW TABLES;
exit;
ecommerce 데이터베이스가 보이면 성공입니다.
아직 테이블은 없는 것이 정상입니다 (seed.js에서 생성 예정).
접속이 안 되면 다음을 확인하세요:
- RDS 상태가 "사용 가능(Available)"인지 확인
- RDS 보안 그룹 인바운드에 EC2 보안 그룹이 소스로 설정되어 있는지 확인
- EC2와 RDS가 같은 VPC에 있는지 확인
- 엔드포인트 주소가 올바른지 확인 (오타 주의)
ShopEasy API 서버가 SQLite 대신 RDS MySQL을 사용하도록 .env 파일을 수정합니다.
EC2에서 ShopEasy API 서버 디렉터리로 이동하여 .env 파일을 수정합니다.
cd ~/ecommerce-app/api-server
nano .env
.env 파일에서 데이터베이스 관련 항목을 다음과 같이 변경합니다.
# 데이터베이스 설정 - SQLite에서 MySQL로 변경
DB_TYPE=mysql
DB_HOST=여기에_RDS_엔드포인트_입력
DB_PORT=3306
DB_USER=admin
DB_PASSWORD=여기에_설정한_비밀번호_입력
DB_NAME=ecommerce
DB_TYPE을 sqlite에서 mysql로 변경하고,
DB_HOST에 RDS 엔드포인트를 입력하는 것이 핵심입니다.
nano 편집기 사용법:
- 화살표 키로 이동
- 텍스트를 직접 입력/삭제
Ctrl + O→ Enter → 저장Ctrl + X→ 편집기 종료
또는 vi를 사용하려면: vi .env → i로 입력 모드 →
수정 후 ESC → :wq → Enter
.env를 변경했으면 API 서버를 재시작하고,
node seed.js로 기본 데이터(테스트 사용자 + 상품 20개)를 입력합니다.
현재 실행 중인 API 서버를 종료합니다.
# pm2를 사용 중인 경우
pm2 stop all
# 또는 프로세스를 직접 종료하는 경우
# Ctrl + C 로 종료
Node.js에서 MySQL에 접속하기 위한 드라이버를 설치합니다.
cd ~/ecommerce-app/api-server
npm install mysql2
seed.js를 실행하여 테이블을 생성하고 기본 데이터를 넣습니다.
node seed.js
성공하면 "시드 데이터 생성 완료!" 메시지와 함께 테스트 계정(test@test.com / password123)과 상품 20개가 생성됩니다.
만약 ER_ACCESS_DENIED_ERROR 오류가 나면 .env의 DB_PASSWORD를 확인하세요.
ECONNREFUSED 오류가 나면 DB_HOST가 올바른지, 보안 그룹 설정이 맞는지 확인하세요.
서버를 다시 시작합니다.
# pm2를 사용하는 경우
pm2 start server.js --name shopeasy-api
pm2 logs shopeasy-api
# 또는 직접 실행
node server.js
"Server running on port 5000" 메시지가 나오고, DB 연결 관련 오류가 없으면 성공입니다.
RDS MySQL로 전환이 완료되었는지 curl 명령어로 API를 테스트합니다. 로컬 PC에서 새 터미널을 열고 테스트합니다.
새로운 사용자를 등록합니다.
curl -X POST http://EC2_PUBLIC_IP:5000/api/auth/signup \
-H "Content-Type: application/json" \
-d '{"username":"testuser","email":"test@test.com","password":"test1234"}'
등록한 사용자로 로그인합니다.
curl -X POST http://EC2_PUBLIC_IP:5000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"test1234"}'
시드 데이터로 넣은 상품 목록을 조회합니다.
curl http://EC2_PUBLIC_IP:5000/api/products
상품 20개의 JSON 목록이 출력되면 RDS MySQL 연동 성공입니다!
EC2_PUBLIC_IP는 EC2 인스턴스의 퍼블릭 IP 주소로 교체하세요.
예시: curl http://3.34.xx.xx:5000/api/products
응답이 안 오면 EC2 보안 그룹에서 포트 5000이 열려있는지 확인하세요.
4. 확인 사항
아래 항목을 모두 확인했는지 체크하세요.
- RDS 보안 그룹(
ShopEasy-RDS-SG)이 생성되었고, 인바운드에 EC2 보안 그룹이 소스로 지정되어 있다 - DB 서브넷 그룹(
shopeasy-db-subnet-group)이 프라이빗 서브넷 2개로 생성되었다 - RDS 인스턴스(
shopeasy-db)가 "사용 가능(Available)" 상태이다 - RDS의 퍼블릭 액세스가 "아니요"로 설정되어 있다
- EC2에서
mysql명령으로 RDS에 접속할 수 있다 - API 서버
.env에서DB_TYPE=mysql로 변경했다 node seed.js로 기본 데이터가 정상 입력되었다- curl로 회원가입, 로그인, 상품 조회 API가 정상 동작한다
- RDS = AWS 관리형 데이터베이스 (백업, 패치, 복구 자동)
- 프라이빗 서브넷 배치 = 외부에서 DB 직접 접근 차단 (보안)
- DB 서브넷 그룹 = RDS가 사용할 서브넷 목록 (Multi-AZ 대비)
- 보안 그룹 참조 = IP 대신 보안 그룹 ID로 접근 제어 (유연함)
- .env 수정 = DB_TYPE을 변경하면 API 서버가 다른 DB를 사용