nginx 부하분산

Posted by 주원이^^
2016.11.01 16:03 리눅스/웹서버(WAS)

 

1. Proxy에 대하여

Proxy 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 연결하게 중계해주는 소프트웨어다.

 

 프락시 서버 개요

 

웹 서비스를 예로 들어보자. 클라이언트(웹 브라우저)는 웹 서버에 직접 연결하는 대신에 프락시 서버에 연결해서 웹 페이지를 요청한다. 이 요청을 읽은 프락시 서버는 웹 서버에 요청을 전달하고, 응답을 받아서 클라이언트에 전송한다. 인터넷 서비스의 규모가 커지면서, 분산 시스템으로 서비스가 구성되는 경우가 많다. 프락시 서버를 이용하면 분산 시스템을 뒤에 숨기는 방식으로 시스템을 단순화 할 수 있다.

 

클라우드 기반 시스템에 서비스를 구축할 경우, 분산 시스템으로 구축하는 경우가 많다. 클라우드 시스템 구축의 핵심 요소며, 사용자 입장에서도 이래 저래 응용할 거리가 많다.

 

2. 클라우드 환경에서의 프락시

가상화와 클라우드 관련된 일을 하고 있다. 과거에는 직접 만드는 일을 했었고, 최근 들어서는 클라우드 인프라 위에서 서비스를 만드는 일을 하고 있다. 클라우드를 기반으로 하는 서비스들은 높은 확률로 분산이 된다. 또한 서비스를 구성하는 자원(데이터베이스, 인스턴스, 컨테이너 등)은 인터넷으로 부터 격리된 공간에 만들어 진다. 따라서 인터넷으로 부터의 요청을 받아서 내부(AWS의 경우 VPC)의 분산된 자원에 요청을 전송하는 프락시가 매우 중요하다.

 

다양한 프락시 타입 중에서 HTTP를 기반으로 하는 리버스 프락시에 대해서 살펴볼 생각이다.

 

3. 리버스 프락시

리버스 프락시는 일반적인 인터넷 서비스에서 널리 사용하고 있다. 리버스 프락시는 유저의 요청을 받아서 반대편(reverse)네트워크에 있는 인터넷 서버에 전달 하는 일을 한다. 리버스 프락시 서버는 단순히 요청을 전달하기만 할 뿐으로 요청의 처리는 뒷단에 있는 웹 서버들이 맡아서 한다. 따라서 하나의 리버스 프락시 서버가 여러 웹 서버로 요청을 전달하도록 구성 할 수 있다. 예컨데 로드 밸런서로의 역할을 수행 할 수 있다. 실제 HAProxy, NginX, Apache 웨서버들이 가지고 있는 리버스 프락시 기능을 이용해서 소프트웨어 기반의 로드밸런싱 환경을 구축하기도 한다.

 

소프트웨어 기반인 만큼 전용 로드밸런서 보다는 성능이 떨어질 수 있지만, 저렴한 비용과 이에 따르는 무지막지한 확장성으로 단점을 커버하고 있다. 클라우드 환경에서 사용할 로드밸런서라면 소프트웨어로 구축하는게 거의 당연하게 여겨진다.

 

4. 리버스 프락시 테스트 환경

처음엔 오픈소스 프락시로 HAProxy를 생각했다. HAproxy는 로드밸런서로 사용하기에는 괜찮은 선택이었으나 다양한 활용이 필요한 리버스 프락시로 사용 하기에는 기능에 한계가 있었다. 그래서 NginX를 사용하기로 했다. NginX외에 Apache를 사용 할 수도 있었겠는데, 웹서버로서의 다양한 기능 보다는 NginX의 성능이 더 중요했다.

 

VirtualBox로 테스트 환경을 만들었다.

 

 Reverse Proxy를 위한 테스트 환경

 

  • 두 개의 호스트 전용 네트워크를 만들었다. 하나는 외부에 연결하기 위해서 사용하고 다른 하나는 내부에 있는 웹 서버에 연결하기 위해서 사용한다.
  • Proxy server는 유저의 요청을 받아서 밑에 있는 web-01과 web-02로 요청을 중계한다.

5. 정적 리버스 프락시 서버 구성

