Как развертывать веб-приложения с балансировкой нагрузки на DigitalOcean с CDK для Terraform и TypeScript

Автор выбрал Фонд Викимедиа для получения пожертвования в рамках программы Write for DOnations.

Введение

Инфраструктура как код (IaC) – это практика автоматизации развертывания и модификации инфраструктуры путем определения состояний ресурсов и их взаимосвязей в коде. Выполнение этого кода создает или модифицирует фактические ресурсы в облаке. IaC позволяет инженерам использовать инструмент IaC, такой как Terraform (от HashiCorp), для предоставления инфраструктуры.

С помощью IaC изменения в вашей инфраструктуре могут проходить через тот же процесс обзора кода, что и ваш код приложения. Вы можете хранить код в системе контроля версий (например, Git), чтобы сохранить историю состояния вашей инфраструктуры, и можете автоматизировать процесс развертывания дополнительно с помощью более высокоуровневых инструментов, таких как внутренняя платформа разработчика (IDP).

Terraform – популярный платформенно-независимый инструмент для управления кодом как инфраструктуры (IaC) благодаря его широкой поддержке многих платформ, включая GitHub, Cloudflare и DigitalOcean. Большинство конфигураций Terraform написаны с использованием декларативного языка, называемого HashiCorp Configuration Language (HCL).

Cloud Development Kit для Terraform (CDKTF) – это инструмент, построенный поверх Terraform, который позволяет определять инфраструктуру с использованием знакомого языка программирования (такого как TypeScript, Python или Go) вместо HCL. Этот инструмент может предоставить более низкую кривую обучения для разработчиков, не знакомых с HCL, позволяя при этом использовать нативные функции программирования, такие как циклы, переменные и функции.

В этом руководстве вы начнете с установки интерфейса командной строки (CLI) инструмента cdktf. Затем вы создадите проект CDKTF на TypeScript и определите проект с двумя серверами NGINX, которые балансируются нагрузкой с помощью балансировщика нагрузки. Затем вы будете использовать cdktf для развертывания инфраструктуры. По завершении этого руководства у вас будет проект CDKTF, с помощью которого вы сможете расширить вашу инфраструктуру.

Примечание: Этот учебник был протестирован с CDKTF 0.11.2 и Terraform 1.2.2.

Предварительные требования

Для завершения этого учебника вам понадобится:

Шаг 1 — Установка CLI cdktf

Для начала установите инструмент командной строки cdktf.

CLI cdktf доступен как пакет NPM. Если вы ищете cdktf на npmjs.com, вы найдете два похожих по названию пакета: cdktf и cdktf-cli.

В концептуальном плане CDKTF является слой абстракции над Terraform. Он состоит из двух частей:

  • библиотека, содержащая набор языконативных конструкций (таких как функции и классы) для определения инфраструктуры. Эта часть инкапсулирована внутри npm-пакета cdktf. Например, вы можете увидеть использование классов App и TerraformStack из пакета cdktf в следующем образце проекта CDKTF:

    import { App, TerraformStack } from "cdktf";
    class APIStack extends TerraformStack {}
    const app = new App();
    new APIStack(app, "feature-x");
    app.synth();
    
  • адаптер, который разбирает конструкции в проекте CDKTF и сокращает их до набора JSON-документов, которые затем вводятся в Terraform таким же образом, как и HCL. Этот адаптер инкапсулирован в инструмент командной строки с названием cdktf, предоставляемом пакетом cdktf-cli.

Чтобы установить инструмент командной строки cdktf, вам понадобится пакет cdktf-cli. Вы можете установить этот пакет глобально, используя npm, yarn или менеджер пакетов по вашему выбору.

Чтобы установить cdktf-cli с помощью npm, выполните следующее:

  1. npm install --global [email protected]

Примечание: После публикации этой статьи вероятно появится более новая версия пакета cdktf-cli. Вы можете попробовать следовать учебнику с последней версией, выполнив вместо этого npm install --global cdktf-cli@latest, но имейте в виду, что некоторые выводы могут немного отличаться.

Кроме того, вы можете использовать Homebrew на macOS или Linux для установки инструмента командной строки cdktf в виде формулы cdktf:

  1. brew install cdktf

Чтобы проверить успешную установку, выполните команду cdktf без аргументов:

  1. cdktf

Вы увидите вывод, аналогичный следующему:

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 Показать номер версии --disable-logging Не записывать файлы журнала. Поддерживается с использованием переменной окружения CDKTF_DISABLE_LOGGING. --disable-plugin-cache-env Не устанавливать каталог TF_PLUGIN_CACHE_DIR автоматически. --log-level Какой уровень журнала должен быть записан. -h, --help Show help Options can be specified via environment variables with the "CDKTF_" prefix (e.g. "CDKTF_OUTPUT")

Вывод показывает вам доступные команды. В остальной части этого руководства вы получите опыт использования cdktf init, cdktf get, cdktf deploy и cdktf destroy.

Теперь, когда вы установили CLI cdktf, вы можете определить инфраструктуру, написав некоторый код TypeScript.

Шаг 2 — Создание нового проекта CDKTF

На этом этапе вы будете использовать только что установленный вами CLI cdktf, чтобы создать каркас проекта CDKTF, на котором вы будете строить в последующих шагах.

Создайте каталог, который будет содержать проект CDKTF, выполнив следующую команду:

  1. mkdir infra

Затем перейдите в только что созданный каталог:

  1. cd infra/

Используйте команду cdktf init, чтобы создать каркас проекта CDKTF, на котором вы будете строить:

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

CDKTF позволяет разработчикам определять инфраструктуру с использованием TypeScript, Python, Java, C# или Go. Опция --template=typescript сообщает cdktf создать этот проект CDKTF с использованием TypeScript.

Terraform (и, следовательно, CDKTF) отслеживает управляемые ресурсы, записывая их определения и состояния в файлы, называемые файлами состояния Terraform. Опция --local указывает CDKTF сохранять эти файлы состояния локально на компьютере, на котором запускается cdktf (каждый файл следует структуре именования terraform.<stack>.tfstate).

После выполнения команды интерфейс командной строки может попросить вас разрешения отправить отчеты о сбоях команде CDKTF, чтобы помочь им улучшить продукт:

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)

Введите Y, если вы хотите дать согласие, или N, если вы не согласны, а затем нажмите ENTER.

Затем cdktf создаст каркас проекта и установит пакеты. Когда проект создан, вы увидите вывод, аналогичный следующему:

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)

Вы также увидите, что в каталоге infra добавлены новые файлы. Самые важные файлы – это cdktf.json и main.ts.

cdktf.json – это файл конфигурации для проекта CDKTF. Если вы откроете файл, он отобразит что-то вроде следующего:

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

Свойство app определяет команду, которая будет запущена для синтеза кода TypeScript в JSON, совместимый с Terraform. Это свойство указывает, что main.ts является точкой входа в проект CDKTF.

Если вы откроете файл main.ts, вы увидите что-то похожее на следующее:

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

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

    // определите ресурсы здесь
  }
}

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

На языке CDKTF коллекцию связанных ресурсов инфраструктуры можно сгруппировать в стек. Например, ресурсы, составляющие приложение API, такие как Droplets, балансировщики нагрузки и DNS-записи, можно сгруппировать в один стек с именем APIStack. Каждый стек сохраняет свое собственное состояние и может быть развернут, изменен или уничтожен независимо от других стеков. Обычное использование стеков заключается в том, чтобы иметь один стек для производства и отдельный стек для разработки.

Приложение является контейнером для нескольких стеков. Например, приложение может сгруппировать стеки различных микросервисов.

Каркас проекта CDKTF, сгенерированный в файле main.ts, содержит единственный класс стека с именем MyStack, в настоящее время не определяющий никаких ресурсов. Экземпляр MyStack создается с именем infra, содержащийся в приложении с именем app. В последующих шагах вы определите ресурсы инфраструктуры в конструкторе MyStack.

После создания проекта следующим шагом является настройка проекта CDKTF с поставщиками.

Шаг 3 — Установка поставщика DigitalOcean

На этом шаге вы установите поставщика DigitalOcean в проект CDKTF.

Поставщики – это библиотеки, которые предоставляют инструкции для Terraform (который используется cdktf внутри) о том, как создавать, обновлять и удалять ресурсы на облачных провайдерах, провайдерах SaaS и других платформах, предоставляющих программные интерфейсы приложений (API). Поставщики инкапсулируют логику вызова этих внешних API в стандартные функции, которые Terraform может вызывать.

Например, если бы вы хотели создать новый Droplet в DigitalOcean без использования Terraform, вам пришлось бы отправить запрос POST к конечной точке /v2/droplets API DigitalOcean. С Terraform вы вместо этого установите поставщика DigitalOcean и определите ресурс digitalocean_droplet, подобно следующему образцу кода:

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

Затем вы можете использовать инструмент CLI cdktf для преобразования этого кода TypeScript в совместимый с Terraform JSON и передачи его поставщику, который сделает соответствующие вызовы API для создания Droplet от вашего имени.

Теперь, когда вы понимаете, что такое поставщик, вы можете настроить поставщика DigitalOcean для вашего проекта CDKTF.

Откройте файл cdktf.json и добавьте строку digitalocean/digitalocean в массив 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 является идентификатором провайдера DigitalOcean в Реестре Terraform.

Сохраните и закройте файл.

Затем выполните cdktf get, чтобы загрузить и установить провайдер.

  1. cdktf get

cdktf get загрузит провайдер, извлечет схему, сгенерирует соответствующие классы TypeScript и добавит его в качестве модуля TypeScript в .gen/providers/. Эта автоматическая генерация кода позволяет использовать любые провайдеры Terraform и модули HCL с CDKTF, именно так CDKTF может обеспечить автозаполнение кода в редакторах, которые его поддерживают.

После завершения выполнения cdktf get вы увидите вывод, аналогичный следующему:

Output
Generated typescript constructs in the output directory: .gen

Вы также увидите новый каталог с названием .gen, содержащий сгенерированный код провайдера.

На этом шаге вы установили провайдер digitalocean/digitalocean в проект. На следующем шаге вы настроите провайдер DigitalOcean с учетными данными, необходимыми для аутентификации провайдера с API DigitalOcean.

Шаг 4 — Настройка провайдера DigitalOcean

На этом этапе вы настроите провайдер DigitalOcean с помощью вашего персонального токена доступа к DigitalOcean, который позволяет провайдеру вызывать API DigitalOcean от вашего имени.

Разные провайдеры требуют и поддерживают разные учетные данные для аутентификации в API. Для провайдера DigitalOcean вам необходимо предоставить ваш персональный токен доступа к DigitalOcean. Вы можете указать токен провайдеру, установив его как переменную среды DIGITALOCEAN_TOKEN или DIGITALOCEAN_ACCESS_TOKEN.

Выполните следующую команду в вашем терминале, чтобы установить переменную среды для этой сессии терминала.

  1. export DIGITALOCEAN_ACCESS_TOKEN="your_personal_access_token"

Примечание: Вы вызываете команду export только для этой сессии терминала. Если вы закроете и заново откроете терминал или запустите команды cdktf в другом терминале, вам придется снова выполнить команду export, чтобы переменная среды вступила в силу.

Затем вы укажете провайдера внутри класса MyStack, что позволит вам определить ресурсы, предоставляемые провайдером в вашем стеке. Обновите файл main.ts следующим образом:

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();

Модуль для провайдера расположен в ./.gen/providers/digitalocean, который был автоматически создан при запуске cdktf get.

На этом этапе вы настроили провайдер digitalocean/digitalocean с учетными данными. Далее вы начнете определять инфраструктуру, которая является частью цели этого руководства.

Шаг 5 — Определение веб-приложений на Droplets

В этом шаге вы определите два сервера NGINX, каждый из которых будет обслуживать разные файлы, развернутые на двух идентичных Droplets с Ubuntu 20.04.

Вы начинаете с определения двух Droplets. Измените main.ts с выделенными изменениями:

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',
      })
    )
  }
}

Вы используете цикл нативного JavaScript (Array.prototype.map()), чтобы избежать дублирования кода.

Как если бы вы создавали Droplet через консоль, необходимо указать несколько параметров:

  • image – дистрибутив Linux и его версию, на котором будет выполняться ваш Droplet.
  • region – центр обработки данных, в котором будет работать Droplet.
  • size – количество ресурсов CPU и памяти, зарезервированных для Droplet.
  • name – уникальное имя, используемое для ссылки на Droplet.

Значения для image, region и size должны быть такими, которые поддерживает DigitalOcean. Вы можете найти допустимые значения (называемые слагами) для всех поддерживаемых образов дистрибутивов Linux, размеров Droplet и регионов на странице Slugs API DigitalOcean. Полный список обязательных и дополнительных атрибутов можно найти на странице документации digitalocean_droplet.

Добавление SSH-ключа

В рамках предварительных требований вы загрузили в свою учетную запись DigitalOcean общедоступный SSH-ключ без пароля и отметили его имя. Теперь вы будете использовать это имя, чтобы получить идентификатор SSH-ключа и передать его в определение вашего Droplet.

Поскольку SSH-ключ был добавлен в вашу учетную запись DigitalOcean вручную, он не является ресурсом, управляемым вашей текущей конфигурацией Terraform. Если вы попытаетесь определить новый ресурс digitalocean_ssh_key, будет создан новый SSH-ключ вместо использования существующего.

Вместо этого вы определите новый источник данных digitalocean_ssh_key data source. В Terraform data sources используются для получения информации о инфраструктуре, которая не управляется текущей конфигурацией Terraform. Другими словами, они предоставляют только для чтения вид на состояние существующей внешней инфраструктуры. После того как источник данных определен, вы можете использовать данные в других местах вашей конфигурации Terraform.

Все еще в main.ts и в конструкторе MyStack определите новый источник данных DataDigitaloceanSshKey и передайте в него имя, которое вы присвоили своему SSH-ключу (здесь имя – 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, {
    ...
  }
}
...

Затем обновите определение Droplet, чтобы включить 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()]
}))
...

После провиженинга вы сможете получить доступ к Droplet, используя закрытый SSH-ключ вместо пароля.

Указание пользовательского скрипта для установки NGINX

Теперь вы определили два идентичных Droplet, работающих на Ubuntu, настроенных для доступа по SSH. Следующая задача – установить NGINX на каждом Droplet.

Когда создается Droplet, инструмент под названием CloudInit будет инициализировать сервер. CloudInit может принимать файл, называемый user data, который может изменить способ инициализации сервера. User data может быть любыми файлами cloud-config или скриптами, которые сервер может интерпретировать, такими как сценарии Bash.

В оставшейся части этого шага вы создадите сценарий Bash и укажете его в качестве user data Droplet. Сценарий будет устанавливать NGINX в рамках процесса инициализации. Кроме того, сценарий также заменит содержимое файла /var/www/html/index.html (файл по умолчанию, обслуживаемый NGINX) именем хоста и IP-адресом Droplet, что приведет к тому, что два сервера NGINX будут обслуживать разные файлы. На следующем шаге вы поместите оба этих сервера NGINX за балансировщик нагрузки; по обслуживаемым разным файлам будет ясно, правильно ли распределяет балансировщик нагрузку запросов или нет.

