مطبات Terraform

مطبات Terraform
دعنا نسلط الضوء على بعض المخاطر، بما في ذلك تلك المتعلقة بالحلقات، وبيانات if، وتقنيات النشر، بالإضافة إلى المشكلات العامة التي تؤثر على Terraform بشكل عام:

  • المعلمات count وfor_each لها قيود؛
  • الحد من عمليات نشر التوقف صفر؛
  • فحتى الخطة الجيدة قد تفشل؛
  • إعادة الهيكلة يمكن أن يكون لها مخاطرها.
  • التماسك المؤجل يتوافق... مع التأجيل.

المعلمات count وfor_each لها قيود

تستخدم الأمثلة الواردة في هذا الفصل بشكل مكثف معلمة count وتعبير for_each في الحلقات والمنطق الشرطي. إنهم يؤدون أداءً جيدًا، لكن لديهم قيدين مهمين يجب أن تكونوا على دراية بهما.

  • لا يمكن لـ Count وfor_each الإشارة إلى أي متغيرات لإخراج الموارد.
  • لا يمكن استخدام العد وfor_each في تكوين الوحدة.

لا يمكن لعدد وfor_each الرجوع إلى أي متغيرات إخراج الموارد

تخيل أنك بحاجة إلى نشر العديد من خوادم EC2 ولسبب ما لا تريد استخدام ASG. الكود الخاص بك يمكن أن يكون مثل هذا:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

دعونا ننظر إليهم واحدا تلو الآخر.

نظرًا لتعيين معلمة العد على قيمة ثابتة، سيعمل هذا الرمز دون مشاكل: عند تشغيل أمر التطبيق، سيتم إنشاء ثلاثة خوادم EC2. ولكن ماذا لو كنت تريد نشر خادم واحد في كل منطقة توافر خدمات (AZ) داخل منطقة AWS الحالية لديك؟ يمكنك جعل التعليمات البرمجية الخاصة بك تقوم بتحميل قائمة المناطق من مصدر بيانات aws_availability_zones ثم تكرار كل منطقة وإنشاء خادم EC2 فيها باستخدام معلمة count والوصول إلى فهرس المصفوفة:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

سيعمل هذا الرمز أيضًا بشكل جيد، حيث يمكن لمعلمة count الرجوع إلى مصادر البيانات دون أي مشاكل. ولكن ماذا يحدث إذا كان عدد الخوادم التي تحتاج إلى إنشائها يعتمد على مخرجات بعض الموارد؟ لتوضيح ذلك، أسهل طريقة هي استخدام المورد Random_integer، والذي، كما يوحي اسمه، يُرجع عددًا صحيحًا عشوائيًا:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

ينشئ هذا الكود رقمًا عشوائيًا بين 1 و3. دعونا نرى ما سيحدث إذا حاولنا استخدام مخرجات هذا المورد في معلمة count للمورد aws_instance:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

إذا قمت بتشغيل خطة terraform على هذا الرمز، فسوف تحصل على الخطأ التالي:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

يتطلب Terraform حساب العدد وfor_each أثناء مرحلة التخطيط، قبل إنشاء أي موارد أو تعديلها. هذا يعني أن العد وfor_each يمكن أن يشيرا إلى القيم الحرفية والمتغيرات ومصادر البيانات وحتى قوائم الموارد (طالما يمكن تحديد طولها في وقت الجدولة)، ولكن ليس إلى متغيرات مخرجات الموارد المحسوبة.

لا يمكن استخدام العد وfor_each في تكوين الوحدة

قد تميل يومًا ما إلى إضافة معلمة count إلى تكوين الوحدة الخاصة بك:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

يحاول هذا الرمز استخدام العد داخل الوحدة النمطية لإنشاء ثلاث نسخ من مورد مجموعة خادم الويب. أو قد ترغب في جعل توصيل الوحدة اختياريًا اعتمادًا على بعض الشروط المنطقية عن طريق تعيين معلمة العد الخاصة بها على 0. قد يبدو هذا كودًا معقولًا، ولكنك ستحصل على هذا الخطأ عند تنفيذ خطة terraform:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

لسوء الحظ، بدءًا من Terraform 0.12.6، أصبح استخدام count أو for_each في مورد الوحدة النمطية غير مدعوم. وفقًا لملاحظات إصدار Terraform 0.12 (http://bit.ly/3257bv4)، تخطط HashiCorp لإضافة هذه الإمكانية في المستقبل، لذا اعتمادًا على وقت قراءتك لهذا الكتاب، قد تكون متاحة بالفعل. لمعرفة ذلك على وجه اليقين، اقرأ سجل التغيير الخاص بـ Terraform هنا.

حدود عمليات النشر الصفرية للتوقف عن العمل

يعد استخدام الكتلة create_before_destroy مع ASG حلاً رائعًا لإنشاء عمليات نشر بدون توقف، باستثناء تحذير واحد: قواعد القياس التلقائي غير مدعومة. أو لكي نكون أكثر دقة، يؤدي هذا إلى إعادة تعيين حجم ASG مرة أخرى إلى min_size في كل عملية نشر، وهو ما قد يمثل مشكلة إذا كنت تستخدم قواعد القياس التلقائي لزيادة عدد الخوادم قيد التشغيل.

على سبيل المثال، تحتوي وحدة مجموعة خادم الويب على زوج من موارد aws_autoscaling_schedule، والتي في الساعة 9 صباحًا تزيد عدد الخوادم في المجموعة من اثنين إلى عشرة. إذا قمت بالنشر في الساعة 11 صباحًا، على سبيل المثال، فسيتم تشغيل ASG الجديد بخادمين فقط بدلاً من عشرة خوادم وسيظل على هذا النحو حتى الساعة 9 صباحًا من اليوم التالي.

يمكن التحايل على هذا القيد بعدة طرق.

  • قم بتغيير معلمة التكرار في aws_autoscaling_schedule من 0 9 * * * ("التشغيل في الساعة 9 صباحًا") إلى ما يشبه 0-59 9-17 * * * ("التشغيل كل دقيقة من 9 صباحًا إلى 5 مساءً"). إذا كان لدى ASG بالفعل عشرة خوادم، فلن يؤدي تشغيل قاعدة القياس التلقائي هذه مرة أخرى إلى تغيير أي شيء، وهو ما نريده. ولكن إذا تم نشر ASG مؤخرًا، فستضمن هذه القاعدة أن يصل عدد خوادمه إلى عشرة في مدة أقصاها دقيقة. لا يعد هذا أسلوبًا أنيقًا تمامًا، كما أن القفزات الكبيرة من عشرة إلى خادمين والعودة يمكن أن تسبب أيضًا مشكلات للمستخدمين.
  • قم بإنشاء برنامج نصي مخصص يستخدم AWS API لتحديد عدد الخوادم النشطة في ASG، واستدعائه باستخدام مصدر بيانات خارجي (راجع "مصدر البيانات الخارجي" في الصفحة 249)، وقم بتعيين معلمة القدرة المطلوبة لـ ASG على القيمة التي يتم إرجاعها بواسطة النص. بهذه الطريقة، سيتم دائمًا تشغيل كل مثيل ASG جديد بنفس سعة كود Terraform الحالي مما يزيد من صعوبة صيانته.

بالطبع، سيكون لدى Terraform بشكل مثالي دعم مدمج لعمليات النشر بدون توقف، ولكن اعتبارًا من مايو 2019، لم يكن لدى فريق HashiCorp أي خطط لإضافة هذه الوظيفة (التفاصيل - هنا).

قد يتم تنفيذ الخطة الصحيحة دون جدوى

في بعض الأحيان، يُنتج أمر الخطة خطة نشر صحيحة تمامًا، لكن أمر التطبيق يُرجع خطأً. حاول، على سبيل المثال، إضافة المورد aws_iam_user بنفس الاسم الذي استخدمته لمستخدم IAM الذي قمت بإنشائه سابقًا في الفصل الثاني:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

الآن، إذا قمت بتشغيل أمر الخطة، فسوف يقوم Terraform بإخراج خطة نشر تبدو معقولة:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

Plan: 1 to add, 0 to change, 0 to destroy.

إذا قمت بتشغيل أمر التطبيق فسوف تحصل على الخطأ التالي:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

المشكلة بالطبع هي أن مستخدم IAM بهذا الاسم موجود بالفعل. ويمكن أن يحدث هذا ليس فقط لمستخدمي IAM، ولكن لأي مورد تقريبًا. من الممكن أن يقوم شخص ما بإنشاء هذا المورد يدويًا أو باستخدام سطر الأوامر، ولكن في كلتا الحالتين، تؤدي مطابقة المعرفات إلى حدوث تعارضات. هناك العديد من الاختلافات في هذا الخطأ والتي غالبًا ما تفاجئ الوافدين الجدد إلى Terraform.

النقطة الأساسية هي أن أمر خطة terraform يأخذ في الاعتبار فقط تلك الموارد المحددة في ملف حالة Terraform. إذا تم إنشاء الموارد بطريقة أخرى (على سبيل المثال، يدويًا عن طريق النقر في وحدة تحكم AWS)، فلن ينتهي بها الأمر في ملف الحالة وبالتالي لن يأخذها Terraform في الاعتبار عند تنفيذ أمر الخطة. ونتيجة لذلك، فإن الخطة التي تبدو صحيحة للوهلة الأولى، سوف يتبين أنها غير ناجحة.

هناك درسان يمكن تعلمهما من هذا.

  • إذا كنت قد بدأت بالفعل العمل مع Terraform، فلا تستخدم أي شيء آخر. إذا تمت إدارة جزء من بنيتك الأساسية باستخدام Terraform، فلن يعد بإمكانك تعديله يدويًا. بخلاف ذلك، فإنك لا تخاطر فقط بأخطاء Terraform الغريبة، ولكنك أيضًا تلغي العديد من فوائد IaC نظرًا لأن الكود لن يعد تمثيلًا دقيقًا للبنية الأساسية لديك.
  • إذا كان لديك بالفعل بعض البنية التحتية، فاستخدم أمر الاستيراد. إذا كنت تبدأ في استخدام Terraform مع البنية التحتية الموجودة، فيمكنك إضافته إلى ملف الحالة باستخدام أمر terraform import. بهذه الطريقة ستعرف Terraform البنية التحتية التي تحتاج إلى إدارتها. يأخذ أمر الاستيراد وسيطتين. الأول هو عنوان المورد في ملفات التكوين الخاصة بك. بناء الجملة هنا هو نفسه بالنسبة لروابط الموارد: _. (مثل aws_iam_user.existing_user). الوسيطة الثانية هي معرف المورد المراد استيراده. لنفترض أن معرف المورد aws_iam_user هو اسم المستخدم (على سبيل المثال، yevgeniy.brikman)، ومعرف المورد aws_instance هو معرف خادم EC2 (مثل i-190e22e5). عادةً ما تتم الإشارة إلى كيفية استيراد مورد في الوثائق الموجودة أسفل صفحته.

    يوجد أدناه أمر استيراد يقوم بمزامنة مورد aws_iam_user الذي أضفته إلى تكوين Terraform مع مستخدم IAM في الفصل 2 (استبدال اسمك بـ yevgeniy.brikman، بالطبع):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    سوف يقوم Terraform باستدعاء AWS API للعثور على مستخدم IAM الخاص بك وإنشاء ارتباط ملف حالة بينه وبين مورد aws_iam_user.existing_user في تكوين Terraform الخاص بك. من الآن فصاعدًا، عند تشغيل أمر الخطة، ستعرف Terraform أن مستخدم IAM موجود بالفعل ولن تحاول إنشائه مرة أخرى.

    تجدر الإشارة إلى أنه إذا كان لديك بالفعل الكثير من الموارد التي تريد استيرادها إلى Terraform، فقد يكون من الصعب كتابة التعليمات البرمجية يدويًا واستيراد كل منها على حدة. لذا، من المفيد البحث في أداة مثل Terraforming (http://terraforming.dtan4.net/)، والتي يمكنها استيراد التعليمات البرمجية والحالة تلقائيًا من حساب AWS الخاص بك.

    إعادة البناء يمكن أن يكون لها مخاطرها

    إعادة بناء التعليمات البرمجية هي ممارسة شائعة في البرمجة حيث تقوم بتغيير البنية الداخلية للكود مع ترك السلوك الخارجي دون تغيير. هذا لجعل الكود أكثر وضوحًا وإتقانًا وأسهل في الصيانة. إعادة البناء هي تقنية لا غنى عنها ويجب استخدامها بانتظام. ولكن عندما يتعلق الأمر بـ Terraform أو أي أداة IaC أخرى، عليك أن تكون حذرًا للغاية بشأن ما تعنيه بـ "السلوك الخارجي" لجزء من التعليمات البرمجية، وإلا ستظهر مشاكل غير متوقعة.

    على سبيل المثال، أحد الأنواع الشائعة لإعادة البناء هو استبدال أسماء المتغيرات أو الوظائف بأسماء أكثر قابلية للفهم. تحتوي العديد من بيئات التطوير المتكاملة على دعم مدمج لإعادة البناء ويمكنها إعادة تسمية المتغيرات والوظائف تلقائيًا خلال المشروع. في لغات البرمجة ذات الأغراض العامة، يعد هذا إجراءً تافهًا قد لا تفكر فيه، ولكن في Terraform عليك أن تكون حذرًا للغاية في هذا الأمر، وإلا فقد تتعرض لانقطاع التيار.

    على سبيل المثال، تحتوي وحدة مجموعة خادم الويب على متغير إدخال اسم المجموعة:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    تخيل أنك بدأت في استخدام هذه الوحدة لنشر خدمة صغيرة تسمى foo. لاحقًا، تريد إعادة تسمية الخدمة إلى bar. قد يبدو هذا التغيير تافهًا، لكنه في الواقع قد يتسبب في انقطاع الخدمة.

    الحقيقة هي أن وحدة كتلة خادم الويب تستخدم متغير اسم المجموعة في عدد من الموارد، بما في ذلك معلمة اسم مجموعتي الأمان و ALB:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    إذا قمت بتغيير معلمة الاسم على أحد الموارد، فسيقوم Terraform بحذف الإصدار القديم من ذلك المورد وإنشاء إصدار جديد بدلاً منه. ولكن إذا كان هذا المورد عبارة عن ALB، فبين حذفه وتنزيل إصدار جديد، لن يكون لديك آلية لإعادة توجيه حركة المرور إلى خادم الويب الخاص بك. وبالمثل، إذا تم حذف مجموعة أمان، فستبدأ خوادمك في رفض أي حركة مرور على الشبكة حتى يتم إنشاء مجموعة جديدة.

    هناك نوع آخر من عمليات إعادة البناء التي قد تكون مهتمًا بها وهو تغيير معرف Terraform. لنأخذ المورد aws_security_group في وحدة webserver-cluster كمثال:

    resource "aws_security_group" "instance" {
      # (...)
    }

    معرف هذا المورد يسمى المثيل. تخيل أنك قررت أثناء إعادة البناء تغييره إلى اسم أكثر قابلية للفهم (في رأيك) من نوعclust_instance:

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    ماذا سيحدث في النهاية؟ هذا صحيح: اضطراب.

    يقوم Terraform بربط كل معرف مورد بمعرف موفر السحابة. على سبيل المثال، يرتبط iam_user بمعرف مستخدم AWS IAM، ويرتبط aws_instance بمعرف خادم AWS EC2. إذا قمت بتغيير معرف المورد (على سبيل المثال، من المثيل إلىclust_instance، كما هو الحال مع aws_security_group)، فسيظهر إلى Terraform كما لو أنك حذفت المورد القديم وأضفت موردًا جديدًا. إذا قمت بتطبيق هذه التغييرات، فسيقوم Terraform بحذف مجموعة الأمان القديمة وإنشاء مجموعة جديدة، بينما تبدأ خوادمك في رفض أي حركة مرور على الشبكة.

    فيما يلي أربعة دروس رئيسية يجب عليك استخلاصها من هذه المناقشة.

    • استخدم دائمًا أمر الخطة. يمكن أن يكشف كل هذه العقبات. قم بمراجعة مخرجاتها بعناية وانتبه إلى المواقف التي تخطط فيها Terraform لحذف الموارد التي من المرجح ألا يتم حذفها.
    • قم بالإنشاء قبل الحذف. إذا كنت تريد استبدال أحد الموارد، فكر جيدًا فيما إذا كنت بحاجة إلى إنشاء بديل قبل حذف المورد الأصلي. إذا كانت الإجابة بنعم، فيمكن أن يساعدك create_before_destroy. يمكن تحقيق نفس النتيجة يدويًا عن طريق تنفيذ خطوتين: أولاً إضافة مورد جديد إلى التكوين وتشغيل أمر التطبيق، ثم إزالة المورد القديم من التكوين واستخدام أمر التطبيق مرة أخرى.
    • يتطلب تغيير المعرفات تغيير الحالة. إذا كنت تريد تغيير المعرف المرتبط بمورد (على سبيل المثال، إعادة تسمية aws_security_group من المثيل إلىcluster_instance) دون حذف المورد وإنشاء إصدار جديد منه، فيجب عليك تحديث ملف حالة Terraform وفقًا لذلك. لا تفعل ذلك يدويًا أبدًا - استخدم أمر terraform State بدلاً من ذلك. عند إعادة تسمية المعرفات، يجب عليك تشغيل الأمر terraformstate mv، الذي يحتوي على الصيغة التالية:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE هو تعبير يشير إلى المورد في شكله الحالي، وNEW_REFERENCE هو المكان الذي تريد نقله إليه. على سبيل المثال، عند إعادة تسمية مجموعة aws_security_group من المثيل إلىcluster_instance، فإنك تحتاج إلى تشغيل الأمر التالي:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      يُخبر هذا Terraform أن الحالة التي كانت مرتبطة سابقًا بـ aws_security_group.instance يجب أن تكون الآن مرتبطة بـ aws_security_group.cluster_instance. إذا لم تظهر خطة Terraform بعد إعادة التسمية وتشغيلها أي تغييرات، فهذا يعني أنك فعلت كل شيء بشكل صحيح.

    • لا يمكن تغيير بعض الإعدادات. معلمات العديد من الموارد غير قابلة للتغيير. إذا حاولت تغييرها، فسيقوم Terraform بحذف المورد القديم وإنشاء مورد جديد بدلاً منه. ستشير كل صفحة مورد عادةً إلى ما يحدث عندما تقوم بتغيير إعداد معين، لذا تأكد من مراجعة الوثائق. استخدم دائمًا أمر الخطة وفكر في استخدام إستراتيجية create_before_destroy.

    والاتساق المؤجل يتفق... مع التأجيل

    بعض واجهات برمجة التطبيقات (APIs) الخاصة بموفري الخدمات السحابية، مثل AWS، غير متزامنة وتتسبب في تأخر الاتساق. يعني عدم التزامن أن الواجهة يمكنها إرجاع الاستجابة على الفور دون انتظار اكتمال الإجراء المطلوب. ويعني تأخر الاتساق أن التغييرات قد تستغرق وقتًا لتنتشر في جميع أنحاء النظام؛ أثناء حدوث ذلك، قد تكون استجاباتك غير متناسقة وتعتمد على النسخة المتماثلة لمصدر البيانات التي تستجيب لاستدعاءات واجهة برمجة التطبيقات (API) الخاصة بك.

    تخيل، على سبيل المثال، أنك تجري اتصالاً بواجهة برمجة التطبيقات (API) مع AWS وتطلب منها إنشاء خادم EC2. ستعيد واجهة برمجة التطبيقات استجابة "ناجحة" (تم الإنشاء 201) على الفور تقريبًا، دون انتظار إنشاء الخادم نفسه. إذا حاولت الاتصال به على الفور، فمن المؤكد تقريبًا أنه سيفشل لأنه في هذه المرحلة لا تزال AWS تقوم بتهيئة الموارد أو، بدلاً من ذلك، لم يتم تمهيد الخادم بعد. علاوة على ذلك، إذا قمت بإجراء مكالمة أخرى للحصول على معلومات حول هذا الخادم، فقد تتلقى خطأ (404 غير موجود). المشكلة هي أن المعلومات المتعلقة بخادم EC2 قد لا تزال منتشرة عبر AWS قبل أن تصبح متاحة في كل مكان، وسيتعين عليك الانتظار بضع ثوانٍ.

    عندما تستخدم واجهة برمجة تطبيقات غير متزامنة ذات تناسق بطيء، يجب عليك إعادة محاولة طلبك بشكل دوري حتى يكتمل الإجراء وينتشر عبر النظام. لسوء الحظ، لا توفر AWS SDK أي أدوات جيدة لهذا الغرض، وكان مشروع Terraform يعاني من الكثير من الأخطاء مثل 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    بمعنى آخر، تقوم بإنشاء مورد (مثل شبكة فرعية) ثم تحاول الحصول على بعض المعلومات عنه (مثل معرف الشبكة الفرعية المنشأة حديثًا)، ولا يتمكن Terraform من العثور عليه. تم إصلاح معظم هذه الأخطاء (بما في ذلك 6813)، لكنها لا تزال تظهر من وقت لآخر، خاصة عندما يضيف Terraform دعمًا لنوع مورد جديد. وهذا أمر مزعج، ولكن في معظم الحالات لا يسبب أي ضرر. عند تشغيل تطبيق Terraform مرة أخرى، يجب أن يعمل كل شيء، لأنه بحلول هذا الوقت ستكون المعلومات قد انتشرت بالفعل في جميع أنحاء النظام.

    هذا المقتطف مقدم من كتاب يفغيني بريكمان "Terraform: البنية التحتية على مستوى الكود".

المصدر: www.habr.com

إضافة تعليق