ไฟดับ รีบูต แล้วกล่องเก็บข้อมูลก็ขึ้นมาช้ากว่าบริการที่พึ่งพามันหนึ่งนาที การเมานต์ NFS ล้มเหลวครั้งเดียวแล้วก็ล้มเหลวอยู่อย่างนั้น Docker ช่วยสร้างโฟลเดอร์ว่างไว้ตรงที่แชร์ควรอยู่ เซิร์ฟเวอร์รูปภาพไม่ยอมเริ่ม เซิร์ฟเวอร์ไฟล์ปิดตัว และเซิร์ฟเวอร์สื่อสแกนคลังว่างเปล่าแล้วเริ่มลืมสิ่งต่าง ๆ นี่คือวิธีแก้สี่ส่วนที่เราใช้บนเครื่อง AI ของเรา ปรับให้ใช้ทั่วไปจนวางลงบน Docker host ใดก็ได้ที่เมานต์จาก NAS
รากของปัญหาคือจังหวะเวลา และจังหวะเวลาไม่เคยแก้ได้ด้วยการเปลี่ยนแปลงเดียว ไอเดียสี่ข้อรวมกันทำให้โฮสต์ไม่สนว่า NAS จะโผล่มาเมื่อไร:
ให้ NAS ออกตัวก่อน ถ้าเซิร์ฟเวอร์เก็บข้อมูลเป็น VM บนไฮเปอร์ไวเซอร์เดียวกัน ให้บอกไฮเปอร์ไวเซอร์ให้เริ่มมันก่อนและรอก่อนเริ่มอย่างอื่น NAS ไม่มี guest agent ที่จะบอกว่า “พร้อมแล้ว” การรอจึงเป็นจำนวนวินาทีตายตัว — นานพอให้อาร์เรย์ขึ้นมา
ลองซ้ำแทนการล้มเหลวครั้งเดียว การเมานต์เครือข่ายใน fstab ถูกพยายามตอนบูตแล้วไม่มีอีกเลย ตั้งค่า nofail ไว้เพื่อไม่ให้ NAS ที่ช้าทำให้การบูตแย่ลง แล้วให้ตัวจับเวลารายนาทีลองต่อไปเรื่อย ๆ จนสำเร็จ
ห้ามเริ่มบนจุดเมานต์เปล่าเด็ดขาด ช่วงเวลาอันตรายไม่ใช่ตอนเมานต์ล้มเหลว แต่คือตอนที่คอนเทนเนอร์เริ่มทำงานไปทั้งอย่างนั้น ไฟล์เครื่องหมายที่มีอยู่บน NAS เท่านั้น bind เข้าคอนเทนเนอร์เป็นไฟล์ที่ Compose ถูกห้ามสร้าง จะเปลี่ยน “แชร์หายไป” ให้เป็น “คอนเทนเนอร์จะไม่เริ่ม” — ซึ่งถูกต้องพอดี
บอกเมื่อมันเกิดขึ้น เมื่อตัวเฝ้าเมานต์ใหม่ มันจะรีสตาร์ตผู้ใช้งานและส่งหนึ่งบรรทัดไปยังช่องทางแจ้งเตือนของคุณ การกู้คืนแบบเงียบ ๆ คือบั๊กที่คุณไม่มีวันได้รู้
.immich ลงในทุกโฟลเดอร์ของมัน และไม่ยอมเริ่มถ้ามีอันไหนหายไป — การปฏิเสธนั้นคือสิ่งที่ดีที่สุดที่เกิดขึ้นในเหตุการณ์ของเรา เพราะมันหยุดเซิร์ฟเวอร์ไม่ให้เขียนไฟล์อัปโหลดลงดิสก์ผิดลูก อย่าตั้ง IMMICH_IGNORE_MOUNT_CHECK_ERRORS เพื่อให้ข้อความหายไป แก้การเมานต์photos, media และ files เมานต์ที่ /mnt/<share> จาก NAS ที่ 192.168.1.10startup และเมื่อมี up= จะรอตามจำนวนวินาทีนั้นก่อนไปเครื่องถัดไป ให้ VM ของ NAS ลำดับต่ำและการหน่วงเวลาจริงจัง ส่วน Docker VM มาทีหลังqm set 109 --onboot 1 --startup order=2,up=180
qm set 104 --onboot 1 --startup order=5
# see the whole boot sequence at a glance
for c in /etc/pve/qemu-server/*.conf; do
printf "%-10s %s\n" "$(basename $c)" "$(grep -E '^(name|onboot|startup):' $c | tr '\n' ' ')"
donenofail กันไม่ให้แชร์ที่หายไปทำให้การบูตแย่ลง x-systemd.mount-timeout กันไม่ให้มันถ่วงการบูตด้วย การลองซ้ำอยู่ในขั้นที่ 3 ไม่ใช่ที่นี่ — fstab ไม่มีการลองซ้ำ192.168.1.10:/mnt/user/photos /mnt/photos nfs rw,soft,timeo=150,_netdev,nofail,x-systemd.mount-timeout=30 0 0
192.168.1.10:/mnt/user/media /mnt/media nfs rw,soft,timeo=150,_netdev,nofail,x-systemd.mount-timeout=30 0 0
192.168.1.10:/mnt/user/files /mnt/files nfs rw,soft,timeo=150,_netdev,nofail,x-systemd.mount-timeout=30 0 0soft พร้อม timeo ที่มีขอบเขตจึงเป็นตัวเลือกที่ซื่อตรง — คำขอที่ค้างจะคืนข้อผิดพลาดแทนการแขวนคอนเทนเนอร์ไว้ตลอดไป สำหรับแชร์ที่รับไฟล์อัปโหลดที่ไม่มีทางหาใหม่ได้และไม่มีการตรวจระดับแอป ให้เลือก hard และยอมรับว่า NAS ที่ตายจะทำให้ตัวเขียนค้าง#!/bin/bash
# Remount lost NAS shares; restart their consumers only when a remount actually
# happened; say so once. Runs as root, every minute.
changed=""
for m in photos media files; do
if ! mountpoint -q /mnt/$m; then
# An unmounted mountpoint may hold directories Docker auto-created while the
# NAS was away. They would be hidden under the mount later — clear them so the
# state stays honest and nothing ever lands on the local disk by mistake.
find /mnt/$m -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null
mount /mnt/$m 2>/dev/null && changed="$changed $m"
fi
done
if [ -n "$changed" ]; then
docker restart immich_server jellyfin >/dev/null 2>&1
docker start opencloud >/dev/null 2>&1
# swap for your notifier of choice
sudo -u duncan -H /home/duncan/scripts/tg-send.sh \
"🔁 nfs-guard remounted:$changed — consumers restarted (the NAS had been away)." \
>/dev/null 2>&1 || true
logger -t nfs-guard "remounted:$changed; consumers restarted"
fi
exit 0# nfs-guard.service
[Unit]
Description=Remount NAS shares and restart their consumers if they were lost
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/nfs-guard.sh
# nfs-guard.timer
[Unit]
Description=Minutely NFS mount guard
[Timer]
OnBootSec=30s
OnUnitActiveSec=1min
AccuracySec=10s
[Install]
WantedBy=timers.targetsudo chmod 755 /usr/local/sbin/nfs-guard.sh
sudo systemctl daemon-reload
sudo systemctl enable --now nfs-guard.timer
systemctl list-timers nfs-guard.timer --no-legendcreate_host_path: false Compose จะไม่ยอมสร้างไฟล์ที่หายไปขึ้นมาเอง คอนเทนเนอร์จึงเริ่มไม่ได้จนกว่าแชร์จะกลับมา — และการรีสตาร์ตของตัวเฝ้าจะพามันขึ้นมาทันทีที่แชร์กลับมาfor m in photos media files; do sudo touch /mnt/$m/.on-nas; doneservices:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
volumes:
- ./config:/config
- ./cache:/cache
- /mnt/media:/media:ro
# the gate: a file that exists only on the NAS, and that Compose may not create
- type: bind
source: /mnt/media/.on-nas
target: /on-nas
read_only: true
bind:
create_host_path: false
restart: unless-stoppeddocker-compose.yml ของตัวเองที่คุณไม่ควรแก้ ให้ใส่บล็อกเดียวกันไว้ใน docker-compose.override.yml ใต้ immich-server: — Compose จะรวมรายการ volume ให้# exports appear only once the NAS array is up
showmount -e 192.168.1.10
# mounts, not directory listings — an empty local directory lists just fine
for m in photos media files; do mountpoint -q /mnt/$m && echo "/mnt/$m mounted" || echo "/mnt/$m MISSING"; done
# the gate is in place when every consumer shows the marker bind
for c in jellyfin opencloud immich_server; do
docker inspect -f '{{.Name}}: {{range .Mounts}}{{if eq .Destination "/on-nas"}}{{.Source}}{{end}}{{end}}' $c
done
# the real test: stop the NAS array, watch the consumers refuse, start it, watch the guard
journalctl -t nfs-guard -fmountpoint -q หรือไฟล์เครื่องหมายที่มีแต่ NAS ให้ได้ คือการทดสอบที่แท้จริง