Еще в файле main.ts добавьте новое свойство userData к объекту конфигурации 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
`
    }))
  }
}

Предупреждение: Убедитесь, что перед шебангом (#!) нет новых строк; в противном случае сценарий может не выполниться.

Когда Droplet впервые создается, сценарий будет запущен от имени пользователя root. Он будет использовать менеджер пакетов Ubuntu, APT, чтобы установить пакет nginx. Затем он будет использовать Службу метаданных DigitalOcean, чтобы получить информацию о себе, и записать имя хоста и IP-адрес в index.html, который обслуживается NGINX.

На этом этапе вы определили два Droplet, работающих под управлением Ubuntu, настроили каждый из них на доступ по SSH и установили NGINX с использованием функции пользовательских данных. На следующем этапе вы определите балансировщик нагрузки, который будет находиться перед этими серверами NGINX, и настроите его для балансировки нагрузки по круговому методу.

Шаг 6 — Определение балансировщика нагрузки

На этом этапе вы определите балансировщик нагрузки DigitalOcean, определив экземпляр ресурса digitalocean_loadbalancer.

В файле main.ts добавьте следующее определение балансировщика нагрузки в конце конструктора 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))
    })
  }
}
...

Аргумент forwardingRule указывает балансировщику нагрузки прослушивать HTTP-запросы на порту 80 и перенаправлять их на каждый из Droplets на порт 80.

Аргумент dropletIds определяет Droplets, на которые балансировщик нагрузки будет передавать запросы. Он принимает число, но значение droplet.id является строкой. Поэтому вы использовали Fn.tonumber функцию Terraform для преобразования строки значения идентификатора Droplet в число.

Примечание: Вы использовали функцию Terraform Fn.tonumber здесь вместо нативной для JavaScript функции parseInt, потому что значение droplet.id неизвестно до тех пор, пока Droplet не будет развернут. Функции Terraform предназначены для работы с неизвестными значениями времени выполнения до применения конфигурации Terraform.

Сохраните и закройте файл.

Теперь вы определили два Droplets и балансировщик нагрузки, который находится перед ними. Ваш main.ts должен выглядеть примерно так:

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();

На следующем этапе вы будете использовать инструмент командной строки cdktf для актуализации всего вашего проекта CDKTF.

Шаг 7 — Развертывание вашей инфраструктуры

На этом этапе вы будете использовать инструмент командной строки cdktf для предоставления каплетов и балансировщиков нагрузки, которые вы определили на предыдущих этапах.

Убедитесь, что вы находитесь в каталоге infra/ и установили переменную среды DIGITALOCEAN_ACCESS_TOKEN для вашей сессии терминала, затем выполните команду cdktf deploy:

  1. cdktf deploy

Вы должны увидеть вывод, аналогичный следующему:

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) будет создан + 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) будет создан + 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

Примечание: CDKTF все еще находится в разработке, и вывод может отличаться от показанного выше.

Этот дисплей перечисляет все ресурсы и свойства, которые cdktf планирует создать, обновить и удалить. Некоторые значения, такие как идентификатор каплета, известны только после предоставления ресурса. Для них вы увидите (known after apply) в качестве значения свойства в выводе.

Просмотрите список ресурсов, чтобы убедиться, что это то, что вы ожидаете. Затем, используя стрелки, выберите опцию Approve и нажмите ENTER.

Вы увидите вывод, аналогичный следующему:

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

Этот вывод сообщает вам, что cdktf взаимодействует с API DigitalOcean для создания каплета. cdktf создает каплеты первыми, потому что балансировщик нагрузки зависит от идентификатора каплета, который неизвестен до предоставления каплетов.

Создание каплета обычно занимает менее минуты. После предоставления каплетов cdktf переходит к созданию балансировщика нагрузки.

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]

Балансировщик нагрузки может занять больше времени. После создания балансировщика нагрузки вы увидите сводку, которая покажет, что стек был успешно развернут.

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.

Теперь вы можете посетить консоль DigitalOcean, где вы увидите один балансировщик нагрузки с именем default и два здоровых Droplet’а с именами foo и bar, каждый из которых служит целью для балансировщика нагрузки.

Вы можете проверить, что NGINX работает и корректно обслуживает контент, перейдя по IP-адресу каждого Droplet’а. Вы должны увидеть текст, аналогичный следующему:

Droplet: bar, IP Address: droplet_ip

Если вы не видите эту строку текста или сервер не отвечает, проверьте правильность указанных вами пользовательских данных и отсутствие символов (включая переносы строк) перед началом shebang (#!). Вы также можете подключиться по SSH к Droplet’у, используя ваш приватный ключ SSH, и просмотреть выводные журналы, созданные CloudInit в /var/log/cloud-init-output.log:

  1. ssh -i path_to_ssh_private_key root@droplet_ip

После подтверждения того, что Droplet’ы работают и обслуживают контент, вы можете начать тестирование балансировщика нагрузки. Для этого отправьте несколько запросов.

Запустите следующую команду из вашего терминала, чтобы отправить десять запросов на балансировщик нагрузки:

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

Вы должны увидеть вывод, аналогичный следующему, хотя показанные IP-адреса будут отличаться:

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

Это показывает, что запросы к балансировщику нагрузки были перенаправлены на каждый Droplet пять раз, что указывает на работу балансировщика нагрузки.

Примечание: Балансировщик нагрузки не всегда сбалансирует нагрузку между двумя Droplet’ами идеально; вы можете обнаружить, что четыре запроса были отправлены на один Droplet, а шесть – на другой. Это нормальное поведение.

На этом этапе вы использовали cdktf для создания ваших ресурсов, а затем использовали консоль DigitalOcean для определения IP-адресов ваших Droplets и балансировщика нагрузки. Затем вы отправили запросы к каждому Droplet и балансировщику нагрузки, чтобы подтвердить, что они работают.

На следующем этапе вы получите IP-адреса Droplets и балансировщика нагрузки без входа в консоль DigitalOcean.

Шаг 8 — Вывод информации

На предыдущем этапе вам пришлось войти в Консоль DigitalOcean, чтобы получить IP-адреса вашего Droplet и балансировщика нагрузки. На этом этапе вы немного измените свой код, чтобы эта информация выводилась в выводе команды cdktf deploy, что позволит вам избежать поездки в консоль.

Terraform записывает конфигурацию и состояние своих управляемых ресурсов в файлы состояния. Для вашего стека infra файл состояния можно найти по адресу infra/terraform.infra.tfstate. Вы сможете найти IP-адреса Droplets и балансировщика нагрузки внутри этого файла состояния.

Однако разбираться с большим файлом может быть неудобно. CDKTF предоставляет конструкцию TerraformOutput, которую вы можете использовать для вывода переменных и сделать их доступными за пределами стека. Любые выходные данные выводятся в stdout после выполнения команды cdktf deploy. Вы также можете вывести выходные данные в любое время, выполнив cdktf output.

Примечание: Хотя в этом руководстве вы используете выходы для вывода информации в консоль, его реальная мощь заключается в использовании стеков, используя выходы из других стеков в качестве входных данных, функция, известная как кросс-стековые ссылки.

Обновите файл main.ts, чтобы включить выходы IP-адресов балансировщика нагрузки и 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
    }))
  }
}
...

Сохраните и закройте файл.

Запустите cdktf deploy, чтобы внести изменения в действие:

  1. cdktf deploy

В выводе вы должны увидеть что-то похожее на следующее:

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. ─────────────────────────────────────────────────────────────────────────────

Этот вывод сообщает вам, что изменения в инфраструктуре не будут внесены, только то, что выведено из стека.

Используйте стрелки для выбора Утвердить, а затем нажмите ENTER. В конце вывода терминала вы должны увидеть что-то похожее на:

Output
infra droplet0IP = droplet_foo_ip droplet1IP = droplet_bar_ip loadBalancerIP = load_balancer_ip

Теперь, каждый раз, когда вы запускаете cdktf deploy или cdktf output, IP-адреса Droplets и балансировщиков нагрузки выводятся в вывод терминала, что устраняет необходимость получения этой информации из консоли DigitalOcean.

Теперь вы создали два Droplets и балансировщик нагрузки и подтвердили, что они работают. Вы можете использовать разработанный вами проект CDKTF в качестве основы для определения более сложной инфраструктуры (вы можете найти ссылку на референсную реализацию по адресу do-community / digitalocean-cdktf-typescript).

Ресурсы, выделенные в этом руководстве, будут платными. Если вы не собираетесь использовать созданную инфраструктуру, вам следует уничтожить её. В следующем и последнем шаге вы очистите проект, уничтожив созданные в этом руководстве ресурсы.

Шаг 9 — Уничтожение вашей инфраструктуры

На этом этапе вы удалите все созданные в этом руководстве ресурсы.

Оставаясь в каталоге infra/, выполните команду cdktf destroy:

  1. cdktf destroy

Вы должны увидеть вывод, аналогичный следующему:

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) будет уничтожен - 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) будет уничтожен - 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

На этот раз, вместо отображения + рядом с каждым ресурсом, отображается -, указывающий, что CDKTF планирует уничтожить ресурс. Ознакомьтесь с предложенными изменениями, затем используйте стрелки для выбора Утвердить и нажмите ENTER. Теперь провайдер DigitalOcean будет взаимодействовать с API DigitalOcean для уничтожения ресурсов.

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]

Балансировщик нагрузки был удален первым, потому что у него нет зависимостей (ни один другой ресурс не ссылается на балансировщик нагрузки в своих входных данных). Поскольку балансировщик нагрузки ссылается на Droplets, они могут быть уничтожены только после уничтожения балансировщика нагрузки.

После того как ресурсы будут уничтожены, вы увидите следующую строку в выводе:

Output
Destroy complete! Resources: 3 destroyed.

Заключение

В этом учебнике вы использовали CDKTF для создания и уничтожения веб-страницы с балансировкой нагрузки, состоящей из двух Droplets DigitalOcean, работающих на серверах NGINX, обслуживаемых за балансировщиком нагрузки. Вы также выводили информацию о ресурсах в терминале.

CDKTF является уровнем абстракции над Terraform. Хорошее понимание Terraform полезно для понимания CDKTF. Если вы хотите узнать больше о Terraform, вы можете прочитать серию Как управлять инфраструктурой с помощью Terraform, которая подробно описывает Terraform.

Вы также можете ознакомиться с официальной документацией по CDK for Terraform и учебными пособиями, чтобы узнать больше о CDKTF.

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