Ten Things I Wish I’d Known About Chef
source link: https://zwischenzugs.com/2017/11/25/ten-things-i-wish-id-known-about-chef/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
1) Understand How Chef Works
This sounds obvious, but is important to call out.
Chef’s structure can be bewildering to newcomers. There are so many concepts that may be new to you to get to grips with all at once. Server, chef-client, knife, chefdk, recipe, role, environment, run list, node, cookbook… the list goes on and on.
I don’t have great advice here, but I would avoid doing too many theoretical tutorials, and just focus on getting an environment that you can experiment on to embed the concepts in your mind. I automated an environment in Vagrant for this purpose for myself here. Maybe you’ve got a test env at work you can use. Either way, unless you’re particularly gifted you’re not going to get conversant with these things overnight.
Then keep the chef docs close to hand, and occasionally browse them to pick up things you might need to know about.
2) A Powerful Debugger in Two Lines
This is less well known than it should be, and has saved me a ton of time. Adding these two lines to your recipes will give you a breakpoint when you run chef-client
.
require 'pry' binding.pry
You’re presented with a ruby shell you can interact with mid-run. Here’s a typical session:
root@chefnode1:~# chef-client Starting Chef Client, version 12.16.42 resolving cookbooks for run list: ["chef-repo"] Synchronizing Cookbooks: - chef-repo (0.1.0) Installing Cookbook Gems: Compiling Cookbooks... Frame number: 0/22 From: /opt/chef/embedded/lib/ruby/gems/2.3.0/gems/chef-12.16.42/lib/chef/cookbook_version.rb @ line 234 Chef::CookbookVersion#load_recipe: 220: def load_recipe(recipe_name, run_context) 221: unless recipe_filenames_by_name.has_key?(recipe_name) 222: raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}" 223: end 224: 225: Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}") 226: recipe = Chef::Recipe.new(name, recipe_name, run_context) 227: recipe_filename = recipe_filenames_by_name[recipe_name] 228: 229: unless recipe_filename 230: raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}" 231: end 232: 233: recipe.from_file(recipe_filename) => 234: recipe 235: end [1] pry(#<Chef::CookbookVersion>)>
The last line above is a prompt from which you can inspect the local state, similar to other breakpoint debuggers.
CTRL-D continues the run.
See here for more.
3) Run Locally-Modified Cookbooks
I spent a long time being frustrated by my inability to re-run chef-client with a slightly modified set of cookbooks in the local cache (in /var/chef/cache...
).
Then the chef client we were using was upgraded, and the--skip-cookbook-sync
option was available. This did exactly what I wanted: use the cache, but run the recipes in exactly the same way, run list and all.
The -z
flag can do similar, but you need to specify the run-list by hand.--skip-cookbook-sync
‘just works’ if you want to keep everything exactly the same and add a log line or something.
4) Learn Ruby
Ruby is the language Chef uses, so learning it is very useful.
I used Learn Ruby the Hard Way to quickly get a feel for the language.
5) Libraries
It isn’t immediately obvious how you avoid re-using the same code recipe after recipe.
Here’s a sample of a ‘ruby library’ embedded in a Chef recipe. It handles the figuring out of the roles of the nodes.
One thing to note is that because you are outside the Chef recipe, to access the standard Chef functions, you need to explicitly refer to its namespace. For example, this line calls the standard search
:
Chef::Search::Query.new.search(:node, "role:rolename")
The library is used eg here. The library object is created:
server_info = OpenShiftHelper::NodeHelper.new(node)
and then the object is referenced as items are needed, eg:
first_master = server_info.first_master master_servers = server_info.master_servers
Note that the node object is passed in, so it’s visible within the library.
6) Logging and .to_s
If you want to ‘quickly’ log something, it’s easy:
log 'my log message do level :debug end
and then run at debug level with:
chef-client -l debug
To turn a value into a string, try the .to_s
function, eg:
log 'This is a string: ' + node.to_s do level :debug end
7) Search and Introspection Functions
The ‘search’ function in Chef is a very powerful tool that allows you to write code that switches based on queries to the Chef server.
Some examples are here, and look like this:
graphite_servers = search(:node, 'role:graphite-server')
Similarly, you can introspect the client’s node using its attributes and standard Ruby functions.
For example, to introspect a node’s run list to determine whether it has the webserver role assigned to it, you can run:
node.run_list.roles.include?("webserver")
This technique is also used in the example code mentioned above.
8) Attribute precedence and force_override
Attribute precedence becomes important pretty quickly.
Quite often I have had to refer to this section of the docs to remind myself of the order that attributes are set.
Also, force_override
is something you should never have to use as it’s a filthy hack, but occasionally it can get you out of a spot. But it can’t override everything (see 10 below)!
9) Chef’s Two-Pass model
This can be the cause of great confusion. If the order of events in Chef seems counter-intuitive in a run, it’s likely that you’ve not understood the way Chef processes its code.
The best explanation of this I’ve found is here. For me, this is the key sentence:
This also means that any Ruby code in the file not explicitly delayed (
ruby_block
,lazy
,not_if
/only_if
) is run when the file is run, during the compile phase.
Don’t feel you need to understand this from day one, just keep it in mind when you’re scratching your head about why things are happening in the wrong order, and come back to that page.
10) Ohai and IP Addresses
This one caused me quite a lot of grief. I needed to override the IP address that ohai (the tool that gathers information about each Chef node and places in the node
object) gets from the node.
It takes the default route’s interface’s IP address by default, but this caused me lots of grief when using Vagrant. force_override
(see 8) above) doesn’t work because it’s an automatic ohai variable.
I am not the only one with this problem, but I never found a ‘correct’ solution.
In the end I used this hack.
Find the ruby file that sets the ip and mac address. Depending on the version this may differ for you:
RUBYFILE='/opt/chef/embedded/lib/ruby/gems/2.4.0/gems/ohai-13.5.0/lib/ohai/plugins/linux/network.rb'
Then get the ip address and mac address of the interface you want to use (in this case the eth1 interface:
IPADDR=$(ip addr show eth1 | grep -w inet | awk '{print $2}' | sed 's/\(.*\).24/\1/'""") MACADDRESS=$(ip addr show eth1 | grep -w link.ether | awk '{print $2}'""")
Finally, use sed (or gsed
if you are on a mac) to hard-code the ruby file that gets the details to return the information you want:
sed -i "s/\(.*${IPADDR} \).*/\1 \"\"/" $RUBBYFILE sed -i "s/\(.*macaddress \)m.*/\1 \"${MACADDRESS}\"/" $RUBYFILE
Author is currently working on the second edition of Docker in Practice
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK