shell script 없이도 systemd timer로 cron 대체하는 방법

조회수: 0

cron 대신 systemd timer를 쓰는 이유

리눅스에서 주기 작업을 실행할 때 가장 먼저 떠오르는 도구가 cron이다.
다만 실제 운영을 해 보면 다음과 같은 이유로 systemd timer가 더 편하게 느껴지는 경우가 많다.

  • 서비스 관리와 스케줄 관리를 한 시스템에서 통합 관리할 수 있다
  • 작업 단위가 systemd 서비스이기 때문에 상태 확인과 재시작이 쉽다
  • 실패 시 재시도나 의존성 같은 고급 기능을 그대로 활용할 수 있다
  • 사용자별 크론 파일을 돌아다니며 찾을 필요 없이 유닛 파일만 보면 된다

게다가 간단한 작업이라면 굳이 별도의 shell script 파일을 만들 필요도 없다.
서비스 유닛의 ExecStart에 명령을 직접 적어 두고, 타이머 유닛으로 실행 시점만 정해 주면 된다.

systemd timer의 기본 구조 이해

systemd timer는 두 개의 유닛이 한 쌍으로 움직인다고 이해하면 된다.

  • 서비스 유닛
    • 확장자 service
    • 실제로 실행할 명령과 환경을 정의
  • 타이머 유닛
    • 확장자 timer
    • 언제 해당 서비스 유닛을 실행할지 정의

주요 개념은 다음과 같다.

  • 서비스 유닛 이름이 backup service라면 타이머 유닛 이름은 backup timer로 만든다
  • 타이머가 활성화된 뒤 조건이 만족되면 systemd가 자동으로 backup service를 한 번 실행한다
  • 반복 실행을 원하면 OnCalendar나 OnActiveSec 같은 옵션을 사용한다

이 구조 덕분에 작업 내용과 스케줄이 깔끔하게 분리된다.

shell script 없이 서비스 유닛 만들기

먼저 실제 명령을 담을 서비스 유닛을 만든다. 예를 들어 매일 로그 디렉터리를 압축하는 작업을 생각해 보자. 기존에는 사용자 홈 디렉터리 어딘가에 backup sh 같은 스크립트를 만들었다면, systemd를 쓸 때는 다음처럼 바로 유닛 파일로 정의할 수 있다.

서비스 유닛 파일 경로 예시

  • 시스템 전체 공용 작업
    • etc systemd system 아래에 생성
  • 특정 사용자 전용 작업
    • home 사용자명 아래 config systemd user 아래에 생성

여기서는 시스템 전체 기준으로 예를 든다.

로그 압축 서비스 유닛 예시

[Unit]
Description=로그 디렉터리 압축 작업

[Service]
Type=oneshot
ExecStart=/usr/bin/tar czf /var/backups/logs.tar.gz /var/log/myapp
User=root
Group=root

[Install]
WantedBy=multi-user.target

설명

  • Type을 oneshot으로 두면 작업이 끝나면 서비스가 바로 종료된다
  • ExecStart에 쉘 스크립트 파일이 아니라 실제 실행할 명령을 직접 적는다
  • User와 Group을 지정하면 해당 계정으로 명령이 실행된다
  • Description은 원하는 설명으로 채운다

이렇게 하면 별도의 shell 파일 없이도 서비스 정의가 완성된다.

timer 유닛으로 실행 주기 정의

이제 이 서비스를 언제 실행할지 타이머 유닛에서 정한다.
systemd는 두 가지 방식의 타이머를 지원한다.

  • OnCalendar
    • 사람에게 읽기 쉬운 일정 표현
    • 매일, 매주, 부팅 후 특정 시점 등
  • OnActiveSec OnBootSec 등
    • 유닛 활성화 후 경과 시간 기준

여기서는 매일 한 번 실행하는 예제를 만들어 보자.

타이머 유닛 예시

[Unit]
Description=로그 압축 서비스용 타이머

[Timer]
OnCalendar=daily
Persistent=true
Unit=log-archive.service

[Install]
WantedBy=timers.target

설명

  • OnCalendar에 daily를 넣으면 하루에 한 번 실행한다
  • Persistent가 true이면 시스템이 꺼져 있는 동안 실행하지 못한 시간이 있어도 다음 부팅 후 바로 한 번 실행해 준다
  • Unit은 트리거할 서비스 이름이다

이처럼 일정 표현을 키워드로 쓰면 복잡한 크론 표현식 없이도 직관적으로 스케줄을 정의할 수 있다.

유닛 파일 배치와 활성화 흐름

유닛 파일을 만들었다면 systemd에 적용하고 활성화해야 한다.
대표적인 흐름은 다음과 같다.

서비스와 타이머 파일 저장

  • etc systemd system log-archive.service
  • etc systemd system log-archive.timer

systemd에 설정 재인식 요청

sudo systemctl daemon-reload

타이머 활성화와 즉시 시작

sudo systemctl enable --now log-archive.timer

상태 확인

sudo systemctl status log-archive.timer
sudo systemctl list-timers

list-timers 명령은 등록된 타이머와 다음 실행 예정 시각, 마지막 실행 시각을 한눈에 보여 주기 때문에 크론보다 훨씬 보기 편하다.

다양한 주기 표현 예시

shell script 없이도 timer만 잘 쓰면 cron이 하던 일을 대부분 대체할 수 있다. 자주 쓰이는 패턴을 정리하면 대략 다음과 같다.

  • 매 분마다 실행
    • OnCalendar=minutely
  • 매시간 실행
    • OnCalendar=hourly
  • 매일 실행
    • OnCalendar=daily
  • 매주 실행
    • OnCalendar=weekly
  • 매달 실행
    • OnCalendar=monthly

부팅 후 일정 시간마다 반복하고 싶다면 OnBootSec과 OnUnitActiveSec을 함께 쓴다.

예를 들면

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

이런 식으로 표현해 부팅 후 다섯 분 뒤 한 번 실행하고 이후에는 한 시간마다 반복하게 만들 수 있다.

cron에서 옮겨올 때 생각할 점

이미 cron으로 돌리고 있는 작업을 systemd timer로 옮길 때는 다음을 주의하면 좋다.

  • cron에서는 기본 쉘이 자동으로 붙지만 systemd 서비스는 ExecStart에 적은 명령만 실행한다
    • 파이프나 리다이렉션이 들어가는 복잡한 명령이라면 ExecStart에 쉘을 명시해야 한다
    • 예를 들어 ExecStart=/bin/sh -c 명령 문자열 형태
  • 환경 변수가 다를 수 있다
    • 필요하다면 Environment나 EnvironmentFile 지시어를 사용한다
  • 작업이 오래 걸리는 경우 Type과 TimeoutSec 설정을 함께 검토한다

가능하다면 기존 스크립트를 그대로 옮기기보다는 유닛 파일 안에서 필요한 명령만 간결하게 재구성하는 편이 관리 측면에서 유리하다.

로그 확인과 디버깅

cron은 작업 실패 시 메일을 보내거나 로그 파일을 따로 만들어 확인하는 방식이 많다.
systemd timer를 쓰면 journalctl만으로 서비스 실행 로그를 전부 볼 수 있다.

예를 들어 로그 압축 서비스 로그를 보고 싶다면

sudo journalctl -u log-archive.service

최근 실행 한 번만 보려면 옵션을 붙여서

sudo journalctl -u log-archive.service -n 20

이처럼 서비스 유닛 기준으로 로그를 모아 볼 수 있기 때문에 작업이 언제 실행됐는지, 어떤 오류가 있었는지 파악하기 쉽다.
타이머 유닛 자체의 상태나 실패 여부는 systemctl status로 확인한다.

사용자 단위 timer로 개인 작업 자동화

루트 권한이 필요하지 않은 개인 작업이라면 사용자 단위 systemd를 활용하는 방법도 있다. 이 경우에는 sudo 없이도 타이머를 만들 수 있다.

대략적인 흐름은 다음과 같다.

  • home 사용자명 아래 config systemd user 디렉터리 생성
  • 그 안에 서비스와 타이머 유닛 파일 저장
  • systemctl 대신 systemctl user 옵션 사용

예를 들면

systemctl --user daemon-reload
systemctl --user enable --now my-task.timer
systemctl --user list-timers

이 방식은 개발 환경에서 개인용 리마인더나 간단한 백업 작업을 설정할 때 유용하다.
데스크톱 환경이라면 사용자 세션이 살아 있는 동안에만 타이머가 동작한다는 점도 함께 기억해 두면 좋다.

언제 cron을 유지하고 언제 timer로 옮길지

실무에서는 모든 환경을 한 번에 바꾸기보다 기준을 세우고 점진적으로 옮기는 편이 안전하다. 다음과 같이 정리해 볼 수 있다.

systemd timer로 옮기는 편이 좋은 경우

  • 대상 서버가 이미 systemd 기반이면
  • 작업 실패 시 재시도나 상태 확인이 중요한 작업이면
  • 서비스와 스케줄을 한눈에 관리하고 싶다면
  • 신규 프로젝트나 새 서버 구축 시

cron을 그대로 두어도 크게 문제없는 경우

  • 아주 단순한 개발용 개인 작업
  • 이미 잘 돌아가고 있고, 로그와 모니터링 구조가 갖춰져 있는 크론 잡들
  • systemd가 아닌 다른 init 시스템을 사용하는 오래된 환경

새로 만드는 작업은 가능하면 systemd timer를 기준으로 설계하고, 기존 크론 잡은 중요도와 복잡도를 보며 단계적으로 옮기는 전략이 현실적이다.

마무리 shell script 없는 systemd timer 활용 정리

정리해 보면 shell script 없이도 systemd timer는 다음과 같은 장점을 제공한다.

  • 서비스 유닛에서 직접 명령을 정의해 스크립트 관리를 줄일 수 있다
  • 타이머 유닛으로 실행 시점을 유연하게 정의할 수 있다
  • systemctl과 journalctl만으로 상태와 로그를 통합 관리할 수 있다
  • 사용자 단위와 시스템 단위를 모두 지원해 범용적으로 쓸 수 있다

기존에 cron에 익숙하다면 처음에는 다소 낯설 수 있지만, 몇 개만 직접 만들어 보면 곧 구조가 눈에 들어온다. 새로운 서버나 신규 프로젝트에서는 systemd 서비스와 타이머를 기준으로 배치 작업을 설계해 두면, 시간이 지날수록 운영과 디버깅이 훨씬 수월해진다는 점을 체감하게 될 것이다.