Criar e restaurar backups incrementais com GNU tar
Use a opção --listed-incremental do GNU tar para criar um backup completo seguido de arquivos tar
com as alterações. Este passo a passo oferece uma cadeia local de backups e um teste de restauração
que verifica o conteúdo dos arquivos, incluindo o que acontece quando você adiciona, modifica ou
exclui arquivos. Cada backup recebe sua própria cópia do snapshot, então uma execução com falha não
consegue avançar o estado do último backup bem-sucedido.
Configurar uma pequena origem de backup
Use Linux, Bash, GNU tar, gzip e os utilitários padrão diff e de arquivos. Os exemplos
foram testados com GNU tar 1.35 e Bash 5; confira se tar --version identifica o GNU tar. Os
arquivos tar incrementais do GNU usam extensões que outras implementações de tar podem não suportar.
Consulte o manual de backups incrementais do GNU tar.
tar --version
bash --version
gzip --version
Execute os blocos abaixo no Bash, a partir do mesmo diretório inicial. Os parênteses mantêm as
mudanças de diretório e as opções do shell restritas a cada bloco. A configuração se recusa a
reutilizar um diretório tar-demo já existente.
(
set -euo pipefail
mkdir tar-demo
cd -- tar-demo
mkdir -p project/cache backups
printf 'version one\n' > project/notes.txt
printf 'keep this file\n' > project/unchanged.txt
printf 'remove this file later\n' > project/obsolete.txt
printf 'rebuildable cache\n' > project/cache/item.txt
)
Mantenha backups fora da árvore de origem, como mostrado aqui, para que os arquivos tar
não possam incluir a própria saída. Use uma origem que não esteja recebendo gravações durante o
backup. Este exercício cobre arquivos comuns em um diretório local; ele não faz um backup
consistente de um banco de dados em execução nem preserva todos os tipos de metadados do sistema.
Salvar cada backup com seu snapshot
O arquivo de snapshot registra o que o tar viu durante o backup anterior. Sem um snapshot, começa um backup completo, ou de nível zero. Ao reutilizar um snapshot, o tar registra as alterações desde aquele snapshot e o atualiza durante a criação. Copie o snapshot anterior antes de criar o próximo arquivo tar; mantenha a cópia anterior intacta.
Salve este script como tar-demo/backup.sh. O primeiro argumento indica o nome de um novo diretório
de backup. O segundo argumento, opcional, indica o backup anterior bem-sucedido. Os nomes podem
conter letras ASCII, dígitos, sublinhados e hifens.
#!/usr/bin/env bash
set -euo pipefail
unset TAR_OPTIONS
cd -- "$(dirname -- "$0")"
if (( $# < 1 || $# > 2 )); then
printf 'Usage: bash backup.sh NAME [PREVIOUS]\n' >&2
exit 2
fi
for name in "$@"; do
case "$name" in
''|*[!a-zA-Z0-9_-]*)
printf 'Invalid backup name: %s\n' "$name" >&2
exit 2
;;
esac
done
output="backups/$1"
previous=${2:-}
if [[ -n "$previous" ]]; then
for file in complete archive.tar.gz state.snar; do
if [[ ! -f "backups/$previous/$file" ]]; then
printf 'Previous backup is incomplete: %s\n' "$previous" >&2
exit 1
fi
done
fi
# Reserve a new directory before installing cleanup that can remove it.
mkdir -- "$output"
trap 'rm -rf -- "$output"' EXIT
trap 'exit 1' HUP INT TERM
if [[ -n "$previous" ]]; then
cp -- "backups/$previous/state.snar" "$output/state.snar"
fi
tar --create --file=- \
--listed-incremental="$output/state.snar" \
--exclude='project/cache' \
-- project | gzip > "$output/archive.tar.gz"
gzip --test -- "$output/archive.tar.gz"
touch -- "$output/complete"
trap - EXIT
printf 'Created %s\n' "$output"
Execute-o uma vez sem predecessor:
bash tar-demo/backup.sh 00-full
Isso cria backups/00-full/archive.tar.gz, state.snar e um marcador complete vazio dentro
de tar-demo. O marcador registra que a criação do arquivo tar e a verificação do gzip
foram concluídas. Ele não substitui um teste de restauração. Se o diretório de saída já existir, o
script falha antes de alterar o conteúdo dele, mesmo quando a entrada não é interativa. Execute
apenas um backup por vez.
Manter as exclusões consistentes
O --exclude='project/cache' entre aspas omite esse diretório e o conteúdo dele. Mantenha o caminho de
origem e as exclusões iguais ao longo de toda a cadeia. Se você mudar o que faz parte do backup,
comece um novo backup completo com um novo nome. Selecionar arquivos recentes com find -mtime
não fornece o registro de entradas de diretório excluídas que este fluxo de trabalho usa; deixe o
tar percorrer o diretório de origem.
O gzip compacta cada arquivo tar de forma independente. Ele não decide quais arquivos foram alterados. Para uma análise à parte das opções de compressão, consulte tar com Zstd e LZ4.
Capturar as alterações em ordem
Modifique um arquivo, adicione outro e exclua o arquivo obsoleto. A pausa curta separa os timestamps deste pequeno exemplo em sistemas de arquivos com resolução de tempo pouco precisa. A seleção incremental do GNU tar depende de timestamps, então não atrase o relógio durante uma cadeia.
(
set -euo pipefail
cd -- tar-demo
sleep 1
printf 'version two\n' > project/notes.txt
printf 'new file\n' > project/added.txt
rm -- project/obsolete.txt
bash backup.sh 01-change 00-full
)
Inspecione o arquivo tar com os arquivos alterados:
tar --list --gzip --file=tar-demo/backups/01-change/archive.tar.gz
Ele contém project/, project/notes.txt e project/added.txt; a ordem pode variar. A
entrada do diretório também carrega metadados incrementais. unchanged.txt ainda depende do
backup completo, enquanto a exclusão de obsolete.txt será aplicada durante a extração
incremental.
Faça mais uma alteração, usando 01-change como predecessor:
(
set -euo pipefail
cd -- tar-demo
sleep 1
printf 'version three\n' > project/notes.txt
bash backup.sh 02-change 01-change
)
A cadeia resultante é 00-full → 01-change → 02-change. Para chegar ao estado final, mantenha os três arquivos
tar. Escolher 00-full como predecessor novamente criaria outro backup relativo àquele
backup completo, em vez de estender esta sequência.
Restaurar a cadeia completa em um diretório vazio
A extração incremental do GNU tar pode excluir arquivos que estavam ausentes de um diretório
arquivado. Restaure apenas seus próprios arquivos tar confiáveis em um destino novo, nunca sobre a
origem em uso. Use --listed-incremental=/dev/null em todas as extrações, incluindo a do backup completo. A
extração lê o histórico dos diretórios a partir dos arquivos tar; ela não precisa dos arquivos
.snar salvos.
(
set -euo pipefail
cd -- tar-demo
mkdir restore
for generation in 00-full 01-change 02-change; do
test -f "backups/$generation/complete"
gzip --test -- "backups/$generation/archive.tar.gz"
tar --extract --gzip --listed-incremental=/dev/null \
--file="backups/$generation/archive.tar.gz" --directory=restore
done
diff --recursive --exclude=cache project restore/project
test ! -e restore/project/obsolete.txt
printf 'Restored files match the source, excluding cache.\n'
)
Em caso de sucesso, diff não imprime nada e a última linha confirma a comparação. O
notes.txt restaurado contém version three, added.txt contém new file,
unchanged.txt é mantido e obsolete.txt não existe mais. Nenhum diretório
cache é restaurado. Isso compara nomes e conteúdos de arquivos, não proprietário,
permissões ou atributos estendidos.
Uma nova execução recusa um diretório restore já existente antes de extrair qualquer
coisa. Após uma restauração com falha, o diretório pode conter resultados parciais; analise o erro e
escolha um novo destino vazio para o próximo teste. Não trate uma restauração parcial como dados
recuperados.
Rejeitar backups incompletos
O sucesso do compressor não prova que o processo que gerou a entrada dele foi bem-sucedido. A
opção pipefail do Bash faz com que uma falha em
tar | gzip cause a falha do pipeline, mesmo que o gzip termine de compactar com sucesso
um arquivo tar parcial. Em seguida, o script remove apenas o novo diretório que reservou. O arquivo
tar e o snapshot anteriores permanecem inalterados, então tente novamente com o mesmo predecessor
depois de corrigir o problema.
Não adicione --ignore-failed-read a este script. A documentação do GNU tar informa que essa opção
faz com que entradas ausentes ou ilegíveis, e arquivos alterados durante a leitura, deixem de afetar
o status de saída. Isso é o oposto de detectar um backup incompleto. Consulte
--ignore-failed-read.
Mantenha os diagnósticos visíveis e rejeite qualquer status de saída diferente de zero, incluindo o
status 1 para arquivos alterados durante a criação. Interrompa o processo que está gravando ou use
um snapshot adequado do sistema de arquivos antes de tentar novamente com uma origem que está
mudando.
tar --list verifica se o tar consegue ler a estrutura do arquivo tar; os checksums de
cabeçalho do tar não cobrem o conteúdo dos arquivos.
gzip --test verifica a integridade do fluxo compactado, mas
não consegue dizer se algum arquivo de entrada foi omitido. É por isso que este fluxo de trabalho
verifica falhas de comandos e restaura o resultado para comparação. As
notas sobre corrupção do GNU tar explicam essa distinção.
Manter cadeias de backup completas juntas
Mantenha juntos o arquivo tar e o snapshot de cada diretório bem-sucedido. O arquivo tar é
necessário para a recuperação; o snapshot é necessário para continuar a cadeia. Uma queda de
energia ou um SIGKILL pode contornar o trap de limpeza e deixar um diretório sem
complete. Quando nenhum processo de backup estiver em execução, inspecione esse
diretório incompleto e remova-o antes de tentar novamente; não o use como predecessor.
Essas cópias locais não protegem contra a perda do disco. Copie os diretórios de backup concluídos para um armazenamento separado e repita o teste de restauração a partir dessas cópias. Este exemplo não implementa transferência por SSH nem divisão de arquivos tar. Se um sistema de armazenamento dividir um arquivo tar, todas as partes precisam ser reunidas antes da verificação do gzip e da restauração.
Mantenha o backup completo e todos os arquivos tar incrementais necessários para um ponto de recuperação mantido. Excluir arquivos tar individuais por idade pode quebrar essa cadeia. Comece um novo backup completo periodicamente, teste o caminho de recuperação dele e descarte as cadeias mais antigas como unidades inteiras, de acordo com sua política de retenção.
