https://developer.hashicorp.com/terraform/language/expressions/conditionals
조건문에 따라 파일을 만드는 예제.
조건문이 참이면 파일을 만들고, 거짓이면 파일을 만들지 않는 예제이다.
0
cd
mkdir 310
cd 310
1
# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"
옳은 경우 앞에것 반영 , 틀릴경우 뒤에것을 반환한다.
2
# 조건식 형태 권장 사항
var.example ? 12 : "hello" # 비권장 , 숫자와 스트링은 비권장.
var.example ? "12" : "hello" # 권장
var.example ? tostring(12) : "hello" # 권장
3
vi 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 : ""
}
// 설명 ?
count = var.enable_file ? 1 : 0
var.enable_file이 조건 = 조건이 true 면 1, 아니면 0
조건은 var.enale , 디폴트가 true 이므로 실행된다.
count는 1이 된다. local_file 을 만드는것이므로 파일이 만들어진다.
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
filename = "${path.module}/foo.bar"
ls
foo.bat
rm -rf *
4
# 변수 우선순위3 : 환경 변수 (TF_VAR 변수 이름)
false 로 먼저 테스트 해보자.
환경변수가 우선이다.
vi 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 : ""
}
export TF_VAR_enable_file=false
export | grep TF_VAR_enable_file
ls
파일 없음.
terraform plan && terraform apply -auto-approve
terraform state list
파일로 만들어 지지 않는다.
5
# 환경 변수 삭제
unset TF_VAR_enable_file
export | grep TF_VAR_enable_file
# 재실행
terraform plan && terraform apply -auto-approve
terraform state list
파일이 만들어진다.
local_file.foo[0]
ls
foo.bar
6
테라폼 콘솔을 echo로 출력해서 보자.
echo "local_file.foo[0]" | terraform console
"filename" = "./foo.bar"
echo "local_file.foo[0].content" | terraform console
"foo!"
7
조건문을 가지고 AWS리소스를 만들어보자.
테라폼은 프로그래밍 언어적인 특성을 가지고 있어서, 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용 할 수 있다.
단, 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지는 않는다.
숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
내장함수
https://developer.hashicorp.com/terraform/language/functions
1
cd
mkdir 311
cd 311
2
vi main.tf
resource "local_file" "foo" {
content = upper("foo! bar!")
filename = "${path.module}/foo.bar"
}
// upper 함수 사용
3
#
terraform init && terraform plan && terraform apply -auto-approve
cat foo.bar ; echo
FOO! BAR!
4
# 내장 함수 간단 사용 = 테라폼 콘솔로 사용해보면 된다.
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
> upper("foo!")
"FOO!"
> max(5, 12, 9)
12
> lower(local_file.foo.content)
"foo! bar!"
> upper(local_file.foo.content)
"FOO! BAR!"
> cidrnetmask("172.16.0.0/12")
"255.240.0.0"
> exit
5
내장 함수 이용하기 예제
https://developer.hashicorp.com/terraform/language/functions
https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax
1
프로비저너를 사용하여 서비스를 위한 서버 또는 기타 인프라 개체를 준비하기 위해 로컬 시스템 또는 원격 시스템에서 특정 작업을 모델링할 수 있습니다.
프로비저너는 프로바이더와 비슷하게 ‘제공자’로 해석되나, 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행.
AWS EC2 생성 후 특정 패키지를 설치해야 하거나 파일을 생성해야 하는 경우, 이것들은 테라폼의 구성과 별개로 동작해야 한다
사용자 편리성 때문에 지원은 한다.
프로비저닝 이후 동작할수 있다. 예를 들어 EC2 생성후 추가 작업이 가능하다.
2
단점 ?
프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않는다.
프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다 ⇒ 선언적 보장 안됨
따라서, 프로비저너 사용을 최소화하는 것이 좋다.
동기화가 안된다.
3
테라폼 프로바이더 엔서블 제공함.
https://registry.terraform.io/providers/ansible/ansible/latest/docs
https://github.com/ansible/terraform-provider-ansible/tree/main
4
cd
mkdir 312
cd 312
5
vi 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}"
}
}
6
terraform init && terraform plan
terraform apply -auto-approve
Plan: 1 to add, 0 to change, 0 to destroy.
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: abc: command not found
local_file.foo: Creation complete after 0s [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
[root@myeks2-bastion-EC2 312]# ls
foo.bar main.tf terraform.tfstate
// 설명 ?
variable 로 컨텐츠 - local file 로 만든다.
첫번째 프로비저너 - 컨텐츠를 찍어본다 - 컨텐츠를 대문자로. SECRET
파일이름은 foo.bar
두번째 프로비저너 - local은 테라폼에서 실행하는 서버에서 실행한다.
abc 실행한다. 에러 난다. 에러시 , 그냥 진행하라.
3번째 프로비저너 - 파일을 삭제할때만 동작한다.
실행할때는 동작 안한다.
ls
foo.bar main.tf terraform.tfstate
foo.bar가 생김.
내용은 대문자.
Provisioning with 'local-exec'...
실행되는 명령어.
echo The content is SECRET
The content is SECRET
두번째 실행
/bin/sh" "-c" "abc
에러.
실패해도 계속 하라고 했으니 실행.
# 테라폼 상태에 프로비저너 정보(실행 및 결과)가 없다.
상태 정보를 보자
terraform state list
파일이 생긴다.
local_file.foo
terraform state show local_file.foo
cat foo.bar ; echo
SECRET
cat terraform.tfstate | jq
선언적인 설정은 저장이 안된다.
프로비저너를 최소화 하라는 것이다.
7
# graph 확인 : 프로비저너 정보(의존성)이 없다
terraform graph > graph.dot
8
# 삭제
terraform destroy -auto-approve
...
Plan: 0 to add, 0 to change, 1 to destroy.
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
삭제할때 동작한다.
파일이 삭제 되었다고
The deleting filename is ./foo.bar
9
2번쨰 실습 = sensitive = true
vi 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}"
}
}
10
# 민감 정보 참조 부분의 실행 및 결과 내용은 출력 안됨
# 실행 실패 시 에러 발생되면 중지
terraform apply -auto-approve
...
Plan: 1 to add, 0 to change, 0 to destroy.
local_file.foo: Creating...
local_file.foo: Provisioning with 'local-exec'...
local_file.foo (local-exec): (output suppressed due to sensitive value in config)
local_file.foo (local-exec): (output suppressed due to sensitive value in config)
local_file.foo: Provisioning with 'local-exec'...
local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "abc"]
local_file.foo (local-exec): /bin/sh: abc: command not found
╷
│ Error: local-exec provisioner error
│
│ with local_file.foo,
│ on main.tf line 29, in resource "local_file" "foo":
│ 29: provisioner "local-exec" {
│
│ Error running command 'abc': exit status 127. Output: /bin/sh: abc: command not found
│
╵
설명 ?
1번째 프로비저너
출력 내용이 감추어짐
종속성이 있어 실행 내용이 안보이는 것이다.
local_file.foo (local-exec): (output suppressed due to sensitive value in config)
local_file.foo (local-exec): (output suppressed due to sensitive value in config)
variable "sensitive_content" {
default = "secret"
sensitive = true
}
11
2번째 프로비저너는 에러라 실행이 안된다.
│ Error: local-exec provisioner error
provisioner "local-exec" {
command = "abc"
#on_failure = continue
}
rm -rf *
12
local-exec 프로비저너 ?
: 테라폼이 실행되는 환경에서 수행할 커맨드를 정의
<< 실행 명령줄
vi main.tf
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!!"
}
}
}
// 설명 ?
null_resource
아무 작업도 수행하지 않는 리소스를 구현
이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생
프로바이더 << 로 만듬.
인터프리터로 실행
작업 디렉토리
# init이 필요하다. = null 프로비너가 없으므로 ~
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"]
...
13
확인
#
terraform state list
null_resource.example1
terraform state show null_resource.example1
cat /tmp/file.txt
cat /tmp/file.txt
Hello!!
world!!
tmp 는 작업자 로컬 pc나 서버 디렉토리이다. mac/ linux 경우 가능.
windows 경우 디렉토리를 만들어여 한다.
https://developer.hashicorp.com/terraform/language/resources/provisioners/remote-exec
1
원격지 서버 연결해 사용하려면 ssh 에 대한 정의가 필요하다.
# connection 블록으로 원격지 연결 정의 한다.
// 아래 프로비저너는 공통인 connection { 을 사용한다.
rm -rf *
vi main.tf
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이 있으면 해당 connection을 사용한다.
2
connection 적용 인수와 설명
https://developer.hashicorp.com/terraform/language/resources/provisioners/connection
참고 자료
https://developer.hashicorp.com/terraform/language/resources/provisioners/remote-exec
3
원격 연결이 요구되는 프로비저너의 경우 스크립트 파일을 원격 시스템에 업로드해 해당 시스템의 기본 쉘에서 실행하도록 하므로 script_path의 경우 적절한 위치를 지정하도록 한다.
Unix/Linux/macOS : /tmp/terraform_%RAND%.sh
Windows(cmd) : C:/windows/temp/terraform_%RAND%.cmd
Windows(PowerShell) : C:/windows/temp/terraform_%RAND%.ps1
4
베스천 호스트를 통해 연결하는 경우 관련 인수를 지원한다
5
파일 프로비저너 (이론)
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"
}
}
provisioner "file" {
source = "conf/configs.d"
destination = "/etc"
source = "conf/configs.d"
// 디렉토리 포함 업로드
provisioner "file" {
source = "apps/app1/"
destination = "D:/IIS/webapp1"
// 파일만 업로드 됨
1
null_resource
아무 작업도 수행하지 않는 리소스를 구현
이런 리소스가 필요한 이유는 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생
https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource
테라폼 1.4 버전이 릴리즈되면서 기존 null_resource 리소스를 대체하는 terraform_data 리소스가 추가 됨.
앞으로는 terraform_data 사용하자.
2
주로 사용되는 시나리오?
프로비저닝 수행 과정에서 명령어 실행
AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시 사용
웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성시 사용
3
cd
mkdir 313
cd 313
4
vi 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-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"
}
// EC2 생성 되고 나서 원격에서 아래 명령어 실행하라.
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"
}
5
# 두 리소스의 종속성이 상호 참조되어 발생하는 에러
terraform init
terraform plan
Error: Cycle: aws_eip.myeip, aws_instance.example
2개 부분 충돌이 나는 것이다.
resource "aws_eip" "myeip" {
resource "aws_instance" "example" {
// 설명?
resource "aws_eip" "myeip" {
#vpc = true
instance = aws_instance.example.id
서로 상호 참조하도록 코드가 되어 있어 중복.
resource "aws_eip" "myeip" {
6
// 수정
중간에 NULL 리소스를 만들어서 해결하자.
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-0116666fed39e2131"
private_ip = "172.31.0.100"
key_name = "kp-xxxxxxxxx" # 각자 자신의 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/xxxxx/.ssh/kp-xxxxx.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"
}
체크 ?
// 172.31.0.100 에 해당 하는 서브넷 id로 수정 필요.
subnet-0824122a3f45618a2
associate_with_private_ip = "172.31.0.100"
private ip 지정함. 100.
connection {
host = aws_eip.myeip.public_ip
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
리소스는 우분트로함.
private_key = file("/Users/xxxxx/.ssh/kp-xxxxxxx.pem") # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
ssh 키 위치, 절대 경로.
[root@myeks2-bastion-EC2 313]# vi 0717-1.pem
[root@myeks2-bastion-EC2 313]# chmod 400 0717-1.pem
7
# null 프로 비저너가 필요함.
terraform plan
에러 남. null 프로비저너가 없으니 초기화 한번 해줘야 한다.
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"
콘솔가서 eip 확인
#
terraform state list
aws_eip.myeip
aws_instance.example
aws_security_group.instance
null_resource.echomyeip
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
유동공인 ip
echo "aws_eip.myeip.public_ip" | terraform console
고정 eip
# 출력된 EC2 퍼블릭IP로 curl 접속 확인
MYIP=$(terraform output -raw eip)
while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
# (임시) 유동 공인 IP로 SSH 접속이 될까요?
안되죠.
7
삭제
terraform destroy -auto-approve
8
null 단점?
별도 프로바이더 필요
null 리소스 사용하면 init 다시 해야 한다.
1
terraform_data 는 null_resource 대체 버전임
https://ghdwlsgur.github.io/docs/Terraform/terraform_data
https://developer.hashicorp.com/terraform/language/resources/terraform-data
2
vi main.tf
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"
}
terraform init
terraform plan
terraform apply -auto-approve
terraform destroy -auto-approve
https://vclock.kr/timer/#countdown=00:10:00&enabled=0&seconds=0&sound=xylophone&loop=1
https://brunch.co.kr/@topasvga/3371
전체 보기
https://brunch.co.kr/@topasvga/3347
https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863
감사합니다.