Railshot
AI deployment & operations platform
ローカルで作ったWebアプリを、用意されたクラウドとオンプレミス環境にデプロイし、運用します。
ローカルで作ったWebアプリを、用意されたクラウドやオンプレミス環境にデプロイし、運用まで続けるAIデプロイ・運用プラットフォームです。
運用クラスターと顧客アプリのクラスターを分け、公開経路をデプロイ制御から切り離しています。
運用K3sでは、ダッシュボード、製品API、build controllerをplatformロールのノードに、runner Jobをbuildロールのノードに配置し、Argo CDがデプロイを担います。顧客アプリは、登録済みの対象ごとに分けた単一ノードK3s + Ciliumクラスターの指定namespaceで動きます。
ビルド → イメージ公開
製品APIがGitHub Actionsの実行を要求し、buildノードのrunnerがそのjobを受け取って検査します。同じ検査を通過したbundleだけを、GitHub-hostedのrelease jobがGHCRに公開します。
製品API · cd.js → bridge.py
railshot-system実行 · 適用: workflow_dispatchloop job · self-hosted runnerrailshot-deploy.yml製品APIが、tenant、app、ソースコミット、対象でGitHub Actionsの実行を要求します。APIがrunner Jobを直接呼び出すことはありません。
build controller · CronJob
railshot-system実行 · 適用: runner Jobを補充runner Job · hostNetwork · Docker socketrailshot-buildbuild controllerのCronJob(毎分、platformノード上)がrailshot-build namespaceにrunner Jobを作成します。そのRoleが許可するのは、Jobのget、list、createと、runner登録用Secret 1つに対するget、update、patchだけで、namespaceや他のSecretは作成できません。
runner Job · hostNetwork · Docker socket
railshot-build実行 · 適用: jobを受け取るloop job · self-hosted runnerrailshot-deploy.ymlbuildロールのノード(正確なhostname、NoSchedule toleration)上のrunnerが、self-hostedラベルでloop jobを受け取って実行します。顧客のソースは、既存の制限されたDockerのビルド・検査コンテナで動きます。
runner Job · hostNetwork · Docker socket
railshot-buildデータ: 検査済みのbundlerelease job · GitHub-hostedrailshot-deploy.ymlloop jobは同じ検査を通過したbundleを残し、release jobはそのbundleだけを受け取ります。
release job · GitHub-hosted
railshot-deploy.ymlデータ: ダイジェストで公開GHCRGitHub-hostedのrelease job(ubuntu-24.04)が、検査済みbundleのイメージを再ビルドせずにGHCRに公開します。イメージの公開が顧客アプリクラスター内で行われることはありません。
config Git → Argo → 顧客namespace
config Gitは望ましいリソースを、GHCRはイメージのバイトを運びます。Argo CDは登録済みの顧客namespaceにのみ適用します。
製品API · cd.js → bridge.py
railshot-systemデータ: workload commit · pushconfig Gitリポジトリcd.jsがbridge.pyを実行し、bridge.pyは公開記録と登録済みの対象が一致する場合にのみworkload.jsonをコミットしてpushし、リモートのrevisionを読み戻します。Gitは望ましいリソースを、GHCRはイメージのバイトを運びます。
config Gitリポジトリデータ: revisionArgo CD · Application
argocdApplicationは、制限されたconfigリポジトリ、パス、revisionだけを読み取ります。
製品API · cd.js → bridge.py
railshot-system実行 · 適用: Application syncArgo CD · Applicationargocd指定のApplicationをserver-side applyで設定し、正確なrevision 1つのsyncをpruneなしで要求します。製品APIのrailshot-product ServiceAccountは、運用者がインストールした登録bootstrap(argocdでのSecret、AppProject、Applicationのcreate、名前を指定したRoleに対するget、update、escalate)と、検証済みの対象ごとに正確な名前で追加されるgetとpatchの権限の両方を持ちます。Kubernetesのcreateは名前で制限できないため、製品の入力検証でも名前をスコープ内に保ちます。
Argo CD · Application
argocd実行 · 適用: namespaceへ適用<app namespace>Argo CDが、登録済みのcluster Secretの認証情報で、顧客クラスターの指定namespaceに適用します。AppProjectはnamespace単位のkindだけを許可し、クラスター単位のリソースは許可しません。
Deployment · Pod · ダイジェスト指定のイメージ
<app namespace>データ: ダイジェストでpullGHCR同じnamespaceのpull Secretを名前で参照し、公開されたダイジェスト指定のイメージをpullします。Secretの値が成果物やconfig Gitに入ることはありません。
公開リクエストの経路
アプリへのリクエストは、プロバイダーのL7入口 → ノードのNodePort → Service → アプリPodと進みます。Ciliumのポリシー強制は、リクエストを運ぶ段階ではなく別の制御です。運用ダッシュボードへの入口は別の経路です。
アプリのユーザーデータ: HTTPS 443プロバイダーのL7入口
アプリのユーザーは、登録済みのhostnameでプロバイダーのL7入口に到達します。入口はプロバイダーごとに個別に実装されます。
プロバイダーのL7入口データ: NodePortNodePort Service · Local
<app namespace>入口はノードのプライベートアドレスとNodePortへ転送します。NodePortは対象の登録時に割り当てられたものです。
NodePort Service · Local
<app namespace>データ: PodへDeployment · Pod · ダイジェスト指定のイメージ<app namespace>Serviceは同じノード上のアプリPod(Deployment)へリクエストを送ります。externalTrafficPolicy Localのため、送信元アドレスはそのままNetworkPolicyの登録済みCIDR検査に届きます。
Cilium · Helm · VXLAN
kube-system実行 · 適用: ポリシーの強制NetworkPolicy · 登録済みCIDRのみ<app namespace>CiliumがNetworkPolicyを強制します。受信は登録済みCIDRからアプリのポートへのみ許可し、送信は既定で許可せず、承認されたデータベースのアドレスだけが例外です。クラウドのファイアウォールとホストのルールも引き続き必要です。
Railshotユーザーデータ: ダッシュボード · APIdashboard · nginx
railshot-systemユーザーは運用の入口からnginxのダッシュボードに入り、nginxが製品APIの前段に立ちます。dashboard Serviceは承認されたnode portが与えられた場合のみNodePortとなり、APIはClusterIPのままです。
確認 · 観測
デプロイの判定はArgoの状態と公開URLの確認だけに基づきます。Podの診断、現在のログ、運用メトリクスは別のシグナルで、判定を変えることはありません。
製品API · cd.js → bridge.py
railshot-system観測: revision · health · イメージArgo CD · Applicationargocddeployedとなるには、Applicationのsync revision、SyncedとHealthy、operation Succeeded、そしてArgo Application statusに記録されたイメージ参照が、レビュー済みDeploymentのダイジェスト指定のイメージと一致する必要があります。PodのimageIDを直接確認するゲートではなく、Argoの緑の状態表示だけでは一致は確認されません。
製品API · cd.js → bridge.py
railshot-system観測: 公開HTTPS確認プロバイダーのL7入口登録済みの公開URLをリダイレクトなしで確認します。Argoの判定とこの確認の両方がそろったときだけsucceededを記録します。
製品API · cd.js → bridge.py
railshot-system観測: Pod診断 · 判定外Deployment · Pod · ダイジェスト指定のイメージ<app namespace>Argoの判定と公開確認の後、登録済みの認証情報でDeployment、ReplicaSet、Podを別途読み取り、imageIDと準備状態をready、progressing、unavailableの診断として追記します。診断が失敗したり異なったりしても、deployedやsucceededの判定が変わることはありません。
製品API · cd.js → bridge.py
railshot-system観測: ログtail · Podごとに100行Deployment · Pod · ダイジェスト指定のイメージ<app namespace>現在デプロイされているoperationに限り、信頼済みのconfigと登録済みの認証情報で読み取ります。現在のPod(最大3つ)ごとに、実行中のイメージダイジェストが期待するダイジェストと一致することを確かめたうえで、Podあたり最大100行を、機密値を伏せてバイト上限内で読みます。過去の試行にはsuperseded、設定がなければnot_configuredと応答します。ログ集約ではありません。
観測VM観測: scrape · NodePortrailshot-observability
観測VMのPrometheusが、ファイアウォールで制限した2つのNodePortを通じてnode-exporterとkube-state-metricsをscrapeします。欠損や古い値はゼロではなく不明として扱います。
観測VM観測: Blackbox probeプロバイダーのL7入口
probe_success=1は、そのプローブ地点で2xxを受け取ったことを意味します。すべてのユーザーのアクセスもデプロイされたバージョンも証明せず、publishedをdeployedに昇格させることもありません。
運用者のインストール · 登録
クラスターのインストール、登録bootstrapと対象の入力、Secret、認証情報の更新設定は運用者の入力であり、テナントアプリのpayloadには含まれません。
運用者のbootstrap実行 · 適用: platformのレンダリング · Secretrailshot-system
運用者がrender-platform.pyでplatformの宣言をレンダリングし、非公開の運用configとSecretを用意します。リモートMCPはイメージが与えられた場合のみ、個人用gateway(WireGuard)は--personalの場合のみ含まれます。
運用者のbootstrap実行 · 適用: 登録bootstrapAppProject · cluster Secret
argocd運用者がargocdに登録bootstrap Roleと空のregistrations Roleをインストールし、対象の入力を用意します。その後、製品APIが実行する登録ヘルパー(environment.py、applications.py)が、スコープを限った認証情報(clusterResources false、許可namespaceを列挙)でrailshot-<target> Secretを書き込み、検証済みの正確な名前をregistrations Roleに追記します。アプリごとの登録では、app-<id> namespaceを同名のAppProjectに結び付けます。
運用者のbootstrap実行 · 適用: K3s · CiliumのインストールCilium · Helm · VXLAN
kube-systeminstall-k3s.shが単一ノードのK3s serverをインストールし(flannel、network policy、traefik、servicelbはオフ、kube-proxyは維持)、CiliumをHelm releaseとしてインストールします(kubeProxyReplacement false、VXLAN、Hubbleはオフ)。
更新CronJob · 2時間ごと
argocd実行 · 適用: TokenRequest · 6時間railshot-argocd SA · トークン<app namespace>railshot-credentials CronJobが、2時間ごとに顧客SAの新しい6時間トークンを要求します。顧客側のRoleは、自身のSAに対するtoken createだけを許可します。6時間はトークン1つのTTLであり、運用者のアクセスが終わる時点ではありません。
すべての接続を同じ太さで表示します。フローを選ぶと、その経路だけを番号付きで強調します。
- 実線 · リクエスト、bundle、イメージ、宣言が流れる方向
- 破線 · 実行要求、適用、インストール、認証情報
- 長い一点鎖線 · 状態、ログ、プローブ、メトリクスの読み取り。判定とは別
- 太い黒線 · 選んだフロー。番号は順序
- ボックス · namespace、見出しに名前(ノードではない)
- 内側のチップ · そのnamespace内の構成要素
- 破線ボックス · クラスター外
- railshot-system · 製品
- argocd · デプロイ権限
- railshot-build · ホストDocker権限
- GitHub · 顧客クラスター
docs/architectureにある10月2日の配置決定(2つのロールノード)と公開READMEの図は履歴で、この構成図は645e28aの宣言とコードに従います。矢印は原因と方向を示すもので、すべてのプロバイダー経路が現在稼働していることの証明ではありません。
- namespaceとノード
- namespaceは名前のスコープです。ハードウェアでも、それ自体でセキュリティ境界でもありません。platformとbuildのロールはnodeSelectorとtaintによるスケジューリング上の配置で、この構成図は運用クラスターのノード数を定めていません。Argo CDのノード配置も別途確認する対象です。
- buildノードの権限
- runner JobはhostNetwork、ホストのDocker socket、NET_ADMIN、rootで実行されます。namespace、taint、RBACではホストのDocker権限を取り除けないため、これはVM管理者レベルの隔離でも、任意コードのためのサンドボックスでもありません。
- 顧客namespace
- 登録済みの対象ごとに、defaultではないnamespaceとAppProjectを使います(例:tenant-demo、アプリごとのapp-<id>)。作成するのは1つのサービスのDeployment(ServiceAccountトークンの自動マウントはオフ)、NodePort Service、NetworkPolicyと、任意のPVCまたは移行Jobだけです。Secretは名前で参照するだけで(pull Secretと承認済みのDATABASE_URL)、作成しません。
- AWSの入口
- Route 53 Alias → 共有ALB HTTPS 443 → 顧客ノードのプライベートアドレスとNodePort。複数のアプリが1つのノードを再利用し、アプリごとにnamespace、Application、NodePortを個別に持ちます。
- GCPの入口
- グローバルEXTERNAL_MANAGED HTTPS LB → ゾーンのGCE_VM_IP_PORT NEG → 既存VMのプライベートNodePort。証明書はCertificate ManagerからDNS authorizationで発行されます。モジュールはVMもVPCも所有せず、VM 1台は単一障害点として残ります。
- OpenStackの入口
- HostとpathのL7ポリシーを持つAmphora TLS listener 443 → HTTP NodePort。既定のアドレスはプライベートVIPで、Octavia、K3s、データベース、DNSはモジュールの外です。公開到達性と現地のAmphoraの準備には別途の受け入れ確認が必要で、このモジュールによる現地での適用はまだ確認されていません。
- プロバイダー間
- 3つの入口はそれぞれ別の実装で、プロバイダー間の自動フェイルオーバーや複製はありません。顧客アプリクラスターは登録済みの対象の一例であり、同じデプロイがすべてのクラウドで同時に動いているという主張ではありません。
- 運用者のオプション
- 個人用gateway(WireGuard)は、運用者が--personalを有効にした場合にのみplatformに加わり、顧客クラウドに必須の経路ではありません。サポートされるdirectモードでは、観測VMを運用のcontrol VMと共用できます。