LVM และ Matryoshka มีอะไรเหมือนกัน?

วันที่ดี
ฉันต้องการแบ่งปันประสบการณ์จริงของฉันในการสร้างระบบจัดเก็บข้อมูลสำหรับ KVM โดยใช้ md RAID + LVM กับชุมชน

โปรแกรมจะประกอบด้วย:

  • การสร้าง md RAID 1 จาก NVMe SSD
  • การประกอบ md RAID 6 จาก SATA SSD และไดรฟ์ปกติ
  • คุณสมบัติของการดำเนินการ TRIM/DISCARD บน SSD RAID 1/6
  • การสร้างอาร์เรย์ md RAID 1/6 ที่สามารถบูตได้บนชุดดิสก์ทั่วไป
  • การติดตั้งระบบบน NVMe RAID 1 เมื่อไม่มีการรองรับ NVMe ใน BIOS
  • การใช้แคช LVM และ LVM แบบบาง
  • การใช้สแน็ปช็อต BTRFS และส่ง/รับเพื่อการสำรองข้อมูล
  • การใช้ LVM Thin Snapshots และ Thin_delta สำหรับการสำรองข้อมูลสไตล์ BTRFS

สนใจดูแมวได้นะครับ

คำแถลง

ผู้เขียนไม่รับผิดชอบต่อผลที่ตามมาของการใช้หรือไม่ใช้เนื้อหา/ตัวอย่าง/รหัส/เคล็ดลับ/ข้อมูลจากบทความนี้ โดยการอ่านหรือใช้เนื้อหานี้ในทางใดทางหนึ่ง คุณจะต้องรับผิดชอบต่อผลที่ตามมาทั้งหมดของการกระทำเหล่านี้ ผลที่ตามมาที่อาจเกิดขึ้น ได้แก่:

  • NVMe SSD ที่คมชัด
  • ใช้ทรัพยากรการบันทึกจนหมดและความล้มเหลวของไดรฟ์ SSD
  • สูญเสียข้อมูลทั้งหมดในไดรฟ์ทั้งหมด รวมถึงสำเนาสำรองด้วย
  • ฮาร์ดแวร์คอมพิวเตอร์ผิดพลาด
  • เสียเวลา ประสาท และเงิน
  • ผลที่ตามมาอื่น ๆ ที่ไม่ได้ระบุไว้ข้างต้น

เหล็ก

มีจำหน่ายคือ:

เมนบอร์ดจากประมาณปี 2013 ที่ใช้ชิปเซ็ต Z87 พร้อมด้วย Intel Core i7 / Haswell

  • โปรเซสเซอร์ 4 คอร์ 8 เธรด
  • แรม 32GB DDR3
  • 1 x 16 หรือ 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • ขั้วต่อ SATA 6 ขนาด 6 x 3 GBps

อะแดปเตอร์ SAS LSI SAS9211-8I กะพริบเป็นโหมด IT / HBA เฟิร์มแวร์ที่เปิดใช้งาน RAID ได้รับการแทนที่โดยเจตนาด้วยเฟิร์มแวร์ HBA เพื่อ:

  1. คุณสามารถโยนอะแดปเตอร์นี้ออกเมื่อใดก็ได้และแทนที่ด้วยอะแดปเตอร์ตัวอื่นที่คุณเจอ
  2. TRIM/Discard ทำงานตามปกติบนดิสก์ เนื่องจาก... ในเฟิร์มแวร์ RAID คำสั่งเหล่านี้ไม่รองรับเลย และโดยทั่วไป HBA จะไม่สนใจว่าคำสั่งใดจะถูกส่งผ่านบัส

ฮาร์ดไดรฟ์ - HGST Travelstar 8K7 1000 ชิ้นความจุ 1 TB ในรูปแบบ 2.5 สำหรับแล็ปท็อป ก่อนหน้านี้ไดรฟ์เหล่านี้อยู่ในอาร์เรย์ RAID 6 พวกเขายังจะมีประโยชน์ในระบบใหม่อีกด้วย เพื่อจัดเก็บข้อมูลสำรองในเครื่อง

นอกจากนี้:

SATA SSD รุ่น Samsung 6 QVO 860TB จำนวน 2 ชิ้น SSD เหล่านี้ต้องการปริมาณมาก มีแคช SLC ความน่าเชื่อถือ และราคาที่ต่ำ จำเป็นต้องมีการสนับสนุน discard/zero ซึ่งตรวจสอบโดยบรรทัดใน dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

NVMe SSD จำนวน 2 ชิ้น Samsung SSD 970 EVO 500GB.

สำหรับ SSD เหล่านี้ ความเร็วในการอ่าน/เขียนแบบสุ่มและความจุของทรัพยากรที่เหมาะกับความต้องการของคุณเป็นสิ่งสำคัญ หม้อน้ำสำหรับพวกเขา อย่างจำเป็น. อย่างแน่นอน. มิฉะนั้น ให้ทอดจนกรอบระหว่างการซิงโครไนซ์ RAID ครั้งแรก

อะแดปเตอร์ StarTech PEX8M2E2 สำหรับ 2 x NVMe SSD ที่ติดตั้งในสล็อต PCIe 3.0 8x นี่เป็นเพียง HBA อีกครั้ง แต่สำหรับ NVMe มันแตกต่างจากอะแดปเตอร์ราคาถูกตรงที่ไม่ต้องการการสนับสนุนการแยกส่วน PCIe จากเมนบอร์ดเนื่องจากมีสวิตช์ PCIe ในตัว มันจะทำงานได้แม้ในระบบที่เก่าแก่ที่สุดที่มี PCIe แม้ว่าจะเป็นสล็อต x1 PCIe 1.0 ก็ตาม ตามธรรมชาติด้วยความเร็วที่เหมาะสม ไม่มีการโจมตีที่นั่น ไม่มี BIOS ในตัวบนบอร์ด ดังนั้น ระบบของคุณจะไม่ได้เรียนรู้การบูตด้วย NVMe อย่างน่าอัศจรรย์ แต่จะทำได้น้อยลงด้วย NVMe RAID ต้องขอบคุณอุปกรณ์นี้

ส่วนประกอบนี้เกิดจากการมี 8x PCIe 3.0 ฟรีเพียงช่องเดียวในระบบและหากมีช่องว่าง 2 ช่องก็สามารถแทนที่ได้อย่างง่ายดายด้วย PEX4M2E1 หรืออะนาล็อกสองเพนนีซึ่งสามารถซื้อได้ทุกที่ในราคา 600 รูเบิล

การปฏิเสธฮาร์ดแวร์ทุกประเภทหรือชิปเซ็ตในตัว/BIOS RAID เกิดขึ้นอย่างจงใจเพื่อให้สามารถแทนที่ระบบทั้งหมดได้อย่างสมบูรณ์ ยกเว้น SSD/HDD เอง ในขณะเดียวกันก็รักษาข้อมูลทั้งหมดไว้ ตามหลักการแล้ว เพื่อให้คุณสามารถบันทึกได้แม้กระทั่งระบบปฏิบัติการที่ติดตั้งไว้ เมื่อเปลี่ยนไปใช้ฮาร์ดแวร์ใหม่ทั้งหมด/ฮาร์ดแวร์ที่แตกต่างกัน สิ่งสำคัญคือมีพอร์ต SATA และ PCIe มันเหมือนกับซีดีแสดงสดหรือแฟลชไดรฟ์ที่สามารถบูตได้ เพียงแต่เร็วมากและเทอะทะเล็กน้อย

อารมณ์ขันมิฉะนั้นคุณจะรู้ว่าเกิดอะไรขึ้น - บางครั้งคุณจำเป็นต้องนำอาร์เรย์ทั้งหมดติดตัวไปด้วยอย่างเร่งด่วนเพื่อนำออกไป แต่ฉันไม่อยากสูญเสียข้อมูล ในการดำเนินการนี้ สื่อที่กล่าวถึงทั้งหมดจะอยู่ในตำแหน่งที่สะดวกบนสไลด์ในช่อง 5.25 ของเคสมาตรฐาน

แน่นอนว่าสำหรับการทดลองด้วยวิธีต่างๆ ของการแคช SSD ใน Linux

การโจมตีด้วยฮาร์ดแวร์นั้นน่าเบื่อ เปิด. มันใช้งานได้หรือไม่ก็ได้ และด้วย mdadm ก็มีตัวเลือกอยู่เสมอ

อ่อน

ก่อนหน้านี้ มีการติดตั้ง Debian 8 Jessie บนฮาร์ดแวร์ซึ่งใกล้เคียงกับ EOL RAID 6 ประกอบขึ้นจาก HDD ที่กล่าวถึงข้างต้นโดยจับคู่กับ LVM มันรันเครื่องเสมือนใน kvm/libvirt

เพราะ ผู้เขียนมีประสบการณ์ที่เหมาะสมในการสร้างแฟลชไดรฟ์ SATA/NVMe ที่สามารถบูตแบบพกพาได้และเพื่อไม่ให้เทมเพลต apt ปกติ Ubuntu 18.04 ได้รับเลือกให้เป็นระบบเป้าหมายซึ่งมีความเสถียรเพียงพอแล้ว แต่ยังคงมีเวลา 3 ปี การสนับสนุนในอนาคต

ระบบดังกล่าวประกอบด้วยไดรเวอร์ฮาร์ดแวร์ทั้งหมดที่เราต้องการทันที เราไม่ต้องการซอฟต์แวร์หรือไดรเวอร์ของบุคคลที่สาม

กำลังเตรียมการติดตั้ง

ในการติดตั้งระบบ เราจำเป็นต้องมี Ubuntu Desktop Image ระบบเซิร์ฟเวอร์มีตัวติดตั้งที่แข็งแกร่งซึ่งแสดงความเป็นอิสระมากเกินไปซึ่งไม่สามารถปิดการใช้งานได้โดยการผลักพาร์ติชันระบบ UEFI ลงบนดิสก์ตัวใดตัวหนึ่งซึ่งทำลายความสวยงามทั้งหมด ดังนั้นจึงติดตั้งในโหมด UEFI เท่านั้น ไม่มีตัวเลือกใดๆ

เราไม่พอใจกับสิ่งนี้

ทำไม?น่าเสียดายที่การบูต UEFI นั้นเข้ากันได้กับซอฟต์แวร์บูต RAID ได้แย่มากเพราะ... ไม่มีใครเสนอการจองพาร์ติชัน UEFI ESP ให้เรา มีสูตรอาหารออนไลน์ที่แนะนำให้วางพาร์ติชัน ESP บนแฟลชไดรฟ์ในพอร์ต USB แต่นี่เป็นจุดที่ล้มเหลว มีสูตรอาหารที่ใช้ซอฟต์แวร์ mdadm RAID 1 พร้อมข้อมูลเมตาเวอร์ชัน 0.9 ที่ไม่ป้องกันไม่ให้ UEFI BIOS เห็นพาร์ติชันนี้ แต่จะมีชีวิตอยู่จนถึงช่วงเวลาที่มีความสุขเมื่อ BIOS หรือระบบปฏิบัติการฮาร์ดแวร์อื่นเขียนบางสิ่งลงใน ESP และลืมซิงโครไนซ์กับอุปกรณ์อื่น กระจกเงา

นอกจากนี้การบูต UEFI ยังขึ้นอยู่กับ NVRAM ซึ่งจะไม่เคลื่อนที่ไปพร้อมกับดิสก์ไปยังระบบใหม่เนื่องจาก เป็นส่วนหนึ่งของเมนบอร์ด

ดังนั้นเราจะไม่สร้างวงล้อใหม่ขึ้นมาใหม่ เรามีรถมอเตอร์ไซค์ของคุณปู่ที่ผ่านการทดสอบตามเวลาแล้ว ซึ่งปัจจุบันเรียกว่าบูท Legacy/BIOS ซึ่งมีชื่ออันน่าภาคภูมิใจของ CSM บนระบบที่เข้ากันได้กับ UEFI เราจะนำมันออกจากชั้นวาง หล่อลื่น ปั้มลมยาง และเช็ดด้วยผ้าหมาด

Ubuntu เวอร์ชันเดสก์ท็อปไม่สามารถติดตั้งได้อย่างถูกต้องกับ Legacy bootloader แต่อย่างน้อยก็มีตัวเลือกอยู่ที่นี่

ดังนั้นเราจึงรวบรวมฮาร์ดแวร์และโหลดระบบจากแฟลชไดรฟ์ที่สามารถบู๊ตได้ของ Ubuntu Live เราจะต้องดาวน์โหลดแพ็คเกจ ดังนั้นเราจะตั้งค่าเครือข่ายที่เหมาะกับคุณ หากไม่ได้ผล คุณสามารถโหลดแพ็คเกจที่จำเป็นลงในแฟลชไดรฟ์ล่วงหน้าได้

