ping web

Deployment em Drupal usando o Database Scripts

Publicado em drupal por Eriksen Costa em Setembro 28, 2008

Um dos grandes desafios no desenvolvimento web com Drupal é quanto ao deployment (implantação) de um projeto que use o CMS. O problema não é o primeiro deployment em si, que geralmente é uma tarefa simples mas sim o de novas versões, com suas novas funcionalidades ou correções de bugs.

“Mas peraí, onde está o problema?” ― você se pergunta. O problema está em como as configurações são armazenadas no Drupal: tudo vai para o banco de dados. Caso você já tenha desenvolvido algum site em Drupal, já deve ter percebido o que acontece quando é necessário aplicar alguma nova feature, uma atualização de um módulo ou do próprio core nos ambientes de desenvolvimento e produção: repetição de tarefas manuais.

Digamos que o seu site Drupal tenha o onipresente CCK instalado e, que uma correção de segurança tenha sido lançada. Para tal, você faria os passos normais de atualização:

  1. Desabilitar o módulo;
  2. Substituir os arquivos do módulo por um novo download ou pelo comando cvs update;
  3. Instalar o módulo e executar o script update.php
  4. Testar;
  5. Colocar o código atualizado sobre controle de versão (commit);
  6. Fazer um update no diretório do projeto em produção;
  7. Repetir os passos de 1 a 4.

Repare no retrabalho: fizemos a mesma tarefa duas vezes e de forma manual. Imagine isso para a aplicação de três atualizações de segurança ou de diversas novas features que você tanto suou para desenvolver. Mas o maior dos problemas é que por mais que você use um sistema de controle de versão, você não tem nenhum rastro das diversas alterações de configuração ao longo do desenvolvimento.

Pensando nisso, o módulo Database Scripts ― ou simplesmente dbscripts ― foi desenvolvido (thanks Kathleen Murtagh!). O módulo é uma coleção de arquivos PHP que fazem o dump, o restore e o merge do banco de dados. São usados na linha de comando e tem como dependência o utilitário diff3 (e do PHP CLI). Atualmente apenas a versão 5 do Drupal é suportada mas um port para 6 está em andamento. Ela utilizou este módulo no desenvolvimento do site HarvardScience e em outros projetos. Assim, mesmo ainda desenvolvendo o site, ela o deixou disponível para que os editores de Harvard adicionassem conteúdo.

Funcionamento do dbscripts

A idéia por trás do dbscripts é simples mas, se não for bem entendida, pode dar horas e horas de dor de cabeça com intermináveis conflitos após as operações de merge. Para efetuar uma operação de merge, o dbscripts faz um dump do banco de dados de forma limpa, com cada instrução SQL em uma linha para que possa ser possível usar o utilitário diff. Também faz operações de “filtro” nos dados, separando o que deve ser preservado ou não no controle de versão.

Ao fazer um merge de um dump do banco do ambiente de desenvolvimento com o de produção, os dados de produção como logs e sessões de usuários (tabelas watchdog e session) são descartados mas os de conteúdo (tabela node, node_revision…) são mantidos.

Supondo que temos um site Drupal 5 para testes, faça o download do módulo dbscripts na última versão estável (DRUPAL-5–1-0).

Usei tanto Linux Debian/Ubuntu para testar os scripts. Usuário Windows podem ter o utilitário diff3 instalando o UnxUtils.

Configurando o dbscripts

Para usá-lo, basta descompactar o tar.gz em qualquer diretório e configurar o config.inc.example salvando-o como config.inc. As variáveis tem comentários bem descritivos:

  • $script_path: caminho para os scripts do módulo, não precisa modificar;
  • $settings_path: caminho para o arquivo settings.php do seu site Drupal, altere para o caminho da sua instalação (ex: /var/www/drupal/sites/example.com);
  • $file_path: caminho onde ficarão os dumps locais. O padrão é o diretório databases. Crie esse diretório e, dentro dele, crie um diretório chamado tmp;
  • $dbtype: se você utiliza a extensão mysql ou mysqli para acessar o banco de dados;
  • $charsets: caminho para o diretório onde ficam os charsets do MySQL. Para saber o caminho no seu sistema, logue no MySQL e dê o comando: SHOW VARIABLES LIKE 'character_sets_dir'.

Salve o arquivo PostMergeInstructions.txt.example como PostMergeInstructions.txt.

Dump

Para fazer um dump, basta chamar o script dump.php na sua linha de comando. Ele irá fazer um dump do banco de dados colocando cada comando SQL em uma única linha. É assim que torna-se possível fazer um diff dos dumps do banco de dados. Ao chamar o script dump.php ($ ./dump.php), você pode passar como parâmetro as strings development, last-merge ou production:

  • development: faz o dump do ambiente de desenvolvimento;
  • last-merge: registra o ponto em que os dumps de desenvolvimento e produção eram semelhantes (você só irá usar esse parâmetro em seu primeiro deployment);
  • production: faz o dump do ambiente de produção.

Então, ao fazer o dump, um arquivo sql com o nome do parâmetro é criado dentro do diretório databases. Você pode passar o parâmetro help para obter a ajuda do comando. O script dump é muito útil durante o desenvolvimento, digamos que você queira testar mudanças em uma View. Antes de qualquer alteração, você faria o dump development, alteraria as configurações necessárias e, caso não obtivesse o resultado desejado, faria o restore do dump para recuperar a configuração original.

Merge

É aqui que tudo fica mais interessante. Durante o merge, o dbscripts junta as diferenças entre os dumps development.sql e production.sql (usando o last-merge como ponto de comparação). O conteúdo de production.sql é preservado (nodes, taxonomia, usuários) enquanto em development.sql somente é preservado a configuração (módulos instalados, configuração dos módulos).

Para fazer um merge, é necessário ter os três dumps: development.sql, last-merge.sql e production.sql. Em seu primeiro deployment, você pode simplesmente fazer um dump para os três arquivos. É aqui que mora o truque: antes de fazer seu primeiro deployment, você deve determinar um range diferente para o ambiente de produção. O que seria isto? É alterar o valor do campo id da tabela sequences para um valor alto (algo acima de 10.000). Dessa forma, você teria nodes que iriam de 0 à 9.999 em desenvolvimento e acima disso para produção e poderia criar nodes à vontade para os testes de uma nova feature, criação de uma nova View, entre outros.

Só que não são todos os ids que devem ter o valor aumentado. Algumas configurações de módulos também confiam na tabela sequences, como é o caso do módulo ImageCache. Caso você mudasse o valor da id das sequences do imagecache, você sempre teria que criar as novas ações em produção. Seria novamente duplicar trabalho. Vejamos um exemplo de sequences, no momento do dump de development (esquerda) e antes do primeiro dump de production (direita):

Sequences para development (esquerda) e production (direita)

Sequences para development (esquerda) e production (direita)

Percebam que não alterei o valor de id para as sequences de imagecache, location, views e vocabulary. Isso quer dizer que todo novo preset de imagecache, nova view ou vocabulário terá que ser criado em desenvolvimento senão, ao fazer o merge, perderei os dados pois serão substituídos pelos de produção ou ocorrerá um conflito.

Nos deployments subseqüentes, você faria primeiro um dump de production, traria o dump para o seu ambiente de desenvolvimento ou teste, faria o dump de development e aí sim a operação merge. Após o merge ter concluído com sucesso, testaria o site e após isso, faria um commit do diretório databases, um update na cópia de trabalho em produção e um restore (mais adiante) do arquivo production.sql. Em desenvolvimento, você faria um restore do arquivo development.sql ou last-merge.sql.

O módulo já define bastante tabelas em que os dados devem ser preservados no array $tables_content (arquivo config.inc). No entanto, dependendo do módulo que você instale, será necessário adicionar a tabela ou tabelas neste array. Um exemplo é o módulo Webform, que tem quatro tabelas de conteúdo. Para adicionar as tabelas do módulo webform, basta adicionar duas entradas no array $tables_content: ‘webform’ e ‘webform_.*’.

Caso ocorra algum conflito, é apresentado os comandos necessários para que você os resolva de forma manual ou automática (seria utilizar como padrão os valores de produção).

O merge é sempre o mais demorado dos scripts (uns 2 minutos para uma base dumpada de 10 MB), eu sugiro que você tenha café por perto =D

Restore

Restore simplismente recupera um dump para o banco de dados. Recebe como parâmetro o nome do arquivo sql desejado ou help. Existem outras opções que lidam com a filtragem de sequences e de dados de sessão. Geralmente as opções de manuseamento filtro e de sequences são feitas automaticamente pelo dbscripts de acordo com o ambiente especificado (development ou production). O módulo já vem bastante otimizado para estas tarefas!

Erase

Apaga o banco de dados.

Lidando com os campos CCK

Para os campos criados pelo módulo CCK há um porém: você deve exportar as definições de campo de desenvolvimento e importá-las em produção antes de fazer o dump, senão, durante a operação de merge, ocorrerá o erro MySQL Column count doesn’t match value count. Isto ocorre porque ao criar um campo CCK, ele adiciona uma coluna em uma tabela content_type_<nome_tipo_conteudo>. Como este campo não está presente em desenvolvimento, o dump traz o schema com mais campos que apresentados nas instruções INSERT. Vamos dizer que temos um tipo de conteúdo chamado página e que adicionamos em desenvolvimento o campo field_pagina_teaser, sem importar o campo para produção, nosso merge criaria um SQL assim:

CREATE TABLE IF NOT EXISTS `content_type_pagina` (
`vid` int(10) unsigned NOT NULL default '0',
`nid` int(10) unsigned NOT NULL default '0',
`field_pagina_peso` int(11) NOT NULL default '0',
`field_pagina_teaser` varchar(255) NOT NULL default '',
PRIMARY KEY  (`vid`),
KEY `nid` (`nid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `content_type_pagina` (`vid`, `nid`, `field_pagina_peso`, `field_pagina_teaser`) VALUES
(30, 15, 10),
(31, 12, 15);

Aplique o patch que vem com o dbscripts no módulo CCK. Ele permite que alterações feitas em campos CCK possam ser importadas.

Mãos na massa

Vamos a um exemplo bem simples de uso: o primeiro deployment. Primeiro, faremos um checkout do Drupal pelo CVS e depois faremos o controle de versão pelo SVN (leia este ótimo tutorial do Diego Albuquerque caso não saiba usar o SVN).

Faça duas instalações Drupal no seu ambiente de desenvolvimento. A primeira chamaremos de drupaldev e a segunda de drupalprod (que será nosso site de “produção”):

Checkout do Drupal:
eriksen@kt:~$ cd /var/www/
eriksen@kt:/var/www$ mkdir drupaldev
eriksen@kt:/var/www$ mkdir drupaldev/files
eriksen@kt:/var/www$ mkdir drupalprod
eriksen@kt:/var/www$ cvs -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal checkout -d drupaldev/ -r DRUPAL-5-10 drupal

Criando repositório local e colocando o projeto sobre revisão:
eriksen@kt:/var/www/drupaldev$ svnadmin create /home/eriksen/drupaldev
eriksen@kt:/var/www/drupaldev$ svn mkdir -m "Criação do layout" file:///home/eriksen/drupaldev/code file:///home/eriksen/drupaldev/databases
eriksen@kt:/var/www/drupaldev$ svn import -m "Importação inicial" . file:///home/eriksen/drupaldev/code
eriksen@kt:/var/www/drupaldev$ rm -rf *; rm .htaccess
eriksen@kt:/var/www/drupaldev$ svn co file:///home/eriksen/drupaldev/code .

Acesse em seu navegador o endereço http://localhost/drupaldev e configure o Drupal. Escolha mysqli como database type e dê o nome drupaldev para o banco.

Crie sua primeira conta e adicione algum conteúdo a seu site. Crie um vocabulário chamado Tags (free tagging) e o habilite para os tipos Story e Page. Coloque algumas tags nos seus posts. Não instale nenhum módulo por enquanto.

Vamos agora utilizar o módulo dbscripts (iremos usar em outro diretório, fora de /var/www):
eriksen@kt:/opt$ cd /opt
eriksen@kt:/opt$ mkdir drupaldev
eriksen@kt:/opt$ cvs -d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal-contrib checkout -d drupaldev/ -r DRUPAL-5--1-0 contributions/modules/dbscripts
eriksen@kt:/opt$ mkdir -p drupaldev/databases/tmp

Configure o arquivo config.inc.example e salve-o como config.inc. Faça o mesmo com o PostMergeInstructions.txt.example. Após isso, faremos nosso primeiro deployment:
eriksen@kt:/opt/drupaldev$ ./dump.php
Dumped the database to development with full filtering options.
eriksen@kt:/opt/drupaldev$ ls -lh databases/
total 40K
-rw-r--r-- 1 eriksen eriksen  36K 2008-09-28 14:57 development.sql
drwxr-xr-x 2 eriksen eriksen 4,0K 2008-09-28 14:53 tmp

Ótimo. Lembra-se do last-merge. Faça o dump de um (nada mais é que um dump de development).
eriksen@kt:/opt/drupaldev$ ./dump.php last-merge
Dumped the database to last-merge with full filtering options.
eriksen@kt:/opt/drupaldev$ ls -lh databases/
total 76K
-rw-r--r-- 1 eriksen eriksen  36K 2008-09-28 14:57 development.sql
-rw-r--r-- 1 eriksen eriksen  36K 2008-09-28 14:59 last-merge.sql
drwxr-xr-x 2 eriksen eriksen 4,0K 2008-09-28 14:53 tmp

Agora, antes de subirmos nosso site para produção, iremos alterar os valores de id da tabela sequences como a seguir:

Sequences para o nosso teste

Sequences para o nosso teste

Pronto, faça o dump de produção:
eriksen@kt:/opt/drupaldev$ ./dump.php production
Dumped the database to production with full filtering options.

Vamos colocar em controle de versão:

eriksen@kt:/opt/drupaldev$ svn import -m "Import do banco" databases/ file:///home/eriksen/drupaldev/databases
Adicionando    databases/development.sql
Adicionando    databases/tmp
Adicionando    databases/production.sql
Adicionando    databases/last-merge.sql
Commit da revisão 3.
eriksen@kt:/opt/drupaldev$ cd databases
eriksen@kt:/opt/drupaldev/databases$ rm -rf *
eriksen@kt:/opt/drupaldev/databases$ svn co file:///home/eriksen/drupaldev/databases .

Vamos simular nosso ambiente de produção agora (estes passos seriam feitos em um servidor de testes ou em produção mas faremos em uma mesma máquina somente para nossos testes – como é que as pessoas escreviam antes de inventarem os parênteses?):
eriksen@kt:/var/www/drupalprod$ svn co file:///home/eriksen/drupaldev/code .

Crie um novo banco (já que estamos testando tudo em uma única máquina) com o nome drupalprod. Altere os parâmetros de configuração no arquivo /var/www/drupalprod/sites/default/settings.php para que esta instalação use o banco drupalprod (variável $db_url). Vamos agora trabalhar com o dbscripts novamente: copie o diretório /opt/drupaldev e altere o $settings_path do config.ini para /var/www/drupalprod/sites/default
eriksen@kt:/opt$ cp -R drupaldev/ drupalprod; cd drupalprod
eriksen@kt:/opt/drupalprod$ vim config.inc
eriksen@kt:/opt/drupalprod$ ./restore.php production
Restored the database preserving the full option of tables

Acesse http://localhost/drupalprod e veja com seus próprios olhos! Crie alguns nodes a mais e repare no id que eles recebem. Agora, depois de adicionar algum conteúdo faça um dump e atualize o SVN:
eriksen@kt:/opt/drupalprod/databases$ svn ci  -m "Banco de dados de produção: 2008-09-28 13:20"
Enviando       production.sql
Transmitindo dados do arquivo .
Commit da revisão 4.

Vamos agora habilitar o módulo Search no nosso site drupaldev. Mude as configurações do módulo como a relevância das palavras e execute o cron manualmente para indexar a busca. Como não instalamos nenhum módulo contribuído, não precisamos fazer um commit do código, vamos apenas utilizar o dbscripts mais uma vez:
eriksen@kt:/opt/drupaldev/databases$ svn update; cd ..
eriksen@kt:/opt/drupaldev$ ./merge.php
Restoring production database... Done.
Preparing temporary files............................................................................................................... Done.
Performing merge of data... Successful!
Preparing final files............ Done.
Merge completed successfully.  Congrats! Pat yourself on the back.
eriksen@kt:/opt/drupaldev$ cd databases
eriksen@kt:/opt/drupaldev/databases$ svn ci -m "Instalação e configuração do módulo Search"
Enviando       development.sql
Enviando       last-merge.sql
Enviando       production.sql
Transmitindo dados do arquivo ...
Commit da revisão 5.

Agora, em produção:
eriksen@kt:/opt/drupalprod/databases$ svn update
U    production.sql
U    development.sql
U    last-merge.sql
Atualizado para revisão 5.
eriksen@kt:/opt/drupalprod/databases$ cd ..
eriksen@kt:/opt/drupalprod$ ./restore.php production
Restored the database preserving the full option of tables

Navegue em seu site de “produção”. Perceba que o módulo Search está habilitado e configurado. Faça uma busca, você perceberá no entanto, que não há conteúdo indexado. Isso se deve as diferenças de conteúdo entre desenvolvimento e produção. Execute o cron e a sua busca estará indexada.

Em desenvolvimento, você terá suas novas configurações mantidas mais o conteúdo de produção. Com a prática você verá que estas operações ficam simples. Antes de aplicar o restore em produção, lembre-se sempre de fazer um backup do banco de dados atual e de seu diretório files!

Conclusões

O módulo dbscripts permite mater um histórico do desenvolvimento de um site em Drupal. Apesar de seu uso não ser trivial (guardar dumps em um repositório de código), seu uso é comprovadamente eficiente. O ponto a favor é que você pode retornar o seu site pelas diferentes revisões e os contras é que somente está disponível para Drupal 5 (quem quiser ajudar no port para o 6, veja a issue queue do módulo) e que ainda não escala bem para grandes bancos de dados.

O processo de deployment é trabalhoso para qualquer CMS (no Plone, segundo meu amigo Rogerio não há nada que faça esse migração de configuração entre ambientes de desenvolvimento e produção – fica a cargo do desenvolvedor importar os archetypes, o CCK deles). No Drupal, as discussões sobre o deployment estão evoluindo e novos módulos (AutoPilot, Backup and Migrate, Patterns – este último é muito promissor) estão sendo criados. Quanto mais discussão e troca de experiências acontecerem, métodos melhores e mais rápidos aparecerão.

Confira a documentação do módulo e não deixe de dar uma lida nos posts da Kathleen sobre deployment Drupal pois são muito bons! O post ficou longo mas espero que ajude alguém a pensar mais sobre o assunto e quem sabe ajudar com melhorias!

That’s all folks!

Etiquetado como:, ,

2 Respostas

Subscreva aos comentários comRSS.

  1. [...] por Eriksen Costa (eriksencostaΘgmail·com) – referência [...]

  2. ulissescastro said, on Setembro 30, 2008 at 4:24 am

    Execelente artigo guerreiro, continue assim!

    Abraços!


Deixe uma resposta