Skip to content

Configuration

Hades edited this page Aug 23, 2026 · 7 revisions

配置体系

两个文件,分工明确

文件 放什么 进版本库?
ipclick.toml 行为配置:超时、重试、限流、引擎、集群拓扑 ✅ 应该进
.env 机密:令牌、密码、集群密钥、代理凭据 ❌ 绝不能进

规则一句话:.env 只放机密,ipclick.toml 只放行为。

.env 里认的键(ipclick -e env 会输出这份模板):

IPCLICK_AUTH_TOKEN            gRPC 鉴权令牌
IPCLICK_WEB_USER              Web 管理端用户名
IPCLICK_WEB_PASSWORD          Web 管理端密码
IPCLICK_PROXY_AUTH_KEY        代理账号
IPCLICK_PROXY_AUTH_PASSWORD   代理密码
IPCLICK_CLUSTER_SECRET        集群共享密钥(所有节点一致;每台的令牌由它派生)

机密写进 ipclick.toml 仍然生效——受信环境里图省事是合理的。只是启动时会点名提醒 (ipclick.toml 通常要进版本库,机密会跟着进 git、备份、CI 日志)。确实想这么放就设 [SECURITY].allow_secrets_in_config = true 关掉提醒。两边都写时环境变量优先

ipclick config-info 会逐项显示每个机密来自哪里

  机密来源:
    gRPC 鉴权令牌      环境变量 / .env
    Web 管理端密码     配置文件 ⚠️ 建议改用环境变量
    集群共享密钥       未配置

优先级(高 → 低)

  1. 命令行参数 / 构造函数参数 —— ipclick run -p 9528Downloader(port=9528)
  2. 真实环境变量
  3. 当前工作目录的 .env —— 只填补尚未设置的变量,不覆盖已有的
  4. 配置文件 —— -c 指定的,或当前目录的 ipclick-<端口>.toml / .ipclick-<端口>.toml / ipclick.toml / .ipclick.toml (带端口的那两个只在 --port 给了值时参与查找)
  5. ~/.ipclick/config.toml
  6. 包内默认配置

.env 排在真实环境变量之后是有意的:容器编排、CI、systemd 注入的变量必须能压过 仓库里那个用于本地开发的 .env

配置文件的查找不向上递归——只看当前工作目录。这样"我在哪个目录起的服务"和 "用了哪份配置"是一一对应的,不会莫名其妙捡到上层目录的配置。

环境变量覆盖表

除了机密,这几个部署参数也能用环境变量给(给容器编排注入用):

环境变量 覆盖
IPCLICK_HOST / IPCLICK_PORT [SERVER].host / port
IPCLICK_MAX_WORKERS [SERVER].max_workers
IPCLICK_MODE [GENERAL].mode
IPCLICK_LOG_LEVEL [LOG].level
IPCLICK_CLUSTER_SELF_ID [CLUSTER].self_id —— 多台机器共用一份配置时靠它区分身份

这张表集中定义在 config_loader/loader.pyENV_OVERRIDES 里。散着写 os.getenv 的话,"到底哪些环境变量有用"只能靠翻代码,文档也必然和实现失步。

配置节一览

管什么 详见
[GENERAL] 运行模式(单机 / 集群 / auto)、调试开关 本页
[SERVER] 监听地址、端口、worker 线程数 本页
[CLIENT] 客户端到服务端这一跳的重试、请求压缩 性能
[PROXY] 出站代理 本页
[CLUSTER] 集群形态、节点列表、负载均衡、探活 集群
[SECURITY] 鉴权令牌、TLS/mTLS、SSRF 防护 安全
[DOWNLOADER] 超时、重试、连接池、按 host 限流 性能
[BROWSER] 浏览器引擎与渲染行为 浏览器渲染
[TRACE] 链路记录与落盘 链路记录
[WEB] Web 管理端 Web 管理端
[MONITOR] 健康检查开关 本页
[LOG] 日志级别、输出、轮转 本页

每一项的完整注释就在 ipclick.toml 里(ipclick -e toml 可以随时看)—— 那份模板是配置项的唯一权威来源,每一项都写了默认值的理由和配错的症状。

[GENERAL]

[GENERAL]
mode = "standalone"   # standalone / cluster / auto
debug = false         # 强制 DEBUG 级别日志,覆盖 [LOG].level

mode 决定 create_client() 返回单机还是集群客户端:

  • standalone —— 单节点 Downloader
  • cluster —— ClusterDownloader没配任何节点时直接报错,不会静默退回单机。 静默退回会让你以为集群生效了,实际所有流量都打在一个节点上、也没有故障转移。
  • auto —— 配了节点就走集群,没配就单机

[SERVER]

[SERVER]
host = "[::]"              # 监听地址。"[::]" = 所有网卡(IPv4 + IPv6)
port = 9528
max_workers = 100          # gRPC 线程池大小
processes = 1              # 工作进程数。0 = 按核数自动(上限 8);仅 Unix
max_concurrent_rpcs = 0    # 准入上限(能排多长的队)。0 = max_workers × 8
max_concurrent_streams = 0 # 单条 HTTP/2 连接上的并发流上限。0 = 跟随 max_concurrent_rpcs,
                            # 且不低于 100(IPClick 自己算出来的,不是 gRPC 的内部默认值)
async_mode = false         # 换成 grpc.aio 协程服务端(实验性)
compression = "gzip"       # 响应压缩:gzip(默认)/ deflate;不压缩则写 none(off/no/identity 同义)

processes —— 想用满多核就得调它

服务端默认单进程,GIL 才是吞吐天花板:实测 16 核机器上单进程只能用出 1.45 个核, 把 max_workers 从 32 调到 256 对吞吐没有任何影响(279.8 / 277.9 QPS,差异在噪声内)。 多进程靠 SO_REUSEPORT 共享同一个端口,分发由内核做,对调用方完全透明(仍然只有一个 地址一个端口)。实测 4 进程 313 → 663 QPS。

processes / max_concurrent_rpcs / max_concurrent_streams 写成非整数或负数会 直接报 ConfigError 拒绝启动(2.0.0 之后改的)。此前它们静默回落默认值: processes = "auto" 悄悄变成 1,四进程的吞吐就这么没了,而 config-info 并不打印 processes,没有任何察觉的途径。升级后若启动报这个错,说明你的配置一直没生效过。

三个前提要知道:仅 Unix(Windows 上会打告警并降级成单进程)、 Web 管理端只在 0 号进程起(否则几个进程抢同一个 Web 端口)、 内存按进程数线性增长(每进程各有一份适配器与连接池,浏览器渲染时尤其要算)。

max_concurrent_rpcs —— 并发一上去就大面积失败时看这里

并发打上去之后大量请求失败、而服务端 CPU 却很空闲,通常不是线程不够而是准入满了。 这一项决定"队能排多长",max_workers 决定"同时能干多少活",是两件事。 客户端收到 UNAVAILABLE 且带 RST_STREAM(REFUSED_STREAM) 时,要调的就是它。

max_workers 有三重含义,调之前要知道:

  1. 同时能处理多少个请求 —— 服务端是一请求一线程做阻塞 IO
  2. SendBatch 的并发度上限。批量自己的线程池(线程名 ipclick-batch), 但容量同样取 max_workers,所以总并发不会变成 max_workers × 批量数 把下游打爆
  3. gRPC 的 maximum_concurrent_rpcs 默认取它的 8 倍(即 max_concurrent_rpcs = 0 时),超出的请求排队而不是无限堆积;也可以显式写一个值

第 3 条在 0.5.0 之前是 ×2,实测 500 并发下成功率掉到 68.7%(症状是客户端收到 RST_STREAM(REFUSED_STREAM))才改成 ×8。看到旧文档写"两倍"的话,那是被修掉的版本。

按 host 限流的等待也占着线程,所以 per_host_max_concurrent 设得很小时, max_workers 要留够余量。见性能

端口撞了会直接起不来——但机制分两种。单进程下 IPClick 显式关掉了 SO_REUSEPORT (gRPC 默认开着它,后果是两个进程都在监听、请求被内核随机分给其中一个,症状是 "改了配置只有一半生效",极难定位)。processes > 1 时必须开着它才能共享端口, 这时改由 fork 之前的一次独占试绑来保证端口冲突照样起不来。

[PROXY]

[PROXY]
scheme = 'http'
host = ''
port = 0
# 账号密码走 .env:IPCLICK_PROXY_AUTH_KEY / IPCLICK_PROXY_AUTH_PASSWORD

# 三方隧道代理
channel_name = ''
session_ttl = ''
country_code = ''
tunnel_server = ''

默认留空表示未配置代理,此时 proxy=True 会打一条警告并直连。

这里刻意不预置 127.0.0.1:7890 之类的本地代理地址——否则所有用户的 proxy=True 都会指向他们自己机器上的某个端口(可能是别的服务)。

请求级用法:

d.get(url, proxy=True)                        # 用 [PROXY] 配置
d.get(url, proxy="http://user:pw@host:8080")  # 直接给串
d.get(url, proxy=False)                       # 强制直连

[MONITOR][LOG]

