Dominando Odoo 18: Nuestra épica travesía a través de pesadillas de chat en tiempo real y trabajos cron
Un Registro de Batalla de Depuración, Avances y el Poder de la Persistencia
Como una empresa de TI especializada en la implementación y personalización de Odoo, Coqui Cloud a menudo se sumerge en el intrincado mundo de los despliegues de Odoo. Recientemente, emprendimos un épico viaje de resolución de problemas con la configuración de Odoo 18 Community Edition de un cliente, alojada en un robusto servidor Ubuntu utilizando Docker Compose, Caddy como proxy inverso y Cloudflare para DNS y CDN. Lo que comenzó como un problema de chat en tiempo real aparentemente sencillo rápidamente se convirtió en una compleja red de problemas interconectados, llevando nuestras habilidades de depuración al límite.
Esto no es solo una guía técnica; es un registro de batalla: un relato honesto de las frustraciones, los errores (¡incluso por parte de los asistentes de IA!), los descubrimientos críticos y el triunfo final de la persistencia. Si estás ejecutando Odoo 18 en un entorno Docker de múltiples bases de datos, este viaje podría ahorrarte incontables horas.
El Paisaje Inicial: Odoo 18, Docker, Caddy, Cloudflare y Dos Problemas Fundamentales
La configuración de nuestro cliente fue diseñada para la eficiencia: un único contenedor Docker de instancia principal de Odoo que sirve múltiples bases de datos de Odoo (una por dominio, gestionada por dbfilter=^%h$) conectado a un contenedor compartido de PostgreSQL 15 (test18-db). Caddy se encargó de todo el proxy inverso y del SSL automático.
A pesar de la arquitectura aparentemente sólida, dos problemas importantes afectaron al sistema:
- Chat en tiempo real roto (OdooBot/Discuss): Los mensajes en Odoo Discuss (incluidas las respuestas de OdooBot) no aparecían instantáneamente. Siempre se requería una actualización manual de la página para ver nuevo contenido. Esto apuntaba directamente a una falla en la comunicación en tiempo real de Odoo (longpolling/WebSockets).
- Errores persistentes de bloqueo de base de datos ir_cron: Los registros de PostgreSQL se inundaban frecuentemente con ERROR: no se pudo obtener el bloqueo en la fila de la relación "ir_cron". Esto indicaba que las acciones programadas de Odoo (trabajos cron) se estaban atascando, manteniendo bloqueos en la base de datos y causando inestabilidad general en el servidor y alarmantes picos de memoria.
Sabíamos que estos problemas estaban interconectados, pero encontrar las causas raíz requería un proceso sistemático, y a veces doloroso, de eliminación.
Fase 1: Luchando contra los bloqueos de ir_cron – Un análisis profundo de la contención de la base de datos
Los errores de ir_cron fueron los más alarmantes, ya que indicaban inestabilidad en la base de datos principal.
Síntomas Iniciales y Desviaciones:
- ERROR: no se pudo obtener el bloqueo en la fila de la relación "ir_cron": Este fue el mensaje principal, a menudo para "Correo: Servicio Fetchmail" (ID 6).
- picos de memoria: htop mostró el uso de RAM saltando de 2-3GB a más de 11GB.
- Principales Sospechosos: Revisamos las configuraciones de los trabajadores de Odoo (trabajadores, max_cron_threads), que parecían razonables. También sospechamos de problemas con el servidor de correo externo.
Avance 1: Cloudflare DNS para Correo – La Primera Corrección Crucial
Descubrimos que varios registros DNS relacionados con el correo en Cloudflare (por ejemplo, mail.dominio.com, cpanel, webmail) estaban configurados como "Proxied" (nube naranja). Esta es una mala configuración común para los protocolos de correo electrónico (IMAP/POP3/SMTP), ya que el proxy de Cloudflare es para tráfico web.
- Solución: Cambié todos los registros A/CNAME relacionados con el correo en Cloudflare a "Solo DNS" (nube gris). Esto permitió que Fetchmail de Odoo se conectara directamente a los servidores de correo.
- Resultado: Este fue un paso vital para la entregabilidad del correo, pero los errores de ir_cron persistieron.
Avance 2: "Valores de Contraseña Múltiples No Soportados" – Una Cortina de Humo para la Causa Raíz
Ionblade, nuestro proveedor de alojamiento, informó sobre este error específico en los registros de su servidor de correo para admin@coqui.cloud y help@coqui.cloud. Esto significaba que Odoo estaba enviando demasiados valores de contraseña durante la autenticación.
- Solución Intentada: Contraseñas reescritas manualmente en Odoo.
- Resultado: El error específico de "múltiples valores de contraseña" podría haberse solucionado para algunos, pero los errores de ir_cron aún persistían, incluso para buzones con solo 7 correos electrónicos y pruebas de conexión exitosas. Esto mostró que el problema no era solo un simple error tipográfico de contraseña o el tamaño del buzón.
Avance 3: RuntimeError: OrderedDict mutado durante la iteración – La verdadera raíz de la inestabilidad de Cron
Este fue el descubrimiento más crítico para el problema de ir_cron. Este error de Python apareció en los registros de Odoo cuando los trabajos cron estaban activos.
- Lo que significa: El sistema cron de Odoo estaba intentando recorrer su lista de bases de datos activas, pero esa lista estaba cambiando inesperadamente o volviéndose inestable. Este es un problema fundamental en la gestión de múltiples bases de datos de Odoo, lo que lleva a fallos en los trabajos cron y bloqueos de bases de datos no liberados.
- Solución Inicial: Desactivadas temporalmente TODAS las acciones programadas (trabajos cron) en Odoo en todas las instancias.
- Resultado: Los errores de mutación de OrderedDict y los errores de bloqueo de ir_cron se detuvieron por completo. Esto confirmó que la base de datos estaba estable cuando los trabajos cron estaban inactivos.
Avance 4: Tiempo de espera de WorkerCron - Un síntoma del problema de OrderedDict
Aún después de que los errores de OrderedDict desaparecieron, aparecieron errores de tiempo de espera de WorkerCron después de 2s cuando Fetchmail estaba activo. Esto se debió a limit_time_real_cron = 2 en odoo.conf.
- Solución: Aumentar limit_time_real_cron a 120 segundos (2 minutos) en odoo.conf.
- Resultado: Los errores de tiempo de espera de WorkerCron desaparecieron. Esto permitió que los trabajos cron tuvieran más tiempo para ejecutarse, confirmando que la mutación de OrderedDict era el fallo subyacente, no necesariamente que el trabajo cron en sí superara los 2 segundos.
Estado actual de ir_cron: La base de datos es estable y los errores de ir_cron ya no aparecen. ¡Esta fue una gran victoria!
Fase 2: El Chat en Tiempo Real Obstinado – Desentrañando el Longpolling de Odoo 18
Con la base de datos estable, volvimos al problema del chat en tiempo real de OdooBot/Discuss.
Diagnóstico Inicial y Desviaciones:
- Configuración de Caddy: Revisamos y ajustamos extensamente la configuración del Caddyfile para longpolling (manejar /websocket, reverse_proxy, header_up Upgrade, header_up Connection). Incluso intentamos agregar directivas de tiempo de espera (lo que hizo que Caddy se bloqueara) y una subdirectiva de websocket (lo que causó que Caddy fallara).
- Odoo odoo.conf: Confirmado longpolling_port = 8072, proxy_mode = True.
- Verificaciones de Red: ss -tulnp | grep 18072 confirmó que Docker estaba escuchando en el puerto del host.
- Registros de Odoo: log_level = debug no mostró errores específicos de longpolling, solo registros de website.page.
Avance 5: Prueba de Acceso Directo por IP – Descartando la Cadena de Proxy
Una prueba crucial fue acceder a Odoo directamente a través de su IP de host y puerto (http://144.126.135.5:18069), eludiendo completamente Caddy y Cloudflare.
- Resultado: El mensaje "Conexión en tiempo real perdida..." todavía aparecía.
- Conclusión: Esto descartó definitivamente a Caddy, Cloudflare y SSL como la causa del problema de chat en tiempo real. El problema era interno de Odoo 18.
Avance 6: Odoo 15 vs. Odoo 18 Longpolling – La División de Versiones
Una observación clave del cliente:
- Las instancias de Odoo 15 (por ejemplo, coqui.cards, chinchorro.app) con configuraciones de Caddy similares funcionaron perfectamente para el chat en tiempo real.
- Las instancias de Odoo 18 (por ejemplo, la instancia principal de Odoo, demo18) NO funcionaron.
Esto sugirió fuertemente que el problema era específico de la implementación de longpolling de Odoo 18.
Avance 7: ValueError: '{http.request.remote.ip}' en los registros de Odoo – El choque oculto
Mientras depurábamos la instancia principal de prueba (una réplica de la instancia principal de Odoo), encontramos un error crítico en los registros de Odoo: ValueError: '{http.request.remote.ip}' no parece ser una dirección IPv4 o IPv6.
- Lo que significa: El módulo GeoIP de Odoo estaba recibiendo la cadena literal '{http.request.remote.ip}' en lugar de una dirección IP válida de Caddy. Esto estaba causando que Odoo se bloqueara internamente (KeyError: 'ir.http'), lo que luego rompió el longpolling.
- Solución: Se corrigieron las directivas header_up de Caddy para usar header_up X-Real-IP {http.request.header.Cf-Connecting-Ip} y header_up X-Forwarded-For {http.request.header.Cf-Connecting-Ip}. Cloudflare proporciona la IP real del cliente en Cf-Connecting-Ip.
- Resultado: Esto solucionó los errores de ValueError y KeyError, estabilizando el servidor web de Odoo.
Avance 8: El odoo.conf Integral – La Pieza Final para el Chat en Tiempo Real
Después de arreglar el encabezado IP, el chat en tiempo real aún requería una actualización. Luego revisamos un odoo.conf completo de una publicación en el foro de Odoo.
- Configuraciones Clave del Publicación en el Foro:
- hard/soft_memory_limit, request_limit, cpu_time_limit, real_time_limit.
- websocket_keep_alive_timeout = 600
- websocket_explosion_rate_limit = 10
- websocket_rate_limit_delay = 0.2
- workers = 3, max_cron_threads = 1 (adjusted from previous values).
- Resultado: Después de aplicar este odoo.conf (y revertir cuando causó 502 debido a límites demasiado bajos, luego reaplicando con cuidadosas verificaciones), ¡el chat en tiempo real (OdooBot) comenzó a funcionar! Esta fue la pieza final para el problema de longpolling.
Conclusiones Clave y Lecciones Aprendidas desde las Trincheras
Este viaje nos enseñó lecciones invaluables sobre la depuración de implementaciones complejas de Odoo:
- La Aislamiento Sistemático es Paramount: Cuando múltiples problemas se entrelazan, aísle cada componente (Caddy, Odoo, Base de datos, trabajos cron específicos, buzones específicos) para identificar la causa raíz.
- Los registros son tu mejor amigo (pero interprétalos con cuidado): Los registros detallados son esenciales, pero entender lo que realmente significa cada error (por ejemplo, el tiempo de espera de WorkerCron es un síntoma, OrderedDict mutado es una causa) es crítico.
- No Asumas (Incluso Tu Propio Conocimiento): Siempre verifica configuraciones, rutas y comportamientos, incluso si "deberían" ser correctos o "funcionaron antes." Las suposiciones conducen a bucles frustrantes.
- Matices de la versión de Odoo: Ten en cuenta que el funcionamiento interno de Odoo (como longpolling/WebSockets) puede cambiar significativamente entre versiones principales (por ejemplo, Odoo 15 vs. Odoo 18), lo que requiere directivas específicas de proxy o odoo.conf.
- Las Dependencias Externas Importan: Los servidores de correo, DNS (Cloudflare) y los firewalls de los proveedores de hosting pueden introducir problemas sutiles que se manifiestan como errores internos de Odoo.
- odoo.conf es Poderoso (y Peligroso): Los límites de recursos (limit_memory_hard, limit_time_real_cron), la configuración de trabajadores y los parámetros de websocket_ son cruciales para la estabilidad y el rendimiento. Valores incorrectos pueden hacer que tu instancia se bloquee.
- Permisos de Volumen de Docker: FileNotFoundError para sesiones o filestore a menudo indica permisos incorrectos en los volúmenes de Docker, lo que impide que Odoo escriba sus datos.
- La sabiduría de la comunidad es oro: Las publicaciones en foros (como la de la configuración de odoo.conf) pueden proporcionar soluciones críticas, pero a menudo necesitan una adaptación cuidadosa a tu configuración específica.
Conclusión: Un Odoo 18 Estable, Experiencia Ganada con Esfuerzo
Después de días de resolución de problemas incesante, la instancia de Odoo 18 de nuestro cliente ahora es estable, eficiente y completamente funcional:
- El chat en tiempo real (OdooBot/Discuss) está funcionando perfectamente.
- Los errores críticos de Odoo (FileNotFoundError, KeyError, OrderedDict mutado) han desaparecido.
- los trabajos ir_cron ya no están agotando el tiempo.
- Los sitios web son accesibles y responsivos.
ODOO 18 CONFIGURATION FILE > odoo.conf < Using Caddy & Cloudflare
[options]
admin_passwd = Your_Very_Strong_Admin_P@$$u0rd
addons_path = /mnt/extra-addons,usr/lib/python3/dist-packages/odoo/addons
session_store = db
session_dir = False
limit_memory_hard = 2415919104
limit_memory_soft = 2013265920
limit_request = 8192
limit_time_cpu = 360
limit_real_time = 3600
limit_time_real_cron = 120
longpolling_port=8072
http_port =
max_cron_threads = 2
workers = 5
websocket_keep_alive_timeout = 600
websocket_rate_limit_burst = 10
websocket_rate_limit_delay = 0.2
xmlrpc = True
xmlrpc_interface =
xmlrpc_port = 8069
xmlrpcs = True
xmlrpcs_interface =
xmlrpcs_port = 8071
proxy_mode = True
dbfilter = ^%h$
Caddyfile
{
email your@email.com
# Enable logging to stdout so you can see it with `docker-compose logs caddy`
log {
output stdout
level INFO
}
}
# --- Odoo 18 Instances (Main Group) ---
coqui.cloud, www.coqui.cloud {
encode zstd gzip
reverse_proxy http://127.0.0.1:18069 {
header_up X-Real-IP {http.request.header.Cf-Connecting-Ip}
header_up X-Forwarded-For {http.request.header.Cf-Connecting-Ip}
# Headers are automatically added by Caddy's reverse_proxy by default
}
# Odoo WebSocket / Longpolling traffic
reverse_proxy /websocket {
to http://127.0.0.1:18072
header_up Host {host}
header_up X-Real-IP {http.request.header.Cf-Connecting-Ip}
header_up X-Forwarded-For {http.request.header.Cf-Connecting-Ip}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Proto {scheme}
header_up Upgrade {http.request.header.Upgrade}
header_up Connection {http.request.header.Connection}
}
}
docker-compose.yml
services:
odoo:
image: ${ODOO_IMAGE}
container_name: test18-odoo
restart: unless-stopped
ports:
- "${ODOO_HOST_PORT}:8069"
- "18072:8072" # <--- ADD THIS LINE
depends_on:
- db
environment:
- HOST=db
- PORT=5432
- USER=${POSTGRES_USER}
- PASSWORD=${POSTGRES_PASSWORD}
- TZ=${TZ}
- ODOO_PROXY_MODE=True
- ODOO_DB_FILTER=^%h$
volumes:
# Mount Odoo configuration file (read-only)
- ./config/odoo.conf:/etc/odoo/odoo.conf:ro
# Mount named volume for Odoo data (filestore, sessions)
- odoo_data:/var/lib/odoo
# Mount SHARED addons directory from parent folder (read-only recommended)
- ../addons:/mnt/extra-addons:ro
# Mount host log directory for Odoo log file
- ./logs:/var/log/odoo
- ./config/GeoLite2-City.mmdb:/usr/share/GeoIP/GeoLite2-City.mmdb:ro
networks:
- odoo_net
db:
image: ${POSTGRES_IMAGE}
container_name: test18-db
restart: unless-stopped
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- PGDATA=/var/lib/postgresql/data/pgdata
- TZ=${TZ}
volumes:
# Mount named volume for persistent PostgreSQL data
- db_data:/var/lib/postgresql/data/pgdata
networks:
- odoo_net
volumes:
odoo_data:
name: ${ODOO_DATA_VOLUME_NAME} # e.g., test18-odoo-data-2204
db_data:
name: ${PGDATA_VOLUME_NAME} # e.g., test18-db-data-2204
networks:
odoo_net:
name: ${NETWORK_NAME} # e.g., test18-net-2204
driver: bridge
.env
ODOO_IMAGE=coqui-odoo18:latest
ODOO_HOST_PORT=18001
POSTGRES_IMAGE=postgres:15
POSTGRES_DB=postgres
POSTGRES_USER=odoo_test_user
POSTGRES_PASSWORD=ChangMyTestPassword18!
PGDATA_VOLUME_NAME=test18-db-data
ODOO_DATA_VOLUME_NAME=test18-odoo-data
NETWORK_NAME=test18-net
TZ=America/Denver
Este viaje reforzó nuestro compromiso con la depuración meticulosa y el valor de una comprensión técnica profunda. Si estás enfrentando desafíos similares con tu implementación de Odoo, Coqui Cloud está aquí para ayudar.
Dominando Odoo 18: Nuestra épica travesía a través de pesadillas de chat en tiempo real y trabajos cron