使用 distcc 进行分布式编译
开源开发中最令人沮丧的方面之一就是花费大量时间等待代码编译。现在,在一台机器上编译 KDE 的基本模块和库大约需要三个小时,而这仅仅是为了获得一个桌面环境。即使使用酷睿 2 双核处理器,这也仍然是很多坐着等待的时间。
如果我能支配另外两台酷睿双核机器,我很乐意能够使用它们所有加起来的处理能力。这时,distcc 就派上用场了。
distcc 是一个程序,允许人们通过网络将编译负载分布到多台机器上。它本质上是 GCC 的前端,适用于 C、C++、Objective C 和 Objective C++ 代码。它不需要大型编译主机集群就能发挥作用——仅仅增加一台性能相近的机器就能显著减少编译时间。在工作场所或大学环境中,当您拥有大量类似的工位时,它是一个非常强大的工具,但我最喜欢的 distcc 用途之一是在楼下咖啡馆舒适地用笔记本电脑进行开发工作,并将所有编译任务通过无线网络推送到楼上更强大的台式 PC 上。这不仅能更快地完成工作,而且笔记本电脑也能保持更凉爽。
在每个系统上使用相同的发行版不是必须的,但强烈建议您使用相同版本的 GCC。除非您已设置交叉编译器,否则还要求您使用相同的 CPU 架构和相同的操作系统。例如,Linux(使用 ELF 二进制文件)和某些 BSD(使用 a.out)默认情况下无法相互编译。如果编译器不匹配,代码可能会以许多创造性和令人沮丧的方式编译错误。
在撰写本文时,distcc 的最新版本是 2.18.3。大多数主要发行版都有软件包,或者您可以下载 tarball 并自行编译。它遵循通常的 automake 过程:./configure; make; make install; 有关详细信息,请参阅 README 和 INSTALL 文件。
需要调用 distcc 来代替编译器。您可以简单地导出 CC=distcc 以替换您想要使用的编译器,但在开发工作站上,我更喜欢更持久的方法。我喜欢在 ~/bin 中创建符号链接,并将其设置为 PATH 变量的前面。然后,始终调用 distcc。这种方法过去用于解决构建 KDE 中使用的 ld 版本中的一些错误,并且被认为具有最广泛的兼容性(有关更多信息,请参阅 distcc 手册页)。
mkdir ~/bin for i in cc c++ gcc g++; do ln -s `which distcc` ~/bin/$i; done
如果 ~/bin 尚未位于您的路径的开头,请将其添加到您的 shellrc 文件中
export PATH=~/bin:$PATH setenv PATH ~/bin:$PATH
每个客户端都需要运行 distcc 守护程序,并且需要允许来自主机的 distcc 端口(3632)上的连接。守护程序可以在启动时手动启动,方法是将其添加到 rc.local 或 bootmisc.sh(取决于发行版),甚至可以从 inetd 启动。如果 distccd 作为非特权用户帐户启动,它将保留该 UID 的所有权。如果它以 root 身份启动,它将尝试更改为 distcc 或 nobody 用户。如果您想以 root 身份启动守护程序(可能从 init 脚本启动)并更改为不是 distcc 或 nobody 的用户,则 -user 选项允许您选择守护程序应作为哪个用户运行。
distccd -user jes -allow 192.168.80.0/24
在本例中,我也使用了 -allow 选项。这接受常见的 CIDR 表示法的主机掩码,并将 distcc 访问限制为指定的主机。在这里,我将访问权限仅限制为我在家庭网络上使用的特定子网上的服务器——地址范围为 192.168.80.1–192.168.80.254 的机器。如果您特别注重安全,您可以将其限制为单个地址 (192.168.80.5) 或此表示法支持的任何地址范围。我喜欢保持相当宽松,因为我经常根据我正在编译的内容和时间来更改哪个主机是主主机。
回到您计划运行编译任务的主系统上,您需要让 distcc 知道您的集群的其余部分在哪里。有两种方法可以实现这一点。您可以将集群的主机名或 IP 地址添加到文件 ~/.distcc/hosts 中,或者您可以导出由空格分隔的变量 DISTCC_HOSTS。这些名称需要解析——要么将您想要使用的名称添加到 /etc/hosts 中,要么在使用内部 DNS 时使用主机的 IP 地址。
192.168.80.128 192.168.80.129 localhost
主机的顺序极其重要。distcc 无法确定哪些主机更强大或负载更低,只是按顺序分配编译作业。对于无法并行运行的作业,例如配置测试,这意味着列表中的第一个主机将承担主要的编译负担。如果您的机器性能各异,那么将最强大的机器放在列表的最前面,将性能最差的机器放在主机列表的最后,会对编译时间产生很大影响。
根据运行 distcc 的计算机的性能,您可能根本不想在主机列表中包含 localhost。Localhost 必须完成所有预处理——这是一个经过深思熟虑的设计选择,这意味着您无需确保每台机器上都具有相同的库和头文件集——以及所有链接,这在大型编译中通常是处理器密集型的。在管理将文件通过网络发送到其他编译器时,也存在一定的少量处理开销。根据经验,distcc 文档建议,对于三到四台主机,localhost 可能应该放在列表的最后,对于超过五台主机,则应该完全排除在外。
现在您已经配置了集群,编译过程与您在没有 distcc 的情况下进行编译非常相似。唯一真正的区别是,在发出 make 命令时,您需要指定多个作业,以便集群中的其他机器有一些工作要做。一般来说,作业数量应约为可用 CPU 数量的两倍。因此,对于具有三台单核机器的设置,您将使用make -j6。对于三台双核机器,您将使用make -j 12。如果您已从主机列表中删除 localhost,请不要将它的 CPU 或 CPU 包含在此计算中。
distcc 包括两个监视工具,可用于监视编译作业的进度。如果您的主主机通过 SSH 访问,则基于控制台的 distccmon-text 特别出色。作为运行编译作业的用户,执行以下命令:distccmon-text $s,其中 $s 是您希望它刷新的秒数。例如,以下命令
distccmon-text 5
每五秒更新一次您的监视器,显示编译作业信息。
如果您从源代码编译,图形化的 distccmon-gnome 将作为 distcc 的一部分分发,但它可能是单独的软件包,具体取决于您的发行版。它在图形显示中提供类似的信息,让您一目了然地看到哪些主机正在被大量使用,以及作业是否正在正确分发。通常需要多次尝试才能获得最佳的主机顺序——像 distccmon-gnome 这样的工具可以更轻松地查看机器是否被过度利用或利用不足,以及是否需要移动构建顺序。
distcc 依赖于网络是受信任的。任何能够连接到机器 distcc 端口的人都可以以 distcc 用户的身份在该主机上运行任意命令。至关重要的是,distccd 进程不以 root 身份运行,而是以 distcc 或 nobody 用户身份运行。仔细考虑您的 -allow 语句并确保它们已针对您的网络进行适当锁定也极其重要。
在家庭或小型工作场所网络等环境中,您可以通过安全防火墙与外部世界隔离,并且可以追究人们在网络上不友好的责任,distcc 是 足够安全 的。如果您的 distccd 主机没有以 root 身份运行守护程序,并且您的 allow 语句适当地限制了连接机器,那么任何人都不太可能或不愿意利用它们。
还存在网络上的其他人可以看到您的 distcc 流量的问题——任何人都可以访问和检查网络上的源代码和目标文件。同样,在受信任的网络上,这不太可能成为问题,但在某些情况下,您不希望这种情况发生,或者可能不允许这种情况发生,具体取决于您正在编译的代码以及在什么条款下编译。
在更不友好的网络上,例如大型大学校园或您知道存在安全问题的工作场所,这些可能会成为严重的问题。
对于这些情况,distcc 可以通过 SSH 运行。这确保了每一端的身份验证和签名,并且还确保代码在传输过程中被加密。由于 SSH 加密开销,SSH 通常慢约 25%。配置非常相似,但它需要使用 ssh-keys。必须使用无密码密钥或 ssh-agent,因为您将无法为 distcc 提供密码以供使用。对于 SSH 连接,必须在客户端上安装 distccd,但它不能监听连接——守护程序将在需要时通过 SSH 启动。
首先,使用以下命令创建 SSH 密钥:ssh-keygen -t dsa,然后将其添加到您的 distcc 主机上目标用户的 ~/.ssh/authorized_keys 中。为了安全起见,建议始终为 SSH 密钥设置密码。
在本例中,我在所有主机上都使用我自己的用户帐户和一个简单的 bash 循环来快速分发密钥:
for i in 192.168.80.120 192.168.80.100; do cat ~/.ssh/id_dsa.pub ↪| ssh jes@$i 'cat - >> ~/.ssh/authorized_keys'; done
要让 distcc 知道它需要通过 SSH 连接到主机,请修改 ~/.distcc/hosts 文件或 $DISTCC_HOSTS 变量。要指示 distcc 使用 SSH,只需在主机名的开头添加一个 @ 即可。如果您需要在任何主机上使用不同的用户名,您可以将其指定为 user@host:
localhost @192.168.80.100 @192.168.80.120
因为我使用的是带有密码的密钥,所以我还需要使用以下命令启动我的 SSH 代理:ssh-add并输入我的密码。对于那些不熟悉 ssh-agent 的人来说,它是一个随 OpenSSH 提供的工具,它方便您只需在一个会话中输入一次密钥密码,并将其保留在内存中。
现在我们已经设置了 SSH 密钥并告知 distcc 使用安全连接,程序与以前相同——只需make -jn.
这种使用您希望 distcc 遵守的选项修改主机名的方法可以用于指定连接类型以外的更多用途。例如,选项 /limit 可用于覆盖将发送到 distccd 服务器的默认作业数。原始限制是每个主机四个作业,localhost 除外,localhost 仅发送两个作业。对于具有两个以上 CPU 的服务器,可以增加此限制。
另一个选项是为 TCP 或 SSH 连接使用 lzo 压缩。这会增加 CPU 开销,但在慢速网络上可能是值得的。将这两个选项结合使用将通过以下方式完成:
localhost 192.168.80.100/6,lzo
此选项将发送到 192.168.80.100 的作业数增加到六个,并启用 lzo 压缩。这些选项按特定顺序解析,因此如果您打算使用它们,建议研究一下手册页。distcc 手册页上可以找到包含示例的完整选项列表。
distcc 的灵活性远不止此处解释的范围。一种流行的配置是将其与编译器缓存 ccache 一起使用。distcc 还可以与 crossdev 结合使用,以分布式方式为不同的架构进行交叉编译。现在,您旧的 SPARC 工作站或您改装成 Linux 盒子的 G5 Mac 也可以参与其中。这些是未来文章的主题;现在,我要去玩我新编译的桌面环境了。
Jes Hall 是来自新西兰的 UNIX 系统顾问和 KDE 开发人员。她热衷于帮助开源软件为那些原本无法获得生活变革信息和工具的人们带来这些资源。