Skip to main content

Terraform Basic(2)

· 5 min read
고현림
프론트엔드 | 블록체인 Developer

상태 관리

테라폼은 자신이 담당하는 인프라가 무엇인지 어떻게 알까요? 그것을 한 번 알아보고자 합니다.

테라폼의 상태란?

테라폼을 실행할 때마다 테라폼 상태 파일에 생성된 인프라에 대한 정보가 같이 기록이 돼요.

/foo/bar 폴더 내부의 .tf 파일을 실행을 하게 되면 해당 폴더 내부에 /terraform.tfstate 파일을 생성해요. 해당 파일에는 trrraform 리소스에서 실제 소스 표현으로의 매핑을 기록이 되어있어요.

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

해당 테라폼 코드를 실행을 하고 .tfstate 파일을 확인해보면

{
"instance": [
{
"attributes": {
"ami": "ami-...",
"id": "i-..."
}
}
]
}

이런식으로 json이 작성이 된 것을 확인할 수 있어요. 이 때 아이디는 실제 aws ec2 인스턴스의 아이디를 의미해요. 따라서 테라폼을 실행할 때마다 aws에서 ec2 인스턴스의 최신 상태를 가져오고 이를 테라폼 구성의 내용과 비교하여 적용해야 할 변경 사항을 결정할 수 있어요.

상태 파일은 비공개 API에요. 이를 직접적으로 편집하거나 직접 읽는 코드를 작성하면 안돼요. 조작해야하는 경우에는 terraform import, terraform state 명령을 사용해주면 돼요.

.tfstate 공유

테라폼을 개인 프로젝트에서 사용을 하면 문제가 없는데, 실제 제품에서 팀으로 테라폼을 사용하게 되면 아래와 같은 문제에 직면해요.

  1. 상태 파일을 위한 공유 저장소 : 테라폼을 사용하여 인프라를 업데이트하려면 각 팀 구성원이 동일한 테라폼 상태 파일에 액세스 해야해요. 이는 해당 파일을 공유 위치에 저장 해야하는 것을 의미해요.
  2. 상태 파일 잠금 : 잠금 없이 팀 구성원이 동시에 테라폼 코드를 실행하는 경우 테라폼 프로세스가 동시에 업데이트하여 충돌할 수 있어요.
  3. 상태 파일 격리 : 인프라를 변경할 때는 다양한 환경을 격리하는 것이 모범적이에요.

해당 문제에 대해서 아래와 같이 해결 할 수가 있어요.

상태 파일을 위한 공유 저장소

보통은 공통된 것에 엑세스를 할 때는 git과 같은 것에 두는게 좋아요. 하지만 테라폼 코드를 버전 제어에 저장하는 것은 아래와 같은 이유로 좋지 않아요:\

  • 수동작업에 대한 오류
    • 테라폼을 실행하기 전 버전 제어에서 최신 변경 사항을 풀다운하거나, 실행한 이후에 최신 변경사항을 푸쉬하는 것을 잊어버리기 쉬워요. 팀의 누군가가 오래된 파일을 실행하여 이전 배포로 롤백하거나 할 수 있기 때문이에요.
  • 잠금
    • 대부분의 버전 제어 시스템들은 동일한 상태 파일에 대해서 apply하는 것을 방지해주지 않아요.
  • 비밀
    • 테라폼의 상태 파일은 기본적으로 일반 텍스트로 저장이 되기 때문에 민감한 데이터가 들어갈 수도 있어요.

버전 제이 시스템을 사용하는 것보다 제일 좋은 방법은 원격 백엔드를 사용하는게 좋아요. 기본 백엔드는 로컬 디스크에 상태 파일을 저장하는 로컬 백엔드이고 원격 백엔드는 원격 공유 저장소에 저장할 수 있어요.

대표적으로는 AWS S#, Azure Storage, Google Cloud Storage 가 있어요.

원격 백엔드를 사용을 하면 위의 문제를 해결할 수 있어요.

  • 수동작업에 대한 오류
    • 원격 백엔드를 구성하고 plan 혹은 apply를 하면 해당 백엔드에 상태 파일을 자동으로 로드하고 상태파일을 저장해주기 때문에 오류가 발생할 가능성이 없어요.
  • 잠금
    • 원격 백엔드는 기본적으로 잠금을 지원요. terraform apply를 실행하기 위해 테라폼은 자동으로 잠금이 됩니다.
  • 비밀
    • 원격 백엔드는 기본적으로 전송 ㅅ중 암호화와 나머지 상태 파일 암호화를 지원해줍니다.

실습

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

resoure "aws_s3_bucket" "terraform_state" {
bucket = "terraform-state-alias"
lifecycle {
prevent_destroy = true
}
}

버킷 이름을 지정하고 prevent_destroy 설정을 두어 삭제를 방지해줄 수 있어요.

그다음에 몇 가지 보호 계층을 추가해줍니다.

  1. 버저닝 리소스를 사용하면 버킷의 파일을 업데이트 할 대마다 해당 파일의 새 버전이 생성돼요. 이를 통해서 이전 버전으로 돌릴 수 있어요.
  2. server_side_encryption_configuration을 사용하여 기록되는 모든 데이터에 대해서 서버 측 암호화를 활성화 해요.
  3. public_access_block을 사용해서 모든 퍼블릭 엑세스를 차단합니다.
resource "aws_s3_bucket_versioning" "enabled" {
bucket = aws_s3_bucket.terraform_state.id

versioning_configuration {
status = "Enabled"
}
}

resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
bucket = aws_s3_bucket.terraform_state.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

resource "aws_s3_bucket_public_access_block" "public_access" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

그 다음에는 dynamoDB를 생성해줍니다. dynamoDB는 NoSQL DB의 일종으로 동시에 여러 사용자가 실행했을 때 발생할 수 있는 충돌을 방지해줍니다.

resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}

기초적인 작업은 모두 마무리했어요. 그런데 해당 상태로만 지정을 해두고 실행을 하면 아직은 로컬 백엔드 환경이에요. 이제 원격 백엔드로 지정을 해주도록 해볼께요.

해당 코드는 제일 최상단에 지정해주면 돼요. 여기에 들어가는 값들은 위에서 지정한 이름들을 하드코딩해주면 됩니다. (더 좋은 방법 충분히 있다고 생각해요.)

terraform {
backend "s3" {
bucket = "terraform-state"
key = "global/s3/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}

삭제를 하기 위해서는

  1. 백엔드 구성을 제거하고 init을 실행합니다.
  2. terraform destroy를 실행하여 리소스를 삭제합니다.

상태 파일 격리

위와 같이 진행을 해준다면 협업에서는 문제가 안돼요. 하지만 격리 부분에 있어서는 문제가 있습니다.

단일 파일 내에서 모든 리소스를 관리를 하게 되버리면 스테이징 단꼐에서 배포를 시도하다가 프로덕션 단계에서 앱이 중된될 수도 있고, 버그로 인해 모든 리소스가 손상될 수도 있어요.

작업 공간을 통한 격리

terraform workspace를 사용하면 테라폼 상태를 별도의 이름이 지정된 여러 작업 공간에 저장할 수 있어요.

작업 공간을 명시하지 않으면 기본 작업 공간이 전체 시간 동안 사용돼요.

실습

앞서 작업한 부분에서 key를 workspaces-example/terraform.tfstate로 만 수정을 해줍니다.

terraform {
backend "s3" {
bucket = "terraform-state"
key = "workspaces-example/terraform.tfstate"
region = "us-east-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
terraform init
terraform apply

위 명령으로 코드를 배포해줍니다.

그 다음에

terraform workspace new example1

을 실행시켜 새 작업 공간을 만들어요 그리고 terraform plan 명령을 실행하면 새로운 ec2를 처음부터 생성하는 것을 확인할 수 있어요.

terraform apply를 실행하면 새 작업 공간에 인스턴스가 배포된 것을 확인할 수 있어요.

아래 명령어를 통해서 작업 공간을 바꾸어줄 수 있어요.

terraform workspace select default

파일 레이아웃을 통한 격리

환경을 완전히 격리하려면 다음과 같이 수행해주면 돼요.

  1. 각 환경의 테라폼 구성 파일을 별도의 폴더에 넣어요.
  2. 서로 다른 인증 매커니즘과 액세스 제어를 사용하여 각 환경에 대한 서로 다른 백엔드를 구성해주어요.

테라폼 프로젝트는 환경 -> 구성요소 -> 구성파일의 계층 구조로 관리를 진행해요

## 최상위 환경 디렉토리

- `global` : 모든 환경에서 공통 사용 (예: S3, IAM)
- `mgmt` : DevOps 도구 및 인프라 관리 (예: 배스천, CI 서버)
- `stage` : 테스트/사전 프로덕션 환경
- `prod` : 실제 운영 환경

## 환경 내 구성요소 디렉토래

- `vpc/` : 네트워크 토폴로지 정의
- `services/` : 서비스 또는 앱 (예: 웹 서버, API 등)
- ex. `services/frontend/`, `services/backend/`
- `data-storage/` : 데이터 저장소
- ex. `data-storage/mysql/`, `data-storage/redis/`

## 구성 요소 내 파일 구성

| 파일명 | 역할 |
| ----------------- | ----------------------------- |
| `main.tf` | 주요 리소스 정의 |
| `variables.tf` | 입력 변수 정의 |
| `outputs.tf` | 출력 변수 정의 |
| `providers.tf` | provider 설정 정의 |
| `dependencies.tf` | 외부 데이터 소스 및 참조 정의 |