Spring Cloud Kubernetes e ConfigMap

Ogni applicazione può avere la necessità di una configurazione, esternalizzata solitamente in dei file di properties che possono poi essere letti a runtime e magari cambiare in base all’ambiente in cui ci troviamo. Questa necessità è ovviamente presente anche in ambiente cloud native come può essere quello di Kubernetes. In questo articolo vedremo quindi come poter sfruttare al meglio le potenzialità di Kubernetes per configurare le nostre applicazioni.

Kubernetes Configmap

All’interno di Kubernetes abbiamo la possibilità di utilizzare un ConfigMap, un componente nativo dove gestire le configurazioni. Quest’ultimo permette di separare le informazioni di configurazione dal nostro Pod (e quindi dalla nostra applicazione). Nella definizione del Pod possiamo “iniettare” queste informazioni in diverse modalità, come ad esempio quella d’inserire i dati del ConfigMap nella variabili d’ambiente. Di seguito vediamo come creare uno di questi componenti a partire da un file di properties

federico@test:~$ kubectl create configmap test --from-file=configmap.properties 
configmap/test created
federico@test:~$ kubectl describe configmaps test
Name:         test
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
configmap.properties:
----
myconfig.properties1=First property
myconfig.properties2=Second property
myconfig.subconfig.properties1=First sub property
myconfig.subconfig.properties2=Second sub property
Events:  <none>

Ora invece vediamo come referenziare queste proprietà all’interno di un nostro Pod

apiVersion: v1
kind: Pod
metadata:
  name: test-configmap-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "env" ]
      env:
        - name: MYCONFIGMAP_INFORMATION
          valueFrom:
            configMapKeyRef:
              name: test
              key: configmap.properties
  restartPolicy: Never

Una volta avviato il Pod possiamo vedere dal log che sarà presente tra le variabili d’ambiente anche MYCONFIGMAP_INFORMATION che contiene tutto il file di properties.

Fabric8

Possiamo accedere al ConfigMap da riga di comando con kubectl o anche attraverso delle librerie che conoscono le API di Kubernetes. In Java è possibile attraverso la client library messo a disposizione da Fabric8, che come potete vedere dal seguente esempio è estremamente semplice nell’utilizzo.

package com.javastaff.spring.k8config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.DoneableConfigMap;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.Resource;
/**
 * Example read ConfigMap using Fabric8 library
 *
 */
public class Fabric8Example {
  private static final Logger logger = LoggerFactory.getLogger(Fabric8Example.class);

  public static void main(String[] args) throws InterruptedException {
    Config config = new ConfigBuilder().build();
    KubernetesClient client = new DefaultKubernetesClient(config);
    String namespace = "default";
    String name = "test";
    try {
      Resource<ConfigMap, DoneableConfigMap> configMapResource = 
    		  client.configMaps().inNamespace(namespace).withName(name);
      ConfigMap configMap=configMapResource.get();
      logger.info(configMap.getData().toString());
    } finally {
      client.close();
    }
  }
}

Attraverso questa libreria possiamo leggere ma volendo anche aggiungere altre informazioni al ConfigMap

Spring Cloud Kubernetes

Spring Cloud Kubernetes è un implementazione dell’interfaccia di Spring Cloud, che permette di gestire i servizi di Kubernetes seguendo l’astrazione definita con Spring Cloud. Questa implementazione usa la libreria client Fabric8 già citata per portare nel “mondo” Spring tutte le feature di Kubernetes. Per i dettagli di tutto quello che è possibile fare vi rimando alla documentazione ufficiale.

Nel nostro caso utilizzeremo solo la parte relativa ai ConfigMap, quindi andremo a definire un progetto Spring Boot e ad aggiungere le dipendenze necessarie per includere ciò che ci interessa. Di seguito viene riportato il pom.xml del nostro progetto d’esempio

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>com.javastaff.spring</groupId>
	<artifactId>k8config</artifactId>
	<version>1.0</version>
	<description>example project for spring properties on kubernetes</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.6.RELEASE</version>
	</parent>

	<properties>
		<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-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
			<version>1.0.2.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
	</dependencies>
</project>

Essendo un’applicazione Spring Cloud dovremo poi andare a definire un file bootstrap.yml dove in questo caso specifichiamo le informazioni necessarie a Spring Cloud Kubernetes per reperire il nostro ConfigMap

management:
  endpoint:
    restart.enabled : true
    health.enabled : true
    info.enabled : true

spring:
  application:
    name: k8config
  cloud:
    kubernetes:
      reload:
        period: 1000
        enabled: true
      config:
        enabled: true
        name: k8config
        namespace: default
        sources:
         - name: myk8config

Questa configurazione, che abbiamo definito essere ricaricabile (spring.cloud.kubernetes.reload.enabled=true), andrà a leggere un ConfigMap di nome myk8config dal namespace default. Le informazioni presenti saranno quindi iniettate come properties nel contesto di Spring, per questo motivo abbiamo definito un oggetto che mappa un @ConfigurationProperties

package com.javastaff.spring.k8config;

import java.io.Serializable;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Configuration
@ConfigurationProperties(prefix = "myconfig")
@Data @AllArgsConstructor @NoArgsConstructor
public class Config implements Serializable{

	private static final long serialVersionUID = -8900976432592584351L;
	private String properties1;
	private String properties2;
	private SubConfig subconfig;
}

Con un endpoint REST andremo poi a definire una chiamata GET che ritorna qualche informazione di debug sui properties che abbiamo definito

package com.javastaff.spring.k8config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestConfigController {
	
	@Autowired
	private Config config;
	
	@Value("${single.property}") 
	private String singleProperty;
	
    @GetMapping
    public String load() {
    	StringBuilder sb=new StringBuilder();
    	sb.append("single.property : ")
    	  .append(singleProperty)
    	  .append(" ----- ")
    	  .append("config: ")
    	  .append(config.toString());
        return sb.toString();
    }
}

Abbiamo definito un oggetto di tipo Config come Autowired mentre invece una proprietà singola l’abbiamo mappata su una stringa tramite l’annotation @Value. Avviando il progetto Spring Boot potremo quindi vedere che all’url http://localhost:8080 avremo stampate le properties del ConfigMap e che se proviamo a modificare il contenuto attraverso kubectl, le modifiche verranno propagate anche agli oggetti presenti nella nostra applicazione. Di seguito il link al progetto d’esempio presente su Github. Buon divertimento con Spring Cloud Kubernetes 🙂

Example using Kubernetes Configmap with Spring Cloud Kubernetes
https://github.com/fpaparoni/spring-cloud-configmap
3 forks.
1 stars.
0 open issues.

Recent commits:

Federico Paparoni

Looking for a right "about me"...