[MONITOR]
health_check = true   # 注册 grpc.health.v1(免鉴权,供 K8s 探针用)

[LOG]
level = "info"        # trace / debug / info / success / warning / error / critical
format = ""           # 留空用内置格式
output = "stdout"     # 三种写法见下

[LOG.rotation]
max_size = 100        # 单文件最大 MB(output 为文件时生效)
max_backups = 5

[LOG].output 的三种写法

output = "stdout"           # 只打控制台(默认)。"stderr" 和留空等价——
                            # 三者实际都写 stderr,好让日志不污染 --json 的 stdout
                            # 契约。想重定向到文件请用 2>,或者直接填文件名(见下)
output = "logs/app.log"     # 写这个文件;没有扩展名时自动补 .log
output = "logs/"            # 以斜杠结尾 = 写进这个**目录**,文件名用 ipclick.log

第三种写法容易踩坑,这里专门处理过。/ 结尾、或指向一个已存在的目录, 都视为目录。否则 output = "logs/" 会被当成"缺扩展名的文件名",logs 被改写成 同级的 logs.log,而 logs/ 目录里空空如也——且不报任何错,用户以为配好了, 真出问题时找不到日志在哪。填明确文件名的行为不变。

[LOG].format 的坑:底层是 loguru,占位符是 {time} / {level} / {message} 这种花括号写法,不是标准库 logging 的 %(asctime)s。写成后者的话 loguru 会把它当 普通文本原样打出来,每行日志变成一串字面量。所以 IPClick 会识别出这种写法、告警并忽略, 而不是照用。

另外 format 里必须含 {message},否则同样告警并回退内置格式——少了它整条日志 正文都会丢掉。

例:format = "[{time:YYYY-MM-DD HH:mm:ss}] {level: <8} | {message}"

同一目录起多个实例:{port} 占位符

--port 就能把 gRPC 端口岔开,但有两项会静默撞车:

配置项 撞车表现
[TRACE].sqlite_path 多实例写同一个库,链路记录混在一起,界面上完全看不出来
[LOG].output 多进程抢写同一个日志文件
[WEB].port 第二个实例的 Web 端起不来(这个明确报错)

前两项不报错、不提示,比"起不来"糟得多——用户以为在看单个实例的数据,实际是多进程 合并的结果。

两项路径值都支持 {port} 占位符,替换成运行时实际生效的监听端口 (--port 覆盖之后的那个,不是配置文件里的原始值):

[TRACE]
sqlite_path = "ipclick-trace.{port}.db"   # -> ipclick-trace.9528.db

[LOG]
output = "logs/{port}/app.log"            # 占位符在路径中间也行

[WEB].port 则补了命令行覆盖(gRPC 端口一直有 --port,Web 端此前没有):

ipclick run --port 9528 --web-port 9531 -w

为什么用占位符,而不是在代码里自动加端口后缀。 自动加后缀会改掉已有部署里用户 自己写死的路径——旧日志和 trace 数据看起来就像丢了。占位符则是:新用户从 ipclick init / ipclick -e 拿到的模板默认就带 {port},天然按端口分离;已有部署 一个字符都不变。而且这条规则写在 toml 里是看得见的,不是藏在代码里的隐式行为。

已有配置想要这个行为,手动把路径改成带 {port} 即可。

即便如此,真撞上了还是会打一条明确告警——检测到目标库正被另一个进程写入时提醒一句, 而不是静默合并。

范围:主要影响"单机模拟多节点"的测试 / 开发场景。生产环境每台机器独立文件系统, 不受影响。

从 Web 管理端改配置

Web 管理端的「配置」页可以改白名单内的行为配置, 写回 ipclick.toml:定点文本替换,保留注释与格式,改动前留 .bak, 写入用临时文件 + os.replace(断电不会留下半个配置文件)。

不可从网页修改[SECURITY] 全部、Web 自己的登录凭据、集群共享密钥与各节点 token、 [BROWSER].allow_scripts。理由见 Web 管理端

配置页还能生成鉴权令牌 / Web 密码 / 集群共享密钥——只显示一次、服务端不保存, 给出可以直接粘进 .env 的那一行。生成而不是写入,是因为机密的正规位置始终是 .env; 集群共享密钥尤其不能自动写,每台各自生成一个就全对不上了。

排查配置问题

ipclick config-info

这个命令回答的是「什么真生效了」,而不是「配置文件里写了什么」——两者不一样: 环境变量会覆盖、有些项有默认值、有些项写错了会被忽略。输出里包括绑定地址、 TLS 与鉴权状态、每个机密的来源、限流设置、浏览器引擎与本体就绪状态、链路记录状态。

下一步

Clone this wiki locally