Wednesday, April 23, 2008

Schritt für Schritt Anleitung: Ruby On Rails Deployment mit Capistrano, Mongrel, Apache, Subversion und Dir

Note to English speaking readers: I've written this tutorial in German because there are a lot of good Rails deployment tutorials out there in English, but not so many in German. If enough people email me or comment and say they want this in English I might translate it ;-).

So, jetzt auf Deutsch: Du willst deine grossartige nagelneu programmierte Ruby On Rails Anwendung fuer die Welt (oder immerhin fuer deinem Kunden) nutzbar machen. Dann will ich dich nicht aufhalten und dir in 27 einfachen Schritten zeigen, wie es geht. Dann wollen wir mal...


  1. Log dich mit ssh in den Server ein, auf dem die Rails Applikation laufen soll.

  2. Wenn du dich nicht als root angemeldet hast, werde jetzt root (su -)

  3. Stell sicher, dass ein Editor wie vi richtig funktioniert. Wenn man von Mac OS X auf einen Linux Rechner geht kann es manchmal Probleme geben. Auf Debian Systemen schafft da oft

    apt-get install ncurses-term

    Abhilfe.

  4. Aktualisiere die verfuegbaren Pakete:

    apt-get update

  5. Installiere folgende Pakete:
    apt-get install mysql-server
    apt-get install ruby
    apt-get install ruby1.8-dev
    apt-get install rubygems
    apt-get install subversion
    apt-get install make
    apt-get install build-essential
    apt-get install curl
    apt-get install apache2
  6. Installiere folgende Ruby Gems:
    gem install --include-dependencies rails
    gem install --include-dependencies capistrano
    gem install --include-dependencies mongrel (jeweils die neuste ruby version nehmen)
    gem install --include-dependencies mongrel_cluster

  7. Gehe nochmal sicher, dass alles aktuell ist:

    gem update --system

    Wenn du irgendwo den Fehler

    /usr/bin/gem:23: uninitialized constant Gem::GemRunner (NameError)

    bekommst ist das damit zu loesen, folgende Zeile in /usr/bin/gem einzufuegen:

    require 'rubygems/gem_runner'

    Und um den Fehler

    undefined method `require_gem' for main:Object

    spaeter beim Deployment zu vermeiden solltest du nochmal

    gem install --remote rake

    ausfuehren. Wenn das nicht hilft, in /var/lib/gems/1.8/bin/rake

    require_gem 'rake', version

    in

    gem 'rake', version

    abaendern.

  8. Jetzt musst du die Datenbank einrichten. Wir nehmen hier MySQL und der Einfachheit halber nenne ich in dieser Anleitung die Datenbank, den Datenbank Benutzer und die Rails-Anwendung "springenwerk".

    Root Passwort setzen:

    mysqladmin -u root password ein_passwort_meiner_wahl

    (danach am besten die bash_history loeschen) Dann die Datenbank anlegen und die Berechtigung setzen:

    mysql -u root -p
    CREATE DATABASE springenwerk;
    GRANT ALL PRIVILEGES ON springenwerk.* TO 'springenwerk'@'localhost' IDENTIFIED BY 'ein_password_meiner_wahl' WITH GRANT OPTION;

    Die Angaben fuer die Produktions-Datenbank (Name, Username und Passwort) muessen den Angaben in der app/config/database.yml Datei der Rails Anwendung entsprechen.

    Jetzt kannst du den Mysql Client mit 'exit' verlassen.

  9. Die ausfuehrbaren Ruby Gems willst du jetzt natuerlich gerne in deinem Pfad haben. Also
    in der /etc/profile
    /var/lib/gems/1.8/bin
    an die PATH Variable haengen. Das kann man so machen:

    PATH=$PATH:/var/lib/gems/1.8/bin

    Du musst nur aufpassen, dass die das vor die Zeile "export PATH" schreibst.

  10. Dann die Aenderungen in der aktuellen Session nutzbar machen:

    source /etc/profile

    Wenn du dann

    rails -v

    ausfuehren kannst hat alles geklappt.

  11. Jetzt solltest du einen eigenen User fuer deine Rails Anwendung anlegen:

    useradd -m springenwerk

    und das Passwort setzten:

    passwd springenwerk

    Und ihn zu den sudoers packen:

    visudo

    und dann unten folgende Zeile hinzufuegen (natuerlich "springenwerk" wieder mit deinem Produktions-Usernamen ersetzen):

    springenwerk ALL=(ALL) ALL

  12. Da sich Capistrano per SSH einloggt aber auch ein gewisse Benutzerumgebung braucht (z.B. den PATH) musst du in die /etc/ssh/sshd_config folgende Zeilen einfuegen:
    # for capistrano
    PermitUserEnvironment yes
  13. Dann SSHD neu starten:

    /etc/init.d/ssh restart
  14. In /home/springenwerk/.ssh/environment (ggf. ".ssh" Verzeichnis und Datei anlegen) folgende Zeilen einfuegen:
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/var/lib/gems/1.8/bin
    RUBYOPT=rubygems
  15. Jetzt musst du auf dem CLIENT Capistrano und Mongrel Cluster installieren (ich gehe davon aus, dass Ruby und Rails schon installiert sind)
    sudo gem install capistrano
    sudo gem install capistrano-ext
    sudo gem install mongrel_cluster

    Ab Mac OS X Leopard ist capistrano schon dabei, aber stelle mit

    sudo gem update capistrano
    sicher, dass du mindestens Version 2.1 hast.

  16. Wechsle in dein Rails Projekt Verzeichnis:

    cd /Users/johannes/Code/springenwerk/trunk/springenwerk
  17. Jetzt erstellst du eine Konfigurationsdatei fuer den Mongrel Cluster:

    mongrel_rails cluster::configure -e production -p 8000 -a 127.0.0.1 -N 2 -c /home/springenwerk/springenwerk/current

    -p 8000 sagt, dass die Server ab Port 8000 lauschen, -N 2 sagt, dass es zwei Instanzen geben wird (also eine auf Port 8000, eine auf 8001) und mit -c gibt man das Verzeichnis an, wo auf dem PRODUKTIONSSERVER die Applikation liegen wird (in diesem Fall ein Unterverzeichnis, das so heisst wie die Applikation im Home-Verzeichnis des Users, den wir angelegt haben (welcher auch so heisst wie die Applikation) und mit dem Zusatz "current". Das musst da stehen, denn das
    ist ein Symlink, den Capistrano anlegt und der immer auf die aktuelle Version zeigt.

  18. Jetzt musst du dein Rails Projekt capistranofizieren ;-). Wechsle in dein Rails Projekt Verzeichnis:

    cd /Users/johannes/Code/springenwerk/trunk/springenwerk

    und fuehre capify aus:

    capify .

  19. Oeffne jetzt config/deploy.rb in deinem Editor und passe die Datei so an, dass sie so aussieht (mit deinen User- und Projektnamen und Passwoertern natuerlich):

    require 'mongrel_cluster/recipes'

    default_run_options[:pty] = true # to make sure password prompts are forwarded to you, the user

    set :application, "springenwerk"
    set :repository_url, "svn://DEIN_SVN_USER@DEIN_SVN_SERVER/springenwerk/trunk/springenwerk"

    if ENV['svn_prompt'] and ENV['svn_prompt'] == '1'
    set :svn_user, Proc.new { Capistrano::CLI.password_prompt('SVN User: ') }
    set :svn_password, Proc.new { Capistrano::CLI.password_prompt('SVN Password: ') }
    set :repository, Proc.new { "--username #{svn_user} --password #{svn_password} #{repository_url}" }
    else
    set :repository, repository_url
    end

    set :deploy_to, "/home/#{application}/#{application}" # defaults to "/u/apps/#{application}"
    set :deploy_via, "export"
    set :user, application # in our case, the user and the application have the same name.

    set :mongrel_conf, "#{current_path}/config/mongrel_cluster.yml"

    role :app, "DEIN_PRODUCTION_SERVER"
    role :web, "DEIN_PRODUCTION_SERVER"
    role :db, "DEIN_PRODUCTION_SERVER", :primary => true

    # Task to copy production db config after deployment
    task :after_update_code, :roles => :app do
    db_config = "#{shared_path}/config/database.yml.production"
    run "cp #{db_config} #{release_path}/config/database.yml"
    end

    Die Datei werde ich hier nicht gross erklaeren, dazu gibt es andere Stellen.

    Diese deploy.rb Datei hat aber eine kleine Besonderheit (Danke an Jonathan).
    Da Subversion natuerlich einen Benutzernamen und ein Passwort erwartet wird normalerweise empfohlen, einmal irgendwo auf dem Produktionsserver das Rails Projekt auszuchecken, damit die Credentials gecachet werden und Capistrano nicht mehr nach SVN Benutzernamen und Passwort fragt. Das koennen wir uns jetzt sparen.
    Wir koennen naemlich jetzt angeben, dass Capistrano uns beim ersten Mal einfach nach den Zugangsdaten fuer SVN fragt. Dazu gleich mehr.


  20. Jetzt kann Capistrano die Verzeichnisse auf dem Produktionsserver anlegen. Auf deinem CLIENT fuehre folgendes aus:

    cap deploy:setup
  21. Stelle die neuen Konfigurationsdateien unter Versionskontrolle und checke sie ein:

    svn add Capfile config/mongrel_cluster.yml config/deploy.rb
    svn ci -m "capistrano config"
  22. Du willst aus Sicherheitsgruenden deine Produktionsdatenbank Passwoerter nicht ins SVN einchecken. Deshalb solltest du eine Datei namens database.yml.production anlegen, die die
    Zugangsdaten fuer deine Produktionsdatenbank enthaelt. Unser "after_update_code" Task in deploy.rb erwartet diese Datei und kopiert sie dann ueber die ausgecheckte database.yml. Zum Beispiel koennte sie so aussehen:

    development:
    adapter: mysql
    database: springenwerk_development
    username: root
    password: root
    host: localhost
    socket: /Applications/MAMP/tmp/mysql/mysql.sock

    # Warning: The database defined as 'test' will be erased and
    # re-generated from your development database when you run 'rake'.
    # Do not set this db to the same as development or production.
    test:
    adapter: mysql
    database: springenwerk_test
    username: root
    password: root
    host: localhost

    production:
    adapter: mysql
    database: springenwerk
    username: springenwerk
    password: DEIN_PASSWORT
    host: localhost
    socket: /var/run/mysqld/mysqld.sock

    Diese packst du auf dem Produktionsserver in folgendes Verzeichnis:

    /home/springenwerk/springenwerk/shared/config
    Das config Verzeichnis musst du vorher anlegen.

  23. Jetzt kannst du mit Capistrano von deinem CLIENT ein Erst-Deployment (cold deployment) durchfuehren (in deinem Rails Projektverzeichnis)

    svn_prompt=1 cap deploy:cold
    "svn_prompt" ist einen Umgebungsvariable, die unserem deploy.rb Skript sagt, dass es nach den SVN Zugangsdaten fragen soll. Denke daran, dass wenn Capistrano dich nach dem Passwort fuer deinen Produktionsserver fragt das Passwort fuer den nicht-root User (in diesem Beispiel "springenwerk") fragt!

    Achte darauf, dass es keine Fehler gibt, am Ende sollte

    command finished
    stehen.

  24. Pruefe auf dem SERVER, ob die Mongrel Dienste laufen:

    curl -I http://127.0.0.1:8000
    Wenn Mongrel laeuft, sollte die Ausgabe ungefaehr so aussehen:

    HTTP/1.1 302 Moved Temporarily
    Connection: close
    Date: Wed, 06 Feb 2008 11:42:33 GMT
    Set-Cookie: _springenwerk_session_id=f4a9b408f36d0df7561fd778e46d2c62; path=/
    Status: 302 Found
    Location: http://127.0.0.1:8000/session/new
    Cache-Control: no-cache
    Server: Mongrel 1.1.3
    Content-Type: text/html; charset=utf-8
    Content-Length: 101

    Sehr gut. Jetzt musst du nur noch Apache als Loadbalancer einrichten. Ein Kinderspiel ;-)

  25. Ok, Apache. Bevor du die folgenden Sachen machst, ein kleiner Disclaimer: ich bin kein Apache-Config-Profi. Es kann gut sein, dass es elegantere Wege gibt, das gute Stueck zu konfigurieren. Alle Hinweise die zur Ergreifung des Taeters fuehren werden sehr geschaetzt.

    Ans Ende von /etc/apache2/apache2.conf folgendes anfuegen (im vi mit G ans Ende der Datei springen):

    <Proxy balancer://mongrel_cluster>
    BalancerMember http://127.0.0.1:8000
    BalancerMember http://127.0.0.1:8001
    </Proxy>
    Der Inhalt der /etc/apache2/sites-available/000-default Datei muss so aussehen (ich gehe in dem Beispiel davon aus, dass der Apache nur fuer deine Rails Anwendung benutzt wird):

    <VirtualHost *:80>
    ServerAdmin webmaster@localhost

    DocumentRoot /home/springenwerk/springenwerk/current/public
    <Directory /home/springenwerk/springenwerk/current/public>
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    allow from all
    </Directory>

    RewriteEngine On

    RewriteLog /var/log/apache2/rewrite.log
    RewriteLogLevel 9

    RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
    RewriteCond %{SCRIPT_FILENAME} !maintenance.html
    RewriteRule ^.*$ /system/maintenance.html [L]

    RewriteRule ^/$ /index.html [QSA]

    RewriteRule ^([^.]+)$ $1.html [QSA]

    RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f

    RewriteRule ^/(.*)$ balancer://mongrel_cluster%{REQUEST_URI} [P,QSA,L]

    ErrorLog /var/log/apache2/error.log

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn

    CustomLog /var/log/apache2/access.log combined
    ServerSignature Off

    </VirtualHost>
  26. Dann musst du in /etc/apache2/mods-enabled folgende Symlinks anlegen:

    ln -s ../mods-available/proxy_balancer.load
    ln -s ../mods-available/proxy.conf
    ln -s ../mods-available/proxy_http.load
    ln -s ../mods-available/proxy.load
    ln -s ../mods-available/rewrite.load

    Der Inhalt der proxy.conf muss so aussehen:

    <IfModule mod_proxy.c>
    #turning ProxyRequests on and allowing proxying from all may allow
    #spammers to use your proxy to send email.

    ProxyRequests Off

    <Proxy *>
    AddDefaultCharset off
    Order allow,deny
    Allow from all
    </Proxy>

    # Enable/disable the handling of HTTP/1.1 "Via:" headers.
    # ("Full" adds the server version; "Block" removes all outgoing Via: headers)
    # Set to one of: Off | On | Full | Block

    ProxyVia On
    </IfModule>

  27. Dann nur noch Apache neu starten:

    /etc/init.d/apache2 restart

  28. Feierabend. Das wars.


Wenn du jetzt eine neue Version deiner Anwendung live stellen willst, musst du nur in deinem Rails Projektverzeichnis

cap deploy

bzw

cap deploy:migrations

ausfuehren wenn es neue Datenbankmigrationen gibt. Capistrano macht dann den Rest!

Bitte benutze die Kommentarfunktion wenn du beim Folgen dieser Schritte ein paar Tipps oder Probleme gefunden hast.
Grosser Dank gilt noch dem grossartigen Tutorial von Coda. Da gibt es noch sehr viel Hintergrundinfos. Ich wollte das hier nicht alles wiederholen, sondern es eher kurz und knapp halten. Ich hoffe, dass es ganz hilfreich ist.

0 comments: