13장. SSM 터널로 RDS 초기화
12장 끝에서 문제가 하나 생겼습니다. 데이터베이스를 프라이빗 서브넷에 잘 숨겨 놨더니, 보안은 완벽한데 정작 우리도 못 들어갑니다. 테이블도 만들어야 하고 초기 데이터도 넣어야 하는데 길이 없습니다. 이번 장에서 이 문제를 "터널"로 해결합니다. 밖에서는 닫혀 있는 RDS에 내 노트북에서 안전하게 접속하는 통로를 열고, Claude Code로 테이블과 초기 데이터를 넣습니다. 여기서 쓰는 방법(SSM)은 실무에서도 운영 데이터베이스에 접속할 때 매일 쓰는 방식이라, 한 번 익혀 두면 두고두고 씁니다.
[지금 여기]
우리는 22단계를 거쳐 "라이브 투표 서비스"를 만들고, 컨테이너에 담아, AWS 클라우드에 배포하는 중입니다. 아래가 그 전체 여정 지도입니다. 지금은 13단계에 있습니다.
- 1장개발 환경 준비 (터미널 / Node.js / Git / GitHub / VS Code / Claude Code)
- 2장Claude Code 사용법 익히기
- 3장무엇을 만들지 기획 (라이브 투표 서비스)
- 4장로컬에서 서비스 만들기 (React + Express + PostgreSQL)
- 5장Docker 개념
- 6장Dockerfile 작성
- 7장컨테이너 빌드·실행
- 8장AWS 시작 (계정 / IAM / 리전 / 비용)
- 9장이미지 태깅
- 10장ECR에 이미지 올리기
- 11장VPC 네트워크 구성
- 12장RDS 데이터베이스 생성
- 13장SSM 터널로 RDS 초기화
- 14장ECS Fargate + ALB로 배포 (기본 완성)
- 15장CI/CD (GitHub Actions)
- 16장IaC (Terraform)
- 17장배포 자동화 연결
- 18장현업 운영 구조 이해
- 19장무중단 배포
- 20장신규 버전 배포 & 롤백
- 21장(심화) 프론트를 Vercel로 분리 배포
- 22장마무리 (회고 + 리소스 삭제)
- 방금 끝낸 것: 12장에서 프라이빗 서브넷에 RDS PostgreSQL 데이터베이스
(
vote-db, 데이터베이스 이름voteapp)를 만들었습니다. 보안은 완벽하지만 아직 텅 빈 상태이고, 밖에서 접속할 길이 없습니다. - 이번 장에서 하는 것: 그 프라이빗 RDS에 내 노트북에서 안전하게 접속하는 SSM 터널을 열고, Claude Code로 테이블(스키마)과 초기 데이터(시드)를 넣어 데이터베이스를 서비스에 쓸 수 있는 상태로 만듭니다.
이번 장에서 완성되는 것
한 문장으로: 프라이빗 서브넷 깊숙이 숨어 있는 RDS에, 포트를 하나도 열지 않고 내 노트북에서 접속해, 투표 서비스용 테이블과 초기 데이터를 심어 둡니다.
이렇게 되면 성공입니다: 내 노트북 터미널에서 터널을 연 뒤 Claude Code에게
확인을 시켰을 때, voteapp 데이터베이스에 polls 테이블 1건과 options
테이블 3건("점심 뭐 먹지?" 설문과 선택지 3개)이 실제로 조회됩니다.
사전 조건
이 장은 1~12장을 마친 상태에서 시작합니다. 아래가 갖춰져 있어야 합니다.
- 11장까지의 네트워크:
vote-vpc(VPC), 그 안의 퍼블릭/프라이빗 서브넷, 프라이빗 서브넷이 밖으로 나갈 수 있게 해 주는 NAT 게이트웨이. - 12장의 RDS: 프라이빗 서브넷에 만든
vote-db(데이터베이스 이름voteapp). 12장에서 RDS 엔드포인트 주소와 마스터 사용자 이름 (postgres)·비밀번호를 메모해 두었을 겁니다. 이번 장에서 씁니다. 기억이 안 나면, AWS 콘솔 상단 검색창에RDS→ 왼쪽 데이터베이스 →vote-db클릭 → 연결 & 보안 탭에서 엔드포인트를 다시 확인할 수 있습니다. - RDS 보안 그룹: 12장에서 만든
vote-db-sg. 이번 장에서 이 보안 그룹에 규칙 하나를 추가합니다. - AWS CLI가 설정된 상태: 앞 장(ECR에 이미지를 올릴 때)에서 이미 내
노트북의 VS Code 터미널에 AWS CLI를 설치하고
aws configure로 로그인 정보를 넣어 두었습니다. 이 장의 터미널 명령들은 그 설정을 그대로 씁니다. 리전은 이 책 내내 서울(ap-northeast-2)로 통일합니다. vote-app폴더와 Claude Code: 1부에서 만든 프로젝트 폴더vote-app. 마지막에 Claude Code로 데이터베이스 작업을 할 때 씁니다.
이 장의 명령은 모두 VS Code 통합 터미널에서
1부에서 정한 대로, 이 장의 터미널 명령도 전부 VS Code 안의 터미널
(터미널 → 새 터미널)에서 실행합니다. vote-app 폴더를 VS Code로 열어
둔 상태에서 진행하세요. AWS 콘솔 작업은 웹브라우저에서 합니다.
콘솔 화면은 조금씩 다를 수 있습니다 AWS 콘솔은 화면 구성과 버튼 이름을 수시로 바꿉니다. 이 책의 메뉴 이름이 실제와 조금 다를 수 있는데, 당황하지 말고 비슷한 뜻의 항목을 찾으면 됩니다. 값(이름, 포트 번호 등)은 그대로 쓰세요.
13.1 왜 바로 접속이 안 되나 — 그림으로 이해
먼저 상황을 그림으로 봅시다.
RDS는 프라이빗 서브넷에 있고, 퍼블릭 액세스도 "아니요"로 설정했습니다. 11장에서 배운 대로 프라이빗 서브넷은 밖에서 먼저 들어올 수 없는 구역입니다. 인터넷 어디에서 접속을 시도해도 길 자체가 없습니다. 보안 측면에서는 정확히 우리가 원한 상태입니다.
그럼 어떻게 들어갈까요? 발상은 이렇습니다. "밖에서 직접은 못 들어간다. 하지만 VPC 안에 있는 누군가는 RDS에 닿을 수 있다. 그렇다면 안에 중계자를 한 명 세워 두고, 그를 통해 들어가자."
이 중계자 역할을 하는 작은 서버를 VPC 안에 두고, 내 노트북과 중계자 사이에 안전한 통로를 뚫는 것. 이것이 터널링입니다.
터널링 / 포트 포워딩(Tunneling / Port Forwarding) 내 컴퓨터의 특정 포트를, 중계자를 거쳐 목적지(RDS)의 포트와 이어 주는 기술입니다. 터널이 열리면 "내 컴퓨터의 5432 포트에 접속하는 것"이 곧 "RDS의 5432 포트에 접속하는 것"이 됩니다. 마치 내 컴퓨터 안에 데이터베이스가 있는 것처럼 쓸 수 있게 되는 거죠. (5432는 PostgreSQL이 쓰는 표준 포트 번호입니다.)
[활용 사례] 터널링은 이럴 때 씁니다
터널링(포트 포워딩)은 우리 프로젝트에만 쓰는 특별한 기술이 아니라, 개발 현업에서 흔한 도구입니다.
- 운영 데이터베이스 점검: 회사의 진짜 서비스 데이터베이스는 보통 인터넷에 닫혀 있습니다. 개발자가 잠깐 데이터를 조회하거나 수정할 때, 오늘 우리가 하는 것처럼 터널을 열어 자기 노트북에서 안전하게 접속합니다.
- 사내 전용 서비스에 원격 접속: 회사 내부에서만 열려 있는 관리자 페이지나 대시보드를, 재택근무 중인 직원이 터널을 통해 마치 사내에 있는 것처럼 접속합니다.
- 로컬 개발과 원격 자원 연결: 내 노트북에서 개발 중인 앱을, 클라우드에 있는 캐시 서버나 데이터베이스에 붙여 테스트할 때 터널로 연결합니다.
공통점은 "목적지를 인터넷에 열지 않은 채, 내 컴퓨터에서 그것을 바로 옆에 있는 것처럼 쓴다"는 것입니다. 우리는 이걸 프라이빗 RDS에 적용합니다.
13.2 중계자에게 접속하는 두 가지 방법 — Bastion vs SSM
그럼 중계자(VPC 안의 작은 서버)에는 어떻게 접속할까요? 두 방식이 있고, 우리는 두 번째를 씁니다. 먼저 전통적인 방식부터 봅니다.
방법 1. Bastion Host (전통적 방식)
Bastion Host(배스천 호스트, 점프박스) 외부 접속을 받기 위해 퍼블릭 서브넷에 세워 두는 중계 전용 서버입니다. 요새의 "망루"라는 뜻으로, 여기로 먼저 들어간 뒤(점프) 내부로 접근한다고 해서 점프박스라고도 부릅니다.
전통적으로는 이 서버에 SSH라는 원격 접속 기술로 들어갔습니다.
SSH(Secure Shell) 다른 컴퓨터에 안전하게 원격 접속하는 표준 기술입니다. 접속하려면 열쇠 파일(키)을 관리해야 하고, 서버의 22번 포트를 인터넷에 열어 둬야 합니다.
이 방식의 단점: 22번 포트를 인터넷에 열어 둬야 해서 공격 표적이 되고, 키 파일을 잃어버리거나 유출되는 사고도 잦습니다. 그래서 요즘 실무는 다음 방식으로 넘어가고 있습니다.
방법 2. SSM Session Manager (실무 권장, 우리가 쓸 방식)
SSM Session Manager(세션 매니저) AWS Systems Manager(SSM)라는 관리 서비스의 기능으로, 포트를 하나도 열지 않고 서버에 접속하게 해 줍니다. 서버 쪽에서 AWS로 먼저 연결을 걸어 두면(밖으로 나가는 방향), 내가 AWS를 통해 그 연결을 타고 들어가는 방식입니다. 누가 접속할 수 있는지는 IAM 권한으로 통제합니다.
두 방식을 비교하면 이렇습니다.
| Bastion + SSH | SSM Session Manager | |
|---|---|---|
| 열어야 하는 포트 | 22번을 인터넷에 개방 | 없음 |
| 열쇠(키) 관리 | SSH 키 파일 필요 | 필요 없음 (IAM으로 통제) |
| 서버 위치 | 퍼블릭 서브넷 필수 | 프라이빗 서브넷도 가능 |
| 접속 기록(감사 로그) | 직접 구성해야 함 | AWS에 자동 기록 |
[활용 사례] 실무에서 SSM을 쓰는 이유
- 포트를 안 열고 서버에 접속: 서버에 22번 포트조차 열지 않으니, 인터넷에서 스캔·공격당할 표면이 사라집니다. 보안팀이 가장 좋아하는 지점입니다.
- 운영 데이터베이스에 안전하게 접속: 오늘 우리가 하는 그대로, 프라이빗 데이터베이스에 터널로 붙어 점검·수정합니다. 키 파일을 주고받을 필요가 없어 사고가 줄어듭니다.
- 감사 로그(접속 기록) 자동화: "누가 언제 어느 서버에 접속했는지"가 AWS에 자동으로 남습니다. 보안 감사나 사고 조사 때 이 기록이 결정적입니다. SSH 방식은 이걸 따로 구축해야 합니다.
- 키 분실 사고 예방: 접속 권한을 사람이 아니라 IAM으로 관리하므로, 퇴사자 접근 차단이나 권한 회수를 클릭 몇 번으로 끝냅니다.
SSM 방식이면 중계 서버조차 프라이빗 서브넷에 숨길 수 있습니다. 인터넷에 노출되는 것이 하나도 없는 셈입니다. (서버가 AWS와 통신할 길만 있으면 되는데, 11장에서 만든 NAT 게이트웨이가 바로 그 길이 됩니다.)
13.3 중계용 EC2 만들기 — SSM 접속 준비
중계자로 쓸 작은 서버를 하나 만듭니다. AWS에서 서버 한 대를 빌리는 서비스가 EC2입니다.
EC2(Elastic Compute Cloud) AWS에서 가상 서버(컴퓨터) 한 대를 빌려 쓰는 서비스입니다. 지금까지 막연히 "서버"라고 부른 것의 실체가 이것입니다. 원하는 사양(CPU·메모리)과 운영체제를 골라 몇 분 만에 서버 한 대를 켤 수 있습니다.
[활용 사례] EC2는 이런 데 쓰입니다
- 웹/API 서버: 회사 서비스의 백엔드를 EC2 서버에 올려 운영합니다. 가장 흔한 용도입니다.
- 배치·데이터 처리: 밤마다 대량 데이터를 집계하는 작업용 서버로 잠깐 켰다 끕니다. 쓴 시간만큼만 요금을 냅니다.
- 중계·점검용 서버(오늘 우리 용도): VPC 안에서 다른 자원(프라이빗 RDS 등)에 접근하기 위한 작은 발판 서버로 씁니다. 우리는 가장 작은 EC2 한 대만 중계용으로 씁니다.
SSM으로 접속되는 서버가 되려면 조건이 두 가지 필요합니다.
- 서버 안에 SSM Agent가 설치되어 있을 것
- [용어] SSM Agent(에이전트): 서버 쪽에서 AWS SSM과 연결을 유지하는 작은 프로그램입니다. 우리가 쓸 Amazon Linux에는 기본으로 설치되어 있어 따로 신경 쓸 게 없습니다.
- 서버에 SSM 권한이 있는 IAM 역할이 붙어 있을 것
- [용어] IAM 역할(IAM Role): 사람이 아닌 AWS 리소스에게 부여하는 권한 묶음입니다. 8장의 IAM 사용자가 "사람용 신분증"이라면, IAM 역할은 "서버용 신분증"입니다. 이 서버가 SSM과 통신해도 된다는 허가증을 달아 주는 것입니다.
이제 콘솔에서 순서대로 만듭니다. 웹브라우저에서 AWS 콘솔에 로그인한 상태로 시작하세요. 화면 오른쪽 위 리전이 서울(ap-northeast-2)인지 먼저 확인합니다.
[콘솔] 1단계: EC2용 IAM 역할 만들기
- 콘솔 상단 검색창에
IAM을 입력해 IAM 서비스로 들어가, 왼쪽 사이드바에서 역할(Roles)을 클릭합니다. - 역할 생성(Create role) 버튼을 누릅니다.
- 신뢰할 수 있는 엔터티 유형(Trusted entity type): AWS 서비스(AWS service)를 선택하고, 아래 사용 사례(Use case)에서 EC2를 선택한 뒤 다음(Next)으로 갑니다. ("이 역할은 EC2 서버가 쓸 것"이라는 뜻입니다.)
- 권한 추가(Add permissions) 화면의 정책 검색창에
AmazonSSMManagedInstanceCore를 입력하고, 나오는 정책 왼쪽 체크박스에 체크한 뒤 다음(Next)으로 갑니다.- [참고] 이 정책이 "SSM으로 관리·접속되기 위한 최소 권한 묶음"입니다. 이름이 길어 헷갈리기 쉬우니 철자를 그대로 붙여넣어 검색하세요.
- 역할 이름(Role name):
vote-bastion-role로 입력하고, 맨 아래 역할 생성(Create role)을 누릅니다.
IAM → 역할 목록에 vote-bastion-role이 보이면 1단계 완료입니다.
[콘솔] 2단계: 중계용 EC2 만들기
- 콘솔 상단 검색창에
EC2를 입력해 EC2 서비스로 들어가, 인스턴스 시작(Launch instance) 버튼을 누릅니다.- [용어] 인스턴스(Instance): 실제로 켜져서 돌아가는 EC2 서버 한 대를 가리키는 말입니다. "서버 한 대 = 인스턴스 하나"로 이해하면 됩니다.
- 설정을 위에서부터 입력합니다.
- 이름(Name):
vote-bastion - 애플리케이션 및 OS 이미지(AMI): 기본으로 선택돼 있는 Amazon Linux
2023을 그대로 둡니다.
- [용어] AMI(Amazon Machine Image): EC2 서버에 설치할 운영체제 묶음입니다. Amazon Linux에는 SSM Agent가 기본 내장되어 있어 우리에게 딱 맞습니다.
- 인스턴스 유형(Instance type):
t3.micro를 선택합니다. (가장 작은 축에 드는 크기로, 중계용으로 충분합니다.) - 키 페어(Key pair (login)): 키 페어 없이 진행(Proceed without a
key pair)을 선택합니다.
- [참고] 키 페어는 SSH 접속용 열쇠 파일입니다. 우리는 SSM으로 접속하므로 필요 없습니다. 이것이 앞에서 말한 "키 관리가 필요 없다"는 SSM의 장점을 바로 보여 줍니다.
- 이름(Name):
- 네트워크 설정(Network settings)에서 오른쪽 편집(Edit)을 누르고
아래처럼 바꿉니다.
- VPC:
vote-vpc를 선택합니다. - 서브넷(Subnet): 프라이빗 서브넷 중 하나를 선택합니다. (11장에서
만든 서브넷 중 이름에
private이 들어간 것.) - 퍼블릭 IP 자동 할당(Auto-assign public IP): 비활성화(Disable)로
둡니다.
- [참고] 프라이빗 서브넷 + 퍼블릭 IP 없음. 이 서버는 인터넷에서 전혀 보이지 않습니다. 그래도 SSM으로 접속할 수 있다는 게 핵심입니다.
- 방화벽(보안 그룹)(Firewall (security groups)): 보안 그룹
생성(Create security group)을 선택하고, 보안 그룹 이름을
vote-bastion-sg로 입력합니다.- 자동으로 만들어진 인바운드 규칙(SSH 22번 허용)이 있으면 삭제합니다. 규칙 오른쪽 제거(Remove)를 눌러 인바운드 규칙을 비웁니다. (들어오는 문 자체를 안 엽니다.)
- VPC:
- 고급 세부 정보(Advanced details)를 펼쳐(제목 왼쪽 화살표 클릭),
IAM 인스턴스 프로파일(IAM instance profile) 항목에서 1단계에서 만든
vote-bastion-role을 선택합니다.- [주의] 이 단계를 빠뜨리면 SSM 접속이 안 됩니다. 나중에 이 서버가 SSM 목록에 안 보이거나 접속이 안 될 때 가장 먼저 확인할 곳이 바로 여기입니다.
- 오른쪽 요약 패널의 인스턴스 시작(Launch instance)을 눌러 만듭니다.
"인스턴스 시작됨" 안내가 나오고, EC2 → 인스턴스(Instances)
목록에 vote-bastion이 나타나면 2단계 완료입니다. 상태가 실행 중
(Running)이 될 때까지 잠시 기다립니다.
[콘솔] 3단계: RDS의 문을 중계 서버에게 열어 주기
중계 서버가 RDS에 닿으려면, 12장에서 잠가 둔 RDS 보안 그룹에 규칙을 하나 추가해야 합니다. "중계 서버에게만 5432번 문을 연다"는 규칙입니다.
- EC2 콘솔 왼쪽 사이드바에서 보안 그룹(Security Groups)을 클릭합니다.
- 목록에서
vote-db-sg(12장에서 만든 RDS 보안 그룹)를 클릭해 선택합니다. - 아래쪽 인바운드 규칙(Inbound rules) 탭 → 인바운드 규칙 편집(Edit inbound rules) 버튼을 누릅니다.
- 규칙 추가(Add rule)를 누르고 입력합니다.
- 유형(Type):
PostgreSQL을 선택합니다. (포트 5432가 자동으로 채워집니다.) - 소스(Source): 오른쪽 검색창을 클릭해
vote-bastion-sg를 찾아 선택합니다. (IP가 아니라 보안 그룹을 소스로 지정하는 것입니다.) - 11장에서 배운 "보안 그룹이 보안 그룹을 가리키는" 방식 그대로입니다.
- 유형(Type):
- 규칙 저장(Save rules)을 누릅니다.
SSM 준비 완료 확인
EC2 콘솔 → 인스턴스에서 vote-bastion이 실행 중(Running)이고,
콘솔 상단 검색창에서 Systems Manager로 들어가 왼쪽 사이드바
플릿 매니저(Fleet Manager)에 이 서버가 나타나면 SSM 준비가 된
것입니다. (등록까지 몇 분 걸릴 수 있으니, 안 보이면 잠시 뒤 새로고침
하세요.) 여기 나타나야 다음 단계 터널이 열립니다.
13.4 내 컴퓨터 준비 — Session Manager Plugin 설치
내 노트북에서 SSM 세션(터널)을 열려면, AWS CLI에 더해 부속 프로그램 하나가 필요합니다.
Session Manager Plugin(세션 매니저 플러그인) 내 컴퓨터의 AWS CLI가 SSM 세션(터널)을 실제로 열 수 있게 해 주는 부속 프로그램입니다. 한 번만 설치하면 됩니다. (AWS CLI 자체는 앞 장에서 이미 설치했으니, 여기서는 이 플러그인만 추가합니다.)
가장 쉬운 방법은 Claude Code에게 맡기는 것입니다. vote-app 폴더를 연
VS Code 터미널에서 claude를 실행한 뒤 이렇게 요청하세요.
직접 설치하고 싶다면 운영체제별로 아래처럼 합니다. 모두 VS Code 터미널에서 실행합니다.
- macOS (Homebrew가 있으면 가장 간단):
brew install --cask session-manager-plugin # Homebrew로 플러그인 설치
- macOS (Homebrew가 없으면 AWS 공식 설치 파일): 아래를 한 줄씩 실행합니다. Apple 실리콘(M1 이상) 맥 기준입니다.
# 1) AWS가 배포하는 설치 파일(.pkg)을 내려받습니다
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac_arm64/session-manager-plugin.pkg" -o "session-manager-plugin.pkg"
# 2) 내려받은 설치 파일을 설치합니다 (관리자 비밀번호를 물으면 노트북 로그인 암호 입력)
sudo installer -pkg session-manager-plugin.pkg -target /
# 3) 어디서든 명령을 쓸 수 있게 연결(심볼릭 링크)을 만듭니다
sudo ln -s /usr/local/sessionmanagerplugin/bin/session-manager-plugin /usr/local/bin/session-manager-plugin
- [참고] 인텔 맥이라면 위 주소의
mac_arm64부분만mac으로 바꾸면 됩니다. 어느 쪽인지 모르겠으면 그냥 Claude Code에게 위 프롬프트로 맡기는 게 가장 안전합니다. - Windows: AWS 공식 문서에서 Windows용 설치 프로그램(EXE)을 내려받아 실행합니다. 검색창에 "Session Manager Plugin Windows install"을 검색해 나오는 AWS 공식 문서(docs.aws.amazon.com)의 다운로드 링크를 쓰세요. 설치 후에는 VS Code 터미널을 새로 열어야(터미널 → 새 터미널) 인식됩니다.
설치 확인
설치가 끝나면 VS Code 터미널(Claude Code를 켠 상태라면 /exit로 나온 뒤)에서
아래를 입력합니다.
session-manager-plugin # 플러그인이 설치됐는지 확인하는 명령
아래와 같은 메시지가 나오면 성공입니다.
The Session Manager plugin is installed successfully. Use the AWS CLI to start a session.
command not found 같은 메시지가 나오면 설치가 아직 반영되지
않은 경우가 많습니다. 터미널 영역 오른쪽 위 휴지통 아이콘으로 터미널을
닫고 터미널 → 새 터미널로 새로 연 다음 다시 입력해 보세요.
13.5 [맛보기] SSM으로 서버에 한번 들어가 보기
바로 터널을 열기 전에, SSM이 실제로 서버에 붙는다는 걸 아주 작게 손으로 확인해 봅시다. 터널(포트 포워딩)은 그다음에 엽니다. 이 맛보기는 우리 데이터베이스와 무관한, "SSM으로 서버에 접속만 해 보는" 최소 실험입니다.
먼저 중계 서버의 인스턴스 ID가 필요합니다. EC2 콘솔 → 인스턴스에서
vote-bastion을 클릭하면 상세 화면에 i-로 시작하는 값(예:
i-0abc123def456)이 보입니다. 그 값을 복사해 둡니다.
VS Code 터미널에서 아래를 실행하세요. i-0abc123def456 자리에는 방금
복사한 여러분의 인스턴스 ID를 넣습니다.
aws ssm start-session \
--target i-0abc123def456 \
--region ap-northeast-2
- aws ssm start-session: SSM 세션(연결)을 시작하라는 명령.
- --target: 접속할 서버의 인스턴스 ID.
- --region ap-northeast-2: 서버가 있는 리전(서울). CLI 기본 리전이 이미 서울이면 이 줄은 없어도 되지만, 안전하게 붙였습니다.
연결되면 프롬프트가 sh-5.2$ 같은 모양으로 바뀝니다. 지금 여러분은 포트를
하나도 열지 않은, 인터넷에 안 보이는 프라이빗 서버 안에 들어와 있는
것입니다. 확인 삼아 아래를 쳐 봅니다.
whoami # 지금 이 서버에서의 사용자 이름을 보여 줍니다 (보통 ssm-user)
exit # 서버에서 빠져나와 SSM 세션을 종료합니다
ssm-user 같은 이름이 나오고 exit로 빠져나오면, SSM 접속이 정상이라는
확인이 끝난 것입니다. 이제 이 접속 방식 위에 "포트 포워딩"을 얹어 진짜
터널을 엽니다.
TargetNotConnected 오류가 나면
서버가 아직 SSM에 등록되는 중이거나, EC2에 vote-bastion-role이 안
붙은 경우입니다. 13.3의 마지막 확인을
다시 점검하세요.
13.6 터널 열기 — SSM 포트 포워딩
이제 진짜 터널을 엽니다. 준비물은 두 가지입니다.
- 중계 서버의 인스턴스 ID: 13.5에서 복사한
i-로 시작하는 값. - RDS 엔드포인트: 12장에서 메모해 둔 RDS 주소(예:
vote-db.xxxx.ap-northeast-2.rds.amazonaws.com). 기억이 안 나면 사전 조건에서 안내한 대로 RDS 콘솔에서 다시 확인합니다.
VS Code 터미널에서 아래 명령을 실행합니다. 한 줄이 길어 \로 나눴습니다.
--target과 host 값은 여러분 것으로 바꿔 넣으세요.
aws ssm start-session \
--target i-0abc123def456 \
--region ap-northeast-2 \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["vote-db.xxxx.ap-northeast-2.rds.amazonaws.com"],"portNumber":["5432"],"localPortNumber":["5432"]}'
각 부분의 뜻을 풀면 이렇습니다.
- aws ssm start-session: SSM 세션(연결)을 시작하라.
- --target: 중계 서버(우리 EC2)의 인스턴스 ID.
- --region ap-northeast-2: 서버가 있는 리전(서울).
- --document-name AWS-StartPortForwardingSessionToRemoteHost: "중계 서버를 거쳐 다른 목적지(RDS)로 포트를 이어 주는" 미리 정의된 작업의 이름입니다. 이름이 길지만 뜻 그대로입니다(포트 포워딩 세션을 원격 호스트로 시작하기).
- --parameters: 세부 값 세 가지를 JSON으로 넘깁니다.
host: 최종 목적지 = RDS 엔드포인트 (내 것으로 바꿔 넣습니다)portNumber: 목적지의 포트 = 5432 (PostgreSQL)localPortNumber: 내 컴퓨터에서 쓸 포트 = 5432
Windows(PowerShell) 사용자
PowerShell에서는 \ 줄바꿈이 동작하지 않아 한 줄로 붙여 써야 하고,
--parameters의 JSON 부분은 따옴표 처리 방식이 달라 오류가 나기 쉽습니다.
이럴 때는 Claude Code에게 "이 명령을 Windows PowerShell에서 그대로
실행되게 바꿔 줘"라고 요청하는 것이 가장 빠릅니다. (아래 [프롬프트]로
아예 명령 조립부터 맡겨도 됩니다.)
실행하면 아래 같은 메시지가 나옵니다.
Starting session with SessionId: ...
Port 5432 opened for sessionId ...
Waiting for connections...
Waiting for connections...가 보이면 터널이 열린 것입니다. 이
터미널 창(탭)은 그대로 켜 두세요. 닫으면 터널도 함께 닫힙니다. 이제 상황이
이렇게 바뀌었습니다.
내 노트북(localhost:5432) ══ SSM 터널 ══▶ EC2(중계) ──▶ RDS:5432
↑
여기 접속하면 곧 RDS에 접속하는 것과 같다
13.7 접속 확인 — 이제 내 컴퓨터에 데이터베이스가 있는 것처럼
터널을 연 터미널은 그대로 둔 채, 새 터미널을 하나 더 엽니다(VS Code
터미널 영역 오른쪽 위 + 버튼, 또는 터미널 → 새 터미널). 접속 정보는
12장 표와 거의 같은데, 호스트만 localhost로 바뀝니다. 터널 덕분에
내 컴퓨터 주소로 RDS에 닿기 때문입니다.
| 항목 | 값 |
|---|---|
| 호스트 | localhost (터널 덕분에 내 컴퓨터 주소를 씁니다) |
| 포트 | 5432 (13.6에서 포트를 바꿨다면 그 번호) |
| 데이터베이스 | voteapp |
| 사용자 | postgres |
| 비밀번호 | 12장에서 정한 마스터 암호 |
이 새 터미널에서 claude를 실행해 Claude Code에게 확인을 맡깁니다.
"연결 성공, 현재 테이블 없음" 같은 결과가 나오면 성공입니다. 프라이빗 서브넷 깊숙이 숨어 있는 RDS에, 포트 하나 열지 않고 안전하게 접속한 것입니다.
13.8 Claude Code로 DB 작업 — 스키마·시드 데이터
접속이 됐으니, 이 데이터베이스를 실제 서비스용으로 준비합니다. 새 용어 셋을 먼저 정리합니다.
스키마(Schema)
데이터베이스 안의 구조(어떤 테이블이 있고, 각 테이블에 어떤 항목이
있는지)를 뜻합니다. 3장에서 그린 데이터 모델(설문 polls, 선택지
options)을 실제 데이터베이스에 구현한 것이 스키마입니다.
마이그레이션(Migration) 스키마를 만들거나 바꾸는 작업을 "기록이 남는 절차"로 실행하는 것입니다. 무엇을 언제 바꿨는지 순서대로 남기 때문에, 다른 환경(예: 새 데이터베이스) 에도 같은 변경을 똑같이 재현할 수 있습니다.
시드 데이터(Seed Data) 서비스 시작에 필요한 초기 데이터입니다. "씨앗"이라는 뜻 그대로, 텅 빈 데이터베이스에 심어 두는 첫 데이터입니다. 우리는 데모용 설문 하나를 심어 둡니다.
이 작업도 Claude Code에게 맡깁니다. 13.7에서 Claude Code를 켠 그 터미널
(터널이 아니라, claude를 실행한 새 터미널)에서 이어서 요청하세요. 우리
vote-app 프로젝트에는 이미 로컬 개발용으로 만들어 둔 테이블 구조가 있으니,
그걸 그대로 RDS에 적용해 달라고 하면 됩니다.
Claude Code가 테이블을 만들고 데이터를 넣은 뒤, 조회 결과를 보여 줍니다.
polls에 설문 1건, options에 선택지 3건(김치찌개 3표, 파스타 5표,
샐러드 1표)이 보이면 완료입니다.
방금 한 일의 의미 3장에서 종이에 그렸던 데이터 모델이, 지금 AWS 클라우드의 실제 데이터베이스 안에 만들어졌습니다. 14장에서 컨테이너를 띄우면, 그 컨테이너가 바로 이 테이블을 읽고 쓰게 됩니다.
작업이 끝났으면 터널을 닫아 둡니다. 터널을 연 터미널(Waiting for
connections...가 떠 있던 그 터미널)로 가서 Ctrl + C를 누르면 터널이
닫힙니다. 필요할 때 13.6의 명령으로 언제든 다시 열 수 있습니다.
[확인]
이번 장이 제대로 끝났는지 눈으로 확인합니다.
- 13.5에서
aws ssm start-session으로 중계 서버에 들어가whoami가ssm-user를 보여 줬다. - 13.6에서 포트 포워딩 명령을 실행했더니
Waiting for connections...가 떴다(터널 열림). - 13.7에서 Claude Code가
localhost:5432→voteapp접속에 성공했다. - 13.8에서 조회 결과에
polls1건,options3건(김치찌개 3표, 파스타 5표, 샐러드 1표)이 나왔다.
여기까지 보였다면, 프라이빗 RDS에 안전하게 접속해 데이터베이스 초기화까지 끝난 것입니다. 네트워크, 데이터베이스, 그리고 데이터베이스 초기화까지 — 무대가 완성됐습니다.
막히면
이 장에서 자주 나는 오류와 대처법입니다.
-
session-manager-plugin이command not found. 플러그인 설치가 아직 반영되지 않은 것입니다. 터미널을 휴지통 아이콘으로 닫고 터미널 → 새 터미널로 새로 연 뒤 다시 확인하세요. 그래도 안 되면 13.4의 설치를 다시 진행합니다. -
aws ssm start-session에서TargetNotConnected가 나온다. 중계 EC2가 아직 SSM에 등록되지 않았거나,vote-bastion-role이 안 붙은 경우입니다. Systems Manager → 플릿 매니저에vote-bastion이 보이는지 확인하고(13.3 [확인]), 안 보이면 EC2 상세의 IAM 역할을 점검합니다. 등록에는 몇 분 걸릴 수 있습니다. -
InvalidInstanceId또는 대상 인스턴스를 못 찾는다. 인스턴스 ID를 잘못 복사했거나, 명령의 리전이 서버 리전과 다릅니다. EC2 → 인스턴스에서vote-bastion의i-로 시작하는 ID를 다시 복사하고, 명령에--region ap-northeast-2가 들어갔는지 확인하세요. -
터널은 열렸는데(
Waiting for connections...) DB 접속이 안 된다. RDS 보안 그룹(vote-db-sg)에vote-bastion-sg를 소스로 하는 5432 허용 규칙이 있는지 확인하세요(13.3 3단계). 이 규칙이 없으면 중계 서버가 RDS 문을 열지 못합니다. -
Address already in use(5432 포트가 이미 쓰이는 중). 내 노트북에 로컬 PostgreSQL이 켜져 있을 수 있습니다. 그것을 끄거나, 13.6 명령의localPortNumber를15432로 바꿔 열고 13.7의 접속 포트도 15432로 맞추세요. -
비밀번호가 기억나지 않는다. 12장에서 정한 RDS 마스터 비밀번호입니다. 기억이 안 나면 RDS 콘솔에서
vote-db의 수정(Modify)으로 새 비밀번호를 설정해 바꿀 수 있습니다.
[체크리스트]
아래가 모두 됐는지 확인하세요. 하나라도 안 됐다면 해당 절로 돌아갑니다.
- □ 프라이빗 RDS에 밖에서 직접 접속할 수 없는 이유를 그림으로 설명할 수 있다
- □ 터널링(포트 포워딩)이 무엇인지 안다
- □ Bastion+SSH 대비 SSM의 장점(포트 무개방·키 불필요·IAM 통제·감사 로그)을 말할 수 있다
- □
vote-bastion-role(IAM 역할)을 만들었다 - □ IAM 역할을 붙인 중계용 EC2(
vote-bastion)를 프라이빗 서브넷에 만들었다 - □ RDS 보안 그룹(
vote-db-sg)에vote-bastion-sg허용 규칙을 추가했다 - □ Session Manager Plugin을 설치하고 확인 메시지를 봤다
- □
aws ssm start-session으로 중계 서버에 들어가 봤다(맛보기) - □ SSM 포트 포워딩으로 터널을 열고
localhost:5432로 RDS에 접속했다 - □ Claude Code로 스키마(polls, options)와 시드 데이터를 RDS에 넣었다
모두 체크됐다면 데이터베이스 초기화가 끝난 겁니다.