개발하는 자몽

Nginx 로드 밸런싱 on Docker 본문

Architecture & Tool/Nginx

Nginx 로드 밸런싱 on Docker

jaamong 2023. 12. 23. 16:27

→😎Nginx의 로드 밸런싱 기능을 사용하게 된 이유는 아래 글을 참고😎

 

서버 부하 테스트 with Locust

개발 중인 애플리케이션에 대해서 조언받는 분에게 부하 테스트(Load Test)를 해보았냐는 질문을 받았다. 항상 개발을 하는 것과 해당 기능을 테스트하는 것에만 집중했지, 서버 그 자체를 테스트

backend-jaamong.tistory.com

위와 같은 이유로 로드 밸런싱을 적용하게 되었다. Nginx를 사용한 이유는 적용 방식이 간단하고 L4, L7 스위치로 하는 방법과 달리 비용이 들지 않기 때문이다. 

 

그리고 Nginx를 도입하기 전 같은 기능을 제공하는 AWS의 ELB(Elastic Load Balancing)와도 비교해 보았다. 자세한 내용은 잘 정리해 둔 아래 글을 참고!

 

AWS ELB vs. NGINX Load Balancer | Sumo Logic수모로직

Sumo Logic은 최신 앱의 로그 및 메트릭 관리에 필요한 업계 최고의 보안 클라우드 기반 서비스로서, 실시간 분석 및 인사이트를 제공합니다

www.sumologickorea.com

 

상황

서버는 Docker 상에서 컨테이너로 하나로 돌아가고 있었다. 여기에 Nginx 컨테이너를 올리고, 서버도 컨테이너를 하나 더 추가하는게 목표였다.

 

체크리스트

본격적으로 적용하기 전 무엇을 해야 하는지 명확하게 정리하고 싶어서 체크 리스트를 정리했다.

 

✅ 한 대의 컨테이너에서 동작하던 서버를 두 대의 컨테이너로 나누기

  • docker-compose.yml 수정
    • `service` 블록에 추가
      • 각 서버 컨테이너 별 port 설정
      • `command`에 적는 port 설정

✅ Nginx 로깅을 위한 폴더 생성

  • Dockerfile에 해당 동작을 하는 명령어 작성

Docker에 Nginx를 컨테이너로 올리기

  • docker-compose.yml 수정
    • `service` 블록에 추가
    • `volumes` 설정
      • `호스트머신의_conf_위치 : nginx_컨테이너_내의_conf 위치`

 Nginx 로드 밸런싱 설정 하기

  • Nginx 설정 파일(nginx.conf) 수정하기 
    • 호스트 머신에서 nginx.conf 생성하기
    • `upstream` 설정으로 두 컨테이너 이어주기
    • `keepalive` 설정 고려

 

코드 작성

docker-compose.yml

version: "3.5"

services:
  _build_image:
    image: ...
    build: .
    
  server1: 
    image: ...
    restart: always
    command: python ... runserver 0.0.0.0:4100
    ports:
#      - 4100:4100  # host_server_port:docker_server_port
      - "4100"  # host server 포트를 열지않고 컨테이너의 포트만 열어둠
    depends_on:
      - _build_image

  server2: 
    image: ...
    restart: always
    command: python ... runserver 0.0.0.0:4100
    ports:
#      - 4101:4100  # host_server_port:docker_server_port
      - "4100"  # host server 포트를 열지않고 컨테이너의 포트만 열어둠
    depends_on:
      - _build_image

  nginx:
    image: nginx    # tag the image with the name "nginx"
    build: ./nginx  # Dockerfile location for Nginx
    volumes: 
      - ./nginx.conf:/etc/nginx/nginx.conf:ro  # read-only
    depends_on:
      - server1
      - server2
    ports:
      - "4100:80"

 

server1, server2

두 대의 컨테이너로 늘리기 위해서 `service` 블록에 server를 하나 더 추가했다. 단순하게 이름 뒤에 숫자를 추가해 주었다. 

 

depends_on

`depends_on`은 어떤 서비스 이후에 실행할 건지 정한다(종속성 순서). nginx를 사용하는 이유가 로드밸런싱 때문이므로 서버 역할을 하는 두 컨테이너가 실행된 이후에 nginx 컨테이너가 실행되도록 위와 같이 작성했다.

 

ports 

server1, server2 포트(주석 처리 X)는 아래와 같이 생각하면 된다.

 

  • `command` : 컨테이너의 4100번 포트에서 서비스 실행
  • `ports` : 호스트 머신의 포트는 열어두지 않고, 도커 컨테이너의 4100번 포트만 열어둔 상태

nginx의 포트는 서버와 다르게 `4100:80`로 설정되어 있는데 이는 호스트 머신의 4100번 포트를 컨테이너의 80번 포트에 연결한다는 의미이다. 

 

이 상태에서 nginx(컨테이너)로 들어온 요청이 두 대의 서버(컨테이너)에게 전달되도록 `nginx.conf`에서 설정하면 된다.

 

Dockerfile  in nginx directory

FROM nginx:latest

# remove nginx default configuration file
# RUN rm /etc/nginx/conf.d/default.conf -> ERROR : No such file or directory

# copy custom nginx configuration file
COPY ./nginx.conf /etc/nginx/nginx.conf

# for Nginx log
RUN mkdir -p /var/log/nginx/server  
WORKDIR /var/log/nginx/server  
RUN touch access.log error.log

참고로 Dockerfile, docker-compose.yml 파일과 같은 위치에 nginx 디렉터리를 생성했다. 이에 맞춰서 docker-compose.yml > services > nginx > build에 Dockerfile의 위치가 설정되어 있다. nginx 디렉터리에는 아래의 파일들이 있다.

 

  • Nginx 이미지 생성을 위한 Dockerfile (서버 컨테이너와는 별도의 이미지를 사용하기 위함)
  • Nginx 설정을 위한 nginx.conf

 

nginx.conf  in nginx directory

worker_processes auto;

events {
    worker_connections 1024;
}

http {
    # log format
    log_format upstream_time '$remote_addr - $remote_user [$time_local] "$request" '
                             '$status $body_bytes_sent "$http_referer" '
                             'rt=$request_time uct="$upstream_connect_time"';

    # --- global log ---
    # access_log /var/log/nginx/access.log upstream_time;
    access_log off;     # 전역 로그 비활성화
    log_not_found off;  # 파일을 찾을 수 없을 때 에러 로그 X
    error_log /var/log/nginx/error.log crit;    # 에러 로그, 로그 레벨 - critical

    # for load balacing
    upstream server {
        server server1:4100;
        server server2:4100;
        keepalive 1024;
    }

    server {
        listen 80; 

        location / {
            proxy_pass http://server/;    # upstream 이름
        }

        # --- server log ---
        access_log /var/log/nginx/server/access.log upstream_time;    # 접속 로그
        error_log /var/log/nginx/server/error.log crit;     # 에러 로그
    }
}

 

worker_processes

Nginx는 하나의 Master Process와 여러 개의 Worker Process로 구성되어 실행된다. Master Process는 설정 파일을 읽고 유효성을 검사하며, Worker Process를 관리한다.

모든 요청은 Worker Process에서 처리된다. Worker Process의 개수는 설정 파일에서 정의되며, 정의된 프로세스 개수와 사용 가능한 CPU 코어 숫자에 맞게 자동으로 조정된다.

Worker Process의 개수는 물리적 CPU 코어 개수만큼 할당하는 것이 가장 이상적이며, 설정 파일에서 `auto`로 지정하면 자동으로 CPU 코어 개수에 알맞게 프로세스를 생성한다.

 

events

`events` 블록은 네트워크의 작동 환경을 설정하는 지시어를 제공한다. `http`, `server`, `local` 블록과는 상속 관계를 갖지 않는다. 아래 지시어들은 `events` 블록 안에서만 사용 가능하다.

  • accept_mutex : LISTEN 소켓을 오픈하기 위한 accept mutex의 사용/해제를 설정한다.
  • accept_mutex_delay : 자원 획득을 다시 시도하기 전에 `worker process`가 기다려야 하는 시간을 정의한다. `accept_mutex` 지시어가 `off`로 설정되어 있으면 이 값은 사용되지 않는다.
  • worker_connections : `Worker Process`가 동시에 처리할 수 있는 접속자 수를 정의한다.
    • worker_processes * worker_connections = 최대 접속자 수

 

http

http 블록은 Nginx로 들어오는 웹 트래픽에 대한 처리 방법과 방향을 설정해 준다. 

 

🔹 upstream

upstream 서버는 다른 말로 Origin 서버라고 부른다. `proxy_pass`를 통해 Nginx 웹 서버가 받은 요청들을 여러 대의 애플리케이션 서버로 넘겨주는 역할을 한다.

http는 데이터 전송을 받기 위해 서버에 접속해서 데이터를 받은 다음, 연결을 끊는다. upstream 안에 있는 `keepalive`를 설정하면 이 연결을 일정 시간 유지 시켜준다. 따라서 지속되는 만큼 매번 연결을 하지 않아도 되기 때문에 성능 향상에 도움 된다.

 

🔹 server

server 블록은 가상 서버에 대해서 정의할 수 있는 곳이다. IP 주소 기반이나 server_name을 기반으로 정의할 수 있다. 

 

  • `listen` : Nginx는 80번 포트를 listening 하여 외부로부터 Nginx(컨테이너) 내부의 80번 포트로 요청을 받는다. 
  • `location` : 서버 안의 리소스에 대한 요청을 어떻게 응답해야 할지를 설정한다. 특정 파일과 특정 디렉터리에 대한 요청을 처리한다. (이 글을 읽고 location를 이해할 수 있었다!)

 

포트 정리

4100번 포트로 요청이 들어오면 Nginx(컨테이너)의 80번 포트(listen)로 연결되며, `upstream` 설정에 의해 server1(컨테이너) 또는 server2(컨테이너)로 요청이 전달된다. 요청이 전달될 서버가 선택되는 디폴트 방식은 라운드 로빈(Round Robin) 알고리즘이다. 그 외에 로드 밸런싱을 선택하는 방법으로는 `Least-connections`, `IP Hash`가 있다. 이와 관련된 자세한 내용은 공식 문서에 잘 나와있다. 

 

회고

개인적으로 포트 설정이 생각보다 어려웠는데, 도커 공부를 제대로 하지 않아서 그랬던 것 같다🙃

도커 포트 관련해서 잘 정리해 둔 블로그 글이 있는데, 읽어보는 것을 추천한다!

 

 

 

'Architecture & Tool > Nginx' 카테고리의 다른 글

[NGINX] HTTPS 서버 구성하기 (Let's Encrypt)  (2) 2024.04.12
Comments