CFEngine 3.27.2 进阶实战与运维
CFEngine 3.27.2 进阶实战与运维:policy server 与客户端多机部署实测、策略分发链路、cf-key 密钥体系、cf-runagent 远程执行、ACL 排障实录、日志报告与例行运维清单
CFEngine 3.27.2 进阶实战与运维
目标:在一台策略服务器(policy server,Ubuntu 22.04 + CFEngine 3.27.2)与一台客户端(voluntary client,Ubuntu 22.04 + CFEngine 3.27.2)的真实环境中,完成多机部署、策略分发、密钥信任、cf-runagent 远程执行与 ACL 排障的完整闭环,并沉淀日志排查与例行运维要点。全部命令与回显均在真实环境实测通过。
一、环境信息
本文所有命令与回显在以下环境实测通过,读者可按同样配置复现:
- 操作系统与内核:Ubuntu 22.04.5 LTS(两台独立主机,版本一致),内核 6.8.0-138-generic
- 架构:x86_64(amd64)
- CFEngine 版本:CFEngine Core 3.27.2(社区版,两端一致)
- 常驻组件:所有节点跑同一套进程——cf-serverd(策略分发)、cf-execd(定时调度)、cf-monitord(指标采集)
- 通信端口:TCP 5308(TLS,cf-serverd 监听,策略分发与 cf-runagent 复用)
- 工作目录:
/var/cfengine/ - 策略文件路径:权威源
/var/cfengine/masterfiles/(含def.json、services/autorun/),生效副本/var/cfengine/inputs/(由update.cf同步),角色记录/var/cfengine/policy_server.dat,密钥目录/var/cfengine/ppkeys/ - 实测拓扑:策略服务器
3b46438779b5(172.27.0.3)+ 客户端71baa2e48d8a(172.27.0.2),同网段互联
二、架构与拓扑说明
CFEngine 多机部署只有两种角色,全部节点跑同一套软件包:
| 角色 | 进程 | 职责 |
|---|---|---|
| policy server | cf-serverd + cf-execd + cf-monitord | 持有 masterfiles 权威源,通过 5308/TCP(TLS)向客户端分发策略 |
| client(voluntary client) | 同样全套进程 | 定时从服务器拉取策略到本地 inputs 并执行;policy_server.dat 记录服务器地址 |
关键理解:客户端也跑完整的 agent/executor/serverd——它既是被管节点,也能对外服务;角色只由「自举时指向谁」决定。分发是 pull 模型:客户端每 5 分钟主动拉取,服务器不推送。
💡 技术拓展:pull 与 push 的取舍。CFEngine 用 pull(客户端定时拉、服务器无状态可横向扩展、断网客户端继续按本地旧策略收敛);Ansible/SaltStack 的即时执行是 push(一条命令全端生效,但依赖控制端可达与 SSH/agent 通道)。pull 模型下「策略变更生效时间 = 调度间隔(默认 5 分钟)」,需要立即生效的场景用 cf-runagent 主动触发(第六章)或缩短调度。这两类模型在生产中常并用:基线收敛交给 CFEngine,应急变更交给一次性 push。
本文实测拓扑(两台独立 Ubuntu 22.04.5 主机,同网段互联):
- 策略服务器:主机名
3b46438779b5,IP172.27.0.3 - 客户端:主机名
71baa2e48d8a,IP172.27.0.2 - 版本:CFEngine Core 3.27.2(两端一致)
⚠️ 注意:先装服务器再装客户端。两端都按《CFEngine 3.27.2 Ubuntu 22.04 安装指南》完成安装;服务器按其 4.4 节自举到
127.0.0.1(成为 policy server),客户端先不做本机自举(本机自举会把角色定成 policy server,需按 3.2 重做指向服务器)。
三、搭建策略服务器与客户端
3.1 服务器侧准备
服务器自举到 127.0.0.1 后即为 policy server。确认监听与角色记录:
cat /var/cfengine/policy_server.dat
ss -tlnp | grep 5308
实测回显:
127.0.0.1
LISTEN 0 128 *:5308 *:* users:(("cf-serverd",pid=5670,fd=5))
⚠️ 注意:
policy_server.dat记录的是自举目标。服务器上它是127.0.0.1没有问题(它自己就是源);客户端上的这个文件必须指向服务器的真实可达 IP——服务器多网卡或经过 NAT 时,客户端要用「客户端能路由到」的那个地址自举。同时确认 def.json 的 ACL 放行客户端网段(默认只放行策略服务器所在 /16,网段不符时见第七章排障)。
3.2 客户端自举到服务器
/var/cfengine/bin/cf-agent --bootstrap 172.27.0.3
实测回显:
notice: Bootstrap mode: implicitly trusting server, use --trust-server=no if server trust is already established
R: Bootstrapping from host '172.27.0.3' via built-in policy '/var/cfengine/inputs/failsafe.cf'
R: This autonomous node assumes the role of voluntary client
R: Updated local policy from policy server
R: Triggered an initial run of the policy
notice: Bootstrap to '172.27.0.3' completed successfully!
逐行解读:
implicitly trusting server:首次连接隐式信任服务器公钥(安全含义见第四章 🔒);This autonomous node assumes the role of voluntary client:角色确定——自愿客户端,与服务器侧自举时的policy server角色行对照;Updated local policy from policy server:第一次真正的跨机策略同步,本机 masterfiles 已被服务器的版本取代。
自举记录写入角色文件:
cat /var/cfengine/policy_server.dat
实测回显:
172.27.0.3
⚠️ 注意:客户端自举需要服务器侧 cf-serverd 可达且 ACL 放行。实测排障中两种典型失败:网络不通(
No suitable server found+ 超时)与 ACL 拒绝(同样No suitable server found,见第七章——报错形式一致,定位时先测端口连通再查 ACL)。重新自举到新服务器是幂等操作,改指服务器直接重跑--bootstrap <新IP>即可(实测多次执行无副作用)。
四、信任与密钥体系
4.1 双向信任的建立过程
CFEngine 用每台主机唯一的 RSA 密钥对(/var/cfengine/ppkeys/localhost.priv/localhost.pub)做身份认证。首次连接的隐式信任链:
- 客户端首次连服务器:服务器收到客户端公钥,存为
/var/cfengine/ppkeys/root-MD5=<客户端公钥摘要>.pub; - 客户端侧:
implicitly trusting server模式下把服务器公钥存入本地 ppkeys。
服务器侧实测(自举完成后):
ls /var/cfengine/ppkeys/
实测回显:
localhost.priv
localhost.pub
root-MD5=818aec2e42923bf496968da90f025810.pub
root-MD5=948e60c433e68e9dca40288127b1c917.pub
解读:818aec... 是服务器自己的公钥(自举到自己时录入),948e60... 就是客户端的公钥——信任已建立。
4.2 cf-key:指纹与连接台账
查看本机密钥指纹:
/var/cfengine/bin/cf-key -p /var/cfengine/ppkeys/localhost.pub
实测回显:
MD5=818aec2e42923bf496968da90f025810
指纹即 ppkeys 文件名里的 MD5 值,比对指纹可以防「服务器被替换」类中间人风险。查看连接台账(lastseen 数据库):
/var/cfengine/bin/cf-key -s
实测回显:
Direction IP Name Last connection Key
Incoming 127.0.0.1 localhost Fri Oct 16 18:12:00 2026 MD5=818aec2e42923bf496968da90f025810
Outgoing 127.0.0.1 localhost Fri Oct 16 18:12:00 2026 MD5=818aec2e42923bf496968da90f025810
Incoming 172.27.0.2 cfengine-3-27-2-ubuntu220 Sat Oct 17 00:56:15 2026 MD5=948e60c433e68e9dca40288127b1c917
Outgoing 172.27.0.2 cfengine-3-27-2-ubuntu220 Sat Oct 17 00:24:28 2026 MD5=948e60c433e68e9dca40288127b1c917
Total Entries: 4
Incoming/Outgoing 各记一条:客户端每 5 分钟的定时拉取会持续刷新 Last connection 时间——这张表是多机排障的第一现场(客户端连不上时,这里的时间戳会停滞)。
🔒 安全提示:生产环境建议关闭隐式信任:在服务器的
/var/cfengine/masterfiles/def.json加classes.cfengine_internal_preset_keys: ["any"]之外的正规做法是预先分发公钥——先用cf-key -p导出客户端指纹,管理员核对后再入白名单;或至少在扩容完成后,在服务器 def.json 中设置variables.skip_accept相关开关(参见官方文档 hardening 章节)。退而求其次的底线:定期cf-key -s审计接入的指纹清单。
五、策略分发:masterfiles → 各端 inputs
5.1 分发链路
多机策略发布只有一条正道:改服务器的 /var/cfengine/masterfiles,各客户端在下一轮调度(默认 5 分钟)自动拉取到本地 inputs 并执行。改客户端本地文件只会被下一次同步覆盖。
在服务器上放一个测试策略(autorun bundle):
cat > /var/cfengine/masterfiles/services/autorun/client_report.cf <<'EOF'
bundle agent client_report
{
meta:
"tags" slist => { "autorun" };
reports:
"hello from client host $(sys.host) ($(sys.ipv4))";
}
EOF
chmod 600 /var/cfengine/masterfiles/services/autorun/client_report.cf
服务器先验证本地生效(强制同步 + 全量跑一次):
/var/cfengine/bin/cf-agent -KIf /var/cfengine/inputs/update.cf --define validated_updates_ready
/var/cfengine/bin/cf-agent -KI
实测回显:
R: hello from client host 3b46438779b5 (172.27.0.3)
服务器 journal 中可见每轮调度都执行(00:50 与 00:55 两轮实测):
Oct 17 00:50:13 3b46438779b5 cf-agent[1228]: CFEngine(agent) R: custom bundle ran on host 3b46438779b5
Oct 17 00:50:13 3b46438779b5 cf-agent[1228]: CFEngine(agent) R: second autorun bundle is alive
Oct 17 00:50:13 3b46438779b5 cf-agent[1228]: CFEngine(agent) R: hello from client host 3b46438779b5 (172.27.0.3)
Oct 17 00:55:14 3b46438779b5 cf-agent[1515]: CFEngine(agent) R: custom bundle ran on host 3b46438779b5
Oct 17 00:55:14 3b46438779b5 cf-agent[1515]: CFEngine(agent) R: second autorun bundle is alive
Oct 17 00:55:14 3b46438779b5 cf-agent[1515]: CFEngine(agent) R: hello from client host 3b46438779b5 (172.27.0.3)
💡 说明:
R: custom bundle ran与R: second autorun bundle is alive来自服务器 masterfiles 中既有的两个 autorun 演示策略(cfe_second_test.cf、custom.cf 实验产物)——autorun 目录是「放进即生效」的,无需登记。
5.2 客户端接收验证
等客户端下一轮调度(或用第六章 cf-runagent 立即触发)后,在客户端查看:
journalctl -u cf-execd --no-pager | grep -E "R: hello" | tail -3
实测回显:
Oct 17 00:45:40 71baa2e48d8a cf-agent[709]: CFEngine(agent) R: hello from client host 71baa2e48d8a (172.27.0.2)
Oct 17 00:50:39 71baa2e48d8a cf-agent[746]: CFEngine(agent) R: hello from client host 71baa2e48d8a (172.27.0.2)
Oct 17 00:55:40 71baa2e48d8a cf-agent[783]: CFEngine(agent) R: hello from client host 71baa2e48d8a (172.27.0.2)
服务器上发布的 bundle 已在客户端运行,且 $(sys.host)/$(sys.ipv4) 展开为客户端自己的值(71baa2e48d8a / 172.27.0.2)——同一段策略在两端各自收敛,输出携带各自身份。客户端本地核对收到的文件:
ls /var/cfengine/inputs/services/autorun/
实测回显:
client_report.cf
注意:autorun 的启用开关(def.json 的 services_autorun 类)也在 masterfiles 里,随策略一起分发——所以 def.json 的所有改动都只在服务器上做,客户端零维护。
六、cf-runagent:远程触发执行
pull 模型下策略变更要等下一轮调度;cf-runagent 让管理员从一台机器远程触发另一台的立即执行(复用 5308 通道):
/var/cfengine/bin/cf-runagent -H 172.27.0.2
实测回显:
172.27.0.2> cf-serverd executing cfruncommand: /bin/sh -c '
"/var/cfengine/bin/cf-agent" -I -D cf_runagent_initiated -f /var/cfengine/inputs/update.cf ;
"/var/cfengine/bin/cf-agent" -I -D cf_runagent_initiated
172.27.0.2> (exit code: 0)
解读:
- 实际执行者是目标主机自己的 cf-serverd(拉起 cf-agent,
-D cf_runagent_initiated打上触发类标记,策略可用cf_runagent_initiated::守卫区分手动触发与定时触发); - 命令先跑 update.cf 同步策略再跑主策略——所以 cf-runagent 也能用于「发布策略后立即全网生效」;
- 退出码 0;目标系统已收敛时无
R:输出(与 5.1 的策略配对,一旦有新 bundle 输出会直接打印在 cf-runagent 会话里)。
常用参数:-H <host> 指定目标(可多个)、-Dclass 附加触发类、-o 强制(忽略并发锁)。cf-runagent 的鉴权同样走 5308 密钥体系;目标主机 body server control 的 allowusers 限制可远程执行的用户(默认 root)。
七、ACL 排障实录:No suitable server found
多机部署最高频的坑:客户端自举/拉取报 No suitable server found。本节按真实排障过程实录(可复现)。
7.1 现场复现
在服务器 def.json 中把 ACL 收紧到只允许本机网段回环(模拟「客户端网段未放行」):
{
"variables": {
"app_name": "demo-app",
"app_port": "8080",
"acl": [
"127.0.0.1/16"
]
}
}
同步并重启:
/var/cfengine/bin/cf-agent -KIf /var/cfengine/inputs/update.cf --define validated_updates_ready
systemctl restart cfengine3
客户端重新自举:
/var/cfengine/bin/cf-agent --bootstrap 172.27.0.3
实测回显(输出节选,保留关键行):
notice: Bootstrap mode: implicitly trusting server, use --trust-server=no if server trust is already established
error: No suitable server found for '/var/cfengine/inputs'
error: Promise belongs to bundle 'failsafe_cfe_internal_update' in file '/var/cfengine/inputs/failsafe.cf' near line 131
error: Errors encountered when actuating files promise '/var/cfengine/inputs'
error: No suitable server found for '/var/cfengine/modules'
error: Promise belongs to bundle 'failsafe_cfe_internal_update' in file '/var/cfengine/inputs/failsafe.cf' near line 139
error: Errors encountered when actuating files promise '/var/cfengine/modules'
7.2 定位
No suitable server found for '<路径>' 表示 TLS 连接建立前的访问控制拒绝——cf-serverd 的 allowconnects 不含客户端 IP。覆盖链(详见《CFEngine 3.27.2 配置参数详解》第七章):def.json variables.acl → def.cf 默认 $(sys.policy_hub)/16 → 最终 control_server_allowconnects = { "127.0.0.1", "::1", @(def.acl) }。本例服务器(172.27.0.3)的默认 ACL 是 172.27.0.3/16(本应包含客户端),但 def.json 显式覆盖成 127.0.0.1/16 后客户端被拒。
排除网络因素先做两步:客户端 nc -zv 172.27.0.3 5308 或 ss -tlnp | grep 5308 在服务器侧确认端口可达且监听;然后检查服务器 /var/cfengine/inputs/def.json(注意是 inputs 里的生效副本)中 acl 的当前值。
7.3 解决与验证
服务器侧把客户端网段并入 ACL:
{
"variables": {
"acl": [
"127.0.0.1/16",
"172.27.0.0/16"
]
}
}
/var/cfengine/bin/cf-agent -KIf /var/cfengine/inputs/update.cf --define validated_updates_ready
systemctl restart cfengine3
客户端重试自举:
/var/cfengine/bin/cf-agent --bootstrap 172.27.0.3
实测回显:
R: This autonomous node assumes the role of voluntary client
R: Updated local policy from policy server
R: Triggered an initial run of the policy
notice: Bootstrap to '172.27.0.3' completed successfully!
排障口诀:No suitable server found → 一查端口通不通,二查 allowconnects 放不放,改 ACL 永远改服务器 masterfiles/def.json(同步 + 重启才生效)。
八、日志与报告
8.1 本地日志清单(客户端与服务器一致)
| 文件/来源 | 内容 |
|---|---|
/var/cfengine/promise_summary.log | 每轮执行的收敛汇总(kept/repaired/not repaired 百分比) |
/var/cfengine/cfagent.<主机名>.log | agent 运行输出留档 |
/var/cfengine/outputs/ | 每次运行的原始输出文件(cf-execd 捕获,用于邮件正文) |
journalctl -u cf-execd(经 journal) | 全部 R: 输出与错误(日常排查主入口) |
journalctl -u cf-serverd / -u cf-monitord | 分发服务与监控采集日志 |
promise_summary 实测样例:
1792198584,1792198585: Outcome of version update.cf 3.27.2 (agent-0): Promises observed to be kept 100.00%, Promises repaired 0.00%, Promises not repaired 0.00%
1792198585,1792198585: Outcome of version CFEngine Promises.cf 3.27.2 (agent-0): Promises observed to be kept 100.00%, Promises repaired 0.00%, Promises not repaired 0.00%
解读:update.cf(策略同步轮)与 Promises.cf(主策略轮)各一条;not repaired > 0% 是健康度告警信号(有承诺反复失败,常见于策略错误或资源冲突)。outputs 目录实测样例:
cf_71baa2e48d8a__1792198238_Sat_Oct_17_00_50_38_2026_0x73847d9d9b80
cf_71baa2e48d8a__1792198538_Sat_Oct_17_00_55_38_2026_0x73847d9d9b80
previous
每轮一个时间戳文件,与 journal 的 R: 输出一致,previous 是最近一轮的指针副本。
8.2 社区版与企业版的报告能力差异
💡 说明:社区版的报告是「本机文件 + journal」级别;没有集中报表。企业版在 hub(管理节点)上提供 cf-hub 组件与 Web 控制台,通过
cf-hub -H <host> query可远程查询任意节点的 inventory/状态并汇入报表数据库,提供 Web 界面的收敛率、变更历史与告警。社区版要做集中视图,惯常做法是各端把 promise_summary 定期外发(自写脚本收集),或使用开源的 cfengine 报表采集方案。本文只覆盖社区版能力。
九、例行运维清单
日常巡检(服务器)
systemctl is-active cfengine3
ss -tlnp | grep 5308
/var/cfengine/bin/cf-key -s | head -8
实测回显(正常基线):
active
LISTEN 0 128 *:5308 *:* users:(("cf-serverd",pid=5670,fd=5))
Direction IP Name Last connection Key
Incoming 127.0.0.1 localhost Fri Oct 16 18:12:00 2026 MD5=818aec2e42923bf496968da90f025810
Outgoing 127.0.0.1 localhost Fri Oct 16 18:12:00 2026 MD5=818aec2e42923bf496968da90f025810
Incoming 172.27.0.2 cfengine-3-27-2-ubuntu220 Sat Oct 17 00:56:15 2026 MD5=948e60c433e68e9dca40288127b1c917
日常巡检(客户端):systemctl is-active cfengine3 为 active;grep -c "not repaired" /var/cfengine/promise_summary.log 长期为 0;lastseen 时间戳持续刷新。
策略发布 SOP:改服务器 masterfiles → cf-agent -KIf update.cf --define validated_updates_ready 本机同步 → 本机 cf-agent -KI 验证 → 等待全网调度或 cf-runagent -H <host> 逐台立即生效。
备份要点:服务器 /var/cfengine/masterfiles(策略唯一权威源,含 def.json)与两端 /var/cfengine/ppkeys(主机身份,丢失=全部重新信任)优先备份;state/、outputs/ 可不备。
服务器 IP 变更:客户端 --bootstrap <新IP> 重新指向即可(实测:测试环境中服务器与客户端地址互换后,客户端重自举一次恢复分发,journal 输出恢复)。
版本升级:先升服务器、后升客户端(保证低版本客户端解析高版本服务器策略的兼容性窗口);包管理层面的升级/降级操作见《CFEngine 3.27.2 Ubuntu 22.04 安装指南》第九章(CentOS 见同系列对应文档)。
cf-monitord:两端常驻采集系统指标(负载、内存、磁盘等),策略中以 mon.value_loadavg 等变量引用,可用于「负载过高时跳过重活」这类自适应承诺;不需要额外配置,保持进程 active 即可。
十、常见问题(FAQ)
10.1 客户端报 No suitable server found
第七章完整实录。先确认 5308 连通性,再核对服务器 inputs/def.json 的 acl 与客户端网段;改服务器 masterfiles/def.json → 同步 → 重启。
10.2 重建主机后服务器拒绝连接(Key authentication failed)
现象:客户端重装系统/重新生成密钥后,连接服务器被拒,日志见 key authentication 类错误。
定位:服务器 ppkeys 里存着旧公钥(同名 MD5 文件),与客户端新密钥不匹配——旧信任条目失效。
解决:确认客户端身份后,在服务器上删除旧信任条目并重启:
rm /var/cfengine/ppkeys/root-MD5=<客户端旧指纹>.pub
systemctl restart cfengine3
客户端重新 --bootstrap 建立新信任(隐式信任开启时直接成功)。同理,客户端侧不信任服务器时,删除客户端 ppkeys/root-MD5=<服务器指纹>.pub 后重自举。
10.3 cf-runagent 无输出或连接被拒
定位顺序:(1) 目标主机 ss -tlnp | grep 5308 确认 cf-serverd 在听;(2) 服务器/目标机 ACL 是否含发起方网段(同第七章);(3) 目标机 allowusers 是否放行发起用户;(4) 两端密钥是否互信(cf-key -s 看条目)。策略收敛时无输出属正常(回显只有 cfruncommand 与 exit code),用 5.1 的报告类 bundle 验证通路。
10.4 自举时报钟差拒绝
现象:跨机自举偶发失败,服务器日志见 clock skew/denybadclocks 相关拒绝。
定位与解决:cf-serverd 默认 denybadclocks 拒绝时钟偏差过大的连接(TLS 与承诺时效都依赖时钟)。两端先对时(NTP/chrony),确认时区与时间一致后重试自举。
10.5 服务器变更 def.json 后客户端行为没变
定位:改的是服务器 masterfiles,而客户端 inputs 还没到下一轮同步;或服务器本机未同步(inputs/def.json 仍旧值);或改了 ACL/调度类参数却没重启对应服务。
解决:标准三步——服务器强制同步(cf-agent -KIf update.cf --define validated_updates_ready)→ 服务器 systemctl restart cfengine3(ACL/调度类需重启)→ 客户端等待调度或 cf-runagent 立即触发。
十一、参考链接
- CFEngine 官方文档(Deploying policy / cf-runagent):https://docs.cfengine.com/latest/guide-writing-and-serving-policy-policy-distribution.html
- cf-key 与密钥管理:https://docs.cfengine.com/latest/reference-components-cfkey.html
- cf-runagent 参考:https://docs.cfengine.com/latest/reference-components-cfrunagent.html
- 同系列文档:《CFEngine 3.27.2 Ubuntu 22.04 安装指南》《CFEngine 3.27.2 CentOS 8 安装指南》《CFEngine 3.27.2 快速入门与策略编写示例》《CFEngine 3.27.2 配置参数详解》