brunch

You can make anything
by writing

C.S.Lewis

by Master Seo Jul 17. 2023

32탄-7. 3주차-테라폼- 조건문


<1> 조건문 (실습)

<2> 내장 함수 사용해보자 (실습)

<3> 프로비저너 = 커맨드와 파일 복사 같은 역할을 수행 (실습)

<4> 프로비저너 - 원격지 연결  (이론)

<5> null_resource (실습)

<6> terraform_data  (이론)




<1> 조건문 (실습)


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리소스를 만들어보자.

https://developer.hashicorp.com/terraform/tutorials/configuration-language/expressions?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS




<2> 내장 함수 사용해보자 (실습)



테라폼은 프로그래밍 언어적인 특성을 가지고 있어서, 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용 할 수 있다.

단, 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지는 않는다.

숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, 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





<3> 프로비저너 = 커맨드와 파일 복사 같은 역할을 수행 (실습)


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 경우 디렉토리를 만들어여 한다.





<4> 프로비저너 - 원격지 연결  (이론)


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

베스천 호스트를 통해 연결하는 경우 관련 인수를 지원한다

https://developer.hashicorp.com/terraform/language/resources/provisioners/connection#connecting-through-a-bastion-host-with-ssh



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"

// 파일만 업로드 됨





<5> null_resource (실습)


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 다시 해야 한다.




<6> terraform_data  (이론)


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

다음은 주말 CloudNet 테라폼 스터디 내용 참고하여  정리한 부분입니다.

https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863    



감사합니다.



keyword
매거진의 이전글 32탄-6. 2주차-테라폼-반복문
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari