Suche
Frühlingswolken

Cloud-native Anwendungen bauen mit Spring Cloud und Netflix OSS

Dr. Joseph Adersberger, Mario-Leander Reimer

@shutterstock/phloxii

Cloud-native Anwendungen skalieren fast beliebig, reagieren elastisch auf Last und sind bis an die Schmerzgrenze fehlertolerant. So etwas sicherzustellen ist komplex. Es braucht neue Frameworks und Infrastruktur, die den Anwendungsentwickler vor dieser Komplexität schützen. Genau das leistet Spring Cloud: Es bietet ein Framework zur Entwicklung von Cloud-nativen Anwendungen als Microservices und integriert die quelloffene Cloud-Infrastruktur von Netflix und Co.

Artikelserie

Teil 1: Der Cloud-Native-Stack
Teil 2: Cloud-native Anwendungen bauen mit Spring Cloud und Netflix OSS
Teil 3: Clusterorchestrierung mit Kubernetes
Teil 4: Mesos: Das Betriebssystem der Cloud

Die Entwicklung und der Betrieb von Cloud-nativen Anwendungen sollte so einfach sein wie bei traditionellen Java-EE-Anwendungen. Dennoch bringt der hohe Verteilungsgrad viel Komplexität mit sich. Das Spring-Cloud-Projekt verspricht hier Abhilfe: Es setzt auf Spring Boot als Container für Microservices, integriert vielerlei Cloud-Infrastruktur und vereinfacht in gewohnter Spring-Manier die Programmierung.

Spring Cloud ist ein Schirmprojekt über derzeit fünfzehn Bausteinprojekte. Es ist Ende 2014 gestartet und unserer Meinung nach die tragfähigste Basis, um Cloud-native Anwendungen mit Java zu entwickeln. Spring Cloud bietet alle Bausteine eines Microservice-Frameworks, wie sie im Vorgängerartikel beschrieben wurden. Abbildung 1 zeigt, wie sich Spring-Cloud-Bausteine auf die typischen Bausteine eines Microservice-Frameworks abbilden.

Abb. 1: Spring-Cloud-Bausteine auf typische Bausteine eines Microservice-Frameworks abbilden

Abb. 1: Spring-Cloud-Bausteine auf typische Bausteine eines Microservice-Frameworks abbilden

Die momentan grundlegendsten Bausteine von Spring Cloud sind Netflix Eureka, Spring Cloud Config, Netflix Hystrix, Netflix Ribbon, Netflix Feign, Netflix Zuul, Hystrix Dashboard und Netflix Turbine.

Netflix Eureka zur Service Discovery, die Registratur für Serviceendpunkte: Microservices können ihre Endpunkte bei Eureka registrieren und nach vorhandenen Endpunkten suchen. Ferner überwacht Eureka kontinuierlich die Verfügbarkeit der Endpunkte. Es besteht aus einer hochskalierbaren und ausfallsicheren Serverkomponente sowie aus clientseitigen Komponenten.

Spring Cloud Config zur Service Configuration übernimmt die zentrale Verwaltung von Konfigurationsparametern der Anwendungen über alle Umgebungen und Stages hinweg. Es stellt dabei die Konsistenz der Konfigurationsstände sicher und ermöglicht es, Konfigurationsparameter zur Laufzeit zu aktualisieren. Netflix Hystrix als Circuit Breaker im Serviceclient sorgt für die nötige Resilienz von Serviceclients [1], [2] und sammelt Statistiken zu Fehlerquoten und Antwortzeiten. Bei Ribbon handelt es sich um einen clientseitigen Load Balancer zur Lastverteilung zwischen mehreren Serviceinstanzen. Diese erfragt er bei Eureka. Netflix Feign, zum einfachen Aufruf von REST-Schnittstellen im Serviceclient, verfolgt einen deklarativen Ansatz; der übliche Boilerplate-Code bei Nutzung von Jersey oder Spring RestTemplate entfällt. Feign macht aus einem normalen Java-Interface plus speziellen Annotationen einen Serviceclient, der bereits mit Hystrix, Ribbon und Eureka integriert ist. Netflix Zuul – als Edge-Server – ist das Zugangstor zu den Endpunkten von außen. Er routet Anfragen zum richtigen Endpunkt und sorgt dabei für Load Balancing. Zuul holt sich die dafür notwendigen Informationen aus Eureka und verwendet Ribbon und Hystrix für das Dispatching der Aufrufe. Das Hystrix Dashboard zeigt die Statistiken der Hystrix Circuit Breaker grafisch an, Netflix Turbine ist verantwortlich, die dafür notwendigen Daten von allen Knoten einzusammeln und zu aggregieren.

