使用 Linux 容器分析气候变化和土壤对新西兰农作物的影响

该方法通过处理大量高分辨率土壤和天气数据来模拟气候变化情景。
167 位读者喜欢这篇文章。
Arrows moving across a landscape

Opensource.com

新西兰的经济依赖于农业,而农业是一个对气候变化高度敏感的部门。这使得开发分析能力来评估其影响并研究可能的减缓和适应方案至关重要。这种分析可以使用诸如农业系统模型等工具来完成。简单来说,它涉及到创建一个模型来量化特定作物在特定条件下的行为,然后模拟改变一些变量来看看这种行为如何变化。可用于此目的的一些软件包括华盛顿州立大学的 CropSyst 和澳大利亚联邦科学与工业研究组织 (CSIRO) 的农业生产系统模拟器 (APSIM)。

从历史上看,这些模型主要用于小面积(基于点)的模拟,其中所有变量都是已知的。对于大面积研究(景观尺度,例如,整个区域或国家层面),土壤和气候数据需要按比例放大或缩小到感兴趣的分辨率,这意味着增加不确定性。造成这种情况有两个主要原因:1) 难以创建和/或获取对高分辨率、地理参考、网格化数据集的访问;2) 作物建模软件最常见的安装是在最终用户的台式机或工作站上,这些机器通常运行受支持的 Microsoft Windows 版本之一(系统建模人员倾向于使用工具的 GUI 功能来准备和运行模拟,这随后限制于所用硬件的计算能力)。

新西兰有几个皇家研究所,为该国经济许多重要领域提供科学研究,包括土地保护研究所、国家水与大气研究所 (NIWA) 和新西兰植物与食品研究所。在一个联合项目中,这些组织贡献了与该国的土壤、地形、气候和作物模型相关的数据集。我们希望创建一个分析框架,该框架使用 APSIM 运行足够的模拟,以涵盖气候变化问题相关的时间尺度(超过 100 年的气候变化数据),空间分辨率约为 25 平方公里的整个新西兰。我们谈论的是数百万次模拟,每次模拟在单个 CPU 核心上至少需要 10 分钟才能完成。如果我们使用标准台式机,可能更快的方法是直接在外面等待看看会发生什么。

进入 HPC

高性能计算 (HPC) 是指使用并行处理来高效、可靠和快速地运行程序。通常,这意味着利用跨多台主机的批处理,每个单独的进程只处理少量数据,并使用作业调度器来协调它们。

并行计算可以意味着分布式计算,其中每个处理线程需要在任务之间(尤其是中间结果)与其他线程通信,或者它可以是“令人尴尬的并行”,其中没有这种需要。在处理后者时,整体性能会随着可用容量的增加而线性增长。

幸运的是,作物建模是一个令人尴尬的并行问题:无论您有多少数据或多少变量,每个变化的变量都意味着需要运行一次完整的模拟。并且由于模拟彼此独立,您可以运行与您拥有的 CPU 一样多的模拟。

解决依赖地狱

APSIM 是一款复杂的软件。它的代码库由在过去三十年中用多种不同编程语言编写并紧密集成的模块组成。该应用程序通过利用 Mono 项目框架实现了 Windows 和 GNU/Linux 操作系统之间的可移植性,但是要在 Linux 环境中运行它所需的外部依赖项和解决方法数量使得实现变得非常复杂。

构建和安装文档很少,并且确实存在的说明针对的是 Ubuntu Desktop 版本。几个必需的依赖项没有文档记录,并且构建过程有时依赖于 binfmt_misc 内核模块来允许直接执行链接到 Mono 库的 .exe 文件(而不是调用 mono file.exe),但这样做并不一致(这已在上游修复)。更令人困惑的是,一些 .exe 文件是 Mono 程序集,而另一些是本机 (libc) 二进制文件(这样做是为了避免操作系统平台之间可执行文件的名称差异)。最后,Linux 构建由开发人员“内部”按需创建,但由于外部用户缺乏兴趣,因此没有公开可访问的自动化构建。

