일반적인 Systemd 설정 오류와 해결 방법
잘못된 경로, 잘못된 서비스 유형, 누락된 환경 변수, 권한 문제, 의존성 순서 등 일반적인 systemd 유닛 파일 실수를 수정합니다.
일반적인 Systemd 설정 오류와 해결 방법
Systemd 설정 오류는 보통 실제보다 더 극적으로 보입니다. 서비스가 시작되지 않거나, 배포가 롤백되거나, 부팅이 거의 기억나지 않는 유닛 이름에서 멈춥니다. 그러고 나서 실제 원인은 ExecStart=에서 누락된 슬래시, 잘못된 사용자로 실행되는 프로세스, 또는 아무도 daemon-reload를 실행하지 않아 systemd 관리자에게 전달되지 않은 유닛 파일 변경 사항인 경우가 많습니다.
이러한 문제를 해결하는 가장 빠른 방법은 유닛 파일을 계약서로 취급하는 것입니다. 유닛 파일은 systemd에 어떤 프로세스를 실행할지, 어떤 사용자로 실행할지, 무엇이 먼저 존재해야 하는지, 준비 상태를 어떻게 보고할지, 충돌 후에 무엇을 해야 하는지를 알려줍니다. 이러한 세부 사항 중 하나가 잘못되면 systemd는 일반적으로 지시받은 대로 정확히 수행합니다. 해야 할 일은 애플리케이션이 필요로 하는 것과 유닛 파일이 실제로 말하는 것 사이의 불일치를 찾는 것입니다.
1. 유닛 파일의 구문 및 경로 오류
서비스 실패의 가장 빈번한 원인 중 하나는 유닛 파일 내의 단순한 오타나 잘못 정의된 경로입니다.
Exec 명령의 잘못된 경로 또는 절대 경로가 아닌 경우
Systemd는 테스트에 사용한 셸 세션과 동일한 환경에서 서비스를 실행하지 않습니다. 제어된 환경에서 프로세스를 시작하므로 별칭, 셸 함수, 가상 환경 활성화 및 사용자 정의 PATH에 대한 가정이 종종 실패합니다. ExecStart=에서 실행 파일에 절대 경로를 사용하고 서비스에 필요한 모든 디렉토리나 파일을 명시적으로 지정하십시오.
오류:
위치를 지정하지 않고 명령 이름을 사용합니다.
[Service]
ExecStart=my-app-server --config /etc/config.yaml
my-app-server가 /usr/local/bin에 있는 경우 systemd가 이를 찾지 못할 가능성이 높습니다.
해결 방법:
항상 실행 파일의 전체 절대 경로를 사용하십시오.
[Service]
ExecStart=/usr/local/bin/my-app-server --config /etc/config.yaml
ExecStart=를 구성하기 전에 command -v my-app-server 또는 which my-app-server로 경로를 확인하십시오. 애플리케이션이 /opt/myapp/venv/bin/gunicorn과 같은 Python 가상 환경과 같은 언어별 위치에 있는 경우 활성화 스크립트에 의존하지 말고 해당 바이너리를 직접 가리키십시오.
철자 오류 및 대소문자 구분
Systemd 구성 지시문은 대소문자를 구분하며 올바른 섹션([Unit], [Service], [Install])에 배치해야 합니다. 철자가 틀리거나 대소문자가 잘못되면 서비스 로드에 실패하거나 예기치 않은 동작이 발생할 수 있습니다.
오류 예시:
[Service]
ExecStart=/usr/bin/python3 app.py
RestartAlways=true ; Restart=always여야 함
해결 방법:
데몬을 다시 로드하기 전에 systemd-analyze verify <unit_file>을 사용하십시오. 모든 런타임 오류를 잡아내지는 못하지만 철자가 틀린 지시문, 잘못된 섹션 배치 및 구문 분석 오류를 많이 잡아내어 애플리케이션 로그를 추적하는 데 시간을 낭비하지 않도록 해줍니다.
$ systemd-analyze verify /etc/systemd/system/my-service.service
2. 서비스 의존성 및 순서 관리 오류
의존성은 서비스에 무엇이 필요한지 정의하고, 순서는 해당 리소스를 언제 사용할 수 있어야 하는지 정의합니다.
Requires와 Wants 혼동
이 지시문은 의존성을 정의하는 데 사용되지만 실패 처리가 다릅니다.
Wants=: 약한 의존성. 원하는 유닛이 실패하거나 시작되지 않아도 현재 유닛은 계속 시작을 시도합니다. 중요하지 않은 의존성에 사용하십시오.Requires=: 강한 의존성. 필요한 유닛을 시작할 수 없으면 현재 유닛도 실패합니다. 필요한 유닛이 명시적으로 중지되면 종속 유닛도 중지됩니다.
적절한 순서 없이 Requires에 의존
예를 들어 Requires=network.target와 같은 의존성을 정의하면 해당 의존성이 트랜잭션에 포함됩니다. 이것만으로는 시작 순서가 생성되지 않으며 network.target이 "네트워크를 아웃바운드 연결에 사용할 수 있음"을 의미하지는 않습니다. 서비스에 구성된 네트워킹이 필요한 경우 network-online.target을 사용하고 해당 동작이 필요할 때 배포판의 wait-online 서비스가 활성화되어 있는지 확인하십시오.
오류:
웹 서버가 시작되지만 네트워킹 스택이 아직 초기화 중이어서 데이터베이스 연결이 실패합니다.
해결 방법: After= 및 Before= 사용
순서를 적용하려면 After=(또는 Before=)를 사용해야 합니다. 일반적인 요구 사항은 진행하기 전에 네트워크가 완전히 작동하고 구성되었는지 확인하는 것입니다.
[Unit]
Description=My Web Application Service
Wants=network-online.target
After=network-online.target
[Service]
...
대부분의 애플리케이션 서비스의 경우 의존성 의도와 순서 의도를 함께 사용하십시오. Wants=postgresql.service는 "PostgreSQL도 시작해 주세요"를 의미합니다. After=postgresql.service는 "PostgreSQL의 시작 작업이 완료된 후에 나를 시작하세요"를 의미합니다. 이들은 서로 다른 문제를 해결합니다.
잘못된 서비스 유형 관리
Systemd 서비스에는 Type= 지시문으로 관리되는 여러 실행 유형이 있습니다. 이를 잘못 구성하면 서비스가 잠시 시작되었다가 즉시 실패하는 일반적인 원인이 됩니다.
오류: Type=forking 오용
애플리케이션이 포그라운드에서 실행되고 단일 메인 프로세스를 유지하도록 설계된 경우 Type=forking을 설정하면 systemd에 구식 데몬 동작을 기대하도록 지시합니다. 이로 인해 혼란스러운 결과가 발생할 수 있습니다. systemd가 부모 프로세스가 종료되기를 기다리거나, 실제 메인 프로세스를 식별하지 못하거나, 애플리케이션이 실제로 준비되지 않았는데 서비스가 활성 상태라고 표시할 수 있습니다.
해결 방법:
- 최신 애플리케이션의 경우:
Type=simple을 사용하십시오. 이것이 기본값이며ExecStart프로세스가 메인 프로세스가 될 것으로 예상합니다. - 데몬화(포크)하는 레거시 애플리케이션의 경우:
Type=forking을 설정하고 결정적으로PIDFile=지시문을 정의하여 systemd가 포크에서 살아남은 자식 프로세스를 추적할 수 있도록 하십시오.
[Service]
Type=forking
PIDFile=/var/run/legacy-app.pid
ExecStart=/usr/sbin/legacy-app
또 다른 일반적인 준비 상태 함정이 있습니다. 사용 가능해지는 데 오래 걸리는 애플리케이션에 Type=simple을 사용하는 것입니다. Type=simple을 사용하면 systemd는 프로세스가 생성되는 즉시 서비스가 시작된 것으로 간주합니다. 다른 서비스가 즉시 시작되어 연결하면 간헐적인 실패가 발생할 수 있습니다. 준비되었을 때 systemd에 알릴 수 있는 애플리케이션의 경우 Type=notify가 더 깔끔합니다. 알릴 수 없는 애플리케이션의 경우 프로세스가 존재한다고 해서 유닛이 완전히 준비되었다고 가장하지 마십시오. 종속 애플리케이션에서 실제 상태 확인, 소켓 활성화 또는 사전 조건이 충분히 간단한 경우 ExecStartPre= 검사를 사용하십시오.
oneshot에도 주의하십시오. Type=oneshot 서비스는 디렉토리 생성, 방화벽 규칙 로드 또는 마이그레이션 실행과 같은 작업을 수행하고 종료되는 명령을 위한 것입니다. 장기 실행 데몬에 사용하면 systemd가 예상한 대로 감독하지 않습니다. 명령이 성공적으로 종료되고 의존성 목적으로 유닛을 "활성" 상태로 유지하려면 RemainAfterExit=yes를 추가하십시오. 그렇지 않으면 종속 유닛이 의도한 상태를 보지 못할 수 있습니다.
3. 환경 및 사용자 컨텍스트 문제
서비스 실패는 종종 서비스가 애플리케이션이 예상하는 것과 다른 컨텍스트에서 실행되어 발생하며, 일반적으로 권한 또는 환경 변수와 관련됩니다.
권한 거부 또는 파일 누락
애플리케이션을 수동으로 테스트할 때는 일반적으로 적절한 권한을 가진 사용자 계정으로 실행됩니다. systemd에 의해 실행될 때는 종종 루트 사용자 또는 유닛 파일에 지정된 사용자로 기본 설정됩니다.
오류:
일반적인 증상은 Permission denied, No such file or directory, Failed to open log file 또는 소켓을 생성하거나, PID 파일을 쓰거나, 구성 파일을 읽을 수 없다는 애플리케이션별 오류입니다. 유닛은 root로 명령을 수동으로 실행할 때 작동하다가 User=app에서 실패할 수 있습니다.
해결 방법:
비루트 사용자 정의: 항상 서비스에 대해 전용의 낮은 권한을 가진 사용자와 그룹을 지정하십시오.
[Service] User=www-data Group=www-data ...소유권 확인: 서비스의 작업 디렉토리, 로그 파일 및 구성 파일이 지정된
User=및Group=이 소유하고 있는지 확인하십시오.sudo chown -R www-data:www-data /var/www/my-app서비스가 접촉하는 모든 경로 확인: 애플리케이션 디렉토리에서 멈추지 마십시오.
WorkingDirectory=, 로그 디렉토리, 업로드 디렉토리, 캐시 디렉토리, TLS 키 파일, Unix 소켓 및 환경 파일에서 참조하는 모든 경로를 확인하십시오.sudo -u www-data test -r /etc/my-app/config.yml sudo -u www-data test -w /var/lib/my-app sudo -u www-data /usr/local/bin/my-app --check-config
서비스가 포트 80 또는 443에 바인딩해야 하는 경우 자동으로 루트로 실행하지 마십시오. 많은 시스템에서 그 앞에 리버스 프록시를 두거나, 소켓 활성화를 사용하거나, 바이너리에 필요한 특정 기능을 부여할 수 있습니다. 올바른 선택은 서비스에 따라 다르지만 광범위한 루트 프로세스가 기본 답변이 되어서는 안 됩니다.
사람들을 종종 놀라게 하는 또 다른 권한 세부 사항은 상위 디렉토리에 실행 권한이 필요하다는 것입니다. 파일이 읽기 가능해 보일 수 있지만 /opt, /opt/myapp 또는 다른 상위 디렉토리가 서비스 사용자의 탐색을 차단하기 때문에 서비스가 여전히 파일에 도달할 수 없습니다. namei -l /opt/myapp/config.yml은 최종 파일뿐만 아니라 각 경로 구성 요소에 대한 권한을 표시하므로 유용합니다.
누락된 환경 변수
Systemd 서비스는 최소 환경에서 실행됩니다. 중요한 환경 변수(예: API 키, 데이터베이스 연결 문자열 또는 사용자 정의 라이브러리 경로)는 명시적으로 전달되어야 합니다.
해결 방법: Environment= 또는 EnvironmentFile= 사용
간단한 변수의 경우 Environment=를 사용하십시오.
[Service]
Environment="APP_PORT=8080"
Environment="API_KEY=ABCDEFG"
복잡하거나 많은 변수의 경우 표준 .env 파일을 가리키는 EnvironmentFile=를 사용하십시오.
[Service]
EnvironmentFile=/etc/default/my-app.conf
비밀을 세계에서 읽을 수 있는 유닛 파일에 보관하지 마십시오. /etc/systemd/system 아래의 유닛 파일은 일반적으로 로컬 사용자가 읽을 수 있습니다. API 키를 Environment=에 직접 넣으면 유닛 파일을 읽거나 프로세스 메타데이터를 검사할 수 있는 모든 사람에게 노출된다고 가정하십시오. 제한적인 권한이 있는 루트 소유 환경 파일, 비밀 관리자 또는 이를 지원하는 배포판의 systemd 자격 증명을 선호하십시오.
또한 EnvironmentFile=는 셸 스크립트가 아닙니다. export APP_PORT=8080과 같은 줄이나 TOKEN=$(cat /run/token)과 같은 명령 대체는 Bash에서처럼 해석되지 않습니다. 일반 할당을 사용하십시오.
APP_PORT=8080
APP_ENV=production
애플리케이션에 로그인 셸 설정이 필요한 경우 일반적으로 좋지 않은 신호입니다. .bashrc에 의존하는 대신 실제 환경을 유닛 파일, 환경 파일 또는 애플리케이션 자체 구성에 넣으십시오.
언어 런타임의 경우 systemd가 실제로 테스트한 런타임을 가리키도록 하십시오. Python 서비스는 일반적으로 /opt/myapp/venv/bin/python 또는 /opt/myapp/venv/bin/gunicorn과 같은 가상 환경 바이너리를 직접 호출해야 합니다. 버전 관리자를 통해 설치된 Node 서비스는 터미널에서 작동할 수 있지만 nvm 또는 asdf가 대화형 셸만 수정했기 때문에 systemd에서 실패할 수 있습니다. 프로덕션 유닛에서는 명시적 경로가 셸 시작 매직보다 우선합니다.
4. 중요한 디버깅 워크플로우
가장 일반적인 구성 오류는 유닛 파일 편집과 서비스 재시작 시도 사이의 중요한 단계를 잊어버리는 것입니다.
데몬 다시 로드 잊기
Systemd는 유닛 파일의 변경 사항을 자동으로 모니터링하지 않습니다. /etc/systemd/system/의 파일을 수정한 후에는 systemd 관리자에게 구성 캐시를 다시 로드하도록 지시해야 합니다.
오류:
파일을 편집하고 systemctl restart my-service를 실행했지만 이전 구성이 계속 사용됩니다.
해결 방법: daemon-reload 실행
유닛 파일 변경 사항을 저장한 직후 항상 이 명령을 실행하십시오.
sudo systemctl daemon-reload
sudo systemctl restart my-service
systemctl edit my-service로 드롭인 오버라이드를 편집한 경우에도 동일한 규칙이 적용됩니다. 생성된 오버라이드는 /etc/systemd/system/my-service.service.d/ 아래에 저장되며 systemd는 새 설정이 적용되기 전에 여전히 유닛 캐시를 다시 로드해야 합니다.
재시작이 이상하게 동작할 때 systemd가 보는 정확한 병합된 유닛을 검사하십시오.
systemctl cat my-service.service
systemctl show my-service.service -p FragmentPath -p DropInPaths -p User -p ExecStart
이것은 일반적인 실수를 잡아냅니다. /etc/systemd/system의 오버라이드가 여전히 설정을 변경하는 동안 /usr/lib/systemd/system의 공급업체 유닛을 편집하거나 systemd가 로드한 유닛이 아닌 유닛의 복사본을 편집하는 것입니다.
로깅 도구의 효과적인 사용
서비스가 실패하면 정확한 진단을 위해 공식 도구에 의존하십시오.
서비스 상태 확인: 즉각적인 상태, 종료 코드 및 마지막 몇 줄의 로그를 제공합니다.
systemctl status my-service.service저널 검사: 저널에는 서비스의 포괄적인 출력(stdout/stderr)이 있습니다. "Permission denied" 또는 *"No such file or directory"*와 같은 단서를 찾으십시오.
# 유닛에 대한 최근 로그 보기 journalctl -u my-service.service --since '1 hour ago' # 로그를 보고 실시간으로 출력 팔로우 journalctl -f -u my-service.service
실용적인 문제 해결 과정
깨진 유닛을 검토할 때 일반적으로 다음 순서로 한 번 확인합니다.
systemctl status my-service.service
journalctl -u my-service.service --since "15 minutes ago"
systemctl cat my-service.service
systemd-analyze verify /etc/systemd/system/my-service.service
그런 다음 간단한 질문을 합니다. ExecStart=가 실제 실행 파일을 가리키나요? 구성된 User=가 실행할 수 있나요? WorkingDirectory=가 존재하나요? 환경 변수가 셸에 의존하지 않고 존재하나요? Type=이 프로세스 동작 방식에 대해 정직한가요? 다른 유닛이 시작되고 이 유닛보다 먼저 순서가 지정되어야 할 때 Wants=와 After=가 모두 있나요?
각 편집 후에는 다시 로드하고 한 가지를 테스트하십시오.
sudo systemctl daemon-reload
sudo systemctl restart my-service.service
systemctl status my-service.service --no-pager
서비스가 계속 실패하면 유닛 파일을 계속 맹목적으로 변경하고 싶은 충동을 억제하십시오. 저널은 일반적으로 다음 문제가 systemd 구성, 애플리케이션 구성, 권한 또는 자체적으로 실패하는 의존성인지 알려줍니다.