Eine vertiefende Beschreibung dieser und weiterer Bausteine von Spring Cloud kann der Dokumentation entnommen werden. Da diese teilweise sehr knapp gehalten ist, raten wir zusätzlich zu einem Blick in die Spring-Cloud-Beispielanwendungen und, gerade wenn es um Konfigurationsparameter geht, in den Quellcode selbst.

Spring Cloud wird in Release Trains versioniert, ähnlich dem Entwicklungsmodell von Eclipse. Damit wird die Entwicklung in den verschiedenen Bausteinprojekten synchronisiert. Die Namen der Release Trains sind Stationen der Londoner Metro: Das aktuelle Release ist Angel, momentan in Entwicklung ist Brixton. Beim Bau einer Cloud-nativen Anwendung muss beachtet werden, dass die Releases von Spring Boot und Spring Cloud kompatibel sind. So läuft Angel noch mit Spring Boot 1.2.x, wohingegen Brixton nur noch mit Spring Boot 1.3.x kompatibel ist.

Für das schnelle Erstellen von Cloud-nativen Anwendungen bietet sich Spring Initializr an. Über eine Weboberfläche können die gewünschten Bausteine ausgewählt und ein Projektgrundgerüst mit Build-Skripten und Spring-Boot-Hauptklasse generiert werden.

Der Zwitscher-Showcase

Nun wollen wir am Beispiel von Zwitscher, unserer Cloud-nativen Beispielanwendung, zeigen, wie Spring Cloud genutzt werden kann. Zwitscher ist eine Webanwendung, die Meldungen aus mehreren Internetquellen aggregiert. Abbildung 2 zeigt den schematischen Aufbau von Zwitscher und welche Bausteine von Spring Cloud dabei verwendet werden.

Abb. 2: Aufbau Zwitscher Showcase

Abb. 2: Aufbau Zwitscher Showcase

Zwitscher besteht aus zwei Microservices: Der Zwitscher Service ruft Tweets und Quotes bei entsprechenden Onlinediensten ab und stellt sie über ein REST-API zur Verfügung. Das Zwitscher Dashboard ist ein Web-UI, mit dem Tweets und Quotes angezeigt und durchsucht werden können. Es nutzt dafür das REST-API des Zwitscher-Service. Um diese beiden anwendungsspezifischen Microservices herum läuft die Infrastruktur des Microservice-Frameworks – natürlich ebenso als Microservice. Der komplette Sourcecode der Beispielanwendung steht auf GitHub zur Verfügung.

Service Discovery mit Netflix Eureka

Im ersten Schritt realisieren wir die Service Discovery auf Basis von Netflix Eureka. Für das schnelle Projekt-Set-up verwenden wir Spring Initializr, wählen den Baustein Eureka Server aus und lassen uns die Projekthülle generieren. Um die generierte Applikation in einen lauffähigen Eureka-Server zu verwandeln, muss man lediglich die @EnableEurekaServer-Annotation zur Spring-Boot-Hauptklasse hinzufügen. Danach ist der Eureka-Server bereits lauffähig, das UI kann unter http://localhost:8761 aufgerufen werden (Abb. 3).

Abb. 3: Eureka-Serviceübersicht

Abb. 3: Eureka-Serviceübersicht

Serviceübersicht in Eureka

Eureka ist auf Hochverfügbarkeit ausgelegt. Unser Server erwartet eine zweite Eureka-Serverinstanz, mit der die Registry-Informationen repliziert werden können. Für ein einfaches lokales Set-up ist dieses Verhalten jedoch nicht erforderlich, weshalb wir Eureka vorerst im Standalone-Modus betreiben. Listing 1 zeigt die hierfür notwendige Konfiguration in der Datei application.yml.

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false

