【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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Gentoo base
FROM gentoo/stage3:latest

# Basic packages
RUN emerge-webrsync && \
emerge sys-devel/distcc sys-devel/gcc llvm-core/clang

# Log file
RUN touch /var/log/distcc.log && chmod 666 /var/log/distcc.log

# keep container running
RUN echo '#!/bin/sh' > /root/start.sh
RUN echo 'distccd --port 3632 --daemon --log-file /var/log/distcc.log -N 19 --allow 0.0.0.0/0 --jobs 192' >> /root/start.sh
RUN echo 'sleep infinity' >> /root/start.sh
RUN chmod +x /root/start.sh
CMD ["/root/start.sh"]

# expose ports
EXPOSE 3632

其中 distccd 是 distcc 的服务端守护进程,参数说明如下:

  • --port :distccd 的服务开放开放端口,这里在本地的 3632 端口
  • --daemon :运行为守护进程
  • --log-file :日志记录文件,这里为 /var/log/distccd.log
  • --nice-N) :进程 nice 等级,越大越不抢别的进程的优先级,最大 19
  • --allow :允许的客户端 IP,格式为 CIDR
  • --jobs :允许的最大并发线程数,这里是 192 ,建议按需进行调整

然后 build 再 run 就行,方便起见可以直接使用本地网络:

1
2
$ docker build -t a3distcc:latest .
$ docker run -d --network=host --name=a3distcc a3distcc:latest

方便起见也可以用 docker-compose,在 Dockerfile 目录编写配置文件 docker-compose.yml 如下:

1
2
3
4
5
6
7
version: '3'
services:
distcc:
image: a3distcc:v1.0
build: .
ports:
- "3632:3632"

方法二:在本地直接搭建服务

本地安装 distcc

用你所使用的发行版的包管理器将 distcc 安装到本地,例如笔者服务器是 openSUSE Tumblewwed:

1
$ sudo zypper in distcc distcc-server

之后根据你所使用的 init 系统进行相应操作:

注:对于 ssh 连接而言 distcc 似乎会通过 ssh 的方式直接启动远程服务器上的 distccd,因此这种情况下似乎不需要我们手动开启服务?

systemd

绝大部分发行版默认使用的应该都是 systemd(红帽的大手太猛了),包括笔者手上的绝大部分机器用的也都是 systemd,所以先来看看如何在 systemd 下进行 distccd 的相应配置

首先创建配置文件:

1
2
$ sudo mkdir /etc/systemd/system/distccd.service.d
$ sudo touch /etc/systemd/system/distccd.service.d/distcc.conf

然后在 /etc/systemd/system/distccd.service.d/distcc.conf 中写入如下内容:

1
2
3
4
[Service]
Environment="ALLOWED_SERVERS=127.0.0.1"
Environment="DISTCC_VERBOSE=1"
Environment="DISTCC_SAVE_TEMPS=1"

参数说明如下:

  • ALLOWED_SERVERS :允许的客户端 IP,格式为 CIDR
  • DISTCC_VERBOSE :开启详细日志
  • DISTCC_SAVE_TEMPS :不删除临时文件

如果有需要,可以运行如下命令修改 distccd 启动参数:

1
2
$ sudo systemctl edit --full distccd.service
$ sudo systemctl daemon-reload

最后启用服务:

1
$ sudo systemctl enable --now distccd

OpenRC

首先创建配置文件 /etc/conf.d/distccd 并写入如下内容:

1
DISTCCD_OPTS="--port 3632 --log-level notice --log-file /var/log/distccd.log --nice 16 --allow 192.168.0.4 --allow 192.168.1.0/24"

参数说明:

  • --port :distccd 的服务开放开放端口,这里在本地的 3632 端口
  • --log-level :日志级别
  • --log-file :日志记录文件,这里为 /var/log/distccd.log
  • --nice-N) :进程 nice 等级,越大越不抢别的进程的优先级,最大 19
  • --allow :允许的客户端 IP,格式为 CIDR

然后创建日志文件:

1
2
$ sudo touch /var/log/distccd.log
$ sudo chown distcc:root /var/log/distccd.log

最后启动服务:

1
2
$ sudo rc-update add distccd default
$ sudo rc-service distccd start

0x02. 配置客户端编译选项

客户端这边的配置就简单得多,只需要安装 distcc 后再配置服务端地址即可

基本配置

首先是安装 distcc,这里以 Gentoo Linux 为例:

1
$ sudo emerge -av sys-devel/distcc

之后配置服务端地址,我们可以直接通过环境变量 DISTCC_HOSTS 指定编译服务器的地址,支持多个服务器,以下是一个例子:

1
export DISTCC_HOSTS="localhost/8 10.0.0.1/4,lzo arttnba3@10.0.0.2/2,cpp,lzo"

在这个示例当中:

  • 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:1145/4 arttnba3@10.0.0.2/2

在这个示例当中:

  • 10.0.0.1 是第一个 distcc 服务器,通过 1145 端口 TCP 连接,/4 表示在该服务器上使用 4 个进程编译
  • 10.0.0.2 是第二个服务器,通过 ssh 进行连接,/2 表示在该服务器上使用 2 个进程编译

localhost 似乎在不显示指定的情况下也会进行编译工作?

为 C/C++ 项目使用 distcc 进行分布式编译

我们只需要把编译器指定为 distcc 原编译器 即可,以 cmake 项目为例:

1
2
3
4
$ export CC="distcc gcc"
$ export CXX="distcc g++"
$ cmake $SOURCE_CODE_PATH
$ make -j线程数

需要注意的是这里不能用 -DCMAKE_CXX_COMPILER="distcc g++" 这样的配置,否则会出错

Extra:为 Portage 配置 distcc 编译

感觉不如 binhost 👋

众所周知 Gentoo 是一个什么软件包都需要从源码进行编译的 Linux 发行版,因此在本地机器配置性能不太行的情况下,考虑使用远程服务器进行辅助分布式编译会是一个不错的选择

首先我们需要在 /etc/portage/make.conf 当中启用 distcc 特性:

1
FEATURES="distcc"

然后是编译与链接线程数配置,通常遵循:

  • 编译线程数 N 应当为总 CPU 核心数(本地 + 远程)的两倍,再 + 1
  • 链接线程数 M 应当为本地 CPU 核心数
1
MAKEOPTS="-jN -lM"

由于我们是在远程机器上编译,不同的机器的 CPU 架构通常不一样,因此如果编译选项中使用 -march=native 那就很容易使得不同的编译机器是针对自身而非本地机器进行优化,最后跑不起来就尴尬了,我们应当根据本地机器的 CPU 架构 显式 指定,以笔者笔记本的 i9-13980hx 为例,其为 raptorlake 架构:

1
2
3
COMMON_FLAGS="-O2 -march=raptorlake -mtune=raptorlake" # don't use -march=native!
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"

最后配置 hosts,需要注意的是这里我们需要将远程服务器的配置写入到 /etc/distcc/hosts 当中

0x03. 可能出现的错误

无法启动 distccd

有的时候你可能会遇到类似如下报错:

1
(dcc_warn_masquerade_whitelist) CRITICAL! /usr/lib64/distcc not found. You must see up masquerade (see distcc(1)) to list whitelisted compilers or pass --enable-tcp-insecure. To set up masquerade automatically run update-distcc-symlinks.

主要是因为 distcc 需要一个伪装路径的存在(闲的),我们可以通过运行如下命令解决:

1
$ sudo update-distcc-symlinks

无法连接 failed with exit code 110

编译的时候可能会遇到如下报错:

1
2
3
4
distcc[3546823] ERROR: compile 源文件路径 on arttnba3@服务器地址 failed with exit code 110
distcc[3546823] (dcc_build_somewhere) Warning: remote compilation of '源文件路径' failed, retrying locally
distcc[3546823] (dcc_mark_timefile) mark /home/arttnba3/.distcc/lock/backoff_ssh_服务器地址_0
distcc[3546823] Warning: failed to distribute /home/arttnba3/Data/DailyProgramming/A3DrawLang/semantic/semantic.cpp to arttnba3@服务器地址, running locally instead

核心原因是连接不上端口,有可能是防火墙的问题,可以检查自己服务器的防火墙配置( ufw 或者 firewall-cmd 一类的)并开启 3632 端口

如果用 telnet 可以直接连接上服务器的 3632 端口就说明正常了

成功连接但无法分发到远程编译

编译的时候可能会遇到如下报错:

1
2
3
4
5
6
7
8
9
10
11
12
distcc[3557593] (dcc_readx) ERROR: unexpected eof on fd5
distcc[3557593] (dcc_r_token_int) ERROR: read failed while waiting for token "DONE"
distcc[3557593] (dcc_r_result_header) ERROR: server provided no answer. Is the server configured to allow access from your IP address? Is the server performing authentication and your client isn't? Does the server have the compiler installed? Is the server configured to access the compiler?
distcc[3557593] 1307914 bytes from 源文件路径 compiled on 服务器地址 in 4.7008s, rate 272kB/s
distcc[3557593] (dcc_mark_timefile) mark /home/arttnba3/.distcc/lock/backoff_tcp_服务器地址_3632_0
distcc[3557593] (dcc_unlock) release lock fd3
distcc[3557593] (dcc_parse_hosts_file) load hosts from /home/arttnba3/.distcc/hosts
distcc[3557593] (dcc_parse_hosts) found tcp token "服务器地址"
distcc[3557593] (dcc_check_backoff) still in backoff period for 服务器地址
distcc[3557593] (dcc_remove_disliked) remove 服务器地址 from list
distcc[3557593] (dcc_mark_timefile) mark /home/arttnba3/.distcc/lock/backoff_tcp_服务器地址_3632_0
distcc[3557593] Warning: failed to distribute /home/arttnba3/Data/DailyProgramming/A3DrawLang/semantic/semantic.cpp to 服务器地址, running locally instead

主要原因是 远程环境和本地环境不同 ,解决方案就是两边使用相同的发行版环境相同的编译器, 这也是为什么笔者推荐使用 docker 进行部署的缘故 :)

提示 DISTCC_HOSTS 为空

有的时候虽然配置了 ~/.distcc/hosts ,但是还是会莫名其妙报 DISTCC_HOSTS 为空的错误,目前据笔者观测这个问题出现得比较随机,解决方案是持久化 DISTCC_HOSTS 环境变量(例如写到 ~/.bashrc )或是开一个新的 shell

/etc/distcc/hosts 写应该也能解决,但是这个问题不太能稳定复现,所以笔者也不知道:(

portage 无法分发到远程编译

这个问题主要针对 Gentoo Linux 用户,可能会有如下的错误信息形式:

注:这种情况下仍然会在本地进行正常编译,而不会报错退出,因此你未必会注意到这个:)

1
2
distcc[371] (dcc_talk_to_include_server) Warning: INCLUDE_SERVER_PORT not set - did you forget to run under 'pump'?
distcc[371] (dcc_build_somewhere) Warning: failed to get includes from include server, preprocessing locally

原因是目前 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.424s1m6.686s1m45.978s三台机器联合编译的速度还没有单独一台机器编译来得快而且还慢了这么多那就未免有点太小丑了……

笔者分析大概有几个可能拖慢编译速度的原因,首先是比较依赖网络带宽,上传和下载其实都是影响编译速度的瓶颈,其次负载均衡在默认情况下似乎很难做好,例如笔者就碰到过 distcc 上报远程一堆 CPU 都是 busy 状态实际上完全空闲的情况,那结果就是经典的一核干活九核围观:

所以 distcc 这个东西,说是做到远程分布式编译了吧那确实做到了,但是在笔者的体验当中这个编译任务分发出去弄成分布式的居然还没有单独在一台机器上执行来得快那确实就有点过于小丑了,不过总的来说也确实实现了笔者最开始的减缓本地编译的压力的目的,所以除了资源利用率比较低这个问题以外其实倒也还好?

另外一个降低笔者分布式编译速度的点是 distcc 的 pump 模式(简单来说能加速并行编译)被在 Bug 702146 当中移除了,原因是 distcc 的 pump-mode 假设源文件不会在生命周期内被修改,而在构建过程当中生成新的源文件这种事情并不少见,因此 Gentoo 团队认为不彳亍, 而从 19 年这个 bug 被提出到现在 distcc 似乎都还没有修复这个问题 ,侧面其实也反映出 distcc 的团队在某些方面也存在一些问题?


【OPS.0x07】使用 distcc 配置分布式编译系统
https://arttnba3.github.io/2025/07/21/OPS-0X07-DISTCC_ACCEL_COMPILE/
作者
arttnba3
发布于
2025年7月21日
许可协议