外网测试机的异常传不回内网?自托管 Sentry,用 cpolar 打通错误上报链路

外网测试机已经报错,内网 Sentry 却一条记录都没有。别急着改 SDK:如果 DSN 指向内网地址,异常连采集入口都到不了。
这篇搭一条专门用于合成异常的上报链路:Sentry 留在内网,Nginx 只放行一个项目的事件接收路径,cpolar 提供外部 HTTPS 入口。验收看后台的 event_id、Issue 和 release,不把"终端打印了 ID"当成成功。
1 先分清:公开的是采集入口,不是管理后台
Sentry 负责接收异常、保存堆栈、聚合 Issue;cpolar 只负责连接外网测试机与内网服务,不参与错误分析。
本篇使用 Python SDK,事件发送到 /api/项目编号/envelope/。链路是:外网脚本 → cpolar HTTPS 地址 → 本机 Nginx 的 9080 端口 → Sentry 的 9000 端口。
这里别直接映射 9000。那是 Sentry 的完整入口,包含后台页面。单独增加采集代理,是为了把登录页和管理 API 挡在公网入口之外。
这套做法适合异地联调和小规模错误上报验证,不是高可用部署方案。如果团队只在局域网测试,先完成本地事件验收即可,不必为了凑齐步骤开放公网。上报入口与后台分开设计,也方便测试结束时只撤销隧道,继续在内网分析已有事件。

