O autor escolheu o Internet Archive para receber uma doação como parte do programa Write for DOnations.

Introdução

O desenvolvimento de aplicações web pode se tornar complexo e demorado ao criar e manter várias tecnologias diferentes. A consideração de opções de menor peso projetadas para reduzir a complexidade e o tempo de produção da sua aplicação pode resultar em uma solução mais flexível e escalável. Como um micro web framework construído em Python, o Flask fornece uma maneira extensível para os desenvolvedores crescerem suas aplicações através de extensões que podem ser integradas em projetos. Para continuar a escalabilidade da pilha de tecnologia de um desenvolvedor, o MongoDB é um banco de dados NoSQL projetado para escalar e trabalhar com alterações frequentes. Os desenvolvedores podem usar o Docker para simplificar o processo de empacotamento e deploy de suas aplicações.

O Docker Compose simplificou ainda mais o ambiente de desenvolvimento, permitindo definir sua infraestrutura, incluindo serviços de aplicações, volumes de rede e montagens, em um único arquivo. O uso do Docker Compose fornece facilidade de uso ao executar vários comandos docker container run. Ele permite definir todos os seus serviços em um único arquivo Compose e, com um único comando, você cria e inicia todos os serviços a partir da sua configuração. Isso garante que haja controle de versão em toda a infraestrutura de container. O Docker Compose usa um nome de projeto para isolar ambientes um do outro, permitindo executar vários ambientes em um único host.

Neste tutorial, você criará, empacotará e executará sua aplicação web de lista de tarefas com o Flask, Nginx e MongoDB dentro de containers Docker. Você definirá toda a configuração da pilha em um arquivo docker-compose.yml, juntamente com os arquivos de configuração para Python, MongoDB e Nginx. O Flask requer um servidor web para atender solicitações HTTP, portanto você também usará o Gunicorn, que é um servidor HTTP Python WSGI, para atender à aplicação. O Nginx atua como um servidor de proxy reverso que encaminha solicitações ao Gunicorn para processamento.

Pré-requisitos

Para seguir este tutorial, você precisará do seguinte:

Passo 1 — Escrevendo a Configuração da Pilha no Docker Compose

A construção de suas aplicações no Docker permite o versionamento da infraestrutura facilmente, dependendo das alterações de configuração feitas no Docker Compose. A infraestrutura pode ser definida em um único arquivo e criada com um único comando. Neste passo, você configurará o arquivo docker-compose.yml para executar sua aplicação Flask.

O arquivo docker-compose.yml permite definir sua infraestrutura de aplicações como serviços individuais. Os serviços podem ser conectados entre si e cada um pode ter um volume anexado a ele para armazenamento persistente. Os volumes são armazenados em uma parte do sistema de arquivos do host gerenciado pelo Docker (/var/lib/docker/volumes/ no Linux).

Os volumes são a melhor maneira de persistir os dados no Docker, pois os dados nos volumes podem ser exportados ou compartilhados com outras aplicações. Para obter informações adicionais sobre o compartilhamento de dados no Docker, consulte How To Share Data Between the Docker Container and the Host.

Para começar, crie um diretório para a aplicação no diretório home do seu servidor:

  • mkdir flaskapp

Mova para o diretório recém-criado:

  • cd flaskapp

Em seguida, crie o arquivo docker-compose.yml:

  • nano docker-compose.yml

O arquivo docker-compose.yml começa com um número de versão que identifica a versão do arquivo Docker Compose. A versão 3 do arquivo do Docker Compose é direcionada à versão do Docker Engine 1.13.0+, que é um pré-requisito para esta configuração. Você também adicionará a tag services que você definirá no próximo passo:

docker-compose.yml
version: '3'
services:

Agora você definirá o flask como o primeiro serviço no seu arquivo docker-compose.yml. Adicione o seguinte código para definir o serviço Flask:

docker-compose.yml
. . .
  flask:
    build:
      context: app
      dockerfile: Dockerfile
    container_name: flask
    image: digitalocean.com/flask-python:3.6
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_DEBUG: "False"
      APP_PORT: 5000
      MONGODB_DATABASE: flaskdb
      MONGODB_USERNAME: flaskuser
      MONGODB_PASSWORD: sua_senha_do_mongodb
      MONGODB_HOSTNAME: mongodb
    volumes:
      - appdata:/var/www
    depends_on:
      - mongodb
    networks:
      - frontend
      - backend

A propriedade build define o contexto do build. Nesse caso, a pasta app que conterá o Dockerfile.

Você usa a propriedade container_name para definir um nome para cada container. A propriedade image especifica o nome da imagem e como a imagem do Docker será identificada ou tagueada. A propriedade restart define como o container deve ser reiniciado — no seu caso é unless-stopped. Isso significa que seus containers serão parados apenas quando o Docker Engine for parado/reiniciado ou quando você os interromper explicitamente. O benefício de usar a propriedade unless-stopped é que os containers serão iniciados automaticamente assim que o Docker Engine for reiniciado ou ocorrer algum erro.

A propriedade environment contém as variáveis de ambiente que são passadas para o container. Você precisa fornecer uma senha segura para a variável de ambiente MONGODB_PASSWORD. A propriedade volumes define os volumes que o serviço está usando. No seu caso, o volume appdata é montado dentro do container no diretório /var/www. A propriedade depends_on define um serviço do qual o Flask depende para funcionar corretamente. Nesse caso, o serviço flask dependerá do mongodb, pois o serviço mongodb atua como o banco de dados da sua aplicação. O depends_on garante que o serviço flask seja executado apenas se o serviço mongodb estiver sendo executado.

A propriedade networks especifica frontend e backend como as redes às quais o serviçoflask terá acesso.

Com o serviço flask definido, você está pronto para adicionar a configuração do MongoDB ao arquivo. Neste exemplo, você usará a imagem mongo oficial na versão 4.0.8. Adicione o seguinte código ao seu arquivo docker-compose.yml seguindo o flask service:

docker-compose.yml
. . .
  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: senha_do_root_do_mongodb
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

O container_name para este serviço é mongodb com uma política de reinicialização unless-stopped. Você usa a propriedade command para definir o comando que será executado quando o container for iniciado. O comando mongod --auth desativará o logon no shell do MongoDB sem credenciais, o que protegerá o MongoDB exigindo autenticação.

As variáveis de ambiente MONGO_INITDB_ROOT_USERNAME e MONGO_INITDB_ROOT_PASSWORD criam um usuário root com as credenciais fornecidas, portanto, substitua o espaço reservado por uma senha forte.

O MongoDB armazena seus dados em /data/db por padrão, portanto os dados na pasta /data/db serão gravados no volume nomeado mongodbdata para persistência. Como resultado, você não perderá seus bancos de dados em caso de reinicialização. O serviço mongoDB não expõe nenhuma porta, portanto, o serviço estará acessível apenas através da rede backend.

Em seguida, você definirá o servidor web para sua aplicação. Adicione o seguinte código ao seu arquivo docker-compose.yml para configurar o Nginx:

docker-compose.yml
. . .
  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "false"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

Aqui você definiu o context do build, que é a pasta nginx que contém o Dockerfile. Com a propriedade image, você especifica a imagem usada para taguear e executar o container. A propriedade ports configurará o serviço Nginx para ser acessível publicamente através das portas :80 e :443 e volumes montam o volume nginxdata dentro do container no diretório /var/log/nginx.

Você definiu o serviço do qual o serviço do servidor web depends_on como flask. Finalmente, a propriedade networks define que o serviço do servidor web terá acesso à rede frontend.

Em seguida, você criará bridge networks para permitir que os containers se comuniquem. Acrescente as seguintes linhas ao final do seu arquivo:

docker-compose.yml
. . .
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

Você definiu duas redes — frontend e backend — para os serviços aos quais se conectar. Os serviços de front-end, como o Nginx, se conectam à rede front-end, pois precisa ser acessível ao público. Serviços de back-end, como o MongoDB, serão conectados à rede back-end para impedir o acesso não autorizado ao serviço.

Em seguida, você usará os volumes para persistir os arquivos de banco de dados, aplicação e configuração. Como sua aplicação usará os bancos de dados e arquivos, é imperativo persistir as alterações feitas neles. Os volumes são gerenciados pelo Docker e armazenados no sistema de arquivos. Adicione este código ao arquivo docker-compose.yml para configurar os volumes:

docker-compose.yml
. . .
volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

