AWS VPC Oluşturma ve Yapılandırma Rehberi

Bulut altyapısı kurarken en çok zaman harcanan ve en kritik kararların alındığı adım, ağ katmanının doğru tasarlanmasıdır. AWS’de bu ağ katmanının temeli VPC’dir (Virtual Private Cloud) ve yanlış yapılandırılmış bir VPC, ilerleyen süreçte güvenlik açıkları, performans sorunları ve yönetim kabuslarına yol açar. Bu yazıda sıfırdan bir VPC oluşturacak, subnet’leri yapılandıracak, internet erişimini doğru şekilde açacak ve güvenlik gruplarını kuracağız. Hem AWS Console üzerinden hem de AWS CLI ile tüm adımları göstereceğim.

VPC Nedir, Neden Önemlidir?

VPC, AWS üzerinde izole edilmiş sanal bir ağ ortamıdır. Kendi IP adres bloğunu, subnet’lerini, routing tablolarını ve ağ geçitlerini kontrol ettiğin özel bir bulut ağı olarak düşünebilirsin. Varsayılan olarak AWS her region’da bir default VPC oluşturur ama production ortamları için bu default VPC’yi kullanmak iyi bir pratik değildir. Nedeni basittir: default VPC’deki her subnet internet’e açıktır, CIDR bloğu herkes tarafından bilinir ve üzerine kurulu altyapıyı izole etmek sonradan çok zor olur.

Gerçek dünyada karşılaştığım bir senaryoyu paylaşayım: Bir e-ticaret şirketinin AWS altyapısını devraldığımda tüm EC2 instance’ları default VPC içindeydi ve database sunucuları public subnet’teydi. Security group kuralları “kısıtlamak için” port 5432’yi sadece belirli IP’lere açmıştı ama bu yeterli değildi çünkü instance’lar public IP almaktaydı. Doğru tasarlanmış bir VPC ile bu riski tamamen ortadan kaldırabilirdin.

Başlamadan Önce: Temel Kavramlar

VPC’ye geçmeden önce bilinmesi gereken temel bileşenleri hızlıca geçelim:

  • CIDR Block: VPC’nin kullanacağı IP adres aralığı. Örneğin 10.0.0.0/16 sana 65.536 IP adresi verir.
  • Subnet: VPC içindeki daha küçük ağ bölümleri. Public ve private olarak ikiye ayrılır.
  • Internet Gateway (IGW): VPC’yi internet ile bağlayan bileşen. Public subnet’ler bu üzerinden dışarı çıkar.
  • NAT Gateway: Private subnet’lerdeki kaynakların internete çıkmasını sağlar ama internetten içeri giriş olmaz.
  • Route Table: Trafiğin nereye yönlendirileceğini belirleyen kural seti.
  • Security Group: Instance seviyesinde çalışan, stateful firewall kuralları.
  • Network ACL (NACL): Subnet seviyesinde çalışan, stateless firewall kuralları.

VPC Tasarımı: Çok Katmanlı Mimari

Profesyonel bir VPC tasarımı genellikle üç katmandan oluşur: public, private ve database katmanları. Her katman kendi subnet’lerinde ve en az iki Availability Zone’da bulunur. Bu yazıda şu mimarıyi kuracağız:

  • VPC CIDR: 10.0.0.0/16
  • Public Subnet A (us-east-1a): 10.0.1.0/24
  • Public Subnet B (us-east-1b): 10.0.2.0/24
  • Private Subnet A (us-east-1a): 10.0.10.0/24
  • Private Subnet B (us-east-1b): 10.0.20.0/24
  • Database Subnet A (us-east-1a): 10.0.100.0/24
  • Database Subnet B (us-east-1b): 10.0.200.0/24

AWS CLI ile VPC Kurulumu

AWS CLI kullanmak, tekrarlanabilir altyapı kurulumu için Console’dan çok daha verimlidir. Önce gerekli araçların kurulu olduğundan emin ol.

# AWS CLI kurulumu ve yapılandırması
aws --version
aws configure
# Access Key ID, Secret Access Key, Region (us-east-1), output format (json) gir

# Mevcut VPC'leri listele
aws ec2 describe-vpcs --query 'Vpcs[*].{VpcId:VpcId,CidrBlock:CidrBlock,IsDefault:IsDefault}' --output table

VPC Oluşturma

# VPC oluştur
VPC_ID=$(aws ec2 create-vpc 
  --cidr-block 10.0.0.0/16 
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=production-vpc},{Key=Environment,Value=production},{Key=Project,Value=ecommerce}]' 
  --query 'Vpc.VpcId' 
  --output text)

echo "VPC ID: $VPC_ID"

# DNS hostname ve DNS resolution'ı aktif et (önemli!)
aws ec2 modify-vpc-attribute 
  --vpc-id $VPC_ID 
  --enable-dns-hostnames

aws ec2 modify-vpc-attribute 
  --vpc-id $VPC_ID 
  --enable-dns-support

# VPC durumunu kontrol et
aws ec2 describe-vpcs --vpc-ids $VPC_ID

DNS hostname’i aktif etmezsen EC2 instance’ların public DNS adları olmaz, bu da birçok uygulamanın iç iletişimini bozar.

Subnet’lerin Oluşturulması

# Public Subnet A - us-east-1a
PUBLIC_SUBNET_A=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.1.0/24 
  --availability-zone us-east-1a 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-subnet-1a},{Key=Type,Value=public}]' 
  --query 'Subnet.SubnetId' 
  --output text)

# Public Subnet B - us-east-1b
PUBLIC_SUBNET_B=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.2.0/24 
  --availability-zone us-east-1b 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=public-subnet-1b},{Key=Type,Value=public}]' 
  --query 'Subnet.SubnetId' 
  --output text)

# Private Subnet A - us-east-1a
PRIVATE_SUBNET_A=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.10.0/24 
  --availability-zone us-east-1a 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-subnet-1a},{Key=Type,Value=private}]' 
  --query 'Subnet.SubnetId' 
  --output text)

# Private Subnet B - us-east-1b
PRIVATE_SUBNET_B=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.20.0/24 
  --availability-zone us-east-1b 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=private-subnet-1b},{Key=Type,Value=private}]' 
  --query 'Subnet.SubnetId' 
  --output text)

# Database Subnet A
DB_SUBNET_A=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.100.0/24 
  --availability-zone us-east-1a 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=db-subnet-1a},{Key=Type,Value=database}]' 
  --query 'Subnet.SubnetId' 
  --output text)

# Database Subnet B
DB_SUBNET_B=$(aws ec2 create-subnet 
  --vpc-id $VPC_ID 
  --cidr-block 10.0.200.0/24 
  --availability-zone us-east-1b 
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=db-subnet-1b},{Key=Type,Value=database}]' 
  --query 'Subnet.SubnetId' 
  --output text)

echo "Public Subnets: $PUBLIC_SUBNET_A, $PUBLIC_SUBNET_B"
echo "Private Subnets: $PRIVATE_SUBNET_A, $PRIVATE_SUBNET_B"
echo "DB Subnets: $DB_SUBNET_A, $DB_SUBNET_B"

# Public subnet'lerde otomatik public IP atamasını aktif et
aws ec2 modify-subnet-attribute 
  --subnet-id $PUBLIC_SUBNET_A 
  --map-public-ip-on-launch

aws ec2 modify-subnet-attribute 
  --subnet-id $PUBLIC_SUBNET_B 
  --map-public-ip-on-launch

Internet Gateway ve Routing Yapılandırması

# Internet Gateway oluştur
IGW_ID=$(aws ec2 create-internet-gateway 
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=production-igw}]' 
  --query 'InternetGateway.InternetGatewayId' 
  --output text)

# IGW'yi VPC'ye bağla
aws ec2 attach-internet-gateway 
  --internet-gateway-id $IGW_ID 
  --vpc-id $VPC_ID

echo "Internet Gateway: $IGW_ID"

# Public Route Table oluştur
PUBLIC_RT=$(aws ec2 create-route-table 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=public-route-table}]' 
  --query 'RouteTable.RouteTableId' 
  --output text)

# Internet'e giden route'u ekle (0.0.0.0/0 -> IGW)
aws ec2 create-route 
  --route-table-id $PUBLIC_RT 
  --destination-cidr-block 0.0.0.0/0 
  --gateway-id $IGW_ID

# Public subnet'leri bu route table'a bağla
aws ec2 associate-route-table 
  --route-table-id $PUBLIC_RT 
  --subnet-id $PUBLIC_SUBNET_A

aws ec2 associate-route-table 
  --route-table-id $PUBLIC_RT 
  --subnet-id $PUBLIC_SUBNET_B

echo "Public Route Table: $PUBLIC_RT"

NAT Gateway Kurulumu

Private subnet’lerdeki instance’ların güncelleme alabilmesi, dış API’lara erişebilmesi için NAT Gateway gereklidir. NAT Gateway saatlik ücret alır, bu yüzden dev ortamlarda single AZ ile başlayabilirsin. Production’da iki AZ’da da NAT Gateway olmalıdır.

# NAT Gateway için Elastic IP al (her AZ için ayrı)
EIP_A=$(aws ec2 allocate-address 
  --domain vpc 
  --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=nat-eip-1a}]' 
  --query 'AllocationId' 
  --output text)

EIP_B=$(aws ec2 allocate-address 
  --domain vpc 
  --tag-specifications 'ResourceType=elastic-ip,Tags=[{Key=Name,Value=nat-eip-1b}]' 
  --query 'AllocationId' 
  --output text)

# NAT Gateway oluştur (Public subnet'lerde!)
NAT_GW_A=$(aws ec2 create-nat-gateway 
  --subnet-id $PUBLIC_SUBNET_A 
  --allocation-id $EIP_A 
  --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-gw-1a}]' 
  --query 'NatGateway.NatGatewayId' 
  --output text)

NAT_GW_B=$(aws ec2 create-nat-gateway 
  --subnet-id $PUBLIC_SUBNET_B 
  --allocation-id $EIP_B 
  --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-gw-1b}]' 
  --query 'NatGateway.NatGatewayId' 
  --output text)

# NAT Gateway'ler hazır olana kadar bekle
echo "NAT Gateway'ler hazır olana kadar bekleniyor..."
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW_A $NAT_GW_B
echo "NAT Gateway'ler hazır!"

# Private Route Table - AZ A
PRIVATE_RT_A=$(aws ec2 create-route-table 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=private-route-table-1a}]' 
  --query 'RouteTable.RouteTableId' 
  --output text)

aws ec2 create-route 
  --route-table-id $PRIVATE_RT_A 
  --destination-cidr-block 0.0.0.0/0 
  --nat-gateway-id $NAT_GW_A

aws ec2 associate-route-table 
  --route-table-id $PRIVATE_RT_A 
  --subnet-id $PRIVATE_SUBNET_A

# Private Route Table - AZ B
PRIVATE_RT_B=$(aws ec2 create-route-table 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=private-route-table-1b}]' 
  --query 'RouteTable.RouteTableId' 
  --output text)

aws ec2 create-route 
  --route-table-id $PRIVATE_RT_B 
  --destination-cidr-block 0.0.0.0/0 
  --nat-gateway-id $NAT_GW_B

aws ec2 associate-route-table 
  --route-table-id $PRIVATE_RT_B 
  --subnet-id $PRIVATE_SUBNET_B

Security Group Yapılandırması

Security group’lar, VPC’nin güvenlik katmanının en kritik bileşenidir. Katmanlı güvenlik için her uygulama tipi için ayrı security group oluşturuyoruz.

# Bastion Host Security Group
BASTION_SG=$(aws ec2 create-security-group 
  --group-name bastion-sg 
  --description "Bastion Host Security Group" 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=bastion-sg}]' 
  --query 'GroupId' 
  --output text)

# Sadece kendi IP'nden SSH erişimi (BURAYA KENDİ IP'NİZİ YAZIN)
MY_IP=$(curl -s https://checkip.amazonaws.com)
aws ec2 authorize-security-group-ingress 
  --group-id $BASTION_SG 
  --protocol tcp 
  --port 22 
  --cidr "$MY_IP/32"

# Application Load Balancer Security Group
ALB_SG=$(aws ec2 create-security-group 
  --group-name alb-sg 
  --description "Application Load Balancer Security Group" 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=alb-sg}]' 
  --query 'GroupId' 
  --output text)

