おれのためのterraformの概念まとめ

仕事で時々terraformを書くんですが割と雰囲気で触ってるので自分の中で怪しい概念とかを勉強しなおしました

基本的には公式ドキュメントの翻訳メモです

英語のドキュメント読みながら書いたけどもしかしたらおれが探してないだけで日本語対応してるのかも

Terraformについて

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.

Introduction - Terraform by HashiCorp

terraformはインフラをコード管理するためのツール

コードで管理できるのでgitによるversion管理やレビューも行えるし、同じ構成のインフラを作りたい時は既存のものを使い回すのも簡単

AWS、Azure、GCPはもちろん、多くのサービスに対応している

terraformではそれらのサービスをproviderと呼ぶ

ざっと数えた感じ使用可能なproviderの数は100は余裕で超えてそう

www.terraform.io

以下は全部AWSについて書いていく (AWSしか使ったことない)

resourceとdata source

resource

resourceは名前の通り、VPCやEC2インスタンスなどのAWSリソースを定義する

以下のtfファイルを作成しterraform applyすると、実際にAWS上にEC2インスタンスが作成される

resource "aws_instance" "web" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

data source

data sourceはterraform内で利用可能なデータを定義する

実際にAWS上にあるデータをfetchしてきて使うことも可能

リソース自体はterraform管理したくないけど、そのリソースを参照して新たなAWSリソースをterraformで定義したい時などに使う

以下のtfファイルを作成しterraform applyすると、terraform内で使えるamiのデータが作成されるがAWSリソースは作成されない

その後このdata resourceのidを使ってEC2インスタンスを立てるなどして利用する

data "aws_ami" "example" {
  most_recent = true

  owners = ["self"]
  tags = {
    Name   = "app-server"
    Tested = "true"
  }
}

terraformはdata sourceのREADのみ可能

そもそもAWSリソースとして存在しないため、resourceのようにCREATE、UPDATE、DELETEを行うことはできない

module

A module is a container for multiple resources that are used together.

Modules - Configuration Language - Terraform by HashiCorp

moduleは一緒に使われるリソースをまとめたコンテナ

全てのterraform configurationはroot moduleというmoduleを持っていて、その中にはmain working directory (おそらくterraform initをしたdirectory)にあるtfファイルに定義されたリソースが含まれている

moduleの呼び出しは、moduleの中身のリソースをconfigurationに追加することを意味する

moduleを呼び出す際には、呼び出すmoduleのpathを指定するsourceと、呼び出しの際に使用するinput valueを宣言する

以下のtfファイルは、./app-cluster配下のterraformリソースをserversという変数と共に呼び出す

module "servers" {
  source = "./app-cluster"

  servers = 5
}

module内のリソースはカプセルかされているため、リソースの要素 (id, nameなど)に直接アクセスすることはできない

アクセスするためには、module内でoutput valueを宣言しておく必要がある

以下のtfファイルでは/app-cluster/ec2.tfで定義したEC2インスタンスのidをinstance_idという名前でoutput valueとして宣言している

メインのworking directoryにあるelb.tfファイルでは、module.servers.instance_idという呼び方でoutputしたinstance_idにアクセスしている

#./app-cluster/ec2.tf
resource "aws_instance" "web" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

output "instanse_id" {
  value = aws_instance.web.id
}

#./elb.tf
resource "aws_elb" "example" {
  # ...

  instances = module.servers.instance_id
}

remote state

terraformでは、localにterraform.tfstateというファイルを作成し、そこに実際のprovider上のリソースの状態を記録している

tfstateとworking directoryを比較することで、新たに作成されたリソースや変更の加わったリソースを検知し、providerのAPIをcallしてその差分を反映している

一人で開発している分にはいいが、チーム開発していると誰かが新たにリソースを作ったらちゃんと自分のtfstateにも反映しないと誤って消してしまうことがあるのでなかなか面倒

remote stateは、AWSであればS3などのremote datastoreにtfstateを置いておくことができる

そのため自分のローカルのtfstateを最新にしなくてもterraform planterraform applyの際にremote stateを参照するだけで意図しない変更を防ぐことができる

また、remote stateでは変数のoutputをすることができるため、workspaceをまたいだ変数の参照を行うことができる

例えば社内のインフラチームがremote stateを利用した上でVPCやSubnetの設定を行い、VPC ID等をoutputしておく

社内の別チームが、会社のVPC内に新たにリソースを作成したい場合はインフラチームのworkspaceのremote stateからVPC IDを参照して開発を行うことができる

data "terraform_remote_state" "vpc" {
  backend = "remote"

  config = {
    organization = "hashicorp"
    workspaces = {
      name = "vpc-prod"
    }
  }
}

# Terraform >= 0.12
resource "aws_instance" "foo" {
  # ...
  subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
}

ちなみにremote state側でのoutputと、resource側での参照を一度にapplyすることはできないらしい

まとめ

基本的には作りたいリソース、行いたい変更を宣言的に記述していくだけなので難しくはない (はず)

ハマりポイントとしては変数の渡し方や参照方法でつまづくことが個人的には多かったのできちんと仕組みを理解してコードを書けばいける (はず)

tfstateがわかればremote stateも怖くない (はず)