{"id":4240,"date":"2013-02-21T01:16:45","date_gmt":"2013-02-20T22:16:45","guid":{"rendered":"http:\/\/railsware.com\/blog\/?p=4240"},"modified":"2021-08-16T11:50:51","modified_gmt":"2021-08-16T08:50:51","slug":"chef-dos-and-donts","status":"publish","type":"post","link":"https:\/\/railsware.com\/blog\/chef-dos-and-donts\/","title":{"rendered":"Chef: DOs and DON&#8217;Ts"},"content":{"rendered":"\n<p>At some time, you came to a point when you need to pick a solution for a deployment of your project, and you decided to pick Chef.<br><\/p>\n\n\n\n<p>Okay, but I have to disappoint you from the start &#8211; if you don\u2019t have any experience with Chef, then you\u2019re going to spend much time trying to sort things out with Chef wiki and that wiki is a tricky one. Just visit its start page and you\u2019ll quickly understand that it is mind-blowing.<\/p>\n\n\n\n<p>Here are some suggestions to help you in lowering the barrier when working with Chef.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Solo or Server<\/h2>\n\n\n\n<p>First of all, you have to pick a proper flavor of Chef.<\/p>\n\n\n\n<p>Go Solo when:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>your project is small and requires an instance or two to run<\/li><li>you have no staging environment or the other way to run it<\/li><li>you don\u2019t need Chef environments and don\u2019t want to patch Chef gem in order to achieve this functionality in Solo mode<\/li><\/ul>\n\n\n\n<p>Go Server when:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>your project runs on numerous instances<\/li><li>you have many projects that do not fit into Solo mode<\/li><li>you want to have staging environment(s) managed by Chef as well<\/li><li>you want all Chef functionality out of the box<\/li><\/ul>\n\n\n\n<p>And finally, choose Hosted Chef Server if you need the second option, but don\u2019t want to deal with the server setup. Just to let you know, Chef Server will run nicely on AWS m1.small instance and will satisfy average demands.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Data storing<\/h2>\n\n\n\n<p>Chef offers two strategies for storing your entities like environments or roles. The recommended way is to keep these entities under version control. You\u2019ll have to upload your entities to Chef server anyway, but SCM protects you from mistakes in the configuration as you can always pick previous settings and re-upload them to server. Also, SCM should be treated as one of the backups for Chef configuration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pollute environments!<\/h2>\n\n\n\n<p>Your setup will typically involve setting attributes via data bags, environments (Chef Server) or roles. It might not be obvious from the start, but it\u2019s better to keep the number of places where you set your attributes as low as possible for readability sake.<\/p>\n\n\n\n<p>If your choice is Chef Server, then it is good to make environments as a central place for this kind of job. In this case, your data bags will become very compact and will hold only general attributes, common across your environments.<\/p>\n\n\n\n<p>Although you can set environment-specific attributes in data bag, but it will become huge and unreadable very soon. Instead, placing such attributes into environment files puts you on the right track. You will always know where to look for a specific attribute.<\/p>\n\n\n\n<p>This way also provides you with the ability to access attributes directly inside recipes\/templates etc via node.attribute call. Data bags are required to be read inside recipe first. Then, proper template variable may be set inside resource definition.<\/p>\n\n\n\n<p>Environment sample:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">name \"myapp-staging\"\ndescription \"My App staging environment\"\ndefault_attributes(\n \"environment\" => \"staging\",\n \"resque\" => { \"workers_amount\" => 2 },\n \"resque_url\" => \"127.0.0.1:6379\",\n \"memcached_url\" => \"127.0.0.1:11211\",\n \"passenger_min_instances\" => 2,\n \"passenger_max_pool_size\" => 2,\n \"databases\" => [\n   {\n     \"name\" => \"staging\",\n     \"host\" => \"db.myapp.com\"\n   }, ...\n ],\n \"db_common\" => {\n   \"database\"  => \"myapp_staging\",\n   \"username\"  => \"myapp_user\", ... }\n)\n<\/pre>\n\n\n\n<p>Data bag sample:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{\n \"id\":\"myapp\",\n \"home_dir\": \"\/var\/www\",\n \"deployment_dir\": \"\/var\/www\/myapp\",\n \"user\": \"www-data\",\n \"group\": \"www-data\", \"repository\":\"git@github.com:my_organisation\/myapp.git\",\n \"revision\":\"master\",\n \"resque_group\": \"myapp-resque\",\n \"resque_queues\": \"my,cute,resque,queues\"\n}\n<\/pre>\n\n\n\n<p>Then, you can access environment attributes with node.environment or node.memcached_url. While for data bags, it will look like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">config = data_bag_item(\"deploy\", \"myapp\")\n\ntemplate '\/etc\/resque.d\/resque' do\n...\n variables(\n   :project_root => \"#{config['deployment_dir']}\/current\",\n   :queue => config['resque_queues']\n )\nend\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Attributes mess<\/h2>\n\n\n\n<p>Chef introduces no less than 4 types for attributes. It\u2019s an enormous amount for most of the projects. It would be wise to stick to one type and ignore the others if possible. Chef\u2019s Ohai plugin, which is responsible for gathering information about system, sets 4th type of attributes, and you don\u2019t have control over it &#8211; you can only read them.<\/p>\n\n\n\n<p>Check <a href=\"http:\/\/wiki.opscode.com\/display\/chef\/Attributes\" target=\"_blank\" rel=\"noreferrer noopener\">this page<\/a> and study well priorities for them. Don\u2019t miss the fact that attributes of \u201cdefault\u201d type set in roles will override ones set in environments while attributes of \u201coverride\u201d type are prioritised vice-versa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Community cookbooks<\/h2>\n\n\n\n<p>While this is the great option to have something that should work out of the box, it would be better not to use these cookbooks. Remember that community cookbooks are written in such a way that covers the majority of use cases: different operating systems, different distributives for an operating system, different ways to install packages (compile them from source or just install assembled by distributive maintainers package) etc &#8211; all of this leads to huge, barely readable cookbooks.<\/p>\n\n\n\n<p>If you need some customization (and sure you will need it), you\u2019ll have to either modify it or to create your own cookbook, depending on community one, or hopefully just to override some of the defined attributes on the upper levels like environments, but you have to be aware of what cookbook does. If you know what it does, then why not to have your own small and simple cookbook?.. Use community cookbooks as a source of inspiration or get vital parts from them and keep your setup readable for yourself and others.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Where the hell my changes are?<\/h2>\n\n\n\n<p>So&#8230; Your choice is Chef Server and your team works on the same cookbook simultaneously. Once the time has come and you decide to test your cookbook on staging. Chef client runs&#8230; and nothing changes. You\u2019re trying again and again, inserting some messages, trying to raise an exception in the middle of the process&#8230; Nothing happens. WTF with my Chef Server?!! You\u2019ve checked Server setup &#8211; url is correct, server is up and running (obviously)&#8230;<\/p>\n\n\n\n<p>It\u2019s time to check who\u2019s working on the same cookbook and talk about cookbook versions stored on the server. If someone else uploads cookbook with higher version than yours, then you\u2019ll not be able to see your changes applied during the chef-client run.<\/p>\n\n\n\n<p>This is Server specific problem and the solution would be to introduce some convention inside a team. Particularly, version should be modified when cookbook is in production ready state and is merged into master. It would be nice to notify everybody about this change. Of course, there are other ways to workaround this problem, but this one is simple and effective.<br>Also you can set explicit cookbook version in your run list, like this: \u201crecipe[deploy::myapp@1.0.0]\u201d. This approach might protect you from surprises. On the other hand, it introduces added complexity in cookbook management.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Riding chef-client<\/h2>\n\n\n\n<p>Chef documentation proposes you two scenarios for chef-client. It might run either as a daemon or a cron task, but you don\u2019t have to follow these options. There\u2019s one more pretty attractive option. You may run chef-client only when you need to. Check out for more information article about <a href=\"https:\/\/railsware.com\/blog\/chef-server-and-amazon-auto-scaling-groups\/\">Chef Server and Amazon Auto Scaling Group (ASG)<\/a>. Its suggestions also applicable for applications that do not utilize ASG.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sharper knife<\/h2>\n\n\n\n<p>Knife provides you all the necessary commands to communicate with Server, but some of them just require too many arguments, so daily usage of knife may become a little bit frustrating. So don\u2019t hesitate and sharpen that knife with your wrapper script!<\/p>\n\n\n\n<p>Just a few examples below of how knife commands are morphed into cute compact ones with our wrapper.<\/p>\n\n\n\n<p><strong>1) SSH command (used to deploy changes)<\/strong><\/p>\n\n\n\n<p>before:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">knife ssh \"chef_environment:myapp-production\" \\\n  \"echo '{\\\"revision\\\":\\\"master\\\",\\\"migrate\\\":true}' > \/tmp\/json_attrs &amp;&amp; sudo chef-client -j \/tmp\/json_attrs\" \\\n  -x user_name -i config\/keys\/user_name.pem\n<\/pre>\n\n\n\n<p>after:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">wr d -e myapp-production -m\n<\/pre>\n\n\n\n<p><em>wr<\/em> is a name of our shell script,<br><em>d<\/em> is a shortcut for \u201cdeploy\u201d,<br><em>-e<\/em> stands for environment name,<br><em>-m<\/em> &#8211; whether to run migration command during deployment or not<\/p>\n\n\n\n<p><strong>2) SSH command to perform across all instances of the given environment (generic)<\/strong><\/p>\n\n\n\n<p>before:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">knife ssh \"chef_environment:myapp-production\" \"date\" \\\n  -x user_name -i config\/keys\/user_name.pem\n<\/pre>\n\n\n\n<p>after:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">wr ssh -e myapp-production 'date'\n<\/pre>\n\n\n\n<p><strong>3) List commands<\/strong><\/p>\n\n\n\n<p>before:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">knife node list\nknife environment list\nknife role list\n<\/pre>\n\n\n\n<p>after:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">wr n\nwr e\nwr r\n<\/pre>\n\n\n\n<p>where n, e, r &#8211; shortcuts for \u201cnodes\u201d, \u201cenvironments\u201d, \u201croles\u201d respectively<\/p>\n\n\n\n<p><strong>4) Opening SSH session to a specific instance<\/strong><\/p>\n\n\n\n<p>before:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>get node IP via knife\u2019s list\/show commands<\/li><li>ssh -i config\/keys\/user_name.pem user_name@node_ip<\/li><\/ul>\n\n\n\n<p>after:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>get node name via wrapper list command &#8211; wr n<\/li><li>wr ssh node_name<\/li><\/ul>\n\n\n\n<p><a id=\"chef-capistrano\"><\/a>## Chef + Capistrano<\/p>\n\n\n\n<p>It\u2019s a popular approach to make Chef responsible for a setup of the node and then use Capistrano to perform deploys. You can do this as well, but answer this question &#8211; why should you use two tools to maintain nodes? What are the benefits? As you might have read above, Chef creators simply copy-pasted Capistrano\u2019s deploy task.<\/p>\n\n\n\n<p>Just check out the code of deploy provider:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">  def deploy\n    verify_directories_exist\n    update_cached_repo # no converge-by - scm provider will dothis\n    enforce_ownership\n    copy_cached_repo\n    install_gems\n    enforce_ownership\n    callback(:before_migrate, @new_resource.before_migrate)\n    migrate\n    callback(:before_symlink, @new_resource.before_symlink)\n    symlink\n    callback(:before_restart, @new_resource.before_restart)\n    restart\n    callback(:after_restart, @new_resource.after_restart)\n    cleanup!\n    Chef::Log.info \"#{@new_resource} deployed to #{@new_resource.deploy_to}\"\n  end\n<\/pre>\n\n\n\n<p>So why bother yourself with the setup of the second tool while you have everything you need in place? If you can\u2019t live without Capistrano, then better say bye to Chef and use it instead as the only tool for your system maintenance.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Re-think your system<\/h2>\n\n\n\n<p>If you\u2019re switching from some other tool to Chef or vice-versa, it\u2019s a good practice to review your system &#8211; the way it is being set up, scripts you use etc. Switching to a new tool gives you an unique ability and motivation to improve things. For example, one of our projects had weak mechanism to manage resque workers. After switching to Chef, we improved this part of the system to the state that satisfies us. Similar sort of improvements were applied to some other parts of the system. So don\u2019t miss your chance :)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>At some time, you came to a point when you need to pick a solution for a deployment of your project, and you decided to pick Chef. Okay, but I have to disappoint you from the start &#8211; if you don\u2019t have any experience with Chef, then you\u2019re going to spend much time trying to&#8230;<\/p>\n","protected":false},"author":20,"featured_media":9444,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[3],"tags":[],"coauthors":["Igor Antonyuk"],"class_list":["post-4240","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"aioseo_notices":[],"categories_data":[{"name":"Engineering","link":"https:\/\/railsware.com\/blog?category=development"}],"post_thumbnails":"https:\/\/railsware.com\/blog\/wp-content\/themes\/railsware\/vendors\/images\/article-thumbnail-default.jpg","amp_enabled":true,"_links":{"self":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4240","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/comments?post=4240"}],"version-history":[{"count":32,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4240\/revisions"}],"predecessor-version":[{"id":14093,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/posts\/4240\/revisions\/14093"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media\/9444"}],"wp:attachment":[{"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/media?parent=4240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/categories?post=4240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/tags?post=4240"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/railsware.com\/blog\/wp-json\/wp\/v2\/coauthors?post=4240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}