如何使用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 配置进行调整。
user 和 group 指令将 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 的连接尝试成功的最长时间。
client 和 server 超时适用于客户端或服务器应在 TCP 过程中确认或发送数据时。 HAProxy 建议将 client 和 server 超时设置为相同的值。
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*]
我们在此处提到的名称将出现在日志和警报中。 该指令支持许多参数,我们将在本文中使用check和cookie参数。 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,其值为 S1 或 S2,具体取决于哪个后端响应了请求。 添加后重新启动服务:
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 使用相同的连接来发出后续请求。