Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Terraform의 tfstate를 원격으로 관리하기

Terraform으로 인프라를 관리할 때 설정 파일을 만들고 이를 적용(apply)하면 terraform.tfstate라는 파일이 생긴다. 이는 Terraform의 설정 파일로 실제 인프라에 적용하면서 나온 결과를 기억하고 있는 상태 파일로 이후 설정 파일을 수정하거나 하면 "설정 파일", "상태 파일", "실제 인프라의 상태"를 비교해서 어떻게 적용할지를 판단하게 된다.

간단히 다음과 같이 EC2에 인스턴스를 하나 띄운다고 해보자.

// ec2.tf
resource "aws_instance" "test" {
  // ubuntu 16.04
  ami = "ami-afb09dc8"
  availability_zone = "ap-northeast-1a"
  instance_type = "t2.nano"
  key_name = "my-key-pair"
  subnet_id = "subnet-00000000"
  associate_public_ip_address = true

  tags {
    Name = "test"
  }
}

이를 적용하면 AWS에 EC2 인스턴스가 새로 생성된다.

$ terraform plan

$ terraform apply

terraform apply를 실행하면 현재 폴더에 terraform.tfstate 파일이 생긴 것을 볼 수 있다. 이 파일에는 사용한 Terraform 버전과 상태의 버전, 그리고 적용한 EC2 인스턴스의 ID, IP 등 다양한 정보가 포함되어 있다. 참고로 수정 후 terraform apply를 또 하면 나중에 복구할 수 있도록 terraform.tfstate.backup파일이 하나 생기게 된다. 이는 최소 이전 버전의 상태 파일을 알 수 있게 하는 용도이다.

terraform.tfstate 관리의 문제점

처음 이 파일을 봤을 때는 이걸 어떻게 관리하는 지가 궁금했다. 물론 처음 봤을 때는 뭐 하는 파일인지도 잘 몰랐지만 terraform.tfstate 파일이 없다면 항상 Terraform은 인프라를 새로 만들려고 할 것이므로 이 파일을 관리하고 있어야 한다.

그래서 Git에 추가해서 관리했다. Terraform 설정 파일을 모두 Git으로 관리하고 있으므로 그 적용 결과인 terraform.tfstate 파일도 Git에 넣어서 함께 관리하는 것이 자연스러워 보였다. 혼자 연습하고 할 때는 문제가 없었지만 금세 이 관리 방식에 문제가 많은 것을 알게 되었다.

  • 동시에 두 사람이 작업한다면 terraform.tfstate가 달라지게 되어 충돌이 발생할 수 있다. 이는 파일의 충돌뿐이 아니라 인프라에도 영향을 줄 수 있다.
  • Git으로 작업하면서 pull로 가져오지 않고 작업한다면 실수로 이전 terraform.tfstate위에서 작업할 수 있다.

그래서 Terraform에서는 이 파일을 원격으로 관리할 방법을 제공한다. 문서에 따르면 tfstate에는 실제 인프라를 적용한 결과가 있으므로 상황에 따라서는 tfstate에 민감한 정보가 포함될 수 있으므로 공개된 장소에서 tfstate를 관리하지 않도록 권장하고 있다.

AWS S3에 tfstate 관리하기

현재 원격 백엔드로는 Azure, Consul, etcd, AWS S3, Terraform Enterprise, Google Cloud Storage를 지원하고 있는데 여기서는 S3를 사용해 보도록 하자.

S3에 tfstate 파일을 저장하려면 tfstate를 저장할 버킷을 만들어야 한다. 이미 존재하는 버킷에 저장해도 큰 문제는 없지만 여기서는 tfstate를 저장할 버킷을 terrafom으로 생성해서 사용해 보자.

// terrafrom state 파일용 lock 테이블
resource "aws_dynamodb_table" "terraform_state_lock" {
  name = "TerraformStateLock"
  read_capacity = 5
  write_capacity = 5
  hash_key = "LockID"

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

먼저 DynamoDB에 테이블을 만든다. 이 테이블은 tfstate를 S3에 관리하면서 동시에 작업이 일어나지 않도록 하는 Lock 테이블이다. Lock을 사용할지 안 할지도 선택사항이기는 하지만 원격으로 상태 파일을 관리하므로 동시에 작업하면서 인프라에 문제가 생기지 않도록 Lock 테이블을 만들면 plan이나 apply를 할 때 먼저 잠그고 작업이 끝나면 잠금을 해제하게 된다.

// 로그 저장용 버킷
resource "aws_s3_bucket" "logs" {
  bucket = "kr.ne.outsider.logs"
  acl    = "log-delivery-write"
}

이는 로그 데이터를 저장할 S3 버킷을 생성하는 부분이다. 이는 이어서 terraform.tfstate용 S3 버킷에서 로깅을 켜서 누가 접근해서 작업했는지 알 수 있도록 여기에 기록을 남긴다. 로그는 다음과 같이 기록되게 된다.

4cc7edaa434aedfd5ce8d7c2403117183f8f6f88a99bd5989542b9c393592acf kr.ne.outsider.terraform.state [21/May/2017:06:12:05 +0000] 211.219.19.33 arn:aws:iam::410655858509:user/outsider F753C234341BC308 REST.GET.OBJECT test/terraform.tfstate "GET /kr.ne.outsider.terraform.state/test/terraform.tfstate HTTP/1.1" 200 - 56417 56417 39 38 "-" "aws-sdk-go/1.8.16 (go1.8; darwin; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.9.5" -

아래는 실제 terraform.tfstate가 저장되는 S3 버킷을 생성하는 부분이다.

// Terraform state 저장용 S3 버킷
resource "aws_s3_bucket" "terraform-state" {
  bucket = "kr.ne.outsider.terraform.state"
  acl    = "private"
  versioning {
    enabled = true
  }
  tags {
    Name = "terraform state"
  }
  logging {
    target_bucket = "${aws_s3_bucket.logs.id}"
    target_prefix = "log/"
  }
  lifecycle {
    prevent_destroy = true
  }
}

버킷을 만들면서 버전 관리를 활성화했다. S3의 버전 관리를 키면 terraform.tfstate 파일을 변경할 때마다 S3가 알아서 예전 버전을 관리해주므로 문제가 생겼을 때 복구할 수 있다. 그리고 로깅을 활성화하고 앞에서 만든 로깅용 S3 버킷을 지정했다. 이제 이 버킷의 파일을 수정할 때마다 로깅 버킷에 데이터가 남게 된다.

이를 적용하면 필요한 S3 버킷과 DynamoDB에 테이블이 생성된다. 이제 이 버킷을 사용하도록 Terraform 설정 파일을 추가하자.

terraform {
  required_version = ">= 0.9.5"
  backend "s3" {
    bucket = "kr.ne.outsider.terraform.state"
    key = "test/terraform.tfstate"
    region = "ap-northeast-1"
    encrypt = true
    lock_table = "TerraformStateLock"
    acl = "bucket-owner-full-control"
  }
}

설정(terraform)이 terraform.tfstate을 원격으로 관리하는 설정이다. 여기서 S3에 저장한다고 지정하고 위에서 만든 S3 버킷을 지정했다. 한 버킷에서 여러 terraform.tfstate를 관리하기 위해서 키를 지정해서 계층을 주었다. encrypt를 설정하면 S3의 암호화 기능으로 암호화해서 저장하므로 혹시나 유출됐을 때 문제를 막을 수 있다. lock_table로 위에서 만든 DynamoDB의 Lock 테이블을 지정했다. 더 자세한 설정은 S3 백엔드 문서를 참고하면 된다.

Terraform 설정 중에 이 부분, 더 정확히는 terraform 키워드를 이용한 백엔드 설정이 있으면 Terraform은 terraform.tfstate를 로컬에서 관리하지 않고 원격에서 관리한다고 생각한다. 그래서 terraform plan을 실행하면 다음과 같이 오류가 난다.

$ terraform plan
Backend reinitialization required. Please run "terraform init".
Reason: Initial configuration of the requested backend "s3"

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

Changes to backend configurations require reinitialization. This allows
Terraform to setup the new configuration, copy existing state, etc. This is
only done during "terraform init". Please run that command now then try again.

If the change reason above is incorrect, please verify your configuration
hasn't changed and try again. At this point, no changes to your existing
configuration or state have been made.

Failed to load backend: Initialization required. Please see the error message above.

이는 Terraform 설정을 초기화해야 한다는 메시지이다. 초기화는 init 명령어를 사용하는데 이는 최초 한 번만 실행하면 된다. 로컬에서 terraform.tfstate를 관리하고 있었다면 이를 백엔드로 올리면서 초기화를 하고 이미 원격에서 관리하는 Terraform 설정을 다운 받았다면 이 설정을 초기화하는 과정이 이루어진다.

$ terraform init
Initializing the backend...
Do you want to copy state from "local" to "s3"?
  Pre-existing state was found in "local" while migrating to "s3". No existing
  state was found in "s3". Do you want to copy the state from "local" to
  "s3"? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

Releasing state lock. This may take a few moments...


Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your environment. If you forget, other
commands will detect it and remind you to do so if necessary.

초기화를 실행하면 위 메시지처럼 로컬에는 terraform.tfstate가 있고 S3에는 terraform.tfstate가 없다고 이를 복사할지를 물어보게 된다. 여기서는 최초 실행하는 단계이므로 yes를 입력하면 S3에 업로드하고 초기화가 완료된다. 초기화를 하면 현재 폴더에 .terraform/ 폴더가 생기는데 여기에는 백엔드 설정 내용이 들어있으므로 굳이 형상관리에 포함할 필요는 없어 보인다.

$ terraform plan
...

Releasing state lock. This may take a few moments...

이제 S3에 올라간 terraform.tfstate를 사용하게 되고 작업을 할 때 Lock을 관리하는 것을 볼 수 있으므로 여러 사람이 동시에 작업을 할 때의 문제를 막을 수 있다.

2017/05/22 02:01 2017/05/22 02:01