一、前言
作为一名地图学与地理信息科学专业的研究生,平常我们主要做的工作还是WebGIS的开发,但毕竟GIS还算是个地理专业,所以有些时候也会出现要使用地理/水文模型来计算的场景。我在实践的过程中发现一些经常困扰我们的问题: 1.可迁移性低:要把模型从下载下来到能够完全在自己的电脑上跑需要花一些时间。 2.部分模型过于耦合:如果有多个模型,写在了一起如果要修改可能牵一发而动全身。 3.自动化程度低:运行一个模型可能需要先处理这个模型需要的原数据,处理数据的可能又是另外一个脚本,每次执行可能都需要再点一下。 当然一定还有很多其他奇奇怪怪的小问题,我这里就不一一列举了。本人所在的课题组长期致力于研究地理模型并且拥有自己的模型平台(平台集成了很多的地理模型),我所说的上述问题在组里也有大佬在过去研究过。但是,在中文互联网上针对较少涉及开发的模型使用者来说,并没有一篇比较“善良”的帖子/教程可供学习,平常可能也极少有时间去钻研如何实现。 那本篇帖子将手把手教你如何实现一套可复现、可共享、可扩展、可自动化的模型工作流。
二、技术介绍
1.Docker:将应用程序及其依赖打包成标准化“集装箱”的工具;封装你的每个任务,确保“在哪跑都一样”。 2.Kubernetes (K8s):自动化部署、扩缩容和管理容器化应用的平台;提供运行环境,让容器任务能被可靠调度和执行。 3.Argo Workflow:基于 K8s 的原生工作流引擎,用于编排多步骤任务;编排你的科研流程,实现“数据→模型→结果”的自动化流水线。 4.RustFS:国产开源的高性能对象存储服务;存储原始/任务间/结果数据。Ps:你也可以使用Minio。
三、软件安装
1.Docker
Windows环境下的Docker就安装Dockersdesktop即可。
2.Kubernetes
使用Dockersdesktop中自带的(见下图)。
3.RustFS
在Docker上安装:
docker run -d \
--name rustfs \
-p 9000:9000 \
-p 9001:9001 \
-e RUSTFS_ACCESS_KEY=用户名 \
-e RUSTFS_SECRET_KEY=密码 \
-e RUSTFS_CONSOLE_ENABLE=true \
-v $(pwd)/data:/data \
rustfs/rustfs:latest
启动RustFS:docker run -p 9000:9000 -p 9001:9001 -v "E:\Graduate\graduate-1\workspace\RustFS:/data/rustfs" rustfs/rustfs /data/rustfs
这段命令不仅启动了RustFS同时还将RustFS的镜像挂载一个自己的本地路径卷,防止数据丢失!
4.Argo Workflow
①创建K8s命名空间(名字可以不叫argo,叫其他的都可以)
kubectl create namespace argo
②在名叫argo的空间中部署Argo(我使用的版本为v3.5.5)
kubectl create -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.5.5/install.yaml
③查看K8s中的Pod检查Argo是否成功运行:
kubectl get pods -n argo
argo-server - Argo的Web UI服务器(可以查看工作流执行情况) workflow-controller - 工作流控制器(执行和管理工作流)
四、示例介绍
1.模型介绍
本示例模型文件为python编写,共四个:pre_01.py、pre_02.py、pre_03.py、solver_flood.py。以及从RustFs下载数据和存储数据的两个go语言编写的文件。
| 名称 | 语言 | 作用 |
|---|---|---|
| pre_01.py | Python | 处理地形与管网基础数据 |
| pre_02.py | Python | 构建边界条件与特征库 |
| pre_03.py | Python | 生成最终计算所需输入 |
| solver_flood.py | Python | 执行核心洪水演进模拟 |
| downloader | Golang | 从云端拉取实验输入数据 |
| uploader | Golang | 将计算结果回传至云端 |
五、实现流程
1.Python 脚本“容器化”
(1)什么是容器化?
做开发的人群一定对这个词语已经见怪不怪了,但是对于长时间科研的大多数同学,容器化的思想可能还是有些许陌生,容器化就是:把你的程序 + 所需环境打包成一个“即插即用”的标准化盒子,确保它在任何地方运行都一模一样。 容器化有很多的技术框架,我选择比较主流的Docker来进行,Docker是一种容器化事实标准工具,包含镜像构建、容器运行、仓库管理等全套功能。
(2)实现步骤
①编写Dockerfile
实现容器化就是针对自己代码写一个“Dockerfile”。Dockerfile 正是实现代码容器化的核心“蓝图。 如果你是一个基本没有接触过开发的小白也不用担心写不来Dockerfile,只需要给大模型的提示词:“这是我的项目结构和代码(粘贴代码),请帮我把这个 Python 脚本容器化,写一个Dockerfile。它需要运行在 Python 3.12 上,请帮我处理好所有的依赖。”它就会生成如下的Dockerfile:
# === 构建阶段 ===
# 定义基础环境
FROM python:3.12-slim-bookworm AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
WORKDIR /app
# 初始化并安装依赖 (pre_01 需要 numpy 和 fastdb4py)
RUN uv init && uv add fastdb4py numpy
# === 运行阶段 ===
FROM python:3.12-slim-bookworm
WORKDIR /app
# 从构建阶段复制虚拟环境
COPY --from=builder /app/.venv /opt/venv
# 复制主脚本和必须的辅助模块
COPY pre_01.py fdb_feature.py ./
# 环境变量设置
ENV VIRTUAL_ENV=/opt/venv
ENV PATH="/opt/venv/bin:$PATH"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
CMD ["python", "pre_01.py"]
②生成Docker镜像
编写好Dockerfile后就可以在控制台输入命令:docker build -t pre_01 -f Dockerfile.pre01 .
注意:pre_01是镜像的名字;Dockerfile.pre01是我Dockerfile的名字;运行一定要是在Dockerfile所在的文件夹下面运行。
构建成功后,打开dockerdesktop后就可以看到:
完成所有的镜像构建后:
2.分享镜像
(1)分享镜像的方式
将代码打包成为镜像后,你就可以分享给你的同事,让他直接拿着你的镜像使用,前提是它也下载了Docker。分享Docker镜像主要有两种方式:推送到 Docker Hub和导出为文件(离线分享)。 本项目因为涉及知识产权就选择导出文件的分享而不选择上传至Docker Hub上了。
(2)具体操作流程
①打包镜像
docker save -o pipe_flood_images.tar p_f_downloader:latest p_f_pre01:latest p_f_pre02:latest p_f_pre03:latest solver_flood:latest p_f_uploader:latest
命令格式:docker save -o <输出文件路径> <镜像名:标签> [其他镜像...]
②分享文件后导入
docker load -i pipe_flood_images.tar
命令格式:docker load -i <tar 文件路径> 用于从一个 tar 归档文件加载(导入)一个或多个镜像到本地镜像仓库的指令。
(3)可能遇到的问题
①【问题】在进行构建镜像的时候,你可能会遇到这样的问题:429 Too Many Requests
遇到这样的情况,可能需要以下操作来解决。 【日常小问】解决Docker构建镜像报错:429 Too Many Requests
②【问题】在构建镜像的时候,你可能会遇到这样的问题:
ERROR: failed to build: failed to solve: alpine:latest: failed to resolve source metadata for docker.io/library/alpine:latest: unable to fetch descriptor
这种问题依旧是网络的问题,可能是镜像不稳定时好时坏 解决方法:更换镜像或者使用以下的方式再配置一遍代Li。 【日常小问】解决Docker拉取镜像报错:alpine:latest
3.在本地 K8s使用 Argo Workflows构建工作流
(1)什么是工作流?
工作流是指为了完成某项业务目标,由多个任务、步骤或活动按照既定顺序和规则自动或半自动执行的过程。
(2)为什么要用到Kubernetes (K8s)?
上一步的我们已经将我们的代码打包成为了Docker的一个个容器,接下来我们就可以使用Kubernetes(K8s)把这些容器按流程图调度、运行、监控、清理。在前文我已经介绍了如何开启Dockerdesktop中自带的K8s,那接下来我们就要使用K8s + Argo来构建一套工作流。 Ps:一般我们本地的电脑一台只能有一个K8s节点,如果想要体现处k8s的集群化优势需要多台,所以针对一般的普通研究者我就不展开细讲了。
(3)K8s和Argo之间是什么关系?
Argo Workflows 是一个开源的容器原生工作流引擎,用于在 Kubernetes 上编排并行作业。你可以把 Argo Workflows 想象是一个安装在 Kubernetes 里面的高级软件,当您提交一个 Workflow YAML 文件时,Argo 的控制器会读懂它,然后指挥 K8s 去创建 Pod、调度任务、监控状态。 Argo安装流程: ①创建K8s命名空间(名字可以不叫argo,叫其他的都可以)
kubectl create namespace argo
②在名叫argo的空间中部署Argo(我使用的版本为v3.5.5)
kubectl create -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.5.5/install.yaml
③查看K8s中的Pod检查Argo是否成功运行:
kubectl get pods -n argo
argo-server - Argo的Web UI服务器(可以查看工作流执行情况) workflow-controller - 工作流控制器(执行和管理工作流)
(4)实现步骤
①编写YAML文件
现在你已经有了构建好的Dockerfile,并且也知道了流程,但是该如何写YAML文件呢?当然是让ai帮你写了:
"请帮我写一个 Argo Workflow YAML。
我有 6 个步骤,需要按顺序执行:
downloader (镜像: p_f_downloader:latest)
pre-01 (镜像: p_f_pre01:latest, 依赖 downloader)
pre-02 (镜像: p_f_pre02:latest, 依赖 pre-01)
pre-03 (镜像: p_f_pre03:latest, 依赖 pre-02)
solver (镜像: solver_flood:latest, 依赖 pre-03)
uploader (镜像: p_f_uploader:latest, 依赖 solver)
关键配置:
使用 HostPath 挂载我的 Windows 目录
E:\Graduate\graduate-1\workspace\argo-workflows-test\data
到容器内的 /app/data。
所有容器的工作目录 (workingDir) 都要设为 /app/data。
Python 步骤的启动命令需要先设置 PYTHONPATH。
Downloader 和 Uploader 需要注入 RustFS 的环境变量(URL, Key, Secret)。"
②启动工作流
kubectl create -n argo -f pipe-flood-workflow.yaml
③将工作流的端口映出来
kubectl -n argo port-forward deployment/argo-server 2746:2746
这一步让你可以通过浏览器访问 Argo Workflows 的可视化控制台(UI界面)。你可以在浏览器中输入:https://localhost:2746 你将看到Argo的图形化界面,可以在那里直观地查看你的 pipe-flood-job 运行状态、DAG 图、每个步骤的日志以及重试失败的任务。
④其他命令
删除所有的工作流:kubectl delete -n argo workflow --all
查看pod的日志:kubectl logs -n argo pipe-flood-job-48h6p-683483916 main
批量构建镜像:docker build -t p_f_pre01:latest -f Dockerfile.pre01 . && docker build -t p_f_pre02:latest -f Dockerfile.pre02 . && docker build -t p_f_pre03:latest -f Dockerfile.pre03 . && docker build -t solver_flood:latest -f Dockerfile.solver . && docker build -t p_f_uploader:latest -f go_tools/Dockerfile.uploader go_tools
(5)迁移工作流
在实现了这一工作流后,你也可以去分享它。 将YAML文件分享,修改配置:
volumes:
- name: workdir
hostPath:
# 修改这里!注意 Windows 路径在 Docker Desktop K8s 中的写法
# 如果新路径是 D:\Project\Data
path: /run/desktop/mnt/host/d/Project/Data
type: Directory
在迁移了YAML文件后,你还需要保证之前的数据也能拿到,就需要看看后续章节的RustFS迁移; 【问题】另外,你在迁移好后运行中可能会遇到私有镜像 UNAUTHORIZED 错误以及Argo权限不足的问题,可以参见我写的另外一篇文章: 【日常小问】解决Argo Workflows本地镜像拉取失败与权限不足问题
4.RustFS的迁移或者共享
(1)作为地学领域研究者为什么我们要使用RustFS/Minio?
RustFS这种对象存储数据库,对于传统的电脑存几百 GB 的高清卫星影像,打开慢、传输卡,甚至直接死机的问题。它能把大文件切成小块同时传输,多大都能跑满网速。当然,你肯定会说我移动数据的方式用U盘更快。的确这样方式可能更快,不过使用RustFS这类对象存储数据库,可以让在同一个局域网内的电脑都能够同时获取,这样就能解决你U盘找不到了或者内存不够时的燃眉之急。
(2)如何拿到同局域网下其他人的数据:
在YAML文件中修改:
- name: RUSTFS_ENDPOINT_URL value: "http://192.168.1.100:9000" # 改成存储数据这台主机的局域网 IP
六、总结——为什么说这是“降维打击”?
完成了整个流程,你一定觉得这么大费周章却只是实现了这么一个简单的流程,我完全可以写一个.sh的脚本直接运行,同样能够实现上文所说的功能,用上述的技术完全是“杀鸡用牛刀”了。但如果半年后你要复现这个实验,或者你的师弟师妹要用你的代码,又假如你处理的数据量翻了100倍怎么办,那你写的这些脚本就会变成噩梦,你可能面临依赖冲突、环境配置、断点续传困难等等问题,你当然能够解决这些问题,但也花费了大量时间,主要是做这些工作非常折磨人! 而现在,我们使用Docker把Python 3.12、Taichi、Go环境全部固化了。无论在你的 Windows 上,还是在学校的超算中心,或者阿里云上,它都能保证 100% 一致的运行结果。这是科研最看重的“可复现性”。 我们使用了Argo,把黑盒的脚本执行过程,变成了白盒的可视化流程,在 UI 上,你可以直观地看到哪一步变红了,哪一步耗时最长。这种对流程的掌控力,自然也是脚本无法比拟的。