웹사이트 검색

HAProxy를 사용하여 Ubuntu VPS에서 HTTP 부하 분산을 설정하는 방법


HAProxy 정보

HAProxy(High Availability Proxy)는 모든 TCP 서비스를 로드 밸런싱할 수 있는 오픈 소스 로드 밸런서입니다. 세션 지속성과 계층 7 처리를 지원하므로 HTTP 로드 밸런싱에 특히 적합합니다.

DigitalOcean Private Networking을 사용하면 HAProxy를 프런트 엔드로 구성하여 프라이빗 네트워크 연결을 통해 두 VPS의 부하를 분산할 수 있습니다.

전주곡

여기서는 3개의 VPS(드롭릿)를 사용합니다.

드롭릿 1 - 로드 밸런서

물방울 2 - 노드 1

물방울 2 - 노드 2

HAProxy 설치

apt-get 명령을 사용하여 HAProxy를 설치합니다.

apt-get install haproxy

init 스크립트로 시작하려면 HAProxy를 활성화해야 합니다.

nano /etc/default/haproxy

ENABLED 옵션을 1로 설정합니다.

ENABLED=1

이 변경이 제대로 수행되었는지 확인하려면 매개변수 없이 HAProxy의 초기화 스크립트를 실행하십시오. 다음이 표시되어야 합니다.

root@haproxy:~# service haproxy
Usage: /etc/init.d/haproxy {start|stop|reload|restart|status}

HAProxy 구성

기본 구성 파일을 이동하고 고유한 구성 파일을 만듭니다.

mv /etc/haproxy/haproxy.cfg{,.original}

새 구성 파일을 만들고 편집합니다.

nano /etc/haproxy/haproxy.cfg

이 파일에 블록별로 구성 블록을 추가하여 시작하겠습니다.

global
	log 127.0.0.1 local0 notice
	maxconn 2000
	user haproxy
	group haproxy

log 지시문은 로그 메시지가 전송될 syslog 서버를 언급합니다. Ubuntu에서 rsyslog는 이미 설치되어 실행 중이지만 어떤 IP 주소에서도 수신 대기하지 않습니다. 나중에 rsyslog의 구성 파일을 수정할 것입니다.

maxconn 지시문은 프런트엔드의 동시 연결 수를 지정합니다. 기본값은 2000이며 VPS 구성에 따라 조정해야 합니다.

usergroup 지시문은 HAProxy 프로세스를 지정된 사용자/그룹으로 변경합니다. 변경해서는 안 됩니다.

defaults
	log     global
	mode    http
	option  httplog
	option  dontlognull
	retries 3
	option redispatch
	timeout connect  5000
	timeout client  10000
	timeout server  10000

이 섹션에서는 기본값을 지정합니다. 수정할 값은 다양한 timeout 지시어입니다. connect 옵션은 VPS에 대한 연결 시도가 성공할 때까지 기다리는 최대 시간을 지정합니다.

클라이언트서버 시간 초과는 클라이언트 또는 서버가 TCP 프로세스 중에 데이터를 확인하거나 보낼 것으로 예상되는 경우에 적용됩니다. HAProxy는 클라이언트서버 시간 초과를 동일한 값으로 설정할 것을 권장합니다.

retries 지시문은 연결 실패 후 VPS에서 수행할 재시도 횟수를 설정합니다.

redispatch 옵션은 연결 실패 시 세션 재분배를 활성화합니다. 따라서 VPS가 다운되면 세션 고정이 무시됩니다.

listen appname 0.0.0.0:80
	mode http
	stats enable
	stats uri /haproxy?stats
	stats realm Strictly\ Private
	stats auth A_Username:YourPassword
	stats auth Another_User:passwd
	balance roundrobin
	option httpclose
	option forwardfor
	server lamp1 10.0.0.1:80 check
	server lamp2 10.0.0.2:80 check

여기에는 프런트엔드와 백엔드 모두에 대한 구성이 포함됩니다. 응용 프로그램을 식별하기 위한 이름인 appname에 대해 포트 80에서 수신 대기하도록 HAProxy를 구성하고 있습니다. stats 지시어는 연결 통계 페이지를 활성화하고 stats auth 지시어에 지정된 자격 증명을 사용하여 HTTP 기본 인증으로 페이지를 보호합니다.

이 페이지는 stats uri에 언급된 URL로 볼 수 있으므로 이 경우에는 http://1.1.1.1/haproxy?stats입니다. 이 페이지의 데모는 여기에서 볼 수 있습니다.

balance 지시문은 사용할 부하 분산 알고리즘을 지정합니다. 사용 가능한 옵션은 라운드 로빈(roundrobin), 정적 라운드 로빈(static-rr), 최소 연결(leastconn), 소스( source), URI(uri) 및 URL 매개변수(url_param).

각 알고리즘에 대한 정보는 공식 문서에서 얻을 수 있습니다.

server 지시문은 백엔드 서버를 선언하며 구문은 다음과 같습니다.

server <name> <address>[:port] [param*]

