# CF Multisite Cloudflare Workers + R2 기반 멀티테넌트 정적 사이트 호스팅 플랫폼 ## 아키텍처 ``` ┌─────────────────────────────────────────────────────────────────┐ │ Gitea (gitea.anvil.it.com) │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ site-a │ │ site-b │ │ site-c │ ... │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ push │ push │ push │ └───────┼─────────────┼─────────────┼─────────────────────────────┘ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Gitea Actions Runner (jp1) │ │ aws s3 sync → R2 │ └───────────────────────────┬─────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Cloudflare R2 Bucket │ │ /sites/site-a/index.html │ │ /sites/site-b/index.html │ │ /sites/site-c/index.html │ └───────────────────────────┬─────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Cloudflare Workers │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Cache │ │ Rate Limit │ │ Admin API │ │ │ │ (Edge) │ │ (KV) │ │ (REST) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └───────────────────────────┬─────────────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Custom Domains │ │ site-a.actions.it.com site-b.actions.it.com ... │ └─────────────────────────────────────────────────────────────────┘ ``` ## 주요 기능 ### 멀티테넌트 호스팅 - 서브도메인 기반 고객 분리: `{customer}.actions.it.com` - R2에 고객별 디렉토리 구조: `/sites/{customer}/` - 자동 index.html 라우팅 ### 캐싱 전략 - Edge 캐시로 R2 요청 최소화 → 비용 절감 - 파일 타입별 TTL 최적화: | 파일 타입 | 캐시 TTL | |-----------|----------| | HTML | 1시간 | | CSS, JS, JSON | 24시간 | | 이미지 (PNG, JPG, GIF, SVG) | 7일 | | 폰트 (WOFF, WOFF2, TTF) | 30일 | ### Rate Limiting (티어별) | 티어 | 분당 요청 | 일일 대역폭 | 월간 대역폭 | |------|-----------|-------------|-------------| | Free | 60 | 5GB | ~150GB | | Basic | 300 | 50GB | ~1.5TB | | Pro | 1,000 | 500GB | ~15TB | ### 사용량 추적 (KV) - 고객별 일일 요청 수 - 고객별 일일 대역폭 - 분당 요청 수 (Rate Limit용) - 7일간 데이터 보관 ## Admin API 모든 API는 Bearer 토큰 인증 필요: ```bash curl -H "Authorization: Bearer $ADMIN_TOKEN" https://site.actions.it.com/api/... ``` ### 엔드포인트 | Method | Endpoint | 설명 | |--------|----------|------| | GET | `/api/usage/:customer?days=7` | 고객 사용량 조회 | | GET | `/api/customers` | 전체 고객 목록 | | PUT | `/api/tier/:customer` | 고객 티어 변경 | | GET | `/api/stats` | 전체 통계 | | DELETE | `/api/customer/:customer` | 고객 데이터 삭제 | ### 사용 예시 ```bash # 고객 사용량 조회 curl -H "Authorization: Bearer $TOKEN" \ "https://multisite-demo.actions.it.com/api/usage/multisite-demo?days=7" # 티어 변경 curl -X PUT \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"tier": "basic"}' \ "https://multisite-demo.actions.it.com/api/tier/customer-name" # 전체 통계 curl -H "Authorization: Bearer $TOKEN" \ "https://multisite-demo.actions.it.com/api/stats" ``` ## 설치 및 배포 ### 1. 프로젝트 클론 ```bash cd ~/projects git clone cf-multisite cd cf-multisite npm install ``` ### 2. Cloudflare 설정 ```bash # wrangler 로그인 npx wrangler login # KV 네임스페이스 생성 npx wrangler kv:namespace create USAGE # R2 버킷 생성 (이미 있으면 스킵) npx wrangler r2 bucket create multisite-bucket ``` ### 3. wrangler.toml 설정 ```toml name = "cf-multisite" main = "src/worker.js" compatibility_date = "2024-12-01" [[r2_buckets]] binding = "BUCKET" bucket_name = "multisite-bucket" [[kv_namespaces]] binding = "USAGE" id = "" routes = [ { pattern = "*.actions.it.com", zone_name = "actions.it.com" } ] ``` ### 4. 시크릿 설정 ```bash # Admin API 토큰 생성 및 설정 openssl rand -hex 32 # 토큰 생성 npx wrangler secret put ADMIN_TOKEN ``` ### 5. 배포 ```bash npm run deploy # 또는 npx wrangler deploy ``` ## 고객 사이트 추가 ### 1. Gitea 저장소 생성 고객용 저장소를 Gitea에 생성합니다. ### 2. Workflow 파일 추가 `.gitea/workflows/deploy.yml`: ```yaml name: Deploy to CF Multisite on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install AWS CLI run: | curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install - name: Deploy to R2 env: AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_KEY }} R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} run: | SITE_ID="${{ github.event.repository.name }}" aws s3 sync . "s3://multisite-bucket/sites/${SITE_ID}/" \ --endpoint-url "${R2_ENDPOINT}" \ --region auto \ --exclude ".git/*" \ --exclude ".gitea/*" \ --exclude "README.md" \ --delete echo "Deployed to: https://${SITE_ID}.actions.it.com" ``` ### 3. Secrets 설정 (Gitea Organization) | Secret | 값 | |--------|-----| | `R2_ACCESS_KEY` | R2 API 토큰 Access Key | | `R2_SECRET_KEY` | R2 API 토큰 Secret Key | | `R2_ENDPOINT` | `https://.r2.cloudflarestorage.com` | ### 4. Push → 자동 배포 ```bash git add . git commit -m "Initial site" git push origin main # → https://{repo-name}.actions.it.com 에서 확인 ``` ## 로컬 개발 ```bash # 개발 서버 실행 npm run dev # 테스트 URL # http://localhost:8787?site=demo # http://localhost:8787?site=multisite-demo ``` ## 파일 구조 ``` cf-multisite/ ├── src/ │ └── worker.js # Workers 메인 코드 │ # - 라우팅 │ # - 캐싱 │ # - Rate Limiting │ # - Admin API ├── scripts/ │ └── upload.js # 수동 R2 업로드 스크립트 ├── sample-site/ # 샘플 사이트 │ ├── index.html │ ├── about.html │ ├── contact.html │ └── style.css ├── .gitea/ │ └── workflows/ │ └── deploy.yml # Gitea Actions 템플릿 ├── wrangler.toml # Workers 설정 ├── package.json └── README.md ``` ## 인프라 구성 | 컴포넌트 | 위치 | 용도 | |----------|------|------| | Gitea | gitea.anvil.it.com | Git 호스팅 | | Gitea Runner | jp1 (Incus) | CI/CD 실행 | | R2 Bucket | multisite-bucket | 정적 파일 저장 | | KV Namespace | USAGE | 사용량 추적 | | Workers | cf-multisite | 라우팅/캐싱/API | | Domain | *.actions.it.com | 와일드카드 도메인 | ## 비용 구조 ### Cloudflare 무료 티어 | 항목 | 무료 한도 | |------|-----------| | Workers 요청 | 일 10만 건 | | R2 저장 | 10GB | | R2 Class A (쓰기) | 월 100만 건 | | R2 Class B (읽기) | 월 1000만 건 | | KV 읽기 | 일 10만 건 | | KV 쓰기 | 일 1000건 | ### 예상 비용 (1000 고객 기준) ``` 저장: 1000 × 50MB = 50GB → 초과 40GB × $0.015 = $0.60/월 읽기: 캐시 히트율 90% 가정 → 대부분 무료 Workers: 캐시 히트 시에도 실행됨 → 유료 플랜 권장 ($5/월) ``` ## 모니터링 ### 응답 헤더 | 헤더 | 설명 | |------|------| | `X-Cache: HIT/MISS` | 캐시 상태 | | `X-Customer` | 고객 ID | | `X-Tier` | 고객 티어 | | `X-RateLimit-Reason` | Rate Limit 사유 (rpm/bandwidth) | ### 사용량 확인 ```bash # 특정 고객 curl -H "Authorization: Bearer $TOKEN" \ "https://any.actions.it.com/api/usage/customer-name" # 전체 현황 curl -H "Authorization: Bearer $TOKEN" \ "https://any.actions.it.com/api/stats" ``` ## 확장 계획 ### 클러스터링 옵션 | 방식 | 장점 | 적합한 규모 | |------|------|-------------| | Incus 단일 노드 | 간단, 저비용 | 현재 | | Incus 클러스터 (jp1+kr1) | HA, 마이그레이션 | 중규모 | | Kubernetes | 자동 스케일링 | 대규모 | ### 결제 연동 (검토 중) | PG | 대상 | 비고 | |----|------|------| | Toss Payments | 국내 고객 | 보증보험 필요 | | Stripe (일본 법인) | 해외/텔레그램 | 직접 연동 가능 | ## 크레덴셜 (Vault) ``` Vault: https://vault.anvil.it.com Path: secret/cf-multisite - admin_token: Admin API 인증 토큰 - r2_access_key: R2 API Access Key - r2_secret_key: R2 API Secret Key ``` ## 관련 링크 - Cloudflare Dashboard: https://dash.cloudflare.com - Gitea: https://gitea.anvil.it.com - 샘플 사이트: https://multisite-demo.actions.it.com