Infrastructure
Bell  

systemd 서비스가 갑자기 멈췄을 때 — status=126, ExecStart 권한 문제 트러블슈팅

📌 핵심 요약

systemd 서비스가 status=126 또는 Main process exited, code=exited, status=203/EXEC로 실패하고 있다면, ExecStart에 지정된 스크립트의 실행 권한(+x)이 사라졌을 가능성을 먼저 확인하세요. git checkout, git reset, 파일 재생성 등이 원인일 수 있습니다.


상황

어느 날 아침, 매시간 돌아야 할 데이터 수집 파이프라인이 약 8시간째 멈춰있다는 걸 발견했습니다.

$ systemctl status data-pipeline.service
● data-pipeline.service - News Pipeline
     Loaded: loaded (/etc/systemd/system/data-pipeline.service; enabled)
     Active: failed (Result: exit-code)
    Process: 12345 ExecStart=/opt/pipeline/pipeline.sh (code=exited, status=126)

status=126은 처음 보는 코드였습니다.


원인 파악

Exit Code 126이란?

126은 POSIX shell에서 정의된 종료 코드로, 명령은 찾았지만 실행할 수 없음(permission denied)을 뜻합니다.

코드 의미
126 Command found but not executable
127 Command not found
128+N Signal N으로 종료

systemd에서 status=203/EXEC는 ExecStart 프로세스를 아예 시작조차 못 했을 때 나타납니다. 두 코드 모두 실행 권한 문제를 강하게 시사합니다.

확인

$ ls -la /opt/pipeline/
-rw------- 1 bell bell 2048 Feb 19 06:00 pipeline.sh
-rw------- 1 bell bell 1024 Feb 19 06:00 collect.sh
-rw------- 1 bell bell  512 Feb 19 06:00 curate.sh

-rwx------여야 할 스크립트들이 모두 -rw-------였습니다. 실행 비트(x)가 모두 사라진 상태.


조치

chmod +x /opt/pipeline/pipeline.sh \
         /opt/pipeline/collect.sh \
         /opt/pipeline/curate.sh

이후 수동 실행으로 status=0/SUCCESS 확인:

$ systemctl start data-pipeline.service
$ systemctl status data-pipeline.service
Active: inactive (dead)
...
Process: 12400 ExecStart=/opt/pipeline/pipeline.sh (code=exited, status=0/SUCCESS)

원인 추적 — 왜 실행 권한이 사라졌나?

정확한 시점을 확인하지 못했지만, 몇 가지 가능성을 정리했습니다.

1. git checkout / git reset

Git은 파일 권한 중 실행 비트(+x)만 추적하고, 소유자/그룹 등은 무시합니다. 만약 해당 스크립트를 Git으로 관리하고 있고, 어느 시점에 +x 없이 커밋된 상태가 있었다면 git checkout이나 git reset --hard가 권한을 초기화했을 수 있습니다.

# Git이 추적하는 권한 확인
$ git ls-files --stage pipeline.sh
100644 a1b2c3... 0   pipeline.sh   # 실행 비트 없음 (644)
100755 a1b2c3... 0   pipeline.sh   # 실행 비트 있음 (755)

커밋 시 실행 비트를 함께 설정하려면:

$ git update-index --chmod=+x pipeline.sh
$ git commit -m "fix: restore execute permission on pipeline.sh"

2. 파일 재생성

일부 텍스트 편집기나 CI/CD 파이프라인이 파일을 삭제 후 재생성하는 경우 기본 umask(보통 0022)가 적용되어 실행 비트가 빠질 수 있습니다.

3. 잘못된 배포 스크립트

cp, rsync, scp 등을 사용한 배포 시 권한 유지 옵션을 빠뜨린 경우:

# 권한 유지 없음 (위험)
cp pipeline.sh /opt/pipeline/

# 권한 유지 (권장)
cp -p pipeline.sh /opt/pipeline/
rsync -a pipeline.sh /opt/pipeline/

재발 방지 전략

1. Git에서 실행 비트 관리

스크립트를 Git으로 관리한다면, 처음부터 실행 비트 포함 커밋:

git update-index --chmod=+x pipeline.sh collect.sh curate.sh

2. systemd 서비스 진단 루틴

서비스 장애 시 체크리스트:

# 1. 종료 코드 확인
systemctl status <service>.service

# 2. 상세 로그 확인
journalctl -u <service>.service -n 50 --no-pager

# 3. 실행 파일 권한 확인
ls -la $(systemctl cat <service>.service | grep ExecStart | awk '{print $2}')

# 4. 실행 테스트
sudo -u <서비스 실행 유저> /path/to/script.sh

3. 배포 후 권한 검증 추가

배포 스크립트나 Makefile에 검증 단계를 추가하면 조기에 문제를 발견할 수 있습니다:

# Makefile 예시
deploy:
    rsync -a --delete ./scripts/ /opt/pipeline/
    @echo "Verifying execute permissions..."
    @for f in /opt/pipeline/*.sh; do \
        [ -x "$$f" ] || (echo "ERROR: $$f is not executable!" && exit 1); \
    done
    @echo "All scripts executable. ✓"
    systemctl restart pipeline.service

4. 모니터링 — 다음 번엔 빨리 감지하기

이번에는 약 8시간 후에 발견했습니다. 더 빠르게 감지하려면:

# /etc/systemd/system/data-pipeline.service 에 OnFailure 추가
[Unit]
OnFailure=notify-failure@%n.service

또는 systemd의 Restart=on-failureRestartSec을 조합해 자동 재시도:

[Service]
Restart=on-failure
RestartSec=60

[Unit]
# StartLimit 옵션은 [Unit] 섹션에 위치 (systemd v230+)
StartLimitIntervalSec=300
StartLimitBurst=3

단, 실행 권한 문제는 재시도해도 해결되지 않으므로 재시도 후에도 실패하면 반드시 알림이 가도록 구성해야 합니다.


트레이드오프

방법 장점 단점
Git --chmod=+x 히스토리 추적 가능, 일관성 Git 초보자에게 낯선 옵션
배포 후 chmod 스크립트 단순, 확실 배포 절차에 의존 → 수동 배포 시 누락 가능
Restart=on-failure 일시적 장애 자동 복구 권한 문제엔 무효, 로그 노이즈 증가
CI/CD 권한 검증 단계 배포 시점에 즉시 발견 CI/CD 구성 필요

마치며

status=126은 사실 매우 명확한 오류 코드입니다. 하지만 처음 마주쳤을 때는 "왜 갑자기?"라는 의문이 앞서서 다른 곳부터 뒤지게 됩니다. 결론은 단순했습니다 — ls -la 한 줄로 바로 찾을 수 있는 문제였죠.

중요한 건 재발 방지입니다. Git 권한 관리, 배포 스크립트 검증, 빠른 장애 감지를 함께 챙기면 같은 실수를 반복하지 않을 수 있습니다.


참고 자료

조회수: 0

댓글 남기기