【OPS.0x07】使用 distcc 配置分布式编译系统
本文最后更新于:2025年7月29日 凌晨
别急,还在编译(1919/114514)
0x00. 一切开始之前
前面忘了中间忘了后面也忘了,总而言之作为 Gentoo Linux 用户的一大痛点就是更新每个软件包都需要从源码编译会耗费大量时间,哪怕不使用 Gentoo,各种日常的编译任务(比如说做 kernel 漏洞利用的肯定得天天编译各种不同的 kernel)也会占用大量的计算资源,对于笔者这种日用系统便是 Linux 的人而言当编译流水线开动起来时本地的资源被大量挤占必然会导致其他日用服务的资源无法得到充足分配,从而影响日用系统的稳定性与使用舒适度(例如音乐播放器会一卡一卡的, 对于习惯一边听音乐一边工作的人这真能忍🐎❓ )
恰好笔者手头还有一些闲置的计算资源,因此将本地的一些计算任务给 offload 到计算集群当中会是一个不错的选择,经过一番调研之后笔者选择尝试 distcc :一个经过多年维护的分布式 C/C++ 编译工具
因此这篇博客主要简单讲讲如何部署 distcc
0x01. 在服务端部署 distcc
方法一:使用 Docker 进行部署(推荐)
用 docker 部署 distcc
首先编写 dockerfile,直接安装 distcc 并运行 distccd
就行,不用考虑服务啥的,这里以 Gentoo 为例,你可以根据你自己所使用的发行版进行微调:
1 |
|
其中 distccd
是 distcc 的服务端守护进程,参数说明如下:
--port
:distccd 的服务开放开放端口,这里在本地的3632
端口--daemon
:运行为守护进程--log-file
:日志记录文件,这里为/var/log/distccd.log
--nice
(-N
) :进程 nice 等级,越大越不抢别的进程的优先级,最大19
--allow
:允许的客户端 IP,格式为 CIDR--jobs
:允许的最大并发线程数,这里是192
,建议按需进行调整
然后 build 再 run 就行,方便起见可以直接使用本地网络:
1 |
|
方便起见也可以用 docker-compose,在 Dockerfile 目录编写配置文件 docker-compose.yml
如下:
1 |
|
方法二:在本地直接搭建服务
本地安装 distcc
用你所使用的发行版的包管理器将 distcc 安装到本地,例如笔者服务器是 openSUSE Tumblewwed:
1 |
|
之后根据你所使用的 init 系统进行相应操作:
注:对于 ssh 连接而言 distcc 似乎会通过 ssh 的方式直接启动远程服务器上的 distccd,因此这种情况下似乎不需要我们手动开启服务?
systemd
绝大部分发行版默认使用的应该都是 systemd(红帽的大手太猛了),包括笔者手上的绝大部分机器用的也都是 systemd,所以先来看看如何在 systemd 下进行 distccd 的相应配置
首先创建配置文件:
1 |
|
然后在 /etc/systemd/system/distccd.service.d/distcc.conf
中写入如下内容:
1 |
|
参数说明如下:
ALLOWED_SERVERS
:允许的客户端 IP,格式为 CIDRDISTCC_VERBOSE
:开启详细日志DISTCC_SAVE_TEMPS
:不删除临时文件
如果有需要,可以运行如下命令修改 distccd 启动参数:
1 |
|
最后启用服务:
1 |
|
OpenRC
首先创建配置文件 /etc/conf.d/distccd
并写入如下内容:
1 |
|
参数说明:
--port
:distccd 的服务开放开放端口,这里在本地的3632
端口--log-level
:日志级别--log-file
:日志记录文件,这里为/var/log/distccd.log
--nice
(-N
) :进程 nice 等级,越大越不抢别的进程的优先级,最大19
--allow
:允许的客户端 IP,格式为 CIDR
然后创建日志文件:
1 |
|
最后启动服务:
1 |
|
0x02. 配置客户端编译选项
客户端这边的配置就简单得多,只需要安装 distcc 后再配置服务端地址即可
基本配置
首先是安装 distcc,这里以 Gentoo Linux 为例:
1 |
|
之后配置服务端地址,我们可以直接通过环境变量 DISTCC_HOSTS
指定编译服务器的地址,支持多个服务器,以下是一个例子:
1 |
|
在这个示例当中:
localhost
是第一个 distcc 服务器(即本地),/8
表示在本地使用 8 个进程进行编译10.0.0.1
是第二个 distcc 服务器,通过默认的3632
端口 TCP 直连,/4
表示在该服务器上使用 4 个进程编译,,lzo
表示传输时使用 LZO 压缩10.0.0.2
是第三个服务器,通过 ssh 进行连接,/2
表示在该服务器上使用 2 个进程编译,,cpp
表示 distcc-pump mode,,lzo
表示传输时使用 LZO 压缩
更多配置可以参见 distcc(1) - Linux man page
对于持久化配置,distcc 支持往家目录的 ~/.distcc/hosts
或系统配置的 /etc/distcc/hosts
里写入服务端地址,以下是一个例子:
1 |
|
在这个示例当中:
10.0.0.1
是第一个 distcc 服务器,通过1145
端口 TCP 连接,/4
表示在该服务器上使用 4 个进程编译10.0.0.2
是第二个服务器,通过 ssh 进行连接,/2
表示在该服务器上使用 2 个进程编译
localhost 似乎在不显示指定的情况下也会进行编译工作?
为 C/C++ 项目使用 distcc 进行分布式编译
我们只需要把编译器指定为 distcc 原编译器
即可,以 cmake 项目为例:
1 |
|
需要注意的是这里不能用
-DCMAKE_CXX_COMPILER="distcc g++"
这样的配置,否则会出错
Extra:为 Portage 配置 distcc 编译
感觉不如 binhost 👋
众所周知 Gentoo 是一个什么软件包都需要从源码进行编译的 Linux 发行版,因此在本地机器配置性能不太行的情况下,考虑使用远程服务器进行辅助分布式编译会是一个不错的选择
首先我们需要在 /etc/portage/make.conf
当中启用 distcc 特性:
1 |
|
然后是编译与链接线程数配置,通常遵循:
- 编译线程数
N
应当为总 CPU 核心数(本地 + 远程)的两倍,再 + 1 - 链接线程数
M
应当为本地 CPU 核心数
1 |
|
由于我们是在远程机器上编译,不同的机器的 CPU 架构通常不一样,因此如果编译选项中使用 -march=native
那就很容易使得不同的编译机器是针对自身而非本地机器进行优化,最后跑不起来就尴尬了,我们应当根据本地机器的 CPU 架构 显式 指定,以笔者笔记本的 i9-13980hx 为例,其为 raptorlake
架构:
1 |
|
最后配置 hosts,需要注意的是这里我们需要将远程服务器的配置写入到 /etc/distcc/hosts
当中
0x03. 可能出现的错误
无法启动 distccd
有的时候你可能会遇到类似如下报错:
1 |
|
主要是因为 distcc 需要一个伪装路径的存在(闲的),我们可以通过运行如下命令解决:
1 |
|
无法连接 failed with exit code 110
编译的时候可能会遇到如下报错:
1 |
|
核心原因是连接不上端口,有可能是防火墙的问题,可以检查自己服务器的防火墙配置( ufw
或者 firewall-cmd
一类的)并开启 3632
端口
如果用 telnet
可以直接连接上服务器的 3632
端口就说明正常了
成功连接但无法分发到远程编译
编译的时候可能会遇到如下报错:
1 |
|
主要原因是 远程环境和本地环境不同 ,解决方案就是两边使用相同的发行版环境相同的编译器, 这也是为什么笔者推荐使用 docker 进行部署的缘故 :)
提示 DISTCC_HOSTS 为空
有的时候虽然配置了 ~/.distcc/hosts
,但是还是会莫名其妙报 DISTCC_HOSTS
为空的错误,目前据笔者观测这个问题出现得比较随机,解决方案是持久化 DISTCC_HOSTS
环境变量(例如写到 ~/.bashrc
)或是开一个新的 shell
往
/etc/distcc/hosts
写应该也能解决,但是这个问题不太能稳定复现,所以笔者也不知道:(
portage 无法分发到远程编译
这个问题主要针对 Gentoo Linux 用户,可能会有如下的错误信息形式:
注:这种情况下仍然会在本地进行正常编译,而不会报错退出,因此你未必会注意到这个:)
1 |
|
原因是目前 Gentoo Linux 把 distcc 的 pump 组件给移除了( 听说是因为写得太烂了 ),解决方案就是在 hosts 当中不使用 cpp
选项指定 pump
0xFF. What’s more…
distcc 的方案虽然理论上听着很好,但看起来存在一些比较明显的问题,笔者第一次测试编译 6.16-rc6
版本的 Linux kernel 的 bzImage
,使用 distcc 本地 13th Gen Intel i9-13980HX
总计 32 线程配合一台 2x INTEL XEON PLATINUM 8558
总计 192 线程的服务器与一台 AMD Ryzen Threadripper PRO 5975WX
总计 64 线程的服务器进行分布式编译用时 5m47.753s
,似乎也没有特别快,但是在这三台机器上分别进行编译耗时分别为 4m41.424s
、 1m6.686s
、1m45.978s
, 三台机器联合编译的速度还没有单独一台机器编译来得快而且还慢了这么多那就未免有点太小丑了……
笔者分析大概有几个可能拖慢编译速度的原因,首先是比较依赖网络带宽,上传和下载其实都是影响编译速度的瓶颈,其次负载均衡在默认情况下似乎很难做好,例如笔者就碰到过 distcc 上报远程一堆 CPU 都是 busy 状态实际上完全空闲的情况,那结果就是经典的一核干活九核围观:
所以 distcc 这个东西,说是做到远程分布式编译了吧那确实做到了,但是在笔者的体验当中这个编译任务分发出去弄成分布式的居然还没有单独在一台机器上执行来得快那确实就有点过于小丑了,不过总的来说也确实实现了笔者最开始的减缓本地编译的压力的目的,所以除了资源利用率比较低这个问题以外其实倒也还好?
另外一个降低笔者分布式编译速度的点是 distcc 的 pump
模式(简单来说能加速并行编译)被在 Bug 702146 当中移除了,原因是 distcc 的 pump-mode 假设源文件不会在生命周期内被修改,而在构建过程当中生成新的源文件这种事情并不少见,因此 Gentoo 团队认为不彳亍, 而从 19 年这个 bug 被提出到现在 distcc 似乎都还没有修复这个问题 ,侧面其实也反映出 distcc 的团队在某些方面也存在一些问题?