해당 내용은 cloudNet@ 팀의 가시다 님이 진행하는 테라폼 스터디 T101 4기에서 다룬 내용과 "테라폼으로 시작하는 IaC" (한빛미디어) 저서 내용을 정리한 것입니다.
10. 조건문
Terraform 에서 조건식은 3항 연산자 형태를 가진다. 조건은 true / false 로 확인 되는 모든 표현식을 사용할 수 있다.
- 일반적으로 비교, 논리 연산자를 사용해 조건을 확인한다.
- 조건식은 ? 기호를 기준으로 왼쪽은 조건이며,
오른쪽은 : 기호를 기준으로 왼쪽이 조건에 대해 true 가 반환되는 경우이고 / 오른쪽은 false가 반환되는 경우다.
# <조건 정의 > ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "defalut-a"
- 위 예시에서 var.a 가 빈 문자열이 아니라면 var.a를 나타내고, 빈 문자열일 경우 "default-a"를 나타낸다.
- 조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행 시 조건 비교를 위해 형태를 추론하여 자동으로 변환하는데, 명시적은 형태 작성을 권장한다.
# 조건식 형태 권장 사항
var.example ? 12 : "hello" # 비권장
var.example ? "12" : "hello" # 권장
var.example ? tostring(12) : "hello" # 권장
- 조건식은 단순히 특정 속성에 대한 정의, 로컬 변수에 대한 재정의, 출력 값에 대한 조건 정의 뿐만 아니라 리소스 생성 여부에 응용할 수 있다.
count에 조건을 결합한 경우 다음과 같이 특정 조건에 따라 리소스 생성 여부를 선택할 수 있다. - 예제 코드
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 : ""
}
11. 함수
Terraform 은 프로그래밍 언어적은 특성을 가지고 있어서, 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용할 수 있다.
https://developer.hashicorp.com/terraform/language/functions
- 단, 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수 기능을 지원하지 않는다.
- 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
- Terraform 코드에 함수를 적용하면 변수, 리소스 속성, 데이터 소스 속성, 출력 값 표현 시 작업을 동적이고 효과적으로 할 수 있다.
- 예제 코드
resource "local_file" "foo" {
content = upper("foo! bar!")
filename = "${path.module}/foo.bar"
}
- terraform console 함수 사용 예제
12. 프로비저너
프로비저너는 프로바이더와 비슷하게 "제공자"로 해석되나, 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행한다.
- 다른 방안이 안되면, 최후의 수단으로 사용할 것.
- 프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다 ⇒ 선언적 보장 안됨
- 따라서 프로비저너 사용을 최소화하는 것이 좋다. 프로비저너의 종류에는 파일 복사와 명령어 실행을 위한 file, local-exec, remote-exec가 있다.
- 프로비저너의 경우 리소스 프로비저닝 이후 동작하도록 구성할 수 있다. 예를 들어 AWS EC2 생성 후 CLI를 통해 별도 작업 수행 상황을 가정
- 예제 코드
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 destroy -auto-approve
- local_exec 프로비저너 : Terraform 이 실행되는 환경에서 수행할 커맨드를 정의
- command(required) : 실행할 명령어
- working_dir(optional) : command를 실행할 경로
- interpreter(optional) : 명령을 실행하는데 필요한 인터프리터
- environment(optional) : 실행 시 환경변수는 실행 환경의 값을 상속받으면, 추가 또는 재할당하려는 경우 해당 인수에 key=value 형태로 설정
- 원격지 연결
- remote-exec와 file 프로비저너를 사용하기 위해 원격지에 연결할 SSH, WinRM 연결 정의가 필요하다.
- 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 = "/etc/myapp.conf"
connection {
type = "winrm"
user = "Administrator"
password = var.admin_password
host = var.host
}
}
}
host 와 admin_password 는 환경변수로 지정하면 된다.
- file 프로비저너 : 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉토리를 복사하는 데 사용한다.
- source : 소스 파일 또는 디렉토리, 현재 작업 중인 디렉토리에서 상태 경로 또는 절대 경로로 지정할 수 있다.
content와 함께 사용할 수 없다. - content : 연결 대상에 복사할 내용을 정의하며 대상이 디렉토리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source 와 함께 사용할 수 없다.
- destination(required) : 항상 절대 경로로 지정되어야 한다.
- source : 소스 파일 또는 디렉토리, 현재 작업 중인 디렉토리에서 상태 경로 또는 절대 경로로 지정할 수 있다.
- remote_exec 프로비저너 : 원격지 환경에서 실행할 커맨드와 스크립트를 정의
- inline : 명령에 대한 목록으로 [] 블록 내에 " " 로 묶인 다수의 명령을 , 로 구분해 구성한다.
- script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
- scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 " "로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다.
- script 나 scripts에 포함된 파일을 실행하는 명령어는 inline 인수에 정의하고, 파일 복사는 file 프로바이더를 활용한다.
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",
]
}
}
13. null_resource 와 terraform_data
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 = "t101-smlim" # 각자 자신의 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/smlim/.ssh/t101-smlim.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_data
- 이 리소스 또한 자체적으로 아무것도 수행하지 않지만 null_resource는 별도의 프로바이더 구성이 필요하다는 점과 비교하여 추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공된다는 것이 장점
- 사용 시나리오는 null_resource와 동일하며, 강제 재실행을 위한 trigger_replace와 상태저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공된다.
- 예제 코드
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"
}
14. moved 블록
- 테라폼의 state에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성된다.
- 하지만 이름만 변경해야 하는 상황이 발생한다.
- 리소스 이름을 변경
- count 로 처리하던 반복문을 for_each로 변경
- 리소스가 모듈로 이동하여 참조되는 주소가 변경
- 리소스의 이름은 변경되지만 이미 Terraform 으로 프로비저닝 된 환경을 그대로 유지하고자 하는 경우 테라폼 1.1 버전부터 moved 블록을 사용할 수 있다.
- moved 블록 이전에는 state 를 직접 편집하는 terraform state mv 명령을 사용하여 state를 직접 건드려야 하는 부담이 있었다면, moved 블록은 state에 접근권한이 없는 사용자라도 변경되는 주소를 리소스 영향 없이 반영할 수 있다.
- 예제 코드
resource "local_file" "a" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.a.content
}
foo.bar 파일이 생성됨
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
}
local_file "a"의 이름을 "b" 로 변경하면서 moved 블록을 선언
본래의 동작이라면 local_file "a"를 삭제하고 local_file "b"를 새로 생성하는 과정을 거쳤겠지만 moved block을 사용함으로써 이름만 변경되는 것을 확인할 수 있다.
echo "local_file.a.id" | terraform console
# moved 블록 적용전 a 파일의 id 와 적용 후 b 파일의 id 확인
echo "local_file.b.id" | terraform console
15. CLI 를 위한 시스템 환경 변수
TF_LOG
- 테라폼 stderr 로그에 대한 레벨을 정의
- trace, debug, info, warn, error, off를 설정할 수 있다.
- 디버깅을 위한 로그 관련 환경 변수 설명은 다음과 같다.
- TF_LOG : 로깅 레벨 지정 또는 해제
- TF_LOG_PATH : 로그 출력 파일 위치 지정
- TF_LOG_CORE : 별도로 테라폼 자체 코어에 대한 로깅 레벨 지정 또는 해제
- TF_LOG_PROVIDER : TF_LOG와 별도로 테라폼에서 사용하는 프로바이더에 대한 로깅 레벨 지정
TF_LOG=info terraform plan
...
TF_INPUT
- 환경에 맞게 TF_INPUT을 0으로 설정하고 terraform plan 동작 실행하면 입력받는 동작을 수행하지 않으므로 입력 변수를 입력해야 하는 경우 에러가 출력된다
TF_INPUT=0 terraform plan
Error : No value for required variable
TF_VAR_name
- terraform 코드 내에 변수를 지정
TF_CLI_ARGS / TF_CLI_ARGS_subcommand
# 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
<정상 계획 예측 출력>
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
'DevOps' 카테고리의 다른 글
[T1014-이론] 5장 State (0) | 2024.07.07 |
---|---|
[T1014-이론] 4장 프로바이더 (0) | 2024.07.07 |
[T1014-이론] 3장 기본 사용법 (5) (0) | 2024.06.30 |
[T1014-이론] 3장 기본 사용법 (4) (0) | 2024.06.22 |
[T1014-이론] 3장 기본 사용법 (3) (0) | 2024.06.20 |