Externalizar la configuración de Spring Boot en Kubernetes con ConfigMap
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
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.enabled | Boolean | Activa o desactiva la configuración con configMap, por defecto es TRUE. |
spring.cloud.kubernetes.config.name | String | El nombre del ConfigMap, por defecto es Spring.application.name |
spring.cloud.kubernetes.config.namespace | String | Establece el nombre del namespace, por defecto en donde se encuentra desplegado. |
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.enabled | true/false | activas o desactivas el reload de propiedades |
spring.cloud.kubernetes.reload.monitoring-config-maps | true/false | se activa si se monitoriza el cambio en el configmap |
spring.cloud.kubernetes.reload.strategy | refresh/restart_context | estrategia refresh solo afecta al bean anotado con @RefreshScope. Restart_context recarga la aplicación. |
spring.cloud.kubernetes.reload.mode | event/poll | En Event con cada cambio se genera un evento de modificación. Con Poll cada cierto tiempo se mirá si hay cambios. |
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!
Muy buen post. Cada vez hay mejor y más contenido en castellano.