BoostUs로그인

Private Subnet에 DB서버를 구축해보자

J003 강동훈10
0
0
2026-01-25
원문 보기
Private Subnet에 DB서버를 구축해보자  글의 썸네일 이미지

개요

프로젝트를 진행하면서 데이터를 저장해야하는 일이 생겨, DB를 연동해야할 일이 발생하였다. DB를 구축할 수 있는 공간은 다음과 같은 선택지가 존재한다.

  1. 서버 내부에 저장
  2. 클라우드 서비스를 이용

서버 내부에 데이터를 저장한다면 별도의 저장소 구축을 위한 비용이 추가되지 않으며 데이터가 점차 누적될수록 서버 내 사용할 수 있는 여유 공간이 부족해진다는 단점이 존재한다.

반면, 클라우드 서비스를 이용한다면 간단히 서버와 연결하여 데이터를 저장소에 저장만 하면 알아서 클라우드가 관리해준다는 장점이 존재하지만 그만큼 클라우드 비용이 발생한다는 부담이 생기며 네트워크 통신을 통해 데이터를 주고받게 된다. 참고로 NCP에서 제공하는 클라우드 DB의 가격은 한 달에 약 36만원 정도 비용이 발생한다.

우리는 돈이 그리 많지 않기에 클라우드 서비스를 사용하지 않고 서버 내부에 저장하기로 결정하였다. 그렇다면 또 두 가지 선택지로 나눠질 수 있다.

  1. 어플리케이션 서버에 저장할 것인가
  2. DB 서버를 구축하여 저장할 것인가

서비스를 제공하는 어플리케이션 서버 내부 용량이 여유가 있다면 같은 서버 내에 데이터를 저장하며 관리할 수도 있다. 이러한 방법을 이용하면 새로운 서버를 생성할 필요없이 간편하게 DB를 구축할 수 있으며 같은 서버 내에서 데이터를 조회하거나 저장하기 때문에 네트워크 통신 비용 또한 절감할 수 있다는 장점이 있다.

하지만 반대로, 서비스가 커지고 데이터가 누적될수록 서버의 용량이 부족하다는 문제는 여전하며 서버의 스펙을 올리든(Scale-Up) DB서버를 분리하여 해결해야 한다는 문제가 생길 수 있다. 또한, 하나의 서버로 서비스를 제공 + 데이터 저장이 이루어지기 때문에 서버가 다운되면 서비스와 DB 모두 사용이 불가하며 최악의 경우 하나의 서버 탈취로 인해 서비스 및 데이터까지 함께 유출될 수 있다는 위험이 존재한다.

이러한 이유로 DB 서버를 하나 구축하여 어플리케이션 서버와 연결하여 데이터를 관리하기로 결정하였다. 마침 NCP에서는 회원가입 후 1년 동안 1개의 Micro 서버를 무료로 제공해주기 때문에, 10기가의 저장소를 무료로 사용할 수 있었다.

DB 서버 생성

현재 프로젝트의 인프라 구조는 다음과 같다. (VPC에 대한 학습은 여기로)

Public Subnet 안에 서비스를 제공하는 어플리케이션 서버가 실행되고 있고 유저들은 인터넷을 통해 우리 서버에 요청을 보내고 응답을 받으며 서비스를 이용하고 있다.

이러한 구조에서 DB 서버를 구축하기 위해서는 다음과 같은 인프라 구조로 개선시키고자 하였다.

Private Subnet은 Internet Gateway를 연결하지 않기 때문에 외부(인터넷)을 통해 접근이 불가하며, 같은 VPC 내에서만 접근이 가능한 영역이다. 그렇기 때문에 해당 영역에 DB 서버를 구축한다면 외부에서는 접근이 불가하지만 내가 생성한 VPC 내의 서버들은 안전하게 접근이 가능한 환경을 구축할 수 있다.

1️⃣ Private Subnet 생성

Internet Gateway를 확인해보면 Private 상태를 갖고 있다. 인터넷을 통해 접근이 불가한 영역을 VPC 내에 생성하였다.