여기에서 언급한 이름은 로그와 경고에 나타납니다. 이 지시문에서 지원하는 많은 매개변수가 있으며 이 문서에서는 checkcookie 매개변수를 사용할 것입니다. check 옵션은 VPS에서 상태 확인을 활성화합니다. 그렇지 않으면 VPS는

구성을 완료하면 HAProxy 서비스를 시작합니다.

service haproxy start

부하 분산 및 장애 조치 테스트

이 설정을 테스트하려면 모든 웹 서버(여기서는 백엔드 서버 - LAMP1 및 LAMP2)에서 PHP 스크립트를 생성하십시오.

<코드>/var/www/file.php

<?php
header('Content-Type: text/plain');
echo "Server IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR'];
?>

이제 우리는 curl을 사용하고 이 파일을 여러 번 요청할 것입니다.

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.2
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

> curl http://1.1.1.1/file.php

Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X

여기에서 HAProxy가 LAMP1과 LAMP2 사이의 연결을 번갈아 토글하는 방법에 주목하십시오. 이것이 라운드 로빈이 작동하는 방식입니다. 여기에 표시되는 클라이언트 IP는 로드 밸런서의 개인 IP 주소이고 X-Forwarded-For 헤더는 사용자의 IP입니다.

장애 조치 작동 방식을 확인하려면 웹 서버로 이동하여 서비스를 중지합니다.

lamp1@haproxy:~#service apache2 stop

작동 방식을 확인하려면 curl로 요청을 다시 보내세요.

세션 고정

웹 애플리케이션이 사용자의 로그인 세션을 기반으로 동적 콘텐츠를 제공하는 경우(애플리케이션은 그렇지 않음) 방문자는 VPS 간의 지속적인 전환으로 인해 이상한 일을 경험하게 될 것입니다. 세션 고정성은 방문자가 첫 번째 요청을 제공한 VPS에 고정되도록 합니다. 쿠키로 각 백엔드 서버에 태그를 지정하면 가능합니다.

세션 고정이 어떻게 작동하는지 보여주기 위해 다음 PHP 코드를 사용할 것입니다.

<코드>/var/www/session.php

<?php
header('Content-Type: text/plain');
session_start();
if(!isset($_SESSION['visit']))
{
        echo "This is the first time you're visiting this server";
        $_SESSION['visit'] = 0;
}
else
        echo "Your number of visits: ".$_SESSION['visit'];

$_SESSION['visit']++;

echo "\nServer IP: ".$_SERVER['SERVER_ADDR'];
echo "\nClient IP: ".$_SERVER['REMOTE_ADDR'];
echo "\nX-Forwarded-for: ".$_SERVER['HTTP_X_FORWARDED_FOR']."\n";
print_r($_COOKIE);
?>

이 코드는 PHP 세션을 생성하고 단일 세션의 페이지 보기 수를 표시합니다.

쿠키 삽입 방법

이 방법에서 클라이언트에 대한 HAProxy의 모든 응답에는 백엔드 서버의 이름이 쿠키 값인 Set-Cookie: 헤더가 포함됩니다. 따라서 앞으로 클라이언트(웹 브라우저)는 모든 요청과 함께 이 쿠키를 포함하고 HAProxy는 쿠키 값을 기반으로 요청을 올바른 백엔드 서버로 전달합니다.

이 방법의 경우 cookie 지시문을 추가하고 listen에서 server 지시문을 수정해야 합니다.

   cookie SRVNAME insert
   server lamp1 10.0.0.1:80 cookie S1 check
   server lamp2 10.0.0.2:80 cookie S2 check

이로 인해 HAProxy는 값이 S1 또는 S2SRVNAME이라는 쿠키가 있는 Set-Cookie: 헤더를 추가합니다. > 요청에 응답한 백엔드를 기반으로 합니다. 이것이 추가되면 서비스를 다시 시작하십시오.

service haproxy restart

curl을 사용하여 이것이 어떻게 작동하는지 확인하십시오.

> curl -i http://1.1.1.1/session.php
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:22 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Set-Cookie: PHPSESSID=l9haakejnvnat7jtju64hmuab5; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 143
Connection: close
Content-Type: text/plain
Set-Cookie: SRVNAME=S1; path=/

This is the first time you're visiting this server
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
)

이것은 우리가 만든 첫 번째 요청이며 Set-Cookie: SRVNAME=S1;에서 볼 수 있듯이 LAMP1에서 응답했습니다. 경로=/. 이제 다음 요청에 대해 웹 브라우저가 수행하는 작업을 에뮬레이션하기 위해 curl의 --cookie 매개변수를 사용하여 이러한 쿠키를 요청에 추가합니다.

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain

Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
    [PHPSESSID] => l9haakejnvnat7jtju64hmuab5
    [SRVNAME] => S1
)

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=l9haakejnvnat7jtju64hmuab5;SRVNAME=S1;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:11:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 183
Connection: close
Content-Type: text/plain

Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.87.127
Array
(
    [PHPSESSID] => l9haakejnvnat7jtju64hmuab5
    [SRVNAME] => S1
)

