수집 자료를 큰 디스크에 쌓으려고 구조를 잡았다.
/opt/collect/data → /mnt/nvme/collect
배포 스크립트에 폴더 생성을 넣었다. 며칠 뒤 시스템 디스크가 찼다.
Table of contents
Open Table of contents
연결이 안 만들어졌다
확인해 보니 /opt/collect/data 가 링크가 아니었다.
$ ls -ld /opt/collect/data
drwxr-xr-x 2 collect collect 4096 Jan 19 03:00 /opt/collect/data
맨 앞 글자가 d 다. 링크면 l 이어야 하는데 실제 폴더였다.
배포 스크립트를 열어 봤다.
mkdir -p /opt/collect/data
mkdir -p /opt/collect/logs
mkdir 로 폴더를 만들기만 하고 ln 으로 잇는 줄이 없었다. 설계에는 있었는데 스크립트에 안 들어간 것이다.
원인 — 오류 없이 지나간 빠짐
빠진 단계가 왜 안 드러났는지를 봤다. mkdir 자체는 성공하기 때문이다.
서비스도 그 경로가 있으니 정상으로 쓴다. 쓰기가 되고 읽기가 되니 아무 데서도 오류가 안 난다.
만들기는 성공하고 잇기는 아예 없으니 실패할 자리가 없었다. 자료가 다른 장치에 쌓인다는 것만 다르다.
이런 종류의 빠짐은 용량이 찰 때가 되어서야 드러난다. 조용히 잘못된 상태가 며칠 동안 이어졌다.
순서를 지켰다
고칠 때는 순서가 중요했다. 그 폴더에 이미 자료가 들어 있었기 때문이다.
TARGET=/mnt/nvme/collect
LINK=/opt/collect/data
mkdir -p "$TARGET"
if [ -L "$LINK" ]; then
: # 이미 링크면 그대로
elif [ -d "$LINK" ]; then
mv "$LINK"/* "$TARGET"/ 2>/dev/null # 쌓인 것을 옮긴다
rmdir "$LINK"
ln -s "$TARGET" "$LINK"
else
ln -s "$TARGET" "$LINK"
fi
이미 링크면 그대로 두고 폴더면 안의 것을 mv 로 옮긴 뒤 링크로 바꾼다. 그냥 지우고 만들면 쌓인 자료를 잃는다.
지울 때 rmdir 을 쓴 것은 비어 있을 때만 지우기 위해서다. rm -rf 를 쓰면 옮기기가 실패했어도 그대로 지워 버린다.
rmdir 이 거부하면 그 자리에서 옮기기가 덜 됐다는 것이 드러난다. 실패해야 할 때 실패하는 명령을 고르는 것이 여기서 중요했다.
주의 — 링크 생성 명령의 성질
링크를 만들고 나서 하나를 더 확인해야 했다. ln 은 가리키는 대상이 없어도 링크를 만들고 종료 코드 0으로 끝난다.
경로를 한 글자 틀리게 적어도 링크는 생기고 명령은 정상으로 보인다. 그 링크는 아무 데도 안 닿는다.
링크가 있다는 것과 그것이 해소된다는 것은 다른 확인이다. 명령의 성공을 결과의 성공으로 삼으면 안 되는 자리가 여기였다.
확인을 넣었다
만든 뒤에 세 가지를 봤다.
if [ ! -L "$LINK" ]; then
echo "링크가 아닙니다: $LINK"
ls -ld "$LINK"
exit 1
fi
RESOLVED=$(readlink -f "$LINK")
if [ "$RESOLVED" != "$TARGET" ]; then
echo "링크 대상이 다릅니다: $RESOLVED (기대: $TARGET)"
exit 1
fi
if [ ! -w "$LINK" ]; then
echo "쓰기 불가: $LINK"
exit 1
fi
echo "링크 확인: $LINK -> $RESOLVED"
-L 로 링크인지 보고 readlink -f 로 어디를 가리키는지 보고 -w 로 쓸 수 있는지 본다. 셋 중 하나라도 어긋나면 exit 1 이다.
기대한 대상 경로를 문자열로 비교하는 것이 핵심이었다. 링크이기만 하면 통과시키면 엉뚱한 데를 가리키는 링크가 그대로 지나간다.
실제로 써 봤다
확인만으로 부족해서 실제로 써 봤다.
TESTFILE="$LINK/.write-test-$$"
if ! echo test > "$TESTFILE" 2>/dev/null; then
echo "쓰기 시험 실패: $LINK"
exit 1
fi
# 실제로 대상 쪽에 생겼는지 확인
if [ ! -f "$TARGET/$(basename "$TESTFILE")" ]; then
echo "링크를 통해 쓴 파일이 대상에 없습니다"
exit 1
fi
rm -f "$TESTFILE"
링크를 통해 쓰고 대상 쪽에서 그 파일을 찾는다. 지정한 방식과 다른 방식으로 되읽는 것이라 링크가 엉뚱한 곳을 가리키면 여기서 걸린다.
$$ 로 프로세스 번호를 붙인 것은 여러 번 돌려도 안 겹치게 하기 위해서다. 시험 파일은 확인이 끝나면 지운다.
검증 — 경로별 장치 확인
이 일을 겪고 나서 어느 경로가 어느 장치에 있는지를 확인하게 됐다.
$ df -h /opt/collect/data /opt/collect/logs /var/log
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p1 1.8T 120G 1.6T 7% /mnt/nvme
/dev/sda2 50G 38G 12G 77% /
/dev/sda2 50G 38G 12G 77% /
df 에 경로를 여러 개 주면 각각이 어느 장치에 있는지 한 번에 나온다. data 만 큰 디스크에 있고 나머지 둘은 시스템 디스크였다.
로그도 옮겨야 했다. 하루 2기가씩 쌓이고 있어서 링크를 고쳐도 시스템 디스크는 계속 찰 상황이었다.
재발 방지 — 마운트별 감시
디스크 감시는 있었는데 전체만 보고 있었다. 마운트별로 보게 고쳤다.
df -h | awk 'NR>1 && $5+0 > 80 {print $6 " " $5}'
awk 로 다섯째 열의 사용률을 숫자로 바꿔 80을 넘는 마운트만 뽑는다. 전체 합으로 보면 큰 디스크가 비어 있어서 평균이 낮게 나온다.
이것을 넣고 나서 시스템 디스크가 차기 전에 알게 됐다. 합쳐서 보면 안 보이던 것이 나눠서 보니 보였다.
정리
- 폴더는 만들었는데 연결을 안 만들면 자료가 엉뚱한 곳에 쌓인다
mkdir은 성공하므로 빠진ln은 오류로 안 드러난다- 이미 폴더가 있으면
mv로 옮기고 나서 링크로 바꾼다 rmdir을 써서 안 비었으면 거기서 멈추게 한다ln은 대상이 없어도 만들고 종료 코드 0으로 끝난다-L과readlink -f와-w로 세 가지를 확인한다- 기대한 대상 경로와 문자열로 비교해야 엉뚱한 링크가 안 지나간다
- 링크로 써 보고 대상 쪽에서 그 파일을 찾아 되읽는다
df에 경로를 주어 어느 장치에 있는지 확인한다- 감시는 전체 합이 아니라 마운트별로 본다