|
本文档由 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 文件中配置以下安全设置:
|
-
REDIS_PASSWORD- 设置一个强大的唯一密码(必填) -
FLASK_SECRET_KEY- 设置一个加密随机密钥(必填) -
FLASK_DEBUG- 生产环境中设置为false -
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; } -
防火墙规则 — 使用
iptables、ufw或云安全组将端口 8080 和 5002 限制到受信任的 IP 范围。 -
VPN / 私有网络 — 部署在 VPN 后面,使只有经过身份验证的 VPN 用户才能访问服务。
如果系统运行在服务器而非本机,请在添加上述访问控制之一之前,确保端口 8080 和 5002 无法公开访问。
1.4. 卷权限
应用容器以非 root 用户(wis2)运行以确保安全。要允许容器将下载的文件写入主机文件系统,必须在 downloads/ 目录上配置适当的权限。
1.4.1. 容器用户详情
| 属性 | 值 |
|---|---|
用户名 |
|
用户 ID(UID) |
|
组名 |
|
组 ID(GID) |
|
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. 应用设置
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
|
日志级别:DEBUG、INFO、WARNING、ERROR |
|
path |
|
下载文件的基础目录 |
|
string |
必填 |
Flask 会话密钥(未设置时应用将失败) |
|
boolean |
|
启用 Flask 调试模式(生产环境中设置为 |
|
string |
|
API 绑定地址 |
|
integer |
|
API 绑定端口(内部) |
2.1.2. MQTT 代理设置
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
必填 |
WIS2 全球代理主机名(例如 |
|
integer |
|
代理端口 |
|
string |
|
MQTT 用户名 |
|
string |
|
MQTT 密码 |
|
string |
|
传输协议: |
|
string |
自动生成 |
用于重启后恢复的持久会话 ID |
2.1.3. Redis 设置
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
|
Redis 服务器主机名 |
|
integer |
|
Redis 服务器端口 |
|
integer |
|
Redis 数据库编号 |
|
string |
必填 |
Redis 认证密码(未设置时应用将失败) |
|
integer |
|
去重键的 TTL |
|
integer |
|
防止并发下载的锁过期时间 |
2.1.4. Celery 设置
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
|
Celery 代理连接字符串(注意密码格式) |
|
string |
|
结果后端连接字符串(注意密码格式) |
2.1.5. 下载过滤
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
空 |
要排除的全球缓存主机名的逗号分隔列表 |
2.1.6. Web 界面设置
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
|
string |
UI 用于访问订阅管理器 API 的内部 URL |
|
|
string |
用于在仪表板视图中嵌入 Grafana 面板的 URL |
|
|
string |
|
NiceGUI 会话存储密钥 — 生产环境中设置唯一值 |
|
integer |
|
在 Redis 中缓存 GDC 目录数据的秒数(默认 6 小时);设置为 |
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. 可用指标
| 指标 | 类型 | 说明 |
|---|---|---|
|
Counter |
MQTT 消息在入队前的接收总数(标签:broker);与 |
|
Counter |
Celery 处理的通知总数(标签:status) |
|
Counter |
成功下载的文件数(标签:cache, media_type) |
|
Counter |
已下载的总字节数(标签:cache, media_type) |
|
Counter |
按原因统计的跳过通知数(标签:reason) |
|
Counter |
失败的下载数(标签:cache, reason) |
|
Counter |
订阅者将 Celery 任务入队失败的次数(标签:broker) |
|
Gauge |
Celery 队列中的当前任务数 |
|
Gauge |
下载卷的总磁盘容量 |
|
Gauge |
下载卷的已用磁盘空间 |
|
Gauge |
下载卷的可用磁盘空间 |
|
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 配置。
|
预配置数据源:
-
Prometheus -
http://prometheus:9090 -
Loki -
http://loki:3100
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. 无下载
-
检查订阅者是否已连接:
docker-compose logs subscriber | grep -i connect -
验证订阅是否存在:
curl http://localhost:5002/subscriptions -
检查 Celery worker 是否在处理:
docker-compose logs celery | tail -50 -
检查队列深度:
curl -s http://localhost:5002/metrics | grep queue_length
6.3. Redis 连接错误
-
检查 Redis 是否在运行:
docker exec redis redis-cli -a $REDIS_PASSWORD PING -
验证网络连接:
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":
-
检查 UI 容器是否成功启动:
docker-compose logs wis2downloader-ui | tail -30 -
确认 Redis 中存在 GDC 缓存键:
docker exec redis redis-cli -a $REDIS_PASSWORD KEYS "gdc:cache:*"如果没有键,初始抓取失败或仍在进行中。等待片刻后重新加载页面。
-
从设置视图(
http://localhost:8080)通过点击 刷新 GDC 数据 强制重新抓取,或重启 UI 容器:docker-compose restart wis2downloader-ui -
如果 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