Yusuf
terraformawsiacdevopscloud-computingnginxsysadminubuntuinfrastructure

Adım Adım Terraform ile AWS Üzerinde Güvenli Web Sunucusu Mimarisi Kurulumu

Terraform (IaC) kullanarak AWS üzerinde sıfırdan kurumsal standartlarda bir web altyapısı inşa etme rehberi. Özel VPC, subnet, Internet Gateway, güvenlik grupları, dinamik Canonical Ubuntu AMI seçimi ve otomatik Nginx kurulumunun aşamaları; dpkg kilitlerini aşma ve Base64 script optimizasyonları gibi kritik saha pratikleriyle bu yazıda.

Yusuf
Yusuf
••9 dk okuma•8 okuma
Adım Adım Terraform ile AWS Üzerinde Güvenli Web Sunucusu Mimarisi Kurulumu

Bulut bilişim projelerinde sanal sunucu ve ağ bileşenlerini konsol üzerinden elle butonlara tıklayarak oluşturmak (ClickOps), prototip aşamasında hızlı görünse de üretim ortamlarında takip edilemeyen yapılandırma sapmalarına (configuration drift), unutulan açık portlara ve ciddi güvenlik zafiyetlerine yol açar.

Infrastructure as Code (IaC) prensibi doğrultusunda Terraform kullanılarak; izole bir sanal ağ (VPC), yönlendirme tabloları, güvenlik duvarı kuralları ve üzerinde Nginx çalışan dinamik bir Ubuntu EC2 sunucusunun sıfırdan nasıl ayağa kaldırılacağı, hangi bileşenin neden seçildiği ve sahada karşılaşılan kritik altyapı incelikleri bu rehberde adım adım incelenmektedir.


1. Mimari Tasarım: Hangi Bileşeni Neden Seçtik?

AWS hesabında varsayılan (default) olarak gelen VPC ve subnet yapıları genellikle genel paylaşımlı kurallara sahiptir. Güvenli ve ölçeklenebilir mimarilerin ilk kuralı, her projenin kendi izole ağ katmanında çalışmasıdır.

🏛️ Mimari Şeması

Aşağıdaki diyagram, bu projenin AWS üzerinde kurduğu bileşenleri ve trafik akışını göstermektedir: Mimari Şema📷 Mimari Şema

Temel Bileşenlerin Görevleri ve Mimari Tercih Nedenleri

  1. Virtual Private Cloud (VPC - 10.0.0.0/16):

    • Görevi: AWS üzerinde projeye özel, mantıksal olarak izole edilmiş sanal bir veri merkezidir. 65.536 adet özel IP içeren güvenli bir havuz oluşturur.
    • Neden Varsayılan Değil de Özel VPC? AWS'nin hazır VPC'si tüm servislerin birbiriyle konuşabildiği kontrolsüz bir alandır. Özel VPC kurarak CIDR bloklarını, alt ağ bölünmelerini ve dış dünya erişim kurallarını sıfırdan kendi kontrolümüze alırız.
  2. Public Subnet (10.0.1.0/24):

    • Görevi: VPC havuzundan ayrılan 256 IP'lik alt ağdır. map_public_ip_on_launch = true özelliğiyle bu subnet içinde başlatılan sunucular otomatik genel IP alır.
    • Neden Public Subnet? Amacımız dışarıdan gelen HTTP isteklerine yanıt veren bir web sunucusu çalıştırmaktır. Arka planda çalışacak bir veri tabanı olsaydı onu dışarıya kapalı bir Private Subnet içerisine konumlandırmamız gerekirdi.
  3. Internet Gateway (IGW) & Route Table:

    • Görevi: Subnet içindeki kaynakların dış dünyayla çift yönlü iletişim kurmasını sağlayan sanal kapıdır. Route Table ise 0.0.0.0/0 (tüm yerel dışı trafik) hedefini IGW'ye bağlayarak internet çıkış rotasını çizer.
  4. Security Group (Sanal Güvenlik Duvarı):

    • Görevi: EC2 sunucusunun hemen önünde duran durum bilgisi tutan (stateful) güvenlik kalkanıdır. Sadece izin verdiğimiz 80 (HTTP) ve 22 (SSH) portlarına gelen paketleri kabul eder. Giden trafik (egress) açık olduğu için paket güncellemeleri sorunsuz indirilir.
  5. Nginx Web Sunucusu:

    • Neden Nginx? Doğrudan bir uygulama çalıştırmak yerine Nginx kullanmak; statik dosyaları mikrosaniyeler içinde sunması, yüksek eşzamanlı bağlantı kapasitesi ve gelecekte backend servisleri önüne ters vekil sunucu (reverse proxy) olarak konumlandırılabilmesi açısından endüstri standardıdır.
  6. EC2 & User Data (Başlatma Betiği):

    • Sunucunun ilk boot anında root yetkisiyle çalışan betiktir (bootstrap script). Paket kilitlerini aşar, Nginx'i kurar ve HTML şablonunu dinamik değişkenlerle besler.

2. Güvenlik ve Kimlik Doğrulama Standartları

Terraform kodlarında AWS API anahtarlarının (AWS_ACCESS_KEY_ID ve AWS_SECRET_ACCESS_KEY) kesinlikle .tf dosyalarına yazılmaması gerekir. Kod versiyon kontrol sistemlerine (Git/GitHub) gönderildiğinde bu anahtarlar botlar tarafından saniyeler içinde taranarak kötüye kullanılabilir.

Güvenli Yetkilendirme (Environment Variables)

Geliştirme ortamında anahtarlar yerel bir .env dosyasında tutulmalı ve oturum değişkeni olarak yüklenmelidir:

Görsel

Hassas Dosyaların Dışlanması (.gitignore)

Projenin kök dizinine aşağıdaki .gitignore dosyası eklenerek state verilerinin ve gizli anahtarların Git reposuna çıkması engellenmelidir:

gitignore
# Kimlik ve çevre değişkenleri
.env
*.env

# Terraform çalışma dizini ve sağlayıcı kütüphaneleri
.terraform/
.terraform.lock.hcl

# Durum dosyaları (Hassas meta veriler ve IP'ler içerir)
*.tfstate
*.tfstate.*
*.tfvars

3. Modüler Dosya Düzeni

Sorumlulukların ayrılması (Separation of Concerns) ilkesi doğrultusunda altyapı 4 temel dosyaya bölünmüştür:

text
├── providers.tf   # Sağlayıcı kısıtlamaları ve ortak etiketler (default_tags)
├── variables.tf   # Parametrik yapılandırma değişkenleri
├── main.tf        # VPC, Subnet, IGW, Route Table, SG ve EC2 tanımları
└── outputs.tf     # Kurulum sonrası elde edilen IP ve web adresi çıktıları

4. Altyapı Bileşenlerinin Kodlanması

A. Sağlayıcı ve Ortak Etiketler (providers.tf)

AWS sağlayıcısının sürüm aralığı belirlenir ve oluşturulan her kaynağa otomatik işlenecek kurumsal etiketler (default_tags) tanımlanır:

hcl
terraform {
  required_version = ">= 1.5.0"

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

provider "aws" {
  region = var.aws_region

  # Tüm kaynaklara otomatik olarak atanacak ortak etiketler
  default_tags {
    tags = {
      Project     = var.project_name
      Environment = var.environment
      ManagedBy   = "Terraform"
      CreatedAt   = "2026"
    }
  }
}

B. Parametrik Değişkenler (variables.tf)

Kodun sert kodlanmış (hardcoded) değerlerden arındırılması, farklı ortamlara (dev, staging, prod) tek hamlede uyarlanabilmesini sağlar:

hcl
variable "aws_region" {
  description = "Kaynakların oluşturulacağı AWS bölgesi."
  type        = string
  default     = "eu-central-1" # Frankfurt
}

variable "project_name" {
  description = "Tüm kaynakların isimlendirilmesinde kullanılacak ön ek."
  type        = string
  default     = "terraform-aws-demo"
}

variable "environment" {
  description = "Ortam etiketi (dev, staging, prod)."
  type        = string
  default     = "dev"
}

variable "vpc_cidr" {
  description = "VPC için CIDR bloğu."
  type        = string
  default     = "10.0.0.0/16"
}

variable "public_subnet_cidr" {
  description = "Public Subnet için CIDR bloğu."
  type        = string
  default     = "10.0.1.0/24"
}

variable "instance_type" {
  description = "EC2 instance tipi. AWS Free Tier için t2.micro önerilir."
  type        = string
  default     = "t2.micro"
}

variable "author_name" {
  description = "Web sayfasında görünecek geliştirici adı."
  type        = string
  default     = "Yusuf"
}

C. Altyapı Kaynakları ve Başlatma Betiği (main.tf)

Tüm ağ bileşenleri, dinamik imaj sorgusu ve sunucu ayağa kaldırma kuralları bu ana dosyada toplanır:

hcl
# --- 1. VPC ---
# Kaynakların çalışacağı izole sanal ağ
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

# --- 2. Internet Gateway ---
# VPC'yi dış dünyaya bağlar
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "${var.project_name}-igw"
  }
}

# --- 3. Public Subnet ---
# Sunucunun yerleştirileceği, otomatik public IP dağıtan alt ağ
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidr
  availability_zone       = "${var.aws_region}a"
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-public-subnet"
  }
}

# --- 4. Route Table ---
# Subnet içindeki bilinmeyen trafiği (0.0.0.0/0) IGW üzerinden yönlendirir
resource "aws_route_table" "public_rt" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = {
    Name = "${var.project_name}-public-rt"
  }
}

# Route Table'ı subnet ile eşleştirme
resource "aws_route_table_association" "public_assoc" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public_rt.id
}

# --- 5. Security Group ---
# Güvenlik duvarı: Port 80 (HTTP) ve Port 22 (SSH) erişimine izin verir
resource "aws_security_group" "web_sg" {
  name        = "${var.project_name}-web-sg"
  description = "HTTP ve SSH erisimi"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # Not: Port 22'nin tüm dünyaya (0.0.0.0/0) açık olması demo amaçlıdır.
  # Canlı ortamlarda bu kural yalnızca kendi statik IP adresinize kısıtlanmalı
  # veya doğrudan AWS Systems Manager (SSM) Session Manager kullanılmalıdır.
  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Paket güncellemeleri için tüm giden trafiğe izin ver"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-sg"
  }
}

# --- 6. Dinamik AMI Sorgusu ---
# Canonical'ın resmi en güncel Ubuntu 22.04 LTS imajı dinamik olarak çekilir
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical resmi yayıncı kimliği

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# --- 7. EC2 Web Sunucusu ---
resource "aws_instance" "web" {
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = var.instance_type
  subnet_id                   = aws_subnet.public.id
  vpc_security_group_ids      = [aws_security_group.web_sg.id]
  user_data_replace_on_change = true

  user_data = <<-EOF
    #!/bin/bash

    # 1. Ubuntu kilit mekanizmasını bekle (unattended-upgrades kilit çözümü)
    for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
      fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || break
      sleep 6
    done

    # 2. Paket kurulumları
    export DEBIAN_FRONTEND=noninteractive
    apt-get update -y
    apt-get install -y nginx
    systemctl enable nginx
    systemctl start nginx

    # 3. HTML sayfasını Base64 üzerinden hatasız yazdır
    echo "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9InRyIj4KPGhlYWQ+CiAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIj4KICA8dGl0bGU+VGVycmFmb3JtIEFXUyBEZW1vPC90aXRsZT4KICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PUludGVyOndnaHRANDAwOzUwMDs2MDA7NzAwJmZhbWlseT1KZXRCcmFpbnMrTW9ubzp3Z2h0QDQwMDs1MDAmZGlzcGxheT1zd2FwIiByZWw9InN0eWxlc2hlZXQiPgogIDxzdHlsZT4KICAgICogeyBib3gtc2l6aW5nOiBib3JkZXItYm94OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IH0KICAgIGJvZHkgewogICAgICBmb250LWZhbWlseTogJ0ludGVyJywgc2Fucy1zZXJpZjsKICAgICAgYmFja2dyb3VuZDogIzBhMGEwYTsKICAgICAgY29sb3I6ICNlNWU1ZTU7CiAgICAgIG1pbi1oZWlnaHQ6IDEwMHZoOwogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgcGFkZGluZzogMzJweCAxNnB4OwogICAgfQogICAgLmNhcmQgewogICAgICB3aWR0aDogMTAwJTsKICAgICAgbWF4LXdpZHRoOiA1NjBweDsKICAgICAgYmFja2dyb3VuZDogIzExMTsKICAgICAgYm9yZGVyOiAxcHggc29saWQgIzIyMjsKICAgICAgYm9yZGVyLXJhZGl1czogMTZweDsKICAgICAgcGFkZGluZzogNDBweDsKICAgIH0KICAgIC5iYWRnZSB7CiAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDZweDsKICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICBmb250LXdlaWdodDogNTAwOwogICAgICBjb2xvcjogIzRhZGU4MDsKICAgICAgYmFja2dyb3VuZDogcmdiYSg3NCwyMjIsMTI4LDAuMDgpOwogICAgICBib3JkZXI6IDFweCBzb2xpZCByZ2JhKDc0LDIyMiwxMjgsMC4yKTsKICAgICAgcGFkZGluZzogNHB4IDEycHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDEwMHB4OwogICAgICBtYXJnaW4tYm90dG9tOiAyNHB4OwogICAgfQogICAgLmRvdCB7CiAgICAgIHdpZHRoOiA2cHg7CiAgICAgIGhlaWdodDogNnB4OwogICAgICBiYWNrZ3JvdW5kOiAjNGFkZTgwOwogICAgICBib3JkZXItcmFkaXVzOiA1MCU7CiAgICB9CiAgICBoMSB7CiAgICAgIGZvbnQtc2l6ZTogMjJweDsKICAgICAgZm9udC13ZWlnaHQ6IDcwMDsKICAgICAgY29sb3I6ICNmZmY7CiAgICAgIG1hcmdpbi1ib3R0b206IDhweDsKICAgIH0KICAgIC5zdWJ0aXRsZSB7CiAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgY29sb3I6ICM2NjY7CiAgICAgIGxpbmUtaGVpZ2h0OiAxLjY7CiAgICAgIG1hcmdpbi1ib3R0b206IDMycHg7CiAgICAgfQogICAgLmRpdmlkZXIgewogICAgICBib3JkZXI6IG5vbmU7CiAgICAgIGJvcmRlci10b3A6IDFweCBzb2xpZCAjMWUxZTFlOwogICAgICBtYXJnaW4tYm90dG9tOiAyNHB4OwogICAgfQogICAgLmluZm8tbGlzdCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIGdhcDogMTRweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMzJweDsKICAgIH0KICAgIC5pbmZvLXJvdyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgIH0KICAgIC5pbmZvLWxhYmVsIHsKICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICBjb2xvcjogIzU1NTsKICAgIH0KICAgIC5pbmZvLXZhbHVlIHsKICAgICAgZm9udC1mYW1pbHk6ICdKZXRCcmFpbnMgTW9ubycsIG1vbm9zcGFjZTsKICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICBjb2xvcjogI2NjYzsKICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgIH0KICAgIC5pbmZvLXZhbHVlLmhpZ2hsaWdodCB7CiAgICAgIGNvbG9yOiAjODE4Y2Y4OwogICAgfQogICAgLnRhZ3MgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBnYXA6IDhweDsKICAgICAgZmxleC13cmFwOiB3cmFwOwogICAgfQogICAgLnRhZyB7CiAgICAgIGZvbnQtc2l6ZTogMTJweDsKICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgY29sb3I6ICM1NTU7CiAgICAgIGJhY2tncm91bmQ6ICMxNjE2MTY7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICMyMjI7CiAgICAgIHBhZGRpbmc6IDRweCAxMHB4OwogICAgICBib3JkZXItcmFkaXVzOiA2cHg7CiAgICB9CiAgPC9zdHlsZT4KPC9oZWFkPgo8Ym9keT4KICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgIDxkaXYgY2xhc3M9ImJhZGdlIj48c3BhbiBjbGFzcz0iZG90Ij48L3NwYW4+IFN1bnVjdSBBa3RpZjwvZGl2PgogICAgPGgxPlRlcnJhZm9ybSBpbGUgQVdTIEFsdHlhcGlzaTwvaDE+CiAgICA8cCBjbGFzcz0ic3VidGl0bGUiPkJ1IG9ydGFtIEluZnJhc3RydWN0dXJlIGFzIENvZGUgcHJlbnNpYml5bGUgVGVycmFmb3JtIGt1bGxhbmlsYXJhayBvdG9tYXRpayBvbGFyYWsgb2x1c3R1cnVsbXVzdHVyLjwvcD4KICAgIDxociBjbGFzcz0iZGl2aWRlciI+CiAgICA8ZGl2IGNsYXNzPSJpbmZvLWxpc3QiPgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPkdlbGlzdGlyaWNpPC9zcGFuPgogICAgICAgIDxzcGFuIGNsYXNzPSJpbmZvLXZhbHVlIGhpZ2hsaWdodCI+QVVUSE9SX1ZBTDwvc3Bhbj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImluZm8tcm93Ij4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby1sYWJlbCI+QVdTIEJvbGdlc2k8L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPlJFR0lPTl9WQUw8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPkluc3RhbmNlIFRpcGk8L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPklUWVBFX1ZBTDwvc3Bhbj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImluZm8tcm93Ij4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby1sYWJlbCI+T3J0YW08L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPkVOVl9WQUw8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPklQIEFkcmVzaTwvc3Bhbj4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby12YWx1ZSIgaWQ9ImlwIj4tPC9zcGFuPgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGhyIGNsYXNzPSJkaXZpZGVyIj4KICAgIDxkaXYgY2xhc3M9InRhZ3MiPgogICAgICA8c3BhbiBjbGFzcz0idGFnIj5UZXJyYWZvcm08L3NwYW4+CiAgICAgIDxzcGFuIGNsYXNzPSJ0YWciPkFXUyBFQzI8L3NwYW4+CiAgICAgIDxzcGFuIGNsYXNzPSJ0YWciPk5naW54PC9zcGFuPgogICAgICA8c3BhbiBjbGFzcz0idGFnIj5VYnVudHUgMjIuMDQ8L3NwYW4+CiAgICAgIDwvZGl2PgogIDwvZGl2PgogIDxzY3JpcHQ+CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaXAnKS50ZXh0Q29udGVudCA9IGxvY2F0aW9uLmhvc3RuYW1lOwogIDwvc2NyaXB0Pgo8L2JvZHk+CjwvaHRtbD4K" | base64 -d > /var/www/html/index.html

    # 4. Terraform değişkenlerini HTML şablonuna dinamik olarak işle
    sed -i 's|AUTHOR_VAL|${var.author_name}|g' /var/www/html/index.html
    sed -i 's|REGION_VAL|${var.aws_region}|g'   /var/www/html/index.html
    sed -i 's|ITYPE_VAL|${var.instance_type}|g' /var/www/html/index.html
    sed -i 's|ENV_VAL|${var.environment}|g'     /var/www/html/index.html

    systemctl restart nginx
  EOF

  tags = {
    Name = "${var.project_name}-web-server"
  }
}

D. Çıktı Değerleri (outputs.tf)

Kurulum tamamlandığında terminale sunucunun genel IP adresini, doğrudan tıklanabilir web bağlantısını ve oluşturulan ağ kimliklerini yazdırmak için çıktılar tanımlanır:

hcl
output "instance_public_ip" {
  description = "EC2 sunucusunun public IP adresi."
  value       = aws_instance.web.public_ip
}

output "web_url" {
  description = "Tarayıcıdan açılabilecek URL."
  value       = "http://${aws_instance.web.public_ip}"
}

output "vpc_id" {
  description = "Oluşturulan VPC'nin ID'si."
  value       = aws_vpc.main.id
}

output "subnet_id" {
  description = "Sunucunun bulunduğu Subnet ID'si."
  value       = aws_subnet.public.id
}

output "security_group_id" {
  description = "Oluşturulan Security Group ID'si."
  value       = aws_security_group.web_sg.id
}

5. Sahada Hayat Kurtaran Altyapı İncelikleri

Bu mimariyi inşa ederken kurumsal üretim ortamlarında fark yaratan 3 kritik pratik uygulanmıştır:

1. Ubuntu Paket Kilidi (dpkg lock) Aşma Döngüsü

Ubuntu sunucuları ilk boot anında güvenlik güncellemelerini çekmek için arka planda unattended-upgrades çalıştırır. Başlatma betiği bu işlemi beklemeden apt-get install nginx çalıştırmaya kalkarsa kilit hatası alır ve Nginx kurulamaz. Betik başına eklenen fuser döngüsü kilidin serbest kalmasını bekleyerek kurulumun %100 başarıyla tamamlanmasını garanti eder.

