Cloudflare Workers 무료 플랜으로 서버리스 API 배포하기


AWS Lambda 프리티어 만료되던 날, Cloudflare Workers를 켰다

AWS Lambda로 간단한 API 엔드포인트를 하나 운영했다. 1년짜리 프리티어가 만료되는 날 청구서에 7달러가 찍혔다. 고작 API 하나에 매달 7달러. 서버 없이 함수 단위로 코드를 실행하는 서버리스 구조는 좋았는데, 비용 구조가 마음에 걸렸다.

그때 발견한 게 Cloudflare Workers다. 하루 10만 요청, 하루 CPU 시간 10ms 이하 기준으로 영구 무료다. 프리티어 만료 같은 함정도 없다. 전 세계 300개 이상의 엣지 서버에서 실행되니 응답 속도도 Lambda보다 오히려 빠르다. 계정 생성부터 실제 API 배포까지 밟은 전 과정, 지금 꺼낸다.



1. Cloudflare Workers 무료 플랜 조건과 동작 원리

Cloudflare Workers의 무료 플랜은 하루 100,000건의 요청Worker당 10ms CPU 시간을 제공한다. 월 기준으로 환산하면 약 300만 건의 요청이 무료다. 개인 프로젝트나 사이드 서비스 수준에서는 한도에 걸릴 일이 거의 없다. AWS처럼 1년 후 만료되는 프리티어가 아니라 영구 무료 플랜이라는 점이 결정적으로 다르다.

동작 방식은 일반 서버와 다르다. Workers는 Node.js나 파이썬 서버를 띄우는 게 아니라, Cloudflare의 전 세계 엣지 네트워크에서 V8 엔진 위에서 JavaScript를 직접 실행한다. 사용자가 API를 호출하면 가장 가까운 Cloudflare 데이터센터에서 코드가 즉시 실행되므로 콜드 스타트 지연이 Lambda보다 훨씬 짧다. 서울 기준 평균 응답 시간이 30ms 이하로, Lambda의 100~200ms와 체감 차이가 크다.

2. 개발 환경 세팅: Wrangler CLI 설치

사전 준비

Workers 개발에는 JavaScript(또는 TypeScript)를 쓴다. Node.js 18 이상이 필요하다. cloudflare.com에서 무료 계정을 먼저 만들어 둔다. 신용카드 없이 가입할 수 있다.

Wrangler CLI 설치 및 로그인

Wrangler는 Cloudflare Workers의 공식 CLI 도구다. 로컬 개발, 테스트, 배포를 전담한다. 설치 후 로그인 명령어를 실행하면 브라우저가 열리며 Cloudflare 계정과 연동된다.

npm install -g wrangler
wrangler login

새 Workers 프로젝트 생성

npm create cloudflare@latest my-api-worker
# 설정 선택:
# "Hello World" Worker 선택
# JavaScript 선택
# Git 초기화: Yes
cd my-api-worker

생성된 프로젝트 폴더에는 src/index.jswrangler.toml 두 파일이 핵심이다. index.js에 API 로직을 작성하고, wrangler.toml에 프로젝트 설정을 담는다. 로컬에서 바로 테스트하려면 wrangler dev를 실행하면 http://localhost:8787에 개발 서버가 뜬다.

3. 실전 API 작성 및 배포: Supabase 연동 JSON API 완성

1단계: 환경변수 등록 (Supabase 키)

Supabase URL과 API 키를 Workers의 환경변수로 등록한다. .env 파일 대신 Wrangler CLI로 직접 등록해야 배포 후에도 안전하게 주입된다.

wrangler secret put SUPABASE_URL
# 프롬프트에 Supabase URL 입력

wrangler secret put SUPABASE_KEY
# 프롬프트에 Supabase anon 키 입력

2단계: 라우팅 기반 JSON API 완성 코드

이전 글에서 만든 Supabase articles 테이블에서 데이터를 읽어 JSON으로 반환하는 API다. /articles 엔드포인트는 최근 기사 목록을, /articles?sentiment=긍정은 필터된 결과를 돌려준다. CORS 헤더도 함께 설정해 브라우저에서 직접 호출할 수 있다.

// src/index.js

const CORS_HEADERS = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type",
  "Content-Type": "application/json;charset=UTF-8",
};

async function fetchArticles(env, sentiment = null) {
  let url = `${env.SUPABASE_URL}/rest/v1/articles?select=id,title,summary,sentiment,created_at&order=created_at.desc&limit=20`;
  if (sentiment) url += `&sentiment=eq.${encodeURIComponent(sentiment)}`;

  const response = await fetch(url, {
    headers: {
      "apikey": env.SUPABASE_KEY,
      "Authorization": `Bearer ${env.SUPABASE_KEY}`,
    },
  });

  if (!response.ok) {
    throw new Error(`Supabase 오류: ${response.status}`);
  }
  return response.json();
}

export default {
  async fetch(request, env) {
    const { pathname, searchParams } = new URL(request.url);

    // CORS preflight 처리
    if (request.method === "OPTIONS") {
      return new Response(null, { headers: CORS_HEADERS });
    }

    try {
      // GET /articles
      if (pathname === "/articles" && request.method === "GET") {
        const sentiment = searchParams.get("sentiment");
        const data = await fetchArticles(env, sentiment);
        return new Response(JSON.stringify({ success: true, count: data.length, data }), {
          headers: CORS_HEADERS,
        });
      }

      // GET /health
      if (pathname === "/health") {
        return new Response(JSON.stringify({ status: "ok", timestamp: new Date().toISOString() }), {
          headers: CORS_HEADERS,
        });
      }

      // 404
      return new Response(JSON.stringify({ error: "Not Found" }), {
        status: 404,
        headers: CORS_HEADERS,
      });

    } catch (err) {
      return new Response(JSON.stringify({ error: err.message }), {
        status: 500,
        headers: CORS_HEADERS,
      });
    }
  },
};

3단계: 배포 한 줄

wrangler deploy

명령어를 실행하면 10초 안에 배포가 완료되고 https://my-api-worker.계정명.workers.dev 형태의 URL이 발급된다. 커스텀 도메인도 Cloudflare DNS에서 무료로 연결할 수 있다. 이제 어느 기기에서든 이 URL로 Supabase 데이터를 JSON으로 받아볼 수 있다.

4. 직접 맞은 에러 2가지와 해결법

에러 ① CPU Time Exceeded — 10ms 제한 초과

무료 플랜은 Worker당 CPU 실행 시간이 10ms로 제한된다. 처음에 Supabase 응답을 받아 가공하는 로직을 Worker 안에서 처리했더니 간헐적으로 Worker exceeded CPU time limit 에러가 났다. 핵심 원인은 불필요한 반복 연산이었다. 데이터 가공 로직을 최소화하고 Supabase에서 이미 정렬·필터된 데이터를 바로 반환하는 구조로 바꾸자 CPU 시간이 2ms 이하로 줄었다. Worker는 얇은 API 게이트웨이 역할만 맡기고 무거운 연산은 DB 단에서 처리하는 것이 원칙이다.

에러 ② 로컬에서는 되는데 배포 후 환경변수가 undefined

로컬 wrangler dev에서는 잘 돌아가다가 배포 후 API 호출 시 Supabase 연결이 실패했다. env.SUPABASE_KEYundefined로 찍혔다. wrangler secret put으로 등록한 시크릿 값은 Worker 재배포 없이도 즉시 반영되어야 하는데, 로그인 세션이 만료된 상태에서 등록한 시크릿이 제대로 저장되지 않은 것이었다. wrangler login으로 재인증한 뒤 시크릿을 다시 등록하고 wrangler deploy를 한 번 더 실행하자 정상 동작했다.

5. AWS Lambda vs Cloudflare Workers 실전 비교

두 플랫폼을 직접 써보고 정리한 기준이다. 용도가 다르기 때문에 무조건 어느 쪽이 낫다고 말하기는 어렵다.

항목 AWS Lambda Cloudflare Workers
무료 한도 월 100만 건 (1년 한시) 하루 10만 건 (영구 무료)
콜드 스타트 100~500ms 0~5ms (V8 Isolate)
실행 환경 Node.js, Python, Go 등 다양 JavaScript / TypeScript 전용
실행 시간 제한 최대 15분 CPU 10ms (무료), 30초 (유료)
적합한 용도 무거운 배치 처리, 파이썬 스크립트 가벼운 API 게이트웨이, 엣지 라우팅

파이썬 기반 무거운 배치 처리나 긴 실행 시간이 필요하면 Lambda가 낫다. 반면 단순 JSON API 응답, 인증 처리, 리다이렉트 같은 가벼운 엣지 로직에는 Workers가 압도적으로 빠르고 저렴하다. 이 시리즈에서 만든 자동화 파이프라인의 최종 출구 — 데이터를 외부에 노출하는 API 레이어로는 Workers가 지금까지 찾은 가장 깔끔한 선택이다.