文档贡献者须知:本文档每行格式化为 80 列,缩进使用偶数个空格,不使用制表符。请严格遵守这些规则,以便文档能够轻松地在任何地方打印。如果您添加章节,请更新下面的摘要以便于搜索。
| 1. | 前提条件 | |
2. |
HAProxy 架构快速回顾 | |
3. |
启动 HAProxy | |
4. |
停止和重启 HAProxy | |
5. |
文件描述符限制 | |
6. |
内存管理 | |
7. |
CPU 使用率 | |
8. |
日志 | |
9. |
统计信息和监控 | |
| 9.1. | ||
| 9.2. | ||
| 9.3. | ||
| 9.4. | ||
10. |
简化配置管理的技巧 | |
11. |
常见陷阱及避免方法 | |
12. |
调试和性能问题 | |
13. |
安全注意事项 |
本文档假设读者在类 UNIX 操作系统上拥有足够多的管理技能,日常使用 shell,并且熟悉 strace 和 tcpdump 等故障排除工具。
HAProxy 是一个单线程、事件驱动、非阻塞的守护进程。这意味着它使用事件多路复用 (event multiplexing) 来调度所有活动,而不是依赖系统来调度多个活动。大多数情况下,它作为一个单独的进程运行,因此在系统上使用 "ps aux" 命令只会显示一个 "haproxy" 进程,除非正在进行软重载,此时旧进程与新进程并行完成工作。因此,使用 strace 工具可以轻松追踪其活动。HAProxy 设计为在启动时将自身隔离在 chroot 监狱中,此时它无法进行任何文件系统访问。这同样适用于它所依赖的库(例如:libc, libssl 等)。其直接后果是,正在运行的进程无法重新加载配置文件以应用更改,而是必须启动一个新进程来加载更新后的配置文件。一些不太明显的后果是,libc 在运行时尝试访问的某些时区文件或解析器文件将找不到,尽管这通常不应该发生,因为它们在启动后就不需要了。该原则的一个良好后果是,HAProxy 进程完全是无状态的,杀死它之后不需要进行任何清理,因此任何有效的杀死方法都能正确处理。HAProxy 不会写入日志文件,但它依赖标准的 syslog 协议将日志发送到远程服务器(通常位于同一系统上)。HAProxy 使用其内部时钟来强制执行超时,该时钟派生自系统时间,并会纠正意外的漂移。这通过限制在 poll() 中等待事件的时间,并测量实际花费的时间来完成。实际上,它从不等待超过一秒。这解释了为什么在对完全空闲的进程运行 strace 时,会注意到周期性的 poll()(或其任何变体)调用,并且这些调用被两个 gettimeofday() 调用包围。它们是正常的、完全无害的,而且如此廉价,以至于它们带来的负载在系统级别上完全无法检测到,所以那里没有不寻常之处。示例:16:35:40.002320 gettimeofday({1442759740, 2605}, NULL) = 0 16:35:40.002942 epoll_wait(0, {}, 200, 1000) = 0 16:35:41.007542 gettimeofday({1442759741, 7641}, NULL) = 0 16:35:41.007998 gettimeofday({1442759741, 8114}, NULL) = 0 16:35:41.008391 epoll_wait(0, {}, 200, 1000) = 0 16:35:42.011313 gettimeofday({1442759742, 11411}, NULL) = 0 HAProxy 是一个 TCP 代理,而不是路由器。它处理由内核验证的已建立连接,而不是处理任何形式的数据包或处于其他状态的套接字(例如:没有 SYN_RECV 或 TIME_WAIT),尽管它们的存在可能会阻止它绑定端口。它依赖系统来接受传入连接和发起传出连接。这直接导致转发连接的两端观察到的数据包之间没有关联,数据包的大小、数量甚至系列都可能不同。由于连接只能从处于 LISTEN 状态的套接字接受,因此它正在监听的所有套接字都可以使用 "netstat" 工具显示监听套接字。示例:# netstat -ltnp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1629/sshd tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 2847/haproxy tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2847/haproxyHAProxy 通过在命令行调用 "haproxy" 程序并带有一些参数来启动。实际语法是:$ haproxy [<options>]*
后面跟着一个或多个字母,可能后面跟着一个或多个额外参数。如果不带任何选项,HAProxy 将显示帮助页面,并提醒支持的选项。可用选项可能因操作系统而异。这些选项中有许多与 "global" 部分中的等效选项重叠。在这种情况下,命令行始终优先于配置文件,以便可以使用命令行快速强制执行某些设置,而无需修改配置文件。当前选项列表为:-- <cfgfile>*: "--" 之后的所有参数都是要加载并按声明顺序处理的配置文件/目录路径。当依赖 shell 加载许多按数字排序的文件时,这非常有用。另请参阅 "-f"。"--" 和 "-f" 之间的区别在于,必须将一个 "-f" 放在每个文件名之前,而只需在所有文件名之前使用一个 "--"。两者都可以一起使用,命令行顺序仍然适用。当指定多个文件时,每个文件都必须从一个节边界开始,因此每个文件的第一个关键字必须是 "global"、"defaults"、"peers"、"listen"、"frontend"、"backend" 等之一。例如,一个文件不能只包含服务器列表。-f <cfgfile|cfgdir>:将 <cfgfile> 添加到要加载的配置文件列表中。如果 <cfgdir> 是一个目录,它包含的所有文件(仅文件)将按词法顺序(使用 LC_COLLATE=C)添加到要加载的配置文件列表中;仅添加扩展名为 ".cfg" 的文件,仅添加非隐藏文件(不以 "." 开头)。配置文件按声明顺序加载和处理。此选项可以指定多次以加载多个文件。另请参阅 "--"。"--" 和 "-f" 之间的区别在于,必须将一个 "-f" 放在每个文件名之前,而只需在所有文件名之前使用一个 "--"。两者都可以一起使用,命令行顺序仍然适用。当指定多个文件时,每个文件都必须从一个节边界开始,因此每个文件的第一个关键字必须是 "global"、"defaults"、"peers"、"listen"、"frontend"、"backend" 等之一。例如,一个文件不能只包含服务器列表。-C <dir>:在加载配置文件之前更改为目录 <dir>。当使用相对路径时,这很有用。警告:在 "--" 之后使用通配符时,它们实际上会在 haproxy 启动前被 shell 替换。-D:以前台进程模式启动。进程在 fork 后从当前终端分离,并且错误不再在终端中报告。它等同于配置文件 "global" 部分中的 "daemon" 关键字。建议在任何 init 脚本中始终强制使用它,以便有缺陷的配置不会阻止系统启动。-L <name>:将本地对等节点名称更改为 <name>,默认为本地主机名。这仅用于对等节点复制。您可以在配置文件中使用变量 $HAPROXY_LOCALPEER 来引用对等节点名称。-N <limit>:将默认的每个代理 maxconn 设置为 <limit>,而不是内置的默认值(通常为 2000)。仅用于调试。-V:启用详细模式(禁用静默模式)。撤销 "-q" 或 "quiet" 的效果。-W:主/工作模式。它等同于配置文件 "global" 部分中的 "master-worker" 关键字。此模式将启动一个 "master",它将监视 "workers"。使用此模式,您可以通过向 master 发送 SIGUSR2 信号直接重新加载 HAProxy。主/工作模式兼容前台或后台模式。建议将此模式与多进程和 systemd 一起使用。-Ws:支持 `notify` 类型 systemd 服务的 master-worker 模式。仅当 HAProxy 使用 `USE_SYSTEMD` 构建选项启用时,此选项才可用。-c:仅检查配置文件并在尝试绑定之前退出。如果一切正常,退出状态为零;如果遇到错误,则为非零。-d:启用调试模式。这将禁用后台模式,强制进程保持在前台并显示传入和传出的事件。它等同于 "global" 部分的 "debug" 关键字。切勿在 init 脚本中使用。-dG:禁用 getaddrinfo() 以将主机名解析为地址。当怀疑 getaddrinfo() 未按预期工作时,可以使用它。此选项之所以可用,是因为各种系统上存在许多 getaddrinfo() 的错误实现,导致难以排查的异常。-dM[<byte>]:强制内存中毒,这意味着用 <byte> 填充每个使用 malloc() 或 pool_alloc() 分配的内存区域,然后再传递给调用者。当未指定 <byte> 时,默认为 0x50 ('P')。虽然这会稍微减慢操作速度,但有助于可靠地触发由代码中缺少初始化引起的、导致随机崩溃的问题。请注意,-dM0 的效果是将任何 malloc() 转换为 calloc()。无论如何,如果使用此选项时出现或消失了 bug,则表示 haproxy 中存在 bug,请予以报告。-dS:禁用 splice() 系统调用的使用。它等同于 "global" 部分的 "nosplice" 关键字。当怀疑 splice() 的行为不当或导致性能问题时,或者在使用 strace 查看转发数据(使用 splice() 时不会显示)时,可以使用此选项。-dV:禁用服务器端的 SSL 验证。它等同于在 "global" 部分具有 "ssl-server-verify none"。这在尝试在生产环境之外重现生产问题时非常有用。切勿在 init 脚本中使用它,因为它会降低服务器的 SSL 安全性。-db:禁用后台模式和多进程模式。进程保持在前台。它主要用于开发或小型测试,因为 Ctrl-C 就足以停止进程。切勿在 init 脚本中使用它。-de:禁用 "epoll" poller 的使用。它等同于 "global" 部分的关键字 "noepoll"。当怀疑与此 poller 相关时,这非常有用。在支持 epoll 的系统上,回退通常是 "poll" poller。-dk:禁用 "kqueue" poller 的使用。它等同于 "global" 部分的关键字 "nokqueue"。当怀疑与此 poller 相关时,这非常有用。在支持 kqueue 的系统上,回退通常是 "poll" poller。-dp:禁用 "poll" poller 的使用。它等同于 "global" 部分的关键字 "nopoll"。当怀疑与此 poller 相关时,这非常有用。在支持 poll 的系统上,回退通常是 "select" poller,它不能被禁用,并且限制为 1024 个文件描述符。-dr:忽略服务器地址解析失败。在生产环境之外验证配置时,通常无法访问相同的解析器并导致服务器地址解析失败,这使得测试配置变得困难。此选项仅将 "none" 方法追加到所有服务器的地址解析方法列表中,确保即使 libc 无法解析地址,启动顺序也不会中断。-m <limit>:将所有进程的总可分配内存限制为 <limit> 兆字节。这可能会导致某些连接被拒绝或某些操作变慢,具体取决于正常操作所需的内存量。这主要用于强制进程在资源受限的使用场景下工作。请注意,内存不会在进程之间共享,因此在多进程场景下,此值首先除以 global.nbproc 再进行 fork。-n <limit>:将每个进程的连接限制为 <limit>。这等同于 "global" 部分的关键字 "maxconn"。它优先于该关键字。这可用于快速强制较低的限制,以避免在资源限制过低的系统上发生服务中断。-p <file>:在启动期间将所有进程的 pid 写入 <file>。这等同于 "global" 部分的关键字 "pidfile"。在进入 chroot 监狱之前打开该文件,并在执行了 "-C" 所暗示的 chdir() 之后打开。每个 pid 占一行。-q:设置 "quiet" 模式。这会禁用某些配置解析期间和启动期间的消息。它可以与 "-c" 结合使用,仅用于检查配置文件是否有效。-S <bind>[,bind_options...]:在主/工作模式下,绑定一个主 CLI,允许访问所有正在运行或即将退出的进程。出于安全原因,建议将主 CLI 绑定到本地 UNIX 套接字。绑定选项与配置文件中的 "bind" 关键字相同,单词用逗号而不是空格分隔。请注意,此套接字不能用于在无缝重新加载期间从旧进程检索监听套接字。-sf <pid>*:在启动完成时向旧进程发送 "finish" 信号(SIGUSR1),要求它们完成正在进行的工作并离开。<pid> 是要发送信号的 pid 列表(每参数一个)。列表在以 "-" 开头的任何选项处结束。如果 pid 列表为空,则没有问题,因此可以根据 "pidof" 或 "pgrep" 等命令的结果动态构建它。-st <pid>*:在启动完成时向旧进程发送 "terminate" 信号(SIGTERM),以立即终止它们,而不完成正在进行的工作。<pid> 是要发送信号的 pid 列表(每参数一个)。列表在以 "-" 开头的任何选项处结束。如果 pid 列表为空,则没有问题,因此可以根据 "pidof" 或 "pgrep" 等命令的结果动态构建它。-v:报告版本和构建日期。-vv:显示版本、构建选项、库版本和可用的 poller。在提交 bug 报告时,系统会要求输出此信息。-x <unix_socket>:连接到指定的套接字并尝试从旧进程检索任何监听套接字,并使用它们,而不是尝试绑定新的。这有助于在 Linux 上重新加载配置时避免丢失任何新连接。必须使用配置文件中的 "expose-fd listeners" 为统计套接字启用该功能。从 init 文件安全启动 HAProxy 的一种方法是强制执行后台模式,将现有 pid 存储到 pid 文件中,并使用此 pid 文件通知旧进程在退出前完成:haproxy -f /etc/haproxy.cfg \ -D -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid) 当配置被拆分成几个特定文件时(例如:tcp vs http),建议使用 "-f" 选项:haproxy -f /etc/haproxy/global.cfg -f /etc/haproxy/stats.cfg \ -f /etc/haproxy/default-tcp.cfg -f /etc/haproxy/tcp.cfg \ -f /etc/haproxy/default-http.cfg -f /etc/haproxy/http.cfg \ -D -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid) 当预期文件数量未知时,例如客户特定的文件,建议为其分配一个以固定长度序列号开头的名称,并使用 "--" 加载它们,可能在加载一些默认设置后:haproxy -f /etc/haproxy/global.cfg -f /etc/haproxy/stats.cfg \ -f /etc/haproxy/default-tcp.cfg -f /etc/haproxy/tcp.cfg \ -f /etc/haproxy/default-http.cfg -f /etc/haproxy/http.cfg \ -D -p /var/run/haproxy.pid -sf $(cat /var/run/haproxy.pid) \ -f /etc/haproxy/default-customers.cfg -- /etc/haproxy/customers/* 有时可能由于任何原因导致启动失败。然后,验证您正在调用的 HAProxy 版本是否是预期的版本,以及它是否支持您期望的功能(例如:SSL、PCRE、压缩、Lua 等)非常重要。这可以使用 "haproxy -vv" 进行验证。其中报告了一些重要信息,如某些构建选项、目标系统以及使用的库的版本。在提交 bug 报告时,系统也会系统地要求提供这些信息:$ haproxy -vv HA-Proxy version 1.6-dev7-a088d3-4 2015/10/08 Copyright 2000-2015 Willy Tarreau <willy@haproxy.org> Build options : TARGET = linux2628 CPU = generic CC = gcc CFLAGS = -pg -O0 -g -fno-strict-aliasing -Wdeclaration-after-statement \ -DBUFSIZE=8030 -DMAXREWRITE=1030 -DSO_MARK=36 -DTCP_REPAIR=19 OPTIONS = USE_ZLIB=1 USE_DLMALLOC=1 USE_OPENSSL=1 USE_LUA=1 USE_PCRE=1 Default settings : maxconn = 2000, bufsize = 8030, maxrewrite = 1030, maxpollevents = 200 Encrypted password support via crypt(3): yes Built with zlib version : 1.2.6 Compression algorithms supported : identity("identity"), deflate("deflate"), \ raw-deflate("deflate"), gzip("gzip") Built with OpenSSL version : OpenSSL 1.0.1o 12 Jun 2015 Running on OpenSSL version : OpenSSL 1.0.1o 12 Jun 2015 OpenSSL library supports TLS extensions : yes OpenSSL library supports SNI : yes OpenSSL library supports prefer-server-ciphers : yes Built with PCRE version : 8.12 2011-01-15 PCRE library supports JIT : no (USE_PCRE_JIT not set) Built with Lua version : Lua 5.3.1 Built with transparent proxy support using: IP_TRANSPARENT IP_FREEBIND Available polling systems : epoll : pref=300, test result OK poll : pref=200, test result OK select : pref=150, test result OK Total: 3 (3 usable), will use epoll. 许多非开发用户可以在这里验证的相关信息是:- 版本:上面的 1.6-dev7-a088d3-4 表示代码目前处于提交 ID "a088d3",这是官方版本 "1.6-dev7" 之后的第 4 个。版本 1.6-dev7 将显示为 "1.6-dev7-8c1ad7"。这里真正重要的是 "1.6-dev7"。这是将成为 1.6 版本的第 7 个开发版本。开发版本不适合在生产环境中使用(除非您确切地知道自己在做什么)。稳定版本将显示为 3 位数字版本,例如 "1.5.14-16f863",表示在版本 1.5 之上进行了第 14 次修复。这是一个可用于生产的版本。- 发布日期:2015/10/08。它以通用的年/月/日格式表示。这里表示 2015 年 8 月 8 日。鉴于稳定版本每隔几个月发布一次(开始时 1-2 个月,产品非常稳定后有时 6 个月),如果您看到一个旧日期,这意味着您可能受到了一些自那时以来已修复的 bug 或安全问题的困扰,可能值得在官方网站上查看。- 构建选项:这些选项与自己构建软件包的用户相关,它们可以解释为什么事物行为不如预期。例如,上面的开发版本是为 Linux 2.6.28 或更高版本构建的,目标是通用 CPU(无 CPU 特定优化),并且缺少任何代码优化(-O0),因此在性能方面表现不佳。- 库版本:zlib 版本报告为在库本身中找到。通常 zlib 被认为是一个非常稳定的产品,升级几乎从不需要。OpenSSL 报告两个版本,构建时使用的版本和当前使用的版本,在系统中找到。这些版本的最后一位字母可能不同,但数字绝对不会不同。还报告了构建日期,因为大多数 OpenSSL bug 都是安全问题,需要认真对待,因此该库绝对需要保持最新。看到一个 4 个月前的版本非常可疑,确实错过了更新。PCRE 提供非常快的正则表达式,强烈推荐。其某些扩展(如 JIT)并非在所有版本中都存在,而且还很新,因此有些人宁愿不使用它们构建,这就是报告构建状态的原因。关于 Lua 脚本语言,HAProxy 需要 5.3 版本,该版本非常新,因为它在 HAProxy 1.6 发布前不久发布。检查 Lua 网站上是否有针对此分支的修复非常重要。- 可用的轮询系统会在处理超过约一千个并发连接时影响进程的可伸缩性。这些系统仅在构建期间 TARGET 变量中指示了正确的系统时可用。强烈推荐在 Linux 上使用 "epoll" 机制,在 BSD 上强烈推荐使用 kqueue 机制。缺少它们将导致使用 poll() 甚至 select(),在处理大量连接时会导致 CPU 使用率过高。HAProxy 支持优雅停止和强制停止。强制停止很简单,当向 haproxy 进程发送 SIGTERM 信号时,它会立即退出,所有已建立的连接都会被关闭。优雅停止由向 haproxy 进程发送 SIGUSR1 信号触发。它仅包括取消绑定监听端口,但继续处理现有连接,直到它们关闭。一旦最后一个连接关闭,进程就会退出。强制停止方法用于服务管理脚本的 "stop" 或 "restart" 操作。优雅停止用于 "reload" 操作,该操作尝试在新进程中无缝重新加载新配置。在重新加载或重启期间,新 haproxy 进程本身可以发送这两个信号,以便在可能的最晚时间发送,并且仅在绝对需要时发送。这分别是 "-st"(强制)和 "-sf"(优雅)选项执行的操作。在主/工作模式下,不需要启动新的 haproxy 进程来重新加载配置。主进程响应 SIGUSR2 信号,通过使用参数 -sf 后跟 worker 的 PID 来重新执行自身。然后主进程将解析配置文件并 fork 新的 worker。为了更好地理解这些信号的使用方式,理解整个重启机制很重要。首先,一个现有的 haproxy 进程正在运行。管理员使用特定于系统的命令(如 "/etc/init.d/haproxy reload")来表示他想使新配置文件生效。然后会发生以下情况。首先,服务脚本(/etc/init.d/haproxy 或同等文件)将使用 "haproxy -c" 验证配置文件是否正确解析。之后,它将尝试使用 "-st" 或 "-sf" 启动具有该配置文件的 haproxy。然后 HAProxy 尝试绑定到所有监听端口。如果发生任何致命错误(例如:系统上不存在地址、权限被拒绝),进程将以错误退出。如果套接字绑定失败,因为端口已被占用,则进程将首先向 "-st" 或 "-sf" PID 列表中的所有 PID 发送 SIGTTOU 信号。这就是所谓的 "pause" 信号。它指示所有现有 haproxy 进程暂时停止监听端口,以便新进程可以再次尝试绑定。在此期间,旧进程继续处理现有连接。如果绑定仍然失败(例如,因为端口与另一个守护进程共享),则新进程会向旧进程发送 SIGTTIN 信号,指示它们像什么都没发生一样恢复操作。旧进程将重新开始监听端口并继续接受连接。请注意,此机制是系统依赖的,某些操作系统在多进程模式下可能不支持它。如果新进程成功绑定到所有端口,则它将发送 SIGTERM(在 "-st" 的情况下强制停止)或 SIGUSR1(在 "-sf" 的情况下优雅停止)到所有进程,以通知它们现在由它负责操作,并且旧进程必须离开,无论是立即离开还是在完成工作后离开。重要的是要注意,在此期间,存在两个几毫秒的短暂窗口,在这两个窗口中,在高负载下可能会出现一些连接失败。通常观察到的失败率大约是每秒 10000 个新连接的重新加载操作中出现 1 次失败,这意味着一个负载很高的站点每秒运行 30000 个新连接可能会在每次重新加载时看到约 3 次连接失败。发生这种情况的两种情况是:- 如果新进程由于旧进程的存在而绑定失败,它将首先经历 SIGTTOU+SIGTTIN 序列,该序列通常需要几十个前端大约一毫秒,在此期间,一些端口将不会绑定到旧进程,也不会绑定到新进程。HAProxy 在支持 SO_REUSEPORT 套接字选项的系统上可以解决这个问题,因为它允许新进程在不首先要求旧进程取消绑定即可绑定。大多数 BSD 系统几乎一直支持此功能。Linux 在 2.0 版本中支持此功能,并在 2.2 版本左右弃用,但当时有一些补丁在流传。它在内核 3.9 中重新引入,因此如果您观察到的连接失败率高于上述提到的,请确保您的内核是 3.9 或更新版本,或者相关的补丁已回溯到您的内核(不太可能)。- 当旧进程关闭监听端口时,内核可能并不总是能重新分配套接字积压中剩余的任何挂起连接。在高负载下,SYN 数据包可能在套接字关闭之前发生,并将导致向客户端发送 RST 数据包。在某些关键环境中,即使一个丢包也是不可接受的,有时会使用防火墙规则来阻止重新加载期间的 SYN 数据包,迫使客户端重新传输。这完全取决于系统,因为某些系统可能能够访问其他监听队列并避免此 RST。第二种情况是客户端对本地套接字(在关闭前处于 SYN_RECV 状态)的 ACK。此 ACK 将导致 RST 数据包,而 haproxy 进程尚未意识到这一点。这种情况更难摆脱,尽管前面提到的防火墙过滤规则在进程重启前一秒左右应用时会很好地工作。对于绝大多数用户来说,这种丢包永远不会发生,因为他们的负载不足以触发竞争条件。对于大多数高流量用户来说,只要他们的系统至少得到了 SO_REUSEPORT 的正确支持,失败率仍然相当在噪声范围内。
为了确保所有进来的连接都能成功处理,HAProxy在加载时会计算进程生命周期内所需的文件描述符总数。一个普通的Unix进程默认通常会被分配1024个文件描述符,而特权进程可以自行提高这个限制。这也是为什么HAProxy通常以root身份启动并允许它调整限制的原因之一。默认的1024个文件描述符限制大约可以处理500个并发连接。此计算基于全局maxconn参数(限制每个进程的总连接数)、监听器数量、启用了健康检查的服务器数量、代理检查、对等体、日志记录器以及可能的一些其他技术要求。一个简单的粗略估算方法是将maxconn值加倍,再加上几十个,即可得到所需文件描述符的大约数量。最初,HAProxy无法计算这个值,因此有必要通过全局部分的“ulimit-n”设置来传递该值。这就是为什么即使今天,许多配置中仍然可以看到此设置。不幸的是,它经常被错误计算,导致在接近maxconn时连接失败,而不是在等待所需资源时限制传入连接。因此,务必移除可能来自旧版本的任何遗留的“ulimit-n”设置。提高文件描述符数量以接受中等负载是必需的,但需要一些特定于操作系统的调整。首先,select()轮询系统限制为1024个文件描述符。实际上,Linux曾经能够处理更多,但由于某些操作系统附带极其严格的SELinux策略,禁止使用超过1024个文件描述符的select(),HAProxy现在会拒绝在此情况下启动,以避免运行时出现问题。在所有支持的操作系统上,poll()都可用,并且不会受到此限制。它会被自动选择,因此无需进行任何操作即可获得有效的配置。但当文件描述符数量增加时,poll()的性能会变得非常慢。虽然HAProxy会尽力限制这种性能影响(例如,通过使用内部文件描述符缓存和批量处理),但一个经验法则是,使用poll()处理超过一千个并发连接会消耗大量CPU。对于基于内核2.6及以上版本的Linux系统,将使用epoll()系统调用。这是一个可伸缩性更好的机制,依赖于内核中的回调,保证无论注册的监控文件描述符数量如何,唤醒时间都是恒定的。只要HAProxy是为Linux版本编译的,并且检测到它,它就会被自动使用。可以使用“haproxy -vv”来验证其存在和支持。对于支持它的BSD系统,kqueue()也可作为替代。得益于其批量处理更改的能力,它比poll()快得多,甚至比epoll()略快。至少FreeBSD和OpenBSD支持它。与Linux的epoll()一样,其支持和可用性也会在“haproxy -vv”的输出中报告。拥有一个好的轮询器是一回事,但进程必须能够达到这些限制。HAProxy启动时,会立即设置新进程的文件描述符限制并验证其是否成功。如果失败,它会在fork之前报告,以便管理员可以看到问题。只要进程是以root身份启动的,就不应该有理由出现此设置失败的情况。但是,如果进程是由非特权用户启动的,则可能会失败。如果出于某种令人信服的原因*不*以root身份启动haproxy(例如,由最终用户或按应用程序账户启动),那么系统管理员可以为该特定用户提高文件描述符限制。可以通过从用户命令行发出“ulimit -n”来验证该设置的有效性。它应该反映新的限制。警告:当用户账户中的非特权用户限制被更改时,这些值通常只在用户登录时才被考虑,而在系统启动时运行的某些脚本或crontabs中根本不被考虑。这完全取决于操作系统,请记住在以这种方式运行时,在启动haproxy之前检查“ulimit -n”。一般建议出于生产目的,永远不要以非特权用户身份启动haproxy。另一个好处是,它可以防止haproxy启用一些安全保护。一旦确定系统将允许haproxy进程使用所需数量的文件描述符,可能会遇到另外两个系统特定的限制。第一个是系统范围的文件描述符限制,即系统上打开的文件描述符总数,涵盖所有进程。当达到此限制时,accept()或socket()通常会返回ENFILE。第二个是每个进程的硬性文件描述符数量限制,它阻止setrlimit()被设置得更高。两者都高度依赖于操作系统。在Linux上,系统限制在启动时根据内存量设置。可以使用“fs.file-max”sysctl进行更改。每个进程的硬限制默认为1048576,但可以使用“fs.nr_open”sysctl进行更改。当进程的文件描述符限制设置得过低时,可能会观察到文件描述符限制。如果进程的文件描述符限制已达到,strace实用程序将报告accept()和socket()返回“-1 EMFILE”。在这种情况下,只需提高“ulimit-n”值(或删除它)即可解决问题。如果这些系统调用返回“-1 ENFILE”,则表示已达到内核限制,并且必须对系统范围的参数进行一些操作。这些问题绝对必须解决,因为它们会导致高CPU使用率(当accept()失败时)以及通常对用户可见的连接失败。一个解决方案还包括降低全局maxconn值以强制串行化,并可能禁用HTTP keep-alive以强制连接更快地释放和重用。
HAProxy使用简单快速的基于池的内存管理。由于它依赖于少数几种不同的对象类型,因此从已经包含适当大小对象的池中获取新对象比为每种不同的大小调用malloc()更有效。这些池被组织成栈或LIFO(后进先出),因此新分配的对象来自最近释放的、仍然在CPU缓存中热的对象。相似大小的池会被合并在一起,以限制内存碎片。默认情况下,由于注重性能,每个释放的对象都会放回其原始池中,并且分配的对象永远不会被释放,因为预计它们很快就会被重用。在CLI中,可以通过“show pools”命令检查内存如何在池中使用:> show pools Dumping pools usage. Use SIGQUIT to flush them. - Pool cache_st (16 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccc40=03 [SHARED] - Pool pipe (32 bytes) : 5 allocated (160 bytes), 5 used, 0 failures, 2 users, @0x9ccac0=00 [SHARED] - Pool comp_state (48 bytes) : 3 allocated (144 bytes), 3 used, 0 failures, 5 users, @0x9cccc0=04 [SHARED] - Pool filter (64 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 3 users, @0x9ccbc0=02 [SHARED] - Pool vars (80 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccb40=01 [SHARED] - Pool uniqueid (128 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9cd240=15 [SHARED] - Pool task (144 bytes) : 55 allocated (7920 bytes), 55 used, 0 failures, 1 users, @0x9cd040=11 [SHARED] - Pool session (160 bytes) : 1 allocated (160 bytes), 1 used, 0 failures, 1 users, @0x9cd140=13 [SHARED] - Pool h2s (208 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccec0=08 [SHARED] - Pool h2c (288 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cce40=07 [SHARED] - Pool spoe_ctx (304 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 2 users, @0x9ccf40=09 [SHARED] - Pool connection (400 bytes) : 2 allocated (800 bytes), 2 used, 0 failures, 1 users, @0x9cd1c0=14 [SHARED] - Pool hdr_idx (416 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cd340=17 [SHARED] - Pool dns_resolut (480 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccdc0=06 [SHARED] - Pool dns_answer_ (576 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9ccd40=05 [SHARED] - Pool stream (960 bytes) : 1 allocated (960 bytes), 1 used, 0 failures, 1 users, @0x9cd0c0=12 [SHARED] - Pool requri (1024 bytes) : 0 allocated (0 bytes), 0 used, 0 failures, 1 users, @0x9cd2c0=16 [SHARED] - Pool buffer (8030 bytes) : 3 allocated (24090 bytes), 2 used, 0 failures, 1 users, @0x9cd3c0=18 [SHARED] - Pool trash (8062 bytes) : 1 allocated (8062 bytes), 1 used, 0 failures, 1 users, @0x9cd440=19 Total: 19 pools, 42296 bytes allocated, 34266 used. 池名称仅供参考,它是使用该池的第一个对象类型的名称。括号中的大小是该池中对象的尺寸。对象尺寸总是向上舍入到最接近的16字节的倍数。报告当前已分配对象的数量和等效字节数,以便轻松了解哪个池导致了最高的内存使用量。“used”字段也报告了当前正在使用的对象的数量。“allocated”和“used”之间的差值对应于已释放并可立即使用的对象。行末的地址是池的地址,后面的数字是池的索引(如果存在),如果未分配索引则报告为-1。可以使用命令行选项“-m”,后跟兆字节数,来限制每个进程可分配的内存量。它覆盖了进程的所有可寻址空间,因此包括一些库使用的内存以及堆栈,但对于构建资源受限的系统来说,这是一个可靠的限制。它与有该选项的系统上的“ulimit -v”或其他系统上的“ulimit -d”的工作方式相同。如果内存分配因达到内存限制或系统内存不足而失败,haproxy将首先尝试释放所有池中的可用对象,然后再尝试重新分配内存。通过向haproxy进程发送SIGQUIT信号可以触发这种释放未使用内存的机制。这样做时,在进程在前台运行时,池刷新前状态也将报告给stderr。在重新加载操作期间,进程切换到平稳停止状态,也会在释放任何连接后自动执行一些刷新,以便释放所有可能的内存以供新进程使用。
HAProxy通常大部分时间花费在系统层面,一小部分时间在用户空间。一个调优良好的3.5 GHz CPU可以在单核上以100% CPU负载维持每秒约80000次端到端的连接建立和关闭。当一个核心饱和时,典型情况如下:- 95%系统,5%用户,适用于长TCP连接或大HTTP对象- 85%系统,15%用户,适用于短TCP连接或小HTTP对象(关闭模式)- 70%系统,30%用户,适用于小HTTP对象(keep-alive模式)规则处理和正则表达式的数量会增加用户空间的部分。防火墙规则、连接跟踪、系统中复杂的路由表的存在会增加系统部分。在大多数系统上,在网络传输期间观察到的CPU时间可以分为4部分:- 中断部分,涉及I/O接收时进行的所有处理,在目标进程已知之前。通常Rx数据包会计入中断。在某些系统(如Linux)上,中断处理可能会推迟到专用线程,这可能表现为softirq,线程称为ksoftirqd/0(对于CPU 0)。处理此负载的CPU通常由硬件设置定义,尽管在softirq的情况下,通常可以将处理重新映射到另一个CPU。此中断部分通常被视为寄生性的,因为它与任何进程无关,但实际上是为了准备进程工作而进行的一些处理。- 系统部分,涉及从用户空间调用的内核代码执行的所有处理。例如,系统调用被计为系统时间。所有同步传输的Tx数据包将计为系统时间。如果由于队列已满而必须推迟某些数据包,则稍后可能在中断上下文(例如,在收到打开TCP窗口的ACK时)进行处理。- 用户部分,仅在用户空间执行应用程序代码。HAProxy仅在此部分运行,尽管它大量使用系统调用。规则处理、正则表达式、压缩、加密都会增加CPU消耗的用户部分。- 空闲部分,即CPU在无事可做时所做的事情。例如,HAProxy等待传入连接,或等待某些数据发送出去,这意味着系统正在等待客户端的ACK来推送这些数据。在实际应用中,对于HAProxy的活动,通常可以合理地(但绝不完全准确地)认为中断/softirq是由内核驱动程序中的Rx处理引起的,用户空间是由HAProxy中的L7处理引起的,而系统时间是由Tx路径上的网络处理引起的。由于HAProxy围绕事件循环运行,它使用poll()(或任何替代方案)等待新事件,并尽可能快地处理所有这些事件,然后返回poll()等待新事件。它会测量在poll()中等待的时间与处理事件的时间的比例。轮询时间与总时间的比例称为“空闲”时间,即等待某事发生的时间量。此比例在统计页面的“idle”行或CLI的“Idle_pct”上报告。当它接近100%时,意味着负载非常低。当它接近0%时,意味着始终有活动。虽然在过载的系统上可能不那么准确,因为其他进程可能会抢占HAProxy进程的CPU,但它仍然提供了HAProxy如何看待其工作情况的良好估计:如果负载低且空闲率也很低,可能表明HAProxy有很多工作要做,可能是由于需要处理非常昂贵的规则。反之,如果HAProxy显示空闲率接近100%,而事情很慢,则意味着它无法加快速度,因为它已经在等待传入数据进行处理。在下面的示例中,haproxy完全空闲:$ echo "show info" | socat - /var/run/haproxy.sock | grep ^Idle Idle_pct: 100 当空闲率开始变得非常低时,调整系统并将进程和中断正确放置以尽可能节省所有任务的CPU资源非常重要。如果存在防火墙,则可能值得尝试禁用它或对其进行调整,以确保它不是性能限制的主要原因。值得注意的是,卸载有状态防火墙通常会减少中断/softirq和系统使用量,因为此类防火墙同时作用于Rx和Tx路径。在Linux上,卸载nf_conntrack和ip_conntrack模块将显示是否有可改进之处。如果有,则模块以默认设置运行,您需要弄清楚如何对其进行调优以获得更好的性能。通常这包括显著增加哈希表的大小。在FreeBSD上,“pfctl -d”将禁用“pf”防火墙及其有状态引擎。如果发现大量时间花费在中断/softirq上,重要的是确保它们不在同一CPU上运行。大多数系统倾向于将任务固定到接收网络流量的CPU上,因为对于某些工作负载来说,这可以提高性能。但对于网络密集型工作负载,情况正好相反,因为haproxy进程将不得不与其内核对应物竞争。将haproxy固定到一个CPU核心,并将中断固定到另一个核心,所有核心共享相同的L3缓存,往往会显著提高网络性能,因为实际上haproxy和网络栈的工作量相当接近,因此它们几乎可以填满一个完整的CPU。在Linux上,这可以通过taskset(用于haproxy)或cpu-map(来自haproxy配置)来完成,中断分配在/proc/irq下。许多网络接口支持多个队列和多个中断。通常,将它们分布在少量CPU核心上(前提是它们都共享相同的L3缓存)会有所帮助。请务必停止irq_balance,它在这种工作负载下总是表现最差。对于涉及大量SSL流量或大量压缩的CPU密集型工作负载,可能值得使用多个专门用于特定任务的进程,尽管这里没有普遍的规则,需要进行实验。为了增加CPU容量,可以通过在全局部分使用“nbproc”指令来运行多个HAProxy进程。但这有一些限制:- 健康检查是每个进程运行的,因此目标服务器将接收与运行进程数量相同的检查;- maxconn值和队列是每个进程的,因此必须设置正确的值以避免服务器过载;- 出站连接应避免使用端口范围以避免冲突;- stick-tables是每个进程的,并且进程之间不共享;- 每个peers节一次只能在一个进程上运行;- CLI操作一次只能作用于一个进程。考虑到这一点,最简单的设置通常是让第一层运行在多个进程上,负责繁重的处理,然后将流量传递给在单个进程中运行的第二层。这种机制适用于SSL和压缩,这两种都是CPU密集型功能。实例可以轻松地通过UNIX套接字(比TCP套接字便宜,而且不浪费端口)以及用于传递客户端信息的代理协议进行链接。这样做时,通常最好将所有单进程任务绑定到进程1,将额外任务绑定到后续进程,因为这样可以更容易地为不同的机器生成相似的配置。在Linux 3.9及以上版本中,当每个进程使用同一IP:port上的不同监听套接字时,HAProxy的多进程模式效率更高;这将使内核在所有进程之间均匀分配负载,而不是唤醒所有进程。有关更多信息,请参阅配置手册中“bind”关键字行的“process”选项。
对于日志记录,HAProxy始终依赖于syslog服务器,因为它不执行任何文件系统访问。标准的用法是通过UDP将日志发送到日志服务器(默认端口为514)。通常配置为127.0.0.1,本地syslog守护进程正在运行,但它也通过网络发送日志到中央服务器。中央服务器提供了额外的优势,特别是在主动-主动场景中,希望将日志按到达顺序合并。HAProxy还可以利用UNIX套接字将其日志发送到本地syslog守护进程,但这完全不推荐,因为如果syslog服务器在haproxy运行时重新启动,套接字将被替换,并且新的日志将被丢失。由于HAProxy将被隔离在一个chroot jail中,它将无法重新连接到新的套接字。在实际应用中也观察到UNIX套接字上的日志缓冲区很小,即使在轻载情况下也会导致消息丢失。但这对于测试来说也可以。建议在“global”部分添加以下指令,以便HAProxy使用“local0”设施将日志记录到本地守护进程:log 127.0.0.1:514 local0 然后在每个“defaults”部分或每个frontend和backend部分添加以下指令:log global 这样,所有日志都将通过全局定义的日志服务器位置进行集中管理。一些syslog守护进程默认不监听UDP流量,因此根据使用的守护进程,启用此功能的语法会有所不同:- 对于sysklogd,您需要在守护进程的命令行上传递“-r”参数,以便它监听UDP套接字以获取“远程”日志;请注意,无法将其限制为地址127.0.0.1,因此它还将接收来自远程系统的日志;- 对于rsyslogd,必须将以下行添加到配置文件中:$ModLoad imudp $UDPServerAddress * $UDPServerRun 514 - 对于syslog-ng,可以按以下方式创建新的源,然后将其添加为“log”指令之一中的有效源:source s_udp { udp(ip(127.0.0.1) port(514)); }; 请查阅您的syslog守护进程手册以获取更多信息。如果系统日志文件中没有看到任何日志,请考虑以下测试:- 重新启动haproxy。每个frontend和backend都会记录一行表明其正在启动。如果收到这些日志,则表示日志工作正常。- 运行“strace -tt -s100 -etrace=sendmsg -p <haproxy的pid>”并执行您期望被记录的某些活动。您应该会在那里看到使用sendmsg()发送的日志消息。如果它们没有出现,请尝试在haproxy顶部使用strace重新启动。如果仍然看不到日志,则绝对意味着您的配置存在问题。- 运行tcpdump以监视端口514,例如在本地回环接口上(如果流量是本地发送的):“tcpdump -As0 -ni lo port 514”。如果在这里看到数据包,则证明它们已被发送,那么需要对syslogd守护进程进行故障排除。虽然流量日志是从frontend(接受传入连接的地方)发送的,但backend也需要能够发送日志,以便在健康检查后报告服务器状态更改。有关所有可能的日志设置的更多信息,请参阅HAProxy的配置手册。选择一个其他守护进程未使用的设施非常方便。HAProxy示例通常建议使用“local0”作为流量日志,“local1”作为管理日志,因为它们从未在实际应用中看到。一个设施也足够了。拥有单独的日志便于日志分析,但也要记住,日志有时可能包含机密信息,因此不能与其他可能意外交给未经授权人员的日志混合。为了在不严重影响服务器容量的情况下进行现场故障排除,建议使用HAProxy提供的“halog”实用程序。这是一种类似grep的实用程序,设计用于以非常高的数据速率处理HAProxy日志文件。典型数字范围在每秒1到2 GB日志之间。它能够仅提取某些日志(例如:搜索特定类的HTTP状态码、连接终止状态、按响应时间范围搜索、仅查找错误)、计数行、将输出限制为行数,并执行一些更高级的统计信息,例如按响应时间或错误计数对服务器进行排序、按时间或计数对URL进行排序、按访问计数对客户端地址进行排序等。它可以非常方便地快速发现异常,例如一个机器人正在循环访问网站,并阻止它们。可以查询 HAProxy 的状态。最常用的机制是 HTTP 统计页面。该页面还为监控工具提供了一种替代的 CSV 输出格式。在 Unix 套接字上也提供了相同的格式。
统计数据既可以通过 unix socket 访问,也可以通过 HTTP 页面访问。这两种方式都提供 CSV 格式,其字段如下。第一行以井号('#')开头,每个逗号分隔的字段只有一个单词,代表列标题。从第二行开始的所有其他行都使用经典的 CSV 格式,以逗号作为分隔符,并使用双引号('"')作为可选的文本分隔符,但仅当包含的文本存在歧义时(如果它包含引号或逗号)。文本中的双引号字符('"')会翻倍('""'),这是大多数工具都能识别的格式。请不要在此类列之前插入任何其他列,以免破坏使用硬编码列位置的工具。每个字段名后的括号中是该字段可能具有的值的类型。类型为 L (Listeners)、F (Frontends)、B (Backends) 和 S (Servers)。 0. pxname [LFBS]: proxy name 1. svname [LFBS]: service name (FRONTEND for frontend, BACKEND for backend, any name for server/listener) 2. qcur [..BS]: current queued requests. For the backend this reports the number queued without a server assigned. 3. qmax [..BS]: max value of qcur 4. scur [LFBS]: current sessions 5. smax [LFBS]: max sessions 6. slim [LFBS]: configured session limit 7. stot [LFBS]: cumulative number of sessions 8. bin [LFBS]: bytes in 9. bout [LFBS]: bytes out 10. dreq [LFB.]: requests denied because of security concerns. - For tcp this is because of a matched tcp-request content rule. - For http this is because of a matched http-request or tarpit rule. 11. dresp [LFBS]: responses denied because of security concerns. - For http this is because of a matched http-request rule, or "option checkcache". 12. ereq [LF..]: request errors. Some of the possible causes are: - early termination from the client, before the request has been sent. - read error from the client - client timeout - client closed connection - various bad requests from the client. - request was tarpitted. 13. econ [..BS]: number of requests that encountered an error trying to connect to a backend server. The backend stat is the sum of the stat for all servers of that backend, plus any connection errors not associated with a particular server (such as the backend having no active servers). 14. eresp [..BS]: response errors. srv_abrt will be counted here also. Some other errors are: - write error on the client socket (won't be counted for the server stat) - failure applying filters to the response. 15. wretr [..BS]: number of times a connection to a server was retried. 16. wredis [..BS]: number of times a request was redispatched to another server. The server value counts the number of times that server was switched away from. 17. status [LFBS]: status (UP/DOWN/NOLB/MAINT/MAINT(via)/MAINT(resolution)...) 18. weight [..BS]: total weight (backend), server weight (server) 19. act [..BS]: number of active servers (backend), server is active (server) 20. bck [..BS]: number of backup servers (backend), server is backup (server) 21. chkfail [...S]: number of failed checks. (Only counts checks failed when the server is up.) 22. chkdown [..BS]: number of UP->DOWN transitions. The backend counter counts transitions to the whole backend being down, rather than the sum of the counters for each server. 23. lastchg [..BS]: number of seconds since the last UP<->DOWN transition 24. downtime [..BS]: total downtime (in seconds). The value for the backend is the downtime for the whole backend, not the sum of the server downtime. 25. qlimit [...S]: configured maxqueue for the server, or nothing in the value is 0 (default, meaning no limit) 26. pid [LFBS]: process id (0 for first instance, 1 for second, ...) 27. iid [LFBS]: unique proxy id 28. sid [L..S]: server id (unique inside a proxy) 29. throttle [...S]: current throttle percentage for the server, when slowstart is active, or no value if not in slowstart. 30. lbtot [..BS]: total number of times a server was selected, either for new sessions, or when re-dispatching. The server counter is the number of times that server was selected. 31. tracked [...S]: id of proxy/server if tracking is enabled. 32. type [LFBS]: (0=frontend, 1=backend, 2=server, 3=socket/listener) 33. rate [.FBS]: number of sessions per second over last elapsed second 34. rate_lim [.F..]: configured limit on new sessions per second 35. rate_max [.FBS]: max number of new sessions per second 36. check_status [...S]: status of last health check, one of: UNK -> unknown INI -> initializing SOCKERR -> socket error L4OK -> check passed on layer 4, no upper layers testing enabled L4TOUT -> layer 1-4 timeout L4CON -> layer 1-4 connection problem, for example "Connection refused" (tcp rst) or "No route to host" (icmp) L6OK -> check passed on layer 6 L6TOUT -> layer 6 (SSL) timeout L6RSP -> layer 6 invalid response - protocol error L7OK -> check passed on layer 7 L7OKC -> check conditionally passed on layer 7, for example 404 with disable-on-404 L7TOUT -> layer 7 (HTTP/SMTP) timeout L7RSP -> layer 7 invalid response - protocol error L7STS -> layer 7 response error, for example HTTP 5xx Notice: If a check is currently running, the last known status will be reported, prefixed with "* ". e. g. "* L7OK". 37. check_code [...S]: layer5-7 code, if available 38. check_duration [...S]: time in ms took to finish last health check 39. hrsp_1xx [.FBS]: http responses with 1xx code 40. hrsp_2xx [.FBS]: http responses with 2xx code 41. hrsp_3xx [.FBS]: http responses with 3xx code 42. hrsp_4xx [.FBS]: http responses with 4xx code 43. hrsp_5xx [.FBS]: http responses with 5xx code 44. hrsp_other [.FBS]: http responses with other codes (protocol error) 45. hanafail [...S]: failed health checks details 46. req_rate [.F..]: HTTP requests per second over last elapsed second 47. req_rate_max [.F..]: max number of HTTP requests per second observed 48. req_tot [.FB.]: total number of HTTP requests received 49. cli_abrt [..BS]: number of data transfers aborted by the client 50. srv_abrt [..BS]: number of data transfers aborted by the server (inc. in eresp) 51. comp_in [.FB.]: number of HTTP response bytes fed to the compressor 52. comp_out [.FB.]: number of HTTP response bytes emitted by the compressor 53. comp_byp [.FB.]: number of bytes that bypassed the HTTP compressor (CPU/BW limit) 54. comp_rsp [.FB.]: number of HTTP responses that were compressed 55. lastsess [..BS]: number of seconds since last session assigned to server/backend 56. last_chk [...S]: last health check contents or textual error 57. last_agt [...S]: last agent check contents or textual error 58. qtime [..BS]: the average queue time in ms over the 1024 last requests 59. ctime [..BS]: the average connect time in ms over the 1024 last requests 60. rtime [..BS]: the average response time in ms over the 1024 last requests (0 for TCP) 61. ttime [..BS]: the average total session time in ms over the 1024 last requests 62. agent_status [...S]: status of last agent check, one of: UNK -> unknown INI -> initializing SOCKERR -> socket error L4OK -> check passed on layer 4, no upper layers testing enabled L4TOUT -> layer 1-4 timeout L4CON -> layer 1-4 connection problem, for example "Connection refused" (tcp rst) or "No route to host" (icmp) L7OK -> agent reported "up" L7STS -> agent reported "fail", "stop", or "down" 63. agent_code [...S]: numeric code reported by agent if any (unused for now) 64. agent_duration [...S]: time in ms taken to finish last check 65. check_desc [...S]: short human-readable description of check_status 66. agent_desc [...S]: short human-readable description of agent_status 67. check_rise [...S]: server's "rise" parameter used by checks 68. check_fall [...S]: server's "fall" parameter used by checks 69. check_health [...S]: server's health check value between 0 and rise+fall-1 70. agent_rise [...S]: agent's "rise" parameter, normally 1 71. agent_fall [...S]: agent's "fall" parameter, normally 1 72. agent_health [...S]: agent's health parameter, between 0 and rise+fall-1 73. addr [L..S]: address:port or "unix". IPv6 has brackets around the address. 74: cookie [..BS]: server's cookie value or backend's cookie name 75: mode [LFBS]: proxy mode (tcp, http, health, unknown) 76: algo [..B.]: load balancing algorithm 77: conn_rate [.F..]: number of connections over the last elapsed second 78: conn_rate_max [.F..]: highest known conn_rate 79: conn_tot [.F..]: cumulative number of connections 80: intercepted [.FB.]: cum. number of intercepted requests (monitor, stats) 81: dcon [LF..]: requests denied by "tcp-request connection" rules 82: dses [LF..]: requests denied by "tcp-request session" rules 83: wrew [LFBS]: cumulative number of failed header rewriting warnings 84: connect [..BS]: cumulative number of connection establishment attempts 85: reuse [..BS]: cumulative number of connection reuses 86: cache_lookups [.FB.]: cumulative number of cache lookups 87: cache_hits [.FB.]: cumulative number of cache hits
“show info”和“show stat”都支持一种模式,其中每个输出值都带有其类型以及足够的信息来了解该值如何在进程之间聚合以及其演变方式。在所有情况下,输出都包含每行一个值,所有信息都分为由冒号(':')分隔的字段。第一个列指示正在转储的对象或指标。其格式特定于生成此输出的命令,在本节中将不进行描述。通常,它将由一系列标识符和字段名组成。第二列包含 3 个字符,分别指示所报告值的来源、性质和范围。第一个字符(来源)指示值是从何处提取的。可能的字符是: M 该值是度量。它在一个时间点上有效,并可能因其性质而异。 S 该值是状态。它表示一个离散值,根据定义不能聚合。它可能是服务器的状态(“UP”或“DOWN”)、进程的 PID 等。 K 该值是一个排序键。它表示一个标识符,可用于将某些值分组在一起,因为它在其类中是唯一的。所有内部标识符都是键。某些名称可以列为键,如果它们是唯一的(例如:前端名称是唯一的)。通常,键来自配置,尽管其中一些可能是自动分配的。在大多数情况下,键可以被认为是等同于配置。 C 该值来自配置。某些配置值在输出中是有意义的,例如并发连接限制或 cookie 名称。根据定义,这些值在从同一配置文件启动的所有进程中都是相同的。 P 该值来自产品本身。这类值很少,最常见的用途是报告产品名称、版本和发布日期。这些元素在所有进程之间也是相同的。第二个字符(性质)指示字段信息的性质,以便聚合器决定使用什么操作来聚合多个值。可能的字符是: A 该值代表自上次事件以来的年龄。这与持续时间略有不同,年龄是根据当前日期自动计算的。一个典型的例子是服务器上上次会话发生在多久以前。年龄通常通过取最小值来聚合,并且不需要存储。 a 该值代表一个已平均值。平均响应时间和服务器权重属于此类。平均值通常可以在进程之间进行平均。 C 该值代表一个累积计数器。这些度量会不断增加,直到回绕。某些监视协议需要区分计数器和仪表以报告不同的类型。通常,计数器可以简单地相加,因为它们代表事件或卷。此性质的度量示例包括连接数或字节数。 D 该值代表状态的持续时间。这有几种用法,其中大部分包括上次健康检查所花费的时间以及服务器花费的下线时间。持续时间通常不相加,大多数时候会保留最大值来计算 SLA。 G 该值代表一个仪表。它是一个即时度量。内存使用量或当前活动连接数属于此类。此类型的度量通常在聚合过程中相加。 L 该值代表一个限制(通常是已配置的)。根据性质,限制更难聚合,因为它们特定于检索它们的位置。在某些情况下,它们可以相加或分开保留。 M 该值代表一个最大值。通常,它适用于仪表并保留已知的最高值。此度量示例可能是产品生命周期中遇到的最大并发连接数。要正确聚合最大值,您应该输出一个范围,从所有最大值中的最小值到所有最大值的总和。确实无法知道它们是否同时遇到。 m 该值代表一个最小值。通常,它适用于仪表并保留已知的最低值。此度量示例可能是产品生命周期中遇到的最小空闲内存池量。要正确聚合最小值,您应该输出一个范围,从所有最小值中的最小值到所有最小值的总和。确实无法知道它们是否同时遇到。 N 该值代表一个名称,因此它是一个字符串。它用于报告代理名称、服务器名称和 cookie 名称。名称具有配置或键作为其来源,并且在所有进程中都应该是相同的。 O 该值代表一个自由文本输出。来自各种命令的输出、健康检查的返回值、节点描述都属于此类。 R 该值代表一个事件速率。它是一个即时度量。它与仪表非常相似,除了接收者知道该度量移动缓慢,并可能决定不保留所有值。此度量示例是每秒连接数的测量量。此类型的度量通常在聚合过程中相加。 T 该值代表日期或时间。发出当前日期的字段属于此类型。聚合此类信息的方法留给实现选择。目前没有字段使用此类型。第三个字符(范围)指示值反映的范围。某些元素可能是每个进程的,而其他元素可能是每个配置或每个系统的。区分这一点很重要,以便知道是在聚合过程中保留单个值,还是需要聚合值。当前支持的字符如下: C 该值对整个节点集群有效,即通过 peers 协议通信的节点集。例如,可能是存在于与 Pears 协议同步的 stick table 中的条目数。目前没有度量使用此范围。 P 该值仅对报告它的进程有效。大多数度量使用此范围。 S 该值对整个服务有效,即从同一配置文件启动的进程集。所有来自配置的度量都使用此范围。其他一些度量也可能将其用于某些共享资源(例如:共享 SSL 缓存统计信息)。 s 该值对整个系统有效,例如系统的主机名、当前日期或资源使用情况。目前没有度量使用此范围。这些信息的消费者通常会拥有这 3 个字符的足够信息,以确定如何在多个进程之间准确地报告聚合信息。在此列之后,第三列指示字段的类型,包括“s32”(有符号 32 位整数)、“s64”(有符号 64 位整数)、“u32”(无符号 32 位整数)、“u64”(无符号 64 位整数)、“str”(字符串)。在解析值以正确读取它之前,了解类型很重要。例如,一个仅包含数字的字符串仍然是一个字符串而不是整数(例如:由检查提取的错误代码)。然后第四列是值本身,根据其类型进行编码。字符串直接在冒号后转储,没有前导空格。如果字符串包含冒号,它将正常显示。这意味着输出不应仅围绕冒号拆分,否则某些检查输出或服务器地址可能会被截断。
stats socket 默认情况下未启用。要启用它,需要在 haproxy 配置的全局部分添加一行。建议添加第二行以设置更长的超时时间,这在手动发出命令时总是受欢迎的: global stats socket /var/run/haproxy.sock mode 600 level admin stats timeout 2m 也可以通过重复该行来添加多个 stats socket 实例,并使其监听 TCP 端口而不是 UNIX socket。由于这很危险,因此默认情况下从不这样做,但在某些情况下可能很有用: global stats socket /var/run/haproxy.sock mode 600 level admin stats socket ipv4@192.168.0.1:9999 level admin stats timeout 2m 要访问 socket,需要一个外部实用程序,如“socat”。Socat 是一个瑞士军刀,可以连接任何东西。我们使用它将终端连接到 socket,或将一对 stdin/stdout 管道连接到它以用于脚本。我们将使用的两个主要语法如下: # socat /var/run/haproxy.sock stdio # socat /var/run/haproxy.sock readline 第一个用于脚本。可以将脚本的输出发送到 haproxy,并将 haproxy 的输出传递给另一个脚本。这对于检索计数器或攻击跟踪非常有用。第二个仅对手动发出命令有用。它的好处是终端由 readline 库处理,该库支持行编辑和历史记录,这在发出重复命令(例如:监视计数器)时非常方便。 socket 支持两种操作模式: - 交互式 - 非交互式 socat 连接到 socket 时,非交互模式是默认模式。在此模式下,可以发送单行。它被整体处理,响应被发送回,并在响应结束后关闭连接。这是脚本和监视工具使用的模式。在此模式下可以发送多个命令,它们需要用分号(';')分隔。例如: # echo "show info;show stat;show table" | socat /var/run/haproxy stdio 如果命令需要使用分号或反斜杠(例如:在值中),则必须在其前面加上反斜杠('\')。交互模式显示一个提示符('>')并等待在行上输入命令,然后处理它们,然后再次显示提示符以等待新命令。通过非交互模式下的第一行必须发送的“prompt”命令进入此模式。该模式是切换开关,如果在交互模式下发送“prompt”,则禁用它,并且在处理同一行的最后一个命令后连接将关闭。因此,在手动调试时,通常会以“prompt”命令开始: # socat /var/run/haproxy readline prompt > show info ... > 由于一次可以发出多个命令,haproxy 使用空行作为分隔符来标记每个命令的输出结束,并确保任何命令都不会在输出中发出空行。因此,脚本可以轻松地解析输出,即使多个命令在一个行中被管道传输。某些命令可能需要可选的有效负载。要为一个命令添加一个,第一行需要以“<<\n”模式结尾。下一行将被视为有效负载,并且可以包含任意多行。要验证带有有效负载的命令,它需要以空行结尾。存在限制:传递给 CLI 的整个缓冲区的长度不得大于 tune.bfsize,并且“<<”模式不得紧贴在行的最后一个单词后面。在交互模式下输入有效负载时,提示符将从“> ”变为“+ ”。理解在同一个 socket 上启动多个 haproxy 进程很重要,任何进程都可能接收请求并输出自己的统计信息。下面提供了当前在 stats socket 上支持的命令列表。如果发送了未知命令,haproxy 将显示使用消息,其中会提醒所有支持的命令。某些命令支持更复杂的语法,通常会在发生这种情况时解释命令的哪个部分无效。某些命令需要更高级别的权限才能工作。如果您没有足够的权限,您将收到“Permission denied”错误。请查看配置手册中的“bind”关键字行的“level”选项以获取更多信息。
向 ACL <acl> 中添加一个条目。<acl> 是由 "show acl" 返回的 #<id> 或 <file>。此命令不验证条目是否已存在。如果引用的 <acl> 是一个也与 map 一起使用的文件,则不能使用此命令。在这种情况下,您必须使用 "add map" 命令来代替 "add acl"。
向 map <map> 中添加一个条目,将值 <value> 与键 <key> 关联起来。此命令不验证条目是否已存在。它主要用于在清除操作后填充 map。请注意,如果引用的 <map> 是一个文件并且与一个 map 共享,那么该 map 也将包含一个新的模式条目。使用 payload 语法,可以通过在不同行上输入多个键/值对来添加它们。在每个新行上,第一个单词是键,行的其余部分被认为是值,甚至可以包含空格。
# socat /tmp/sock1 - prompt > add map #-1 << + key1 value1 + key2 value2 with spaces + key3 value3 also with spaces + key4 value4 >
清除每个代理(前端和后端)和每个服务器中统计计数器的最大值。累积的计数器不受影响。由 "show activity" 报告的内部活动计数器也会被重置。这可用于在事件发生后获得干净的计数器,而无需重新启动或清除流量计数器。此命令受限制,只能在配置为“operator”或“admin”级别的套接字上发出。
清除每个代理(前端和后端)和每个服务器中的所有统计计数器。这与重新启动具有相同的效果。此命令受限制,只能在配置为“admin”级别的套接字上发出。
从 ACL <acl> 中删除所有条目。<acl> 是由 "show acl" 返回的 #<id> 或 <file>。请注意,如果引用的 <acl> 是一个文件并且与一个 map 共享,那么该 map 也将被清除。
从 map <map> 中删除所有条目。<map> 是由 "show map" 返回的 #<id> 或 <file>。请注意,如果引用的 <map> 是一个文件并且与一个 acl 共享,那么该 acl 也将被清除。
从 stick-table <table> 中删除条目。这通常用于解除阻止一些抱怨其服务被滥用拒绝访问的用户,但也可用于清除与即将被替换的服务器匹配的某些粘性条目(有关详细信息,请参阅下面的“show table”)。请注意,有时删除条目会被拒绝,因为它当前正被一个会话跟踪。通常在会话结束后几秒钟重试是足够的。如果没有给出选项参数,所有条目都将被删除。当使用“data.”形式时,会删除使用存储数据(请参阅第 4.2 节中的“stick-table”)应用的过滤器匹配的条目。必须在 <type> 中指定存储的数据类型,并且该数据类型必须存储在表中,否则会报告错误。根据 <operator> 将数据与 64 位整数 <value> 进行比较。运算符与 ACL 中的相同: - eq:匹配数据等于此值的所有条目 - ne:匹配数据不等于此值的所有条目 - le:匹配数据小于或等于此值的所有条目 - ge:匹配数据大于或等于此值的所有条目 - lt:匹配数据小于此值的所有条目 - gt:匹配数据大于此值的所有条目当使用 key 形式时,会删除条目 <key>。键必须与表的数据类型相同,目前仅限于 IPv4、IPv6、整数和字符串。
$ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:2 >>> 0x80e6a4c: key=127.0.0.1 use=0 exp=3594729 gpc0=0 conn_rate(30000)=1 \ bytes_out_rate(60000)=187 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191 $ echo "clear table http_proxy key 127.0.0.1" | socat stdio /tmp/sock1 $ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:1 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191 $ echo "clear table http_proxy data.gpc0 eq 1" | socat stdio /tmp/sock1 $ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:1
从 acl <acl> 中删除所有与键 <key> 对应的 acl 条目。<acl> 是由“show acl”返回的 #<id> 或 <file>。如果使用了 <ref>,此命令仅删除列出的引用。引用可以通过列出 acl 的内容找到。请注意,如果引用的 <acl> 是一个文件并且与 map 共享,该条目也将在 map 中被删除。
从 map <map> 中删除所有与键 <key> 对应的 map 条目。<map> 是由“show map”返回的 #<id> 或 <file>。如果使用了 <ref>,此命令仅删除列出的引用。引用可以通过列出 map 的内容找到。请注意,如果引用的 <map> 是一个文件并且与 acl 共享,该条目也将在 acl 中被删除。
将辅助代理检查标记为临时停止。在代理检查作为辅助检查运行的情况下(由于服务器指令的 agent-check 参数),只有当代理处于启用状态时才会初始化新的检查。因此,disable agent 将阻止任何新的代理检查被启动,直到使用 enable agent 重新启用代理。当代理被禁用时,对在代理启用时启动的辅助代理检查的处理如下:所有会改变权重的结果,特别是 "drain" 或代理返回的权重,都将被忽略。代理检查的其他处理方式保持不变。此功能的动机是允许暂停代理检查的权重更改效果,以便可以使用 set weight 配置服务器的权重,而不会被代理覆盖。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
为后端 <backend> 禁用动态 cookie 的生成
将前端标记为临时停止。这对应于软重启期间使用的模式:前端释放端口,但如果需要可以再次启用。应谨慎使用此命令,因为一些非 Linux 操作系统无法重新启用它。这旨在用于那些甚至无法想象停止代理但必须修复配置错误的代理的环境中。这样就有可能释放端口并将其绑定到另一个进程以恢复操作。前端将在统计页面上显示为 "STOP" 状态。前端可以通过其名称或其数字 ID(前缀为井号 '#')指定。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
将主健康检查标记为临时停止。这将禁用发送健康检查,并且最后的健康检查结果将被忽略。服务器将处于未检查状态并被认为是 UP,除非辅助代理检查强制其 down。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
将服务器标记为 DOWN 进行维护。在此模式下,直到服务器离开维护模式,否则不会再对其进行检查。如果该服务器被其他服务器跟踪,那些服务器在维护期间也将被设置为 DOWN。在统计页面上,处于维护状态的服务器将显示 "MAINT" 状态,其跟踪服务器将显示 "MAINT(via)" 状态。后端和服务器都可以通过其名称或其数字 ID(前缀为井号 '#')指定。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
恢复临时停止的辅助代理检查。有关临时启动和停止辅助代理的效果的详细信息,请参见 "disable agent"。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
为后端 <backend> 启用动态 cookie 的生成。还必须提供一个密钥。
恢复一个临时停止的前端。某些监听端口可能无法再次绑定(例如:自'disable frontend'操作以来,另一个进程占用了它们)。如果发生这种情况,将显示错误。某些操作系统可能无法恢复被禁用的前端。前端可以通过其名称或其数字 ID(前缀为井号 '#')指定。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
恢复一个临时停止的主健康检查。这将再次启用发送健康检查。详情请参见 "disable health"。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
如果服务器之前被标记为 DOWN 进行维护,此命令将服务器标记为 UP 并重新启用检查。后端和服务器都可以通过其名称或其数字 ID(前缀为井号 '#')指定。此命令受限,并且只能在配置为 "admin" 级别的套接字上发出。
在 map <map> 或 ACL <acl> 中查找值 <value>。 <map> 或 <acl> 是“show map”或“show acl”返回的 #<id> 或 <file>。此命令返回与此 map 关联的所有匹配模式。这对于调试 map 和 ACL 非常有用。输出格式由每个匹配类型的一行组成。每行由空格分隔的单词组成。前两个单词是: <match method>:应用的匹配方法。它可以是“found”、“bool”、“int”、“ip”、“bin”、“len”、“str”、“beg”、“sub”、“dir”、“dom”、“end”或“reg”。 <match result>:结果。可以是“match”或“no-match”。仅当模式匹配条目时,才会返回后续单词。 <index type>:“tree”或“list”。内部查找算法。 <case>:“case-insensitive”或“case-sensitive”。大小写的解释。 <entry matched>:match="<entry>"。返回匹配的模式。对于正则表达式很有用。最后两个单词用于显示返回值及其类型。对于“acl”情况,模式不存在。 return=nothing:没有返回值,因为没有“map”。 return="<value>":以字符串格式返回的值。 return=cannot-display:无法将值转换为字符串。 type="<type>":返回样本的类型。
报告后端 <backend> 中服务器 <server> 的当前权重和初始权重,如果任一不存在则报告错误。初始权重是配置文件中出现的权重。除非当前权重已被更改,否则两者通常相等。后端和服务器都可以通过其名称或其数字 ID(前缀为井号 '#')指定。
打印已知关键字及其基本用法的列表。对于未知命令,也会显示相同的帮助屏幕。
切换行首的提示符,并进入或离开交互模式。在交互模式下,命令完成后连接不会关闭。相反,提示符会再次出现,表示解释器正在等待新命令。提示符由一个右尖括号后跟一个空格“> ”组成。当希望定期检查信息(如统计数据或错误)时,此模式特别方便。在发出“help”命令之前进入交互模式也是一个好主意。
在交互模式下关闭连接。
修改用于生成动态持久性 cookie 的密钥。这将破坏现有会话。
修改 map <map> 中每个键 <key> 对应的值。<map> 是由“show map”返回的 #<id> 或 <file>。如果使用 <ref> 代替 <key>,则只更改 <ref> 指向的条目。新值为 <value>。
动态更改指定前端的 maxconn 设置。允许任何正值,包括零,但设置大于全局 maxconn 的值没有太大意义。如果限制增加并且有待处理的连接,它们将立即被接受。如果限制降低到低于当前连接数的水平,新连接的接受将被延迟,直到达到阈值。前端可以通过其名称或其前缀为井号('#')的数字 ID 来指定。
动态更改指定服务器的 maxconn 设置。允许任何正值,包括零,但设置大于全局 maxconn 的值没有太大意义。
在初始全局 maxconn 设置定义的范围内动态更改全局 maxconn 设置。如果它被增加并且有待处理的连接,它们将立即被接受。如果它被降低到低于当前连接数的水平,新连接的接受将被延迟,直到达到阈值。值为零将恢复初始设置。
为指定的子系统启用或禁用 CPU 分析。这等同于在配置文件的 "global" 部分中设置或清除 "profiling" 设置。另请参阅 "show profiling"。
更改进程范围的连接速率限制,该限制由全局 'maxconnrate' 设置。值为零表示禁用限制。此限制适用于所有前端,并且更改会立即生效。值以每秒连接数的形式传递。
更改最大输入压缩速率,该速率由全局 'maxcomprate' 设置。值为零表示禁用限制。值以每秒千字节数的形式传递。该值可在 "show info" 的 "CompressBpsRateLim" 行中以字节为单位查看。
更改进程范围的会话速率限制,该限制由全局 'maxsessrate' 设置。值为零表示禁用限制。此限制适用于所有前端,并且更改会立即生效。值以每秒会话数的形式传递。
更改进程范围的 SSL 会话速率限制,该限制由全局 'maxsslrate' 设置。值为零表示禁用限制。此限制适用于所有前端,并且更改会立即生效。值以每秒发送到 SSL 堆栈的会话数的形式传递。它在握手之前应用,以保护堆栈免受握手滥用。
用提供的 IP 地址替换服务器的当前 IP 地址。可选地,可以使用 'port' 参数更改端口。请注意,更改端口也支持从端口映射(表示为 +X 或 -Y)切换或切换到端口映射,前提是为健康检查配置了端口。
强制服务器的代理进入新状态。例如,这对于不考虑某些缓慢的代理检查而立即切换服务器状态非常有用。请注意,如果有跟踪服务器,此更改将传播到它们。
更改服务器代理检查的地址。允许在运行时将代理检查迁移到另一个地址。您可以指定 IP 和主机名,它将被解析。
更改发送到代理检查目标的代理字符串。允许在更改服务器地址时更新字符串以保持两者匹配。
强制服务器的健康状态进入新状态。例如,这对于不考虑某些缓慢的健康检查而立即切换服务器状态非常有用。请注意,如果有跟踪服务器,此更改将传播到它们。
将用于健康检查的端口更改为 <port>
强制服务器的管理状态进入新状态。这对于禁用负载均衡和/或到服务器的任何流量非常有用。将状态设置为 "ready" 会使服务器进入正常模式,该命令等同于 "enable server" 命令。将状态设置为 "maint" 会禁用任何到服务器的流量以及任何健康检查。这等同于 "disable server" 命令。将模式设置为 "drain" 只会从负载均衡中移除服务器,但仍允许其被检查并接受新的持久连接。如果有跟踪服务器,更改将传播到它们。
将服务器的权重更改为参数中传递的值。这与下面的 "set weight" 命令完全等效。
将服务器的 FQDN 更改为参数中传递的值。这要求为该服务器配置并启用了内部运行时 DNS 解析器。
更改当前会话期间连接的 stats socket 的严重性输出格式。
此命令用于更新证书的 OCSP 响应(参见 "bind" 行上的 "crt")。执行的控制与初始加载响应时相同。<response> 必须作为来自 OCSP 服务器的 DER 编码响应的 base64 编码字符串传递。BoringSSL 不支持此命令。
openssl ocsp -issuer issuer.pem -cert server.pem \ -host ocsp.issuer.com:80 -respout resp.der echo "set ssl ocsp-response $(base64 -w 10000 resp.der)" | \ socat stdio /var/run/haproxy.stat 使用 payload 语法:echo -e "set ssl ocsp-response <<\n$(base64 resp.der)\n" | \ socat stdio /var/run/haproxy.stat
将 <id> 侦听器的下一个 TLS 密钥设置为 <tlskey>。此密钥成为最终密钥,而倒数第二个密钥用于加密(其他密钥仅用于解密)。最旧的 TLS 密钥将被覆盖。<id> 是由 "show tls-keys" 返回的数字 #<id> 或 <file>。<tlskey> 是 base64 编码的 48 位或 80 位 TLS 票证密钥(例如:openssl rand 80 | openssl base64 -A)。
在 stick-table 中创建或更新一个条目。如果键不存在,则插入一个条目。请参阅 4.2 节 中的 stick-table 以查找 <data_type> 的所有可能值。最常见的用途是动态输入源 IP 地址的条目,并在 gpc0 中设置一个标志来动态阻止 IP 地址或影响其服务质量。可以在单次调用中传递多个 data_type。
更改当前连接的 CLI 接口超时。这在长时间的调试会话中非常有用,用户需要不断检查一些指标而不会被断开连接。延迟以秒为单位传递。
将服务器的权重更改为参数中传递的值。如果值以 '%' 符号结尾,则新权重将相对于最初配置的权重。绝对权重允许在 0 到 256 之间。相对权重必须为正,并且结果的绝对权重上限为 256。运行静态负载均衡算法的集群中的服务器具有更严格的限制,因为一旦设置,权重就不能更改。因此,对于这些服务器,唯一接受的值是 0 和 100%(或 0 和初始权重)。更改会立即生效,尽管某些 LB 算法需要一定数量的请求才能考虑更改。此命令的一个典型用法是在更新期间通过将服务器权重设置为零来禁用服务器,然后在更新完成后将其设置回 100% 来重新启用它。此命令受到限制,只能在配置为 admin 级别的 socket 上发出。后端和服务器都可以通过其名称或其数字 ID(以井号 '#' 为前缀)来指定。
转储有关 acl 转换器的信息。不带参数时,返回所有可用 acl 的列表。如果指定了 <acl>,则转储其内容。<acl> 是 #<id> 或 <file>。转储格式与 map 相同,甚至对于样本值也是如此。返回的数据不是可用 ACL 的列表,而是构成任何 ACL 的所有模式的列表。这些模式中的许多可以与 map 共享。
转储正在运行的进程中可用的后端列表。
显示当前 CLI 会话的 CLI 级别。结果可能是 'admin'、'operator' 或 'user'。另请参阅 'operator' 和 'user' 命令。
$ socat /tmp/sock1 readline prompt > operator > show cli level operator > user > show cli level user > operator 权限被拒绝
将当前 CLI 会话的 CLI 级别降低到操作员。它不能被提高。另请参阅“show cli level”
将当前 CLI 会话的 CLI 级别降低到用户。它不能被提高。另请参阅“show cli level”
报告有关内部事件的一些计数器,这些计数器将帮助开发人员以及更广泛地帮助足够了解 haproxy 的人员缩小异常行为报告的原因范围。一个典型的例子是一个正常运行的进程从不休眠,并消耗 100% 的 CPU。输出字段将由每个度量的一行组成,并且每行上的计数器都是每个线程的。这些计数器是 32 位的,在进程生命周期中会回绕,这没关系,因为通常会执行两次此命令的调用。故意不对字段进行文档记录,以便在馈送计数器的代码中验证它们的精确含义。这些值也会被“clear counters”命令重置。
列出 CLI 套接字。输出格式由 3 个以空格分隔的字段组成。第一个字段是套接字地址,可以是 unix 套接字、ipv4 地址:端口对或 ipv6 地址:端口对。其他类型的套接字不会被转储。第二个字段描述套接字的级别:'admin'、'user' 或 'operator'。最后一个字段列出套接字绑定的进程,用逗号分隔,可以是数字或 'all'。
$ echo 'show cli sockets' | socat stdio /tmp/sock1 # socket lvl processes /tmp/sock1 admin all 127.0.0.1:9999 user 2,3,4 127.0.0.2:9969 user 2 [::1]:9999 operator 2
列出配置的缓存以及存储在每个缓存树中的对象。 $ echo 'show cache' | socat stdio /tmp/sock1 0x7f6ac6c5b03a: foobar (shctx:0x7f6ac6c5b000, available blocks:3918) 1 2 3 4 1. 指向缓存结构的指针 2. 缓存名称 3. 指向 mmap 区域 (shctx) 的指针 4. shctx 中可供重用的块数 0x7f6ac6c5b4cc hash:286881868 size:39114 (39 blocks), refcount:9, expire:237 1 2 3 4 5 6 1. 指向缓存条目的指针 2. 哈希的前 32 位 3. 对象的字节大小 4. 对象使用的块数 5. 使用该条目的事务数 6. 过期时间,如果已过期则可以为负数
转储进程已知的一个或所有环境变量。不带任何参数时,将转储所有变量。带一个参数时,如果指定的变量存在,则仅转储该变量。否则将输出 "Variable not found"。变量的转储格式与它们存储或由 "env" 实用程序返回的格式相同,即 "<name>=<value>"。这在调试大量使用环境变量的某些配置文件时非常方便,以确保它们包含预期的值。此命令受限,并且只能在配置为 "operator" 或 "admin" 级别的套接字上发出。
转储由前端和后端收集的最后已知的请求和响应错误。如果指定了 <iid>,则限制转储到 ID 为 <iid> 的前端或后端的错误。代理 ID“-1”将导致转储所有实例。如果指定了代理名称而不是 ID,则使用其 ID 作为过滤器。如果在代理名称或 ID 之后添加了“request”或“response”,则只转储请求或响应错误。此命令受到限制,只能在配置为“operator”或“admin”级别的 socket 上发出。可能收集的错误是协议违规引起的最后请求和响应错误,通常是由于请求头名称中存在无效字符。报告精确地指出了哪个确切字符违反了协议。其他重要信息,如检测到错误的准确日期、前端和后端名称、服务器名称(已知时)、内部会话 ID 和启动该会话的源地址也一并报告。所有字符都会被返回,不可打印字符会被编码。最常见的字符(\t = 9, \n = 10, \r = 13 和 \e = 27)被编码为一个字母,后面跟着反斜杠。反斜杠本身被编码为 '\\' 以避免混淆。其他不可打印字符被编码为 '\xNN',其中 NN 是字符 ASCII 码的两位十六进制表示。行以其第一个字符的位置为前缀,从缓冲区的开头开始为 0。每行最多打印一个输入行,并且长行将被分解为多个连续的输出行,以便输出不超过 79 个字符宽度。很容易检测一行是否被分解,因为它不会以 '\n' 结尾,并且下一行的偏移量将后跟一个 '+' 符号,表示它是上一行的延续。
$ echo "show errors -1 response" | socat stdio /tmp/sock1 >>> [04/Mar/2009:15:46:56.081] backend http-in (#2) : invalid response src 127.0.0.1, session #54, frontend fe-eth0 (#1), server s2 (#1) response length 213 bytes, error at position 23: 00000 HTTP/1.0 200 OK\r\n 00017 header/bizarre:blah\r\n 00038 Location: blah\r\n 00054 Long-line: this is a very long line which should b 00104+ e broken into multiple lines on the output buffer, 00154+ otherwise it would be too large to print in a ter 00204+ minal\r\n 00211 \r\n 在上面的示例中,我们看到内部 ID 为 2 的后端“http-in”阻止了其服务器 s2(内部 ID 为 1)的无效响应。请求发生在会话 54,由源 127.0.0.1 启动,并由 ID 为 1 的前端 fe-eth0 接收。错误检测时,总响应长度为 213 字节,错误位于第 23 字节。这是标题名称“header/bizarre”中的斜杠('/'),它不是 HTTP 标题名称的有效字符。
转储所有打开的文件描述符的列表,或者如果指定了数字 <fd>,则仅转储该数字。这仅面向需要观察内部状态以调试诸如异常 CPU 使用率等复杂问题的开发人员。每行报告一个 fd,对于每个 fd,使用大写字母表示已启用标志,小写字母表示已禁用标志,使用“P”表示“已轮询”,“R”表示“就绪”,“A”表示“活动”,事件状态使用“H”表示“挂起”,“E”表示“错误”,“O”表示“输出”,“P”表示“优先级”,“I”表示“输入”,其他一些标志如“N”表示“新”(刚刚添加到 fd 缓存中),“U”表示“已更新”(在 fd 缓存中收到更新),“L”表示“linger_risk”,“C”表示“已克隆”,然后是缓存条目位置、内部所有者的指针、I/O 回调的指针及其已知名称。当所有者是连接时,将报告连接标志和目标(前端、代理或服务器)。当所有者是侦听器时,将报告侦听器的状态及其前端。在不充分了解内部机制的情况下使用此命令毫无意义。值得注意的是,输出格式可能会随时间演变,因此此输出不得由旨在持久的工具解析。
转储当前进程中 haproxy 状态的信息。如果传递了“typed”作为可选参数,还将发出字段编号、名称和类型,以便外部监控产品可以轻松检索、可能聚合,然后报告它们不知道的字段中的信息。每个字段都在自己的行上转储。如果传递了“json”作为可选参数,则“typed”输出提供的信息将以 JSON 格式作为 JSON 对象列表提供。默认情况下,格式仅包含两个由冒号(“:”)分隔的列。左边是字段名,右边是值。非常重要的是要注意,在 typed 输出格式中,单个对象的转储是连续的,因此消费者无需一次性存储所有内容。在使用 typed 输出格式时,每行由四个由冒号(“:”)分隔的列组成。第一列是一个由 3 个元素组成的点分隔系列。第一个元素是字段在列表中的数字位置(从零开始)。此位置不会随时间改变,但可能会出现空白,具体取决于构建选项或将来是否删除某些字段。第二个元素是默认的“show info”输出中出现的字段名。第三个元素是相对进程编号,从 1 开始。从第一个冒号开始的其余行遵循上面部分中描述的“typed output format”。简而言之,第二列(第一个“:”之后)指示变量的来源、性质和范围。第三列指示字段的类型,包括“s32”、“s64”、“u32”、“u64”和“str”。然后第四列是值本身,消费者知道如何解析(感谢第三列)以及如何处理(感谢第二列)。因此,typed 模式下的整体行格式为:<field_pos>.<field_name>.<process_num>:<tags>:<type>:<value>
> show info Name: HAProxy Version: 1.7-dev1-de52ea-146 Release_date: 2016/03/11 Nbproc: 1 Process_num: 1 Pid: 28105 Uptime: 0d 0h00m04s Uptime_sec: 4 Memmax_MB: 0 PoolAlloc_MB: 0 PoolUsed_MB: 0 PoolFailed: 0 (...) > show info typed 0.Name.1:POS:str:HAProxy 1.Version.1:POS:str:1.7-dev1-de52ea-146 2.Release_date.1:POS:str:2016/03/11 3.Nbproc.1:CGS:u32:1 4.Process_num.1:KGP:u32:1 5.Pid.1:SGP:u32:28105 6.Uptime.1:MDP:str:0d 0h00m08s 7.Uptime_sec.1:MDP:u32:8 8.Memmax_MB.1:CLP:u32:0 9.PoolAlloc_MB.1:MGP:u32:0 10.PoolUsed_MB.1:MGP:u32:0 11.PoolFailed.1:MCP:u32:0 (...)
在类型化格式中,第一列末尾的进程ID使得从多个进程的输出中进行视觉聚合变得非常容易。
$ ( echo show info typed | socat /var/run/haproxy.sock1 ; \ echo show info typed | socat /var/run/haproxy.sock2 ) | \ sort -t . -k 1,1n -k 2,2 -k 3,3n 0.Name.1:POS:str:HAProxy 0.Name.2:POS:str:HAProxy 1.Version.1:POS:str:1.7-dev1-868ab3-148 1.Version.2:POS:str:1.7-dev1-868ab3-148 2.Release_date.1:POS:str:2016/03/11 2.Release_date.2:POS:str:2016/03/11 3.Nbproc.1:CGS:u32:2 3.Nbproc.2:CGS:u32:2 4.Process_num.1:KGP:u32:1 4.Process_num.2:KGP:u32:2 5.Pid.1:SGP:u32:30120 5.Pid.2:SGP:u32:30121 6.Uptime.1:MDP:str:0d 0h01m28s 6.Uptime.2:MDP:str:0d 0h01m28s (...)
JSON 输出的格式在一个 schema 中描述,可以使用“show schema json”输出该 schema。JSON 输出不包含额外的空白字符,以减少输出量。为了方便人类阅读,可以通过一个美化打印器来处理输出。示例:$ echo "show info json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool JSON 输出不包含额外的空白字符,以减少输出量。为了方便人类阅读,可以通过一个美化打印器来处理输出。示例:$ echo "show info json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool
转储关于 map 转换器的信息。不带参数时,返回所有可用 map 的列表。如果指定了 <map>,则转储其内容。<map> 是 #<id> 或 <file>。第一列是唯一标识符。它可以作为 "del map" 和 "set map" 操作的参考。第二列是模式,第三列是样本(如果可用)。返回的数据不是直接的可用 map 列表,而是构成任何 map 的所有模式的列表。这些模式中的许多可以与 ACL 共享。
转储“peers”部分中配置的对等方的信息。如果不带参数,则列出所有“peers”部分所属的对等方列表。如果指定了 <peers section>,则仅转储属于该“peers”部分的信息。以下是“sharedlb”对等方部分的 hostA、hostB 和 hostC 对等方的示例输出。只有 hostA 和 hostB 已连接。只有 hostA 向 hostB 发送了数据。 $ echo "show peers" | socat - /tmp/hostA 0x55deb0224320: [15/Apr/2019:11:28:01] id=sharedlb state=0 flags=0x3 \ resync_timeout=<PAST> task_calls=45122 0x55deb022b540: id=hostC(remote) addr=127.0.0.12:10002 status=CONN \ reconnect=4s confirm=0 flags=0x0 0x55deb022a440: id=hostA(local) addr=127.0.0.10:10000 status=NONE \ reconnect=<NEVER> confirm=0 flags=0x0 0x55deb0227d70: id=hostB(remote) addr=127.0.0.11:10001 status=ESTA reconnect=2s confirm=0 flags=0x20000200 appctx:0x55deb028fba0 st0=7 st1=0 task_calls=14456 \ state=EST xprt=RAW src=127.0.0.1:37257 addr=127.0.0.10:10000 remote_table:0x55deb0224a10 id=stkt local_id=1 remote_id=1 last_local_table:0x55deb0224a10 id=stkt local_id=1 remote_id=1 shared tables: 0x55deb0224a10 local_id=1 remote_id=1 flags=0x0 remote_data=0x65 last_acked=0 last_pushed=3 last_get=0 teaching_origin=0 update=3 table:0x55deb022d6a0 id=stkt update=3 localupdate=3 \ commitupdate=3 syncing=0 $ echo "show peers" | socat - /tmp/hostB 0x55871b5ab320: [15/Apr/2019:11:28:03] id=sharedlb state=0 flags=0x3 \ resync_timeout=<PAST> task_calls=3 0x55871b5b2540: id=hostC(remote) addr=127.0.0.12:10002 status=CONN \ reconnect=3s confirm=0 flags=0x0 0x55871b5b1440: id=hostB(local) addr=127.0.0.11:10001 status=NONE \ reconnect=<NEVER> confirm=0 flags=0x0 0x55871b5aed70: id=hostA(remote) addr=127.0.0.10:10000 status=ESTA \ reconnect=2s confirm=0 flags=0x20000200 appctx:0x7fa46800ee00 st0=7 st1=0 task_calls=62356 \ state=EST remote_table:0x55871b5ab960 id=stkt local_id=1 remote_id=1 last_local_table:0x55871b5ab960 id=stkt local_id=1 remote_id=1 shared tables: 0x55871b5ab960 local_id=1 remote_id=1 flags=0x0 remote_data=0x65 last_acked=3 last_pushed=0 last_get=3 teaching_origin=0 update=0 table:0x55871b5b46a0 id=stkt update=1 localupdate=0 \ commitupdate=0 syncing=0
转储内部内存池的状态。这对于在怀疑有内存泄漏时跟踪内存使用情况很有用。它与在前台运行时发送 SIGQUIT 信号的作用完全相同,只是它不会刷新池。
转储当前的分析设置,每行一个,以及更改它们所需的命令。
转储运行配置中找到的服务器状态。可以提供后端名称或标识符来将输出限制在该后端。转储格式如下:- 第一行包含格式版本(本规范中为 1);- 第二行包含列标题,前面有一个井号 ('#');- 第三行及之后的行包含数据;- 以井号 ('#') 开头的每一行都将被视为注释。由于可能存在输出的多个版本,因此以下是每个文件格式版本的字段列表及其顺序:1:be_id:后端唯一 ID。be_name:后端标签。srv_id:服务器唯一 ID(在后端中)。srv_name:服务器标签。srv_addr:服务器 IP 地址。srv_op_state:服务器运行状态(UP/DOWN/...)。0 = SRV_ST_STOPPED 服务器已关闭。1 = SRV_ST_STARTING 服务器正在预热(已启动但已节流)。2 = SRV_ST_RUNNING 服务器已完全启动。3 = SRV_ST_STOPPING 服务器已启动但正在软停止(例如 404)。srv_admin_state:服务器管理状态(MAINT/DRAIN/...)。该状态实际上是一个值掩码:0x01 = SRV_ADMF_FMAINT 服务器已明确强制进入维护状态。0x02 = SRV_ADMF_IMAINT 服务器已从受监控服务器继承维护状态。0x04 = SRV_ADMF_CMAINT 服务器因配置而处于维护状态。0x08 = SRV_ADMF_FDRAIN 服务器已明确强制进入排水状态。0x10 = SRV_ADMF_IDRAIN 服务器已从受监控服务器继承排水状态。0x20 = SRV_ADMF_RMAINT 服务器因 IP 地址解析失败而处于维护状态。0x40 = SRV_ADMF_HMAINT 服务器的 FQDN 是从统计套接字设置的。srv_uweight:用户可见的服务器权重。srv_iweight:服务器的初始权重。srv_time_since_last_change:自上次操作更改以来的时间。srv_check_status:上次健康检查状态。srv_check_result:上次检查结果(FAILED/PASSED/...)。0 = CHK_RES_UNKNOWN 默认初始化为该值。1 = CHK_RES_NEUTRAL 有效检查但没有状态信息。2 = CHK_RES_FAILED 检查失败。3 = CHK_RES_PASSED 检查成功,服务器已完全启动。4 = CHK_RES_CONDPASS 检查报告服务器不希望新的会话。srv_check_health:检查上升/下降计数器。srv_check_state:检查状态(ENABLED/PAUSED/...)。该状态实际上是一个值掩码:0x01 = CHK_ST_INPROGRESS 检查当前正在运行。0x02 = CHK_ST_CONFIGURED 此检查已配置,可以启用。0x04 = CHK_ST_ENABLED 此检查目前已获得管理启用。0x08 = CHK_ST_PAUSED 由于维护(仅健康)而暂停检查。srv_agent_state:代理检查状态(ENABLED/PAUSED/...)。此状态使用与“srv_check_state”相同的掩码值,并添加此特定值:0x10 = CHK_ST_AGENT 检查是代理检查(否则是健康检查)。bk_f_forced_id:标志,用于了解后端 ID 是否由配置强制。srv_f_forced_id:标志,用于了解服务器 ID 是否由配置强制。srv_fqdn:服务器 FQDN。srv_port:服务器端口。srvrecord:与此 SRV 关联的 DNS SRV 记录。转储所有已知会话。避免在慢速连接上执行此操作,因为这可能会产生大量数据。此命令受限制,只能在配置为“operator”或“admin”级别的套接字上发出。请注意,在连接快速回收的机器上,此输出报告的条目数可能少于实际存在的条目数,因为它将转储在输入命令之前创建的所有现有会话直到最后一个;在此期间终止的会话将不会出现。
显示有关指定会话标识符的大量内部信息。此标识符是“show sess”转储中行开头的第一个字段(它对应于会话指针)。这些信息对大多数用户无用,但可能被 haproxy 开发人员用于排查复杂的错误。输出格式有意不作文档说明,以便可以根据需求自由演变。您可以在 src/dumpstats.c 中找到所有返回字段的描述。特殊 id "all" 会转储所有会话的状态,必须尽可能避免使用,因为它非常消耗 CPU 并且可能需要很长时间。
使用 CSV 格式转储统计信息;如果 "typed" 在其他参数后传递,则使用上面章节描述的扩展类型化输出格式;或者如果 "json" 在其他参数后传递,则以 JSON 格式转储。通过传递 <id>、<type> 和 <sid>,可以只转储选定的项目:- <iid> 是代理 ID,-1 表示转储所有内容。或者,可以指定代理名称 <proxy>。在这种情况下,此代理的 ID 将用作 ID 选择器。- <type> 选择可转储对象的类型:1 表示前端,2 表示后端,4 表示服务器,-1 表示所有内容。这些值可以进行或运算,例如:1 + 2 = 3 -> 前端 + 后端。1 + 2 + 4 = 7 -> 前端 + 后端 + 服务器。- <sid> 是服务器 ID,-1 表示转储所选代理的所有内容。
$ echo "show info;show stat" | socat stdio unix-connect:/tmp/sock1 >>> Name: HAProxy Version: 1.4-dev2-49 Release_date: 2009/09/23 Nbproc: 1 Process_num: 1 (...) # pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq, (...) stats,FRONTEND,,,0,0,1000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,1,0, (...) stats,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,0,0,0,,0,250,(...) (...) www1,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,1,1,0,,0,250, (...) $
在此示例中,一次发出了两个命令。这样可以轻松地在多进程模式下找到统计信息属于哪个进程。在 typed 输出格式中不需要这样做,因为进程号会报告在每一行上。注意信息输出后的空行,它标志着第一个块的结束。第二个块(统计信息)的末尾会出现类似的空行,以便读者知道输出没有被截断。指定“typed”时,输出格式更适合监控工具,因为它提供数字位置并指示每个输出字段的类型。每个值都单独一行,包含进程号、元素号、性质、来源和范围。相同的格式可通过 HTTP 统计信息获得,只需在 URI 后附加“;typed”。非常重要的是要注意,在 typed 输出格式中,单个对象的转储是连续的,因此消费者无需一次性存储所有内容。在使用 typed 输出格式时,每行由四个由冒号(“:”)分隔的列组成。第一列是一个由 5 个元素组成的点分隔系列。第一个元素是一个字母,指示正在描述的对象的类型。目前已知以下对象类型:'F' 代表前端,'B' 代表后端,'L' 代表侦听器,'S' 代表服务器。第二个元素是一个正整数,表示对象所属代理的唯一标识符。它等同于 CSV 输出的“iid”列,并匹配前端或后端部分中可选“id”指令前面的值。第三个元素是一个正整数,包含代理内部的唯一对象标识符,对应于 CSV 输出的“sid”列。转储前端或后端时报告 ID 0。对于侦听器或服务器,这对应于它们在代理内部各自的 ID。第四个元素是字段在列表中的数字位置(从零开始)。此位置不会随时间改变,但可能会出现空白,具体取决于构建选项或将来是否删除某些字段。第五个元素是字段名,与 CSV 输出中的名称相同。第六个元素是一个正整数,是相对进程编号,从 1 开始。从第一个冒号开始的其余行遵循上面部分中描述的“typed output format”。简而言之,第二列(第一个“:”之后)指示变量的来源、性质和范围。第三列指示字段的类型,包括“s32”、“s64”、“u32”、“u64”和“str”。然后第四列是值本身,消费者知道如何解析(感谢第三列)以及如何处理(感谢第二列)。因此,typed 模式下的整体行格式为:<obj>.<px_id>.<id>.<fpos>.<fname>.<process_num>:<tags>:<type>:<value> 以下是 typed 输出格式的示例:$ echo "show stat typed" | socat stdio unix-connect:/tmp/sock1 F.2.0.0.pxname.1:MGP:str:private-frontend F.2.0.1.svname.1:MGP:str:FRONTEND F.2.0.8.bin.1:MGP:u64:0 F.2.0.9.bout.1:MGP:u64:0 F.2.0.40.hrsp_2xx.1:MGP:u64:0 L.2.1.0.pxname.1:MGP:str:private-frontend L.2.1.1.svname.1:MGP:str:sock-1 L.2.1.17.status.1:MGP:str:OPEN L.2.1.73.addr.1:MGP:str:0.0.0.0:8001 S.3.13.60.rtime.1:MCP:u32:0 S.3.13.61.ttime.1:MCP:u32:0 S.3.13.62.agent_status.1:MGP:str:L4TOUT S.3.13.64.agent_duration.1:MGP:u64:2001 S.3.13.65.check_desc.1:MCP:str:Layer4 timeout S.3.13.66.agent_desc.1:MCP:str:Layer4 timeout S.3.13.67.check_rise.1:MCP:u32:2 S.3.13.68.check_fall.1:MCP:u32:3 S.3.13.69.check_health.1:SGP:u32:0 S.3.13.70.agent_rise.1:MaP:u32:1 S.3.13.71.agent_fall.1:SGP:u32:1 S.3.13.72.agent_health.1:SGP:u32:1 S.3.13.73.addr.1:MCP:str:1.255.255.255:8888 S.3.13.75.mode.1:MAP:str:http B.3.0.0.pxname.1:MGP:str:private-backend B.3.0.1.svname.1:MGP:str:BACKEND B.3.0.2.qcur.1:MGP:u32:0 B.3.0.3.qmax.1:MGP:u32:0 B.3.0.4.scur.1:MGP:u32:0 B.3.0.5.smax.1:MGP:u32:0 B.3.0.6.slim.1:MGP:u32:1000 B.3.0.55.lastsess.1:MMP:s32:-1 (...) 在 typed 格式中,第一列末尾的进程 ID 使从多个进程聚合输出变得非常容易,如下面的示例所示,其中每个进程的每一行都会出现:$ ( echo show stat typed | socat /var/run/haproxy.sock1 - ; \ echo show stat typed | socat /var/run/haproxy.sock2 - ) | \ sort -t . -k 1,1 -k 2,2n -k 3,3n -k 4,4n -k 5,5 -k 6,6n B.3.0.0.pxname.1:MGP:str:private-backend B.3.0.0.pxname.2:MGP:str:private-backend B.3.0.1.svname.1:MGP:str:BACKEND B.3.0.1.svname.2:MGP:str:BACKEND B.3.0.2.qcur.1:MGP:u32:0 B.3.0.2.qcur.2:MGP:u32:0 B.3.0.3.qmax.1:MGP:u32:0 B.3.0.3.qmax.2:MGP:u32:0 B.3.0.4.scur.1:MGP:u32:0 B.3.0.4.scur.2:MGP:u32:0 B.3.0.5.smax.1:MGP:u32:0 B.3.0.5.smax.2:MGP:u32:0 B.3.0.6.slim.1:MGP:u32:1000 B.3.0.6.slim.2:MGP:u32:1000 (...) JSON 输出的格式在模式中进行了描述,该模式可以使用“show schema json”输出。JSON 输出不包含额外的空格,以减少输出量。供人类消费,通过美化打印机处理输出可能很有帮助。示例:$ echo "show stat json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool JSON 输出不包含额外的空格,以减少输出量。供人类消费,通过美化打印机处理输出可能很有帮助。示例:$ echo "show stat json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool
转储给定解析器部分或所有解析器部分(如果未提供部分)的统计信息。对于每个名称服务器,报告以下计数器: sent: 发送到此服务器的 DNS 请求数 valid: 从此服务器收到的有效 DNS 响应数 update: 用于更新服务器 IP 地址的 DNS 响应数 cname: CNAME 响应数 cname_error: 此服务器遇到的 CNAME 错误数 any_err: 空响应数 (即:服务器不支持 ANY 类型) nx: 从此服务器收到的不存在的域响应 timeout: 此服务器未及时应答的次数 refused: 此服务器拒绝的请求数 other: 任何其他 DNS 错误 invalid: 无效的 DNS 响应(从协议角度看) too_big: 响应过大 outdated: 响应到达过晚的次数(在另一个名称服务器之后)
转储所有已知粘性表的一般信息。返回它们的名称(持有它们的代理的名称)、它们的类型(目前为零,总是 IP)、它们的最大可能条目数大小,以及当前使用的条目数。
$ echo "show table" | socat stdio /tmp/sock1 >>> # table: front_pub, type: ip, size:204800, used:171454 >>> # table: back_rdp, type: ip, size:204800, used:0
转储 stick-table <name> 的内容。在此模式下,第一行报告有关表的通用信息,就像使用“show table”一样,然后转储所有条目。由于这可能非常庞大,因此可以指定过滤器来指定要显示哪些条目。当使用“data.”形式时,过滤器应用于存储的数据(参见“stick-table”在第 4.2 节)。必须在 <type> 中指定存储的数据类型,并且该数据类型必须存储在表中,否则将报告错误。数据根据 <operator> 与 64 位整数 <value> 进行比较。运算符与 ACL 中的运算符相同:- eq:匹配数据等于此值的所有条目- ne:匹配数据不等于此值的所有条目- le:匹配数据小于或等于此值的所有条目- ge:匹配数据大于或等于此值的所有条目- lt:匹配数据小于此值的所有条目- gt:匹配数据大于此值的所有条目当使用 key 形式时,将显示条目 <key>。键的类型必须与表的类型相同,该类型目前仅限于 IPv4、IPv6、整数和字符串。
$ echo "show table http_proxy" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:2 >>> 0x80e6a4c: key=127.0.0.1 use=0 exp=3594729 gpc0=0 conn_rate(30000)=1 \ bytes_out_rate(60000)=187 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191 $ echo "show table http_proxy data.gpc0 gt 0" | socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:2 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191 $ echo "show table http_proxy data.conn_rate gt 5" | \ socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:2 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191 $ echo "show table http_proxy key 127.0.0.2" | \ socat stdio /tmp/sock1 >>> # table: http_proxy, type: ip, size:204800, used:2 >>> 0x80e6a80: key=127.0.0.2 use=0 exp=3594740 gpc0=1 conn_rate(30000)=10 \ bytes_out_rate(60000)=191
当数据标准应用于依赖于时间的动态值(例如字节速率)时,该值在评估条目期间动态计算,以决定是否必须转储它。这意味着这样的过滤器可能会在一段时间内匹配,然后由于时间的推移,平均事件速率下降而不再匹配。可以利用这一点来提取滥用服务的 IP 地址列表,以便监视它们甚至在防火墙中将它们列入黑名单。
$ echo "show table http_proxy data.gpc0 gt 0" \ | socat stdio /tmp/sock1 \ | fgrep 'key=' | cut -d' ' -f2 | cut -d= -f2 > abusers-ip.txt ( 或 | awk '/key/{ print a[split($2,a,"=")]; }' )
转储所有已加载的 TLS ticket 密钥引用。将显示 TLS ticket 密钥引用 ID 和加载密钥的文件。这两个都可用于使用“set ssl tls-key”更新 TLS 密钥。如果指定了一个 ID 作为参数,它将转储 tickets,使用 * 将转储每个引用的所有密钥。
转储用于“show info json”和“show stat json”输出的模式。为了减少输出量,其中不包含额外的空格。为了方便人类阅读,可以通过一个美化打印器来处理输出。示例:$ echo "show schema json" | socat /var/run/haproxy.sock stdio | \ python -m json.tool 该模式遵循“JSON Schema”(json-schema.org),因此可以使用验证器来验证“show info json”和“show stat json”的输出是否符合该模式。
完全删除指定的前端。它所绑定的所有端口都将被释放。此操作后将无法再启用该前端。这旨在用于那些甚至无法想象停止代理但必须修复配置错误的代理的环境中。这样就可以释放端口并将其绑定到另一个进程以恢复操作。一旦终止,该前端将完全不会出现在统计页面上。前端可以通过其名称或以井号('#')为前缀的数字 ID 来指定。此命令受限制,只能在配置为 "admin" 级别的套接字上发出。
立即终止与指定会话标识符匹配的会话。此标识符是“show sess”转储中行开头的第一个字段(它对应于会话指针)。这可用于终止一个长时间运行的会话,而无需等待超时或当正在进行无休止的传输时。此类被终止的会话在日志中以“K”标志报告。
立即终止附加到指定服务器的所有会话。例如,这可用于在服务器进入维护模式后终止长时间运行的会话。此类被终止的会话在日志中以“K”标志报告。
master CLI 是在 master-worker 模式下绑定到 master 进程的套接字。此 CLI 提供了对每个正在运行或正在退出的进程中的 unix 套接字命令的访问,并允许对这些进程进行基本监控。master CLI 只能通过 haproxy 程序的 -S 选项进行配置。此选项还接受以逗号分隔的绑定选项。
# haproxy -W -S 127.0.0.1:1234 -f test1.cfg # haproxy -Ws -S /tmp/master-socket,uid,1000,gid,1000,mode,600 -f test1.cfg # haproxy -W -S /tmp/master-socket,level,user -f test1.cfg
主 CLI 引入了一个新的 'show proc' 命令来监督进程
$ echo 'show proc' | socat /var/run/haproxy-master.sock - #<PID> <type> <relative PID> <reloads> <uptime> 1162 master 0 5 0d 00h02m07s # workers 1271 worker 1 0 0d 00h00m00s 1272 worker 2 0 0d 00h00m00s # old workers 1233 worker [was: 1] 3 0d 00h00m43s
在此示例中,主节点已重新加载 5 次,但其中一个旧工作进程仍在运行,并已成功处理 3 次重新加载。您可以访问该工作进程的 CLI 来了解发生了什么。当提示符已启用(通过“prompt”命令)时,CLI 正在处理的上下文将显示在提示符中。主节点由“master”字符串标识,其他进程由其 PID 标识。如果最后一次重新加载失败,主提示符将变为“master[ReloadFailed]>”,以便可以看到该进程仍在运行先前配置,并且新配置不可操作。主 CLI 使用特殊的否定表示法来访问多个进程。这种表示法很容易识别,因为它以 @ 开头。@ 前缀后面可以跟一个相对进程号,或者一个感叹号和一个 PID。(例如 @1 或 @!1271)。单独的 @ 可以用于指定主节点。残留进程只能使用 PID 访问,因为相对进程号只能与当前进程一起使用。
$ socat /var/run/haproxy-master.sock readline prompt master> @1 show info; @2 show info [...] Process_num: 1 Pid: 1271 [...] Process_num: 2 Pid: 1272 [...] master> $ echo '@!1271 show info; @!1272 show info' | socat /var/run/haproxy-master.sock - [...]
前缀可以作为一个命令使用,它将把之后的所有命令发送到指定的进程。
$ socat /var/run/haproxy-master.sock readline prompt master> @1 1271> show info [...] 1271> show stat [...] 1271> @ master> $ echo '@1; show info; show stat; @2; show info; show stat' | socat /var/run/haproxy-master.sock - [...]
您还可以使用“reload”命令重新加载 HAProxy 主进程,该命令与 `kill -USR2` 对主进程执行的操作相同,前提是用户至少具有 "operator" 或 "admin" 权限。
$ echo "reload" | socat /var/run/haproxy-master.sock
请注意,重新加载将关闭与 master CLI 的连接。
非常常见的情况是,构成集群的两个 HAProxy 节点除了少数地址外,配置完全相同。无需为每个节点维护重复的配置(这不可避免地会产生分歧),可以通过在配置中包含环境变量来实现。因此,多个配置可以共享完全相同的配置文件,而只需更改少数几个系统范围的环境变量。这始于 1.5 版本,当时只允许地址包含环境变量,而 1.6 版本更进一步,支持在任何地方使用环境变量。语法与 UNIX shell 相同,变量以美元符号('$')开头,后跟开括号('{'),然后是变量名,最后是闭括号('}')。除了地址之外,环境变量仅在用双引号括起来的参数中进行解释(这是必要的,以免破坏现有使用涉及美元符号的正则表达式的设置)。环境变量也使得编写可在各种站点上运行的配置变得方便,这些配置的唯一变化是地址。它还可以允许从某些配置中删除密码。以下示例显示了 init 脚本在启动时如何加载“site1.env”文件:$ cat site1.env LISTEN=192.168.1.1 CACHE_PFX=192.168.11 SERVER_PFX=192.168.22 LOGGER=192.168.33.1 STATSLP=admin:pa$$w0rd ABUSERS=/etc/haproxy/abuse.lst TIMEOUT=10s $ cat haproxy.cfg global log "${LOGGER}:514" local0 defaults mode http timeout client "${TIMEOUT}" timeout server "${TIMEOUT}" timeout connect 5s frontend public bind "${LISTEN}:80" http-request reject if { src -f "${ABUSERS}" } stats uri /stats stats auth "${STATSLP}" use_backend cache if { path_end .jpg .css .ico } default_backend server backend cache server cache1 "${CACHE_PFX}.1:18080" check server cache2 "${CACHE_PFX}.2:18080" check backend server server cache1 "${SERVER_PFX}.1:8080" check server cache2 "${SERVER_PFX}.2:8080" check有时会有人报告说,系统重启后,haproxy 服务没有启动,手动启动后就可以工作。大多数情况下,这些人正在使用集群 IP 地址机制,例如 keepalived,仅将服务 IP 地址分配给主节点,并且虽然他们在绑定 haproxy 到地址 0.0.0.0 时曾经工作过,但在绑定到虚拟 IP 地址后就停止工作了。这里发生的情况是,当服务启动时,虚拟 IP 地址尚未由本地节点拥有,因此当 HAProxy 尝试绑定到它时,系统会拒绝,因为它不是本地 IP 地址。修复方法不是延迟 haproxy 服务的启动(因为它无法承受重新启动),而是正确配置系统以允许绑定到非本地地址。在 Linux 上,通过将 net.ipv4.ip_nonlocal_bind sysctl 设置为 1 可以轻松实现这一点。为了透明地拦截 HAProxy 为特定目标地址传递的 IP 流量,这也很有必要。涉及源端口范围的多进程配置似乎也能正常工作,但它们会在高负载下导致一些随机故障,因为多个进程可能尝试使用相同的源端口连接到同一服务器,这是不可能的。系统将报告一个错误,然后重试,选择另一个端口。虽然“retries”参数的高值可以在一定程度上隐藏此效果,但它也会导致 CPU 使用率和处理时间增加。日志也将报告一定数量的重试。因此,在多进程配置中应避免使用端口范围。由于 HAProxy 使用 SO_REUSEPORT 并支持多个独立进程绑定到同一 IP:port,因此在故障排除过程中,可能会发生旧进程在启动新进程之前未停止的情况。这提供了荒谬的测试结果,倾向于表明对配置的任何更改都被忽略了。实际上,即使新进程已用新配置重新启动,旧进程也会收到一些传入连接并处理它们,从而返回意外结果。如有疑问,只需停止新进程然后重试。如果仍然有效,则很可能意味着旧进程仍然存在,必须停止。Linux 的“netstat -lntp”对此很有帮助。当从命令行向 ACL 添加条目时(例如,在阻止源地址时),重要的是要记住,这些条目不会同步到文件,如果有人重新加载配置,这些更新将丢失。虽然这通常是期望的效果(用于黑名单),但当更改是为了修复问题时,它可能不符合预期。请参阅 CLI 界面的“add acl”操作。
当 HAProxy 使用“-d”选项启动时,它将保持在前台运行,并为每个事件打印一行,例如传入连接、连接结束,以及看到的每个请求或响应头行的信息。此调试输出在内容处理之前发出,因此它们不考虑本地修改。主要用途是显示请求和响应,而无需运行网络嗅探器。当并行处理多个连接时,输出可读性较差,尽管 examples/ 目录中的“debug2ansi”和“debug2html”脚本通过着色输出来提供帮助。如果 HAProxy 因请求或响应格式错误而拒绝请求或响应,最好的方法是连接到 CLI 并执行“show errors”,这将报告每个前端和后端捕获的最后一个有问题的请求和响应,并包含所有必要信息,以精确指示输入流中被拒绝的第一个字符。有时需要用它来向客户或开发人员证明他们的代码中存在错误。在这种情况下,通常可以通过使用“option accept-invalid-http-request”或其等效选项来放宽检查(但仍保留捕获),以处理来自服务器的响应“option accept-invalid-http-response”。更多详情请参阅配置手册。
> show errors Total events captured on [13/Oct/2015:13:43:47.169] : 1 [13/Oct/2015:13:43:40.918] frontend HAProxyLocalStats (#2): invalid request backend <NONE> (#-1), server <NONE> (#-1), event #0 src 127.0.0.1:51981, session #0, session flags 0x00000080 HTTP msg state 26, msg flags 0x00000000, tx flags 0x00000000 HTTP chunk len 0 bytes, HTTP body len 0 bytes buffer flags 0x00808002, out 0 bytes, total 31 bytes pending 31 bytes, wrapping at 8040, error at position 13: 00000 GET /invalid request HTTP/1.1\r\n
CLI 上“show info”的输出提供了大量有关已达到的最大连接速率、已达到的最大 SSL 密钥速率以及通常有助于解释 CPU 或内存使用率临时问题的所有信息。示例:> show info Name: HAProxy Version: 1.6-dev7-e32d18-17 Release_date: 2015/10/12 Nbproc: 1 Process_num: 1 Pid: 7949 Uptime: 0d 0h02m39s Uptime_sec: 159 Memmax_MB: 0 Ulimit-n: 120032 Maxsock: 120032 Maxconn: 60000 Hard_maxconn: 60000 CurrConns: 0 CumConns: 3 CumReq: 3 MaxSslConns: 0 CurrSslConns: 0 CumSslConns: 0 Maxpipes: 0 PipesUsed: 0 PipesFree: 0 ConnRate: 0 ConnRateLimit: 0 MaxConnRate: 1 SessRate: 0 SessRateLimit: 0 MaxSessRate: 1 SslRate: 0 SslRateLimit: 0 MaxSslRate: 0 SslFrontendKeyRate: 0 SslFrontendMaxKeyRate: 0 SslFrontendSessionReuse_pct: 0 SslBackendKeyRate: 0 SslBackendMaxKeyRate: 0 SslCacheLookups: 0 SslCacheMisses: 0 CompressBpsIn: 0 CompressBpsOut: 0 CompressBpsRateLim: 0 ZlibMemUsage: 0 MaxZlibMemUsage: 0 Tasks: 5 Run_queue: 1 Idle_pct: 100 node: wtap description: 当 HAProxy 的新版本中随机出现问题时(例如,每第二个请求被中止,偶尔崩溃等),值得尝试启用内存中毒,以便每次调用 malloc() 后立即用可配置的字节填充内存区域。默认情况下,此字节是 0x50(ASCII 字符 'P'),但可以使用任何其他字节,包括零(这将产生与 calloc() 相同的影响,并可能使问题消失)。通过命令行使用“-dM”选项启用内存中毒。它会轻微降低性能,不建议在生产环境中使用。如果启用内存中毒后问题始终存在,或者当中毒使用零字节时问题从未出现,这明确表示您发现了错误,并且绝对需要报告它。否则,如果没有明显的变化,问题就与此无关。在调试某些延迟问题时,重要的是要同时使用本地机器上的 strace 和 tcpdump,以及远程系统上的另一个 tcpdump。原因是处理链中的延迟无处不在,重要的是要知道哪个导致了延迟,以便知道采取何种行动。实际上,本地 tcpdump 将指示输入数据何时到来。Strace 将指示 haproxy 何时接收到这些数据(使用 recv/recvfrom)。警告,openssl 使用 read()/write() 系统调用而不是 recv()/send()。Strace 还将显示 haproxy 何时发送数据,tcpdump 将显示系统何时将这些数据发送到接口。然后,外部 tcpdump 将显示发送的数据何时真正被接收(因为本地 tcpdump 只显示数据包何时排队)。在本地系统上嗅探的好处是 strace 和 tcpdump 将使用相同的参考时钟。Strace 应与“-tts200”一起使用,以获取完整的时戳并报告足够大的数据块以供读取。Tcpdump 应与“-nvvttSs0”一起使用,以报告完整的数据包、实际序列号和完整时戳。实际上,接收到的数据几乎总是立即被 haproxy 接收(除非机器的 CPU 饱和或这些数据无效且未被传递)。如果数据已接收但未发送,通常是因为输出缓冲区已满(即:接收方消耗数据的速度不够快)。这可以通过观察轮询未通知输出文件描述符何时可写来确认(在 strace 输出中,数据最终离开,然后回滚查看何时通知了写事件,这通常更容易发现)。它通常与接收方收到的 ACK 匹配,并由 tcpdump 检测到。一旦数据发送出去,它们可能会在系统中花费一些时间而不做任何事情。这里,TCP 拥塞窗口可能受到限制,不允许这些数据离开,等待 ACK 来打开窗口。如果流量空闲,并且数据花费 40 毫秒或 200 毫秒才离开,这是一个不同的问题(不是问题),这是 Nagle 算法阻止空数据包立即离开,希望它们能与后续数据合并。HAProxy 在纯 TCP 模式和隧道中自动禁用 Nagle。但是,在转发 HTTP 正文时,它肯定会启用(这通过减少数据包数量来提高性能)。一些不符合 HTTP 标准的应用程序可能对发送不完整 HTTP 响应消息时的延迟敏感。在这种情况下,您将需要启用“option http-no-delay”来禁用 Nagle,以解决它们的局限性,并记住链中的任何其他代理也可能受到类似影响。如果 tcpdump 报告数据立即离开但另一端没有很快看到它们,这可能意味着 WAN 链路拥塞,启用了流控制的 LAN 拥塞导致数据无法离开,或者更常见的是 HAProxy 实际上运行在虚拟机中,并且由于某种原因,虚拟机管理程序决定数据不需要立即发送。在虚拟化环境中,延迟问题几乎总是由虚拟化层引起,因此为了节省时间,首先比较 VM 内外的 tcpdump 是值得的。任何差异都应归因于虚拟机管理程序及其配套驱动程序。当在 tcpdump 跟踪中看到一些 TCP SACK 段时(使用 -vv),这始终意味着发送它们的一方已经收到了丢失数据包的证明。虽然看不到它们并不意味着没有丢失,但看到它们绝对意味着网络是易失的。网络上的丢失是正常的,但 SACK 难以察觉的速率是正常的。如果它们在跟踪中大量出现,就值得调查究竟发生了什么以及数据包在哪里丢失。HTTP 在处理 TCP 丢失方面表现不佳,这会引入巨大的延迟。“netstat -i”命令将报告每个接口的统计信息。Rx-Ovr 计数器增长的接口表示系统没有足够的资源来接收所有传入的数据包,并且它们在被网络驱动程序处理之前就丢失了。Rx-Drp 表示由于应用程序处理速度不够快,一些接收到的数据包在网络堆栈中丢失了。这在某些攻击中也可能发生。Tx-Drp 意味着输出队列已满,必须丢弃数据包。在使用 TCP 时,这种情况应该非常罕见,但可能表示出站链路饱和。
HAProxy 被设计成以非常有限的权限运行。标准的用法是将其隔离到一个 chroot 监狱中,并将其权限降级为一个在该监狱中没有任何权限的非 root 用户,这样,如果未来发现任何漏洞,其被攻破也不会影响系统的其余部分。为了执行 chroot,它首先需要以 root 用户身份启动。在手动创建的 chroot 中启动进程是徒劳的,这些 chroot 很难构建,从未得到妥善维护,并且总是包含比主文件系统多得多的错误。而且,在被攻破的情况下,入侵者可以利用这些特意构建的文件系统。不幸的是,许多管理员将“以 root 身份启动”和“以 root 身份运行”混淆,导致在启动 haproxy 之前就更改了 uid,从而降低了有效的安全限制。HAProxy 需要以 root 身份启动才能:- 调整文件描述符限制- 绑定到特权端口号- 绑定到特定的网络接口- 透明地监听外部地址- 在 chroot 监狱中隔离自身- 降级到另一个非特权 UID。HAProxy 可能需要以 root 身份运行才能:- 绑定接口以进行出站连接- 绑定特权源端口以进行出站连接- 透明地绑定外部地址以进行出站连接。大多数用户永远不需要“以 root 身份运行”的情况。但是,“以 root 身份启动”涵盖了大多数用法。安全的配置将包含:- 一个 chroot 声明,指向一个没有任何访问权限的空位置。这可以在 UNIX 命令行上这样准备:# mkdir /var/empty && chmod 0 /var/empty || echo "Failed" 并在 HAProxy 配置的全局部分中这样引用:chroot /var/empty- 在全局部分中同时包含 uid/user 和 gid/group 声明:user haproxy group haproxy- 一个 stats socket,其模式、uid 和 gid 设置为匹配允许访问 CLI 的用户和/或组,以便没有人可以访问它:stats socket /var/run/haproxy.stat uid hatop gid hatop mode 600