Introdução

DigitalOcean é uma plataforma que oferece aos desenvolvedores um local para hospedar seus aplicativos. Eles oferecem o humilde Virtual Private Server (VPS), que eles chamam de”droplets”, bem como produtos mais avançados, como balanceadores de carga e bancos de dados gerenciados. Discutiremos tudo isso nas seções subsequentes.

Para acompanhar este guia, você precisará criar uma conta DigitalOcean . Você também precisará criar uma conta GitHub se ainda não tiver uma. Como sou um desenvolvedor Node.js, este guia fará uso de um serviço Node.js básico (Docker), embora possa ser facilmente adaptado para funcionar com qualquer plataforma com a qual você esteja mais familiarizado.

Construindo a infraestrutura em DigitalOcean

Ao final desta demonstração, você criará dois $ 5/mês. gotas, um $ 10/mês. balanceador de carga e um registro de contêiner gratuito. A DigitalOcean cobra por hora por esses produtos, então, depois de construir tudo e fazer tudo funcionar, você pode optar por derrubar imediatamente a infraestrutura e pagar apenas alguns dólares.

Dê uma olhada na infraestrutura que vamos construir:

Digital-Ocean-Infrastructure

Depois que tudo estiver feito, você terá uma ação GitHub que implanta automaticamente o branch principal do seu repositório para os droplets api-1 e api-2 .

Em uma compilação normal, isso resultaria em algum tempo de inatividade, já que um serviço ficará inativo conforme o novo código é implantado, e há uma quantidade de tempo diferente de zero que leva para as verificações de integridade para determinar se um serviço está para baixo. Com este guia, no entanto, você aprenderá a implantar de uma forma que não resulte em tempo de inatividade. E, embora este exemplo use serviços em execução em dois droplets, você poderia escalá-lo facilmente para três ou mais.

Cronograma de implantação

Nesta seção, revisaremos uma explicação de alto nível da abordagem abordada neste documento, que pode ser adaptada para muitas plataformas, não apenas para DigitalOcean. Por exemplo, se você quiser usar o HAProxy como um balanceador de carga para rotear solicitações para dois processos Golang, todos em um único servidor robusto, você pode fazer isso com certeza.

Abaixo está uma linha do tempo das operações que ocorrerão. Entraremos em muito mais detalhes sobre a instância api-1 do que a instância api-2 para economizar espaço, embora as duas passem pelo mesmo processo:

Digital-Ocean-Deployment-Timeline

No gráfico acima, o eixo x representa o tempo e se move da esquerda para a direita. Quando o processo de implantação é iniciado pela primeira vez, há duas instâncias de serviço em execução, API 1 e API 2, que executam a V1 da base de código. Enquanto isso está acontecendo, o balanceador de carga está enviando verificações de integridade para ambos para garantir que possam receber solicitações.

Eventualmente, ocorrerá uma implantação que resulta na chamada do endpoint de desligamento. A partir de então, as verificações de integridade falharão. Observe que, embora a verificação de integridade esteja falhando, o serviço ainda é capaz de lidar com solicitações e ainda está sendo roteado o tráfego. Quando duas verificações falham, essa instância do servidor é removida do balanceador de carga, substituída pela V2 da base de código e restaurada. Após a aprovação de três verificações de integridade, o balanceador de carga começa a encaminhar as solicitações para a instância novamente. Feito isso, o processo de implantação continuará para a próxima instância de serviço.

Em um nível geral, há duas informações importantes a serem tiradas do acima:

  1. Sempre há pelo menos uma instância disponível para a qual o balanceador de carga fará o roteamento.
  2. Uma instância sempre será capaz de fornecer respostas enquanto as solicitações estão sendo encaminhadas para ela.

Equipado com esse conhecimento, agora você está pronto para passar para nosso guia específico do DigitalOcean.

Guia de implantação: tempo de inatividade zero usando DigitalOcean

Criar tokens

Os tokens permitem que os aplicativos interajam com a API DigitalOcean em seu nome. Para este exemplo, eles serão usados ​​para que um servidor de compilação GitHub possa enviar imagens do Docker para o registro do contêiner e para que seus droplets possam extrair do registro do contêiner.

Visite a página Configurações da API DigitalOcean e gere dois novos tokens. Nomeie o primeiro “ Ações do GitHub” e o segundo “ Droplet Registry Pull ”. Ambos podem ser configurados para acesso de leitura e gravação para este exemplo. Anote esses tokens de API, pois você precisará deles para uso posterior.

Esses tokens devem permanecer em segredo para terceiros. Estamos usando dois tokens para que, se um ficar comprometido, ele possa ser excluído sem afetar o outro.

Gere uma chave SSH

Ao se comunicar com servidores por SSH, é muito mais seguro usar uma chave SSH do que usar uma senha. Por esse motivo, agora você gerará uma chave SSH (que é mais longa e mais aleatória do que uma senha) para acessar os droplets.

Usar uma chave SSH permitirá que você se conecte manualmente e execute algumas configurações iniciais, além de permitir que o GitHub transfira arquivos para os droplets também.

Para gerar uma chave SSH, execute o seguinte comando:

 $ ssh-keygen-t rsa-f ~/.ssh/api-droplets
# deixe a senha em branco

Este comando criará dois arquivos-chave. O primeiro está localizado em ~/.ssh/api-droplets e é sua chave privada que você não deve compartilhar com terceiros. O segundo arquivo está localizado em ~/.ssh/api-droplets.pub e é a chave pública. Você pode ser menos mesquinho com este.

Criar droplets (VPCs)

Usando a interface DigitalOcean, crie dois droplets .

Ao fazer isso, você será solicitado a fornecer alguns detalhes. Para a distribuição, escolha Debian 10 . Para o plano, escolha Básico $ 5/mês . Para a opção de datacenter, escolha o datacenter que está mais próximo de você e certifique-se de que o balanceador de carga que você criar mais tarde esteja no mesmo datacenter. Eu escolhi SFO2 para mim.

Na seção de autenticação, clique no botão Nova chave SSH . Dê à chave SSH um nome como Droplet SSH Key e cole o conteúdo do arquivo ~/.ssh/api-droplets.pub na entrada da chave SSH e clique em Adicionar chave SSH . Defina o número de gotas a serem criadas como 2 .

Para os nomes de host, chame-os de api-1 e api-2 . Por fim, marque ambas as gotas com uma nova tag chamada http-api . Essa tag será usada posteriormente pelo balanceador de carga para corresponder às solicitações aos droplets.

Depois de criar os droplets, você deve vê-los listados na interface, como:

Droplet-Interface-Visual

O endereço IP listado aqui é o endereço IP público do seu droplet. Esses endereços identificam exclusivamente suas gotículas na Internet. Usando esses endereços IP, você agora fará o SSH nas duas gotas de sua máquina de desenvolvimento.

Execute o seguinte comando para o seu primeiro droplet:

 $ ssh root @ -i ~/.ssh/api-droplets
# para a primeira conexão, digite'sim'e pressione enter

Quando estiver conectado, você precisará fazer algumas coisas.

Primeiro, instale o Docker no VPS. Isso será usado para encapsular e executar seu aplicativo. Você também precisará instalar o binário doctl e autenticar com ele, o que permite que o VPS interaja com o DigitalOcean. Para realizar esta configuração, execute o seguinte comando:

 $ sudo apt install curl xz-utils
# digite'y'e pressione enter
$ curl-fsSL https://get.docker.com-o get-docker.sh && sh get-docker.sh
$ wget https://github.com/digitalocean/doctl/releases/download/v1.54.0/doctl-1.54.0-linux-amd64.tar.gz
$ tar xf ~/doctl-1.54.0-linux-amd64.tar.gz
$ mv doctl/usr/local/bin/
$ doctl auth init
# Cole o  e pressione Enter
$ exit

Como um lembrete, você precisará executar esses conjuntos de comandos em ambos os seus droplets, portanto, depois de sair, execute o comando ssh novamente para o endereço IP do segundo droplet. Este é apenas um processo de inicialização único ao qual você não terá que retornar mais tarde.

Crie um balanceador de carga

Agora que você criou seus droplets, a próxima etapa será levá-los para criar um balanceador de carga usando a IU DigitalOcean. Para esta demonstração, a opção Pequeno $ 10/mês é adequada. Conforme observado anteriormente, certifique-se de que ele esteja localizado na mesma região onde você criou suas duas gotas.

No campo “Adicionar gotas”, digite a tag http-api e clique no resultado para corresponder dinamicamente às suas gotas. A opção de encaminhar HTTP da porta 80 para a porta 80 é suficiente para este projeto.

Edite as configurações avançadas para definir os terminais de verificação de integridade. Normalmente, o balanceador de carga faz uma solicitação ao/endpoint, mas este projeto precisa de um endpoint dedicado apenas para as verificações de integridade.

Para configurar este endpoint dedicado, altere o “Caminho” para /health , defina o “Limite insalubre” para 2 e defina o “Limite de integridade” para 3 . Sua configuração agora deve ser semelhante a esta:

Load-Balancer-DigitalOcean-UI

Dê ao seu balanceador de carga um nome cativante e fácil de lembrar. No meu caso, escolhi sfo2-api .

Depois de salvar seu balanceador de carga, você deve vê-lo listado na IU. Meu balanceador de carga se parece com isto (observe que 0 dos 2 droplets correspondentes estão íntegros porque o servidor não está executando neles):

Load-Balancer-Visual-Healthy-Droplets

Como foi o caso com os droplets, o endereço IP é o endereço IP exclusivo que identifica seu balanceador de carga. Neste momento, você pode fazer uma solicitação ao balanceador de carga da máquina de desenvolvimento para garantir que ele funcione. Execute o seguinte comando em seu terminal:

 $ curl-v http:///

Ao fazer isso, você deve obter um erro HTTP 503 Service Unavailable , com um corpo de resposta dizendo “Nenhum servidor está disponível para lidar com esta solicitação.” Isso é esperado; neste ponto do nosso processo, não há servidores íntegros.

Crie um registro de contêiner

A seguir, você criará um registro de contêiner usando a IU do DigitalOcean. É aqui que as imagens do Docker são armazenadas.

Por padrão, você está limitado a 500 MB de armazenamento gratuito, o que é suficiente para esta experiência. Para projetos maiores, você vai superar esse número muito rapidamente. Na verdade, a primeira implantação deste projeto consome cerca de 300 MB de armazenamento, embora implantações adicionais adicionem apenas alguns megabytes.

Ao criar o registro, você precisará dar a ele um nome exclusivo. Neste exemplo, escolhi o nome foo , mas você precisará escolher algo que seja globalmente único em todos os clientes DigitalOcean.

Crie um repositório GitHub

Para continuar configurando nossa implantação com tempo de inatividade zero com DigitalOcean, usaremos a IU do GitHub para criar um novo repositório .

Configure um diretório local para apontar para o repositório. Certifique-se de usar a nova convenção de ramificação principal em vez de master . A tela do novo repositório do GitHub fornece todos os comandos de que você precisa para fazer isso.

Depois de fazer isso, adicione os seguintes arquivos ao repositório:

.github/workflows/main-deploy.yml

As ações do GitHub usam o diretório .github/workflows/ para encontrar descrições das várias ações a serem usadas pelo projeto. Por exemplo, você pode ter um arquivo que descreve as ações a serem executadas quando uma solicitação pull é feita, como a execução de um linter e alguns testes.

Nesse caso, você só precisa de um único arquivo para descrever o processo de implantação quando o código é mesclado com o branch principal. Use o arquivo a seguir como um modelo, observando que você desejará substituir pelo nome de seu registro DigitalOcean, como o valor foo que usei.

 nome: Implementar na produção
em: empurrar: ramos: -a Principal # Permite que você execute este fluxo de trabalho manualmente na guia Ações workflow_dispatch:
empregos: Construir: roda em: ubuntu-mais recente passos: -nome: Verificar Repo usa: ações/checkout @ v2 -nome: Instale o DigitalOcean Controller usa: digitalocean/action-doctl @ v2 com: token: $ {{secrets.DIGITALOCEAN_ACCESS_TOKEN}} -name: Configurar o Docker Builder usa: docker/setup-buildx-action @ v1 -nome: Autenticar com DigitalOcean Container Registry run: doctl registry login--expiry-seconds 180 -name: Build and Push to DigitalOcean Container Registry usa: docker/build-push-action @ v2 com: contexto:. push: true tags: | registry.digitalocean.com//api:latest registry.digitalocean.com//api: sha-$ {{github.sha}} deploy-api-1: necessidades: construir roda em: ubuntu-mais recente passos: # Droplets já tem docker, doctl + auth e curl instalados -name: Implementar api para DigitalOcean Droplet usa: appleboy/[email protected] com: host: $ {{secrets.DO_API1_HOST}} nome de usuário: root chave: $ {{secrets.DO_API_KEY}} porta: 22 script: | login de registro doctl-expiração-segundos 180 docker pull registry.digitalocean.com//api:latest echo"chamando ponto final de desligamento..." curl--silent http://localhost/shutdown || verdade echo"dando tempo para a verificação de integridade falhar..." sono 30 # ((insalubre + 1) * intervalo) docker stop api || verdade docker rm api || verdade echo"iniciando instância do servidor..." docker run-d \ -reiniciar sempre \ -p 0.0.0.0:80:80 \ --name api \ registry.digitalocean.com//api:latest echo"dando tempo para a verificação de saúde se recuperar..." sono 40 # ((saudável + 1) * intervalo) curl--silent--fail http://localhost/health deploy-api-2: necessidades: deploy-api-1 # rolling deploy roda em: ubuntu-mais recente passos: # Droplets já tem docker, doctl + auth e curl instalados -name: Implementar api para DigitalOcean Droplet usa: appleboy/[email protected] com: host: $ {{secrets.DO_API2_HOST}} nome de usuário: root chave: $ {{secrets.DO_API_KEY}} porta: 22 script: | login de registro doctl-expiração-segundos 180 docker pull registry.digitalocean.com//api:latest echo"chamando ponto final de desligamento..." curl--silent http://localhost/shutdown || verdade echo"dando tempo para a verificação de integridade falhar..." sono 30 # ((insalubre + 1) * intervalo) docker stop api || verdade docker rm api || verdade echo"iniciando instância do servidor..." docker run-d \ -reiniciar sempre \ -p 0.0.0.0:80:80 \ --name api \ registry.digitalocean.com//api:latest echo"dando tempo para a verificação de saúde se recuperar..." sono 40 # ((saudável + 1) * intervalo) curl--silent--fail http://localhost/health

Este arquivo contém três trabalhos. O primeiro é build , que construirá o contêiner docker dentro de uma máquina virtual Ubuntu. Ele também marca o contêiner e o envia por push para o registro do contêiner.

Os trabalhos deploy-api-1 e deploy-api-2 também são executados em uma máquina virtual Ubuntu, mas fazem todo o seu trabalho por SSH. Especificamente, eles se conectam aos seus droplets, obtêm a nova imagem do docker, dizem ao serviço para encerrar e aguardam que as verificações de integridade falhem. Depois disso, o contêiner antigo é removido e um novo contêiner baseado na nova imagem é iniciado.

Com o novo contêiner iniciado, uma nova verificação de integridade será e executada. Por segurança, o endpoint de verificação de integridade também será chamado. Dessa forma, se a chamada falhar, o trabalho falhará e as implantações subsequentes não acontecerão.

Reconhecidamente, um problema gritante com esse arquivo é que todo o conteúdo de cada implantação é copiado e colado e, embora seja possível convertê-los em ações GitHub combináveis ​​/reutilizáveis, esse é um guia para outro dia.

Arquivos relevantes explicados

<”Dockerfile

Este arquivo descreve como construir a imagem Docker. É o mais simples possível e não está necessariamente pronto para produção, mas é bom o suficiente para este exemplo:

 DO nó: 14 EXPOR 80 WORKDIR/srv/api
ADICIONAR./srv/api EXECUTAR npm install--produção CMD ["nó","api.mjs"]

