devops2025-02-28·8 min·166/348

Vercel Cron Jobs 설정

Vercel에서 Cron Jobs를 설정하여 정기적인 작업을 자동화하는 방법을 알아봅니다.

Vercel Cron Jobs 설정

Introduction

Cron Jobs는 정기적으로 실행되는 작업을 자동화하는 데 유용합니다. Vercel에서 Cron Jobs를 설정하고 관리하는 방법을 알아보겠습니다.

Environment

  • Vercel 프로젝트 (Next.js 14+)
  • Vercel Pro 플랜
  • Node.js 18+
  • 외부 API 통합

Problem

정기적인 작업을 수동으로 실행해야 하는 문제:

# 매일 아침 데이터베이스 백업
# 매주 리포트 생성
# 매월 사용량 분석

# 수동으로 실행하면 실수할 수 있음

Analysis

Vercel Cron Jobs의 특징:

  1. 무료 플랜 제한: Hobby 플랜은 Cron Jobs 지원 안함
  2. 최소 1분 간격: 1분 미만 간격은 지원 안함
  3. 시간대: UTC 기준
  4. 실행 시간 제한: 함수 실행 시간 제한 적용

Solution

1. vercel.json Cron 설정

{
  "crons": [
    {
      "path": "/api/cron/daily-backup",
      "schedule": "0 2 * * *"
    },
    {
      "path": "/api/cron/weekly-report",
      "schedule": "0 9 * * 1"
    },
    {
      "path": "/api/cron/monthly-cleanup",
      "schedule": "0 0 1 * *"
    }
  ]
}

2. Cron 함수 구현

// app/api/cron/daily-backup/route.js
import { NextResponse } from 'next/server';

export async function GET(request) {
  // 시크릿 키 검증
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  try {
    // 백업 작업 실행
    await performBackup();
    
    return NextResponse.json({
      success: true,
      timestamp: new Date().toISOString(),
    });
  } catch (error) {
    console.error('Backup failed:', error);
    return NextResponse.json(
      { error: 'Backup failed' },
      { status: 500 }
    );
  }
}

async function performBackup() {
  // 데이터베이스 백업 로직
  console.log('Starting backup...');
}

3. 스케줄 표현식

// cron-schedules.js
export const schedules = {
  // 매분 실행
  everyMinute: '* * * * *',
  
  // 매시간 실행
  everyHour: '0 * * * *',
  
  // 매일 아침 9시
  dailyAt9AM: '0 9 * * *',
  
  // 매주 월요일
  weeklyMonday: '0 9 * * 1',
  
  // 매월 1일
  monthlyFirst: '0 0 1 * *',
  
  // 평일 매일
  weekdays: '0 9 * * 1-5',
  
  // 주말
  weekends: '0 10 * * 0,6',
};

4. Cron 작업 모니터링

// lib/cron-monitor.js
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

export async function logCronExecution(jobName, status, duration) {
  const log = {
    jobName,
    status,
    duration,
    timestamp: Date.now(),
  };

  await redis.lpush('cron-logs', JSON.stringify(log));
  await redis.ltrim('cron-logs', 0, 999);
  
  return log;
}

export async function getCronStats() {
  const logs = await redis.lrange('cron-logs', 0, 49);
  const parsed = logs.map(log => JSON.parse(log));
  
  const stats = {
    total: parsed.length,
    success: parsed.filter(log => log.status === 'success').length,
    failed: parsed.filter(log => log.status === 'failed').length,
    avgDuration: parsed.reduce((sum, log) => sum + log.duration, 0) / parsed.length,
  };
  
  return stats;
}

5. 알림 설정

// lib/cron-alerts.js
export async function sendAlert(message, level = 'info') {
  // Slack 알림
  if (process.env.SLACK_WEBHOOK_URL) {
    await fetch(process.env.SLACK_WEBHOOK_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        text: `[Cron ${level.toUpperCase()}] ${message}`,
      }),
    });
  }
  
  // 이메일 알림 (SendGrid 등)
  if (level === 'error' && process.env.SENDGRID_API_KEY) {
    await fetch('https://api.sendgrid.com/v3/mail/send', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        personalizations: [{ to: [{ email: process.env.ADMIN_EMAIL }] }],
        from: { email: 'noreply@example.com' },
        subject: `Cron Job Alert: ${message}`,
        content: [{ type: 'text/plain', value: message }],
      }),
    });
  }
}

6. Cron 작업 테스트

// scripts/test-cron.js
async function testCronJob(path) {
  const response = await fetch(`https://your-app.vercel.app${path}`, {
    headers: {
      'Authorization': `Bearer ${process.env.CRON_SECRET}`,
    },
  });

  const result = await response.json();
  console.log('Cron job result:', result);
  
  return result;
}

// 테스트 실행
testCronJob('/api/cron/daily-backup');

Lessons Learned

  1. 시크릿 키 관리: Cron 작업의 시크릿 키는 환경 변수로 관리해야 합니다.
  2. 에러 핸들링: Cron 작업 실패 시 알림을 받을 수 있도록 설정해야 합니다.
  3. 모니터링: Cron 작업 실행 이력을 모니터링하면 문제를 조기에 발견할 수 있습니다.
  4. 테스트: Cron 작업을 주기적으로 테스트하여 올바르게 동작하는지 확인해야 합니다.
  5. 플랜 확인: Vercel 플랜에 따라 Cron Jobs 지원 여부가 다를 수 있습니다.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.