AI가 매일/매주 콘텐츠를 자동 생성하는 서버리스 정적 포털
이 문서는 이 사이트(moontaeyang.com) 자체를 하나의 Azure 데모로 보고, 어떤 서비스로 어떻게 구성했는지, 왜 그렇게 설계했는지를 직접 구축하려는 엔지니어 관점에서 설명합니다.
moontaeyang.com은 매일/매주 AI가 콘텐츠를 자동 생성해 올리는 개인 학습 포털입니다. 정적 웹 호스팅 한 개(Static Web Apps) 위에, 두 개의 예약 실행 AI 에이전트(Container Apps Job)가 콘텐츠를 만들어 붙입니다. 핵심 키워드는 서버리스 · 키리스(시크릿 없음) · 스케줄 기반 · 정적 우선입니다.
$web 누적 저장). ┌──────────────────────────────────────┐
사용자(브라우저) ──HTTPS──▶ │ Azure Static Web Apps (Standard) │
moontaeyang.com │ - 글로벌 CDN + 무료 TLS 인증서 │
│ - 커스텀 도메인(moontaeyang.com, www)│
소셜 로그인 ──/.auth/*──▶ │ - 내장 인증(Google·GitHub·Kakao OIDC)│
│ - 정적 파일 서빙 + SPA fallback │
└───────────────▲──────────────────────┘
│ swa deploy(전체 포털 재조립)
┌────────────────────────────────┴────────────────────────────────┐
│ │
┌─────────────┴─────────────┐ ┌─────────────────┴───────────────┐
│ Container Apps Job │ 매일 06:00 KST │ Container Apps Job │ 매주 월 06:00 KST
│ engitdaily-job │ (cron 0 21 * * *) │ azwhatsnew-job │ (cron 0 21 * * 0)
│ 1) HN/RSS 수집 │ │ 1) Azure 업데이트 RSS 수집 │
│ 2) Azure OpenAI 요약/생성 │◀── Managed Identity(키리스) ──▶ │ 2) Azure OpenAI 요약 │
│ 3) Storage $web 에 누적 │ │ 3) Storage $web 에 누적 │
│ 4) 포털 전체 재조립→배포 │ │ 4) 포털 전체 재조립→배포 │
│ 5) Telegram 알림 │ │ 5) Telegram 알림 │
└─────────────┬─────────────┘ └─────────────────┬───────────────┘
│ │
┌─────────────▼─────────────┐ ┌─────────────────▼───────────────┐
│ Storage(engit) $web │ 과거 일자 페이지 누적 │ Storage(azwn) $web │ 과거 주차 페이지 누적
│ engitdailyst… │ (영구 "창고") │ azwhatsnewst… │ (영구 "창고")
└────────────────────────────┘ └─────────────────────────────────┘
공통: 각 에이전트는 전용 ACR(이미지) · User-Assigned Managed Identity(UAMI) · Container Apps Environment 보유.
정적 섹션(/labs, /demos)은 저장소 HTML → 포털 셸에 포함되어 매 배포 시 함께 게시.
| 서비스 | 역할 | 비고 |
|---|---|---|
| Azure Static Web Apps (Standard) | 공개 웹 서빙, 커스텀 도메인, TLS, 소셜 로그인 | 커스텀 인증·OIDC는 Standard 필수 |
| Azure Container Apps Job | 예약(cron) 배치로 콘텐츠 생성 | 평소 0개 인스턴스 → 실행 시에만 과금(서버리스) |
| Azure OpenAI (gpt-5.4) | 기사/요약/단어 생성 | 키리스: Managed Identity + RBAC로 호출 |
Azure Storage(Blob, $web) |
생성된 페이지 누적 보관(창고) + md 이력 | 정적 웹사이트 컨테이너 $web 활용 |
| Azure Container Registry(ACR) | 에이전트 컨테이너 이미지 저장 | az acr build로 클라우드 빌드 |
| User-Assigned Managed Identity | ACR pull · Storage 읽기/쓰기 · OpenAI 호출 권한 | 시크릿 0개 운영의 핵심 |
| Telegram Bot API(외부) | 게시 완료 알림 | 토큰은 Job 환경변수(시크릿)로만 |
Microsoft Entra ID(앱 등록)는 소셜 로그인 중 GitHub/Google/Kakao OAuth 앱과 SWA 인증 연동에 사용됩니다.
customOpenIdConnectProviders(OIDC discovery)로 연결./.auth/* 엔드포인트가 마운트됩니다. 없으면 통째로 404.swa deploy는 사이트 전체를 통째로 교체합니다. "추가(append)" 개념이 없습니다.$web 을 영구 누적 창고로 쓰고, 배포할 때마다
두 창고 + 포털 셸 + 정적 섹션을 전부 다시 조립해 한 번에 swa deploy 합니다.
→ 누가 배포하든 항상 "전체 포털"이 올라가 서로 덮어쓰지 않습니다.PORTAL_SECTIONS=engit-daily=<$web>;azure-daily=<$web> 로 전달합니다./engit-daily, /azure-daily): 에이전트가 생성 → Storage $web 누적 → 배포 시 미러링./labs, /demos): 저장소의 HTML을 포털 셸에 포함 → 매 배포에 그대로 따라감(에이전트·Storage 불필요).Cognitive Services OpenAI User,
Storage Blob Data Contributor(자기 스토리지), Storage Blob Data Reader(상대 스토리지 미러용), AcrPull.DefaultAzureCredential() 하나로 인증 → 저장소에 시크릿이 남지 않음.0 21 * * * · 주간(월): 0 21 * * 0(일요일 21:00 UTC = 월 06:00 KST).publicNetworkAccess=Disabled 상태에서 막힙니다 → 본 데모는 RBAC로 보호하되 공개 접근은 켜 두는 방식 사용
(정책으로 강제 차단되는 환경이라면 Private Endpoint + VNet 통합이 정석. Front Door는 인바운드 가속이라 이 용도엔 부적합).(A) 사용자 요청 흐름
1. 브라우저가 moontaeyang.com 요청 → SWA가 글로벌 CDN에서 정적 파일 응답(TLS 자동).
2. 로그인 클릭 → /.auth/login/<provider> → 공급자(Google/Kakao/GitHub) 인증 → 콜백 → 세션 쿠키.
3. SPA fallback: 매칭 안 되는 경로는 셸 index.html로(단, /engit-daily/*,/azure-daily/*,/labs/*,/demos/*,/.auth/*는 제외).
(B) 콘텐츠 생성 흐름(에이전트, 하루/주 1회)
1. cron이 Job 실행 → 컨테이너 시작(이미지는 ACR에서 pull).
2. 소스 수집(Hacker News API / Azure 업데이트 RSS).
3. Azure OpenAI(키리스) 로 기사·요약·단어 생성.
4. 결과를 Storage $web 에 누적(오늘/이번주 페이지 + 섹션 index 갱신), md 이력은 Blob에 보관.
5. 포털 재조립: 셸(+정적 섹션 labs/demos) 복사 + 두 동적 섹션을 각 $web에서 미러링 → swa deploy.
6. Telegram으로 "게시 완료 + 링크" 알림.
가격은 변동되므로 실제 청구는 Azure Pricing/Cost Management로 확인하세요(여기 수치는 안내용).
moontaeyang.com, www) 연결 + DNS(A/ALIAS, TXT 검증).staticwebapp.config.json에 auth 블록(+Kakao는 customOIDC) → client-id/secret을 SWA 앱 설정에 주입(없으면 /.auth/* 404).$web 정적 웹 활성화) + Azure OpenAI(배포 gpt-5.4).AcrPull · Cognitive Services OpenAI User · 자기 Storage Blob Data Contributor · 상대 Storage Blob Data Reader(미러).az acr build로 이미지 → Job 업데이트(환경변수: PORTAL_SECTIONS, PORTAL_BASE_URL, SWA_DEPLOY_TOKEN(시크릿) 등).swa deploy./labs, /demos는 마크다운→HTML로 빌드해 포털 셸에 포함./.auth/*가 404 → 공급자 앱 설정(client-id/secret)이 없어서. 설정 후 마운트됨.swa deploy는 전체 교체. 매번 전체 포털을 재조립해야 함(PORTAL_SECTIONS 미러).AuthorizationFailure → Storage publicNetworkAccess=Disabled. 정책 강제면 Private Endpoint, 아니면 공개 접근 Enabled + RBAC.