[Docker] nginx web server를 이용한 로드 밸런싱

최근 들어, 쿠버네티스를 활용하려 노력하면서도 가끔은 Docker를 다시 돌아보는 경향이 많아졌습니다. 로드 밸런싱을 하는 방법에는 여러 방법이 있지만 그 중에서도 컨테이너를 이용한 로드 밸런싱은 여러모로 많은 도움이 되었습니다. 무엇보다도 100개 이상의 컨테이너를 생성하기 위해 Docker Compose, Docker Swarm을 많이 사용했었다는 점. 이 부분은 수동으로 인스턴스를 만들어주는 번거로움을 많이 덜어주었고, 그 때문에 컨테이너 기반 운영을 많이 선호했던 점이 기억에 남았습니다.

서론을 접고, 이제 본론으로 넘어가보죠. 이전에 로드 밸런싱에 대한 글을 올렸던 적이 있었습니다. 로드 밸런싱은 실무에서 많이 다루는 기술이지만 이를 적용하기 위해서는 역시 개념에 대해 정확히 알고 사용해야 합니다. 

2020/06/02 - [Infrastructure/Network] - [Network] Load Balancing (로드 밸런싱)의 개념과 이해

 

[Network] Load Balancing (로드 밸런싱)의 개념과 이해

처음 서버를 개발하고, 이를 운영하는 데까지 많은 학습 시간이 필요했습니다. 서버 개발을 위해 Servlet, JSP, Spring을 배우게 되었고, 나아가서는 더 나은 프레임워크 및 차이를 알아보기 위해 Flask

blog.neonkid.xyz

 

nginx

nginx는 웹 서버 프로그램 중 하나로 싱글 스레드로 운영되고 있는 작은 웹 서버입니다. 이전에는 apache2 웹 서버를 사용했는데, 최근에는 nginx 웹 서버가 많이 사용되고 있죠. 왜 그렇냐구요? 그 점은 바로 웹 서버의 처리 방식에 따른 차이 등이 있는데, 이 부분은 현재 다루고자 하는 주제와 조금 연관성이 벗어나므로, 다른 포스트에서 다루도록 하겠습니다.

우리는 Docker 컨테이너를 로드 밸런싱하기 위해 nginx 웹 서버를 사용할 수 있습니다. nginx 웹 서버에서는 요청을 분산 처리할 수 있는 로드 밸런서 기능을 가지고 있기 때문입니다. 따라서 우리는 아래의 명령어로 nginx를 설치하고, Spring boot 웹 애플리케이션을 로드 밸런싱 시켜보도록 하겠습니다.

$ sudo apt install nginx

설치가 끝났다면, 위 명령어를 이용하여 nginx service가 active (running) 인지를 확인하여 서버가 잘 구동되고 있는지 확인합니다.

 

 

Create Docker Container

서비스하고자 하는 Docker 컨테이너를 만들어야겠죠? 저는 간단한 Spring boot Application을 1개 만들어서, 포트 주소를 각각 3000, 4000, 5000번 포트를 주고, 테스트를 해보도록 하겠습니다.

