서버 관리 개요
multi-saas-kit은 클라우드 서버를 코드로 관리(IaC) 하는 표준 체계를 제공합니다. Ansible로 OS 프로비저닝/소프트웨어 배포를, Tailscale로 서버 간 보안 네트워크를 구성합니다. IaaS 제공자는 선택 가능 — Vultr, DigitalOcean, Linode, Hetzner, AWS EC2 등 Ubuntu LTS를 제공하는 어떤 클라우드도 동일한 방식으로 동작합니다.
이 가이드의 예시에서 Vultr가 등장한다면, 그것은 하나의 참고 예시일 뿐입니다. 각 운영자가 선호하는 IaaS로 자유롭게 교체할 수 있으며, Ansible 코드는 수정 없이 재사용됩니다. 변경되는 것은 inventory/hosts.ini의 IP와 초기 VM 생성 단계뿐입니다.
이 체계의 의사결정 과정과 대안 비교는 ADR-018 Ansible 기반 서버 프로비저닝 채택을 참조하세요.
왜 이 조합인가
| 도구 | 역할 | 선택 이유 |
|---|---|---|
| IaaS 제공자 | VM 인프라 | 운영자 선호 (Vultr, DigitalOcean, Linode, Hetzner, AWS EC2 등). API·cloud-init·root-only 초기 상태 등 공통 요건만 충족하면 가능 |
| Ansible | OS 프로비저닝 + 배포 | Agentless(SSH만), 선언적, 멱등성, 5~100대 스윗스팟, IaaS 중립 |
| Tailscale | 서버 간 사내망 | 제로 설정 VPN, ACL 기반 권한, 방화벽 대체 |
| Docker Compose | 애플리케이션 레이어 | 기존 생태계와 호환, 앱 배포 단위 |
아키텍처 한눈에
┌──────────────────────────────────────────────────────────────┐
│ 로컬 Control Node (운영자 PC 또는 CI) │
│ workspace/_infra/ansible/ │
│ ├── inventory/ # 서버 목록 │
│ ├── roles/ # 역할별 설정 │
│ ├── playbooks/ # 실행 시나리오 │
│ └── vault/ # 암호화된 Secret │
└──────────────────┬───────────────────────────────────────────┘
│ SSH (Tailscale)
▼
┌──────────────────────────────────────────────────────────────┐
│ IaaS 서버들 (Tailscale Mesh 100.64.0.0/10) │
│ │
│ NPM 서버 ─▶ App 서버 1 ─▶ DB 서버 │
│ │ App 서버 2 │
│ └─▶ DevTools (pgAdmin, RedisInsight, Grafana) │
│ │
│ 모든 서버: Docker + Tailscale + node_exporter + promtail │
└──────────────────────────────────────────────────────────────┘
설계 원칙
| 원칙 | 의미 |
|---|---|
| 선언적 | "이 상태여야 한다"만 기술 — 절차 X, 결과 O |
| 멱등성 | 같은 Playbook을 반복 실행해도 결과 동일 |
| 최소 권한 | root 직접 로그인 차단, deploy 유저 + sudo |
| 비밀 분리 | Secret은 ansible-vault로 암호화 저장 |
| 역할 분리 | 공통부(common) + 역할별(role) 조합 |
| 점진 도입 | 5대 → Ansible 단독 / 10대+ → Terraform 추가 |
AI 협업과 IaC drift 관리
AI 에이전트(예: Claude Code)로 서버를 운영할 때 가장 큰 위험은 configuration drift — 에이전트나 사람이 호스트에서 직접 apt install·수동 설정을 하면, 그 변경이 플레이북(SSOT)에 없어 서버의 실제 상태와 코드가 어긋나는 현상입니다. drift 가 쌓이면 "새 서버를 코드로 찍어도 기존 서버와 달라져" 재현성과 서버 이전 가능성이 무너집니다.
multi-saas-kit 은 이를 "AI 를 막는다"가 아니라 구조적으로 drift 가 일어나기 어렵게, 일어나도 드러나게 만드는 3가지 방식으로 다룹니다.
| 전략 | 방법 | 효과 |
|---|---|---|
| ① 변경 표면 축소 (2계층 격리) | 호스트(OS·Docker·네트워크·드라이버)는 Ansible 이 관리하는 거의 불변 베이스로 유지하고, 변동성 큰 개발 의존성(파이썬/모델 SDK 등)은 전부 컨테이너·uv·pipx 로 격리 | 에이전트가 무엇을 설치하든 컨테이너에서 끝나 호스트가 오염되지 않음 |
| ② 호스트 변경은 코드 경유 | 호스트 레벨 변경이 필요하면 직접 설치 대신 ansible role 에 추가 → 적용. AI 지침에 "호스트 직접 apt/pip 금지"를 명문화 | 에이전트의 변경이 곧 SSOT 변경이 되어 drift 가 생기지 않음 |
| ③ drift 자동 탐지 | 컨트롤 노드에서 정기적으로 ansible-playbook --check --diff 를 cron 실행 → 차이가 있으면 알림(Discord 등) | drift 가 항상 보이는 상태 — 발견되면 코드에 반영(흡수)하거나 되돌림 |
"완벽한 관리"는 drift 가 0 인 상태가 아니라 drift 가 항상 드러나는 상태입니다. 변동성을 컨테이너로 몰아넣어 호스트를 단순하게 유지하면, Ansible 이 관리할 표면이 작아져 자동으로 관리가 쉬워집니다. AI 에이전트를 여러 개 동시에 운영할수록 이 격리·탐지 전략이 더 중요해집니다.
이미 운영 중인 호스트의 설정을 코드와 정합화할 때, Docker daemon.json 처럼 변경 시 데몬 재시작을 유발하는 파일은 무검증 적용이 위험합니다(실행 중 컨테이너 영향). 먼저 --check --diff 로 영향을 확인하고, 실제 상태와 내용이 정확히 일치하도록 변수화(parameterize) 하여 재시작 없이 SSOT 만 맞추는 방식을 권장합니다.
데이터(운영 DB)는 Ansible 의 관리 대상이 아닙니다 — 끊임없이 변하는 가변 상태라 멱등성 개념이 성립하지 않습니다. 서버 이전 시에는 pg_dump → 전송 → 복원 → 검증 절차를 Ansible 로 오케스트레이션할 수 있으나, 데이터 자체의 원천은 어디까지나 백업본과 운영 DB 입니다.
서버 역할 분류
Ansible inventory는 그룹 단위로 서버를 분류합니다.
| 그룹 | 역할 | 예시 서버 |
|---|---|---|
npm_servers | Nginx Proxy Manager (reverse proxy + SSL) | 1대 |
app_servers | Laravel SaaS 앱 | N대 (프로젝트별) |
bot_servers | 메신저 봇 호스트 (사설망/VPN 전용 노출) | 1~N대 |
db_servers | PostgreSQL | 1~N대 |
monitoring_servers | Grafana LGTM (로그/메트릭) | 1대 |
devtools_servers | pgAdmin, RedisInsight 등 | 1대 |
bot_fleet_managers | 봇 Fleet 중앙 제어 평면 | 1대 |
bot_nodes | 봇 Fleet Agent 설치 대상 | 1~N대 |
공통 그룹 all — 모든 서버에 기본 role 적용: common / user / hardening / tailscale / docker / node_exporter / promtail
관리 평면과 실행 평면 분리
봇 Fleet 관련 두 그룹은 권한을 일부러 반대로 갖습니다.
중앙 제어 평면 (bot_fleet_managers) | Agent (bot_nodes) | |
|---|---|---|
| Docker socket·저장소 | ❌ 없음 | ✅ 있음 (실제 작업 수행) |
| 외부에서 접속 | ✅ 리버스 프록시 경유 | ❌ 없음 (내보내는 연결만) |
관리 UI 가 곧 호스트 권한이 되는 구조를 피하기 위한 배치입니다. 한쪽이 뚫려도 다른 쪽 권한으로 곧장 이어지지 않습니다. 두 역할 모두 기본 비활성이라, 사용하지 않는 서버에는 아무 영향이 없습니다.
단계별 도입 로드맵
| Phase | 시기 | 도입 범위 | 서버 수 |
|---|---|---|---|
| 1 | 지금 | Ansible 단독 (정적 inventory), 기본 role 3~5개 | 5대 |
| 2 | 향후 | + Terraform (IaaS별 provider), dynamic inventory | 10대+ |
| 3 | Secret 복잡 | SOPS + age 마이그레이션 검토 | — |
| 4 | 팀 규모↑ | CI에서 Playbook 실행 (GitHub Actions), 승인 워크플로 | 50대+ |
지금 권장: Phase 1만 시작. Terraform은 VM이 자주 생성/파괴될 때 가치 — 초기엔 각 IaaS의 대시보드 UI로도 충분.
저장소 구조 (멀티 계정 Overlay)
multi-saas-kit은 공통 코드와 계정(tenant)별 데이터를 폴더 수준으로 분리합니다. 스타터킷을 구매한 개발사는 자사 서버 + 자기 고객사 서버까지 한 저장소로 관리할 수 있습니다(MSP 패턴).
workspace/_infra/
├── ansible/ ← 스타터킷 공통 (판매 대상)
│ ├── ansible.cfg # account-aware (환경변수 override)
│ ├── playbooks/{bootstrap,site}.yml
│ ├── roles/{common,user,hardening,tailscale,docker, …}
│ └── docs/ # 내부 세부 가이드
│
├── ansible-accounts/ ← 계정 루트
│ ├── _example/ ← 템플릿 (판매 포함)
│ ├── self/ ← 자사 계정 (gitignore)
│ ├── customer-a/ ← 고객사 A (gitignore)
│ └── customer-b/ ← 고객사 B (gitignore)
│
└── scripts/
└── ansible-account.sh ← 계정 선택 실행 wrapper
계정 격리 장치 3가지
| 장치 | 효과 |
|---|---|
계정별 .vault-pass 분리 | 다른 계정의 secret을 실수로 복호화 불가 |
ANSIBLE_INVENTORY 강제 주입 | ansible all -m ... 명령도 해당 계정 서버만 대상 |
| 계정 폴더 존재 검증 | 오타(예: customer-A vs customer-a) 즉시 에러 |
실행 예시
cd workspace/_infra
# 자사 서버
scripts/ansible-account.sh self site
# 고객사 서버
scripts/ansible-account.sh customer-a bootstrap --limit npm-01
scripts/ansible-account.sh customer-b site --tags hardening --check --diff
설계 배경: ADR-020 멀티 계정 Overlay
네트워크 설계
Public → Tailscale → Private 3단 구조
Public Internet
│ *.codebase.how (DNS: Cloudflare)
▼
NPM 서버 (80/443 only)
│ SSL 종료 + reverse proxy
▼
Tailscale Mesh (100.64.0.0/10)
├─ App 서버들
├─ DB 서버
└─ DevTools 어드민
방화벽 정책
| 포트 | 외부 공개 | Tailscale 내부 |
|---|---|---|
| 22 (SSH) | ❌ | ✅ Tailscale IP로만 |
| 80 (HTTP) | NPM만 ✅ | — |
| 443 (HTTPS) | NPM만 ✅ | — |
| 앱/DB/Redis | ❌ | ✅ 내부만 |
주요 자동화 범위
Ansible이 담당하는 작업:
| 범주 | 작업 |
|---|---|
| OS 기본 | hostname, timezone, apt update, 필수 패키지 |
| 계정 | deploy 유저 생성, SSH 키 주입, sudo NOPASSWD |
| 보안 | SSH hardening, UFW 규칙, fail2ban, unattended-upgrades |
| 네트워크 | Tailscale 가입 + ACL 태그 |
| 컨테이너 | Docker Engine + Compose v2 설치 |
| 관측 | node_exporter + promtail 설치·설정 |
| 앱 배포 | git clone + docker compose up -d + 환경변수 주입 |
| 역할별 | NPM / Laravel / PostgreSQL / Grafana LGTM 배포 |
다음 단계
- 서버 프로비저닝 가이드 — 임의 IaaS 서버 생성부터 Ansible bootstrap까지
- 일상 운영 시나리오 — 서버 추가/패치/장애 복구