Tuesday, June 30, 2015

Integrando o Docker com Maven e Gradle

Para otimizar o processo de build em aplicações Java é possível incluir no ciclo de construção uma etapa para construir imagens do Docker. Isso pode ser feito no Maven ou Gradle através de plugins.

Docker no build do Maven

Trecho do pom.xml com o plugin do Docker:
...

<properties>
  <docker.image.prefix>yaw</docker.image.prefix>
</properties>

...

<build>
  <plugins>
    <plugin>
      <groupId>com.spotify</groupId>
      <artifactId>docker-maven-plugin</artifactId>
      <version>0.2.3</version>
      <configuration>
        <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
        <dockerDirectory>src/main/docker</dockerDirectory>
        <resources>
          <resource>
            <targetPath>/</targetPath>
            <directory>${project.build.directory}</directory>
            <include>${project.build.finalName}.jar</include>
          </resource>
        </resources>
      </configuration>
    </plugin>
  </plugins>
</build>

O pessoal do Spotify desenvolveu o plugin docker-maven-plugin. A propriedade docker.image.prefix é usada como prefixo para o nome da imagem, nesse caso yaw. O restante é o próprio nome do artefato. Na tag dockerDirectory indico o diretório aonde fica contido o arquivo Dockerfile, com as diretrizes para geração da imagem. Na tag resources indicamos os recursos que são compartilhados na execução do Docker.

Para criar a imagem Docker, a partir do ciclo de build do Maven, executamos o seguinte comando:
mvn clean package docker:build

Nesse caso a imagem é gerada após a construção do artefato (package). Outra abordagem seria configurar o Maven para construir a imagem implicitamente a partir do mvn package. Para isso é necessário customizar a tag execution com phase e goals.

A pŕoxima etapa seria criar o Container Docker e executar a aplicação. Vou demonstrar isso depois de apresentar o plugin do Gradle.

Docker no build do Gradle

Trecho do build.grade com plugin do Docker:
buildscript {
  ...
  dependencies {
    ...
    classpath('se.transmode.gradle:gradle-docker:1.2')
  }
}

...

group = 'yaw'
apply plugin: 'docker'

task buildDocker(type: Docker, dependsOn: build) {
  push = false
  applicationName = jar.baseName
  dockerfile = file('src/main/docker/Dockerfile')
  doFirst {
    copy {
      from jar
      into stageDir
    }
  }
}

No Gradle usei o plugin gradle-docker. Nele indico o caminho do Dockerfile, o prefixo e o nome da imagem. A próxima etapa é gerar a imagem em conjunto com o build Gradle:
gradle build buildDocker

Como referência é possível visualizar exemplos desses dois builds no projeto querydsl-spring-data, no meu Github. Esse projeto utiliza o Spring Boot, com Tomcat embutido. A seguir eu coloco o conteúdo do Dockfile utilizado para construir a imagem para executar esse projeto:
FROM edermag/ubuntu-java-8-dev

VOLUME /tmp
ADD querydsl-spring-data.jar app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

A imagem herda a estrutura com o JDK 8 da Oracle no Ubuntu 14.04, e faz o startup do projeto com Spring Boot. A última etapa é criar o Container a partir da imagem construida via Maven ou Gradle. Execute a instrução:
docker run -p 8080:8080 -t yaw/querydsl-spring-data

A aplicação será inicializada (Spring Boot) dentro do Container Docker, usando a porta 8080. Para encerrar o Container será necessário executar o comando docker stop indicando o apelido do Container.

Como referência indico o guia do Spring IO, descrevendo como usar o Docker com Spring Boot.

www.yaw.com.br

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