Bash 스크립트에서 timeout 활용법
(heitorpb.github.io)- Bash 스크립트에서 웹 서버 상태 확인을 위해 반복적인 접속 시도를 진행하면 서버가 예기치 않게 무한 루프에 빠질 수 있는 문제가 발생함
- 이를 해결하기 위한 도구인
timeout
은 명령 실행 제한 시간을 정하고, 초과 시 신호를 보내 프로세스 종료를 시도 -
until
과 같은 shell built-in에는 직접 적용이 불가해 bash 프로세스 래핑 또는 스크립트 분리를 통해 해결 가능함
Bash 스크립트에서 웹 서버 대기와 무한 루프 문제
- 실무에서 Bash 스크립트를 활용해 웹 서버 세팅 및 상태 체크를 수행하고 있음
- 서버가 올라오는 동안 다음 작업을 보류하는 구조로, 기본적으로 문제없이 동작함
- 그러나 서버가 시작 중 크래시가 발생하는 경우 무한 루프에 빠져 해결이 필요해졌음
until
사용 예시와 한계
- 다음과 같은 구문으로 웹 서버 Health 체크를 반복함
until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1 done
- 서버가 실패할 때에는
sleep 1
이 영원히 반복되는 상황이 발생함
timeout 유틸리티의 도입
-
timeout
명령어는 지정한 시간 안에 명령어가 완료되지 않으면 신호(SIGTERM 등) 를 보내 종료함 - 예:
timeout 1s sleep 5
의 경우, 1초가 지난 후 sleep 프로세스 종료 시도 - 종료 시 비정상 종료 코드(예: 124) 를 반환함
timeout과 until의 조합 시도 및 문제점
- 자연스레
timeout
과until
을 아래처럼 조합 시도함timeout 1m until curl ...; do sleep 1 done
- 하지만
timeout
은 프로세스 대상으로 신호 전송이 가능하나,until
은 shell 내장 키워드로 직접 적용 불가함
해결 방법: Bash 프로세스 래핑 또는 외부 스크립트 사용
-
until
루프 전체를 bash -c로 래핑하여 별도 프로세스로 실행하면 timeout 적용 가능함timeout 1m bash -c "until curl ...; do sleep 1; done"
- 또는 루프 부분을 외부 Bash 스크립트 분리 후, 해당 스크립트에 timeout 적용 가능함
timeout 1m ./until.sh
- shell built-in에는 직접 timeout이 적용되지 않지만, 위 방법으로 원하는 동작을 달성 가능함
Hacker News 의견
-
내가 가장 아끼는 잘 알려지지 않은 트릭은 strace fault injection으로 다양한 시스템 콜 실패를 테스트하는 방법 소개
$ strace -e trace=clone -e fault=clone:error=EAGAIN
관련 링크에서 더 자세한 내용 설명
-
이 기능이 정말 놀랍다는 생각 들고 예전에 미리 알았다면 좋았을 것 같은 경험 공유
실패 분기를 테스트할 방법이 없어서 함수의 일부만 임시 코드로 대체하곤 했는데 이 트릭 덕분에 더 간결한 접근 가능성 존재 -
이 방법이 정말 유용해 보인다는 의견
Windows에서도 이와 비슷한 기능이 있는지 궁금증 표시
-
-
서비스 헬스 체크에서 최적의 방법은 최대 타임아웃 시간과 최대 재시도 횟수를 모두 설정하는 것이라는 제안
보통 X번까지 재시도를 시도하고, 최대 Y시간 안에 실패로 판단
너무 오랜 시간 대기하기보단 가능한 한 빨리 실패 결정 필요성 강조
표준적인 서비스에서는 컨테이너 종속성이 충분히 보장되고 작동 준비가 된 뒤에만 헬스 체크 시작
Kubernetes에서는 Init Container, AWS ECS에서는 dependsOn, Docker Compose에서는 depends_on 설정 참고
POSIX 쉘 스크립트 예시 제공
하지만 curl 자체에 이런 기능이 내장되어 있어서 별도 스크립트 없이 아래와 같이 사용 가능성 언급curl --silent --fail-with-body --connect-timeout 5 --retry-all-errors --retry-delay 1 --retry-max-time 300 --retry 300 10.0.0.1:8080/health
-
Mac에서 timeout 명령어가 기본적으로 제공되지 않아서 bash 빌트인만으로 타임아웃을 구현하려고 여러 시도해 본 경험 공유
sleep 커맨드는 POSIX에서 표준이므로 사용할 수 있다는 점 설명
아래와 같이 타임아웃 기능 구현 예시 제공# TIMEOUT SYSTEM(요약) # function timeout <num_seconds> <command> # 일정 시간 경과 후 <command> 트리거
times_up라는 함수로 타임아웃 처리
10초 타임아웃으로 for문 20회 반복 테스트 예시 제공-
12년 전에 Stack Overflow의 조언을 따라 비슷한 방법을 구현했던 경험 공유
참고 링크에서 상세 내용 확인 가능
shell builtins와 sleep만 사용하며, 해당 코드는 POSIX 호환이 필수였음 강조
예제에서 bash의 {1..20} 구문은 POSIX가 아니어서 주의 필요성 언급
내 개선점은 타임아웃이 발생하지 않으면 true, 발생하면 false를 반환해 에러 처리를 스크립트 내에서 간단하게 할 수 있도록 했다는 점 -
아래처럼 명령어와 sleep을 병렬로 실행하고, 지정한 시간이 지나면 시그널로 명령어 종료하는 아주 단순한 방법 공유
<command> & sleep <timeout>; kill -SIGALRM %1
-
13년 전에
read -t
를 활용해 타임아웃을 구현했던 스크립트 사례 공유
링크
-
-
curl에는 이미
--retry-connrefused
플래그가 있어서 셸 루프 없이도 이 기능을 바로 활용 가능성 안내 -
bash -c를 사용할 때 변수 전달이 필요하다면 다음과 같이 인자를 추가하는 방법 추천
bash -c 'some command "$1" "$2"' -- "$var1" "$var2"
"--"를 쓰는 이유와 argv[0]의 역할에 대해 설명
printf %q를 쓸 수도 있지만 Bourne 호환 방식을 선호함 언급-
"--"는 bash 및 대부분의 Unix/Linux CLI에서 옵션 종료 신호로 의미가 매우 명확하다는 점 설명
관련 참고 -
Busybox는 argv[0]의 값을 기반으로 실행할 프로그램을 결정하므로 "ls", "mv", "cp" 등 원하는 명령으로 지정 가능성 공유
-
-
반복 시도 로직이 필요할 때 내가 주로 사용하는 방법은 아래와 같음
for i in {0..60}; do true -- "$i" if eventually_succeeds; then break; fi sleep 1s done
좀 세련되진 않았지만 대체로 정확하고, 더 고급 단계로는 지수 백오프 적용 가능성 언급
확장성 면에서도 이점 존재-
shellcheck에서는 위 문제를
_
변수로 사용해 처리하는 방법을 권장함
참고 링크 -
eventually_succeeds 함수가 상황에 따라 timeout이나 별도 방어 코딩이 필요할 수도 있다는 점 강조
POSIX/프로세스/IO에서 항상 방어적인 코드 작성 필요성 상기
-
-
과거 아이들이 어릴 때, 30분 동안 프로그램 한 편만 시청할 수 있도록 아래 명령어로 일종의 부모 통제 도구로 사용했던 경험 공유
timeout 1800 mplayer show.mp4 ; sudo pm-suspend
아이디어가 매우 유용하게 활용됐다는 평
- 이 용도가 가장 멋지게 설명된 사례라는 의견 추가
-
서브프로세스에 신호를 보내야 해서 명령어 인라인이나 임시 스크립트 파일을 사용하는 걸 별로 선호하지 않는다고 밝힘
내가 선호하는 방법은 원하는 복잡한 논리를 함수로 만들어 export한 뒤 timeout bash -c로 감싸는 방식 제안
aidenn0가 언급한 인자 전달 안전 처리법과 관련#!/usr/bin/env bash long_fn () { # 원하는 논리 구현 sleep $1 } to () { local duration="$1"; shift local fn_name="$1"; shift export -f "$fn_name" timeout "$duration" bash -c "$fn_name"' "$@"' _ $@ } time to 1s long_fn 5
- 마지막에
"$@"
를 반드시 써야 한다고 지적
그렇지 않으면 공백이 포함된 인수가 올바르게 전달되지 않는 문제 발생
이 점을 확인할 수 있는 long_fn 예시 공유
- 마지막에
-
예전에 timeout을 언급했던 블로그 포스팅 내용 상기
관련 블로그에서 셸이 아닌 일반 프로그래밍 언어나 내부 동작 원리에 더 궁금한 경우 참고 추천 -
Kubernetes 세팅에서 명령어 타임아웃을 추가한 경험 공유
await-cmd.sh, await-http.sh, await-tcp.sh 같은 POSIX 셸 스크립트가 성숙하고 특정 상황에서 꽤 유용하게 활용 가능성 안내
관련 프로젝트 링크