Рубрики
Uncategorized

Terraform: Напишите конфигурации с помощью node.js

В этой статье я собираюсь поделиться своим опытом в написании конфигураций Terraform с использованием node.js …. Tagged с Terraform, Node, DevOps, ShowDev.

В этой статье я собираюсь поделиться своим опытом в написании конфигураций Terraform с использованием node.js.

Я собираюсь продемонстрировать некоторые преимущества написания конфигураций Terraform в JavaScript/TypeScript по сравнению с написанием собственных конфигураций Terraform.

Рекомендует ли это Хашикор?

Terraform также поддерживает альтернативный синтаксис, который совместим с JSON. Этот синтаксис полезен при разработке части конфигурации, поскольку существующие библиотеки JSON можно использовать для подготовки сгенерированных файлов конфигурации.

Выше цитата можно увидеть в документации Terraform: https://www.terraform.io/docs/configuration/syntax-json.html Анкет

Хотя Hashicorp на самом деле не рекомендует использовать какой -либо инструмент для создания конфигураций Terraform, он признает, что вполне возможно и хорошо для программного создания конфигураций Terraform.

Инструмент

Инструмент, который я использую, называется Terraform-Generator Анкет Он доступен в реестре NPM: https://www.npmjs.com/package/terraform Generator Анкет

Что Terraform-Generator DISE помогает генерировать конфигурации Terraform, используя возможности Node.js & JavaScript/TypeScript. В настоящее время он поддерживает создание конфигураций для Terraform 0,11 и 0,12.

Отношения между Terraform-Generator и конфигурация Terraform аналогична конфигурации запроса и запроса базы данных, TypeScript и JavaScript или React.js и HTML & Web JavaScript.

Синтаксис

Синтаксис на самом деле очень похож на нативный синтаксис Terraform. Ниже приводится сравнение создания ресурса в нативном терраформе и в Terraform-Generator Анкет

Терраформ

resource "aws_vpc" "default" {
  cidr_block = "172.88.0.0/16"
}

Терраформ-генератор

tfg.resource('aws_vpc', 'default', {
  cidr_block: '172.88.0.0/16'
});

Преимущества

Расширение VSCODE

В прошлый раз, когда я проверил, нет расширения VSCODE для Terraform 0.12. Это боль, когда вы хотите ориентироваться между ресурсами, переменными и т. Д.

Написав JavaScript/TypeScript в VSCODE, у вас не будет проблем с этим. Он также предоставляет все обычные преимущества, такие как Auto-Complete, которые недоступны в родном Terraform.

Общие конфигурации

Проект Terraform — это папка, которая содержит один или много файлов .tf и папку .Terraform, которая содержит необходимые плагины Terraform.

Допустим, у меня есть 3 проекта для настройки системы, у них есть общий поставщик, некоторые общие локальные переменные и некоторые общие входные переменные, общие вещи должны существовать во всех 3 проектах. Это заставляет мои сценарии иметь дублированные элементы и уменьшает обслуживание.

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

Используя Node.js и Terraform-Generator, общие переменные или код могут быть записаны везде, где вы считаете подходящим, их использование-это просто импорт их.

Местные экологические состояния

Terraform будет генерировать Terraform.tfstate и Terraform.tfstate.backup, когда мы применяем проект. Один проект может иметь только одно состояние.

Допустим, мой проект будет выполнен в 3 средах (разработка, постановка и производство), я не смогу сохранить состояние в своем местном каталоге, потому что у меня будет 3 разных штата, 1 для каждой среды. Мне придется сохранить штаты в удаленном хранилище (например, AWS S3).

Одним из способов достижения сохранения состояний для нескольких среда в локации является перемещение состояний в другую папку, вне папки проекта после выполнения проекта.

Ниже приведен пример моей структуры папок Terraform и того, как я решаю общие конфигурации и проблемы окружающей среды со сценарием Bash. Это увеличивает сложность и уменьшает обслуживание.

Структура папки

.
├── common                          # Shared configurations, to be copied to project folder before execution
|   ├── constants.tf
|   ├── env.tf
|   ├── provider.tf
|
├── env                             # Environmental variables, to be copied to project folder before execution
|   ├── dev.tfvars
|   ├── stg.tfvars
|   ├── prd.tfvars
|
├── outputs                         # Environmental states, to be copied to project folder before execution,
|   |                                 and then moved out from project folder after execution
|   ├── project1
|   |   ├── dev
|   |   |   ├── terraform.tfstate
|   |   |   ├── terraform.tfstate.backup
|   |   |
|   |   ├── stg
|   |   |   ├── ...
|   |   |
|   |   ├── prd
|   |       ├── ...
|   |
|   ├── project2
|   |   ├── ...
|   |
|   ├── project3
|       ├── ...
|
├── projects                        # Actual Terraform projects
|   ├── project1
|   |   ├── .terraform
|   |   ├── terraform.tf
|   |
|   ├── project2
|   |   ├── ...
|   |
|   ├── project3
|       ├── ...
|
├── run.sh                          # Bash script to do all the copying and moving of all the shared & environmental
                                      configurations and environmental states

run.sh

ACTION=$1
PROJECT=$2
ENV=$3

cd projects/$PROJECT

# Copy common tf, tfvars & tfstate to project folder
cp ../../common/* .
cp ../../env/$ENV.tfvars .
cp ../../outputs/$PROJECT/$ENV/* .

# Run terraform
terraform $ACTION -var-file=$ENV.tfvars

# Remove common tf & tfvars
rm -f constants.tf
rm -f env.tf
rm -f provider.tf
rm -f $ENV.tfvars

# Move tfstate to outputs folder
mkdir -p ../../outputs/$PROJECT/$ENV
mv terraform.tfstate ../../outputs/$PROJECT/$ENV
mv terraform.tfstate.backup ../../outputs/$PROJECT/$ENV

Используя Terraform-Generator, сохраняя один исходный код, я смогу генерировать несколько проектов Terraform для нескольких сред, чтобы сэкономить местные государства в их соответствующей папке проекта.

Ниже приведен пример моей структуры папок Terraform Generator, чтобы показать вам, где находятся созданные конфигурации и состояния Terraform.

.
├── node_modules
|   ├── ...
|
├── outputs
|   ├── dev
|   |   ├── project1
|   |   |   ├── .terraform
|   |   |   ├── terraform.tf
|   |   |   ├── terraform.tfstate
|   |   |   ├── terraform.tfstate.backup
|   |   |
|   |   ├── project2
|   |   |   ├── ...
|   |   |
|   |   ├── project3
|   |       ├── ...
|   |
|   ├── stg
|   |   ├── ...
|   |
|   ├── prd
|       ├── ...
|
├── src
|   ├── constants
|   |   ├── ...
|   |
|   ├── env
|   |   ├── dev.env.ts
|   |   ├── index.ts
|   |   ├── stg.env.ts
|   |   ├── prd.env.ts
|   |
|   ├── projects
|       ├── project1
|       ├── ...
|       |
|       ├── project2
|       ├── ...
|       |
|       ├── project3
|       ├── ...
|
├── package.json
├── tsconfig.json

Папка SRC содержит мой исходный код, он генерирует конфигурацию Terraform для папки выходов в соответствии с средой и проектом, а состояния сохраняются в той же папке, что и сгенерированная конфигурация Terraform.

Короче говоря, у меня будет 3 аналогичные конфигурации Terraform и 3 состояния, сохраняя только 1 исходный код.

Переменные

Чтобы использовать переменные, Terraform требует от нас написать что -то вроде этого:

variable "env" {
  type = string
}

variable "vpc_cidr" {
  type = string
}

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

В JavaScript использование переменной является лишь вопросом импорта переменной или импорта файла JSON.

Если вы используете TypeScript и хотите объявить интерфейс для всех ваших переменных, он так же просто, как и следующий пример:

export interface Variables {
  env: string;
  vpcCidr: string;
}

Вы также можете использовать различные библиотеки для управления переменными, например, Dotenv.

Условные

Terraform не поддерживает if-else-statement, точка.

Используя JavaScript/TypeScript, вы можете использовать if-else или коммутатор, как вам нравится.

В следующем примере показан один из использования использования if-else в моем проекте:

const getAvailabilityZone = (idx: number): string => {
  const i = 3 % idx;
  if (i === 0) {
    return 'ap-southeast-1a';
  } else if (i === 1) {
    return 'ap-southeast-1b';
  } else {
    return 'ap-southeast-1c';
  }
};

for (let i = 0; i < 3; i++) {
  tfg.resource('aws_subnet', `subnet${i}`, {
    vpc_id: vpc.attr('id'),
    cidr_block: env.subnetCidrs[i],
    availability_zone: getAvailabilityZone(i)
  });
}

Без петли (я расскажу об этом в следующем разделе) и If-Else Statement, мне придется повторить создание подсети 3 раза, чтобы создать их в 3 зонах доступности.

Вы также можете использовать условные для управления атрибутами ресурсов и создания ресурсов, например

if (env === 'production') {
  // create resource that is exclusive to production environment
}

tfg.resource('resource_type', 'resource_name', {
  attribute: env === 'production' ? 'some value': 'other value'
}

Петли

Terraform поддерживает какую -то цикл, например, count & for_each.

Ограничение петли Terraform заключается в том, что они поддерживаются только блоком ресурсов, но не модульными блоками на данный момент.

Что если мы хотим создать несколько ресурсов в цикле? Нам придется использовать счет/for_each в каждом блоке ресурсов. Разве не было бы более выгодным иметь только 1 петлю и создавать все ресурсы внутри цикла?

Цикл Terraform — это один цикл уровня (1 петля в блоке ресурсов). Что если есть необходимость иметь вложенную петлю? Например. Используя цикл для создания 3 групп безопасности для каждой группы безопасности, создайте 3 правила группы безопасности. Без вложенного цикла невозможно сохранить вашу конфигурацию в чистоте.

В Terraform мне придется сделать что -то вроде этого:

resource "aws_security_group" "sg" {
  count = 3
  ...
}

resource "aws_security_group_rule" "sgrule0" {
  count = length(aws_security_group.sg)
  security_group_id = aws_security_group.sg.*.id[count.index]
  ...
}

resource "aws_security_group_rule" "sgrule1" {
  count = length(aws_security_group.sg)
  security_group_id = aws_security_group.sg.*.id[count.index]
  ...
}

resource "aws_security_group_rule" "sgrule2" {
  count = length(aws_security_group.sg)
  security_group_id = aws_security_group.sg.*.id[count.index]
  ...
}

Используя Terraform-Generator, вы сможете сделать что-то вроде этого:

for (let i = 0; i < 3; i++) {
  const sg = tfg.resource('aws_security_group', `sg${i}`, {
    ...
  });

  for (let j = 0; j < 3; j++) {
    tfg.resource('aws_security_group_rule', `sgrule${j}`, {
      security_group_id = sg.attr('id')
      ...
    });
  }
}

Используя JavaScript/TypeScript, не стесняйтесь использовать любой цикл, как вы считаете нужным. Пример использования для петли показан в предыдущем разделе.

Модули против функций

Модуль Terraform аналогичен проекту Terraform (проект также известен как корневой модуль).

Процесс создания и использования модуля Terraform утомите, у меня будет другая папка с другим набором файлов .tf и требуемыми плагинами, и он даже не имеет прямого доступа к моим общим конфигурациям и переменным окружающей среды.

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

variable "project_name" {
  type = string
}

variable "env" {
  type = string
}

output "tags" {
  value = {
    Project = var.project_name
    Env     = var.env
  }
}

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

module "tags" {
  source        = "../../modules/tags"
  project_name  = local.projectName
  env           = var.env
}

resource "aws_vpc" "default" {
  cidr_block = var.vpc_cidr

  tags = merge(module.tags.tags, map(
    "Name", "vpc-${local.project_name}-${var.env}"
  ))
}

Project_Name и Env — мои общие переменные, они обмениваются всеми проектами в рамках одной и той же настройки системы, но мне все еще нужно передать их в модуль, потому что он не может получить к ним доступ напрямую.

Кроме того, блок модуля имеет набор фиксированных атрибутов и фиксированный выход, я не могу передать аргументы и получить адаптированный выход, поэтому мне нужно объединить мои переменные теги с моими постоянными тегами вручную. Процесс утомительный.

Используя функцию Terraform-Generator и JavaScript, вот как я это сделаю:

const getTags = (name: string): Map => map({
  Name: `${name}-${constants.projectName}-${env.env}`,
  Project: constants.projectName,
  Env: env.env
});

tfg.resource('aws_vpc', 'default', {
  cidr_block: env.vpcCidr,
  tags: getTags('vpc')
});

Очевидно, что версия TypeScript намного проще и гораздо проще. Он имеет доступ к моим постоянным и переменным окружающей среды, он принимает аргументы и возвращает именно то, что мне нужно.

Другие возможности

Сила использования node.js для генерации конфигураций терраформ безгранична, или я должен сказать, что он ограничен только теми способностью, предоставляемыми Node.js и миром JavaScript, что намного шире, чем то, что обеспечивается Terraform. Вы сможете использовать любые модули API node.js и NPM.

Когда мы должны использовать это

Если вы профессиональный поставщик услуг, я не могу сообщить вам о том, использует ли это Terraform-Generator это хороший шаг, потому что это не широко принятый инструмент (пока). Есть больше соображений, о которых можно подумать, например, Примут ли ваши клиенты использование этого непопулярного инструмента? Ваша компания/коллеги достаточно открыты, чтобы попробовать это? Будет ли он иметь проблему оперативного/технического обслуживания в будущем?

Однако, если вы делаете свою собственную облачную инфра -настройку и думаете, что это может решить некоторые ваши боли в использовании Terraform, почему бы не попробовать и рассказать мне, что вы думаете в разделе комментариев.

Оригинал: «https://dev.to/ahzhe/writing-terraform-using-node-js-3l09»