บทความ › เซิร์ฟเวอร์และ DevOps
สำรองข้อมูล 3-2-1 บน VPS และซ้อม restore ทุกเดือน
backup ที่ไม่เคยลองกู้คือความหวัง ไม่ใช่แผน กฎ 3-2-1 และ 3-2-1-1-0 อะไรต้องสำรองบน VPS เข้ารหัสด้วย restic คำนวณ retention และพื้นที่ ซ้อมกู้ทุกเดือน
- 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 |
| 1 | 1 ชุดอยู่นอกสถานที่ | object storage คนละผู้ให้บริการ คนละบัญชี |
| 1 | 1 ชุดออฟไลน์ แยกขาด หรือลบไม่ได้ (immutable) | bucket ที่เปิด object lock หรือเครื่องอื่นที่ดึง backup ไปเอง |
| 0 | ตรวจกู้แล้ว error เป็นศูนย์ | restic check + ซ้อมกู้ทุกเดือน |
3-2-1-1-0 เป็นส่วนขยายที่ Veeam ใช้ สองตัวท้ายตอบภัยยุคนี้ตรง ๆ ถ้าคนร้ายหรือสคริปต์พังได้สิทธิ์ root บนเซิร์ฟเวอร์ มันใช้กุญแจเดียวกับที่ backup ใช้ลบ backup ได้ด้วย สำเนาที่ลบไม่ได้คือทางรอด ส่วนเลข 0 คือหัวข้อของบทความนี้
อะไรต้องสำรอง อะไรไม่ต้อง
สำรองสิ่งที่สร้างขึ้นใหม่ไม่ได้ ไม่ต้องสำรองสิ่งที่ติดตั้งใหม่ได้
| สำรอง | ไม่ต้องสำรอง |
|---|---|
dump ฐานข้อมูล (pg_dump, SQLite VACUUM INTO) | ไฟล์ OS, /usr, แพ็กเกจ |
| ไฟล์ที่ผู้ใช้อัปโหลด | node_modules, venv, build output |
config: /etc/nginx, systemd unit, crontab, .env | cache, 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 7 | 7 วันล่าสุด | - | 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/... เช็กลิสต์ที่ต้องได้ทุกเดือน:
- กู้ได้โดยใช้เฉพาะของที่อยู่นอกเซิร์ฟเวอร์ (รหัสผ่าน, key, โน้ต)
- จำนวนแถวในตารางหลักใกล้กับ production
- เปิดไฟล์อัปโหลดสุ่มได้จริง
- จดเวลาเริ่มถึงเสร็จ นั่นคือ RTO ของเดือนนี้ ถ้าช้าลงเรื่อย ๆ แปลว่าข้อมูลโตจนแผนต้องปรับ
ข้อผิดพลาดที่เจอบ่อย
- รหัสผ่าน repository อยู่บนเซิร์ฟเวอร์ที่เดียว เครื่องพัง backup ก็เปิดไม่ได้
- ไม่มีใครรู้ว่า cron หยุดรัน ต้องมีแจ้งเตือนทั้งตอนล้มและตอนที่ไม่รันเลย
- dump ได้ไฟล์ว่างหรือครึ่งเดียว แต่สคริปต์ไม่มี
set -euo pipefailเลยไปต่อและ backup ไฟล์ว่างอย่างมีความสุข - ใช้ key หลักของบัญชี cloud ถ้า key รั่ว ทั้งบัญชีรั่ว และใครได้สิทธิ์บนเซิร์ฟเวอร์ก็ลบ backup ทุกชุดได้
- สำรองทุกอย่างรวม OS ใช้พื้นที่มาก กู้ช้า และไม่ได้อะไรที่ติดตั้งใหม่ไม่ได้
- ไม่เคยซ้อม เจอวันจริงว่าขาดไฟล์
.envหรือ dump มาจากฐานข้อมูลผิดตัว
แหล่งอ้างอิง
- Preparing a new repository — restic documentation
- Removing backup snapshots (forget, prune) — restic documentation
- Working with repositories (check) — restic documentation และ Restoring from backup
- References (crypto design) — restic documentation
- borg check — BorgBackup 1.4.5 documentation
- pg_dump — PostgreSQL 18 Documentation
- 3-2-1-1-0 Rule — Veeam Best Practice Guide
- Recovery point objective — NIST CSRC Glossary และ Recovery time objective
คำถามที่พบบ่อย
snapshot ของผู้ให้บริการ VPS พอไหม
เป็นสำเนาที่ดีหนึ่งชุด แต่ส่วนใหญ่อยู่กับผู้ให้บริการเดียวกันและผูกกับบัญชีเดียวกัน ถ้าบัญชีโดนล็อกหรือถูกลบ snapshot ก็หายไปด้วย นับเป็นหนึ่งใน 3 สำเนาได้ แต่ไม่ใช่ชุดนอกสถานที่
copy โฟลเดอร์ข้อมูลของ PostgreSQL ไปเลยได้ไหม
ไม่ควรทำตอนฐานข้อมูลรันอยู่ ไฟล์ที่ได้อาจไม่ตรงกัน ใช้ pg_dump ซึ่งได้สำเนาที่ consistent โดยไม่บล็อกผู้อ่านและผู้เขียน แล้วสำรองไฟล์ dump แทน
ลืมรหัสผ่าน repository ของ restic แล้วกู้ได้ไหม
ไม่ได้ เอกสาร restic บอกตรง ๆ ว่าเสียรหัสผ่านเท่ากับเสียข้อมูลถาวร เก็บรหัสผ่านไว้ใน password manager นอกเซิร์ฟเวอร์อีกชุดเสมอ
คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