เราเข้าสู่สภาพแวดล้อมเดสก์ท็อป เปิดโปรแกรมจำลองเทอร์มินัล และไปกันเลย:

#sudo bash

ยังไง…?บรรทัดด้านบนเป็นตัวกระตุ้นสำหรับโฮลิวอร์เกี่ยวกับ sudo ซีบีоโอกาสที่มากขึ้นมาและоความรับผิดชอบที่มากขึ้น คำถามคือว่าคุณสามารถทำเองได้หรือไม่ หลายคนคิดว่าการใช้ sudo ในลักษณะนี้อย่างน้อยก็ไม่ระวัง อย่างไรก็ตาม:

#apt-get install mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc

ทำไมไม่ ZFS...?เมื่อเราติดตั้งซอฟต์แวร์บนคอมพิวเตอร์ของเรา เราจะให้นักพัฒนาซอฟต์แวร์นี้ยืมฮาร์ดแวร์ของเราเพื่อขับเคลื่อน
เมื่อเราไว้วางใจซอฟต์แวร์นี้ด้วยความปลอดภัยของข้อมูลของเรา เราจะกู้เงินเท่ากับต้นทุนในการกู้คืนข้อมูลนี้ ซึ่งเราจะต้องชำระคืนสักวันหนึ่ง

จากมุมมองนี้ ZFS คือ Ferrari และ mdadm+lvm ก็เหมือนกับจักรยานมากกว่า

โดยส่วนตัวแล้ว ผู้เขียนชอบที่จะให้ยืมจักรยานโดยให้เครดิตแก่บุคคลที่ไม่รู้จัก แทนที่จะเป็นเฟอร์รารี ที่นั่นราคาของปัญหาไม่สูง ไม่ต้องมีสิทธิ. ง่ายกว่ากฎจราจร มีที่จอดรถฟรี ความสามารถในการข้ามประเทศจะดีกว่า คุณสามารถต่อขาเข้ากับจักรยานได้ตลอดเวลาและคุณสามารถซ่อมจักรยานด้วยมือของคุณเองได้

แล้วทำไม BTRFS...?ในการบูตระบบปฏิบัติการ เราจำเป็นต้องมีระบบไฟล์ที่รองรับ Legacy/BIOS GRUB ตั้งแต่แกะกล่อง และในขณะเดียวกันก็รองรับ live snapshots เราจะใช้สำหรับพาร์ติชัน /boot นอกจากนี้ผู้เขียนต้องการใช้ FS นี้สำหรับ / (รูท) โดยไม่ลืมที่จะทราบว่าสำหรับซอฟต์แวร์อื่น ๆ คุณสามารถสร้างพาร์ติชันแยกต่างหากบน LVM และเมานต์ในไดเร็กทอรีที่จำเป็น

เราจะไม่จัดเก็บรูปภาพของเครื่องเสมือนหรือฐานข้อมูลใดๆ บน FS นี้
FS นี้จะใช้เพื่อสร้างสแน็ปช็อตของระบบโดยไม่ต้องปิดเครื่องเท่านั้น จากนั้นจึงถ่ายโอนสแน็ปช็อตเหล่านี้ไปยังดิสก์สำรองข้อมูลโดยใช้การส่ง/รับ

นอกจากนี้ โดยทั่วไปผู้เขียนมักชอบที่จะเก็บซอฟต์แวร์ขั้นต่ำไว้บนฮาร์ดแวร์โดยตรง และเรียกใช้ซอฟต์แวร์อื่นๆ ทั้งหมดในเครื่องเสมือนโดยใช้สิ่งต่างๆ เช่น การส่งต่อ GPU และตัวควบคุมโฮสต์ PCI-USB ไปยัง KVM ผ่าน IOMMU

สิ่งเดียวที่เหลืออยู่บนฮาร์ดแวร์คือการจัดเก็บข้อมูล ระบบเสมือนจริง และการสำรองข้อมูล

หากคุณเชื่อถือ ZFS มากกว่านั้น ตามหลักการแล้ว สำหรับแอปพลิเคชันที่ระบุ ZFS เหล่านั้นจะสามารถใช้แทนกันได้

อย่างไรก็ตาม ผู้เขียนจงใจเพิกเฉยต่อคุณสมบัติการมิเรอร์/RAID และความซ้ำซ้อนในตัวที่ ZFS, BRTFS และ LVM มี

เพื่อเป็นข้อโต้แย้งเพิ่มเติม BTRFS มีความสามารถในการเปลี่ยนการเขียนแบบสุ่มเป็นการเขียนตามลำดับ ซึ่งมีผลเชิงบวกอย่างมากต่อความเร็วในการซิงโครไนซ์สแน็ปช็อต/การสำรองข้อมูลบน HDD

มาสแกนอุปกรณ์ทั้งหมดอีกครั้ง:

#udevadm control --reload-rules && udevadm trigger

มาดูรอบ ๆ กัน:

#lsscsi && nvme list
[0:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sda
[1:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdb
[2:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdc
[3:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdd
[4:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sde
[5:0:0:0] disk ATA Samsung SSD 860 2B6Q /dev/sdf
[6:0:0:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdg
[6:0:1:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdh
[6:0:2:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdi
[6:0:3:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdj
[6:0:4:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdk
[6:0:5:0] disk ATA HGST HTS721010A9 A3B0 /dev/sdl
[6:0:6:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdm
[6:0:7:0] disk ATA HGST HTS721010A9 A3J0 /dev/sdn
Node SN Model Namespace Usage Format FW Rev
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1 S466NXXXXXXX15L Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7
/dev/nvme1n1 S5H7NXXXXXXX48N Samsung SSD 970 EVO 500GB 1 0,00 GB / 500,11 GB 512 B + 0 B 2B2QEXE7

เค้าโครงดิสก์

NVMe SSD

แต่เราจะไม่ทำเครื่องหมายไว้ แต่อย่างใด ในทำนองเดียวกัน BIOS ของเราไม่เห็นไดรฟ์เหล่านี้ ดังนั้นพวกเขาจะไปที่ซอฟต์แวร์ RAID ทั้งหมด เราจะไม่สร้างส่วนต่างๆ ที่นั่นด้วยซ้ำ หากคุณต้องการปฏิบัติตาม “หลักการ” หรือ “หลัก” ให้สร้างพาร์ติชันขนาดใหญ่หนึ่งพาร์ติชัน เช่น HDD

SATA HDD

ไม่จำเป็นต้องประดิษฐ์อะไรเป็นพิเศษที่นี่ เราจะสร้างส่วนหนึ่งสำหรับทุกสิ่ง เราจะสร้างพาร์ติชันเนื่องจาก BIOS เห็นดิสก์เหล่านี้และอาจพยายามบูตจากดิสก์เหล่านั้นด้วยซ้ำ เราจะติดตั้ง GRUB บนดิสก์เหล่านี้ในภายหลังเพื่อให้ระบบสามารถทำสิ่งนี้ได้ในทันที

#cat >hdd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sdg
unit: sectors

/dev/sdg1 : start= 2048, size= 1953523120, type=fd, bootable
EOF
#sfdisk /dev/sdg < hdd.part
#sfdisk /dev/sdh < hdd.part
#sfdisk /dev/sdi < hdd.part
#sfdisk /dev/sdj < hdd.part
#sfdisk /dev/sdk < hdd.part
#sfdisk /dev/sdl < hdd.part
#sfdisk /dev/sdm < hdd.part
#sfdisk /dev/sdn < hdd.part

SATA SSD

นี่คือสิ่งที่น่าสนใจสำหรับเรา

ประการแรก ไดรฟ์ของเรามีขนาด 2 TB ซึ่งอยู่ในช่วงที่ยอมรับได้สำหรับ MBR ซึ่งเป็นสิ่งที่เราจะใช้ หากจำเป็น สามารถแทนที่ด้วย GPT ได้ ดิสก์ GPT มีเลเยอร์ความเข้ากันได้ซึ่งช่วยให้ระบบที่เข้ากันได้กับ MBR สามารถดู 4 พาร์ติชันแรกได้ หากอยู่ภายใน 2 เทราไบต์แรก สิ่งสำคัญคือพาร์ติชันสำหรับบูตและพาร์ติชัน bios_grub บนดิสก์เหล่านี้ควรอยู่ที่จุดเริ่มต้น นอกจากนี้ยังช่วยให้คุณสามารถบูตจากไดรฟ์ GPT Legacy/BIOS ได้อีกด้วย

แต่นี่ไม่ใช่กรณีของเรา

ที่นี่เราจะสร้างสองส่วน อันแรกจะมีขนาด 1 GB และใช้สำหรับ RAID 1 /boot

อันที่สองจะใช้สำหรับ RAID 6 และจะใช้พื้นที่ว่างที่เหลือทั้งหมด ยกเว้นพื้นที่ขนาดเล็กที่ไม่ได้ถูกจัดสรรที่ส่วนท้ายของไดรฟ์

พื้นที่ที่ไม่มีเครื่องหมายนี้คืออะไร?ตามแหล่งที่มาบนเครือข่าย SATA SSD ของเรามีแคช SLC ที่สามารถขยายได้แบบไดนามิกในขนาดตั้งแต่ 6 ถึง 78 กิกะไบต์ เราได้รับ 6 กิกะไบต์ "ฟรี" เนื่องจากความแตกต่างระหว่าง "กิกะไบต์" และ "กิกะไบต์" ในแผ่นข้อมูลของไดรฟ์ ส่วนที่เหลืออีก 72 กิกะไบต์จะถูกจัดสรรจากพื้นที่ที่ไม่ได้ใช้

ควรสังเกตว่าเรามีแคช SLC และพื้นที่ว่างในโหมด MLC 4 บิต ซึ่งสำหรับเราอย่างมีประสิทธิภาพหมายความว่าสำหรับพื้นที่ว่างทุกๆ 4 กิกะไบต์เราจะได้รับแคช SLC เพียง 1 กิกะไบต์เท่านั้น

คูณ 72 กิกะไบต์ด้วย 4 และรับ 288 กิกะไบต์ นี่คือพื้นที่ว่างที่เราจะไม่ทำเครื่องหมายเพื่อให้ไดรฟ์ใช้แคช SLC ได้อย่างเต็มที่

ดังนั้นเราจะรับแคช SLC สูงสุด 312 กิกะไบต์จากไดรฟ์ทั้งหมดหกไดรฟ์อย่างมีประสิทธิภาพ จากไดรฟ์ทั้งหมด 2 ไดรฟ์จะถูกใช้ใน RAID เพื่อความซ้ำซ้อน

แคชจำนวนนี้จะทำให้เราแทบจะไม่พบสถานการณ์ในชีวิตจริงที่การเขียนไม่ได้ไปที่แคช สิ่งนี้สามารถชดเชยข้อเสียเปรียบที่น่าเศร้าที่สุดของหน่วยความจำ QLC ได้เป็นอย่างดี ซึ่งเป็นความเร็วการเขียนที่ต่ำมากเมื่อข้อมูลถูกเขียนโดยไม่ผ่านแคช หากโหลดของคุณไม่สอดคล้องกับสิ่งนี้ ฉันขอแนะนำให้คุณคิดให้ดีว่า SSD ของคุณจะใช้งานได้นานแค่ไหนภายใต้โหลดดังกล่าว โดยคำนึงถึง TBW จากแผ่นข้อมูล

#cat >ssd.part << EOF
label: dos
label-id: 0x00000000
device: /dev/sda
unit: sectors

/dev/sda1 : start= 2048, size= 2097152, type=fd, bootable
/dev/sda2 : start= 2099200, size= 3300950016, type=fd
EOF
#sfdisk /dev/sda < ssd.part
#sfdisk /dev/sdb < ssd.part
#sfdisk /dev/sdc < ssd.part
#sfdisk /dev/sdd < ssd.part
#sfdisk /dev/sde < ssd.part
#sfdisk /dev/sdf < ssd.part

การสร้างอาร์เรย์

ก่อนอื่นเราต้องเปลี่ยนชื่อเครื่องก่อน นี่เป็นสิ่งจำเป็นเนื่องจากชื่อโฮสต์เป็นส่วนหนึ่งของชื่ออาร์เรย์ภายใน mdadm และส่งผลต่อบางอย่างในที่ใดที่หนึ่ง แน่นอนว่าสามารถเปลี่ยนชื่ออาร์เรย์ได้ในภายหลัง แต่นี่เป็นขั้นตอนที่ไม่จำเป็น

#mcedit /etc/hostname
#mcedit /etc/hosts
#hostname
vdesk0

NVMe SSD

#mdadm --create --verbose --assume-clean /dev/md0 --level=1 --raid-devices=2 /dev/nvme[0-1]n1

ทำไม -assume-clean...?เพื่อหลีกเลี่ยงการเริ่มต้นอาร์เรย์ สำหรับทั้ง RAID ระดับ 1 และ 6 สิ่งนี้ใช้ได้ ทุกอย่างสามารถทำงานได้โดยไม่ต้องเริ่มต้นหากเป็นอาร์เรย์ใหม่ นอกจากนี้ การเริ่มต้นอาร์เรย์ SSD ขณะสร้างถือเป็นการสิ้นเปลืองทรัพยากร TBW เราใช้ TRIM/DISCARD เมื่อเป็นไปได้บนอาร์เรย์ SSD ที่ประกอบเพื่อ "เริ่มต้น"

สำหรับอาร์เรย์ SSD นั้น รองรับ RAID 1 DISCARD ตั้งแต่แกะกล่อง

สำหรับอาร์เรย์ SSD RAID 6 DISCARD คุณต้องเปิดใช้งานในพารามิเตอร์โมดูลเคอร์เนล

สิ่งนี้ควรทำหาก SSD ทั้งหมดที่ใช้ในอาร์เรย์ระดับ 4/5/6 ในระบบนี้รองรับการทำงานสำหรับ discard_zeroes_data บางครั้งคุณเจอไดรฟ์แปลก ๆ ที่บอกเคอร์เนลว่ารองรับฟังก์ชันนี้ แต่จริงๆ แล้วไม่ได้อยู่ที่นั่นหรือฟังก์ชันไม่ทำงานเสมอไป ในขณะนี้การสนับสนุนมีให้บริการเกือบทุกที่อย่างไรก็ตามมีไดรฟ์และเฟิร์มแวร์เก่าที่มีข้อผิดพลาด ด้วยเหตุนี้ การสนับสนุน DISCARD จึงถูกปิดใช้งานตามค่าเริ่มต้นสำหรับ RAID 6

โปรดทราบ คำสั่งต่อไปนี้จะทำลายข้อมูลทั้งหมดบนไดรฟ์ NVMe โดย "เตรียมใช้งาน" อาร์เรย์ด้วย "ศูนย์"

#blkdiscard /dev/md0

หากมีสิ่งผิดปกติเกิดขึ้น ให้ลองระบุขั้นตอน

#blkdiscard --step 65536 /dev/md0

SATA SSD

#mdadm --create --verbose --assume-clean /dev/md1 --level=1 --raid-devices=6 /dev/sd[a-f]1
#blkdiscard /dev/md1
#mdadm --create --verbose --assume-clean /dev/md2 --chunk-size=512 --level=6 --raid-devices=6 /dev/sd[a-f]2

ทำไมใหญ่จัง...?การเพิ่มขนาดก้อนมีผลเชิงบวกต่อความเร็วของการอ่านแบบสุ่มในบล็อกจนถึงขนาดรวม สิ่งนี้เกิดขึ้นเนื่องจากการดำเนินการในขนาดที่เหมาะสมหรือเล็กกว่าสามารถดำเนินการให้เสร็จสิ้นได้ทั้งหมดในอุปกรณ์เครื่องเดียว ดังนั้น IOPS จากอุปกรณ์ทั้งหมดจึงถูกรวมเข้าด้วยกัน ตามสถิติ 99% ของ IO ไม่เกิน 512K

RAID 6 IOPS ต่อการเขียน เสมอ น้อยกว่าหรือเท่ากับ IOPS ของหนึ่งไดรฟ์ เมื่ออ่านแบบสุ่ม IOPS อาจมากกว่าไดรฟ์เดียวหลายเท่า และขนาดบล็อกมีความสำคัญอย่างยิ่งที่นี่
ผู้เขียนไม่เห็นประเด็นในการพยายามปรับพารามิเตอร์ที่แย่ใน RAID 6 ด้วยการออกแบบให้เหมาะสม แต่กลับปรับให้เหมาะสมกับสิ่งที่ RAID 6 ทำได้ดีแทน
เราจะชดเชยการเขียนแบบสุ่มที่ไม่ดีของ RAID 6 ด้วยแคช NVMe และเทคนิค Thin-Provisioning

เรายังไม่ได้เปิดใช้งาน DISCARD สำหรับ RAID 6 ดังนั้นเราจะไม่ "เริ่มต้น" อาร์เรย์นี้ในตอนนี้ เราจะดำเนินการนี้ในภายหลังหลังจากติดตั้งระบบปฏิบัติการ

SATA HDD

#mdadm --create --verbose --assume-clean /dev/md3 --chunk-size=512 --level=6 --raid-devices=8 /dev/sd[g-n]1

LVM บน NVMe RAID

เพื่อความรวดเร็ว เราต้องการวางระบบไฟล์รูทบน NVMe RAID 1 ซึ่งก็คือ /dev/md0
อย่างไรก็ตาม เรายังคงต้องการอาร์เรย์ที่รวดเร็วนี้สำหรับความต้องการอื่นๆ เช่น swap, metadata และ LVM-cache และ LVM-thin metadata ดังนั้นเราจะสร้าง LVM VG บนอาร์เรย์นี้

#pvcreate /dev/md0
#vgcreate root /dev/md0

มาสร้างพาร์ติชันสำหรับระบบไฟล์รูทกันดีกว่า

#lvcreate -L 128G --name root root

มาสร้างพาร์ติชั่นสำหรับสลับตามขนาดของ RAM กันดีกว่า

#lvcreate -L 32G --name swap root

การติดตั้งระบบปฏิบัติการ

โดยรวมแล้วเรามีทุกสิ่งที่จำเป็นในการติดตั้งระบบ

เปิดตัวช่วยสร้างการติดตั้งระบบจากสภาพแวดล้อม Ubuntu Live การติดตั้งปกติ ในขั้นตอนการเลือกดิสก์สำหรับการติดตั้งเท่านั้นคุณจะต้องระบุสิ่งต่อไปนี้:

  • /dev/md1, - จุดเมานท์ /boot, FS - BTRFS
  • /dev/root/root (aka /dev/mapper/root-root) - จุดเมานต์ / (รูท), FS - BTRFS
  • /dev/root/swap (aka /dev/mapper/root-swap) - ใช้เป็นพาร์ติชั่นสลับ
  • ติดตั้งโปรแกรมโหลดบูตบน /dev/sda

เมื่อคุณเลือก BTRFS เป็นระบบไฟล์รูท โปรแกรมติดตั้งจะสร้างวอลุ่ม BTRFS สองวอลุ่มชื่อ "@" สำหรับ / (root) และ "@home" สำหรับ /home โดยอัตโนมัติ

มาเริ่มการติดตั้งกัน...

การติดตั้งจะสิ้นสุดด้วยกล่องโต้ตอบโมดอลที่ระบุข้อผิดพลาดในการติดตั้ง bootloader ขออภัย คุณจะไม่สามารถออกจากกล่องโต้ตอบนี้โดยใช้วิธีมาตรฐานและทำการติดตั้งต่อได้ เราออกจากระบบและเข้าสู่ระบบอีกครั้ง และจบลงที่เดสก์ท็อป Ubuntu Live ที่สะอาดตา เปิดเทอร์มินัลและอีกครั้ง:

#sudo bash

สร้างสภาพแวดล้อม chroot เพื่อดำเนินการติดตั้งต่อ:

#mkdir /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@ /dev/mapper/root-root /mnt/chroot
#mount -o defaults,space_cache,noatime,nodiratime,discard,subvol=@home /dev/mapper/root-root /mnt/chroot/home
#mount -o defaults,space_cache,noatime,nodiratime,discard /dev/md1 /mnt/chroot/boot
#mount --bind /proc /mnt/chroot/proc
#mount --bind /sys /mnt/chroot/sys
#mount --bind /dev /mnt/chroot/dev

มากำหนดค่าเครือข่ายและชื่อโฮสต์ใน chroot:

#cat /etc/hostname >/mnt/chroot/etc/hostname
#cat /etc/hosts >/mnt/chroot/etc/hosts
#cat /etc/resolv.conf >/mnt/chroot/etc/resolv.conf

เข้าสู่สภาพแวดล้อม chroot กันเถอะ:

#chroot /mnt/chroot

ก่อนอื่นเราจะจัดส่งพัสดุ:

apt-get install --reinstall mdadm lvm2 thin-provisioning-tools btrfs-tools util-linux lsscsi nvme-cli mc debsums hdparm

มาตรวจสอบและแก้ไขแพ็คเกจทั้งหมดที่ติดตั้งผิดเนื่องจากการติดตั้งระบบไม่สมบูรณ์:

#CORRUPTED_PACKAGES=$(debsums -s 2>&1 | awk '{print $6}' | uniq)
#apt-get install --reinstall $CORRUPTED_PACKAGES

หากมีบางอย่างไม่ได้ผล คุณอาจต้องแก้ไข /etc/apt/sources.list ก่อน

มาปรับพารามิเตอร์สำหรับโมดูล RAID 6 เพื่อเปิดใช้งาน TRIM/DISCARD:

#cat >/etc/modprobe.d/raid456.conf << EOF
options raid456 devices_handle_discard_safely=1
EOF

มาปรับแต่งอาร์เรย์ของเรากันหน่อย:

#cat >/etc/udev/rules.d/60-md.rules << EOF
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/stripe_cache_size", ATTR{md/stripe_cache_size}="32768"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_min", ATTR{md/sync_speed_min}="48000"
SUBSYSTEM=="block", KERNEL=="md*", ACTION=="change", TEST=="md/sync_speed_max", ATTR{md/sync_speed_max}="300000"
EOF
#cat >/etc/udev/rules.d/62-hdparm.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/hdparm -B 254 /dev/%k"
EOF
#cat >/etc/udev/rules.d/63-blockdev.rules << EOF
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", RUN+="/sbin/blockdev --setra 1024 /dev/%k"
SUBSYSTEM=="block", ACTION=="add|change", KERNEL=="md*", RUN+="/sbin/blockdev --setra 0 /dev/%k"
EOF

มันคืออะไร..?เราได้สร้างชุดกฎ udev ที่จะทำดังต่อไปนี้:

  • ตั้งค่าขนาดบล็อกแคชสำหรับ RAID 2020 ให้เพียงพอสำหรับปี 6 ดูเหมือนว่าค่าเริ่มต้นจะไม่เปลี่ยนแปลงตั้งแต่การสร้าง Linux และไม่เพียงพอมาเป็นเวลานาน
  • สำรอง IO ขั้นต่ำไว้ตลอดระยะเวลาการตรวจสอบ/การซิงโครไนซ์อาร์เรย์ นี่เป็นการป้องกันไม่ให้อาร์เรย์ของคุณติดอยู่ในสถานะการซิงโครไนซ์ชั่วนิรันดร์ภายใต้โหลด
  • จำกัด IO สูงสุดระหว่างการตรวจสอบ/การซิงโครไนซ์อาร์เรย์ นี่เป็นสิ่งจำเป็นเพื่อให้การซิงโครไนซ์/การตรวจสอบ SSD RAID ไม่ทำให้ไดรฟ์ของคุณมีความคมชัด นี่เป็นเรื่องจริงโดยเฉพาะอย่างยิ่งสำหรับ NVMe (จำเรื่องหม้อน้ำได้ไหมฉันไม่ได้ล้อเล่น)
  • ห้ามดิสก์ไม่ให้หยุดการหมุนสปินเดิล (HDD) ผ่าน APM และตั้งค่าการหมดเวลาพักเครื่องสำหรับตัวควบคุมดิสก์เป็น 7 ชั่วโมง คุณสามารถปิดการใช้งาน APM ได้อย่างสมบูรณ์หากไดรฟ์ของคุณสามารถทำได้ (-B 255) ด้วยค่าเริ่มต้น ไดรฟ์จะหยุดหลังจากผ่านไปห้าวินาที จากนั้นระบบปฏิบัติการต้องการรีเซ็ตดิสก์แคช ดิสก์จะหมุนอีกครั้ง และทุกอย่างจะเริ่มต้นใหม่อีกครั้ง แผ่นดิสก์มีจำนวนการหมุนสปินเดิลสูงสุดที่จำกัด วงจรเริ่มต้นง่ายๆ ดังกล่าวสามารถทำลายดิสก์ของคุณได้อย่างง่ายดายภายในสองสามปี ไม่ใช่ว่าดิสก์ทุกตัวจะประสบปัญหานี้ แต่ดิสก์ของเราเป็น "แล็ปท็อป" โดยมีการตั้งค่าเริ่มต้นที่เหมาะสม ซึ่งทำให้ RAID ดูเหมือน mini-MAID
  • ติดตั้ง readahead บนดิสก์ (หมุน) 1 เมกะไบต์ - สองบล็อกติดต่อกัน/ก้อน RAID 6
  • ปิดใช้งานการอ่านล่วงหน้าบนอาร์เรย์ด้วยตนเอง

มาแก้ไขกัน /etc/fstab:

#cat >/etc/fstab << EOF
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
# file-system mount-point type options dump pass
/dev/mapper/root-root / btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@ 0 1
UUID=$(blkid -o value -s UUID /dev/md1) /boot btrfs defaults,space_cache,noatime,nodiratime,discard 0 2
/dev/mapper/root-root /home btrfs defaults,space_cache,noatime,nodiratime,discard,subvol=@home 0 2
/dev/mapper/root-swap none swap sw 0 0
EOF

ทำไมเป็นอย่างนั้น..?เราจะค้นหาพาร์ติชัน /boot ด้วย UUID การตั้งชื่ออาร์เรย์อาจมีการเปลี่ยนแปลงในทางทฤษฎี

เราจะค้นหาส่วนที่เหลือด้วยชื่อ LVM ในรูปแบบ /dev/mapper/vg-lv เนื่องจาก พวกเขาระบุพาร์ติชันไม่ซ้ำกัน

เราไม่ได้ใช้ UUID สำหรับ LVM เพราะ UUID ของวอลุ่ม LVM และสแน็ปช็อตสามารถเหมือนกันได้เมานต์ /dev/mapper/root-root.. สองครั้ง?ใช่. อย่างแน่นอน. คุณสมบัติของ BTRFS ระบบไฟล์นี้สามารถติดตั้งได้หลายครั้งด้วย subvols ที่แตกต่างกัน

เนื่องจากคุณสมบัติเดียวกันนี้ ฉันขอแนะนำไม่ให้สร้างสแน็ปช็อต LVM ของวอลุ่ม BTRFS ที่ใช้งานอยู่ คุณอาจได้รับความประหลาดใจเมื่อคุณรีบูต

มาสร้างการกำหนดค่า mdadm ใหม่:

#/usr/share/mdadm/mkconf | sed 's/#DEVICE/DEVICE/g' >/etc/mdadm/mdadm.conf

มาปรับการตั้งค่า LVM กัน:

#cat >>/etc/lvm/lvmlocal.conf << EOF

activation {
thin_pool_autoextend_threshold=90
thin_pool_autoextend_percent=5
}
allocation {
cache_pool_max_chunks=2097152
}
devices {
global_filter=["r|^/dev/.*_corig$|","r|^/dev/.*_cdata$|","r|^/dev/.*_cmeta$|","r|^/dev/.*gpv$|","r|^/dev/images/.*$|","r|^/dev/mapper/images.*$|","r|^/dev/backup/.*$|","r|^/dev/mapper/backup.*$|"] issue_discards=1
}
EOF

มันคืออะไร..?เราได้เปิดใช้งานการขยายพูลแบบบางของ LVM โดยอัตโนมัติเมื่อถึง 90% ของพื้นที่ครอบครอง 5% ของปริมาตร

เราได้เพิ่มจำนวนบล็อกแคชสูงสุดสำหรับแคช LVM

เราได้ป้องกันไม่ให้ LVM ค้นหาปริมาณ LVM (PV) บน:

  • อุปกรณ์ที่มีแคช LVM (cdata)
  • อุปกรณ์ที่แคชไว้โดยใช้แคช LVM โดยข้ามแคช ( _โคริก) ในกรณีนี้ อุปกรณ์ที่แคชไว้จะยังคงถูกสแกนผ่านแคช (เพียง ).
  • อุปกรณ์ที่มีข้อมูลเมตาแคช LVM (cmeta)
  • อุปกรณ์ทั้งหมดใน VG พร้อมชื่อภาพ ที่นี่เราจะมีดิสก์อิมเมจของเครื่องเสมือน และเราไม่ต้องการให้ LVM บนโฮสต์เปิดใช้งานโวลุ่มที่เป็นของ guest OS
  • อุปกรณ์ทั้งหมดใน VG พร้อมการสำรองชื่อ ที่นี่เราจะมีสำเนาสำรองของอิมเมจเครื่องเสมือน
  • อุปกรณ์ทั้งหมดที่มีชื่อลงท้ายด้วย “gpv” (เกสต์ฟิสิคัลวอลุ่ม)

เราได้เปิดใช้งานการสนับสนุน DISCARD เมื่อเพิ่มพื้นที่ว่างบน LVM VG ระวัง. ซึ่งจะทำให้การลบ LV บน SSD ค่อนข้างใช้เวลานาน สิ่งนี้ใช้ได้กับ SSD RAID 6 โดยเฉพาะ อย่างไรก็ตาม ตามแผน เราจะใช้การจัดเตรียมแบบบาง ดังนั้นสิ่งนี้จะไม่ขัดขวางเราเลย

มาอัปเดตอิมเมจ initramfs กัน:

#update-initramfs -u -k all

ติดตั้งและกำหนดค่าด้วง:

#apt-get install grub-pc
#apt-get purge os-prober
#dpkg-reconfigure grub-pc

ดิสก์ใดให้เลือก?ทุกคนที่เป็น sd* ระบบจะต้องสามารถบูตจากไดรฟ์ SATA หรือ SSD ที่ใช้งานได้

ทำไมเขาถึงเพิ่ม os-prober..?เพื่อความเป็นอิสระและมือที่ขี้เล่น

มันทำงานไม่ถูกต้องหาก RAID ตัวใดตัวหนึ่งอยู่ในสถานะลดระดับ พยายามค้นหาระบบปฏิบัติการบนพาร์ติชันที่ใช้ในเครื่องเสมือนที่ทำงานบนฮาร์ดแวร์นี้

หากคุณต้องการคุณสามารถทิ้งมันไว้ได้ แต่โปรดจำไว้ทั้งหมดข้างต้น ฉันแนะนำให้มองหาสูตรกำจัดมือซุกซนทางออนไลน์

ด้วยเหตุนี้ เราจึงได้เสร็จสิ้นการติดตั้งเบื้องต้น ได้เวลารีบูทเข้าสู่ระบบปฏิบัติการที่เพิ่งติดตั้งใหม่ อย่าลืมถอด Live CD/USB ที่สามารถบู๊ตได้ออก

#exit
#reboot

เลือก SATA SSD ใดก็ได้เป็นอุปกรณ์สำหรับบู๊ต

LVM บน SATA SSD

ณ จุดนี้ เราได้บูตเข้าสู่ระบบปฏิบัติการใหม่แล้ว กำหนดค่าเครือข่าย apt เปิดโปรแกรมจำลองเทอร์มินัล และเปิดตัว:

#sudo bash

มาต่อกันเลย

“ เริ่มต้น” อาร์เรย์จาก SATA SSD:

#blkdiscard /dev/md2

หากไม่ได้ผล ให้ลอง:

#blkdiscard --step 65536 /dev/md2
สร้าง LVM VG บน SATA SSD:

#pvcreate /dev/md2
#vgcreate data /dev/md2

ทำไมต้องมี VG อีก..?จริงๆ แล้ว เรามี VG ชื่อ root อยู่แล้ว ทำไมไม่รวมทุกอย่างไว้ใน VG เดียวล่ะ?

หากมี PV หลายตัวใน VG ดังนั้นเพื่อให้ VG เปิดใช้งานได้อย่างถูกต้อง PV ทั้งหมดจะต้องมีอยู่ (ออนไลน์) ข้อยกเว้นคือ LVM RAID ซึ่งเราจงใจไม่ใช้

เราต้องการจริงๆ ว่าหากมีความล้มเหลว (การสูญเสียข้อมูลในการอ่าน) ในอาร์เรย์ RAID 6 ใดๆ ระบบปฏิบัติการจะบูตได้ตามปกติและให้โอกาสเราในการแก้ไขปัญหา

เพื่อทำเช่นนี้ ในระดับแรกของนามธรรม เราจะแยก "สื่อ" ทางกายภาพแต่ละประเภทออกเป็น VG ที่แยกจากกัน

หากพูดในเชิงวิทยาศาสตร์ อาร์เรย์ RAID ที่แตกต่างกันจะอยู่ใน “โดเมนความน่าเชื่อถือ” ที่แตกต่างกัน คุณไม่ควรสร้างจุดล้มเหลวร่วมกันเพิ่มเติมให้พวกเขาโดยการอัดรวมไว้ใน VG เดียว

การมีอยู่ของ LVM ในระดับ “ฮาร์ดแวร์” จะทำให้เราสามารถตัดส่วนต่างๆ ของอาร์เรย์ RAID ต่างๆ โดยพลการโดยการรวมเข้าด้วยกันในรูปแบบต่างๆ ตัวอย่างเช่น - วิ่ง พร้อมกัน bcache + LVM Thin, bcache + BTRFS, แคช LVM + LVM Thin, การกำหนดค่า ZFS ที่ซับซ้อนพร้อมแคช หรือส่วนผสมที่ชั่วร้ายอื่น ๆ เพื่อลองและเปรียบเทียบทั้งหมด

ในระดับ “ฮาร์ดแวร์” เราจะไม่ใช้สิ่งอื่นใดนอกจากวอลุ่ม LVM “หนา” แบบเก่าที่ดี ข้อยกเว้นสำหรับกฎนี้อาจเป็นพาร์ติชันสำรอง

ฉันคิดว่าตอนนี้ผู้อ่านหลายคนเริ่มสงสัยบางอย่างเกี่ยวกับตุ๊กตาทำรังแล้ว

LVM บนฮาร์ดดิสก์ SATA

#pvcreate /dev/md3
#vgcreate backup /dev/md3

VG ใหม่อีกแล้ว..?เราต้องการจริงๆ ว่าหากดิสก์อาร์เรย์ที่เราจะใช้สำรองข้อมูลล้มเหลว ระบบปฏิบัติการของเราจะยังคงทำงานได้ตามปกติ โดยยังคงรักษาการเข้าถึงข้อมูลที่ไม่ได้สำรองข้อมูลไว้ตามปกติ ดังนั้น เพื่อหลีกเลี่ยงปัญหาการเปิดใช้งาน VG เราจึงสร้าง VG แยกต่างหาก

การตั้งค่าแคช LVM

มาสร้าง LV บน NVMe RAID 1 เพื่อใช้เป็นอุปกรณ์แคชกันดีกว่า

#lvcreate -L 70871154688B --name cache root

ทำไมมีน้อยจัง...?ความจริงก็คือ NVMe SSD ของเรามีแคช SLC เช่นกัน "ฟรี" 4 กิกะไบต์และไดนามิก 18 กิกะไบต์เนื่องจากพื้นที่ว่างใน MLC 3 บิต เมื่อแคชนี้หมด NVMe SSD จะไม่เร็วกว่า SATA SSD ที่มีแคชของเรามากนัก ที่จริงแล้ว ด้วยเหตุนี้ จึงไม่สมเหตุสมผลที่เราจะสร้างพาร์ติชันแคช LVM ที่มีขนาดใหญ่กว่าสองเท่าของขนาดแคช SLC ของไดรฟ์ NVMe สำหรับไดรฟ์ NVMe ที่ใช้ ผู้เขียนเห็นว่าสมเหตุสมผลที่จะสร้างแคชขนาด 32-64 กิกะไบต์

ต้องใช้ขนาดพาร์ติชันที่กำหนดเพื่อจัดระเบียบแคช ข้อมูลเมตาของแคช และการสำรองข้อมูลเมตาดาต้า 64 กิกะไบต์

นอกจากนี้ ฉันทราบว่าหลังจากการปิดระบบสกปรก LVM จะทำเครื่องหมายแคชทั้งหมดว่าสกปรก และจะซิงโครไนซ์อีกครั้ง นอกจากนี้ จะทำซ้ำทุกครั้งที่ใช้ lvchange บนอุปกรณ์นี้จนกว่าระบบจะรีบูตอีกครั้ง ดังนั้นฉันขอแนะนำให้สร้างแคชใหม่ทันทีโดยใช้สคริปต์ที่เหมาะสม

มาสร้าง LV บน SATA RAID 6 เพื่อใช้เป็นอุปกรณ์แคชกัน

#lvcreate -L 3298543271936B --name cache data

ทำไมเพียงสามเทราไบต์..?เพื่อให้คุณสามารถใช้ SATA SSD RAID 6 สำหรับความต้องการอื่นๆ ได้หากจำเป็น ขนาดของพื้นที่แคชสามารถเพิ่มขึ้นแบบไดนามิกได้ทันทีโดยไม่ต้องหยุดระบบ ในการดำเนินการนี้ คุณจะต้องหยุดชั่วคราวและเปิดใช้งานแคชอีกครั้ง แต่ข้อดีที่โดดเด่นของแคช LVM เหนือกว่า เช่น bcache ก็คือสามารถทำได้ทันที

มาสร้าง VG ใหม่สำหรับการแคชกันดีกว่า

#pvcreate /dev/root/cache
#pvcreate /dev/data/cache
#vgcreate cache /dev/root/cache /dev/data/cache

มาสร้าง LV บนอุปกรณ์แคชกันดีกว่า

#lvcreate -L 3298539077632B --name cachedata cache /dev/data/cache

ที่นี่เราใช้พื้นที่ว่างทั้งหมดบน /dev/data/cache ทันที เพื่อให้พาร์ติชันที่จำเป็นอื่น ๆ ทั้งหมดถูกสร้างขึ้นทันทีบน /dev/root/cache หากคุณสร้างบางอย่างผิดที่ คุณสามารถย้ายมันได้โดยใช้ pvmove

มาสร้างและเปิดใช้งานแคชกันเถอะ:

#lvcreate -y -L 64G -n cache cache /dev/root/cache
#lvcreate -y -L 1G -n cachemeta cache /dev/root/cache
#lvconvert -y --type cache-pool --cachemode writeback --chunksize 64k --poolmetadata cache/cachemeta cache/cache
#lvconvert -y --type cache --cachepool cache/cache cache/cachedata

ทำไมชิ้นใหญ่ขนาดนี้..?จากการทดลองเชิงปฏิบัติ ผู้เขียนสามารถค้นพบว่าผลลัพธ์ที่ดีที่สุดจะเกิดขึ้นได้หากขนาดของบล็อกแคช LVM ตรงกับขนาดของบล็อกแบบบางของ LVM นอกจากนี้ ยิ่งขนาดมีขนาดเล็กลง การกำหนดค่าก็จะทำงานได้ดีขึ้นในการบันทึกแบบสุ่ม

64k คือขนาดบล็อกขั้นต่ำที่อนุญาตสำหรับ LVM แบบบาง

ระวังเขียนกลับ..!ใช่. แคชประเภทนี้เลื่อนการซิงโครไนซ์การเขียนไปยังอุปกรณ์แคช ซึ่งหมายความว่าหากแคชสูญหาย คุณอาจสูญเสียข้อมูลในอุปกรณ์แคช หลังจากนั้น ผู้เขียนจะบอกคุณว่ามาตรการใดบ้างที่สามารถใช้เพื่อชดเชยความเสี่ยงนี้ นอกเหนือจาก NVMe RAID 1

ประเภทแคชนี้ถูกเลือกโดยเจตนาเพื่อชดเชยประสิทธิภาพการเขียนแบบสุ่มที่ไม่ดีของ RAID 6

ตรวจสอบสิ่งที่เราได้รับ:

#lvs -a -o lv_name,lv_size,devices --units B cache
LV LSize Devices
[cache] 68719476736B cache_cdata(0)
[cache_cdata] 68719476736B /dev/root/cache(0)
[cache_cmeta] 1073741824B /dev/root/cache(16384)
cachedata 3298539077632B cachedata_corig(0)
[cachedata_corig] 3298539077632B /dev/data/cache(0)
[lvol0_pmspare] 1073741824B /dev/root/cache(16640)

เฉพาะ [cachedata_corig] เท่านั้นที่ควรอยู่ใน /dev/data/cache หากมีสิ่งผิดปกติให้ใช้ pvmove

คุณสามารถปิดการใช้งานแคชได้หากจำเป็นด้วยคำสั่งเดียว:

#lvconvert -y --uncache cache/cachedata

นี้จะกระทำแบบออนไลน์ LVM จะซิงค์แคชกับดิสก์ ลบออก และเปลี่ยนชื่อ cachedata_corig กลับไปเป็น cachedata

การตั้งค่า LVM แบบบาง

ลองประมาณคร่าวๆ ว่าเราต้องการพื้นที่เท่าใดสำหรับเมตาดาต้าแบบบางของ LVM:

#thin_metadata_size --block-size=64k --pool-size=6terabytes --max-thins=100000 -u bytes
thin_metadata_size - 3385794560 bytes estimated metadata area size for "--block-size=64kibibytes --pool-size=6terabytes --max-thins=100000"

ปัดเศษขึ้นเป็น 4 กิกะไบต์: 4294967296B

คูณด้วยสองและเพิ่ม 4194304B สำหรับข้อมูลเมตา LVM PV: 8594128896B
มาสร้างพาร์ติชันแยกต่างหากบน NVMe RAID 1 เพื่อวางข้อมูลเมตาแบบบางของ LVM และสำเนาสำรองไว้:

#lvcreate -L 8594128896B --name images root

เพื่ออะไร..?คำถามนี้อาจเกิดขึ้น: เหตุใดจึงแยกข้อมูลเมตาแบบบางของ LVM ออกจากกัน หากจะยังคงแคชไว้บน NVMe และจะทำงานได้อย่างรวดเร็ว

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

ด้วยการย้ายข้อมูลเมตาไปยังโวลุ่มแยกต่างหากที่ไม่ใช่แคช แต่รวดเร็ว เรารับประกันความปลอดภัยของข้อมูลเมตาในกรณีที่แคชสูญหายหรือเสียหาย ในกรณีนี้ ความเสียหายทั้งหมดที่เกิดจากการสูญเสียแคชจะถูกแปลเป็นภาษาท้องถิ่นภายในวอลุ่มแบบบาง ซึ่งจะทำให้ขั้นตอนการกู้คืนง่ายขึ้นตามลำดับความสำคัญ มีความเป็นไปได้สูง ความเสียหายเหล่านี้จะถูกกู้คืนโดยใช้บันทึก FS

ยิ่งไปกว่านั้น หากก่อนหน้านี้มีการถ่ายภาพสแน็ปช็อตของ Thin Volume และหลังจากนั้นแคชถูกซิงโครไนซ์อย่างสมบูรณ์อย่างน้อยหนึ่งครั้ง ดังนั้น เนื่องจากการออกแบบภายในของ LVM Thin ความสมบูรณ์ของสแน็ปช็อตจะได้รับการรับประกันในกรณีที่แคชสูญหาย .

มาสร้าง VG ใหม่ที่จะรับผิดชอบในการจัดเตรียมแบบบาง:

#pvcreate /dev/root/images
#pvcreate /dev/cache/cachedata
#vgcreate images /dev/root/images /dev/cache/cachedata

มาสร้างพูลกันเถอะ:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
ทำไม -Z ยนอกเหนือจากสิ่งที่โหมดนี้มีไว้สำหรับ - เพื่อป้องกันไม่ให้ข้อมูลจากเครื่องเสมือนหนึ่งรั่วไหลไปยังเครื่องเสมือนอื่นเมื่อกระจายพื้นที่ - การใช้ค่าศูนย์เพิ่มเติมเพื่อเพิ่มความเร็วของการเขียนแบบสุ่มในบล็อกที่เล็กกว่า 64k การเขียนใดๆ ที่น้อยกว่า 64k ไปยังพื้นที่ที่ไม่ได้จัดสรรก่อนหน้านี้ของไดรฟ์ข้อมูลแบบบางจะกลายเป็น 64K ที่จัดชิดขอบในแคช ซึ่งจะทำให้การดำเนินการทั้งหมดดำเนินการผ่านแคช โดยข้ามอุปกรณ์ที่แคชไว้

มาย้าย LV ไปยัง PV ที่เกี่ยวข้องกัน:

#pvmove -n images/thin-pool_tdata /dev/root/images /dev/cache/cachedata
#pvmove -n images/lvol0_pmspare /dev/cache/cachedata /dev/root/images
#pvmove -n images/thin-pool_tmeta /dev/cache/cachedata /dev/root/images

ตรวจสอบ:

#lvs -a -o lv_name,lv_size,devices --units B images
LV LSize Devices
[lvol0_pmspare] 4294967296B /dev/root/images(0)
thin-pool 274877906944B thin-pool_tdata(0)
[thin-pool_tdata] 274877906944B /dev/cache/cachedata(0)
[thin-pool_tmeta] 4294967296B /dev/root/images(1024)

มาสร้างวอลลุ่มเล็กๆ สำหรับการทดสอบกันดีกว่า:

#lvcreate -V 64G --thin-pool thin-pool --name test images

เราจะติดตั้งแพ็คเกจสำหรับการทดสอบและการตรวจสอบ:

#apt-get install sysstat fio

นี่คือวิธีที่คุณสามารถสังเกตพฤติกรรมของการกำหนดค่าพื้นที่เก็บข้อมูลของเราแบบเรียลไทม์:

#watch 'lvs --rows --reportformat basic --quiet -ocache_dirty_blocks,cache_settings cache/cachedata && (lvdisplay cache/cachedata | grep Cache) && (sar -p -d 2 1 | grep -E "sd|nvme|DEV|md1|md2|md3|md0" | grep -v Average | sort)'

นี่คือวิธีที่เราสามารถทดสอบการกำหนดค่าของเรา:

#fio --loops=1 --size=64G --runtime=4 --filename=/dev/images/test --stonewall --ioengine=libaio --direct=1
--name=4kQD32read --bs=4k --iodepth=32 --rw=randread
--name=8kQD32read --bs=8k --iodepth=32 --rw=randread
--name=16kQD32read --bs=16k --iodepth=32 --rw=randread
--name=32KQD32read --bs=32k --iodepth=32 --rw=randread
--name=64KQD32read --bs=64k --iodepth=32 --rw=randread
--name=128KQD32read --bs=128k --iodepth=32 --rw=randread
--name=256KQD32read --bs=256k --iodepth=32 --rw=randread
--name=512KQD32read --bs=512k --iodepth=32 --rw=randread
--name=4Kread --bs=4k --rw=read
--name=8Kread --bs=8k --rw=read
--name=16Kread --bs=16k --rw=read
--name=32Kread --bs=32k --rw=read
--name=64Kread --bs=64k --rw=read
--name=128Kread --bs=128k --rw=read
--name=256Kread --bs=256k --rw=read
--name=512Kread --bs=512k --rw=read
--name=Seqread --bs=1m --rw=read
--name=Longread --bs=8m --rw=read
--name=Longwrite --bs=8m --rw=write
--name=Seqwrite --bs=1m --rw=write
--name=512Kwrite --bs=512k --rw=write
--name=256write --bs=256k --rw=write
--name=128write --bs=128k --rw=write
--name=64write --bs=64k --rw=write
--name=32write --bs=32k --rw=write
--name=16write --bs=16k --rw=write
--name=8write --bs=8k --rw=write
--name=4write --bs=4k --rw=write
--name=512KQD32write --bs=512k --iodepth=32 --rw=randwrite
--name=256KQD32write --bs=256k --iodepth=32 --rw=randwrite
--name=128KQD32write --bs=128k --iodepth=32 --rw=randwrite
--name=64KQD32write --bs=64k --iodepth=32 --rw=randwrite
--name=32KQD32write --bs=32k --iodepth=32 --rw=randwrite
--name=16KQD32write --bs=16k --iodepth=32 --rw=randwrite
--name=8KQD32write --bs=8k --iodepth=32 --rw=randwrite
--name=4kQD32write --bs=4k --iodepth=32 --rw=randwrite
| grep -E 'read|write|test' | grep -v ioengine

อย่างระมัดระวัง! ทรัพยากร!รหัสนี้จะทำการทดสอบที่แตกต่างกัน 36 รายการ แต่ละการทดสอบใช้เวลา 4 วินาที ข้อสอบครึ่งหนึ่งมีไว้สำหรับการบันทึก คุณสามารถบันทึกจำนวนมากบน NVMe ได้ใน 4 วินาที มากถึง 3 กิกะไบต์ต่อวินาที ดังนั้น การทดสอบการเขียนแต่ละครั้งจะกินทรัพยากร SSD ของคุณได้มากถึง 216 กิกะไบต์

การอ่านและการเขียนปะปนกัน?ใช่. ควรทำการทดสอบการอ่านและเขียนแยกกัน ยิ่งไปกว่านั้น ควรทำให้แน่ใจว่าแคชทั้งหมดซิงโครไนซ์เพื่อให้การเขียนที่ทำไว้ก่อนหน้านี้ไม่ส่งผลต่อการอ่าน

ผลลัพธ์จะแตกต่างกันอย่างมากในระหว่างการเปิดตัวครั้งแรกและครั้งต่อๆ ไป เนื่องจากแคชและวอลุ่มแบบบางเต็ม และยังขึ้นอยู่กับว่าระบบจัดการซิงโครไนซ์แคชที่เติมระหว่างการเปิดตัวครั้งล่าสุดหรือไม่

เหนือสิ่งอื่นใด ฉันแนะนำให้วัดความเร็วบนปริมาตรที่บางอยู่แล้วซึ่งเพิ่งถ่ายภาพสแนปช็อต ผู้เขียนมีโอกาสสังเกตว่าการเขียนแบบสุ่มเร่งความเร็วอย่างรวดเร็วทันทีหลังจากสร้างสแน็ปช็อตแรก โดยเฉพาะอย่างยิ่งเมื่อแคชยังไม่เต็ม สิ่งนี้เกิดขึ้นเนื่องจากซีแมนทิกส์การเขียนแบบคัดลอกเมื่อเขียน การจัดตำแหน่งของแคชและบล็อกวอลุ่มแบบบาง และความจริงที่ว่าการเขียนแบบสุ่มไปยัง RAID 6 จะกลายเป็นการอ่านแบบสุ่มจาก RAID 6 ตามด้วยการเขียนไปยังแคช ในการกำหนดค่าของเรา การอ่านแบบสุ่มจาก RAID 6 จะสูงถึง 6 เท่า (จำนวน SATA SSD ในอาเรย์) เร็วกว่าการเขียน เพราะ บล็อกสำหรับ CoW จะถูกจัดสรรตามลำดับจากพูลแบบบาง จากนั้นการบันทึกโดยส่วนใหญ่จะกลายเป็นตามลำดับด้วย

คุณสมบัติทั้งสองนี้สามารถใช้เพื่อประโยชน์ของคุณได้

แคชสแน็ปช็อต "สอดคล้องกัน"

เพื่อลดความเสี่ยงของการสูญเสียข้อมูลในกรณีที่แคชเสียหาย/สูญหาย ผู้เขียนเสนอให้แนะนำแนวทางปฏิบัติในการหมุนสแน็ปช็อตเพื่อรับประกันความสมบูรณ์ของภาพในกรณีนี้

ประการแรก เนื่องจากข้อมูลเมตาที่มีปริมาณน้อยอยู่บนอุปกรณ์ที่ไม่ได้แคช ข้อมูลเมตาจึงมีความสอดคล้องกัน และการสูญเสียที่อาจเกิดขึ้นจะถูกแยกออกจากบล็อกข้อมูล

วงจรการหมุนเวียนสแน็ปช็อตต่อไปนี้รับประกันความสมบูรณ์ของข้อมูลภายในสแน็ปช็อตในกรณีที่แคชสูญหาย:

  1. สำหรับ Thin Volume แต่ละวอลุ่มที่มีชื่อ <name> ให้สร้างสแน็ปช็อตโดยใช้ชื่อ <name>.cached
  2. มาตั้งค่าเกณฑ์การย้ายข้อมูลให้มีมูลค่าสูงพอสมควร: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. ในลูปเราจะตรวจสอบจำนวนบล็อกสกปรกในแคช: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' จนกว่าเราจะได้ศูนย์ หากศูนย์หายไปนานเกินไป สามารถสร้างได้โดยการเปลี่ยนแคชเป็นโหมดการเขียนผ่านชั่วคราว อย่างไรก็ตาม เมื่อคำนึงถึงลักษณะความเร็วของอาร์เรย์ SATA และ NVMe SSD ของเราตลอดจนทรัพยากร TBW คุณจะสามารถจับภาพช่วงเวลาได้อย่างรวดเร็วโดยไม่ต้องเปลี่ยนโหมดแคช ไม่เช่นนั้นฮาร์ดแวร์ของคุณจะกินทรัพยากรทั้งหมดจนหมด ไม่กี่วัน. เนื่องจากข้อจำกัดด้านทรัพยากร โดยหลักการแล้ว ระบบจะไม่สามารถโหลดการเขียนต่ำกว่า 100% ได้ตลอดเวลา NVMe SSD ของเราที่มีภาระการเขียนต่ำกว่า 100% จะทำให้ทรัพยากรในนั้นหมดไปโดยสิ้นเชิง 3 4-วัน. SATA SSD จะมีอายุการใช้งานเพียงสองเท่าเท่านั้น ดังนั้น เราจะถือว่าโหลดส่วนใหญ่จะไปที่การอ่าน และเรามีกิจกรรมที่สูงมากในระยะสั้นรวมกับโหลดโดยเฉลี่ยสำหรับการเขียนที่ต่ำ
  4. ทันทีที่เราพบ (หรือสร้าง) ค่าศูนย์ เราจะเปลี่ยนชื่อ <name>.cached เป็น <name>.comMIT <name>.commit เก่าถูกลบไปแล้ว
  5. อีกทางเลือกหนึ่ง หากแคชเต็ม 100% แคชก็สามารถสร้างใหม่ได้ด้วยสคริปต์ ดังนั้นจะเป็นการล้างแคช ด้วยแคชที่ว่างครึ่งหนึ่ง ระบบจะทำงานเร็วขึ้นมากเมื่อเขียน
  6. ตั้งค่าเกณฑ์การย้ายข้อมูลเป็นศูนย์: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata วิธีนี้จะป้องกันไม่ให้แคชซิงโครไนซ์กับสื่อหลักชั่วคราว
  7. เรารอจนกว่าการเปลี่ยนแปลงจะสะสมอยู่ในแคชค่อนข้างมาก #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' หรือตัวจับเวลาจะดับลง
  8. เราทำซ้ำอีกครั้ง

เหตุใดจึงเกิดปัญหากับเกณฑ์การโยกย้าย...?ประเด็นก็คือ ในทางปฏิบัติจริง การบันทึกแบบ "สุ่ม" ไม่ใช่การสุ่มทั้งหมด หากเราเขียนบางสิ่งลงในเซกเตอร์ขนาด 4 กิโลไบต์ มีความเป็นไปได้สูงที่ในอีกไม่กี่นาทีข้างหน้าจะมีการบันทึกข้อมูลลงในเซกเตอร์เดียวกันหรือเซกเตอร์ใกล้เคียง (+- 32K)

ด้วยการตั้งค่าเกณฑ์การย้ายข้อมูลเป็นศูนย์ เราจะเลื่อนการซิงโครไนซ์การเขียนบน SATA SSD และรวมการเปลี่ยนแปลงต่างๆ ไว้ในบล็อก 64K หนึ่งบล็อกในแคช สิ่งนี้ช่วยประหยัดทรัพยากรของ SATA SSD ได้อย่างมาก

รหัสอยู่ไหน..?น่าเสียดายที่ผู้เขียนคิดว่าตัวเองไม่มีความสามารถเพียงพอในการพัฒนาสคริปต์ทุบตีเพราะเขาเรียนรู้ด้วยตนเอง 100% และฝึกฝนการพัฒนาที่ขับเคลื่อนด้วย "google" ดังนั้นเขาจึงเชื่อว่ารหัสที่น่ากลัวที่ออกมาจากมือของเขาไม่ควรถูกใช้โดยใครก็ตาม อื่น.

ฉันคิดว่าผู้เชี่ยวชาญในสาขานี้จะสามารถอธิบายตรรกะทั้งหมดที่อธิบายไว้ข้างต้นได้อย่างอิสระหากจำเป็น และอาจออกแบบให้เป็นบริการ systemd ได้อย่างสวยงามตามที่ผู้เขียนพยายามทำ

รูปแบบการหมุนเวียนสแน็ปช็อตแบบง่าย ๆ ดังกล่าวไม่เพียงช่วยให้เรามีสแน็ปช็อตเดียวที่ซิงโครไนซ์อย่างสมบูรณ์บน SATA SSD อย่างต่อเนื่อง แต่ยังช่วยให้เราใช้ยูทิลิตี้ Thin_delta เพื่อค้นหาว่าบล็อกใดที่มีการเปลี่ยนแปลงหลังจากการสร้าง และด้วยเหตุนี้จึงจำกัดความเสียหายใน ไดรฟ์ข้อมูลหลักทำให้การกู้คืนง่ายขึ้นอย่างมาก

TRIM/DISCARD ใน libvirt/KVM

เพราะ พื้นที่จัดเก็บข้อมูลจะถูกใช้สำหรับ KVM ที่ใช้งาน libvirt จึงเป็นความคิดที่ดีที่จะสอน VM ของเราไม่เพียงแต่ให้ใช้พื้นที่ว่างเท่านั้น แต่ยังเพิ่มพื้นที่ว่างที่ไม่จำเป็นอีกต่อไปอีกด้วย

ซึ่งทำได้โดยการจำลองการสนับสนุน TRIM/DISCARD บนดิสก์เสมือน ในการดำเนินการนี้ คุณจะต้องเปลี่ยนประเภทคอนโทรลเลอร์เป็น virtio-scsi และแก้ไข xml

#virsh edit vmname
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='writethrough' io='threads' discard='unmap'/>
<source dev='/dev/images/vmname'/>
<backingStore/>
<target dev='sda' bus='scsi'/>
<alias name='scsi0-0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>

<controller type='scsi' index='0' model='virtio-scsi'>
<alias name='scsi0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</controller>

การยกเลิกดังกล่าวจากระบบปฏิบัติการเกสต์ได้รับการประมวลผลอย่างถูกต้องโดย LVM และบล็อกจะถูกปล่อยอย่างถูกต้องทั้งในแคชและในพูลแบบบาง ในกรณีของเรา สิ่งนี้จะเกิดขึ้นในลักษณะล่าช้าเป็นหลักเมื่อลบสแนปชอตถัดไป

การสำรองข้อมูล BTRFS

ใช้สคริปต์สำเร็จรูปกับ สุดขีด ข้อควรระวังและ อยู่ในความเสี่ยงของตัวเอง. ผู้เขียนเขียนโค้ดนี้เองและเพื่อตัวเขาเองโดยเฉพาะ ฉันแน่ใจว่าผู้ใช้ Linux ที่มีประสบการณ์หลายคนมีเครื่องมือที่คล้ายกัน และไม่จำเป็นต้องคัดลอกเครื่องมือของผู้อื่น

มาสร้างโวลุ่มบนอุปกรณ์สำรองข้อมูลกันเถอะ:

#lvcreate -L 256G --name backup backup

มาจัดรูปแบบเป็น BTRFS:

#mkfs.btrfs /dev/backup/backup

มาสร้างจุดเมานท์และเมานต์ส่วนย่อยรูทของระบบไฟล์:

#mkdir /backup
#mkdir /backup/btrfs
#mkdir /backup/btrfs/root
#mkdir /backup/btrfs/back
#ln -s /boot /backup/btrfs
# cat >>/etc/fstab << EOF

/dev/mapper/root-root /backup/btrfs/root btrfs defaults,space_cache,noatime,nodiratime 0 2
/dev/mapper/backup-backup /backup/btrfs/back btrfs defaults,space_cache,noatime,nodiratime 0 2
EOF
#mount -a
#update-initramfs -u
#update-grub

มาสร้างไดเร็กทอรีสำหรับการสำรองข้อมูล:

#mkdir /backup/btrfs/back/remote
#mkdir /backup/btrfs/back/remote/root
#mkdir /backup/btrfs/back/remote/boot

มาสร้างไดเร็กทอรีสำหรับสคริปต์สำรอง:

#mkdir /root/btrfs-backup

มาคัดลอกสคริปต์กัน:

รหัสทุบตีที่น่ากลัวมากมาย ใช้ความเสี่ยงของคุณเอง อย่าเขียนจดหมายโกรธถึงผู้เขียน...#cat >/root/btrfs-backup/btrfs-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".@base"
PEND_SUFFIX=".@pend"
SNAP_SUFFIX=".@snap"
MOUNTS="/backup/btrfs/"
BACKUPS="/backup/btrfs/back/remote/"

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function backup()
{
SOURCE_PATH="$MOUNTS$1"
TARGET_PATH="$BACKUPS$1"
SOURCE_BASE_PATH="$MOUNTS$1$BASE_SUFFIX"
TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
TARGET_BASE_DIR="$(dirname $TARGET_BASE_PATH)"
SOURCE_PEND_PATH="$MOUNTS$1$PEND_SUFFIX"
TARGET_PEND_PATH="$BACKUPS$1$PEND_SUFFIX"
if [ -d "$SOURCE_BASE_PATH" ] then
echo "$SOURCE_BASE_PATH found"
else
echo "$SOURCE_BASE_PATH File not found creating snapshot of $SOURCE_PATH to $SOURCE_BASE_PATH"
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_BASE_PATH
sync
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found out of sync with source... removing..."
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
fi
fi
if [ -d "$TARGET_BASE_PATH" ] then
echo "$TARGET_BASE_PATH found"
else
echo "$TARGET_BASE_PATH not found. Synching to $TARGET_BASE_DIR"
btrfs send $SOURCE_BASE_PATH | btrfs receive $TARGET_BASE_DIR
sync
fi
if [ -d "$SOURCE_PEND_PATH" ] then
echo "$SOURCE_PEND_PATH found removing..."
btrfs subvolume delete -c $SOURCE_PEND_PATH
sync
fi
btrfs subvolume snapshot -r $SOURCE_PATH $SOURCE_PEND_PATH
sync
if [ -d "$TARGET_PEND_PATH" ] then
echo "$TARGET_PEND_PATH found removing..."
btrfs subvolume delete -c $TARGET_PEND_PATH
sync
fi
echo "Sending $SOURCE_PEND_PATH to $TARGET_PEND_PATH"
btrfs send -p $SOURCE_BASE_PATH $SOURCE_PEND_PATH | btrfs receive $TARGET_BASE_DIR
sync
TARGET_DATE_SUFFIX=$(suffix)
btrfs subvolume snapshot -r $TARGET_PEND_PATH "$TARGET_PATH$TARGET_DATE_SUFFIX"
sync
btrfs subvolume delete -c $SOURCE_BASE_PATH
sync
btrfs subvolume delete -c $TARGET_BASE_PATH
sync
mv $SOURCE_PEND_PATH $SOURCE_BASE_PATH
mv $TARGET_PEND_PATH $TARGET_BASE_PATH
sync
}

function list()
{
LIST_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
LIST_TARGET_BASE_DIR="$(dirname $LIST_TARGET_BASE_PATH)"
LIST_TARGET_BASE_NAME="$(basename -s .$BASE_SUFFIX $LIST_TARGET_BASE_PATH)"
find "$LIST_TARGET_BASE_DIR" -maxdepth 1 -mindepth 1 -type d -printf "%fn" | grep "${LIST_TARGET_BASE_NAME/$BASE_SUFFIX/$SNAP_SUFFIX}.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_BASE_PATH="$BACKUPS$1$BASE_SUFFIX"
REMOVE_TARGET_BASE_DIR="$(dirname $REMOVE_TARGET_BASE_PATH)"
btrfs subvolume delete -c $REMOVE_TARGET_BASE_DIR/$2
sync
}

function removeall()
{
DATE_OFFSET="$2"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$1" "$SNAPSHOT"
done < <(list "$1" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1"
;;
"list")
list "$1"
;;
"remove")
wait_lock_or_terminate
remove "$1" "$2"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

มันทำอะไรได้บ้าง..?ประกอบด้วยชุดคำสั่งง่ายๆ สำหรับการสร้างสแน็ปช็อต BTRFS และคัดลอกไปยัง FS อื่นโดยใช้การส่ง/รับ BTRFS

การเปิดตัวครั้งแรกอาจค่อนข้างยาวนานเพราะ... ในตอนเริ่มต้นข้อมูลทั้งหมดจะถูกคัดลอก การเปิดตัวครั้งต่อไปจะเร็วมากเพราะ... ระบบจะคัดลอกเฉพาะการเปลี่ยนแปลงเท่านั้น

สคริปต์อื่นที่เราจะใส่ลงใน cron:

รหัสทุบตีเพิ่มเติมบางส่วน#cat >/root/btrfs-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/btrfs-backup.sh"
RETENTION="-60 day"
$BACKUP_SCRIPT backup root/@
$BACKUP_SCRIPT removeall root/@ "$RETENTION"
$BACKUP_SCRIPT backup root/@home
$BACKUP_SCRIPT removeall root/@home "$RETENTION"
$BACKUP_SCRIPT backup boot/
$BACKUP_SCRIPT removeall boot/ "$RETENTION"
EOF

มันทำอะไร..?สร้างและซิงโครไนซ์สแน็ปช็อตส่วนเพิ่มของวอลุ่ม BTRFS ที่แสดงรายการบน FS สำรอง หลังจากนี้ ระบบจะลบรูปภาพทั้งหมดที่สร้างขึ้นเมื่อ 60 วันที่ผ่านมา หลังจากเปิดตัว สแน็ปช็อตตามวันที่ของวอลุ่มที่ระบุไว้จะปรากฏในไดเร็กทอรีย่อย /backup/btrfs/back/remote/

ให้สิทธิ์ในการเรียกใช้โค้ด:

#chmod +x /root/btrfs-backup/cron-daily.sh
#chmod +x /root/btrfs-backup/btrfs-backup.sh

ลองตรวจสอบและใส่ไว้ใน cron:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup
#cat /var/log/syslog | grep btrfs-backup
#crontab -e
0 2 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/btrfs-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t btrfs-backup

การสำรองข้อมูลแบบบางของ LVM

มาสร้างพูลแบบบางบนอุปกรณ์สำรองข้อมูลกันเถอะ:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T backup/thin-pool

มาติดตั้ง ddrescue กันดีกว่า เพราะ... สคริปต์จะใช้เครื่องมือนี้:

#apt-get install gddrescue

มาสร้างไดเร็กทอรีสำหรับสคริปต์กันดีกว่า:

#mkdir /root/lvm-thin-backup

มาคัดลอกสคริปต์กัน:

ข้างในทุบตีมากมาย...#cat >/root/lvm-thin-backup/lvm-thin-backup.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

LOCK_FILE="/dev/shm/$SCRIPT_NAME.lock"
DATE_PREFIX='%Y-%m-%d'
DATE_FORMAT=$DATE_PREFIX'-%H-%M-%S'
DATE_REGEX='[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
BASE_SUFFIX=".base"
PEND_SUFFIX=".pend"
SNAP_SUFFIX=".snap"
BACKUPS="backup"
BACKUPS_POOL="thin-pool"

export LVM_SUPPRESS_FD_WARNINGS=1

function terminate ()
{
echo "$1" >&2
exit 1
}

function wait_lock()
{
flock 98
}

function wait_lock_or_terminate()
{
echo "Wating for lock..."
wait_lock || terminate "Failed to get lock. Exiting..."
echo "Got lock..."
}

function suffix()
{
FORMATTED_DATE=$(date +"$DATE_FORMAT")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function filter()
{
FORMATTED_DATE=$(date --date="$1" +"$DATE_PREFIX")
echo "$SNAP_SUFFIX.$FORMATTED_DATE"
}

function read_thin_id {
lvs --rows --reportformat basic --quiet -othin_id "$1/$2" | awk '{print $2}'
}

function read_pool_lv {
lvs --rows --reportformat basic --quiet -opool_lv "$1/$2" | awk '{print $2}'
}

function read_lv_dm_path {
lvs --rows --reportformat basic --quiet -olv_dm_path "$1/$2" | awk '{print $2}'
}

function read_lv_active {
lvs --rows --reportformat basic --quiet -olv_active "$1/$2" | awk '{print $2}'
}

function read_lv_chunk_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -ochunk_size "$1/$2" | awk '{print $2}'
}

function read_lv_size {
lvs --rows --reportformat basic --quiet --units b --nosuffix -olv_size "$1/$2" | awk '{print $2}'
}

function activate_volume {
lvchange -ay -Ky "$1/$2"
}

function deactivate_volume {
lvchange -an "$1/$2"
}

function read_thin_metadata_snap {
dmsetup status "$1" | awk '{print $7}'
}

function thindiff()
{
DIFF_VG="$1"
DIFF_SOURCE="$2"
DIFF_TARGET="$3"
DIFF_SOURCE_POOL=$(read_pool_lv $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_POOL=$(read_pool_lv $DIFF_VG $DIFF_TARGET)

if [ "$DIFF_SOURCE_POOL" == "" ] then
(>&2 echo "Source LV is not thin.")
exit 1
fi

if [ "$DIFF_TARGET_POOL" == "" ] then
(>&2 echo "Target LV is not thin.")
exit 1
fi

if [ "$DIFF_SOURCE_POOL" != "$DIFF_TARGET_POOL" ] then
(>&2 echo "Source and target LVs belong to different thin pools.")
exit 1
fi

DIFF_POOL_PATH=$(read_lv_dm_path $DIFF_VG $DIFF_SOURCE_POOL)
DIFF_SOURCE_ID=$(read_thin_id $DIFF_VG $DIFF_SOURCE)
DIFF_TARGET_ID=$(read_thin_id $DIFF_VG $DIFF_TARGET)
DIFF_POOL_PATH_TPOOL="$DIFF_POOL_PATH-tpool"
DIFF_POOL_PATH_TMETA="$DIFF_POOL_PATH"_tmeta
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" != "-" ] then
(>&2 echo "Thin pool metadata snapshot already exist. Assuming stale one. Will release metadata snapshot in 5 seconds.")
sleep 5
dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap
fi

dmsetup message $DIFF_POOL_PATH_TPOOL 0 reserve_metadata_snap
DIFF_POOL_METADATA_SNAP=$(read_thin_metadata_snap $DIFF_POOL_PATH_TPOOL)

if [ "$DIFF_POOL_METADATA_SNAP" == "-" ] then
(>&2 echo "Failed to create thin pool metadata snapshot.")
exit 1
fi

#We keep output in variable because metadata snapshot need to be released early.
DIFF_DATA=$(thin_delta -m$DIFF_POOL_METADATA_SNAP --snap1 $DIFF_SOURCE_ID --snap2 $DIFF_TARGET_ID $DIFF_POOL_PATH_TMETA)

dmsetup message $DIFF_POOL_PATH_TPOOL 0 release_metadata_snap

echo $"$DIFF_DATA" | grep -E 'different|left_only|right_only' | sed 's/</"/g' | sed 's/ /"/g' | awk -F'"' '{print $6 "t" $8 "t" $11}' | sed 's/different/copy/g' | sed 's/left_only/copy/g' | sed 's/right_only/discard/g'

}

function thinsync()
{
SYNC_VG="$1"
SYNC_PEND="$2"
SYNC_BASE="$3"
SYNC_TARGET="$4"
SYNC_PEND_POOL=$(read_pool_lv $SYNC_VG $SYNC_PEND)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SYNC_VG $SYNC_PEND_POOL)
SYNC_PEND_PATH=$(read_lv_dm_path $SYNC_VG $SYNC_PEND)

activate_volume $SYNC_VG $SYNC_PEND

while read -r SYNC_ACTION SYNC_OFFSET SYNC_LENGTH ; do
SYNC_OFFSET_BYTES=$((SYNC_OFFSET * SYNC_BLOCK_SIZE))
SYNC_LENGTH_BYTES=$((SYNC_LENGTH * SYNC_BLOCK_SIZE))
if [ "$SYNC_ACTION" == "copy" ] then
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SYNC_PEND_PATH" "$SYNC_TARGET"
fi

if [ "$SYNC_ACTION" == "discard" ] then
blkdiscard -o $SYNC_OFFSET_BYTES -l $SYNC_LENGTH_BYTES "$SYNC_TARGET"
fi
done < <(thindiff "$SYNC_VG" "$SYNC_PEND" "$SYNC_BASE")
}

function discard_volume()
{
DISCARD_VG="$1"
DISCARD_LV="$2"
DISCARD_LV_PATH=$(read_lv_dm_path "$DISCARD_VG" "$DISCARD_LV")
if [ "$DISCARD_LV_PATH" != "" ] then
echo "$DISCARD_LV_PATH found"
else
echo "$DISCARD_LV not found in $DISCARD_VG"
exit 1
fi
DISCARD_LV_POOL=$(read_pool_lv $DISCARD_VG $DISCARD_LV)
DISCARD_LV_SIZE=$(read_lv_size "$DISCARD_VG" "$DISCARD_LV")
lvremove -y --quiet "$DISCARD_LV_PATH" || exit 1
lvcreate --thin-pool "$DISCARD_LV_POOL" -V "$DISCARD_LV_SIZE"B --name "$DISCARD_LV" "$DISCARD_VG" || exit 1
}

function backup()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
SOURCE_PEND_LV="$SOURCE_LV$PEND_SUFFIX"
TARGET_PEND_LV="$TARGET_LV$PEND_SUFFIX"
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "Source base not found creating snapshot of $SOURCE_VG/$SOURCE_LV to $SOURCE_VG/$SOURCE_BASE_LV"
lvcreate --quiet --snapshot --name "$SOURCE_BASE_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo "Discarding $SOURCE_BASE_LV_PATH as we need to bootstrap."
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
sync
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found out of sync with source... removing..."
lvremove -y --quiet $TARGET_BASE_LV_PATH || exit 1
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
sync
fi
fi
SOURCE_BASE_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_BASE_LV")
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_VG/$TARGET_LV not found. Creating empty volume."
lvcreate --thin-pool "$BACKUPS_POOL" -V "$SOURCE_BASE_SIZE"B --name "$TARGET_BASE_LV" "$TARGET_VG" || exit 1
echo "Have to rebootstrap. Discarding source at $SOURCE_BASE_LV_PATH"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SOURCE_BASE_CHUNK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)
discard_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
TARGET_BASE_POOL=$(read_pool_lv $TARGET_VG $TARGET_BASE_LV)
TARGET_BASE_CHUNK_SIZE=$(read_lv_chunk_size $TARGET_VG $TARGET_BASE_POOL)
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
echo "Discarding target at $TARGET_BASE_LV_PATH"
discard_volume "$TARGET_VG" "$TARGET_BASE_LV"
sync
fi
if [ "$SOURCE_PEND_LV_PATH" != "" ] then
echo "$SOURCE_PEND_LV_PATH found removing..."
lvremove -y --quiet "$SOURCE_PEND_LV_PATH" || exit 1
sync
fi
lvcreate --quiet --snapshot --name "$SOURCE_PEND_LV" "$SOURCE_VG/$SOURCE_LV" || exit 1
SOURCE_PEND_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_PEND_LV")
sync
if [ "$TARGET_PEND_LV_PATH" != "" ] then
echo "$TARGET_PEND_LV_PATH found removing..."
lvremove -y --quiet $TARGET_PEND_LV_PATH
sync
fi
lvcreate --quiet --snapshot --name "$TARGET_PEND_LV" "$TARGET_VG/$TARGET_BASE_LV" || exit 1
TARGET_PEND_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_PEND_LV")
SOURCE_PEND_LV_SIZE=$(read_lv_size "$SOURCE_VG" "$SOURCE_PEND_LV")
lvresize -L "$SOURCE_PEND_LV_SIZE"B "$TARGET_PEND_LV_PATH"
activate_volume "$TARGET_VG" "$TARGET_PEND_LV"
echo "Synching $SOURCE_PEND_LV_PATH to $TARGET_PEND_LV_PATH"
thinsync "$SOURCE_VG" "$SOURCE_PEND_LV" "$SOURCE_BASE_LV" "$TARGET_PEND_LV_PATH" || exit 1
sync

TARGET_DATE_SUFFIX=$(suffix)
lvcreate --quiet --snapshot --name "$TARGET_LV$TARGET_DATE_SUFFIX" "$TARGET_VG/$TARGET_PEND_LV" || exit 1
sync
lvremove --quiet -y "$SOURCE_BASE_LV_PATH" || exit 1
sync
lvremove --quiet -y "$TARGET_BASE_LV_PATH" || exit 1
sync
lvrename -y "$SOURCE_VG/$SOURCE_PEND_LV" "$SOURCE_BASE_LV" || exit 1
lvrename -y "$TARGET_VG/$TARGET_PEND_LV" "$TARGET_BASE_LV" || exit 1
sync
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function verify()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
echo Comparing "$SOURCE_BASE_LV_PATH" with "$TARGET_BASE_LV_PATH"
cmp "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function resync()
{
SOURCE_VG="$1"
SOURCE_LV="$2"
TARGET_VG="$BACKUPS"
TARGET_LV="$SOURCE_VG-$SOURCE_LV"
SOURCE_BASE_LV="$SOURCE_LV$BASE_SUFFIX"
TARGET_BASE_LV="$TARGET_LV$BASE_SUFFIX"
TARGET_BASE_LV_PATH=$(read_lv_dm_path "$TARGET_VG" "$TARGET_BASE_LV")
SOURCE_BASE_LV_PATH=$(read_lv_dm_path "$SOURCE_VG" "$SOURCE_BASE_LV")

if [ "$SOURCE_BASE_LV_PATH" != "" ] then
echo "$SOURCE_BASE_LV_PATH found"
else
echo "$SOURCE_BASE_LV_PATH not found"
exit 1
fi
if [ "$TARGET_BASE_LV_PATH" != "" ] then
echo "$TARGET_BASE_LV_PATH found"
else
echo "$TARGET_BASE_LV_PATH not found"
exit 1
fi
activate_volume "$TARGET_VG" "$TARGET_BASE_LV"
activate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
SOURCE_BASE_POOL=$(read_pool_lv $SOURCE_VG $SOURCE_BASE_LV)
SYNC_BLOCK_SIZE=$(read_lv_chunk_size $SOURCE_VG $SOURCE_BASE_POOL)

echo Syncronizing "$SOURCE_BASE_LV_PATH" to "$TARGET_BASE_LV_PATH"

CMP_OFFSET=0
while [[ "$CMP_OFFSET" != "" ]] ; do
CMP_MISMATCH=$(cmp -i "$CMP_OFFSET" "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH" | grep differ | awk '{print $5}' | sed 's/,//g' )
if [[ "$CMP_MISMATCH" != "" ]] ; then
CMP_OFFSET=$(( CMP_MISMATCH + CMP_OFFSET ))
SYNC_OFFSET_BYTES=$(( ( CMP_OFFSET / SYNC_BLOCK_SIZE ) * SYNC_BLOCK_SIZE ))
SYNC_LENGTH_BYTES=$(( SYNC_BLOCK_SIZE ))
echo "Synching $SYNC_LENGTH_BYTES bytes at $SYNC_OFFSET_BYTES from $SOURCE_BASE_LV_PATH to $TARGET_BASE_LV_PATH"
ddrescue --quiet --force --input-position=$SYNC_OFFSET_BYTES --output-position=$SYNC_OFFSET_BYTES --size=$SYNC_LENGTH_BYTES "$SOURCE_BASE_LV_PATH" "$TARGET_BASE_LV_PATH"
else
CMP_OFFSET=""
fi
done
echo Done...
deactivate_volume "$TARGET_VG" "$TARGET_BASE_LV"
deactivate_volume "$SOURCE_VG" "$SOURCE_BASE_LV"
}

function list()
{
LIST_SOURCE_VG="$1"
LIST_SOURCE_LV="$2"
LIST_TARGET_VG="$BACKUPS"
LIST_TARGET_LV="$LIST_SOURCE_VG-$LIST_SOURCE_LV"
LIST_TARGET_BASE_LV="$LIST_TARGET_LV$SNAP_SUFFIX"
lvs -olv_name | grep "$LIST_TARGET_BASE_LV.$DATE_REGEX"
}

function remove()
{
REMOVE_TARGET_VG="$BACKUPS"
REMOVE_TARGET_LV="$1"
lvremove -y "$REMOVE_TARGET_VG/$REMOVE_TARGET_LV"
sync
}

function removeall()
{
DATE_OFFSET="$3"
FILTER="$(filter "$DATE_OFFSET")"
while read -r SNAPSHOT ; do
remove "$SNAPSHOT"
done < <(list "$1" "$2" | grep "$FILTER")

}

(
COMMAND="$1"
shift

case "$COMMAND" in
"--help")
echo "Help"
;;
"suffix")
suffix
;;
"filter")
filter "$1"
;;
"backup")
wait_lock_or_terminate
backup "$1" "$2"
;;
"list")
list "$1" "$2"
;;
"thindiff")
thindiff "$1" "$2" "$3"
;;
"thinsync")
thinsync "$1" "$2" "$3" "$4"
;;
"verify")
wait_lock_or_terminate
verify "$1" "$2"
;;
"resync")
wait_lock_or_terminate
resync "$1" "$2"
;;
"remove")
wait_lock_or_terminate
remove "$1"
;;
"removeall")
wait_lock_or_terminate
removeall "$1" "$2" "$3"
;;
*)
echo "None.."
;;
esac
) 98>$LOCK_FILE

EOF

มันทำอะไร...?ประกอบด้วยชุดคำสั่งสำหรับจัดการ Thin Snapshots และซิงโครไนซ์ความแตกต่างระหว่าง Thin Snapshot สองอันที่ได้รับผ่าน Thin_delta ไปยังอุปกรณ์บล็อกอื่นโดยใช้ ddrescue และ blkdiscard

สคริปต์อื่นที่เราจะใส่ใน cron:

ทุบตีอีกสักหน่อย#cat >/root/lvm-thin-backup/cron-daily.sh << EOF
#!/bin/bash
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

SCRIPT_FILE="$(realpath $0)"
SCRIPT_DIR="$(dirname $SCRIPT_FILE)"
SCRIPT_NAME="$(basename -s .sh $SCRIPT_FILE)"

BACKUP_SCRIPT="$SCRIPT_DIR/lvm-thin-backup.sh"
RETENTION="-60 days"

$BACKUP_SCRIPT backup images linux-dev
$BACKUP_SCRIPT backup images win8
$BACKUP_SCRIPT backup images win8-data
#etc

$BACKUP_SCRIPT removeall images linux-dev "$RETENTION"
$BACKUP_SCRIPT removeall images win8 "$RETENTION"
$BACKUP_SCRIPT removeall images win8-data "$RETENTION"
#etc

EOF

มันทำอะไร...?ใช้สคริปต์ก่อนหน้าเพื่อสร้างและซิงโครไนซ์การสำรองข้อมูลของ Thin Volume ที่ระบุไว้ สคริปต์จะทิ้งสแน็ปช็อตที่ไม่ได้ใช้งานของไดรฟ์ข้อมูลที่ระบุไว้ ซึ่งจำเป็นสำหรับการติดตามการเปลี่ยนแปลงนับตั้งแต่การซิงโครไนซ์ครั้งล่าสุด

ต้องแก้ไขสคริปต์นี้ โดยระบุรายการ Thin Volume ที่ควรทำสำเนาสำรอง ชื่อที่ให้ไว้มีวัตถุประสงค์เพื่อเป็นตัวอย่างเท่านั้น หากต้องการคุณสามารถเขียนสคริปต์ที่จะซิงโครไนซ์ทุกเล่ม

เรามาให้สิทธิ์กัน:

#chmod +x /root/lvm-thin-backup/cron-daily.sh
#chmod +x /root/lvm-thin-backup/lvm-thin-backup.sh

ลองตรวจสอบและใส่ไว้ใน cron:

#/usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup
#cat /var/log/syslog | grep lvm-thin-backup
#crontab -e
0 3 * * * /usr/bin/nice -n 19 /usr/bin/ionice -c 3 /root/lvm-thin-backup/cron-daily.sh 2>&1 | /usr/bin/logger -t lvm-thin-backup

การเปิดตัวครั้งแรกจะยาวนานเพราะ... ไดรฟ์ข้อมูลแบบบางจะถูกซิงโครไนซ์อย่างสมบูรณ์โดยการคัดลอกพื้นที่ที่ใช้ทั้งหมด ขอบคุณเมตาดาต้าแบบ Thin ของ LVM เราจึงทราบว่าบล็อกใดที่มีการใช้งานจริง ดังนั้นเฉพาะบล็อก Thin Volume ที่ใช้จริงเท่านั้นที่จะถูกคัดลอก

การรันครั้งต่อๆ ไปจะคัดลอกข้อมูลทีละน้อยด้วยการติดตามการเปลี่ยนแปลงผ่านเมตาดาต้าแบบบางของ LVM

มาดูกันว่าเกิดอะไรขึ้น:

#time /root/btrfs-backup/cron-daily.sh
real 0m2,967s
user 0m0,225s
sys 0m0,353s

#time /root/lvm-thin-backup/cron-daily.sh
real 1m2,710s
user 0m12,721s
sys 0m6,671s

#ls -al /backup/btrfs/back/remote/*
/backup/btrfs/back/remote/boot:
total 0
drwxr-xr-x 1 root root 1260 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 322 мар 26 02:00 .@base
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
drwxr-xr-x 1 root root 516 мар 6 09:39 [email protected]
...
/backup/btrfs/back/remote/root:
total 0
drwxr-xr-x 1 root root 2820 мар 26 09:11 .
drwxr-xr-x 1 root root 16 мар 6 09:30 ..
drwxr-xr-x 1 root root 240 мар 26 09:11 @.@base
drwxr-xr-x 1 root root 22 мар 26 09:11 @home.@base
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 22 мар 6 09:39 @[email protected]
...
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
drwxr-xr-x 1 root root 240 мар 6 09:39 @[email protected]
...

#lvs -olv_name,lv_size images && lvs -olv_name,lv_size backup
LV LSize
linux-dev 128,00g
linux-dev.base 128,00g
thin-pool 1,38t
win8 128,00g
win8-data 2,00t
win8-data.base 2,00t
win8.base 128,00g
LV LSize
backup 256,00g
images-linux-dev.base 128,00g
images-linux-dev.snap.2020-03-08-10-09-11 128,00g
images-linux-dev.snap.2020-03-08-10-09-25 128,00g
...
images-win8-data.base 2,00t
images-win8-data.snap.2020-03-16-14-11-55 2,00t
images-win8-data.snap.2020-03-16-14-19-50 2,00t
...
images-win8.base 128,00g
images-win8.snap.2020-03-17-04-51-46 128,00g
images-win8.snap.2020-03-18-03-02-49 128,00g
...
thin-pool <2,09t

เกี่ยวอะไรกับตุ๊กตาทำรัง?

เป็นไปได้มากว่าโลจิคัลวอลุ่ม LVM LV สามารถเป็นฟิสิคัลวอลุ่ม LVM PV สำหรับ VG อื่นๆ ได้ LVM สามารถเรียกซ้ำได้ เช่น ตุ๊กตาทำรัง สิ่งนี้ทำให้ LVM มีความยืดหยุ่นอย่างมาก

PS

ในบทความถัดไป เราจะพยายามใช้ระบบจัดเก็บข้อมูลแบบเคลื่อนที่/KVM ที่คล้ายกันหลายระบบเป็นพื้นฐานสำหรับการสร้างคลัสเตอร์พื้นที่เก็บข้อมูล/vm แบบกระจายทางภูมิศาสตร์ที่มีความซ้ำซ้อนในหลายทวีปโดยใช้เดสก์ท็อปที่บ้าน อินเทอร์เน็ตภายในบ้าน และเครือข่าย P2P

ที่มา: will.com

เพิ่มความคิดเห็น