Лучший способ в настоящее время создать доступный инвентарь из terraform

У меня есть длинный список машин, каждая из которых немного отличается по функциональности в системе. Я хотел бы организовать эти машины и автоматически добавить их в файл инвентаризации хостов, чтобы я мог запускать и управлять инвентаризацией. Есть ли хорошие решения для этого?

Я думаю, что ansible hosts должен выглядеть примерно так...

[webservers]
someip
someip
[integration]
someip
someip

и т.д..

Задав вопрос, я сейчас изучаю выходные переменные и использую их для рендеринга шаблона из файла.


person DrM    schedule 03.08.2017    source источник
comment
Можете ли вы добавить немного подробностей о том, как организован ваш терраформ и как вы ожидаете, что ваш файл инвентаря будет организован? если вы выводите IP-адреса машин из terraform, вы можете использовать команду terraform output <identifier>, чтобы вернуть список IP-адресов, которые могут быть набором инвентаря для ваших игр.   -  person RaGe    schedule 03.08.2017
comment
Это полезно. На самом деле я думаю, что, возможно, мне следует использовать комбинации шаблонов и выходных переменных для рендеринга. Я нашел следующее репо с кодом github.com/opencredo/k8s. -terraform-ansible-sample/tree/master/, где человек отображает конфигурацию ssh.   -  person DrM    schedule 03.08.2017
comment
просто чтобы вы знали, что вы также можете запускать плейбуки изнутри terraform, используя ресурс null_configuration.   -  person RaGe    schedule 03.08.2017
comment
Знаете ли вы об этом: github.com/adammck/terraform-inventory? Я нашел его некоторое время назад, просматривая теги Terraform на Github (кстати, классная функция).   -  person Matt Schuchard    schedule 03.08.2017
comment
Нет, это довольно крутая идея — преобразовать его с помощью файла состояния терраформирования. Я также ценю примечание от RaGe о запуске ansible с использованием нулевого ресурса. Звучит как хороший трюк, но как вы будете запускать его, только если в этом сценарии состояние изменится - не уверен.   -  person DrM    schedule 03.08.2017
comment
Вы добавляете триггер к null_resource, который зависит от соответствующего ресурса. экземпляр EC2, например. Всякий раз, когда воссоздается EC2, запускается null_resource. Здесь это не по теме, если вы разместите новый вопрос, я опубликую более подробный ответ.   -  person RaGe    schedule 08.08.2017


Ответы (7)


Я понял.

data "template_file" "dev_hosts" {
  template = "${file("${path.module}/templates/dev_hosts.cfg")}"
  depends_on = [
    "aws_instance.dev-api-gateway",
    "aws_instance.dev-api-gateway-internal",
    ....
  ]
  vars {
    api_public = "${aws_instance.dev-api-gateway.private_ip}"
    api_internal = "${aws_instance.dev-api-gateway-internal.private_ip}"
  }
}

resource "null_resource" "dev-hosts" {
  triggers {
    template_rendered = "${data.template_file.dev_hosts.rendered}"
  }
  provisioner "local-exec" {
    command = "echo '${data.template_file.dev_hosts.rendered}' > dev_hosts"
  }
}

Затем создайте шаблон в файле, указанном ранее.

Содержимое примера dev_hosts.cfg

[public]
${api_public}


[private]
${api_internal}
person DrM    schedule 03.08.2017
comment
Обратите внимание: если вы используете Terraform в Windows (не повезло вам!) через что-то вроде Git Bash, вам также необходимо добавить interpreter = ["sh", "-c"], иначе он будет использовать cmd и не сможет сохранить файл. - person Andrei Sinitson; 09.11.2018
comment
Еще подсказка: если у вас есть список (например, если вы используете count), а не один IP, то вам нужно интерполировать его до простого значения — строки в секции vars. Вот как это сделать: servers = "${join("\n", module.servers.server_ip)}" - person Andrei Sinitson; 09.11.2018
comment
В дополнение к этому, если у вас есть список и вы хотите сложное выражение, вы можете сделать что-то вроде этого: "${join("\n", [for instance in aws_instance.web-servers : join("", [instance.tags.Name, " ansible_host=", instance.public_ip])] )}" - person Datum Geek; 30.08.2019

Наш подход немного отличается. Мы определяем модуль Terraform (terraform-null-ansible), который вызывает ansible каждый раз, когда мы хотим запустить плейбук на хосте, используя динамическую инвентаризацию.

https://github.com/cloudposse/terraform-null-ansible

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

Использование довольно простое:

module "web_provisioner" {
   source    = "git::https://github.com/cloudposse/terraform-null-ansible.git?ref=tags/0.3.8"

   arguments = ["--user=ubuntu"]
   envs      = ["host=${aws_instance.web.public_ip}"]
   playbook  = "../ansible/playbooks/test.yml"
   dry_run   = false
}

Дополнительные документы находятся на GitHub README.md

person Erik Osterman    schedule 06.09.2017

Начиная с Terraform 0.12+ есть функция templatefile, которая пригодится, особенно если вам нужно заполнить группы хостов:

# generate inventory file for Ansible
resource "local_file" "hosts_cfg" {
  content = templatefile("${path.module}/templates/hosts.tpl",
    {
      kafka_processors = aws_instance.kafka_processor.*.public_ip
      test_clients = aws_instance.test_client.*.public_ip
    }
  )
  filename = "../ansible/inventory/hosts.cfg"
}

Где файл шаблона hosts.tpl (будущий инвентарь Ansible) может выглядеть так:

[kafka_broker_hosts]
%{ for ip in kafka_processors ~}
${ip}
%{ endfor ~}

[test_client_hosts]
%{ for ip in test_clients ~}
${ip}
%{ endfor ~}

Конечный результат:

[kafka_broker_hosts]
18.224.140.239
18.224.140.234

[test_client_hosts]
3.21.134.83
person Andy Malakov    schedule 14.05.2020

Для нескольких серверов:

Файл inventory.tf

data  "template_file" "k8s" {
    template = "${file("./templates/k8s.tpl")}"
    vars {
        k8s_master_name = "${join("\n", azurerm_virtual_machine.k8s-master.*.name)}"
    }
}

resource "local_file" "k8s_file" {
  content  = "${data.template_file.k8s.rendered}"
  filename = "./inventory/k8s-host"
}

Файл k8s.tpl

[kube-master]
${k8s_master_name}

конечный результат

[kube-master]
k8s-master-01
k8s-master-02
k8s-master-03
person Roy Zeng    schedule 26.03.2019

Мой подход: от шаблона к файлу инвентаря, используйте template_file для рендеринга контента и используйте local_file для вывода файла.

файл шаблона:

## file inventory.tpl

[frontend]
${bastion_pub_ip}

[all:vars]
ansible_ssh_private_key_file = ${key_path}
ansible_ssh_user = ubuntu

визуализировать и выводить:

## file inventory.tf

data "template_file" "inventory" {
    template = "${file("./test/inventory.tpl")}"

    vars {
       bastion_pub_ip = "${element(azurerm_public_ip.bastion.*.ip_address, count.index)}"
       key_path = "~/.ssh/id_rsa"
    }
}

resource "local_file" "save_inventory" {
  content  = "${data.template_file.inventory.rendered}"
  filename = "./myhost"
}

Это работает для одного сервера, если у вас есть список, я не нахожу подходящего способа сделать это.

person Roy Zeng    schedule 13.03.2019

Это сработало для меня:

data "template_file" "ansible_inventory" {
  template = "${file("${path.module}/hosts.tmpl")}"

  vars = {
    public_ips = "${join("\n", aws_instance.public_instance.*.public_ip)}"
  }
}

resource "local_file" "hosts" {
  filename = "${path.module}/hosts"

  content = data.template_file.ansible_inventory.rendered
}
person Marcelo Mourão    schedule 15.04.2020

Это сработало для меня на aws ec2:

main.tf:

resource "aws_instance" "instance" {
  for_each      = toset(["ingress-01", "node-01", "node-02", "master-01" ])
  ami           = "ami-0c239ecd40dcc174c"
  instance_type = "t2.micro"

  tags = {
    Name = "${each.key}"
  }
}

resource "local_file" "inventory" {
  content = templatefile("inventory.tmpl", { content = tomap({
    for instance in aws_instance.instance:
      instance.tags.Name => instance.public_dns
    })
  })
  filename = format("%s/%s", abspath(path.root), "inventory.yaml")
}

шаблон (inventory.tmpl):

all:
  children:
    ingress:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("ingress", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    master:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("master", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}
    nodes:
      hosts:
%{ for content_key, content_value in content }
%{~ if length(regexall("node", content_key)) > 0 ~}
        ${content_key}:
          ansible_host: ${content_value}
%{ endif ~}
%{~ endfor ~}

кошачий инвентарь.yaml

all:
  children:
    ingress:
      hosts:
        ingress-01:
          ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
    master:
      hosts:
        master-01:
          ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
    nodes:
      hosts:
        node-01:
           ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
        node-02:
           ansible_host: ec2-xx-xx-xx-xx.eu-central-1.compute.amazonaws.com
person thdonatello    schedule 07.05.2021