State file
상태 파일은 terraform apply시에 생성되는 .tfstate 파일이다.
해당 파일은 인프라의 상태를 json 형태로 가지고 있는데, 테라폼 내부에서만 사용하기 위한 파일이기 때문에 테라폼 상태 파일을 직접 편집하거나 직접 읽는 코드로 작성해서는 안된다.
팀 단위로 작업하는 경우 아래 조건이 갖춰져야 한다.
- 상태 파일을 저장하는 공유 스토리지 (Shared storage for state files)
- 각 팀원이 동일한 테라폼 상태 파일 사용을 위해서, 공유 위치에 저장이 필요
- 상태 파일 잠금 (Locking state files)
- 잠금 기능 없이 동시에 테라폼 실행 시 여러 테라폼 프로세스가 상태 파일을 동시에 업데이트하여 경쟁 상태(race condition)로 인한 충돌 가능
- 상태 파일 격리 (Isolating state files)
- 테스트(dev), 검증(stage), 상용(prodction) 등 각 환경에 대한 상태 파일의 격리가 필요
AWS S3, Azure Blob Storage, Google Cloud Storage 등이 원격 백엔드를 지원하기 때문에 많이 쓰인다.
이런 백엔드를 사용할 경우 일반적으로 발생하는 아래 문제들을 해결할 수 있다.
- 수동 오류: plan/apply 실행 시 마다 해당 백엔드에서 파일을 자동을 로드, apply 후 상태 파일을 백엔드에 자동 저장
- 잠금: apply 실행 시 테라폼은 자동으로 잠금을 활성화, -lock-timout=<TIME> 로 대기 시간 설정 지정 가능
- 보안: 대부분 원격 백엔드는 기본적으로 데이터를 보내거나 상태 파일을 저장할 때 암호화 기능을 지원
아래 코드를 사용하면 DynamoDB로 잠금 상태를 제어하는 AWS S3 Backend를 만들수 있다.
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "mys3bucket" {
bucket = "hwan001-t101study-tfstate"
}
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"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
만들어진 백엔드와 DynamoDB 잠금은 아래처럼 사용할 수 있다.
provider "aws" {
region = "ap-northeast-2"
}
terraform {
backend "s3" {
bucket = "hwan001-t101study-tfstate"
key = "dev/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
# encrypt = true
}
}
Module
모듈은 대부분의 프로그래밍 언어에서 쓰이는 라이브러리나 패키지와 역할이 비슷하다.
모듈은 기본적으로 아래와 같은 구조를 가지며 이렇게 모듈화를 할 경우 재활용성이 높아진다.
Root-Module과 Child-Module로 나누어지고, root 모듈 내부에서 child 모듈을 불러서 사용할 수 있다.
모듈을 사용하는 방법은 로컬 디렉토리, 테라폼 레지스트리와 github에 있는 모듈 사용 등이 있다.
아래는 깃허브를 사용한 방식으로 ec2 인스턴스를 띄워봤다.
먼저 github에 public 리포지토리를 생성한다. 생성 시 .gitignore에서 Terraform을 선택하면 .terraform 등의 과정에서 생성된 파일들이 github push 시에 올라가지 않는다.
VPC Module
data "aws_availability_zones" "available" {
state = "available"
}
# variables.tf
variable "cidr_block" {
description = "The CIDR block for the VPC"
type = string
}
variable "tags" {
description = "Tags for the VPC"
type = map(string)
default = {}
}
variable "primary_subnet_cidr" {
description = "CIDR block for the primary subnet"
type = string
}
variable "primary_subnet_tags" {
description = "Tags for the primary subnet"
type = map(string)
default = {}
}
variable "secondary_subnet_cidr" {
description = "CIDR block for the secondary subnet"
type = string
}
variable "secondary_subnet_tags" {
description = "Tags for the secondary subnet"
type = map(string)
default = {}
}
# main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
tags = var.tags
}
resource "aws_subnet" "primary" {
vpc_id = aws_vpc.this.id
cidr_block = var.primary_subnet_cidr
availability_zone = data.aws_availability_zones.available.names[0]
tags = var.primary_subnet_tags
}
resource "aws_subnet" "secondary" {
vpc_id = aws_vpc.this.id
cidr_block = var.secondary_subnet_cidr
availability_zone = data.aws_availability_zones.available.names[1]
tags = var.secondary_subnet_tags
}
# output.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.this.id
}
output "primary_subnet_id" {
description = "The ID of the primary subnet"
value = aws_subnet.primary.id
}
output "secondary_subnet_id" {
description = "The ID of the secondary subnet"
value = aws_subnet.secondary.id
}
Security Group Module
# variables.tf
variable "description" {
description = "Description of the security group"
type = string
}
variable "vpc_id" {
description = "VPC ID to associate the security group with"
type = string
}
variable "ingress_rules" {
description = "List of ingress rules"
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))
}
variable "name" {
description = "Name of the security group"
type = string
}
# main.tf
resource "aws_security_group" "this" {
description = var.description
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = var.name
}
}
# output.tf
output "security_group_id" {
description = "The ID of the security group"
value = aws_security_group.this.id
}
EC2 Module
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
}
variable "subnet_id" {
description = "Subnet id to launch the instance in"
type = string
}
variable "security_group_ids" {
description = "List of security group ids to associate with the instance"
type = list(string)
}
variable "instance_name" {
description = "Name to be used on all resources as prefix"
type = string
}
variable "associate_public_ip_address" {
description = "Associate a public ip address with the instance"
type = bool
default = false
}
# main.tf
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "this" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = var.security_group_ids
associate_public_ip_address = var.associate_public_ip_address
tags = {
Name = var.instance_name
}
}
# output.tf
output "instance_id" {
description = "The ID of the instance"
value = aws_instance.this.id
}
이제 만들어진 모듈을 사용해 리소스를 생성해보자.
ec2 모듈에 count = 3을 넣어주어 3개의 인스턴스를 생성했다.
provider "aws" {
region = "ap-northeast-2"
}
terraform {
backend "s3" {
bucket = "hwan001-t101study-tfstate"
key = "dev/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
# encrypt = true
}
}
module "vpc" {
source = "git::https://github.com/aws-hwan-001/vpc.git"
cidr_block = "10.10.0.0/16"
tags = { Name = "hwan001-vpc" }
primary_subnet_cidr = "10.10.1.0/24"
primary_subnet_tags = { Name = "hwan001-subnet-1" }
secondary_subnet_cidr = "10.10.2.0/24"
secondary_subnet_tags = { Name = "hwan001-subnet-2" }
}
module "security_group" {
source = "git::https://github.com/aws-hwan-001/sg.git"
description = "hwan001 Security Group"
vpc_id = module.vpc.vpc_id
name = "hwan001-sg"
ingress_rules = [
{
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
}
module "ec2_instance" {
source = "git::https://github.com/aws-hwan-001/ec2.git"
count = 3
instance_type = "t2.micro"
subnet_id = module.vpc.primary_subnet_id
security_group_ids = [module.security_group.security_group_id]
instance_name = "hwan001-ec2"
associate_public_ip_address = true
}
상태파일은 위에서 만든 백엔드를 사용하고 있는 걸 볼 수 있다.
'DevOps > IaC (Infrastructure as Code)' 카테고리의 다른 글
Terraform Backend 생성하기 (S3 버킷) (0) | 2023.10.22 |
---|---|
[T1012] Week 6. 테라폼으로 협업하기 (0) | 2023.08.13 |
[T1012] Week 3. 테라폼 기본 사용법 정리 (3/3) (0) | 2023.07.22 |
[T1012] Week 2. 테라폼 기본 사용법 정리 (2/3) (0) | 2023.07.15 |
[T1012] Week 1. 테라폼 기본 사용법 정리 (1/3) (0) | 2023.07.08 |
Infrastructure as Code : 코드를 통한 인프라 구축 (0) | 2022.12.30 |
댓글