2024. 7. 7. 00:40ㆍEKS@Terraform
- CloudNet에서 주관하는 Terraform 스터디 내용입니다
- 내용은 위 책 테라폼으로 시작하는 IaC 를 기준으로 정리하였습니다.
- 실습은 M1 macbook air 에서 진행했습니다.
- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다
- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!
이번주에는 한 주 스터디 내용을 적당히 분리해서 포스팅해보려고 한다. 필요해서 내 지난 포스팅을 찾을 일이 있었는데, 필요한 내용을 찾기가 좀 어려운 것 같아서. 다음주는 원복할지도 모르겠다.
State
테라폼의 멱등성을 보장하기 위해 테라폼은 state file을 운영한다. 앞의 provider 실습을 진행한 폴더에 들어가 보면 terraform.tfstate 와 terraform.tfstate.backup 파일 2개가 생성되어 있다.
(중요) 그리고 이 파일은 테라폼이 상태를 관리하기 위해 스스로 운영하기 때문에, 사람이 편집하지는 않는다!
(중요2) 팀단위 작업 시, 각 팀원이 동일한 테라폼 상태파일을 사용해야 하기 때문에 공유 위치에 저장해야 한다.
멱등성이 익숙하지 않은 분들을 위해 잠시 짚고 넘어가보자. 멱등성은 '여러번 실행하더라도 결과가 달라지지 않음을 보장' 한다는 말이다.
예시로 0이 입력되었을 때 10으로 만드는 함수를 만들어보자.
def a_to_ten (a: int):
for i in range(10):
a += 1
return a
def b_to_ten (b: int):
while b < 10:
b += 1
if b == 10:
return b
a_to_ten 함수는 무조건 1을 10번 더해서 반환하기 때문에 한번 실행되었을 때는 0이 입력되었을 때는 10이 반환되지만, 이 함수가 같은 변수에 대해 반복적으로 실행된다면 결과값은 10, 20.... 계속 상승하게 되므로 멱등성이 지켜지지 않는다. 반대로 b_to_ten 함수는 여러번 실행되어도 무조건 10을 반환하기 때문에 멱등성이 지켜진다.
state의 기능은
- 메타데이터를 저장하고 추적한다 (Dependency 등)
- 테라폼의 구성과 실제를 동기화하고 각 리소스에 고유 아이디(주소) 로 맵핑한다
- 테라폼 구성으로 프로비저닝 결과를 캐싱한다.
state 실습을 위해 랜덤 프로바이더를 생성해보자.
랜덤 프로바이더는 리소스명/패스워드 등의 생성에 유용하게 쓰인다고 한다.
resource "random_password" "mypw" {
length = 16
special = true
override_special = "!#$%"
}
마찬가지로 terraform init && terraform plan && terraform apply -auto-approve 로 수행하면 아래와 같이 값이 생성됨을 볼 수 있다. 하지만 별도 파일이 생성되는 게 아니라 terraform state file 내부에 저장된다.
그러나 sensitive value 이기 때문에 terraform console 에서 조회하면 full text 를 볼 수 없고, 블라인드되어 나온다. 하지만 terraforms state file 에서 검색하면 찾을 수 있다.
echo "random_password.mypw" | terraform console
cat terraform.tfstate | grep result
State 를 이용한 복구
terraform 은 아래 그림과 같이 기존 state 와 구성을 비교해 실행 계획에서 생성/수정/삭제 여부를 결정한다.
아래 각각의 시나리오 별로 state에 따라 발생하는 동작들을 정리해보자.
type\existance | 구성 리소스 정의(*.tf) | state 구성파일(*.tfstate) | 실제 리소스 | 예상동작 |
A | O | 리소스 생성 | ||
B | O | O | 리소스 생성 | |
C | O | O | O | 동작 없음 |
D | O | O | 리소스 삭제 |
scenario A
신규리소스 정의 -> apply ->리소스 생성
사실상 신규생성의 경우
main.tf 을 새로운 iam user 를 생성하는 내용으로 구성하고 배포한다.
locals {
name = "mytest"
}
resource "aws_iam_user" "myiamuser1" {
name = "${local.name}1"
}
resource "aws_iam_user" "myiamuser2" {
name = "${local.name}2"
}
배포 코드
# 배포
terraform init && terraform apply -auto-approve
# 확인
terraform state list #생성목록확인
terraform state show aws_iam_user.myiamuser1 #개별 리스트에 대한 자세한 정보 확인
2명의 iam user 가 신규 배포되었음을 확인할 수 있다
terraform 에서 멱등성이 지켜지는지를 확인하기 위해 다시한번 apply 를 실행해보면, "No changes. Your infrastructure matches the configuration" 메시지가 뜨면서 아무 일도 일어나지 않는다.
scenario B
실제 리소스 수동 제거 -> apply -> 리소스 생성
테라폼으로 띄워놓은 리소스가 죽은 경우 복구
의도치 않은 리소스변경 상황 연출을 위해 scenario A 에서 만든 두명의 iam user 를 aws cli상에서 수동으로 지운다.
# 실제 리소스 수동 제거
aws iam delete-user --user-name mytest1
aws iam delete-user --user-name mytest2
리소스 삭제 후 확인해보면 terraform state 에 저장된 내용과 aws 에 실제 배포된 user 가 다름을 볼 수 있다.
이 iam user 2명은 기존에 필요해서 사용하고 있었던 user 이고, terraform 으로 생성한 mytest1, mytest2는 삭제되어 보이지 않는다
aws iam list-users | jq
terraform state list
복구를 위해 다시한번 현재 state file 을 aws 에 apply 해 보자
terraform apply -auto-approve
plan 단계에서 2개의 리소스가 생성될 것을 예고하고, 실제 mytest1, mytest2 가 iam user로 다시 추가된 것을 볼 수 있다.
scenario C
apply -> 코드, 실제리소스, state 모두 일치 -> 현상유지
nothing happens, 변동사항 있나 확인 후 현상유지 한다.
scenario D
코드에서 리소스 삭제 -> apply -> 리소스 삭제
테라폼으로 구성해놓은 인프라를 변경하고 싶은 경우에 해당한다.
main.tf 파일을 아래와 같이 수정해서 mytest1만 남기고 mytest2는 삭제한다.
locals {
name = "mytest"
}
resource "aws_iam_user" "myiamuser1" {
name = "${local.name}1"
}
다시 apply 를 실행하면 1개의 리소스가 삭제된다는 안내가 나온다.
위에서 사용한 명령어를 이용해서 다시 확인하면 기존 mytest2는 없어지고 mytest1만 남아있다. 나머지 iam 들은 영향을 받지 않은 것도 독립성이 지켜졌다는 것을 보여준다
AWS 환경에서 테라폼 백엔드 구성
aws 환경에서 테라폼 백엔드를 구성하기 위해서는 S3(Storage)과 DynamoDB(NoSQL DB)를 사용한다.
S3은 terraform state file 을 저장하는 용도로 사용하고, DynamoDB는 공용작업 상황에서 여러사람이 동시에 *.tfstate 파일을 변경하지 않도록 하는 Locking 기능을 구현한다. 악분님 블로그를 참고해 구성했다.
1. S3배포: 악분님 깃허브에서 S3을 구성하는 테라폼 파일을 클론한 다음 terraform.tfvars에서 bucket_name 을 바꾸고 apply 해서 배포한다 테라폼을 위한 테라폼
악분님이 올려놓은 S3 배포 테라폼을 간단하게 살펴보면 이런 구조로 되어 있다
코드의 내용을 간단히 살펴보면 아래와 같다. 실습용이 아니라 프로덕션 환경에서 어떻게 terraform file을 구성해야 하는지 보여주는 좋은 예제 같다. 실습 배포를 위해 다른 사람의 리소스와 이름이 겹치지 않도록 terraform.tfvars의 bucket-name 에 개인 닉네임이나 랜덤값 등을 추가한다. 언젠가 S3 이름이 중복되서 요금폭탄을 맞았다는 사례를 본 것 같은데... 찾으면 추가해놓겠다.
# variables.tf
variable "bucket_name" {
type = string
}
# terraform.tfvars
bucket_name = "hello-tf102-remote-backend"
# provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.4.0"
}
}
required_version = ">= 1.4"
}
provider "aws" {}
# main.tf
resource "aws_s3_bucket" "main" {
bucket = var.bucket_name
tags = {
Name = "terraform test"
}
}
resource "aws_s3_bucket_versioning" "main" {
bucket = aws_s3_bucket.main.id
versioning_configuration {
status = "Enabled"
}
}
해당 terraform file 이 있는 디렉토리에서 init, apply 를 실행하고 배포상태를 확인한다
# 악분님 레포 클론
git clone https://github.com/sungwook-practice/t101-study.git example
cd example/state/step3_remote_backend/s3_backend
# 배포
terraform init && terraform apply -auto-approve
# 확인
terraform state list
aws s3 ls
확인...해보려고 했는데... 예전에 만들고 안지운 s3 storage 가 하나 있는 걸 알게되었다 ㅋㅋㅋㅋ
아무튼 테라폼 리모트 백엔드에서 사용할 스토리지는 잘 배포되었다 (hithere-hello-tf102-remote-backend)
그러나 현재 프로바이더는 리모트 백엔드 설정이 되어 있지 않기 때문에 vpc 의 provider 에서 리모트 백엔드 저장소 설정을 활성화한다. 이때 생성하는 버킷명은 위에서 생성한 s3의 이름과 같아야 한다
cd ../vpc
vi provider.tf
# provider.tf 수정
...
backend "s3" {
#bucket = "<닉네임>-hello-t1014-remote-backend"
bucket = "hithere-hello-tf102-remote-backend"
key = "terraform/state-test/terraform.tfstate"
region = "ap-northeast-2"
#dynamodb_table = "terraform-lock" 주석처리
}
...
terraform init && terraform apply -auto-approve 로 배포한다. 아래 명령어를 사용하면 배포한 s3 내에 tfstate 파일이 있는 것을 볼 수 있다.
MYBUCKET=hithere-hello-tf102-remote-backend
aws s3 ls s3://$MYBUCKET --recursive --human-readable --summarize
2. DynamoDB 배포: 잠금기능을 사용하기 위해서이기 때문에, LockID라는 기본 키가 있는 테이블을 생성해야 한다. 마찬가지로 아까 클론한 레포에 DynamoDB를 위한 테라폼 설정파일이 들어 있다.
# 작업 경로
$HOME/tfstudy/provider_practice/example/state/step3_remote_backend/dynamodb
terraform init && terraform apply -auto-approve
이번에는 테라폼코드가 main.tf와 provider.tf 로만 구성되어 있다. 개별적으로 이름 등을 설정할 필요는 없기때문인것 같다.
# provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.4.0"
}
}
required_version = ">= 1.4"
}
provider "aws" {}
# main.tf
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "terraform-lock" # table이름
hash_key = "LockID" # key 이름
billing_mode = "PAY_PER_REQUEST"
attribute {
name = "LockID"
type = "S" # key 타입
}
}
배포된 dynamodb를 lock 용도로 사용한다는 것을 vpc의 provider을 수정해서 명시한다. 처음으로 -migrate-state라는 옵션을 줘서 새로 백엔드를 명시하되 기존의 state 내용을 이관해 오게 된다.
cd ../vpc
vi provider.tf
# 내용 수정
...
backend "s3" {
bucket = "gasida-hello-t1014-remote-backend"
key = "terraform/state-test/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-lock"
}
...
# 백엔드 수정 반영
terraform init -migrate-state
새로 initialization이 잘 되었다.
aws web console 에서 각각의 상태를 볼 수 있다. 현재까지는 한번도 state에 수정이 일어나지 않았기 때문에 s3에는 1개의 tfstate file이 있고 dynamodb는 비어 있다.
그러면 리소스 변경 후 apply 시 백엔드에 어떤 동작이 일어나는지 확인해보자.
현재 작업 디렉토리인 /vpc에서 main.tf 를 수정하여 vpc 이름을 바꾼 후 apply 한다 (auto-approve X)
# before
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
# after
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC 2"
}
}
현재 auto-approve 되지 않고 user의 approve를 기다리고 있는 시점에서 s3과 dynamodb를 확인해보면
- s3: 내용변경이 없음
- dynamodb: 현재 request된 apply가 들어와 있음
그렇다면 apply 실행 후에는 각각이 어떻게 변했는지 살펴보자
- s3: 변경된 내용이 반영되었음 (버전 표시에서 확인)
- dynamodb: request는 처리되었고 digest ID 로 기록되어 있음
이렇게 팀작업시 테라폼 백엔드에서 충돌이 나지 않게 lock 기능을 구현해 보았다.
실제 구성시 어떻게 작업해야 할지에 대한 가이드가 되어 유익했고, 예제에서 벗어나 (작지만) 프로젝트 단위로 관리된 테라폼 코드를 볼 수 있어서 조금 덜 막막하게 구성할 수 있게 되었다.
자원 삭제
우리의 지갑은 소중하니까요!
# 테라폼 배포 리소스 삭제 : 현재 vpc 디렉터리
terraform destroy -auto-approve
# DynamoDB 삭제
cd ../dynamodb
terraform destroy -auto-approve
# S3 삭제
cd ../s3_backend
terraform destroy -auto-approve
# S3 버킷에 객체 삭제
aws s3 rm s3://$MYBUCKET --recursive
# S3 버킷에 버저닝 객체 삭제
aws s3api delete-objects \
--bucket $MYBUCKET \
--delete "$(aws s3api list-object-versions \
--bucket "$MYBUCKET" \
--output=json \
--query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"
# S3 버킷에 삭제마커 삭제
aws s3api delete-objects --bucket $MYBUCKET \
--delete "$(aws s3api list-object-versions --bucket "$MYBUCKET" \
--query='{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')"
# S3 삭제
terraform destroy -auto-approve
이번 주차 스터디 과제를 하면서 예제에서 벗어나 본격적인 테라폼 실사용에 접어들었다는 느낌이 들어 뿌듯했다.
항상 좋은 실습 만들어주시고, 또 스터디 이끌어주셔서 감사합니다!
'EKS@Terraform' 카테고리의 다른 글
[Terraform@CloudNet] Terraform Runner: Atlantis (0) | 2024.07.13 |
---|---|
[Terraform@CloudNet] Terraform Module (0) | 2024.07.13 |
[Terraform@CloudNet] Terraform Provider (0) | 2024.07.06 |
[Terraform@CloudNet] Terraform 기본 사용 (2) (2) | 2024.06.23 |
[Terraform@CloudNet] Terraform 기본 사용 (1) (2) | 2024.06.15 |