所有这些可能在一个组织内有效,但这使得 APSIM 在其他环境中难以采用。HPC 集群倾向于标准化为一种 Linux 发行版(例如,Red Hat Enterprise Linux、CentOS、Ubuntu 等)和作业调度器(例如,PBS、HTCondor、Torque、SGE、Platform LSF、SLURM 等),并且可以实现不同的存储和网络架构、网络配置、用户身份验证和授权策略等。因此,可用的软件、版本以及它们的集成方式都高度依赖于环境。诸如 OpenHPC 之类的项目旨在为这种情况提供一些合理性,但现实情况是,大多数 HPC 集群本质上都是定制的,根据组织的需求量身定制。

解决这些问题的一个简单方法是引入容器化技术。这不应该令人惊讶(毕竟它在本文的标题中)。容器允许创建独立的、自给自足的工件,可以在任何支持运行它们的环境中无需更改即可运行。但从“可重复研究”的角度来看,容器也提供了额外的优势:软件容器可以以可重复的方式创建,一旦创建,生成的容器镜像既是可移植的又是不可变的

  • 可重复性:一旦按照最佳实践编写了容器定义文件(例如,确保显式定义了安装的软件版本),就可以以确定性的方式创建相同的生成的容器镜像。

  • 可移植性:当管理员创建容器镜像时,他们可以编译、安装和配置所有需要的软件,并包括运行它们所需的任何外部依赖项或库,一直到 Linux 发行版本身。在此过程中,除了硬件之外,无需针对执行环境做任何事情。一旦创建,容器镜像可以作为独立工件分发。这干净地将特定软件的构建和安装阶段与执行该软件的运行时阶段分离开来。

  • 不可变性:构建完成后,容器镜像就是不可变的。也就是说,如果不创建新镜像,就无法更改其内容并持久化它们。

这些属性使得可以捕获处理过程中使用的软件堆栈的精确状态,并将其与原始数据一起分发,以便在不同的环境中复制分析,即使该环境中使用的 Linux 发行版与容器镜像内部使用的发行版不匹配。

Docker

虽然操作系统级虚拟化并不是一项新技术,但主要是因为 Docker,它变得越来越受欢迎。Docker 提供了一种以简单方式开发、部署和运行软件容器的方法。

APSIM 容器镜像的第一个迭代是在 Docker 中实现的,部分复制了开发人员记录的构建环境。这样做是为了验证容器化和运行应用程序的可行性的概念证明。第二个迭代引入了多阶段构建:一种创建容器镜像的方法,允许将构建阶段与安装阶段分离。这种分离很重要,因为它减小了生成的容器镜像的最终大小,其中将不包括任何仅在构建时需要的依赖项。Docker 容器并不特别适合多租户 HPC 环境。有三个主要事项需要考虑

1. 数据所有权

容器镜像通常不存储与企业身份验证目录(例如,Active Directory、LDAP 等)集成所需的配置,因为这会降低可移植性。相反,用户信息通常直接在镜像中显式硬编码(并且当没有硬编码时,默认使用 root)。当容器启动时,包含的进程将使用此硬编码的身份运行(请记住,默认使用 root)。结果是容器化进程创建的输出数据的所有者是可能仅存在于容器镜像内部的用户。而不是启动容器的用户(还有,我提到默认使用 root 吗?)。

解决此问题的一种可能方法是在容器启动时覆盖运行时用户(使用 docker run -u… 标志)。但这为用户增加了复杂性,他们现在必须了解用户身份 (UID)、POSIX 所有权和权限、docker run 命令的正确语法,以及找到其 UID、组标识符 (GID) 以及他们可能需要的任何其他组的正确值。所有这些都是为了只想完成一些科学研究的人。

还值得注意的是,此方法并非每次都有效。并非所有应用程序都乐于以任意用户或系统数据库(例如,/etc/passwd 文件)中不存在的用户身份运行。这些是边缘情况,但它们确实存在。

2. 访问持久性存储

