Artigo original: You should never ever run directly against Node.js in production. Maybe.

Às vezes, fico me perguntando se eu sei muito sobre poucas coisas.

Há algumas semanas, eu conversava com um amigo que mencionou despretensiosamente que "você nunca deveria rodar uma aplicação do Node diretamente em produção".

Eu balancei minha cabeça vigorosamente em sinal de afirmação para demonstrar que eu também nunca rodaria Node em produção porque... hahaha... todo mundo sabe disso. Só que eu não sabia disso!  Eu deveria saber disso? EU AINDA TENHO PERMISSÃO PARA PROGRAMAR?

Se eu fosse desenhar um Diagrama de Venn do que eu sei em comparação com o que eu acho que os outros sabem, seria algo assim...

1_ThJbM2JMjrnTuHczo0tLqw
O que eu sei x o que eu acho que os outros sabem

A propósito, aquele pontinho bem pequeno ali fica cada vez menor conforme eu fico velho.

Existe um diagrama melhor criado por Alicia Liu que, de certo modo, mudou minha vida. Ela diz que, na verdade, seria mais parecido com isso...

1_N7vv39ri9yC0l6krsSFlQA

Eu amo esse diagrama porque eu quero muito que ele seja verdade. Eu não quero passar o resto da minha vida como um pontinho azul pequeno – e cada vez menor – e insignificante.

MUITO DRAMÁTICO. Ao menos, não estamos partindo da estakazero. Eu não estou controlando qual a próxima música que vai tocar enquanto estou escrevendo, mas escrever artigos com confidências é viciante como uma droga

Bem, assumindo que o diagrama de Alicia seja verdade, eu gostaria de compartilhar com você o que eu sei agora sobre rodar aplicações do Node em produção. Talvez nossos Diagramas de Venn relativos não se sobreponham nesse tema.

Primeiro, vamos analisar a declaração "nunca rodar aplicações do Node diretamente em produção".

Nunca rodar o Node diretamente em produção

Talvez, sim; talvez, não. Vamos falar sobre o raciocínio por trás dessa declaração. Primeiro, vamos dar uma olhada nos motivos contra fazermos isso.

Suponha que temos um simples servidor do Express. O servidor do Express mais simples que eu posso pensar...

const express = require("express");
const app = express();
const port = process.env.PORT || 3000;

