9 분 소요

1. State

State의 목적과 의미

  • 상태 파일은 배포할 때마다 변경되는 프라이빗 API private API로, 오직 테라폼 내부에서 사용하기 위한 것
  • 테라폼 상태 파일직접 편집하거나 직접 읽는 코드로 작성해서는 안됩니다.

팀 단위에서 테라폼 운영 시 문제점

  1. 상태 파일을 저장하는 공유 스토리지 Shared storage for state files
    • 각 팀원이 동일한 테라폼 상태 파일 사용을 위해서, 공유 위치에 저장이 필요
  2. 상태 파일 잠금 Locking state files
    • 잠금 기능 없이 두 팀원이 동시에 테라폼 실행 시 여러 테라폼 프로세스가 상태 파일을 동시에 업데이트하여 충돌 가능(경쟁 상태 race condition)
  3. 상태 파일 격리 Isolating state files
    • 예를 들면 테스트 dev 와 검증 stage 과 상용 prodction 각 환경에 대한 격리가 필요

상태 파일 공유로 버전 관리 시스템 비추천

  1. 수동 오류 Manual error
    • 테라폼을 실행하기 전에 최신 변경 사항을 가져오거나 실행하고 나서 push 하는 것을 잊기 쉽습니다(?).
    • 팀의 누군가가 이전 버전의 상태 파일로 테라폼을 실행하고, 그 결과 실수로 이전 버전으로 롤백하거나 이전에 배포된 인프라를 복제하는 문제가 발생 할 수 있음.
  2. 잠금 Locking
    • 대부분의 버전 관리 시스템(VCS)은 여러 명의 팀 구성원이 동시에 하나의 상태 파일에 terraform apply 명령을 실행하지 못하게 하는 잠금 기능이 제공되지 않음.
  3. 시크릿 Secrets
    • 테라폼 상태 파일의 모든 데이터는 평문으로 저장됨. 민감 정보가 노출될 위험.

지원되는 원격 백엔드 : AWS S3, Azure Blob Storage, Google Cloud Storage, Consul, Postgres database 등 - 링크

  1. 수동 오류 해결 : plan/apply 실행 시 마다 해당 백엔드에서 파일을 자동을 로드, apply 후 상태 파일을 백엔드에 자동 저장
  2. 잠금(Lock) : apply 실행 시 테라폼은 자동으로 잠금을 활성화, -lock-timout=
  3. 시크릿 : 대부분 원격 백엔드는 기본적으로 데이터를 보내거나 상태 파일을 저장할 때 암호화(Encryption)하는 기능을 지원
  • 소개 및 확인 : 테라폼은 Stateful 애플리케이션. 프로비저닝 결과 State를 저장하고 추적에 활용
    • 개인 1인 : 로컬 환경으로 terraform.tfstate 파일에 JSON 형태로 저장
    • 팀이나 조직 : 공동 관리를 위해 원격 저장소에 저장해 공유 - 링크
    • State에는 작업자가 정의한 코드와 실제 반영된 프로비저닝 결과를 저장하고, 이 정보를 토대로 이후의 리소스 생성, 수정, 삭제에 대한 동작 판단 작업을 수행

    State 역할

  • State에는 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디(리소스 주소)로 맵핑
  • 리소스 종속성과 같은 메타데이터를 저장하고 추적
  • 테라폼 구성으로 프로비저닝 결과를 캐싱하는 역할을 수행

    • 테라폼에서는 JSON 형태로 작성된 State를 통해 속성인수를 읽고 확인할 수 있다. 테라폼에서는 typename으로 고유한 리소스분류하며, 해당 리소스의 속성인수를 구성과 비교해 대상 리소스생성, 수정, 삭제한다.
  • State는 테라폼만을 위한 API로 정의할 수도 있다. Plan을 실행하면 암묵적으로 refresh 동작을 수행하면서 리소스 생성의 대상(클라우드 등)과 State를 기준으로 비교하는 과정을 거친다. 이 작업은 프로비저닝 대상의 응답 속도와 기존 작성된 State의 리소스 양에 따라 속도 차이가 발생한다. 대량의 리소스를 관리해야 하는 경우 Plan 명령에서 -refresh=false 플래그를 사용해 State를 기준으로 실행 계획을 생성하고, 이를 실행에 활용해 대상 환경과의 동기화 과정을 생략할 수 있다.
