파이썬 예약 실행이 안 될 때, launchd 환경부터 확인하는 순서

터미널에서는 실행되는 Python 스크립트가 macOS launchd 예약 실행에서 실패한다면, 먼저 코드보다 실행 환경을 확인해야 합니다. launchd에는 셸의 활성화 상태가 그대로 전달되지 않을 수 있으므로 가상환경 Python의 절대 경로, 작업 디렉터리, 표준 오류 로그를 명시하는 것이 출발점입니다. 이 글에서는 인터프리터, 가상환경, 경로와 환경변수, 로그를 순서대로 좁혀 가는 방법을 설명합니다.

1. 터미널 실행과 launchd 실행을 분리해서 보기

터미널에서 python 또는 python3를 입력하면 현재 셸의 PATH와 활성화된 가상환경을 이용합니다. 반면 launchd 작업은 plist에 지정된 실행 정보와 작업 환경을 기준으로 시작합니다. 그래서 같은 스크립트라도 서로 다른 Python이 선택되거나, 상대 경로가 다른 위치를 가리킬 수 있습니다.

운영 관점에서 먼저 보는 질문은 하나입니다. “스크립트가 실패한 것인가, 아니면 스크립트가 실행되기 전 환경에서 실패한 것인가?” 실행 파일 경로와 로그를 고정하면 이 둘을 구분하기 쉬워집니다.

2. LaunchAgent 위치와 실행 프로그램 확인

사용자 세션에서 실행할 작업이라면 Apple 문서가 설명하는 사용자 LaunchAgent 위치인 ~/Library/LaunchAgents에 plist를 둡니다. plist의 ProgramArguments는 실행할 프로그램과 인자를 지정하는 항목이므로, 셸 명령 한 줄을 막연히 적기보다 실행 파일과 스크립트 경로를 각각 명시하는 편이 진단에 유리합니다.

예시는 다음처럼 구성할 수 있습니다. 아래의 /Users/yourname/project는 실제 프로젝트 경로로 바꿔야 합니다.

<key>ProgramArguments</key>
<array>
    <string>/Users/yourname/project/.venv/bin/python</string>
    <string>/Users/yourname/project/run_job.py</string>
</array>

여기서 중요한 것은 python3라는 이름에 기대지 않고, 실행에 사용할 인터프리터를 하나로 고정하는 것입니다. 경로가 실제로 존재하는지, 해당 Python으로 스크립트를 실행할 수 있는지는 예약 등록 전에 같은 절대 경로로 확인합니다.

3. 가상환경 활성화 대신 Python 절대 경로 지정

Python 공식 문서에 따르면 가상환경 활성화는 해당 환경의 bin 디렉터리를 PATH 앞에 추가하는 동작입니다. 또한 가상환경은 활성화하지 않고도 환경 안의 Python 인터프리터 전체 경로를 직접 지정해 실행할 수 있습니다. 예약 작업에서는 두 번째 방식이 환경 차이를 줄이는 데 적합합니다.

/Users/yourname/project/.venv/bin/python \
  /Users/yourname/project/run_job.py

실행 중인 인터프리터가 가상환경인지 확인해야 한다면 sys.prefix와 sys.base_prefix를 비교할 수 있습니다. 둘이 다르면 현재 인터프리터가 가상환경을 가리키는지 확인하는 단서가 됩니다.

import sys

print(sys.executable)
print(sys.prefix)
print(sys.base_prefix)

VIRTUAL_ENV만 보고 판단하는 것은 충분하지 않습니다. Python 문서가 설명하듯 이 변수는 활성화 과정에서 설정되는 값이므로, 활성화 없이 절대 경로로 실행한 경우에는 가상환경 사용 여부의 유일한 판정 근거가 될 수 없습니다.

4. 작업 디렉터리와 환경변수 명시

스크립트가 config/settings.json처럼 상대 경로를 사용한다면, 터미널에서의 현재 디렉터리와 예약 작업의 현재 디렉터리가 다를 때 실패할 수 있습니다. Apple 문서의 WorkingDirectory 항목으로 작업 디렉터리를 지정하고, 코드에서는 가능하면 기준 경로를 명확히 계산합니다.

<key>WorkingDirectory</key>
<string>/Users/yourname/project</string>

API 키 같은 비밀값은 plist나 글에 실값을 적지 말고 운영 환경의 안전한 주입 방식을 사용해야 합니다. 필요한 환경변수가 없다면 코드 오류처럼 보이는 예외가 먼저 나타날 수 있으므로, 예약 실행에 필요한 변수 목록을 별도로 확인합니다. 다만 이 글의 범위에서는 특정 프로젝트의 비밀값이나 서버 설정을 전제로 하지 않습니다.

5. 표준 출력·오류 로그를 먼저 열어 보기

예약 실행이 조용히 끝난다면 성공으로 간주하지 말고 stdout과 stderr를 파일로 남깁니다. Apple 문서가 설명하는 StandardOutPath와 StandardErrorPath를 plist에 지정하면 프로그램이 출력한 내용과 오류를 분리해서 확인할 수 있습니다.

<key>StandardOutPath</key>
<string>/Users/yourname/project/logs/job.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/yourname/project/logs/job.err.log</string>

오류 로그에서 먼저 볼 순서는 실행 파일을 찾지 못했다는 메시지, 모듈·파일을 찾지 못했다는 메시지, 환경변수 누락입니다. 로그가 아예 생기지 않는다면 Python 코드 내부보다 plist 경로, 실행 권한, 로그 디렉터리의 존재 여부를 먼저 확인합니다. 로그 파일을 애플리케이션 로그처럼 설계하고 싶다면 파이썬 로그 콘솔·파일 레벨 분리 글도 다음 읽을거리로 참고할 수 있습니다.

6. 최소 readback으로 경계를 좁히기

처음부터 실제 업무를 모두 실행하지 말고, 예약 작업이 다음 정보를 남기는 최소 스크립트로 경계를 좁혀 보십시오.

from pathlib import Path
import sys

Path("readback.txt").write_text(
    f"executable={sys.executable}\n"
    f"prefix={sys.prefix}\n"
    f"base_prefix={sys.base_prefix}\n",
    encoding="utf-8",
)

이 파일의 생성 여부와 내용으로 작업이 시작되었는지, 어떤 Python이 선택되었는지, 작업 디렉터리가 예상과 맞는지 확인할 수 있습니다. 그 다음에 실제 API 호출이나 파일 처리 단계를 한 번에 하나씩 붙이면 실패 지점을 빠르게 좁힐 수 있습니다.

마무리: 확인 순서를 고정하면 재현성이 생깁니다

파이썬 예약 실행 안됨 문제는 다음 순서로 확인하는 것이 안전합니다.

  1. 사용자 LaunchAgent와 plist가 맞는지 확인합니다.
  2. ProgramArguments에 가상환경 Python과 스크립트의 절대 경로를 지정합니다.
  3. WorkingDirectory와 필요한 환경변수를 확인합니다.
  4. StandardOutPath와 StandardErrorPath로 실행 결과를 남깁니다.
  5. 최소 readback으로 인터프리터와 경로를 확인한 뒤 본 작업을 붙입니다.

이 순서는 Python 코드가 잘못됐다는 결론을 서두르지 않게 해 줍니다. 반대로 readback은 정상인데 본 작업만 실패한다면, 그때부터는 해당 코드의 입력·권한·외부 서비스 의존성을 별도 문제로 좁혀 가면 됩니다.

참고한 공식 자료

Leave a Comment