Documentação/ Implantação/ Hooks de Implantação

Hooks de Implantação

Execute um comando a partir da imagem do seu aplicativo em quatro momentos de uma implantação Git: antes e depois do build, antes e depois da troca de tráfego.

O que é um hook de implantação

Um hook de implantação é um único comando associado a um aplicativo e a uma etapa da sua implantação. O sh0 o executa em um contêiner descartável iniciado a partir da imagem do aplicativo, registra a saída no log da implantação e decide, pelo código de saída, se a implantação continua.

Usos típicos: uma migração de banco de dados antes que o tráfego chegue à nova versão (npm run migrate, php artisan migrate --force), um teste rápido da imagem recém-construída, um aquecimento de cache após a troca.

Quais aplicativos executam hooks

Somente aplicativos implantados a partir de um repositório Git executam hooks. O sh0 recusa criar um hook em qualquer outro aplicativo (envio de arquivo com sh0 push, imagem Docker, Dockerfile colado, arquivo Compose, modelo), porque o hook nunca seria disparado:

Terminal
$ sh0 hooks my-site create --hook-type pre_build --command "node --version"
Error: Deploy hooks only run on git-based deploys; app 'my-site' is a 'upload' app, where hooks would never fire. Remove the hook or convert the app to a git deploy. (BAD_REQUEST)

Um aplicativo Git cujo repositório é implantado como uma pilha Compose também não executa hooks: o caminho Compose sai do pipeline antes da primeira etapa de hooks.

As quatro etapas

A etapa decide quando o hook é executado, em qual imagem ele roda, o que recebe e o que uma falha provoca:

EtapaQuandoImagem e entradasSe falhar
pre_buildApós o clone, antes do build da imagemA imagem da versão atualmente implantada. Sem variáveis de ambiente nem volumes. Ignorado na primeira implantação (ainda não existe imagem).A implantação falha. A versão anterior continua atendendo o tráfego.
post_buildApós o build da imagem, antes de o novo contêiner iniciarA nova imagem, com as variáveis de ambiente do aplicativo. Sem volumes.A implantação falha. A versão anterior continua atendendo o tráfego.
pre_deployDepois que o novo contêiner passa na verificação de saúde, antes de a rota mudar para eleA nova imagem, com as variáveis de ambiente e os volumes do aplicativo montados.A implantação falha, o novo contêiner é removido e a versão anterior continua atendendo o tráfego.
post_deployApós a troca da rota e a remoção do contêiner antigoA nova imagem, com as variáveis de ambiente e os volumes.Registrado como falha; a implantação continua bem-sucedida (a nova versão já está no ar).
Warning
Não existe uma opção “falhar em caso de erro” por hook. Só a etapa decide: um hook pre_build, post_build ou pre_deploy com falha sempre faz a implantação falhar; um hook post_deploy com falha, nunca.

Criar um hook

No painel, abra o aplicativo, depois a aba Hooks de implantação, e clique em Criar hook. Escolha o Tipo de hook, informe o Comando e, se necessário, o Tempo limite (segundos) (300 por padrão).

Pela CLI, o tipo é passado com --hook-type (pre_build, post_build, pre_deploy, post_deploy):

Terminal
$ sh0 hooks my-api create --hook-type post_build --command "id"
Deploy hook created: 949d4097-4350-48c0-9c7c-6ca3c582a51a
  Type: post_build
  Command: id
  Timeout: 300s

$ sh0 hooks my-api list
ID                                   Type           Command                                  Timeout  Enabled
----------------------------------------------------------------------------------------------------------
281e3d14-fdca-4478-8ae3-985d4a604562 post_deploy    env | grep NODE                          300      Yes
1b025181-05a1-4f8e-bd6b-3a26fcb69848 post_deploy    printenv PORT                            300      Yes
22ee2ab3-7fc4-4714-a05d-f1dd779514f3 pre_deploy     echo second                              300      Yes
6f1fa1be-b188-4cc0-b277-cf5acfbe0780 pre_deploy     echo first                               300      Yes
949d4097-4350-48c0-9c7c-6ca3c582a51a post_build     id                                       300      Yes
b8303249-54fe-4358-90f8-a287390c219f pre_build      node --version                           300      Yes

