Platform engineering et internal developer platform : concepts et applications
1. Aperçu
À mesure que le nombre de services augmente, chaque équipe doit gérer elle-même son CI/CD, son infrastructure, ses logs, sa sécurité et sa documentation. Elles passent beaucoup de temps sur des tâches répétitives au lieu de développer des fonctionnalités. Le platform engineering résout ce problème en regroupant tout cela dans une internal developer platform (IDP) offrant l’auto-service aux développeurs.
À la fin de cet article, vous comprendrez le concept et les raisons de son intérêt croissant. Vous pourrez aussi construire un petit « golden path » avec un starter Spring Boot, un service catalog et une vérification de politique simple. La fin de l’article couvre les cas où il ne faut pas investir dans une plateforme.
2. Qu’est-ce que le platform engineering ?
Le platform engineering est le travail d’une équipe dédiée (platform team) qui construit et exploite un ensemble d’outils, de processus et de services internes. Les équipes produits (stream-aligned teams) utilisent cet ensemble pour compiler, déployer et opérer les services sans avoir besoin de maîtriser les détails de chaque couche d’infrastructure sous-jacente.
2.1. Internal developer platform
Une IDP est le produit créé par la platform team. Ce n’est généralement pas un outil unique. C’est une couche d’intégration qui regroupe plusieurs éléments déjà existants : Git, CI, Kubernetes, cloud, gestionnaire de secrets, observabilité. Les développeurs interagissent avec cette couche via un portail, une CLI, des templates ou simplement un fichier de configuration dans le dépôt.
2.2. Trois idées centrales
- Golden path (paved road) : le chemin recommandé et supporté pour accomplir une tâche commune, par exemple « créer un nouveau service REST ». Suivre ce chemin est rapide et sûr. Il est possible de prendre un autre chemin, mais l’équipe en assume la responsabilité.
- Self-service : les développeurs créent eux-mêmes les services, les bases de données ou les environnements de test sans avoir à ouvrir un ticket et attendre quelqu’un d’autre.
- Platform as a product : les développeurs internes sont les clients. La platform team doit les interviewer, mesurer l’utilisation et prioriser le backlog comme une véritable équipe produit.
2.3. Différence avec DevOps ?
DevOps est une culture : l’équipe qui écrit le code est aussi responsable de son exploitation (« you build it, you run it »). Le platform engineering ne remplace pas DevOps. C’est une façon de réaliser DevOps à grande échelle. Au lieu de demander à chaque équipe de devenir experte en Kubernetes, Terraform et IAM, la platform team encapsule cette connaissance dans des abstractions faciles à utiliser.
3. Pourquoi ce sujet suscite-t-il l’intérêt ?
Il y a plusieurs raisons pratiques, indépendantes de tout outil spécifique :
- Charge cognitive croissante : un service moderne peut nécessiter un Dockerfile, un manifeste Kubernetes, un pipeline, un tableau de bord, des alertes, des politiques de sécurité et des configurations de secrets. Chacun de ces éléments offre de nombreuses options.
- Un style par équipe : sans normes communes, chaque service enregistre les logs dans un format différent et nomme les métriques à sa manière. En cas d’incident, il est difficile pour la personne de garde de trouver quelle équipe possède ce service.
- Exigences de sécurité et de conformité : scanner les dépendances, signer les artefacts, contrôler l’accès. Ces tâches sont faciles à appliquer uniformément si elles sont intégrées dès le départ dans le golden path.
- Onboarding lent : les nouveaux développeurs doivent savoir « où créer un service, comment le déployer, où voir les logs ». Une bonne plateforme répond à toutes ces questions au même endroit.
4. Composants courants d’une IDP
| Composant | Rôle |
|---|---|
| Service catalog | Liste des services, propriétaires, documentation, dépendances |
| Software template | Créer de nouveaux projets conformes aux normes avec quelques paramètres |
| CI/CD partagé | Pipelines prêts à l’emploi pour la compilation, les tests, l’analyse et le déploiement |
| Infrastructure self-service | Créer des bases de données, des files d’attente, des buckets via une configuration déclarative |
| Observabilité par défaut | Logs, métriques et traces prêts à l’emploi au démarrage du service |
| Guardrails / policies | Vérification automatique des règles obligatoires |
Vous n’avez pas besoin de tous les éléments dès le départ. Les sections suivantes illustrent trois petits éléments : un starter Spring Boot pour le golden path, un descripteur de catalogue et une vérification de politique.
5. Golden path avec un starter Spring Boot
Le moyen le moins coûteux de normaliser les services Java est de publier un starter interne. Tout service ajoutant ce starter aura automatiquement des métadonnées standard : nom, équipe propriétaire, niveau d’importance. Les métadonnées sont exposées via actuator.
5.1. Dépendances
Le starter interne a besoin des dépendances suivantes :
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Actuator est marqué comme optional. Les services qui n’utilisent pas actuator peuvent toujours utiliser le starter.
5.2. Properties et descripteur
package dev.nonla.platform;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("platform.service")
public record PlatformServiceProperties(String name, String owner, String tier) {
public PlatformServiceProperties {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("platform.service.name is required");
}
if (owner == null || owner.isBlank()) {
throw new IllegalArgumentException("platform.service.owner is required");
}
if (tier == null || tier.isBlank()) {
tier = "standard";
}
}
}
package dev.nonla.platform;
public record ServiceDescriptor(String name, String owner, String tier) {
}
Le constructeur compact de l’enregistrement fait deux choses :
- Force la déclaration de
nameetowner. Si l’un d’eux manque, l’application ne démarre pas. C’est un guardrail : pas de service « orphelin ». - Assigne la valeur par défaut
standardàtiers’il n’est pas déclaré.
5.3. Auto-configuration
package dev.nonla.platform;
import java.util.Map;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@AutoConfiguration
@EnableConfigurationProperties(PlatformServiceProperties.class)
public class PlatformServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ServiceDescriptor serviceDescriptor(PlatformServiceProperties props) {
return new ServiceDescriptor(props.name(), props.owner(), props.tier());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(InfoContributor.class)
static class ActuatorConfiguration {
@Bean
InfoContributor platformInfoContributor(ServiceDescriptor descriptor) {
return builder -> builder.withDetail("platform", Map.of(
"name", descriptor.name(),
"owner", descriptor.owner(),
"tier", descriptor.tier()));
}
}
}
Explication de chaque partie :
@ConditionalOnMissingBeanpermet au service de définir son propreServiceDescriptor. C’est une « échappatoire » au golden path si vraiment nécessaire.- La classe imbriquée
ActuatorConfigurationn’est chargée que siInfoContributorest sur le classpath. Ainsi, les services qui n’utilisent pas actuator ne rencontreront pas deClassNotFoundException.
Pour que Spring Boot 3 découvre cette auto-configuration, enregistrez-la dans le fichier src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports :
dev.nonla.platform.PlatformServiceAutoConfiguration
Du côté du service, par exemple order-service, il suffit d’ajouter le starter et quelques lignes de configuration :
platform.service.name=order-service
platform.service.owner=team-checkout
platform.service.tier=critical
management.endpoints.web.exposure.include=health,info
management.info.env.enabled=false
Un appel à GET /actuator/info retournera :
{"platform":{"name":"order-service","owner":"team-checkout","tier":"critical"}}
À partir de là, les outils de surveillance ou le catalogue peuvent scraper ce point de terminaison pour savoir qui possède quel service.
5.4. Test du comportement du starter
L’équipe platform doit tester le starter minutieusement. Une erreur ici affecterait tous les services qui l’utilisent. ApplicationContextRunner aide à tester l’auto-configuration rapidement sans démarrer un serveur web complet :
package dev.nonla.platform;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class PlatformServiceAutoConfigurationTest {
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(PlatformServiceAutoConfiguration.class));
@Test
void givenNameAndOwner_whenStarting_thenDescriptorUsesDefaultTier() {
runner.withPropertyValues(
"platform.service.name=order-service",
"platform.service.owner=team-checkout")
.run(ctx -> {
assertThat(ctx).hasSingleBean(ServiceDescriptor.class)
.hasSingleBean(InfoContributor.class);
assertThat(ctx.getBean(ServiceDescriptor.class).tier())
.isEqualTo("standard");
});
}
@Test
void givenMissingOwner_whenStarting_thenContextFails() {
runner.withPropertyValues("platform.service.name=order-service")
.run(ctx -> {
assertThat(ctx).hasFailed();
assertThat(ctx.getStartupFailure())
.rootCause()
.hasMessageContaining("platform.service.owner");
});
}
}
Le premier test confirme la valeur par défaut et l’enregistrement de InfoContributor. Le second test confirme le guardrail : en l’absence de owner, le contexte ne démarre pas et le message d’erreur indique clairement l’attribut manquant.
6. Catalogue de services et guardrails
6.1. Descripteur de catalogue dans le dépôt
La plupart des IDP stockent les métadonnées du service directement dans le dépôt, à côté du code. Voici un descripteur selon le style couramment utilisé dans les portails open source :
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: order-service
description: Traite les commandes et les paiements
tags:
- java
- spring-boot
spec:
type: service
lifecycle: production
owner: team-checkout
system: checkout
dependsOn:
- resource:orders-db
providesApis:
- order-api
Conserver ce fichier dans Git offre deux avantages. Les métadonnées sont examinées avec le code, et elles ne s’écartent pas de la réalité comme une page wiki oubliée. Le portail n’a besoin que de lire ce fichier pour afficher le propriétaire, les dépendances et l’API du service.
6.2. Vérification de politique simple
Les guardrails n’ont pas nécessairement besoin d’un moteur de politique complexe. Au départ, un simple code exécuté dans le CI suffit pour bloquer les mauvaises configurations :
package dev.nonla.platform.policy;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ServicePolicy {
private static final Set<String> ALLOWED_TIERS = Set.of("critical", "standard", "experimental");
public record ServiceSpec(String name, String owner, String tier, int replicas) {
}
public List<String> violations(ServiceSpec spec) {
List<String> errors = new ArrayList<>();
if (!spec.name().matches("[a-z][a-z0-9-]{2,40}")) {
errors.add("name must be kebab-case, 3-41 chars");
}
if (!spec.owner().startsWith("team-")) {
errors.add("owner must be a team, not a person");
}
if (!ALLOWED_TIERS.contains(spec.tier())) {
errors.add("unknown tier: " + spec.tier());
}
if ("critical".equals(spec.tier()) && spec.replicas() < 2) {
errors.add("critical services need at least 2 replicas");
}
return errors;
}
}
Avec l’entrée new ServiceSpec("Order_Service", "alice", "critical", 1), la fonction retourne :
[name must be kebab-case, 3-41 chars, owner must be a team, not a person, critical services need at least 2 replicas]
Le point important est que les messages d’erreur doivent indiquer clairement ce qui doit être corrigé. Un bon guardrail guide le développeur vers la correction, il ne l’arrête pas simplement.
7. Mise en pratique : par où commencer ?
- Interrogez d’abord les développeurs. Demandez-leur où ils perdent le plus de temps : créer un nouveau service, attendre de l’infrastructure, ou déboguer le pipeline. Priorisez le problème qui fait le plus mal, pas l’outil le plus passionnant.
- Commencez par une « thinnest viable platform ». Parfois, un simple template de dépôt, un pipeline partagé et une page de documentation suffisent. Le portail peut venir plus tard.
- Choisissez un seul golden path. Par exemple « service REST Spring Boot déployé sur Kubernetes ». Perfectionnez ce chemin, puis élargissez.
- Mesurez. Suivez le nombre d’équipes utilisant la plateforme, le temps entre la création du dépôt et le premier déploiement, et les retours réguliers des développeurs. Si personne n’utilise volontairement, c’est un signal que le produit n’est pas au point.
- Gardez une échappatoire. Le golden path devrait être le chemin le plus facile, pas le seul chemin obligatoire. Comme dans la section 5.3,
@ConditionalOnMissingBeanen est un petit exemple. - Versioning et dépréciation clairs. Les starters, les templates et les pipelines partagés sont tous des API. Les changements incompatibles doivent être annoncés et avoir une feuille de route de migration.
8. Erreurs courantes et quand ne pas utiliser
Les erreurs courantes :
- Construire le portail avant d’avoir du contenu. Un beau portail avec un catalogue vide et des templates obsolètes sera rapidement oublié.
- Renommer l’équipe ops en platform team. Si les développeurs doivent toujours ouvrir des tickets et attendre, ce n’est pas du self-service.
- Abstraction trop épaisse. Masquer complètement Kubernetes empêche les développeurs de déboguer en cas de problème. La plateforme doit réduire la charge cognitive, pas éliminer la capacité à voir les couches inférieures.
- Appliquer par obligation plutôt que par valeur. Si vous devez donner des ordres pour que les gens l’utilisent, cela signifie que la plateforme ne résout pas vraiment le problème.
- Starter « qui fait tout ». Un starter qui entraîne trop de dépendances et de configuration implicite causera des conflits de versions et rendra les mises à jour difficiles.
Note : Le platform engineering a un coût fixe considérable. Vous avez besoin de gens pour construire, exploiter et supporter la plateforme elle-même.
Quand ne pas investir dans une IDP dédiée :
- Petite entreprise avec seulement quelques équipes et services. Un bon README avec un pipeline partagé suffit généralement.
- Produit en recherche de product-market fit avec une architecture qui change constamment. Normaliser trop tôt verrouille des choix qui vont bientôt changer.
- Les services gérés du cloud répondent déjà à vos besoins. Construire votre propre couche équivalente ajoute juste de la maintenance.
- Personne n’est prête à être product owner de la plateforme. Sans quelqu’un pour écouter les développeurs, la plateforme devient une collection d’outils que personne ne veut utiliser.
9. Conclusion
Le platform engineering n’est pas un nouvel outil. C’est une façon d’organiser le travail : une équipe qui considère l’expérience des développeurs internes comme un produit, puis encapsule les connaissances en infrastructure dans un golden path et du self-service. Vous avez vu trois petits éléments : un starter Spring Boot qui normalise les métadonnées avec des tests, un descripteur de catalogue dans le dépôt et une vérification de politique dans le CI.
Commencez petit, mesurez vraiment, et n’élargissez que quand les développeurs utilisent réellement. Si votre organisation est petite, un bon template et un pipeline partagé peuvent offrir la plupart des avantages sans avoir besoin d’une plateforme complète.