이번 포스팅에서는 Terraform State와 Module에 대해서 알아보겠다.
Terraform state는 말 그대로 Terraform의 상태정보를 정장해놓는 파일이다.
그림출처: https://kschoi728.tistory.com/135
*.tfstate 형식으로 파일이 생성되는데, 이 파일과 실제 타켓되는 CSP의 자원들과의 상태를 비교하여 create, replace, update, destroy를 판단하게 된다.
간단히 실습을 해보자.
cat <<EOT > vpc.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
tags = {
Name = "t101-study"
}
}
EOT
# 배포
terraform init && terraform plan && terraform apply -auto-approve
# 상태 파일 확인 : JSON 형식
ls
cat terraform.tfstate | jq | grep serial
...
"serial": 1,
...
terraform apply까지 하게되면, 이전에는 없었던 tfstate 파일이 생성된다. 파일의 내용을 살펴보자
☁ t101-4week cat terraform.tfstate
{
"version": 4,
"terraform_version": "1.5.6",
"serial": 1,
"lineage": "3XXXXb4-eXX-d4e2-e2d7-41XXXXXXXX41",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_vpc",
"name": "myvpc",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 1,
"attributes": {
"arn": "arn:aws:ec2:ap-northeast-2:0XXXXXXXXXXX:vpc/vpc-026cad42e4b415fed",
"assign_generated_ipv6_cidr_block": false,
"cidr_block": "10.10.0.0/16",
"default_network_acl_id": "acl-085667c8954a1c3c1",
"default_route_table_id": "rtb-043e6f4af8f037afe",
"default_security_group_id": "sg-028b5400191f9b57f",
"dhcp_options_id": "dopt-0b8e4f60",
"enable_dns_hostnames": false,
"enable_dns_support": true,
"enable_network_address_usage_metrics": false,
"id": "vpc-026cad42e4b415fed",
"instance_tenancy": "default",
"ipv4_ipam_pool_id": null,
"ipv4_netmask_length": null,
"ipv6_association_id": "",
"ipv6_cidr_block": "",
"ipv6_cidr_block_network_border_group": "",
"ipv6_ipam_pool_id": "",
"ipv6_netmask_length": 0,
"main_route_table_id": "rtb-043e6f4af8f037afe",
"owner_id": "033359017116",
"tags": {
"Name": "t101-study"
},
"tags_all": {
"Name": "t101-study"
}
},
"sensitive_attributes": [],
"private": "eXXXXXXXXXXXXXXXXXXXXXXXXXXX=="
}
]
}
],
"check_results": null
}
방금 생성한 AWS VPC의 정보들이 담겨있다. 이 상태에서 Terraform 코드를 업데이트 하면 versioning이 자동으로 된다.
cat <<EOT > vpc.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
tags = {
Name = "tf-state"
}
}
EOT
# 배포 : plan 시 tfstate 상태와 코드 내용을 비교해서 검토
terraform plan && terraform apply -auto-approve
# 상태 파일 비교 : 백업 파일 생성됨
ls terraform.tfstate*
terraform.tfstate terraform.tfstate.backup
diff terraform.tfstate terraform.tfstate.backup
< "serial": 3,
---
> "serial": 1,
직전의 tfstate 파일이 tfstate.backup이 되어, 혹시 모를 상황에 대비도 가능하다.
기본적으로 tfstate파일은 로컬 경로에 저장이되는데, terraform을 혼자가 아닌 팀원이 사용할 경우 이렇게 로컬에 저장되어 있는 형태는 매우 위험하다.
충돌로 인한 인프라 이슈가 생길 수 있고, 혹시 tfstate 파일이 날아갔을 경우, 골치아픈일이 생길 수 있다.
그래서 보통은 tfstate 파일에 대한 locking과 versioning 기능을 사용하게 되는데, 보통 CSP에서 제공하는 스토리지로 세팅이 가능하다.
그림출처: https://medium.com/devops-mojo/terraform-remote-states-overview-what-is-terraform-remote-state-storage-introduction-936223a0e9d0
AWS의 경우 S3와 DynamoDB를 통해 Versioning과 Locking 기능을 구현하게된다.
그림출처: https://github.com/binbashar/terraform-aws-tfstate-backend
(참고로 Azure의 경우 Blob Storage 만으로 Vesioning과 Locking이 가능하다.)
backend를 활용하여 위와 같은 구현이 가능한데, 아래 코드를 참고하자.
terraform {
backend "s3" {
bucket = "nowjean-t101study-tfstate-week3"
key = "workspaces-default/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks-week3"
}
Backend 실습
$ mkdir backend
$ cd backend
backend.tf
소스코드의 S3, DynamoDB 관련 설정을보면, Versioning과 Locking 기능을 활성화 시킨 것을 볼 수 있다.
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "mys3bucket" {
bucket = "kimchigood-t101study-tfstate-week3"
}
# Enable versioning so you can see the full revision history of your state files
resource "aws_s3_bucket_versioning" "mys3bucket_versioning" {
bucket = aws_s3_bucket.mys3bucket.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "mydynamodbtable" {
name = "terraform-locks-week3"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
output "s3_bucket_arn" {
value = aws_s3_bucket.mys3bucket.arn
description = "The ARN of the S3 bucket"
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.mydynamodbtable.name
description = "The name of the DynamoDB table"
}
$ terraform init && terraform plan && terraform apply -auto-approve
cd ..
main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "ami-0c76973fbe0ee100c"
instance_type = "t2.micro"
tags = {
Name = terraform.workspace == "default" ? "default-t101-week3" : "t101-week3"
}
}
terraform {
backend "s3" {
bucket = "kimchigood-t101study-tfstate-week3"
key = "workspaces-default/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks-week3"
}
}
$ terraform init && terraform plan && terraform apply -auto-approve
Versioning 확인을 위해 main.tf의 ami 이미지를 ami-0c5781df01df48d58 로 바꾼 후 terraform plan, apply를 해보자.
더 자세한 내용은 지난 블로그글에 나와있다. https://velog.io/@kubernetes/Terraform-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC
Module은 복잡하거나 큰 인프라를 Terraform으로 구현할 때 구조화를 위해 필요하다. 간단히 설명하면, 디렉토리 구조를 갖추는 방법이라고 할 수 있다.
그림참조: 참조: https://www.oreilly.com/library/view/terraform-up-and/9781491977071/ch04.html
- 모듈은 루트 모듈과 자식 모듈로 구분된다
- 루트 모듈 Root Module : 테라폼을 실행하고 프로비저닝하는 최상위 모듈
- 자식 모듈 Child Module : 루트 모듈의 구성에서 호출되는 외부 구성 집합
위 그림과 같이 로컬에서 디렉토리 구조를 구현할 수 도 있고, Terraform Registry, 깃헙, S3 버킷등과 연동하는 방법도 가능하다.
https://developer.hashicorp.com/terraform/language/modules/sources#terraform-registry
루트, 자식 모듈을 세팅하면, 루트 모듈에서 module을 선언하여 값을 참조하는 것도 가능하다.
module "mypw1" {
source = "../modules/terraform-random-pwgen"
}
module "mypw2" {
source = "../modules/terraform-random-pwgen"
isDB = true
}
output "mypw1" {
value = module.mypw1
}
output "mypw2" {
value = module.mypw2
}
terraform registry를 통해 모듈을 참조하여, 요구사항에 맞게 사용하는 방법도 있다.
https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = true
tags = {
Terraform = "true"
Environment = "dev"
}
}
그럼 aws vpc 모듈을 사용하는 실습을 해보자.
terraform registry 실습
git clone https://github.com/terraform-aws-modules/terraform-aws-vpc/
tree terraform-aws-vpc/examples -L 1
tree terraform-aws-vpc/examples -L 2
cd terraform-aws-vpc/examples/simple
ls *.tf
cat main.tf
# 서울 리전 변경
grep 'eu-west-1' main.tf
sed -i -e 's/eu-west-1/ap-northeast-2/g' main.tf
# VPC CIDR 변경
grep '10.0.0.0' main.tf
sed -i -e 's/10.0.0.0/10.10.0.0/g' main.tf
# main.tf 파일 내용 확인
cat main.tf
...
module "vpc" {
source = "../../"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
tags = local.tags
}
# 모듈 확인
tree ../../modules
코드실행
terraform init
cat .terraform/modules/modules.json| jq
# 생성된 리소스들 확인해보자!
terraform apply -auto-approve
terraform output
terraform state list
# 실습 완료 후 리소스 삭제
terraform destroy -auto-approve
위 예제에서는 소스를 모두 받아서 모듈에 필요한 값을 ../../ 경로에서 받았지만,
<NAMESPACE>/<Name of module>/<Name of Provider>
이 규칙에 따라, 아래와 같이 실제 registry 경로를 참조하여 사용 가능하다.
참조: https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
source = "terraform-aws-modules/vpc/aws"
인프라 구성이 간단할 경우는 크게 필요는 없겠지만, 점점 크기가 커지고 복잡해지면 network, storage 등의 모듈로 나눠서 관리하는 것이 바람직하다.
module을 잘 설계하면, variable과 함께 활용해서, terraform 코드를 특정 프로젝트에만 국한된 것이 아니라, 붕어빵 기계처럼 여러 프로젝트에서 살짝만 변경해서 인프라를 찍어내는 것도 가능하다.
잘 익혀두면 좋을 것 같다.