Terraformで始めるインフラ管理 – 第3回

目次

はじめに

前回の記事では、Terraform プロジェクトディレクトリの初期化方法や、構成ファイルの書き方について学びました。今回の記事では、Terraform におけるリソースのライフサイクルについて学びます。リソースが作成されてから削除されるまでに、どのようなステップを経るのかを見ていきましょう。

Terraform を使って、AWS 上に S3(AWS Simple Cloud Storage)を作成し、リソースのライフサイクルについて学んでいきます。

Terraform のライフサイクルの中で呼び出される関数。

Tất cả các resource type của Terraform đều Implement một CRUD Interface, trong CRUD Interface này sẽ có các hàm là Create(), Read(), Update(), Delete() và những hàm này sẽ được thực thi nếu gặp đúng điều kiện phù hợp. Còn data type của Terraform thì Implement một Read Interface chỉ có một hàm là Read(), hình minh họa.

Create() sẽ được gọi trong quá trình tạo resource, Read() được gọi trong quá trình plan, Update() được gọi trong quá trình cập nhật resource, và Delete() được gọi trong quá trình xóa resource.

S3 の例

これから Terraform ファイルを作成して S3 を作成し、上記の各関数について簡単に説明します。
s3 という名前のワークスペースを作成し、その後 main.tf という名前のファイルを作成し、次のコードを記述します。

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

resource "aws_s3_bucket" "terraform-bucket" {
  bucket = "terraform-series-bucket"

  tags = {
    Name        = "Terraform Series"
  }
}

Ở tệp tin trên trên ta dùng resource là aws_s3_bucket, đây là resource dùng để tạo S3 Bucket trên AWS Cloud, trong đó trường bucket sẽ là tên Bucket của ta. Sau khi viết xong thì ta chạy câu lệnh init để Terraform tải Provider xuống Workspace hiện tại.

terraform init

プラン

前回の記事でも述べたように、リソースを作成する前に terraform plan を実行して、どのリソースが作成されるかを確認するのが望ましいです。

また、terraform plan は単に新しく作成されるリソースを表示するだけでなく、すでに存在するリソースに対して Terraform ファイル内の値を変更した場合、それがどのように更新されるかも表示してくれます。これは、以前に作成されたリソースの状態(State)に基づいて行われます。

もし Terraform ファイル内に変更がない場合は、terraform plan を実行しても、追加または更新されるリソースはないと表示されます。

この plan プロセスはとても有用な出力を提供してくれます。出力を読むだけで、自分のインフラにどのような変更が加えられるのかが分かります。
terraform plan コマンドを実行すると、Terraform は主に以下の3つのステップを実行します。

  • 設定ファイルと State ファイルの読み込み – Terraform はまず、設定ファイルと(存在する場合は)State ファイルを読み込み、リソースに関する情報を取得します。
  • 次に実行すべきアクションの決定 – Terraform は計算を行い、どのアクション(Create()Read()Update()Delete()、あるいは何もしない No-op)が実行されるべきかを判断します。
  • 出力

S3の作成

次に、apply コマンドを実行して AWS 上に S3 を作成します。apply を実行すると、確認ステップが追加され、「yes」と入力するように求められます。
確認ステップをスキップしたい場合は、コマンドに -auto-approve オプションを付けて実行します。

terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-bucket will be created
  + resource "aws_s3_bucket" "terraform-bucket" {
  ...
  }

Plan: 1 to add, 0 to change, 0 to destroy.
aws_s3_bucket.terraform-bucket: Creating...
aws_s3_bucket.terraform-bucket: Still creating... [10s elapsed]
aws_s3_bucket.terraform-bucket: Creation complete after 15s [id=terraform-series-bucket]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

apply コマンドの実行が完了すると、Terraform は terraform.tfstate という名前のファイルを生成します。
このファイルを開くと、S3 に関する情報が記録されているのが確認できます。
また、AWS の Web コンソールを開けば、自分の S3 バケットが作成されていることを確認できます。

Terraform はどのようにしてこの S3 バケットを作成したのでしょうか?
それは apply の過程で、aws_s3_bucket リソースの Create() 関数が呼び出されるからです。

No-op

リソースを作成し終えたあと、設定ファイルに何も変更を加えていない場合、plan を実行すると Terraform は No-op(何もしない)ステップを通過します。
このとき、Terraform はまず設定ファイルを読み込み、次に State ファイルが存在することを検出して、それも読み込みます。
以下のようにイメージできます。

State ファイルの読み込みが完了した後、Terraform はその中に S3 バケットの情報が存在するかどうかを確認します。
存在していれば、Terraform は aws_s3_bucket リソースの Read() 関数を実行します。

Read() 関数には、現在の S3 バケットの情報を取得するために AWS の API を呼び出すコードが含まれています。
その後、取得した情報は State ファイル内の S3 バケットの情報と比較されます。
もし差分がなければ、Read() 関数は「変更なし」という結果を返し、Terraform は一切のアクションを実行しません。

S3 の更新

Terraform には update コマンドというものは存在しません。
設定ファイルを修正して apply コマンドを再実行するだけで、Terraform は自動的にリソースの更新が必要かどうかを判断してくれます。

ここでは、S3 バケットの名前を変更してみましょう。

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

resource "aws_s3_bucket" "terraform-bucket" {
  bucket = "terraform-series-bucket-update"

  tags = {
    Name        = "Terraform Series"
  }
}

その後、再度 terraform plan コマンドを実行します。

aws_s3_bucket.terraform-bucket: Refreshing state... [id=terraform-series-bucket]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-bucket must be replaced
-/+ resource "aws_s3_bucket" "terraform-bucket" {
      + acceleration_status         = (known after apply)
      ~ arn                         = "arn:aws:s3:::terraform-series-bucket" -> (known after apply)
      ...
    }

Plan: 1 to add, 0 to change, 1 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.

すると、Terraform は S3 バケットを「更新」するのではなく、一度削除してから新しく作成するという動作を行うことが分かります。
つまり、まず古い S3 バケットを削除し、その後に新しい名前で S3 バケットを作成します。

なぜこのような挙動になるのでしょうか?
それは、aws_s3_bucket リソースの bucket 属性が Force New(強制的に新規作成される)属性 だからです。

このような属性は変更できず、値が変わった場合はリソース全体を再作成する必要があります。

Terraform では、リソースの属性(プロパティ)は大きく分けて 「Force New」「Normal Update」 の2種類があります。

  • Force New:リソースは削除されてから再作成されます。
    つまり、まず既存のリソースが削除され、その後に新しいリソースが作成されます。
  • Normal Update:リソースは通常通りに更新され、既存のリソースを削除する必要はありません。

ある属性が Force New に分類されるか Normal Update に分類されるかは、使用している Provider によって決まります
上記の例では、aws_s3_bucketForce New 属性 を変更したため、Terraform はリソースを一度削除してから再作成しました。

ただし、リソースの削除と再作成には多くのリスクや問題が伴う可能性があるため、常に terraform plan を実行して、なぜそのような動作が発生するのかを事前に確認することが非常に重要です

今回のように、S3 バケットはまだ新しく、中にデータも入っていないので、そのまま terraform apply を実行して問題ありません。Terraform に任せてリソースを安全に更新させましょう。

terraform apply -auto-approve
aws_s3_bucket.terraform-bucket: Refreshing state... [id=terraform-series-bucket]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

 # aws_s3_bucket.terraform-bucket must be replaced
-/+ resource "aws_s3_bucket" "terraform-bucket" {
     + acceleration_status         = (known after apply)
     ~ arn                         = "arn:aws:s3:::terraform-series-bucket" -> (known after apply)
     ...
   }

Plan: 1 to add, 0 to change, 1 to destroy.
aws_s3_bucket.terraform-bucket: Destroying... [id=terraform-series-bucket]
aws_s3_bucket.terraform-bucket: Destruction complete after 1s
aws_s3_bucket.terraform-bucket: Creating...
aws_s3_bucket.terraform-bucket: Still creating... [10s elapsed]
aws_s3_bucket.terraform-bucket: Creation complete after 15s [id=terraform-series-bucket-update]

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

実行が完了すると、新しい名前の S3 バケットが作成されていることが確認できます。

S3の削除

リソースを削除するには、destroy コマンドを使用します。
apply コマンドと同様に、確認ステップがあり、「yes」と入力する必要がありますが、
-auto-approve オプションを付ければ、確認ステップをスキップすることができます。

terraform destroy -auto-approve
aws_s3_bucket.terraform-bucket: Refreshing state... [id=terraform-series-bucket-update]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
 - destroy

Terraform will perform the following actions:

 # aws_s3_bucket.terraform-bucket will be destroyed
 - resource "aws_s3_bucket" "terraform-bucket" {
     - acl                         = "private" -> null
     - arn                         = "arn:aws:s3:::terraform-series-bucket-update" -> null
     ...
   }

Plan: 0 to add, 0 to change, 1 to destroy.
aws_s3_bucket.terraform-bucket: Destroying... [id=terraform-series-bucket-update]
aws_s3_bucket.terraform-bucket: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.

destroy コマンドを実行すると、Terraform はまず自分の State ファイルを読み込み、その中に該当するリソースが存在するかどうかを確認します。
もし存在すれば、Terraform は aws_s3_bucket リソースの Delete() 関数を実行して、そのリソースを削除します。

destroy コマンドの実行が完了すると、ワークスペースの状態は次のようになります。

新たに terraform.tfstate.backup というファイルが生成されているのが確認できます。
このファイルは、リソースの以前の状態(State)を確認するためのバックアップであり、変更前の状態を追跡する目的で使用されます。

また、もし設定ファイル内のすべてのリソース定義を削除してから terraform apply を実行した場合、これは terraform destroy を実行したのと同じ効果になります。

これで、Terraform におけるリソースのライフサイクル(作成、読み取り、更新、削除)についての説明は完了です。
次に取り上げるのは、Terraform 以外の方法でリソースが変更された場合はどうなるのか? というよくある問題です。
Terraform はこのような外部変更(ドリフト)にどう対応するのでしょうか?続きを見ていきましょう。

Resource Drift(リソースのドリフト)

リソースドリフトとは、Terraform の外部でリソースの設定が変更されることによって発生する問題です。
たとえば AWS の場合、Terraform で作成したリソースに対して、誰かが AWS Web コンソール を使って設定を変更してしまうようなケースが該当します。

上記の例に戻ると、Terraform で作成した S3 バケットに対して Web コンソール経由で何か設定変更を加えた場合、それが Resource Drift になります。
Terraform 側ではまだ変更前の情報を保持しているため、状態(State)と実インフラの間にズレが生じてしまうのです。

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

resource "aws_s3_bucket" "terraform-bucket" {
  bucket = "terraform-series-bucket-update"

  tags = {
    Name        = "Terraform Series"
  }
}
terraform apply -auto-approve
...
Plan: 1 to add, 0 to change, 0 to destroy.
aws_s3_bucket.terraform-bucket: Creating...
aws_s3_bucket.terraform-bucket: Still creating... [10s elapsed]
aws_s3_bucket.terraform-bucket: Creation complete after 15s [id=terraform-series-bucket-update]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

その後、AWS の Web コンソールにアクセスして、S3 バケットの tags フィールドを修正します。

Terraform は自動で変更を検出して設定ファイルを更新するようなことはしません。そんなに魔法のような存在ではありません。
ですが、apply コマンドを実行すると、Terraform はその変更を検出し、Terraform の設定ファイルに書かれている tags と一致するように、外部で変更された tags を元に戻します。
まずは plan コマンドを実行して確認してみましょう。

aws_s3_bucket.terraform-bucket: Refreshing state... [id=terraform-series-bucket-update]

Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

  # aws_s3_bucket.terraform-bucket has been changed
  ~ resource "aws_s3_bucket" "terraform-bucket" {
        id                          = "terraform-series-bucket-update"
      ~ tags                        = {
          ~ "Name" = "Terraform Series" -> "Terraform Series Drift"
        }
      ~ tags_all                    = {
          ~ "Name" = "Terraform Series" -> "Terraform Series Drift"
        }
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

...

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-bucket will be updated in-place
  ~ resource "aws_s3_bucket" "terraform-bucket" {
        id                          = "terraform-series-bucket-update"
      ~ tags                        = {
          ~ "Name" = "Terraform Series Drift" -> "Terraform Series"
        }
      ~ tags_all                    = {
          ~ "Name" = "Terraform Series Drift" -> "Terraform Series"
        }
        # (9 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.

apply コマンドを再実行すると、tags が元の設定通りに更新されるのが確認できます。

terraform apply -auto-approve
...
Plan: 0 to add, 1 to change, 0 to destroy.
aws_s3_bucket.terraform-bucket: Modifying... [id=terraform-series-bucket-update]
aws_s3_bucket.terraform-bucket: Still modifying... [id=terraform-series-bucket-update, 10s elapsed]
aws_s3_bucket.terraform-bucket: Modifications complete after 13s [id=terraform-series-bucket-update]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

まとめ

これで、Terraform におけるリソースのライフサイクルについての学習は完了です。
次回は、Terraform における 関数型プログラミング(Functional Programming) について学んでいきましょう。