들어가며
이번 주차에는 지난주에 이어 테라폼 조건문과 함수 프로바이더, 프로비저너에 대한 개념을 배웠습니다. 비로소 테라폼에서 사용되는 기초문법을 다 배우게 된 시간이었습니다. 이전에 배운 문법들도 과제와 세미나를 통해 반복적으로 사용하다 보니 많이 익숙해진 것 같네요!
* 본 스터디의 자료는 아래의 책을 기반으로 합니다.
테라폼으로 시작하는 IaC | 김민수, 김재준, 이규석, 이유종 | 링크
조건문
테라폼에서의 조건식은 3항 연산자 형태를 갖습니다. 조건은 true 또는 false로 확인되는 모든 표현식을 사용할 수 있습니다. 조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행 시 조건 비교를 위해 형태를 추론하여 자동으로 변환합니다. 하지만 헷갈리지 않게 명시적인 형태 작성을 권장합니다.
# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"
# 조건식 형태 권장 사항
var.example ? 12 : "hello" # 비권장
var.example ? "12" : "hello" # 권장
var.example ? tostring(12) : "hello" # 권장
조건식이 사용되는 경우는 다음과 같습니다.
- 특정 속성에 대한 정의
- 로컬 변수에 대한 재정의
- 출력 값에 대한 조건 정의
- 리소스 생성 여부에 응용
예제 - 리소스 생성 여부 판단
# 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 : ""
}
# 변수 우선순위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
# 환경 변수 삭제
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
변수 TF_VAR_enable_file을 false로 설정하면 조건식을 만족하지 않아 enable_file 이 생성되지 않습니다.
여기서 TF_VAR_enable_flie의 값을 삭제하면 default 값이 적용되어 정상적으로 파일이 생성되는 것을 확인할 수 있습니다.
함수
테라폼은 프로그래밍 언어적인 특성을 가지고 있습니다. 그래서 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용할 수 있습니다.
함수에 대한 Docs - 링크
테라폼에서는 사용자 정의 함수를 지원하지 않는다고 합니다. 하지만 위에서 보이는 것과 같이 다양한 처리를 수행하는 내장함수를 지원하므로, 적절히 사용한다면 인프라 구성에 큰 무리가 없을 것으로 보입니다 :)
실습 - 내장 함수 사용
#main.tf
resource "local_file" "foo" {
content = upper("foo! bar!")
filename = "${path.module}/foo.bar"
}
terraform console
>
upper("foo!")
max(5, 12, 9)
lower(local_file.foo.content)
cidrnetmask("172.16.0.0/12")
exit
내장 함수를 사용하여 문자 변환 또는 수식과 같은 다양한 기능을 사용할 수 있습니다.
프로비저너(Provisioner)
프로비저너는 테라폼이 리소스를 생성하거나 삭제할 때 특정 로직이나 동작을 실행하기 위해 사용합니다. 예를 들어서 VM을 생성한 후에 특정 스크립트를 실행하거나 설정을 변경하는 등의 작업을 프로비저너를 통해 수행할 수 있습니다. 테라폼에는 다음과 같이 여러 종류의 프로비저너가 있습니다.
프로비저너의 종류에는 파일 복사와 명령어 실행을 위한 file, local-exec, remote-exec가 있습니다. 이와 함께 테라폼 코드에서 사용자 데이터를 사용할 때는 cloud-init, Packer, Provisioner Connections 등을 사용할 수 있습니다.
local-exec
로컬 머신에서 명령을 실행합니다. 리눅스나 윈도우 등 테라폼을 실행하는 환경에 맞게 커맨드를 정의합니다. 사용되는 인수 값은 다음과 같습니다.
- command(필수) : 실행할 명령줄을 입력하며 << 연산자를 통해 여러 줄의 커맨드 입력 가능
- working_dir(선택) : command의 명령을 실행할 디렉터리를 지정해야 하고 상대/절대 경로로 설정
- interpreter(선택) : 명령을 실행하는 데 필요한 인터프리터를 지정하며, 첫 번째 인수로 인터프리터 이름이고 두 번째부터는 인터프리터 인수 값
- environment(선택) : 실행 시 환경 변수는 실행 환경의 값을 상속받으며, 추가 또는 재할당하려는 경우 해당 인수에 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!!" #실행시 재할당
}
}
}
remote-exec
원격 기기에서 명령을 실행합니다. 원격지에서 배포할 때 필요한 파일을 전달하기 유용합니다. 아래와 같이 emote-exec 프로비저너를 사용하기 위해 원격지에 연결할 SSH 정의가 필요합니다. 사용되는 인수는 다음과 같습니다.
- inline : 명령에 대한 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 명령을 , 로 구분해 구성합니다.
- script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행합니다.
- scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 스크립트 경로를 , 로 구분해 구성합니다.
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",
]
}
}
커넥션을 만들 때 필요한 인수는 Docs에서 확인할 수 있습니다.
Connection 인수 - 링크
file 프로비저너
파일을 원격 기기로 복사하는 데 사용합니다. 사용되는 인수는 다음과 같습니다.
- source : 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상대 경로 또는 절대 경로로 지정할 수 있습니다. content와 함께 사용할 수 없습니다.
- content : 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없습니다.
- destination : 필수 항목으로 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리입니다.
주의할 점은 ssh 연결 시 대상 디렉터리가 존재하지 않으면 실행되지 않습니다.
그리고, 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 디렉터리 내의 파일들만 /Users/devlos/Desktop/dev/terraform-101/3.10 디렉터리 내에 업로드
provisioner "file" {
source = "apps/app1/"
destination = "/Users/devlos/Desktop/dev/terraform-101/3.10"
}
}
프로비저너는 리소스의 생명 주기 중 특정 시점에서만 실행되므로, 관리 상태나 리소스의 변경을 감지할 때 파악되지 않습니다. 또한 프로비저너로 실행된 결과는 테라폼의 상태파일과 동기화되지 않기 때문에 프로비저닝 결과가 항상 같다고 보장할 수 없습니다. 그렇기 때문에 프로비저너는 최소한으로 사용하는 것이 좋다고 합니다.
null_resource와 terraform_data
null_resource는 아무 작업도 수행하지 않는 리소스라고 합니다. 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생하며, 프로바이더가 제공하는 수명주기 관리만으로는 이를 해결하기 어렵기 때문이라고 합니다.
주로 사용되는 시나리오는 다음과 같습니다.
- 프로비저닝 수행 과정에서 명령어 실행
- 프로비저너와 함께 사용
- 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
- 출력을 위한 데이터 가공
실습
시나리오는 다음과 같습니다.
- AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다
- 웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.
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 = "devlos-winitech" # 각자 자신의 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/devlos/Desktop/devlos-winitech.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"
}
그래프를 출력하면 다음과 같이 null_resource와 output.eip가 aws_eip.myeip를 참조하는 것을 확인할 수 있습니다.
출력된 IP로 접속이 되는지 확인해 봅니다.
MYIP=$(terraform output -raw eip)
while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
terraform_Data는 추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공될 수 있다는 장점이 있습니다.
예제
기본 null_resource와 동일하며 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공됩니다.
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 블록
moved 블록은 리소스의 이름을 변경하거나, 모듈 참조 주소가 변경되는 등 리소스 변경 시에도 프로비저닝 된 환경을 그대로 유지하기 위해 사용합니다. moved를 이용하여 테라폼 state에서 옮겨진 대상의 이전 주소와 새 주소를 알리는 역할을 수행하게 됩니다.
# 생성
resource "local_file" "b" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.b.content
}
# 프로비저닝 결과를 유지하며 리소스 변경
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
}
moved 블록을 사용하면 리소스의 변화가 없는 것을 확인할 수 있습니다.
CLI를 위한 시스템 환경변수
테라폼은 환경 변수를 통해 실행 방식과 출력 내용에 대한 옵션을 조절할 수 있습니다. 시스템 환경 변수 설정으로, 영구적으로 로컬 환경에 적용되는 옵션이나 별도 서버 환경에서 실행하기 위한 옵션을 부여합니다.
- TF_LOG: 로깅레벨을 지정하거나 해제합니다.
- TF_LOG_PATH: 로그 출력 파일 위치 지정합니다.
- TF_LOG_CORE: TF_LOG와 별도로 테라폼 자체 코어에 대한 로깅 레벨을 지정하거나 해제합니다.
- TF_LOG_PROVIDER: TF_LOG와 별도로 테라폼에서 사용하는 프로바이더에 대한 로깅 레벨을 지정하거나 해제합니다.
- TF_INPUT: 0을 설정하면 입력을 받지 않고 수행합니다.
- TF_CLI_ARGS / TF_CLI_ARGS_subcommand: 테라폼 실행 시 추가할 인수를 정의합니다.
- TF_DATA_DIR: State 데이터 디렉터리를 지정합니다.
TF_LOG=info terraform plan
TF_INPUT=0 terraform plan
# 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=./.terraform_tmp terraform plan
# Error: Required plugins anr not installed
다음은 TF_LOG를 통해 log level을 INFO로 변경한 경우입니다.
프로바이더
프로바이더는 테라폼이 지원하는 인프라 서비스 또는 플랫폼에 대한 API와의 인터페이스를 제공합니다. 이를 통해 해당 서비스의 리소스를 생성, 관리 및 수정할 수 있습니다. 첫 번째 스터디에서 확인했듯이, 수많은 공식 프로바이더를 지원하며, 커뮤니티에서도 다양한 프로바이더를 개발하고 있습니다. (테라폼 0.13부터 프로바이더는 버전을 명시적으로 관리할 수 있습니다. 이를 통해 특정 버전의 프로바이더를 사용하여 안정성을 확보할 수 있습니다.)
테라폼은 terraform 바이너리 파일을 시작으로 로컬 환경에나 배포 서버와 같은 원격 환경에서 원하는 대상을 호출하는 방식으로 실행됩니다. 호출하는 방식이 서로 다르지만 대상의 공급자, 즉 프로바이더가 제공하는 API를 호출해 상호작용 합니다. 프로바이더는 플러그인 형태로 테라폼에 결합되어 대상이 되는 클라우드, SaaS, 기타 서비스 API를 사용해 동작을 수행한다. 각 프로바이더는 테라폼이 관리하는 리소스 유형과 데이터 소스를 사용할 수 있도록 연결합니다.
Office, Partner 프로바이더는 검증된 것이지만, Community는 기술 지원이 안될 수 있거나, 예상하지 못한 이슈가 발생할 수 있으므로 가급적 사용하지 않는 것이 좋다고 합니다.
프로바이더 에코시스템
테라폼의 프로바이더 에코시스템은 매우 광범위하며, 다양한 클라우드 서비스, 플랫폼, 소프트웨어와의 통합을 가능하게 합니다. 이 에코시스템은 크게 두 가지 주요 카테고리로 나눌 수 있습니다:
워크플로우 파트너 (Workflow Partners)
이 카테고리의 프로바이더는 일반적으로 CI/CD, 모니터링, 로깅, 알림 및 기타 DevOps 도구와 관련된 서비스를 대상으로 합니다.
워크플로우 파트너 프로바이더를 사용하면 테라폼 코드 내에서 이러한 서비스와의 통합을 자동화하고, 인프라 프로비저닝과 함께 워크플로우를 구축할 수 있습니다. 예를 들면, GitHub, GitLab, Datadog, PagerDuty 등의 서비스와의 통합을 위한 프로바이더가 있습니다.
인프라스트럭처 파트너 (Infrastructure Partners)
이 카테고리의 프로바이더는 클라우드 서비스, 온-프레미스 인프라, 네트워크 장비, 데이터베이스 등의 인프라스트럭처와 관련된 서비스를 대상으로 합니다. 인프라스트럭처 파트너 프로바이더를 사용하면 테라폼 코드를 통해 해당 서비스의 리소스를 생성, 수정 및 관리할 수 있습니다. AWS, Azure, Google Cloud, VMware, Oracle Cloud, Alibaba Cloud 등의 주요 클라우드 서비스 프로바이더뿐만 아니라, Cisco, Palo Alto, F5 등의 네트워크 장비와 관련된 프로바이더도 포함됩니다. 테라폼의 프로바이더 에코시스템은 계속해서 성장하고 있으며, HashiCorp와 커뮤니티의 기여를 통해 다양한 서비스와의 통합을 지원하고 있습니다. 이를 통해 사용자는 테라폼을 중심으로 한 코드 기반의 인프라 관리를 통해 전체 IT 환경을 효과적으로 관리할 수 있습니다.
HashiCorp 파트너사는 다음과 같습니다.
프로바이더 요구사항 정의 블록의 구성은 다음과 같습니다.
terraform {
required_providers {
<프로바이더 로컬 이름> = {
source = [<호스트 주소>/]<네임스페이스>/<유형>
version = <버전 제약>
}
...
}
}
그리고 스터디에도 강조하였지만, 테라폼 소스코드를 변경하지 않고 클라우드 프로바이더를 변경하는 것은 불가능합니다. 코드가 어느 정도 프로바이더에 종속적인 부분이 있기 때문입니다.
프로바이더 경험해 보기
아래의 예시는 kubernetes 프로바이더를 통해 Nginx 배포를 수행하는 것입니다.
# main.tf
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
# kubernetes.tf
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_deployment" "nginx" {
metadata {
name = "nginx-example"
labels = {
App = "t101-nginx"
}
}
spec {
replicas = 2
selector {
match_labels = {
App = "t101-nginx"
}
}
template {
metadata {
labels = {
App = "t101-nginx"
}
}
spec {
container {
image = "nginx:1.7.8"
name = "example"
port {
container_port = 80
}
}
}
}
}
}
resource "kubernetes_service" "nginx" {
metadata {
name = "nginx-example"
}
spec {
selector = {
App = kubernetes_deployment.nginx.spec.0.template.0.metadata[0].labels.App
}
port {
node_port = 30080
port = 80
target_port = 80
}
type = "NodePort"
}
}
쿠버네티스를 로컬에 생성하고, 다음의 kubernetes provider를 사용하면 hcl 코드를 기반으로 pod를 배포하는 것을 확인할 수 있습니다.
도전과제 1 - 조건문을 활용하여 AWS 리소스를 배포하는 코드를 작성해 보자!
저는 공식예제의 EC2 인스턴스 생성 시 고가용성 사용 여부에 따라 인스턴스 수가 다르게 프로비저닝 되는 예제에 대해 코드를 작성해 보았습니다.
provider "aws" {
region = "ap-northeast-2"
}
variable "high_availability" {
type = bool
description = "If this is a multiple instance deployment, choose `true` to deploy 3 instances"
}
resource "aws_instance" "ec2" {
count = (var.high_availability == true ? 3 : 1)
ami = "ami-084e92d3e117f7692"
instance_type = "t2.micro"
}
apply를 수행할 때 true를 입력할 경우 3개의 인스턴스가 생기고,
false를 입력할 경우 1개의 인스턴스가 생성되는 것을 확인할 수 있습니다.
마치며
이번 포스팅에서는 테라폼 조건문과 함수 프로바이더, 프로비저너에 대한 개념을 배웠습니다. 스터디에서 언급했던 것처럼 IaC를 처음 접하는 과정에서 테라폼을 통해 멀티 클라우드를 관리할 수 있다는 생각으로 시작했었는데.. 하다 보니 프로바이더 디펜던트한 코드들이 많아 불가능하다는 것을 알게 되었습니다. 하지만, 전체적인 인프라 구성의 개념은 코드에 녹아있기 때문에, 클라우드 인프라 이관이 필요할 때도 해석하여 업무를 수행하면 될 것 같습니다.
IaC 형태로 관리가 되고 있지 않다면 더더욱 혼돈의 카오스일 테니까요..
긴 포스팅을 따라와 주시느라 고생 많으셨습니다 :) 감사합니다! 이번주 스터디도 파이팅입니다!!
'클라우드 컴퓨팅 & NoSQL > [T101] 테라폼 기초 입문 스터디' 카테고리의 다른 글
[6주차] T101 테라폼 기초 입문 스터디 (23.10.08) (2) | 2023.10.10 |
---|---|
[5주차] T101 테라폼 기초 입문 스터디 (23.09.24) (1) | 2023.09.29 |
[4주차] T101 테라폼 기초 입문 스터디 (23.09.17) (0) | 2023.09.23 |
[2주차] T101 테라폼 기초 입문 스터디 (23.08.27) (0) | 2023.09.08 |
[1주차] T101 테라폼 기초 입문 스터디 (23.08.27) (0) | 2023.09.01 |