콘텐츠로 이동

cron 예약 실행과 클라우드 실행

11/11(수)

한 줄 요약: "매일 18시에 이 작업을 돌려라"를 맡길 곳은 내 컴퓨터(cron·launchd)와 클라우드 서버(GitHub Actions 등) 두 가지다. 둘을 가르는 건 "실행 시각에 그 기계가 켜져 있느냐"와 "그 기계가 재료 파일을 볼 수 있느냐"다.

📍 만난 상황

  • 2026-11-11 — 학습 일지를 매일 18시에 자동으로 정리해 push하는 구조를 만들던 중이었다. 2번 물음 → 그날 일지
  • 10:03 직전에 "Mac에 18시 launchd 예약을 등록했다"는 보고를 들었다. "꼭 이 Mac에서 cron을 돌려야 하나? 노트북을 바꾸면 안 되잖아"라고 물었다. → 대화 원본이 그 컴퓨터에만 있어서 완전히 클라우드로만은 불가능하다는 답을 들었다.
  • 10:50 할 일 정리를 들은 뒤 "실행 위치를 어디로 하면 좋을까, 세션을 안 닫아도 18시에 정리되면 좋겠다"고 물었다. → 노트북 교체는 두 방식의 차이가 아니고, 실제 차이는 18시에 노트북이 깨어 있느냐 하나라는 답을 들었다.

🧩 쉽게 말하면

매일 저녁 6시에 일기 정리를 누군가에게 맡긴다고 하자.

  • 집 비서(내 컴퓨터): 내 책상 위 메모를 다 볼 수 있다. 하지만 6시에 집이 불 꺼져 있으면(노트북을 덮었으면) 일을 못 한다.
  • 회사 비서(클라우드): 6시엔 항상 출근해 있다. 하지만 내 책상 메모는 못 본다. 내가 미리 사진 찍어 보내 준 것만 정리할 수 있다.

예약 실행이 없으면 매일 6시에 내가 직접 명령을 쳐야 한다. 잊는 날이 반드시 생긴다.

📖 제대로 알기

예약 실행(스케줄링)이란

정해진 시각이나 간격에 프로그램을 자동으로 실행하는 기능이다. 사람이 없어도 돈다.

로컬 예약 실행

  • cron: 유닉스·리눅스의 전통적인 예약 실행 도구다. crontab(cron 표: 언제 무엇을 돌릴지 적은 목록)에 한 줄씩 적는다.
  • cron 식: 0 18 * * *처럼 칸 다섯 개로 시각을 적는다. 순서는 분·시·일·월·요일이다. 이 예는 "매일 18시 0분"이다.
  • launchd: macOS의 공식 예약·상주 실행 관리자다. Mac에서도 cron이 돌긴 하지만 Apple은 launchd를 권한다. 설정은 ~/Library/LaunchAgents/의 plist(XML 설정 파일)에 적는다.

