Skalierbare und robuste Anwendungen erstellen

Ein wesentlicher Bestandteil jeder Anwendungsarchitektur ist die Entwicklung von Webanwendungen, die sowohl robust als auch skalierbar sind. Eine gut konzipierte Anwendung sollte sich bei steigender und abnehmender Nachfrage nahtlos skalieren lassen und robust genug sein, um den Verlust einer oder mehrerer Computing-Ressourcen zu überstehen.

Dieses Dokument richtet sich an Systems-Operations-Profis, die mit Compute Engine vertraut sind. In diesem Dokument erfahren Sie, wie Sie mithilfe der Google Cloud Platform (GCP) sowie von Mustern und Vorgehensweisen, die für fast jede Anwendung gelten, skalierbare und robuste Anwendungsarchitekturen erstellen. Sie erfahren auch, wie Sie diese Prinzipien auf reale Szenarien anwenden. Dazu dient die Bereitstellung des beliebten Open-Source-Projektmanagementtools Redmine, eine Ruby-on-Rails-basierte Anwendung, als Beispiel. Im Abschnitt Beispiellösung bereitstellen haben Sie die Möglichkeit, die Anwendung selbst bereitzustellen und alle Quellcodes zum Nachlesen herunterzuladen.

Auf der GCP können Sie skalierbare und robuste Webanwendungen erstellen sowie ausführen. Mit Diensten wie Compute Engine und Autoscaling können Sie die Ressourcen Ihrer Anwendung je nach Bedarf anpassen. Gemäß dem Compute Engine-Preismodell bezahlen Sie außerdem pro Sekunde und erhalten mit Rabatten für kontinuierliche Nutzung automatisch den besten Preis ohne komplizierte Kapazitäts- oder Reservierungsplanung.

Eine allgemeine Übersicht über Ihre Optionen für das Webhosting auf der GCP finden Sie unter Websites bereitstellen.

Skalierbarkeit und Robustheit definieren

Vor der Beschreibung einer beispielhaften Anwendungsarchitektur sollen zuerst die Begriffe Skalierbarkeit und Robustheit definiert werden.

Skalierbarkeit: Kapazitäten an Nachfrage anpassen

Eine skalierbare Anwendung funktioniert mit nur einem Nutzer genauso gut wie mit einer Million Nutzern. Sie passt sich automatisch den Schwankungen des Traffics an. Durch die Möglichkeit, virtuelle Maschinen je nach Bedarf hinzuzufügen und zu entfernen, nutzen skalierbare Anwendungen immer nur die Ressourcen, die für den jeweiligen Bedarf erforderlich sind.

Das folgende Diagramm zeigt, wie eine skalierbare Anwendung auf Anstieg oder Rückgang des Bedarfs reagiert.

Ein Diagramm, das zeigt, wie Ressourcen nach Bedarf skaliert werden.

Beachten Sie, dass die Kapazität sich dynamisch an die Veränderungen der Nachfrage anpasst. Mit dieser Konfiguration, die auch als "Designelastizität" bezeichnet wird, bezahlen Sie immer nur die Rechenressourcen, die Ihre Anwendung zu einer bestimmten Zeit auch wirklich benötigt.

Robustheit: immer auf das Unerwartete vorbereitet

Eine hoch verfügbare oder robuste Anwendung funktioniert auch dann weiter, wenn bei Systemkomponenten erwartete oder unerwartete Fehler auftreten. Sollte eine einzelne Instanz ausfallen oder ein Fehler in einer gesamten Zone auftreten, bleibt eine robuste Anwendung fehlertolerant – sie funktioniert weiterhin und repariert sich automatisch selbst, falls notwendig. Da zustandsorientierte Informationen nicht in einer einzelnen Instanz gespeichert sind, wirkt sich der Ausfall einer einzelnen Instanz oder sogar einer ganzen Zone nicht auf die Leistung der Anwendung insgesamt aus.

Eine wirklich robuste Anwendung erfordert sowohl in der Softwareentwicklung als auch auf Ebene der Anwendungsarchitektur viel Planung. Dieses Dokument bezieht sich größtenteils auf die Ebene der Anwendungsarchitektur.

Folgendes gehört in der Regel zum Entwurf einer Anwendungsarchitektur für eine robuste Anwendung:

  • Load-Balancer, um Server zu überwachen und den Traffic auf die Server zu verteilen, die Anfragen am besten bearbeiten können
  • Hosting von virtuellen Maschinen in mehreren Regionen
  • Konfiguration einer robusten Speicherlösung

GCP: flexibel und kostengünstig

Für herkömmliche Architekturen, die Skalierbarkeit und Robustheit unterstützen, sind oft hohe Investitionen in Ressourcen erforderlich. Bei lokalen Lösungen führt Skalierbarkeit häufig dazu, dass die Entscheidung zwischen zwei Möglichkeiten getroffen werden muss: entweder zu hohe Ausgaben für Serverkapazitäten, die an die maximale Nutzungsrate angepasst sind, oder an durchschnittlichen Nutzungswerten orientierte Ausgaben, die das Risiko schlechterer Anwendungsleistung und Nutzererfahrungen zu Traffic-Spitzenzeiten mit sich bringen. Robustheit hängt jedoch von mehr als nur Serverkapazitäten ab – auch der Standort spielt eine wichtige Rolle. Um die Auswirkungen physischer Ereignisse wie schwerer Stürme oder Erdbeben zu begrenzen, sollten Sie Ihre Server an verschiedenen physischen Standorten betreiben. Dies kann zu hohen Kosten führen.

Die GCP bietet eine Alternative: eine Reihe von Clouddiensten als flexible Möglichkeit, Ihre Architektur skalierbar und robust zu machen. Darüber hinaus stellt die GCP diese Dienste mit einer Preisstruktur bereit, mit der Sie die Kosten kontrollieren können.

Robuste und skalierbare Architekturen mit der GCP aufbauen

Die folgende Tabelle zeigt Ihnen, wie sich verschiedene GCP-Dienste den wichtigsten Voraussetzungen zuordnen lassen, die für das Erstellen skalierbarer und robuster Anwendungen gegeben sein müssen.

Architekturvoraussetzung GCP-Dienst
Load-Balancing HTTP-Load-Balancing
Serverhosting Compute Engine, Regionen und Zonen
Servermanagement Instanzvorlagen, verwaltete Instanzgruppen, Autoscaling
Datenspeicherung Cloud SQL, Cloud Storage

Die folgende Grafik zeigt, wie diese GCP-Komponenten durch ihr Zusammenspiel eine skalierbare, robuste Anwendung bilden. Die Rollen der einzelnen Komponenten werden im nächsten Abschnitt genauer beschrieben.

Ein Diagramm, das eine skalierbare und robuste Anwendung zeigt

Komponenten

Jede Komponente in der Beispiel-Anwendungsarchitektur trägt dazu bei, dass die Anwendung sowohl skalierbar als auch robust ist. Im folgenden Abschnitt werden diese Dienste zuerst einzeln beschrieben. In späteren Abschnitten wird ihr Zusammenwirken erläutert.

HTTP-Load-Balancer

Der HTTP-Load-Balancer enthält eine einzige öffentliche IP-Adresse, über die Kunden auf die Anwendung zugreifen können. Diese IP-Adresse kann mit einem DNS-A-Datensatz (z. B. example.com) oder einem CNAME (z. B. www.example.com) verknüpft sein. Eingehende Anfragen werden in den Zonen je nach Kapazität der einzelnen Gruppen auf die Instanzgruppen verteilt. In der Zone werden Anfragen gleichmäßig auf die Instanzen in der Gruppe verteilt. Der HTTP-Load-Balancer kann den Traffic auch zwischen mehreren Regionen ausgleichen. In dieser Beispiellösung wird er aber in einer einzelnen Region mit mehreren Zonen verwendet.

Zone

Eine Zone ist ein isolierter Ort innerhalb einer Region. Zonen haben Netzwerkverbindungen mit hoher Bandbreite und niedriger Latenz zu anderen Zonen in derselben Region. Google empfiehlt, Anwendungen in jeweils mehreren Zonen einer Region bereitzustellen.

Instanz

Eine Instanz ist eine virtuelle Maschine, die in der Infrastruktur von Google gehostet wird. Sie können Instanzen wie physische Server installieren und konfigurieren. In der Beispielbereitstellung verwenden Sie Startskripts und das Tool Chef zum Konfigurieren von Instanzen mit dem Anwendungsserver und Code für die Webanwendung.

Instanzvorlage

Eine Instanzvorlage definiert den Maschinentyp sowie Image, Zone, Labels und andere Instanzattribute. Mithilfe einer Instanzvorlage können Sie eine verwaltete Instanzgruppe erstellen.

Verwaltete Instanzgruppe

Eine verwaltete Instanzgruppe enthält einheitliche Instanzen, die auf einer bestimmten Instanzvorlage basieren. Sie können für eine verwaltete Instanzgruppe ein HTTP-Load-Balancing-Modul einrichten, mit dem die Arbeitslast auf die Instanzen in der Gruppe verteilt wird. Eine verwaltete Instanzgruppe hat eine entsprechende Instanzgruppenmanager-Ressource für das Hinzufügen und Entfernen von Instanzen in der Gruppe.

Autoscaling

Mit dem Compute Engine-Autoscaling werden einer verwalteten Instanzgruppe durch Verknüpfung mit dem Gruppenmanager Compute Engine-Instanzen hinzugefügt oder sie werden daraus entfernt. Dies erfolgt in Abhängigkeit von Schwankungen des Traffics und der CPU-Auslastung sowie aufgrund anderer Signale. In unserer Beispiellösung reagiert das Autoscaling auf den RPS-Messwert (Requests per Second, Anfragen pro Sekunde) des HTTP-Load-Balancers. Autoscaling ist für jede Instanzgruppe erforderlich, die automatisch skaliert werden soll.

Cloud SQL

Cloud SQL ist ein vollständig verwalteter Datenbankdienst, der sowohl MySQL als auch PostgreSQL unterstützt. Replikation, Verschlüsselung, Patches und Sicherungen werden von Google verwaltet. Eine Cloud SQL-Instanz wird in einer einzelnen Zone bereitgestellt und die Daten werden automatisch in andere Zonen repliziert. Die Anwendung Redmine in diesem Beispiel ist mit MySQL kompatibel und funktioniert problemlos mit Cloud SQL.

Cloud Storage

Mit Cloud Storage können Objekte (in der Regel Dateien) über eine einfache und skalierbare Oberfläche gespeichert und abgerufen werden. Für diese Lösung werden mit einem Cloud Storage-Bucket private SSL-Schlüssel an die skalierbaren Compute Engine-Instanzen verteilt. Darüber hinaus werden damit alle in die Redmine-Anwendung hochgeladenen Dateien gespeichert, d. h., es werden keine zustandsorientierten Informationen auf den Laufwerken einer Instanz gespeichert.

Robustheit

Damit diese Beispielarchitektur robust ist, muss sie Instanzen, die ausgefallen oder nicht mehr verfügbar sind, automatisch ersetzen. Wenn eine Instanz online geht, sollte sie:

  • ihre Rolle im System kennen
  • sich automatisch selbst konfigurieren
  • mögliche Abhängigkeiten erkennen
  • Anfragen automatisch verarbeiten

