本文档由 Claude AI 从英文自动翻译。 WMO/气象领域术语在正式使用前应由母语人士审阅。 请参阅 英文原版 以获取权威版本。

1. 部署

1.1. 前提条件

  • Docker 20.10+

  • Docker Compose 2.0+

  • 最少 4GB 内存(生产环境建议 8GB)

  • 能访问 WIS2 全球代理的网络(端口 443)

1.2. 安装

git clone https://github.com/World-Meteorological-Organization/wis2downloader
cd wis2downloader
cp default.env .env

编辑 .env 配置您的部署,然后启动:

docker-compose up -d

1.3. 安全配置

在部署到生产环境之前,必须在 .env 文件中配置以下安全设置:
  1. REDIS_PASSWORD - 设置一个强大的唯一密码(必填)

  2. FLASK_SECRET_KEY - 设置一个加密随机密钥(必填)

  3. FLASK_DEBUG - 生产环境中设置为 false

  4. Grafana 密码 - 首次登录后从默认的 admin/admin 更改

.env 文件包含敏感凭据。请确保:

  • 不提交到版本控制(添加到 .gitignore

  • 只有部署用户可读(chmod 600 .env

  • 安全备份

1.3.1. 访问控制

WIS2 Downloader 在 Web 界面和订阅管理器 REST API 上未实现身份验证或授权。任何能够访问 UI(端口 8080)或 API(端口 5002)的网络用户都可以查看、创建、修改和删除订阅。

在共享或面向互联网的环境中,您*必须*在网络层面限制访问。推荐的方法:

  • 使用身份验证的反向代理 — 在 UI 和 API 端口前部署 nginx 或 Caddy,在转发请求前强制执行 HTTP Basic Auth 或 OAuth。一个最简 nginx 配置示例:

    location / {
        auth_basic           "WIS2 Downloader";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass           http://localhost:8080;
    }
  • 防火墙规则 — 使用 iptablesufw 或云安全组将端口 8080 和 5002 限制到受信任的 IP 范围。

  • VPN / 私有网络 — 部署在 VPN 后面,使只有经过身份验证的 VPN 用户才能访问服务。

如果系统运行在服务器而非本机,请在添加上述访问控制之一之前,确保端口 8080 和 5002 无法公开访问。

1.4. 卷权限

应用容器以非 root 用户(wis2)运行以确保安全。要允许容器将下载的文件写入主机文件系统,必须在 downloads/ 目录上配置适当的权限。

1.4.1. 容器用户详情

属性

用户名

wis2

用户 ID(UID)

10001

组名

wis2

组 ID(GID)

988

Celery worker 在容器内将文件写入 /data,对应主机上的 ./downloads/

1.4.2. 选项 1:创建匹配的用户/组(推荐)

在主机系统上创建与容器 UID/GID 匹配的用户和组:

# 创建 GID 为 988 的 wis2 组
sudo groupadd -g 988 wis2

# 创建 UID 为 10001 的 wis2 用户
sudo useradd -u 10001 -g wis2 -M -s /usr/sbin/nologin wis2

# 设置下载目录的所有权
sudo mkdir -p downloads
sudo chown -R wis2:wis2 downloads

1.4.3. 选项 2:使用 ACL(灵活)

如果无法创建匹配的用户/组,使用 POSIX 访问控制列表为容器的 UID 授予写访问权限:

# 确保已安装 ACL 支持
sudo apt-get install acl  # Debian/Ubuntu
sudo yum install acl      # RHEL/CentOS

# 创建下载目录
mkdir -p downloads

# 授予 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. 选项 3:宽松目录

此方式安全性较低,不建议在生产环境中使用。
mkdir -p downloads
chmod 777 downloads

1.4.5. 验证权限

测试容器是否可以写入卷:

# 启动 celery 容器
docker-compose up -d celery

# 从容器内部测试写入权限
docker exec celery touch /data/test-write && echo "Write OK" || echo "Write FAILED"

# 清理
docker exec celery rm -f /data/test-write

如果测试失败并显示"Permission denied",请检查主机上 downloads/ 目录的所有权和权限。

1.4.6. 与主机用户共享访问

如果需要主机用户读取下载的文件,将其添加到 wis2 组:

# 将您的用户添加到 wis2 组
sudo usermod -aG wis2 $USER

# 注销并重新登录以使组成员身份生效
# 然后验证
groups  # Should include 'wis2'

或者,使用 ACL 为特定用户授予读访问权限:

# 授予特定用户读取权限
sudo setfacl -R -m u:youruser:rx downloads
sudo setfacl -R -d -m u:youruser:rx downloads

1.5. 验证部署

以下示例使用 localhost。如果系统部署在远程服务器上,请将 localhost 替换为服务器的主机名或 IP 地址。如有需要,端口(API 使用 5002,UI 使用 8080)可在 docker-compose.yaml 中更改。
# 检查所有服务是否正在运行
docker-compose ps

# 检查健康检查端点(订阅管理器)
curl http://localhost:5002/health

# 检查 Web 界面是否正常运行
curl -o /dev/null -s -w "%{http_code}" http://localhost:8080

# 查看日志
docker-compose logs -f subscriber

2. 配置

2.1. 环境变量

2.1.1. 应用设置

变量 类型 默认值 说明

LOG_LEVEL

string

DEBUG

日志级别:DEBUG、INFO、WARNING、ERROR

DATA_PATH

path

/data

下载文件的基础目录

FLASK_SECRET_KEY

string

必填

Flask 会话密钥(未设置时应用将失败)

FLASK_DEBUG

boolean

false

启用 Flask 调试模式(生产环境中设置为 false

FLASK_HOST

string

0.0.0.0

API 绑定地址

FLASK_PORT

integer

5001

API 绑定端口(内部)

2.1.2. MQTT 代理设置

变量 类型 默认值 说明

GLOBAL_BROKER_HOST

string

必填

WIS2 全球代理主机名(例如 globalbroker.meteo.fr

GLOBAL_BROKER_PORT

integer

443

代理端口

GLOBAL_BROKER_USERNAME

string

everyone

MQTT 用户名

GLOBAL_BROKER_PASSWORD

string

everyone

MQTT 密码

MQTT_PROTOCOL

string

websockets

传输协议:websocketstcp

MQTT_SESSION_ID

string

自动生成

用于重启后恢复的持久会话 ID

2.1.3. Redis 设置

变量 类型 默认值 说明

REDIS_HOST

string

redis

Redis 服务器主机名

REDIS_PORT

integer

6379

Redis 服务器端口

REDIS_DATABASE

integer

0

Redis 数据库编号

REDIS_PASSWORD

string

必填

Redis 认证密码(未设置时应用将失败)

REDIS_TTL_SECONDS

integer

3600

去重键的 TTL

REDIS_MESSAGE_LOCK

integer

300

防止并发下载的锁过期时间

2.1.4. Celery 设置

变量 类型 默认值 说明

CELERY_BROKER_URL

string

redis://:${REDIS_PASSWORD}@redis:6379/0

Celery 代理连接字符串(注意密码格式)

CELERY_RESULT_BACKEND

string

redis://:${REDIS_PASSWORD}@redis:6379/1

结果后端连接字符串(注意密码格式)

2.1.5. 下载过滤

变量 类型 默认值 说明

GC_EXCLUDE

string

要排除的全球缓存主机名的逗号分隔列表

2.1.6. Web 界面设置

变量 类型 默认值 说明

WIS2_SUBSCRIPTION_MANAGER_URL

string

http://subscription-manager:5001

UI 用于访问订阅管理器 API 的内部 URL

WIS2_GRAFANA_URL

string

http://localhost:3000

用于在仪表板视图中嵌入 Grafana 面板的 URL

STORAGE_SECRET

string

wis2box-rx-secret

NiceGUI 会话存储密钥 — 生产环境中设置唯一值

GDC_CACHE_TTL_SECONDS

integer

21600

在 Redis 中缓存 GDC 目录数据的秒数(默认 6 小时);设置为 0 始终获取实时数据

2.2. 文件结构

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. 扩展

3.1. Redis

系统使用单个 Redis 实例用于:

  • 服务间的发布/订阅消息传递

  • 订阅状态存储

  • 去重跟踪

  • Celery 任务队列和结果

系统使用单个 Redis 实例,不提供高可用性。无论规模大小,确保 Redis 数据持久化:

  • 启用 Redis 持久化(AOF + RDB)

  • 实施定期的 Redis dump 备份

3.2. 扩展 Worker

增加 Celery worker 并发数以提高吞吐量:

# docker-compose.yaml
celery:
  command: ["... --concurrency=16 ..."]

或运行多个 worker 容器:

docker-compose up -d --scale celery=3

3.3. 添加全球代理

每个订阅者服务连接到单个 WIS2 全球代理。要从多个代理接收数据,在 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
每个订阅者必须有唯一的 container_name

所有订阅者共享相同的 Redis 后端,因此通过 API 创建的订阅会被所有订阅者实例接收。下载会进行去重,因此即使通知来自多个代理,同一文件也不会被下载两次。

4. 监控

4.1. Prometheus 指标

指标使用 HINCRBYFLOAT 原子操作存储在 Redis 中,并以 Prometheus 文本格式在 /metrics 端点公开。这种方式在多个 Celery worker 容器之间是安全的,无需共享文件系统或 prometheus_client 多进程目录。

4.1.1. 可用指标

指标 类型 说明

wis2downloader_notifications_received_total

Counter

MQTT 消息在入队前的接收总数(标签:broker);与 notifications_total 对比可检测队列故障

wis2downloader_notifications_total

Counter

Celery 处理的通知总数(标签:status)

wis2downloader_downloads_total

Counter

成功下载的文件数(标签:cache, media_type)

wis2downloader_downloads_bytes_total

Counter

已下载的总字节数(标签:cache, media_type)

wis2downloader_skipped_total

Counter

按原因统计的跳过通知数(标签:reason)

wis2downloader_failed_total

Counter

失败的下载数(标签:cache, reason)

wis2downloader_queue_errors_total

Counter

订阅者将 Celery 任务入队失败的次数(标签:broker)

wis2downloader_celery_queue_length

Gauge

Celery 队列中的当前任务数

wis2downloader_disk_total_bytes

Gauge

下载卷的总磁盘容量

wis2downloader_disk_used_bytes

Gauge

下载卷的已用磁盘空间

wis2downloader_disk_free_bytes

Gauge

下载卷的可用磁盘空间

wis2downloader_disk_downloads_bytes

Gauge

已下载文件占用的字节数(增量跟踪)

4.1.2. 示例查询

# 每分钟下载速率
sum(rate(wis2downloader_downloads_total[1m]))

# 按缓存分类的下载量
sum by (cache) (rate(wis2downloader_downloads_total[5m]))

# 按原因分类的失败下载
sum by (reason) (wis2downloader_failed_total)

# 过去一小时下载的字节数
sum(increase(wis2downloader_downloads_bytes_total[1h]))

# 队列深度
wis2downloader_celery_queue_length

4.2. Grafana

访问 Grafana:http://localhost:3000(默认凭据:admin/admin)

首次登录后更改默认 Grafana 密码,或在生产部署中通过环境变量 GF_SECURITY_ADMIN_PASSWORD 配置。

预配置数据源:

4.3. 日志

所有服务以 UTC 时间戳记录到 stdout:

2026-01-28T10:15:30.123Z subscriber INFO Connected successfully

通过 Docker 查看日志:

# 所有服务
docker-compose logs -f

# 特定服务
docker-compose logs -f celery

# 带时间戳
docker-compose logs -f -t subscriber

日志也由 Loki 收集,可在 Grafana 中查询。

5. 维护

5.1. 备份

5.1.1. Redis 数据

# 触发 RDB 快照
docker exec redis redis-cli -a $REDIS_PASSWORD BGSAVE

# 复制快照
docker cp redis:/data/dump.rdb ./backup/

5.1.2. 下载的文件

# 备份下载目录
tar -czf wis2-downloads-$(date +%Y%m%d).tar.gz downloads/

5.2. 清除数据

这些操作是破坏性的。
# 清除所有订阅
docker exec redis redis-cli -a $REDIS_PASSWORD DEL global:subscriptions

# 清除去重缓存(允许重新下载所有数据)
docker exec redis redis-cli -a $REDIS_PASSWORD KEYS "wis2:notifications:*" | xargs -r docker exec -i redis redis-cli -a $REDIS_PASSWORD DEL

# 清除已下载的文件
rm -rf downloads/*

5.3. 重启服务

# 重启单个服务
docker-compose restart subscriber

# 重启所有服务
docker-compose restart

# 完全重建(代码更改后)
docker-compose build && docker-compose up -d

6. 故障排除

6.1. 服务无法启动

# 检查日志
docker-compose logs <service-name>

# 检查容器状态
docker-compose ps

# 验证环境
docker-compose config

6.2. 无下载

  1. 检查订阅者是否已连接:

    docker-compose logs subscriber | grep -i connect
  2. 验证订阅是否存在:

    curl http://localhost:5002/subscriptions
  3. 检查 Celery worker 是否在处理:

    docker-compose logs celery | tail -50
  4. 检查队列深度:

    curl -s http://localhost:5002/metrics | grep queue_length

6.3. Redis 连接错误

  1. 检查 Redis 是否在运行:

    docker exec redis redis-cli -a $REDIS_PASSWORD PING
  2. 验证网络连接:

    docker network inspect wis2downloader_redis-net

6.4. 内存使用率高

  • 增加 max-tasks-per-child 以更频繁地回收 worker

  • 降低 worker 并发数

  • 检查是否有大文件压垮 worker

6.5. 指标未更新

  • Prometheus 抓取间隔默认为 15 秒

  • 通过 Grafana 检查 Prometheus 目标:Connections → Data sources → Prometheus → Save & test

  • 验证指标端点:curl http://localhost:5002/metrics

6.6. Web 界面:目录数据未加载

如果目录搜索或树搜索视图显示"Catalogue data not loaded":

  1. 检查 UI 容器是否成功启动:

    docker-compose logs wis2downloader-ui | tail -30
  2. 确认 Redis 中存在 GDC 缓存键:

    docker exec redis redis-cli -a $REDIS_PASSWORD KEYS "gdc:cache:*"

    如果没有键,初始抓取失败或仍在进行中。等待片刻后重新加载页面。

  3. 从设置视图(http://localhost:8080)通过点击 刷新 GDC 数据 强制重新抓取,或重启 UI 容器:

    docker-compose restart wis2downloader-ui
  4. 如果 GDC 抓取持续失败,检查从 UI 容器到公共 GDC 端点的网络连接:

    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