大概在2015年的时候,我还在搞运维开发,当时选择了 Puppet(ver.3) 作为自动化运维框架,并在随后的一年里写了不少模块,甚至还给 Puppet 提过两个有效 Pull Request。后来我发现 Puppet 是那种比较典型的经不起细看的技术,乍一看优点很多,OS 支持全面、DSL 语法简单、易于扩展、第三方模块多、还特么提供了 HTTP API,但深入研究之后就会发现浑身都是洞。

事隔四年,我又因为工作需要重新看了一下最新的 Puppet 6,发现这个项目的混乱程度有增无减,遂吐槽一篇。

DSL 让事情变得更复杂

Puppet 的团队大概是为了照顾不懂开发的运维人员,所以用 Ruby 造了一个 DSL(即所谓 Puppet Language),咋一看语法挺简单,门槛很低,极易上手,其实碰到稍微复杂一点的需求就捉襟见肘。为了弥补这一点,Puppet 又提供了 Custom FunctionsCustom TypeCustom Provider 三种扩展,使用 Ruby 编写,用于增强 Puppet 语法的表达力,但很多都是在重复实现那些通过 Ruby 本身可以更容易做到的事情。即便没用过 Puppet,看一下它的 标准库(stdlib) 就能明白这一点,整个模式大概就是——用 Ruby 实现一个 DSL,然后再用这个 DSL 反过来实现 Ruby 的一些语法糖。

更显而易见的是,如果用户已经能运用 Ruby 来扩展,何苦要重新学习一个语法并不简洁统一的语言呢?

现如今 Puppet 语言还加了很多时髦的特性,比如支持所谓「静态类型」等,Custom Function 都能用 Puppet 语言直接写,但是这反倒让我觉得坑挖得更深了。

如下是官方提供的使用 Puppet 语言编写 Custom Funcion 的示例。

function apache::bool2http(Variant[String, Boolean] $arg) >> String {
  case $arg {
    false, undef, /(?i:false)/ : { 'Off' }
    true, /(?i:true)/          : { 'On' }
    default                    : { "$arg" }
  }
}

拉取模式带来很多问题

Puppet 的核心是拉取模式,也就是客户端定时发送本机信息到服务端,获取配置,然后在本地应用的模式。这个模式,最大的特点在于「状态定义」,也就是你在服务端是在给所有机器定义一种目标状态。很多其他自动化运维工具,更多地是在做「行为定义」,也就是定义针对客户端做什么操作。

但是这种客户端定期拉取的模式,很难满足如今大部分运维场景的需求,客户端是否每次都准确应用了我定义的状态,有没有出现异常,没有一个直接有效的方式。有时我们会通过 Custom Fact 收集客户端的信息来判断,但是这种反馈又会滞后。

当然 Puppet 还提供了 MCollective 、Bolt 两种推送模式的工具,但始终不是它的强项。

技术栈混乱

Puppet 原本是一个 Ruby 项目,照说技术栈几乎都是 Ruby 系的,但一开始 PuppetDB 就用的 Clojure(运行在 JVM 上的 LISP),这就比较怪了。

Facter 模块本来也是用 Ruby 实现的,后来觉得性能有问题,Facter 3 用 C++ 重写了,废掉了不少 Ruby API,如今似乎又觉得 C++ 让贡献代码的门槛变得过高,又想重新用回 Ruby,开了个项目叫 facter-ng(Next Generation,开源世界的命名滥觞了,动辄NG),不知道什么时候又会切过去。

Puppet Server 和 Puppet Agent 以前都是运行在 Ruby 默认的运行时 MRI 上的,如今为了性能,Server 用又特么用 Clojure 重写了,Ruby 的运行时也自然换到了 JVM ,但 Client 端还是 MRI,于是用户在编写代码的时候,还要小小地担心一下运行时不同的问题。

从这几件事上能看出 Puppet 的技术团队总是想通过更换语言来解决性能问题,我猜他们要么经常更换 CTO,要么 CTO 是个愤青。


当年我在每天和 Puppet 打交道时,踩了很多坑,但现在只想到了上面这三点,有空再吐。

那么既然 Puppet 这么破,自动化运维框架哪家强呢?Ansible?Saltstack?Chef?我的建议是,不要搞运维😂。