raw
DevOps

Docker-Compose로 여러 프로젝트 배포해보자

2025.04.13·11분
Docker-Compose로 여러 프로젝트 배포해보자

Docker compose

Docker Compse 는 다중 컨테이너 애플리케이션이다. 공유 할 수 있도록 개발된 도구로 단일 명령을 사용하여 모두 실행 또는 종료할 수 있도록 개발된 도구다.

언제 사용하나요?

개발 환경을 표준화

팀 구성원이 수동으로 개발 환경을 정비하면 시간이 너무 오래걸린다. 또한 실수 비용도 커지기 때문에, Docker Compose를 사용하여 설정 파일을 전달하면, 개발 환경의 세부 사항을 걱정하지 않고 하나의 명령으로 환경을 정비 할 수 있다. 작업도 간소화 가능

테스트 환경의 자동화

CI/CD 할 경우, end to end 테스트를 자동화하려면 이상적으로 테스트마다 독립적인 환경을 제공하는 것이 좋은데, Docker Compose는 테스트 환경을 명령어 1개로 시작 가능하다

🔥단일 호스트 배포🔥

운영 환경이 단일 서버일 경우 Docker Compose를 사용하여 배포 가능하다. 공부가 목적이나 서버 비용을 절약하는 방법 중 하나이다. pretier를 사용 중이거나 여러 서버를 증설할 필요가 없는 분은 단일 호스트 배포를 통해 여러 프로젝트를 할 수 있는 것이다.

Docker-Compose로 여러 프로젝트 배포해보자

그림은 위처럼 여러개의 컨테이너가 네트워크에 소속되어 있는 모습이고, 임의로 컨테이너를 네트워크에 연결하기 위해서는 docker network connect network container 명령을 사용 가능하다 그러면 해당 네트워크가 가진 private IP 대역의 IP 중 하나를 container가 할당 받는다.

단일 호스트 배포의 흐름

나는 우선 두개의 프로젝트 각각 도메인을 설정했다. 하나의 EC2route53 으로 연결 해줬다.

사용자가 도메인A나 도메인B에 접속할 때 흐름

  1. DNS 확인: 사용자가 브라우저에 URL(예: 도메인A)을 입력합니다. DNS 서버가 해당 도메인을 EC2 인스턴스의 공인 IP 주소로 변환합니다.

  2. HTTP 요청 수신: 사용자의 요청이 EC2 인스턴스의 80포트(HTTP)로 전송됩니다. EC2의 포트 80은 nginx-proxy 컨테이너에 바인딩되어 있습니다.

  3. Nginx 프록시의 요청 처리: nginx-proxy 컨테이너는 요청 헤더의 Host 필드를 확인합니다. 해당 도메인(csmorning.co.kr 또는 kidsbugs.co.kr)에 맞는 설정을 찾습니다. HTTP 요청을 HTTPS로 리다이렉트합니다(301 상태 코드).

  4. HTTPS 리다이렉트 및 암호화 연결: 브라우저는 HTTPS 요청을 EC2 인스턴스의 443포트로 다시 전송합니다. nginx-proxy는 도메인에 맞는 SSL 인증서를 사용하여 암호화된 연결을 설정합니다.

  5. 내부 서비스 라우팅: nginx-proxy는 요청을 적절한 내부 서비스로 전달합니다: 기본 경로('/') 요청은 해당 도메인의 landing-page 컨테이너로 전달 '/api' 경로 요청은 해당 도메인의 api-server 컨테이너로 전달

6.프론트엔드 처리(landing-page 컨테이너): React 기반의 프론트엔드 애플리케이션이 요청을 처리합니다. 정적 자원(HTML, CSS, JavaScript)이 사용자 브라우저로 전송됩니다. 브라우저에서 React 앱이 렌더링됩니다.

7.API 요청 처리(필요한 경우): 프론트엔드 앱이 데이터가 필요한 경우 /api 엔드포인트로 요청합니다. 이 요청은 다시 nginx-proxy를 통해 api-server 컨테이너로 전달됩니다. api-server는 요청을 처리하고(필요시 데이터베이스 조회), 응답을 반환합니다. 프론트엔드는 이 데이터로 화면을 업데이트합니다.

text
1사용자 → 브라우저 → DNS → EC2 IP 주소
2
3HTTP 요청 (80포트)
4
5nginx-proxy 컨테이너 → HTTPS 리다이렉트
6
7HTTPS 요청 (443포트)
8
9nginx-proxy (SSL 암호화, 도메인 확인)
10
11 ├─→ landing-page 컨테이너 (/) → React 앱 렌더링
12 │ ↓
13 │ 사용자에게 UI 표시
14
15 └─→ api-server 컨테이너 (/api) → 데이터 처리
16
17 데이터베이스 조회/업데이트 (필요시)
18
19 JSON 응답 반환 → 프론트엔드 업데이트

EC2 인스턴스와 Docker 구조

Docker-Compose로 여러 프로젝트 배포해보자

csmornig.co.krkidsbugs.co.kr 두개의 도메인으로 요청 처리 흐름 시퀀스 다이어그램으로 표현해봤다.

루트에서 nginx-proxy를 통해 컨테이너가 호스트 네트워크 모드로 실행되면서 모든 외부 요청을 받아서 적절한 내부 컨테이너로 라우팅을 하는 것이다.

공통 리소스(SSL 처리, 라우팅)는 중앙 nginx-proxy를 통해 관리한다.

장단점

장점

  • 도메인 기반 멀티 호스팅 단일 서버에서 여러 도메인과 서비스를 호스팅할 수 있습니다. 각 도메인별로 완전히 독립적인 애플리케이션을 실행할 수 있습니다.

  • 리소스 효율성 여러 VM이나 서버를 사용하는 대신 하나의 EC2 인스턴스에서 여러 서비스를 실행함으로써 비용을 절감합니다. 하드웨어 자원(CPU, 메모리)을 효율적으로 공유합니다.

  • 네트워크 격리 각 프로젝트가 독립적인 Docker 네트워크에서 실행되어 보안이 향상됩니다. 한 서비스의 문제가 다른 서비스에 미치는 영향을 최소화합니다.

  • SSL 관리 중앙화 단일 nginx-proxy에서 모든 도메인의 SSL 인증서를 관리하므로 구성이 간소화됩니다. Let's Encrypt 인증서 갱신 등의 작업을 한 곳에서 처리할 수 있습니다.

  • 유지보수 용이성 라우팅 규칙을 nginx-proxy의 설정 파일 하나에서 관리할 수 있습니다. 새 서비스 추가 시 기존 서비스를 중단하지 않고도 구성할 수 있습니다.

단점

  • 단일 장애점 (Single Point of Failure) nginx-proxy에 문제가 발생하면 모든 서비스가 영향을 받습니다. 하나의 EC2 인스턴스에 의존하므로 인스턴스 장애 시 모든 서비스가 중단됩니다.

  • 리소스 경합 여러 서비스가 동일한 물리적 자원을 공유하므로 한 서비스의 과도한 자원 사용이 다른 서비스의 성능에 영향을 줄 수 있습니다. 특히 트래픽이 많은 시간대에 문제가 될 수 있습니다.

  • 복잡성 증가 단일 서버에서 여러 서비스를 관리하는 것은 개별 서버보다 구성이 복잡합니다. 네트워크 설정, 포트 관리, 라우팅 규칙을 이해하고 관리해야 합니다.

  • 확장성 제한 수직적 확장(더 큰 EC2 인스턴스로 업그레이드)은 가능하지만, 수평적 확장(여러 서버에 분산)이 어렵습니다. 트래픽이 크게 증가하면 결국 단일 인스턴스의 한계에 도달할 수 있습니다.

  • 보안 우려 호스트 네트워크 모드는 컨테이너에 더 많은 네트워크 액세스 권한을 부여하므로 보안 격리가 약화될 수 있습니다. 모든 서비스가 동일한 인스턴스에 있으므로 한 서비스의 보안 취약점이 다른 서비스에 영향을 줄 수 있습니다.

문제

github action으로 ci/cd 스크립트 작성 시 컨테이너 간 네트워크 연결에서 타이밍 이슈를 조심해야한다

ubuto
1name: Deploy Kidsbugs
2
3on:
4 push:
5 branches: [ main ]
6
7jobs:
8 deploy:
9 runs-on: ubuntu-latest
10 steps:
11 - uses: actions/checkout@v3
12
13 - name: Set up SSH
14 uses: webfactory/ssh-agent@v0.7.0
15 with:
16 ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
17
18 - name: Add host key to known_hosts
19 run: |
20 mkdir -p ~/.ssh
21 ssh-keyscan ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
22
23 - name: Deploy to server
24 run: |
25 ssh ${{ secrets.SSH_USER }}@${{ secrets.SERVER_IP }} << 'EOF'
26 cd ~/kidsbugs
27 git pull origin main
28 docker-compose down
29 docker-compose build
30 docker-compose up -d
31 # 잠시 기다린 후 nginx-proxy 재시작
32 sleep 10
33 cd ~/nginx-proxy
34 docker-compose restart
35 EOF

이 처럼 sleep 을 추가해서 잠시 기다리면 간단하면서도 효과적으로 해결할 수 있다.

api 수요가 없는 랜딩페이지 작업이 제일 효율이 좋은 것 같다.