RAILSHOT.
Retour à la salle

Railshot

AI deployment & operations platform

Déployer une application web construite en local sur des environnements cloud et sur site préparés, puis l’exploiter.

Une plateforme IA de déploiement et d’exploitation qui mène une application web construite en local vers des environnements cloud ou sur site préparés, puis continue de l’exploiter.

2026.09.28 — 2026.10.04 · Terminé

Projet d’équipe · ma contribution : conception et implémentation de la CI, clusters CSP et interface Ansible, exploitation, revue de code de l’équipe et intégration

SoftBank Hackathon 2026 powered by KOREC & ProgateParticipant aux préliminaires · sélection pour la finale annoncée le 2026.10.26 (au 2026.10.07)

  • WebImplémenté · hackathon
  • Serveur MCPImplémenté · distant · local

Les clusters d’exploitation et d’applications clientes sont séparés, et le chemin public est tenu à l’écart du contrôle des déploiements.

Dans le K3s d’exploitation, le tableau de bord, l’API produit et le build controller sont placés sur des nœuds de rôle platform et les Jobs runner sur un nœud de rôle build, tandis qu’Argo CD gère le déploiement. Les applications clientes tournent dans un namespace désigné d’un cluster K3s + Cilium à nœud unique, distinct pour chaque cible enregistrée.

Carte des clusters · tracée d’après les déclarations et le code au commit public 645e28a
Carte des clusters d’exploitation et clients de RailshotLe cluster K3s d’exploitation et un cluster d’application client enregistré. Dans le cluster d’exploitation, le namespace railshot-system place le tableau de bord, l’API produit, le MCP distant (seulement si son image est fournie) et le build controller sur des nœuds de rôle platform ; les Jobs runner du namespace railshot-build prennent les jobs GitHub Actions sur le nœud de rôle build, doté d’un taint. Un job de release hébergé par GitHub publie le bundle testé sur GHCR. L’API produit commite le workload dans le Git de config et synchronise une Application du namespace argocd, et Argo CD applique un Deployment, un Service NodePort et une NetworkPolicy au namespace enregistré dans le cluster client. Les requêtes vers l’application entrent par un point d’entrée L7 du fournisseur jusqu’au NodePort et au Service du nœud, puis au Pod de l’application. Le verdict de déploiement ne repose que sur l’état Argo et le contrôle de l’URL publique ; diagnostics de Pod, journaux et métriques sont lus comme des signaux distincts. Les entrées d’installation et d’enregistrement de l’opérateur sont tenues à part de la charge utile de l’application du tenant.Utilisateur de Railshottableau de bord · MCPBootstrap de l’opérateurinstaller · enregistrer · SecretGHCRimages par digestdépôt Git de configworkload.json · revisionPoint d’entrée L7 du fournisseurALB · GCP LB · AmphoraUtilisateur de l’applicationHTTPS publicVM d’observationhors du K3s client · Composerailshot-systemdashboard · nginxMCP distant · seulement avec une imagebuild controller · CronJobAPI produit · cd.js → bridge.pyargocdArgo CD · ApplicationAppProject · cluster SecretCronJob de renouvellement · toutes les 2 hrailshot-buildrunner Job · hostNetwork · Docker socketrailshot-deploy.ymlloop job · self-hosted runnerrelease job · GitHub-hosted<app namespace>Deployment · Pod · image par digestNodePort Service · LocalNetworkPolicy · CIDR listésSA railshot-argocd · tokenkube-systemCilium · Helm · VXLANrailshot-observabilitynode-exporterkube-state-metricsCluster d’exploitation · K3sAWS · nœuds par rôlenœuds de rôle platformnodeSelectornœud de rôle buildNoSchedule taintGitHubActions · GHCRCluster d’application client · exempleun par cible enregistréeUn nœud K3s · serverCilium · kube-proxy conservé1workflow_dispatch2réalimenter les Jobs runner3prendre le job4bundle testé5 publier par digest · release job · GitHub-hosted → GHCR51workload commit · push2revision3 Application sync · API produit · cd.js → bridge.py → Argo CD · Application34appliquer au namespace5pull par digest1HTTPS 4432NodePort3 vers le Pod · NodePort Service · Local → Deployment · Pod · image par digest34 appliquer la politique · Cilium · Helm · VXLAN → NetworkPolicy · CIDR listés45 tableau de bord · API · Utilisateur de Railshot → dashboard · nginx51 révision · health · images · API produit · cd.js → bridge.py → Argo CD · Application12contrôle HTTPS public3Diagnostic de Pod · hors verdict4fin des journaux · 100 lignes par Pod5collecte · NodePorts6Blackbox probe1rendu de la plateforme · Secret2bootstrap d’enregistrement3installer K3s · Cilium4TokenRequest · 6 h

Build → publication de l’image

L’API produit demande une exécution GitHub Actions, et le runner du nœud build prend et contrôle le job. Seul un bundle qui a passé les mêmes contrôles est publié sur GHCR par un job de release hébergé par GitHub.

  1. API produit · cd.js → bridge.pyrailshot-systemexécuter · appliquer: workflow_dispatchloop job · self-hosted runnerrailshot-deploy.yml

    L’API produit demande une exécution GitHub Actions avec tenant, app, commit source et cible. L’API n’appelle jamais directement un Job runner.

  2. build controller · CronJobrailshot-systemexécuter · appliquer: réalimenter les Jobs runnerrunner Job · hostNetwork · Docker socketrailshot-build

    Le CronJob du build controller (chaque minute, sur un nœud platform) crée des Jobs runner dans le namespace railshot-build. Son Role n’autorise que get, list et create sur les Jobs, plus get, update et patch sur l’unique Secret d’enregistrement du runner ; il ne peut créer ni namespaces ni autres Secrets.

  3. runner Job · hostNetwork · Docker socketrailshot-buildexécuter · appliquer: prendre le jobloop job · self-hosted runnerrailshot-deploy.yml

    Le runner du nœud de rôle build (hostname exact, tolération NoSchedule) prend le job loop par ses labels self-hosted et l’exécute. La source du client tourne dans les conteneurs restreints existants de build Docker et de contrôle.

  4. runner Job · hostNetwork · Docker socketrailshot-builddonnées: bundle testérelease job · GitHub-hostedrailshot-deploy.yml

    Le job loop laisse le bundle qui a passé les mêmes contrôles, et le job de release ne prend que ce bundle.

  5. release job · GitHub-hostedrailshot-deploy.ymldonnées: publier par digestGHCR

    Un job de release hébergé par GitHub (ubuntu-24.04) publie l’image du bundle testé sur GHCR sans la reconstruire. La publication d’images n’a jamais lieu dans le cluster d’application client.

Git de config → Argo → namespace client

Le Git de config porte les ressources souhaitées et GHCR les octets de l’image. Argo CD n’applique qu’au namespace client enregistré.

  1. API produit · cd.js → bridge.pyrailshot-systemdonnées: workload commit · pushdépôt Git de config

    cd.js exécute bridge.py, qui ne commite et ne pousse workload.json que si l’enregistrement de publication et la cible enregistrée concordent, puis relit la révision distante. Git porte les ressources souhaitées ; GHCR porte les octets de l’image.

  2. dépôt Git de configdonnées: revisionArgo CD · Applicationargocd

    L’Application ne lit que son dépôt de config, son chemin et sa révision restreints.

  3. API produit · cd.js → bridge.pyrailshot-systemexécuter · appliquer: Application syncArgo CD · Applicationargocd

    L’Application nommée est posée par server-side apply, et une sync d’une révision exacte est demandée sans prune. Le ServiceAccount railshot-product de l’API produit détient à la fois le bootstrap d’enregistrement installé par l’opérateur (create sur les Secrets, AppProjects et Applications dans argocd ; get, update et escalate sur des Roles nommés) et des droits get et patch ajoutés par nom exact pour chaque cible validée. Le create de Kubernetes ne peut pas être limité par nom ; la validation des entrées du produit maintient donc aussi les noms dans le périmètre.

  4. Argo CD · Applicationargocdexécuter · appliquer: appliquer au namespace<app namespace>

    Argo CD applique au namespace désigné du cluster client avec l’identifiant du Secret de cluster enregistré. L’AppProject n’autorise que des kinds à portée namespace, sans ressource à portée cluster.

  5. Deployment · Pod · image par digest<app namespace>données: pull par digestGHCR

    L’image publiée par digest est tirée avec un Secret de pull référencé par son nom dans le même namespace. Les valeurs des Secrets n’entrent jamais dans l’artefact ni dans le Git de config.

Chemin des requêtes publiques

Les requêtes vers l’application suivent point d’entrée L7 du fournisseur → NodePort du nœud → Service → Pod de l’application. L’application des politiques par Cilium est un contrôle distinct, pas une étape qui transporte la requête. L’accès au tableau de bord d’exploitation suit un autre chemin.

  1. Utilisateur de l’applicationdonnées: HTTPS 443Point d’entrée L7 du fournisseur

    Les utilisateurs de l’application atteignent le point d’entrée L7 du fournisseur par le hostname enregistré. Chaque fournisseur implémente son propre point d’entrée.

  2. Point d’entrée L7 du fournisseurdonnées: NodePortNodePort Service · Local<app namespace>

    Le point d’entrée transmet à l’adresse privée et au NodePort du nœud. Le NodePort est celui alloué lors de l’enregistrement de la cible.

  3. NodePort Service · Local<app namespace>données: vers le PodDeployment · Pod · image par digest<app namespace>

    Le Service envoie la requête au Pod de l’application (Deployment) sur le même nœud. Avec externalTrafficPolicy Local, l’adresse source atteint inchangée le contrôle des CIDR enregistrés de la NetworkPolicy.

  4. Cilium · Helm · VXLANkube-systemexécuter · appliquer: appliquer la politiqueNetworkPolicy · CIDR listés<app namespace>

    Cilium applique la NetworkPolicy : entrée seulement depuis les CIDR enregistrés vers le port de l’application ; aucune sortie par défaut, hormis une adresse de base de données approuvée. Pare-feu cloud et règles d’hôte restent nécessaires.

  5. Utilisateur de Railshotdonnées: tableau de bord · APIdashboard · nginxrailshot-system

    Les utilisateurs passent par le point d’entrée d’exploitation jusqu’au tableau de bord nginx, et nginx sert de façade à l’API produit. Le Service du tableau de bord n’est un NodePort que si un port de nœud approuvé est fourni ; l’API reste en ClusterIP.

Vérifier · observer

Le verdict de déploiement ne repose que sur l’état Argo et le contrôle de l’URL publique. Diagnostics de Pod, journaux actuels et métriques d’exploitation sont des signaux distincts qui ne le modifient jamais.

  1. API produit · cd.js → bridge.pyrailshot-systemobserver: révision · health · imagesArgo CD · Applicationargocd

    Deployed exige la révision de sync de l’Application, Synced et Healthy, une opération Succeeded, et que les références d’image consignées dans le statut de l’Application Argo égalent les images par digest du Deployment revu. Ce n’est pas une porte directe sur l’imageID du Pod, et un statut Argo vert ne suffit pas à établir la correspondance.

  2. API produit · cd.js → bridge.pyrailshot-systemobserver: contrôle HTTPS publicPoint d’entrée L7 du fournisseur

    L’URL publique enregistrée est vérifiée sans redirection. Succeeded n’est consigné qu’avec à la fois le verdict Argo et ce contrôle.

  3. API produit · cd.js → bridge.pyrailshot-systemobserver: Diagnostic de Pod · hors verdictDeployment · Pod · image par digest<app namespace>

    Après le verdict Argo et le contrôle public, le Deployment, les ReplicaSets et les Pods sont lus séparément avec l’identifiant enregistré, et l’imageID et la disponibilité sont ajoutés comme diagnostic ready, progressing ou unavailable. Un diagnostic en échec ou divergent ne change jamais le verdict deployed ou succeeded.

  4. API produit · cd.js → bridge.pyrailshot-systemobserver: fin des journaux · 100 lignes par PodDeployment · Pod · image par digest<app namespace>

    Seulement pour l’opération actuellement déployée, avec la config de confiance et l’identifiant enregistré. Pour chaque Pod actuel (trois au plus), le digest de l’image en cours doit égaler le digest attendu ; puis jusqu’à 100 lignes caviardées par Pod sont lues dans une limite d’octets. Une tentative plus ancienne répond superseded ; sans configuration, not_configured. Ce n’est pas une agrégation de journaux.

  5. VM d’observationobserver: collecte · NodePortsrailshot-observability

    Prometheus sur la VM d’observation collecte node-exporter et kube-state-metrics via deux NodePorts restreints par pare-feu. Les valeurs manquantes ou périmées sont inconnues, pas nulles.

  6. VM d’observationobserver: Blackbox probePoint d’entrée L7 du fournisseur

    probe_success=1 signifie un 2xx à cet emplacement de sonde. Cela ne prouve ni l’accès de chaque utilisateur ni la version déployée, et ne fait jamais passer published à deployed.

Installation · enregistrement par l’opérateur

Installation du cluster, bootstrap d’enregistrement et entrées de cible, Secrets et configuration du renouvellement des identifiants sont des entrées de l’opérateur, jamais une partie de la charge utile de l’application du tenant.

  1. Bootstrap de l’opérateurexécuter · appliquer: rendu de la plateforme · Secretrailshot-system

    L’opérateur produit la déclaration de plateforme avec render-platform.py et fournit la config d’exploitation privée et les Secrets. Le MCP distant n’est inclus que si son image est fournie, la passerelle personnelle (WireGuard) seulement avec --personal.

  2. Bootstrap de l’opérateurexécuter · appliquer: bootstrap d’enregistrementAppProject · cluster Secretargocd

    L’opérateur installe dans argocd le Role de bootstrap d’enregistrement et un Role d’enregistrements vide, et prépare les entrées de cible. Les helpers d’enregistrement qu’exécute l’API produit (environment.py, applications.py) écrivent ensuite le Secret railshot-<target> avec un identifiant à portée limitée (clusterResources false, namespaces autorisés listés) et ajoutent les noms exacts validés au Role d’enregistrements. L’enregistrement par application lie un namespace app-<id> à un AppProject du même nom.

  3. Bootstrap de l’opérateurexécuter · appliquer: installer K3s · CiliumCilium · Helm · VXLANkube-system

    install-k3s.sh installe un serveur K3s à nœud unique (flannel, network policy, traefik et servicelb désactivés ; kube-proxy conservé), et Cilium est installé comme release Helm (kubeProxyReplacement false, VXLAN, Hubble désactivé).

  4. CronJob de renouvellement · toutes les 2 hargocdexécuter · appliquer: TokenRequest · 6 hSA railshot-argocd · token<app namespace>

    Le CronJob railshot-credentials demande toutes les deux heures un nouveau token de six heures pour le SA client. Le Role côté client n’autorise que la création de token pour son propre SA. Six heures est la TTL d’un token, pas le moment où l’accès de l’opérateur prend fin.

Toutes les connexions avec la même épaisseur. Choisissez un flux pour ne mettre en évidence que son chemin, avec des étapes numérotées.

  • Trait plein · sens de circulation des requêtes, bundles, images et déclarations
  • Tirets · demandes d’exécution, application, installation, identifiants
  • Trait mixte long · lecture d’état, journaux, sondes, métriques ; distinct du verdict
  • Trait noir épais · le flux sélectionné ; les numéros donnent l’ordre
  • Cadre · un namespace, son nom dans l’en-tête (pas un nœud)
  • Pastille intérieure · composant de ce namespace
  • Cadre en tirets · hors des clusters
  • railshot-system · produit
  • argocd · autorité de déploiement
  • railshot-build · autorité Docker de l’hôte
  • GitHub · cluster client

La décision de placement du October 2 dans docs/architecture (deux nœuds de rôle) et le schéma du README public relèvent de l’historique ; cette carte suit les déclarations et le code de 645e28a. Les flèches indiquent cause et direction, pas la preuve que chaque chemin de fournisseur est actif aujourd’hui.

Namespaces et nœuds
Un namespace est une portée de nommage : ni du matériel ni, à lui seul, une frontière de sécurité. Les rôles platform et build sont un placement de scheduling par nodeSelector et taint ; cette carte ne fixe pas le nombre de nœuds du cluster d’exploitation. Le placement des nœuds d’Argo CD est lui aussi un contrôle distinct.
Autorité du nœud build
Les Jobs runner tournent avec hostNetwork, le socket Docker de l’hôte, NET_ADMIN et root. Un namespace, un taint ou le RBAC ne retirent pas l’autorité Docker de l’hôte ; ce n’est donc ni une isolation vis-à-vis de l’administrateur de la VM ni un bac à sable pour du code arbitraire.
Namespace client
Chaque cible enregistrée utilise un namespace et un AppProject non par défaut (par exemple tenant-demo, ou app-<id> par application). Seuls sont créés le Deployment d’un service (montage automatique du token ServiceAccount désactivé), un Service NodePort et une NetworkPolicy, plus un PVC ou un Job de migration optionnels ; les Secrets ne sont que référencés par nom (le Secret de pull et un DATABASE_URL approuvé), jamais créés.
Entrée AWS
Alias Route 53 → ALB partagé HTTPS 443 → adresse privée et NodePort du nœud client. Plusieurs applications réutilisent un même nœud, chacune avec son namespace, son Application et son NodePort.
Entrée GCP
LB HTTPS global EXTERNAL_MANAGED → NEG zonal GCE_VM_IP_PORT → NodePort privé de la VM existante. Les certificats viennent de Certificate Manager avec autorisation DNS. Le module ne détient ni la VM ni le VPC, et une VM unique reste un point de défaillance unique.
Entrée OpenStack
Listener TLS Amphora 443 avec politiques L7 Host et path → NodePort HTTP. L’adresse par défaut est une VIP privée ; Octavia, K3s, la base de données et le DNS sont hors du module. L’accessibilité publique et la préparation d’Amphora sur site exigent une recette distincte, et aucune application sur site n’a encore été vérifiée avec ce module.
Entre fournisseurs
Les trois entrées sont des implémentations distinctes, sans basculement ni réplication automatiques entre fournisseurs. Le cluster d’application client est un exemple de cible enregistrée, pas l’affirmation que le même déploiement tourne sur tous les clouds à la fois.
Options de l’opérateur
La passerelle personnelle (WireGuard) ne rejoint la plateforme que si l’opérateur active --personal ; ce n’est pas un chemin requis pour les clouds clients. Dans le mode direct pris en charge, la VM d’observation peut partager la VM de contrôle d’exploitation.