我想在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在内的基础架构配置转化为代码。此外,将配置文件的更改内容反映到实际配置中也很容易。
尽管尚有许多功能尚未实现,但我认为如果能够有效地使用它,可以提高管理和运维的效率,所以请务必尝试一下。

广告
将在 10 秒后关闭
bannerAds