Hochverfügbare Webanwendung mit AWS Cloudformation

In diesem Blog-Artikel möchte ich einen Ansatz vorstellen, wie man eine Webanwendung mit AWS Cloudformation in die AWS Cloud deployt, die trotz möglicher Ausfälle von Availability Zones und Servern hochverfügbar bleibt. Ausgangspunkt für diesen Blog Post ist eine bereits vorhandene Netzwerkarchitektur (siehe Bild 1), sodass ich im Folgenden den Fokus vor allem auf die Rolle und Funktionsweise des Load Balancers legen werde. Alle Komponenten, auf die ich hier nicht genauer eingehen werde, können in meinem GitHub Repository nachgeschaut werden.

Bild 1: Netzwerkarchitektur

Zuallererst möchte ich einmal kurz das obige Bild beschreiben. Wenn man seine Anwendung in die Cloud deployen möchte, ist es aus Sicherheitsgründen sinnvoll, dafür Server in privaten Subnetzen zu verwenden. Und da eine Availability Zone mal nicht erreichbar sein kann, verwenden wir zwei private Subnetze. Beide jeweils in einer anderen Availability Zone. Damit unsere Server – die wir später in die beiden privaten Subnetze installieren – mit der Außenwelt kommunizieren können, verwenden wir sogenannte NAT-Gateways. Aus den gleichen Gründen wie bei den privaten Subnetzen verwenden wir auch zwei öffentliche Subnetze, denen wir jeweils ein NAT-Gateway zuordnen. Mit Routing Tabellen für die privaten und öffentlichen Subnetze kann definiert werden, dass Server aus den privaten Subnetzen mithilfe des NAT-Gateways das Internet Gateway erreichen können, umgekehrt jedoch nicht.

Das Ziel ist es nun, mit einem Load Balancer – zu Deutsch „Lastverteiler“ – die hereinkommenden Anfragen auf unsere Server in den privaten Subnetzen zu verteilen, damit unser System als Ganzes auch bei höherer Last verfügbar bleibt. Und nicht nur das. Der Load Balancer soll den Health Status unserer Server überwachen und bei Nichterreichbarkeit eines Servers einen neuen Server anhand eines von uns definierten Templates hochfahren, sodass wir die Lauffähigkeit einer bestimmten Anzahl von Servern sicherstellen können. Wie das geht, schauen wir uns jetzt an.

Bild 2: Aufbau eines Load Balancers Quelle: Nanodegree Cloud DevOpsEngineer von udacity.com

Target Group

Target Groups werden verwendet, um Anfragen an mehrere Ziele weiterzuleiten. Dabei kann man mehrere Zielgruppen für unterschiedliche Anfragen erstellen. Wir erstellen für unsere Zielgruppe zudem Zustandsprüfungseinstellungen, damit der Load Balancer den Status der einzelnen Instanzen überwachen kann, um bei negativen Health Checks neue Instanzen hochfahren zu können.

  • HealthCheckIntervalSeconds: Die Zeit zwischen Checks einzelner Targets.
  • HealthCheckPath: Der Pfad an dem der Health Check durchgeführt wird. Antwortet die Anwendung auf dem Server mit http-Status 200 ist alles in Ordnung.
  • HealthCheckProtocol: Das Protokoll, das der Load Balancer für die Checks verwendet.
  • HealthCheckTimeoutSeconds: Anzahl an Sekunden, nachdem ein Health Check fehlschlägt, wenn bis dahin keine Antwort vom Server gesendet wurde.
  • HealthyThresholdCount: Anzahl an Health Checks, bis eine Instanz mit negativem Health Check in den Status healthy wechselt.
  • UnhealthyThresholdCount: Anzahl an Health Checks, bis eine Instanz als unhealthy deklariert wird.
WebAppTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 8
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 5
      VpcId:
        Fn::ImportValue:
          Fn::Sub: "${EnvironmentName}-VPCID"

Launch Configuration

Um zu definieren, wie unsere Server aufgebaut sein sollen, verwenden wir eine Launch Configuration. Unter der Property UserData können wir ein Benutzerskript schreiben, das nach Server-Startup ausgeführt werden soll. In unserem Fall installieren wir den Apache Webserver und erstellen eine index.html, die angezeigt wird, wenn wir die entsprechende Adresse später im Browser aufrufen. Zudem geben wir die ID des Amazon Machine Image der Instanz an, sowie die Sicherheitsgruppe und ein 10 GB großes Volume.

WebAppLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          apt-get update -y
          apt-get install unzip awscli -y
          apt-get install apache2 -y
          systemctl start apache2.service
          cd /var/www/html
          echo "Demo Web Server Up and Running!" > index.html
      ImageId: ami-00d5e377dd7fad751
      SecurityGroups:
        - Ref: WebServerSecGroup
      InstanceType: t2.micro
      BlockDeviceMappings:
      - DeviceName: "/dev/sdk"
        Ebs:
          VolumeSize: '10'

Auto Scaling Group

Als Nächstes kümmern wir uns um die Auto Scaling Group. Eine solche Auto Scaling Group ist eine Sammlung von EC2-Instanzen zur automatischen Skalierung und Verwaltung. Hier geben wir unsere privaten Subnetze an, in denen neue Instanzen erstellt werden können. Die zuvor erstellte Launch Configuration gibt an, wie unsere Instanzen aussehen sollen, die in dieser Gruppe sind. Mit den MinSize und MaxSize Properties definieren wir, wie groß diese Gruppe mindestens und maximal sein darf. Haben wir beispielsweise vier laufende Instanzen und eine davon verabschiedet sich, wird direkt eine neue hochgefahren, weil wir als MinSize 4 angegeben haben.

WebAppGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      VPCZoneIdentifier:
      - Fn::ImportValue: 
          !Sub "${EnvironmentName}-PRIV-NETS"
      LaunchConfigurationName: 
        Ref: WebAppLaunchConfig
      MinSize: '4'
      MaxSize: '4'
      TargetGroupARNs:
        - Ref: WebAppTargetGroup

Load Balancer

Der Load Balancer ist nicht weiter komplex. Wir geben hier unsere zwei öffentlichen Subnetze und die Sicherheitsgruppe für den Load Balancer an. Die Angabe von zwei Subnetzen ist auch hier wieder AWS Best Practice, um Schwierigkeiten zu umgehen, falls eine Availability Zone nicht erreichbar ist.

WebAppLB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Subnets:
      - Fn::ImportValue: !Sub "${EnvironmentName}-PUB1-SN"
      - Fn::ImportValue: !Sub "${EnvironmentName}-PUB2-SN"
      SecurityGroups:
      - Ref: LBSecurityGroup

Listener

Mit dem Listener definieren wir einen Prozess, der mit einem angegebenen Protokoll und Port Verbindungsanforderungen prüft. Unser Listener soll an Port 80 auf HTTP Client-Anfragen warten und diese an unsere Target Group weiterleiten.

Listener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn:
            Ref: WebAppTargetGroup
      LoadBalancerArn:
        Ref: WebAppLB
      Port: '80'
      Protocol: HTTP

Listener Rule

Mit einer Listener Rule kann man bestimmen, wie der Load Balancer Anforderungen an die Target Groups weiterleitet. In unserem Beispiel leiten wir alle Anfragen von Clients, die unsere Root URL aufrufen, an unsere Target Group weiter. Hätten wir mehrere Target Groups, könnten wir so einkommende Requests anhand eines Pfad-Patterns auf unsere verschiedenen Target Groups aufteilen.

ALBListenerRule:
      Type: AWS::ElasticLoadBalancingV2::ListenerRule
      Properties:
        Actions:
        - Type: forward
          TargetGroupArn: !Ref 'WebAppTargetGroup'
        Conditions:
        - Field: path-pattern
          Values: [/]
        ListenerArn: !Ref 'Listener'
        Priority: 1

Infrastruktur mit AWS Cloudformation bereitstellen

Um die vorgestellte Infrastruktur zu erstellen, muss auf dem eigenen Rechner die AWS CLI installiert sein. Danach sind folgende 3 Schritte notwendig:

git clone https://github.com/mlk4D03/high-availability-webapp-with-cloudformation.git

cd high-availability-webapp-with-cloudformation

./create.sh network-stack network.yml network-parameters.json

./create.sh server-stack server.yml server-parameters.com

Zusammenfassung

Die obige Netzwerkarchitektur haben wir nun dahingehend erweitert, dass wir einen Load Balancer hinzugefügt haben, der jeweils zwei Server in den privaten Subnetzen erstellt und bei Nichterreichbarkeit eines Servers einfach einen neuen hochfährt. Durch diese Architektur haben wir sichergestellt, dass auch durch Ausfall eines Servers oder einer ganzen Availability Zone unsere Anwendung dennoch erreichbar bleibt und auch hohen Nutzerzahlen standhalten könnte. Wer sich von der Funktionsweise des Load Balancers einmal selbst überzeugen möchte, der kann nach Bereitstellung der Server durch AWS Cloudformation per Hand einen Server terminieren. Es sollte nicht lange dauern, bis dies bemerkt wird und ein neuer Server hochgefahren ist.

Bild 3: Gesamtarchitektur
Short URL for this post: https://blog.oio.de/zc5Nf
This entry was posted in Java Runtimes - VM, Appserver & Cloud and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *