著者は、 Write for DOnations プログラムの一環として、 Free and Open SourceFundを選択して寄付を受け取りました。

序章

Terraform モジュールを使用すると、インフラストラクチャの個別のリソースを単一の統合リソースにグループ化できます。 必要になるたびにリソース定義を繰り返すことなく、後でカスタマイズして再利用できます。これは、大規模で複雑な構造のプロジェクトに役立ちます。 定義した入力変数を使用してモジュールインスタンスをカスタマイズしたり、出力を使用してモジュールインスタンスから情報を抽出したりできます。 独自のカスタムモジュールを作成する以外に、 TerraformRegistryで公開されている既成のモジュールを使用することもできます。 開発者は、作成したモジュールなどの入力を使用してそれらを使用およびカスタマイズできますが、ソースコードはクラウドに保存され、クラウドから取得されます。

このチュートリアルでは、冗長性のためにロードバランサーの背後に複数のドロップレットを設定するTerraformモジュールを作成します。 また、Hashicorp構成言語(HCL)のfor_eachおよびcountループ機能を使用して、モジュールの複数のカスタマイズされたインスタンスを同時にデプロイします。

前提条件

注:このチュートリアルは、Terraform1.1.3で特別にテストされています。

モジュールの構造と利点

このセクションでは、モジュールがもたらすメリット、プロジェクト内の通常の場所、およびモジュールの構造化方法について学習します。

カスタムTerraformモジュールは、大規模なプロジェクトで頻繁に一緒に使用および展開される接続コンポーネントをカプセル化するために作成されます。 それらは自己完結型であり、必要なリソース、変数、およびプロバイダーのみをバンドルします。

モジュールは通常、プロジェクトのルートにある中央フォルダーに格納され、それぞれがその下のそれぞれのサブフォルダーに格納されます。 モジュール間の明確な分離を維持するために、常に単一の目的を持つようにモジュールを設計し、サブモジュールが含まれないようにしてください。

頻繁にカスタマイズしないでモジュールを繰り返す場合は、リソーススキームからモジュールを作成すると便利です。 単一のリソースをモジュールとしてパッケージ化することは不要であり、アーキテクチャ全体の単純さを徐々に取り除きます。

小規模な開発およびテストプロジェクトの場合、モジュールを組み込む必要はありません。これらの場合、モジュールはあまり改善されないためです。 モジュールはカスタマイズ機能を備えているため、複雑に構造化されたプロジェクトの構成要素です。 コードの重複を回避することには大きな利点があるため、開発者は大規模なプロジェクトにモジュールを使用します。 モジュールには、定義を1か所で変更するだけで済み、それがインフラストラクチャの残りの部分に伝播されるという利点もあります。

次に、Terraformプロジェクトでモジュールを定義、使用、およびカスタマイズします。

モジュールの作成

このセクションでは、複数のドロップレットとロードバランサーをTerraformリソースとして定義し、それらをモジュールにパッケージ化します。 また、モジュール入力を使用して、結果のモジュールをカスタマイズ可能にします。

モジュールは、modulesというディレクトリの下のdroplet-lbという名前のディレクトリに保存します。 前提条件の一部として作成したterraform-modulesディレクトリにいると仮定して、次のコマンドを実行して両方を同時に作成します。

  1. mkdir -p modules/droplet-lb

-p引数は、指定されたパスにすべてのディレクトリを作成するようにmkdirに指示します。

そこに移動します:

  1. cd modules/droplet-lb

前のセクションで説明したように、モジュールには、使用するリソースと変数が含まれています。 Terraform 0.13から、使用するプロバイダーの定義も含める必要があります。 Terraformは、プロジェクトのルートディレクトリも含め、HCLコードを含むすべてのディレクトリをモジュールと見なすため、モジュールは、コードがモジュールを表すことに注意するための特別な構成を必要としません。

モジュールで定義された変数はその入力として公開され、リソース定義で使用してカスタマイズできます。 作成するモジュールには、作成するドロップレットの数とそのグループの名前の2つの入力があります。 variables.tfというファイルを作成して開き、変数を保存します。

  1. nano variables.tf

次の行を追加します。

modules / droplet-lb / variables.tf
variable "droplet_count" {}
variable "group_name" {}

ファイルを保存して閉じます。

ドロップレット定義をdroplets.tfという名前のファイルに保存します。 編集のために作成して開きます。

  1. nano droplets.tf

次の行を追加します。

modules / droplet-lb / droplets.tf
resource "digitalocean_droplet" "droplets" {
  count  = var.droplet_count
  image  = "ubuntu-20-04-x64"
  name   = "${var.group_name}-${count.index}"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

作成するリソースのインスタンスの数を指定するcountパラメーターの場合、droplet_count変数を渡します。 その値は、モジュールがメインプロジェクトコードから呼び出されるときに指定されます。 デプロイされた各ドロップレットの名前は異なります。これは、現在のドロップレットのインデックスを指定されたグループ名に追加することで実現できます。 ドロップレットのデプロイはfra1リージョンで行われ、Ubuntu20.04を実行します。

完了したら、ファイルを保存して閉じます。

ドロップレットが定義されたら、ロードバランサーの作成に進むことができます。 そのリソース定義をlb.tfという名前のファイルに保存します。 次のコマンドを実行して、編集用に作成して開きます。

  1. nano lb.tf

リソース定義を追加します。

モジュール/液滴-lb/lb.tf
resource "digitalocean_loadbalancer" "www-lb" {
  name   = "lb-${var.group_name}"
  region = "fra1"

  forwarding_rule {
    entry_port     = 80
    entry_protocol = "http"

    target_port     = 80
    target_protocol = "http"
  }

  healthcheck {
    port     = 22
    protocol = "tcp"
  }

  droplet_ids = [
    for droplet in digitalocean_droplet.droplets:
      droplet.id
  ]
}

区別できるようにするために、名前にグループ名が含まれるロードバランサーを定義します。 ドロップレットと一緒にfra1リージョンにデプロイします。 次の2つのセクションでは、ターゲットと監視ポートおよびプロトコルを指定します。

強調表示されたdroplet_idsブロックは、ロードバランサーによって管理される必要があるドロップレットのIDを取り込みます。 複数の液滴があり、それらの数は事前にわからないため、forループを使用して液滴のコレクション(digitalocean_droplet.droplets)をトラバースし、それらのIDを取得します。 forループを角かっこ([])で囲んで、結果のコレクションがリストになるようにします。

ファイルを保存して閉じます。

これで、モジュールのドロップレット、ロードバランサー、および変数が定義されました。 プロバイダー要件を定義し、モジュールが使用するプロバイダー(バージョンや場所など)を指定する必要があります。 Terraform 0.13以降、モジュールは、使用するHashicorpが管理していないプロバイダーのソースを明示的に定義する必要があります。 これは、親プロジェクトから継承しないためです。

プロバイダー要件をprovider.tfという名前のファイルに保存します。 次のコマンドを実行して、編集用に作成します。

  1. nano provider.tf

digitaloceanプロバイダーを要求するには、次の行を追加します。

modules / droplet-lb / Provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

完了したら、ファイルを保存して閉じます。 droplet-lbモジュールにdigitaloceanプロバイダーが必要になりました。

モジュールは、リソースの状態に関する内部情報を抽出するために使用できる出力もサポートします。 ロードバランサーのIPアドレスを公開する出力を定義し、outputs.tfという名前のファイルに保存します。 編集用に作成します。

  1. nano outputs.tf

次の定義を追加します。

modules / droplet-lb / outputs.tf
output "lb_ip" {
  value = digitalocean_loadbalancer.www-lb.ip
}

この出力は、ロードバランサーのIPアドレスを取得します。 ファイルを保存して閉じます。

これで、droplet-lbモジュールが機能的に完成し、展開の準備が整いました。 プロジェクトのルートに保存するメインコードから呼び出します。 まず、ファイルディレクトリを2回上に移動して、そこに移動します。

  1. cd ../..

次に、main.tfというファイルを作成して開き、モジュールを使用します。

  1. nano main.tf

次の行を追加します。

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups.lb_ip
}

この宣言では、sourceとして指定されたディレクトリにあるdroplet-lbモジュールを呼び出します。 提供する入力droplet_countgroup_nameを構成します。これは、group1に設定されているため、後でインスタンスを識別できます。

ロードバランサーのIP出力はモジュールで定義されているため、プロジェクトを適用するときに自動的に表示されることはありません。 これに対する解決策は、その値(loadbalancer_ip)を取得する別の出力を作成することです。

完了したら、ファイルを保存して閉じます。

次のコマンドを実行してモジュールを初期化します。

  1. terraform init

出力は次のようになります。

