【问题标题】:Create multiple new instances with Terraform使用 Terraform 创建多个新实例
【发布时间】:2021-03-16 09:21:24
【问题描述】:

我编写了一个 Terraform 脚本,它应该从一个模板创建多个服务器实例。我现在已经创建了两个不同的变量文件。

但是当我运行脚本时,会使用第一个变量文件创建一个新实例,但第二个变量文件总是会覆盖/更改第一个实例。我不知道为什么 Terraform 引用了以前新创建的实例。我怎样才能防止这种情况发生?

server-1.tfvars:

vsphere_user                     = "administrator@vsphere.local"
vsphere_password                 = "#Password"
vsphere_server                   = "vsphere.server"
vsphere_datacenter               = "Datacenter"
vsphere_datastore                = "Storage_1"
vsphere_compute_cluster          = "Cluster"
vsphere_network                  = "Network_1"
vsphere_virtual_machine_template = "Template_Microsoft_Windows_Server_2019_x64_english"
system_name                      = "server-1"
system_cores                     = 2
system_cores_per_socket          = 2
system_memory                    = 2048
system_local_admin_password      = "#Password"
system_ipv4_address              = "172.22.15.11"
system_ipv4_netmask              = 24
system_dns_server_list           = ["172.22.15.101"]
system_ipv4_gateway              = "172.22.15.1"
system_disk1_size                = 75
system_domain_admin_user         = "Administrator"
system_domain_admin_password     = "#Password"

server-2.tfvars:

vsphere_user                     = "administrator@vsphere.local"
vsphere_password                 = "#Password"
vsphere_server                   = "vsphere.server"
vsphere_datacenter               = "Datacenter"
vsphere_datastore                = "Storage_1"
vsphere_compute_cluster          = "Cluster"
vsphere_network                  = "Network_1"
vsphere_virtual_machine_template = "Template_Microsoft_Windows_Server_2019_x64_english"
system_name                      = "server-2"
system_cores                     = 2
system_cores_per_socket          = 2
system_memory                    = 2048
system_local_admin_password      = "#Password"
system_ipv4_address              = "172.22.15.12"
system_ipv4_netmask              = 24
system_dns_server_list           = ["172.22.15.101"]
system_ipv4_gateway              = "172.22.15.1"
system_disk1_size                = 75
system_domain_admin_user         = "Administrator"
system_domain_admin_password     = "#Password"

provider.tf:

provider "vsphere" {
  user                 = var.vsphere_user
  password             = var.vsphere_password
  vsphere_server       = var.vsphere_server
  allow_unverified_ssl = true
}

data.tf:

# Data Sources
# Datacenter
data "vsphere_datacenter" "dc" {
  name = var.vsphere_datacenter
}

# Datastore
data "vsphere_datastore" "datastore" {
  name          = var.vsphere_datastore
  datacenter_id = data.vsphere_datacenter.dc.id
}

# Cluster
data "vsphere_compute_cluster" "cluster" {
  name          = var.vsphere_compute_cluster
  datacenter_id = data.vsphere_datacenter.dc.id
}

# Network
data "vsphere_network" "network" {
  name          = var.vsphere_network
  datacenter_id = data.vsphere_datacenter.dc.id
}

# Template
data "vsphere_virtual_machine" "template" {
  name          = var.vsphere_virtual_machine_template
  datacenter_id = data.vsphere_datacenter.dc.id
}

resource.tf:

# Virtual Machine Resource
resource "vsphere_virtual_machine" "server-instance" {
  # System
  firmware  = "efi"
  guest_id  = data.vsphere_virtual_machine.template.guest_id
  scsi_type = data.vsphere_virtual_machine.template.scsi_type

  # VM-Name
  name             = var.system_name
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  # CPU
  num_cpus               = var.system_cores
  num_cores_per_socket   = var.system_cores_per_socket
  cpu_hot_add_enabled    = true
  cpu_hot_remove_enabled = true

  # Memory
  memory                 = var.system_memory
  memory_hot_add_enabled = true

  # Network
  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = "e1000e"
  }

  # Storage
  # Drive 0 (C)
  disk {
    label            = "disk0"
    unit_number      = 0
    size             = data.vsphere_virtual_machine.template.disks.0.size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.0.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }

  # Drive 1 (D)
  disk {
    label            = "disk1"
    unit_number      = 1
    size             = var.system_disk1_size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.1.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.1.thin_provisioned
  }

  # Template clone and OS settings
  clone {
    template_uuid = data.vsphere_virtual_machine.template.id

    customize {
      windows_options {
        computer_name         = var.system_name
        admin_password        = random_password.password.result
        join_domain           = var.system_domain
        domain_admin_user     = var.system_domain_admin_user
        domain_admin_password = var.system_domain_admin_password
        auto_logon            = true
      }

      network_interface {
        ipv4_address    = var.system_ipv4_address
        ipv4_netmask    = var.system_ipv4_netmask
        dns_server_list = var.system_dns_server_list
      }

      ipv4_gateway = var.system_ipv4_gateway
    }
  }
}

密码.tf:

# Import the Random Password Provider
terraform {
  required_providers {
    random = {
      source = "hashicorp/random"
    }
  }
}

resource "random_password" "password" {
  length           = 25
  upper            = true
  lower            = true
  number           = true
  special          = true
  min_upper        = 2
  min_lower        = 2
  min_numeric      = 2
  min_special      = 1
  override_special = "!@#$%&*()-_=+[]{}<>:?"
}

【问题讨论】:

  • 您只有一个名为server-instance 的实例。因此,您第二次尝试部署实例只会覆盖现有的server-instance
  • 这是否意味着我必须为每个实例重写“资源”/蓝图?这不应该是变量的用途吗?
  • 有很多方法可以解决这个问题。您可以使用实例定义代码创建模块,使用 for_each 迭代带有实例变量的 map
  • 您能否提供一个示例,说明这在实践中应该是什么样子?我想保持这种方式,每个实例都有一个单独的变量文件。
  • 我会,但是我还没有完全理解你的设置。我猜你想在一个terraform apply 操作中同时创建两个实例?

标签: terraform vsphere terraform-provider-vsphere


【解决方案1】:

Terraform 的模型是,您的配置中的每个资源实例都绑定到零个或一个远程对象 - 如果您尚未创建远程对象,则为零,然后在第一次创建对象后绑定一个。

Terraform 模型的另一个重要特征是它是声明性的。您不应该将terraform apply 理解为“创建所有这些东西”,而应理解为“采取任何必要的措施使远程系统匹配此配置”。在第一次运行时,通常会导致许多创建操作,但在随后的运行中,您通常会对已经存在的对象进行更改,因为提供者旨在找到破坏性最小的方法更改远程系统以匹配更新的配置。

考虑到这一点,当您为 vsphere_virtual_machine.server-instance 的参数使用不同的值重新运行 Terraform 时,Terraform(和 vsphere 提供程序)理解您想要更改之前创建的现有对象,而不是创建一个新对象。

为了同时存在多个虚拟机,您必须拥有多个对应的资源实例,每个虚拟机都有一个。在 Terraform 的模型中,每个resource 块可以代表一个或多个资源实例;获取两个资源实例最直接的方法是编写两个resource 块,因此每个块将声明一个实例:

resource "vsphere_virtual_machine" "server_1" {
  # ...
}

resource "vsphere_virtual_machine" "server_2" {
  # ...
}

但是,如果您的多个实例是系统地以您可以使用 Terraform 语言中的表达式表达的方式创建的,那么您还有其他一些选择。

如果您认为所有虚拟机在某种意义上都是彼此的“副本”,在功能上都是等效的,那么您可能会选择使用the count meta-argument,这会导致resource 块拥有多个资源实例与之相关联的数字——count 表达式给出的数字——除了用特殊符号 count.index 表示的一些细微差别之外,它们都具有基本相同的配置,它给出了当前索引的索引:

resource "vsphere_virtual_machine" "server" {
  count = 2

  # VM-Name
  name             = "${var.system_name}-${count.index}"
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  # System
  firmware  = "efi"
  guest_id  = data.vsphere_virtual_machine.template.guest_id
  scsi_type = data.vsphere_virtual_machine.template.scsi_type

  # CPU
  num_cpus               = var.system_cores
  num_cores_per_socket   = var.system_cores_per_socket
  cpu_hot_add_enabled    = true
  cpu_hot_remove_enabled = true

  # Memory
  memory                 = var.system_memory
  memory_hot_add_enabled = true

  # Network
  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = "e1000e"
  }

  # Storage
  # Drive 0 (C)
  disk {
    label            = "disk0"
    unit_number      = 0
    size             = data.vsphere_virtual_machine.template.disks.0.size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.0.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }

  # Drive 1 (D)
  disk {
    label            = "disk1"
    unit_number      = 1
    size             = var.system_disk1_size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.1.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.1.thin_provisioned
  }

  # Template clone and OS settings
  clone {
    template_uuid = data.vsphere_virtual_machine.template.id

    customize {
      windows_options {
        computer_name         = var.system_name
        admin_password        = random_password.password.result
        join_domain           = var.system_domain
        domain_admin_user     = var.system_domain_admin_user
        domain_admin_password = var.system_domain_admin_password
        auto_logon            = true
      }

      network_interface {
        ipv4_address    = var.system_ipv4_address
        ipv4_netmask    = var.system_ipv4_netmask
        dns_server_list = var.system_dns_server_list
      }

      ipv4_gateway = var.system_ipv4_gateway
    }
  }
}

以上内容与您包含的resource "vsphere_virtual_machine" "server" 相同,只是我在开头添加了count = 2 并更改了name,以便它认为var.system_name 是一个名称​​前缀而不是一个完整的名称,添加当前索引以创建一个完整的唯一名称。我想您可能还需要对system_ipv4_address 遵循类似的策略,可能使用the cidrhost function 来系统地计算IP 地址,但为了简单起见,我将把它放在这里。

如果我们还更改变量文件,使 var.system_name 只是 "server" 而不是 "server-1",那么这将从单个资源块声明 两个 资源实例:

  • vsphere_virtual_machine.server[0]name = "server-0"
  • vsphere_virtual_machine.server[1]name = "server-1"

在您分享的示例中,count 似乎最适合您的情况,因为您的服务器虚拟机在其他方面都配置相同。但是,如果您需要将每个服务器视为完全独立配置,以便它们都可能具有不同的参数,那么您还有另一个选项,形式为 the for_each meta-argument。与count 一样,它从单个资源块声明多个资源实例,但它对映射中的每个元素都这样做,而不仅仅是将整数增加到特定限制。

这种方法确实需要对输入变量采用稍微不同的策略,因为我们需要输入是对象映射,其中映射的每个元素代表一个虚拟机:

variable "virtual_machines" {
  type = map(object({
    system_cores            = number
    system_cores_per_socket = number
    system_memory           = number
    system_ipv4_address     = string
    # (and so on, for all of the attributes that vary between
    # your virtual machines)
  }))
}

因为这是定义所有虚拟机的单个变量,您还需要更改您的 .tfvars 文件以以不同方式设置它:

vsphere_user                     = "administrator@vsphere.local"
vsphere_password                 = "#Password"
vsphere_server                   = "vsphere.server"
vsphere_datacenter               = "Datacenter"
vsphere_datastore                = "Storage_1"
vsphere_compute_cluster          = "Cluster"
vsphere_network                  = "Network_1"
vsphere_virtual_machine_template = "Template_Microsoft_Windows_Server_2019_x64_english"

virtual_machines = {
  server-1 = {
    system_cores            = 2
    system_cores_per_socket = 2
    system_memory           = 2048
    system_ipv4_address     = "172.22.15.11"
    # ...
  }
  server-2 = {
    system_cores            = 2
    system_cores_per_socket = 2
    system_memory           = 2048
    system_ipv4_address     = "172.22.15.12"
    # ...
  }
}

带有for_each 集的resource 块将如下所示:

resource "vsphere_virtual_machine" "server" {
  for_each = var.virtual_machines

  # VM-Name
  name             = each.key
  resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
  datastore_id     = data.vsphere_datastore.datastore.id

  # System
  firmware  = "efi"
  guest_id  = data.vsphere_virtual_machine.template.guest_id
  scsi_type = data.vsphere_virtual_machine.template.scsi_type

  # CPU
  num_cpus               = each.value.system_cores
  num_cores_per_socket   = each.value.system_cores_per_socket
  cpu_hot_add_enabled    = true
  cpu_hot_remove_enabled = true

  # Memory
  memory                 = each.value.system_memory
  memory_hot_add_enabled = true

  # Network
  network_interface {
    network_id   = data.vsphere_network.network.id
    adapter_type = "e1000e"
  }

  # Storage
  # Drive 0 (C)
  disk {
    label            = "disk0"
    unit_number      = 0
    size             = data.vsphere_virtual_machine.template.disks.0.size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.0.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.0.thin_provisioned
  }

  # Drive 1 (D)
  disk {
    label            = "disk1"
    unit_number      = 1
    size             = each.value.system_disk1_size
    eagerly_scrub    = data.vsphere_virtual_machine.template.disks.1.eagerly_scrub
    thin_provisioned = data.vsphere_virtual_machine.template.disks.1.thin_provisioned
  }

  # Template clone and OS settings
  clone {
    template_uuid = data.vsphere_virtual_machine.template.id

    customize {
      windows_options {
        computer_name         = each.value.system_name
        admin_password        = random_password.password.result
        join_domain           = each.value.system_domain
        domain_admin_user     = each.value.system_domain_admin_user
        domain_admin_password = each.value.system_domain_admin_password
        auto_logon            = true
      }

      network_interface {
        ipv4_address    = each.value.system_ipv4_address
        ipv4_netmask    = each.value.system_ipv4_netmask
        dns_server_list = each.value.system_dns_server_list
      }

      ipv4_gateway = each.value.system_ipv4_gateway
    }
  }
}

同样,这与您原来的resource 块基本相同,但我添加了for_each = var.virtual_machines,将name 设置为each.key 以使用映射键作为名称,并替换了所有其他引用到引用了each.value 属性的变量,它表示映射中当前元素的值。

在这种情况下,此资源块将声明以下资源实例:

  • vsphere_virtual_machine.server["server-0"]name = "server-0"
  • vsphere_virtual_machine.server["server-1"]name = "server-1"

请注意,Terraform 现在正在使用 map 键来识别每个实例,因此,如果您在 .tfvars 文件中编辑与现有键关联的值并运行 terraform plan,那么 Terraform 会在您打算更新时理解这一点具有该键的现有对象,但如果您向地图添加一个全新的键,则 Terraform 将理解这一点,因为您打算创建一个新的虚拟机。随着时间的推移,您可以通过更新、创建和删除var.virtual_machines 中的相应条目来更新、创建和删除虚拟机。

Terraform 希望每次运行它时都向它提供它负责管理的系统部分的完整状态的描述,因此没有一个使用模型可以让 Terraform 添加一个新的虚拟机,而无需为所有现有虚拟机提供未更改的配置。如果您省略现有的,Terraform 将理解您打算销毁它们。

【讨论】:

  • 非常感谢您的详细解释!这解决了我的问题。
猜你喜欢
  • 2020-06-28
  • 1970-01-01
  • 2020-11-30
  • 1970-01-01
  • 2020-12-16
  • 2021-09-20
  • 1970-01-01
  • 2021-05-04
  • 1970-01-01
相关资源
最近更新 更多