Criando uma nova imagem no Docker, Parte 2: Agora com argumentos.
Overview
Seja bem-vindo a um mergulho mais profundo no universo Docker com este guia ilustrativo, onde evoluímos da criação básica de imagens para algo muito mais flexível e poderoso. Desta vez, abordaremos como tornar uma imagem Docker não apenas funcional, mas também versátil através do uso inteligente de variáveis e a adoção do gunicorn como webserver, solucionando problemas de conexões concorrentes. Tudo isso enquanto mantemos a leveza e a eficiência no desenvolvimento de aplicações Flask. Acompanhe este passo a passo e supere os desafios comuns do deploy!
No último post, mostrei como fazer uma imagem do docker utilizando arquivos locais e buscando do Git. Neste eu evoluo um pouco este processo. A imagem criada neste post utiliza arquivos locais e tem a vantagem da flexibilidade de variáveis.
Qual a vantagem de usar variáveis para criar uma imagem?
Bom, esta é uma pergunta um tanto quanto obvia, mas é valida. A ideia é flexibilizar a criação da imagem e passar valores padrões para variáveis de ambiente. É claro que você pode ser criativo e achar outras utilidades, mas estas são as mais obvias e são bem flexíveis.
Do que se trata esta imagem?
No último post, a imagem criada foi para uma API dummy especifica que eu fiz. Sendo assim, o nome do arquivo (.py) é fixo e a imagem só serve para aquela API específica. Outra desvantagem daquela imagem é que estou executando a aplicação Flask através do próprio script e isso é um problema, pois desta forma chamadas concorrentes vão fazer a API cair.
A ideia desta imagem é faze-la genérica o suficiente para que possa ser utilizada com qualquer aplicação Flask e utilizar o gunicorn como webserver, o que vai acabar com o problema de conexões concorrentes.
Vamos a imagem.
Passo 01: Base da imagem.
# Base for this image
FROM python:3.7-slim
# A few labels...
LABEL description="my flask application"
LABEL maintainer="Breno RdV <breno@raccoon.ninja>"
Nesta primeira parte do script, estou definindo:
- FROM: A imagem que será utilizada com base;
- LABEL description: Label com descrição da imagem;
- LABEL maintainer: Label com informação do criador/autor da imagem;
Uma explicação: Na última imagem, utilizei o Alpine como base, mas agora utilizei o python:3.7-slim. Esta imagem é uma versão bem leve do Debian, que vem com o Python 3.7 instalado. Fiz isso por que nos poupa o trabalho de fazer estas instalações manualmente e por que, de acordo com algumas pesquisas que fiz, o Alpine é mais leve, mas também é mais propenso a ser o culpado por falhas em execução de scripts/aplicações Python.
Passo 02: Variáveis de ambiente.
# Source folder
ARG source_folder_app=/app
ENV source_folder=${source_folder_app}
# Target folder (where it the code will be copied.)
ARG target_folder_app=/app
ENV target_folder=${target_folder_app}
# Host
ARG host_addr="0.0.0.0"
ENV host=${host_addr}
# Port
ARG port_num=20042
ENV port=${port_num}
# Bind
ENV bind="${host}:${port}"
# Number of workers
ARG qty_workers=3
ENV workers=${qty_workers}
# App file
ARG app_file_name="web_isn_batch_add_processo"
ENV app_file=${app_file_name}
# Variable name
ARG app_var_name="app"
ENV app_var=${app_var_name}
# Variable that will be used as the APP_MODULE value.
ENV full_app_var="${app_file}:${app_var}"
No código acima, criei 9 variáveis de ambiente e 7 argumento de build.
Como identificar cada um?
- As variáveis de ambiente são as que começam com ENV;
- Os argumentos de build são os que começam com ARG;
Qual a diferença entre estes dois?
- O argumento de build só pode ser definido e acessado na hora da criação da imagem. (docker build …);
- Já as variáveis de ambiente são, literalmente, variáveis de sistema que estarão presentes no container.
Se você reparar, quase todas as variáveis de ambiente (ENV) possuem um par (ARV). Exemplo: ARG port_num e ENV port.
Criei desta forma para fazer com que as variáveis de ambiente tenham valores que possam ser definidos no momento da criação da imagem, dando mais flexibilidade para ela.
Uma breve explicação de cada um dos parâmetros. Primeiro vou falar o que o parametro faz e depois mostrar qual a variável de ambiente (ENV) e argumento de build (ARG) correspondentes:
- Pasta, na maquina local, onde está a aplicação que será utilizada na imagem
Importante: O caminho não pode ser fora do diretório onde está o Dockerfile. Até onde me consta, o Docker não consegue enxergar pastas fora deste contexto.- ARG source_folder_app
- ENV source_folder
- Pasta destino, para onde a aplicação será copiada
- ARG target_folder_app
- ENV target_folder
- IP/Host que será utilizado no bind.
- ARG host_addr
- ENV host
- Porta que será utilizada no bind.
- ARG port_num
- ENV port
- Quantidade de workers que estarão funcionando no container do gunicorn
- ARG qty_workers
- ENV workers
- Nome do arquivo python onde está a variável da aplicação Flask
(nome do arquivo sem a extensão .py.)- ARG app_file_name
- ENV app_file
- Nome da variável da aplicação Flask
- ARG app_var_name
- ENV app_var
Com as variáveis acima, podemos definir valores padrão para cada uma delas e estes valores serão utilizados na hora de criar o container.
Passo 03: Copiando arquivos para a imagem
# Copies source files
COPY .${source_folder} ${target_folder}
# Copies gunicorn config file. (sample file, slightly modified.)
COPY ./gunicorn_conf.py /gunicorn.conf.py
No código acima, faço a copia dos arquivos da pasta source para a pasta destino. Ambas foram definidas pelas variáveis do passo anterior. Respectivamente falando: source_folder e target_folder.
Depois copiamos o arquivo de configuração (gunicorn_conf.py) para a raiz da imagem, renomeando ele para gunicorn.conf.py. (Não tinha necessidade de renomeá-lo, mas resolvi fazer mesmo assim.)
Passo 04: Instando as coisas
# Installs stuff...
RUN pip install --upgrade pip \
&& pip install --upgrade setuptools \
&& pip install wheel \
&& pip install gunicorn \
&& pip install -r ${target_folder}/requirements.txt
Agora que já temos os arquivos necessários e as variáveis criadas, basta instalarmos o que precisamos. Neste caso, estou instalando/atualizando o setuptools, wheel e o gunicorn, que é o nosso webserver. Depois eu faço a instalação dos requirements da aplicação.
Passo 05: Preparativos finais
# Finishing up...
WORKDIR ${target_folder}/
ENV PYTHONPATH=${target_folder}
EXPOSE ${port}
Acima estão alguns preparativos finais:
- Definindo o “working directory” para a pasta onde está o fonte da aplicação
- Definindo o PYTHONPATH para a mesma pasta
- Expondo a porta que foi definida na variável de ambiente port.
Passo 06: Definindo a linha de comando que será utilizada
CMD gunicorn --config /gunicorn.conf.py "${app_file}:${app_var}"
A linha de comando acima passa o arquivo de configuração (/gunicorn.conf.py e depois o nome do “modulo” (arquivo python onde está a variável da aplicação) e o nome da variável em si.
Em diversos sites, você vai ver uma variante da linha de comando com a seguinte sintaxe: CMD [“gunicorn”, “arg1”, “arg2”]. Todavia, esta abordagem não funciona para deixar o nome do modulo e da variável dinâmicos.
Passo 07: Gerando build da imagem
docker build -t pyimage:latest .
Lembrando que você deve rodar o comando acima com o console no diretório onde está o arquivo Dockerfile. O nome que eu escolhi para esta image foi pyimage e a tag foi latest (pyimage:latest), mas você pode escolher outro.
Passo 08: Criando um arquivo docker-compose
version: '3'
services:
flask-app:
image: pyimage:latest
ports:
- "5042:20042"
Por conveniência, criei um arquivo docker-compose. Ele vai gerar uma instancia da imagem que acabamos de criar.
Exemplo completo
No meu Github existe um exemplo completo que você pode utilizar.
Este foi um post longo, mas espero ter ajudado.
Postagens nesta série
- [resolvido] Docker Desktop iniciando com o Windows.
- Comandos básicos do Docker, parte 2.
- Controlando Docker Remotamente.
- Criando uma nova imagem no Docker
- Criando uma nova imagem no Docker, Parte 2: Agora com argumentos.
- Interface Gráfica para Docker.
- Introdução ao Docker
- O que é e como usar o Docker Stack.
- Trabalhando com clusters no Docker Swarm
- Um pouco sobre Docker Compose