Sincronizando arquivos com Csync2

Sobre o Csync2

O Csync2 (não confundir com csync) é uma ferramenta para sincronização assíncrona de arquivos em clusters. A sincronização assíncrona de arquivos é boa para arquivos que são raramente modificados – como por exemplo arquivos de configuração or imagens de aplicativos – mas não é adequada para alguns outros tipos de dados, como bancos de dados.

Vantagens da sincronização assíncrona de arquivos

  1. A maior parte das ferramentas deste tipo (incluindo o Csync2) são implementadas como comandos de um único passo, que precisam ser executadas a cada vez para iniciar um ciclo de sincronização. Com isso é possível testar as mudanças em um host antes de aplicá-las aos outros (e também retornar ao estágio antigo se as alterações se mostrarem erradas).
  2. Os algoritmos de sincronização são muito mais simples e, portanto, menos propensos a erros.
  3. Estas ferramentas podem ser (e normalmente são) implementadas como programas normais que operam na camada de usuário. Já as ferramentas de sincronização síncrona precisam ser implementadas como extensões do sistema operacional. Portanto ferramentas assíncronas são mais fácil de implementar e mais portáveis.
  4. É muito mais fácil criar sistemas que permitem configurações com muitos hosts e regras complexas de replicação.

Apesar destas vantagens, tais ferramentas geralmente são bastante primitivas, sendo a maioria invólucros para software de cópia remota (como o rsync e o scp), fazendo com que elas não cubram alguns cenários. Para sanar isto, foi criado o Csync2. Projeto patrocinado pela LINBIT. Empresa cujo foco é a alta disponibilidade.

nota: a partitr daqui sincronização é sinônimo de sincronização assíncrona

Recursos

Os principais recursos do Csync2 são descritos a seguir.

Detecção de conflito

A maior parte das ferramentas apenas copia os arquivos mais novos no lugar dos mais velhos. Isto pode ser bastante perigoso se o mesmo arquivo foi modificado (o conteúdo mudou, o arquivo foi deletado, as permissões mudaram, etc) em mais de um host. O Csync2 detecta tais situações e gera um conflito, que podem ser tratados manualmente pelo administrador ou automaticamente pela ferramenta.

Replicando remoção de arquivos

Muitas ferramentas de sincronização não pode sincronizar remoção de arquivos porque elas não podem distinguir entre o arquivo sendo removido em um host e sendo criado no outro. Então, ao invés de remover o arquivo no host secundário, elas recriam ele no primeiro.

Csync2 detecta remoções de arquivos e é capaz de sincronizá-las corretamente.

Configurações complexas

Csync2 pode trabalhar com vários hosts (ao contrário de muitas ferramentas de sincronização, que foram feitas para apenas dois) e possibilita que determinados arquivos sejam sincronizados apenas com determinados nós do cluster.

Reagir a atualizações

Em muitos casos não é suficiente simplesmente sincronizar um arquivo entre os nós do cluster.  Também é importante dizer à aplicação que os utiliza que um de seus arquivos foi alterado, ex.: reiniciar a aplicação.

O algoritmo

Muitas outras ferramentas de sincronização comparam os hosts, tentam descobrir qual host é o mais atualizado e então sincronização o estado deste host com todos os outros hosts. Este algoritmo não pode detectar conflitos, não pode distinguir entre arquivos removidos e arquivos criados e, portanto, isto não é usado em Csync2.

Csync2 cria um pequeno banco de dados com os metadados do sistema de arquivos em cada host (/var/lib/csync2/nomeDoHost.db). Este banco é usado pelo Csync2 para detectar mudanças na comparação com o sistema de arquivos local. A sincronização em si é então executada utilizando o protocolo Csync2 (que opera na porta TCP 30865).

Note que esta abordagem implica que Csync2 pode somente empurrar as mudanças a partir da máquina onde elas ocorreram para as outras máquinas no cluster. Rodando Csync2 em qualquer outra maquina no cluster não pode detectar e assim não pode sincronizar as atualizações.

A librsync é utilizada para economizar largura de banda dos arquivos sincronizados e SSL é utilizado para criptografar o tráfego de rede. A biblioteca sqlite2 é utilizada para gerenciar o banco de dados do Csync2. A autenticação é feita utilizando chaves auto geradas e previamente compartilhadas em combinação com o endereço IP e certificado SSL do nó.

