Skip to content
~/sph.sh

AWS Fargate 103: Size Saatler Kazandıracak Production Dersleri

Fargate'i ölçekte çalıştırırken karşılaşılan production olayları. Memory leak'ler, ENI limitleri, subnet arızaları ve işe yarayan debug teknikleri.

Fargate kurulumunuz konusunda kendinize güvendiğiniz, monitoring dashboard'larında her şeyin yeşil göründüğü, sonra da hiç düşünmediğiniz kör noktalar keşfettiğiniz anları bilir misiniz? Production gerçeğine hoş geldiniz.

Fargate workload'larını ölçekte çalıştırmak, tutorial'larda veya basit implementasyonlarda karşılaşmadığınız zorlukları ortaya çıkarır. Memory leak'ler yavaş yavaş task'ları öldürür, ENI limitleri ani scaling sırasında deployment'ları bloke eder—bu bölüm her iki senaryoya da pratik debug yaklaşımları sunuyor. Fargate serimizin önceki bölümlerinde (101, 102) temelleri ve gelişmiş pattern'leri ele aldık. Burada değerli dersler öğreten production senaryoları ve etkili olduğu kanıtlanmış debug yaklaşımları ile çözümleri paylaşıyorum. ENI limitleri ve subnet IP tükenmesi gibi sorunlar sadece peak trafikte ortaya çıkar—bu bölüm bu tuzakları önceden planlamana yardımcı olacak. Her Fargate task'ı bir ENI tüketir; VPC subnet'lerindeki IP aralığı sınırlıdır. Yüksek task sayılarıyla bu limitlere çarpmak an meselesidir. CloudWatch custom metric ile ENI kullanımını izlemek, sorunu production'a varmadan tespit etmeni sağlar. Seride bir sonraki bölüm olan 104'te Infrastructure-as-Code deployment pattern'lerini inceleyeceğiz.

Black Friday'in Büyük ENI Kıtlığı

Kurulum: E-ticaret sitesi, Black Friday trafiği normalin 10 katı bekleniyor. Fargate auto-scaling yapılandırılmış, load test geçti, herkes iyi hissediyor.

Ne Oldu: Şükran Günü 23:47'de, task'larımız şu gizemli hatayla başarısız olmaya başladı:

ResourcesNotReady: The ENI allocation could not be completed

Fargate task'larımız PENDING durumunda takılı kaldı. Yeni deployment'lar başarısız oldu. Auto-scaling ölçeklenmiyordu. Dashboard her şeyi yeşil gösteriyordu, sadece küçük bir detay dışında: VPC'mizdeki mevcut ENI'lar sıfıra inmişti.

Araştırma

Her Fargate task'ının kendi ENI'ya ihtiyacı olduğunu hatırlıyor musunuz? Peki, 3 availability zone'da 200 task'ımız çalışıyordu ve AWS'nin VPC başına ENI limitleri var. us-east-1'de default VPC için:

  • Default ENI limiti: VPC başına 5.000
  • Her Fargate task: 1 ENI
  • Her RDS instance: 1 ENI
  • VPC'deki her Lambda: ENI pool'u paylaşır
  • Her ELB: Birden fazla ENI

Limitimizi kontrol ettik:

bash
aws ec2 describe-account-attributes \  --attribute-names max-instances
# Ama gerçek limit burada:aws service-quotas get-service-quota \  --service-code ec2 \  --quota-code L-0263D0A3  # VPC başına ENI

4.847 ENI'daydık. "Load testing"imiz ENI tüketen diğer servisleri hesaba katmamıştı.

Çözüm (Acil Durum)

Anında çözüm (03:15):

bash
# Önce development VPC'deki task'ları öldüraws ecs update-service \  --cluster development \  --service api \  --desired-count 0
# Acil quota artırımı talep et (AWS Support)aws support create-case \  --subject "URGENT: ENI quota increase needed - production impact" \  --service-code "service-quota-increase"

