Atsushi2022の日記

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

メモ ~ Udemy HashiCorp Certified: Terraform Associate 2022

概要

Udemyで受講した「HashiCorp Certified: Terraform Associate 2022」のメモ。

すごいわかりやすいので、Terraform初心者の方はUdemyで視聴してみると良いと思います。

Terraformバージョン

初めてTerraformをローカルPCで実行させるようとしたとき、Windows PCではTerraformのウェブサイトからバイナリ実行ファイル(terraform.exe)をダウンロードしてくる。ウェブサイトでは、最新の安定バージョンと2~3世代前のバージョンがダウンロードできる。
それよりも前のバージョンはGithubからダウンロードできる。

Terraformバージョンの指定

terraformブロック内で、terraform.exeのバージョンを指定することができる。

terraform {
  required_version = "> 1.1.0"
}

バージョンの指定に利用できる記号はこちらの通り。

もし実行するterraform.exeのバージョンが指定した条件を満たさない場合はUnsupported Terraform Core versionエラーを返す。

プロバイダーのバージョン制約・指定

AWSやAzure、GCPといったパブリッククラウド毎(プロバイダーと呼ぶ)に、プラグインをダウンロードしてくる。 AWS上にプロビジョニングする場合には、AWSのプロバイダを指定する。

ちなみにProviderブロックは必須なわけではないことに注意。Providerを利用する場合でもProviderブロックを定義しなくて大丈夫。TerraformはProviderブロックが空とみなして、プラグインを取得する。ただ、ベストプラクティスとして、Providerを利用する場合は明示的にProviderブロックを設定すべき。

Terraform assumes an empty default configuration for any provider that is not explicitly configured.

Provider Configuration - Configuration Language | Terraform by HashiCorp

terraform {
  required_version = "> 1.1.0"
}

provider "aws" {
  region = "us-east-1"
}

プロバイダーのバージョン制約をterraformブロック内にrequired_providersとして記載することができる。
また、providerブロック内にダウンロードしてくるプロバイダーのバージョンをversionとして指定できる。

terraformブロック

  • required_version:terraform.exeのバージョン制約
  • required_providers:プロバイダーのバージョン制約

providerブロック

  • version:プロバイダーのバージョン指定
terraform {
  required_version = "> 1.1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.0"
    }
  }
}

provider "aws" {
  region  = "us-east-1"
  version = ">=2.10,<=2.30"
}

もし、実行するプロバイダープラグインのバージョンが指定した条件を満たさない場合には、Failed to query available provider packagesエラーを返す。

注意

Terraform 0.13以降、providerブロック内のversion引数は非推奨となった

The version argument in provider configurations is deprecated. In Terraform 0.13 and later, always declare provider version constraints in the required_providers block. The version argument will be removed in a future version of Terraform.

.terraform.lock.hcl ファイル

terraform initコマンドでプロバイダープラグインがダウンロードされる。一度プラグインがダウンロードされると.terraform.lock.hclファイルでプロバイダープラグインのバージョンがロックされる。プラグインのバージョンを変更したい場合には、.terraform.lock.hclファイルを削除する必要がある。

# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "2.30.0"
  constraints = "~> 2.0, >= 2.10.0, <= 2.30.0"
  hashes = [
    "h1:fSYy...",
    ...,
    ...,
  ]
}

Terraformバージョン毎の違い

Terraform v0.13の前後で大きな違いがある。

  • Terraform v0.13以降

    Hashicorpが管理しているプロバイダーについては、required_providersブロックは必須ではないが、required_providersブロックを書くのが推奨される

      terraform {
        required_providers {
          github = {
            source = "integrations/github"
            version = "4.3.2"
          }
        }
      }
    
      provider "github" {
        token = "8c6f0e838e998844a8bb32b0050a7dee6a31a4df"
      }
    
  • Terraform v0.12以前

    required_providersブロックは記載しない

      provider "github" {
        token = "8c6f0e838e998844a8bb32b0050a7dee6a31a4df"
      }
    

AWSプロバイダの認証設定

AWS Providerの場合、認証方法にはいろんな方法がある。

  1. Parameters in the provider configuration
  2. Environment variables
  3. Shared credentials files
  4. Shared configuration files
  5. Container credentials
  6. Instance profile credentials and region

providerブロック内にクレデンシャルを記述する場合は次のようになる、

provider "aws" {
  region     = "us-west-2"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}

Variable

.tfコード内に変数を定義できる。
必須ではないが型制約は記述すること。変数に型制約を設けることで、誤ったデータ型の値を代入してしまうという ミスを防げる。
descriptionも記述すること。

variable "instance_name" {
  description = "Value of the Name tag for the EC2 instance"
  type        = string
  default     = "AppServerInstance"
}

データ型

データ型には次の種類がある。

  • string
  • number
  • bool
  • list
  • map
  • set
  • object
  • tuple

Terraformでは、number型とbool型は自動的にstring型に変換される。 string型からnumber型、bool型にも自動で変換される。

  • number/bool ⇔ string

Variableへの代入

こちらの通り、変数の代入方法は次の5つの方法がある。
本番環境ではterraform.tfvarsファイルに変数値を設定するのがベストらしい。

  • Terraform Cloud workspace (口述)
  • -var command line option
    • terraform apply -var="image_id=ami-abc123"という形式で指定して実行する
  • Variable definitions (.tfvars) files
    • デフォルトではterraform.tfvarsを探して読み込む。カスタムファイル名を利用したい場合はterraform apply -var-file="custom_name.tfvars"とファイル名を指定して実行する
  • Environment variables
    • 環境変数として設定しておく。
    • export TF_VAR_image_id='ami-abc123'
  • Variable Defaults json variable "image_id" { default = "ami-abc123" }

変数の読込み優先順位

次の優先順位で変数が読み込まれる。
同じ変数に複数回値が代入される場合には、最後に読み込んだ値で上書きされる。

  • Environment variables
  • The terraform.tfvars file, if present.
  • The terraform.tfvars.json file, if present.
  • Any .auto.tfvars or .auto.tfvars.json files, processed in lexical order of their filenames.
  • Any -var and -var-file options on the command line, in the order they are provided. (This includes variables set by a Terraform Cloud workspace.)

Localについて

localを利用しすぎると可読性が低くなり、メンテナンス性も下がるので、利用しすぎには要注意。

locals {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

resource "aws_instance" "server" {
  ami           = local.ami
  instance_type = local.instance_type
}

Countについて

リソースの単純な複製

resourceブロック内で、count = 3とすると単純に同じリソースが3つ作られる。

resource "aws_instance" "server" {
  count = 3 

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}

ListとCountを組み合わせた利用法

count.indexとリスト型の変数を組み合わせることで、異なる名称でリソースを複製することができる。

variable "name" {
  default = ["dev", "stg", "prd"]

}

resource "aws_iam_user" "lb"{
  count = length(var.name)
  name = var.name[count.index]
}

参考演算子とCountを組み合わせた利用法

countと三項演算子を組み合わせることでリソースを生成するかどうかの判定を実現する。 以下により、変数がtrueであればリソースを生成し、falseであれば生成しない。

variable creation {
  type    = bool
  default = true
}

resource "aws_instance" "server" {
  count = var.creation == true ? 1 : 0

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
}
  • 三項演算子とは

    <条件式> ? <条件が真の場合の値> : <条件が偽の場合の値>

Dynamicブロックについて

dynamicブロックにより、ブロックを動的に複製することができる。

for_eachにリスト型の変数をセットし、<dynamicブロック名>.valueでリスト型変数の値を取得してブロックを複製する。

variable "sg_ports" {
  type        = list(number)
  description = "list of ingress ports"
  default     = [8200, 8201,8300, 9200, 9500]
}

resource "aws_security_group" "dynamicsg" {
  name        = "dynamic-sg"
  description = "Ingress for Vault"

  dynamic "ingress" {
    for_each = var.sg_ports
    iterator = port
    content {
      from_port   = port.value
      to_port     = port.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

for_eachについて

countでArgument Valueが異なるリソースを複数作成しようとした場合、元となるリストの要素順序が変更されると影響がでてしまう可能性がある。そうした場合にはfor_eachを使う。

同じものを複製するなら、countを利用する。Argument Value一部でも異なる場合には、for_eachを利用する。

for_eachとtosetを組み合わせることで、インデックスと要素を同じ値にすることができる。 for_eachに集合のkeyだけを格納できる。その場合、each.keyでkey値を取り出すことができる。

resource "aws_iam_user" "iam" {
  for_each = toset( ["user-01","user-02", "user-03"] )
  name     = each.key
}

for_eachにmap型を格納することで、keyだけでなくvalueも格納できる。key値はeach.keyで取り出す。value値はeach.valueで取り出す。for_eachにmap型を格納した場合も、リソースを複数生成できる。

resource "aws_instance" "myec2" {
  ami = "ami-0cea098ed2ac54925"
  for_each  = {
      small = "t2.micro"
      normal = "t2.medium"
   }
  instance_type    = each.value
  key_name         = each.key
  tags =  {
   Name = each.key
  }
}

Provisionerについて

remote-exec、local-execといった、色んな種類のprovisionerが存在する。

Provisionerは最後の手段。一般的にそれ以外に良い方法がある。

remote-exec

リソース作成後にリモートリソース側でスクリプトを呼び出し実行する。例えば、作成したEC2にSSH接続してスクリプトを実行できる。Ansibleの代わりになる。

SSHとWinRMで接続が可能

resource "aws_instance" "web" {
  # ...

  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = self.public_ip
  }

  provisioner "remote-exec" {
    on_failure = continue
    inline = [
      "puppet apply",
      "consul join ${aws_instance.web.private_ip}",
    ]
  }
}

selfオブジェクト

ブロック内の式はprovisioner、親リソースを名前で参照できない。代わりに、特別なselfオブジェクトを使用できる。

selfオブジェクトはプロビジョナーの親リソースを表し、そのリソースのすべての属性を持っている。例えば、aws_instance.public_ipの属性を参照するためにself.public_ip使用します。

local-exec

リソース作成後に、ローカル側で実行可能なファイルを呼び出す。例えば、ローカルPCに配置したAnsibleプレイブックを実行できる。

resource "aws_instance" "web" {
  # ...

  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }
}

on_failure パラメータ

on_failure = continueとすると、もしProvisionerの実行に失敗してもProvisionerの処理が正常に完了したとみなす。on_failure = failあるいはon_failureパラメータを設定しない状態で、もしProvisionerの実行に失敗した場合はリソースがtainted状態となり、次回のterraform apply実行時に再作成される。

destroy-timeプロビジョナー

destroy-timeプロビジョナー:
Provisionerブロック内にwhen = destroyと記載するとdestroy-timeプロビジョナーになる。destroy-timeプロビジョナーはリソース削除(=terraform destroy)時にのみ実行される。

creation-timeプロビジョナー:
デフォルトのProvisionerはcreation-timeプロビジョナーである。明示的に記載するにはwhen = creationとする。リソース作成時にのみ実行される。Provisionerの実行に失敗した場合には、.tfstateファイルにtaintedとマークされる。taintedになっているため、再度terraform applyした際にいったんリソースが削除されてその後にリソースが再作成される。

null_resource

null_resourceを使うことで、null_resourceでGoogleサーバが起動していることが確認できた場合に、リソースを作成するといったフローが実現できる。

null_resource内では、プロビジョナーによりcurlコマンドを実行し、Googleサーバが起動していることを確認している。

resource "aws_eip" "lb" {
  vpc      = true
  depends_on = [null_resource.health_check]
}

resource "null_resource" "health_check" {

 provisioner "local-exec" {

    command = "curl https://google.com"
  }
}

triggersをnull_resourceブロック内にパラメータとして設定できる。

triggersにはmap型のデータを格納できる。格納したデータが変更された場合に、プロビジョナーが再実行され、null_resourceが再作成される。

以下の例では、aws_eip.lbのcountが0になっている。つまり、EIPは作成されない。このコードでterraform applyを実行すると、null_resource.ip_checkのみ作成される。

次にcountの値を0から1に変更してEIPが作成されるよう変更する。null_resource.ip_checkはすでに作成済みのため、triggersがなければ再作成されない。しかし、triggersがあることでnull_resource.ip_checkが再作成され、Provisionerも再実行される。

resource "aws_eip" "lb" {
  vpc      = true
  count = 0
}

resource "null_resource" "ip_check" {

 triggers = {
    latest_ips = join(",", aws_eip.lb[*].public_ip)
  }

 provisioner "local-exec" {

   command = "echo Latest IPs are ${null_resource.ip_check.triggers.latest_ips} > sample.txt"

  }
}

Dataについて

dataAWSからAMIの値を取得することができる。filterでフィルタリングすることができる。

data "aws_ami" "web" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

Moduleについて

DRY原則とは

DRYとは、Don’t Repeat Yourselfの略で、ソフトウェア開発の原則の一つ。同じソフトウェアパターンの繰り返しはやめようということ。

→ 繰り返し利用するリソースは「モジュール」にして、再利用できるようにする。

以下例では、./modue/ec2配下にec2.tf(モジュール)を配置し、./myec2.tfでモジュールを参照している。

moduleをapplyするには、まずはterraform initコマンドでモジュールを読み込む必要がある。

./modules/ec2/ec2.tf

resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
}

./myec2.tf

module "ec2module" {
  source = "./modules/ec2"
}

リソース内で変数を代入しておくと、モジュールブロック内で変数の値をオーバーライトできる。 変数を使いたいがオーバーライトさせたくない場合は、localを利用する。

モジュール内で定義したoutputブロックはCLI上に出力されないことに注意。

モジュール側でoutputブロックを利用することで、他のリソース作成時にモジュールのoutput値を利用できる。module.<Module_Name>.<Output_Name>という形式でoutput値を参照する。

./modules/ec2/ec2.tf

resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = var.instance_type
}

