Logos do Docker, MariaDb e Wordpress

Esse blog foi migrado para uma máquina mais nova. É um WordPress, rodando numa máquina no Azure. Ele estava inicialmente numa máquina pequena, e conforme a audiência foi crescendo, a máquina cresceu junto. Mas ainda estava no modelo antigo de gestão do Azure (ASM), e a máquina era menos eficiente que as mais novas, que tem SSD, entre outros pontos. Resolvemos migrá-lo para o modelo ARM e máquinas melhores. No processo, oras, por que não colocá-lo pra rodar num contêiner com Docker. Foi exatamente o que fizemos, e este post é pra contar pra vocês como fazer isso. Parecia meio complicado a princípio, mas foi bem tranquilo no final.

WordPress em contêiner e suas peculiaridades

O WordPress possui um repositório oficial no Docker Hub, e as imagens estão sempre taggeadas de acordo com a versão do WordPress que elas representam. Na home do repo há até indicações de como rodar um contêiner já com o MySql, e até um exemplo com docker-compose. Só que contêineres WordPress são um pouco peculiares. É importante eu fazer essa introdução para que algumas decisões que tomamos fiquem mais claras.

O WordPress possui um banco de dados pros dados estruturados, e um diretório wp-content para armazenar todo conteúdo, como imagens e arquivos em geral. É nesse diretório também que ficam os temas e plugins. Isso é ótimo, porque assumimos que tudo que está fora desse diretório é do engine do WordPress, e esse diretório mantém o estado do blog em si. Entretanto isso não é verdade. Não é incomum plugins e temas instalarem recursos em outros diretórios do WordPress. Se todo conteúdo ficasse somente no diretório wp-content seria fácil: o diretório seria montado a partir de um volume, e poderiamos trocar a imagem de base quando quiséssemos atualizar o WordPress. Mas, como há conteúdo fora desse diretório, isso não é possível.

Outro problema é o próprio sistema de atualizações do WordPress. Ele é capaz de atualizar não só plugins, mas o WordPress em si. E quando o WordPress é atualizado não somente os arquivos diversos do WordPress são atualizados (todos os que não estão no diretório wp-content), mas também o banco de dados. Ou seja, trocar a imagem de base do WordPress gerará um problema de inconsistência com a base de dados.

Isso quer dizer que o volume do Docker para o WordPress não pode ser somente o diretório wp-content, mas todo o conteúdo do WordPress, inclusive os arquivos do WordPress em si, e não só o conteúdo do blog. O que fica estranho depois é que, ao inspecionar o contêiner você pode ver a versão, por exemplo, 4.2.0, mas na verdade a versão que está rodando já foi atualizada, e agora é a 4.4.0. Mas não há como resolver esse problema. O próprio repo do Docker Hub te direciona a isso. A imagem de base e a versão servem somente para fazer o bootstrap inicial do WordPress, depois disso, não significam nada.

O interessante é que, se você montar o diretório onde o WordPress vai rodar, que é o /var/www/html (ele baseado na imagem de Php, e no caso estamos falando de Apache2), o contêiner vai respeitar isso, e não vai sobrescrever nenhum arquivo. Guarde essa informação.

O banco de dados é um conteiner simples de MySql. Pode ser também um MariaDb, que é um fork feito pela comunidade quando o MySql foi parar na mão da Oracle (tire suas próprias conclusões do porquê) plenamente compatível, e é o que estou usando. A escolha é sua. Tanto o MariaDb quando o MySql possuem repositórios oficiais no Docker Hub.

Preparando o banco de dados

O primeiro passo é fazer um dump do MySql/MariaDb que contém o banco de dados atual do WordPress. Isso é bem simples, basta rodar, no server, o mysqldump:

mysqldump -u <usuario> -p<Senha> <nomedobancodedados> > /caminho/pro/backup.sql

Você também pode fazer com um server remoto, veja mais nos docs do mysqldump pra ver as opções. Isso vai gerar um dump, que você vai poder usar pra restaurar depois. É um arquivo de texto simples, você pode abrir ele pra ver o que ele gerou. No entanto, ele não contém o nome do banco, assim, abra o arquivo, e na primeira linha coloque:

use <nomedobancodedados>;

Escolhi montar o contêiner do MariaDb com um volume montado a partir do file system do host. Poderia ser também com um volume do Docker direto, não montado no host, não faria diferença. Vamos criar um server MariaDb que inicialmente não conterá nenhum dado, depois que ele subir eu vou acessá-lo e restaurar o dump, o que vai gerar os dados no volume. Assim, rodei:

docker run --name mariadbtemp -e MYSQL_ROOT_PASSWORD=<senha> -v /data/blog/mysql/:/var/lib/mysql/ -v /tmp/dump/:/backup/ -ti -d mariadb

O diretório do contêiner /var/lib/mysql é onde o MariaDb coloca os dados, e montei esse diretório no host em /data/blog/mysql. E meu dump estava em /tmp/dump que montei no contêiner no diretório /backup. Depois disso, basta acessar o contêiner, que já estava rodando:

docker exec -ti mariadbtemp /bin/bash

Com isso estamos com um terminal dentro do contêiner que está rodando o banco de dados. Agora vamos acessar a console do MariaDb e criar o banco. Digite:

mysql -u root -p

Em seguida, digite sua senha. Você deve estar na console.

console com saída do mariadb

Então, crie o banco de dados:

create database <nomedobancodedados>;

E crie um usuário para acessar o banco de dados do WordPress. É importante o uso do @'%' que identifica o usuário porque o acesso vai vir pra um ip/host que você não controlará. Se colocar @'localhost' não vai funcionar:

GRANT ALL PRIVILEGES ON *.* To '<nomedousuario>'@'%' IDENTIFIED BY '<senha>';

(Você provavelmente não quer dar todos os privilégios a esse usuário, estude os docs para dar as permissões mais apropriadas ao seu cenário.)

Então saia da console, e importe o arquivo de dump que foi montado no diretório /backup:

mysql -u root -p < /backup/backup.sql

Com isso feito, você já pode matar esse contêiner. A essa altura o banco já foi restaurado com sucesso e os arquivos já estão no volume. Não queremos manter esse mesmo contêiner porque que tem um diretório de backup montado.

docker rm -f mariadbtemp

E assim a parte do banco está concluída.

Preparando o WordPress

O WordPress é bem simples de se preparar. Eu resolvi montar uma imagem própria porque eu tinha configurações a fazer no Php e no Apache, assim, criei esse Dockerfile:

FROM wordpress:4.7.1-apache
COPY config/php.ini /usr/local/etc/php/conf.d/
COPY config/default-ssl.conf /etc/apache2/sites-enabled/default-ssl.conf
VOLUME /etc/ssl/certs/wordpress/
VOLUME /etc/ssl/private/wordpress/
RUN a2enmod ssl

A notar:

  • Estou usando o WordPress com Apache
  • Tenho um arquivo php.ini pra configurar o php sendo montado no local indicado no repositório do Docker Hub do Php;
  • Tenho um arquivo default-ssl.conf sendo colocado como um site habilitado no Apache, ele vai ser responsável por habilitar SSL na minha aplicação;
  • Estou colocando como volumes os diretórios pra subir os certificados SSL e a chave;
  • Estou habilitando o SSL no Apache.

Se você não quiser usar SSL e não quiser configurar o Php, pode usar direto a imagem de base do wordpress:4.7.1-apache (ou até outra, há opções com Alpine e FPM, por exemplo).

Meu arquivo de Php.ini possui somente 3 configs, para o tamanho do upload e tempo de execução:

upload_max_filesize = 10M
post_max_size = 10M
max_execution_time = 30

Além disso, você vai precisar observar o arquivo wp-config.php e notar se precisa alterar algo. Minhas informações do banco de dados mudaram. Especificamente a variável DB_HOST, vai mudar, utilize mysql para o novo nome. Vamos linkar o contêiner do MariaDb com esse nome.

Eu também precisei alterar as variável WPCACHEHOME porque eu uso o plugin WP Super Cache (que é muito bom, por sinal), e essa variável aponta o local do cache. O novo local ficou como: /var/www/html/wp-content/plugins/wp-super-cache/. E esse plugin mantinha o local também no arquivo wp-content/wp-cache-config.php, que também precisei alterar.

Tive problemas com permissões nos arquivos. Como os arquivos já existem e vão ser montados a partir do file system do host, o Apache do contêiner não conseguia acessar. No host, tive que alterar o owner dos arquivos e diretórios para www-data. Isso é no host, não no contêiner, e é antes do contêiner subir. Vá ao diretório onde os arquivos do WordPress estão e rode:

chown -R www-data:www-data /caminho/para/arquivos/do/wordpress

Executando com docker-compose

Não vamos rodar tudo na mão, né? Pra isso tempos o docker-compose. Aqui o meu compose file:

version: '3'
services:
  wordpress:
    image: custom-wordpress
    build: .
    container_name: my-wordpress
    environment:
      WORDPRESS_DB_USER: ${DB_USER}
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: ${DB_NAME}
    links:
      - db:mysql
    ports:
      - 80:80
      - 443:443
    volumes:
      - /data/blog/html/:/var/www/html
      - /caminho/para/certs/public/:/etc/ssl/certs/wordpress
      - /caminho/para/certs/private/:/etc/ssl/private/wordpress
    restart: always
  db:
    image: mariadb
    container_name: www-mariadb
    environment:
      MYSQL_ROOT_PASSWORD: ${ROOT_DB_PASSWORD}
    volumes:
      - /data/blog/mysql/:/var/lib/mysql
    restart: always

Pra criar os contêineres a partir da configuração do compose basta rodar:

docker-compose up -d

A notar:

  • O ambiente precisa ter 4 variáveis de ambiente configuradas, as senhas do banco, o usuário e o nome do banco de dados;
  • O link nomeado do WordPress pro MariaDb, com nome mysql, o que permitiu usar esse nome na variável do wp-config.php que mencionei anteriormente;
  • Os diretórios montados pro WordPress, tanto o que contém os arquivos do wordpress que já estavam no blog (que coloquei em /data/blog/html) e os dos certificados, conforme o Dockerfile visto anteriormente;
  • O diretório montado pro MariaDb, apontando pros dados que salvamos antes;
  • O restart: always, fundamental para que os contêineres subam junto com o host, no caso de um reinício do servidor.

Resultado

Tudo funcionou perfeitamente. Testei na minha máquina primeiro, alterando o arquivo hosts pra ver se funcionava. Com o sucesso, alterei o DNS do blog pra apontar para o novo local. No Azure, configurei a política do Network Security Group para abrir somente as portas 80 e 443.

O resultado é que uma máquina menor está com um desempenho melhor. Aqui os testes do Application Insights, que testam a app a cada 5 minutos. Notem que na madrugada do dia 26/1 o desempenho melhora significativamente.

Estatísticas de um dia do Application Insights mostrando que o tempo de resposta caiu pela metade, com o tempo em 814ms na média

Fiz ainda um outro teste. Derrubei os contêineres, e subi do zero. Como os dados estavam em volumes, nada deveria mudar. Foi exatamente o que aconteceu. Confiança total.

Tenho agora alguns outros sites bem pequenos (perto do blog da Lambda3) pra migrar. Vou avaliar como vou entregá-los usando o mesmo host, já que antes o Apache cuidava disso. Provavelmente vou rotear pelo Azure e usar portas diferentes.

A configuração do servidor foi feita com 4 discos SSD em RAID com stripe, utilizando mdadm. Desempenho excepcional, com 4TB de espaço, e custo ridiculamente baixo (só pago pelo que uso). O storage do Azure ainda me garantiu replicação em 3 locais diferentes (sem que eu precise fazer nada para isso), com garantias absurdas de precisão e segurança na persistência.

Os logs estão direcionados via fluentd para o OMS, que também roda como SASS no Azure. Aqui um dos relatórios que ele me dá, entre os diversos logs, alertas, etc. E é super simples de configurar, é uma linha no terminal do host.

OMS com gráficos de desempenho de CPU, memória e disco livre

E o servidor que hospeda tudo está limpo. Nada de Apache, MariaDb, etc, instalado. Ele roda somente uma coisa: Docker. E pra que precisaria de mais?

Conteiner all the things!

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.