آٹومیشن اسکرپٹس اکثر سسٹم کی حیثیت کے بجائے عمل کی تکمیل کو چیک کرتی ہیں۔
Kubernetes Pods چل سکتے ہیں جبکہ اندرونی ایپلی کیشنز ڈیٹا بیس کی تصدیق نہیں کر سکتیں۔ ایک ٹیرافارم کی تعیناتی صاف طور پر واپس آسکتی ہے جب کوئی دستی طور پر کلاؤڈ کنسول میں بنیادی ڈھانچے کو تبدیل کرتا ہے۔ ایک کینری لانچ میں کوئی خامی بالکل نہیں دکھائی دے سکتی ہے جب کہ صارف ہر درخواست کے لیے 5 سیکنڈ انتظار کرتا ہے۔
مسئلہ آلے کا نہیں ہے۔ مسئلہ یہ ہے کہ ایک نظام صحت مند ظاہر ہوسکتا ہے جب حقیقت میں ایسا نہیں ہے۔
یہ ہینڈ بک آپ کو Bash اور Python کا استعمال کرتے ہوئے پروڈکشن طرز کے آٹومیشن کے پانچ منظرناموں سے گزرتی ہے:
-
اپنا ماہانہ بل آنے سے پہلے غیر معمولی AWS اخراجات کا پتہ لگائیں۔
-
ٹریکنگ آئی ڈیز کا استعمال کرتے ہوئے متعدد سروسز میں لاگز کو آپس میں جوڑیں۔
-
ٹیرافارم کے باہر انفراسٹرکچر کے بہاؤ کو تلاش کرنا
-
درخواست کی سطح پر خفیہ گردش کی توثیق کو محفوظ بنائیں
-
صارفین کی شکایت کرنے سے پہلے خودکار طور پر سست تعیناتیوں کو رول بیک کریں۔
اس ہینڈ بک کے اختتام تک، آپ چھوٹے اسکرپٹ لکھنے کے قابل ہو جائیں گے جو آپ کو یہ محسوس کرنے میں مدد کریں گے کہ جب آپ کے سسٹم میں کچھ غلط ہے، یہاں تک کہ جب ٹول کہے کہ سب کچھ ٹھیک ہے۔
اسکرپٹ جان بوجھ کر چھوٹا ہے۔ اہم حصہ اس کے پیچھے آپریشنل سوچ ہے: اسکرپٹ کے اقدامات کو کیا اشارہ دیتا ہے، یہ کس ناکامی کے طریقوں کا پتہ لگا سکتا ہے، اور پلیٹ فارم کن مفروضوں پر مبنی ہے۔
ہر استعمال کے کیس میں چلائے جانے کے قابل ڈیمو ماحول، ایک مکمل اسکرپٹ، متعلقہ نظام کے رویے کا تجزیہ، اور جان بوجھ کر غلطیاں شامل ہوتی ہیں جنہیں صارف براہ راست انجام دے سکتے ہیں۔
اگر آپ اس ورک فلو میں نئے ہیں، تو Use Case 1 کے ساتھ شروع کریں اور اپنے راستے پر کام کریں۔ اس کے بعد کے حصے اسی طرز پر مبنی ہیں۔ آٹومیشن نہ صرف عمل کو مکمل کرنے بلکہ حقیقت کی جانچ فراہم کرنے کے لیے بھی مفید ہے۔
شرطیں
شروع کرنے سے پہلے، درج ذیل سیٹ کریں:
python3 -m venv venv
source venv/bin/activate
# on Windows:
venvScriptsactivate
یہ انسٹال شدہ پیکجوں کو سسٹم Python سے الگ رکھے گا اور مشترکہ کمپیوٹرز پر اجازت کی غلطیوں کو روکے گا۔
-
بیج – ازگر کا پیکج انسٹالر ازگر کے ساتھ شامل ہے۔
-
AWS CLI ورک پروفائل کے طور پر تشکیل شدہ – مفت ٹائر AWS اکاؤنٹس 1، 3 اور 4 کے استعمال کے لیے کافی ہیں۔ چیک کریں کہ آیا یہ اس کے ساتھ کام کرتا ہے:
aws sts get-caller-identity -
ڈوکر اور ڈوکر کمپوز – استعمال کے معاملات 2، 4، اور 5 کے لیے درکار ہے۔
-
قسم (Docker پر Kubernetes) – استعمال کے کیسز 4 اور 5 کے لیے مقامی طور پر Kubernetes کو کیسے چلایا جائے۔ اس کے ساتھ انسٹال کریں
brew install kindmacOS پر، Kind Quick start گائیڈ پر عمل کریں۔ -
کیوبیکٹل – آپ کے Kubernetes کلسٹر کے ساتھ بات چیت کرنے کے لیے ایک کمانڈ لائن ٹول۔ Kind انسٹال کرنے کے بعد اسے چلائیں۔
kind create clusterkubectl خود بخود کنفیگر ہوجاتا ہے۔ -
کنٹرول – استعمال کیس کے لیے Kubernetes کے لیے پیکیج مینیجر کی ضرورت ہے 5۔ اس کے ساتھ انسٹال کریں۔
brew install helmیا ہیلم انسٹالیشن گائیڈ -
ٹیرافارم – استعمال کے لیے درکار کیس 3۔ انسٹال کریں۔
brew install terraformmacOS پر یا Terraform انسٹالیشن گائیڈ پر عمل کریں۔ اسے چیک کریںterraform version. -
B.C – ایک کیلکولیٹر یوٹیلیٹی جو کینری واچ اسکرپٹ کے ذریعے فلوٹنگ پوائنٹ کے موازنہ کے لیے استعمال کی جاتی ہے۔ کے ساتھ انسٹال کریں۔
brew install bcmacOS یاapt install bcاوبنٹو پر۔ چلائیںbc --versionکیس 5 کا استعمال شروع کرنے سے پہلے، چیک کریں کہ آیا یہ دستیاب ہے۔
علم اور مہارت
-
آپ کو ازگر اور باش اسکرپٹس کو شروع سے لکھے بغیر پڑھنے میں آرام سے رہنا چاہئے۔
-
آپ کے پاس بنیادی لینکس ٹرمینل کی سہولت ہونی چاہیے، بشمول ڈائرکٹریوں کو نیویگیٹ کرنا، اسکرپٹ کو چلانا، اور آؤٹ پٹ پڑھنا۔
-
بنیادی سطح پر، آپ کو یہ جاننے کی ضرورت ہے کہ Kubernetes pods اور تعیناتیاں کیا ہیں۔ کیسز 4 اور 5 استعمال کریں Kubernetes کے تصورات کو متعارف کراتے ہیں جو آپ استعمال کرتے ہیں اور گہرائی سے Kubernetes کی مہارت کی ضرورت نہیں ہے۔
-
AWS کے بنیادی اصولوں سے واقفیت جیسے EC2، IAM، اور Secrets Manager کے استعمال کے کیسز 1، 3 اور 4 میں مدد ملے گی، جبکہ کیس 2 کا استعمال مکمل طور پر آپ کی مقامی مشین پر چلتا ہے اور اسے AWS کے علم کی ضرورت نہیں ہے۔
-
کیس 3 کے استعمال کے لیے، یہ جاننا مددگار ہے کہ Terraform کیا ہے اور ریاستی فائلیں کیا کرتی ہیں۔ آپ کو Terraform لکھنے کی ضرورت نہیں ہے، لیکن یہ سمجھنا کہ Terraform کیا ٹریک کرتا ہے اور کیا پیدا کرتا ہے آپ کے استعمال کے پورے کیس کی بنیاد ہے۔
AWS IAM اجازت درکار ہے۔
اس آرٹیکل میں موجود اسکرپٹس اصل AWS API کال کرتی ہیں۔ ایک IAM صارف یا کردار کو درج ذیل کم از کم اجازتوں کی ضرورت ہوتی ہے: (اگر آپ دیکھیں AccessDenied اگر آپ کو کوئی غلطی ہو رہی ہے، تو سب سے پہلے جو جگہیں دیکھیں وہ یہ ہیں:
| کیسز استعمال کریں۔ | IAM اجازتیں درکار ہیں۔ |
|---|---|
| 1 – لاگت میں بے ضابطگی کا پتہ لگانا | ce:GetCostAndUsage |
| 3 – آلگائے کا پتہ لگانا | ec2:DescribeSecurityGroups |
| 4 – خفیہ تبدیلی | secretsmanager:GetSecretValue، secretsmanager:PutSecretValue |
اگر آپ ایک نیا AWS Free Tier اکاؤنٹ استعمال کر رہے ہیں۔ AdministratorAccess چونکہ وہ منسلک ہیں، یہ اجازتیں پہلے سے شامل ہیں اور آپ اس مرحلہ کو چھوڑ سکتے ہیں۔
اگر آپ ایک محدود IAM صارف ہیں، تو اس صارف کو شامل کرنے کا طریقہ یہاں ہے: AWS کنسول میں، IAM پر جائیں، صارفین پر کلک کریں، اور پھر اپنے صارف نام پر کلک کریں۔ اجازت والے ٹیب پر، اجازت شامل کریں پر کلک کریں، اور پھر ان لائن پالیسی بنائیں پر کلک کریں۔
JSON ٹیب پر جائیں اور اوپر دیے گئے جدول میں اجازت دینے والے پالیسی دستاویز کو چسپاں کریں اور محفوظ کریں۔
اگر آپ کی کمپنی آپ کی تنظیم کے ذریعے AWS کا انتظام کرتی ہے اور آپ کو اپنی IAM پالیسیوں میں ترمیم کرنے کی اجازت نہیں ہے، تو اپنے منتظم سے ان اجازتوں کو اپنے کردار میں شامل کرنے کو کہیں۔
ساتھی GitHub ذخیرہ
تمام ڈیمو پروجیکٹس یہاں موجود ہیں: https://github.com/irvingtalks/devops-scripting-labs
ہر استعمال کے کیس میں ایک مکمل اسکرپٹ، سپورٹ فائلیں، setup.sh ماحول تیار کریں، break_it.sh یہ مخصوص غلطیاں انجیکشن کرتا ہے جو ہر استعمال کے معاملے کی بنیاد ہیں۔
شروع کرنے سے پہلے ذخیرہ کلون کریں۔
git clone https://github.com/irvingtalks/devops-scripting-labs
cd devops-scripting-labs
اپنے استعمال کے کیس کو چلانے سے پہلے یقینی بنائیں کہ سب کچھ انسٹال ہے۔
./preflight.sh
اس میں وہ تمام ٹولز شامل ہیں جن کی آپ کو مشق کے لیے ضرورت ہے، بشمول Python، AWS CLI، Docker، Kind، Helm، اور Terraform۔ bc یہ آپ کو بتاتا ہے کہ ہر انسٹالیشن کمانڈ میں کیا غائب ہے۔
انڈیکس
کیس 1 استعمال کریں – لاگت میں بے ضابطگی کا پتہ لگانا
ماحول: AWS Cost Explorer API (صرف پڑھنے کے لیے، تمام اکاؤنٹس کے لیے دستیاب) زبان: ازگر
پیداوار کے مسائل
ایک جونیئر انجینئر Kubernetes کنفیگریشن کی جانچ کر رہا ہے۔ آپ AWS میں ایک منظم نوڈ گروپ کو گھماتے ہیں (EC2 ورچوئل مشینوں کا ایک سیٹ جسے آپ کا Kubernetes کلسٹر اپنے کام کے بوجھ کو چلانے کے لیے استعمال کرتا ہے) اور کلسٹر آٹو اسکیلر کو کنفیگر کرتے ہیں، ایک Kubernetes جزو جو آپ کے کلسٹر کو زیادہ صلاحیت کی ضرورت ہونے پر مزید مشینیں شامل کرتا ہے۔ امتحان اچھا ہوا اور جمعہ کی دوپہر تک ہم ماحول کو تباہ کرنا بھول چکے تھے۔
چونکہ ہفتے کے آخر میں آزمائشی کام کا بوجھ جاری رہتا ہے اور وسائل کی درخواست کرتا ہے، آٹو اسکیلر نئے نوڈس کی فراہمی جاری رکھتا ہے۔ میرے پاس نوڈس کا ایک گروپ تھا جو پیر کی صبح تک 2 1/2 دنوں سے خاموشی سے بڑھ رہا تھا اور 3 ہفتوں کے بعد بل آنے تک کسی نے نوٹس نہیں لیا۔
اس استعمال کے کیس کا اسکرپٹ موجود ہے کیونکہ AWS بل صرف ماہانہ نمبر نہیں ہیں۔ یہ ایک ٹائم سیریز ہے اور اس کی نگرانی اسی طرح کی جا سکتی ہے جس طرح آپ ایپلیکیشن میٹرکس کی نگرانی کرتے ہیں۔ روزانہ چیک ان کرکے اور اپنی بنیادی لائن کو جان کر، آپ اس قسم کے واقعات کو ہفتوں کے بجائے گھنٹوں میں پکڑ سکتے ہیں۔
نظام کی سطح پر اصل میں کیا ہو رہا ہے؟
یہ کیا نہیں ہے: یہ مالیاتی ڈیش بورڈ نہیں ہے۔ یہ رویے سے متعلق بے ضابطگی کا پتہ لگانے والا ہے اور جس سگنل پر نظر رکھتا ہے وہ قیمت ہے۔ لیکن جو حقیقت میں اس کا پتہ چلتا ہے وہ غیر متوقع بنیادی ڈھانچے کا رویہ ہے، جیسے چلانے والے وسائل، آٹو اسکیلر ایونٹس، اور بھولے ہوئے ماحول۔
AWS Cost Explorer ایک ایسی خدمت ہے جو ادائیگی کے ڈیٹا کو ذخیرہ کرتی ہے اور اسے API کے ذریعے ظاہر کرتی ہے۔ اسے کال کرنے سے آپ کے اکاؤنٹ کی ادائیگی کی سرگزشت کے خلاف ایک استفسار چلے گا، جس میں وقت کی حد، گرینولریٹی، اور آپ نتائج کو کس طرح گروپ کرنا چاہتے ہیں کی وضاحت کرے گا۔
دکھائے گئے اخراجات کی تحقیق شروع کرنے سے پہلے ایک چیز جو آپ کو معلوم ہونی چاہیے وہ یہ ہے کہ AWS یہ فیصلہ کرتا ہے کہ آپ سے نہیں بلکہ کن سروس کیٹیگریز کے لیے آپ کو چارج کیا جائے گا۔ متعدد خطوں میں چلنے والی EBS سنیپ شاٹ کاپیاں ڈیٹا ٹرانسفر کے بجائے EC2 کے تحت ظاہر ہو سکتی ہیں۔ دوسرے لفظوں میں، EC2 کے اخراجات میں اضافے کا مطلب یہ نہیں ہے کہ آپ کے EC2 مثالوں میں کوئی مسئلہ ہے۔ اسکرپٹ اسپائکس کو صحیح طریقے سے جھنڈا دیتا ہے، لیکن ان کی چھان بین کرنے کا مطلب ہے سوالات پوچھنا جیسے: "اس تاریخ کو میرے بنیادی ڈھانچے میں کیا تبدیلی آئی” بلکہ "ابھی EC2 پر کیا چل رہا ہے؟”
بل کا لیبل ایک نقطہ آغاز ہے، تشخیص نہیں۔
ڈیمو کی ترتیبات
اگلے پر جائیں۔ 01-cost-anomaly/ یہ ساتھی ذخیرہ میں ہے۔ اس استعمال کے معاملے کے لیے کسی کلسٹر سیٹ اپ کی ضرورت نہیں ہے کیونکہ اسکرپٹ براہ راست آپ کے AWS اکاؤنٹ کے خلاف چلتا ہے اور اس کا واحد انحصار boto3 ہے۔
cd 01-cost-anomaly
pip install boto3
یقینی بنائیں کہ آپ کے حقیقی اکاؤنٹ کے خلاف چلنے سے پہلے آپ کے AWS اسناد کو ترتیب دیا گیا ہے۔ اسکرپٹ ان تمام اسناد کا استعمال کرتی ہے جو AWS CLI نے ترتیب دی ہیں۔ اگر آپ نے ابھی تک یہ نہیں کیا ہے:
aws configure
اس کے بعد آپ اپنی AWS رسائی کلید ID، خفیہ رسائی کلید، اور پہلے سے طے شدہ علاقہ (استعمال کرتے ہوئے us-east-1 اگر آپ کو یقین نہیں ہے) اور آؤٹ پٹ فارمیٹ (type json)۔ آپ اپنی رسائی کیز کو AWS کنسول میں IAM → صارفین → صارف نام → سیکیورٹی اسناد → رسائی کیز بنائیں کے تحت تلاش کر سکتے ہیں۔
آپ کے اکاؤنٹ میں ce:GetCostAndUsage اگر آپ ایک نیا اکاؤنٹ استعمال کرتے ہیں جس میں پہلے سے ایڈمنسٹریٹر رسائی شامل ہے تو آپ کو بھی اجازت ہوگی۔
اگر آپ کے پاس AWS اکاؤنٹ ہے جس میں کئی ہفتوں کی بلنگ کی تاریخ ہے، تو آپ اسکرپٹ کو براہ راست حقیقی ڈیٹا پر چلا سکتے ہیں۔
python detect_cost_anomaly.py
حقیقی اکاؤنٹ کے خلاف چلنے سے پہلے آپ کو دو چیزیں جاننے کی ضرورت ہے۔ سب سے پہلے، Cost Explorer ڈیٹا میں 24 گھنٹے کی تاخیر ہوتی ہے۔ اس کا مطلب یہ ہے کہ آج کے اخراجات کل تک ظاہر نہیں ہوں گے، اس لیے نامکمل نتائج سے بچنے کے لیے اسکرپٹ خود بخود حالیہ تاریخ کو خارج کر دیتا ہے۔
دوسرا، اسکرپٹ ملاوٹ شدہ اخراجات کا استعمال کرتا ہے، جو کہ وہ قیمتیں ہیں جو آپ اصل میں ایک اکاؤنٹ سیٹ اپ میں ادا کرتے ہیں۔ ملاوٹ شدہ قیمتیں وزنی اوسط ہیں جو کثیر اکاؤنٹی تنظیموں میں استعمال ہوتی ہیں جو محفوظ صلاحیت کا اشتراک کرتے ہیں اور مختلف نمبر فراہم کرتے ہیں۔
اگر آپ کے پاس نیا اکاؤنٹ ہے یا آپ اصل بلنگ ڈیٹا استعمال نہیں کرنا چاہتے تو اسکرپٹ میں شامل ہیں: --sample ایمبیڈڈ ڈیٹا استعمال کرنے اور AWS APIs کو کال نہ کرنے کے لیے ایک جھنڈا۔
کوڈ کو پڑھنے سے پہلے، یہ دیکھنے کے لیے پہلے چلائیں کہ آؤٹ پٹ کیسا لگتا ہے۔
python detect_cost_anomaly.py --sample
سکرپٹ
#!/usr/bin/env python3
# detect_cost_anomaly.py — Use Case 1: Cost Anomaly Detection
# Full explanation of every function is in the article.
import statistics
import sys
from datetime import datetime, timedelta
import boto3
def build_sample_data(days=30):
"""Synthetic Cost Explorer rows for the last `days` (ending yesterday).
The EC2 spike is placed on yesterday (device local date) so sample output
always matches the same window as live Cost Explorer mode.
"""
last_day = datetime.today().date() - timedelta(days=1)
first_day = last_day - timedelta(days=days - 1)
anomaly_day_index = days - 1
results = []
for i in range(days):
day = first_day + timedelta(days=i)
d = i + 1
results.append(
{
"TimePeriod": {
"Start": str(day),
"End": str(day + timedelta(days=1)),
},
"Groups": [
{
"Keys": ["Amazon EC2"],
"Metrics": {
"UnblendedCost": {
"Amount": str(
round(
18.50
if i == anomaly_day_index
else 1.10 + (d % 3) * 0.10,
2,
)
)
}
},
},
{
"Keys": ["Amazon S3"],
"Metrics": {
"UnblendedCost": {
"Amount": str(round(0.04 + (d % 5) * 0.01, 2))
}
},
},
{
"Keys": ["Amazon RDS"],
"Metrics": {
"UnblendedCost": {
"Amount": str(round(0.85 + (d % 4) * 0.05, 2))
}
},
},
],
}
)
return results, str(last_day)
def get_daily_costs(days=30):
ce = boto3.client("ce", region_name="us-east-1")
end = datetime.today().date() - timedelta(days=1)
start = end - timedelta(days=days)
response = ce.get_cost_and_usage(
TimePeriod={"Start": str(start), "End": str(end)},
Granularity="DAILY",
Metrics=["UnblendedCost"],
GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
)
return response["ResultsByTime"]
def build_service_timeseries(results):
services = {}
for day in results:
date_str = day["TimePeriod"]["Start"]
for group in day["Groups"]:
service = group["Keys"][0]
cost = float(group["Metrics"]["UnblendedCost"]["Amount"])
if service not in services:
services[service] = []
services[service].append({"date": date_str, "cost": cost})
return services
def detect_anomalies(services, baseline_days=7, multiplier=2.0, recent_days=None):
"""Flag days where cost exceeds prior `baseline_days` average + 2σ.
Uses a rolling baseline (each day vs the previous week). If `recent_days`
is set, only returns anomalies on or after today - recent_days.
"""
cutoff = None
if recent_days is not None:
cutoff = datetime.today().date() - timedelta(days=recent_days)
anomalies = []
for service, daily in services.items():
if len(daily) < baseline_days + 1:
continue
for i in range(baseline_days, len(daily)):
day = daily[i]
day_date = datetime.strptime(day["date"], "%Y-%m-%d").date()
if cutoff is not None and day_date < cutoff:
continue
baseline_costs = [d["cost"] for d in daily[i - baseline_days : i]]
avg = statistics.mean(baseline_costs)
if avg < 0.01:
continue
try:
std = statistics.stdev(baseline_costs)
except statistics.StatisticsError:
continue
threshold = avg + (multiplier * std)
if day["cost"] > threshold:
anomalies.append(
{
"service": service,
"date": day["date"],
"actual": round(day["cost"], 4),
"baseline_avg": round(avg, 4),
"threshold": round(threshold, 4),
"pct_above": round(((day["cost"] - avg) / avg) * 100, 1),
}
)
return sorted(anomalies, key=lambda x: x["date"])
def parse_args(argv):
use_sample = "--sample" in argv
recent_days = None
for arg in argv[1:]:
if arg.startswith("--recent-days="):
recent_days = int(arg.split("=", 1)[1])
return use_sample, recent_days
def run(use_sample=False, recent_days=None):
if use_sample:
results, anomaly_date = build_sample_data()
print("Running against sample data (--sample mode).")
print(
f"This data represents 30 days of billing ending yesterday, "
f"with a realistic EC2 anomaly on {anomaly_date}.n"
)
else:
print("Fetching 30 days of daily AWS costs by service...")
print("Note: today is excluded — Cost Explorer has a 24-hour billing lag.n")
results = get_daily_costs(days=30)
if recent_days is not None:
since = datetime.today().date() - timedelta(days=recent_days)
print(
f"Checking for spikes in the last {recent_days} days only "
f"(on or after {since}), each vs its prior 7-day average.n"
)
services = build_service_timeseries(results)
anomalies = detect_anomalies(services, recent_days=recent_days)
if not anomalies:
print("No anomalies detected.")
print("nNote: this script flags statistical outliers against your own baseline.")
print("A consistently elevated spend level will not trigger — only sudden increases.")
return
print(f"{'=' * 60}")
print(f"ANOMALIES DETECTED: {len(anomalies)}")
print(f"{'=' * 60}n")
for a in anomalies:
print(f"Service: {a['service']}")
print(f"Date: {a['date']}")
print(f"Actual cost: ${a['actual']}")
print(f"Baseline avg: ${a['baseline_avg']} (prior 7-day average)")
print(f"Threshold: ${a['threshold']}")
print(f"Overage: {a['pct_above']}% above baseline")
print()
print("=" * 60)
print("A note on AWS cost attribution:")
print("The service label in Cost Explorer is assigned by AWS, not by the resource")
print("that caused the cost. An EC2 spike may be caused by EBS snapshot copies,")
print("cross-region data transfer, or autoscaling events that AWS categorizes under")
print("EC2 in billing — not a running EC2 instance you can find in the console.")
print()
print("Before investigating the flagged service directly, ask:")
print("What changed in my infrastructure on or before the flagged date?")
print("Work backward from the operational change, not forward from the billing label.")
if __name__ == "__main__":
use_sample, recent_days = parse_args(sys.argv)
run(use_sample=use_sample, recent_days=recent_days)
اسکرپٹ کیسے کام کرتا ہے۔
get_daily_costs گزشتہ 30 دنوں کا AWS بلنگ ڈیٹا حاصل کریں۔
build_service_timeseries AWS سے خام ڈیٹا لیں اور اسے دوبارہ ترتیب دیں۔ AWS ڈیٹا کو پہلے تاریخ کے لحاظ سے اور پھر سروس کے لحاظ سے گروپ کرتا ہے۔ یہ خصوصیت اس کا رخ موڑ دیتی ہے اور ہر سروس کو روزانہ کے اخراجات کی اپنی فہرست رکھنے کی اجازت دیتی ہے جو اسے پتہ لگانے کے مرحلے کے دوران چلنا چاہیے۔
detect_anomalies یہ وہ جگہ ہے جہاں حقیقی تصدیق ہوتی ہے۔ ہر سروس کے لیے، اپنے یومیہ اخراجات کا پچھلے سات دنوں سے موازنہ کریں۔ اگر کل کے اخراجات پچھلے ہفتے کے مقابلے میں نمایاں طور پر زیادہ ہیں، تو اسکرپٹ اس کو جھنڈا دیتا ہے۔ بس۔
--recent-days=7 طریقہ "براہ کرم صرف پچھلے 7 دنوں کی بے ضابطگیاں دکھائیں۔” چونکہ ہمیں موازنہ کا حساب لگانے کے لیے اس تاریخ کی ضرورت ہے، اس لیے اسکرپٹ اب بھی 30 دن کے ڈیٹا میں کھینچتا ہے، لیکن نتائج کو مطلوبہ ونڈو میں فلٹر کیا جاتا ہے۔ پیر کی صبح فوری چیک اپ کے لیے بہت اچھا ہے۔
--sample یہ آپ کے AWS اکاؤنٹ کو چھوئے بغیر چلتا ہے۔ ہم کل کی تاریخوں میں اسپائکس کے ساتھ بلٹ ان جعلی دعووں کا ڈیٹا استعمال کرتے ہیں، لہذا پتہ لگانے کو ہمیشہ متحرک کیا جاتا ہے۔ اصلی ڈیٹا سے منسلک ہونے سے پہلے چیک کریں کہ آؤٹ پٹ کیسا لگتا ہے۔
آؤٹ پٹ نتیجہ
چل رہا ہے --sample (اسپائک کی تاریخیں کل کی اصل تاریخ کے طور پر دکھائی جاتی ہیں، ایک مقررہ قدر نہیں۔)
Running against sample data (--sample mode).
30 days of billing ending yesterday, with an EC2 spike on 2026-05-14.
============================================================
ANOMALIES DETECTED: 1
============================================================
Service: Amazon EC2
Date: 2026-05-14
Actual cost: $18.5
Baseline avg: $1.2143 (prior 7-day average)
Threshold: $1.3939
Overage: 1423.4% above baseline
============================================================
A note on AWS cost attribution:
The service label in Cost Explorer is assigned by AWS, not by the resource
that caused the cost. An EC2 spike may be caused by EBS snapshot copies,
cross-region data transfer, or autoscaling events that AWS categorizes under
EC2 in billing - not a running EC2 instance you can find in the console.
Before investigating the flagged service directly, ask:
What changed in my infrastructure on or before the flagged date?
Work backward from the operational change, not forward from the billing label.
نمونے کا ڈیٹا متحرک طور پر آج کی تاریخ تیار کرتا ہے، اس لیے نمبر اوپر سے کچھ مختلف ہیں۔ اسپائکس ہمیشہ کل ظاہر ہوتے ہیں، اور ارد گرد کے بیس لائن نمبر اس تاریخ کے لحاظ سے تبدیل ہوتے ہیں جب آپ انہیں چلاتے ہیں۔
وہ فیصلے جو اسکرپٹ نہیں کر سکتا
استثنا EC2 لائن میں ہے، جہاں میری جبلت EC2 مثالوں کو چلانے کے لیے ہے۔ تاہم، جیسا کہ آؤٹ پٹ خبردار کرتا ہے، انتساب AWS کا انتخاب ہے، آپ کا نہیں۔
EC2 کنسول کھولنے سے پہلے، اس دن کی تعیناتی کی تاریخ چیک کریں۔ کیا تقسیم کیا گیا؟ کیا نیا ماحول پیدا ہوا ہے؟ کیا آٹو اسکیلر واقعات کو برطرف کیا گیا ہے؟ آپریشنل تبدیلیوں کے ساتھ شروع کرتے ہوئے بلنگ ڈیٹا پر تھریڈ پر عمل کریں۔ اس کی وجہ یہ ہے کہ بلنگ لیبل سے شروع کرنا اور پیچھے کی طرف کام کرنا سست اور اکثر گمراہ کن ہوتا ہے۔
جان بوجھ کر توڑ دو
# See the spike immediately with no AWS account needed
python detect_cost_anomaly.py --sample
# Run against your real account
python detect_cost_anomaly.py
# Only show anomalies from the last 7 days, good for a quick this-week check
python detect_cost_anomaly.py --recent-days=7
# Combine both flags - sample data filtered to the last 7 days
python detect_cost_anomaly.py --sample --recent-days=7
اگر کوئی حقیقی اکاؤنٹ "کوئی بے ضابطگی نہیں پایا” لوٹاتا ہے تو یہ ناکامی نہیں ہے۔ اس کا مطلب یہ ہے کہ خرچ کرنا مستقل ہے۔ صاف اکاؤنٹ صاف آؤٹ پٹ واپس کرتا ہے۔ اسکرپٹ بالکل وہی کر رہی ہے جو اسے کرنا چاہیے تھا۔
جب آپ کے اکاؤنٹ میں حقیقی واقعات رونما ہوتے ہیں، جیسے کہ آٹو اسکیلرز چل رہے ہیں، بھولے ہوئے ماحول، یا غیر متوقع ڈیٹا کی منتقلی، تو ہم انہیں آپ کے انوائس بھیجنے سے پہلے ہی پکڑ لیتے ہیں۔
کیس 2 استعمال کریں – کراس سروس لاگ ارتباط
ماحول: مکمل طور پر مقامی – ڈوکر کمپوز، تین ازگر خدمات
زبان: ازگر
پیداوار کے مسائل
صارف کی اطلاع ہے کہ ادائیگی ناکام ہوگئی۔ لاگنگ ٹول کھولیں اور اسے تلاش کریں۔ تصدیق کی خدمت نے ایک کامیاب تصدیق ریکارڈ کی ہے۔ لیجر سروس نے ایک کامیاب ٹرانزیکشن ریکارڈ کی، لیکن نوٹیفکیشن سروس، جسے ادائیگی کی تصدیقی ای میل بھیجنی چاہیے تھی، نے کچھ بھی ریکارڈ نہیں کیا۔
دو خدمات نے کامیابی کی اطلاع دی، لیکن ایک خاموش رہا۔ ادائیگی اب بھی ناکام ہو گئی اور 3 لاگز ہیں لیکن کوئی واضح جواب نہیں کہ یہ سلسلہ کہاں ٹوٹا تھا۔
نظام کی سطح پر اصل میں کیا ہو رہا ہے؟
یہ کیا نہیں ہے: یہ لاگ ایگریگیشن ٹول انسٹال کرنے کے لیے گائیڈ نہیں ہے۔ سب سے پہلے، یہ ان ڈیٹا ڈھانچے کے بارے میں ہے جو لاگ کو ارتباط کو فعال کرتے ہیں اور کیا ہوتا ہے جب وہ ڈھانچے ایک سروس کے خرابی کے راستے میں ٹوٹ جاتے ہیں۔
ایک ہی سروس والے سسٹمز میں ڈیبگ کرنا آسان ہے۔ ایک سروس، ایک لاگ فائل، ایک ٹائم لائن۔ تاہم، اگر صارف کی درخواست متعدد خدمات سے گزرتی ہے، تو آپ کو تمام لاگز کو آپس میں جوڑنے کا طریقہ درکار ہوگا۔ اس لنک کو ٹریکنگ آئی ڈی کہا جاتا ہے۔
اسے کسی سرکاری دفتر میں ٹکٹ نمبر کی طرح سمجھیں۔ جب آپ اندر جائیں گے تو آپ کو A247 جیسا نمبر نظر آئے گا۔ کوئی بھی ڈیسک جو آپ کے کیس کو سنبھالتا ہے آپ کی فائل میں A247 نوٹ کرے گا۔ جب کوئی مسئلہ پیدا ہوتا ہے، مینیجرز تمام ریکارڈز کو A247 میں کھینچتے ہیں تاکہ یہ دیکھا جا سکے کہ ہر میز پر اور ترتیب سے کیا ہوا ہے۔ یہ ٹریکنگ آئی ڈی ہے۔ درخواست پر کارروائی کرنے والی تمام سروسز کے ذریعے اشتراک کردہ ایک نمبر۔
ڈیمو میں، جب کوئی ادائیگی آتی ہے، توثیق کی خدمت ادائیگی کے لیے ایک منفرد ID بناتی ہے۔ اس ادائیگی کے لیے تصدیق، لیجر، اور اطلاعات کو ریکارڈ کرنے والی تمام لاگ لائنیں ایک ہی ID پر مشتمل ہوں گی۔ اگر کچھ ٹوٹ جائے تو دوڑو correlate.py اس ID کا استعمال کرتے ہوئے، ہم تینوں خدمات سے تمام متعلقہ لاگ لائنیں تلاش کرتے ہیں اور انہیں وقت کے مطابق ترتیب دیتے ہیں۔
python correlate.py pay-abc123
لاگ درج ذیل ہے: تمام لائنیں ایک جیسی ہیں۔ trace_id:
{"timestamp": "2026-05-01T14:23:01.234Z", "trace_id": "pay-abc123", "service": "auth", "event": "user_authenticated", "level": "INFO", "user_id": "u_789", "duration_ms": 12}
{"timestamp": "2026-05-01T14:23:01.891Z", "trace_id": "pay-abc123", "service": "ledger", "event": "transaction_recorded", "level": "INFO", "amount": 50.0, "currency": "USD"}
{"timestamp": "2026-05-01T14:23:02.103Z", "trace_id": "pay-abc123", "service": "notification", "event": "email_queued", "level": "INFO", "recipient": "user@example.com"}
اب یہاں مسئلہ ہے۔ اطلاع کی خدمت ای میل فراہم کنندہ کنکشن کے ٹائم آؤٹ پر پہنچ گئی ہے۔ ڈویلپر جس نے ایرر ہینڈلر لکھا تھا وہ ٹریس آئی ڈی شامل کرنا بھول گیا تھا، لہذا مناسب لاگ لائن کے بجائے، وہ لکھتا ہے:
2026-05-01T14:23:02.415Z ERROR Connection timeout to email provider smtp.example.com:587
ایک خرابی پیش آ گئی۔ لاگ لائن موجود ہے۔ لیکن کیونکہ ایسی کوئی بات نہیں ہے۔ trace_id، correlate.py اسے نہیں مل سکتا۔
اطلاعات اب بھی آپ کی ٹائم لائن میں ظاہر ہوں گی اور آپ دیکھ سکتے ہیں: email_send_attempt – لیکن email_queued اسے کبھی کاپی نہ کریں۔
Timeline — 5 events across 3 service(s):
[2026-05-15T21:59:00.605307+00:00] [AUTH] [INFO] payment_request_received
[2026-05-15T21:59:00.606008+00:00] [AUTH] [INFO] user_authenticated
[2026-05-15T21:59:00.617331+00:00] [LEDGER] [INFO] transaction_recorded
[2026-05-15T21:59:00.630313+00:00] [NOTIFICATION] [INFO] email_send_attempt
[2026-05-15T21:59:00.685182+00:00] [AUTH] [INFO] payment_complete
کوششیں ہوتی ہیں، لیکن ناکامی نہیں ہوتی۔ ڈویلپر ایک فیلڈ بھول گیا۔
ڈیمو کی ترتیبات
اگلے پر جائیں۔ 02-log-correlation/ ہم تین خدمات شروع کرتے ہیں:
cd 02-log-correlation
docker compose up -d
اس سے تصدیق، لیجر، اور اطلاع کی خدمات شروع ہوتی ہیں۔ کچھ لاگ بنانے کے لیے ادائیگی کی درخواست چلائیں۔
./trigger_request.sh

اسکرپٹ استعمال شدہ ٹریکنگ ID کو پرنٹ کرتا ہے۔ مکمل کام کرنے کا راستہ دیکھنے کے لیے، ID کو کاپی کریں اور ارتباط کا اسکرپٹ چلائیں۔
python correlate.py pay-5831e1bf
آپ کو کچھ اس طرح نظر آنا چاہئے (ٹریکنگ آئی ڈیز مختلف ہیں، لیکن ساخت ایک جیسی ہے):
Loading logs from ./logs/...
Loaded 6 structured log lines.
============================================================
Trace ID: pay-5831e1bf
============================================================
Timeline - 6 events across 3 service(s):
[2026-05-15T21:42:28.079046+00:00] [AUTH] [INFO] payment_request_received
service: auth
user_id: u_789
amount: 50.0
[2026-05-15T21:42:28.080718+00:00] [AUTH] [INFO] user_authenticated
service: auth
user_id: u_789
duration_ms: 12
[2026-05-15T21:42:28.145528+00:00] [LEDGER] [INFO] transaction_recorded
service: ledger
user_id: u_789
amount: 50.0
currency: USD
[2026-05-15T21:42:28.210088+00:00] [NOTIFICATION] [INFO] email_send_attempt
service: notification
recipient: user@example.com
[2026-05-15T21:42:28.347893+00:00] [NOTIFICATION] [INFO] email_queued
service: notification
recipient: user@example.com
amount: 50.0
[2026-05-15T21:42:28.378402+00:00] [AUTH] [INFO] payment_complete
service: auth
user_id: u_789
amount: 50.0

یہ ادائیگی کا پورا سفر ہے، بشمول تصدیق، لیجر، اور اطلاعات بالکل اسی ترتیب میں جس میں وہ واقع ہوئے ہیں۔ اب دیکھتے ہیں کہ اسکرپٹ کیسے کام کرتا ہے۔
سکرپٹ
# correlate.py
import json
import os
import sys
SERVICES = ["auth", "ledger", "notification"]
LOG_DIR = "./logs"
def load_logs(log_dir):
"""
Read each service's log file and parse every line as JSON.
Lines that fail JSON parsing are printed as warnings.
They are not silently dropped - a plain-text error line in a service
that should emit structured logs is itself evidence worth seeing.
"""
all_lines = []
for service in SERVICES:
log_file = os.path.join(log_dir, f"{service}.log")
if not os.path.exists(log_file):
print(f" WARNING: No log file for '{service}' at {log_file}")
continue
with open(log_file) as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
parsed = json.loads(line)
parsed["_source"] = service
all_lines.append(parsed)
except json.JSONDecodeError:
# This line exists in the log but cannot be correlated.
print(f" WARNING: {service}.log line {line_num} is not structured JSON:")
print(f" {line[:100]}")
print(f" This line will NOT appear in any trace-based search.")
return all_lines
def correlate(trace_id, all_lines):
"""
Find every log line with this trace_id and sort by timestamp.
The sorted result is the reconstructed timeline of the request.
"""
matched = [line for line in all_lines if line.get("trace_id") == trace_id]
matched.sort(key=lambda x: x.get("timestamp", ""))
return matched
def find_missing_services(matched):
"""
Check which services produced zero trace-tagged lines for this request.
A missing service is not just an absence - it is a signal.
Either the request never reached that service, or an error path swallowed
the trace ID. Both are worth investigating.
"""
services_seen = {line["_source"] for line in matched}
return [s for s in SERVICES if s not in services_seen]
def print_timeline(trace_id, matched, missing):
print(f"n{'=' * 60}")
print(f"Trace ID: {trace_id}")
print(f"{'=' * 60}")
if not matched:
print("nNo structured log lines found with this trace ID.")
print("Either the trace ID is wrong, or no service emitted")
print("a structured log line for this request.")
return
services_count = len({line["_source"] for line in matched})
print(f"nTimeline - {len(matched)} events across {services_count} service(s):n")
for line in matched:
ts = line.get("timestamp", "unknown")
service = line.get("_source", "unknown").upper()
event = line.get("event", "unknown event")
level = line.get("level", "INFO")
extras = {k: v for k, v in line.items()
if k not in ("timestamp", "trace_id", "event", "level", "_source")}
print(f" [{ts}] [{service}] [{level}] {event}")
for k, v in extras.items():
print(f" {k}: {v}")
if missing:
print(f"n{'=' * 60}")
print("MISSING TELEMETRY")
print(f"{'=' * 60}")
print(f"These services produced no trace-tagged events for trace {trace_id}:n")
for s in missing:
print(f" - {s}")
print()
print("This means one of three things:")
print(" 1. The request never reached this service.")
print(" 2. The service received it but an error path swallowed the trace ID,")
print(" leaving a plain-text log line that trace correlation cannot find.")
print(" 3. This service's log file was not included in this run.")
print()
print("Check the raw log file for a plain-text error line around the same timestamp.")
print("If one exists, that is your root cause - and a structured logging gap to fix.")
def run(trace_id):
print(f"Loading logs from {LOG_DIR}/...")
all_lines = load_logs(LOG_DIR)
print(f"Loaded {len(all_lines)} structured log lines.n")
matched = correlate(trace_id, all_lines)
missing = find_missing_services(matched)
print_timeline(trace_id, matched, missing)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python correlate.py ")
print("Example: python correlate.py pay-abc123")
sys.exit(1)
run(sys.argv[1])
اسکرپٹ کیسے کام کرتا ہے۔
load_logs ہر سروس سے لاگ فائلیں پڑھیں۔ ہر لائن JSON ہونی چاہیے۔ اگر قطار JSON نہیں ہے تو، ایک انتباہ عام طور پر خرابی کے لاگ میں پرنٹ کیا جائے گا، مطلب یہ ہے کہ ٹریکنگ ID غائب ہے اور اسے ٹریک نہیں کیا جا سکتا۔
correlate دی گئی ٹریس ID سے مماثل تمام لاگز تلاش کرتا ہے اور انہیں وقت کے مطابق ترتیب دیتا ہے۔ یہ پوری سروس میں درخواست کے پورے بہاؤ کو دوبارہ لکھے گا۔
find_missing_services چیک کریں کہ کس سروس میں اس ٹریکنگ آئی ڈی کے لیے کوئی لاگ نہیں ہے۔ یہ آپ کو بتائے گا کہ درخواست کہاں رکی ہے یا ٹریکنگ آئی ڈی کہاں گم ہو گئی ہے۔
print_timeline پوری درخواست کی ٹائم لائن ترتیب سے دکھاتا ہے۔ یہ یہ بھی دکھاتا ہے کہ اگر کوئی چیز صحیح طریقے سے لاگ ان نہیں ہوئی ہے تو کون سی خدمات غائب ہیں۔
ایک حقیقی Kubernetes ماحول میں اسے استعمال کرتے وقت ذہن میں رکھنے کی ایک چیز:
Kubernetes میں kubectl logs صرف فی الحال چلنے والے کنٹینرز دکھاتا ہے۔
ایک بار جب آپ کا پوڈ دوبارہ شروع ہو جائے تو، آپ استعمال کر سکتے ہیں:
kubectl logs --previous
تاہم، یہ صرف آخری ری اسٹارٹ پر لاگو ہوتا ہے۔ اگر آپ Loki یا CloudWatch جیسا لاگنگ سسٹم استعمال نہیں کرتے ہیں، تو آپ کے پرانے لاگز غائب ہو جائیں گے۔
توڑنے کے بعد آؤٹ پٹ
اس سیکشن کا مقصد یہ بتانا ہے کہ کیا ہوتا ہے جب کوئی سروس خاموشی سے ناکام ہو جاتی ہے، یعنی جب لاگ میں کوئی خرابی ہوتی ہے، لیکن اسکرپٹ اسے تلاش نہیں کر پاتا کیونکہ ڈویلپر ایک فیلڈ بھول جاتا ہے۔
break_it.sh یہ نوٹیفکیشن سروس کو ای میل بھیجنے کی کوشش کرنے پر ناکام ہونے پر مجبور کرتا ہے، اور چونکہ ایرر ہینڈلر کو ٹریکنگ آئی ڈی کے بغیر لکھا گیا تھا، اس لیے ناکامی سادہ متن میں لاگ ان ہوتی ہے جسے اصل درخواست سے دوبارہ لنک نہیں کیا جا سکتا۔
چلائیں:
./break_it.sh
یہ پھر ایک نئی درخواست کو متحرک کرتا ہے۔
./trigger_request.sh
پرنٹ شدہ ٹریکنگ آئی ڈی کو کاپی کریں اور پھر اسے جوڑیں۔
python correlate.py pay-xxxxxxxx
آپ کچھ اس طرح دیکھ سکتے ہیں:
Loading logs from ./logs/...
WARNING: notification.log line 10 is not structured JSON:
2026-05-15T21:59:00.681583+00:00 ERROR Connection timeout to email
provider http://mock-email:80/ after 0.001s - failed to send
confirmation to user@example.com
This line will NOT appear in any trace-based search.
Loaded 29 structured log lines.
============================================================
Trace ID: pay-6cf69a8c
============================================================
Timeline - 5 events across 3 service(s):
[2026-05-15T21:59:00.605307+00:00] [AUTH] [INFO] payment_request_received
[2026-05-15T21:59:00.606008+00:00] [AUTH] [INFO] user_authenticated
[2026-05-15T21:59:00.617331+00:00] [LEDGER] [INFO] transaction_recorded
[2026-05-15T21:59:00.630313+00:00] [NOTIFICATION] [INFO] email_send_attempt
[2026-05-15T21:59:00.685182+00:00] [AUTH] [INFO] payment_complete
اس کو غور سے دیکھیں۔ اطلاعات ٹائم لائن پر ہیں اور لاگ ان ہیں۔ email_send_attempt. لیکن email_queued یہ غائب ہے۔ اس کا مطلب ہے کہ ای میل اصل میں نہیں بھیجی گئی تھی، جو کہ غلطی ہے جو بتاتی ہے کہ یہ آپ کی ٹائم لائن پر بالکل کیوں نہیں دکھائی دیتی ہے۔ یہ سب سے اوپر وارننگ میں پوشیدہ ہے، اور آپ کو بتاتا ہے کہ اسکرپٹ کو ایک لائن ملی ہے جس کا تجزیہ نہیں کیا جا سکتا۔
یہی مسئلہ ہے۔ میں کوششیں دیکھتا ہوں، لیکن میں ناکامیاں نہیں دیکھتا۔
چلائیں cat logs/notification.log نیچے تک سکرول کریں۔
{"timestamp": "2026-05-15T21:59:00.630313+00:00", "trace_id": "pay-6cf69a8c",
"service": "notification", "event": "email_send_attempt", ...}
2026-05-15T21:59:00.681583+00:00 ERROR Connection timeout to email provider
http://mock-email:80/ after 0.001s - failed to send confirmation to user@example.com
نوٹ کی دو سطریں: پہلی سطر میں ٹریکنگ ID ہوتی ہے جسے اسکرپٹ نے ٹائم لائن میں پایا اور دکھایا۔ دوسرا ایسا نہیں ہے۔ اسکرپٹ نے اسے انتباہ کے طور پر نشان زد کیا اور اسے چھوڑ دیا۔ غلطی کوشش کے 0.075 سیکنڈ بعد ہوئی۔ لاگ فائل میں دونوں لائنیں ہیں۔ ٹائم لائن پر صرف ایک ہے۔
وہ کیا ہے "غیر مرئی ناکامی” ایسا لگتا ہے کہ یہ پیداوار میں ہے۔ ادائیگی پر کارروائی ہو چکی ہے۔ کوئی تصدیقی ای میل نہیں بھیجی گئی۔ غلطی وہیں لاگ فائل میں ہے۔ Connection timeout to email provider after 0.001s تاہم، مندرجہ بالا ارتباط کی پیداوار ایک ٹائم لائن کو ظاہر کرتی ہے۔ email_send_attempt پھر سیدھے اندر کودیں۔ payment_complete درمیان میں کچھ نہیں ہے۔ کوئی غلطی نہیں، کوئی ناکامی، کوئی خلا نہیں ہے۔ ایسا لگتا ہے کہ سب کچھ کام کر گیا ہے۔
یہاں اصلاحات ہیں: 02-log-correlation/services/notification/main.py. ٹوٹا ہوا ایرر ہینڈلر یہ ہے:
except httpx.TimeoutException:
emit_plain(f"Connection timeout to email provider {EMAIL_PROVIDER_URL}")
return {"status": "ok"}
اور یہاں ترمیم شدہ ورژن ہے۔ صرف تبدیلی گزر رہی ہے۔ req.trace_id ~ میں emit کال کرنے کے بجائے emit_plain:
except httpx.TimeoutException:
emit(req.trace_id, "email_timeout", level="ERROR",
provider=EMAIL_PROVIDER_URL)
return {"status": "ok"}
جب کوئی تبدیلی کی جائے گی، ٹائم آؤٹ ایرر باقی سب کی طرح ٹائم لائن پر ظاہر ہوگا۔
[2026-05-15T21:59:00.681583+00:00] [NOTIFICATION] [ERROR] email_timeout
provider: http://mock-email:80/
ایک کمانڈ، ایک ٹریکنگ آئی ڈی، پوری تصویر۔
وہ فیصلے جو اسکرپٹ نہیں کر سکتا
ارتباط کا اسکرپٹ اطلاعات کو خالی کے طور پر شناخت کرتا ہے۔ اگر آپ اصل متن کو چیک کرتے ہیں، notification.logدرخواست سروس تک پہنچ گئی اور تصدیق اور لین دین دونوں کی ریکارڈنگ کامیاب رہی، لیکن مجھے ایک سادہ ٹیکسٹ ٹائم آؤٹ ایرر ملا جس میں بتایا گیا کہ ای میل ناکام ہو گئی۔
آیا نوٹیفکیشن کی ناکامی ادائیگی کی ناکامی کا مکمل انحصار اس بات پر ہے کہ سسٹم کو کس طرح ڈیزائن کیا گیا ہے۔ اگر نوٹیفکیشن نرم انحصار ہے، تو اس غلطی کو صارف کو ادائیگی کی ناکامی کے طور پر نہیں دکھایا جانا چاہیے، لیکن سسٹم کے ڈیزائن میں کچھ اور غلط ہے۔ اگر یہ ایک سخت انحصار ہے، تو لین دین کو خود ہی واپس کر دینا چاہیے۔ اسکرپٹ کو پتہ چل جائے گا کہ مسئلہ کہاں ہے، لیکن درست جواب آپ کے ڈیزائن پر منحصر ہوگا۔
جان بوجھ کر توڑ دو
-
چلائیں
./break_it.sh– نوٹیفکیشن سروس کو ایک موڈ میں رکھیں جہاں ایرر ہینڈلر ٹریکنگ آئی ڈی کو ضائع کر دیتا ہے۔ -
چلائیں
./trigger_request.shنئی ادائیگی کی درخواست بنانے اور ایک نیا ٹریکنگ ID حاصل کرنے کے لیے -
چلائیں
python correlate.py– ٹائم لائن سے اطلاعات غائب ہیں۔ -
چلائیں
cat logs/notification.log– ٹائم آؤٹ کی غلطیاں براہ راست اسکرپٹ میں ٹریکنگ ID کے بغیر ہوتی ہیں۔
کیس 3 کا استعمال کریں – انفراسٹرکچر ڈرفٹ کا پتہ لگانا
ماحول: AWS فری ٹائر (1 سیکیورٹی گروپ) + ٹیرافارم
زبان: ازگر
پیداوار کے مسائل
مجھے اپنے Terraform پلان میں کوئی تبدیلی نظر نہیں آ رہی ہے۔ تعیناتیاں کل کی نسبت مختلف طریقے سے کام کر رہی ہیں، اور اگر آپ آس پاس سے پوچھیں گے، تو آخرکار کوئی یاد کرے گا۔ ایک ساتھی کارکن نے اسٹیجنگ ٹیسٹوں کو غیر مسدود کرنے کے لیے پچھلے ہفتے AWS کنسول میں سیکیورٹی گروپ میں فوری دستی تبدیلی کی۔ انہوں نے واپس جانے اور ٹیرافارم کے ذریعے اسے لاگو کرنے کی کوشش کی، لیکن اس کے بارے میں بھول گئے۔
ٹیرافارم اسٹیٹ فائلز اور اصل AWS انفراسٹرکچر تب سے خاموشی سے متفق نہیں ہیں۔ یہ زور سے نہیں ٹوٹتا اور نہ ہی الارم کا سبب بنتا ہے۔ ٹیرافارم کو اس وقت تک معلوم نہیں ہوگا جب تک کہ کوئی اسے نہ چلائے۔ terraform plan میں نے چیک کیا، لیکن اس منظر نامے میں کسی نے نہیں کیا۔
اسے انفراسٹرکچر ڈرفٹ کہا جاتا ہے، اور یہ اس سے کہیں زیادہ عام ہے جتنا کہ زیادہ تر ٹیمیں تسلیم کرتی ہیں۔
نظام کی سطح پر اصل میں کیا ہو رہا ہے؟
یہ کیا نہیں ہے: یہ بھاگنے کی طرح نہیں ہے۔ terraform plan. منصوبہ ظاہر کرتا ہے کہ Terraform کیا ہے۔ ہو جائے گا تبدیلی یہ اسکرپٹ دکھاتا ہے۔ پہلے ہی AWS میں Terraform کو جانے بغیر تبدیلی کی گئی۔
اسکرپٹ خود ٹیرافارم کمانڈز نہیں چلاتی ہے۔ پہلے سے تیار کردہ ٹیرافارم اسٹیٹ فائل کو پڑھیں۔ ڈیمو میں، Terraform اس فائل کو تخلیق کرتا ہے۔ حقیقی دنیا میں، یہ پہلے سے ہی عام کام کے بہاؤ میں موجود ہے۔
ٹیرافارم کی اسٹیٹ فائل کو ایک رسید کے طور پر سوچیں۔ Terraform بالکل وہی چیز ریکارڈ کرتا ہے جب آپ سیکیورٹی گروپ بناتے ہیں: قواعد، بندرگاہیں، اور CIDRs۔ وہ رسید آپ کی اسٹیٹ فائل ہے۔
اسکرپٹ اس رسید کا اس رسید سے موازنہ کرتا ہے جو اصل میں AWS کے پاس ہے۔ اگر کوئی AWS کنسول پر جاتا ہے اور کوئی ایسا قاعدہ شامل کرتا ہے جو رسید میں نہیں ہے، تو اسکرپٹ اسے بڑھے ہوئے کے طور پر نشان زد کرے گا۔
بلائنڈ اسپاٹ یہ ہے کہ اگر کوئی کنسول میں بالکل نیا سیکیورٹی گروپ بناتا ہے اور Terraform کو بالکل استعمال نہیں کرتا ہے تو اس کی کوئی رسید نہیں ہے۔ اسکرپٹ کا موازنہ کسی ایسی چیز سے نہیں کیا جا سکتا جسے آپ نے کبھی نہیں دیکھا۔ یہ صاف طور پر واپس آتا ہے اور گروپ آپ کے اکاؤنٹ میں نہیں پایا جاتا ہے۔
ڈیمو دونوں کو دکھاتا ہے۔ پہلے، معلوم وسائل کو روکیں۔ پھر --invisible منظر نامہ Terraform کے باہر کچھ بالکل نیا بناتا ہے، اور اب اسکرپٹ صاف طور پر واپس آتا ہے چاہے اکاؤنٹ میں اضافی سیکیورٹی گروپ موجود ہوں۔
ڈیمو کی ترتیبات
اگلے پر جائیں۔ 03-drift-detection/ ساتھی ذخیرہ سے:
cd 03-drift-detection
pip install -r requirements.txt
ترتیبات چلائیں۔ یہ حقیقی Terraform استعمال کرتا ہے، ایک مذاق نہیں.
./setup.sh
یہ چلتا ہے۔ terraform init اور terraform applyایک حقیقی AWS سیکیورٹی گروپ بنائیں۔

میں حقیقی مصنوعات بھی استعمال کرتا ہوں۔ terraform.tfstate فائل یہ دیکھنے کے لیے کہ Terraform اصل میں کیا پیدا کرتا ہے، اسے ٹیکسٹ ایڈیٹر میں کھولیں۔ یہ JSON ہے، یہ پڑھنے کے قابل ہے، اور یہ حقیقی ہے۔

سیٹ اپ مکمل ہونے کے بعد، اسکرپٹ کو چلائیں۔
python detect_drift.py terraform.tfstate
آپ کو درج ذیل کی طرح کچھ نظر آئے گا، لیکن اصل سیکورٹی گروپ ID مختلف ہے:
Loading Terraform state from: terraform.tfstate
Checking: sg-0a1b2c3d4e5f6a7b8
OK - No drift detected.
لیب زندہ ہے اور معاہدے کے دونوں فریق متفق ہیں۔ اب دیکھتے ہیں اسکرپٹ کیا کرتا ہے۔
اسکرپٹ (کوڈ فائل)
# detect_drift.py
import boto3
import json
import sys
def load_tfstate(path):
"""
The Terraform state file is plain JSON - open it in any text editor
and you will see a 'resources' array listing everything Terraform knows about.
This function reads that file and returns the parsed contents.
"""
with open(path) as f:
return json.load(f)
def get_security_groups_from_state(tfstate):
"""
Walk through the resources array and collect every security group entry.
Each resource has a 'type', a 'name', and an 'instances' array holding
the attribute values Terraform recorded when it last ran.
We extract the resource ID and the ingress (inbound) rules.
"""
resources = {}
for resource in tfstate.get("resources", []):
if resource["type"] == "aws_security_group":
for instance in resource.get("instances", []):
sg_id = instance["attributes"]["id"]
resources[sg_id] = {
"ingress": instance["attributes"].get("ingress", [])
}
return resources
def get_security_group_from_aws(sg_id):
"""
Call the AWS EC2 API to fetch the live current state of this security group.
Under the hood, boto3 constructs an authenticated HTTPS request, signs it with
your AWS credentials, sends it to the EC2 API endpoint in your configured region,
and parses the response. The response contains far more data than we need -
we extract only the inbound rules.
"""
ec2 = boto3.client("ec2")
response = ec2.describe_security_groups(GroupIds=[sg_id])
sg = response["SecurityGroups"][0]
return {"ingress": sg.get("IpPermissions", [])}
def normalize_state_rules(rules):
"""
Terraform stores ingress rules in its own format.
We normalize them into a set of tuples for easy comparison.
Each tuple is: (from_port, to_port, protocol, cidr_block)
"""
normalized = set()
for rule in rules:
for cidr in rule.get("cidr_blocks", []):
normalized.add((
rule.get("from_port", 0),
rule.get("to_port", 0),
rule.get("protocol", "-1"),
cidr
))
return normalized
def normalize_aws_rules(rules):
"""
AWS returns ingress rules in a different format from Terraform's.
We normalize them into the same tuple shape so the comparison works.
"""
normalized = set()
for rule in rules:
from_port = rule.get("FromPort", 0)
to_port = rule.get("ToPort", 0)
protocol = rule.get("IpProtocol", "-1")
for ip_range in rule.get("IpRanges", []):
normalized.add((from_port, to_port, protocol, ip_range["CidrIp"]))
return normalized
def detect_drift(tfstate_path):
print(f"Loading Terraform state from: {tfstate_path}")
tfstate = load_tfstate(tfstate_path)
state_sgs = get_security_groups_from_state(tfstate)
if not state_sgs:
print("No security groups found in state file. Nothing to compare.")
return
drift_found = False
for sg_id, state_data in state_sgs.items():
print(f"nChecking: {sg_id}")
try:
aws_data = get_security_group_from_aws(sg_id)
except Exception as e:
print(f" ERROR: Could not fetch {sg_id} from AWS - {e}")
print(f" Check your IAM permissions: ec2:DescribeSecurityGroups is required.")
continue
state_rules = normalize_state_rules(state_data["ingress"])
aws_rules = normalize_aws_rules(aws_data["ingress"])
# Rules in AWS that Terraform does not know about (manual additions)
added_in_aws = aws_rules - state_rules
# Rules Terraform expects that no longer exist in AWS (manual deletions)
removed_from_aws = state_rules - aws_rules
if added_in_aws:
drift_found = True
print(" DRIFT - Rules present in AWS but missing from state file:")
for rule in added_in_aws:
print(f" Port {rule[0]}-{rule[1]} | Protocol: {rule[2]} | CIDR: {rule[3]}")
if removed_from_aws:
drift_found = True
print(" DRIFT - Rules in state file but removed from AWS:")
for rule in removed_from_aws:
print(f" Port {rule[0]}-{rule[1]} | Protocol: {rule[2]} | CIDR: {rule[3]}")
if not added_in_aws and not removed_from_aws:
print(" OK - No drift detected.")
print("n" + "=" * 60)
if drift_found:
print("Drift detected. See above for details.")
else:
print("No drift detected in monitored resources.")
print("nIMPORTANT: This script only checks resources tracked in your state file.")
print("Resources created manually in AWS without Terraform are invisible to this check.")
print("A clean output here does not mean your AWS account is clean - it means")
print("the resources you are watching match what Terraform last recorded.")
if __name__ == "__main__":
tfstate_path = sys.argv[1] if len(sys.argv) > 1 else "terraform.tfstate"
detect_drift(tfstate_path)
اسکرپٹ کیسے کام کرتا ہے۔
load_tfstate کھولتا ہے terraform.tfstate اور اسے پڑھیں۔ چلائیں cat terraform.tfstate سیٹ اپ کے بعد، آپ دیکھیں گے کہ یہ صرف ایک ٹیکسٹ فائل ہے اور جو کچھ Terraform آپ کے انفراسٹرکچر کے بارے میں جانتا ہے وہ یہاں محفوظ ہے۔
get_security_groups_from_state اس فائل سے، ہمیں تمام سیکیورٹی گروپس، AWS کے ذریعے تفویض کردہ IDs، اور Terraform کے لکھے ہوئے آخری ان باؤنڈ اصول ملتے ہیں۔ متوقع قدریں ہیں:
get_security_group_from_aws AWS API کو کال کریں اور اسی سیکیورٹی گروپ کے لیے موجودہ ان باؤنڈ قوانین حاصل کریں۔ یہ اصل قدر ہے۔ اسکرپٹ میں اب ایک ہی مواد کے دو ورژن ہیں۔
normalize_state_rules اور normalize_aws_rules یہ موجود ہے کیونکہ Terraform اور AWS ایک جیسے اصولوں کو قدرے مختلف فارمیٹس میں اسٹور کرتے ہیں۔ موازنہ کام کرتا ہے کیونکہ یہ دونوں افعال ایک ہی قسم میں تبدیل ہوتے ہیں۔
موازنہ آخری مرحلہ ہے۔ ایک اصول دستی طور پر شامل کیا گیا جو AWS میں موجود ہے لیکن ریاستی فائل میں نہیں ہے۔ وہ قواعد جو اسٹیٹ فائل میں ہیں لیکن AWS میں نہیں ہیں دستی طور پر حذف کردیئے گئے ہیں۔ اسکرپٹ دونوں کو پرنٹ کرتا ہے۔
آؤٹ پٹ نتیجہ
صاف، بہاؤ سے پاک عملدرآمد:
Loading Terraform state from: terraform.tfstate
Checking: sg-0a1b2c3d4e5f6a7b8
OK - No drift detected.
============================================================
No drift detected in monitored resources.
IMPORTANT: This script only checks resources tracked in your state file.
Resources created manually in AWS without Terraform are invisible to this check.
A clean output here does not mean your AWS account is clean - it means
the resources you are watching match what Terraform last recorded.
ڈرفٹ انجیکشن کے بعد:

Loading Terraform state from: terraform.tfstate
Checking: sg-0a1b2c3d4e5f6a7b8
DRIFT - Rules present in AWS but missing from state file:
Port 22-22 | Protocol: tcp | CIDR: 0.0.0.0/0
============================================================
Drift detected. See above for details.

وہ فیصلے جو اسکرپٹ نہیں کر سکتا
اسکرپٹ بڑھے کی تلاش کرتا ہے، جو کہ ایک اندرونی اصول ہے جس کے بارے میں ٹیرافارم نہیں جانتا ہے۔ جبلت دوڑ کر فوری طور پر واپس آنا ہے: terraform applyلیکن اس سے پہلے ایک سوال پوچھ لیں۔ کیا یہ تبدیلی ایک ہنگامی ہاٹ فکس تھی؟ کسی نے سمجھوتہ شدہ سروس کو بحال کرنے کے لیے 2 بجے دستی طور پر بندرگاہ کھولی ہو گی جب کہ مناسب حل موجود تھا۔ اور خودکار پر واپس آنا ان چیزوں کو بھی کالعدم کر سکتا ہے جو آپ نے جان بوجھ کر اپنی سروس کو جاری رکھنے کے لیے رکھی ہیں۔
بہاؤ کا پتہ لگانا آپ کو بتاتا ہے کہ چیزیں مختلف ہیں۔ یہ آپ کو یہ نہیں بتاتا کہ کون سا ورژن درست ہے، اور اس کی چھان بین کرنا اسکرپٹ کے چلنے کے بعد ہوتا ہے۔
جان بوجھ کر توڑ دو
-
چلائیں
./break_it.sh. یہ براہ راست AWS CLI کے ذریعے SSH ان باؤنڈ رول (پورٹ 22) کو شامل کرکے دستی کنسول کی تبدیلی کی نقل کرتا ہے۔ -
چلائیں
python detect_drift.py terraform.tfstate. آلگائے آؤٹ پٹ میں ظاہر ہوتا ہے۔ -
چلائیں
./break_it.sh --invisibleایک نیا سیکیورٹی گروپ بنانے کے لیے اسکرپٹ کو دوبارہ چلائیں جو آپ کی اسٹیٹ فائل میں بالکل نہیں ہے۔ یہاں تک کہ اگر آپ کے اکاؤنٹ میں نئے وسائل موجود ہیں، تو وہ صاف طور پر واپس کر دیے جائیں گے تاکہ کوریج میں کوئی بھی فرق نمایاں ہو جائے۔ -
چلائیں
./teardown.sh. ایک بار مکمل ہونے کے بعد، درج ذیل چلیں گے:terraform destroyسیکیورٹی گروپ کو حذف کریں اور تمام AWS وسائل کو صاف کریں۔ اس کے بعد آپ سے کوئی چارج نہیں لیا جائے گا۔
کیس 4 استعمال کریں – بغیر ڈاؤن ٹائم کے خفیہ گردش
ماحول: AWS سیکرٹس مینیجر + مقامی قسم کا کلسٹر
زبان: ازگر
پیداوار کے مسائل
اس استعمال کے کیس کے مقاصد: Kubernetes کا کہنا ہے کہ پوڈ صحت مند ہے، لیکن صارفین کو ڈیٹا بیس کی خرابی ملتی ہے. اسکرپٹ ان خالی جگہوں کو پکڑتا ہے اس سے پہلے کہ صارفین ایک اضافی چیک چلانے سے متاثر ہوں جو Kubernetes کبھی نہیں چلتا ہے۔
ڈیٹا بیس کی اسناد کو تبدیل کریں۔ پوڈ دوبارہ شروع ہو جائے گا. kubectl get pods یہ دوڑتا دکھاتا ہے۔ صارفین 10 منٹ کے بعد لاگ ان نہیں ہو سکیں گے۔
لوپ نے کام کیا، لیکن مسئلہ یہ تھا کہ کبرنیٹس نے چیک کیا کہ آیا HTTP سرور زندہ ہے، نہ کہ یہ ڈیٹا بیس سے تصدیق کر سکتا ہے۔ یہ دو مختلف چیزیں ہیں۔
اصل میں کیا ہو رہا ہے؟
یہ کیا نہیں ہے: یہ اس بارے میں نہیں ہے کہ کوبرنیٹس میں راز کو کیسے ذخیرہ کیا جائے۔ یہ اس کے بارے میں ہے کہ رازوں کو تبدیل کرنے کے بعد کیا ہوتا ہے۔
اگر پوڈ پہلے سے چل رہا ہے، تو گردش ہونے سے پہلے اس میں کھلے، تصدیق شدہ ڈیٹا بیس کنکشنز کا ایک تالاب ہوگا۔ یہ کنکشن پاس ورڈ کی تبدیلی کے بعد بھی برقرار رہیں گے کیونکہ تبدیلی سے پہلے ان کی توثیق کی گئی تھی اور ڈیٹا بیس انہیں باہر نہیں نکالے گا۔ تاہم، اگر پول کو نیا کنکشن کھولنے کی ضرورت ہے، تو یہ اب بھی پرانے پاس ورڈ سمیت موجودہ ماحولیاتی اسناد کا استعمال کرے گا۔ وہ نیا کنکشن فوری طور پر ناکام ہو جائے گا۔
دریں اثنا، Kubernetes ایسے پوڈز کی جانچ پڑتال کرتا ہے جو HTTP کو جواب دیتے ہیں اور انہیں چلانے کے طور پر نشان زد کرتے ہیں، جس کی وجہ سے آپ بغیر کسی اشارے کے غلطی پیدا کرتے ہیں کہ آپ کے کلسٹر میں کچھ غلط ہے۔
کیا /healthz/db اختتامی نقطہ کیا کرتا ہے۔
/healthz اگر HTTP سرور زندہ ہے، تو یہ 200 لوٹاتا ہے۔ Kubernetes معائنہ کا مقصد یہی ہے۔
/healthz/db موجودہ اسناد کا استعمال کرتے ہوئے نیا ڈیٹا بیس کنکشن کھولیں اور چلائیں۔ SELECT 1. اگر یہ سائیکل چلانے کے بعد ناکام ہوجاتا ہے، تو پوڈ چل رہا ہے لیکن ڈیٹا بیس کی درخواستوں کی خدمت نہیں کرسکتا۔ متبادل اسکرپٹ اس اختتامی نقطہ کو آخری مرحلہ کہتا ہے۔ اس کا مطلب ہے کہ Kubernetes کی تصدیق نہیں چلے گی۔
یہاں ہے ڈیمو FastAPI ایپلیکیشن (کوڈ فائل) کیسی دکھتی ہے:
# app.py (relevant section)
import os
import asyncpg
from fastapi import FastAPI, HTTPException
app = FastAPI()
DB_HOST = os.environ.get("DB_HOST", "postgres")
DB_PORT = int(os.environ.get("DB_PORT", "5432"))
DB_NAME = os.environ.get("DB_NAME", "appdb")
DB_USERNAME = os.environ.get("DB_USERNAME", "appuser")
DB_PASSWORD = os.environ.get("DB_PASSWORD", "")
@app.get("/healthz")
async def healthz():
# Always returns 200 if the HTTP server is alive.
# This is all the Kubernetes readiness probe checks.
return {"status": "ok"}
@app.get("/healthz/db")
async def healthz_db():
# Opens a fresh connection using the current environment credentials.
# If the password was rotated and this pod has not restarted yet,
# the environment still has the old password - this connection fails.
# /healthz above would still return 200. Your users would see errors.
try:
conn = await asyncpg.connect(
host=DB_HOST, port=DB_PORT,
database=DB_NAME, user=DB_USERNAME, password=DB_PASSWORD,
)
await conn.execute("SELECT 1")
await conn.close()
return {"status": "ok", "db": "authenticated"}
except asyncpg.InvalidPasswordError:
raise HTTPException(
status_code=503,
detail=(
f"Authentication failed for '{DB_USERNAME}'. "
"Password may have been rotated. "
"Readiness probe does not check this."
)
)
except Exception as e:
raise HTTPException(status_code=503, detail=f"Database error: {str(e)}")
ان دو اختتامی نکات کے درمیان فرق اس استعمال کے معاملے کا پورا سبق ہے۔
ڈیمو کی ترتیبات
اگلے پر جائیں۔ 04-secrets-rotation/ سیٹ اپ اسکرپٹ چلائیں۔
cd 04-secrets-rotation
./setup.sh
Kind کلسٹر شروع ہو گیا ہے اور اصل PostgreSQL کو تعینات کر دیا گیا ہے۔ appuser ڈیمو FastAPI ایپ کو تعینات کرنے اور AWS Secrets Manager میں ابتدائی راز بنانے کے لیے ایک اکاؤنٹ پہلے سے ہی بنایا اور منسلک ہے۔
سیٹ اپ مکمل ہونے کے بعد، انحصار انسٹال کریں۔
pip install boto3 kubernetes
لوپ چلانے سے پہلے یقینی بنائیں کہ سب کچھ چل رہا ہے۔
kubectl get pods
آپ کو دیکھنا چاہئے myapp اور postgres دونوں پھلیاں چلتی حالت میں ہیں۔ اگر پوڈ زیر التواء یا خرابی دکھاتا ہے، 30 سیکنڈ انتظار کریں اور دوبارہ چیک کریں۔ PostgreSQL کو شروع کرنے میں ایک لمحہ لگے گا۔
آپ یہ بھی تصدیق کر سکتے ہیں کہ آپ کا پاس ورڈ AWS میں بنایا گیا تھا۔ کنسول میں، AWS Secrets Manager پر جائیں اور درج ذیل کو تلاش کریں: myapp/db-credentials:

اگر آپ CLI کو ترجیح دیتے ہیں:
aws secretsmanager get-secret-value --secret-id myapp/db-credentials
اگر دونوں پوڈز چل رہے ہیں اور راز موجود ہے، تو وہ پورے راستے کو چیک کرنے کے لیے ایک لوپ چلاتے ہیں۔
python rotate_secret.py
اگر پہلا کلین اپ رن پر مرحلہ 6 فیلڈ دکھاتا ہے:یہ تقریبا ہمیشہ ایک وقت کا مسئلہ ہے. ایپ پوڈ کامیابی کے ساتھ دوبارہ شروع ہوا، لیکن /healthz/db نیا پوڈ اپنا پہلا ڈیٹا بیس کنکشن قائم کرنے سے پہلے ہی چلا گیا۔ تقریباً 20 سیکنڈ انتظار کریں اور چلائیں۔ python rotate_secret.py دوبارہ اگر یہ بار بار ناکام ہوجاتا ہے تو، چلائیں: kubectl logs deployment/myapp دیکھیں کہ ایپ کیا رپورٹ کرتی ہے۔
آپ دیکھ سکتے ہیں کہ تمام 6 مراحل صفائی کے ساتھ مکمل ہو گئے ہیں اور ہم اس کے ساتھ ختم ہوتے ہیں:
Rotation complete. Credential verified at the application level.
AWS Secrets Manager: updated
PostgreSQL: updated (ALTER USER)
Kubernetes Secret: updated
Application pod: restarted, authenticated
لیبارٹری زندہ ہے اور پوری سرکلر چین سرے سے آخر تک کام کرتی ہے۔ اب دیکھتے ہیں اسکرپٹ کیا کرتا ہے۔
اسکرپٹ (کوڈ فائل)
# rotate_secret.py
import boto3
import base64
import json
import subprocess
import sys
from kubernetes import client, config
def get_current_secret(secret_name):
"""
Fetch the current credential from AWS Secrets Manager.
The secret is stored as a JSON string with 'username' and 'password' fields.
"""
sm = boto3.client("secretsmanager")
response = sm.get_secret_value(SecretId=secret_name)
return json.loads(response["SecretString"])
def rotate_in_aws(secret_name, username, new_password):
"""
Write the new credential to AWS Secrets Manager.
put_secret_value creates a new version - the previous version is
not deleted immediately, giving you a short rollback window.
"""
sm = boto3.client("secretsmanager")
new_value = json.dumps({"username": username, "password": new_password})
sm.put_secret_value(SecretId=secret_name, SecretString=new_value)
print(" [AWS] Secret updated in Secrets Manager.")
def update_kubernetes_secret(namespace, k8s_secret_name, username, new_password):
"""
Patch the Kubernetes Secret object with the new credential values.
Kubernetes requires secret data to be base64-encoded - this is encoding,
not encryption. Anyone with access to the Secret object can decode the values.
Real encryption at rest requires separate etcd encryption configuration.
"""
config.load_kube_config()
v1 = client.CoreV1Api()
secret_data = {
"username": base64.b64encode(username.encode()).decode(),
"password": base64.b64encode(new_password.encode()).decode()
}
v1.patch_namespaced_secret(
name=k8s_secret_name,
namespace=namespace,
body={"data": secret_data}
)
print(f" [K8s] Kubernetes Secret '{k8s_secret_name}' updated.")
def rolling_restart(namespace, deployment_name):
"""
Trigger a rolling restart of the deployment.
Rolling restart means Kubernetes creates one new pod, waits for it to pass
its readiness probe, then terminates one old pod - and repeats until all
pods have been replaced. Availability is preserved throughout.
This is very different from deleting all pods at once.
"""
result = subprocess.run(
["kubectl", "rollout", "restart",
f"deployment/{deployment_name}", "-n", namespace],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"Rolling restart failed: {result.stderr}")
print(f" [K8s] Rolling restart triggered for '{deployment_name}'.")
def wait_for_rollout(namespace, deployment_name, timeout=120):
"""
Block until the rolling restart finishes or times out.
'Finished' means all new pods are Running and their readiness probes passed.
This does NOT mean the application can authenticate with the new credential.
That is what verify_credential checks next.
"""
print(f" [K8s] Waiting for rollout (timeout: {timeout}s)...")
result = subprocess.run(
["kubectl", "rollout", "status",
f"deployment/{deployment_name}",
"-n", namespace,
f"--timeout={timeout}s"],
capture_output=True, text=True
)
if result.returncode != 0:
raise RuntimeError(f"Rollout did not complete: {result.stderr}")
print(" [K8s] Rollout complete. All pods report Ready.")
def verify_credential(namespace, deployment_name):
"""
This is the check the readiness probe does not make.
We exec into the running pod and call /healthz/db - an endpoint that
makes an actual authenticated query to the database.
If this passes: the credential is working at the application level.
If this fails after the readiness probe passed: the contract mismatch is confirmed.
The pod is Running. The application cannot serve database requests.
"""
print(" [Verify] Running post-rotation credential check...")
result = subprocess.run(
["kubectl", "get", "pods", "-n", namespace,
"-l", f"app={deployment_name}",
"-o", "jsonpath={.items[0].metadata.name}"],
capture_output=True, text=True
)
pod_name = result.stdout.strip()
if not pod_name:
print(" [Verify] ERROR: No running pod found for this deployment.")
return False
verify = subprocess.run(
["kubectl", "exec", pod_name, "-n", namespace,
"--", "curl", "-sf", "http://localhost:8000/healthz/db"],
capture_output=True, text=True
)
if verify.returncode != 0:
print(" [Verify] FAILED - Pod is Running but database authentication failed.")
print(" The readiness probe validated HTTP reachability.")
print(" The application cannot authenticate with the new credential.")
print(" These are two different contracts. Only one was checked automatically.")
return False
print(" [Verify] PASSED - Application confirmed it can authenticate with the new credential.")
return True
def rotate(secret_name, new_password, namespace, k8s_secret_name, deployment_name):
print("n[Step 1/6] Reading current secret from AWS Secrets Manager...")
current = get_current_secret(secret_name)
username = current["username"]
print("[Step 2/6] Updating AWS Secrets Manager...")
rotate_in_aws(secret_name, username, new_password)
print("[Step 3/6] Rotating password at the database level (ALTER USER)...")
rotate_postgres_password(namespace, new_password)
print("[Step 4/6] Updating Kubernetes Secret object...")
update_kubernetes_secret(namespace, k8s_secret_name, username, new_password)
print("[Step 5/6] Triggering rolling restart...")
rolling_restart(namespace, deployment_name)
wait_for_rollout(namespace, deployment_name)
print("[Step 6/6] Verifying the new credential works at the application level...")
success = verify_credential(namespace, deployment_name)
print("n" + "=" * 60)
if success:
print("Rotation complete. Credential verified at the application level.")
else:
print("Rotation incomplete. Readiness probe passed but credential verification failed.")
print("Recommended action: force-restart all pods to flush the connection pool,")
print("or investigate the database session timeout configuration.")
sys.exit(1)
if __name__ == "__main__":
import secrets as _secrets
rotate(
secret_name="myapp/db-credentials",
new_password=_secrets.token_urlsafe(16),
namespace="default",
k8s_secret_name="db-credentials",
deployment_name="myapp"
)
اسکرپٹ کیسے کام کرتا ہے۔
get_current_secret نیا پاس ورڈ بنانے سے پہلے، AWS سیکرٹس مینیجر آپ کے موجودہ اسناد کو پڑھتا ہے تاکہ اسکرپٹ کو آپ کا صارف نام معلوم ہو۔
rotate_in_aws سیکرٹس مینیجر کو نئی اسناد لکھیں۔ چونکہ ہم پچھلے ورژن کو اوور رائٹ کرنے کے بجائے ایک نیا ورژن بناتے ہیں، اس لیے کچھ غلط ہونے کی صورت میں واپس رول بیک کرنے کا وقت کم ہوتا ہے۔
_pg_password_literal اور rotate_postgres_password یہ اس قدم کا خیال رکھتا ہے جسے زیادہ تر متبادل اسکرپٹس چھوڑ دیتے ہیں: دراصل PostgreSQL میں پاس ورڈ تبدیل کرنا۔ یہ چلانے سے کیا جاتا ہے: ALTER USER appuser PASSWORD '...' براہ راست پوسٹگری ایس کیو ایل پوڈ سے۔ اس قدم سے پہلے، ڈیٹا بیس اب بھی آپ کا پرانا پاس ورڈ قبول کرے گا۔ اس قدم کے بعد نہیں۔
update_kubernetes_secret نیا پاس ورڈ کوبرنیٹس سیکرٹ میں ریکارڈ کریں تاکہ شروع ہونے والے ہر نئے پوڈ کو شروع سے ہی نئی اسناد ملیں۔
rolling_restart اور wait_for_rollout ایپلیکیشن پوڈز کو ایک وقت میں دوبارہ شروع کریں تاکہ یہ یقینی بنایا جا سکے کہ آپ کی تعیناتی پوری جگہ دستیاب رہے گی۔ ایک بار جب یہ مرحلہ مکمل ہو جاتا ہے، تمام پوڈز چل رہے ہیں اور تیاری کی جانچ گزر چکی ہے۔ لیکن ذہن میں رکھیں کہ "دوڑنے” کا مطلب صرف کچھ ہے۔ /healthz یہ 200 واپس آیا، جو اس استعمال کے معاملے میں مسئلہ ہے۔
verify_credential یہ ایک اضافی قدم ہے جسے Kubernetes کبھی انجام نہیں دیتا ہے۔ نئی پوڈ کے اندر پہنچیں اور اسے کال کریں۔ /healthz/dbیہ پوڈ کے موجودہ ماحول سے اسناد کا استعمال کرتے ہوئے ایک حقیقی ڈیٹا بیس کنکشن کھولتا ہے۔ اگر یہ گزر جاتا ہے تو، گردش اصل میں مکمل ہے. اگر یہ تیاری کی تحقیقات سے گزرنے کے بعد ناکام ہوجاتا ہے، تو آپ نے ایک خلا کی نشاندہی کی ہے۔ اس کا مطلب ہے کہ پوڈ صحت مند دکھائی دیتا ہے لیکن ڈیٹا بیس کی درخواستوں پر کارروائی نہیں کر سکتا۔
آؤٹ پٹ نتیجہ
کامیاب گردش:
[Step 1/6] Reading current secret from AWS Secrets Manager...
[Step 2/6] Updating AWS Secrets Manager...
[AWS] Secrets Manager updated.
[Step 3/6] Rotating password at the database level (ALTER USER)...
[DB] Running ALTER USER on PostgreSQL...
[DB] Password changed at the database level.
New connections now require the new password.
Existing pool connections remain valid until they close.
[Step 4/6] Updating Kubernetes Secret object...
[K8s] Kubernetes Secret 'db-credentials' updated.
[Step 5/6] Triggering rolling restart...
[K8s] Rolling restart triggered for 'myapp'.
[K8s] Waiting for rollout (timeout: 120s)...
[K8s] Rollout complete. All pods report Ready.
[Step 6/6] Verifying the new credential works at the application level...
[Verify] Running post-rotation credential check...
[Verify] PASSED - Application confirmed it can authenticate with the new credential.
============================================================
Rotation complete. Credential verified at the application level.
AWS Secrets Manager: updated
PostgreSQL: updated (ALTER USER)
Kubernetes Secret: updated
Application pod: restarted, authenticated
لیبارٹری زندہ ہے اور پوری سرکلر چین سرے سے آخر تک کام کرتی ہے۔
کسی بھی چیز کو روکنے سے پہلے یقینی بنائیں کہ پھلی صحت مند ہے۔
kubectl get pods
آپ کو دیکھنا چاہئے myapp چلنے کی حیثیت۔ یہ بنیادی باتیں ہیں۔ سب کچھ توقع کے مطابق کام کرتا ہے۔ اب ہم اسے توڑ دیتے ہیں۔

جان بوجھ کر توڑ دو
مرحلہ 1: ڈی بی ڈی سنکرونائزیشن
./break_it.sh
یہ چلتا ہے۔ ALTER USER غلط پاس ورڈ کے ساتھ PostgreSQL سے براہ راست جڑنا۔ K8s سیکریٹ میں اب بھی پرانا پاس ورڈ ہے، اس لیے پوڈ کا ماحول اور ڈیٹا بیس اب ہم آہنگی سے باہر ہے۔
مرحلہ 2: چیک کریں کہ آپ Kubernetes میں کیا دیکھتے ہیں۔
kubectl exec deployment/myapp -- curl -s http://localhost:8000/healthz
آپ دیکھیں گے {"status":"ok"}. پوڈ اب بھی کہتا ہے تیار اندر۔ kubectl get pods. کبرنیٹس کو اندازہ نہیں ہے کہ کیا غلط ہے۔ یہ ٹرمینل میں ظاہر ہونے والا معاہدہ خالی ہے۔
مرحلہ 3: صارف کے تجربے کی تصدیق کریں۔
kubectl exec deployment/myapp -- curl -s http://localhost:8000/healthz/db
آپ دیکھ سکتے ہیں 503 غلطی نیا ڈیٹا بیس کنکشن ناکام ہو جاتا ہے۔ آپ کے صارفین اسے پہلے ہی دیکھ رہے ہیں۔
مرحلہ 4: ملاوٹ شدہ پیٹرنز دیکھیں (اختیاری)
./load_test.sh
کچھ درخواستیں کامیاب ہو جاتی ہیں کیونکہ وہ خلل ڈالنے سے پہلے پچھلے توثیق شدہ پول کنکشن تک پہنچ جاتی ہیں۔ کچھ ناکام ہو جاتے ہیں کیونکہ انہیں نئے کنکشن کی ضرورت ہوتی ہے۔ پھلیاں صحت مند نظر آتی ہیں، لیکن ٹریفک کا آدھا حصہ ناکام ہو رہا ہے۔
مرحلہ 5: سرکلر اسکرپٹ چلائیں۔
python rotate_secret.py
اس بار ہم ناکامی کو مرحلہ 6 میں دیکھتے ہیں۔ یہ ہے جو آپ دیکھتے ہیں:
[Step 5/6] Triggering rolling restart...
[K8s] Rollout complete. All pods report Ready.
[Step 6/6] Verifying the new credential works at the application level...
[Verify] Running post-rotation credential check...
[Verify] FAILED - Pod is Running but database authentication failed.
The readiness probe validated HTTP reachability.
The application cannot authenticate with the new credential.
These are two different contracts. Only one was checked automatically.
============================================================
Rotation incomplete. Readiness probe passed but credential verification failed.
پھلی چل رہی ہے اور تیار دکھائی دیتی ہے۔ kubectl get pods. متبادل اسکرپٹ کا کہنا ہے کہ اسناد سے سمجھوتہ کیا گیا ہے۔ یہ کنٹریکٹ وائٹ اسپیس ہے جو ٹرمینل میں نظر آتی ہے، صارف کے دبانے سے پہلے پکڑی جاتی ہے۔
سبق: /healthz یہ آپ کو بتاتا ہے کہ HTTP سرور زندہ ہے۔ /healthz/db یہ آپ کو بتاتا ہے کہ ایپلی کیشن دراصل ڈیٹا بیس سے جڑ سکتی ہے۔ Kubernetes صرف پہلی آئٹم کو چیک کرتا ہے جب تک کہ آپ ڈیٹا بیس پروب شامل نہ کریں۔ متبادل اسکرپٹ ہر متبادل کے آخر میں ایک چیک کا اضافہ کرتا ہے تاکہ آپ غلطیوں کے ناکام ہونے سے پہلے ان کو پکڑ سکیں۔
وہ فیصلے جو اسکرپٹ نہیں کر سکتا
تصدیق ناکام ہوگئی، پوڈ چل رہا ہے، ڈیٹا بیس کی درخواست ناکام ہوگئی۔ دو اختیارات ہیں:
-
کنکشن پول کو فلش کرنے کے لیے ایک ساتھ تمام پوڈز کو دوبارہ شروع کرنے پر مجبور کرتا ہے (تیز، لیکن صلاحیت میں تھوڑی کمی کے ساتھ)۔
-
پچھلے سیشن کے قدرتی طور پر ختم ہونے کا انتظار کریں (یہ ڈاؤن ٹائم کو روکتا ہے، لیکن پل سائیکل تک درخواستوں کو وقفے وقفے سے ناکام ہونے کا سبب بنتا ہے)۔
اسکرپٹ نے مسئلہ دریافت کر لیا ہے، لیکن یہ انجینئر پر منحصر ہے جو سسٹم کو جانتا ہے کہ وہ فیصلہ کرے کہ آگے کیا کرنا ہے۔
گلنا
./teardown.sh
کیس 5 کا استعمال کریں – خودکار کینری رول بیک ٹرگر
ماحول: مکمل طور پر مقامی – قسم، ہیلم کے ذریعے Prometheus
زبان: bash
یہ استعمال کیس کیا کرتا ہے اور یہ کیوں اہمیت رکھتا ہے۔
یہ استعمال کا کیس ایک اسکرپٹ چلاتا ہے جو نئی تعیناتیوں کو دیکھتا ہے اور اگر صارف سپورٹ کی قطار میں سیلاب آنے سے پہلے کچھ غلط ہو جاتا ہے تو خود بخود واپس چلا جاتا ہے۔
یہ پروڈکشن میں اہم ہے کیونکہ جب آپ نیا ورژن جاری کرتے ہیں، تو تمام ٹریفک فوری طور پر نہیں پہنچ جائے گی۔ مثال کے طور پر، ایک چھوٹا ٹکڑا بھیجیں جہاں 20% نئے ورژن میں بھیجے جائیں اور 80% پرانے ورژن میں بھیجے جائیں۔ اگر کوئی نیا ورژن ٹوٹ جاتا ہے، تو صرف 20% صارفین متاثر ہوں گے اور نقصان پھیلنے سے پہلے اسے واپس لیا جا سکتا ہے۔ لیکن رول بیک صرف اس صورت میں کام کرتا ہے جب آپ صحیح مواد کو دیکھ رہے ہوں۔
گھر میں کیا لے جانا ہے: دو اسکرپٹ ایک ہی ناکام کینری کے لیے دیکھتے ہیں۔ ایک اطلاع دیتا ہے کہ سب کچھ ٹھیک ہے۔ دوسرا ایک رول بیک شروع کرتا ہے۔ فرق صرف وہی ہے جو ناپا جاتا ہے۔ آٹومیشن صرف اتنا ہی اچھا ہے جتنا آپ دیکھتے ہیں۔
نوٹ کرنے کی چیزیں: canary_watch_v1.sh یہ صرف غلطیوں کو دیکھتا ہے اور اس وقت تک خاموش رہتا ہے جب تک کینری سست ہو۔ canary_watch_v2.sh غلطیوں اور جوابی اوقات کا مشاہدہ کریں اور رول بیک شروع کریں۔ ان میں فرق سبق کا ہے۔
یہ کیا نہیں ہے: یہ کینری کو تعینات کرنے کا رہنما نہیں ہے۔ یہ اس کے بارے میں ہے کہ جب آپ صرف ایک سگنل کو دیکھتے ہیں تو نگرانی سے کیا کمی محسوس ہوتی ہے۔
یہ کیسے کام کرتا ہے۔
کلسٹر پر تین چیزیں چل رہی ہیں: ایک مستحکم ایپ (3 پوڈز، زیادہ تر ٹریفک کو سنبھالنا)، ایک کینری ایپ (1 پوڈ، ایک چھوٹا سا ٹکڑا ہینڈل کرنا)، اور پرومیتھیس (ہر 15 سیکنڈ میں دونوں جگہوں سے جوابی اوقات اور غلطی کی گنتی جمع کرنا)۔
واچ اسکرپٹ ہر 15 سیکنڈ میں پرومیتھیس سے درخواست کرتی ہے۔ "کیا کینری عام طور پر برتاؤ کر رہی ہے؟” اگر مسلسل تین چیکوں کے لیے جواب نفی میں ہے، تو کینری خود بخود واپس چلا جاتا ہے۔
سوال یہ ہے کہ آپ کیا کرتے ہیں؟ "عام برتاؤ کرو” اوسط؟ یہ استعمال کا پورا معاملہ ہے۔

ڈیمو کی ترتیبات
اگلے پر جائیں۔ 05-canary-rollback/ پھر چلائیں:
cd 05-canary-rollback
./setup.sh
اسے ترتیب دینے میں چند منٹ لگتے ہیں۔ Prometheus انسٹال کریں، ڈیمو ایپ کے دو ورژن لگائیں، اور ایک لوڈ جنریٹر پوڈ شروع کریں جو دونوں ورژنز پر مسلسل ٹریفک بھیجتا ہے تاکہ Prometheus کے پاس ہمیشہ ڈیٹا موجود رہے۔
سیٹ اپ مکمل ہونے کے بعد، یقینی بنائیں کہ سب کچھ چل رہا ہے۔
kubectl get pods
آپ اس طرح آؤٹ پٹ دیکھیں گے:
NAME READY STATUS RESTARTS AGE
load-generator-68c59698b7-kws2l 1/1 Running 0 4m54s
myapp-canary-6d6979c66f-g9lgw 1/1 Running 0 32s
myapp-stable-6bcf994fc4-b4k9l 1/1 Running 0 4m55s
myapp-stable-6bcf994fc4-ndhxc 1/1 Running 0 4m55s
myapp-stable-6bcf994fc4-z97kx 1/1 Running 0 4m55s
prometheus-kube-prometheus-operator-59b847d96c-mp72s 1/1 Running 0 5m58s
prometheus-prometheus-kube-prometheus-prometheus-0 2/2 Running 0 5m1s
میرے پاس 3 مستحکم پوڈز، 1 کینری پوڈ، 1 لوڈ جنریٹر، اور پرومیتھیس چل رہا ہے۔ لیب زندہ ہے۔
براہ کرم کوئی اور کام کرنے سے پہلے 60 سیکنڈ انتظار کریں۔ Prometheus کو Pod سے پہلی میٹرک کو کھرچنے کے لیے وقت درکار ہوتا ہے۔ اگر آپ اسے چھوڑ دیتے ہیں، تو واچ اسکرپٹ بغیر کسی وضاحت کے خالی ڈیٹا لوٹائے گا۔
3-ٹرمینل ونڈو
اس کے لیے بیک وقت تین الگ الگ کمانڈ پرامپٹس کی ضرورت ہے۔
macOS کے لیے: ٹرمینل کھولیں اور دبائیں۔ Cmd+T ڈبل۔ اب آپ کے پاس تین ٹیبز ہیں، جن میں سے ہر ایک آزاد ٹرمینل ہے۔
لینکس کے لیے: دبائیں Ctrl+Shift+T زیادہ تر ٹرمینل ایپس میں، یا دائیں کلک کریں اور "نیا ٹیب کھولیں” کو منتخب کریں۔
واچ اسکرپٹ ٹرمینل 1، ناکامی انجیکشن ٹرمینل 2، اور لیٹنسی آبزرویشن ٹرمینل 3 پر لیبل لگائیں۔
سکرپٹ
ورژن 1: صرف غلطیوں کو دیکھیں (کوڈ یہاں ہے)۔
#!/usr/bin/env bash
# canary_watch_v1.sh
PROMETHEUS="http://localhost:9090"
DEPLOYMENT="myapp-canary"
NAMESPACE="default"
ERROR_THRESHOLD="0.05"
CHECK_INTERVAL=15
STRIKE_LIMIT=3
strikes=0
echo "Canary monitor running (v1 - error rate only)."
echo "Rollback triggers if error rate exceeds ({ERROR_THRESHOLD} for ){STRIKE_LIMIT} checks."
echo ""
while true; do
ts=$(date '+%Y-%m-%dT%H:%M:%S')
error_query='sum(rate(http_requests_total{app="myapp-canary",status=~"5.."}[1m])) / sum(rate(http_requests_total{app="myapp-canary"}[1m]))'
error_rate=((curl -sf "){PROMETHEUS}/api/v1/query"
--data-urlencode "query=${error_query}" |
python3 -c "
import sys, json
d = json.load(sys.stdin)
result = d['data']['result']
print(result[0]['value'][1] if result else '0')
" 2>/dev/null)
error_rate=${error_rate:-0}
above=((echo ")error_rate > $ERROR_THRESHOLD" | bc -l)
echo "[(ts] error_rate=){error_rate} | threshold=({ERROR_THRESHOLD} | breach=)([ "$above" = "1" ] && echo YES || echo NO)"
if [ "$above" = "1" ]; then
strikes=$((strikes + 1))
echo " Strike ({strikes}/){STRIKE_LIMIT}"
if [ "(strikes" -ge ")STRIKE_LIMIT" ]; then
echo " ROLLBACK TRIGGERED"
kubectl rollout undo deployment/"({DEPLOYMENT}" -n "){NAMESPACE}"
exit 0
fi
else
strikes=0
fi
sleep "${CHECK_INTERVAL}"
done
ورژن 2: خرابی کی شرح اور جوابی وقت کی نگرانی
#!/usr/bin/env bash
# canary_watch_v2.sh
PROMETHEUS="http://localhost:9090"
DEPLOYMENT="myapp-canary"
NAMESPACE="default"
ERROR_THRESHOLD="0.05"
LATENCY_THRESHOLD="2.0"
CHECK_INTERVAL=15
STRIKE_LIMIT=3
strikes=0
echo "Canary monitor running (v2 - error rate + P99 latency)."
echo "Error threshold: ({ERROR_THRESHOLD} | Latency P99 threshold: ){LATENCY_THRESHOLD}s"
echo ""
while true; do
ts=$(date '+%Y-%m-%dT%H:%M:%S')
error_query='sum(rate(http_requests_total{app="myapp-canary",status=~"5.."}[1m])) / sum(rate(http_requests_total{app="myapp-canary"}[1m]))'
error_rate=((curl -sf "){PROMETHEUS}/api/v1/query"
--data-urlencode "query=${error_query}" |
python3 -c "
import sys, json
d = json.load(sys.stdin)
result = d['data']['result']
print(result[0]['value'][1] if result else '0')
" 2>/dev/null)
latency_query='histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{app="myapp-canary"}[1m])) by (le))'
latency=((curl -sf "){PROMETHEUS}/api/v1/query"
--data-urlencode "query=${latency_query}" |
python3 -c "
import sys, json
d = json.load(sys.stdin)
result = d['data']['result']
print(result[0]['value'][1] if result else '0')
" 2>/dev/null)
error_rate=${error_rate:-0}
latency=${latency:-0}
error_breach=((echo ")error_rate > $ERROR_THRESHOLD" | bc -l)
latency_breach=((echo ")latency > $LATENCY_THRESHOLD" | bc -l)
triggered_by=""
[ "(error_breach" = "1" ] && triggered_by="error_rate(){error_rate})"
[ "(latency_breach" = "1" ] && triggered_by="){triggered_by:+({triggered_by}, }latency_p99(){latency}s)"
echo "[(ts] error_rate=){error_rate} | latency_p99=({latency}s | breach=){triggered_by:-none}"
if [ "(error_breach" = "1" ] || [ ")latency_breach" = "1" ]; then
strikes=$((strikes + 1))
echo " Strike ({strikes}/){STRIKE_LIMIT} | Triggered by: ${triggered_by}"
if [ "(strikes" -ge ")STRIKE_LIMIT" ]; then
echo ""
echo " ROLLBACK TRIGGERED"
echo " Signal: ${triggered_by}"
kubectl rollout undo deployment/"({DEPLOYMENT}" -n "){NAMESPACE}"
exit 0
fi
else
strikes=0
fi
sleep "${CHECK_INTERVAL}"
done
اسکرپٹ کیسے کام کرتا ہے۔
کہ error rate query پرومیتھیس پوچھتا ہے: "کینری درخواستوں کا کتنا فیصد آخری لمحے میں ایک غلطی واپس کرتا ہے؟” نتیجہ 0.0 اس کا مطلب ہے کہ کوئی غلطی نہیں ہے۔ نتیجہ 0.06 اس کا مطلب ہے کہ 6% درخواستیں ناکام ہو جائیں گی کیونکہ وہ 5% حد سے تجاوز کر جائیں گی۔ آؤٹ پٹ میں آپ یہ دیکھیں گے:
error_rate=0.06 | threshold=0.05 | breach=YES
کہ latency query پوچھیں: "آج کینری کو آنے والی 1% درخواستوں کی رفتار کتنی سست ہے؟” نتیجہ 5.234 اس کا مطلب ہے کہ 100 میں سے 1 درخواستوں میں 5 سیکنڈ سے زیادہ وقت لگتا ہے۔ آپ اسے اس طرح دیکھتے ہیں:
latency_p99=5.234s | breach=latency_p99(5.234s)
V1 صرف پہلا سوال چلاتا ہے۔ V2 دونوں چلتا ہے۔ ایک ہی کینری، ایک ہی مسئلہ، مختلف جواب۔
تین سٹرائیکس کے اصول کا مطلب ہے کہ ایک خراب چیک رول بیک کو متحرک نہیں کرے گا۔ لگاتار تین ایک رول بیک کو متحرک کرتے ہیں۔ رول بیک کے شروع ہونے سے پہلے نمائش کا وقت 45 سیکنڈ ہے (ہر 15 سیکنڈ میں تین چیک)۔
تین حملوں کے بعد، نگرانی سکرپٹ خود کو پھانسی دی جاتی ہے.
kubectl rollout undo deployment/myapp-canary -n default
وہ ایک لائن رول بیک کا سبب بنتی ہے۔ اس میں رہتے ہیں canary_watch_v2.sh یہ خود بخود چلتا ہے لہذا آپ کو کچھ کرنے کی ضرورت نہیں ہے۔ اسکرپٹ کو سمجھنا، فیصلہ کرنا اور عمل کرنا ہے۔
جان بوجھ کر توڑ دو
ٹرمینل 1 پرv1 مانیٹر شروع کریں۔
./canary_watch_v1.sh
آپ اسے ہر 15 سیکنڈ میں دہراتے ہوئے دیکھ سکتے ہیں:
Canary monitor running (v1 - error rate only).
Rollback triggers if error rate exceeds 0.05 for 3 checks.
[2026-05-17T11:53:12] error_rate=0 | threshold=0.05 | breach=NO
[2026-05-17T11:53:27] error_rate=0 | threshold=0.05 | breach=NO
[2026-05-17T11:53:42] error_rate=0 | threshold=0.05 | breach=NO
breach=NO اس کا مطلب ہے کہ کینری صحت مند نظر آتی ہے۔ اسے ویسے ہی رہنے دیں اور ٹرمینل 2 پر جائیں۔
ٹرمینل 2 پرکینری میں تاخیر کا انجیکشن لگائیں۔
./break_it.sh
اس کی وجہ سے کینری کی ہر درخواست میں 5 سیکنڈ لگیں گے۔ درخواست اب بھی 200 واپس کرتی ہے۔ کوئی خرابی نہیں ہے، یہ صرف سست ہے۔ آپ دیکھ سکتے ہیں:
Injecting latency into the canary deployment...
deployment "myapp-canary" successfully rolled out
Latency injection is active.
The canary pod is Running and passing its readiness probe.
Every request to the canary now takes 5 seconds.
Error rate: 0% | P99 latency: ~5s
اب ٹرمینل 1 پر ایک اور نظر ڈالیں۔ v1 مانیٹر ابھی بھی پرنٹ کر رہا ہے۔ breach=NO. کینری فی درخواست 5 سیکنڈ لیتا ہے اور نگرانی سے پتہ چلتا ہے کہ سب کچھ ٹھیک ہے۔ وہ ناکامی ہے۔
ٹرمینل 3 پرمعلوم کریں کہ آپ کے صارفین اصل میں کیا تجربہ کر رہے ہیں۔
./check_latency.sh
TIMESTAMP STABLE (ms) CANARY (ms) STATUS
--------- ----------- ----------- ------
2026-05-17T11:55:14 18ms 5008ms CANARY DEGRADED
2026-05-17T11:55:20 7ms 5008ms CANARY DEGRADED
2026-05-17T11:55:27 6ms 5008ms CANARY DEGRADED
مستحکم 6 سے 18 ملی سیکنڈ کے اندر جواب دیتا ہے۔ کینری میں 5 سیکنڈ سے زیادہ وقت لگتا ہے۔ کینری کے صارفین ہر بار صفحہ لوڈ ہونے پر 5 سیکنڈ انتظار کرتے ہیں۔ ٹرمینل 1 کا v1 مانیٹر اب بھی دکھاتا ہے: breach=NO.
یہ سبق ہے۔ مانیٹرنگ اور صارف کا تجربہ بالکل الگ ہے۔ اسکرپٹ خراب نہیں ہے۔ آپ غلط چیز کو دیکھ رہے ہیں۔
اب آئیے ترمیمات کو دیکھتے ہیں۔ دبائیں Ctrl+C ٹرمینل 1 پر v1 کو روکیں۔ اسی ٹرمینل میں v2 شروع کریں۔
./canary_watch_v2.sh
ٹرمینل 2 میں لیٹنسی کو دوبارہ لگائیں۔
./break_it.sh
ٹرمینل 1 دیکھیں۔ V2 تاخیر کا تعین کرتا ہے اور تین حملوں کے بعد رول بیک کو انجام دیتا ہے۔
Canary monitor running (v2 - error rate + P99 latency).
Error threshold: 0.05 | Latency P99 threshold: 2.0s
[2026-05-15T14:30:00] error_rate=0.0 | latency_p99=0.082s | breach=none
[2026-05-15T14:30:15] error_rate=0.0 | latency_p99=5.234s | breach=latency_p99(5.234s)
Strike 1/3 | Triggered by: latency_p99(5.234s)
[2026-05-15T14:30:30] error_rate=0.0 | latency_p99=5.891s | breach=latency_p99(5.891s)
Strike 2/3 | Triggered by: latency_p99(5.891s)
[2026-05-15T14:30:45] error_rate=0.0 | latency_p99=6.102s | breach=latency_p99(6.102s)
Strike 3/3 | Triggered by: latency_p99(6.102s)
ROLLBACK TRIGGERED
Signal: latency_p99(6.102s)
deployment.apps/myapp-canary rolled back
غلطی کی شرح کبھی بھی 0 سے منتقل نہیں ہوئی۔ V2 کو بہرحال واپس کر دیا گیا کیونکہ تاخیر حد سے زیادہ تھی۔ ایک اضافی پیمائش سے یہی فرق پڑتا ہے۔
رول بیک کے بعد، یقینی بنائیں کہ کینری غیر فعال ہے لیکن حذف نہیں ہوئی ہے۔
kubectl rollout history deployment/myapp-canary -n default
REVISION CHANGE-CAUSE
1
2
دو نظرثانی۔ رول بیک نے نظرثانی 2 کو 0 تک کم کر دیا اور نظرثانی 1 کو بحال کر دیا۔ اگر آپ یہ طے کرتے ہیں کہ کچھ بھی حذف نہیں کیا گیا ہے اور رول بیک غلط الارم تھا، تو آپ دوبارہ تعینات کر سکتے ہیں۔
وہ فیصلے جو اسکرپٹ نہیں کر سکتا
V2 تاخیر کی بنیاد پر غلطی کے بغیر واپس کر دیا گیا۔ دوبارہ تعینات کرنے سے پہلے، پوچھیں کہ کیا تاخیر نئے کوڈ میں ایک حقیقی رجعت ہے، یا ایک عارضی اسپائک، جیسے کہ ڈیٹا بیس کیش کا پہلے استعمال پر گرم ہونا۔ دونوں ایک ہی سگنل پیدا کرتے ہیں۔ صرف آپ کو معلوم ہو گا کہ کون سا زیادہ امکان ہے کہ کیا بدلا ہے۔
غلط مثبت رول بیکس تعیناتی کو سست کرتے ہیں اور آٹومیشن میں اعتماد کو کم کرتے ہیں۔ درست حد صارف اور سسٹم پر منحصر ہے۔
اسکرپٹ کا اطلاق وہی ہوتا ہے جسے آپ ترتیب دیتے ہیں۔
گلنا
./teardown.sh
اب آپ کیا کر سکتے ہیں۔
اس ہینڈ بک میں استعمال کا ہر کیس ایک اسکرپٹ تھا جس نے ایک مخصوص مسئلہ حل کیا جسے معیاری ٹولز نے گرفت میں نہیں لیا۔ لینڈنگ کے مقامات ہیں:
آپ انوائس وصول کرنے سے پہلے AWS لاگت میں اضافے کو پکڑ سکتے ہیں، اور آپ دیکھ سکتے ہیں کہ سروس لیبل AWS کی خاصیت ہے نہ کہ اصل میں لاگت کی وجہ کیا ہے۔ آپریشنل تبدیلیوں سے شروع کریں، بلنگ لیبلز سے نہیں۔
ایک واحد ٹریکنگ ID کے ساتھ، آپ متعدد سروسز میں ناکام درخواستوں کی پوری ٹائم لائن کو دوبارہ تشکیل دے سکتے ہیں، اور جان سکتے ہیں کہ اس ٹائم لائن سے غائب ہونے والی کوئی بھی سروس ثبوت ہے، نہ کہ صرف غیر موجودگی۔
آپ بنیادی ڈھانچے کے بڑھنے کا پتہ لگا کر اس کا موازنہ کر سکتے ہیں کہ Terraform کیا مانتا ہے اور AWS اصل میں کیا رکھتا ہے، اور آپ دیکھیں گے کہ صاف نتائج کا مطلب یہ نہیں ہے کہ آپ کا پورا AWS اکاؤنٹ صاف ہے، بلکہ یہ کہ Terraform کا انتظام کرنے والے وسائل مطابقت پذیر ہیں۔
آپ ایپلیکیشن کی سطح کے ساتھ ساتھ انفراسٹرکچر کی سطح پر خفیہ گردش کی تصدیق کر سکتے ہیں اور ریڈی نیس پروب پاس ہونے اور ڈیٹا بیس سے جڑنے کے قابل ہونے والی ایپلیکیشن کے درمیان فرق بتا سکتے ہیں۔
آپ ایک کینری رول بیک ٹرگر بنا سکتے ہیں جو صحیح سگنلز کو دیکھتا ہے، اور صرف خرابی کی شرح کو دیکھنا آپ کو بتائے گا کہ آپ کے صارفین کے انتظار کے دوران آپ سست، ٹوٹی ہوئی تعیناتی کیوں چلا رہے ہیں۔
استعمال کے پانچوں معاملات کا پیٹرن ایک جیسا ہے۔ معیاری ٹول نے اطلاع دی کہ سب کچھ ٹھیک تھا، حالانکہ حقیقت میں کچھ غلط ہو رہا تھا۔ لاگت کا اسکرپٹ صاف طور پر واپس آیا، پوڈز چلتے ہوئے دکھائے گئے، اور کینریز نے کوئی غلطی نہیں دکھائی۔ یہ اس لیے نہیں ہے کہ ٹولز غلط ہیں، یہ اس لیے ہے کہ ہم صرف اس کی جانچ کر رہے ہیں جو چیک کرنا آسان ہے۔ یہ اسکرپٹ ان چیزوں کی جانچ کرتا ہے جو معیاری ٹولز چھوڑ دیتے ہیں۔
GitHub ذخیرہ: https://github.com/Osomudeya/devops-scripting-labs
میں ہفتہ وار DevOps کے بارے میں لکھتا ہوں، جس میں حقیقی دنیا کے نظام، انٹرویوز، CV ٹپس اور ٹرکس، اور حقیقی دنیا کے واقعات کا احاطہ کیا جاتا ہے۔ ہمارے نیوز لیٹر کے لیے سائن اپ کریں۔.