Spring Boot Config Server und Client mit Git-Repository

01.06.2016

Spring Boot - Config Server

In diesem Artikel soll gezeigt werden wie der Spring Config Server verwendet werden kann, um Konfigurationen für Microservices zentral zu verwalten. Dafür wird ein Config Server erstellt, der die Konfiguration in einem git-Repository speichert. Außerdem wird gezeigt, wie ein Client die Konfigurationen beim Server abfragt und wie mit Hilfe des Actuator Konfigurationsänderungen auch zur Laufzeit ohne Neustart der Applikation verteilt werden. Meine Motivation für den Config Server war, dass in dem Artikel zum Netatmo-Service die Konfiguration komplett in der Datei application.properties gespeichert wird und die Datei in dem jar verpackt ist, so dass zur Laufzeit keine Änderungen möglich sind. Das wird durch den Config Server gändert. Los geht es auf der Seite start.spring.io. Wo der Config Server ausgewählt wird um dann anschliessend das Projekt-Template zu generieren.

Das Template wird anschliessend in einer IDE geöffnet. In der Klasse ConfigurationService muss die Annotation @EnableConfigServer ergänzt werden.

@SpringBootApplication
@EnableConfigServer
public class ConfigurationServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConfigurationServiceApplication.class, args);
	}
}

In der Datei application.properties müssen dem Config Server mitgeteilt werden auf welchem Port er starten soll und wo die Konfigurationen zu finden sind:

Der Port 8888 ist der Standard-Port unter dem die Clients den Config Server suchen, wenn kein anderer Port für den Config Server im Client angegeben ist.

spring.cloud.config.server.git.uri=/Users/hameister/Documents/ConfigServerGit
server.port=8888

In dem Git-Repository befinden sich alle Konfigurationsdateien, die der Config Server zur Verfügung stellt. Wenn man Konfigurationen für mehrere Services über den Config Server zur Verfügung stellen möchte, ist das auch möglich. Damit das funktioniert muss der Dateiname mit dem Servicenamen übereinstimmen.

Existiert beispielsweise ein Service mit dem Namen: spring.application.name=date-service, dann muss die Datei date-service.properties heißen.

Anlegen eines Git-Repositories mit:

git init

in dem gewünschten Verzeichnis in einem Terminal. Anschließend die Konfigurationsdatei in das Verzeichnis legen und in das Repository einchecken.

git add date-service.properties && git commit -m "Checkin"

Weitere Erklärungen zu Git findet man im Git-Tutorial

Anzumerken ist, dass Konfigurationsdateien auch im YAML-Format gespeichert werden können. Der Server kann nun gestartet werden und ist anschliessend auf dem Port 8888 erreichbar und eine Konfiguration für den hello-service ist verfügbar.

Zum Testen des Config Servers kann man einen kleinen Service (hello-service) erstellen, der über eine Rest-Schnittstelle bei einem HTTP-GET eine Begrüßung mit einem konfigurierten Namen zurückliefert.

Die Spring Boot Applikation sieht folgendermaßen aus:

package org.hameister;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConfigClientApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConfigClientApplication.class, args);
	}
}

Der Rest Controller mit der sayHello()-Methode sieht so aus:

package org.hameister;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

/**
 * Created by hameister on 01.06.16.
 */
@RestController
@EnableAutoConfiguration
public class HelloController {

    @Value("${nameConfig}")
    private  String name;

    @RequestMapping(value = "/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    ResponseEntity<String> sayHello() {
        return new ResponseEntity<String>("Hello: "+name, HttpStatus.OK);
    }
}

Mit der Zeile @Value("${nameConfig}") sorgt man dafür, dass der Wert bei Config Server abgefragt wird, sofern er nicht in der Datei application.properties existiert.

Die notwendige Maven pom-Datei mit allen Abhängigkeiten sieht folgendermaßen aus:

  <?xml version="1.0" encoding="UTF-8"?>
  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  	<modelVersion>4.0.0</modelVersion>

  	<groupId>org.hameister</groupId>
  	<artifactId>ConfigClient</artifactId>
  	<version>0.0.1-SNAPSHOT</version>
  	<packaging>jar</packaging>

  	<name>ConfigClient</name>
  	<description>Demo project for Spring Boot</description>

  	<parent>
  		<groupId>org.springframework.boot</groupId>
  		<artifactId>spring-boot-starter-parent</artifactId>
  		<version>1.3.5.RELEASE</version>
  		<relativePath/> <!-- lookup parent from repository -->
  	</parent>

  	<properties>
  		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  		<java.version>1.8</java.version>
  	</properties>

  	<dependencies>
  		<dependency>
  			<groupId>org.springframework.boot</groupId>
  			<artifactId>spring-boot-starter-web</artifactId>
  		</dependency>

  		<dependency>
  			<groupId>org.springframework.boot</groupId>
  			<artifactId>spring-boot-starter-test</artifactId>
  			<scope>test</scope>
  		</dependency>

  		<dependency>
  			<groupId>org.springframework.cloud</groupId>
  			<artifactId>spring-cloud-starter-config</artifactId>
  		</dependency>

  	</dependencies>


  	<dependencyManagement>
  		<dependencies>
  			<dependency>
  				<groupId>org.springframework.cloud</groupId>
  				<artifactId>spring-cloud-dependencies</artifactId>
  				<version>Brixton.RELEASE</version>
  				<type>pom</type>
  				<scope>import</scope>
  			</dependency>
  		</dependencies>
  	</dependencyManagement>

  </project>
 

In der Datei application.properties sollte ein Service-Name vergeben werden, damit die Anwendung beim Server nach der passenden Konfiguration fragen kann.

Also beispielsweise spring.application.name=hello-service, wobei die Konfigurationsdatei auf der Server dann hello-service.properties heissen muss.

Wenn man dem Service keinen Namen gibt oder die Datei nicht gefunden wird, dann schaut der Config Server per default in die Datei application.properties, um den angeforderten Wert zu finden.

Wenn man Änderungen an der Konfigurationsdatei vornimmt, dann darf nicht vergessen werden die Änderungen im Git-Repository einzuchecken.

Der Actuator in der pom.xml sorgt dafür, dass bei einer Änderung an der Konfiguration der Client nicht neu gestartet werden muss, damit die Konfiguration auch dort verfügbar ist, weil der Actuator einen neuen REST-Endpoint zur Verfügung stellt durch den mittels eines HTTP-POST dafür gesorgt werden kann, dass die Konfiguration erneut beim Server geholt wird.

Dazu ruft man einfach ein POST mit curl auf. (Unter der Annahme, dass der Hello-Service unter Port 8080 läuft.)

 curl -d {} http://localhost:8080
 

Durch den Aufruf fragt der Hello-Service die Konfiguration erneut beim Config Server ab. Anzumerken ist, dass das Verteilen von Konfigurationen auch mit einer Reihe von anderen Methoden möglich ist.

Was hier nicht besprochen wurde sind Themen, wie Security, d.h. Transport-Verschlüsselung der Konfigurationsdaten und verschlüsselte Ablage der Konfigurationen. Das sollte beim produktiven Einsatz auf alle Fälle bedacht werden...