IM Info
리눅스

systemd 서비스 유닛 파일, 이렇게 만들면 됩니다

kuro editor
5분
systemd 서비스 유닛 파일, 이렇게 만들면 됩니다

부팅할 때마다 터미널을 열어서 python3 myapp.py &를 직접 입력하고 있다면, 그 프로그램은 아직 진짜 “서비스”가 아니에요. 리부팅하면 사라지고, 터미널을 닫으면 죽고, 죽었는지 살았는지도 매번 눈으로 확인해야 하죠. systemd가 리눅스 대부분의 배포판에서 기본 init 시스템으로 자리잡은 지금, 이 문제는 유닛 파일 하나만 작성하면 깔끔하게 끝나요. 그렇다면 systemd 서비스 유닛 파일은 실제로 어떻게 만들고 등록해야 할까요?

systemd 유닛 파일이란 무엇인가

systemd 유닛 파일은 프로세스를 언제, 어떻게 실행하고 관리할지를 systemd에게 알려주는 설정 텍스트 파일이에요. 확장자가 .service인 서비스 유닛이 가장 흔하게 쓰이는데, 실행 경로·실행 순서·실패 시 처리 방식 같은 정보를 선언적으로 적어두면 systemd가 그 규칙대로 프로세스를 관리해줘요. 시작·정지·재시작 로직을 셸 스크립트로 직접 짜야 했던 전통적인 sysvinit 방식과 비교하면, 설정만 채우면 되는 유닛 파일 쪽이 훨씬 간단하고 실수도 적어요. 물론 모든 배포판이 systemd를 쓰는 건 아니라서, systemd 없는 데비안, Devuan처럼 다른 init을 쓰는 계열도 있다는 점은 참고할 만해요.

유닛 파일은 왜 세 섹션으로 나뉘나

유닛 파일이 [Unit], [Service], [Install] 세 섹션으로 나뉘는 이유는 각 섹션이 서로 다른 시점의 관심사를 책임지기 때문이에요. [Unit]은 이 서비스가 무엇이고 다른 유닛과 어떤 순서 관계를 갖는지를, [Service]는 프로세스를 실제로 어떻게 실행·종료할지를, [Install]은 이 서비스를 언제 활성화할지(어떤 target에 묶을지)를 정의해요. After=network.target처럼 쓰면 네트워크가 올라온 뒤에 서비스를 시작하라는 순서 지정이 되고, ExecStart는 실행할 명령어 전체 경로를 적는 자리, Restart=on-failure는 프로세스가 비정상 종료됐을 때 자동으로 다시 살리라는 지시예요. WantedBy=multi-user.target은 이 서비스를 일반적인 다중 사용자 부팅 단계에 포함시키라는 뜻인데, 이 값이 있어야 systemctl enable이 자동 시작 링크를 걸 대상을 알 수 있어요.

직접 만든 스크립트를 서비스로 등록하려면 어떻게 하나

직접 만든 스크립트를 서비스로 등록하려면 유닛 파일을 작성해서 /etc/systemd/system/에 저장하면 돼요. 예를 들어 /opt/myapp/app.py라는 파이썬 스크립트를 부팅 시 자동 실행되는 서비스로 만든다고 하면, 아래처럼 myapp.service 파일을 작성할 수 있어요.

[Unit]
Description=My Custom App Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=on-failure
RestartSec=5
User=myapp
WorkingDirectory=/opt/myapp

[Install]
WantedBy=multi-user.target

여기서 User=myapp은 root가 아닌 별도 계정으로 프로세스를 실행하라는 지정이고, WorkingDirectory는 실행될 때의 작업 디렉터리예요. 보안 관점에서 데몬을 굳이 root로 돌릴 필요가 없다면 전용 계정을 지정하는 편이 안전해요. 파일을 다 작성했으면 sudo systemctl daemon-reload를 실행해서 systemd가 새 유닛 파일을 인식하게 만드는 과정을 잊으면 안 돼요.

systemctl로 서비스를 켜고 확인하는 순서는 어떻게 되나

systemctl로 서비스를 다루는 기본 순서는 시작 확인, 자동 시작 등록, 상태 점검 세 단계예요. sudo systemctl start myapp으로 지금 당장 실행해보고 sudo systemctl status myapp으로 정상적으로 떠 있는지 확인하는 게 첫 단계인데, 여기서 활성(active) 상태와 최근 로그 몇 줄이 함께 출력되기 때문에 실행 직후 문제를 바로 잡아낼 수 있어요. 문제없이 잘 뜬다면 sudo systemctl enable myapp을 실행해서 다음 부팅부터 자동으로 시작되도록 등록하면 돼요. 여기서 끝이 아니에요. 유닛 파일 자체를 고쳤을 때는 daemon-reloadrestart를 함께 실행해야 변경 사항이 실제로 반영된다는 점도 기억해둘 만해요.

서비스가 안 뜰 때는 뭘 먼저 봐야 하나

서비스가 안 뜰 때 가장 먼저 봐야 할 곳은 journalctl -u myapp 로그예요. systemd는 각 서비스의 표준 출력과 에러를 journald로 모아주기 때문에, 이 명령 하나로 프로세스가 왜 죽었는지 대부분 파악할 수 있어요. 흔한 실패 원인은 ExecStart에 적은 경로가 실제 파일 경로와 다르거나, 지정한 User 계정에 실행 권한이 없거나, 스크립트가 참조하는 환경변수가 서비스 실행 환경에는 없는 경우예요. 터미널에서는 잘 되던 스크립트가 서비스로 등록하면 안 되는 경우 대부분은 셸 환경변수(PATH, 홈 디렉터리 등) 차이 때문인데, 필요한 값은 [Service] 섹션에 Environment= 옵션으로 넣어주면 해결돼요. 로그만 제대로 읽으면 유닛 파일 문제의 8~9할은 원인이 바로 드러난다는 점을 기억해두면 디버깅 시간이 크게 줄어요.

마치며

결론부터 말하면 systemd 유닛 파일은 몇 줄 텍스트만으로 프로세스 관리, 자동 재시작, 부팅 자동 실행을 모두 얻을 수 있는 효율적인 도구예요. 세 섹션의 역할만 이해하면 어떤 스크립트든 몇 분 안에 안정적인 서비스로 등록할 수 있고, 문제가 생겨도 journalctl로 원인을 빠르게 추적할 수 있어요. systemd가 리눅스 생태계 전반에 깊게 자리잡게 된 배경이 궁금하다면 리눅스 커널과 배포판의 관계 글도 참고할 만하고, 반대로 systemd 없이도 잘 굴러가는 시스템이 궁금하다면 Void Linux, systemd 없이 살기 글도 비교 삼아 읽어볼 만해요. 이제 터미널을 계속 열어둘 필요 없이, 유닛 파일 하나로 그 스크립트를 진짜 서비스로 만들어보면 어떨까요?

자주 묻는 질문

Q1. 유닛 파일은 어느 디렉터리에 저장해야 하나요?

직접 만든 서비스는 /etc/systemd/system/ 아래에 저장하는 게 표준이에요. /usr/lib/systemd/system/은 배포판 패키지가 설치한 유닛용 디렉터리라서 손대지 않는 게 안전해요.

Q2. 유닛 파일을 수정한 뒤에는 뭘 해야 하나요?

systemctl daemon-reload를 실행해야 해요. systemd는 부팅 시점에 유닛 파일을 캐시해두기 때문에, 파일을 고쳐도 이 명령을 실행하지 않으면 변경 사항이 반영되지 않아요.

Q3. enable과 start의 차이가 뭔가요?

enable은 부팅할 때 자동으로 시작되도록 등록만 하는 명령이고, start는 지금 당장 서비스를 실행하는 명령이에요. 둘은 서로 독립적이라 새 서비스를 테스트할 때는 보통 start로 먼저 확인한 뒤 enable을 걸어요.

Q4. 서비스가 죽었을 때 자동으로 재시작하게 하려면 어떻게 하나요?

[Service] 섹션에 Restart=on-failure 같은 옵션을 추가하면 돼요. RestartSec으로 재시작 대기 시간까지 함께 지정하는 경우가 많아요.

Q5. Type=simple과 Type=forking은 뭐가 다른가요?

Type=simple은 ExecStart로 지정한 프로세스 자체가 메인 프로세스로 계속 살아있는 경우고, Type=forking은 프로세스가 자식을 하나 띄운 뒤 부모는 종료되는 전통적인 데몬 방식이에요.

Q6. 서비스가 왜 실패했는지는 어떻게 확인하나요?

journalctl -u 서비스이름 명령으로 해당 서비스의 로그만 따로 모아 볼 수 있어요. systemctl status 서비스이름으로도 최근 로그 몇 줄과 실패 원인을 바로 확인할 수 있어요.

k
kuro editor 자료 조사하고 분석하고 글을 읽기 쉽게 작성합니다
프로필

댓글

첫 번째 댓글을 남겨보세요!