A seção volumes declara os volumes que a aplicação usará para persistir os dados. Aqui você definiu os volumes mongodbdata, appdata e nginxdata para persistir seus bancos de dados MongoDB, dados da aplicação Flask e logs do servidor web Nginx, respectivamente. Todos esses volumes usam um driver local para armazenar os dados localmente. Os volumes são usados para persistir esses dados, para que dados como os bancos de dados MongoDB e os logs do servidor web Nginx possam ser perdidos depois que você reiniciar os containers.

Seu arquivo docker-compose.yml completo terá a seguinte aparência:

docker-compose.yml
version: '3'
services:

  flask:
    build:
      context: app
      dockerfile: Dockerfile
    container_name: flask
    image: digitalocean.com/flask-python:3.6
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_DEBUG: "False"
      APP_PORT: 5000
      MONGODB_DATABASE: flaskdb
      MONGODB_USERNAME: flaskuser
      MONGODB_PASSWORD: sua_senha_do_mongodb
      MONGODB_HOSTNAME: mongodb
    volumes:
      - appdata:/var/www
    depends_on:
      - mongodb
    networks:
      - frontend
      - backend

  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: senha_do_root_do_mongodb
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "true"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

Salve o arquivo e saia do editor após verificar sua configuração.

Você definiu a configuração do Docker para toda a pilha de aplicações no arquivo docker-compose.yml. Agora você passará a escrever os Dockerfiles para o Flask e o servidor web.

Passo 2 — Escrevendo os Dokerfiles para o Flask e para o Servidor Web

Com o Docker, você pode criar containers para executar suas aplicações a partir de um arquivo chamado Dockerfile. O Dockerfile é uma ferramenta que permite criar imagens personalizadas que você pode usar para instalar o software exigido pela sua aplicação e configurar seus containers com base em seus requisitos. Você pode enviar as imagens personalizadas que você criou para o Docker Hub ou qualquer registro privado.

Neste passo, você escreverá os Dockerfiles para os serviços do Flask e do servidor web. Para começar, crie o diretório app para o sua alicação Flask:

  • mkdir app

Em seguida, crie o Dockerfile para a sua aplicação Flask no diretório app:

  • nano app/Dockerfile

Adicione o seguinte código ao arquivo para personalizar seu container do Flask:

app/Dockerfile
FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="Nome Sobrenome <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

Neste Dockerfile, você está criando uma imagem com base na imagem 3.6.8-alpine3.9, que é baseada no Alpine 3.9 com Python 3.6.8 pré-instalado.

A diretiva ENV é usada para definir as variáveis de ambiente para o nosso grupo e ID do usuário. A Linux Standard Base (LSB) especifica que UIDs e GIDs 0-99 são alocados estaticamente pelo sistema. UIDs 100-999 devem ser alocados dinamicamente para usuários e grupos do sistema. UIDs 1000-59999 devem ser alocados dinamicamente para contas de usuário. Tendo isso em mente, você pode atribuir com segurança um UID e GID de 1000, além disso, você pode alterar o UID/GID atualizando o GROUP_ID e o USER_ID para atender aos seus requisitos.

A diretiva WORKDIR define o diretório de trabalho do container. Certifique-se de substituir o campo LABEL MAINTAINER pelo seu nome e endereço de e-mail.

Adicione o seguinte bloco de código para copiar o aplicativo Flask no container e instalar as dependências necessárias:

app/Dockerfile
. . .
ADD ./requirements.txt /var/www/requirements.txt
RUN pip install -r requirements.txt
ADD . /var/www/
RUN pip install gunicorn

O código a seguir usará a diretiva ADD para copiar arquivos do diretório local app para o diretório /var/www no container. Em seguida, o Dockerfile usará a diretiva RUN para instalar o Gunicorn e os pacotes especificados no arquivo requirements.txt, que você criará posteriormente no tutorial.

O seguinte bloco de código adiciona um novo usuário e grupo e inicializa a aplicação:

app/Dockerfile
. . .
RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

Por padrão, os containers Docker são executados como o usuário root. O usuário root tem acesso a tudo no sistema, portanto, as implicações de uma violação de segurança podem ser desastrosas. Para mitigar esse risco de segurança, isso criará um novo usuário e grupo que terá acesso apenas ao diretório /var/www.

Este código usará primeiro o comando addgroup para criar um novo grupo chamado www. A flag -g definirá o ID do grupo para a variável ENV GROUP_ID=1000, definida anteriormente no Dockerfile.

A linha adduser -D -u $USER_ID -G www www -s /bin/sh cria um usuário www com um ID de usuário 1000, conforme definido pela variável ENV. A flag -s cria o diretório home do usuário se ele não existir e define o shell de login padrão como /bin/sh. A flag -G é usada para definir o grupo de login inicial do usuário como www, que foi criado pelo comando anterior.

O comando USER define que os programas executados no container usarão o usuário www. O Gunicorn escutará na porta :5000, então você abrirá esta porta com o comando EXPOSE.

Finalmente a linha CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"] executa o comando para iniciar o servidor Gunicorn com quatro workers escutando na porta 5000. O número geralmente deve estar entre 2 a 4 workers por núcleo no servidor. A documentação do Gunicorn recomenda (2 x $num_cores) + 1 como o número de workers para começar.

O seu Dockerfile completo terá a seguinte aparência:

app/Dockerfile
FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="Nome Sobrenome <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

ADD . /var/www/
RUN pip install -r requirements.txt
RUN pip install gunicorn

RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

Salve o arquivo e saia do editor de texto.

Em seguida, crie um novo diretório para manter sua configuração do Nginx:

  • mkdir nginx

Depois, crie o Dockerfile para o seu servidor web Nginx no diretório nginx:

  • nano nginx/Dockerfile

Adicione o seguinte código ao arquivo para criar o Dockerfile que criará a imagem para seu container Nginx:

nginx/Dockerfile
FROM digitalocean.com/alpine:latest

LABEL MAINTAINER="Nome Sobrenome <example@domain.com>"

RUN apk --update add nginx && \
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log && \
    mkdir /etc/nginx/sites-enabled/ && \
    mkdir -p /run/nginx && \
    rm -rf /etc/nginx/conf.d/default.conf && \
    rm -rf /var/cache/apk/*

COPY conf.d/app.conf /etc/nginx/conf.d/app.conf

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

Este Dockerfile de Nginx usa uma imagem base alpine, que é uma pequena distribuição Linux com uma superfície de ataque mínima criada para segurança.

Na diretiva RUN, você está instalando o nginx e criando links simbólicos para publicar os logs de erro e de acesso na saída padrão de erro (/dev/stderr) e na saída padrão (/dev/stdout). A publicação de erros na saída padrão de erro e na saída padrão é uma prática recomendada, pois os containers são efêmeros. Dessa forma, os logs são enviados para os logs do docker e, a partir daí, você pode encaminhar seus logs para um serviço de log como a pilha Elastic para persistência. Depois disso feito, são executados comandos para remover o default.conf e /var/cache/apk/* para reduzir o tamanho da imagem resultante. A execução de todos esses comandos em um único RUN diminui o número de camadas na imagem, o que também reduz o tamanho da imagem resultante.

A diretiva COPY copia a configuração app.conf do servidor web dentro do container. A diretiva EXPOSE garante que os containers escutem nas portas :80 e :443, pois sua aplicação será executada na porta :80 com a :443 como porta segura.

Finalmente, a diretiva CMD define o comando para iniciar o servidor Nginx.

Salve o arquivo e saia do editor de texto.

Agora que o Dockerfile está pronto, você está pronto para configurar o proxy reverso do Nginx para rotear o tráfego para a aplicação Flask.

Passo 3 — Configurando o Proxy Reverso Nginx

Neste passo, você configurará o Nginx como um proxy reverso para encaminhar solicitações ao Gunicorn na porta :5000. Um servidor de proxy reverso é usado para direcionar solicitações de clientes ao servidor de back-end apropriado. Ele fornece uma camada adicional de abstração e controle para garantir o fluxo suave do tráfego de rede entre clientes e servidores.

Comece criando o diretório nginx/conf.d:

  • mkdir nginx/conf.d

Para configurar o Nginx, você precisa criar um arquivo app.conf com a seguinte configuração na pasta nginx/conf.d/. O arquivo app.conf contém a configuração que o proxy reverso precisa para encaminhar as solicitações ao Gunicorn.

  • nano nginx/conf.d/app.conf

Coloque o seguinte conteúdo no arquivo app.conf:

nginx/conf.d/app.conf
upstream app_server {
    server flask:5000;
}

server {
    listen 80;
    server_name _;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    client_max_body_size 64M;

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
        gzip_static on;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_buffering off;
        proxy_redirect off;
        proxy_pass http://app_server;
    }
}

Isso primeiro definirá o servidor upstream, que normalmente é usado para especificar um servidor web ou servidor de aplicação para roteamento ou balanceamento de carga.

Seu servidor upstream, app_server, define o endereço do servidor com a diretiva server, que é identificada pelo nome do container flask:5000.

A configuração do servidor web Nginx é definida no bloco server. A diretiva listen define o número da porta na qual o servidor escutará as solicitações recebidas. As diretivas error_log e access_log definem os arquivos para gravar logs. A diretiva proxy_pass é usada para configurar o servidor upstream para encaminhar os pedidos para http://app_server.

Salve e feche o arquivo.

Com o servidor web Nginx configurado, é possível criar a API da lista de tarefas do Flask.

Passo 4 — Criando a API da Lista de Tarefas do Flask

Agora que você construiu seu ambiente, você está pronto para criar sua aplicação. Neste passo, você escreverá uma aplicação de API da lista de tarefas que salvará e exibirá as notas de tarefas enviadas de uma solicitação POST.

Comece criando o arquivo requirements.txt no diretório app:

  • nano app/requirements.txt

Este arquivo é usado para instalar as dependências para sua aplicação. A implementação deste tutorial usará o Flask, Flask-PyMongo, e requests. Adicione o seguinte ao arquivo requirements.txt:

app/requirements.txt
Flask==1.0.2
Flask-PyMongo==2.2.0
requests==2.20.1

Salve o arquivo e saia do editor após inserir os requisitos.

Em seguida, crie o arquivo app.py para conter o código da aplicação Flask no diretório app:

  • nano app/app.py

No seu novo arquivo app.py, digite este código para importar as dependências:

app/app.py
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

O pacote os é usado para importar as variáveis de ambiente. Da biblioteca flask, você importou os objetos Flask, request e jsonify para instanciar a aplicação, tratar solicitações e enviar respostas JSON, respectivamente. Do flask_pymongo, você importou o objeto PyMongo para interagir com o MongoDB.

Em seguida, adicione o código necessário para conectar-se ao MongoDB:

app/app.py
. . .
application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

O Flask (__ name __) carrega o objeto da aplicação na variável application. A seguir, o código cria a string de conexão ao MongoDB a partir das variáveis de ambiente usando o os.environ. Passar o objeto application para o método PyMongo() fornecerá a você o objeto mongo, que por sua vez fornece o objeto db em mongo.db.

Agora você adicionará o código para criar uma mensagem de index:

app/app.py
. . .
@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

O @application.route('/') define a rota GET / da sua API. Aqui sua função index() retorna uma string JSON usando o método jsonify.

Em seguida, adicione a rota /todo para listar todas as tarefas a fazer:

app/app.py
. . .
@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

O @application.route('/todo') define a rota GET /todo da sua API, que retorna as tarefas no banco de dados. O método db.todo.find() retorna todas as tarefas a fazer no banco de dados. A seguir, você itera sobre _todos para construir um item que inclui apenas o id e todo dos objetos anexando-os a uma matriz data e finalmente os retorna como JSON.

Em seguida, adicione o código para criar a tarefa:

app/app.py
. . .
@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

O @application.route('/todo') define a rota POST /todo da sua API, que cria uma nota de tarefa no banco de dados. O request.get_json(force=True) obtém o JSON que você publica na rota e o item é usado para construir o JSON que será salvo na tarefa. O db.todo.insert_one(item) é usado para inserir um item no banco de dados. Depois que a tarefa é salva no banco de dados, você retorna uma resposta JSON com um código de status 201 CREATED.

Agora você adiciona o código para executar a aplicação:

app/app.py
. . .
if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

A condição __name__ == "__main__" é usada para verificar se a variável global, __name__, no módulo é o entry point para o seu programa, é "__main__" e, em seguida, executar a aplicação. Se __name__ for igual a "__main__", então o código dentro do bloco if executará a aplicação usando este comando application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG).

A seguir, obtemos os valores para ENVIRONMENT_DEBUG e ENVIRONMENT_PORT das variáveis de ambiente usando os.environ.get(), usando a chave como primeiro parâmetro e o valor padrão como o segundo parâmetro. O application.run() define os valores host, port e debug para a aplicação.

O arquivo app.py completo ficará assim:

app/app.py
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

Salve o arquivo e saia do editor.

Em seguida, crie o arquivo wsgi.py no diretório app.

  • nano app/wsgi.py

O arquivo wsgi.py cria um objeto de aplicação (ou que pode ser chamado) para que o servidor possa usá-lo. Cada vez que uma solicitação é recebida, o servidor usa esse objeto de aplicação para executar os manipuladores de solicitação da aplicação ao fazer o parse da URL.

Coloque o seguinte conteúdo no arquivo wsgi.py, salve o arquivo e saia do editor de texto:

app/wsgi.py
from app import application

if __name__ == "__main__":
  application.run()

Este arquivo wsgi.py importa o objeto da aplicação do arquivo app.py e cria um objeto de aplicação para o servidor Gunicorn.

A aplicação de lista de tarefas já está em vigor, então você está pronto para começar a executar a aplicação em containers.

Passo 5 — Criando e Executando os Containers

Agora que você definiu todos os serviços no seu arquivo docker-compose.yml e suas configurações, você pode iniciar os containers.

Como os serviços são definidos em um único arquivo, você precisa emitir um único comando para iniciar os containers, criar os volumes e configurar as redes. Este comando também cria a imagem para a sua aplicação Flask e o servidor web Nginx. Execute o seguinte comando para criar os containers:

  • docker-compose up -d

Ao executar o comando pela primeira vez, ele fará o download de todas as imagens necessárias do Docker, o que pode levar algum tempo. Depois que as imagens são baixadas e armazenadas na sua máquina local, o docker-compose criará seus containers. A flag -d roda o processo como um daemon, o que permite que ele seja executado como um processo em segundo plano.

Use o comando a seguir para listar os containers em execução quando o processo de compilação estiver concluído:

  • docker ps

Você verá uma saída semelhante à seguinte:

Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f20e9a7fd2b9 digitalocean.com/webserver:latest "nginx -g 'daemon of…" 2 weeks ago Up 2 weeks 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp webserver 3d53ea054517 digitalocean.com/flask-python:3.6 "gunicorn -w 4 --bin…" 2 weeks ago Up 2 weeks 5000/tcp flask 96f5a91fc0db mongo:4.0.8 "docker-entrypoint.s…" 2 weeks ago Up 2 weeks 27017/tcp mongodb

O CONTAINER ID é um identificador exclusivo usado para acessar containers. IMAGE define o nome da imagem para o container especificado. O campo NAMES é o nome do serviço sob o qual os containers são criados. Semelhante ao CONTAINER ID, eles podem ser usados para acessar containers. Finalmente, STATUS fornece informações sobre o estado do container, seja ele executando, reiniciando ou parado.

Você usou o comando docker-compose para criar seus containers a partir de seus arquivos de configuração. No próximo passo, você criará um usuário MongoDB para sua aplicação.

Passo 6 — Criando um Usuário para o seu Banco de Dados MongoDB

Por padrão, o MongoDB permite que os usuários efetuem login sem credenciais e concede privilégios ilimitados. Neste passo, você protegerá seu banco de dados MongoDB criando um usuário dedicado para acessá-lo.

Para fazer isso, você precisará do nome de usuário e senha raiz definidos nas variáveis de ambiente do arquivo docker-compose.yml, que são MONGO_INITDB_ROOT_USERNAME e MONGO_INITDB_ROOT_PASSWORD para o serviço mongodb. Em geral, é melhor evitar o uso da conta administrativa root ao interagir com o banco de dados. Em vez disso, você criará um usuário de banco de dados dedicado para sua aplicação Flask, bem como um novo banco de dados que a aplicação Flask terá permissão para acessar.

Para criar um novo usuário, primeiro inicie um shell interativo no container mongodb:

  • docker exec -it mongodb bash

Você usa o comando docker exec para executar um comando dentro de um container em execução junto com a flag -it para executar um shell interativo dentro do container.

Uma vez dentro do container, efetue login na conta administrativa root do MongoDB:

  • mongo -u mongodbuser -p

Você será solicitado pela senha digitada como o valor da variável MONGO_INITDB_ROOT_PASSWORD no arquivo docker-compose.yml. A senha pode ser alterada definindo um novo valor para MONGO_INITDB_ROOT_PASSWORD no serviço mongodb, nesse caso você terá que executar novamente o comando docker-compose up -d

Execute o comando show dbs; para listar todos os bancos de dados:

  • show dbs;

Você verá a seguinte saída:

Output
admin 0.000GB config 0.000GB local 0.000GB 5 rows in set (0.00 sec)

O banco de dados admin é um banco de dados especial que concede permissões administrativas aos usuários. Se um usuário tiver acesso de leitura ao banco de dados admin, ele terá permissões de leitura e gravação em todos os outros bancos de dados. Como a saída lista o banco de dados admin, o usuário tem acesso a esse banco de dados e, portanto, pode ler e gravar em todos os outros bancos de dados.

Salvando a primeira nota de tarefa a executar automaticamente criará o banco de dados MongoDB. O MongoDB permite que você alterne para um banco de dados que não existe usando o comando use database. Ele cria um banco de dados quando um documento é salvo em uma coleção. Portanto, o banco de dados não é criado aqui; isso acontecerá quando você salvar sua primeira nota de tarefa no banco de dados a partir da API. Execute o comando use para alternar para o banco de dados flaskdb:

  • use flaskdb

Em seguida, crie um novo usuário com permissão para acessar este banco de dados:

  • db.createUser({user: 'flaskuser', pwd: 'your password', roles: [{role: 'readWrite', db: 'flaskdb'}]})
  • exit

Este comando cria um usuário chamado flaskuser com acesso readWrite ao banco de dados flaskdb. Certifique-se de usar uma senha segura no campo pwd. O user e a pwd aqui são os valores que você definiu no arquivo docker-compose.yml na seção de variáveis de ambiente para o serviço flask.

Efetue login no banco de dados autenticado com o seguinte comando:

  • mongo -u flaskuser -p your password --authenticationDatabase flaskdb

Agora que você adicionou o usuário, efetue logout do banco de dados.

  • exit

E, finalmente, saia do container:

  • exit

Agora você configurou um banco de dados dedicado e uma conta de usuário para sua aplicação Flask. Os componentes do banco de dados estão prontos, portanto, agora você pode executar a aplicação de lista de tarefas do Flask.

Passo 7 — Executando a Aplicação de Lista de Tarefas do Flask

Agora que seus serviços estão configurados e em execução, você pode testar sua aplicação navegando até http://ip_do_seu_servidor em um navegador. Além disso, você pode executar curl para ver a resposta JSON do Flask:

  • curl -i http://ip_do_seu_servidor

Você receberá a seguinte resposta:

Output
{"message":"Welcome to the Dockerized Flask MongoDB app!","status":true}

A configuração para a aplicação Flask é passada para a aplicação a partir do arquivo docker-compose.yml. A configuração referente à conexão com o banco de dados é definida usando as variáveis MONGODB_* definidas na seção environment do serviço flask.

Para testar tudo, crie uma nota de tarefa usando a API do Flask. Você pode fazer isso com uma solicitação POST no curl para a rota /todo:

  • curl -i -H "Content-Type: application/json" -X POST -d '{"todo": "Dockerize Flask application with MongoDB backend"}' http://ip_do_seu_servidor/todo

Essa solicitação resulta em uma resposta com um código de status 201 CREATED quando o item de tarefa é salvo no MongoDB:

Output
{"message":"To-do saved successfully!","status":true}

Você pode listar todas as notas de tarefas do MongoDB com uma solicitação GET para a rota /todo:

  • curl -i http://ip_do_seu_servidor/todo
Output
{"data":[{"id":"5c9fa25591cb7b000a180b60","todo":"Dockerize Flask application with MongoDB backend"}],"status":true}

Com isso, você Dockerizou uma API do Flask executando um back-end do MongoDB com o Nginx como um proxy reverso deployado em seus servidores. Para um ambiente de produção, você pode usar o sudo systemctl enable docker para garantir que o serviço Docker seja iniciado automaticamente em tempo de execução ou runtime.

Conclusão

Neste tutorial, você fez o deloy de uma aplicação Flask com Docker, MongoDB, Nginx e Gunicorn. Agora você tem uma aplicação de API stateless moderna e funcional que pode ser escalada. Embora você possa alcançar esse resultado usando um comando como docker container run, o docker-compose.yml simplifica seu trabalho, pois essa pilha pode ser colocada no controle de versão e atualizada conforme necessário.

A partir daqui, você também pode dar uma olhada em nossos tutoriais de Framework do Python.

0 Comments

Creative Commons License