티스토리 뷰
쿠버네티스 실습: SaaS 청구·과금(Billing) 시스템 구축
테넌트별 API 사용량 → 요금 항목 변환 → Stripe 자동 결제 파이프라인
앞선 글에서는 테넌트별 Rate Limit & Quota Enforcement를 구축해
SaaS 서비스의 핵심 기능인 “사용량 통제”를 안정적으로 완성했습니다.
이제 실제 SaaS 비즈니스의 필수 마지막 레이어,
Billing(과금) & Subscription(정기 구독) 시스템을 구축합니다.
목표는 다음과 같습니다.
“테넌트별 API 사용량을 정확하게 계산하여 요금 항목으로 변환하고,
Stripe와 연동해 자동 청구·결제·영수증 발행까지 구현한다.”
실제 SaaS 서비스가 운영되는 방식 그대로 따라가는 실습입니다.
1. 전체 아키텍처
[Istio Gateway logs / Nest API logs / Redis Stream (API usage)]
│
▼
[Aggregator Worker (NestJS or Go)] ← 5분/1시간 단위 집계
│
▼
[Billing DB (PostgreSQL + Prisma)] ← 테넌트별 사용량 저장
│
▼
[Billing Engine] ← 요율(Price Plan) 적용
│
▼
[Stripe] ← 결제/구독/영수증 발행/세금
Billing의 핵심은 3단계입니다:
1) Usage 수집
2) Usage → 요금 계산
3) Stripe로 자동 청구
2. 테넌트별 Price Plan 구조 설계
Prisma 기반 Billing 스키마:
model Tenant {
id String @id @default(uuid())
name String
stripeCustomerId String?
plans Plan[]
usages Usage[]
invoices Invoice[]
}
model Plan {
id String @id @default(uuid())
tenantId String
type String // basic, pro, enterprise
pricePerRequest Float // 예: 0.001 USD
monthlyBaseFee Float // 기본요금 예: 29 USD
freeTierRequests Int // 예: 10,000 free
Tenant Tenant @relation(fields: [tenantId], references: [id])
}
model Usage {
id String @id @default(uuid())
tenantId String
timestamp DateTime @default(now())
count Int
}
model Invoice {
id String @id @default(uuid())
tenantId String
periodStart DateTime
periodEnd DateTime
amount Float
stripeInvoiceId String?
}
이 스키마는 SaaS 시장에서 흔히 쓰는 구조(Segment, Stripe Metered Billing)를 그대로 차용했습니다.
3. API Usage 집계 (Redis Stream → Batch Aggregator)
Redis Stream에서 사용량을 1분 혹은 5분 단위로 집계합니다.
usage_worker.ts
import Redis from "ioredis";
import { PrismaClient } from '@prisma/client';
const redis = new Redis(process.env.REDIS_URL);
const prisma = new PrismaClient();
async function aggregate() {
const records = await redis.xrange("api_stream", "-", "+");
const usageMap = new Map<string, number>();
for (const [id, fields] of records) {
const tenant = fields[1];
usageMap.set(tenant, (usageMap.get(tenant) || 0) + 1);
}
for (const [tenant, count] of usageMap.entries()) {
await prisma.usage.create({
data: {
tenantId: tenant,
count,
},
});
}
// Clear stream
await redis.xtrim("api_stream", "MAXLEN", 0);
}
aggregate().finally(() => process.exit());
이 워커는 CronJob 또는 KEDA ScaledJob으로 실행합니다.
4. Price Plan을 반영한 요금 계산 엔진
핵심 로직
청구금액 = 기본요금 + max(0, (총요청수 - 무료요청수)) × 단가
billing_engine.ts
export async function calculateInvoice(tenantId: string, start: Date, end: Date) {
const plan = await prisma.plan.findFirst({ where: { tenantId } });
const usages = await prisma.usage.findMany({
where: { tenantId, timestamp: { gte: start, lt: end } }
});
const total = usages.reduce((sum, u) => sum + u.count, 0);
const billable = Math.max(0, total - plan.freeTierRequests);
const amount = plan.monthlyBaseFee + (billable * plan.pricePerRequest);
return { total, billable, amount };
}
5. Stripe 연동: Customer 생성
테넌트 생성 시 Stripe Customer를 자동으로 만듭니다.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET, { apiVersion: "2023-10-16" });
async function createCustomer(tenantId: string, email: string) {
const customer = await stripe.customers.create({
email,
metadata: { tenantId }
});
await prisma.tenant.update({
where: { id: tenantId },
data: { stripeCustomerId: customer.id }
});
return customer.id;
}
6. Stripe Invoice 생성
async function createStripeInvoice(tenantId: string, amount: number) {
const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }});
if (!tenant?.stripeCustomerId) throw new Error("No customer ID");
const invoiceItem = await stripe.invoiceItems.create({
customer: tenant.stripeCustomerId,
amount: Math.round(amount * 100), // USD → 센트
currency: "usd",
description: `API usage billing`,
});
const invoice = await stripe.invoices.create({
customer: tenant.stripeCustomerId,
auto_advance: true, // 자동 청구
});
return invoice.id;
}
7. 매월 자동 청구 CronJob (Kubernetes)
apiVersion: batch/v1
kind: CronJob
metadata:
name: monthly-billing
namespace: billing
spec:
schedule: "0 0 1 * *" # 매월 1일 00시
jobTemplate:
spec:
template:
spec:
containers:
- name: billing-worker
image: ghcr.io/your/billing-engine:latest
env:
- name: STRIPE_SECRET
valueFrom:
secretKeyRef:
name: stripe-secret
key: key
restartPolicy: OnFailure
실제로 Stripe는 매월 자동 인보이스 발송, 영수증 발행, 재시도까지 처리합니다.
8. 테넌트 포털에서 “청구내역” 조회 API
billing.controller.ts
@Get('invoices')
async getInvoices(@Req() req) {
const tenantId = req.tenantId;
return prisma.invoice.findMany({
where: { tenantId },
orderBy: { periodStart: "desc" }
});
}
9. 단가에 따른 다양한 요금 모델 구현
SaaS에서 흔히 쓰는 과금 모델도 쉽게 추가 가능:
모델 설명
| Tiered | 10k까지 0.0001 USD, 이후 0.00005 USD |
| Volume | 전체 사용량에 따라 단가 자동 변경 |
| Flat + Metered | 기본 구독료 + 사용량 |
| Premium Endpoint 요율 | 특정 API만 가중치 부여 |
예시: Premium API 단가
if (endpoint === "/v1/ocr") {
billable += 3; // 3배 가격
}
10. Fraud / Abuse 방어
Stripe + RateLimit + Quota + Fraud Detection을 결합하면 완전한 상업용 인프라:
- stolen API key → 즉시 사용량 급증 → RateLimit + Loki Alert
- 지나친 요청 폭주 → AIOps 예측 모델로 early throttle
- 고액 청구 → Stripe Radar로 사기 방지
11. 운영 시나리오
상황 동작
| API 과다 사용 | RateLimit → Quota → Stripe 청구 증가 |
| 월말 사용량 집계 | CronJob이 Invoice 생성 |
| 결제 실패 | Stripe 재시도 + failover Webhook |
| 테넌트 구독 변경 | Price Plan 업데이트 → 다음 달부터 반영 |
| 고액 API 폭주 | AIOps 기반 예측 차단 |
12. 정리
이번 글에서 구축한 Billing 시스템은 실제 SaaS 기업이 사용하는 방식 그대로입니다.
- 테넌트별 API 사용량 수집
- Redis Stream → Worker 집계
- Prisma 기반 Usage/Invoice 저장
- Price Plan에 따라 요금 계산
- Stripe Customer/Invoice/Payment 자동 처리
- 월별 CronJob 실행
- Fraud 방어, Premium API 지원, 대량 API 구독 모델까지 확장
그리고 이 아키텍처는 앞에서 구축한
RateLimit, Multi-Tenant, AIOps, Observability, Zero Trust 보안 체계와
유기적으로 결합됩니다.
다음 글 예고
다음 편에서는 이 SaaS 시스템에 “관리 콘솔(Admin Console)” 을 구축합니다.
관리자가 GUI로
- 테넌트 생성
- Price Plan 설정
- API Key 발급/취소
- 사용량 그래프
- 청구 내역
- 보안 정책/RateLimit 정책 조정
까지 모두 관리할 수 있는 SaaS 운영 대시보드(NestJS + NextJS + Grafana API) 를 만듭니다.
쿠버네티스,SaaS,Billing,Stripe,Prisma,RedisStream,UsageBasedBilling,Subscription,RateLimit,K8s실습
✅ 참고할 최신 자료
- Stripe 공식 가이드: SaaS 과금 모델(구독, 사용량 기반 등)을 설명해 있으며, “요금 모델 선택”, “자동 청구”, “안정적 수익 구조” 측면을 잘 정리하고 있습니다. (Stripe)
- Stripe 블로그: SaaS 지불 처리에서 직면하는 문제 및 해결책을 설명한 글 (예: 반복 결제, 실패 대응, 지역별 과금) (Stripe)
- Stripe 활용 사례: 사용 기반 요금 모델(usage-based billing)에서의 설계와 유의사항에 대해 정리되어 있습니다. (iteratorshq.com)
⚠️ 보완 / 강화 제안
아래 항목들을 글에 추가하시면 독자가 실무 구현 시 놓치기 쉬운 부분까지 대비할 수 있어요.
- 요금 모델 선택 및 구조화
글에서 기본요금 + 사용량 단가 구조를 예시로 들었는데, 추가로 “Tiered Pricing”, “Volume Pricing”, “Add-on 기반 요금” 등 실제 SaaS에서 자주 쓰이는 모델을 간단히 정리하면 좋습니다. 참고자료에서도 이 부분이 강조되어 있어요. (iteratorshq.com) - 청구 처리 및 실패 대응 흐름
예: 결제 실패 시 재시도, 카드 유효기간 만료, 구독 해지 시의 처리, 영수증 발행과 세금 계산. Stripe 관련 자료에서 이 부분이 중요하게 다뤄집니다. (Stripe) - 사용량 집계 정확성 및 지연 고려
API 사용량을 Redis Stream → Worker로 집계하는 구조인데, 실제 운영에서는 이벤트 누락, 중복 집계, 지연(latency) 등이 문제가 됩니다. 이 부분을 “데이터 청소(Cleaning) + 중복 제거 + 타임존 고려” 등으로 보완하면 좋습니다. - 투명성 있는 과금 제공
고객(테넌트)에게 과금 내역을 명확히 보여주는 것도 중요합니다 — “사용량 단위(예: API 호출 수) vs 청구 금액” 비교, 무료 티어, 할인 적용 등. Stripe 자료에서도 “사용 단위 정의(clear usage unit)”가 강조되어 있어요. (iteratorshq.com) - 보안/컴플라이언스 측면
결제정보, 고객 정보가 포함되므로 PCI-DSS, 지역별 세금(예: 부가가치세 VAT) 등 준수사항을 간단히 언급하면 신뢰도가 올라갑니다. - 테넌트별 과금 자동화 및 확장 고려사항
다수의 테넌트를 운용할 때 “사용량 증가 → 요금 상승”이 자동으로 반영되도록 설계해야 하며, 테넌트별 과금 이력 저장, 과금 리포트 제공, 테넌트별 요금 정책 변경(업그레이드/다운그레이드) 대응 흐름이 들어가면 더 좋습니다.
'project > 맥미니로 시작하는 쿠버네티스' 카테고리의 다른 글
| 쿠버네티스 실습: 완전 자동화된 Multi-Tenant Provisioning Pipeline 구축 (0) | 2025.11.25 |
|---|---|
| 쿠버네티스 실습: SaaS 운영관리(Admin Console) 구축 (0) | 2025.11.21 |
| 쿠버네티스 실습: 테넌트별 Rate Limiting & Quota Enforcement (0) | 2025.11.17 |
| 쿠버네티스 실습: 테넌트별 실시간 관측(Observability) 대시보드 구축 (0) | 2025.11.13 |
| 쿠버네티스 실습: SaaS형 API 플랫폼 구축 — 테넌트별 인증, OIDC, Redis, Gateway 통합 (0) | 2025.11.12 |
- Total
- Today
- Yesterday
- Prisma
- JWT
- 딥러닝
- 백엔드개발
- llm
- flax
- Python
- REACT
- LangChain
- node.js
- ai철학
- PostgreSQL
- nextJS
- Express
- seo 최적화 10개
- rag
- kotlin
- JAX
- SEO최적화
- Docker
- 웹개발
- 생성형AI
- NestJS
- 쿠버네티스
- Redis
- Next.js
- fastapi
- 개발블로그
- CI/CD
- DevOps
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