variable "instance_type" {
  default = "t2.nano"
}

output "instance" {
  value = aws_instance.myec2
}

./myec2.tf

module "ec2module" {
  source        = "./modules/ec2"
  instance_type = "t2.micro"
}

output "module_output" {
  value = module.ec2module.instance
}

moduleブロック内で、version = "0.0.5" という形式でモジュールの version を指定することができる。

モジュールのversion制約指定について詳しくはこちら

Terraform Registry

モジュールをTerraformレジストリから直接取ってくることもできる。

Terraformレジストリから取得したモジュールは、terraform init後にローカルの./.terraform/modules内に格納される。

module "ec2_cluster" {
  source                 = "terraform-aws-modules/ec2-instance/aws"
  version                = "~> 4.0"

  name                   = "my-cluster"

  ami                    = "ami-0d6621c01e8c2de2c"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-4dbfb206"

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Terraformレジストリには、Terraformコミュニティのメンバーが作成したモジュールが置かれている。まずはTerraformレジストリに参考となるモジュールがないか探すと良い。

特にverified moduleはHashicorpにレビューされており、コントリビューターによって最新であるよう維持管理されている。

verified moduleである場合は、モジュールの横に青いバッジが記されている。

Terraformレジストリの各モジュールの”Notes”はちゃんと読んだほうがよい。必須のパラメータについて解説がある。

Terraform Registryへのモジュールのパブリッシュ

モジュールをTerraformレジストリにパブリッシュすることもできる。

モジュールをTerraformレジストリにパブリッシュするための要件は全部で5つある。全部満たしてないといけない。

標準のModule構造

基本的には以下の4つのファイルをModule用のフォルダに配置することが多い。

(ファイル名は任意だが以下のように命名することが多く、デファクトスタンダードになっている)