Comparação com outras ferramentas

  • rsync é uma ferramenta para rápida transferência incremental de arquivos, mas não é uma ferramenta completa para sincronização, pois não detecta conflitos e arquivos deletados de forma apropriada.
  • unison utiliza um algoritmo similar ao do Csync2, porém é limitado a configurações em pares (sejam eles hosts ou discos). É focado em sincronizações interativas (possuiu uma interface gráfica) e seu alvo é sincronização entre notebooks e desktops.
  • sistemas de controle de versão tem a vantagem de poderem fazer _three-way merge_  e preservar o histórico completo de um repositório. A desvantagem é ser muito lento e requerer mais espaço em disco do que ferramentas feitas para sincronização.

Instalação, configuração e sincronização

Neste artigo será demonstrado o uso do Csync2 rodando no Debian 8 (jessie) e CentOS 6.

Instalação

Utilizarei o sistema de gerenciamento de pacotes das duas distribuições GNU/Linux envolvidas: apt e yum. Em ambos a versão do csync2 é a 1.3.4 e a última versão atualmente disponível no site do projeto é a 2.0.

No Debian, basta usar o apt-get:

root@debian8:/# apt-get install csync2

No CentOS 6 é preciso encontrar um repositório que contenha o pacote. Utilizarei o EPEL:

root@centos6:/# yum install epel-release
root@centos6:/# yum install csync2

Configuração

Pós instalação

Ao invés de um daemon próprio, o csync2 utiliza o xinetd, que requer configuração. No Debian o pacote realiza essa configuração automaticamente, no CentOS é preciso editar o arquivo /etc/xinetd.d/csync2 e trocar o yes na instrução disable para no.

Agora é preciso gerar, em cada host, o certificado que será utilizado pelo Csync2. No CentOS 6 os arquivos foram gerados pelo pacote de instalação.

No Debian 8 (ou se quiser gerar outro certificado no CentOS), execute:

root@debian8:/# openssl genrsa -out /etc/csync2_ssl_key.pem 1024
root@debian8:/# openssl req -new -key /etc/csync2_ssl_key.pem -out /etc/csync2_ssl_cert.csr
root@debian8:/# openssl x509 -req -days 600 -in /etc/csync2_ssl_cert.csr -signkey /etc/csync2_ssl_key.pem -out /etc/csync2_ssl_cert.pem

Atenção 01: no Debian 8, não preencha o Common Name (CN) durante a execução do segundo comando. Detalhes aqui.

Atenção 02: neste exemplo estou utilizando hosts consideravelmente diferentes, com pacotes diferentes. Creio que por isso os certificados SSL gerados em um host foram incopatíveis com o outro. Para resolver esse problema veja aqui. Não obtive problemas usando apenas hosts Debian 8 ou hosts CentOS 6.

Note que os arquivos, no Debian, ficam em /etc, já no CentOS ficam em /etc/csync2. Este comportamento não pode ser alterado via arquivo de configuração.

Chaves compartilhadas

Juntamente com o certificado, o Csync2 utiliza um arquivo contendo uma chave que deve ser compartilhada com todos os hosts envolvidos na sincronização. Para gerar a chave utilize o parâmetro -k do csync2, como em:

root@debian8:/# csync2 -k /etc/csync2/exemplo.key

Repare que criei, no Debian, o diretório /etc/csync2 para uma melhor organização das chaves.

Após gerar a chave, copie o arquivo para todos os outros hosts. Exemplo:

root@debian8:/# scp /etc/csync2/exemplo.key root@centos6:/etc/csync2/

Mais a frente será visto o conceito de grupos no Csync2, mas adianto que o recomendado é que cada grupo tenha uma chave única.

Arquivo de configuração

No Debian 8 o arquivo de configuração é /etc/csync2.cfg, já no CentOS 6 é /etc/csync2/csync2.cfg. Neste repositório você poderá ver um arquivo de configuração mais comentado e completo.  Aqui irei utilizar uma configuração simples, apenas para demonstrar o uso da ferramenta.

# Grupo de sincronização
group exemplo {
        # nome dos hosts
        host debian8 centos6;
        # chave compartilhada
        key /etc/csync2/exemplo.key;
        # Diretorio que será sincronizado
        include /tmp/teste;
        # No diretório sincronizado, ignore arquivos terminados com til e começados com ponto
        exclude *~ .*;
        # em caso de conflito, tente utilizar o arquivo mais novo
        auto younger;
        # faça até três backups dos arquivos modificados
        backup-directory "/var/backup/csync2";
        backup-generations 3;
}

Sincronização

A menos que o parâmetro -N seja passado, o Csync2 exige que o hostname dos nós sejam os mesmos do arquivo de configuração. Execute o comando hostname e certifique-se de que o nome que aparece pode ser resolvido para o endereço ip. Neste exemplo adicionei os hosts e seus respectivos ips no /etc/hosts dois dois hosts envolvidos:

10.0.0.209 centos6
10.0.0.230 debian8

Seguindo com o csync2.cfg de exemplo, crie os diretórios /tmp/teste e /var/backups/cysync2 em ambos os hosts. Após isso crie, em apenas um dos nós, alguns arquivos para serem sincronizados:

root@debian8:~# for i in $(seq 1 50); do touch /tmp/teste/$i.txt; done

O próximo passo é executar a sincronização em si. O Csync2 fará uma conexão TCP na porta 30865 do outro nó e, se for a primeira execução, os hosts armazenarão a chave do outro nó no banco de dados local.

root@debian8:/# csync2 -xv
Marking file as dirty: /tmp/teste
Marking file as dirty: /tmp/teste/9.txt
Marking file as dirty: /tmp/teste/8.txt
Marking file as dirty: /tmp/teste/7.txt
Marking file as dirty: /tmp/teste/6.txt
Marking file as dirty: /tmp/teste/50.txt
Marking file as dirty: /tmp/teste/5.txt
Marking file as dirty: /tmp/teste/49.txt
Marking file as dirty: /tmp/teste/48.txt
Marking file as dirty: /tmp/teste/47.txt
Marking file as dirty: /tmp/teste/46.txt
Marking file as dirty: /tmp/teste/45.txt
Marking file as dirty: /tmp/teste/44.txt
Marking file as dirty: /tmp/teste/43.txt
Marking file as dirty: /tmp/teste/42.txt
Marking file as dirty: /tmp/teste/41.txt
Marking file as dirty: /tmp/teste/40.txt
Marking file as dirty: /tmp/teste/4.txt
Marking file as dirty: /tmp/teste/39.txt
Marking file as dirty: /tmp/teste/38.txt
Marking file as dirty: /tmp/teste/37.txt
Marking file as dirty: /tmp/teste/36.txt
Marking file as dirty: /tmp/teste/35.txt
Marking file as dirty: /tmp/teste/34.txt
Marking file as dirty: /tmp/teste/33.txt
Marking file as dirty: /tmp/teste/32.txt
Marking file as dirty: /tmp/teste/31.txt
Marking file as dirty: /tmp/teste/30.txt
Marking file as dirty: /tmp/teste/3.txt
Marking file as dirty: /tmp/teste/29.txt
Marking file as dirty: /tmp/teste/28.txt
Marking file as dirty: /tmp/teste/27.txt
Marking file as dirty: /tmp/teste/26.txt
Marking file as dirty: /tmp/teste/25.txt
Marking file as dirty: /tmp/teste/24.txt
Marking file as dirty: /tmp/teste/23.txt
Marking file as dirty: /tmp/teste/22.txt
Marking file as dirty: /tmp/teste/21.txt
Marking file as dirty: /tmp/teste/20.txt
Marking file as dirty: /tmp/teste/2.txt
Marking file as dirty: /tmp/teste/19.txt
Marking file as dirty: /tmp/teste/18.txt
Marking file as dirty: /tmp/teste/17.txt
Marking file as dirty: /tmp/teste/16.txt
Marking file as dirty: /tmp/teste/15.txt
Marking file as dirty: /tmp/teste/14.txt
Marking file as dirty: /tmp/teste/13.txt
Marking file as dirty: /tmp/teste/12.txt
Marking file as dirty: /tmp/teste/11.txt
Marking file as dirty: /tmp/teste/10.txt
Marking file as dirty: /tmp/teste/1.txt
Connecting to host centos6 (SSL) ...
Adding peer x509 certificate to db: 30820251308201BA0209009EC7086D09AE699E300D06092A864886F70D01010B0500306D310B30090603550406130242523112301006035504080C0953616F2D5061756C6F3114301206035504070C0B53616E746F20416E64726531123010060355040A0C09746F626961732E77733120301E06092A864886F70D0109011611636F6E7461746F40746F626961732E7773301E170D3135313030323138343033325A170D3137303532343138343033325A306D310B30090603550406130242523112301006035504080C0953616F2D5061756C6F3114301206035504070C0B53616E746F20416E64726531123010060355040A0C09746F626961732E77733120301E06092A864886F70D0109011611636F6E7461746F40746F626961732E777330819F300D06092A864886F70D010101050003818D0030818902818100B00052797718DF9FACAD587B06C55B2A61E4D887C67492B1D8CD832894B05B53C7AF6D5ACA4E432A4F45DDDBE892FD12A18C77A8C6AFBDF0A581F1AE0796B341A1379CBD9FA45B4A12B4155EDAEF5C4633A73DE3FA27015567BE66FCB613160D82F62AE94710BB668619CFA92E3DEE0A354A075F5D2E07EBC0CE7CA143F27E5D0203010001300D06092A864886F70D01010B050003818100546CE5AE58FA7D5B0898717AF855449B0F1328085215A1263179D202F0EDE8E3175A4BFCC3D27359753320CE9F918B4F475F3A5A6B72F4F41B3C7E4F2BA7A2B3A0C7CC243CB25CFA9DF547A51B72118C1EE658CD5BC0633507F0BDB9FC802B666B5A6905BDC2FD09DA4130245535BA93155E67398354872078C93C87322B259E
Updating /tmp/teste on centos6 ...
File is already up to date on peer.
Updating /tmp/teste/1.txt on centos6 ...
Updating /tmp/teste/10.txt on centos6 ...
Updating /tmp/teste/11.txt on centos6 ...
Updating /tmp/teste/12.txt on centos6 ...
Updating /tmp/teste/13.txt on centos6 ...
Updating /tmp/teste/14.txt on centos6 ...
Updating /tmp/teste/15.txt on centos6 ...
Updating /tmp/teste/16.txt on centos6 ...
Updating /tmp/teste/17.txt on centos6 ...
Updating /tmp/teste/18.txt on centos6 ...
Updating /tmp/teste/19.txt on centos6 ...
Updating /tmp/teste/2.txt on centos6 ...
Updating /tmp/teste/20.txt on centos6 ...
Updating /tmp/teste/21.txt on centos6 ...
Updating /tmp/teste/22.txt on centos6 ...
Updating /tmp/teste/23.txt on centos6 ...
Updating /tmp/teste/24.txt on centos6 ...
Updating /tmp/teste/25.txt on centos6 ...
Updating /tmp/teste/26.txt on centos6 ...
Updating /tmp/teste/27.txt on centos6 ...
Updating /tmp/teste/28.txt on centos6 ...
Updating /tmp/teste/29.txt on centos6 ...
Updating /tmp/teste/3.txt on centos6 ...
Updating /tmp/teste/30.txt on centos6 ...
Updating /tmp/teste/31.txt on centos6 ...
Updating /tmp/teste/32.txt on centos6 ...
Updating /tmp/teste/33.txt on centos6 ...
Updating /tmp/teste/34.txt on centos6 ...
Updating /tmp/teste/35.txt on centos6 ...
Updating /tmp/teste/36.txt on centos6 ...
Updating /tmp/teste/37.txt on centos6 ...
Updating /tmp/teste/38.txt on centos6 ...
Updating /tmp/teste/39.txt on centos6 ...
Updating /tmp/teste/4.txt on centos6 ...
Updating /tmp/teste/40.txt on centos6 ...
Updating /tmp/teste/41.txt on centos6 ...
Updating /tmp/teste/42.txt on centos6 ...
Updating /tmp/teste/43.txt on centos6 ...
Updating /tmp/teste/44.txt on centos6 ...
Updating /tmp/teste/45.txt on centos6 ...
Updating /tmp/teste/46.txt on centos6 ...
Updating /tmp/teste/47.txt on centos6 ...
Updating /tmp/teste/48.txt on centos6 ...
Updating /tmp/teste/49.txt on centos6 ...
Updating /tmp/teste/5.txt on centos6 ...
Updating /tmp/teste/50.txt on centos6 ...
Updating /tmp/teste/6.txt on centos6 ...
Updating /tmp/teste/7.txt on centos6 ...
Updating /tmp/teste/8.txt on centos6 ...
Updating /tmp/teste/9.txt on centos6 ...
Finished with 0 errors.

