Skip to content

管理控制台(Admin)

Ingress 可在与反向代理同一进程中嵌入运维控制台。在 ingress.yaml 顶层配置 admin: 即可启用,没有单独的 ingress admin 子命令。

控制台提供 HTTP API(默认端口 9080)和 React UI,用于查看路由、日志、TLS、缓存、WAF 事件,以及配置的校验 / 发布 / 热重载。

快速开始

最小配置:

yaml
version: v1
port: 8080

admin:
  enabled: true
  port: 9080

rules:
  - host: example.com
    backend:
      service:
        name: backend-service
        port: 8080

启动:

bash
ingress run -c ingress.yaml

启动日志示例:

text
Admin started at http://127.0.0.1:9080
Server started at http://127.0.0.1:8080

生产构建下(admin.web.dev_proxy: false)在浏览器打开 http://127.0.0.1:9080 即可使用内置 UI。

默认 admin.auth.typenone(无登录门禁)。在不可信网络暴露 admin 端口前,请设置 basicoauth — 见 认证与 RBAC

完整示例包:examples/admin-console/ — 多路由、示例日志、TLS 证书与 SQLite 状态。参见 Admin 控制台示例

配置项

字段类型说明默认值
admin.enabledboolingress run 一起启动 adminfalse
admin.portintAdmin 监听端口9080
admin.database.driverstring审计 / 修订记录数据库驱动sqlite
admin.database.dsnstringSQLite DSN(相对路径相对 ingress 配置文件目录解析)file:admin.db?cache=shared&_fk=1
admin.web.dev_proxybool仅 API;UI 由 Vite 开发服务器提供(代理 /apifalse
admin.auth.typestring控制台登录模式:none(默认)、basicoauthnone
admin.auth.basic.usernamestring引导超级管理员 RBAC 用户名(每次启动同步)配置密码时默认 admin
admin.auth.basic.passwordstring引导用户密码(写入 RBAC;仅首次创建时使用)配置用户名时默认 admin
admin.auth.oauth.*object第三方 OAuth(providerclient_idclient_secret 等)
admin.access_log_pathstring日志页读取的 access 日志路径(覆盖)来自 logging 文件 transport
admin.error_log_pathstring日志页读取的 error 日志路径(覆盖)来自 logging 文件 transport

本地 SQLite + UI 开发模式示例:

yaml
version: v1
port: 8080
admin:
  enabled: true
  port: 9080
  database:
    driver: sqlite
    dsn: file:./admin.db?cache=shared&_fk=1
  web:
    dev_proxy: true
  geoip:
    ingress_label: 上海
    ingress_lat: 31.2304
    ingress_lng: 121.4737
cache:
  ttl: 300
  prefix: "ingress:"
waf:
  enabled: true
  log_only: false
  builtin: true
  trust_proxy: true
healthcheck:
  outer:
    enable: true
    path: /healthz
    ok: true
  inner:
    enable: true
    interval: 30
    timeout: 5
https:
  port: 8443
  redirect_from_http:
    permanent: true
  ssl:
    - domain: api.example.com
      cert:
        certificate: ./certs/api.example.com.pem
        certificate_key: ./certs/api.example.com.key.pem
    - domain: cdn.example.com
      cert:
        certificate: ./certs/cdn.example.com.pem
        certificate_key: ./certs/cdn.example.com.key.pem
    - domain: assets.cdn.example.com
      cert:
        certificate: ./certs/assets.cdn.example.com.pem
        certificate_key: ./certs/assets.cdn.example.com.key.pem
    - domain: admin.internal
      cert:
        certificate: ./certs/admin.internal.pem
        certificate_key: ./certs/admin.internal.key.pem
    - domain: legacy.example.com
      cert:
        certificate: ./certs/legacy.example.com.pem
        certificate_key: ./certs/legacy.example.com.key.pem
    - domain: tunnel-a.inlets.example.com
      cert:
        certificate: ./certs/tunnel-a.inlets.example.com.pem
        certificate_key: ./certs/tunnel-a.inlets.example.com.key.pem
    - domain: waf-demo.example.com
      cert:
        certificate: ./certs/waf-demo.example.com.pem
        certificate_key: ./certs/waf-demo.example.com.key.pem
    - domain: portal.example.com
      cert:
        certificate: ./certs/portal.example.com.pem
        certificate_key: ./certs/portal.example.com.key.pem
fallback:
  type: handler
  handler:
    type: static_response
    headers:
      Content-Type: text/plain; charset=utf-8
    body: |
      fallback ok
services:
  - name: api.internal
    port: 8080
    note: API 主集群(演示用 handler 替代)
  - name: api-v2.internal
    port: 8080
    note: API v2 路径专用
  - name: home
    port: 8080
rules:
  - host: api.example.com
    backend:
      type: handler
      handler:
        type: static_response
        headers:
          Content-Type: application/json
        body: |
          {"ok":true,"service":"api"}
      cache:
        enabled: true
        ttl: 300
    paths:
      - path: /v2
        backend:
          type: handler
          handler:
            type: static_response
            headers:
              Content-Type: application/json
            body: |
              {"v2":true,"users":[]}
          cache:
            enabled: true
            ttl: 600
            max_body_bytes: 2097152
      - path: /public
        backend:
          type: handler
          handler:
            type: static_response
            headers:
              Content-Type: application/json
            body: |
              {"public":true}
          cache:
            enabled: true
            ttl: 300
            max_body_bytes: 2097152
      - path: /search
        backend:
          type: handler
          handler:
            type: static_response
            headers:
              Content-Type: application/json
            body: |
              {"results":[]}
      - path: /error/400
        backend:
          type: handler
          handler:
            type: static_response
            status_code: 400
            headers:
              Content-Type: application/json
            body: |
              {"error":"bad request"}
      - path: /error/403
        backend:
          type: handler
          handler:
            type: static_response
            status_code: 403
            headers:
              Content-Type: application/json
            body: |
              {"error":"forbidden"}
      - path: /error/500
        backend:
          type: handler
          handler:
            type: static_response
            status_code: 500
            headers:
              Content-Type: application/json
            body: |
              {"error":"internal"}
  - host: cdn.example.com
    backend:
      type: handler
      handler:
        type: file_server
        root_dir: ./static
        index_file: assets/app.js
      cache:
        enabled: true
        ttl: 3600
  - host: assets.cdn.example.com
    host_type: exact
    backend:
      type: handler
      handler:
        type: file_server
        root_dir: ./static
        index_file: static/main.js
      cache:
        enabled: true
        ttl: 3600
  - host: portal.example.com
    backend:
      type: handler
      handler:
        type: static_response
        headers:
          Content-Type: text/html; charset=utf-8
        body: |
          <!doctype html><html><body><h1>portal</h1></body></html>
      cache:
        enabled: true
        ttl: 120
  - host: ^([a-z0-9-]+)\.inlets\.example\.com$
    host_type: regex
    backend:
      service:
        name: ${host.1}.tunnel
        port: 443
        protocol: https
  - host: admin.internal
    backend:
      type: handler
      handler:
        headers:
          Content-Type: text/plain; charset=utf-8
        body: |
          admin console demo host
      cache:
        enabled: true
        ttl: 60
    paths:
      - path: /healthz
        backend:
          type: handler
          handler:
            headers:
              Content-Type: text/plain; charset=utf-8
            body: |
              ok
  - host: legacy.example.com
    backend:
      type: redirect
      redirect:
        url: https://www.example.com$request_uri
        permanent: true
      cache:
        enabled: true
        ttl: 120
  - host: httpbin.work
    backend:
      service:
        mode: external
        protocol: https
        name: httpbin.zcorky.com
        port: 443
scenarios:
  active: default
  items:
    - id: peak
      label: 高峰
      description: Admin 演示 — 延长 api.example.com 缓存 TTL
      overlay:
        rules:
          - host: api.example.com
            backend:
              cache:
                enabled: true
                ttl: 900

启用控制台只需 admin: 段;文件中其余内容为路由演示。

日志与日志查看器

Admin 日志页从磁盘文件读取。默认与 ingress 的 logging 在 prepare 之后使用的路径一致。

admin.enabled: true未配置 logging(无 enableleveltransports)时,ingress 默认:

  • logging.enable: true
  • 在配置文件同目录写入 access.logerror.log

显式配置的 logging.* 始终优先,包括 logging.enable: false 或自定义 transports。当 未启用 admin 时,若只设置 logging.enable: true 且未写 transports,仍默认使用 /var/log/ingress/access.logerror.log

仅覆盖 admin 读取路径(不改变 ingress 自身 logging):

yaml
admin:
  enabled: true
  access_log_path: /var/log/ingress/access.log
  error_log_path: /var/log/ingress/error.log

访问日志行格式见 配置参考 · 访问日志字段。查询支持 cache_hitwaf_blockhoststatus 以及按字节 offset 尾部读取。

UI 开发

前端开发时开启 dev proxy 并单独跑 Vite:

yaml
admin:
  enabled: true
  web:
    dev_proxy: true
bash
ingress run -c ingress.yaml
cd core/admin/web && pnpm dev

Vite 开发服务器将 /api 代理到 admin 端口。生产 UI 在 cd core/admin && make build 时编译进二进制(-tags adminui;产物在 core/admin/static/dist,不提交 Git)。

HTTP API

基础路径:/api/v1。响应为 JSON 封装格式。

方法路径用途
GET/status进程 / 配置摘要
GET/routes扁平化路由表
POST/routes/matchdry-run 匹配(JSON:hostpath
GET/logs检索 / 尾部读取 access 或 error 日志
GET/metrics/overview基于内存 rollup 聚合(不足时回退 access 日志 tail)
GET/waf/events最近 WAF 审计记录(SQLite)
GET/tls/certs配置中证书文件的元数据
POST/tls/certs/check检查单个域名
GET/cache/overview缓存引擎 / 键概览
GET/config读取 ingress YAML
PUT/config保存 YAML(写入修订记录)
POST/config/validate校验 YAML 或磁盘上的文件
POST/config/preview预览 / diff 待发布变更
POST/config/publish校验、保存并重载
POST/config/modules列出配置编辑器模块
POST/config/modules/merge合并单个模块补丁
GET/config/revisions修订历史列表
GET/config/revisions/:id单条修订
POST/reload校验磁盘配置并重载 ingress
GET/auth/config登录模式与当前会话用户
POST/auth/loginBasic 登录(JSON:usernamepassword
POST/auth/logout清除会话
GET/auth/oauth/login发起 OAuth 跳转(可选 ?redirect=
GET/auth/oauth/callbackOAuth 回调
GET/rbac/menus按当前用户 menu:* 权限过滤的侧栏树
GET/POST/PUT/DELETE/rbac/users/rbac/roles/rbac/permissionsRBAC 管理
GET/routes/:ri/:pi路由详情(配置 + auth/cache/healthcheck)
GET/routes/:ri/:pi/metrics路由级聚合指标
GET/events/streamSSE 实时事件流(?channels=...
GET/healthcheck健康检查探测结果与汇总
GET/settingsAdmin 与 ingress 设置快照

在控制台内发布 / 重载会先校验配置文件,再触发进程内热重载(与 ingress reload / SIGHUPingress run 启动时效果一致)。

实时事件(SSE)

管理控制台通过 Server-Sent Events(SSE)推送实时更新。连接端点:

GET /api/v1/events/stream?channels=metrics,waf,logs,health

支持的频道:metricswaflogshealth。UI 根据当前页面自动订阅。单 IP 最多 5 个并发 SSE 连接;超出或不可用时客户端自动降级为轮询。

总览指标数据路径

GET /api/v1/metrics/overview?window=15m 返回 JSON,其中 source 表示窗口数据来源:

source含义
rollup_live仅进程内环形缓冲(ingress 与 Admin 同进程且有流量时常见)
rollup_hybrid较旧分钟来自 SQLite 分钟桶 + 最近数据来自 live 缓冲
rollup_persisted仅 SQLite 分钟桶(如重启后 live 缓冲为空的长窗口)
access_log解析 access 日志 tail(rollup 未覆盖窗口时的回退)
access_log_partialtail 行数上限导致未读到窗口起点
error读日志/解析失败

实时路径: 每个请求在 ingress core 的 logAccess() 中发出 AccessMetricsEvent,Admin MetricsRollup.Record 写入内存。冷启动: Admin 加载持久化桶(26h),缓冲为空时从 access 日志种子最多 1h;仅当 Admin 无 CoreInstance 时才 tail 新行(避免双计)。持久化: 每分钟 flush 已关闭分钟到 SQLite;内置任务 purge_metrics_bucketsparams.retain_days(默认 30)清理过期桶。

日志页仍走 SSE tail + offset;仅总览聚合走 rollup。

总览趋势图联动

同一页面内的时间序列图(域名流量、全站流量、质量、缓存、上游延迟等)通过 ECharts connect 共享十字线与 tooltip:鼠标悬停任意一张图,其余图在同一时间桶对齐高亮。X 轴刻度来自同一份 metrics.timeline[]host_timeline[].points[].label 与之间对齐)。非时间序列面板(状态码 donut、延迟 SLO 占比、Top 排名等)不参与联动。

事件格式:

event: channel:action
data: {"key": "value", ...}

路由详情

点击路由列表中的任意行,进入路由详情页 /routes/:ruleIndex/:pathIndex。展示内容:

  • 完整路由配置(host、path、backend、auth、cache、healthcheck、WAF)
  • 实时指标(QPS、延迟分位数、错误率、缓存命中率)
  • 该路由的过滤日志、WAF 事件、缓存数据

拓扑图

拓扑页面(/topology)以纯 SVG 渲染三层图:Host → Path → Backend。节点颜色标识健康状态(绿色=正常、黄色=告警、红色=故障)。点击节点可跳转至路由详情或路由列表。

健康检查面板

健康检查页面(/health)展示所有配置了 healthcheck 的后端状态。后端每 30 秒探测一次,5 秒超时。状态变更通过 SSE health 频道推送。

配置草稿与撤销

配置编辑器追踪编辑历史(最多 50 步)。使用 Ctrl+Z / Cmd+Z 撤销,Ctrl+Shift+Z / Cmd+Shift+Z 重做。Tab 栏的草稿徽章显示未保存的变更。

一键回滚

配置版本历史面板中,每个修订都有回滚按钮。点击后弹出确认对话框,然后校验、发布并重载所选版本 — 无需手动复制粘贴。

证书到期告警

总览页现在读取真实 TLS 证书数据,而非硬编码值。证书在 30 天内到期显示黄色告警,7 天内显示红色严重告警。

版本一致性标识

总览页对比运行配置 hash最新修订 hash。绿色徽章表示「配置一致」,黄色表示「有变更未发布」。

认证与 RBAC

控制台登录与路由级 backend.service.auth(转发到上游的 Basic/Bearer)相互独立,在 admin.auth 下配置。

最小 Basic 登录示例:

yaml
version: v1
port: 8080

# Minimal Admin Console with local login + RBAC.
# Default UI login: admin / admin (see admin.auth.basic below).
admin:
  enabled: true
  port: 9080
  database:
    driver: sqlite
    dsn: file:./admin-auth.db?cache=shared&_fk=1
  auth:
    type: basic
    basic:
      username: admin
      password: admin

rules:
  - host: app.example.com
    backend:
      service:
        name: app-service
        port: 8080

更多示例:examples/admin-auth/ — 参见 Admin 认证示例

登录模式

admin.auth.type行为
none(默认)无登录门禁 — 仅适合 localhost / 可信网络
basic本地登录页;凭据与 SQLite 中的 RBAC 用户 校验
oauth跳转第三方登录(GitHub、GitLab、Google、飞书等)

admin.auth.typebasicoauth 时,除 auth 端点外的所有 /api/v1/* 均需有效 session cookie

引导超级管理员

每次启动时,ingress 将 admin.auth.basic.username(默认 admin)同步到 RBAC:

  • 首次运行创建用户(密码来自 admin.auth.basic.password,省略时默认 admin
  • 确保该用户始终拥有内置 admin 角色(超级管理员)
  • 在 UI 中修改的密码会保留;后续启动不会用配置覆盖已有密码

更多操作员可在侧栏 权限 中管理(用户 / 角色 / 权限)。

RBAC 模型

实体作用
权限细粒度授权 — 操作码(routes:readconfig:write …)与侧栏 menu:*
角色权限集合,分配给用户
用户控制台操作员(bcrypt 密码)

菜单 vs 操作权限

  • 侧栏入口需要对应 menu:*(例如 menu:routes 才显示「路由」)
  • 仅有操作权限(例如只有 routes:read、没有 menu:routes不会显示菜单
  • 编辑角色时请同时勾选 菜单 分组

登录规则

  • Basic 登录成功要求账号至少有一个可见菜单
  • 否则返回 403不创建会话(仅有操作权限、无菜单的角色无法登录)

内置角色

启动时种子同步(内置角色不可删除):

Code名称适用场景
admin管理员全部功能
viewer只读观察监控 / 流量 / 安全只读
operator运维工程师只读 + 维护 / 定时任务 / Web 终端
developer路由开发路由 / 服务 / 缓存 / 配置 / 设置
security安全工程师事件 / 日志 / WAF / TLS / 健康检查

OAuth(可选)

yaml
admin:
  auth:
    type: oauth
    oauth:
      provider: github
      client_id: "..."
      client_secret: "..."
      # redirect_url: https://admin.example.com/api/v1/auth/oauth/callback
      scopes:
        - user:email

OAuth 登录后会话用户名为提供商 profile 推导值。若需按菜单过滤,请在 RBAC 中创建对应用户并分配角色。

安全说明

  • admin.auth.type 默认为 none(开放 API/UI)。在 localhost 或可信网络之外暴露 admin 端口前,请启用 basicoauth
  • 配置发布会写入 live ingress.yaml 并重载代理 — 即使已启用认证,也应限制 admin 端口访问面。
  • 不要在不可信网络暴露 admin.web.dev_proxy: true

相关命令

运行或部署前校验:

bash
ingress validate -c ingress.yaml

修改磁盘上的配置后重载:

bash
ingress reload -c ingress.yaml
# 或:kill -HUP $(cat /tmp/gozoox.ingress.pid)

runvalidatereload 的选项见 快速开始 · 命令行选项

Released under the MIT License.