一旦使用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
当一个资源被创建后,在本地(非云端资源)调用一个可以执行程序/脚本。