CFEngine 3.27.2 进阶实战与运维

CFEngine 3.27.2 进阶实战与运维:policy server 与客户端多机部署实测、策略分发链路、cf-key 密钥体系、cf-runagent 远程执行、ACL 排障实录、日志报告与例行运维清单

CFEngineCFEngineLinux自动化配置管理2026/10/17

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 servercf-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,IP 172.27.0.3
  • 客户端:主机名 71baa2e48d8a,IP 172.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)做身份认证。首次连接的隐式信任链:

  1. 客户端首次连服务器:服务器收到客户端公钥,存为 /var/cfengine/ppkeys/root-MD5=<客户端公钥摘要>.pub;
  2. 客户端侧: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.<主机名>.logagent 运行输出留档
/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 立即触发。

十一、参考链接