Chef运维自动化入门:基础设施即代码实战指南 1. Chef 是什么一个运维老手眼里的“服务器厨师长”你有没有试过给十台服务器装同样的软件、配同样的防火墙规则、开同样的服务端口还要确保每台机器的时钟同步、用户权限一致、日志路径统一我刚入行那会儿靠手动 SSH 连一台一台敲命令配完五台第二天发现第三台被人改过 hosts 文件第四台的 cron 里多了一条可疑脚本——不是忘了是真记混了。后来团队扩到三十台光是部署一个新版本的 Java 应用光是检查 JDK 版本、JVM 参数、应用配置文件路径、启动脚本权限这四件事就要花掉整整一个下午还经常漏掉某台测试机上的 SELinux 状态没关结果服务起不来排查两小时才发现是策略拦截。Chef 就是为解决这种“人肉运维熵增”而生的。它不是个命令行工具也不是个图形界面软件而是一套以代码定义基础设施Infrastructure as Code, IaC的完整体系。你可以把它理解成一位极其较真的“服务器厨师长”你不用亲自切菜、炒菜、摆盘但必须把每道菜的原料清单what、处理步骤how、火候时间when、装盘标准validation全部写成清晰可读的“菜谱”Cookbook然后交给 Chef 去执行。这个“菜谱”不是自然语言写的而是用 Ruby 语法编写的 DSL领域专用语言它足够贴近运维人员的思维习惯又足够严谨能被机器精确解析和执行。很多人一看到“Ruby”就皱眉觉得门槛高。其实大可不必。Chef 的核心价值从来不在编程语言本身而在于它强制你把“怎么做运维”这件事从模糊的经验、零散的笔记、口头的交接变成一份可版本控制、可重复验证、可自动回滚、可多人协作的代码资产。它不关心你用的是 Ubuntu 还是 CentOS是物理机还是 AWS EC2 实例是 Docker 容器还是 Kubernetes Pod——只要目标节点上装了 Chef Client它就能按你的菜谱把环境调成你想要的样子。这背后解决的是 DevOps 最底层的痛点环境一致性。开发说“在我本地跑得好好的”测试说“在测试环境报错”运维说“生产环境根本起不来”——八成问题出在三套环境的细微差异上。Chef 就是那个拿着同一份菜谱在三口锅里做出完全一样味道的菜的人。关键词里提到“Artificial Intelligence”这里需要明确一点Chef 本身不是 AI 工具它不学习、不推理、不生成代码。它是一个确定性的、声明式的自动化引擎。它的“智能”体现在设计哲学上——把人的经验固化为可执行的逻辑让机器去承担重复、易错、耗时的体力活从而把运维工程师从“救火队员”解放成“架构师”和“质量守门员”。如果你正在看这篇文章大概率你已经经历过因环境不一致导致的线上故障或者正被频繁的重复部署压得喘不过气。那么 Chef 不是你技术栈里的“加分项”而是你走向稳定、高效、可扩展运维的必经之路。它适合所有需要管理 5 台以上 Linux/Unix 服务器的团队无论你是初创公司一人身兼数职还是大型企业拥有数百名运维工程师。关键不在于规模而在于你是否还愿意接受“这次部署成功了但下次不一定”的不确定性。2. Chef 的核心设计思路与为什么选它而非其他2.1 整体架构客户端-服务器模式的“中央厨房分店执行”Chef 的经典部署模式是Chef Server Chef ClientNode这就像一个中央厨房Chef Server向遍布各地的连锁分店Chef Client下发标准化操作指令。这个设计不是拍脑袋想出来的而是源于对大规模、异构环境管理的深刻洞察。Chef Server是整个体系的“大脑”和“心脏”。它不直接执行任何操作而是扮演三个关键角色配置仓库存储所有 Cookbooks、Roles、Environments、Data Bags、策略中心定义不同环境如 dev/staging/prod 的差异化配置、状态协调者记录每个 Node 的当前状态、最后更新时间、运行报告。它本质上是一个高度定制化的 REST API 服务所有交互都通过 HTTPS 进行这意味着它可以轻松集成到 CI/CD 流水线中——比如 Jenkins 在构建完应用包后直接调用 Chef Server API 触发一次部署。Chef Client是部署在每一台受管服务器上的轻量级代理。它定期默认 30 分钟主动连接 Chef Server拉取最新的策略和 Cookbooks然后在本地执行。这个“Pull-Based”拉取式模型是 Chef 的标志性设计也是它区别于 AnsiblePush-Based的核心。为什么选“拉”不选“推”实操下来有三大硬理由网络穿透友好Client 主动连 Server意味着 Server 只需开放一个 HTTPS 端口443Client 所在的服务器可以位于任何 NAT 后、防火墙后甚至私有云内网无需在 Server 端为每台 Client 开放 SSH 端口或配置密钥。这对混合云、多云架构简直是刚需。状态收敛可靠Client 每次执行都是一个完整的“收敛循环”Convergence Cycle。它会先检查系统当前状态比如某个软件包是否已安装、某个文件内容是否匹配再决定是否执行动作。即使某次执行失败或中断下一次拉取时它会自动续上确保最终状态一定符合预期。这比“推”模式下一次性的、不可逆的命令执行要健壮得多。资源消耗可控Client 进程非常轻量内存占用通常不到 50MBCPU 占用几乎为零只在执行周期内短暂活跃。而“推”模式的工具如早期的 Puppet Agent在 Server 端需要维护大量并发连接对 Server 资源压力更大。提示对于极小规模5 台或临时性任务Chef Solo无 Server 模式也完全可用它直接在本地运行 Cookbooks适合快速验证或 CI 环境中的单次构建。2.2 核心概念解耦为什么“菜谱”、“食材”、“菜单”要分开新手最容易混淆的是 Chef 的几个核心对象Cookbook、Recipe、Resource、Attribute、Role、Environment。它们不是随意堆砌的术语而是对运维工作流的精准抽象每一层都解决一个特定问题。Resource资源是最小的、不可再分的“原子操作单元”。它代表“系统上的一件东西”及其“期望状态”。比如package nginx表示“Nginx 这个软件包”service nginx表示“Nginx 这个服务”file /etc/nginx/nginx.conf表示“Nginx 的配置文件”。每个 Resource 都有action动作如:install,:start,:create和properties属性如version 1.20.1,content ...。Resource 的设计哲学是声明式Declarative你告诉 Chef “我要什么状态”而不是“怎么达到这个状态”。Chef 会自动判断当前状态与目标状态的差异并选择最合适的底层命令apt-get install或yum install去实现。这保证了跨平台兼容性。Recipe食谱是一组有序的 Resources 的集合用来完成一个具体的、可交付的任务。比如nginx::installRecipe 负责安装 Nginxnginx::configRecipe 负责写入配置文件并启动服务。Recipe 的本质是流程编排。它不包含业务逻辑只负责串联 Resources。一个 Recipe 通常只做一件事并且做到底这是 Chef 社区推崇的“单一职责原则”。Cookbook菜谱集是 Recipe 的容器也是版本管理的基本单位。一个 Cookbook 通常围绕一个软件或一个服务组织比如nginxCookbook 包含install,config,default等多个 Recipe还包含该服务所需的模板文件templates/、静态文件files/、自定义 Resourcelibraries/以及元数据metadata.rb。Cookbook 是 Chef 生态的“货币”你可以从官方 Supermarket 下载、复用、修改也可以把自己的 Cookbook 发布出去。它让知识沉淀和共享变得像 Git 提交代码一样简单。Attribute属性是 Cookbook 的“可配置参数”。它把 Recipe 中硬编码的值如port 8080抽离出来变成可外部覆盖的变量。Attributes 有严格的优先级层级从低到高defaultnormaloverrideautomatic。automatic是 Chef 自动探测的如 CPU 核心数、内存大小default是 Cookbook 内置的默认值override则可以在 Role 或 Environment 中全局覆盖。这种设计让同一个 Cookbook 能在开发、测试、生产环境中无缝切换只需修改几行 Attribute无需碰 Recipe 代码。Role角色和Environment环境是更高维度的“策略封装”。Role 定义了一组服务器“应该是什么样子”比如webserverRole 可能包含nginx::default,php::fpm,logrotate::default这几个 Recipe并设置nginx.port 80。Environment 则定义了“在什么场景下运行”比如productionEnvironment 会设置node[app][debug] false而developmentEnvironment 则设为true。它们共同构成了 Chef 的“策略即代码”Policy as Code能力让运维决策如“所有生产 Web 服务器必须启用 SSL”变成了可审计、可版本化的代码。这套设计之所以强大是因为它完美映射了现实世界的运维复杂性你不可能用一个脚本搞定所有事也不可能把所有配置都写死。Chef 用分层、解耦、可组合的方式把混沌的运维工作变成了像搭乐高积木一样清晰、可控、可复用的过程。3. 核心实操从零开始部署一个 Nginx Web 服务器3.1 环境准备搭建你的第一个 Chef Server使用 Chef Automate别被“Server”吓到现在部署 Chef Server 已经非常简单。我们推荐使用官方的Chef Automate它是一个一体化的商业版有免费社区版集成了 Chef Server、InSpec合规扫描、Habitat应用打包和可视化 Dashboard。对于学习和中小团队它提供了开箱即用的体验。第一步在一台干净的 Ubuntu 22.04 服务器上安装 Chef Automate# 下载最新版 Chef Automate CLI curl https://packages.chef.io/files/current/automate/latest/chef-automate_linux_amd64.zip | gunzip -c chef-automate chmod x chef-automate # 初始化配置会生成一个 automate-config.toml sudo ./chef-automate init-config # 编辑配置最关键的两行是 # [global] # fqdn your-server-domain.com # 必须是可解析的域名或公网IP # [automate] # admin_password YourStrongPass123! # 设置管理员密码 # 启动整个过程约 5-10 分钟会自动下载、配置、启动所有服务 sudo ./chef-automate deploy --airgap-bundle chef-automate-chef-server-a2-ha-bundle-2023.12.127.tgz注意--airgap-bundle参数用于离线环境如果你的服务器能联网直接用sudo ./chef-automate deploy即可。部署完成后访问https://your-fqdn用你设置的密码登录就能看到漂亮的 Dashboard。第二步在目标服务器Node上注册 Chef Client假设你有一台待部署的 Ubuntu 22.04 服务器IP 为192.168.1.100。# 在目标服务器上下载并安装 Chef Infra Client curl https://packages.chef.io/files/stable/chef/18.9.18/ubuntu/22.04/chef_18.9.18-1_amd64.deb | sudo dpkg -i # 创建 Chef Client 的配置文件 /etc/chef/client.rb echo chef_server_url https://your-server-domain.com/organizations/myorg | sudo tee /etc/chef/client.rb echo validation_client_name myorg-validator | sudo tee -a /etc/chef/client.rb # 从 Chef Automate 的 Web UI 中进入 Settings - Users Teams - Create User创建一个新用户如 devops-user并下载其 .pem 私钥文件如 devops-user.pem # 将这个 .pem 文件安全地复制到目标服务器的 /etc/chef/ 目录下 sudo cp devops-user.pem /etc/chef/validation.pem # 第一次运行向 Chef Server 注册自己 sudo chef-client -r recipe[nginx::default]这个chef-client命令会做三件事1) 用validation.pem向 Server 证明身份2) Server 为其创建一个唯一的 Node 对象3) Server 返回nginx::defaultRecipe 并执行。如果一切顺利你会看到终端输出Converging 10 resources最后以Chef Infra Client finished, 10/10 resources updated结束。此时你的 Nginx 服务器就已经部署好了。3.2 编写你的第一个 Cookbooknginx现在我们来亲手写一个最简化的nginxCookbook。这不是为了炫技而是为了彻底理解 Chef 的工作流。创建 Cookbook 结构# 在你的开发机非 Server上安装 Chef Workstation包含所有开发工具 # 下载地址https://www.chef.io/downloads/tools/workstation # 初始化一个新 Cookbook chef generate cookbook nginx cd nginx # 查看自动生成的目录结构 tree -L 2 # . # ├── Berksfile # 依赖管理类似 Gemfile # ├── README.md # ├── attributes # 存放所有 Attribute 文件 # │ └── default.rb # 默认属性 # ├── files # 存放二进制文件如预编译的二进制包 # ├── libraries # 存放自定义 Ruby 类库 # ├── metadata.rb # Cookbook 元数据名称、版本、依赖 # ├── recipes # 存放所有 Recipe # │ └── default.rb # 默认执行的 Recipe # ├── resources # 存放自定义 Resource # └── templates # 存放 ERB 模板文件用于动态生成配置编写核心 Reciperecipes/default.rb# recipes/default.rb # 这个 Recipe 的目标安装 Nginx启动服务确保开机自启 # 1. 安装 Nginx 包 package nginx do action :install end # 2. 确保 Nginx 服务已启用并正在运行 service nginx do action [:enable, :start] end # 3. 创建一个简单的欢迎页面用 file Resource file /var/www/html/index.html do content h1Welcome to Chef! This server is managed by Infrastructure as Code./h1 mode 0644 owner root group root end编写默认属性attributes/default.rb# attributes/default.rb # 定义可配置的参数这里我们只定义一个端口 default[nginx][port] 80编写模板化配置进阶templates/default/nginx.conf.erb# templates/default/nginx.conf.erb # 这是一个 ERB 模板Chef 会将其渲染成真实的 nginx.conf user www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; # 动态插入端口 server { listen % node[nginx][port] %; server_name localhost; location / { root /var/www/html; index index.html; } } }修改 Recipe 以使用模板recipes/default.rb更新版# recipes/default.rb (更新) package nginx do action :install end # 使用 template Resource 渲染配置文件 template /etc/nginx/nginx.conf do source nginx.conf.erb # 指向 templates/default/ 下的文件 mode 0644 owner root group root variables( port: node[nginx][port] # 将 Attribute 传入模板 ) end service nginx do action [:enable, :start] # 添加一个通知当配置文件改变时自动重载服务 notifies :reload, service[nginx], :immediately end file /var/www/html/index.html do content h1Welcome to Chef! This server is managed by Infrastructure as Code./h1 mode 0644 owner root group root end上传 Cookbook 到 Chef Server# 在 Cookbook 目录下使用 BerkshelfChef 的依赖管理器上传 berks upload --no-ssl-verify # --no-ssl-verify 仅用于自签名证书的测试环境 # 如果提示未找到 Berksfile先运行 berks init在 Chef Server 上将 Cookbook 分配给你的 Node登录 Chef Automate Web UI。进入 Infrastructure - Nodes找到你的192.168.1.100节点。点击节点名进入详情页点击 Edit Run List。在 Run List 中添加recipe[nginx::default]保存。触发一次新的 Convergence回到你的目标服务器手动运行sudo chef-client你会看到 Chef Client 拉取最新的 Cookbook然后执行default.rb。它会检测到/etc/nginx/nginx.conf文件内容与模板渲染结果不一致于是更新该文件并立即nginx -s reload重载配置。整个过程全自动、可预测、可审计。3.3 关键参数与配置详解为什么这些数字如此重要Chef 的强大往往藏在那些看似不起眼的配置参数里。理解它们是避免“部署成功但线上不稳”的关键。convergence_interval收敛间隔默认是 1800 秒30 分钟。这个值不是越小越好。太小如 30 秒会导致 Client 频繁轮询 Server增加网络和 Server 负载太大如 2 小时则会让配置变更生效延迟太久。我们的经验是生产环境用 1800CI/CD 流水线中的临时环境用 3005分钟开发测试机可以用 601分钟。这个值在/etc/chef/client.rb中配置# /etc/chef/client.rb interval 1800splay随机偏移这是一个天才的设计。当你的集群有 100 台服务器如果它们都在整点 00:00 同时向 Server 发起请求Server 很可能瞬间被打垮。splay参数会在interval基础上为每台 Client 随机增加一个 0 到splay秒的偏移量。例如splay 300意味着每台 Client 的实际轮询间隔在 1800-2100 秒之间随机分布。这极大地平滑了 Server 的负载曲线。强烈建议在所有生产环境的client.rb中加入splay 300log_level日志级别默认是:warn只记录警告和错误。但在调试阶段你需要:info甚至:debug。然而debug日志会记录每一个 Resource 的详细状态对比日志量巨大会迅速占满磁盘。我们的实操心得是日常运行用:warn部署新 Cookbook 时临时改为:info定位疑难杂症时才开:debug并且务必配合log_location将日志写入独立文件避免污染系统日志。log_level :info log_location /var/log/chef/client.logohai_hintOhai 探测提示Ohai 是 Chef 的硬件/系统信息探测器它在每次 Convergence 前运行收集 CPU、内存、网络、文件系统等信息并作为node对象的automatic属性提供给 Recipe 使用。有时 Ohai 无法正确探测某些特殊硬件如某些 NVMe SSD 的型号这时你可以通过ohai_hint文件手动“提示”它。例如在/etc/chef/ohai_hints/目录下创建ssd.json{ssd: true}然后在 Recipe 中就可以用node[ssd]来判断是否为 SSD并据此选择不同的 I/O 调度策略。这体现了 Chef 的灵活性——它不强迫你接受它的探测结果而是给你留出了干预的入口。4. 常见问题与实战排查技巧4.1 “Convergence Failed”最常见的失败场景与速查表Chef 的错误信息通常很直白但新手常被冗长的堆栈跟踪吓住。其实90% 的失败都集中在以下五个环节。我整理了一份“Convergence 失败速查表”每次遇到问题按顺序检查基本都能快速定位。问题现象最可能原因排查命令/步骤解决方案ERROR: Connection refused - connect(2) for chef-server.com port 443Chef Client 无法连接 Chef Serverping chef-server.comtelnet chef-server.com 443curl -I https://chef-server.com检查 Client 网络、DNS、防火墙确认 Server 的 HTTPS 服务是否正常运行检查/etc/chef/client.rb中的chef_server_url是否正确注意末尾的/organizations/myorgERROR: Cookbook nginx not foundCookbook 未上传或名称拼写错误在 Chef Server UI 中进入 Infrastructure - Cookbooks搜索nginxberks list(在 Cookbook 目录下)运行berks upload上传检查metadata.rb中的name nginx是否与recipes/default.rb中引用的nginx::default一致确认上传时使用的组织organization是否正确ERROR: Expected process to exit with 0, but received 1---- Begin output of apt-get -q -y install nginx ----Package Resource 安装失败sudo apt-get updatesudo apt-get install nginx(手动执行看具体报错)更新 APT 缓存在 Recipe 中加execute apt-get update检查包名是否正确Ubuntu 是nginxCentOS 是nginx或epel-release考虑使用packageResource 的options属性指定额外参数ERROR: File /etc/nginx/nginx.conf not foundTemplate Resource 渲染失败ls -la /var/chef/cache/cookbooks/nginx/templates/cat /var/chef/cache/cookbooks/nginx/templates/default/nginx.conf.erb确认模板文件路径是否正确templates/default/检查模板文件名是否与source属性完全一致包括大小写确认templateResource 的source属性值是nginx.conf.erb而不是templates/default/nginx.conf.erbERROR: service[nginx] (nginx::default line 25) had an error: Mixlib::ShellOut::ShellCommandFailed: Expected process to exit with 0, but received 1Service 启动失败sudo nginx -t(检查配置语法)sudo journalctl -u nginx -n 50 --no-pager(查看最近 50 行日志)nginx -t会告诉你哪一行配置错了journalctl会显示更详细的启动失败原因如端口被占用、用户权限不足在serviceResource 中添加supports status: true, restart: true并notifies :restart, service[nginx]提示sudo chef-client -l debug是终极调试命令它会输出所有 Resource 的详细状态对比比如file[/etc/nginx/nginx.conf]会告诉你“当前内容长度 1234 字节期望内容长度 1235 字节”让你一眼看出差异所在。4.2 “环境漂移”如何发现并修复被手动修改的服务器这是 Chef 最常被质疑的一点“如果有人偷偷登录服务器手动改了配置Chef 会发现吗”答案是会而且能自动修复但前提是你知道怎么看。Chef 的核心机制是“收敛”它每次运行都会检查系统当前状态Current State与 Recipe 中声明的期望状态Desired State是否一致。如果不一致它就会执行动作去修正。所以手动修改恰恰是 Chef 最擅长处理的场景。如何主动发现“漂移”利用 Chef Automate 的 Compliance 扫描Automate 内置了 InSpec 扫描引擎。你可以创建一个 Profile定义“Nginx 配置文件必须包含listen 80;”这样的规则然后让 Automate 定期扫描所有 Node。一旦发现不合规Dashboard 上会立刻亮起红灯并生成详细的修复报告。查看 Chef Client 的运行报告每次chef-client运行结束后它会向 Chef Server 发送一份 JSON 格式的 Report。在 Automate UI 中进入 Infrastructure - Nodes - 选择节点 - Reports你可以看到每一次运行的详细摘要其中resources_updated字段就是本次修复了多少处不一致。如果这个数字长期为 0说明环境非常健康如果某次突然飙升到 10那很可能有人动了手脚。在 Recipe 中加入显式校验这是最主动的方式。利用executeResource 执行 Shell 命令并用returns属性定义期望的退出码。# 在 recipes/default.rb 中加入 execute validate_nginx_config do command nginx -t returns [0] # 期望 nginx -t 返回 0成功 notifies :reload, service[nginx], :immediately end这样如果有人改坏了nginx.confnginx -t会失败Chef Client 就会报错并停止后续执行迫使你去修复配置而不是让一个错误的配置上线。我的实操心得不要把 Chef 当成一个“防君子不防小人”的工具。相反要把它当成一个“永不疲倦的质检员”。我们团队的 SLO服务等级目标之一就是“所有生产服务器的 Chef Convergence 报告中resources_updated的 95 分位数必须小于 2”。这个指标比任何人工巡检都可靠。记住自动化不是取代人而是把人从重复劳动中解放出来去做更有价值的事——比如设计更健壮的架构或者写更好的测试。4.3 性能瓶颈当 Chef Client 变慢怎么办在管理数百台服务器时你可能会发现chef-client的执行时间越来越长从 30 秒涨到 2 分钟。这不是 Chef 的缺陷而是你踩到了几个典型的性能陷阱。陷阱一过度使用executeResource 执行 Shell 命令execute apt-get update看似简单但它会阻塞整个 Convergence 流程且无法被 Chef 的幂等性保障。如果apt-get update因网络问题卡住整个 Chef Client 就会 hang 死。解决方案用apt_updateResource 替代。它是 Chef Infra Client 内置的、专为 APT 设计的 Resource支持超时、重试、幂等性并且会智能地只在必要时才运行apt-get update。apt_update update_apt_cache do frequency 86400 # 每 24 小时更新一次缓存 end陷阱二在 Recipe 中进行复杂的 Ruby 计算或网络请求Chef Client 是单线程的。如果你在recipes/default.rb中写了require net/http; Net::HTTP.get(URI.parse(http://api.example.com))那么所有后续的 Resources 都要等这个 HTTP 请求完成。解决方案把这类耗时操作移到libraries/目录下的 Ruby 文件中并用lazy块包裹。lazy会延迟计算直到真正需要该值时才执行。# libraries/helpers.rb module MyHelpers def get_api_data # 这里放你的网络请求逻辑 JSON.parse(Net::HTTP.get(URI.parse(http://api.example.com))) end end # 在 recipes/default.rb 中 node.default[myapp][config] lazy { get_api_data }陷阱三Cookbook 依赖过多且未使用 Berkshelf 锁定版本Berksfile中如果写cookbook nginx, ~ 10.0每次berks install都会拉取最新的10.x版本。如果上游 Cookbook 作者发布了不兼容的更新你的部署就可能失败或变慢。解决方案运行berks lock生成Berksfile.lock并将它和Berksfile一起提交到 Git。这样无论谁在何时何地berks install得到的都是完全相同的 Cookbook 版本组合保证了构建的可重现性。注意Chef 的性能优化核心思想是“让 Chef 做它最擅长的事——声明状态”而把不适合它做的事如复杂计算、网络 IO交给更合适的工具如外部 API、CI/CD 流水线中的前置步骤。5. 进阶实践Chef 与现代 DevOps 流水线的深度整合5.1 在 CI/CD 中自动化 Cookbook 测试与发布一个未经测试的 Cookbook就像一把没试过枪的子弹危险系数极高。Chef 社区早已形成了一套成熟的测试金字塔Unit TestChefSpec- Integration TestTest Kitchen- Compliance TestInSpec。跳过任何一层都可能在线上引发灾难。ChefSpec单元测试快如闪电ChefSpec 是基于 RSpec 的单元测试框架它不启动真实服务器只模拟 Chef Client 的执行过程。它能在毫秒级内验证一个 Recipe 是否会创建正确的 Resources是否设置了正确的属性。# spec/unit/recipes/default_spec.rb require spec_helper describe nginx::default do # 指定测试平台和 Chef 版本 platform ubuntu, 22.04 let(:chef_run) { ChefSpec::SoloRunner.new(platform: ubuntu, version: 22.04).converge(described_recipe) } # 测试是否安装了 nginx 包 it installs nginx package do expect(chef_run).to install_package(nginx) end # 测试是否创建了 index.html 文件 it creates index.html file do expect(chef_run).to create_file(/var/www/html/index.html) end # 测试是否启动了 nginx 服务 it starts and enables nginx service do expect(chef_run).to enable_service(nginx) expect(chef_run).to start_service(nginx) end end运行rspec它会瞬间告诉你所有测试是否通过。这是 CI 流水线的第一道防线必须 100% 通过才能进入下一步。Test Kitchen集成测试在真实虚拟机中运行ChefSpec 再快也无法替代在真实环境中运行。Test Kitchen 就是为此而生。它能自动创建虚拟机Vagrant、Docker 容器甚至云实例AWS EC2然后在上面部署你的 Cookbook并运行一系列验证脚本。# .kitchen.yml --- driver: name: docker use_sudo: false provisioner: name: chef_zero product_name: chef product_version: 18.9.18 platforms: - name: ubuntu-22.04 suites: - name: default run_list: - recipe[nginx::default] verifier: inspec_tests: - test/integration/defaulttest/integration/default/controls/example.rb中你可以用 InSpec 语法写真正的系统级断言# test/integration/default/controls/example.rb describe package(nginx) do it { should be_installed } end describe service(nginx) do it { should be_enabled } it { should be_running } end describe port(80) do it { should be_listening } end在 CI 中kitchen test命令会自动完成创建容器 - 安装 Chef - 上传 Cookbook - 运行 Convergence - 运行 InSpec 测试 - 销毁容器。整个过程