실패한 Systemd 서비스 문제 해결: 시스템 관리자를 위한 실용 가이드

Systemd 서비스는 현대 리눅스 시스템의 핵심이지만, 실패할 수 있습니다. 이 실용 가이드는 시스템 관리자가 일반적인 systemd 서비스 실패를 체계적으로 문제 해결하고 해결할 수 있도록 지원합니다. `journalctl`을 사용한 로그 분석, 종속성 문제 진단, 종료 코드 해석, 웹 서버, 데이터베이스 등에 대한 특정 수정 사항을 적용하여 서비스 기능을 신속하게 복원하는 방법을 배우게 됩니다.

실패한 Systemd 서비스 문제 해결: 시스템 관리자를 위한 실용 가이드

실패한 systemd 서비스는 일반적으로 처음 보이는 것보다 덜 신비롭습니다. 유용한 증거는 이미 시스템에 있습니다: 유닛 정의, systemd가 실행하려고 시도한 정확한 명령, 종료 상태, 그리고 실패 주변의 저널 라인입니다. 핵심은 메시지가 바뀌길 바라며 서비스를 열 번 재시작하는 대신 올바른 순서로 읽는 것입니다.

저는 보통 세 가지 질문으로 시작합니다: systemd가 유닛을 찾았는지, 프로세스가 시작되었는지, 애플리케이션 자체가 구성이나 환경을 거부했는지입니다. 아래 명령어는 그 조사를 현실적으로 유지합니다.

Systemd 서비스 실패 이해하기

systemd 서비스가 시작에 실패하거나 예기치 않게 충돌할 때, 종종 다양한 이유가 있습니다. 이는 단순한 구성 오류, 누락된 종속성, 리소스 제한, 서비스 자체의 버그에 이르기까지 다양할 수 있습니다. Systemd는 이러한 실패의 정확한 원인을 파악하는 데 도움이 되는 강력한 메커니즘을 제공합니다.

서비스 실패의 일반적인 원인:

  • 구성 오류: 서비스의 .service 유닛 파일 또는 관련 구성 파일의 잘못된 설정.
  • 누락된 종속성: 서비스가 사용할 수 없거나 아직 시작되지 않은 다른 시스템 리소스(네트워크, 다른 서비스, 특정 파일 시스템 등)에 의존합니다.
  • 리소스 고갈: 서비스가 시스템이 제공할 수 있는 것보다 더 많은 메모리, CPU 또는 디스크 I/O를 필요로 합니다.
  • 권한 문제: 서비스 프로세스에 필요한 파일, 디렉터리 또는 네트워크 포트에 액세스하는 데 필요한 권한이 없습니다.
  • 서비스의 버그: 애플리케이션 자체에 시작 또는 작동 중 충돌을 일으키는 버그가 있습니다.
  • 손상된 데이터: 서비스에서 사용하는 필수 데이터 파일이 손상되었습니다.
  • 네트워크 문제: 네트워크 인터페이스, DNS 또는 방화벽 규칙 문제로 인해 서비스가 포트에 바인딩하거나 통신하지 못합니다.

1단계: 서비스 상태 확인

실패한 서비스를 문제 해결하는 첫 번째 단계는 현재 상태를 확인하는 것입니다. Systemd의 systemctl 명령이 이를 위한 기본 도구입니다.

systemctl status 사용

systemctl status <service_name>.service 명령은 서비스의 현재 상태, 최근 로그 항목 및 프로세스 정보에 대한 간결한 개요를 제공합니다.

sudo systemctl status nginx.service

예제 출력 (실패한 서비스):

● nginx.service - A high performance web server and reverse proxy
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
     Active: failed (result=exit-code) since Tue 2023-10-27 10:30:00 UTC; 1min ago
       Docs: man:nginx(8)
    Process: 1234 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=1/FAILURE)
   Main PID: 1234 (code=exited, status=1/FAILURE)

Oct 27 10:30:00 your-server systemd[1]: Starting A high performance web server and reverse proxy...
Oct 27 10:30:00 your-server nginx[1234]: nginx: [emerg] bind() to port 80 failed (98: Address already in use)
Oct 27 10:30:00 your-server systemd[1]: nginx.service: Main process exited, code=exited, status=1/FAILURE
Oct 27 10:30:00 your-server systemd[1]: Failed to start A high performance web server and reverse proxy.

systemctl status 출력에서 찾아야 할 주요 정보:

  • Active:: 이 줄은 현재 상태를 나타냅니다. failed는 우리가 관심 있는 상태입니다. failed (result=exit-code) 또는 failed (result=oom-kill)로 표시될 수도 있습니다. result는 종종 단서를 제공합니다.
  • Process:: systemd가 실행하려고 시도한 프로세스에 대한 세부 정보. code=exited, status=...가 표시되면 중요합니다.
  • 로그 항목: 가장 최근 로그 라인에는 서비스의 직접적인 오류 메시지가 포함되는 경우가 많습니다.

2단계: journalctl로 로그 분석

journalctl 명령은 systemd 저널에서 로그를 쿼리하고 표시하기 위한 systemd의 강력한 도구입니다. 서비스가 실패한 이유에 대한 자세한 통찰력을 얻는 데 필수적입니다.

서비스에 대한 기본 journalctl 사용법

특정 서비스의 로그를 보려면 -u 플래그를 사용하십시오:

sudo journalctl -u <service_name>.service

실시간으로 로그를 보려면:

sudo journalctl -f -u <service_name>.service

마지막 부팅 이후의 로그를 보려면 (시작 중 실패한 서비스에 유용):

sudo journalctl -b -u <service_name>.service

특정 시간 이후의 로그를 보려면:

sudo journalctl --since "2023-10-27 10:00:00" -u <service_name>.service

journalctl 출력 해석

애플리케이션이나 systemd 자체에서 보고한 오류 메시지, 스택 추적 또는 특정 오류 코드를 찾으십시오. systemctl status의 예제 출력은 이미 주요 오류를 보여주었습니다: bind() to port 80 failed (98: Address already in use). 이는 다른 프로세스가 이미 포트 80을 사용하고 있어 Nginx가 시작되지 못했음을 명확히 나타냅니다.

팁: 서비스가 매우 장황한 경우 출력을 제한할 수 있습니다:

sudo journalctl -n 50 -u <service_name>.service  # 마지막 50줄 표시

3단계: 서비스 종속성 및 요구 사항 확인

Systemd 서비스는 종종 다른 서비스나 시스템 리소스를 사용할 수 있어야 합니다. 종속성이 충족되지 않으면 서비스가 시작되지 않습니다.

종속성 보기

systemctl cat을 사용하고 Requires=, Wants=, After=, Before=, PartOf=와 같은 지시문을 확인하여 서비스의 종속성을 검사할 수 있습니다.

systemctl cat <service_name>.service

예를 들어, 특정 주소에 바인딩되는 서비스는 네트워크가 구성된 후에 순서가 지정되어야 할 수 있습니다. After=network-online.target은 순서만 제어합니다. 자체적으로 해당 대상을 트랜잭션으로 가져오지 않습니다. 서비스가 실제로 필요하다면 종종 둘 다 볼 수 있습니다:

Wants=network-online.target
After=network-online.target

Requires=는 신중하게 사용하십시오. 더 강력한 관계를 생성하며 필수 유닛이 중지되면 서비스를 중지시킬 수 있습니다. 많은 애플리케이션 서비스는 Wants=After=만 있으면 됩니다.

누락된 종속성 확인

systemctl status는 종종 종속성 문제를 나타내지만, 필수 서비스가 활성 상태인지 명시적으로 확인하는 것이 도움이 될 수 있습니다.

systemctl is-active <dependency_service_name>.service

필수 서비스가 마스킹되거나 중지되면 대상 서비스가 시작되지 않을 수 있습니다.

systemctl list-dependencies <service_name>.service

이 명령은 전체 종속성 트리를 보여줍니다.

4단계: 종료 코드 이해

서비스가 실패하면 특정 종료 코드로 종료됩니다. 이 코드는 실패의 성격에 대한 귀중한 정보를 제공합니다.

  • 종료 코드 0: 성공.
  • 종료 코드 1: 많은 프로그램의 일반적인 실패. 구체적인 의미는 애플리케이션에 따라 다릅니다.
  • 종료 코드 127: 명령을 찾을 수 없음 (종종 잘못된 ExecStart 경로 또는 실행 파일 누락으로 인해).
  • 종료 코드 137: SIGKILL에 의해 종료됨. 항상 그런 것은 아니지만 종종 메모리 압력과 관련됩니다.
  • 종료 코드 139: SIGSEGV (세그멘테이션 오류)에 의해 종료됨.

