개발 · 빌드 · 배포 서버/인프라
이 문서는 앞의 두 문서(core·template)에서 만든 AI 하네스가 실제 클라우드 서버 위에서 어떻게 돌아가는가를 다룹니다. 보안을 위해 구체적 IP·인스턴스 식별자·계정 번호는 생략하고, 구성요소와 데이터 흐름 중심으로 봅니다.
1. 서버 토폴로지
인프라는 같은 클라우드 계정·같은 VPC 안의 prod 박스와 dev 박스 두 대로 나뉩니다.
- prod 박스: 신규 프로덕션 전용.
- dev 박스: 모든 프로젝트의 개발 서버가 도는 곳(과거 레거시 prod도 여기 있었으나 분리 이전 중).
두 박스 모두 같은 스택을 올려 둡니다.
| 구성요소 | 역할 |
|---|---|
| Apache 2.4 | 외부에서 닿는 유일한 관문. 80(→HTTPS 리다이렉트)·443(TLS 종단) + 리버스 프록시 |
| 앱 서버 | prod=Gunicorn(systemd 데몬) · dev=Django runserver(포그라운드) |
| PostgreSQL 14 | localhost:5432. prod DB=<project>_prod · dev DB=<project> |
| Redis · huey | 비동기 작업 큐(쓰는 프로젝트만, prod는 별도 systemd 서비스) |
| 정적 "CDN" 디렉토리 | Apache가 직접 서빙하는 파일 디렉토리(<slug>_static_cdn/) |
| Route53 · Let's Encrypt | DNS · 인증서 자동 발급/갱신 |
핵심 규칙 두 가지: - 앱 포트(8000번대)는 VPC 내부에서만 닿습니다. 외부 요청은 언제나 Apache 443을 거칩니다. - 프로덕션은 배포자 개인 홈에서 개인 계정으로 돌립니다(SSH·DB·git 동작이 개인 단위로 추적됨).
2. 설정 스위치
같은 코드베이스가 dev인지 prod인지는 환경변수 하나로 갈립니다.
<PROJ>_PRODUCTION=True 이면 production 설정 + .env.production + <project>_prod DB로 뜨고, 설정이 없으면 local 설정 + .env + <project> DB로 뜹니다.
그래서 한 코드베이스가 dev·개인 dev·prod 여러 인스턴스로 동시에 존재할 수 있고, 이들을 가르는 것은 오직 .env와 이 스위치입니다.
3. 개발 흐름
개발자는 자기 clone에서 runserver로 프로젝트를 띄웁니다.
포트는 8000번대에서 하나 배정받고, /dev 스킬이 그 포트에 대해 Apache vhost·Let's Encrypt 인증서·Route53 레코드를 자동 세팅해 <slug>-dev 도메인으로 HTTPS 접근을 엽니다.
DB는 로컬 PostgreSQL의 dev DB를 씁니다.
개발 요청의 데이터 경로는 이렇습니다.
정적 파일은 dev(DEBUG=True)에선 runserver가 직접 내보냅니다.
4. 빌드·배포 흐름
/release는 dev 박스에서 실행돼 prod 박스로 SSH하는 2-호스트 원격 배포입니다. master 병합만으로는 배포되지 않고, /release가 유일한 배포 경로입니다.
변경 델타를 분석해 필요한 단계만 조건부로 실행합니다.
- 프리플라이트: prod에서
git fetch, 배포 델타 계산. 새 커밋 없으면 SKIP, 트리가 더러우면 BLOCKED. - 코드 반영:
git pull --ff-only(또는 태그 checkout). 코드가 GitHub에서 prod로 들어옴. - 의존성(requirements 변경 시):
pip install. core를 고정 태그로, playwright 등도 함께 설치. - 마이그레이션(마이그레이션 파일 변경 시, 사람 승인 게이트): 실패하면 이후 단계 중단.
- 정적 수집(static/templates 변경 시):
collectstatic이 내용 해시된 파일을 정적 CDN 디렉토리에 씀. - 재시작: gunicorn 서비스 항상 재시작.
tasks.py변경 시 huey 소비자 서비스도 재시작. - 헬스체크: HTTPS 루트·리다이렉트·정적·admin·내부 포트를 curl. 하나라도 실패면 BLOCKED(자동 롤백 없음).
5. 프로덕션 요청 경로
프로덕션 요청은 동기(웹) 와 비동기(작업 큐) 두 갈래로 흐릅니다.
동기 경로: 브라우저 → Route53 → prod 박스 Apache 443(TLS 종단).
여기서 갈립니다. /static·/media는 Apache가 정적 CDN 디렉토리에서 직접 내보내고, 나머지 동적 요청만 내부 포트의 Gunicorn → Django(production, DEBUG=False) → PostgreSQL prod DB로 갑니다.
비동기 경로: Django 뷰가 작업을 큐에 넣으면(enqueue) → Redis/huey 큐 → 별도 huey 소비자 프로세스가 꺼내 LLM 등 긴 작업을 실행 → 결과를 prod DB에 씁니다.
주의: 소비자가 실제로 떠 있어야 합니다. 안 그러면 "비동기" 작업이 gunicorn 워커 안에서 돌다 타임아웃으로 죽습니다.
외부로 나가는 LLM·구글 API 호출은 core를 거쳐 나가며 트레이싱됩니다(standarda-core 참조).
마무리
- 핵심 정리: 외부 관문은 언제나 Apache 443 하나이고, 앱 포트(8000번대)는 VPC 내부 전용입니다. prod는 Gunicorn, dev는
runserver이며, 정적은 Apache가 CDN 디렉토리에서 직접 내보냅니다./release는 dev→prod SSH로 코드·의존성·정적·서비스 재시작을 조건부로 처리합니다. - 주의 사항: 비동기 작업은 huey 소비자가 떠 있어야만 실제로 비동기로 돕니다. 소비자가 없으면 gunicorn 안에서 돌다 타임아웃으로 죽습니다.
- 다음 문서: 이 챕터로 core·template·인프라 3부작이 끝납니다. 앞의 core·template·devtools와 함께 보면 "AI 하네스가 어떻게 만들어져 클라우드에서 도는가"가 하나로 이어집니다.