5

How To Deal With Microservice Configuration - DZone

 1 year ago
source link: https://dzone.com/articles/how-to-deal-with-microservice-configuration
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring Cloud: How To Deal With Microservice Configuration (Part 1)

In this article, we cover how to use a Spring Cloud Configuration module to implement a minimal microservice scenario based on a remote configuration.

by

·

Jan. 24, 23 · Tutorial
Like (1)
1.34K Views

Configuring a software system in a monolithic approach does not pose particular problems. To make the configuration properties available to the system, we can store them in a file inside an application folder, in some place in the filesystem, or as OS environment variables. Microservice configuration is a more complex subject. We have to deal with a number, which can be huge, of totally independent services, each with its own configuration. We could even face a scenario in which several instances of the same service need different configuration values. 

In such a situation, a way to centralize and simplify configuration management would be of great importance. Spring Cloud has its own module to solve these problems, named Spring Cloud Config. This module provides an implementation of a server that exposes an API to retrieve the configuration information, usually stored in some remote repository like Git, and, at the same time, it gives us the means to implement the client side meant to consume the services of that API.

In the first part of this article, we will discuss the basic features of this Spring Cloud module and storing the configuration in the configuration server classpath. In part two, we will show how to use other, more effective, repository options, like Git, and how to refresh the configuration without restarting the services. Then, in later posts, we will show how the centralized configuration can be coupled with service discovery features to set a solid base for the whole microservice system.

Microservice Configuration—Spring Cloud Config Server Side

The first component we need in a distributed configuration scenario is a server meant to provide the configuration information for the services. To implement such a server component by Spring Cloud Config, we have to use the right Spring Boot “starter” dependency, like in the following configuration fragment:

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

Then, we have to annotate the Spring Boot main class with the @EnableConfigServer annotation:

@SpringBootApplication
@EnableConfigServer
public class AppMain {

	public static void main(String[] args) {
		new SpringApplicationBuilder(Config.class).run(args);
	}
}

The Spring Cloud Config server, according to the auto-configuration features of Spring Boot, would run on the default 8080 port as all Spring Boot applications. If we want to customize it, we can do it by the application.properties or application.yml file:

server:  
  port: ${PORT:8888}
    
spring:
  application:
    name: config-server

If we run the application with the above configuration, it will use the 8888 port as default. We can override the default by launching the application with a different port, by the PORTplaceholder:

java -jar -DPORT=8889 sample-server-1.0-SNAPSHOT.jar

In any case, if we launch the application with the spring.config.name=configserver argument instead, the default port will be 8888. This is due to a configserver.yml default file embedded in the spring-cloud-config-server library. As a matter of fact, it would be very convenient to launch the server config application on the 8888 port, either by explicitly configuring it by the server.port parameter, like in the example above, or passing spring.config.name=configserver in the startup Java command because 8888 happens to be the default port used by the client side. 

Important Note: The spring.config.name=configserver option only works if passed in the startup command and seems to be ignored, for some reason, if set in the configuration file. We can see below an example of how to start the config server with a Java command:

java -jar -Dspring.config.name=configserver spring-cloud-config-native-server-1.0-SNAPSHOT.jar

By default, the Spring Cloud Config server uses Git as a remote repository to store the configuration data. To simplify the discussion, we will focus on a more basic approach based on files stored on the application classpath. We will describe this option in the next section. It must be stressed that, in a real scenario, this would be far from ideal, and Git would be surely a better choice.

Enforcing Basic Authentication on the Server Side

We can provide the server with a basic security layer in the form of an authentication mechanism based on user and password. To do that, we must first add the following security starter to the POM:

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

And then add the following piece of configuration in the application.yml file:

  security:
    user:
      name: myusername
      password: mypassword

With the above, the client side should be configured accordingly to be able to connect to the server, as we will see in the section related to the client side. We will discuss more advanced securing mechanisms in later articles.

Spring Cloud Config Backend Storing Options

The Spring Cloud Config server can store the configuration data in several ways:

  • By a remote Git system, which is the default.
  • Other Version Control Systems (VCS) like SVN.
  • VAULT: is a tool by HashiCorp specialized in storing passwords, certificates, or other entities as secrets.
  • By storing it in some place in the file system or the classpath.

Below, we will describe the filesystem/classpath option. Spring Cloud Config has a profile named native that covers this scenario. In order to run the config server with a filesystem/classpath backend storage, we have to start it with the spring.profiles.active=native option. 

In the native scenario, the config server will search by default in the following places:

  • classpath:/
  • classpath:/config
  • file:./
  • file:./ config

So, we can simply store the configuration files inside the application jar file. If we want to use an external filesystem directory instead or customize the above classpath options, we can set the spring.cloud.config.server.native.searchLocations property accordingly.

Config Server API

The Config Server can expose the configuration properties of a specific application by an HTTP API with the following endpoints:

  • /{ application}/{ profile}[/{ label}]: this returns the configuration data as JSON with the specific application, profile, and an optional label parameter.
  • /{ label}/{ application}-{ profile}.yml: this returns the configuration data in YAML format, with the specific application, profile, and an optional label parameter.
  • /{ label}/{ application}-{ profile}.properties: this returns the configuration data as raw text, with the specific application, profile, and an optional label parameter.

The application part represents the name of the application configured by the spring.application.name property and the profile part represents the active profile. A profile is a feature to segregate a set of configurations related to specific environments, such as development, test, and production. The label part is optional and is used when using Git as a backend repository to identify a specific branch.

Microservice Configuration—Spring Cloud Config Client Side

If we want our services to obtain their own configuration from the server, we must provide them with a dependency named spring-cloud-starter-config:

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

Clearly, the configuration must be obtained as the first step during its startup.  To deal with this requirement, Spring Cloud introduces a bootstrap context. The bootstrap context can be seen as the parent of the application context. It serves the purpose of loading configuration retrieved data from some external source and making it available to the application context.

In earlier versions of Spring Cloud, we could provide the configuration properties for the bootstrap context by a bootstrap.yml file. This is deprecated in the new versions. Now, we simply have to provide a config.import property=optional:configserver: property in the standard application.yml:

  config:
    import: "optional:configserver:"

With the optional:configserver value, the config client service will use the default http://localhost:8888 address to contact the config server. If we exclude the optional part, an error will be raised during startup if the server is unreachable. 

If we want to set a specific address and port, we can add the address part to the value like this:

  config:
    import: "optional:configserver:http://myhost:myport"

Configuring Security on the Client Side

If we have secured the server with basic authentication, we must provide the necessary configuration to the client. Adding the following piece of configuration to the application.yml will be enough:

  security:
    user:
      name: myusername
      password: mypassword

Putting the Pieces Together in a Simple Demo

Using the notions described above, we can realize a simple demo with a configuration server and a single client service, as shown in the picture below:

Server Side Implementation

To implement the server side, we create a Spring Boot application with the required Spring Cloud release train and Spring Cloud Config starter:

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>

Then, we write the application.yml file, setting the port to the conventional 8888 value, the active profile as native, and, finally, the application name:

server:  
  port: ${PORT:8888}
  
spring:
  profiles:
     active: native
  application:
    name: config-server

Since we have set the spring.profiles.activeequal to the native value, this config server application storage will be based on the filesystem/classpath. In our example, we choose to store the configuration file of the client service in the classpath in a config subdirectory of the “/resources” folder. We name the client service application file name as client-service.yml, and we fill it with the following content:

server:  
  port: ${PORT:8081}

myproperty: value  

myproperties:
   properties:
      - value1
      - value2

The mypropertyand mypropertiesparts will be used to test this minimal demo: we will expose them by some REST service on the client, and if all works as expected, the above values will be returned.

Client Side Implementation

We configure the client application with the same release train of the server. As dependencies, we have a spring-cloud-starter-config starter and also a spring-boot-starter-web because we want our application to expose some HTTP REST services: 

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

The application.yml properties will be consumed by a specific component class by the @Value and @ConfigurationProperties annotations:

@Component
@ConfigurationProperties(prefix = "myproperties")
public class DemoClient {
	private List<String> properties = new ArrayList<String>();

	public List<String> getProperties() {
		return properties;
	}

	@Value("${myproperty}")
	private String myproperty;

	public String getMyproperty() {
		return myproperty;
	}
}

Then, a controller class will implement two REST services, /getProperties and /getProperty, returning the above class properties:

@RestController
public class ConfigClientController {
	private static final Logger LOG = LoggerFactory.getLogger(ConfigClientController.class);
	
	@Autowired
	private DemoClient demoClient;
	
	@GetMapping("/getProperties")
	public List<String> getProperties() {
		LOG.info("Properties: " + demoClient.getProperties().toString());
		return demoClient.getProperties();
	}
	
	@GetMapping("/getProperty")
	public String getProperty() {
		LOG.info("Property: " + demoClient.getMyproperty().toString());
		return demoClient.getMyproperty();
	}
}

Compiling and Running the Config Server and Client Service

After compiling the two applications by Maven, we can take the resulting jars and run first the server part and then the client from the command line:

java -jar spring-cloud-config-native-server-1.0-SNAPSHOT.jar 
... 
java -jar spring-cloud-config-native-client-1.0-SNAPSHOT.jar

We can test the correct behavior by executing a call with the following address in the browser:

http://localhost:8081/getProperties

If all works as expected, we will have the following values printed on the screen:

[ "value1", "value2" ]

The Maven projects related to the demo described above are available on GitHub at the following addresses:

Conclusion

In this article, we have covered the basic notions required to configure a microservice system based on remote configuration. We have used the native approach here, using the classpath as a storage repository. In part two, we will show how to use a remote Git repository and how to refresh the configuration at runtime without restarting the services.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK