안전한 임시 파일 및 잠금 디렉터리
mktemp와 flock을 사용하여 경쟁 조건이 없는 임시 리소스를 만들고 스크립트의 동시 실행을 방지합니다.
안전한 임시 파일 및 잠금 디렉터리은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
임시 파일이 보안 위험인 이유
Bash 스크립트에는 중간 결과, 잠금 표시, 준비 영역 등 임시 저장 공간이 자주 필요합니다. 하지만 부주의하게 임시 파일을 만들면 심각한 취약점이 발생합니다.
- 경쟁 조건: 다른 프로세스가 파일 이름을 예측하여 먼저 파일을 만들고, 사용자의 쓰기 대상을 바꿀 수 있습니다.
- 심볼릭 링크 공격: 공격자가 예상 경로에
/etc/passwd같은 민감한 파일을 가리키는 심볼릭 링크를 만들 수 있습니다. - 잔여 파일: 스크립트가 비정상 종료되면 임시 파일이 쌓이고 민감한 데이터가 노출될 수 있습니다.
이러한 문제를 없애는 핵심 도구는 mktemp와 flock입니다. 이 단원에서는 두 도구를 안전하고 방어적으로 사용하는 방법을 알아봅니다.
mktemp로 안전한 임시 파일 만들기
mktemp는 무작위로 예측할 수 없는 이름의 임시 파일을 만들고 그 경로를 반환합니다. 파일을 원자적으로 생성하므로 다른 프로세스가 먼저 해당 이름을 차지할 수 없습니다.
- 문법:
mktemp [TEMPLATE]— 템플릿은 끝에 최소 세 개의X문자가 있어야 합니다. - 각
X는 무작위 문자로 바뀌어/tmp/script.aB3kQz와 같은 고유한 이름이 생성됩니다. - 파일은 자동으로 권한
0600으로 생성됩니다(소유자만 읽을 수 있음).
나중에 참조하고 정리할 수 있도록 반환된 경로를 항상 즉시 변수에 저장하십시오.
#!/usr/bin/env bash
set -euo pipefail
# Create a secure temp file
TMPFILE=$(mktemp /tmp/myapp.XXXXXX)
echo "Temp file created at: $TMPFILE"
# Write data to it
echo "some intermediate result" > "$TMPFILE"
# Read it back
cat "$TMPFILE"
# Clean up
rm -f "$TMPFILE"항상 trap으로 정리하기
스크립트가 오류, 신호 또는 set -e의 작동으로 인해 예기치 않게 종료되면 정리 처리기를 등록하지 않은 한 임시 파일이 남습니다.
trap 내장 명령은 셸이 신호를 받거나 종료될 때 명령을 실행합니다. 임시 파일 정리를 위한 표준 방식은 다음과 같습니다.
- 임시 파일을 만든 직후 트랩을 등록합니다.
EXIT에 트랩을 설정하여 정상 종료와 비정상 종료 모두에서 정리가 실행되도록 합니다.- 스크립트가 오래 실행되거나 대화형으로 동작한다면
INT와TERM에도 트랩을 설정합니다.
이렇게 하면 스크립트가 실행 중간에 강제 종료되더라도 고아 파일이 남지 않습니다.
#!/usr/bin/env bash
set -euo pipefail
TMPFILE=$(mktemp /tmp/report.XXXXXX)
# Register cleanup before doing any real work
cleanup() {
rm -f "$TMPFILE"
echo "Cleaned up $TMPFILE" >&2
}
trap cleanup EXIT
# Do work — even if this fails, cleanup() will run
echo "Processing..." > "$TMPFILE"
grep "result" "$TMPFILE" || true
echo "Done. File will be removed on exit."mktemp -d로 임시 디렉터리 만들기
여러 파일을 준비하기 위한 전체 디렉터리가 필요한 경우가 있습니다. 예를 들어 아카이브를 만들거나 처리하기 전에 tarball을 추출할 때가 그렇습니다. mktemp -d를 사용하면 안전한 임시 디렉터리를 만들 수 있습니다.
- 디렉터리는
0700권한으로 생성되므로 소유자만 접근할 수 있습니다. trap에서rm -rf를 사용해 정리하되, 하드코딩된 경로는 절대 삭제하지 말고 변수만 삭제하도록 주의해야 합니다.- 추가 안전 장치로 큰따옴표를 사용하고,
rm -rf를 호출하기 전에 변수가 비어 있지 않은지 확인해야 합니다.
#!/usr/bin/env bash
set -euo pipefail
TMPDIR=$(mktemp -d /tmp/extract.XXXXXX)
cleanup() {
# Guard: only rm if variable is set and non-empty
[[ -n "${TMPDIR:-}" ]] && rm -rf "$TMPDIR"
}
trap cleanup EXIT
echo "Working in $TMPDIR"
# Simulate staging files
echo "file one" > "$TMPDIR/part1.txt"
echo "file two" > "$TMPDIR/part2.txt"
ls "$TMPDIR"
echo "All done."스크립트가 동시에 실행될 때의 문제
Cron 작업, systemd 타이머, 수동으로 실행한 스크립트는 동일한 스크립트의 여러 인스턴스를 동시에 쉽게 실행할 수 있습니다. 이로 인해 다음과 같은 문제가 발생합니다.
- 중복 처리: 동일한 데이터베이스 레코드나 파일이 두 번 처리됩니다.
- 출력 손상: 두 인스턴스가 동일한 출력 파일에 동시에 기록합니다.
- 교착 상태 또는 불완전한 상태: 두 인스턴스가 공유 리소스를 예측할 수 없는 순서로 뒤섞어 수정합니다.
전통적인 해결 방법은 PID 파일을 작성하고 시작할 때 확인하는 것이었지만, 이 방법에는 확인과 작성 사이에 경쟁 조건이 발생할 수 있는 구간이 있습니다. 현대적인 올바른 해결책은 flock입니다. flock은 커널의 권고 잠금 메커니즘을 사용하여 경쟁 조건이 발생하지 않도록 잠금을 보장합니다.
flock을 사용한 잠금: 한 줄 패턴
flock은 명령을 실행하기 전에 파일 디스크립터에 권고 잠금을 획득합니다. 가장 간단한 사용법은 명령줄에서 전체 스크립트를 감싸는 것입니다.
flock -n /var/lock/myscript.lock bash myscript.sh
-n(비차단): 잠금이 이미 사용 중이면 기다리지 않고 상태 1로 즉시 종료합니다.-n이 없으면flock은 잠금을 사용할 수 있을 때까지 차단됩니다. 이는 작업을 대기열에 넣을 때 유용합니다.- 잠금 파일 자체는 단순한 표시일 뿐이며, 내용은 중요하지 않습니다. 실행이 끝난 뒤에도 그대로 두어도 안전합니다.
- 잠금을 보유한 프로세스가 종료되면 커널이 자동으로 잠금을 해제하므로 수동으로 정리할 필요가 없습니다.
#!/usr/bin/env bash
# launcher.sh — prevents concurrent runs of worker.sh
set -euo pipefail
LOCKFILE="/tmp/myworker.lock"
if ! flock -n "$LOCKFILE" bash -c 'echo "Running worker..."; sleep 2; echo "Done."'; then
echo "Another instance is already running. Exiting." >&2
exit 1
fi파일 디스크립터를 사용해 스크립트 내부에서 flock 사용하기
스크립트를 외부에서 감싸는 대신 스크립트 내부에서 잠그려면 exec으로 파일 디스크립터를 연 다음 해당 디스크립터에 flock을 호출합니다. 이는 운영 환경의 스크립트에서 사용하는 관용적인 패턴입니다.
exec 200>"$LOCKFILE"은 디스크립터 200번으로 파일을 쓰기 위해 엽니다. 필요한 경우 파일도 생성합니다.flock -n 200은 디스크립터 200번을 비차단 방식으로 잠그려고 시도합니다.- 잠금은 파일 이름이 아니라 파일 디스크립터에 연결되므로 셸 프로세스가 종료되면 자동으로 해제됩니다.
- 표준 입력·표준 출력·표준 오류와 충돌하지 않도록 일반적으로 200~299번 디스크립터를 사용합니다.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/myjob.lock"
# Open lock file on FD 200
exec 200>"$LOCKFILE"
# Attempt non-blocking lock
if ! flock -n 200; then
echo "ERROR: Another instance of this script is running." >&2
exit 1
fi
echo "Lock acquired. Starting work..."
sleep 1
echo "Work complete. Lock will be released on exit."하나의 스크립트에서 mktemp와 flock 결합하기
실제로 방어적으로 작성한 스크립트에는 두 가지가 모두 필요합니다. 동시 실행을 막는 잠금과 중간 데이터를 저장할 안전한 임시 파일입니다. 다음은 두 기법을 결합한 전체 패턴입니다.
- 먼저 잠금을 획득합니다. 임시 파일을 만들기 전에 잠가야 실제 작업을 수행하는 인스턴스가 하나뿐이 됩니다.
- 잠금이 확인된 후에 임시 리소스를 만듭니다.
- 임시 리소스를 만든 즉시
trap을 등록하여 스크립트가 어떤 방식으로 종료되더라도 정리가 보장되도록 합니다. - 잠금 파일은 임시 디렉터리에 두지 않습니다.
flock이 참조할 수 있도록 실행 사이에도 유지되어야 합니다.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/report_builder.lock"
exec 200>"$LOCKFILE"
if ! flock -n 200; then
echo "Already running — aborting." >&2
exit 1
fi
# Now safe to create temp resources
TMPDIR=$(mktemp -d /tmp/report.XXXXXX)
TMPLOG=$(mktemp /tmp/report_log.XXXXXX)
cleanup() {
rm -rf "${TMPDIR:-}"
rm -f "${TMPLOG:-}"
}
trap cleanup EXIT
echo "Building report in $TMPDIR" | tee "$TMPLOG"
echo "Step 1 complete" >> "$TMPLOG"
cat "$TMPLOG"대체 잠금 메커니즘으로 사용하는 잠금 디렉터리
flock을 사용할 수 없는 시스템(일부 임베디드 시스템이나 NFS 같은 네트워크 파일 시스템)에서는 대신 잠금 디렉터리를 사용할 수 있습니다. POSIX 시스템에서 mkdir은 원자적으로 동작하므로 디렉터리가 아직 존재하지 않을 때만 성공합니다.
mkdir /tmp/myscript.lock.d로 잠금 디렉터리를 만듭니다. 다른 인스턴스가 이미 만들었다면mkdir은 즉시 실패합니다.- 진단을 위해 디렉터리 안에 PID와 같은 메타데이터를 저장합니다.
- 항상
EXIT의trap에서 디렉터리를 삭제합니다. - 주의:
flock과 달리 디렉터리 잠금은 프로세스가-9로 강제 종료되거나 컴퓨터가 재부팅되어도 자동으로 해제되지 않습니다. 오래된 잠금을 감지하는 확인 절차를 추가해야 합니다.
#!/usr/bin/env bash
set -euo pipefail
LOCKDIR="/tmp/myscript.lock.d"
# Atomic mkdir — fails if directory already exists
if ! mkdir "$LOCKDIR" 2>/dev/null; then
# Check if the holding PID is still alive
HOLDER_PID=$(cat "$LOCKDIR/pid" 2>/dev/null || echo "")
if [[ -n "$HOLDER_PID" ]] && kill -0 "$HOLDER_PID" 2>/dev/null; then
echo "Locked by PID $HOLDER_PID. Exiting." >&2
exit 1
else
echo "Stale lock detected. Removing and continuing." >&2
rm -rf "$LOCKDIR"
mkdir "$LOCKDIR"
fi
fi
echo "$$" > "$LOCKDIR/pid"
trap 'rm -rf "$LOCKDIR"' EXIT
echo "Lock acquired via directory. Running..."
sleep 1
echo "Done."flock을 사용한 제한 시간 대기
잠금을 즉시 실패하지 않고 기다리되, 무한정 기다리지는 않으려는 경우가 있습니다. flock은 -w 플래그로 제한 시간을 지원합니다.
flock -w 10 200은 잠금을 최대 10초 동안 기다린 후에도 사용할 수 없으면 상태 1로 종료합니다.- 짧은 시간 실행되는 이전 작업의 뒤에서 대기하되, 이전 작업이 멈춘 경우에는 포기해야 하는 스크립트에 적합합니다.
-w를 상황을 설명하는 의미 있는 오류 메시지와 함께 사용합니다. 잠금 파일 경로와 기다린 시간을 포함하면 운영자가 멈춤 현상을 빠르게 진단할 수 있습니다.
#!/usr/bin/env bash
set -euo pipefail
LOCKFILE="/tmp/data_sync.lock"
TIMEOUT=15
exec 200>"$LOCKFILE"
echo "Waiting up to ${TIMEOUT}s for lock on $LOCKFILE..."
if ! flock -w "$TIMEOUT" 200; then
echo "ERROR: Could not acquire lock after ${TIMEOUT}s." \
"Another instance may be hung." >&2
exit 1
fi
echo "Lock acquired. Syncing data..."
sleep 1
echo "Sync complete."방어적 점검 목록: 안전한 임시 리소스
임시 파일이나 잠금을 사용하는 스크립트를 배포하기 전에 다음 점검 목록을 확인하십시오.
mktemp를 사용하고 하드코딩된 경로는 사용하지 않습니다 —/tmp/myapp.tmp는 예측할 수 있어 악용될 수 있습니다.- 경로를 즉시 저장합니다 — 다른 명령을 실행하기 전에
TMPFILE=$(mktemp ...)를 실행합니다. - 생성 직후
trap cleanup EXIT을 등록합니다 — 스크립트 끝에서 등록해서는 안 됩니다. - 모든 변수 사용을 큰따옴표로 묶습니다 —
rm -f "$TMPFILE"를 사용하고,rm -f $TMPFILE는 사용하지 않습니다. - PID 파일보다
flock을 우선합니다 — 커널이 관리하며 충돌 시 자동으로 해제됩니다. - 기본값으로 비차단
-n을 사용합니다 — 조용히 차단되는 잠금은 성능 문제를 숨깁니다. - 잠금 파일을 임시 디렉터리 밖에 둡니다 — 정리용
trap이 실행되어도 잠금 파일이 유지됩니다. - 정리 동작을 테스트합니다 — 스크립트를 실행한 다음 중간에
kill -9로 종료하고 남은 파일이 없는지 확인합니다(flock기반 스크립트의 경우이며, 디렉터리 잠금은 추가로 주의해야 합니다).
지식 확인: flock 플래그 동작
Cron 작업이 매분 실행되며 공유 파일을 처리합니다. 이전 실행이 아직 진행 중이면 새 호출이 기다리지 않고 오류와 함께 즉시 종료되기를 원합니다. 이를 올바르게 구현하는 flock 호출은 무엇입니까?
복습: 안전한 임시 파일과 잠금 디렉터리
이 수업에서는 배시에서 방어적으로 리소스를 관리하기 위한 두 가지 필수 도구를 배웠습니다.
mktemp는 예측할 수 없고 안전한 권한이 설정된 임시 파일(0600)과 디렉터리(0700)를 만들어 하드코딩된 경로에서 발생하는 경쟁 조건과 심볼릭 링크 공격을 없앱니다.trap cleanup EXIT을 생성 직후 등록하면 정상 종료, 오류로 인한 종료, 신호로 인한 종료를 포함한 모든 종료 상황에서 임시 파일 삭제를 보장합니다.flock은 커널이 강제하는 권고 잠금을 제공합니다. 경합 시 빠르게 실패하려면-n을, 제한 시간과 함께 기다리려면-w N을 사용하고, 커널이 프로세스 종료 시 자동으로 해제하는 스크립트 내부 잠금에는exec 200>file패턴을 사용합니다.- 잠금 디렉터리(
mkdir)는flock을 사용할 수 없는 환경에서 이식성 있는 대체 방법을 제공하지만, 오래된 잠금을 명시적으로 감지해야 합니다. - 항상 잠금 파일을 임시 디렉터리 밖에 두고, 정리에 사용하는 모든 변수를 큰따옴표로 묶습니다.
mktemp + flock + trap을 결합하면 동시 호출, 예측할 수 없는 충돌, 악의적인 파일 시스템 조작에도 안전한 스크립트를 만들 수 있습니다.
자주 묻는 질문
“안전한 임시 파일 및 잠금 디렉터리” 강의는 무료인가요?
네 — “안전한 임시 파일 및 잠금 디렉터리” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“안전한 임시 파일 및 잠금 디렉터리”에서 뭘 배우나요?
mktemp와 flock을 사용하여 경쟁 조건이 없는 임시 리소스를 만들고 스크립트의 동시 실행을 방지합니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“안전한 임시 파일 및 잠금 디렉터리” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- set -euo pipefail을 활용한 엄격 모드
- 정리 및 신호 처리를 위한 트랩 핸들러
- 안전한 임시 파일 및 잠금 디렉터리
- 멱등적 스크립트와 지수 백오프 재시도 로직