Dynamic IP Allocation¶
saltext-nebula can assign each minion a stable Nebula overlay IP
automatically, so a new node joins the mesh without a hand-written
ip in pillar. Allocation is handled by the nebula_ipam external
pillar, which runs on the master and injects the address at
nebula:hosts:<minion_id>:ip. Because certificate signing and config
assembly already read the address from that exact location, nothing
downstream changes.
How it works¶
The ext_pillar assigns the lowest free address from a configured pool the first time it sees a minion, records it in a SQLite database on the master, and returns the same address on every subsequent pillar compile. The allocation is stable for the life of the node, which matters because changing it would invalidate the minion’s signed certificate.
Allocation is deliberately conservative:
A minion that already has an explicit
ipin pillar is left untouched. Static assignments keep working, and dynamic allocation is purely opt-in.Lighthouse overlay addresses, every statically assigned host IP, and any operator
reserveentries are excluded from the pool, so a dynamic address can never collide with a pinned one.Allocation runs inside a
BEGIN IMMEDIATESQLite transaction with aUNIQUEconstraint on the address. Parallel pillar compilations across master worker threads cannot hand out the same IP twice.A minion with no
nebulapillar at all is skipped entirely, so only mesh nodes are allocated addresses.
Configuration¶
Add the ext_pillar to the master config:
ext_pillar:
- nebula_ipam:
network: 10.10.10.0/24
pool: 10.10.10.10-10.10.10.250
store: /etc/nebula/ipam.sqlite
Option |
Description |
|---|---|
|
Overlay network in CIDR form (required). Its prefix length formats allocated addresses, e.g. |
|
Optional |
|
Path to the SQLite database. Default: |
|
Optional list of individual addresses to exclude. |
|
Exclude lighthouse overlay addresses found in pillar. Default: |
|
Top-level pillar key holding the Nebula config. Default: |
The injected ip merges into the minion’s existing host entry, so
keys such as groups and duration are preserved. This relies on
Salt’s default recursive pillar merge
(pillar_source_merging_strategy: smart, the default).
Minimal frictionless host¶
With the allocator configured, adding a node to the mesh can be as
little as assigning the shared nebula pillar via the top file. No
per-host block is required:
nebula:
hosts:
web01:
groups:
- web
web01 receives an overlay IP automatically. If a host does not
deviate from the global mesh definition, even the groups list can be
omitted and the node still gets an address and a working certificate.
Managing allocations¶
The runner exposes admin operations against the same store. The store
path defaults to nebula.ipam_store in the master config, which
should match the store set for the ext_pillar.
# List every allocation, ordered by address
salt-run nebula.ipam_list
# Show one minion's allocation
salt-run nebula.ipam_show minion_id=web01
# Release an allocation when decommissioning a node
salt-run nebula.ipam_release minion_id=web01
Releasing frees the address for reuse. Reusing an address while a previously issued certificate for it is still valid is risky, so release only after the old certificate has been revoked or has expired.
End-to-end: allocate, sign, deploy¶
Allocation is implicit in pillar compilation. nebula.get_certificate
calls pillar.show_pillar on the master, which runs the nebula_ipam
ext_pillar and assigns the address before the certificate is signed
with it. There is no separate “allocate” call to make: compiling the
minion’s pillar is what allocates, and every path that reads the
pillar triggers it.
For a single host the sequence is:
# Compiling the pillar assigns the address; show_config previews the result.
salt-run nebula.show_config minion_id=web01
salt-run nebula.ipam_show minion_id=web01 # confirm the assigned address
# Sign the certificate. This compiles the pillar again (idempotent: the same
# address is returned) and signs with it.
salt-run nebula.get_certificate minion_id=web01
# Deploy certificates, config and service to the minion.
salt web01 state.apply nebula
Orchestration¶
Initialize the CA once as a prerequisite:
salt-run nebula.ca_init
Then this orchestration allocates, signs and deploys across the whole
mesh. It targets minions by a roles:nebula grain; adjust the target
to however you assign the nebula pillar (grain, nodegroup, or pillar
match).
# /srv/salt/orch/nebula_deploy.sls
{% set nebula_minions = salt.saltutil.runner(
'manage.up', tgt='roles:nebula', tgt_type='grain') %}
# 1. Allocate an overlay IP (transparently, via the nebula_ipam ext_pillar) and
# sign each minion's certificate with it. get_certificate compiles the
# minion's pillar, which is what performs the allocation, so no separate
# allocate step is needed.
{% for minion in nebula_minions %}
nebula_cert_{{ minion }}:
salt.runner:
- name: nebula.get_certificate
- minion_id: {{ minion }}
{% endfor %}
# 2. Surface the resulting allocations for review before deploying.
nebula_ipam_report:
salt.runner:
- name: nebula.ipam_list
- require:
{% for minion in nebula_minions %}
- salt: nebula_cert_{{ minion }}
{% endfor %}
# 3. Deploy certificates, config and service to the minions.
nebula_deploy:
salt.state:
- tgt: 'roles:nebula'
- tgt_type: grain
- sls:
- nebula
- require:
- salt: nebula_ipam_report
# 4. Verify mesh connectivity.
nebula_verify:
salt.function:
- name: nebula.test_connectivity
- tgt: 'roles:nebula'
- tgt_type: grain
- require:
- salt: nebula_deploy
Run it with:
salt-run state.orchestrate orch.nebula_deploy
Because allocation is allocate-once, the orchestration is safe to
re-run: hosts that already hold an address keep it, and only genuinely
new minions are assigned one. A frictionless host that has only the
shared nebula pillar (no per-host block) is allocated an address and
signed on its first pass with no extra configuration.