Registration
The registration system publishes service discovery entries to NATS when managed resources reach a stable state. Resources that pass all health checks and are not in a failed state announce themselves to a shared registry. Other nodes discover these services dynamically through template lookups.
Supported Version
Added in version 0.0.20
Use cases
- Dynamic load balancer configuration - Web server resources register themselves on successful deploy, and a file resource on the load balancer node uses template lookups to generate an upstream configuration
- Service mesh discovery - Database and cache resources register their addresses and ports, allowing application nodes to build connection strings from live registry data
- Canary deployments - New service instances register with lower priority values, allowing consumers to prefer established instances while gradually shifting traffic
Resource configuration
Any resource type can publish registration entries by adding register_when_stable to its properties. Each entry describes a service endpoint to advertise.
This manifest ensures Nginx is running, verifies it responds to health checks, and then registers the node as a web service in the production cluster.
Entry properties
| Property | Template Key | Description |
|---|---|---|
cluster (required) | Cluster | Logical cluster name, must match [a-zA-Z][a-zA-Z\d_-]* |
service (required) | Service | Service name, must match [a-zA-Z][a-zA-Z\d_-]* |
protocol (required) | Protocol | Protocol identifier, must match [a-zA-Z][a-zA-Z\d_-]* |
address (required) | Address | IP address or hostname of the service endpoint |
port (integer) | Port | Port number, between 1 and 65535 |
priority (required) | Priority | Priority value between 1 and 255, lower values indicate higher priority |
ttl | TTL | Time-to-live for the entry: a duration (e.g., 10m, 1h) or never to disable expiry. Sets the Nats-TTL header |
annotations (map) | Annotations | Arbitrary key-value metadata published with the entry |
The cluster, address, port, and annotations fields support template expressions for dynamic values resolved at apply time.
How it works
Registration entries are published after a resource is applied. The following conditions must all be met:
- The resource has one or more
register_when_stableentries configured - The resource is not in noop mode
- The resource apply did not fail
- All health checks (if any) passed with OK status
Each entry is published to a NATS subject with the structure and may be persisted in a NATS stream.
Prerequisites
NATS stream
When using the jetstream destination, a JetStream stream must exist before registration entries can be published. Use ccm registration init to create or update the stream:
This creates a stream named REGISTRATION (or updates it if it already exists) with the correct subject filter, rollup, and TTL settings. Use --registration (-R) to specify a different stream name and --context to select a NATS context.
The --max-age value should exceed the agent’s apply or health check interval to prevent entries from expiring between runs. It also controls how long deletion markers for expired entries are retained in the stream.
Agent configuration
Add the registration field to the agent configuration file at /etc/choria/ccm/config.yaml:
Valid values for registration are nats (core NATS, fire-and-forget) and jetstream (reliable delivery). Omitting the field disables registration.
Template lookups
Other resources can query the registration registry using the registrations() function in templates. This enables dynamic configuration based on what services are currently registered.
The function takes four string arguments and returns an array of matching registration entries. Each entry exposes the struct fields listed in the Template Key column of the Entry Properties table (e.g., Address, Port, Priority).
Any argument can be "*" to wildcard that position.
Note
The registrations() function queries JetStream and requires a NATS connection and a configured registration stream.
The registrations() function is available in all three template engines.
Format transformers
The result of registrations() supports transformation methods that convert entries into formats consumed by other systems. These methods are callable from all three template engines.
Prometheus file service discovery
The PrometheusFileSD() method converts registration entries into the Prometheus file-based service discovery JSON format. Entries are grouped by cluster, service, and protocol into target groups.
Entries without a port are excluded from the output.
For entries where the service or protocol is prometheus, entries are included by default and only excluded if the prometheus.io/scrape annotation is explicitly set to something other than "true". For all other entries, the prometheus.io/scrape annotation must be explicitly set to "true" for the entry to be included.
Labels on each target group include cluster, service, protocol, and annotations that have non-empty values, do not start with __, and whose keys match [a-zA-Z_][a-zA-Z0-9_]*. Other annotations are silently skipped.
Example output
Given two registered web servers in the production cluster with a prometheus.io/scrape: "true" annotation:
CLI usage
The ccm apply and ccm ensure file commands can query the registration registry by passing the --registration flag with the name of the JetStream stream holding registration data.
Apply a manifest that uses registrations() in its templates:
Create a file whose content is generated from a template that queries registered services:
A resource can delegate to a template file that calls registrations():
Creating entries
The create subcommand publishes a single registration entry to the JetStream stream. This is useful for manually registering services or for integration with external provisioning tools.
All entry properties are specified as flags:
| Flag | Required | Default | Description |
|---|---|---|---|
--cluster | Yes | Logical cluster name | |
--service | Yes | Service name | |
--protocol | Yes | Protocol identifier | |
--address | Yes | IP address of the service endpoint | |
--port | Yes | Port number (1-65535) | |
--priority | No | 100 | Priority value (1-255, lower is higher) |
--ttl | No | 30s | Time-to-live for the entry |
-A/--annotation | No | Annotations as key=value, repeatable |
Add annotations by repeating the -A flag:
Removing entries
The rm subcommand (alias delete) purges a registration entry from the JetStream stream. All identifying fields must be specified to match the entry to remove.
The command prompts for confirmation before removing the entry. Use --force to skip the confirmation prompt:
| Flag | Required | Default | Description |
|---|---|---|---|
--cluster | Yes | Logical cluster name | |
--service | Yes | Service name | |
--protocol | Yes | Protocol identifier | |
--address | Yes | IP address of the service endpoint | |
--port | Yes | Port number (1-65535) | |
--force | No | false | Skip confirmation prompt |
Querying and watching
The ccm registration command provides tools for inspecting and monitoring registration data from the command line.
Both subcommands accept the same positional arguments for filtering: cluster, protocol, service, and address. All default to * (wildcard). The --context flag (env: NATS_CONTEXT) controls NATS authentication and defaults to CCM. The --registration (-R) flag sets the JetStream stream name and defaults to REGISTRATION.
Query
The query subcommand performs a point-in-time lookup of registered entries.
This returns all entries across all clusters, protocols, services, and addresses. Filter by positional arguments:
Output is a human-readable listing grouped by service and cluster. Machine-readable formats are available with --json or --yaml:
Results are sorted by cluster, then protocol, then service.
Watch
The watch subcommand subscribes to the registration stream and displays changes in real time. It runs until interrupted.
New and updated entries are logged as info-level messages. Entries removed by TTL expiry, deletion, or purge are logged as warnings including the removal reason.
Filter the watch to specific entries using the same positional arguments:
The --json flag outputs each event as a JSON object for integration with other tools.
Full example
This example shows two nodes working together. A web server registers itself, and a load balancer discovers all web servers to generate its configuration.
Web server node
Load balancer node
With a backends.cfg.jet template:
Each time the agent runs on the load balancer, it queries the registry for all web services and regenerates the HAProxy configuration. The exec resource reloads HAProxy when the configuration file changes.