Managing Memcache with Capistrano
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 are 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. #!/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. # ==================================== # 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
Language Ruby / Tagged with rails, capistrano, memcache, caching