aws_instance 中的 Terraform 可扩展 user_data
Terraform extensible user_data in aws_instance
美好的一天,
我们的团队使用一个模块创建 Linux 个具有 user_data 中标准配置的实例,定义如下。
resource "aws_instance" "this" {
...
user_data = templatefile("${path.module}/user_data.tp", { hostname = upper("${local.prefix}${count.index + 1}"), domain = local.domain })
...
}
user_data.tp 的内容:
#cloud-config
repo_update: true
repo_upgrade: all
preserve_hostname: false
hostname: ${hostname}
fqdn: ${hostname}.${domain}
manage_etc_hosts: false
runcmd:
- 'echo "preserve_hostname: true" >> /etc/cloud/cloud.cfg.d/99_hostname.cfg'
修改此模块的最佳方法是什么,以便始终执行 user_data.tp 的内容并且 可选地 另一个可以传递块来安装某些软件包或执行某些 shell 脚本?
我假设它涉及使用 cloudinit_config 和多部分 MIME 配置,但不胜感激任何建议。
谢谢。
由于您展示了一个 cloud-config
模板,我在这里假设您正在为在启动时运行 cloud-init 的 AMI 准备一个 user_data
。这意味着这可能更像是一个 cloud-init 问题而不是 Terraform 问题,但我知道您还想知道如何将 cloud-init 特定的答案转换为可行的 Terraform 配置。
User-data Formats documentation describes various possible ways to format user_data
for cloud-init to consume. You mentioned multipart MIME in your question and indeed that could be a viable answer here if you want cloud-init to interpret the two payloads separately, rather than as a single artifact. The cloud-init docs talk about the tool make-mime
, but the Terraform equivalent of that is the cloudinit_config
data source belonging to the hashicorp/cloudinit
provider:
variable "extra_cloudinit" {
type = object({
content_type = string
content = string
})
# This makes the variable optional to set,
# and var.extra_cloudinit will be null if not set.
default = null
}
data "cloudinit_config" "user_data" {
# set "count" to be whatever your aws_instance count is set to
count = ...
part {
content_type = "text/cloud-config"
content = templatefile(
"${path.module}/user_data.tp",
{
hostname = upper("${local.prefix}${count.index + 1}")
domain = local.domain
}
)
}
dynamic "part" {
# If var.extra_cloud_init is null then this
# will produce a zero-element list, or otherwise
# it'll produce a one-element list.
for_each = var.extra_cloudinit[*]
content {
content_type = part.value.content_type
content = part.value.content
# NOTE: should probably also set merge_type
# here to tell cloud-init how to merge these
# two:
# https://cloudinit.readthedocs.io/en/latest/topics/merging.html
}
}
}
resource "aws_instance" "example" {
count = length(data.cloudinit_config.user_data)
# ...
user_data = data.cloudinit_config.user_data[count.index].rendered
}
如果您希望额外的 cloud-init 配置总是以额外的 cloud-config YAML 值的形式出现,那么另一种方法是在 Terraform 中将两个数据结构合并在一起,然后 yamlencode
合并结果:
variable "extra_cloudinit" {
type = any
# This makes the variable optional to set,
# and var.extra_cloudinit will be null if not set.
default = {}
validation {
condition = can(merge(var.extra_cloudinit, {}))
error_message = "Must be an object to merge with the built-in cloud-init settings."
}
}
locals {
cloudinit_config = merge(
var.extra_cloudinit,
{
repo_update = true
repo_upgrade = "all"
# etc, etc
},
)
}
resource "aws_instance" "example" {
count = length(data.cloudinit_config.user_data)
# ...
user_data = <<EOT
#!cloud-config
${yamlencode(local.cloudinit_config)}
EOT
}
这种方法的一个缺点是 Terraform 的 merge
功能始终只是浅层合并,而 cloud-init 本身具有 various other merging options。但是,一个优点是生成的单个 YAML 文档通常比多部分 MIME 有效负载更简单,因此可能更容易检查 terraform plan
输出中的正确性。
美好的一天,
我们的团队使用一个模块创建 Linux 个具有 user_data 中标准配置的实例,定义如下。
resource "aws_instance" "this" {
...
user_data = templatefile("${path.module}/user_data.tp", { hostname = upper("${local.prefix}${count.index + 1}"), domain = local.domain })
...
}
user_data.tp 的内容:
#cloud-config
repo_update: true
repo_upgrade: all
preserve_hostname: false
hostname: ${hostname}
fqdn: ${hostname}.${domain}
manage_etc_hosts: false
runcmd:
- 'echo "preserve_hostname: true" >> /etc/cloud/cloud.cfg.d/99_hostname.cfg'
修改此模块的最佳方法是什么,以便始终执行 user_data.tp 的内容并且 可选地 另一个可以传递块来安装某些软件包或执行某些 shell 脚本?
我假设它涉及使用 cloudinit_config 和多部分 MIME 配置,但不胜感激任何建议。
谢谢。
由于您展示了一个 cloud-config
模板,我在这里假设您正在为在启动时运行 cloud-init 的 AMI 准备一个 user_data
。这意味着这可能更像是一个 cloud-init 问题而不是 Terraform 问题,但我知道您还想知道如何将 cloud-init 特定的答案转换为可行的 Terraform 配置。
User-data Formats documentation describes various possible ways to format user_data
for cloud-init to consume. You mentioned multipart MIME in your question and indeed that could be a viable answer here if you want cloud-init to interpret the two payloads separately, rather than as a single artifact. The cloud-init docs talk about the tool make-mime
, but the Terraform equivalent of that is the cloudinit_config
data source belonging to the hashicorp/cloudinit
provider:
variable "extra_cloudinit" {
type = object({
content_type = string
content = string
})
# This makes the variable optional to set,
# and var.extra_cloudinit will be null if not set.
default = null
}
data "cloudinit_config" "user_data" {
# set "count" to be whatever your aws_instance count is set to
count = ...
part {
content_type = "text/cloud-config"
content = templatefile(
"${path.module}/user_data.tp",
{
hostname = upper("${local.prefix}${count.index + 1}")
domain = local.domain
}
)
}
dynamic "part" {
# If var.extra_cloud_init is null then this
# will produce a zero-element list, or otherwise
# it'll produce a one-element list.
for_each = var.extra_cloudinit[*]
content {
content_type = part.value.content_type
content = part.value.content
# NOTE: should probably also set merge_type
# here to tell cloud-init how to merge these
# two:
# https://cloudinit.readthedocs.io/en/latest/topics/merging.html
}
}
}
resource "aws_instance" "example" {
count = length(data.cloudinit_config.user_data)
# ...
user_data = data.cloudinit_config.user_data[count.index].rendered
}
如果您希望额外的 cloud-init 配置总是以额外的 cloud-config YAML 值的形式出现,那么另一种方法是在 Terraform 中将两个数据结构合并在一起,然后 yamlencode
合并结果:
variable "extra_cloudinit" {
type = any
# This makes the variable optional to set,
# and var.extra_cloudinit will be null if not set.
default = {}
validation {
condition = can(merge(var.extra_cloudinit, {}))
error_message = "Must be an object to merge with the built-in cloud-init settings."
}
}
locals {
cloudinit_config = merge(
var.extra_cloudinit,
{
repo_update = true
repo_upgrade = "all"
# etc, etc
},
)
}
resource "aws_instance" "example" {
count = length(data.cloudinit_config.user_data)
# ...
user_data = <<EOT
#!cloud-config
${yamlencode(local.cloudinit_config)}
EOT
}
这种方法的一个缺点是 Terraform 的 merge
功能始终只是浅层合并,而 cloud-init 本身具有 various other merging options。但是,一个优点是生成的单个 YAML 文档通常比多部分 MIME 有效负载更简单,因此可能更容易检查 terraform plan
输出中的正确性。