اختبار البنية التحتية كرمز مع Pulumi. الجزء 1

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

اختبار البنية التحتية كرمز مع Pulumi. الجزء 1

يوفر استخدام لغة Pulumi ولغات البرمجة ذات الأغراض العامة لكود البنية التحتية (البنية التحتية كرمز) العديد من المزايا: توفر المهارات والمعرفة، والتخلص من القالب المعياري في الكود من خلال التجريد، والأدوات المألوفة لفريقك، مثل IDEs وinters. كل أدوات هندسة البرمجيات هذه لا تجعلنا أكثر إنتاجية فحسب، بل تعمل أيضًا على تحسين جودة التعليمات البرمجية لدينا. لذلك، فمن الطبيعي أن يتيح لنا استخدام لغات البرمجة ذات الأغراض العامة تقديم ممارسة مهمة أخرى لتطوير البرمجيات - اختبار.

في هذه المقالة، سنلقي نظرة على كيفية مساعدة Pulumi لنا في اختبار بنيتنا التحتية كرمز.

اختبار البنية التحتية كرمز مع Pulumi. الجزء 1

لماذا اختبار البنية التحتية؟

قبل الخوض في التفاصيل، يجدر طرح السؤال: "لماذا يتم اختبار البنية التحتية أصلاً؟" هناك أسباب كثيرة لذلك وهنا بعض منها:

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

وحدة التجارب

تتم كتابة برامج Pulumi بلغات برمجة للأغراض العامة مثل JavaScript أو Python أو TypeScript أو Go. ولذلك، فإن القوة الكاملة لهذه اللغات، بما في ذلك أدواتها ومكتباتها، بما في ذلك أطر الاختبار، متاحة لهم. Pulumi عبارة عن سحابة متعددة، مما يعني أنه يمكن استخدامها للاختبار من أي مزود سحابي.

(في هذه المقالة، على الرغم من كونها متعددة اللغات ومتعددة السحابة، فإننا نستخدم JavaScript وMocha ونركز على AWS. يمكنك استخدام Python unittestأو إطار اختبار Go أو أي إطار اختبار آخر تفضله. وبالطبع، يعمل Pulumi بشكل رائع مع Azure وGoogle Cloud وKubernetes.)

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

  • المثيلات لها علامة Name.
  • يجب ألا تستخدم المثيلات برنامجًا نصيًا مضمنًا userData - يجب علينا استخدام AMI (صورة).
  • يجب ألا يكون هناك SSH مكشوفًا للإنترنت.

ويستند هذا المثال على المثال الخاص بي aws-js-webserver:

مؤشر.js:

"use strict";
 
let aws = require("@pulumi/aws");
 
let group = new aws.ec2.SecurityGroup("web-secgrp", {
    ingress: [
        { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
        { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
    ],
});
 
let userData =
`#!/bin/bash
echo "Hello, World!" > index.html
nohup python -m SimpleHTTPServer 80 &`;
 
let server = new aws.ec2.Instance("web-server-www", {
    instanceType: "t2.micro",
    securityGroups: [ group.name ], // reference the group object above
    ami: "ami-c55673a0"             // AMI for us-east-2 (Ohio),
    userData: userData              // start a simple web server
});
 
exports.group = group;
exports.server = server;
exports.publicIp = server.publicIp;
exports.publicHostName = server.publicDns;

هذا هو برنامج Pulumi الأساسي: فهو ببساطة يخصص مجموعة أمان EC2 ومثيلًا. ومع ذلك، تجدر الإشارة إلى أننا هنا نكسر القواعد الثلاثة المذكورة أعلاه. دعونا نكتب الاختبارات!

اختبارات الكتابة

سيبدو الهيكل العام لاختباراتنا مثل اختبارات Mocha العادية:

ec2tests.js

test.js:
let assert = require("assert");
let mocha = require("mocha");
let pulumi = require("@pulumi/pulumi");
let infra = require("./index");
 
describe("Infrastructure", function() {
    let server = infra.server;
    describe("#server", function() {
        // TODO(check 1): Должен быть тэг Name.
        // TODO(check 2): Не должно быть inline-скрипта userData.
    });
    let group = infra.group;
    describe("#group", function() {
        // TODO(check 3): Не должно быть SSH, открытого в Интернет.
    });
});

لنكتب الآن اختبارنا الأول: تأكد من أن المثيلات تحتوي على العلامة Name. للتحقق من ذلك، نحصل ببساطة على كائن مثيل EC2 ونتحقق من الخاصية المقابلة tags:

 // check 1: Должен быть тэг Name.
        it("must have a name tag", function(done) {
            pulumi.all([server.urn, server.tags]).apply(([urn, tags]) => {
                if (!tags || !tags["Name"]) {
                    done(new Error(`Missing a name tag on server ${urn}`));
                } else {
                    done();
                }
            });
        });

يبدو وكأنه اختبار عادي، ولكن مع بعض الميزات الجديرة بالملاحظة:

  • نظرًا لأننا نقوم بالاستعلام عن حالة المورد قبل النشر، فإن اختباراتنا يتم تشغيلها دائمًا في وضع "التخطيط" (أو "المعاينة"). وبالتالي، هناك العديد من الخصائص التي لن يتم استرجاع قيمها أو تحديدها. يتضمن ذلك جميع خصائص المخرجات التي يحسبها موفر السحابة الخاص بك. وهذا أمر طبيعي بالنسبة لاختباراتنا - فنحن نتحقق فقط من البيانات المدخلة. سنعود إلى هذه المشكلة لاحقًا، عندما يتعلق الأمر باختبارات التكامل.
  • نظرًا لأن جميع خصائص موارد Pulumi هي مخرجات، ويتم تقييم العديد منها بشكل غير متزامن، فنحن بحاجة إلى استخدام الأسلوب application للوصول إلى القيم. هذا مشابه جدًا للوعود والوظيفة then .
  • نظرًا لأننا نستخدم العديد من الخصائص لإظهار مورد URN في رسالة الخطأ، فنحن بحاجة إلى استخدام الوظيفة pulumi.allللجمع بينهما.
  • أخيرًا، بما أن هذه القيم يتم حسابها بشكل غير متزامن، فنحن بحاجة إلى استخدام ميزة رد الاتصال غير المتزامن المضمنة في Mocha done أو إرجاع الوعد.

بمجرد الانتهاء من إعداد كل شيء، سيكون لدينا إمكانية الوصول إلى المدخلات كقيم JavaScript بسيطة. ملكية tags هي خريطة (مصفوفة ترابطية)، لذلك سنتأكد فقط من أنها (1) ليست خاطئة، و(2) يوجد مفتاح لـ Name. الأمر بسيط جدًا والآن يمكننا اختبار أي شيء!

الآن دعونا نكتب الشيك الثاني. إنها أبسط:

 // check 2: Не должно быть inline-скрипта userData.
        it("must not use userData (use an AMI instead)", function(done) {
            pulumi.all([server.urn, server.userData]).apply(([urn, userData]) => {
                if (userData) {
                    done(new Error(`Illegal use of userData on server ${urn}`));
                } else {
                    done();
                }
            });
        });

وأخيرا، دعونا نكتب الاختبار الثالث. سيكون هذا أكثر تعقيدًا بعض الشيء لأننا نبحث عن قواعد تسجيل الدخول المرتبطة بمجموعة الأمان، والتي يمكن أن يكون هناك الكثير منها، ونطاقات CIDR في تلك القواعد، والتي يمكن أن يكون هناك الكثير منها أيضًا. لكننا تمكنا من:

    // check 3: Не должно быть SSH, открытого в Интернет.
        it("must not open port 22 (SSH) to the Internet", function(done) {
            pulumi.all([ group.urn, group.ingress ]).apply(([ urn, ingress ]) => {
                if (ingress.find(rule =>
                        rule.fromPort == 22 && rule.cidrBlocks.find(block =>
                            block === "0.0.0.0/0"))) {
                    done(new Error(`Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group ${urn}`));
                } else {
                    done();
                }
            });
        });

هذا كل شئ. الآن دعونا نجري الاختبارات!

تشغيل الاختبارات

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

للتغلب على هذه المشكلة، نحتاج فقط إلى تحديد ما يلي:

  • اسم المشروع الموجود في متغير البيئة PULUMI_NODEJS_PROJECT (أو بشكل أعم PULUMI__PROJECT для других языков).
    اسم المكدس المحدد في متغير البيئة PULUMI_NODEJS_STACK (أو بشكل أعم PULUMI__ STACK).
    متغيرات تكوين المكدس الخاص بك. ويمكن الحصول عليها باستخدام متغير البيئة PULUMI_CONFIG وتنسيقها هو خريطة JSON مع أزواج المفاتيح/القيمة.

    سيقوم البرنامج بإصدار تحذيرات تشير إلى أن الاتصال بـ CLI/المحرك غير متاح أثناء التنفيذ. يعد هذا أمرًا مهمًا لأن برنامجك لن ينشر أي شيء فعليًا وقد يكون مفاجئًا إذا لم يكن هذا هو ما كنت تنوي القيام به! لإخبار Pulumi أن هذا هو بالضبط ما تحتاجه، يمكنك التثبيت PULUMI_TEST_MODE в true.

    تخيل أننا بحاجة إلى تحديد اسم المشروع في my-ws، اسم المكدس devومنطقة AWS us-west-2. سيبدو سطر الأوامر لتشغيل اختبارات Mocha كما يلي:

    $ PULUMI_TEST_MODE=true 
        PULUMI_NODEJS_STACK="my-ws" 
        PULUMI_NODEJS_PROJECT="dev" 
        PULUMI_CONFIG='{ "aws:region": "us-west-2" }' 
        mocha tests.js

    القيام بذلك، كما هو متوقع، سيُظهر لنا أن لدينا ثلاثة اختبارات فاشلة!

    Infrastructure
        #server
          1) must have a name tag
     	 2) must not use userData (use an AMI instead)
        #group
          3) must not open port 22 (SSH) to the Internet
    
      0 passing (17ms)
      3 failing
     
     1) Infrastructure
           #server
             must have a name tag:
         Error: Missing a name tag on server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     2) Infrastructure
           #server
             must not use userData (use an AMI instead):
         Error: Illegal use of userData on server
            urn:pulumi:my-ws::my-dev::aws:ec2/instance:Instance::web-server-www
    
     3) Infrastructure
           #group
             must not open port 22 (SSH) to the Internet:
         Error: Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group

    دعونا نصلح برنامجنا:

    "use strict";
     
    let aws = require("@pulumi/aws");
     
    let group = new aws.ec2.SecurityGroup("web-secgrp", {
        ingress: [
            { protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
        ],
    });
     
    let server = new aws.ec2.Instance("web-server-www", {
        tags: { "Name": "web-server-www" },
        instanceType: "t2.micro",
        securityGroups: [ group.name ], // reference the group object above
        ami: "ami-c55673a0"             // AMI for us-east-2 (Ohio),
    });
     
    exports.group = group;
    exports.server = server;
    exports.publicIp = server.publicIp;
    exports.publicHostName = server.publicDns;
    

    ثم قم بإجراء الاختبارات مرة أخرى:

    Infrastructure
        #server
          ✓ must have a name tag
          ✓ must not use userData (use an AMI instead)
        #group
          ✓ must not open port 22 (SSH) to the Internet
     
     
     3 passing (16ms)

    كل شيء سار على ما يرام... مرحى! ✓✓✓

    هذا كل ما لدينا اليوم، لكننا سنتحدث عن اختبار النشر في الجزء الثاني من الترجمة 😉

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

إضافة تعليق