HAProxy 使用 LXD 容器负载均衡 Apache¶
简介¶
HAProxy 是“高可用性代理”的缩写。此代理可以部署在任何 TCP 应用程序(如 Web 服务器)之前,但它通常用作多个网站实例之间的负载均衡器。
这样做可能有多种原因。如果您的网站流量很大——添加该网站的另一个实例并将 HAProxy 放在两者前面——可以帮助您分配流量。另一个原因可能是能够更新网站内容而无需任何停机时间。HAProxy 还可以帮助减轻 DOS 和 DDOS 攻击。
本指南将探讨在同一 LXD 主机上使用 HAProxy 和两个网站实例,并通过轮循(round-robin)方式进行负载均衡。这可能是确保更新无停机时间的理想解决方案。
但是,如果您的挑战是网站性能,您可能需要将多个网站分布在实际的裸机或多个 LXD 主机上。当然,可以在不使用 LXD 的情况下在裸机上完成所有这些工作。但是,LXD 提供了极大的灵活性和性能,并且也非常适合实验室测试。
先决条件和假设¶
- 在 Linux 机器上熟练使用命令行
- 熟悉命令行编辑器(此处使用
vim
) - 熟悉
crontab
- 了解 LXD。更多信息,您可以参考 LXD 服务器 文档。可以在笔记本电脑或工作站上安装 LXD,而不必进行全面的服务器安装。本文档是基于一台运行 LXD 的实验室机器编写的,但并未像上面链接的文档那样进行完整的服务器设置。
- 对安装、配置和使用 Web 服务器有一定的了解。
- 我们将假设 LXD 已安装并准备好创建容器。
安装容器¶
在您用于本指南的 LXD 主机上,您将需要三个容器。如果您愿意,可以有更多的 Web 服务器容器。我们将使用 **web1** 和 **web2** 作为我们的网站容器,**proxyha** 作为我们的 HAProxy 容器。要在此 LXD 主机上安装它们,请执行以下操作:
lxc launch images:rockylinux/8 web1
lxc launch images:rockylinux/8 web2
lxc launch images:rockylinux/8 proxyha
运行 lxc list
应该会返回类似以下内容:
+---------+---------+----------------------+------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------+---------+----------------------+------+-----------+-----------+
| proxyha | RUNNING | 10.181.87.137 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
| web1 | RUNNING | 10.181.87.207 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
| web2 | RUNNING | 10.181.87.34 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
创建和使用 macvlan
配置文件¶
容器运行在具有桥接分配的 DHCP 地址的默认桥接接口上。这些需要更改为来自我们本地 LAN 的 DHCP 地址。首先需要创建并分配 macvlan
配置文件。
开始创建配置文件
lxc profile create macvlan
确保您的编辑器设置为首选编辑器,此处为 vim
export EDITOR=/usr/bin/vim
接下来,更改 macvlan
配置文件。在此之前,您需要知道主机用于我们 LAN 的接口。运行 ip addr
并查找具有 LAN IP 地址的接口
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether a8:5e:45:52:f8:b6 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.141/24 brd 192.168.1.255 scope global dynamic noprefixroute eno1
注意
在这种情况下,您要查找的接口是“eno1”,在您的系统上可能完全不同。请使用 **您的** 接口信息!
现在您已经知道了 LAN 接口,可以更改我们的 macvlan
配置文件了。为此,在命令行中输入:
lxc profile edit macvlan
将配置文件编辑为类似以下内容。作者省略了文件顶部的注释,但如果您是 LXD 新手,请检查这些注释。
config: {}
description: ""
devices:
eth0:
name: eth0
nictype: macvlan
parent: eno1
type: nic
name: macvlan
创建 macvlan
配置文件时,系统会复制 default
配置文件。无法更改 default
配置文件。
现在 macvlan
配置文件已存在,您需要将其应用于我们的三个容器。
lxc profile assign web1 default,macvlan
lxc profile assign web2 default,macvlan
lxc profile assign proxyha default,macvlan
不幸的是,macvlan
在内核中的默认行为在 LXD 容器内会莫名其妙地损坏(请参阅 此文档)。在容器启动时,dhclient
无法正常工作。
使用 DHCP 时,这非常简单。只需为每个容器执行以下操作:
lxc exec web1 bash
,这将使您进入 **web1** 容器的命令行。crontab -e
,这将编辑容器中 root 的crontab
。- 输入 I 以进入插入模式。
- 添加一行:
@reboot /usr/sbin/dhclient
- 按 Esc 键退出插入模式。
- 使用 Shift + :wq + 保存更改。
- 输入
exit
退出容器。
对 **web2** 和 **proxyha** 重复这些步骤。
完成这些步骤后,重启容器。
lxc restart web1
lxc restart web2
lxc restart proxyha
再次运行 lxc list
时,您将看到 DHCP 地址现在是从您的 LAN 分配的。
+---------+---------+----------------------+------+-----------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------+---------+----------------------+------+-----------+-----------+
| proxyha | RUNNING | 192.168.1.149 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
| web1 | RUNNING | 192.168.1.150 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
| web2 | RUNNING | 192.168.1.101 (eth0) | | CONTAINER | 0 |
+---------+---------+----------------------+------+-----------+-----------+
安装 Apache 并更改欢迎页面¶
我们的环境已准备就绪。接下来,在每个 Web 容器上安装 Apache (httpd
)。您可以直接执行此操作,无需物理访问它们。
lxc exec web1 dnf install httpd
lxc exec web2 dnf install httpd
任何现代 Web 服务器都需要比 Apache 更多的东西,但这足以进行一些测试。
接下来,启用 httpd
,启动它,并更改默认的欢迎页面。这样,当您尝试通过代理访问时,您就知道服务器正在响应。
启用并启动 httpd
lxc exec web1 systemctl enable httpd
lxc exec web1 systemctl start httpd
lxc exec web2 systemctl enable httpd
lxc exec web2 systemctl start httpd
更改欢迎页面。当没有配置网站时,会显示此页面,基本上是加载的默认页面。在 Rocky Linux 中,此页面位于 /usr/share/httpd/noindex/index.html
。更改此文件不需要直接访问容器。只需执行以下操作:
lxc exec web1 vi /usr/share/httpd/noindex/index.html
搜索 <h1>
标签,它将显示:
<h1>HTTP Server <strong>Test Page</strong></h1>
将该行更改为:
<h1>SITE1 HTTP Server <strong>Test Page</strong></h1>
对 web2 重复此过程。现在通过 IP 在浏览器中访问这些机器将返回各自正确的欢迎页面。Web 服务器还有更多工作要做,但现在先放下它们,转到代理服务器。
在 proxyha 上安装 HAProxy 和 LXD 代理配置¶
在代理容器上安装 HAProxy 非常简单。同样,无需直接访问该容器。
lxc exec proxyha dnf install haproxy
接下来,您需要配置 haproxy
以便监听 Web 服务的 80 端口和 443 端口。使用 lxc
的 configure 子命令执行此操作:
lxc config device add proxyha http proxy listen=tcp:0.0.0.0:80 connect=tcp:127.0.0.1:80
lxc config device add proxyha https proxy listen=tcp:0.0.0.0:443 connect=tcp:127.0.0.1:443
出于测试目的,您只会使用 80 端口,即 HTTP 流量,但这显示了如何配置容器以监听 HTTP 和 HTTPS 的默认 Web 端口。使用此命令还可以确保重新启动 **proxyha** 容器时会保留这些监听端口。
HAProxy 配置¶
您已经在容器上安装了 HAProxy,但尚未对其进行任何配置。在配置之前,您需要做一些事情来解析您的主机。通常您会使用完全限定域名(FQDN),但在本实验室环境中,您将使用 IP。为了让机器拥有一些名称,您将向 **proxyha** 容器添加一些主机文件记录。
lxc exec proxyha vi /etc/hosts
将以下记录添加到文件末尾:
192.168.1.150 site1.testdomain.com site1
192.168.1.101 site2.testdomain.com site2
这样,**proxyha** 容器就可以解析这些名称了。
编辑 haproxy.cfg
文件。您不会使用原始文件的大部分内容。您需要先备份该文件,将其重命名为其他名称:
lxc exec proxyha mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.orig
创建一个新的配置文件:
lxc exec proxyha vi /etc/haproxy/haproxy.cfg
请注意,暂时注释掉了 HTTPS 协议行。在生产环境中,您将需要使用覆盖您的 Web 服务器的通配符证书并启用 HTTPS。
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# For now, all https is remarked out
#
#ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
#ssl-default-bind-ciphers EECDH+AESGCM:EDH+AESGCM
#tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
# For now, all https is remarked out
# frontend www-https
# bind *:443 ssl crt /etc/letsencrypt/live/example.com/example.com.pem
# reqadd X-Forwarded-Proto:\ https
# acl host_web1 hdr(host) -i site1.testdomain.com
# acl host_web2 hdr(host) -i site2.testdomain.com
# use_backend subdomain1 if host_web1
# use_backend subdomain2 if host_web2
frontend http_frontend
bind *:80
acl web_host1 hdr(host) -i site1.testdomain.com
acl web_host2 hdr(host) -i site2.testdomain.com
use_backend subdomain1 if web_host1
use_backend subdomain2 if web_host2
backend subdomain1
# balance leastconn
balance roundrobin
http-request set-header X-Client-IP %[src]
# redirect scheme https if !{ ssl_fc }
server site1 site1.testdomain.com:80 check
server site2 web2.testdomain.com:80 check
backend subdomain2
# balance leastconn
balance roundrobin
http-request set-header X-Client-IP %[src]
# redirect scheme https if !{ ssl_fc }
server site2 site2.testdomain.com:80 check
server site1 site1.testdomain.com:80 check
以上内容简要解释。当您进行本指南的测试部分(下方)时,您应该会看到这些内容。
**site1** 和 **site2** 的定义在“acl”部分。每个站点都在彼此的轮循(round-robins)中,作为各自的后端。当您在测试中访问 site1.testdomain.com 时,URL 不会改变,但页面会在您每次从 **site1** 访问页面切换到 **site2** 测试页面时发生变化。site2.testdomain.com 也是如此。
这样做可以展示切换正在发生,但实际上,您的网站内容将完全相同,无论您访问哪个服务器。请注意,文档显示了您可能希望如何在多个主机之间分发流量。您也可以在 balance 行中使用“leastcon”,它不是基于上一次点击进行切换,而是加载连接数最少的站点。
错误文件¶
某些版本的 HAProxy 附带一套标准的 Web 错误文件,但来自 Rocky Linux(以及上游供应商)的版本没有这些文件。您可能 **确实** 希望创建它们,因为它们可能有助于您解决任何问题。这些文件位于 /etc/haproxy/errors
目录中,该目录目前不存在。
首先,创建该目录:
lxc exec proxyha mkdir /etc/haproxy/errors
在该目录中创建每个文件。请注意,您可以使用命令 lxc exec proxyha vi /etc/haproxy/errors/filename.http
从您的 LXD 主机为每个文件名执行此操作,其中“filename.http”指代以下文件名之一。在生产环境中,您的公司可能有更具体的错误页面。
文件名 400.http
HTTP/1.0 400 Bad request
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>400 Bad request</h1>
Your browser sent an invalid request.
</body></html>
文件名 403.http
HTTP/1.0 403 Forbidden
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>403 Forbidden</h1>
Request forbidden by administrative rules.
</body></html>
文件名 408.http
HTTP/1.0 408 Request Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>408 Request Time-out</h1>
Your browser didn't send a complete request in time.
</body></html>
文件名 500.http
HTTP/1.0 500 Internal Server Error
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>500 Internal Server Error</h1>
An internal server error occurred.
</body></html>
文件名 502.http
HTTP/1.0 502 Bad Gateway
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>502 Bad Gateway</h1>
The server returned an invalid or incomplete response.
</body></html>
文件名 503.http
HTTP/1.0 503 Service Unavailable
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>503 Service Unavailable</h1>
No server is available to handle this request.
</body></html>
文件名 504.http
HTTP/1.0 504 Gateway Time-out
Cache-Control: no-cache
Connection: close
Content-Type: text/html
<html><body><h1>504 Gateway Time-out</h1>
The server didn't respond in time.
</body></html>
运行代理¶
在启动服务之前,为 haproxy
创建一个“run”目录:
lxc exec proxyha mkdir /run/haproxy
接下来,启用服务并启动它:
lxc exec proxyha systemctl enable haproxy
lxc exec proxyha systemctl start haproxy
如果您遇到任何错误,请使用以下命令研究原因:
lxc exec proxyha systemctl status haproxy
如果一切顺利启动并运行,您就可以继续进行测试了。
测试代理¶
就像在 /etc/hosts
中设置主机一样,以便我们的 **proxyha** 容器能够解析 Web 服务器,并且由于我们的实验室环境没有运行本地 DNS 服务器,因此请在您的本地机器上为每个网站设置 IP 地址,以对应我们的 haproxy 容器。
为此,请修改您本地机器上的 /etc/hosts
文件。将此域名解析方法视为“简易 DNS”。
sudo vi /etc/hosts
添加这两行:
192.168.1.149 site1.testdomain.com site1
192.168.1.149 site2.testdomain.com site2
现在,如果您从本地机器 ping **site1** 或 **site2**,您将收到来自 **proxyha** 的响应。
PING site1.testdomain.com (192.168.1.149) 56(84) bytes of data.
64 bytes from site1.testdomain.com (192.168.1.149): icmp_seq=1 ttl=64 time=0.427 ms
64 bytes from site1.testdomain.com (192.168.1.149): icmp_seq=2 ttl=64 time=0.430 ms
打开您的 Web 浏览器,并在地址栏中输入 site1.testdomain.com(或 site2.testdomain.com)作为 URL。您将收到来自两个测试页面之一的响应,如果您再次加载页面,您将收到下一个服务器的测试页面。请注意,URL 不会改变,但返回的页面会在服务器之间交替变化。
日志记录¶
尽管我们的配置文件已正确设置为日志记录,但您还需要两件事:首先,在 /var/lib/haproxy/ 下创建一个名为“dev”的目录:
lxc exec proxyha mkdir /var/lib/haproxy/dev
接下来,创建一个 rsyslogd
系统进程,以便从套接字(此处为 /var/lib/haproxy/dev/log
)捕获实例,并将其存储在 /var/log/haproxy.log
中:
lxc exec proxyha vi /etc/rsyslog.d/99-haproxy.conf
将以下内容添加到该文件中:
$AddUnixListenSocket /var/lib/haproxy/dev/log
# Send HAProxy messages to a dedicated logfile
:programname, startswith, "haproxy" {
/var/log/haproxy.log
stop
}
保存文件并退出,然后重启 rsyslog
:
lxc exec proxyha systemctl restart rsyslog
为了立即填充日志文件,请再次重启 haproxy
:
lxc exec proxyha systemctl restart haproxy
查看创建的日志文件:
lxc exec proxyha more /var/log/haproxy.log
这将显示类似以下内容:
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy http_frontend started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy http_frontend started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain1 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain1 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain2 started.
Sep 25 23:18:02 proxyha haproxy[4602]: Proxy subdomain2 started.
结论¶
HAProxy 是一个强大的代理引擎,用于多种用途。它是一个高性能的开源负载均衡器和反向代理,适用于 TCP 和 HTTP 应用程序。本文档演示了如何对两个 Web 服务器实例进行负载均衡。
将其用于其他应用程序,包括数据库也是可能的。它可以在 LXD 容器内以及独立服务器上运行。
本文档未涵盖许多其他用途。请查看 HAProxy 官方手册。
作者:Steven Spencer
贡献者:Ezequiel Bruni, Antoine Le Morvan, Ganna Zhyrnova