# 好的 Cookbook 应该:
# 1. 单一职责 - 每个 Cookbook 只做一件事
# 2. 可重用 - 避免硬编码
# 3. 可测试 - 编写单元测试和集成测试
my_cookbook/
├── attributes/ # 属性定义
├── recipes/ # 配方
├── templates/ # 模板文件
├── files/ # 静态文件
├── libraries/ # 库文件
├── resources/ # 自定义资源
├── tests/ # 测试文件
└── metadata.rb # 元数据
# 属性优先级(从低到高)
default['myapp']['port'] = 8080 # 默认值
override['myapp']['port'] = 9090 # 强制覆盖
# 使用命名空间避免冲突
default['company']['app']['setting'] = value
default['company']['database']['host'] = 'localhost'
# 推荐:使用自定义资源
resource_name :my_service
provides :my_service
property :name, String, name_property: true
property :version, String
action :install do
# 资源实现
end
# 不推荐:Definition (已弃用)
# 好的做法
template '/etc/app/config' do
source 'config.erb'
notifies :restart, 'service[app]', :delayed
end
# 避免
service 'app' do
action :restart
end
# 确保资源是幂等的
package 'nginx' do
action :install # 不会重复安装
end
file '/tmp/lock' do
content 'locked'
not_if { ::File.exist?('/tmp/lock') }
end
# environments/production.rb
name 'production'
default_attributes(
'myapp' => {
'env' => 'prod',
'log_level' => 'warn'
}
)
# roles/webserver.rb
name 'webserver'
run_list(
'recipe[nginx]',
'recipe[myapp::web]'
)
# Policyfile.rb
name 'myapp'
run_list 'myapp::default'
default_source :chef_repo, '..'
# 加密 Data Bag
knife data bag create secrets --secret-file /path/to/secret
# 在配方中访问
secret = data_bag_item('secrets', 'db_password', IO.read('/path/to/secret'))
# 更安全的方式
chef_gem 'chef-vault' do
compile_time true
end
require 'chef-vault'
vault_item = ChefVault::Item.load('secrets', 'db_password')
┌─────────────────────┐
│ 验收测试 (InSpec) │ # 验证最终状态
├─────────────────────┤
│ 集成测试 (Kitchen) │ # 端到端测试
├─────────────────────┤
│ 单元测试 (ChefSpec) │ # 快速反馈
└─────────────────────┘
describe 'mycookbook::default' do
let(:chef_run) { ChefSpec::ServerRunner.new.converge(described_recipe) }
it 'installs nginx' do
expect(chef_run).to install_package('nginx')
end
end
# .kitchen.yml
driver:
name: vagrant
provisioner:
name: chef_zero
verifier:
name: inspec
platforms:
- name: ubuntu-20.04
- name: centos-8
suites:
- name: default
run_list:
- recipe[mycookbook::default]
# 查找数据库服务器
db_nodes = search(:node, "role:database AND chef_environment:#{node.chef_environment}")
db_host = db_nodes.first['ipaddress'] if db_nodes.any?
# 1. 避免不必要的搜索
# 2. 使用 lazy 延迟求值
template '/etc/config' do
variables(
lazy {
{ db_host: search_for_db }
}
)
end
# 3. 减少通知链
# 4. 避免在配方中使用 Ruby 代码块做复杂计算
ignore_failure 和 retriesexecute 'download_large_file' do
command 'wget http://example.com/large.zip'
retries 3
retry_delay 5
ignore_failure true # 谨慎使用
end
# metadata.rb 中描述 Cookbook
description 'Installs and configures MyApp web server'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
# 在复杂配方中添加注释
# 配置 Nginx 反向代理
# 注意:需要 SSL 证书已安装
template '/etc/nginx/sites-available/myapp' do
# ...
end
| 类别 | 最佳实践 |
|---|---|
| 设计 | 单一职责、可重用、幂等性 |
| 属性 | 命名空间、合理使用优先级 |
| 安全 | 加密敏感数据、最小权限 |
| 测试 | 单元测试+集成测试+验收测试 |
| 版本 | 语义化版本、版本控制 |
| 部署 | 使用 Policyfile、渐进式发布 |
建议结合 Chef Workstation、Chef Infra Server 和 Automate 构建完整的自动化流水线。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。