บทความ › เซิร์ฟเวอร์และ DevOps

สำรองข้อมูล 3-2-1 บน VPS และซ้อม restore ทุกเดือน

backup ที่ไม่เคยลองกู้คือความหวัง ไม่ใช่แผน กฎ 3-2-1 และ 3-2-1-1-0 อะไรต้องสำรองบน VPS เข้ารหัสด้วย restic คำนวณ retention และพื้นที่ ซ้อมกู้ทุกเดือน

เริ่มต้น อ่าน 11 นาที 22 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • 3-2-1 คือ 3 สำเนา บน 2 สื่อ อย่างน้อย 1 ชุดนอกสถานที่ และ 3-2-1-1-0 เพิ่มสำเนาที่ลบไม่ได้กับผลตรวจที่ไม่มี error
  • บน VPS สำรอง dump ฐานข้อมูล ไฟล์ที่ผู้ใช้อัปโหลด และ config ไม่ต้องสำรองตัว OS
  • restic เข้ารหัสทุกอย่างก่อนส่งออก ทำ retention ด้วย forget --prune และตรวจด้วย check --read-data-subset
  • ซ้อมกู้ทุกเดือนบนเครื่องเปล่า จับเวลาไว้ นั่นคือ RTO จริงของคุณ

backup ที่ไม่เคยลองกู้คือความหวัง ไม่ใช่แผน งานสำรองข้อมูลส่วนใหญ่พังแบบเงียบ ๆ: cron หยุดรันมาสามเดือน, dump ได้ไฟล์ว่าง, หรือรหัสผ่านอยู่บนเครื่องที่เพิ่งพังไป บทความนี้เล่าวิธีสำรองเซิร์ฟเวอร์เล็กให้กู้ได้จริง ตั้งแต่กฎ 3-2-1 สิ่งที่ต้องสำรอง การเข้ารหัส การคำนวณพื้นที่ จนถึงการซ้อมกู้ทุกเดือน

ตรวจกับเอกสาร restic 0.19 (stable), BorgBackup 1.4.5, pg_dump ของ PostgreSQL 18, คู่มือ best practice ของ Veeam และ NIST SP 800-34 คำสั่งเขียนสำหรับ Ubuntu/Debian

กฎ 3-2-1 และ 3-2-1-1-0

ตัวเลขความหมายบน VPS เล็ก หน้าตาเป็นยังไง
3ข้อมูล 3 สำเนา (ตัวจริง + backup 2 ชุด)ข้อมูลบนเซิร์ฟเวอร์ + snapshot ของผู้ให้บริการ + repository restic
2อยู่บนสื่อ 2 แบบดิสก์ VPS กับ object storage
11 ชุดอยู่นอกสถานที่object storage คนละผู้ให้บริการ คนละบัญชี
11 ชุดออฟไลน์ แยกขาด หรือลบไม่ได้ (immutable)bucket ที่เปิด object lock หรือเครื่องอื่นที่ดึง backup ไปเอง
0ตรวจกู้แล้ว error เป็นศูนย์restic check + ซ้อมกู้ทุกเดือน

3-2-1-1-0 เป็นส่วนขยายที่ Veeam ใช้ สองตัวท้ายตอบภัยยุคนี้ตรง ๆ ถ้าคนร้ายหรือสคริปต์พังได้สิทธิ์ root บนเซิร์ฟเวอร์ มันใช้กุญแจเดียวกับที่ backup ใช้ลบ backup ได้ด้วย สำเนาที่ลบไม่ได้คือทางรอด ส่วนเลข 0 คือหัวข้อของบทความนี้

ตัวเลขความหมาย3สำเนาข้อมูล 3 ชุด2อยู่บนสื่อ 2 แบบ1อยู่นอกสถานที่ 1 ชุด1ลบหรือแก้ไม่ได้ 1 ชุด0กู้แล้วไม่มี error
สองตัวท้ายคือสิ่งที่ทำให้ backup กู้ได้จริงวันที่ระบบโดนเจาะหรือพัง

อะไรต้องสำรอง อะไรไม่ต้อง

สำรองสิ่งที่สร้างขึ้นใหม่ไม่ได้ ไม่ต้องสำรองสิ่งที่ติดตั้งใหม่ได้

สำรองไม่ต้องสำรอง
dump ฐานข้อมูล (pg_dump, SQLite VACUUM INTO)ไฟล์ OS, /usr, แพ็กเกจ
ไฟล์ที่ผู้ใช้อัปโหลดnode_modules, venv, build output
config: /etc/nginx, systemd unit, crontab, .envcache, log เก่า, Docker image
รายชื่อแพ็กเกจและสคริปต์ติดตั้งโฟลเดอร์ข้อมูลของ DB ขณะรันอยู่

OS ติดตั้งใหม่ได้ในไม่กี่นาที แต่ไฟล์ที่ผู้ใช้อัปโหลดเมื่อปีที่แล้วสร้างใหม่ไม่ได้ ห้าม copy โฟลเดอร์ข้อมูล PostgreSQL ตรง ๆ ขณะรัน ให้ใช้ pg_dump ซึ่งได้สำเนาที่ consistent และไม่บล็อกผู้ใช้คนอื่น ถ้าใช้ SQLite ดูวิธีสำรองในบทความ SQLite

เข้ารหัสด้วย restic

restic เข้ารหัสทุกอย่างบนเครื่องก่อนส่งออก (AES-256 แบบ CTR + Poly1305-AES เป็น MAC กุญแจได้จากรหัสผ่านผ่าน scrypt) ผู้ให้บริการ storage เห็นแต่ก้อนข้อมูลที่อ่านไม่ออก และ restic ตัดข้อมูลซ้ำ (dedup) ให้ snapshot ใหม่เก็บแค่ส่วนที่เปลี่ยน BorgBackup ทำงานคล้ายกัน เลือกตัวใดตัวหนึ่ง บทนี้ใช้ restic

ตั้งค่าครั้งแรกบนเซิร์ฟเวอร์ (ตัวอย่างใช้ S3-compatible storage):

# /root/.restic-env  (chmod 600)
export RESTIC_REPOSITORY="s3:https://s3.example.com/my-backups"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID="EXAMPLE_KEY_ID"
export AWS_SECRET_ACCESS_KEY="EXAMPLE_SECRET"
source /root/.restic-env
restic init

เสียรหัสผ่าน = เสียข้อมูลถาวร เก็บสำเนารหัสผ่านไว้ใน password manager นอกเซิร์ฟเวอร์ และใช้ access key ที่สิทธิ์แคบเฉพาะ bucket นี้ ไม่ใช่ key หลักของบัญชี (เหตุผลเดียวกับแยก credential ทุกงาน)

สคริปต์สำรองรายวัน

#!/usr/bin/env bash
# /usr/local/bin/backup.sh  รันเป็น root ทุกคืนผ่าน cron หรือ systemd timer
set -euo pipefail
source /root/.restic-env

mkdir -p /var/backups/db
sudo -u postgres pg_dump -Fc appdb > /var/backups/db/appdb.dump

restic backup /var/backups/db /srv/app/uploads /srv/app/.env \
              /etc/nginx /etc/systemd/system /var/spool/cron/crontabs

restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check --read-data-subset=5%

-Fc คือ custom format บีบอัดในตัว กู้ด้วย pg_restore set -e ทำให้สคริปต์หยุดทันทีที่ขั้นไหนล้ม ส่วนแจ้งเตือนเมื่อล้มใช้วิธีเดียวกับระบบเฝ้าเซิร์ฟเวอร์ส่ง Telegram backup ที่ล้มเงียบ ๆ แย่กว่าไม่มี backup เพราะทำให้เราเชื่อว่ามี

forget แค่ลบรายการ snapshot ข้อมูลจริงจะถูกลบตอน prune ซึ่ง --prune สั่งให้ทำต่อทันที check --read-data-subset=5% สุ่มดาวน์โหลด 5% ของข้อมูลมาถอดรหัสตรวจทุกคืน 20 คืนรวมกันเท่ากับขนาด repository แต่เพราะสุ่ม บางก้อนอาจไม่เคยโดนตรวจ ถ้าอยากครบแน่นอน ใช้แบบแบ่งกลุ่ม 1/20 ถึง 20/20 คืนละกลุ่มวนไป ฝั่ง borg ตัวเทียบเท่าคือ borg check --verify-data

คำนวณ retention: เก็บกี่ snapshot ใช้พื้นที่เท่าไร

นโยบาย --keep-daily 7 --keep-weekly 4 --keep-monthly 6 กับ backup วันละครั้ง

restic ตรวจแต่ละ snapshot กับทุกกฎ ตรงกฎไหนก็เก็บ และนับครั้งเดียว ไม่ซ้ำ

กฎเก็บซ้อนกับกฎก่อนเพิ่มจริง
daily 77 วันล่าสุด-7
weekly 4ตัวล่าสุดของ 4 สัปดาห์สัปดาห์นี้อยู่ใน daily แล้ว3
monthly 6ตัวล่าสุดของ 6 เดือนเดือนนี้อยู่ใน daily แล้ว5
รวม≈ 15 (สูงสุด 7 + 4 + 6 = 17)

ย้อนกลับได้ไกลสุดราว 6 เดือน ด้วย snapshot แค่ราว 15 ชุด

พื้นที่ สมมติเซิร์ฟเวอร์มี dump ฐานข้อมูล 1 GB (บีบอัดแล้ว) กับไฟล์อัปโหลด 8 GB

  • ไฟล์อัปโหลด: dedup ทำให้ snapshot ที่ไฟล์ไม่เปลี่ยนแทบไม่กินที่เพิ่ม ใช้ราว 8 GB บวกไฟล์ที่ถูกลบภายใน 6 เดือน (ยังอยู่ใน snapshot เก่า)
  • dump: ไฟล์ที่บีบอัดแล้วเปลี่ยนนิดเดียวก็ทำให้ข้อมูลข้างในเลื่อนไปทั้งไฟล์ dedup ได้น้อย คิดแบบแย่สุดว่าทุกชุดใหม่หมด 15 × 1 GB = 15 GB
  • รวมแบบแย่สุด: 8 + 15 = ≈ 23 GB บวกไฟล์ที่ลบไปในช่วงนั้น

ดูของจริงด้วย restic stats --mode raw-data แล้วเอาไปคูณราคาต่อ GB ของ storage ที่ใช้ (ราคาเปลี่ยนบ่อย ดูหน้าราคาของผู้ให้บริการ ณ วันที่ตั้ง)

RPO และ RTO แบบภาษาคน

  • RPO (recovery point objective) = ยอมเสียข้อมูลย้อนหลังได้กี่ชั่วโมง NIST นิยามว่าเป็นจุดเวลาที่ข้อมูลต้องกู้กลับไปถึง backup ทุกคืนตอนตี 2 ถ้าเครื่องพังตอนตี 1:59 คืนถัดไป ข้อมูลหายเกือบ 24 ชั่วโมง RPO จึงเท่ากับ 24 ชั่วโมง
  • RTO (recovery time objective) = ยอมให้ระบบล่มได้นานเท่าไรก่อนกลับมาใช้ได้

ตัวอย่าง RTO: กู้แค่ snapshot ล่าสุด คือ 8 GB + 1 GB = 9 GB ถ้าดาวน์โหลดได้ 50 MB/s (สมมติ วัดของคุณเอง) ใช้ 9,000 ÷ 50 = 180 วินาที = 3 นาที แต่นั่นแค่ดาวน์โหลด ยังต้องสร้างเซิร์ฟเวอร์ ติดตั้งแพ็กเกจ pg_restore และตั้ง DNS ตัวเลขจริงมาจากการซ้อมเท่านั้น

RTO บนกระดาษเป็นแค่ความหวัง RTO จริงคือเวลาที่จับได้ตอนซ้อมกู้ครั้งล่าสุด

ซ้อมกู้ทุกเดือน

ใช้เครื่องเปล่า (VPS ชั่วคราว หรือ VM ในเครื่อง) ไม่ใช่เครื่อง production และทำตามโน้ตที่เขียนไว้ ไม่ใช่จากความจำ

# บนเครื่องซ้อม หลังติดตั้ง restic และ PostgreSQL
source /root/.restic-env          # ใช้รหัสผ่านจาก password manager ไม่ใช่จากเครื่องเดิม
restic snapshots
restic restore latest --target /tmp/drill

sudo -u postgres createdb drill_test
sudo -u postgres pg_restore -d drill_test /tmp/drill/var/backups/db/appdb.dump
sudo -u postgres psql -d drill_test -c "SELECT count(*) FROM users"
ls /tmp/drill/srv/app/uploads | head

restic คืนไฟล์ไว้ใต้ --target ตาม path เดิม จึงอยู่ที่ /tmp/drill/var/backups/... เช็กลิสต์ที่ต้องได้ทุกเดือน:

  1. กู้ได้โดยใช้เฉพาะของที่อยู่นอกเซิร์ฟเวอร์ (รหัสผ่าน, key, โน้ต)
  2. จำนวนแถวในตารางหลักใกล้กับ production
  3. เปิดไฟล์อัปโหลดสุ่มได้จริง
  4. จดเวลาเริ่มถึงเสร็จ นั่นคือ RTO ของเดือนนี้ ถ้าช้าลงเรื่อย ๆ แปลว่าข้อมูลโตจนแผนต้องปรับ

ข้อผิดพลาดที่เจอบ่อย

  • รหัสผ่าน repository อยู่บนเซิร์ฟเวอร์ที่เดียว เครื่องพัง backup ก็เปิดไม่ได้
  • ไม่มีใครรู้ว่า cron หยุดรัน ต้องมีแจ้งเตือนทั้งตอนล้มและตอนที่ไม่รันเลย
  • dump ได้ไฟล์ว่างหรือครึ่งเดียว แต่สคริปต์ไม่มี set -euo pipefail เลยไปต่อและ backup ไฟล์ว่างอย่างมีความสุข
  • ใช้ key หลักของบัญชี cloud ถ้า key รั่ว ทั้งบัญชีรั่ว และใครได้สิทธิ์บนเซิร์ฟเวอร์ก็ลบ backup ทุกชุดได้
  • สำรองทุกอย่างรวม OS ใช้พื้นที่มาก กู้ช้า และไม่ได้อะไรที่ติดตั้งใหม่ไม่ได้
  • ไม่เคยซ้อม เจอวันจริงว่าขาดไฟล์ .env หรือ dump มาจากฐานข้อมูลผิดตัว

แหล่งอ้างอิง

คำถามที่พบบ่อย

snapshot ของผู้ให้บริการ VPS พอไหม

เป็นสำเนาที่ดีหนึ่งชุด แต่ส่วนใหญ่อยู่กับผู้ให้บริการเดียวกันและผูกกับบัญชีเดียวกัน ถ้าบัญชีโดนล็อกหรือถูกลบ snapshot ก็หายไปด้วย นับเป็นหนึ่งใน 3 สำเนาได้ แต่ไม่ใช่ชุดนอกสถานที่

copy โฟลเดอร์ข้อมูลของ PostgreSQL ไปเลยได้ไหม

ไม่ควรทำตอนฐานข้อมูลรันอยู่ ไฟล์ที่ได้อาจไม่ตรงกัน ใช้ pg_dump ซึ่งได้สำเนาที่ consistent โดยไม่บล็อกผู้อ่านและผู้เขียน แล้วสำรองไฟล์ dump แทน

ลืมรหัสผ่าน repository ของ restic แล้วกู้ได้ไหม

ไม่ได้ เอกสาร restic บอกตรง ๆ ว่าเสียรหัสผ่านเท่ากับเสียข้อมูลถาวร เก็บรหัสผ่านไว้ใน password manager นอกเซิร์ฟเวอร์อีกชุดเสมอ

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#Serverกลาง

PostgreSQL 18: ใช้ uuidv7() แทน UUID v4 และเปิด async I/O ให้ถูก

PostgreSQL 18 มี uuidv7() ที่เรียงตามเวลา ทำให้ B-tree index เล็กและร้อนน้อยกว่า v4 และมี async I/O ผ่าน io_method เลือก worker หรือ io_uring พร้อมตัวอย่างคำนวณขนาด index

อ่าน 9 นาที
#Serverขั้นสูง

ย้าย schema PostgreSQL ไม่ต้องปิดระบบ: expand, migrate, contract

เปลี่ยนชื่อคอลัมน์ใน 3 deploy โดยไม่มีใครเห็น error ล็อกตัวไหนต้องระวัง CREATE INDEX CONCURRENTLY, NOT VALID แล้ว VALIDATE, lock_timeout และ backfill ทีละก้อน

อ่าน 11 นาที
#Serverกลาง

เก็บข้อมูลตามเวลาด้วย TimescaleDB บีบอัด สรุปรายชั่วโมง และเมื่อไหร่ห้ามลบ

ข้อมูลที่เข้าทุกไม่กี่วินาทีโตจนฐานข้อมูลช้า TimescaleDB แก้ด้วย hypertable บีบอัดของเก่า สรุปรายชั่วโมงอัตโนมัติ และเมื่อไหร่ไม่ควรตั้งให้ลบ พร้อม SQL จริง

อ่าน 8 นาที