Für das automatische Ersetzen einer fehlerhaften Instanz können Sie mehrere Compute Engine-Komponenten kombiniert verwenden:

Ein Startskript wird ausgeführt, wenn Ihre Instanz gestartet oder neu gestartet wird. Mit diesen Skripts können Sie Software und Updates installieren, um sicherzustellen, dass Dienste auf der virtuellen Maschine ausgeführt werden, oder auch ein Konfigurationsverwaltungstool wie Chef, Puppet, Ansible oder Salt installieren.

In diesem Szenario wird Chef Solo mit einem Startskript installiert. Das Tool konfiguriert wiederum Instanzen so, dass sie mit der Anwendung zusammenarbeiten. Am Ende dieses Themas erfahren Sie unter Anhang: Neue Instanz hinzufügen, wie Sie mit Startskripts und Chef Solo Instanzen automatisch konfigurieren.

Zusätzlich zu einem Startskript müssen Sie noch weitere Elemente definieren, bevor Sie eine Compute Engine-Instanz starten. So müssen Sie zum Beispiel den Maschinentyp sowie das zu verwendende Betriebssystem-Image und alle Laufwerke, die Sie hinzufügen möchten, angeben. Diese Optionen legen Sie mithilfe einer Instanzvorlage fest.

Gemeinsam definieren eine Instanzvorlage und ein Startskript, wie eine Compute Engine-Instanz gestartet und wie die Software für diese Instanz so konfiguriert wird, dass sie eine bestimmte Funktion in Ihrer Anwendungsarchitektur erfüllen kann.

Ein Diagramm, das das Zusammenwirken von Startskripts, Instanzvorlagen und Instanzen zeigt.

Dabei bleibt eine Instanzvorlage natürlich nur eine Vorlage. Damit sie wirksam wird, müssen Sie einen Weg finden, sie auf neue Compute Engine-Instanzen anzuwenden, die online gehen. Dazu erstellen Sie eine verwaltete Instanzgruppe. Sie legen die Anzahl der Instanzen fest, die zu einer bestimmten Zeit ausgeführt werden sollen, und geben an, welche Instanzvorlage auf diese Instanzen angewendet werden soll. Ein Instanzgruppenmanager ist dann für das Starten und Konfigurieren dieser Instanzen nach Bedarf zuständig.

Die folgende Grafik zeigt, wie diese Komponenten zusammenspielen:

  • Startskript
  • Instanzvorlage
  • Instanzgruppenmanager
  • Verwaltete Instanzgruppe
Ein Diagramm, das das Zusammenwirken von Startskripts, Instanzvorlagen, Instanzgruppenmanagern und verwalteten Instanzgruppen zeigt.

Bei einer verwalteten Instanzgruppe und beim zugehörigen Instanzgruppenmanager kann es sich um zonenspezifische oder regionale Ressourcen handeln. Eine Instanzvorlage ist eine Ressource auf Projektebene, die für mehrere verwaltete Instanzgruppen in einer beliebigen Zone und Region verwendet werden kann. Sie haben aber auch die Möglichkeit, in einer Instanzvorlage zonale Ressourcen anzugeben, was die Verwendung der Vorlage dann auf die Zone beschränkt, in der sich die zonalen Ressourcen befinden.

Mit Startskripts, Instanzvorlagen und verwalteten Instanzgruppen haben Sie nun ein System, mit dem fehlerhafte Instanzen durch neue ersetzt werden können. Im nächsten Abschnitt wird eine Möglichkeit beschrieben, eine fehlerhafte Instanz zu definieren und zu erkennen.

Systemdiagnosen

Die Beispielanwendung verfügt nun über fast alle notwendigen Tools für einen robusten Zustand. Eine Eigenschaft fehlt jedoch noch: Sie muss in der Lage sein, fehlerhafte Instanzen zu erkennen, um sie ersetzen zu können.

Diese Anwendung ist so konzipiert, dass Nutzer mithilfe eines HTTP-Load-Balancers auf eine geeignete, fehlerfreie Instanz zugreifen können. Mit dieser Architektur können Sie zwei Dienste zum Ermitteln von Instanzen nutzen, die in der Lage sind, Anfragen zu übernehmen:

  • Systemdiagnosen. Eine HTTP-Systemdiagnose gibt auf jeder Instanz den Port und den Pfad an, auf dem die Systemdiagnose ausgeführt werden soll. Die Systemdiagnose erwartet von einer fehlerfreien Instanz die Antwort 200 OK.
  • Back-End-Dienste. Ein Back-End-Dienst definiert eine oder mehrere Instanzgruppen, die Traffic von einem Load-Balancer empfangen. Der Back-End-Dienst gibt den Port und das Protokoll an, die von den Instanzen bereitgestellt werden (z. B. HTTP-Port 80), sowie die HTTP-Systemdiagnose, die auf die Instanzen in der Instanzgruppe angewendet werden soll.

Die folgende Grafik stellt die Anwendungsarchitektur dar und zeigt, wie ein Back-End-Dienst und eine HTTP-Systemdiagnose mit dem Load-Balancer und der Instanzgruppe zusammenhängen.

Ein Diagramm, das Systemdiagnose- und Back-End-Services zeigt.

Datenrobustheit mit Cloud SQL

Die drei Hauptbereiche jeder Anwendungsarchitektur sind Netzwerk, Computing und Speicherung. Die hier beschriebene Anwendungsarchitektur enthält bereits die Netzwerk- und Computing-Komponenten, es fehlt aber noch die Speicherkomponente.

Diese Beispiellösung verwendet Cloud SQL-Instanzen der ersten Generation für die Bereitstellung einer vollständig verwalteten MySQL-Datenbank. Mit Cloud SQL verwaltet Google automatisch Replikation, Verschlüsselung, Patchmanagement und Sicherungen.

