0Pricing
DevOps Bootcamp · 강의

안전한 임시 파일 및 잠금 디렉터리

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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. set -euo pipefail을 활용한 엄격 모드
  2. 정리 및 신호 처리를 위한 트랩 핸들러
  3. 안전한 임시 파일 및 잠금 디렉터리
  4. 멱등적 스크립트와 지수 백오프 재시도 로직
← DevOps Bootcamp(으)로 돌아가기