本文介绍如何使用Terraform解决存量云资源管理难题。

背景信息

本文内容适用于以下四种运维场景:
  • 场景一:长期使用控制台、阿里云CLI、资源编排服务或者直接调用API创建和管理资源,初次使用Terraform的场景。
  • 场景二:长期使用Terraform管理资源,如果通过控制台对单个云资源做属性变更,希望保持原有的资源状态(State)一致的场景。
  • 场景三:所有资源都定义在一个模板中,想要对原有模板进行重构拆分,以降低随着资源不断增多而带来的模板和state的管理复杂度的场景。
  • 场景四:想要将新版Provider中新增的参数同步到原文档中的场景。

Terraform基于资源模板定义不仅可以实现对新资源的创建,变更,删除等操作,还可以通过简单的命令将那些游离在Terraform管理体系之外的云资源进行导入和纳管,进而实现对所有云资源的统一管理。

Terraform 导入存量资源

Terraform对资源的导入可以分为三个步骤:
  1. 获取资源ID:

    基于资源ID查询资源并获取其属性。

  2. 模板声明所要导入的资源:

    模板驱动,即使是要导入的资源,也需要在模板中进行声明。

  3. 补齐资源模板定义:

    导入成功后,需要根据资源属性补齐已经在模板中声明的资源定义。

  1. 获取资源ID。
    对资源ID的获取可以通过Web控制台,CLI,API等多种方式,最简单的方式是通过Terraform的DataSource,输入简单的查询条件,例如获取一个负载均衡实例:
    data "alicloud_slbs" "default" {
      name_regex  = "for-demo*"
    }
    output "slb_ids" {
      value = data.alicloud_slbs.default.ids
    }
    运行 terraform apply 命令即可展示所有符合条件的SLB的ID:
    $ terraform apply
    data.alicloud_slbs.default: Refreshing state...
    
    Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    slb_ids = [
      "lb-gw8vinrqxxxxxxxxxxx",
      "lb-gw8axostxxxxxxxxxxx",
    ]
  2. 模板声明所要导入的资源。
    和创建资源一样,在导入资源前,也需要在模板中进行资源声明,以便指定所要导入的资源在State中的存放路径。如下所示,声明一个负载均衡实例:
    resource "alicloud_slb" "this" {}
    简单的声明之后,无需定义具体的参数即可开始资源的导入操作。在Terraform中,导入一个资源的操作通过import命令来完成,完整的命令格式为terraform import <资源类型>.<资源标识> <资源ID> ,详细操作如下:
    $ terraform import alicloud_slb.this lb-gw8vinrqxxxxxxxxxxx
    alicloud_slb.this: Importing from ID "lb-gw8vinrqxxxxxxxxxxx"...
    alicloud_slb.this: Import prepared!
      Prepared alicloud_slb for import
    alicloud_slb.this: Refreshing state... [id=lb-gw8vinrqxxxxxxxxxxx]
    
    Import successful!
    
    The resources that were imported are shown above. These resources are now in
    your Terraform state and will henceforth be managed by Terraform.
  3. 补齐资源模板定义。
    由于模板中没有完成对所导入资源的详细定义,因此,资源导入成功后,模板内容与State存储的内容存在差异,此时如果直接运行plan命令,将会看到一个update:
    $ terraform plan
    Refreshing Terraform state in-memory prior to plan...
    The refreshed state will be used to calculate this plan, but will not be
    persisted to local or remote state storage.
    
    data.alicloud_slbs.default: Refreshing state...
    alicloud_slb.this: Refreshing state... [id=lb-gw8vinrqxxxxxxxxxxx]
    
    ------------------------------------------------------------------------
    
    An execution plan has been generated and is shown below.
    Resource actions are indicated with the following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # alicloud_slb.this will be updated in-place
      ~ resource "alicloud_slb" "this" {
            address              = "47.254.181.122"
            ...
          ~ delete_protection    = "on" -> "off"
            id                   = "id=lb-gw8vinrqxxxxxxxxxxx"
            ...
          ~ name                 = "for_demo-test" -> "tf-lb-20191108144235105700000001"
            ...
        }
    
    Plan: 0 to add, 1 to change, 0 to destroy.
    
    ------------------------------------------------------------------------
    
    Note: You didn't specify an "-out" parameter to save this plan, so Terraform
    can't guarantee that exactly these actions will be performed if
    "terraform apply" is subsequently run.
    为了保持资源模板与资源状态的一致,需要在模板中手动补齐缺失的参数定义,直到运行plan不会再有变更信息为止:
    resource "alicloud_slb" "this" {
      delete_protection = "on"
      name              = "for_demo-test"
    }
    所要补齐的内容主要以那些引起更新的字段为主,补齐完成后运行terraform plan进行测试:
    $ terraform plan
    Refreshing Terraform state in-memory prior to plan...
    The refreshed state will be used to calculate this plan, but will not be
    persisted to local or remote state storage.
    
    data.alicloud_slbs.default: Refreshing state...
    alicloud_slb.this: Refreshing state... [id=lb-gw8vinrqtqx1ro1r94c96]
    
    ------------------------------------------------------------------------
    
    No changes. Infrastructure is up-to-date.
    
    This means that Terraform did not detect any differences between your
    configuration and real physical resources that exist. As a result, no
    actions need to be performed.

    可以看到,此时已经没有任何需要变更的信息。至此完成了对一个资源的完整导入。

Terraform移除存量资源

在实际操作场景中,经常会遇到资源的误导入,导入路径不符,想要调整资源路径,想要永久保留某资源等多种复杂情况。面对这些情况,整体的实现思路也是非常简单:先将导入后的资源从State中移除,然后重新导入。

资源的移除操作可以通过state rm命令来完成,完整的命令格式为terraform state rm <资源类型>.<资源标识> ,详细操作如下:
$ terraform state rm alicloud_slb.this
Removed alicloud_slb.this
Successfully removed 1 resource instance(s).

state rm命令只是将指定的资源从State文件中移除,并不会将其真正删除,这也正是为后续的导入操作做好了铺垫。

总结

对于以上四种场景我们可以使用以下解决方案:
  • 场景一的解决方案:

    通过terraform import命令来完成对存量资源的导入,进而使用Terraform统一管理。

  • 场景二的解决方案:

    在确定清楚参数属性的具体值之后,如果以模板参数值为准,那么只需要运行apply命令再变更回来即可;如果以控制台的值为准,那么只需要补充或修改模板参数值即可。

  • 场景三的解决方案:

    可以先通过terraform state rm命令将所有需要重组的资源移出State,等模板重构结束后,再使用terraform import将其导入即可。

  • 场景四的解决方案:

    和上一解决方案一样,通过“先移出再导入”调整即可。

Terraform的命令非常灵活和简单,基于模板和State一致性的原理,借助terraform import可以轻松地实现对存量资源的统一管理,不用再担心那些游离在Terraform管理体系之外资源无法管理的痛点,也无需惧怕某个资源从State中移除后无法继续管理的问题,所有的云资源都可以被Terraform统一管理起来。