Lura v2.14.1 快速入门(构建第一个 API 网关)

从零跑通 Lura v2.14.1 网关的第一个透传端点与两个上游 JSON 服务的聚合端点,理解端点、后端、分组与字段加工的基本配置。

LuraLuraKrakenDAPI 网关UbuntuUbuntu 22.04LinuxGo微服务2026/10/17

Lura v2.14.1 快速入门(构建第一个 API 网关)

目标:从零跑通第一个 Lura 网关——先以透传端点验证链路,再把两个上游 JSON 服务聚合为一个端点,并完成字段加工与排障速查。已在 Ubuntu 22.04 实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04(与《Lura v2.14.1 Ubuntu 22.04 安装指南》同一实测环境,root 用户)
  • 前置条件:已按安装指南完成 Lura v2.14.1 网关构建(/usr/local/bin/lura-gateway)与 systemd 托管
  • Go 工具链:go1.25.0 linux/amd64
  • 演示上游:Python 脚本模拟的 JSON 服务——8090(产品)、8091(库存)、8097(嵌套结构)
  • 端口:网关监听 8080
  • 配置文件路径:/etc/lura/lura.json(修改后 systemctl restart lura-gateway 生效)

二、10 秒理解 Lura 的工作方式

Lura 是配置驱动的网关:一个 JSON 文件描述全部行为。三个核心概念:

  • endpoint:网关对外暴露的路径(如 /api/products/{id}),客户端请求它;
  • backend:每个端点背后真正的上游服务列表,网关并发调用它们;
  • group:给每个上游响应起个名字,聚合结果按名字合并成一个 JSON。

本文目标:把两个独立的上游 JSON 服务(产品库 8090、库存库 8091)聚合成一个端点 /api/products/{id},客户端一次请求拿到产品+库存。

前置条件:已按《Lura v2.14.1 Ubuntu 22.04 安装指南》完成网关构建与 systemd 托管。自检:

root@ubuntu22:~# which lura-gateway
/usr/local/bin/lura-gateway
root@ubuntu22:~# go version
go version go1.25.0 linux/amd64

三、准备两个演示上游

创建模拟上游脚本(一个脚本两种角色,靠参数区分):

root@ubuntu22:~# cat > /srv/mock_backend.py << 'EOF'
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json, sys

PORT = int(sys.argv[1])
NAME = sys.argv[2]

DATA = {
    "backend-a": {
        "/products/1": {"id": 1, "name": "Widget", "price": 9.99, "currency": "USD"},
        "/products/2": {"id": 2, "name": "Gadget", "price": 24.50, "currency": "USD"},
    },
    "backend-b": {
        "/stock/1": {"product_id": 1, "in_stock": True, "qty": 42},
        "/stock/2": {"product_id": 2, "in_stock": False, "qty": 0},
    },
}

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = DATA.get(NAME, {}).get(self.path)
        if body is None:
            self.send_response(404)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": "not found", "path": self.path}).encode())
            return
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(body).encode())
    def log_message(self, *a):
        pass

ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
EOF
root@ubuntu22:~# nohup python3 /srv/mock_backend.py 8090 backend-a > /tmp/backend-a.log 2>&1 &
root@ubuntu22:~# nohup python3 /srv/mock_backend.py 8091 backend-b > /tmp/backend-b.log 2>&1 &
root@ubuntu22:~# curl -s http://127.0.0.1:8090/products/1
{"id": 1, "name": "Widget", "price": 9.99, "currency": "USD"}
root@ubuntu22:~# curl -s http://127.0.0.1:8091/stock/1
{"product_id": 1, "in_stock": true, "qty": 42}

两个上游各自返回各自的 JSON——现在让网关把它们合并。

四、第一个透传端点

先写一个只含透传端点的配置,确认链路。no-op 编码表示网关原样搬运上游响应体,不做解析与重打包:

root@ubuntu22:~# cat > /etc/lura/lura.json << 'EOF'
{
  "version": 3,
  "name": "Lura Demo Gateway",
  "port": 8080,
  "cache_ttl": "60s",
  "timeout": "3s",
  "endpoints": [
    {
      "endpoint": "/api/health",
      "method": "GET",
      "output_encoding": "no-op",
      "backend": [
        {
          "host": ["http://127.0.0.1:8090"],
          "url_pattern": "/products/1",
          "encoding": "no-op"
        }
      ]
    }
  ]
}
EOF
root@ubuntu22:~# systemctl restart lura-gateway
root@ubuntu22:~# curl -s http://127.0.0.1:8080/api/health
{"id": 1, "name": "Widget", "price": 9.99, "currency": "USD"}

响应与上游一字不差,透传成功。

五、核心:聚合两个上游为一个端点

5.1 配置聚合端点

在 endpoints 里加入聚合端点:一个端点挂两个 backend,各自用 group 命名;{id} 是 URL 占位符,网关会把客户端请求里的实际值替换进每个上游的 url_pattern:

root@ubuntu22:~# cat > /etc/lura/lura.json << 'EOF'
{
  "version": 3,
  "name": "Lura Demo Gateway",
  "port": 8080,
  "cache_ttl": "60s",
  "timeout": "3s",
  "endpoints": [
    {
      "endpoint": "/api/products/{id}",
      "method": "GET",
      "backend": [
        {
          "host": ["http://127.0.0.1:8090"],
          "url_pattern": "/products/{id}",
          "group": "product"
        },
        {
          "host": ["http://127.0.0.1:8091"],
          "url_pattern": "/stock/{id}",
          "group": "stock"
        }
      ]
    },
    {
      "endpoint": "/api/health",
      "method": "GET",
      "output_encoding": "no-op",
      "backend": [
        {
          "host": ["http://127.0.0.1:8090"],
          "url_pattern": "/products/1",
          "encoding": "no-op"
        }
      ]
    }
  ]
}
EOF
root@ubuntu22:~# systemctl restart lura-gateway

5.2 一次请求,两路并发,一个响应

root@ubuntu22:~# curl -s http://127.0.0.1:8080/api/products/1
{"product":{"currency":"USD","id":1,"name":"Widget","price":9.99},"stock":{"in_stock":true,"product_id":1,"qty":42}}
root@ubuntu22:~# curl -s http://127.0.0.1:8080/api/products/2
{"product":{"currency":"USD","id":2,"name":"Gadget","price":24.5},"stock":{"in_stock":false,"product_id":2,"qty":0}}

发生了什么:客户端只请求了 /api/products/1 一个地址,网关同时向 8090 与 8091 各发一路请求,然后把两个响应按 group 名字合并为 {"product": {...}, "stock": {...}}。对比客户端自行串行调用两个服务的方案:请求数从 2 变 1,时延从"相加"变"取最慢一路"。

5.3 URL 占位符的行为

{id} 会被替换为实际路径段。请求一个上游不存在的 id:

root@ubuntu22:~# curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/api/products/99
500

99 在两个上游都是 404,网关把聚合失败转换为 500 返回。聚合端点中只要有一路上游失败,默认整体报错——生产上可通过给 backend 配置 extra_config 里的 qos 组件(熔断等)兜底,见《Lura v2.14.1 进阶实战与运维》。

六、给聚合结果做字段加工

6.1 mapping 重命名、deny 删除、allow 保留

在 backend 上加 mapping(重命名字段)与 deny(剔除字段),另一路用 allow(只保留白名单字段):

{
  "endpoint": "/api/filtered",
  "method": "GET",
  "backend": [
    {
      "host": ["http://127.0.0.1:8090"],
      "url_pattern": "/products/1",
      "deny": ["currency"],
      "mapping": {"name": "product_name"}
    },
    {
      "host": ["http://127.0.0.1:8091"],
      "url_pattern": "/stock/1",
      "allow": ["in_stock"]
    }
  ]
}

重启网关后实测:

root@ubuntu22:~# systemctl restart lura-gateway
root@ubuntu22:~# curl -s http://127.0.0.1:8080/api/filtered
{"id":1,"in_stock":true,"price":9.99,"product_name":"Widget"}

对 deny/mapping/allow 三种字段操作:

💡 技术拓展:三者作用时机相同(上游响应进入网关后、聚合前),按语义选择——mapping 只改键名不改数据面;allow 是白名单模式,适合对外收窄数据面;deny 是黑名单模式,适合剔除个别敏感字段。allow 与 deny 不要同时配同一个 backend。

6.2 target 抽取嵌套对象

上游返回嵌套 JSON 时,用 target 只取其中一层。新建一个返回嵌套结构的上游(端口 8097):

root@ubuntu22:~# cat > /srv/mock_nested.py << 'EOF'
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = {"user": {"name": "alice", "email": "alice@example.com", "level": 3}, "extra": "ignored"}
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(body).encode())
    def log_message(self, *a):
        pass

ThreadingHTTPServer(("0.0.0.0", 8097), Handler).serve_forever()
EOF
root@ubuntu22:~# nohup python3 /srv/mock_nested.py > /tmp/backend-nested.log 2>&1 &

backend 加 "target": "user":

{
  "endpoint": "/api/profile",
  "method": "GET",
  "backend": [
    {"host": ["http://127.0.0.1:8097"], "url_pattern": "/profile/1", "target": "user"}
  ]
}
root@ubuntu22:~# systemctl restart lura-gateway
root@ubuntu22:~# curl -s http://127.0.0.1:8080/api/profile
{"email":"alice@example.com","level":3,"name":"alice"}

extra 与 user 包装层被剥掉,客户端只拿到 user 对象本身。

七、验证与排障速查

现象原因处置
启动即退出,日志 cannot unmarshal number ... cache_ttl of type stringcache_ttl 写成了数字改为字符串 "60s"
启动即退出,日志 unsupported version: 2 (want: 3)配置 version 不是 3v2.x 引擎固定 "version": 3
配置里端点消失,日志 ignoring the '/__xxx' endpoint端点名用了 __ 保留前缀改名,如 /healthz
聚合端点返回 500某一路上上游非 2xx查上游;或配熔断/降级(进阶篇)
改配置不生效网关只在启动时读配置systemctl restart lura-gateway

至此第一个网关已完整工作。深入每个参数的含义与取值,继续阅读《Lura v2.14.1 配置参数详解》。

八、参考链接