Cloud SQL-Datenbanken sind flächendeckend, d. h., Daten werden innerhalb einer Region zonenübergreifend repliziert. Das ist in etwa so, als würde bei jeder Aktualisierung Ihrer Daten eine Sicherung erstellt werden. Auch im unwahrscheinlichen Fall eines kompletten Ausfalls einer Zone bleiben so alle Daten erhalten.

Mit Cloud SQL können Sie zwischen zwei Replikationstypen wählen:

  • Synchrone Replikation. Bei einer synchronen Replikation werden Aktualisierungen in mehrere Zonen kopiert, bevor sie zum Client zurückkehren. Das macht die Anwendung im Notfall zuverlässig und verfügbar, dafür sind die Schreibvorgänge langsamer.
  • Asynchrone Replikation. Die asynchrone Replikation erhöht den Durchsatz für Schreibvorgänge, da Schreibvorgänge bestätigt werden, sobald sie lokal im Cache gespeichert werden, bevor die Daten in andere Standorte kopiert werden. Eine asynchrone Replikation führt zu schnelleren Schreibvorgängen in der Datenbank, da nicht auf den Abschluss der Replikation gewartet werden muss. Allerdings ist es möglich, dass die letzten Aktualisierungen verloren gehen. Dies kann in dem unwahrscheinlichen Fall vorkommen, dass ein Systemfehler im Rechenzentrum in den ersten Sekunden nach der Aktualisierung der Datenbank auftritt.

Die in dieser Lösung verwendete Redmine-Anwendung verwendet die synchrone Replikation, da die Arbeitslast nicht schreibintensiv ist. Sie können zwischen synchroner und asynchroner Replikation wählen, abhängig von den Anforderungen an Schreibleistung und Langlebigkeit der Daten der Anwendung.

Skalierbarkeit

In den vorherigen Abschnitten wurde gezeigt, wie die Beispielanwendung mithilfe der GCP eine robuste Anwendung erstellt. Robustheit allein reicht jedoch nicht aus – Skalierbarkeit ist ebenfalls wichtig. Die Anwendung sollte sowohl für einen als auch für eine Million Nutzer gut funktionieren und ihre Ressourcen sollten je nach Anzahl der Nutzer zu- oder abnehmen, um kostengünstig zu bleiben.

Damit die Ressourcen zu- oder abnehmen können, benötigt die Anwendung:

  • ein Mittel, um Instanzen dem Dienst hinzuzufügen oder wieder daraus zu entfernen. Sie muss ebenfalls entscheiden können, wann eine Instanz hinzugefügt und wann eine Instanz entfernt werden sollte. Das Autoscaling der GCP löst dieses Problem.
  • eine Möglichkeit, zustandsorientierte Daten zu speichern. Da Instanzen kommen und gehen, ist es nicht empfehlenswert, zustandsorientierte Daten in diesen Instanzen zu speichern. Die Anwendungsarchitektur löst dieses Problem für relationale Daten, indem diese in einer separaten Cloud SQL-Instanz gespeichert werden. Allerdings muss es auch eine Lösung für Dateien geben, die von Nutzern hochgeladen werden. Cloud Storage erfüllt diese Anforderung.

In den folgenden Abschnitten wird beschrieben, wie Sie das Autoscaling zum Skalieren der Infrastruktur verwenden, auf der die Redmine-Anwendung ausgeführt wird, und wie Sie Cloud Storage für hochgeladene Dateien nutzen.

Mit Autoscaling skalieren

Bei schwankender Nutzung der Anwendung muss die Anwendung die erforderlichen Ressourcen dynamisch anpassen. Diese Anforderung können Sie mit Compute Engine-Autoscaling lösen.

Nehmen Traffic oder Lasten zu, fügt Autoscaling Ressourcen hinzu, um die zusätzliche Aktivität zu verarbeiten, und entfernt zwecks Kosteneinsparung Ressourcen, wenn Traffic oder Lasten abnehmen. Autoscaling führt diese Aktionen automatisch im Rahmen der von Ihnen definierten Skalierungsregeln aus und ohne dass Sie zusätzlich eingreifen müssen.

Autoscaling wirkt sich in doppelter Hinsicht aus:

  1. Ihre Nutzer profitieren mit Ihrer Anwendung von optimaler Nutzerfreundlichkeit, da immer ausreichend Ressourcen für ihre Anforderungen vorhanden sind.
  2. Sie selbst haben mehr Kontrolle über Ihre Kosten, da Autoscaling Instanzen entfernt, wenn die Nachfrage unter einen bestimmten Schwellenwert fällt.

Autoscaling skaliert die Anzahl virtueller Maschinen je nach CPU-Auslastung, Bereitstellungskapazität oder einem Stackdriver Monitoring-Messwert. Diese Lösung greift auf den Messwert für Bereitstellungskapazität zurück, um Compute Engine-Instanzen je nach Anzahl der RPS, die die Instanzen vom Load-Balancer erhalten, hinzuzufügen oder zu entfernen. Weitere Informationen finden Sie unter Batchverarbeitung mit Compute Engine-Autoscaling.

Anfragen pro Sekunde (RPS)

In den vorherigen Abschnitten wurde ein einzelner Back-End-Dienst beschrieben, der die Instanzgruppen bestimmt, die den Traffic vom Load-Balancer erhalten sollen. Für jede mit dem Back-End-Dienst verknüpfte Instanzgruppe legt die Beispiellösung balancingMode=RATE fest. Dieses Attribut weist den Load-Balancer an, das Load-Balancing jeweils anhand des RPS-Wertes vorzunehmen, der im Attribut maxRatePerInstance festgelegt ist – in diesem Beispiel 100. Diese Konfiguration bedeutet, dass der Load-Balancer versucht, jede Instanz auf oder unter 100 RPS zu halten. Weitere Informationen zu den Konfigurationsattributen eines Back-End-Dienstes
finden Sie in der Dokumentation zu den Back-End-Diensten.

