참고) 모든 예시 코드는 블로그 포스팅용으로 재구성되었습니다.
1. 프로젝트 개요
기존 서비스는 다음과 같은 AWS 서비스들을 활용하고 있었다.
- 프론트엔드
- 백엔드
- S3: 파일 저장소
- CloudFront: CDN 및 이미지 서빙
- Lambda server: 마이크로 서버
- RDS: 데이터베이스
- CloudWatch: 로깅
이를 On-Premise 환경에서 구현하기 위해 Docker와 Nginx를 활용한 마이그레이션을 진행했다.
아키텍쳐 설계
2.1 Docker Compose 기반 마이크로서비스 구성
version: "3.8"
services:
my-nginx:
build: ./nginx
container_name: my-nginx
restart: always
depends_on:
- frontend
- backend
- micro-server
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs
- ./nginx/logs:/var/log/nginx
- /data:/data
networks:
- app_network
frontend:
build: ./frontend
container_name: frontend
restart: always
depends_on:
- backend
ports:
- "3000:3000"
networks:
- app_network
backend:
build: ./backend
container_name: backend
restart: always
volumes:
- /data:/data
env_file:
- .env
environment:
- DATABASE_URL=비밀
ports:
- "8000:8000"
depends_on:
- database
networks:
- app_network
database:
image: my-mysql
container_name: database
restart: always
environment:
비밀비밀
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app_network
micro-server:
build: ./micro-server
container_name: micro-server
restart: always
ports:
- "3001:3001"
networks:
- app_network
volumes:
- /data:/data
env_file:
- .env
networks:
app_network:
driver: bridge
volumes:
mysql_data:
다음과 같이 4개의 주요 서비스를 컨테이너로 구성했다.
- Nginx
- Frontend (Next.js)
- Backend (FastAPI)
- Micro Server (Node.js)
- Database (MySQL) - 이건 빌드시 테스트용으로 만든 컨테이너. 실제 온프레미스 환경에서 데이터베이스는 따로 구축했다.
2.2 Nginx를 활용한 서비스 라우팅 구조
http {
upstream backend {
server backend:80; # FastAPI 컨테이너
}
upstream frontend {
server frontend:3000; # Next.js 컨테이너
}
upstream micro-server {
server micro-server:80; # Node.js 컨테이너
}
server {
listen 80;
# Next.js (프론트엔드)
location / {
proxy_pass http://frontend;
}
# ✅ FastAPI (백엔드)
location /api/ {
proxy_pass http://backend; # ✅ FastAPI 컨테이너로 프록시
}
# Micro Server (Node.js)
location /micro-server/ {
proxy_pass http://micro-server/;
}
location /data/ {
alias /data/;
autoindex off;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
# 이미지 파일에 대한 설정
location ~* \.(jpg|jpeg|png|gif|ico|webp)$ {
expires 1d;
add_header Cache-Control "public, no-transform";
}
}
}
}
B --> D[내부 Nginx :80 백엔드] --> E[FastAPI :8000]
B --> F[내부 Nginx :80 micro-server] --> G[Node.js :3001]
- Frontend (Next.js)
- SSR 애플리케이션으로 직접 3000 포트 노출
- Next 자체로 프로덕션 서버로서 기능을 포함해 내부 Nginx 불필요
- Backend (FastAPI)
- 내부 Nginx가 8000 포트의 FastAPI 서버로 프록시
- 보안 및 로드밸런싱 설정 가능
- 프로세스 관리로 supervisor 사용
- Micro Server (Node.js)
- 내부 Nginx가 3001 포트의 Node.js 서버로 프록시
- 프로세스 관리로 supervisor 사용
요청 흐름 예시:
- API 요청 (/api)
- 클라이언트 → 외부 Nginx(:80) → Backend 컨테이너(:80) → 내부 Nginx → FastAPI(:8000)
- micro server 요청 (/micro-server)
- 클라이언트 → 외부 Nginx(:80) → micro server 컨테이너(:80) → 내부 Nginx → Node.js(:3001)
- 웹페이지 요청 (/)
- 클라이언트 → 외부 Nginx(:80) → Frontend 컨테이너(:3000) → Next.js SSR
3. AWS 서비스 대체
3.1 파일 시스템 (S3/CloudFront 대체)
AWS S3와 CloudFront를 대체하기 위해 Nginx와 로컬 파일시스템을 활용한 구조를 설계해 온프레미스 환경에서도 동일한 파일 저장 및 서빙 기능을 제공한다.
Nginx 설정을 통한 정적 파일 서빙
http {
# ... 기존 설정 ...
server {
listen 80;
# 정적 파일 서빙을 위한 location 설정
location /data/ {
alias /data/;
autoindex off;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
# 이미지 파일에 대한 캐싱 설정
location ~* \.(jpg|jpeg|png|gif|ico|webp)$ {
expires 1d;
add_header Cache-Control "public, no-transform";
}
}
}
}
Nginx의 정적 파일 서빙 기능을 활용하여 로컬 파일시스템에 저장된 파일들을 HTTP를 통해 제공한다. /data 디렉토리의 파일들을 웹에서 접근 가능하도록 설정하며, 이는 CloudFront의 CDN 기능을 대체하는 역할을 한다.
또한 이미지 서빙 시 URL에 불필요한 쿼리 파라미터가 포함되는 경우가 있어서 try_files $uri =404 를 사용해 동일한 이미지에 대해 다른 쿼리 파라미터가 붙더라도 하나의 캐시로 처리했다.
Docker 볼륨 마운트
services:
my-nginx:
volumes:
- /data:/data # 파일 저장소를 nginx 컨테이너에 마운트
backend:
volumes:
- /data:/data
파일 저장소를 nginx와 백엔드 서비스가 공유할 수 있도록 Docker 볼륨을 구성해준다.
파일 업로드 및 접근 로직
async def upload_file(file: UploadFile, file_path: str) -> dict:
try:
content = await file.read()
if STAGE == 'on-premise':
# URL 형태의 경로를 파일 시스템 경로로 변환
if file_path.startswith(IMAGE_DOMAIN_URL):
file_path = file_path.replace(IMAGE_DOMAIN_URL, '')
# 온프레미스 환경: 로컬 파일시스템에 저장
DATA_DIR = os.environ.get('FILE_DIR') # /data 디렉토리 경로
full_path = os.path.join(DATA_DIR, file_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'wb') as f:
f.write(content)
# nginx를 통한 파일 접근 URL 생성
file_url = f"{IMAGE_DOMAIN_URL}/{file_path}"
else:
# AWS 환경: S3에 업로드 후 CloudFront URL 반환
s3_client.put_object(
Bucket=aws_bucket_name,
Key=file_path,
Body=content
)
file_url = f"{CLOUDFRONT_URL}/{file_path}"
return {
"filename": file.filename,
"location": file_url # 브라우저에서 접근 가능한 URL 반환
}
파일 업로드 로직에서는 환경(AWS/온프레미스)에 따라 저장소를 분기처리하면서도, 일관된 URL 구조를 반환하도록 구현했다.
온프레미스 환경에서는 IMAGE_DOMAIN_URL을 기반으로 한 파일 경로를 생성하고, AWS 환경에서는 CloudFront URL을 사용한다. 이를 통해 프론트엔드에서는 환경과 관계없이 동일한 방식으로 파일에 접근할 수 있다.
예를 들어 동일한 이미지 파일이
와 같이 서로 다른 환경에서도 일관된 구조로 접근 가능하게 했다.
3.2 Micro Server (Lambda 대체)
// Lambda 핸들러를 Express 엔드포인트로 변환
app.post('/micro-server', async (req, res) => {
try {
const result = await handler(req.body);
if (result.statusCode === 200) {
res.setHeader('Content-Type', result.headers['Content-Type']);
// 로직로직
res.send(결과);
}
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
AWS Lambda로 구현되어 있던 기능을 독립적인 Node.js 서비스로 마이그레이션했다. Docker 컨테이너로 실행되며, REST API 엔드포인트를 통해 기존 Lambda 함수와 동일한 기능을 제공한다.
4. 배포 프로세스
4.1 빌드 프로세스 (온프레미스 환경으로 가져가기 전, 도커 이미지 빌드 과정)
빌드 과정
- 도커 rpm 설치 파일 다운로드
- 각 서비스(Frontend, Backend, Micro Server)를 Docker 이미지로 빌드
- 온프레미스 환경 배포를 위해 이미지를 tar 파일로 저장 (외부 네트워크 접근 차단된 환경)
이렇게 스크립트화 해서 한번에 배포에 필요한 파일들을 만들게 해두었다.
4.2 배포 자동화
1. 데이터베이스 배포 스크립트
미리 저장된 docker rpm 패키지로 도커 설치
Docker 서비스 활성화
Docker 그룹 생성 및 사용자 권한 설정
.tar파일로부터 도커 이미지 로드
MySQL 컨테이너 실행
2. 어플리케이션 서비스 배포 스크립트
미리 저장된 docker rpm 패키지로 도커 설치
Docker 서비스 활성화
Docker 그룹 생성 및 사용자 권한 설정
docker compose 설정
.tar파일로부터 도커 이미지 로드
docker compose를 통해 서비스 시작
대강 이런식으로 온프레미스 각 서버에서 스크립트를 실행하면 환경설정과 서비스가 실행되도록 스크립트를 구성했다.
마무리아무말
끝난것같지만 아직 끝이 아니다. 크롬익스텐션, electron 윈도우 어플리케이션도 이 환경에서 되게 테스트해봐야함.
로깅도 수정해야함. 테스트도 더 해봐야함
이러다 글 또 평생 못올릴까봐 생각난김에 일단 된거 올려버리기
'🌀Full-Stack&Beyond' 카테고리의 다른 글
🐳 Docker 네트워크 충돌 문제 및 해결 방법 (네트워크 개념을 곁들인) (0) | 2025.02.26 |
---|---|
네트워크 기초 완벽 정리: 패킷, 라우팅, 게이트웨이, 네트워크 인터페이스 (0) | 2025.02.26 |
서브넷 마스크와 네트워크 대역 쉽게 이해하기 (0) | 2025.02.26 |
인터넷에 www.naver.com을 입력하면 무슨 일이 일어날까? 🌍 (0) | 2025.02.23 |
무인 주류 판매 플랫폼 구축기 (그런데 이제 인공지능을 곁들인) (0) | 2023.07.03 |
참고) 모든 예시 코드는 블로그 포스팅용으로 재구성되었습니다.
1. 프로젝트 개요
기존 서비스는 다음과 같은 AWS 서비스들을 활용하고 있었다.
- 프론트엔드
- 백엔드
- S3: 파일 저장소
- CloudFront: CDN 및 이미지 서빙
- Lambda server: 마이크로 서버
- RDS: 데이터베이스
- CloudWatch: 로깅
이를 On-Premise 환경에서 구현하기 위해 Docker와 Nginx를 활용한 마이그레이션을 진행했다.
아키텍쳐 설계
2.1 Docker Compose 기반 마이크로서비스 구성
version: "3.8"
services:
my-nginx:
build: ./nginx
container_name: my-nginx
restart: always
depends_on:
- frontend
- backend
- micro-server
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/certs:/etc/nginx/certs
- ./nginx/logs:/var/log/nginx
- /data:/data
networks:
- app_network
frontend:
build: ./frontend
container_name: frontend
restart: always
depends_on:
- backend
ports:
- "3000:3000"
networks:
- app_network
backend:
build: ./backend
container_name: backend
restart: always
volumes:
- /data:/data
env_file:
- .env
environment:
- DATABASE_URL=비밀
ports:
- "8000:8000"
depends_on:
- database
networks:
- app_network
database:
image: my-mysql
container_name: database
restart: always
environment:
비밀비밀
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app_network
micro-server:
build: ./micro-server
container_name: micro-server
restart: always
ports:
- "3001:3001"
networks:
- app_network
volumes:
- /data:/data
env_file:
- .env
networks:
app_network:
driver: bridge
volumes:
mysql_data:
다음과 같이 4개의 주요 서비스를 컨테이너로 구성했다.
- Nginx
- Frontend (Next.js)
- Backend (FastAPI)
- Micro Server (Node.js)
- Database (MySQL) - 이건 빌드시 테스트용으로 만든 컨테이너. 실제 온프레미스 환경에서 데이터베이스는 따로 구축했다.
2.2 Nginx를 활용한 서비스 라우팅 구조
http {
upstream backend {
server backend:80; # FastAPI 컨테이너
}
upstream frontend {
server frontend:3000; # Next.js 컨테이너
}
upstream micro-server {
server micro-server:80; # Node.js 컨테이너
}
server {
listen 80;
# Next.js (프론트엔드)
location / {
proxy_pass http://frontend;
}
# ✅ FastAPI (백엔드)
location /api/ {
proxy_pass http://backend; # ✅ FastAPI 컨테이너로 프록시
}
# Micro Server (Node.js)
location /micro-server/ {
proxy_pass http://micro-server/;
}
location /data/ {
alias /data/;
autoindex off;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
# 이미지 파일에 대한 설정
location ~* \.(jpg|jpeg|png|gif|ico|webp)$ {
expires 1d;
add_header Cache-Control "public, no-transform";
}
}
}
}
B --> D[내부 Nginx :80 백엔드] --> E[FastAPI :8000]
B --> F[내부 Nginx :80 micro-server] --> G[Node.js :3001]
- Frontend (Next.js)
- SSR 애플리케이션으로 직접 3000 포트 노출
- Next 자체로 프로덕션 서버로서 기능을 포함해 내부 Nginx 불필요
- Backend (FastAPI)
- 내부 Nginx가 8000 포트의 FastAPI 서버로 프록시
- 보안 및 로드밸런싱 설정 가능
- 프로세스 관리로 supervisor 사용
- Micro Server (Node.js)
- 내부 Nginx가 3001 포트의 Node.js 서버로 프록시
- 프로세스 관리로 supervisor 사용
요청 흐름 예시:
- API 요청 (/api)
- 클라이언트 → 외부 Nginx(:80) → Backend 컨테이너(:80) → 내부 Nginx → FastAPI(:8000)
- micro server 요청 (/micro-server)
- 클라이언트 → 외부 Nginx(:80) → micro server 컨테이너(:80) → 내부 Nginx → Node.js(:3001)
- 웹페이지 요청 (/)
- 클라이언트 → 외부 Nginx(:80) → Frontend 컨테이너(:3000) → Next.js SSR
3. AWS 서비스 대체
3.1 파일 시스템 (S3/CloudFront 대체)
AWS S3와 CloudFront를 대체하기 위해 Nginx와 로컬 파일시스템을 활용한 구조를 설계해 온프레미스 환경에서도 동일한 파일 저장 및 서빙 기능을 제공한다.
Nginx 설정을 통한 정적 파일 서빙
http {
# ... 기존 설정 ...
server {
listen 80;
# 정적 파일 서빙을 위한 location 설정
location /data/ {
alias /data/;
autoindex off;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
# 이미지 파일에 대한 캐싱 설정
location ~* \.(jpg|jpeg|png|gif|ico|webp)$ {
expires 1d;
add_header Cache-Control "public, no-transform";
}
}
}
}
Nginx의 정적 파일 서빙 기능을 활용하여 로컬 파일시스템에 저장된 파일들을 HTTP를 통해 제공한다. /data 디렉토리의 파일들을 웹에서 접근 가능하도록 설정하며, 이는 CloudFront의 CDN 기능을 대체하는 역할을 한다.
또한 이미지 서빙 시 URL에 불필요한 쿼리 파라미터가 포함되는 경우가 있어서 try_files $uri =404 를 사용해 동일한 이미지에 대해 다른 쿼리 파라미터가 붙더라도 하나의 캐시로 처리했다.
Docker 볼륨 마운트
services:
my-nginx:
volumes:
- /data:/data # 파일 저장소를 nginx 컨테이너에 마운트
backend:
volumes:
- /data:/data
파일 저장소를 nginx와 백엔드 서비스가 공유할 수 있도록 Docker 볼륨을 구성해준다.
파일 업로드 및 접근 로직
async def upload_file(file: UploadFile, file_path: str) -> dict:
try:
content = await file.read()
if STAGE == 'on-premise':
# URL 형태의 경로를 파일 시스템 경로로 변환
if file_path.startswith(IMAGE_DOMAIN_URL):
file_path = file_path.replace(IMAGE_DOMAIN_URL, '')
# 온프레미스 환경: 로컬 파일시스템에 저장
DATA_DIR = os.environ.get('FILE_DIR') # /data 디렉토리 경로
full_path = os.path.join(DATA_DIR, file_path)
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'wb') as f:
f.write(content)
# nginx를 통한 파일 접근 URL 생성
file_url = f"{IMAGE_DOMAIN_URL}/{file_path}"
else:
# AWS 환경: S3에 업로드 후 CloudFront URL 반환
s3_client.put_object(
Bucket=aws_bucket_name,
Key=file_path,
Body=content
)
file_url = f"{CLOUDFRONT_URL}/{file_path}"
return {
"filename": file.filename,
"location": file_url # 브라우저에서 접근 가능한 URL 반환
}
파일 업로드 로직에서는 환경(AWS/온프레미스)에 따라 저장소를 분기처리하면서도, 일관된 URL 구조를 반환하도록 구현했다.
온프레미스 환경에서는 IMAGE_DOMAIN_URL을 기반으로 한 파일 경로를 생성하고, AWS 환경에서는 CloudFront URL을 사용한다. 이를 통해 프론트엔드에서는 환경과 관계없이 동일한 방식으로 파일에 접근할 수 있다.
예를 들어 동일한 이미지 파일이
와 같이 서로 다른 환경에서도 일관된 구조로 접근 가능하게 했다.
3.2 Micro Server (Lambda 대체)
// Lambda 핸들러를 Express 엔드포인트로 변환
app.post('/micro-server', async (req, res) => {
try {
const result = await handler(req.body);
if (result.statusCode === 200) {
res.setHeader('Content-Type', result.headers['Content-Type']);
// 로직로직
res.send(결과);
}
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
AWS Lambda로 구현되어 있던 기능을 독립적인 Node.js 서비스로 마이그레이션했다. Docker 컨테이너로 실행되며, REST API 엔드포인트를 통해 기존 Lambda 함수와 동일한 기능을 제공한다.
4. 배포 프로세스
4.1 빌드 프로세스 (온프레미스 환경으로 가져가기 전, 도커 이미지 빌드 과정)
빌드 과정
- 도커 rpm 설치 파일 다운로드
- 각 서비스(Frontend, Backend, Micro Server)를 Docker 이미지로 빌드
- 온프레미스 환경 배포를 위해 이미지를 tar 파일로 저장 (외부 네트워크 접근 차단된 환경)
이렇게 스크립트화 해서 한번에 배포에 필요한 파일들을 만들게 해두었다.
4.2 배포 자동화
1. 데이터베이스 배포 스크립트
미리 저장된 docker rpm 패키지로 도커 설치
Docker 서비스 활성화
Docker 그룹 생성 및 사용자 권한 설정
.tar파일로부터 도커 이미지 로드
MySQL 컨테이너 실행
2. 어플리케이션 서비스 배포 스크립트
미리 저장된 docker rpm 패키지로 도커 설치
Docker 서비스 활성화
Docker 그룹 생성 및 사용자 권한 설정
docker compose 설정
.tar파일로부터 도커 이미지 로드
docker compose를 통해 서비스 시작
대강 이런식으로 온프레미스 각 서버에서 스크립트를 실행하면 환경설정과 서비스가 실행되도록 스크립트를 구성했다.
마무리아무말
끝난것같지만 아직 끝이 아니다. 크롬익스텐션, electron 윈도우 어플리케이션도 이 환경에서 되게 테스트해봐야함.
로깅도 수정해야함. 테스트도 더 해봐야함
이러다 글 또 평생 못올릴까봐 생각난김에 일단 된거 올려버리기
'🌀Full-Stack&Beyond' 카테고리의 다른 글
🐳 Docker 네트워크 충돌 문제 및 해결 방법 (네트워크 개념을 곁들인) (0) | 2025.02.26 |
---|---|
네트워크 기초 완벽 정리: 패킷, 라우팅, 게이트웨이, 네트워크 인터페이스 (0) | 2025.02.26 |
서브넷 마스크와 네트워크 대역 쉽게 이해하기 (0) | 2025.02.26 |
인터넷에 www.naver.com을 입력하면 무슨 일이 일어날까? 🌍 (0) | 2025.02.23 |
무인 주류 판매 플랫폼 구축기 (그런데 이제 인공지능을 곁들인) (0) | 2023.07.03 |