本文介紹如何使用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統一管理起來。