FROM 	openjdk:8-jdk-alpine
EXPOSE  ${SERVER_PORT}
ARG		JAR_FILE=build/libs/*.jar
COPY	${JAR_FILE}	app.jar
ENTRYPOINT	["java", "-jar", "/app.jar", "$@"]

기존의 Dockerfile에서 EXPOSE를 추가하여 명령어 인자를 주고, Server Port를 설정하면, Docker 컨테이너 생성시, 명령어에 포트 번호를 입력하여 원하는 포트 번호로 컨테이너 이미지를 만들 수 있습니다.

# docker run -d -e SERVER_PORT 3000 -p 3000:3000 --name dockerexample01 neonkid/dockerexample

위 명령어를 바탕으로 3개의 컨테이너를 만듭니다.

 

 

 

Load Balancing Settings

마지막으로 nginx 웹 서버에서 로드 밸런싱을 설정하면 됩니다.

$ netstat -anp | grep LISTEN

로드 밸런싱을 진행하기 전에, 우리가 주어준 3000 ~ 5000번 포트의 서비스와 nginx 웹 서버가 네트워크 상에서 잘 동작하고 있는지 확인해줍니다.

 

 

그런 다음 nginx 설정 파일을 위와 같이 만들어줍니다. 설정 파일에 대해 간단히 설명을 드리자면, upstream은 서비스하고자 하는 서비스들의 주소를 나열하는 곳입니다. upstream의 뜻은 상류라는 뜻인데, nginx가 상류에 있고, 클라이언트로부터 요청을 받으면 해당 upstream에서 아래로 뿌려질 서비스를 나열한다고 생각하면 쉽게 이해할 수 있습니다. 그리고 upstream 옆에는 해당 상류가 어떤 서비스인지 태깅할 수 있는 이름을 적으시면 됩니다.

nginx는 정적 페이지 로딩에 최적화된 웹 서버입니다. 따라서 뒤에 붙여지는 정적 엔드포인트에 대해서 분산 처리를 할 수 있습니다. 우리는 최상위 엔드포인트에 대해 로드 밸런싱을 수행하도록 합니다. 여기에 proxy_pass를 이용하여 nginx를 프록시 서버로 삼고 upstream 리스트에 있는 서버로 통과시키도록 설정하면 됩니다.

그럼 새로고침할 때마다 접속 인스턴스가 계속 바뀌고 있는 것을 알 수 있습니다.

 

 

nginx의 로드 밸런싱 방식

우리는 nginx 웹 서버를 이용하여 Docker 컨테이너에 담겨진 서비스를 로드 밸런싱을 하였습니다. 따라서 nginx가 로드 밸런싱을 수행하고, 우리가 원하는 서비스는 Docker 컨테이너에서 받는 원리인 것이죠.

그렇다면 nginx 웹 서버가 제공하는 로드 밸런싱 방식에 대해 공부할 필요가 있을 것입니다. 왜냐하면 지금 우리는 새로고침마다 인스턴스를 바꿔가며 접속하였지만 데이터가 이들끼리 공유되지 않는다면 문제가 생기겠죠?

  • RR (Round-Robin, default): 처음부터 차례대로 선택하는 알고리즘
  • hash: hash 값으로 분배하는 알고리즘 ex) hash $remote_address로 ip_hash 결정
  • ip_hash: 접속한 IP 주소를 hashing 하여 분배
  • random: 무작위로 분배
  • least_conn: WLC 알고리즘과 동일 (접속수가 적은 수를 분배하되, 가중치 설정)
  • least_time: 연결수가 적으면서 평균 응답 시간이 가장 적은쪽을 선택하여 분배 (SED 알고리즘과 유사)

기존 로드 밸런싱 방식과 거의 비슷한 형태의 알고리즘을 제공하는 것도 있고, 아예 새로운 방법으로 제공하는 알고리즘도 있습니다. 따라서 이들에 알맞게 사용하는 것이 가장 현명한 선택일 것입니다

만약, ip_hash를 준다고 한다면..

접속한 IP와 매핑시킨 upstream을 해쉬값으로 저장하여 분배하기 때문에 한 번 접속한 IP는 항상 이 인스턴스로만 접속하게 됩니다.

만약 가중치가 있는 알고리즘을 사용할 경우에는 어떻게 하면 될까요?

이렇게 최소 연결 알고리즘을 선택한 뒤, 원하는 인스턴스 주소 옆에 파라미터를 주어서 설정할 수 있습니다. 여기에 설정할 수 있는 파라미터는 아래와 같습니다.

  • weight: 가중치를 설정하여 더 많은 사용자를 받도록 하는 파라미터
  • max_conns: 최대 가용 연결 수를 지정하는 파라미터
  • max_fails: 최대 실패 횟수를 지정하는 파라미터, 최대 실패 횟수가 도달할 경우, nginx가 더 이상 이 인스턴스를 분배하지 않음
  • fail_timeout: 응답 최소 시간을 지정하는 파라미터, 응답 시간이 지나면 실패 횟수 카운트가 증가됨
  • backup: 해당 인스턴스를 백업 서버로 설정, 다른 메인 서버가 응답하지 않을 경우 사용하지만 hash나 random 알고리즘을 사용할 경우에는 동작하지 않음
  • down: 해당 서버는 죽었음을 표시하고, 분배하지 않음

다양한 파라미터를 활용해서 인스턴스 분배 방식을 설정할 수 있는 점은 메리트 있어 보이네요.

 

 

마치며...

여기까지 nginx 웹 서버를 이용하여 Docker 컨테이너를 로드 밸런싱하는 방법에 대해 알아봤습니다.

그런데, nginx 웹 서버가 비교적 가볍고, 사용하기 편한 장점은 있지만 굳이 로드 밸런싱을 위해서 nginx 웹 서버를 사용해만할까요? 꼭 그렇지만은 않습니다. Docker 컨테이너를 이용해서 로드 밸런싱을 할 수 있는 방법은 여러가지가 있는데, 

다음 포스트에서는 HAProxy를 이용하여 Docker 컨테이너를 로드 밸런싱할 수 있는 방법에 대해 적어보도록 하겠습니다.

comments powered by Disqus

Tistory Comments 0