如何在 Linux 上使用 Privoxy 和 HAProxy 构建共享的 ProxyScrape 代理网关
将一个 ProxyScrape SOCKS5 端点转变为一个可供整个团队使用的 HTTP 代理地址,支持跨多个上游的轮询轮换、健康检查和私有网络访问控制。
概述
每台机器单独配置代理的方式无法扩展。当多个人、几个 CI 运行器和一些容器都需要同一个代理池时,凭据最终会被复制到每个启动器和脚本中,并且没有人能分辨出哪些流量使用了哪个上游。
本教程构建的是相反的方式:一台 Linux 主机,它只保存一次你的 ProxyScrape 凭据,并向你的私有网络暴露一个单一的 HTTP 代理地址。
客户端应用 -> HAProxy (TCP :3128) -> Privoxy 实例 (127.0.0.1:8118-8120) -> ProxyScrape SOCKS5 -> 互联网
本指南使用 ProxyScrape SOCKS5 上游,因为本地前端必须向上游传递用户名和密码,而 Privoxy 支持 SOCKS5 上游的凭据,但不支持 HTTP 上游。如果你特别需要通过经过身份验证的 HTTP 上游进行链式连接,请改用支持 HTTP 父级认证的链式工具。
| 层级 | 软件 | 作用 |
|---|---|---|
| 客户端边缘 | HAProxy (TCP 模式) | 为客户端提供一个主机和端口,将新连接分散到本地代理实例,移除停止响应的实例 |
| 本地代理 | Privoxy | 接受 HTTP 代理请求,并将每个请求转发到单个 ProxyScrape SOCKS5 端点 |
| 上游 | ProxyScrape SOCKS5 | 使用代理池中的代理 IP 解析并获取目标 URL |
这种设计的一个实际后果:轮换是基于每个新连接发生的,而不是基于每个请求。重用 keep-alive 连接的客户端会一直停留在同一个上游实例上,直到重新连接。
先决条件
- 一台 Linux 主机(Debian/Ubuntu 或 RHEL/Fedora 系列),在私有网络、VPN 或 tailnet 上具有稳定地址。不要将网关端口暴露到公共互联网。
- 来自仪表板的 ProxyScrape SOCKS5 端点详细信息:主机、端口、用户名和密码。
- sudo 访问权限,以及在私有网络上打开一个端口(下面示例中的 3128)的权限。
- 基本熟悉 systemd 单元和编辑配置文件。
步骤
步骤 1:在构建任何东西之前验证 ProxyScrape SOCKS5 端点
将你的凭据放入 shell 变量中,以便后续测试保持一致:
export PS_HOST="socks5-host-from-your-dashboard"
export PS_PORT="1080"
export PS_USER="your-username"
export PS_PASS="your-password"
直接通过端点发起一个请求:
curl --silent --show-error \
--proxy "socks5h://${PS_USER}:${PS_PASS}@${PS_HOST}:${PS_PORT}" \
https://api.ipify.org
printf '\n'
socks5h:// 方案使 curl 通过代理发送 DNS 查询,这正是网关所需要的。运行该命令两到三次,并记下返回的地址。如果第一个命令失败,请在继续之前修复凭据或主机和端口,因为此后的每一层都依赖于它。
步骤 2:安装 Privoxy 和 HAProxy
Debian 和 Ubuntu:
sudo apt update
sudo apt install --yes privoxy haproxy
RHEL、Fedora 和兼容系统:
sudo dnf install --yes privoxy haproxy
确认二进制文件存在并记录其路径,因为下面的 systemd 单元直接引用了 Privoxy 二进制文件:
command -v privoxy
command -v haproxy
步骤 3:为每个上游创建一个 Privoxy 实例
Privoxy 读取单个平面配置文件,这使得运行多个独立实例(每个上游端点一个)变得容易。首先为它们创建一个目录:
sudo mkdir -p /etc/privoxy/instances
创建 /etc/privoxy/instances/instance-1.conf:
confdir /etc/privoxy
logdir /var/log/privoxy
logfile instance-1.log
toggle 0
listen-address 127.0.0.1:8118
forward-socks5t / USERNAME:PASSWORD@HOST:PORT .
将 USERNAME、PASSWORD、HOST 和 PORT 替换为你的 ProxyScrape SOCKS5 值。
toggle 0禁用 Privoxy 内容过滤。网关应原样转发流量,而不是重写或阻止它。listen-address 127.0.0.1:8118使实例仅对主机私有。HAProxy 将是唯一暴露的表面。forward-socks5t将请求发送到 SOCKS5 上游,并远程解析 DNS,因此主机名不会从你自己的网络解析。- 前导
/匹配每个 URL,尾随.是 Privoxy 文档中用于 Tor 和类似设置的 SOCKS 转发规则形式的一部分。
锁定文件,因为它包含凭据:
sudo chown root:privoxy /etc/privoxy/instances/instance-1.conf
sudo chmod 640 /etc/privoxy/instances/instance-1.conf
如果发行版软件包已经在端口 8118 上启动了 Privoxy 服务,请先停止它,以便释放端口:
sudo systemctl disable --now privoxy
在前台测试实例:
sudo privoxy --no-daemon /etc/privoxy/instances/instance-1.conf
在第二个终端中,通过它发送请求:
curl --silent --proxy http://127.0.0.1:8118 https://api.ipify.org
printf '\n'
如果返回一个地址,最难的部分就完成了。用 Ctrl+C 停止前台进程,并为你想要使用的每个额外的 ProxyScrape 端点重复配置文件,只更改日志文件名和监听端口。
步骤 4:将实例作为 systemd 模板服务运行
创建 /etc/systemd/system/[email protected]:
[Unit]
Description=Privoxy proxy gateway instance %i
Documentation=man:privoxy(8)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=privoxy
Group=privoxy
ExecStart=/usr/sbin/privoxy --no-daemon /etc/privoxy/instances/instance-%i.conf
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
如果 command -v privoxy 打印了不同的路径,请调整 ExecStart 以匹配。然后启用实例:
sudo systemctl daemon-reload
sudo systemctl enable --now privoxy-gateway@1 privoxy-gateway@2 privoxy-gateway@3
systemctl list-units 'privoxy-gateway@*' --no-pager
确认每个实例都在自己的回环端口上监听:
for port in 8118 8119 8120; do
printf '%s -> ' "$port"
curl --silent --proxy "http://127.0.0.1:${port}" https://api.ipify.org
printf '\n'
done
每一行都应返回一个地址,而且它们不一定是相同的地址,因为每个实例都与自己的上游通信。
步骤 5:将 HAProxy 置于前端作为 TCP 负载均衡器
前端不需要理解 HTTP。它只需要中继 TCP 连接,这使得 HAProxy 的 TCP 模式非常适合位于 SOCKS5 上游前面的 HTTP 代理。
备份打包的配置,并将 /etc/haproxy/haproxy.cfg 替换为以下内容。将 10.8.0.1 更改为此主机的私有地址。
global
log /dev/log local0
maxconn 4000
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
defaults
mode tcp
log global
option tcplog
retries 2
timeout connect 10s
timeout client 5m
timeout server 5m
frontend proxy_gateway
bind 10.8.0.1:3128
default_backend privoxy_instances
frontend proxy_gateway_sticky
bind 10.8.0.1:3129
default_backend privoxy_instance_1
backend privoxy_instances
balance roundrobin
server privoxy1 127.0.0.1:8118 check
server privoxy2 127.0.0.1:8119 check
server privoxy3 127.0.0.1:8120 check
backend privoxy_instance_1
server privoxy1 127.0.0.1:8118 check
listen stats
bind 127.0.0.1:8404
mode http
stats enable
stats uri /stats
stats refresh 10s
关于这里的选择的几点说明:
- 端口
3128是轮换入口点。每个新的 TCP 连接都被发送到下一个健康的 Privoxy 实例。 - 端口
3129固定到单个实例,适用于你希望每次运行期间每个请求都通过同一上游离开的任务。 - 普通的
check关键字执行 TCP 连接检查,因此停止接受连接的实例会自动从轮换中移除。 - 统计页面仅绑定到回环。通过 SSH 隧道或内部跳板机访问它,而不是将端口 8404 开放给网络。
验证并重新加载:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy
systemctl --no-pager --lines=20 status haproxy
步骤 6:限制对私有网络的访问
将 HAProxy 绑定到私有、VPN 或 tailnet 地址意味着网关只能从该网络内部访问。如果主机还有一个公共接口,请添加防火墙规则,以便该接口上的任何内容都无法访问代理端口。
以下 nftables 示例是可选的,仅用于说明。从第二个终端会话应用它,以免错误将你锁在机器外,并根据你自己的网络调整源范围:
sudo nft add table inet proxygw
sudo nft add chain inet proxygw input '{ type filter hook input priority 0; }'
sudo nft add rule inet proxygw input tcp dport { 3128, 3129 } ip saddr != 10.8.0.0/24 drop
此堆栈不验证客户端;访问控制来自网关绑定到的网络。如果你需要个人凭据,请在主机前的 VPN 或 tailnet 上终止用户访问。
步骤 7:端到端验证轮换
向轮换端口发送一系列单独的请求:
for i in $(seq 1 6); do
curl --silent --proxy http://10.8.0.1:3128 https://api.ipify.org
printf '\n'
done
每次 curl 调用都会打开一个新连接,因此你应该看到地址的分布,而不是单个重复的地址。然后打开统计页面(如果需要,通过隧道),并确认每个后端都标记为 UP:
ssh -L 8404:127.0.0.1:8404 user@gateway-host
# in the browser on your workstation: http://127.0.0.1:8404/stats
最后,将真实客户端指向网关,例如使用代理进行命令行工具的 shell 会话:
export http_proxy="http://10.8.0.1:3128"
export https_proxy="http://10.8.0.1:3128"
curl --silent https://api.ipify.org
printf '\n'
unset http_proxy https_proxy
轮换与粘性行为
选择与任务匹配的前端端口。两个选项都已在上方配置。
- 轮换(
3128):新连接分布到所有健康的实例。对于浏览器、一般浏览以及频繁打开新连接的任务来说,是一个好的默认选择。 - 粘性(
3129):所有流量都转到一个实例。适用于必须在会话期间呈现一致的源地址的工作流。 - 按组隔离:创建一个包含实例子集的第二个后端,并将新的前端端口指向它,这样一个团队永远不会消耗整个池。
故障排除
| 症状 | 可能原因 | 修复 |
|---|---|---|
| Privoxy 返回 403 响应 | 内容过滤已启用,并且过滤规则匹配了请求 | 确认实例配置中存在 toggle 0,并重启该实例 |
| Privoxy 返回 502 或上游连接错误 | SOCKS5 主机、端口或凭据错误 | 重新运行步骤 1 中的直接 curl --proxy "socks5h://..." 测试 |
所有后端在统计页面中显示为 DOWN |
Privoxy 实例未运行或未在 HAProxy 期望的位置监听 | 运行 systemctl list-units 'privoxy-gateway@*' 和 `ss -ltnp |
| 每个请求都从同一地址退出 | 客户端正在重用 keep-alive 连接,或者只有一个实例在运行 | 强制每个请求使用新连接,并确认所有后端报告 UP |
| 主机名从你自己的网络解析 | 实例使用 forward-socks5 而不是 forward-socks5t |
切换到 forward-socks5t,以便通过代理解析 DNS |
HAProxy 启动失败,出现 /dev/log 错误 |
主机上没有 syslog 套接字 | 删除 log /dev/log local0 行,或安装并启用 syslog 服务 |
| 大文件下载在传输中途超时 | 默认 HAProxy 超时对于工作负载来说太低 | 在 defaults 部分增加 timeout client 和 timeout server |
总结
你现在拥有一个单一的 HTTP 代理地址,它前端有多个 ProxyScrape SOCKS5 端点,HAProxy 侧具有自动健康检查,并且端口 3128 上具有按连接轮换。凭据存放在一组 root 拥有的配置文件中,而不是复制到每台机器上的每个工具中。
自然的下一步:每当你想增加上游容量时,添加一个实例和一行 server,将 Privoxy 配置文件排除在版本控制之外,并通过编辑实例配置并一次一个地重启模板单元来轮换凭据,以便网关永远不会完全离线。