2️⃣ DB 서버 생성

DB 서버에 DBMS 설치

우리는 이쁜 코끼리 PostgreSQL을 사용하기로 결정하였기 때문에 DB 서버에 데이터를 저장하기 위해서 PostgreSQL을 설치해야 했다.

서버를 생성하면서 추측하였던 한 가지 문제점은 "DB 서버 내에 PostgreSQL을 설치하기 위해서는 인터넷에 연결해야만 한다"는 점이다.

서버 내 프로그램을 설치하려면 인터넷에서 제공하는 패키지를 패칭하여 설치해야 하는데, DB 서버는 Private Subnet에 위치하기 때문에 추가적인 조치를 하지 않으면 인터넷으로부터 설치가 불가하다.

하지만 추측일 뿐이니 우선적으로 DB 서버에 접근하여 apt를 업데이트하고 postgreSQL을 설치해보았다.

sudo apt update를 통해 정상적으로 apt를 업데이트해왔다.

postgreSQL도 정상적으로 설치되었다. ??!!!?!

다만 26.01.19일 시점 최신 release는 18버전인데, 25.08.14에 배포된 16.10버전이 설치되어 있어 의아함이 존재하였다.

이에 대해 이런 저런 원인을 조사하다가 직접 NCP에 문의를 남겨보았다.

그리고 이런 링크를 제공받았다 - Linux OS Repository 설정 점검

미러 서버란?

리눅스에서 sudo apt를 통해서 편하게 패키지를 설치하였다.

APT (Advanced Package Tool) 란 Debian과 Ubuntu 기반 Linux 배포판에서 소프트웨어 패키지를 관리하는 도구며 apt 명령어를 통해 Repository에 포함된 패키지를 쉽게 설치할 수 있다.

그러면 apt를 통해 설치할 Repository가 어디에 연결되어 있는 지가 중요한데,,,

기본적으로 http://archive.ubuntu.com/ubuntu/ 주소가 Ubuntu 운영체제의 공식 소프트웨어 저장소 서버인데, NCP에서는 Private Subnet과 같은 특정 환경에서는 인터넷을 통해 접근이 불가하니, 사설 Repository를 제공하여 패키지를 설치할 수 있도록 설정해두었다고 한다.

다만, 해당 Repository에 패키지는 아무래도 수동으로 올라온 패키지를 받는 것이기 때문에 최신 패키지를 받아오지 못할 확률이 높으며 이러한 이유로 나 또한 반 년 전에 배포된 16버전을 설치된 것이다.

NAT Gateway

NCP Repository 덕분에 큰 설정없이 정상적으로 DBMS를 설치하였지만 반대로 NAT Gateway를 연결해볼 수 있는 경험을 놓칠 순 없으니 최신 DBMS를 설치할 겸, NAT Gateway를 통해 Internet과 Private Subnet을 연결해보고자 한다.

1️⃣ Subnet 생성

NAT Gateway를 포함시킬 Subnet을 생성해주었다.

설정
IP 범위192.168.xx.x/xx
Internet GatewayY (Public)
용도NAT Gateway

2️⃣ NAT Gateway 생성

그리고 생성한 Subnet 내부에 NAT Gateway를 생성해주었다.