클라우드 예약 실행

  • GitHub Actions: GitHub가 제공하는 자동 실행 서비스다. 저장소 안 .github/workflows/*.yml에 적으면 GitHub 서버가 돌려 준다.
  • schedule 트리거: Actions에서 cron 식으로 시각을 정하는 방법이다. 시각은 UTC(세계 표준시) 기준이다. 한국 18시는 UTC 9시다.
  • 러너(runner: 작업을 실제로 돌리는 임시 서버): 실행마다 새로 만들어지고 끝나면 사라진다. 그래서 저장소에 올라온 것만 들고 시작한다.
  • Secrets: 토큰 같은 비밀값을 저장소 설정에 숨겨 두고 실행 때만 넣어 주는 기능이다.

⚙️ 어떻게 동작하나

로컬(launchd) 방식

  1. launchd가 plist를 읽어 "매일 18:00"을 기억한다.
  2. 18:00이 되면 지정한 스크립트를 실행한다.
  3. 스크립트는 Mac의 파일(예: 대화 기록 ~/.claude/projects/*.jsonl)을 바로 읽는다.
  4. 정리 결과를 commit하고 push한다.
  5. 그 시각에 Mac이 잠자기 상태였다면, launchd는 깨어날 때 놓친 실행을 한 번 돌린다. 전원이 꺼져 있었다면 그날 실행은 없다.

클라우드(GitHub Actions) 방식

  1. 낮 동안 Mac이 재료(정리 메모)를 주기적으로 GitHub에 올린다. 이 부분은 여전히 Mac의 일이다.
  2. UTC 9:00(한국 18:00)에 GitHub가 러너를 하나 띄운다.
  3. 러너가 저장소를 내려받는다(checkout). 올라온 재료만 손에 쥔다.
  4. Secrets의 토큰으로 정리 작업을 돌리고 push한다.
  5. 러너가 사라진다. Mac이 꺼져 있어도 1~5는 그대로 일어난다.

flowchart LR
    subgraph L["① 로컬 방식"]
        M1["Mac 파일"] -->|"18시 직접 읽기"| J1["launchd 작업"]
    end
    subgraph C["② 클라우드 방식"]
        M2["Mac 파일"] -->|"낮 동안 업로드"| G["GitHub 저장소"]
        G -->|"18시 checkout"| J2["Actions 러너"]
    end
    J1 -->|"push"| R["일지 저장소"]
    J2 -->|"push"| R
①은 재료를 직접 읽지만 Mac이 깨어 있어야 한다. ②는 Mac이 꺼져도 돌지만, 재료가 업로드라는 다리를 먼저 건너야 한다.

🧪 예시

  • 예시 1 · 일상에 빗대어: 오늘 대화가 09:00~17:55에 있었다고 하자. 업로드는 20분마다 한다.
  • ① 로컬: 18:00에 Mac이 켜져 있으면 17:55 대화까지 전부 들어간다. 17:30에 덮고 퇴근했다면 실행은 다음 날 아침 Mac을 열 때로 밀린다. 퇴근길에 볼 일지가 없다.
  • ② 클라우드: 마지막 업로드가 17:40이었다면 17:40~17:55의 15분은 그날 정리에서 빠진다. 대신 17:30에 덮고 나가도 18:00에 일지가 나온다.

  • 예시 2 · 실제로는: ① launchd plist의 핵심 부분이다.

    <key>ProgramArguments</key>
    <array>
      <string>/bin/zsh</string>
      <string>-lc</string>
      <string>~/learning-log/.tools/run.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
      <key>Hour</key><integer>18</integer>
      <key>Minute</key><integer>0</integer>
    </dict>
    
    ① ProgramArguments: 실행할 명령이다. zsh -lc로 감싸 로그인 셸 환경(PATH 등)을 불러온다. ② StartCalendarInterval: 매일 18시 0분에 실행하라는 뜻이다. cron의 0 18 * * *와 같다.

② GitHub Actions 워크플로의 핵심 부분이다.

on:
  schedule:
    - cron: "0 9 * * *"     # UTC 9시 = 한국 18시
  workflow_dispatch:         # 손으로도 실행 가능
jobs:
  digest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./.tools/run.sh
        env:
          CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
① schedule.cron: 시각을 UTC로 적는다. 한국 시각으로 적는 실수가 흔하다. ② actions/checkout: 저장소를 러너에 내려받는다. 러너가 볼 수 있는 건 이것뿐이다. ③ secrets.…: 저장소 설정에 등록한 토큰을 실행 때만 넣는다. 파일에는 값이 남지 않는다.

⚖️ 비교 · 흔한 오해

항목 ① 로컬 (launchd) ② 클라우드 (Actions)
내 파일 직접 읽기 됨 안 됨 (올린 것만)
18시에 노트북 덮음 깨어날 때 실행 정상 실행
18시 직전 대화 전부 들어감 마지막 업로드 이후는 빠짐
구성 단순 업로드 + 워크플로 + 토큰
노트북 교체 clone + 설치 clone + 설치

오해 1. "클라우드 예약 작업도 내 Mac 파일을 읽을 수 있다" 러너는 GitHub 데이터센터의 임시 서버다. 내 Mac과 네트워크로 이어져 있지 않다. 그래서 "Mac에만 있는 재료"는 누군가 먼저 올려 줘야 한다. 왜 헷갈리기 쉬운가: 둘 다 "내 저장소의 스크립트"를 돌린다. 같은 코드를 돌리니 같은 파일을 볼 거라고 느끼기 쉽다. 하지만 코드는 같아도 실행되는 기계가 다르다.

오해 2. "노트북을 바꾸면 로컬 방식은 못 쓴다" 스크립트와 설정을 저장소에 넣어 두면 새 노트북에서 git clone + 설치 스크립트로 똑같이 복구된다. 클라우드 방식도 업로드 부분은 새 노트북에 설치해야 한다. 그래서 교체 문제는 두 방식의 차이가 아니다. 오늘 Claude도 처음엔 이걸 클라우드의 장점처럼 설명했다가 바로잡았다.

오해 3. "cron 시각에 정확히 돈다" Mac은 잠자기 중이면 못 돈다. GitHub Actions의 schedule도 사용량이 몰리면 수 분~수십 분 늦거나 드물게 건너뛴다. 정각이 꼭 중요하면 이 점을 감안한다.

🛠️ 실무에서는

  • 고르는 기준은 하나다: 실행 시각에 그 기계가 깨어 있나? 재료는 그 기계에 있나?
  • 재료가 내 컴퓨터에만 있고, 그 시각에 켜져 있다 → 로컬.
  • 그 시각에 꺼져 있을 수 있다 → 클라우드. 단, 재료를 올리는 장치를 따로 둔다.
  • 체크리스트
  • 스크립트·설정을 저장소에 넣었나? (교체 대비)
  • 클라우드라면 시각을 UTC로 적었나?
  • 비밀값은 파일이 아니라 Keychain·Secrets에 두었나?
  • 실행 결과를 남기는 로그나 알림이 있나? (놓친 날을 알아채려면)
  • 안 돌았을 때 먼저 볼 것
  • launchd: launchctl list | grep <이름>으로 등록 여부, 스크립트 로그의 실행 시각.
  • Actions: 저장소의 Actions 탭 실행 기록. 저장소에 60일간 활동이 없으면 예약 실행이 꺼질 수 있다(공개 저장소).
  • 처음엔 단순한 쪽(로컬)으로 며칠 써 보고, 놓치는 날이 생기면 옮기는 게 안전하다. 정리 로직을 스크립트로 분리해 두면 옮겨도 재사용된다.

❓ 자주 묻는 질문

  • Q. Mac에서 cron과 launchd 중 무엇을 쓰나? A. launchd를 권한다. cron은 잠자기 중 놓친 실행을 그냥 건너뛰지만, launchd는 깨어날 때 한 번 돌려 준다.
  • Q. 클라우드로 가면 Mac에서 할 일이 아예 없어지나? A. 재료가 Mac에만 생기면 없어지지 않는다. 재료를 올리는 가벼운 작업(주기적 업로드, 세션 종료 hook 등)이 남는다.
  • Q. 회사 서버에 cron을 걸면 되지 않나? A. 항상 켜진 서버라는 점에선 클라우드와 같다. 다만 그 서버도 내 Mac 파일은 못 보니 업로드 문제는 똑같이 남는다.

🔭 더 깊이 보기

📚 참고 자료

  • GitHub Actions 설명서 (GitHub Docs 한국어) — 워크플로 작성, schedule 트리거, Secrets 사용법 · 링크
  • Creating Launch Daemons and Agents (Apple 개발자 문서, 영문) — plist 작성법과 StartCalendarInterval의 잠자기 동작 · 링크
  • crontab guru (영문) — cron 식을 넣으면 언제 도는지 말로 풀어 주는 도구 · 링크

🗓️ 이력

  • 2026-11-11: 클라우드에서 돌리면 노트북 교체 문제가 없지 않냐, 두 실행 위치가 실제로 어떻게 다르냐를 2번 물었다 → 일지

🕸️ 연결된 주제