테라폼은 내 계정에 있는 수많은 리소스 중 어떤 리소스가 관리 대상인지 어떻게 알 수 있을까?
이번 포스팅에서는 테라폼이 인프라의 상태를 어떻게 탐지하는지, 그리고 테라폼 프로젝트의 파일 레이아웃, 격리, 잠금 등에 미치는 영향을 어떻게 탐지하는지 알아보고자 한다.
-lock-timeout=<TIME>
매개 변수를 사용하면 잠금이 해제되기까지 테라폼이 얼마 동안 대기하도록 할지 설정할 수 있다.# 별도 디렉터리 생성
mkdir tfstate-backend
cd tfstate-backend
# backend.tf 생성
cat <<EOT > backend.tf
provider "aws" {
region = "ap-northeast-2"
}
# 버킷명은 모든 AWS 리전의 모든 AWS 계정에서 고유해야 한다.
resource "aws_s3_bucket" "mys3bucket" {
bucket = "jinju-tf-study-tfstate"
}
# 코드 이력을 관리하기 위해 상태 파일의 버전 관리를 활성화한다.
# 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"
}
}
output "s3_bucket_arn" {
value = aws_s3_bucket.mys3bucket.arn
description = "The ARN of the S3 bucket"
}
EOT
# 배포
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
배포가 정상적으로 완료되었고, Outputs 변수를 통해 해당 버킷의 arn 또한 출력되었다.
이전에 콘솔에서 DynamoDB를 생성한 경험이 없으므로, 코드 적용 전 해당 실습을 먼저 진행하였다.
DynamoDB 콘솔을 사용하여 새 Music 테이블을 생성하려면
1. AWS Management Console에 로그인하고 DynamoDB 콘솔을 연다.
2. 콘솔 왼쪽의 탐색 창에서 [Dashboard]를 선택한다.
3. 콘솔의 오른쪽에서 [Create table(테이블 생성)]을 선택한다.
4. 다음과 같이 테이블 세부 정보를 입력한다.
이 단계에서는 이전 단계에서 생성한 Music 테이블에 여러 항목을 삽입한다.
1. 콘솔 왼쪽의 탐색 창에서 [Tables]를 선택한다.
2. 테이블 목록에서 [Music] 테이블을 선택한다.
3. [Actions] 메뉴에서 [Create item]을 선택한다.
4. [Add new attribute]를 선택한 다음 [Number]를 선택한다. 생성된 Attribute name은 Awards
로 지정한다.
5. 4번 과정을 반복하여 [String] item을 하나 더 생성하고, 이번에 생성된 Attribute name은 AlbumTitle
로 지정한다.
6. 각 항목들에 대해 다음과 같은 값을 입력한 후 [Create Item]을 선택한다.
7. 위 절차를 반복하여 다음 값으로 다른 항목을 생성한다.
8. 이 작업을 한 번 더 수행하여 이전 단계와 Artist는 같지만 다른 속성의 값이 다른 항목을 또 하나 생성한다.
이외 데이터 업데이트, 데이터 쿼리 방법 등은 AWS 공식 문서 내용을 참고하자.
cat <<EOT > backend.tf
provider "aws" {
region = "ap-northeast-2"
}
# 버킷명은 모든 AWS 리전의 모든 AWS 계정에서 고유해야 한다.
resource "aws_s3_bucket" "mys3bucket" {
bucket = "jinju-tf-study-tfstate"
}
# 코드 이력을 관리하기 위해 상태 파일의 버전 관리를 활성화한다.
# 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"
}
}
# DynamoDB 잠금을 사용하기 위해서는 LockID 라는 기본 키가 있는 테이블을 생성해야 함.
resource "aws_dynamodb_table" "mydynamodbtable" {
name = "jinju-terraform-locks"
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"
}
EOT
# 배포
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
# DynamoDB 테이블 생성 확인
aws dynamodb describe-table --table-name jinju-terraform-locks | jq
aws dynamodb describe-table --table-name jinju-terraform-locks --output table
# 리소스 삭제
terraform destroy -auto-approve
terraform workspace show
: 현재 작업 공간 위치 확인terraform workspace new
: 새 작업 공간 생성terraform workspace list
: 현재 생성된 작업 공간 목록terraform workspace select
: 작업 공간 전환따라서 환경을 적절하게 격리하려면 아래 파일 레이아웃 방식을 사용하는 것이 좋다.
global/s3/main.tf ..
# 디렉터리 생성
mkdir -p global/s3 && cd global/s3
# 코드 파일 생성
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "mys3bucket" {
bucket = "jinju-s3-tfstate-week3-files"
}
# 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 = "jinju-terraform-locks-week3-files"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
EOT
cat <<EOT > outputs.tf
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"
}
EOT
# 배포
terraform init && terraform plan && terraform apply -auto-approve
# 배포 확인
terraform state list
aws s3 ls
aws dynamodb list-tables --output text
# 기존 작업 디렉터리로 이동
cd ../..
stage/data-stores/mysql/main.tf ..
# 디렉터리 생성
mkdir -p stage/data-stores/mysql && cd stage/data-stores/mysql
# VPC & 보안그룹 코드 파일 생성: main-vpcsg.tf
cat <<EOT > main-vpcsg.tf
terraform {
backend "s3" {
bucket = "jinju-s3-tfstate-week3-files"
key = "stage/data-stores/mysql/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "jinju-terraform-locks-week3-files"
}
}
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "jinju-tf-study-vpc"
}
}
resource "aws_subnet" "mysubnet3" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.3.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "jinju-tf-study-subnet3"
}
}
resource "aws_subnet" "mysubnet4" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.4.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "jinju-tf-study-subnet4"
}
}
resource "aws_route_table" "myrt2" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "jinju-tf-study-rt2"
}
}
resource "aws_route_table_association" "myrtassociation3" {
subnet_id = aws_subnet.mysubnet3.id
route_table_id = aws_route_table.myrt2.id
}
resource "aws_route_table_association" "myrtassociation4" {
subnet_id = aws_subnet.mysubnet4.id
route_table_id = aws_route_table.myrt2.id
}
resource "aws_security_group" "mysg2" {
vpc_id = aws_vpc.myvpc.id
name = "jinju-tf-study-SG-RDS"
description = "jinju T101 Study SG - RDS"
}
resource "aws_security_group_rule" "rdssginbound" {
type = "ingress"
from_port = 0
to_port = 3389
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg2.id
}
resource "aws_security_group_rule" "rdssgoutbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg2.id
}
EOT
# 백엔드 적용 및 VPC 등 배포
terraform init -force-copy && terraform plan && terraform apply -auto-approve
terraform state list
엥? 잘 나가다 갑자기 다음과 같은 오류가 출력되었다. - 참고
현재 위치에 존재하는 .terraform 폴더를 지우고 다시 init을 실행하니 정상 작동 및 배포되었다.│ Error: Error inspecting states in the "s3" backend: │ S3 bucket does not exist. │ │ The referenced S3 bucket must have been previously created. If the S3 bucket │ was created within the last minute, please wait for a minute or two and try │ again. │ │ Error: NoSuchBucket: The specified bucket does not exist │ status code: 404, request id: C0H87VKBF2VB3VPF, host id: PMCX2qmljtcSCUV5BznEppG6LAhLv7UDvsFEJEPsElN3FQbaY6IVq+qPMwSmOELh7/h1s1toQD4= │ │ │ Prior to changing backends, Terraform inspects the source and destination │ states to determine what kind of migration steps need to be taken, if any. │ Terraform failed to load the states. The data in both the source and the │ destination remain unmodified. Please resolve the above error and try again.
main.tf
cat <<EOT > main.tf
resource "aws_db_subnet_group" "mydbsubnet" {
name = "jinjudbsubnetgroup"
subnet_ids = [aws_subnet.mysubnet3.id, aws_subnet.mysubnet4.id]
tags = {
Name = "Jinju DB subnet group"
}
}
resource "aws_db_instance" "myrds" {
identifier_prefix = "jinju"
engine = "mysql"
allocated_storage = 10
instance_class = "db.t2.micro"
db_subnet_group_name = aws_db_subnet_group.mydbsubnet.name
vpc_security_group_ids = [aws_security_group.mysg2.id]
skip_final_snapshot = true
db_name = var.db_name
username = var.db_username
password = var.db_password
}
EOT
outputs.tf
cat <<EOT > outputs.tf
output "address" {
value = aws_db_instance.myrds.address
description = "Connect to the database at this endpoint"
}
output "port" {
value = aws_db_instance.myrds.port
description = "The port the database is listening on"
}
output "vpcid" {
value = aws_vpc.myvpc.id
description = "Jinju VPC Id"
}
EOT
variables.tf
cat <<EOT > variables.tf
# ---------------------------------------------------------------------------------------------------------------------
# REQUIRED PARAMETERS
# You must provide a value for each of these parameters.
# ---------------------------------------------------------------------------------------------------------------------
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
}
# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These parameters have reasonable defaults.
# ---------------------------------------------------------------------------------------------------------------------
variable "db_name" {
description = "The name to use for the database"
type = string
default = "tstudydb"
}
EOT
# MacOS 로컬 변수 입력
export TF_VAR_db_username='cloudneta'
export TF_VAR_db_password='cloudnetaQ!'
export | grep TF_VAR
로컬의 환경 변수를 통해 시크릿 값을 테라폼에 전달하는 방법이다.
이 외에도 테라폼 데이터 소스를 사용하여 AWS 시크릿 매니저와 같은 시크릿 저장소에서 정보를 읽을 수도 있다.
terraform plan && terraform apply -auto-approve
# 이전 디렉터리로 이동
cd ../../..
stage/services/webserver-cluster/main.tf ..
# 디렉터리 생성 및 이동
mkdir -p stage/services/webserver-cluster && cd stage/services/webserver-cluster
terraform_remote_state 데이터 소스를 사용할 수 있게 설정 및 백엔드 설정과 EC2가 사용할 서브넷을 생성: main.tf
cat <<EOT > main.tf
terraform {
backend "s3" {
bucket = "jinju-s3-tfstate-week3-files"
key = "stage/services/webserver-cluster/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "jinju-terraform-locks-week3-files"
}
}
provider "aws" {
region = "ap-northeast-2"
}
data "terraform_remote_state" "db" {
backend = "s3"
config = {
bucket = "jinju-s3-tfstate-week3-files"
key = "stage/data-stores/mysql/terraform.tfstate"
region = "ap-northeast-2"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = data.terraform_remote_state.db.outputs.vpcid
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "jinju-tf-study-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = data.terraform_remote_state.db.outputs.vpcid
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "jinju-tf-study-subnet2"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = data.terraform_remote_state.db.outputs.vpcid
tags = {
Name = "jinju-tf-study-igw"
}
}
resource "aws_route_table" "myrt" {
vpc_id = data.terraform_remote_state.db.outputs.vpcid
tags = {
Name = "jinju-tf-study-rt"
}
}
resource "aws_route_table_association" "myrtassociation1" {
subnet_id = aws_subnet.mysubnet1.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route_table_association" "myrtassociation2" {
subnet_id = aws_subnet.mysubnet2.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route" "mydefaultroute" {
route_table_id = aws_route_table.myrt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.myigw.id
}
resource "aws_security_group" "mysg" {
vpc_id = data.terraform_remote_state.db.outputs.vpcid
name = "jinju-tf-study-SG"
description = "tf Study SG"
}
resource "aws_security_group_rule" "mysginbound" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
resource "aws_security_group_rule" "mysgoutbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
EOT
# 백엔드 설정 및 plan & apply
terraform init -force-copy && terraform plan && terraform apply -auto-approve
# 리소스 확인
terraform state list
# User data 파일 생성
cat <<EOT > user-data.sh
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
cat > index.html <<EOF
<h1>JINJU TF Study</h1>
<p>My RDS DB address: \${db_address}</p>
<p>My RDS DB port: \${db_port}</p>
EOF
nohup ./busybox httpd -f -p \${server_port} &
EOT
# 오토 스케일링 그룹 코드 내용 추가
cat <<EOT >> main.tf
data "template_file" "user_data" {
template = file("user-data.sh")
vars = {
server_port = 8080
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
}
}
data "aws_ami" "my_amazonlinux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_launch_configuration" "mylauchconfig" {
name_prefix = "jinju-lauchconfig-"
image_id = data.aws_ami.my_amazonlinux2.id
instance_type = "t2.micro"
security_groups = [aws_security_group.mysg.id]
associate_public_ip_address = true
# Render the User Data script as a template
user_data = templatefile("user-data.sh", {
server_port = 8080
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
# Required when using a launch configuration with an auto scaling group.
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "myasg" {
name = "jinjuasg"
launch_configuration = aws_launch_configuration.mylauchconfig.name
vpc_zone_identifier = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
min_size = 2
max_size = 10
tag {
key = "Name"
value = "jinju-tf-study-asg"
propagate_at_launch = true
}
}
EOT
# 배포 및 확인
terraform init -upgrade
terraform plan && terraform apply -auto-approve
# ALB 코드 내용 추가
cat <<EOT >> main.tf
resource "aws_lb" "myalb" {
name = "jinju-tf-study-alb"
load_balancer_type = "application"
subnets = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
security_groups = [aws_security_group.mysg.id]
tags = {
Name = "jinju-tf-study-alb"
}
}
resource "aws_lb_listener" "myhttp" {
load_balancer_arn = aws_lb.myalb.arn
port = 8080
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
resource "aws_lb_target_group" "myalbtg" {
name = "jinju-tf-study-alb-tg"
port = 8080
protocol = "HTTP"
vpc_id = data.terraform_remote_state.db.outputs.vpcid
health_check {
path = "/"
protocol = "HTTP"
matcher = "200-299"
interval = 5
timeout = 3
healthy_threshold = 2
unhealthy_threshold = 2
}
}
resource "aws_lb_listener_rule" "myalbrule" {
listener_arn = aws_lb_listener.myhttp.arn
priority = 100
condition {
path_pattern {
values = ["*"]
}
}
action {
type = "forward"
target_group_arn = aws_lb_target_group.myalbtg.arn
}
}
output "myalb_dns" {
value = aws_lb.myalb.dns_name
description = "The DNS Address of the ALB"
}
EOT
# ASG 코드 내용 중간에 직접 아래 내용 2줄 추가
# 코드 파일 편집
vi main.tf
---------------------
resource "aws_autoscaling_group" "myasg" {
health_check_type = "ELB"
target_group_arns = [aws_lb_target_group.myalbtg.arn]
...
# 배포 및 접속 확인: curl 혹은 웹 브라우저에서 접속 확인
terraform plan && terraform apply -auto-approve
# ALB DNS주소로 curl 접속 확인
ALBDNS=$(terraform output -raw myalb_dns)
while true; do curl --connect-timeout 1 http://$ALBDNS:8080 ; echo; echo "------------------------------"; date; sleep 1; done
curl -s http://$ALBDNS:8080
# 각 폴더에서 리소스 삭제
$ cd ~/Study-Terraform/stage/services/webserver-cluster && terraform destroy -auto-approve
$ cd ~/Study-Terraform/stage/data-stores/mysql && terraform destroy -auto-approve
# S3 버킷에 객체 삭제
aws s3 rm s3://jinju-s3-tfstate-week3-files --recursive
# S3 버킷에 버저닝 객체 삭제
aws s3api delete-objects \
--bucket jinju-s3-tfstate-week3-files \
--delete "$(aws s3api list-object-versions \
--bucket "jinju-s3-tfstate-week3-files" \
--output=json \
--query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"
# S3 버킷에 삭제마커 삭제
aws s3api delete-objects --bucket jinju-s3-tfstate-week3-files \
--delete "$(aws s3api list-object-versions --bucket "jinju-s3-tfstate-week3-files" \
--query='{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')"
# 백엔드 리소스 삭제
$ cd ~/Study-Terraform/global/s3 && terraform destroy -auto-approve
# 관련 디렉터리 삭제
$ cd ~/Study-Terraform && rm -rf *