Information Security Study

240708 local, output, count, foreach, for 등, 상태 저장소(backend), 상태 관리(state) 본문

네트워크 캠퍼스/Terraform

240708 local, output, count, foreach, for 등, 상태 저장소(backend), 상태 관리(state)

gayeon_ 2024. 7. 8. 15:48

local, output, count, foreach, for 등

 

local 실습

 

 

새 파일 생성 후 가용 영역을 서울 리전으로 설정

 

 

 

서브넷의 가용 영역 값을 변수명으로 변경했다.

 

 

 

output 실습

output은 aws_vpc의 id값을 조회하는 등 이미 배정된 요소의 값을 구할 때 사용할 수 있다. 

 

 

하위 모듈에서 상위 모듈에 값을 보내준다.

 

${module.vpc_module.vpc_id}나 ${module.deploy_vpc_module.vpc_id}

로 값을 넣어줄 수 있다.

 

 

count 실습

보통 resource 블록 하나는 한 개의 인프라 오브젝트를 담당한다.

만약 100개의 ec2 인스턴스가 필요하다면 100개의 resource 블록이 필요할 것이다.

이런 경우 count argument로 간편하게 생성할 수 있다.

 

 

count로 resource 생성하기 예제

count 값에 따라 리소스가 반복적으로 생성된다.

 

resource "aws_instance" "example" {
  count         = 2  # 인스턴스를 2개 생성한다.
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  tags = {
    Name = "example-instance-${count.index}"
  }
}

 

위 예제는 count 설정으로 aws_instance 리소스를 두 개 생성한다.

Name의 ${count.index}는 0부터 시작하는 인덱스이다. 각 리소스마다 고유한 값을 가진다.

 

 

output에서 Array처럼 조회하기 예제

output으로 생성된 리소스의 속성을 배열 형태로 출력할 수 있다.

 

output "instance_ids" {
  description = "The IDs of the created instances"
  value       = aws_instance.example[*].id
}

 

위 예제에서 aws_instance.example[*].id는 생성된 모든 aws_instance 리소스의 ID를 배열로 반환한다.

[*]가 모든 인덱스를 의미한다.

 

count를 사용했을 때 개별 요소의 이름을 지정할 수 없고 인덱싱으로만 조회 가능하다.

이름을 지정하고 싶다면 for_each를 사용할 수 있다.

 

 

for_each 예제

 

provider "aws" {
  region = "us-west-2"
}

# Set of names to create IAM users
locals {
  user_names = toset(["terra", "form", "aws"])
}

resource "aws_iam_user" "example" {
  for_each = local.user_names
  name     = each.key
}

output "iam_user_names" {
  description = "The names of the created IAM users"
  value       = [for user in aws_iam_user.example : user.name]
}

 

locals의 toset 함수는 중복을 허용하지 않는 집합으로 변환한다.

for_each를 사용해 local.user_names의 각 요소에 대해 aws_iam_user 리소스를 생성한다.

aws_iam_user 리소스의 이름을 each.key로 설정한다.

output의 description은 출력 값에 대한 설명이다.

value는 생성된 각 aws_iam_user 리소스의 이름을 배열로 출력한다.

 

 

 

그리고 NAT, 게이트웨이, 개발과 배포용 모듈은 주석처리 했다.

* 모듈을 추가하거나 제거할 때 꼭 terraform init을 해야 한다.

count 실습용 모듈을 하나 추가했다.

 

 

 

기존에 있던 vpc, nat, 인터넷 게이트웨이가 삭제되었고 

employee vpc 두 개가 생성되었다.

 

 

 

default 리스트에 있는 이름으로 리소스의 이름을 관리하고 싶다면 위와 같이 작성하면 된다.

 

 

 

vpc name이 변경되었다.

 

 

리소스 내 조건문 사용

테라폼의 구성을 더 동적으로 만들기 위해 조건문을 사용한다.

조건문으로 특정 조건에 따라 다른 값을 설정할 수 있다.

 

테라폼의 조건문은 보통 삼항 연산자를 사용한다.

 

 

조건문 예시

variable "count" {
  default = 3
}

output "result" {
  value = var.count > 5 ? "많음" : "적음"
}

 

count 값이 5보다 클 경우 출력변수는 "많음"을 반환하고

작을 경우 "적음"을 반환한다.

count 값이 3이기 때문에 value는 "적음"을 반환하게 된다.

 

 

리소스 내에서 조건문을 사용해 특정 속성의 값을 동적으로 설정하는 예시

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = var.environment == "production" ? "Prod Instance" : "Dev Instance"
  }
}

 

var.environment가 "production"인 경우 인스턴스의 이름 태그가 "Prod Instance"가 되고

아닌 경우 "Dev Instance"가 된다.

 

 

맵과 리스트에서의 조건문 사용

 

예시

variable "environment" {
  type    = string
  default = "development"
}

locals {
  config = {
    "development" = "dev-config"
    "production"  = "prod-config"
  }
}

output "config_file" {
  value = local.config[var.environment == "production" ? "production" : "development"]
}

 

var.environment 값이 "production"이면 local.config에서 "pod-config"를 반환하고

아니면 "dev-config"를 반환한다.

 

 

실습

동일한 코드 내에서 조건문을 사용하는 실습을 했다.

 

dev 영역

public subnet, private subnet

 

deploy 영역

private subnet

 

서브넷이 위와 같이 생성되도록 조건문을 사용했다.

 

 

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "ap-northeast-2"
}

/* 개발 */
/*
module "vpc_module" {
  source = "./vpc_module"
  env = "dev"
}
*/

/* 배포 */
/*
module "deply_vpc_module" {
  source = "./vpc_module"
  env = "deploy"
}
*/

variable "teammate_name" {
	type = list(string)
	default = ["dev", "deploy", ""]
}

/* count 실습용 */
module "employee_vpc_module" {
  for_each = toset([for name in var.teammate_name : name if name != ""])
  source = "./vpc_module"
  env = each.key
}

 

루트 모듈의 main.tf를 수정했다.

teammate_name에 공백 문자를 추가했다.

기존에 있던 개발, 배포 모듈은 공백처리했다.

 

count 모듈에서 name이 ""가 아닌 경우만 생성하도록 작성했다.

 

 

그리고 vpc_module을 수정해서 deploy일 때는 public subnet이 생성되지 않도록 해야 한다.

 

 

vpc_module의 main.tf에서 public_subnet1의 count 값이 0이라면

오브젝트는 존재하지만 실제로 생성되지 않고

1 이상이면 생성된다.

 

 

 

위와 같이 count의 값을 가변적으로 처리할 수 있도록 삼항연산자를 사용했다.

deploy면 0, 아니면 1 이도록 작성했다.

 

 

resource "aws_nat_gateway" "private_nat" {
  connectivity_type = "public"
  subnet_id         = aws_subnet.public_subnet1[0].id
  
  tags = {
    Name = "swu_private_subnet1_${var.env}"
  }
}

 

NAT와 같이 연결 구조를 설정할 때는 인덱싱은 되지만 public_subnet1 자체가 생성이 안 되는 deploy 모듈에서 문제가 발생한다.

deploy 모듈에서는 public_subnet1이 없기 때문에 위와 같이 작성하면 에러가 난다.

 

 

resource "aws_nat_gateway" "private_nat" {
  count = var.env == "deploy" ? 0 : 1
  connectivity_type = "public"
  subnet_id         = aws_subnet.public_subnet1[0].id
  
  tags = {
    Name = "swu_private_subnet1_${var.env}"
  }
}

 

해결하기 위해서는 resource를 count로 조절하면 된다.

subnet에서도 count로 조절했듯이 resource도 조절하면 된다.

 

 

상태 저장소(backend), 상태 관리(state)

테라폼의 백엔드

: 상태 파일을 저장하고 관리하는 방식을 정의한다.

 

상태 파일

: 테라폼이 관리하는 인프라의 현재 상태를 저장한다.

 

 

백엔드 유형

1) 로컬 백엔드

2) 원격 백엔드

 

로컬 백엔드

  • 로컬 디스크에 상태 파일이 저장된다.
  • 로컬 디렉터리의 terraform.tfstate 파일에 상태를 저장한다.
terraform {
  backend "local" {
    path = "./terraform.tfstate"
  }
}

 

 

원격 백엔드

  • 원격 저장소에 상태 파일이 저장된다.
  • 상태 파일의 중앙 집중화와 협업이 가능하다.

 

AWS S3 버킷에 저장

terraform {
  backend "s3" {
    bucket = "my-bucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}

 

 

관리형 Terraform 서비스에 저장

terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "my-org"

    workspaces {
      name = "my-workspace"
    }
  }
}

 

 

 

Azure Storage 계정의 Blob 컨테이너에 저장

terraform {
  backend "azurerm" {
    storage_account_name = "mystorageaccount"
    container_name       = "mycontainer"
    key                  = "terraform.tfstate"
  }
}

 

 

Google Cloud Storgae 버킷에 저장

terraform {
  backend "gcs" {
    bucket = "my-gcs-bucket"
    prefix = "terraform/state"
  }
}

 

 

백엔드의 기능

1) 상태 파일 잠금

2) 원격 상태 조회

3) 보안과 관리

 

상태 파일 잠금

  • 여러 사용자가 동시에 상태 파일을 수정하는 것을 방지한다.
  • S3 백엔드는 DynamoDB를 사용해서 상태 파일 잠금을 구현한다.

 

원격 상태 조회

  • 원격 백엔드를 사용하면 Terraform 구성에서 상태 데이터를 참조할 수 있다.
  • 인프라 간 종속성을 관리할 수 있다.

 

보인과 관리

  • 원격 백엔드를 사용하면 상태 파일을 중앙에서 관리하고 접근 제어, 감사 로그, 버전 관리 등을 적용할 수 있다.

 

백엔드 설정하기

terraform {
  backend "s3" {
    bucket = "my-bucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}

 

terraform 블록 안에 backend 블록을 추가해서 설정할 수 있다.

백엔드 설정은 terraform init 명령어를 실행할 때 적용된다.

 

위 예시는 테라폼이 S3 버킷에 상태 파일을 저장하도록 초기화한다.

 

 

 

위 명령어로 현재 생성된  목록을 볼 수 있다.

terraform.tfstate 파일에 내역이 작성되어 있다.

 

 

추가 방법 공식 문서

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket

 

Terraform Registry

 

registry.terraform.io

 

 

S3 사용한 실습

resource "aws_s3_bucket" "tf_backend_s3" {
  bucket = "버킷"

  tags = {
    Name        = "tf_backend"
  }
}

resource "aws_s3_bucket_versioning" "tf_backend_versioning" {
  bucket = aws_s3_bucket.tf_backend_s3.id
  versioning_configuration {
    status = "Enabled"
  }
}

 

루트 모듈의 main.tf에 작성했다.

aws_s3_bucket_versioning 블록으로 버전 관리를 한다.

 

s3는 파일+트래픽양으로 과금하기 때문에 versioning을 사용하면

더 과금이 되겠지만 백업을 위해 추가했다.

 

 

resource "aws_s3_bucket_acl" "tf_backend_acl" {
  bucket = aws_s3_bucket.tf_backend_s3.id
  acl    = "private"
}

 

ACL 설정 코드도 같은 파일에 작성했다.

ACL은 여러 번 사용하면 에러가 뜨기 때문에 한 번 사용하고 주석처리를 해야 한다.

 

 

 

버킷이 생성되었다.

 

 

 

그리고 루트모듈의 main.tf의 terraform 내부에 backend 블록을 생성했다.

bucket명을 정확히 작성한다.

 

terraform 블록 내에서는 참조가 불가능하기 때문에 절댓값을 적어야 한다.

 

참조가 불가능한 이유

: terraform 블록이 먼저 실행되고 나머지 자원이나 프로바이더가 세팅되기 때문이다.

 

backend 블록을 작성했기 때문에 apply 전 init을 해야 한다.

 

 

 

버킷에 파일이 잘 생성되었다.

 

 

s3 오브젝트 스토리지 특성상 주소만 알면 외부에서 파일을 조회할 수 있기 때문에 

 

 

속성의 기본 암호화 편집으로 들어가 

버킷 키를 활성화로 변경한다.

 

이렇게 하면 이후에 업로드하는 파일은 전부 암호화가 되어 올라간다.

 

 

 

또한 권한 탭에서 퍼블릭 액세스 차단도 활성화해 주면 좋다.

 

 

상태 관리

협업을 하면 여러 pc에 하나의 계정을 타깃으로 하는 상태 파일을 나눠서 가지게 되는데

이때 충돌이 날 수 있다.

 

상태 관리는 dynamo db 등을 써서 동시성 문제를 해결하거나

루트모듈을 여러 개 만들어 관리한다.

 

충돌난 파일을 맞추는 것보다 state locking 등을 걸어서 위험하게 변경이 이뤄지지 않도록 한다.