Published on

linux timeout command 사용법: 실행 시간 제한과 종료 신호 정리

Authors
  • avatar
    Name
    Indo Yoon
    Twitter

리눅스에서 끝나지 않을 수 있는 작업에 실행 시간 상한을 두고 싶다면 timeout 명령어가 가장 간단한 답입니다. timeout은 지정한 시간이 지나면 실행 중인 명령에 신호를 보내 작업을 중단시키고, 시간 초과 여부를 종료 코드로 알려줍니다. 이 글은 배포·운영 스크립트에서 timeout을 안전하게 쓰는 방법과 신호 선택 기준, 종료 코드 해석, 파이프라인과 컨테이너 환경에서의 주의점을 정리합니다.

한눈에 보기

timeout은 "이 명령은 정해진 시간 안에서만 실행한다"는 의도를 한 줄로 표현하는 도구입니다. 배포 스크립트, 헬스 체크, 외부 API 호출, 데이터 마이그레이션처럼 끝나지 않을 가능성이 있는 명령에 상한을 두면 파이프라인이 무한 대기 상태로 멈추는 일을 줄일 수 있습니다.

  • 실행 형식은 timeout [옵션] DURATION COMMAND [인자...]입니다.
  • DURATION에 단위를 붙이지 않으면 초로 해석되고, s, m, h, d 접미사로 단위를 명시할 수 있습니다.
  • 시간 초과 시 기본 동작은 TERM 신호 전송이며, 이때 종료 코드 124를 반환합니다.
  • -k(또는 --kill-after)를 함께 쓰면 TERM으로 끝나지 않는 프로세스에 KILL 신호를 추가로 보냅니다.
  • --preserve-status를 쓰면 시간 초과 여부와 관계없이 실행한 명령의 종료 코드를 그대로 받습니다.

즉 timeout은 "언제 끊을지"와 "어떻게 끊을지"를 분리해서 지정하는 도구라고 이해하면 됩니다. 상한 시간은 DURATION이, 끊는 방식은 신호 옵션이 담당합니다.

기본 문법과 옵션 비교

자주 쓰는 옵션을 먼저 표로 정리했습니다. 옵션 이름과 축약형이 함께 제공되는 경우가 많으므로 스크립트에서는 읽기 쉬운 긴 이름을 쓰는 편이 좋습니다.

옵션하는 일사용 예
DURATION실행 시간 상한. 단위를 생략하면 초로 해석timeout 30 curl -sS https://example.com/health
-s, --signal시간 초과 시 보낼 신호 지정timeout -s INT 30 ./bin/job
-k, --kill-after신호를 보낸 뒤 추가 유예 시간을 두고 KILL 전송timeout -k 10 30 ./bin/job
--preserve-status시간 초과 시에도 명령의 종료 상태를 유지timeout --preserve-status 30 ./bin/job
--foreground터미널 입력이 필요한 대화형 명령을 위한 모드timeout --foreground 30 ./bin/repl
-v, --verbose시간 초과로 신호를 보냈을 때 진단 메시지 출력timeout -v 30 ./bin/job

여기서 가장 많이 헷갈리는 것이 -k의 위치입니다. -k 뒤의 값은 "첫 신호를 보낸 뒤 얼마나 더 기다릴지"이고, 그 뒤에 오는 DURATION이 전체 상한입니다. timeout -k 10 30 ./bin/job은 "상한에 도달하면 TERM을 보내고, 그로부터 유예 시간이 더 지나도 살아 있으면 KILL"이라는 뜻입니다.

단계별로 적용하는 방법

바로 상한 값을 정하는 대신, 관찰 → 설정 → 안전장치 → 분기 처리 순서로 적용하면 운영 중 사고를 줄일 수 있습니다.

1단계, 실제 소요 시간을 먼저 관찰합니다. time 명령이나 애플리케이션 로그로 평상시 소요 시간과 최악의 경우를 확인한 뒤 상한을 정합니다. 관찰 없이 정한 값은 정상 작업을 잘라내기 쉽고, 그 실패는 대개 배포 중에 처음 드러납니다.

