Atsushi2022の日記

データエンジニアリングに関連する記事を投稿してます

読書メモ 〜 実践Terraform

概要

  • 「実践Terraform」を読んで、忘れたくないところをかいつまんでメモしておく
  • 主に1〜3章の内容と17章以降の内容をメモする
  • for文については言及なし。for_eachについてもそれほど詳しくないので別途調べる必要あり
  • 公式ドキュメントはここ Overview - Configuration Language | Terraform by HashiCorp

バージョン

2019年5月にリリースされたTerraform 0.12は、0.11以前のバージョンと互換性がないので要注意。

コマンド

  • terraform init
  • terraform plan
  • terraform apply
  • terraform destroy
  • terraform fmt -recursive
  • terraform validate

 記号

  • 「+」 : 追加されるリソース
  • 「-」 :削除されるリソース
  • 「-/+」:削除後に再度追加になるリソース

ファイル

  • tfstateファイル:terraform planを一度でも実行すると、terraform.tfstateファイルが作成される。現在のリソースの状態を記録。tfstateファイルとHCLコードに差分があれば差分のみ変更する。

variable

  • 変数は、variableとlocalの2種類がある
  • terraform apply -var 'variable名'という形で、実行時に変数を上書き可能
  • デフォルト値を設定することも可能
variable "example_instance_type" {
  default = "t3.micro"
}

resource "aws_instance" "example" {
  ami = "ami0c3fd0f5d33134a76"
  instance_type = var.example_instance_type
}

変数の型

  • 全7種類
  • string, number, bool, list, tuple, map, object
  • 以下に、list、object、tupleの例を記載
variable "ports" {
  type = list(number)
}
variable "person" {
  type = object({ name=string, age=number})
}
variable "person" {
  type = tuple([string, number])
}

local

  • 上述の通り、変数はvariableとlocalの2種類がある
  • 実行時に上書き不可
  • デフォルト値 設定不可
locals {
  example_instance_type = "t3.micro"
}

resource "aws_instance" "example" {
  ami = "ami0c3fd0f5d33134a76"
  instance_type = local.example_instance_type
}

output

  • terraform apply実行後に、出力される値を定義
  • 以下の例だと、example_instance_id = i02bd77505ab68856fというのがディスプレイに出力される
resource "aws_instance" "example" {
  ami = "ami0c3fd0f5d33134a76"
  instance_type = "t3.micro"
}

output "example_instance_id" {
  value = aws_instance.example.id
}

data

  • AWS AMIイメージ等の外部データを参照
  • 以下の例では、イメージ名とイメージの状態(available)でフィルタし、most_recentで最新のAMIを取得している
data "aws_ami" "recent_amazon_linux_2" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name = "name"
    values = ["amzn2-ami-hvm-2.0.????????-x86_64-gp2"]
  }

  filter {
    name = "state"
    values = ["available"]
  }
}

resource "aws_instance" "example" {
  ami= data.aws_ami.recent_amazon_linux_2.image_id
  instance_type = "t3.micro"
}

provider

  • AWSGCPなどのAPIの違いを吸収してくれる
  • terraform initによりプロバイダのバイナリファイルがダウンロードされる
  • 暗黙的に検出もしてくれるが、明示した方が管理上好ましい
  • デフォルトのリージョンなども指定可能
provider "aws" {
  region = "apnortheast1"
}

リスト []

  • [ ]によりリストを渡すことができる
  • 以下の例だと、ingressとegressのセキュリティグループ2つをリストとして、[aws_security_group.example_ec2.id]で渡している
resource "aws_security_group" "example_ec2" {
  name = "example-ec2

  ingress { 
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1" 
    cidr_blocks = ["0.0.0.0/0"]
  }
}


resource "aws_instance" "example"{
  ami = "ami0c3fd0f5d33134a76"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.example_ec2.id]
}

組み込み関数

  • 文字列操作などのよくある処理が組み込み関数として提供されている
  • file('ファイルのパス')で、外部ファイルを読み込むことができる

モジュール

module "web_server" {
  source        = "./http_server"
  instance_type = "t3.micro"
}

./http_serverディレクトリ配下にmain.tfを作成して次のコードを実装する。モジュール呼び出し側で指定したinstance_typeを引数にとって、モジュールからインスタンスを作成している。

variable "instance_type" {}

resource "aws_instance" "default" {
  ami                    = "ami-0c3fd0f5d33134a76"
  instance_type          = var.instance_type
}

三項演算子

  • 三項演算子で条件分岐を実現できる。
  • 以下の例では、terraform plan -var 'env=prod'とすると、instance_typeはm5.largeになる。envがprod以外の値だと、instance_typeはt3.microになる。
variable "env" {}

resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = var.env == "prod" ? "m5.large" : "t3.micro"
}

count

  • 複数のリソースをいっぺんに作成するのに利用できる。
  • count = 3の場合、count.indexの値は{0, 1, 2}となる。
  • 以下の例では、3つのVPC(10.0.0.0/16、10.1.0.0/16、10.2.0.0/16)が作成される。
resource "aws_vpc" "examples" {
  count      = 3
  cidr_block = "10.${count.index}.0.0/16"
}

randomプロバイダ

  • ランダム文字列を自動で生成できる
  • 文字列長と特殊文字の有無を指定可能
  • 以下の例だと、32文字のランダム文字列(特殊文字なし)を生成している
  • random_string.password.resultに値が返ってくる
provider "random" {}

resource "random_string" "password" {
  length  = 32
  special = false
}

dynamicとfor_each

  • dynamicにより動的にリソース生成可能
  • 以下の例だと、for_eachにセットした値は、ingress.value、つまり<動的ブロック名>.valueで取り出している
variable "ports" {
  type = list(number)
}

resource "aws_security_group" "default" {
  name = "simple-sg"

  dynamic "ingress" {
    for_each = var.ports

    content {
      from_port   = each.value
      to_port     = ins.value
      cidr_blocks = ["0.0.0.0/0"]
      protocol    = "tcp"
    }
  }
}

ベストプラクティス

  • Terraformのバージョンは固定すべき
  • providerのバージョンも固定すべき(パブリッククラウドは進化が早く、環境差異が出やすい)
  • 削除操作を抑止
  • でも、.tfファイルからリソース定義を削除してterraform applyするとリソースが削除されてまう(※マジで要注意!!!)
  • コードフォーマットすべし
  • コードのバリデーションをすべし(サブディレクトリ配下まで実行するにはコマンドに工夫要)
  • TFLintで不正なコードを検出すべし(tflint --deep
  • 暗黙的な依存関係を把握し、depends_onで依存関係を定義する

構造化、リソース参照パターン(22〜23章)

手間なのでスキップするが、大事なので忘れたら読み返そう