Terraform

[ Terraform101 Study - 3w ] null_resource와 terraform_data

su''@ 2024. 6. 30. 04:41
Terrafrom T101 4기 실습 스터디 게시글입니다.
"테라폼으로 시작하는 IaC" 도서를 참고하여 정리했습니다.

 

테라폼 1.4 버전이 릴리즈되면서 기존 null_resource 리소스를 대체하는 terraform_data 리소스가 추가되었다

 

 

null_resource : 아무 작업도 수행하지 않는 리소스를 구현 - 링크 Blog
  • 이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생하여, 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문이다.
  • 주로 사용되는 시나리오
    • 프로비저닝 수행 과정에서 명령어 실행
    • 프로비저너와 함께 사용
    • 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
    • 출력을 위한 데이터 가공
  • 예를 들어 다음의 상황을 가정
    • AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다
    • 웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.
  • AWS EC2 인스턴스를 프로비저닝하기 위해 aws_instance 리소스 구성 시 앞서 확인한 프로비저너를 활용하여 웹서비스를 실행하고자 한다
  • 실습을 위해서 3.13 디렉터리를 신규 생성 후 main.tf 열기
    mkdir 3.13 && cd 3.13
    touch main.tf
  • 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"  # 각자 default VPC에 subnet ID 아무거나
      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"
    }

    • aws_eip가 생성되는 고정된 IP를 할당하기 위해서는 대상인 aws_instance의 id값이 필요하다
    • aws_instance의 프로비저너 동작에서는 aws_eip가 생성하는 속성 값인 public_ip가 필요하다
    • 실행 : 테라폼 구성 정의에서 상호 참조가 발생하는 상황으로, 실제 실행되는 코드를 작성하여 plan 수행 시 에러 발생
      # 두 리소스의 종속성이 상호 참조되어 발생하는 에러
      terraform init
      terraform plan
      Error: Cycle: aws_eip.myeip, aws_instance.example
  • main.tf 파일 내용 수정 : 둘 중 하나의 실행 시점을 한 단계 뒤로 미뤄야 한다.
    • 이런 경우 실행에 간격을 추가하여 실제 리소스와는 무관한 동작을 수행하기 위해 null_resource를 활용한다.
      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.0.100"
        key_name               = "kp-gasida" # 각자 자신의 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 "null_resource" "echomyeip" {
        provisioner "remote-exec" {
          connection {
            host = aws_eip.myeip.public_ip
            type = "ssh"
            user = "ubuntu"
            private_key =  file("/Users/gasida/.ssh/kp-gasida.pem") # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
            #password = "qwe123"
          }
          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 plan
    terraform init -upgrade
    
    # 실행 : EIP 할당 전 (임시) 유동 공인 IP 출력
    terraform plan
    terraform apply -auto-approve
    ...
    null_resource.echomyeip (remote-exec): Connected!
    null_resource.echomyeip (remote-exec): 13.125.25.238
    ...
    Outputs:
    eip = "13.125.25.238"
    public_ip = "43.201.63.58"
    
    #
    terraform state list
    terraform state show aws_eip.myeip
    terraform state show aws_instance.example
    terraform state show null_resource.echomyeip
    
    # graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
    terraform graph > graph.dot
    
    # 데이터소스 값 확인
    echo "aws_instance.example.public_ip" | terraform console
    echo "aws_eip.myeip.public_ip" | terraform console
    
    # 출력된 EC2 퍼블릭IP로 curl 접속 확인
    MYIP=$(terraform output -raw eip)
    while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
    
    # (임시) 유동 공인 IP로 SSH 접속이 될까요?

  • 삭제: terraform destroy -auto-approve
  • null_resource는 정의된 속성이 ‘id’가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 Plan 과정에서 실행 계획에 포함되지 못한다.
  • 따라서 사용자가 null_resource에 정의된 내용을 강제로 다시 실행하기 위한 인수로 trigger가 제공된다.
  • trigger는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 null_resource 내부에 정의된 행위를 다시 실행한다.
  • trigger 정의와 동작 예제

 

 

terraform_data: ‘잘가, null_resource’ -링크 링크2
  • 이 리소스 또한 자체적으로 아무것도 수행하지 않지만 null_resource는 별도의 프로바이더 구성이 필요하다는 점과 비교하여 추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공된다는 것이 장점이다.
  • 사용 시나리오는 기본 null_resourcr와 동일하며 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공된다.
  • triggers_replace에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다
  • terraform_data 리소스의 trigger_replace 정의와 동작 예제
    resource "aws_instance" "web" {
      # ...
    }
    
    resource "aws_instance" "database" {
      # ...
    }
    
    # A use-case for terraform_data is as a do-nothing container
    # for arbitrary actions taken by a provisioner.
    resource "terraform_data" "bootstrap" {
      triggers_replace = [
        aws_instance.web.id,
        aws_instance.database.id
      ]
    
      provisioner "local-exec" {
        command = "bootstrap-hosts.sh"
      }
    }

    resource "terraform_data" "foo" {
      triggers_replace = [
        aws_instance.foo.id,
        aws_instance.bar.id
      ]
    
      input = "world"
    }
    
    output "terraform_data_output" {
      value = terraform_data.foo.output  # 출력 결과는 "world"
    }