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을 관리하는 것을 볼 수 있으므로 여러 사람이 동시에 작업을 할 때의 문제를 막을 수 있다.
Comments