Top Banner
What Makes a Good Cookbook? Julian C. Dunn Senior Consulting Engineer Engineering Team Lead Chef Software, Inc. <[email protected] >
47

What Makes a Good Chef Cookbook? (May 2014 Edition)

Jan 27, 2015

Download

Technology

Julian Dunn

What makes a good or bad Chef cookbook?
Presented at the Boston Chef meetup on May 22, 2014.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: What Makes a Good Chef Cookbook? (May 2014 Edition)

What Makes a Good Cookbook?Julian C. DunnSenior Consulting Engineer Engineering Team LeadChef Software, Inc.<[email protected]>

Page 2: What Makes a Good Chef Cookbook? (May 2014 Edition)

$ whoami•Consulting Engineer Engineering Team Lead at Chef

•System Administrator

•Reformed Java Developer

•Writes silly code like this

•https://github.com/juliandunn/doge-chef-formatter

Page 3: What Makes a Good Chef Cookbook? (May 2014 Edition)

Finding a Good CookbookLMGTCFY

Page 4: What Makes a Good Chef Cookbook? (May 2014 Edition)
Page 5: What Makes a Good Chef Cookbook? (May 2014 Edition)

Do judge a cookbook by its cover

Page 6: What Makes a Good Chef Cookbook? (May 2014 Edition)
Page 7: What Makes a Good Chef Cookbook? (May 2014 Edition)
Page 8: What Makes a Good Chef Cookbook? (May 2014 Edition)
Page 9: What Makes a Good Chef Cookbook? (May 2014 Edition)
Page 10: What Makes a Good Chef Cookbook? (May 2014 Edition)

missing_attrs = %w{ postgres }.select do |attr| node['postgresql']['password'][attr].nil? end.map { |attr| "node['postgresql']['password']['#{attr}']" }

if !missing_attrs.empty? Chef::Application.fatal!([ "You must set #{missing_attrs.join(', ')} in chef-solo mode.", "For more information, see https://github.com/opscode-cookbooks/postgresql#chef-solo-note" ].join(' '))end

Too Clever for Its Own Good

Page 11: What Makes a Good Chef Cookbook? (May 2014 Edition)

Chef::Application.fatal!([ "You must set #{missing_attrs.join(', ')} in chef-solo mode.", "For more information, see https://github.com/opscode-cookbooks/postgresql#chef-solo-note" ].join(' '))

Poking at Chef Internals

•Other abuses: Messing with run_context and run_state

Page 12: What Makes a Good Chef Cookbook? (May 2014 Edition)

if node.run_list.recipes.include?('foo::bar')...end

Poking run_list and environment

if node.chef_environment == 'production'...end

• Use feature flags!

Page 13: What Makes a Good Chef Cookbook? (May 2014 Edition)

template "/etc/whatever.conf" do ... not_if { foo }end

Compile vs. Execute Errors

if foo template "/etc/whatever.conf" do ... endend

not the same thing as

Page 14: What Makes a Good Chef Cookbook? (May 2014 Edition)

execute 'yum install httpd' do not_if 'rpm -qa | grep -x httpd'end

Not declarative

•Also, the Chef recipe with 100 bash or powershell_script resource declarations

Page 15: What Makes a Good Chef Cookbook? (May 2014 Edition)

execute '/i/will/run/every/time' do action :run # because I don't have a guard hereend

Missing guards

Page 16: What Makes a Good Chef Cookbook? (May 2014 Edition)

default['mydaemon']['port'] = '1433'# don't you mean the integer 1433?default['mydaemon']['knob'] = 'disabled' # don't you mean false?

Not using native Ruby data types

• If you use native data types you can validate people’s input.

Page 17: What Makes a Good Chef Cookbook? (May 2014 Edition)

Fear of LWRPs•Missed abstraction opportunities

•No good example to put here; they’re all 200 lines long (thus proving my point)

Page 18: What Makes a Good Chef Cookbook? (May 2014 Edition)

remote_file 'whatever.tar.gz' do source 'http://hardcoded.url.com/'end

Hardcoded Strings

Page 19: What Makes a Good Chef Cookbook? (May 2014 Edition)

Excess Conditions & Recipe Length•https://github.com/opscode-cookbooks/mysql/blob/v3.0.12/recipes/server.rb

• (We’ve since refactored this)

Page 20: What Makes a Good Chef Cookbook? (May 2014 Edition)

Good Cookbooks...

