woshidan's blog

そんなことよりコードにダイブ。

terraformのmoduleの基本的な使い方を試しました

あれもこれもやらなくては~と思っていたらどれにも手がついておらず結構ガッカリですが、ひとまず今日はterraformのmoduleの基本についてまとめておきます。

TL;DR

  • terraformがビルドに使うのはカレントディレクトリにある.tfファイル
  • moduleで他のディレクトリや他のサーバにある設定をコピーしてきて再利用する
    • moduleのsourceで指定するコピー元がローカルファイルでも terraform plan の前に terraform init 必要
  • variableを利用してmoduleを書いた側で一部の変数を代入する

terraformがビルドに使うのはカレントディレクトリにある.tfファイル

terraformが terraform planterraform apply した時に利用する設定ファイル(.tf)はterraformのコマンドを実行したディレクトリとなります。

たとえば、

$ tree .

.
├── foo
│   └── test_3.tf
├── test.tf
└── test_2.tf
// test.tf
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}
 
resource "aws_instance" "hoge" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}
// test_2.tf
resource "aws_instance" "fuga" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}
// test_3.tf
resource "aws_instance" "bar" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}

というようなディレクトリ構成のとき、カレントディレクトリで 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.


------------------------------------------------------------------------

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:

  + aws_instance.fuga
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + aws_instance.hoge
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

のように aws_instance.fuga, aws_instance.hoge の2つだけです。

他のディレクトリのファイルの設定を利用する場合は、次に説明する module を利用します。

moduleで他のディレクトリや他のサーバにある設定をコピーしてきて再利用する

moduleを使うと、

module "test" {
  source  = "./modules/test"
}

のように、参照元の設定ファイルがあるディレクトリを指定して、参照元に書かれた設定を他のディレクトリで利用することができます。

たとえば、先ほどの設定ファイルに対し、

// test.tf
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}
 
resource "aws_instance" "hoge" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"
}

module "foo" {
  source = "./foo"
}

のように module の部分を追加すると、terraform plan の結果に foo/test_3.tf に書かれていた aws_instance.barmodule.foo.aws_instance.bar として登場するようになります。

$ 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.


------------------------------------------------------------------------

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:

  + aws_instance.fuga
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + aws_instance.hoge
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

  + module.foo.aws_instance.bar
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...

ここで、 test_2.tf にも同じ設定を追記すると

$ terraform plan 

Error: Failed to load root config module: Error loading modules: module foo: duplicated. module names must be unique

となります。

設定側で module に対して一意になるように名前をつけるのは、おそらく module が、どこかにある設定を利用する側の都合でコピーしてきたり、コピーしてきた設定に一部変数を代入して使うためのもののようだからと思います。

先ほどまで試していたローカルファイルシステム上の他のディレクトリは、 modulesource として指定できるデータの取得元の一部で、 https://www.terraform.io/docs/modules/sources.html によると、他には

  • Terraform Registry (Docker HubみたいにTerraformの設定をアップロードできるサイト)
  • GitHub
  • BitBucket
  • 他のgitリポジトリ
  • 特定のサイトのURL
  • S3のバケット

があります。

そういえば、この取得元の一覧を見ていると module で外部ソースを指定した時は git fetch ではないんですが、何かコマンドを打たなければいけないような気がします。

じつは、新しく module を設定に追加するとその module のソースがローカルファイルでも一度 terraform init を打つ必要があります。

terraform init を忘れて terraform plan を実行したときのログと、その後 terraform init を打って、./foo 以下から module foo の設定ファイルを取得している様子のログが以下となります。

$ terraform plan 

Error: Failed to load root config module: Error loading modules: module foo: not found, may need to run 'terraform init'

$ terraform init
Initializing modules...
- module.foo
  Getting source "./foo"

Initializing provider plugins...

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

variableを利用してmoduleを書いた側で一部の変数を代入する

さて、moduleの使い方について「module がどこかにある設定を利用する側の都合でコピーしてきたり、コピーしてきた設定に一部変数を代入して使うためのもののようだ」と書きましたが、「コピーしてきた設定に一部変数を代入して使うためのもののようだ」の方についてももう少し詳しく見ておくことにします。

variables については今度 local と並べて使える型と使う場面の違いについてまとめようかな、と思いますが、terraformには

variable "hogehoge" {}

のように変数があることを宣言して、設定ファイル中で

${var.hogehoge}

のように利用できる変数 variables があります。各 variables の変数の値は、

  • 設定ファイル中で記載したり
  • 別の設定ファイルに抜き出してコマンドの実行時に -var-file=foo のように変数が書かれたファイルを指定したり
  • コマンドの実行時に -var 'foo=bar' のように一つずつ設定したり

できます。

設定ファイル中で記載できる箇所には module の中もあり、これを利用して

  • modulesource に指定する設定ファイル: 可変にしたい場所は ${var.env} のように variable を利用して書いておく
  • module を定義する側: module のブロックの中で、 source の設定ファイルに書かれた variable の値を設定する

のようにすると、production, staging環境で一部だけ設定が違う、というような場合の記述が便利にできます。

簡単なサンプルを書いてためしておきましょう。

$ tree .
.
├── modules
│   └── ec2.tf
├── production
│   └── main.tf
└── staging
    └── main.tf
// ./modules/ec2.tf
// stagingとproductionでAWSアカウントを分けてないとする
provider "aws" {
    access_key = "HOGEHOGE"
    secret_key = "FUGAFUGA"
    region = "ap-northeast-1"
}

variable "environment" {}
 
resource "aws_instance" "app" {
    ami = "ami-29160d47"
    instance_type = "t2.nano"

    tags {
       Name = "app-${var.environment}"
       env  = "${var.environment}"
    }
}
// ./production/main.tf
module "app_production" {
    source = "./../modules"
    environment = "production"
}
// ./staing/main.tf
module "app_staging" {
    source = "./../modules"
    environment = "staging"
}
production$ terraform init
production$ terraform plan
...

  + module.app_production.aws_instance.app
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...
      instance_type:                "t2.nano"
      ...
      tags.Name:                    "app-production"
      tags.env:                     "production"
staging$ terraform init
staging$ terraform plan
...

  + module.app_staging.aws_instance.app
      id:                           <computed>
      ami:                          "ami-29160d47"
      arn:                          <computed>
      ...
      instance_type:                "t2.nano"
      ...
      tags.Name:                    "app-staging"
      tags.env:                     "staging"

現場からは以上です。

参考