Jeder Eureka-Server ist gleichzeitig auch ein Eureka-Client, da er seine Dienste über Endpunkte zur Verfügung stellt. Die obigen Parameter bringen den Client im Server dazu, den Service nicht zu registrieren und sich den kompletten Registry-Inhalt nicht anfänglich herunterzuladen. Damit ist sein Wunsch nach einem zweiten Eureka-Server abgekappt.

Um später einen Service am Eureka-Server zu registrieren, muss dem Projekt die spring-cloud-starter-eureka BuildDependency hinzugefügt und die Hauptklasse mit der @EnableDiscoveryClient-Annotation versehen werden. Daraufhin registriert sich die Anwendung unter dem Namen, der als Property spring.application.name in der Datei bootstrap.yml hinterlegt ist. Damit der Eureka-Client den Server findet, definieren wir den Eureka-Service-URL in der application.yml (Listing 2). Der Parameter defaultZone erlaubt, dass man kommasepariert auf eine oder mehrere Eureka-Serverinstanzen verweist.

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

Service Configuration mit Spring Cloud Config

Die Konfigurationen der einzelnen Services können über Spring Cloud Config zentral über einen Config-Server verwaltet werden. Eine Anwendung sucht beim Start den Configuration Service per Service Discovery und ruft anschließend die jeweiligen Konfigurationswerte ab.

Für Zwitscher erzeugen wir per Spring Initializr einen neuen Config-Server. Die Hauptklasse wird anschließend mit der @EnableConfigServer-Annotation versehen, um den Config-Server in die Spring-Boot-Applikation einzubetten. Zusätzlich annotieren wir die Applikation mit @EnableDiscoveryClient, damit der Config-Server später per Service Discovery gefunden wird. Der Server stellt die Konfigurationen über eine REST-Schnittstelle bereit. Es gibt verschiedene Möglichkeiten zur Ablage der Konfigurationsdateien: in einem Git oder Subversion Repository oder nativ im Klassenpfad des Config-Servers. Letztere Option ist speziell zu Beginn der Entwicklung praktisch. Listing 3 zeigt die entsprechende Konfiguration des Config-Servers.

spring:
  profiles: native
  cloud:
    config:
      server:
        native:
          searchLocations: classpath:/config

Alle Konfigurationsdateien werden direkt im Verzeichnis src/main/resources/config im Config-Server-Projekt abgelegt und folgen dem Namensmuster {application}-{profile}.yml. Zu jedem Microservice können also noch verschiedene Konfigurationsvarianten (Profile) hinterlegt werden. Dies kann man z. B. nutzen, um unterschiedliche Parameter für die lokale Ausführung und die Ausführung in der Cloud zu hinterlegen.

Es ist natürlich nicht ratsam, sensible Daten wie Passwörter oder API Keys im Klartext in den Konfigurationsdateien zu hinterlegen. Darum bietet Spring Cloud Config die Möglichkeit, Konfigurationswerte symmetrisch oder asymmetrisch zu verschlüsseln. Vor Auslieferung der Konfigurationsparameter an die Anwendung werden die Werte entschlüsselt. Mehr Sicherheit bietet nur noch ein Baustein wie Vault, der aber noch nicht in Spring Cloud integriert ist.

Damit nun Anwendungen beim Start ihre Konfiguration beim Config-Server abrufen können, muss die URL des Config-Endpunkts per Service Discovery ermittelt werden. Listing 4 zeigt die dafür notwendige Konfiguration. Der Parameter ZWITSCHER-CONFIG ist dabei der Servicenamen des Config-Servers in Eureka.

spring:
  cloud:
    config:
      enabled: true
      discovery:
        enabled: true
        serviceId: ZWITSCHER-CONFIG

Serviceclients mit Feign, Ribbon und Hystrix

Es liegt in der Natur von Cloud-nativen Anwendungen, dass sie viel Remote kommunizieren. Es braucht also Unterstützung dabei, schnell Serviceclients von hoher Qualität entwickeln zu können. Die Netflix-Bausteine Feign, Ribbon und Hystrix, integriert mit Spring Cloud, bieten hier eine elegante Lösung. Feign ist ein deklarativer Web-Service-Client: Die Serviceschnittstelle wird über ein annotiertes Java-Interface beschrieben (Listing 5). Das Interface wird mit @FeignClient annotiert und dabei der Servicename des Endpunkts in Eureka angegeben. Zusätzlich kann ein Fallback Handler angegeben werden, der alle Anfragen im Fehlerfall beantwortet. Feign kümmert sich selbstständig um die Service Discovery, und alle Anfragen an den Endpunkt werden automatisch über einen Ribbon Load Balancer abgesetzt und mit einem Hystrix Circuit Breaker versehen.

@FeignClient(name = "zwitscher-service", fallback = Quote.Fallback.class)
public interface QuoteRepository {
  @RequestMapping(method = RequestMethod.GET, value = "/quote")
  Quote getNextQuote();
}

Will man den Serviceclient nun nutzen, muss man zunächst in der Spring-Boot-Hauptklasse ein paar Annotationen ergänzen (Listing 6). Zusätzlich dazu kann auch noch die Annotation @RibbonClient verwendet werden, um das Load-Balancing-Verhalten von Ribbon zu konfigurieren. Nun steht der Serviceclient fertig initialisiert per Spring Dependency Injection zur Verfügung.

@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients
public class ZwitscherBoardApplication {
  //...
}

Der Ribbon Loader Balancer kann auch ohne Feign im Zusammenspiel mit einem RestTemplate verwendet werden (Listing 7). Beim Injizieren wird lediglich die @LoadBalanced-Annotation zusätzlich angegeben. Die Nutzung der Instanz erfolgt wie gewohnt, mit der Ausnahme, dass der URL anstatt eines Hostnames einen Eureka-Servicenamen enthalten muss.

@Autowired @LoadBalanced
private RestTemplate restTemplate;

final String tweetsRibbonUrl = "http://zwitscher-service/tweets?q={q}";
Zwitscher[] tweets = restTemplate.getForObject(tweetsRibbonUrl,
  Zwitscher[].class, q);

Edge-Server mit Netflix Zuul

Ein Zuul-Edge-Server bildet das Zugangstor zu Zwitscher. Sowohl das Zwitscher-Service-REST-API als auch das Zwitscher Dashboard werden als Endpunkte von Zuul exportiert und sind unter einem gemeinsamen Basis-URL erreichbar.

Auch hier nutzen wir wieder den Spring Initializr für einen schnelle Start und wählen dabei die folgenden Bausteine aus: Config Client, Eureka Discovery und Zuul. Um die generierte Spring-Boot-Applikation zu einem Zuul Proxy zu machen, muss lediglich die @EnableZuulProxy-Annotation zur Hauptklasse hinzugefügt werden. Diese Annotation aktiviert neben der Zuul-Konfiguration auch einen Eureka-Discovery-Client und den Hystrix Circuit Breaker. Netflix Ribbon sorgt für die Lastverteilung aller weitergeleiteten Anfragen und Hystrix für deren Resilienz.

Das Herzstück unseres Edge-Servers besteht aus der Konfiguration von Routen. Jede Route definiert, welches URL-Pattern an welchen in Eureka registrierten Service weitergeleitet wird. In Listing 8 sind zwei Beispiele für Routendefinitionen zu sehen. So leitet die api-Route alle Aufrufe unterhalb des Pfads /api/ an den Service mit dem Namen zwitscher-service weiter. Das Präfix (api) wird dabei abgeschnitten. Die zweite Route verwendet die kurze Notation; hiermit werden die Anfragen an den Service mit dem Namen zwitscher-board weitergeleitet. Präfix und Servicename sind hier gleich. Über das zuul.ignoredPatterns Property werden Patterns definiert, die ignoriert und nicht weitergeleitet werden sollen. Alle administrativen URLs wollen wir natürlich nicht über den Edge-Server zugänglich machen.

