Pular para conteúdo

Fazendo deploy no Fly.io


Objetivos da aula:

  • Entender o que é o Fly.io e como usar sua CLI
  • Aprender a fazer o deploy de uma aplicação Docker no Fly.io
  • Configurar uma instância do PostgreSQL no Fly.io
  • Configurar as variáveis de ambiente
  • Rodar as migrações em produção
Caso prefira ver a aula em vídeo

Esse aula ainda não está disponível em formato de vídeo, somente em texto!

Aula Slides Código


Agora que temos uma API criada com integração ao banco de dados e testes sendo executados via integração contínua. Chegou a tão esperada hora de colocar nossa aplicação em produção para que todas as pessoas possam acessá-la. Colocaremos nossa aplicação em produção usando um serviço de PaaS, chamado Fly.io.

O Fly.io

O Fly.io é uma plataforma de deploy que nos permite lançar nossas aplicações na nuvem e que oferece serviços para diversas linguagens de programação e frameworks como Python e Django, PHP e Laravel, Ruby e Rails, Elixir e Phoenix, etc.

Ao mesmo tempo, em que permite que o deploy de aplicações em containers docker também possam ser utilizadas, como é o nosso caso. Além disso, o Fly disponibiliza bancos de dados para serem usados em nossas aplicações, como PostgreSQL e Redis.

O motivo pela escolha do Fly é que ele permite que façamos deploys de aplicações em desenvolvimento / provas de conceito de forma gratuita - o que usaremos para "colocar nossa aplicação no mundo".

Para fazer o uso do fly.io é necessário que você crie uma conta no serviço.

Flyclt

Uma das formas de interagir com a plataforma é via uma aplicação de linha de comando disponibilizada pelo Fly, o flyctl.

O flyctl precisa ser instalado em seu computador. Em algumas distribuições linux o flyctl está disponível nos repositórios de aplicações. Para Mac/Windows ou distribuições linux que não contam com o pacote no repositório, você pode seguir o guia de instalação oficial.

Após a instalação, você pode verificar se o flyctl está instalado em seu sistema operacional digitando o seguinte comando no terminal:

$ Execução no terminal!
flyctl version

flyctl v0.1.134 linux/amd64 Commit: ... BuildDate: 2023-12-08T18:58:44Z

A versão instalada no meu sistema é a 0.1.134. No momento da sua instalação, você pode se deparar com uma versão mais recente do que a minha no momento, mas os comandos devem funcionar da mesma forma em qualquer versão menor que 0.2.0.

Fazendo login via terminal

Após a instalação do flyctl é importante que você efetue o login usando suas credenciais, para que o flyctl consiga vincular suas credenciais com a linha de comando. Para isso podemos executar o seguinte comando:

$ Execução no terminal!
flyctl auth login
Opening https://fly.io/app/auth/cli/91283719231023123 ...

Waiting for session...

Isso abrirá uma janela em seu browser pedindo que você efetue o login:

Captura de tela da tela inicial de login do fly.io

Após inserir suas credenciais, você pode fechar o browser e no shell a execução do comando terminará mostrando a conta em que você está logado:

$ Continuação da resposta do terminal
Waiting for session... Done
successfully logged in as <seu-email@de-login.com>

Desta forma, toda a configuração necessária para o iniciar o deploy está pronta!

Configurações para o deploy

Agora com o flyctl devidamente configurado. Podemos iniciar o processo de lançamento da nossa aplicação. O flyctl tem um comando específico para lançamento, o launch. Contudo, o comando launch é bastante interativo e ao final dele, o deploy da aplicação é executado. Para evitar o deploy no primeiro momento, pois ainda existem coisas para serem configuradas, vamos executá-lo da seguinte forma:

$ Execução no terminal!
flyctl launch --no-deploy

Como resultado desse comando, o flyctl iniciará o modo interativo e exibirá uma resposta próxima a essa:

$ Resposta do comando `launch`
Detected a Dockerfile app
Creating app in /home/dunossauro/ci-example-fastapi
We're about to launch your app on Fly.io. Here's what you're getting:

Organization: <Seu Nome>             (fly launch defaults to the personal org)
Name:         fast-zero              (derived from your directory name)
Region:       Sao Paulo, Brazil      (this is the fastest region for you)
App Machines: shared-cpu-1x, 1GB RAM (most apps need about 1GB of RAM)
Postgres:     <none>                 (not requested)
Redis:        <none>                 (not requested)

? Do you want to tweak these settings before proceeding? (y/N) 

Nesse texto estão destacadas as configurações padrões do Fly. Como a Região onde seu deploy será feito (Sao Paulo, Brazil, o mais próximo a mim nesse momento), a configuração da máquina do deploy App Machines: shared-cpu-1x, 1GB RAM e a opção padrão do Postgres: Postgres: <none>.

Uma nota sobre o Postgres + Fly

Antes de avançarmos, é importante mencionar uma especificidade do Fly.io: para criar uma instância do PostgreSQL, a plataforma requer que um cartão de crédito seja fornecido. Esta é uma medida de segurança adotada para evitar o uso indevido de seus serviços, como a execução de ferramentas de mineração. Apesar dessa exigência, o serviço de PostgreSQL é oferecido de forma gratuita. Mais detalhes podem ser encontrados neste artigo.

Caso você não adicione um cartão, o erro levatado pelo flyctl está descrito na issue #73

A pergunta feita ao final dessa seção Do you want to tweak these settings before proceeding? pode ser traduzida como: Você deseja ajustar essas configuração antes de prosseguir?. Diremos que sim, digitando Y e em seguida Enter.

Assim, a configuração do lançamento deve avançar e travar novamente com um texto parecido com esse:

$ Continuação do comando `launch`
? Do you want to tweak these settings before proceeding? Yes
Opening https://fly.io/cli/launch/59f08b31a5efd30bdf5536ac516de5ga ...

Waiting for launch data...⣽

Nesse momento, ele abrirá o browser novamente exibira uma tela de ajustes de configurações:

captura de tela da janela de tweak da aplicação

Nesse momento faremos alguns ajustes em nossa configuração:

  1. Seção Basics: adicionaremos o nome da nossa aplicação no Fly. (Usarei fastzeroapp)
  2. Seção Memory & CPU: alteraremos o campo VM Memory para 256MB
  3. Seção Database:
    • alteraremos o campo Postgres para Fly Postgres
    • criaremos um nome para o nosso serviço de banco de dados. (Usarei fastzerodb)
    • no campo Configuration alteraremos para Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
  4. Por fim, clicamos em Confirm Settings!

Após esse ajuste, você pode fechar a janela do browser e voltar ao terminal, pois a parte interativa do launch ainda estará em execução. Como a resposta a seguir é bastante grande, colocarei ... para pular algumas linhas que não nos interessam nesse momento:

$ Continuação do comando `launch`
Created app 'fastzeroapp' in organization 'personal'
Admin URL: https://fly.io/apps/fastzeroapp
Hostname: fastzeroapp.fly.dev
Creating postgres cluster in organization personal
Creating app...

...

Postgres cluster fastzerodb created
  Username:    postgres
  Password:    t0Vf35P21eDlIVS
  Hostname:    fastzerodb.internal
  Flycast:     fdaa:2:77b0:0:1::a
  Proxy port:  5432
  Postgres port:  5433
  Connection string: postgres://postgres:t0Vf35P21eDlIVS@fastzerodb.flycast:5432

...

Postgres cluster fastzerodb is now attached to fastzeroapp
The following secret was added to fastzeroapp:
  DATABASE_URL=postgres://fastzeroapp:zHgBlc6JNaslGtz@fastzerodb.flycast:5432/fastzeroapp?sslmode=disable
Postgres cluster fastzerodb is now attached to fastzeroapp
? Create .dockerignore from .gitignore files? (y/N)

