리눅스 스크립트를 서비스로 등록하기: systemd.service의 [Unit], [Service], [Install] 작성 가이드

✍️

🛠️

👤

🖊️ 글을 쓰게 된 이유?

이전에 작성한 글인 리눅스 부팅 후 사용자 명령어 자동 실행하는 법 (without. rc.local)의 내용 보충이 필요해 관련 항목을 정리하다보니 어느세 글을 쓰게되었다.

이전 글에서는 어떠한 방식으로 .service 파일을 생성하여 systemd에 등록하고 활성화 하는지 간략하게 보여주었다면, 이번에는 .service 파일을 생성하는데 있어 [Unit], [Service], [Install]의 각 부분이 어떠한 옵션이 있고 어떠한 상황에서 사용할 수 있는지 간략한 예시와 함께 설명한다.

추가적으로 본 글에서는 간단하게 서비스를 등록하는 방법을 위해 .service의 각 부분에 사용되는 옵션 중 자주 사용되는 옵션만 정리하므로 이 글에서 설명되지 않은 옵션은 man systemd.service 또는 man systemd.unit 명령어나 https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html  또는 https://wiki.archlinux.org/title/Systemd 이 곳을 방문하여 참고 바란다.

🧐 아니 systemd.unit이랑 systemd.service는 무슨 차이지?

잠깐 본격적으로 systemd.service를 설명하기 앞서서 systemd.service의 상위 개념인 systemd.unit에 대하여 설명한다.

아니 이걸 왜 알아야해? 할 수 도 있지만, 잘못된 내용을 기재하여 시스템에 치명적인 대미지를 줄 수 있거나 해당 서비스를 실행하지 못하도록 할 수 있으므로 구분을 위해 알아보자.

본 글에서 작성하려는 systemd.service의 파일 형식 이외에도 위의 다이어그램과 같이 .target .mount .socket 등 다양한 파일 형식으로 systemd 유닛을 정의 할 수 있다. 그리고 이러한 모든 유닛은 systemd.unit이라는 최상위 개념이 포괄하며, 이를 통해 모든 하위 개념의 파일들에는 유닛이라는 항목(섹션)이 기재된다.

그리고 이미지와 같이 systemd.service가 가지는 [Service]에서 사용할 수 있는 옵션은 my-setting.service와 같이 .service 확장자로 설정된 파일에서만 해당 옵션을 [Service] 항목 아래에 기재할 수 있으며, my-settings.service 파일에 갑자기 [Mount] 항목과 옵션을 기재한다면 systemd가 경고를 띄우며 해당 항목을 무시하거나 에러를 일으킬 수 있으므로 주의하자.

또한, 본 글은 systemd.service에 집중하여 다이어그램에서 다른 유닛의 옵션은 그림에서 배제하였다. 각 유닛들도 고유의 항목(Section)과 옵션이 있다.

🖇️ systemd.service의 기본구조

systemd 서비스 파일은 이전 글에서 생성했던 위치와 동일하게, 파일의 위치는 /etc/systemd/system/ 경로에 생성되어야 한다. 또한, 파일 이름은 {서비스 이름}.service와 같이 .service 확장자로 파일 네이밍을 해야한다.

기본적인 systemd.service 파일의 구조는 앞서 말한것과 같이 [Unit], [Service], [Install]로 세 부분으로 나뉘어지며, 한 영역이라도 기입하지 않을시 서비스가 실행되지 않고 journalctl(별도로 변경하지 않았을 경우) 명령어를 통해 실패 로그가 기록되니, 항상 세 부분은 작성해야 한다.

  • [Unit]
    • 서비스 자체에 대한 설명과 의존성 정의를 하는 항목이다.
  • [Service]
    • 실제 실행할 명령어(또는 실행 파일)와 동작 방식을 정의하는 항목이다.
  • [Install]
    • 부팅 시 어느 단계(Target)에서 실행될지 정의 하는 항목이다.

이러한 개념적인 설명보다는 예시를 보고 각 영역에 어떻게 넣어야할지 생각해보자.

📌 예시를 통해 보는 systemd.service 구성하기

[Unit]
Description=Apply custom HDD/SSD sleep settings <- 서비스에 대한 설명을 기재
After=local-fs.target <- 의존성을 기입

[Service]
Type=oneshot <- 어떻게 서비스를 실행할지?
ExecStart=/usr/local/bin/my_settings_on_boot.sh <- 이 서비스를 구성하는 프로그램 또는 스크립트 기재

[Install]
WantedBy=multi-user.target <- 그래서 어느 타겟 시점에서 이 서비스를 실행할지(정확히는 systemctl enable 명령어 실행시 심볼링 링크가 어느 타겟에 걸릴지 정의하는 것의다. 이를 통해 systemctl enable my-service 와 같이 명령어를 기재하면 multi-user.target에 심볼링 링크 위치가 정의 된다.)

이제 각 영역을 떼어내서 어떠한 옵션과 어떻게 사용하는지 다음 예시들과 함께 보자. 대부분의 옵션은 유닛에 속한 모든 것을 전부 활용할 수 있으므로, .service .target .mount .socket 등 다양한 유닛을 함께 사용할 수 있으므로 이점을 꼭 기억하자! (이해하기 어렵다면 아래의 Unit Section의 target과 service가 혼재되어 조건을 불러오는 것을 본다면 이해하기 쉬울 것이다.)

🪄 [Unit] Section

유닛 항목은 기본적으로 대부분의 .service .target .mount .socket 등에서 공통적으로 사용되는 항목이므로 이 점은 인지하자.

[Unit]
// 일반 정보
Description=High Priority Security Service   <- 유닛에 대한 설명
OnFailure=reboot.target    <- 해당 유닛이 죽으면 재시작 타겟으로 넘어가 시스템 리부팅하게 된다.

// 순서 : 서비스 실행 순서 지정
After=network.target    <- network.target 유닛이 시작되고 이후 현재 유닛을 시작한다.
Before=sshd.service    <- sshd.service가 시작되기전에 현재 유닛을 시작한다.

// 종속성 : 해당 유닛(서비스)이 실행되기 위한 필수적 혹은 선호하는 다른 유닛을 지정
Wants=cron.service    <- 단어 그 자체 의미로, 기재한 유닛이 시작되어 있기를 원하는 옵션이며 cron.service가 실패해도 현재 서비스는 계속 시작을 시도한다.
Requires=rsyslog.service    <- 현재 서비스가 시작되기 위해 기재된 유닛이 필수적으로 필요함을 명시한다. rsyslog.service가 시작되어 있지 않았을 경우 이 서비스도 실패한다.
BindsTo=sshd.service    <- sshd.service가 정상/비정상 구분없이 종료되면 현재 유닛도 같이 종료됨을 명시한다.
Conflicts=bypass-security.service    <- bypass-security.service와 동시에 활성화 될 수 없음을 명시한다.

일반 정보나 메타 데이터를 정의하는 지시어

지시어기능
Description=현재 유닛(Service)에 대한 설명을 기재한다.
OnFailure=현재 유닛(Service)이 실패할 경우 활성화할 유닛 목록을 지정한다.

OnFailure= 지시어에 systemd.target 옵션 중 시스템의 전원과 관련된 타겟을 지정했을 경우에는 서비스 설계를 확실히 해야한다. shutdown.taget이나 reboot.target이면 서비스 시작하자마자 시스템이 무한 루프로 자책골죽어버릴 수 있으니 주의하자.

순서를 지정하는 지시어

지시어기능
After=현재 유닛(Service)이 기재된 유닛 이후에 시작되게 한다.
Before=현재 유닛(Service) 기재된 유닛 이전에 시작되게 한다.

순서를 지정하는 지시어의 경우 After=와 Before=를 같이 사용할 수는 있지만 지정한 유닛과 순환 종속성을 가지게 될 경우 현재 유닛(Service)도 같이 루프할 수 있으므로 주의하자.

종속성을 지정하는 지시어

지시어기능
Wants=현재 유닛(Service)이 기재된 유닛과 같이 시작되는 행동이 Requires= 보다 약한 종속성으로 기재된 유닛이 실패해도 현 유닛은 시작을 계속 시도한다.
Requires=현재 유닛(Service)이 기재된 유닛이 시작되지 않았을 경우 같이 시작하지 않는 강한 종속성을 가진 지시어이다.
BindsTo=현재 유닛(Service)이 기재된 유닛과 생명주기를 같이하며, 기재된 유닛이 정상/비정상 구분없이 종료되면 현 유닛도 같이 종료된다.
Conflicts=현재 유닛(Service)이 기재된 유닛과 동시에 활성화 될 수 없다.

종속성 지시어는 다양한 옵션으로 어떻게 현재 유닛(Service)을 다른 유닛과 묶어서 사용할지 지정하는 옵션이다.

🪄 [Service] Section

여기서부터는 [Service] 항목이 단일로 가지는 systemd.service의 고유 지시어이다. 다른 유닛에서는 비슷한 지시어가 있을 수 있지만 항상 각 유닛의 man을 참조하여 작성하도록 하자.

[Service]
// 서비스 유형을 지정하는 지시어
Type=simple    <- 가장 기본이 되는 서비스 지시어. (일반적임)

//
ExecStart=/usr/local/bin/web-service --port 8080    <- 현재 서비스가 시작시 실행할 프로그램의 파일 경로와 인수를 지정한다.
ExecStop=/usr/bin/pkill -f 'web-service'    <- 현재 서비스를 중지시 실행할 명령어를 기재한다.
ExecReload=/usr/bin/kill -HUP 1542    <- 현재 서비스를 다시 불러올 경우 실행할 명령어를 기재한다.
 
//
User=web-serv    <- 현재 서비스를 시작할 사용자를 지정한다.
Group=web-serv    <- 현재 서비스를 시작할 그룹을 지정한다.

Restart=on-failure    <- 현재 서비스가 비정상적으로 죽었을 경우 항상 재시작하게 한다.
RestartSec=10s    <- 현재 서비스의 재시작 시도를 10초 대기하게 한다.

서비스의 유형을 설정하는 지시어

지시어기능
Type=simpleExecStart=에 지정된 명령이 주 프로세스가 되고 문제 없이 실행될 경우 systemd에서 정상적으로 서비스가 시작되었음을 확인한다.
oneshot명령을 딱 한 번 실행하고 성공적으로 종료되면 서비스를 성공한 것으로 간주한다. ExecStart=에 기재한 명령도 단발성으로 실행된다.
forking실행되는 프로세스가 자식 프로세스를 fork하고 부모 프로세스는 종료되는 방식이며, 사용 빈도가 많이 줄어든 옵션이다.

서비스 상태에 따른 명령을 내리는 지시어

지시어기능
ExecStart=서비스 시작시 실행될 프로그램 파일의 경로와 인수를 지정한다.(파일 경로는 절대경로로 기입할걸 추천한다.
ExecStop=서비스 종료시 실행될 프로그램 파일의 경로와 인수를 지정한다.
ExecReload=서비스가 리로드 될 시 프로그램 파일의 경로와 인수를 지정한다.

서비스의 권한을 실행할 사용자 계정 지정 지시어

지시어기능
User=현재 서비스를 시작할 사용자를 지정한다.
Group=현재 서비스를 시작할 그룹을 지정한다.

서비스 재시작시 필요한 정책 지시어

지시어기능
Restart=no서비스의 프로세스가 죽어도 재시작하지 않는다.
always서비스의 프로세스가 정상/비정상으로 종료되었어도 무조건 재시작을 시도한다.
on-failure서비스의 프로세스가 비정상적으로 종료되었을때 재시작을 시도한다.
RestartSec=숫자와 접미사(예시: 5s, 1m, 1d, 100us, 500ms 등)지정한 값만큼 서비스 재시작 시도시 대기한다.

🪄 [Install] Section

[Install]
// systemctl enable 명령어로 시스템에 등록할 때 필요한 정보를 기재한다.
WantedBy=multi-user.target <- multi-user.target.wants 디렉터리에 심볼릭 링크를 생성하여 연결한다.

[Install] 항목의 경우 없어도 해당 서비스를 systemctl start로 시작은 할 수 있다. 단지 systemctl enable로 부팅 시 자동 실행되도록 설정하는 건 불가능하므로 상황에 맞춰 설정해두면 된다.

활성화 대상 지시어

지시어기능
WantedBy=주로 타겟 유닛을 지정하며, 해당 타겟의 디렉터리에 심볼릭 링크를 생성한다.(권장값은 타겟 유닛이지만, 다른 유닛 유형에도 지정할 수 있지만 시스템 불안정을 야기하므로 타겟 유닛만 작성하도록 하자.)

📝 정리하며

이 글에서 정리한 유닛과 항목은 정말 다양하므로 서두에서 언급한것과 같이 온라인 man 페이지나 (systemd를 채용한)리눅스 배포판 상에서 명령어로 man systmed.unit이나 man systemd.service와 같이 입력하면 각 유닛에 대한 설명을 참조 할 수 있다. systemd.service와 관한 서비스를 생성할 때는 본 글을 읽고 충분히 작업할 수 있지만 이외의 유닛의 경우 각기각색의 다양한 옵션을 가지고 있으므로, 해당 내용을 참조하여 systemd를 다룰 수 있기를 바란다.

목차