Railshot
AI deployment & operations platform
로컬에서 만든 웹 앱을, 준비된 클라우드와 온프레미스에 배포하고 운영합니다.
로컬에서 만든 웹 앱을 클라우드나 온프레미스에 배포하고 운영까지 이어 주는 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 · source commit · target으로 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 하나의 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데이터: digest 게시GHCRGitHub-hosted release job(ubuntu-24.04)이 검사한 bundle의 이미지를 다시 빌드하지 않고 GHCR에 게시합니다. 이미지 게시는 고객 앱 클러스터 안에서 일어나지 않습니다.
구성 Git → Argo → 고객 namespace
config Git은 원하는 자원을, GHCR는 이미지 바이트를 나릅니다. Argo CD는 등록된 고객 namespace에만 적용합니다.
제품 API · cd.js → bridge.py
railshot-system데이터: workload commit · pushconfig Git 저장소cd.js가 bridge.py를 실행해, 게시 기록과 등록 대상이 맞을 때만 workload.json을 commit · 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 하나로 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만 허용하고 cluster 범위 자원은 허용하지 않습니다.
Deployment · Pod · digest 이미지
<app namespace>데이터: digest pullGHCR같은 namespace의 pull Secret을 이름으로 참조해 게시된 digest 이미지를 받습니다. Secret 값은 artifact나 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 · digest 이미지<app namespace>Service가 같은 노드의 앱 Pod(Deployment)로 보냅니다. externalTrafficPolicy Local이라 출발지 주소가 그대로 NetworkPolicy의 등록 CIDR 검사에 닿습니다.
Cilium · Helm · VXLAN
kube-system실행 · 적용: 정책 집행NetworkPolicy · 등록 CIDR만<app namespace>Cilium이 NetworkPolicy를 집행합니다. 들어오는 연결은 등록된 CIDR에서 앱 포트로만, 나가는 연결은 기본으로 없고 승인된 DB 주소만 열 수 있습니다. 클라우드 방화벽과 호스트 규칙도 여전히 필요합니다.
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 · ApplicationargocdApplication의 sync revision, Synced · Healthy, operation Succeeded와, Argo Application 상태에 기록된 이미지 참조가 검토한 Deployment의 digest 이미지와 같아야 deployed입니다. 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(최대 3개)마다 실행 이미지 digest가 기대한 digest와 같은지 확인한 뒤, Pod당 최대 100줄을 바이트 한도 안에서 민감한 값을 가린 채 읽습니다. 지난 시도는 superseded, 설정이 없으면 not_configured로 답합니다. 로그 수집 시스템이 아닙니다.
관측 VM관측: scrape · NodePortrailshot-observability
관측 VM의 Prometheus가 node-exporter와 kube-state-metrics를 방화벽으로 제한한 두 NodePort로 수집합니다. 없거나 오래된 값은 0이 아니라 알 수 없음입니다.
관측 VM관측: Blackbox probe공급자 L7 진입점
probe_success=1은 그 검사 위치에서 2xx를 받았다는 뜻입니다. 모든 이용자의 접속이나 배포 버전을 증명하지 않고, 게시를 배포로 올리지 않습니다.
운영자 설치 · 등록
클러스터 설치, 등록 bootstrap과 대상 정보, Secret, 자격 갱신 설정은 운영자 입력이며 고객 앱 payload에 들어 있지 않습니다.
운영자 bootstrap실행 · 적용: platform 렌더 · Secretrailshot-system
운영자가 render-platform.py로 platform 선언을 만들고 비공개 운영 설정 · Secret을 넣습니다. 원격 MCP는 이미지를 줄 때만, 개인 gateway(WireGuard)는 --personal일 때만 포함됩니다.
운영자 bootstrap실행 · 적용: 등록 bootstrapAppProject · cluster Secret
argocd운영자가 argocd에 등록 bootstrap Role과 빈 등록 Role을 설치하고 대상 정보를 준비합니다. 그 뒤 제품 API가 실행하는 등록 helper(environment.py · applications.py)가 범위를 정한 자격으로 railshot-<target> Secret(clusterResources false, 허용 namespace 명시)을 쓰고, 검증한 정확한 이름을 등록 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시간은 토큰 하나의 TTL이지 운영 접근이 끝나는 시점이 아닙니다.
모든 연결을 같은 굵기로 보여 줍니다. 흐름을 고르면 그 경로만 번호와 함께 강조됩니다.
- 실선 · 요청 · bundle · 이미지 · 선언이 흐르는 방향
- 파선 · 실행 요청 · 적용 · 설치 · 자격
- 긴 점선 · 상태 · 로그 · probe · 지표 읽기, 판정과 별개
- 굵은 검은 선 · 고른 흐름, 번호는 순서
- 상자 · 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 socket · NET_ADMIN · root로 실행됩니다. namespace, taint, RBAC가 호스트 Docker 권한을 없애지 않으므로 VM 관리자 격리나 임의 코드 샌드박스가 아닙니다.
- 고객 namespace
- 등록 대상마다 기본이 아닌 namespace와 AppProject를 씁니다(예: tenant-demo, 앱별 app-<id>). 서비스 하나의 Deployment(ServiceAccount 토큰 자동 마운트 끔) · NodePort Service · NetworkPolicy와 선택적 PVC · migration Job만 만들고, Secret은 pull Secret과 승인된 DATABASE_URL을 이름으로 참조할 뿐 만들지 않습니다.
- AWS 진입점
- Route 53 Alias → 공유 ALB HTTPS 443 → 고객 노드 사설 주소:NodePort. 한 노드를 여러 앱이 재사용하고 앱마다 namespace · Application · NodePort를 따로 받습니다.
- GCP 진입점
- 전역 EXTERNAL_MANAGED HTTPS LB → zonal GCE_VM_IP_PORT NEG → 기존 VM의 사설 NodePort. 인증서는 Certificate Manager의 DNS authorization으로 발급합니다. 모듈은 VM · VPC를 소유하지 않으며, VM 한 대는 단일 장애점으로 남습니다.
- OpenStack 진입점
- Amphora TLS listener 443과 Host · path L7 정책 → HTTP NodePort. 기본 주소는 사설 VIP이고 Octavia · K3s · DB · DNS는 모듈 밖입니다. 공개 접속과 현장 Amphora 준비는 따로 인수해야 하며, 현장 적용은 이 모듈로 아직 확인하지 않았습니다.
- 공급자 사이
- 세 진입점은 서로 다른 구현이며 공급자 사이의 자동 failover나 복제는 없습니다. 고객 앱 클러스터는 등록 대상의 예시이고, 모든 클라우드에 같은 배포가 동시에 떠 있다는 뜻이 아닙니다.
- 운영자 선택
- 개인 gateway(WireGuard)는 운영자가 --personal로 켤 때만 platform에 포함되며 고객 클라우드의 필수 경로가 아닙니다. 관측 VM은 지원되는 direct 모드에서 운영 control VM에 함께 둘 수 있습니다.