이전글에서 한동안 멈춰 있던 BBC Football Gossip 번역 봇의 파싱 문제를 수정한걸 다뤘었다.
문제는 BBC 페이지의 HTML 구조가 바뀌면서 기존 selector가 더 이상 기사 문단을 찾지 못했고,
그 결과 실제로는 파싱이 깨졌는데도 가십 없음으로 정상 종료되는 것이었다.
그래서 fallback selector를 추가하고, 실제 BBC 페이지를 가져와 확인하는 smoke test를 만들고, 파싱 실패 시 조용히 넘어가지 않도록 수정했다. 이번에는 그 다음 단계로, 기존에 GitHub Actions 스케줄로 돌리던 봇을 Ubuntu VM의 cron job으로 옮겨봤다.
이번 작업의 핵심은 “GitHub Actions가 하던 정기 실행 역할을 Ubuntu VM의 cron으로 옮기고, 실제 Slack 발송까지 확인하는 것”이었다.
1. 왜 cron으로 옮기려고 했나
이 봇은 원래 GitHub Actions의 schedule 기능으로 매일 실행되고 있었다. 구조 자체는 단순했다.
이번에 사용한 VM은 맥북 위에서 돌아가는 로컬 Ubuntu VM이라, 맥북이 sleep 상태에 들어가면 cron도 실행되지 않는다. 그래도 리눅스 환경에서 직접 스케줄링을 구성하고, 로그를 확인하고, 실행 환경을 줄여보는 경험을 해보기에는 좋은 작업이었다고 생각한다.
2. VM 상태 확인
먼저 Ubuntu VM에 접속해서 Python, Git, timezone, cron 상태를 확인했다.
확인 결과는 다음과 같았다.
timezone이 이미 Asia/Seoul로 잡혀 있어서 cron 시간은 KST 기준으로 생각하면 됐다.
VM timezone이 UTC라면 KST 오전 10시는 UTC 오전 1시로 계산해야 한다. 이번 VM은 KST로 설정되어 있어서 cron에
0 10 * * *를 그대로 사용할 수 있었다.
3. 프로젝트 준비
VM 안에서 프로젝트를 clone하고 가상환경을 만들었다.
처음에는 python3 -m venv .venv에서 실패했다. Ubuntu에 python3.10-venv 패키지가 없었기 때문이다.
에러 메시지에서도 해결 방법을 알려줬다.
그래서 패키지를 설치했다.
그 다음 다시 가상환경을 생성했다.
정상적으로 Python 3.10.12 가상환경이 잡혔다.
의존성도 설치했다.
4. 환경변수 설정
이 봇은 Slack Webhook URL이 필요하다. 로컬 실행과 cron 실행 모두 같은 설정을 쓰기 위해 프로젝트 루트에 .env 파일을 두었다.
처음에는 반드시 DRY_RUN=1로 두고 테스트하는 게 좋다. Slack 실제 발송을 막고, 수집/파싱/번역 흐름만 확인할 수 있기 때문이다.
5. 테스트 실행
먼저 샘플 HTML 기반 파서 테스트를 실행했다.
결과는 통과였다.
다음으로 실제 BBC 페이지를 가져오는 smoke test를 실행했다.
결과는 다음과 같았다.
실제 BBC 페이지 기준으로도 문단을 찾고, 가십 item을 정상적으로 추출했다.
6. 앱 수동 실행
이제 실제 앱을 실행했다.
처음 의도는 DRY_RUN=1 상태로 확인하는 것이었지만, 실행 로그에서 t(slack)가 찍힌 것을 보고 실제 Slack 전송까지 된 것을 확인했다.
[DRY_RUN] Slack 전송 생략이 아니라 t(slack)가 찍혔기 때문에 실제 Slack Webhook 요청이 나간 상태였다.
결과적으로 VM에서 실제 Slack 발송까지 성공한 셈이다.
DRY_RUN 상태를 기대하고 있다면 실행 전에
.env의DRY_RUN값을 꼭 확인해야 한다.
7. cron 등록
수동 실행이 성공했으니 cron을 등록했다.
먼저 로그 디렉터리를 만들었다.
그리고 crontab을 열었다.
매일 KST 오전 10시에 실행되도록 다음 내용을 등록했다.
여기서 중요한 부분은 flock이다.
이걸 넣어두면 이전 실행이 아직 끝나지 않았을 때 다음 실행이 겹치는 것을 막을 수 있다. 이 봇은 보통 1~2초 안에 끝나지만, 네트워크 지연이나 번역 API 문제로 오래 걸릴 수도 있으니 중복 실행 방지를 넣어두는 게 좋다.
cron 등록 상태는 다음 명령으로 확인했다.
등록된 내용은 다음과 같았다.
8. cron 로그 확인
cron 실행 결과는 logs/cron.log로 남기도록 했다.
로그에는 실제 실행 결과가 남았다.
이걸로 cron에서도 정상적으로 실행되고, Slack 전송까지 되는 것을 확인했다.
9. VM 스펙 줄이기
처음 VM은 RAM 2GB, CPU 2 core로 설정되어 있었다. 그런데 이 봇은 매일 한 번 실행되는 간단한 Python 작업이라 리소스를 많이 쓸 이유가 없었다.
먼저 메모리 사용량을 확인했다.
2GB 설정에서는 대부분의 메모리가 비어 있었다.
앱 실행 시 실제 최대 메모리 사용량도 확인했다.
결과를 보면 앱 실행 중 최대 메모리 사용량은 약 37MB 정도였다.
그래서 RAM을 512MB까지 줄여봤다. 앱 자체는 돌아갈 가능성이 높았지만, Ubuntu 부팅이 눈에 띄게 느려졌다. 앱이 가벼운 것과 VM 부팅/운영이 쾌적한 것은 별개의 문제였다.
결국 최종적으로는 RAM 1GB, CPU 1 core로 맞췄다.
1GB 설정에서 다시 확인했다.
앱 실행도 정상적이었다.
이 봇 운영용으로는 RAM 1GB, CPU 1 core 정도가 적당해 보였다. 512MB도 실행 자체는 가능할 수 있지만, 부팅이나 패키지 설치, 업데이트 작업에서 답답할 수 있다.
10. GitHub Actions schedule 비활성화
VM cron이 정상 동작하는 것을 확인했으니 GitHub Actions의 정기 실행은 제거했다.
기존 workflow에는 다음처럼 여러 개의 cron schedule이 있었다.
이 상태로 두면 GitHub Actions와 VM cron이 둘 다 실행되면서 Slack 메시지가 중복 발송될 수 있다.
그래서 schedule은 제거하고, 수동 실행만 남겼다.
이제 자동 실행은 Ubuntu VM cron이 담당하고, GitHub Actions는 필요할 때 수동으로 실행하는 용도로만 남겨두었다.
11. 주의할 점
이번 VM은 클라우드 서버가 아니라 맥북 위에서 실행하는 로컬 Ubuntu VM이다. 그래서 중요한 제약이 있다.
맥북이 sleep 상태에 들어가면 VM도 멈추고, VM이 멈추면 cron도 실행되지 않는다.
즉 이 구성은 완전한 상시 운영 환경이라기보다는, 리눅스와 cron을 연습하면서 직접 운영 환경을 만들어보는 구성에 가깝다.
정말 안정적으로 매일 실행하려면 다음 중 하나가 더 적합하다.
클라우드 VM 항상 켜져 있는 미니 PC NAS 라즈베리파이 GitHub Actions schedule 유지
그래도 이번 작업을 통해 cron 등록, 로그 리다이렉션, flock을 이용한 중복 실행 방지, VM 리소스 조정까지 직접 확인할 수 있었다.
정리
이번 작업으로 BBC Football Gossip 번역 봇의 실행 환경을 GitHub Actions schedule에서 Ubuntu VM cron으로 옮겼다.
최종 구성은 다음과 같다.
VM 스펙은 RAM 1GB, CPU 1 core로 줄였고, 앱 실행 시 최대 메모리 사용량은 약 37MB 정도였다.
GitHub Actions는 정기 schedule을 제거하고 수동 실행 용도로만 남겨두었다. 덕분에 VM cron과 GitHub Actions가 동시에 Slack 메시지를 보내는 중복 발송 문제도 피할 수 있게 됐다.
이번 마이그레이션을 통해 단순히 “어디서 실행할 것인가”만 바꾼 것이 아니라, cron 기반 운영에서 필요한 기본 요소들을 한 번씩 확인해볼 수 있었다.
실행 환경 확인, 가상환경 구성, 테스트 실행, cron 등록, 로그 확인, 중복 실행 방지, 리소스 조정까지 한 번에 다뤄본 작업이었다.
작업한 코드는 아래 레포지토리에 정리되어 있다.
GitHub 레포: https://github.com/hahbr88/bbc_gossip_kr