ysuekkom의 IT study note

[Terraform 따라하기] Terraform Backend 관리, remote state file & output으로 참조하기 본문

DevOps/terraform

[Terraform 따라하기] Terraform Backend 관리, remote state file & output으로 참조하기

ysuekkom 2023. 4. 27. 22:09

Terraform Backend로 관리하기

terraform remote state file & output

variable 사용하기

 

 

Locking과 백엔드 관리

terraform init 이후 생성 된 state file을 로컬이 아닌 AWS S3에 저장하는 이유?

생성되는 terraform.tfstate는 apply의 결과를 저장해놓은 것으로 이해하면 된다. 즉, "현재"의 시점이 아닌, 내가 apply를 한 "적용 한" 시점의 상태를 나타내는 것이다. 

 

 본 포스팅에서처럼 아주 간단한 기초 실습을 할 때, 혼자서 테스트를 할 때는 상관없지만 실무에서는 여러명의 작업자와 함께 인프라를 운영하게 된다. 쉽게 말해 락킹이 발생할 수 있는 위험성이 있는데, 원격저장소를 사용하여 동시에 state에 접근하는 것을 막아 신뢰성이 깨지는 상황을 예방하는 것이다. 다시 말하면 여러명이 액세스하고 작업하기 위한(여러명이 작업한 '현재'인프라 형상을 공유) 장치라고 보면 좋겠다. 또, 여러명이 사용하다보니 인프라 코드 수정 시 어떤 부분이 변경되었는지를 추적할 수 있다. 

 

 terraform backend는 state file을 저장할 곳을 설정 및 정의한다. 즉, backend라는 지시자를 통해 원격저장소에 state 파일을 저장하는 것이다. 이렇게 저장된 remote state파일을 사용하기 위해서는 output 지시자를 이용한다.

 

실습-따라하기

terraform plan 시점의 정보를 캡쳐하여  원격저장소에 저장하고, 관리의 용이성과 비용, 그리고 버저닝 제공하는 S3에 저장해보도록 하겠다.

backend.tf 파일을 만들어서 아래와 같이 terraform backend 블록을 추가하여 백엔드를 구성한다.

 (백엔드 구성 제한사항: 하나의 백엔드 블록만 제공 가능, 백엔드 블록은 value를 참조할 수 없음)

또한 백엔드 구성을 변경할 때는 terraform init명령어를 수행하여야 한다.(credential을 포함 할 수 있으니 git등에 올리지 않도록 주의)

테라폼 백엔드 파일은 해당 해당 경로에 맞춰서 key값을 작성해준다. 이때 작성되는 key는 S3 내에서 저장되는 경로를 의미하므로 맞춰서 작성해주는게 관리에 용이하다. (백엔드는 폴더 기준으로 생성하여, 리소스나 특정 서비스 기준으로 나누어 관리한다)

암호화 설정은 true로, dynamoDB 테이블은 terraform-lock이라는 테이블 명으로 설정한다.

[ec2-user@ip-172-31-44-156 aws]$ cat backend.tf

terraform {
    backend "s3" {
      bucket         = "terra-test-ysuekkom-tfstate"
      key	     = "aws/terraform.tfstate"
      region         = "ap-northeast-2"
      encrypt        = true
      dynamodb_table = "terraform-lock"
    }
}

 

terraform init 명령어 수행 시,

로컬에 있는 terraform.tfstate file을 위 코드에서 작성한 backend로 copy할거냐는 메시지 창이 뜨고 yes 입력 시, 생성한 S3의 지정 경로에 저장되게 된다. 백엔드에서 state file을 가지고있기 때문에 로컬의 state file은 삭제되어도 구동되게 된다.

즉, 동적으로 저장소에서 state file을 관리할 수 있도록 설정한 것이다.

 

 

실행결과

S3 bucket명 terra-test-ysuekkom-tfstate, aws/terraform.tfstate 경로에 암호화하여 생성

 

S3 버전관리 true설정, DynamoDB에서 terraform lock 설정: S3 원격 저장소에서 관리하면서 동시 작업이 이루어지지 않도록 잠궈주는 테이블(동시에 접근 시, plan이나 apply시 작업이 되지 않는다)

provider "aws" {
  region = "ap-northeast-2"
#  version = "~>2.49.0"
}

# S3 bucket for backend
resource "aws_s3_bucket" "tfstate" {
  bucket = "terra-test-ysuekkom-tfstate"

  versioning {
    enabled = true # Prevent from deleting tfstate file
  }
}

# DynamoDB for terraform state lock
resource "aws_dynamodb_table" "terraform_state_lock" {
  name           = "terraform-lock"
  hash_key       = "LockID"
  billing_mode   = "PAY_PER_REQUEST"

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

 

 

 

작업 후 terraform init 명령어 수행

 

 

매니지먼트에서 확인 >aws/terraform.tfstate 경로에 생성된 것 확인

 

 

이제 로컬에서 tfstate 파일을 지우고 terraform을 실행해 보자! 

삭제 후,

 

terraform plan 수행 정상 동작! 확인!

 

로컬의 state파일을 삭제하고도 정상적으로 terraform plan이 수행되는 것을 확인 했다.

여러사람이 동시에 작업이 가능하게 하는 것, 그리고 버전관리를 제공하는 S3로 한 번 더 안전장치를 마련할 수 있다!

 


Terraform remote state file & output

 

output을 활용해보자. 

terraform output 지시자는 특정 값을 출력하고, state파일의 output 섹션에 저장한다. apply 시 출력되기 때문에 사용자 지정 상태를 확인하는데 사용하기도 한다. 옵션으로 description, sensitive, depends_on 이 있다.  

output은 실제 리소스에는 변화가 없으나, output에 직접 지정해 놓은 걸 terraform.tfstate file에 기록하는 것이다.

 

 

output 출력 값은 output 블록을 사용해서 선언한다. output은 terraform plan시에는 출력되지 않으며 apply 시 출력된다.

output "instance_ip_addr" { 
  value  = aws_instance.server.private_ip 
}

 

 

다음으로 output을 remote state를 사용하는데 써보자.

terraform에서는 remote state 기능을 제공하는데, 이때 output을 사용하게 된다.(output 사용의 이유)

remote state?

다른 디렉터리 혹은 원격지에 있는 state file에 있는 값을 참조하는 것으로, output 섹션에 저장된 값은 가져와서 사용할 수 있다.

여러팀이 운영하는 인프라 구조 상, 여러명의 작업자가 작업한 리소스를 참조해야 하는 경우가 많아 remote state 기능은 필수적이다. 

즉, 인프라 구성 요소 중 전역적인 요소를 output 섹션에 저장하여 참조할 수 있도록하면 추후 확장성에 유리하다. 

remote state file을 가져올 때는, data block "terraform_remote_state"와 outputs 지시자를 조합한다. 

 

간단한 도식화

 

 

실습 따라하기

아래와 같이 vpc에 대한 resource를 ouput 섹션에 작성하면(#ouput section declar 이하)

output 섹션에 value가 저장되는 것 까지 확인 할 수 있다.

# aws vpc declar

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terra-test"
  }
}

# output section declar

output "aws_vpc" {
  value = aws_vpc.main.id
}

# state file 가져와서 outputs 섹션 확인하기

[ec2-user@ip-172-31-44-156 aws]$ terraform state pull
{
  "version": 4,
  "terraform_version": "0.12.29",
  "serial": 4,
  "lineage": "a1f116d7-6f17-d7a5-7e38-1ca89bfc245b",
  "outputs": {
    "aws_vpc": {
      "value": "vpc-02f9f06af1e0f96b6",
      "type": "string"
    }
  },
  
  .
  .
  .

terrraform apply시 아래와 같이 출력되고, 정상적으로 ouput섹션에 저장됐음을 terraform state pull로 확인 가능하다.

 

 

이제 remote state를 통해 output에 저장된 vpc id를 가져와보자.

# terraform backend code block
terraform {
    backend "s3" {
      bucket         = "terra-test-ysuekkom-tfstate"
      key	     = "aws/terraform.tfstate"
      region         = "ap-northeast-2"
      encrypt        = true
      dynamodb_table = "terraform-lock"
    }
}

# remote_state_vpc.tf 
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terra-test"
  }
}


data "terraform_remote_state" "vpc" {
    backend = "s3"
    config = {
      bucket         = "terra-test-ysuekkom-tfstate"
      key            = "aws/terraform.tfstate"
      region         = "ap-northeast-2"
      encrypt        = true
      dynamodb_table = "terraform-lock"
    }
}

resource "aws_security_group" "terra-security_group" {
  name        = "security_group"
  description = "Allow TLS inbound traffic"
 # vpc_id      = aws_vpc.main.id
  vpc_id        = data.terraform_remote_state.vpc.outputs.aws_vpc

  ingress {
    description      = "TLS from VPC"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = [
        "10.0.0.0/8"
    ]
 #  ipv6_cidr_blocks = []
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = [
        "0.0.0.0/0"
    ]
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "terra-test-security_group"
  }
}

# output 
output "aws_vpc" {
  value = aws_vpc.main.id
}

 

여기서 주의해야 할 것은 security group의 vpc_id를 받아올 때, terraform_remote_state.vpc.이하 참조주의

>>remote state file을 참조하기 위해서는 output에서 참조할 것이기 때문에, 제공자인 output에서 선언한 그대로 가져와야 한다.

 

 

변수 참조를 잘못했을 때 에러메시지

 

변수 수정 후, terraform apply 결과 확인

 

 

terraform remote file & output 참조 결과 확인

매니지먼트 콘솔로 돌아가서, aws_vpc = vpc-02f9f06af1e0f96b6의 security group 설정이 정상적으로 반영됐는지 확인해보자.

아래코드와 위 결과를 비교하면 정상적으로 반영됐다는 것을 확인할 수 있다.

 

resource "aws_security_group" "terra-security_group" {
  name        = "security_group"
  description = "Allow TLS inbound traffic"
 # vpc_id      = aws_vpc.main.id
  vpc_id        = data.terraform_remote_state.vpc.outputs.aws_vpc

  ingress {
    description      = "TLS from VPC"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = [
        "10.0.0.0/8"
    ]
 #  ipv6_cidr_blocks = []
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = [
        "0.0.0.0/0"
    ]
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "terra-test-security_group"
  }
}

 

 

 


Terraform Variable 사용하기

 

기존 사용자그룹의 User명을 아래와 같이 지정된 하드코드로 받았다면, Variable을 이용하여 변수를 선언, 받아보자!

 

[기존 user를 하드코드로 받았을 때]

// **Create IAM User & IAM Group

resource "aws_iam_user" "ysuekkom_m" {
  name = "ysuekkom.m"
}
resource "aws_iam_group" "dev-ysuekkom" {
  name = "dev-ysuekkom"
}

// **Create IAM group membership

resource "aws_iam_group_membership" "dev-ysuekkom" {
  name = aws_iam_group.dev-ysuekkom.name

  users = ["ysuekkom.m"]
  //또는 users = [aws_iam_user.ysuekkom_m.name]
  
  group = aws_iam_group.dev-ysuekkom.name
}

 

 

variable.tf 파일 생성하여 변수 선언하기

# Variable Declaration

variable "aws_region" {
  description = "region for aws."
}

# Declare varibles to receive "IAM Group membership 'users'" in iam.tf file
variable "iam_user_list" {
  type = list(string)
}

 

terraform.tfvars 파일 생성하여 variable.tf파일에 선언된 변수 값 주입

#Inject variables declared in variable.tf
aws_region = "ap-northeast-2"

# iam_user_list
iam_user_list = ["ysuekkom.m","ysuekkom.f"]

 

 

users에 variable.tf에서 선언한 iam_user_list로 대체하기 위해 var.iam_user_list로 수정

// **Create IAM group membership

resource "aws_iam_group_membership" "dev-ysuekkom" {
  name = aws_iam_group.dev-ysuekkom.name

  users = var.iam_user_list
  group = aws_iam_group.dev-ysuekkom.name
}

 

 

사용자는 각 유저명.tf 파일로 생성하여 관리

// **Create IAM User 

resource "aws_iam_user" "ysuekkom_m" {
  name = "ysuekkom.m"
}

// **Create User Policy

resource "aws_iam_user_policy" "ysuekkom_devops" {
  name  = "super-admin"
  user  = aws_iam_user.ysuekkom_m.name

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
EOF
}

 

 

위 코드에서 새롭게 생성한 사용자 ysuekkom.f (user_ysuekkom_f.tf, variable.tf, terraform.tfvars 참고) 매니지먼트에서 확인

-Variable로 사용자 생성 및 사용자 그룹 추가 실습 확인

1. iam_user_list 변수 선언

2. IAM user 정보 작성

3. 선언된 변수에 값 주입(리스트로)

4. 사용자 그룹 dev-ysuekkom에 사용자 값을 리스트로 받아오도록 수정 

 

ysuekkom.m과 같은 조건(cp했기 때문)의 ysuekkom.f 사용자가 dev-ysuekkom 사용자그룹에 추가된 것 확인

 

 

이상으로 terraform backend관리, remote state file 참조, variable 사용등에 대한 기초적인 내용을 살펴봤다.

 

 

 

 

 

 

 

 

 

 

 

 

 

https://developer.hashicorp.com/terraform/language/settings/backends/configuration

https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/Versioning.html

https://terraform101.inflearn.devopsart.dev/advanced/backend/

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