Якщо zfs send — це відділ експорту, то zfs receive — це митниця: усе виглядає гаразд, доки хтось не перевірить документи. Властивості — це ті документи. Ігноруєте їх — і отримаєте не просто дещо неохайний імпорт, а набори даних, змонтовані не там, де потрібно, ключі шифрування, які не можна завантажити, «успішні» реплікації, що не завантажуються під час завантаження, і продуктивність, яка нагадує інтерпретаційний танець системи зберігання.
Я запускав реплікацію ZFS у нудних корпоративних середовищах, в панічних SaaS-командах і в таких корпоративних підвалах, де «план DR» означає «надіятися». Схема повторюється: команди вважають receive тупим каналом. Такого не буває. zfs receive — це місце, де ZFS вирішує, що ваші дані означають на цільовому боці: де вони змонтуються, як стиснуті, чи будуть взагалі читабельними й що станеться, якщо стрім містить властивості, на які ви не розраховували.
Що насправді робить zfs receive (і чому властивості — пастка)
zfs receive приймає стрім реплікації та відтворює набори даних, снапшоти й за бажанням властивості. Останнє — тихий вбивця: властивості — це не просто корисні настроювання на кшталт «compression=lz4». Властивості включають точки монтування, поведінку canmount, метадані шифрування, квоти, резервації, recordsize, режим ACL, зберігання xattr та купу платформно-специфічних речей, які на цільовій ОС можуть значити зовсім інше.
Неправильне припущення — що стріми реплікації — це «тільки дані». Насправді типовий ZFS-стрім може включати:
- Блоки вмісту набору даних.
- Метадані снапшота.
- Властивості набору даних (залежно від прапорів send і реалізації ZFS).
- Фіч-флаги та очікування сумісності.
Потім zfs receive застосовує стрім у простір імен пулу, де локальні політики цілі (і наявні набори даних) можуть не погоджуватися з джерелом.
У практиці «не погодитися» не означає дружнє попередження. Це означає:
- Набір даних монтується поверх реальної теки й ховає потрібні вам дані.
- Властивість, успадкована на джерелі, стає локальною на цілі (або навпаки), і ви витрачаєте години на пошук причин розбіжності поведінки.
- Зашифрований набір даних прибуває без очікуваного ключового матеріалу.
- Реплікація «працює», але відновлення зламане, бо ви реплікували точку монтування, яка має сенс лише на хості джерела.
Жарт №1: ставитися до zfs receive як до «просто сторони імпорту» — як ставитися до подушок безпеки як до «тільки надувної частини». Ви зрозумієте, що помилялися, лише на швидкості.
Факти та історичний контекст, які справді важливі в продакшні
- ZFS було спроектовано з реплікацією як первинною примітивою. Снапшоти і send/receive були частиною початкового бачення ери Sun: послідовна передача у точці часу без потреби «заморожувати» файлову систему.
- Властивості — частина контракту файлової системи. ZFS використовує властивості для керування поведінкою, яку інші файлові системи виносять у опції монтування або зовнішні інструменти.
- OpenZFS роками розходився між платформами. Поведінка щодо ACL, xattr і деяких значень за замовчуванням може відрізнятися між illumos, FreeBSD і Linux — особливо в старих розгортаннях.
- Фіч-флаги змінили ризик оновлення. Пули й набори даних отримали фіч-флаги, що дозволяють покрокове оновлення й безпечнішу активацію, але також створюють сюрпризи «не можна імпортувати на старішу систему», якщо ви сліпо реплікуєте.
- Шифрування з’явилося пізніше за снапшоти. Нативне шифрування — це сучасний OpenZFS; багато середовищ ще змішують «традиційне шифрування на рівні диска» (SSED, LUKS) із нативним ZFS-шифруванням, що впливає на припущення щодо реплікації.
lz4став дефолтним компресором не просто так. Він досить швидкий, щоб бути «звичайно ввімкненим» без операційного сорому від марної витрати CPU при відновленні.- Токени відновлення існують, бо довгі передачі фейляться. Реплікація ZFS у масштабі стикається з нестабільними мережами, вікнами технічного обслуговування і нетерпінням людей; токени resume перетворили «почати спочатку» на «продовжити».
mountpoint— це властивість, а не опція mount(8). Такий підхід потужний, але означає, що реплікація може переносити вашу розкладку монтувань — хочете ви того чи ні.- Recordsize і volblocksize — це важелі продуктивності з наслідками. Вони можуть зробити бази даних швидкими або знищити робочі навантаження з маленькими файлами, і реплікація може ненавмисно перенести ці налаштування між середовищами.
Практична модель: стрім, набір даних, властивості і монтування
Коли ви виконуєте:
cr0x@server:~$ zfs send poolA/app@weekly | zfs receive poolB/restore/app
ви не просто «копіюєте файли». Ви застосовуєте журнал транзакцій, який відтворює дерево набору даних. Це має три рівні наслідків:
1) Простір імен: куди потрапить набір даних?
poolB/restore/app — це ім’я набору даних, у яке ви приймаєте. Але всередині стріму набори даних можуть мати свої імена й властивості, і якщо ви використовуєте прапори реплікації, що включають дочірні набори, receive може створити піддерево під вашою цільовою адресою.
2) Властивості: яка поведінка приїжджає з собою?
Властивості можуть бути встановлені як локальні значення або успадковані. Реплікація може зберегти ці вибори. Це добре, коли ви хочете віртуальне дзеркало, і жахливо, коли ви приймаєте дані в інше експлуатаційне середовище (DR, dev, analytics) з іншими точками монтування, квотами та налаштуваннями продуктивності.
3) Поведінка монтування: коли і де воно змонтується?
За замовчуванням отримана файлова система може автоматично змонтуватися (залежно від canmount, mountpoint та прапорів receive). Це може конфліктувати з існуючими шляхами та сервісами. «Чому мій додаток раптом читає старий конфіг?» — поширена тема розбору подій.
Поведінка властивостей під час receive: наслідувані, локальні та «сюрприз, змінилося»
Ключ до збереження sanity — розділити два питання:
- Які властивості є в стрімі? (вирішується на стороні send і залежить від того, чи робите ви raw send, replication send і т. д.)
- Які властивості застосовуються на цілі? (вирішується опціями receive і наявним деревом наборів даних на цілі)
Властивості, що зазвичай спричиняють реальні відмови
- mountpoint: реплікує шлях, який може не існувати або бути зайнятим на цілі.
- canmount: набір даних, який не повинен монтуватися, монтується (або навпаки), змінюючи те, що бачать сервіси.
- sharenfs / sharesmb: раптово ви експортуєте дані в мережі, яку не мали торкатися.
- atime: змінює метадані активності; може перетворити навантаження з читання в несподівані записи.
- recordsize: безпечний у багатьох випадках, але руйнівний для БД і образів VM, коли встановлений неправильно.
- xattr і acltype: міжплатформні відмінності можуть зробити дозволи «виглядати нормально», поки програма не почне падати.
- quota/reservation/refquota/refreservation: репліковані обмеження можуть заблокувати відновлення, миттєво «заповнивши» набір даних з точки зору цілі.
Перезаписання vs виключення властивостей під час receive
В операційній практиці зазвичай потрібен один з таких підходів:
- Вірне дзеркало (DR-дзеркало, яке ви можете підвищити): зберегти властивості, але переконатися, що стратегія простору імен/монтування безпечна (часто receive з
-u, а потім явна установка mountpoint). - Зона прийому даних (analytics/dev/test): переоприділити mountpoint, вимкнути шеринги й застосувати локальну політику продуктивності (compression, recordsize), що підходить для цілі.
Якщо ваш ZFS підтримує прапори виключення/перезапису властивостей під час receive (поширені в сучасному OpenZFS), вони — ваші запобіжні рейки. Конкретні прапори залежать від реалізації/версії, тому перевіряйте у своєму середовищі за допомогою zfs receive -?. У цій статті я покажу шаблони, що працюють широко, і вкажу, де версії мають значення.
Шифрування під час receive: ключі, raw sends і жанр «чому я не можу змонтувати це»
Нативне шифрування ZFS вводить поняття, яке адміністратори без шифрування часто пропускають: набір даних може існувати і реплікуватися ідеально, але бути непридатним до використання, поки ключі не оброблені правильно. Це не баг; це сенс.
Два режими реплікації з дуже різною поведінкою
Non-raw send (або sends, що фактично розшифровують/перешифровують залежно від інструментів) можуть дозволити приймачу зберігати дані не як байт-в-байт зашифровану копію. Це може вимагати ключів на джерелі й може витікати властивості, яких ви не очікували.
Raw send (zfs send -w у багатьох збірках OpenZFS) передає зашифрований набір даних як зашифровані блоки плюс достатньо метаданих для збереження шифрування. Приймач не потребує plaintext, і корінь шифрування/параметри зберігаються. Це режим, який ви хочете для «реплікації на недовірений DR-сайт» або «бекуп-апарату».
Обробка ключів на цілі — операційний робочий процес
Отримання зашифрованого набору даних без плану щодо keylocation та keyformat — спосіб створити найнадійніший у світі прес-пап’є. Ви запустите zfs list, побачите набори даних, а далі все впаде при монтуванні.
Жарт №2: зашифрований набір даних без ключа — найчистіша форма запису лише на запис. Аудитори це люблять; користувачі — ні.
Точки монтування та автомонтування: як люди DOS themselves with success
Найпоширеніший сценарій «receive зламав продукцію» — не корупція даних. Це колізія монтування. Виконано receive, набір даних змонтувався автоматично, і раптом:
/varна цілі опинився в тіні за рахунок реплікованої точки монтування.- Тека з живими даними прихована за точкою монтування, через що сервіси читають застарілі конфіги або не можуть знайти файли.
- Середовище відновлення несподівано шариться через NFS/SMB, бо властивості share прийшли разом.
Виправлення зазвичай просте — отримати без монтування, потім явно встановити mountpoint і canmount перед підняттям сервісів. Складно — пам’ятати про це, коли ви втомлені, 02:00, і менеджмент питає ETA, ніби це спорт.
Реальні операційні задачі (команди + інтерпретація)
Нижче практичні задачі, які я дійсно використовував (або бачив, як комусь хотілося б використовувати) в робочих потоках реплікації ZFS. Команди припускають типове середовище OpenZFS; підлаштуйте імена пулів/наборів даних під свою реальність.
Задача 1: Перевірте, які властивості важливі на джерелі перед відправленням
cr0x@src:~$ zfs get -r -o name,property,value,source mountpoint,canmount,compression,recordsize,quota,refquota,sharenfs,sharesmb poolA/app
NAME PROPERTY VALUE SOURCE
poolA/app mountpoint /srv/app local
poolA/app canmount on default
poolA/app compression lz4 local
poolA/app recordsize 128K default
poolA/app quota none default
poolA/app sharenfs off default
Інтерпретація: Це показує, яку поведінку ви збираєтеся реплікувати. Якщо mountpoint локальний і вказує на шлях, якого немає на цілі, вирішіть зараз, чи потрібно його перезаписати.
Задача 2: Прогон плану receive, перевіривши простір імен і конфлікти
cr0x@dst:~$ zfs list -o name,mountpoint,canmount -r poolB/restore
NAME MOUNTPOINT CANMOUNT
poolB/restore /poolB/restore on
poolB/restore/app - -
Інтерпретація: Якщо poolB/restore/app вже існує (або якщо батьківський набір має успадкований mountpoint, що спричиняє колізію), виправте простір імен до receive.
Задача 3: Отримати без монтування, щоб уникнути колізій
cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore/app'
Інтерпретація: -u тримає його незмонтованим навіть якщо стрім містить налаштування mountpoint/canmount, які зазвичай змонтували б його. Це найбезпечніший дефолт для відновлень і первинного заповнення DR.
Задача 4: Примусово встановити безпечний mountpoint після receive (шаблон landing zone)
cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app
cr0x@dst:~$ zfs mount poolB/restore/app
Інтерпретація: Ви зробили монтування явним і передбачуваним. noauto запобігає несподіваним монтуванням під час завантаження/імпорту; ви контролюєте, коли сервіси бачать дані.
Задача 5: Отримати повне піддерево (реплікація) в префіксовану адресу
cr0x@src:~$ zfs send -R poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore'
Інтерпретація: Це може створити кілька наборів даних під poolB/restore. Чудово для повних дерев застосунків; небезпечно, якщо ви не очікували дочірніх наборів зі своїми mountpoint і властивостями шерингу.
Задача 6: Перевірити отримані снапшоти та використання простору
cr0x@dst:~$ zfs list -t snapshot -o name,used,refer,mountpoint -r poolB/restore/app | head
NAME USED REFER MOUNTPOINT
poolB/restore/app@weekly 0B 220G /srv/restore/app
poolB/restore/app@daily-2025-12 0B 218G /srv/restore/app
Інтерпретація: Снапшоти присутні, розміри refer виглядають адекватно. Якщо USED раптово великий, можливо, ви отримали зайві снапшоти або невідповідні інкрементали пізніше.
Задача 7: Інкрементальний send/receive з чіткою базовою точкою
cr0x@src:~$ zfs send -I poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -u poolB/restore/app'
Інтерпретація: -I надсилає всі проміжні снапшоти між weekly і daily. Якщо ціль не має базового снапшота, це зазнає невдачі. Така невдача — корисна: вона запобігає мовчанковій дивергенції.
Задача 8: Обробити набір даних, що вже існує на цілі (force receive)
cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -F -u poolB/restore/app'
Інтерпретація: -F відкатить цільовий набір даних до останнього снапшота, що збігається зі стрімом, і відкине розбіжні зміни. Це потужно й небезпечно: якщо хтось записав нові дані на ціль, вони зникають.
Задача 9: Використовувати resume tokens, коли довгий receive переривається
cr0x@dst:~$ zfs get -H -o value receive_resume_token poolB/restore/app
1-2f4c9e1b7a-8000000000-1a2b3c4d5e6f...
cr0x@dst:~$ zfs send -t 1-2f4c9e1b7a-8000000000-1a2b3c4d5e6f... | zfs receive -u poolB/restore/app
Інтерпретація: Якщо ваше середовище підтримує resume tokens, ви можете продовжити без пересилання з нуля. Якщо токен - або порожній, resume недоступний або неактивний.
Задача 10: Підтвердити стан шифрування і вимоги щодо ключів після receive
cr0x@dst:~$ zfs get -o name,property,value encryption,keylocation,keystatus -r poolB/restore/app
NAME PROPERTY VALUE SOURCE
poolB/restore/app encryption aes-256-gcm -
poolB/restore/app keylocation file:///... local
poolB/restore/app keystatus unavailable -
Інтерпретація: Набір даних існує, але ключі не завантажені. Ви не можете його змонтувати, поки не завантажите ключі. Якщо keylocation невідповідний для цілі, виправте його перед спробою монтування.
Задача 11: Завантажити ключі і змонтувати безпечно (шифровані набори даних)
cr0x@dst:~$ zfs set keylocation=prompt poolB/restore/app
cr0x@dst:~$ zfs load-key poolB/restore/app
Enter passphrase for 'poolB/restore/app':
cr0x@dst:~$ zfs mount poolB/restore/app
Інтерпретація: Перехід на prompt — поширений операційний вибір для відновлень DR. Для безлюдного завантаження ви можете використовувати файл як keylocation, але це окрема розмова про безпеку, яку варто провести свідомо.
Задача 12: Виміряти, чи receive обмежений CPU, диском або мережею
cr0x@dst:~$ zpool iostat -v 1
capacity operations bandwidth
poolB alloc free read write read write
---------------------------- ----- ----- ----- ----- ----- -----
poolB 8.2T 10.1T 0 1200 0 450M
raidz2-0 8.2T 10.1T 0 1200 0 450M
sda - - 0 150 0 58M
sdb - - 0 148 0 57M
Інтерпретація: Якщо запис диску високий і стабільний, але receive повільний, можливо, ви обмежені CPU на стисненні/перевірці контрольних сум, або обмежені налаштуваннями sync. Якщо записи низькі, дивіться на мережу/SSH або на сторону джерела.
Задача 13: Виявляти колізії mountpoint до того, як вони вдарять
cr0x@dst:~$ zfs get -r -o name,property,value,source mountpoint,canmount poolB/restore | grep -E '(/var|/usr|/home|/srv)'
poolB/restore/app mountpoint /srv/app local
Інтерпретація: Ця точка монтування — проблема, якщо /srv/app — місце реального продакшн-додатка на цілі. Виправте її до монтування.
Задача 14: Перевірити, що змінилося під час receive, порівнявши джерела властивостей
cr0x@dst:~$ zfs get -o name,property,value,source -s local,inherited compression,recordsize,atime,acltype,xattr poolB/restore/app
NAME PROPERTY VALUE SOURCE
poolB/restore/app compression lz4 local
poolB/restore/app atime off inherited
poolB/restore/app xattr sa local
Інтерпретація: Так ви помічаєте «на DR інакше» проблеми без гадань. Якщо властивість успадкована від батька на цілі, можливо, потрібно встановити її локально, щоб відповідати очікуванням.
Швидкий план діагностики (що перевіряти спочатку, далі, потім)
Ось контрольний список, який я використовую, коли реплікація повільна, зламана або «успішна, але неправильна». Мета — ідентифікувати проблемний шар за хвилини, не години.
Спочатку: чи неправильний стан набору даних (властивості/монтування/шифрування)?
cr0x@dst:~$ zfs list -o name,type,mountpoint,canmount -r poolB/restore/app
cr0x@dst:~$ zfs get -o name,property,value,source mountpoint,canmount,readonly,sharenfs,sharesmb poolB/restore/app
cr0x@dst:~$ zfs get -o name,property,value encryption,keystatus,keylocation poolB/restore/app
Інтерпретація: Якщо не монтується — шифрування і mountpoint — головні підозрювані. Якщо змонтувалося деінде — mountpoint/canmount і успадковані властивості зазвичай винні.
По-друге: чи неправильні відносини стріму (базовий інкремент відсутній, потрібен відкат, resume token)?
cr0x@dst:~$ zfs list -t snapshot -o name -r poolB/restore/app | tail
cr0x@dst:~$ zfs get -H -o value receive_resume_token poolB/restore/app
Інтерпретація: Інкрементальні збої часто зводяться до «ціль не має базового снапшота» або «ціль розійшлася». Наявність resume token повідомляє, чи можна продовжити.
По-третє: де вузьке місце (диск, CPU, мережа, sync)?
cr0x@dst:~$ zpool iostat 1
cr0x@dst:~$ iostat -xz 1
cr0x@dst:~$ vmstat 1
Інтерпретація: Ви шукаєте насичений ресурс: диски навантажені, CPU навантажений (часто при перевірці/стисненні/дешифруванні) або трубопровід сповільнений через SSH/мережу. Якщо записи стоять з високою затримкою, перевірте стан пулу й відповідність recordsize/робочого навантаження.
Поширені помилки: конкретні симптоми та виправлення
Помилка 1: Receiving у шлях, який авто-монтується поверх реальних даних
Симптоми: Сервіси «втрачають» файли, конфіг зникає, теки виглядають порожніми або раптово з’являються старі дані. mount показує нову ZFS FS, змонтовану в критичному шляху.
Виправлення: Негайно відмонтуйте отриманий набір даних і встановіть безпечний mountpoint.
cr0x@dst:~$ zfs unmount poolB/restore/app
cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app
Помилка 2: Інкрементальний receive падає з «does not exist» або «most recent snapshot does not match»
Симптоми: Receive завершується помилкою; список снапшотів цілі не містить базового інкремента; або ціль має додаткові снапшоти, яких немає на джерелі.
Виправлення: Повторно ініціюйте з правильним базовим снапшотом, або примусово відкотіть, якщо ви навмисно хочете перезаписати розбіжності.
cr0x@src:~$ zfs send poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore/app'
cr0x@src:~$ zfs send -i poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -u poolB/restore/app'
Якщо є розбіжності і зміни на цілі можна відкинути:
cr0x@src:~$ zfs send -i poolA/app@weekly poolA/app@daily | ssh dst 'zfs receive -F -u poolB/restore/app'
Помилка 3: Квоти/резервації реплікуються в DR і блокують відновлення
Симптоми: Ви успішно отримали, але записи негайно падають з «out of space», незважаючи на вільний простір у пулі. zfs get quota показує обмежувальне значення.
Виправлення: Перезапишіть квоти/резервації на цілі після receive (або виключіть їх під час receive, якщо підтримується).
cr0x@dst:~$ zfs get -o name,property,value quota,refquota,reservation,refreservation poolB/restore/app
cr0x@dst:~$ zfs set quota=none refquota=none reservation=none refreservation=none poolB/restore/app
Помилка 4: Отримання зашифрованих наборів даних без плану keylocation/keystatus
Симптоми: Набір даних існує; монтування не вдається; keystatus=unavailable.
Виправлення: Встановіть правильний keylocation і завантажте ключі, потім змонтуйте.
cr0x@dst:~$ zfs set keylocation=prompt poolB/restore/app
cr0x@dst:~$ zfs load-key poolB/restore/app
cr0x@dst:~$ zfs mount poolB/restore/app
Помилка 5: «Оптимізація» шляхом зміни recordsize/compression на цілі під час потоку
Симптоми: Receive стає повільнішим; фрагментація зростає; продуктивність застосунку змінюється непередбачувано після промоції.
Виправлення: Розглядайте властивості продуктивності як частину системного дизайну. Визначте політику для ролі набору даних (БД проти логів проти образів VM) і застосовуйте її послідовно — бажано до першого запису або в контрольовані вікна перезапису.
cr0x@dst:~$ zfs set recordsize=16K poolB/restore/db
cr0x@dst:~$ zfs set compression=lz4 poolB/restore/db
Помилка 6: Ненавмисна реплікація властивостей шерингу в неправильну мережеву зону
Симптоми: Дані з’являються в мережі несподівано; команда комплаєнсу дзвонить; логи фаєрвола світяться.
Виправлення: Переконайтеся, що sharenfs/sharesmb вимкнені на цільових receive (або успадковані як off від батька).
cr0x@dst:~$ zfs set sharenfs=off sharesmb=off poolB/restore
cr0x@dst:~$ zfs inherit -r sharenfs poolB/restore/app
cr0x@dst:~$ zfs inherit -r sharesmb poolB/restore/app
Контрольні списки / покроковий план
План A: Безпечне відновлення в нове середовище (рекомендований дефолт)
- Створіть батьківський landing dataset з безпечними успадкованими властивостями (жодного шерінгу, жодних авто-монтажних сюрпризів).
- Отримуйте з
-u, щоб запобігти авто-монтуванню. - Перезапишіть mountpoints у префікс відновлення.
- Явно обробіть ключі шифрування (політика prompt або файл).
- Перевірте снапшоти і джерела властивостей перед тим, як відкривати дані сервісам.
cr0x@dst:~$ zfs create -o canmount=off -o mountpoint=/srv/restore poolB/restore
cr0x@dst:~$ zfs set sharenfs=off sharesmb=off poolB/restore
cr0x@src:~$ zfs send -R poolA/app@weekly | ssh dst 'zfs receive -u poolB/restore'
cr0x@dst:~$ zfs set mountpoint=/srv/restore/app poolB/restore/app
cr0x@dst:~$ zfs set canmount=noauto poolB/restore/app
План B: DR-дзеркало, яке ви можете підвищити (вірно, але контрольовано)
- Приймайте незмонтованим.
- Зберігайте властивості, якщо тільки у вас немає виняткової політики.
- Тримайте mountpoints «DR-безпечними», отримуючи під спеціальним коренем і змінюючи mountpoints тільки під час промоції.
- Тестуйте промоцію на репетиціях (монтування, завантаження ключів, запуск сервісів) з runbook, що передбачає людські помилки під стресом.
План C: Довготривала реплікація через ненадійні канали
- Віддавайте перевагу resumable receive, де це підтримується.
- Відстежуйте resume tokens і уникати вбивства receives, якщо тільки ви цього не хочете.
- Вимірюйте вузькі місця за допомогою
zpool iostatі метрик CPU перед «тюнінгом».
Три міні-історії з корпоративного світу
Міні-історія 1: Інцидент, спричинений хибним припущенням (mountpoint — «лише локально»)
Одна корпоративна інфраструктурна команда створила нове DR-середовище. План виглядав непогано: реплікувати продакшн-набори щонічно, тримати їх офлайн і в разі катастрофи просто «змонтувати і працювати». Вони протестували механіку реплікації — стріми текли, снапшоти з’являлися, графіки зберігання спокійні. Всі пішли додому раніше, що завжди підозріло.
Через місяці рутинна репетиція DR перетворилася на інцидент. Молодший інженер виконав receive у пул, який також хостив staging. Набір даних прибув з mountpoint=/var/lib/app, успадкованим з продакшну. На DR-хості ця тека існувала і містила staging-дані. Receive змонтувався автоматично і сховав staging-тека. Сервіси не впали одразу; вони тихо почали читати старі репліковані файли, що випадково виглядали валідними.
Команда спочатку почала дебажити додаток. Вони переглядали логи, перезапускали контейнери, звинувачували DNS і дивилися дашборди, поки хтось нарешті не запустив mount і не побачив ZFS-файлову систему на шляху, який ніколи не мав торкатися.
Виправлення зайняло хвилини: відмонтування, receive з -u, встановлення безпечного mountpoint і потім явне монтування. Постмортем зайняв більше часу, бо помилкове припущення («mountpoint — локальна конфігурація») вкоренилося в скриптах і tribal knowledge. Справжня корекція була культурною: трактувати властивості як частину реплікованого стану і завжди приземляти відновлення в простір імен, який не може колідувати з живими шляхами.
Міні-історія 2: Оптимізація, що обернулась проти (крутіння ручок під час потоку)
Інша організація мала пайплайн реплікації, який був «занадто повільним». Нічні інкременти іноді заходили в робочий час, що дратувало віце-президента, який любив чисті графіки. Команда зберігання вирішила пришвидшити процес, змінивши налаштування compression і recordsize на цілі, міркуючи, що «receive просто записує блоки; властивості цілі зроблять це швидше».
Вони встановили агресивне стиснення і підкоригували recordsize для наборів даних з образами VM. На папері менші блоки і сильніше стиснення звучали як менше I/O і швидші передачі. На практиці receive став обмежений CPU. Цільові вузли не були розмірені для важкої роботи зі стисненням/дешифруванням, і вікно реплікації погіршилося. Паралельно набори даних образів VM почали фрагментуватися так, що подальші промоції стали повільними і непередбачуваними.
Найцікавіше: оскільки receives все ще були «успішними», ніхто не помітив одразу. Біль з’явився через тижні під час тесту відмови, коли завантаження BOOT штормів тривало довше і латентність зберігання зросла. Команда оптимізувала пайплайн реплікації, але ненавмисно погіршила операційне навантаження, яке насправді мало значення.
Відновлення було нудним: повернути lz4, вирівняти recordsize з реаліями навантаження і розділити цілі. Якщо ви хочете швидшу реплікацію, працюйте над пайплайном (мережа, вибір шифру SSH, прапори send, паралелізм) та дизайном пулу (layout vdev, політика sync), а не випадково змінюючи семантику набору даних під час стріму.
Міні-історія 3: Нудна, але правильна практика, що врятувала день (receive незмонтований, потім промоція свідомо)
Найстійкіший ZFS-шейоп, який я бачив, не мав найкрутішого обладнання. Він мав найскучнішу дисципліну реплікації: кожен receive приземлявся незмонтованим під виділеним кореневим набором з «безпечними дефолтами». Ніякого шарингу. Ніякого автомонту. Чітка неймінг-конвенція. І кожна промоція мала runbook з явними перевірками властивостей.
Під час реального інциденту — проблеми контролера зберігання на первинному — команда вирішила підвищити DR. Тиск був реальним, але кроки були механічними: завантажити ключі шифрування, перевірити наявність снапшотів, встановити mountpoints у шляхи продакшну, переключити canmount, змонтувати, запустити сервіси. Вони не «просто змонтували все й подивилися, що станеться», що прирівнюється до вільного соло-клаймінгу в світі зберігання.
Вони все ще мали проблеми — як у всіх. Один набір даних мав застарілу квоту, репліковану з експерименту з ємністю давніх часів. Але оскільки вони завжди запускали чеклист дифу властивостей перед промоцією, вони помітили це до того, як додаток почав писати. Вони зняли квоту, змонтували і продовжили.
Висновок не в героїці. Він у тому, що нудні процеси працюють під стресом. Runbook DR, що враховує міни властивостей, коштує більше, ніж тисяча рядків хитромудрих скриптів реплікації.
FAQ
1) Чому zfs receive взагалі дбає про властивості?
Тому що в ZFS властивості визначають поведінку, яку інші файлові системи виносять назовні. Реплікація даних без поведінки часто дає зламане відновлення: неправильні mountpoint, профіль продуктивності, модель дозволів.
2) Чи завжди зберігати властивості при реплікації?
Ні. Зберігайте властивості для справжнього дзеркала, яке ви можете підвищити. Перезаписуйте/виключайте властивості для landing zone (dev/analytics/forensics), де розкладка джерела, шеринги та квоти не застосовні.
3) Який найбезпечніший дефолтний прапор receive у незнайомому середовищі?
-u (не монтувати). Це запобігає найбільш поширеному класу самопошкоджень: колізіям mountpoint і несподіваним взаємодіям сервісів.
4) Коли слід використовувати zfs receive -F?
Коли ви маєте намір перезаписати розбіжний стан цілі і погоджуєтеся втратити будь-які зміни, зроблені на цілі з моменту спільного снапшоту. Це підходить для DR-дзеркал, де ціль не є записуваним первинним.
5) Чому мій інкрементальний receive впав, хоча набір даних існує?
Інкременти залежать від графа снапшотів. Ціль повинна мати точний базовий снапшот (або ланцюжок снапшотів), на який посилається стрім. Якщо ціль розійшлася або базовий снапшот був видалений, receive відмовляє, щоб запобігти корупції через припущення.
6) Як ключі шифрування впливають на реплікацію?
Зашифровані набори даних можуть реплікуватися як зашифровані (raw) або в режимі, що передбачає plaintext на відправнику. На приймачі набір даних може існувати, але залишатися незмонтуваним, поки ключі не завантажені. Завжди перевіряйте keystatus і вирішуйте, як keylocation має поводитися в DR.
7) Чому receive повільний?
Більшість повільних receive обмежені одним із: пропускною здатністю/латентністю запису диска, CPU (контрольні суми/стиск/дешифрування), мережею/SSH або синхронними налаштуваннями запису. Виміряйте з zpool iostat, iostat і метриками CPU перед зміною властивостей набору даних.
8) Чи можна безпечно реплікувати з однієї ОС на іншу?
Зазвичай так, але стежте за властивостями, пов’язаними з ACL і xattr, фіч-флагами та поведінкою за замовчуванням. Розглядайте міжплатформову реплікацію як проект сумісності: перевіряйте семантику дозволів реальними тестами застосунків, а не лише ls -l.
9) Чи нормально змінювати mountpoints після receive?
Так, і це часто правильний крок. Отримайте в безпечному неймспейсі, а потім встановлюйте mountpoints в рамках промоції. Це зберігає механічну консистентність реплікації і запобігає колізіям.
10) Як уникнути реплікації «небезпечних» властивостей, як шеринги?
Встановіть безпечні успадковані дефолти на батьківському наборі даних на цілі (шеринг вимкнений), отримайте незмонтованим, а потім явним чином налаштовуйте будь-який шеринґ, який дійсно потрібен. Якщо ваш ZFS підтримує виключення властивостей при receive, використовуйте це — але все одно тримайте паттерн «батько-безпеки» як запасний захід.
Висновок
zfs receive — не пасивний кінцевий пункт. Це місце, де ваша реплікована інформація стає реальною файловою системою з реальною поведінкою, і ці поведінки керуються властивостями — деякі очевидні, деякі тонкі і деякі, які проявляються тільки під тиском відновлення.
Якщо взяти одне оперативне правило з цього: отримуйте незмонтованим у безпечний простір імен, а потім свідомо застосовуйте політику властивостей перед тим, як монтувати або шарити що-небудь. Ця одна звичка запобігає класичним аваріям через колізії mountpoint, робить відновлення шифрування передбачуваним і перетворює реплікацію з ритуалу на інженерну систему, якій можна довіряти.