  • READEME.md
  • main.tf
  • variables.tf
  • outputs.tf

Workspaceについて

terraform workspace new <workspace_name> によりワークスペースを作成すると、カレントディレクトterraform.tfstate.d フォルダが作成され、その中にワークスペース名のフォルダが作られる。ワークスペース名のフォルダ配下に.tfstateファイルが作成される。このステートファイルはdefaultワークスペース.tfstateファイルとは異なる。

.tfファイル内で terraform.workspace とすればワークスペース名を取得できる。

さらにlookup関数を使って、lookup(var.<map変数>, terraform.workspace) とすれば、ワークスペース名に紐づけた値を取得できる。

terraform workspaceワークスペース毎にリソースを作り分けることができる。

ワークスペースのリストを確認するには、terraform workspace list を実行する。

チームコラボレーション

チームでTerraformコードを開発するには以下の環境を準備する。

git:.tfファイルを配置し、共同で開発する。

S3:terraform.tfstateファイルを配置することで、チームメンバー間でステートファイルを共有する。ステートファイルにはパスワード等も平文で記載されているため、S3にはアクセス制限を設定する。

DynamoDB:.terraform.lock.hclファイルを配置する。ステートファイルのロック状態をチームメンバー間で共有する。

上記が理想的なアーキテクチャである。

ちなみに、ステートファイルがS3にあり、ロック状態をDynamoDBで管理しながら(つまり、AWSでBackendとステートロックを管理しながら)、AzureやGCP上にリソースを作成することもできる。

ここで.terraformフォルダやterraform.tfvarsファイル、terraform.tfstateファイル、crash.logファイルはgit commitを避けるべきなので、間違えてコミットされないよう.gitignoreファイルに記載しておく。

git statusコマンドでコミットされてないファイルとフォルダを一覧表示できるので、誤ってコミットされてないか確認しておく。

.gitignoreファイル

.terraform

*.tfvars

*.tfstate
*.tfstate.*

crash.log

Gitについて

ローカルのモジュールを参照するには./../で始まるローカルパスを source に記述する。
一方、Gitリポジトリにアップロードした .tf ファイルのモジュールを参照するにはリポジトリのURLにプレフィックスとして git:: を付ける。
コードのリビジョン(ブランチ)を指定するには ?ref=サフィックスに付ける。

module "demomodule" {
  source = "git::https://github.com/zealvora/tmp-repo.git?ref=v1.2.0"
}

Backends

terraform.tfstateファイルを保存する場所のことを、Backendと呼んでいる。

デフォルトでは、BackendはローカルPCである。

S3をBackendに指定するには、terraformブロックにbackendブロックを記載する。S3バケットは事前に作成しておく。

S3以外もbackendに利用できる。

terraform {
  backend "s3" {
    bucket = "kplabs-terraform-backend"
    key    = "network/terraform.tfstate"
    region = "us-east-1"
  }
}

上記に加えてS3バケットにアクセスするためのアクセスキーが必要になる。

アクセスキーは ~/.aws/config ファイルに記載しておく。AWS CLIをダウンロードし、aws configure コマンドで~/.aws/config ファイルを作成できる。

ステートファイルロック

誰かがterraform.tfstateファイルを変更するような操作を行っている場合は、terraform.tfstateファイルがロックされている必要がある。さもなければ、他の人がterraform.tfstateファイルに変更を加えて一貫性がなくなる。

誰かがterraform applyterraform destroyを実行すると、ロックが取得される。すると、.terraform.tfstate.lock.info というファイルが自動的に作成される。Terraformはこのファイルの有無によってロック中かどうかを見分ける。書き込み操作が終了したら、.terraform.tfstate.lock.info ファイルは自動的に削除される。

ロックが取得されている間に他のユーザーが書き込み操作をした場合、Error acquiring the state lock エラーが出力される。

チームでState Lock機能を使うためには、DynamoDBを利用する。

注意!
Backendによってはロック機能をサポートしてない。サポートしているかどうかは各Backendのドキュメントを確認すること。S3はサポートしており、DynamoDBを利用する。

DynamoDBを利用してステートロック機能を実現するには、backendブロックに dynamodb_table 変数に、事前に作成しておいたDynamoDBの名前を格納する。

ここで、DynamoDBのパーティションキー名は必ず LockID で、かつ String 型でなければならない。さもなければ、ステートロック機能がDisableになる。

terraform {
  backend "s3" {
    bucket = "kplabs-terraform-backend"
    key    = "network/demo.tfstate"
    region = "us-east-1"
    dynamodb_table = "terraform-state-locking"
  }
}

パスワードをGitHub上に保存しないテクニック

GitHubとクローンしたフォルダの外にパスワードファイルを置く。

.tfからパスワードファイルをfile関数で参照する。file("../password.txt")のような感じ。

.gitignoreファイルで、Gitコミットしたくないファイルやフォルダを指定できる。

.tfstateファイルの保護

上記のやり方でも.tfstateファイルに平文のパスワードが記載されてしまっている。

マルチリージョン

providerブロック内にaliasを記載すると、2つ以上のproviderを記述できる。

resourceブロック内で利用するproviderを指定する。

provider "aws" {
  region     =  "us-west-1"
}

provider "aws" {
  alias      =  "aws02"
  region     =  "ap-south-1"
  profile    =  "account02"
}

resource "aws_eip" "myeip" {
  vpc = "true"
}

resource "aws_eip" "myeip01" {
  vpc = "true"
  provider = "aws.aws02"
}

Logについて

環境変数を設定することでログを出力できる。

ログレベルはこちらを参照。

export TF_LOG=TRACE

環境変数TF_LOG_PATHにログファイルのパスをセットすることで、ログをファイルに出力することも可能。

export TF_LOG_PATH=./trace.log

リソース間の依存関係

Terraformが自動でImplicit dependency(暗黙の依存関係)を見つけて、リソースの作成順序を決めてくれる。

一方、depends_on変数によって、明示的に依存関係を定義することもできる。

depends_on変数を利用する場合、なぜdepends_on変数が必要なのかをコメントで説明することが推奨される。

resource "aws_iam_role" "example" {
  name = "example"

  # assume_role_policy is omitted for brevity in this example. Refer to the
  # documentation for aws_iam_role for a complete example.
  assume_role_policy = "..."
}

resource "aws_iam_instance_profile" "example" {
  # Because this expression refers to the role, Terraform can infer
  # automatically that the role must be created first.
  role = aws_iam_role.example.name
}

resource "aws_iam_role_policy" "example" {
  name   = "example"
  role   = aws_iam_role.example.name
  policy = jsonencode({
    "Statement" = [{
      # This policy allows software running on the EC2 instance to
      # access the S3 API.
      "Action" = "s3:*",
      "Effect" = "Allow",
    }],
  })
}

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