Gerçek çözüm:

  1. Birden fazla VPC: Workload'ları dev, staging ve prod VPC'lerine böl
  2. ENI monitoring: ENI kullanımını takip eden CloudWatch custom metric
  3. Doğru boyutlandırma: Fazla provision edilmiş task'ları azalt
  4. Lambda optimizasyonu: Mümkün olan Lambda'ları VPC dışına taşı
typescript
// ENI monitoring Lambdaexport const monitorENIs = async () => {  const ec2 = new AWS.EC2();  const cloudWatch = new AWS.CloudWatch();    const enis = await ec2.describeNetworkInterfaces().promise();  const inUse = enis.NetworkInterfaces?.length || 0;    await cloudWatch.putMetricData({    Namespace: 'Custom/VPC',    MetricData: [{      MetricName: 'ENIsInUse',      Value: inUse,      Unit: 'Count'    }]  }).promise();};

Öğrenilen dersler:

  • Load testing sadece uygulamanızı değil, tüm altyapı bileşenlerini içermeli
  • ENI limitleri servis başına değil, VPC başına
  • AWS Support sabah 3'te şaşırtıcı derecede hızlı yanıt veriyor

Hain Subnet

Kurulum: Üç private subnet'te Multi-AZ Fargate deployment'ı. Aylarca sorunsuz çalışıyor.

Ne Oldu: Salı sabahı, task'larımızın %40'ı aralıklı bağlantı sorunları göstermeye başladı. Bazı HTTP istekleri başarılı oluyordu, diğerleri 30 saniye sonra timeout'a düşüyordu.

Garip olan kısım? Sadece belirli bir subnet'teki (us-east-1a) task'lar etkileniyordu.

Araştırma Süreci

İlk olarak, açık kontroller:

bash
# Task sağlığını kontrol etaws ecs list-tasks --cluster production --service-name apiaws ecs describe-tasks --cluster production --tasks task-abc123
# Network interface'leri kontrol etaws ec2 describe-network-interfaces \  --filters "Name=subnet-id,Values=subnet-12345" \  --query 'NetworkInterfaces[*].[NetworkInterfaceId,Status,PrivateIpAddress]'

Task'lar sağlıklı görünüyordu. Network interface'leri bağlı ve aktifti. Ama bir şeyler yanlıştı.

Çıkış noktası: VPC Flow Logs'u etkinleştirdik ve dumanı çıkan silahı bulduk:

bash
# Problem subnet için VPC Flow Logs'u etkinleştiraws ec2 create-flow-logs \  --resource-type Subnet \  --resource-ids subnet-12345 \  --traffic-type ALL \  --log-destination-type cloud-watch-logs \  --log-group-name /aws/vpc/flowlogs

Flow log'lar paketlerin subnet'imizden çıktığını ama hedeflerine hiç ulaşmadığını gösterdi. Dönüş paketleri bir yerde drop ediliyordu.

Gerçek Suçlu

Ağ ekibimizin o sabah erkenden subnet'in route table'ını değiştirdiği ortaya çıktı. NAT gateway route'unu 0.0.0.0/0 → nat-gateway-123'ten 0.0.0.0/0 → nat-gateway-456'ya değiştirmişlerdi, Fargate task'larının orada çalıştığını fark etmeden.

Yeni NAT gateway farklı bir AZ'deydi ve farklı security group kurallarına sahipti. Klasik.

Çözüm:

bash
# Subnet ile ilişkili route table'ı kontrol etaws ec2 describe-route-tables \  --filters "Name=association.subnet-id,Values=subnet-12345"
# Route'ları doğrulaaws ec2 describe-route-tables --route-table-ids rtb-abc123 \  --query 'RouteTables[*].Routes[*].[DestinationCidrBlock,GatewayId,State]'
# Route'u düzelt (orijinal NAT gateway'e geri dön)aws ec2 replace-route \  --route-table-id rtb-abc123 \  --destination-cidr-block 0.0.0.0/0 \  --nat-gateway-id nat-gateway-123

