In einer Microservice-Architektur besteht eine der großen Herausforderungen darin, dass Services, die miteinander kommunizieren wollen, sich gegenseitig finden. Eine Möglichkeit, dies zu realisieren, besteht im Betrieb einer Service Registry. Netflix hat hier mit Eureka eine speziell für die AWS Cloud geschaffene Lösung im Portfolio. Da nun allerdings alle Services von dieser Registry abhängen, muss dafür gesorgt werden, dass sie jederzeit erreichbar ist. Nachfolgend wird gezeigt, wie ein dazu geeigneter Stack mittels Amazon Web Services (AWS) CloudFormation beschrieben werden kann.

Vorbedingungen

Damit aus dem AWS CloudFormation Template erfolgreich ein Stack erstellt werden kann, müssen Vorbedingungen erfüllt werden.

  1. AMI
  2. Elastic IPs

AMI

Die Basis einer Amazon Elastic Compute Cloud (EC2)-Instanz innerhalb der AWS Cloud bildet ein Amazon Machine Image (AMI). Diese enthält im Basisfall das Betriebssystem, kann jedoch auch bereits installierte Software enthalten. In unserem Fall haben wir mithilfe von Packer ein AMI erstellt, welches neben dem Betriebssystem auch Java 7 und einen Tomcat mit darin deploytem Eureka enthält.

Elastic IPs

Da in der AWS Cloud jede Instanz eine zufällige IP erhält, kann man diese nicht als Referenzpunkt in einem Service verwenden. Da jedoch die Clients von Eureka eine feste Adresse brauchen, gibt es die folgenden zwei Möglichkeiten:

  1. DNS
  2. feste IP

Beides wird von AWS und auch von Eureka unterstützt. In diesem Fall haben wir uns für feste IPs entschieden. Dieses Konzept wird in der AWS Cloud unter dem Begriff Elastic IP angeboten. Interessant hierbei ist, dass man für eine Elastic IP nur dann zahlt, wenn man diese aktuell nicht nutzt (siehe: http://aws.amazon.com/ec2/pricing/#Elastic_IP_Addresses).

Da Eureka sich selbst eine freie Elastic IP zuweist, muss man lediglich eine pro Region registrieren.

Template

Innerhalb von AWS CloudFormation wird ein Stack mithilfe eines Templates beschrieben. In unserem Falle besteht dieses Template aus den folgenden vier Ressourcen:

  1. Auto Scaling Group
  2. Launch Configuration
  3. IAM Role
  4. Security Group

Auto Scaling Group

Amazon unterteilt seine AWS Cloud in Regionen (Standorte) und innerhalb dieser Regionen in Availability Zones (AZ). Die Empfehlung von Amazon lautet, eine Applikation immer in mindestens zwei AZ zu betreiben, damit im Falle eines Ausfalls einer AZ die Applikation der zweiten AZ weiter läuft. Eureka empfiehlt sogar, dass pro verfügbarer AZs und Region eine Instanz betrieben wird.

Damit dies sichergestellt werden kann, muss man eine Auto Scaling Group definieren. Diese beschreibt, wieviele Instanzen einer Applikation man betrieben möchte und wie sich diese Instanzen auf die verfügbaren AZs verteilen.

In AWS CloudFormation sieht eine solche Beschreibung folgendermaßen aus:

"AsgEureka": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "Properties" : {
        "LaunchConfigurationName": { "Ref": "LcEureka" },
        "MinSize": "2",
        "MaxSize": "2",
        "AvailabilityZones": [
          "eu-west-1a", "eu-west-1b"
        ]
      }
    }

Launch Configuration

Eine Auto Scaling Group alleine weiß nicht, wie die Instanzen aussehen, die durch sie beschrieben werden. Hierzu benötigt man eine Launch Configuration. Diese beschreibt wie die Instanzen aussehen. Die Basis hierfür bildet der Typ der Instanz sowie das Image, auf welcher die Instanz basiert. Zudem kann hier eine Zuweisung zur IAM Rolle und Security Group erfolgen.

"LcEureka": {
      "Type": "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "ImageId": "ami-beae01c9",
        "InstanceType": "m1.small",
        "IamInstanceProfile": { "Fn::GetAtt" : ["IamProfileEureka", "Arn"] },
        "SecurityGroups": [
          { "Ref": "SgEureka" }
        ],
        "BlockDeviceMappings": [
          {
            "DeviceName": "/dev/sda1",
            "Ebs": {
              "VolumeSize": 8
            }
          },
          {
            "VirtualName": "ephemeral0",
            "DeviceName": "/dev/sdb"
          }
        ]
      }
    }

IAM Role

Innherhalb der AWS Cloud kann eine Applikation über eine definierte API (TODO: Link) AWS-spezifische Information abfragen oder auch setzen. Eureka nutzt dies z.B. dazu, sich selber eine der Elastischen IPs zuzuweisen. Damit dies gelingt, muss die Instanz allerdings über entsprechende Rechte verfügen.

"IamRoleEureka": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "ec2.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "Path": "/",
        "Policies": [
          {
            "PolicyName": "Eureka",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Resource": "*",
                  "Effect": "Allow",
                  "Action": [
                    "ec2:DescribeAddresses",
                    "ec2:AssociateAddress",
                    "ec2:DisassociateAddress"
                  ]
                },
                {
                  "Resource": "*",
                  "Effect": "Allow",
                  "Action": [
                    "autoscaling:DescribeAutoScalingGroups"
                  ]
                }
              ]
            }
          }
        ]
      }
    },
    "IamProfileEureka": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          { "Ref": "IamRoleEureka" }
        ]
      }
    }

Security Group

Als letzte Ressource wird eine Security Group benötigt. Eine Security Group innerhalb der AWS Cloud kann mit einer Firewall gleichgesetzt werden. Im Falle von Eureka müssen sich wenigstens die über die verschiedenen AZs verteilten Instanzen erreichen können. Deshalb wird eine Security Group definiert die dafür sorgt, dass dies funktioniert.

"SgEureka": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Security group for eureka"
      }
    },
    "SgEurekaIngressOwn": {
      "Type": "AWS::EC2::SecurityGroupIngress",
      "Properties": {
        "GroupName": { "Ref": "SgEureka" },
        "IpProtocol": "tcp",
        "FromPort": "0",
        "ToPort": "65535",
        "SourceSecurityGroupName": { "Ref": "SgEureka" }
      }
    }

Fazit

Mein Ziel war es, zu zeigen, wie man mit einfachen Mitteln den ausfallsicheren Betrieb von Eureka in der AWS Cloud sicherstellen kann. Das Template Format ist jedoch noch weitaus mächtiger und bietet somit das flexibelste Modell, um die eigene Infrastruktur in der AWS Cloud zu beschreiben. Weiterhin kümmert sich AWS CloudFormation um parallelisierte Ausführung beim Erstellen eines Stacks und kann auch Updates eines Stacks ohne Downtime durchführen.

Wer sich tiefergehend mit AWS CloudFormation auseinandersetzen möchte, dem sei die Amazon eigene Dokumentation empfohlen.