openresty 不兼容 nginx-upsync-module,因其底层 abi 冲突且官方明确不支持;应使用 balancer_by_lua* + shared_dict 或 redis/consul 实现动态 upstream,并注意 composer 协议特性如 302 透传、host 头保留及 tls 配置。

OpenResty 本身不带 upsync 模块 —— 这是微博开源的第三方 Nginx 模块,仅支持原生 Nginx(需手动编译),不能直接用在 OpenResty 上。想在 OpenResty 环境里实现 Composer 镜像源的动态上游管理,得换路子。
为什么不能直接在 OpenResty 上用 nginx-upsync-module
nginx-upsync-module 依赖 Nginx 的 upstream 框架底层 hook(如 ngx_http_upstream_init_round_robin),而 OpenResty 的 ngx_http_upstream_module 已被 Lua 模块深度介入和扩展,二者 ABI 不兼容。即使强行编译进 OpenResty,大概率触发段错误或 upstream 调度失效。
- 运行
nginx -V查看 OpenResty 的 configure 参数,里面不会出现--add-module=...对应 upsync 路径 - 尝试加载会报错:
unknown directive "upsync"或 worker 进程启动即崩溃 - OpenResty 官方 Wiki 明确标注:不兼容所有需要 patch core upstream 的第三方模块
OpenResty 下真正可行的动态 upstream 方案
用 balancer_by_lua* + 共享字典(shared_dict)或外部存储(Redis/Consul),才是 OpenResty 原生、稳定、可灰度的路径。
-
init_worker_by_lua_block里启动定时器,从 Consul KV 或 Redis 拉取镜像源节点列表(JSON 格式),写入shared_dict -
balancer_by_lua_block中读取该字典,按权重/健康状态选节点,调用set_current_peer(ip, port) - 节点健康检查必须自己实现:比如记录失败次数+指数退避,或配合
lua-resty-healthcheck库 - 示例片段(简化):
balancer_by_lua_block {
local peers = ngx.shared.peers
local list = peers:get("composer_upstream") or {}
if #list == 0 then
ngx.exit(503)
end
local peer = list[ngx.time() % #list + 1] -- 简单轮询,实际应加权重和健康判断
ngx_balancer.set_current_peer(peer.host, peer.port)
}
Composer 镜像源场景的特殊注意点
Composer 客户端默认使用 HTTP 302 重定向(非透明代理),所以 OpenResty 必须以反向代理模式工作,且要透传 Location 头、保持 Host 和 User-Agent 不变;动态 upstream 只影响后端转发,不影响客户端行为。
- 务必关闭
proxy_redirect自动改写,否则重定向地址会被错误替换 -
proxy_set_header Host $host;不能写成$upstream_http_host,否则 Packagist 认证可能失败 - 镜像源通常要求 TLS 终止在边缘,OpenResty 必须正确传递 SNI 并验证后端证书(用
proxy_ssl_trusted_certificate) - PHP Composer 请求头含
X-Composer-Version等字段,别在balancer_by_lua里误删
要不要迁回原生 Nginx + upsync?
如果团队已重度依赖 OpenResty 的 Lua 生态(比如已有鉴权、限流、日志增强等逻辑),倒退回原生 Nginx + nginx-upsync-module 得不偿失。维护两套配置、失去 Lua 能力、无法复用现有模块,长期成本更高。
- 真正卡点往往不是“能不能动态”,而是“如何让动态逻辑与 Composer 协议语义对齐”
- 一个健壮的
balancer_by_lua实现,比拼接 upsync + 健康检查模块 + reload 兜底更可控 - 容易被忽略的是:Composer 的
packages.json请求频率低但响应体大,共享字典需预留足够内存(lua_shared_dict peers 10m;至少 5–10MB)