가장 기본적인 리버스 프락시 구성이다. 일반적인 로드밸런서라고 보면 된다. upstream영역에 프락시 대상 호스트의 목록을 설정하면 된다. upstream은 proxy할 타겟 서버를 설정하기 위해서 사용한다.
1
2
3
4
upstream test_proxy {
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
test_proxy는 upstream의 이름이다. NginX는 하나 이상의 upstream으로 구성할 수 있으며, 이름으로 구분할 수 있다.

 

이제 test_proxy를 upstream으로 사용하도록 server 설정을 변경하면 된다. 아래는 완전한 설정파일이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# cat /etc/nginx/sites-available/default
upstream test_proxy {
server web-01;
server web-02;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
proxy_pass http://test_proxy;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
location / 에 대한 모든 요청에 대해서, test_proxy로 중계하라고 설정했다.

 

5.1. 로드 밸런싱 메서드

Nginx는 4개의 로드밸런싱 메서드를 제공한다.

 

라운드로빈(Round-robin)은 기본으로 사용하는 메서드로 모든 서버에 동등하게 요청을 분산한다.
1
2
3
4
upstream test_proxy {
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

least_conn은 연결이 가장 작은 서버로 요청을 보낸다.
1
2
3
4
5
6
upstream test_proxy {
least_conn;
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

ip_hash는 클라이언트 IP주소를 기준으로 요청을 분배한다. IP주소가 같다면, 동일한 서버로 요청을 전송한다.
1
2
3
4
5
6
upstream test_proxy {
ip_hash;
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

hash는 유저가 정의한 key나 변수 혹은 이들의 조합을 해시해서 분산한다. key로 소스 IP나 포트 URI 등을 사용 할 수 있다.
1
2
3
4
5
6
upstream test_proxy {
hash $request_uri consistent;
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
hashconsistent파라메터를 사용 할 수 있다. consistent를 사용하면 Ketama 컨시스턴시 해시 알고리즘을 이용해서 upstream 그룹에 서버가 추가 되거나 삭제 될 때, 키의 분배를 최소화 함으로써 캐시 실패를 줄일 수 있다.

 

least_time메서드는 NginX Plus에서 지원한다. 평균 레이턴시와 연결을 기준으로 검사해서 로드가 적은 서버로 요청을 보낸다.
1
2
3
4
5
6
upstream test_proxy {
least_time header;
server web-01;
server web-02;
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
header는 첫번째 바이트를 받을 때까지의 지연, last_byte는 모든 요청을 받을 때까지의 지연시간을 기준으로 삼는다.

 

6. 프락시 응용

정적 프락시는 upstream 서버가 결정되 있다. 일반적인 웹 서비스라면 이 정도로 사용하는데 문제 없을 것이다. server가 추가되거나 삭제 할 경우 upstream 그룹을 수정해야 하겠지만 수정이 빈번하지 않으니 문제될게 없다.

 

하지만 클라우드 환경에서는 프락시 설정이 동적으로 이루어져야 할 필요가 있다. 컨테이너 기반으로 웹 서비스를 제공한다면, 빈번하게 프락시 설정이 바뀔 수 있을 것이다. 웹 서비스들이 수시로 올라올 테고, 웹 서비스를 위한 도메인 이름도 계속 변경될 것이다. 이런 환경에서의 프락시 응용에 대해서 살펴보려 한다.

 

6.1. 도메인 이름 기반 dynamic Proxy

Slack이나 Jira와 같은 클라우드 기반의 인터넷 서비스를 한다고 가정해보자. 유저가 클라우드 서비스를 구축하면, 도메인도 함께 만들어줘야 할 것이다. 예컨데 jira라면, user-01.jira.com, user-02.jira.com과 같이 유저별로 도메인을 제공해야 한다. 대략 아래와 같은 구성이 될 것이다.

 

 Domain Name 기반 Proxy

 

이 방식의 서비스를 위해서는 DNS 서버의 도움이 필요하다. DNS 서버에 아스테리크(*)도메인에 대해서 Proxy 서버를 CNAME 혹은 A 레코드에 등록한다. 호스팅 업체를 통해서 DNS 서비스를 받는다면, 아스테리크를 지원하는지 확인을 해야 한다. 대부분이 아스테리크를 지원하지만 몇 몇 지원하지 않는 호스팅 업체도 있다.

 

이 서비스는 다음과 같이 작동 한다. Jira를 기준으로 설명한다.
  1. 유저가 user_01이라는 이름으로 jira 서비스를 구입했다. Jira는 user_01.jira.com 도메인을 유저에게 할당한다.
  2. Jira는 user_01.jira.com을 위한 서비스 인스턴스를 만들고 내부 DNS에 이 정보를 등록한다.
  3. 유저가 브라우저로 user_01.jira.com에 접속하려 한다.
  4. ns.jira.com에 네임 정보를 요청한다. ns.jira.com은 *.jira.com에 등록된 Proxy의 IP를 되돌려 준다.
  5. 유저는 Proxy 서버에 접속한다.
  6. Proxy Server는 HTTP의 Host 필드의 user_01.jira.com을 읽어서, 이 도메인에 대한 IP를 찾는다.
  7. user_01.jira.com에 대한 인스턴스 IP로 요청을 proxy 한다.
이 서비스를 테스트 하려면, DNS 서버를 구축해야 한다. 꽤나 복잡 할 것 같지만 dnsmasq를 이용하면 간단하게 테스트 환경을 만들 수 있다. 아래는 테스트 환경이다.

 

 dnsmasq를 이용한 네임서버 기반 proxy

 

테스트를 위해서 4개의 인스턴스를 만들었다. 먼저 DNS Instance에 dnsmasq를 설치한다.
1
# apt-get install dnsmasq
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

*.test.priv에 대해서 proxy server ip를 알려주도록 설정을 변경했다.
1
2
3
# cat /etc/dnsmasq.conf
....
address=/.test.priv/192.168.56.10
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
이제 *.test.priv 요청에 대해서 NginX Proxy server의 IP를 반환한다.

 

그리고 web-01.test.priv와 web-02.test.priv를 등록한후 dnsmasq 서버를 리로드 했다.
1
2
3
4
# cat /etc/hosts
...
192.168.57.3 web-01.test.priv
192.168.57.4 web-02.test.priv
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

호스트 운영체제에서 curl로 테스트 할 생각이다. /etc/resolv.conf 파일을 수정했다.
1
2
3
4
# cat /etc/resolv.conf
nameserver 192.168.56.10
# service nginx reload
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

네임을 제대로 찾는지 테스트를 했다.
1
2
3
4
5
6
# nslookup web-01.test.priv
Server: 192.168.56.10
Address: 192.168.56.10#53
Name: web-01.test.priv
Address: 192.168.56.2
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

 

이제 proxy server를 설정한다. nginx를 설치 한 후 설정파일을 수정했다.
1
2
3
4
5
6
7
8
9
10
# cat /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
resolver 192.168.56.10;
location / {
proxy_pass http://$request_uri;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
resolver로 앞서 만든 dns server를 설정했다. nginx를 restart 한 뒤, 호스트 운영체제에서 테스트를 수행했다.
1
2
3
4
# curl web-01.test.priv
<h1>WEB-01</h1>
# curl web-02.test.priv
<h1>WEB-02</h1>
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
잘 된다. 도메인 이름 기반 Proxy는 PaaS나 SaaS 환경을 구축 하려고 할 때, 특히 유용하게 사용 할 수 있을 거다.

 

이 방식의 유일한 문제점은 아마도 dnsmasq 서버의 유지 보수일 것이다. 새로운 도메인이 추가 되면 /etc/hosts를 변경 한 다음 realod를 해줘야 한다. 단순한 파일 관리기는 하지만 그다지 깔끔하지는 않다.

 

SaaS/PassS 환경을 구축하려면 service discovery 시스템을 만들어야 한다. 아마도 DNS 기반으로 만들어야 할 건데, dnsmasq 클러스터를 구성 해야 할 것이다. 주키퍼를 이용해서 구축 할 수 있겠다.

 

 key/value 기반의 proxy 환경

 

  1. 주키퍼는 dnsmasq 노드들을 관리한다.
  2. 도메인 정보는 주키퍼에 저장된다.
  3. dnsmasq가 주키퍼에 저장된 도메인 정보로 /etc/hosts를 재구성하기는 쉽지 않을 것이다. 별도의 애플리케이션으로 /etc/hosts를 재구성하고 dnsmasq를 reload 한다.
  4. 다이나믹하게 도메인 정보가 바뀌는 서비스에서 dnsmasq는 효율적이지 않다. 새로 만드는 것도 생각해봐야 겠다.
Saas/PaaS를 위한 DNS 기반의 service discovery 시스템은 간단하게 다룰 만한 내용은 아니다. 언젠가 제대로 고민을 해봐야 겠다. 지금은 이정도로 하고 넘어간다.

 

6.2. 정규표현을 이용한 dynamic proxy

컨테이너를 기반으로 하는 SaaS 인프라를 개발한다고 가정해보자. 이 인프라에는 host_01, host_02, host_03 3개의 호스트가 있다. 유저가 SaaS 서비스인 워드프레스(wordpress)를 요청하면, 3개의 호스트 중 적당한 호스트를 골라서 워드프레스 컨테이너를 배치한다. 이제 유저 요청이 들어오면, 유저에게 할당한 컨테이너로 프락시해줘야 한다. 정규표현을 이용해서 프락시를 해보기로 하자.

 

 정규표현을 이용한 다이나믹 프락시

 

프로세스는 다음과 같다.
  1. 유저 컨테이너를 만들고 나면, 컨테이너를 위한 도메인을 등록한다. 도메인 이름에는 애플리케이션 이름, 컨테이너의 ID, 호스트 번호등이 들어간다. wordpress_container001_01.test.priv에는 host_01에 있는 container001로 프락시하라 라는 정보가 담겨있다. 정규표현식을 이용해서 처리 할 수 있을 것이다.
  2. 물론 *.test.priv는 로드밸런서(LB)로 향하도록 도메인 작업을 해둬야 한다. 약간 응용하면 dnsmasq로 만들 수 있다. 이 과정은 다루지 않겠다.
  3. 유저 요청은 로드밸런서 밑에 있는 NginX 프락시 서버로 이동한다. NginX 프락시 서버는 정규표현식을 이용해서 프락시할 호스트의 위치를 찾는다.
  4. 유저 요청은 NginX에 의해서 워드프레스 컨테이너가 있는 호스트로 전달된다. 이 호스트에는 역시 NginX 기반으로 된 Local Proxy가 있는데, 정규표현식을 이용해서 컨테이너의 이름을 찾아서 프락시 한다.
NginX Proxy Server의 설정은 대략 다음과 같다.
1
2
3
4
5
6
7
8
9
server {
server_name ~^([a-zA-Z0-9]+)_([a-zA-Z0-9]+)_([0-9]+)\.test\.org$;
resolver 127.0.0.1;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_pass http://host_$3;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$1은 SaaS 애플리케이션 이름, $2는 컨테이너 ID, $3은 호스트 번호다. 예를 들어서 http://wordpress_container001_01.test.priv 요청이 들어오면, http://host_01로 요청을 프락시 한다.

'리눅스 > 웹서버(WAS)' 카테고리의 다른 글

nginx 부하분산  (0) 2016.11.01
웹서버 분산 세션공유  (0) 2014.06.11
이 댓글을 비밀 댓글로

웹서버 분산 세션공유

Posted by 주원이^^
2014.06.11 11:13 리눅스/웹서버(WAS)

L4, LVS, DNS RR 등으로 여러대의 웹서버를 운영하다보면
늘 고민하게 되는게 세션 공유일 것 같다.

물론 L4의 Metric 중에 Hash를 이용하면 어느정도 해결 될 수 있는 문제이지만,
가끔 클라이언트가 다른 서버로 세션이 할당되는 경우를 볼 수 있다.

나 또한 이 문제로 많은 고민을 했고, 여러 많은 HOW를 보았지만 썩 마음에 드는것이 없었다.
아마 세션 공유를 위해서 사용되는 방법은 아래의 3가지가 가장 보편적이지 않을까 싶다.

1. NFS, Samba
특정 서버의 디렉토리를 다수의 웹서버에서 Network를 통해 공유하는 것이다.
그럼 한곳의 디렉토리에 모든 세션 파일이 존재할 것이고 문제는 해결이 된다.
그렇지만 NFS나 Samba는 Write 효율성이 상당히 떨어지는 데몬이다.
세션은 그 특성상 read/write가 매우 빈번히 일어나기 때문에 효율성 면에서는 좋지 않다.
(개인적으로 이 방법을 사용하다가 피본적이 있다-_-;;)

2. DBMS
세션 데이터를 DBMS와 연동하여 DB에 저장하는 방식이다.
보편적으로 주로 사용되는 방법이지만, 접속자가 매우 많은 환경에서는
DB서버에 부하가 늘어날 수 밖에 없는 구조이다.
또한 DBMS에서 생기는 lock도 무시못할 변수로 떠오를 수 있다.

3. Daemon
말 그대로 데몬을 이용하여 세션을 공유하는 방식이다.
세션서버를 따로 구축하기에 제일 적절한 방법이 아닐까 싶다.

늘 그렇지만.. 난 언제나 서론이 참 길다.
오늘은 sharedance 라는 데몬을 소개하고자 한다.
지인의 소개로 알게된 데몬인데..
약 2년정도를 15대의 웹서버에 적용해서 사용하고 계신다는데.. 별 문제는 없으시다고 한다.
그래서 현재 우리 회사에서도 적용중인 데몬이다.

1. 세션 서버 구성
이 데몬은 세션 서버로 사용될 서버에만 설치해주면 된다. 웹서버에는 설치할 필요가 없다.
sharedance의 최신버전은 http://sharedance.pureftpd.org/ 여기서 구할 수 있으며,
RPM으로 구하고 싶다면 http://rpm.pbone.net/ 에서 sharedance로 검색하면 된다.

1-1. RPM 설치

설치 끝났다. 참 쉽다. -_-

1-2. config 설정
sharedance의 환경설정 파일은 /etc/sysconfig/sharedance 에서 해주면 된다.
설정할 내용이 많은 것도 아니고, 달랑 2개면 된다.

SHAREDANCE_DIR="/var/lib/sharedance"
SHAREDANCE_OPTIONS="--expiration=1800 --timeout=1800"

SHAREDANCE_DIR 은 세션 파일이 저장될 디렉토리를 의미한다.
SHAREDANCE_OPTIONS 는 데몬을 시작할때 사용할 옵션을 지정해주는 것이다.
어떤 옵션들이 있는지 보고 싶다면 sharedanced --help 라고 치면 나온다.

1-3. sharedance 시작

[root@localhost]# /etc/init.d/sharedance start

이제 sharedance 사용 준비가 모두 완료 되었다.


2. Client 설정 (웹서버)
웹서버는 크게 설정하거나 설치할 내용이 없다.
단순히 php.ini에서 sharedance의 handler 파일만 호출해주도록 설정하면 된다.

2-1. handler 파일 준비
이 파일을 직접 만들 필요는 없고, sharedance 에서 제공하는 파일을 그대로 가져다 쓰면 된다.
세션서버에서 sharedance를 RPM으로 설치했다면,
/usr/share/doc/sharedance-0.6/php/ 에 핸들러 파일이 존재할 것이다.

[root@localhost]# ls /usr/share/doc/sharedance-0.6/php
session_handler.php sharedance.php test_session.php test_sharedance.php

필요한 파일은 session_handler.php와 sharedance.php 파일이다.
이 2개의 파일을 웹서버로 전송한다.

2-2. handler 파일 복사 및 설정
세션서버에서 전송받은 핸들러 파일을 적당한곳에 잘 복사해두면 된다.
필자는 편의상 서버 운영에 필요한 파일을 한곳에 몰아서 관리하고 있기 때문에
/etc/sadmin/httpd/sharedance 에 복사를 했다.

핸들러 파일이 세션서버를 이용할 수 있도록, 핸들러 파일을 약간 수정해야 된다.
그렇지만 그리 어려운 내용은 아니기 때문에 쉽게 수정할 수 있다.

[root@localhost]# vi /etc/sadmin/httpd/sharedance/session_handler.php
define('SESSION_HANDLER_HOST', '192.168.0.100');

이 부분은 세션서버를 지정해주는 부분이다.
만약 세션서버가 같은 서버에서 돌고 있다면 localhost로 지정하면 될 것이고,
다른 서버에서 돌고 있다면 그 서버의 ip 혹은 host를 적어주면 된다.

[root@localhost]# vi /etc/sadmin/httpd/sharedance/sharedance.php
define('SHAREDANCE_DEFAULT_PORT', 1042);
define('SHAREDANCE_DEFAULT_TIMEOUT', 10);

이 부분은 세션서버의 listening port와 timeout 시간을 설정해주는 부분이다.
만약 세션서버에서 데몬의 포트가 다르게 설정되어 있다면 이곳에서 수정을 해주면 된다.

2-3. php.ini 설정
이제 모든 준비가 끝났으므로 웹서버의 PHP에서 session을 이용할때
sharedance의 핸들러를 통해 세션서버와 통신하도록 설정해주기만 하면 된다.

[root@localhost]# vi /etc/php.ini
...
auto_prepend_file = /etc/sadmin/httpd/sharedance/session_handler.php
...
session.save_handler = user

php.ini 수정이 모두 끝났으면 웹서버를 재시작하여 변경된 php.ini의 내용을 reload 시킨다.

3. sharedance 확인
모든 설정과 준비가 끝났다.
세션서버에 세션파일이 잘 생성 되는지 확인을 해보자.

[root@localhost]# ls /var/lib/sharedance
01c6hof0u872jp3grka4ifo3u4 4u44789jq4vtjt7l2fp3t1ime6 e7d6nq244j27l8bllif792pp25
...
...

위와 같이 세션파일이 생성이 된다면, 정상적으로 웹서버와 세션서버가 통신하고 있는 것이다

'리눅스 > 웹서버(WAS)' 카테고리의 다른 글

nginx 부하분산  (0) 2016.11.01
웹서버 분산 세션공유  (0) 2014.06.11