Skip to content

Upstream Discovery ​

An upstream.discovery plugin resolves a service into the servers that back it right now, through a service registry, a container orchestrator or a cloud API. A person binds an nginx upstream to a service; Nginx UI calls the plugin on the binding's refresh interval, writes the answer as an upstream block into a file of its own and reloads nginx when the servers changed.

MethodRequiredPurpose
discovery.resolveyesResolve one service into its current servers.

The plugin must request the network permission.

Declaring Providers ​

json
"capabilities": ["upstream.discovery"],
"permissions": ["network"],
"discovery": {
  "providers": [
    {
      "code": "registry",
      "name": "Service registry",
      "configuration": {
        "fields": [
          { "key": "address", "display_name": "Registry address", "required": true },
          { "key": "token", "display_name": "ACL token", "secret": true },
          { "key": "healthy_only", "type": "bool", "display_name": "Only healthy instances" }
        ]
      }
    }
  ]
}

code, name and configuration.fields work as for notification channels.

Resolving ​

json
{
  "jsonrpc": "2.0", "id": 50, "method": "discovery.resolve",
  "params": { "provider": "registry", "config": { "address": "https://registry.example.com:8500", "token": "tok_live_xxx" }, "service": "api" }
}

service is the service in the provider's own terms: a name, a label selector, a tag. Answer with every server that backs it now:

json
{
  "jsonrpc": "2.0", "id": 50,
  "result": {
    "targets": [
      { "address": "10.0.1.12", "port": 8080, "weight": 1, "tags": ["zone-a"] },
      { "address": "10.0.2.7", "port": 8080, "weight": 2, "tags": ["zone-b", "canary"] },
      { "address": "2001:db8::17", "port": 8443 }
    ],
    "ttl_seconds": 30
  }
}
FieldMeaning
targets[].addressAn IPv4 or IPv6 address without brackets, or a host name. No port or scheme.
targets[].portTCP port, 1 to 65535.
targets[].weightRelative weight, 1 to 1000. 0 or absent means 1.
targets[].tagsLabels for display.
ttl_secondsHow long the answer stays fresh. 0 leaves it to the interval.

An empty list means the service has no servers right now. When the service cannot be resolved, answer an error, so Nginx UI keeps the servers it has: -32003 with data.field for causes in the configuration, including rejected credentials and a service the provider does not know (which names service), and -32000 otherwise. Treat every config value as secret.

What Nginx UI Does With the Answer ​

  • It keeps only targets whose address parses as an IP address or a valid host name and whose port and weight are in range, drops the rest and reports how many. No other text, tags included, reaches the configuration. When no valid target remains, it keeps the current file and records a failure, since an upstream without servers is invalid.

  • It writes each binding to <nginx conf dir>/upstreams/<name>.conf, where the person chooses the upstream name:

    nginx
    # generated by nginx-ui
    upstream api {
        server 10.0.1.12:8080 weight=1;
        server 10.0.2.7:8080 weight=2;
        server [2001:db8::17]:8443 weight=1;
        keepalive 32;
    }

    The last lines are extra directives the person entered for the binding.

  • It reloads nginx only when the file changed, tests the configuration first and restores the previous file when the test fails.

  • It shows the person the line to add at the http level: include upstreams/<name>.conf;.

Bindings refresh every 60 seconds by default and no more often than every 10 seconds, earlier when the last answer had a shorter ttl_seconds. Nginx UI waits 30 seconds for discovery.resolve and shows the outcome of the last resolution next to the binding.

Released under the AGPL-3.0 License. (350eea3f)