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

View File

@@ -0,0 +1,570 @@
# Ansible Dynamic Inventory from NetBox
## Overview
Use NetBox as a dynamic inventory source for Ansible, eliminating the need for static inventory
files and ensuring your automation always has up-to-date infrastructure data.
## Architecture
```text
┌──────────┐
│ NetBox │ (Source of Truth)
│ IPAM │
└────┬─────┘
│ API Query
┌────────────────┐
│ nb_inventory │ (Ansible Plugin)
│ Plugin │
└────┬───────────┘
│ Generates Dynamic Inventory
┌────────────────┐
│ Ansible │ (Uses inventory for playbooks)
│ Playbooks │
└────────────────┘
```
## Prerequisites
### Install NetBox Ansible Collection
```bash
cd ansible
uv run ansible-galaxy collection install netbox.netbox
```
**Or add to requirements.yml:**
```yaml
---
collections:
- name: netbox.netbox
version: ">=3.0.0"
```
```bash
uv run ansible-galaxy collection install -r requirements.yml
```
### NetBox API Token
Create read-only API token in NetBox:
**NetBox UI:** Admin → API Tokens → Add
- User: ansible (create service user)
- Key: Generated automatically
- Write enabled: No (read-only)
**Save token securely:**
```bash
# Option 1: Environment variable
export NETBOX_API_TOKEN="your-token-here"
# Option 2: Ansible Vault
ansible-vault create group_vars/all/vault.yml
# Add: netbox_token: "your-token-here"
```
## Basic Configuration
### Create Inventory File
**File:** `ansible/inventory/netbox.yml`
```yaml
---
plugin: netbox.netbox.nb_inventory
# NetBox API connection
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
# Validate SSL (set to false for self-signed certs)
validate_certs: true
# Group hosts by these NetBox attributes
group_by:
- device_roles
- tags
- sites
- platforms
# Set ansible_host variable from primary_ip4
compose:
ansible_host: primary_ip4
# Only include active devices/VMs
query_filters:
- status: active
```
### Test Inventory
```bash
# List all hosts
ansible-inventory -i ansible/inventory/netbox.yml --list
# View in YAML format
ansible-inventory -i ansible/inventory/netbox.yml --list --yaml
# View specific host
ansible-inventory -i ansible/inventory/netbox.yml --host docker-01-nexus
# Graph inventory
ansible-inventory -i ansible/inventory/netbox.yml --graph
```
## Advanced Configuration
### Filter by Tags
**Only include hosts with specific tag:**
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
# Only hosts tagged with "ansible-managed"
query_filters:
- tag: ansible-managed
- status: active
group_by:
- tags
```
### Filter by Device Role
**Only include specific device roles:**
```yaml
query_filters:
- role: docker-host
- role: k8s-node
- status: active
```
### Custom Groups
**Create custom groups based on NetBox data:**
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
group_by:
- device_roles
- tags
- sites
# Custom group mappings
keyed_groups:
- key: tags
prefix: tag
- key: device_role.name
prefix: role
- key: platform.name
prefix: platform
compose:
ansible_host: primary_ip4
ansible_user: ansible
ansible_become: true
```
### Include Custom Fields
**Use NetBox custom fields in inventory:**
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
compose:
ansible_host: primary_ip4
# Use custom fields from NetBox
backup_schedule: custom_fields.backup_schedule
monitoring_enabled: custom_fields.monitoring_enabled
application_owner: custom_fields.owner
group_by:
- tags
- custom_fields.environment
```
## Usage Examples
### Example 1: Configure All Docker Hosts
**Inventory:** `ansible/inventory/netbox.yml`
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
query_filters:
- tag: docker-host
- status: active
group_by:
- tags
compose:
ansible_host: primary_ip4
ansible_user: ansible
ansible_become: true
```
**Playbook:** `ansible/playbooks/configure-docker-hosts.yml`
```yaml
---
- name: Configure Docker hosts from NetBox inventory
hosts: tag_docker_host
become: true
tasks:
- name: Ensure Docker is running
ansible.builtin.systemd:
name: docker
state: started
enabled: true
- name: Update Docker daemon config
ansible.builtin.copy:
dest: /etc/docker/daemon.json
content: |
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
notify: Restart Docker
handlers:
- name: Restart Docker
ansible.builtin.systemd:
name: docker
state: restarted
```
**Run playbook:**
```bash
cd ansible
uv run ansible-playbook -i inventory/netbox.yml playbooks/configure-docker-hosts.yml
```
### Example 2: Site-Specific Deployments
**Inventory with site grouping:**
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
group_by:
- sites
- tags
compose:
ansible_host: primary_ip4
query_filters:
- status: active
```
**Playbook targeting specific site:**
```yaml
---
- name: Update hosts at primary site
hosts: site_homelab # Automatically grouped by site name
become: true
tasks:
- name: Update all packages
ansible.builtin.apt:
upgrade: dist
update_cache: true
when: ansible_os_family == "Debian"
```
### Example 3: Platform-Specific Configuration
**Inventory:**
```yaml
---
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;...
group_by:
- platforms
compose:
ansible_host: primary_ip4
keyed_groups:
- key: platform.name
prefix: platform
```
**Playbook with platform-specific tasks:**
```yaml
---
- name: Platform-specific configuration
hosts: all
become: true
tasks:
- name: Configure Ubuntu hosts
ansible.builtin.apt:
name: netbox-agent
state: present
when: "'ubuntu' in group_names"
- name: Configure Rocky hosts
ansible.builtin.dnf:
name: netbox-agent
state: present
when: "'rocky' in group_names"
```
## Integration with Secrets Management
### Use with Infisical
**Combine dynamic inventory with Infisical secrets:**
```yaml
---
- name: Deploy app with NetBox inventory and Infisical secrets
hosts: tag_app_server
become: true
vars:
infisical_project_id: "7b832220-24c0-45bc-a5f1-ce9794a31259"
infisical_env: "prod"
infisical_path: "/app-config"
tasks:
- name: Retrieve database password
ansible.builtin.include_tasks: "{{ playbook_dir }}/../tasks/infisical-secret-lookup.yml"
vars:
secret_name: 'DB_PASSWORD'
secret_var_name: 'db_password'
- name: Deploy application config
ansible.builtin.template:
src: app-config.j2
dest: /etc/app/config.yml
owner: app
group: app
mode: '0600'
vars:
db_host: "{{ hostvars[groups['tag_database'][0]]['ansible_host'] }}"
db_password: "{{ db_password }}"
```
## Caching for Performance
### Enable Inventory Caching
**File:** `ansible/ansible.cfg`
```ini
[defaults]
inventory_plugins = /usr/share/ansible/plugins/inventory
[inventory]
enable_plugins = netbox.netbox.nb_inventory
# Enable caching
cache = true
cache_plugin = jsonfile
cache_timeout = 3600 # 1 hour
cache_connection = /tmp/ansible-netbox-cache
```
**Benefits:**
- Faster playbook runs
- Reduced API calls to NetBox
- Works offline (for cache duration)
**Clear cache:**
```bash
rm -rf /tmp/ansible-netbox-cache
```
## Troubleshooting
### Authentication Errors
**Error:** `Failed to query NetBox API`
**Check:**
```bash
# Test API token
curl -H "Authorization: Token $NETBOX_API_TOKEN" \
https://netbox.spaceships.work/api/dcim/devices/ | jq
# Verify token permissions
# Token must have read access to: DCIM, IPAM, Virtualization
```
### SSL Certificate Errors
**Error:** `SSL: CERTIFICATE_VERIFY_FAILED`
**Solutions:**
```yaml
# Option 1: Add CA certificate
validate_certs: true
ssl_ca_cert: /path/to/ca-bundle.crt
# Option 2: Disable for self-signed (dev only!)
validate_certs: false
```
### No Hosts Found
**Error:** Inventory is empty
**Check:**
```bash
# List all devices in NetBox
curl -H "Authorization: Token $NETBOX_API_TOKEN" \
https://netbox.spaceships.work/api/dcim/devices/ | jq '.count'
# Check query filters
# Ensure devices match your filters (status, tags, etc.)
```
**Debug inventory plugin:**
```bash
ansible-inventory -i ansible/inventory/netbox.yml --list -vvv
```
### Primary IP Not Set
**Error:** `ansible_host` is undefined
**Cause:** Devices/VMs in NetBox don't have primary_ip4 set
**Solution:**
```yaml
# Fallback to custom field or use DNS name
compose:
ansible_host: primary_ip4 | default(custom_fields.management_ip) | default(name + '.spaceships.work')
```
## Best Practices
### 1. Use Service Account
Create dedicated NetBox user for Ansible:
```text
Username: ansible-automation
Permissions: Read-only (DCIM, IPAM, Virtualization)
Token: Never expires (or set appropriate expiration)
```
### 2. Tag for Inventory
Tag devices/VMs intended for Ansible management:
```text
Tag: ansible-managed
```
**Filter in inventory:**
```yaml
query_filters:
- tag: ansible-managed
```
### 3. Set Primary IPs
Always set primary_ip4 in NetBox for devices/VMs:
```text
Device → Edit → Primary IPv4
```
### 4. Use Custom Fields
Add custom fields to NetBox for Ansible-specific data:
```text
ansible_user (Text)
ansible_port (Integer)
ansible_python_interpreter (Text)
backup_enabled (Boolean)
```
### 5. Test Before Running
Always test inventory before running playbooks:
```bash
# Verify hosts
ansible-inventory -i inventory/netbox.yml --graph
# Test connectivity
ansible all -i inventory/netbox.yml -m ping
```
### 6. Document in NetBox
Use NetBox description fields to document:
- Ansible playbooks that manage this host
- Special configuration requirements
- Dependencies on other hosts
## Further Reading
- [NetBox Ansible Collection Documentation](https://docs.ansible.com/ansible/latest/collections/netbox/netbox/)
- [Dynamic Inventory Plugin Guide](https://docs.ansible.com/ansible/latest/plugins/inventory.html)
- [NetBox API Documentation](https://demo.netbox.dev/api/docs/)

View File

@@ -0,0 +1,592 @@
# DNS Automation Workflows
## Overview
This guide covers end-to-end workflows for automating DNS record management using NetBox as the
source of truth and PowerDNS as the authoritative DNS server.
## Architecture
```text
┌─────────────┐
│ Terraform │───┐
│ Ansible │ │
│ Manual │ │
└─────────────┘ │
┌──────────┐
│ NetBox │ (Source of Truth)
│ IPAM │
└────┬─────┘
│ netbox-powerdns-sync plugin
┌──────────┐
│ PowerDNS │ (Authoritative DNS)
│ API │
└────┬─────┘
│ Zone files / API
┌──────────┐
│ DNS │ (Resolvers query here)
│ Clients │
└──────────┘
```
## Workflow 1: Create VM with Automatic DNS
### Using Terraform
**End-to-end automation:**
```hcl
# 1. Create VM in Proxmox
resource "proxmox_vm_qemu" "docker_host" {
name = "docker-01-nexus"
target_node = "foxtrot"
vmid = 101
clone = "ubuntu-template"
full_clone = true
cores = 4
memory = 8192
network {
bridge = "vmbr0"
model = "virtio"
tag = 30
}
disk {
storage = "local-lvm"
type = "scsi"
size = "50G"
}
# Cloud-init IP configuration
ipconfig0 = "ip=192.168.1.100/24,gw=192.168.1.1"
sshkeys = file("~/.ssh/id_rsa.pub")
}
# 2. Register in NetBox (triggers DNS sync)
resource "netbox_ip_address" "docker_host" {
ip_address = "192.168.1.100/24"
dns_name = "docker-01-nexus.spaceships.work"
status = "active"
description = "Docker host for Nexus container registry"
tags = [
"terraform",
"production-dns", # Triggers auto DNS sync
"docker-host"
]
# Ensure VM is created first
depends_on = [proxmox_vm_qemu.docker_host]
}
# 3. DNS records automatically created by netbox-powerdns-sync plugin:
# A: docker-01-nexus.spaceships.work → 192.168.1.100
# PTR: 100.1.168.192.in-addr.arpa → docker-01-nexus.spaceships.work
# 4. Output for verification
output "vm_fqdn" {
value = netbox_ip_address.docker_host.dns_name
}
output "vm_ip" {
value = split("/", netbox_ip_address.docker_host.ip_address)[0]
}
```
**Apply workflow:**
```bash
cd terraform/netbox-vm/
tofu init
tofu plan
tofu apply
# Verify DNS
dig @192.168.3.1 docker-01-nexus.spaceships.work +short
# Returns: 192.168.1.100
# Verify PTR
dig @192.168.3.1 -x 192.168.1.100 +short
# Returns: docker-01-nexus.spaceships.work
```
### Using Ansible
**Playbook for VM with DNS:**
```yaml
---
- name: Provision VM with automatic DNS
hosts: localhost
gather_facts: false
vars:
vm_name: docker-01-nexus
vm_ip: 192.168.1.100
vm_fqdn: "{{ vm_name }}.spaceships.work"
proxmox_node: foxtrot
tasks:
# 1. Create VM in Proxmox
- name: Clone template to create VM
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "{{ proxmox_node }}"
vmid: 101
name: "{{ vm_name }}"
clone: ubuntu-template
full: true
storage: local-lvm
net:
net0: 'virtio,bridge=vmbr0,tag=30'
ipconfig:
ipconfig0: 'ip={{ vm_ip }}/24,gw=192.168.1.1'
cores: 4
memory: 8192
agent: 1
state: present
register: vm_result
- name: Start VM
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: "{{ proxmox_node }}"
vmid: 101
state: started
# 2. Register in NetBox
- name: Create IP address in NetBox
netbox.netbox.netbox_ip_address:
netbox_url: "{{ netbox_url }}"
netbox_token: "{{ netbox_token }}"
data:
address: "{{ vm_ip }}/24"
dns_name: "{{ vm_fqdn }}"
status: active
description: "Docker host for Nexus container registry"
tags:
- name: production-dns
- name: ansible
- name: docker-host
# 3. Wait for DNS propagation
- name: Wait for DNS record
ansible.builtin.command: dig @192.168.3.1 {{ vm_fqdn }} +short
register: dns_check
until: dns_check.stdout == vm_ip
retries: 10
delay: 5
changed_when: false
# 4. Verify DNS resolution
- name: Verify DNS forward resolution
ansible.builtin.command: dig @192.168.3.1 {{ vm_fqdn }} +short
register: forward_dns
changed_when: false
- name: Verify DNS reverse resolution
ansible.builtin.command: dig @192.168.3.1 -x {{ vm_ip }} +short
register: reverse_dns
changed_when: false
- name: Report DNS status
ansible.builtin.debug:
msg:
- "VM created: {{ vm_name }}"
- "IP: {{ vm_ip }}"
- "FQDN: {{ vm_fqdn }}"
- "Forward DNS: {{ forward_dns.stdout }}"
- "Reverse DNS: {{ reverse_dns.stdout }}"
```
**Run playbook:**
```bash
cd ansible
uv run ansible-playbook playbooks/provision-vm-with-dns.yml
```
## Workflow 2: Bulk IP Address Management
### Reserve IP Range in NetBox
```python
#!/usr/bin/env python3
# /// script
# dependencies = ["pynetbox"]
# ///
import pynetbox
import os
netbox = pynetbox.api(
os.getenv("NETBOX_URL"),
token=os.getenv("NETBOX_TOKEN")
)
# Define IP range for Docker hosts
docker_ips = [
{"ip": "192.168.1.100/24", "dns": "docker-01-nexus.spaceships.work", "desc": "Nexus registry"},
{"ip": "192.168.1.101/24", "dns": "docker-02-gitlab.spaceships.work", "desc": "GitLab CI/CD"},
{"ip": "192.168.1.102/24", "dns": "docker-03-monitoring.spaceships.work", "desc": "Monitoring stack"},
]
for entry in docker_ips:
ip = netbox.ipam.ip_addresses.create(
address=entry["ip"],
dns_name=entry["dns"],
description=entry["desc"],
status="reserved",
tags=[{"name": "production-dns"}, {"name": "docker-host"}]
)
print(f"Created: {ip.dns_name}{entry['ip']}")
```
**Run script:**
```bash
export NETBOX_URL="https://netbox.spaceships.work"
export NETBOX_TOKEN="your-api-token"
uv run reserve-docker-ips.py
```
### Update Status to Active
When VMs are deployed, update IPs from "reserved" to "active":
```python
#!/usr/bin/env python3
# /// script
# dependencies = ["pynetbox"]
# ///
import pynetbox
import os
import sys
netbox = pynetbox.api(
os.getenv("NETBOX_URL"),
token=os.getenv("NETBOX_TOKEN")
)
fqdn = sys.argv[1] if len(sys.argv) > 1 else "docker-01-nexus.spaceships.work"
# Find IP by DNS name
ips = netbox.ipam.ip_addresses.filter(dns_name=fqdn)
if not ips:
print(f"No IP found for {fqdn}")
sys.exit(1)
ip = ips[0]
ip.status = "active"
ip.save()
print(f"Updated {ip.dns_name}: {ip.address} → active")
```
## Workflow 3: DNS Record Auditing
### Verify NetBox and PowerDNS are in Sync
```python
#!/usr/bin/env python3
# /// script
# dependencies = ["pynetbox", "requests"]
# ///
import pynetbox
import requests
import os
import sys
netbox = pynetbox.api(
os.getenv("NETBOX_URL"),
token=os.getenv("NETBOX_TOKEN")
)
powerdns_url = os.getenv("POWERDNS_URL", "http://192.168.3.1:8081/api/v1")
powerdns_key = os.getenv("POWERDNS_API_KEY")
zone = sys.argv[1] if len(sys.argv) > 1 else "spaceships.work"
# Get NetBox IPs tagged for DNS
netbox_ips = netbox.ipam.ip_addresses.filter(tag="production-dns")
# Get PowerDNS records
headers = {"X-API-Key": powerdns_key}
pdns_resp = requests.get(f"{powerdns_url}/servers/localhost/zones/{zone}", headers=headers)
pdns_zone = pdns_resp.json()
# Extract A records from PowerDNS
pdns_records = {}
for rrset in pdns_zone.get("rrsets", []):
if rrset["type"] == "A":
name = rrset["name"].rstrip(".")
for record in rrset["records"]:
pdns_records[name] = record["content"]
# Compare
print("NetBox → PowerDNS Sync Status\n")
print(f"{'DNS Name':<45} {'NetBox IP':<15} {'PowerDNS IP':<15} {'Status'}")
print("-" * 90)
for nb_ip in netbox_ips:
if not nb_ip.dns_name:
continue
dns_name = nb_ip.dns_name.rstrip(".")
nb_addr = str(nb_ip.address).split("/")[0]
pdns_addr = pdns_records.get(dns_name, "MISSING")
if pdns_addr == nb_addr:
status = "✓ SYNCED"
elif pdns_addr == "MISSING":
status = "✗ NOT IN POWERDNS"
else:
status = f"✗ MISMATCH"
print(f"{dns_name:<45} {nb_addr:<15} {pdns_addr:<15} {status}")
```
**Run audit:**
```bash
export NETBOX_URL="https://netbox.spaceships.work"
export NETBOX_TOKEN="your-netbox-token"
export POWERDNS_URL="http://192.168.3.1:8081/api/v1"
export POWERDNS_API_KEY="your-powerdns-key"
uv run dns-audit.py spaceships.work
```
## Workflow 4: Dynamic Inventory for Ansible
### Use NetBox as Inventory Source
**Create dynamic inventory file:**
```yaml
# ansible/inventory/netbox.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.spaceships.work
token: !vault |
$ANSIBLE_VAULT;1.1;AES256
...
# Group hosts by tags
group_by:
- tags
# Group hosts by device role
compose:
ansible_host: primary_ip4
# Filter to only include VMs with production-dns tag
query_filters:
- tag: production-dns
```
**Test inventory:**
```bash
ansible-inventory -i ansible/inventory/netbox.yml --list --yaml
```
**Use in playbook:**
```yaml
---
- name: Configure all Docker hosts
hosts: tag_docker_host # Automatically grouped by tag
become: true
tasks:
- name: Ensure Docker is running
ansible.builtin.systemd:
name: docker
state: started
enabled: true
- name: Report host info
ansible.builtin.debug:
msg: "Configuring {{ inventory_hostname }} ({{ ansible_host }})"
```
**Run with dynamic inventory:**
```bash
cd ansible
uv run ansible-playbook -i inventory/netbox.yml playbooks/configure-docker-hosts.yml
```
## Workflow 5: Cleanup and Decommission
### Remove VM and DNS Records
**Terraform destroy workflow:**
```bash
cd terraform/netbox-vm/
tofu destroy
# This will:
# 1. Remove NetBox IP address record
# 2. netbox-powerdns-sync plugin removes DNS records
# 3. Proxmox VM is deleted
```
**Ansible decommission playbook:**
```yaml
---
- name: Decommission VM and remove DNS
hosts: localhost
gather_facts: false
vars:
vm_fqdn: docker-01-nexus.spaceships.work
vm_ip: 192.168.1.100
tasks:
- name: Remove IP from NetBox
netbox.netbox.netbox_ip_address:
netbox_url: "{{ netbox_url }}"
netbox_token: "{{ netbox_token }}"
data:
address: "{{ vm_ip }}/24"
state: absent
# DNS records automatically removed by plugin
- name: Verify DNS record removed
ansible.builtin.command: dig @192.168.3.1 {{ vm_fqdn }} +short
register: dns_check
failed_when: dns_check.stdout != ""
changed_when: false
- name: Delete VM from Proxmox
community.proxmox.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_token_id }}"
api_token_secret: "{{ proxmox_token_secret }}"
node: foxtrot
vmid: 101
state: absent
```
## Best Practices
### 1. Always Use Tags
Tag IP addresses for automatic DNS sync:
```hcl
tags = ["terraform", "production-dns", "service-type"]
```
### 2. Reserve Before Deploy
Reserve IPs in NetBox before deploying VMs:
```text
Status: reserved → active (after deployment)
```
### 3. Validate Names
Use naming convention validation before creating:
```bash
./tools/validate_dns_naming.py docker-01-nexus.spaceships.work
```
### 4. Monitor Sync Status
Regular audits to ensure NetBox and PowerDNS are in sync:
```bash
./tools/dns-audit.py spaceships.work
```
### 5. Use Descriptions
Document in NetBox description field:
```text
Description: Docker host for Nexus container registry
Owner: Platform Team
Related: docker-02-gitlab.spaceships.work
```
### 6. Test DNS Resolution
Always verify DNS after creation:
```bash
dig @192.168.3.1 <fqdn> +short
dig @192.168.3.1 -x <ip> +short
```
## Troubleshooting
### DNS Records Not Created
#### Check 1: Tag matching
```bash
# Verify IP has production-dns tag
curl -H "Authorization: Token $NETBOX_TOKEN" \
"$NETBOX_URL/api/ipam/ip-addresses/?address=192.168.1.100" | jq '.results[0].tags'
```
#### Check 2: Plugin configuration
```python
# In NetBox: Plugins → NetBox PowerDNS Sync → Zones
# Verify zone exists and tag rules match
```
#### Check 3: Manual sync
```bash
# In NetBox UI: Plugins → NetBox PowerDNS Sync → Zones → <zone> → Sync Now
```
### DNS Resolution Failures
**Check PowerDNS API:**
```bash
curl -H "X-API-Key: $POWERDNS_API_KEY" \
http://192.168.3.1:8081/api/v1/servers/localhost/zones/spaceships.work
```
**Check DNS server:**
```bash
dig @192.168.3.1 spaceships.work SOA
```
## Further Reading
- [NetBox PowerDNS Sync Plugin](../reference/sync-plugin-reference.md)
- [Terraform NetBox Provider](../reference/terraform-provider-guide.md)
- [DNS Naming Conventions](naming-conventions.md)

View File

@@ -0,0 +1,418 @@
# DNS Naming Conventions
## Overview
Consistent DNS naming is critical for automation and infrastructure documentation. This document defines the naming
conventions used in the Virgo-Core infrastructure.
## Standard Pattern
**Format:** `<service>-<number>-<purpose>.<domain>`
**Components:**
- `<service>` - Service type (docker, k8s, proxmox, storage, db, etc.)
- `<number>` - Instance number (01, 02, 03, etc.) - always 2 digits
- `<purpose>` - Specific purpose or application name
- `<domain>` - DNS domain (e.g., spaceships.work)
**Regex Pattern:**
```regex
^[a-z0-9-]+-\d{2}-[a-z0-9-]+\.[a-z0-9.-]+$
```
## Service Types
### Container Platforms
**Docker hosts:**
```text
docker-01-nexus.spaceships.work # Nexus container registry
docker-02-gitlab.spaceships.work # GitLab CI/CD
docker-03-monitoring.spaceships.work # Monitoring stack (Prometheus, Grafana)
```
**Kubernetes nodes:**
```text
k8s-01-master.spaceships.work # Control plane node 1
k8s-02-master.spaceships.work # Control plane node 2
k8s-03-master.spaceships.work # Control plane node 3
k8s-04-worker.spaceships.work # Worker node 1
k8s-05-worker.spaceships.work # Worker node 2
```
### Infrastructure
**Proxmox nodes:**
```text
proxmox-foxtrot-mgmt.spaceships.work # Foxtrot management interface
proxmox-foxtrot-ceph.spaceships.work # Foxtrot CEPH public interface
proxmox-golf-mgmt.spaceships.work # Golf management interface
proxmox-hotel-mgmt.spaceships.work # Hotel management interface
```
**Storage systems:**
```text
storage-01-nas.spaceships.work # NAS storage (TrueNAS/FreeNAS)
storage-02-backup.spaceships.work # Backup storage
storage-03-archive.spaceships.work # Long-term archive storage
```
### Databases
```text
db-01-postgres.spaceships.work # PostgreSQL primary
db-02-postgres.spaceships.work # PostgreSQL replica
db-03-mysql.spaceships.work # MySQL/MariaDB
db-04-redis.spaceships.work # Redis cache
```
### Network Services
```text
network-01-pfsense.spaceships.work # pfSense router
network-02-unifi.spaceships.work # UniFi controller
network-03-dns.spaceships.work # DNS server (PowerDNS)
network-04-dhcp.spaceships.work # DHCP server
```
### Application Services
```text
app-01-netbox.spaceships.work # NetBox IPAM
app-02-vault.spaceships.work # HashiCorp Vault
app-03-consul.spaceships.work # HashiCorp Consul
app-04-nomad.spaceships.work # HashiCorp Nomad
```
## Special Cases
### Management Interfaces
For infrastructure with multiple interfaces, include interface purpose:
```text
proxmox-<node>-mgmt.spaceships.work # Management network
proxmox-<node>-ceph.spaceships.work # CEPH public network
proxmox-<node>-backup.spaceships.work # Backup network
```
### Virtual IPs (FHRP/VIPs)
```text
vip-01-k8s-api.spaceships.work # Kubernetes API VIP
vip-02-haproxy.spaceships.work # HAProxy VIP
vip-03-postgres.spaceships.work # PostgreSQL VIP
```
### Service Endpoints
```text
service-01-api.spaceships.work # API endpoint
service-02-web.spaceships.work # Web frontend
service-03-cdn.spaceships.work # CDN endpoint
```
## Rules and Best Practices
### Mandatory Rules
1. **Always lowercase** - No uppercase letters
2. **Hyphens only** - No underscores or other special characters
3. **Two-digit numbers** - Use 01, 02, not 1, 2
4. **Descriptive purpose** - Purpose should clearly indicate function
5. **Valid DNS characters** - Only `a-z`, `0-9`, `-`, `.`
### Recommended Practices
1. **Consistent service names** - Stick to established service types
2. **Logical numbering** - Start at 01, increment sequentially
3. **Purpose specificity** - Be specific but concise (nexus, not nexus-container-registry)
4. **Avoid ambiguity** - Don't use `test-01-prod` or similar confusing names
5. **Document exceptions** - If you must break a rule, document why
## Validation
### Python Validation Script
```python
#!/usr/bin/env python3
# /// script
# dependencies = []
# ///
import re
import sys
PATTERN = r'^[a-z0-9-]+-\d{2}-[a-z0-9-]+\.[a-z0-9.-]+$'
def validate_dns_name(name: str) -> tuple[bool, str]:
"""Validate DNS name against convention."""
if not re.match(PATTERN, name):
return False, "Name doesn't match pattern: <service>-<NN>-<purpose>.<domain>"
parts = name.split('.')
if len(parts) < 2:
return False, "Must include domain"
hostname = parts[0]
components = hostname.split('-')
if len(components) < 3:
return False, "Hostname must have at least 3 components: <service>-<NN>-<purpose>"
# Check number component (should be 2 digits)
number_component = components[1]
if not number_component.isdigit() or len(number_component) != 2:
return False, f"Number component '{number_component}' must be exactly 2 digits (01-99)"
return True, "Valid"
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: validate_dns_naming.py <dns-name>")
sys.exit(1)
name = sys.argv[1]
valid, message = validate_dns_name(name)
if valid:
print(f"{name}: {message}")
sys.exit(0)
else:
print(f"{name}: {message}", file=sys.stderr)
sys.exit(1)
```
**Usage:**
```bash
./tools/validate_dns_naming.py docker-01-nexus.spaceships.work
# ✓ docker-01-nexus.spaceships.work: Valid
./tools/validate_dns_naming.py Docker-1-Nexus.spaceships.work
# ✗ Docker-1-Nexus.spaceships.work: Name doesn't match pattern
```
## NetBox Integration
### Setting DNS Names in NetBox
**Via Web UI:**
IP Addresses → Add → DNS Name field: `docker-01-nexus.spaceships.work`
**Via Terraform:**
```hcl
resource "netbox_ip_address" "docker_nexus" {
ip_address = "192.168.1.100/24"
dns_name = "docker-01-nexus.spaceships.work"
description = "Docker host for Nexus container registry"
tags = ["terraform", "production-dns"]
}
```
**Via Ansible:**
```yaml
- name: Create IP address in NetBox
netbox.netbox.netbox_ip_address:
netbox_url: "{{ netbox_url }}"
netbox_token: "{{ netbox_token }}"
data:
address: "192.168.1.100/24"
dns_name: "docker-01-nexus.spaceships.work"
tags:
- name: production-dns
```
### Tagging for Auto-DNS
Tag IP addresses to trigger automatic DNS record creation:
**Production DNS:**
```text
Tag: production-dns
Zone: spaceships.work
```
**Development DNS:**
```text
Tag: dev-dns
Zone: dev.spaceships.work
```
**Lab/Testing DNS:**
```text
Tag: lab-dns
Zone: lab.spaceships.work
```
## PowerDNS Record Generation
### Automatic Record Creation
When an IP address with correct tags is created in NetBox:
```text
IP: 192.168.1.100/24
DNS Name: docker-01-nexus.spaceships.work
Tag: production-dns
→ A record created: docker-01-nexus.spaceships.work → 192.168.1.100
→ PTR record created: 100.1.168.192.in-addr.arpa → docker-01-nexus.spaceships.work
```
### Sync Verification
```bash
# Verify DNS record exists
dig @192.168.3.1 docker-01-nexus.spaceships.work +short
# Should return: 192.168.1.100
# Verify PTR record
dig @192.168.3.1 -x 192.168.1.100 +short
# Should return: docker-01-nexus.spaceships.work
```
## Common Mistakes
### Wrong Number Format
```text
❌ docker-1-nexus.spaceships.work # Single digit
✓ docker-01-nexus.spaceships.work # Two digits
❌ docker-001-nexus.spaceships.work # Three digits
✓ docker-01-nexus.spaceships.work # Two digits
```
### Wrong Separators
```text
❌ docker_01_nexus.spaceships.work # Underscores
✓ docker-01-nexus.spaceships.work # Hyphens
❌ docker.01.nexus.spaceships.work # Dots in hostname
✓ docker-01-nexus.spaceships.work # Hyphens only
```
### Wrong Case
```text
❌ Docker-01-Nexus.spaceships.work # Mixed case
✓ docker-01-nexus.spaceships.work # Lowercase only
❌ DOCKER-01-NEXUS.SPACESHIPS.WORK # Uppercase
✓ docker-01-nexus.spaceships.work # Lowercase only
```
### Missing Components
```text
❌ docker-nexus.spaceships.work # Missing number
✓ docker-01-nexus.spaceships.work # Complete
❌ 01-nexus.spaceships.work # Missing service
✓ docker-01-nexus.spaceships.work # Complete
❌ docker-01.spaceships.work # Missing purpose
✓ docker-01-nexus.spaceships.work # Complete
```
## Migration Strategy
### From Legacy Names
If you have existing DNS names that don't follow the convention:
1. **Document current names** - Create inventory of legacy names
2. **Create new names** - Following convention
3. **Create CNAME records** - Point legacy names to new names
4. **Update configs gradually** - Migrate services to use new names
5. **Monitor usage** - Track legacy CNAME usage
6. **Deprecate legacy names** - Remove after migration complete
**Example migration:**
```text
Legacy: nexus.spaceships.work
New: docker-01-nexus.spaceships.work
CNAME: nexus.spaceships.work → docker-01-nexus.spaceships.work
After 6 months: Remove CNAME, update all references to use new name
```
## Environment-Specific Domains
### Production
```text
Domain: spaceships.work
Example: docker-01-nexus.spaceships.work
```
### Development
```text
Domain: dev.spaceships.work
Example: docker-01-nexus.dev.spaceships.work
```
### Lab/Testing
```text
Domain: lab.spaceships.work
Example: docker-01-nexus.lab.spaceships.work
```
## Documentation
### In NetBox
Use the **Description** field to document:
- Primary purpose
- Hosted applications
- Related services
- Contact owner
**Example:**
```text
IP: 192.168.1.100/24
DNS Name: docker-01-nexus.spaceships.work
Description: Docker host for Nexus container registry.
Serves Docker and Maven artifacts.
Owner: Platform Team
Related: docker-02-gitlab.spaceships.work
```
### In Infrastructure Code
**Terraform example:**
```hcl
resource "netbox_ip_address" "docker_nexus" {
ip_address = "192.168.1.100/24"
dns_name = "docker-01-nexus.spaceships.work"
description = "Docker host for Nexus container registry"
tags = ["terraform", "production-dns", "docker-host", "nexus"]
}
```
## Further Reading
- [RFC 1035 - Domain Names](https://www.rfc-editor.org/rfc/rfc1035)
- [DNS Best Practices](https://www.ietf.org/rfc/rfc1912.txt)