如何使用 InSpec 和 Kitchen 测试您的 Ansible 部署

作者选择了 多样性在技术基金作为 写给捐款计划的一部分接受捐款。

介绍

InSpec是一个开源审计和自动化测试框架,用于描述和测试监管问题,建议或要求。它被设计为人体可读和无平台。开发人员可以本地使用InSpec或使用SSH,WinRM或Docker来运行测试,因此不需要在正在测试的基础设施上安装任何包。

尽管在InSpec中,您可以直接在服务器上运行测试,但存在可能导致基础设施问题的人类错误的潜力。 为了避免这种情况,开发人员可以使用 Kitchen创建虚拟机并在测试运行的机器上安装他们选择的操作系统。 Kitchen 是一种测试运行器或测试自动化工具,允许您在一个或多个孤立平台上测试基础设施代码。 它还支持许多测试框架,并且具有 驱动程序插件架构的灵活性,适用于各种平台,如Vagrant,AWS,DigitalOcean,Docker,LXC容器等。

在本教程中,您将为您在DigitalOcean Ubuntu 18.04 Droplet上运行的Ansible播放书编写测试,您将使用Kitchen作为测试运行器和InSpec来编写测试。

前提条件

在您开始使用本指南之前,您还需要一个 DigitalOcean 帐户 除了以下内容:

您可以在您的机器上安装 Ruby 的本地安装。您可以按照本系列的分布式教程来安装 Ruby: 如何安装和设置 Ruby 的本地编程环境

步骤1 - 设置和初始化厨房

您已安装 ChefDK 作为配备厨房的先决条件的一部分. 在此步骤中,您将设置 Kitchen 以与 DigitalOcean 通信。

在初始化厨房之前,您将创建并移动到一个项目目录. 在本教程中,我们将称之为ansible_testing_dir

运行以下命令来创建目录:

1mkdir ~/ansible_testing_dir

然后进入它:

1cd ~/ansible_testing_dir

在您的本地计算机上安装 kitchen-digitalocean包,这样您就可以告诉‘kitchen’在运行测试时使用 DigitalOcean 驱动程序:

1gem install kitchen-digitalocean

在项目目录中,您将运行厨房 init命令,指定ansible_playbook作为提供器和digitalocean作为驱动程序,当您初始化厨房:

1kitchen init --provisioner=ansible_playbook --driver=digitalocean

您将看到以下输出:

1[secondary_label Output]
2create kitchen.yml
3create chefignore
4create test/integration/default

这在您的项目目录中创建了以下内容:

  • test/integration/default是您将测试文件保存到的目录。
  • chefignore是您将使用的文件,以确保某些文件不会上传到 Chef Infra Server,但您不会在本教程中使用它。

现在,您需要将您的 DigitalOcean 凭证导出为环境变量,以便您可以从您的 CLI 创建 Droplets。

1export DIGITALOCEAN_ACCESS_TOKEN="YOUR_DIGITALOCEAN_ACCESS_TOKEN"

您还需要获取您的 SSH 密钥 ID 号码;请注意,YOUR_DIGITALOCEAN_SSH_KEY_IDS必须是您的 SSH 密钥的数字 ID,而不是象征性名称。

1curl -X GET https://api.digitalocean.com/v2/account/keys -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN"

从此命令中,您将看到您的 SSH 密钥和相关元数据的列表. 阅读输出以找到正确的密钥并识别输出中的 ID 号码:

1[secondary_label Output]
2...
3 {"id":your-ID-number,"fingerprint":"fingerprint","public_key":"ssh-rsa your-ssh-key","name":"your-ssh-key-name"
4...

<$>[注] 注: 如果你想让你的输出更易读,以获得你的数字ID,你可以找到和下载 jq基于你的操作系统在 jq 下载页面

1curl -X GET https://api.digitalocean.com/v2/account/keys -H "Authorization: Bearer $DIGITALOCEAN_ACCESS_TOKEN" | jq

您将看到您的 SSH 密钥信息格式类似于:

 1[secondary_label Output]
 2{
 3  "ssh_keys": [
 4    {
 5      "id": YOUR_SSH_KEY_ID,
 6      "fingerprint": "2f:d0:16:6b",
 7      "public_key": "ssh-rsa AAAAB3NzaC1yc2 [email protected]",
 8      "name": "sannikay"
 9    }
10  ],
11}

美元

一旦您确定了您的 SSH 数字 ID,请使用以下命令导出它们:

1export DIGITALOCEAN_SSH_KEY_IDS="YOUR_DIGITALOCEAN_SSH_KEY_ID"

您已经初始化了厨房并为您的 DigitalOcean 凭证设置了环境变量,现在您将直接从命令行创建和运行您的 DigitalOcean Droplets 测试。

步骤 2 – 创建 Ansible Playbook

在此步骤中,您将创建一个 playbook and roles,将 Nginx 和 Node.js 设置在下一步由 `kitchen' 创建的 Droplet 上。

首先,为 Nginx 和 Node.js 角色创建一个角色目录:

1mkdir -p roles/{nginx,nodejs}/tasks

这将创建一个目录结构如下:

1roles
2├── nginx
3│   └── tasks
4└── nodejs
5    └── tasks

现在,使用您喜爱的编辑器在roles/nginx/tasks目录中创建一个main.yml文件:

1nano roles/nginx/tasks/main.yml

在此文件中,创建一个 task,通过添加以下内容来设置并启动 Nginx:

 1[label roles/nginx/tasks/main.yml]
 2---
 3- name: Update cache repositories and install Nginx
 4  apt:
 5    name: nginx
 6    update_cache: yes
 7
 8- name: Change nginx directory permission
 9  file:
10    path: /etc/nginx/nginx.conf
11    mode: 0750
12
13- name: start nginx
14  service:
15    name: nginx
16    state: started

添加内容后,保存并退出文件。

roles/nginx/tasks/main.yml中,您定义了一项任务,该任务将更新您的Droplet的缓存库,这相当于在服务器上手动运行apt update命令。

您还将在角色/nodejs/tasks中创建一个main.yml文件,以定义设置 Node.js 的任务:

1nano roles/nodejs/tasks/main.yml

将以下任务添加到此文件中:

 1[label roles/nodejs/tasks/main.yml]
 2---
 3- name: Update caches repository
 4  apt:
 5    update_cache: yes
 6
 7- name: Add gpg key for NodeJS LTS
 8  apt_key:
 9    url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
10    state: present
11
12- name: Add the NodeJS LTS repo
13  apt_repository:
14    repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main"
15    state: present
16    update_cache: yes
17
18- name: Install Node.js
19  apt:
20    name: nodejs
21    state: present

保存和退出文件,当你完成。

roles/nodejs/tasks/main.yml中,您首先定义了一项任务,该任务将更新您的Droplet的缓存库,然后在下一个任务中添加了用于验证Node.jsapt存储库的GPG密钥。

现在你将定义你的 Ansible 配置,如变量,你想要你的角色运行的顺序,以及超级用户特权设置。 要做到这一点,你将创建一个名为 playbook.yml 的文件,这将作为厨房的入口点。 当你运行你的测试时,厨房将从你的 playbook.yml 文件开始,并寻找要运行的角色,即你的 roles/nginx/tasks/main.ymlroles/nodejs/tasks/main.yml 文件。

运行以下命令来创建playbook.yml:

1nano playbook.yml

将以下内容添加到文件中:

1[label ansible_testing_dir/playbook.yml]
2---
3 - hosts: all
4   become: true
5   remote_user: ubuntu
6   vars:
7    NODEJS_VERSION: 8

保存和退出文件。

您已经创建了 Ansible 播放书角色,您将对其进行测试,以确保播放书中指定的条件得到满足。

步骤3 - 写你的InSpec测试

在此步骤中,您将编写测试来检查Node.js是否安装在您的Droplet上。在编写测试之前,让我们看看InSpec测试的格式。与许多测试框架一样,InSpec代码类似于自然语言。

1[label block A]
2describe '<entity>' do
3  it { <expectation> }
4end

在 A 块中,关键字 doend 定义一个 block。 关键字 describe 通常被称为测试套件,其中包含测试案例。

<entity> 是您要检查的主题,例如,一个包名称、服务、文件或网络端口。 <expectation> 指定了所需的结果或预期状态,例如, Nginx 应该安装或应该有特定版本。

另一个InSpec测试块的例子:

1[label block B]
2control 'Can be anything unique' do  
3  impact 0.7                         
4  title 'A human-readable title'     
5  desc  'An optional description'
6  describe '<entity>' do             
7    it { <expectation> }
8  end
9end

区块A和区块B之间的区别是控制区块,控制区块被用作监管控制、建议或要求的手段,控制区块有一个名称;通常有一个独特的ID、元数据如desc,标题,影响,最后组合相关的描述区块来执行检查。

影响定义了从0.01.0的数值,其中0.0<0.01被归类为无影响,0.010.4被归类为低影响,0.40.7被归类为中等影响,0.70.9被归类为高影响,0.91.0被归类为关键控制。

使用区块 A 的语法,您将使用 InSpec 的 package 资源来测试在系统上是否安装了 Node.js. 在您的 test/integration/default目录中创建一个名为sample.rb` 的文件。

创建sample.rb:

1nano test/integration/default/sample.rb

将以下内容添加到您的文件中:

1[label test/integration/default/sample.rb]
2describe package('nodejs') do
3  it { should be_installed }
4end

在这里,你的测试正在使用资源来检查Node.js是否安装。

保存和退出文件,当你完成。

要运行此测试,您需要编辑kitchen.yml,以指定您之前创建的播放簿并添加到您的配置中。

打开您的kitchen.yml文件:

1nano ansible_testing_dir/kitchen.yml

用以下内容取代「kitchen.yml」的内容:

 1[label ansible_testing_dir/kitchen.yml]
 2---
 3driver:
 4  name: digitalocean
 5
 6provisioner:
 7  name: ansible_playbook
 8  hosts: test-kitchen
 9  playbook: ./playbook.yml
10
11verifier:
12  name: inspec
13
14platforms:
15  - name: ubuntu-18
16    driver_config:
17      ssh_key: PATH_TO_YOUR_PRIVATE_SSH_KEY
18      tags:
19        - inspec-testing
20      region: fra1
21      size: 1gb
22      private_networking: false
23    verifier:
24      inspec_tests:
25        - test/integration/default
26suites:
27  - name: default

平台选项包括以下几点:

您正在指定以下选项:Driver_config:您正在使用的图像.

  • driver_config:您的 DigitalOcean Droplet 配置. 您正在指定以下选项:
  • ssh_key: 您正在使用的图像. 您的 YOUR_PRIVATE_SSH_KEY 的路径. 您的 YOUR_PRIVATE_SSH_KEY 位于您创建 ssh 键时指定的目录中。
  • tags: 与您的 Droplet 相关的标签.
  • region: 您希望您的 Droplet 托管的 区域.
  • size: 您希望您的 Droplet 拥有的

请注意,名称地区使用缩写,您可以在测试厨房文档(https://github.com/test-kitchen/kitchen-digitalocean)中查看您可以使用的缩写。

添加配置后,保存并退出文件。

运行厨房测试命令来运行测试,这将检查是否安装了 Node.js – 这会故意失败,因为您目前在playbook.yml文件中没有 Node.js 角色:

1kitchen test

您将看到类似于以下的输出:

 1[secondary_label Output: failing test results]
 2-----> Starting Kitchen (v1.24.0)
 3-----> Cleaning up any prior instances of <default-ubuntu-18>
 4-----> Destroying <default-ubuntu-18>...
 5       DigitalOcean instance <145268853> destroyed.
 6       Finished destroying <default-ubuntu-18> (0m2.63s).
 7-----> Testing <default-ubuntu-18>
 8-----> Creating <default-ubuntu-18>...
 9       DigitalOcean instance <145273424> created.
10       Waiting for SSH service on 138.68.97.146:22, retrying in 3 seconds
11       [SSH] Established
12       (ssh ready)
13
14       Finished creating <default-ubuntu-18> (0m51.74s).
15-----> Converging <default-ubuntu-18>...
16$$$$$$ Running legacy converge for 'Digitalocean' Driver
17-----> Installing Chef Omnibus to install busser to run tests
18       PLAY [all] *********************************************************************
19
20       TASK [Gathering Facts] *********************************************************
21       ok: [localhost]
22
23       PLAY RECAP *********************************************************************
24       localhost                  : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
25
26       Downloading files from <default-ubuntu-18>
27       Finished converging <default-ubuntu-18> (0m55.05s).
28-----> Setting up <default-ubuntu-18>...
29$$$$$$ Running legacy setup for 'Digitalocean' Driver
30       Finished setting up <default-ubuntu-18> (0m0.00s).
31-----> Verifying <default-ubuntu-18>...
32       Loaded tests from {:path=>". ansible_testing_dir.test.integration.default"}
33
34Profile: tests from {:path=>"ansible_testing_dir/test/integration/default"} (tests from {:path=>"ansible_testing_dir.test.integration.default"})
35Version: (not specified)
36Target:  ssh://root@138.68.97.146:22
37
38  System Package nodejs
39     ×  should be installed 
40     expected that System Package nodejs is installed
41
42Test Summary: 0 successful, 1 failure, 0 skipped
43>>>>>> ------Exception-------
44>>>>>> Class: Kitchen::ActionFailed
45>>>>>> Message: 1 actions failed.
46>>>>>>     Verify failed on instance <default-ubuntu-18>. Please see .kitchen/logs/default-ubuntu-18.log for more details
47>>>>>> ----------------------
48>>>>>> Please see .kitchen/logs/kitchen.log for more details
49>>>>>> Also try running `kitchen diagnose --all` for configuration
50
51  4.54s user 1.77s system 5% cpu 2:02.33 total

输出指出,你的测试失败了,因为你没有安装Node.js在你提供的厨房的Dropplet上。

编辑playbook.yml文件以包含nodejs角色:

1nano playbook.yml

添加以下突出的行到您的文件:

 1[label ansible_testing_dir/playbook.yml]
 2---
 3 - hosts: all
 4   become: true
 5   remote_user: ubuntu
 6   vars:
 7    NODEJS_VERSION: 8
 8
 9   roles:
10    - nodejs

保存并关闭文件。

现在,您将使用厨房测试命令重新启动测试:

1kitchen test

您将看到以下输出:

 1[secondary_label Output]
 2......
 3Target:  ssh://[email protected]:22
 4
 5  System Package nodejs
 6     ✔  should be installed
 7
 8Test Summary: 1 successful, 0 failures, 0 skipped
 9       Finished verifying <default-ubuntu-18> (0m4.89s).
10-----> Destroying <default-ubuntu-18>...
11       DigitalOcean instance <145512952> destroyed.
12       Finished destroying <default-ubuntu-18> (0m2.23s).
13       Finished testing <default-ubuntu-18> (2m49.78s).
14-----> Kitchen is finished. (2m55.14s)
15  4.86s user 1.77s system 3% cpu 2:56.58 total

您的测试现在通过,因为您使用nodejs角色安装了 Node.js。

以下是厨房在测试行动中所做的总结:

  • 破坏Droplet如果存在
  • 创建Droplet
  • 转换Droplet
  • 验证Droplet使用InSpec
  • 破坏Droplet

如果遇到任何问题,Kitchen 将中止您的 Droplet 运行,这意味着如果您的 Ansible 播放簿失败,InSpec 将不会运行,您的 Droplet 将不会被破坏,这会让你有机会检查实例的状态并修复任何问题。最终破坏操作的行为如果需要,可以被翻译。通过运行厨房帮助测试命令,检查 CLI 帮助破坏旗帜。

您已经编写了您的第一个测试,并在解决问题之前运行它们对您的播放簿,其中一个实例失败了。

步骤 4 – 添加测试案例

在此步骤中,您将向测试文件添加更多的测试案例,以检查 Nginx 模块是否安装在您的 Droplet 上,并且配置文件是否具有正确的权限。

编辑您的sample.rb文件以添加更多测试案例:

1nano test/integration/default/sample.rb

将以下测试案例添加到文件的末尾:

 1[label test/integration/default/sample.rb]
 2. . .
 3control 'nginx-modules' do
 4  impact 1.0
 5  title 'NGINX modules'
 6  desc 'The required NGINX modules should be installed.'
 7  describe nginx do
 8    its('modules') { should include 'http_ssl' }
 9    its('modules') { should include 'stream_ssl' }
10    its('modules') { should include 'mail_ssl' }
11  end
12end
13
14control 'nginx-conf' do
15  impact 1.0
16  title 'NGINX configuration'
17  desc 'The NGINX config file should owned by root, be writable only by owner, and not writeable or and readable by others.'
18  describe file('/etc/nginx/nginx.conf') do
19    it { should be_owned_by 'root' }
20    it { should be_grouped_into 'root' }
21    it { should_not be_readable.by('others') }
22    it { should_not be_writable.by('others') }
23    it { should_not be_executable.by('others') }
24  end
25end

这些测试案例检查您的Droplet上的nginx模块是否包括http_ssl,stream_sslmail_ssl

您正在使用关键字来定义您的测试. 关键字仅用于访问 资源的属性。

保存和退出文件,一旦您添加了测试案例。

现在运行厨房测试命令再次测试:

1kitchen test

您将看到以下输出:

 1[secondary_label Output]
 2...
 3Target:  ssh://root@104.248.131.111:22
 4
 5    nginx-modules: NGINX modules
 6       The `nginx` binary not found in the path provided.
 7  ×  nginx-conf: NGINX configuration (2 failed)
 8     ×  File /etc/nginx/nginx.conf should be owned by "root"
 9     expected `File /etc/nginx/nginx.conf.owned_by?("root")` to return true, got false
10     ×  File /etc/nginx/nginx.conf should be grouped into "root"
11     expected `File /etc/nginx/nginx.conf.grouped_into?("root")` to return true, got false
12       File /etc/nginx/nginx.conf should not be readable by others
13       File /etc/nginx/nginx.conf should not be writable by others
14       File /etc/nginx/nginx.conf should not be executable by others
15
16  System Package nodejs
17       should be installed
18Profile Summary: 0 successful controls, 1 control failure, 1 control skipped
19Test Summary: 4 successful, 2 failures, 1 skipped

您将看到一些测试失败,您将通过将nginx角色添加到播放簿文件并重新启动测试来修复这些测试,在失败的测试中,您正在检查当前不在您的服务器上存在的nginx模块和文件权限。

打开您的playbook.yml文件:

1nano ansible_testing_dir/playbook.yml

将以下突出的行添加到您的角色中:

 1[label ansible_testing_dir/playbook.yml]
 2---
 3- hosts: all
 4  become: true
 5  remote_user: ubuntu
 6  vars:
 7  NODEJS_VERSION: 8
 8
 9  roles:
10  - nodejs
11  - nginx

保存并关闭文件,当你完成。

然后再做测试:

1kitchen test

您将看到以下输出:

 1[secondary_label Output]
 2...
 3Target:  ssh://[email protected]:22
 4✔  nginx-modules: NGINX version
 5     ✔  Nginx Environment modules should include "http_ssl"
 6     ✔  Nginx Environment modules should include "stream_ssl"
 7     ✔  Nginx Environment modules should include "mail_ssl"
 8  ✔  nginx-conf: NGINX configuration
 9     ✔  File /etc/nginx/nginx.conf should be owned by "root"
10     ✔  File /etc/nginx/nginx.conf should be grouped into "root"
11     ✔  File /etc/nginx/nginx.conf should not be readable by others
12     ✔  File /etc/nginx/nginx.conf should not be writable by others
13     ✔  File /etc/nginx/nginx.conf should not be executable by others
14
15  System Package nodejs
16     ✔  should be installed
17
18Profile Summary: 2 successful controls, 0 control failures, 0 controls skipped
19Test Summary: 9 successful, 0 failures, 0 skipped

nginx角色添加到播放簿后,所有的测试都通过了,输出显示了http_ssl,stream_sslmail_ssl模块已安装在您的Droplet上,并为配置文件设置了正确的权限。

一旦你完成了,或者你不再需要你的Droplet,你可以通过运行厨房摧毁命令来摧毁它,在运行测试后删除它:

1kitchen destroy

按照这个命令,你会看到类似的输出:

1[secondary_label Output]
2-----> Starting Kitchen (v1.24.0)
3-----> Destroying <default-ubuntu-18>...
4       Finished destroying <default-ubuntu-18> (0m0.00s).
5-----> Kitchen is finished. (0m5.07s)
6  3.79s user 1.50s system 82% cpu 6.432 total

你已经为你的演示本写了测试,运行了测试,并修复了失败的测试,以确保所有测试都通过了。

结论

您现在有灵活的基础来测试您的 Ansible 部署,这允许您在实时服务器上运行之前测试您的播放书. 您还可以将测试包装成 配置文件. 您可以使用配置文件通过 Github 或 Chef Supermarket共享您的测试,并轻松地在实时服务器上运行。

有关 InSpec 和厨房的更多详细信息,请参阅 官方 InSpec 文件官方厨房文件

Published At
Categories with 技术
comments powered by Disqus