Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Angels & Daemons
Supporting your rails application with custom daemons




                                               ...
Our Problem
• Customer database in Rails
• Needed client-side integration -- mail,
  address book, outlook
• LDAP to Activ...
Our Solution
                           Active
         Rails             Record




                            DB


    ...
LDAP AR Gateway
       http://thoughtbot.com/projects/ldap-ar-gateway

    http://svn.thoughtbot.com/ldap-activerecord-gat...
cups
NFSd
                            Mongrel
       Apache
                   Syslog
   Cron                          Sam...
What’s a Daemon?
Less than gods (OS & kernel), but more than
mortals (programs)
From the Unix FAQ:

  A daemon process is ...
Daemonizing
                   Fork off and die

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel.for...
Daemonizing
    Become a session and process group leader

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  ...
Daemonizing
               Fork off and Die (again)

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel...
Daemonizing
                       Set umask

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel.fork a...
Daemonizing
                      Change to /

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel.fork ...
Daemonizing
                 Close inherited files

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel.f...
Daemonizing
                     Reopen stdio

def daemonize
  Kernel.fork and Kernel.exit
  Process.setsid
  Kernel.fork ...
Daemonizing
                (the easy way)


        require 'daemons'
        Daemons.daemonize

        loop {
         ...
Great! Now what?
• Interacting with Rails
• Starting and stopping your daemon
• Ensuring only one is running at a time
• C...
Interacting with Rails
    Load the Rails environment - DB, Models,
    Libraries, etc

require quot;/path/to/config/envir...
Interacting with Rails
                    Threads and Rails

ActiveRecord::Base.allow_concurrency = true




Mysql::Error...
Starting & Stopping
                        UNIX Init Scripts

#!/usr/bin/env ruby
basedir = File.expand_path(File.join(Fi...
Starting & Stopping
                     OS X Launchd
<?xml version=quot;1.0quot; encoding=quot;UTF-8quot;?>
<!DOCTYPE pli...
Pid Files
class PidFile
  def initialize(file)
    @file = file
  end

  def pid
    File.file?(@file) and IO.read(@file)
...
Configuration Files
   rails_dir: /path/to/rails/application/
   debug: false # comment
   complex:
    part1: <%= puts 'tr...
Logging

@logger = Logger.new(quot;#{basedir}/log/server.logquot;, 7, 1048576)

@logger.level = @config[:debug] ? Logger::...
Dropping Privileges
def become_user(username = 'nobody', chroot = false)
  user = Etc::getpwnam(username)

  Dir.chroot(us...
Testing
• Spawn daemon before tests
 • Complicated
 • Broken daemon may not die
 • Tests interfere with each other



    ...
Testing
       Mocking makes this much safer
require quot;daemonquot;

class DaemonTest < Test::Unit::TestCase
  include D...
Alternatives
        Rake tasks or ./script/runner

• Easy to write
• Launch via cron or by hand
• Only good for single-sh...
Alternatives
                  nohup

• Simple: just traps HUP signal
• Easy to use (“nohup script.rb”)
• Not as powerful ...
Alternatives
               inetd & xinetd

• Easily write internet services
• Get tcp wrappers for free (usually)
• Not v...
Final Tips

• Daemonize as soon in your code
• Rescue anything that could fail
• logger.debug with vigor


               ...
Where can we go from
       here?
• Other ways of interfacing with your data
 • WebDAV
 • SNMP
 • FTP
• System monitoring
...
Where can we go from
       here?
• Interfacing with other apps through REST
 • FTP Highrise
 • IRC Campfire proxy




    ...
Where can we go from
       here?

   Highrise to LDAP proxy
  http://svn.thoughtbot.com/highrise-ldap-proxy




         ...
Thanks to

             Thoughtbot.com


               Brian Candler
           for the ruby-ldapserver gem
http://raa.ru...
Upcoming SlideShare
Loading in …5
×

Angels And Daemons

8,750 views

Published on

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

Published in: Technology
  • Finally found a service provider which actually supplies an essay with an engaging introduction leading to the main body of the exposition Here is the site ⇒⇒⇒WRITE-MY-PAPER.net ⇐⇐⇐
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • ⇒ www.HelpWriting.net ⇐ This service will write as best as they can. So you do not need to waste the time on rewritings.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Sex in your area is here: ❶❶❶ http://bit.ly/2F90ZZC ❶❶❶
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Dating for everyone is here: ❶❶❶ http://bit.ly/2F90ZZC ❶❶❶
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 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.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Angels And Daemons

  1. 1. Angels & Daemons Supporting your rails application with custom daemons Tammer Saleh, tsaleh@thoughtbot.com Thoughtbot, Inc. 1
  2. 2. 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
  3. 3. Our Solution Active Rails Record DB LDAP Server Client Server 3
  4. 4. 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
  5. 5. cups NFSd Mongrel Apache Syslog Cron Samba BackgroundDRb sshd MySQL Memcached FTPd 5
  6. 6. 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
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. 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
  11. 11. 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
  12. 12. 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
  13. 13. 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
  14. 14. Daemonizing (the easy way) require 'daemons' Daemons.daemonize loop { # ..or whatever.. conn = accept_conn() serve(conn) } Daemon gem: http://daemons.rubyforge.org 14
  15. 15. Great! Now what? • Interacting with Rails • Starting and stopping your daemon • Ensuring only one is running at a time • Config files • Logging • Security 15
  16. 16. 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
  17. 17. Interacting with Rails Threads and Rails ActiveRecord::Base.allow_concurrency = true Mysql::Error: Lost connection to MySQL server during query: 17
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. Testing • Spawn daemon before tests • Complicated • Broken daemon may not die • Tests interfere with each other 24
  25. 25. 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
  26. 26. 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
  27. 27. Alternatives nohup • Simple: just traps HUP signal • Easy to use (“nohup script.rb”) • Not as powerful or configurable 27
  28. 28. 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
  29. 29. Final Tips • Daemonize as soon in your code • Rescue anything that could fail • logger.debug with vigor 29
  30. 30. 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
  31. 31. Where can we go from here? • Interfacing with other apps through REST • FTP Highrise • IRC Campfire proxy 31
  32. 32. Where can we go from here? Highrise to LDAP proxy http://svn.thoughtbot.com/highrise-ldap-proxy 32
  33. 33. Thanks to Thoughtbot.com Brian Candler for the ruby-ldapserver gem http://raa.ruby-lang.org/project/ruby-ldapserver/ 33

×