  # Terraform can infer from this that the instance profile must
  # be created before the EC2 instance.
  iam_instance_profile = aws_iam_instance_profile.example

  # However, if software running in this EC2 instance needs access
  # to the S3 API in order to boot properly, there is also a "hidden"
  # dependency on the aws_iam_role_policy that Terraform cannot
  # automatically infer, so it must be declared explicitly:
  depends_on = [
    aws_iam_role_policy.example
  ]
}

フォルダ構成

本番環境では、provider.tfvariables.tf<リソース>.tfoutput.tfといった形でファイルを分割するのが望ましい。

こうすることでどのファイルに何が記載されているかが一目でわかる。新しくPJに参加した人でもわかりやすい。

リソースが大規模な場合

大規模なリソースを作成しようとすると、AWS等プロバイダー側のAPI上限に達する可能性がある。達しないまでも、処理に時間を要するようになる。

大量のリソースを作成済みの場合、terraform applyにより作成済みリソースのステータスを取得しようと大量にAPIリクエストを投げることになる。そういった場合に、terraform apply -refresh=falseとすることでAPIリクエストの量を軽減できる。

リソース種別ごとにフォルダを分けてterraform applyを実行することで、一度に大量のAPIリクエストを送信することを防ぐことができる。

セキュリティ上の注意点

  • クレデンシャルはproviderブロックに記載せずに、 ~/.aws/config ファイルに記載しておく。
  • .terraformフォルダやterraform.tfvarsファイル、terraform.tfstateファイル、crash.logファイルはgitにアップしない
  • terraform.tfstateファイルにはクレデンシャルが平文で記載されるため、S3でアクセス管理を行う。
  • クレデンシャルを .tf ファイルに記載しない。.tfからパスワードファイルをfile関数で参照する。file("../password.txt")のような感じ。
  • outputブロック内にsensitiveパラメータをtrueで設定すると、標準出力にoutputの値が\と表示されるようになる。但し、ステートファイルには記載されてしまうので要注意。

Terraformコマンド

  • terraform taint

    • 特定のリソースをtaintすることで、terraform apply時にリソースが再作成される。terraform taint aws_instance.mydc2
    • taintされたリソースは、terraform.tfstateファイル上でステータスがtaintedになる。
    • terraform taintでリソースが再作成されることで、EC2に割り当てられたIPが変更されるかもしれない。依存関係などの再作成に伴うリスクは事前に確認しておくこと。
    • 多くの会社ではマニュアルでのリソース変更を認めておらず、マニュアルで変更を加えた場合にはterraform taintでリソースの状態をもとに戻している、らしい。
  • terraform refresh

    • terraform.tfstateファイルに、現在のプロビジョニングされているリソースの状態を反映する
  • terraform graph

    • リソース作成順序を図示する(グラフ化する)ことができる。
  • terraform init

  • terraform init -upgrade

    • Providerのバージョンをアップグレードできる。
  • terrafrom fmt

    • インデントのズレをあわせてくれる。
  • terraform plan -out=<出力先パス名>

    • terraform planの解析結果をバイナリとして出力する。
    • terraform apply <出力先パス名>とすることで解析結果をもとにリソース作成できる。
  • terraform output <output変数>

    • ステートファイルに記載されているoutput変数の値を出力する。
    • もちろん.tfstateを直接確認することでもoutput変数に格納されている値を確認できる。あるいは、terraform applyコマンドでoutputを出力させることもできる。
  • terraform apply -target=ec2

    • -targetにより、特定のリソースに絞って実行できる。
    • これによりAPIリクエストの量を軽減できる。
  • terraform apply -refresh=false

    • 大量のリソースを作成済みの場合、terraform applyにより作成済みリソースのステータスを取得しようと大量にAPIリクエストを投げることになる。
    • そういった場合に、terraform apply -refresh=falseとすることでAPIリクエストの量を軽減できる。
  • terraform apply -refresh-only

    • こちらによると、どうやらterraform refreshコマンドは非推奨で、terraform apply -refresh-onlyを利用するのが良いそう。

    In previous versions of Terraform, the only way to refresh your state file was by using the terraform refresh subcommand. However, this was less safe than the -refresh-only plan and apply mode since it would automatically overwrite your state file without giving you the option to review the modifications first. In this case, that would mean automatically dropping all of your resources from your state file.

  • terraform login

    • The terraform login command can be used to automatically obtain and save an API token for Terraform Cloud, Terraform Enterprise, or any other host that offers Terraform services.
  • terraform force-unlock

    • ステートロックを解除する。
  • terraform plan -destroy

    • terraform destroyコマンドをプレビューすることができる。
  • terraform show

    • The terraform show command is used to provide human-readable output from a state or plan file. This can be used to inspect a plan to ensure that the planned operations are expected, or to inspect the current state as Terraform sees it.

Machine-readable output is generated by adding the -json command-line flag.

terraform state コマンド

terraform stateコマンドにより、ステートファイルを確認したり、変更することができる。

  • terraform state pull

    • backendにあるステートファイルをダウンロードできる。
  • terraform state show

    • 特定のリソースに対するterraform state pullと同義
  • terraform state mv <既存リソース名> <新リソース名>

    • terraformのリソース名を変更しようとすると、リソースがいったん削除されてしまう。terraform state mvを使うことで、リソースを削除せずにリソース名を変更できる。
  • terraform state list

    • ステートファイルに含まれるリソースのリスト表示
  • terraform state rm <リソースタイプ>.<ローカル名> 【重要】

    • ステートファイルから削除される。Terraformでの管理を止めたい場合に利用する。

terraform import コマンド

マニュアル作成したリソースをステートファイルに取り込むことができる。但し、インポートできるリソースは限られているの事前に確認が必要。

インポートする場合は、事前に .tf ファイルにresourceブロックを記述しておく。

次に terraform import コマンドを実行する。

terraform import aws_instance.foo i-abcd1234

Terraform Cloudについて

Terraform Cloudは以下の機能を提供する

  • コスト確認

  • 事前に設定したポリシーを満たしているかのチェック(Sentinel)

  • コードレビューの記録

  • モジュールのレジストリ

  • Gitに保管したTerraformコードとの連携

どうやら、Terraform Enterpriseや有償のTerraform Cloudを使っている会社は多くないらしい。というか、実質ほとんどいない。無料のTerraform Cloudで十分らしい。

  • Terraform Cloud 無料版

    • State management
    • Remote operations
    • Private module registry
  • Terraform Cloud 有償版(Team & Governance)

    • Team management
    • Sentinel policy as code
    • Run tasks
    • Additional concurrency
  • Terraform Cloud 有償版(Enterprise)

    • Drift detection
    • SSO
    • Audit logs
    • Self-hosted agents
    • Custom concurrency

OSS、Terraform Cloudの比較は以下に詳細がわかりやすくまとめられている。

こちらの画像は抜粋。比較表の全文はこちら

Sentinel

SentinelはPolicy as a Codeなフレームワークの1つ。ポリシーの記述に利用する。

Sentinelを利用して、コードレベルのポリシーチェックができる。ポリシーに沿ってないコードは弾かれる。SentinelはTerraformコードのポリシーチェックのみなので、AWS Configなどを使ってマニュアル作成されたリソースについてもポリシーチェックが必要となる。

Terraform Vaultについて

Vaultはリクエストに応じてクレデンシャル情報を払い出してくれる。払い出されたクレデンシャルは一定時間経過後にVaultが自動で削除する。そのため、クレデンシャルが外部に漏れた場合のリスクを低減できる。

Vaultにクレデンシャル払い出しをリクエストする際に、権限も指定できる。

用語

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

Resource block - resourceで始まる{...}範囲のこと

Resource type - 上記例での"aws_instance"のこと

Resource name (= local name) - 上記例での"server"のこと

Argument Name - 上記例でのamiinstance_typeのこと

Argument Value - 上記例での"ami-a1b2c3d4""t2.micro"のこと

Child module - いわゆるmoduleのこと。Child moduleを呼び出す.tf のメインの部分を「Root module」と呼ぶ。

Address (Resource Address) - リソースを特定できる文字列のこと。モジュールパスと、リソーススペックから成る。[module path][resource spec]

Module path - A module path addresses a module within the tree of modules. It takes the form module.module_name[module index]

Resource spec - A resource spec addresses a specific resource instance in the selected module. It has the following syntax resource_type.resource_name[instance index]

aws_instance.myec2module.foo[0].module.bar["a"]https://www.terraform.io/cli/state/resource-addressing

コメントアウトについて

コメントアウトに利用できる記号は3種類。

#
//
/* */

関数

TerraformではUDF(ユーザ定義関数)は作れない。Terraformの組み込み関数のみ。

toset関数

リストを集合に変換できる。

「SET」 = 「集合」のことで、リスト型とは異なり、重複した値を持つことはできない。

Terraformがサポートしている言語

HCLだけでなく、JSONでも記述できる。

www.terraform.io