Hace unos días noté algo extraño en mi homelab: los dominios internos como grafana.chocolandiadc.local o homeassistant.chocolandiadc.local dejaron de resolver. Los sitios públicos funcionaban perfectamente, pero mis servicios locales eran inaccesibles.

Mi Setup

Tengo un cluster K3s corriendo en mi homelab con Pi-hole desplegado como el DNS principal de la red. Mi router eero está configurado para usar Pi-hole como DNS primario, lo que significa que todas las consultas DNS de mis ~40 dispositivos pasan por el eero antes de llegar a Pi-hole.

None

El Síntoma

El síntoma era confuso: podía navegar a Google, YouTube, cualquier sitio público sin problemas. Pero cuando intentaba acceder a grafana.chocolandiadc.local, el navegador mostraba DNS_PROBE_FINISHED_NXDOMAIN.

Lo que estaba pasando era esto:

sequenceDiagram
    participant D as Dispositivo
    participant E as eero Router
    participant P as Pi-hole (Primario)
    participant S as DNS Secundario (8.8.8.8)

    D->>E: Query: grafana.chocolandiadc.local
    E->>P: Forward query
    P--xE: REFUSED (rate-limited)

    Note over E: Pi-hole rechazó la query.<br/>Fallback a DNS secundario.

    E->>S: Query: grafana.chocolandiadc.local
    S-->>E: NXDOMAIN (no existe)
    E-->>D: NXDOMAIN

    Note over D: ❌ No puede resolver<br/>dominios internos

El eero, al recibir REFUSED de Pi-hole, hacía fallback a su DNS secundario (8.8.8.8). Pero Google DNS no conoce mis dominios internos .chocolandiadc.local, así que respondía NXDOMAIN. Los dominios públicos funcionaban porque el DNS secundario sí podía resolverlos.

El Problema Real

Al revisar los logs de Pi-hole, encontré los warnings que explicaban todo:

[DNSMASQ_WARN] Maximum number of concurrent DNS queries reached (150)

[RATE_LIMIT] Client 192.168.4.1 has been rate-limited
             (current config allows up to 1000 queries in 60 seconds)
None

Pi-hole estaba viendo todas las queries de mi red como si vinieran de un solo cliente (192.168.4.1 - el eero). Cuando mis 40 dispositivos generaban más de 1000 queries en un minuto (lo cual es completamente normal con Smart TVs, IoT, etc.), Pi-hole bloqueaba al eero, causando el fallback al DNS secundario.

La Investigación

Configuración Default de Pi-hole v6

Pi-hole v6 trajo cambios significativos en cómo se configura. El viejo archivo pihole-FTL.conf ya no existe, y las opciones de configuración ahora usan un formato diferente.

Revisé los valores default:

Parámetro                    Valor Default    Problema
─────────────────────────────────────────────────────────────────────────
dns.rateLimit.count          1000             Muy bajo para router forwarder
dns.rateLimit.interval       60               (segundos)
dns-forward-max              150              Muy bajo para 40+ dispositivos
dns.rateLimit.exempt         NO EXISTE        Pi-hole v6 eliminó esta opción

El Descubrimiento sobre Pi-hole v6

Descubrí algo importante: Pi-hole v6 eliminó la opción de eximir IPs específicas del rate-limiting. En versiones anteriores existía una opción para agregar IPs a una lista de excepciones, pero en v6 esto ya no está disponible.

La solución entonces era aumentar los límites lo suficiente para manejar el tráfico agregado de toda la red.

La Solución

Mi Pi-hole corre en Kubernetes gestionado con OpenTofu (fork de Terraform). La configuración se hace mediante variables de entorno con el prefijo FTLCONF_.

Variables de Entorno para Pi-hole v6

Aquí está el detalle importante que me costó encontrar: Pi-hole v6 usa un formato específico para las variables de entorno:

Configuración                    Variable de Entorno
─────────────────────────────────────────────────────────────────────────
dns.rateLimit.count         →    FTLCONF_dns_rateLimit_count
dns.rateLimit.interval      →    FTLCONF_dns_rateLimit_interval
misc.dnsmasq_lines          →    FTLCONF_misc_dnsmasq_lines

Nótese que se usa _ (underscore) para separar los niveles de configuración, no . (punto).

Los Cambios en Terraform

Agregué tres nuevas variables a mi módulo de Pi-hole:

variable "dns_max_concurrent_queries" {
  description = "Max concurrent DNS queries via dnsmasq dns-forward-max"
  type        = number
  default     = 150
}
variable "dns_rate_limit_count" {
  description = "Max queries from a single client within the interval"
  type        = number
  default     = 1000
}
variable "dns_rate_limit_interval" {
  description = "Rate limit time interval in seconds"
  type        = number
  default     = 60
}

Y las variables de entorno en el deployment:

env {
  name  = "FTLCONF_dns_rateLimit_count"
  value = tostring(var.dns_rate_limit_count)
}
env {
  name  = "FTLCONF_dns_rateLimit_interval"
  value = tostring(var.dns_rate_limit_interval)
}
dynamic "env" {
  for_each = var.dns_max_concurrent_queries > 150 ? [1] : []
  content {
    name  = "FTLCONF_misc_dnsmasq_lines"
    value = "dns-forward-max=${var.dns_max_concurrent_queries}"
  }
}

Los Valores Finales

Parámetro                    Antes       Después     Justificación
─────────────────────────────────────────────────────────────────────────
dns.rateLimit.count          1000        10000       166 queries/seg
dns.rateLimit.interval       60          60          Sin cambios
dns-forward-max              150         1000        Soporta picos de 40+
                                                     dispositivos
None

Problema Bonus: MinIO Storage Full

Durante el proceso de aplicar estos cambios, me encontré con que mi backend de OpenTofu (MinIO corriendo en el mismo cluster) estaba al 100% de capacidad. Los backups de Velero con Kopia estaban consumiendo los 50GB del PVC.

La solución fue simple: expandir el PVC de Longhorn:

$ kubectl patch pvc minio-data -n minio \
    -p '{"spec":{"resources":{"requests":{"storage":"300Gi"}}}}'

Longhorn maneja la expansión online, así que el filesystem creció automáticamente sin necesidad de reiniciar el pod.

Lecciones Aprendidas

  1. Los síntomas pueden ser engañosos: "DNS público funciona, DNS interno no" me hizo pensar inicialmente en un problema de configuración de Pi-hole. El problema real era rate-limiting + fallback del router.
  2. El rate-limiting per-client no funciona bien con routers como DNS forwarders: Cuando todo el tráfico aparece como un solo cliente, los límites default son insuficientes.
  3. Pi-hole v6 cambió significativamente: La configuración migró de pihole-FTL.conf a variables de entorno FTLCONF_*, y algunas opciones como rateLimitExempt fueron eliminadas.
  4. El formato de las variables importa: En Pi-hole v6, dns.rateLimit.count se convierte en FTLCONF_dns_rateLimit_count (underscores, no puntos).
  5. Monitorea tu storage: Los backups pueden crecer silenciosamente hasta llenar el disco. Longhorn hace trivial expandir PVCs online.

Referencias

Este artículo documenta mi experiencia optimizando Pi-hole v6 corriendo en K3s con MetalLB. Tu configuración puede variar dependiendo de tu arquitectura de red.