Output
Initializing modules... - groups in modules/droplet-lb Initializing the backend... Initializing provider plugins... - Finding digitalocean/digitalocean versions matching "~> 2.0"... - Installing digitalocean/digitalocean v2.19.0... - Installed digitalocean/digitalocean v2.19.0 (signed by a HashiCorp partner, key ID F82037E524B9C0E8) ... 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.

プロジェクトを計画して、Terraformが実行するアクションを確認できます。

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

出力は次のようになります。

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.groups.digitalocean_droplet.droplets[0] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-0" ... } # module.groups.digitalocean_droplet.droplets[1] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-1" ... } # module.groups.digitalocean_droplet.droplets[2] will be created + resource "digitalocean_droplet" "droplets" { ... + name = "group1-2" ... } # module.groups.digitalocean_loadbalancer.www-lb will be created + resource "digitalocean_loadbalancer" "www-lb" { ... + name = "lb-group1" ... } Plan: 4 to add, 0 to change, 0 to destroy. ...

この出力は、Terraformがgroup1-0group1-1、およびgroup1-2という名前の3つのドロップレットを作成し、group1-lbという名前のロードバランサーを作成することを詳しく説明しています。 3つのドロップレットとの間のトラフィックを管理します。

次のコマンドを実行して、プロジェクトをクラウドに適用してみてください。

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

プロンプトが表示されたら、yesと入力します。 出力にはすべてのアクションが表示され、ロードバランサーのIPアドレスも表示されます。

Output
module.groups.digitalocean_droplet.droplets[1]: Creating... module.groups.digitalocean_droplet.droplets[0]: Creating... module.groups.digitalocean_droplet.droplets[2]: Creating... ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: loadbalancer-ip = ip_address

カスタマイズ可能な数のドロップレットとロードバランサーを含むモジュールを作成しました。これらは、着信トラフィックと発信トラフィックを管理するように自動的に構成されます。

デプロイされたリソースの名前の変更

前のセクションでは、定義したモジュールをデプロイし、それをgroupsと呼びました。 名前を変更したい場合は、モジュール呼び出しの名前を変更するだけでは、期待した結果は得られません。 呼び出しの名前を変更すると、Terraformはリソースを破棄して再作成するように促され、過度のダウンタイムが発生します。

たとえば、main.tfを開いて、次のコマンドを実行して編集します。

  1. nano main.tf

強調表示されているように、groupsモジュールの名前をgroups_renamedに変更します。

main.tf
module "groups_renamed" {
  source = "./modules/droplet-lb"

  droplet_count = 3
  group_name    = "group1"
}

output "loadbalancer-ip" {
  value = module.groups_renamed.lb_ip
}

ファイルを保存して閉じます。 次に、プロジェクトを再度初期化します。

  1. terraform init

これで、プロジェクトを計画できます。

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

出力は長くなりますが、次のようになります。

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create - destroy Terraform will perform the following actions: # module.groups.digitalocean_droplet.droplets[0] will be destroyed ... # module.groups_renamed.digitalocean_droplet.droplets[0] will be created ...

Terraformは、既存のインスタンスを破棄して新しいインスタンスを作成するように求めるプロンプトを表示します。 これは破壊的で不必要であり、望ましくないダウンタイムにつながる可能性があります。

代わりに、movedブロックを使用して、古いリソースを新しい名前で移動するようにTerraformに指示できます。 main.tfを開いて編集し、ファイルの最後に次の行を追加します。

moved {
  from = module.groups
  to   = module.groups_renamed
}

完了したら、ファイルを保存して閉じます。

これで、プロジェクトを計画できます。

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

main.tfmovedブロックが存在する場合、Terraformはリソースを再作成するのではなく、移動したいと考えています。

Output
Terraform will perform the following actions: # module.groups.digitalocean_droplet.droplets[0] has moved to module.groups_renamed.digitalocean_droplet.droplets[0] ... # module.groups.digitalocean_droplet.droplets[1] has moved to module.groups_renamed.digitalocean_droplet.droplets[1] ...

リソースを移動すると、Terraform状態での場所が変わります。つまり、実際のクラウドリソースが変更、破棄、または再作成されることはありません。

次の手順で構成を大幅に変更するため、以下を実行して、デプロイされたリソースを破棄します。

  1. terraform destroy -var "do_token=${DO_PAT}"

プロンプトが表示されたら、yesと入力します。 出力は次のようになります。

Output
... Destroy complete! Resources: 4 destroyed.

このセクションでは、プロセスでリソースを破棄せずに、Terraformプロジェクトのリソースの名前を変更しました。 ここで、for_eachcountを使用して、同じコードからモジュールの複数のインスタンスをデプロイします。

