使用 Terraform 模塊和模板創建可重用的基礎設施

【導讀】Terraform 是基礎架構即代碼工具,本文介紹了它的使用。

介紹

基礎設施即代碼 (IAC) 的主要好處之一是重用已定義基礎設施的部分內容。在 Terraform 中,您可以使用模塊將邏輯連接的組件封裝到一個實體中,並使用您定義的輸入變量對其進行自定義。通過使用模塊在高層定義您的基礎設施,您可以通過僅將不同的值傳遞給相同的模塊來分離開發、暫存和生產環境,從而最大限度地減少代碼重複並最大限度地提高簡潔性。

您不僅限於使用自定義模塊。Terraform Registry 集成到 Terraform 中,並列出了您可以通過在required_providers部分定義它們立即合併到項目中的模塊和提供程序。引用公共模塊可以加快您的工作流程並減少代碼重複。如果您有一個有用的模塊並希望與全世界分享,您可以考慮將其發佈到註冊表上以供其他開發人員使用。

在本教程中,我們將考慮在 Terraform 項目中定義和重用代碼的一些方法。您將引用 Terraform Registry 中的模塊,使用模塊分離開發和生產環境,瞭解模板及其使用方式,以及如何使用depends_onmeta 參數顯式指定資源依賴關係。

先決條件

注意:我們已經使用 Terraform 專門測試了本教程0.13

分離開發和生產環境

在本節中,您將使用模塊來實現目標部署環境之間的分離。您將根據更復雜項目的結構來安排這些。您將首先創建一個包含兩個模塊的項目,其中一個將定義 Droplet 和負載均衡器,另一個將設置 DNS 域記錄。之後,您將爲兩個不同的環境(devprod)編寫配置,這將調用相同的模塊。

創建dns-records模塊

作爲先決條件的一部分,你已經下設立的項目開始terraform-reusability,創造了droplet-lb在自己的子目錄下的模塊modules。您現在將設置名爲 的第二個模塊dns-records,其中包含變量、輸出和資源定義。假設您在terraform-reusabilitydns-records通過運行創建:

mkdir modules/dns-records

導航到它:

cd modules/dns-records

此模塊將包含您的域的定義和稍後將指向負載均衡器的 DNS 記錄。您將首先定義變量,這些變量將成爲該模塊將公開的輸入。您將它們存儲在一個名爲variables.tf. 創建它以進行編輯:

nano variables.tf

添加以下變量定義:

terraform-reusability/modules/dns-records/variables.tf

variable "domain_name" {}
variable "ipv4_address" {}

保存並關閉文件。現在,您將定義域和隨行ACNAME在一個名爲文件中的記錄records.tf。通過運行創建並打開它進行編輯:

nano records.tf

添加以下資源定義:

terraform - 可重用性 / 模塊 / dns-records/records.tf

resource "digitalocean_domain" "domain" {
  name = var.domain_name
}

resource "digitalocean_record" "domain_A" {
  domain = digitalocean_domain.domain.name
  type   = "A"
  name   = "@"
  value  = var.ipv4_address
}

resource "digitalocean_record" "domain_CNAME" {
  domain = digitalocean_domain.domain.name
  type   = "CNAME"
  name   = "www"
  value  = var.ipv4_address
}

首先,在您的 DigitalOcean 帳戶中爲您的域名定義域。雲會自動添加三個 DigitalOcean 域名服務器作爲NS記錄。然後,您A爲您的域定義一條記錄,將其路由(@asvalue表示真正的域名,沒有子域)到作爲變量提供的 IP 地址ipv4_address。爲完整起見,後面的CNAME記錄指定www子域也應指向相同的 IP 地址。完成後保存並關閉文件。

接下來,您將定義此模塊的輸出。輸出將顯示所創建記錄的 FQDN(完全限定域名)。創建並打開outputs.tf以進行編輯:

nano outputs.tf

添加以下幾行:

terraform - 可重用性 / 模塊 / dns-records/outputs.tf

output "A_fqdn" {
  value = digitalocean_record.domain_A.fqdn
}

output "CNAME_fqdn" {
  value = digitalocean_record.domain_CNAME.fqdn
}

完成後保存並關閉文件。

定義變量、DNS 記錄和輸出後,您需要指定的最後一件事是此模塊的提供程序要求。你會指定該dns-records模塊需要digitalocean在一個名爲文件提供provider.tf。創建並打開它進行編輯:

nano provider.tf

添加以下幾行:

terraform - 可重用性 / 模塊 / dns-records/provider.tf

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
    }
  }
  required_version = ">= 0.13"
}

完成後,保存並關閉文件。該dns-records模塊現在需要digitalocean提供程序並且功能完整。

創建不同的環境

以下是該terraform-reusability項目的當前結構:

terraform_reusability/
├─ modules/
│  ├─ dns-records/
│  │  ├─ outputs.tf
│  │  ├─ provider.tf
│  │  ├─ records.tf
│  │  ├─ variables.tf
│  ├─ droplet-lb/
│  │  ├─ droplets.tf
│  │  ├─ lb.tf
│  │  ├─ outputs.tf
│  │  ├─ provider.tf
│  │  ├─ variables.tf
├─ main.tf
├─ provider.tf

到目前爲止,您的項目中有兩個模塊:您剛剛創建的模塊 ( dns-records) 和droplet-lb作爲先決條件的一部分創建的模塊。

爲了方便不同的環境中,您將存儲devprod環境配置文件名爲目錄下environments,這將駐留在項目的根。兩種環境都將調用相同的兩個模塊,但具有不同的參數值。這樣做的好處是當模塊將來在內部發生變化時,您只需要更新您傳入的值。

首先,通過運行導航到項目的根目錄:

cd ../..

然後,dev同時prod在環境下創建和目錄:

mkdir -p environments/dev && mkdir environments/prod

-p參數的命令mkdir創建在給定的路徑中的所有目錄。

導航到該dev目錄,因爲您將首先配置該環境:

cd environments/dev

您將代碼存儲在名爲 的文件中main.tf,因此創建它以進行編輯:

nano main.tf

添加以下幾行:

terraform-reusability/environments/dev/main.tf

module "droplets" {
  source   = "../../modules/droplet-lb"

  droplet_count = 2
  group_name    = "dev"
}

module "dns" {
  source   = "../../modules/dns-records"

  domain_name   = "your_dev_domain"
  ipv4_address  = module.droplets.lb_ip
}

在這裏你調用和配置兩個模塊,droplet-lbdns-records,這將共同導致兩個 Droplet 的創建。它們前面有一個負載均衡器;所提供域的 DNS 記錄設置爲指向該負載均衡器。請記住將環境替換your_dev_domain爲您想要的域名dev,然後保存並關閉文件。

接下來,您將配置 DigitalOcean 提供程序併爲其創建一個變量,以便能夠接受您作爲先決條件的一部分創建的個人訪問令牌。打開一個名爲 的新文件provider.tf進行編輯:

nano provider.tf

添加以下幾行:

terraform-reusability/environments/dev/provider.tf

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "1.22.2"
    }
  }
}

variable "do_token" {}

provider "digitalocean" {
  token = var.do_token
}

在此代碼中,您要求digitalocean提供程序可用並將do_token變量傳遞給其實例。保存並關閉文件。

通過運行初始化配置:

terraform init

您將收到以下輸出:

Output
Initializing modules...
- dns in ../../modules/dns-records
- droplets in ../../modules/droplet-lb

Initializing the backend...

Initializing provider plugins...
- Finding latest version of digitalocean/digitalocean...
- Installing digitalocean/digitalocean v2.0.2...
- Installed digitalocean/digitalocean v2.0.2 (signed by a HashiCorp partner, key ID F82037E524B9C0E8)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/plugins/signing.html

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, we recommend adding version constraints in a required_providers block
in your configuration, with the constraint strings suggested below.

* digitalocean/digitalocean: version = "~> 2.0.2"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

prod環境的配置類似。通過運行導航到其目錄:

cd ../prod

創建並打開main.tf以進行編輯:

nano main.tf

添加以下幾行:

terraform - 可重用性 / 環境 / prod/main.tf

module "droplets" {
  source   = "../../modules/droplet-lb"

  droplet_count = 5
  group_name    = "prod"
}

module "dns" {
  source   = "../../modules/dns-records"

  domain_name   = "your_prod_domain"
  ipv4_address  = module.droplets.lb_ip
}

這與您的dev代碼之間的區別在於將部署五個 Droplet。此外,您應該用您的prod域名替換的域名會有所不同。完成後保存並關閉文件。

然後,從dev以下位置複製提供程序配置:

cp ../dev/provider.tf .

也初始化此配置:

terraform init

此命令的輸出將與您上次運行時相同。

您可以嘗試通過運行以下命令來規劃配置以查看 Terraform 將創建哪些資源:

terraform plan -var "do_token=${DO_PAT}"

的輸出prod如下:

Output...
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.dns.digitalocean_domain.domain will be created
  + resource "digitalocean_domain" "domain" {
      + id   = (known after apply)
      + name = "your_prod_domain"
      + urn  = (known after apply)
    }

  # module.dns.digitalocean_record.domain_A will be created
  + resource "digitalocean_record" "domain_A" {
      + domain = "your_prod_domain"
      + fqdn   = (known after apply)
      + id     = (known after apply)
      + name   = "@"
      + ttl    = (known after apply)
      + type   = "A"
      + value  = (known after apply)
    }

  # module.dns.digitalocean_record.domain_CNAME will be created
  + resource "digitalocean_record" "domain_CNAME" {
      + domain = "your_prod_domain"
      + fqdn   = (known after apply)
      + id     = (known after apply)
      + name   = "www"
      + ttl    = (known after apply)
      + type   = "CNAME"
      + value  = (known after apply)
    }

  # module.droplets.digitalocean_droplet.droplets[0] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "prod-0"
...
    }

  # module.droplets.digitalocean_droplet.droplets[1] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "prod-1"
...
    }

  # module.droplets.digitalocean_droplet.droplets[2] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "prod-2"
...
    }

  # module.droplets.digitalocean_droplet.droplets[3] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "prod-3"
...
    }

  # module.droplets.digitalocean_droplet.droplets[4] will be created
  + resource "digitalocean_droplet" "droplets" {
...
      + name                 = "prod-4"
...
    }

  # module.droplets.digitalocean_loadbalancer.www-lb will be created
  + resource "digitalocean_loadbalancer" "www-lb" {
...
      + name                     = "lb-prod"
...

Plan: 9 to add, 0 to change, 0 to destroy.
...

這將部署五個帶有負載均衡器的 Droplet。它還會創建prod您指定的域,其中兩個 DNS 記錄指向負載均衡器。您也可以嘗試爲dev環境規劃配置– 您會注意到將計劃部署兩個 Droplet。

注意:您可以使用以下命令將此配置應用於devprod環境:

terraform apply -var "do_token=${DO_PAT}"

下面演示了您如何構建此項目:

terraform_reusability/
├─ environments/
│  ├─ dev/
│  │  ├─ main.tf
│  │  ├─ provider.tf
│  ├─ prod/
│  │  ├─ main.tf
│  │  ├─ provider.tf
├─ modules/
│  ├─ dns-records/
│  │  ├─ outputs.tf
│  │  ├─ provider.tf
│  │  ├─ records.tf
│  │  ├─ variables.tf
│  ├─ droplet-lb/
│  │  ├─ droplets.tf
│  │  ├─ lb.tf
│  │  ├─ outputs.tf
│  │  ├─ provider.tf
│  │  ├─ variables.tf
├─ main.tf
├─ provider.tf

添加的是environments目錄,其中包含devprod環境的代碼。

這種方法的好處是對模塊的進一步更改會自動傳播到項目的所有區域。除非對模塊輸入進行任何可能的自定義,否則這種方法不是重複的,並且儘可能地提高了可重用性,即使是跨部署環境。總的來說,這減少了混亂,並允許您使用版本控制系統跟蹤修改。

在本教程的最後兩節中,您將回顧depends_on元參數和templatefile函數。

聲明依賴以按順序構建基礎設施

在計劃操作時,Terraform 會自動嘗試感知現有依賴項並將它們構建到其依賴項圖中。它可以檢測到的主要依賴項是明確的引用;例如,當模塊的輸出值傳遞給另一個資源上的參數時。在這種情況下,模塊必須首先完成其部署以提供輸出值。

Terraform 無法檢測到的依賴項是隱藏的——它們具有副作用和無法從代碼推斷出的相互引用。一個例子是當一個對象不依賴於存在,而是依賴於另一個對象的行爲,並且不從代碼訪問它的屬性時。爲了克服這個問題,您可以使用depends_on以顯式方式手動指定依賴項。從 Terraform 開始0.13,您還可以使用depends_onon modules 強制在部署模塊本身之前完全部署列出的資源。可以對depends_on每種資源類型使用 meta 參數。depends_on還將接受其指定資源所依賴的其他資源列表。

在本教程的上一步中,您沒有使用 指定任何顯式依賴項depends_on,因爲您創建的資源沒有無法從代碼中推斷出的副作用。Terraform 能夠檢測您編寫的代碼中的引用,並相應地安排資源進行部署。

depends_on接受對其他資源的引用列表。它的語法如下所示:

resource "resource_type" "res" {
  depends_on = [...] # List of resources

  # Parameters...
}

請記住,您只應將其depends_on用作最後的選擇。如果使用,則應妥善記錄,因爲資源所依賴的行爲可能不會立即顯現。

使用模板進行自定義

在 Terraform 中,模板是在適當的地方替換表達式的結果,例如在資源上設置屬性值或構造字符串時。您已在前面的步驟和教程先決條件中使用它來動態生成 Droplet 名稱和其他參數值。

替換字符串中的值時,這些值被指定並用 括起來${}。模板替換經常在循環中使用,以便於定製所創建的資源。它還允許通過替換資源屬性中的輸入來定製模塊。

Terraform 提供了該templatefile函數,該函數接受兩個參數:要從磁盤讀取的文件和與其值配對的變量映射。它返回的值是用替換的表達式呈現的文件的內容——就像 Terraform 在規劃或應用項目時通常所做的那樣。由於函數不是依賴關係圖的一部分,因此無法從項目的其他部分動態生成該文件。

想象一下調用的模板文件內容droplets.tmpl如下:

%{ for address in addresses ~}
${address}:80
%{ endfor ~}

更長的聲明必須用 包圍%{},就像forendfor聲明一樣,它們分別表示for循環的開始和結束。在droplets調用函數並提供實際值之前,變量的內容和類型是未知的,如下所示:

templatefile("${path.module}/droplets.tmpl", { addresses = ["192.168.0.1", "192.168.1.1"] })

templatefile調用將返回的值如下:

Output192.168.0.1:80
192.168.1.1:80

這個函數有它的用例,但它們並不常見。例如,當配置的一部分需要以專有格式存在時,您可以使用它,但它依賴於其餘的值並且必須動態生成。在大多數情況下,最好儘可能直接在 Terraform 代碼中指定所有配置參數。

結論

在本文中,您在一個示例 Terraform 項目中最大限度地重用了代碼。主要方式是將常用的特性和配置打包成一個可定製的模塊,並在需要時使用。通過這樣做,您不會重複底層代碼(這可能容易出錯)並實現更快的週轉時間,因爲修改模塊幾乎是您引入更改所需要做的全部。

您不僅限於自己的模塊。如您所見,Terraform Registry 提供了可以合併到項目中的第三方模塊和提供程序。

轉自:

gingerdoc.com/tutorials/how-to-create-reusable-infrastructure-with-terraform-modules-and-templates

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/kb157s0QtoQqriJ1n6NMAw