Terraform상태관리
terraform apply를 실행할때마다 생성한 리소스를 찾아 그에 따라 업데이트를 수행
terraform의 상태관리 : 상태정보를 저장한 파일의 내용(terraform.tfstate)과수행작업을 담은 main.tf파일을 비교하여 상태와 일치하지 않은 경우에만 작업을 수행 → 멱등성을 보장
( terraform.tfstate :: 멱등성을 위해 상태정보를 저장 )
업무 프로세스는 다양한 리소스의 상태파일(tfstate)을 공유
상태파일 공유저장소(동일한 상태파일에 접근)를 구축 → 상태파일 잠금기능(lock)으로 무결성 보장, 격리
git에 업로드하는 것은 권장하지 않음( 버전관리 서비스가 아닌 공유저장소 : S3 등 ⇒ 원격 백앤드가 지원됨 )
S3 : DB를 통한 잠금(lock)을 수행( DynamoDB ) → 트랜잭션 관리
Terrafrom 상태파일(tfstate)
- Terraform을 실행할 때마다 Terraform 상태 파일에 생성된 인프라에 대한 정보가 기록 = Terraform 상태 파일(terraform.tfstate)은 terraform apply 명령어가 실행될 때마다 업데이트
- 실제 리소스 표현으로의 매핑을 기록하는 사용자 지정 JSON형식, tfstate확장자
- 상태파일은 비공개 API → 내부 용도로만 사용됨, 편집하거나 읽는것에 제한
[ Terraform 백엔드 ] : Terraform이 상태를 로드하고 저장하는 방법을 결정( 상태파일관리 시스템 )
- 로컬 디스크에 상태파일을 저장하는 로컬 백엔드
- 원격 백엔드 : 상태파일을 원격 공유 저장소에 저장 → Amazon S3, Azure Storage, Google Cloud Storage, HashiCorp의 Terraform Cloud와 Terraform Enterprise
- 수동작업에 대한 오류 해소 : 해당 백엔드에서 상태파일을 자동으로 로드하고 테라폼 수행후 자동으로 저장
- 잠금지원( 한 구성원이 apply를 실행중이라면 다른 구성원이 접근불가 ) 잠금없이 여러 구성원이 동시에 접근하는 경우 충돌, 데이터 손실 등을 초래
- 상태파일 암호화 지원
- 상태파일 격리 : 다양한 환경에 따라 인프라 격리
상태파일을 위한 공유 저장소
로컬로 존재하는 단일 파일에 상태저장 → 프로젝트 환경에서 팀 구성원이 공유위치의 동일한 상태파일에 엑세스
- 버전 관리 저장소는 부적절( 수동작업 오류, 잠금지원X, 비밀화 불가 )
- Amazon S3(Simple Storage Service)
- 관리형 서비스로 추가 배포 및 관리 X, 강한 내구성과 가용성
- DynamoDB를 통한 잠금지원 ( 트랜잭션을 통한 데이터 lock ) : S3와 상호호환성이 뛰어난 DynamoDB를 사용
- 버전관리를 지원( 롤백가능 )
[ 실습 ] 상태파일 공유 저장소 구축
(1) ~/terraform-demo/storage의 main.tf : S3리소스, dynamoDB리소스 ⇒ 공유저장소 완성
캐시참조를 위해 .terraformrc파일 복사
+) terraform fmt : 형식 맞는지 확인
- S3 버킷 리소스 생성
- bucket명은 전역적으로 고유한 이름
- 삭제방지 : 라이프사이클에 prevent_destroy 설정
Terraform이 리소스를 삭제하지 않도록 강제함 ( terraform destory를 수행해도 콘솔로 직접 삭제하지 않으면 삭제되지 않음 )
[ +추가적인 보호기능 ]
- aws_s3_bucket_versioning 리소스 : S3 버저닝 기능 버전관리 활성화하여 파일 업데이트마다 버전으로 관리 → 잘못된 데이터 입력시 이전버전으로 회귀가능
- aws_s3_bucket_server_side_encryption_configuration 리소스 : S3 server_side 암호화 기능 기록된 모든 데이터에 대해 서버측 암호화를 활성화
- aws_s3_bucket_public_access_block 리소스 : 버킷에 대한 퍼블릭 접근을 차단 상태파일의 민감데이터, secret이 포함되기에 외부 접근 차단 버킷 주소나 이름이 노출되더라도 외부에서 접근이 차단
- aws_dynamodb_table 리소스 : 잠금에 사용할 dynamo DB생성 ( 분산 키-값 저장소 → 강력하게 일관된 읽기 및 조건부 쓰기를 지원 ) LockID라는 기본키가 있는 DynamoDB 테이블을 생성 ⇒ Terraform이 상태 파일 수정 시 해당 테이블의 LockID를 사용하여 락(Lock)을 걸고 작업
terraform {
backend "s3" {
bucket = "terraform-state-rudalsss-wave"
key = "global/s3/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "terraform-state-rudalsss-wave"
# Prevent accidental deletion of this S3 bucket
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "enabled" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "public_access" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
output "s3_bucket_arn" {
value = aws_s3_bucket.terraform_state.arn
description = "The ARN of the S3 bucket"
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.terraform_locks.name
description = "The name of the DynamoDB table"
}
(2) 공유저장소(원격백엔드)에 상태저장을 사용할 테라폼 tf파일
S3 버킷에 상태를 저장하도록 Terraform을 구성하려면(암호화 및 잠금 포함) Terraform 코드에 백엔드 구성을 추가
terraform {
backend "<BACKEND_NAME>" {
[CONFIG...]
}
}
BACKEND_NAME은 사용하려는 백엔드의 이름(예: "s3” )
S3는 디렉토리 구조가 아니라(평면구조) 키로 구성되어있는 항목
terraform {
backend "s3" {
bucket = "terraform-state-rudalsss-wave"
key = "global/s3/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
bucket : 사용할 S3 버킷의 이름
key : Terraform 상태 파일을 작성해야 하는 S3 버킷 내의 파일 경로
region : 버킷이 존재하는 AWS 리전
dynamodb_table : lock에 사용할 DynamoDB 테이블
** 원격 백엔드로 이용되는 S3 버킷과 DynamoDB 테이블 이름만 제대로 지정되면 작업 디렉터리 위치와 관계없이 Terraform 상태관리가 정상적으로 동작
(3) terraform init으로 Terraform 백엔드를 구성 → 상태파일 복사
이미 로컬에 상태 파일이 있음을 자동으로 감지하고 이를 새 S3 백엔드로 복사하라는 메시지
출력변수로 S3 버킷의 Amazon 리소스 이름(ARN)과 DynamoDB 테이블의 이름을 출력
테라폼 제한사항
(1) Terraform을 사용하여 Terraform 상태를 저장할 S3 버킷을 생성할 때 닭과 달걀이 결정되는 상황
- 생성 : 코드로 s3를 생성( terraform apply ) ⇒ 특정 테라폼의 local backend의 tfstate를 remote backend(S3)에 저장( backend구성이후 terraform init ) ⇒ 저장공간을 생성후 데이터를 저장
- 원격 삭제 : 삭제먼저 한다면 데이터가 소실되는 문제 저장된 상태파일을 local backend에 재복사( backend구성제거이후 terraform init ) → 원격 저장소 인프라를 삭제( terraform destory, 콘솔삭제 )
(2) Terraform의 백엔드 블록에서는 변수나 참조를 사용할 수 없음 → 구성파일을 분리 .hcl
→ Terraform 모듈에 수동으로 복사하여 붙여넣기를 수행해야함
상태파일 환경격리
- 개발(Dev), 테스트(Test), 운영(Prod) 등의 각 환경마다 별도의 상태 파일을 사용하여 서로 영향을 미치지 않도록 격리하는 방법
- 인프라의 환경별 격리 필요성 : 스테이징 단계에서 앱의 새 버전을 배포하려고 시도하는 동안 프로덕션 단계 에서 앱이 중단될 수 있습니다
- 단일 Terraform 구성세트에서 모든 환경을 관리하는 경우 격리가 수행되지 않음 → 환경별로 다른 Terraform 및 인프라 구성을 갖춤
(1) 작업공간(workspace) 기능으로 격리( git의 branch와 유사 ) ⇒ 동일한 구성에 대한 빠르고 격리된 테스트에 유용
(2) 파일레이아웃을 통한 격리 : 디렉토리를 구축하여 파일을 개별저장 → 더욱 가시적이고 직관적임 ⇒ 환경간 강력한 분리가 필요한 production 사례에서 많이 사용됨
(1) 작업공간을 통한 격리
Terraform workspace : 별도의 이름이 지정된 작업공간, 독립적인 Terrform 상태( 상태파일 tfstate )를 소유함
( 작업 공간을 명시적으로 지정하지 않으면 기본 작업 공간이 전체 시간 동안 사용됨 )
terraform workspace show
현재 workspace 목록확인 ( 기본 default )
cp -r storage workspace-demo
storage 디렉터리에서 정의된 원격 백엔드 설정을 workspace-demo에서 사용
terraform-demo/storage/main.tf
provider aws {}
resource "aws_instance" "workspace-test" {
ami = "ami-024ea438ab0376a47"
instance_type = "t2.micro"
}
terraform{
backend "s3" {
bucket = "terraform-state-rudalsss-wave"
key = "workspaces-example/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
key 값 : Terraform 상태 파일(terraform.tfstate)이 S3 버킷 내에 저장되는 경로(키)
terraform init -reconfigure
terraform apply -auto-approve
[ 워크스페이스 실습 ]
위에는 default 워크스페이스에서 수행하고 지정된 경로의 원격백엔드에 상태가 저장됨
새로운 워크스페이스 생성후에는 apply하면 별도의 원격백엔드에 상태가 저장됨 & 동일한 구성임에도 인스턴스가 하나 더 생성
terraform workspace new example1
terraform apply -auto-approve
terraform workspace new example2
terraform apply -auto-approve
범용버킷 terraform-state-rudalsss-wave > env:/ 하의 workspace
env: 폴더 내에는 각 작업 공간에 대해 하나씩 폴더가 생성됨
연속적으로 생성된 3개의 인스턴스
Terraform에서 새로운 워크스페이스를 생성하면 해당 워크스페이스의 상태 파일(terraform.tfstate)이 자동으로 지정된 S3 버킷에 저장
⇒ 환경 분리(dev/prod/test)에 매우 유용
key 경로가 워크스페이스에 맞춰 변경되어 정상적으로 동작
key = "envs/${terraform.workspace}/원래 지정경로"
workspace의 전환 = 상태파일이 저장된 경로(키)를 변경
(2) 파일레이아웃을 통한 격리
⇒ 인프라의 격리성 & 리소스 코드에 대한 명확한 이해와 파악
- 각 "환경"에 대한 별도의 폴더( stage, prod .. ) → "구성 요소"에 대한 별도의 폴더 →각각 main.tf를 구축 ( ex. prod/vpc/main.tf )
- 각 환경에 대해 서로다른 백엔드 구성 ( 서로다른 S3 버킷, 계정을 사용 )
main.tf를 분리 → 변수, 데이터 소스용 tf파일을 별도로 구축
- variables.tf : 입력변수
- outputs.tf : 출력 변수
- main.tf : 리소스 및 데이터 소스
- 이외의 dependencies.tf, provider.tf
⇒ 이는 terraform이 작업 디렉토리에서 모든 tf확장자를 모두 읽어들여서 실행(apply)하기에 가능함, 관리용이
[root@controller terraform-demo]# ls
run-test.sh stage storage terraform.tfstate terraform.tfstate.backup
[root@controller terraform-demo]# mkdir -p stage/services/webserver-cluster
[root@controller terraform-demo]# mv [main.tf](<http://main.tf/>) stage/services/webserver-cluster
- tfstate파일은 s3저장소에서 공유하기에 별도로 복사하지 않아도됨
- .terraformrc에 캐시를 설정하여 개별적인 .terraform 폴더를 구축하고 사용하지 않도록 함
mkdir ~/.terraform.d/plugin-cache/ vi ~/.terraformrc # plugin_cache_dir = "$HOME/.terraform.d/plugin-cache" 추가
[ 실습 ]
서버 클러스터 코드와 이 장에서 작성한 Amazon S3 및 DynamoDB 코드를 가져와 다음 그림의 폴더 구조를 사용하여 재정렬해보기
웹 서버 클러스터가 MySQL 데이터베이스와 통신하도록 구축 → 이때 별도의 디렉토리 data-stores/my-sql를 작업디렉토리로 이용하여 코드 관리 및 격리
terraform_remote_state 데이터 소스
데이터 소스 data.terraform_remote_state을 통해 테라폼 리소스의 상태파일을 가져오고 참조가능
( ↔ 데이터소스 aws_subnets는 AWS에서 읽기 전용 정보를 가져옴 )
- 테라폼으로 프로비저닝된 리소스에 대한 상태를 저장하고 그 값을 가져오는 것과 같다( 테라폼 생성 리소스에 한함 )
- 다른 작업 디렉터리나 워크스페이스에서 생성된 리소스의 상태 파일(tfstate)을 참조하여 리소스 정보를 가져오는 데 사용하는 Terraform 데이터 소스
[ 선언 ]
data "terraform_remote_state" "NAME" {
backend = "s3" # 원격 백엔드 유형
config = {
bucket = "terraform-state-bucket" # 상태 파일이 저장된 S3 버킷
key = "project-path/terraform.tfstate" # 상태 파일 경로
region = "ap-northeast-2" # 버킷 지역
dynamodb_table = "terraform-locks" # (선택) Lock 관리 테이블
}
}
config : S3내에서 해당 리소스의 상태파일을 찾기 위한 설정값들
NAME : 리소스의 상태파일을 테라폼 코드내에서 참조하기 위한 이름
[ 사용 ]
data.terraform_remote_state.<NAME>.outputs.<ATTRIBUTE>
⇒ NAME을 가진 리소스의 속성 값을 참조하는 식
리소스 내부 속성들은 상태 파일에 있지만 직접 참조하기 어렵기 때문에 outputs속성을 사용하여 접근하는 것이 일반적 ( 이때 output 값은 현재 작업 디렉터리가 아닌, 리모트 상태 파일에 기록된 테라폼 코드에서 정의된 output 값 ) *참조된 상태 파일에서 정의된 output 들을 참조
[ 실습 ]
기존에 복사해온 main.tf의 내용 → db_instance를 추가하는 작업을 data-stores/mysql 디렉토리에서 진행 ( 격리된 디렉토리 환경을 구성( services ↔ data-stores ), 상태파일관리 )
[root@controller terraform-demo]# ls
[run-test.sh](<http://run-test.sh/>) stage storage terraform.tfstate terraform.tfstate.backup
[root@controller terraform-demo]# cd stage/
[root@controller stage]# mkdir data-stores/mysql -p
[ DB구축 전용 디렉토리(data-stores) ]
기능별로 tf파일을 분리하여 구축
(1) main.tf
stag/data-stores/mysql/main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_db_instance" "example" {
identifier_prefix = "terraform-mysql"
engine = "mysql"
allocated_storage = 10
instance_class = "db.t3.micro"
skip_final_snapshot = true
db_name = "example_database"
# How should we set the username and password?
username = var.db_username
password = var.db_password
}
variable "db_username" {
description = "The username for the database"
type = string
sensitive = true
}
variable "db_password" {
description = "The password for the database"
type = string
sensitive = true
}
- mysql DB인스턴스 리소스를 생성 → 이때 db_username, db_password라는 입력변수로 입력값을 받아서 만들어서 속성값으로 활용 ( 비번은 8자리 이상으로 지정 )
- 환경변수를 이용하는 방법 : TF_VAR_db_username 및 TF_VAR_db_password를 설정( export )
(2) 백앤드 구성 추가
terraform.tf라는 새로운 tf파일을 구성하여 구축하게 함
stag/data-stores/mysql/terraform.tf
terraform {
backend "s3" {
# Replace this with your bucket name!
bucket = "terraform-state-rudalsss"
key = "stage/data-stores/mysql/terraform.tfstate"
region = "ap-northeast-2"
# Replace this with your DynamoDB table name!
dynamodb_table = "terraform-locks"
encrypt = true
}
}
(3) 출력변수(output)정의 → 생성된 리소스( AWS RDS 인스턴스 )에 대한 정보 출력
stag/data-stores/mysql/output.tf
output "address" {
value = aws_db_instance.example.address
description = "Connect to the database at this endpoint"
}
output "port" {
value = aws_db_instance.example.port
description = "The port the database is listening on"
}
⇒ 추후에 해당 리소스를 remote_state 데이터 소스로 참조할때 리소스 속성으로 활용될 수 있다.
(4) DB 생성
terraform init, terraform apply ⇒ DB가 생성된다
기본적으로 백앤드의 동작은 lock을 잡고 terraform apply시에 리소스의 ftstate를 관리( 이떄 lock파일을 잡고 격리적으로 수행 )
그러나 interrupt와 같이 비정상종료를 하게 되면 lock파일이 잡혀있는 것으로 인식 ⇒ 강제적으로 terraform apply 시에 옵션으로 -lock=false로 lock을 무시하게끔한다
** Terraform 백엔드 동작: terraform apply 시 lock 파일을 잡아 상태 파일(tfstate)을 관리합니다. 이는 여러 사용자가 동시에 상태 파일을 변경하지 못하도록 보장하는 중요한 메커니즘입니다.
user : rudals
pwd : 12345678
로 지정하여 데이터베이스 생성
생성되면 address가 반환된다 ( endpoint = address + port )
DB관련 tfstate파일이 생성됨
지정된 저장위치 ⇒ s3://terraform-state-rudalsss-wave/stage/data-stores/mysql/terraform.tfstate
(5) web-cluster에서, 생성한 DB에 대한 데이터소스의 상태파일을 terraform_remote_state로 가져와서 활용하기
5-1) 스크립트 환경에서(template함수를 이용하지 않고 userdata구성시) terraform_remote_state의 값에 대한 랜더링 처리
/root/terraform-demo/stage/services/webserver-cluster 의 내용변경
terraform_remote_state 데이터 소스에서 데이터베이스 주소와 포트를 가져오고 해당 정보를 HTTP 응답에 노출
데이터소스를 참조하는 식 → 랜더링작업이 필요함 ${{ }}
stag/services/webserver-cluster/main.tf
...
resource "aws_launch_configuration" "example" {
image_id = "ami-024ea438ab0376a47"
instance_type = var.instance_type
security_groups = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
cat > index.html <<EOF
<h1>Hello, World</h1>
<p>DB address: ${data.terraform_remote_state.db.outputs.address}</p>
<p>DB port: ${data.terraform_remote_state.db.outputs.db_port}</p>
nohup busybox httpd -f -p ${server_port} &
EOF
lifecycle {
create_before_destroy = true
}
}
...
data "terraform_remote_state" "db" {
backend = "s3"
config = {
bucket = "terraform-state-rudalsss-wave"
key = "stage/data-source/mysql/terraform.tfstate"
region = "ap-northeast-2"
}
}
생성되는 인스턴스들에 테라폼을 이용하여 생성해두었던 RDS정보를 가져와서 연결하는 설정
data.terraform_remote_state.db.outputs.address
data.terraform_remote_state.db.outputs.port
+) terraform init 실행 결과 추가 정리
terraform init명령어의 역할 : provider plugin 다운로드, backend 로드, module구성
즉, 공급자를 설치하고, 백엔드를 구성하고, 모듈을 다운로드하는 등 모두 init 이라는 하나의 편리한 명령으로 수행 Initializing modules... Initializing the backend... Initializing provider plugins... Terraform has been successfully initialized!
+) templatefile 내장 함수
[ Terraform의 내장함수 형식 ]
함수명( 매개변수, 처리대상값 )
# format function
format(<FMT>, <ARGS>, ...)
# template function
templatefile(<PATH>, <VARS>)
templatefile 함수 : PATH 에서 파일을 읽고, 이를 템플릿으로 렌더링하고, 결과를 문자열로 반환
파일을 읽어서 해당 변수를 치환
⇒ 스크립트를 간결하게 작성가능, Bash 스크립트를 인라인으로 작성하는 것보다 간결함
이전상태( 스크립트내부에서 직접 정의 )
resource "aws_launch_configuration" "example" {
image_id = "ami-024ea438ab0376a47"
instance_type = "t3.micro"
security_groups = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
echo "${data.terraform_remote_state.db.outputs.address}" >> index.html
echo "${data.terraform_remote_state.db.outputs.port}" >> index.html
nohup busybox httpd -f -p ${var.server_port} &
EOF
lifecycle {
create_before_destroy = true
}
}
랜더링 대상이되는 스크립트를 따로 분리( user_data.sh )
template함수에서 해당 스크립트의 경로와, 내부의 변수에 대한 정의를 매개변수로 지정
스크립트 내의 변수는 접두사가 필요하지 않음 ( ${var.server_port} 가 아닌 ${server_port} 를 사용 )
resource "aws_launch_configuration" "example" {
image_id = "ami-024ea438ab0376a47"
instance_type = "t3.micro"
security_groups = [aws_security_group.instance.id]
user_data = templatefile("user-data.sh", {
server_port = var.server_port
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
lifecycle {
create_before_destroy = true
}
}
'CLOUDWAVE' 카테고리의 다른 글
IaC : Terraform - 테라폼 모듈(module)로 재사용가능한 인프라 구성 (0) | 2025.02.07 |
---|---|
IaC : Terraform 실습 - 웹클러스터 배포, 로드밸런서 배포, 자원반환 (0) | 2025.02.07 |
IaC : Terraform 실습 - 단일 서버배포, 단일 웹서버배포, 구성가능한 웹서버배포 (0) | 2025.02.07 |
IaC : Terraform - 테라폼 소개, 테라폼 환경 구축 (0) | 2025.02.07 |
IaC : Ansible 변수관리 및 작업제어 (0) | 2025.02.01 |