Kiểm tra cơ sở hạ tầng dưới dạng mã với Pulumi. Phần 1

Chào buổi chiều các bạn. Dự đoán sự bắt đầu của một luồng mới với tốc độ "Các phương pháp và công cụ DevOps" Chúng tôi đang chia sẻ với bạn một bản dịch mới. Đi.

Kiểm tra cơ sở hạ tầng dưới dạng mã với Pulumi. Phần 1

Việc sử dụng Pulumi và các ngôn ngữ lập trình đa năng cho mã cơ sở hạ tầng (Cơ sở hạ tầng dưới dạng Mã) mang lại nhiều lợi thế: sự sẵn có của các kỹ năng và kiến ​​thức, loại bỏ bản soạn sẵn trong mã thông qua tính năng trừu tượng hóa, các công cụ quen thuộc với nhóm của bạn, chẳng hạn như IDE và linters. Tất cả những công cụ kỹ thuật phần mềm này không chỉ giúp chúng ta làm việc hiệu quả hơn mà còn cải thiện chất lượng mã của chúng ta. Do đó, việc sử dụng các ngôn ngữ lập trình có mục đích chung là điều tự nhiên cho phép chúng tôi giới thiệu một phương pháp phát triển phần mềm quan trọng khác - thử nghiệm.

Trong bài viết này, chúng ta sẽ xem cách Pulumi giúp chúng ta kiểm tra cơ sở hạ tầng dưới dạng mã của mình.

Kiểm tra cơ sở hạ tầng dưới dạng mã với Pulumi. Phần 1

Tại sao phải kiểm tra cơ sở hạ tầng?

Trước khi đi vào chi tiết, cần đặt câu hỏi: “Tại sao phải kiểm tra cơ sở hạ tầng?” Có nhiều lý do cho việc này và đây là một số trong số đó:

  • Kiểm tra đơn vị các chức năng riêng lẻ hoặc các đoạn logic chương trình của bạn
  • Xác minh trạng thái mong muốn của cơ sở hạ tầng đối với các ràng buộc nhất định.
  • Phát hiện các lỗi phổ biến, chẳng hạn như thiếu mã hóa bộ lưu trữ hoặc truy cập mở, không được bảo vệ từ Internet đến máy ảo.
  • Kiểm tra việc thực hiện cung cấp cơ sở hạ tầng.
  • Thực hiện kiểm tra thời gian chạy của logic ứng dụng chạy bên trong cơ sở hạ tầng “đã được lập trình” của bạn để kiểm tra chức năng sau khi cung cấp.
  • Như chúng ta có thể thấy, có rất nhiều lựa chọn thử nghiệm cơ sở hạ tầng. Polumi có cơ chế kiểm tra ở mọi điểm trên quang phổ này. Hãy bắt đầu và xem nó hoạt động như thế nào.

Kiểm tra đơn vị

Các chương trình Pulumi được viết bằng các ngôn ngữ lập trình có mục đích chung như JavaScript, Python, TypeScript hoặc Go. Do đó, toàn bộ sức mạnh của các ngôn ngữ này, bao gồm các công cụ và thư viện, bao gồm cả khung kiểm tra, đều có sẵn cho chúng. Pulumi là dịch vụ nhiều đám mây, có nghĩa là nó có thể được sử dụng để thử nghiệm từ bất kỳ nhà cung cấp đám mây nào.

(Trong bài viết này, mặc dù đa ngôn ngữ và multicloud nhưng chúng tôi sử dụng JavaScript và Mocha và tập trung vào AWS. Bạn có thể sử dụng Python unittest, Đi khung kiểm tra hoặc bất kỳ khung kiểm tra nào khác mà bạn thích. Và tất nhiên, Pulumi hoạt động tốt với Azure, Google Cloud, Kubernetes.)

Như chúng ta đã thấy, có một số lý do khiến bạn muốn kiểm tra mã cơ sở hạ tầng của mình. Một trong số đó là thử nghiệm đơn vị thông thường. Bởi vì mã của bạn có thể có các chức năng - ví dụ: để tính CIDR, tính toán động tên, thẻ, v.v. - có lẽ bạn sẽ muốn kiểm tra chúng. Điều này cũng giống như viết bài kiểm tra đơn vị thông thường cho các ứng dụng bằng ngôn ngữ lập trình yêu thích của bạn.
Để phức tạp hơn một chút, bạn có thể kiểm tra cách chương trình của bạn phân bổ tài nguyên. Để minh họa, hãy tưởng tượng rằng chúng ta cần tạo một máy chủ EC2 đơn giản và chúng ta muốn chắc chắn những điều sau:

  • Các trường hợp có một thẻ Name.
  • Các trường hợp không nên sử dụng tập lệnh nội tuyến userData - chúng ta phải sử dụng AMI (hình ảnh).
  • Không nên có SSH tiếp xúc với Internet.

Ví dụ này dựa trên ví dụ của tôi aws-js-webserver:

chỉ mục.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;

Đây là chương trình Pulumi cơ bản: nó chỉ phân bổ một nhóm bảo mật EC2 và một phiên bản. Tuy nhiên, cần lưu ý rằng ở đây chúng tôi đang vi phạm cả ba quy tắc nêu trên. Hãy viết bài kiểm tra!

Viết bài kiểm tra

Cấu trúc chung của các bài kiểm tra của chúng tôi sẽ giống như các bài kiểm tra Mocha thông thường:

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, открытого в Интернет.
    });
});

