14

我们有 cronjob 和 shell 脚本,我们希望在使用 terraform 创建实例时将其复制或上传到 aws ec2 实例。

我们尝试了

  1. 文件配置器:但它不是 wokring ,并且阅读此选项不适用于所有 terraform 版本
      provisioner "file" {
        source      = "abc.sh"
        destination = "/home/ec2-user/basic2.sh"
      }
  1. 尝试过的数据模板文件选项
    data "template_file" "userdata_line" {
      template = <<EOF
    #!/bin/bash
    mkdir /home/ec2-user/files2
    cd /home/ec2-user/files2
    sudo touch basic2.sh
    sudo chmod 777 basic2.sh
    base64 basic.sh |base64 -d >basic2.sh
    EOF
    }

尝试了所有选项,但都没有工作。
你能帮忙或建议吗?
我是 terraform 的新手,所以长期以来一直在努力解决这个问题。

4

5 回答 5

29

从安装了cloud-init的 AMI 开始时(这在许多官方 Linux 发行版中很常见),我们可以使用 cloud-init 的write_files模块将任意文件放入文件系统,只要它们足够小以适应论点以及user_data所有其他cloud-init数据。

与所有 cloud-init 模块一样,我们write_files使用cloud-init 的基于 YAML 的配置格式进行配置,该格式以单独一行的特殊标记字符串开头#cloud-config,然后是 YAML 数据结构。因为 JSON 是 YAML 的子集,所以我们可以使用 Terraformjsonencode来生成有效值[1]

locals {
  cloud_config_config = <<-END
    #cloud-config
    ${jsonencode({
      write_files = [
        {
          path        = "/etc/example.txt"
          permissions = "0644"
          owner       = "root:root"
          encoding    = "b64"
          content     = filebase64("${path.module}/example.txt")
        },
      ]
    })}
  END
}

write_files我们设置 时,模块可以接受 base64 格式的数据encoding = "b64",因此我们将它与 Terraform 的filebase64函数结合使用以包含外部文件的内容。其他方法在这里也是可能的,例如使用 Terraform 模板动态生成字符串并将base64encode其编码为文件内容。

如果您可以像上面一样在单个配置文件中表达您希望 cloud-init 执行的所有操作,那么您可以local.cloud_config_config直接分配为您的 instance user_data,并且 cloud-config 应该在系统启动时识别并处理它:

  user_data = local.cloud_config_config

如果您需要将创建文件与其他一些操作(例如运行 shell 脚本)结合起来,您可以使用 cloud-init 的多部分存档格式来编码多个“文件”以供 cloud-init 处理。Terraform 有一个cloudinit提供程序,其中包含一个数据源,用于轻松为 cloud-init 构建多部分存档:

data "cloudinit_config" "example" {
  gzip          = false
  base64_encode = false

  part {
    content_type = "text/cloud-config"
    filename     = "cloud-config.yaml"
    content      = local.cloud_config_config
  }

  part {
    content_type = "text/x-shellscript"
    filename     = "example.sh"
    content  = <<-EOF
      #!/bin/bash
      echo "Hello World"
    EOT
  }
}

此数据源将生成一个字符串,cloudinit_config.example.rendered该字符串是适用user_data于 cloud-init 的多部分存档:

  user_data = cloudinit_config.example.rendered

EC2 规定最大用户数据大小为 64 KB,因此所有编码数据一起必须符合该限制。如果您需要放置接近或超过该限制的大文件,最好使用中间其他系统来传输该文件,例如让 Terraform 将文件写入 Amazon S3 存储桶并将软件放入您的实例使用实例配置文件凭据检索该数据。不过,对于用于系统配置的小数据文件来说,这应该不是必需的。

需要注意的是,从 Terraform 和 EC2 的角度来看,内容user_data只是一个任意字符串。处理字符串的任何问题都必须在目标操作系统本身内进行调试,方法是读取 cloud-init 日志以查看它如何解释配置以及尝试采取这些操作时发生了什么。


[1]:我们也可能使用yamlencode,但在我写这篇文章的时候,这个函数有一个警告,它的确切格式可能会在未来的 Terraform 版本中发生变化,这是不可取的,user_data因为它会导致实例被替换。如果您将来阅读本文并且该警告不再出现在yamldecode文档中,请考虑yamlencode改用。

于 2020-05-30T16:29:04.020 回答
14

provisioner "file"只是为此使用,没有问题......
但你必须提供一个连接:

resource "aws_instance" "foo" {
...
  provisioner "file" {
    source      = "~/foobar"
    destination = "~/foobar"

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = "${file("~/Downloads/AWS_keys/test.pem")}"
      host        = "${self.public_dns}"
    }
  }
...
}

以下是一些代码示例:
https ://github.com/heldersepu/hs-scripts/blob/master/TerraForm/ec2_ubuntu.tf#L21

于 2020-05-30T11:40:33.120 回答
5

不知何故,在公司领域,没有一个选项起作用。但最后我们能够使用 s3 存储桶复制/下载文件。

创建 s3.tf 以上传此文件 basic2.sh

resource "aws_s3_bucket" "demo-s3" {

  bucket = "acom-demo-s3i-<bucketID>-us-east-1"
  acl    = "private"


  tags {
    Name = "acom-demo-s3i-<bucketID>-us-east-1"
    StackId = "demo-s3"
  }
}

resource "aws_s3_bucket_policy" "s3_policy" {

  bucket = "${aws_s3_bucket.demo-s3.id}"

  policy = <<EOF
{
    "Version": "2009-10-17",
    "Statement": [
            {
            "Sid": "Only allow specific role",
            "Effect": "allow",
            "Principal":{ "AWS": ["arn:aws:iam::<bucketID>:role/demo-s3i"]},
            "Action":  "s3:*",
            "Resource": [
          "arn:aws:s3:::acom-demo-s3i-<bucketID>-us-east-1",
          "arn:aws:s3:::acom-demo-s3i-<bucketID>-us-east-1/*"
            ]

        }
    ]
}
EOF
}


resource "aws_s3_bucket_object" "object" {
  bucket = "acom-demo-s3i-<bucketID>-us-east-1"
  key    = "scripts/basic2.sh"
  source = "scripts/basic2.sh"
  etag = "${filemd5("scripts/basic2.sh")}"
}

然后在其他 tpl 文件中声明文件下载部分。

 aws s3 cp s3://acom-demo-s3i-<bucketID>-us-east-1/scripts/basic2.sh /home/ec2-user/basic2.sh
于 2020-06-26T07:56:12.463 回答
2

这是一个更简单的示例,如@martin-atkins 所述,如何使用write_fileswithcloud-init

的内容templates/cloud-init.yml.tpl

#cloud-config
package_update: true
package_upgrade: true

packages:
  - ansible

write_files:
  - content: |
      ${base64encode("${ansible_playbook}")}
    encoding: b64
    owner: root:root
    path: /opt/ansible-playbook.yml
    permissions: '0750'

runcmd:
 - ansible-playbook /opt/ansible-playbook.yml

文件内容main.tf

data "template_file" "instance_startup_script" {
  template = file(format("%s/templates/templates/cloud-init.yml.tpl", path.module))

  vars = {
    ansible_playbook = templatefile("${path.module}/templates/ansible-playbook.yml.tpl", {
      playbookvar = var.play_book_var
    })
    
    cloudinitvar = var.cloud_init_var
  }
}

可以对 cloud-init 和 ansible-playbook 模板使用变量插值

于 2021-04-27T23:10:48.593 回答
0

您必须使用文件配置器与 ec2 实例的连接详细信息。示例配置如下所示:

provisioner "file" {
  source      = "${path.module}/files/script.sh"
  destination = "/tmp/script.sh"

  connection {
    type     = "ssh"
    user     = "root"
    password = "${var.root_password}"
    host     = "${var.host}"
  }
}

您可以使用用户名/密码、私钥甚至堡垒主机进行连接。更多详情https://www.terraform.io/docs/provisioners/connection.html

于 2020-05-30T14:44:39.347 回答