
Farm passe en open source: des environnements de preview sans douleur
Farm passe en open source: des environnements de preview sans douleur
Comment Farm est passé d’un script self-hosted sur une seule VM à un orchestrateur d’environnements de preview sur Docker et Kubernetes — et pourquoi nous avons ouvert son code.

Une pull request, on ne veut pas seulement qu’elle passe les tests — on veut la montrer en action: au designer, au manager, à l'équipe voisine, pour que tout ce monde puisse cliquer un peu partout dans l’UI — et s’en réjouir, ou dénicher des bugs — et s’en désoler. Rien de sorcier en apparence: on monte un environnement et on partage un lien. Mais quand les pull requests tombent par dizaines chaque jour et que les équipes sont légion, « simplement monter un environnement » se transforme en véritable projet d’infrastructure.
Bonjour! Je m’appelle Mikhail Golbakh et je suis développeur frontend senior chez Yandex Cloud. Depuis plusieurs années, mon équipe et moi développons Farm — un service qui monte un environnement de preview pour chaque pull request. Dans cet article, je vais vous raconter comment Farm est passé d’un simple script auto-hébergé sur une seule machine virtuelle à un orchestrateur fonctionnant sur Docker et Kubernetes — et pourquoi nous avons fini par ouvrir son code.
Comment nous sommes arrivés à la première version de Farm
Yandex compte de nombreuses équipes frontend qui créent des centaines de pull requests par jour. Chaque pull request déclenche une multitude de tests et d’autres vérifications en CI. Tests unitaires, vérifications de types, linters: tout cela tourne sans problème sur des machines virtuelles isolées.
Mais que faire quand il faut monter une application complète depuis une branche? De cette question découle une tâche que les développeurs frontend rencontrent en permanence: monter une « bêta » pour chaque pull request. La première idée qui vient à l’esprit est de mettre en place un environnement de dev dédié, à l’image de la production, et de le partager entre développeurs. C’est souvent ce que font les petites équipes à leurs débuts, mais elles se heurtent vite à un problème de mise à l'échelle. Un environnement unique ne peut plus être partagé, et impossible d’y faire tourner de vrais tests e2e — leur stabilité soulèverait clairement des questions.
Traditionnellement, chaque équipe met en place son propre processus de déploiement des environnements de dev, en y consacrant des efforts non négligeables et des ressources pour la maintenance. Nous n’avons pas fait exception et avons élaboré notre propre solution. Mais nous l’avons pensée dès le départ comme une technologie commune, capable de s'étendre à des dizaines d'équipes malgré d'éventuelles différences d’infrastructure.
En 2018 déjà, l'équipe frontend de Yandex Cloud a construit la première version de Farm — au fond, un simple service auto-hébergé qui téléchargeait le code depuis une branche du dépôt, exécutait les commandes de build de l’application cible et lançait son processus sur une VM, en acheminant le trafic depuis un domaine dédié. Même le jeu de commandes était codé en dur: Farm visait avant tout nos propres projets, construits sur des composants communs pour les services web en Node.js. Le résultat: une sorte de système de déploiement simplifié à la sauce Yandex.
Au fil des années, le service Farm a évolué: il a appris à travailler avec des configurations de build et de démarrage distinctes pour chaque projet, s’est doté d’une UI à part entière, d’une base de données et de beaucoup de code supplémentaire simplifiant le travail avec l’infrastructure de Yandex. Mais l’essence du service n’a pas changé — il résolvait toujours les mêmes tâches:
-
télécharger le code et construire le projet à partir d’une pull request;
-
acheminer le trafic vers l’application lancée;
-
unifier la solution dans un seul composant d’infrastructure, facile à diffuser et à intégrer dans les processus CI de Yandex.
Principe de fonctionnement
Farm est une petite application Node.js, dont le fonctionnement simplifié ressemble à ceci:
-
Farm reçoit une demande de génération. Elle peut venir d’une tâche CI ou directement d’un utilisateur via un formulaire dans l’UI.
-
Une nouvelle instance de l’application atterrit dans la base de données avec le statut queued et prend place dans la file d’attente des builds.
-
Farm dépile la file d’attente, télécharge le code du dépôt et de la branche cibles, lit la configuration et lance le build.
-
Le processus de l’application démarre: on attend de lui qu’il ouvre un socket à l’emplacement
<instance_dir>/dist/server.sock. -
Farm — ou plutôt nginx avec une config spéciale à ce stade — commence à router le trafic vers un domaine unique, par exemple: d008838956420e285cee652262dd5d18b2ed2a2a.farm.example.com.

Comme le montre le schéma, nous n’avons pas confié à Farm la responsabilité de l'équilibrage de charge — nous avons utilisé nginx. Cette approche est là pour durer: maintenir le proxying du trafic est une tâche non triviale que nous préférons ne pas résoudre nous-mêmes. Il y avait aussi la prise en charge de Git et d’Arc — le système de gestion de versions interne de Yandex.
Le build et le démarrage eux-mêmes se résumaient à des commandes exécutées par Farm. Le tout aussi brut et direct que possible:
# Construction
npm ci
npm run build
# Démarrage
npm run start
Pour compléter le tableau, regardons aussi un exemple de config farm.json:
{
"preview-generator": {
"build": ["npm ci", "npm run build"],
"start": {
"command": "npm",
"args": "run start"
},
"env": {
"APP_BUILDER_CDN": "false",
"IS_FARM": "true"
},
"instances": [
{
"name": "preprod",
"urlTemplate": "https://{hash}.farm.example.com",
"env": {
"APP_INSTALLATION": "russia",
"APP_ENV": "preprod"
}
},
{
"name": "prod",
"urlTemplate": "https://{hash}.farm.example.com",
"env": {
"APP_INSTALLATION": "russia",
"APP_ENV": "prod"
}
}
]
}
}
Au final, nous avons obtenu une véritable bête de somme. Il ne restait à l'équipe cible qu'à préparer une machine virtuelle, décrire la configuration de Farm et de ses projets, le démarrer et mettre en place un processus CI pour les pull requests. Ça a l’air simple, non? Mais…
Croissance et nouveaux défis
Farm a vécu dans cet état pendant un bon moment et remplissait correctement ses missions. Au début, nous avions même une « colocation “: une seule installation de Farm partagée entre plusieurs équipes frontend, pour ne pas gaspiller d'énergie à maintenir plusieurs installations. Mais les équipes grandissaient, leur nombre augmentait, et Farm ne pouvait fonctionner qu'à l’intérieur d’une seule VM. Avec le temps, même les configurations les plus puissantes ont cessé de suffire.
Exécuter plusieurs builds en parallèle consommait toutes les ressources de la machine, et une instance pouvait patienter un long moment dans la file d’attente. Un processus organique s’est donc enclenché: le découpage de la « colocation » en installations plus petites, parfois même une par projet.
Évidemment, ce découpage ne résolvait pas le problème — il ne faisait que le repousser. Les ressources de calcul continuaient malgré tout à s'épuiser, et les nouvelles équipes devaient consacrer des efforts supplémentaires au support et à la maintenance de Farm. Par exemple, le disque de la VM se remplissait assez régulièrement, ce qui obligeait à lancer un nettoyage manuel des fichiers inutiles. Premier problème, donc: des ressources limitées, sans gestion ni mise à l'échelle. Gardons cela en tête.
Autre problème: sous le capot, Farm utilisait SQLite comme base de données, sans la moindre abstraction du type Knex. Cela créait du vendor lock et nous obligeait aussi à effacer toutes les données à chaque changement de schéma, faute de mécanisme de migrations. Et comme Farm était éclaté en une multitude d’installations par équipe, presque personne ne voulait le mettre à jour: qui sait ce qui pourrait casser — et il faudrait très probablement effacer ses données par-dessus le marché. Comme le dit l’adage: « si ça marche, on n’y touche pas “.
Qui plus est, Farm ne savait lancer que des applications Node.js, et tous les processus tournaient directement sur l’hôte, sans aucune isolation. Et faute d’isolation, les applications ne pouvaient pas se contenter d’utiliser leur port habituel pour recevoir le trafic, puisque Farm ne gérait pas les ports. C’est pourquoi la communication passait par des sockets unix, ce qui forçait les applications à respecter ce contrat un peu étrange.
Nous devions donc résoudre les problèmes suivants:
-
Des ressources limitées, sans gestion ni mise à l'échelle.
-
Pas de migrations de base de données et une dépendance forte à SQLite.
-
Pas d’isolation des instances, plus la contrainte d'être une application Node.js publiant un socket unix.
-
Le poids du support et de l’exploitation — après tout, nous voulions au départ simplifier la vie des équipes, pas leur ajouter des maux de tête (c’est un problème supplémentaire que nous laissons de côté pour l’instant, mais nous y reviendrons vers la fin).
Comment nous avons relevé ces défis
Un ingénieur expérimenté, devant ces tâches, proposerait très probablement de faire passer Farm sur les rails d’un cluster k8s. C’est justement l’idée à laquelle nous sommes progressivement arrivés: déplacer le build et le déploiement des applications sur la puissance de calcul du cluster, dans des pods séparés, et exploiter ses capacités de mise à l'échelle automatique et de gestion des ressources. Les applications elles-mêmes seraient livrées sous la forme du bon vieux conteneur docker — un contrat très clair et simple pour notre industrie. En parallèle, nous devions conserver la possibilité de lancer les applications à l’ancienne, comme des processus, pour épargner aux petites équipes une migration laborieuse vers la solution k8s.
Abandonner purement et simplement Farm pour déployer les applications directement depuis la CI vers un cluster k8s était impossible, pour les raisons suivantes:
-
Farm avait pris racine profondément dans tous nos processus existants et dans la CI. Forcer les équipes à migrer vers une solution entièrement nouvelle n’avait aucun sens.
-
En creusant les détails, Farm ne se contente pas de construire et de démarrer l’application — il prend aussi en charge le routage (certes via nginx), arrête et supprime les instances inutilisées, et fait bien plus encore.
-
Et l’UI, bien pratique, compte beaucoup elle aussi: elle permet non seulement aux développeurs frontend, mais aussi aux développeurs backend de monter une instance d’une application pour tester diverses fonctionnalités.
En pratique, ajouter la prise en charge de k8s s’est révélé plus difficile que prévu. Dès les premières versions, la logique de gestion des instances d’applications était éparpillée dans toute la base de code, et au fil des ans une foule de détails et de hacks élégants s’y étaient accumulés — y glisser une implémentation de plus relevait de l’acrobatie.
Un refactoring en profondeur
Comme première étape, nous avons entrepris un vaste refactoring du code de Farm. Il fallait d’abord encapsuler la logique de gestion des instances dans une classe de provider dédiée — une sorte de backend de notre Farm, qui définirait la stratégie de build et de déploiement d’une application. Cela nous permettrait de faire cohabiter pour un temps le schéma legacy et k8s, puis d’ajouter de nouvelles implémentations via une interface unique.
Après avoir désossé le code, nous avons réussi à extraire toutes les méthodes propres au provider et à préparer une classe abstraite pour les décrire. En simplifiant un peu, tout s’est ramené à ceci:
class BaseFarmProvider {
startup(): Promise<void>;
buildInstance(
generateData: GenerateInstanceData,
observer: SubscriptionObserver<InstanceObservableEmitValue>,
): Promise<void>;
stopBuilder(hash: string): Promise<void>;
startInstance(instance: Instance): Promise<void>;
stopInstance(hash: string): Promise<void>;
restartInstance(instance: Instance): Promise<void>;
deleteInstance(hash: string): Promise<void>;
getInstanceStatus(instance: Instance): Promise<InstanceProviderStatus>;
getInstances(): Promise<Array<InstanceProviderInfo>>;
getInstanceLogs(params: {
hash: string;
stdout?: LogParams;
stderr?: LogParams;
}): Promise<{stdout?: string; stderr?: string}>;
}
À peu près au même moment, nous avons abstrait tout l’accès à la base de données et l’avons fait passer sur Knex, afin de pouvoir, à l’avenir, basculer vers un autre SGBD, plus puissant et persistant. En bonus, nous avons obtenu un mécanisme de migrations bon marché qui facilite les changements de schéma de la base.
Le nouveau schéma avec Kubernetes
Dans le nouveau schéma, Farm est déployé dans le cluster, fonctionne comme un contrôleur, se connecte à l’API k8s et se met à gérer les ressources du cluster: lancer des pods pour les builds et pour l’application, créer et supprimer d’autres ressources.
Pour le comprendre, il suffit de couvrir les deux processus principaux: le build et le déploiement d’une instance d’application.
Le build se déroule en plusieurs étapes:
-
Un utilisateur ou la CI lance le build (la méthode buildInstance).
-
La configuration Farm de l’application cible est récupérée depuis la branche (le fichier farm.json à la racine du dépôt).
-
Un pod de build démarre: il télécharge le code de la branche, construit une image docker (Dockerfile.farm par défaut) et la pousse vers un registre prédéfini.
-
Tous les logs du pod de build sont diffusés en temps réel et affichés dans l’UI pour le débogage.
-
Une fois le build terminé, Farm supprime le pod de build.

Le build produit un artefact: une image de l’application poussée dans le registre, prête à être déployée et démarrée. En bonus, nous avons obtenu la mise en cache des builds via Docker, très utile pour accélérer la CI.
Le déploiement démarre juste après le build, dans la même méthode buildInstance; reprenons donc à partir du moment où l’image est poussée:
-
Un Deployment est créé dans le cluster, utilisant l’image produite à l'étape précédente.
-
Un Service est créé avec le port que nous avons indiqué dans la configuration de Farm, ainsi qu’un Ingress avec un domaine basé sur l’ID de l’instance.
-
Puis un nouvel acteur entre en scène — l'Ingress NGINX Controller. Il prend en charge tout le routage et rend l’application accessible depuis l’extérieur. Ici, Farm reste fidèle à lui-même en déléguant le proxying du trafic à un autre composant du système.

Techniquement, n’importe quel autre contrôleur Ingress pourrait assurer le routage — jusqu'à un ALB cloud si nécessaire. Nous avons choisi l’Ingress NGINX Controller pour sa simplicité et la rapidité de mise à jour des configs.
Voilà, en gros, comment nous obtenons une instance d’application fonctionnelle, prête à être ouverte et testée. En plus de cela, Farm veille à ce que les ressources ne soient pas dupliquées et à ce que toutes les opérations restent idempotentes — un point crucial pour un fonctionnement stable.
Un schéma simplifié avec Docker seul
Une fois née l’idée de la prise en charge de k8s dans Farm, une proposition assez évidente a suivi: pourquoi ne pas prendre en charge le lancement des instances simplement sur une machine virtuelle, dans Docker? Cela résoudrait le problème d’isolation des instances, lèverait les restrictions liées à l’application Node.js et aux ports, et donnerait à Farm un mode de fonctionnement de plus — cette fois pour les petites équipes sans cluster k8s. À terme, nous pourrions abandonner complètement l’ancien mode de lancement sous forme de simples processus Node.js, ce qui simplifierait grandement la maintenance en nous permettant de supprimer un tas de code legacy.
Comment cela fonctionne-t-il au final? Farm se connecte au Docker Engine via un socket et joue lui-même le rôle de builder — ici, pas de pods de build séparés. Et voici la première différence importante avec le schéma k8s: l’image de l’instance est construite localement, sur le même démon, et y reste. Inutile de la pousser vers un registre — le registre ne sert guère qu'à récupérer les images de base pendant le build. C’est nettement plus simple, et c’est exactement ce dont ont besoin les petites équipes sans cluster.
Nous avons prévu deux modes de fonctionnement:
-
docker_container — Farm tourne lui-même en conteneur avec le socket de l’hôte monté et dirige, tel un chef d’orchestre, les conteneurs « voisins » sur le même démon;
-
vm — Farm vit directement sur la VM avec son propre Docker Engine.
Farm et toutes les instances rejoignent un réseau Docker commun (farm par défaut), et chaque instance d’application reçoit un conteneur au nom éloquent farm-docker-<hash>. C’est par ce nom que nous la retrouverons plus tard, quand viendra le moment d’acheminer le trafic.
Comme dans le schéma k8s, build et démarrage cohabitent dans la même méthode buildInstance, simplement sans pods séparés:
-
Un utilisateur ou la CI lance le build (la méthode buildInstance).
-
Le code est récupéré depuis la branche et la configuration Farm est lue dans farm.json — c’est de là que viennent le chemin du Dockerfile ainsi que les variables de build et d’exécution.
-
Farm construit l’image docker localement et lui appose le tag
farm-docker-<hash>, tout en diffusant au passage tous les logs de build dans l’UI pour le débogage. -
À partir de l’image obtenue, un conteneur est créé et démarré dans le réseau farm — avec les variables d’environnement transmises et, si on le souhaite, une commande de démarrage redéfinie.

Routage du trafic
Ici, Farm délègue une fois de plus le proxying à nginx. Le conteneur de l’instance ne publie aucun port sur l’hôte — on peut l’atteindre par le nom farm-docker-<hash> à l’intérieur du réseau Docker farm, où ce nom est résolu par le DNS intégré de Docker.
La seule question, c’est où vit ce fameux nginx capable de résoudre ce nom — et cela dépend du mode:
-
En mode docker_container, une requête venant de l’hôte entre dans le conteneur de Farm, et son nginx interne, présent sur le réseau farm, achemine le trafic directement jusqu'à l’instance.
-
En mode vm, le nginx de Farm tourne directement sur l’hôte et ne voit pas les noms des conteneurs; il relaie donc la requête vers un conteneur Docker Proxy séparé (le même nginx, mais lancé à l’intérieur du réseau farm), qui remet ensuite le trafic à la bonne instance.

Et voilà comment nous obtenons une instance fonctionnelle sur une machine virtuelle ordinaire avec Docker, sans le moindre cluster: le code est construit en une image, l’image devient un conteneur, et nginx lui achemine le trafic.
Au passage, nous avons aussi réglé le problème d’isolation, avec ce fameux contrat étrange des sockets unix — l’application est désormais livrée sous forme d’image docker classique et écoute tranquillement sur son port, sans rien savoir de la tambouille interne de Farm.
L'évolution de Farm et le passage en open source
Après toutes ces améliorations — les abstractions de providers, la prise en charge de différents schémas de build et de lancement, le passage à Knex — Farm ressemblait déjà à un produit mûr et complet, où les spécificités de Yandex ne jouaient plus un grand rôle. Farm était progressivement devenu un orchestrateur sans couplage fort avec les technologies qu’il faisait tourner sous lui.
Il évoquait désormais un système de déploiement simplifié dans l’esprit de Heroku, mais sous forme de service auto-hébergé. Des plans plus ambitieux ont donc émergé: publier le code de Farm dans le cadre de notre projet open source Gravity UI, pour qu’il soit utile non seulement au sein de l’entreprise, mais à toute la communauté.
Mais cette belle idée d’ouverture du code avait un hic. Farm avait beau être devenu plus abstrait et plus propre, il restait encore beaucoup de spécificités Yandex: l’authentification interne, la prise en charge d’Arc, la publication de statuts dans notre tracker, la télémétrie et d’autres briques liées à l’infrastructure interne. Publier tout cela n’est pas une bonne idée — et la communauté n’en a de toute façon aucun usage. Mais se contenter de le supprimer n'était pas une option non plus: nos propres installations, que des dizaines d'équipes utilisent chaque jour, vivent précisément sur ces spécificités.
La tâche s’est donc résumée à ceci: sortir du tableau tout ce qui est propre à Yandex et extraire de Farm un noyau configurable, dans lequel ces spécificités pourraient être rebranchées de l’extérieur sans toucher au code du noyau. En substance, il nous fallait tracer une frontière de plus entre ce que nous sommes prêts à ouvrir au monde et ce qui reste en interne.
Au final, nous avons physiquement scindé Farm en deux bases de code.
-
Le noyau ouvert: toute l’orchestration, les providers Docker et k8s, la prise en charge de Git, la base de données sur Knex, l’API, l’UI, la file d’attente des builds, le healthcheck — bref, tout ce qui fait de Farm un produit. Le noyau ne sait rien de Yandex et peut sans crainte être publié tel quel.
-
Une fine couche d’extensions, qui ajoute par-dessus tout ce qui est propriétaire.
Le ciment entre les deux, c’est un registre de plugins: le noyau démarre de lui-même et fournit un point d’entrée unique, à travers lequel la couche d’extensions enregistre ses implémentations selon des interfaces bien définies. Résultat: toute la partie Yandex vit désormais dans un seul petit module et se branche de façon déclarative:
initCoreExtension(async () => {
// une façon personnalisée de lancer les instances
coreRegistry.farmProviders.plugIn('process', {
constructor: ({internalApi, config}) => new ProcessFarmProvider(internalApi, config),
});
// un système de gestion de versions personnalisé
coreRegistry.vcs.plugIn('arc', {constructor: () => new ArcVcs()});
// une action webhook personnalisée — par exemple, publier un statut dans le tracker
coreRegistry.webhookActions.plugIn('tracker', new TrackerWebhookAction());
// une authentification personnalisée
coreRegistry.authProviders.plugIn('internal', {constructor: () => new InternalAuthProvider()});
// ...et aussi la télémétrie, les domaines CSP, les éléments de menu de l'UI
});
Le registre est organisé comme un ensemble de points d’extension distincts, chacun avec sa propre interface — tout ce qui peut varier passe par eux:
-
les providers (farmProviders) — comment et où déployer;
-
les systèmes de gestion de versions (vcs) — git public, arc interne;
-
l’authentification (authProviders) — comment laisser entrer les utilisateurs dans l’UI et l’API;
-
les actions webhook (webhookActions) — que faire en réponse à un événement CI, par exemple renvoyer un statut dans le tracker;
-
le schéma de farm.json (farmJsonConfig) — des champs personnalisés dans la configuration de l’application pour les besoins d’un provider donné;
-
l’UI et la sécurité (uiConfiguration, cspDirectives) — les éléments de menu et la liste des domaines de confiance dans la CSP.
Le point clé, c’est que pour le noyau, toutes ces implémentations se valent: arc ne diffère en rien de git, et process en rien de docker ou k8s. On peut donc ouvrir le noyau sans se retourner sur la tambouille interne, tandis que notre installation continue de fonctionner comme avant — simplement en assemblant le noyau avec la couche d’extensions.
Le déploiement de Farm sur k8s
Après un long chemin, nous en sommes enfin arrivés à migrer nos installations de Farm vers k8s. La stratégie retenue: faire passer la plus grosse installation sur les rails de Kubernetes, puis, si tout fonctionne bien, faire revivre la « colocation » en déplaçant les projets restants des installations locales vers une installation commune. C’est précisément cette approche qui résout le problème de complexité de maintenance que nous avions signalé au début. Les équipes n’ont de nouveau plus à se soucier d’entretenir leur propre installation ni d’en surveiller les ressources, puisque le cluster partagé s’adapte automatiquement à la charge.
Nous sommes libres d’utiliser nos propres services et offres cloud pour bâtir l’infrastructure. Nous avons donc créé un cluster avec Yandex Managed Service for Kubernetes® et décrit absolument toutes les ressources avec Terraform. Nous avons aussi mis en place une gestion des secrets séparée pour chaque projet, avec des droits d’accès atomiques sur chaque ressource. Le résultat: un module Terraform réutilisable qui rend assez facile le déploiement d’une installation k8s de Farm.
En une seule journée, nous avons déplacé avec succès tous les projets en cours vers l’installation k8s de Farm. Ce ne fut pas bien compliqué, puisque le contrat et les configs d’origine restaient les mêmes. Le nouveau Farm s’est plutôt bien comporté, et d’autres projets ont commencé eux aussi à y migrer. Assez vite, un nouveau problème a fait surface: le cluster a beau se mettre à l'échelle en RAM et en CPU, l’espace disque continuait de se remplir, car beaucoup d’images étaient construites chaque jour et occupaient du stockage.
Ici, nous avons résolu le problème frontalement en ajoutant un mécanisme de nettoyage:
-
pour Docker, cela fonctionne ainsi: on définit, sous forme d’expression cron, l’heure précise à laquelle supprimer les images inutilisées;
-
pour k8s, le mécanisme s’appuie sur la ressource native CronJob, qui lance sur chaque nœud des pods supprimant eux aussi les vieilles images.
Nous avons déclaré l’expérience « Farm sur k8s » réussie en nous fondant sur la réaction des équipes: les retours, le nombre de problèmes et le nombre de sollicitations auprès des mainteneurs.
Ce que Farm sait faire aujourd’hui
Au fil de ce parcours, Farm est passé d’une bête de somme mono-stack à un orchestrateur mature d’environnements de preview. Voici un rapide résumé de ce qu’il sait faire aujourd’hui:
-
Build et lancement depuis une branche. Déclenché par un webhook ou depuis l’UI, Farm récupère le code, construit l’application et monte une instance isolée avec sa propre URL.
-
Plusieurs providers. k8s pour les grosses installations avec mise à l'échelle horizontale, docker pour une seule machine virtuelle sans cluster. Le contrat de l’application est le même pour tous les providers.
-
Un noyau configurable avec un registre de plugins. Providers, systèmes de gestion de versions, authentification et actions webhook se branchent via un registre unique, ce qui permet d'étendre Farm sans modifier son noyau.
-
Un planificateur et une file d’attente de builds. Les demandes de génération sont mises en file, et le planificateur la dépile en respectant les limites de builds simultanés et d’instances en cours d’exécution.
-
Gestion du cycle de vie. Démarrage automatique d’une instance depuis l’UI, ainsi qu’arrêt et suppression des instances inactives après un délai d’expiration.
-
Healthcheck. Farm surveille l'état des instances et reflète leur statut à jour dans l’UI et l’API: avec ses propres vérifications de disponibilité en Docker et en mode process, et via les sondes natives liveness/readiness dans Kubernetes.
-
Transmission des variables d’environnement. Une configuration flexible de l’environnement: variables de build et d’exécution, variables protégées impossibles à redéfinir à la génération, et pour Docker — l’héritage des variables de l’hôte ou du conteneur, en prime.
-
Une base de données persistante avec migrations. Knex par-dessus SQLite, PostgreSQL ou un autre SGBD.
-
UI et API. Une interface web pour lancer et déboguer les instances et une API HTTP pour l’intégration en CI.
-
Nettoyage de l’espace disque. Une suppression régulière des images inutilisées dans Docker et k8s.
Et la suite?
Parlons maintenant de nos plans. Il reste beaucoup de choses que nous aimerions améliorer dans Farm. Voici les principales pistes du moment:
-
Le passage à la Gateway API en remplacement d’Ingress dans le provider k8s.
-
La prise en charge des alias, pour qu’une instance soit accessible par un nom lisible plutôt que par un hash.
-
Une autorisation à part entière — un système d’utilisateurs, de rôles et de droits d’accès.
-
Des quotas séparés pour les différents projets.
Comme pour tout projet open source, nous continuerons d’améliorer la documentation et les exemples pour rendre Farm plus simple et plus agréable à utiliser — et pour que le nombre d’utilisateurs externes grandisse rapidement. Nous aimerions qu’avec le temps, Farm devienne l’un des outils de référence pour les environnements de preview dans l’industrie — mais on verra bien comment ça se passe.
Comment l’essayer
Passez nous voir sur le dépôt, parcourez le README et la documentation correspondante. Pour l’essayer, vous pouvez librement monter Farm en local avec Docker Engine installé. Nous serons reconnaissants pour tout retour, alors ouvrez des issues, envoyez des PR, posez vos questions et apportez vos feature requests s’il vous manque une fonctionnalité!
Merci de votre lecture! Si notre projet vous plaît, nous serons ravis de recevoir vos étoiles! Et si les dernières nouvelles de l'équipe Yandex Cloud vous intéressent, rejoignez notre chaîne.

Mikhail Golbakh
Développeur frontend senior chez Yandex Cloud