committed by
GitHub
1 changed files with 321 additions and 0 deletions
@ -0,0 +1,321 @@ |
|||
# 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](https.md){.internal-link target=_blank} 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. |
|||
|
|||
<img class="shadow" src="/img/deployment/concepts/image01.png"> |
|||
|
|||
--- |
|||
|
|||
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... |
|||
|
|||
/// tip | "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](https.md){.internal-link target=_blank} 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. |
|||
|
|||
<img src="/img/deployment/concepts/process-ram.svg"> |
|||
|
|||
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. |
|||
|
|||
/// tip | "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](docker.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## 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. |
|||
|
|||
/// tip | "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. |
|||
|
|||
/// tip | "Dica" |
|||
|
|||
Darei exemplos mais concretos de como fazer isso com contêineres em um capítulo futuro: [FastAPI em contêineres - Docker](docker.md){.internal-link target=_blank}. |
|||
|
|||
/// |
|||
|
|||
## 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. 🚀 |
Loading…
Reference in new issue