Mit AWS kann man relativ einfach einen elastischen (hochverfügbareren und skallierbaren) Webstack deployen. Als Tool wollen wir dazu CloudFormation einsetzen. Der Fokus soll dabei auf Elastic Load Balancing (ELB) und Auto Scaling liegen.
Webstack Overview
Elastizität
Elastic oder auch elastisch ist ein Setup dann, wenn es selbstständig auf eine steigende oder sinkende Last reagierten kann. Bei hoher Last werden zusätzliche Ressourcen angefordert während bei geringer Last Ressourcen abgebaut werden. Liegt die Reaktionszeit innerhalb weniger Minuten kann man von einem elastischen Setup sprechen. Zusäzlich werden ausfallende Server automatisch ersetzt.
Ein elastisches Setup ist daher skalierbar und ausfallsicher.
Der kurzlebige Server
Ein neuer Server wird beschafft, physikalisch angeschlossen, installiert und konfiguriert und läuft dann über Jahre hinweg. Dieser Ansatz ist wenig flexibel. Der Server wird sehr unterschiedlich ausgelastet, so dass Benutzer manchmal länger und manchmal kürzer auf eine Antwort warten müssen. Werden die Antwortzeiten über einen längeren Zeitraum zu schlecht, wird ein zweiter Server angeschafft. Generell ist der Server eigentlich nur werktags zwischen 8 und 18 Uhr ausgelastet, da er eine interne Applikation im Unternehmen anbietet.
Der kurzlebige Server dagegen wird nur gestartet wenn er benötigt wird. So wird die typische Lebenszeit eines virtuellen Servers eher in Tagen gemessen als in Jahren. Kurzlebige Server benötigen Automatisierung. Vom Beschaffen bis zur Konfiguration muss alles automatisch geschehen.
Schritt | langlebig | kurzlebig |
---|---|---|
Beschaffung | Telefonat mit Lieferant | AWS API Request |
Verkabeln | System-Administrator | - |
Installieren | System-Administrator | Booten von Image (AMI) |
Konfigurieren | System-Administrator | Puppet / Chef / Skript |
Auto Scaling
Eine Auto Scaling Gruppe ist eine Liste von EC2 Instanzen die nach einer Schablone (Launch Configuration) erzeugt werden. Eine Auto Scaling Gruppe kennt ihre minimale und maximale Größe. Außerdem weiß sie über denn IST und SOLL Zustand der aktuellen Instanzen bescheid. Ein Beispiel:
Minimum | Maximum | SOLL | IST | Interne Aktion |
---|---|---|---|---|
1 | 3 | 2 | 1 | Neue Instanz nach Schablone starten |
1 | 3 | 2 | 2 | Nichts passiert |
1 | 3 | 1 | 2 | Eine Instanz wird terminiert |
Ein CloudWatch Alarm kann über eine Aktion SOLL um 1 erhöhen oder SOLL um 1 reduzieren wodurch wir automatisch nach z.B. CPU Load die Anzahl der Instanzen regeln können.
Elastic Load Balancer (ELB)
Wenn sich die Anzahl der Server in einer Auto Scaling Gruppe ständig ändert, wie können wir dann eine Anfrage von einem Kunde überhaupt an den richtigen Server durchreichen? Dabei helfen Load Balancer. Instanzen aus der Auto Scaling Gruppe melden sich beim Load Balancer an. Der Load Balancer führ alle t Sekunden einen Healthcheck durch und prüft ob die Instanz noch verfügbar ist. Erreicht jetzt eine Anfrage den Load Balancer schaut dieser nach, welche Instanzen verfügbar sind und leitet die Anfrage an einen der Server weiter. ELB ist ein von AWS angebotener und hochverfügbarer Service.
Vereinfachtes Setup
CloudFormation Template
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Elastic Webstack",
"Parameters": {
"AutoScalingDesiredCapacityParameter": {
"Description": "Desired Capacity for Auto Scaling",
"Type": "Number",
"Default": "2"
},
"AutoScalingMaxSizeParameter": {
"Description": "Max Size for Auto Scaling",
"Type": "Number",
"Default": "3"
},
"AutoScalingMinSizeParameter": {
"Description": "Min Size for Auto Scaling",
"Type": "Number",
"Default": "1"
},
"KeyParameter": {
"Description": "Key name",
"Type": "String"
},
"AMIParameter": {
"Description": "AMI id (default works only in eu-west-1)",
"Type": "String",
"Default": "ami-6e7bd919"
},
"VPCParameter": {
"Description": "VPC id",
"Type": "String"
},
"SubnetsParameter": {
"Description": "Subnet ids (separated by commas)",
"Type": "CommaDelimitedList"
}
},
"Resources": {
"ELBSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "elastic-loadbalancer",
"SecurityGroupEgress": [{
"IpProtocol": "-1",
"CidrIp": "0.0.0.0/0"
}],
"SecurityGroupIngress": [{
"CidrIp": "0.0.0.0/0",
"FromPort": 80,
"IpProtocol": "tcp",
"ToPort": 80
}],
"VpcId": {"Ref": "VPCParameter"}
}
},
"LoadBalancer": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
"CrossZone": true,
"HealthCheck": {
"HealthyThreshold": "2",
"Interval": "15",
"Target": "HTTP:8888/",
"Timeout": "10",
"UnhealthyThreshold": "2"
},
"LoadBalancerName": "elastic-loadbalancer",
"Listeners": [{
"InstancePort": "8888",
"InstanceProtocol": "HTTP",
"LoadBalancerPort": "80",
"Protocol": "HTTP"
}],
"Scheme": "internet-facing",
"SecurityGroups": [{ "Ref": "ELBSecurityGroup" }],
"Subnets": {"Ref": "SubnetsParameter"}
}
},
"IAMRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Service": [ "ec2.amazonaws.com" ]
},
"Action": [ "sts:AssumeRole" ]
}]
},
"Path": "/",
"Policies": []
}
},
"InstanceProfile": {
"Type": "AWS::IAM::InstanceProfile",
"Properties": {
"Path": "/",
"Roles": [{"Ref": "IAMRole"}
]
}
},
"EC2SecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "elastic-instances",
"SecurityGroupEgress": [{
"IpProtocol": "-1",
"CidrIp": "0.0.0.0/0"
}],
"SecurityGroupIngress": [{
"CidrIp": "0.0.0.0/0",
"FromPort": 22,
"IpProtocol": "tcp",
"ToPort": 22
}, {
"FromPort": 8888,
"IpProtocol": "tcp",
"SourceSecurityGroupId": {"Ref": "ELBSecurityGroup"},
"ToPort": 8888
}],
"VpcId": {"Ref": "VPCParameter"}
}
},
"LaunchConfiguration": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"AssociatePublicIpAddress": true,
"EbsOptimized": false,
"IamInstanceProfile": {"Ref": "InstanceProfile"},
"ImageId": {"Ref": "AMIParameter"},
"InstanceType": "t2.micro",
"KeyName": {"Ref": "KeyParameter"},
"SecurityGroups": [{ "Ref": "EC2SecurityGroup" }],
"UserData": { "Fn::Base64": { "Fn::Join": ["", [
"#!/bin/bash -ex\n",
"mkdir -p /srv\n",
"cd /srv\n",
"python -m SimpleHTTPServer 8888 &\n"
] ] } }
}
},
"AutoScalingGroup": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AvailabilityZones": { "Fn::GetAZs": { "Ref": "AWS::Region" } },
"DesiredCapacity": {"Ref": "AutoScalingDesiredCapacityParameter"},
"LaunchConfigurationName": { "Ref": "LaunchConfiguration" },
"MaxSize": {"Ref": "AutoScalingMaxSizeParameter"},
"MinSize": {"Ref": "AutoScalingMinSizeParameter"},
"VPCZoneIdentifier": {"Ref": "SubnetsParameter"},
"LoadBalancerNames": [{"Ref": "LoadBalancer"}]
}
}
}
}
Ergebnis
- Eine Instanz ist bei der Auto Scaling Gruppe angemeldet.
- Der Load Balancer meldet ebenfalls eine Instanz. Der Healthcheck war erfolgreich und die Instanz ist somit InService. (Es kann einige Zeit dauern bis die Instanz bei dir als InService angezeigt wird).
Fazit
Mit AWS kann man mit wenig Aufwandeinen elastischen Webstack erzeugen. Dieser ist nicht nur per se ausfallsicher sondern auch noch skallierbar. Mit Hilfe von CloudWatch Alarmen kann man den SOLL Zustand (Desired Capacity) der Auto Scaling Gruppe dynamisch z.B. in Abhängigkeit der CPU Load der Gruppe anpassen.
Der gezeigte Stack eignet sich nicht nur für Web-Applikationen sondern generell für alle Request / Response Applikationen. Vorraussetzung ist, dass die Server selbst keinen Zustand halten. Zustand gehört in eine Datenbank.