AWS کلاؤڈ لاگت کی نگرانی، انتباہات، اور اصلاح: ڈویلپرز کے لیے ایک رہنما

ہماری انجینئرنگ ٹیم کی ہر ماہ عام گفتگو ہوتی ہے۔ کسی نے مجھے اپنے AWS بل کا اسکرین شاٹ فارورڈ کیا۔ یہ تعداد گزشتہ ماہ سے زیادہ ہے۔ ہر کوئی سر ہلاتا ہے اور اتفاق کرتا ہے کہ اسے نیچے جانے کی ضرورت ہے۔ کچھ بھی ٹھوس فیصلہ نہیں کیا جاتا ہے، اور سائیکل دہرایا جاتا ہے.

اس گائیڈ کو اس سائیکل کو ختم کرنے کے لیے ڈیزائن کیا گیا ہے تاکہ اسے مزید مفید چیز سے تبدیل کیا جا سکے: ایک منظم، سروس کے لیے مخصوص نقطہ نظر جو آپ کو یہ جاننے میں مدد کرتا ہے کہ آپ کیا خرچ کر رہے ہیں، آپ اسے کیوں خرچ کر رہے ہیں، بل آنے سے پہلے مسائل کی نشاندہی کریں، اور قیاس کے بغیر آپ کی رقم کی بچت کریں۔

یہ منظم ہے تاکہ آپ دوبارہ اس کا حوالہ دے سکیں۔ ہر حصہ اپنے طور پر مکمل ہوتا ہے۔ اگر یہ آپ کا موجودہ مسئلہ ہے، تو آپ براہ راست RDS سیکشن پر جا سکتے ہیں، یا اگر آپ شروع سے FinOps پریکٹس بنا رہے ہیں، تو آپ شروع سے آخر تک گائیڈ کی پیروی کر سکتے ہیں۔ آپ کوئی بھی کمانڈ چلا سکتے ہیں اور کسی بھی اسکرپٹ کو تعینات کر سکتے ہیں۔

انڈیکس

جو آپ سیکھیں گے۔

  • ایتھینا کا استعمال کرتے ہوئے لاگت اور استعمال کی رپورٹ کے سوالات کیسے مرتب کیے جائیں، تمام سنجیدہ لاگت کے تجزیہ کی بنیاد۔

  • پانچ ڈیش بورڈز جن کی ہر انجینئرنگ ٹیم کی ضرورت ہوتی ہے ان سوالات کے ساتھ بنائے گئے ہیں جنہیں آپ آج چلا سکتے ہیں۔

  • انتباہی تھکاوٹ کا سبب بنے بغیر لاگت میں اضافے کو پکڑنے کے لیے تین درجے کی انتباہی حکمت عملی

  • EC2، S3، Lambda، RDS، DynamoDB، اور ڈیٹا کی منتقلی کے لیے سروس کے لیے مخصوص آپٹیمائزیشن پلے بکس

  • پہلے مہینے میں قابل پیمائش لاگت کی بچت حاصل کرنے کے لیے ایک مخصوص 30 دن کا سپرنٹ۔

آئیے اسے شروع سے بنائیں۔

شرائط

اس گائیڈ پر عمل کرنے سے پہلے، آپ کو ضرورت ہو گی:

علم:

  • AWS خدمات (EC2، S3، RDS، Lambda، اور VPC) کے بارے میں کام کرنے کا علم۔

  • آرام دہ اور پرسکون پڑھنے Python اور SQL

  • IAM کی پالیسیاں اور کردار کیسے کام کرتے ہیں اس کی بنیادی سمجھ

پوزیشن:

  • بلنگ تک رسائی کے ساتھ AWS اکاؤنٹ۔ IAM صارف یا اس کردار کے لیے درکار ہے جس کے ساتھ آپ کام کر رہے ہیں۔ ce:GetCostAndUsage، ec2:Describe*، rds:Describe*اور s3:GetBucketLifecycleConfiguration اتھارٹی

  • AWS CLI v2 کنفیگریشن

  • ایتھینا تک رسائی حاصل کریں (حصہ 1 میں CUR سوالات کے لیے)

ترتیب:

  • Cost Explorer کو فعال کریں اگر یہ پہلے سے فعال نہیں ہے۔ یہ مفت ہے اور اس گائیڈ میں زیادہ تر کمانڈز کے لیے ضروری ہے۔
aws ce enable-cost-explorer --region us-east-1
  • آپ کو لاگت اور استعمال کی رپورٹ (CUR) کو ترتیب دینے اور اسے S3 بالٹی میں برآمد کرنے کی ضرورت ہے۔ اگر آپ نے اسے پہلے سے ترتیب نہیں دیا ہے، تو AWS CUR سیٹ اپ گائیڈ اس عمل میں آپ کی رہنمائی کرتا ہے۔ براہ کرم اپنی پہلی ڈیٹا فائل بنانے کے لیے سیٹ اپ کے 24 گھنٹے بعد رپورٹس فراہم کریں۔

حصہ 1: نگرانی – جانیں کہ ہر ڈالر کہاں جا رہا ہے۔

1.1 لاگت اور استعمال کی رپورٹ – معلومات کا ذریعہ

لاگت ایکسپلورر سروس کی سطح کا مجموعہ دکھاتا ہے۔ اگرچہ رجحان کی شناخت کے لیے مفید ہے، لیکن یہ بنیادی وجہ کے تجزیہ کے لیے کافی نہیں ہے۔ اگر آپ کو یہ جاننے کی ضرورت ہے کہ $12,000/ماہ کے آئٹم کے لیے کون سا مخصوص وسیلہ ذمہ دار ہے، تو آپ کو قیمت اور استعمال کی رپورٹوں کی ضرورت ہوگی جو ایتھینا کے ذریعے پوچھے گئے ہیں۔

اپنے CUR ڈیٹا کے لیے ایتھینا ٹیبل بنائیں۔

-- Run this once in Athena after your CUR starts generating data
-- Replace 'your-cur-bucket' and 'your-prefix' with your actual values

CREATE EXTERNAL TABLE IF NOT EXISTS cur_database.billing (
    bill_billing_period_start_date  STRING,
    bill_payer_account_id           STRING,
    line_item_usage_start_date      STRING,
    line_item_resource_id           STRING,
    line_item_usage_type            STRING,
    line_item_usage_amount          DOUBLE,
    line_item_unblended_cost        DOUBLE,
    product_servicecode             STRING,
    product_instance_type           STRING,
    product_region                  STRING,
    resource_tags_user_environment  STRING,
    resource_tags_user_team         STRING,
    resource_tags_user_service      STRING,
    resource_tags_user_owner        STRING
)
PARTITIONED BY (year STRING, month STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LOCATION 's3://your-cur-bucket/your-prefix/'
TBLPROPERTIES ('skip.header.line.count'='1');

MSCK REPAIR TABLE cur_database.billing;

یہاں تین سوالات ہیں جو آپ کو پہلے دن چلانا چاہئے:

-- Query 1: Top 20 resources by cost this month
-- Run this first. It tells you where to focus.
SELECT
    line_item_resource_id,
    product_servicecode,
    resource_tags_user_team     AS team,
    resource_tags_user_service  AS service,
    SUM(line_item_unblended_cost) AS total_cost_usd
FROM cur_database.billing
WHERE line_item_usage_start_date >= DATE_FORMAT(
    DATE_TRUNC('month', CURRENT_DATE), '%Y-%m-%d'
)
  AND line_item_unblended_cost > 0
GROUP BY 1, 2, 3, 4
ORDER BY total_cost_usd DESC
LIMIT 20;
-- Query 2: Week-over-week cost growth by service
-- Identifies which services are growing fastest — these need investigation
WITH weekly AS (
    SELECT
        DATE_TRUNC('week', CAST(line_item_usage_start_date AS DATE)) AS week,
        product_servicecode,
        SUM(line_item_unblended_cost) AS cost
    FROM cur_database.billing
    WHERE line_item_usage_start_date >=
          DATE_FORMAT(DATE_ADD('day', -42, CURRENT_DATE), '%Y-%m-%d')
    GROUP BY 1, 2
)
SELECT
    curr.product_servicecode AS service,
    ROUND(prev.cost, 2)      AS prev_week_cost,
    ROUND(curr.cost, 2)      AS curr_week_cost,
    ROUND(
        100.0 * (curr.cost - prev.cost) / NULLIF(prev.cost, 0),
        1
    ) AS pct_change
FROM weekly curr
JOIN weekly prev
  ON curr.product_servicecode = prev.product_servicecode
  AND curr.week = DATE_ADD('week', 1, prev.week)
WHERE curr.week = DATE_TRUNC('week', CURRENT_DATE)
  AND ABS((curr.cost - prev.cost) / NULLIF(prev.cost, 0)) > 0.20
ORDER BY pct_change DESC;
-- Query 3: Resources running with no usage (candidates for shutdown)
-- Finds resources that incurred cost but had zero usage quantity
-- in the past 7 days — strong signal for idle or orphaned resources
SELECT
    line_item_resource_id,
    product_servicecode,
    resource_tags_user_owner    AS owner,
    resource_tags_user_team     AS team,
    SUM(line_item_unblended_cost) AS cost_past_7_days
FROM cur_database.billing
WHERE line_item_usage_start_date >=
      DATE_FORMAT(DATE_ADD('day', -7, CURRENT_DATE), '%Y-%m-%d')
  AND line_item_usage_amount = 0
  AND line_item_unblended_cost > 5
GROUP BY 1, 2, 3, 4
ORDER BY cost_past_7_days DESC
LIMIT 30;

1.2 5 ضروری لاگت والے ڈیش بورڈز

یہ پانچ خیالات زیادہ تر انجینئرنگ ٹیموں کی نگرانی کی ضروریات کو پورا کرتے ہیں۔ ہر ایک سوالات پر بنایا گیا ہے جو تھرڈ پارٹی ٹولز کی ضرورت کے بغیر باکس سے باہر چل سکتا ہے۔

ڈیش بورڈ 1: خلاصہ (ہفتہ وار لیڈر شپ اپ ڈیٹس کے لیے)

# executive_summary.py
import boto3
from datetime import datetime, timedelta

ce = boto3.client('ce')


def weekly_summary():
    today     = datetime.now()
    start_mtd = today.replace(day=1).strftime('%Y-%m-%d')
    today_str = today.strftime('%Y-%m-%d')

    # Month-to-date spend
    mtd = ce.get_cost_and_usage(
        TimePeriod={'Start': start_mtd, 'End': today_str},
        Granularity='MONTHLY',
        Metrics=['UnblendedCost']
    )
    mtd_spend = float(
        mtd['ResultsByTime'][0]['Total']['UnblendedCost']['Amount']
    )

    # End-of-month forecast
    forecast = ce.get_cost_forecast(
        TimePeriod={
            'Start': today_str,
            'End':   (today.replace(day=28) + timedelta(days=4)).replace(day=1).strftime('%Y-%m-%d'),
        },
        Metric="UNBLENDED_COST",
        Granularity='MONTHLY'
    )
    eom_forecast = float(forecast['Total']['Amount']) + mtd_spend

    # Top 5 services
    by_service = ce.get_cost_and_usage(
        TimePeriod={'Start': start_mtd, 'End': today_str},
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
    )
    services = sorted(
        [
            (g['Keys'][0], float(g['Metrics']['UnblendedCost']['Amount']))
            for g in by_service['ResultsByTime'][0]['Groups']
        ],
        key=lambda x: x[1],
        reverse=True
    )[:5]

    print(f"\n{'─'*48}")
    print(f"  AWS Cost Summary — {today.strftime('%B %Y')}")
    print(f"{'─'*48}")
    print(f"  Month-to-date:     ${mtd_spend:>12,.2f}")
    print(f"  End-of-month est:  ${eom_forecast:>12,.2f}")
    print(f"\n  Top 5 Services:")
    for name, cost in services:
        short = name.replace('Amazon ', '').replace('AWS ', '')
        print(f"    {short:<32} ${cost:>9,.2f}")
    print(f"{'─'*48}\n")


weekly_summary()

ڈیش بورڈ 2: ٹیم لاگت کا تجزیہ (انجینئرنگ لیڈز کے لیے)

# team_breakdown.py
import boto3
from datetime import datetime

ce = boto3.client('ce')


def team_breakdown():
    start = datetime.now().replace(day=1).strftime('%Y-%m-%d')
    end   = datetime.now().strftime('%Y-%m-%d')

    response = ce.get_cost_and_usage(
        TimePeriod={'Start': start, 'End': end},
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[
            {'Type': 'TAG',       'Key': 'Team'},
            {'Type': 'DIMENSION', 'Key': 'SERVICE'},
        ]
    )

    by_team = {}
    for group in response['ResultsByTime'][0].get('Groups', []):
        team_raw = group['Keys'][0]
        team     = team_raw.replace('Team$', '') if team_raw else 'untagged'
        service  = group['Keys'][1]
        cost     = float(group['Metrics']['UnblendedCost']['Amount'])

        if team not in by_team:
            by_team[team] = {'total': 0.0, 'by_service': {}}
        by_team[team]['total'] += cost
        by_team[team]['by_service'][service] = (
            by_team[team]['by_service'].get(service, 0.0) + cost
        )

    total_bill = sum(d['total'] for d in by_team.values())

    print(f"\n{'─'*58}")
    print(f"  Team Cost Breakdown — MTD {datetime.now().strftime('%Y-%m-%d')}")
    print(f"  Total: ${total_bill:,.2f}")
    print(f"{'─'*58}")

    for team, data in sorted(by_team.items(), key=lambda x: x[1]['total'], reverse=True):
        pct = (data['total'] / total_bill * 100) if total_bill else 0
        print(f"\n  {team:<20}  ${data['total']:>10,.2f}  ({pct:.1f}%)")
        top3 = sorted(data['by_service'].items(), key=lambda x: x[1], reverse=True)[:3]
        for svc, cost in top3:
            short = svc.replace('Amazon ', '').replace('AWS ', '')
            print(f"    └─ {short:<30} ${cost:>8,.2f}")

    print()


team_breakdown()

ڈیش بورڈ 3: فضلہ کا پتہ لگانا (ہفتہ وار صفائی کے جائزے کے لیے):

# waste_detector.py
import boto3
from datetime import datetime, timezone, timedelta

ec2  = boto3.client('ec2')
elbv2 = boto3.client('elbv2')
cw   = boto3.client('cloudwatch')


def detect_waste():
    report = {'items': [], 'total_monthly_waste': 0.0}

    # Unattached EBS volumes
    for vol in ec2.describe_volumes(
        Filters=[{'Name': 'status', 'Values': ['available']}]
    )['Volumes']:
        age  = (datetime.now(timezone.utc) - vol['CreateTime']).days
        cost = round(vol['Size'] * 0.08, 2)
        tags = {t['Key']: t['Value'] for t in vol.get('Tags', [])}
        report['items'].append({
            'type':         'Unattached EBS Volume',
            'id':           vol['VolumeId'],
            'detail':       f"{vol['Size']}GB — {age} days old",
            'owner':        tags.get('Owner', '—'),
            'monthly_cost': cost,
        })
        report['total_monthly_waste'] += cost

    # Unassociated Elastic IPs
    for addr in ec2.describe_addresses()['Addresses']:
        if 'AssociationId' not in addr:
            report['items'].append({
                'type':         'Unassociated Elastic IP',
                'id':           addr.get('AllocationId', ''),
                'detail':       addr['PublicIp'],
                'owner':        '—',
                'monthly_cost': 3.60,
            })
            report['total_monthly_waste'] += 3.60

    # Idle load balancers (fewer than 100 requests in 7 days)
    for lb in elbv2.describe_load_balancers()['LoadBalancers']:
        metrics = cw.get_metric_statistics(
            Namespace="AWS/ApplicationELB",
            MetricName="RequestCount",
            Dimensions=[{'Name': 'LoadBalancer',
                         'Value': lb['LoadBalancerArn'].split(':loadbalancer/')[-1]}],
            StartTime=datetime.now() - timedelta(days=7),
            EndTime=datetime.now(),
            Period=604800,
            Statistics=['Sum']
        )['Datapoints']
        total_requests = metrics[0]['Sum'] if metrics else 0

        if total_requests < 100:
            report['items'].append({
                'type':         'Idle Load Balancer',
                'id':           lb['LoadBalancerName'],
                'detail':       f"{int(total_requests)} requests in 7 days",
                'owner':        '—',
                'monthly_cost': 22.0,
            })
            report['total_monthly_waste'] += 22.0

    print(f"\n  Waste Detection Report — {datetime.now().strftime('%Y-%m-%d')}")
    print(f"  Estimated monthly waste: ${report['total_monthly_waste']:.2f}\n")

    for item in sorted(report['items'], key=lambda x: x['monthly_cost'], reverse=True)[:20]:
        print(f"  [{item['type']}]")
        print(f"    ID:     {item['id']}")
        print(f"    Detail: {item['detail']}")
        print(f"    Owner:  {item['owner']}")
        print(f"    Cost:   ${item['monthly_cost']:.2f}/month\n")

    return report


detect_waste()

1.3 ٹیگنگ کی حکمت عملی - تمام انتساب کی بنیاد

تمام لاگت کے انتساب ماڈلز ٹیگ پر منحصر ہیں۔ وہ ٹیمیں جو ٹیگ کرنا چھوڑ دیتی ہیں ڈیش بورڈز بناتی ہیں جو بغیر کسی وضاحت کے ٹوٹل دکھاتی ہیں۔ اصول یہ ہے کہ ٹیگنگ کو طریقہ کار کے بجائے ساختی بنایا جائے۔ اس کا مطلب یہ ہے کہ وہ Confluence کی یاد دہانیوں کے بجائے انفراسٹرکچر کوڈ کے ذریعے نافذ ہوتے ہیں۔

مطلوبہ ٹیگ سیٹ:

# terraform/variables.tf
variable "mandatory_tags" {
  description = "Tags applied to every resource in this account"
  type        = map(string)

  validation {
    condition = alltrue([
      contains(keys(var.mandatory_tags), "Environment"),
      contains(keys(var.mandatory_tags), "Team"),
      contains(keys(var.mandatory_tags), "Owner"),
      contains(keys(var.mandatory_tags), "Service"),
    ])
    error_message = "mandatory_tags must include Environment, Team, Owner, and Service."
  }
}

locals {
  common_tags = merge(var.mandatory_tags, {
    ManagedBy    = "terraform"
    LastModified = timestamp()
  })
}

resource "aws_instance" "api_server" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.medium"
  tags          = merge(local.common_tags, {Name = "api-server-${var.environment}"})
}

ہر ہفتے، ہم غیر ٹیگ شدہ وسائل تلاش کرتے ہیں اور ان کی رپورٹ کرتے ہیں۔

#!/usr/bin/env bash
# find_untagged.sh

echo "Untagged EC2 instances (missing Team tag):"
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query "Reservations[].Instances[?!not_null(Tags[?Key=='Team'].Value|[0])].[InstanceId,InstanceType,LaunchTime]" \
  --output table

echo "Untagged RDS instances:"
aws rds describe-db-instances \
  --query "DBInstances[?!not_null(TagList[?Key=='Team'].Value|[0])].DBInstanceIdentifier" \
  --output table

حصہ 2: اطلاعات — آپ کا بل آنے سے پہلے اسپائکس کو پکڑیں۔

ایک عام دریافت ٹائم لائن جس میں کوئی پیشگی اطلاع نہیں ہے: اخراجات میں اضافہ 5 تاریخ کو ہوتا ہے، ماہانہ بل 20 تاریخ کو آتا ہے، کوئی 22 تاریخ کو مطلع کرتا ہے، 23 تاریخ کو تفتیش شروع ہوتی ہے، اور آپ دو ہفتوں تک دعوی کردہ فضلہ کو بازیافت نہیں کر پائیں گے۔ پیشگی انتباہ کے ساتھ، تلاش گھنٹوں میں ہو جائے گی۔

2.1 3 درجے کی وارننگ کا ڈھانچہ

انتباہی تھکاوٹ اتنا ہی نقصان دہ ہے جتنا کوئی انتباہ نہیں۔ تین درجے کا ماڈل مختلف ردعمل کی توقعات کے ساتھ مختلف چینلز پر مختلف شدت کی سطحوں کو روٹ کرکے اعلی سگنل کو برقرار رکھتا ہے۔

# alert_router.py
import boto3
import json
import urllib.request
from enum import Enum

SLACK_INFO_WEBHOOK  = 'https://hooks.slack.com/services/INFO/WEBHOOK'
SLACK_ALERT_WEBHOOK = 'https://hooks.slack.com/services/ALERT/WEBHOOK'
SNS_CRITICAL_TOPIC  = 'arn:aws:sns:us-east-1:YOUR_ACCOUNT:cost-critical'


class AlertTier(Enum):
    INFO     = 1
    WARNING  = 2
    CRITICAL = 3


def route_alert(tier: AlertTier, subject: str, message: str):
    """Send an alert to the appropriate channel for its severity tier."""
    icons   = {AlertTier.INFO: ':information_source:',
               AlertTier.WARNING: ':warning:', AlertTier.CRITICAL: ':rotating_light:'}
    payload = {'text': f"{icons[tier]} *{subject}*\n{message}"}

    if tier == AlertTier.INFO:
        _post_slack(SLACK_INFO_WEBHOOK, payload)
    elif tier == AlertTier.WARNING:
        _post_slack(SLACK_ALERT_WEBHOOK, payload)
        _send_sns(SNS_CRITICAL_TOPIC, subject, f"WARNING: {message}")
    elif tier == AlertTier.CRITICAL:
        _post_slack(SLACK_ALERT_WEBHOOK, payload)
        _send_sns(SNS_CRITICAL_TOPIC, subject, f"CRITICAL: {message}")


def _post_slack(webhook: str, payload: dict):
    req = urllib.request.Request(
        webhook,
        data=json.dumps(payload).encode(),
        headers={'Content-Type': 'application/json'}
    )
    urllib.request.urlopen(req)


def _send_sns(topic_arn: str, subject: str, message: str):
    sns = boto3.client('sns')
    sns.publish(TopicArn=topic_arn, Subject=subject[:100], Message=message)

3 درجے اور جوابی مواد: ٹائر 1 INFO روزانہ لاگت کے خلاصے، ہفتہ وار رجحان کی رپورٹس، اور ٹیگ کی تعمیل کی تازہ کاریوں کے لیے سلیک انفارمیشن چینل پر جاتا ہے۔ کسی کارروائی کی ضرورت نہیں ہے۔

ٹائر 2 الرٹس سلیک نوٹیفکیشن چینل پر ڈیلیور کیے جاتے ہیں اور 75% سے زیادہ کے استعمال کے لیے ای میل، 25% کے ہفتہ وار اضافہ، اور بچت کے منصوبوں کی میعاد ختم ہونے کے لیے بجٹ۔ براہ کرم 24 گھنٹے کے اندر چیک کریں۔

