Systemd 마스터하기: 첫 번째 사용자 정의 서비스 유닛 파일 만들기

사용자 정의 유닛 파일을 생성하여 Systemd 서비스 관리의 기본을 배웁니다. 이 튜토리얼은 필수 `[Unit]`, `[Service]`, `[Install]` 섹션을 분석하고, `systemctl`을 사용하여 Linux에서 기본 백그라운드 서비스를 정의, 활성화, 시작 및 확인하는 단계별 지침을 제공합니다.

Systemd 마스터하기: 첫 번째 사용자 정의 서비스 유닛 파일 만들기

사용자 정의 systemd 서비스 유닛은 스크립트나 작은 애플리케이션이 터미널 세션, screen 창, 또는 취약한 cron 임시 해결책을 벗어났을 때 사용하는 도구입니다. 충돌 후 재시작해야 하는 작업자가 있을 수 있습니다. 네트워크가 준비된 후에 시작해야 하는 작은 내부 API가 있을 수 있습니다. 백업 스크립트가 제어된 서비스로 실행되어 로그가 저널에 남고 운영자가 다른 모든 것에 사용하는 동일한 systemctl 명령을 사용할 수 있기를 원할 수 있습니다.

systemd의 유용한 점은 유닛 파일이 복잡하다는 것이 아닙니다. 유닛 파일이 프로세스를 명시적으로 만든다는 점입니다: 무엇을 실행하는지, 누구로 실행하는지, 언제 시작하는지, 어떻게 중지하는지, 로그가 어디에 저장되는지, 실패 시 systemd가 무엇을 해야 하는지. 이러한 결정이 기록되면 서비스를 운영하기가 훨씬 쉬워집니다.

이 안내서는 처음부터 작은 서비스를 구축합니다. 예제는 의도적으로 간단하지만, 패턴은 백그라운드 작업자, 큐 소비자, 메트릭 내보내기, 또는 내부 데몬에 사용하는 것과 동일합니다.

유닛 파일이 아닌 실제 명령으로 시작하세요

좋은 서비스 유닛은 이미 수동으로 작동하는 명령으로 시작합니다. systemd 구성을 작성하기 전에 프로그램을 직접 실행할 수 있고 포그라운드에서 무엇을 하는지 이해하는지 확인하세요.

이 예제에서는 작은 리포터 스크립트를 만듭니다:

sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh

다음 내용을 추가하세요:

#!/usr/bin/env bash
set -euo pipefail

while true; do
  echo "$(date --iso-8601=seconds) reporter heartbeat"
  sleep 10
done

실행 가능하게 만들고 테스트하세요:

sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh

몇 줄을 본 후 Ctrl-C로 중지하세요. 스크립트가 /var/log/reporter.log에 직접 추가하는 대신 표준 출력에 쓰는 것을 확인하세요. 이는 의도적입니다. 대부분의 사용자 정의 서비스의 경우, systemd가 stdout과 stderr를 저널에 캡처하도록 하는 것이 모든 스크립트가 자체 로그 파일 권한, 순환 및 실패 동작을 관리하게 하는 것보다 깔끔합니다.

전용 서비스 사용자 생성

애플리케이션 서비스를 루트로 실행하는 것은 실제로 루트 권한이 필요한 경우가 아니라면 피하세요. 하트비트 스크립트는 필요하지 않습니다. 웹 앱은 일반적으로 필요하지 않습니다. 큐에서 읽고 데이터베이스에 쓰는 작업자는 일반적으로 필요하지 않습니다.

잠긴 시스템 사용자를 생성하세요:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter

배포판이 다른 nologin 경로를 사용하는 경우 다음으로 확인하세요:

command -v nologin || command -v false

서비스 사용자는 쓰기 권한이 필요한 파일만 소유해야 합니다. 이 예제에서 스크립트는 systemd를 통해 저널에 쓰므로 /opt/my-custom-service의 소유권이 필요하지 않습니다.

서비스 유닛 작성

사용자 정의 관리자 관리 시스템 유닛은 일반적으로 /etc/systemd/system/에 있습니다. 벤더 패키지 유닛은 일반적으로 배포판에 따라 /usr/lib/systemd/system/ 또는 /lib/systemd/system/ 아래에 있습니다. 가능하면 벤더 유닛 파일을 직접 편집하지 마세요. 자신의 유닛과 재정의를 위한 드롭인에는 /etc/systemd/system/을 사용하세요.

유닛을 생성하세요:

sudo nano /etc/systemd/system/my-reporter.service

실용적인 첫 번째 버전으로 다음을 사용하세요:

[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

[Unit] 섹션은 관계를 설명합니다. After=network-online.target은 순서를 제어합니다. 자체적으로 network-online 대상을 가져오지 않습니다. Wants=network-online.target은 systemd에 해당 대상도 시작하도록 요청합니다. 서비스에 네트워크가 필요하지 않으면 두 줄을 제거하고 유닛을 더 간단하게 유지하세요.

[Service] 섹션은 프로세스를 설명합니다. Type=simple은 자체적으로 백그라운드로 포크하지 않는 포그라운드 프로세스에 적합합니다. 이는 현대 서비스의 일반적인 경우입니다. 레거시 데몬이 포크하고, PID 파일을 작성하고, 제어권을 셸에 반환하는 경우 Type=forking이 필요할 수 있지만, 단어가 더 데몬처럼 들린다고 해서 사용하지 마세요.

ExecStart는 절대 경로여야 합니다. 파이프, 리디렉션 및 &&와 같은 셸 기능은 명시적으로 셸을 실행하지 않는 한 해석되지 않습니다(예: ExecStart=/bin/bash -lc 'command one && command two'). 명령에 셸 로직이 필요한 경우 스크립트를 선호하세요. 테스트하기 쉽고 읽기 쉽습니다.

Restart=on-failure는 systemd에 비정상 종료 후 서비스를 다시 시작하도록 지시합니다. 깨끗한 systemctl stop 후에는 다시 시작되지 않습니다. RestartSec=5s는 빡빡한 재시작 루프가 머신을 강타하는 것을 방지합니다.

여기의 강화 옵션은 적당하지만 유용합니다. NoNewPrivileges=true는 프로세스와 그 자식이 setuid 바이너리 또는 파일 기능을 통해 새로운 권한을 얻는 것을 방지합니다. PrivateTmp=true는 서비스에 개인 /tmp 뷰를 제공합니다. 이는 일반적으로 간단한 서비스에 안전하지만, 일부 소프트웨어는 공유 임시 경로를 예상하므로 실제 애플리케이션으로 테스트하세요.

유닛 로드 및 시작

유닛 파일을 추가하거나 변경한 후 systemd의 관리자 구성을 다시 로드하세요:

sudo systemctl daemon-reload

지금 서비스를 시작하세요:

sudo systemctl start my-reporter.service

상태를 확인하세요:

systemctl status my-reporter.service

Active: active (running)이 표시되어야 합니다. 실패한 경우 추측하지 마세요. 로그를 읽으세요:

journalctl -u my-reporter.service -n 50 --no-pager

테스트 중에 실시간 로그를 따르세요:

journalctl -u my-reporter.service -f

스크립트 경로가 잘못되었거나, 권한이 누락되었거나, 사용자가 존재하지 않거나, 명령이 즉시 종료되는 경우 systemd는 일반적으로 저널에서 명확하게 말합니다.

부팅 시 시작 활성화

서비스 시작과 서비스 활성화는 다른 작업입니다. start는 지금 실행합니다. enable는 부팅 대상에 연결하여 향후 부팅 시 시작되도록 합니다.

sudo systemctl enable my-reporter.service

유닛을 테스트한 후 한 명령으로 둘 다 수행할 수 있습니다:

sudo systemctl enable --now my-reporter.service

활성화되었는지 확인하려면:

systemctl is-enabled my-reporter.service

실패 진단을 더 쉽게 만들기

가장 흔한 첫 서비스 실패는 systemd 옷을 입은 일반적인 Linux 문제입니다.

status=203/EXEC가 표시되면 systemd가 명령을 실행할 수 없었습니다. 경로, 실행 비트, shebang 줄 및 줄 끝을 확인하세요. Windows에서 CRLF 줄 끝으로 복사된 스크립트는 편집기에서 괜찮아 보여도 실패할 수 있습니다.

권한 오류가 표시되면 서비스가 셸 사용자가 아닌 reporter로 실행된다는 것을 기억하세요. 다음으로 테스트하세요:

sudo -u reporter /opt/my-custom-service/reporter.sh

서비스가 시작되고 즉시 중지되면 프로세스가 종료될 가능성이 높습니다. Type=simple은 명령이 계속 실행되기를 기대합니다. 일회성 설정 명령은 simple이 아닌 Type=oneshot을 사용해야 합니다.

로그가 누락된 경우 애플리케이션이 stdout/stderr 대신 파일에 쓰거나 내부적으로 사용자를 변경하는지 확인하세요. 대부분의 작은 서비스의 경우 stdout에 쓰는 것이 가장 덜 놀라운 옵션입니다.

유용한 관리 명령

유닛이 준비되면 일상적인 운영은 간단합니다:

sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart

systemctl cat은 드롭인 재정의가 있는 머신에서 특히 유용합니다. systemd가 읽고 있는 효과적인 유닛 조각을 보여주기 때문입니다.

사용자 정의 유닛 파일은 영리할 필요가 없습니다. 지루하고, 명시적이며, 테스트 가능해야 합니다. 명령을 수동으로 작동시키고, 전용 사용자로 실행하고, 서비스를 정확하게 설명하는 가장 작은 유닛을 작성하고, systemd를 다시 로드하고, 무언가 실패할 때 저널을 사용하세요. 그 워크플로는 장난감 리포터 스크립트에서 실제 프로덕션 데몬까지 확장됩니다.

환경 및 구성을 깔끔하게 추가

조만간 서비스에는 포트, 데이터베이스 URL, 기능 플래그 또는 경로와 같은 구성이 필요합니다. 환경에 따라 달라지는 값을 유닛 파일 내에 묻지 마세요. 일반적인 패턴은 환경 파일입니다:

sudo nano /etc/my-reporter.env

예:

REPORT_INTERVAL=10
REPORT_LABEL=production

민감한 내용이 포함된 경우 파일을 잠그세요:

sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env

그런 다음 유닛에서 참조하세요:

[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh

스크립트에서 기본값으로 변수를 읽으세요:

interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"

비밀의 경우 주의하세요. 환경 변수는 시스템 구성 및 권한에 따라 프로세스 검사 또는 서비스 메타데이터를 통해 노출될 수 있습니다. 매우 민감한 값의 경우 적절한 비밀 관리자, 엄격한 권한이 있는 자격 증명 파일, 또는 배포판이 지원하는 경우 systemd의 최신 자격 증명 기능을 선호하세요. 중요한 습관은 편리하기 때문에 유닛 파일에 암호를 뿌리는 대신 의도적으로 결정하는 것입니다.

로컬 변경을 위해 드롭인 재정의 사용

패키지가 유닛을 설치하고 하나의 설정을 변경해야 하는 경우 벤더 파일을 편집하지 마세요. 드롭인을 사용하세요:

sudo systemctl edit my-reporter.service

그러면 /etc/systemd/system/my-reporter.service.d/ 아래에 재정의 파일이 열립니다. 예:

[Service]
RestartSec=15s

저장 후 다시 로드하고 다시 시작하세요:

sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service

병합된 결과를 확인하세요:

systemctl cat my-reporter.service

드롭인이 중요한 이유는 패키지 업데이트가 벤더 유닛을 대체할 수 있기 때문입니다. /etc의 재정의는 계속 표시되고 의도적입니다.

종료 동작에 대해 생각하기

시작은 수명 주기의 절반일 뿐입니다. 서비스는 또한 깔끔하게 중지되어야 합니다. 기본적으로 systemd는 SIGTERM을 보내고, 기다린 후 프로세스가 종료되지 않으면 SIGKILL을 보낼 수 있습니다. 많은 간단한 서비스의 경우 괜찮습니다. 큐 작업자, 업로드 프로세서 및 데이터베이스 작성자의 경우 프로세스가 현재 작업을 안전하게 완료하거나 포기할 수 있도록 종료를 처리해야 할 수 있습니다.

시간 초과를 조정할 수 있습니다:

[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM

이유 없이 매우 긴 중지 시간 초과를 설정하지 마세요. 긴 종료는 배포, 재부팅 및 인시던트 복구를 느리게 합니다. 작업자는 일반적으로 새 작업 수락을 중지하고, 처리 중인 항목을 완료하고, 제한된 시간 내에 종료해야 합니다.

시끄러운 재시작 루프 방지

Restart=on-failure는 유용하지만, 깨진 서비스는 계속해서 다시 시작될 수 있습니다. 실패 모드가 시끄러울 수 있는 경우 제한을 추가하세요:

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

이것은 systemd에 간격 내에 너무 많은 실패 후 시도를 중지하도록 지시합니다. 문제를 해결할 때 실패 상태를 재설정하세요:

sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service

이 명령은 테스트 중에도 유용합니다. 스크립트나 권한을 수정한 후에도 서비스는 실패 상태로 남을 수 있습니다.

의존하기 전에 유닛 검증

Systemd에는 유용한 검증기가 있습니다:

systemd-analyze verify /etc/systemd/system/my-reporter.service

모든 애플리케이션 문제를 잡지는 못하지만, 구문 오류, systemd 버전에서 알 수 없는 설정 및 일부 순서 문제를 잡을 수 있습니다. 더 큰 편집 후 또는 배포판 간에 유닛을 복사할 때 실행하세요. Systemd 기능은 버전에 따라 다르므로 새 Fedora 서버에서 작동하는 강화 옵션이 오래된 엔터프라이즈 배포판에는 존재하지 않을 수 있습니다.

또한 시작 순서가 혼란스러울 때 유닛의 종속성 보기를 확인하세요:

systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service

첫 번째 명령은 서비스가 가져오거나 의존하는 것을 보여줍니다. 역방향 보기는 무엇이 그것에 의존하는지 보여줍니다. 이는 서비스가 예기치 않게 시작되거나 하나의 유닛을 비활성화할 때 다른 유닛에 영향을 미칠 때 유용합니다.

서비스가 시스템 수준인지 사용자 수준인지 결정

이 문서는 /etc/systemd/system/ 아래의 시스템 서비스를 사용합니다. 이는 부팅 시 시작되고 로그인 세션과 독립적으로 실행되어야 하는 머신 서비스에 올바른 선택입니다. Systemd는 또한 사용자 서비스를 지원하며, 일반적으로 systemctl --user로 관리되며, 사용자별 백그라운드 프로세스용입니다.

sudo를 피하기 위해 인프라 데몬에 사용자 서비스를 사용하지 마세요. 사용자 서비스는 다른 수명 주기 규칙, 환경 처리 및 로그인 동작을 가지고 있습니다. 애플리케이션 작업자, 내보내기 및 호스트 수준 에이전트의 경우 전용 최소 권한 사용자가 있는 시스템 서비스가 일반적으로 추론하기 더 쉽습니다.

첫 번째 유닛을 지루하게 유지

온라인에서 찾은 모든 강화 지시문(개인 장치, 읽기 전용 경로, syscall 필터, 기능 제한, 네임스페이스 제한 등)을 추가하고 싶은 유혹이 있습니다. 이는 귀중한 도구이지만, 서비스가 작동한 후에 한 번에 하나씩 추가하세요. 심하게 제한된 서비스가 시작에 실패할 때 초보자는 종종 유닛이 잘못되었는지, 애플리케이션이 잘못되었는지, 또는 샌드박스 설정이 필요한 파일을 차단했는지 알 수 없습니다.

좋은 프로덕션 경로는 점진적입니다: 작동하는 서비스, 전용 사용자, 신뢰할 수 있는 로깅, 재시작 정책, 기본 강화, 그런 다음 애플리케이션이 여전히 올바르게 동작한다는 것을 증명하는 테스트를 한 후 더 강력한 샌드박싱.