systemctl status 출력에서 status=1/FAILURE를 보았습니다. 이는 일반적인 실패이며, 상태 1로 실패한 이유를 이해하려면 앞의 로그 메시지가 필수적입니다.

OOM 킬 식별

systemctl statusfailed (result=oom-kill)이 표시되면 Linux OOM(Out-Of-Memory) 킬러가 시스템 메모리가 심각하게 부족하여 서비스 프로세스를 종료했음을 의미합니다.

이를 확인하려면 journalctl 또는 dmesg에서 관련 메시지를 찾을 수 있습니다:

dmesg | grep -i oom

OOM 오류 문제 해결

  • 시스템 RAM 증가: 가능한 경우.
  • 메모리 사용량 줄이기: 서비스 또는 다른 실행 중인 프로세스를 최적화합니다.
  • 스왑 구성: 적절한 스왑 공간이 있는지 확인합니다.
  • 서비스 메모리 제한 확인: MemoryMax= 설정은 호스트에 여유 메모리가 있어도 서비스별 OOM을 유발할 수 있습니다.
  • 최근 배포 검토: 메모리 실패는 종종 구성 변경, 트래픽 변경 또는 버전 변경 후에 발생합니다.

5단계: Systemd가 실제로 사용하는 유닛 파일 확인

편집기의 파일이 완전한 유닛이라고 가정하지 마십시오. 패키지, 드롭인 및 재정의가 최종 정의로 결합될 수 있습니다:

systemctl cat <service_name>.service
systemctl show <service_name>.service -p FragmentPath -p DropInPaths

이것은 일반적인 문제를 잡아냅니다: 누군가 /usr/lib/systemd/system/app.service를 편집했지만 /etc/systemd/system/app.service.d/override.conf의 재정의가 여전히 Environment= 또는 ExecStart=를 변경합니다.

유닛 파일 또는 드롭인을 편집한 후 systemd를 다시 로드하십시오:

sudo systemctl daemon-reload

이 단계를 잊으면 systemctl restart가 이전 유닛 정의를 계속 사용할 수 있습니다.

6단계: 일반적인 서비스별 문제 및 수정

위 단계는 일반적이지만 특정 서비스에는 일반적인 실패 모드가 있습니다.

웹 서버 (Nginx, Apache)

  • 포트 이미 사용 중: 예제에서 본 것처럼 다른 프로세스가 포트 80 또는 443에서 수신 대기 중일 수 있습니다. sudo ss -tulnp | grep :80을 사용하여 문제가 되는 프로세스를 찾으십시오.
  • 구성 구문 오류: 웹 서버의 구성 테스트를 실행하십시오 (예: sudo nginx -t 또는 sudo apachectl configtest).
  • SSL 인증서 누락: 인증서 파일이 있고 읽을 수 있는지 확인하십시오.

데이터베이스 (MySQL, PostgreSQL)

  • 데이터 디렉터리 권한: 데이터베이스 사용자가 데이터 디렉터리에 대해 올바른 읽기/쓰기 액세스 권한을 가지고 있는지 확인하십시오.
  • 손상된 데이터 파일: 백업에서 복원하거나 데이터베이스별 복구 도구를 사용해야 할 수 있습니다.
  • 디스크 공간 부족: 데이터베이스는 상당한 디스크 공간을 소비할 수 있습니다.

네트워킹 서비스

  • 잘못된 IP 주소 또는 호스트 이름: 네트워크 구성을 확인하십시오.
  • 방화벽 규칙: 필요한 포트가 열려 있는지 확인하십시오.
  • DNS 확인 문제: /etc/resolv.conf 및 네트워크 연결을 확인하십시오.

7단계: 고급 문제 해결 기술

서비스 재활성화 및 다시 시작

변경 후 필요한 경우 유닛을 다시 로드한 다음 서비스를 다시 시작하십시오. 부팅 동작을 변경하지 않는 한 매번 enable을 실행할 필요는 없습니다.

sudo systemctl daemon-reload # systemd 관리자 구성 다시 로드
sudo systemctl restart <service_name>.service

systemctl --failed 사용

이 명령은 현재 실패 상태에 있는 모든 유닛을 나열합니다.

systemctl --failed

리소스 제한 확인 (ulimit)

일부 서비스는 운영 체제 수준의 리소스 제한에 도달하면 실패할 수 있습니다. 서비스가 실행되는 사용자로 ulimit -a를 사용하여 제한을 확인하거나 유닛 파일에서 systemd의 자체 리소스 제어 지시문을 확인하십시오.

systemd 관리 서비스의 경우 유닛 속성이 대화형 셸의 ulimit보다 더 관련성이 높은 경우가 많습니다:

systemctl show <service_name>.service -p LimitNOFILE -p User -p Group -p MemoryMax -p TasksMax

애플리케이션에서 too many open files라고 표시되면 LimitNOFILE을 애플리케이션의 연결 수 및 파일 사용량과 비교하십시오. 서비스가 스레드나 자식 프로세스를 생성할 수 없으면 TasksMax를 확인하십시오.

디버깅 플래그

많은 애플리케이션에는 .service 파일의 ExecStart 라인에서 명령줄 인수를 통해 활성화할 수 있는 디버그 모드 또는 자세한 로깅이 있습니다. 애플리케이션의 문서를 참조하십시오.

빠른 예: 서비스가 수동으로 작동하지만 부팅 시 실패

이것은 가장 일반적인 systemd 불만 사항 중 하나입니다. 개발자가 명령을 직접 실행하면 작동합니다. 동일한 명령이 서비스로 실패합니다. 일반적인 차이점은 환경입니다.

서비스 사용자와 작업 디렉터리를 확인하십시오:

systemctl show myapp.service -p User -p Group -p WorkingDirectory
systemctl cat myapp.service

그런 다음 앱에서 가정을 찾으십시오: 상대 경로, 홈 디렉터리의 파일, .bashrc의 환경 변수 또는 대화형 셸에서 로드한 자격 증명. systemd는 서비스에 대해 셸 시작 파일을 읽지 않습니다. 앱에 APP_ENV=production 또는 DATABASE_URL=...이 필요한 경우 해당 구성을 Environment=, EnvironmentFile= 또는 일반적인 비밀 관리 경로를 사용하여 유닛에 넣으십시오.

부팅 전용 실패는 순서 문제일 수도 있습니다. 서비스가 DNS, 네트워크 주소 또는 마운트된 파일 시스템이 준비되기 전에 시작될 수 있습니다. 애플리케이션에서 맹목적인 sleep으로 수정하지 마십시오. 유닛에서 종속성을 표현하십시오:

Wants=network-online.target
After=network-online.target
RequiresMountsFor=/srv/myapp

RequiresMountsFor=는 서비스에 특정 경로가 필요할 때 유용하며, 특히 해당 경로가 별도의 디스크나 네트워크 마운트에서 오는 경우 더욱 그렇습니다. 광범위한 대상이 먼저 완료되기를 바라는 것보다 더 명확합니다.

실패 상태 재설정

서비스가 실패한 후 systemd는 재설정되거나 유닛이 성공할 때까지 실패 상태를 기억합니다. 이는 가시성에 도움이 되지만 문제를 이미 해결한 후 상태 확인을 혼동할 수 있습니다:

sudo systemctl reset-failed myapp.service
sudo systemctl restart myapp.service
systemctl status myapp.service

필요한 증거를 확보한 후 reset-failed를 사용하십시오. 인시던트 중에는 실패 상태와 저널 타임스탬프가 유용한 단서입니다.

시끄러운 실패 후에 도움이 되는 또 하나의 작은 습관: 아무것도 편집하기 전에 유닛이 재시작 루프에 빠져 있는지 확인하십시오.

systemctl show myapp.service -p NRestarts -p RestartUSec

재시작 횟수가 빠르게 증가하면 조사하는 동안 유닛을 중지하십시오. 이렇게 하면 종속성이 반복적인 잘못된 연결로부터 보호되고 저널을 읽을 수 있게 유지됩니다.

신뢰할 수 있는 패턴은 다음과 같습니다: status를 읽고, 저널을 읽고, systemctl cat으로 효과적인 유닛을 검사하고, 종속성과 경로를 확인한 다음, 무엇이 변경되었는지 안 후에만 다시 시작하십시오. 이렇게 하면 systemd 문제 해결이 지루해지며, 이는 중단 중에 정확히 원하는 것입니다.