resource "random_password" "mypw" {
  length           = 16
  special          = true
  override_special = "!#$%"
}
terraform init && terraform plan
terraform apply -auto-approve

# State List 및 생성된 Password 확인
terraform state list
terraform state show random_password.mypw
---
  random_password.mypw
# random_password.mypw:
resource "random_password" "mypw" {
    bcrypt_hash      = (sensitive value)
    id               = "none"
    length           = 16
    lower            = true
    min_lower        = 0
    min_numeric      = 0
    min_special      = 0
    min_upper        = 0
    number           = true
    numeric          = true
    override_special = "!#$%"
    result           = (sensitive value)
    special          = true
    upper            = true
}
---
  • State에 저장된 result를 확인해보자! -> 어떻게 관리해야할까요?
    ls *.tfstate
    cat terraform.tfstate | jq
    cat terraform.tfstate | jq | grep result
    
    "result": "ednRVV8r%!dd$7y3",
    "check_results": null
    

    (참고) sensitive value 내용은 테라폼 콘솔에서 보일까요? (정답: x)

echo "random_password.mypw" | terraform console
echo "random_password.mypw.result" | terraform console
  • 테라폼에서는 JSON 형태로 작성된 State를 통해 속성인수를 읽고 확인할 수 있다. 테라폼에서는 typename으로 고유한 리소스분류하며, 해당 리소스의 속성인수를 구성과 비교해 대상 리소스생성, 수정, 삭제한다.
  • State는 테라폼만을 위한 API로 정의할 수도 있다. Plan을 실행하면 암묵적으로 refresh 동작을 수행하면서 리소스 생성의 대상(클라우드 등)과 State를 기준으로 비교하는 과정을 거친다. 이 작업은 프로비저닝 대상의 응답 속도와 기존 작성된 State의 리소스 양에 따라 속도 차이가 발생한다. 대량의 리소스를 관리해야 하는 경우 Plan 명령에서 -refresh=false 플래그를 사용해 State를 기준으로 실행 계획을 생성하고, 이를 실행에 활용해 대상 환경과의 동기화 과정을 생략할 수 있다.

## 2. State 동기화

3 워크스페이스

  1. 작업 공간을 통한 격리 Isolation via workspaces - 동일한 구성에서 빠르고 격리된 테스트 환경에 유용 - 논리적 격리 가능
  2. 파일 레이아웃을 이용한 격리 Isolation via file layout
    • 보다 강력하게 분리해야 하는 운영 환경에 적합
    • 테라폼 프로젝트의 파일 레이아웃 설명
      • 각 테라폼 구성 파일을 분리된 폴더에 넣기. (예. stage , prod)
      • 각 환경에 서로 다른 백엔드 구성. (예. S3 버킷 백엔드의 AWS 계정을 분리)

최상위 폴더

  • stage : 테스트 환경과 같은 사전 프로덕션 워크로드 workload 환경
  • prod : 사용자용 맵 같은 프로덕션 워크로드 환경
  • mgmt : 베스천 호스트 Bastion Host, 젠킨스 Jenkins 와 같은 데브옵스 도구 환경
  • global : S3, IAM과 같이 모든 환경에서 사용되는 리소스를 배치

