An edge site runs a small NATS cluster in its own Kubernetes cluster. It joins the production hub as a leaf: local clients publish telemetry that reaches the hub’s telemetry account, and a local stream keeps accepting while the link is down. The leaf is not a supercluster member and has no auth plane of its own.
The hub #
The hub is story 9’s prod-east, whose NatsCluster gains a leafnode listener, and the edge site gets a user in the account its traffic belongs to, usable only as a leaf.
01-hub.yaml
# In the hub's Kubernetes cluster: prod-east as story 9 declares it, with a
# leafnode listener under `leafnodes`.
apiVersion: cluster.nats.mikluko.io/v1beta1
kind: NatsCluster
metadata:
name: prod-east
namespace: nats-system
spec:
version: 2.15.0
replicas: 5
resources:
requests:
cpu: "6"
memory: 29Gi
limits:
memory: 29Gi
jetstream:
# Overrides the memory store derived from limits.memory.
limits:
maxMemoryStore: 10Gi
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 500Gi
serverTags:
cluster: prod-east
region: us-east-2
auth:
trustRef:
name: acme
systemCredentials:
secretKeyRef:
name: cluster-controller-creds
gateway:
discovery: Explicit
remotes:
- name: prod-east
url: tls://nats.prod-east.acme.example:7222
- name: dev-east
url: tls://nats.dev-east.acme.example:7222
- name: prod-west
url: tls://nats.prod-west.acme.example:7222
tls:
certManager:
issuerRef:
kind: Issuer
name: nats-gateway-ca
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
external-dns.alpha.kubernetes.io/hostname: nats.prod-east.acme.example
advertise: nats.prod-east.acme.example:7222
leafnodes:
# Optional, as on every listener.
tls:
certManager:
issuerRef:
kind: ClusterIssuer
name: letsencrypt
service:
type: LoadBalancer
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: external
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
external-dns.alpha.kubernetes.io/hostname: leaf.prod-east.acme.example
advertise: leaf.prod-east.acme.example:7422
---
# What the edge site authenticates as: an ordinary user in the account its
# traffic lands in, usable only for a leafnode connection. Its Secret is
# carried to the edge by External Secrets, as the remote controllers' are.
apiVersion: auth.nats.mikluko.io/v1beta1
kind: NatsUser
metadata:
name: edge-site-1
namespace: nats-system
spec:
accountRef:
kind: NatsAccount
name: telemetry
# A leaf user narrowed to some of the account's subjects names the connection
# type beside its permissions; `preset: leafnode` alone is a leaf connection
# with full account access.
connectionTypes: [LEAFNODE]
permissions:
publish:
allow: ["telemetry.>"]
subscribe:
allow: ["telemetry.>", "_INBOX.>"]
credentials:
secretKeyRef:
name: edge-site-1-leaf-creds
---
# For a leaf under a NATS operator (01-edge-operator.yaml): the system-account
# leaf user its account lookups travel over.
apiVersion: auth.nats.mikluko.io/v1beta1
kind: NatsUser
metadata:
name: edge-site-2-system
namespace: nats-system
spec:
accountRef:
kind: NatsSystemAccount
name: sys
preset: leafnode
credentials:
secretKeyRef:
name: edge-site-2-system-leaf-creds
kubectl --context hub apply -f https://nats-operator.io/docs/stories/10-leafnodes/01-hub.yamlThe leaf #
A leaf is a NatsCluster that dials out through a NatsConnection, the same kind the JetStream controller uses. Its JetStream runs in a domain of its own. A NatsConnection in another namespace needs a NatsReferenceGrant there, and that grant hands the connection’s credentials to the leaf’s namespace, where the cluster controller copies them into the Secret <name>-leaf-remotes.
01-edge.yaml
# In the edge site's own Kubernetes cluster. No auth controller, no NATS
# operator: the leaf runs with no accounts, and everything its local clients
# publish reaches the hub in the account the remote's credentials sign into.
apiVersion: nats.mikluko.io/v1beta1
kind: NatsConnection
metadata:
name: hub
namespace: nats-system
spec:
servers: ["tls://leaf.prod-east.acme.example:7422"]
credentials:
secretKeyRef:
name: edge-site-1-leaf-creds
---
apiVersion: cluster.nats.mikluko.io/v1beta1
kind: NatsCluster
metadata:
name: edge-site-1
namespace: nats-system
spec:
version: 2.15.0
replicas: 3
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
memory: 1Gi
jetstream:
# Required with leafRemotes.
domain: edge-site-1
volumeClaimTemplate:
spec:
resources:
requests:
storage: 20Gi
# A leaf is a NatsCluster that dials out. This one sets no
# auth.systemCredentials, so any change to its remotes restarts its servers.
leafRemotes:
- connectionRef:
name: hub
# With no auth plane on the leaf there is one local account, the
# global one, and the remote binds it.
---
# A local buffer that keeps accepting while the link is down, and a source
# on the hub side is how it drains; that part is ordinary JetStream config
# in the hub's telemetry account, not shown.
apiVersion: nats.mikluko.io/v1beta1
kind: NatsConnection
metadata:
name: local
namespace: nats-system
spec:
servers: ["nats://edge-site-1.nats-system.svc:4222"]
---
apiVersion: jetstream.nats.mikluko.io/v1beta1
kind: NatsStream
metadata:
name: telemetry-buffer
namespace: nats-system
spec:
connectionRef:
name: local
name: TELEMETRY_BUFFER
subjects: ["telemetry.>"]
replicas: 3
maxAge: 72h
kubectl --context edge apply -f https://nats-operator.io/docs/stories/10-leafnodes/01-edge.yaml01-status-natscluster-edge-site-1.yaml
status:
observedGeneration: 1
conditions:
- type: Ready
status: "True"
reason: AllServersReady
- type: Settled
status: "True"
reason: AllGroupsCurrent
- type: LeafnodesConnected
status: "True"
reason: AllRemotesConnected
message: 3 of 3 servers connected to 1 remote
leafRemotes:
- connectionNamespace: nats-system
connectionName: hub
connected: 3
account: ATELEMETRY... # the hub account the credentials sign into
endpoints:
client: nats://edge-site-1.nats-system.svc:4222
A leaf that enforces the hub’s accounts #
A second site trusts the hub’s NATS operator, so its clients authenticate against the hub’s accounts locally. It reads the same trust roots the supercluster members do, and resolves accounts over a second remote bound to the system account; without that remote it cannot fetch an account it has not cached.
01-edge-operator.yaml
# A second edge site that enforces the hub's accounts locally: it trusts the
# hub's NATS operator, and its clients authenticate against the same accounts
# as the hub's. Still not a supercluster member.
#
# The same NatsOperatorTrust the supercluster members read, copied here by
# GitOps.
apiVersion: nats.mikluko.io/v1beta1
kind: NatsOperatorTrust
metadata:
name: acme
namespace: nats-system
spec:
operatorJWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ...
systemAccountJWT: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ...
---
# The creds are a hub user in the system account.
apiVersion: nats.mikluko.io/v1beta1
kind: NatsConnection
metadata:
name: hub-system
namespace: nats-system
spec:
servers: ["tls://leaf.prod-east.acme.example:7422"]
credentials:
secretKeyRef:
name: edge-site-2-system-leaf-creds
---
apiVersion: nats.mikluko.io/v1beta1
kind: NatsConnection
metadata:
name: hub-telemetry
namespace: nats-system
spec:
servers: ["tls://leaf.prod-east.acme.example:7422"]
credentials:
secretKeyRef:
name: edge-site-2-leaf-creds
---
apiVersion: cluster.nats.mikluko.io/v1beta1
kind: NatsCluster
metadata:
name: edge-site-2
namespace: nats-system
spec:
version: 2.15.0
replicas: 3
jetstream:
domain: edge-site-2
volumeClaimTemplate:
spec:
resources:
requests:
storage: 20Gi
auth:
trustRef:
name: acme
# resolver is omitted: a leaf that preloads accounts runs Full, on the
# JetStream volume; one that preloads nothing runs Cache. Memory is not
# offered.
leafRemotes:
- connectionRef:
name: hub-system
# The leaf's system account, known from trustRef.
localSystemAccount: true
- connectionRef:
name: hub-telemetry
localAccountTrustRef:
name: telemetry
---
# The account the telemetry remote binds to. In the home Kubernetes cluster
# this would be `accountRef: {name: telemetry}`, resolved from the live
# NatsAccount; here, with no auth controller, the literal form, copied by
# GitOps. `accountRef` and `publicKey` are exclusive.
apiVersion: nats.mikluko.io/v1beta1
kind: NatsAccountTrust
metadata:
name: telemetry
namespace: nats-system
spec:
publicKey: ATELEMETRY...
# Preloaded, so the account's clients authenticate while the leaf link is
# down or before it first connects. It expires with the account's jwtTTL
# (48h by default) unless the NatsAccount sets `jwtTTL: 0`.
jwt: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ...
kubectl --context edge apply -f https://nats-operator.io/docs/stories/10-leafnodes/01-edge-operator.yaml