개인 프로젝트 | 02 _ 홈서버 구축하기 with 도커

2025. 1. 3. 11:53·프로젝트

1. 도커란?

서버를 가상화하는 방법에는 여러가지가 있는데, 그 중에서도 반가상화에 해당하는 것이 도커이다.
반가상화란 컨테이너 가상화라고도 하며, 운영체제를 두고 그 위에서 여러 가상환경을 운용하는 방식이다. OS 커널을 여러 가상환경이 공유하기 때문에, 각각의 host OS와 커널을 사용하는 전가상화 등의 방식보다 서비스 실행 속도가 빠르다

도커를 사용하는 이유

하나의 물리적 서버를 나눠서 여러개의 서버처럼 작동하게 만드는 것을 도와준다.

  • 안전성 강화
  • 항상 같은 환경 구현 가능
  • 환경 구축의 편리성
  • 환경의 일관성
  • 여러 컨테이너 운영을 통해 하나의 서버로 여러개의 작업 가능 (여러 서버를 쓰는 효과)
  • 가볍고 빠름. 컨테이너 각각이 host OS를 사용하는 것이 아니고, 하나의 host OS 를 공유하기 때문에 실행 속도가 빠르고 컨테이너의 용량이 가벼움

 

2. Docker와 프로젝트 폴더 구조 정리

  • C:\Users\YourName\MyAppName 폴더가 존재한다
  • 폴더 안에 docker-compose.yml 파일과 nginx.conf 파일을 생성한다
    • docker-compose.yml : 이를 통해 폴더의 컨테이너를 생성 관리가능해진다

 

3. Docker Desktop 으로 한번에 설치하기

Docker를 사용할 때에 주로 함께 사용되는 것들이 있다. 이게 없으면 Docker를 쓸 수 없는 건 아니지만.. 굳이 그런걸 안쓸거면 도커를 왜쓰는지 모를? 편리한 도구들을 함께 설치해준다.
한마디로 도커를 사용하는 분들이 대부분 사용하는, Docker가 제공하는 서비스들을 한번에 설치받을 수 있는 것이 Docker Desktop이다.
여기에는 많은 도커의 서비스들이 포함되어있는데, 그 중에 하나가 Docker Compose 이다.

 

4. Docker Compose 사용하기

Docker Compose 란?

도커 내에서 돌아가는 각각의 도구들(nginx, DB 등)은 개별 이미지로 만들어져서 각각의 컨테이너에서 돌아간다. 근데 하나의 앱을 만들 때 보통 여러 개의 도구가 필요하다(웹서버, API 서버, DB, CI/CD 등). Docker Compose는 이런 여러 컨테이너들을 한번에 관리할 수 있게 해주는 도커의 컨테이너 관리 도구이다. 이때 모든 컨테이너의 설정(사용할 이미지, 포트, 볼륨 등)은 docker-compose.yml이라는 하나의 파일에서 쉽게 관리할 수 있다.

 

사용되는 이유

보통 하나의 앱을 운용하려면 여러 개의 컨테이너가 필요하다.

(ex. DB랑 웹서버만 운영해도 하나의 서비스(앱)를 구동하기 위해 사용되는 도커 컨테이너(=도커 서비스)가 2개)

 

근데 이 컨테이너들을 하나하나 따로 명령어로 실행하거나 테스트하면 운영에 비효율적이다. 그래서 하나의 앱에 필요한 모든 컨테이너들을 한꺼번에 묶어서 실행하고 테스트할 수 있게 만든 게 Docker Compose이다.

 

Docker Compose를 이용하면, docker-compose.yml 이라는 파일로 각 컨테이너에 대한 설정, 컨테이너 간의 네트워크 설정, 컨테이너 간 실행 순서 지정까지 전부 한 곳에서 관리할 수 있다. 예를 들어 DB가 완전히 실행된 다음에 웹서버가 시작되도록 순서를 정할 수도 있다. 

 

docker-compose.yml 파일이란?

이 파일은 한마디로 명세서이다.

컨테이너이름 , 필요한 이미지 이름, 이미지 버전, 이 서비스와 연결되는 통신, db 등 다른 컨테이너에 대한 연결 설정, 포트 설정 등을 작성해두게 된다.

 

이를 통해 일일이 컨테이너를 생성하지 않고, 한번 파일을 써두고 명령어를 통해 컨테이너를 생성할 수 있게 된다. 또한 하나의 파일에서 명세를 적어두니 프로젝트에 사용되는 여러 컨테이너들에 대한 관리가 쉬워진다. 그냥 텍스트파일을 수정하면 되기 때문이다.

docker-compose.yaml 은 기본적으로 앱 별로 구성한다. 

 

docker-compose.yml 파일과 명령어로 컨테이너 생성하기

  • 이미지를 다운받으면, 해당 이미지는 실제 파일 형태로 존재하는 것이 아닌, Docker가 관리해주는 이미지 저장소에 따로 저장되어있다.
  • 다운받은 이미지를 docker-compose.yml 이라는 명세서에 적어두고 docker-compose up —build (=Docker Compose를 실행시킴) 라는 명령어를 실행하면 필요한 이미지들에 대한 각각의 컨테이너가 생성된다.

 

+) Docker Swarm 이란?

도커에 대해 처음 검색하다보면 Docker Swarm을 이용해 홈서버를 구축한다는 이야기들이 많이 보인다.
또 Docker Swarm이랑 Docker Compose이랑 같이 언급되는경우도 많다. 이 둘은 비슷해보이만 엄연히 다르다.
Docker Swarm은 여러 서버 중에 사용량이 적은 서버에 컨테이너를 할당하는 식으로 로드밸런싱을 해주는 것이다. 이는 확장에 용이하여, 사용자가 갑자기 늘어나는 서비스나 성장가도를 달리는 서비스에 유용하다. Docker Swarm에서도 클러스터로 컨테이너들을 묶어서 여러 컨테이너를 한번에 실행할 수도 있다.
한마디로 Docker Swarm은 쿠버네티스같은 오케스트레이션 도구이다.

Docker Compose는 단일 서버에서 실행된다. 이는 하나의 서비스를 위해 구동되는 여러 컨테이너를 묶어서 관리해주는 역할을 한다.
나는 1인개발에 서버도 N100 한대이니 Docker Compose를 사용하는것이 적절해보인다. 서버가 여러대가 필요할 정도로 트래픽이 늘어나면 그때 Docker Swarm을 도입하거나 클라우드 서비스를 사용하는 방향으로 조정할 생각이다.

 

5. 도커 네트워크 사용하기

참고 : https://docs.docker.com/compose/how-tos/networking/

 

Docker Compose 에서 네트워크 기본 구조

네트워크의 역할

가상 인터페이스를 만들고 호스트 OS의 IP 테이블로 패킷 라우팅을 관리함

  • 외부에서 들어오는 요청을 도커의 특정 컨테이너의 포트로 전달함
  • 내부 컨테이너간 통신을 가능하게 함 

 

docker-compose 의 기본 네트워크

  • docker-compose가 실행되면, 사용할 네트워크를 따로 지정하지 않아도 자동으로 기본 bridge 네트워크가 생성됨
    • 생성되는 기본 네트워크 이름: [docker-compose.yaml가 위치한 디렉토리명]_default
    • ex. /home/user/my_project/ → my_project_default
  • docker-compose가 생성하는 기본 bridge 네트워크는 사용자 정의 bridge 네트워크임 (도커의 기본 bridge 네트워크와는 다름)
  • docker-compose.yml에 명시된 컨테이너 중 네트워크를 별도로 설정해주지 않은 컨테이너는 이 기본 네트워크에 연결됨
  • 각 컨테이너는 자신이 속한 네트워크 내부에서 private IP를 할당받음
  • 도커 네트워크는 기본적으로 외부에서 바로 접근 불가능
  • 각 컨테이너는 서로 컨테이너 이름으로 통신

 

사용자 정의 네트워크

기본 네트워크 외에도 docker-compose.yml에서 추가로 사용자 정의 네트워크를 생성하여 사용할 수 있다.

파일의 최상단 또는 하단에 networks 섹션을 추가하여 원하는 네트워크를 정의하면 된다.

 

[docker-compose.yaml 파일 설정 예시]

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

이렇게 사용하면 세분화된 네트워크 격리와 관리가 가능하다.

얻을 수 있는 이점 :

  • 보안강화
  • 환경별 네트워크 분리로 충돌방지

 

외부 클라이언트 요청을 도커 내부 네트워크로 연결하기

바로 위에서 본것처럼, 도커 내부 네트워크는 기본적으로 외부에서 바로 접근 불가능하다.

그래서 클라이언트 요청이 도커 네트워크에 연결되어있는 웹서버 컨테이너로 정상적으로 도착하려면, 호스트 시스템의 특정 포트를 컨테이너의 포트와 연결(포트 바인딩)해야한다. 이를 통해 외부 요청이 호스트를 거쳐 컨테이너로 전달될 수 있다. 이는 docker-compose.yaml 파일에서 설정 가능하다. 

 

[docker-compose.yaml 파일 설정 예시]

version: '3.8'

services:
  web:
    image: nginx:latest
    ports:
      - "8080:80"
  • 의미 : 호스트의 포트 8080과 컨테이너의 포트 80을 연결(바인딩)함
    • Nginx 컨테이너가 내부적으로 80번 포트에서 HTTP 서버를 실행하는 상황
    • Docker 데몬은 호스트 OS의 8080번 포트를 컨테이너의 80번 포트와 연결
    • 호스트의 8080번 포트에 대한 모든 트래픽이 컨테이너의 프라이빗 IP 주소와 80번 포트로 전달됨

 

이처럼 설정하면 외부 클라이언트 요청은 오직 Nginx를 통해 내부 서비스(도커 컨테이너들)로 라우팅된다. Nginx가 리버스 프록시 서버 역할을 하고 있는 것이다. 

  • ex. 외부 요청이 공유기를 통해 홈서버의 고정된 프라이빗 IP(예: http://192.168.x.x:8080)로 전달되면, Docker 데몬이 해당 요청을 캡처하여 설정된 포트 매핑(위 예시에서는 "8080:80")에 따라 Nginx 컨테이너의 80번 포트로 전달

 

컨테이너 간 통신 방식

1. 기본 네트워크를 사용하는 경우 (docker-compose를 사용하지 않아 docker demon에 의해 형성되는 네트워크)

-> 컨테이너간 통신시에 각 컨테이너의 privateIP를 사용해서 직접 통신을 해줘야하며 추천되는 방식이 아니다.

 

 

2. 사용자 정의 네트워크를 사용하는 경우 (docker-compose를 사용하는 경우)

-> Docker의 내장 DNS 기능이 자동으로 활성화되어 컨테이너들이 이름이나 별칭을 통해 서로를 식별하고 통신할 수 있다. (https://docs.docker.com/engine/network/drivers/bridge/)

그러므로 컨테이너간 통신이 필요한 경우 docker-compose를 사용하는 것이 편리하다.

 


정리하자면, 도커에서 하나의 사용자 정의 네트워크 안에 있는 컨테이너끼리는 서로의 컨테이너 이름을 통해 통신할 수 있다. 이는 Docker Compose가 각 컨테이너에 이름을 부여하고 이를 DNS 해석을 통해 컨테이너의 IP 주소와 매핑하여 호스트 이름처럼 사용할 수 있도록 지원하는 덕분이다.

 

이 방식은 컨테이너 재구동 시 컨테이너에 할당된 privateIP 주소가 바뀌는 문제를 해결하며 컨테이너간 통신을 간편하게 만들어준다. 또한 도커의 '동일한 설정으로 항상 다시 실행 가능한 서비스'라는 철학이 효과적으로 동작하게 돕는다고 생각한다. 

 

 

여러 컨테이너가 하나의 사용자 정의 네트워크를 사용하도록 설정하기

  • docker-compose.yaml에서 컨테이너들을 같은 네트워크로 설정가능

[docker-compose.yaml 파일 설정 예시]

version: '3'

services:
  app:
    image: my_app_image
    networks:
      - my_bridge_network
  db:
    image: my_db_image
    networks:
      - my_bridge_network
      
networks:
  my_bridge_network:
    driver: bridge
  • app, db 컨테이너는 my_bridge_network라는 사용자 정의 브리지 네트워크에 연결된다.
  • 같은 네트워크의 컨테이너들은 외부 포트 노출 없이 내부 통신 가능
    • 호스트의 포트와 컨테이너의 포트를 매핑할 필요가 없다. 컨테이너 간의 통신은 내부 네트워크를 통해 직접 이루어지기 때문에, 각자의 내부 포트를 통해 직접 통신 가능하다.
    • Docker의 내장 DNS가 서비스 이름(또는 별칭)을 해당 컨테이너의 privateIP로 해석하여, 네트워크 내에서 패킷이 직접 전달되도록 지원한다.

 

6. 도커를 사용하는 홈서버 아키텍처

[지금까지 내용 정리]

1. window위에 hostOS인 리눅스를 깔고 그 위에 도커를 설치한다.(Docker Desktop을 통해)
2. 도커에서 여러 컨테이너들을 만들어 독립된 환경에서 각 서비스를 운용한다.

3. 하나의 서비스에 사용되는 여러 컨테이너(api서버, db 컨테이너 등)들은 docker-compose.yml 파일을 통해 묶어서 관리해준다.

4. 각 컨테이너들은 하나의 사용자 정의 도커 네트워크로 묶어, 도커 내부의 가상 네트워크에서 서로 이름으로 통신할 수 있도록 한다.

 
전체 통신 구조도

인터넷 ↔ 공유기 ↔ 리버스 프록시 서버(Nginx) ↔ fastAPI 서버 ↔ MySQL 디비

 

A. 인터넷(외부 클라이언트)과 Nginx 사이의 통신
= https + SSL 인증서 를 사용하여 통신

  • 클라이언트와의 통신에서는 민감한 데이터 등을 주고받을 수 있기 때문에 https 통신이 필요하다.
    • 도커에서 nginx.conf 파일을 수정하여 https만 사용하도록 설정 가능하다.
  • let's encrypt 에서 무료 인증서(3개월마다 자동 갱신 가능)를 발급받아 nginx SSL에 적용해주면 된다.
    • SSL 인증서는 클라이언트가 나의 도메인으로 접속할 때, 이 접속이 믿을 수 있는 정상적인 서버와의 연결이라는 걸 인증해주고 통신을 암호화해주는 역할을 한다.

 

B. 프록시 서버와 fastAPI 서버 사이의 통신

= 컨테이너 이름을 사용하여 통신

 

C. fastAPI 서버와 데이터베이스 사이의 통신
= 컨테이너 이름을 사용하여 통신

 

Nginx, fastAPI 서버, 디비 모두 하나의 네트워크로 연결하여 사용하면 된다.

 

 

설치할 이미지

이미지 = 말그대로 컨테이너를 실행하기 위한 기본 템플릿이다. 

  1. nginx
  2. mysql
  3. python 환경_ fastapi를 사용하기 위한

 

이미지가 사용할 컨테이너 생성하기

Docker-Compose를 통해 필요한 컨테이너를 쉽게 생성해보자. 

  • docker-compose.yml 파일 예시
version: '3.8'
services:
  nginx:
    image: nginx
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/ssl
    networks:
      - frontend
      - backend

  mysql:
    image: mysql
    container_name: mysql-db
    environment:
      MYSQL_ROOT_PASSWORD: yourpassword
      MYSQL_DATABASE: yourdb
      MYSQL_USER: youruser
      MYSQL_PASSWORD: yourpassword
    # 외부 포트 노출 없음
    networks:
      - backend
    volumes:
      - mysql_data:/var/lib/mysql

  fastapi:
    build:
      context: .
    container_name: fastapi-server
    # 외부 포트 노출 없음
    networks:
      - backend
    depends_on:
      - mysql

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  mysql_data:
  • 파일 실행을 통해 컨테이너 생성
docker-compose up -d

 

 

7. nginx 를 reverse proxy 서버로 사용하기

왜 reverse proxy 서버를 사용하는가?

reverse proxy 서버는 인터넷에서 들어오는 요청을 집 공유기 내부 네트워크의 홈서버로 전달하는 역할을 한다. 이를 통해 트래픽 라우팅, 보안 강화, 서브도메인 관리 등의 기능을 수행할 수 있다.

 

이때, 외부에서 홈서버로의 접속을 위해서는 가비아의 DDNS(Dynamic DNS), 공유기의 포트 포워딩, 도커의 포트바인딩 설정이 필수적이다. 왜 그래야할까? 사실 서비스 사용자들의 요청이 어떻게 전달되는지 생각해보면 당연한 이야기이다. 

 

외부 요청이 홈서버까지 오는 과정

1. 사용자가 도메인으로 접속/요청

2. 도메인 → 공유기 공용 IP로 요청 전달됨

3. 공유기 → 내부 네트워크의 홈서버로 전달됨

4. 홈서버 → Docker의 Nginx로 전달됨

5. Nginx → 각 서비스 컨테이너로 라우팅

 

우선 인터넷 통신에 대해 간단히 짚고 넘어가면.. 인터넷 통신에는 크게 두 종류의 IP가 사용된다

  • 공용 IP: ISP가 제공하는 인터넷상의 고유한 주소로, 외부와 통신할 때 사용
  • 사설 IP: 192.168.x.x와 같이 내부 네트워크에서만 사용되는 주소. 공용IP의 부족을 해결하기 위해 등장

외부에서는 사설 IP를 가진 내부 기기에 직접 접근할 수 없다. 이때, NAT(Network Address Translation)는 이 두 IP 체계를 이어주는 기술이다. 공유기가 NAT를 통해 내부 기기들의 사설 IP를 하나의 공용 IP로 변환해서 인터넷과 통신하게 해준다. 

 

흠 그러면 공유기의 공용 IP를 통해 접근을 하면 될 것 같다.

그런데 여기에는 3가지 문제가 있다.

 

1. 가정용 인터넷의 공유기에는 동적 공용 IP가 할당된다는 것이다. 즉, IP가 계속 바뀐다는 것이다. (고정시킬 방법은 없다)

당연하게도 도메인에 처음 연결해둔 공유기 공용 ip와 시간이 지난 후의 바뀐 공유기 ip가 서로 일치하지 않으므로 올바르게 우리집 공유기로 트래픽을 보내지 못하는 문제가 발생한다.

2. 공유기가 사설망 내부의 여러 기기 중 홈서버를 어떻게 찾아야하느냐 하는 것이다. 

3. 서버컴퓨터에서 도커로 요청을 어떻게 전달할 것이냐 하는 것이다. 

 

첫 번째 문제 해결 : 가비아 DDNS 사용

 우리는 이 문제를 가비아의 DDNS(Dynamic DNS) 기능을 활용하여 아주 쉽게 해결할 수 있다. (꼭 가비아가 아니어도 된다)

( DDNS : IP 변경 감지 → 도메인 연결 자동 업데이트 )

그래서 공유기 ip가 변경되면 도메인을 관리하는 가비아 측에서 이를 감지하고, 해당 ip 로 도메인 연결을 바꿔주도록 설정하는 것이다.

 

이제 외부의 요청이 안정적으로 공유기 ip를 찾아 들어올 수 있게 되었다.

 

두 번째 문제 해결 : 공유기 포트포워딩 설정

우선, 사설망 내에서 홈서버에 할당된 privateIP를 고정해둬야한다. 이는 공유기 설정에서 제공하는 "MAC 주소를 특정 ip에 고정 할당하기" 기능을 사용하면 간단히 해결된다.

 

그럼 그 다음, 이 ip를 가진 기기의 몇번 포트로 접근해야할까? 그때 포트포워딩이라는 것이 나온다. 개념은 역시 단순하다. 

특정 포트로 들어온 외부 요청을 홈서버의 특정 포트로 전달하도록 설정한다는 것이다.

ex. 외부에서 공유기의 80/443 포트로 들어오는 요청을 내부 홈서버(고정되어있는 privateIP)의 특정 포트로 전달

 

이 포트포워딩 설정도 간단하다. 공유기 설정에서 포트포워딩 설정란이 있다. 그 부분을 설정해주면 된다. 방법은 인터넷에 잘 나와있다!

 

이제 외부 요청이 도메인 → 공유기 공용 IP → 서버 컴퓨터 host OS 까지 잘 전달되게 되었다 😀

 

근데 아직 한 단계가 더 남았다.

도커 컨테이너 내부의 Nginx가 요청을 처리하려면, host OS가 해당 요청을 컨테이너가 노출한 포트로 전달해야 한다.

이때 docker-compose.yml에서 포트 바인딩(port mapping)을 설정하면 해결된다.

 

세 번째 문제 해결 : 도커에서 포트 바인딩,  nginx.conf 설정

1. docker-compose.yml 을 수정하여 포트 바인딩

포트 바인딩 : 호스트 OS의 특정 포트를 도커 컨테이너의 특정 포트와 연결

  • 이 설정을 통해, hostOS의 특정 포트로 전달된 요청이 도커 컨테이너 내부의 Nginx로 전달됨

[docker-compose.yml 파일 설정 예시]

services:
  nginx:
    image: nginx:latest
    container_name: nginx_proxy
    ports:
      - "80:80"   # 호스트의 포트 80을 컨테이너의 포트 80으로 바인딩
      - "443:443" # 호스트의 포트 443을 컨테이너의 포트 443으로 바인딩
  • 이때 보안을 위해 잘 알려진 포트번호(ex. 80,443 등) 말고 임의의 높은 포트(ex. 8080, 8443, 50000 등)를 사용하는 것이 추천됨
더보기

포트 = 하나의 컴퓨터 내부에서 돌아가는 여러 서비스의 주소

  • nignx가 쓰는 포트 번호가 있고, 백엔드가 돌아가는 컨테이너가 쓰는 포트번호가 있고, MySQL DB가 쓰는 포트 번호가 있음

 

2. 백엔드 컨테이너 등으로 요청 전달

[docker-compose.yml 파일 설정 예시]

version: '3.8'

services:
  web:
    image: nginx:latest
    container_name: nginx_proxy
    ports:
      - "8080:80"
    volumes:
      - ./컴퓨터_내에서_nginx.conf_파일경로:/etc/nginx/nginx.conf:ro 
    depends_on:
      - backend
      - certbot
    networks:
      - my_bridge_network

  backend:
    image: my_backend_image
    container_name: backend_service
    networks:
      - my_bridge_network

  certbot:
    image: certbot/certbot
    container_name: certbot_service
    networks:
      - my_bridge_network

networks:
  my_bridge_network:
    driver: bridge

사용한 디렉티브

  • volumes = 호스트 파일을 컨테이너에서 쓰게하기 위함
  • /etc/nginx/nginx.conf = 컨테이너 내부 경로
  • :ro = read only 
  • depends_on = 서비스의 시작 순서를 제어(ready 상태를 보장하지는 않음)

 

 

[nginx.conf 파일 설정 예시]

nginx.conf 파일을 통해 외부 요청을 각 컨테이너로 라우팅하는 방법을 알아보자

events {
    worker_connections  1024;
}

http {
    # 여러 백엔드 컨테이너를 묶은 그룹
    upstream backend_server {
        # docker-compose.yml에서 정의한 컨테이너 이름을 사용
        server backend_service:80;
        server backend_service:81;
        
        # 업스트림 서버에 대해 유휴 keepalive 연결 유지
        keepalive 4;
    }
    
    server {
    	# /mydomain.com 으로 들어오는 요청을 nginx 80번 포트로 수신 
        listen  80;
        server_name  mydomain.com;  

        # /api 로 들어오는 요청은 backend 컨테이너로 프록시(중계)
        location /api/ {
            proxy_pass http://backend_server/;
            # 연결 유지 활성화
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }

        # /var/www/certbot 으로 들어오는 요청은 certbot_service 컨테이너로 프록시
        location /var/www/certbot/ {
            proxy_pass http://certbot_service:80/;
        }
        
        # 그 외의 요청은 기본적으로 정적 파일 등을 처리하거나 404 처리
        location / {
            try_files $uri $uri/ =404;
        }
    }
}
  • 라우팅 설정하기
    • /api 요청 → 백엔드 컨테이너로 전달
    • /var/www/certbot 요청 → certbot 컨테이너로 전달
    • 그 외 요청 → 404 처리 또는 정적 파일 제공
  • 로드밸런싱 설정하기
    • nginx 리버스 프록시 서버는 업스트림 서버로서 로드밸런싱을 구현할 수 있게 해준다. 그래서 docker swarm 까지 사용하지 않고 docker compose에서 업스트림 그룹 설정으로 백엔드 서버에 대해 로드밸런싱 구성을 해봤다. 
    • Docker의 내부 DNS 기능은 동일한 서비스 이름에 대해 해당 서비스에 연결된 모든 컨테이너의 프라이빗 IP 주소를 반환한다.
      그래서 Nginx 설정에서 업스트림 서버를 단순히 서비스 이름(ex. backend_service)으로 설정해두기만 해도, 여러 백엔드 컨테이너가 자동으로 로드밸런싱 대상이 되어 요청이 분산 처리된다.

 

사용한 디렉티브

  • worker_connections
    • nginx가 동시에 처리할 수 있는 연결(클라이언트 요청)의 수
  • location /api/ 
    • /api/ 로 받은 요청을 업스트림 그룹 backend_servers에 포함된 여러 백엔드 컨테이너로 분산
  • keepalive
    • 업스트림 서버와의 연결을 일정 시간 유지하도록 함. 매 요청마다 새 연결을 생성하는 오버헤드를 줄이고 성능 향상 가능

 

이제 홈서버 기본 인프라 구축이 끝났다 👍🏻

 

reverse proxy 서버 기능 정리

  • router의 역할을 한다 (인터넷에서 들어오는 요청을 내부 네트워크의 특정 서버로 전달)
  • 내 서버 ip를 숨길 수 있다 (보안을 위해 중요한 부분)
    • 요청이 먼저 Reverse Proxy 서버를 거쳐 내부 서버로 전달되므로, 실제 서버 IP가 외부에 노출되지 않는다.
  • coding.com 이라는 나만의 도메인을 하나 구입해서 서브도메인을 마음대로 지정해줄 수 있게 만들어준다
    • mysql.coding.com → MySQL Container 에 접근 == MySQL 서버에 접근
  • docker swarm 없이도 docker compose환경에서도 로드밸런싱 구성 가능하도록 함
    • nginx 업스트림 서버로서의 기능

 

8. 간단한 nginx(reverse proxy server)설정으로 디도스 공격에 대비하기

우리의 소중한 서버가 무서운 디도스공격에 당할 수 있다. 

 

우선 디도스 공격을 크게 2가지로 나눌 수 있다.

1. 짧은 시간에 많은 요청을 보내서 서버를 마비시키는 디도스 공격

2. 연결 시간을 길게하여(request의 커넥션을 오래 유지시켜서) 서버가 정상 사용자의 요청을 처리하지 못하게(다른 ip에서 request를 못하게 공격) 하는 디도스 공격 

 

서버를 지켜야한다. 대비책은 존재한다. 

참고 : https://blog.nginx.org/blog/mitigating-ddos-attacks-with-nginx-and-nginx-plus#limit_req_zone

나름 간단하게 nginx.conf 파일을 통해 해결 가능한 것 같다. 이 부분은 추후에 실제 테스트를 해보고 내용을 보완해야겠다. 일단은 위의 자료를 간단히 정리해보자. 

 

1. nginx 서버에서 요청 속도 제한 걸기 _ 1번에 대한 해결책

[nginx.conf 파일 설정 예시]

# 클라이언트 IP별로 초당 약 0.5 요청(즉, 2초에 한 번, 또는 30 요청/분) 제한
limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m;

server {
    listen       80;
    server_name  mydomain.com;

    location /login.html {
        limit_req zone=one;
        # 추가 proxy 설정 등
    }
    # 다른 location 블록
}

 

사용한 디렉티브 

  • limit_req_zone:
    • $binary_remote_addr(=클라이언트 IP)를 키로 사용하여, one이라는 이름의 10MB 공유 메모리 영역에 요청 상태를 저장
    • rate=30r/m
      분당 30 요청을 허용하는 속도 제한
  • limit_req:
    • /login.html 경로에 대해 위에서 정의한 one zone을 적용하여, 각 클라이언트가 제한된 속도로만 요청을 할 수 있도록 함

 

 

2. nginx 서버에서 요청 수 제한을 걸기 _ 1번에 대한 해결책

[nginx.conf 파일 설정 예시]

# 클라이언트 IP별로 최대 10개의 연결 제한 (10MB의 공유 메모리 사용)
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    listen       80;
    server_name  mydomain.com;

    location /store/ {
        limit_conn addr 10;
        # 추가 proxy 설정 등
    }
    # 다른 location 블록
}
  • 위의 nginx.conf 파일에서 라우팅하는 부분에 대해 limit_conn를 추가해줌으로서 설정해줄 수 있다. 

사용한 디렉티브

  • limit_conn_zone:
    • $binary_remote_addr를 키로 사용하여, addr이라는 이름의 10MB 공유 메모리 영역에 연결 상태를 저장
  • limit_conn:
    • /store/ 경로에 대해 각 클라이언트가 동시에 열 수 있는 연결 수를 최대 10개로 제한

 

 

3. request 만료시간 설정하기 _ 2번에 대한 해결책

  • 위의 nginx.conf 파일에서 client_body_timeout, client_header_timeout 을 설정하면 된다. 
server {
    listen       80;
    server_name  mydomain.com;

    # 클라이언트가 본문을 보내는 데 5초 이상 걸리면 연결 종료
    client_body_timeout 5s;
    # 클라이언트 헤더를 받는 데 5초 이상 걸리면 연결 종료
    client_header_timeout 5s;

    # 나머지 location 블록들
}

 

사용한 디렉티브 

  • client_body_timeout:
    • 클라이언트가 요청 본문을 전송하는 데 걸리는 최대 시간 설정
  • client_header_timeout:
    • 클라이언트가 요청 헤더를 전송하는 데 걸리는 최대 시간 설정

 

 

추가적으로 특정 ip주소를 막는 방법도 있다. 그치만 디도스 공격은 ip 주소를 막 바꿔가며 공격을 하므로, ip 주소를 막는 것은 최소한의 해결책이라 생각한다. 안정적인 디도스 방어를 위해서는 1,2,3번의 방법들이 선행되어야 한다. 

 

+) DB를 선택하는 기준에 관하여