// Visível em http://localhost:3000
app.get("/", function(req, res) {
  res.send("Again I Go Unnoticed"); //"De novo, ninguém me nota", em português 
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`)); //"App de exemplo escutando na porta... ", em português 

Rodaríamos isso usando o script start no arquivo package.json.

"scripts": {
  "dev": "npx supervisor index.js",
  "start": "node index.js"
}
1_VceC98Qk5zKqzBmiu1szDQ

Existem dois problemas aqui. O primeiro é um problema de desenvolvimento. O segundo é um problema de produção.

O problema de desenvolvimento: ao mudarmos o código, teremos que parar e começar a aplicação para termos nossas mudanças captadas.

Para solucionar isso, normalmente usamos algo como um gerenciador de processos do Node, como o supervisor ou o nodemon. Esses pacotes observarão nosso projeto e reiniciarão nosso servidor sempre que fizermos mudanças. Eu normalmente faço algo assim:

"scripts": {  "dev": "npx supervisor index.js",  "start": "node index.js"}

Então, eu rodo npm run dev. Note que eu estou rodando npx supervisor aqui, o que me permite usar o pacote supervisor sem ter de instalá-lo. Eu amo a modernidade (na maior parte do tempo).

Nosso outro problema é que ainda estamos rodando diretamente o Node e já dissemos que era algo ruim. Agora, estamos prestes a descobrir o porquê.

Adiciono outro roteador aqui, que tenta ler um arquivo do disco que não existe. Esse é um erro que poderia facilmente aparecer em qualquer aplicação do mundo real.

const express = require("express");
const app = express();
const fs = require("fs");
const port = process.env.PORT || 3000;

// viewed at http://localhost:3000
app.get("/", function(req, res) {
  res.send("Again I Go Unnoticed");
});

app.get("/read", function(req, res) {
  // Isso aqui não existe 
  fs.createReadStream("minha-autoestima.txt");
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

Se rodarmos diretamente o Node com o npm start e navegarmos até o endpoint read, vamos obter um erro, pois o arquivo não existe.

1_pAUoJV-LRJwxs7MRegnuoA

Não é um grande problema, certo? É um erro. Acontece.

É um grande problema, sim. Se você voltar ao seu terminal, verá que sua aplicação quebrou completamente.

1_69LuEt53W2isIXP34vUlYA

Isso significa que, se você voltar ao navegador e se tentar acessar o URL raiz do site, terá a mesma mensagem de erro. Um erro em apenas um método quebrou sua aplicação para todos os usuários.

Isso é péssimo – muito ruim, mesmo. Essa é uma das razões principais porque as pessoas dizem "nunca rode o Node diretamente em produção".

OK. Então, se nós não podemos rodar o Node diretamente em produção, qual seria a maneira certa de fazê-lo?

Opções para o Node em produção

Temos algumas opções.

Uma delas seria simplesmente usar algo como o supervisor ou o nodemon em produção da mesma maneira que o usamos em ambiente de desenvolvimento. Isso poderia funcionar, mas essas ferramentas são muito pouco para um controle adequado. Uma opção melhor é chamada de pm2.

Resolva com o pm2

O pm2 é um gerenciador de processos do Node que tem muitos alertas. Assim como todo o resto em "JavaScript", você o instala (globalmente) através do npm — ou pode simplesmente usar o npx outra vez. Faça como você quiser.

Existem muitas maneiras de rodar sua aplicação com o pm2. A maneira mais simples possível seria apenas chamar pm2 start no seu ponto de entrada.

"scripts": {
  "start": "pm2 start index.js",
  "dev": "npx supervisor index.js"
},

Você verá algo mais ou menos assim no seu terminal…,

1_uvsnmQahRBHtnh0X7tt_xA

Esse é o nosso processo rodando, monitorado pelo pm2 em segundo plano. Se você visitar o endpoint read e se a aplicação quebrar, o pm2 vai reinicializá-la automaticamente. Você não verá nada disso no terminal, pois está rodando em segundo plano. Se você quiser observar o pm2 fazendo o trabalho, precisará rodar pm2 log 0, sendo 0 o id do processo cujos relatórios desejamos ver.

1_AbR1eyySpr2IllYtA4wE-Q

Pronto, tudo certo! Você pode ver a reinicialização da aplicação quando ela cair devido ao nosso erro não tratado.

Podemos também colocar nosso comando dev e fazer com que o pm2 observe os arquivos para nós e os reinicialize em quaisquer mudanças.

"scripts": {
  "start": "pm2 start index.js --watch",
  "dev": "npx supervisor index.js"
},

Note que, em função de o pm2 rodar tudo em segundo plano, você não pode simplesmente apertar ctrl+c para terminar o processo do pm2. Você tem que pará-lo usando o ID ou o nome.

pm2 stop 0

pm2 stop index

Observe, também, que o pm2 retém uma referência para o processo para que você possa recomeçá-lo.

1_Z4yLru6TmwUVQv84DkZDAQ

Se você quiser excluir a referência do processo, precisa executar pm2 delete. Você pode parar e excluir um processo em um comando com delete.

pm2 delete index

Também podemos usar o pm2 para rodar múltiplos processos da nossa aplicação. O pm2 automaticamente balanceará o carregamento através dessas instâncias.

Múltiplos processos com pm2 no modo fork

O pm2 tem diversas opções de configuração e elas estão contidas em um arquivo de "ecossistema". Para criar um, execute pm2 init. Você vai obter um resultado parecido com esse...

module.exports = {
  apps: [
    {
      name: "Express App",
      script: "index.js",
      instances: 4,
      autorestart: true,
      watch: true,
      max_memory_restart: "1G",
      env: {
        NODE_ENV: "development"
      },
      env_production: {
        NODE_ENV: "production"
      }
    }
  ]
};

Eu vou ignorar a seção "deploy" nesse artigo, pois não tenho a menor ideia do que ela faz.

A seção "apps" é onde você define as aplicações que você deseja que o pm2 rode e monitore. Você pode rodar mais de uma. Muitas dessas configurações do arquivo são, provavelmente, autoexplicativas. Eu quero me concentrar naquela configuração chamada instances (em português, instâncias).

O pm2 pode rodar múltiplas instâncias da sua aplicação. Você pode informar um número de instances que você deseja rodar e o pm2 fará o trabalho. Então, se queremos rodar 4 instances, podemos rodar o seguinte arquivo de configuração:

module.exports = {
  apps: [
    {
      name: "Express App",
      script: "index.js",
      instances: 4,
      autorestart: true,
      watch: true,
      max_memory_restart: "1G",
      env: {
        NODE_ENV: "development"
      },
      env_production: {
        NODE_ENV: "production"
      }
    }
  ]
};

Então, tudo que precisamos é digitar pm2 start.

1__rYp7NMQTCMmOfBV0w0RTw

O pm2 está agora rodando em um modo cluster (em português, "agrupamento"). Cada um desses processos está rodando em uma CPU diferente na minha máquina, dependendo de quantos núcleos de processamento (em inglês, cores) eu tiver. Se rodarmos um processo para cada core, sem saber quantos cores temos, precisamos apenas passar o parâmetro max para o valor instances.

{
   ...
   instances: "max",
   ...
}

Vamos descobrir quantos cores eu tenho nessa máquina.

1*nhjuG0xFsMgkYB382_xfyw

8 CORES! Nossa!!! Eu vou instalar Subnautica na minha máquina da Microsoft. Não diga a eles que eu disse isso.

A parte coisa sobre rodar processos em CPUs separadas é que, se você tiver um processo que perca o controle e tome 100% da CPU, os outros ainda funcionarão. Se você passar mais instâncias do que cores, o pm2 dobrará o número de processos em uma CPUs conforme for necessário.

Você pode fazer MUITO mais com o pm2, incluindo monitoramento e o tratamento daquelas variáveis de ambiente irritantes.

Um outro item a se observar: se, por alguma razão, você quiser executar seu script npm start, você pode fazer isso através do npm como processo e passar a bandeira -- start. O espaço antes de start (em português, "início") é super importante aqui.

pm2 start npm -- start

No Azure AppService, incluímos o pm2 por padrão em segundo plano. Se você quiser usar o pm2 no Azure, não precisa incluí-lo em seu arquivo package.json. Você somente precisa adicionar um arquivo de ecossistema e está pronto para continuar.

1_TYOm2q57lXad3te35tGwqg

Certo! Agora que aprendemos tudo sobre o pm2, vamos falar um pouco mais sobre o porquê você pode querer não usá-lo. Também veremos que pode, de fato, não haver problema em rodar o Node diretamente.

Rodando o Node diretamente em produção

Eu tinha algumas dúvidas sobre isso. Então, falei com Tierney Cyren, que é uma parte enorme do círculo laranja de conhecimento, especialmente quando o assunto é o Node.

Tierney me apontou algumas desvantagens de se usar o Node baseado em gerenciadores de processos, como o pm2.

A principal delas é que você não deveria usar Node para monitorar a si mesmo. Você não quer usar a coisa que você está monitorando para monitorar a si mesma. Seria como pedir ao meu filho adolescente para tomar conta de si mesmo em uma sexta-feira à noite: isso acabaria mal? Pode ser que sim, pode ser que não, mas você não quer descobrir da pior maneira.

Tierney recomenda que você não tenha um gerenciador de processos do Node rodando em sua aplicação nunca. Ao invés disso, tenha algo em um nível mais alto que observe em conjunto as instâncias separadas de sua aplicação. Por exemplo, uma configuração ideal seria se você tivesse um agrupamento de Kubernetes com sua aplicação rodando em contêineres separados. O Kubernetes podem, então, monitorar aqueles contêineres e, se algum deles cair, pode trazê-lo de volta e registrar sua condição de integridade.

Nesse caso, você pode rodar o Node diretamente porque você o está monitorando um nível acima.

Acontece que o Azure já está fazendo isso. Se não subirmos um arquivo de ecossistema do pm2 para o Azure, a aplicação iniciará com nosso script start do nosso arquivo package.json e podemos, então, rodar diretamente o Node.

"scripts": {
  "start": "node index.js"
}

Nesse caso, estamos rodando diretamente o Node e não há problema. Se a aplicação quebrar, você notará que ela reinicializará. Isso acontece porque, no Azure, sua aplicação roda em um contêiner. O Azure está orquestrando o contêiner que sua aplicação está rodando e saberá quando ela tiver problemas.

1_YSvtZOR4DIt1McSdDChVew

Porém, você ainda tem apenas uma instância aqui. É necessário, ao contêiner, algum tempo para voltar a ficar on-line depois de quebrar, o que significa que, nesse tempo, sua aplicação estaria fora do ar para seus usuários.

Idealmente, você vai querer ter mais de um contêiner rodando. A solução para isso seria subir múltiplas instances de sua aplicação para múltiplos sites do Azure AppService e, então, usar o Azure Front Door para balancear a carga das aplicações por detrás de um único endereço de IP. O Front Door saberá quando um contêiner cair e roteará o tráfego para outras instâncias funcionais de sua aplicação.

Saiba mais sobre o serviço Azure Front Door aqui.

systemd

Outra sugestão que o Tierney nos dá é rodar o Node com systemd. Eu não entendo muito (na verdade, não entendo nada) sobre systemd e já estraguei tudo parafraseando ele uma vez. Então, vamos deixar Tierney isso dizer com suas próprias palavras…

Essa opção só é possível se você tiver acesso ao Linux na sua implementação (em inglês, deployment), e se você controlar o modo como o Node é inicializado em nível de serviço. Se você estiver rodando seu processo do Node.js em uma máquina virtual (em inglês, VM), Linux de longa duração, como as VMs do Azure, poderá rodar o Node.js com systemd sem problemas. Se você só estiver fazendo o deploy de seus arquivos para um serviço como o Azure AppService ou o Heroku ou rodando o Node dentro de um ambiente conteinerizado, como o Azure Container Instances, você provavelmente deverá evitar essa opção.

Rodando seu App Node.js com Systemd - Parte 1 (texto em inglês sobre o assunto)

Worker Threads do Node.js

Tierney também gostaria de informar que as Worker Threads estão chegando ao Node. Isso permitirá a você começar sua aplicação em múltiplos "trabalhadores" (em inglês, workers), evitando, desse modo, a necessidade de algo como o pm2. Talvez. Eu não sei. De fato, não li esse artigo.

Documentação d0 Node.js sobre Worker Threads

Seja um adulto

A última sugestão de Tierney era apenas para lidar com o erro e escrever alguns testes (como um adulto faria), mas quem tem tempo para isso, não é mesmo?

O pequeno círculo permanece

Agora você sabe a maior parte do que está no pequeno círculo azul. O restante tem a ver apenas com fatos inúteis sobre bandas e cerveja.

Para mais informações sobre o pm2, Node e Azure, confira os recursos a seguir: