Initial commit
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
# VM with Automatic DNS Registration
|
||||
|
||||
**Learning objective:** Experience the full infrastructure automation workflow from VM creation to DNS resolution.
|
||||
|
||||
## What This Example Demonstrates
|
||||
|
||||
This is the "holy grail" of infrastructure automation - deploy infrastructure and have DNS automatically configured:
|
||||
|
||||
```text
|
||||
Terraform Deploy → Proxmox VM Created → NetBox IP Registered
|
||||
→ PowerDNS Records Auto-Created → Ready for Ansible
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- ✅ No manual DNS configuration
|
||||
- ✅ Single source of truth (NetBox)
|
||||
- ✅ Audit trail for all infrastructure changes
|
||||
- ✅ Automatic forward + reverse DNS
|
||||
- ✅ Ready for dynamic inventory
|
||||
|
||||
## Architecture
|
||||
|
||||
```text
|
||||
┌──────────────┐
|
||||
│ Terraform │
|
||||
└──────┬───────┘
|
||||
│
|
||||
├─ Creates VM in Proxmox ─────────────► VM: docker-01-nexus
|
||||
│ IP: 192.168.1.100
|
||||
│
|
||||
└─ Registers IP in NetBox ────────────► NetBox IPAM
|
||||
│ Tags: ["production-dns"]
|
||||
│
|
||||
│ netbox-powerdns-sync plugin (automatic)
|
||||
│
|
||||
▼
|
||||
┌────────────┐
|
||||
│ 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
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### 1. Proxmox Template
|
||||
|
||||
Template must exist (default: VMID 9000):
|
||||
|
||||
```bash
|
||||
# Create using Virgo-Core template deployment
|
||||
cd ../../../terraform/netbox-template
|
||||
tofu apply
|
||||
```
|
||||
|
||||
### 2. NetBox Configuration
|
||||
|
||||
**NetBox PowerDNS Sync Plugin** must be configured with:
|
||||
|
||||
- Zone: `spaceships.work`
|
||||
- Tag rule matching: `production-dns`
|
||||
- PowerDNS server connection configured
|
||||
|
||||
See: [../../reference/sync-plugin-reference.md](../../reference/sync-plugin-reference.md)
|
||||
|
||||
### 3. Environment Variables
|
||||
|
||||
```bash
|
||||
# Proxmox
|
||||
export PROXMOX_VE_ENDPOINT="https://192.168.3.5:8006"
|
||||
export PROXMOX_VE_API_TOKEN="user@realm!token-id=secret"
|
||||
|
||||
# NetBox
|
||||
export NETBOX_API_TOKEN="your-netbox-token"
|
||||
export TF_VAR_netbox_api_token="$NETBOX_API_TOKEN"
|
||||
|
||||
# SSH Key
|
||||
export TF_VAR_ssh_public_key="$(cat ~/.ssh/id_rsa.pub)"
|
||||
```
|
||||
|
||||
### 4. DNS Naming Convention
|
||||
|
||||
FQDN must follow: `<service>-<NN>-<purpose>.<domain>`
|
||||
|
||||
**Validate name first:**
|
||||
|
||||
```bash
|
||||
../../tools/validate_dns_naming.py docker-01-nexus.spaceships.work
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Initialize
|
||||
|
||||
```bash
|
||||
tofu init
|
||||
```
|
||||
|
||||
### 2. Plan
|
||||
|
||||
```bash
|
||||
tofu plan
|
||||
```
|
||||
|
||||
**Expected resources:**
|
||||
|
||||
- 1 Proxmox VM (docker-01-nexus)
|
||||
- 1 NetBox IP address (192.168.1.100)
|
||||
- DNS records created automatically (not shown in plan)
|
||||
|
||||
### 3. Deploy
|
||||
|
||||
```bash
|
||||
tofu apply
|
||||
```
|
||||
|
||||
**Wait 30 seconds** for DNS propagation (netbox-powerdns-sync runs async).
|
||||
|
||||
### 4. Verify Complete Workflow
|
||||
|
||||
#### Step 1: Check VM exists
|
||||
|
||||
```bash
|
||||
# On Proxmox node
|
||||
qm status $(terraform output -raw vm_id)
|
||||
```
|
||||
|
||||
#### Step 2: Check NetBox registration
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||
"https://netbox.spaceships.work/api/ipam/ip-addresses/?address=192.168.1.100" | jq
|
||||
```
|
||||
|
||||
#### Step 3: Verify DNS forward resolution
|
||||
|
||||
```bash
|
||||
dig @192.168.3.1 docker-01-nexus.spaceships.work +short
|
||||
# Expected: 192.168.1.100
|
||||
```
|
||||
|
||||
#### Step 4: Verify DNS reverse resolution
|
||||
|
||||
```bash
|
||||
dig @192.168.3.1 -x 192.168.1.100 +short
|
||||
# Expected: docker-01-nexus.spaceships.work.
|
||||
```
|
||||
|
||||
#### Step 5: SSH into VM
|
||||
|
||||
```bash
|
||||
ssh ansible@docker-01-nexus.spaceships.work
|
||||
# or
|
||||
ssh ansible@192.168.1.100
|
||||
```
|
||||
|
||||
### 5. Cleanup
|
||||
|
||||
```bash
|
||||
tofu destroy
|
||||
```
|
||||
|
||||
**Note:** DNS records are automatically removed when NetBox IP is deleted.
|
||||
|
||||
## How It Works
|
||||
|
||||
### The Magic Tag: `production-dns`
|
||||
|
||||
```hcl
|
||||
resource "netbox_ip_address" "docker_host" {
|
||||
tags = [
|
||||
"terraform",
|
||||
"production-dns", # ← This triggers DNS automation
|
||||
"docker-host"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `production-dns` tag matches a zone rule in NetBox PowerDNS Sync plugin configuration.
|
||||
|
||||
### NetBox PowerDNS Sync Plugin
|
||||
|
||||
**Configuration (in NetBox):**
|
||||
|
||||
```python
|
||||
# Plugins → NetBox PowerDNS Sync → Zones → spaceships.work
|
||||
zone_config = {
|
||||
"name": "spaceships.work",
|
||||
"powerdns_server": "PowerDNS Server 1",
|
||||
"tag_match": ["production-dns"], # ← Matches our tag
|
||||
"naming_method": "device", # Use device/IP name
|
||||
"auto_sync": True,
|
||||
"sync_schedule": "*/5 * * * *" # Every 5 minutes
|
||||
}
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
|
||||
1. IP address created in NetBox with `production-dns` tag
|
||||
2. Plugin detects new IP matches zone rule
|
||||
3. Plugin calls PowerDNS API to create records:
|
||||
- A record from `dns_name` field
|
||||
- PTR record from IP address
|
||||
4. Records appear in DNS within 30 seconds
|
||||
|
||||
## Customization
|
||||
|
||||
### Different Service Type
|
||||
|
||||
```hcl
|
||||
# In terraform.tfvars or as -var
|
||||
vm_name = "k8s-01-master"
|
||||
fqdn = "k8s-01-master.spaceships.work"
|
||||
```
|
||||
|
||||
### Development Environment
|
||||
|
||||
Use different domain and tags:
|
||||
|
||||
```hcl
|
||||
variable "dns_domain" {
|
||||
default = "dev.spaceships.work"
|
||||
}
|
||||
|
||||
resource "netbox_ip_address" "docker_host" {
|
||||
tags = ["terraform", "dev-dns", "docker-host"] # Different tag for dev zone
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple Environments
|
||||
|
||||
```bash
|
||||
# Production
|
||||
tofu apply -var-file=prod.tfvars
|
||||
|
||||
# Development
|
||||
tofu apply -var-file=dev.tfvars
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### DNS Records Not Created
|
||||
|
||||
**1. Check NetBox IP registration:**
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||
"https://netbox.spaceships.work/api/ipam/ip-addresses/?dns_name=docker-01-nexus.spaceships.work" | jq
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
- IP exists in NetBox
|
||||
- Has `production-dns` tag
|
||||
- `dns_name` field is set
|
||||
|
||||
**2. Check zone configuration:**
|
||||
|
||||
- NetBox → Plugins → NetBox PowerDNS Sync → Zones
|
||||
- Verify `spaceships.work` zone exists
|
||||
- Check tag rules match `production-dns`
|
||||
|
||||
**3. Check sync results:**
|
||||
|
||||
- NetBox → Plugins → NetBox PowerDNS Sync → Zones → spaceships.work
|
||||
- Click "Sync Now" to manually trigger
|
||||
- Review sync results for errors
|
||||
|
||||
**4. Check PowerDNS:**
|
||||
|
||||
```bash
|
||||
# Query PowerDNS API directly
|
||||
curl -H "X-API-Key: $POWERDNS_API_KEY" \
|
||||
http://192.168.3.1:8081/api/v1/servers/localhost/zones/spaceships.work | jq
|
||||
```
|
||||
|
||||
### SSH Connection Fails
|
||||
|
||||
**Try IP first:**
|
||||
|
||||
```bash
|
||||
ssh ansible@192.168.1.100
|
||||
```
|
||||
|
||||
If IP works but FQDN doesn't:
|
||||
|
||||
- DNS not propagated yet (wait 30s)
|
||||
- Check DNS resolution: `dig @192.168.3.1 docker-01-nexus.spaceships.work`
|
||||
|
||||
### NetBox Provider Authentication
|
||||
|
||||
**Error:** `authentication failed`
|
||||
|
||||
**Solution:**
|
||||
|
||||
```bash
|
||||
# Test API token
|
||||
curl -H "Authorization: Token $NETBOX_API_TOKEN" \
|
||||
https://netbox.spaceships.work/api/status/ | jq
|
||||
|
||||
# Regenerate token if needed
|
||||
# NetBox → Admin → API Tokens → Create
|
||||
```
|
||||
|
||||
## Integration with Ansible
|
||||
|
||||
After deployment, use for Ansible configuration:
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Ansible playbook using NetBox dynamic inventory
|
||||
- name: Configure Docker hosts
|
||||
hosts: tag_docker_host # From NetBox tags
|
||||
become: true
|
||||
|
||||
tasks:
|
||||
- name: Install Docker
|
||||
ansible.builtin.apt:
|
||||
name: docker.io
|
||||
state: present
|
||||
```
|
||||
|
||||
**Run with dynamic inventory:**
|
||||
|
||||
```bash
|
||||
cd ../../../ansible
|
||||
ansible-playbook -i inventory/netbox.yml playbooks/configure-docker.yml
|
||||
```
|
||||
|
||||
See: [../../workflows/ansible-dynamic-inventory.md](../../workflows/ansible-dynamic-inventory.md)
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Learn More Workflows
|
||||
|
||||
- **Bulk Deployment:** `../02-bulk-deployment/` (coming soon)
|
||||
- **DNS Automation Guide:** [../../workflows/dns-automation.md](../../workflows/dns-automation.md)
|
||||
- **Naming Conventions:** [../../workflows/naming-conventions.md](../../workflows/naming-conventions.md)
|
||||
|
||||
### Production Checklist
|
||||
|
||||
Before using in production:
|
||||
|
||||
- [ ] NetBox PowerDNS Sync plugin tested and working
|
||||
- [ ] DNS naming convention documented
|
||||
- [ ] Zone rules configured for all environments
|
||||
- [ ] API tokens secured (not in version control)
|
||||
- [ ] Backup/restore procedures for NetBox
|
||||
- [ ] Monitoring for DNS sync failures
|
||||
- [ ] Documentation for team
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
**Single Source of Truth:**
|
||||
|
||||
- All infrastructure documented in NetBox
|
||||
- DNS automatically matches reality
|
||||
- Easy to audit what exists
|
||||
|
||||
**Automation:**
|
||||
|
||||
- No manual DNS configuration
|
||||
- Reduced human error
|
||||
- Faster deployments
|
||||
|
||||
**Consistency:**
|
||||
|
||||
- Naming convention enforced
|
||||
- Tag-based organization
|
||||
- Audit trail via Terraform
|
||||
|
||||
**Integration:**
|
||||
|
||||
- Ansible dynamic inventory
|
||||
- Monitoring integrations
|
||||
- IPAM + DNS + DCIM in one place
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [NetBox Provider Guide](../../reference/terraform-provider-guide.md)
|
||||
- [PowerDNS Sync Plugin](../../reference/sync-plugin-reference.md)
|
||||
- [DNS Naming Conventions](../../workflows/naming-conventions.md)
|
||||
- [DNS Automation Workflows](../../workflows/dns-automation.md)
|
||||
@@ -0,0 +1,191 @@
|
||||
# =============================================================================
|
||||
# VM with Automatic DNS Registration
|
||||
# =============================================================================
|
||||
# This example demonstrates the complete infrastructure automation workflow:
|
||||
# 1. Create VM in Proxmox (using unified module)
|
||||
# 2. Register IP address in NetBox with DNS name
|
||||
# 3. DNS records automatically created in PowerDNS (via netbox-powerdns-sync plugin)
|
||||
# 4. Ready for Ansible configuration management
|
||||
#
|
||||
# Result: Fully automated infrastructure from VM → DNS → Configuration
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = "~> 0.69"
|
||||
}
|
||||
netbox = {
|
||||
source = "e-breuninger/netbox"
|
||||
version = "~> 5.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# === Proxmox Provider ===
|
||||
provider "proxmox" {
|
||||
endpoint = var.proxmox_endpoint
|
||||
# Credentials from environment:
|
||||
# PROXMOX_VE_API_TOKEN or PROXMOX_VE_USERNAME/PASSWORD
|
||||
}
|
||||
|
||||
# === NetBox Provider ===
|
||||
provider "netbox" {
|
||||
server_url = var.netbox_url
|
||||
api_token = var.netbox_api_token
|
||||
# Or use environment: NETBOX_API_TOKEN
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Step 1: Create VM in Proxmox
|
||||
# =============================================================================
|
||||
|
||||
module "docker_host" {
|
||||
source = "github.com/basher83/Triangulum-Prime//terraform-bgp-vm?ref=vm/1.0.1"
|
||||
|
||||
# VM Configuration
|
||||
vm_type = "clone"
|
||||
pve_node = var.proxmox_node
|
||||
vm_name = var.vm_name
|
||||
|
||||
# Clone from template
|
||||
src_clone = {
|
||||
datastore_id = "local-lvm"
|
||||
tpl_id = var.template_id
|
||||
}
|
||||
|
||||
# Production-ready resources
|
||||
vm_cpu = {
|
||||
cores = 4 # Docker workload
|
||||
}
|
||||
|
||||
vm_mem = {
|
||||
dedicated = 8192 # 8GB for containers
|
||||
}
|
||||
|
||||
# Disk configuration
|
||||
vm_disk = {
|
||||
scsi0 = {
|
||||
datastore_id = "local-lvm"
|
||||
size = 100 # Larger for Docker images
|
||||
main_disk = true
|
||||
}
|
||||
}
|
||||
|
||||
# Network with VLAN
|
||||
vm_net_ifaces = {
|
||||
net0 = {
|
||||
bridge = var.network_bridge
|
||||
vlan_id = var.vlan_id
|
||||
ipv4_addr = "${var.ip_address}/24"
|
||||
ipv4_gw = var.gateway
|
||||
}
|
||||
}
|
||||
|
||||
# Cloud-init
|
||||
vm_init = {
|
||||
datastore_id = "local-lvm"
|
||||
|
||||
user = {
|
||||
name = "ansible"
|
||||
keys = [var.ssh_public_key]
|
||||
}
|
||||
|
||||
dns = {
|
||||
domain = var.dns_domain
|
||||
servers = [var.dns_server]
|
||||
}
|
||||
}
|
||||
|
||||
# EFI disk
|
||||
vm_efi_disk = {
|
||||
datastore_id = "local-lvm"
|
||||
}
|
||||
|
||||
# Tags for organization
|
||||
vm_tags = ["terraform", "docker-host", "production"]
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Step 2: Register IP in NetBox with DNS Name
|
||||
# =============================================================================
|
||||
|
||||
resource "netbox_ip_address" "docker_host" {
|
||||
ip_address = "${var.ip_address}/24"
|
||||
dns_name = var.fqdn # e.g., docker-01-nexus.spaceships.work
|
||||
status = "active"
|
||||
description = var.vm_description
|
||||
|
||||
# CRITICAL: This tag triggers automatic DNS sync via netbox-powerdns-sync plugin
|
||||
tags = [
|
||||
"terraform",
|
||||
"production-dns", # ← Matches zone rule in NetBox PowerDNS Sync plugin
|
||||
"docker-host"
|
||||
]
|
||||
|
||||
# Ensure VM is created first
|
||||
depends_on = [module.docker_host]
|
||||
|
||||
lifecycle {
|
||||
# Prevent accidental deletion of IP registration
|
||||
prevent_destroy = false # Set to true for production
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Step 3: DNS Records Created Automatically
|
||||
# =============================================================================
|
||||
#
|
||||
# The netbox-powerdns-sync plugin automatically creates:
|
||||
# - 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
|
||||
#
|
||||
# No manual DNS configuration needed!
|
||||
#
|
||||
# Verify with:
|
||||
# dig @192.168.3.1 docker-01-nexus.spaceships.work +short
|
||||
# dig @192.168.3.1 -x 192.168.1.100 +short
|
||||
|
||||
# =============================================================================
|
||||
# Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "vm_id" {
|
||||
description = "Proxmox VM ID"
|
||||
value = module.docker_host.vm_id
|
||||
}
|
||||
|
||||
output "vm_name" {
|
||||
description = "VM name"
|
||||
value = module.docker_host.vm_name
|
||||
}
|
||||
|
||||
output "vm_ip" {
|
||||
description = "VM IP address"
|
||||
value = var.ip_address
|
||||
}
|
||||
|
||||
output "fqdn" {
|
||||
description = "Fully qualified domain name (with automatic DNS)"
|
||||
value = var.fqdn
|
||||
}
|
||||
|
||||
output "netbox_ip_id" {
|
||||
description = "NetBox IP address ID"
|
||||
value = netbox_ip_address.docker_host.id
|
||||
}
|
||||
|
||||
output "ssh_command" {
|
||||
description = "SSH command to access the VM"
|
||||
value = "ssh ansible@${var.fqdn}"
|
||||
}
|
||||
|
||||
output "verification_commands" {
|
||||
description = "Commands to verify DNS resolution"
|
||||
value = {
|
||||
forward = "dig @${var.dns_server} ${var.fqdn} +short"
|
||||
reverse = "dig @${var.dns_server} -x ${var.ip_address} +short"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
variable "proxmox_endpoint" {
|
||||
description = "Proxmox API endpoint"
|
||||
type = string
|
||||
default = "https://192.168.3.5:8006"
|
||||
}
|
||||
|
||||
variable "proxmox_node" {
|
||||
description = "Proxmox node for deployment"
|
||||
type = string
|
||||
default = "foxtrot"
|
||||
}
|
||||
|
||||
variable "netbox_url" {
|
||||
description = "NetBox server URL"
|
||||
type = string
|
||||
default = "https://netbox.spaceships.work"
|
||||
}
|
||||
|
||||
variable "netbox_api_token" {
|
||||
description = "NetBox API token"
|
||||
type = string
|
||||
sensitive = true
|
||||
# Set via: export TF_VAR_netbox_api_token="your-token"
|
||||
# Or use: NETBOX_API_TOKEN environment variable
|
||||
}
|
||||
|
||||
variable "template_id" {
|
||||
description = "Proxmox template VMID to clone from"
|
||||
type = number
|
||||
default = 9000
|
||||
}
|
||||
|
||||
variable "vm_name" {
|
||||
description = "VM name (short, used in Proxmox)"
|
||||
type = string
|
||||
default = "docker-01-nexus"
|
||||
}
|
||||
|
||||
variable "fqdn" {
|
||||
description = "Fully qualified domain name (must follow naming convention: <service>-<NN>-<purpose>.<domain>)"
|
||||
type = string
|
||||
default = "docker-01-nexus.spaceships.work"
|
||||
|
||||
validation {
|
||||
condition = can(regex("^[a-z0-9-]+-\\d{2}-[a-z0-9-]+\\.[a-z0-9.-]+$", var.fqdn))
|
||||
error_message = "FQDN must follow naming convention: <service>-<NN>-<purpose>.<domain> (e.g., docker-01-nexus.spaceships.work)"
|
||||
}
|
||||
}
|
||||
|
||||
variable "vm_description" {
|
||||
description = "VM description (shown in both Proxmox and NetBox)"
|
||||
type = string
|
||||
default = "Docker host for Nexus container registry"
|
||||
}
|
||||
|
||||
variable "ip_address" {
|
||||
description = "Static IP address (without CIDR)"
|
||||
type = string
|
||||
default = "192.168.1.100"
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "Network gateway"
|
||||
type = string
|
||||
default = "192.168.1.1"
|
||||
}
|
||||
|
||||
variable "network_bridge" {
|
||||
description = "Proxmox network bridge"
|
||||
type = string
|
||||
default = "vmbr0"
|
||||
}
|
||||
|
||||
variable "vlan_id" {
|
||||
description = "VLAN ID (null for no VLAN)"
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "dns_domain" {
|
||||
description = "DNS domain for cloud-init"
|
||||
type = string
|
||||
default = "spaceships.work"
|
||||
}
|
||||
|
||||
variable "dns_server" {
|
||||
description = "DNS server IP"
|
||||
type = string
|
||||
default = "192.168.3.1"
|
||||
}
|
||||
|
||||
variable "ssh_public_key" {
|
||||
description = "SSH public key for VM access"
|
||||
type = string
|
||||
# Set via: export TF_VAR_ssh_public_key="$(cat ~/.ssh/id_rsa.pub)"
|
||||
}
|
||||
Reference in New Issue
Block a user