공부하면서/Terraform

[T1012] 3주차 - 테라폼 기본 사용 3/3 (조건문,함수,프로비저너)

omelette master 2023. 7. 21. 16:37
해당 내용은 T1012 스터디에 나온 내용과 '테라폼으로 시작하는 IaC' 책을 기준으로 정리 했습니다

3.10 조건문

테라폼에서의 조건식은 3항 연산자 형태를 갖는다. 조건은 true 또는 false 로 확인되는 모든 표현식을 사용할 수 있다
 

Conditional Expressions - Configuration Language | Terraform | HashiCorp Developer

Conditional expressions select one of two values. You can use them to define defaults to replace invalid values.

developer.hashicorp.com

  • 일반적으로 비교, 논리 연산자를 사용해 조건을 확인
  • 조건식은 "?" 기호를 기준으로 왼쪽은 조건
  • 오른쪽은 ":" 기호를 기준으로 왼쪽은 조건에 대해 옳은경우에 대하여 반환, 오른쪽은 틀린 경우에 대한 반환
# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"

조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행시 조건 비교를 위해 형태를 추론하여 자동으로 변환을 한다

그렇기 때문에 명시적인 형태 작성을 권장!

# 조건식 형태 권장 사항 
var.example ? 12 : "hello"            # 비권장
var.example ? "12" : "hello"          # 권장
var.example ? tostring(12) : "hello"  # 권장
  • 조건식은 단순히 특정 속성에 대한 정의, 로컬 변수에 대한 재정의, 출력 값에 대한 조건 정의 뿐만 아니라
    리소스 생성 여부에 응용할 수 있다.
  • count에 조건식을 결합한 경우 다음과 같이 특정 조건에 따라 리소스 생성 여부를 선택할 수 있다.
# 실습 환경 구성
mkdir 3.10 && cd 3.10

# main.tf 파일 생성
variable "enable_file" {
  default = true
}

resource "local_file" "foo" {
  count    = var.enable_file ? 1 : 0
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

output "content" {
  value = var.enable_file ? local_file.foo[0].content : ""
}

 

var.enable_file 의 내용이 true 이기 때문에 foo.bar 의 파일 내용은 foo!로 생성이 되겠지만...

false 일 경우에 대해 먼저 진행을 해본다

 

 

[T1012] 2주차 - 테라폼 기본 사용 2/3 (입력변수)

해당 내용은 T1012 스터디에 나온 내용과 '테라폼으로 시작하는 IaC' 책을 기준으로 정리 했습니다 실습 환경 mkdir 3.5 && cd 3.5 3.6 입력 변수 Variable 입력변수는 인프라를 구성하는데 필요한 속성 값

portnumber.tistory.com

# 변수 우선순위3 : 환경 변수 (TF_VAR 변수 이름)
export TF_VAR_enable_file=false
export | grep TF_VAR_enable_file

# 
terraform init && terraform plan && terraform apply -auto-approve
terraform state list

보다시피 false 인경우 foo.bar가 생성되지 않았다

다음은 다시 정상 진행을 위해 TF_VAR 변수를 해제후 진행

# 환경 변수 삭제
unset TF_VAR_enable_file
export | grep TF_VAR_enable_file

# 재실행
terraform plan && terraform apply -auto-approve
terraform state list

#
echo "local_file.foo[0]" | terraform console
echo "local_file.foo[0].content" | terraform console

3.11 함수

테라폼은 프로그래밍 언어적인 특성을 가지고 있어서, 값의 유형을 변경하거나
조합할 수 있는 내장 함수 를 사용 할 수 있다
 

Functions - Configuration Language | Terraform | HashiCorp Developer

An introduction to the built-in functions that you can use to transform and combine values in expressions.

developer.hashicorp.com

  • 단, 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지는 않는다.
  • 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
  • 테라폼 코드에 함수를 적용하면 변수, 리소스 속성, 데이터 소스 속성, 출력 값 표현 시 작업을 동적이고 효과적으로 수행할 수 있다.
# 실습 환경 구성
mkdir 3.11 && cd 3.11

# 내장함수인 upper를 이용해 대문자로 content 내용 생성하기
# main.tf 파일 생성
resource "local_file" "foo" {
  content  = upper("foo! bar!")
  filename = "${path.module}/foo.bar"
}

실행

#
terraform init && terraform plan && terraform apply -auto-approve
cat foo.bar ; echo

# 내장 함수 간단 사용
terraform console
>
-----------------
upper("foo!")
max(5, 12, 9)
lower(local_file.foo.content)
upper(local_file.foo.content)

cidrnetmask("172.16.0.0/12")

exit
-----------------

3.12 프로비저너

프로비저너는 프로바이더와 비슷하게 '제공자'로 해석되나,
프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행
 

Provisioners | Terraform | HashiCorp Developer

Provisioners run scripts on a local or remote machine during resource creation or destruction. Learn how to declare provisioners in a configuration.

developer.hashicorp.com

  • 예를 들어 AWS EC2 생성 후 특정 패키지를 설치해야 하거나 파일을 생성해야 하는 경우, 이것들은 테라폼의 구성과 별개로 동작해야 한다.
  • 프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다 ⇒ 선언적 보장 안됨
  • 따라서 프로비저너 사용을 최소화하는 것이 좋다. 프로비저너의 종류에는 파일 복사와 명령어 실행을 위한 file,
    local-exec, remote-exec
    가 있다.
  • 재미있는점은 최후의 수단으로 사용하길 공식문서에 나와있다는 것
    Even if your specific use-case is not described in the following sections, we still recommend attempting to solve it using other techniques first, and use provisioners only if there is no other option.

프로비저너 예제

# 실습 환경 구성
mkdir 3.12 && cd 3.12

# 프로비저너의 경우 리소스 프로비저닝 이후 동작하도록 구성이 가능
# 예를 들어 AWS EC2 생성후 CLI를 통해 별도 작업 수행 상황을 가정

# main.tf 생성
variable "sensitive_content" {
  default   = "secret"
  #sensitive = true
}

resource "local_file" "foo" {
  content  = upper(var.sensitive_content)
  filename = "${path.module}/foo.bar"

  provisioner "local-exec" {
    command = "echo The content is ${self.content}"
  }

  provisioner "local-exec" {
    command    = "abc"
    on_failure = continue # 실패해도 계속 진행
  }

  provisioner "local-exec" {
    when    = destroy
    command = "echo The deleting filename is ${self.filename}"
  }
}

실행

terraform init && terraform plan

terraform apply -auto-approve

# 테라폼 상태에 프로비저너 정보(실행 및 결과)가 없다
terraform state list
terraform state show local_file.foo
cat foo.bar ; echo

실행시 중간에 "/bin/sh: 1: abc: not found" 라는 오류를  확인할수 있는데 on_failure옵션 덕분에 계속 진행 되고

특정 변수를 사용하여 출력되는 프로비저너에 대해 모든 내용이 출력됨을 알수있다

다음은 프로비저너에서 특정 변수 사용시 출력 방지와, 실패시 중지에 대해 수정

variable "sensitive_content" {
  default   = "secret"
  sensitive = true # 이곳을 주석 해제
}

resource "local_file" "foo" {
  content  = upper(var.sensitive_content)
  filename = "${path.module}/foo.bar"

  provisioner "local-exec" {
    command = "echo The content is ${self.content}"
  }

  provisioner "local-exec" {
    command    = "abc"
    #on_failure = continue # 이걸 주석
  }

  provisioner "local-exec" {
    when    = destroy
    command = "echo The deleting filename is ${self.filename}"
  }
}

"output suppressed due to sensitive value in config" 라는 출력으로 바뀌었고

abc 명령 not found 에러가 발생한후 중지됨을 확인할수 있다

local-exec 프로비저너

테라폼이 실행되는 환경에서 수행할 커맨드를 정의

 

 

Provisioner: local-exec | Terraform | HashiCorp Developer

The `local-exec` provisioner invokes a local executable after a resource is created. This invokes a process on the machine running Terraform, not on the resource. See the `remote-exec` provisioner to run commands on the resource.

developer.hashicorp.com

  • 리눅스나 윈도우등 테라폼을 실행하는 환경에 맞게 커맨드를 정의, 아래 사용하는 인수 값은 다음과 같다
[ 필수인수 ]
command : 실행할 명령줄을 입력하며 << 연산자를 통해 여러 줄의 커맨드 입력 가능

[ 선택적 인수 ]
working_dir: command의 명령을 실행할 디렉터리를 지정해야 하고 상대/절대 경로로 설정
interpreter: 명령을 실행하는 데 필요한 인터프리터를 지정하며, 첫 번째 인수로 인터프리터 이름이고 두 번째부터는 인터프리터 인수 값
environment: 실행 시 환경 변수 는 실행 환경의 값을 상속받으면, 추가 또는 재할당하려는 경우 해당 인수에 key = value 형태로 설정
  • OS 환경에 맞는 코드 예시
Unix/Linux/macOS Windows
resource "null_resource" "example1" {
  
  provisioner "local-exec" {
    command = <<EOF
      echo Hello!! > file.txt
      echo $ENV >> file.txt
      EOF
    
    interpreter = [ "bash" , "-c" ]

    working_dir = "/tmp"

    environment = {
      ENV = "world!!"
    }

  }
}
resource "null_resource" "example1" {
  
  provisioner "local-exec" {
    command = <<EOF
      Hello!! > file.txt
      Get-ChildItem Env:ENV >> file.txt
      EOF
    
    interpreter = [ "PowerShell" , "-Command" ]

    working_dir = "C:\\windows\temp"

    environment = {
      ENV = "world!!"
    }

  }
}

예제를 따라하기 전에 Windows 에서 WSL를 사용한 환경에서

vscode로 작성하다 CRLF 형식으로 줄바꿈이 이루어 지는데

그렇기 때문에 WSL에서는 "\r\n" 으로 인식하여 잘못된 결과로 나오니, LF 형식인지 확인 합니다

아니면 WSL 자체에서 진행하면 됩니다.

CRLF 형식으로 진행된 경우...

resource "null_resource" "example1" {
  
  provisioner "local-exec" {
    command = <<EOF
      echo Hello!! > file.txt
      echo $ENV >> file.txt
      EOF
    
    interpreter = [ "bash" , "-c" ]

    working_dir = "/tmp"

    environment = {
      ENV = "world!!"
    }

  }
}

실행

# 초기화
terraform init -upgrade

# 실행
terraform plan && terraform apply -auto-approve

# 확인
terraform state list
terraform state show null_resource.example1
cat /tmp/file.txt

connection 블록

remote-exec와 file 프로비저너를 사용하기 위해 원격지에 연결할 SSH, WinRM 연결 정의가 필요
테라폼 코드를 실행하는 곳에서 SSH, 혹은 bastion 서버를 통해 실행할수도 있다

 

 

Provisioner: remote-exec | Terraform | HashiCorp Developer

The `remote-exec` provisioner invokes a script on a remote resource after it is created. This can be used to run a configuration management tool, bootstrap into a cluster, etc. To invoke a local process, see the `local-exec` provisioner instead. The `remot

developer.hashicorp.com

  • connection 블록 리소스 선언 시, 해당 리소스 내에 구성된 프로비저너에 대해 공통으로 선언되고,
    프로비저너 내에 선언되는 경우, 해당 프로비저너에서만 적용된다.
# connection 블록으로 원격지 연결 정의
resource "null_resource" "example1" {
  
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = var.host
  }

  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
  }

  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "C:/App/myapp.conf"

    connection {
        type     = "winrm"
        user     = "Administrator"
        password = var.admin_password
        host     = var.host
    }
  }
}

connection 에 사용하는 인수와 설명은 다음 사이트를 참고!

 

Provisioner Connection Settings | Terraform | HashiCorp Developer

The connection block allows you to manage provisioner connection defaults for SSH and WinRM.

developer.hashicorp.com

file 프로비저너

테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는 데 사용
[ 사용되는 인수 ]
source: 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상태 경로 또는 절대 경로로 지정할 수 있다. content와 함께 사용할 수 없다.
content: 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없다.
destination: 필수 항목으로 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리다.

주의할 점으로 ssh로 연결할경우 destination의 경로를 지정할때 디렉터리가 존재하는 상태여야하며
winrm으로 연결할경우 디렉터리가 없으면 자동으로 생성

그리고 source 경로 형태에 따라 동작이 바뀌는데...

source가 다음과 같은 구조를 가질경우

[source]
/foo
├── file01
├── file02
└── source01
    └── s-file01

예시1)
source: /foo
destination: /tmp

[결과]
/tmp
└── foo
    ├── file01
    ├── file02
    └── source01
        └── s-file01

예시2) source의 마지막에 "/"
source: /foo/
destination: /tmp

[결과]
/tmp
├── file01
├── file02
└── source01
    └── s-file01

file 프로비저너 구성 예

resource "null_resource" "foo" {
  
  # myapp.conf 파일이 /etc/myapp.conf 로 업로드
  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
  }
  
  # content의 내용이 /tmp/file.log 파일로 생성
  provisioner "file" {
    content     = "ami used: ${self.ami}"
    destination = "/tmp/file.log"
  }
  
  # configs.d 디렉터리가 /etc/configs.d 로 업로드
  provisioner "file" {
    source      = "conf/configs.d"
    destination = "/etc"
  }
  
  # apps/app1 디렉터리 내의 파일들만 D:/IIS/webapp1 디렉터리 내에 업로드
  provisioner "file" {
    source      = "apps/app1/"
    destination = "D:/IIS/webapp1"
  }

}

remote-exec 프로비저너

원격지 환경에서 실행할 커맨드와 스크립트를 정의
AWS의 EC2 인스턴스를 생성하고 해당 인스턴스에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미
 

Provisioner: remote-exec | Terraform | HashiCorp Developer

The `remote-exec` provisioner invokes a script on a remote resource after it is created. This can be used to run a configuration management tool, bootstrap into a cluster, etc. To invoke a local process, see the `local-exec` provisioner instead. The `remot

developer.hashicorp.com

[ 사용되는 인수 ]
inline : 명령에 대한 목록으로 [ ] 블록 내에 " "로 묶인 다수의 명령을 , 로 구분해 구성한다.
script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 " "로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다
  • script 또는 scripts의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없음
  • 인수가 필요할 때 file 프로바이더로 해당 스크립트를 업로드하고 inline 인수를 활용해 스크립트에 인수를 추가해야 함

remote-exec 프로비저너 구성 예

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

  # Establishes connection to be used by all
  # generic remote provisioners (i.e. file/remote-exec)
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = self.public_ip
  }

  provisioner "file" {
    source      = "script.sh"
    destination = "/tmp/script.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh args",
    ]
  }
}