Ambiente Docker para análise de dados

Docker para análise de dados

TL;DR: Reproducibilidade garante não só mais transparência, como é uma ferramenta essencial para qualquer trabalho de “Ciência”.

Ao longo da minha carreira eu venho lutado um bom combate em prol da reprodutibilidade em ciência de dados dentro do mundo corporativo.

Seja falando de algumas ferramentas que eu uso diariamente, seja falando que até a toda poderosa revista científica Nature aprova pesquisa que não seria aprovada nem em trabalho de TCC, de como a reproducibilidade ajuda na detecção de problemas metodológicos.

Por fim eu penso que a reproducibilidade não apenas acaba com a cultura de opinião, como desmascara um monte de pessoas com muita opinião e poucos dados, dado que os dados e o código falam por sí só.

Nesse post eu vou compartilhar o que eu uso no dia a dia para análises de dados, prototipações e afins e espero que ajude a quem esteja indo para uma direção de maior reprodutibilidade nas análises.

Muito do que eu estou usando tem ligação direta com o post do Danny Janz, chamado “Containerize your whole Data Science Environment (or anything you want) with Docker-Compose”, então para quem quiser algo mais detalhado no que se refere às imagens Docker eu recomendo fortemente o post.

Estrutura de arquivos

Eu uso a seguinte estrutura de diretórios para gerar o ambiente para cada análise:

analysis
|- src
|  - data
|- Dockerfile
|- .dockerignore
|- docker-compose.yml
|- README.md
|- requirements.txt

Tem muitos templates legais de estrutura de projetos como o excelente Cookiecutter Data Science, mas no meu caso eu prefiro de começar com menos pastas possíveis, por questão de organização pessoal. Claro que dependendo do projeto pode ser que exista a necessidade de algo mais estruturado desde o começo.

Ainda em relação a diretórios, eu costumo colocar os dados dentro da pasta src.

Eu sei que isso é um anti-padrão, mas isso além de me aliviar em relação à
navegação nos sub-diretórios eu tenho a vantagem de usar o DVC para versionamento dos dados (os dados em si ficam no AWS S3 e o DVC registra apenas os ponteiros de versionamento dos aquivos) e os arquivos do DVC ficam organizados em um único diretório.

Mas vamos ao que interessa que são os outros arquivos.

Dockerfile

No Dockerfile eu uso como imagem base o python:3.6-buster.

A minha escolha pelo Buster foi (i) pela estabilidade da distribuição, (ii) o fato de que vai ter ainda um longo suporte para o Python 3.6, e (iii) ainda que a imagem não seja um primor em relação ao espaço em disco em comparação com o péssimo Alpine, ao menos não vai ter o absurdo tamanho de imagem das versões ubuntu:18+.

Como alguns desses pipelines eu executo nos do Gitlab/Github e no Amazon ECS eu tenho alguns critérios de desempenho no deploy (i.e. o deploy não pode demorar muito) o python:3.6-buster consegue entregar um balanço interessante entre a distribuição e a velocidade da criação da imagem.

O artigo do Itamar Turner-Trauring chamado “The best Docker base image for your Python application (April 2020)” ajudou muito na minha escolha. Contudo, como eu tive alguns problemas no passado com o uso de imagens slim eu acabei indo para a imagem buster completa.

(A propósito o blog dele é sensacional para quem trabalha com Python + Docker e tem um benchmark interessante sobre essas imagens.)

O restante do Dockerfile não é nada especial, a não ser que para desenvolvimento local eu rodo o Jupyter Notebook com as opções padrão:

.dockerignore

O Alexei Ledenev fez um post sobre os perigos e os problemas de não usar o .dockerignore.

Essencialmente ele coloca que o não uso do .dockerignore além de deixar o build lento, a ausência do .dockerignore pode potencialmente causar casos de vazamento de variável de ambiente e credenciais.

Este é o .dockerignore que eu uso:

requirements.txt

No requirements.txt eu mantenho um conjunto canônico de coisas que eu geralmente uso para análise exploratória de dados como jupyter, matplotlib, pandas e numpy.

Caso eu precise trainar algum algoritmo com referência (baseline) eu deixo o sklearn.

