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

序章

Infrastructure as Code(IAC)の主な利点の1つは、定義されたインフラストラクチャの一部を再利用することです。 Terraformでは、モジュールを使用して、論理的に接続されたコンポーネントを1つのエンティティにカプセル化し、定義した入力変数を使用してそれらをカスタマイズできます。 モジュールを使用してインフラストラクチャを高レベルで定義することにより、同じモジュールに異なる値を渡すだけで、開発、ステージング、および本番環境を分離できます。これにより、コードの重複が最小限に抑えられ、簡潔さが最大化されます。

カスタムモジュールのみを使用することに限定されません。 TerraformレジストリはTerraformに統合されており、プロジェクトで定義することですぐにプロジェクトに組み込むことができるモジュールとプロバイダーを一覧表示します。 required_providers セクション。 パブリックモジュールを参照すると、ワークフローが高速化され、コードの重複が減ります。 便利なモジュールがあり、それを世界と共有したい場合は、他の開発者が使用できるようにレジストリに公開することを検討できます。

このチュートリアルでは、Terraformプロジェクトでコードを定義して再利用する方法のいくつかを探ります。 Terraformレジストリからモジュールを参照し、モジュールを使用して開発環境と本番環境を分離し、テンプレートとその使用方法について学習し、を使用してリソースの依存関係を明示的に指定します。 depends_on メタ引数。

前提条件

  • DigitalOceanパーソナルアクセストークン。DigitalOceanコントロールパネルから作成できます。 手順については、DigitalOcean製品ドキュメントパーソナルアクセストークンの作成方法を参照してください。
  • ローカルマシンにインストールされたTerraformと、DigitalOceanプロバイダーでセットアップされたプロジェクト。 DigitalOcean チュートリアルでTerraformを使用する方法のステップ1およびステップ2を完了し、プロジェクトフォルダーに名前を付けてください terraform-reusability、 それ以外の loadbalance. ステップ2の間に、 pvt_key 変数とSSHキーリソース。
  • The droplet-lb 下で利用可能なモジュール modulesterraform-reusability. カスタムモジュールの作成方法チュートリアルに従い、 droplet-lb モジュールは機能的に完了しています。 (つまり、 cd ../.. モジュールの作成セクションのコマンド。)
  • Terraformプロジェクトの構造化アプローチに関する知識。 詳細については、チュートリアルTerraformプロジェクトを構築する方法を参照してください。
  • (オプション)ネームサーバーがレジストラのDigitalOceanを指す2つの別個のドメイン。 ドメインはまだDigitalOceanアカウントに追加されてはなりません。 これを設定するには、共通ドメインレジストラからDigitalOceanネームサーバーを指定する方法チュートリアルを参照してください。 このチュートリアルで作成するプロジェクトをデプロイする予定がない場合は、これを行う必要がないことに注意してください。

注:このチュートリアルはTerraformを使用してテストされています 1.0.2.

開発環境と本番環境の分離

このセクションでは、モジュールを使用してターゲットのデプロイメント環境を分離します。 より複雑なプロジェクトの構造に従ってこれらを配置します。 2つのモジュールでプロジェクトを作成します。1つはドロップレットとロードバランサーを定義し、もう1つはDNSドメインレコードを設定します。 その後、2つの異なる環境の構成を記述します(devprod)、同じモジュールを呼び出します。

の作成 dns-records モジュール

前提条件の一部として、以下の下で初期プロジェクトを設定します。 terraform-reusability と作成しました droplet-lb 下の独自のサブディレクトリにあるモジュール modules. 次に、2番目のモジュールを設定します。 dns-records、変数、出力、およびリソース定義を含みます。 から terraform-reusability ディレクトリ、作成 dns-records 実行することによって:

  1. mkdir modules/dns-records

そこに移動します:

  1. cd modules/dns-records

このモジュールには、ドメインの定義と、後でロードバランサーを指すDNSレコードが含まれます。 最初に変数を定義します。これは、このモジュールが公開する入力になります。 それらをというファイルに保存します variables.tf. 編集用に作成します。

  1. nano variables.tf

次の変数定義を追加します。

terraform-reusability / modules / dns-records / variables.tf
variable "domain_name" {}
variable "ipv4_address" {}

ファイルを保存して閉じます。 ここで、ドメインとそれに付随するドメインを定義します ACNAME 名前の付いたファイルのレコード records.tf. 次のコマンドを実行して、編集用に作成して開きます。

  1. nano records.tf

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

terraform-reusability / modules / 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  = "@"
}

まず、DigitalOceanアカウントにドメイン名を追加します。 クラウドは、3つのDigitalOceanネームサーバーを次のように自動的に追加します NS 記録。 Terraformに指定するドメイン名は、DigitalOceanアカウントにまだ存在していない必要があります。存在していないと、インフラストラクチャの作成中にTerraformにエラーが表示されます。

次に、 A ドメインを記録し、ルーティングします( @ なので value 変数として指定されたIPアドレスに(サブドメインを含まない)実際のドメイン名を示します ipv4_address. モジュールのインスタンスを初期化すると、実際のIPアドレスが渡されます。 完全を期すために、 CNAME 次のレコードは、 www サブドメインも同じドメインを指している必要があります。 完了したら、ファイルを保存して閉じます。

次に、このモジュールの出力を定義します。 出力には、作成されたレコードのFQDN(完全修飾ドメイン名)が表示されます。 作成して開く outputs.tf 編集用:

  1. nano outputs.tf

次の行を追加します。

terraform-reusability / modules / 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. 編集のために作成して開きます。

  1. nano provider.tf

次の行を追加します。

terraform-再利用性/モジュール/dns-records/provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

完了したら、ファイルを保存して閉じます。 今では digitalocean プロバイダーが定義されている、 dns-records モジュールは機能的に完了しています。

さまざまな環境の作成

の現在の構造 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
├─ provider.tf

これまでのところ、プロジェクトには2つのモジュールがあります。作成したモジュール(dns-records)および前提条件の一部として作成したもの(droplet-lb).

さまざまな環境を促進するために、 devprod と呼ばれるディレクトリの下の環境設定ファイル environments、プロジェクトのルートに存在します。 どちらの環境も同じ2つのモジュールを呼び出しますが、パラメーター値は異なります。 これの利点は、モジュールが将来内部的に変更されたときに、渡す値を更新するだけでよいことです。

まず、次のコマンドを実行して、プロジェクトのルートに移動します。

  1. cd ../..

次に、を作成します devprod 下のディレクトリ environments 同時に:

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

The -p 引数の順序 mkdir 指定されたパスにすべてのディレクトリを作成します。

に移動します dev 最初にその環境を構成するので、ディレクトリ:

  1. cd environments/dev

コードをという名前のファイルに保存します main.tf、編集用に作成します。

  1. nano main.tf

次の行を追加します。

terraform-再利用性/環境/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
}

ここでは、2つのモジュールを呼び出して構成します。 droplet-lbdns-records、これにより、2つのドロップレットが作成されます。 それらの前面にはロードバランサーがあり、提供されたドメインのDNSレコードは、そのロードバランサーを指すように設定されています。 交換することを忘れないでください your_dev_domain に必要なドメイン名を使用して dev 環境を保存してから、ファイルを保存して閉じます。

次に、DigitalOceanプロバイダーを構成し、前提条件の一部として作成した個人用アクセストークンを受け入れることができるように、プロバイダーの変数を作成します。 と呼ばれる新しいファイルを開きます provider.tf、編集用:

  1. nano provider.tf

次の行を追加します。

terraform-再利用性/環境/dev/provider.tf
terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

variable "do_token" {}

provider "digitalocean" {
  token = var.do_token
}

このコードでは、 digitalocean プロバイダーが利用可能になり、 do_token そのインスタンスへの変数。 ファイルを保存して閉じます。

次のコマンドを実行して構成を初期化します。

  1. terraform init

次の出力が表示されます。

Output
Initializing modules... - dns in ../../modules/dns-records - droplets in ../../modules/droplet-lb Initializing the backend... Initializing provider plugins... - Finding digitalocean/digitalocean versions matching "~> 2.0"... - Installing digitalocean/digitalocean v2.10.1... - Installed digitalocean/digitalocean v2.10.1 (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/cli/plugins/signing.html Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. 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 環境は似ています。 次のコマンドを実行して、そのディレクトリに移動します。

  1. cd ../prod

作成して開く main.tf 編集用:

  1. 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 コードは、5つのドロップレットがデプロイされることです。 さらに、ドメイン名。これを自分のドメイン名に置き換える必要があります。 prod ドメイン名は異なります。 完了したら、ファイルを保存して閉じます。

次に、プロバイダー構成をからコピーします dev:

  1. cp ../dev/provider.tf .

この構成も初期化します。

  1. terraform init

このコマンドの出力は、前回実行したときと同じになります。

構成を計画して、Terraformが次のコマンドを実行して作成するリソースを確認できます。

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

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

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.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 = "@" } # 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. ...

