Angels And Daemons
Upcoming SlideShare
Loading in...5
×
 

Angels And Daemons

on

  • 11,385 views

Tammer Saleh,

Tammer Saleh,
tsaleh@thoughtbot.com
Thoughtbot, Inc.

Statistics

Views

Total Views
11,385
Views on SlideShare
11,370
Embed Views
15

Actions

Likes
14
Downloads
128
Comments
5

1 Embed 15

http://www.slideshare.net 15

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Impressive presentation of 'Angels And Daemons'. You've shown your credibility on presentation with this slideshow. This one deserves thumbs up. I'm John, owner of www.freeringtones.ws/ . Hope to see more quality slides from you.

    Best wishes.
    Are you sure you want to
    Your message goes here
    Processing…
  • Great demonstration about the need to innovate company models; how you can represent them succinctly; along with the intent to make advancement initiatives actionable. Superb use of images and obvious to see illustrative samples.
    Anisa
    http://financejedi.com http://healthjedi.com
    Are you sure you want to
    Your message goes here
    Processing…
  • Daemons present unique problems, hence copious projects to try to solve those problems. Shameless plug: I wrote Rooster to manage dozens of daemons, and it's working great for us: http://github.com/findchris/rooster/
    Are you sure you want to
    Your message goes here
    Processing…
  • Hmm it prolly should. I haven't tried it this way. Also try the gem daemonize. RubyYarv seems to have Process.daemonize too, which is cool.
    Are you sure you want to
    Your message goes here
    Processing…
  • shouldn't this read:
    STDIN.reopen
    STDOUT.reopen etc....

    Peter Burkholder
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Angels And Daemons Angels And Daemons Presentation Transcript

  • Angels & Daemons Supporting your rails application with custom daemons Tammer Saleh, tsaleh@thoughtbot.com Thoughtbot, Inc. 1
  • Our Problem • Customer database in Rails • Needed client-side integration -- mail, address book, outlook • LDAP to ActiveRecord Gateway • Uses the ruby-ldapserver gem • Returns results from the DB 2
  • Our Solution Active Rails Record DB LDAP Server Client Server 3
  • LDAP AR Gateway http://thoughtbot.com/projects/ldap-ar-gateway http://svn.thoughtbot.com/ldap-activerecord-gateway/ But how do we make it stick around? 4
  • cups NFSd Mongrel Apache Syslog Cron Samba BackgroundDRb sshd MySQL Memcached FTPd 5
  • What’s a Daemon? Less than gods (OS & kernel), but more than mortals (programs) From the Unix FAQ: A daemon process is usually defined as a background process that does not belong to a terminal session. • No parent process • No terminal • Runs in background 6
  • Daemonizing Fork off and die def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 7
  • Daemonizing Become a session and process group leader def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 8
  • Daemonizing Fork off and Die (again) def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 9
  • Daemonizing Set umask def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 10
  • Daemonizing Change to / def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 11
  • Daemonizing Close inherited files def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 12
  • Daemonizing Reopen stdio def daemonize Kernel.fork and Kernel.exit Process.setsid Kernel.fork and Kernel.exit File.umask 0 Dir.chdir '/' ObjectSpace.each_object(IO) {|io| io.close rescue nil} STDIN.open( '/dev/null') STDOUT.open('/dev/null', 'a') STDERR.open('/dev/null', 'a') end 13
  • Daemonizing (the easy way) require 'daemons' Daemons.daemonize loop { # ..or whatever.. conn = accept_conn() serve(conn) } Daemon gem: http://daemons.rubyforge.org 14
  • Great! Now what? • Interacting with Rails • Starting and stopping your daemon • Ensuring only one is running at a time • Config files • Logging • Security 15
  • Interacting with Rails Load the Rails environment - DB, Models, Libraries, etc require quot;/path/to/config/environment.rbquot; if not defined? RAILS_ROOT raise RuntimeError, quot;Cannot load rails environmentquot; end 16
  • Interacting with Rails Threads and Rails ActiveRecord::Base.allow_concurrency = true Mysql::Error: Lost connection to MySQL server during query: 17
  • Starting & Stopping UNIX Init Scripts #!/usr/bin/env ruby basedir = File.expand_path(File.join(File.dirname(__FILE__), quot;..quot;)) require File.join(basedir, quot;libquot;, quot;serverquot;) case ARGV[0] when quot;startquot;: Server.new(ARGV[1]).start when quot;stopquot;: Server.new(ARGV[1]).stop when quot;restartquot;: Server.new(ARGV[1]).restart else puts quot;Usage: #{File.basename(__FILE__)} {start|stop|restart} argquot; end 18
  • Starting & Stopping OS X Launchd <?xml version=quot;1.0quot; encoding=quot;UTF-8quot;?> <!DOCTYPE plist PUBLIC quot;-//Apple Computer//DTD PLIST 1.0//ENquot; quot;http://www.apple.com/DTDs/PropertyList-1.0.dtdquot;> <plist version=quot;1.0quot;> <dict> <key>My Daemon</key> <string>localhost.etc.rc.command</string> <key>ProgramArguments</key> <array> <string>/path/to/daemon</string> <string>argument1</string> <string>argument2</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> http://developer.apple.com/macosx/launchd.html 19
  • Pid Files class PidFile def initialize(file) @file = file end def pid File.file?(@file) and IO.read(@file) end def remove if self.pid FileUtils.rm @file end end def create File.open(@file, quot;wquot;) { |f| f.write($$) } end end 20
  • Configuration Files rails_dir: /path/to/rails/application/ debug: false # comment complex: part1: <%= puts 'true' %> part2: false @config = YAML.load( ERB.new( File.read(quot;config_filequot;) ).result ).symbolize_keys 21
  • Logging @logger = Logger.new(quot;#{basedir}/log/server.logquot;, 7, 1048576) @logger.level = @config[:debug] ? Logger::DEBUG : Logger::INFO @logger.datetime_format = quot;%H:%M:%Squot; 22
  • Dropping Privileges def become_user(username = 'nobody', chroot = false) user = Etc::getpwnam(username) Dir.chroot(user.dir) and Dir.chdir('/') if chroot Process::initgroups(username, user.gid) Process::Sys::setegid(user.gid) Process::Sys::setgid(user.gid) Process::Sys::setuid(user.uid) end 23
  • Testing • Spawn daemon before tests • Complicated • Broken daemon may not die • Tests interfere with each other 24
  • Testing Mocking makes this much safer require quot;daemonquot; class DaemonTest < Test::Unit::TestCase include Daemon def test_should_take_steps_to_daemonize Kernel.expects(:fork).times(2).returns(true) Kernel.expects(:exit).times(2) Process.expects(:setsid) File.expects(:umask).with(0) Dir.expects(:chdir).with('/') ObjectSpace.each_object(IO) { |io| io.expects(:close) } STDIN.expects( :reopen).with(quot;/dev/nullquot;) STDOUT.expects(:reopen).with(quot;/dev/nullquot;, quot;aquot;) STDERR.expects(:reopen).with(quot;/dev/nullquot;, quot;aquot;) daemonize end end 25
  • Alternatives Rake tasks or ./script/runner • Easy to write • Launch via cron or by hand • Only good for single-shot or periodic tasks • Overlapping execution problem • Crontabs can be a pain 26
  • Alternatives nohup • Simple: just traps HUP signal • Easy to use (“nohup script.rb”) • Not as powerful or configurable 27
  • Alternatives inetd & xinetd • Easily write internet services • Get tcp wrappers for free (usually) • Not very efficient: must spawn application for each connection • Not as portable 28
  • Final Tips • Daemonize as soon in your code • Rescue anything that could fail • logger.debug with vigor 29
  • Where can we go from here? • Other ways of interfacing with your data • WebDAV • SNMP • FTP • System monitoring • Long-running or CPU intensive tasks 30
  • Where can we go from here? • Interfacing with other apps through REST • FTP Highrise • IRC Campfire proxy 31
  • Where can we go from here? Highrise to LDAP proxy http://svn.thoughtbot.com/highrise-ldap-proxy 32
  • Thanks to Thoughtbot.com Brian Candler for the ruby-ldapserver gem http://raa.ruby-lang.org/project/ruby-ldapserver/ 33