複数のモジュールインスタンスの展開

このセクションでは、countおよびfor_eachを使用して、droplet-lbモジュールをカスタマイズして複数回展開します。

countを使用する

同じモジュールの複数のインスタンスを一度にデプロイする1つの方法は、countパラメーターにいくつを渡すことです。これは、すべてのモジュールで自動的に使用可能になります。 main.tfを開いて編集します。

  1. nano main.tf

既存の出力定義とmovedブロックを削除して、次のように変更します。

main.tf
module "groups" {
  source = "./modules/droplet-lb"

  count  = 3

  droplet_count = 3
  group_name    = "group1-${count.index}"
}

count3に設定することにより、モジュールを3回、それぞれ異なるグループ名でデプロイするようにTerraformに指示します。 完了したら、ファイルを保存して閉じます。

次を実行して展開を計画します。

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

出力は長くなり、次のようになります。

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.groups[0].digitalocean_droplet.droplets[0] will be created ... # module.groups[0].digitalocean_droplet.droplets[1] will be created ... # module.groups[0].digitalocean_droplet.droplets[2] will be created ... # module.groups[0].digitalocean_loadbalancer.www-lb will be created ... # module.groups[1].digitalocean_droplet.droplets[0] will be created ... # module.groups[1].digitalocean_droplet.droplets[1] will be created ... # module.groups[1].digitalocean_droplet.droplets[2] will be created ... # module.groups[1].digitalocean_loadbalancer.www-lb will be created ... # module.groups[2].digitalocean_droplet.droplets[0] will be created ... # module.groups[2].digitalocean_droplet.droplets[1] will be created ... # module.groups[2].digitalocean_droplet.droplets[2] will be created ... # module.groups[2].digitalocean_loadbalancer.www-lb will be created ... Plan: 12 to add, 0 to change, 0 to destroy. ...

3つのモジュールインスタンスのそれぞれに3つのドロップレットとロードバランサーが関連付けられているというTerraformの詳細が出力に表示されます。

for_eachを使用する

より複雑なインスタンスのカスタマイズが必要な場合、またはインスタンスの数がコードの記述中に認識されないサードパーティのデータ(多くの場合マップとして表示される)に依存する場合は、モジュールにfor_eachを使用できます。

次に、グループ名をドロップレットカウントにペアリングするマップを定義し、それに応じてdroplet-lbのインスタンスをデプロイします。 main.tfを開いて、次のコマンドを実行して編集します。

  1. nano main.tf

次のようにファイルを変更します。

main.tf
variable "group_counts" {
  type    = map
  default = {
    "group1" = 1
    "group2" = 3
  }
}

module "groups" {
  source   = "./modules/droplet-lb"
  for_each = var.group_counts

  droplet_count = each.value
  group_name    = each.key
}

最初に、group_countsというマップを定義します。このマップには、特定のグループに必要なドロップレットの数が含まれています。 次に、モジュールdroplet-lbを呼び出しますが、for_eachループが直前に定義したマップであるvar.group_countsで動作するように指定します。 droplet_countは、現在のグループのドロップレットの数である現在のペアの値であるeach.valueを取ります。 group_nameはグループの名前を受け取ります。

完了したら、ファイルを保存して閉じます。

次のコマンドを実行して構成を適用してみてください。

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

出力には、ドロップレットとロードバランサーを使用して2つのグループを作成するためにTerraformが実行するアクションの詳細が示されます。

Output
... Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # module.groups["group1"].digitalocean_droplet.droplets[0] will be created ... # module.groups["group1"].digitalocean_loadbalancer.www-lb will be created ... # module.groups["group2"].digitalocean_droplet.droplets[0] will be created ... # module.groups["group2"].digitalocean_droplet.droplets[1] will be created ... # module.groups["group2"].digitalocean_droplet.droplets[2] will be created ... # module.groups["group2"].digitalocean_loadbalancer.www-lb will be created ...

このステップでは、countおよびfor_eachを使用して、同じコードから同じモジュールの複数のカスタマイズされたインスタンスをデプロイしました。

結論

このチュートリアルでは、Terraformモジュールを作成してデプロイしました。 モジュールを使用して論理的にリンクされたリソースをグループ化し、それらをカスタマイズして、中央のコード定義から複数の異なるインスタンスをデプロイしました。 また、出力を使用して、モジュールに含まれるリソースの属性を表示しました。

Terraformの詳細については、Terraformシリーズでインフラストラクチャを管理する方法をご覧ください。