각 환경별 구성 요소

  • vpc : 해당 환경을 위한 네트워크 토폴로지
  • services : 해당 환경에서 서비스되는 애플리케이션, 각 앱은 자체 폴더에 위치하여 다른 앱과 분리
  • data-storage : 해당 환경 별 데이터 저장소. 각 데이터 저장소 역시 자체 폴더에 위치하여 다른 데이터 저장소와 분리

    Module

  • 테라폼으로 인프라와 서비스를 관리하면 시간이 지날수록 구성이 복잡해지고 관리하는 리소스가 늘어나게 된다. 테라폼의 구성 파일과 디렉터리 구성에는 제약이 없기 때문에 단일 파일 구조상에서 지속적으로 업데이트할 수 있지만, 다음과 같은 문제가 발생한다.
    • 테라폼 구성에서 원하는 항목을 찾고 수정하는 것이 점점 어려워짐
    • 리소스들 간의 연관 관계가 복잡해 질수록 변경 작업의 영향도를 분석하기 위한 노력이 늘어남
    • 개발/스테이징/프로덕션 환경으로 구분된 경우 비슷한 형태의 구성이 반복되어 업무 효율이 줄어듦
    • 새로운 프로젝트를 구성하는 경우 기존 구성에서 취해야 할 리소스 구성과 종속성 파악이 어려움
  • 모듈은 루트 모듈과 자식 모듈로 구분된다
    • 루트 모듈 Root Module : 테라폼을 실행하고 프로비저닝하는 최상위 모듈
    • 자식 모듈 Child Module : 루트 모듈의 구성에서 호출되는 외부 구성 집합
  • 모듈은 테라폼 구성의 집합이다. 테라폼으로 관리하는 대상의 규모가 커지고 복잡해져 생긴 문제를 보완하고 관리 작업을 수월하게 하기 위한 방안으로 활용
    • 관리성 : 모듈은 서로 연관 있은 구성의 묶음이다. 원하는 구성요소를 단위별로 쉽게 찾고 업데이트할 수 있다. 모듈은 다른 구성에서 쉽게 하나의 덩어리로 추가하거나 삭제할 수 있다. 또한 모듈이 업데이트되면 이 모듈을 사용하는 모든 구성에서 일관된 변경 작업을 진행할 수 있다.
    • 캡슐화 : 테라폼 구성 내에서 각 모듈은 논리적으로 묶여져 독립적으로 프로비저닝 및 관리되며, 그 결과는 은닉성을 갖춰 필요한 항목만을 외부에 노출시킨다.
    • 재사용성 : 구성을 처음부터 작성하는 것에는 시간과 노력이 필요하고 작성 중간에 디버깅과 오류를 수정하는 반복 작업이 발생한다. 테라폼 구성을 모듈화하면 이후에 비슷한 프로비저닝에 이미 검증된 구성을 바로 사용할 수 있다.
    • 일관성표준화 : 테라폼 구성 시 모듈을 활용하는 워크플로는 구성의 일관성을 제공하고 서로 다른 환경과 프로젝트에도 이미 검증한 모듈을 적용해 복잡한 구성과 보안 사고를 방지할 수 있다.

1 모듈 작성 기본 원칙

  • 기본 원칙 : 모듈은 대부분의 프로그래밍 언어에서 쓰이는 라이브러리나 패키지와 역할이 비슷하다

    아래와 같은 기본 작성 원칙을 제안함

    • 모듈 디렉터리 형식을 terraform-<프로바이더 이름>-<모듈 이름> 형식을 제안한다. 이 형식은 Terraform Cloud, Terraform Enterprise에서도 사용되는 방식으로 1) 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고, 2) 어떤 프로바이더의 리소스를 포함하고 있으며, 3) 부여된 이름이 무엇인지 판별할 수 있도록 한다.
    • 테라폼 구성은 궁극적으로 모듈화가 가능한 구조로 작성할 것을 제안한다. 처음부터 모듈화를 가정하고 구성파일을 작성하면 단일 루트 모듈이라도 후에 다른 모듈이 호출할 것을 예상하고 구조화할 수 있다. 또한 작성자는 의도한 리소스 묶음을 구상한 대로 논리적인 구조로 그룹화할 수 있다.
    • 각각의 모듈을 독립적으로 관리하기를 제안한다. 리모트 모듈을 사용하지 않더라도 처음부터 모듈화가 진행된 구성들은 때로 루트 모듈의 하위 파일 시스템에 존재하는 경우가 있다. 하위 모듈 또한 독립적인 모듈이므로 루트 모듈 하위에 두기보다는 동일한 파일 시스템 레벨에 위치하거나 별도 모듈만을 위한 공간에서 불러오는 것을 권장한다. 이렇게 하면 VCS를 통해 관리하기가 더 수월하다.
    • 공개된 테라폼 레지스트리의 모듈을 참고 하기를 제안한다. 대다수의 테라폼 모듈은 공개된 모듈이 존재하고 거의 모든 인수에 대한 변수 처리, 반복문 적용 리소스, 조건에 따른 리소스 활성/비활성 등을 모범 사례로 공개해두었다. 물론 그대로 가져다 사용하는 것보다는 프로비저닝하려는 상황에 맞게 참고하는 것을 권장한다.
    • 작성된 모듈은 공개 또는 비공개로 게시해 팀 또는 커뮤니티와 공유하기를 제안한다. 모듈의 사용성을 높이고 피드백을 통해 더 발전된 모듈을 구성할 수 있는 자극이 된다.

    • 모듈을 독립적으로 관리하기 위해 디렉터리 구조를 생성할 때 모듈을 위한 별도 공간을 생성하는 방식으로 진행한다. 특정 루트 모듈 하위에 자식 모듈을 구성하는 경우 단순히 복잡한 코드를 분리하는 용도로 명시되며 종속성이 발생하므로 루트 모듈 사이에 모듈 디렉터리를 지정한다

      2 모듈화 해보기

자식 모듈 작성

# main.tf
resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

# DB일 경우 Password 생성 규칙을 다르게 반영 
resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}
  
variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}
  
output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}
  • 자식 모듈 호출 실습
  • 다수의 리소스를 같은 목적으로 여러 번 반복해서 사용하려면 리소스 수만큼 반복해 구성 파일을 정의해야 하고 이름도 고유하게 설정해줘야 하는 부담이 있지만, 모듈을 활용하면 반복되는 리소스 묶음을 최소화할 수 있다.
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 Cloud, Terraform Enterprise에서 제공되는 비공개(Private) 테라폼 모듈을 사용할 때 설정하는 소스 지정 방식이다.
  • 공개된 모듈 - 링크 aws_vpc
  • 공개된 테라폼 모듈을 source에 선언할 때는 <네임스페이스>/<이름>/<프로바이더> 형태로 설정한다.

[도전과제4]

테라폼 레지스트리공개된 모듈을 사용하여 리소스를 배포해보세요!

# main.tf

provider "aws" {
  region = "ap-northeast-2"  # 원하는 AWS 리전으로 변경
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"
}

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs = ["ap-northeast-2a", "ap-northeast-2c"]
}

[도전과제1]

T101 1기 노션 내용에 AWS DynamoDB/S3를 원격 저장소로 사용하는 실습을 따라해보세요!

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_s3_bucket" "mys3bucket" {
  bucket = "yrk-t101study-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"
}

terraform {
  backend "s3" {
    bucket = "yrk-t101study-tfstate"
    key    = "dev/terraform.tfstate"
    region = "ap-northeast-2"
    dynamodb_table = "terraform-locks"
    # encrypt        = true
  }
}

resource "aws_dynamodb_table" "mydynamodbtable" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}



output "dynamodb_table_name" {
  value       = aws_dynamodb_table.mydynamodbtable.name
  description = "The name of the DynamoDB table"
}

[도전과제3]

각자 사용하기 편리한 리소스를 모듈화 해보고, 해당 모듈을 활용해서 반복 리소스들 배포해보세요!

mkdir -p module-traning/multi_provider_for_module/
# variable.tf
variable "instance_type" {
  description = "vm 인스턴스 타입 정의"
  default     = "t2.micro"
}

variable "instance_name" {
  description = "vm 인스턴스 이름 정의"
  default     = "my_ec2"
}
# main.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

resource "aws_default_vpc" "default" {}

data "aws_ami" "default" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

resource "aws_instance" "default" {
  depends_on    = [aws_default_vpc.default]
  ami           = data.aws_ami.default.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}


반복문 사용

provider "aws" {
  region = "ap-northeast-2"  
}

module "ec2_seoul" {
  count  = 2
  source = "../modules/terraform-aws-ec2"
  instance_type = "t3.small"
}

output "module_output" {
  value  = module.ec2_seoul[*].private_ip   
}

댓글남기기