1. Despliegue
1.1. Requisitos previos
-
Docker 20.10+
-
Docker Compose 2.0+
-
4 GB de RAM mínimo (8 GB recomendados para producción)
-
Acceso de red al bróker global WIS2 (puerto 443)
1.2. Instalación
git clone https://github.com/World-Meteorological-Organization/wis2downloader
cd wis2downloader
cp default.env .env
Edite .env para configurar su despliegue, luego inicie:
docker-compose up -d
1.3. Configuración de seguridad
Antes de desplegar en producción, debe configurar estos ajustes de seguridad en su archivo .env:
|
-
REDIS_PASSWORD- Establezca una contraseña única y segura (obligatorio) -
FLASK_SECRET_KEY- Establezca una clave secreta criptográficamente aleatoria (obligatorio) -
FLASK_DEBUG- Establezca enfalsepara producción -
Contraseña de Grafana - Cambie el valor predeterminado
admin/admintras el primer inicio de sesión
El archivo .env contiene credenciales sensibles. Asegúrese de que:
-
No esté versionado (agréguelo a
.gitignore) -
Solo sea legible por el usuario de despliegue (
chmod 600 .env) -
Esté respaldado de forma segura
1.3.1. Control de acceso
| WIS2 Downloader no implementa autenticación ni autorización en la interfaz web ni en la API REST del gestor de suscripciones. Cualquier persona con acceso de red a la interfaz (puerto 8080) o a la API (puerto 5002) puede ver, crear, modificar y eliminar suscripciones. |
En un entorno compartido o expuesto a Internet, debe restringir el acceso a nivel de red. Los enfoques recomendados son:
-
Proxy inverso con autenticación — Coloque nginx o Caddy delante de los puertos de la interfaz y la API, aplicando autenticación básica HTTP u OAuth antes de reenviar las solicitudes. Una configuración mínima de nginx:
location / { auth_basic "WIS2 Downloader"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_pass http://localhost:8080; } -
Reglas de cortafuegos — Restrinja los puertos 8080 y 5002 a rangos de IP de confianza usando
iptables,ufwo grupos de seguridad en la nube. -
VPN / red privada — Despliegue detrás de una VPN para que solo los usuarios VPN autenticados puedan acceder a los servicios.
Si el sistema se ejecuta en un servidor en lugar de en localhost, asegúrese de que los puertos 8080 y 5002 no sean accesibles públicamente antes de añadir uno de los controles de acceso anteriores.
1.4. Permisos de volúmenes
Los contenedores de la aplicación se ejecutan como usuario no root (wis2) por razones de seguridad. Para permitir que los contenedores escriban archivos descargados en el sistema de archivos del host, debe configurar los permisos adecuados en el directorio downloads/.
1.4.1. Detalles del usuario del contenedor
| Propiedad | Valor |
|---|---|
Nombre de usuario |
|
Identificador de usuario (UID) |
|
Nombre del grupo |
|
Identificador de grupo (GID) |
|
El worker Celery escribe archivos en /data dentro del contenedor, que se mapea a ./downloads/ en el host.
1.4.2. Opción 1: Crear usuario/grupo coincidente (Recomendado)
Cree un usuario y grupo en el sistema host que coincidan con el UID/GID del contenedor:
# Crear el grupo wis2 con GID 988
sudo groupadd -g 988 wis2
# Crear el usuario wis2 con UID 10001
sudo useradd -u 10001 -g wis2 -M -s /usr/sbin/nologin wis2
# Establecer propiedad en el directorio de descargas
sudo mkdir -p downloads
sudo chown -R wis2:wis2 downloads
1.4.3. Opción 2: Usar ACL (Flexible)
Si no puede crear un usuario/grupo coincidente, use listas de control de acceso POSIX para otorgar acceso de escritura al UID del contenedor:
# Asegurarse de que el soporte ACL está instalado
sudo apt-get install acl # Debian/Ubuntu
sudo yum install acl # RHEL/CentOS
# Crear el directorio de descargas
mkdir -p downloads
# Conceder acceso completo a UID 10001
sudo setfacl -R -m u:10001:rwx downloads
sudo setfacl -R -d -m u:10001:rwx downloads # Default ACL for new files
1.4.4. Opción 3: Directorio permisivo
| Este enfoque es menos seguro y no se recomienda para producción. |
mkdir -p downloads
chmod 777 downloads
1.4.5. Verificación de permisos
Pruebe que el contenedor puede escribir en el volumen:
# Iniciar el contenedor celery
docker-compose up -d celery
# Probar el acceso de escritura desde dentro del contenedor
docker exec celery touch /data/test-write && echo "Write OK" || echo "Write FAILED"
# Limpiar
docker exec celery rm -f /data/test-write
Si la prueba falla con "Permission denied", revise la propiedad y los permisos del directorio downloads/ en el host.
1.4.6. Acceso compartido con usuarios del host
Si necesita que los usuarios del host lean los archivos descargados, agréguelos al grupo wis2:
# Agregar su usuario al grupo wis2
sudo usermod -aG wis2 $USER
# Cerrar sesión y volver a iniciarla para que el grupo tenga efecto
# Luego verificar
groups # Should include 'wis2'
También puede usar ACL para otorgar acceso de lectura a usuarios específicos:
# Conceder acceso de lectura a un usuario específico
sudo setfacl -R -m u:youruser:rx downloads
sudo setfacl -R -d -m u:youruser:rx downloads
1.5. Verificación del despliegue
Los ejemplos a continuación usan localhost. Si el sistema está desplegado en un servidor remoto, reemplace localhost por el nombre de host o la dirección IP del servidor. Los puertos (5002 para la API, 8080 para la interfaz) pueden cambiarse en docker-compose.yaml si es necesario.
|
# Verificar que todos los servicios están en ejecución
docker-compose ps
# Verificar el punto de control de salud (gestor de suscripciones)
curl http://localhost:5002/health
# Verificar que la interfaz web está activa
curl -o /dev/null -s -w "%{http_code}" http://localhost:8080
# Ver registros
docker-compose logs -f subscriber
2. Configuración
2.1. Variables de entorno
2.1.1. Configuración de la aplicación
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
|
Nivel de registro: DEBUG, INFO, WARNING, ERROR |
|
path |
|
Directorio base para archivos descargados |
|
string |
obligatorio |
Clave secreta de sesión Flask (la aplicación falla si no está configurada) |
|
boolean |
|
Habilitar modo debug de Flask (establecer en |
|
string |
|
Dirección de enlace de la API |
|
integer |
|
Puerto de enlace de la API (interno) |
2.1.2. Configuración del bróker MQTT
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
obligatorio |
Nombre de host del bróker global WIS2 (ej.: |
|
integer |
|
Puerto del bróker |
|
string |
|
Nombre de usuario MQTT |
|
string |
|
Contraseña MQTT |
|
string |
|
Protocolo de transporte: |
|
string |
generado automáticamente |
ID de sesión persistente para reanudar tras reinicio |
2.1.3. Configuración de Redis
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
|
Nombre de host del servidor Redis |
|
integer |
|
Puerto del servidor Redis |
|
integer |
|
Número de base de datos Redis |
|
string |
obligatorio |
Contraseña de autenticación Redis (la aplicación falla si no está configurada) |
|
integer |
|
TTL para claves de deduplicación |
|
integer |
|
Expiración del bloqueo para prevención de descargas concurrentes |
2.1.4. Configuración de Celery
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
|
Cadena de conexión al bróker Celery (notar formato de contraseña) |
|
string |
|
Cadena de conexión al backend de resultados (notar formato de contraseña) |
2.1.5. Filtrado de descargas
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
vacío |
Lista separada por comas de nombres de host de caché global a excluir |
2.1.6. Configuración de la interfaz web
| Variable | Tipo | Valor predeterminado | Descripción |
|---|---|---|---|
|
string |
URL interna que usa la interfaz para acceder a la API del gestor de suscripciones |
|
|
string |
URL utilizada para incrustar paneles de Grafana en la vista Panel de control |
|
|
string |
|
Clave secreta de almacenamiento de sesión NiceGUI — establezca un valor único en producción |
|
integer |
|
Segundos para almacenar en caché los datos del catálogo GDC en Redis (6 horas por defecto); establezca en |
2.2. Estructura de archivos
wis2downloader/
├── config/
│ ├── grafana/provisioning/ # Grafana datasource config
│ ├── loki/ # Loki configuration
│ └── prometheus/ # Prometheus scrape config
├── containers/ # Dockerfiles
├── downloads/ # Downloaded data (mounted volume)
├── modules/ # Python modules
│ ├── shared/ # Shared utilities
│ ├── subscriber/ # MQTT subscriber
│ ├── subscription_manager/ # REST API
│ ├── task_manager/ # Celery tasks
│ └── ui/ # NiceGUI web interface
├── docker-compose.yaml
├── default.env
└── README.adoc
3. Escalado
3.1. Redis
El sistema utiliza una única instancia Redis para:
-
Mensajería pub/sub entre servicios
-
Almacenamiento del estado de suscripciones
-
Seguimiento de deduplicación
-
Cola de tareas Celery y resultados
El sistema usa una única instancia Redis y no proporciona alta disponibilidad. Independientemente de la escala, asegúrese de que los datos Redis sean duraderos:
-
Habilite la persistencia Redis (AOF + RDB)
-
Implemente copias de seguridad regulares del dump Redis
3.2. Escalado de workers
Aumente la concurrencia del worker Celery para mayor rendimiento:
# docker-compose.yaml
celery:
command: ["... --concurrency=16 ..."]
O ejecute múltiples contenedores worker:
docker-compose up -d --scale celery=3
3.3. Agregar brókers globales
Cada servicio suscriptor se conecta a un único bróker global WIS2. Para recibir datos de múltiples brókers, agregue servicios suscriptores adicionales en docker-compose.yaml:
subscriber-france:
container_name: subscriber-france
restart: always
build:
context: .
dockerfile: ./containers/subscriber/Dockerfile
env_file: *default-env
environment:
GLOBAL_BROKER_HOST: globalbroker.meteo.fr
GLOBAL_BROKER_PORT: 443
GLOBAL_BROKER_USERNAME: everyone
GLOBAL_BROKER_PASSWORD: everyone
MQTT_PROTOCOL: websockets
depends_on:
- redis
networks:
- redis-net
logging: *default-logging
subscriber-china:
container_name: subscriber-china
restart: always
build:
context: .
dockerfile: ./containers/subscriber/Dockerfile
env_file: *default-env
environment:
GLOBAL_BROKER_HOST: gb.wis.cma.cn
GLOBAL_BROKER_PORT: 443
GLOBAL_BROKER_USERNAME: everyone
GLOBAL_BROKER_PASSWORD: everyone
MQTT_PROTOCOL: websockets
depends_on:
- redis
networks:
- redis-net
logging: *default-logging
Cada suscriptor debe tener un container_name único.
|
Todos los suscriptores comparten el mismo backend Redis, por lo que las suscripciones creadas a través de la API son recibidas por todas las instancias de suscriptores. Las descargas están deduplicadas, por lo que el mismo archivo no se descargará dos veces aunque las notificaciones lleguen de múltiples brókers.
4. Monitoreo
4.1. Métricas de Prometheus
Punto de acceso de métricas: http://localhost:5002/metrics
Las métricas se almacenan de forma atómica en Redis usando HINCRBYFLOAT y se exponen en el punto de acceso /metrics en formato de texto Prometheus. Este enfoque es seguro en múltiples contenedores de workers Celery sin requerir un sistema de archivos compartido ni el directorio multiproceso de prometheus_client.
|
4.1.1. Métricas disponibles
| Métrica | Tipo | Descripción |
|---|---|---|
|
Counter |
Mensajes MQTT recibidos antes de encolar (etiquetas: broker); compare con |
|
Counter |
Total de notificaciones procesadas por Celery (etiquetas: status) |
|
Counter |
Archivos descargados correctamente (etiquetas: cache, media_type) |
|
Counter |
Total de bytes descargados (etiquetas: cache, media_type) |
|
Counter |
Notificaciones omitidas por motivo (etiquetas: reason) |
|
Counter |
Descargas fallidas (etiquetas: cache, reason) |
|
Counter |
Fallos al encolar una tarea Celery desde el suscriptor (etiquetas: broker) |
|
Gauge |
Número actual de tareas en la cola Celery |
|
Gauge |
Capacidad total de disco del volumen de descargas |
|
Gauge |
Espacio de disco utilizado en el volumen de descargas |
|
Gauge |
Espacio de disco disponible en el volumen de descargas |
|
Gauge |
Bytes utilizados por los archivos descargados (seguimiento incremental) |
4.1.2. Consultas de ejemplo
# Tasa de descarga por minuto
sum(rate(wis2downloader_downloads_total[1m]))
# Descargas por caché
sum by (cache) (rate(wis2downloader_downloads_total[5m]))
# Descargas fallidas por razón
sum by (reason) (wis2downloader_failed_total)
# Bytes descargados en la última hora
sum(increase(wis2downloader_downloads_bytes_total[1h]))
# Profundidad de la cola
wis2downloader_celery_queue_length
4.2. Grafana
Acceda a Grafana en http://localhost:3000 (credenciales predeterminadas: admin/admin)
Cambie la contraseña predeterminada de Grafana tras el primer inicio de sesión o configúrela mediante la variable de entorno GF_SECURITY_ADMIN_PASSWORD en despliegues de producción.
|
Fuentes de datos preconfiguradas:
-
Prometheus -
http://prometheus:9090 -
Loki -
http://loki:3100
4.3. Registro
Todos los servicios registran en stdout con marcas de tiempo UTC:
2026-01-28T10:15:30.123Z subscriber INFO Connected successfully
Consulte los registros mediante Docker:
# Todos los servicios
docker-compose logs -f
# Servicio específico
docker-compose logs -f celery
# Con marcas de tiempo
docker-compose logs -f -t subscriber
Los registros también son recopilados por Loki y consultables en Grafana.
5. Mantenimiento
5.1. Copia de seguridad
5.1.1. Datos Redis
# Activar instantánea RDB
docker exec redis redis-cli -a $REDIS_PASSWORD BGSAVE
# Copiar instantánea
docker cp redis:/data/dump.rdb ./backup/
5.1.2. Archivos descargados
# Respaldar el directorio de descargas
tar -czf wis2-downloads-$(date +%Y%m%d).tar.gz downloads/
5.2. Borrado de datos
| Estas operaciones son destructivas. |
# Borrar todas las suscripciones
docker exec redis redis-cli -a $REDIS_PASSWORD DEL global:subscriptions
# Borrar caché de deduplicación (permite volver a descargar todos los datos)
docker exec redis redis-cli -a $REDIS_PASSWORD KEYS "wis2:notifications:*" | xargs -r docker exec -i redis redis-cli -a $REDIS_PASSWORD DEL
# Borrar archivos descargados
rm -rf downloads/*
5.3. Reinicio de servicios
# Reiniciar un servicio individual
docker-compose restart subscriber
# Reiniciar todos los servicios
docker-compose restart
# Reconstrucción completa (después de cambios de código)
docker-compose build && docker-compose up -d
6. Solución de problemas
6.1. El servicio no inicia
# Verificar registros
docker-compose logs <service-name>
# Verificar estado del contenedor
docker-compose ps
# Verificar entorno
docker-compose config
6.2. Sin descargas
-
Verifique que el suscriptor está conectado:
docker-compose logs subscriber | grep -i connect -
Verifique que existe la suscripción:
curl http://localhost:5002/subscriptions -
Verifique que el worker Celery está procesando:
docker-compose logs celery | tail -50 -
Verifique la profundidad de la cola:
curl -s http://localhost:5002/metrics | grep queue_length
6.3. Errores de conexión Redis
-
Verifique que Redis está en ejecución:
docker exec redis redis-cli -a $REDIS_PASSWORD PING -
Verifique la conectividad de red:
docker network inspect wis2downloader_redis-net
6.4. Uso elevado de memoria
-
Aumente
max-tasks-per-childpara reciclar workers con más frecuencia -
Reduzca la concurrencia de workers
-
Verifique si hay archivos grandes sobrecargando los workers
6.5. Las métricas no se actualizan
-
El intervalo de scraping de Prometheus es de 15 segundos por defecto
-
Verifique los objetivos de Prometheus a través de Grafana: Connections → Data sources → Prometheus → Save & test
-
Verifique el punto de acceso de métricas:
curl http://localhost:5002/metrics
6.6. Interfaz web: datos del catálogo no cargados
Si las vistas de catálogo o árbol muestran "Catalogue data not loaded":
-
Verifique si el contenedor de la interfaz inició correctamente:
docker-compose logs wis2downloader-ui | tail -30 -
Confirme que las claves de caché GDC existen en Redis:
docker exec redis redis-cli -a $REDIS_PASSWORD KEYS "gdc:cache:*"Si no hay claves presentes, la obtención inicial falló o aún está en progreso. Espere un momento y recargue la página.
-
Fuerce una obtención nueva desde la vista Configuración (
http://localhost:8080) haciendo clic en Actualizar datos GDC, o reiniciando el contenedor de la interfaz:docker-compose restart wis2downloader-ui -
Si la obtención de GDC falla constantemente, verifique la conectividad de red desde el contenedor de la interfaz hacia los puntos de acceso GDC públicos:
docker exec wis2downloader-ui curl -s -o /dev/null -w "%{http_code}" \ https://wis2-gdc.weather.gc.ca/collections/wis2-discovery-metadata/items?limit=1