Production != Staging und trotzdem alles unter einem Hut

In einer idealen Welt der Entwicklung sehen natürlich Staging- und Produktivsystem völlig identisch aus, da Probleme, die im Produktivsystem auftreten würden, bereits auf dem Stagingsystem absehbar sind.

In der realen Welt ist dies jedoch längst nicht immer der Fall, sei es nun historisch bedingt, aus Kostengründen, wenn beispielsweise eine teuere kommerzielle Linuxdistribution für das Produktivsystem genutzt wird, oder weil das Produktivsystem so gar nicht abbildbar ist – Gründe gibt es genug, warum Staging- und Produktivsystem nicht so identisch sind, wie sie es seien sollten.
So könnte ein typisches Stagingsystem auf einem virtualisierten Server laufen, neben diversen anderen Stagingsystemen, und sich die Pfade völlig von denen des Produktivsystems unterscheiden. Oder auf dem Produktivsystem läuft ein Red Hat Enterprise, das aus Kostengründen nicht auf dem Stagingserver laufen kann.

Spätestens beim gewünschten Auto-deployment kommt es dann zu der in jedem Fall zu vermeidenden Situation, das zwei verschiedene Systeme für das Deployment aufgebaut werden müssen, da beide völlig andere Anforderungen haben.

Hier hat uns bisher das Deployment-Tool Capistrano geholfen, das mit der enthaltenen Stages-Erweiterung eine Möglichkeit bietet, Standardtasks (also Deployment-Schritte) mit systemspezifischen Tasks zu überschreiben.

Capistrano ist ein in Ruby geschriebenes Tool, das typische Deployment-Schritte bereits von Haus aus mitbringt und sich über eigene Tasks erweitern lässt. Dabei lädt Capistrano den Quellcode auf die Zielserver, führt das Deployment aus, und hängt anschließend nur einen Symlink um, so dass die Erreichbarkeit der Webanwendung zu keiner Zeit beeinträchtig ist.

Zwar ist Capistrano insbesondere in der Ruby-on-Rails-Community beliebt, es lässt sich jedoch auch ohne großen Aufwand für PHP-Anwendungen nutzen, um die über verschiedene Server zu verteilen.

Der Fokus dieses Artikels soll jedoch nicht auf dem Deployment der Anwendung auf mehrere Server liegen, sondern auf der Abbildung von verschiedenen Umgebungen, auf denen das Deployment ausgeführt wird.

Grundlegend funktioniert das ebenso wie auch in einem normalen Capistrano-Projekt.
Voraussetzung sind eine lauffähige Ruby-Umgebung, sowie die Ruby-Paketverwaltung Gem, über die man per

gem install capistrano

das Capistrano-Paket installiert. Anschließend lässt sich im bestehenden Projekt mit

capify .

ein neues Capistrano-Projekt anlegen. Über den Befehl wird ein Capfile angelegt, das als Einstiegspunkt für Capistrano fungiert, alle zusätzlich benötigten Komponenten lädt, sowie die eigenen Tasks aus einem angebenen Unterordner (Standard: /config/deploy/).
Für PHP-Projekte sind das die railsless-deploy-Komponenten, zu installieren per

gem install railsless-deploy

Zusätzlich die capistrano-ext-Komponenten, die die von uns benötigten Fertigkeiten der multiplen Systeme mitbringt. Installation via

gem install capistrano-ext

Ergänzend empfehle ich, die capistrano_colors-Komponente zu installieren und zu verwenden, da sie die Möglichkeit bietet, die Ausgaben nach entsprechenden Markern farbig zu gestalten, was die Nachvollziehbarkeit im Deployment-Prozess deutlich erhöht.

Das entsprechende Capfile könnte dann wie folgt aussehen:

require 'rubygems'
require 'railsless-deploy'
require 'capistrano_colors'

load 'config/deploy'

#colorize all the things
capistrano_color_matchers = [
  { :match => /command finished/,       :color => :hide,      :prio => 10 },
  { :match => /executing command/,      :color => :blue,      :prio => 10, :attribute => :underscore },
  { :match => /^transaction: commit$/,  :color => :magenta,   :prio => 10, :attribute => :blink },
  { :match => /git/,                    :color => :white,     :prio => 20, :attribute => :reverse },
  { :match => /(Start|Stopp)ing /,      :color => :yellow,    :prio => 20, :attribute => :bright },
]

colorize( capistrano_color_matchers )

Damit werden die oben genannten Komponenten in den ersten drei Zeilen geladen, sowie alle Tasks aus dem config/deploy-Unterordner in Zeile 6. Die Definition, welcher Marker wie einzufärben ist, muss man jedoch selbst vornehmen (zu sehen ab Zeile 8), was sich über die Nutzung von regulären Ausdrücken sehr einfach und flexibel gestaltet.

Mit dem Anlegen des Capistrano-Projektes wurde im config-Ordner ebenfalls eine deploy.rb-Datei angelegt, die bereits einige Dummy-Definitionen enthält, sowie etliche Kommentare. Wichtig ist, hier an erster Stelle die Zeile

require 'capistrano/ext/multistage'

einzufügen, die die Stages-Erweiterung für Capistrano lädt. Sie ermöglicht es Tasks, Werte und sogar Abfolgen von Tasks für spezifische Systeme zu überschreiben.

Zusätzlich gibt es für die Erweiterung noch die Variable :stages, die alle möglichen Stages/Systeme definiert, :default_stage, die eine zu wählende Stage angibt (wenn keine Stage beim Aufruf von Capistrano explizit übergeben wurde), sowie das Verzeichnis, unter dem die jeweiligen Konfigurationen für die Stages zu finden sind. Letzteres wird in der Variable :stage_dir angegeben.

Alle Variablen werden in Capistrano außerhalb des Namespaces über den Aufruf der set-Funktion definiert. Sie erhält als ersten Parameter den Variablennamen, mit führendem Doppelpunkt, sowie als zweiten den Inhalt der Variable. Das Ganze sähe dann in der deploy.rb-Datei wie folgt aus:

require 'capistrano/ext/multistage'

set :stages, %w(local, staging, productive)
set :default_stage, "local"
set :stage_dir, "stages"

Capistrano kann von Haus aus all das, was jedes Deployment-System immer wieder implementiert: Quellcode von einer beliebigen Ressource hochladen (das kann ein Git-Repository oder auch ein lokales Projekt sein), Datenbankmigrationen durchführen,  Services neu starten, mit Assets umgehen.

Dabei ist anzumerken, das Capistrano hier von Standard-Annahmen ausgeht, die auf PHP-Projekte nur in seltenen Fällen zutreffen. So macht es Sinn, bestimmte Standard-Tasks durch eigene zu Überschreiben. Eine Liste dieser Aufgaben findet man im Capistrano Wiki. Hilfreich ist auch der Ablaufplan von Capistrano.

Besonders deutlich wird der Nutzen der Standardvorgaben bei der Auswahl der Quelle (mit :scm gekennzeichnet). Hier können Anwender zwischen einer Vielzahl von Versionskontrollsystemen wählen, darunter Git und SVN, die selbstverständlich auch mit einer Authentifizierung per Passwort oder SSH-Schlüssel funktionieren. Die einfachste Art ist der Typ none, der genutzt werden kann, um ein Deployment von einem lokalen Ordner auszuführen (ähnlich einem SCP-Upload).

Neben der Quelle muss man durch das Setzten der Variable :deploy_via auch die Art des Transfers angeben. Für Versionsverwaltungssysteme bietet sich hier die Option :remote_cache an, die nur die Änderungen am Quellcode herunterlädt – und nicht das ganze Repository neu auscheckt, hierfür wäre :checkout zu benutzen.

Ein entsprechender Abschnitt im deploy.rb-Skript könnte wie folgt aussehen:

set :repository, "git@github.com:user/repo.git"  # Your clone URL
set :scm, "git"
set :user, "deployer"  # The server's user for deploys
set :scm_passphrase, "p@ssw0rd"  # The deploy user's password
set :branch, "master"

Schon an dieser Stelle kommt die Stages-Erweiterung zum Zug, die es unter anderem ermöglicht, für den Staging-Server den aktuellen Develop Branch zu nutzen (statt dem Master Branch). Dazu muss im angegeben stages-Ordner eine Datei gleichnamig zu der aufgerufenen Stage existieren, die alle identischen Angaben von Variablen und Tasks überschreibt.

In unserem Fall ist hier nun die Datei staging.rb im Ordner stages von Nöten, die vorerst nur

set :branch, "develop"

zum Inhalt hätte und somit statt des Master Branches den Develop Branch deployen würde. Die entsprechenden Stages werden beim Aufruf von Capistrano mit angegeben, wobei bei fehlender Angabe der Stage die in :default_stage definierte Stage genutzt würde. Ein Aufruf des Deploy-Tasks, der standardmäßig einen Container für die Ausführung aller anderen Tasks ist, sähe damit wie folgt aus:

cap staging deploy

Damit durch diesen Aufruf auch wirklich ein, wenn auch unspektakuläres, Deployment ausgeführt wird, muss man per server-Kommando noch den Zielserver angeben.

server "example.com", :app, :web, :db, :primary => true

An dieser Stelle sei nur am Rande erwähnt, dass Capistrano Möglichkeiten bietet, um zwischen verschiedenen Servertypen, wie Datenbank und Anwendungsserver, zu unterscheiden. In der Task-Definition kann man dann mit angegeben, auf welchem Server der Task ausgeführt werden soll.

Zudem bietet Capistrano die Möglichkeit, für ein rollenbasiertes Deployment eben Rollen für verschiedene Server zu definieren, was vor allem bei größeren Anwendungen, die nicht mehr auf einen Server beschränkt sind, nützlich ist.

Um den Umfang an dieser Stelle nicht zu Sprengen, sei hier auf das Blog von Railsware.com hingewiesen, das diese Möglichkeit detaillierter erklärt.

Wie zuvor erwähnt, können und sollten einige der Standard-Tasks den eigenen Bedürfnissen entsprechend überschrieben werden. Das ist auch in den jeweiligen Stages möglich, wobei die im selben Namespace wie die zu überschreibenden Tasks liegen müssen. Der vorgegebene Namespace ist hier immer :deploy.

Ein Task besteht aus einer Beschreibung, einem Namen und dem Ruby-Code, der die eigentliche Aufgabe abbildet. Für ein Backup der Datenbank könnte das etwa so aussehen:

namespace :deploy do
    desc <<-DESC
        Backup der MySQL Datenbank
    DESC
    task :db_backup do
        run "mysqldump --opt --routines --single-transaction -u  -p  | gzip -c > dump.sql.gz"
    end #task db_backuo
end #namespace deploy

Zu beachten ist, dass das run-Kommando auf dem Server ausgeführt wird. Möchte man ein Kommando lokal ausführen, nimmt man den Befehl run_locally.

Durch die Möglichkeit, seine eigenen Tasks vor oder nach einem anderem Task ausführen zu lassen, kann man sehr flexibel auf Anforderungen verschiedener Systeme reagieren. So gibt es mit der Methode before die Option, seinen eigenen Task vor einem anderen auszuführen. Das ist außerhalb des Namespaces zu definieren und dabei ist darauf zu achten, dass der vollständige Name des Tasks (bestehend aus Namespace, Doppelpunkt und Taskname) angegeben wird. Als Gegenstück existiert die after-Methode, mit der sich eine Aufgabe nach der erfolgreichen Ausführung einer anderen Aufgabe ausführen lässt.

Als kleines Beispiel sei der oben genannte Datenbank-Backup-Task vor dem Standard Datenbankmigrations-Task ausgeführt:

before "deploy:migrations", "deploy:db_backup"
namespace :deploy do
    desc <<-DESC
        Backup der MySQL Datenbank
    DESC
    task :db_backup do
        run "mysqldump --opt --routines --single-transaction -u  -p  | gzip -c > dump.sql.gz"
    end #task db_backuo
end #namespace deploy

Meiner Meinung nach ist Capistrano ein sehr flexibles Tool, das mit relativ geringem Aufwand auch schwierigen Anforderungen an ein Deployment-System gerecht wird. Der Funktionsumfang geht weit über den hier genannten hinaus, wobei das Auffinden der richtigen Funktion – aufgrund der eher schlichten Dokumentation – sehr schwierig ist. Vor allem die Magie im Hintergrund macht es oftmals schwierig zu verstehen, warum ein Problem innerhalb von Capistrano auftritt.

Nicht desto trotz kann ich nur jedem, der sich mit Deployments beschäftigt empfehlen, sich mit der Materie zu auseinanderzusetzen – mit ein wenig Handarbeit ist fast alles möglich. Ein guter Ausgangspunkt für die weitere Recherche ist hier die Capistrano Webseite.

Für neue Blogupdates anmelden:


4 Gedanken zu “Production != Staging und trotzdem alles unter einem Hut

  1. Schöner Artikel, der hoffentlich weiter zur Verbreitung von Capistrano beiträgt. Danke Leute.

    Leider enthalten die Code-Beispiele an echt vielen Stellen HTML Entities ;-)

    Man sollte generell vermeiden SCM- oder Datenbank-Passwörter in der deployment Konfiguration im Klartext einzubauen, da diese ja eventuell auch im Repository landet.

    Hierfür nutzt man in der Regel Passwort-Abfragen / Umgebungsvariablen.
    ( Beispiele hier: http://jonathan.tron.name/2006/07/15/capistrano-password-prompt-tips )

    Ein Verweis auf Capifony ( http://capifony.org/ ) für Symfony2 Deployments wäre ebenfalls sinnvoll.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.