같은 명령이어도 실행 조건은 다를 수 있다

프로젝트 폴더에서 python3 job.py를 실행하면 정상인데, 예약 작업에서는 설정 파일을 찾지 못한다. 이런 문제를 만나면 먼저 예약 시간이 틀렸는지와 프로그램이 시작한 뒤 실패했는지를 구분해야 한다. 프로그램 오류가 남아 있다면 실행 당시의 조건을 비교할 차례다.

터미널에서 성공했다는 사실에는 현재 디렉터리와 셸에 설정한 환경변수 같은 조건이 함께 들어 있다. 명령 한 줄만 옮겼다고 그 조건까지 같아지는 것은 아니다. 이번에는 작은 Python 프로그램을 만들고 세 가지를 각각 바꿨다. 현재 작업 디렉터리, 프로그램이 요구하는 환경변수, 보조 명령을 찾는 PATH다.

실제 cron 작업을 등록하거나 운영 스크립트를 실행하지 않았다. Python의 subprocess.run에 cwd와 env를 지정해 조건 차이를 만든 격리 실험이다. 따라서 아래 결과는 특정 cron 서비스의 장애 기록이 아니다.

실행 위치를 바꾸자 옆에 있는 설정 파일을 못 찾았다

임시 app 디렉터리에 job.py와 settings.json을 나란히 만들었다. 다른 디렉터리 elsewhere에는 아무 설정 파일도 두지 않았다. 설정을 읽는 첫 코드는 다음과 같다.

from pathlib import Path
import json

settings = json.loads(Path('settings.json').read_text())

Python 실행 파일과 job.py는 두 경우 모두 같은 절대 경로로 호출했다. 바꾼 것은 자식 프로세스의 작업 디렉터리뿐이다. app에서 시작하면 settings-ok를 읽었지만, elsewhere에서 시작하면 FileNotFoundError가 발생했다.

스크립트의 위치를 절대 경로로 적어도, 코드 안의 상대 경로가 자동으로 스크립트 폴더를 기준으로 바뀌지는 않았다. 이 설정 파일을 항상 스크립트와 함께 배치한다는 전제에서는 기준을 다음처럼 정할 수 있다.

from pathlib import Path
import json

base = Path(__file__).resolve().parent
settings = json.loads((base / 'settings.json').read_text())

바꾼 코드로 elsewhere에서 시작했을 때는 같은 설정값을 읽었다. 다만 이것이 모든 파일의 경로를 정하는 규칙은 아니다. resolve()는 심볼릭 링크를 따라가므로 링크가 놓인 폴더와 실제 파일의 폴더가 다를 수 있다. 외부 설정 파일이나 배포 후에도 유지해야 하는 출력 데이터는 별도의 명시적인 경로로 받는 편이 적합할 수 있다. 이번 실험은 일반 파일과 읽기만 사용했다.

작업 폴더를 맞춰도 환경변수와 명령 검색은 따로 남는다

두 번째 비교에서는 작업 폴더를 고정하고 합성 환경변수 PUNCHBLOG_LAB_REGION만 바꿨다. 코드가 os.environ['PUNCHBLOG_LAB_REGION']을 읽을 때 변수가 없으면 KeyError, lab-region을 제공하면 정상 반환이었다. 작업 폴더를 바꾸는 조치로 환경변수 누락까지 해결되지는 않는다.

세 번째 비교에서는 helper-ok만 출력하는 작은 보조 명령을 만들었다. 이 파일이 있는 폴더를 PATH에 넣으면 이름만으로 실행할 수 있었다. PATH를 실험용 빈 폴더로 지정하면 같은 호출은 FileNotFoundError를 냈다. 보조 명령의 절대 경로를 지정하면 그 빈 PATH 조건에서도 성공했다.

여기서 빈 PATH 폴더는 검색 실패를 확실히 구분하려고 만든 조건이다. cron의 실제 기본 PATH가 비어 있다는 주장이 아니다. 보조 명령도 /bin/sh를 명시하고 셸 내장 printf만 사용했으므로, 절대 경로로 실행한 일반 프로그램의 모든 내부 의존성이 해결된다는 뜻은 아니다.

여덟 번의 실행 결과

Linux, Python 3.10.12 환경에서 2026-09-19에 관찰했다. 세 실패는 실험 스크립트가 예상한 예외를 잡아 종류를 기록하고 종료 코드 1을 반환하게 했다. 예외를 잡지 않은 실제 애플리케이션의 전체 오류 출력과는 다르다.

직접 관찰한 결과 · 2026-09-19
조건종료 코드확인한 결과
상대 설정 경로, app에서 실행0settings-ok
상대 설정 경로, elsewhere에서 실행1FileNotFoundError
스크립트 기준 설정 경로, elsewhere에서 실행0settings-ok
필수 환경변수 없음1KeyError
필수 환경변수 명시0lab-region
보조 명령 폴더가 PATH에 있음0helper-ok
PATH가 실험용 빈 폴더1FileNotFoundError
빈 PATH, 보조 명령을 절대 경로로 호출0helper-ok

FileNotFoundError라는 이름만 보고 설정 파일 문제라고 단정하면 안 된다. 이번에도 파일 읽기 실패와 보조 명령 실행 실패가 같은 예외 종류였다. 어느 줄에서 무엇을 찾다가 실패했는지까지 봐야 한다.

실제 작업에서는 시작 직후의 조건을 좁혀 기록한다

다음은 적용 예시이며 이번 실험의 측정값은 아니다. 문제가 있는 작업의 시작 부분에 짧게 넣어 직접 실행과 예약 실행의 로그를 비교할 수 있다.

import json
import os
import shutil
import sys
from pathlib import Path

print(json.dumps({
    'cwd': str(Path.cwd()),
    'python': sys.executable,
    'settings_exists': Path('settings.json').is_file(),
    'region_present': 'PUNCHBLOG_LAB_REGION' in os.environ,
    'helper_path': shutil.which('punchblog-lab-helper'),
}), flush=True)

변수와 명령 이름은 실제 애플리케이션의 것으로 바꾼다. 환경변수 전체를 출력하기보다 필요한 값의 존재 여부부터 기록한다. 예시의 파일 존재 여부는 상대 경로를 기준으로 하며, 파일을 실제로 읽을 권한이나 JSON 내용의 유효성을 보장하지 않는다.

확인 뒤에는 실패 지점에 맞는 조건을 정한다. 상대 파일 경로가 문제라면 시작 폴더를 명시하거나 설정 경로를 인자로 받는다. 필수 환경변수가 없다면 작업 실행 방식에 맞춰 필요한 설정을 전달하고, 누락 시 원인을 알아볼 수 있게 종료한다. 다른 Python이 선택된다면 사용할 인터프리터를 명시한다. 보조 명령 검색이 문제라면 확인한 실행 경로 또는 의도한 PATH를 사용한다.

가상환경을 쓰는 작업에서는 해당 환경의 Python 경로도 확인 대상이다. 다만 이번 실험은 하나의 Python을 계속 사용했으며 가상환경과 패키지 차이를 재현하지 않았다.

증상먼저 확인수정 방향
설정 파일을 못 찾음cwd와 실제 설정 경로명시적 작업 디렉터리 또는 검증된 절대 경로
필수 설정이 없다고 종료필요한 환경변수의 존재 여부실행 주체에 맞게 필수 설정 전달
하위 명령을 못 찾음PATH와 사용할 실행 파일 위치확인한 실행 파일 경로 또는 제한된 PATH 지정
터미널만 성공실행 계정·권한과 위 조건 차이예약 작업의 실제 조건으로 별도 재확인

진단 로그는 필요한 변수의 존재 여부와 비밀값이 아닌 실행 조건만 남긴다. env 전체나 접근 토큰을 기록할 필요는 없다. 이 표의 환경 실험이 실제 cron의 시작·메일·스케줄 해석까지 검사했다는 의미는 아니다.

cron 자체에서 확인할 것은 따로 있다

참고한 Cronie crontab(5) 매뉴얼은 작업 명령을 기본적으로 /bin/sh 또는 지정한 SHELL로 실행한다고 설명한다. 터미널의 셸 문법과 예약 실행에 쓰이는 셸이 같은지도 확인할 항목이다. 설치된 cron 구현과 설정이 다를 수 있으므로 실제 서버의 매뉴얼을 기준으로 점검한다.

이 실험으로 작업 등록 오류, 실행 시각과 시간대, 권한, 네트워크, 중복 실행까지 진단할 수는 없다. 특히 프로그램이 시작조차 하지 않았다면 여기의 파일 경로 수정만으로 해결되지 않는다. 실행 기록이 있는지를 먼저 확인하고, 실행됐다면 작업 디렉터리와 필요한 환경부터 비교한다.

작업이 두 번 겹쳐 실행되는 문제는 아래 이어서 읽기의 예약 작업 잠금 글에서 별도로 다룬다. 이번 글의 범위는 한 번의 실행에 필요한 조건이 달라지는 경우다.

실험 환경과 원자료

관찰 시각: 2026-09-19T02:07:46.997170+00:00
os: Linux · python: 3.10.12

같은 Python과 스크립트의 절대 경로를 사용해 자식 프로세스를 8번 실행했다. 작업 디렉터리와 합성 환경변수를 명시했으며 운영 환경변수는 상속하지 않았다.

확인하지 않은 범위: cron이나 systemd 타이머의 실제 실행, 운영 작업, 로그인 셸, 가상환경, 권한과 시간대 차이는 시험하지 않았다. 빈 PATH 폴더는 실패 조건을 구분하려고 만든 실험용 설정이다.

Python 3과 /bin/sh가 있는 Linux에서 실행합니다. 관리자 권한과 네트워크 접근은 필요하지 않습니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.

python3 lab-job-environment.py --output new-observations.json

참고한 공식 문서

원고 수정: 2026-09-22 · 정정 요청

이 글의 보완 기록

  • · 증상별 환경 확인과 수정 방향을 정리하고 비밀값 없는 진단 범위를 명시했다.