로드 밸런싱이란?
- 로드(부하)를 밸런싱(균형, 분산)하는 것을 말한다. 쉽게 말해, 클라이언트 요청이 많아져 서버에 부하가 많아질 때 요청들을 여러 서버에 균형있게 분산시켜 각 서버가 원활히 동작하도록 한다.
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
...
참고 자료