Nginx로 로드 밸런싱(Load Balancing) 적용해보기

junto·2024년 9월 25일
0
post-thumbnail

로드 밸런싱이란?

  • 로드(부하)를 밸런싱(균형, 분산)하는 것을 말한다. 쉽게 말해, 클라이언트 요청이 많아져 서버에 부하가 많아질 때 요청들을 여러 서버에 균형있게 분산시켜 각 서버가 원활히 동작하도록 한다.

Scale Up과 Scale Out

  • 사용자 요청이 급격하게 증가할 때 대처 방안으로 서버 자체의 성능을 향상 시키는 Scale Up과 비슷한 서버를 증설하여 요청을 분배하는 Scale Out 방법이 있다.

  • Scale Up의 경우 하드웨어 성능을 무한정 올리기에 한계가 있고, 시스템 장애에 취약하다는 단점이 있다. (SPOF, Single Point Of Failure)
  • Scale Out은 비교적 적은 비용으로 다수의 사용자 요청을 처리할 수 있으며, 무중단 서비스를 제공할 수도 있다. 하지만 세션 불일치 문제, 서버 관리 등 기술적 난이도가 존재한다.

Scale Out과 로드 밸런서를 이용하여 성능 및 가용성 이점을 얻을 수 있고, reverse proxy를 이용한다면 API 서버를 내부망에 감춰 보안이 향상된다!

SSL Offloading

  • SSL Offloading이란 암호화 및 복호화 작업을 각 서버가 하는 것이 아닌 로드 밸런서에서 처리하는 것을 말한다.

  • SSL OffLoading을 적용하면 로드 밸런서에 SSL 인증서가 존재하기에 SSL Handshake 과정이 효율적으로 진행된다. 또한 암호화된 요청이 아닌 평문으로 웹 서버에 전달하기 때문에 네트워크 대역폭을 더 효율적으로 사용할 수 있다. 이외에도 서버를 증설할 때 SSL 인증서를 추가하지 않아도 된다는 점, 증설된 서버는 암호화 / 복호화하는 데 CPU를 사용하지 않아도 된다는 장점이 있다.

서비스 디스커버리(Service Discovery)

  • 서비스가 Scale Out되어 추가되면, 클라우드 환경에서는 IP 주소가 동적으로 할당된다. 로드 밸런서는 어떻게 동적으로 부여된 IP주소를 파악하여 로드 밸런싱을 할 수 있을까?
  • AWS를 사용하는 클라우드 환경에서는 ELB(Elastic Load Balancer)를 사용해 인스턴스에 트래픽을 분배할 수 있으며, Scale Out된 인스턴스에도 쉽게 대응할 수 있다.
  • Spring Boot를 사용하면, Netflix가 개발한 Eureka Server와 Eureka Client 의존성을 추가하여 Auto Scaling된 인스턴스의 IP와 상태를 동적으로 파악하고 로드밸런싱을 처리할 수 있다.

  • 그림에서, Spring Cloud Gateway는 오토 스케일링된 인스턴스의 존재를 모르지만, Eureka Server가 Gateway에 목록을 전달하여 로드밸런싱 기능을 수행할 수 있다.

로드 밸런싱 알고리즘

1. Round Robin (라운드 로빈)

  • 다수의 서버에게 순서대로 요청을 할당한다.
  • 서버에 균등하게 요청을 분배할 수 있다는 장점이 있으나, 각 서버의 처리량이나 서버 상태, 작업 부하가 달라지는 경우 등을 고려하지 못한다는 단점이 있다.

2. Least Connection (최소 연결)

  • TCP/IP 프로토콜로 생성되는 Connection 기반으로 부하를 분산한다.
  • 사용자와 서버가 정상적인 연결을 맺으면 Connection을 생성하기에 가장 Connection이 적은 서버에 요청을 전달한다.
  • 작업마다 처리 시간 변동폭이 클 때 효과적으로 대응할 수 있지만, 각 서버의 활성 연결 수를 계속해서 추적해야 하는 복잡성이 존재하고, 여전히 서버 응답 시간이나 상태는 고려하지 못한다는 단점이 있다.

3. Weight Ratio (가중치 비율)

  • 각 서버의 처리 능력을 고려하여 서버가 가질 수 있는 처리량 또는 Connection 비율 가중치를 토대로 부하를 분산한다.
  • 각 서버 성능에 따른 효율적인 처리가 가능하지만, 최적 서버 가중치를 유지하는 데 어려움이 있을 수 있다.

4. IP Hash (IP 해시)

  • 위 방법들은 독립된 여러 서버마다 세션 불일치 문제가 발생할 수 있다. 사용자 세션 정보가 A 서버에 저장되어 있을 때, B 서버로 요청이 가게 되면 B서버도 A서버에 있는 세션 정보를 가지고 있을 필요(Session Clustering)가 있다. 즉, 중복 데이터 문제가 생길 수 있다.
  • IP Hash 방식을 사용하면 패킷의 IP 주소를 해싱하여 결과 해시 값에 해당하는 서버가 처리하는 것을 보장하기 때문에 세션 불일치 문제를 해결할 수 있다. 하지만, 적은 수의 클라이언트와 많은 요청을 처리하는 경우 특정 서버에 부하가 몰릴 가능성이 존재한다.

5. Least Response Time(최소 응답 시간)

  • 응답 시간이 가장 빠른 서버에 우선적으로 요청을 할당하는 방식이다.
  • 사용자 경험이 좋아진다는 장점이 있지만, 서버 응답 시간을 파악하기 위한 모니터링 시스템을 구축해야 하므로 구현에 복잡성이 더해진다. 또한, 각 서버 상태나 처리량을 고려하지 못한다.

각 방법마다 장단점이 있기 때문에, 비즈니스 성격에 맞게 복합적인 로드 밸런싱 알고리즘을 구축할 필요가 있다.

Nginx로 로드 밸런싱하기

전체 코드: https://github.com/ji-jjang/Learning/tree/main/Practice/LoadBalancer

1. 환경 구성

2. 라운드로빈 방식

upstream api_servers {
    server 172.29.115.222:8080;
    server 34.64.229.17:5001;
    server 34.64.222.54:5002;
}

server {
    listen 80;

    location / {
        proxy_pass http://api_servers;
		proxy_set_header Host $host;
    }
}
// 출력 결과
> curl 172.29.115.222:80/hello
hello Server1

> curl 172.29.115.222:80/hello
hello Server2

> curl 172.29.115.222:80/hello
hello Server3

> curl 172.29.115.222:80/hello
hello Server1
...
  • Host 헤더를 지정하지 않으면 400 Bad Request 에러가 발생한다. 그 이유는 api_servers에 proxy_pass를 하게되는데, 이는 호스트 도메인이 아니기 때문이다. 따라서 원래 Host값(172.29.115.222)을 명시해주어야 한다.

1) L4 로드 밸런서

  • L4 로드 밸런서는 IP와 PORT를 기준으로 로드 밸런싱을 수행한다. 위의 예시처럼 아이피와 포트 번호를 가지고 요청을 분산시킨다.

2) L7 로드 밸런서

  • L7 로드 밸런서는 URL, Header, Cookie 등의 내용으로 요청을 라우팅한다.
Server {
    location / {
		proxy_pass http://nextjs-app:3000;
    }

    location ~ ^/api {
		proxy_pass http://app:8080;
    }

    location ~ ^/actuator {
		proxy_pass http://app:8080;
    }
}

3. Least Connection (최소 연결 방식)

  • 기본 커넥션 수가 동일하므로 라운드 로빈 방식과 동일하게 동작한다.
upstream api_servers {
	least_conn;
    server 172.29.115.222:8080;
    server 34.64.229.17:5001;
    server 34.64.222.54:5002;
}

server {
    listen 80;

    location / {
        proxy_pass http://api_servers;
		proxy_set_header Host $host;
    }
}

4. Weight Ratio (가중치 방식)

upstream api_servers {
    server 172.29.115.222:8080 weight=3;
    server 34.64.229.17:5001 weight=2;
    server 34.64.222.54:5002 weight=1;
}

server {
    listen 80;

    location / {
        proxy_pass http://api_servers;
		proxy_set_header Host $host;
    }
}
// 출력 결과
> curl 172.29.115.222:80/hello
hello Server1

> curl 172.29.115.222:80/hello
hello Server2

> curl 172.29.115.222:80/hello
hello Server1

> curl 172.29.115.222:80/hello
hello Server3
...

5. IP Hash (IP 해시)

upstream api_servers {
	ip_hash;
    server 172.29.115.222:8080;
    server 34.64.229.17:5001;
    server 34.64.222.54:5002;
}

server {
    listen 80;

    location / {
        proxy_pass http://api_servers;

				proxy_set_header Host $host;
    }
}
// 출력 결과
> curl 172.29.115.222:80/hello
hello server3

> curl 172.29.115.222:80/hello
hello server3

...

참고 자료

profile
꾸준하게

0개의 댓글