2단계, 정상 범위보다 넉넉한 상한을 잡습니다. 상한은 "평균 소요 시간"이 아니라 "이 시간을 넘으면 사람이 개입해야 하는 시점"을 기준으로 정합니다. 외부 API 대기나 디스크 지연처럼 일시적인 요인이 끼어들 수 있기 때문입니다.

3단계, -k로 강제 종료 안전장치를 둡니다. TERM을 받고도 정리 작업이 끝나지 않는 프로세스가 실제로 존재하므로, 유예 시간을 두고 KILL로 넘어가는 경로를 마련해 둡니다.

4단계, 종료 코드로 분기합니다. 시간 초과와 일반 실패를 같은 메시지로 뭉뚱그리면 원인 파악이 어려워집니다.

#!/usr/bin/env bash
set -uo pipefail

timeout -k 10 120 ./bin/migrate
status=$?

if [ "$status" -eq 0 ]; then
  echo "마이그레이션 완료"
elif [ "$status" -eq 124 ]; then
  echo "상한 시간을 넘겨 중단했습니다" >&2
  exit 1
else
  echo "명령이 실패했습니다: $status" >&2
  exit "$status"
fi

5단계, 상한 값과 결과를 로그에 남깁니다. 같은 작업이 언제 끊겼는지 기록이 있어야 상한을 조정할 근거가 생깁니다.

종료 코드 읽기

timeout을 스크립트에 넣었다면 종료 코드 해석 규칙을 함께 익혀야 합니다. GNU coreutils 매뉴얼 기준으로 자주 만나는 값은 다음과 같습니다.

코드의미
124상한 시간을 넘겨 timeout이 명령을 중단시킴
125timeout 자체가 실패함
126명령을 찾았지만 실행할 수 없음
127명령을 찾을 수 없음
128+N명령이 신호 N을 받고 종료됨. KILL이면 137

여기서 주의할 점은 --preserve-status를 사용하면 124 대신 명령 자신의 종료 코드가 반환된다는 것입니다. 시간 초과를 별도로 감지해야 하는 스크립트라면 이 옵션을 쓰지 않거나, 감지 로직을 다른 방식으로 마련해야 합니다. 또한 KILL로 종료된 경우 137이 반환될 수 있으므로 "중단"을 판별할 때 124와 137을 함께 고려하는 편이 안전합니다.

상황별 선택: 신호와 유예 시간

신호 선택은 애플리케이션이 정리 작업을 얼마나 필요로 하는지에 달려 있습니다.

TERM은 기본값이자 가장 무난한 선택입니다. 대부분의 서버 애플리케이션은 TERM을 받으면 커넥션을 닫고 진행 중인 요청을 마무리하는 정상 종료 절차를 수행합니다. 즉시 중단이 자연스러운 대화형 도구나 배치성 작업에는 INT를 지정하는 편이 의도가 분명해집니다. 사용자가 중단 키를 누른 것과 비슷한 경로를 타기 때문입니다.

KILL은 정리 작업을 허용하지 않습니다. 임시 파일, 잠금, 열린 트랜잭션이 남을 수 있으므로 첫 신호로 쓰기보다는 -k를 통한 최후 수단으로 두는 편이 안전합니다. 유예 시간은 애플리케이션의 정상 종료에 필요한 시간보다 길게 잡아야 하며, 그보다 짧으면 정리 도중에 강제 종료가 걸립니다.

파이프라인에서는 적용 범위를 확인해야 합니다. timeout 30 curl ... | tee out.log 형태에서 상한이 걸리는 대상은 timeout이 직접 실행하는 첫 번째 명령입니다. 파이프라인 전체에 상한을 걸고 싶다면 timeout 30 sh -c 'cmd1 | cmd2'처럼 셸을 한 겹 감싸야 합니다.

대화형 프로그램이라면 --foreground를 검토합니다. 이 모드에서는 명령이 터미널에 접근할 수 있게 되지만, 매뉴얼이 안내하듯 명령의 자식 프로세스에는 시간 초과 신호가 전달되지 않을 수 있습니다. 따라서 자식까지 정리해야 하는 작업에는 적합하지 않습니다.

주의할 점

  • 셸 내장 명령과 셸 함수에는 그대로 적용되지 않습니다. timeout은 외부 실행 파일을 실행하는 도구이므로, 내장 명령이나 함수에 상한을 걸려면 timeout 30 bash -c '...'처럼 셸을 감싸서 실행해야 합니다.
  • 프로세스 그룹과 자식 정리 방식을 확인해야 합니다. 기본 동작과 --foreground의 차이를 모르면 자식 프로세스가 남거나 반대로 신호가 닿지 않는 상황이 생깁니다.
  • 상한이 여러 겹으로 겹치지 않게 관리합니다. systemd의 서비스 타임아웃, 오케스트레이터의 종료 유예, 애플리케이션 내부 상한이 동시에 걸리면 어느 쪽이 먼저 동작하는지에 따라 정리 순서가 달라집니다. 안쪽 상한을 더 짧게 두면 정리 시간을 확보하기 쉽습니다.
  • set -e를 쓰는 스크립트에서는 124를 실패로 처리할지 결정해야 합니다. 의도적으로 상한을 둔 재시도 루프라면 124를 별도 분기로 빼야 루프가 즉시 종료되지 않습니다.
  • 측정 기준은 벽시계 시간입니다. 시스템 부하나 일시 정지 상태에 따라 실제 실행 시간과 체감이 달라질 수 있습니다.
  • 배포판에 따라 제공 형태가 다릅니다. GNU coreutils의 timeout이 기준이며, 일부 환경에서는 기본 포함되어 있지 않아 별도 설치가 필요할 수 있습니다. 스크립트를 이식할 때는 대상 환경에서 먼저 확인하는 편이 좋습니다.

자주 묻는 질문

timeout은 셸 내장 명령이나 함수에도 적용되나요?

직접 적용할 수 없습니다. timeout은 외부 실행 파일을 실행하는 도구이므로 cd, export, 사용자 정의 셸 함수에는 그대로 쓸 수 없습니다. 이 경우 timeout 30 bash -c '...'처럼 셸을 한 겹 감싸서 실행하고, 내부 프로세스 정리 여부는 로그로 확인하는 편이 좋습니다.

종료 코드 124와 137은 어떻게 다른가요?

124는 timeout이 상한 시간을 넘겨 작업을 중단시켰다는 뜻이고, 137은 프로세스가 KILL 신호로 종료되었다는 뜻입니다. -k를 사용하면 TERM 이후에도 살아남은 프로세스가 KILL로 끝나면서 137이 반환될 수 있습니다. 스크립트에서는 두 값을 모두 중단으로 묶어 처리하되 로그에서는 구분해 두는 편이 원인 파악에 유리합니다.

시간 초과 시 바로 KILL을 보내면 안 되나요?

KILL은 프로세스에 정리 작업을 할 기회를 주지 않습니다. 임시 파일이나 잠금, 열린 트랜잭션이 남을 수 있으므로 먼저 TERM을 보내고 -k로 유예를 두는 방식이 안전합니다. 즉시 중단이 반드시 필요한 작업에만 KILL을 첫 신호로 지정합니다.

파이프라인 전체에 시간 제한을 걸려면 어떻게 하나요?

timeout은 자신이 실행하는 명령에만 상한을 적용하므로 파이프의 뒷단에는 적용되지 않습니다. 파이프라인 전체를 하나의 작업으로 묶고 싶다면 timeout 30 sh -c 'cmd1 | cmd2'처럼 셸을 통해 실행합니다. 이때 종료 코드는 감싼 셸의 것이 되므로 분기 처리를 함께 조정해야 합니다.

컨테이너 환경에서도 timeout을 써야 하나요?

오케스트레이터나 systemd가 이미 종료 유예를 관리한다면 이중으로 걸리지 않게 순서를 정해야 합니다. 애플리케이션 안쪽의 상한을 더 짧게 두면 종료 신호를 받은 뒤 정리 작업을 마칠 시간을 확보하기 쉽습니다. 어느 계층이 먼저 끊는지 문서로 남겨 두면 장애 분석이 빨라집니다.

출처

함께 보면 좋은 운영 글도 참고해 보세요. systemd 서비스 타임아웃 설정, curl 타임아웃 옵션 정리, 배포 스크립트에서 종료 코드 다루기에서 이 글의 내용을 실제 운영 설정과 연결해 확인할 수 있습니다.