أدت محاولة نقل 58 إنذارًا من CloudWatch وثمانية مرشحات للمقاييس إلى مكدس CloudFormation مستقل إلى منع نشر إصدارات جديدة من تطبيقنا. وصل المكدس المصدر إلى الحالة UPDATE_ROLLBACK_FAILED، ووصل مكدس الوجهة الفارغ إلى ROLLBACK_FAILED. لم تُعِد طلبات التراجع الخمسة المكدسين إلى العمل، رغم أن الخدمة قبلتها. وفي النهاية أصلحتهما AWS داخليًا. ثم فشلت محاولة النقل الجماعي التالية بخطأ مختلف.
كنا نفصل موارد المراقبة في تطبيقي المفتوح المصدر للبطاقات التعليمية، وهو المشروع نفسه الذي شرحت واجهة برمجة تطبيقاته للوكلاء هنا. كان المكدس المصدر يضم 497 موردًا فعليًا. أردنا نقل 66 موردًا للمراقبة مع إبقاء قاعدة البيانات في مكانها. الأثر الذي تحققنا منه كان تعذر نشر إصدارات جديدة؛ ولم نثبت حدوث انقطاع في التطبيق أو فقدان للبيانات.
نقلنا في النهاية الموارد الـ66 كلها، وحافظنا على هوياتها الأصلية أثناء النقل، ثم نشرنا إصدارات اعتيادية. ما يفيد في هذه الحادثة هو الطريقة التي أثبتنا بها كل نتيجة من هذه النتائج، رغم حقول الحالة ووسوم الملكية التي أعطت انطباعًا مضللًا.
إذا تعثرت إعادة الهيكلة، ابدأ من هنا
| ما تراه | الخطوة التالية |
|---|---|
| اكتمال معاينة إعادة الهيكلة | اقرأ قيمة ExecutionStatus وسببها إلى جانب Status. قارن كل ربط مقترح للموارد بالحالة المرجعية قبل التنفيذ. |
UPDATE_ROLLBACK_FAILED أو فشل التراجع عن إعادة الهيكلة | احفظ تفاصيل العملية والمكدسين والأخطاء مع طوابعها الزمنية. لا تعاود المحاولة إلا بعد تحديد المتطلب المسبق الذي تغير. |
خطأ يفيد بأن AlarmName يساوي null أو بأن مخطط الوسوم غير مدعوم | احتفظ بنص الرسالة كما ورد واربطه بالعملية التي أصدرته. في حادثتنا، ظهرت هذه الأخطاء في مراحل مختلفة. |
| اختلاف بين جرد الموارد ووسوم النظام | قارن المعرّفات الفعلية وإعدادات مزوّد الموارد بجرد كل من المكدسين. أوقف عمليات النشر المعتمدة عليها مؤقتًا حتى تتضح الملكية. |
| إبلاغ AWS باكتمال التعافي | تحقق بصورة منفصلة من قابلية تشغيل المكدسين، والحفاظ على الموارد، وسلامة التطبيق، ونجاح عملية نشر اعتيادية. |
حددنا أحجام دفعات الإنتاج من خلال تجارب محدودة النطاق بعد أن أعادت AWS المكدسين الأصليين إلى العمل. هذه ملاحظات تستحق البحث، وليست وصفة عامة للتعافي.

اجمع الحالة الحالية قبل إعادة المحاولة
هذه الأوامر تقرأ حالة AWS فقط. استبدل كل قيمة نائبة محاطة بعلامتي اقتباس بالقيمة المناسبة لبيئتك، واحتفظ بمخرجات JSON في مكان خاص، وكرر أوامر المكدس لكل مكدس مشارك. قد تحتوي القوالب والمخرجات على تفاصيل حساسة.
ابدأ بهوية المستدعي وحالة المكدس وعملية إعادة الهيكلة المحددة:
aws --profile '<profile>' --region '<region>' sts get-caller-identity \
--output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation describe-stacks \
--stack-name '<stack-id>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation describe-stack-refactor \
--stack-refactor-id '<refactor-id>' --output json --no-cli-pager
يحدد get-caller-identity هوية جلسة التشخيص. لكنه لا يحدد كل دور استخدمته CloudFormation أثناء الفشل. احتفظ بقيمة RoleARN للمكدس عند وجودها، وتحقق من هوية التنفيذ بصورة منفصلة.
ثم اجمع الأحداث وجرد الموارد والقوالب والإجراءات المقترحة:
aws --profile '<profile>' --region '<region>' cloudformation describe-stack-events \
--stack-name '<stack-id>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation list-stack-resources \
--stack-name '<stack-id>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation get-template \
--stack-name '<stack-id>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation list-stack-refactor-actions \
--stack-refactor-id '<refactor-id>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudformation list-stack-refactors \
--output json --no-cli-pager
يبقى جلب النتائج عبر الصفحات مفعّلًا. يعطّل --no-cli-pager عارض المخرجات في الطرفية، ولا يعطّل جلب الصفحات. عند جمع أدلة كاملة، لا تضف --no-paginate ولا تترك حدًا تفرضه --max-items دون متابعة بقية النتائج. إذا حددت عدد نتائج CLI عمدًا، فتابع باستخدام الرمز المعاد عبر --starting-token؛ أما مستدعو API مباشرة فعليهم متابعة كل NextToken. راجع خيارات جلب الصفحات في CLI وواجهة ListStackRefactors API.
لكل إنذار متأثر، قارن حالته ووسومه لدى مزوّد الموارد بجرد المكدس:
aws --profile '<profile>' --region '<region>' cloudwatch describe-alarms \
--alarm-names '<alarm-name>' --output json --no-cli-pager
aws --profile '<profile>' --region '<region>' cloudwatch list-tags-for-resource \
--resource-arn '<alarm-arn>' --output json --no-cli-pager
احتفظ بالربط بين المعرّفات المنطقية والفعلية قبل النقل وبعده، وبأنواع الموارد وإعداداتها، بما فيها إعدادات مرشحات المقاييس إذا شملها النقل. توضح لقطة حديثة ما هو موجود الآن؛ لكنك ما زلت بحاجة إلى الحالة المرجعية السابقة للنقل لإثبات الحفاظ على الموارد.
أوقف عمليات النشر المعتمدة على هذه الموارد مؤقتًا إذا كانت الملكية غير واضحة، أو انتهت مهلة عملية، أو فشلت المحاولة نفسها مجددًا دون تغير متطلب مسبق. انتهاء المهلة لا يثبت أن التنفيذ توقف. حذف الموارد، أو استبدال الأدوار، أو عكس اتجاه النقل، أو التحول إلى الإبقاء والاستيراد، كلها خيارات تحتاج إلى خطة تعافٍ مستقلة خضعت للمراجعة.
جهّز ملفًا خاصًا لتقديمه إلى AWS Support، يتضمن الحساب والمنطقة، ومعرّفات المكدسات والعمليات، وتسلسلًا زمنيًا بتوقيت UTC، ونصوص الأخطاء ومعرّفات الطلبات، ومعرّف الـcommit وتشغيل CI، وأدلة الأدوار والسياسات، ومقارنات القوالب، ومعرّفات الموارد التي جرى تجاوزها، ومحاولات التعافي، والأثر على التطبيق. إذا طلب الدعم إبقاء الحالة كما هي للتحقيق، فواصل المراقبة دون إجراء تغييرات حتى يسمح بإنهاء ذلك الانتظار.
نجاح المعاينة لم يعنِ نجاح النقل
تعيد ميزة إعادة هيكلة مكدسات CloudFormation المدمجة تنظيم الموارد الموجودة مع الحفاظ على خصائصها وبياناتها. قبل محاولة النقل، تحقق من أهلية الموارد والقيود المتعلقة بسياسات المكدسات والتبعيات والمعلمات الزائفة التي تعتمد على المكدس. أما تغييرات الإعدادات فمكانها تحديث مستقل.
تميّز واجهة API بين إنشاء عملية إعادة الهيكلة وتنفيذها. أعادت تجربتنا اللاحقة الفاشلة هذين الحقلين معًا:
{
"Status": "CREATE_COMPLETE",
"ExecutionStatus": "ROLLBACK_COMPLETE"
}
كانت المعاينة قد اكتملت، لكن عملية النقل خضعت للتراجع. اقرأ أيضًا حقلي السبب: StatusReason وExecutionStatusReason. يعرّفهما مرجع DescribeStackRefactor API كلًا على حدة.
ظهرت مشكلة التراجع الأولى بعَرَض مختلف. تضمنت سجلات سابقة لموارد الإنذارات الرسالة التالية من مزوّد الموارد، بعد حذف رمز الطلب:
Resource handler returned message: "Cannot invoke "String.equals(Object)" because the return value of "software.amazon.cloudwatch.alarm.ResourceModel.getAlarmName()" is null" (HandlerErrorCode: InternalFailure)
كانت قيمة AlarmName الخالية دليلًا مفيدًا لتصعيد المشكلة إلى الدعم. لكنها لم تحدد إصلاحًا يمكننا تنفيذه بأنفسنا.
ظل نشر الإصدارات متعذرًا رغم قبول خمسة طلبات تراجع
شملت محاولاتنا الخمس طلبًا موثقًا لتجاوز 22 إنذارًا، وإعادة محاولة طلبها مهندس AWS يوم 27 سبتمبر في الساعة 08:39 بتوقيت UTC. قبلت الخدمة الطلبات، لكن العمليات الناتجة عنها فشلت. تكرار الاستدعاء لم يثبت إحراز تقدم.
تحصر AWS استخدام continue-update-rollback وResourcesToSkip في مسار تعافٍ محدد: تجاوز الموارد المؤهلة التي فشلت أثناء التراجع، واستخدم أقل مجموعة لازمة، ثم أزل التعارض بين الموارد المتجاوزة والقالب قبل التحديث التالي. تجاوز مورد لا يثبت أن حالته الفعلية تطابق القالب. اتبع إجراء التراجع لدى AWS عند اتخاذ هذا القرار.
تغيرت حالتا المكدسين قرابة الساعة 22:00 بتوقيت UTC يوم 1 أكتوبر. وفي صباح اليوم التالي أكد AWS Support أن فريقه الداخلي أعاد المكدسين إلى حالة قابلة للعمل، وأن النشر يمكن أن يُستأنف. شكرًا لذلك الفريق على إخراجنا من هذه الحالة العالقة. لم تقدم AWS أمر الإصلاح الداخلي، ولم تثبت أنها أصدرت تصحيحًا عامًا للخدمة. التسلسل الزمني بعد تنقيحه لإزالة التفاصيل الحساسة موجود في إجراء الحادثة المثبت عند نسخة محددة.
تفسير RDS كان يعتمد على الهوية التي نفذت القراءة
عزت AWS الفشل الأصلي إلى غياب الإذن rds:DescribeDBInstances عن دور التنفيذ. وكان تفسيرها أن تحديد نقطة نهاية RDS ضمن مخرجات المكدس يتطلب هذه القراءة، رغم أن قاعدة البيانات كانت ستبقى في مكدس التطبيق.
كان ذلك تقييم AWS. أما ملاحظاتنا المستقلة فلم تثبت السبب الجذري كاملًا. رأينا أن الدور المذكور يحمل AdministratorAccess، وأن نتيجة محاكاة IAM تسمح بالعملية. لكن هاتين الملاحظتين لم تثبتا ما كانت عليه سياسة الدور أو جلسة الخدمة الفعلية وقت الفشل.
هناك عدة هويات يجب أخذها في الحسبان: الهوية التي تجري الاستدعاء من CI، والأدوار التي يجري توليها للنشر أو الاستعلام، ومستدعي واجهة API لإعادة الهيكلة المدمجة، ودور تنفيذ CloudFormation. نجاح قراءة قاعدة البيانات من الطرفية يختبر فقط الهوية التي تستخدمها تلك الطرفية. تشرح وثائق دور الخدمة متى تستخدم CloudFormation بيانات اعتماد الدور المهيأ، وتشرح وثائق محاكي IAM سبب احتمال اختلاف المحاكاة عن طلب فعلي.
عمليًا، تتبّع سلسلة الهويات هذه وعمليات القراءة المطلوبة من مزوّدي الموارد، بما فيها قراءة الموارد المشار إليها في المخرجات. لا تكفي أدلتنا للتوصية بإضافة إذن RDS واحد باعتباره حلًا لفشل إعادة هيكلة الإنذارات.
تكرر فشل النقل الجماعي التالي دون RDS
بعد إصلاح AWS الداخلي، انتهت محاولة نقل جماعي أخرى في الإنتاج بالتراجع. وتمكنا في تجربة معزولة من تكرار الخطأ نفسه باستخدام 58 إنذارًا ووجهة أنشأتها ميزة إعادة الهيكلة المدمجة. لم تكن هناك أي تبعية لـRDS في تلك التجربة. وبعد حذف بادئة ARN الخاصة بالمكدس، كان نص السبب:
Stack Refactor does not support AWS::CloudWatch::Alarm because the resource type defines an unsupported tag schema.
لم تكن قراءة RDS وحدها كافية لتفسير هذه النتيجة اللاحقة. كما بدت الرسالة أعم مما تدعمه تجاربنا، فقد نجحت عدة عمليات نقل للإنذارات باستخدام الميزة المدمجة.
| التجربة | النتيجة المرصودة |
|---|---|
| إنذارات منفردة بأسماء مولدة أو محددة صراحة، ووسوم غائبة أو فارغة أو موجودة | نجحت عمليات النقل المدمجة؛ وتحققنا من الهوية الفعلية ووسوم الوجهة والتنظيف. |
| وجهات أنشأتها الميزة المدمجة مع 2 و10 و25 إنذارًا | نجحت؛ وتحققنا من المعرّفات والإعدادات ووسوم النظام الخاصة بالوجهة والتنظيف. |
| وجهة أنشأتها الميزة المدمجة مع 58 إنذارًا ودون RDS | تراجعت العملية مع خطأ مخطط الوسوم غير المدعوم. |
| حالة منفصلة تضم 58 إنذارًا ووجهة أُنشئت مسبقًا | أعادت InternalFailure، وهو خطأ مختلف. |
| نقل إنذارين متأثرين، ثم إنذارين آخرين إلى الوجهة نفسها | نجحت عمليتا النقل؛ وبقيت الهويات الأصلية الـ58 وإعداداتها سليمة. |
يشمل السجل العام تشغيل 2/10/25، وتشغيل 58 إنذارًا، وتشغيل إعادة استخدام الوجهة. قد تنتهي مدة الاحتفاظ بالسجلات أو تتطلب تسجيل الدخول؛ وتحفظ وثائق الحادثة المثبتة الاستنتاجات.
نجحت أيضًا حالة واحدة بوجهة أنشأتها الميزة المدمجة دون RoleARN للوجهة، لذا لم يكن غياب هذا الحقل وحده كافيًا لإعادة إنتاج الفشل. ونجحت حالة اختبار مؤقتة أُنشئت بالاستيراد، لكننا لم نستخدم الاستيراد للتعافي في الإنتاج. أما حالة الاستيراد ذات الاسم المحدد صراحة فلم تبدأ أصلًا، ولا تقدم أي نتيجة.
نجحت دفعة تضم 25 إنذارًا في الاختبار؛ وهذا لا يعني أننا اكتشفنا حدًا لدى AWS. كان عدد الموارد وطريقة إنشاء الوجهة والآثار المتبقية بعد التراجع جوانب مفيدة للتحقيق. هذه النتائج لا تكشف السبب الجذري الداخلي للخدمة، ولا تضمن أن دفعات أصغر ستعيد مكدسًا آخر إلى العمل.
اختلف جرد المكدس عن الوسوم بعد التراجع
بعد فشل تجربة الـ58 إنذارًا المعزولة، أظهر جرد CloudFormation أن الإنذارات عادت إلى المكدس المصدر. ومع ذلك، احتفظ 49 إنذارًا بوسوم نظام تشير إلى الوجهة التي حُذفت لاحقًا؛ ولم تُشر وسوم سوى تسعة إنذارات إلى المصدر. وواجهت عملية التنظيف أيضًا خطأ InternalFailure في GetTemplate.
لذلك كان فحص الملكية بالاعتماد على aws:cloudformation:stack-id وحده سيعطي صورة خاطئة. قارنا جرد المكدس والمعرّفات الفعلية والإعدادات الفعلية والوسوم كلًا على حدة. وكان يجب حسم هذا الاختلاف قبل نشر الإصدارات المعتمدة على تلك الموارد.
في تجربة التعافي، حافظت عمليتا نقل متتاليتان، كل منهما لإنذارين إلى الوجهة نفسها، على الهويات الـ58 وإعداداتها جميعًا. وكانت وسوم النظام صحيحة للإنذارات الأربعة المنقولة كلها. لم نكتب وسوم aws: المحمية يدويًا. وتحققنا لاحقًا من اكتمال التنظيف، دون بقاء أي مكدسات أو إنذارات اختبار مؤقتة.
وقد يضلل تفصيلان من السجل السابق أي تحقيق جديد أيضًا. بقي 22 صفًا لإنذارات بحالة UPDATE_FAILED بعد تعافي المكدس؛ ولم تثبت تلك الصفوف وحدها وجود فشل حالي في الإنذارات الفعلية. واختفت معاينة قديمة من ListStackRefactors، لكنها بقيت قابلة للقراءة باستخدام معرّفها الدقيق. قارن الطوابع الزمنية بالحالة الحالية لدى مزوّد الموارد، واستعلم مباشرة عن العمليات المعروفة حتى عندما لا تظهر في القائمة. يسجل دليل الملكية المثبت عند نسخة محددة هاتين الملاحظتين.
اكتمل التعافي بإصدار اعتيادي
اكتمل النقل في الإنتاج بأربع دفعات عبر الميزة المدمجة: ثمانية مرشحات للمقاييس، ثم 25 إنذارًا، ثم 25 إنذارًا، ثم ثمانية إنذارات. احتفظت الموارد الفعلية الأصلية الـ497 كلها بهوياتها وأنواعها أثناء النقل. واستقرت موارد المراقبة الـ66 كلها في مكدس المراقبة.
تحققنا من أربعة جوانب للتعافي، كل منها على حدة:
| نتيجة التعافي | الأدلة المطلوب جمعها |
|---|---|
| قابلية تشغيل المكدسين | حالات حديثة للمكدسين، وحالة واضحة ومفسرة لكل عملية إعادة هيكلة معروفة. |
| الحفاظ على الموارد والإعدادات والبيانات | مقارنات الهويات والإعدادات قبل النقل وبعده، إضافة إلى فحوص البيانات الخاصة بكل خدمة. ثبات المعرّفات الفعلية وحده لا يثبت سلامة البيانات. |
| سلامة التطبيق | فحوص الحالة الحالية واختبارات التشغيل الأساسية ذات الصلة للويب وAgent API وMCP. |
| القدرة على النشر الاعتيادي | نشر commit معروف بنجاح عبر مسار الإصدار المعتاد. |
نجح أول إصدار اعتيادي بعد الفصل، عند ad297a8. ونجح الإصدار الاعتيادي اللاحق، عند 0a530c7، في المهام التسع كلها واختبارات التشغيل الأساسية الثلاثة. وأبلغت المنصة والويب ولوحة الإدارة عن الـcommit المتوقع نشره.
استبدل ذلك الإصدار اللاحق عمدًا موردًا واحدًا من نوع AWS::Lambda::Version، وهو مورد غير قابل للتعديل. وبقيت الهويات الأصلية الـ496 الأخرى دون تغيير، بما فيها موارد المراقبة الـ66 كلها. هذه مقارنة منفصلة عن الحفاظ على الهويات الـ497 كلها أثناء النقل: إنشاء إصدار Lambda جديد خلال إصدار اعتيادي لا ينقض نتيجة النقل السابقة. تسجل أدلة الاكتمال المثبتة عند نسخة محددة النتيجتين.
بعد التحقق، أزلنا صلاحيات إعادة الهيكلة وأدوات التشخيص المؤقتة. وبعد اكتمال النقل، أصبحت أداة المساعدة على الإصدار تتحقق من الملكية دون إعادة تنفيذ النقل. أنهينا العمل بنشر إصدار اعتيادي من commit معروف، تدعمه مقارنات الموارد وفحوص تؤكد عمل التطبيق. وكان هذا هو الدليل على قدرتنا على نشر إصدارات جديدة مجددًا.