Für eine Skalierung nach RPS-Wert müssen Sie für jede Instanzgruppe, die automatisch skaliert werden soll, ein Autoscaling erstellen. In diesem Beispiel ist die Instanzgruppe eine zonenspezifische Ressource. Daher müssen Sie für jede Zone ein Autoscaling erstellen.

Ein Diagramm, das zeigt, wie Autoscaling in die Architektur einer Anwendung passt

Jedes Autoscaling hat ein Attribut utilizationTarget, das den Anteil an der maximalen Bereitstellungskapazität des Load-Balancers festlegt, den das Autoscaling aufrechterhält. In diesem Beispiel wird der Wert des Attributs utilizationTarget des Autoscaling auf 80 % der maximalen Rate des Back-End-Dienstes von 100 RPS für jede Instanz festgelegt. Das bedeutet, dass das Autoscaling skaliert, sobald der RPS 80 % der maximalen Rate pro Instanz überschreitet, was 80 RPS entspricht. Das Autoscaling skaliert herunter, wenn der RPS-Wert unter diesen Grenzwert fällt.

Ein Flussdiagramm, dass zeigt, wie Autoscaling bestimmt, ob eine Instanz hinzugefügt oder entfernt werden soll

Jedes Autoscaling definiert auch eine minimale und eine maximale Anzahl von Instanzen, die das Autoscaling nicht unter- bzw. überschreiten kann.

Beachten Sie, dass Autoscaling-Funktionen nur für verwaltete Instanzgruppen verwendet werden können. Weitere Informationen finden Sie unter Instanzgruppen und Autoscaling von Instanzgruppen.

Dateiuploads verarbeiten

Zum Funktionsumfang der Redmine-Anwendung gehört, dass angemeldete Nutzer Dateien hochladen und speichern können. Redmine und viele vergleichbare Webanwendungen speichern diese Dateien standardmäßig direkt auf lokalen Laufwerken. Diese Lösung ist in Ordnung, wenn Sie nur einen Server mit einem klar definierten Sicherungsmechanismus haben. Es ist jedoch nicht der optimale Ansatz, wenn sich hinter einem Load-Balancer mehrere automatisch skalierte Compute Engine-Instanzen befinden. Wenn ein Nutzer eine Datei hochlädt, gibt es keine Garantie dafür, dass die nächste Anfrage bei der Maschine ankommt, auf der die Datei gespeichert ist. Es ist ebenfalls nicht gewährleistet, dass eine überflüssige Instanz, auf der die Datei gespeichert ist, nicht vom Autoscaling entfernt wird.

Eine bessere Lösung stellt Cloud Storage dar. Es bietet einen zentralen Ort, an dem Dateiuploads durch eine automatisch skalierte Gruppe von Webservern gespeichert werden und von dem aus auf die Uploads zugegriffen werden kann. Cloud Storage enthält außerdem eine API, die mit Amazon S3-Clients interoperabel ist. Damit ist es ohne Änderungen mit vorhandenen Anwendungs-Plug-ins für S3, einschließlich des Redmine S3-Plug-ins, kompatibel. Viele Drittanbieter- und Open-Source-Anwendungen haben Plug-ins, die Objektspeicher wie Cloud Storage unterstützen. Wenn Sie Ihre eigene Anwendung erstellen, können Sie die Cloud Storage API direkt für das Speichern von Dateien verwenden.

Das folgende Diagramm zeigt den Ablauf für den Upload (blaue Pfeile) und das Abrufen (grüne Pfeile) von Dateien mit Redmine und Cloud Storage:

Ein Diagramm, das den Lauf von Anfragen durch die Anwendung Redmine zeigt.

Folgender Prozess wird in der Grafik dargestellt:

  1. Der Nutzer POSTet die Datei aus einem Webbrowser.
  2. Das Lastenausgleichsmodul wählt eine Instanz aus, um die Anfrage zu verarbeiten.
  3. Die Instanz speichert die Datei in Cloud Storage.
  4. Die Instanz speichert Dateimetadaten (z. B. Name, Eigentümer und Speicherort in Cloud Storage) in der Cloud SQL-Datenbank.
  5. Wenn ein Nutzer eine Datei anfordert, wird die Datei von Cloud Storage in eine Instanz gestreamt.
  6. Die Instanz sendet den Stream durch den Load-Balancer.
  7. Die Datei wird an den Nutzer gesendet.

Speicherkapazität

Cloud Storage entfernt nicht nur zustandsorientierte Dateiuploads von Compute Engine-Instanzen und lässt sie dynamisch skalieren, sondern bietet auch redundanten, dauerhaften Speicher für eine praktisch unbegrenzte Anzahl von Dateiuploads. Diese Speichermöglichkeit ist robust, skalierbar und kostengünstig – Sie bezahlen nur für den Speicher, den Sie nutzen, ohne sich um Kapazitätsplanung kümmern zu müssen. Außerdem werden Daten automatisch zonenübergreifend redundant gespeichert.

Kosten

Bisher wurde mit der in diesem Dokument beschriebenen Anwendungsarchitektur gezeigt, wie mithilfe der GCP eine robuste und skalierbare Anwendung erstellt werden kann. Die Frage ist nun, wie die Kosten dafür kontrolliert werden können.

In diesem Abschnitt wird gezeigt, dass die in diesem Dokument beschriebene Anwendungsarchitektur nicht nur robust und skalierbar ist, sondern auch eine hohe Kosteneffizienz aufweist. Als Erstes werden einige allgemeine Annahmen getroffen, wie viel und wie oft die Anwendung genutzt wird. Anschließend werden diese Annahmen in eine einfache Kostenschätzung umgewandelt. Beachten Sie, dass es sich dabei ausschließlich um Annahmen handelt. Sie können diese Zahlen an die erwartete Nutzung Ihrer eigenen Anwendungen anpassen.

Berechnung

Ein wichtiger Aspekt jeder Anwendungsarchitektur ist die Höhe der Betriebskosten der Server. Dieser Kostenanalyse liegen folgende Annahmen zugrunde:

Messwert Wert
Durchschnittliche Seitenaufrufe pro Monat 20.000.000
Durchschnittliche HTTP-Anfragen pro Monat 120.000.000
Spitzenzugriffszeiten (90 % oder höher) Montags bis freitags zwischen 7:00 Uhr und 18:00 Uhr
Datenübertragung pro Seitenaufruf 100 KB
Spitzenzeiten pro Monat (Stunden) 220
Anfragerate zu Spitzenzeiten 127 Anfragen/Sekunde (RPS)
Anfragerate außerhalb der Spitzenzeiten 6 Anfragen/Sekunde (RPS)

Von diesen Annahmen ausgehend können Sie ausrechnen, wie viele Seitenaufrufe die Anwendung während der Spitzenzeiten von Montag bis Freitag zwischen 7:00 Uhr und 18:00 Uhr jeden Monat erhält:

20.000.000 (Aufrufe/Monat) * 6 (Anfragen/Aufruf) * 90 % (zu Spitzenzeiten eingehend) = 108.000.000

Jeden Monat gibt es durchschnittlich 22 Arbeitstage. Wenn jeder Arbeitstag elf Spitzenstunden enthält, müssen Sie ausreichend Rechenressourcen bereitstellen, um 242 Spitzenstunden im Monat bewältigen zu können.

Als Nächstes müssen Sie festlegen, welche Art von Compute Engine-Instanz diese Art von Traffic verarbeiten kann. Diese Anwendungsarchitektur wurde mithilfe von gatling.io einfachen Belastungstests unterzogen. Die Ergebnisse dieser Tests ergaben, dass vier Compute Engine-Instanzen des Typs n1-standard-1 ausreichen.

Außerhalb der Spitzenzeiten werden bei dieser Lösung mindestens zwei Instanzen vom Typ n1-standard-1 ausgeführt.

Die aktuellen Preisschätzungen des GCP-Preisrechners geben Ihnen Auskunft darüber, wie viel die Ausführung der Instanzen kosten wird. Beachten Sie dabei, dass für diese Instanzen in beiden Fällen automatisch ein Rabatt für kontinuierliche Nutzung angesetzt wird.

Load-Balancing und Datenübertragung

Diese Anwendung stellt einen Load-Balancer mit einer einzelnen Weiterleitungsregel bereit. Dies ist die öffentliche IP-Adresse, mit der Nutzer eine Verbindung herstellen. Diese Weiterleitungsregel wird pro Stunde abgerechnet.

Für Datenübertragungsschätzungen sollten Sie zuerst vom ungünstigsten Fall ausgehen. Beim Lastenausgleichsmodul fallen Kosten für verarbeitete eingehende Daten an und normale Raten für vom Lastenausgleichsmodul ausgehenden Traffic. Dabei wird angenommen, dass 99,5 % der 120.000.000 HTTP-Anfragen von Nutzern eingehen, die eine Redmine-Projektseite laden. Wenn eine Seite geladen wird, zählt das als eine HTTP GET-Anfrage, die dazu führt, dass fünf weitere HTTP GET-Anfragen andere Inhalte laden (CSS, Bilder und jQuery). Für das Laden einer ganzen Seite sind also sechs HTTP-Anfragen notwendig. Das Ergebnis:

  • Ca. 20.000.000 komplette Seiten geladen pro Monat
  • Rund 10 KB an verarbeiteten eingehenden Daten und 450 KB an Datenübertragungen pro Seite
  • Gesamtmenge von ca. 214 GB Daten, die vom Load-Balancer pro Monat verarbeitet werden, und 9.091 GB ausgehendem Traffic

Die restlichen 0,5 % der 20.000.000 HTTP-Anfragen sind HTTP POST-Anfragen, um eine Datei durchschnittlicher Größe (ca. 0,5 MB) für zusätzliche 500 GB verarbeitete Daten pro Monat hochzuladen.

Diese Schätzung des GCP-Preisrechners zeigt die erwarteten Kosten für die 714 GB an Datenübertragungen an, die der Load-Balancer verarbeiten würde. Dazu kommen 9.091 GB ausgehender Traffic für dieses Szenario.

Diese Schätzung der Datenübertragung betrifft den ungünstigsten Fall, weil dabei der gesamte Inhalt – einschließlich statischer Ressourcen – für jede Anfrage von einer Compute Engine-Instanz und über einen Load-Balancer ohne Caching oder Content Delivery Network (CDN) bereitgestellt wird. Von den ca. 450 KB Nutzlast für jeden Seitenaufruf werden 333 KB dieser Nutzlast benötigt, um jQuery zu laden – wobei diese Lösung von mehr als 20 Millionen Seitenaufrufen pro Monat ausgeht. Wenn Sie nur eine Zeile der Anwendung ändern, um jQuery aus von Google gehosteten Bibliotheken zu laden, reduzieren sich die Datenübertragungskosten um 73 %.

Diese aktualisierte Preisschätzung zeigt, wie viel an Datenübertragung eingespart werden kann, wenn Sie zu von Google gehosteten Bibliotheken wechseln.

Speicherung

Diese Lösung verwendet Cloud Storage für alle Dateien, die mit der Redmine-Anwendung hochgeladen werden. Wie im vorherigen Abschnitt beschrieben, machen Dateiuploads ca. 0,5 % dieser Nutzung aus. Die durchschnittliche Größe der Uploads beträgt ca. 0,5 MB pro Datei. Das bedeutet, dass 1.000.000 neue Dateiuploads jeden Monat zu erwarten sind, was zu zusätzlichen 500 GB Speicher pro Monat führt. Diese Lösung geht außerdem von 1.000.000 HTTP PUT-Vorgängen pro Monat aus, um neue Dateien zu speichern. Dies wird als Vorgang der Klasse A abgerechnet.

Diese Preisschätzung des GCP-Preisrechners zeigt die erwarteten Kosten für die Verwendung von Cloud Storage an.

Diese Architektur verwendet Cloud SQL, um alle relationalen Daten für die Anwendung zu speichern. Von den oben beschriebenen Beispielmesswerten ausgehend, sollte der Datenbanktyp D2 mit 1.024 MB RAM ausreichend Kapazität für die Arbeitslast der Anwendung bieten. Die Datenbank soll rund um die Uhr zur Verfügung stehen. Da diese Datenbank höchstwahrscheinlich sehr viel genutzt wird, ist die Option Heavy (Hoch) bei E/A-Vorgängen im Preisrechner zu empfehlen. Für diese Beispielarchitektur wurde ein Test erstellt, bei dem 100.000 Dokumente eingefügt wurden, mit dem Ergebnis, dass ein 50-GB-Laufwerk mehr als 100.000.000 Dokumente unterstützt. Damit kann die Datenbank zur genannten Rate mehr als acht Jahre genutzt werden.

Diese Schätzung des GCP-Preisrechners zeigt die erwarteten Kosten für diesen Teil der Architekturkosten an.

Beispiellösung bereitstellen

Gehen Sie zum GitHub-Repository Skalierbare und robuste Anwendungen auf der GCP, um die Beispielanwendung aus dieser Lösung bereitzustellen.

Anhang: Neue Instanz hinzufügen

Wenn Sie eine robuste und skalierbare Anwendungsarchitektur erstellen möchten, müssen Sie festlegen, wie neue Instanzen hinzugefügt werden sollen. Insbesondere müssen Sie angeben, wie neue Instanzen automatisch konfiguriert werden sollen, wenn sie online gehen.

In diesem Abschnitt werden einige verfügbare Optionen beschrieben.

Bootstrapping von Softwareinstallationen

Zur Reaktion auf die Webanfrage eines Nutzers benötigt jede Instanz zusätzlich zum grundlegenden Betriebssystem weitere Software und Konfigurationsdaten. Die Konfigurationsdaten enthalten die Informationen zur Datenbankverbindung und den Namen des Cloud Storage-Buckets, in dem die Dateien gespeichert sind. Wenn Sie sich diese Komponenten als Schichten vorstellen, können Sie den gesamten Stapel visualisieren, der auf jeder Instanz ausgeführt wird.

Ein Diagramm, dass den Softwarestapel einer Instanz zeigt.

Diese Lösung verwendet eine Instanzvorlage, die das Compute Engine-Image festlegt, das Instanzen beim Start nutzen. Dabei wird insbesondere das von Canonical entwickelte und unterstützte Ubuntu 14.10-Image verwendet. Da dies das grundlegende Betriebssystem-Image ist, enthält es keine Software oder Konfiguration, die von der Anwendung benötigt wird.

Damit der Rest des Stacks automatisch installiert wird, wenn neue Instanzen hinzugefügt werden, können Sie eine Kombination aus Compute Engine-Startskripts und Chef Solo für ein Bootstrapping zur Startzeit verwenden. Sie haben die Möglichkeit, ein Startskript festzulegen. Fügen Sie hierzu ein Metadaten-Attributelement startup-script zu einer Instanzvorlage hinzu. Ein Startskript wird ausgeführt, wenn die Instanz gestartet wird.

Dieses Startskript leistet Folgendes:

  1. Es installiert den Chef-Client.
  2. Es lädt die besondere Chef-Datei node.json herunter. Diese Datei weist Chef an, welche Konfiguration für diese Instanz ausgeführt werden soll.
  3. Es führt Chef aus und sorgt dafür, dass es sich um die detaillierte Konfiguration kümmert.

Hier ist das gesamte Startskript:

#! /bin/bash

# Install Chef
curl -L https://www.opscode.com/chef/install.sh | bash

# Download node.json (runlist)
curl -L https://github.com/googlecloudplatform/... > /tmp/node.json

# Run Chef
chef-solo -j /tmp/node.json -r https://github.com/googlecloudplatform/...

Anwendungskonfigurationen bereitstellen

Wenn eine neue Instanz gestartet ist und sich selbst mithilfe des Startskripts und Chef konfiguriert hat, benötigt sie noch einige Informationen, um Anfragen verarbeiten zu können. In diesem Beispiel benötigt jede Instanz die Informationen zur Datenbankverbindung, z. B. den Hostnamen, den Nutzernamen und das Passwort sowie den Namen des zu verwendenden Cloud Storage-Buckets und die Anmeldedaten zum Herstellen der Verbindung.

Jede Compute Engine-Instanz hat Metadatenattribute, die Sie definieren können. Sie haben bereits erfahren, wie das spezielle Metadatenattribut startup-script hinzugefügt wird. Sie können aber auch beliebige Schlüssel/Wert-Paare einfügen. Hier haben Sie die Möglichkeit, Attribute in der Instanzvorlage anzugeben und damit Konfigurationsdaten festzulegen, die die Instanzen für die Herstellung der Verbindung zur Datenbank und zum Cloud Storage-Bucket benötigen.

So sehen die Metadaten einer Instanzvorlage in der GCP Console aus:

Ein Screenshot der Google Cloud Platform Console mit den benutzerdefinierten Metadaten für eine Instanzvorlage.

Chef verwendet ein Tool namens Ohai, um diese Konfigurationsinformationen aus den Metadaten der Instanz zu parsen und damit Vorlagen für das Erstellen der für die Anwendung erforderlichen Konfigurationsdateien auszufüllen. Hier sehen Sie die Vorlage, die die Datei database.yaml mit den Informationen zur Datenbankverbindung erstellt und automatisch auf die relevanten Metadatenelemente zugreift:

production:
    adapter: mysql2
    database: <%= node['gce']['instance']['attributes']['dbname'] %>
    host:     <%= node['gce']['instance']['attributes']['dbhost'] %>
    username: <%= node['gce']['instance']['attributes']['dbuser'] %>
    password: <%= node['gce']['instance']['attributes']['dbpassword'] %>

Sie können innerhalb einer Instanz mithilfe des lokalen Metadatendienstes auch manuell auf Metadatenwerte zugreifen. Hier können Sie curl verwenden, um auf das Passwort der Datenbank zuzugreifen:

curl "http:/metadata.google.internal/computeMetadata/v1/instance/attributes/dbpassword" -H "Metadata-Flavor: Google"

Hinweise zu Leistung und Abhängigkeiten

Der Bootstrapping-Ansatz aus dieser Lösung beginnt mit einem Standardbetriebssystem-Image, wobei die Software mit Chef beim Start installiert wird und zur Bereitstellung der Konfigurationsdaten der Anwendung die Metadaten der Instanz verwendet werden.

Ein Diagramm, dass den Softwarestapel einer Instanz zeigt. Dieser Stapel zeigt, wie Software mit dem Image gebündelt ist. Einige Softwareprogramme werden beim Start installiert, andere nach dem Start bereitgestellt.

Ein Vorteil dieses Ansatzes ist, dass die Systemkonfiguration in einem Chef-Cookbook festgelegt ist. Für das Cookbook kann die Version verwaltet und es kann freigegeben werden. Außerdem besteht die Möglichkeit, es zur lokalen Bereitstellung virtueller Maschinen für Testzwecke mithilfe von Vagrant oder Docker oder zur Konfiguration von Servern in Ihrem Rechenzentrum oder mit verschiedenen Cloudanbietern zu verwenden. Die Image-Verwaltung ist ebenfalls einfacher: In dieser Beispielanwendung müssen Sie nur das eine Basisbetriebssystem-Image berücksichtigen, das von der Anwendung verwendet wird.

Zu den Nachteilen gehören mögliche langsame Startzeiten, da die gesamte Software heruntergeladen und installiert sowie in einigen Fällen kompiliert werden muss. Die Abhängigkeiten, die zu dieser Methode gehören, müssen ebenfalls beachtet werden: In diesem Beispiel hat Chef mehrere Pakete von apt, Rubygems und GitHub installiert. Sollte eines dieser Repositories beim Starten einer neuen Instanz nicht verfügbar sein, schlägt die Konfiguration fehl.

Benutzerdefinierte Images und Bootstrapping

Da Sie Ihre eigenen benutzerdefinierten Images mit Compute Engine erstellen können, ist die gesamte Installation beim Start nicht der einzige Ansatz für das Bootstrapping. Sie können zum Beispiel:

  1. Ein Ubuntu 14.10-Image starten.
  2. Alles außer der Redmine-App installieren (Ruby, nginx und so weiter).
  3. Aus dem Ergebnis ein Image erstellen.
  4. Dieses Image in der Instanzvorlage verwenden.

Wenn die neue Instanz nun gestartet wird, muss sie nur noch Redmine installieren. Die Startzeit wird verbessert und die Anzahl der externen Paketabhängigkeiten reduziert.

Ein Diagramm, das einen Instanzstack zeigt, auf dessen Image alles außer Redmine installiert wurde.

Sie können den Ansatz mit dem benutzerdefinierten Image noch weiter führen und alles in einem Image verankern, einschließlich aller Abhängigkeiten, Anwendungsquellcodes und Konfigurationen. Der Vorteil dabei sind die schnellste Startzeit und keine externen Abhängigkeiten. Wenn sich aber irgendetwas in Ihrer Anwendung ändert, müssen Sie ein neues Image erstellen und die Instanzvorlage aktualisieren.

Ein Diagramm, das einen Instanzstack mit gebündelter Software im Image zeigt.

Stellen Sie es sich so vor, dass sich die Ansätze zum Bootstrapping einer Instanz auf einem Kontinuum befinden. Mehr Konfiguration zum Startzeitpunkt bedeutet langsamere Startzeiten, aber dafür weniger Images, die verwaltet werden müssen. Mehr Konfiguration in einem benutzerdefinierten Image bedeutet schnellere Startzeiten und weniger Abhängigkeiten, aber möglicherweise sehr viel mehr Images, die verwaltet werden müssen. Für die meisten Kunden ist der richtige Ansatz ein Kompromiss, der sich irgendwo dazwischen befindet. Wählen Sie den Ansatz, der für Sie und Ihre Anwendung am sinnvollsten ist.

Ein Diagramm, das die vielen Möglichkeiten zeigt, wie Software auf einer Instanz installiert wird.Die Spanne reicht von der Installation aller Komponenten nach dem Start bis zu allen Komponenten, die mit dem Image in einem Bundle zusammengefasst werden.

Weitere Informationen

  • Eine allgemeine Übersicht über Ihre Optionen für das Webhosting auf der GCP finden Sie unter Websites bereitstellen.
  • Weitere Google Cloud Platform-Features testen: Anleitungen
Hat Ihnen diese Seite weitergeholfen? Teilen Sie uns Ihr Feedback mit:

Feedback geben zu...