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

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。

介绍

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

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

在本教程中,您将为在 DigitalOcean Ubuntu 18.04 Droplet 上运行的 Ansible 剧本编写测试。 您将使用 Kitchen 作为测试运行程序并使用 InSpec 编写测试。 在本教程结束时,您将能够测试您的 Ansible playbook 部署。

先决条件

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

第 1 步 — 设置和初始化厨房

您已经安装了 ChefDK 作为 kitchen 附带的先决条件的一部分。 在此步骤中,您将设置 Kitchen 以与 DigitalOcean 通信。

在初始化 Kitchen 之前,您将创建并移至项目目录。 在本教程中,我们将其称为 ansible_testing_dir

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

mkdir ~/ansible_testing_dir

然后进入它:

cd ~/ansible_testing_dir

使用 gem 在本地机器上安装 kitchen-digitalocean 包。 这允许您告诉 kitchen 在运行测试时使用 DigitalOcean 驱动程序:

gem install kitchen-digitalocean

在项目目录中,您将在初始化 Kitchen 时运行 kitchen init 命令,指定 ansible_playbook 作为配置器,将 digitalocean 作为驱动程序:

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

您将看到以下输出:

Outputcreate  kitchen.yml
create  chefignore
create  test/integration/default

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

  • test/integration/default 是您将保存测试文件的目录。
  • chefignore 是用于确保某些文件不会上传到 Chef Infra Server 的文件,但您不会在本教程中使用它。
  • kitchen.yml 是描述您的测试配置的文件:您要测试的内容和目标平台。

现在,您需要将 DigitalOcean 凭据导出为环境变量,以便能够从 CLI 中创建 Droplet。 首先,通过运行以下命令从您的 DigitalOcean 访问令牌开始:

export DIGITALOCEAN_ACCESS_TOKEN="YOUR_DIGITALOCEAN_ACCESS_TOKEN"

您还需要获取您的 SSH 密钥 ID 号; 请注意,YOUR_DIGITALOCEAN_SSH_KEY_IDS 必须是 SSH 密钥的数字 ID,而不是符号名称。 使用 DigitalOcean API,您可以使用以下命令获取密钥的数字 ID:

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

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

Output...
 {"id":your-ID-number,"fingerprint":"fingerprint","public_key":"ssh-rsa your-ssh-key","name":"your-ssh-key-name"
...

注意:如果你想让你的输出更具可读性来获取你的数字ID,你可以在jq下载页面上找到并根据你的操作系统下载jq . 现在,您可以运行之前通过管道传输到 jq 的命令,如下所示:

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

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

Output{
  "ssh_keys": [
    {
      "id": YOUR_SSH_KEY_ID,
      "fingerprint": "2f:d0:16:6b",
      "public_key": "ssh-rsa AAAAB3NzaC1yc2 example@example.local",
      "name": "sannikay"
    }
  ],
}

确定 SSH 数字 ID 后,使用以下命令将其导出:

export DIGITALOCEAN_SSH_KEY_IDS="YOUR_DIGITALOCEAN_SSH_KEY_ID"

您已初始化 kitchen 并为您的 DigitalOcean 凭据设置环境变量。 现在,您将继续从命令行直接在 DigitalOcean Droplets 上创建和运行测试。

第 2 步——创建 Ansible 剧本

在这一步中,您将创建一个 剧本和角色,在下一步由 kitchen 创建的 Droplet 上设置 Nginx 和 Node.js。 您的测试将针对 playbook 运行,以确保满足 playbook 中指定的条件。

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

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

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

roles
├── nginx
│   └── tasks
└── nodejs
    └── tasks

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

nano roles/nginx/tasks/main.yml

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

角色/nginx/tasks/main.yml

---
- name: Update cache repositories and install Nginx
  apt:
    name: nginx
    update_cache: yes

- name: Change nginx directory permission
  file:
    path: /etc/nginx/nginx.conf
    mode: 0750

- name: start nginx
  service:
    name: nginx
    state: started

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

roles/nginx/tasks/main.yml 中,您定义了一个将更新 Droplet 的缓存存储库的任务,这相当于在服务器上手动运行 apt update 命令。 此任务还会更改 Nginx 配置文件权限并启动 Nginx 服务。

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

nano roles/nodejs/tasks/main.yml

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

角色/nodejs/tasks/main.yml

---
- name: Update caches repository
  apt:
    update_cache: yes

- name: Add gpg key for NodeJS LTS
  apt_key:
    url: "https://deb.nodesource.com/gpgkey/nodesource.gpg.key"
    state: present

- name: Add the NodeJS LTS repo
  apt_repository:
    repo: "deb https://deb.nodesource.com/node_{{ NODEJS_VERSION }}.x {{ ansible_distribution_release }} main"
    state: present
    update_cache: yes

- name: Install Node.js
  apt:
    name: nodejs
    state: present

完成后保存并退出文件。

roles/nodejs/tasks/main.yml 中,您首先定义一个任务,该任务将更新您的 Droplet 的缓存存储库。 然后在下一个任务中添加 Node.js 的 GPG 密钥,作为验证 Node.js apt 存储库真实性的一种方式。 最后两个任务添加 Node.js apt 存储库并安装 Node.js。

现在您将定义 Ansible 配置,例如变量、您希望角色运行的顺序以及超级用户权限设置。 为此,您将创建一个名为 playbook.yml 的文件,该文件用作 Kitchen 的入口点。 当您运行测试时,Kitchen 从您的 playbook.yml 文件开始并查找要运行的角色,即您的 roles/nginx/tasks/main.ymlroles/nodejs/tasks/main.yml 文件。

运行以下命令创建 playbook.yml

nano playbook.yml

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

ansible_testing_dir/playbook.yml

---
 - hosts: all
   become: true
   remote_user: ubuntu
   vars:
    NODEJS_VERSION: 8

保存并退出文件。

您已经创建了 Ansible playbook 角色,您将针对这些角色运行测试,以确保满足 playbook 中指定的条件。

第 3 步 — 编写 InSpec 测试

在此步骤中,您将编写测试以检查您的 Droplet 上是否安装了 Node.js。 在编写测试之前,让我们看一下示例 InSpec 测试的格式。 与许多测试框架一样,InSpec 代码类似于自然语言。 InSpec 有两个主要组成部分,要检查的主题和主题的预期状态:

块A

describe '<entity>' do
  it { <expectation> }
end

在块 A 中,关键字 doend 定义了一个 describe 关键字通常称为测试套件,其中包含测试用例。 it 关键字用于定义测试用例。

<entity> 是您要检查的主题,例如,包名称、服务、文件或网络端口。 <expectation> 指定期望的结果或期望的状态,例如,应安装 Nginx 或应具有特定版本。 您可以查看 InSpec DSL 文档 以了解有关 InSpec 语言的更多信息。

另一个 InSpec 测试块示例:

B座

control 'Can be anything unique' do  
  impact 0.7                         
  title 'A human-readable title'     
  desc  'An optional description'
  describe '<entity>' do             
    it { <expectation> }
  end
end

块 A 和块 B 之间的区别是 control 块。 control 块用作监管控制、建议或要求的手段。 control 块有一个名字; 通常是唯一的 ID,元数据如 desctitleimpact,最后将相关的 describe 块组合在一起以实现检查。

desctitleimpact 定义元数据,以简洁完整的描述充分描述控件的重要性及其用途。 impact 定义了一个范围从 0.01.0 的数值,其中 0.0<0.01 被归类为无影响,[ X136X] 至 <0.4 被归类为低影响,0.4<0.7 被归类为中等影响,0.7<0.9 被归类为高影响,0.91.0 被归类为关键控制。

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

创建 sample.rb

nano test/integration/default/sample.rb

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

测试/集成/默认/sample.rb

describe package('nodejs') do
  it { should be_installed }
end

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

完成后保存并退出文件。

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

打开您的 kitchen.yml 文件:

nano ansible_testing_dir/kitchen.yml

kitchen.yml 的内容替换为以下内容:

ansible_testing_dir/kitchen.yml

---
driver:
  name: digitalocean

provisioner:
  name: ansible_playbook
  hosts: test-kitchen
  playbook: ./playbook.yml

verifier:
  name: inspec

platforms:
  - name: ubuntu-18
    driver_config:
      ssh_key: PATH_TO_YOUR_PRIVATE_SSH_KEY
      tags:
        - inspec-testing
      region: fra1
      size: 1gb
      private_networking: false
    verifier:
      inspec_tests:
        - test/integration/default
suites:
  - name: default

platform 选项包括以下内容:

  • name:您正在使用的图像。
  • driver_config:您的 DigitalOcean Droplet 配置。 您正在为 driver_config 指定以下选项:
    • ssh_key:到 YOUR_PRIVATE_SSH_KEY 的路径。 您的 YOUR_PRIVATE_SSH_KEY 位于您在创建 ssh 密钥时指定的目录中。
    • tags:与您的 Droplet 关联的标签。
    • region:您希望托管 Droplet 的 region
    • size:你希望你的 Droplet 拥有的内存。
  • verifier:这定义了项目包含 InSpec 测试。
    • inspec_tests 部分指定测试存在于项目 test/integration/default 目录下。

注意 nameregion 使用缩写。 您可以查看 测试厨房文档 以了解您可以使用的缩写。

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

运行kitchen test命令运行测试。 这将检查是否安装了 Node.js - 这将故意失败,因为您当前在 playbook.yml 文件中没有 Node.js 角色:

kitchen test

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

Output: failing test results-----> Starting Kitchen (v1.24.0)
-----> Cleaning up any prior instances of <default-ubuntu-18>
-----> Destroying <default-ubuntu-18>...
       DigitalOcean instance <145268853> destroyed.
       Finished destroying <default-ubuntu-18> (0m2.63s).
-----> Testing <default-ubuntu-18>
-----> Creating <default-ubuntu-18>...
       DigitalOcean instance <145273424> created.
       Waiting for SSH service on 138.68.97.146:22, retrying in 3 seconds
       [SSH] Established
       (ssh ready)

       Finished creating <default-ubuntu-18> (0m51.74s).
-----> Converging <default-ubuntu-18>...
$$$$$$ Running legacy converge for 'Digitalocean' Driver
-----> Installing Chef Omnibus to install busser to run tests
       PLAY [all] *********************************************************************

       TASK [Gathering Facts] *********************************************************
       ok: [localhost]

       PLAY RECAP *********************************************************************
       localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

       Downloading files from <default-ubuntu-18>
       Finished converging <default-ubuntu-18> (0m55.05s).
-----> Setting up <default-ubuntu-18>...
$$$$$$ Running legacy setup for 'Digitalocean' Driver
       Finished setting up <default-ubuntu-18> (0m0.00s).
-----> Verifying <default-ubuntu-18>...
       Loaded tests from {:path=>". ansible_testing_dir.test.integration.default"}

Profile: tests from {:path=>"ansible_testing_dir/test/integration/default"} (tests from {:path=>"ansible_testing_dir.test.integration.default"})
Version: (not specified)
Target:  ssh://root@138.68.97.146:22

  System Package nodejs
     ×  should be installed 
     expected that System Package nodejs is installed

Test Summary: 0 successful, 1 failure, 0 skipped
>>>>>> ------Exception-------
>>>>>> Class: Kitchen::ActionFailed
>>>>>> Message: 1 actions failed.
>>>>>>     Verify failed on instance <default-ubuntu-18>.  Please see .kitchen/logs/default-ubuntu-18.log for more details
>>>>>> ----------------------
>>>>>> Please see .kitchen/logs/kitchen.log for more details
>>>>>> Also try running `kitchen diagnose --all` for configuration

  4.54s user 1.77s system 5% cpu 2:02.33 total

输出说明您的测试失败,因为您没有在使用 kitchen 配置的 Droplet 上安装 Node.js。 您将通过将 nodejs 角色添加到 playbook.yml 文件并再次运行测试来修复您的测试。

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

nano playbook.yml

将以下突出显示的行添加到您的文件中:

ansible_testing_dir/playbook.yml

---
 - hosts: all
   become: true
   remote_user: ubuntu
   vars:
    NODEJS_VERSION: 8

   roles:
    - nodejs

保存并关闭文件。

现在,您将使用 kitchen test 命令重新运行测试:

kitchen test

您将看到以下输出:

Output......
Target:  ssh://root@46.101.248.71:22

  System Package nodejs
     ✔  should be installed

Test Summary: 1 successful, 0 failures, 0 skipped
       Finished verifying <default-ubuntu-18> (0m4.89s).
-----> Destroying <default-ubuntu-18>...
       DigitalOcean instance <145512952> destroyed.
       Finished destroying <default-ubuntu-18> (0m2.23s).
       Finished testing <default-ubuntu-18> (2m49.78s).
-----> Kitchen is finished. (2m55.14s)
  4.86s user 1.77s system 3% cpu 2:56.58 total

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

以下是 Kitchen 在 Test Action 中所做的总结:

  • 销毁 Droplet(如果存在)
  • 创建液滴
  • 收敛液滴
  • 使用 InSpec 验证 Droplet
  • 破坏液滴

如果遇到任何问题,Kitchen 将中止您的 Droplet 的运行。 这意味着如果您的 Ansible playbook 失败,InSpec 将不会运行,您的 Droplet 也不会被销毁。 这使您有机会检查实例的状态并修复任何问题。 如果需要,可以覆盖最终销毁操作的行为。 通过运行 kitchen help test 命令查看 --destroy 标志的 CLI 帮助。

您已经编写了第一个测试并针对您的剧本运行它们,其中一个实例在修复问题之前失败。 接下来,您将扩展您的测试文件。

第 4 步 — 添加测试用例

在这一步中,您将在测试文件中添加更多测试用例,以检查您的 Droplet 上是否安装了 Nginx 模块以及配置文件是否具有正确的权限。

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

nano test/integration/default/sample.rb

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

测试/集成/默认/sample.rb

. . .
control 'nginx-modules' do
  impact 1.0
  title 'NGINX modules'
  desc 'The required NGINX modules should be installed.'
  describe nginx do
    its('modules') { should include 'http_ssl' }
    its('modules') { should include 'stream_ssl' }
    its('modules') { should include 'mail_ssl' }
  end
end

control 'nginx-conf' do
  impact 1.0
  title 'NGINX configuration'
  desc 'The NGINX config file should owned by root, be writable only by owner, and not writeable or and readable by others.'
  describe file('/etc/nginx/nginx.conf') do
    it { should be_owned_by 'root' }
    it { should be_grouped_into 'root' }
    it { should_not be_readable.by('others') }
    it { should_not be_writable.by('others') }
    it { should_not be_executable.by('others') }
  end
end

这些测试用例检查您的 Droplet 上的 nginx-modules 是否包括 http_sslstream_sslmail_ssl。 您还在检查 /etc/nginx/nginx.conf 文件权限。

您同时使用 itits 关键字来定义您的测试。 关键字 its 仅用于访问 资源 的属性。 例如,modulesnginx 的一个属性。

添加测试用例后保存并退出文件。

现在运行 kitchen test 命令再次测试:

kitchen test

您将看到以下输出:

Output...
Target:  ssh://root@104.248.131.111:22

  ↺  nginx-modules: NGINX modules
     ↺  The `nginx` binary not found in the path provided.
  ×  nginx-conf: NGINX configuration (2 failed)
     ×  File /etc/nginx/nginx.conf should be owned by "root"
     expected `File /etc/nginx/nginx.conf.owned_by?("root")` to return true, got false
     ×  File /etc/nginx/nginx.conf should be grouped into "root"
     expected `File /etc/nginx/nginx.conf.grouped_into?("root")` to return true, got false
     ✔  File /etc/nginx/nginx.conf should not be readable by others
     ✔  File /etc/nginx/nginx.conf should not be writable by others
     ✔  File /etc/nginx/nginx.conf should not be executable by others

  System Package nodejs
     ✔  should be installed
Profile Summary: 0 successful controls, 1 control failure, 1 control skipped
Test Summary: 4 successful, 2 failures, 1 skipped

你会看到一些测试失败了。 您将通过将 nginx 角色添加到您的剧本文件并重新运行测试来解决这些问题。 在失败的测试中,您正在检查服务器上当前不存在的 nginx 模块和文件权限。

打开您的 playbook.yml 文件:

nano ansible_testing_dir/playbook.yml

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

ansible_testing_dir/playbook.yml

---
- hosts: all
  become: true
  remote_user: ubuntu
  vars:
  NODEJS_VERSION: 8

  roles:
  - nodejs
  - nginx

完成后保存并关闭文件。

然后再次运行测试:

kitchen test

您将看到以下输出:

Output...
Target:  ssh://root@104.248.131.111:22
✔  nginx-modules: NGINX version
     ✔  Nginx Environment modules should include "http_ssl"
     ✔  Nginx Environment modules should include "stream_ssl"
     ✔  Nginx Environment modules should include "mail_ssl"
  ✔  nginx-conf: NGINX configuration
     ✔  File /etc/nginx/nginx.conf should be owned by "root"
     ✔  File /etc/nginx/nginx.conf should be grouped into "root"
     ✔  File /etc/nginx/nginx.conf should not be readable by others
     ✔  File /etc/nginx/nginx.conf should not be writable by others
     ✔  File /etc/nginx/nginx.conf should not be executable by others

  System Package nodejs
     ✔  should be installed

Profile Summary: 2 successful controls, 0 control failures, 0 controls skipped
Test Summary: 9 successful, 0 failures, 0 skipped

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

完成后,或者不再需要 Droplet,您可以通过运行 kitchen destroy 命令在运行测试后将其删除:

kitchen destroy

执行此命令后,您将看到类似于以下内容的输出:

Output-----> Starting Kitchen (v1.24.0)
-----> Destroying <default-ubuntu-18>...
       Finished destroying <default-ubuntu-18> (0m0.00s).
-----> Kitchen is finished. (0m5.07s)
  3.79s user 1.50s system 82% cpu 6.432 total

你已经为你的剧本编写了测试,运行了测试,并修复了失败的测试,以确保所有的测试都通过了。 您现在可以创建一个虚拟环境,为您的 Ansible Playbook 编写测试,并使用 Kitchen 在虚拟环境上运行您的测试。

结论

您现在有一个灵活的基础来测试您的 Ansible 部署,这允许您在运行在实时服务器上之前测试您的 playbook。 您还可以将您的测试打包到 配置文件 中。 您可以使用配置文件通过 Github 或 Chef Supermarket 共享您的测试,并在实时服务器上轻松运行它。

有关 InSpec 和 Kitchen 的更全面详细信息,请参阅 官方 InSpec 文档官方 Kitchen 文档