O que LVM e Matryoshka têm em comum?

Bom dia.
Gostaria de compartilhar com a comunidade minha experiência prática na construção de um sistema de armazenamento de dados para KVM usando md RAID + LVM.

O programa incluirá:

  • Construindo MD RAID 1 a partir de SSD NVMe.
  • Montando MD RAID 6 de SSD SATA e unidades regulares.
  • Recursos de operação TRIM/DISCARD em SSD RAID 1/6.
  • Criando um array MD RAID 1/6 inicializável em um conjunto comum de discos.
  • Instalando o sistema em NVMe RAID 1 quando não há suporte NVMe no BIOS.
  • Usando cache LVM e LVM fino.
  • Usando instantâneos BTRFS e envio/recebimento para backup.
  • Usando snapshots finos LVM e thin_delta para backups estilo BTRFS.

Se você estiver interessado, consulte gato.

Afirmação

O autor não se responsabiliza pelas consequências do uso ou não de materiais/exemplos/código/dicas/dados deste artigo. Ao ler ou utilizar este material de qualquer forma, você assume a responsabilidade por todas as consequências dessas ações. As possíveis consequências incluem:

  • SSDs NVMe fritos e crocantes.
  • Recursos de gravação completamente esgotados e falha nas unidades SSD.
  • Perda completa de todos os dados em todas as unidades, incluindo cópias de backup.
  • Hardware de computador com defeito.
  • Perda de tempo, nervosismo e dinheiro.
  • Quaisquer outras consequências que não estejam listadas acima.

ferro

Disponíveis estavam:

Placa-mãe por volta de 2013 com chipset Z87, completa com Intel Core i7 / Haswell.

  • Processador 4 núcleos, 8 threads
  • 32 GB de RAM DDR3
  • 1 x 16 ou 2 x 8 PCIe 3.0
  • 1 x 4 + 1 x 1 PCIe 2.0
  • 6 conectores SATA 6 de 3 GBps

Adaptador SAS LSI SAS9211-8I atualizado para o modo IT/HBA. O firmware habilitado para RAID foi substituído intencionalmente pelo firmware HBA para:

  1. Você pode jogar fora este adaptador a qualquer momento e substituí-lo por qualquer outro que encontrar.
  2. TRIM/Discard funcionou normalmente em discos, porque... no firmware RAID, esses comandos não são suportados e o HBA, em geral, não se importa com quais comandos são transmitidos pelo barramento.

Discos rígidos - 8 unidades HGST Travelstar 7K1000 com capacidade de 1 TB em formato 2.5, como para laptops. Anteriormente, essas unidades estavam em uma matriz RAID 6. Eles também terão utilidade no novo sistema. Para armazenar backups locais.

Adicionado adicionalmente:

6 peças SATA SSD modelo Samsung 860 QVO 2TB. Esses SSDs exigiam um grande volume, a presença de um cache SLC, confiabilidade e um preço baixo eram desejados. Foi necessário suporte para descarte/zero, o que é verificado pela linha no dmesg:

kernel: ata1.00: Enabling discard_zeroes_data

2 peças de SSD NVMe modelo Samsung SSD 970 EVO 500GB.

Para esses SSDs, a velocidade aleatória de leitura/gravação e a capacidade de recursos para suas necessidades são importantes. Radiador para eles. Necessariamente. Absolutamente. Caso contrário, frite-os até ficarem crocantes durante a primeira sincronização RAID.

Adaptador StarTech PEX8M2E2 para 2 x SSD NVMe instalado em slot PCIe 3.0 8x. Novamente, este é apenas um HBA, mas para NVMe. Ele difere dos adaptadores baratos porque não requer suporte de bifurcação PCIe da placa-mãe devido à presença de um switch PCIe integrado. Funcionará mesmo no sistema mais antigo com PCIe, mesmo que seja um slot x1 PCIe 1.0. Naturalmente, na velocidade apropriada. Não há RAIDs lá. Não há BIOS integrado a bordo. Portanto, seu sistema não aprenderá magicamente a inicializar com NVMe, muito menos a fazer NVMe RAID graças a este dispositivo.

Este componente deveu-se unicamente à presença de apenas um 8x PCIe 3.0 grátis no sistema e, se houver 2 slots livres, pode ser facilmente substituído por dois centavos PEX4M2E1 ou análogos, que podem ser adquiridos em qualquer lugar pelo preço de 600 rublos.

A rejeição de todos os tipos de hardware ou chipset/BIOS RAIDs integrados foi feita deliberadamente, de forma a poder substituir completamente todo o sistema, com exceção dos próprios SSD/HDD, preservando todos os dados. Idealmente, para que você possa salvar até mesmo o sistema operacional instalado ao mudar para um hardware completamente novo/diferente. O principal é que existem portas SATA e PCIe. É como um live CD ou uma unidade flash inicializável, só que muito rápido e um pouco volumoso.

HumorCaso contrário, você sabe o que acontece - às vezes você precisa urgentemente levar todo o array com você para levar embora. Mas não quero perder dados. Para fazer isso, todas as mídias mencionadas estão convenientemente localizadas nos slides das baias 5.25 do gabinete padrão.

Bem, e, claro, para experimentar diferentes métodos de cache SSD no Linux.

As invasões de hardware são chatas. Ligue-o. Ou funciona ou não. E com o mdadm sempre há opções.

macio

Anteriormente, o Debian 8 Jessie era instalado no hardware, que está próximo do EOL. O RAID 6 foi montado a partir dos HDDs mencionados acima emparelhados com o LVM. Ele executou máquinas virtuais em kvm/libvirt.

Porque O autor tem experiência adequada na criação de unidades flash SATA / NVMe inicializáveis ​​​​portáteis e também, para não quebrar o modelo usual do apt, o Ubuntu 18.04 foi escolhido como sistema alvo, que já foi suficientemente estabilizado, mas ainda tem 3 anos de suporte no futuro.

O sistema mencionado contém todos os drivers de hardware de que precisamos prontos para uso. Não precisamos de nenhum software ou driver de terceiros.

Preparando para instalação

Para instalar o sistema precisamos do Ubuntu Desktop Image. O sistema do servidor possui uma espécie de instalador vigoroso, que mostra independência excessiva que não pode ser desabilitada empurrando a partição do sistema UEFI para um dos discos, estragando toda a beleza. Conseqüentemente, ele é instalado apenas no modo UEFI. Não oferece nenhuma opção.

Não estamos felizes com isso.

Por quê?Infelizmente, a inicialização UEFI é extremamente pouco compatível com o software de inicialização RAID, porque... Ninguém nos oferece reservas para a partição UEFI ESP. Existem receitas online que sugerem colocar a partição ESP em uma unidade flash em uma porta USB, mas isso é um ponto de falha. Existem receitas usando software mdadm RAID 1 com metadados versão 0.9 que não impedem que o UEFI BIOS veja esta partição, mas isso dura até o momento feliz quando o BIOS ou outro sistema operacional de hardware grava algo no ESP e esquece de sincronizá-lo com outro espelhos.

Além disso, a inicialização UEFI depende da NVRAM, que não será movida junto com os discos para o novo sistema, porque faz parte da placa-mãe.

Portanto, não reinventaremos uma nova roda. Já temos uma bicicleta do avô pronta e testada pelo tempo, agora chamada de inicialização Legacy/BIOS, que leva o orgulhoso nome de CSM em sistemas compatíveis com UEFI. Basta tirar da prateleira, lubrificar, encher os pneus e passar um pano úmido.

A versão desktop do Ubuntu também não pode ser instalada corretamente com o bootloader Legacy, mas aqui, como dizem, pelo menos há opções.

E assim, coletamos o hardware e carregamos o sistema a partir da unidade flash inicializável do Ubuntu Live. Precisaremos baixar pacotes, então configuraremos a rede que funciona para você. Se não funcionar, você pode carregar os pacotes necessários em uma unidade flash com antecedência.

Entramos no ambiente Desktop, iniciamos o emulador de terminal e pronto:

#sudo bash

Como…?A linha acima é o gatilho canônico para holiwars sobre sudo. Cbоmaiores oportunidades surgem eоmaior responsabilidade. A questão é se você pode assumir isso sozinho. Muitas pessoas pensam que usar o sudo dessa forma é, pelo menos, pouco cuidadoso. No entanto:

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

Por que não ZFS...?Quando instalamos software em nosso computador, essencialmente emprestamos nosso hardware para os desenvolvedores desse software dirigirem.
Quando confiamos a este software a segurança dos nossos dados, contraímos um empréstimo igual ao custo de restauração desses dados, que um dia teremos que pagar.

Deste ponto de vista, ZFS é uma Ferrari e mdadm+lvm é mais parecido com uma bicicleta.

Subjetivamente, o autor prefere emprestar uma bicicleta a crédito a desconhecidos em vez de uma Ferrari. Lá, o preço da emissão não é alto. Não há necessidade de direitos. Mais simples que as regras de trânsito. O estacionamento é gratuito. A capacidade de cross-country é melhor. Você sempre pode prender pernas a uma bicicleta e consertar uma bicicleta com suas próprias mãos.

Por que então BTRFS...?Para inicializar o sistema operacional, precisamos de um sistema de arquivos que seja compatível com Legacy/BIOS GRUB pronto para uso e que, ao mesmo tempo, suporte instantâneos ao vivo. Iremos usá-lo para a partição /boot. Além disso, o autor prefere usar este FS para / (root), não esquecendo de observar que para qualquer outro software você pode criar partições separadas no LVM e montá-las nos diretórios necessários.

Não armazenaremos nenhuma imagem de máquinas virtuais ou bancos de dados neste FS.
Este FS será usado apenas para criar instantâneos do sistema sem desligá-lo e depois transferir esses instantâneos para um disco de backup usando envio/recebimento.

Além disso, o autor geralmente prefere manter um mínimo de software diretamente no hardware e executar todos os outros softwares em máquinas virtuais usando recursos como encaminhamento de GPUs e controladores host PCI-USB para KVM via IOMMU.

As únicas coisas que restam no hardware são armazenamento de dados, virtualização e backup.

Se você confia mais no ZFS, então, em princípio, para o aplicativo especificado eles são intercambiáveis.

No entanto, o autor ignora deliberadamente os recursos integrados de espelhamento/RAID e redundância que ZFS, BRTFS e LVM possuem.

Como argumento adicional, o BTRFS tem a capacidade de transformar gravações aleatórias em sequenciais, o que tem um efeito extremamente positivo na velocidade de sincronização de snapshots/backups no HDD.

Vamos verificar novamente todos os dispositivos:

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

Vamos dar uma olhada:

#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

Layout do disco

SSD NVMe

Mas não os marcaremos de forma alguma. Mesmo assim, nosso BIOS não vê essas unidades. Então, eles irão inteiramente para o RAID de software. Nem criaremos seções lá. Se você quiser seguir o “cânon” ou “principalmente”, crie uma partição grande, como um HDD.

SATA HDD

Não há necessidade de inventar nada de especial aqui. Criaremos uma seção para tudo. Criaremos uma partição porque o BIOS vê esses discos e pode até tentar inicializar a partir deles. Iremos até instalar o GRUB nesses discos mais tarde, para que o sistema possa fazer isso repentinamente.

#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

É aqui que as coisas ficam interessantes para nós.

Em primeiro lugar, nossas unidades têm 2 TB de tamanho. Isso está dentro da faixa aceitável para MBR, que é o que usaremos. Se necessário, pode ser substituído por GPT. Os discos GPT possuem uma camada de compatibilidade que permite que sistemas compatíveis com MBR vejam as primeiras 4 partições se estiverem localizadas nos primeiros 2 terabytes. O principal é que a partição de boot e a partição bios_grub nesses discos estejam no início. Isso permite até mesmo inicializar a partir de unidades GPT Legacy/BIOS.

Mas este não é o nosso caso.

Aqui criaremos duas seções. O primeiro terá 1 GB e será usado para RAID 1/boot.

O segundo será usado para RAID 6 e ocupará todo o espaço livre restante, exceto uma pequena área não alocada no final da unidade.

O que é essa área não marcada?De acordo com fontes na rede, nossos SSDs SATA possuem um cache SLC dinamicamente expansível com tamanho variando de 6 a 78 gigabytes. Obtemos 6 gigabytes “de graça” devido à diferença entre “gigabytes” e “gibibytes” na ficha técnica do drive. Os 72 gigabytes restantes são alocados de espaço não utilizado.

Deve-se notar aqui que temos um cache SLC, e o espaço é ocupado no modo MLC de 4 bits. O que para nós significa efetivamente que para cada 4 gigabytes de espaço livre obteremos apenas 1 gigabyte de cache SLC.

Multiplique 72 gigabytes por 4 e obtenha 288 gigabytes. Este é o espaço livre que não marcaremos para permitir que os drives aproveitem totalmente o cache SLC.

Assim, obteremos efetivamente até 312 gigabytes de cache SLC de um total de seis unidades. De todas as unidades, 2 serão usadas em RAID para redundância.

Essa quantidade de cache nos permitirá raramente encontrar uma situação na vida real em que uma gravação não vá para o cache. Isso compensa extremamente bem a desvantagem mais triste da memória QLC - a velocidade de gravação extremamente baixa quando os dados são gravados ignorando o cache. Se suas cargas não corresponderem a isso, recomendo que você pense bem sobre quanto tempo seu SSD durará sob tal carga, levando em consideração o TBW da folha de dados.

#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

Criando matrizes

Primeiro, precisamos renomear a máquina. Isso é necessário porque o nome do host faz parte do nome do array em algum lugar dentro do mdadm e afeta algo em algum lugar. É claro que os arrays podem ser renomeados posteriormente, mas esta é uma etapa desnecessária.

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

SSD NVMe

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

Por que -supor-limpo...?Para evitar a inicialização de arrays. Para ambos os níveis de RAID 1 e 6 isto é válido. Tudo pode funcionar sem inicialização se for um novo array. Além disso, inicializar o array SSD na criação é um desperdício de recursos TBW. Usamos TRIM/DISCARD sempre que possível em arrays SSD montados para “inicializá-los”.

Para matrizes SSD, RAID 1 DISCARD é compatível imediatamente.

Para arrays SSD RAID 6 DISCARD, você deve habilitá-lo nos parâmetros do módulo do kernel.

Isso só deverá ser feito se todos os SSDs usados ​​em arrays de nível 4/5/6 neste sistema tiverem suporte funcional para descartar_zeroes_data. Às vezes você se depara com unidades estranhas que informam ao kernel que esta função é suportada, mas na verdade ela não existe ou a função nem sempre funciona. No momento, o suporte está disponível em quase todos os lugares, porém, existem unidades e firmware antigos com erros. Por esse motivo, o suporte DISCARD está desabilitado por padrão para RAID 6.

Atenção, o comando a seguir destruirá todos os dados nos drives NVMe “inicializando” o array com “zeros”.

#blkdiscard /dev/md0

Se algo der errado, tente especificar uma etapa.

#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

Por que tão grande...?Aumentar o tamanho do bloco tem um efeito positivo na velocidade de leitura aleatória em blocos até o tamanho do bloco inclusive. Isso acontece porque uma operação de tamanho apropriado ou menor pode ser concluída inteiramente em um único dispositivo. Portanto, o IOPS de todos os dispositivos é somado. Segundo as estatísticas, 99% do IO não excede 512K.

RAID 6 IOPS por gravação sempre menor ou igual ao IOPS de uma unidade. Quando, como uma leitura aleatória, o IOPS pode ser várias vezes maior que o de uma unidade, e aqui o tamanho do bloco é de fundamental importância.
O autor não vê sentido em tentar otimizar um parâmetro que é ruim no RAID 6 por design e, em vez disso, otimiza aquilo em que o RAID 6 é bom.
Compensaremos a gravação aleatória ruim do RAID 6 com um cache NVMe e truques de provisionamento dinâmico.

Ainda não habilitamos o DISCARD para RAID 6. Portanto, não iremos “inicializar” este array por enquanto. Faremos isso mais tarde, após instalar o sistema operacional.

SATA HDD

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

LVM em RAID NVMe

Para maior velocidade, queremos colocar o sistema de arquivos raiz no NVMe RAID 1, que é /dev/md0.
No entanto, ainda precisaremos desse array rápido para outras necessidades, como swap, metadados e cache LVM e metadados finos de LVM, então criaremos um LVM VG neste array.

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

Vamos criar uma partição para o sistema de arquivos raiz.

#lvcreate -L 128G --name root root

Vamos criar uma partição para troca de acordo com o tamanho da RAM.

#lvcreate -L 32G --name swap root

Instalação do sistema operacional

No total, temos tudo o que é necessário para instalar o sistema.

Inicie o assistente de instalação do sistema no ambiente Ubuntu Live. Instalação normal. Somente na fase de seleção dos discos para instalação, você precisa especificar o seguinte:

  • /dev/md1, - ponto de montagem /boot, FS - BTRFS
  • /dev/root/root (também conhecido como /dev/mapper/root-root), - ponto de montagem / (root), FS - BTRFS
  • /dev/root/swap (também conhecido como /dev/mapper/root-swap), - use como partição swap
  • Instale o bootloader em /dev/sda

Ao selecionar BTRFS como sistema de arquivos raiz, o instalador criará automaticamente dois volumes BTRFS chamados "@" para / (root) e "@home" para /home.

Vamos começar a instalação...

A instalação terminará com uma caixa de diálogo modal indicando um erro na instalação do bootloader. Infelizmente, você não poderá sair desta caixa de diálogo usando meios padrão e continuar a instalação. Saímos do sistema e efetuamos login novamente, terminando em um desktop Ubuntu Live limpo. Abra o terminal e novamente:

#sudo bash

Crie um ambiente chroot para continuar a instalação:

#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

Vamos configurar a rede e o nome do host no chroot:

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

Vamos para o ambiente chroot:

#chroot /mnt/chroot

Em primeiro lugar, entregaremos os pacotes:

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

Vamos verificar e corrigir todos os pacotes que foram instalados incorretamente devido à instalação incompleta do sistema:

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

Se algo não funcionar, pode ser necessário editar /etc/apt/sources.list primeiro

Vamos ajustar os parâmetros do módulo RAID 6 para habilitar TRIM/DISCARD:

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

Vamos ajustar um pouco nossos arrays:

#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

O que foi isso..?Criamos um conjunto de regras do udev que farão o seguinte:

  • Defina o tamanho do cache de bloco para RAID 2020 como adequado para 6. O valor padrão, ao que parece, não mudou desde a criação do Linux e não é adequado há muito tempo.
  • Reserve um mínimo de IO durante as verificações/sincronizações do array. Isso evita que seus arrays fiquem presos em um estado de sincronização eterna sob carga.
  • Limite o IO máximo durante verificações/sincronização de arrays. Isso é necessário para que a sincronização/verificação de RAIDs SSD não quebre suas unidades. Isto é especialmente verdadeiro para NVMe. (Lembra do radiador? Eu não estava brincando.)
  • Proíbe que os discos parem a rotação do eixo (HDD) via APM e defina o tempo limite de suspensão dos controladores de disco para 7 horas. Você pode desabilitar completamente o APM se suas unidades puderem fazer isso (-B 255). Com o valor padrão, as unidades irão parar após cinco segundos. Então o sistema operacional deseja redefinir o cache do disco, os discos girarão novamente e tudo começará novamente. Os discos têm um número máximo limitado de rotações do fuso. Um ciclo padrão tão simples pode facilmente destruir seus discos em alguns anos. Nem todos os discos sofrem com isso, mas os nossos são de “laptop”, com as configurações padrão adequadas, que fazem o RAID parecer um mini-MAID.
  • Instale o readahead em discos (rotativos) 1 megabyte - dois blocos/pedaços consecutivos RAID 6
  • Desative o readahead nos próprios arrays.

Vamos editar o /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

Por que é que..?Procuraremos a partição /boot por UUID. A nomenclatura da matriz poderia, teoricamente, mudar.

Procuraremos as seções restantes pelos nomes do LVM na notação /dev/mapper/vg-lv, porque eles identificam partições de maneira bastante exclusiva.

Não usamos UUID para LVM porque O UUID dos volumes LVM e seus instantâneos podem ser os mesmos.Montar /dev/mapper/root-root.. duas vezes?Sim. Exatamente. Recurso do BTRFS. Este sistema de arquivos pode ser montado várias vezes com diferentes subvols.

Devido a esse mesmo recurso, recomendo nunca criar instantâneos LVM de volumes BTRFS ativos. Você pode ter uma surpresa ao reiniciar.

Vamos regenerar a configuração do mdadm:

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

Vamos ajustar as configurações do 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

O que foi isso..?Habilitamos a expansão automática de thin pools LVM ao atingir 90% do espaço ocupado por 5% do volume.

Aumentamos o número máximo de blocos de cache para cache LVM.

Evitamos que o LVM procurasse volumes LVM (PV) em:

  • dispositivos contendo cache LVM (cdata)
  • dispositivos armazenados em cache usando cache LVM, ignorando o cache ( _corig). Neste caso, o próprio dispositivo em cache ainda será verificado através do cache (apenas ).
  • dispositivos contendo metadados de cache LVM (cmeta)
  • todos os dispositivos em VG com o nome de imagens. Aqui teremos imagens de disco de máquinas virtuais e não queremos que o LVM no host ative volumes pertencentes ao sistema operacional convidado.
  • todos os dispositivos em VG com o nome backup. Aqui teremos cópias de backup de imagens de máquinas virtuais.
  • todos os dispositivos cujo nome termina com “gpv” (volume físico convidado)

Habilitamos o suporte DISCARD ao liberar espaço livre no LVM VG. Tome cuidado. Isso tornará a exclusão de LVs no SSD bastante demorada. Isso se aplica especialmente ao SSD RAID 6. Porém, de acordo com o plano, usaremos provisionamento thin, então isso não nos atrapalhará em nada.

Vamos atualizar a imagem initramfs:

#update-initramfs -u -k all

Instale e configure o grub:

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

Quais discos escolher?Todos os que são SD*. O sistema deve ser capaz de inicializar a partir de qualquer unidade SATA ou SSD em funcionamento.

Por que eles adicionaram os-prober..?Para independência excessiva e mãos brincalhonas.

Não funciona corretamente se um dos RAIDs estiver em estado degradado. Ele tenta procurar o sistema operacional em partições usadas em máquinas virtuais executadas neste hardware.

Se precisar, você pode deixá-lo, mas lembre-se de todos os itens acima. Recomendo procurar receitas online para se livrar das mãos malcriadas.

Com isso concluímos a instalação inicial. É hora de reiniciar o sistema operacional recém-instalado. Não se esqueça de remover o Live CD/USB inicializável.

#exit
#reboot

Selecione qualquer um dos SSDs SATA como dispositivo de inicialização.

LVM em SSD SATA

Neste ponto, já inicializamos o novo sistema operacional, configuramos a rede, apt, abrimos o emulador de terminal e lançamos:

#sudo bash

Nós continuamos.

“Inicialize” o array do SSD SATA:

#blkdiscard /dev/md2

Se não funcionar, tente:

#blkdiscard --step 65536 /dev/md2
Crie LVM VG em SSD SATA:

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

Por que outro VG..?Na verdade, já temos um VG chamado root. Por que não adicionar tudo em um VG?

Se houver vários PVs em um VG, para que o VG seja ativado corretamente, todos os PVs deverão estar presentes (online). A exceção é o LVM RAID, que deliberadamente não usamos.

Nós realmente queremos que se houver uma falha (perda de dados de leitura) em qualquer um dos arrays RAID 6, o sistema operacional inicialize normalmente e nos dê a oportunidade de resolver o problema.

Para fazer isso, no primeiro nível de abstração isolaremos cada tipo de “mídia” física em um VG separado.

Cientificamente falando, diferentes arrays RAID pertencem a diferentes “domínios de confiabilidade”. Você não deve criar um ponto comum adicional de falha para eles, amontoando-os em um VG.

A presença do LVM no nível de “hardware” nos permitirá cortar arbitrariamente pedaços de diferentes arrays RAID, combinando-os de diferentes maneiras. Por exemplo - execute ao mesmo tempo bcache + LVM thin, bcache + BTRFS, LVM cache + LVM thin, uma configuração ZFS complexa com caches ou qualquer outra mistura infernal para tentar comparar tudo.

No nível de “hardware”, não usaremos nada além dos bons e velhos volumes LVM “grossos”. A exceção a esta regra pode ser a partição de backup.

Acho que a essa altura muitos leitores já haviam começado a suspeitar de algo sobre a boneca aninhada.

LVM em HDD SATA

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

Novo VG de novo ..?Nós realmente queremos que, se a matriz de disco que usaremos para backup de dados falhar, nosso sistema operacional continue funcionando normalmente, enquanto mantém o acesso aos dados que não são de backup, como de costume. Portanto, para evitar problemas de ativação do VG, criamos um VG separado.

Configurando cache LVM

Vamos criar um LV no NVMe RAID 1 para usá-lo como dispositivo de cache.

#lvcreate -L 70871154688B --name cache root

Por que há tão pouco...?O fato é que nossos SSDs NVMe também possuem um cache SLC. 4 gigabytes de “grátis” e 18 gigabytes de dinâmico devido ao espaço livre ocupado no MLC de 3 bits. Quando esse cache se esgotar, os SSDs NVMe não serão muito mais rápidos do que nosso SSD SATA com cache. Na verdade, por esse motivo, não faz sentido tornar a partição de cache LVM muito maior que o dobro do tamanho do cache SLC da unidade NVMe. Para as unidades NVMe utilizadas, o autor considera razoável fazer de 32 a 64 gigabytes de cache.

O tamanho de partição fornecido é necessário para organizar 64 gigabytes de cache, metadados de cache e backup de metadados.

Além disso, observo que após um desligamento sujo do sistema, o LVM marcará todo o cache como sujo e sincronizará novamente. Além disso, isso será repetido toda vez que o lvchange for usado neste dispositivo até que o sistema seja reinicializado novamente. Portanto, recomendo recriar imediatamente o cache usando o script apropriado.

Vamos criar um LV no SATA RAID 6 para usá-lo como dispositivo em cache.

#lvcreate -L 3298543271936B --name cache data

Por que apenas três terabytes..?Para que, se necessário, você possa usar SATA SSD RAID 6 para algumas outras necessidades. O tamanho do espaço em cache pode ser aumentado dinamicamente, em tempo real, sem parar o sistema. Para fazer isso, você precisa parar temporariamente e reativar o cache, mas a vantagem distintiva do cache LVM sobre, por exemplo, o bcache é que isso pode ser feito em tempo real.

Vamos criar um novo VG para cache.

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

Vamos criar um LV no dispositivo em cache.

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

Aqui ocupamos imediatamente todo o espaço livre em/dev/data/cache para que todas as outras partições necessárias fossem criadas imediatamente em/dev/root/cache. Se você criou algo no lugar errado, poderá movê-lo usando pvmove.

Vamos criar e habilitar o cache:

#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

Por que esse tamanho de pedaço ..?Através de experimentos práticos, o autor conseguiu descobrir que o melhor resultado é alcançado se o tamanho do bloco de cache do LVM coincidir com o tamanho do bloco fino do LVM. Além disso, quanto menor o tamanho, melhor será o desempenho da configuração em uma gravação aleatória.

64k é o tamanho mínimo de bloco permitido para LVM thin.

Tenha cuidado ao escrever..!Sim. Este tipo de cache adia a sincronização de gravação para o dispositivo armazenado em cache. Isso significa que se o cache for perdido, você poderá perder dados no dispositivo armazenado em cache. Posteriormente, o autor dirá quais medidas, além do NVMe RAID 1, podem ser tomadas para compensar esse risco.

Este tipo de cache foi escolhido intencionalmente para compensar o baixo desempenho de gravação aleatória do RAID 6.

Vamos verificar o que temos:

#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)

Somente [cachedata_corig] deve estar localizado em/dev/data/cache. Se algo estiver errado, use pvmove.

Você pode desativar o cache, se necessário, com um comando:

#lvconvert -y --uncache cache/cachedata

Isso é feito on-line. O LVM irá simplesmente sincronizar o cache com o disco, removê-lo e renomear cachedata_corig de volta para cachedata.

Configurando o LVM fino

Vamos estimar aproximadamente quanto espaço precisamos para os metadados finos do 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"

Arredonde para 4 gigabytes: 4294967296B

Multiplique por dois e adicione 4194304B para metadados LVM PV: 8594128896B
Vamos criar uma partição separada no NVMe RAID 1 para colocar metadados finos do LVM e sua cópia de backup nele:

#lvcreate -L 8594128896B --name images root

Para que..?Aqui pode surgir a questão: por que colocar os metadados finos do LVM separadamente se eles ainda serão armazenados em cache no NVMe e funcionarão rapidamente.

Embora a velocidade seja importante aqui, está longe de ser o motivo principal. O problema é que o cache é um ponto de falha. Algo pode acontecer com ele, e se os metadados finos do LVM forem armazenados em cache, tudo será completamente perdido. Sem metadados completos, será quase impossível montar volumes finos.

Ao mover os metadados para um volume separado, não armazenado em cache, mas rápido, garantimos a segurança dos metadados em caso de perda ou corrupção do cache. Nesse caso, todos os danos causados ​​pela perda de cache estarão localizados dentro de volumes finos, o que simplificará o procedimento de recuperação em ordens de grandeza. Com grande probabilidade, esses danos serão restaurados usando logs FS.

Além disso, se um snapshot de um volume thin foi tirado anteriormente, e depois disso o cache foi totalmente sincronizado pelo menos uma vez, então, devido ao design interno do LVM thin, a integridade do snapshot será garantida em caso de perda de cache .

Vamos criar um novo VG que será responsável pelo provisionamento dinâmico:

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

Vamos criar um pool:

#lvcreate -L 274877906944B --poolmetadataspare y --poolmetadatasize 4294967296B --chunksize 64k -Z y -T images/thin-pool
Por que -Z eAlém da finalidade real desse modo - evitar que dados de uma máquina virtual vazem para outra máquina virtual ao redistribuir o espaço - a zeragem é usada adicionalmente para aumentar a velocidade de gravação aleatória em blocos menores que 64k. Qualquer gravação inferior a 64k em uma área anteriormente não alocada do volume fino se tornará 64K alinhada à borda no cache. Isso permitirá que a operação seja executada inteiramente através do cache, ignorando o dispositivo armazenado em cache.

Vamos mover os LVs para os PVs correspondentes:

#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

Verifica:

#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)

Vamos criar um volume fino para testes:

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

Instalaremos pacotes para testes e monitoramento:

#apt-get install sysstat fio

É assim que você pode observar o comportamento da nossa configuração de armazenamento em tempo real:

#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)'

É assim que podemos testar nossa configuração:

#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

Com cuidado! Recurso!Este código executará 36 testes diferentes, cada um executando por 4 segundos. Metade dos testes são para gravação. Você pode gravar muito no NVMe em 4 segundos. Até 3 gigabytes por segundo. Portanto, cada execução de testes de gravação pode consumir até 216 gigabytes de recursos SSD de você.

Leitura e escrita misturadas?Sim. Faz sentido executar os testes de leitura e gravação separadamente. Além disso, faz sentido garantir que todos os caches estejam sincronizados para que uma gravação feita anteriormente não afete a leitura.

Os resultados irão variar muito durante a primeira inicialização e nas subsequentes à medida que o cache e o volume fino são preenchidos e também dependendo se o sistema conseguiu sincronizar os caches preenchidos durante a última inicialização.

Entre outras coisas, recomendo medir a velocidade em um volume fino já cheio do qual um instantâneo acabou de ser tirado. O autor teve a oportunidade de observar como as gravações aleatórias aceleram drasticamente imediatamente após a criação do primeiro instantâneo, especialmente quando o cache ainda não está completamente cheio. Isso acontece devido à semântica de gravação copy-on-write, ao alinhamento do cache e aos blocos de volume fino e ao fato de que uma gravação aleatória no RAID 6 se transforma em uma leitura aleatória do RAID 6 seguida por uma gravação no cache. Em nossa configuração, a leitura aleatória do RAID 6 é até 6 vezes (o número de SSDs SATA no array) mais rápida do que a gravação. Porque os blocos para CoW são alocados sequencialmente a partir de um pool fino, então a gravação, em sua maior parte, também se transforma em sequencial.

Ambos os recursos podem ser usados ​​a seu favor.

Armazenar instantâneos “coerentes” em cache

Para reduzir o risco de perda de dados em caso de dano/perda de cache, o autor propõe introduzir a prática de rotação de snapshots para garantir sua integridade neste caso.

Primeiro, como os metadados de volume reduzido residem em um dispositivo sem cache, os metadados serão consistentes e possíveis perdas serão isoladas dentro dos blocos de dados.

O seguinte ciclo de rotação de snapshots garante a integridade dos dados dentro dos snapshots em caso de perda de cache:

  1. Para cada volume fino com o nome <nome>, crie um instantâneo com o nome <nome>.cached
  2. Vamos definir o limite de migração para um valor razoavelmente alto: #lvchange --quiet --cachesettings "migration_threshold=16384" cache/cachedata
  3. No loop verificamos o número de blocos sujos no cache: #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' até chegarmos a zero. Se o zero estiver faltando por muito tempo, ele poderá ser criado alternando temporariamente o cache para o modo write-through. No entanto, levando em consideração as características de velocidade de nossos arrays SSD SATA e NVMe, bem como seus recursos TBW, você poderá aproveitar rapidamente o momento sem alterar o modo de cache, ou seu hardware consumirá completamente todos os seus recursos em alguns dias. Devido a limitações de recursos, o sistema é, em princípio, incapaz de ficar abaixo de 100% da carga de gravação o tempo todo. Nossos SSDs NVMe com carga de gravação de 100% esgotarão completamente os recursos em dias 3-4. Os SSDs SATA durarão apenas o dobro. Portanto, assumiremos que a maior parte da carga vai para a leitura e temos surtos de atividade extremamente alta de prazo relativamente curto combinados com uma carga média baixa para escrita.
  4. Assim que pegamos (ou marcamos) um zero, renomeamos <nome>.cached para <nome>.comprometido. O antigo <name>.committed é excluído.
  5. Opcionalmente, se o cache estiver 100% cheio, ele poderá ser recriado por um script, limpando-o assim. Com um cache meio vazio, o sistema funciona muito mais rápido durante a gravação.
  6. Defina o limite de migração como zero: #lvchange --quiet --cachesettings "migration_threshold=0" cache/cachedata Isso impedirá temporariamente a sincronização do cache com a mídia principal.
  7. Esperamos até que muitas alterações se acumulem no cache #lvs --rows --reportformat basic --quiet -ocache_dirty_blocks cache/cachedata | awk '{print $2}' ou o cronômetro irá disparar.
  8. Repetimos novamente.

Por que dificuldades com o limite de migração...?O problema é que, na prática, uma gravação “aleatória” não é completamente aleatória. Se escrevermos algo em um setor de 4 kilobytes de tamanho, há uma grande probabilidade de que nos próximos minutos uma gravação seja feita no mesmo setor ou em um dos setores vizinhos (+- 32K).

Ao definir o limite de migração como zero, adiamos a sincronização de gravação no SSD SATA e agregamos diversas alterações em um bloco de 64K no cache. Isso economiza significativamente os recursos do SSD SATA.

Onde está o código ..?Infelizmente, o autor se considera insuficientemente competente no desenvolvimento de scripts bash porque é 100% autodidata e pratica o desenvolvimento orientado pelo “google”, portanto acredita que o terrível código que sai de suas mãos não deve ser usado por ninguém outro.

Acho que os profissionais da área serão capazes de representar de forma independente toda a lógica descrita acima, se necessário, e talvez até projetá-la lindamente como um serviço systemd, como o autor tentou fazer.

Um esquema tão simples de rotação de instantâneos nos permitirá não apenas ter constantemente um instantâneo totalmente sincronizado no SSD SATA, mas também nos permitirá, usando o utilitário thin_delta, descobrir quais blocos foram alterados após sua criação e, assim, localizar danos em os principais volumes, simplificando bastante a recuperação.

TRIM/DISCARD em libvirt/KVM

Porque o armazenamento de dados será usado para KVM rodando libvirt, então seria uma boa ideia ensinar nossas VMs não apenas a ocupar espaço livre, mas também a liberar o que não é mais necessário.

Isso é feito emulando o suporte TRIM/DISCARD em discos virtuais. Para fazer isso, você precisa alterar o tipo de controlador para virtio-scsi e editar o arquivo 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>

Esses DISCARDs de sistemas operacionais convidados são processados ​​corretamente pelo LVM e os blocos são liberados corretamente tanto no cache quanto no thin pool. No nosso caso, isso acontece principalmente de forma retardada, ao excluir o próximo snapshot.

Backup BTRFS

Use scripts prontos com крайней cautela e por sua própria conta e risco. O autor escreveu este código sozinho e exclusivamente para si mesmo. Tenho certeza de que muitos usuários experientes do Linux possuem ferramentas semelhantes e não há necessidade de copiar as de outra pessoa.

Vamos criar um volume no dispositivo de backup:

#lvcreate -L 256G --name backup backup

Vamos formatá-lo em BTRFS:

#mkfs.btrfs /dev/backup/backup

Vamos criar pontos de montagem e montar as subseções raiz do sistema de arquivos:

#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

Vamos criar diretórios para backups:

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

Vamos criar um diretório para scripts de backup:

#mkdir /root/btrfs-backup

Vamos copiar o script:

Muitos códigos bash assustadores. Use por sua conta e risco. Não escreva cartas raivosas ao autor...#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

O que isso faz ..?Contém um conjunto de comandos simples para criar instantâneos BTRFS e copiá-los para outro FS usando envio/recebimento BTRFS.

O primeiro lançamento pode ser relativamente longo, porque... No início, todos os dados serão copiados. Os próximos lançamentos serão muito rápidos, porque... Somente as alterações serão copiadas.

Outro script que colocaremos no cron:

Mais um pouco de código bash#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

O que isso faz..?Cria e sincroniza instantâneos incrementais dos volumes BTRFS listados no FS de backup. Depois disso, exclui todas as fotos criadas há 60 dias. Após o lançamento, instantâneos datados dos volumes listados aparecerão nos subdiretórios /backup/btrfs/back/remote/.

Vamos dar direitos de execução de código:

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

Vamos verificar e colocar no 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

Backup fino LVM

Vamos criar um thin pool no dispositivo de backup:

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

Vamos instalar o ddrescue, porque... scripts usarão esta ferramenta:

#apt-get install gddrescue

Vamos criar um diretório para scripts:

#mkdir /root/lvm-thin-backup

Vamos copiar os scripts:

Muita bagunça por dentro...#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

O que isso faz...?Contém um conjunto de comandos para manipular snapshots finos e sincronizar a diferença entre dois snapshots finos, obtidos via thin_delta, para outro dispositivo de bloco usando ddrescue e blkdiscard.

Outro script que colocaremos no cron:

Um pouco mais de festa#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

O que isso faz...?Usa o script anterior para criar e sincronizar backups dos thin volumes listados. O script deixará instantâneos inativos dos volumes listados, que são necessários para rastrear alterações desde a última sincronização.

Este script deve ser editado, especificando a lista de volumes finos para os quais devem ser feitas cópias de backup. Os nomes fornecidos são apenas para fins ilustrativos. Se desejar, você pode escrever um script que sincronizará todos os volumes.

Vamos dar os direitos:

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

Vamos verificar e colocar no 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

O primeiro lançamento será longo, porque... volumes finos serão totalmente sincronizados copiando todo o espaço usado. Graças aos metadados finos do LVM, sabemos quais blocos estão realmente em uso, portanto, apenas os blocos de volume fino realmente usados ​​serão copiados.

As execuções subsequentes copiarão os dados de forma incremental graças ao rastreamento de alterações por meio de metadados finos do LVM.

Vamos ver o que aconteceu:

#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

O que isso tem a ver com bonecos de nidificação?

Muito provavelmente, dado que os volumes lógicos LVM LV podem ser volumes físicos LVM PV para outros VGs. O LVM pode ser recursivo, como bonecos aninhados. Isso dá extrema flexibilidade ao LVM.

PS

No próximo artigo, tentaremos usar vários sistemas de armazenamento móvel/KVM semelhantes como base para a criação de um cluster de armazenamento/VM distribuído geograficamente com redundância em vários continentes usando desktops domésticos, Internet doméstica e redes P2P.

Fonte: habr.com

Adicionar um comentário