내용
Public IP49.50.xxx.xxx
Private IP192.168.xx.x
Subnet`nat-gateway (192.168.xx.x/xx)

3️⃣ Private Subnet Route Table 설정

이제 NAT Gateway를 통해 Private Subnet에 연결되어야 하기 때문에 Private Subnet의 Route Table에서 해당 연결을 허용시켜주었다.

4️⃣ 실제 인터넷 연결이 가능한 지 테스트

root@web02-db:~# curl https://google.com

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="https://www.google.com/&quot;&gt;here&lt;/A>. </BODY></HTML>

root@web02-db:~# psql --version psql (PostgreSQL) 18.1 (Ubuntu 18.1-1.pgdg24.04+2)

DB 서버에 접속하여 실제 인터넷을 통해 데이터를 받아올 수 있는 지 확인해보기 위해 구글에 요청을 보내보았고 정상적으로 데이터를 받아올 수 있었다.

만약 Route Table에서 NAT Gateway로 연결을 시켜주지 않는다면 Timeout이 발생한다.

그리고 PostgreSQL 공식문서에서 제공하는 명령어를 통해 18버전을 설치할 수 있었다.

DB 서버에 SSH 접속

여기서 두 번째 문제가 발생한다.

계속 언급하듯 DB 서버는 Private Subnet에 위치하기 때문에 인터넷을 통해 외부 접근이 불가하다. 그렇다면 내 로컬 환경에서는 DB 서버에 어떻게 접근할 수 있을까?

보통 서버에 접근하기 위해서는 개인 키와 함께 ssh 명령어를 사용한다. 하지만** Private Subnet에 위치한 서버는 Public IP가 없기 때문에 로컬 환경에서 직접 ssh로 접속할 수 없다.**

이 문제를 해결하기 위해 일반적으로 Bastion Host를 통해 접근이 가능하다.

간단하게 Users -> Private Server 이 불가하기 때문에 Users -> Public Server(Bastion Host) -> Private Server 와 같은 과정을 통해 접근하는 방법이다.

Users에서 Private Server는 인터넷에서 접근하는 것이기 때문에 불가하지만 Public Server는 같은 VPC 내에 위치하기 때문에, Private Server 에서 허용만 한다면 접근이 가능하다.

이와 같은 접근을 허용하려면 Private Subnet의 ACG Inbound를 수정해주어야 한다. Bastion Host에서 SSH로 접근을 허용해야하기 때문에 Bastion Host의 Private IP에 22번 포트를 허용해주었다.

현재는 Public Server가 하나뿐이라 해당 서버의 Private IP를 Source로 지정했지만, Public Subnet에 위치한 Bastion 서버가 사용하는 ACG를 Private Subnet Inbound 규칙의 접근 소스로 지정하는 방식도 가능하다.

또한 DB 서버로 접근하기 위해서 DB 서버의 개인 키도 필요하였는데, 이 또한 로컬에서 복사하여 Public Server에 생성해두었다.

이렇게 환경을 구성해주면 로컬에서 ssh로 Bastion Host에 접근하고 다시 ssh로 DB 서버에 접근이 가능하다.

로컬에서 DB 서버에 데이터 저장

나는 "DB 서버를 생성하여 접근하는 것"이 목적이 아니다.

"DB 서버를 생성하여 데이터를 저장하는 것"이 목적이다.

기본적으로 배포 서버에서 DB 서버로 데이터를 저장하거나 조회하겠지만, 개발하다보면 로컬에서 DB 서버에 저장된 데이터를 조작해야 하는 경우가 있다. 무엇보다 현재는 DB를 세팅하는 중이기 때문에 디버깅을 위해 로컬에서 DB 서버로 접근이 가능해야 했다.

이러한 문제를 해결하기 위해서는 SSH Tunneling 을 통해 문제를 해결하였다.

이 또한 Bastion Host를 이용하여 SSH Tunnel을 생성하는 방식이다.

DB 서버는 Public Server를 통해서만 접근이 가능해야 하기 때문에 로컬 환경에서 직접적으로 접근이 불가하다.

그렇기에 로컬 환경에서 Public Server로 SSH 터널을 통해 DB 접속을 요청하면, Public Server는 해당 포트를 Private Subnet의 DB 서버로 포워딩한다. 이를 통해 외부에서는 DB 서버에 대한 직접적인 네트워크 접근 없이도 안전하게 DB에 연결할 수 있다.

1️⃣ SSH 터널링 명령어

ssh를 통해 SSH Tunnel을 생성하는 명령어는 다음과 같다.

ssh -i key.pem \
  -L 5433:db-private-ip:5432 \
  user@bastion-public-ip
  1. -i: 개인 키 파일을 통해 접속을 시도한다
  2. -L: -L [로컬포트]:[원격에서 접근할 대상 IP]:[대상 포트]
    1. ⇒ 로컬 5433 포트를 통해 private server의 5432 포트에 접근할 것입니다.
  3. -N : ssh를 통해 접속하여도 터널링만 유지한다. (쉘 제공 x)

2️⃣ DB 서버 postgreSQL 세팅

SSH Tunneling을 통해 DB 서버에 접근하기 위해서 DB 서버에서도 접근을 허용해주어야 한다.

  1. postgresql.conf

해당 파일은 어떤 IP의 연결 시도를 허용할 것인지 지정할 수 있다.

listen_addresses = '*'      # what IP address(es) to listen on;

DB 서버에서 해당 파일을 찾아서 접근 요청을 허용할 IP를 수정해주어야 한다.

기본값으로 아마 localhost가 적혀있었는데, 모든 접근 요청을 허용해주도록 *로 설정해주었다.

  1. pg_hba.conf

해당 파일은 접근을 허용할 특정 IP의 구체적인 설정을 지정할 수 있다.

# IPv4 local connections:
host    all             all             127.0.0.1/32          scram-sha-256
host    all             all             192.168.xx.xx/xx          scram-sha-256
  1. Private Subnet Inbound 포트 허용(여기서 4시간 디버깅함🔥🔥)

2번까지만 설정하고 터널링을 시도했는데 연결이 안되어서 4시간동안 디버깅하였다. 문제는 DB 서버에서 접근을 허용한 것이지, Private Subnet에서의 접근을 허용하지 않았던 점이다.

Bastion Host에서 SSH 22번 포트를 허용한 것처럼 동일하게 5432포트를 통해 접근할 것이기 때문에 5432 포트 또한 열어준다. (이미지는 위와 동일)

3️⃣ 로컬에서 DB 서버 접근 시도

환경을 구성하고 1️⃣ 에서의 명령어를 시도하여 성공적으로 연결이 된다면 해당 터미널은 응답없이 멈추게 된다. (-N 설정으로 터널링만 유지하기에)

그렇다면 현재 Public Server를 통해 터널을 생성해두었기에, 로컬 환경에서 5433 포트를 통해 접속을 시도하면 DB 서버에 접근이 가능해진다.

psql -h 127.0.0.1 -p 5433 -U web02_users -d web02
  1. -h : 호스트
  2. -p : 포트
  3. -U : 유저
  4. -d: DB 명

정확한 진단을 위해 Bastion Host를 통해 DB 서버에 SSH로 접속하여 로컬에서의 데이터 조작이 잘 반영되는지 실시간으로 확인하였다.

성공적으로 로컬 CLI에서 조작한 데이터가 DB 서버에 잘 적용되었다!

코드에서 DB로 연결해보자

CLI를 통해 정상적으로 DB 서버에 데이터를 저장할 수 있다면 코드를 통해서도 쉽게 로컬에서 DB에 연결하여 데이터를 조작할 수 있다.

우리 프로젝트에서는 Prisma를 사용하여 DB와 연결하였는데, Prisma에서는 DATABASE_URL이라는 환경변수를 이용하여 DB 연동을 시도한다.

아마 다른 ORM, 라이브러리 모두 비슷할 것이다.

DATABASE_URL="postgresql://web02_users:<Password>@localhost:5432/web02?schema=public"

해당 URL은 내 로컬에서 5432포트를 통해 postgreSQL을 연결하겠다는 명령어이다.

여기서 만약 로컬 -> DB 서버로 데이터를 조작하고자 한다면 두 가지만 설정하면 된다.

  1. SSH Tunneling을 통해 터널을 열어둔다.
  2. 5433 포트 번호로 로컬에서 연결을 시도한다.

그러면 정상적으로 DB 조작 코드들을 실행시키면 DB 서버에 정상적으로 반영되는 것을 확인할 수 있을 것이다.

Reference