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

外网测试机的异常传不回内网?自托管 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 限流

相关推荐
小小龙学IT1 小时前
Python SQLAlchemy 2.0 深度解析:从 Core 到 ORM 的数据访问双引擎
开发语言·数据库·python
2601_962885721 小时前
如何用 Python 计算 ROC 变动率指标做动量分析?
开发语言·python·算法
在世修行1 小时前
干货:文件上传端点解析
python·fastapi·接收上传
cpolar技术支持2 小时前
Spring Boot 接口本地正常,异地前端却报跨域?用 cpolar 跑通 CORS 预检与白名单
java·springboot·cpolar·前后端分离·cors
ZhangJun952 小时前
Mobike 共享单车分析项目
人工智能·python·算法·kmeans·聚类·knn
XLYcmy2 小时前
PDF 论文处理器 — 优势与功能文档
大数据·python·网络安全·pdf·dify·rag·漏洞检测
小猴子爱上树2 小时前
跨马翻译:批量图片翻译+视频字幕+智能抠图,跨境电商在线图片翻译工具
大数据·人工智能·python·音视频
winfredzhang3 小时前
用 Python 手写一个“所见即所得“的图片水印工具:wxPython + Pillow 实战详解
python·pillow·rotate·crop·身份改水印
xUxIAOrUIII3 小时前
日常笔记-1005-1
linux·笔记·python·macos