sh0 hooks my-api delete <id> remove um hook. Para pausá-lo sem removê-lo, envie PATCH /api/v1/deploy-hooks/<id> com enabled=false. A API também oferece GET e POST /api/v1/apps/<app-id>/deploy-hooks. Criar ou alterar um hook exige o papel de desenvolvedor no aplicativo.

Onde um hook é executado

Nunca no próprio servidor. Cada hook recebe um contêiner de uso único, iniciado a partir da imagem indicada na tabela acima, com estas propriedades:

  • Ele roda com o usuário sem privilégios 1000:1000, nunca como root.
  • O ENTRYPOINT da imagem é esvaziado: o comando é executado como está, e não como argumentos de node ou npm.
  • Ele é conectado à rede sh0-net, então consegue alcançar os bancos de dados do aplicativo pelo nome de host interno.
  • Sem shell. O comando é dividido nos espaços e executado diretamente. |, ;, &, >, <, crases e $( são recusados, e as aspas são repassadas literalmente. Para tudo o que precisar de um shell, coloque um script na sua imagem e chame-o: sh scripts/migrate.sh. As regras completas são as das tarefas agendadas; um comando é limitado a 4.096 caracteres.

Um hook post_build que executa id em uma imagem Node.js mostra o usuário do ambiente isolado:

Build log
[HOOKS] post_build hooks:
  [OK] id (242ms, exit: Some(0))
    stdout: uid=1000(node) gid=1000(node) groups=1000(node)

Ordem e tempo limite

Os hooks de uma mesma etapa são executados um após o outro, o criado mais recentemente primeiro. Se a ordem importar, crie primeiro o que deve rodar por último, ou encadeie os passos em um único script.

Cada hook tem seu próprio tempo limite, 300 segundos por padrão. Um hook que o ultrapassa conta como falha (Hook timed out after 5s no log). Seu contêiner não é parado: continua rodando até o comando terminar sozinho e depois permanece no servidor.

Ler os resultados

Os resultados são acrescentados ao log de build da implantação, na aba Implantações. Cada hook tem uma linha com o status (OK, FAIL ou SKIP), a duração e o código de saída, seguida das 10 primeiras linhas da saída padrão e da saída de erro. Os segredos são mascarados nessas linhas. Log real de uma primeira implantação com cinco hooks:

Build log
[HOOKS] pre_build hooks:
  [SKIP] node --version (0ms, exit: None)
    stderr: skipped: no app image available yet (hooks run in a container, never on the host)
[HOOKS] post_build hooks:
  [OK] id (292ms, exit: Some(0))
    stdout: uid=1000(node) gid=1000(node) groups=1000(node)
[HOOKS] pre_deploy hooks:
  [OK] echo second (287ms, exit: Some(0))
    stdout: second
  [OK] echo first (295ms, exit: Some(0))
    stdout: first
[HOOKS] post_deploy hooks:
  [OK] printenv PORT (249ms, exit: Some(0))
    stdout: 3001

Quando um hook bloqueante falha, o log termina com o erro e a remoção do novo contêiner. Aqui um hook pre_deploy saiu com o código 3, e a versão já no ar continuou rodando:

Build log
[STEP 5/6] Health check passed (previous step: 8.3s)
[ERROR] Deploy hook 'node -e process.exit(3)' failed with exit code Some(3):
[WARNING] Removed the container this failed deployment had started (/sh0-17b83e1c-my-api-6abc39e3). A failed deploy leaves nothing running.

Nesse caso, a saída do hook com falha, e a dos hooks da mesma etapa executados antes dele, não é mantida: só resta a linha de erro.

Limites

  • Um comando com um caractere proibido é aceito na criação do hook e só recusado na execução. Em uma etapa bloqueante, a implantação falha; em post_deploy, o hook some do log sem deixar rastro. Verifique o comando antes de salvá-lo.
  • Os hooks não enviam notificações. Os alertas do sh0 e seus canais (e-mail, Slack, Discord, Telegram, webhook) são descritos na página de monitoramento.
  • Uma reversão de um aplicativo Git passa de novo por todo o pipeline, hooks incluídos. Veja a página de reversões para saber o que uma reversão realmente implanta.