Search
Duplicate
🍴

[linux] fork 그만, systemd를 이용해 데몬 생성하기

간단소개
initd와 systemd의 차이점, 데몬 생성법에 대해 정리해봤습니다
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Linux
Scrap
태그
9 more properties
last edit 2023.11.25
2015년, 많은 리눅스 배포판들은 init system(pid 1)을 init.d에서 systemd로 전환했다. service 명령 대신 systemctl 명령어를, syslog 대신 journalctl을 사용하는 등 많은 변화가 있었다. 하지만 데비안, RHEL등 보편적인 배포판이 systemd를 서비스 매니저로 채택하고 많은 시간이 흘렀지만, 아직도 많은 자료들은 init.d를 기준으로 설명한다.
그 중 대표적인 것이 스케쥴링 작업이다. 많은 사람들은 일정한 주기마다 실행되어야 하는 작업들을 cron 서비스를 이용해 스케쥴링한다. 매 시간 my-program이라는 프로세스를 실행해야 한다면, 대부분 다음과 같은 설정을 /etc/crontab 파일에 추가했을 것이다.
0 * * * * /usr/bin/my-program
Plain Text
복사
이 작업을 systemd를 이용해 구현해보자. 그리고, systemd를 이용한 구현에 어떤 장점이 있는지 알아보자.
(주: mac os는 2010년 launchd로 전환했다. 클러스터 맥에서 ps -f 1을 실행해서 어떤 init프로세스가 사용되었는지 확인할 수 있다.)

service, daemon

init.dsystemd는 데몬을 관리하는 서비스 매니저이다. cron은 정해진 시간마다 작업을 수행하는 데몬이다.
리눅스 환경에서, 서비스와 데몬은 동치이다. 데몬은 터미널에서 분리되어서 백그라운드에서 작업하는 프로세스를 말한다. 데몬 프로세스는 표준입력과 표준출력을 이용해 사용자와 상호작용할 수 없고, 서비스 매니저의 하위 프로세스이다.
터미널에서 분리되어서라는 말에 주목하자. 데몬 프로세스는 소켓 또는 파일을 통해 다른 프로세스와 상호작용하고, 서비스 매니저가 보내는 시그널(SIGTERM)로 제어된다. 터미널에서 잘못된 시그널이 입력되어 데몬이 정지되는 것을 막기 위해 데몬 프로세스는 터미널(tty)에서 분리되어야 하는 것이다.
전통적인 init.d에 의해 시작되는 서비스를 SysV daemon이라고 한다.
Linux man page(daemon(7))는 SysV daemon을 만들기 위해 다음 과정을 거쳐야 한다고 설명한다.
주목할 점은 double fork 과정을 거쳐 데몬을 생성하는 것이다. 이는 Unix Network Programming에 나온 방법으로, setsid를 이용해 터미널에서 분리시키기 위한 기법이다. 유닉스의 프로세스는 sesion, process group 속성을 갖는다.
이때 session별로 제어 터미널을 가지므로, 데몬 프로세스가 새로운 세션에서 실행된다면 이 세션을 tty가 아닌 제어 터미널로 지정할 수 있다. 따라서 setsid를 사용하기 위해 double fork 과정을 거쳐 세션을 분리한다. 또한, (fork된) 자식 프로세스보다 부모 프로세스가 먼저 종료되면 PPID가 자동적으로 1(init.d)로 지정되어 init.d 서비스 매니저의 PGID에 속하게 되고, 서비스 매니저가 데몬에 시그널을 보내 제어할 수 있게 된다. (자세한 설명)
그런데 서비스 매니저인 init.d는 최초로 실행한 프로세스의 PID만 알 수 있을 뿐, 이렇게 두 번 fork해서 만들어진 데몬 프로세스의 PID는 알 수 없다. 따라서 init.d가 데몬 프로세스를 제어할 수 있도록 별도의 파일에 PID를 기록하는 과정이 필요하다.(주로 /run 밑에 저장)
PID 파일을 기준으로 데몬의 상태를 확인하기 때문에, PID 파일 접근 race-free해야 하며 이미 PID 파일이 존재할 경우 데몬을 추가로 실행하지 않는다는 점도 흥미로운 부분이다. PEP 3143에 제안된 파이썬 구현은 PID 파일에 lock을 구현하고 데몬 종료시 이 lock을 해제하면서 파일을 삭제하는 방식으로 구현되어 있다.
그런데 systemd를 사용하는 시스템에서 데몬(aka new-style daemon)을 만들 때에는 이러한 사항들을 고려하지 않아도 된다. systemd가 데몬 프로세스를 실행하는 과정에서 위 사항들을 처리해준다. SysV 방식을 사용하는 시스템에서 사용할 것이 아니면, 굳이 데몬이 fork하고 SID를 설정하는 과정을 거칠 필요가 없다.
또한, init.d를 이용한 실행은 항상 daemon 사용자로 실행되지만 systemd를 통한 실행은 데몬을 시작한 사용자를 지정할 수 있다.

systemd

systemdinit.d를 대체하는 서비스 매니저이다. systemdinit.d와의(SysV Unix와의) 호환성을 잃지 않으면서도, 다양한 기능을 추가했다. 큰 차이점 중 하나는 단순히 fork와 PGID를 이용한 관리만 담당했던 init.d와 달리, systemd는 networkd, udevd등의 데몬을 내장하면서 조건에 따른 실행과 데몬 간의 의존성 관계를 설정할 수 있게 되었다는 것이다. 특정 서비스 실행 이후에 서비스가 실행되도록 관계를 설정할 수도 있고, 의존성이 없는 unit들은 병렬로 실행하여 부팅 시간을 줄인다는 장점이 있다.
(주: systemd는 부팅 단계에서 시작되는 데몬과, 유저 로그인시 시작되는 데몬으로 구분된다. Ubuntu 22.04 버전을 기준으로, system systemd의 unit 파일은 /lib/systemd/system 하위에, user systemd의 unit 파일은 ~/.config/systemd/user에 저장된다. 이 글에서는 부팅 단계에서 시작되는 데몬 (= system systemd)을 다룬다. )

systemd.service

systemd는 unit 단위로 구성된다. 11개의 unit 종류가 있지만(service, timer, target, device등), 이 글에서는 servicetimer 유닛을 다룬다.
각각의 unit 파일은 섹션으로 구분되고, 섹션에는 key=vaule 형식의 설정값이 들어간다. 모든 유닛이 공통으로 가지는 섹션도 있고, 유닛 종류별로 특별하게 가지는 섹션도 있다. 각 섹션에 대한 설명은 systemd.unit(5), systemd.service(5) 등의 manpage를 참고하면 된다.
systemd의 특징은 유닛 사이의 관계에 따라 실행 흐름을 정할 수 있다는 것이다. 두 유닛이 중복으로 실행되지 않도록(Conflict 옵션) 만들거나, 특정 유닛 시작 이후에 유닛이 실행되도록(After 옵션) 만들 수 있다. 데몬을 만들기 위해 사용되는 유닛은 service 유닛이다. 일반적으로 데몬은 시스템 모듈들이 로딩된 후 ~ 유저가 로그인하기 전에 실행되기를 원한다. 부팅 과정에서 실행되는 유닛을 참고하여 유저가 로그인하기 직전(runlevel 2)에 /usr/bin/42service를 실행하는 서비스를 작성해보자.
[Unit] Description=42 service Wants=network-online.target After=basic.target [Service] WorkingDirectory=/home/kyungjle/ User=kyungjle Type=simple ExecStart=/usr/bin/42service [Install] WantedBy=multi-user.target
Plain Text
복사
이 파일을 /lib/systemd/system/42service.service로 저장하고, systemctl daemon-reload - systemctl enable 42.service - systemctl start 42.service 순으로 명령을 실행하면 데몬이 등록되고 실행되는 것을 볼 수 있다.

systemd.timer

cron을 대체하여 데몬을 실행시키는 오늘의 주인공이다. timer unit은 지정된 간격마다 특정 unit을 실행한다. (unit이 지정되어 있지 않으면 동명의 서비스를 실행)
timer unit들은 cron의 crontab파일과 유사하다. 다만 특정 조건에서만 실행되게 하거나, 둘 중 하나의 데몬만 활성화 가능하게 하는 등 cron에서는 불가능한 다양한 설정들이 가능하다. OnBootSecOnUnitActiveSec 등을 조합하면 부팅 후 특정 간격마다 서비스를 활성화할 수 있다. OnCalender를 사용하면 기존 cron과 유사한 방식으로 스케쥴을 짤 수 있다.
다음 유닛 파일은 매시 정각에 서비스를 실행한다.
[Unit] Description=42service timer [Timer] OnCalendar=*-*-* *:00:00 AccuracySec=1s [Install] WantedBy=timers.target
Plain Text
복사
하나의 조건만 있을 필요는 없다. 여러 조건을 중첩해서 적용할 수도 있다.
[Unit] Description=42service timer [Timer] OnBootSec=30s OnCalendar=*-*-* *:00:00 AccuracySec=1s [Install] WantedBy=timers.target
Plain Text
복사
이 파일을 서비스 파일이 있는 디렉토리에 같은 이름 (확장자만 .timer로)으로 저장하고 systemctl로 서비스를 활성화하면 곧바로 적용된다. 타이머 실행 정보는 systemctl list-timers로 최근 실행시간과 다음 실행시간을 확인할 수 있다.