
서비스 · 프로세스 관리 36 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
"오늘 밤 11시에 한 번만 서비스를 재시작해 주세요." 이런 일회성 작업에 cron을 쓰면 실행 후 등록을 지우는 것을 잊기 쉽다. 이럴 때 쓰는 도구가 at 이다. at은 지정한 시각에 딱 한 번 명령을 실행하고 스스로 정리된다.
이번 글에서는 at 작업을 예약·확인·삭제하는 방법과, at이 예약 당시의 환경을 스크립트로 저장 하는 구조를 확인한다. 그리고 부하가 낮을 때 실행하는 batch, 스풀 위치와 접근 제어, 예약 작업 점검에서 at을 빠뜨리지 않는 방법을 정리한다.
| 명령 | 동작 |
|---|---|
at TIME | 표준입력의 명령을 TIME에 실행하도록 예약 |
at -f script TIME | 파일 내용을 예약 |
batch | 시스템 부하가 낮아지면 실행 |
atq (at -l) | 대기 작업 목록 (root는 전체 사용자) |
at -c N | 작업 N의 전체 스크립트 출력 |
atrm N | 작업 삭제 |
| 표현 | 의미 |
|---|---|
now + 5 minutes / + 2 hours / + 1 day | 상대 시각 |
23:50 | 오늘 23:50 (이미 지났으면 내일) |
02:00 tomorrow, noon, midnight | 이름 있는 시각 |
10:00 2026-10-01 | 날짜 지정 |
| 항목 | at | cron | systemd-run --on-active (35편) |
|---|---|---|---|
| 실행 횟수 | 1회 | 반복 | 1회 |
| 실행 환경 | 예약 당시 환경 저장 | 최소 환경 | 서비스 환경 |
| 실행 후 | 스풀 파일 삭제 | 등록 유지 | 임시 unit 삭제 |
| 결과 기록 | atd 시작 로그 (출력은 메일) | CMD 로그 | journal에 출력까지 |

