使用Pulumi将您的基础架构作为代码进行测试。 第一部分

朋友们,下午好。 期待“ DevOps实践和工具”课程上有新的开端, 我们正在与您分享新的翻译。 走吧



将Pulumi和通用编程语言用于基础结构作为代码具有许多优点:拥有技能和知识,通过抽象消除代码中的样板,团队熟悉的工具,例如IDE和linters。 所有这些软件工程工具不仅使我们工作效率更高,而且还提高了代码质量。 因此,使用通用编程语言自然可以使您在软件开发- 测试中引入另一种重要实践。

在本文中,我们将研究Pulumi如何帮助测试“基础结构即代码”。



为什么要测试基础架构?


在详细介绍之前,值得提出一个问题:“为什么我们需要对基础设施进行全面测试?” 造成这种情况的原因有很多,其中有一些原因:

  • 对程序中的各个功能或逻辑部分进行单元测试
  • 检查所需的基础结构状态,以确保符合某些限制。
  • 检测常见错误,例如缺少存储桶加密或不安全,可以从Internet开放访问虚拟机。
  • 验证基础架构配置。
  • 对在“编程”基础架构中运行的应用程序的逻辑执行运行时测试,以在置备后检查运行状况。
  • 我们可以看到,有各种各样的基础架构测试选项。 Polumi具有在此频谱中每个点进行测试的机制。 让我们开始,看看它是如何工作的。

单元测试


Pulumi程序是使用通用编程语言(例如JavaScript,Python,TypeScript或Go)创建的。 因此,它们可以使用这些语言的全部功能,包括其工具和库,包括测试框架。 Pulumi是多云的,这意味着可以使用任何云提供商进行测试。

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

正如我们已经看到的,有几个原因可能导致您需要测试基础结构代码。 其中之一是通常的单元测试。 由于您的代码可能具有某些功能-例如,计算CIDR,动态计算名称,标签等。 -您可能要测试它们。 这与使用您喜欢的编程语言为应用程序编写常规单元测试相同。
如果您使事情复杂化,可以检查程序如何分配资源。 为了说明,假设我们需要创建一个简单的EC2服务器,并且需要确保以下几点:

  • 实例具有Name标签。
  • 实例不应使用userData内联脚本-我们必须使用AMI(图像)。
  • Internet上不应打开SSH。

本示例是基于我的aws-js-webserver示例编写的:

index.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方法访问值。 这和诺言非常相似。
  • 由于我们使用几个属性来在错误消息中显示资源URN,因此我们需要使用pulumi.all函数将它们组合在一起。
  • 最后,由于这些值是异步计算的,因此我们需要将Mocha的内置异步功能与done回调或promise返回一起使用。

配置完所有内容后,我们将可以使用简单的JavaScript值访问输入数据。 tags属性是一个映射(关联数组),因此我们只需确保它是(1)不为false,并且(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的一项功能。
通常,Pulimi CLI(命令行界面,命令行界面)用于启动Pulumi程序,该程序设置语言的运行时,控制Pulumi引擎的启动,以便可以记录资源操作并将其包含在计划中,等等。 但是,有一个问题。 在测试框架的控制下启动时,CLI和Pulumi引擎之间将没有连接。

为了解决这个问题,我们只需要指定以下内容:

  • 项目的名称,包含在环境变量PULUMI_NODEJS_PROJECT (或更PULUMI__PROJECT ).PULUMI__PROJECT ).PULUMI__PROJECT ).
    在环境变量PULUMI_NODEJS_STACK (或更PULUMI__ STACK).指定的堆栈名称PULUMI__ STACK).
    您的堆栈配置变量。 可以使用PULUMI_CONFIG环境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) 

    一切进展顺利...万岁! ✓✓✓

    今天就这些了,但是我们将翻译的第二部分中讨论测试部署;-)

Source: https://habr.com/ru/post/zh-CN463001/


All Articles