後ろを向いて後退します

これって前に進んでいることになりませんか?

Terraformのリソースの書き方(resource, data source, variable, output)

一人Terraform Advent Calendar

この記事は一人Terraform Advent Calendarの5日目の記事です。

adventar.org

Terraformのリソースの書き方

TerraformのレシピはHashiCorp Configuration Language(HCL)の文法で記述されます。

www.terraform.io

この記事では、

  • resource
  • data source
  • variable
  • output

の4つについて書き方と使い方を解説します。

resource

www.terraform.io

インフラのレシピを構成するコンポーネントで、記述されたresourceの定義通りにTerraformはインフラの構成に変更を加えます。resourceを新しく定義すると作成、一部を変えると変更(もしくは削除→再作成)、定義を消すと削除の操作がレシピで定義されている全てのインフラ上のリソースに対してそれぞれ行われます。

resource "aws_s3_bucket" "my-bucket" {
  bucket        = "my-bucket-01"
  region        = "ap-northeast-1"

  versioning {
    enabled    = false
    mfa_delete = false
  }

  force_destroy = true
}

resource "aws_s3_bucket_policy" "my-bucket-policy" {
  bucket = "${aws_s3_bucket.my-bucket.id}"

  policy = <<POLICY
{
  "Version":"2012-10-17",
  "Statement": [
    {
      "Effect":"Deny",
      "Principal":"*",
      "Action":"s3:*",
      "Resource": [
        "arn:aws:s3:::${aws_s3_bucket.my-bucket.id}",
        "arn:aws:s3:::${aws_s3_bucket.my-bucket.id}/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:username": [
            "my-bucket-user"
          ]
        }
      }
    }
  ]
}
POLICY
}

上のコードが書かれたレシピを適用するとmy-bucket-01というS3バケットと、それにアタッチされるS3バケットポリシーが作成されます。

resourceはresource "{resource_type}" "{resource_name}"の形で定義されます。{resource_type}には上の例のaws_s3_bucket以外にも、GCPMicrosoft Azure、OpenStackなどTerraformが提供する様々なresourceを指定することができます。同様に{resource_name}には、そのresource定義で作成したリソースをTerraformで管理するために名前を付与します。ここで定義した名前は同じモジュール、resourceのタイプの中で一意になる必要がありますが、異なっていれば同じ名前を付与することも可能です。ただし、ここで定義した名前は実行時に表示されるトレース内容でそのまま使われるのでわかりにくい名前をつけないほうが良いでしょう。

resourceの本体はargument = valueの形で列挙して定義します。HCLはJSON互換の言語なので、ここで定義されたresourceはTerraformがJSONとしてパースして、各クラウドプロバイダが提供するREST APIなどにリクエストのBODYとして送信します。resourceのタイプによって必要なargumentの型や取りうる値はTerraformの公式ドキュメントを参照すれば見ることができます。

aws_s3_bucket_policyのresource定義ではヒアドキュメント記法が使用されています。

argument = <<DOC
...
DOC

上記のような形で、改行を含む文字列をそのままargumentに渡すことができます。JSONのようにPrettifiedな文字列で書けたほうが嬉しいものはヒアドキュメントを使うか、別の記事で解説するtemplate_fileを使った方法が良いでしょう。

data source

www.terraform.io

data sourceはresourceと似たような振る舞いをしますが、唯一違うのはレシピ上の定義の変更がクラウド上のリソースに変更を及ぼさないことです。data sourceの定義はクラウド上にリソースを作成するのではなく、指定したargumentをもとにクラウド上のリソースの実体の情報を取得して他のresourceと同じように扱います。

data "aws_vpc" "selected" {
  id = "${var.vpc_id}"
}

data sourceの定義でargumentに渡すIDは既存のリソースのIDです。data sourceが定義された状態でrefreshコマンドを実行するとstateにその指定されたリソースの情報が反映されます。data sourceの定義に変更を加えた場合には、クラウド上のリソースに変更が加えられるのではなく、そのdata sourceが参照するリソースが指定されたものに入れ替わります。

基本的には、Terraformで管理できない範囲のリソースや意図的に管理から外しているリソースを見かけ上Terraformの管理下にあるように扱うために用います。

variable

www.terraform.io

variable "my-variable" {
  type = "string"
  default = "variable is not set"
  description.= "This is an example of variable declaration."
}

variableは、モジュール外部からの値の注入を許容した上でその値をモジュール内のレシピから参照することができます。レシピではinteger型やboolean型の値が使えますが、varaibleのtypeとして使用可能なのは、

  • string
  • list
  • map

の3つのみです。typeが未定義のときはdefaultの値を見て自動的に型が判別されますが、defaultも未定義のときにはstring型として扱われます。

定義した変数はvar.my-variableのようにすると参照することができますが、variableの定義がされているモジュール内でしか参照ができないことに注意してください。

my-module
├── my-sub-module
│   ├── main.tf
│   └── variables.tf
├── main.tf
└── variables.tf

上のようなディレクトリ構造では、my-moduleの下のmain.tfでは同じ階層にあるvariables.tfで定義されているvariableは使えますが、my-sub-moduleの中で使うことはできません。my-sub-moduleで同じ値を使う場合には、my-sub-moduleの下のvariables.tfなどで改めてvariableを定義して値を渡す必要があります。

output

www.terraform.io

同じモジュール内のresource同士は、定義されたargumentやattributeを互いに参照することができます。しかしそれがモジュールをまたいだ場合には、明示的にoutputを定義して他のモジュールに渡せるようにする必要があります。

resource "aws_s3_bucket" "my-bucket" {
  bucket        = "my-bucket-01"
  region        = "ap-northeast-1"

  versioning {
    enabled    = false
    mfa_delete = false
  }

  force_destroy = true
}

output "my-bucket-id" {
  value = "${aws_s3_bucket.my-bucket.id}"
  description = "my-bucket id"
}

output "my-bucket-arn" {
  value = "${aws_s3_bucket.my-bucket.arn}"
  description = "my-bucket arn"
}

定義はvariableのものと似ていますが、typeは実際の値を見て動的に定義されます。valueにセットできる値は、resourceのargumentやattribute以外にもvariableそのものやdata sourceなどinterpolation syntaxで展開できるものなら何でもOKです。モジュールのvariableに渡す値が他のモジュールのoutputに依存している場合は、その依存が適切に解決されるような順序でモジュールが作成されますが、outputを相互参照している場合はモジュールの作成時に正しい値がvariableにセットされないので注意してください。

また、別の記事で解説するオブジェクトストレージを利用したstate管理でterraform_remote_stateを利用する場合にも、同様にoutputを定義して参照可能な値を明示する必要があります。

まとめ

  • resource ... レシピを構成するコンポーネント
  • data source ... Terraform管理外のリソース
  • variable ... モジュール内の変数
  • output ... モジュールの出力