한줄 요약

보통 관계형 DB, Document DB 사용

  • 입출력이 매우 잦은 경우, Document DB를 사용
  • 정확도와 일관성이 중요하면 관계형 DB를사용

나의 선택

관계형 DB (MySQL)
이유 :
1) 데이터 정규화를 통해 중복되는 데이터 저장을 줄여서 저장공간을 효율적으로 관리하기 위함
2) 과거에 SQL에 대해 배우고 연구실에서 조금이나마 사용해봤으며 프로그래머스로 쿼리문을 짜는 것을 연습해본 것을 고려함
 

 

+) Docker로 추가적으로 할 수 있는 것

  • CI/CD 툴 띄워보기
    • jenkins
    • Portainer - 도커 컨테이너를 웹으로 관리
    • k8s - MiniCube
  • Kafka
    • Single Kafka
    • Cluster Kafka
  • Admin tools

'프로젝트' 카테고리의 다른 글

개인 프로젝트 | 01 _ 웹앱 도입에 대하여  (0) 2024.09.28
개인 프로젝트 | 00 _ 개인 프로젝트 아키텍처  (3) 2024.09.06
presigned url을 이용하여 S3에 이미지 업로드하기 (+ S3버킷 접근 권한 을 관리하는 몇가지 방법들)  (0) 2024.08.25
'프로젝트' 카테고리의 다른 글
  • 개인 프로젝트 | 01 _ 웹앱 도입에 대하여
  • 개인 프로젝트 | 00 _ 개인 프로젝트 아키텍처
  • presigned url을 이용하여 S3에 이미지 업로드하기 (+ S3버킷 접근 권한 을 관리하는 몇가지 방법들)
c_jm
c_jm
  • c_jm
    c_jm
    c_jm

    🎋 어제보다 발전한 오늘

  • 전체
    오늘
    어제
    • 분류 전체보기 (32)
      • 프로젝트 (4)
      • 개발 관련 (4)
      • 백준 문제풀이 (19)
      • 프로그래머스 문제풀이 (4)
      • 기타 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    docker-compose
    복붙
    nginx
    docker
    티스토리 다크모드
    html
    홈서버
    docker-compose.yml
    인라인 css
    reverse proxy
    다크모드
    리버스 프록시 서버
    도커
    리버스 프록시
    도커컴포즈
    글자색
    코드블럭
    백준 #1152 #c++
    jquery
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
c_jm
개인 프로젝트 | 02 _ 홈서버 구축하기 with 도커
상단으로

티스토리툴바