容器镜像仅包含应用程序运行所需的文件。它们通常不包括应用程序要处理的输入或原始数据。默认情况下,当容器镜像被实例化时(即,当容器启动时),呈现给容器化应用程序的文件系统将仅显示容器镜像中存在的那些文件和目录。要访问输入或原始数据,最终用户必须显式地将所需挂载点从主机服务器映射到容器中文件系统内的路径(通常使用绑定挂载)。使用 Docker,这些“卷挂载”无法全局预配置,并且必须在启动容器时在每个容器的基础上完成映射。这不仅增加了运行应用程序所需的命令的复杂性,而且还引入了另一个不良影响……

3. 计算主机安全

以任意用户身份启动进程以及将主机服务器中的任意文件或目录映射到正在运行的容器的文件系统中的能力是 Docker 向操作员提供的几个强大功能中的两个。但它们之所以成为可能,是因为在 Docker 采用的安全模型中,运行容器的守护程序必须在主机上以 root 权限启动。因此,有权访问 Docker 守护程序的最终用户最终拥有等同于主机 root 访问权限的权限。这引入了安全问题,因为它违反了 最小权限原则。恶意行为者可以执行超出其初始授权范围的操作,但最终用户也可能在没有恶意意图的情况下意外损坏或破坏数据。

解决此问题的一种可能方法是实施用户命名空间。但在实践中,这些命名空间维护起来很麻烦,尤其是在用户身份在企业目录中集中的企业环境中。

Singularity

为了解决这些问题,APSIM 容器的第三次迭代是使用 Singularity 实现的。 Singularity Community 于 2016 年发布,是一个专门为科学和 HPC 环境设计的开源容器平台。Singularity 容器内的用户与容器外的用户是同一用户 是 Singularity 的定义特征之一。它允许最终用户以自己的身份在容器镜像内部运行命令。相反,它不允许在启动容器时模拟其他用户。 

Singularity 方法的另一个优势是容器镜像在磁盘上的存储方式。使用 Docker,容器镜像存储在多个单独的“层”中,Docker 守护程序需要在容器运行时覆盖和展平这些层。当多个容器镜像重用同一层时,只需要该层的一个副本即可重新创建运行时容器的文件系统。这提高了存储的利用效率,但在分发和检查容器镜像时确实增加了一些复杂性,因此 Docker 提供了特殊的命令来执行此操作。使用 Singularity,整个执行环境都包含在一个单独的可执行文件中。当多个镜像具有相似内容时,这会引入重复,但它使得这些镜像的分发变得非常简单,因为它现在可以使用传统的文件传输方法、协议和工具来完成。

Docker 容器配方文件(即 Dockerfile 和相关资产)可用于重新创建为项目构建的容器镜像。Singularity 允许本地导入和运行 Docker 容器,因此相同的文件可以用于这两种引擎。

一天的工作

为了用一个实际例子来说明以上内容,让我们把自己放在计算科学家的角度。为了不特别点名任何人,假设您想使用 ToolA,它处理输入文件并创建包含关于它们的统计信息的输出。在请求系统管理员帮助您之前,您决定在本地桌面上测试该工具,看看它是否有效。

ToolA 语法简单。它是一个单独的二进制文件,它接受一个或多个文件名作为命令行参数,并接受 -o {json|yaml} 标志来更改结果的格式。输出存储在与输入文件相同的路径中。例如

$ ./ToolA file1 file2
$ ls
file1 file1.out file2 file2.out ToolA

您有数千个文件要处理,但即使 ToolA 使用多线程独立处理文件,您的机器也没有数千个 CPU 核心。您必须使用集群的作业调度器。大规模执行此操作的最简单方法是启动与您需要处理的文件一样多的作业,每个作业使用一个 CPU 线程。您测试新方法

$ export PATH=$(pwd):${PATH}
$ cd ~/input/files/to/process/samples
$ ls -l | wc -l
38
$ # we will set this to the actual qsub command when we run in the cluster
$ qsub=""
$ for myfiles in *; do $qsub ToolA $myfiles; done
...
$ ls -l | wc -l
75