이 두 요청은 모두 LAMP1에서 처리했으며 세션이 적절하게 유지되었습니다. 이 방법은 웹 서버의 모든 파일을 고정하려는 경우에 유용합니다.

쿠키 접두사 방법

반면에 특정 쿠키에 대해서만 고정을 원하거나 세션 고정을 위한 별도의 쿠키를 원하지 않는 경우 prefix 옵션이 적합합니다.

이 방법을 사용하려면 다음 cookie 지시문을 사용하십시오.

cookie PHPSESSID prefix

PHPSESSID는 자신의 쿠키 이름으로 바꿀 수 있습니다. server 지시문은 이전 구성과 동일하게 유지됩니다.

이제 이것이 어떻게 작동하는지 봅시다.

> curl -i http://1.1.1.1/session.php
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:27 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Set-Cookie: PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 143
Content-Type: text/plain

This is the first time you're visiting this server
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
)

server 쿠키 S1이 세션 쿠키에 접두사로 붙는 방식에 주목하십시오. 이제 이 쿠키를 사용하여 두 개의 추가 요청을 보내겠습니다.

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:45 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain

Your number of visits: 1
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
    [PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)

> curl -i http://1.1.1.1/session.php --cookie "PHPSESSID=S1~6l2pou1iqea4mnhenhkm787o56;"
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2013 13:36:54 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: PHP/5.3.10-1ubuntu3.8
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 163
Content-Type: text/plain

Your number of visits: 2
Server IP: 10.0.0.1
Client IP: 10.0.0.100
X-Forwarded-for: 117.213.X.X
Array
(
    [PHPSESSID] => 6l2pou1iqea4mnhenhkm787o56
)

우리는 두 요청이 모두 LAMP1에 의해 제공되었고 세션이 완벽하게 작동함을 분명히 볼 수 있습니다.

HAProxy에 대한 로깅 구성

HAProxy 구성을 시작할 때 syslog 메시지를 localhost IP 주소로 보내는 log 127.0.0.1 local0 notice 줄을 추가했습니다. 그러나 기본적으로 우분투의 rsyslog는 어떤 주소도 수신하지 않습니다. 그래서 우리는 그렇게 해야 합니다.

rsyslog의 구성 파일을 편집합니다.

nano /etc/rsyslog.conf

다음 줄을 추가/편집/주석 해제합니다.

$ModLoad imudp
$UDPServerAddress 127.0.0.1
$UDPServerRun 514

이제 rsyslog는 주소 127.0.0.1의 UDP 포트 514에서 작동하지만 모든 HAProxy 메시지는 /var/log/syslog로 이동하므로 메시지를 분리해야 합니다.

HAProxy 로그에 대한 규칙을 만듭니다.

nano /etc/rsyslog.d/haproxy.conf

다음 행을 추가하십시오.

if ($programname == 'haproxy') then -/var/log/haproxy.log

이제 rsyslog 서비스를 다시 시작하십시오.

service rsyslog restart

그러면 모든 HAProxy 메시지와 액세스 로그가 /var/log/haproxy.log에 기록됩니다.

HAProxy의 Keepalives

listen 지시문에서 Connection: close 헤더를 추가하는 옵션 httpclose를 사용했습니다. 이것은 클라이언트(웹 브라우저)에게 응답을 받은 후 연결을 닫으라고 지시합니다.

HAProxy에서 연결 유지를 활성화하려면 option httpclose 줄을 다음으로 바꾸십시오.

option http-server-close
timeout http-keep-alive 3000

몇 개의 연결이 로드 밸런서의 모든 리소스를 소모하지 않도록 연결 유지 제한 시간을 현명하게 설정하십시오.

Keepalive 테스트

Keepalives는 동시에 여러 요청을 전송하여 curl을 사용하여 테스트할 수 있습니다. 다음 예에서는 불필요한 출력이 생략됩니다.

> curl -v http://1.1.1.1/index.html http://1.1.1.1/index.html
* About to connect() to 1.1.1.1 port 80 (#0)
*   Trying 1.1.1.1... connected
> GET /index.html HTTP/1.1
> User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5
> Host: 1.1.1.1
> Accept: */*
>
......[Output omitted].........
* Connection #0 to host 1.1.1.1 left intact
* Re-using existing connection! (#0) with host 1.1.1.1
* Connected to 1.1.1.1 (1.1.1.1) port 80 (#0)
> GET /index.html HTTP/1.1
> User-Agent: curl/7.23.1 (x86_64-pc-win32) libcurl/7.23.1 OpenSSL/0.9.8r zlib/1.2.5
> Host: 1.1.1.1
> Accept: */*
>
.......[Output Omitted].........
* Connection #0 to host 1.1.1.1 left intact
* Closing connection #0

이 출력에서 다음 행을 찾아야 합니다. Re-using existing connection! (#0) with host 1.1.1.1, 이는 curl이 후속 요청을 만들기 위해 동일한 연결을 사용했음을 나타냅니다.