使用 Pulumi 测试基础设施即代码。 第1部分

朋友们下午好。 预计新的流量将以以下速度开始 “DevOps 实践和工具” 我们正在与您分享一个新的翻译。 去。

使用 Pulumi 测试基础设施即代码。 第1部分

使用 Pulumi 和通用编程语言进行基础设施代码(基础设施即代码)提供了许多优势:技能和知识的可用性、通过抽象消除代码中的样板、团队熟悉的工具,例如 IDE 和 linter。 所有这些软件工程工具不仅提高了我们的工作效率,而且还提高了代码的质量。 因此,使用通用编程语言很自然地让我们引入了另一种重要的软件开发实践—— 测试.

在本文中,我们将了解 Pulumi 如何帮助我们测试基础设施即代码。

使用 Pulumi 测试基础设施即代码。 第1部分

为什么要测试基础设施?

在详细讨论之前,有必要问一个问题:“为什么要测试基础设施?” 造成这种情况的原因有很多,以下是其中的一些原因:

  • 对程序逻辑的各个函数或片段进行单元测试
  • 根据某些限制验证基础设施的所需状态。
  • 检测常见错误,例如存储桶缺乏加密或未受保护的、从互联网到虚拟机的开放访问。
  • 检查基础设施配置落实情况。
  • 对“编程”基础设施内运行的应用程序逻辑执行运行时测试,以在配置后检查功能。
  • 正如我们所看到的,基础设施测试有多种选择。 Polumi 拥有针对该范围内每个点进行测试的机制。 让我们开始看看它是如何工作的。

单元测试

Pulumi 程序是用 JavaScript、Python、TypeScript 或 Go 等通用编程语言编写的。 因此,他们可以使用这些语言的全部功能,包括它们的工具和库,包括测试框架。 Pulumi 是多云的,这意味着它可以用于任何云提供商的测试。

(在本文中,尽管是多语言和多云,但我们使用 JavaScript 和 Mocha 并重点关注 AWS。您可以使用 Python unittest、Go 测试框架或您喜欢的任何其他测试框架。 当然,Pulumi 可以很好地与 Azure、Google Cloud、Kubernetes 配合使用。)

正如我们所看到的,您可能想要测试基础设施代码的原因有多种。 其中之一是传统的单元测试。 因为你的代码可能有函数——例如,计算CIDR、动态计算名称、标签等。 - 你可能想测试它们。 这与用您最喜欢的编程语言为应用程序编写常规单元测试相同。
更复杂一点的是,您可以检查程序如何分配资源。 为了说明这一点,我们假设我们需要创建一个简单的 EC2 服务器,并且我们希望确定以下几点:

  • 实例有一个标签 Name.
  • 实例不应使用内联脚本 userData - 我们必须使用 AMI(图像)。
  • 不应将 SSH 暴露于 Internet。

这个例子是基于 我的示例 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 资源属性都是输出,并且其中许多属性是异步计算的,因此我们需要使用 apply 方法来访问这些值。 这与 Promise 和 afunction 非常相似 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)

    一切都很顺利...万岁! ✓✓✓

    这就是今天的全部内容,但我们将在翻译的第二部分讨论部署测试 😉

来源: habr.com

添加评论