Cron을 넘어: 왜 systemd Timers를 사용해야 하는가

수십 년 동안 "cron job"은 Unix 계열 시스템에서 예약된 작업의 표준이었습니다. 이는 너무나 근본적인 컴퓨팅 프리미티브(primitive)여서, 실제 실행하는 데몬이 무엇인지와 관계없이 거의 모든 예약된 작업을 "cron job"이라고 부르는 대유법이 될 정도입니다. 그러나 시스템 관리가 진화함에 따라 전통적인 cron의 한계가 더욱 분명해지고 있습니다.

현대적인 Linux 생태계에서 systemd timers는 cron의 단점을 해결하는 동시에, 이전에는 구현하기 어렵거나 불가능했던 기능들을 도입하는 강력하고 통합된 대안을 제공합니다. 만약 여전히 crontab에 의존하고 있다면, 이제 전환할 때가 된 이유를 소개합니다.

전통적인 Cron의 한계

cron은 단순하고 성숙했지만, 전문적인 환경에서는 종종 마찰을 일으킵니다. 일반적인 문제점은 다음과 같습니다:

  • 환경의 모호성: cron의 $PATH 설정은 예측 불가능할 수 있으며, 이는 셸에서는 작동하지만 데몬에 의해 실행될 때는 실패하는 스크립트로 이어지는 경우가 많습니다.
  • 출력의 "블랙홀": 기본적으로 stdoutstderr는 종종 유실되거나 거의 모니터링되지 않는 로컬 메일 시스템으로 전송됩니다. 한 커뮤니티 구성원이 언급했듯이, 많은 관리자들은 syslog에서 가시성을 확보하기 위해 모든 것을 logger로 파이핑하는 방식을 택합니다.
  • 불투명한 실행 이력: 작업이 실행되었는지, 왜 실패했는지, 또는 마지막으로 언제 실행되었는지 확인하려면 서로 다른 로그를 뒤져야 합니다.
  • 난해한 구문문법: 강력하지만, cron의 스케줄링 문법(예: 01,31 04,05 1-15 1,6 *)은 직관적이지 않으며 해독하기 위해 외부 도구가 필요한 경우가 많습니다.

systemd Timers의 작동 방식

스케줄과 명령을 한 줄에 결합하는 cron과 달리, systemd는 이를 두 개의 별도 유닛(unit)으로 분리합니다: Service unit (.service)와 Timer unit (.timer).

1. Service Unit

Service unit은 무엇을 실행할지를 정의합니다. 이는 실행 파일과 그 인수를 지정하는 표준 systemd service 파일입니다. 이러한 분리는 디버깅을 위해 timer가 작동하기를 기다리지 않고 systemctl start를 통해 작업을 수동으로 트리거할 수 있게 해줍니다.

2. Timer Unit

Timer unit은 서비스를 언제 실행할지를 정의합니다. 기본적으로 timer는 동일한 이름의 service unit을 찾습니다 (예: backup.timerbackup.service를 트리거합니다).

고급 스케줄링 기능

systemd timers의 가장 큰 장점 중 하나는 스케줄링 로직의 유연성입니다. 이들은 두 가지 주요 트리거 유형을 지원합니다:

Calendar Events

이는 cron의 wall-clock 스케줄과 유사합니다 (예: OnCalendar=daily). 하지만 systemd는 더 인간이 읽기 쉬운 구문와 강력한 검증 도구인 systemd-analyze calendar를 제공합니다. 이를 통해 시간 표현식을 테스트하고 배포하기 전에 정확히 언제 다음 실행이 이루어질지 확인할 수 있습니다.

Monotonic Timers (상대적 시간)

이 부분은 timer가 cron을 진정으로 압도하는 지점입니다. wall clock 대신 다른 이벤트와 관련된 작업을 예약할 수 있습니다:

  • OnBootSec=: 시스템이 부팅된 후 X만큼의 시간이 지난 뒤 실행됩니다.
  • OnUnitActiveSec=: 서비스가 마지막으로 활성화된 후 X만큼의 시간이 지난 뒤 실행됩니다.

이는 /tmp를 정리하는 작업과 같이, 시스템 부하가 발생할 수 있는 고정된 시간에 실행하기보다는 부팅 후 한 시간 뒤, 그리고 그 이후로 매 시간마다 실행하는 것이 더 합리적인 작업에 이상적입니다.

엔터프라이즈급 기능

Systemd timers는 일반적인 분산 시스템 문제를 해결하는 여러 내장 기능을 포함하고 있습니다:

"Thundering Herd" 문제 해결

수천 대의 머신이 정확히 자녀정(midnight)에 작업을 실행하도록 예약되어 있다면, 중앙 서버를 다운시킬 수 있습니다. Systemd는 RandomizedOffsetSec=를 사용하여 실행을 균등 분포로 분산시켜, 업데이트나 체크인이 네트워크에 한꺼 번에 몰리지 않도록 보장합니다.

지속성 및 신뢰성

예약된 이벤트가 발생하는 동안 머신이 꺼져 있다면, 전통적인 cron job은 단순히 누락됩니다. Persistent=true를 설정하면 systemd는 마지막 트리거 시간을 디스크에 저장합니다. 만약 timer가 활성화되어 다운타임 동안 예약된 실행을를 누락했다는 것을 발견하면, 즉시 서비스를 트리거합니다.

시스템 통합

  • WakeSystem=: Timer는 중요한 작업을 실행하기 위해 시스템을 절전 모드에서 깨우도록 구성할 수 있습니다.
  • Journal Integration: 모든 출력은 자동으로 journalctl에 의해 캡처되어, 모든 실행에 대한 중앙 집중식의 검색 가능한 이력을 제공합니다.
  • Visibility: systemctl list-timers 명령은 모든 활성 스케줄을 한눈에 볼 수 있게 해주며, 각 작업이 마지막으로 언제 실행되었고 다음 실행 예정 시간은 언제인지 정확히 보여줍니다.

커뮤니티 관점 및 트레이드오프

장점에도 불구하고, systemd timers로의 전환은 논쟁의 여지가 있습니다. 일부 사용자들은 timer가 /etc/systemd/system/ 또는 /usr/lib/systemd/system/의 유닛 파일들에 분산되어 있어 crontab -l의 단순함이 사라졌다고 주장합니다.

다른 이들은 유닛 파일의 구문문법이 crontab의 한 줄과 비교했을 때 "ugly"하거나 덜 직관적이라고 느낍니다. 한 사용자가 지적했듯이, 초보자에게 별적인 service와 timer 유닛의 개념을 설명하는 것은 cron 한 줄을 설명하는 것보다 더 복잡합니다.

하지만 복잡한 복도한 인프라를 관리하는 사람들에게 트레이드오프는 systemd 쪽으로 크게 기울어져 있습니다. 네이티브 조건부 실행을 위한 ExecCondition=를 사용할 수 있는 능력과 Ansible과 같은 도구와의 통합을 통해 배포할 수 있다는 점은 timer를 프로덕션 환경을 위한 더 견고한 선택으로 만듭니다. n## 구현을 위한 요약 체크리스트

cron에서 systemd timers로 마이그레이션하는 경우, 다음 팁을 유념하십시오:

  1. systemd-analyze calendar를 사용하여 스케줄을 검증하십시오.
  2. Persistent=true를 사용하여 다운타임 동안 누락되지 않아야 하는 모든 작업에 적용하십시오.
  3. RandomizedOffsetSec=를 사용하여 네트워크 API나 공유 리소스에 접근하는 모든 작업에 적용하십시오.
  4. systemctl list-timers를 정기적으로 확인하여 시스템의 예약된 상태를 모니터링하십시오.

Sources