【问题标题】:terraform copy/upload files to aws ec2 instanceterraform 将文件复制/上传到 aws ec2 实例
【发布时间】:2020-09-17 21:52:34
【问题描述】:

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

我们尝试过

  1. 文件配置器: 但它不工作,并且阅读此选项不适用于所有 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 的新手,所以长期以来一直在努力解决这个问题。

【问题讨论】:

    标签: terraform terraform-provider-aws terraform-template-file


    【解决方案1】:

    我为此使用了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

    【讨论】:

    • 我在使用这种方法时遇到的问题是您不能在用户数据中使用该文件,因为这发生在启动后。
    • @openCivilisation 是的,你需要在操作系统中存在的任何东西都应该放入你的 AMI 中,查看 Packer
    【解决方案2】:

    您必须使用具有与 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

    【讨论】:

      【解决方案3】:

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

      与所有 cloud-init 模块一样,我们使用 cloud-init's YAML-based configuration format 配置 write_files,它以单独的一行特殊标记字符串 #cloud-config 开头,后跟 YAML 数据结构。因为 JSON 是 YAML 的子集,我们可以使用 Terraform 的 jsonencode 来生成有效值[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
      }
      

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

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

        user_data = local.cloud_config_config
      

      如果您需要将创建文件与其他一些操作(例如运行 shell 脚本)结合起来,您可以使用 cloud-init 的 multipart archive 格式对多个“文件”进行编码以供 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 处生成单个字符串,这是一个适合用作 cloud-init 的 user_data 的多部分存档:

        user_data = cloudinit_config.example.rendered
      

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

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


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

      【讨论】:

      • 是否可以使用 Elastic Beanstalk 实例来执行此操作?比如在那里创建一个.env 文件以避免4K环境限制
      • 恐怕我对 Elastic Beanstalk 不是很熟悉。它似乎与其他与 EC2 相关的部署选项完全不同,因此我建议在 Stack Exchange 上提出一个新问题,以便熟悉 Elastic Beanstalk 的人更容易看到它。
      • 嗨 Martin,我认为“user_data = cloudinit_config.example.rendered”应该是“user_data = data.cloudinit_config.example.rendered”。还是有我不知道的不同用法?也许与 terraform 版本有关?
      【解决方案4】:

      不知何故,在公司域中,没有一个选项起作用。 但最终我们能够使用 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
      

      【讨论】:

        【解决方案5】:

        这里有一个更简单的示例,如@martin-atkins 所述,如何将write_filescloud-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-05-15
          • 2015-11-19
          • 1970-01-01
          • 2023-03-22
          • 2016-02-12
          • 1970-01-01
          • 2020-12-13
          • 2016-08-25
          • 1970-01-01
          相关资源
          最近更新 更多