10.3 Configuração do MLflow
O MLflow precisa de um back-end para salvar os dados dos experimentos, tais como as métricas, parâmetros, artefatos e modelos. Na seção anterior, executamos os tutoriais básicos, que utilizam o sistema de arquivos como back-end, mas essa abordagem é muito limitada, pois exige que os comandos sejam executados sempre na mesma máquina. Para um único usuário interessado em controlar seus experimentos e modelos, pode servir, mas para uma equipe que deseja um mínimo de consistência e colaboração, é desejável a configuração de um local centralizado.
Existem dois tipos de storage que precisam ser configurados para o funcionamento completo do MLflow:
Um serviço de storage simples, como Amazon S3 ou Google File storage, para armazenar artefatos e parâmetros/métrica;
Um serviço de banco de dados, como MySQL ou PostgreSQL, para armazenar dados para registro de modelos.
Faremos aqui uma configuração completa usando Docker, que possibilita que o MLflow seja utilizado com todos seus recursos. O exemplo foi adaptado deste repositório aqui e da documentação oficial do MLFlow.
10.3.1 Serviço de storage MinIO
O primeiro serviço que iremos configurar é o armazenamento de arquivos. O MinIO é uma ferramenta open source compatível com o Amazon S3, portanto é uma excelente opção para demonstrar como esse tipo de armazenamento pode ser utilizado. O leitor pode optar por configurar sua própria instância do MinIO ou utilizar o Amazon S3, com as mesmas configurações do lado do MLflow.
Para configurar o MinIO, utilizaremos uma imagem Docker pronta. Faremos também uso do docker compose
como facilitador na configuração do ambiente.
Crie uma pasta em um local qualquer, chamada mlflow-server
. Crie um arquivo chamado compose.yml
:
services:
minio:
image: minio/minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
expose:
- 53
environment:
- MINIO_ROOT_USER=${AWS_ACCESS_KEY_ID}
- MINIO_ROOT_PASSWORD=${AWS_SECRET_ACCESS_KEY}
healthcheck:
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1
interval: 1s
timeout: 10s
retries: 5
command: server /data --console-address ":9001"
networks:
- internal
- public
volumes:
- minio_volume:/data
networks:
internal:
public:
driver: bridge
volumes:
minio_volume:
Esse arquivo faz a configuração de um contêiner com base na imagem oficial do MinIO. Há duas portas sendo abertas, 9000 (para a interface principal) e 9001 (para os comandos internos). A porta 53 está sendo exposta para uso interno, pois será necessária para um serviço que criaremos a seguir. A configuração do usuário e senha também é feita nesse arquivo, com base em duas variáveis de ambiente que serão definidas a seguir. O comando que executa o servidor é o seguinte:
server /data --console-address ":9001"
/data
é a pasta onde serão salvos os arquivos e metadadosconsole-address
define a porta para onde os comandos devem ser enviados
O arquivo do docker compose
também define duas redes onde esse serviço estará disponível. A rede internal
será compartilhada com outros serviços que definiremos mais adiante, e a rede public
é, como o nome sugere, a rede por onde acessaremos o MinIO a partir da máquina local e de outras máquinas, caso quisermos.
Outra configuração importante desse arquivo é o trecho healthcheck
. Esse trecho define um script responsável verificar a saúde do serviço, ou seja, se ele está disponível a ponto de poder ser utilizado por outros serviços. No caso, verifica se a porta 9000 está disponível por meio do protocolo TCP, o que indica que o serviço está rodando. Isso será necessário mais adiante. Mais informações sobre essa configuração do Docker podem ser encontradas na documentação oficial do Docker.
Por último, o arquivo define que existirá um volume, chamado minio_volume
que ficará mapeado para a pasta /data
do contêiner (a mesma indicada no comando que executa o servidor).
Para poder rodar esse serviço, é necessário definir as configurações de usuário e senha. Faremos isso em um arquivo chamado .env
:
AWS_ACCESS_KEY_ID=admin
AWS_SECRET_ACCESS_KEY=senhasenha
Fique à vontade para trocar os valores, caso desejar.
Agora já podemos rodar. Execute:
docker compose up
Assim que o contêiner for iniciado, podemos testar, abrindo o navegador no endereço http://localhost:9000
.

Experimente a interface, crie novos buckets, faça o envio de arquivos para ver como funciona.
O MLflow irá armazenar seus artefatos em um bucket. Um bucket é uma unidade de armazenamento no Amazon S3, confira a documentação para saber mais. Podemos criá-lo manualmente, pela interface, e depois informar ao MLflow o nome do bucket. Mas podemos também automatizar essa etapa. Assim, seguindo a filosofia de infraestrutura como código, podemos deixar tudo programado de modo a gerar menos erros.
Vamos fazer uso do CLI do MinIO, uma ferramenta em linha de comando que permite a execução de diversos comandos. Por exemplo, para criar um novo bucket chamado "umbucketqualquer" podemos executar os comandos:
.\mc alias set minio http://localhost:9000 admin senhasenha
.\mc mb minio/umbucketqualquer
O primeiro comando configura a ferramenta CLI para acessar o servidor local, e o segundo solicita a criação de um novo bucket.
Portanto, vamos configurar um serviço no arquivo compose
para automaticamente criar um bucket, caso não exista, assim que o serviço for executado. Modifique o arquivo compose.yml
:
services:
minio:
image: minio/minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
expose:
- 53
environment:
- MINIO_ROOT_USER=${AWS_ACCESS_KEY_ID}
- MINIO_ROOT_PASSWORD=${AWS_SECRET_ACCESS_KEY}
healthcheck:
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1
interval: 1s
timeout: 10s
retries: 5
command: server /data --console-address ":9001"
networks:
- internal
- public
volumes:
- minio_volume:/data
+ create_s3_buckets:
+ image: minio/mc
+ depends_on:
+ minio:
+ condition: service_healthy
+ entrypoint: >
+ bash -c "
+ mc alias set minio http://minio:9000 '${AWS_ACCESS_KEY_ID}' '${AWS_SECRET_ACCESS_KEY}' &&
+ mc mb minio/${AWS_BUCKET_NAME}
+ "
+ networks:
+ - internal
networks:
internal:
public:
driver: bridge
volumes:
minio_volume:
O novo serviço utiliza uma imagem que tem apenas o cliente do MinIO instalado, pronto para usar. Então podemos apenas executar o comando que quisermos. Mas não podemos executar nada antes de termos certeza que o serviço do MinIO terminou de rodar. No arquivo, existe uma dependência deste novo serviço para o serviço do MinIO (depends_on
), mas isso não garante que ele execute apenas após o outro estar pronto, como já discutimos antes, na Seção 5.2. Por isso, o comando definido nesse serviço acrescenta uma condição service_healthy
, que espera até que o outro container esteja passando por sua própria condição de healthcheck
, como configurado acima. Apenas quando essa tentativa é bem sucedida que o comando segue para a criação do bucket. O nome do bucket deve ser definido no arquivo .env
:
AWS_ACCESS_KEY_ID=admin
AWS_SECRET_ACCESS_KEY=senhasenha
+AWS_BUCKET_NAME=mlflow
É importante entender que o comando primeiro verifica se o bucket já existe. Se ele já existir, não será criado. Como estamos fazendo uso de um volume, os dados ficarão persistentes mesmo entre as execuções. Para testar, interrompa e execute o docker compose up
novamente. Após a execução, acesse a interface novamente e veja como o bucket foi criado.

10.3.2 Serviço de banco de dados MySQL
O próximo serviço a ser executado é o banco de dados. Faremos uso do MySQL, que também já tem uma imagem Docker pronta para ser utilizada. Vamos modificar o arquivo compose.yml
:
services:
minio:
image: minio/minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
expose:
- 53
environment:
- MINIO_ROOT_USER=${AWS_ACCESS_KEY_ID}
- MINIO_ROOT_PASSWORD=${AWS_SECRET_ACCESS_KEY}
healthcheck:
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1
interval: 1s
timeout: 10s
retries: 5
command: server /data --console-address ":9001"
networks:
- internal
- public
volumes:
- minio_volume:/data
create_s3_buckets:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
bash -c "
mc alias set minio http://minio:9000 '${AWS_ACCESS_KEY_ID}' '${AWS_SECRET_ACCESS_KEY}' &&
mc mb minio/${AWS_BUCKET_NAME}
"
networks:
- internal
+ db:
+ image: mysql
+ restart: unless-stopped
+ container_name: mlflow_db
+ expose:
+ - "3306"
+ environment:
+ - MYSQL_DATABASE=${MYSQL_DATABASE}
+ - MYSQL_USER=${MYSQL_USER}
+ - MYSQL_PASSWORD=${MYSQL_PASSWORD}
+ - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
+ volumes:
+ - db_volume:/var/lib/mysql
+ networks:
+ - internal
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "{MYSQL_USER}", "-p{MYSQL_PASSWORD}"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
networks:
internal:
public:
driver: bridge
volumes:
+ db_volume:
minio_volume:
Não há muita novidade aqui. Estamos expondo a porta 3306, que é a porta padrão do MySQL, configurando dados do banco de dados, como base de dados, usuário e senha, um volume para armazenamento persistente e uso da rede internal
(somente o mlflow precisará acessar esse serviço). Também definimos uma condição de healthcheck
que usa o comando mysqladmin
para testar se o serviço já está respondendo.
Modifique também o arquivo .env
para adicionar as informações adicionadas:
AWS_ACCESS_KEY_ID=admin
AWS_SECRET_ACCESS_KEY=senhasenha
AWS_BUCKET_NAME=mlflow
+MYSQL_DATABASE=mlflow
+MYSQL_USER=mlflow_user
+MYSQL_PASSWORD=mlflow_password
+MYSQL_ROOT_PASSWORD=senhasenha
Já podemos subir os serviços e ver o resultado. Na verdade não há muito o que ver, exceto as mensagens no terminal informando que o MySQL está de fato rodando.
10.3.3 Serviço do MLflow
O terceiro e último serviço que iremos rodar é o MLflow. Também não é difícil configurá-lo no docker compose
, sendo basicamente configurações e endereços dos demais serviços para a orquestração. Porém, não existe uma imagem oficial do mlflow para simplesmente utilizarmos no Docker compose. No entanto, sua instalação para efeitos de demonstração é trivial, de modo que faremos isso usando um arquivo Dockerfile
, com o seguinte conteúdo:
FROM python
RUN pip install mlflow boto3 pymysql cryptography
ADD . /app
WORKDIR /app
Essa imagem é baseada em uma imagem padrão python
, e terá instalado o MLflow e demais dependências necessárias para conexão aos demais serviços.
Agora vamos modificar o compose.yml
:
services:
minio:
image: minio/minio
restart: unless-stopped
ports:
- "9000:9000"
- "9001:9001"
expose:
- 53
environment:
- MINIO_ROOT_USER=${AWS_ACCESS_KEY_ID}
- MINIO_ROOT_PASSWORD=${AWS_SECRET_ACCESS_KEY}
healthcheck:
test: timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1
interval: 1s
timeout: 10s
retries: 5
command: server /data --console-address ":9001"
networks:
- internal
- public
volumes:
- minio_volume:/data
create_s3_buckets:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
bash -c "
mc alias set minio http://minio:9000 '${AWS_ACCESS_KEY_ID}' '${AWS_SECRET_ACCESS_KEY}' &&
mc mb minio/${AWS_BUCKET_NAME}
"
networks:
- internal
db:
image: mysql
restart: unless-stopped
container_name: mlflow_db
expose:
- "3306"
environment:
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
volumes:
- db_volume:/var/lib/mysql
networks:
- internal
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "{MYSQL_USER}", "-p{MYSQL_PASSWORD}"]
interval: 30s
timeout: 10s
retries: 5
+ mlflow:
+ container_name: mlflow-server-container
+ image: mlflow-server
+ restart: unless-stopped
+ depends_on:
+ db:
+ condition: service_healthy
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - "5000:5000"
+ environment:
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
+ - AWS_DEFAULT_REGION=${AWS_REGION}
+ - MLFLOW_S3_ENDPOINT_URL=http://minio:9000
+ networks:
+ - public
+ - internal
+ entrypoint: mlflow server --backend-store-uri mysql+pymysql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE} --artifacts-destination s3://${AWS_BUCKET_NAME}/ --serve-artifacts -h 0.0.0.0
networks:
internal:
public:
driver: bridge
volumes:
db_volume:
minio_volume:
Ainda falta configurar uma última coisa no .env
:
AWS_ACCESS_KEY_ID=admin
AWS_SECRET_ACCESS_KEY=senhasenha
AWS_BUCKET_NAME=mlflow
+AWS_REGION=us-east-1
MYSQL_DATABASE=mlflow
MYSQL_USER=mlflow_user
MYSQL_PASSWORD=mlflow_password
MYSQL_ROOT_PASSWORD=senhasenha
Esta variável define em qual região da AWS o bucket está armazenado. Como o MinIO roda localmente, este valor não é nenhum valor real, mas é necessário para que a configuração fique completa para quando isso for migrado para a AWS realmente.
O principal a ser analisado aqui é o entrypoint:
mlflow server --backend-store-uri mysql+pymysql://${MYSQL_USER}:${MYSQL_PASSWORD}@db:3306/${MYSQL_DATABASE} --artifacts-destination s3://${AWS_BUCKET_NAME}/ --serve-artifacts -h 0.0.0.0
mlflow server
: comando que inicia o MLflow--backend-store-uri XXX
: indica ao MLflow onde armazenar os metadados referentes aos modelos (no caso, o MySQL)--artifacts-destination s3://${AWS_BUCKET_NAME}/ --serve-artifacts
: indica ao MLflow onde armazenar os artefatos (no caso, o MinIO, ou poderia ser Amazon S3). O mlflow server estará funcionando como um proxy para armazenar e recuperar os artefatos do local especificado-h 0.0.0.0
: diz ao MLflow para aceitar requisições de fora da máquina local
Pronto, basta executar o comando docker compose up
e teremos tudo funcionando. Estamos com uma instância do MLflow completamente configurada e acessível no endereço http://localhost:5000
. Na próxima seção mostraremos como utilizar essa instância para aproveitar das funcionalidades dos componentes do MLflow de forma centralizada.
Last updated