Bây giờ hãy viết bài kiểm tra đầu tiên của chúng ta: đảm bảo rằng các phiên bản có thẻ Name. Để kiểm tra điều này, chúng ta chỉ cần lấy đối tượng phiên bản EC2 và kiểm tra thuộc tính tương ứng 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();
                }
            });
        });

Nó trông giống như một bài kiểm tra thông thường nhưng có một số tính năng đáng chú ý:

  • Vì chúng tôi truy vấn trạng thái của tài nguyên trước khi triển khai nên các thử nghiệm của chúng tôi luôn chạy ở chế độ "kế hoạch" (hoặc "xem trước"). Do đó, có nhiều thuộc tính mà giá trị của chúng sẽ không được truy xuất hoặc không được xác định. Điều này bao gồm tất cả các thuộc tính đầu ra được tính toán bởi nhà cung cấp đám mây của bạn. Điều này là bình thường đối với các thử nghiệm của chúng tôi - chúng tôi chỉ kiểm tra dữ liệu đầu vào. Chúng ta sẽ quay lại vấn đề này sau khi nói đến kiểm thử tích hợp.
  • Vì tất cả các thuộc tính tài nguyên Pulumi đều là đầu ra và nhiều thuộc tính trong số chúng được đánh giá không đồng bộ nên chúng ta cần sử dụng phương thức áp dụng để truy cập các giá trị. Điều này rất giống với lời hứa và chức năng then .
  • Vì chúng ta đang sử dụng một số thuộc tính để hiển thị URN tài nguyên trong thông báo lỗi nên chúng ta cần sử dụng hàm pulumi.allđể kết hợp chúng.
  • Cuối cùng, do các giá trị này được tính toán không đồng bộ nên chúng ta cần sử dụng tính năng gọi lại async tích hợp sẵn của Mocha done hoặc trả lại một lời hứa.

Sau khi thiết lập xong mọi thứ, chúng ta sẽ có quyền truy cập vào dữ liệu đầu vào dưới dạng các giá trị JavaScript đơn giản. Tài sản tags là một bản đồ (mảng kết hợp), vì vậy chúng tôi sẽ đảm bảo rằng nó (1) không sai và (2) có chìa khóa cho Name. Nó rất đơn giản và bây giờ chúng tôi có thể kiểm tra mọi thứ!

Bây giờ hãy viết séc thứ hai của chúng tôi. Nó thậm chí còn đơn giản hơn:

 // 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();
                }
            });
        });

Và cuối cùng, hãy viết bài kiểm tra thứ ba. Điều này sẽ phức tạp hơn một chút vì chúng tôi đang tìm kiếm các quy tắc đăng nhập được liên kết với nhóm bảo mật, trong đó có thể có nhiều quy tắc và phạm vi CIDR trong các quy tắc đó, trong đó cũng có thể có nhiều quy tắc. Nhưng chúng tôi đã quản lý:

    // 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();
                }
            });
        });

Đó là tất cả. Bây giờ hãy chạy thử nghiệm!

Chạy thử nghiệm

Trong hầu hết các trường hợp, bạn có thể chạy thử nghiệm theo cách thông thường, sử dụng khung thử nghiệm mà bạn chọn. Nhưng có một đặc điểm của Pulumi đáng được chú ý.
Thông thường, để chạy các chương trình Pulumi, pulimi CLI (giao diện dòng lệnh) được sử dụng để định cấu hình thời gian chạy ngôn ngữ, điều khiển khởi chạy công cụ Pulumi để các hoạt động với tài nguyên có thể được ghi lại và đưa vào kế hoạch, v.v. Tuy nhiên, có một vấn đề. Khi chạy dưới sự kiểm soát của khung thử nghiệm của bạn, sẽ không có giao tiếp giữa CLI và công cụ Pulumi.

Để giải quyết vấn đề này, chúng ta chỉ cần xác định rõ như sau:

  • Tên dự án, được chứa trong biến môi trường PULUMI_NODEJS_PROJECT (hay nói chung hơn là PULUMI__PROJECT для других языков).
    Tên của ngăn xếp được chỉ định trong biến môi trường PULUMI_NODEJS_STACK (hay nói chung hơn là PULUMI__ STACK).
    Các biến cấu hình ngăn xếp của bạn. Chúng có thể thu được bằng cách sử dụng biến môi trường PULUMI_CONFIG và định dạng của chúng là bản đồ JSON với các cặp khóa/giá trị.

    Chương trình sẽ đưa ra cảnh báo cho biết rằng kết nối với CLI/engine không khả dụng trong quá trình thực thi. Điều này quan trọng vì chương trình của bạn sẽ không thực sự triển khai bất kỳ thứ gì và bạn có thể ngạc nhiên nếu đó không phải là điều bạn dự định làm! Để nói với Pulumi rằng đây chính xác là thứ bạn cần, bạn có thể cài đặt PULUMI_TEST_MODE в true.

    Hãy tưởng tượng chúng ta cần chỉ định tên dự án trong my-ws, tên ngăn xếp devvà Khu vực AWS us-west-2. Dòng lệnh để chạy thử nghiệm Mocha sẽ như thế này:

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

    Làm điều này, đúng như mong đợi, sẽ cho chúng ta thấy rằng chúng ta đã thất bại ba lần kiểm tra!

    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

    Hãy sửa chương trình của chúng tôi:

    "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;
    

    Và sau đó chạy lại các bài kiểm tra:

    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)

    Mọi việc diễn ra tốt đẹp... Hoan hô! ✓ ✓ ✓

    Đó là tất cả cho ngày hôm nay, nhưng chúng ta sẽ nói về thử nghiệm triển khai trong phần thứ hai của bản dịch 😉

Nguồn: www.habr.com

Thêm một lời nhận xét