Externalizar la configuración de Spring Boot en Kubernetes con ConfigMap

spring-cloud-kubernetes

spring-cloud-kubernetes


En esta nueva entrada de refactorizando vamos a ver como externalizar la configuración de Spring Boot en Kubernetes con ConfigMap. Al igual que con Spring Cloud, podemos externalizar nuestra configuración en un Config Server con diferentes pérfiles, por ejemplo, para diferentes entornos, haciedo uso de ConfigMap de kubernetes podemos obtener el mismo resultado.

En esta entrada contaremos paso a paso mediante un ejemplo como podemos conectar nuestra aplicación Spring Boot con un ConfigMap en Kubernetes, lo que nos permitirá externalizar nuestra configuración y pudiendo cambiarla en Kubernetes directamente. Además si ya añadiemos un Bus para que nuestra configuración tome los cambios al instante sería perfecto.

Si quieres ver el Ejemplo completo lo puedes ver en nuestro github.

¿Qué es un ConfigMap de kubernetes?

Podríamos definir un ConfigMap en Kubernetes como un diccionario Clave valor en el que almacenar valores que no sean críticos en cuanto a seguridad se refiere. Por lo general se va a guardar información relativa a los pods sobre configuración, o valores que no quieres tener en tu aplicación, de forma que esta información no sea confidencial.

Configuración de ConfigMap con Spring Boot

Estructura del proyecto de externalizar la configuración de Spring Boot en Kubernetes con ConfigMap
Estructura del proyecto de externalizar la configuración de Spring Boot en Kubernetes con ConfigMap

Lo primero que vamos a hacer será añadir las dependencias Maven necesarias:

Dependencias Maven con Spring Boot y ConfigMap

La siguiente dependencia añadirá las dependencias necesarias para conectar nuestra aplicación.

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

Creación de ConfigMap para usarlo con Spring Boot

Para incluir un application.yml en un ConfigMap, se deberá crear el ConfigMap haciendo uso de la sintaxis de Kubernetes (Kind: ConfigMap) y en su interior en la parte de data, se incorporará el application.yml o application.properties. A continuación mostramos como se deberá de realizar:

kind: ConfigMap
apiVersion: v1
metadata:
  name: order-service
data:
  application.yml: |-
    server:
      port: 8080
    spring:
      application:
        name: order-service
      cloud:
        kubernetes:
          reload:
            enabled: true
        secrets:
          name: db-secret
      data:
        mongodb:
          host: mongodb-service
          port: 27017
          database: admin
          username: ${MONGO_USERNAME}
          password: ${MONGO_PASSWORD}
    management:
      endpoint:
        health:
          enabled: true
        info:
          enabled: true
        restart:
          enabled: true

En el caso en el que se quieran diferentes profiles, en los ficheros se establecerán profiles en la manera por defecto de Spring, por ejemplo:

kind: ConfigMap
apiVersion: v1
metadata:
  name: order-service
data:
  application.yml: |-
    greeting:
      message: Say something
    farewell:
      message: I Say Goodbye
    ---
    spring:
      profiles: development
    greeting:
      message: Say something
    farewell:
      message: I say hi

o se podrán applications diferentes por profile, application-dev.yml o application-pro.yml.

Configuración de los ficheros de Configuración de Spring.

Para poder hacer uso de la configuración externalizada haciendo uso de un ConfigMap, debemos de realizar una configuración através de spring.cloud.kubernetes.config.sources. Esta configuración deberá ir dentro de nuestro bootstrap.properties o bootstrap.yml. En este fichero se deberá de informar el nombre del ConfigMap así como el namespace.

En el caso en el que el nombre del ConfigMap y el Namespace no se informen se tomará el Namespace por defecto en el que se encuentre desplegada la aplicación y el ConfigMap será el del nombre de la aplicación.

Entre otras propiedades de configuración en nuetrso bootstrap.yml tendremos:

spring.cloud.kubernetes.config.enabledBooleanActiva o desactiva la configuración con configMap, por defecto es TRUE.
spring.cloud.kubernetes.config.nameStringEl nombre del ConfigMap, por defecto es Spring.application.name
spring.cloud.kubernetes.config.namespaceStringEstablece el nombre del namespace, por defecto en donde se encuentra desplegado.
Propiedades de configuración con ConfigMap de Kubernetes en Spring Cloud

A continuación vamos a escribir nuestro bootstrap.yml:

spring:
  application:
    name: order-service
  cloud:
    kubernetes:
      config:
        enabled: true

En nuestro fichero anterior hemos activado la configuración del ConfigMap, pero realmente no haría falta, una vez que se añade la dependencia de maven se activa por defecto. El nombre del ConfigMap como el namespace, no lo indicamos porque tomará el nombre de order-service, y el namespace será el de por defecto donde se encuentre desplegado.

Utilización de las propiedades de ConfigMap en Spring Boot

Una vez se ha añadido la configuración necesaria, el ConfigMap funcionará como un application.properties o application.yml de nuestra configuración. Para verlo simplemente añadiremos un @Value en nuestro controlador:

    private final OrderRepository orderRepository;

    @Value("${spring.data.mongodb.username}")
    private String username;

    @Value("${spring.data.mongodb.password}")
    private String password;

    @GetMapping("/orders")
    public ResponseEntity<List<Order>> getOrders() {

        return new ResponseEntity<>(orderRepository.findAll(), HttpStatus.OK);

    }

    @PostMapping("/orders")
    public ResponseEntity<Order> saveOrder(@RequestBody Order order) {

        return new ResponseEntity<>(orderRepository.save(order), HttpStatus.CREATED);

    }

    @GetMapping("/")
    public ResponseEntity<String> info() throws UnknownHostException {

        String serviceInfo = "Host: " + InetAddress.getLocalHost().getHostName() + "<br/>"
                + "IP: " + InetAddress.getLocalHost().getHostAddress() + "<br/>"
                + "Type: " + "Order Service" + "<br/>"
                + "Connecting to mongodb with :  username:  " + username + " password: " + password;
        return new ResponseEntity<>(serviceInfo, HttpStatus.OK);
    }
}

El @Value que hemos indicado en el controlador funcionará de manera idéntica a cuando el application.yml se encuentra en el proyecto.

Recarga de las propiedades con Spring Cloud en Kubernetes

Una de las funcionalidades que nos aporta Spring Cloud es el poder cambiar las propiedades de nuestros ficheros de configuración (Configmap o secrets) y que se apliquen a nuestra aplicación en el momento. Para ello vamos a añadir a nuestro Bootstrap.yml la siguiente configuración:

spring:
  application:
    name: order-service
  cloud:
    kubernetes:
      config:
        enabled: true
      reload:
        enabled: true
        monitoring-config-maps: true
        strategy: refresh
        mode: event

Los campos que se van encargar de dotar a nuestra aplicación estos cambios en caliente son:

spring.cloud.kubernetes.reload.enabledtrue/falseactivas o desactivas el reload de propiedades
spring.cloud.kubernetes.reload.monitoring-config-maps true/falsese activa si se monitoriza el cambio en el configmap
spring.cloud.kubernetes.reload.strategyrefresh/restart_contextestrategia refresh solo afecta al bean anotado con @RefreshScope. Restart_context recarga la aplicación.
spring.cloud.kubernetes.reload.modeevent/pollEn Event con cada cambio se genera un evento de modificación. Con Poll cada cierto tiempo se mirá si hay cambios.
Propiedades reload en Spring Cloud Config

Hay que tener en cuenta que si activamos la estrategia de refresh deberemos añadir la anotación @RefreshScope, por ejemplo:

@Configuration(proxyBeanMethods = false)
@ConfigurationProperties(prefix = "messages")
@RefreshScope
@Getter
@Setter
public class MeesageProperties {

  private String message;


}

En la anterior clase, se indica que cada vez que el configmap tenga algún cambio en message, ese valor automáticamente será capturado por la aplicación. Sin que exista en ningún momento falta de disponibilidad o que kubernetes nos pueda hacer un rollout de nuestro servicio.

Ejemplo de externalización de la configuración de Spring Boot en Kubernetes con ConfigMap

A continuación vamos a desarrollar un ejemplo en el que desplegaremos una Base de Datos Mongo, cuyas claves se encontrarán en un secret de Kubernetes. Y nuestra aplicación Spring Boot se conectará a este mongo haciendo uso de un ConfigMap que desplegaremos en nuestro Clúster de Kubernetes. Puedes seguir el ejemplo en nuestro github.

Para comenzar vamos a desplegar nuestra Base de Datos y secrets:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mongo
  name: mongodb-service
spec:
  type: NodePort
  ports:
    - name: "http"
      port: 27017
      protocol: TCP
      targetPort: 27017
  selector:
    service: mongo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mongo
spec:
  replicas: 1
  selector:
    matchLabels:
      service: mongo
  template:
    metadata:
      labels:
        service: mongo
      name: mongodb-service
    spec:
      containers:
        - args:
            - mongod
          image: mongo:latest
          name: mongo
          env:
            - name: MONGO_INITDB_ROOT_USERNAME
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: username
            - name: MONGO_INITDB_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
data:
  username: dXNlcg==
  password: cDQ1NXcwcmQ=

Para desplegar ambos ficheros en Kubernetes haremos:

Kubectl apply -f mongo-deployment.yaml
kubectl apply -f secrets.yaml

A continuación desplegaremos nuestro fichero ConfigMap.yaml, tal y como comentamos y explicamos anteriormente, este fichero tendrá la configuración de nuestro application.yml:

kind: ConfigMap
apiVersion: v1
metadata:
  name: order-service
data:
  application.yml: |-
    server:
      port: 8080
    spring:
      application:
        name: order-service
      cloud:
        kubernetes:
          reload:
            enabled: true
        secrets:
          name: db-secret
      data:
        mongodb:
          host: mongodb-service
          port: 27017
          database: admin
          username: dXNlcg==
          password: cDQ1NXcwcmQ=
    management:
      endpoint:
        health:
          enabled: true
        info:
          enabled: true
        restart:
          enabled: true


Al igual que antes haremos:

Kubectl apply -f configmap.yaml

Y finalmente configuramos nuestro bootstrap.yml, la activación de kubernetes no haría falta, ya que al tener la dependencia de maven añade la configuración del configmap por defecto.

spring:
  application:
    name: order-service
  cloud:
    kubernetes:
      config:
        enabled: true

No nos podemos olvidar añadir nuestro service account para que exista comunicación del servicio con nuestra base de datos.

Y añadimos para que exista comunicación con la base de datos nuestro service account.

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: namespace-reader
rules:
  - apiGroups: ["", "extensions", "apps"]
    resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
    verbs: ["get", "list", "watch"]

---

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: namespace-reader-binding
  namespace: default
subjects:
  - kind: ServiceAccount
    name: default
    apiGroup: ""
roleRef:
  kind: Role
  name: namespace-reader
  apiGroup: ""

A continuación solo faltaría crear la aplicación Spring Boot de manera normal. Y desplegar en Kubernetes. Para ello vamos a crear primero nuestra imagen docker:

Crear imagen docker: docker build -t order-service .

Desplegar la imagen en kubernetes, para ello vamos a crear un deployment y un service:

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - protocol: TCP
      port: 8080
      nodePort: 30081
  type: NodePort
  externalIPs:
    - 1.2.4.120
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  selector:
    matchLabels:
      app: order-service
  replicas: 2
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: order-service:latest
          imagePullPolicy: Never
          ports:
            - containerPort: 8080
          env:
            - name: MONGO_USERNAME
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: username
            - name: MONGO_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password

Y de la misma manera que antes con kubectl apply -f deployment.yml , tendríamos nuestro servicio funcionando.

No olvides que todos los ficheros de kubernetes los ejecutaremos con kubectl apply -f <nombre_fichero>

Para probarlo puedes ejecutar el siguiente comando:

minikube service order-service

Conclusión

Spring Cloud nos ofrece poder trabajar con configuración externalizada, en esta entrada externalizar la configuración de Spring Boot en Kubernetes con ConfigMap, hemos visto como poder externalizar esa configuración pero haciendo uso de kubernetes y su configmap. Si quieres ver más ejemplos con kubernetes puedes echar un ojo a este artículo.

Si te ha gustado no dudes en poner un comentario así como si tienes alguna duda.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


1 pensamiento sobre “Externalizar la configuración de Spring Boot en Kubernetes con ConfigMap

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *