Como Implantar Aplicações Web Balanceadas na DigitalOcean com CDK para Terraform e TypeScript

O autor selecionou a Fundação Wikimedia para receber uma doação como parte do programa Escreva para Doações.

Introdução

A Infraestrutura como Código (IaC) é uma prática de automatização da implantação e modificações da infraestrutura, definindo os estados dos recursos e suas relações em código. A execução desse código então cria ou modifica os recursos reais na nuvem. A IaC permite aos engenheiros utilizar uma ferramenta de IaC como o Terraform (da HashiCorp) para o provisionamento da infraestrutura.

Com a IaC, as mudanças em sua infraestrutura podem passar pelo mesmo processo de revisão de código que o código de sua aplicação. Você pode armazenar o código em controle de versão (como o Git) para manter um histórico do estado de sua infraestrutura, e pode automatizar ainda mais o processo de implantação com ferramentas de nível superior, como uma plataforma interna de desenvolvimento self-service (IDP).

Terraform é uma ferramenta popular de IaC (Infraestrutura como Código) agnóstica de plataforma devido ao seu amplo suporte para muitas plataformas, incluindo o GitHub, Cloudflare e DigitalOcean. A maioria das configurações do Terraform é escrita usando uma linguagem declarativa chamada de HashiCorp Configuration Language (HCL).

O Kit de Desenvolvimento em Nuvem para Terraform (CDKTF) é uma ferramenta construída em cima do Terraform que permite definir infraestrutura usando uma linguagem de programação familiar (como TypeScript, Python ou Go) em vez de HCL. Esta ferramenta pode proporcionar uma curva de aprendizado mais suave para desenvolvedores não familiarizados com HCL, ao mesmo tempo em que permite que os desenvolvedores usem recursos nativos de programação como loops, variáveis e funções.

Neste tutorial, você começará instalando a ferramenta de linha de comando (CLI) cdktf. Em seguida, você criará um projeto CDKTF em TypeScript e definirá o projeto com dois servidores NGINX balanceados por um balanceador de carga. Em seguida, você usará o cdktf para implantar a infraestrutura. No final deste tutorial, você terá um projeto CDKTF a partir do qual poderá expandir sua infraestrutura.

Observação: Este tutorial foi testado com CDKTF 0.11.2 e Terraform 1.2.2.

Pré-requisitos

Para concluir este tutorial, você precisará de:

Passo 1 — Instalando a CLI cdktf

Para começar, você irá instalar a ferramenta de linha de comando cdktf.

A CLI cdktf está disponível como um pacote NPM. Se você pesquisar por cdktf no npmjs.com, encontrará dois pacotes com nomes semelhantes: cdktf e cdktf-cli.

Conceitualmente, o CDKTF é uma camada de abstração sobre o Terraform. Ele consiste em duas partes:

  • uma biblioteca contendo um conjunto de construções nativas da linguagem (como funções e classes) para definir infraestrutura. Esta parte é encapsulada dentro do pacote npm cdktf. Por exemplo, você pode ver o uso das classes App e TerraformStack do pacote cdktf no seguinte projeto de exemplo CDKTF:
    import { App, TerraformStack } from "cdktf";
    class APIStack extends TerraformStack {}
    const app = new App();
    new APIStack(app, "feature-x");
    app.synth();
    
  • Um adaptador que analisa os construtos dentro do projeto CDKTF e os reduz a um conjunto de documentos JSON, que são então inseridos no Terraform da mesma forma que o HCL é inserido. Este adaptador é encapsulado em uma ferramenta CLI chamada cdktf, fornecida pelo pacote cdktf-cli.

Para instalar a ferramenta CLI cdktf, você precisa do pacote cdktf-cli. Você pode instalar este pacote globalmente usando npm, yarn, ou um gerenciador de pacotes de sua escolha.

Para instalar o cdktf-cli com o npm, execute o seguinte:

  1. npm install --global [email protected]

Observação: Provavelmente haverá uma versão mais recente do pacote cdktf-cli após a publicação deste artigo. Você pode tentar seguir o tutorial com a versão mais recente executando npm install --global cdktf-cli@latest em vez disso, mas esteja ciente de que algumas saídas podem diferir ligeiramente.

Alternativamente, você pode usar o Homebrew no macOS ou Linux para instalar a CLI cdktf como a fórmula cdktf:

  1. brew install cdktf

Para verificar se a instalação foi bem-sucedida, execute o comando cdktf sem argumentos:

  1. cdktf

Você verá uma saída semelhante ao seguinte:

Output
Please pass a command to cdktf, here are all available ones: cdktf Commands: cdktf init Create a new cdktf project from a template. cdktf get Generate CDK Constructs for Terraform providers and modules. cdktf convert Converts a single file of HCL configuration to CDK for Terraform. cdktf deploy [stacks...] Deploy the given stacks cdktf destroy [stacks..] Destroy the given stacks cdktf diff [stack] Perform a diff (terraform plan) for the given stack cdktf list List stacks in app. cdktf login Retrieves an API token to connect to Terraform Cloud. cdktf synth Synthesizes Terraform code for the given app in a directory. cdktf watch [stacks..] [experimental] Watch for file changes and automatically trigger a deploy cdktf output [stacks..] Prints the output of stacks cdktf debug Get debug information about the current project and environment cdktf completion generate completion script Options: --version Mostrar número da versão --disable-logging Não escrever arquivos de log. Suportado usando o env CDKTF_DISABLE_LOGGING. --disable-plugin-cache-env Não definir automaticamente TF_PLUGIN_CACHE_DIR. --log-level Qual nível de log deve ser escrito. -h, --help Show help Options can be specified via environment variables with the "CDKTF_" prefix (e.g. "CDKTF_OUTPUT")

A saída mostra os comandos disponíveis. No restante deste tutorial, você ganhará experiência usando cdktf init, cdktf get, cdktf deploy e cdktf destroy.

Agora que você instalou o CLI cdktf, você pode definir infraestrutura escrevendo algum código TypeScript.

Passo 2 — Criando um Novo Projeto CDKTF

Neste passo, você usará o CLI cdktf que acabou de instalar para criar um projeto de modelo CDKTF, no qual você irá construir nos próximos passos.

Crie um diretório que abrigará o projeto CDKTF executando o seguinte comando:

  1. mkdir infra

Em seguida, navegue até o diretório recém-criado:

  1. cd infra/

Use o comando cdktf init para criar um esqueleto de projeto CDKTF no qual você irá trabalhar:

  1. cdktf init --template=typescript --project-name=base --project-description="Base architecture" --local

CDKTF permite que os desenvolvedores definam infraestrutura usando TypeScript, Python, Java, C# ou Go. A opção --template=typescript diz ao cdktf para criar este projeto CDKTF usando TypeScript.

O Terraform (e, portanto, o CDKTF) acompanha os recursos que está gerenciando registrando suas definições e estados em arquivos chamados arquivos de estado do Terraform. A opção --local diz ao CDKTF para manter esses arquivos de estado localmente na máquina em que o cdktf está sendo executado (cada arquivo segue a estrutura de nomeação terraform.<stack>.tfstate).

Após executar o comando, o CLI pode solicitar sua permissão para enviar relatórios de falhas para a equipe do CDKTF para ajudá-los a melhorar o produto:

Output
? Do you want to send crash reports to the CDKTF team? See https://www.terraform.io/cdktf/create-and-deploy/configuration-file for more information (Y/n)

Digite Y se desejar consentir ou N se discordar e pressione ENTER.

cdktf então criará o esqueleto do projeto e instalará os pacotes. Quando o projeto estiver esqueletizado, você verá uma saída semelhante à seguinte:

Output
Your cdktf typescript project is ready! cat help Print this message Compile: npm run get Import/update Terraform providers and modules (you should check-in this directory) npm run compile Compile typescript code to javascript (or "npm run watch") npm run watch Watch for changes and compile typescript in the background npm run build Compile typescript Synthesize: cdktf synth [stack] Synthesize Terraform resources from stacks to cdktf.out/ (ready for 'terraform apply') Diff: cdktf diff [stack] Perform a diff (terraform plan) for the given stack Deploy: cdktf deploy [stack] Deploy the given stack Destroy: cdktf destroy [stack] Destroy the stack Test: npm run test Runs unit tests (edit __tests__/main-test.ts to add your own tests) npm run test:watch Watches the tests and reruns them on change Upgrades: npm run upgrade Upgrade cdktf modules to latest version npm run upgrade:next Upgrade cdktf modules to latest "@next" version (last commit)

Você também verá alguns novos arquivos adicionados ao diretório infra. Os arquivos mais importantes são cdktf.json e main.ts.

cdktf.json é o arquivo de configuração para o projeto CDKTF. Se você abrir o arquivo, ele exibirá algo como o seguinte:

cdktf.json
{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "28c87598-4343-47a9-bb5d-8fb0e031c41b",
  "terraformProviders": [],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}

A propriedade app define o comando que será executado para sintetizar o código TypeScript em JSON compatível com o Terraform. Esta propriedade indica que main.ts é o ponto de entrada para o projeto CDKTF.

Se você abrir o arquivo main.ts, você verá algo semelhante ao seguinte:

main.ts
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    // defina os recursos aqui
  }
}

const app = new App();
new MyStack(app, "infra");
app.synth();

Na linguagem do CDKTF, uma coleção de recursos de infraestrutura relacionados pode ser agrupada em um stack. Por exemplo, os recursos que compõem uma aplicação de API, como Droplets, balanceadores de carga e registros DNS, podem ser agrupados em um único stack chamado APIStack. Cada stack mantém seu próprio estado e pode ser implantado, modificado ou destruído independentemente de outros stacks. Um uso comum de stacks é ter um stack para produção e um stack separado para desenvolvimento.

Uma aplicação é um contêiner para vários stacks. Por exemplo, uma aplicação pode agrupar os stacks de vários microsserviços.

O projeto CDKTF scaffold gerado em main.ts contém uma única classe de stack chamada MyStack, atualmente definindo nenhum recurso. Uma instância de MyStack é criada com o nome infra, contida dentro de uma aplicação chamada app. Em etapas subsequentes, você definirá recursos de infraestrutura dentro do construtor de MyStack.

Após criar o projeto, o próximo passo é configurar o projeto CDKTF com provedores.

Passo 3 — Instalando o Provedor DigitalOcean

Nesta etapa, você instalará o provedor DigitalOcean no projeto CDKTF.

Provedores são bibliotecas que fornecem instruções ao Terraform (que é usado pelo cdktf nos bastidores) sobre como criar, atualizar e excluir recursos em provedores de nuvem, provedores de software como serviço (SaaS) e outras plataformas que expõem interfaces de programação de aplicativos (APIs). Os provedores encapsulam a lógica de chamada dessas APIs de upstream em funções padrão que o Terraform pode chamar.

Por exemplo, se você fosse criar um novo Droplet DigitalOcean sem o Terraform, teria que enviar uma solicitação POST para o endpoint /v2/droplets da API DigitalOcean. Com o Terraform, você instalaria o provedor DigitalOcean e definiria um recurso digitalocean_droplet, semelhante ao seguinte trecho de código de exemplo:

new Droplet(this, 'web', {
  image: 'ubuntu-20-04-x64',
  name,
  region: 'lon1',
  size: 's-1vcpu-1gb',
}

Em seguida, você pode usar a ferramenta CLI cdktf para traduzir este código TypeScript em JSON compatível com o Terraform e passá-lo para o provedor, que fará as chamadas de API apropriadas para criar o Droplet em seu nome.

Agora que você entende o que é um provedor, pode configurar o provedor DigitalOcean para o seu projeto CDKTF.

Abra o arquivo cdktf.json e adicione a string digitalocean/digitalocean ao array terraformProviders:

cdktf.json
{
  "language": "typescript",
  "app": "npx ts-node main.ts",
  "projectId": "28c87598-4343-47a9-bb5d-8fb0e031c41b",
  "terraformProviders": ["digitalocean/digitalocean"],
  "terraformModules": [],
  "context": {
    "excludeStackIdFromLogicalIds": "true",
    "allowSepCharsInLogicalIds": "true"
  }
}

digitalocean/digitalocean é o identificador do provedor DigitalOcean no Registro do Terraform.

Salve e feche o arquivo.

Em seguida, execute cdktf get para baixar e instalar o provedor.

  1. cdktf get

cdktf get irá baixar o provedor, extrair o esquema, gerar as classes TypeScript correspondentes e adicioná-lo como um módulo TypeScript em .gen/providers/. Essa geração de código automática permite que você use qualquer provedor Terraform e módulos HCL com CDKTF, e é assim que CDKTF pode fornecer autocompletar em editores que o suportam.

Assim que cdktf get terminar de ser executado, você verá uma saída semelhante à seguinte:

Output
Generated typescript constructs in the output directory: .gen

Você também verá um novo diretório chamado .gen contendo o código gerado do provedor.

Neste passo, você instalou o provedor digitalocean/digitalocean no projeto. No próximo passo, você configurará o provedor DigitalOcean com as credenciais necessárias para autenticar o provedor com a API da DigitalOcean.

Passo 4 — Configurando o Provedor DigitalOcean

Neste passo, você configurará o provedor DigitalOcean com seu Token de Acesso Pessoal da DigitalOcean, que permite que o provedor chame a API da DigitalOcean em seu nome.

Diferentes provedores requerem e suportam credenciais diferentes para autenticar com a API upstream. Para o provedor DigitalOcean, você precisa fornecer seu Token de Acesso Pessoal da DigitalOcean. Você pode especificar o token para o provedor configurando-o como as variáveis de ambiente DIGITALOCEAN_TOKEN ou DIGITALOCEAN_ACCESS_TOKEN.

Execute o seguinte comando em seu terminal para definir a variável de ambiente para essa sessão do terminal.

  1. export DIGITALOCEAN_ACCESS_TOKEN="your_personal_access_token"

Nota: Ao chamar export, você está definindo a variável de ambiente apenas para essa sessão do terminal. Se você fechar e reabrir o terminal ou executar os comandos cdktf em um terminal diferente, será necessário executar o comando export novamente para que a variável de ambiente entre em vigor.

Em seguida, você especificará o provedor dentro da classe MyStack, o que permitirá que você defina recursos fornecidos pelo provedor dentro do seu stack. Atualize o arquivo main.ts para o seguinte:

main.ts
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { DigitaloceanProvider } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new DigitaloceanProvider(this, 'provider')
    
  }
}

const app = new App();
new MyStack(app, "infra");
app.synth();

O módulo para o provedor está localizado em ./.gen/providers/digitalocean, que foi gerado automaticamente quando você executou cdktf get.

Você configurou o provedor digitalocean/digitalocean com credenciais neste passo. Em seguida, você começará a definir a infraestrutura que faz parte do objetivo deste tutorial.

Passo 5 — Definindo Aplicações Web em Droplets

Neste passo, você irá definir dois servidores NGINX, cada um servindo arquivos diferentes, implantados em dois Droplets Ubuntu 20.04 idênticos.

Você começa com a definição dos dois Droplets. Modifique o arquivo main.ts com as alterações destacadas:

main.ts
...
import { DigitaloceanProvider, Droplet } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const dropletNames = ['foo', 'bar']
    const droplets = dropletNames.map(name => new Droplet(this, name, {
        image: 'ubuntu-20-04-x64',
        name,
        region: 'lon1',
        size: 's-1vcpu-1gb',
      })
    )
  }
}

Você utiliza um loop nativo do JavaScript (Array.prototype.map()) para evitar duplicações no código.

Assim como se estivesse criando o Droplet através do console, existem vários parâmetros a especificar:

  • image – a distribuição Linux e a versão na qual seu Droplet será executado.
  • region – o data center onde o Droplet será executado.
  • size – a quantidade de recursos de CPU e memória a reservar para o Droplet.
  • name – um nome único usado para se referir ao Droplet.

Os valores para image, region e size devem ser coisas que o DigitalOcean suporta. Você pode encontrar os valores válidos (chamados de slugs) para todas as imagens de distribuição Linux suportadas, tamanhos de Droplet e regiões na página de Slugs da API do DigitalOcean. Você pode encontrar uma lista completa de atributos obrigatórios e opcionais na página de documentação do digitalocean_droplet.

Adicionando uma Chave SSH

Como parte dos pré-requisitos, você enviou uma chave pública SSH sem senha para sua conta do DigitalOcean e anotou seu nome. Agora você usará esse nome para recuperar o ID da chave SSH e passá-lo para a definição do seu Droplet.

Como a chave SSH foi adicionada manualmente à sua conta do DigitalOcean, ela não é um recurso gerenciado pela sua configuração atual do Terraform. Se você tentar definir um novo recurso digitalocean_ssh_key, ele criará uma nova chave SSH em vez de usar a existente.

Em seguida, você irá definir um novo digitalocean_ssh_key fonte de dados. No Terraform, fontes de dados são usadas para obter informações sobre infraestrutura que não são gerenciadas pela configuração atual do Terraform. Em outras palavras, elas fornecem uma visualização somente leitura do estado da infraestrutura externa pré-existente. Uma vez que uma fonte de dados é definida, você pode usar os dados em outros lugares da sua configuração do Terraform.

Ainda no arquivo main.ts e dentro do construtor de MyStack, defina uma nova fonte de dados DataDigitaloceanSshKey, e passe o nome que você atribuiu à sua chave SSH (aqui, o nome é do_cdktf):

main.ts
...
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const dropletNames = ['foo', 'bar']
    const sshKey = new DataDigitaloceanSshKey(this, 'sshKey', {
      name: 'do_cdktf',
    })
    const droplets = dropletNames.map(name => new Droplet(this, name, {
    ...
  }
}
...

Em seguida, atualize a definição do Droplet para incluir a chave SSH:

main.ts
...
const droplets = dropletNames.map(name => new Droplet(this, name, {
  image: 'ubuntu-20-04-x64',
  name,
  region: 'lon1',
  size: 's-1vcpu-1gb',
  sshKeys: [sshKey.id.toString()]
}))
...

Ao ser provisionado, você pode acessar o Droplet usando uma chave SSH privada em vez de uma senha.

Especificando o Script de Dados do Usuário para Instalar o NGINX

Agora você definiu dois Droplets idênticos executando o Ubuntu, configurados com acesso SSH. A próxima tarefa é instalar o NGINX em cada Droplet.

Quando um Droplet está sendo criado, uma ferramenta chamada CloudInit inicializará o servidor. O CloudInit pode aceitar um arquivo chamado dados do usuário, que pode modificar como o servidor é inicializado. Os dados do usuário podem ser qualquer arquivo cloud-config ou scripts que o servidor possa interpretar, como scripts Bash.

No restante deste passo, você criará um script Bash e o especificará como os dados do usuário do Droplet. O script instalará o NGINX como parte do processo de inicialização. Além disso, o script também substituirá o conteúdo do arquivo /var/www/html/index.html (o arquivo padrão servido pelo NGINX) pelo nome do host e endereço IP do Droplet, o que fará com que os dois servidores NGINX sirvam arquivos diferentes. No próximo passo, você colocará ambos esses servidores NGINX atrás de um balanceador de carga; ao servir arquivos diferentes, ficará evidente se o balanceador de carga está distribuindo corretamente as solicitações ou não.

Ainda em main.ts, adicione uma nova propriedade userData ao objeto de configuração do Droplet:

main.ts
...
class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const droplets = dropletNames.map(name => new Droplet(this, name, {
      image: 'ubuntu-20-04-x64',
      name,
      region: 'lon1',
      size: 's-1vcpu-1gb',
      sshKeys: [sshKey.id.toString()],
      userData: `#!/bin/bash

apt-get -y update
apt-get -y install nginx
export HOSTNAME=$(curl -s http://169.254.169.254/metadata/v1/hostname)
export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
echo Droplet: $HOSTNAME, IP Address: $PUBLIC_IPV4 > /var/www/html/index.html
`
    }))
  }
}

Aviso: Certifique-se de que não há novas linhas antes do shebang (#!); caso contrário, o script pode não ser executado.

Quando o Droplet é provisionado pela primeira vez, o script será executado como usuário root. Ele usará o gerenciador de pacotes do Ubuntu, APT, para instalar o pacote nginx. Em seguida, ele usará o Serviço de Metadados da DigitalOcean para recuperar informações sobre si mesmo e escrever o nome do host e o endereço IP em index.html, que é servido pelo NGINX.

Neste passo, você definiu os dois Droplets executando o Ubuntu, configurou cada um com acesso SSH e instalou o NGINX usando o recurso de dados do usuário. No próximo passo, você definirá um balanceador de carga que ficará na frente desses servidores NGINX e o configurará para balanceamento de carga de forma round-robin.

Passo 6 — Definindo um Balanceador de Carga

Neste passo, você definirá um Balanceador de Carga da DigitalOcean ao definir uma instância do recurso digitalocean_loadbalancer.

Ainda em main.ts, adicione a seguinte definição para um balanceador de carga ao final do construtor MyStack:

main.ts
...
import { App, Fn, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    new Loadbalancer(this, 'lb', {
      name: 'default',
      region: 'lon1',
      algorithm: 'round_robin',
      forwardingRule: [{
        entryProtocol: 'http',
        entryPort: 80,
        targetProtocol: 'http',
        targetPort: 80,
      }],
      dropletIds: droplets.map((droplet) => Fn.tonumber(droplet.id))
    })
  }
}
...

O argumento forwardingRule indica ao balanceador de carga para escutar as solicitações HTTP na porta 80 e encaminhá-las para cada uma das instâncias no Droplet na porta 80.

Os dropletIds especificam os Droplets para os quais o balanceador de carga encaminhará as solicitações. Ele aceita um número, mas o valor de droplet.id é uma string. Portanto, você utilizou a função Fn.tonumber do Terraform para converter o valor da ID do Droplet de string para número.

Observação: Você utilizou a função Fn.tonumber do Terraform aqui em vez do parseInt nativo do JavaScript porque o valor de droplet.id é desconhecido até que o Droplet seja provisionado. As funções do Terraform são projetadas para operar em valores de tempo de execução desconhecidos antes que o Terraform aplique uma configuração.

Salve e feche o arquivo.

Você agora definiu dois Droplets e um balanceador de carga que fica na frente deles. Seu arquivo main.ts deve se parecer com isso:

main.ts
import { Construct } from "constructs";
import { App, Fn, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    super(scope, name);

    new DigitaloceanProvider(this, 'provider')

    const dropletNames = ['foo', 'bar']
    const sshKey = new DataDigitaloceanSshKey(this, 'sshKey', {
      name: 'do_cdktf',
    })
    const droplets = dropletNames.map(name => new Droplet(this, name, {
        image: 'ubuntu-20-04-x64',
        name,
        region: 'lon1',
        size: 's-1vcpu-1gb',
        sshKeys: [sshKey.id.toString()],
        userData: `#!/bin/bash

apt-get -y update
apt-get -y install nginx
export HOSTNAME=$(curl -s http://169.254.169.254/metadata/v1/hostname)
export PUBLIC_IPV4=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address)
echo Droplet: $HOSTNAME, IP Address: $PUBLIC_IPV4 > /var/www/html/index.html
`
      })
    )

    new Loadbalancer(this, 'lb', {
      name: 'default',
      region: 'lon1',
      algorithm: 'round_robin',
      forwardingRule: [{
        entryProtocol: 'http',
        entryPort: 80,
        targetProtocol: 'http',
        targetPort: 80,
      }],
      dropletIds: droplets.map((droplet) => Fn.tonumber(droplet.id))
    })
  }
}

const app = new App();
new MyStack(app, "infra");
app.synth();

No próximo passo, você usará a ferramenta de linha de comando cdktf para atualizar seu projeto inteiro do CDKTF.

Passo 7 — Provisionamento da Sua Infraestrutura

Neste passo, você usará a ferramenta CLI cdktf para provisionar os Droplets e os balanceadores de carga que você definiu nos passos anteriores.

Certifique-se de estar no diretório infra/ e ter definido a variável de ambiente DIGITALOCEAN_ACCESS_TOKEN para sua sessão no terminal, então execute o comando cdktf deploy:

  1. cdktf deploy

Você deve ver uma saída semelhante ao seguinte:

Output
infra Initializing the backend... infra Initializing provider plugins... infra - Reusing previous version of digitalocean/digitalocean from the dependency lock file infra - Using previously-installed digitalocean/digitalocean v2.19.0 infra Terraform has been successfully initialized! infra Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: infra # digitalocean_droplet.bar (bar) will be created + resource "digitalocean_droplet" "bar" { + backups = false + created_at = (known after apply) + disk = (known after apply) + graceful_shutdown = false + id = (known after apply) + image = "ubuntu-20-04-x64" + ipv4_address = (known after apply) + ipv4_address_private = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + locked = (known after apply) + memory = (known after apply) + monitoring = false + name = "bar" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "lon1" + resize_disk = true + size = "s-1vcpu-1gb" + ssh_keys = [ + "34377800", ] + status = (known after apply) + urn = (known after apply) + user_data = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } # digitalocean_droplet.foo (foo) será criado + resource "digitalocean_droplet" "foo" { + backups = false + created_at = (known after apply) + disk = (known after apply) + graceful_shutdown = false + id = (known after apply) + image = "ubuntu-20-04-x64" + ipv4_address = (known after apply) + ipv4_address_private = (known after apply) + ipv6 = false + ipv6_address = (known after apply) + locked = (known after apply) + memory = (known after apply) + monitoring = false + name = "foo" + price_hourly = (known after apply) + price_monthly = (known after apply) + private_networking = (known after apply) + region = "lon1" + resize_disk = true + size = "s-1vcpu-1gb" + ssh_keys = [ + "34377800", ] + status = (known after apply) + urn = (known after apply) + user_data = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" + vcpus = (known after apply) + volume_ids = (known after apply) + vpc_uuid = (known after apply) } # digitalocean_loadbalancer.lb (lb) será criado + resource "digitalocean_loadbalancer" "lb" { + algorithm = "round_robin" + disable_lets_encrypt_dns_records = false + droplet_ids = (known after apply) + enable_backend_keepalive = false + enable_proxy_protocol = false + id = (known after apply) + ip = (known after apply) + name = "default" + redirect_http_to_https = false + region = "lon1" + size_unit = (known after apply) + status = (known after apply) + urn = (known after apply) + vpc_uuid = (known after apply) + forwarding_rule { + certificate_id = (known after apply) + certificate_name = (known after apply) + entry_port = 80 + entry_protocol = "http" + target_port = 80 + target_protocol = "http" + tls_passthrough = false } + healthcheck { + check_interval_seconds = (known after apply) + healthy_threshold = (known after apply) + path = (known after apply) + port = (known after apply) + protocol = (known after apply) + response_timeout_seconds = (known after apply) + unhealthy_threshold = (known after apply) } + sticky_sessions { + cookie_name = (known after apply) + cookie_ttl_seconds = (known after apply) + type = (known after apply) } } Plan: 3 to add, 0 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan" Please review the diff output above for infra ❯ Approve Applies the changes outlined in the plan. Dismiss Stop

Nota: O CDKTF ainda está em desenvolvimento, e a saída pode ser diferente da mostrada acima.

Esta exibição lista todos os recursos e propriedades que o cdktf planeja criar, atualizar e destruir. Alguns valores, como o ID de um Droplet, só são conhecidos após o provisionamento do recurso. Para esses, você verá (known after apply) como o valor da propriedade na saída.

Revise a lista de recursos para garantir que seja o que você espera. Em seguida, use as teclas de seta para selecionar a opção Aprovar e pressione ENTER.

Você verá uma saída semelhante ao seguinte:

Output
infra digitalocean_droplet.foo (foo): Creating... digitalocean_droplet.bar (bar): Creating... infra digitalocean_droplet.bar (bar): Still creating... [10s elapsed] infra digitalocean_droplet.foo (foo): Still creating... [10s elapsed] 1 Stack deploying 0 Stacks done 0 Stacks waiting

Esta saída informa que o cdktf está se comunicando com a API da DigitalOcean para criar o Droplet. O cdktf está criando os Droplets primeiro porque o balanceador de carga depende do ID do Droplet, que é desconhecido até que os Droplets sejam provisionados.

A criação do Droplet geralmente leva menos de um minuto. Assim que os Droplets forem provisionados, o cdktf passará a criar o balanceador de carga.

Output
infra digitalocean_droplet.bar (bar): Creation complete after 54s [id=298041598] infra digitalocean_droplet.foo (foo): Creation complete after 55s [id=298041600] infra digitalocean_loadbalancer.lb (lb): Creating... infra digitalocean_loadbalancer.lb (lb): Still creating... [10s elapsed]

O balanceador de carga pode demorar mais. Depois que o balanceador de carga for criado, você verá um resumo que mostra que o stack foi implantado com sucesso.

Output
infra digitalocean_loadbalancer.lb (lb): Still creating... [1m30s elapsed] infra digitalocean_loadbalancer.lb (lb): Creation complete after 1m32s [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1] infra Apply complete! Resources: 3 added, 0 changed, 0 destroyed. No outputs found.

Agora você pode visitar o console do DigitalOcean, onde você pode ver um balanceador de carga chamado default e dois Droplets saudáveis chamados foo e bar, cada um servindo como um alvo para o balanceador de carga.

Você pode testar se o NGINX está em execução e servindo conteúdo corretamente visitando o endereço IP de cada Droplet. Você deve ver um texto semelhante ao seguinte:

Droplet: bar, IP Address: droplet_ip

Se você não ver essa sequência de texto ou o servidor não estiver respondendo, verifique se os dados do usuário que você especificou estão corretos e se nenhum caractere (incluindo novas linhas) precede o shebang (#!). Você também pode fazer SSH no Droplet usando sua chave privada SSH e revisar os logs de saída gerados pelo CloudInit em /var/log/cloud-init-output.log:

  1. ssh -i path_to_ssh_private_key root@droplet_ip

Depois de confirmar que os Droplets estão acima e servindo conteúdo, você pode começar a testar o balanceador de carga. Você faz isso enviando algumas solicitações.

Execute o seguinte comando do seu terminal para enviar dez solicitações para o balanceador de carga:

  1. for run in {1..10}; do curl http://load_balancer_ip/; done

Você deve ver uma saída semelhante ao seguinte, embora os endereços IP mostrados sejam diferentes:

Output
Droplet: foo, IP Address: droplet_foo_ip Droplet: bar, IP Address: droplet_bar_ip Droplet: foo, IP Address: droplet_foo_ip Droplet: bar, IP Address: droplet_bar_ip Droplet: bar, IP Address: droplet_bar_ip Droplet: foo, IP Address: droplet_foo_ip Droplet: bar, IP Address: droplet_bar_ip Droplet: foo, IP Address: droplet_foo_ip Droplet: bar, IP Address: droplet_bar_ip Droplet: foo, IP Address: droplet_foo_ip

Isso mostra que as solicitações ao balanceador de carga foram encaminhadas para cada Droplet cinco vezes, indicando que o balanceador de carga está funcionando.

Observação: O balanceador de carga nem sempre equilibrará perfeitamente entre os dois Droplets; você pode descobrir que quatro solicitações foram enviadas para um Droplet e seis para o outro. Esse comportamento é normal.

Neste passo, você utilizou cdktf para provisionar seus recursos e, em seguida, utilizou o console da DigitalOcean para descobrir os endereços IP de seus Droplets e balanceador de carga. Em seguida, enviou solicitações para cada Droplet e balanceador de carga para confirmar que eles funcionam.

No próximo passo, você obterá os endereços IP dos Droplets e do balanceador de carga sem fazer login no console da DigitalOcean.

Passo 8 — Exibindo Informações

No passo anterior, você precisou fazer login no Console da DigitalOcean para obter os endereços IP do seu Droplet e balanceador de carga. Neste passo, você modificará ligeiramente seu código para que essas informações sejam impressas na saída do comando cdktf deploy, poupando-lhe uma viagem ao console.

O Terraform registra a configuração e o estado de seus recursos gerenciados em arquivos de estado. Para sua pilha infra, o arquivo de estado pode ser encontrado em infra/terraform.infra.tfstate. Você será capaz de encontrar os endereços IP dos Droplets e do balanceador de carga dentro deste arquivo de estado.

No entanto, classificar através de um arquivo grande pode ser inconveniente. O CDKTF fornece o construtor TerraformOutput, que você pode usar para exibir variáveis e torná-las disponíveis fora da pilha. Quaisquer saídas são impressas em stdout após a execução de cdktf deploy. Executar cdktf output também pode imprimir saídas a qualquer momento.

Nota: Embora você apenas use saídas para imprimir informações no console neste tutorial, seu verdadeiro poder vem de pilhas que utilizam saídas de outras pilhas como entrada, uma funcionalidade conhecida como referências entre pilhas.

Atualize o arquivo main.ts para incluir as saídas dos endereços IP do balanceador de carga e das gotas (Droplets):

main.ts
import { Construct } from "constructs";
import { App, Fn, TerraformOutput, TerraformStack } from "cdktf";
import { DataDigitaloceanSshKey, DigitaloceanProvider, Droplet, Loadbalancer } from "./.gen/providers/digitalocean"

class MyStack extends TerraformStack {
  constructor(scope: Construct, name: string) {
    ...
    const lb = new Loadbalancer(this, 'lb', {
      ...
    })

    new TerraformOutput(this, "loadBalancerIP", {
      value: lb.ip,
    });

    droplets.forEach((droplet, index) => new TerraformOutput(this, `droplet${index}IP`, {
      value: droplet.ipv4Address
    }))
  }
}
...

Salve e feche o arquivo.

Execute cdktf deploy para atualizar a mudança:

  1. cdktf deploy

Dentro da saída, você deve ver algo semelhante ao seguinte:

Output
───────────────────────────────────────────────────────────────────────────── Changes to Outputs: + droplet0IP = "droplet_foo_ip" + droplet1IP = "droplet_bar_ip" + loadBalancerIP = "load_balancer_ip" You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure. ─────────────────────────────────────────────────────────────────────────────

Esta saída informa que nenhuma alteração de infraestrutura será feita, apenas o que é produzido pela pilha.

Use as teclas de seta para selecionar Aprovar, e então pressione ENTER. No final da saída do terminal, você deve ver algo semelhante a:

Output
infra droplet0IP = droplet_foo_ip droplet1IP = droplet_bar_ip loadBalancerIP = load_balancer_ip

Agora, cada vez que você executar cdktf deploy ou cdktf output, o endereço IP das gotas e dos balanceadores de carga são impressos na saída do terminal, eliminando a necessidade de acessar essas informações pelo console do DigitalOcean.

Você agora provisionou duas gotas e um balanceador de carga e confirmou que estão funcionando. Você pode usar o projeto CDKTF que desenvolveu como base para definir infraestrutura mais sofisticada (você pode encontrar uma implementação de referência em do-community / digitalocean-cdktf-typescript).

Os recursos provisionados neste tutorial incorrerão em custos. Se você não pretende usar a infraestrutura criada, você deve destruí-la. No próximo e último passo, você limpará o projeto destruindo os recursos criados neste tutorial.

Passo 9 — Destruindo Sua Infraestrutura

Neste passo, você removerá todos os recursos criados neste tutorial.

Ainda dentro do diretório infra/, execute cdktf destroy:

  1. cdktf destroy

Você deverá ver uma saída semelhante à seguinte:

Output
infra Initializing the backend... infra Initializing provider plugins... infra - Reusing previous version of digitalocean/digitalocean from the dependency lock file infra - Using previously-installed digitalocean/digitalocean v2.19.0 infra Terraform has been successfully initialized! infra digitalocean_droplet.bar (bar): Refreshing state... [id=298041598] digitalocean_droplet.foo (foo): Refreshing state... [id=298041600] infra digitalocean_loadbalancer.lb (lb): Refreshing state... [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1] infra Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: infra # digitalocean_droplet.bar (bar) will be destroyed - resource "digitalocean_droplet" "bar" { - backups = false -> null - created_at = "2022-05-02T10:04:16Z" -> null - disk = 25 -> null - graceful_shutdown = false -> null - id = "298041598" -> null - image = "ubuntu-20-04-x64" -> null - ipv4_address = "droplet_bar_public_ip" -> null - ipv4_address_private = "droplet_bar_private_ip" -> null - ipv6 = false -> null - locked = false -> null - memory = 1024 -> null - monitoring = false -> null - name = "bar" -> null - price_hourly = 0.00744 -> null - price_monthly = 5 -> null - private_networking = true -> null - region = "lon1" -> null - resize_disk = true -> null - size = "s-1vcpu-1gb" -> null - ssh_keys = [ - "34377800", ] -> null - status = "active" -> null - tags = [] -> null - urn = "do:droplet:298041598" -> null - user_data = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" -> null - vcpus = 1 -> null - volume_ids = [] -> null - vpc_uuid = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null } # digitalocean_droplet.foo (foo) será destruído - resource "digitalocean_droplet" "foo" { - backups = false -> null - created_at = "2022-05-02T10:04:16Z" -> null - disk = 25 -> null - graceful_shutdown = false -> null - id = "298041600" -> null - image = "ubuntu-20-04-x64" -> null - ipv4_address = "droplet_foo_public_ip" -> null - ipv4_address_private = "droplet_foo_private_ip" -> null - ipv6 = false -> null - locked = false -> null - memory = 1024 -> null - monitoring = false -> null - name = "foo" -> null - price_hourly = 0.00744 -> null - price_monthly = 5 -> null - private_networking = true -> null - region = "lon1" -> null - resize_disk = true -> null - size = "s-1vcpu-1gb" -> null - ssh_keys = [ - "34377800", ] -> null - status = "active" -> null - tags = [] -> null - urn = "do:droplet:298041600" -> null - user_data = "f9b1d9796d069fe504ce0d89439b6b664b14b1a1" -> null - vcpus = 1 -> null - volume_ids = [] -> null - vpc_uuid = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null } # digitalocean_loadbalancer.lb (lb) será destruído - resource "digitalocean_loadbalancer" "lb" { - algorithm = "round_robin" -> null - disable_lets_encrypt_dns_records = false -> null - droplet_ids = [ - 298041598, - 298041600, ] -> null - enable_backend_keepalive = false -> null - enable_proxy_protocol = false -> null - id = "4f9ae2b7-b649-4fb4-beed-96b95bb72dd1" -> null - ip = "load_balancer_ip" -> null - name = "default" -> null - redirect_http_to_https = false -> null - region = "lon1" -> null - size_unit = 1 -> null - status = "active" -> null - urn = "do:loadbalancer:4f9ae2b7-b649-4fb4-beed-96b95bb72dd1" -> null - vpc_uuid = "bed80b32-dc82-11e8-83ec-3cfdfea9f3f0" -> null - forwarding_rule { - entry_port = 80 -> null - entry_protocol = "http" -> nul infra l - target_port = 80 -> null - target_protocol = "http" -> null - tls_passthrough = false -> null } - healthcheck { - check_interval_seconds = 10 -> null - healthy_threshold = 5 -> null - path = "/" -> null - port = 80 -> null - protocol = "http" -> null - response_timeout_seconds = 5 -> null - unhealthy_threshold = 3 -> null } - sticky_sessions { - cookie_ttl_seconds = 0 -> null - type = "none" -> null } } Plan: 0 to add, 0 to change, 3 to destroy. ───────────────────────────────────────────────────────────────────────────── Saved the plan to: plan To perform exactly these actions, run the following command to apply: terraform apply "plan" Please review the diff output above for infra ❯ Approve Applies the changes outlined in the plan. Dismiss Stop

Desta vez, em vez de mostrar + ao lado de cada recurso, ele mostra -, indicando que o CDKTF planeja destruir o recurso. Analise as alterações propostas e, em seguida, use as teclas de seta para selecionar Aprovar e pressione ENTER. O provedor da DigitalOcean agora irá se comunicar com a API da DigitalOcean para destruir os recursos.

Output
infra digitalocean_loadbalancer.lb (lb): Destroying... [id=4f9ae2b7-b649-4fb4-beed-96b95bb72dd1] infra digitalocean_loadbalancer.lb (lb): Destruction complete after 1s infra digitalocean_droplet.bar (bar): Destroying... [id=298041598] digitalocean_droplet.foo (foo): Destroying... [id=298041600]

O balanceador de carga foi excluído primeiro porque não possui dependências (nenhum outro recurso faz referência ao balanceador de carga em suas entradas). Como os Droplets fazem referência ao balanceador de carga, eles só podem ser destruídos após o balanceador de carga ser destruído.

Após os recursos terem sido destruídos, você verá a seguinte linha impressa na saída:

Output
Destroy complete! Resources: 3 destroyed.

Conclusão

Neste tutorial, você utilizou o CDKTF para provisionar e destruir uma página da web balanceada, composta por dois Droplets da DigitalOcean executando servidores NGINX, servidos atrás de um balanceador de carga. Você também exibiu informações sobre os recursos no terminal.

O CDKTF é uma camada de abstração acima do Terraform. Um bom entendimento do Terraform é útil para entender o CDKTF. Se você gostaria de aprender mais sobre o Terraform, pode ler a série Como Gerenciar Infraestrutura com Terraform, que cobre o Terraform em profundidade.

Você também pode conferir a documentação oficial do CDK for Terraform e os tutoriais para aprender mais sobre o CDKTF.

Source:
https://www.digitalocean.com/community/tutorials/how-to-deploy-load-balanced-web-applications-on-digitalocean-with-cdk-for-terraform-and-typescript