Average Apache Process Memory Usage

Exactly as the title suggests! A little shell snippet:

ps aux | grep apache2 | grep -v pts | awk '{ tot += $6; procs += 1; print $2,$6,$11 } END { print "TOTAL: ",tot,"/",procs,"=",tot/procs }'

You may need to swap out “apache2″ for “httpd” depending on your server setup. Why did I need this? At the moment I’m monitoring the average memory use, and trying to determine how it changes as I adjust the max-requests that a prefork process can serve before it’s recycled. Once I have that number, I’ll adjust the maximum amount of processes accordingly to prevent swapping.

Posted in Lesson | Tagged , , | 1 Comment

Managing Standalone Cookbooks in Chef

Managing cookbooks in Chef is a topic of heavy discussion right now in the community. The biggest argument seems to be between forking and maintaining an entire cookbooks repository (like the main Opsode one), or managing your custom cookbooks as single git repositories. At the moment, I’m leaning towards the one repo/cookbook approach because I feel that it takes advantage of all the built in GitHub goodness that I’ve become accustomed to, as well as makes it easier to share single cookbooks with the communtity. The biggest problem I think people have with this approach is that when you’re developing your cookbooks and testing, that you either make changes in your full Chef repository and then backport the changes into your standalone repo, or vice-versa. After stuggling with this for a bit, I think I’ve found an approach that now seems pretty obvious. However, because it wasn’t at the time, I thought I’d share.

Step 1: Working with Single Repo Cookbooks

One of the biggest pieces facilitating this kind of setup is Jesse Newland’s (@jnewland) Knife plugin for working with github cookbooks. It nicely follows the conventions that Knife already uses for vendor cookbooks (forks, downloads, merges) so your processes really don’t have to change to install or update the cookbooks that you’d like to use, and the files still end up in your main Chef repo. I find this more useful then some of the other plugins that keep the cookbooks out of the repo, especially when using Chef Solo, or developing with others. For those of you that just skim, typing commands as they arrive:

gem install knife-github-cookbooks

Step 2: Give Back

So now you’re using cookbooks from here and there as needed, and everything is swell until you realize that you just wrote a really useful cookbook. Or maybe, a really niche cookbook that someone else out there might need. Either way, it’s time to share it with the world! Of course, the Opscode Community is kinda the official place for this sort of thing, but they want a tarball. Hey, you know where you can make a tarball from your source pretty easily? GitHub! So, go create a repo for your cookbook. Because you’re a convention loving sort of person, you’ll prefix it with “chef-” or add “-cookbook” as a suffix, which the Knife plugin will kindly remove from the folder when it installs (I know, they thought of everything!).

Step 3: No Backporting Local Development

You’re happy at this point. Your cookbook is out there for the world to use, and the sun is shining. Then, out of the blue: bugs, feature requests, pull requests! The eye of the community has now focused it’s gaze on you and it’s time to get back to work. You’ve got your cookbook nicely vendored into your main repo, but that’s not where you want to make changes. So, why not create another location for your cloned repos? Chef is structured in a way that if you add additional cookbook paths, each one overrides the previous, so create a new folder in your main repo. I went with “forked-cookbooks” myself, and immediately added it to my .gitignore file. Now pop into that folder and clone your standalone repo into there, making sure to change the name of the folder so that it matches what the Knife plugin would do. Example from real life:

git clone https://github.com/wharton/chef-coldfusion9 ./forked-cookbooks/coldfusion9 

Now, add that path to your Knife config, Vagrantfile, or wherever else you specify it, and you can start making and testing your changes. Because we’re big on examples here, the line in my .chef/knife.rb looks like this:

cookbook_path ["#{current_dir}/../cookbooks", "#{current_dir}/../site-cookbooks", "#{current_dir}/../forked-cookbooks"] 

And the appropriate line in my Vagrantfile looks like this:

chef.cookbooks_path = ["~/Projects/knowledge-chef/cookbooks", "~/Projects/knowledge-chef/forked-cookbooks"]

When you’re happy with it, push your changes to your cookbook repo, and then use Knife to install a new copy of the cookbook into your main cookbooks path. The detailed changes go into your cookbook repo, where I think they belong, and your main Chef repo is up to date for use in solo deploys, or sharing with your team. Done with changes for now, or maybe your pull request was accepted? Kill the folder in forked-cookbooks, and you’re ensured that you’re back to using what everyone else is, and not accidentally pushing old copies of the cookbook up to your Chef Server. Finally: go grab a cold one, you’ve earned it!

Update: It appears that the debate is largely over, as Opsode has announced they’ll be moving to the one cookbook/one repo approach. I would like to say it was because of this post, but that would be a flat out lie. Props to Opscode for being willing to rework their development processes around the new setup.

Posted in Lesson | Tagged | 1 Comment

Keeping your machine clean with Vagrant & Chef

Recently a coworker of mine (@hectcastro) turned me on to Vagrant. In their words: “Vagrant is a tool for building and distributing virtualized development environments.” What does that mean for me? No more installing all sorts of madness for the variety of servers I need to work with in our somewhat mixed-technology environment. In essence, disposable VMs for local development once Chef is configured to provision the machines properly. Because I needed to update an older ColdFusion application last week, and didn’t have it installed, I figured it was as good an excuse as any to kick the tires a little. Here’s what I did:

  1. Install Vagrant
    $ gem install vagrant
  2. Install their Lucid Box
    $ vagrant box add base http://files.vagrantup.com/lucid32.box
  3. Create a new folder, initialize vagrant and checkout the chef repo. Vagrant will end up sharing this folder with the VM, which makes it handy for moving extraneous files around.
    $ mkdir YOUR_FOLDER
    $ cd YOUR_FOLDER
    $ vagrant init
    $ git clone gitosis@MYREPO:cfenv-chef.git
  4. Modify the Vagrantfile to change the VM specs, base box, IP you’ll access it on, and set up chef provisioning. Here are the relevant lines from my setup:
    # Every Vagrant virtual environment requires a box to build off of.
     config.vm.box = "lucid32"
    
    # Assign this VM to a host only network IP, allowing you to access it
    # via the IP.
    config.vm.network "33.33.33.50"
    
    # Boost the RAM slightly and give it a reasonable name
    config.vm.customize do |vm|
      vm.memory_size = 512
      vm.name = "CFEnv"
    end
    
    # Enable provisioning with chef solo, specifying a cookbooks path (relative
    # to this Vagrantfile), and adding some recipes and/or roles.
    #
    config.vm.provision :chef_solo do |chef|
      chef.cookbooks_path = "cfenv-chef/cookbooks"
      chef.roles_path = "cfenv-chef/roles"
      chef.add_role "cfserver"
    end
  5. Bring it up! (It takes a few minutes the first time.) When it’s done you should have CF running on port 8500 of the ip you specified in the Vagrantfile. It’s installed in /opt/coldfusion if you need to find it, and the administrator password is set by the chef cookbook.
    $ vagrant up
  6. What I do after this is share folders into the CF root so I can edit locally, but run in the VM. Here’s two examples which you can modify to your own taste (also in Vagrantfile):
    config.vm.share_folder "kw-core", "/opt/coldfusion/wwwroot/core", "~/Sites/core"
    config.vm.share_folder "kw", "/opt/coldfusion/wwwroot/knowledge", "~/Sites/knowledge"
  7. To see your changes to the file shares, run a vagrant reload. If you modify the chef stuff, you can run a vagrant provision to kick off a chef run. Because of the way I set up the chef recipe, that will also have the side effect of restarting CF. Just for reference, you can log into the box with vagrant ssh. That’s it!

