Le but de Spring Boot Admin est de centraliser l’accès aux endpoints /actuator
d’un ensemble d’applications Spring.
C’est particulièrement utile pour des déployements en cluster, lorsque des applications sont déployées sur plusieurs instances.
Dans cette situation, il faut identifier chaque instance et faire de la réécriture de requête. Ça peut se gérer assez facilement dans un environnement à l’ancienne, avec quelques instances et nginx, ou équivalent, configuré de façon statique. Par contre, avec un déploiement plus dynamique, les choses se compliquent. C’est là que Spring Boot Admin est utile, en collectant les informations des différentes instances et centralisant les accès.
Premiers pas avec Spring Boot Admin
Spring Boot Admin est un projet géré par Johannes Edmeier et son ancien employeur Codecentric. Et même si le projet est indépendant de Spring, il est intégré au Spring Initializr.
Installation
Pour utiliser Spring Boot Admin, il faut faire sa propre application, avec le starter de.codecentric:spring-boot-admin-starter-server
, en activant le serveur avec l’annotation @EnableAdminServer
.
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
@SpringBootApplication
@EnableAdminServer
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
On va donc pouvoir le personnaliser comme n’importe quelle application Spring Boot, par exemple pour la sécurité et les restrictions d’accès.
Fonctionnalités
Spring Boot Admin a des endpoints pour la liste des applications, la liste des instances et relayer des requêtes à ces endpoints.
Il a aussi une interface graphique, qu’on peut supprimer en excluant la dépendance.
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
<exclusions>
<exclusion>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</exclusion>
</exclusions>
</dependency>
Configuration
La configuration de notre application d’admin est similaire à une application classique: port, sécurité, logging,…
Les propriétés propres à Spring Boot Admin sont dans la catégorie spring.boot.admin
.
La seule propriété qui doit être configurée concerne CORS.
En effet, sans elle le header origin
est transmis entre l’application d’admin et le endpoint d’actuator, ce qui peut causer des erreurs 403 CORS
.
Enregistrement des instances
Pour fonctionner, notre application d’admin doit connaître les instances auxquelles il devra transmettre les requêtes.
Pour les connaître, il y a 3 logiques:
-
chaque instance s’enregistre auprès du serveur,
-
le serveur est configuré pour connaître les instances,
-
le serveur interroge un registre comme Eureka, Consul ou K8S pour y récupèrer la liste des instances.
Spring Boot Admin client
Dans cette configuration, chaque instance vient s’enregistrer auprès du serveur SBA. Pour ça, on doit ajouter la dépendance au client SBA à chaque application.
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
Ensuite on configure l’URL du serveur SBA auquel elle doit s’enregistrer.
spring:
boot:
admin:
client:
url: http://localhost:9999
Ainsi, chaque instance envoie des informations pour que le serveur puisse se connecter, collecter des informations et transmettre les requêtes à l'`/actuator`.
Par défaut, le client envoie des informations de connexion basée sur le canonical hostname en utilisant la méthode InetAddress::geCanonicalHostName
.
Le problème classique, c’est que ce nom peut être un nom privé, impossible à exploiter pour le serveur.
A la place, on peut envoyer l’adresse IP, avec la propriété spring.boot.admin.client.instance.service-host-type
.
spring:
boot:
admin:
client:
instance
service-host-type: IP
Si l’actuator est sécurisé, le client doit envoyer les informations au serveur, dans les métadonnées de l’instance (spring.boot.admin.client.instance.metadata
).
spring:
boot:
admin:
client:
url: http://localhost:9999
instance:
metadata:
user.name: monitor
user.password: monitor-pwd
Si le serveur est sécurisé, le client doit avoir les informations d’authentification.
spring:
boot:
admin:
client:
url: http://localhost:9999
username: admin
password: admin-pwd
Synthèse: configuration complète du client SBA
spring:
boot:
admin:
client:
url: http://localhost:9999
username: admin
password: admin-pwd
instance:
service-host-type: IP
metadata:
user.name: monitor
user.password: monitor-pwd
Découverte statique
Avec cette solution, la liste des instances et leurs données de connexion est directement dans la configuration du serveur. Cette solution est simple à implémenter mais a l’inconvénient d’être purement statique, et donc contraire aux objectifs.
Même pour cette solution statique, il faut que le serveur ait une dépendance vers Spring Cloud.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
Il n’y a rien à configurer coté instance. Du coté du serveur, il faut ajouter les informations de connexion, avec éventuellement des informations d’authentification.
spring:
cloud:
discovery:
client:
simple:
instances:
api:
- uri: https://api1.example.com
metadata:
user.name: monitor
user.password: monitor-pwd
- uri: https://api2.example.com
Découverte Kubernetes
Si l’application est déployée sur un Kubernetes, Spring est capable de découvrir la topologie du cluster grâce à l’API de Kubernetes.
Pour exploiter cette possibilité, il faut que le serveur SBA ait une dépendance vers le service de découverte pour Kubernetes.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
</dependency>
L’ajout du starter suffit à activer la découverte. Il faut toutefois que les permissions soient activées au niveau de l’API Kubernetes (commande testée avec Minikube, sur le namespace par défaut).
~$ kubectl create clusterrolebinding make-default-sa-cluster-admin \
--serviceaccount=default:default --clusterrole=cluster-admin
Il faut aussi activer le scheduling pour que la recherche des instances se fasse à intervalle régulier.
@SpringBootApplication
@EnableAdminServer
@EnableScheduling
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
Enfin, il faut filtrer les services par label afin de ne récupérer que les nodes Spring qui ont un actuator.
spring:
cloud:
kubernetes:
discovery:
service-labels:
category: application
Si les actuateurs sont sécurisés, on peut renseigner le nom d’utilisateur et le mot de passe au niveau global, pour toutes les instances.
spring:
boot:
admin:
instance-auth:
default-user-name: monitor
default-password: monitor-pwd
La configuration spécifique d’une instance est prioritaire par rapport à cette configuration globale. Pour ça, il faut ajouter les informations au niveau des labels du service Kubernetes.
apiVersion: v1
kind: Service
metadata:
name: app1
labels:
category: application
user.name: monitor
user.password: monitor-psswd
Attention, il faut bien mettre user.name
ET user.password
.
Avec un seul des deux, il sera ignoré.
API
Je n’ai pas trouvé de documentation de l’API. On peut la découvrir en parcourant l’interface graphique ou en parcourant le code.
J’ai essayé d’ajouter springdoc, mais il ne détecte pas automatiquement les endpoints. |
Applications
-
GET {{admin.url}}/applications
liste des applications avec la liste des instances par application -
GET {{admin.url}}/applications/{name}
détail d’une application, avec la liste de ses instances -
XXX {{admin.url}}/applications/{name}/actuator/{endpoint}
endpoint de l’actuator d’une application, la requête est envoyée à chaque instance et la réponse aggrège les différentes réponses
Instances
-
GET {{admin.url}}/instances
liste des instances -
GET {{admin.url}}/instances?name={name}
liste des instances, peut être limitée à une seule application avec le paramètrename
-
GET {{admin.url}}/instances/{id}
détail d’une instance
Actuators
Le serveur SBA fonctionne en relais entre le client et l’actuateur. Par conséquents les endpoints disponibles dépendent des endpoints activés pour chaque application et instance.
Les endpoints sont généralement appelés pour une instance.
-
XXX {{admin.url}}/instances/{id}/actuator/{endpoint}
endpoint de l’actuator d’une instance, quelle que soit la méthode GET, POST,…; la racine n’est pas accessible
Exemple pour /loggers
-
GET {{admin.url}}/instances/{id}/actuator/loggers
liste et configuration des loggers de l’instance -
GET {{admin.url}}/instances/{id}/actuator/loggers/{logger.name}
configuration d’un' logger de l’instance -
POST {{admin.url}}/instances/{id}/actuator/loggers/{logger.name}
modification de la configuration d’un' logger de l’instance
Il y a aussi la possibilité d’appeler un endpoint pour une application. Dans ce cas, la requête est transmise à chaque instance et la réponse est aggrégée.
-
XXX {{admin.url}}/applications/{name}/actuator/{endpoint}
endpoint de l’actuator d’une application
Exemple pour /loggers
-
POST {{admin.url}}/applications/{name}/actuator/loggers/{logger.name}
modification de la configuration d’un' logger pour toutes les instances de l’application
Tips
CORS
Le header Origin est transmis à l’instance, ce qui peut poser des erreurs CORS sur les POST
Il y a 2 solutions:
-
ajouter SBA dans les URLs autorisées par l’instance,
-
empécher le transfert du header Origin.
Je préfère largement la 2°, d’autant qu’elle est assez simple à mettre en oeuvre, coté SBA, avec la propriété spring.boot.admin.instance-proxy.ignored-headers
.
spring:
boot:
admin:
instance-proxy:
ignored-headers: Cookie, Set-Cookie, Authorization, Origin
La valeur par défaut de cette propriété est Cookie, Set-Cookie, Authorization
, j’y ajoute Origin
.
Il faut noter que les headers suivants sont ignorés quelle que soit la configuration, car rangés dans la catégorie hop_by_hop: "Host", "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade", "X-Application-Context".
oauth2
L’authentification par oauth2 n’est que partiellement supportée.
SBA peut être sécurisé en utilisant une authentification oauth2 avec une validation des token par JWKS.
Par contre pe n’ai rien trouvé pour l’interface graphique concernant le support d’authentification oauth2 et de token. De plus, en enregistrement par le client il faut un autre mode.
De même pour la sécurisation des actuators, si on utilise oauth2, il faut aussi un mode annexe car SBA vient interroger périodiquement les endpoints de l’actuator, indépendamment de toute requête d’utilisateur.
ID d’instances
Les IDs des instances survivent au redémarrage de SBA. Ils sont calculés à partir de l’URL du service. C’est le SHA-1 de de l’URL de health, en hexadécimal.