これにより、ロードバランサーを使用して5つのドロップレットがデプロイされます。 また、 prod ロードバランサーを指す2つのDNSレコードで指定したドメイン。 の構成を計画してみることができます dev 環境も同様です。2つのドロップレットの展開が計画されていることに注意してください。

注:この構成を devprod 次のコマンドを使用する環境:

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

破棄するには、次のコマンドを実行して入力します yes プロンプトが表示されたら:

  1. terraform destroy -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
├─ provider.tf

追加は environments ディレクトリ。 devprod 環境。

このアプローチの利点は、モジュールへのさらなる変更がプロジェクトのすべての領域に自動的に伝播することです。 モジュール入力の可能なカスタマイズを除いて、このアプローチは反復的ではなく、展開環境全体でさえ、可能な限り再利用性を促進します。 全体として、これにより混乱が減り、バージョン管理システムを使用して変更を追跡できるようになります。

このチュートリアルの最後の2つのセクションでは、 depends_on メタ引数と templatefile 関数。

インフラストラクチャを順番に構築するための依存関係の宣言

アクションを計画している間、Terraformは既存の依存関係を自動的に識別し、それらを依存関係グラフに組み込みます。 検出できる主な依存関係は明確な参照です。 たとえば、モジュールの出力値が別のリソースのパラメーターに渡された場合です。 このシナリオでは、モジュールは最初に展開を完了して出力値を提供する必要があります。

Terraformが検出できない依存関係は隠されています。それらには、コードから推測できない副作用と相互参照があります。 この例は、オブジェクトが存在ではなく別のオブジェクトの動作に依存し、コードからその属性にアクセスしない場合です。 これを克服するために、あなたは使用することができます depends_on 明示的な方法で依存関係を手動で指定します。 テラフォーム以来 0.13、使用することもできます depends_on モジュール自体をデプロイする前に、リストされたリソースを強制的に完全にデプロイします。 使用することが可能です depends_on すべてのリソースタイプのメタ引数。 depends_on 指定されたリソースが依存する他のリソースのリストも受け入れます。

depends_on 他のリソースへの参照のリストを受け入れます。 その構文は次のようになります。

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

  # Parameters...
}

使用する必要があるのは depends_on ラストリゾートオプションとして。 リソースが依存する動作がすぐにはわからない場合があるため、使用する場合は、十分に文書化しておく必要があります。

このチュートリアルの前のステップでは、を使用して明示的な依存関係を指定していません。 depends_on、作成したリソースには、コードから推測できない副作用がないためです。 Terraformは、作成したコードから作成された参照を検出し、それに応じてリソースをデプロイするようにスケジュールします。

カスタマイズのためのテンプレートの使用

Terraformでは、テンプレート化とは、リソースに属性値を設定したり、文字列を作成したりする場合など、適切な場所で式の結果を置き換えることです。 これは、前の手順とチュートリアルの前提条件で使用して、ドロップレット名とその他のパラメーター値を動的に生成しました。

文字列に値を代入する場合、値は指定され、で囲まれます ${}. テンプレート置換は、作成されたリソースのカスタマイズを容易にするためにループでよく使用されます。 また、リソース属性の入力を置き換えることにより、モジュールのカスタマイズも可能です。

テラフォームは templatefile 関数。2つの引数を受け入れます。読み取るディスクからのファイルと、それらの値とペアになっている変数のマップです。 返される値は、プロジェクトを計画または適用するときに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 callは次の値を返します。

Output
192.168.0.1:80 192.168.1.1:80

この関数にはユースケースがありますが、それらは一般的ではありません。 たとえば、構成の一部が独自の形式で存在する必要があるが、残りの値に依存し、動的に生成する必要がある場合に使用できます。 ほとんどの場合、可能であれば、すべての構成パラメーターをTerraformコードで直接指定することをお勧めします。

結論

この記事では、Terraformプロジェクトの例でコードの再利用を最大化しました。 主な方法は、頻繁に使用する機能と構成をカスタマイズ可能なモジュールとしてパッケージ化し、必要なときにいつでも使用することです。 そうすることで、基礎となるコードを複製せず(エラーが発生しやすくなる可能性があります)、変更を導入するために必要なのはモジュールの変更だけであるため、所要時間を短縮できます。

自分のモジュールに限定されません。 これまで見てきたように、 Terraform Registry は、プロジェクトに組み込むことができるサードパーティのモジュールとプロバイダーを提供します。

このチュートリアルは、Terraformシリーズでインフラストラクチャを管理する方法の一部です。 このシリーズでは、Terraformの初めてのインストールから複雑なプロジェクトの管理まで、Terraformの多くのトピックを取り上げています。