Railshot
AI deployment & operations platform
将在本机构建的 Web 应用部署到预先准备的云端与本地机房环境,并加以运维。
一个 AI 部署与运维平台:把在本机构建的 Web 应用部署到预先准备的云端或本地机房环境,并持续运行。
运维集群与客户应用集群相互分离,公开路径与部署控制分开。
在运维 K3s 中,仪表盘、产品 API 和 build controller 放置在 platform 角色节点上,runner Job 放置在 build 角色节点上,由 Argo CD 负责部署。客户应用运行在每个已注册目标各自独立的单节点 K3s + Cilium 集群的指定 namespace 中。
构建 → 发布镜像
产品 API 请求一次 GitHub Actions 运行,build 节点上的 runner 领取该作业并执行检查。只有通过同样检查的 bundle 才由 GitHub 托管的 release 作业发布到 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 Jobrunner Job · hostNetwork · Docker socketrailshot-buildbuild controller CronJob(每分钟一次,运行在 platform 节点上)在 railshot-build namespace 中创建 runner Job。其 Role 只允许对 Job 执行 get、list 和 create,以及对唯一的 runner 注册 Secret 执行 get、update 和 patch;它不能创建 namespace 或其他 Secret。
runner Job · hostNetwork · Docker socket
railshot-build运行 · 应用: 领取作业loop job · self-hosted runnerrailshot-deploy.ymlbuild 角色节点上的 runner(精确 hostname,NoSchedule toleration)按 self-hosted 标签领取 loop 作业并运行。客户源码在现有的受限 Docker 构建与检查容器中运行。
runner Job · hostNetwork · Docker socket
railshot-build数据: 已测试 bundlerelease job · GitHub-hostedrailshot-deploy.ymlloop 作业留下通过同样检查的 bundle,release 作业只接收该 bundle。
release job · GitHub-hosted
railshot-deploy.yml数据: 按 digest 发布GHCR由 GitHub 托管的 release 作业(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 才 commit 并 push workload.json,然后读回远程 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 进行不带 prune 的 sync。产品 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 · digest 镜像
<app namespace>数据: 按 digest pullGHCR已发布的 digest 镜像通过同一 namespace 中按名称引用的 pull Secret 拉取。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>数据: 到 PodDeployment · Pod · digest 镜像<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 才是 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 状态中记录的镜像引用等于经审查的 Deployment 的 digest 镜像。这不是直接检查 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 · digest 镜像<app namespace>完成 Argo 判定和公开检查后,用已注册凭据另行读取 Deployment、ReplicaSet 和 Pod,并将 imageID 与就绪状态作为 ready、progressing 或 unavailable 诊断附加上去。诊断失败或不一致,都不会改变 deployed 或 succeeded 判定。
产品 API · cd.js → bridge.py
railshot-system观测: 日志 tail · 每个 Pod 100 行Deployment · Pod · digest 镜像<app namespace>仅针对当前已部署的 operation,使用可信 config 和已注册凭据。对当前每个 Pod(最多三个),运行中的镜像 digest 必须等于预期 digest;随后在字节上限内为每个 Pod 读取最多 100 行已脱敏的日志。较早的尝试返回 superseded;未配置时返回 not_configured。这不是日志聚合。
观测 VM观测: 抓取 · NodePortrailshot-observability
观测 VM 上的 Prometheus 通过两个受防火墙限制的 NodePort 抓取 node-exporter 和 kube-state-metrics。缺失或过期的值为未知,而不是零。
观测 VM观测: Blackbox probe提供商 L7 入口
probe_success=1 表示在该探测位置收到 2xx。它既不证明所有用户都能访问,也不证明已部署的版本,也绝不会把已发布提升为已部署。
运维人员安装 · 注册
集群安装、注册 bootstrap 与目标输入、Secret 以及凭据续期设置都是运维人员的输入,从不属于租户应用 payload。
运维人员初始化运行 · 应用: 渲染 platform · Secretrailshot-system
运维人员用 render-platform.py 渲染 platform 声明,并提供私有的运维配置和 Secret。远程 MCP 仅在提供镜像时纳入,个人 gateway(WireGuard)仅在使用 --personal 时纳入。
运维人员初始化运行 · 应用: 注册 bootstrapAppProject · cluster Secret
argocd运维人员在 argocd 中安装注册 bootstrap Role 和一个空的 registrations Role,并准备目标输入。随后,产品 API 运行的注册 helper(environment.py、applications.py)以限定范围的凭据写入 railshot-<target> Secret(clusterResources false,列出允许的 namespace),并将经验证的确切名称追加到 registrations Role。按应用注册时,app-<id> namespace 与同名 AppProject 绑定。
运维人员初始化运行 · 应用: 安装 K3s · CiliumCilium · 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 每两小时为客户 SA 请求一个新的六小时令牌。客户侧 Role 只允许为其自身 SA 执行 token create。六小时是单个令牌的 TTL,而不是运维访问结束的时间。
所有连接以相同粗细显示。选择一个流程,即只突出显示其路径,并标出步骤编号。
- 实线 · 请求、bundle、镜像与声明的流向
- 虚线 · 运行请求、应用、安装、凭据
- 长点划线 · 读取状态、日志、探针、指标;独立于判定
- 粗黑线 · 所选流程;数字表示顺序
- 方框 · 一个 namespace,标题栏为其名称(不是节点)
- 内层小框 · 该 namespace 中的组件
- 虚线框 · 集群之外
- railshot-system · 产品
- argocd · 部署权限
- railshot-build · 主机 Docker 权限
- GitHub · 客户集群
docs/architecture 中 10月2日的布局决定(两个角色节点)和公开 README 中的图都属于历史;本结构图依据 645e28a 的声明与代码。箭头表示原因与方向,并不证明每条提供商路径当前都在运行。
- namespace 与节点
- namespace 是命名范围:既不是硬件,本身也不是安全边界。platform 与 build 角色是通过 nodeSelector 和 taint 实现的调度布局;本图不规定运维集群的节点数。Argo CD 的节点布局也需另行检查。
- build 节点权限
- runner Job 以 hostNetwork、主机 Docker 套接字、NET_ADMIN 和 root 运行。namespace、taint 或 RBAC 都不能移除主机 Docker 权限,因此这既不是 VM 管理员级别的隔离,也不是任意代码的沙箱。
- 客户 namespace
- 每个已注册目标使用非默认的 namespace 和 AppProject(例如 tenant-demo,或按应用的 app-<id>)。只创建一个服务的 Deployment(关闭 ServiceAccount 令牌自动挂载)、NodePort Service 和 NetworkPolicy,以及可选的 PVC 或迁移 Job;Secret 只按名称引用(pull Secret 和经批准的 DATABASE_URL),从不创建。
- AWS 入口
- Route 53 Alias → 共享 ALB HTTPS 443 → 客户节点私有地址和 NodePort。多个应用复用一个节点,每个应用各有自己的 namespace、Application 和 NodePort。
- GCP 入口
- 全局 EXTERNAL_MANAGED HTTPS LB → 可用区 GCE_VM_IP_PORT NEG → 现有 VM 的私有 NodePort。证书由 Certificate Manager 通过 DNS 授权签发。该模块既不拥有 VM 也不拥有 VPC,单台 VM 仍是单点故障。
- OpenStack 入口
- Amphora TLS 监听器 443,带 Host 与路径 L7 策略 → HTTP NodePort。默认地址是私有 VIP;Octavia、K3s、数据库和 DNS 不在模块范围内。公网可达性和现场 Amphora 就绪需另行验收,且尚未用该模块验证过现场应用。
- 跨提供商
- 三个入口是各自独立的实现,提供商之间没有自动故障转移或复制。客户应用集群是已注册目标的一个示例,并不表示同一部署同时运行在所有云上。
- 运维人员选项
- 个人 gateway(WireGuard)仅在运维人员启用 --personal 时加入 platform;它不是客户云的必经路径。在受支持的 direct 模式下,观测 VM 可以与运维控制 VM 共用。