我想在CloudStack中尝试使用Terraform
初次见面,我是@atsaki。今天我要介绍如何使用Terraform在CloudStack上工作。
这篇文章是Apache CloudStack Advent Calendar 2014的12/21的参与作品。
昨天是@shida1234先生尝试安装CloudStack的初学者感想。
明天是@oraccha先生关于在AIST Super Green Cloud中运营CloudStack的故事。
2015年09月02日,我们已经更新并开始使用嵌入式的CloudStack提供者。
Terraform是什么?
Terraform是一个用于构建、变更和版本控制基础设施的工具。它主要针对云上的基础设施进行构建,并支持AWS、DigitalOcean、Google Cloud Platform等云服务提供商。
不仅限于IaaS专用工具,只要是可定义操作如添加、删除、更新的资源,都可以进行管理。实际上,我们还支持Heroku、Consul等IaaS以外的服务和工具。
这个项目是由HashiCorp开发的,HashiCorp是一个以Vagrant等工具而闻名的公司。
例子的运用
使用例を通じて、Terraformの概要について説明します。
环境
-
- Mac OSX 10.10.1
-
- Terraform 0.6.3
- IDCFクラウド
安装
Terraform可以通过下载并解压链接上的二进制文件来使用。
提供商的设置
我們需要設定使用Terraform通過CloudStack API所需的相關資訊。請設定CloudStack provider所需的CloudStack的端點、API金鑰和密鑰。
以下是使用IDCF云时的设置文件示例。
用以下的内容创建一个名为providers.tf的文件。API密钥和密钥还可以直接写在providers.tf中,就像终端一样,但在这里,我们将引用写在另一个名为terraform.tfvars的文件中的值。通过将敏感信息分隔到另一个文件中,我们可以防止意外地将认证信息提交到版本管理系统中。
Terraform 会使用 POST 方法来执行默认的虚拟机部署 API,但目前 IDCF 云不支持 POST 方法。在使用 IDCF 云时,需要指定 http_get_only = true,以便通过 GET 方法执行所有的 API。
variable "api_key" {}
variable "secret_key" {}
provider "cloudstack" {
api_url = "https://compute.jp-east.idcfcloud.com/client/api"
api_key = "${var.api_key}"
secret_key = "${var.secret_key}"
http_get_only = true
}
api_key = "YOUR_API_KEY"
secret_key = "YOUR_SECRET_KEY"
创建资源
请参考链接查看可以在CloudStack中使用的资源。
提供商:云层堆栈 – HashiCorp的Terraform
作为示例,我们将使用cloudstack_instance创建虚拟机。
请根据您的环境设置,使用以下内容创建terraform.tf文件。请根据您的环境设置正确填写区域、服务提供、和模板的信息。
resource "cloudstack_instance" "vm01" {
name = "vm01"
display_name = "vm01"
zone = "tesla"
service_offering = "light.S1"
network = "network1"
template = "Ubuntu Server 14.04 LTS 64-bit"
expunge = true
}
现在我们准备好了创建虚拟机的条件。
在这里,我们使用terraform plan命令来显示执行计划。
确认执行计划后再实际应用更改,这是Terraform的一个特点之一。
当执行terraform plan命令时,将显示对资源所做的哪些更改。(下面的示例中有一部分输出被省略以提高可读性)
第一行开头的+表示将创建新的资源。
表示在创建资源时将确定值。CloudStack虚拟机的IP将在创建虚拟机时分配,因此ipaddress显示为。
$ terraform plan
+ cloudstack_instance.vm01
display_name: "" => "vm01"
expunge: "" => "1"
ipaddress: "" => "<computed>"
name: "" => "vm01"
network: "" => "network1"
service_offering: "" => "light.S1"
template: "" => "Ubuntu Server 14.04 LTS 64-bit"
zone: "" => "tesla"
确认内容没有问题后,执行`terraform apply`命令将开始创建虚拟机。
完成后,您可以使用”terraform show”命令查看所创建虚拟机的信息。您可以确认已将中的ipaddress字段替换为具体值(例如10.3.0.39)。
$ terraform apply
$ terraform show
cloudstack_instance.vm01:
id = 9a0802ad-a509-4774-a0df-18917daa662b
display_name = vm01
expunge = true
ipaddress = 10.3.0.39
name = vm01
network = network1
project =
service_offering = light.S1
template = Ubuntu Server 14.04 LTS 64-bit
zone = tesla
terraform show的输出中包含了terraform plan中不包含的ID。Terraform会记录已创建资源的ID,并在下次执行时对该资源进行操作。
Terraform创建的资源信息存储在同一目录下名为terraform.tfstate的文件中。
$ cat terraform.tfstate
{
"version": 1,
"serial": 0,
"modules": [
{
"path": [
"root"
],
"outputs": {},
"resources": {
"cloudstack_instance.vm01": {
"type": "cloudstack_instance",
"primary": {
"id": "9a0802ad-a509-4774-a0df-18917daa662b",
"attributes": {
"display_name": "vm01",
"expunge": "true",
"id": "9a0802ad-a509-4774-a0df-18917daa662b",
"ipaddress": "10.3.0.39",
"name": "vm01",
"network": "network1",
"project": "",
"service_offering": "light.S1",
"template": "Ubuntu Server 14.04 LTS 64-bit",
"zone": "tesla"
}
}
}
}
}
]
}
资源设置更改
通过修改配置文件,您可以在 Terraform 中将变更应用到实际资源中。
在这里,我们将尝试更改所创建的虚拟机的显示名称。
将 terraform.tf 文件中的 display_name 从 vm01 修改为 VM01。
resource "cloudstack_instance" "vm01" {
name = "vm01"
display_name = "VM01"
zone = "tesla"
service_offering = "light.S1"
network = "network1"
template = "Ubuntu Server 14.04 LTS 64-bit"
}
执行 terraform plan 命令后,将会获得以下输出结果。在创建虚拟机之前的 terraform plan 中,第一行的标记从 + 变为了 ~。这个标记表示资源将被修改。
~ cloudstack_instance.vm01
display_name: "vm01" => "VM01"
使用terraform apply命令应用更改后,可以通过terraform show命令进行确认,确保display_name已经被修改。
$ terraform apply
$ terraform show
cloudstack_instance.vm01:
id = 9a0802ad-a509-4774-a0df-18917daa662b
display_name = VM01
expunge = true
ipaddress = 10.3.0.39
name = vm01
network = network1
project =
service_offering = light.S1
template = Ubuntu Server 14.04 LTS 64-bit
zone = tesla
接下来,我们尝试将“name”更改为“VM01”。
resource "cloudstack_instance" "vm01" {
name = "VM01"
display_name = "VM01"
zone = "tesla"
service_offering = "light.S1"
network = "network1"
template = "Ubuntu Server 14.04 LTS 64-bit"
}
当执行terraform plan来检查执行计划时,与更改display_name时的情况不同,第一行的标记变为-/+。该标记表示需要先删除现有资源,然后再创建新资源。在CloudStack中,无法更改虚拟机的名称,需要重新创建虚拟机,所以会出现这种情况。此外,在创建虚拟机时,IP地址会发生变化,因此ipaddress再次变为。
-/+ cloudstack_instance.vm01
display_name: "VM01" => "VM01"
expunge: "true" => "1"
ipaddress: "10.3.0.39" => "<computed>"
name: "vm01" => "VM01" (forces new resource)
network: "network1" => "network1"
service_offering: "light.S1" => "light.S1"
template: "Ubuntu Server 14.04 LTS 64-bit" => "Ubuntu Server 14.04 LTS 64-bit"
zone: "tesla" => "tesla"
执行terraform apply后,虚拟机会被先删除再重新创建。在重新创建完成之后,通过terraform show确认信息时可以看到id和ipaddress已经改变。
$ terraform apply
$ terraform show
cloudstack_instance.vm01:
id = 68dace58-46f5-4b50-a242-fa4858c15b1f
display_name = VM01
expunge = true
ipaddress = 10.3.0.165
name = VM01
network = network1
project =
service_offering = light.S1
template = Ubuntu Server 14.04 LTS 64-bit
zone = tesla
增加资源
然后,我们将在此虚拟机中添加一个磁盘。我们会将以下内容追加到terraform.tf文件中。
创建一个名为disk01的10G磁盘,并将其附加到先前创建的虚拟机上。
在Terraform中,您可以使用变量来引用已创建资源的信息。在这里,我们使用cloudstack_instance.vm01.id来引用要挂载磁盘的虚拟机的ID。
如果一个资源使用变量引用另一个资源的信息,那么该资源将依赖于被引用的资源。在这个例子中,磁盘(disk01)依赖于虚拟机(vm01)。在Terraform中,通过使用变量,可以轻松地表示资源之间的依赖关系。
resource "cloudstack_disk" "disk01" {
name = "disk01"
disk_offering = "Custom Disk"
size = 10
attach = true
virtual_machine = "${cloudstack_instance.vm01.id}"
zone = "${cloudstack_instance.vm01.zone}"
}
当运行terraform plan时,可以看到将创建新的磁盘。
+ cloudstack_disk.disk01
attach: "" => "1"
device: "" => "<computed>"
disk_offering: "" => "Custom Disk"
name: "" => "disk01"
shrink_ok: "" => "0"
size: "" => "10"
virtual_machine: "" => "68dace58-46f5-4b50-a242-fa4858c15b1f"
zone: "" => "tesla"
当执行 terraform apply 和 terraform show 命令时,您可以确认磁盘已实际创建并附加到虚拟机上。
$ terraform apply
$ terraform show
cloudstack_disk.disk01:
id = d2cb73d4-bafa-4d43-beb2-1a8dbc850522
attach = true
device = /dev/xvdb
disk_offering = Custom Disk
name = disk01
shrink_ok = false
size = 10
virtual_machine = 68dace58-46f5-4b50-a242-fa4858c15b1f
zone = tesla
cloudstack_instance.vm01:
id = 68dace58-46f5-4b50-a242-fa4858c15b1f
display_name = VM01
expunge = true
ipaddress = 10.3.0.165
name = VM01
network = network1
project =
service_offering = light.S1
template = Ubuntu Server 14.04 LTS 64-bit
zone = tesla
更改设置的关联资源
在这种情况下,我们尝试更改虚拟机的名称。当更改名称时,虚拟机将被重新创建,但与其相关联的磁盘会发生什么呢?
将虚拟机的名称从VM01更改回vm01。
resource "cloudstack_instance" "vm01" {
name = "vm01"
display_name = "VM01"
zone = "tesla"
service_offering = "light.S1"
network = "network1"
template = "Ubuntu Server 14.04 LTS 64-bit"
}
resource "cloudstack_disk" "disk01" {
name = "disk01"
disk_offering = "Custom Disk"
size = 10
attach = true
virtual_machine = "${cloudstack_instance.vm01.name}"
zone = "${cloudstack_instance.vm01.zone}"
}
当你运行 “terraform plan” 命令来确认执行计划时,可以确认虚拟机将被重新创建。磁盘似乎会连接到新建的虚拟机上。这可以说是符合预期的行为。
~ cloudstack_disk.disk01
virtual_machine: "68dace58-46f5-4b50-a242-fa4858c15b1f" => "${cloudstack_instance.vm01.id}"
-/+ cloudstack_instance.vm01
display_name: "VM01" => "VM01"
expunge: "true" => "1"
ipaddress: "10.3.0.165" => "<computed>"
name: "VM01" => "vm01" (forces new resource)
network: "network1" => "network1"
service_offering: "light.S1" => "light.S1"
template: "Ubuntu Server 14.04 LTS 64-bit" => "Ubuntu Server 14.04 LTS 64-bit"
zone: "tesla" => "tesla"
执行apply和show命令后,虚拟机被重新创建,id发生了变化,而磁盘的id保持不变,并重新挂载到新创建的虚拟机上,可以确认这一点。
$ terraform apply
$ terraform show
cloudstack_disk.disk01:
id = d2cb73d4-bafa-4d43-beb2-1a8dbc850522
attach = true
device = /dev/xvdb
disk_offering = Custom Disk
name = disk01
shrink_ok = false
size = 10
virtual_machine = bb7c017f-01c0-4703-9224-6566b92c1b58
zone = tesla
cloudstack_instance.vm01:
id = bb7c017f-01c0-4703-9224-6566b92c1b58
display_name = VM01
expunge = true
ipaddress = 10.3.0.228
name = vm01
network = network1
project =
service_offering = light.S1
template = Ubuntu Server 14.04 LTS 64-bit
zone = tesla
删除资源
在Terraform中,您可以通过从配置文件中删除资源并执行terraform apply来删除资源。
可以简单地从文件中删除,也可以将其注释掉。在Terraform的配置文件中,可以使用/* 〜 */来表示注释(也可以使用#、//进行单行注释)。
resource "cloudstack_instance" "vm01" {
name = "vm01"
display_name = "VM01"
zone = "tesla"
service_offering = "light.S1"
network = "network1"
template = "Ubuntu Server 14.04 LTS 64-bit"
}
/*
resource "cloudstack_disk" "disk01" {
name = "disk01"
disk_offering = "Custom Disk"
size = 10
attach = true
virtual_machine = "${cloudstack_instance.vm01.name}"
zone = "${cloudstack_instance.vm01.zone}"
}
*/
确认计划后的结果如下:在此状态下,会出现以下情况。” – ” 标记表示资源将被删除。通过执行 terraform apply,删除将被执行。
- cloudstack_disk.disk01
如果要一次性删除所有资源,可以使用terraform destroy命令执行删除。默认情况下,在删除之前会显示确认消息。
可以通过在terraform plan命令中添加-destroy选项来查看terraform destroy的执行计划。
$ terraform plan -destroy
(略)
- cloudstack_disk.disk01
- cloudstack_instance.vm01
$ terraform destroy
研究如何使用供应商资源。
在文档中有提到如何使用提供者和资源,但有时候查看源代码会更快。
在这里,我将介绍如何通过源代码来了解资源的使用方法。
Terraform的CloudStack提供商相关文件位于builtin/providers/cloudstack。
首先,我们检查provider.go文件。通过查看这个文件,我们可以了解到为了使用提供者,需要设置哪些值以及提供者提供的资源。
要使用提供商,您需要设置的值可以在providerConfigure函数中查看。您需要设置api_url、api_key和secret_key。
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := Config{
APIURL: d.Get("api_url").(string),
APIKey: d.Get("api_key").(string),
SecretKey: d.Get("secret_key").(string),
HTTPGETOnly: d.Get("http_get_only").(bool),
Timeout: int64(d.Get("timeout").(int)),
}
return config.NewClient()
}
接下来,我们将调查CloudStack提供的资源。provider.go文件中的以下部分将是可用的资源。我们也可以看出,目前还不能支持负载均衡器和安全组等功能。
ResourcesMap: map[string]*schema.Resource{
"cloudstack_disk": resourceCloudStackDisk(),
"cloudstack_egress_firewall": resourceCloudStackEgressFirewall(),
"cloudstack_firewall": resourceCloudStackFirewall(),
"cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(),
"cloudstack_loadbalancer_rule": resourceCloudStackLoadBalancerRule(),
"cloudstack_network": resourceCloudStackNetwork(),
"cloudstack_network_acl": resourceCloudStackNetworkACL(),
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
"cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(),
"cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(),
"cloudstack_template": resourceCloudStackTemplate(),
"cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(),
"cloudstack_vpn_customer_gateway": resourceCloudStackVPNCustomerGateway(),
"cloudstack_vpn_gateway": resourceCloudStackVPNGateway(),
},
我們最後將確認每個資源可以在各自的設定中設置的項目。在builtin/providers/cloudstack中,有一個名為provider.go的文件,其中包含我們查詢的資源和相對應的文件,我們將檢查其內容。
这次我要调查cloudstack_instance。与该资源相对应的文件是resource_cloudstack_instance.go。
若关注以下部分,您可以确认设置了诸如name或service_offering等字符串作为示例。尽管本例中未使用user_data等,但可以设置。
func resourceCloudStackInstance() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackInstanceCreate,
Read: resourceCloudStackInstanceRead,
Update: resourceCloudStackInstanceUpdate,
Delete: resourceCloudStackInstanceDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"display_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"service_offering": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"network": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ipaddress": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"template": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"zone": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"keypair": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"user_data": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
StateFunc: func(v interface{}) string {
switch v.(type) {
case string:
hash := sha1.Sum([]byte(v.(string)))
return hex.EncodeToString(hash[:])
default:
return ""
}
},
},
"expunge": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
每个设定项都有一个名为Type的关键字来指定数据的类型。name的Type被设定为TypeString,表示它是一个字符串。expunge的Type被设定为TypeBool,表示它是一个布尔值。
必須(Required)和可选(Optional)表示该项目是必须的还是可选的。如果未设置必须的项目,则无法执行应用程序(apply)命令等操作。
Computed表明了Terraform获取值的方式。它可以用于在资源创建时获取分配的值,比如ipaddress。
ForceNew表示如果更改了该项,则要删除资源并重新创建。例如,display_name没有ForceNew,因此可以更改而无需重新创建,但是name有ForceNew,所以如果更改了name,将会重新创建。
有一些更复杂的数据类型,但基本上只要记住这些,使用时应该不会受太多限制。如果使用了不同的资源,通过查找源代码中的API命令名称,也可以大致知道它们在背后执行了什么操作。
与其他工具进行比较
我已经看了Terraform的使用方法,但可能很难理解Vagrant、Chef和Ansible等配置管理工具之间的区别。实际上,使用这些工具时往往可以实现类似的功能。然而,这些工具所试图解决的问题和各自的优劣点是不同的。
游民
通过编写一个名为Vagrantfile的配置文件来描述虚拟机的信息,Vagrant提供了一个工具,可以轻松进行虚拟机的创建、登录和配置等操作。
另外还有一个CloudStack的插件(vagrant-cloudstack)可用。(请参考Vagrant文档中关于如何使用vagrant-cloudstack的示例,如在Vagrant上使用IDCF云、Cloud n和ALTUS。)
Vagrant专注于处理虚拟机,并提供了方便的命令来登录(vagrant ssh)和执行配置(vagrant provision)。虽然可以使用Terraform创建虚拟机,但没有相应的命令可用。
如果您想要灵活地进行网络设置,那么使用Terraform更方便。Vagrant的安全组和端口转发功能是为了实现与虚拟机的连接而实施的,因此无法进行太灵活的设置。而在Terraform中,可以进行与虚拟机连接无关的其他设置,还可以通过修改配置文件来轻松更改实际配置。
Vagrant的开发者是和Terraform一样的HashiCorp。近日,HashiCorp宣布推出了Atlas,这是一个结合了HashiCorp的产品(Vagrant、Packer、Consul、Terraform)来管理基础设施的服务。由于Atlas的文档对于使用Vagrant和Terraform非常有参考价值,我推荐你去阅读一下。
厨师、Ansible、SaltStack、Puppet等
通过使用这些配置管理工具,根据各自的配置文件,可以对安装在服务器上的软件包和服务进行设置。尽管有些工具支持CloudStack,可以创建虚拟机等功能,但是这些工具可以操作的项目相当有限,因此似乎很难完全管理CloudStack上的整个基础设施。
由于 Terraform 无法在服务器内进行配置,因此一种常见的用法是使用配置管理工具通过 provisioning 功能在创建虚拟机时执行配置操作。(请注意,Terraform 的 provisioning 只会在创建虚拟机时执行。)
在使用Terraform创建虚拟机时,还可以使用已经配置好的镜像。通过使用配置管理工具来创建镜像的源虚拟机,可以实现对镜像配置和版本的有效管理。(尽管我还没有尝试过,但是packer-cloudstack可能也对创建和管理镜像有用。)
云编排、热力、堆栈校正器等
使用AWS的CloudFormation或OpenStack的Heat,您可以在每个提供者上使用配置文件来管理基础架构。在CloudStack中,有一个名为Stackmate的项目,它可以使CloudFormation模板在CloudStack中可用。
尽管Terraform和这些工具试图解决的问题几乎相同,但Terraform具有同时使用多个提供商的特点。使用Terraform可以统一管理多个环境作为基础设施。
关于另一个CloudStack提供者实现
我个人正在开发另一个实现(terraform-provider-cloudstack),它允许使用负载均衡器和安全组。我希望能够将其与内置版本合并。此外,考虑到需要在各种配置下建立和验证CloudStack的情况,我还希望在terraform-provider-cloudstack中实现一些对管理员有用但内置版本可能较为困难的资源(例如区域和服务提供方)。
总结
通过使用Terraform,可以将包括CloudStack在内的基础架构配置转化为代码。此外,将配置文件的更改内容反映到实际配置中也很容易。
尽管尚有许多功能尚未实现,但我认为如果能够有效地使用它,可以提高管理和运维的效率,所以请务必尝试一下。