Öğrenilen dersler:

  • Route değişikliklerini her zaman production dışında test edin
  • Network debugging için VPC Flow Logs dostunuz
  • Hangi route table'ların hangi servislere hizmet verdiğini dokümante edin
  • Route table değişiklikleri için monitoring kurun

Memory Leak Gizemi (SSH Yok Edisyonu)

Kurulum: Fargate'te çalışan Node.js API, task başına 2GB memory limiti. Haftalarca sorunsuz çalıştı.

Ne Oldu: Memory kullanımı 3-4 saat boyunca yavaşça artıyor, sonra task'lar OOM kill ediliyor. Memory kullanım grafikleri kayak pistine benziyordu.

Ama işin can alıcı yanı: Container'a debug için SSH yapmanın yolu yoktu.

Araştırma Cephaneliği

SSH yapamayacağımız için yaratık olmamız gerekiyor:

1. ECS Exec (kurtarıcımız):

bash
# Önce service'te etkinleştiraws ecs update-service \  --cluster production \  --service api \  --enable-execute-command
# Sonra çalışan task'a bağlanaws ecs execute-command \  --cluster production \  --task task-abc123 \  --container api \  --interactive \  --command "/bin/bash"
# Container içinde memory kullanımını kontrol et> ps aux --sort=-%mem | head -20> cat /proc/meminfo> pmap -x 1  # PID 1'in memory map'i

2. Application-level monitoring:

javascript
// Node.js uygulamanıza ekleyinconst express = require('express');const app = express();
// Memory monitoring endpointapp.get('/debug/memory', (req, res) => {  const used = process.memoryUsage();  const stats = {    rss: Math.round(used.rss / 1024 / 1024 * 100) / 100,      // MB    heapTotal: Math.round(used.heapTotal / 1024 / 1024 * 100) / 100,    heapUsed: Math.round(used.heapUsed / 1024 / 1024 * 100) / 100,    external: Math.round(used.external / 1024 / 1024 * 100) / 100,    arrayBuffers: Math.round(used.arrayBuffers / 1024 / 1024 * 100) / 100  };    res.json(stats);});
// Heap snapshot endpoint (ekstrem debugging için)app.get('/debug/heapdump', (req, res) => {  const heapdump = require('heapdump');  const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;    heapdump.writeSnapshot(filename, (err, filename) => {    if (err) {      res.status(500).send(err.message);    } else {      res.download(filename);    }  });});

3. Dedektiflik çalışması:

ECS Exec kullanarak debugging araçları yükledik ve HTTP client'ımızın bağlantıları düzgün kapatmadığını bulduk:

bash
# Container içinde> npm install -g clinic> clinic doctor --on-port 8080 -- node index.js &> curl http://localhost:8080/debug/memory
# Açık file descriptor'ları kontrol et> ls -la /proc/1/fd | wc -l> lsof -p 1 | grep TCP

Bingo! CLOSE_WAIT durumunda binlerce TCP bağlantısı.

Kök Sebep

Node.js HTTP client kodumuz yeterince masum görünüyordu:

javascript
// Problemli kodconst axios = require('axios');
async function callExternalAPI() {  const response = await axios.get('https://api.example.com/data');  return response.data;}

Ama connection pooling veya timeout'ları düzgün yapılandırmıyorduk. Her istek temizlenmeyen yeni bağlantılar yaratıyordu.

Çözüm:

javascript
// Düzgün yapılandırmalı düzeltilmiş versiyonconst axios = require('axios');const https = require('https');const http = require('http');
// Connection pooling'i yapılandırconst httpAgent = new http.Agent({  keepAlive: true,  maxSockets: 50,  timeout: 5000,});
const httpsAgent = new https.Agent({  keepAlive: true,  maxSockets: 50,  timeout: 5000,});
const axiosInstance = axios.create({  httpAgent,  httpsAgent,  timeout: 10000, // 10 saniye});
// Graceful shutdownprocess.on('SIGTERM', () => {  httpAgent.destroy();  httpsAgent.destroy();});
async function callExternalAPI() {  const response = await axiosInstance.get('https://api.example.com/data');  return response.data;}

Öğrenilen dersler:

  • ECS Exec containerized debugging için paha biçilmez
  • HTTP client'ları production'da her zaman düzgün yapılandırın
  • Sadece memory değil, file descriptor'ları da izleyin
  • "Basit" HTTP client'lar için bile connection pool'lar önemli

30 Saniyelik Connection Timeout Hayaleti

Kurulum: İki Fargate servisi arasında internal servis-servis iletişimi. %99 zaman sorunsuz çalışıyor.

Ne Oldu: Rastgele, isteklerin yaklaşık %1'i tam 30 saniye askıda kalıp sonra connection timeout ile başarısız oluyordu.

Pattern tamamen rastgeleydi. Yük, günün saati veya deployment geçmişi ile korelasyon yoktu.

Debugging Destanı

Network katman araştırması:

bash
# VPC Flow Logs analiziaws logs filter-log-events \  --log-group-name /aws/vpc/flowlogs \  --start-time 1645564800000 \  --filter-pattern "REJECT"
# Security group kuralları denetimiaws ec2 describe-security-groups \  --group-ids sg-12345 \  --query 'SecurityGroups[*].{GroupId:GroupId,IpPermissions:IpPermissions}'

Security group'lar iyi görünüyordu. Flow log'lar paketlerin normal akışını gösteriyordu.

Application katman araştırması:

javascript
// Detaylı bağlantı takibi eklendiconst net = require('net');const original_connect = net.Socket.prototype.connect;
net.Socket.prototype.connect = function(...args) {  const startTime = Date.now();  console.log(`[${new Date().toISOString()}] Starting connection to ${args[0]?.host || args[0]?.path}`);    const result = original_connect.apply(this, args);    this.on('connect', () => {    const duration = Date.now() - startTime;    console.log(`[${new Date().toISOString()}] Connected after ${duration}ms`);  });    this.on('error', (err) => {    const duration = Date.now() - startTime;    console.log(`[${new Date().toISOString()}] Connection error after ${duration}ms:`, err.message);  });    return result;};

Çıkış Noktası

Log'lar ilginç bir şey gösterdi: başarılı bağlantılar 2-5ms sürüyordu, ama askıda kalanlar tam 30.000ms. Bu rastgele değil - bu bir timeout.

Sonra pattern'i fark ettik: sadece her iki servis aynı availability zone'da olduğunda ve bağlantı load balancer'dan geçtiğinde oluyordu.

Problem: AWS ALB'nin aynı AZ'deki bağlantıların ara sıra load balancer altyapısı üzerinden geri dönebileceği bilinen bir tuhaflığı var ve bu gecikmelere neden oluyor.

Çözüm (birden fazla strateji):

  1. Aynı AZ için direkt servis iletişimi:
javascript
// AZ awareness ile service discoveryconst AWS = require('aws-sdk');const ecs = new AWS.ECS();
async function getServiceEndpoints() {  const tasks = await ecs.listTasks({    cluster: 'production',    serviceName: 'target-service'  }).promise();    const taskDetails = await ecs.describeTasks({    cluster: 'production',    tasks: tasks.taskArns  }).promise();    return taskDetails.tasks.map(task => ({    ip: task.attachments[0].details.find(d => d.name === 'privateIPv4Address').value,    az: task.availabilityZone,    port: 8080  }));}
// Akıllı routingasync function callService(endpoint, data) {  const currentAZ = process.env.AWS_AVAILABILITY_ZONE;  const endpoints = await getServiceEndpoints();    // Önce aynı AZ direkt bağlantıyı dene  const sameAZEndpoint = endpoints.find(e => e.az === currentAZ);  if (sameAZEndpoint) {    try {      return await axios.post(`http://${sameAZEndpoint.ip}:${sameAZEndpoint.port}${endpoint}`, data);    } catch (error) {      // Load balancer'a geri dön      return await axios.post(`https://internal-service.example.com${endpoint}`, data);    }  }    // Cross-AZ için load balancer kullan  return await axios.post(`https://internal-service.example.com${endpoint}`, data);}
  1. Connection timeout ayarlaması:
javascript
const axiosInstance = axios.create({  timeout: 5000,  // 30s beklemek yerine çabuk başarısız ol  httpsAgent: new https.Agent({    timeout: 2000,  // Connection timeout    keepAlive: true,  })});

Öğrenilen dersler:

  • ALB'ler aynı AZ iletişimi için beklenmedik gecikme ekleyebilir
  • Service discovery direkt iletişim pattern'lerini mümkün kılar
  • Her zaman SLA'nızdan daha kısa connection timeout'lar uygulayın
  • Load balancer'lar her zaman en hızlı yol değildir

Deploy Olmayan Deployment

Kurulum: CodeDeploy kullanarak standart blue-green deployment. Daha önce yüzlerce kez çalıştı.

Ne Oldu: Yeni deployment saatlerce %50'de takılı kaldı. Task'ların yarısı yeni versiyon, yarısı eski versiyon çalışıyordu. CodeDeploy dashboard "In Progress" gösteriyordu, hata mesajı yoktu.

Auto-rollback tetiklenmiyordu çünkü teknik olarak hiçbir şey "başarısız olmuyordu".

Araştırma

CodeDeploy log'ları işe yaramadı:

bash
aws deploy get-deployment --deployment-id d-XXXXXXXXX# Status: InProgress, hata bilgisi yok
aws logs filter-log-events \  --log-group-name /aws/codedeploy-agent \  --start-time $(date -d '1 hour ago' +%s)000

ECS service event'leri gerçek hikayeyi gösterdi:

bash
aws ecs describe-services \  --cluster production \  --services api \  --query 'services[0].events[0:10]'

Event'ler şunu gösteriyordu:

"(service api) failed to launch a task with (error ECS was unable to assume role...)"

Kök Sebep

Task execution role'ümüz başka bir ekip tarafından alakasız bir servis için değiştirilmişti ve yanlışlıkla ECS'nin role'ü assume etmesine izin veren trust relationship'i kaldırmışlardı.

Role policy şu şekildeydi:

json
{  "Version": "2012-10-17",  "Statement": [    {      "Effect": "Allow",      "Principal": {        "Service": "ec2.amazonaws.com"  // YANLIŞ!      },      "Action": "sts:AssumeRole"    }  ]}

Şöyle olmalıydı:

json
{  "Version": "2012-10-17",  "Statement": [    {      "Effect": "Allow",      "Principal": {        "Service": "ecs-tasks.amazonaws.com"  // DOĞRU      },      "Action": "sts:AssumeRole"    }  ]}

Çözüm:

bash
# Role trust policy'sini kontrol etaws iam get-role --role-name fargate-task-execution-role \  --query 'Role.AssumeRolePolicyDocument'
# Güncelleaws iam update-assume-role-policy \  --role-name fargate-task-execution-role \  --policy-document file://trust-policy.json

Önleme stratejisi:

typescript
// Otomatik role validasyonuexport const validateTaskRoles = async () => {  const iam = new AWS.IAM();    const role = await iam.getRole({    RoleName: 'fargate-task-execution-role'  }).promise();    const trustPolicy = JSON.parse(decodeURIComponent(role.Role.AssumeRolePolicyDocument));  const ecsService = trustPolicy.Statement.some(statement =>    statement.Principal?.Service === 'ecs-tasks.amazonaws.com'  );    if (!ecsService) {    await sendAlert('Task execution role missing ECS trust relationship!');    return false;  }    return true;};

Öğrenilen dersler:

  • ECS service event'leri CodeDeploy log'larından daha detaylı
  • Role trust policy'leri kırılgan ve monitoring gerektirir
  • Blue-green deployment'lar limbo'da takılabilir
  • İşler gizemli şekilde çalışmayı durdurduğunda her zaman IAM'ı kontrol edin

Gerçekten İşe Yarayan Debug Araç Kutusu

Tüm bu olaylardan sonra, bize sayısız saat kazandıran debugging toolkit'i:

1. Ultimate Fargate Debug Container

dockerfile
FROM node:18-alpineRUN apk add --no-cache \    curl \    wget \    netcat-openbsd \    bind-tools \    tcpdump \    strace \    htop \    iotop \    lsof \    procps \    net-tools
# Uygulamanızı ekleyinCOPY . /appWORKDIR /app
# Debug endpoint'leriRUN npm install express heapdump clinic

2. Monitoring Stack

typescript
// Detaylı teşhislerle health check endpointapp.get('/health/detailed', async (req, res) => {  const health = {    timestamp: new Date().toISOString(),    uptime: process.uptime(),    memory: process.memoryUsage(),    cpu: process.cpuUsage(),    connections: {      active: await getActiveConnections(),      waiting: await getWaitingConnections()    },    environment: {      nodeVersion: process.version,      availabilityZone: process.env.AWS_AVAILABILITY_ZONE || 'unknown',      region: process.env.AWS_REGION || 'unknown'    }  };    res.json(health);});
async function getActiveConnections() {  return new Promise((resolve) => {    require('child_process').exec('netstat -an | grep ESTABLISHED | wc -l',       (error, stdout) => {        resolve(parseInt(stdout.trim()) || 0);      }    );  });}

3. Otomatik Olay Müdahalesi

yaml
# Gerçekten yardım eden CloudWatch alarm'larıENIUtilizationAlarm:  Type: AWS::CloudWatch::Alarm  Properties:    AlarmName: High-ENI-Utilization    MetricName: ENIsInUse    Namespace: Custom/VPC    Statistic: Maximum    Period: 300    EvaluationPeriods: 2    Threshold: 4500  # 5000 limitinin %90'ı    ComparisonOperator: GreaterThanThreshold    AlarmActions:      - !Ref SNSTopic
MemoryUtilizationAlarm:  Type: AWS::CloudWatch::Alarm    Properties:    AlarmName: Fargate-Memory-High    MetricName: MemoryUtilized    Namespace: ECS/ContainerInsights    Statistic: Average    Period: 300    EvaluationPeriods: 3    Threshold: 80  # %80 memory kullanımı    ComparisonOperator: GreaterThanThreshold

Fargate Debugging'in Evrensel Yasaları

Tüm bu maceralardan sonra, doğru olan pattern'ler:

  1. Task'lar başlamadığında: ENI limitleri, security group'lar ve IAM role'leri kontrol et (bu sırayla)

  2. Task'lar yavaş olduğunda: Genelde network'tür (route table'lar, NAT gateway'ler, DNS)

  3. Memory sürekli arttığında: Her zaman connection pooling veya event listener'lar

  4. Deployment'lar asılı kaldığında: Deployment log'lar değil, service event'leri kontrol et

  5. İsteklerin %1'i başarısız olduğunda: Load balancer tuhaflıkları veya cross-AZ sorunları ara

  6. Hiçbir şey mantıklı olmadığında: VPC Flow Logs ve ECS Exec'i etkinleştir

Gerçek ders? Fargate bir sürü altyapı karmaşıklığını kaldırır, ama işler ters gittiğinde, temel AWS networking ve compute primitive'lerini anlamanız gerekir. Abstraction sızıntılıdır ve production her zaman sızıntıları bulur.

Bu debugging tekniklerini elinizin altında bulundurun. İnanın, bir gün sabah 3'te ihtiyacınız olacak ve o zaman, önceden kurduğunuz her monitoring endpoint ve teşhis aracı için minnettarlık duyacaksınız.

Bir dahaki sefere biri size serverless container'ların "kur ve unut" olduğunu söylediğinde, onlara bu seriyi gösterin. Production'ın başka planları var, ama artık onlara hazırsınız.

AWS Fargate Derinlemesine Serisi

AWS Fargate'e temellerden production'a tam rehber. Gerçek dünya deneyimleri ile serverless container'ları, maliyet optimizasyonu, debugging tekniklerini ve Infrastructure-as-Code deployment pattern'lerini öğrenin.

İlerleme3/4 yazı tamamlandı

İlgili Yazılar