일반적인 Systemd 서비스 장애 효과적으로 해결하기
systemctl, journalctl, 종료 코드, 유닛 점검 및 실용적인 복구 단계를 통해 일반적인 systemd 서비스 장애를 진단합니다.
일반적인 Systemd 서비스 장애 효과적으로 해결하기
대부분의 systemd 서비스 장애는 세 가지 질문을 분리하면 더 이상 신비롭지 않습니다: systemd가 유닛 파일을 읽었는지, 명령을 실행할 수 있었는지, 애플리케이션이 시작된 후 정상 상태를 유지했는지입니다. 이들은 서로 다른 장애 지점이며, 다른 단서를 남깁니다.
제가 가장 자주 보는 실수는 바로 유닛 파일을 편집하는 것입니다. 먼저 상태와 로그를 읽으십시오. 실패한 서비스는 일반적으로 누락된 실행 파일, 잘못된 사용자, 권한 문제, 종속성 순서 문제 또는 애플리케이션 충돌 중 어떤 문제인지 알려줍니다. 정확한 문구가 중요합니다.
필수 진단 도구 키트
효과적인 문제 해결은 서비스 상태와 운영 로그에 대한 즉각적인 피드백을 제공하는 두 가지 기본 systemd 도구에 의존합니다.
1. 서비스 상태 확인
systemctl status 명령은 유닛의 현재 상태, 최근 로그, 프로세스 ID(PID) 및 종료 코드와 같은 중요한 메타데이터를 포함한 유닛 상태의 즉각적인 스냅샷을 제공합니다.
$ systemctl status myapp.service
찾아야 할 주요 정보:
Load:유닛 파일이 올바르게 읽혔는지 확인합니다.loaded는 좋습니다.not found가 표시되면 서비스 파일이 잘못된 위치에 있거나 철자가 틀린 것입니다.Active:핵심 상태입니다.failed로 표시되면 서비스가 시작을 시도했지만 예기치 않게 종료된 것입니다.Exit Code:종종Active: failed와 함께 표시되는 이 숫자 코드는 매우 중요합니다. 프로세스가 종료된 이유를 나타냅니다(예: 0은 정상 종료, 1 또는 2는 일반 애플리케이션 오류, 203은 실행 경로 오류).- 최근 로그: Systemd는 종종 서비스의 마지막 몇 줄의 로그 출력을 포함하며, 이는 오류를 즉시 드러낼 수 있습니다.
2. Journalctl을 사용한 로그 심층 분석
systemctl status가 요약을 제공하는 반면, journalctl은 표준 출력 및 표준 오류 스트림을 포함한 서비스 실행 기록의 전체 컨텍스트를 제공합니다.
다음 명령을 사용하여 실패한 서비스에 대한 저널을 구체적으로 확인하고, -x 플래그로 설명을, -e 플래그로 끝(가장 최근 항목)으로 이동합니다:
$ journalctl -xeu myapp.service
팁: 장애가 몇 시간 또는 며칠 전에 발생한 경우,
journalctl -u myapp.service --since "2 hours ago"와 같은 시간 필터링 옵션을 사용하십시오.
일반적인 장애의 단계별 진단
Systemd 장애는 일반적으로 몇 가지 예측 가능한 범주에 속합니다. 상태와 로그를 검토하여 문제를 신속하게 분류하고 적절한 해결책을 적용할 수 있습니다.
장애 유형 1: 실행 오류 (종료 코드 203)
종료 코드 203/EXEC는 systemd가 ExecStart 지시문에 지정된 파일을 실행할 수 없음을 의미합니다. 이는 가장 흔한 구성 실수 중 하나입니다.
원인 및 해결책:
잘못된 경로: 실행 파일의 경로가 잘못되었거나 절대 경로가 아닙니다.
- 해결책:
ExecStart에 항상 전체 절대 경로를 사용하십시오. 해당 정확한 위치에 실행 파일이 있는지 확인하십시오.
# 잘못됨 ExecStart=myapp # 올바름 ExecStart=/usr/local/bin/myapp- 해결책:
누락된 권한: 파일에 서비스를 실행하는 사용자에 대한 실행 권한이 없습니다.
- 해결책: 실행 권한을 확인하고 적용하십시오:
chmod +x /path/to/executable.
- 해결책: 실행 권한을 확인하고 적용하십시오:
누락된 인터프리터 (Shebang):
ExecStart가 스크립트(예: Python 또는 Bash)를 가리키는 경우, shebang 라인(#!/usr/bin/env python)이 없거나 잘못되어 실행을 방해할 수 있습니다.- 해결책: 스크립트에 유효한 shebang 라인이 있는지 확인하십시오.
장애 유형 2: 애플리케이션 충돌 (종료 코드 1 또는 2)
서비스가 성공적으로 시작되고(systemd가 실행 파일을 찾음) 있지만 즉시 일반 애플리케이션 오류 코드(보통 1 또는 2)와 함께 failed 상태가 되는 경우, 문제는 애플리케이션 로직 또는 환경에 있습니다.
원인 및 해결책:
구성 파일 오류: 애플리케이션이 필요한 구성 파일을 읽을 수 없거나 파일에 잘못된 구문이 포함되어 있습니다.
- 해결책:
journalctl출력을 주의 깊게 검토하십시오. 애플리케이션은 일반적으로 구성 파일 경로 또는 구문에 대한 특정 오류 메시지를 출력합니다. 구성 파일이 상대 경로인 경우WorkingDirectory=지시문을 사용하십시오.
- 해결책:
리소스 경합/액세스 거부: 애플리케이션이 권한 제한으로 인해 필요한 포트를 열거나, 데이터베이스에 액세스하거나, 로그 파일에 쓰지 못했습니다.
- 해결책: 서비스 파일의
User=지시문을 확인하고 해당 사용자가 필요한 모든 리소스 및 디렉토리에 대한 읽기/쓰기 액세스 권한을 가지고 있는지 확인하십시오.
- 해결책: 서비스 파일의
장애 유형 3: 종속성 실패
서비스가 데이터베이스, 네트워크 인터페이스 또는 마운트된 파일 시스템과 같은 필수 종속성이 준비되기 전에 시작되어 실패할 수 있습니다.
원인 및 해결책:
네트워크 미준비: 네트워크 연결이 필요한 서비스(예: 웹 서버, 프록시)는 네트워크 스택이 초기화되기 전에 시작되면 종종 실패합니다.
- 해결책: 서비스가 시작 중에 주소나 경로를 필요로 하는 경우,
network-online.target순서를 추가하고 네트워크 관리자에 대한 대기-온라인 서비스가 배포판에서 활성화되어 있는지 확인하십시오:
[Unit] Description=My Web Service After=network-online.target Wants=network-online.target- 해결책: 서비스가 시작 중에 주소나 경로를 필요로 하는 경우,
파일 시스템 미마운트: 서비스가 아직 마운트되지 않은 볼륨(특히 보조 스토리지 또는 네트워크 마운트의 경우 중요)의 파일에 액세스하려고 시도합니다.
- 해결책:
RequiresMountsFor=를 사용하여 시작하기 전에 사용할 수 있어야 하는 경로를 systemd에 명시적으로 알리십시오.
[Unit] RequiresMountsFor=/mnt/data/storage- 해결책:
장애 유형 4: 사용자 및 환경 문제 (종료 코드 217)
종료 코드 217/USER는 종종 사용자 또는 그룹 지시문과 관련된 실패 또는 환경 변수를 사용할 수 없음을 나타냅니다.
원인 및 해결책:
잘못된 사용자/그룹:
User=또는Group=지시문에 지정된 사용자가 시스템에 존재하지 않습니다.- 해결책:
id <username>을 통해 사용자 이름이 존재하는지 확인하십시오.
- 해결책:
누락된 환경 변수: Systemd 서비스는 깨끗한 환경에서 실행되므로 셸 변수(예:
PATH또는 사용자 정의 API 키)는 상속되지 않습니다.- 해결책: 서비스 파일에 직접 또는 환경 파일을 통해 필요한 변수를 정의하십시오.
[Service] # 직접 정의 Environment="API_KEY=ABCDEFG" # 외부 파일 사용 (예: /etc/sysconfig/myapp) EnvironmentFile=/etc/sysconfig/myapp
문제 해결 워크플로우 및 모범 사례
서비스 파일을 수정할 때는 항상 이 3단계 주기를 따라 변경 사항이 올바르게 적용되고 테스트되는지 확인하십시오.
1. 구성 구문 확인
서비스를 시작하기 전에 systemd-analyze verify를 사용하여 서비스 유닛 파일을 확인하십시오. 이는 간단한 구문 오류를 잡아냅니다.
$ systemd-analyze verify /etc/systemd/system/myapp.service
2. 데몬 다시 로드
Systemd는 구성 파일을 캐시합니다. 유닛 파일을 변경한 후에는 반드시 systemd에 구성을 다시 로드하도록 지시해야 합니다.
$ systemctl daemon-reload
3. 다시 시작 및 상태 확인
서비스를 다시 시작하고 즉시 상태와 로그를 확인하십시오.
$ systemctl restart myapp.service
$ systemctl status myapp.service
즉시 다시 시작 및 시간 초과 처리
서비스가 restarting 루프에 빠지거나 명백한 로그 메시지 없이 즉시 실패하는 경우, [Service] 섹션에서 다음 지시문을 조정하는 것을 고려하십시오:
| 지시문 | 목적 | 모범 사례 |
|---|---|---|
Type= |
systemd가 프로세스를 관리하는 방법(예: simple, forking). |
애플리케이션이 명시적으로 데몬화하지 않는 한 simple을 사용하십시오. |
TimeoutStartSec= |
systemd가 메인 프로세스가 성공 신호를 보낼 때까지 기다리는 시간. | 애플리케이션 시작 시간이 긴 경우(예: 대규모 데이터베이스 초기화) 이 값을 늘리십시오. |
Restart= |
서비스를 자동으로 다시 시작해야 하는 시기를 정의합니다(예: always, on-failure). |
반복되는 구성 오류로 인한 무한 재시작 루프를 방지하려면 프로덕션 애플리케이션의 경우 on-failure를 사용하십시오. |
실패 상태 더 주의 깊게 읽기
failed만이 유일한 나쁜 상태는 아닙니다. 유닛은 정상 종료 후 inactive (dead) 상태가 될 수 있으며, 이는 Type=oneshot 작업의 경우 정상이지만 계속 실행될 것으로 예상되는 데몬의 경우 의심스럽습니다. 유닛은 TimeoutStartSec=이 만료될 때까지 activating 상태일 수 있습니다. 유닛은 명령이 완료되고 systemd가 이를 수용 가능하다고 판단할 때 active (exited) 상태일 수 있습니다. 다시 시작 정책을 변경하기 전에 서비스 유형이 프로그램과 일치하는지 확인하십시오.
일반적인 포그라운드 프로세스의 경우 다음으로 시작하십시오:
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
한 번 실행되고 종료되는 스크립트의 경우:
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/rotate-reports
스스로 백그라운드로 포크하는 오래된 데몬의 경우 Type=forking이 필요할 수 있지만, 습관적으로 사용하지 마십시오. 많은 최신 애플리케이션은 systemd에서 실행될 때 이미 포그라운드에 남아 있습니다. systemd에 포킹을 기대하라고 지시했는데 프로세스가 systemd가 기대하는 방식으로 포크하지 않으면 오해의 소지가 있는 시작 실패가 발생할 수 있습니다.
압박 속에서 작동하는 분류 체크리스트
서비스가 중단되고 사람들이 기다리고 있을 때, 고정된 순서를 사용하십시오:
systemctl status myapp.service --no-pager
journalctl -u myapp.service -b --no-pager
systemctl cat myapp.service
systemctl show myapp.service -p FragmentPath -p User -p Group -p WorkingDirectory -p ExecStart
마지막 줄이 아닌 첫 번째 실제 오류를 찾으십시오. 마지막 저널 항목은 systemd가 유닛 실패를 표시했다고만 말할 수 있습니다. 유용한 줄은 종종 그 위에 있습니다: Permission denied, No such file or directory, Address already in use, Failed at step USER 또는 애플리케이션별 예외입니다.
서비스가 최근에 편집된 경우 구문 및 다시 로드 상태를 확인하십시오:
sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
systemctl status가 디스크에서 유닛 파일이 변경되었다고 말하면, systemd는 관리자가 새 정의를 다시 로드하지 않았음을 경고하는 것입니다. daemon-reload 전에 서비스를 다시 시작하면 오래된 설정을 계속 사용할 수 있습니다.
권한 문제처럼 보이지 않는 권한 문제
서비스가 셸에서 완벽하게 실행되지만 systemd에서는 실패할 수 있습니다. 이는 사용자로 실행되지 않기 때문입니다. User=, Group=, WorkingDirectory= 및 ProtectSystem=, ReadWritePaths=, PrivateTmp= 또는 NoNewPrivileges=와 같은 강화 옵션을 확인하십시오.
예를 들어:
[Service]
User=webapp
WorkingDirectory=/srv/webapp
ExecStart=/srv/webapp/bin/server
ReadWritePaths=/srv/webapp/var
ProtectSystem=strict
ProtectSystem=strict를 사용하면 파일 시스템의 대부분이 서비스에 대해 읽기 전용이 됩니다. 이는 좋은 강화 설정이지만, 애플리케이션이 명시적으로 허용한 경로에만 써야 함을 의미합니다. 저널에서 앱이 PID 파일, 캐시 파일, SQLite 데이터베이스 또는 업로드 디렉토리를 만들 수 없다고 말하면 유닛의 샌드박싱이 원인일 수 있습니다.
또한 상위 디렉토리 권한을 확인하십시오. 실행 파일의 모드가 755일 수 있지만, /srv/webapp이 서비스 사용자가 검색 가능하지 않으면 systemd는 여전히 실행에 실패합니다. 다음을 사용하십시오:
namei -l /srv/webapp/bin/server
sudo -u webapp /srv/webapp/bin/server --check-config
서비스 사용자로 안전한 구성 확인을 실행하면 전체 데몬을 시작하지 않고도 많은 문제를 잡을 수 있습니다.
다시 시작 루프 및 속도 제한
Restart=on-failure는 유용하지만, 반복되는 시작의 홍수 속에서 원래 오류를 숨길 수 있습니다. Systemd는 또한 시작 속도 제한을 적용합니다. 서비스가 짧은 시간 내에 너무 많이 실패하면 start-limit-hit가 표시될 수 있습니다.
유용한 명령:
systemctl status myapp.service
systemctl reset-failed myapp.service
sudo systemctl start myapp.service
reset-failed는 원인을 해결하지 않습니다. systemd의 실패 상태와 속도 제한 메모리만 지워 변경 후 다시 테스트할 수 있도록 합니다. 계속 필요하다면 속도를 늦추고 저널의 첫 번째 실패를 수정하십시오.
지속적인 문제 디버깅
표준 로그로 문제가 드러나지 않으면 애플리케이션이 출력을 리디렉션하고 있을 수 있습니다.
StandardOutput및StandardError검토: 기본적으로 이들은 저널로 전달됩니다./dev/null또는 파일로 설정된 경우 오류 메시지에 대해 해당 위치를 직접 확인해야 합니다.- 임시 상세 모드: 가능하면 애플리케이션(또는
ExecStart의 명령줄 인수)을 일시적으로 최대 상세 모드(예:--debug또는-v)로 구성하여 실패 시 더 자세한 로그 출력을 생성하십시오.
합리적인 중단점
서비스가 시작되면 한 가지 더 확인하십시오: 실제 작업을 수행하는지 여부입니다. systemctl status는 systemd의 관점에서 프로세스 상태만 알려줄 수 있습니다. 웹 서비스는 500을 반환하면서 활성 상태일 수 있습니다. 작업자는 모든 작업에 실패하면서 활성 상태일 수 있습니다. 유닛 수준 문제를 해결한 후에는 애플리케이션 자체 상태 확인을 실행하고, 애플리케이션 로그를 살펴보고, 통신하는 종속성에 연결할 수 있는지 확인하십시오.
대부분의 인시던트에서 유용한 경로는 짧습니다: systemctl status, journalctl -u, systemctl cat으로 유닛 검사, 구성된 서비스 사용자로 명령 테스트. 이렇게 하면 증거에 가깝게 유지되고 무작위 유닛 파일 변경에서 멀어집니다.
서비스의 런북 또는 배포 노트에 최종 원인을 기록해 두십시오. "systemd 수정"은 나중에 유용하지 않습니다. "배포가 /opt/app/current/bin/server를 실행 권한 없이 생성하여 서비스가 203/EXEC로 실패했습니다"가 유용합니다. 다음 인시던트는 일반적으로 마지막 인시던트와 유사할 것입니다.