Systemd 타이머 가이드: 안정적인 스케줄링을 위한 Cron 작업 대체

저널 로그, 누락 실행 처리, 의존성 및 리소스 제어가 필요할 때 cron 대신 systemd 타이머를 사용하세요.

Systemd 타이머 가이드: 안정적인 스케줄링을 위한 Cron 작업 대체

cron은 여전히 많은 작업에 적합합니다. 매일 밤 쉘 스크립트를 실행해야 하고 이미 작동하는 로그 리디렉션이 있다면, 이를 대체할 이유는 없습니다. 많은 팀이 예약된 Linux 작업을 systemd 타이머로 옮기는 이유는 유행 때문이 아닙니다. systemd 타이머는 작업에 실제 서비스 유닛, 예측 가능한 로그, 의존성 처리, 누락 실행 동작 및 리소스 제한을 제공하기 때문입니다.

이는 작업이 단순히 "명령 실행"이 아닐 때 중요합니다. 백업 작업은 마운트된 디스크가 필요할 수 있습니다. 캐시 워머는 네트워크가 사용 가능해야 할 수 있습니다. 보고서 내보내기 작업은 잠긴 서비스 계정으로 실행되고 다음 당직자가 읽을 수 있는 로그를 남겨야 할 수 있습니다. systemd 타이머를 사용하면 이러한 요구 사항을 나머지 서비스 수명 주기를 관리하는 동일한 위치에서 설명할 수 있습니다.

Systemd 타이머 이해하기

systemd 타이머는 다른 systemd 유닛, 일반적으로 service 유닛이 활성화되는 시기를 제어하는 systemd 유닛 파일입니다. 독립 실행형 데몬인 cron과 달리 systemd 타이머는 systemd init 시스템의 필수적인 부분입니다. 이러한 깊은 통합은 특히 안정성, 로깅 및 리소스 관리 측면에서 몇 가지 중요한 이점을 제공합니다.

systemd 타이머는 항상 다른 유닛(가장 일반적으로 service 유닛)과 함께 작동합니다. .timer 파일은 이벤트가 발생해야 하는 시기를 정의하고, 해당 .service 파일은 해당 이벤트가 트리거될 때 수행해야 하는 작업을 정의합니다. 이러한 명확한 관심사 분리는 systemd 타이머를 매우 모듈화되고 유연하게 만듭니다.

Cron 대비 Systemd 타이머의 주요 장점

cron이 기능적이긴 하지만, systemd 타이머는 많은 제한 사항을 해결하여 더 강력하고 기능이 풍부한 스케줄링 솔루션을 제공합니다.

  • 안정성 및 지속성: 캘린더 타이머가 Persistent=true를 사용하고 예약된 실행 중에 시스템이 꺼지면 systemd는 실행이 누락되었음을 기록하고 다음 부팅 후 연결된 서비스를 시작합니다. 일반 cron은 anacron과 같은 별도 도구 없이는 일반적으로 따라잡지 못합니다.
  • systemd와의 통합: 타이머는 systemd의 강력한 로깅(journalctl을 통해), 의존성 관리 및 리소스 제어(cgroups)의 이점을 누립니다. 이는 더 나은 모니터링, 명확한 오류 보고, 예약된 작업에 대한 복잡한 시작 시퀀스 또는 리소스 제한을 정의할 수 있는 기능을 의미합니다.
  • 재현성 및 버전 관리: systemd 유닛 파일은 일반 텍스트 파일로, 버전 관리 시스템에 쉽게 저장할 수 있습니다. 이를 통해 재현 가능한 배포가 가능하고 여러 시스템에서 예약된 작업에 대한 변경 사항을 더 쉽게 추적할 수 있습니다.
  • 이벤트 기반 스케줄링: 단순한 시간 기반 스케줄링 외에도 systemd 타이머는 시스템 부팅(OnBootSec) 기준 또는 유닛의 마지막 활성화(OnUnitActiveSec) 이후에 트리거되어 더 동적인 스케줄링 옵션을 제공할 수 있습니다.
  • 유연한 시간 표현: systemdcron 구문보다 더 읽기 쉽고 다재다능한 풍부한 캘린더 이벤트 표현 세트를 제공합니다. 여기에는 매시간, 매일, 매주 및 특정 날짜/시간이 포함됩니다.
  • 리소스 관리 및 의존성: 타이머에 의해 시작된 systemd 서비스는 cgroup 설정을 포함한 systemd 환경을 상속하며, 다른 systemd 유닛에 대한 의존성을 선언할 수 있습니다(예: 실행 전에 네트워크 또는 데이터베이스를 사용할 수 있을 때까지 대기).
  • 표준 출력/오류 처리: systemd는 타이머에 의해 시작된 서비스의 stdoutstderr를 자동으로 캡처하여 시스템 저널로 보내므로, cron의 이메일 기반 출력 또는 수동 리디렉션보다 디버깅 및 감사가 훨씬 간단합니다.

