Terraform教程04 - provisioning


一旦使用Terraform创建了云资源,比如云服务器之后,下一步就是在云服务器上安装相应的软件了。这时候有不同的方法:

  • 创建自定义的AMI,这样一旦使用Terraform通过该AMI创建云服务器,相应的软件就会自动安装好了。但其缺点就是需要自己维护AMI镜像,升级,安全补丁等。推荐工具:Packer
  • 另外一种方式则是使用标准的AMI,在云服务器启动之后,再使用类似Puppet, Chef, Ansible等软件来安装后续软件

Terraform目前:

  • 支持Chef
  • 可以通过remote-exec来运行puppet agent
  • 可以运行terraform,输出IP地址,然后在这些主机上运行ansible-playbook。整个过程可以在工作流脚本中被自动化。

一定要注意Terraform和Ansible/Puppet/Chef等软件的分工。Terraform更注重于基础架构的创建:VPC, Subnet, VM等。一旦完成了这些基础架构的创建,至于在一个VM内如何安装和配置软件,就应该交给Ansible/Puppet等软件来完成

因此,尽管Terraform能够做一些provisioning的工作,但官方并不推荐(Provisioners are a Last Resort):

  • 推荐使用user_data,而不是provisioner
  • 使用provisioner会破坏其等幂的设计理念(idempotency)
  • Terraform无从得知将被执行的脚本将会做什么
  • 如果必须要执行相应脚本,可以将其作为CI/CD的一部分

通过user_data来实现初始软件的安装

在完成一个VM的创建之后,可能需要安装一些最基本的软件,比如Docker,这时的一个选择就是使用user_data:

terraform
resource "aws_instance" "web-server" {
  ami = "xxxxxxxxxxxxxxx"
  instance_type = "t2.micro"

  subnet_id = aws_subnet.lcoding-subnet-1.id
  vpc_security_group_ids = [aws_security_group.lcoding-security-group.id]
  availability_zone = var.available_zone
  associate_public_ip_address = true

  key_name = "server-key-pair"

  user_data = <<-EOF
                #!/bin/bash
                sudo yum update -y && sudo yum install -y docker
                sudo systemctl start docker

                sudo usermod -aG docker ec2-user
                docker run -p 8080:80 nginx
              EOF  
}

上面的脚本就会在创建EC2之后安装Docker并创建及运行Nginx容器。但要注意:截至到user_data之前,Terraform的工作已经完成,在user_data中的工作是由provider来负责的,其运行结果对于Terraform也是不可见的

通过文件方式provisioning

terraform
resource "aws_instance" "example" {
  ami = "xxxxxxxx"
  instance_type = "t2.micro"

  provisioner "file" {
    source = "app.conf"
    destination = "/etc/myapp.conf"
  }
}

在整个provision的过程中,可能使用SSH (Linux),或WinRM (Windows)。

使用用户名/密码连接connection

terraform
resource "aws_instance" "example" {
  ami = "xxxxxxxx"
  instance_type = "t2.micro"

  provisioner "file" {
    source = "script.sh"
    destination = "/opt/script.sh"
    connection {
      user = "${var.username}"
      password = "${var.password}"
    }
  }
}

使用SSH Key连接

terraform
resource "aws_key_pair" "lcoding-key"{
  key_name = 'lkey'
  public_key = 'ssh-rsa your-public-key'
}

resource "aws_instance" "example" {
  ami = "xxxxxxxx"
  instance_type = "t2.micro"
  key_name = "${aws_key_pair.lkey.key_name}"

  provisioner "file" {
    source = "script.sh"
    destination = "/opt/script.sh"
    connection {
      user = "${var.username}"
      private_key = "${file(${var.path_to_private_key})}"
    }
  }
}

运行一个脚本

terraform
resource "aws_instance" "example" {
  ami = "xxxxxxxx"
  instance_type = "t2.micro"

  provisioner "file" {
    source = "script.sh"
    destination = "/opt/script.sh"
    // ...
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /opt/script.sh",
      "/opt/script.sh arguments"
    ]
  }
}

remote-exec provisioner

remote-exec provisioner可以让我们在一个资源创建后在该资源上运行一个脚本。这种方式和前面提到的user_data的最大区别在于:

  • user_data: 把数据传给AWS,由AWS来负责运行
  • remote-exec: 由Terraform负责通过SSH连接到服务器并运行命令
terraform
resource "aws_instance" "web-server" {
  # ...

  connection {
    type = "ssh"
    host = self.public_ip
    user = "ec2-user"
    private_key = file(var.private_key_location)
  }

  provisioner "remote-exec" {
    inline = [
      "sudo yum update -y && sudo yum install -y docker",
      "sudo systemctl start docker",
      "sudo usermod -aG docker ec2-user",
      "docker run -p 8080:80 nginx"
    ]
  }
}

也可以不使用inline,而使用script,但前提是entry-script.sh必须已经在服务器上

terraform
provisioner "file" {
  source = "entry-script.sh"
  destination = "/home/ec2-user/entry-script.sh"
}

provisioner "remote-exec" {
  script = file("entry-script.sh")
}

local-exec provisioner

当一个资源被创建后,在本地(非云端资源)调用一个可以执行程序/脚本。


文章作者: 逻思
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 逻思 !