Esta imagem é baseada na linha Node.js 14 LTS. Ele sugere que o serviço interno escuta na porta 80. O código do aplicativo é copiado para o diretório /srv/api/ dentro da imagem. Em seguida, ele faz uma instalação de produção antes de finalmente executar o arquivo api.mjs .

.dockerginore

Este arquivo lista os arquivos e diretórios que não devem ser copiados para a imagem:

. git
.gitignore
node_modules
npm-debug.log
teste

A linha mais importante aqui é a do diretório node_modules/. É importante porque esses arquivos devem ser gerados durante o processo de construção da imagem, e não copiados do seu sistema operacional.

.gitignore

Este arquivo serve principalmente para evitar que node_modules/ seja confirmado:

 node_modules
npm-debug.log 

api.mjs

Este arquivo representa uma API muito simples que estará disponível por trás de um balanceador de carga e é o ponto de entrada para o serviço:

 #!/usr/bin/env node importar fastify de'fastify';
servidor const=fastify ();
deixe morrer=falso;
const id=Math.floor (Math.random () * 1000); server.get ('/', async ()=> ({api:'resposta feliz', id})); server.get ('/health', assíncrono (_req, responder)=> { if (morrer) { reply.code (503).send ({status:'desligamento'}); } outro { reply.code (200).send ({status:'ok'}); }
}); server.get ('/shutdown', async ()=> { morrer=verdadeiro; return {shutdown: true};
}); endereço const=espera server.listen (80,'0.0.0.0');
console.log (`ouvindo em $ {address}`);

A rota GET/ mostra principalmente que o serviço pode ser executado gerando um número aleatório para atuar como um identificador. Esse número permanecerá consistente durante todo o tempo de vida da instância.

O GET/health é o que o balanceador de carga usa para saber se o aplicativo está íntegro e pode receber solicitações. O GET/shutdown define a variável die como true . Quando isso acontecer, todas as solicitações subsequentes para GET/health retornarão um código de status 503 infeliz. Este é o mecanismo que nos permite declarar normalmente que um serviço deve ser descartado do balanceador de carga.

package.json e package-lock.json

Esses dois arquivos podem ser gerados executando os seguintes comandos:

 $ npm init-y
$ npm install fastify @ 3

Isso cria o diretório node_modules/ e cria os dois arquivos de pacote. Esses arquivos de pacote serão usados ​​posteriormente durante o processo de compilação do Docker para fazer o download dos arquivos de pacote necessários do repositório de pacotes npmjs.com .

Segredos do projeto GitHub

Para executar sua implantação, você também precisará criar alguns segredos do projeto GitHub. Essas são variáveis ​​que podem ser usadas pelos arquivos YAML de ação do GitHub.

Para criar os segredos do seu projeto, vá para a guia de configurações do projeto GitHub e adicione quatro entradas.

Sua primeira entrada será DIGITALOCEAN_ACCESS_TOKEN . Este é o valor do token de acesso de ações do GitHub que você gerou em uma etapa anterior.

Sua segunda entrada será DO_API_KEY . Este será o conteúdo do arquivo de chave privada ~/.ssh/api-droplets que você gerou anteriormente. Tenha cuidado ao colar o conteúdo, pois você deseja garantir que as novas linhas sejam preservadas.

Finalmente, você adicionará duas entradas, DO_API1_HOST e DO_API2_HOST . Ambos conterão o endereço IP dos dois droplets API que você criou. Sua tela de segredos agora deve ter a seguinte aparência:

GitHub-Project-Secrets-Screen

Todos os quatro nomes secretos são mencionados no arquivo GitHub Action YAML que você criou anteriormente.

Execute sua primeira implantação

Para executar sua primeira implantação, siga estas etapas:

  1. Mescle as alterações do arquivo no branch principal do GitHub, criando e mesclando uma solicitação pull ou adicionando-o diretamente ao branch principal e enviando por push. Uma vez feito isso, o processo de implantação deve começar.
  2. No repositório GitHub, verifique a guia Ações. Você deve ver uma ação ativa em execução associada à fusão do código com o branch principal. Clique para ver mais informações. Na minha tela é assim:

First-Deploy-Process-Output

Troubleshooting

If you get a failure at this stage in the process, you may need to modify a previous step.

If there’s a problem with the code you transcribed, then modify it and commit it again to the main branch. This will automatically kick off another build.

If you need to change a GitHub secret, then go and change it using the GitHub UI – just know that this won’t start another deployment. Instead, visit the Actions tab again, click the “Deploy to Production” button on the left, and use the “Run workflow” dropdown on the right to start the build again from the main branch.

In our example, you can see that after build is successfully completed, at step two, api-1 is deployed. The next step, which is to deploy api-2, hasn’t happened yet because it is waiting for api-1 to complete. If the deployment were to fail then api-2 would not get deployed. This gives you time to fix any problems and to deploy a fix. Moreover, if any of these steps were to fail you could click on them to get more information.

Monitor application health

The DigitalOcean graphs for the load balancer display the health of the application over time, and in my experience, polls the health of the application every minute.

Depending on the timing, you might see that one service goes down and then up, and the other goes down and then up. If you wait several minutes after deploying the first change, then trigger another deployment, you should be able to see the effects in the DigitalOcean graphs.

Here’s what happened in my case:

Monitor-Application-Health-DigitalOcean-Graph

The Downtime graph clearly shows app-1 (green) having downtime. The other app-2 (brown) wasn’t polled at the right moment to cause the graph to spike. The Health checks graph shows that app-2 was affected slightly.

The build step pushes Docker images to your container repository. Each time this happens the image is tagged twice; once containing the latest tag, and another containing the git commit hash of the main branch when the build happened.

This is what my container registry looks like after performing two builds:

Container-Registry-Two-Builds

The latest tag is replaced with each build. This is the tag that is used to deploy the docker images to production. The tag using the commit hash is just a convenience to show you that it’s working. A more robust system could use that to rollback deploys to previous commits.

Make a load-balanced request

At this point in our project, you have now got a service that automatically deploys to production when code is merged to the main branch. Best of all, it does so in such a way that future deployments should result in zero downtime!

Now, you’re ready to prove that the application is running in a redundant manner. You can do so by running the following command a few times:

$ curl http:///
# {"api":"happy response","id":930}
$ curl http:///
# {"api":"happy response","id":254}

In the response, you should see that two different id values are returned. With each request you make, the returned id should alternate. This is because the load balancer is configured to route requests using the “round-robin” algorithm by default.

If one of your servers were to crash, then it would be removed from the rotation. With the configuration of the health checks it might take between 11 and 20 seconds for the load balancer to realize that one of the instances is down. During that time, 50 percent of the requests being sent to the load balancer would fail. More aggressive health checks can reduce this time, but it’s difficult to build a system that is 100 percent resilient to failure.

Of course, passing around IP Addresses isn’t all that convenient, but you can configure DNS settings for a domain to point to the IP address. Again, another guide for another day.

Productionalizing

All things considered, this is a fairly brief guide, intended only to show you how to achieve zero-downtime deployments. It glosses over many important details, especially with regards to security. Without being comprehensive, here are some additional steps that you should take to make your infrastructure more secure:

  • Don’t expose the shutdown endpoint on port :80. Instead listen on a different port on 127.0.0.1 (local interface) only. Note that currently, anyone can call http:///shutdown to disable a droplet.
  • Rename the healthcheck endpoint to something that is more difficult to guess
  • For a real app, forward HTTPS requests from the load balancer to HTTP on the APIs
  • Use a non-root account on the droplets

Finally, keep in mind that the API services listen on 0.0.0.0 (all interfaces), so a client could bypass the load balancer by requesting the Droplet IP directly. Remember that each droplet exposes two network interfaces, one public and one private, and that the Node.js services should listen on the private interface, where the load balancer can reach.

Conclusão

In a normal build, deployment usually incurs some amount of downtime. In this guide, we have reviewed how to use DigitalOcean, GitHub, and Docker to deploy in such a way that results in zero downtime and is scalable for services running on two droplets or more.

The post Zero-downtime deploys with DigitalOcean, GitHub, and Docker appeared first on LogRocket Blog.

Source link