Como construir um ambiente personalizado para o Jupyter no Docker

imagem: Juno probe / NASA

Se você tem desenvolvido software nos últimos anos, você provavelmente teve algum contato com o uso de containers não apenas para fazer o deploy, mas também durante o desenvolvimento para ter certeza de que o seu ambiente é totalmente reproduzível em sistemas diferentes.

Também é muito popular nos meios de ciência de dados, visualização de dados e outros relacionados usar Python e Jupyter Notebooks e Jupyter Lab para explorar e experimentar com os dados. Algumas pessoas criticam o Jupyter por frequentemente resultar em trabalhos não reproduzíveis, algo muito importante para o método científico, pois o ambiente de desenvolvimento pode ser diferente daquele que o vai reproduzir e as células podem ser executadas fora de ordem. Por exemplo, em um experimento feito pela equipe Datalore do Jetbrains Datalore no ano passado que baixou quase 10 milhões de cadernos do Github, eles descobriram que 36% desses cadernos eram inconsistentes, isto é, eram executados em uma ordem não linear.

Por outro lado, se o seu ambiente estiver definido em um container Docker e você tomar o cuidado de fazer a boa prática de executar as células na ordem apropriada, os cadernos podem ser completamente reproduzíveis.

Pode não ser tão óbvio como fazê-lo, todavia. Por isso, aqui está um guia passo a passo de como usar o Jupyter Lab e o Jupyter Notebook dentro do Docker com os pacotes Python de sua escolha.

Este guia foi escrito pensando em um sistema baseado em GNU-Linux e foi testado no Ubuntu 18.04 e 20.04. Ele deve funcionar em outros sistemas baseados em GNU-Linux. Todavia, se você estiver usando Windows ou MacOS, você terá que fazer algumas adaptações para funcionar, principalmente em relação às operações de linha de comando e variáveis de ambiente, mas isso não é abordado neste guia.

Preparações

Antes de começar, certifique-se de que o Docker e o Docker Compose estão instalados.

Escolhendo uma imagem base

O Jupyter Docker Stacks fornece algumas imagens prontas para rodar e algumas receitas para criar a sua própria imagem de Docker herdando a partir destas (o que eles chamam de imagem Docker filha).

Para isso, precisamos primeiro escolher uma imagem base da qual herdar. Para este exercício vamos usar a jupyter/scipy-notebook, que inclui o Pandas, o NumPy e mais algumas coisas. Entretanto, dê uma olhada na seção de seleção de imagem na documentação do Jupyter Docker Stacks para ver outras opções.

Então vamos criar um Dockerfile para construir a imagem Docker personalizada e a seguinte linha para definir a imagem base:

FROM jupyter/scipy-notebook

Se você não quiser criar um novo Dockerfile do zero, você também pode clonar este repositório de exemplo a partir do Github, que tem um Dockerfile pronto para uso.

Configurando os locales

Se você algum dia for trabalhar com dados de outros locales que não sejam os EUA, você provavelmente deveria configurar um ou mais outros locales. Por exemplo, isso vai tornar mais fácil trabalhar com separadores de casas decimais diferentes ou com os nomes dos dias da semana e dos meses do ano em diferentes idiomas.

Para este exercício, vamos configurar os locales do sistema para usar ambos os locales en_US.UTF-8 (inglês, EUA) e pt_BR.UTF-8 (português brasileiro). A seguinte seção do Dockerfile faz exatamente isso.

# instala os locales que você quiser usar
RUN set -ex \
   && sed -i 's/^# en_US.UTF-8 UTF-8$/en_US.UTF-8 UTF-8/g' /etc/locale.gen \
   && sed -i 's/^# pt_BR.UTF-8 UTF-8$/pt_BR.UTF-8 UTF-8/g' /etc/locale.gen \
   && locale-gen en_US.UTF-8 pt_BR.UTF-8 \
   && update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 \

Escolhendo os seus pacotes Python

Em seguida, nós vamos escolher os pacotes Python que estarão disponíveis para o Jupyter dentro do ambiente. Neste exemplo, escolhemos a ferramenta de visualização de dados chamada Plotly e o pacote de traçar mapas chamado Folium. Esta é a seção do Dockerfile onde definimos isso.

# instala os pacotes Python que você usa frequentemente
RUN set -ex \
   && conda install --quiet --yes \
   # escolhe os pacotes Python que você precisa
   'plotly==4.9.0' \
   'folium==0.11.0' \
   && conda clean --all -f -y \
   # instala as extensões do Jupyter Lab que você precisa
   && jupyter labextension install jupyterlab-plotly@4.9.0 --no-build \
   && jupyter lab build -y \
   && jupyter lab clean -y \
   && rm -rf "/home/${NB_USER}/.cache/yarn" \
   && rm -rf "/home/${NB_USER}/.node-gyp" \
   && fix-permissions "${CONDA_DIR}" \
   && fix-permissions "/home/${NB_USER}"

Substitua-os ou adicione os pacotes Python e extensões do Jupyter Lab que você quiser.

Construindo o container

Este passo é muito fácil de se fazer, mas pode demorar bastante para terminar. Simplesmente execute o seguinte comando:

$ docker build --rm -t docker-jupyter-extensible .

e vá fazer um lanche ou fazer alguma outra coisa e volte depois de um tempo.

Corrigindo as permissões

Um problema comum ao montar uma pasta do host dentro do container é que os donos dos arquivos frequentemente não se correspondem. Então você fica ou sem acesso à pasta ou com somente leitura. Para corrigir isso, precisamos configurar o usuário e o grupo usados dentro do container para ter os mesmos números de id que o usuário e grupo que você estiver usando no host. Pode parecer complicado, mas com essas imagens é algo muito fácil de se fazer.

Apenas crie um arquivo chamado .env, rodando o seguinte comando:

$ printf "UID=$(id -u)\nGID=$(id -g)\n" > .env

Isso vai permitir que você use a pasta notebooks tanto dentro como fora do container.

Executando o container

Está pronto! A cada vez que você quiser iniciar o Jupyter, apenas rode o container com o comando:

$ docker-compose up

Você deve ver as mensagens do container no terminal. Se tudo correr bem, você deve ver um link começando com http://127.0.0.1:8888 que também contém um token de acesso. Abra esse link em um navegador para usar o Jupyter Notebook. Se, em vez disso, você quiser usar o Jupyter Lab, é só trocar o início da URL para http://127.0.0.1:8888/lab, mas mantenha tudo depois disso, incluindo o token de acesso.

Tela do terminal mostrando a saída do container Jupyter se iniciando.

A saída do terminal ao iniciar o container mostra a URL e o token para abrir no navegador o Jupyter Lab e o Jupyter Notebook.

Para verificar que os pacotes foram corretamente instalados e configurados, apenas abra um novo caderno e os importe. Veja, por exemplo, o Plotly Express:

Uma janela do navegador com o Jupyter Lab rodando o Plotly Express.

Um exemplo de gráfico do Plotly Express rodando dentro do nosso novo container Docker.

Agora divirta-se com o seu novo Jupyter Lab ou Jupyter Notebook!

Se, mais tarde, você quiser acrescentar quaisquer novos pacotes, só vá para o passo “escolhendo os seus pacotes”, faça as alterações que deseja e siga a partir dali, construindo o container novamente.

Referências: este texto foi mencionado na newsletter da comunidade Data Hackers de fevereiro de 2020.