DDetB.Log
article thumbnail
이 포스팅은 CloudNet@ 팀이 진행하는 테라폼 기초 입문 스터디에 참여하며 ‘테라폼으로 시작하는 IaC’ 책을 기준하여 정리한 글입니다.

terraform_data 

아무 작업도 수행하지 않는 리소스를 구현하며, 테라폼 1.4 버전이 릴리즈되면서 기존 null_resource 리소스를 대체하는 terraform_data 리소스가 추가됨. 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생할 때 사용.

  • 주요 사용 시나리오
    • 프로비저닝 수행 과정에서 명령어 실행
    • 프로비저너와 함께 사용
    • 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
    • 출력을 위한 데이터 가공
  • 요구사항
    • AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시켜야 함
    • 웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하므로 aws_eip 리소스를 생성

main.tf

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_security_group" "instance" {
  name = "t101sg"

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

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

}

resource "aws_instance" "example" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-dbc571b0" 
  private_ip             = "172.31.1.100"
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, T101 Study" > index.html
              nohup busybox httpd -f -p 80 &
              EOF

  tags = {
    Name = "Single-WebSrv"
  }

  provisioner "remote-exec" {
    inline = [
      "echo ${aws_eip.myeip.public_ip}"
     ]
  }
}

resource "aws_eip" "myeip" {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = "172.31.1.100"
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}

실행 및 확인

$ terraform plan
╷
│ Error: Cycle: aws_eip.myeip, aws_instance.example
│ 
╵

=> 두 리소스의 종속성이 상호 참조되어 발생하는 에러

=> aws_eip가 생성되는 고정된 IP를 할당하기 위해서는 대상인 aws_instance의 id값이 필요

=> aws_instance의 프로비저너 동작에서는 aws_eip가 생성하는 속성 값인 public_ip가 필요

main.tf 수정

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_security_group" "instance" {
  name = "t101sg"

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

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

}

resource "aws_instance" "example" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-98d276f3"
  private_ip             = "172.31.0.100"
  key_name               = "aiden_key" # 각자 자신의 EC2 SSH Keypair 이름 지정
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, T101 Study" > index.html
              nohup busybox httpd -f -p 80 &
              EOF

  tags = {
    Name = "Single-WebSrv"
  }

}

resource "aws_eip" "myeip" {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = "172.31.0.100"
}

resource "terraform_data" "echomyeip" {
  provisioner "remote-exec" {
    connection {
      host = aws_eip.myeip.public_ip
      type = "ssh"
      user = "ubuntu"
      private_key =  file("/home/aiden/terraform_study/aiden_key.pem") # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
    }
    inline = [
      "echo ${aws_eip.myeip.public_ip}"
      ]
  }
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}

output "eip" {
  value       = aws_eip.myeip.public_ip
  description = "The EIP of the Instance"
}

실행 및 확인

$  terraform apply -auto-approve
...
aws_eip.myeip: Creating...
aws_eip.myeip: Creation complete after 2s [id=eipalloc-04a4474b8c52faf40]
terraform_data.echomyeip: Creating...
terraform_data.echomyeip: Provisioning with 'remote-exec'...
terraform_data.echomyeip (remote-exec): Connecting to remote host via SSH...
terraform_data.echomyeip (remote-exec):   Host: 15.165.173.77
terraform_data.echomyeip (remote-exec):   User: ubuntu
terraform_data.echomyeip (remote-exec):   Password: false
terraform_data.echomyeip (remote-exec):   Private key: true
terraform_data.echomyeip (remote-exec):   Certificate: false
terraform_data.echomyeip (remote-exec):   SSH Agent: false
terraform_data.echomyeip (remote-exec):   Checking Host Key: false
terraform_data.echomyeip (remote-exec):   Target Platform: unix
...
terraform_data.echomyeip (remote-exec): Connected!
terraform_data.echomyeip (remote-exec): 15.165.173.77
...
Outputs:

eip = "15.165.173.77"
public_ip = "52.78.242.232"

$ terraform state list
aws_eip.myeip
aws_instance.example
aws_security_group.instance
terraform_data.echomyeip

# 출력된 EC2 퍼블릭IP로 curl 접속 확인
$ MYIP=$(terraform output -raw eip)

$ while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done

Hello, T101 Study
------------------------------
Sat 22 Jul 2023 04:05:27 AM KST
Hello, T101 Study
------------------------------
Sat 22 Jul 2023 04:05:28 AM KST
...

moved blocks

테라폼의 State에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성된다. 리소스의 이름은 변경되지만 이미 테라폼으로 프로비저닝된 환경을 그대로 유지하고자 하는 경우 moved 블록을 사용한다. 예를 들면 아래와 같은 상황이 있다.

  • 리소스 이름을 변경
  • count로 처리하던 반복문을 for_each로 변경
  • 리소스가 모듈로 이동하여 참조되는 주소가 변경

main.tf

resource "local_file" "a" {
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

output "file_content" {
  value = local_file.a.content
}

실행 및 확인

$ terraform init && terraform plan && terraform apply -auto-approve
...

$ terraform state show local_file.a
# local_file.a:
resource "local_file" "a" {
    content              = "foo!"
    content_base64sha256 = "wOCqrqBQvPO+JsDCPVj6iQwN+3nIojAWtKhs0oym6nE="
    content_base64sha512 = "TSCmPxazw2YStHurNILfRykjK/J4evgArH/2KnQuzQZodz4cq1f/ig2GeQO7mBI+Qx5jkTQEZxLCGs3mPtsB3Q=="
    content_md5          = "35af8b7a9490467f75f19c1e5459f7e7"
    content_sha1         = "4bf3e335199107182c6f7638efaad377acc7f452"
    content_sha256       = "c0e0aaaea050bcf3be26c0c23d58fa890c0dfb79c8a23016b4a86cd28ca6ea71"
    content_sha512       = "4d20a63f16b3c36612b47bab3482df4729232bf2787af800ac7ff62a742ecd0668773e1cab57ff8a0d867903bb98123e431e639134046712c21acde63edb01dd"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "./foo.bar"
    id                   = "4bf3e335199107182c6f7638efaad377acc7f452"
}

main.tf 수정 - local_file 이름 변경

resource "local_file" "b" {
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

output "file_content" {
  value = local_file.b.content
}

=> local_file 이름을 a에서 b로 변경

실행 및 확인

$ terraform plan
...
Plan: 1 to add, 0 to change, 1 to destroy.

=> 기존 리소스를 제거하고 새로운 리소스를 생성

main.tf 수정 - moved block 추가

resource "local_file" "b" {
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

moved {
  from = local_file.a
  to   = local_file.b
}

output "file_content" {
  value = local_file.b.content
}

실행 및 확인

$ terraform plan
...
  # local_file.a has moved to local_file.b
    resource "local_file" "b" {
        id                   = "4bf3e335199107182c6f7638efaad377acc7f452"
        # (10 unchanged attributes hidden)
    }
    
Plan: 0 to add, 0 to change, 0 to destroy.

=> 기존 리소스를 제거하지 않고 이름 변경. State ID도 동일함을 확인.

=> apply 이후 moved 블럭을 삭제하면 리팩터링 완료.

profile

DDetB.Log

@DDetMok

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!