시작에 앞서
Proxy Server와 로드밸런싱, 수평확장(Scale Out)과 수직확장(Scale Up)에 대해 에서도 말씀드렸듯이 서비스 사용자가 늘어남에따라 서버의 부하를 방지하도록 로드밸런싱을 하여 트래픽을 적절히 분산시킨다고 했었습니다.
- 이번 포스팅에서는 Nginx 로 직접 Reverse Proxy 를 셋팅하고 뒷단에 스프링부트 서버 3대를 배치하여, 트래픽을 골구로 분산시키는 것을 구현해 보겠습니다.
다중서버 구축 도메인 설계
저희는 총 4개의 클라우드 서버 인스턴스를 생성해야합니다. 하나는 WS(웹서버)로써 Reverse Proxy 를 위한 Nginx 서버입니다. 나머지 3개의 서버에서는 8080포트로 다른 IP 서버를 구축할겁니다.
-
클라우드 서버는 EC2 인스턴스를 4개 생성할까 고민했지만, 비용이 많이 나가는 것을 고려하여 Linode 에서 Ubuntu 22.04 LTS 를 기반으로 서버를 구축했습니다.
-
추가적으로 로드밸런싱 알고리즘은 가장 기본적인 Round Robin(라운드 로빈) 알고리즘으로 트래픽을 분산시켰습니다. (알고리즘에 대한 설명도 할것이니 걱정마세요!)
애플리케이션 설계
동일한 코드를 각 서버에서 실행시킬겁니다. 각 서버에서는 서버이름(serverName) 과 사용자 방문횟수(visitedCount) 를 멤버변수로 가지고 있는 구조입니다.
매 방문마다 어떤 배포 서버에 접속했는지를 알 수 있도록 ServerName 와, 방문횟수를 매번 카운팅하여 몇번 방문했는지를 나타내는 visitedCount 변수의 값을 Map 형태로 리턴받는 구조입니다.
SpringBoot 코드 구현
ServerController
아래와 같이 RestController 하나를 생성하고, 이 컨트롤러로 현재 요청을 보낸 서버에 대한 이름과 방문횟수를 조회할 수 있는 기능을 만들었습니다.
이떄 @PropertySource 어노테이션을 통해 application.yml 에 존재하는 serverName 변수값을 불러왔습니다.
@RestController
@PropertySource(value = "application.yml")
public class ServerController {
@Value("${serverName}")
private String serverName;
private Integer visitedCount = 0;
@GetMapping("/getServerInfo")
public ResponseEntity<Map<String, String>> getServerInfo(){
visitedCount++;
Map<String, String> serverInfo = new HashMap<>();
serverInfo.put("ServerName:", serverName);
serverInfo.put("visitedCount:", visitedCount.toString());
return ResponseEntity.ok(serverInfo);
}
}
application.yml
또 아래와 같이 application.yml 파일을 구성해줬습니다. 이떄 중요한 것은 각 서버를 구분짓기 위한 프로필(Profile) 설정을 진행해준 것입니다.
각 서버의 애플리케이션 코드는 모두 동일하나, 단순히 IP, 사용자 정보 정도만 바뀌는 정도라면 이렇게 application.yml 과 같은 설정파일에 프로필을 기록하는 것입니다.
즉, 애플리케이션 코드는 전부 동일한데 설정정보만 조금 다르게 하고 싶다면, 각 서버를 구분지어줄 프로필을 부여하면 됩니다. 그러고 각 서버를 실행시킬때 각각 다른 프로필 정보에 기반해 실행하면 됩니다.
spring:
config:
activate:
on-profile: server1
serverName: my-server1
---
spring:
config:
activate:
on-profile: server2
serverName: my-server2
---
spring:
config:
activate:
on-profile: server3
serverName: my-server3
추후에 클라우드 서버에 올린 jar 파일을 실행할때 위와 같이 프로필 정보를 함께 입력해서 nohup 으로 실행하면, 해당 프로파일의 설정으로 애플리케이션을 시작할 수 있게됩니다.
클라우드 환경 구축
클라우드 서버는 앞서 말씀드렸듯이 1개의 Nginx 서버와, 뒷단에서 스프링부트 애플리케이션이 실행되고 있는 3개의 서버를 구축했습니다. 우선 스프링부트 서버를 어떻게 구축했는지를 설명해보고자 합니다.
스프링부트 클라우드 서버 환경
1.jdk-11 설치
현재 Ubuntu 서버에는 자바가 설치되어 있지 않을겁니다. 자바 설치 여부를 확인해봅시다.
java --version
만일 설치되어 있지 않다면 아래와 같이 자바를 서버에 설치해줍시다. default-jdk 로 jdk 를 설치하는 경우 기본적으로 jdk-11 이 설치된다는 점을 알고 넘어갑시다.
- 만일 본인의 프로젝트가 JAVA 11 버전이 아니라면 다른 방법을 통해 설치해주셔야합니다!
apt-get update
apt install default-jdk -y
그러고 다시 자바 버전을 확인하여 자바가 정상적으로 설치되었는지 확인해봅시다!
java --version
2.로컬에있는 jar 파일 전송
다음으로 로컬에서 작업한 스프링부트 프로젝트를 빌드하고 생성된 jar 파일을 클라우드 서버에 전송해봅시다. 저는 이번에는 scp 전송을 활용했습니다. (SSH 도 사용해도 되지만, 예전에 사용해본 경험이 있어서 새로운 방식을 택했습니다!)
아래처럼 gradlew 디렉토리에서 booJar 를 통해 프로젝트를 빌드하여 jar 파일을 생성합시다.
$ ./gradlew bootJar
아래처럼 빌드에 성공해야합니다!
그러면 생성된 jar 파일은 build/libs 안에 존재할겁니다.
$ cd build
$ cd libs
$ ls // core-0.0.1-SNAPSHOT.jar
3.로컬에 생성한 jar 파일을 클라우드 서버 3개에 각각 전송하기
이렇게 생성된 jar 파일을 클라우드 서버에 전송하면 됩니다. 앞서 말씀드렸듯이 SCP 를 활용했으며, 그 방법은 다음과 같이 진행해주시면 됩니다.
참고로 test-server 란 제가 jar 파일을 제가 새롭게 파일명을 지어준것입니다.
또 당연한 말이지만, 클라우드 서버 인스턴스를 3개 생성했다면 각 서버에 대해 모두 jar 파일을 각각 전송해주셔야합니다!
$ {파일명}.jar root@111.111.111.111:test-server.jar
// build/libs 에서 실행해주세요! (jar 파일에 존재하는 디렉토리에서)
ex) $ scp core-0.0.1-SNAPSHOT.jar root@111.111.111.111:test-server.jar
jar 파일을 전송할떄 아래와 같이 root 계정의 비밀번호를 입력하도록 해야하는데, 본인의 클라우드 서버의 루트 계정을 입력해주시면 전송에 성공할겁니다.
클라우드 서버에서 jar 파일이 잘 전송되었는지 확인도 해봅시다!
4.스프링부트 애플리케이션 실행하기 (Background 실행)
이제 클라우드 서버에 전송된 jar 파일을 실행시키면 스프링부트 애플리케이션이 배포될 겁니다. 저희는 서버가 다운되는 일이 없도록 nohup 을 통해 무중단배포를 실행할겁니다.
-
이떄 프로필명을 구분지어서 각 서버에서 다른 프로필에 기반하여 각각의 다른 설정정보를 기반으로 스프링부트 애플리케이션을 실행시켜주셔야 합니다.
-
아래와 같이 각 3개의 서버에서 프로필명을 각각 server1, server2, server3 로 실행시켜주시면 됩니다.
$ nohup java -jar Dspring.profiles.active="프로필명" test-server.jar &
ex) $ nohup java -jar -Dspring.profiles.active=server1 test-server.jar &
아래와 같이 실행되면 정상입니다.
또 백그라운드에서 스프링부트 애플리케이션이 잘 실행되고 있는지를 확인해봅시다.
$ jobs
Nginx 로드밸런서 서버 구축
다음으로 Nginx 로 로드밸런싱을 해주도록, 인스턴스 서버 하나에 Nginx 를 설치하고 Reverse Proxy 를 셋팅해주겠습니다.
Nginx 설치
아래와 같이 Nginx 를 설치해줍시다.
$ apt install nginx // nginx 설치
$ serivce nginx start // 설치한 Nginx 가 정상적으로 반영되도록 재시작
$ ps -ef | grep nginx // 정상적으로 Nginx가 실행되었는지 확인
Nginx 설정파일 생성
그러고 Nginx 가 로드밸런싱을 하기위한 설정파일을 새롭게 생성해주고, 그 설정파일을 기반으로 뒷단의 스프링부트 애플리케이션에 트래픽이 분산되도록 해야합니다.
-
기본적으로 Nginx 는 http 요청이 들어왔을때 sites-enabled 디렉토리 안에 있는 파일들의 설정정보를 기반으로 로드밸런싱과 같은 일련의 작업을 수행합니다.
-
따라서 아래와 같이 sites-enabled 로 이동해서 로드밸런싱을 해주기 위한 새로운 파일을 생성하고, 설정정보를 기입해줍시다.
-
default 파일은 말그대로 http 요청이 들어왔을때 Nginx 에 어떤 작업을 수행할지에 대한 디폴트 설정정보 파일입니다. 이 디폴트 파일은 삭제시키고 저희만의 새로운 로드밸런싱 관련 파일을 만들어줍시다.
$ cd /etc/nginx/sites-enabled
$ rm -rf default // default 파일 삭제
$ vi myapp // nginx 로드밸럱싱 파일에 대한 새로운 파일생성
cf) Nginx 에 대한 설정정보 파일을 스캔하는 디렉토리를 새롭게 설정할 수 있습니다. 즉 sites-enabled 가 아닌 다른 디렉토리로도 설정이 가능합니다. 이는 nginx.conf 에서 수정해주시면 됩니다!
Nginx 로드밸런싱 설정하기
myapp 설정파일 구성
다음으로 이번 포스팅의 핵심인 Nginx 의 로드밸런싱 설정입니다. 직전에 생성한 myapp 에서 아래와 같이 구성해주시면 됩니다.
-
클라이언트의 모든 요청은 80 포트로 들어오게 됩니다. 80 포트로 들어온 요청을 upstream 에 지정해준 3개의 스프링부트 서버로 로드밸런싱이 진행됩니다.
-
Nginx upstream 이란?
=> upstream 서버는 말로 Origin 서버라고도 부르비다. proxy_pass 를 통해 Nginx 웹서버가 받은 요청들을 여러대의 스프링부트 애플리케이션 서버로 넘겨주는 역할을 수행합니다.
upstream backend{
// 로드밸런싱 디폴트 알고리즘 : Round Robin
server 111.111.111:8080; // 스프링부트 애플리케이션이 실행되고 있는
server 111.222.222:8080; // 서버 3개에 대한 IP 주소를 명시해주자!
server 333.333.333:8080;
}
server{
listen 80; // 클라이언트가 요청하는 포트
location / {
proxy_pass http://backend; // 설정한 이름으로 요청 보내기
}
}
~
설정정보 다시 불러오기
$ nginx -s reload
설정파일인 myapp 을 변경했다면 정상적으로 반영이 되도록, 위 명령을 통해 Nginx 의 설정 파일을 다시 로드하도록 합시다!
트래픽 분산 눈으로 확인해보기
위와 같이 Nginx 설정정보가 반영되었다면 Nginx 서버에 API 요청을 보내봅시다. 그러면 아래와 같은 결과를 확인할 수 있을겁니다!
- 보시듯이 3대의 서버에 트래픽이 골구로 분산되는 모습을 확인할 수 있습니다.
{
"ServerName:": "my-server1",
"visitedCount:": "1"
}
{
"ServerName:": "my-server2",
"visitedCount:": "1"
}
{
"ServerName:": "my-server3",
"visitedCount:": "2"
}
{
"ServerName:": "my-server1",
"visitedCount:": "2"
}
{
"ServerName:": "my-server2",
"visitedCount:": "2"
}
{
"ServerName:": "my-server3",
"visitedCount:": "2"
}
{
"ServerName:": "my-server1",
"visitedCount:": "3"
}
// ...
로드밸런싱 알고리즘
사실 로드밸런싱을 하는 방법이 다양하다는 사실 알고 계신가요?
- 위에서 진행한 방식은 Round Robin(라운드 로빈) 알고리즘을 사용해서 트래픽을 분산시킨 것입니다.
별도로 알고리즘을 지정해주지 않으면 라운드 로빈 알고리즘을 사용하는 것이죠.
알고리즘 종류
알고리즘 종류 | 설명 |
---|---|
Round Robin (Defualt) | 요청을 순서대로 처리 |
least_conn | 현재 연결수가 가장 적은 서버로 요청을 전송 |
ip_hash | 클라이언트 IP 를 해싱하여 특정 특정 클라이언트가 특정 서버로 연결되게 한다 |
Random | 트래픽을 무작위로 분배 |
least_time | 연결수가 가장 적으면서 + 평균 응답시간이 가장 적은 서버를 선택해서 분배 (Nginx Plus) 에서만 가능 |
알고리즘과 어떻게 적용시키지?
알고리즘 적용은 앞서 적용했던 Nginx 설정파일에서 upstream 에 지정해주시면 됩니다.
upstream backend{
leaast_conn; // least_conn 알고리즘을 적용
server 111.111.111:8080;
server 111.222.222:8080;
server 333.333.333:8080;
}
server{
listen 80;
location / {
proxy_pass http://backend;
}
}
server 에 대한 다양한 옵션
알고리즘 외에도 다양한 server 지시어에 다양한 옵션을 부여해서 트래픽을 분산시킬 수 있습니다.
weight
특정 서버에 가중치를 설정 가능합니다. 가중치가 설정된 서버는 다른 서버보다 가중치의 배수만큼 트래픽을 받게됩니다.
upstream backend {
server 111.111.111:8080 weight=4; // 다른 서버보다 4배의 트래픽을 받는다.
server 222.222.222:8080;
server 333.333.333:8080;
}
down
down 옵션을 부여하면 특정 서버로 트래픽이 분배되지 않도록 할 수 있습니다.
upstream backend {
server 111.111.111:8080 down; // 2, 3번째 서버만 트래픽을 분배받는다.
server 222.222.222:8080;
server 333.333.333:8080;
}
max_conns
서버와의 최대 동시 커넥션 수를 지정된 숫자만큼으로 제한하는 옵션입니다.
upstream backend {
server 111.111.111:8080 max_conns=10; // 최대 10개의 트래픽을 분배받는다.
server 222.222.222:8080;
server 333.333.333:8080;
}
마치며
지금까지 Nginx 를 통해 로드밸런싱을 진행하고, 뒷단에 존재하는 3대의 스프링부트 서버에 대해 트래픽을 분산시키는 다양한 알고리즘과 옵션에 대해 알아봤습니다. Nginx 를 학습하시는 분들에게 이번 포스팅이 도움이 되었으면 하는 바람입니다 😉
참고
[Nginx] Load Balancing 설정하기 [Nginx] 로드밸런싱 개념 및 구축 WebServer[nginx] nginx 로 로드밸런싱 하기 Nginx 로드밸런싱 적용하기
[springboot] 스프링부트 jar 파일 백그라운드 실행방법 스프링부트 Profile 사용하기 cannot find symbol method value() 에러 해결 nohup 명령어 활용하기