angels and daemons

33
Angels & Daemons Supporting your rails application with custom daemons Tammer Saleh, [email protected] Thoughtbot, Inc. 1

Upload: vishnu

Post on 06-May-2015

7.602 views

Category:

Technology


4 download

DESCRIPTION

Tammer Saleh, [email protected] Thoughtbot, Inc.

TRANSCRIPT

Page 1: Angels And Daemons

Angels & DaemonsSupporting your rails application with custom daemons

Tammer Saleh,[email protected]

Thoughtbot, Inc.

1

Page 2: Angels And Daemons

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

Page 3: Angels And Daemons

Our Solution

Client Server

RailsActive

Record

DB

LDAP

Server

3

Page 4: Angels And Daemons

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

http://svn.thoughtbot.com/ldap-activerecord-gateway/

But how do we make it stick around?

4

Page 5: Angels And Daemons

sshd

Mongrel

Memcached

Apache

Cron

BackgroundDRb

Syslog

MySQL

NFSd

Samba

FTPd

cups

5

Page 6: Angels And Daemons

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

Page 7: Angels And Daemons

Daemonizing

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

Fork off and die

7

Page 8: Angels And Daemons

Daemonizing

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

Become a session and process group leader

8

Page 9: Angels And Daemons

Daemonizing

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

Fork off and Die (again)

9

Page 10: Angels And Daemons

Daemonizing

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

Set umask

10

Page 11: Angels And Daemons

Daemonizing

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

Change to /

11

Page 12: Angels And Daemons

Daemonizing

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

Close inherited files

12

Page 13: Angels And Daemons

Daemonizing

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

Reopen stdio

13

Page 14: Angels And Daemons

Daemonizing

Daemon gem: http://daemons.rubyforge.org

(the easy way)

require 'daemons'Daemons.daemonize

loop { # ..or whatever.. conn = accept_conn() serve(conn)}

14

Page 15: Angels And Daemons

Great! Now what?

• Interacting with Rails

• Starting and stopping your daemon

• Ensuring only one is running at a time

• Config files

• Logging

• Security

15

Page 16: Angels And Daemons

Interacting with RailsLoad the Rails environment - DB, Models, Libraries, etc

require "/path/to/config/environment.rb"if not defined? RAILS_ROOT raise RuntimeError, "Cannot load rails environment" end

16

Page 17: Angels And Daemons

Interacting with RailsThreads and Rails

Mysql::Error: Lost connection to MySQL server during query:

ActiveRecord::Base.allow_concurrency = true

17

Page 18: Angels And Daemons

Starting & Stopping

#!/usr/bin/env rubybasedir = File.expand_path(File.join(File.dirname(__FILE__), ".."))

require File.join(basedir, "lib", "server")

case ARGV[0] when "start": Server.new(ARGV[1]).start when "stop": Server.new(ARGV[1]).stop when "restart": Server.new(ARGV[1]).restart else puts "Usage: #{File.basename(__FILE__)} {start|stop|restart} arg"end

UNIX Init Scripts

18

Page 19: Angels And Daemons

Starting & Stopping

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"> <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>

OS X Launchd

http://developer.apple.com/macosx/launchd.html

19

Page 20: Angels And Daemons

Pid Filesclass 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, "w") { |f| f.write($$) } endend

20

Page 21: Angels And Daemons

Configuration Files

rails_dir: /path/to/rails/application/debug: false # commentcomplex: part1: <%= puts 'true' %> part2: false

@config = YAML.load( ERB.new( File.read("config_file") ).result ).symbolize_keys

21

Page 22: Angels And Daemons

Logging

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

@logger.level = @config[:debug] ? Logger::DEBUG : Logger::INFO

@logger.datetime_format = "%H:%M:%S"

22

Page 23: Angels And Daemons

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

Page 24: Angels And Daemons

Testing

• Spawn daemon before tests

• Complicated

• Broken daemon may not die

• Tests interfere with each other

24

Page 25: Angels And Daemons

TestingMocking makes this much safer

require "daemon"

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("/dev/null") STDOUT.expects(:reopen).with("/dev/null", "a") STDERR.expects(:reopen).with("/dev/null", "a")

daemonize endend

25

Page 26: Angels And Daemons

Alternatives

• 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

Rake tasks or ./script/runner

26

Page 27: Angels And Daemons

Alternatives

• Simple: just traps HUP signal

• Easy to use (“nohup script.rb”)

• Not as powerful or configurable

nohup

27

Page 28: Angels And Daemons

Alternatives

• Easily write internet services

• Get tcp wrappers for free (usually)

• Not very efficient: must spawn application for each connection

• Not as portable

inetd & xinetd

28

Page 29: Angels And Daemons

Final Tips

• Daemonize as soon in your code

• Rescue anything that could fail

• logger.debug with vigor

29

Page 30: Angels And Daemons

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

Page 31: Angels And Daemons

Where can we go from here?

• Interfacing with other apps through REST

• FTP Highrise

• IRC Campfire proxy

31

Page 32: Angels And Daemons

Where can we go from here?

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

32

Page 33: Angels And Daemons

Thanks to

Thoughtbot.com

Brian Candler for the ruby-ldapserver gem

http://raa.ruby-lang.org/project/ruby-ldapserver/

33