✅ 이 블로깅은 테라폼으로 시작하는 IaC 책을 기반으로 작성한다.

조건문

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

일반적으로 비교, 논리 연산자를 사용한다.

# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"

일반 프밍 언어와 비슷한 형태이다.

# 조건식 형태 권장 사항 
var.example ? 12 : "hello"            # 비권장
var.example ? "12" : "hello"          # 권장
var.example ? tostring(12) : "hello"  # 권장

조건식 형태에는 권장 사항도 있다. 보통은 형태가 잘못되어도 자동 변환해 주지만 권장 사항대로 조건식을 사용해 주자!

예제 ) 리소스 생성 여부 결정에 활용

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 : ""
}

main.tf를 작성하고 terraform init 한다.

위 파일은 var.enable_file 값에 의해 파일이 생성되거나 또는 생성되지 않는다.

  1. default 값 true 사용

     $ ls 
     	foo.bar  main.tf  terraform.tfstate
     $ cat foo.bar 
     	foo!
    

    일단 default 값은 true이기 때문에 처음에는 foo!라는 내용을 가진 foo.bar 파일 하나가 생성된다.

  2. 환경 변수로 false 지정

     $ export TF_VAR_enable_file=false
     $ export | grep TF_VAR_enable_file
     	declare -x TF_VAR_enable_file="false"
    

    false로 다시 apply 해 본다면

     Outputs:
        
     content = ""
    

    Outputs은 null 값으로 출력되고

     $ ls
     	main.tf  terraform.tfstate  terraform.tfstate.backup
    

    마찬가지로 파일도 삭제된 걸 확인할 수 있었다

함수

테라폼은 내장 함수가 있다.

Functions - Configuration Language, Terraform, HashiCorp Developer

함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.

$ terraform console 
	> upper("foo!")
	"FOO!"
	> max(5,12,9)
	12

내장 함수를 간단하게 테스트해 보려면 위와 같이 terraform console로 확인하는 것을 추천한다!

프로비저너

프로비저너는 프로바이더와 비슷한 ‘제공자’로 해석되지만, 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행한다. 테라폼으로 인프라를 생성하고 나서 무언가를 실행할 수 있는 것인데, AWS EC2의 user data라든지 init script도 포함된다고 볼 수 있다.

Provisioners, Terraform, HashiCorp Developer

프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않아, 프로비저닝에 대한 결과는 선언적 보장이 되지 않는다.

따라서 프로비저너 사용은 최소화하는 것이 좋다. 프로비저너를 사용하지 않아도 다양한 방법이 있을테니 이는 고민하고 상황에 맞게 적절한 걸로 사용하면 될 거같다.

테라폼 코드에서 userdata, cloud-init, packer, provisioner connections 등을 사용할 수 있는 거 같다.

예제 ) local-exec

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}"
  }
}

main.tf를 작성하고 init & apply 한다.

  1. apply 시 1,2번 local-exec 실행

     local_file.foo: Creating...
     local_file.foo: Provisioning with 'local-exec'...
     local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "echo The content is SECRET"]
     local_file.foo (local-exec): The content is SECRET
     local_file.foo: Provisioning with 'local-exec'...
     local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "abc"]
     local_file.foo (local-exec): /bin/sh: 1: abc: not found
     local_file.foo: Creation complete after 0s [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
    

    첫 번째 local-exec를 실행한다.

    sensitive_content인 secret을 대문자로 바꾸고 The content is SECRET으로 출력한다.

    두 번째 local-exec를 실행한다.

    abc 커맨드를 실행하는데 abc라는 커맨드가 없어서 /bin/sh: 1: abc: not found 라는 에러를 출력한다. 원래 같으면 테라폼 실행은 실패로 동작해야 하지만, main.tf 파일에 on_failure = continue도 같이 설정해 줬기 때문에 에러가 발생해도 넘어갈 수 있다!

  2. destroy 시 3번 local-exec 실행

     local_file.foo: Destroying... [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
     local_file.foo: Provisioning with 'local-exec'...
     local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "echo The deleting filename is ./foo.bar"]
     local_file.foo (local-exec): The deleting filename is ./foo.bar
     local_file.foo: Destruction complete after 0s
    

    when = destroy가 적용되어 local-exec를 한번 실행하고 The deleting filename is ./foo.bar를 출력한다.

local-exec 프로비저너

Provisioner: local-exec, Terraform, HashiCorp Developer

local-exec는 말 그대로 생성된 리소스에서 exec로 커맨드를 수행할 수 있다.

command, working_dir, interpreter, environment를 사용할 수 있으며 command만 필수 인수 값이다.

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!!"
    }

  }
}

interpreter로 bash 쉘을 지정하고 /tmp 디렉토리에서 command를 실행한다. 환경변수도 활용해서 file.txt 파일을 만들었다.

null_resource.example1: Creating...
null_resource.example1: Provisioning with 'local-exec'...
null_resource.example1 (local-exec): Executing: ["bash" "-c" "      echo Hello!! > file.txt\n      echo $ENV >> file.txt\n"]
null_resource.example1: Creation complete after 0s [id=6360805182083979892]

local-exec 프로비저너를 통해 file.txt를 생성하는 커맨드라인을 확인할 수 있고

$ cat /tmp/file.txt 
	Hello!!
	world!!

내용도 정확하게 들어가 있는 것도 확인할 수 있었다!

원격지 연결

Provisioner: remote-exec, Terraform, HashiCorp Developer

remote-exec와 file 프로비저너를 사용하기 위해 원격지에 연결할 SSH, WinRM 연결 정의가 필요하다

# connection 블록으로 원격지 연결 정의
resource "null_resource" "example1" {
  
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = var.host
  }
}

connection 블록으로 원격 실행을 할 수 있다. ssh로 호스트에 접근한다고 보면 된다

ssh 또는 winrm 유형으로 접근할 수 있다.

file 프로비저너

테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는 데 사용할 수 있다.

Provisioner: file, Terraform, HashiCorp Developer

source, content, destination을 인수로 사용할 수 있으며, destination은 필수 항목이고 source 또는 content는 동시에 사용할 수 없고 둘 중에 하나만 사용해야 한다.

destination 사용할 때, 윈도우 쪽은 디렉토리 없어도 자동 생성이지만, ssh는 대상 디렉토리 경로대로 존재해야 사용할 수 있다.

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의 user data와 같다.

inline, script, scripts 인수를 사용할 수 있는데, inline은 명령에 대한 목록으로 다수의 명령을 사용할 수 있고, script는 로컬 스트립트 경로를 넣고 원격에 복사해 실행할 수 있다. (scripts는 다수의 스크립트로)

하지만 script 또는 scripts는 실행만 할 뿐 관련 구성에서 스크립트 실행에 필요한 인수 선언은 할 수 없다!

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",
    ]
  }
}

위 main.tf 에서는 file 프로비저너로 해당 스크립트를 업로드하고 inline 인수를 통해 script.sh 스크립트에 args 인수를 추가한다!

Categories:

Updated:

Leave a comment