티스토리 뷰

DevOps/Terraform

Terraform GCP resource dependencies

Reo Dongmin Lee 2019. 9. 4. 01:32

GCP에는 다양한 리소스가 존재하고

각 리소스들이 독립적으로도 존재하지만 서로간 의존성이 있는 리소스들도 존재한다.

Terraform은 이런 리소스간 의존성을 파악하고 선행 되어야할 리소스 api를 먼저 호출한다. 

실제로 그러한지 코드 예제로 확인 해보자.

 

이 포스팅의 예제 및 설명은  Terraform 공식가이드 Getting Started with the Google Provider - Terraform by HashiCorp 와 Terraform 공식 튜토리얼 Resource Dependencies 의 내용을 참고하였습니다.

 

Prerequisite

 

 

Resource 생성

다음의 내용으로 main.tf 파일을 생성한다.

provider "google" {
  project = "{{YOUR GCP PROJECT}}"
  region  = "us-central1"
  zone    = "us-central1-c"
}

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "f1-micro"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    # A default network is created for all GCP projects
    network       = "${google_compute_network.vpc_network.self_link}"
    access_config {
    }
  }
}

resource "google_compute_network" "vpc_network" {
  name                    = "terraform-network"
  auto_create_subnetworks = "true"
}

GCP free tier 사용을 위해 us-central1 region과 f1-micro 타입의 instance를 생성한다.

f1-micro instance는 한개까지만 무료이니 참고한다.

 

main.tf 파일 위치에서 init 명령어 실행.

terraform init

 

init이 정상적으로 되었다면 다음 명령어로 생성될 resource를 확인한다.

terraform plan

 

다음과 같은 결과를 확인할 수 있다.

 

다음 명령어로 resource를 생성한다.

terraform apply

 

terraform apply 결과

 

instance가 잘 생성되었는지 확인한다.

 

Resource 변경

Resource dependency 케이스를 확인하기 위해 static ip를 생성하여 위에서 생성된 vm_instance에게 부여 해보자.

다음의 코드를 main.tf 끝부분에 추가한다.

resource "google_compute_address" "vm_static_ip" {
  name = "terraform-static-ip"
}

 

코드 추가후에 plan 명령어로 변경 될 사항을 확인한다.

terraform plan

terraform plan 결과

 

생성될 static ip를 vm_instance에 부여한다. 

vm_instance의 network interface를 다음과 같이 변경한다.

network_interface {
    network = "${google_compute_network.vpc_network.self_link}"
    access_config {
      nat_ip = "${google_compute_address.vm_static_ip.address}"
    }
  }

 

network_interface의 access_config 에 여러 options 들이 있는데 예제에서는 그 중 nat_ip를 static ip로 설정한다.

위와 같이 설정을 변경하면 terraform은 vm_instance의 network_instance를 변경하기전에 의존성이 있는 vm_static_ip 를 먼저 생성하게 된다.

plan 명령어로 실제 terraform이 어떻게 동작할지 확인해보자.

$terraform plan

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

google_compute_network.vpc_network: Refreshing state... [id=terraform-network]
google_compute_instance.vm_instance: Refreshing state... [id=terraform-instance]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # google_compute_address.vm_static_ip will be created
  + resource "google_compute_address" "vm_static_ip" {
      + address            = (known after apply)
      + address_type       = "EXTERNAL"
      + creation_timestamp = (known after apply)
      + id                 = (known after apply)
      + name               = "terraform-static-ip"
      + network_tier       = (known after apply)
      + project            = (known after apply)
      + region             = (known after apply)
      + self_link          = (known after apply)
      + subnetwork         = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_instance.vm_instance will be updated in-place
  ~ resource "google_compute_instance" "vm_instance" {
        can_ip_forward       = false
        cpu_platform         = "Intel Haswell"
        deletion_protection  = false
        guest_accelerator    = []
        id                   = "terraform-instance"
        instance_id          = "8298189766579695582"
        label_fingerprint    = "42WmSpB8rSM="
        labels               = {}
        machine_type         = "f1-micro"
        metadata             = {}
        metadata_fingerprint = "qO_86hfv8YQ="
        name                 = "terraform-instance"
        project              = "terraform-reo"
        self_link            = "https://www.googleapis.com/compute/v1/projects/terraform-reo/zones/us-central1-c/instances/terraform-instance"
        tags                 = []
        tags_fingerprint     = "42WmSpB8rSM="
        zone                 = "us-central1-c"

        boot_disk {
            auto_delete = true
            device_name = "persistent-disk-0"
            source      = "https://www.googleapis.com/compute/v1/projects/terraform-reo/zones/us-central1-c/disks/terraform-instance"

            initialize_params {
                image  = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-9-stretch-v20190813"
                labels = {}
                size   = 10
                type   = "pd-standard"
            }
        }

      ~ network_interface {
            name               = "nic0"
            network            = "https://www.googleapis.com/compute/v1/projects/terraform-reo/global/networks/terraform-network"
            network_ip         = "10.128.0.2"
            subnetwork         = "https://www.googleapis.com/compute/v1/projects/terraform-reo/regions/us-central1/subnetworks/terraform-network"
            subnetwork_project = "terraform-reo"

          ~ access_config {
              ~ nat_ip       = "104.197.227.169" -> (known after apply)
                network_tier = "PREMIUM"
            }
        }

        scheduling {
            automatic_restart   = true
            on_host_maintenance = "MIGRATE"
            preemptible         = false
        }
    }

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

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

 

위 케이스는 vm_instance 라는 이름의 google_compute_instance 리소스가

vm_static_ip라는 이름의 google_compute_address 리소스에 대해 의존성을 가지는 경우다.

plan에서 보다시피 vm_static_ip를 먼저 생성하고 vm_instance를 update 하는것을 확인 할수있다.

이와같이 tf 파일에 정의된 순서대로 순차적으로 리소스를 생성, 변경 하는것이 아니라 의존성이 있는 리소스를 먼저 정의하게 된다.

테스트가 끝났으니 생성한 리소스를 아래 명령어로 삭제한다.

terraform destroy

 

 

Explicit dependencies

위 케이스처럼 Terraform 이 알아서 의존성을 캐치하고 실행하는 경우도 있지만

Terraform 입장에서 의존성을 파악할 수 없는 케이스들도 존재한다.

예를들어, 특정 cloud storage bucket 에 접근해야 하는 어플리케이션이 instance 위에 동작한다고 가정할때

cloud storage bucket에 대한 의존성이 어플리케션 코드 안에 숨어있는 경우 Terraform 입장에선 알수가 없다.

이 경우 아래와 같이 depends_on 이라는 인자값을 정의하여 의존성을 강제로 정의할 수 있다.

provider "google" {
  project = "terraform-reo"
  region  = "us-central1"
  zone    = "us-central1-c"
}

resource "google_compute_instance" "vm_instance" {
  depends_on = [google_storage_bucket.terraform_bucket]
  name         = "terraform-instance"
  machine_type = "f1-micro"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    # A default network is created for all GCP projects
    network       = "${google_compute_network.vpc_network.self_link}"
    access_config {
    }
  }
}

resource "google_storage_bucket" "terraform_bucket" {
  name     = "terraform-bucket-reolee-20190903"
  location = "US"
}

resource "google_compute_network" "vpc_network" {
  name                    = "terraform-network"
  auto_create_subnetworks = "true"
}

 

공식 가이드에는 위와같이 가이드 되긴 했는데 실제 테스트 해보니 GCP provider의 경우 depends_on 옵션이 정상적으로 작동 하지 않는다.

정상작동한다면 의존성이 있는 storage_bucket 먼저 생성해야 하지만 위의 tf 코드로 terraform plan을 실행하면 다음과 같이 instance를 먼저 생성하고 bucket을 생성하는 것을 알수있다.

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.  
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_instance.vm_instance will be created
  + resource "google_compute_instance" "vm_instance" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)    
      + deletion_protection  = false
      + guest_accelerator    = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "f1-micro"
      + metadata_fingerprint = (known after apply)
      + name                 = "terraform-instance"
      + project              = (known after apply)
      + self_link            = (known after apply)
      + tags_fingerprint     = (known after apply)
      + zone                 = (known after apply)

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + source                     = (known after apply)

          + initialize_params {
              + image  = "debian-cloud/debian-9"
              + labels = (known after apply)
              + size   = (known after apply)
              + type   = (known after apply)
            }
        }

      + network_interface {
          + address            = (known after apply)
          + name               = (known after apply)
          + network            = (known after apply)
          + network_ip         = (known after apply)
          + subnetwork         = (known after apply)
          + subnetwork_project = (known after apply)

          + access_config {
              + assigned_nat_ip = (known after apply)
              + nat_ip          = (known after apply)
              + network_tier    = (known after apply)
            }

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

  # google_compute_network.vpc_network will be created
  + resource "google_compute_network" "vpc_network" {
      + auto_create_subnetworks         = true
      + delete_default_routes_on_create = false
      + gateway_ipv4                    = (known after apply)
      + id                              = (known after apply)
      + name                            = "terraform-network"
      + project                         = (known after apply)
      + routing_mode                    = (known after apply)
      + self_link                       = (known after apply)
    }

  # google_storage_bucket.terraform_bucket will be created
  + resource "google_storage_bucket" "terraform_bucket" {
      + bucket_policy_only = (known after apply)
      + force_destroy      = false
      + id                 = (known after apply)
      + location           = "US"
      + name               = "terraform-bucket-reolee-20190903"
      + project            = (known after apply)
      + self_link          = (known after apply)
      + storage_class      = "STANDARD"
      + url                = (known after apply)
    }

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

 

Github issue를 찾아보니 해당 이슈가 #3561 있는데 close 되었고 동일이슈 #2341 이 아직 open 되어있다.

2015년에 등록된 이슈인데 아직 안고쳐진건가.. 공식 튜토리얼에서 설명하는 예제와 설명부터 버그라니 이건 조금 실망스럽다.

AWS provider에도 같은 이슈가 있는듯 하다. #16200

이슈 히스토리도 그렇고.. 0.11 때도 똑같았고 0.12에도 같은 버그가 있는걸 보면 explicit dependencies 설정은 없다고 생각하는 편이 속 편할듯 하다.

 

 

References

https://learn.hashicorp.com/terraform/gcp/dependencies

https://www.terraform.io/docs/providers/google/getting_started.html

'DevOps > Terraform' 카테고리의 다른 글

Terraform GCP Credentials  (0) 2019.09.04
Terrafrom 으로 Google Cloud Instance 생성하기  (3) 2019.08.08
댓글