内部服务器

OxPHP 会在一个专用端口上运行一个独立的 HTTP 服务器,用于健康检查、Prometheus 指标以及实时配置查看。它与主应用监听器完全隔离:没有 TLS,没有限流,没有请求 ID,也不做事件处理。

工作原理

INTERNAL_ADDR 设置为一个监听地址(例如 127.0.0.1:9090),OxPHP 就会在该地址上启动第二个 HTTP 监听器。内部服务器会暴露下列内置端点:

  • /health — 聚合的 JSON 状态
  • /health/liveness/healthz(别名)— 存活探针
  • /health/readiness/readyz(别名)— 就绪探针
  • /health/startup/startupz(别名)— 启动探针
  • /metrics — Prometheus 格式输出
  • /config — 当前生效的服务器配置 JSON

插件可以在 /__<plugin>/ 前缀下注册额外的端点。在优雅关闭期间,内部服务器会一直保持可用,直到主服务器完成连接排空。

Note

只有在显式设置了 INTERNAL_ADDR 时,内部服务器才会启动。若不设置,健康检查、指标和配置端点将无法通过 HTTP 访问。

配置

Variable Default Description
INTERNAL_ADDR (unset) 内部服务器的地址。未设置时不启动。仅指定端口的值(:90909090)会绑定到 127.0.0.1;要将其暴露到主机之外,请使用显式的 0.0.0.0:9090。示例:127.0.0.1:9090
INTERNAL_ALLOW_IPS (unset) 以逗号分隔的 CIDR/IP 允许列表。列表之外的对端在访问 /metrics/config 以及 /__<plugin>/ 路径时会收到 403;健康探针始终保持可达。未设置/为空 = 允许所有。回环地址不会被隐式包含——请列出 127.0.0.1/32 以保留 localhost 访问。列表格式错误会中止启动

端点

GET /health

返回 JSON 格式的健康状态。可将其用于 Kubernetes 的就绪探针和存活探针。

200 OK — 所有系统健康:

json
{ "status": "ok", "uptime_secs": 3612, "total_requests": 48203, "active_connections": 7, "executor_healthy": true, "plugins": { "otel": "ok" } }

503 Service Unavailable — 某个插件报告了失败:

json
{ "status": "degraded", "uptime_secs": 3612, "total_requests": 48203, "active_connections": 7, "executor_healthy": true, "plugins": { "otel": "failed" } }

plugins 对象会列出每个已加载的插件及其健康状态:"ok""degraded""failed"。当任意插件报告 "failed"或者脚本执行器不健康(executor_healthy: false)时,该端点会返回 503。"degraded" 的插件会出现在 JSON 响应体中,但 HTTP 状态码仍为 200。

GET /metrics

以文本格式(text/plain; version=0.0.4; charset=utf-8)返回 Prometheus 兼容的指标。始终返回 200。

bash
curl http://localhost:9090/metrics
text
# HELP oxphp_requests_total Total HTTP requests # TYPE oxphp_requests_total counter oxphp_requests_total 48203 # HELP oxphp_active_connections Current open connections # TYPE oxphp_active_connections gauge oxphp_active_connections 7 ...

有关可用指标的完整列表,请参阅 Metrics

GET /config

以 JSON 形式返回当前生效的服务器配置。始终返回 200。

bash
curl -s http://localhost:9090/config | jq .
json
{ "listen_addr": "0.0.0.0:80", "document_root": "/var/www/html/public", "entry_file": "/var/www/html/public/index.php", "log_level": "info", "executor_type": "sapi", "php_workers": "8", "tokio_workers": 4, "queue_capacity": 1024, "max_connections": 10000, "drain_timeout_seconds": 30, "header_timeout_seconds": 5, "rate_limit": 100, "rate_window_seconds": 60, "tls_enabled": true, "tls_min_version": "1.2", "compression_level": 4, "access_log": "all", "max_query_body": 524288, "worker_mode_enabled": false, "worker_max_memory_mib": 0, "static_max_age": 2592000, "static_revalidate": false, "async_workers": 0, "async_queue_capacity": 0, "async_max_fibers": 256, "async_in_flight_cap": 0, "trace_context": false, "superglobals_enabled": true, "trusted_proxies": false, "plugins": {} }

TLS 证书和密钥文件路径永远不会被输出(只暴露 tls_enabled 布尔值),而 internal_addrerror_pages_dir 也会从所返回的响应中被清除——这些部署拓扑和文件系统路径对攻击者有帮助,而抓取程序并不需要它们。

插件端点

插件可以在 /__<plugin_name>/ 前缀下注册自定义端点。例如,一个名为 otel 的插件可以暴露 /__otel/status。只有在对应的插件已加载并注册了处理器时,这些端点才可用。

任何既不匹配内置端点也不匹配插件端点的路径都会返回 404 Not Found

Kubernetes 集成

就绪探针

使用 /health 来控制 Kubernetes 是否将流量路由到该 Pod:

yaml
readinessProbe: httpGet: path: /health port: 9090 initialDelaySeconds: 2 periodSeconds: 5

/health 返回 503 时,Kubernetes 会将该 Pod 从 Service 的端点列表中移除。当该端点重新返回 200 时,流量恢复。

存活探针

使用同一个端点来重启变得无响应的 Pod:

yaml
livenessProbe: httpGet: path: /health port: 9090 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3

启动探针

适用于启动较慢的应用(大型框架、繁重的自动加载器):

yaml
startupProbe: httpGet: path: /health port: 9090 initialDelaySeconds: 1 periodSeconds: 2 failureThreshold: 15

安全

内部服务器没有 bearer-token 认证;访问控制取决于它绑定在何处以及 INTERNAL_ALLOW_IPS。要保护它:

  • 绑定到 localhost — 仅指定端口的 INTERNAL_ADDR:9090)已经会绑定到 127.0.0.1;显式的 127.0.0.1:9090 可让该端口仅能从容器或主机内部访问
  • 设置 INTERNAL_ALLOW_IPS — 当监听器必须能从主机之外访问时使用它——这是一个 CIDR/IP 允许列表,对列表之外的对端在访问 /metrics/config 以及插件路径时返回 403,同时健康探针保持可达。回环地址不会被隐式包含,因此如果你仍需要 localhost 访问,请加入 127.0.0.1/32。如果监听器暴露到主机之外却未设置允许列表,服务器会在启动时发出警告
  • 不要将其作为 Kubernetes Service 暴露 — 将该端口声明为 containerPort,但不要为它创建 Service。Kubernetes 探针会直接访问容器端口
  • 使用网络策略 — 如果该端口必须暴露,请在网络层限制访问
Warning

/config 端点会泄露运维细节(文档根目录、限流参数、工作进程数量、超时值)。TLS 路径、internal_addrerror_pages_dir 会被清除,但仍需考虑其余内容是否应当能从 Pod 外部访问。

Docker 示例

要快速查看,你可以将内部端口发布出来;在生产环境中,请将内部服务器绑定到 localhost 并使用 Kubernetes 探针。

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "80:80" - "9090:9090" environment: - DOCUMENT_ROOT=/var/www/html/public - INTERNAL_ADDR=0.0.0.0:9090

故障排查

健康检查、指标和配置端点不可用

INTERNAL_ADDR 未设置。

解决方法: 添加该环境变量:

bash
INTERNAL_ADDR=0.0.0.0:9090
/health 返回 503 但应用正常工作

某个已加载的插件正在报告 PluginHealth::Failed。检查健康响应中的 plugins 对象以确定是哪个插件出了问题:

bash
curl -s http://localhost:9090/health | jq '.plugins'
无法从容器外部访问内部服务器

该服务器绑定到了 127.0.0.1,只能从容器内部访问。

解决方法: 改为 0.0.0.0 以供外部访问,或使用直接访问容器的 Kubernetes 探针。

指标中未显示工作进程模式或异步指标

工作进程模式的指标只有在启用了工作进程模式(WORKER_MODE_ENABLED=true)时才会出现。异步指标只有在 ASYNC_WORKERS > 0 且至少有一个任务被派发或被拒绝时才会出现。

另请参阅