E no outro host:

[root@centos6 csync2]# csync2 -xv
[root@centos6 csync2]#

Neste momento o processo está completado. Você pode querer criar o arquivo /etc/cron.d/csync2, nos dois hosts, com o seguinte conteúdo:

*/3 * * * * root /usr/sbin/csync2 -x

Que irá executar a sincronização a cada 3 minutos.

Dicas

  • Você pode se sentir tentado a sincronização a configuração do Csync2, porém isso poderá trazer problemas. Se você sincronizar as chaves compartilhadas e uma das sincronizações der errado, é possível que as sincronizações deixem de ocorrer porquê não há mais a chave correta em um dos nós; e se o arquivo de configuração for sincronizado, um atacante poderá modificar o arquivo de apenas uma máquina e afetar todo o cluster.
  • Mesmo que seu servidor DNS resolva os nomes dos hosts, considere inserir os dados no /etc/hosts, isso evitará ataques de DNS spoofing.

Troubleshooting

Como debugar

O Csync2 aceita o parâmetro -v, que deixa a saída mais verbosa. É possível usar até três vês, que aumentam progressivamente as informações na saída.

Abaixo um exemplo de uma conexão que falhou devido ao xinetd não iniciar o Csync2.

root@debian8:~# csync2 -xvv
[..]
Don't check at all: /boot
Don't check at all: /bin
SQL: SELECT peername FROM dirty GROUP BY peername ORDER BY random()
SQL Query finished.
SQL: SELECT filename, myname, force FROM dirty WHERE peername = 'centos6' ORDER by filename ASC
SQL Query finished.
Connecting to host centos6 (SSL) ...
Can't connect to remote host.
ERROR: Connection to remote host failed.
Host stays in dirty state. Try again later...
SQL: SELECT command, logfile FROM action GROUP BY command, logfile
SQL Query finished.
SQL: COMMIT TRANSACTION
Finished with 1 errors.
root@debian8:~#

Também pode-se fazer uma conexão “crua” usando telnet ou netcat. Nos exemplos abaixo as conexões foram bem sucedidas, falhando no início do SSL, o que é esperado neste caso.

root@debian8:~# telnet centos6 30865
Trying 10.0.0.209...
Connected to centos6.
Escape character is '^]'.
exit
Expecting SSL (optional) and CONFIG as first commands.
Connection closed by foreign host.
root@debian8:~# netcat centos6 30865
exit
Expecting SSL (optional) and CONFIG as first commands.

Erro na conexão SSL

O Csync2 pode exibir este erro em diversas ocasiões. Em todos os casos, primeiramente verifique se o seu firewall permite conexões tcp de entrada na porta 30865 (csync2) e se a chave compartilhada é idêntica em todos os hosts envolvidos.

Caso o erro persista, há etapas que podem ser seguidas dependendo da distribuição GNU/Linux escolhida.

No Debian 8

Ao tentar realizar uma conexão (a primeira?) você recebe a mensagem “Establishing SSL connection failed.“, como em:

root@debian8:/home/tobias# csync2 -xvvv
[...]
Don't check at all: /bin
SQL: SELECT peername FROM dirty GROUP BY peername ORDER BY random()
SQL Query finished.
SQL: SELECT filename, myname, force FROM dirty WHERE peername = 'centos6' ORDER by filename ASC
SQL Query finished.
Connecting to host centos6 (SSL) ...
Establishing SSL connection failed.

Isso significa que há um erro no certificado em algum dos nós. Se algum deles foi gerado no Debian 8, significa que você foi vítima deste bug e deve gerar novamente o certificado deixando o campo Common Name (CN) em branco.

Esse erro não ocorre no Debian 9, Stretch, que atualmente está em teste e será a próxima versão stable do Debian.

No CentOS

Verifique o SELinux.

Entre hosts com versões diferentes do openssl

[root@centos6 ~]# csync2 -xvvv
[...]
Connecting to host debian8 (SSL) ...
EXT[0x8c43680]: Sending extension CERT_TYPE
EXT[0x8c43680]: Sending extension SAFE_RENEGOTIATION
EXT[0x8c43680]: Found extension 'SAFE_RENEGOTIATION/65281'
ASSERT: auth_cert.c:232
ASSERT: gnutls_record.c:507
ASSERT: gnutls_record.c:638
ASSERT: gnutls_record.c:947
ASSERT: gnutls_handshake.c:2861
ASSERT: gnutls_handshake.c:3040
ASSERT: gnutls_record.c:262
SSL: handshake failed: A record packet with illegal version was received. (GNUTLS_E_UNSUPPORTED_VERSION_PACKET)

Olhando mais afundo é opssível ver que CentOS e Debian utilizam uma versão diferente do openssl:

root@debian8:/# openssl version
OpenSSL 1.0.1k 8 Jan 2015
[root@centos6 csync2]# openssl version
OpenSSL 1.0.1e-fips 11 Feb 2013

Para resolver gere os certificados de ambos os hosts através de apenas um deles, depois copie para o outro. Pode-se usar o mesmo certificado em todos os hosts, mas isso prejudica a segurança.

Fontes e referências

Grande parte das informações deste artigo vieram do paper disponível no site do projeto Csync2, datado de 02/05/2013 e acessado em 24/09/2015.