Nas linhas em destaque, vemos que o Fly se encarregou de criar um dashboard para vermos o status atual da nossa aplicação (https://fly.io/apps/nome-do-seu-app), inicializou um banco de dados postgres para usarmos em conjunto com nossa aplicação e também adicionou a url do banco de dados a variável de ambiente DATABASE_URL com a configuração do postgres referente a nossa aplicação.

A Connection string do banco de dados deve ser armazenada por você, essa informação não será disponibilizada novamente, nem mesmo na parte web do Fly. Por isso guarde-a com cuidado e não compartilhem de forma alguma.

Assim sendo, para prosseguir com o launch devemos responder a seguinte pergunta: Create .dockerignore from .gitignore files? (y/N), que pode ser traduzida como Crie um .dockerignore partindo do arquivo .gitignore?. Vamos novamente responder que sim. Digitando Y e em seguida Enter.

$ Continuação do comando `launch`
Created <seu-path>/.dockerignore from 6 .gitignore files.
Wrote config file fly.toml
Validating <seu-path>/fly.toml
Platform: machines
 Configuration is valid
Your app is ready! Deploy with `flyctl deploy`

Agora o flyctl criou um arquivo .dockerignore que não copia os arquivos do .gitignore para dentro do container docker e também criou um arquivo de configuração do Fly, o arquivo fly.toml.

Na última linha ele nos disse que nossa aplicação está pronta para o deploy. Mas ainda temos mais configurações a fazer!

Configuração dos segredos

Para que nossa aplicação funcione de maneira adequada, todas as variáveis de ambiente precisam estar configuradas no ambiente. O flyctl tem um comando para vermos as variáveis que já foram definidas no ambiente e também para definir novas. O comando secrets.

Para vermos as variáveis já configuradas no ambiente, podemos executar o seguinte comando:

$ Execução no terminal!
flyctl secrets list

NAME            DIGEST              CREATED AT
DATABASE_URL    f803df294e7326fa    22m43s ago

Uma coisa que podemos notar na resposta do secrets é que ele leu nosso arquivo .env e adicionou a variável de ambiente DATABASE_URL com base no postgres que foi criado durante o comando launch. Um ponto de atenção que devemos tomar nesse momento, é que a variável criada é iniciada com o prefixo postgres://. Para que o sqlalchemy reconheça esse endereço como válido, o prefixo deve ser alterado para postgresql+psycopg://. Para isso, usaremos a url fornecida pelo comando launch e alterar o prefixo.

Desta forma, podemos registar a variável de ambiente DATABASE_URL novamente. Agora com o valor correto:

$ Execução no terminal!
flyctl secrets set DATABASE_URL=postgresql+psycopg://postgres:t0Vf35P21eDlIVS@fastzerodb.flycast:5432
Secrets are staged for the first deployment

Contudo, não é somente a variável de ambiente do postgres que é importante para que nossa aplicação seja executada. Temos que adicionar as outras variáveis contidas no nosso .env ao Fly.

Iniciaremos adicionando a variável ALGORITHM:

$ Execução no terminal!
flyctl secrets set ALGORITHM="HS256"
Secrets are staged for the first deployment

Seguida pela variável SECRET_KEY:

$ Execução no terminal!
flyctl secrets set SECRET_KEY="your-secret-key"
Secrets are staged for the first deployment

E por fim a variável ACCESS_TOKEN_EXPIRE_MINUTES:

$ Execução no terminal!
flyctl secrets set ACCESS_TOKEN_EXPIRE_MINUTES=30
Secrets are staged for the first deployment

Com isso, todos os segredos da nossa aplicação já estão configurados no nosso ambiente do Fly. Agora podemos partir para o nosso tão aguardado deploy.

Deploy da aplicação

Para efetuarmos o deploy da aplicação, podemos usar o comando deploy doflyctl. Uma coisa interessante nessa parte do processo é que o Fly pode fazer o deploy de duas formas:

  1. Copiar seus arquivos e fazer o build do docker na nuvem;
  2. Você pode fazer o build localmente e subir apenas o container para um repositório disponível no Fly.

Optaremos por fazer o build localmente para não serem alocadas duas máquinas em nossa aplicação1. Para executar o build localmente usamos a flag --local-only.

O Fly sobre duas instâncias por padrão da nossa aplicação para melhorar a disponibilidade do app. Como vamos nos basear no uso gratuito, para todos poderem executar o deploy, adicionaremos a flag --ha=false ao deploy. Para desativamos a alta escalabilidade:

$ Execução no terminal!
fly deploy --local-only --ha=false

Como a resposta do comando deploy é bastante grande, substituirei o texto por ... para pular algumas linhas que não nos interessam nesse momento:

$ Resultado do comando `deploy`
==> Verifying app config
Validating /home/dunossauro/ci-example-fastapi/fly.toml
Platform: machines
 Configuration is valid
--> Verified app config
==> Building image
==> Building image with Docker
...
 => exporting to image                                                  0.0s
 => => exporting layers                                                 0.0s
 => => writing image sha256:b95a9d9f8abcea085550449a720a0bb9176e195fe4  0.0s
 => => naming to registry.fly.io/fastzeroapp:deployment-01HHKKDMF87FN4  0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/fastzeroapp]
...
deployment-01HHKKDMF87FN441VA6H0JR4BS: digest: sha256:153a13e2931f923ab60df7e9dd0f18e2cc89fff7833ac18443935c7d0763a329 size: 2419
--> Pushing image done
image: registry.fly.io/fastzeroapp:deployment-01HHKKDMF87FN441VA6H0JR4BS
image size: 349 MB

Watch your deployment at https://fly.io/apps/fastzeroapp/monitoring

-------
Updating existing machines in 'fastzeroapp' with rolling strategy

-------
  Machine 1781551ad22489 [app] update succeeded
-------

Visit your newly deployed app at https://fastzeroapp.fly.dev/

As primeiras linhas da resposta estão relacionadas ao build do docker e a publicação no repositório de imagens docker do Fly.

Na sequência, temos algumas informações importantes a respeito do deploy da nossa aplicação. Como a URL de monitoramento (https://fly.io/apps/<nome-do-app>/monitoring), o aviso de que o deploy foi efetuado com sucesso (Machine 1781551ad22489 [app] update succeeded) e por fim, a URL de acesso a nossa aplicação (https://<nome-do-app>.fly.dev/).

Dessa forma podemos acessar a nossa aplicação acessando a URL fornecida pela última linha de resposta em nosso browser, como https://fastzeroapp.fly.dev/:

descrição

E pronto, nossa aplicação está disponível para acesso! Obtivemos o nosso "Olá mundo". 🚀🚀🚀🚀🚀🚀

Porém, contudo, entretanto, ainda existe um problema na nossa aplicação no ar. Para ficar evidente, tente acessar o swagger da sua aplicação no ar e registrar um usuário usando o endpoint /user com o método POST:

descrição

Você receberá uma mensagem de erro, um erro 500: Internal Server Error, por de não efetuarmos a migração no banco de dados de produção. Porém, para ter certeza disso, podemos usar a URL de monitoramento do Fly para ter certeza do erro ocorrido. Acessando: https://fly.io/apps/<nome-do-app>/monitoring, podemos visualizar os erros exibidos no console da nossa aplicação:

descrição

Podemos ver no console a mensagem: Relation "users" does not exist. Que traduzida pode ser lido como A relação "users" não existe. O significa que a tabela "users" não foi criada ou não existe no banco de dados.

Desta forma, para que nossa aplicação funcione corretamente precisamos executar as migrações.

Migrations

Agora que nosso container já está em execução no fly, podemos executar o comando de migração dos dados, pois ele está na mesma rede do postgres configurado pelo Fly2. Essa conexão é feita via SSH e pode ser efetuada com o comando ssh do flyctl.

Podemos fazer isso de duas formas, acessando efetivamente o container remotamente ou enviando somente um comando para o Fly. Optarei pela segunda opção, pois ela não é interativa e usará somente uma única chamada do shell. Desta forma:

$ Execução no terminal!
flyctl ssh console -a fastzeroapp -C "poetry run alembic upgrade head"

Connecting to fdaa:2:77b0:a7b:1f60:3f74:a755:2... complete
Skipping virtualenv creation, as specified in config file.
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> e018397cecf4, create users table
INFO  [alembic.runtime.migration] Running upgrade e018397cecf4 -> de865434f506, create todos table
Possível erro que pode ocorrer!

Uma das formas de funcionamento padrão do Fly é desativar a máquina caso ninguém esteja usando. Aí quando uma requisição for feita para aplicação, ele inicia a máquina novamente.

Caso você tente fazer um ssh e a aplicação não estiver de pé no momento, você vai receber um erro como esse:

flyctl ssh console -a fastzeroapp -C "poetry run alembic upgrade head"

Error: app fastzeroapp has no started VMs.
It may be unhealthy or not have been deployed yet.
Try the following command to verify:

fly status

Nesse caso, você pode tentar acessar sua aplicação pelo browser ou via terminal e ela iniciará novamente. Nesse momento, quando a máquina estiver rodando, você pode rodar a migração novamente.

O comando ssh do flyctl é um grupo de subcomandos para executar operações específicas em um container. Podemos pedir os logs de certificado com ssh log, inserir ou recuperar arquivos via FTP com o ssh ftp.

O subcomando que utilizamos ssh console nos fornece acesso ao shell do container. Por isso tivemos que especificar com a flag -a o nome da nossa aplicação (poderíamos acessar o console do banco de dados, também). E a flag -C é o comando que queremos que seja executado no console do container. Nesse caso, o comando completo representa: "Acesse o console do app fastzeroapp via SSH e execute o comando poetry run alembic upgrade head".

Dessa forma temos a migração executada com sucesso. Você pode usar o comando ssh console sem especificar o comando também, dessa forma ele fará um login via ssh no container.

Com isso, podemos voltar ao swagger e tentar executar a operação de criação de um novo user com um POST no endpoit /users. Tudo deve ocorrer perfeitamente dessa vez:

descrição

Agora, SIM, nossa aplicação está em produção para qualquer pessoa poder usar e aproveitar da sua aplicação. Mande o link para geral e veja o que as pessoas acham da sua mais nova aplicação. 🚀

Commit

Agora que fizemos todas as alterações necessárias, devemos adicionar ao nosso repositório os arquivos criados pelo flyctl launch. Os arquivos .dockerignore e fly.toml:

$ Execução no terminal!
git add .
git commit -m "Adicionando arquivos gerados pelo Fly"
git push

E pronto!

Conclusão

Assim, como prometido, chegamos ao final da jornada! Temos uma aplicação pequena, mas funcional em produção! 🚀

Ao longo desta aula, percorremos uma jornada sobre como implantar uma aplicação FastAPI com Docker no Fly.io, uma plataforma que oferece uma maneira simples e acessível de colocar suas aplicações na nuvem. Exploramos alguns comandos do flyctl e fomos desde a configuração inicial até o processo de deploy e resolução de problemas. Agora, com nossa aplicação pronta para o mundo, você possui o conhecimento necessário para compartilhar suas criações com outras pessoas e continuar sua jornada no desenvolvimento web.

Na próxima aula discutiremos um pouco sobre o que mais você pode aprender para continuar desenvolvendo seus conhecimentos em FastAPI e desenvolvimento web, além de claro, de algumas dicas de materiais. Até lá!


  1. No plano gratuito existe uma limitação de máquinas disponíveis por aplicação. Quando usamos mais de uma máquina, temos que ter um plano pago, por esse motivo, faremos o build localmente. 

  2. É possível executar a migração usando a sua máquina como ponto de partida. Para isso é necessário usar o proxy do Fly: fly proxy 5432 -a fastzerodb. Dessa forma, a porta 5432 é disponibilizada localmente para executar o comando. Acredito, porém, que a conexão via ssh é mais proveitosa, no momento em que podemos explorar mais uma forma de interagir com o Fly.