PHP-Deployment mit Capistrano und Git

Eine umfassende Schritt für Schritt-Anleitung für die Einrichtung eines ordentlichen PHP-Deployments für Drupal.

Am Beispiel Drupal, Github und Uberspace.

Deployment, das ist der Prozess des »auf dem Server einrichtens«. Wäre es nicht schön, wenn man statt des aufwändigen manuellen Vorgangs einfach mit einer Zeile im Terminal die Computer für sich arbeiten zu lassen? Wäre es nicht noch schöner, wenn dabei gleichzeitig die Zeit in der die Website nicht erreichbar ist, minimiert wird? Und wäre es nicht das Sahnehäubchen, wenn wir bei Problemen binnen Sekunden wieder zum alten Stand zurückspringen können?

Wer den Titel gelesen hat, oder sich in Ruby/Rails-Gefilden auskennt, weiss, dass die Antwort Capistrano ist: cap deploy in die Kommandozeile, fertig.

TL;DR

Capistrano ist ein Kommandozeilen-Werkzeug für Deployments und ist so flexibel, dass es für fast jede Umgebung geeignet ist. Unsere Umgebung hier: Drupal, Github, Uberspace.

  • Wir benötigen Ruby und RubyGems.
  • Capistrano via RubyGems installieren: $ gem install capistrano.
  • Im Projekt-Ordner: $ capify ..
  • Inhalt der config/deploy.rb mit dem fertigen Rezept austauschen, Pfade, Zugangsdaten usw. anpassen.
  • Deployment auf dem Server vorbereiten: $ cap deploy:setup.
  • Server ggf. Konfigurieren. Das Verzeichnis current beinhaltet die aktuelle Version der Seite.
  • Weiterarbeiten und deployen: $ cap deploy.

Was ist Capistrano?

„Capistrano is a developer tool for deploying web applications.“

Und ein sehr flexibles noch dazu. Seinen Ursprung hat es in der Rails/Ruby-Gemeinde und es ist so flexibel, dass es ohne Probleme in fast jeder Umgebung eingesetzt werden kann. In unserem konkreten Fall ist das Drupal 6, Github und ein Live-Server bei Uberspace.

Vorraussetzungen und Einrichtung

Für unsere Vorgehensweise gehen wir davon aus, dass wir SSH-Zugriff auf den Server haben und uns mit SSH-Keys dort einloggen.

Capistrano selbst ist am einfachsten als RubyGem zu installieren:


$ gem install capistrano

Anmerkung: Zeilen, die mit einem $ beginnen sind für die Kommando-Zeile bestimmt.

Natürlich muss dafür Ruby und auch RubyGems auf dem System installiert sein. Ruby ist glücklicherweise auf vielen UNIXoiden Systemen (bei uns MacOS X) bereits installiert, und RubyGems sind fix installiert. Das installiert dann auch gleich alles Andere mit, was von Capistrano benötigt wird. Auf dem Server ist weder RubyGems noch Ruby erforderlich.

Allerdings werde ich nicht groß auf irgendwelche syntaktischen Eigenarten von Ruby eingehen, bei fragen, schreibt mir einfach auf Twitter (@nerdismus). Zum Glück ist ruby eine sehr leserliche Sprache und Vieles ist selbsterklärend.

Falls noch nicht geschehen, wechseln wir nun in das Verzeichnis, in dem unsere Webseite liegt und initialisieren Capistrano:

$ cd ~/sites/beispielSeite
$ capify .
[add] writing './Capfile'
[add] making directory './config'
[add] writing './config/deploy.rb'
[done] capified!

Capistrano legt also eine Datei Capfile im aktuellen Verzeichnis und eine Datei deploy.rb im Unterordner config an. Die deploy.rb ist die Datei, für die wir uns jetzt besonders interessieren. Die löschen wir nämlich erstmal.

Capistrano Rezept

Denn das Meiste aus der deploy.rb brauchen wir nicht. Also legen wir eine neue an und konfigurieren Capistrano:

set :domain       , "vortrieb.net"
set :user         , "vortrieb"
set :repository   , "git@github.com:Vortrieb/Vortrieb.net.git"
set :scm          , :git # git statt svn benutzen
set :branch       , "master"  # der git-branch, der deployt werden soll
set :use_sudo     , false     # auf uberspace dürfen wir natürlich kein 
                              # sudo benutzen.

Das dürfte ziemlich selbsterklärend sein. Zu beachten ist allerdings, dass :domain und :user zum Verbindungsaufbau per SSH benutzt werden.

Da wir uns mit unseren SSH-Keys einloggen wollen, um nicht bei jedem Deployment ein Passwort eingeben zu müssen, sagen wir Capistrano, dass es die Keys benutzen soll:

ssh_options[:forward_agent] = true

Und natürlich muss Capistrano noch wissen wo es die Dateien hinschieben soll.

set :deploy_to, "/var/www/virtual/#{user}/"
role :website, domain, :primary => true

Die zweite Zeile lässt bereits vermuten wie mächtig Capistrano ist. Es ist nämlich überhaupt keine großes Sache Capistrano in komlexeren Umgebungen mit verschiedenen Servern und/oder Datenbanken einzusetzen. Da wir es hier aber mit nur einem Server zu tun haben, reicht es eine Rolle :website zu definieren und als Standard festzulegen.

Als nächstes bereiten wir das Deployment auf dem Server vor.

Deployment vorbereiten

Capistrano arbeitet mit 3 Ordnern auf dem Server: current, shared und releases. Naja nicht so ganz. current ist ein Symlink auf den aktuellsten bzw. aktiven Release im releases-Ordner. Das hat den unglaublichen Vorteil, dass beim deployment die Ausfall-Zeit der Seite praktisch auf 0 reduziert wird.

Das eigentliche Deployment erfolgt in den releases-Ordner. Für jede veröffentliche Version wird darin ein weiterer Ordner mit dem aktuellen Datum als Namen angelegt. Erst wenn alle Daten vollständig hochgeladen wurden, wird der Symlink auf das neue Release geändert.

Ein weiterer Vorteil durch diese Arbeitsweise, der wirklich Gold wert ist, sind Rollbacks. Gibt es irgendwelche Probleme mit der neuen Version, kann man schnell zum vorherigen Release zurückspringen. Wieviele Releases gespeichert werden sollen, könnten wir mit set :keep_releases, 5 festlegen. 5 ist der Default-Wert und für uns völlig ausreichend.

Der shared-Ordner ist für gemeinsame Daten unter den Releases gedacht. Also zum Beispiel die User-Uploads, die bei Drupal in sites/default/files liegen und vom Deployment unangestatstet bleiben sollen.

Darum kümmern wir uns aber erst später.

$ cap deploy:setup
* executing `deploy:setup'
* executing "mkdir -p /var/www/virtual/vortrieb/test /var/www/virtual/vortrieb/test/releases /var/www/virtual/vortrieb/test/shared /var/www/virtual/vortrieb/test/shared/system /var/www/virtual/vortrieb/test/shared/log /var/www/virtual/vortrieb/test/shared/pids"
  servers: ["vortrieb.net"]
  [vortrieb.net] executing command
  command finished in 80ms
* executing "chmod g+w /var/www/virtual/vortrieb/test /var/www/virtual/vortrieb/test/releases /var/www/virtual/vortrieb/test/shared /var/www/virtual/vortrieb/test/shared/system /var/www/virtual/vortrieb/test/shared/log /var/www/virtual/vortrieb/test/shared/pids"
  servers: ["vortrieb.net"]
  [vortrieb.net] executing command
  command finished in 88ms

Mit diesem Befehl lassen wir uns von Capistrano die notwendigen Ordner anlegen und prüfen gleichzeitig, ob wir bisher alles richtig gemacht haben. Auf dem Server müssten nun die Ordner shared und releases existieren.

Ist dies der Fall, könnten wir eigentlich schon fröhlich unsere Seite mit $ cap deploy auf den Server schieben. Das sollte auch funktionieren und wer mag, kann das auch mal ausprobieren.

Aber wie bereits angemerkt, müssen wir uns noch um die gemeinsamen Dateien kümmern – und ein wenig vom Standard-Verhalten eliminieren.

Capistrano Tasks

Das Herzstück von Capistrano sind die Tasks, mit denen wir das Deployment an unsere Umgebung anpassen können. Ein Deployment lässt sich in kleine Schritte aufteilen: "Neues Release herunterladen", "Zugriffsrechte einstellen", "Datenbank migrieren", "Symlinks aktualisieren", "Server Neustarten", "Cache leeren" und was nicht noch alles.

Capistrano hat einige Standard-Tasks, die vor allem für Rails gedacht sind. Das ist eigentlich kein Problem, erzeugt für uns aber ein paar unschöne Fehlermeldungen. Wir überschreiben diese Tasks also:

namespace :deploy do
  task :set_permissions, :except => { :no_release => true } do
    # noop
  end

  task :restart do
    # noop
  end

  task :start do
    # noop
  end
end

Wer nicht so vertraut mit Ruby ist, wird das hier vermutlich etwas ungewöhnlich finden. Die do … end-Teile liessen sich auch { … } schreiben. Das sind sind sogenannte Blöcke, ein mächtiges Werkzeug aus der funktionalen Programmierung, das in Ruby quasi allgegenwärtig ist (Stichwort: Closures).

Ich erkläre hier jetzt mal nicht genauer, wie die Blöcke funktionieren, wir wollen ja heute auch noch fertig werden. Was wir hier machen ist dagegen ziemlich simpel: wir überschreiben die Default-Tasks, die zu Fehlern führten und lassen sie einfach gar nichts machen.

Unsere speziellen Tasks für Drupal fassen wir in einem eigenen Namespace zusammen:

namespace :drupal do
end

Zunächst fügen wir einen Task ein, der uns einen Unterordner files im shared-Ordner anlegt.

namespace :drupal do
  task :setup do
    run "mkdir -p #{shared_path}/files"
  end
end

shared_path enthält den Pfad zum shared-Ordner. Diesen Befehl können wir nun ausführen:

$ cap drupal:setup
* executing `drupal:setup'
* executing "mkdir -p /var/www/virtual/vortrieb/test/shared/files"
  servers: ["vortrieb.net"]
  [vortrieb.net] executing command
  command finished in 80ms

Wir können uns übrigens die verfügbaren Befehle mit $ cap -T anzeigen lassen. Jetzt kümmern wir uns noch um den sites/default/files-Ordner.

Wir müssen nach jedem Deployment einen Symlink in sites/default des aktuellen Releases anlegen, der auf den shared/files-Ordner zeigt. Das klingt zum Glück komplizierter als es ist:

task :symlink do
  run "ln -s #{shared_path}/files #{latest_release}/sites/default/files"
end

Natürlich wollen wir das nicht jedesmal von Hand eintippen, sondern das soll automatisch bei jedem Deployment erfolgen. Dazu fügen wir ausserhalb von namespace :drupal do … end folgende Zeilen ein:

after 'deploy:symlink' , 'drupal:symlink'
after 'deploy:setup'   , 'drupal:setup'

Nach jedem deploy:symlink, soll also auch ein drupal:symlink gemacht werden. Man kann es sich fast denken, before gibt es natürlich auch.

Fertig.

Bonus Level: Drupal Cache Leeren

Es liegt auf der Hand, dass man mit Capistrano auch andere Aufgaben, die nicht zwingend zum Deployment gehören, automatisieren lassen kann. Wir haben gesehen, dass wir mit run "command" ganz einfach Befehle auf dem Server ausführen können – warum nicht einen Schritt weiter gehen und zum Beispiel mit Drush den Cache auf dem Server leeren?

Achtung: wenn Du noch nie von $PATH gehört hast und nicht weisst, was es damit auf sich hat, dann könnte der folgende Teil etwas schwer zu verstehen sein. Oder gehe direkt zur Zusammenfassung

Wenn Drush auf dem Server eingerichtet ist, geht das ganz einfach:

task :clear_cache do
  run "drush cc all"
end

$ cap drupal:clear_cache
  * executing \`drupal:clear_cache'
  * executing "drush cc all"
    servers: ["vortrieb.net"]
    [vortrieb.net] executing command
*** [err :: vortrieb.net] sh: drush: command not found
    command finished in 82ms
failed: "sh -c 'drush cc all'" on vortrieb.net

Hoppla. drush wurde nicht gefunden. Ganz so einfach ist es also doch nicht?

Das Deployment soll natürlich möglichst schnell gehen, deswegen wird eine .bashrc oder .zshrc nicht geladen, sodass unser $PATH eventuell ziemlich leer ist und drush folglich nicht gefunden werden kann.

Aber zum Glück haben die Capistrano-Leute auch daran gedacht.

set :default_environment, {
  'PATH' => "$HOME/bin/:/package/host/localhost/php-5/bin/:$PATH:"
}

Die Drush-bin liegt bei uns in ~/bin/ und wir brauchen für Drush mindestens PHP 5.2.0. Wir fügen also zusätzlich noch den Pfad zur aktuellen PHP-Version hinzu, die bei Überspace in /package/host/localhost/php-5/bin/ liegt.

Das reicht leider noch immer nicht. drush muss im Root-Ordner einer Drupal-Instanz ausgeführt werden und das Working-Directory ist das, was wir unter :deploy_to angegeben haben. Also müssen wir noch ins Verzeichnis des aktuellen Releases wechseln.

Also noch einmal:

task :clear_cache do
  run "cd #{latest_release} && drush cc all"
end

$ cap drupal:clear_cache
* executing `drupal:clear_cache'
  * executing "ls -x /var/www/virtual/vortrieb/test/releases"
    servers: ["vortrieb.net"]
    [vortrieb.net] executing command
    command finished in 80ms
  * executing "cd /var/www/virtual/vortrieb/test/releases/20111208131850 && drush cc all"
    servers: ["vortrieb.net"]
    [vortrieb.net] executing command
** [out :: vortrieb.net] 
*** [err :: vortrieb.net] 'all' cache was cleared [success]

Und um das ganze abzuschliessen, leeren wir den Cache nun nach jedem Deployment.

after 'deploy', 'drupal:clear_cache'

Zusammenfassung

Wir können nun ganz normal mit unserer Versionsverwaltung weiterarbeiten und mit $ cap deploy die aktuelle Version unserer Drupal-Seite aus dem Master-Branch auf unseren Server schieben lassen.

Bei jeder neuen Drupal-Seite müssen wir nun lediglich ein paar Werte in unserer deploy.rb anpassen und schon können wir Capistrano auch dort benutzen. Und wir haben gesehen, dass es überhaupt kein Problem ist das Deployment so anzupassen, dass wir Capistrano praktisch mit jedem CMS, Framework oder dergleichen einsetzen können.

Die komplette deploy.rb mit erklärenden Kommentaren gibt es auf gist.github.

Weiterführende Links

Portraitfoto Andreas Dantz

Andreas Dantz

Andreas ist Designer und Berater für Digitales und ist spezialisiert auf Designsysteme & Apps.

Weiterlesen

Sie benötigen Hilfe mit dem Design Ihrer (Web-)App?

Dann zögern Sie nicht und lassen Sie uns herausfinden, ob eine Zusammenarbeit für Beide Seiten ein Gewinn ist.