HCL Dili: Terraform’da Kaynak ve Değişken Tanımlama Rehberi
Terraform ile altyapı yönetimine adım atan her sysadmin’in er ya da geç karşılaştığı en temel engel, HCL sözdizimini kavramaktır. HashiCorp Configuration Language, ilk bakışta basit görünse de kaynak ve değişken tanımlama konusunda bilmeniz gereken birçok nüans barındırır. Bu yazıda HCL’nin temel yapı taşlarını, gerçek dünya senaryolarıyla birlikte ele alacağız.
HCL Nedir ve Neden Bu Kadar Önemli?
HCL, HashiCorp tarafından geliştirilen ve insan tarafından okunabilir olması için tasarlanmış bir konfigürasyon dilidir. JSON ile YAML arasında bir yerde duran bu dil, hem makine tarafından işlenebilir hem de sistem yöneticilerinin rahatça anlayabileceği bir sözdizimi sunar.
Terraform’da her şey HCL ile yazılır. Bir AWS EC2 instance’ı ayağa kaldırmak ister misiniz? HCL. Azure’da bir sanal ağ oluşturmak? HCL. Google Cloud’da Kubernetes cluster’ı yönetmek? Yine HCL. Dolayısıyla bu dili iyi anlamak, Terraform’u verimli kullanmanın olmazsa olmaz koşuludur.
Temel Sözdizimi ve Blok Yapısı
HCL’de her şey bloklar üzerine kuruludur. Bir bloğun temel yapısı şu şekildedir:
<BLOK_TIPI> "<TIP>" "<ISIM>" {
arguman1 = deger1
arguman2 = deger2
}
Bu yapıyı somutlaştıralım. Terraform’da en çok kullanacağınız blok tipleri şunlardır:
- resource: Gerçek altyapı bileşenlerini tanımlar
- variable: Giriş değişkenlerini tanımlar
- output: Çıktı değerlerini tanımlar
- locals: Yerel hesaplanmış değerleri tanımlar
- data: Mevcut kaynakları sorgular
- module: Modül çağrılarını tanımlar
- provider: Sağlayıcı konfigürasyonlarını tanımlar
Şimdi bunları tek tek inceleyelim.
Resource Blokları: Altyapının Temeli
Resource bloğu, Terraform’un kalbini oluşturur. Bir kaynak tanımladığınızda Terraform’a “bu altyapı bileşenini yönet” diyorsunuz demektir.
resource "aws_instance" "web_sunucu" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
tags = {
Name = "WebSunucu-Prod"
Environment = "production"
Owner = "sysadmin-team"
}
}
Burada dikkat etmeniz gereken birkaç nokak var. aws_instance provider ve kaynak tipini belirtir. web_sunucu ise bu kaynağa verdiğiniz isimdir ve Terraform konfigürasyonu içinde bu kaynağa referans verirken kullanırsınız. Bu iki parça birleşince aws_instance.web_sunucu şeklinde bir kaynak adresi oluşur.
Gerçek dünya senaryosunda bir üretim ortamı için EC2 instance’ını biraz daha detaylı yapılandırmak gerekir:
resource "aws_instance" "uygulama_sunucu" {
ami = var.sunucu_ami
instance_type = var.instance_type
subnet_id = aws_subnet.ozel_subnet.id
vpc_security_group_ids = [aws_security_group.uygulama_sg.id]
key_name = var.ssh_key_name
iam_instance_profile = aws_iam_instance_profile.sunucu_profil.name
root_block_device {
volume_type = "gp3"
volume_size = 50
delete_on_termination = true
encrypted = true
}
user_data = templatefile("${path.module}/scripts/baslangic.sh", {
db_endpoint = aws_db_instance.veritabani.endpoint
app_version = var.uygulama_versiyonu
})
lifecycle {
create_before_destroy = true
ignore_changes = [ami]
}
tags = local.ortak_etiketler
}
Bu örnekte birkaç önemli konsept bir arada kullanılıyor. var. prefix’i değişkenlere referans vermeyi, aws_subnet.ozel_subnet.id gibi ifadeler başka kaynaklara referans vermeyi, local. ise yerel değerlere erişmeyi sağlıyor.
Variable Blokları: Esneklik ve Yeniden Kullanılabilirlik
Değişkenler, Terraform konfigürasyonlarını esnek ve yeniden kullanılabilir hale getiren temel mekanizmadır. Bir değişken tanımladığınızda, o konfigürasyonu farklı ortamlarda farklı değerlerle çalıştırabilirsiniz.
Temel Değişken Tanımlama
variable "region" {
description = "AWS bölgesi"
type = string
default = "eu-west-1"
}
variable "instance_sayisi" {
description = "Kaç adet instance oluşturulacak"
type = number
default = 2
}
variable "production_ortami" {
description = "Production ortamı mı?"
type = bool
default = false
}
Değişken tanımlarken kullanabileceğiniz parametreler:
- description: Değişkenin ne işe yaradığını açıklar, ekip çalışmasında kritik önem taşır
- type: Değişkenin veri tipini belirtir, string/number/bool/list/map/object/any olabilir
- default: Değişken için varsayılan değer atar, bu parametreyi kullanırsanız değişken opsiyonel hale gelir
- validation: Değişken değeri için doğrulama kuralları tanımlar
- sensitive: true olarak ayarlandığında değer loglarda ve çıktılarda gizlenir
Karmaşık Değişken Tipleri
Gerçek projelerde basit string veya number değişkenlerin ötesine geçmeniz gerekir. Liste ve map tipleri çok daha güçlü yapılar oluşturmanızı sağlar:
variable "izin_verilen_portlar" {
description = "Güvenlik grubuna izin verilecek portlar"
type = list(number)
default = [80, 443, 8080]
}
variable "ortam_etiketleri" {
description = "Kaynaklara uygulanacak etiketler"
type = map(string)
default = {
Environment = "staging"
Project = "altyapi-modernizasyon"
ManagedBy = "terraform"
}
}
variable "veritabani_konfig" {
description = "Veritabanı konfigürasyonu"
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
multi_az = bool
})
default = {
engine = "postgres"
engine_version = "14.7"
instance_class = "db.t3.medium"
allocated_storage = 100
multi_az = false
}
}
Değişken Doğrulama
Validation bloğu, değişkenlere geçersiz değer girilmesini engellemenin en temiz yoludur. Büyük ekiplerde çalışırken bu özellik gereksiz hataların önüne geçer:
variable "instance_type" {
description = "EC2 instance tipi"
type = string
default = "t3.medium"
validation {
condition = contains([
"t3.small",
"t3.medium",
"t3.large",
"m5.large",
"m5.xlarge"
], var.instance_type)
error_message = "Gecersiz instance tipi. Izin verilen tipler: t3.small, t3.medium, t3.large, m5.large, m5.xlarge"
}
}
variable "ortam_adi" {
description = "Deployment ortamı"
type = string
validation {
condition = can(regex("^(dev|staging|production)$", var.ortam_adi))
error_message = "Ortam adi sadece 'dev', 'staging' veya 'production' olabilir."
}
}
Locals: Hesaplanmış Değerlerin Gücü
Locals, değişkenlerden farklı olarak dışarıdan değer almaz. Konfigürasyon içinde hesaplanan, türetilen değerleri tanımlamak için kullanılır. Kodun tekrar kullanılabilirliği ve okunabilirliği açısından son derece değerlidir.
locals {
uygulama_adi = "webapi"
# Ortama göre dinamik isim oluşturma
kaynak_prefix = "${local.uygulama_adi}-${var.ortam_adi}"
# Hesaplanmış değerler
toplam_disk_kapasitesi = var.instance_sayisi * var.disk_boyutu_gb
# Koşullu değer ataması
instance_tipi = var.production_ortami ? "m5.large" : "t3.medium"
# Tüm kaynaklara uygulanacak ortak etiketler
ortak_etiketler = merge(var.ortam_etiketleri, {
Application = local.uygulama_adi
ManagedBy = "terraform"
LastUpdated = timestamp()
})
# Port listesinden dinamik kural oluşturma
guvenlik_kurallari = {
for port in var.izin_verilen_portlar :
"port_${port}" => {
from_port = port
to_port = port
protocol = "tcp"
}
}
}
Locals’ı özellikle karmaşık ifadeleri bir kez yazıp defalarca kullanmak için tercih edin. Hem kodu temiz tutar hem de bir değişiklik yapmanız gerektiğinde tek bir yerden düzenlemenize olanak tanır.
Data Sources: Mevcut Kaynakları Sorgulamak
Data source’lar, Terraform dışında veya başka bir state’te yönetilen kaynakları sorgulamanızı sağlar. Mevcut altyapıyla entegrasyon kurarken vazgeçilmez bir araçtır.
# Mevcut VPC'yi sorgula
data "aws_vpc" "mevcut_vpc" {
filter {
name = "tag:Name"
values = ["production-vpc"]
}
}
# En güncel Amazon Linux 2 AMI'sini bul
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Mevcut IAM rolünü çek
data "aws_iam_role" "mevcut_rol" {
name = "existing-app-role"
}
# Bu data source'ları kullanmak
resource "aws_instance" "uygulama" {
ami = data.aws_ami.amazon_linux.id
subnet_id = data.aws_vpc.mevcut_vpc.id
tags = {
Name = "${local.kaynak_prefix}-instance"
}
}
Output Blokları: Değerleri Dışa Aktarmak
Output’lar iki amaçla kullanılır. Birincisi, terraform apply sonrasında size kritik bilgileri göstermek için. İkincisi, bir modülün çıktılarını başka modüllere veya root konfigürasyona aktarmak için.
output "web_sunucu_ip" {
description = "Web sunucusunun public IP adresi"
value = aws_instance.uygulama_sunucu.public_ip
}
output "veritabani_endpoint" {
description = "RDS veritabani endpoint adresi"
value = aws_db_instance.veritabani.endpoint
sensitive = true
}
output "yuk_dengeleyici_dns" {
description = "Application Load Balancer DNS adresi"
value = aws_lb.main.dns_name
}
output "olusturulan_kaynaklar" {
description = "Bu deployment ile olusturulan tum kaynak ID'leri"
value = {
instance_id = aws_instance.uygulama_sunucu.id
sg_id = aws_security_group.uygulama_sg.id
subnet_ids = aws_subnet.ozel_subnet[*].id
}
}
Gerçek Dünya Senaryosu: Çok Ortamlı Yapılandırma
Şimdiye kadar öğrendiklerimizi bir araya getirelim. Birçok şirketin ihtiyaç duyduğu dev/staging/production ortam ayrımını HCL ile nasıl yönetirsiniz?
Proje yapısı şu şekilde olabilir:
main.tf: Ana kaynak tanımlarıvariables.tf: Değişken tanımlarıoutputs.tf: Çıktı tanımlarılocals.tf: Yerel değerlerenvironments/dev.tfvars: Dev ortamı değerlerienvironments/prod.tfvars: Production ortamı değerleri
variables.tf dosyası:
variable "ortam" {
description = "Deployment ortami (dev/staging/production)"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.ortam)
error_message = "Gecerli ortamlar: dev, staging, production"
}
}
variable "sunucu_sayisi" {
description = "Her ortam icin sunucu sayisi"
type = number
}
variable "enable_yedekleme" {
description = "Otomatik yedekleme aktif mi?"
type = bool
default = false
}
variable "izin_verilen_cidr_bloklar" {
description = "SSH erisimi icin izin verilen CIDR bloklar"
type = list(string)
default = []
}
environments/prod.tfvars dosyası:
ortam = "production"
sunucu_sayisi = 4
enable_yedekleme = true
izin_verilen_cidr_bloklar = ["10.0.0.0/8", "172.16.0.0/12"]
locals.tf dosyası:
locals {
# Ortama gore instance boyutu belirle
instance_boyutu = {
dev = "t3.small"
staging = "t3.medium"
production = "m5.large"
}
secilen_instance = local.instance_boyutu[var.ortam]
# Ortama gore backup retention suresi
backup_retention = var.ortam == "production" ? 30 : 7
# Tum kaynaklara uygulanacak etiketler
global_etiketler = {
Environment = var.ortam
ManagedBy = "terraform"
Team = "platform-engineering"
}
}
Sık Yapılan Hatalar ve Kaçınma Yolları
HCL yazarken en çok karşılaşılan hatalar ve bunlardan nasıl kaçınabileceğiniz:
Döngüsel bağımlılıklar: Kaynak A, kaynak B’ye; kaynak B de kaynak A’ya referans verirse Terraform apply çalışmaz. Bunu önlemek için bağımlılıkları dikkatli tasarlayın, gerekirse depends_on kullanın ama bunu son çare olarak düşünün.
Sensitive değerlerin output’lara eklenmesi: Veritabanı şifresi veya API anahtarı gibi değerleri output olarak tanımlayacaksanız mutlaka sensitive = true ekleyin.
Hardcoded değerler: Region, account ID veya resource ARN gibi değerleri direkt koda yazmak, konfigürasyonu yeniden kullanılamaz hale getirir. Bunları her zaman değişken veya data source üzerinden alın.
Değişken tipi belirtmemek: type parametresi olmayan değişkenler any tipini varsayar. Bu esneklik sağlar ama aynı zamanda beklenmedik hatalara yol açar. Mümkün olduğunca açık tip belirtin.
Uzun ve karmaşık locals: Bir locals bloğu içine çok fazla mantık koymak kodu takip edilmez hale getirir. Gerekirse mantığı birden fazla locals bloğuna bölün ya da helper modüller oluşturun.
terraform.tfvars ve Değişken Geçirme Yöntemleri
Değişkenlere değer geçirmenin birden fazla yolu vardır ve bunların öncelik sırası önemlidir. En düşükten en yükseğe doğru sıralama:
- Environment variable (TF_VAR_ prefix):
export TF_VAR_ortam=production - terraform.tfvars dosyası: Otomatik yüklenir
- .auto.tfvars uzantılı dosyalar: Otomatik ve alfabetik sırayla yüklenir
- -var-file parametresi:
terraform apply -var-file="environments/prod.tfvars" - -var parametresi:
terraform apply -var="instance_sayisi=3"
# terraform.tfvars ornegi
ortam = "dev"
sunucu_sayisi = 1
enable_yedekleme = false
# Cok satira yayilan liste
izin_verilen_cidr_bloklar = [
"192.168.1.0/24",
"10.10.0.0/16"
]
CI/CD pipeline’larında genellikle environment variable veya -var-file yöntemi tercih edilir. Hassas değerleri asla .tfvars dosyalarına yazmayın ve bu dosyaları git repository’sine commit etmeyin.
Sonuç
HCL’yi kavramak, Terraform yolculuğunuzun en kritik adımıdır. Resource blokları altyapınızın iskeletini oluştururken, variable ve locals mekanizmaları bu iskeletin farklı ortam ve koşullara uyum sağlamasını mümkün kılar. Data source’lar mevcut altyapıyla köprü kurmanızı, output’lar ise değerli bilgileri dışarıya aktarmanızı sağlar.
Pratik olarak şunu öneririm: Yeni bir Terraform projesi başlatırken önce değişken tanımlarınızı yazın, sonra locals ile iş mantığınızı kodlayın, en son kaynak tanımlarına geçin. Bu sıralama, kodu daha okunabilir ve bakımı daha kolay hale getirir.
Bir sonraki adım olarak Terraform modüllerini ve remote state yönetimini incelemenizi tavsiye ederim. HCL temellerini oturdurduktan sonra bu konular çok daha anlamlı gelecek. Sorularınız veya farklı senaryolar için yorum bölümünü kullanabilirsiniz.