Systemd 타이머 구성하기

systemd 타이머를 구성하려면 서비스 유닛(.service)과 타이머 유닛(.timer)의 두 가지 유닛 파일을 생성해야 합니다. 이러한 파일은 일반적으로 시스템 전체 타이머의 경우 /etc/systemd/system/에, 사용자별 타이머의 경우 ~/.config/systemd/user/에 배치됩니다.

1. 서비스 유닛(.service 파일)

서비스 유닛은 실행할 실제 명령 또는 스크립트를 정의합니다. 표준 systemd 서비스 파일이지만, 종종 비대화식으로 실행되고 특정 작업을 수행하도록 설계됩니다.

예: /etc/systemd/system/mytask.service

[Unit]
Description=내 예약 작업 서비스

[Service]
Type=oneshot
ExecStart=/usr/local/bin/mytask.sh
User=myuser
Group=mygroup
# 선택 사항: 최신 systemd 릴리스에서 리소스 제한
# CPUWeight=50
# MemoryMax=1G

[Install]
WantedBy=multi-user.target

설명:

  • [Unit]: 유닛에 대한 일반 정보를 포함합니다.
    • Description: 사람이 읽을 수 있는 설명입니다.
  • [Service]: 서비스별 구성을 정의합니다.
    • Type=oneshot: 서비스가 단일 명령을 실행하고 종료됨을 나타냅니다. 이는 예약된 작업에 일반적입니다.
    • ExecStart: 실행할 명령 또는 스크립트입니다. 전체 경로를 제공하십시오.
    • User, Group: 명령이 실행될 사용자와 그룹을 정의합니다. 작업은 항상 필요한 최소 권한으로 실행하십시오.
    • CPUWeight, MemoryMax: 선택적 cgroup 제어입니다. 예약된 작업이 호스트의 나머지 부분을 굶주리게 해서는 안 되는 경우 유용합니다.
  • [Install]: 유닛을 활성화하는 방법을 정의합니다.
    • WantedBy=multi-user.target: 있지만, 이 섹션은 타이머 트리거 서비스의 경우 덜 중요합니다. 타이머 유닛 자체가 일반적으로 활성화를 결정하기 때문입니다. 그러나 서비스를 수동으로 활성화할 수 있도록 하거나 다른 systemd 대상에 통합하려는 경우 유용할 수 있습니다.

2. 타이머 유닛(.timer 파일)

타이머 유닛은 해당 서비스 유닛이 활성화되어야 하는 시기를 정의합니다. 서비스 대응 파일(예: mytask.service의 경우 mytask.timer)과 동일한 이름을 가져야 합니다.

예: /etc/systemd/system/mytask.timer

[Unit]
Description=매일 mytask.service 실행

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=600
AccuracySec=1min

[Install]
WantedBy=timers.target

설명:

  • [Unit]: 일반 정보입니다.
    • Description: 타이머에 대한 설명입니다.
  • [Timer]: 타이머별 구성을 정의합니다.
    • OnCalendar: 가장 일반적인 설정으로, 캘린더 이벤트를 정의합니다. 다음과 같은 표현식을 사용합니다.
      • daily: 매일 자정.
      • weekly: 매주 월요일 자정.
      • monthly: 매월 1일 자정.
      • hourly: 매시간 정각.
      • *-*-* 03:00:00: 매일 오전 3시.
      • Mon..Fri 08:00..17:00: 평일 오전 8시에서 오후 5시 사이.
      • Mon *-*-* 03:00:00: 매주 월요일 오전 3시.
    • OnBootSec: 시스템 부팅 후 지정된 시간 후에 서비스를 활성화합니다. 예: OnBootSec=10min.
    • OnUnitActiveSec: 서비스의 마지막 활성화 후 지정된 시간 후에 서비스를 활성화합니다. 예: OnUnitActiveSec=1h는 이전 실행이 완료된 후 매시간 실행됩니다.
    • Persistent=true: 안정성에 중요합니다. 예약된 실행 중에 시스템이 꺼져 있으면 서비스는 다음 부팅 직후에 트리거됩니다.
    • RandomizedDelaySec=600: 최대 600초의 무작위 지연을 추가합니다. 이는 여러 시스템이 동일한 타이머를 공유하고 모든 호스트가 정확히 동시에 데이터베이스, API 또는 백업 서버에 접속하는 것을 원하지 않을 때 유용합니다.

실제 Cron에서 타이머로 마이그레이션

현재 다음과 같은 루트 cron 항목이 있다고 가정해 보겠습니다.

15 2 * * * /usr/local/sbin/backup-app.sh >> /var/log/backup-app.log 2>&1

조용한 시스템에서는 작동하지만 일반적인 약점이 있습니다. 백업 디스크가 마운트되지 않으면 스크립트가 중간에 실패할 수 있습니다. 서버가 오전 2시 15분에 꺼져 있으면 실행이 건너뜁니다. 스크립트가 유용한 오류를 작성하면 누군가가 확인할 사용자 정의 로그 파일을 기억해야 합니다. 스크립트가 너무 많은 메모리를 사용하기 시작하면 cron이 이를 제한하는 데 도움이 되지 않습니다.

systemd 버전은 명령과 일정을 분리합니다.

# /etc/systemd/system/backup-app.service
[Unit]
Description=애플리케이션 데이터 백업
RequiresMountsFor=/mnt/backups
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
User=backup
Group=backup
WorkingDirectory=/srv/app
ExecStart=/usr/local/sbin/backup-app.sh
MemoryMax=1G
CPUWeight=40
Nice=10
# /etc/systemd/system/backup-app.timer
[Unit]
Description=매일 밤 애플리케이션 백업 실행

[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
RandomizedDelaySec=15min
AccuracySec=1min
Unit=backup-app.service

[Install]
WantedBy=timers.target

주목할 만한 몇 가지 세부 사항이 있습니다. RequiresMountsFor=/mnt/backups는 systemd에 서비스가 시작되기 전에 경로가 마운트되어야 함을 알려줍니다. After=network-online.targetWants=network-online.target은 네트워크 관리자가 실제로 대기 온라인 서비스를 제공하는 경우에만 유용합니다. 많은 배포판에서 해당 서비스는 기본적으로 비활성화되어 있습니다. 백업이 로컬 디스크에만 기록하는 경우 네트워크 의존성을 제거하십시오.

Type=oneshot은 작업을 수행하고 종료되는 스크립트에 적합합니다. 실행 상태를 유지하는 데몬에는 사용하지 마십시오. WorkingDirectory=는 실수로 특정 디렉토리의 쉘에서 시작되는 것에 의존하는 스크립트로부터 사용자를 구해줍니다. User=backup은 일반적으로 작업을 root로 실행하고 스크립트 내부의 모든 명령이 신중하기를 바라는 것보다 낫습니다.

파일을 저장한 후:

sudo systemctl daemon-reload
sudo systemctl enable --now backup-app.timer
systemctl list-timers backup-app.timer

작업을 즉시 테스트하려면 타이머가 아닌 서비스를 시작하십시오.

sudo systemctl start backup-app.service
journalctl -u backup-app.service -n 100 --no-pager

이 한 가지 차이점은 많은 혼란을 방지합니다. backup-app.timer를 시작하면 일정이 준비됩니다. backup-app.service를 시작하면 실제 백업이 실행됩니다.

올바른 타이머 표현식 선택하기

OnCalendar=는 cron 구문에 가장 가까운 대체품이지만 다르게 읽힙니다. 배포하기 전에 systemd가 표현식의 의미를 어떻게 생각하는지 확인할 수 있습니다.

systemd-analyze calendar 'Mon..Fri 03:30'
systemd-analyze calendar '*-*-01 04:00:00'
systemd-analyze calendar 'Sun *-*-* 23:00:00'

벽시계 작업에는 캘린더 타이머를 사용하십시오. 야간 백업, 주간 보고서, 월간 정리, 인증서 확인 및 인간 달력이 중요한 기타 작업입니다. "무언가 발생한 후 실행" 동작에는 모노토닉 타이머를 사용하십시오.

[Timer]
OnBootSec=10min
OnUnitActiveSec=1h

이 패턴은 부팅 후 10분 후에 서비스를 시작한 다음 마지막 활성화 후 1시간 후에 다시 시작합니다. 폴링, 로컬 정리 및 가벼운 유지 관리 루프에 적합합니다. "매시간 0분"과는 다릅니다. 작업이 12분이 걸리면 다음 실행은 벽시계 기대치가 아닌 활성화 타이밍에서 계산됩니다.

또한 중복에 대해 생각하십시오. 일반 서비스 유닛의 경우 systemd는 다음 타이머 이벤트가 도착했다고 해서 동일한 활성 유닛의 두 번째 복사본을 시작하지 않습니다. 작업이 간격보다 오래 실행될 수 있는 경우 이것이 허용되는지 결정하십시오. 때로는 스크립트의 잠금(예: flock)이 정답입니다. "이전 실행이 아직 활성 상태입니다"라는 명확한 메시지를 생성할 수 있기 때문입니다. 때로는 간격을 늘리는 것이 정답입니다.

시간을 절약하는 운영 습관

타이머 보기는 첫 번째 대시보드입니다.

systemctl list-timers --all

마지막 실행, 다음 실행 및 각 타이머가 활성화하는 유닛을 표시합니다. 타이머가 나열되었지만 서비스가 실행되지 않는 경우 캘린더 표현식과 타이머가 활성화되었는지 확인하십시오. 서비스가 실행되고 실패하는 경우 잠시 타이머를 무시하고 서비스를 검사하십시오.

systemctl status backup-app.service
journalctl -u backup-app.service --since today

두 유닛 파일 중 하나를 편집할 때 다음을 실행하십시오.

sudo systemctl daemon-reload
sudo systemctl restart backup-app.timer

일정 변경 후 타이머를 다시 시작하는 것은 좋은 습관입니다. 다음 활성화 시간이 즉시 새로 고쳐지기 때문입니다. 스크립트 자체만 변경한 경우 일반적으로 daemon-reload가 필요하지 않습니다.

사용자 타이머의 경우 systemctl --user를 사용하고 유닛을 ~/.config/systemd/user/에 배치하십시오. 개발자 워크스테이션 및 사용자별 자동화에 유용하지만 한 가지 중요한 주의 사항이 있습니다. 기본적으로 사용자 서비스는 사용자의 로그인 세션에 연결됩니다. 로그아웃 후에도 사용자 타이머가 계속 실행되도록 하려면 loginctl enable-linger username으로 지속을 활성화하십시오. 이는 기사 내에서 마법 수정으로 숨길 것이 아니라 의도적인 관리 선택입니다.

Cron이 여전히 더 나은 도구인 경우

모든 것을 맹목적으로 옮기지 마십시오. Cron은 특히 systemd가 PID 1이 아닌 오래된 서버나 최소 컨테이너에서 작은 사용자 로컬 작업을 읽기에 더 쉽습니다. 유일한 요구 사항이 "이 무해한 명령을 5분마다 실행"인 경우 cron이 가장 명확한 답변일 수 있습니다.

Systemd 타이머는 작업에 서비스와 같은 요구 사항(제어된 ID, 저널의 로그, 리소스 제한, 의존성, 따라잡기 동작 또는 유닛 파일을 통한 표준 배포)이 있을 때 효과적입니다. 실제로 저는 예약된 작업이 실패할 경우 누군가를 깨울 때 타이머를 사용합니다. 추가 유닛 파일은 다음 운영자에게 "무엇이 실행되었는가?"에서 "무엇이 실패했는가?"에서 "무엇이 변경되었는가?"로의 직접적인 경로를 제공할 때 가치가 있습니다.

마이그레이션 중에 채택할 가치가 있는 한 가지 최종 습관은 타이머가 몇 번 성공적으로 실행될 때까지 이전 cron 항목을 주석 처리하여 근처에 두었다가 제거하는 것입니다. 중복 일정은 조용한 손상 원인입니다. 두 개의 백업 작업이 동일한 잠금을 놓고 경쟁할 수 있고, 두 개의 정리 작업이 예상보다 일찍 파일을 삭제할 수 있으며, 두 개의 보고서 작업이 중복 이메일을 보낼 수 있습니다. 타이머를 활성화한 후 systemctl list-timers --all을 확인하고 서비스 저널을 확인하고 이전 cron 경로가 더 이상 활성화되지 않았는지 확인하십시오.