aws ec2 authorize-security-group-ingress 
  --group-id $ALB_SG 
  --protocol tcp 
  --port 80 
  --cidr 0.0.0.0/0

aws ec2 authorize-security-group-ingress 
  --group-id $ALB_SG 
  --protocol tcp 
  --port 443 
  --cidr 0.0.0.0/0

# Application Server Security Group (sadece ALB'den ve Bastion'dan erişim)
APP_SG=$(aws ec2 create-security-group 
  --group-name app-sg 
  --description "Application Server Security Group" 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=app-sg}]' 
  --query 'GroupId' 
  --output text)

aws ec2 authorize-security-group-ingress 
  --group-id $APP_SG 
  --protocol tcp 
  --port 8080 
  --source-group $ALB_SG

aws ec2 authorize-security-group-ingress 
  --group-id $APP_SG 
  --protocol tcp 
  --port 22 
  --source-group $BASTION_SG

# Database Security Group (sadece App Server'lardan erişim)
DB_SG=$(aws ec2 create-security-group 
  --group-name db-sg 
  --description "Database Security Group" 
  --vpc-id $VPC_ID 
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=db-sg}]' 
  --query 'GroupId' 
  --output text)

aws ec2 authorize-security-group-ingress 
  --group-id $DB_SG 
  --protocol tcp 
  --port 5432 
  --source-group $APP_SG

echo "Security Groups - Bastion: $BASTION_SG, ALB: $ALB_SG, App: $APP_SG, DB: $DB_SG"

VPC Flow Logs Aktif Etme

VPC Flow Logs, ağ trafiğini izlemek ve güvenlik olaylarını araştırmak için kritik öneme sahiptir. Production ortamında mutlaka aktif edilmelidir.

# CloudWatch Log Group oluştur
aws logs create-log-group 
  --log-group-name /aws/vpc/flowlogs/production-vpc

# Flow Logs için IAM Role ARN'ını al (önceden oluşturulmuş olmalı)
# Flow Logs'u aktif et
aws ec2 create-flow-logs 
  --resource-type VPC 
  --resource-ids $VPC_ID 
  --traffic-type ALL 
  --log-destination-type cloud-watch-logs 
  --log-group-name /aws/vpc/flowlogs/production-vpc 
  --deliver-logs-permission-arn arn:aws:iam::ACCOUNT_ID:role/VPCFlowLogsRole

# Flow Logs durumunu kontrol et
aws ec2 describe-flow-logs 
  --filter "Name=resource-id,Values=$VPC_ID" 
  --query 'FlowLogs[*].{FlowLogId:FlowLogId,FlowLogStatus:FlowLogStatus,LogGroupName:LogGroupName}'

Yapılandırmayı Doğrulama

Her şeyi kurduktan sonra yapılandırmanın doğru çalıştığını test etmek gerekir.

# Tüm subnet'leri listele ve route table bağlantılarını kontrol et
aws ec2 describe-subnets 
  --filters "Name=vpc-id,Values=$VPC_ID" 
  --query 'Subnets[*].{SubnetId:SubnetId,CidrBlock:CidrBlock,AZ:AvailabilityZone,Name:Tags[?Key==`Name`]|[0].Value}' 
  --output table

# Route table'ları listele
aws ec2 describe-route-tables 
  --filters "Name=vpc-id,Values=$VPC_ID" 
  --query 'RouteTables[*].{RouteTableId:RouteTableId,Routes:Routes[*].{Dest:DestinationCidrBlock,Target:GatewayId}}' 
  --output json

# Security group kurallarını kontrol et
aws ec2 describe-security-groups 
  --filters "Name=vpc-id,Values=$VPC_ID" 
  --query 'SecurityGroups[*].{GroupId:GroupId,GroupName:GroupName,InboundRules:IpPermissions}' 
  --output json

# NAT Gateway durumunu kontrol et
aws ec2 describe-nat-gateways 
  --filter "Name=vpc-id,Values=$VPC_ID" 
  --query 'NatGateways[*].{NatGatewayId:NatGatewayId,State:State,SubnetId:SubnetId}'

Sık Yapılan Hatalar ve Çözümleri

Yıllar içinde gördüğüm en yaygın VPC hatalarını ve çözümlerini paylaşayım:

CIDR bloğu çok küçük seçmek: /24 ile başlayan VPC’lerde ilerleyen süreçte IP tükenir. Her zaman /16 ile başla, subnet’leri /24 olarak böl. VPC CIDR sonradan değiştirilemez, yalnızca ek CIDR eklenebilir.

Tek AZ tasarımı: Maliyet kaygısıyla tek AZ’da kurulan sistemler, AZ arızasında tamamen çevrimdışı kalır. Her zaman en az iki AZ kullan.

Database sunucularını public subnet’e koymak: Yukarıda bahsettiğim gerçek dünya senaryosunda olduğu gibi, database’ler mutlaka private subnet’te olmalı ve internete hiçbir şekilde route’u bulunmamalıdır.

Security group’larda 0.0.0.0/0 kullanmak: Sadece ALB gibi gerçekten herkese açık olması gereken kaynaklarda kullan. Diğer tüm yerlerde kaynak olarak security group ID’si kullan.

Flow Logs’u kapatmak: “Maliyet çok fazla” diye Flow Logs kapatıldığında güvenlik olaylarını araştırmak neredeyse imkansız hale gelir. Log retention süresini kısa tutarak maliyeti düşürebilirsin.

NAT Gateway’i private subnet’e kurmak: NAT Gateway, public subnet’te olmalıdır. Private subnet’e konulduğunda internet erişimi hiç çalışmaz.

Maliyet Optimizasyonu

VPC’nin kendisi ücretsizdir ama içindeki bileşenler ücretlidir:

  • NAT Gateway: Saatlik ücret artı işlenen veri başına ücret alır. Dev ortamda iki AZ yerine tek AZ kullanmak veya NAT Instance kullanmak maliyeti düşürür.
  • VPC Endpoints: S3 ve DynamoDB için Gateway Endpoint ücretsizdir ve NAT Gateway trafiğini azaltır. Interface Endpoint’ler ücretlidir ama NAT Gateway’den genellikle ucuzdur.
  • Elastic IP: Kullanılmayan Elastic IP’ler ücretlendirilir. NAT Gateway ile ilişkilendirilmiş EIP’ler ücretsizdir ama atanmamış EIP’ler ücret kesilir.

S3 için VPC Endpoint eklemek trafiği AWS backbone üzerinden geçirir ve NAT Gateway ücretinden tasarruf sağlar:

# S3 için Gateway VPC Endpoint oluştur (ücretsiz)
aws ec2 create-vpc-endpoint 
  --vpc-id $VPC_ID 
  --service-name com.amazonaws.us-east-1.s3 
  --route-table-ids $PRIVATE_RT_A $PRIVATE_RT_B 
  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=s3-endpoint}]'

Sonuç

Doğru tasarlanmış bir VPC, güvenli ve ölçeklenebilir bir AWS altyapısının temelidir. Bu yazıda ele aldığımız multi-AZ, çok katmanlı subnet mimarisi production için geçerli bir başlangıç noktasıdır. Özetlemek gerekirse:

  • VPC CIDR bloğunu geniş tut, /16 ile başla
  • Her uygulama katmanı için ayrı subnet kullan ve bunları birden fazla AZ’a yay
  • Public subnet’lere sadece gerçekten internet’e açık olması gereken kaynakları koy
  • Security group’ları en az ayrıcalık prensibiyle yapılandır, kaynakları IP yerine SG ID ile referansla
  • Flow Logs’u aktif et, ileride seni kurtaracak
  • S3 ve diğer AWS servisleri için VPC Endpoint kullanarak hem güvenliği artır hem maliyet düşür
  • Tüm kurulum adımlarını script haline getir, bir sonraki ortamı dakikalar içinde kurabilirsin

Bu altyapıyı Terraform veya CloudFormation ile kod olarak yönetmek, özellikle birden fazla environment varsa çok daha sağlıklı bir yaklaşımdır. Infrastructure as Code konusunu ayrı bir yazıda ele alacağım.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir