포스트

systemd 서비스 관리: systemctl, 유닛 파일, 부팅 등록과 타이머

프로세스를 systemd 서비스로 관리하는 흐름을 start/enable, unit dependency, ExecStart, journalctl, timer 기준으로 정리한다. network.target과 network-online.target의 경계도 구분한다.

systemd 서비스 관리: systemctl, 유닛 파일, 부팅 등록과 타이머

리눅스 로드맵3단계 — 서비스 글이다.

터미널에서 직접 띄운 프로세스를 장기 운영하려면 누가 시작하고, 실패하면 어떻게 재시작하고, 부팅 시 언제 올라오며, 로그를 어디서 볼지를 관리해야 한다. systemd service unit은 이 수명 주기를 선언적으로 관리하는 대표적인 방법이다.

startenable은 다른 축이다

1
2
sudo systemctl start nginx
sudo systemctl enable nginx
  • start — 지금 이 순간 unit을 시작한다.
  • enable — unit file의 [Install] 정보에 따라 부팅 target 등에서 시작될 수 있도록 symlink를 만든다.

enable현재 프로세스를 실행하는 명령이 아니다.

둘을 한 번에 하려면:

1
sudo systemctl enable --now nginx

반대로:

1
sudo systemctl disable --now nginx

은 disable과 stop을 함께 수행한다.

일상적으로 확인할 명령

1
2
3
4
5
6
systemctl status nginx
systemctl is-active nginx
systemctl is-enabled nginx
systemctl --failed
journalctl -u nginx -e
journalctl -u nginx -f

statusActive:는 현재 실행 상태, is-enabled는 enable 상태를 본다. 둘을 같은 의미로 보지 않는다.

unit file 기본 구조

예를 들어 /etc/systemd/system/myapp.service:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=My App
After=network.target

[Service]
Type=simple
User=appsvc
ExecStart=/usr/local/bin/myapp
Restart=on-failure

[Install]
WantedBy=multi-user.target

각 section의 질문은 다르다.

1
2
3
4
5
6
7
8
[Unit]
→ 다른 unit과 어떤 관계·순서를 가지는가

[Service]
→ 어떤 프로세스를 어떤 방식으로 실행하는가

[Install]
→ enable할 때 어느 target 등에 연결되는가

After=는 의존성 자체가 아니다

After=network.target시작 순서(ordering)를 지정한다. network.target을 반드시 끌어오거나 네트워크 연결이 실제 사용 가능할 때까지 기다린다는 뜻은 아니다.

1
2
3
4
5
After=
→ 순서 관계

Wants= / Requires=
→ requirement dependency

외부 네트워크가 실제 준비된 뒤에만 시작해야 하는 서비스라면 배포판의 network management 구성에 따라 network-online.target과 해당 wait-online service를 검토한다.

예:

1
2
3
[Unit]
Wants=network-online.target
After=network-online.target

다만 모든 daemon에 무조건 network-online.target을 붙이는 것도 좋은 기본값은 아니다. 가능한 서비스는 네트워크 상태 변화에 동적으로 대응하는 편이 부팅 지연과 결합을 줄일 수 있다.

ExecStart= 실행 파일 경로

ExecStart=의 첫 실행 항목은 절대경로 또는 slash가 없는 단순 실행 파일명을 사용할 수 있다.

1
ExecStart=/usr/local/bin/myapp

또는 standard binary search path에 있는 명령이라면:

1
ExecStart=echo hello

같은 형태가 가능하다. 단순 실행 파일명은 systemd가 고정된 binary search path에서 찾는다. 현재 기본 검색 경로는 다음으로 확인할 수 있다.

1
systemd-path search-binaries-default

따라서 “ExecStart는 무조건 절대경로”라고 외우기보다 사용자 shell의 $PATH를 그대로 기대하지 않는다가 더 정확한 원칙이다. 프로젝트 전용 executable은 절대경로를 쓰는 편이 명확하다.

unit file을 바꿨다면 daemon-reload

unit 정의를 만들거나 수정한 뒤에는 manager가 파일을 다시 읽게 한다.

1
2
sudo systemctl daemon-reload
sudo systemctl restart myapp

daemon-reload와 서비스 자체의 reload는 다른 동작이다.

1
2
3
4
5
systemctl daemon-reload
→ systemd manager가 unit file을 다시 읽음

systemctl reload myapp
→ myapp이 지원하는 reload 동작을 요청

모든 서비스가 reload를 지원하는 것은 아니다. 지원 여부는 unit 정의와 서비스 자체 동작을 확인한다.

restartreload

1
2
sudo systemctl restart nginx
sudo systemctl reload nginx

restart는 unit을 다시 시작한다. reload는 unit에 정의된 reload 동작이 있을 때 서비스 설정을 재로딩한다.

따라서

1
연결을 끊기 싫으면 무조건 reload부터

처럼 일반화하지 않는다. 먼저 해당 서비스가 reload를 지원하는지, reload로 어떤 설정까지 반영되는지 확인한다.

service account

가능하면 애플리케이션을 필요 이상의 root 권한으로 실행하지 않는다.

1
2
3
[Service]
User=appsvc
Group=appsvc

필요한 파일·socket·port·capability에 맞춰 최소 권한을 설계한다. 단순히 User=를 넣는 것만으로 전체 hardening이 끝나는 것은 아니다.

재시작 정책

1
Restart=on-failure

대표 값:

  • no — 자동 재시작하지 않음
  • on-failure — 실패 조건에서 재시작
  • always — 종료 이유와 무관하게 재시작

실제 운영에서는 RestartSec=, start-limit 관련 설정과 함께 재시작 루프를 고려해야 한다.

로그 — journal

service stdout/stderr가 journal로 연결된 구성에서는 다음처럼 본다.

1
2
3
4
journalctl -u myapp
journalctl -u myapp -e
journalctl -u myapp -f
journalctl -u myapp --since "10 min ago"

systemctl status는 상태와 일부 최근 로그를 빠르게 보는 용도이고, 상세 조사는 journalctl로 내려간다.

timer — 주기 작업

systemd timer는 무엇을 실행할지(service)언제 실행할지(timer)를 나눈다.

backup.service:

1
2
3
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh

backup.timer:

1
2
3
4
5
6
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true

[Install]
WantedBy=timers.target

활성화:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers

Persistent=true는 timer가 비활성 상태였던 동안 놓친 일정이 있을 때 재활성화 후 실행을 보충할 수 있게 한다. 세부 동작은 timer 종류와 마지막 trigger 상태를 함께 확인한다.

cron과 timer는 서로 완전한 상하 대체재가 아니다.

1
2
3
4
5
cron
→ 단순한 시간 기반 주기 실행에 가벼움

systemd timer
→ service lifecycle, dependency, journal과 함께 관리하기 좋음

운영 확인 순서

서비스가 뜨지 않을 때는 무작정 restart를 반복하기보다 층을 나눈다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. unit이 존재하는가
   systemctl cat myapp

2. manager가 최신 unit을 읽었는가
   daemon-reload 여부

3. 시작 자체가 실패했는가
   systemctl status myapp

4. 실제 오류는 무엇인가
   journalctl -u myapp -e

5. 실행 사용자·파일 권한·경로가 맞는가

6. dependency/order 조건이 맞는가

기억할 경계

1
2
3
4
5
enable ≠ start
After ≠ Requires
network.target ≠ network-online.target
manager daemon-reload ≠ service reload
systemd 실행 환경 ≠ interactive shell 환경

이 다섯 경계를 잡으면 systemd 초반 혼동이 크게 줄어든다.

Reference

  • man systemd.service
  • man systemd.unit
  • man systemd.timer
  • man systemd.special
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.