테라폼 기초 강좌 #2 HCL 핵심 문법: 블록, 인수, 리소스 주소와 변경의 세 가지 모양
지난 글에서 main.tf 하나로 S3 버킷을 만들고 지웠습니다. 그때 “문법은 다음 편에서"라고 미뤄 둔 약속을 지킬 차례입니다. 테라폼의 코드는 HCL(HashiCorp Configuration Language)이라는 전용 언어로 작성합니다. 범용 언어보다 배울 것이 훨씬 적고, 구조를 한번 파악하면 처음 보는 리소스도 문서를 보며 바로 쓸 수 있습니다. 이번 글에서는 블록과 인수라는 뼈대, 리소스 주소와 참조, 그리고 plan 출력을 읽는 법을 손에 익히겠습니다.
HCL의 뼈대: 블록과 인수 #
HCL 파일은 전부 블록(block) 과 인수(argument) 두 가지로 이루어집니다. 지난 글의 코드를 다시 봅니다.
resource "aws_s3_bucket" "hello" {
bucket = "my-terraform-hello-20260822"
tags = {
ManagedBy = "terraform"
}
}- 블록은
블록타입 "라벨" { ... }형태입니다.resource블록은 라벨을 두 개(리소스 타입, 이름) 받고,provider블록은 하나,terraform블록은 라벨이 없습니다. 몇 개를 받는지는 블록 타입이 정합니다. - 인수는 블록 몸통 안의
이름 = 값한 줄입니다.bucket = "..."이 인수입니다. - 블록 안에 블록이 또 들어갈 수 있습니다. 값이 단순한
tags는 맵 값을 가진 인수지만, 뒤에서 만날lifecycle { ... }처럼=없이 중괄호가 바로 오는 중첩 블록도 있습니다. - 주석은
#으로 시작합니다.
문법 전체가 사실상 이것뿐입니다. 리소스마다 달라지는 것은 “어떤 인수를 받는가"이고, 그것은 provider 문서에 리소스별로 정리되어 있습니다. HCL을 안다는 것은 문법을 외우는 일이 아니라 문서에서 인수를 찾아 채우는 일에 가깝습니다.
리소스 주소: 타입과 이름 #
resource "aws_s3_bucket" "hello"에서 두 라벨을 합친 aws_s3_bucket.hello가 이 리소스의 주소입니다. 여기서 초심자가 가장 많이 헷갈리는 것이 두 번째 라벨입니다. hello는 AWS에 전달되는 값이 아니라 테라폼 코드 안에서만 쓰는 이름입니다. 실제 버킷 이름은 bucket 인수의 값이고, 콘솔 어디에도 hello라는 글자는 나타나지 않습니다. 그래서 같은 타입의 리소스를 여러 개 선언할 때 서로 다른 이름을 붙여 구분합니다.
주소는 세 곳에서 쓰입니다. plan 출력이 리소스를 가리킬 때, 다른 리소스가 참조할 때, 그리고 #5에서 다룰 state 명령이 대상을 지정할 때입니다. 코드를 읽는 사람이 역할을 알 수 있는 이름(logs, static_assets)을 붙이는 것이 좋고, bucket1 같은 이름은 한 달 뒤의 자신을 괴롭힙니다.
리소스끼리 참조하기 #
인프라는 리소스 하나로 끝나지 않습니다. 버킷에 버저닝 설정을 붙여 보겠습니다. AWS provider에서 버저닝은 별도 리소스입니다.
resource "aws_s3_bucket" "hello" {
bucket = "my-terraform-hello-20260822"
}
resource "aws_s3_bucket_versioning" "hello" {
bucket = aws_s3_bucket.hello.id
versioning_configuration {
status = "Enabled"
}
}핵심은 bucket = aws_s3_bucket.hello.id입니다. 주소 뒤에 점을 찍고 속성 이름을 붙이면 그 리소스의 값을 가져옵니다. 버킷 이름을 문자열로 다시 적지 않고 참조로 연결했기 때문에, 버킷 이름이 바뀌어도 이 줄은 고칠 필요가 없습니다. 어떤 속성을 참조할 수 있는지도 provider 문서의 리소스 페이지 하단(Attribute Reference)에 정리되어 있습니다. 참조에는 문법 이상의 의미가 하나 더 있는데, 테라폼이 이 참조를 보고 생성 순서를 결정합니다. 그 이야기는 #6에서 하겠습니다.
변경의 세 가지 모양: create, update, replace #
이 코드로 terraform apply를 하고 나서, 태그를 하나 바꿔 다시 plan을 실행해 봅니다.
# aws_s3_bucket.hello will be updated in-place
~ resource "aws_s3_bucket" "hello" {
~ tags = { ... }
}기호가 +가 아니라 ~입니다. 이미 있는 버킷을 그대로 두고 태그만 고치겠다는 update in-place입니다. 이번에는 bucket 인수의 이름 자체를 바꿔 봅니다.
# aws_s3_bucket.hello must be replaced
-/+ resource "aws_s3_bucket" "hello" {
~ bucket = "my-terraform-hello-20260822" -> "new-name" # forces replacement
}-/+는 지우고 다시 만든다는 뜻입니다. S3 버킷 이름은 만든 뒤에 바꿀 수 없는 속성이라, 이름을 바꾸려면 교체밖에 없습니다. 안에 데이터가 있었다면 그대로 날아가는 작업이므로, plan에서 이 기호를 보면 반드시 멈춰서 확인해야 합니다. 정리하면 plan의 기호는 네 가지입니다.
| 기호 | 의미 |
|---|---|
+ | 새로 만듭니다 |
~ | 그 자리에서 수정합니다 |
-/+ | 지우고 다시 만듭니다(교체) |
- | 지웁니다 |
어떤 속성이 교체를 일으키는지는 provider가 정하며, plan 출력의 # forces replacement 주석이 알려 줍니다. 교체를 제어하는 방법은 #7에서 자세히 다루겠습니다.
fmt와 validate: 커밋 전 습관 #
HCL에는 공식 포매터가 내장되어 있습니다.
terraform fmt # 현재 디렉터리의 .tf 파일 정렬·들여쓰기 정리
terraform validate # 문법과 참조 오류 검사 (AWS 호출 없음)fmt는 들여쓰기와 = 정렬을 표준형으로 맞추고, validate는 AWS에 연결하지 않고 코드 자체의 오류(오타 난 인수, 없는 참조)를 잡습니다. 둘 다 몇 초면 끝나므로 커밋 전에 돌리는 습관을 들이면 리뷰에서 스타일 지적이 사라집니다. 팀에서 이것을 CI로 강제하는 방법은 운영 시리즈에서 다루겠습니다.
정리 #
이번 글에서 다룬 내용입니다.
- HCL은 블록과 인수 두 가지로 이루어집니다. 리소스별 인수는 외우는 것이 아니라 provider 문서에서 찾는 것입니다
aws_s3_bucket.hello의hello는 테라폼 안에서만 쓰는 이름이며 AWS에는 전달되지 않습니다. 역할이 드러나는 이름을 붙입니다- 다른 리소스의 값은
주소.속성으로 참조합니다. 문자열을 복사하지 않고 참조로 연결하면 변경에 강해집니다 - plan 기호
+,~,-/+,-중에서-/+(교체)는 리소스를 지웠다 다시 만든다는 뜻이므로 반드시 확인합니다 terraform fmt와validate는 커밋 전 습관입니다
다음 글(#3 변수·출력·로컬 값)에서는 지금까지 하드코딩해 온 값들을 변수로 빼내는 방법을 다룹니다. 같은 코드로 개발용과 운영용을 만들 수 있게 되는 첫걸음입니다.