Nginx로 여러 대 서버를 로드밸런싱(Load-Balancing)하기
DevOps

Nginx로 여러 대 서버를 로드밸런싱(Load-Balancing)하기

우선 원하는 구조는 위와 같으며, 흐름은 다음과 같다.

  • nginx는 proxy역할을 하며, 별도의 인스턴스로 구성한다.
  • WAS는 Spring Application이며, 총 두 대로 구성한다.
  • 두 대의 WAS는 하나의 Database를 바라본다.
  • 클라이언트는 정해진 도메인으로 접속하면, nginx에서 ip의 해시값에 따라 적절한 WAS로 요청을 분배하며, 각 WAS는 동일한 DB를 바라보고 있기 때문에 동일한 데이터를 응답할 수 있다.

사실 너무 간단해서 글로 쓰기도 뭣하지만.. 나중을 위해!

WAS 구축하기

Spring Application을 띄울 EC2 인스턴스를 하나 생성한다.

생성된 EC2에 접속한 뒤, 스프링을 띄울 수 있는 환경설정을 한다.

sudo apt update
sudo apt install default-jre
sudo apt install default-jdk

 

이후, 소스코드를 github에서 클론한다.

git clone -b {branch명} --single-branch {레포지토리 주소}

// 서브모듈이 포함되어있으며 레포지토리가 바라보고 있는 서브모듈을 가져올 경우
git clone -b {branch명} --single-branch {레포지토리 주소} --recurse-submodules

// 서브모듈이 포함되어있으며 최신의 서브모듈을 가져올 경우
git clone -b {branch명} --single-branch {레포지토리 주소} --remote-submodules

 

클론한 레포지토리에서 build.gradle이 있는 디렉토리로 이동한 뒤 소스코드 배포, 빌드를 진행한 뒤 실행한다.

./gradlew clean build

cd build/libs
java -jar -Dspring.profiles.active={activeProfile} *.jar &

 

이 과정을 두 개의 인스턴스에서 각각 진행한다.

이후 pull request 및 push에 따라 자동으로 빌드 및 실행이 진행되도록 하기 위해서는 jenkins에 연동하는 것이 좋다.

ps -ef | grep java

위 명령어를 통해 java -jar .. 이 프로세스로서 실행되고 있는지를 확인하는 것이 좋다. 빌드는 됐지만 실행이 안될수도..

 

 

nginx에 로드밸런싱 설정하기

두 대의 WAS를 모두 띄웠다면, nginx 역할을 담당할 인스턴스를 생성한다.

 

인스턴스가 생성되었다면, 먼저 도커를 설치한다.

sudo apt-get update && \
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common && \
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && \
sudo apt-key fingerprint 0EBFCD88 && \
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
sudo apt-get update && \
sudo apt-get install -y docker-ce && \
sudo usermod -aG docker ubuntu && \
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
sudo chmod +x /usr/local/bin/docker-compose && \
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

 

내가 진행할 nginx는 도커에서 설치하며, 우아한 테크코스에서 제공해주는 docker image를 사용한다. 나중에 서버에 직접 nginx를 설치해서 하는 방법도 시도해보아야겠다.

 

아무쪼록 도커가 설치되었다면, 이제 http://내도메인.한국 에 접속해서 사용할 무료 도메인을 생성한다. 그리고 TLS 설정을 진행한다.

 

TLS 설정

서버의 보안과 별개로 서버와 클라이언트간 통신상의 암호화가 필요하기에 TLS를 설정해준다. 평문으로 통신할 경우, 패킷을 스니핑할 수 있다.

 

letsencrypt를 통해 무료로 TLS 인증서를 사용해보자.

docker run -it --rm --name certbot \
  -v '/etc/letsencrypt:/etc/letsencrypt' \
  -v '/var/lib/letsencrypt:/var/lib/letsencrypt' \
  certbot/certbot certonly -d '[도메인 주소]' --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

위 명령어를 통해 인증서를 생성하는 중, Please deploy a DNS TXT record under the name 이라는 문구가 나오면 다음을 진행한다.

출처: 우아한 테크코스

TXT는 내도메인.한국의 도메인 설정에서 진행하는 것이며, 내도메인.한국에서 입력을 모두 마치고 저장한 뒤 약 5초정도 기다린 다음 letsencrypt 설정을 완료해주면 정상적으로 완료된다.

 

생성한 인증서를 현재 경로로 이동한다.

sudo cp /etc/letsencrypt/live/[도메인주소]/fullchain.pem ./
sudo cp /etc/letsencrypt/live/[도메인주소]/privkey.pem ./

 

vi Dockerfile 이라는 명령어를 통해 Dockerfile을 생성하고, 다음과 같이 작성한다.

// Dockerfile
FROM nginx

COPY nginx.conf /etc/nginx/nginx.conf 
COPY fullchain.pem /etc/letsencrypt/live/[도메인주소]/fullchain.pem
COPY privkey.pem /etc/letsencrypt/live/[도메인주소]/privkey.pem

 

같은 디렉토리에서 vi nginx.conf 파일을 생성한 뒤, 다음과 같이 작성한다.

events {}

http {       
  # 생성한 두 대의 WAS private IP를 upstream app 아래에 작성한다.
  upstream app {
    server {WAS private IP}:{포트번호};
    server {WAS private IP}:{포트번호};
  }
  
  # Redirect all traffic to HTTPS
  server {
    listen 80;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;  
    ssl_certificate /etc/letsencrypt/live/[도메인주소]/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/[도메인주소]/privkey.pem;

    # Disable SSL
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # 통신과정에서 사용할 암호화 알고리즘
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    # Enable HSTS
    # client의 browser에게 http로 어떠한 것도 load 하지 말라고 규제합니다.
    # 이를 통해 http에서 https로 redirect 되는 request를 minimize 할 수 있습니다.
    add_header Strict-Transport-Security "max-age=31536000" always;

    # SSL sessions
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;      

    location / {
      proxy_pass http://app;    
    }
  }
}

 

nginx가 여러 서버에 분배해주므로 upstream 서버에 작성한다. 로드 밸런싱은 요청을 고루 분배해서 서버로 넘겨주는데, 이 때 어떻게 분배할 지 규칙을 정해줄 수 있다.

지금 위 nginx.conf처럼 아무 규칙도 upstream에 적지 않고 server ip만 작성한다면 default인 round-robin 방식으로 동작하게 된다.

 

upstream app {
    ip_hash;
    server {WAS private IP}:{포트번호};
    server {WAS private IP}:{포트번호};
  }

ip_hash를 적어주게 되면, ip를 해시값으로 나눠주게 된다. 이렇게 분배하게 되면 한번 접속했던 ip는 같은 해시값을 가지므로 매번 동일한 서버에게 요청을 보내게 된다. 세션으로 무언가를 관리하는 경우 이 방법을 사용하면 좋을 것 같다. (약간의 뇌피셜)

 

이 외에도 여러가지 방법이 있다.

  • round-robin(default)
  • hash - hash <키> 형태로 작성하며 해시한 값으로 분배한다. ex)hash $remote_addr (= 이는 ip_hash와 같다)
  • ip_hash - 아이피를 해싱해 요청을 분배한다.
  • random
  • least_conn - 연결수가 가장 적은 서버에 가중치를 고려해서 분배
  • least_time - 연결수가 가장 적으면서 평균 응답시간이 가장 적은 쪽을 선택해서 분배

자세한 내용은 nginx-stream-upstream-module 에서 확인할 수 있다.

 

방법을 설명하던 중 가중치라는 말이 나왔는데 특정 서버에 2배의 가중치를 주고 싶다면 다음과 같이 작성하면 된다.

upstream app {
    server {WAS private IP}:{포트번호} weight=2;
    server {WAS private IP}:{포트번호};
  }

위와 같이 작성하면 규칙은 라운드 로빈으로 유지하되 첫번째 서버를 2배 더 많이 사용하게 된다.

 

nginx의 configuration을 통해 로드밸런싱 설정을 모두 마쳤으니, 도커 컨테이너를 띄우면 정상적으로 로드밸런싱이 가능해진다.

docker build -t nextstep/reverse-proxy:0.0.2 .
docker run -d -p 80:80 -p 443:443 --name proxy nextstep/reverse-proxy:0.0.2