Monday, June 22, 2015

Explorando o Docker para construir ambientes Java

Containers

Agilidade, produtividade e qualidade são termos constantes no contexto de desenvolvimento de sistemas. O que ao primeiro momento, de forma enganosa, nos remete apenas a escrita de código em si. Na verdade existem diversos outros fatores que impactam diretamente na agilidade de um time de desenvolvimento. Fatores que ultrapassam as fronteiras da escrita de código, como por exemplo o provisionamento da infra-estrutura.

Analisando a questão do provisionamento, a virtualização foi uma das alternativas para reduzir os custos e otimizar a infra-estrutura, principalmente com o crescimento e adoção dos serviços em Cloud Computing (PaaS). Mas existem outras alternativas nesse mesmo campo. O Docker, por exemplo, é uma plataforma para construir e manter ambientes para a excecução de sistemas distribuídos.

A plataforma é formada por diversor módulos, como o Docker Engine e o Docker Hub. Ela opera sob o conceito de containers do LXC sigla de Linux Containers, que atua de forma diferente das máquinas virtualizadas. Ao invés de isolar um o SO inteiro o Linux host compartilha seu kernel para outros processos isolados rotulados como Container. O Docker Engine é uma camada sobre o LXC, e permite que usuários criem e executem um ou vários Containers, simulando um SO 'puro', abaixo de um mesmo kernel compartilhado pelo Linux host. Um processo mais leve do que a virtualização de máquinas.

Dessa forma eu poderia montar dois ambientes Java distintos, na mesma máquina, em poucos passos. Por exemplo: o primeiro Container configurado para executar uma aplicação usando o Java 7, Tomcat e Gradle no CentOS; o segundo Container configurado para executar uma aplicação usando o Java 8, Jetty e Maven no Ubuntu. Esses dois Containers poderiam ser mantidos um Mac OS X Yosemite como host (no Mac OS ou Windows será necessário instalar um boot2docker).

No Docker o Container é tratado como o artefato, ou seja, o ambiente pode ser versionado e distribuído da mesma forma como fazemos com o código fonte da aplicação. Antes de explorar o Docker, é importante compreender como funcionam três tipos de componentes:
  1. Image: uma imagem é um template que define a estrutura para Containers.
  2. Container: o Container simula o ambiente de execução como um todo, é uma 'instância' da imagem.
  3. Repository / registry: registro aonde as imagens são mantidas (versionamento). O Docker Hub é uma central aonde imagens podem ser persistidas e compartilhadas.

Primeiro Container Java

A partir desse ponto irei demonstrar como trabalhar com Containers do Docker, construindo ambientes para execução de aplicações Java. Para simular os passos seguintes é necessário que você instale o Docker e faça o HelloWorld para testar a ferramenta.

No início vou trabalhar com uma imagem do Java 8, disponibilizada (public) no Docker Hub. Essa imagem trata-se de um Ubuntu 14.04 com o JDK versão 8, disponibilizado pelo OpenJDK. Para baixar a imagem do Docker Hub use a instrução:
$ sudo docker pull java:8

No comando docker pull informei o conteúdo java:8, aonde java representa o repositório, : o separador, e 8 a versão. Após concluir o download, é possível verificar a imagem através do comando:
$ sudo docker images

A lista apresenta o repositório, a versão e tamanho da imagem. O id é uma informação que pode ser usada para remover a imagem do host. A próxima etapa é criar um Container a partir dessa imagem. e usar de alguma forma o JDK: 
$ sudo docker run java:8 java -version

O comando docker run cria a instância da imagem, o Container. O conteúdo java:8 indica qual imagem deve ser usada, e por fim a instrução que deverá ser processada pelo container. Utilizei o comando java -version como exemplo. Assim que a versão do Java é impressa na console, o processo Docker é encerrado.

Para listar todos os Containers, ativos ou que foram concluídos, use a instrução:
$ sudo docker ps -a

Note que a primeira linha exibe o Container criado para a imagem java:8, veja o valor na coluna NAMES. Trata-se de um apelido gerado pelo Docker para identificarmos o Container. Caso você execute novamente o comando docker run, um novo container será criado. Para evitar isso basta usar a opção --rm, que descartará o Container no fim da execução.

A próxima instrução criará um Container para executar uma classe Java. Como exemplo usei o clássico HelloWorld, que exibe uma mensagem na console. Importante notar que a classe já foi compilada, e o arquivo class está no filesystem do host, fora do Container:
$ sudo docker run --rm -v "$PWD":/home/user/test -w /home/user/test java:8 
  java OlaDocker

A opção -v indica o volume que é carregado para o Container, e -w o diretório de trabalho. No exemplo as duas opções apontam para o mesmo diretório (/home/user/test), que contém o arquivo OlaDocker.class. Ajuste a instrução indicando o path e o nome adequado da sua classe Java.

Docker para dev Java Web

Próximo passo é executar uma aplicação Java Web com Docker. Para isso vou usar uma imagem que criei com as seguintes característica: Tomcat 8, JDK 8 (Oracle), Maven 3.3 e Ubuntu 14.04. O conteúdo a seguir é um exemplo do Dockerfile, usando a imagem edermag/tomcat-8-dev, disponível no Docker Hub
FROM edermag/tomcat-8-dev

WORKDIR /source

ENV app appJavaWeb
ADD pom.xml /source/pom.xml
ADD src /source/src

RUN mvn clean package && \
    mv /source/target/$app-0.0.1-SNAPSHOT.war $CATALINA_HOME/webapps/          

CMD ["catalina.sh", "run"]

Importante: o Tomcat utiliza a porta 8080 configurada na imagem edermag/tomcat-8-dev.

Esse exemplo assume que o projeto segue o layout do Maven. Sendo assim, o arquivo Dockerfile deve ficar no mesmo diretório do arquivo pom.xml. Nesse arquivo colocamos as instruções para que o Docker construa uma imagem, local, usando como base a imagem edermag/tomcat-8-dev. As instruções são:
  • Criar o diretório /source, e usá-lo como diretório de trabalho (manipulação) do Container;
  • Setar uma variável de ambiente com o nome do artefato gerado pelo Maven;
  • Copiar o arquivo pom.xml do host para a imagem. Também copiar todo o código fonte (diretório src) do host para a imagem;
  • Executar o Maven para construir o projeto e fazer o deploy do WAR no Tomcat;
  • Por fim inicializar o Tomcat;
Para criar a imagem no Docker a partir desse arquivo, execute a seguinte instrução no diretório raiz da aplicação:
$ sudo docker build -t javaweb .

De acordo com a instrução acima, o nome da imagem é javaweb. Durante a geração dessa imagem, o build e o deploy serão realizados. Caso o código seja modificado a imagem deverá ser gerada novamente (nova versão). Após executar o comando acima, verifique novamente as imagens instaladas no host através do comando: docker images. O próximo passo é instanciar o Container da imagem javaweb:
$ sudo docker run -d -p 8081:8080 javaweb

Com essa instrução o Docker cria o Container em modo Daemon, via opção -d, e aloca a porta 8081 para redirecionar a porta 8080 da imagem, via opção -p. Verifique o NAME (apelido) do Container através do comando: docker ps.

Como o build e deploy são realizados durante a geração da imagem, a execução do Container irá inicializar o Tomcat. Para visualizar os logs do Tomcat execute a instrução (use o NAME do Container):
$ sudo docker logs {coloqueOConteudoDoNAME}

Para encerrar a execução do Container execute a instrução:
$ sudo docker logs {coloqueOConteudoDoNAME}

Com pequenos ajustes seria possível criar uma imagem (e Container) para executar a aplicação Java Web no Jetty.

www.yaw.com.br

No comments: