如何使用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 帐户:
- 在您的机器上本地安装
Ruby
。 您可以按照本系列中的发行版教程安装 Ruby:如何为 Ruby 安装和设置本地编程环境。 - Chef Development Kit (ChefDK) 安装在您的机器上。
- 按照 如何设置 SSH 密钥 的步骤 1 和 2 在您的机器上设置 SSH 密钥。 要将您的 SSH 公钥上传到您的 DigitalOcean 帐户,您可以按照我们的 如何将 SSH 密钥添加到 DigitalOcean 帐户 教程。
- 一个读写DigitalOcean个人访问令牌。 确保将令牌记录在安全的地方; 您将在本教程后面使用它。 这允许您在 DigitalOcean 上创建一个 Droplet,本教程将在其中运行测试。
第 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.yml
和 roles/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 中,关键字 do
和 end
定义了一个 块。 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,元数据如 desc
、title
、impact
,最后将相关的 describe
块组合在一起以实现检查。
desc
、title
和 impact
定义元数据,以简洁完整的描述充分描述控件的重要性及其用途。 impact
定义了一个范围从 0.0
到 1.0
的数值,其中 0.0
到 <0.01
被归类为无影响,[ X136X] 至 <0.4
被归类为低影响,0.4
至 <0.7
被归类为中等影响,0.7
至 <0.9
被归类为高影响,0.9
到 1.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
目录下。
注意 name
和 region
使用缩写。 您可以查看 测试厨房文档 以了解您可以使用的缩写。
添加配置后,保存并退出文件。
运行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_ssl
、stream_ssl
和 mail_ssl
。 您还在检查 /etc/nginx/nginx.conf
文件权限。
您同时使用 it
和 its
关键字来定义您的测试。 关键字 its
仅用于访问 资源 的属性。 例如,modules
是 nginx
的一个属性。
添加测试用例后保存并退出文件。
现在运行 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_ssl
、stream_ssl
和 mail_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 文档 。