Ir para o conteúdo

Conceitos de Implantações

Ao implantar um aplicativo FastAPI, ou na verdade, qualquer tipo de API da web, há vários conceitos com os quais você provavelmente se importa e, usando-os, você pode encontrar a maneira mais apropriada de implantar seu aplicativo.

Alguns dos conceitos importantes são:

  • Segurança - HTTPS
  • Executando na inicialização
  • Reinicializações
  • Replicação (o número de processos em execução)
  • Memória
  • Etapas anteriores antes de iniciar

Veremos como eles afetariam as implantações.

No final, o principal objetivo é ser capaz de atender seus clientes de API de uma forma segura, evitar interrupções e usar os recursos de computação (por exemplo, servidores remotos/máquinas virtuais) da forma mais eficiente possível. 🚀

Vou lhe contar um pouco mais sobre esses conceitos aqui, e espero que isso lhe dê a intuição necessária para decidir como implantar sua API em ambientes muito diferentes, possivelmente até mesmo em futuros ambientes que ainda não existem.

Ao considerar esses conceitos, você será capaz de avaliar e projetar a melhor maneira de implantar suas próprias APIs.

Nos próximos capítulos, darei a você mais receitas concretas para implantar aplicativos FastAPI.

Mas por enquanto, vamos verificar essas importantes ideias conceituais. Esses conceitos também se aplicam a qualquer outro tipo de API da web. 💡

Segurança - HTTPS

No capítulo anterior sobre HTTPS aprendemos como o HTTPS fornece criptografia para sua API.

Também vimos que o HTTPS normalmente é fornecido por um componente externo ao seu servidor de aplicativos, um Proxy de terminação TLS.

E tem que haver algo responsável por renovar os certificados HTTPS, pode ser o mesmo componente ou pode ser algo diferente.

Ferramentas de exemplo para HTTPS

Algumas das ferramentas que você pode usar como um proxy de terminação TLS são:

  • Traefik
    • Lida automaticamente com renovações de certificados ✨
  • Caddy
    • Lida automaticamente com renovações de certificados ✨
  • Nginx
    • Com um componente externo como o Certbot para renovações de certificados
  • HAProxy
    • Com um componente externo como o Certbot para renovações de certificados
  • Kubernetes com um controlador Ingress como o Nginx
    • Com um componente externo como cert-manager para renovações de certificados
  • Gerenciado internamente por um provedor de nuvem como parte de seus serviços (leia abaixo 👇)

Outra opção é que você poderia usar um serviço de nuvem que faz mais do trabalho, incluindo a configuração de HTTPS. Ele pode ter algumas restrições ou cobrar mais, etc. Mas, nesse caso, você não teria que configurar um Proxy de terminação TLS sozinho.

Mostrarei alguns exemplos concretos nos próximos capítulos.


Os próximos conceitos a serem considerados são todos sobre o programa que executa sua API real (por exemplo, Uvicorn).

Programa e Processo

Falaremos muito sobre o "processo" em execução, então é útil ter clareza sobre o que ele significa e qual é a diferença com a palavra "programa".

O que é um Programa

A palavra programa é comumente usada para descrever muitas coisas:

  • O código que você escreve, os arquivos Python.
  • O arquivo que pode ser executado pelo sistema operacional, por exemplo: python, python.exe ou uvicorn.
  • Um programa específico enquanto está em execução no sistema operacional, usando a CPU e armazenando coisas na memória. Isso também é chamado de processo.

O que é um Processo

A palavra processo normalmente é usada de forma mais específica, referindo-se apenas ao que está sendo executado no sistema operacional (como no último ponto acima):

  • Um programa específico enquanto está em execução no sistema operacional.
    • Isso não se refere ao arquivo, nem ao código, refere-se especificamente à coisa que está sendo executada e gerenciada pelo sistema operacional.
  • Qualquer programa, qualquer código, só pode fazer coisas quando está sendo executado. Então, quando há um processo em execução.
  • O processo pode ser terminado (ou "morto") por você, ou pelo sistema operacional. Nesse ponto, ele para de rodar/ser executado, e ele não pode mais fazer coisas.
  • Cada aplicativo que você tem em execução no seu computador tem algum processo por trás dele, cada programa em execução, cada janela, etc. E normalmente há muitos processos em execução ao mesmo tempo enquanto um computador está ligado.
  • Pode haver vários processos do mesmo programa em execução ao mesmo tempo.

Se você verificar o "gerenciador de tarefas" ou o "monitor do sistema" (ou ferramentas semelhantes) no seu sistema operacional, poderá ver muitos desses processos em execução.

E, por exemplo, você provavelmente verá que há vários processos executando o mesmo programa de navegador (Firefox, Chrome, Edge, etc.). Eles normalmente executam um processo por aba, além de alguns outros processos extras.


Agora que sabemos a diferença entre os termos processo e programa, vamos continuar falando sobre implantações.

Executando na inicialização

Na maioria dos casos, quando você cria uma API web, você quer que ela esteja sempre em execução, ininterrupta, para que seus clientes possam sempre acessá-la. Isso é claro, a menos que você tenha um motivo específico para querer que ela seja executada somente em certas situações, mas na maioria das vezes você quer que ela esteja constantemente em execução e disponível.

Em um servidor remoto

Ao configurar um servidor remoto (um servidor em nuvem, uma máquina virtual, etc.), a coisa mais simples que você pode fazer é usar fastapi run (que usa Uvicorn) ou algo semelhante, manualmente, da mesma forma que você faz ao desenvolver localmente.

E funcionará e será útil durante o desenvolvimento.

Mas se sua conexão com o servidor for perdida, o processo em execução provavelmente morrerá.

E se o servidor for reiniciado (por exemplo, após atualizações ou migrações do provedor de nuvem), você provavelmente não notará. E por causa disso, você nem saberá que precisa reiniciar o processo manualmente. Então, sua API simplesmente permanecerá inativa. 😱

Executar automaticamente na inicialização

Em geral, você provavelmente desejará que o programa do servidor (por exemplo, Uvicorn) seja iniciado automaticamente na inicialização do servidor e, sem precisar de nenhuma intervenção humana, tenha um processo sempre em execução com sua API (por exemplo, Uvicorn executando seu aplicativo FastAPI).

Programa separado

Para conseguir isso, você normalmente terá um programa separado que garantiria que seu aplicativo fosse executado na inicialização. E em muitos casos, ele também garantiria que outros componentes ou aplicativos também fossem executados, por exemplo, um banco de dados.

Ferramentas de exemplo para executar na inicialização

Alguns exemplos de ferramentas que podem fazer esse trabalho são:

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker em Modo Swarm
  • Systemd
  • Supervisor
  • Gerenciado internamente por um provedor de nuvem como parte de seus serviços
  • Outros...

Darei exemplos mais concretos nos próximos capítulos.

Reinicializações

Semelhante a garantir que seu aplicativo seja executado na inicialização, você provavelmente também deseja garantir que ele seja reiniciado após falhas.

Nós cometemos erros

Nós, como humanos, cometemos erros o tempo todo. O software quase sempre tem bugs escondidos em lugares diferentes. 🐛

E nós, como desenvolvedores, continuamos aprimorando o código à medida que encontramos esses bugs e implementamos novos recursos (possivelmente adicionando novos bugs também 😅).

Pequenos erros são tratados automaticamente

Ao criar APIs da web com FastAPI, se houver um erro em nosso código, o FastAPI normalmente o conterá na única solicitação que acionou o erro. 🛡

O cliente receberá um Erro Interno do Servidor 500 para essa solicitação, mas o aplicativo continuará funcionando para as próximas solicitações em vez de travar completamente.

Erros maiores - Travamentos

No entanto, pode haver casos em que escrevemos algum código que trava todo o aplicativo, fazendo com que o Uvicorn e o Python travem. 💥

E ainda assim, você provavelmente não gostaria que o aplicativo permanecesse inativo porque houve um erro em um lugar, você provavelmente quer que ele continue em execução pelo menos para as operações de caminho que não estão quebradas.

Reiniciar após falha

Mas nos casos com erros realmente graves que travam o processo em execução, você vai querer um componente externo que seja responsável por reiniciar o processo, pelo menos algumas vezes...

Dica

...Embora se o aplicativo inteiro estiver travando imediatamente, provavelmente não faça sentido reiniciá-lo para sempre. Mas nesses casos, você provavelmente notará isso durante o desenvolvimento, ou pelo menos logo após a implantação.

Então, vamos nos concentrar nos casos principais, onde ele pode travar completamente em alguns casos específicos no futuro, e ainda faz sentido reiniciá-lo.

Você provavelmente gostaria de ter a coisa responsável por reiniciar seu aplicativo como um componente externo, porque a essa altura, o mesmo aplicativo com Uvicorn e Python já havia travado, então não há nada no mesmo código do mesmo aplicativo que possa fazer algo a respeito.

Ferramentas de exemplo para reiniciar automaticamente

Na maioria dos casos, a mesma ferramenta usada para executar o programa na inicialização também é usada para lidar com reinicializações automáticas.

Por exemplo, isso poderia ser resolvido por:

  • Docker
  • Kubernetes
  • Docker Compose
  • Docker no Modo Swarm
  • Systemd
  • Supervisor
  • Gerenciado internamente por um provedor de nuvem como parte de seus serviços
  • Outros...

Replicação - Processos e Memória

Com um aplicativo FastAPI, usando um programa de servidor como o comando fastapi que executa o Uvicorn, executá-lo uma vez em um processo pode atender a vários clientes simultaneamente.

Mas em muitos casos, você desejará executar vários processos de trabalho ao mesmo tempo.

Processos Múltiplos - Trabalhadores

Se você tiver mais clientes do que um único processo pode manipular (por exemplo, se a máquina virtual não for muito grande) e tiver vários núcleos na CPU do servidor, você poderá ter vários processos em execução com o mesmo aplicativo ao mesmo tempo e distribuir todas as solicitações entre eles.

Quando você executa vários processos do mesmo programa de API, eles são comumente chamados de trabalhadores.

Processos do Trabalhador e Portas

Lembra da documentação Sobre HTTPS que diz que apenas um processo pode escutar em uma combinação de porta e endereço IP em um servidor?

Isso ainda é verdade.

Então, para poder ter vários processos ao mesmo tempo, tem que haver um único processo escutando em uma porta que então transmite a comunicação para cada processo de trabalho de alguma forma.

Memória por Processo

Agora, quando o programa carrega coisas na memória, por exemplo, um modelo de aprendizado de máquina em uma variável, ou o conteúdo de um arquivo grande em uma variável, tudo isso consome um pouco da memória (RAM) do servidor.

E vários processos normalmente não compartilham nenhuma memória. Isso significa que cada processo em execução tem suas próprias coisas, variáveis ​​e memória. E se você estiver consumindo uma grande quantidade de memória em seu código, cada processo consumirá uma quantidade equivalente de memória.

Memória do servidor

Por exemplo, se seu código carrega um modelo de Machine Learning com 1 GB de tamanho, quando você executa um processo com sua API, ele consumirá pelo menos 1 GB de RAM. E se você iniciar 4 processos (4 trabalhadores), cada um consumirá 1 GB de RAM. Então, no total, sua API consumirá 4 GB de RAM.

E se o seu servidor remoto ou máquina virtual tiver apenas 3 GB de RAM, tentar carregar mais de 4 GB de RAM causará problemas. 🚨

Processos Múltiplos - Um Exemplo

Neste exemplo, há um Processo Gerenciador que inicia e controla dois Processos de Trabalhadores.

Este Processo de Gerenciador provavelmente seria o que escutaria na porta no IP. E ele transmitiria toda a comunicação para os processos de trabalho.

Esses processos de trabalho seriam aqueles que executariam seu aplicativo, eles executariam os cálculos principais para receber uma solicitação e retornar uma resposta, e carregariam qualquer coisa que você colocasse em variáveis ​​na RAM.

E, claro, a mesma máquina provavelmente teria outros processos em execução, além do seu aplicativo.

Um detalhe interessante é que a porcentagem da CPU usada por cada processo pode variar muito ao longo do tempo, mas a memória (RAM) normalmente fica mais ou menos estável.

Se você tiver uma API que faz uma quantidade comparável de cálculos todas as vezes e tiver muitos clientes, então a utilização da CPU provavelmente também será estável (em vez de ficar constantemente subindo e descendo rapidamente).

Exemplos de ferramentas e estratégias de replicação

Pode haver várias abordagens para conseguir isso, e falarei mais sobre estratégias específicas nos próximos capítulos, por exemplo, ao falar sobre Docker e contêineres.

A principal restrição a ser considerada é que tem que haver um único componente manipulando a porta no IP público. E então tem que ter uma maneira de transmitir a comunicação para os processos/trabalhadores replicados.

Aqui estão algumas combinações e estratégias possíveis:

  • Uvicorn com --workers
    • Um gerenciador de processos Uvicorn escutaria no IP e na porta e iniciaria vários processos de trabalho Uvicorn.
  • Kubernetes e outros sistemas de contêineres distribuídos
    • Algo na camada Kubernetes escutaria no IP e na porta. A replicação seria por ter vários contêineres, cada um com um processo Uvicorn em execução.
  • Serviços de nuvem que cuidam disso para você
    • O serviço de nuvem provavelmente cuidará da replicação para você. Ele possivelmente deixaria você definir um processo para executar, ou uma imagem de contêiner para usar, em qualquer caso, provavelmente seria um único processo Uvicorn, e o serviço de nuvem seria responsável por replicá-lo.

Dica

Não se preocupe se alguns desses itens sobre contêineres, Docker ou Kubernetes ainda não fizerem muito sentido.

Falarei mais sobre imagens de contêiner, Docker, Kubernetes, etc. em um capítulo futuro: FastAPI em contêineres - Docker.

Etapas anteriores antes de começar

Há muitos casos em que você deseja executar algumas etapas antes de iniciar sua aplicação.

Por exemplo, você pode querer executar migrações de banco de dados.

Mas na maioria dos casos, você precisará executar essas etapas apenas uma vez.

Portanto, você vai querer ter um processo único para executar essas etapas anteriores antes de iniciar o aplicativo.

E você terá que se certificar de que é um único processo executando essas etapas anteriores mesmo se depois, você iniciar vários processos (vários trabalhadores) para o próprio aplicativo. Se essas etapas fossem executadas por vários processos, eles duplicariam o trabalho executando-o em paralelo, e se as etapas fossem algo delicado como uma migração de banco de dados, elas poderiam causar conflitos entre si.

Claro, há alguns casos em que não há problema em executar as etapas anteriores várias vezes; nesse caso, é muito mais fácil de lidar.

Dica

Além disso, tenha em mente que, dependendo da sua configuração, em alguns casos você pode nem precisar de nenhuma etapa anterior antes de iniciar sua aplicação.

Nesse caso, você não precisaria se preocupar com nada disso. 🤷

Exemplos de estratégias de etapas anteriores

Isso dependerá muito da maneira como você implanta seu sistema e provavelmente estará conectado à maneira como você inicia programas, lida com reinicializações, etc.

Aqui estão algumas ideias possíveis:

  • Um "Init Container" no Kubernetes que roda antes do seu app container
  • Um script bash que roda os passos anteriores e então inicia seu aplicativo
    • Você ainda precisaria de uma maneira de iniciar/reiniciar aquele script bash, detectar erros, etc.

Dica

Darei exemplos mais concretos de como fazer isso com contêineres em um capítulo futuro: FastAPI em contêineres - Docker.

Utilização de recursos

Seu(s) servidor(es) é(são) um recurso que você pode consumir ou utilizar, com seus programas, o tempo de computação nas CPUs e a memória RAM disponível.

Quanto dos recursos do sistema você quer consumir/utilizar? Pode ser fácil pensar "não muito", mas, na realidade, você provavelmente vai querer consumir o máximo possível sem travar.

Se você está pagando por 3 servidores, mas está usando apenas um pouco de RAM e CPU, você provavelmente está desperdiçando dinheiro 💸, e provavelmente desperdiçando energia elétrica do servidor 🌎, etc.

Nesse caso, seria melhor ter apenas 2 servidores e usar uma porcentagem maior de seus recursos (CPU, memória, disco, largura de banda de rede, etc).

Por outro lado, se você tem 2 servidores e está usando 100% da CPU e RAM deles, em algum momento um processo pedirá mais memória, e o servidor terá que usar o disco como "memória" (o que pode ser milhares de vezes mais lento), ou até mesmo travar. Ou um processo pode precisar fazer alguma computação e teria que esperar até que a CPU esteja livre novamente.

Nesse caso, seria melhor obter um servidor extra e executar alguns processos nele para que todos tenham RAM e tempo de CPU suficientes.

Também há a chance de que, por algum motivo, você tenha um pico de uso da sua API. Talvez ela tenha se tornado viral, ou talvez alguns outros serviços ou bots comecem a usá-la. E você pode querer ter recursos extras para estar seguro nesses casos.

Você poderia colocar um número arbitrário para atingir, por exemplo, algo entre 50% a 90% da utilização de recursos. O ponto é que essas são provavelmente as principais coisas que você vai querer medir e usar para ajustar suas implantações.

Você pode usar ferramentas simples como htop para ver a CPU e a RAM usadas no seu servidor ou a quantidade usada por cada processo. Ou você pode usar ferramentas de monitoramento mais complexas, que podem ser distribuídas entre servidores, etc.

Recapitular

Você leu aqui alguns dos principais conceitos que provavelmente precisa ter em mente ao decidir como implantar seu aplicativo:

  • Segurança - HTTPS
  • Executando na inicialização
  • Reinicializações
  • Replicação (o número de processos em execução)
  • Memória
  • Etapas anteriores antes de iniciar

Entender essas ideias e como aplicá-las deve lhe dar a intuição necessária para tomar qualquer decisão ao configurar e ajustar suas implantações. 🤓

Nas próximas seções, darei exemplos mais concretos de possíveis estratégias que você pode seguir. 🚀