zuul:
  ignoredPatterns: /**/admin/**
  routes:
    api:
      path: /api/**
      serviceId: zwitscher-service
      stripPrefix: true
    zwitscher-board: /**

Monitoring mit Hystrix und Turbine

Im letzten Schritt statten wir die Anwendung mit einem Monitoring aus. Wir wollen den Zustand der Circuit Breaker über alle Services hinweg überwachen. Genau die Circuit Breaker sitzen an den neuralgischen Stellen eines Microservice-Systems. Die aufrufenden Microservices können am besten beurteilen, wie es einem aufgerufenen Microservice geht – ob er zu viele Fehler produziert, abgestürzt ist oder zu langsam antwortet. Den Zustand der Hystrix Circuit Breaker aller Services sammeln wir über Turbine zusammen und stellen sie per Hystrix Dashboard zur Verfügung (Abb. 4).

Abb. 4: Das Zwitscher Hystrix Dashboard

Abb. 4: Das Zwitscher Hystrix Dashboard

Wie schon in den vorherigen Schritten erzeugen wir ein neues Projekt per Spring Initialzr und wählen dabei die folgenden Bausteine: Discovery Client, Config Client, Hystrix Dashboard und Turbine.

Die Hauptklasse der Monitoringanwendung wird danach mit den @EnableHystrixDashboard– und @EnableTurbine-Annotationen versehen. Zusätzlich definieren wir noch ein Request Mapping, um Anfragen auf oder direkt an das Hystrix Dashboard weiterzuleiten (Listing 9).

@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine
@EnableHystrixDashboard
@Controller
public class ZwitscherMonitorApplication {

  @RequestMapping(path = "/", method = RequestMethod.GET)
  public String index() {
    return "forward:/hystrix";
  }

  public static void main(String[] args) { // ... }
}

Um den aggregierten Zustand der Circuit Break über mehrere Applikationen hinweg anzeigen zu können, muss Turbine konfiguriert werden. Wie üblich passiert dies direkt in der Anwendungskonfiguration (Listing 10).

turbine:
  appConfig: zwitscher-service,zwitscher-board,zwitscher-edge
  instanceUrlSuffix: /admin/hystrix.stream
  instanceInsertPort: true
  aggregator:
    clusterConfig: ZWITSCHER

Über das turbine.appConfig Property wird die Liste an Eureka-Servicenamen konfiguriert, deren Hystrix-Zustandsdaten aggregiert werden sollen. Nach dem Start der Applikation kann das Hystrix Dashboard direkt unter dem per server.port Property konfigurierten Port aufgerufen werden.

Fazit und Ausblick

Der Zwitscher-Showcase zeigt, dass mit Spring Cloud einfache Cloud-native Anwendungen schnell umgesetzt werden können. Der Programmierstil ist in gewohnter Spring-Manier und geht einfach von der Hand. Alle Bausteine der Infrastruktur sind Microservices. Eine vollständige Umgebung ist deshalb mit wenigen Kommandos schnell gestartet.

Spring Cloud macht einen reifen und runden Eindruck, wird sich aber durch die hohe Dynamik im Bereich der Cloud-nativen Anwendungen stärker verändern, als man dies bei anderen Frameworks gewohnt ist. So gibt es ständig neue „Treiber“-Bausteine für aufkommende Cloud-Infrastruktur. Momentan wird z. B. gerade intensiv an einer tieferen Integration von Spring Cloud mit Kubernetes gearbeitet. Ferner deutet sich aktuell an, dass die Bedeutung von Consul bei der Service Discovery und der Service Configuration zunimmt. Die hohe Dynamik merkt man der Dokumentation an. Hier liefert ein Blick in die Beispielapplikationen oder in den Quellcode von Spring Cloud selbst oft die besseren Antworten für Fragen abseits des normalen Trampelpfads.

Im nächsten Artikel dieser Serie werden wir Zwitscher auf einem Kubernetes-Cluster zum Laufen bringen.

Aufmacherbild: spring cloud word
von Shutterstock / Urheberrecht: phloxii

Geschrieben von
Dr. Joseph Adersberger
Dr. Joseph Adersberger
Dr. Joseph Adersberger ist technischer Geschäftsführer der QAware GmbH, einem IT-Projekthaus mit Schwerpunkt auf Cloud-nativen Anwendungen und Softwaresanierung.
Mario-Leander Reimer
Mario-Leander Reimer
Mario-Leander Reimer ist Cheftechnologe bei der QAware. Er ist Spezialist für den Entwurf und die Umsetzung von komplexen System- und Softwarearchitekturen auf Basis von Open-Source-Technologien.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.