RAILSHOT.
전시실로

Railshot

AI deployment & operations platform

로컬에서 만든 웹 앱을, 준비된 클라우드와 온프레미스에 배포하고 운영합니다.

로컬에서 만든 웹 앱을 클라우드나 온프레미스에 배포하고 운영까지 이어 주는 AI 배포 · 운영 플랫폼입니다.

2026.09.28 — 2026.10.04 · 종료

팀 프로젝트 · 맡은 일: CI 기획·구현, CSP 클러스터·Ansible 인터페이스, 운영, 팀 코드 리뷰·통합

SoftBank Hackathon 2026 powered by KOREC & Progate예선 참가 · 본선 선발 발표 예정 10.26 (10.07 기준)

  • 웹구현 · 해커톤
  • MCP 서버구현 · 원격 · 로컬

운영 클러스터와 고객 앱 클러스터를 나누고, 접속 경로와 배포 제어를 구분합니다.

운영 K3s에서는 대시보드 · 제품 API · build controller가 platform 역할 노드에, runner Job이 build 역할 노드에 배치되고 Argo CD가 배포를 맡습니다. 고객 앱은 등록한 대상마다 따로 둔 단일 노드 K3s + Cilium 클러스터의 지정 namespace에서 실행됩니다.

클러스터 구성도 · 공개 커밋 645e28a의 선언과 코드로 그림
Railshot 운영 · 고객 클러스터 구성도운영 K3s 클러스터와 등록한 고객 앱 클러스터의 구성입니다. 운영 클러스터의 railshot-system namespace에서 대시보드, 제품 API, 원격 MCP(이미지를 줄 때만), build controller가 platform 역할 노드에 배치되고, railshot-build namespace의 runner Job은 taint를 둔 build 역할 노드에서 GitHub Actions job을 받습니다. 검사한 bundle은 GitHub-hosted release job이 GHCR에 게시합니다. 제품 API는 config Git에 workload를 commit하고 argocd namespace의 Application을 sync하며, Argo CD가 등록한 고객 클러스터의 namespace에 Deployment · NodePort Service · NetworkPolicy를 적용합니다. 앱 요청은 공급자 L7 진입점에서 노드의 NodePort와 Service를 거쳐 앱 Pod로 들어옵니다. 배포 판정은 Argo 상태와 공개 URL 확인으로만 정해지고, Pod 진단 · 로그 · 지표는 따로 읽는 신호입니다. 운영자의 설치 · 등록 입력은 고객 앱 payload와 분리되어 있습니다.Railshot 사용자대시보드 · MCP운영자 bootstrap설치 · 등록 · SecretGHCRdigest 이미지config Git 저장소workload.json · revision공급자 L7 진입점ALB · GCP LB · Amphora앱 이용자공개 HTTPS관측 VM고객 K3s 밖 · Composerailshot-systemdashboard · nginx원격 MCP · 이미지를 줄 때만build controller · CronJob제품 API · cd.js → bridge.pyargocdArgo CD · ApplicationAppProject · cluster Secret자격 갱신 CronJob · 2시간마다railshot-buildrunner Job · hostNetwork · Docker socketrailshot-deploy.ymlloop job · self-hosted runnerrelease job · GitHub-hosted<app namespace>Deployment · Pod · digest 이미지NodePort Service · LocalNetworkPolicy · 등록 CIDR만railshot-argocd SA · 토큰 발급kube-systemCilium · Helm · VXLANrailshot-observabilitynode-exporterkube-state-metrics운영 클러스터 · K3sAWS · 역할별 노드platform 역할 노드nodeSelectorbuild 역할 노드NoSchedule taintGitHubActions · GHCR고객 앱 클러스터 · 예시등록 대상마다 따로K3s 노드 1대 · serverCilium · kube-proxy 유지1workflow_dispatch2runner Job 보충3job 수령4검사한 bundle5 digest 게시 · release job · GitHub-hosted → GHCR51workload commit · push2revision3 Application sync · 제품 API · cd.js → bridge.py → Argo CD · Application34등록 namespace에 적용5digest pull1HTTPS 4432NodePort3 Pod로 전달 · NodePort Service · Local → Deployment · Pod · digest 이미지34 정책 집행 · Cilium · Helm · VXLAN → NetworkPolicy · 등록 CIDR만45 대시보드 · API · Railshot 사용자 → dashboard · nginx51 revision · health · 이미지 · 제품 API · cd.js → bridge.py → Argo CD · Application12공개 HTTPS 확인3Pod 진단 · 판정 밖4로그 tail · Pod당 100줄5scrape · NodePort6Blackbox probe1platform 렌더 · Secret2등록 bootstrap3K3s · Cilium 설치4TokenRequest · 6시간

빌드 → 이미지 게시

제품 API는 GitHub Actions 실행을 요청하고, build 노드의 runner가 그 job을 받아 검사합니다. 같은 검사를 통과한 bundle만 GitHub-hosted release job이 GHCR에 게시합니다.

  1. 제품 API · cd.js → bridge.pyrailshot-system실행 · 적용: workflow_dispatchloop job · self-hosted runnerrailshot-deploy.yml

    제품 API가 tenant · app · source commit · target으로 GitHub Actions 실행을 요청합니다. API가 runner Job을 직접 부르지 않습니다.

  2. build controller · CronJobrailshot-system실행 · 적용: runner Job 보충runner Job · hostNetwork · Docker socketrailshot-build

    build controller CronJob(매분, platform 노드)이 railshot-build namespace에 runner Job을 만듭니다. 그 Role은 Job get · list · create와 runner 등록 Secret 하나의 get · update · patch뿐이며, namespace나 다른 Secret을 만들지 못합니다.

  3. runner Job · hostNetwork · Docker socketrailshot-build실행 · 적용: job 수령loop job · self-hosted runnerrailshot-deploy.yml

    build 역할 노드(정확한 hostname · NoSchedule toleration)의 runner가 self-hosted 라벨의 loop job을 받아 실행합니다. 고객 소스는 기존의 제한된 Docker 빌드 · 검사 컨테이너에서 돕니다.

  4. runner Job · hostNetwork · Docker socketrailshot-build데이터: 검사한 bundlerelease job · GitHub-hostedrailshot-deploy.yml

    loop job이 같은 검사를 통과한 bundle을 남기고, release job은 그 bundle만 받습니다.

  5. release job · GitHub-hostedrailshot-deploy.yml데이터: digest 게시GHCR

    GitHub-hosted release job(ubuntu-24.04)이 검사한 bundle의 이미지를 다시 빌드하지 않고 GHCR에 게시합니다. 이미지 게시는 고객 앱 클러스터 안에서 일어나지 않습니다.

구성 Git → Argo → 고객 namespace

config Git은 원하는 자원을, GHCR는 이미지 바이트를 나릅니다. Argo CD는 등록된 고객 namespace에만 적용합니다.

  1. 제품 API · cd.js → bridge.pyrailshot-system데이터: workload commit · pushconfig Git 저장소

    cd.js가 bridge.py를 실행해, 게시 기록과 등록 대상이 맞을 때만 workload.json을 commit · push하고 원격 revision을 다시 읽습니다. Git은 원하는 자원을, GHCR는 이미지 바이트를 나릅니다.

  2. config Git 저장소데이터: revisionArgo CD · Applicationargocd

    Application은 제한된 config 저장소 · 경로 · revision만 읽습니다.

  3. 제품 API · cd.js → bridge.pyrailshot-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는 이름으로 제한되지 않으므로, 이름 범위는 제품의 입력 검증도 함께 지킵니다.

  4. Argo CD · Applicationargocd실행 · 적용: 등록 namespace에 적용<app namespace>

    Argo CD가 등록한 cluster Secret의 자격으로 고객 클러스터의 지정 namespace에 적용합니다. AppProject는 namespace 범위 kind만 허용하고 cluster 범위 자원은 허용하지 않습니다.

  5. Deployment · Pod · digest 이미지<app namespace>데이터: digest pullGHCR

    같은 namespace의 pull Secret을 이름으로 참조해 게시된 digest 이미지를 받습니다. Secret 값은 artifact나 config Git에 들어가지 않습니다.

공개 요청 경로

앱 요청은 공급자 L7 진입점 → 노드 NodePort → Service → 앱 Pod로 갑니다. Cilium의 정책 집행은 이 요청을 나르는 단계가 아니라 따로 거는 제어입니다. 운영 대시보드로 들어오는 길은 별개입니다.

  1. 앱 이용자데이터: HTTPS 443공급자 L7 진입점

    앱 이용자는 등록된 hostname으로 공급자의 L7 진입점에 접속합니다. 진입점은 공급자마다 따로 구현합니다.

  2. 공급자 L7 진입점데이터: NodePortNodePort Service · Local<app namespace>

    진입점이 노드의 사설 주소:NodePort로 전달합니다. NodePort는 대상 등록에서 할당된 값입니다.

  3. NodePort Service · Local<app namespace>데이터: Pod로 전달Deployment · Pod · digest 이미지<app namespace>

    Service가 같은 노드의 앱 Pod(Deployment)로 보냅니다. externalTrafficPolicy Local이라 출발지 주소가 그대로 NetworkPolicy의 등록 CIDR 검사에 닿습니다.

  4. Cilium · Helm · VXLANkube-system실행 · 적용: 정책 집행NetworkPolicy · 등록 CIDR만<app namespace>

    Cilium이 NetworkPolicy를 집행합니다. 들어오는 연결은 등록된 CIDR에서 앱 포트로만, 나가는 연결은 기본으로 없고 승인된 DB 주소만 열 수 있습니다. 클라우드 방화벽과 호스트 규칙도 여전히 필요합니다.

  5. Railshot 사용자데이터: 대시보드 · APIdashboard · nginxrailshot-system

    운영 진입점에서 nginx 대시보드로 들어오고, nginx가 제품 API를 앞에서 받습니다. dashboard Service는 승인된 node port를 줄 때만 NodePort이고, API는 ClusterIP로 남습니다.

확인 · 관측

배포 판정은 Argo 상태와 공개 URL 확인으로만 정해집니다. Pod 진단, 현재 로그, 운영 지표는 따로 읽는 신호이며 판정을 바꾸지 않습니다.

  1. 제품 API · cd.js → bridge.pyrailshot-system관측: revision · health · 이미지Argo CD · Applicationargocd

    Application의 sync revision, Synced · Healthy, operation Succeeded와, Argo Application 상태에 기록된 이미지 참조가 검토한 Deployment의 digest 이미지와 같아야 deployed입니다. Pod의 imageID를 직접 확인하는 게이트가 아니며, Argo의 초록 표시만으로 이 일치가 확인되지도 않습니다.

  2. 제품 API · cd.js → bridge.pyrailshot-system관측: 공개 HTTPS 확인공급자 L7 진입점

    등록된 공개 URL을 리다이렉트 없이 확인합니다. Argo 판정과 이 확인이 모두 있어야 succeeded로 기록합니다.

  3. 제품 API · cd.js → bridge.pyrailshot-system관측: Pod 진단 · 판정 밖Deployment · Pod · digest 이미지<app namespace>

    Argo 판정과 공개 확인을 마친 뒤, 등록 자격으로 Deployment · ReplicaSet · Pod를 따로 읽어 imageID와 준비 상태를 ready · progressing · unavailable 진단으로 덧붙입니다. 이 진단이 실패하거나 달라도 deployed · succeeded 판정은 바뀌지 않습니다.

  4. 제품 API · cd.js → bridge.pyrailshot-system관측: 로그 tail · Pod당 100줄Deployment · Pod · digest 이미지<app namespace>

    현재 배포된 operation에만, 신뢰된 config와 등록 자격으로 읽습니다. 현재 Pod(최대 3개)마다 실행 이미지 digest가 기대한 digest와 같은지 확인한 뒤, Pod당 최대 100줄을 바이트 한도 안에서 민감한 값을 가린 채 읽습니다. 지난 시도는 superseded, 설정이 없으면 not_configured로 답합니다. 로그 수집 시스템이 아닙니다.

  5. 관측 VM관측: scrape · NodePortrailshot-observability

    관측 VM의 Prometheus가 node-exporter와 kube-state-metrics를 방화벽으로 제한한 두 NodePort로 수집합니다. 없거나 오래된 값은 0이 아니라 알 수 없음입니다.

  6. 관측 VM관측: Blackbox probe공급자 L7 진입점

    probe_success=1은 그 검사 위치에서 2xx를 받았다는 뜻입니다. 모든 이용자의 접속이나 배포 버전을 증명하지 않고, 게시를 배포로 올리지 않습니다.

운영자 설치 · 등록

클러스터 설치, 등록 bootstrap과 대상 정보, Secret, 자격 갱신 설정은 운영자 입력이며 고객 앱 payload에 들어 있지 않습니다.

  1. 운영자 bootstrap실행 · 적용: platform 렌더 · Secretrailshot-system

    운영자가 render-platform.py로 platform 선언을 만들고 비공개 운영 설정 · Secret을 넣습니다. 원격 MCP는 이미지를 줄 때만, 개인 gateway(WireGuard)는 --personal일 때만 포함됩니다.

  2. 운영자 bootstrap실행 · 적용: 등록 bootstrapAppProject · cluster Secretargocd

    운영자가 argocd에 등록 bootstrap Role과 빈 등록 Role을 설치하고 대상 정보를 준비합니다. 그 뒤 제품 API가 실행하는 등록 helper(environment.py · applications.py)가 범위를 정한 자격으로 railshot-<target> Secret(clusterResources false, 허용 namespace 명시)을 쓰고, 검증한 정확한 이름을 등록 Role에 덧붙입니다. 앱별 등록은 app-<id> namespace와 같은 이름의 AppProject로 묶입니다.

  3. 운영자 bootstrap실행 · 적용: K3s · Cilium 설치Cilium · Helm · VXLANkube-system

    install-k3s.sh가 단일 노드 K3s server를 설치하고(flannel · network policy · traefik · servicelb 끔, kube-proxy 유지), Cilium을 Helm release로 설치합니다(kubeProxyReplacement false · VXLAN · Hubble 끔).

  4. 자격 갱신 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에 함께 둘 수 있습니다.