“But where is the link to this chef repo!” you ask? Well, because I developed it just for myself I embedded the CF installer in it (which isn’t open source). However, because some co-workers have shown some interest, I’ll change a few things and try to get up it publicly. More importantly, I was able to provision a machine for local development without having to install any of the footprint of the app server I really didn’t want to install locally, and think this could be a very powerful solution for developing in a mixed-technology enviroment, or even in a single tech workplace for consistency of dev setups.

UPDATE: I moved the installer of the repo and pushed it to github with slightly modified directions.

Posted in Lesson | Tagged , , , , , | 2 Comments

A Look at Rails and Volume of Code

This is not a Rails tutorial. There a billion of them out there, and many of them quite good. My goal is not even to explain everything I’m doing. If you know Rails, you’ll get it, if you don’t, I refer back to my previous sentence and encourage you to at least take a look, and even if you don’t, you’ll should see that ruby is pretty easy to read. Having said that, my goal is to just take a look at a small web app I wrote, and evaluate the actual amount of code I had to write.

I want to do this because it seems that when people are doing framework comparisons, they can get a little hung up on the folder structure and volume of files. Look, with any new framework, you’re going to have to learn something new, it’s a given, but once you do, all that structure and code just sort of fades into the background, and you start concentrating on the actual work. So, we’re here to look at the actual work to create a simple application I’ve dubbed [click].

The App

It’s probably worth at least briefly describing the app, so you know what we’re trying to accomplish. The idea was for a simple photo portfolio-style site. I didn’t want a slideshow, I only wanted to display a few pictures, one at a time, with simple keyboard (or mouse) navigation. I wanted it all in HTML5, all AJAX-y (if that’s a word), but with shareable urls. Since a picture is worth a 1,000 words, a picture app must be worth 1000x, where x is the number of pictures on the site. (Sorry, math humor.) Anyway the point is: it’s up, so you can see the final product here.

Setup

On to the meat. Let’s make an app. First, we’re going to need Rails. For our purposes all commands are from the root folder that was created for this project. Assume your environment is already setup (again, part of learning the framework, not using it.), and all lines starting with $ are bash/shell.

$ rails new click

Files and folders created. Now we’re going to need a few libraries to accomplish what we want, so we edit /Gemfile to be:

source 'http://rubygems.org'

gem 'rails', '3.0.7'
gem 'sqlite3-ruby', '1.3.2', :require => 'sqlite3'
gem "paperclip", "~> 2.3"
gem 'typus'
gem 'exifr'

group :development do
  gem 'rspec-rails', '2.5.0'
  gem 'annotate-models', '1.0.4'
end

group :test do
  gem 'rspec', '2.5.0'
  gem 'webrat', '0.7.1'
end

Typus will generate our admin for us, paperclip performs attachment magic, exifr reads EXIF data, and sitemap_generator does what it’s name suggests. The rest are for testing/convenience. Now we:

$ bundle install

And all our libraries are ready.

The Model

We need to store our images and some info, so let’s create a model.

$ rails generate model photo name:string taken_on:date exposure:string f_stop:string focal_length:string iso:string description:string
$ rails generate paperclip photo image

Now, for our functionality. The model is the meat of this application, and thus follows /app/models/photo.rb:

class Photo < ActiveRecord::Base
  has_attached_file :image, :styles => { :display => "1050x700>", :rss => "400x300>" }

  validates :name, :presence => true
  validates :description, :presence => true

  validates_attachment_presence :image
  validates_attachment_content_type :image, :content_type => ['image/jpeg']

  scope :sorted, order('taken_on DESC')

  before_save :load_exif

  def load_exif
    # Pull the Exif data
    exif_data = EXIFR::JPEG.new(self.image.to_file)
    return if exif_data.nil? or not exif_data.exif?
    # Set the attributes
    self.exposure = exif_data.exposure_time.to_s
    self.f_stop = exif_data.f_number.to_f.to_s
    self.focal_length = exif_data.focal_length.to_f.round.to_s
    self.iso = exif_data.iso_speed_ratings
    self.taken_on = exif_data.date_time.to_date
  rescue
    false
  end

  def info_line
    "#{self.focal_length}mm #{self.f_stop}f #{self.exposure} ISO #{self.iso}"
  end

  def self.id_list
    @id_list = Array.new
    self.sorted.each { |photo| @id_list << photo.id }
    @id_list
  end

  def next_id
    @id_list = Photo.id_list
    @id_list.last.id == self.id ? @id_list.first : @id_list[@id_list.index(self.id) + 1]
  end

  def prev_id
    @id_list = Photo.id_list
    @id_list.first.id == self.id ? @id_list.last : @id_list[@id_list.index(self.id) - 1]
  end

end

What’s that do? It adds file attachment support to the photo object, and automatically creates two more versions of the file at the specified sizes. It makes name, description, and an image that is a jpeg mandatory. Before saving the record, it pull the EXIF data from the image file. Finally, there are some helpers for display and ordering.

Done. Let’s create the DB.

$ rake db:migrate

We now have the database created, table for our model created, some helpers for display and all our image attachment handling taking care of.

The Controller

Time to power up the retrieval of this data. For that we’ll need a controller.

$ rails generate controller Photos

And we’ll need to respond to json requests only (we’re all AJAX).

class PhotosController < ApplicationController
  responds_to :json

  # GET /photos/1.json
  def show
    @photo = Photo.find(params[:id])
    @photo_json = {
      'title' => @photo.name,
      'description' => @photo.description,
      'taken' => @photo.taken_on.strftime('%b %d, %Y'),
      'info' => @photo.info_line,
      'url' => @photo.image.url(:display),
      'next_id' => @photo.next_id,
      'prev_id' => @photo.prev_id
    }
    respond_with @photo_json
  end

  # GET /photos/first.json
  def first
    params[:id] = Photo.id_list.first
    show()
  end

end

Now when you call /photos/X.json, you’ll get JSON encoded information about the photo (id=X) to display.

The Admin

Typus will handle our admin interface. It will provide a user system, understands ActiveRecord models and handles paperclip attachments, so we just need a few commands.

$ rails generate typus
$ rails generate typus:migration
$ rails generate typus:controller photo
$ rake db:migrate

Here’s what that looks like:

.

We’re Done!

Well, more specifically, we’re done the Rails part. We will ultimately add a static html homepage, css styles, and an application javascript file that powers the display. This page and javascript has nothing to do with Rails, and would be the same regardless of framework, so it’s not really part of this discussion. Things I didn’t do include write any SQL, extend the framework at all, handle image uploads and/or resizing, put one line of code into a backend, manage any JSON encoding, manually manage any revisions to the DB during development (migrations are fantastic!), or set up a development and test environment or server (done automatically).

Disclaimer/Conclusion

I’m not Rails expert. In fact, I’m barely qualified to be called a beginner. There are things above that could be done better, there are even more things like caching and minification that Rails could do for me with a few lines of code. There’s a whole testing component to the app that was 75% auto-generated by Rails that I didn’t even mention. To scale this app up, you’d want to run the database on a database server. If you’re really interested, you can get the full app from my github repo. Having said that, I don’t feel that any of that matters for this example. What matters is that having been working programmer in Java/PHP/Perl/ColdFusion for over a decade, I can see why so many of the frameworks in those languages are basically taking some or all of their feature set from Rails. I enjoyed working on this codebase, and hope to continue improving it as I have free time or think of new ideas because it doesn’t require jumping through hoops. I’m not about to start pushing hard to switch everything to Rails, because I tend to be a bit of a language agnostic, and I think there’s a right tool for every job. However, I have a feeling that Rails IS the right tool for many MVC web projects, and definitely think it warrants a deeper look if you haven’t had the opportunity to do so.

Posted in Lesson | Tagged , , , , | Leave a comment

Browser Popularity – Knowledge@Wharton Edition

Everyone once in a while, I think it’s useful to take a look at the browser usage of your sites. You can see what the overall industry trends with a little googling (I like this page), and that’s important to know, but it’s also good to know if your users follow those trends. In the spirit of sharing, I thought I’d throw up a few quick graphs using the numbers from the main Knowledge@Wharton site. I basically just took the number our of Google Analytics for the month of February.

For the developers that have to fight with IE every day, I also thing it’s work breaking down that IE Pie:

My quick hitting thoughts are:

  1. IE6 is down to <5% of our total browser picture. Thank god.
  2. IE overall is no longer the majority browser. It’s just the biggest minority, and it’s trending down.
  3. Chrome as entered the double digits.

Now, a little history. Here’s every month for the past 3 years (Feb 2008 – Feb 2011), which I like to call “Chrome is eating IE”:

Posted in Observation | Tagged , , , , , | Leave a comment

The Return from Multisite

I was recently faced with moving a single site out of a multisite WordPress install (which was running domain mapped to it’s own URL) back to it’s own individual install. Although it pains me to add another site to patch, there were multiple reasons regarding the way that site was going to be used that make good sense. Disclaimer: these could be cleaned up and scripted, but I don’t have the time to do so at the moment. Additionally, most people would suggest you just export the old site and import the new, but I wanted to preserve all the plugin settings, plugins, user accounts, etc. Finally, you’ll be bringing over more then you need, and I’ve put in no method to clean that stuff out (unused plugins, themes, etc). You’ve been warned, so let’s proceed:

What You’ll Need

  • A new place to install WordPress
  • A copy of your old site’s files (or access to rsync them)
  • A dump of your multisite database

Some Notes

  • MSR = Old multisite root
  • WPR = New wordpress root
  • OLDPRE = Multisite prefix. WordPress sets a database prefix (by default it’s wp)
  • OLDNUM = Old site number. In multisite each site gets a number added to the prefix in the database for it’s site-specific tables. In my case, it was site 3. (so the full prefix of site-specific tables was “wp_3_”
  • NEWDB = The new database name.
  • NEWPRE = The new database prefix
  • OLDDBBACKUP = SQL backup of your old site (from mysqldump)>
  • OLDURL = The old site url (full w/ trailing slash)
  • NEWURL = The new site url (full w/ trailing slash)

The Steps

  1. Install WordPress in the new location.
    • Important: Install with a NEW prefix. This is important, because we’re going to restore the old DB into our new one as well, and we don’t want conflicts.
  2. Copy over the plugins folder from your multisite install. I used rsync myself:
    rsync -avz MSR/wp-content/plugins/* WPR/wp-content/plugins/
  3. Copy over your old site uploads to the single site path.
    mkdir WPR/wp-content/uploads
    rsync -avz MSR/wp-content/blogs.dir/OLDNUM/files/* WPR/wp-content/uploads/
  4. Sync the theme folder. (Note, I didn’t do this, I just checked out the site’s theme from git into the themes folder, so this is for the non-source controlled folk.
    rsync -avz MSR/wp-content/themes/* WPR/wp-content/themes/
  5. Restore the old database into the new one. Because you changed the prefix, there should be no conflicts (you changed the prefix, right??)
    mysql NEWDB < OLDDBBACKUP.sql
  6. Run the magic SQL. Ok there’s nothing magic about it, but this is the bulk of the work, please adjust accordingly for your setup, and for the love of all things holy, test, test, TEST!
    /* Get Users */
    
    TRUNCATE TABLE NEWPRE_users;
    
    INSERT INTO NEWPRE_users (ID, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name)
    SELECT ID, user_login, user_pass, user_nicename, user_email, user_url, user_registered, user_activation_key, user_status, display_name
    FROM OLDPRE_users;
    
    DROP TABLE OLDPRE_users;
    
    /* Get User Meta - Some from main site, from from site table */
    
    TRUNCATE TABLE NEWPRE_usermeta;
    
    INSERT INTO NEWPRE_usermeta
    SELECT *
    FROM OLDPRE_usermeta
    WHERE meta_key IN ('first_name','last_name','nickname','description','rich_editing','comment_shortcuts','admin_color','use_ssl','aim','yim','jabber');
    
    INSERT INTO NEWPRE_usermeta
    SELECT umeta_id, user_id, REPLACE(meta_key,'OLDPRE_OLDNUM_', 'NEWPRE_'), meta_value
    FROM OLDPRE_usermeta WHERE meta_key LIKE 'OLDPRE_OLDNUM_%';
    
    DROP TABLE OLDPRE_usermeta;
    
    /* Migrate Core Tables */
    DROP TABLE NEWPRE_commentmeta, NEWPRE_comments, NEWPRE_links, NEWPRE_options, NEWPRE_postmeta, NEWPRE_posts, NEWPRE_term_relationships, NEWPRE_term_taxonomy, NEWPRE_terms;
    
    RENAME TABLE
    OLDPRE_OLDNUM_commentmeta TO NEWPRE_commentmeta,
    OLDPRE_OLDNUM_comments TO NEWPRE_comments,
    OLDPRE_OLDNUM_links TO NEWPRE_links,
    OLDPRE_OLDNUM_options TO NEWPRE_options,
    OLDPRE_OLDNUM_postmeta TO NEWPRE_postmeta,
    OLDPRE_OLDNUM_posts TO NEWPRE_posts,
    OLDPRE_OLDNUM_term_relationships TO NEWPRE_term_relationships,
    OLDPRE_OLDNUM_term_taxonomy TO NEWPRE_term_taxonomy,
    OLDPRE_OLDNUM_terms TO NEWPRE_terms;
    
    /* Migrate Plugin Tables - This you have to do manually, add a like like this for every remaining OLDPRE_OLDNUM_ table */
    RENAME TABLE `OLDPRE_OLDNUM_TABLE` TO `NEWPRE_TABLE`;
    
    /* Update Roles Prefix */
    UPDATE NEWPRE_options
    SET option_name = 'NEWPRE_user_roles' WHERE option_name = 'OLDPRE_OLDNUM_user_roles';
    
    /* Update Upload Path */
    UPDATE NEWPRE_options
    SET option_value = 'wp-content/uploads' WHERE option_name = 'upload_path';
    
    UPDATE NEWPRE_options
    SET option_value = 'NEWURLwp-content/uploads' WHERE option_name = 'fileupload_url';
    
    /* Update URLS in DB */
    UPDATE NEWPRE_options
    SET option_value = replace(option_value, 'OLDURL', 'NEWURL')
    WHERE option_name = 'home' OR option_name = 'siteurl';
    
    UPDATE NEWPRE_posts
    SET guid = replace(guid, 'OLDURL', 'NEWURL');
    
    UPDATE NEWPRE_posts
    SET post_content = replace(post_content, 'OLDURLfiles/', 'NEWURLwp-content/uploads/');
    
    UPDATE NEWPRE_posts
    SET post_content = replace(post_content, 'OLDURL', 'NEWURL');
    
  7. Log in, go to permalinks, and set your .htaccess appropriately (most likely at the bottom). At this point your site should work.
  8. Adjust filesystem permissions as appropriate. I’m not going to suggest anything here because every setup is different. You should also clear all the tables out of your DB at this point that do not have your new site’s prefix on them.

Enjoy!

Posted in Lesson | Tagged , , | 3 Comments

Chef: Rounding the Bend

Since you’ve already ready my intro to Chef, and well as my article on getting started (right?) we’re going to do away with the long recap and instead give you a “Preiviously on 24″ style list:

  • You know what Chef is
  • You’ve installed and configured Chef
  • You’ve created a node
  • You’ve created a role
  • You’ve downloaded and used some existing cookbooks, changing default settings as necessary
  • Jack Bauer has muttered something about not having enough time

You can actually accomplish a fair amount just using the methods above, but eventually you’re going to need some functionality that goes beyond what the one-size-fits-all cookbooks can provide. A common reason that I’ve encountered for this is some recipes put in place a structure where you can easily extend them with your own templates, so we’ll look at that next.

Extending Recipes with Site Cookbooks

If you’ve been following along so far, you’ve got an empty site-cookbooks folder in the root of your chef repository (if you don’t, go ahead and create one). How does this work? Basically, you create a structure in there to mimic that of the cookbooks folder, and when knife uploads the cookbook, it uses the files it finds in site-cookbooks instead of those in cookbooks. To clarify: you only need to create the folders and files that you plan on overriding, or files that don’t exist in the cookbook. So, why don’t you just edit the cookbook itself? Actually, if it’s your own cookbook, that’s what you should do, but I’m talking about the case where you’re extending one you’ve found online. Granted, you could just as easily update it in the cookbooks folder, using git to manage your changes, but I personally thing it’s cleaner to use the site cookbooks method. This way, you can keep track of what you’ve written and/or changed. Additionally, if the author of the original enhances their cookbook while you’re off conquering the world with your new Chef setup, I think it’s easier to just replace the original in cookbooks, because you can use version control to see what’s changed and preserve all your own customizations.

So, on to the example. At the moment we have some basic functionality happening in our base_server.rb role file, but now we want to lock the machine down with an iptables firewall. Luckily, there’s a cookbook for that called, appopriately, iptables, so let’s vendor that cookbook with knife:

knife cookbook site vendor git -d

If you glance through the source of the cookbook, you’ll see that it’s creating a /etc/iptables.d directory, in which it will be placing rules, these rules are created by template files with a ‘definition’ call. Finally, the machine is locked down to only accept connections defined in those rule files. Two things worth noting here: First, this is our first look at a definition in Chef, so that warrants an explanation. To quote the chef wiki: “Definitions allow you to create new Resources by stringing together existing resources.” There’s some good examples on there as well, but we’re going to procede with ours. Here’s the relevant source from the definitions/iptables_rule.rb file:

define :iptables_rule, :enable => true, :source => nil, :variables => {} do
  template_source = params[:source] ? params[:source] : "#{params[:name]}.erb"

  template "/etc/iptables.d/#{params[:name]}" do
    source template_source
    mode 0644
    variables params[:variables]
    backup false
    notifies :run, resources(:execute => "rebuild-iptables")
    if params[:enable]
      action :create
    else
      action :delete
    end
  end
end

In short, it’s creating a new file in the iptables.d folder using a source based either on the name of the definition we’re creating or one we pass as a parameter, and enabling it. Why is this handy? Because instead of putting all that code in our recipe, we get a nice reusable snippet. Here’s how this is used in the default.rb recipe:

iptables_rule "all_established"
iptables_rule "all_icmp"

Sure beats writing all that code up there over and over again. Also, in Ruby fashion, it’s really easy to read. (I read: “Create an iptables rule for all established connections and for ping”). Just to fill in the last piece of the puzzle, here’s the template all_icmp.erb (which, if you’re following, is called by the iptables_rule definition):

# ICMP
-A FWR -p icmp -j ACCEPT

Now, this is all well and good, except that most people are going to need rules for more then just established connections and ping. That brings us to the second thing worth noting about this cookbook: we need more templates! Enter: site cookbooks. For our example below, let’s extend this cookbook to include rule for a web server. To begin, create the appropriate structure (run from the site-cookbooks folder):

mkdir -p iptables/templates/default/
mkdir -p iptables/attributes
mkdir -p iptables/recipes

Let’s start with some simple templates for iptables including rules for http and https traffic from anywhere, as well as one for ssh (we do want to be able to administrate the box remotely, right?). Here’s the templates/default/all_http.erb file:

# HTTP
-A FWR -p tcp --dport 80 -j ACCEPT

Next, the template/default/all_https.erb file:

# HTTPS
-A FWR -p tcp --dport 443 -j ACCEPT

And finally, template/default/all_ssh.erb:

# SSH
-A FWR -p tcp --dport ssh -j ACCEPT

I suppose you could combine those into one template, but at this level I prefer to keep things as granular as possibly, so we can mix and match down the road. Now, let’s get tricky and try to apply one of the other things we know about Chef: the templates can be dynamic. So, let’s throw in some rules for locked down versions of those same services. Here’s templates/default/network_http.erb:

# HTTP Locked Down
<% @node[:iptables][:ssh][:addresses].each do |address| %>
-A FWR -p tcp -s <%= address %> --dport 80 -j ACCEPT
<% end %>

And to match, templates/default/network_https.erb:

# HTTPS Locked Down
<% @node[:iptables][:ssh][:addresses].each do |address| %>
-A FWR -p tcp -s <%= address %> --dport 443 -j ACCEPT
<% end %>

Finally, templates/default/network_ssh.erb:

# SSH Locked Down
<% @node[:iptables][:ssh][:addresses].each do |address| %>
-A FWR -p tcp -s <%= address %> --dport 22 -j ACCEPT
<% end %>

Now we have a nice base to work with, some iptables templates we can mix and match as necessary. Of note: we’re introduced a node variable to the network rules, so we have to remember to cover that in our recipe. Also worth noting: most programmers are going to start to see repetition above, and may feel tempted to create an all_tcp rule with an additional “port” variable. Don’t let me stop you, it might make sense. Two reasons why I didn’t: 1) Down the road there could be more complicated services I’m defining in templates that could have multiple iptables rules, and I would prefer to have them in one template so that.. 2) each service remains a granular object, easy to read when defined in the recipe. I’m willing to sacrifice a little bit of repetition if it makes my recipes easier to read and administrate. Again, personal choice, and you’re a rugged individualist, so do your own thing if it makes you happy!

Moving on, I think recipes don’t have to be as granular (because we did so in the templates), so let’s create a recipe using these templates for “iptables::web” encompassing http and https, as well as a decision on which rules to use based on a node variable. Here’s our recipes/web.rb:

# Have we decided to lock down the node?
if node[:iptables][:web][:addresses].empty?
  # Use the all_ rules
  iptables_rule "all_http"
  iptables_rule "all_https"
  # Disable the network rules
  iptables_rule "network_http", :enable => false
  iptables_rule "network_https", :enable => false
else
  # Use the network rule
  iptables_rule "network_http"
  iptables_rule "network_https"
  # Disable the all traffic rules
  iptables_rule "all_http", :enable => false
  iptables_rule "all_https", :enable => false
end

Note: if we don’t do that enable => false bit, the file will remain on the server even if we remove the line later. Strange, I know. Moving on, one for ssh, “iptables::ssh” (recipes/ssh.rb):

# Have we decided to lock down the node?
if node[:iptables][:ssh][:addresses].empty?
  # Use the all_ssh rule
  iptables_rule "all_ssh"
  # Disable the network ssh rule
  iptables_rule "network_ssh", :enable => false
else
  # Use the network rule
  iptables_rule "network_ssh"
  # Disable the all traffic rule
  iptables_rule "all_ssh", :enable => false
end

Pretty simple right? If we’ve defined addresses on the node, use the lock down rules, otherwise, open the port up to the world. Finally, because we’ve introduced new node attributes, we need to create two attribute files to correspond to our new recipes. First, attributes/web.rb:

# Web Traffic Allowed Networks (IP/NETMASK)
default[:iptables][:web][:addresses] = Array.new

And one for SSH as well (attributes/ssh.rb):

# SSH Allowed Networks (IP/NETMASK)
default[:iptables][:ssh][:addresses] = Array.new

Awesome. Now the big finish: let’s add the ssh recipe to our base server, then create a new role for a web server that applies our “base server” configuration and then locks down the machine. In the real world, this would be part of a role that also configures your web servers. Coincidentally, the apache2 cookbook uses a very similar mechanism so if you’re anxious you can move ahead using the versatile “web_app” definition in that cookbook. Now our roles/base_server.rb looks like this (locking down ssh to an arbitrary subnet):

name "base_server"
description "Common Server Base Configuration"
run_list(
  "recipe[fail2ban]",
  "recipe[git]",
  "recipe[vim]",
  "recipe[ntp]",
  "recipe[iptables]",
  "recipe[iptables::ssh]"
)
default_attributes(
  "ntp" => {
    "servers" => ["timeserver1.upenn.edu", "timeserver2.upenn.edu", "timeserver3.upenn.edu"]
  },
  "resolver" => {
    "nameservers" => ["128.91.87.123", "128.91.91.87", "128.91.2.13"],
    "search" => "wharton.upenn.edu"
  },
  "postfix" => {
    "relayhost" => "SOME.RELAY.SERVER"
  },
  "iptables" => {
    "ssh" => { "addresses" => ["128.91.0.0/255.255.0.0", "130.91.0.0/255.255.0.0"] }
  }
)

Great, now lets create a more task specific role for a web server, building off of that base role. Because I want to prove it works, why don’t you go and download the cookbook for apache2. I’ll wait. Ok, let’s include it to do a base install so you can check easily. Here’s the new roles/web_server.rb role file:

name "web_server"
description "Generic Web Server"
run_list(
  "role[base_server]",
  "recipe[apache2]",
  "recipe[apache2::mod_ssl]",
  "recipe[iptables::web]"
)

There you have it. Note the “role[base_server]” line in run_list, that includes all the good stuff we have in our base server role (obvious, right?) Upload the cookbooks, update the roles, and try assigning the new web server role to a test node and you’re rolling!

Housekeeping

To complete this example, I need to include a few more things I did. This falls into a grey area for me because I actually think this belongs in the original recipe, which is something you’re likely to encounter as well as you extend cookbooks: When to override and when to patch? My approach is going to be fix in site cookbooks, and then submit a patch back to the author, if it gets included, great, update the cookbook and remove it from the site cookbook. Because I haven’t gotten this patch back to Opscode yet, and I want you to be able to use my examples if you’d like, here was the last piece of the iptables functionality I had to change.

The problem: iptables weren’t persisting through reboot. A pretty big issue! My solution was to add a script to the ifup.d folder that would call the rebuild-iptables script (created by the iptables rule) when network interfaces come up. To do this required a short template (templates/default/iptables.erb):

#!/bin/sh
/usr/sbin/rebuild-iptables

And a recipe to place it (recipes/on_boot):

if platform?("debian", "ubuntu")
  # Add a script to restore the rules on boot
  template "/etc/network/if-up.d/iptables" do
    source "iptables.erb"
    owner "root"
    group "root"
    mode 0755
  end
end

Now, include the “iptables::on_boot” recipe in your base_server.rb role and you’re good to go! In case you’re curious, I haven’t submitted the patch yet because I haven’t had time to test it on anything but the ubuntu boxes I’m running (thus the “if platform?” conditional), and I’d prefer to submit a patch that also works in the RHEL space as well. Opsode, don’t let this stop you from taking this and running!

Seems to me we’ve gone quite long again, so we’ll wrap this one up. Still to come: the wild wild west, creating your own cookbook in it’s entirety from scratch! Until then, enjoy extending recipes. At this point if you’ve been following along you’re already wielding a pretty powerful configuration tool!

Posted in Lesson | Tagged , , , , , | 6 Comments

Chef: Out of the Gate

Next in my series of posts about the configuration management tool chef, I’d like to talk a little bit about how I got started. First up:

The Server

Although there are several varieties of chef, I prefer to have a central server from which I can examine my nodes (client machines) from. Because I didn’t want to go through the trouble of setting up and maintaining my own, I decided to go with a hosted service from Opscode, which is free up to 5 nodes, and has very reasonable pricing beyond that. You can sign up for an account right on their homepage. Another nice thing about having a server running is that you can use it to examine all the properties of your cookbooks and nodes to see if values are being set the way you expect them two. For example, being able to browse the node attributes on the server helped me diagnose a problem of mismatched brackets, that was putting putting all my custom attributes into one cookbook, but was not an invalid file on my end.

Getting Started

You can find this basic info on a variety of sites, so I’m not going to go into painstaking detail, just run through getting your initial chef directory structure in place. This will require git, which you can learn more about from Brian and Hector’s tech talk, or on the web. The official installation instructions you can get from the Opscode Knowledge Base. This is a condensed version.

First, we want to pull down an empty chef structure (note, all my commands will be for *nix environments, if necessary, you’ll have to swap in the windows equivalents):

git clone git://github.com/opscode/chef-repo.git

This will create the folders that knife expects to find when it runs. We’ll enter the folder and add one more for site cookbooks (which is a clever way to extend cookbooks we download from 3rd parties):

cd chef-repo
mkdir site-cookbooks

Finally, we need to set up knife to be able to talk with our server. On the Opscode site in your console you need to set up a new organization, then generate a new key for it (save this file, we’ll call it ORGANIZATION-validator.pem) and use the “Generate knife config” file to get the knife.rb file. Finally, on your user account page use the “Get a new private key” link to download your keyfile (I’ll call it USERNAME.pem). Now we’ll create the appropriate location in our chef repo and copy the files in (all commands will assume we’re sitting in the chef-repo folder from this point forward):

mkdir .chef
cp /YOURPATH/ORGANIZATION-validator.prm .chef
cp /YOURPATH/knife.rb .chef
cp /YOURPATH/USERNAME.pem .chef

And a quick test:

knife client list

Should output:

[
    "ORGANIZATION-validator"
]

Ok. Now we’re cooking! Heh, get it? Sorry. Let’s move on.

Roles

It’s hard to know whether to introduce roles or recipes first, because they’re pretty useless without one another, but we’ll start with roles. A role is basically defines a list of recipes and attributes that you can apply as a single unit to a node. Although you can alter your roles on the server once you create them, I think that always creating them from a source file is the best way to go about it. I got started with this concept by thinking about the environment I needed to manage, and decided to keep it simple, breaking all the servers up into two pieces:

  • A base role containing all the software and configuration that’s common across all server types (network setup, security, mail)
  • A role for the specific server type (application, database, file, etc.)

So, we’ll get started with the base role. Create a file called base_server.rb in the roles folder of your repository and we’ll start with the bare minimum in there:

name "base_server"
description "Common Server Base Configuration"

To create the role and add it to the server, enter knife:

knife role from file base_server.rb

To see if it worked, log on to the chef server and look in the “Roles” tab. You should see your new role. If you click on it, you’ll get more details, but that’s pretty much empty right now. Before we can do anything clever there we need to learn a little bit about cookbooks. Additionally, we’re going to need a machine to experiment on, so let’s work on that next.

Nodes

Nodes are the computers that you’re controlling with chef. Although you could manually install chef on a machine, register it with your server, and use the server’s web interface to add roles to that node, the easiest way (assuming you have ssh access to your test machine) is to use knife’s bootstrapping command (which we covered in the first chef article). Let’s bootstrap our test system and assign it our new base_server role:

knife bootstrap TEST.SERVER.ADDRESS -x USERNAME -P PASSWORD -r 'role[base_server]' --sudo

When that completes you should see the machine in the node list on the server, or you can even quickly check with:

knife node list

From this point forward on that box, to realize changes you’ve made to your cookbooks and recipes on the test box you need to run the chef-client command. This will contact the server, pull down any new or changed cookbooks, and apply the appropriate recipies given the roles the machine has been assigned. So, on a *nix box:

sudo chef-client

Now, let’s do something with our test machine.

Cookbooks and Recipes

Cookbooks are collections of… wait for it… recipes! (Pretty obvious, huh?) Recipes are what actually makes chef do anything useful, like install a web server, or change your DNS settings. Even better, there’s already a bunch of recipes out there for common tasks. On the down side, not all the recipes you download are going to have good (or any) documentation at all. However, once you understand how they work, it’s easy to read a recipe and figure out what it’s doing (although this does not excuse developers out there from documenting!! Do it!!!)

Cookbooks: Straight up Defaults

To get our feet wet with cookbooks, we’ll get to work on our base server role created above, and have it start to do some stuff. The easiest recipies to use are the ones that require no additional input from us at all, so lets start there. We use git here as version control software, so I’d like that installed as part of our base build. A search on the Opcode Cookbooks Site shows me that someone has already been nice enough to create a cookbook for git. Because it’s there, we can use knife to pull a copy:

knife cookbook site vendor git -d

Using the vendor command does some git magic behind the scenes, which, honestly, I do not entirely understand yet, but this is the preferred way to use other downloaded cookbooks. Once that command runs, you can take a cook in your cookbooks folder and see what got downloaded. Cookbooks have a standard folder structure inside of them, and the first place I look is in the root of the cookbook for some sort of documentation. The next place I tend to look is in the “recipes” sub-folder. In here is the list of recipes that is available to you (with default.rb what is run if you just use the cookbook name as your recipe.) In our case for git the default.rb recipe is what installs the git client packages. Here’s the rule itself for the curious:

case node[:platform]
when "debian", "ubuntu"
  package "git-core"
else
  package "git"
end

As you can see, the DSL (domain-specific language) in Ruby is pretty easy to read. When the platform is debian or ubuntu, install the “git-core” package, otherwise install the “git” package. Chef is smart enough to have different methods for installing the packages depending on what platform you’re on, but is nice enough to have that abstracted away in the recipes. This will work for our needs, so let’s add a new section to our base_server.rb role file:

run_list(
  "recipe[git]"
)

Had we wanted to the install the non-default recipe of a cookbook, we’d have used the double colon notation. For example, there’s a server recipe in the git cookbook, and to use that we would have added “recipe[git::server]” to our run list. Every time we change one of our roles, we need to tell knife to update the server. Additionally, before we can use a cookbook on a node, we need to upload it to the server as well. (Note: behind the scenes this is all happening via REST API calls to the chef server. Knife is actually just a RESTful client to the chef server API. Now you know.)

knife cookbook upload git
knife role from file base_server.rb

And on the client:

sudo chef-client

Ok, I’m done typing that for now. You’ll know what I mean now when I say to update the cookbook, update the role, and run the client moving forward, right? So, let’s continue. I’m also going to download cookbooks for vim and fail2ban and add them to my base role as well. In it’s entirety base_server.rb now looks like this:

name "base_server"
description "Common Server Base Configuration"
run_list(
  "recipe[fail2ban]",
  "recipe[git]",
  "recipe[vim]"
)

Upload the cookbooks, update the role, and run the client and we now have a server with git and vim installed, and fail2ban providing some security.

Cookbooks: Tinkering with the Defaults

That’s all well and good, but we’ve gotten to the point where I need to install some software as well as change some configuration. As an example, I’d like to set up a NTP service on the box to keep the clock in sync with the Penn time servers. A quick search shows that there’s already a “ntp” service, so let’s start with that.

knife cookbook site vendor ntp -d

Let’s take a look at the relevant parts of the default.rb recipe:

case node[:platform]
when "ubuntu","debian"
  package "ntpdate" do
    action :install
  end
end

package "ntp" do
  action :install
end

service node[:ntp][:service] do
  action :start
end

template "/etc/ntp.conf" do
  source "ntp.conf.erb"
  owner "root"
  group "root"
  mode 0644
  notifies :restart, resources(:service => node[:ntp][:service])
end

Ok, the first part is easiest enough to understand: install some packages, some conditional on the distribution we’re running. Now we come to some new stuff. First, let’s talk about the “node[:ntp][:service]” variable. This is the chef way off accessing node specific attributes. This could be different on every machine we run chef on, but has default values set by the cookbook and/or the role (they can also be overridden in either of these places, but we don’t need to go into that yet). To get an idea of what variables the cookbook contains, take a look at the default values which are all set by files in the attributes folder. The files in this folder should be named to correspond to the recipes. So, in the attributes/default.rb file we see:

case platform
when "ubuntu","debian"
  default[:ntp][:service] = "ntp"
when "redhat","centos","fedora"
  default[:ntp][:service] = "ntpd"
end

default[:ntp][:is_server] = false
default[:ntp][:servers]   = ["0.us.pool.ntp.org", "1.us.pool.ntp.org"]

So, we set the name of the service depending on the platform we’re on, by default do not run NTP as a server for others to access, and have a default list of ntp servers. Slightly confusing side note: although it would be nice if this file was commented, it seems that the place to actually do so is in the cookbook root in the metadata.rb file. Here’s the related section from that file:

attribute "ntp",
  :display_name => "NTP",
  :description => "Hash of NTP attributes",
  :type => "hash"

attribute "ntp/service",
  :display_name => "NTP Service",
  :description => "Name of the NTP service",
  :default => "ntp"

attribute "ntp/is_server",
  :display_name => "NTP Is Server?",
  :description => "Set to true if this is an NTP server",
  :default => "false"

attribute "ntp/servers",
  :display_name => "NTP Servers",
  :description => "Array of servers we should talk to",
  :type => "array",
  :default => ["0.us.pool.ntp.org", "1.us.pool.ntp.org"]

Between those two places you should be able to determine what you have control over in your cookbook. Now, to put this into use and change the servers we’ll be talking to to the local UPenn time servers, we need to add the recipe, and add a new section to our base_server.rb role, keeping the structure we’ve identified above:

name "base_server"
description "Common Server Base Configuration"
run_list(
  "recipe[fail2ban]",
  "recipe[git]",
  "recipe[vim]",
  "recipe[ntp]"
)
default_attributes(
  "ntp" => {
    "servers" => ["timeserver1.upenn.edu", "timeserver2.upenn.edu", "timeserver3.upenn.edu"]
  }
)

Those couples lines are actually all we need to accomplish the task. Upload the cookbook, update the role, and do a client run and we’re now running the NTP service tied to our local time servers. Although we’re done, let’s take a look at the last section of that recipe and see what it’s doing. Specifically, the template command. If you look at it like english you can basically read that it’s creating a file on the system (“/etc/ntp.conf”) from a source file (“ntp.conf.erb”), setting permissions on the file, and then restarting the NTP service. You’ll see there’s no path on the source file, and that’s because the cookbook expects template files to live in the template folder, beyond there in a folder for the recipe you’re running. In our case, that’s templates/default/ntp.conf.erb. ERB is ruby’s templating system, and provides a way to insert variables and some logic (if necessary) into arbitrary text files. Let’s take a look:

driftfile /var/lib/ntp/ntp.drift
statsdir /var/log/ntpstats/

statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

<% if node[:ntp][:is_server] -%>
server 0.us.pool.ntp.org
server 1.us.pool.ntp.org
server 2.us.pool.ntp.org
server 3.us.pool.ntp.org
<% else -%>
<% node[:ntp][:servers].each do |ntpserver| -%>
  server <%= ntpserver %>
<% end -%>
<% end -%>
restrict default kod notrap nomodify nopeer noquery

restrict 127.0.0.1 nomodify

The parts that are ERB specific are all contained in <% blocks %>. In this case, the text file generates the list of servers we use based on the attributes we’ve specified on the node. Although a pretty simple example, I think you can see how this is an easy way to manage settings files, and most recipes leverage this template system pretty heavily.

Ok, getting a little long here, so let’s just add a few more cookbooks that will just require a few attribute changes and call it a day. We’ll add cookbooks to set our DNS settings (resolver), and to add a mail server and set it to relay through an internal relay host. At the end of all of that, here’s our final base_server.rb file:

name "base_server"
description "Common Server Base Configuration"
run_list(
  "recipe[fail2ban]",
  "recipe[git]",
  "recipe[vim]",
  "recipe[ntp]"
)
default_attributes(
  "ntp" => {
    "servers" => ["timeserver1.upenn.edu", "timeserver2.upenn.edu", "timeserver3.upenn.edu"]
  },
  "resolver" => {
    "nameservers" => ["128.91.87.123", "128.91.91.87", "128.91.2.13"],
    "search" => "wharton.upenn.edu"
  },
  "postfix" => {
    "relayhost" => "SOME.RELAY.SERVER"
  }
)

You should definitely go through the postfix and resolver cookbooks at this point and make sure you can understand what’s going on. When you’re through with that, you’ll be ready for our next installment, which is to build on all this knowledge and extend vendor cookbooks, and ultimately author our own!

Posted in Lesson | Tagged , , , , , , , , | 2 Comments

Rapid Configuration with Chef

Here’s the scenario: it’s 3:30 on Friday, and I’d like to spin up a new application server to sit in a linux cluster. Here’s what I have:

  • A provisioned, but completely empty VM

Here’s what I need:

  • A full install of Ubuntu Linux
  • Networking configured, because this will be an LVS client, this entails:
    • Setting up a primary static ip
    • Adding multiple IPs netmasked to 255.255.255.255 on the loopback interface
    • Adding routing rules for those IPs
    • Disabling ARPing from those interfaces
    • Firewalling the box and allowing various services different levels of access
    • DNS setup
  • OpenSSH Server Installed
  • Apache Fully Configured, including:
    • PHP5 installed and configured
    • SSL Certificates Installed
    • Multiple Virtual Hosts setup
  • NFS setup, and appropriate shares mounted
  • NTP setup for clock synchronization
  • VMWare tools installed
  • A mail server enabled and configured to only accept mail from localhost, and relay through another machine on the network
  • Monitoring software installed and configured
  • Security software installed and configured
  • Memcached installed and enabled
  • An assortment of utilities for diagnosing load and network issues installed

I’d like to do all of this with the guaranteed consistency, so I do not want to do a single piece of it manually. So, let’s do it. I’ve got the Ubuntu install ISO mounted in the VM, I reboot, and add the following to the boot parameters:

ks=http://[MY_BOX]/[SERVER].cfg

Sit back and watch. 10 minutes later we have a full OS install with openssh server running, firewalled, and one user account added. Now, on my machine, I run one command:

knife bootstrap [SERVER] -x [USER] -P [PASSWORD] -r 'role[kw_server]' --sudo

It’s 3:50, and I’m done. Everything is installed, configured, and secured. Need to add a second or third server to the cluster? Rinse and repeat. The million dollar question: how on earth is this all possible? The answer: Chef. What is it? I’ll quote the creators (Opscode):

Chef is an open source systems integration framework built to bring the benefits of configuration management to your entire infrastructure. You write source code to describe how you want each part of your infrastructure to be built, then apply those descriptions to your servers. The result is a fully automated infrastructure: when a new server comes on line, the only thing you have to do is tell Chef what role it should play in your architecture.

I was able to accomplish automating system configuration through a combination of custom defined “roles”, and an assortment of “cookbooks” some downloaded and used as it, some tailored to my specific use, and some written from scratch. It is my goal over a series of posts to take you through the process of doing this yourself, attempting to address some of the problem areas I encountered while doing so, and some of the items that take a little longer to grasp. Of note, Chef is entirely written in ruby, and although I have very limited experience with the language thus far, the syntax is easy enough to understand that it shouldn’t frighten anyone off. There’s no advanced programming concepts, mostly what you’re doing is a putting together simple templates, and I did not see my lack of knowledge as a hinderance in any way.

The Nitty Gritty

Because I’d like these to be constructive, I’ll try to explain what every command does for the uninitiated:

ks=http://[MY_BOX]/[SERVER].cfg

Adding that line to the boot parameters on Ubuntu points the installed to what’s called a kickstart file. In the simplest terms, this is basically an answer file to all the questions the installer would ask you, as well as allowing you to do some minimal configuration and additional package installations. In my case, I’ve found the easiest way to configure a user account, partitioning, initial access, and the primary IP is to have a kickstart file define these things. Would it have been relatively easy to just set these things after installing the box? Yes. However, I think the most important/difficult part about going through this process is that you have to force yourself to do some extra work up front so that you never have to touch the actual server. The payoff down the line is well worth the time up front if you ever find yourself having to provision and deploy boxes at a rapid clip, you’ll want everything to happen automatically, with nothing waiting on your manual input. Initially I started with a very generic kickstart that would bring the box up via DHCP, but then I would have had to put properties on the node itself once chef was installed to configure networking, which didn’t seem ideal (too manual, even if it still didn’t involve touching the server). However, as I embraced the chef way of doing things, I found it was just as easy to create a dedicated kickstart file per-server automatically, and that actually lead to a better starting point for the box. I will go through all the details of creating this kickstart cookbook in part two when I introduce the concept of the cookbook.

knife bootstrap [SERVER] -x [USER] -P [PASSWORD] -r 'role[kw_server]' --sudo

Knife is the command line tool for chef, and you’ll find yourself using it quite regularly while working with chef’s cookbooks and recipes. In this case, however, we’re using the bootstrapping functionality of knife to completely configure our new server. What does the bootstrapping do? It logs onto the new box via SSH, installs ruby, ruby gems and chef itself. It configures chef on that box and registers it as a client to your server (in my case, I’m using the free server provided by Opscode). Finally, it can assign a role to the box. In our case, we’re assigning the “kw_server” role which is a collection of all the recipes and configuration needed to turn our box from a base OS install, to a fully configured application server. We’ll go through how the role system works in a later post as well.

Thus concludes my intro. If you’d like to see any of this in action (and you work with me) let me know and I’d be glad to run through it with you. As well, my goal is to get a series of posts written as sort of a road map for starting to use Chef at Wharton. Finally, I intend open-source the cookbooks I’ve written once I make them a little more cross platform, and make sure they’re general purpose / configurable enough to benefit other users.

Posted in Lesson | Tagged , , , | 3 Comments

Easy to Use = People Will Use It

Including this post, since November 1 there have been 22 posts in across Beacon. Although not an earth shattering number, we’re averaging well over one entry per work day, which is significant growth over the previous platform. Additionally, there are a multiple projects in the works bringing even more people and voices to Beacon specifically, and to WordPress in general, across campus. Why do I think this is notable?

  1. People didn’t all of a sudden decide that they had things then wanted to share.
  2. The actual capability for this function (which is basically, to blog) already existed.

So, why didn’t they?

I think the biggest difference the usability of the platform. Although putting time and effort into making the backend of your system as nice as the frontend isn’t as interesting to most developers, I think that a large part of the success of the WordPress platform is due to just that type of work. We see it on a small scale here, and the biggest reason I constantly here from non-technical folk is just that it’s “easy”. This is something all developers should keep in mind, regardless of the type of project you’re working on: One “that was easy” from users is worth a day of your trying to convey all the techy awesomeness you built into your latest application. Also, people will use it, which is kinda the whole point, it’s it?

Posted in Lesson | Tagged , , , | 5 Comments

University of Pennsylvania Logo
Copyright © 2012 The Wharton School, University of Pennsylvania