Seguindo a lista do modelo 12factor, temos “Construa, lance, execute” como quinta boa prática.
Em um processo de automatização de infraestrutura de implantação de software, precisamos ter alguns cuidados para que o comportamento do processo esteja dentro das expectativas e que erros humanos causem baixo impacto no processo completo do desenvolvimento ao lançamento em produção.
Visando organizar, dividir responsabilidade e deixar o processo mais claro, o 12factor indica que o código base para ser colocado em produção deva passar por três fases:
- Construa é quando o código do repositório é convertido em um pacote executável. É nesse processo onde se obtém todas as dependências, compila-se o binário e todos os ativos desse código;
- Lance é quando o pacote produzido pela fase de construir é combinado com sua configuração. O resultado é o ambiente completo, configurado e pronto para ser colocado em execução;
- Execute (também como conhecido como “runtime”) é quando um lançamento (aplicação + configuração daquele ambiente) é colocado em execução, iniciado com base nas configurações especificas do seu ambiente requerido.
Essa boa prática indica que sua aplicação tenha separações explícitas nas fases de Construa, Lance e Execute. Assim, cada mudança no código da aplicação é construída apenas uma vez na etapa de Construa. Mudanças da configuração não necessitam de uma nova construção, sendo apenas necessário passar pelas etapas de lançar e executar.
Dessa forma, é possível criar controles e processos claros em cada etapa, ou seja, caso algo ocorra na construção do código, uma medida pode ser tomada e até mesmo o lançamento dele pode ser abortado, para que o código em produção não seja comprometido por conta do possível erro.
Com a separação das responsabilidades é possível saber exatamente em qual etapa o problema aconteceu e atuar manualmente, caso necessário.
Os artefatos produzidos devem sempre ter um identificador de lançamento único, que pode ser o timestamp do seu lançamento (como 2011-04-06-20:32:17) ou um número incremental (como v100).
Com o uso de artefato único, é possível garantir o uso de uma versão antiga, seja para um plano de retorno ou até mesmo para comparar comportamentos após mudanças no código.
Para atendermos essa boa prática, precisamos primeiro construir a imagem Docker com a aplicação dentro, ou seja, ela será nosso artefato.
Teremos um script novo, que aqui chamaremos de build.sh. Nesse arquivo teremos o seguinte conteúdo:
#!/bin/bash USER="gomex" TIMESTAMP=$(date "+%Y.%m.%d-%H.%M") echo "Construindo a imagem ${USER}/app:${TIMESTAMP}" docker build -t ${USER}/app:${TIMESTAMP} . echo "Marcando a tag latest também" docker tag ${USER}/app:${TIMESTAMP} ${USER}/app:latest echo "Enviando a imagem para nuvem docker" docker push ${USER}/app:${TIMESTAMP} docker push ${USER}/app:latest
Como podem ver no script acima, além de construir a imagem, ele envia a mesma para o repositório de imagem do Docker.
Lembre-se que o código acima e todos os outros dessa boa prática estão nesse repositório na pasta “factor5“.
O envio da imagem para o repositório é parte importante da boa prática em questão, pois isso faz com que o processo seja isolado, ou seja, caso a imagem não fosse enviada para um repositório, ela estaria apenas no servidor que executou o processo de construção da imagem, sendo assim, a próxima etapa precisaria, necessariamente, ser executada no mesmo servidor, pois ela precisará da imagem disponível.
No modelo proposto, uma vez que a imagem está no repositório central, ela estará disponível para ser baixada no servidor, caso ela não exista localmente. Caso você utilize uma ferramenta de pipeline, é importante que você, ao invés de utilizar a data para tornar o artefato único, utilize variáveis do seu produto para garantir que a imagem que será consumida na etapa de Executar seja a mesma construída no Lançar. Por exemplo, no GoCD, temos as variáveis GO_PIPELINE_NAME e GO_PIPELINE_COUNTER, que podem ser usadas em conjunto para garantir isso.
Com a geração da imagem, podemos garantir que a etapa Construir foi atendida perfeitamente, pois agora temos um artefato construído e pronto para ser reunido a sua configuração.
E etapa de Lançamento é o arquivo docker-compose.yml em si, pois o mesmo deve receber as configurações devidas para o ambiente que se deseja colocar a aplicação em questão. Sendo assim, o arquivo docker-compose.yml muda um pouco e deixará de fazer construção da imagem, pois agora ele será utilizado apenas para Lançamento e Execução (posteriormente):
version: "2" services: web: image: gomex/app:latest ports: - "5000:5000" volumes: - .:/code labels: - 'app.environment=${ENV_APP}' environment: - HOST_RUN=${HOST_RUN} - DEBUG=${DEBUG} - PORT_REDIS=6379 - HOST_REDIS=redis redis: image: redis:3.2.1 volumes: - dados:/data labels: - 'app.environment=${ENV_APP}' volumes: dados: external: false
No exemplo docker-compose.yml acima usamos a tag latest para garantir que ele usará sempre a última imagem construída nesse processo; mas como falei anteriormente, caso utilize alguma ferramenta de entrega contínua (exemplo: GoCD), faça uso das suas variáveis para garantir o uso da imagem criada naquela execução específica do pipeline.
Dessa forma, lançamento e execução sempre utilizarão o mesmo artefato: a imagem Docker, construída na fase de construção.
E etapa de execução basicamente é executar o docker-compose com o comando abaixo:
docker-compose up -d