조건문
Conditional Expressions 테라폼에서의 조건식은 3항 연산자 형태를 갖는다.
조건은 true / false로 확인되는 모든 표현식 사용이 가능하다. Docs
조건식은 ? 기호를 기준으로 왼쪽은 조건이며, 오른쪽은 : 기호를 기준으로 왼쪽이 조건에 대해 true가 반환되는 경우이고,
오른쪽이 false가 반환된다.
# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"
조건식을 통해 단순 변수 재정의나 출력 값에 대한 조건 정의 뿐만 아니라 리소스 생성 여무에 응용할 수 있다.
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 : ""
}
# 변수 우선순위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
echo "var.enable_file ? 1 : 0" | terraform console
# 환경 변수 삭제
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
echo "var.enable_file ? 1 : 0" | terraform console
OS 변수 우선순위가 높으므로 enable_file이 false가 되고, 조건문에 따라 파일이 생성되지 않는다.
OS 변수를 unset하여 코드에 있는 변수가 적용된 상태에서 코드를 실행하면 조건문에 따라 파일이 생성되는 것을 확인할 수 있다.
함수
테라폼은 프로그래밍 언어 특성을 가지고 있어, 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용 할 수 있다. Docs
- 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수는 지원하지 않는다.
- 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
upper 내장 함수 사용 예제
resource "local_file" "foo" {
content = upper("foo! bar!")
filename = "${path.module}/foor.bar"
}
Provisioner
프로비저너는 프로바이더와 비슷하게 '제공자'로 해석되지만 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행 Docs
Provisioners are a Last Resort (다른 방안이 안되면, 최후의 수단으로 사용)
- 예를 들어 AWS EC2 생성 후 특정 패키지를 설치해야 하거나, 파일을 생성해야 하는 경우, 이것들은 테라폼의 구성과 별대로 동작해야 한다.
- 프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다
=> 선언적 보장 안됨
프로비저너의 경우 리소스 프로비저닝 이후 동작하도록 구성할 수 있다.
variable "sensitive_content" {
default = "secret"
}
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 = fail
}
provisioner "local-exec" {
when = destroy
command = "echo The deleting filenam is ${self.filename}"
}
}
실행 시 로컬 파일 생성은 정상 생성되고, content도 대문자로 변환된다.
두번째 provisioner는 배포 시 오류가 확인되는 것을 확인할 수 있는데,
abc라는 커맨드가 없기 때문에 에러가 발생하고 있다.
Destroy 시 설정된 프로비저너 설정에 따라 출력 구문을 확인 할 수 있다.
- local-exec 프로비저너 : 테라폼이 실행되는 환경에서 수행할 커맨드를 정의
- 리눅스나 윈도우등 테라폼을 실행하는 환경에 맞게 커맨드를 정의, 아래 사용하는 인수 값
- command(필수) : 실행할 명령줄을 입력하며 << 연산자를 통해 여러 줄의 커맨드 입력 가능
- working_dir(선택) : command의 명령을 실행할 디렉터리를 지정해야 하고 상대/절대 경로로 설정
- interpreter(선택) : 명령을 실행하는 데 필요한 인터프리터를 지정하며, 첫 번째 인수로 인터프리터 이름이고,
두 번째부터는인터프리터 인수 값 - environment(선택) : 실행 시 환경 변수는 실행 환경의 값을 상속받으면, 추가 또는 재할당하려는 경우 해당 인수에 key = value 쌍의 맵이다.
실습 예제
resource "null_resource" "test" {
provisioner "local-exec" {
command = <<EOF
echo Hello! > file.txt
echo $ENV >> file.txt
EOF
interpreter = [ "bash", "-c" ]
working_dir = "${path.module}"
environment = {
ENV = "world"
}
}
}
remote-exec 프로비저너 : 원격지 환경에서 실행할 커맨드와 스크립트를 정의
- EC2 생성 후 실행되어야 하는 스크립트 실행과 같은 동작
- 사용하는 인수 (각 인수는 서로 배타적이다.)
- inline : 명령에 대한 목록으로 [] 블록 내에 "" 로 묶인 다수의 명령을, 로 구분해 구성한다.
- script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
- scripts : 로컬의 스크립트 경로의 목록으로 [] 블록 내에 ""로 묶인 다수의 스크립트 경로를, 로 구분해 구성한다.
구성 예시
resource "aws_instance" "test-vm" {
instance_type = t2.micro
ami = "ami-0e73d7a01dba794a4"
tags = {
Name = "test-vm"
}
connection {
type = "ssh"
user = "root"
password = var.root_password
host = self.public.ip
}
provisioner "file" {
source = "script.sh"
destination = "/usr/local/src/script.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /usr/local/src/script.sh",
"/usr/local/src/script.sh args",
]
}
}
remote-exec을 사용한 원격지 연결 실습
- connection 블럭 선언 시, 리소스 내에 구성된 프로비저너에게 공통으로 선언
- 프로비저너 내에 있을 경우 프로비저너 내에서만 적용
variable "root_password" {
default = "linuxpass!@$"
}
variable "admin_password" {
default = "winpass1$%"
}
variable "host" {
default = "127.0.0.1"
}
resource "null_resource" "test" {
# 공통 connection 설정
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 = "D:\webapp\myapp.conf"
# 프로비저너 내부에 적용되는 connection 설정
connection {
type = "winrm"
user = "administrator"
password = var.admin_password
host = var.host
}
}
}
- connection 적용 인수와 설명
file 프로비저너 : 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는데 사용
- terraform 실행 환경 > 대상 시스템으로 파일 또는 디렉터리를 송신한다.
파일 송신 시 connection 에서 사용하는 프로토콜을 사용한다. - 사용되는 인수
- source : 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상태 경로 또는 절대 경로로 지정할 수 있다.
- content : 연결 대상에 복사할 내용을 정의하며, 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고,
파일인 경우 해당 파일에 내용이 기록된다. - destination : 필수 항목으로 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리이다.
file 프로비저너 예제
resource "null_resource" "test" {
# myapp.conf 파일이 /etc/myapp.conf 로 업로드
provisioner "file" {
source = "conf/myapp.conf"
destination = "/etc/myapp.conf"
}
# content 내용을 원격지 경로에 생성하고 입력
provisioner "file" {
content = "ami used: ${self.ami}"
destination = "/tmp/file.log"
}
# configs.d 디렉터리가 /etc/configs.d로 업로드
provisioner "file" {
source = "conf/configs.d"
destination = "/etc"
}
# app1 디렉터리 하위 파일이 webapps 디렉터리 내로 업로드
provisioner "file" {
source = "apps/app1/"
destination = "/data/webapps"
}
}
null_resource와 terraform_data
테라폼 1.4 버전이 릴리즈되면서 기존 null_resource 리소스를 대체하는 terraform_data 리소스가 추가되었다.
- 이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생하여, 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문이다.
- 주로 사용되는 시나리오
- 프로비저닝 수행 과정에서 명령어 실행
- 프로비저너와 함께 사용
- 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
- 출력을 위한 데이터 가공
- 실습 예제 - EC2 프로비저닝 시 내부 설정 전 확인한 프로비저너를 활용하여 웹서비스를 실행한다.
provider "aws" {
region = "ap-northeast-2"
}
data "aws_ami" "amzn2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm*"]
}
}
resource "aws_security_group" "web-sg" {
name = "t101-sg"
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"]
}
egress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web-srv" {
ami = "${data.aws_ami.amzn2.id}"
instance_type = "t2.micro"
subnet_id = "subnet-0210e58904c6b4a91"
private_ip = "172.31.1.10"
key_name = "my-ec2-keypair.pem"
user_data = <<EOF
#!/bin/bash
echo "hello, t101 study" > index.html
nohup busybox httpd -f -p 80 &
EOF
tags = {
Name = "test-web-srv"
}
provisioner "remote-exec" {
inline = [
"echo ${aws_eip.ssungz-eip.public_ip}"
]
}
}
resource "aws_eip" "ssungz-eip" {
instance = aws_instance.web-srv.id
associate_with_private_ip = aws_instance.web-srv.private_ip
}
output "public_ip" {
value = aws_instance.web-srv.public_ip
description = "public ip of the instance"
}
배포 시 위와 같이 오류가 발생하는데, 테라폼 구성에서 상호 참조되어 에러가 발생한 것이다.
코드 수정
provider "aws" {
region = "ap-northeast-2"
}
data "aws_ami" "amzn2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm*"]
}
}
resource "aws_security_group" "web-sg" {
name = "t101-sg"
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"]
}
egress {
from_port = 0
to_port = 0
protocol = -1
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "web-srv" {
ami = "${data.aws_ami.amzn2.id}"
instance_type = "t2.micro"
subnet_id = "subnet-0210e58904c6b4a91"
private_ip = "172.31.1.10"
key_name = "my-ec2-keypair"
vpc_security_group_ids = [aws_security_group.web-sg.id]
user_data_replace_on_change = true
user_data = <<EOF
#!/bin/bash
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "hello, t101 study" > /var/www/html/index.html
EOF
tags = {
Name = "test-web-srv"
}
}
resource "null_resource" "pubip" {
provisioner "remote-exec" {
connection {
type = "ssh"
host = aws_eip.ssungz-eip.public_ip
user = "ec2-user"
private_key = file("/Users/ssungz/Desktop/study/my-ec2-keypair.pem")
}
inline = [
"echo ${aws_eip.ssungz-eip.public_ip}"
]
}
}
resource "aws_eip" "ssungz-eip" {
instance = aws_instance.web-srv.id
associate_with_private_ip = aws_instance.web-srv.private_ip
}
output "public_ip" {
value = aws_instance.web-srv.public_ip
description = "public ip of the instance"
}
output "eip" {
value = aws_eip.ssungz-eip.public_ip
description = "EIP OF the instance"
}
null resource에서 connect 시그널도 확인되었다.
terraform graph로 보면 아래와 같이 확인된다.
user_data에 설정한 웹 페이지에도 정상 접근되는 것을 확인했다.
- null_resource는 정의된 속성이 'id'가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 plan 과정에서 실행 계획에 포함되지 못한다.
- 따라서 사용자가 null_resource에 정의된 내용을 강제로 다시 실행하기 위한 인수로 trigger가 제공된다.
- trigger는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 null_resouce 내부에 정의된 액션을 다시 수행한다.
- trigger 사용 예제
resource "null_resource" "foo"{
triggers = {
ec2_id = aws_instance.bar.id # instance의 id가 변경되는 경우 재실행
}
}
...
resouce "null_resource" "bar" {
trigger = {
ec2_id = time() # 테라폼으로 실행 계획을 생성할 때마다 재실행
}
...
}
- terraform_data
- terraform_data 리소스도 null_resource와 같이 자체적으로는 아무런 수행도 하지 않지만,
별도의 프로바이더 구성이 필요하지 않다는 점과 테라폼 자체에 포함된 수명주기 관리자가 제공되는 것이 장점이다. - 사용 시나리오는 기본 null_resouce와 동일하며, 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 있다.
- trigger_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"
}
moved 블록
[리소스 이름 변경, count > for_each로 변경, 리소스가 모듈로 이동하여 참조 주소 변경]과 같은 상황이 있다.
이 경우 리소스의 이름은 변경되지만 배포된 환경을 유지하고자 할 경우 moved 블록을 사용할 수 있다.
사용 예제
resource "local_file" "a" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.a.content
}
3.14 ➤ echo "local_file.a.id" | terraform console
"4bf3e335199107182c6f7638efaad377acc7f452"
시나리오 : local_file "a" 라고 선언되어 배포된 이름을 "b" 로 변경하자
resource "local_file" "b" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.b.content
}
위와 같이 실행할 경우에 이름은 변경되겠지만 프로비저닝된 파일 역시 재생성이 된다.
이름만 변경할 것이기 때문에 이 방법은 요구 사항과 맞지 않는다.
그렇다면 moved 블록을 사용하여 확인해보겠다.
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
}
3.14 ➤ echo "local_file.b.id" | terraform console
"4bf3e335199107182c6f7638efaad377acc7f452"
local_file.a와 동일하게 id 값이 변경되지 않고, 이름만 변경된 것을 확인해 볼 수 있다.
마지막으로 선언했던 moved 블록을 삭제하고 작업을 완료한다.
resource "local_file" "b" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.b.content
}
'Cloud > Terraform' 카테고리의 다른 글
[T101] Terraform 101 Study 실습(2) - precondition (0) | 2024.07.01 |
---|---|
[T101] Terraform 101 Study 실습(1) - ec2 배포 (0) | 2024.07.01 |
[T101] Terraform 101 Study 3주차 (1) (0) | 2024.06.26 |
[T101] Terraform 101 Study 2주차 (3) (0) | 2024.06.22 |
[T101] Terraform 101 Study 2주차 (2) (0) | 2024.06.21 |