bust a cap in dat ass
atmos : February 18th, 2007
Capistrano memcached tasks
Capistrano rocks for deployment. If you haven’t ever used it to deploy an app, you’re seriously missing out. There’s a lot of good recipes floating around online and this stuff changes so often that there’s probably something out there that’s better. However I figured I’d share how I’m currently managing our memcache daemons at work.
memcached, rails wtf?
We’re using defunkt’s cache_fu plugin right now to cache models in our system. It’s pretty straightforward to use; an acts_as class method, and a config/memcached.yml file. The memcached.yml file lets you provide one or more servers that the memcache clients can connect to. Unfortunately there’s no centralized way to manage those memcache daemons on your deploy hosts. With our good friend cap, some ruby, and a little time invested we can roll our own solution.
Whatcha Want?
The requirements for this task are dead simple.
- use cap to get out to the remote hosts
- the memcache daemon list comes from cache_fu’s yml file
- write a script to start, stop, restart, get the status of, and commit mass genocide against(I want all processes kill -9’d right now).
It turned out to be a pretty simple hack; parse the yaml, get the host’s ip address, see if the host is in the server list, if it is do whatever command line operation we were given. Check it out.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#!/usr/bin/env ruby # this goes in your script/ directory # it parses your memcached.yml file and hooks you up w/ some info # it keeps you from having to mess w/ stale memcached daemons for whatever reason. require 'yaml' class MemcachedCtl attr_accessor :memcached, :memory, :pids, :servers, :ip_address, :ethernet_device def initialize env = ENV['RAILS_ENV'] || 'development' self.memcached = `which memcached`.chomp self.servers = [ ] self.pids = { } self.ethernet_device = ENV['ETH'] || 'eth0' self.ip_address = get_ip_address || '0.0.0.0' self.memory = '128' config = YAML.load_file(File.expand_path(File.dirname(__FILE__) + "/../config/memcached.yml")) self.servers = [ config['defaults']['servers'] ].flatten rescue ['127.0.0.1:11211'] self.servers = [ config[env]['servers'] ].flatten if config[env]['servers'] self.servers.reject! { |server| host,port = server.split(/:/); self.ip_address == host } self.memory = config[env]['memory'] unless config[env]['memory'].nil? each_server do |host,port| `ps auwwx | grep memcached | grep '\\-l #{ip_address} \\-p #{port}' | grep -v grep`.split(/\n/).each do |line| self.pids[port] = line.split(/\s+/)[1] end self.pids[port] ||= 'Down' end end def execute(cmd) send(cmd) end def restart; stop; sleep 1; start; end def status each_server { |host,port| puts "Port #{port} -> #{pids[port] =~ /\d+/ ? 'Up' : 'Down'}" } end def kill each_server { |host,port| `kill -9 #{pids[port]} > /dev/null 2>&1` if pids[port] =~ /\d+/ } end def stop; kill; end def start each_server do |host,port| `#{memcached} -d -m #{memory} -l #{ip_address} -p #{port}` STDERR.puts "Try memcached_ctl status" unless $? == 0 end end protected def each_server(&block) servers.each do |server| host,port = server.split(/:/) yield host, port end end def get_ip_address # this works on linux you might have to tweak this on other oses line = `/sbin/ifconfig #{ethernet_device} | grep inet | grep -v inet6`.chomp if line =~ /\s*inet addr:((\d+\.){3}\d+)\s+.*/ self.ip_address = $1 end end end ########################################################################### cmd = ARGV.shift unless cmd.nil? MemcachedCtl.new.execute(cmd) end |
Get Your Cap On
So I named the script memcached_ctl and added it to my script/ directory in svn and redeployed. Now I can start poppin caps in our memcache daemons. If I throw the following code at the bottom of my config/deploy.rb I can see what’s up with my memcache daemons on my production hosts. If I were smart/motivated I’d make a gem and let you good people include these tasks, you know like you should be doing for mongrel_cluster.
1 2 3 4 5 6 7 8 9 |
# ==================================== # Memcached Server TASKS # ==================================== %w(start stop restart kill status).each do |cmd| desc "#{cmd} your memcached servers" task "memcached_#{cmd}".to_sym, :roles => :app do run "RAILS_ENV=production #{ruby} #{current_path}/script/memcached_ctl #{cmd}" end end |
Checkout how easy it is to work with my memcache daemons now.
mpro% cap memcached_status
* executing task memcached_status
* executing "RAILS_ENV=production /opt/local/bin/ruby /home/production/Sites/oracle/current/script/memcached_ctl status"
servers: ["10.2.1.29", "10.2.1.30"]
[10.2.1.29] executing command
[10.2.1.30] executing command
** [out :: 10.2.1.29] Port 11212 -> Up
** [out :: 10.2.1.30] Port 11212 -> Up
command finished
mpro% cap memcached_stop
* executing task memcached_stop
* executing "RAILS_ENV=production /opt/local/bin/ruby /home/production/Sites/oracle/current/script/memcached_ctl stop"
servers: ["10.2.1.29", "10.2.1.30"]
[10.2.1.29] executing command
[10.2.1.30] executing command
command finished
mpro% cap memcached_status
* executing task memcached_status
* executing "RAILS_ENV=production /opt/local/bin/ruby /home/production/Sites/oracle/current/script/memcached_ctl status"
servers: ["10.2.1.29", "10.2.1.30"]
[10.2.1.29] executing command
[10.2.1.30] executing command
** [out :: 10.2.1.29] Port 11212 -> Down
** [out :: 10.2.1.30] Port 11212 -> Down
command finished
mpro% cap memcached_start
* executing task memcached_start
* executing "RAILS_ENV=production /opt/local/bin/ruby /home/production/Sites/oracle/current/script/memcached_ctl start"
servers: ["10.2.1.29", "10.2.1.30"]
[10.2.1.29] executing command
[10.2.1.30] executing command
command finished
mpro% cap memcached_status
* executing task memcached_status
* executing "RAILS_ENV=production /opt/local/bin/ruby /home/production/Sites/oracle/current/script/memcached_ctl status"
servers: ["10.2.1.29", "10.2.1.30"]
[10.2.1.29] executing command
[10.2.1.30] executing command
** [out :: 10.2.1.29] Port 11212 -> Up
** [out :: 10.2.1.30] Port 11212 -> Up
command finished
bust your own caps.
I really dig this approach because I can deploy all sorts of apps without ever logging in and starting things manually. Things that you bounce less(like your memcacheds) are even bigger candidates for automation. At any given time I can tell you whether they’re up or down, and I can restart them in a few seconds if you so desire. This kind of approach, while really simple, works really well with Capistrano. So get out there are start bustin caps.
Sorry, comments are closed for this article.