블로그를 운영하다 보면 딜레마가 하나 있습니다. 꾸준히 채우고는 싶은데, 매번 글감을 찾아 직접 쓰는 건 부담입니다. 그러다 생각했습니다. "최신 트렌드를 AI가 대신 조사해서 초안을 쓰고, 검증까지 통과하면 알아서 발행하면 어떨까?"
동시에 Docker와 GCP 서버리스가 실제로 어떻게 도는지 손으로 익히고 싶었습니다. 그래서 이 프로젝트는 "블로그 자동화"이자 "배포 파이프라인 학습"이라는 두 목적을 겸했습니다.
봇은 실행될 때마다 아래 파이프라인을 한 번 돕니다.
plain스캔(Google Search 그라운딩) → 기존 글과 중복 제거 → 심층 리서치 → 6단 구조로 집필 → 문체 윤문 → 자가검증 게이트 → Notion 발행 → 알림
조사·집필·검증을 전부 Gemini 하나로 통일하되, 사실 조사는 Google Search 그라운딩으로 근거를 붙여 할루시네이션을 줄였습니다. 발행은 회당 1글, 그것도 검증을 통과할 때만 나갑니다.
안전장치는 세 겹입니다. 주제가 기존 글과 겹치면 건너뛰고, 근거가 부족하거나 형식이 어긋나면 draft로 저장한 뒤 알림만 보냅니다. 그리고 회당 1글 상한을 둡니다. 무인 발행에서 가장 무서운 건 아무도 안 보는 사이 이상한 글이 나가는 상황이라, 이 게이트가 사실상 시스템의 심장입니다.
이 프로젝트에서 제가 가장 궁금했던 부분입니다. 결론부터 말하면, 코드는 Docker 이미지로 포장돼 창고에 올라가고, 실행 주체는 그 이미지를 가리키기만 합니다.
plain[내 코드] --docker build--> [이미지] --docker push--> [Artifact Registry(창고)] ▲ 참조(포인터) [Cloud Scheduler(cron)] --주기적 트리거--> [Cloud Run Job] --실행 시 pull--> 컨테이너 실행 후 종료
gcloud run jobs update --image=...:v7처럼 포인터를 바꾸는 일이고, 롤백도 포인터만 되돌리면 됩니다.여기서 중요한 오해 하나를 짚고 싶습니다. 유지해야 할 서버가 없습니다. Cloud Run Job은 상주 서버가 아니라 배치라서, 스케줄러가 깨우면 약 2.5분 실행하고 완전히 종료합니다. 유휴 시간엔 과금도 0입니다. "컨테이너를 돌리려면 VM이나 쿠버네티스를 띄워야 한다"는 통념을 서버리스가 지운 지점입니다.
교과서대로 되진 않았습니다. 실제로 막혔던 지점만 남깁니다.
로컬(Apple Silicon)에서 빌드한 이미지를 올리자 이런 에러가 났습니다.
plainContainer manifest type 'application/vnd.oci.image.index.v1+json' must support amd64/linux.
Cloud Run은 amd64/linux 이미지를 요구하는데, macOS 기본 빌드는 arm64 기반의 멀티아치 OCI 인덱스를 만들어 그 조건에 맞지 않았습니다. 해결은 빌드 플래그 두 개였습니다.
bashdocker build --platform=linux/amd64 --provenance=false \ -f apps/trend-writer/Dockerfile -t <이미지>:v2 .
--platform=linux/amd64로 아키텍처를 맞추고, --provenance=false로 인덱스를 만드는 어테스테이션을 꺼서 단일 amd64 매니페스트로 밀어넣으니 통과했습니다.
마크다운을 Notion 블록으로 변환하는데, 분명 > … 인용문에 텍스트를 넣었는데 발행된 페이지엔 내용 없는 빈 인용 블록만 남았습니다. 확인해보니 쓰던 변환 라이브러리가 인용(>)과 raw HTML <div> 콜아웃의 내용을 변환 과정에서 날려버렸습니다. 이모지 문제가 아니라 블록 타입 문제였습니다. 그래서 상단 안내와 요약을 일반 볼드 문단으로 바꾸니 정상적으로 렌더됐습니다. "마크다운이면 다 되겠지"가 통하지 않는 지점이었습니다.
투명성을 위해 자동 발행 봇 글의 머리에는 "이 글은 AI가 작성했습니다"라는 고지를 항상 붙이도록 했습니다. 그런데 자가검증 게이트가 매번 이 문장을 보고 "AI 티가 과하다"며 draft로 떨궜습니다. 요구사항 두 개, 즉 반드시 AI 고지 / AI 티 억제가 정면충돌한 겁니다. 결국 검증 프롬프트에 "상단 고지와 하단 출처는 필수 구성요소이니 슬롭으로 간주하지 말 것"을 명시해 예외 처리하고서야 발행됐습니다.
만들면서 계속 든 의문입니다. 정직하게 정리하면 이렇습니다.
표준은 맞습니다. "컨테이너를 주기적으로 한 번씩 실행"은 GCP에선 Cloud Run Job + Cloud Scheduler가 정석입니다. AWS의 EventBridge Scheduler + Fargate, Azure의 Container Apps Jobs와 같은 계열입니다.
다만 이 정도 워크로드(주 몇 회, 자체 완결)라면 더 가벼운 선택지도 있습니다.
그런데도 저는 Cloud Run Job을 골랐습니다. 이유는 세 가지입니다. Docker와 GCP 서버리스를 직접 배우는 게 목적이었고, 봇을 블로그 웹앱과 완전히 격리하고 싶었으며, 실행 시간이나 향후 복잡도가 커져도 여유가 있었기 때문입니다. 학습과 격리라는 맥락이 없었다면, 솔직히 GitHub Actions cron이 더 간단한 정답이었을 겁니다.
AI에게 블로그를 완전히 맡기는 건 생각보다 위험하지만, 검증 게이트와 정직한 고지를 앞세우면 충분히 쓸 만한 자동화가 됩니다. 그리고 이 과정에서 얻은 진짜 수확은 봇 자체보다, "컨테이너 이미지를 창고에 올리고 서버리스 러너가 그걸 가리켜 실행한다"는 배포 모델을 손으로 이해한 것이었습니다.
// Comments