terraform 将文件复制/上传到 aws ec2 实例

Posted

技术标签:

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

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

我们尝试过

    文件配置器: 但它不工作,并且阅读此选项不适用于所有 terraform 版本
      provisioner "file" 
        source      = "abc.sh"
        destination = "/home/ec2-user/basic2.sh"
      
    试过数据模板文件选项
    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 的新手,所以长期以来一直在努力解决这个问题。

【问题讨论】:

【参考方案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 模板都使用变量插值

【讨论】:

以上是关于terraform 将文件复制/上传到 aws ec2 实例的主要内容,如果未能解决你的问题,请参考以下文章

js base64与canvas base64

如何将我的 .war 文件复制到 AWS 上的 EC2 实例?

如何使用aws java sdk将文件从S3存储桶从一个区域复制到另一个区域?

在弹性 beantalk 上构建战争文件时从 AWS S3 复制文件

如何将文件上传到 AWS 的云端

如何使用 AWS AppSync 将文件上传到 AWS S3