TI é assim: um dia vai dar merda. A questão aqui não é o 'se'. É o 'quando', o que é impossível prever sob as perspectivas de tempo e circunstância. Quando esse dia chegar, e ele vai chegar, esteja preparado para se recuperar da merda. Seus dados precisam sobreviver a isso, de preferência com você são. Sem preparo, sifu! Simples assim.

O único meio eficiente que conheço para se recuperar de uma merda em TI é um treco chamado DRP, de Disaster Recovery Plan, ou na mais precisa tradução para o português, 'como sair da merda numa boa, merrrmão'.

Requisitos para isso, direto do 'Tao do Backup':

  1. Abrangência;
  2. Frequência;
  3. Distribuição;
  4. Longevidade;
  5. Teste;
  6. Segurança;
  7. Integridade.

Partindo daí contarei como resolvi o meu DRP. Mas antes, um aviso:

Todo o meu DRP é para o meu uso, segundo as minhas premissas e restrições. Ele foi criado para as minha máquinas de uso doméstico e profissional e de forma alguma se aplica a servidores em produção, onde os métodos e ferramentas são radicalmente diferentes, mas os conceitos são idênticos.

Atualizado em 11/09/2014: a segunda parte deste artigo altera algumas premissas importantes relativas ao backup dos seus dados, especificamente com relação a abrangência, frequencia e longevidade. Esteja atento a essas mudanças antes de implementar o seu DRP. O conteúdo deste artigo não foi alterado para manter o histórico.

Backup: seguro antimerda

Se você já usou um Mac OS X a partir do 10.5, certamente já viu ou usou o Time Machine. Como tudo que a Apple faz, ele parece lindo e funcional, mas é emburrecedor e perigoso. As pessoas se esquecem que o Time Machine não resolve os problemas de um DRP. Mais especificamente, ele exagera na abrangência, frequência e longevidade e trata com extremo descaso a distribuição, o teste, a segurança e a integridade.

Depois de muitos anos de uso do Mac OS X, o Time Machine não falhou comigo nas duas vezes que precisei dele. Por pura sorte, já que poderia muito bem ter falhado e eu não teria um plano B. A culpa neste caso teria sido integralmente minha em função da natureza do Time Machine enquanto recurso emburrecedor e da minha falta de curiosidade para tratar a questão com o cuidado que ela merece. Menos mal que não foi assim.

Hoje, no Slackware, eu não tenho o Time Machine. E isso é ótimo! Finalmente eu sei e controlo o que está acontecendo com os meus backups em todas as etapas do processo do meu DRP e do Tao.

Clone ou imagem: descartado

Antes de abordar o processo do meu DRP, quero deixar uma coisa bem clara: clonar ou fazer uma imagem do disco inteiro não é estratégia de backup.

Possuir um clone ou imagem de um disco é algo vantajoso do ponto de vista de tempo de recuperação do desastre, mas é ridiculamente ineficiente para escalar: é difícil fazer todo dia, necessita pelo menos o mesmo espaço ocupado no disco de origem, depende de hardware disponível, toma muito tempo na execução, não é incremental etc.

Outro problema do clone/imagem: você perdeu o hardware (desktop, notebook) e precisou adquirir um novo. Assim que terminar de clonar o disco de origem para a nova máquina e bootar, pau! Com toda probabilidade o novo hardware é diferente e o seu kernel antigo vai se recusar a rodar nele sem que você faça uma intervenção chata e demorada. E aí se vão algumas horas que poderiam ser a vantagem de um clone ou imagem do disco original inteiro.

Evidentemente isso 'fere' o quesito abrangência do Tao. Eu não faço backup de tudo que tem na máquina porque considero isso ineficiente e irrelevante. Só faço backup das partes que não podem ser reproduzidas (o meu conteúdo) caso ocorra um desastre e das configurações da máquina e do ambiente. Todo o resto eu posso reproduzir, pois são coisas que estão amplamente disponíveis em outros lugares. É burrice, perda de tempo, de largura de banda e de espaço manter uma réplica disso.

Por estas razões, o meu DRP não inclui clone/imagem. Eu optei pela possibilidade de eventualmente trocar algumas horas a mais na recuperação por um processo de backup mais eficiente, fácil de reproduzir e escalar.

NAS: o guardião {#nas-o-guardião}

O primeiro elo do meu DRP é o NAS. Ele é o destino de todos os dados das máquinas sob a proteção do meu DRP e é a partir dele que os dados são distribuídos para outros destinos, na nuvem ou não.

Ele está devidamente preparado para rodar os serviços NFS, rsync e git. Se o seu NAS está dentro de um ambiente que somente você controla, o NFS pode rodar sem criptografia em nível de protocolo em nome da simplicidade operacional e de manutenção. Caso contrário, use NFS com Kerberos ou SSHFS, ao preço de alguma dor de cabeça para implementar e manter. Já o git e o rsync terão a autenticação e o tráfego criptografados pelo SSH por definição.

Dentro do NAS os dados poderão ser armazenados em um volume criptografado ou não. De lá eles serão enviados, sempre em pacotes fechados e criptografados a outros destinos sempre bem longe do seu NAS e do seu computador (a tal distribuição do Tao). Isso deve ser feito também todos os dias por tarefas específicas que rodam no próprio NAS.

Lembre-se: você não usa um NAS como usa um computador. Se o poder de processamento dele é fraquinho, não importa, pois tudo o que ele tem na vida é tempo. Ele só não pode é demorar a receber os seus dados que chegam todos os dias, o que impede que você saia correndo com o computador embaixo do braço para um compromisso qualquer.

Como fazer essas coisas é um exercício que deixo para o leitor, pois cada NAS e destino, na nuvem ou não, tem suas particularidades.

Ah! E faça o backup do seu NAS inteiro também, mas esse pode ser local em um disco externo conectado e montado diretamente nele. Sobre o seu NAS estar devidamente configurado com um volume RAID1+0, é claro que está, não está? ;)

luckyBackup: o operário {#luckybackup-o-operário}

Esse pequeno e eficiente aplicativo é o responsável por fazer o backup diário do que eu considero mais importante no meu disco e também de manter tudo no destino de backup por um período que eu considero razoável. Na minha experiência pessoal, sete dias é suficiente.

O luckyBackup faz os backups via rsync em um método chamado snapshot. O primeiro backup do conteúdo que eu desejo é feito por inteiro. A partir deste primeiro backup, os demais são incrementais, onde somente os dados modificados são copiados novamente e os dados anteriores são 'aproveitados' para completar. Isso torna os backups diários muito rápidos.

Para se ter uma idéia da eficiência deste processo, o meu primeiro backup completo levou cerca de 4h para ser executado e consome aproximadamente 11GB. A execução de hoje levou exatos 0h3m15s. Você leu certo: três minutos e quinze segundos. Apenas 332MB de dados foram transferidos, porque o resto já existia e não foi alterado.

A minha tarefa de backup no luckyBackup é parecida com a configuração abaixo. Os dados exatos foram obviamente alterados. Você pode fazer toda essa configuração direto na tela do luckyBackup.

[Task] - 0
Name=Meu backup fodao
TypeDirContents=1
TypeDirName=0
TypeSync=0
Source=/
Destination=nas_nfs_volume/hostname/
TaskDescription=Backup diario de:
TaskDescription=
TaskDescription=/boot
TaskDescription=/etc
TaskDescription=/home
TaskDescription=/usr/local
TaskDescription=/var/www/htdocs
TaskDescription=
TaskDescription=As regras detalhadas de exclusao e inclusao estao em ~/.luckyBackup/profiles/filters.
TaskDescription=
TaskDescription=O destino e:
TaskDescription=rsync.user@nas::nas_nfs_volume/hostname/
TaskDescription=
TaskDescription=A autenticao esta em ~/.luckyBackup/profiles/secret.
TaskDescription=
TaskDescription=Mantem snapshot das 7 ultimas execucoes.
TaskDescription=
TaskDescription=Rodar com script que verifica se a rede permite a execucao (onde o servidor de rsync esta):
TaskDescription=/usr/local/bin/luckubackup-net
TaskDescription=
TaskDescription=Agendamento no cron do usuario:
TaskDescription=26 04 * * * sudo /usr/local/bin/luckybackup-net >> /var/log/luckybackup.log 2>&1
TaskDescription=
Args=-h
Args=--progress
Args=--stats
Args=-r
Args=-tgo
Args=-p
Args=-l
Args=--numeric-ids
Args=--update
Args=--delete-after
Args=--prune-empty-dirs
Args=--delete-excluded
Args=--exclude-from=/home/ze/.luckyBackup/profiles/filters
Args=--protect-args
Args=--password-file=/home/ze/.luckyBackup/profiles/secret
Args=--filter=protect .luckybackup-snaphots/
Args=--log-file=/home/ze/.luckyBackup/snaps/changes.log
Args=--log-file-format=[changed_data]%i[LB]%n
Args=/
Args=rsync.user@nas::nas_nfs_volume/hostname/
ConnectRestore=
KeepSnapshots=7
Exclude=1
ExcludeFromFile=1
ExcludeFile=/home/ze/.luckyBackup/profiles/filters
ExcludeTemp=0
ExcludeCache=0
ExcludeBackup=0
ExcludeMount=0
ExcludeLostFound=0
ExcludeSystem=0
ExcludeTrash=0
ExcludeGVFS=0
Include=0
IncludeFromFile=0
IncludeModeNormal=1
IncludeFile=
Remote=1
RemoteModule=1
RemoteDestination=1
RemoteSource=0
RemoteSSH=0
RemoteHost=nas
RemoteUser=rsync.user
RemotePassword=/home/ze/.luckyBackup/profiles/secret
RemoteSSHPassword=
RemoteSSHPasswordStr=
RemoteSSHOptions=
RemoteSSHPort=0
OptionsUpdate=1
OptionsDelete=1
OptionsRecurse=1
OptionsOwnership=1
OptionsSymlinks=1
OptionsPermissions=1
OptionsDevices=0
OptionsCVS=0
OptionsHardLinks=0
OptionsFATntfs=0
OptionsSuper=0
OptionsNumericIDs=1
OptionsRestorent=0
OptionsVss=0
LuckyBackupDir=/home/ze/.luckyBackup/
VshadowDir=/usr/bin
RsyncCommand=rsync
SshCommand=ssh
DosdevCommand=/usr/bin/dosdev.exe
CygpathCommand=/usr/bin/cygpath.exe
TempPath=/dev/shm
OptionsListItem=--prune-empty-dirs
ByPassWarning=0
CloneWarning=0
RepeatOnFail=0
IncludeState=1
[Task_end] - 0

Você pode notar a partir da tarefa acima que o conteúdo a ser 'backupeado' vem do arquivo ~/.luckyBackup/profiles/filters. Esse método de seleção de arquivos é muito mais poderoso do que o disponível na interface do luckyBackup por usar as FILTER RULES do rsync (ver man rsync).

Veja um exemplo do meu, também alterado para remover dados sensíveis:

# General exclusions
# These are global exclusions, even if included later
- **~
- *.bak
- *.directory
- *.lck
- *.lock
- .git*
- *.new
- *.old
- *.orig
- *.rej
- **/.gvfs/
- **/*cache*/
- **/*Cache*/
- **/lost+found*/
- **/*tmp*/
- **/*Trash*/
- **/*trash*/

# Include /boot while excluding some files and directories within
+ /boot/
- /boot/efi
- /boot/initrd**
+ /boot/**

# Include entire /etc
+ /etc/
+ /etc/**

# Include home directories (not files within, see bellow)
+ /home/
+ /home/*/

# Then exclude files from home
- /home/*/.X*
- /home/*/.bash*
- /home/*/.cache
- /home/*/.dbus
- /home/*/.directory
- /home/*/.gstreamer*
- /home/*/.icedtea
- /home/*/.kde/*-hostname
- /home/*/.macromedia
- /home/*/.mplayer
- /home/*/.neocomplcache
- /home/*/.pip
- /home/*/.recently-used
- /home/*/.spf13*
- /home/*/.thumbnails*
- /home/*/*vim*
- /home/*/.xsession-errors
- /home/*/Dev/
- /home/*/Downloads/
- /home/*/Músicas/
- /home/*/Público/
- /home/*/Vídeos/
- /home/*/VirtualBox*
- /home/maria/**

# Then special rules to include only the desired log files under home
+ /home/*/.someapplication/*.log

# Then exclude all other logs
- *.log

# Then include some dirs within Dev
+ /home/*/Dev/
+ /home/*/Dev/myprojects/
+ /home/*/Dev/myprojects/projectA/
+ /home/*/Dev/myprojects/projectA/**
+ /home/*/Dev/myprojects/projectB/
+ /home/*/Dev/myprojects/projectB/**

# Then include every other file under home
+ /home/*/**

# Include entire /usr/local
+ /usr/
+ /usr/local/
+ /usr/local/**

# Include /var/www/htdocs while excluding some files and directories within
+ /var/
+ /var/www/
+ /var/www/htdocs/
- /var/www/htdocs/htdig/
- /var/www/htdocs/manual/
+ /var/www/htdocs/**

# Exclude everything else
- *

Sacou? Tudo o que importa é guardado todo dia com essa entrada no cron do seu usuário:

26 04 * * * sudo /usr/local/bin/luckybackup-net >> /var/log/luckybackup.log 2>&1

'Mas peraí!' Você interrompe. E pergunta: 'Que porra de é essa de luckybackup-net?'

Se eu não estou na mesma rede do NAS, ele está inacessível. Por qual razão rodar o backup se ele não vai para lugar nenhum, não é? O luckbackup-net verifica isso e é assim:

#!/usr/bin/env bash

# Network where the rsync host is
NET=192.168.0

## The main script.
# Network check
IF1=eth0
IF2=wlan0
LNET1=`/sbin/ifconfig $IF1|grep 'inet\s'|sed 's/\:/ /'|awk '{print $2}'|cut -d. -f-3`
LNET2=`/sbin/ifconfig $IF2|grep 'inet\s'|sed 's/\:/ /'|awk '{print $2}'|cut -d. -f-3`

if [ "$NET" = "$LNET1" ] ||  [ "$NET" = "$LNET2" ] ; then
  echo "`date +[%d/%b/%Y:%T]`: Host on $NET.0 network. Going to backup..."
  sudo /usr/bin/luckybackup -c --no-questions --skip-critical /home/ze/.luckyBackup/profiles/default.profile
  sudo chown ze:users /home/ze/.luckyBackup/logs/*.log /home/ze/.luckyBackup/snaps/*.log
  echo "`date +[%d/%b/%Y:%T]`: Backup profile done. Check the log for errors."
else
  echo "`date +[%d/%b/%Y:%T]`: Host NOT in $NET.0 network. No backup for today."
fi

Esperto, não? Esse script pode ainda, com pequenas modificações, verificar pela existência do seu NAS em várias redes (casa, escritório, VPN, nuvem etc). Deixo como exercício para você adicionar essa função no script caso necessite.

etckeeper: o grande irmão {#etckeeper-o-grande-irmão}

Dias atrás eu propus no G+ um método para manter o histórico de todas as alterações em arquivos importantes do Slackware. Logo na segunda resposta, do David Sullins, surgiu o etckeeper. Aliás, o David é o mantenedor do SackBuild para o etckeeper.

O etckeeper usa um VCS (Git, Mercurial, Bazaar ou Darcs) para manter um histórico de revisões de todo o diretório /etc. Ele resolve os seguintes problemas:

  1. O Git, o Mercurial e o Bazaar possuem limitações quanto ao registro de permissões de arquivos. Somente a permissão de execução é verificada. O etckeeper armazena as permissões completas e proprietários (usuários e grupos) em um arquivo próprio e sob versionamento;
  2. O Git e o Mercurial não armazenam diretórios vazios. O etckeeper também cuida disso no arquivo próprio para recriar os diretórios vazios quando necessário;
  3. Vários tipos de arquivos não são suportados por nenhum VCS: sockets, pipes, hardlinks e devices. O etckeeper não resolve este problema, mas te avisa caso exista alguma coisa do tipo em seu /etc (o que não é comum);
  4. O Darcs não suporta symlinks. O etckeeper resolve o caso.

Em resumo: o etckeeper faz tudo o que eu desejava em minha proposta original e estava ali, prontinho para ser usado.

Eu também desejava há muito tempo outra coisa que o etckeeper não pode resolver sozinho: manter o histórico de mudanças de tudo que ocorreu no /etc desde a primeira instalação do Slackware64 -current (eu não uso a versão estável, por isso não preciso ir tão longe). Tomei o cuidado de começar láááá de trás. Tornando curta uma história longa com um log:

* 0000004 Life goes on being tracked...
*   0000003 (tag: vS64-merge) Merge production upstream.
|\
| * 0000002 (tag: vS64-current-multilib-production) Init repo: Slackware64 -current multilib production.
* 0000001 (tag: vS64-current-multilib) Slackware64 -current multilib.
* 0000000 (tag: vS64-current-fresh) Init repo: Slackware64 -current fresh install.

Fino, néam?

Pois bem, para fazer o etckeeper trabalhar, instalei-o com:

# sbopkg -i etckeeper

Feita a instalação, configurei o /etc/etckeeper/etckeeper.conf assim:

VCS="git"
GIT_COMMIT_OPTIONS=""
HG_COMMIT_OPTIONS=""
BZR_COMMIT_OPTIONS=""
DARCS_COMMIT_OPTIONS="-a"
HIGHLEVEL_PACKAGE_MANAGER=
LOWLEVEL_PACKAGE_MANAGER=
PUSH_REMOTE="origin"

Para inicializar o repositório local:

# cd /etc
# etckeeper init

O etckeeper vai fazer o trabalho sujo e retornar. Nada de emocionalmente visual acontecerá, mas você terá agora um arquivo /etc/.gitignore. Esse arquivo controla o que ficará de fora dos olhos e garras do etckeeper e do Git. Eu adicionei no final desse arquivo o seguinte:

...
# end section managed by etckeeper
*.bak
*.orig
*.new
random-seed

Depois adicionei o meu repositório privado do servidor Git no NAS. Para isso:

# git remote add origin user@nas:~/repos/etckeeper.git

Então peguei a a chave pública de SSH do root (pode ser ~/.ssh/id_rsa.pub) e coloquei no ~/.ssh/authorized_keys do usuário do NAS onde fica o repositório privado. Isso é importante para a operação automática do etckeeper.

Hora de mandar o /etc inteiro e devidamente versionado para o NAS:

# etckeeper commit "Init repo: now watching /etc."

Com esse comando o etckeeper já faz o git push sozinho, esse danadinho. A partir de agora, o etckeeper é 'o grande irmão' do seu /etc.

Hora te testar. Numa conta de usuário comum (não root) e com acesso ao repositório privado, faça isso:

$ git clone user@nas:~/repos/etckeeper.git
$ cd etckeeper
$ ls etckeeper

A saída do ls deve ser o seu /etc do jeitinho que ele é em seu local original, exceto é claro por uns links quebrados que naturalmente estão assim pois os caminhos das referências originais são relativas somente ao /etc. Não se preocupe pois eles estarão certinhos quando precisarem voltar para o /etc.

Um detalhe importante sobre segurança: cada clone do seu /etc original trará todos os arquivos com permissão de leitura, inclusive os mais secretos como o /etc/shadow (que contém o hash de senhas dos usuários) e outros. Não clone o seu repositório do etckeeper em qualquer lugar. Se precisar dele para alguma coisa, clone-o em uma máquina que você confie, faça o que tem que fazer e apague tudo em seguida. Logo:

$ cd..
$ rm -rf etckeeper

Agora só falta automatizar o etckeeper. Mexer nos arquivos no /etc já é uma tarefa que demanda alguma atenção. Nunca mexemos lá por nada e, quando o fazemos, normalmente estamos concentrados no que é preciso ser feito. No meio dessa função, lembrar que tem que rodar um etckeeper commit "mensagem" é um pé no saco.

Ao invés de se preocupar com um passo adicional em seu workflow, use o seu /etc como sempre fez e deixe o cron e o etckeeper fazerem os trabalhos deles. Basta incluir o arquivo /etc/cron.daily/etckeeper com o seguinte conteúdo:

#!/bin/sh
set -e

NOW=$( LANG=C date +%c )

if [ -x /usr/bin/etckeeper ] && [ -e /etc/etckeeper/etckeeper.conf ]; then
  . /etc/etckeeper/etckeeper.conf
  if [ "$AVOID_DAILY_AUTOCOMMITS" != "1" ]; then
    # avoid autocommit if an install run is in progress
    lockfile=/var/cache/etckeeper/packagelist.pre-install
    if [ -e "$pe" ] && [ -n "$(find "$lockfile" -mtime +1)" ]; then
      rm -f "$lockfile" # stale
    fi
    if [ ! -e "$lockfile" ]; then
      AVOID_SPECIAL_FILE_WARNING=1
      export AVOID_SPECIAL_FILE_WARNING
      if etckeeper unclean; then
        etckeeper commit "$NOW daily autocommit" >/dev/null
        logger -t etckeeper "daily autocommit done"
      fi
    fi
  fi
fi

Tem uma coisa aí que eu faço diferente o arquivo de cron original do etckeeper. No original não tem essa linha logger -t etckeeper "daily autocommit done". Isso é só para registrar a execução no /var/log/messages e é completamente opcional.

Salve o arquivo e:

# chmod +x /etc/cron.daily/etckeeper

A partir daí, pode fuçar no seu /etc a vontade. No dia seguinte, tudo o que você fez estará lá. Se não mudou nada, o etckeeper não fará nada.

Hoje paro aqui, porque você já está seguro. Talvez não tenha uma boa idéia do que fazer para se recuperar da merda ainda, mas fique tranquilo. No próximo artigo eu fecho isso.