[chef] The future of the database and application cookbooks


Chronological Thread 
  • From: Noah Kantrowitz < >
  • To: ,
  • Subject: [chef] The future of the database and application cookbooks
  • Date: Tue, 30 Aug 2011 19:39:55 -0700

As some people have noticed from my musings on IRC and elsewhere, I have 
embarked on a (probably long overdue) overhaul of the application and 
database cookbooks. This is largely orthogonal (for now) with the recent 
LWRPs added to the database cookbook, though in the end they would be 
removed. What follows is a set of syntax ideas for where I think this should 
head. Not all of this is implemented, but you can see what is in my cookbooks 
branch https://github.com/coderanger/cookbooks/tree/COOK-634.

Database clusters
=================

In the new LWRP hierarchy the top level resource is a database_cluster. This 
defines an abstract model of a single-master-multi-slave cluster setup. 
Within a cluster is one or more database_servers, each of which defines a 
single type of database service (MySQL, Postgres, Redis, Cassandra, etc) 
mapped on to the containing cluster. In general I would expect most clusters 
to only contain a single database_server block, but this isn't required and 
if you have a beefy primary box for, say, both Postgres and Redis and wanted 
to have both use the same backup machine you could put those both in the same 
cluster definition. Within a database_server you can define database and 
users, though this isn't needed for all types of servers and the exact 
implementation of what those two constructs do is left up the backend plugin 
implementing the specified server type.

With all that laying down the model, lets look at some syntax:

database_cluster "prod"
 master_role "prod_database_master"
 slave_role "prod_database_slave"

 database_server do
   type :mysql
   database "prod" do
     engine "innodb"
   end
   user "myuser" do
     password "xxx"
     grant do
       select "prod"
       insert "prod"
       update "prod"
     end
   end
 end

 database_server do
   type :redis
 end
end

This will create a cluster running a MySQL server with one database named 
prod and a Redis server (in the case of Redis there are no specific tuning 
params we need to worry about so far). This example shows a very verbose form 
of the desired syntax, below is an equivalent version using some more sugar 
and allowing for sane defaults:

database_cluster "prod"
 mysql do
   database do
     engine "innodb"
   end
   user "myuser" do
     password "xxx"
     grant "prod" do
       select
       insert
       update
     end
   end
 end

 redis
end

You can also pass Hashes to database and user instead of blocks if you want 
to feed them from an external data source (such as a data bag):

database_cluster "prod"
 mysql do
   database "prod", {:engine => "innodb"}
   user "myuser", {:password => "xxx", :grant => {:select => "prod, ...}}
 end

 redis
end

As mentioned before, the actual implementation can choose how to handle 
databases and users. In the above examples, if you added any to the redis 
section it would simply be a no-op (or maybe a runtime error?). Also as a 
structural piece, database and user definitions will only ever be processed 
on the database master (are there databases where this isn't the case and 
slaves should create users and such too?). The individual backends for this 
would live in the cookbook for that program, so the database cookbook would 
only hold the core infrastructure and plumbing to run things. The idea is 
that this resource block would be placed somewhere central and assigned to 
all servers so they can use it for reference (see below in the app cookbook 
section). Only nodes with the master or slave role would actually do 
anything. As a special case if the system detects that the master role 
actually doesn't exist it will run in a degraded mode assuming a single-node 
cluster (so obviously the current node is the master).

The database cookbook would also grow an associated knife-database plugin to 
handle replication setup/teardown as for some databases you have to perform 
an out-of-band data sync before attaching the slave.

Seen in isolation, does this seem suitable flexible to cover most needs? I am 
mostly familiar with SQL databases and a few smaller NoSQL products, so for 
the greater NoSQL world is this still a viable model?


Application deployment
======================

The model for the application LWRPs is generally simpler in terms of data, 
but is also more callback-driven. The top-level application resource just 
contains the central data used for all applications, and then any framework 
or platform specific bits are contained in sub-resources similar to the 
database_server arrangement. Below is an example of deploying the Radiant CMS:

application "radiant" do
 path "/srv/radiant"
 owner "nobody"
 group "nogroup"
 repository "git://github.com/radiant/radiant.git"
 revision "master"
 packages ["libxml2-dev", "libxslt1-dev", "libsqlite3-dev"]
 migrate true

 rails do 
   gems "bundler" => nil, "RedCloth" => "4.2.3"
   database :mysql => "radiant_production" do
     user "radiant"
     reconnect true
     encoding "utf8"
   end
   migration_command "bundle exec rake db:migrate"
 end

 unicorn do
   port 8000
 end
end

The global data maps very directly to fields in the existing application data 
bags, except that they are not Hashes indexed by environment name. In the 
rails sub-resource you see that again things mostly map to the data bag. The 
biggest exception is the database section which doesn't actually state any 
information about the database. Instead things are looked up by reference to 
the information in the database resource. It would still be possible to 
specify all information manually if you aren't using the database cookbook.

Application backends define actions as callbacks more or less, to provide 
code to execute at different points during the deploy process. The current 
callbacks are :before_compile, :before_deploy, :before_migrate, 
:before_symlink, :before_restart, and :after_restart. The last 4 just map to 
the existing callbacks in the deploy resource system. before_compile takes 
place as the first thing when the application resource executes (so when the 
provider is compiled), while before_deploy takes place during the execute 
phase in the provider, but after the main application folder structure is 
created.

The plan is to convert all the existing application cookbook recipes into a 
small wrapper just piping data bags into this LWRP structure, so they should 
continue to work as they always have.

So, what do people think? Is this a step in the right direction for 
application deployment? Do you like the idea but what a different syntax for 
it? Are there big, glaring use cases I've missed?

--Noah Kantrowitz



tl;dr Application deployment with Chef is going to be awesome!

Attachment: smime.p7s
Description: S/MIME cryptographic signature




Archive powered by MHonArc 2.6.16.

§