ALB يُعيد خطأ 502 Bad Gateway رغم أن الـ Target Group يُظهر الـ Instances بحالة Healthy
واجهت هذا السيناريو مرات عديدة في بيئات الإنتاج: الـ ALB يُعيد 502، لوحة التحكم تُظهر أن جميع الـ instances في حالة 'Healthy'، ولا يوجد أي خطأ واضح في السجلات على مستوى التطبيق. المشكلة ليست في صحة الـ instance — بل في ما يحدث بعد إنشاء الاتصال بالفعل.
ملخص سريع (TL;DR): أسباب 502 في ALB
| السبب | الأثر المرئي | طبقة التشخيص |
|---|---|---|
| استجابة HTTP غير صالحة من التطبيق | 502 فوري، لا يوجد retry | سجلات ALB Access Logs |
| عدم تطابق Keep-Alive Timeout | 502 متقطع تحت الحمل | Keep-alive settings على التطبيق |
| الـ Target أغلق الاتصال قبل إرسال الاستجابة | 502 مع connection reset في السجلات | سجلات التطبيق + ALB logs |
| خطأ في بروتوكول HTTPS بين ALB والـ Target | 502 عند استخدام HTTPS على Target Group | إعدادات Target Group Protocol |
| استجابة تتجاوز حجم الـ header المسموح به | 502 على طلبات بعينها | حجم response headers |
كيف يعمل ALB وما الفرق بين Healthy وقادر على الاستجابة
فحص الصحة (Health Check) في ALB يقيس شيئًا واحدًا فقط: هل الـ target يرد على مسار محدد بكود HTTP ضمن النطاق المقبول؟ هذا لا يعني أن التطبيق يُنتج استجابات HTTP صحيحة لكل الطلبات. الـ instance يمكن أن يجتاز الـ health check على `/health` ويُفشل في معالجة طلبات حقيقية بسبب مشكلة في الـ response format أو إعدادات الاتصال.
فكر في الأمر كمطعم يجيب على مكالمة الهاتف ويقول 'نعم نحن مفتوحون' — لكن عندما تصل وتطلب الطعام، المطبخ لا يُخرج أطباقًا بالشكل الصحيح. الـ health check هو مكالمة الهاتف، وليس تجربة الطعام الفعلية.
عندما يتلقى ALB استجابة من الـ target لا تتوافق مع بروتوكول HTTP — سواء كانت headers مشوهة، أو اتصال مُغلق بشكل مفاجئ، أو استجابة فارغة — يُعيد 502 للعميل. هذا سلوك ALB الصحيح، وليس خللًا فيه.
- Client → ALB: العميل يُرسل طلب HTTP.
- ALB → Target: ALB يُحيل الطلب إلى الـ instance المحدد.
- Target → ALB (استجابة مشكوك فيها): التطبيق يُرسل استجابة غير صالحة أو يُغلق الاتصال.
- ALB → Client (502): ALB لا يستطيع تمرير استجابة غير صالحة، فيُعيد 502 للعميل.
- Health Check (منفصل): فحص الصحة يعمل بشكل مستقل ويُظهر الـ instance بحالة Healthy لأن مسار الـ health check يعمل بشكل صحيح.
الخطوة الأولى: تفعيل ALB Access Logs وقراءة حقل target_status_code
قبل أي شيء آخر، تحتاج إلى بيانات فعلية من ALB نفسه. الـ 502 الذي يصل للعميل لا يُخبرك بما حدث داخل الاتصال بين ALB والـ target. سجلات الوصول هي المصدر الوحيد الذي يُظهر ما أعاده الـ target فعلًا — أو إذا لم يُعد شيئًا على الإطلاق.
تفعيل السجلات على الـ Load Balancer:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--attributes Key=access_logs.s3.enabled,Value=true \
Key=access_logs.s3.bucket,Value=my-alb-logs-bucket \
Key=access_logs.s3.prefix,Value=alb-logs
بعد تفعيل السجلات وإعادة إنتاج المشكلة، ابحث في السجلات عن الحقل target_status_code. إذا كانت قيمته - (شرطة)، فهذا يعني أن ALB لم يتلقَّ أي استجابة من الـ target — الاتصال انقطع أو انتهت مهلته قبل أن يُرسل الـ target أي شيء. هذا يُضيق نطاق البحث بشكل كبير.
للتحقق من إعدادات السجلات الحالية:
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--query 'Attributes[?Key==`access_logs.s3.enabled`]'
الخطوة الثانية: التحقق من Keep-Alive Timeout — السبب الأكثر شيوعًا للـ 502 المتقطع
هذا هو السبب الذي يُضيع أكثر وقت في التشخيص. الـ 502 يظهر تحت الحمل أو بشكل عشوائي، والـ instances تبدو سليمة تمامًا. الخطأ يحدث لأن ALB يحتفظ باتصالات HTTP مستمرة (persistent connections) مع الـ targets، وإذا أغلق التطبيق هذا الاتصال قبل أن يُغلقه ALB، يُرسل ALB طلبًا على اتصال مُغلق فعلًا ويتلقى reset — فيُعيد 502.
القاعدة العملية: يجب أن يكون Keep-Alive timeout على التطبيق أعلى من Keep-Alive timeout على ALB. قيمة ALB الافتراضية موثقة في AWS وتخضع للتغيير — تحقق دائمًا من التوثيق الرسمي للقيمة الحالية.
للتحقق من إعداد idle timeout على ALB:
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'
لتعديل idle timeout على ALB (مثال: 60 ثانية):
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/1234567890abcdef \
--attributes Key=idle_timeout.timeout_seconds,Value=60
بعد معرفة قيمة ALB، تأكد أن Keep-Alive timeout في تطبيقك أعلى منها. على Nginx مثلًا، هذا يعني ضبط keepalive_timeout على قيمة أعلى من idle timeout الخاص بـ ALB. على Node.js، تحتاج إلى ضبط server.keepAliveTimeout يدويًا لأن القيمة الافتراضية في بعض إصدارات Node.js كانت أقل من القيمة الافتراضية لـ ALB — وهذا كان مصدر أعطال شائعة.
الخطوة الثالثة: التحقق من بروتوكول Target Group وإعدادات HTTPS
إذا كان الـ Target Group مُعدًّا لاستخدام HTTPS للتواصل مع الـ instances، فإن ALB يتوقع TLS handshake صحيح مع الـ target. أي مشكلة في الشهادة على الـ instance — حتى شهادة self-signed غير مُضافة إلى قائمة الـ trusted certificates — يمكن أن تُسبب 502.
للتحقق من بروتوكول الـ Target Group:
aws elbv2 describe-target-groups \
--target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
--query 'TargetGroups[*].{Protocol:Protocol,Port:Port,HealthCheckProtocol:HealthCheckProtocol}'
إذا كان البروتوكول HTTPS، تحقق من إعداد TargetGroupStickinessConfig وإعدادات الـ certificate validation. في حالات كثيرة، الأبسط هو تغيير بروتوكول الـ Target Group إلى HTTP وترك تشفير TLS للـ ALB نفسه — هذا النمط أكثر شيوعًا وأقل تعقيدًا في الإدارة.
الخطوة الرابعة: فحص Security Groups وتأكيد أن ALB يصل إلى الـ Target Port
الـ health check قد يعمل على port مختلف عن port التطبيق الفعلي. إذا كان الـ Security Group يسمح بـ health check port لكن يحجب port التطبيق، ستحصل على instances تبدو Healthy لكن الطلبات الفعلية تفشل. هذا سيناريو نادر لكنه يحدث عند تغيير إعدادات الـ health check دون مراجعة الـ Security Group.
للتحقق من الـ Target Group health check port مقارنة بـ traffic port:
aws elbv2 describe-target-groups \
--target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg/1234567890abcdef \
--query 'TargetGroups[*].{TrafficPort:Port,HealthCheckPort:HealthCheckPort,HealthCheckPath:HealthCheckPath}'
للتحقق من قواعد الـ Security Group المرتبطة بالـ instances:
aws ec2 describe-security-groups \
--group-ids sg-0123456789abcdef0 \
--query 'SecurityGroups[*].IpPermissions'
تأكد أن الـ Security Group الخاص بالـ instances يسمح بحركة المرور الواردة من الـ Security Group الخاص بـ ALB على الـ port الذي يستمع عليه التطبيق.
الخطوة الخامسة: اختبار الاستجابة مباشرة من الـ Instance
بعد استبعاد مشاكل الشبكة والبروتوكول، الخطوة التالية هي إثبات أن التطبيق نفسه يُنتج استجابة HTTP صالحة. الطريقة الأكثر موثوقية هي الاتصال مباشرة بالـ instance وإرسال طلب مطابق لما يُرسله ALB.
من داخل نفس الـ VPC (عبر bastion host أو Systems Manager Session Manager):
curl -v http://<instance-private-ip>:<app-port>/<path>
انظر بعناية إلى:
- هل الاستجابة تحتوي على سطر Status Line صحيح (مثل
HTTP/1.1 200 OK)؟ - هل الـ headers مُنسقة بشكل صحيح؟
- هل الاتصال يُغلق بشكل نظيف بعد الاستجابة؟
إذا كان curl يُعيد نتيجة صحيحة لكن ALB يُعيد 502، فالمشكلة على الأرجح في طبقة الاتصال (Keep-Alive، timeouts) وليس في محتوى الاستجابة.
سيناريو حقيقي: الـ 502 الذي ظهر فقط تحت الحمل
في أحد بيئات الإنتاج، بدأت تظهر أخطاء 502 متقطعة على تطبيق Node.js خلف ALB. الـ instances كانت Healthy، وسجلات التطبيق لم تُظهر أي خطأ. الافتراض الأول كان مشكلة في الـ database connection pool تحت الحمل.
بعد تفعيل ALB Access Logs، وجدنا أن target_status_code كان - في جميع حالات الـ 502 — الـ target لم يُرسل أي استجابة. سجلات التطبيق أظهرت أن الطلبات كانت تصل وتُعالج، لكن الاستجابة لم تصل إلى ALB.
السبب الفعلي: Node.js HTTP server كان يُغلق اتصالات Keep-Alive بعد مهلة أقصر من مهلة ALB. تحت الحمل العادي، ALB كان يُعيد استخدام اتصالات موجودة — وبعض هذه الاتصالات كانت قد أُغلقت من جانب Node.js في اللحظة ذاتها التي أرسل فيها ALB طلبًا جديدًا عليها. الحل كان ضبط server.keepAliveTimeout في Node.js على قيمة أعلى من idle timeout الخاص بـ ALB.
الدرس: الـ 502 الذي لا يترك أثرًا في سجلات التطبيق دائمًا يشير إلى طبقة الاتصال، وليس منطق التطبيق.
IAM Policy للوصول إلى ALB Logs في S3
إذا كنت تحتاج إلى منح صلاحية قراءة سجلات ALB من S3 لفريق التشخيص:
🔽 انقر لعرض IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-alb-logs-bucket",
"arn:aws:s3:::my-alb-logs-bucket/alb-logs/*"
]
},
{
"Effect": "Allow",
"Action": [
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth"
],
"Resource": "*"
}
]
}
ملاحظة: إجراءات Describe على مستوى ELB تتطلب "Resource": "*" — تحقق من Service Authorization Reference للتأكد من دعم resource-level permissions لكل إجراء.
مخطط تشخيص 502 في ALB
اضبط timeout التطبيق أعلى من ALB"] F -->|لا| H["تحقق من Security Groups
وTarget Port"] E --> I{"بروتوكول Target Group"} I -->|HTTPS| J["تحقق من TLS Certificate
على الـ Instance"] I -->|HTTP| K["اختبر curl مباشرة
على الـ Instance"] K --> L{"curl يعمل؟"} L -->|نعم| G L -->|لا| M["مشكلة في التطبيق نفسه
راجع سجلات التطبيق"]
- هل target_status_code = '-'؟ إذا نعم، الـ target لم يُرسل استجابة — تحقق من Keep-Alive وTimeouts.
- هل target_status_code موجود؟ إذا نعم، الـ target أرسل كودًا لكن الاستجابة غير صالحة — تحقق من response format.
- هل المشكلة متقطعة تحت الحمل؟ على الأرجح Keep-Alive timeout mismatch.
- هل المشكلة مستمرة على كل الطلبات؟ تحقق من بروتوكول Target Group وإعدادات TLS.
- هل curl مباشرة على الـ instance يعمل؟ إذا نعم، المشكلة في طبقة الاتصال بين ALB والـ target.
الخلاصة والخطوات التالية لحل 502 Bad Gateway في ALB
خطأ 502 Bad Gateway في ALB مع instances بحالة Healthy يعني دائمًا أن المشكلة في ما يحدث بعد إنشاء الاتصال — وليس في صحة الـ instance نفسه. ابدأ بتفعيل Access Logs وقراءة target_status_code، ثم تحقق من Keep-Alive timeout alignment بين ALB والتطبيق، ثم انتقل إلى بروتوكول الـ Target Group وإعدادات الشبكة.
للتعمق أكثر، راجع:
مسرد المصطلحات
| المصطلح | التعريف |
|---|---|
| ALB (Application Load Balancer) | موزع حمل من الطبقة السابعة يعمل على مستوى HTTP/HTTPS، يُوجه الطلبات إلى الـ targets بناءً على قواعد محددة. |
| Target Group | مجموعة من الـ targets (instances، IPs، أو Lambda functions) التي يُوجه إليها ALB حركة المرور. |
| Keep-Alive Timeout | المدة التي يظل فيها اتصال HTTP مفتوحًا بين طلبين متتاليين قبل إغلاقه تلقائيًا. |
| target_status_code | حقل في ALB Access Logs يُظهر كود HTTP الذي أعاده الـ target. قيمة '-' تعني أن الـ target لم يُرسل أي استجابة. |
| Idle Timeout | المدة التي ينتظرها ALB على اتصال غير نشط قبل إغلاقه. يجب أن يكون Keep-Alive timeout التطبيق أعلى من هذه القيمة. |
تعليقات
إرسال تعليق