echo '명령' | at now + 1 minute
→ at: 현재 셸의 환경변수 · 작업 디렉터리 · umask 를 읽어 /bin/sh 스크립트 생성
→ 스풀 디렉터리에 작업 파일 저장 (소유자 = 예약한 사용자, 권한 700)
→ atd: 시각이 되면 해당 사용자로 스크립트 실행 → 완료 후 파일 삭제
cron이 최소 환경(33편)으로 실행되는 것과 달리, at은 예약한 셸의 환경을 재현 한다. 그래서 대화형 셸에서 되던 명령이 at에서도 대부분 그대로 동작한다. 반대로 말하면 예약 당시의 환경변수(PATH 등)가 스풀 파일에 그대로 남는다.
# 1) (Rocky, analyst) 예약 · 목록 · 내용 확인 · 실행 · 삭제
echo 'id -un > ~/at-result.txt; date +%T >> ~/at-result.txt' | at now + 1 minute
echo 'echo second job' | at 23:50
atq
at -c $(atq | sort -n | head -1 | cut -f1) | tail -5
at -c $(atq | sort -n | head -1 | cut -f1) | grep -cE '^[A-Z_]+=.*; export' # 저장된 환경변수 수
sleep $((63 - $(date +%S))); cat ~/at-result.txt; atq
atrm $(atq | cut -f1); atq | wc -l
# 2) (Rocky, root) 스풀 파일과 접근 제어, 실행 로그
echo 'sleep 1' | su - analyst -c 'at now + 2 hours'
ls -l /var/spool/at/ | head -4
ls -l /etc/at.allow /etc/at.deny
journalctl -u atd --since "-5min" --no-pager -o cat | tail -3
atq; atrm $(atq | cut -f1)
# 3) (Ubuntu) batch 와 스풀 위치
echo 'uptime > /tmp/batch.txt' | batch; sleep 3; cat /tmp/batch.txt
ls -la /var/spool/cron/atjobs/ | head -4



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ echo 'id -un > ~/at-result.txt; date +%T >> ~/at-result.txt' | at now + 1 minute 2>&1
warning: commands will be executed using /bin/sh
job 1 at Thu Sep 24 12:32:00 2026
[analyst@rocky9-lab ~]$ echo 'echo second job' | at 23:50 2>&1
warning: commands will be executed using /bin/sh
job 2 at Thu Sep 24 23:50:00 2026
[analyst@rocky9-lab ~]$ atq
1 Thu Sep 24 12:32:00 2026 a analyst
2 Thu Sep 24 23:50:00 2026 a analyst
[analyst@rocky9-lab ~]$ at -c $(atq | sort -n | head -1 | cut -f1) | tail -5
}
${SHELL:-/bin/sh} << 'marcinDELIMITER19cf8baa'
id -un > ~/at-result.txt; date +%T >> ~/at-result.txt
marcinDELIMITER19cf8baa
[analyst@rocky9-lab ~]$ at -c $(atq | sort -n | head -1 | cut -f1) | grep -cE '^[A-Z_]+=.*; export'
25
[analyst@rocky9-lab ~]$ sleep $((63 - $(date +%S))); cat ~/at-result.txt; atq
analyst
12:32:00
2 Thu Sep 24 23:50:00 2026 a analyst
[analyst@rocky9-lab ~]$ atrm $(atq | cut -f1); atq | wc -l
0
[root@rocky9-lab ~]# echo 'sleep 1' | su - analyst -c 'at now + 2 hours' 2>&1
warning: commands will be executed using /bin/sh
job 3 at Thu Sep 24 14:32:00 2026
[root@rocky9-lab ~]# ls -l /var/spool/at/ | head -4
total 8
-rwx------ 1 analyst analyst 856 Sep 24 12:32 a0000301c74968
drwx------ 1 root root 4096 Sep 24 12:32 spool
[root@rocky9-lab ~]# ls -l /etc/at.allow /etc/at.deny 2>&1; cat /etc/at.deny | head -3
ls: cannot access '/etc/at.allow': No such file or directory
-rw-r--r-- 1 root root 1 Nov 11 2025 /etc/at.deny
[root@rocky9-lab ~]# journalctl -u atd --since "-5min" --no-pager -o cat | tail -3
Starting job 1 (a0000101c748f0) for user 'analyst' (1000)
pam_unix(atd:session): session opened for user analyst(uid=1000) by analyst(uid=0)
pam_unix(atd:session): session closed for user analyst
[root@rocky9-lab ~]# atq; atrm $(atq | cut -f1)
3 Thu Sep 24 14:32:00 2026 a analyst
root@ubuntu-lab:~# echo 'uptime > /tmp/batch.txt' | batch 2>&1; sleep 3; cat /tmp/batch.txt
warning: commands will be executed using /bin/sh
job 1 at Thu Sep 24 12:32:00 2026
12:32:04 up 44 min, 1 user, load average: 0.00, 0.01, 0.02
root@ubuntu-lab:~# ls -la /var/spool/cron/atjobs/ | head -4
total 20
drwxrwx--T 1 daemon daemon 4096 Sep 24 12:32 .
drwxr-xr-x 1 root root 4096 Sep 24 09:32 ..
-rw------- 1 daemon daemon 6 Sep 24 12:32 .SEQ
| 관찰 | 의미 |
|---|---|
warning: commands will be executed using /bin/sh | at 작업은 로그인 셸이 아니라 /bin/sh로 실행된다 |
job 1 at Thu Sep 24 12:32:00 2026 | now + 1 minute은 다음 분의 0초 로 맞춰진다 |
atq: 작업 번호 · 실행 시각 · 큐(a) · 사용자 | 대기 작업 목록. root로 보면 모든 사용자의 작업이 보인다 |
at -c 끝부분: heredoc 안에 원래 명령 | at이 만든 스크립트의 마지막에 예약한 명령이 들어 있다 |
| 저장된 환경변수 25개 | 예약 당시 셸의 환경변수가 export 형태로 스풀 파일에 저장되었다 |
결과 analyst, 12:32:00 후 atq에서 사라짐 | 예약한 사용자 권한으로 정시에 실행되고 대기 목록에서 제거되었다 |
스풀 파일 a0000301c74968 (analyst 소유, -rwx------) | 작업 파일은 예약한 사용자 소유, 본인만 접근 가능하다 |
at.allow 없음, at.deny 존재 | allow가 없으므로 deny에 없는 모든 사용자가 at을 쓸 수 있다 |
atd 로그 Starting job 1 (...) for user 'analyst' (1000) | 실행 시작 기록. 명령 내용은 남지 않는다 |
Ubuntu batch → 즉시 uptime 결과 | 부하가 낮아 바로 실행되었다 |
Ubuntu 스풀 /var/spool/cron/atjobs/, .SEQ | 배포판마다 스풀 위치가 다르다. .SEQ는 다음 작업 번호 |
| 주제 | 내용 |
|---|---|
| 점검 누락 | 예약 작업 점검을 crontab만으로 끝내면 at 작업을 놓친다. 39편의 자동 실행 경로 점검에 atq를 포함한다 |
| 사용 제한 | at이 필요 없는 서버는 atd를 비활성화하거나 /etc/at.allow로 허용 계정만 지정한다 |
| 환경 노출 | 스풀 파일에 예약 당시 환경변수가 저장된다. 환경변수에 토큰·비밀번호를 두는 습관은 여기서도 노출 위험이 된다 |
| 기록의 한계 | atd 로그에는 "누가 언제 실행했는가"만 남는다. 실행 내용은 스풀 파일이 삭제되기 전에 at -c로 확보해야 한다 |
[정기 점검] atq ← 대기 작업 (root 로 실행)
ls -la /var/spool/at/ (Ubuntu: /var/spool/cron/atjobs/)
systemctl is-enabled atd ; ls -l /etc/at.allow /etc/at.deny
↓
[작업 확인] at -c <번호> | tail -20 ← 실제 실행될 명령
↓
[판단] 변경 관리(작업 요청) 기록에 없는 예약 · 서비스 계정의 예약 → 담당자 확인
↓
[기록] 실행 이력: journalctl -u atd --since ... -o short-iso
| 점검 항목 | 기준 |
|---|---|
| atd 필요 여부 | 사용하지 않으면 systemctl disable --now atd |
| 허용 계정 | /etc/at.allow에 운영 계정만 |
| 대기 작업 | 모든 작업에 요청자·목적이 설명되는가 |
| 실수 | 결과 | 예방 |
|---|---|---|
| 명령의 출력을 리다이렉션하지 않음 | 결과를 볼 수 없음 (메일로 감) | > 파일 2>&1 |
now + 1 minute을 정확히 60초 후로 생각 | 다음 분 0초에 실행 | 필요한 시각을 명시 |
| 예약자와 다른 계정으로 atq 확인 | 작업이 안 보임 | root로 확인 |
| atd가 꺼진 서버에 예약 | 실행되지 않음 | systemctl is-active atd 확인 |
| 반복 작업을 at으로 재예약 | 관리가 흩어짐 | 반복은 cron·timer |
[ ] at now + 1 minute 로 작업을 예약하고 실행을 확인했다
[ ] atq, at -c, atrm 으로 작업을 관리했다
[ ] at 스풀 파일에 환경변수가 저장되는 것을 확인했다
[ ] 스풀 파일 위치와 권한을 Rocky/Ubuntu 에서 확인했다
[ ] at.allow / at.deny 규칙을 설명할 수 있다
[ ] batch 로 부하 기반 실행을 확인했다
/var/spool/at/, Ubuntu /var/spool/cron/atjobs/이다.at -c로 확인한다.다음 글 「37. 소켓 활성화 (socket unit)」 에서는 16·22편에서 본 "systemd가 22번 포트를 쥐고 있는" 구조를 직접 만든다. 연결이 들어올 때만 서비스를 실행하는 socket unit을 작성하고, Ubuntu의 ssh.socket이 어떻게 동작하는지 확인한다.