图 1:采集与管理分离架构示意:外网只到采集代理,管理员通过内网 SSH 转发访问后台。
图中应有两条独立路线:外网脚本只到采集代理,管理员从内网访问后台。本文不配置浏览器 SDK,因此也不涉及浏览器跨域预检。
2 环境准备:先核对资源,再安装
准备一台专用 Ubuntu 服务器,以及一台不在同一局域网的 Python 测试机。服务器需安装 Git、curl、Docker Engine 和 Docker Compose 插件;外网测试机需有 Python 3 与 venv。
Sentry 官方当前最低要求是 4 核 CPU、16 GB 内存加 16 GB swap、20 GB 可用磁盘;推荐 32 GB 内存。Docker 最低为 19.03.6,Compose 最低为 2.32.2。20 GB 是安装门槛,不是长期事件存储预算,别拿低配设备直接套用。
在服务器检查环境:
bash
nproc
free -h
df -h .
docker version
docker compose version
Docker 安装按官方 Ubuntu 指南执行。下面假定当前用户已经能运行 Docker 命令,服务器能够下载仓库和镜像。
3 部署 Sentry:后台只监听本机
使用官方最新发布版本,不直接运行开发分支。以下命令在新的工作目录执行:
bash
VERSION=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
https://github.com/getsentry/self-hosted/releases/latest)
VERSION=${VERSION##*/}
git clone https://github.com/getsentry/self-hosted.git
cd self-hosted
git checkout "$VERSION"
git describe --tags --always
启动前打开仓库的 .env,找到已有的 SENTRY_BIND,将其值改为 127.0.0.1:9000。不要重复追加同名变量,也不要覆盖文件里的其他配置。
bash
./install.sh
docker compose up --wait
docker compose ps
curl -fsS http://127.0.0.1:9000/_health/
按安装提示创建管理员,并明确选择是否发送安装监控信息。健康检查应返回 HTTP 200;若没有,先检查 docker compose ps 和 docker compose logs --tail=100,不要急着开隧道。
后台绑定回环地址后,管理员可从内网电脑使用 SSH 本地转发。下面在管理员电脑的 Bash 终端执行,输入实际 SSH 登录目标:
bash
read -r -p '服务器 SSH 登录目标(用户名@内网IP): ' SERVER
ssh -N -L 9000:127.0.0.1:9000 "$SERVER"
保持窗口打开,在该电脑访问 http://127.0.0.1:9000。这个访问方式只供管理,不拿来给外网 SDK 上报。
4 创建 Python 项目:取出项目 DSN
登录后创建 Python 项目,命名为 external-error-demo。从项目配置示例或项目设置的 Client Keys (DSN) 复制 DSN。
DSN 包含协议、公钥、主机地址和项目编号。它不是管理员令牌,也不提供读取后台数据的权限;但持有者能提交事件,所以它同样需要防滥用。

图 2:DSN 与数字项目编号对应关系(教学示意,非实测截图,公钥已遮挡)。
截图需要突出末尾的数字项目编号,并遮挡完整密钥。下一步代理放行的编号必须与这里相同,不要把项目名称填进 URL。
5 配置 Nginx:只接收这个项目的 Envelope
在 Sentry 服务器安装独立 Nginx。下面使用 Ubuntu 默认加载的 /etc/nginx/conf.d/,先查看现有配置,确认 9080 未被占用:
bash
sudo apt-get update
sudo apt-get install -y nginx
sudo nginx -T
sudo ss -lntp | grep ':9080' || true
新建 /etc/nginx/conf.d/sentry-ingest.conf,内容如下。示例中的项目编号为 1,保存前务必替换为自己的实际数字编号;已有同名文件时先备份并合并。
nginx
limit_req_zone $server_name zone=sentry_ingest:10m rate=2r/s;
server {
listen 127.0.0.1:9080;
server_name _;
client_max_body_size 1m;
limit_req_status 429;
location = /api/1/envelope/ {
if ($request_method != POST) { return 405; }
limit_req zone=sentry_ingest burst=5 nodelay;
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host 127.0.0.1:9000;
}
location / {
return 404;
}
}
这是合成异常测试用的窄入口:只接收 POST,限制请求体为 1 MB,入口共享平均每秒 2 个请求的速率限制,突发额度为 5。它不是按外网客户端 IP 分别限流,也不是每日事件配额。
proxy_pass 后面不添加 URI,目的是保留原始接收路径和查询参数。不要加一个放行所有 /api/ 的规则,否则管理 API 也会进入转发范围。
bash
sudo nginx -t && sudo systemctl reload nginx
curl -i http://127.0.0.1:9080/
curl -i http://127.0.0.1:9080/auth/login/
这两次请求都应返回 404,说明首页与登录页没有被代理。注意:限制路径不等于检查 Envelope 内的数据类型,本例不宣称建立了完整的生产采集安全网关。
6 安装 cpolar:将 9080 接到 HTTPS 入口
现在再开隧道,排错范围就只剩外部链路。按 cpolar 官方 Linux 文档安装:
bash
curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash
cpolar version
安装脚本以管理员权限运行,执行前应审阅脚本。安装后检查默认示例隧道,停用与本次测试无关的 web、ssh 隧道,不要把样例留作公网入口。
本次采用前台临时测试:若后台服务已运行,先确认没有其他业务隧道依赖它,再停止服务,避免多实例冲突。
bash
sudo systemctl stop cpolar
read -r -s -p '粘贴 cpolar Authtoken: ' CPOLAR_TOKEN
printf '\n'
cpolar authtoken "$CPOLAR_TOKEN"
unset CPOLAR_TOKEN
cpolar http 9080
账号与 Authtoken 从 cpolar 后台获取。保留前台窗口,复制输出中的真实 HTTPS 地址;这里不要映射 9000,也不要映射 cpolar 管理端口 9200。
在外网测试机访问该 HTTPS 地址的 / 和 /auth/login/,仍应得到 404。这证明入口可达且后台没有开放,不是页面部署失败。
7 在外网发送异常:保留公钥,只换采集主机
在外网测试机创建干净的虚拟环境,避免把现有业务日志带进实验:
bash
python3 -m venv sentry-demo-env
source sentry-demo-env/bin/activate
python -m pip install sentry-sdk
python -m pip freeze > requirements-demo.txt
read -r -p '粘贴项目原始 DSN: ' INTERNAL_DSN
read -r -p '粘贴 cpolar HTTPS 根地址: ' INGEST_ORIGIN
export INTERNAL_DSN INGEST_ORIGIN
保存下面脚本为 send_error.py。脚本从真实 DSN 取公钥和项目编号,只将协议、主机换成外部采集地址,不需要手拼密钥。
python
import os
from urllib.parse import urlsplit
import sentry_sdk
source = urlsplit(os.environ["INTERNAL_DSN"])
origin = urlsplit(os.environ["INGEST_ORIGIN"])
project_id = source.path.strip("/")
assert source.username and project_id.isdigit()
assert origin.scheme == "https" and origin.hostname
assert not origin.username and origin.path in ("", "/")
dsn = f"https://{source.username}@{origin.netloc}/{project_id}"
def clean_event(event, hint):
for key in ("user", "request", "breadcrumbs", "extra", "server_name"):
event.pop(key, None)
return event
sentry_sdk.init(
dsn=dsn,
environment="external-test",
release="external-error-demo@1.0.0",
send_default_pii=False,
include_local_variables=False,
default_integrations=False,
before_send=clean_event,
)
try:
raise RuntimeError("synthetic-external-ingest-check")
except RuntimeError as error:
event_id = sentry_sdk.capture_exception(error)
sentry_sdk.flush(timeout=10)
print("event_id:", event_id)
bash
python send_error.py
这里主动关闭默认集成和局部变量采集,再删除用户、请求等字段。异常消息只用合成文字;不要把真实令牌、用户信息或生产堆栈复制进脚本。异常堆栈仍包含测试文件信息,应在专用测试目录运行。
建议第一次只执行一次,不要套循环压测。先记录运行时间与输出的事件标识,再去后台对照;这样即使项目里已有其他测试事件,也不会把旧记录误认成本次结果。依赖清单已经保存,后续复现时沿用同一份版本记录。
注意三个地址的分工:后台地址供管理员访问,cpolar 地址供事件上传,SDK DSN 则在外部地址上携带原项目公钥和编号。不要把 Sentry 的 system.url-prefix 改成这个只支持上传的入口,否则后台生成的链接会指向错误位置。
8 回到内网验收:对上 ID,才算送达
打开项目 Issue,找到 synthetic-external-ingest-check,展开具体事件,核对终端输出的 event_id、external-test 环境和 external-error-demo@1.0.0 版本。

图 3:终端与后台事件 ID、环境及 release 的验收对照(教学示意,非实测截图)。
图中应展示同一条事件,而不只是 Issue 列表。SDK 返回 ID 只代表事件获得标识;后台能找到同一 ID,才是完整链路的验收证据。
再运行一次脚本,核对两条不同 event_id 是否归入相同 Issue。若要验证修复,将抛异常改为正常分支并升级 release,同时另发一条合成探针确认链路仍通;单看"没有新错误"不能证明修复有效。
遇到问题按层检查:404 看项目编号和末尾 /envelope/;405 看请求方法;413 看请求体限制;429 看入口限流及 Sentry 返回的限流信息;502 先在服务器检查 9000 健康状态。请求已接收但后台无事件,再查 Sentry 容器日志、项目与时间筛选,别反复更换隧道。
9 总结:让错误进来,把后台留在里面
按上述流程,外网测试脚本有了独立 HTTPS 采集入口,Sentry 后台仍只在内网管理链路上访问。验收对象是具体事件,不是一个能打开的首页。
- Sentry 先完成健康检查与项目初始化,再开放采集链路。
- Nginx 限定项目路径、方法、请求体与速率,cpolar 只映射 9080。
- 用合成异常核对 event_id、Issue 和 release,不上传真实业务隐私。
临时测试结束后按 Ctrl+C 关闭前台隧道,并撤销不再使用的项目客户端密钥。长期接入还需安排稳定域名、容量与配额管理、备份和升级;地址发生变化时同步更新 SDK DSN。先把这一条小链路验清楚,再接入真实应用,排错会轻松得多。
参考:Sentry 自托管要求与安装 · DSN 说明 · Envelope 接收协议 · Python SDK 配置 · Nginx 限流