太棒了。是时候麻烦系统管理员并在集群中安装 ToolA 了。

事实证明,ToolA 很容易在 Ubuntu Bionic 中安装,因为它已经在存储库中,但在我们的 HPC 集群使用的 CentOS 7 中编译却是一场噩梦。因此,系统管理员决定创建一个 Docker 容器镜像并将其推送到公司的注册表。他还将您添加到 docker 组,并在恳求您不要胡作非为之后。

您查找 Docker 命令的语法,并决定在提交数千个可能失败的作业之前进行一些测试运行。

$ cd ~/input/files/to/process/samples
$ rm -f *.out
$ ls -l | wc -l
38
$ docker run -d registry.example.com/ToolA:latest file1
e61d12292d69556eabe2a44c16cbd27486b2527e2ce4f95438e504afb7b02810
$ ls -l | wc -l
38
$ ls *out
$

啊,当然,您忘记挂载文件了。让我们再试一次。

$ docker run -d -v $(pwd):/mnt registry.example.com/ToolA:latest /mnt/file1
653e785339099e374b57ae3dac5996a98e5e4f393ee0e4adbb795a3935060acb
$ ls -l | wc -l
38
$ ls *out
$
$ docker logs 653e785339
ToolA: /mnt/file1: Permission denied

您向系统管理员寻求帮助,他告诉您 SELinux 阻止进程访问文件,并且您的 docker run 中缺少一个标志。您不知道 SELinux 是什么,但您记得文档中某处提到了它,因此您查找它并再次尝试

$ docker run -d -v $(pwd):/mnt:z registry.example.com/ToolA:latest /mnt/file1
8ebfcbcb31bea0696e0a7c38881ae7ea95fa501519c9623e1846d8185972dc3b
$ ls *out
$
$ docker logs 8ebfcbcb31
ToolA: /mnt/file1: Permission denied

您回到系统管理员那里,他告诉您容器默认使用 UID 为 1000 的 myuser,但您的文件仅对您可读,并且您的 UID 不同。因此,您做了您知道是不良做法的事情,但您已经厌倦了:您在再次尝试之前运行 chmod 777 file1。您也厌倦了复制和粘贴哈希值,因此您在 docker run 中添加了另一个标志

$ docker run -d --name=test -v $(pwd):/mnt:z registry.example.com/ToolA:latest /mnt/file1
0b61185ef4a78dce988bb30d87e86fafd1a7bbfb2d5aea2b6a583d7ffbceca16
$ ls *out
$
$ docker logs test
ToolA: cannot create regular file '/mnt/file1.out': Permission denied

唉,至少这次您得到了不同的错误。进步!友好的系统管理员告诉您,容器中的进程将没有您目录的写入权限,因为身份不匹配,并且您需要在命令行上添加更多标志。

$ docker run -d -u $(id -u):$(id -g) --name=test -v $(pwd):/mnt:z registry.example.com/ToolA:latest /mnt/file1
docker: Error response from daemon: Conflict. The container name "/test" is already in use by container "0b61185ef4a78dce988bb30d87e86fafd1a7bbfb2d5aea2b6a583d7ffbceca16". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.
$ docker rm test
$ docker run -d -u $(id -u):$(id -g) --name=test -v $(pwd):/mnt:z registry.example.com/ToolA:latest /mnt/file1
06d5b3d52e1167cde50c2e704d3190ba4b03f6854672cd3ca91043ad23c1fe09
$ ls *out
file1.out
$

成功!现在我们只需要用作业调度器使用的命令包装我们的命令,然后再次用我们的 for 循环包装所有这些。

$ cd ~/input/files/to/process
$ ls -l | wc -l
934752984
$ for myfiles in *; do qsub -q short_jobs -N "toola_${myfiles}" docker run -d -u $(id -u):$(id -g) --name="toola_${myfiles}" -v $(pwd):/mnt:z registry.example.com/ToolA:latest /mnt/${myfiles}; done

现在这有点笨拙,不是吗?让我们看看使用 Singularity 如何简化它。

$ cd ~
$ singularity pull --name ToolA.simg docker://registry.example.com/ToolA:latest
$ ls
input ToolA.simg
$ ./ToolA.simg
Usage: ToolA [-o {json|yaml}] <file1> [file2...fileN]
$ cd ~/input/files/to/process
$ for myfiles in *; do qsub -q short_jobs -N "toola_${myfiles}" ~/ToolA.simg ${myfiles}; done

还需要我多说吗?

这之所以有效,是因为默认情况下,Singularity 容器以启动它们的用户身份运行。没有后台守护程序,因此不允许权限提升。Singularity 还默认绑定挂载一些目录($PWD$HOME/tmp/proc/sys/dev)。管理员可以配置全局(即主机)基础上也默认挂载的其他目录,最终用户也可以(可选地)在运行时绑定任意目录。当然,标准的 Unix 权限适用,因此这仍然不允许无限制地访问主机文件。

但是气候变化呢?

哦!当然。回到正题。我们决定按项目分解我们需要运行的大量模拟。然后,每个项目都可以专注于特定的作物、特定的地理区域或不同的作物管理技术。在完成特定项目的所有模拟后,它们被整理到一个 MariaDB 数据库中,并使用 RStudio Shiny Web 应用程序进行可视化。

Prototype Shiny app screenshot

原型 Shiny 应用程序屏幕截图显示了全国范围内运行的气候变化对青贮玉米的影响,比较了当前和世纪末的情景。

该应用程序允许我们比较用户可以通过从与气候(包括当前气候和世纪中期和世纪末的气候变化预测)、土壤和特定管理技术(如灌溉或施肥)相关的变量组合中选择来构建的两种不同情景(参考与替代)。结果显示为栅格值或差异(每个像素的结果的平均值或变异系数)及其在感兴趣区域的分布。

上面的屏幕截图显示了一个原型全国范围内跨“耕地”运行的示例,我们在其中比较了基线 (1985-2005) 与未来气候变化 (2085-2100) 在最极端排放情景下的青贮玉米生物量。在此示例中,我们没有考虑任何管理技术的变化,例如调整播种日期。我们看到南半球产量的大部分负面影响发生在北部地区,而最南端则显示出积极的反应。当然,我们建议(并且您会期望)农民开始适应年初的温暖气温并做出相应的反应(例如,提前播种,这将减少负面影响并增强正面影响)。

下一步

随着框架的到位,剩下的就是繁重的工作。运行所有模拟!当然,说起来容易做起来难。我们的内部集群是一个共享资源,我们必须与其他几个项目和团队竞争容量。

计划开展更多工作,以进一步概括我们如何在计算资源之间分配作业,以便我们可以利用任何可以获得的容量(包括公共云,如果项目获得足够的额外资金)。这将意味着变得与作业调度器无关并解决数据引力问题。

还在进行进一步改进 Web 应用程序的 UI 和 UX 方面的工作,直到我们确信它可以发布给政策制定者和其他感兴趣的各方。

如果您从科学的角度对我们的工作感兴趣,请联系我,我会让您与项目负责人联系。对于所有其他咨询,您也可以联系我,我会尽力提供帮助。 


Eric Burgueño 将在 1 月 21 日至 25 日在新西兰基督城的 linux.conf.au 上介绍 使用容器分析气候变化和土壤对新西兰农作物的影响

User profile image.
大家好!我是一名 IT 专业人士,专门从事 GNU/Linux 和开源。我也有法律学位,但计算机才是我的真正热情。我对很多事情都有大概的了解。我是一位科学爱好者,也是一位有抱负的通晓多种语言的人(包括人类语言和计算机语言)。我使用牛津逗号,并用空格缩进我的代码 😄。

3 条评论

干杯!是的,我们确实使用了。我们使用的实际提交命令比描述的要复杂得多,并且肯定使用了作业数组。但为了本文的目的,我不得不使用可以说明问题的最简单语法。

回复 作者 lpryszcz

这真是我今天读到的最好的文章,太棒了

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.