12 KiB
12 KiB
QGIS Docker 部署指南
QGIS 专题图产出通过 Docker 容器执行,主进程(Python 3.10)通过 docker exec 调用容器内的 QGIS 环境。
1. 环境要求
- Docker(Windows: Docker Desktop;Linux: docker-ce)
- 项目代码已部署到服务器
- 输出文件目录已创建
2. 拉取 QGIS 镜像
# 官方 QGIS 镜像(含 QGIS 3.x + Python + Qt5)
docker pull qgis/qgis:3.44.11
# 验证
docker run --rm qgis/qgis:3.44.11 qgis --version
无外网环境(离线导入)
# 在有网的机器上导出
docker save qgis/qgis:3.44.11 -o qgis-34411.tar
# 传输到目标服务器后导入
docker load -i qgis-34411.tar
3. 启动 QGIS 容器
# ---- Linux / macOS ----
docker run -d \
--name qgis-server \
--restart unless-stopped \
-v "/www/wwwroot/xian_algorithm_new:/app:ro" \
-v "/www/wwwroot/xian_algorithm_new/files:/files" \
qgis/qgis:3.44.11 \
sleep infinity
# ---- Windows (cmd.exe) ----
# 注意:Windows 路径必须用正斜杠 /,不能用反斜杠 \
# 挂载目标必须是 Linux 路径(容器是 Linux)
docker run -d ^
--name qgis-server ^
--restart unless-stopped ^
-v "F:/project/xian/xian_algorithm_new:/app:ro" ^
-v "G:/files:/files" ^
qgis/qgis:3.44.11 ^
sleep infinity
参数说明
| 参数 | 说明 |
|---|---|
--name qgis-server |
容器名称,需与 settings.toml 中 QGIS_DOCKER_CONTAINER 一致 |
-v 项目代码:/app:ro |
项目代码只读挂载,容器内路径由 QGIS_DOCKER_PROJECT_DIR 配置 |
-v 输出目录:输出目录 |
文件输出目录读写挂载,保持主机与容器路径一致 |
sleep infinity |
保持容器运行,等待 docker exec 调用 |
4. 预拷贝静态数据到容器本地 FS(必须,性能关键)
WSL2 9P 文件系统随机读取极慢(GPKG 62MB 耗时 6-10s/模板,模板 ZIP 也慢),拷贝到容器内 /data/ 后读取仅需 ~0.5s。
方式一:Python 脚本(推荐)
python app/script/copy_data_to_container.py
输出示例:
=== 预拷贝静态数据到容器 qgis-server ===
[GPKG] 主机目录: F:\project\xian\xian_algorithm_new\app\data\gpkg
文件数: 23, 总大小: 62.3 MB
容器目标: qgis-server:/data/gpkg
拷贝完成: 3.2s, 容器内 23 个文件
[模板] 主机目录: F:\project\xian\xian_algorithm_new\app\data\template
文件数: 34, 总大小: 45.1 MB
容器目标: qgis-server:/data/template
拷贝完成: 2.1s, 容器内 34 个文件
=== 总耗时: 5.4s ===
其他用法:
python app/script/copy_data_to_container.py --dry-run # 仅查看信息
python app/script/copy_data_to_container.py --only gpkg # 只拷贝 GPKG
python app/script/copy_data_to_container.py --only template # 只拷贝模板
方式二:手动 docker cp
# 清理旧文件
docker exec qgis-server rm -rf /data/gpkg /data/template
# 拷贝 GPKG
docker cp F:\project\xian\xian_algorithm_new\app\data\gpkg qgis-server:/data/gpkg
# 拷贝模板
docker cp F:\project\xian\xian_algorithm_new\app\data\template qgis-server:/data/template
# 验证
docker exec qgis-server ls -la /data/gpkg
docker exec qgis-server ls -la /data/template/rainfall
docker exec qgis-server ls -la /data/template/earthquake
何时需要重新执行
- GPKG 或模板文件有更新时
- 容器重建后(
/data目录丢失) - 首次部署时
5. 安装中文字体(手动,必须执行)
QGIS 模板使用了 SimHei(黑体)、SimSun(宋体)、Microsoft YaHei(微软雅黑)等 Windows 中文字体, Docker 镜像默认不包含这些字体,会导致中文全部乱码。字体需手动安装,代码不会自动安装。
5.1 准备字体文件
字体文件存放在项目根目录的 fonts/ 目录下,已预置 4 个常用中文字体:
fonts/
├── simhei.ttf — 黑体(模板默认字体)
├── simsun.ttc — 宋体
├── msyh.ttc — 微软雅黑
└── msyhbd.ttc — 微软雅黑粗体
如果字体缺失,从 Windows 主机复制(C:\Windows\Fonts\):
# Linux 服务器用 SCP 从 Windows 传
scp "C:\Windows\Fonts\simhei.ttf" root@服务器IP:/www/wwwroot/xian_algorithm_new/fonts/
scp "C:\Windows\Fonts\simsun.ttc" root@服务器IP:/www/wwwroot/xian_algorithm_new/fonts/
scp "C:\Windows\Fonts\msyh.ttc" root@服务器IP:/www/wwwroot/xian_algorithm_new/fonts/
scp "C:\Windows\Fonts\msyhbd.ttc" root@服务器IP:/www/wwwroot/xian_algorithm_new/fonts/
5.2 一键安装到容器
python app/script/install_fonts_to_container.py
输出示例:
=== 安装中文字体到 Docker 容器 qgis-server ===
字体目录: /www/wwwroot/xian_algorithm_new/fonts
字体文件: 4 个
- msyh.ttc (4165 KB)
- msyhbd.ttc (4167 KB)
- simhei.ttf (2637 KB)
- simsun.ttc (10126 KB)
容器目标: qgis-server:/usr/share/fonts/truetype/winfonts
[1/3] 复制字体文件...
OK msyh.ttc
OK msyhbd.ttc
OK simhei.ttf
OK simsun.ttc
[2/3] 刷新字体缓存...
OK
[3/3] 验证字体...
中文字体: ['SimHei', 'SimSun', 'Microsoft YaHei', 'Microsoft YaHei UI']
=== 完成,耗时 3.2s ===
其他用法:
python app/script/install_fonts_to_container.py --dry-run # 仅查看信息
python app/script/install_fonts_to_container.py --container my # 指定容器名
5.3 持久化(推荐)
容器重建后字体丢失,强烈建议挂载 fonts/ 目录。
# 停掉旧容器
docker stop qgis-server && docker rm qgis-server
# 重建容器,加上字体挂载
docker run -d \
--name qgis-server \
--restart unless-stopped \
-v "/www/wwwroot/xian_algorithm_new:/app:ro" \
-v "/www/wwwroot/xian_algorithm_new/files:/files" \
-v "/www/wwwroot/xian_algorithm_new/fonts:/usr/share/fonts/truetype/winfonts:ro" \
qgis/qgis:3.44.11 \
sleep infinity
# 挂载后只需刷新一次缓存
docker exec qgis-server fc-cache -fv
5.4 无法获取 Windows 字体时的替代方案
# Linux 服务器安装开源中文字体
yum install wqy-microhei-fonts # CentOS / RHEL
apt install fonts-wqy-microhei # Debian / Ubuntu
# 将系统字体复制到项目 fonts/ 目录
cp /usr/share/fonts/wqy-microhei/wqy-microhei.ttc /www/wwwroot/xian_algorithm_new/fonts/
6. 验证容器
# 检查容器状态
docker ps
# 测试 QGIS 可用性
docker exec qgis-server python3 -c "from qgis.core import QgsApplication; print('OK')"
# 查看容器内项目代码
docker exec qgis-server ls /app/app/services/qgis/
7. 配置文件
所有 Docker 相关配置集中在 settings.toml 的 [default] 段:
[default]
# ---- Docker QGIS 配置 ----
QGIS_DOCKER_CONTAINER = "qgis-server" # 容器名称(需与 docker run --name 一致)
QGIS_DOCKER_PROJECT_DIR = "/app" # 容器内项目代码挂载路径
QGIS_DOCKER_PYTHON = "/usr/bin/python3" # 容器内 Python 解释器路径
QGIS_DOCKER_IMAGE = "qgis/qgis:3.44.11" # Docker 镜像名称
QGIS_DOCKER_PREFIX_PATH = "/usr" # QGIS prefixPath(安装根目录)
QGIS_DOCKER_PYTHONPATH = [ # QGIS Python 包路径列表
"/usr/lib/python3/dist-packages",
"/usr/lib/python3.10/dist-packages",
"/usr/lib/python3.11/dist-packages",
"/usr/lib/python3.12/dist-packages",
]
QGIS_DOCKER_QT_PLATFORM = "offscreen" # Qt 无头模式
QGIS_DOCKER_KEEP_ALIVE = "sleep infinity" # 容器保活命令
# ---- 专题图参数 ----
QGIS_GPKG_DIR = "app/data/gpkg" # GPKG 目录(相对于项目根)
QGIS_DOCKER_GPKG_DIR = "/data/gpkg" # 容器内 GPKG 本地路径(预拷贝后读取)
QGIS_DOCKER_TEMPLATE_DIR = "/data/template" # 容器内模板本地路径(预拷贝后读取)
QGIS_EXPORT_DPI = 200 # 导出 DPI
QGIS_PARALLEL_WORKERS = 4 # 并行 docker exec 子进程数
QGIS_MAX_CONCURRENT = 2 # 最大并发请求数
# ---- 文件路径 ----
FILE_STORE_DIR = "G:/files" # 主机端文件输出目录
环境变量覆盖
export QGIS_DOCKER_CONTAINER="qgis-server"
export QGIS_DOCKER_PROJECT_DIR="/app"
export QGIS_DOCKER_PYTHON="/usr/bin/python3"
跨机器适配
不同机器只需修改 settings.toml 中的路径配置:
| 配置项 | 开发机 (Windows) | 生产机 (Linux) |
|---|---|---|
FILE_STORE_DIR |
"G:/files" |
"/data" |
DB_HOST |
"47.92.216.173" |
"10.22.245.138" |
DB_PORT |
7654 |
54321 |
8. 目录结构
项目根目录/
├── app/
│ ├── data/
│ │ ├── gpkg/ # 静态底图 GeoPackage 文件
│ │ └── template/ # 专题图模板 (.qgz)
│ ├── services/qgis/
│ │ ├── qgis_env.py # Docker 环境配置(路径映射、命令构建)
│ │ ├── qgis_runner.py # 容器内子进程入口
│ │ ├── map_service.py # 地图生成主流程
│ │ ├── template_modifier.py # 模板 XML 修改
│ │ └── map_exporter.py # 图片导出
│ └── api/
│ └── qgis_map_export.py # FastAPI 专题图导出接口
├── script/
│ └── copy_data_to_container.py # GPKG + 模板预拷贝脚本
├── config.py # Dynaconf 配置入口
├── settings.toml # 全部配置
├── requirements.txt # Python 依赖
├── QGIS_DOCKER_README.md # 本文档
└── tmp/ # 临时文件目录(容器内映射为 /app/tmp/)
9. 临时文件
- 主机端临时 JSON(批量产图配置):写入
{项目根}/tmp/,容器内可通过/app/tmp/访问 - 容器端临时 .qgz(修改后的模板):写入容器内
/tmp/,由 runner 自动清理
10. 故障排查
# 容器未运行
docker ps -a | grep qgis-server
# 查看容器日志
docker logs qgis-server
# 手动测试 runner
docker exec qgis-server python3 /app/app/services/qgis/qgis_runner.py --help
# 检查 QGIS 依赖
docker exec qgis-server python3 -c "
from qgis.core import QgsApplication
QgsApplication.setPrefixPath('/usr', True)
app = QgsApplication([], False)
app.initQgis()
print('QGIS', QgsApplication.version())
app.exitQgis()
"
# 检查中文字体
docker exec qgis-server python3 -c "
from PyQt5.QtGui import QFontDatabase
db = QFontDatabase()
zh = [f for f in db.families() if any(k in f for k in ['SimHei','YaHei','SimSun','WenQuanYi'])]
print('中文字体:', zh if zh else '未安装!')
"
# 检查 GPKG 文件(容器本地路径,性能关键)
docker exec qgis-server ls /app/app/data/gpkg/ # 挂载路径(慢)
docker exec qgis-server ls /data/gpkg/ # 本地路径(快,需预拷贝)
# 检查模板文件(容器本地路径)
docker exec qgis-server ls /data/template/rainfall/ # 本地路径(快)
docker exec qgis-server ls /data/template/earthquake/
# 检查临时文件目录
docker exec qgis-server ls /app/tmp/
静态数据相关问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 产图慢(>60s) | GPKG/模板从挂载目录读取(9P 慢) | 执行 python app/script/copy_data_to_container.py |
日志显示 gpkg_dir=/app/... |
未预拷贝或 QGIS_DOCKER_GPKG_DIR 为空 |
检查 settings.toml 配置 |
| 容器重建后变慢 | /data 目录丢失 |
重新执行拷贝脚本 |
docker cp 权限错误 |
容器未运行 | docker start qgis-server |
11. 工作流程
用户请求 POST /qgis/export/map
→ docker inspect 检查容器是否运行
→ 查询推理结果
→ 扫描模板文件夹
→ 构建配置(路径映射到容器内路径)
→ 并行启动 docker exec 子进程
→ 容器内 qgis_runner.py
→ 加载 QGIS Python 包
→ 初始化 QgsApplication
→ 逐模板处理:
→ TemplateModifier 修改模板 XML(替换连接参数、静态层→GPKG)
→ project.read() 加载模板
→ 图层过滤、缩放、文本更新
→ 导出 JPG
→ 实时写入进度表