Como parte dos meus dados estão no AWS S3 e em bancos de dados do MySQL eu já deixo como padrão os pacotes boto3 e o s3fs para acessar os dados no S3, e o PyMySQL se eu precisar extrair dados via MySQL queries.

Dois pontos sobre o requirements.txt que eu gosto de ressaltar:(i) manutenção de versão de desenvolvimento e produção e (ii) a importância de passar a versão do pacote pip de forma explícita.

Eu sempre deixo as versões do Docker iguais ao que tem em produção não importa o quão grande é o pacote.

Isso garante desde o minuto zero da análise que eu não vou ter problemas de ambiente com bibliotecas diferentes.

Isso garante a consistência de ambiente (sistema operacional + bibliotecas), o que vai evitar que eu cometa erros como o cometido pela Universidade do Hawaii em relação ao uso da biblioteca glob.

No caso, o glob apresenta diferenças de comportamento de ordenação (e na consequente sequência de leitura dos arquivos) de acordo com o sistema operacional.

Devido a uma falta de atenção dos pesquisadores que pegaram o script do estudo original escrito 6 anos antes (época em que o sistema operacional lidava com a ordenação e a biblioteca não tinha a função de ordenação) a versão do estudo publicado começou a apresentar diferenças e invalidou parte dos experimentos.

Resultado final: Mais de 100 artigos tiveram que ser corrigidos.

Lição: Sempre use a versão explicitamente no requirements.txt.

O outro ponto sobre o requirements.txt é que como eu uso alguns pacotes que tem algumas dependências obscuras (pela falta de um nome melhor), eu sempre estou olhando alguns relatórios de vulnerabilidade do snyk, Safety e do Bandit.

Para quem não tiver um orçamento para algumas dessas feramentas, o Github oferece o mesmo tipo de serviço.

Dá um pouco mais de trabalho, pois se alguma alteração tiver que ser colocada em em produção isso tem que ser transposto para os ambientes de desenvolvimento.

Ao menos pra mim funciona bem, pois eu tenho de cabeça o controle de tudo o que precisa ser checado para garantir a consistência dos ambientes.

docker-compose.yml

A primeira pergunta que pode aparecer é: “Porque usar o docker compose?”.

No meu caso foi uma necessidade de realizar prototipação em ferramentas fora do espectro de Ciência de Dados, como Redis para teste de caching com predições/scores feitas(os) offline, SQLLite quando eu precisei testar coisas de engenharia de dados para embarcar um banco de dados, ou quando tive que usar o H2O.ai para um treinamento de machine learning específico.

Nestes casos eu precisei apenas escolher uma imagem destas plataformas (ou criar uma própria) para ter todos serviços isolados em cada um em seu próprio contêiner. Adeus inferno de dependências.

Uma última coisa que eu queria ressaltar em relação ao uso de configurações e credenciais em variáveis de ambiente: Evite ao máximo a utilização de variáveis de ambiente no Docker, dado os problemas claros de segurança como pode ser visto no post do Diego Monica chamado “Why you shouldn’t use ENV variables for secret data”

Eu recomendo o uso para gerenciamento de credenciais o uso do Docker Secrets. Pessoalmente, só quando eu preciso passar algo que não vale a pena armazenar no Docker Secrets, aí eu passo na forma que está no arquivo abaixo

README.md

Finalmente no README.md eu coloco as instruções como, que é a solução, quais são os dados, o ticket do Jira, como fazer o build da imagem e como subir o container no Docker Compose.

export ENV_VAR_1='***********' && \
export ENV_VAR_2='***********' && \
docker build -t data_science_analysis . &&  \
docker-compose up

Por fim, eu vou no Jupyter Notebook no http://localhost:8888/ com a senha da minha escolha. Neste caso eu coloquei a senha do Jupyter Notebook como root apenas para fins de demonstração.

Considerações Finais

Pessoalmente eu penso no Docker como uma ferramenta que revolucionou a forma de fazer ciência de dados no que diz respeito a aspectos como reprodutibilidade e gerenciamento de ambientes.

A configuração acima não é definitiva, mas é a forma que eu encontrei para andar mais rápido em prototipações e análises e espero que ajude a quem estiver começando a incorporar o Docker nas análises de dados.

Referências