如何使用HAProxy在UbuntuVPS上设置HTTP负载平衡

来自菜鸟教程
跳转至:导航、​搜索

关于 HAProxy


HAProxy(High Availability Proxy)是一个开源负载均衡器,可以对任何 TCP 服务进行负载均衡。 它特别适合 HTTP 负载平衡,因为它支持会话持久性和第 7 层处理。

借助 DigitalOcean Private Networking,可以将 HAProxy 配置为前端,通过专用网络连接对两个 VPS 进行负载平衡。

序幕


我们将在这里使用三个 VPS(液滴):

Droplet 1 - 负载均衡器主机名:haproxy 操作系统:Ubuntu 公共 IP:1.1.1.1 私有 IP:10.0.0.100

Droplet 2 - 节点 1 主机名:lamp1 操作系统:Ubuntu 上的 LAMP 私有 IP:10.0.0.1

Droplet 2 - 节点 2 主机名:lamp2 操作系统:Ubuntu 上的 LAMP 私有 IP:10.0.0.2

安装 HAProxy


使用 apt-get 命令安装 HAProxy。

apt-get install haproxy

我们需要启用 HAProxy 以通过 init 脚本启动。

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 指令提到将发送日志消息的系统日志服务器。 在 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 的连接尝试成功的最长时间。

clientserver 超时适用于客户端或服务器应在 TCP 过程中确认或发送数据时。 HAProxy 建议将 clientserver 超时设置为相同的值。

retries 指令设置连接失败后在 VPS 上执行的重试次数。

option 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

这包含前端和后端的配置。 我们将 HAProxy 配置为在端口 80 上侦听 appname,这只是用于识别应用程序的名称。 stats 指令启用连接统计页面,并使用 stats auth 指令指定的凭据通过 HTTP Basic authentication 保护它。

这个页面可以用stats uri中提到的URL查看,所以在这种情况下,它是http://1.1.1.1/haproxy?stats; 此页面的演示可以在这里查看

balance 指令指定要使用的负载平衡算法。 可用选项包括循环 (roundrobin)、静态循环 (static-rr)、最少连接 (leastconn)、源 (source)、URI ([ X135X])和 URL 参数(url_param)。

各个算法的信息可以从【X58X】官方文档【X84X】中获取。

server 指令声明一个后端服务器,语法是:

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

我们在此处提到的名称将出现在日志和警报中。 该指令支持许多参数,我们将在本文中使用checkcookie参数。 check 选项启用 VPS 上的健康检查,否则,VPS 始终被视为可用。

完成配置后,启动 HAProxy 服务:

service haproxy start

测试负载平衡和故障转移


要测试此设置,请在所有 Web 服务器(后端服务器 - 此处为 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 之间的连接,这就是 Round Robin 的工作原理。 我们在这里看到的客户端 IP 是负载均衡器的私有 IP 地址,而 X-Forwarded-For 标头是您的 IP。

要查看故障转移的工作原理,请转到 Web 服务器并停止服务:

lamp1@haproxy:~#service apache2 stop

再次使用 curl 发送请求,看看事情是如何工作的。

会话粘性


如果您的 Web 应用程序根据用户的登录会话提供动态内容(哪个应用程序没有),由于 VPS 之间的不断切换,访问者会遇到奇怪的事情。 会话粘性确保访问者坚持服务于他们的第一个请求的 VPS。 这可以通过使用 cookie 标记每个后端服务器来实现。

我们将使用以下 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 会话 并显示单个会话中的页面浏览量。

Cookie插入方法

在此方法中,从 HAProxy 到客户端的所有响应都将包含一个 Set-Cookie: 标头,其中后端服务器的名称作为其 cookie 值。 因此,客户端(Web 浏览器)将在其所有请求中包含此 cookie,HAProxy 将根据 cookie 值将请求转发到正确的后端服务器。

对于此方法,您需要添加 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 添加一个 Set-Cookie: 标头,其中包含一个名为 SRVNAME 的 cookie,其值为 S1S2,具体取决于哪个后端响应了请求。 添加后重新启动服务:

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; path=/ 中可以看出,LAMP1 已回答了它。 现在,为了模拟 Web 浏览器对下一个请求执行的操作,我们使用 curl 的 --cookie 参数 将这些 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 提供服务,并且会话得到了适当的维护。 如果您希望 Web 服务器上的所有文件具有粘性,则此方法很有用。

Cookie 前缀方法


另一方面,如果您只希望特定 cookie 具有粘性,或者不想为会话粘性设置单独的 cookie,则 prefix 选项适合您。

要使用此方法,请使用以下 cookie 指令:

cookie PHPSESSID prefix

PHPSESSID 可以替换为您自己的 cookie 名称。 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 cookie S1 如何作为会话 cookie 的前缀。 现在,让我们用这个 cookie 再发送两个请求。

> 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 时,我们添加了一行:log 127.0.0.1 local0 notice,它将 syslog 消息发送到 localhost IP 地址。 但默认情况下,Ubuntu 上的 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 中的 Keepalive


listen 指令下,我们使用了 option httpclose 添加了 Connection: close 标头。 这告诉客户端(Web 浏览器)在收到响应后关闭连接。

如果要在 HAProxy 上启用 keep-alives,请将 option httpclose 行替换为:

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

明智地设置保持活动超时,这样一些连接就不会耗尽负载均衡器的所有资源。

测试 Keepalive

可以使用 curl 通过同时发送多个请求来测试 Keepalive。 以下示例中将省略不必要的输出:

> 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 使用相同的连接来发出后续请求。