본문 바로가기

[Terraform] 테라폼 기초 (3) 본문

DevOps

[Terraform] 테라폼 기초 (3)

겨울바람_ 2024. 8. 28. 22:31

Provider

테라폼은 terraform 바이너리 파일을 시작으로 로컬 환경이나 배포 서버와 같은 원격 환경에서 원하는 대상을 호출하는 방식으로 실행된다.

 

이때 호출되는 대상은 프로바이더가 제공하는 API를 호출해 상호작용 하게 된다. 즉, 테라폼이 대상과의 상호작용을 할 수 있도록 하는 것을 프로바이더라 한다.

 

각 프로바이더의 API 구현은 서로 다르지만, 테라폼의 고유 문법으로 동일한 동작을 수행하도록 구현되어 있다. 프로바이더는 플러그인 형태로 테라폼에 결합되어 대상이 되는 클라우드, SaaS, 기타 서비스 API를 사용해 동작을 수행한다.

 

각 프로바이더는 테라폼이 관리하는 리소스 유형과 데이터 소스를 사용할 수 있도록 연결한다. 그렇기 때문에 테라폼은 프로바이더 없이는 어떤 종류의 인프라와 서비스도 관리할 수 없다는 의미다.

 

대부분의 프로바이더는 대상 인프라 환경이나 서비스 환경에 대해 리소스를 관리하므로, 프로바이더를 구성할 때는 대상과의 연결과 인증에 대한 정보가 제공되어야 한다.

Provider Config

프로바이더 구성에 대한 요구사항은 공식 레지스트리 사이트인 테레폼 레지스트리에 공개되어 있는 구성 방식을 참고하는 것이 좋다.

 

https://registry.terraform.io/

Required_Providers

terraform 블록의 required_provieders 블록 내에 <로컬 이름> = { }으로 여러 개의 프로바이더를 정의할 수 있다.

 

로컬 이름은 테라폼 모듈 내에서 고유해야 한다. 로컬 이름과 리소스 접두사는 각각 독립적으로 선언되며, 각 프로바이더의 소스 경로가 지정되면 프로바이더의 고유 접두사가 제공된다.

 

만약 동일한 접두사를 사용하는 프로바이더가 선언되는 경우 로컬 이름을 달리해 관련 리소스에서 어떤 프로바이더를 사용하는지 명시적으로 지정할 수 있다.

 

예를 들어, 아래의 main.tf에서처럼 동일한 http 이름을 사용하는 다수의 프로바이더가 있는 경우 각 프로바이더에 고유한 이름을 부여하고 리소스와 데이터 소스에 어떤 프로바이더를 사용할지 provider 인수에 명시한다.

 

단, 동일한 source에 대해 다수의 정의는 불가능하다.

terraform {
    required_providers {
        architect-http = {
            source = "architect-team/http"
            version = "~> 3.0"
        }
        http = {
            source = "hashicorp/http"
        }
        aws-http = {
            source = "terraform-aws-modules/http"
        }
    }
}

data "http" "example" {
    provider = aws-http
    url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"
    request_headers = {
        Accept = "application/json"
    }
}

위의 코드에서 required_providers 블록 내부의 요소들을 하나씩 살펴보자. architect-http, http, aws-http는 모두 프로바이더의 로컬 이름을 의미한다.

 

source의 경우 프로바이더 다운로드 경로를 지정하고, version은 버전 제약을 명시한다. version을 생략하는 경우 가장 최신 버전으로 선택된다.

 

아래의 예제 코드를 참고하자.

terraform {
    required_providers {
        <프로바이더 로컬 이름> = {
            source = [<호스트 주소>/]<네임스페이스>/<유형>
            version = <버전 제약>
        }
    }
}

 

source를 좀 더 분석해보면, 호스트 주소는 프로바이더를 배포하는 주소로 기본값은 테라폼의 레지스트리인 registry.terraform.io다.

 

네임스페이스는 지정된 레지스트리 내에서 구분하는 네임스페이스로, 공개된 레지스트리 및 Terraform Cloud의 비공개 레지스트리의 프로바이더를 게시하는 조직을 의미한다.

 

유형은 프로바이더에서 관리되는 플랫폼이나 서비스 이름으로 일반적으로 접두사와 일치하지만 일부 예외가 존재한다.

Multiple Provider

동일한 프로바이더를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다.

 

예를 들어, AWS 프로바이더를 사용하는데 서로 다른 권한의 IAM을 갖는 Access ID 또는 리전을 지정해야 하는 경우다. 이때는 프로바이더 선언에서 alias를 명시하고 사용하는 리소스와 데이터 소스에서는 provider 메타인수를 사용해 특정 프로바이더를 지정할 수 있다.

 

provider 메타인수에 지정되지 않은 경우 alias가 없는 프로바이더가 기본 프로바이더로 동작한다. 아래의 예제를 통해 살펴보자.

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

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

resource "aws_instance" "app_server" {
    provider = aws.seoul
    ami = "ami-..."
    instance_type = "t2.micro"
}

AWS Provider

AWS 리소스를 관리하는 AWS 프로바이더를 통해 리소스를 프로비저닝하기 위해서는, 가입된 계정의 액세스 키와 비밀 액세스 키가 필요하다.

 

발급받은 자격증명을 AWS 프로바이더에서 사용하기 위해서는 환경 변수, 파일, 테라폼 구성에 추가하는 각각의 방식을 사용해야 한다. 이번에는 테라폼 구성에 추가하는 방식을 통해 확인해보자.

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

    required_version = ">= 1.0"
}

provider "aws" {
    region = "ap-northeast-2"
    access_key = "<my_access_key>"
    secret_key = "<my_secret_key>"
}

data "aws_ami" "amzn2" {
    most_recent = true
    owners = ["amazon"]

    filter {
        name = "owner-alias"
        values = ["amazon"]
    }

    filter {
        name = "name"
        values = ["amzn2-ami-hvm*"]
    }
}

resource "aws_instance" "app_server" {
    ami = data.aws_ami.amzn2.id
    instance_type = "t2.micro"

    tags = {
        Name = "ExampleAppServerInstance"
    }
}

 

위의 코드의 내용 중 몇 가지를 간단하게 분석해보자. required_version은 해당 코드를 실행하기 위한 테라폼의 버전을 의미하며 해당 코드에서는 1.0버전 이상의 테라폼만이 코드를 동작시킬 수 있다.

 

data.aws_ami.amzn2는 AWS의 AMI를 조회하기 위한 데이터 블럭이며, most_recent = true는 가장 최신 버전의 AMI를 가져오도록 하며 owners는 AMI의 소유자를 설정하는데 해당 코드에서는 AWS 소유의 AMI만을 필터링한다.

 

첫 번째 filterowner-alias가 amazon인 AMI를 찾는다는 의미이고, 두 번째는 amzn2-ami-hvm*로 시작하는 AMI를 찾는다는 의미다.

 

resource 블럭은 AWS 인스턴스를 생성하기 위한 블럭이다. ami에서는 위의 data 블럭에서 조회한 AMI의 ID를 사용하여 인스턴스를 생성한다. 이때 생성된 인스턴스에 ExampleAppServerInstance라는 태그를 붙인다.

 

환경변수를 통해 엑세스 키와 시크릿 키를 추가하는 방법도 있다.

# Linux / Unix
export AWS_ACCESS_KEY_ID = <my_access_key>
export AWS_SECRET_ACCESS_KEY = <my_secret_key>

# Windows
set AWS_ACCESS_KEY_ID = <my_access_key>
set AWS_SECRET_ACCESS_KEY = <my_secret_key>

# Windows Powershell
$Env:AWS_ACCESS_KEY_ID = <my_access_key>
$Env:AWS_SECRET_ACCESS_KEY = <my_secret_key>

위처럼 OS에 환경변수로 자격증명을 등록하고 기존에 작성했던 코드에서 provider.access_key, provider.secret_key를 제외하면 된다.

State

테라폼은 Stateful한 애플리케이션이다. 프로비저닝 결과에 따른 State를 저장하고 프로비저닝한 모든 내용을 저장된 상태로 추적한다. 로컬 실행 환경에서는 terraform.tfstate 파일에 JSON 형태로 저장되고, 팀이나 조직에서의 공동 관리를 위해서는 원격 저장소에 저장해 공유하는 방식을 활용한다.

 

State에는 작업자가 정의한 코드와 실제 반영된 프로비저닝 결과를 저장하고, 이 정보를 토대로 이후의 리소스 생성, 수정, 삭제에 대한 동작 판단 작업을 수행한다.

Why State?

테라폼은 State를 사용해 대상 환경에서 어떤 리소스가 테라폼으로 관리되는 리소스인지 판별하고 결과를 기록한다.

State의 역할은 다음과 같다.

  • 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디로 매핑한다.
  • 리소스 종속성과 같은 메타데이터를 저장하고 추적한다.
  • 테라폼 구성으로 프로비저닝된 결과를 캐싱하는 역할을 수행한다.

terraform plan을 실행하면 암묵적으로 refresh 동작을 수행하면서 리소스 생성의 대상과 State를 기준으로 비교하는 과정을 거친다.

 

이 작업은 프로비저닝 대상의 응답 속도와 기존 작성된 State의 리소스 양에 따라 속도 차이가 발생한다.

 

대량의 리소스를 관리해야 하는 경우 terraform plan -refresh=false를 통해 대상 환경과의 동기화 과정을 생략할 수 있다.

State Synchronization

테라폼 구성 파일은 기존 State와 구성을 비교해 실행 계획에서 생성, 수정, 삭제 여부를 결정한다.

Workspace

State를 관리하는 논리적인 가상 공간을 워크스페이스라고 한다. 테라폼 구성 파일은 동일하지만 작업자는 서로 다른 State를 갖는 실제 대상을 프로비저닝할 수 있다.

 

워크스페이스는 기본 default로 정의된다. 로컬 작업 환경의 워크스페이스를 관리하기 위한 CLI 명령어로 workspace가 있다.

 

terraform workspace list 명령어를 통해 존재하는 워크스페이스의 목록을 확인할 수 있으며, 현재 사용하고 있는 워크스페이스 이름 옆에는 *기호가 표시되어 있다.

 

terraform workspace new <워크스페이스 이름> 명령어를 통해 default가 아닌 새로운 워크스페이스를 생성하고 terraform plan을 실행하면, 기존 default 워크스페이스와는 다른 State를 가지고 있기 때문에 앞서 정의한 테라폼 구성의 리소스를 다시 생성한다는 메시지가 출력된다.

 

워크스페이스를 구분하면 동일한 구성에서 기존 인프라에 영향을 주지 않으며 간편하게 테라폼 프로비저닝을 테스트하고 확인할 수 있다. 또한 테라폼 코드 상에서 terraform.workspace를 사용해 워크스페이스 이름을 조회하여 조건을 부여하는 것도 가능하다.

resource "aws_instance" "web" {
    count = "${terraform.workspace == "default" ? 5 : 1}"
    ami = "ami-..."
    instance_type = "t2.micro"

    tags = {
        Name = "Hello World - ${terraform.workspace}"
    }
}

 

다수의 워크스페이스를 사용하면 다음과 같은 장점이 있다.

  • 하나의 루트 모듈에서 다른 환경을 위한 리소스를 동일한 테라폼 구성으로 프로비저닝하고 관리할 수 있다.
  • 기존 프로비저닝된 환경에 영향을 주지 않고 변경 사항 실험이 가능하다.
  • 깃의 브랜치 전략처럼 동일한 구성에서 서로 다른 리소스 결과를 관리할 수 있다.

단점은 다음과 같다.

  • State가 동일한 저장소(로컬 또는 백엔드)에 저장되어 State 접근 권한 관리가 불가능하다.
  • 모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생할 수 있다.
  • 프로비저닝 대상에 대한 인증 요소를 완벽히 분리하기가 어렵다.

워크스페이스 사용의 근본적인 단점은 완벽한 격리가 불가능하다는 것이다. 이를 해결하기 위해 루트 모듈을 별도로 구성하는 디렉토리 기반의 레이아웃을 사용할 수도 있다.

Comments