Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:00:21 +08:00
commit 26377bd9be
20 changed files with 8845 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,997 @@
# NetBox Best Practices for Virgo-Core
**NetBox Version:** 4.3.0
**Audience:** Infrastructure automation engineers
Comprehensive best practices for using NetBox as the source of truth for the Matrix cluster infrastructure, including data organization, security, performance, and integration patterns.
---
## Table of Contents
- [Data Organization](#data-organization)
- [Naming Conventions](#naming-conventions)
- [IP Address Management](#ip-address-management)
- [Device Management](#device-management)
- [Virtualization](#virtualization)
- [Tagging Strategy](#tagging-strategy)
- [Security](#security)
- [Performance](#performance)
- [API Integration](#api-integration)
- [Automation Patterns](#automation-patterns)
- [Troubleshooting](#troubleshooting)
---
## Data Organization
### Hierarchical Structure
Follow this order when setting up infrastructure in NetBox:
```text
1. Sites → Create physical locations first
2. Prefixes → Define IP networks (IPAM)
3. VLANs → Network segmentation
4. Device Types → Hardware models
5. Device Roles → Purpose categories
6. Clusters → Virtualization clusters
7. Devices → Physical hardware
8. Interfaces → Network interfaces
9. IP Addresses → Assign IPs to interfaces
10. VMs → Virtual machines
```
**Why this order?**
- Parent objects must exist before children
- Avoids circular dependencies
- Enables atomic operations
### Site Organization
**✅ Good:**
```python
# One site per physical location
site = nb.dcim.sites.create(
name="Matrix Cluster",
slug="matrix",
description="3-node Proxmox VE cluster at home lab",
tags=[{"name": "production"}, {"name": "homelab"}]
)
```
**❌ Bad:**
```python
# Don't create separate sites for logical groupings
site_proxmox = nb.dcim.sites.create(name="Proxmox Nodes", ...)
site_vms = nb.dcim.sites.create(name="Virtual Machines", ...)
```
Use **device roles** and **tags** for logical grouping, not separate sites.
### Consistent Data Entry
**Required fields:** Always populate these
```python
device = nb.dcim.devices.create(
name="foxtrot", # ✅ Required
device_type=device_type.id, # ✅ Required
device_role=role.id, # ✅ Required
site=site.id, # ✅ Required
status="active", # ✅ Required
description="AMD Ryzen 9 9955HX", # ✅ Recommended
tags=[{"name": "proxmox-node"}] # ✅ Recommended
)
```
**Optional but recommended:**
- `description` - Hardware specs, purpose
- `tags` - For filtering and automation
- `comments` - Additional notes
- `custom_fields` - Serial numbers, purchase dates
---
## Naming Conventions
### Device Names
**✅ Use hostname only (no domain):**
```python
device = nb.dcim.devices.create(name="foxtrot", ...) # ✅ Good
device = nb.dcim.devices.create(name="foxtrot.spaceships.work", ...) # ❌ Bad
```
**Rationale:** Domain goes in DNS name field, not device name.
### Interface Names
**✅ Match actual OS interface names:**
```python
# Linux
interface = nb.dcim.interfaces.create(name="enp1s0f0", ...) # ✅ Good
# Not generic names
interface = nb.dcim.interfaces.create(name="eth0", ...) # ❌ Bad (unless actually eth0)
```
**Why?** Enables automation that references interfaces by name.
### DNS Naming Convention
Follow the Matrix cluster pattern: **`<service>-<number>-<purpose>.<domain>`**
```python
# ✅ Good examples
dns_name="docker-01-nexus.spaceships.work"
dns_name="k8s-01-master.spaceships.work"
dns_name="proxmox-foxtrot-mgmt.spaceships.work"
# ❌ Bad examples
dns_name="server1.spaceships.work" # Not descriptive
dns_name="nexus.spaceships.work" # Missing number
dns_name="DOCKER-01.spaceships.work" # Uppercase not allowed
```
See [../workflows/naming-conventions.md](../workflows/naming-conventions.md) for complete rules.
### Slugs
**✅ Lowercase with hyphens:**
```python
site = nb.dcim.sites.create(slug="matrix", ...) # ✅ Good
site = nb.dcim.sites.create(slug="Matrix_Cluster", ...) # ❌ Bad
```
**Pattern:** `^[a-z0-9-]+$`
---
## IP Address Management
### Plan IP Hierarchy
**Matrix cluster example:**
```text
192.168.0.0/16 (Home network supernet)
├── 192.168.3.0/24 (Management)
│ ├── 192.168.3.1 (Gateway)
│ ├── 192.168.3.5-7 (Proxmox nodes)
│ ├── 192.168.3.10+ (VMs)
│ └── 192.168.3.200+ (Reserved for future)
├── 192.168.5.0/24 (CEPH Public, MTU 9000)
├── 192.168.7.0/24 (CEPH Private, MTU 9000)
└── 192.168.8.0/24 (Corosync, VLAN 9)
```
### Use Prefix Roles
Create roles for clarity:
```python
# Create roles
role_mgmt = nb.ipam.roles.create(name='Management', slug='management')
role_storage = nb.ipam.roles.create(name='Storage', slug='storage')
role_cluster = nb.ipam.roles.create(name='Cluster', slug='cluster')
# Apply to prefixes
prefix = nb.ipam.prefixes.create(
prefix='192.168.3.0/24',
role=role_mgmt.id,
description='Management network for Matrix cluster'
)
```
### Reserve Important IPs
**✅ Explicitly reserve gateway, broadcast, network addresses:**
```python
# Gateway
gateway = nb.ipam.ip_addresses.create(
address='192.168.3.1/24',
status='active',
role='anycast',
description='Management network gateway'
)
# DNS servers
dns1 = nb.ipam.ip_addresses.create(
address='192.168.3.2/24',
status='reserved',
description='Primary DNS server'
)
```
### Use Prefixes as IP Pools
**✅ Enable automatic IP assignment:**
```python
prefix = nb.ipam.prefixes.create(
prefix='192.168.3.0/24',
is_pool=True, # ✅ Allow automatic IP assignment
...
)
# Get next available IP
ip = prefix.available_ips.create(dns_name='docker-02.spaceships.work')
```
**❌ Don't manually track available IPs** - let NetBox do it.
### IP Status Values
Use appropriate status:
| Status | Use Case |
|--------|----------|
| `active` | Currently in use |
| `reserved` | Reserved for specific purpose |
| `deprecated` | Planned for decommission |
| `dhcp` | Managed by DHCP server |
```python
# Production VM
ip = nb.ipam.ip_addresses.create(address='192.168.3.10/24', status='active')
# Future expansion
ip = nb.ipam.ip_addresses.create(address='192.168.3.50/24', status='reserved')
```
### VRF for Isolation
**Use VRFs for true isolation:**
```python
# Management VRF (enforce unique IPs)
vrf_mgmt = nb.ipam.vrfs.create(
name='management',
enforce_unique=True,
description='Management network VRF'
)
# Lab VRF (allow overlapping IPs)
vrf_lab = nb.ipam.vrfs.create(
name='lab',
enforce_unique=False,
description='Lab/testing VRF'
)
```
**When to use VRFs:**
- Multiple environments (prod, dev, lab)
- Overlapping IP ranges
- Security isolation
---
## Device Management
### Create Device Types First
**✅ Always create device type before devices:**
```python
# 1. Create manufacturer
manufacturer = nb.dcim.manufacturers.get(slug='minisforum')
if not manufacturer:
manufacturer = nb.dcim.manufacturers.create(
name='MINISFORUM',
slug='minisforum'
)
# 2. Create device type
device_type = nb.dcim.device_types.create(
manufacturer=manufacturer.id,
model='MS-A2',
slug='ms-a2',
u_height=0, # Not rack mounted
is_full_depth=False
)
# 3. Create device
device = nb.dcim.devices.create(
name='foxtrot',
device_type=device_type.id,
...
)
```
### Use Device Roles Consistently
**Create specific roles:**
```python
roles = [
('Proxmox Node', 'proxmox-node', '2196f3'), # Blue
('Docker Host', 'docker-host', '4caf50'), # Green
('K8s Master', 'k8s-master', 'ff9800'), # Orange
('K8s Worker', 'k8s-worker', 'ffc107'), # Amber
('Storage', 'storage', '9c27b0'), # Purple
]
for name, slug, color in roles:
nb.dcim.device_roles.create(
name=name,
slug=slug,
color=color,
vm_role=True # If role applies to VMs too
)
```
**✅ Consistent naming helps automation:**
```python
# Get all Proxmox nodes
proxmox_nodes = nb.dcim.devices.filter(role='proxmox-node')
# Get all Kubernetes workers
k8s_workers = nb.virtualization.virtual_machines.filter(role='k8s-worker')
```
### Always Set Primary IP
**✅ Set primary IP after creating device and IPs:**
```python
# Create device
device = nb.dcim.devices.create(name='foxtrot', ...)
# Create interface
iface = nb.dcim.interfaces.create(device=device.id, name='enp2s0', ...)
# Create IP
ip = nb.ipam.ip_addresses.create(
address='192.168.3.5/24',
assigned_object_type='dcim.interface',
assigned_object_id=iface.id
)
# ✅ Set as primary (critical for automation!)
device.primary_ip4 = ip.id
device.save()
```
**Why?** Primary IP is used by:
- Ansible dynamic inventory
- Monitoring tools
- DNS automation
### Document Interfaces
**✅ Include descriptions:**
```python
# Management
mgmt = nb.dcim.interfaces.create(
device=device.id,
name='enp2s0',
type='2.5gbase-t',
mtu=1500,
description='Management interface (vmbr0)',
tags=[{'name': 'management'}]
)
# CEPH public
ceph_pub = nb.dcim.interfaces.create(
device=device.id,
name='enp1s0f0',
type='10gbase-x-sfpp',
mtu=9000,
description='CEPH public network (vmbr1)',
tags=[{'name': 'ceph-public'}, {'name': 'jumbo-frames'}]
)
```
---
## Virtualization
### Create Cluster First
**✅ Create cluster before VMs:**
```python
# 1. Get/create cluster type
cluster_type = nb.virtualization.cluster_types.get(slug='proxmox')
if not cluster_type:
cluster_type = nb.virtualization.cluster_types.create(
name='Proxmox VE',
slug='proxmox'
)
# 2. Create cluster
cluster = nb.virtualization.clusters.create(
name='Matrix',
type=cluster_type.id,
site=site.id,
description='3-node Proxmox VE 9.x cluster'
)
# 3. Create VMs in cluster
vm = nb.virtualization.virtual_machines.create(
name='docker-01',
cluster=cluster.id,
...
)
```
### Standardize VM Sizing
**✅ Use consistent resource allocations:**
| Role | vCPUs | Memory (MB) | Disk (GB) |
|------|-------|-------------|-----------|
| Small (dev) | 2 | 2048 | 20 |
| Medium (app) | 4 | 8192 | 100 |
| Large (database) | 8 | 16384 | 200 |
| XL (compute) | 16 | 32768 | 500 |
```python
VM_SIZES = {
'small': {'vcpus': 2, 'memory': 2048, 'disk': 20},
'medium': {'vcpus': 4, 'memory': 8192, 'disk': 100},
'large': {'vcpus': 8, 'memory': 16384, 'disk': 200},
}
# Create VM with standard size
vm = nb.virtualization.virtual_machines.create(
name='docker-01',
cluster=cluster.id,
**VM_SIZES['medium']
)
```
### VM Network Configuration
**✅ Complete network setup:**
```python
# 1. Create VM
vm = nb.virtualization.virtual_machines.create(...)
# 2. Create interface
vm_iface = nb.virtualization.interfaces.create(
virtual_machine=vm.id,
name='eth0',
type='virtual',
enabled=True,
mtu=1500
)
# 3. Assign IP from pool
prefix = nb.ipam.prefixes.get(prefix='192.168.3.0/24')
vm_ip = prefix.available_ips.create(
dns_name='docker-01-nexus.spaceships.work',
assigned_object_type='virtualization.vminterface',
assigned_object_id=vm_iface.id,
tags=[{'name': 'production-dns'}] # ✅ Triggers PowerDNS sync
)
# 4. Set as primary IP
vm.primary_ip4 = vm_ip.id
vm.save()
```
---
## Tagging Strategy
### Tag Categories
Organize tags by purpose:
**Infrastructure Type:**
- `proxmox-node`, `ceph-node`, `docker-host`, `k8s-master`, `k8s-worker`
**Environment:**
- `production`, `staging`, `development`, `lab`
**DNS Automation:**
- `production-dns`, `lab-dns` (triggers PowerDNS sync)
**Management:**
- `terraform`, `ansible`, `manual`
**Networking:**
- `management`, `ceph-public`, `ceph-private`, `jumbo-frames`
### Tag Naming Convention
**✅ Lowercase with hyphens:**
```python
tags = [
{'name': 'proxmox-node'}, # ✅ Good
{'name': 'production-dns'}, # ✅ Good
{'name': 'Proxmox Node'}, # ❌ Bad (spaces, capitals)
{'name': 'production_dns'}, # ❌ Bad (underscores)
]
```
### Apply Tags Consistently
**✅ Tag at multiple levels:**
```python
# Tag device
device = nb.dcim.devices.create(
name='foxtrot',
tags=[{'name': 'proxmox-node'}, {'name': 'ceph-node'}, {'name': 'production'}]
)
# Tag interface
iface = nb.dcim.interfaces.create(
device=device.id,
name='enp1s0f0',
tags=[{'name': 'ceph-public'}, {'name': 'jumbo-frames'}]
)
# Tag IP
ip = nb.ipam.ip_addresses.create(
address='192.168.3.5/24',
tags=[{'name': 'production-dns'}, {'name': 'terraform'}]
)
```
**Why?** Enables granular filtering:
```bash
# Get all CEPH nodes
ansible-playbook -i netbox-inventory.yml setup-ceph.yml --limit tag_ceph_node
# Get all production DNS-enabled IPs
ips = nb.ipam.ip_addresses.filter(tag='production-dns')
```
---
## Security
### API Token Management
**✅ Store tokens in Infisical (Virgo-Core standard):**
```python
from infisical import InfisicalClient
def get_netbox_token() -> str:
"""Get NetBox API token from Infisical."""
client = InfisicalClient()
secret = client.get_secret(
secret_name="NETBOX_API_TOKEN",
project_id="7b832220-24c0-45bc-a5f1-ce9794a31259",
environment="prod",
path="/matrix"
)
return secret.secret_value
# Use token
nb = pynetbox.api('https://netbox.spaceships.work', token=get_netbox_token())
```
**❌ Never hardcode tokens:**
```python
# ❌ NEVER DO THIS
token = "a1b2c3d4e5f6..."
nb = pynetbox.api(url, token=token)
```
### Use Minimal Permissions
Create tokens with appropriate scopes:
| Use Case | Permissions |
|----------|-------------|
| Read-only queries | Read only |
| Terraform automation | Read + Write (DCIM, IPAM, Virtualization) |
| Full automation | Read + Write (all) |
| Emergency admin | Full access |
**✅ Create separate tokens for different purposes:**
```text
NETBOX_API_TOKEN_READONLY → Read-only queries
NETBOX_API_TOKEN_TERRAFORM → Terraform automation
NETBOX_API_TOKEN_ANSIBLE → Ansible dynamic inventory
```
### HTTPS Only
**✅ Always use HTTPS in production:**
```python
# ✅ Production
nb = pynetbox.api('https://netbox.spaceships.work', token=token)
# ❌ Never HTTP in production
nb = pynetbox.api('http://netbox.spaceships.work', token=token)
```
**For self-signed certs (dev/lab only):**
```python
# ⚠️ Dev/testing only
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
nb = pynetbox.api(
'https://netbox.local',
token=token,
ssl_verify=False # Only for self-signed certs in lab
)
```
### Rotate Tokens Regularly
**Best practice:** Rotate every 90 days
```bash
# 1. Create new token in NetBox UI
# 2. Update Infisical secret
infisical secrets set NETBOX_API_TOKEN="new-token-here"
# 3. Test new token
./tools/netbox_api_client.py sites list
# 4. Delete old token in NetBox UI
```
### Audit API Usage
**✅ Log API calls in production:**
```python
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='/var/log/netbox-api.log'
)
logger = logging.getLogger(__name__)
def audit_api_call(action: str, resource: str, details: dict):
"""Log API calls for security audit."""
logger.info(f"API Call: {action} {resource} - User: {os.getenv('USER')} - {details}")
# Usage
ip = nb.ipam.ip_addresses.create(address='192.168.1.1/24')
audit_api_call('CREATE', 'ip-address', {'address': '192.168.1.1/24'})
```
---
## Performance
### Use Filtering Server-Side
**✅ Filter on server:**
```python
# ✅ Efficient: Server filters results
devices = nb.dcim.devices.filter(site='matrix', status='active')
```
**❌ Don't filter client-side:**
```python
# ❌ Inefficient: Downloads all devices then filters
all_devices = nb.dcim.devices.all()
matrix_devices = [d for d in all_devices if d.site.slug == 'matrix']
```
### Request Only Needed Fields
**✅ Use field selection:**
```python
# Get only specific fields
devices = nb.dcim.devices.filter(site='matrix', fields=['name', 'primary_ip4'])
```
### Use Pagination for Large Datasets
**✅ Process in batches:**
```python
# Paginate automatically
for device in nb.dcim.devices.filter(site='matrix'):
process_device(device) # pynetbox handles pagination
# Manual pagination for control
page_size = 100
offset = 0
while True:
devices = nb.dcim.devices.filter(limit=page_size, offset=offset)
if not devices:
break
for device in devices:
process_device(device)
offset += page_size
```
### Cache Lookups
**✅ Cache static data:**
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_site(site_slug: str):
"""Cached site lookup."""
return nb.dcim.sites.get(slug=site_slug)
@lru_cache(maxsize=256)
def get_device_type(slug: str):
"""Cached device type lookup."""
return nb.dcim.device_types.get(slug=slug)
```
### Use Bulk Operations
**✅ Bulk create is faster:**
```python
# ✅ Fast: Bulk create
ips = [
{'address': f'192.168.3.{i}/24', 'status': 'active'}
for i in range(10, 20)
]
nb.ipam.ip_addresses.create(ips)
# ❌ Slow: Loop with individual creates
for i in range(10, 20):
nb.ipam.ip_addresses.create(address=f'192.168.3.{i}/24', status='active')
```
---
## API Integration
### Error Handling
**✅ Always handle errors:**
```python
import pynetbox
from requests.exceptions import HTTPError
try:
device = nb.dcim.devices.get(name='foxtrot')
if not device:
console.print("[yellow]Device not found[/yellow]")
return None
except HTTPError as e:
if e.response.status_code == 404:
console.print("[red]Resource not found[/red]")
elif e.response.status_code == 403:
console.print("[red]Permission denied[/red]")
else:
console.print(f"[red]HTTP Error: {e}[/red]")
sys.exit(1)
except pynetbox.RequestError as e:
console.print(f"[red]NetBox API Error: {e.error}[/red]")
sys.exit(1)
except Exception as e:
console.print(f"[red]Unexpected error: {e}[/red]")
sys.exit(1)
```
### Validate Before Creating
**✅ Validate input before API calls:**
```python
import ipaddress
import re
def validate_ip(ip_str: str) -> bool:
"""Validate IP address format."""
try:
ipaddress.ip_interface(ip_str)
return True
except ValueError:
return False
def validate_dns_name(name: str) -> bool:
"""Validate DNS naming convention."""
pattern = r'^[a-z0-9-]+-\d{2}-[a-z0-9-]+\.[a-z0-9.-]+$'
return bool(re.match(pattern, name))
# Use before API calls
if not validate_ip(ip_address):
raise ValueError(f"Invalid IP address: {ip_address}")
if not validate_dns_name(dns_name):
raise ValueError(f"Invalid DNS name: {dns_name}")
ip = nb.ipam.ip_addresses.create(address=ip_address, dns_name=dns_name)
```
### Check Before Create
**✅ Check existence before creating:**
```python
# Check if device exists
device = nb.dcim.devices.get(name='foxtrot')
if device:
console.print("[yellow]Device already exists, updating...[/yellow]")
device.status = 'active'
device.save()
else:
console.print("[green]Creating new device...[/green]")
device = nb.dcim.devices.create(name='foxtrot', ...)
```
---
## Automation Patterns
### Idempotent Operations
**✅ Design operations to be safely re-run:**
```python
def ensure_vm_exists(name: str, cluster: str, **kwargs) -> pynetbox.core.response.Record:
"""Ensure VM exists (idempotent)."""
# Check if exists
vm = nb.virtualization.virtual_machines.get(name=name)
if vm:
# Update if needed
updated = False
for key, value in kwargs.items():
if getattr(vm, key) != value:
setattr(vm, key, value)
updated = True
if updated:
vm.save()
console.print(f"[yellow]Updated VM: {name}[/yellow]")
else:
console.print(f"[green]VM unchanged: {name}[/green]")
return vm
else:
# Create new
vm = nb.virtualization.virtual_machines.create(
name=name,
cluster=nb.virtualization.clusters.get(name=cluster).id,
**kwargs
)
console.print(f"[green]Created VM: {name}[/green]")
return vm
```
### Terraform Integration
See [terraform-provider-guide.md](terraform-provider-guide.md) for complete examples.
**Key pattern:**
```hcl
# Use NetBox as data source
data "netbox_prefix" "management" {
prefix = "192.168.3.0/24"
}
# Create IP in NetBox via Terraform
resource "netbox_ip_address" "vm_ip" {
ip_address = cidrhost(data.netbox_prefix.management.prefix, 10)
dns_name = "docker-01-nexus.spaceships.work"
status = "active"
tags = ["terraform", "production-dns"]
}
```
### Ansible Dynamic Inventory
See [../workflows/ansible-dynamic-inventory.md](../workflows/ansible-dynamic-inventory.md).
**Key pattern:**
```yaml
# netbox-dynamic-inventory.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
group_by:
- device_roles
- tags
- site
```
---
## Troubleshooting
### Common Issues
**Problem:** "Permission denied" errors
**Solution:** Check API token permissions
```bash
# Test token
curl -H "Authorization: Token YOUR_TOKEN" \
https://netbox.spaceships.work/api/
```
**Problem:** IP not syncing to PowerDNS
**Solution:** Check tags
```python
# IP must have tag matching zone rules
ip = nb.ipam.ip_addresses.get(address='192.168.3.10/24')
print(f"Tags: {[tag.name for tag in ip.tags]}")
# Must include 'production-dns' or matching tag
```
**Problem:** Slow API queries
**Solution:** Use filtering and pagination
```python
# ❌ Slow
all_devices = nb.dcim.devices.all()
# ✅ Fast
devices = nb.dcim.devices.filter(site='matrix', limit=50)
```
### Debug Mode
**Enable verbose logging:**
```python
import logging
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
# Now pynetbox will log all API calls
nb = pynetbox.api('https://netbox.spaceships.work', token=token)
devices = nb.dcim.devices.all()
```
---
## Related Documentation
- [NetBox API Guide](netbox-api-guide.md) - Complete API reference
- [NetBox Data Models](netbox-data-models.md) - Data model relationships
- [DNS Naming Conventions](../workflows/naming-conventions.md) - Naming rules
- [Terraform Provider Guide](terraform-provider-guide.md) - Terraform integration
- [Tools: netbox_api_client.py](../tools/netbox_api_client.py) - Working examples
---
**Next:** Review [API Integration Patterns](netbox-api-guide.md#api-integration)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
# NetBox PowerDNS Sync Plugin Reference
*Source: <https://github.com/ArnesSI/netbox-powerdns-sync*>
## Overview
A NetBox plugin that automatically generates DNS records in PowerDNS based on NetBox IP Address and Device objects.
## Features
- Automatically generates A, AAAA & PTR records
- Manages multiple DNS Zones across multiple PowerDNS servers
- Flexible rules to match NetBox IP Addresses into DNS zones
- Multiple options to generate DNS names from IP address or Device
- Scheduled sync of DNS zones
- Can add DNS records for new zones immediately
- Per-zone synchronization schedule
## DNS Name Generation
### Zone Matching Priority
When determining the zone for an IP Address, match rules are evaluated in this order:
1. Check if `IPAddress.dns_name` matches any zone
2. Check if IPAddress is assigned to Device/VirtualMachine and if its name matches any zone
3. Check if IPAddress is assigned to FHRPGroup and if its name matches any zone
4. Try to match based on assigned tags (in order):
- `IPAddress.tags`
- `Interface.tags`
- `VMInterface.tags`
- `Device.tags`
- `VirtualMachine.tags`
- `Device.device_role`
- `VM.role`
5. Use default zone if configured
### Name Generation Methods
Each zone can use multiple naming methods (tried in order):
1. **IP naming method** - Generate name from IP address
2. **Device naming method** - Generate name from Device/VirtualMachine
3. **FHRP group method** - Generate name from FHRP Group
## Installation
### Via pip
```bash
# Activate NetBox virtual environment
source /opt/netbox/venv/bin/activate
# Install plugin
pip install netbox-powerdns-sync
```
### From GitHub
```bash
pip install git+https://github.com/ArnesSI/netbox-powerdns-sync.git@master
```
### Configuration
Add to `/opt/netbox/netbox/netbox/configuration.py`:
```python
PLUGINS = [
'netbox_powerdns_sync'
]
PLUGINS_CONFIG = {
"netbox_powerdns_sync": {
"ttl_custom_field": "",
"powerdns_managed_record_comment": "netbox-powerdns-sync",
"post_save_enabled": False,
},
}
```
### Apply Migrations
```bash
cd /opt/netbox/netbox/
python3 manage.py migrate
python3 manage.py reindex --lazy
```
## Configuration Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `ttl_custom_field` | `None` | Name of NetBox Custom field applied to IP Address objects for TTL |
| `powerdns_managed_record_comment` | `"netbox-powerdns-sync"` | Plugin only touches records with this comment. Set to `None` to manage all records |
| `post_save_enabled` | `False` | Immediately create DNS records when creating/updating IP Address, Device, or FHRP Group |
### Custom TTL Field
To set TTL per DNS record:
1. Create NetBox Custom Field:
- Type: Integer
- Apply to: IP Address objects
- Name: e.g., "dns_ttl"
2. Set in plugin config:
```python
"ttl_custom_field": "dns_ttl"
```
3. Set TTL value on IP Address objects in NetBox
## Compatibility
| NetBox Version | Plugin Version |
|---------------|----------------|
| 3.5.0-7 | 0.0.1 - 0.0.6 |
| 3.5.8 | 0.0.7 |
| 3.6.x | 0.8.0 |
## Usage Workflow
### 1. Configure DNS Zones in NetBox
Create zones in the plugin interface with:
- Zone name (e.g., `spaceships.work`)
- PowerDNS server connection
- Tag matching rules
- DNS name generation method
### 2. Tag Resources
Apply tags to IP Addresses, Devices, or Interfaces to match zones:
```python
# Example: Tag IP for specific zone
ipaddress.tags.add("production-dns")
```
### 3. Schedule Sync
Configure sync schedule for each zone:
- Immediate (on save)
- Scheduled (cron-style)
- Manual only
### 4. Monitor Sync Results
View sync results in NetBox:
- Records created
- Records updated
- Records deleted
- Sync errors
## Best Practices
### DNS Naming Conventions
For homelab naming like `docker-01-nexus.spaceships.work`:
1. Use Device name as base: `docker-01-nexus`
2. Zone maps to domain: `spaceships.work`
3. Set device naming method in zone config
### Tag Organization
```python
# Production resources
tags: ["production", "dns-auto"]
# Development resources
tags: ["development", "dns-dev"]
```
### TTL Strategy
- Default TTL in zone: 300 (5 minutes)
- Override with custom field for specific records
- Longer TTL for stable infrastructure (3600)
- Shorter TTL for dynamic services (60-300)
### PowerDNS Server Management
- Configure multiple PowerDNS servers for HA
- Use different servers for different zones
- Monitor PowerDNS API connectivity
## Integration Patterns
### With Terraform
Use NetBox as data source, sync DNS automatically:
```hcl
# Terraform creates resource in NetBox
resource "netbox_ip_address" "server" {
ip_address = "192.168.1.100/24"
dns_name = "docker-01-nexus"
tags = ["production-dns"]
}
# Plugin automatically creates DNS in PowerDNS
# A record: docker-01-nexus.spaceships.work -> 192.168.1.100
# PTR record: 100.1.168.192.in-addr.arpa -> docker-01-nexus.spaceships.work
```
### With Ansible
Use NetBox dynamic inventory with automatic DNS:
```yaml
---
# Ansible creates VM in Proxmox
- name: Create VM
proxmox_kvm:
name: docker-01-nexus
# ... vm config ...
# Add to NetBox via API
- name: Register in NetBox
netbox.netbox.netbox_ip_address:
data:
address: "192.168.1.100/24"
dns_name: "docker-01-nexus"
tags:
- production-dns
# DNS records created automatically by plugin
```
## Troubleshooting
### Records Not Syncing
1. Check zone matching rules
2. Verify tags applied correctly
3. Check PowerDNS API connectivity
4. Review sync results for errors
### Duplicate Records
If `powerdns_managed_record_comment` is `None`, plugin manages ALL records. Set a comment to limit scope:
```python
"powerdns_managed_record_comment": "netbox-managed"
```
### Performance Issues
- Disable `post_save_enabled` for large environments
- Use scheduled sync instead
- Batch changes before sync
### Name Generation Not Working
1. Check zone name generation method configuration
2. Verify Device/IP naming follows expected pattern
3. Test with manual sync first
## API Endpoints
Plugin adds REST API endpoints:
- `/api/plugins/netbox-powerdns-sync/zones/` - List/manage zones
- `/api/plugins/netbox-powerdns-sync/servers/` - PowerDNS servers
- `/api/plugins/netbox-powerdns-sync/sync-results/` - Sync history
## Example Configuration
### Zone for Production
```python
zone_config = {
"name": "spaceships.work",
"server": powerdns_server_prod,
"default_ttl": 300,
"naming_methods": ["device", "ip"],
"tag_match": ["production-dns"],
"auto_sync": True,
"sync_schedule": "*/15 * * * *" # Every 15 minutes
}
```
### Zone for Lab
```python
zone_config = {
"name": "lab.spaceships.work",
"server": powerdns_server_dev,
"default_ttl": 60,
"naming_methods": ["ip", "device"],
"tag_match": ["lab-dns"],
"auto_sync": False # Manual sync only
}
```

View File

@@ -0,0 +1,429 @@
# Terraform NetBox Provider Guide
*Source: <https://registry.terraform.io/providers/e-breuninger/netbox/latest/docs*>
## Overview
The Terraform NetBox provider enables full lifecycle management of NetBox resources using Infrastructure as Code.
## Version Compatibility
| NetBox Version | Provider Version |
|---------------|------------------|
| v4.3.0 - 4.4.0 | v5.0.0 and up |
| v4.2.2 - 4.2.9 | v4.0.0 - 4.3.1 |
| v4.1.0 - 4.1.11 | v3.10.0 - 3.11.1 |
| v4.0.0 - 4.0.11 | v3.9.0 - 3.9.2 |
| v3.7.0 - 3.7.8 | v3.8.0 - 3.8.9 |
| v3.6.0 - 3.6.9 | v3.7.0 - 3.7.7 |
| v3.5.1 - 3.5.9 | v3.6.x |
**Important**: NetBox makes breaking API changes even in non-major releases. Match provider version to NetBox version.
## Provider Configuration
### Basic Setup
```hcl
terraform {
required_providers {
netbox = {
source = "e-breuninger/netbox"
version = "~> 5.0.0" # Match your NetBox version
}
}
}
provider "netbox" {
server_url = "https://netbox.spaceships.work"
api_token = var.netbox_api_token
}
```
### Environment Variables
Configure via environment instead of hard-coding:
```bash
export NETBOX_SERVER_URL="https://netbox.spaceships.work"
export NETBOX_API_TOKEN="your-api-token-here"
```
```hcl
# Provider auto-reads from environment
provider "netbox" {}
```
## Configuration Schema
### Required
- `api_token` (String) - NetBox API authentication token
- Environment: `NETBOX_API_TOKEN`
- `server_url` (String) - NetBox server URL (with scheme and port)
- Environment: `NETBOX_SERVER_URL`
### Optional
- `allow_insecure_https` (Boolean) - Allow invalid certificates
- Environment: `NETBOX_ALLOW_INSECURE_HTTPS`
- Default: `false`
- `ca_cert_file` (String) - Path to PEM-encoded CA certificate
- Environment: `NETBOX_CA_CERT_FILE`
- `default_tags` (Set of String) - Tags added to every resource
- Useful for tracking Terraform-managed resources
- `headers` (Map of String) - Custom headers for all requests
- Environment: `NETBOX_HEADERS`
- `request_timeout` (Number) - HTTP request timeout (seconds)
- Environment: `NETBOX_REQUEST_TIMEOUT`
- `skip_version_check` (Boolean) - Skip NetBox version validation
- Environment: `NETBOX_SKIP_VERSION_CHECK`
- Default: `false`
- Useful for: Testing, unsupported versions
- `strip_trailing_slashes_from_url` (Boolean) - Auto-fix URL format
- Environment: `NETBOX_STRIP_TRAILING_SLASHES_FROM_URL`
- Default: `true`
## Usage Examples
### Create IP Address
```hcl
resource "netbox_ip_address" "server_ip" {
ip_address = "192.168.1.100/24"
dns_name = "docker-01-nexus.spaceships.work"
status = "active"
description = "Docker host - Nexus container registry"
tags = [
"terraform",
"production",
"dns-auto"
]
}
```
### Create Device
```hcl
resource "netbox_device" "proxmox_node" {
name = "foxtrot"
device_type = netbox_device_type.minisforum_ms_a2.id
role = netbox_device_role.hypervisor.id
site = netbox_site.homelab.id
primary_ip4 = netbox_ip_address.foxtrot_mgmt.id
tags = [
"terraform",
"proxmox",
"cluster-matrix"
]
comments = "Proxmox node in Matrix cluster - AMD Ryzen 9 9955HX"
}
```
### Create Prefix
```hcl
resource "netbox_prefix" "vlan_30_mgmt" {
prefix = "192.168.3.0/24"
vlan = netbox_vlan.management.id
status = "active"
description = "Management network for Proxmox cluster"
tags = [
"terraform",
"mgmt-network"
]
}
```
### Create VLAN
```hcl
resource "netbox_vlan" "management" {
vid = 30
name = "MGMT"
site = netbox_site.homelab.id
status = "active"
description = "Management VLAN for infrastructure"
tags = ["terraform"]
}
```
## Integration Patterns
### With Proxmox Provider
```hcl
# Create VM in Proxmox
resource "proxmox_vm_qemu" "docker_host" {
name = "docker-01-nexus"
target_node = "foxtrot"
# ... vm config ...
}
# Register in NetBox
resource "netbox_ip_address" "docker_host_ip" {
ip_address = "192.168.1.100/24"
dns_name = "${proxmox_vm_qemu.docker_host.name}.spaceships.work"
description = "Docker host for Nexus registry"
tags = [
"terraform",
"production-dns",
"docker-host"
]
}
# DNS record auto-created by netbox-powerdns-sync plugin
```
### Data Sources
Query existing NetBox data:
```hcl
# Get all production IPs
data "netbox_ip_addresses" "production" {
filter {
name = "tag"
value = "production"
}
}
# Get device details
data "netbox_device" "proxmox_node" {
name = "foxtrot"
}
# Use in other resources
resource "proxmox_vm_qemu" "new_vm" {
target_node = data.netbox_device.proxmox_node.name
# ... config ...
}
```
### Dynamic Inventory for Ansible
```hcl
# Export NetBox data for Ansible
output "ansible_inventory" {
value = {
for device in data.netbox_devices.all.devices :
device.name => {
ansible_host = device.primary_ip4_address
device_role = device.role
site = device.site
tags = device.tags
}
}
}
```
Save to file:
```bash
terraform output -json ansible_inventory > inventory.json
```
## Best Practices
### 1. Use Default Tags
Track all Terraform-managed resources:
```hcl
provider "netbox" {
server_url = var.netbox_url
api_token = var.netbox_token
default_tags = ["terraform", "iac"]
}
```
### 2. Organize with Modules
```hcl
module "vm_network" {
source = "./modules/netbox-vm"
vm_name = "docker-01"
ip_address = "192.168.1.100/24"
vlan_id = 30
dns_zone = "spaceships.work"
}
```
### 3. Use Variables for Secrets
Never hard-code tokens:
```hcl
variable "netbox_api_token" {
description = "NetBox API token"
type = string
sensitive = true
}
```
### 4. State Management
Use remote state for team collaboration:
```hcl
terraform {
backend "s3" {
bucket = "terraform-state"
key = "netbox/terraform.tfstate"
region = "us-east-1"
}
}
```
### 5. Version Pinning
Pin provider version to prevent breaking changes:
```hcl
terraform {
required_providers {
netbox = {
source = "e-breuninger/netbox"
version = "= 5.0.0" # Exact version
}
}
}
```
## Common Workflows
### 1. VM Provisioning Workflow
```hcl
# 1. Reserve IP in NetBox
resource "netbox_ip_address" "vm_ip" {
ip_address = "192.168.1.100/24"
dns_name = "app-server.spaceships.work"
status = "reserved"
description = "Reserved for new application server"
}
# 2. Create VM in Proxmox
resource "proxmox_vm_qemu" "app_server" {
# ... config using netbox_ip_address.vm_ip.ip_address ...
}
# 3. Mark IP as active
resource "netbox_ip_address" "vm_ip_active" {
ip_address = netbox_ip_address.vm_ip.ip_address
status = "active" # Update status
description = "Application server - deployed ${timestamp()}"
}
```
### 2. DNS Automation Workflow
```hcl
# Create IP with DNS name and auto-DNS tag
resource "netbox_ip_address" "service" {
ip_address = "192.168.1.200/24"
dns_name = "service-01-api.spaceships.work"
tags = [
"terraform",
"production-dns" # Triggers netbox-powerdns-sync
]
}
# DNS records created automatically by plugin
# No manual DNS configuration needed
```
### 3. Network Documentation Workflow
```hcl
# Document entire network in NetBox
module "network_documentation" {
source = "./modules/network"
site_name = "homelab"
vlans = {
"mgmt" = { vid = 30, prefix = "192.168.3.0/24" }
"storage" = { vid = 40, prefix = "192.168.5.0/24" }
"ceph" = { vid = 50, prefix = "192.168.7.0/24" }
}
devices = var.proxmox_nodes
}
```
## Troubleshooting
### Version Mismatch Warning
```text
Warning: NetBox version X.Y.Z is not officially supported by provider version A.B.C
```
**Solution**: Use matching provider version or set `skip_version_check = true`
### API Authentication Errors
```text
Error: authentication failed
```
**Solution**:
1. Verify `api_token` is valid
2. Check token has required permissions
3. Ensure `server_url` includes scheme (`https://`)
### SSL Certificate Errors
```text
Error: x509: certificate signed by unknown authority
```
**Solution**:
```hcl
provider "netbox" {
server_url = var.netbox_url
api_token = var.netbox_token
ca_cert_file = "/path/to/ca.pem"
# OR for dev/testing only:
# allow_insecure_https = true
}
```
### Trailing Slash Issues
```text
Error: invalid URL format
```
**Solution**: Remove trailing slashes from `server_url` or let provider auto-fix:
```hcl
provider "netbox" {
server_url = "https://netbox.example.com" # No trailing slash
strip_trailing_slashes_from_url = true # Auto-fix if present
}
```
## Further Resources
- [Provider GitHub Repository](https://github.com/e-breuninger/terraform-provider-netbox)
- [NetBox Official Documentation](https://docs.netbox.dev/)
- [NetBox API Reference](https://demo.netbox.dev/api/docs/)