3.10 조건문

  • 테라폼의 조건식은 3학 연산자 형태
  • true, false로 확인된는 모든 표현식 사용 가능
  • 비교대상의 형태가 다르면 자동 변환해서 비교 -> 명시적 형태로 해야 오류가 없다
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 : ""
}

3.11 함수

  • 테라폼은 프로그래밍 언어적 특성을 지닌다
    -> 값의 유형을 변경하거나 조합할 수 있는 내장 함수 사용 가능 : Built-in Functions
  • 별도의 사용자 정의 함수를 지원하지는 않는다
  • 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
  • 테라폼 코드에 함수를 적용하면 변수, 리소스 속성, 데이터 소스 속성, 출력 값 표현 시 작업을 동적이고 효과적으로 수행할 수 있다
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)

cidrnetmask("172.16.0.0/12")

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

3.12 프로비저너

  • 프로바이더와 비슷하게 '제공자'로 해석 되나 다름
  • 프로바이더로 실행되지 않는 커맨드, 파일 복사와 같은 역할 수행 : Provisioners
  • local-exec Provisioner : 테라폼이 실행되는 환경에서 수행할 커맨드를 정의 
  • 리눅스나 윈도우 등 테라폼을 실행하는 환경에 맞게 커맨드를 정의
  • 사용 인수
    • command(req)
      • 실행할 명령어 라인을 입력한다
      • `<<` 연산자를 통해 여러 줄의 커맨드를 입력 가능하다.
    • working_dir(option)
      • command의 명령을 실행할 디렉터리를 지정
      • 상대/절대 경로 설정
    • intgerpreter(option)
      • command의 명령을 실행하는데 필요한 인터프리터를 지정
      • 첫번째 인수는 인터프리터 이름, 그 이후부터는 인터프리터 인수 값
    • environment(option)
      • 실행시 환경 변수는 실행 환경의 값을 상속 받는다
      • 추가 재할당시 해당 인수에 key=value 형태로 설정한다
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
...
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"]
...

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

remote-exec Provisioner(원격지 연결)

  • remote-exec, file 프로비저너를 사용하기 위해서는 원격지에 연결한 SSH, WinRM 연결 정의 필요
  • 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
    }
  }
}

Provisioner Connection Settings(커넥션 적용 세팅)

Conbnection 적용 인수와 설명

 

원격 연결이 요구되는 프로비저너의 경우
->스크립트 파일을 원격 시스템에 업로드 해 해당 시스템의 기본 쉘에서 실행하도록 한다.
-> script_path 경우 적절한 위치를 지정하도록 한다.
-> 경로는 난수인 `%RAND%` 경로가 포함되어 생성된다.

Unix/Linux/macOS : /tmp/terraform_%RAND%.sh
Windows(cmd) : C:/windows/temp/terraform_%RAND%.cmd
Windows(PowerShell) : C:/windows/temp/terraform_%RAND%.ps1

Connecting through a Bastion Host with SSH(배스천 호스트를 통해 연결)

connection 적용 시 Bastion 호스트를 설정하는 인수와 설명3.1

3.12.4 file 프로비저너

  • 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는데 사용
  • 사용되는 인수
    • source : 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상대 경로 또는 절대 경로로 지정할 수 있다. content와 함께 사용할 수 없다.
    • content : 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없다.
    • destination : 필수 항목으로 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리다.
  • destination 지정 시 주의해야 할 점은 winrm 연결은 디렉터리가 없는 경우 자동으로 생성하지만, ssh 연결의 경우 대상 디렉터리가 존재해야 한다.
  • 디렉터리를 대상으로 하는 경우에는 source 경로 형태에 따라 동작에 차이가 생긴다.
  • destination이 /tmp인 경우 source가 디렉터리로 /foo 처럼 마지막에 /가 없는 경우 대상 디렉터리에 지정한 디렉터리가 업로드되어 연결된 시스템에 /tmp/foo 디렉터리가 업로드된다.
  • source가 디렉터리로 /foo/ 처럼 마지막에 /가 포함되는 경우 source 디렉터리 내의 파일만 /tmp 디렉터리에 업로드된다.

예시 - 파일 프로비저너 구성 예시

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

}

3.12.5 remote - exec Provisioner

  • 예 - AWS의 EC2 인스턴스를 생성 해당 VM에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미한다.
  • 사용하는 인수는 다음과 같고 각 인수는 서로 배타적이다.
    • inline : 명령에 대한 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 명령을 , 로 구분해 구성한다.
    • script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
    • scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다
  • script 또는 scripts의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없으므로 필요할 때 file 프로바이더로 해당 스크립트를 업로드하고 inline 인수를 활용해 스크립트에 인수를 추가한다.

구성예시

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

3.13 null_resource와 terraform_data

테라폼 1.4가 릴리즈 되면서 기존 null_resource를 대체하는 terraform_data리소스가 추가 되었다.
두 리소스의 의미와 사용 방식 확인 필요
  • null_resource - 아무 작업도 수행하지 않는 리소스 구현
    • 이런 리소스가 필요한 이유??
      • 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생함
      • 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문.
    • 주로 사용되는 시나리오
      • 프로비저닝 수행 과정에서 명령어 실행
      • 프로비저너와 함께 사용
      • 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
      • 출력을 위한 데이터 가공
    • 예를 들어 다음의 상황을 가정
      • AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다
      • 웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.
    • AWS EC2 인스턴스를 프로비저닝하기 위해 aws_instance 리소스 구성 시 앞서 확인한 프로비저너를 활용하여 웹서비스를 실행하고자 한다

예시

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"
}
  • aws_eip가 생성되는 고정된 IP를 할당하기 위해서는 대상인 aws_instance의 id값이 필요하다
  • aws_instance의 프로비저너 동작에서는 aws_eip가 생성하는 속성 값인 public_ip가 필요하다
  • 상호 참조 발생
    • 테라폼 구성 정의에서 상호 참조가 발생하는 상황
    • 실제 코드에서 plan 을 수행하면 에러가 발생한다.

수정 - 둘 중 하나의 실행 시점을 한 단계 뒤로 미룬다 -> 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-0b92d8356a0cbca38"
  private_ip             = "172.31.0.100"
  key_name               = "kp-kaje" # 각자 자신의 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("/home/kaje/kp-kaje.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 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
  • 삭제: `terraform destroy -auto-approve`
  • null_resource는 정의된 속성이 ‘id’가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 Plan 과정에서 실행 계획에 포함되지 못한다.
  • 따라서 사용자가 null_resource에 정의된 내용을 강제로 다시 실행하기 위한 인수로 trigger가 제공된다.
  • trigger는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 null_resource 내부에 정의된 행위를 다시 실행한다.]
  • trigger 예시
resource "null_resource" "foo" {
  triggers = {
    ec2_id = aws_instance.bar.id # instance의 id가 변경되는 경우 재실행
  }
  ...생략...
}

resource "null_resource" "bar" {
  triggers = {
    ec2_id = time() # 테라폼으로 실행 계획을 생성할 떄마다 재실행
  }
  ...생략...
}

terraform_data (번역)

  • 이 리소스 또한 자체적으로 아무것도 수행하지 않는다.
  • null_resource는 별도의 프로바이더 구성이 필요하다는 점과 비교하여
    추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공된다는 것이 장점이다.
  • 사용 시나리오는 기본 null_resource와 동일하며 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공된다.
  • triggers_replace에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다
  • terraform_data 리소스의 trigger_replace 정의와 동작 예제
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"
}

3.14 moved block

  • 테라폼의 State에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성됨을
    앞의 설명에서 확인.
  • 하지만 테라폼 리소스를 선언하다 보면 이름을 변경해야 하는 상황이 발생하기도 하는데, 예를 들면 다음과 같다.
    • 리소스 이름을 변경
    • count로 처리하던 반복문을 for_each로 변경
    • 리소스가 모듈로 이동하여 참조되는 주소가 변경
  • 리소스의 이름은 변경되지만 이미 테라폼으로 프로비저닝된 환경을 그대로 유지하고자 하는 경우 테라폼 1.1 버전부터 moved 블록을 사용할 수 있다.
  • ‘moved’라는 단어가 의미하는 것처럼 테라폼 State에서 옮겨진 대상의 이전 주소와 새 주소를 알리는 역할을 수행한다.
  • moved 블록 이전에는 State를 직접 편집하는 terraform state mv 명령을 사용하여 State를 건드려야 하는 부담이 있었다면, moved 블록은 State에 접근 권한이 없는 사용자라도 변경되는 주소를 리소스 영향 없이 반영할 수 있다.

 

3.15 System Environment Variables for CLI

  • 테라폼은 환경 변수를 통해 실행 방식과 출력 내용에 대한 옵션을 조절 가능
    • 시스템 환경 변수 설정으로, 영구적으로 로컬 환경에 적용되는 옵션이나 별도 서버 환경에서 실행하기 위한 옵션을 부여 가능.
    • 이를 통한 로컬 작업 환경과 다른 환경 구성에서만 사용될 특정 옵션을 적용.
Mac/리눅스/유닉스: export <환경 변수 이름>=<값>
Windows CMD: set <환경 변수 이름>=<값> 
Windows PowerShell: $Env:<환경 변수 이름>='<값>'

3.15.1 TF_LOG

  • 테라폼의 stderr 로그에 대한 레벨을 정의
    • trace, debug, info, warn, error, off를 설정할 수 있고 관련 환경 변수가 없는 경우 off와 동일하다
    • 디버깅을 위한 로그 관련 환경 변수 설명은 다음과 같다
      • TF_LOG: 로깅 레벨 지정 또는 해제
      • TF_LOG_PATH: 로그 출력 파일 위치 지정
      • TF_LOG_CORE: TF_LOG와 별도로 테라폼 자체 코어에 대한 로깅 레벨 지정 또는 해제
      • TF_LOG_PROVIDER: TF_LOG와 별도로 테라폼에서 사용하는 프로바이더에 대한 로깅 레벨 지정 또는 해제
    • 환경에 맞게 TF_LOG를 info로 설정하고, terraform plan 동작을 실행하면 테라폼 출력에 관련 로그가 출력된다
      (TF_LOG=info terraform plan ...)

3.15.2 TF_INPUT

  •  값을 false 또는 0으로 설정하면 테라폼 실행 시 인수에 -input=false 를 추가한 것과 동일한 수행 결과를 확인
    • 환경에 맞게 TF_INPUT을 0으로 설정하고 terraform plan 동작 실행하면 입력받는 동작을 수행하지 않으므로 입력 변수를 입력해야 하는 경우 에러가 출력된다
    • TF_INPUT=0 terraform plan Error : No value for required variable

3.15.3 TF_VAR_name 

TF_VAR_<변수 이름>을 사용하면 입력 시 또는 default로 선언된 변수 값을 대체한다 ← 3.6절에서 확인!

# 3.15.4
# TF_CLI_ARGS="-input=false" terraform apply -auto-approve 는 terraform apply -input=false -auto-approve 와 같다
TF_CLI_ARGS="-input=false" terraform apply -auto-approve
Error: No value for required variable

# TF_CLI_ARGS_apply로 인수를 정의하면 terraform apply 커맨드 수행 시에만 동작한다
export TF_CLI_ARGS_apply="-input=false"
terraform apply -auto-approve
<에러>

terraform plan
<정상 계획 예측 출력>

3.15.5 TF_DATA_DIR

State 저장 백엔드 설정과 같은 작업 디렉터리별 데이터를 보관하는 위치를 지정

  • 데이터는 .terraform 디렉터리 위치에 기록되지만 TF_DATA_DIR에 경로가 정의되면 기본 경로를 대체하여 사용된다.
  • 일관된 테라폼 사용을 위해서 해당 변수는 실행 시마다 일관되게 적용될 수 있도록 설정하는 것이 중요하다.
  • 설정 값이 이전 실행 시에만 적용되는 경우 init 명령으로 수행된 모듈, 아티팩트 등의 파일을 찾지 못한다.
  • 이미 terraform init이 수행된 상태에서 TF_DATA_DIR로 경로를 재지정하고 실행하는 경우 플러그인 설치가 필요하다는 메시지 출력을 확인할 수 있다.
TF_DATA_DIR=./.terraform_tmp terraform plan
Error: Required plugins anr not installed
블로그 이미지

감동맨

rkaehdaos의 블로그

,