Skip to content
Go back

퀀트 플랫폼 개발기 #7 — Tailscale + GitHub Actions runner 로 운영 흐름 만들기

시작

지난 편 (#6) 에서 Vultr Seoul VPS 에 paper 자동매매를 띄웠다. cron 이 돌고 매매가 일어나는 건 확인했지만, 실제로 그 결과를 보러 가는 길은 여전히 번거로웠다. 매번 ssh -L 8080:localhost:8080 으로 터널 띄우고, 코드 한 줄 바꿀 때마다 다시 ssh 들어가서 git pull && docker compose up -d --build 손으로. 운영 페이스가 안 잡혔다. 이번엔 그걸 정리한 작업.

무엇을 했나

시행착오 3개

(a) Mac DB ≠ VPS DB — 30분을 헤매다

실현손익 카드를 만들고 web 에 띄웠는데 데이터가 안 보였다. 디버깅하다 보니 web 이 호출하는 API 가 Mac 의 docker 8080 이었고, 그 API 는 4일 전 Mac DB 를 보고 있었다. 매매는 VPS 에서 일어났는데 두 DB 가 별개라는 사실을 잠깐 잊고 있었다.

[Mac 브라우저 :3000] → localhost:8080 → Mac docker api → Mac DB  ← 4일 전 dump 시점
[VPS cron 15:20]    → run-live.sh    → VPS DB                   ← 매매 결과

docker compose stop api engine 으로 Mac docker 를 끄고 ssh -L 로 VPS API 를 보게 전환하니 비로소 카드에 +₩6,271,600 이 표시됐다. 이 잠깐의 헤맴이 다음 두 시행착오 (Tailscale 도입, docker-compose.override.yml 분리) 의 동기가 됐다.

(b) cloud-init 이 sshd_config 를 조용히 override

SSH 키 인증으로 전환하면서 /etc/ssh/sshd_configPasswordAuthentication no 로 변경했는데, 비번 인증이 계속 통과됐다. effective 값을 직접 보면:

sshd -T -C user=root -C host=localhost -C addr=127.0.0.1 -f /etc/ssh/sshd_config | grep password
# → passwordauthentication yes   ← ?

cloud image 의 흔한 함정이었다. /etc/ssh/sshd_config.d/50-cloud-init.conf 가 메인 config 를 덮어쓰고 있었다. 그 파일에서 yesno 로 바꾸고 ssh 재시작하자 효과가 적용됐다.

교훈 — grep -E "PasswordAuthentication" /etc/ssh/sshd_config 만 보고 안심하지 말고 sshd -Teffective 값 을 확인할 것. cloud image 는 거의 다 sshd_config.d/ include 가 걸려있다.

(c) docker 의 iptables 가 ufw 를 우회

ufw 로 22 외 모두 차단 + Tailscale interface 만 허용한 뒤 외부 차단 검증:

curl --max-time 5 http://<VPS-PUBLIC-IP>:8080/api/...
# → 정상 응답   ← 막혔어야 하는데?

docker 가 자체 iptables DOCKER chain 으로 패킷을 직접 라우팅한다. ufw 의 INPUT chain 을 안 거치는 게 docker 의 설계 의도라서 ufw 만으로는 못 막는다. 우회법 두 가지:

  1. daemon.jsoniptables: false — docker 가 iptables 못 만지게. 다른 부작용 위험
  2. ports 바인딩을 외부 NIC 에 안 하기 — listen 자체를 막음

(2) 가 정공법. 0.0.0.0:8080:8080<TAILSCALE-IP>:8080:8080 (Tailscale IP) 로 변경하면 외부 NIC 에선 listen 안 함. 단, 이걸 git 추적 docker-compose.yml 에 박으면 Mac 개발 환경에선 못 쓴다. 그래서 docker-compose.override.yml 패턴:

# docker-compose.override.yml — VPS 에만, gitignore 됨
services:
  api:
    ports: !override
      - "<TAILSCALE-IP>:8080:8080"
  engine:
    ports: !override
      - "<TAILSCALE-IP>:8000:8000"

!override 는 docker compose v2.24+ 의 list-merge 우회 태그. 본 yml 의 ports list 와 append 되는 게 아니라 완전 교체. 이러면 본 yml 은 Mac 기준 127.0.0.1 그대로 유지하고, VPS 에선 override 가 자동 merge 되어 Tailscale IP 바인딩.

운영 흐름 (after)

[Mac] git push origin dev
   ↓ (1분 안)
[GitHub Actions → VPS runner (outbound polling)]

deploy.sh — git pull + docker compose up -d --build api engine

[Mac 브라우저 localhost:3000] Tailscale 통해 즉시 반영

self-hosted runner 의 핵심 — inbound 포트 0개. VPS 가 GitHub 으로 outbound polling 하는 패턴이라 외부에 새 endpoint 를 노출하지 않는다. 직전 단계에서 닦은 ufw + 키 인증 + fail2ban 보안을 그대로 유지한 채 자동 배포가 얹혔다.

운영 흐름 비교:

작업BeforeAfter
새 코드 배포ssh + git pull + rebuild 4단계git push
UI 보러 가기ssh -L 8080:... 매번Tailscale 켜놓기만
외부 봇 차단docker 가 ufw 우회해서 사실상 노출Tailscale IP 만 listen, 외부 timeout

회고

VPS 셋업 자체보다, 그 다음날의 잡일을 제거하는 데 비슷한 시간이 들었다. 운영 가능한 시스템과 실험용 시스템의 차이는 거의 다 이런 잡일에 있다.

오늘 배운 거 세 가지:

paper 1-2개월 검증 동안은 이 흐름으로 충분할 듯하다. 실전 모드 가기 전엔 (1) runner 를 비-root user 로 격리, (2) VPS 정기 스냅샷 정도만 추가 예정.


Share this post on:

Comments


Previous Post
퀀트 플랫폼 개발기 #8 — 백테스트 · 스크리너 · 자동탐색 최적화하기
Next Post
퀀트 플랫폼 개발기 #6 — Mac → Vultr Seoul VPS 이주, paper 자동매매 띄우기