사용자 지정 Systemd 유닛 파일 효과적으로 작성 및 관리하는 방법
최신 Linux 배포판은 초기화 시스템 및 서비스 관리자로 주로 systemd를 사용합니다. systemd를 이해하는 것은 안정적으로 애플리케이션을 배포하고 관리해야 하는 모든 Linux 시스템 관리자 또는 개발자에게 중요합니다. 많은 애플리케이션에 미리 빌드된 systemd 유닛 파일이 포함되어 있지만, 사용자 지정 유닛 파일을 작성하는 기능은 자체 애플리케이션, 스크립트 또는 사용자 지정 프로세스의 시작, 종료 및 일반적인 수명 주기 관리를 표준화할 수 있게 해줍니다.
이 문서는 사용자 지정 systemd .service 유닛 파일을 생성, 구성 및 관리하는 과정을 안내할 것입니다. 애플리케이션이 실행되는 방식을 정의하고, 종속성을 설정하며, 견고한 작동을 보장하는 필수 지시문을 탐색할 것입니다. 이 문서를 마치면, 사용자 지정 서비스를 Linux 운영 체제에 원활하게 통합하여 부팅 시 자동으로 시작하고, 실패 시 다시 시작하며, systemctl을 사용하여 쉽게 관리할 수 있게 될 것입니다.
사용자 지정 systemd 유닛 파일을 마스터하면 서비스에 대한 세부적인 제어가 가능해지고, 시스템 안정성이 향상되며, 관리 작업이 단순해집니다. 이제 전문가처럼 애플리케이션을 관리하는 데 필요한 핵심 구성 요소와 실질적인 단계를 살펴보겠습니다.
Systemd 유닛 파일 이해하기
Systemd는 구성 파일로 정의되는 유닛으로 알려진 다양한 시스템 리소스를 관리합니다. 이러한 유닛에는 서비스(.service), 마운트 지점(.mount), 장치(.device), 소켓(.socket) 등이 포함됩니다. 애플리케이션 및 백그라운드 프로세스 관리를 위해 .service 유닛 유형이 가장 일반적이며 관련성이 높습니다.
Systemd 유닛 파일은 일반적으로 특정 디렉터리에 저장되는 일반 텍스트 파일입니다. 우선순위 순서대로 주요 위치는 다음과 같습니다:
/etc/systemd/system/: 시스템 기본값보다 우선하며 시스템 업데이트 시에도 유지되므로, 사용자 지정 유닛 파일 및 재정의를 위한 권장 위치입니다./run/systemd/system/: 런타임에 생성된 유닛 파일에 사용됩니다./usr/lib/systemd/system/: 설치된 패키지에서 제공하는 유닛 파일이 포함됩니다. 이 디렉터리의 파일을 직접 수정하지 마십시오.
사용자 지정 유닛 파일을 /etc/systemd/system/에 배치하면 systemd에 의해 제대로 인식되고 관리됩니다.
.service 유닛 파일의 구조
systemd .service 유닛 파일은 [SectionName]으로 표시되는 여러 섹션으로 구성되며, 각 섹션에는 다양한 지시문(키-값 쌍)이 포함됩니다. 서비스 유닛의 세 가지 주요 섹션은 [Unit], [Service], [Install]입니다.
사용할 가장 중요한 지시문을 살펴보겠습니다:
[Unit] 섹션
이 섹션은 유닛, 해당 설명 및 종속성에 대한 일반적인 옵션을 포함합니다.
Description: 서비스에 대한 사람이 읽을 수 있는 문자열입니다.systemctl status출력에 나타납니다.
ini Description=My Custom Python Web ApplicationDocumentation: 서비스 문서로 연결되는 URL (선택 사항)입니다.
ini Documentation=https://example.com/docs/my-appAfter: 이 유닛이 나열된 유닛들 이후에 시작되어야 함을 지정합니다. 이는 시작 순서를 관리하는 데 도움이 됩니다. 웹 애플리케이션의 경우 네트워크가 작동 중인지 확인하고 싶을 수 있습니다.
ini After=network.targetRequires:After와 유사하지만 더 강력한 종속성을 의미합니다. 필요한 유닛이 실패하면 이 유닛은 시작되지 않거나 중지됩니다.
ini Requires=docker.serviceWants:Requires보다 약한 형태입니다. 원하는 유닛이 실패하거나 찾을 수 없어도 이 유닛은 여전히 시작을 시도합니다. 일반적으로 중요하지 않은 종속성에 대해서는Requires보다 선호됩니다.
ini Wants=syslog.target
[Service] 섹션
이 섹션은 서비스가 시작, 중지 및 작동하는 방식을 포함하여 서비스의 실행 매개변수를 정의합니다.
-
Type: 프로세스 시작 유형을 정의합니다. systemd가 서비스를 모니터링하는 방식에 중요합니다.simple(기본값):ExecStart명령이 서비스의 주 프로세스입니다. systemd는ExecStart가 호출된 직후 서비스가 시작된 것으로 간주합니다. 프로세스가 포그라운드에서 무기한 실행될 것으로 예상합니다.forking:ExecStart명령이 자식 프로세스를 포크하고 부모는 종료됩니다. systemd는 부모 프로세스가 종료되면 서비스가 시작된 것으로 간주합니다. 애플리케이션이 스스로 데몬화하는 경우에 사용합니다.oneshot:ExecStart명령은 작업이 완료되면 종료되는 일회성 프로세스입니다. 작업을 수행하고 종료되는 스크립트에 유용합니다 (예: 백업 스크립트).notify:simple과 유사하지만, 서비스가 준비되면 systemd에 알림을 보냅니다.libsystemd-dev와 애플리케이션에 특정 코드가 필요합니다.idle:ExecStart명령은 모든 작업이 완료된 후에만 실행되며, 시스템이 대부분 유휴 상태가 될 때까지 실행을 지연시킵니다.
ini Type=simple -
ExecStart: 서비스가 시작될 때 실행할 명령입니다. 이 섹션에서 가장 중요한 지시문입니다. 항상 실행 파일 또는 스크립트의 절대 경로를 사용하십시오.
ini ExecStart=/usr/bin/python3 /opt/my_app/app.py ExecStop: 서비스가 중지될 때 실행할 명령 (선택 사항)입니다. 지정하지 않으면 systemd는 프로세스에SIGTERM을 보냅니다.
ini ExecStop=/usr/bin/pkill -f 'my_app/app.py'ExecReload: 서비스의 구성을 다시 로드하기 위해 실행할 명령 (선택 사항)입니다.
ini ExecReload=/bin/kill -HUP $MAINPIDUser: 서비스 프로세스가 실행될 사용자 계정입니다. 보안에 필수적이며, 절대적으로 필요한 경우가 아니면root를 피하십시오.
ini User=myappuserGroup: 서비스 프로세스가 실행될 그룹 계정입니다.
ini Group=myappgroupWorkingDirectory: 실행되는 명령의 작업 디렉터리입니다.
ini WorkingDirectory=/opt/my_appRestart: 서비스가 자동으로 다시 시작되어야 하는 시기를 정의합니다.no(기본값): 절대 다시 시작하지 않습니다.on-success: 서비스가 깨끗하게 종료될 경우에만 다시 시작합니다.on-failure: 서비스가 0이 아닌 상태 코드로 종료되거나 신호에 의해 종료될 경우에만 다시 시작합니다.always: 종료 상태와 관계없이 항상 서비스를 다시 시작합니다.
ini Restart=on-failure
RestartSec: 서비스를 다시 시작하기 전에 대기할 시간 (예:5s는 5초)입니다.
ini RestartSec=5sEnvironment: 실행되는 명령에 대한 환경 변수를 설정합니다.
ini Environment="APP_ENV=production" "DEBUG=false"EnvironmentFile: 파일에서 환경 변수를 읽습니다. 각 줄은KEY=VALUE형식이어야 합니다.
ini EnvironmentFile=/etc/default/my_appLimitNOFILE: 서비스에 허용되는 최대 열린 파일 디스크립터 수 (예:100000)를 설정합니다. 고동시성 애플리케이션에 중요합니다.
ini LimitNOFILE=65536
[Install] 섹션
이 섹션은 서비스가 부팅 시 자동으로 시작되도록 활성화되는 방법을 정의합니다.
WantedBy: 이 서비스를 '원하는' 대상 유닛을 지정합니다. 대상 유닛이 활성화되면 이 서비스는 해당.wants디렉터리에 심볼릭 링크되어 대상과 함께 효과적으로 시작됩니다.multi-user.target: 대부분의 서버 서비스에 대한 표준 대상이며, 비그래픽 다중 사용자 로그인이 있는 시스템을 나타냅니다.graphical.target: 그래픽 환경을 요구하는 서비스용입니다.
ini WantedBy=multi-user.target
RequiredBy:WantedBy와 유사하지만 더 강력한 종속성입니다. 대상이 활성화되면 이 유닛도 활성화되며, 이 유닛이 실패하면 대상도 실패합니다.
팁: 서버의 백그라운드에서 실행되도록 의도된 대부분의 사용자 지정 서비스의 경우,
Type=simple과WantedBy=multi-user.target이 가장 일반적이고 적절한 선택입니다.
단계별: 사용자 지정 Systemd 서비스 생성 및 관리
실용적인 예시를 만들어 봅시다: 지정된 디렉터리에서 파일을 제공하는 간단한 Python HTTP 서버입니다. 이를 systemd 서비스로 설정할 것입니다.
1단계: 애플리케이션/스크립트 준비
먼저 애플리케이션 스크립트를 생성합니다. 이 예시에서는 간단한 Python HTTP 서버를 사용할 것입니다. 애플리케이션용 디렉터리 (예: /opt/my_app)를 생성하고, 그 안에 app.py를 배치하십시오.
# /opt/my_app/app.py
import http.server
import socketserver
import os
PORT = int(os.environ.get("PORT", 8000))
DIRECTORY = os.environ.get("DIRECTORY", os.getcwd())
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=DIRECTORY, **kwargs)
print(f"Serving directory {DIRECTORY} on port {PORT}")
with socketserver.TCPServer(("", PORT), Handler) as httpd:
print("Server started.")
httpd.serve_forever()
디렉터리와 파일을 생성하십시오:
sudo mkdir -p /opt/my_app
sudo nano /opt/my_app/app.py
(파이썬 코드 붙여넣기)
스크립트가 실행 가능하도록 만드십시오 ( python3 명령에는 선택 사항이지만 좋은 습관):
sudo chmod +x /opt/my_app/app.py
보안상의 이유로 서비스 전용 사용자를 생성하는 것을 고려하십시오:
sudo useradd --system --no-create-home myappuser
애플리케이션 디렉터리에 적절한 소유권을 설정하십시오:
sudo chown -R myappuser:myappuser /opt/my_app
2단계: 유닛 파일 생성
이제 Python 애플리케이션을 위한 systemd 유닛 파일을 생성합니다. 이름을 my_app.service로 지정할 것입니다.
sudo nano /etc/systemd/system/my_app.service
다음 내용을 붙여넣으십시오:
# /etc/systemd/system/my_app.service
[Unit]
Description=My Custom Python HTTP Server
Documentation=https://github.com/example/my_app
After=network.target
[Service]
Type=simple
User=myappuser
Group=myappuser
WorkingDirectory=/opt/my_app
Environment="PORT=8080" "DIRECTORY=/var/www/html"
ExecStart=/usr/bin/python3 /opt/my_app/app.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
참고: 서비스의 출력을 systemd 저널로 보내
journalctl로 로그를 쉽게 볼 수 있도록StandardOutput=journal및StandardError=journal을 설정했습니다.
3단계: 유닛 파일 배치
지시된 대로, 유닛 파일을 /etc/systemd/system/에 배치했습니다. 이곳은 사용자 지정 유닛 파일이 있어야 하는 곳입니다.
4단계: Systemd 데몬 다시 로드
유닛 파일을 생성하거나 수정한 후에는 systemd에 변경 사항을 알려야 합니다. 이는 systemd 데몬을 다시 로드하여 수행됩니다:
sudo systemctl daemon-reload
5단계: 서비스 시작
이제 서비스를 시작할 수 있습니다:
sudo systemctl start my_app.service
6단계: 서비스 상태 및 로그 확인
서비스가 올바르게 실행 중인지 확인하십시오:
systemctl status my_app.service
예시 출력 (생략됨):
● my_app.service - My Custom Python HTTP Server
Loaded: loaded (/etc/systemd/system/my_app.service; disabled; vendor preset: enabled)
Active: active (running) since Tue 2023-10-26 10:30:00 UTC; 5s ago
Docs: https://github.com/example/my_app
Main PID: 12345 (python3)
Tasks: 1 (limit: 1100)
Memory: 6.5M
CPU: 45ms
CGroup: /system.slice/my_app.service
└─12345 /usr/bin/python3 /opt/my_app/app.py
Oct 26 10:30:00 yourhostname python3[12345]: Serving directory /var/www/html on port 8080
Oct 26 10:30:00 yourhostname python3[12345]: Server started.
서비스 로그를 보려면 journalctl을 사용하십시오:
journalctl -u my_app.service -f
이 명령은 my_app.service에 대한 로그를 보여주며, -f (follow)는 새로운 로그를 실시간으로 표시합니다.
브라우저나 curl을 사용하여 http://localhost:8080에서 서버를 테스트할 수도 있습니다 ( /var/www/html이 존재하고 일부 파일이 포함되어 있다고 가정).
7단계: 자동 시작을 위해 서비스 활성화
시스템이 부팅될 때마다 서비스가 자동으로 시작되도록 하려면 활성화해야 합니다:
sudo systemctl enable my_app.service
이 명령은 /etc/systemd/system/multi-user.target.wants/my_app.service에서 /etc/systemd/system/my_app.service로 심볼릭 링크를 생성합니다.
8단계: 서비스 중지 및 비활성화
실행 중인 서비스를 중지하려면:
sudo systemctl stop my_app.service
서비스가 부팅 시 자동으로 시작되는 것을 방지하려면 (수동으로 시작할 수 있도록 활성화된 상태로 유지하면서):
sudo systemctl disable my_app.service
서비스를 완전히 제거하려면 먼저 disable한 다음 stop하고, 마지막으로 /etc/systemd/system/에서 .service 파일을 삭제한 후 sudo systemctl daemon-reload를 실행하십시오.
9단계: 서비스 업데이트
app.py 스크립트 또는 my_app.service 유닛 파일을 수정한 경우, systemd를 업데이트하고 서비스를 다시 시작해야 합니다:
/opt/my_app/app.py또는/etc/systemd/system/my_app.service를 편집합니다.- 유닛 파일을 수정한 경우,
sudo systemctl daemon-reload를 실행합니다. - 서비스를 다시 시작합니다:
sudo systemctl restart my_app.service.
모범 사례 및 문제 해결
- 절대 경로:
ExecStart,WorkingDirectory및 유닛 파일 내의 다른 모든 파일 경로에는 항상 절대 경로를 사용하십시오. 상대 경로는 예기치 않은 동작을 유발할 수 있습니다. - 전용 사용자: 보안을 강화하고 침해 시 잠재적 손상을 제한하기 위해 비특권, 전용 사용자 계정 (예:
myappuser)으로 서비스를 실행하십시오. - 명확한 로깅:
StandardOutput=journal및StandardError=journal을 활용하여 서비스 출력을 systemd 저널로 보냅니다.journalctl -u <service_name>을 사용하여 로그를 확인하십시오. - 종속성:
After,Wants,Requires를 신중하게 고려하여 서비스가 종속성 (예: 네트워킹, 데이터베이스)에 상대적으로 올바른 순서로 시작되도록 하십시오. - 변경 사항 테스트: 서비스가 부팅 시 시작되도록 활성화하기 전에 수동으로 시작하고 중지하여 철저히 테스트하십시오. 상태 및 로그를 확인하십시오.
- 리소스 제한:
LimitNOFILE,LimitNPROC,MemoryLimit등과 같은 지시문을 사용하여 폭주하는 서비스가 모든 시스템 리소스를 소비하는 것을 방지하십시오. - 환경 변수: 유닛 파일이나 스크립트에 하드 코딩하는 대신, 변경될 수 있거나 환경마다 다를 수 있는 구성 값에는
Environment=또는EnvironmentFile=을 사용하십시오. - 스크립트의 오류 처리: 애플리케이션 스크립트가 오류를 우아하게 처리하는지 확인하십시오. 0이 아닌 종료 코드는
Restart=on-failure를 트리거합니다.
경고:
/usr/lib/systemd/system/에 있는 유닛 파일을 직접 수정하지 마십시오. 변경 사항은 패키지 업데이트로 덮어쓰여질 가능성이 높습니다. 사용자 지정 유닛 또는 재정의에는/etc/systemd/system/을 사용하십시오.
결론
Systemd 유닛 파일은 Linux 시스템에서 프로세스 및 애플리케이션을 관리하기 위한 강력하고 유연한 메커니즘입니다. 그 구조와 주요 지시문을 이해함으로써 사용자 지정 서비스의 시작, 종료 및 모니터링을 효과적으로 표준화하여 시스템 안정성을 향상시키고 관리를 단순화할 수 있습니다. ExecStart로 시작 명령을 정의하는 것부터 After로 종속성을 관리하고 WantedBy로 자동 시작을 활성화하는 것까지, 이제 애플리케이션을 systemd 생태계에 원활하게 통합할 수 있는 도구를 갖게 되었습니다. 이 기본적인 기술은 견고하고 신뢰할 수 있는 Linux 배포를 유지하는 데 매우 중요합니다.
더욱 정교한 서비스 관리 시나리오를 위해 타이머(.timer), 소켓 활성화(.socket), cgroup과 같은 고급 systemd 기능을 계속 탐색하십시오.