Desenvolvimento

16 ago, 2018

Meu ambiente de desenvolvimento com Docker

Publicidade

Bom, pessoal, comecei a programar há muitos anos e no decorrer desse tempo tenho tentado encontrar um formato adequado para meu sistema. Neste artigo vou tentar mostrar um pouco do estado atual.

Colocando um pouco de contexto, passei a utilizar Linux por volta de 1994. Até 2006, mudei diversas vezes de distribuição, até que conheci o Fedora – que na época se chamava Fedora Core, que utilizo até hoje.

Se você trabalha com Linux, sabe que temos dois modelos de distribuição: as rolling release e as fixed release; a última, fixed release, é a mesma adotada pelo Fedora. Isso significa que de seis em seis meses, mais ou menos, o sistema é atualizado; o que também significa que de seis em seis meses eu formato minha máquina, reinstalo o sistema, ambiente de desenvolvimento, etc.

Formatar a máquina e instalar a distro tendo o menor impacto possível sempre foi problema, já que formatar a máquina e reinstalar o sistema, quando se trabalha com diversos projetos, chaves públicas/privadas, gpg, códigos, ambientes configurados, é bastante problemático; especialmente para voltar a trabalhar.

Para simplificar a restauração do sistema, eu criava diversas partições; essa foi minha primeira medida e esse era meu layout inicial:

  • swap
  • /
  • /boot
  • /usr
  • /var/www/html
  • /src
  • /opt
  • /home

Por alguns anos esse foi o layout de partições que eu usei; funcionou bem por algum tempo, especialmente quando eu trabalhava com apenas uma tecnologia e tinha que configurar apenas um ambiente. Mas conforme o tempo foi passando, foi se tornando complicado ter diversos ambientes de desenvolvimento configurados; eu tinha o código, mas precisava – ainda – configurar outras coisas: servidor, banco de dados, linguagens/tecnologias/plataformas, etc.

Imagine só: se você trabalha com php, você precisa de um servidor web, um servidor de banco de dados, além da própria linguagem; aí precisa lidar com alguma coisa no front – ou mesmo renderizar a coisa –, instalar um npm e mais um monte de dependências, mas em determinado projeto você precisa usar Ruby, aí vai lá e instala o Ruby e um Capistrano; aí noutro projeto você precisa usar Ruby numa versão específica e você vai lá e instala um rvm para gerir essas versões. O mesmo ocorre com Java e qualquer que seja a tecnologia de trabalho.

Ter um layout específico para facilitar após uma formatação, deixou de ter o mesmo efeito que no passado, porque agora eu tinha o código, mas também precisava configurar o ambiente, e configurar um ambiente não é uma coisa que se faz rapidamente; muitas vezes essa configuração é complexa, demorada e já me fez desistir algumas vezes de atualizar o sistema, porque eu não queria ter que configurar o ambiente novamente.

Como aquele monte de partição havia perdido razão de ser, ela foi simplificada, e hoje tenho:

  • /
  • /boot/efi
  • /home
  • /src

Meu código ficou concentrado em /src e uso git com alguns remotes: tenho um remote na minha rede, outro num Github, Gitlab, Bitbucket ou qualquer que seja o caso, etc. Meu /home ficou com minhas coisas pessoais, como chave ssh, gpg, algumas autenticações, etc. Já o ambiente, que antes era complexo e problemático, fica em imagens num registry na minha rede. Por exemplo; vamos supor que eu vá trabalhar com PHP e precise da linguagem, de um servidor web e de um servidor de banco de dados:

mkdir -p /src/sample/php/src
cd /src/sample/php
git init

echo -e "<?php\necho 'PHP é legal.';">src/index.php

A partir daqui, tenho um diretório de trabalho e um arquivo index.php, mas como acabei de formatar a máquina, não tenho servidor web, banco de dados, nem nada. Em vez do ambiente da linguagem, instalo Docker:

sudo dnf -y install docker docker-compose

Com o compose instalado, vamos por partes; primeiro resolvemos o servidor – no caso, vou usar nginx:

version: "3"
services:
  nginx:
    image: nginx
    ports:
    - "80:80"

Isso basicamente diz que estamos usando a versão 3 do arquivo de configuração do docker-compose e que, entre os serviços, temos um que demos o nome de nginx e usaremos a imagem nginx. Como não informamos nenhuma tag, é feito o download da latest, ou última. Estamos dizendo que a porta 80 da nossa máquina é mapeada para a porta 80 do container, ou seja, se acessarmos nosso browser agora, vamos ver a página de boas vindas do nginx:

Welcome to nginx!

Agora precisamos fazer o PHP funcionar! Para isso, precisamos:

  • 1. Configurar o nginx para trabalhar com o PHP e,
  • 2. Configurar o container para usar nosso código PHP:
mkdir nginx
vim nginx/site.conf

nginx/site.conf

server {
  index index.php index.html;
  root /var/www/html;

  error_log /dev/stdout;
  access_log /dev/stdout;

  location ~ \.php$ {
    try_files $uri =404;

    fastcgi_pass php:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}

Nosso arquivo de configuração do nginx está instruindo o servidor a ter como index, o index.php ou index.html; também está direcionando os logs para stdout – pessoalmente gosto disso, pois me permite ver o que está acontecendo sem precisar acessar o container para ler os logs; finalmente, se o arquivo acessado for “algumacoisa.php“, ele passa a requisito para o php-fpm – que estará na porta 9000 – lidar com ela.

Com o nginx configurado, o docker-compose.yml é ajustado:

version: "3"
services:
  nginx:
    image: nginx
    ports:
    - "80:80"
    links:
    - php
    volumes:
    - ./nginx:/etc/nginx/conf.d:ro
    - ./src:/var/www/html:ro
  php:
    image: php:7.2.8-fpm
    volumes:
    - ./src:/var/www/html:z

Agora temos dois serviços: o nginx e o php e no nginx temos duas mudanças:

  • 1. Link para o php
  • 2. Mapeamento de volumes.

O links vai permitir que o container do nginx consiga conversar com o container do php; se observar no arquivo de configuração do nginx, verá que fizemos php:9000; isso só foi possível porque fizemos o link. Na sequência, fizemos o mapeamento dos volumes; o diretório nginx que tem nosso arquivo de configuração foi montado em /etc/nginx/conf.d, e nosso src com o código foi montado em /var/www/html. Como precisamos que o código seja compartilhado e acessível pelos dois containers, mapeamos em ambos.

Agora, ao acessar nosso localhost no browser, veremos:

php é legal

Com servidor web e php rodando, falta o MariaDB:

version: "3"
services:
  mariadb:
    image: mariadb
    environment:
    - MYSQL_ROOT_PASSWORD=default123
  nginx:
    image: nginx
    ports:
    - "80:80"
    links:
    - php
    volumes:
    - ./nginx:/etc/nginx/conf.d:ro
    - ./src:/var/www/html:ro
  php:
    image: php:7.2.8-fpm
    links:
    - mariadb
    volumes:
    - ./src:/var/www/html:z

Assim como o nginx, não usamos uma tag de versão para o mariadb; a única coisa diferente ali, é que definimos a variável de ambiente MYSQL_ROOT_PASSWORD com um valor qualquer. Esse valor será utilizado para conexões como root para instalar o banco; para uma aplicação, são definidas outras variáveis, como MYSQL_DATABASE, MYSQL_USER e MYSQL_PASSWORD, mas para o propósito do artigo, só o MYSQL_ROOT_PASSWORD resolve.

Bom, agora ajustamos o código PHP para conectar nessa base:

src/index.php

<?php
$pdo = new PDO('mysql:host=mariadb;dbname=mysql', 'root', 'default123');
$stm = $pdo->query('SELECT <code>User</code>, <code>Host</code> FROM <code>user</code>;');

foreach ($stm->fetchAll(PDO::FETCH_OBJ) as $row) {
    printf('%s@%s', $row->User, $row->Host);
}

Lembra do link que permitia que o nginx visse o php na porta 9000? Aqui é a mesma coisa: para que o PHP consiga trabalhar com o MariaDB, precisamos fazer o link. Agora, se rodarmos esse código, veremos o seguinte no navegador:

could not find driver

O que aconteceu é que a imagem do PHP que estamos usando não tem a extensão do MySQL que é necessária para trabalhar com o banco de dados. Nesse caso, um Dockerfile que faz a instalação é necessário:

FROM php:7.2.8-fpm

RUN apt-get update && \
    docker-php-ext-install pdo_mysql

Com isso, o docker-compose.yml sofre uma última alteração:

version: "3"
services:
  mariadb:
    image: mariadb
    environment:
    - MYSQL_ROOT_PASSWORD=default123
  nginx:
    image: nginx
    ports:
    - "80:80"
    links:
    - php
    volumes:
    - ./nginx:/etc/nginx/conf.d:ro
    - ./src:/var/www/html:ro
  php:
    image: phpmariadb
    build: .
    links:
    - mariadb
    volumes:
    - ./src:/var/www/html:z

Quando o container for levantar, ele vai ver que a imagem phpmariadb não existe; como temos um build logo abaixo e um Dockerfile, ele vai fazer a construção da imagem em vez de procurá-la no registry. Com a imagem construída e a extenção instalada, o código PHP agora funciona:

php+mariadb+nginx no docker

Agora que tenho uma imagem do PHP 7.2.8 configurada, posso enviá-la para um registry remoto, ou para o meu registry na minha rede local. De qualquer forma, se amanhã eu formatar minha máquina, basta fazer um pull na imagem que tenho o ambiente PHP configurado e pronto para trabalhar.

O mesmo vai acontecer com Node, Ruby, .NET Core e qualquer que seja a tecnologia que eu precisar trabalhar. Em apenas alguns instantes, consigo ter meu código e meu ambiente prontos para trabalhar – mesmo em máquinas distintas da minha. Basta fazer o pull, e pronto!