Rails 5.1 Consulting und Schulung vom Autor:
www.wintermeyer-consulting.de/rails/

15.3. Webserver ohne Deployment

Bitte loggen Sie sich als root auf dem Webserver ein.

Warnung

Bitte beachten Sie bei der Verwendung dieser Installationsanleitung für andere Rails-Projekte, dass der Name "blog" (für die jeweilige Rails-Applikation) in allen Konfigurationsdateien und Skripten mit dem jeweiligen Projektnamen ersetzt werden muss.

Neues Rails-Projekt aufsetzen

Um diese Anleitung so einfach wie möglich zu halten, erstellen wir als User deployer ein einfaches Blog direkt auf dem Server.
root@debian:~# su - deployer
deployer@debian:~$ rails new blog
[...]
deployer@debian:~$ cd blog
deployer@debian:~/blog$ rails generate scaffold post subject content:text
[...]
deployer@debian:~/blog$

Gemfile anpassen

Bitte schreiben Sie in die Datei Gemfile folgenden Inhalt:
source 'https://rubygems.org'

gem 'rails', '3.2.6'

gem 'sqlite3'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', :platforms => :ruby

  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'

# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'

group :production do
  # Use MySQL as the production database
  gem 'mysql'

  # Use unicorn as the app server
  gem 'unicorn'
end
Danach mit bundle install alle Gems installieren:
deployer@debian:~/blog$ bundle install
[...]
deployer@debian:~/blog$

Production Datenbank-Konfiguration

In der Datei config/database.yml müssen wir für das Produktionssystem die Datenbank-Konfiguration für die MySQL-Datenbank eintragen. Bitte achten Sie darauf, das korrekte Passwort einzutragen.
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# 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: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: mysql
  encoding: utf8
  database: blog
  username: deployer
  password: IhrLieblingsPasswort

Unicon-Konfiguration

Für die Unicorn-Konfiguration nehmen wir als Basis die Datei https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb und speichern sie an unseren Server angepasst wie folgt in der Datei config/unicorn.rb ab:
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "deployer", "www-data"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
APP_PATH = "/var/www/blog/current"
working_directory APP_PATH

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/unicorn.blog.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid APP_PATH + "/tmp/pids/unicorn.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, some applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path APP_PATH + "/log/unicorn.blog.stderr.log"
stdout_path APP_PATH + "/log/unicorn.blog.stdout.log"

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  # Before forking, kill the master process that belongs to the .oldbin PID.
  # This enables 0 downtime deploys.
  old_pid = "/tmp/unicorn.my_site.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # the following is *required* for Rails + "preload_app true",
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "/var/www/blog/current/Gemfile"
end

rake db:migration

Wir müssen noch die Datenbank anlegen:
deployer@debian:~/blog$ rake db:migrate RAILS_ENV=production
[...]
deployer@debian:~/blog$ 

Wichtig

Bitte beachten Sie, dass rake db:migrate mit einem RAILS_ENV=production abgeschlossen wird. Nur so wird die Produktionsdatenbank migriert.

rake assets:precompile

rake assets:precompile stellt sicher, dass alle in der Asset Pipeline vorhandenen Assets für die Produktionsumgebung bereitgestellt werden (siehe Kapitel 12, Asset Pipeline).
deployer@debian:~/blog$ rake assets:precompile
/home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby /home/deployer/.rvm/gems/ruby-1.9.3-p194@global/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
deployer@debian:~/blog$

Unicorn Init-Skript

Jetzt müssen Sie als User root weiterarbeiten:
deployer@debian:~$ exit
Abgemeldet
root@debian:~# 
Erstellen Sie nachfolgend das Init-Skript /etc/init.d/unicorn_blog mit folgendem Inhalt:
#!/bin/bash

### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Unicorn webserver
# Description:       Unicorn webserver for the blog
### END INIT INFO

UNICORN=/home/deployer/.rvm/bin/bootup_unicorn
UNICORN_ARGS="-D -c /home/deployer/blog/config/unicorn.rb -E production"
KILL=/bin/kill
PID=/home/deployer/tmp/unicorn.pid

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

case "$1" in
        start)
                echo "Starting unicorn..."
                $UNICORN $UNICORN_ARGS
                ;;
        stop)
                sig QUIT && exit 0
                echo >&2 "Not running"
                ;;
        restart)
                $0 stop
                $0 start
                ;;
        status)
                ;;
        *)
                echo "Usage: $0 {start|stop|restart|status}"
                ;;
esac
Das Init-Skript muss jetzt noch scharf geschaltet und Unicorn gestartet werden:
root@debian:~# chmod +x /etc/init.d/unicorn_blog 
root@debian:~# update-rc.d -f unicorn_blog defaults
update-rc.d: using dependency based boot sequencing
root@debian:~# /etc/init.d/unicorn_blog start
root@debian:~# 
Ihr Rails-Projekt ist jetzt über die IP-Adresse des Webservers erreichbar.

nginx-Konfiguration

Für das Rails-Projekt fügen wir eine neue Konfigurationdatei /etc/nginx/conf.d/blog.conf mit folgendem Inhalt hinzu:
upstream unicorn {
  server unix:/tmp/unicorn.blog.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  # server_name example.com;
  root /home/deployer/blog/public;

  location / {
    gzip_static on;
  }

  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://unicorn;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 4G;
  keepalive_timeout 10;
}
Die Default-Konfigurationsdatei benennen wir um, damit sie nicht mehr ausgeführt wird. Danach restarten wir nginx.
root@debian:~# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.backup
root@debian:~# /etc/init.d/nginx restart
Restarting nginx: nginx.
root@debian:~#

Neue Version des Rails-Projektes einspielen

Wie die Überschrift schon sagt, haben wir bei dieser Variante keine automatischen Deployment-Mechanismen zur Verfügung. Diese können Sie sich aber leicht selbst per Skript realisieren.
Wenn Sie Veränderungen am Rails-Projekt aktivieren wollen, so müssen Sie als User deployer nach einem Update des Codes mit rake assets:precompile sicherstellen, dass alle in der Asset Pipeline vorhandenen Assets für die Produktionsumgebung wieder bereitgestellt werden (siehe Kapitel 12, Asset Pipeline).
deployer@debian:~/blog$ rake assets:precompile
/home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby /home/deployer/.rvm/gems/ruby-1.9.3-p194@global/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
deployer@debian:~/blog$
Sollten Sie neue Migrationen einspielen, müssen Sie natürlich auch rake db:migrate RAILS_ENV=production ausführen:
deployer@debian:~/blog$ rake db:migrate RAILS_ENV=production
[...]
deployer@debian:~/blog$ 
Danach müssen Sie als User root Unicorn neu starten:
root@debian:~# /etc/init.d/unicorn_blog restart
root@debian:~# 

Tipp

In den meisten Fällen ist eine Installation mit einem Capistrano-Deployment sinnvoller. Es ist initial ein Tick mehr Aufwand (siehe Abschnitt 15.4, „Webserver mit Capistrano-Deployment“), aber bietet dann mehr Komfort und Sicherheit.

Autor

Stefan Wintermeyer