Coming from Puppet, the first culture shock in Chef is the multiple configuration methods. Here's how to navigate them:
# Ruby DSL example (recommended for version control)
name "web_server"
description "Apache web server role"
run_list "recipe[apache]", "recipe[our_app]"
default_attributes({
"apache" => {
"listen_ports" => [ "80", "443" ]
}
})
Key considerations:
- Ruby DSL (recommended): Version-control friendly, full programming capabilities
- JSON: Good for API interactions and automation
- Management Console: Quick edits but not ideal for production changes
For your company-specific cookbooks, consider this structure:
chef-repo/
└── cookbooks/
├── ourcompany/
│ ├── custom_app/
│ │ ├── recipes/
│ │ │ └── default.rb
│ │ └── metadata.rb
│ └── shared_libs/
│ └── ...
└── community/
├── apache/
└── mysql/
Best practices:
- Use wrapper cookbooks to customize community cookbooks
- Namespace company-specific cookbooks
- Maintain separate cookbooks for distinct functionalities
Here's how to structure role-cookbook dependencies:
# webserver role
name "webserver"
run_list "recipe[ourcompany::base]", "recipe[apache]", "recipe[our_app]"
# our_app/metadata.rb
depends "apache"
depends "database"
# our_app/recipes/default.rb
include_recipe "ourcompany::security"
apache_site "our_app" do
# configuration
end
For your dev cluster setup, bootstrap with knife:
# Bootstrap a new node with role assignment
knife bootstrap NODE_IP -x ssh_user -N node_name \
--sudo --run-list 'role[webserver]' \
--secret-file /path/to/encrypted_data_bag_secret
# Alternative: Use policyfiles for more controlled environments
name 'web_server'
default_source :supermarket
run_list 'ourcompany::web_setup'
Beyond official docs:
- Test Kitchen for cookbook development (kitchen.yml example below)
- ChefSpec for unit testing
- Foodcritic for linting
# Sample kitchen.yml
driver:
name: vagrant
provisioner:
name: chef_zero
always_update_cookbooks: true
platforms:
- name: ubuntu-20.04
suites:
- name: default
run_list:
- recipe[ourcompany::web_setup]
attributes:
apache:
port: 8080
Remember: Chef's flexibility is both its strength and learning curve. Start with simple patterns and gradually adopt more advanced features as you become comfortable.
When moving from Puppet to Chef, the first conceptual hurdle is understanding why Chef offers multiple ways to define configurations:
# Ruby DSL example (roles/base.rb)
name "base"
description "Base role for all nodes"
run_list "recipe[ntp]", "recipe[security]"
default_attributes(
"ntp" => {
"servers" => ["time.nist.gov"]
}
)
Versus JSON format:
{
"name": "base",
"description": "Base role for all nodes",
"run_list": [
"recipe[ntp]",
"recipe[security]"
],
"default_attributes": {
"ntp": {
"servers": ["time.nist.gov"]
}
}
}
The Ruby DSL is generally preferred because:
- It supports version control better with diff capabilities
- Allows for dynamic logic in role definitions
- Is more maintainable for complex configurations
For organizing cookbooks into subdirectories, Chef supports this through the cookbook_path
setting in knife.rb:
cookbook_path [
"#{current_dir}/cookbooks",
"#{current_dir}/vendor-cookbooks",
"#{current_dir}/cookbooks/ourcompanystuff"
]
Best practice is to create atomic cookbooks that do one thing well (like the Apache cookbook) and then create wrapper cookbooks that compose these together:
# webserver cookbook metadata.rb
depends 'apache2'
depends 'php'
depends 'ourcustomapp'
Chef provides several approaches for node classification:
- Roles: Static classification of nodes
- Environments: Stage-specific configurations
- Policyfiles: Newer approach that bundles dependencies
For automatic node classification similar to Puppet's ENC, use Chef's chef-client
bootstrap with JSON attributes:
knife bootstrap NODE_IP -x USER -P PASSWORD --json-attributes '{"run_list": ["role[webserver]"]}'
A complete provisioning workflow for development environments:
# 1. Bootstrap the node
knife bootstrap NODE_IP -x USER -P PASSWORD -N node-name --sudo
# 2. Assign roles (either method works)
knife node run_list add node-name "role[webserver]"
# 3. Trigger initial configuration
knife ssh 'name:node-name' 'sudo chef-client' -x USER -P PASSWORD
After completing the basics, focus on:
- Test Kitchen for cookbook development
- ChefSpec for unit testing
- ServerSpec for integration testing
- Policyfiles for dependency management
Example Test Kitchen configuration (.kitchen.yml
):
---
driver:
name: vagrant
provisioner:
name: chef_zero
always_update_cookbooks: true
platforms:
- name: ubuntu-20.04
suites:
- name: default
run_list:
- recipe[ourcustomapp::default]
attributes: