A maioria dos projetos de desenvolvimento envolve vários ambientes: produção, desenvolvimento, QA, homologação e, claro, o ambiente local de cada pessoa da equipe. Manter tudo isso em sincronia para que o projeto rode igual (ou simplesmente rode) em cada ambiente pode ser um baita desafio.
As causas de incompatibilidade são muitas, mas o Docker ajuda a eliminar quase todas. Neste artigo, vamos falar sobre Docker, contêineres de aplicação e como essas ferramentas permitem executar sua aplicação em qualquer lugar onde seja possível instalar o Docker, evitando problemas de compatibilidade.
O que é um contêiner de aplicação?
Um contêiner de aplicação é um pacote leve e independente que reúne uma aplicação e todas as suas dependências, incluindo bibliotecas, ambientes de execução e arquivos de configuração. O contêiner guarda tudo o que a aplicação precisa para funcionar bem em um ambiente isolado. Esses contêineres foram feitos para rodar em qualquer sistema operacional e hardware compatíveis, já que abstraem as diferenças entre distribuições de sistema operacional (SO) e compartilham o kernel do sistema operacional do host. Assim, várias aplicações ou serviços isolados rodam em um único host sem precisar de um SO completo para cada aplicação.
Os contêineres de aplicação se diferenciam da virtualização tradicional em alocação de recursos, sistema operacional, isolamento e escalabilidade. Eles compartilham recursos como CPU, memória e armazenamento no nível do SO e consomem menos do que as máquinas virtuais (VMs), que exigem um SO completo por instância. Por isso, contêineres iniciam em segundos, enquanto VMs podem levar minutos. Essa característica também torna os contêineres mais escaláveis. As VMs, por outro lado, se destacam no isolamento: cada uma roda o próprio sistema operacional, o que garante uma separação bem mais forte entre instâncias.
Por que colocar aplicações em contêineres?
Colocar aplicações em contêineres traz muitos benefícios e facilita vários desafios do desenvolvimento, da implantação e da operação de software. Entre os principais motivos, estão:
- Portabilidade:a conteinerização cria um ambiente de execução consistente ao empacotar a aplicação junto com suas dependências e configurações. Isso garante o mesmo comportamento tanto na máquina do desenvolvedor quanto em uma plataforma de nuvem.
- Escalabilidade: os contêineres permitem implantar, aplicar patches e escalar aplicações rapidamente. Orquestradores de contêineres fazem escalonamento inteligente, mantendo apenas a quantidade de instâncias necessária para a carga atual e respeitando os recursos disponíveis.
- Produtividade: os contêineres entregam um ambiente previsível e consistente onde quer que rodem. Em vez de configurar cada ambiente na mão, o desenvolvedor geralmente executa um único comando para iniciar um contêiner, e ele mesmo monta e sobe o ambiente.
- Flexibilidade: a eficiência dos contêineres facilita dividir uma aplicação em microsserviços menores e independentes, sem o custo de recursos de manter vários servidores físicos ou mesmo VMs.
Conhecendo o Docker
O Docker surgiu em 2013 como uma plataforma de código aberto que simplifica criar, executar, gerenciar e distribuir aplicações por meio da conteinerização. Para muitas equipes de desenvolvimento, é uma ferramenta fundamental nos fluxos de desenvolvimento e implantação de software, já que permite empacotar aplicações com suas dependências em contêineres leves e portáteis.
Vamos dar uma olhada rápida em alguns componentes-chave do Docker para entender como tudo funciona.
Dockerfile
O Dockerfile é um arquivo de texto com as instruções para construir uma imagem Docker. Ele define qual imagem Docker será usada como base, onde está o código-fonte da aplicação que vai junto com a imagem e quais bibliotecas, pacotes e outras dependências a aplicação precisa para rodar. O Docker lê esse arquivo e executa as instruções nele para construir a imagem.
Imagens Docker
A imagem Docker é um modelo somente leitura que contém o código-fonte e as dependências da aplicação e serve de base para os contêineres Docker. As imagens são criadas a partir de um Dockerfile e podem ser versionadas, armazenadas em registros e compartilhadas. Elas usam uma arquitetura em camadas, em que cada camada representa uma alteração na imagem. Como esse empilhamento está diretamente ligado a cada comando do Dockerfile, toda instrução pode gerar uma nova camada
Contêineres Docker
O contêiner Docker é uma instância em execução de uma imagem Docker. Como os demais contêineres, roda em ambiente isolado no sistema host, é leve, inicia rápido e se comporta da mesma forma independentemente do host.
Como conteinerizar uma aplicação com Docker
Muitos tipos de aplicação podem ser colocados em contêineres, tanto de frontend quanto de backend. A seguir, vamos ver como conteinerizar uma aplicação web simples em Python Flask. Existem outras linguagens e aplicações que podem ser empacotadas com Docker, mas usaremos Python Flask nos exemplos.
Pré-requisitos para a conteinerização com Docker
Como a conteinerização já empacota todo o código e as dependências da aplicação, os requisitos são poucos:
- Ter o Docker instalado. Os pacotes de instalação para Linux, Mac ou Windows estão aqui.
- Criar uma pasta de projeto para guardar todos os arquivos da aplicação. No exemplo abaixo, usaremos apenas três arquivos.
- Talvez queira instalar localmente as linguagens e bibliotecas que está usando para testar a aplicação antes de conteinerizá-la. Mas isso não é obrigatório, já que elas serão instaladas dentro do contêiner e o Docker cuida dessa parte.
Entendendo o código da aplicação e suas dependências
No nosso exemplo, vamos criar um app simples em Python Flask. A pasta do projeto fica assim:
my_flask_app/
- app.py
- requirements.txtA aplicação tem dois arquivos: app.py, com o código da aplicação web, e requirements.txt, usado pelo Python para instalar as dependências. Veja o conteúdo de app.py, com alguns comentários explicando cada parte:
# Import the Python Flask module
from flask import Flask
# Flask constructor uses the name of the current module
app = Flask(__name__)
# The route() function is a decorator,
# It binds a URL to an associated function
@app.route('/')
def hello_world():
return 'Hello World'
# If the file is run directly
# run the Flask application
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
O arquivo requirements.txt é bem enxuto. Ele apenas lista o Flask como dependência:
Flask==2.3.2
Guia passo a passo para conteinerizar uma aplicação
Agora que temos uma aplicação para conteinerizar, o próximo passo é criar um Dockerfile com as instruções para gerar a imagem Docker.
Criando o Dockerfile
Primeiro, crie um Dockerfile vazio no projeto. O arquivo não tem extensão. A pasta do projeto deve ficar assim:
my_flask_app/
- app.py
- Dockerfile
- requirements.txtEste é o conteúdo do Dockerfile:
FROM python:3.10 COPY . /app WORKDIR /app RUN pip install -r requirements.txt EXPOSE 5000 CMD ["python3", "app.py"]
Veja o que cada parte significa:
- FROM: define qual contêiner usaremos como base. Há uma grande variedade de contêineres base disponíveis no Docker Hub.
- COPY: na construção do contêiner, os arquivos do projeto, representados pelo ponto, serão copiados para a pasta /app dentro do contêiner.
- WORKDIR: indica ao Docker onde executar o comando que está no fim do arquivo, ou seja, a pasta para onde copiamos o projeto.
- RUN: a instrução RUN informa ao Docker um comando a ser executado durante a construção do contêiner. Esse comando roda na criação da imagem, e não quando o contêiner inicia. Aqui, mandamos instalar os requisitos do arquivo requirements.txt.
- EXPOSE: informa ao Docker em qual porta o contêiner vai escutar, que é a porta usada pela aplicação Flask.
- CMD: informa ao Docker quais comandos serão executados quando o contêiner iniciar e vai rodar o app Flask. A instrução CMD define os padrões de execução de um contêiner e pode ser sobrescrita no docker run.
Construindo a imagem Docker
Com o Dockerfile pronto, dá para construir a imagem Docker executando este comando no diretório do projeto:
docker build -t flask-app .
No comando, -t significa tag e marca a imagem com um nome, flask-app é o nome que daremos à imagem e o . define o contexto de build, que é o diretório atual.
Depois de executar o comando, o Docker constrói a imagem e você verá uma saída parecida com esta:
Executando o contêiner Docker
Com a imagem Docker criada, execute o contêiner com o seguinte comando no diretório do projeto:
docker run -it -p 5000:5000 flask-app
No comando acima, -it diz ao Docker para rodar o contêiner em modo interativo, o que permite interagir com o shell do contêiner, e para alocar um tty, que dá um console de texto. Mas o uso de -it nem sempre é necessário com a aplicação Flask, já que ela não é uma ferramenta de linha de comando que exija terminal interativo. A flag -p define o mapeamento de portas do contêiner. O primeiro 5000 é a porta da máquina host que você quer mapear para a porta do contêiner. O segundo 5000 é a porta do contêiner. E flask-app é o nome da imagem que acabamos de criar e queremos executar.
A saída no terminal depois de executar o comando será mais ou menos assim:
E, ao acessar http://localhost:5000/, verá a aplicação Flask bem básica.
Boas práticas para a conteinerização com Docker
Conteinerizar uma aplicação com Docker traz inúmeros benefícios para o desenvolvimento e a implantação, mas é preciso seguir boas práticas para aproveitá-los de verdade.
Veja algumas regras importantes:
- Use uma imagem base mínima: escolha a imagem base mais leve possível, como o Alpine Linux, para reduzir o tamanho da imagem final, diminuir o consumo de recursos e acelerar os builds.
- Use builds multiestágio: compile em um estágio e copie apenas os artefatos necessários para o estágio final, reduzindo o tamanho da imagem.
- Reduza as camadas: diminua o número de camadas da imagem combinando comandos em um único RUN, porque cada camada gera sobrecarga.
- Limpe arquivos desnecessários: remova arquivos temporários, caches e artefatos de build depois de instalar as dependências ou construir a aplicação para reduzir o tamanho da imagem.
- Execute contêineres sem estado: guarde os dados persistentes fora do contêiner, em bancos de dados, armazenamento de objetos ou outro sistema externo, assim é possível desligar o contêiner sem perder dados.
- Marque suas imagens com tags: as tags ajudam a gerenciar as versões das imagens. Usar tags únicas nas implantações permite escalar rapidamente um cluster de produção para muitos nós.
- Aplique patches nos contêineres: aplicar patches e atualizar os contêineres é uma forma proativa de reforçar a segurança e reduzir ameaças.
Precisa de um jeito melhor de implantar seus apps? Assista a como conteinerizar uma aplicação com Docker.
Docker: ferramenta essencial para o desenvolvimento moderno
A conteinerização com Docker revolucionou o desenvolvimento de aplicações. Aplicações em contêineres trazem benefícios significativos: portabilidade, escalabilidade, produtividade, eficiência e flexibilidade. O Docker tornou esse processo simples ao oferecer uma abordagem padronizada e fácil de usar para a conteinerização.
Adotar o Docker e seguir as boas práticas de conteinerização melhora o desenvolvimento, a implantação e o gerenciamento de aplicações modernas. Como oferecem um ambiente de execução consistente, os contêineres permitem desenvolver a aplicação uma vez e executá-la em qualquer lugar, o que simplifica o trabalho em ambientes variados. A escalabilidade dos contêineres viabiliza implantar e escalar aplicações rapidamente, dividindo-as em microsserviços menores e independentes.
Como principal plataforma de conteinerização, o Docker simplifica a construção, a execução, o gerenciamento e a distribuição de aplicações em contêineres, o que faz dele uma ferramenta essencial para o desenvolvimento de software moderno.
