Heroku ist eine Cloud-Plattform und war eine ganze Weile die einzige mir bekannte Plattform mit einem kostenlosen Tarif (ohne behaupten zu wollen, dass es damals nichts anderes gab). Da mir Ruby und besonders Sinatra sehr gut gefällt und Heroku für Ruby eine Weile der Cloudhoster-Vorzeigekandidat war, wollte ich schon vor einiger Zeit eine Anwendung von mir dort laufen lassen. Damals scheiterte ich - das ganze Ruby-Universum war mir noch zu neu - aber inzwischen habe ich ein anderes Projekt erfolgreich portiert. Man muss allerdings einiges beachten, was genau halte ich hier mal fest.
Alles hier gilt für Ubuntu 12.04.
Heroku einrichten
Herokus Seite ist der einfache Part. Auf https://www.heroku.com/ einen Account anlegen und der Anleitung folgen:
wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh # Heroku-Tools installieren
heroku login # einloggen und public ssh-keys hochladen
Nun der rubyspezifische Teil (diese Anleitung). Für die Verwaltung der gems muss eine Datei Gemfile im Anwendungsverzechnis angelegt werden. Der Inhalt:
source :rubygems
gem 'sinatra'
gem 'pg'
# die weiteren genutzten gems
Wer aus der Rails-Welt kommt, kennt das wohl. Die Datei dann auch nutzen:
bundle install
Außerdem fehlt noch die config.ru mit diesem Inhalt:
web: bundle exec rackup config.ru -p $PORT
Damit sind die benötigten Dateien zusammen. Dies alles nun in git festhalten und heruko übergeben:
git init
git add .
git commit -m "init"
heroku create
git push heroku master
Um später Updates an heroku zu sende, diesen Schritt ohne git init
und heroku create
ausführen
Postgresql installieren
Leider kann heroku nicht die von mir bevorzugte SQL-Datenbank nutzen, sqlite. Stattdessen muss man Postgresql nutzen, was mir eigentlich gar nicht passte - mir sind die nicht-dateibasierten Datenbank zu konfigurationslastig. So ist auch postgresql etwas knifflig einzurichten. Immerhin sollte der Wechsel der Performance der Anwendung gut tun.
Auf Heroku ist die postgresql-Datenbank schnell aktiviert:
heroku addons:add heroku-postgresql:dev
Um aber den Code weiter lokal testen zu können und den Datenbank-Remotezugriff nutzen zu können, muss Postgresql auch lokal installiert werden. Dafür installiert man das Paket postgresql. Die Datenbank muss aber noch konfiguriert werden. Am einfachsten ist dieser Weg:
sudo -u postgres createuser --superuser $USER
sudo -u postgres psql
postgres=# \password $USERNAME # des Hauptnutzers, unter dem entwickelt wird
postgres=# \q
createdb $USER
Hier wird eine Datenbank so eingerichtet, dass der Hauptnutzer sich mit einem psql in die Datenbank einwählen kann.
Datenbankcode für Postgresql anpassen
Nun muss noch der Code angepasst werden. Wer hier ein ORM nutzt, findet dafür in der Doku Code. Wer wie ich direkt mit SQL-Statements arbeiten will, der hat es etwas schwerer. Funktionierenden Beispielcode fand ich gar nicht.
Zuerst braucht man den Datenbankpfad. Ein
heroku config | grep HEROKU_POSTGRESQL
sollte einen String der Form postgres://USERNAME:PASSWORD@ec2-107-...-213.compute-1.amazonaws.com:5432/d9...rnr
zurückgeben. Diesem können nun die Datenbankdaten entnommen werden.
Im Ganzen: Ich habe eine database.rb, vorher ein Wrapper für sqlite. Vorher:
class Database
def initialize()
begin
@@db # create a singleton - if this class-variable is uninitialized, this will fail and can then be initialized
rescue
@@db = SQLite3::Database.new "rssnotifier.db"
begin
puts "creating Database"
@@db.execute "CREATE TABLE IF NOT EXISTS watches(
...
@@db.execute "PRAGMA foreign_keys = ON;"
@@db.results_as_hash = true
rescue => error
puts "error creating tables: #{error}"
end
end
end
def getPages(subscribed)
begin
pages = []
@@db.execute('SELECT DISTINCT url FROM watches WHERE subscribed = ?', subscribed ? 1 : 0) do |row|
pages.push(Page.new(row["url"]))
end
return pages
rescue => error
puts "error getting pages: #{error}"
end
end
...
Umgestellt auf postgresql:
class Database
def initialize()
db = URI.parse(ENV['DATABASE_URL'] || 'postgres://USERNAME:PASSWORD@ec2-107-...-213.compute-1.amazonaws.com:5432/d9...rnr')
@db = PG::Connection.open(:dbname => db.path[1..-1], :user => db.user, :password => db.password, :port => db.port, :host => db.host, :sslmode => 'require')
#@db = PG::Connection.open(:dbname => 'onli', :user => 'onli', :port => 5433) # lokaler Zugriff
begin
puts "creating Database"
@db.exec "CREATE TABLE IF NOT EXISTS watches(
....
rescue => error
puts "error creating tables: #{error}"
end
end
def getPages(subscribed)
begin
pages = []
@db.exec('SELECT DISTINCT url FROM watches WHERE subscribed = $1', [subscribed] ? [1] : [0]) do |results|
results.each do |row|
pages.push(Page.new(row["url"]))
end
end
return pages
rescue => error
puts "error getting pages: #{error}"
end
end
...
Eine Liste der Änderungen:
- Kein Singleton für die Verbindung nutzen! Das führte zu ziemlich widerlichen SSL-Fehlern und es dauerte ewig, bis ich darauf kam, dass es daran lag (übrigens kein Heroku-spezifischer Bug).
- Die Datenbankkonfiguration wird aus der Datenbankurl geparst (abgeleitet aus der Doku). Da habe ich mir Beispielcode (ohne ActiveRecord) in der Doku und einen schöneren Weg, ohne Parsen des Datenbankpfades, erhofft.
- exec ersetzt execute.
- Man beachte den zusätzlichen |results|-Zwischenschritt.
- (Nicht im Code) Postgresql beherrscht kein INSERT OR REPLACE. Stattdessen erst ein UPDATE, dann ein INSERT ausführen.
- Die Parameterersetzung hat eine andere Syntax: $X statt ? und Übergabe eines einzelnen Arrays statt der einzelnen Argumente.
- Den Code zum Zugriff auf die lokale Datenbank habe ich der Einfachheit halber hier festgehalten (den muss man nämlich auch erstmal finden). Man beachte den Port, der nicht der Standardport ist!
Die Anpassungen an den SQL-Queries selbst ist nicht zu schlimm, größtenteils scheint sqlite3 eine Submenge von postgresql zu sein. Bei den Datumsfunktionen bin ich dagegen direkt auf Rubycode umgestiegen.
Lohnt das?
Durchaus ein Haufen Aufwand, vor allem ohne diese Anleitung. Für meine Anwendung hat sich das ganze nichtmal gelohnt, heroku war schlicht zu langsam und die mit meiner Anwendung kommunizierenden Dienste liefen immer(!) in Timeouts. Wenn der freie Plan nichtmal zum Testen ohne jegliche Last reicht, ist das kein gutes Zeichen. Um festzustellen, an welcher Stelle die Anwendung hakt, bietet heroku mit New Relic die laut Liste nötigen Daten, ebenfalls mit einem kostenlosen Tarif - aber um den zu aktivieren, wird trotzdem eine Kreditkarte gefordert. Die ich weder besitze noch zu diesem Zeitpunkt angeben wollen würde.
Und so reizvoll die Skalierbarkeit solch einer Cloudplattform auch ist: Die Preise sind heftig. Ein eigener Server hat da echte Vorteile.
onli blogging am : Ruby/Sinatra-Anwendung auf AppFog laufen lassen
Vorschau anzeigen