들어가며
* 본 스터디의 자료는 아래의 책을 기반으로 합니다.
테라폼으로 시작하는 IaC | 김민수, 김재준, 이규석, 이유종 | 링크
작년 테라폼 T101 3기 스터디에 참여해 테라폼을 배우고, 해외 데모 서비스의 인프라를 테라폼+TFC로 구성하여 운영하고 있습니다. 하지만 테라폼을 EKS 클러스터로 구축하며 Best Practice에서 이해되지 않는 부분이 많아 고민하던 중 CloudNet@ 스터디에서 4기 스터디를 진행하는 것을 알게 되었습니다. 이번 스터디에서는 EKS 클러스터를 구축하는 부분까지 다루는 것으로 되어 있어, 복기 및 연습에 도움이 되고자 스터디에 참여하게 되었습니다.
지금까지 CloudNet@ 스터디를 회사에서 혼자 참여하다가, 마음이 맞는 클라우드팀 팀원들과 함께 스터디에 참여하게 되었어요.. 같이 고군분투하며 새로운 주제로 이야기하게 되니 힘들지만 너무 재미있는 경험입니다 :)
ps. 다들 마지막까지 살아남으시길..
테라폼의 기본적인 실행 명령어에 대한 설명은 지난 스터디 자료를 참고 하시면 좋을 듯합니다. 이번 시간에는 4기 스터디에서 새로 배운 내용과 주요 내용, 그리고 실습을 위주로 정리해 나갈 생각입니다.
스터디에서는 Terrafrom 1.8.5 버전을 사용하기로 했으니, 참고 하시기 바랍니다. 버전에 따라 문법이 조금씩 다르거든요!
실습 2 - EC2 한대 배포해 보기
워밍업으로 ec2 인스턴스 한대를 테라폼을 이용하여 배포합니다.
#Terraform 파일 생성
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
}
EOT
# terraform 초기화
terraform init
# init을 통해 설치한 테라폼 모듈들 확인
tree .terraform
terraform plan
terraform apply
init 과정 수행 후 tree 명령어를 통해. terraform 디렉터리를 살펴보면 다음과 같이 aws provider가 설치된 것을 확인할 수 있습니다.
terraform apply 명령어를 수행하면 다음과 같이 인스턴스가 정상적으로 배포되는 것을 확인할 수 있습니다.
만약 인스턴스의 리소스의 tag를 변경하고자 한다면 tag를 수정 후(t101-study) plan apply를 다시 수행합니다.
apply가 완료되면 인스턴스의 이름이 변경된 것을 확인할 수 있습니다.
Terraform validate 명령어 실습
validate라는 명령어를 통해 작성된 HCL을 분석하여 terraform 기반 프로비저닝 시 발생할 수 있는 명령어를 테스트해 볼 수 있습니다.
아래와 같이 resource를 정상적으로 생성하는 코드가 있습니다.
# 코드 작성
resource "local_file" "abc" {
content = "abc!"
filename = "${path.module}/abc.txt"
}
# Terraform 리소스 생성
terraform plan
terraform apply
이 코드에서 filename 부분을 주석처리하여 validate를 실행하면,
# 코드의 filename을 주석처리
resource "local_file" "abc" {
content = "abc!"
#filename = "${path.module}/abc.txt"
}
terraform validate
resource local_file의 정의에 필수인 filename이 없어서 다음과 같은 오류가 발생합니다.
다시 주석을 풀면 정상적으로 리소스가 만들어집니다.
리소스를 만들 때는 tfplan이라는 plan 결과를 바이너리로 출력하는 명령어를 사용해 보았습니다.
#Terraform 코드 주석 해저
resource "local_file" "abc" {
content = "abc!"
filename = "${path.module}/abc.txt"
}
# terraform plan 결과인 바이너리를 저장
terraform plan -out=tfplan
# 바이너리 파일로 apply 수행
terraform apply tfplan
다음과 같이 리소스가 정상적으로 만들어지는 것을 확인할 수 있었습니다.
tfplan의 사용 사례를 찾아보니 인프라의 CI/CD 파이프라인 구축 시 terraform plan -out=tfplan 명령어로 변경 사항을 계획하고, 이후 승인이 완료되면 terraform apply tfplan 명령어를 사용하여 자동으로 인프라를 업데이트할 때 사용한다고 합니다.
프로비저닝 완료된 리소스의 속성을 변경하고 싶을 때는 코드를 변경한 후 apply를 수행합니다. 이때 plan 명령어를 사용하면 어떤 리소스가 변경이 되는지 시각적으로 확인할 수 있습니다.
프로비저닝 된 특정 리소스를 삭제 후 다시 생성할 때는 아래와 같이 apply 시 '-replace'를 활용합니다.
ls -l abc.txt
terraform apply -replace=local_file.abc -auto-approve
ls -l abc.txt
tip: 스터디에서 배운 꿀 팁으로, terraform fmt 명령어를 통해 테라폼 파일을 표준 형식과 스타일로 업데이트할 수 있습니다.
실습 3 - EC2 1대 배포 & 웹 서버 설정
EC2 1대를 배포하고 외부에서 접속이 가능하도록 설정합니다.
# 우분투 ami 버전을 설정
UBUNTUID=ami-0572f73f0a5650b33
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$UBUNTUID"
instance_type = "t2.micro"
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 8080 &
EOF
tags = {
Name = "Single-WebSrv"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "terraform-example-instance"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
EOT
배포 시에 security_group을 통해 ingress port 설정을 해주지 않으면 아래와 같이 인스턴스가 배포되어도 외부와 통신을 할 수 없습니다.
따라서 security_group을 생성하여 inbound, outbound 포트를 개방하고, 인스턴스에 security_group_id를 이용하여 연결해야만 외부로 서비스를 제공할 수 있습니다.
이를 그래프로 출력해 보면 다음과 같은 의존성 구조를 확인할 수 있습니다.
HCL과 Terraform 블록
IaC는 코드를 통해 인프라를 관리하고 프로비저닝 하는 것을 말하고, HCL이 Terraform의 코드 영역을 담당합니다.
// 한줄 주석 방법1
# 한줄 주석 방법2
/*
라인
주석
*/
locals {
key1 = "value1" # = 를 기준으로 키와 값이 구분되며
myStr = "TF ♡ UTF-8" # UTF-8 문자를 지원한다.
multiStr = <<EOF
Multi
Line
String
with anytext
EOF
boolean1 = true # boolean true
boolean2 = false # boolean false를 지원한다.
deciaml = 123 # 기본적으로 숫자는 10진수,
octal = 0123 # 0으로 시작하는 숫자는 8진수,
hexadecimal = "0xD5" # 0x 값을 포함하는 스트링은 16진수,
scientific = 1e10 # 과학표기 법도 지원한다.
# funtion 호출 예
myprojectname = format("%s is myproject name", var.project)
# 3항 연산자 조건문을 지원한다.
credentials = var.credentials == "" ? file(var.credentials_file) : var.credentials
}
Terraform block은 구성을 명시하는 용도로 사용합니다. Terraform block은 Terraform이나 다른 모듈 프로바이더들의 버전(시멘틱 버전 관리 방식 사용: version = major.minor.patch)을 자동을 설정하지만, 가급적 명시적으로 버전을 선언하고 필요한 조건들을 설정하여 실행 오류를 최소화할 것을 권장합니다.
프로바이더들은 테라폼 레지스트리에서 확인할 수 있으며 해당 버전이 없을 경우 다음과 같이 에러를 출력합니다.
terraform {
required_version = ">= 1.0.0"
required_providers {
local = {
source = "hashicorp/local"
version = ">=10000.0.0" #버전을 과하게 높임
}
}
}
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
또한 1.1 version에서 기존에서 선언하던 backend 블록이 cloud 블록으로 변경되었습니다. 이 블록은 Terraform 실행 시 저장되는 Terraform State의 저장위치를 선언하는 역할을 합니다. 저장된 State데이터를 통해 버전 관리 및 리소스 추적 작업을 통해 인프라의 선언과 현재 상태를 비교하며 확인해 볼 수 있습니다. 여러 사람이 동시에 작업할 경우. terraform.tfstate.lock.info 파일이 생성되어 terraform이 실행 중임을 관리하여 실행 중에 다른 사람이 동시에 사용하지 못하도록 차단합니다.
만약 테라폼의 상태를 관리하는 terraform.tfstate 파일이 삭제되면, Terraform은 기존의 상태를 인지하지 못합니다. 그래서 plan, apply를 수행 시 새로운 리소스를 만들게 되고, terraform.tfstate 파일을 새로 생성합니다. 만약, 리소스가 프로비저닝 되어 있다면 충돌 및 예기치 못한 상황이 발생할 수 있으므로, state파일 관리가 매우 중요합니다.
리소스 블록
리소스 블록은 선언된 항목들을 생성하는 역할을 하고, 선언하는 포맷은 다음과 같습니다.
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
resource "local_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
리소스 들은 프로바이더에 종속성을 가집니다. init 수행 시 각 리소스를 사용하는데 필요한 프로바이더들을 자동으로 설치하게 됩니다.
종속성에 따라 Terraform은 프로비저닝 요소들의 생성 순서를 구분합니다. Terraform은 리소스 간 관계성을 따져 자동으로 연관관계를 정의하는 암시성 종속성을 가지고 있습니다. 다음과 같이 depends_on 옵션을 통해 사용자가 명시적으로 리소스 간의 종속성을 나타낼 수 있습니다.
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
resource "local_file" "def" {
depends_on = [
local_file.abc
]
content = local_file.abc.content
filename = "${path.module}/def.txt"
}
terraform init
terraform plan
terraform apply -auto-approve
local_file def 리소스를 local_file abc에 의존하도록 설정하면, 다음과 같은 그래프를 확인할 수 있습니다.
만약 리소스에 다른 리소스의 속성을 참고하려면 다음과 같은 패턴을 활용합니다.
#포맷
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 리소스 참조
<리소스 유형>.<이름>.<인수>
<리소스 유형>.<이름>.<속성>
#예시
resource "kubernetes_namespace" "example" {
metadata {
annotations = {
name = "example-annotation"
}
name = "terraform-example-namespace"
}
}
resource "kubernetes_secret" "example" {
metadata {
namespace = kubernetes_namespace.example.metadata.0.name # namespace 리소스 인수 참조
name = "terraform-example"
}
data = {
password = "P4ssw0rd"
}
}
이런 식으로 사용하게 되면, 예시처럼 namespace의 이름이 변경되었을 때, 이를 참고하는 secret이 영향을 받아 업데이트 되게 됩니다.
리소스는 수명주기(Life cycle)를 가집니다. 수명주기를 관리하는 속성들을 활용하여 업데이트 프로비저닝 인프라의 중지 시간을 최소화하거나, 다른 인프라가 영향을 받지 않도록 관리할 수 있습니다.
수명주기를 관리하는 속성들을 다음과 같습니다.
- create_before_destory (bool) : 리소스 수정 시 신규 리소스를 우선 생성하고 기존 리소스를 삭제합니다.
- prevent_destroy (bool) : 해당 리소스를 삭제 Destroy 하려 할 때 명시적으로 거부합니다.
- ignore_changes (list) : 리소스 요소에 선언된 인수의 변경 사항을 테라폼 실행 시 무시합니다.
- precondition : 리소스 요소에 정의하여 인수 조건을 validation 하는 역할을 수행합니다.
- postcondition : 리소스 요소에 선언하여 Plan과 Apply 이후의 결과 값을 validation 하는 역할을 수행합니다.
이러한 속성들은 테라폼 기반 인프라를 관리하기 위해 잘 이해하고 있어야 한다고 합니다. 스터디에서 보여준 사례 중에 create_before_destroy를 true로 선언하여, 기존처럼 삭제 후 생성 방식이 아니라 생성 후 삭제 방식을 적용하였더니, 리소스를 다시 생성한 후 삭제되어 스테이트와 리소스 존재 유무가 일치하지 않는 현상이 발생했었습니다.
prevent_destory 옵션을 사용하면 중요한 리소스가 삭제되는 상황을 방지할 수 있습니다.
ignore_changes 옵션을 활용하면 다음과 같이 apply를 실행해도 리소스가 변화하지 않습니다.
precondition이나 postcondition을 사용하면 다음과 같이 필요한 리소스 속성을 validation 할 수 있습니다.
variable "file_name" {
default = "step0.txt" #파일 이름을 step0.txt로 지정
}
resource "local_file" "abc" {
content = "lifecycle - step 6"
filename = "${path.module}/${var.file_name}"
lifecycle {
precondition {
condition = var.file_name == "step6.txt" #파일이름이 step6.txt인지 확인
error_message = "file name is not \"step6.txt\""
}
}
}
validation 검사 후 문제 발생 시 error_message에 명시된 것처럼 다음의 오류를 출력합니다.
도전과제 1 - EC2 리소스 배포
Ubuntu에 httpd를 설치하고 닉네임을 출력하는 웹서비스 배포하기(닉네임을 출력!!)
과제를 해결하기 위해 user-data.sh라는 파일에 웹 페이지를 만들고, 웹서버를 배포하도록 스크립트를 작성하였습니다.
다음으로 user-data.sh를 terraform의 data 블록을 통해 aws_instance 리소스에서 사용할 수 있도록 바인딩했습니다.
#user-data.sh 사용자 데이터 파일 생성
#!/bin/bash
echo "DEVLOS" > index.html
nohup busybox httpd -f -p 8080 &
#main.tf
provider "aws" {
region = "ap-northeast-2"
}
data "template_file" "user_data" {
template = file("user-data.sh")
}
resource "aws_instance" "example" {
ami = "ami-0572f73f0a5650b33"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = "${data.template_file.user_data.rendered}"
tags = {
Name = "terraform-Study-101"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "terraform-example-instance"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
HCL을 terraform으로 실행하면 다음과 같이 DEVLOS 웹 서비스가 정상적으로 배포되는 것을 확인할 수 있습니다.
리소스 그래프를 출력해 보면 다음과 같이 user_data를 활용하는 것을 확인할 수 있습니다.
과제 트러블 슈팅
1. 맥북 M3 arm 아키텍처 문제
테스트한 맥북의 OS가 arm 기반 운영체제이다 보니 다음과 같은 에러가 발생했습니다.
"Template v2.2.0 does not have a package available"
(으..)
이를 해결하기 위해 kreuzwerker 사에서 만든 Terraform provider helper를 사용했습니다.
설치 방법은 다음과 같습니다.
brew install kreuzwerker/taps/m1-terraform-provider-helper
m1-terraform-provider-helper activate
m1-terraform-provider-helper install hashicorp/template -v v2.2.0
2. template_file 읽기 포맷 문제
스터디에서는 user_data를 마운트 할 때 data.temlpate~ 형태로 접근한다고 배웠습니다만, 참고하는 레퍼런스에는 template_file.~ 형태로 많이 사용되고 있어 헷갈렸습니다. 테라폼 버전별로 변경되는 문법을 잘 체크해 보고, 터미널의 에러 문구를 잘 살펴보면서 문제를 해결할 수 있었습니다.
마치며
이번 시간에는 테라폼의 기초 사용법과 리소스의 수명주기를 중점으로 스터디 내용을 정리했습니다. 스터디에서 다룬 수명주기 사례들을 살펴보면서 운영 중인 클러스터에 어떤 식으로 적용해야 할지 고민이 많아지네요!
이상으로 이번 시간 포스팅을 마치도록 하겠습니다.
'클라우드 컴퓨팅 & NoSQL > [T101] 테라폼 4기 스터디' 카테고리의 다른 글
[6주차 - Well-Architected 방식으로 워크로드를 안전하게 마이그레이션 및 현대화하기 Workshop ] T101 4기 스터디 (24.07.14) (0) | 2024.07.17 |
---|---|
[5주차 - 모듈 & Runner] T101 4기 스터디 (24.07.07) (1) | 2024.07.13 |
[4주차 - Provider & State] T101 4기 스터디 (24.06.30) (0) | 2024.07.04 |
[3주차 - 기본사용#3] T101 4기 스터디 (24.06.23) (0) | 2024.06.29 |
[2주차 - 기본사용#2] T101 4기 스터디 (24.06.16) (0) | 2024.06.22 |