logo hsb.horse
← 블로그 목록으로 돌아가기

블로그

PM2로 Cron 작업 만들기: launchd의 대안으로

macOS에서 launchd 대신 PM2를 사용해 Cron 작업을 만드는 방법. 설정 파일과 Bun을 사용한 주기 실행 예시를 정리했다.

게시일:

macOS에서 주기 실행 작업을 하고 싶었는데, launchd 설정은 여전히 읽기 어렵고 더 편하게 가고 싶어서 PM2를 쓰기로 했다.

PM2로 Cron 작업을 돌리기 위한 설정

Terminal window
cd $PROJECT_ROOT
pm2 ecosystem

ecosystem.config.cjs 에서 설정을 관리한다.

module.exports = {
apps: [
{
name: "cronJob",
script: "main.ts",
interpreter: "~/.bun/bin/bun",
// 인스턴스 수는 1
instances: 1,
// 15분마다 실행
cron_restart: "*/15 * * * *",
exec_mode: "fork",
// 대상 파일이 바뀌었을 때 자동 재시작할지: 안 함
watch: false,
// 자동 재시작할지: 안 함
autorestart: false,
},
],
};

포인트

  • cron_restart: Cron 형식으로 실행 간격 지정
  • autorestart: false: Cron 작업으로 실행하므로 자동 재시작은 끈다
  • interpreter: Bun 같은 원하는 런타임을 지정 가능

실행 명령:

Terminal window
pm2 start ecosystem.config.cjs
pm2 save

실행 작업 샘플 구현

IP 주소를 주기적으로 가져와 DB에 저장하는 처리.

import { Database } from 'bun:sqlite';
const DDL = `CREATE TABLE IF NOT EXISTS ip_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
async function fetchIpConfig() {
const response = await fetch("https://ifconfig.io/ip").catch((err) => new Error(err));
if (response instanceof Error || !response.ok) {
return null;
}
const ip = await response.text();
return ip;
}
async function main() {
const ip = await fetchIpConfig();
if (ip == null) return;
using db = new Database('ip-monitor.sqlite', { create: true });
db.exec(DDL);
using query = db.query("INSERT INTO ip_history (ip) VALUES ($ip);");
query.run({ $ip: ip });
}
await main();

참고 자료

정리

PM2를 쓰면 launchd 보다 훨씬 단순한 설정으로 Cron 작업을 관리할 수 있다.

ecosystem.config.cjs 에 작업을 정의해 두면 로그 확인과 필요 시 재시작 관리도 편해진다.