ٹائر 3 CRITICAL 90%+ بجٹ کے استعمال، 24 گھنٹے میں 100% اضافہ، کریپٹو کرنسی مائننگ کا پتہ لگانے، اور 120%+ اوور سپنڈ پروجیکشن کے لیے PagerDuty اور SMS پر جاتا ہے۔ 1 گھنٹے کے اندر تحقیق کریں۔

2.2 ریئل ٹائم بجٹ مانیٹر

AWS بجٹ بطور ڈیفالٹ دن میں ایک بار اطلاعات بھیجتا ہے۔ یومیہ ونڈو کا مطلب ہے کہ 08:00 سے شروع ہونے والی لاگت میں اضافے کو اس وقت تک پکڑا نہیں جائے گا جب تک کہ اگلے دن الرٹ جاری نہیں کیا جاتا ہے۔ نیچے دی گئی لیمبڈا فی گھنٹہ چلتا ہے اور بجٹ کے مطلق استعمال اور تبدیلی کی فی گھنٹہ شرح دونوں کو چیک کرتا ہے۔

# budget_monitor.py
# Lambda triggered by EventBridge every hour

import boto3
from datetime import datetime, timedelta
from alert_router import route_alert, AlertTier

ce      = boto3.client('ce')
budgets = boto3.client('budgets', region_name="us-east-1")
ACCOUNT_ID  = boto3.client('sts').get_caller_identity()['Account']
BUDGET_NAME = 'monthly-infrastructure'


def get_mtd_spend() -> float:
    start = datetime.now().replace(day=1).strftime('%Y-%m-%d')
    end   = datetime.now().strftime('%Y-%m-%d')
    r = ce.get_cost_and_usage(
        TimePeriod={'Start': start, 'End': end},
        Granularity='MONTHLY',
        Metrics=['UnblendedCost']
    )
    return float(r['ResultsByTime'][0]['Total']['UnblendedCost']['Amount'])


def get_budget_limit() -> float:
    r = budgets.describe_budget(AccountId=ACCOUNT_ID, BudgetName=BUDGET_NAME)
    return float(r['Budget']['BudgetLimit']['Amount'])


def get_hourly_costs(hours: int = 4) -> list:
    """Return hourly cost totals for the last N hours."""
    end   = datetime.now()
    start = end - timedelta(hours=hours)
    r = ce.get_cost_and_usage(
        TimePeriod={'Start': start.strftime('%Y-%m-%d'), 'End': end.strftime('%Y-%m-%d')},
        Granularity='HOURLY',
        Metrics=['UnblendedCost']
    )
    return [
        float(period['Total']['UnblendedCost']['Amount'])
        for period in r['ResultsByTime']
    ]


def lambda_handler(event, context):
    mtd_spend    = get_mtd_spend()
    budget_limit = get_budget_limit()
    utilisation  = mtd_spend / budget_limit * 100

    days_elapsed  = datetime.now().day
    projected_eom = (mtd_spend / days_elapsed) * 30
    projected_pct = projected_eom / budget_limit * 100

    if utilisation >= 90:
        route_alert(
            AlertTier.CRITICAL,
            f'Budget at {utilisation:.0f}%',
            f'MTD spend ${mtd_spend:,.2f} is {utilisation:.0f}% of ${budget_limit:,.0f} budget. '
            f'Projected EOM: ${projected_eom:,.2f}.'
        )
    elif utilisation >= 75:
        route_alert(
            AlertTier.WARNING,
            f'Budget at {utilisation:.0f}%',
            f'MTD spend ${mtd_spend:,.2f} is {utilisation:.0f}% of ${budget_limit:,.0f} budget. '
            f'Projected EOM: ${projected_eom:,.2f}.'
        )

    # Check hourly spike
    hourly = get_hourly_costs(hours=4)
    if len(hourly) >= 2:
        last_hour = hourly[-1]
        prev_avg  = sum(hourly[:-1]) / len(hourly[:-1])
        if prev_avg > 0.10 and last_hour > prev_avg * 1.5:
            route_alert(
                AlertTier.WARNING,
                'Hourly cost spike detected',
                f'Last hour: ${last_hour:.2f} vs prior 3-hour avg ${prev_avg:.2f} '
                f'(+{(last_hour/prev_avg - 1)*100:.0f}%)'
            )

    return {
        'mtd_spend':       round(mtd_spend, 2),
        'utilisation_pct': round(utilisation, 1),
        'projected_eom':   round(projected_eom, 2),
    }

حصہ 3: سروس کے لیے مخصوص اصلاح

3.1 EC2 - ترجیحی ترتیب میں 7 لیور

EC2 زیادہ تر AWS اکاؤنٹس میں سب سے بڑا آئٹم ہے اور سب سے زیادہ اصلاح کے اختیارات والا ہے۔ ان لیورز کو ترتیب کے ساتھ استعمال کریں، کیونکہ ہر لیور بیس لائن کو نیچے کرتا ہے جہاں سے اگلا لیور چلتا ہے۔

لیور 1: ایسی مثالیں تلاش کریں جو حقیقت میں بیکار ہیں (14 دنوں کے لیے CPU 1% سے کم)۔

# ec2_idle_finder.py
import boto3
from datetime import datetime, timedelta

ec2 = boto3.client('ec2')
cw  = boto3.client('cloudwatch')


def find_idle_instances(avg_cpu_threshold: float = 1.0, days: int = 14):
    instances = [
        inst
        for r in ec2.describe_instances(
            Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
        )['Reservations']
        for inst in r['Instances']
    ]

    idle = []
    for inst in instances:
        iid   = inst['InstanceId']
        stats = cw.get_metric_statistics(
            Namespace="AWS/EC2",
            MetricName="CPUUtilization",
            Dimensions=[{'Name': 'InstanceId', 'Value': iid}],
            StartTime=datetime.utcnow() - timedelta(days=days),
            EndTime=datetime.utcnow(),
            Period=days * 86400,
            Statistics=['Average']
        )['Datapoints']

        avg_cpu = stats[0]['Average'] if stats else 0.0
        if avg_cpu < avg_cpu_threshold:
            tags = {t['Key']: t['Value'] for t in inst.get('Tags', [])}
            idle.append({
                'instance_id':   iid,
                'instance_type': inst['InstanceType'],
                'avg_cpu':       round(avg_cpu, 2),
                'environment':   tags.get('Environment', '—'),
                'owner':         tags.get('Owner', '—'),
            })

    return sorted(idle, key=lambda x: x['avg_cpu'])


for inst in find_idle_instances():
    print(f"  {inst['instance_id']}  {inst['instance_type']}  "
          f"{inst['avg_cpu']}% CPU  env:{inst['environment']}  owner:{inst['owner']}")

لیور 2: دائیں سائز سے زیادہ فراہم کردہ مثالیں (سی پی یو 20٪ سے نیچے، برقرار)۔

ایک ہی اسکرپٹ استعمال کریں۔ avg_cpu_threshold=20.0. یہ دائیں سائز کے امیدوار ہیں، بند کرنے والے امیدوار نہیں۔

لیور 3: ڈیولپمنٹ اور اسٹیجنگ ختم کرنے کے لیے ایونٹ برج کے اصول استعمال کریں۔ AutoShutdown=true ٹیگ شدہ مثالیں۔

لیور 4: لیور 1-3 مکمل کرنے کے بعد ہی بچت کا منصوبہ خریدیں۔

لیور 5: گریویٹن کی طرف ہجرت کریں (زیادہ تر کام کے بوجھ کے لیے 20% سستی اور ایک جیسی کارکردگی)۔

لیور 6: غلطی برداشت کرنے والی تعیناتی اور ترقیاتی کام کے بوجھ کے لیے اسپاٹ کا استعمال کریں۔

لیور 7: خودکار بن پیکیجنگ کے لیے کارپینٹر کا استعمال کرتے ہوئے کنٹینرائزڈ ورک بوجھ کو EKS میں منتقل کریں۔

متوقع طور پر بچت:

# spot_price_analyser.py
import boto3

ec2 = boto3.client('ec2')


def spot_savings_estimate(instance_type: str) -> dict:
    spot_history = ec2.describe_spot_price_history(
        InstanceTypes=[instance_type],
        ProductDescriptions=['Linux/UNIX'],
        MaxResults=1
    )['SpotPriceHistory']
    spot_price = float(spot_history[0]['SpotPrice']) if spot_history else 0

    on_demand_approx = {
        't3.medium': 0.0416, 'm5.large': 0.096,
        'c5.xlarge': 0.17,   'r5.2xlarge': 0.504,
    }
    od_price    = on_demand_approx.get(instance_type, 0)
    savings_pct = ((od_price - spot_price) / od_price * 100) if od_price else 0

    return {
        'instance_type': instance_type,
        'spot_price':    round(spot_price, 4),
        'on_demand':     od_price,
        'savings_pct':   round(savings_pct, 1),
        'monthly_spot':  round(spot_price * 730, 2),
        'monthly_od':    round(od_price * 730, 2),
    }


for itype in ['t3.medium', 'm5.large', 'c5.xlarge']:
    r = spot_savings_estimate(itype)
    print(f"  {r['instance_type']:<15} Spot: ${r['spot_price']}/hr  "
          f"OD: ${r['on_demand']}/hr  Savings: {r['savings_pct']}%")

3.2 S3 — لائف سائیکل پالیسی اور اسٹوریج کلاس کا انتخاب

S3 آپٹیمائزیشن کے دو اجزاء ہیں: لائف سائیکل پالیسیوں کے ذریعے سستی اسٹوریج کی کلاسوں میں کبھی کبھار رسائی حاصل کرنے والے ڈیٹا کو منتقل کرنا اور فضول نمونوں کو ختم کرنا جیسے کہ نامکمل ملٹی پارٹ اپ لوڈز۔

# s3_lifecycle_applier.py
import boto3

s3 = boto3.client('s3')

LOG_POLICY = {
    'Rules': [{
        'ID': 'standard-tiering',
        'Status': 'Enabled',
        'Filter': {'Prefix': ''},
        'Transitions': [
            {'Days': 30,  'StorageClass': 'STANDARD_IA'},
            {'Days': 90,  'StorageClass': 'GLACIER_IR'},
            {'Days': 365, 'StorageClass': 'DEEP_ARCHIVE'},
        ],
        'Expiration': {'Days': 2555},
        'AbortIncompleteMultipartUpload': {'DaysAfterInitiation': 7},
    }]
}

TEMP_POLICY = {
    'Rules': [{
        'ID': 'temp-data-retention',
        'Status': 'Enabled',
        'Filter': {'Prefix': ''},
        'Expiration': {'Days': 30},
        'AbortIncompleteMultipartUpload': {'DaysAfterInitiation': 1},
    }]
}

for bucket in s3.list_buckets()['Buckets']:
    name = bucket['Name']
    try:
        s3.get_bucket_lifecycle_configuration(Bucket=name)
        print(f"  {name} — policy already exists, skipping")
    except s3.exceptions.ClientError:
        policy = TEMP_POLICY if any(k in name for k in ['temp', 'build', 'cache']) else LOG_POLICY
        s3.put_bucket_lifecycle_configuration(
            Bucket=name, LifecycleConfiguration=policy
        )
        print(f"  {name} — applied {'TEMP' if policy is TEMP_POLICY else 'LOG'} policy")

3.3 RDS - اصلاح کی 5 سطحیں۔

پانی کی سطح کارروائی عام بچت خطرہ
1 غیر استعمال شدہ پڑھی ہوئی نقلیں حذف کریں۔ نقل کی لاگت کا 30-50٪ کم
2 بیک اپ برقرار رکھنے کو کم سے کم تعمیل تک کم کریں۔ اسٹوریج کے اخراجات کا 20-30٪ کم
3 مناسب سائز کی مثال کی کلاس (سی پی یو 20٪ سے نیچے رہتا ہے) 20-40% کمپیوٹنگ درمیانی
4 پیداوار کے لیے محفوظ مثالیں خریدیں۔ 30-60% کمپیوٹنگ کم
5 متغیر لوڈ DB کو Aurora Serverless v2 میں منتقل کریں۔ کل 40~70% اعلی کوشش

ضرورت سے زیادہ آر ڈی ایس کی مثالیں دیکھیں۔

# rds_rightsizer.py
import boto3
from datetime import datetime, timedelta

rds = boto3.client('rds')
cw  = boto3.client('cloudwatch')


def find_oversized_rds():
    instances  = rds.describe_db_instances()['DBInstances']
    candidates = []

    for inst in instances:
        iid    = inst['DBInstanceIdentifier']
        iclass = inst['DBInstanceClass']

        stats = cw.get_metric_statistics(
            Namespace="AWS/RDS",
            MetricName="CPUUtilization",
            Dimensions=[{'Name': 'DBInstanceIdentifier', 'Value': iid}],
            StartTime=datetime.utcnow() - timedelta(days=14),
            EndTime=datetime.utcnow(),
            Period=1209600,
            Statistics=['Average', 'Maximum']
        )['Datapoints']

        if not stats:
            continue

        avg_cpu = stats[0]['Average']
        max_cpu = stats[0]['Maximum']

        if avg_cpu < 20 and max_cpu < 50:
            candidates.append({
                'id':       iid,
                'class':    iclass,
                'avg_cpu':  round(avg_cpu, 1),
                'max_cpu':  round(max_cpu, 1),
                'engine':   inst['Engine'],
            })

    return candidates


for c in find_oversized_rds():
    print(f"  {c['id']}  {c['class']}  avg:{c['avg_cpu']}%  max:{c['max_cpu']}%  engine:{c['engine']}")

3.4 DynamoDB — آن ڈیمانڈ اور پروویژننگ فیصلے

آن ڈیمانڈ آسان ہے، لیکن متوقع کام کے بوجھ کی فراہمی سے 3 سے 5 گنا زیادہ مہنگا ہو سکتا ہے۔ آٹو اسکیلنگ کے ساتھ فراہمی زیادہ تر معاملات کو بہت کم قیمت پر سنبھال سکتی ہے۔

# dynamodb_mode_advisor.py
import boto3
from datetime import datetime, timedelta

dynamodb = boto3.client('dynamodb')
cw       = boto3.client('cloudwatch')


def analyse_table_billing(table_name: str) -> dict:
    table = dynamodb.describe_table(TableName=table_name)['Table']
    mode  = table.get('BillingModeSummary', {}).get('BillingMode', 'PROVISIONED')

    stats = {}
    for metric in ['ConsumedReadCapacityUnits', 'ConsumedWriteCapacityUnits']:
        data = cw.get_metric_statistics(
            Namespace="AWS/DynamoDB",
            MetricName=metric,
            Dimensions=[{'Name': 'TableName', 'Value': table_name}],
            StartTime=datetime.utcnow() - timedelta(days=30),
            EndTime=datetime.utcnow(),
            Period=86400,
            Statistics=['Average', 'Maximum']
        )['Datapoints']
        if data:
            avg  = sum(d['Average'] for d in data) / len(data)
            peak = max(d['Maximum'] for d in data)
            stats[metric] = {'avg': round(avg, 1), 'peak': round(peak, 1)}

    if mode == 'PAY_PER_REQUEST':
        avg_rcu = stats.get('ConsumedReadCapacityUnits', {}).get('avg', 0)
        avg_wcu = stats.get('ConsumedWriteCapacityUnits', {}).get('avg', 0)

        if avg_rcu > 2000 or avg_wcu > 500:
            return {
                'table':          table_name,
                'current_mode':   'PAY_PER_REQUEST',
                'recommendation': 'Switch to PROVISIONED with auto-scaling',
                'reason': f'Avg {avg_rcu:.0f} RCU/s and {avg_wcu:.0f} WCU/s — predictable pattern',
            }

    return {'table': table_name, 'current_mode': mode, 'recommendation': 'No change needed'}


for table in dynamodb.list_tables()['TableNames']:
    r = analyse_table_billing(table)
    if r['recommendation'] != 'No change needed':
        print(f"  {r['table']}: {r['recommendation']}")

3.5 ڈیٹا کی منتقلی - فضلہ کے تین بڑے نمونے۔

ڈیٹا کی منتقلی کی فیس اکثر آپ کے AWS بل میں سب سے زیادہ الجھا دینے والی چیز ہوتی ہے۔ تین اہم نمونے اور ان میں ترمیم یہ ہیں:

کراس-AZ ٹریفک (سب سے زیادہ عام اور قابل ترمیم): مختلف AZs میں خدمات ہر سمت میں $0.01 فی GB خرچ کرتی ہیں۔ ایک حل یہ ہے کہ Kubernetes سروسز میں ٹوپولوجی سے آگاہ روٹنگ کا استعمال کریں یا اس بات کو یقینی بنائیں کہ ایپلیکیشن ٹائر اور ڈیٹا بیس ٹائر ایک ہی AZ پلیسمنٹ کا استعمال کریں۔

اندرونی AWS ٹریفک کے لیے NAT گیٹ وے چارجز: S3, ECR, DynamoDB، اور SQS ٹریفک NAT گیٹ وے کے ذریعے روٹ کرنے پر $0.045 فی GB لاگت آتی ہے۔ حل: VPC اینڈ پوائنٹس اس مسئلے کو مکمل طور پر ختم کر دیتے ہیں۔

تعمیل سے پاک کراس ریجن ریپلیکیشن: اصل تعمیل کی ضروریات کے خلاف سہ ماہی میں اپنے S3 ریپلیکیشن رولز کا آڈٹ کریں۔

# Find S3 buckets with active replication
for bucket in $(aws s3api list-buckets --query 'Buckets[*].Name' --output text); do
    result=$(aws s3api get-bucket-replication --bucket "$bucket" 2>&1)
    if ! echo "$result" | grep -q "ReplicationConfigurationNotFoundError"; then
        echo "  $bucket — replication active, verify compliance requirement"
    fi
done

حصہ 4: 30 دن کی اصلاحی اسپرنٹ

یہ سپرنٹ پہلے مہینے میں لاگت کی اہم بچت فراہم کرتا ہے۔ یہ ایک واحد انجینئر کے لیے فی ہفتہ 2-4 گھنٹے وقف FinOps وقت کے ساتھ ڈیزائن کیا گیا ہے۔

ہفتہ 1 - مرئیت: CUR کو فعال کریں، ایک ایتھینا ٹیبل ترتیب دیں، 3 دن کے سوالات چلائیں، نتائج کی بنیاد پر اسکرین شاٹس لیں، 100% چلنے والے EC2 اور RDS مثالوں کو ٹیگ کریں، اور ہر ایک کے لیے دستاویزی مفروضوں کے ساتھ ٹاپ 3 لاگت والے ڈرائیوروں کی شناخت کریں۔

ہفتہ 2 - فوری جیت: ایک یتیم ریسورس رپورٹر Lambda کو تعینات کریں، S3 لائف سائیکل پالیسیوں کو تین سب سے بڑی بالٹیوں پر لاگو کریں، ایک آئیڈیل انسٹینس ڈیٹیکٹر چلائیں، مالک کی مخالفت کے بغیر اوسط CPU کو 1% سے کم روکیں، اور S3، ECR، اور DynamoDB کے لیے VPC اینڈ پوائنٹس شامل کریں۔

ہفتہ 3 - رائٹ سائزنگ: EC2 رائٹ سائزنگ اینالائزر چلائیں، تین سب سے زیادہ اعتماد والے امیدواروں کا سائز کم کریں (پہلے غیر پروڈکشن)، RDS رائٹ سائزنگ اسکرپٹ چلائیں، کم سے کم ٹریفک فراہم کرنے والی ریڈ ریپلیکا تلاش کریں، اور اسے ختم کریں۔

ہفتہ 4 - اطلاعات اور آٹومیشن: گھنٹہ وار بجٹ مانیٹر Lambda کو تعینات کریں، 3-ٹیر نوٹیفکیشن روٹنگ کو ترتیب دیں، ہفتہ وار ویسٹ رپورٹنگ ترتیب دیں، انفراسٹرکچر ریپوزٹری میں Infracost GitHub ایکشن شامل کریں، ماہانہ 30 منٹ کی FinOps جائزہ میٹنگ کا شیڈول بنائیں۔

30 دنوں کے بعد متوقع نتائج: ماہانہ AWS اخراجات میں 15-25% کمی، تمام تبدیلیوں کے دستاویزی ثبوت، اور ایک بار بار عمل کو یقینی بنانے کے لیے کہ وہی فضلہ دوبارہ ڈھیر نہ ہو۔

بہترین طریقوں کا خلاصہ

کرنا: دیگر نگرانی سے پہلے CUR + Athena سیٹ کریں۔ Cost Explorer آپ کا نقطہ آغاز ہے اور CUR آپ کی معلومات کا ذریعہ ہے۔

کرنا: Terraform یا CloudFormation میں ٹیگ لگائیں۔ پروسیس پر مبنی ٹیگنگ ختم ہو گئی ہے، لیکن انفراسٹرکچر سے نافذ ٹیگنگ مستقل ہے۔

کرنا: بیکار مثال کی تلاش اور فضلہ کی رپورٹنگ ہفتہ وار چلائیں۔ فضلہ جمع ہوتا رہتا ہے۔ ہفتہ وار رپورٹیں ڈھیر چھوٹا رکھتی ہیں۔

کرنا: یہ تین درجے کا الرٹنگ ماڈل استعمال کرتا ہے۔ ایک سب پر مشتمل انتباہی چینل تھکاوٹ کا سبب بنتا ہے اور خاموش ہو جاتا ہے۔

کرنا: ترتیب میں EC2 آپٹیمائزیشن ٹول کے ذریعے جائیں۔ بچت کے منصوبوں سے پہلے صحیح طریقے سے سائز کرنے سے آپ کو رعایتی قیمتوں پر پیسہ ضائع کرنے سے بچنے میں مدد ملے گی۔

کرنا: اپنے حقیقی استعمال کے نمونوں کی بنیاد پر ہر سہ ماہی میں اپنا DynamoDB بلنگ موڈ چیک کریں۔

مت کرو: بغیر تحقیق کے غیر ٹیگ شدہ وسائل کو حذف کریں۔ غیر ٹیگ شدہ کا مطلب غیر استعمال شدہ نہیں ہے، اس کا مطلب صرف غیر دعوی شدہ ہے۔

مت کرو: پہلے آڈٹ رسائی کے نمونوں کے بغیر جارحانہ S3 لائف سائیکل پالیسیوں کا اطلاق کریں۔ اگر ڈیٹا تک توقع سے زیادہ کثرت سے رسائی حاصل کی جاتی ہے، تو برفانی بازیافت کے اخراجات معیاری اسٹوریج کے اخراجات سے زیادہ ہو سکتے ہیں۔

مت کرو: اپنی پہلی تعیناتی میں، آپ ایک ویسٹ رپورٹر لیمبڈا چلاتے ہیں جس کے ساتھ آٹو ڈیلیٹ کرنا فعال ہے۔ حذف کرنے کی منطق کو شامل کرنے سے پہلے دو ہفتوں تک صرف رپورٹ موڈ میں چلا کر آؤٹ پٹ کی توثیق کریں۔

وسائل

Scroll to Top