2. Base64 ile Heredoc Çakışmalarını Önleme

Bash heredoc (<<-EOF) bloğu içine CSS veya JavaScript barındıran zengin HTML kodları gömüldüğünde çift tırnaklar ve özel karakterler Terraform derleyicisiyle çakışır. HTML kodunu Base64 formatına çevirip sunucuda base64 -d ile açmak ve dinamik değerleri sed ile enjekte etmek hem kod okunabilirliğini korur hem de sözdizimi hatalarını sıfırlar.

3. Yapılandırma Değişiminde Otomatik Yeniden Kurulum

Normal şartlarda user_data güncellendiğinde Terraform mevcut sunucuyu silmez; bu da yeni başlatma betiğinin çalışmamasına yol açar. user_data_replace_on_change = true direktifi sayesinde başlatma betiğinde bir değişiklik yapıldığında sunucu temiz şekilde baştan ayağa kaldırılır.


6. Dağıtım Döngüsü: Adım Adım Komutlar

Tüm kod dosyaları hazırlandıktan sonra deployment süreci sırasıyla şu 4 adımla işletilir:

1. Çalışma Alanını Hazırlama

bash
terraform init

AWS sağlayıcı eklentisi indirilir ve arka plan yapılandırması tamamlanır.

2. Değişiklik Simülasyonu

bash
terraform plan

Görsel

Kod ile AWS üzerindeki gerçek durum kıyaslanır. Eklenecek 7 kaynak (VPC, IGW, Subnet, Route Table, Association, SG, EC2) incelenir.

3. Canlıya Alma (Deployment)

bash
terraform apply

Onay verildiğinde ortalama 45 saniye içinde tüm altyapı ayağa kalkar ve terminalde çıktılar listelenir:

Görsel

Terminalde verilen web_url tarayıcıda açıldığında, Terraform değişkenleriyle dinamik olarak beslenmiş koyu temalı sistem bilgi kartı görüntülenir.

4. Kaynak İmhası (Maliyet Kontrolü)

Test süreci bittiğinde gereksiz bulut maliyetlerinin önüne geçmek için tüm kaynaklar tek komutla kaldırılır:

bash
terraform destroy

Görsel


Sistemin Çalışır Hali📷 Sistemin Çalışır Hali

7. GitHub Kaynak Kodları

Bu projede kullanılan tüm Terraform yapılandırma dosyalarına, örnek .env.example şablonuna ve detaylı kurulum adımlarına GitHub Deposu üzerinden ulaşabilirsiniz. Repoyu yıldızlayarak (star) destekleyebilir ve kendi projeleriniz için şablon olarak kullanabilirsiniz.


Sonuç

Terraform ile altyapı inşa etmek; sistemlerin belgelendiği, sürüm kontrolüne alındığı, insan hatasından arındırıldığı ve dakikalar içinde baştan kurulabildiği güvenli bir mühendislik ortamı sunar. İzole bir VPC'den dinamik AMI seçimine ve otomatik paket kurulumuna kadar tüm zincirin kod haline getirilmesi, bulut altyapınızı kurumsal standartlara taşımanın en sağlam adımıdır.

Bu yazıyı faydalı bulduysanız beğenebilir veya sosyal medyada paylaşabilirsiniz.
Yusuf Aytaş

Yusuf Aytaş

Yazılım Mühendisi — Cloud & DevOps

Bulut mimarileri, Kubernetes, Docker, Terraform ve CI/CD otomasyonları üzerine çalışıyorum. Teknik deneyimlerimi ve projelerimi burada paylaşıyorum.