Table of contents
- 프로바이더가 하는 일
- provider 블록의 기본형
- source — 프로바이더의 주소
- version — 버전 제약
- provider 블록 설정
- alias — 같은 프로바이더, 다른 설정
- 모듈에 프로바이더 전달하기
- 공식 vs 파트너 vs 커뮤니티
- 여러 프로바이더 동시 사용
- 캐시와 네트워크 주의
- 자주 하는 실수
- 다음으로
프로바이더가 하는 일
Terraform이 AWS나 GCP에 리소스를 만들 때 실제로 API를 호출하는 건 Terraform Core가 아니다. 각 클라우드/서비스 전용 플러그인이 그 일을 맡는다. 이 플러그인을 프로바이더(Provider)라고 부른다.
구조를 그림으로 보면 이렇다.
flowchart TB
CORE[Terraform Core<br/>계획/상태/그래프]
subgraph Providers
AWS[AWS Provider]
GCP[GCP Provider]
K8S[Kubernetes Provider]
GH[GitHub Provider]
end
AWS_API[(AWS API)]
GCP_API[(GCP API)]
K8S_API[(Kubernetes API)]
GH_API[(GitHub API)]
CORE --> AWS
CORE --> GCP
CORE --> K8S
CORE --> GH
AWS --> AWS_API
GCP --> GCP_API
K8S --> K8S_API
GH --> GH_API
Terraform Core는 리소스 간 의존성 그래프를 계산하고, 실행 계획을 세우고, 상태를 관리하는 “뇌” 역할만 한다. 실제로 “EC2 인스턴스를 만들어라” 같은 HTTP 호출은 프로바이더의 몫이다. 이 분업 덕분에 Terraform은 AWS/GCP/Cloudflare/GitHub/Datadog 등 수많은 플랫폼을 동일한 워크플로우로 관리할 수 있다.
2025년 기준 Terraform Registry에는 3000개가 넘는 프로바이더가 등록되어 있다. “API가 있는 것”이면 대부분 프로바이더가 있다고 보면 된다.
provider 블록의 기본형
프로바이더를 쓰려면 두 단계가 필요하다. required_providers에 출처와 버전을 선언하고, provider 블록으로 실제 설정(리전, 프로파일 등)을 준다.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-2"
profile = "terraform-demo"
}
두 단계로 나뉜 이유는 책임 분리 때문이다. required_providers는 “이 코드가 어떤 프로바이더의 몇 번대 버전을 요구하는지”를 선언하고, provider 블록은 런타임 설정을 맡는다. 모듈을 만들 땐 required_providers만 선언하고 provider 블록은 루트 모듈에서 주입받는 게 일반적이다.
source — 프로바이더의 주소
source는 프로바이더를 유일하게 가리키는 주소다. 세 부분으로 나뉜다.
[HOST/]NAMESPACE/TYPE
- HOST: 프로바이더가 게시된 레지스트리 (기본값
registry.terraform.io, 생략 가능) - NAMESPACE: 게시자 이름 (
hashicorp,integrations,cloudflare등) - TYPE: 프로바이더 이름 (
aws,kubernetes,github등)
실제 예시를 보면 명확하다.
required_providers {
# HashiCorp 공식 — 네임스페이스가 hashicorp
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
# 파트너 프로바이더 — 네임스페이스가 서비스 제공자
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
# 커뮤니티 프로바이더 — 네임스페이스가 개인/조직
cloudinit = {
source = "hashicorp/cloudinit"
}
}
네임스페이스를 빼고 aws만 쓰면 예전 버전 호환성을 위해 자동으로 hashicorp/aws로 해석되지만, 최신 Terraform은 명시적으로 쓰는 걸 권장한다.
version — 버전 제약
프로바이더 버전을 지정하지 않으면 처음엔 문제가 없다가, 몇 달 뒤 terraform init을 새로 하면 갑자기 다른 버전이 깔려서 예상치 못한 변경이 생길 수 있다. 버전 제약은 필수다.
연산자
version = "5.0.0" # 정확히 이 버전만
version = ">= 5.0" # 이상
version = "<= 5.10" # 이하
version = "~> 5.0" # 5.x 중 최신 (메이저는 고정)
version = "~> 5.1" # 5.1.x 중 최신 (마이너까지 고정)
version = ">= 5.0, < 6.0" # 범위
특히 ~> 5.0은 “pessimistic constraint”라고 부르는 표현이다. Ruby의 ~> 연산자와 같다. 5.0, 5.1, 5.99까지 허용하고 6.0은 거부한다. 메이저 버전 업그레이드는 보통 호환성이 깨지는 변경을 담기 때문에, 이런 식으로 막아두는 게 안전하다.
lock 파일
terraform init을 실행하면 선택된 버전이 .terraform.lock.hcl에 고정된다.
# .terraform.lock.hcl (자동 생성)
provider "registry.terraform.io/hashicorp/aws" {
version = "5.70.0"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
...
]
}
lock 파일이 있으면 다음 init에서도 같은 버전을 쓴다. 이 파일은 Git에 커밋한다. 그래야 팀원과 CI가 동일한 프로바이더를 쓴다.
프로바이더를 업그레이드하고 싶을 땐 명시적으로 허락한다.
terraform init -upgrade
이 옵션이 있어야 lock 파일의 버전을 무시하고 제약 범위 내 최신 버전을 설치한다. 운영 반영 전에 plan을 꼼꼼히 확인하자.
provider 블록 설정
provider "<이름>" 블록에는 프로바이더별 인자를 넣는다. AWS를 예로 보자.
provider "aws" {
region = "ap-northeast-2"
profile = "terraform-demo"
shared_credentials_files = ["~/.aws/credentials"]
default_tags {
tags = {
ManagedBy = "terraform"
Environment = var.environment
Project = var.project_name
}
}
}
default_tags는 실무에서 특히 유용하다. 이 프로바이더로 만들어지는 모든 AWS 리소스에 해당 태그가 자동으로 붙는다. 리소스 하나하나에 같은 태그를 반복해 적지 않아도 된다. 리소스별로 다른 태그가 필요하면 tags를 별도로 지정하면 합쳐진다.
자격 증명은 여러 방식을 섞어 쓸 수 있다.
- CLI 프로파일 (
profile = "...") - 환경 변수 (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - IAM Role 가정 (
assume_role블록) - EC2 Instance Profile (EC2에서 실행할 때 자동)
실무에선 보통 CI 환경 변수 또는 IAM Role assume을 쓴다. 로컬 실습에선 프로파일이 편하다. 접근 키를 코드에 하드코딩하는 건 절대 안 된다.
alias — 같은 프로바이더, 다른 설정
실무에선 한 프로젝트에서 두 리전, 혹은 두 계정을 동시에 써야 할 때가 많다. 대표적인 예가 “us-east-1에 CloudFront 인증서를 만들고, ap-northeast-2에 나머지 리소스를 배포”하는 구성이다. ACM 인증서는 CloudFront에 연결하려면 반드시 us-east-1에 있어야 하기 때문이다.
이때 alias를 쓴다.
# 기본 프로바이더 — 서울 리전
provider "aws" {
region = "ap-northeast-2"
profile = "terraform-demo"
}
# alias가 붙은 추가 프로바이더 — 버지니아 리전
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
profile = "terraform-demo"
}
리소스에서 특정 프로바이더를 고를 땐 provider를 명시한다.
# 서울 리전 리소스 (기본값)
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
}
# CloudFront용 인증서는 버지니아 리전에 만든다
resource "aws_acm_certificate" "cdn" {
provider = aws.us_east_1
domain_name = "www.example.com"
validation_method = "DNS"
}
# 데이터 소스도 alias 지정 가능
data "aws_ami" "us_east_amazon_linux" {
provider = aws.us_east_1
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
멀티 리전/계정 배포의 구조는 이렇게 그려볼 수 있다.
flowchart LR
subgraph TF[Terraform Project]
P1[provider aws<br/>default = 서울]
P2[provider aws<br/>alias = us_east_1]
P3[provider aws<br/>alias = prod_account]
end
subgraph CLOUD[클라우드]
R1[(Seoul: EC2, RDS)]
R2[(N.Virginia: ACM Cert)]
R3[(Prod Account: S3)]
end
P1 --> R1
P2 --> R2
P3 --> R3
같은 프로바이더(aws)지만 리전과 계정이 다른 여러 접점을 한 프로젝트 안에서 관리할 수 있다. 이걸 모르면 “us-east-1에 인증서를 어떻게 만들지?”에서 막히게 된다.
모듈에 프로바이더 전달하기
모듈을 만들 때 주의할 점이 있다. alias가 걸린 프로바이더는 자동으로 자식 모듈에 전달되지 않는다. 명시적으로 넘겨줘야 한다.
# 모듈 쪽 — 필요한 프로바이더를 선언
# modules/cdn/versions.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
configuration_aliases = [aws.us_east_1]
}
}
}
# 모듈 내부에서는 alias를 명시해서 사용
resource "aws_acm_certificate" "cdn" {
provider = aws.us_east_1
domain_name = var.domain_name
validation_method = "DNS"
}
# 호출 쪽 — providers로 매핑 전달
module "cdn" {
source = "./modules/cdn"
providers = {
aws.us_east_1 = aws.us_east_1
}
domain_name = "www.example.com"
}
configuration_aliases로 “이 모듈은 이런 alias가 필요하다”를 선언하고, 호출부에서 providers 맵으로 실제 매핑을 넘긴다. 이 단계가 빠지면 “provider aws.us_east_1 is not defined” 류의 에러로 이어진다.
공식 vs 파트너 vs 커뮤니티
Terraform Registry의 프로바이더는 세 등급으로 나뉜다. 어느 등급인지 아는 게 실무에서 중요하다.
Official (공식)
HashiCorp가 직접 유지보수한다. 네임스페이스가 hashicorp다.
- 예:
hashicorp/aws,hashicorp/google,hashicorp/kubernetes,hashicorp/random
지원과 업데이트가 안정적이다. 가장 많이 쓰이는 프로바이더들이 여기 속한다.
Partner (파트너)
해당 서비스의 공식 벤더가 직접 관리한다. HashiCorp가 인증한다. 네임스페이스가 벤더명이다.
- 예:
cloudflare/cloudflare,datadog/datadog,mongodb/mongodbatlas
벤더가 직접 만든 만큼 API 최신화가 빠르고, 공식이라고 봐도 무방하다.
Community (커뮤니티)
개인이나 조직이 오픈소스로 기여한 프로바이더. 네임스페이스가 GitHub 핸들이나 조직명이다.
- 예:
custom-user/some-provider
쓰기 전에 유지보수 현황(최근 커밋, 이슈 수), 다운로드 수, 라이선스를 확인해야 한다. 특정 SaaS의 유일한 프로바이더가 커뮤니티 등급인 경우가 종종 있는데, 회사 내부에서 합의한 뒤 쓰는 게 좋다.
선택의 우선순위는 이렇게 본다.
flowchart LR
NEED[프로바이더 필요] -->|1순위| OFF[Official<br/>hashicorp/*]
NEED -->|2순위| PART[Partner<br/>벤더/*]
NEED -->|3순위| COM[Community<br/>개인/*]
NEED -->|최후| CUSTOM[직접 작성]
커뮤니티 프로바이더도 잘 만든 게 많지만, 프로덕션에 들일 땐 신중해야 한다. 2~3년 방치된 리포라면 그 리스크는 당신이 떠안게 된다.
여러 프로바이더 동시 사용
실제 프로젝트는 한 프로바이더만 쓰지 않는다. AWS 리소스를 만들면서 Kubernetes에도 배포하고, 도메인은 Cloudflare, 모니터링은 Datadog에서 관리하는 식이다.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "ap-northeast-2"
}
# Kubernetes는 EKS 클러스터 생성 후 그 정보로 자동 설정
provider "kubernetes" {
host = aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", aws_eks_cluster.main.name]
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
주의할 점은 프로바이더 설정값에 다른 리소스의 속성을 넣으면 의존성이 생긴다는 것이다. 위 예에서 kubernetes 프로바이더는 aws_eks_cluster.main에 의존하므로, EKS 클러스터가 만들어진 뒤에야 Kubernetes 리소스를 적용할 수 있다. 한 apply로 EKS를 만들고 그 안에 리소스까지 넣는 건 실무에서 꼬일 수 있다. 프로덕션에선 “인프라(EKS)“와 “애플리케이션(K8s 리소스)“을 다른 Terraform 프로젝트로 나누는 편이 깔끔하다.
캐시와 네트워크 주의
프로바이더 바이너리는 크다. AWS 프로바이더는 100MB를 가볍게 넘는다. 여러 프로젝트에서 동일한 프로바이더를 반복 다운로드하면 시간과 디스크가 낭비된다.
해결책이 플러그인 캐시다. ~/.terraformrc(Linux/macOS) 또는 %APPDATA%\terraform.rc(Windows)에 다음을 쓴다.
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
이 디렉토리를 만들어두면 모든 Terraform 프로젝트가 프로바이더를 여기에 캐싱하고 공유한다. 여러 프로젝트를 오가며 쓰는 실무에선 필수 설정이다.
자주 하는 실수
- 버전을 고정하지 않는다. 동료와 버전이 달라지고, CI에서도 엇갈린다.
~>는 최소한의 방어선이다 .terraform.lock.hcl을 커밋하지 않는다. 커밋해야 같은 버전을 공유한다alias없이 같은 프로바이더를 중복 선언한다. “provider block already defined” 에러로 이어진다- 시크릿을 provider 블록에 하드코딩한다.
api_token = "abc123..."은 절대 금물. 변수나 환경변수로 분리 - 파일마다 provider 블록을 중복한다. provider 블록은 루트 모듈에서 한 번만 선언한다. 여러 번 쓰려면 alias를 활용
다음으로
프로바이더까지 다뤘으니 이제 Terraform의 본 무대인 리소스를 자세히 들여다볼 차례다. resource 블록의 구조, 리소스 간 참조, 암묵적 의존성과 명시적 의존성, 그리고 라이프사이클 메타 인자(create_before_destroy, prevent_destroy, ignore_changes)까지 본격적으로 살펴본다.


Loading comments...