Page 21: What Makes a Good Chef Cookbook? (May 2014 Edition)

Put control flow in attributes•Especially for cross-platform cookbooks

•Set common set of attributes, write common behavior in recipe context

Page 22: What Makes a Good Chef Cookbook? (May 2014 Edition)

case node['platform']

when "debian", "ubuntu" default['postgresql']['client']['packages'] = %w{postgresql-client libpq-dev} default['postgresql']['server']['packages'] = %w{postgresql} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib}

when "fedora", "amazon" default['postgresql']['client']['packages'] = %w{postgresql-devel} default['postgresql']['server']['packages'] = %w{postgresql-server} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} default['postgresql']['server']['service_name'] = "postgresql"

when "redhat", "centos", "scientific", "oracle" default['postgresql']['version'] = "8.4" default['postgresql']['dir'] = "/var/lib/pgsql/data"

if node['platform_version'].to_f >= 6.0 default['postgresql']['client']['packages'] = %w{postgresql-devel} default['postgresql']['server']['packages'] = %w{postgresql-server} default['postgresql']['contrib']['packages'] = %w{postgresql-contrib} else default['postgresql']['client']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-devel"] default['postgresql']['server']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-server"] default['postgresql']['contrib']['packages'] = ["postgresql#{node['postgresql']['version'].split('.').join}-contrib"] end default['postgresql']['server']['service_name'] = "postgresql"

end

Control Flow in Attributes

Page 23: What Makes a Good Chef Cookbook? (May 2014 Edition)

node['postgresql']['server']['packages'].each do |pg_pack|

package pg_pack

end

Common Recipe Code

•Easy to support more platforms without modifying recipe

Page 24: What Makes a Good Chef Cookbook? (May 2014 Edition)

default.rb

server.rb

_suse.rb_fedora.rb_windows.rb

Separate recipes by OS• If things you do are very different per platform, separate them into different recipes

Page 25: What Makes a Good Chef Cookbook? (May 2014 Edition)

“Public” versus “Private” recipes• ‘_’ faux-namespacing

Page 26: What Makes a Good Chef Cookbook? (May 2014 Edition)

loaded_recipes = if run_context.respond_to?(:loaded_recipes) run_context.loaded_recipes else node.run_state[:seen_recipes] end

node['mysql']['client']['packages'].each do |name| resources("package[#{name}]").run_action(:install)end

Do not abuse compile-time

•.run_action(:must_die)

•Use sparingly, if at all!

Page 27: What Makes a Good Chef Cookbook? (May 2014 Edition)

if some_error_condition fail "Helpful error message" # rather than Chef::Application.fatal!("error")end

Avoid poking Chef Internals

•Chef::Application.fatal is for use by Chef itself

•fail or raise is better

Page 28: What Makes a Good Chef Cookbook? (May 2014 Edition)

Attributes only where necessary• “Let’s create a node attribute for each of the 15,000 tunables in this daemon”

•Not necessary if you never touch 14,975 of those knobs

Page 29: What Makes a Good Chef Cookbook? (May 2014 Edition)

git clone git://github.com/foozolix/foozolix.git

cd foozolix && ./configure

make

make install

Give people options for installation

•At least give people a way to install from packages.

• “Compile from source” should be banned in most cases.

Page 30: What Makes a Good Chef Cookbook? (May 2014 Edition)

Be declarative•Know and use built-in Chef resources

•Know where to find LWRPs to avoid batch/execute/powershell_script

•Consider log resource versus Chef::Log

•Shows up in reporting as an updated resource instead of having to trawl through client.log

•Set an idempotency guard!

•Log at the right log level

Page 31: What Makes a Good Chef Cookbook? (May 2014 Edition)

Run System Commands Safely• system

•backticks

•Chef::Mixin::ShellOut

•shell_out

•shell_out!

Page 32: What Makes a Good Chef Cookbook? (May 2014 Edition)

$ chef-apply -s

Chef::Recipe.send(:include, Chef::Mixin::ShellOut)

cmd = shell_out!("echo -n Ohai, world")log cmd.stdout^DRecipe: (chef-apply cookbook)::(chef-apply recipe) * log[Ohai, world] action write

Example Recipe Context

Page 33: What Makes a Good Chef Cookbook? (May 2014 Edition)

unless node.chef_environment('pigsty') include_recipe 'bacon::default'end

Feature Flags Example

if node['foo']['bar']['can_haz_bacon'] include_recipe 'bacon::default'end

• Instead:

Page 34: What Makes a Good Chef Cookbook? (May 2014 Edition)

node['jboss']['instances'].each do |instance| link "/etc/init.d/#{instance['name']}" do to "/etc/init.d/jbossas" end

template "/etc/sysconfig/#{instance['name']}" do source "jbossas.sysconfig.erb" owner node['jboss']['server']['user'] group node['jboss']['server']['group'] mode "00644" variables( :jbossconf => instance['name'] ) action :create end

template "#{node['jboss']['server']['home']}/bin/standalone.sh" do source "standalone.sh.erb" owner node['jboss']['server']['user'] group node['jboss']['server']['group'] mode "00755" action :create end link "#{node['jboss']['server']['home']}/bin/#{instance['name']}.sh" do to "#{node['jboss']['server']['home']}/bin/standalone.sh" endend

Repetition == LWRP Candidate

Page 35: What Makes a Good Chef Cookbook? (May 2014 Edition)

actions :create, :delete

attribute :instance_name, :kind_of => String, :name_attribute => trueattribute :console_log_level, :kind_of => String, :required => trueattribute :datasources, :kind_of => Hash, :default => {}

.

.

.

default_action :create

Repetition == LWRP Candidate•Perfect for abstracting!

•Resource interface:

Page 36: What Makes a Good Chef Cookbook? (May 2014 Edition)

jboss_instance "petstore" do instance_name "can_haz_cheezburgerz" console_log_level "DEBUG" datasources {'db1' => 'jdbc://whatever:5432/db1'}end

Repetition == LWRP Candidate

•Write/debug hard logic once

•Clear consumer interface

•Parameter validation & sanity checking

•Non-JBoss experts can invoke without knowing gnarly details

Page 37: What Makes a Good Chef Cookbook? (May 2014 Edition)

module MyCookbook module Helper

# returns Windows friendly version of the provided path, # ensures backslashes are used everywhere def win_friendly_path(path) path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR) if path end end end

Write helper libraries

•Create reusable helper functions in pure Ruby

•Move repetitive detail out of recipe context.

•http://tinyurl.com/chef-libraries

Page 38: What Makes a Good Chef Cookbook? (May 2014 Edition)

Keep Recipes Small•< 100 lines

• If longer than this, consider breaking up functionality

•Example: nagios::server recipe does too much

• Installs Nagios

•Configures Nagios

•Pokes around in data bags for config items

Page 39: What Makes a Good Chef Cookbook? (May 2014 Edition)

Use Community Helpers•Chef Sugar - http://code.sethvargo.com/chef-sugar/

•Chef Cutlery - https://github.com/realityforge/chef-cutlery

•Attribute Validator - https://github.com/clintoncwolfe/attribute-validator

•You can also crib the ideas if you want to avoid external dependencies

Page 40: What Makes a Good Chef Cookbook? (May 2014 Edition)

Wrap-Up

Page 41: What Makes a Good Chef Cookbook? (May 2014 Edition)

Testing• I didn’t mention testing once in this talk!

• I’m assuming you will write tests for your cookbooks.

•A whole other talk...

• ... including good/bad things to test

Page 42: What Makes a Good Chef Cookbook? (May 2014 Edition)

Make your code aromatic•Keep recipes small

•Keep recipes simple

•Use a consistent style

•Use Foodcritic

Page 43: What Makes a Good Chef Cookbook? (May 2014 Edition)

Beware Expertise Bias•Hide gnarly details from recipe context

•Libraries

•LWRPs

•Attributes

•Resist urge to be overly clever - not everyone’s an expert

•Akin to the one-line sed/awk script

•http://tinyurl.com/the-expertise-bias

Page 44: What Makes a Good Chef Cookbook? (May 2014 Edition)

Learn from Software Developers•Everything I told you about information hiding, design patterns, testing, etc.

•Ops can learn from devs as well!

•Maybe we should call it OpsDev...

Page 45: What Makes a Good Chef Cookbook? (May 2014 Edition)

Don’t Yet Know Chef?•2-Day Chef Fundamentals Training in Boston

• June 16-17

•New Horizons, 75 Federal St., Suite 1205

•Use code MEETUP to save 10%

Page 46: What Makes a Good Chef Cookbook? (May 2014 Edition)

Thank You!E: [email protected]: https://github.com/juliandunnT: @julian_dunnW: www.juliandunn.net

Page 47: What Makes a Good Chef Cookbook? (May 2014 Edition)