Wednesday, January 30, 2019

Test Ansible Roles with Test-Kitchen and Vagrant

Overview

This writing aims to serve as notes and an introductory tutorial on the use of test-kitchen for the testing framework and exploring some of its strengths toward ansible playbooks integration testing. 

NOTE:
  The source of this article can be found here.

First things first, what is test-kitchen
Test Kitchen is an integration tool for developing and testing infrastructure code and software on isolated target platforms.

Test-kitchen Installation 

test-kitchen is a RubyGem and can be installed with:

$ gem install test-kitchen 
* If you use Bundler, you can add gem "test-kitchen" to your Gemfile and make sure to run bundle install.

The next step is to initialize a new project and add support to your library, Chef cookbook, or empty project :

$ kitchen init

Install the kitchen vagrant driver:

$ gem install kitchen-vagrant kitchen-ansible

    NOTE:  
     for docker support install 'kitchen-docker'

Lets tests everything works as expected. Start by getting a listing of your platform instances with:

$ kitchen list

Instance Driver Provisioner Verifier Transport Last Action Last Error
default-ubuntu-1604 Vagrant ChefSolo Busser Ssh <Not Created> <None> default-centos-7 Vagrant ChefSolo Busser Ssh <Not Created> <None>

Run Chef on an instance, in this case,default-ubuntu-1604 with:
$ kitchen converge default-ubuntu-1604

Destroy all instances with:
$ kitchen destroy

Run tests with: 
$ kitchen test

Basic Kitchen commands

  • kitchen create – Will create the VM or Docker Container to execute the tests on
  • kitchen converge – Will Execute the playbook in test (e.g. chef or ansible) 
  • kitchen verify – Will execute your verifier tests (e.g. ServerSpec)
  • kitchen destroy – Will destroy the Kitchen tests environment

Writing our first test

Before we move into creating our test lets review some basic terms of test kitchen i.e DriverPlatformsProvisioners and Test Suites.

Driver
  Drivers are the components that allow test kitchen to interface with various cloud providers and virtualization technologies in order to run your code.

  NOTE:
   Another popular choice could be docker but can also be providers such as AWS, EC2, GCE etc

Platforms
  Platforms are the operating systems you want to test on. This can be almost any operating system or even multiple. In this example, we use ubuntu trusty and centos 7.

Provisioner
 The provisioner is the tool that we are going to use to converge our machine and/or product.  This guide in focused on ansible configuration management tool which is to be used as out provisioner. Another popular provisioner includes chefsaltstack etc.

     Busser
     In the provisioner section, we can define  require_ruby_for_busser as true. This instructs Test  Kitchen to install ruby in platform box.  In our case, this is required because we are about to unit test and ensure that we expect to see specific changes.

     ruby_bindir
     Defining this property we can specify the path our ruby executable file exists.

Test suites
 Tests Suites or just suites corresponds the lists of tests to run against each platform.

Test an Ansible role

Let's start to create an Ansible role that we will test using test-kitchen.  

Now, let's initialize test-kitchen for testing our role using vagrant as a driver and ansible as a provisioner:

$ kitchen init --driver=vagrant --provisioner=ansible create kitchen.yml create chefignore create test/integration/default

From the init command we can see that kitchen.yml and test/integration/default folder to have been created. The kitchen.yml file aims to host our configuration while the test/integration/default folder to host our tests.

The contents of kitchen.yml should be :
---driver: name: vagrant
provisioner: name: ansible
platforms:
- name: ubuntu-16.04 - name: centos-7
suites: - name: default run_list: attributes:
The platform names resolve to vagrant boxes and some modification to further adjust the provisioner section configuration changes should be made as seen below:
--- driver: name: vagrant provisioner: # please correct if ansible only provided name: ansible_playbook # Use the latest version on ansible for the test # (please specify the one you wish to test against) ansible_version: latest # It is a good practive to keep logging verbose for # while testing your playbooks, it provide valuable # info ansible_verbose: true # define verbosity level ansible_verbosity: 2 require_ansible_repo: true # install ruby on the testing platform require_ruby_for_busser: true # Playbook configuration name: ansible_playbook # define the host of the testing platform hosts: test-kitchen # set env to local extra_vars: env: local
busser: # define the ruby execiutable location ruby_bindir: /usr/bin platforms: - name: ubuntu-16.04 - name: centos-7 # testing suites to run against the deployment # and verify state suites: - name: default driver: network: # define a specific address for the vm - ["private_network", { ip: "192.168.0.2" } ]
Let's verify our installation and configuration so far. In order to do a first sanity check use kitchen list command to list all kitchen instances.

$ kitchen list
Instance Driver Provisioner Verifier Transport Last Action
default-ubuntu-1604 Vagrant AnsiblePlaybook Busser Ssh <Not Created>
default-centos-7 Vagrant AnsiblePlaybook Busser Ssh <Not Created>

Create a sanity test role to test against the platforms, i.e copy-paste the following snippet in test/integration/default/default.yml
--- - hosts: test-kitchen tasks: - name: echo hello command: echo 'Hello World' - name: create a folder file: path: /tmp/test mode: 0755 state: directory
Now proceed with testing in both ubuntu and centos platforms.
$ kitchen create # to create the vms
...
$ kitchen converge # to run the playbooks in test
...
-----> Kitchen is finished. (0m2.01s)
Up until now, we have created the VMS and successfully run the playbooks against them. The next step is to verify our changes. For this part, we are going to use ServerSpec framework (i.e RSpec for infrastructure).

  The ansible role has just printed a "hello" message and created a "/tmp/test" folder.  In this example test, we are going to verify that the folder is created indeed. The following two files will allow us to proceed with this test.

 Firstly create a file test/integration/default/serverspec/spec_helper.rb with the following contents .
# -*- encoding: utf-8 -*- require 'serverspec' set :backend, :exec
And then define the serverspec test in test/integration/default/serverspec/sample_spec.rb with the following contents.

# -*- encoding: utf-8 -*- require 'spec_helper' describe 'Nothing' do describe file('/tmp/test') do it { should be_directory } end end
In order to run the tests and verify the changes, we need to run the verify command. Let's try to verify ubuntu VM i.e

$ kitchen verify ubuntu -----> Starting Kitchen (v1.23.3) -----> Verifying <default-ubuntu-1604>... Preparing files for transfer -----> Busser installation detected (busser) -----> Busser plugin detected: busser-serverspec Removing /tmp/verifier/suites/serverspec Transferring files to <default-ubuntu-1604> -----> Running serverspec test suite /usr/bin/ruby2.3 -I/tmp/verifier/suites/serverspec -I/tmp/verifier/gems/gems/rspec-support-3.8.0/lib:/tmp/verifier/gems/gems/rspec-core-3.8.0/lib /tmp/verifier/gems/bin/rspec --pattern /tmp/verifier/suites/serverspec/\*\*/\*_spec.rb --color --format documentation --default-path /tmp/verifier/suites/serverspec Nothing File "/tmp/test" should be directory Finished in 0.07129 seconds (files took 0.17919 seconds to load) 1 example, 0 failures Finished verifying <default-ubuntu-1604> (0m1.16s). -----> Kitchen is finished. (0m1.28s)
NOTE: Please note that centos testing would not work out of the box and some additional work will need, this will not part of the guide.
And the test shows us that the changes are successful! 

Conclusion 

Test-kitchen with ansible and serverspec is a powerful combination which allows to fully test our deployments in multiple environments. The very first setup might seem a bit complex but is easy and fast to get the idea and create powerful feedback loops allowing a Test-Driven Development  (TDD) approach to infrastructure deployment toward more stable and safe deployments.

Enjoy!

Troubleshooting

Dependencies issues

In case of issues risen installing the gem dependencies by hand please consider a Gemfile i.e
source 'https://rubygems.org' 
gem 'test-kitchen', '~> 1.8.0' gem 'kitchen-vagrant' gem 'kitchen-ansible' gem 'net-ssh' gem 'serverspec' gem 'kitchen-verifier-serverspec'
And run bundler install in order to proceed with installing them.

Windows Support

Test-kitchen tests support Windows, assuming your test server contains software required to support winrm. The winrm module can be used and provide the connection configuration for the windows server.
For example in .kitchen.yml we need to define the following properties:
ansible_connection: winrm
require_windows_support: true
require_chef_for_busser: false

Parsing date error 

In some cases, we have encountered the following error from the safe_yaml package when try to parse date i.e:
$ kitchen/var/lib/gems/2.3.0/gems/safe_yaml-1.0.4/lib/safe_yaml/parse/date.rb:22:in `<class:Date>': uninitialized constant SafeYAML::Parse::Date::DateTime (NameError)Did you mean? SafeYAML::Parse::Date::DATE_MATCHER from /var/lib/gems/2.3.0/gems/safe_yaml-1.0.4/lib/safe_yaml/parse/date.rb:3:in `<class:Parse>' from /var/lib/gems/2.3.0/gems/safe_yaml-1.0.4/lib/safe_yaml/parse/date.rb:2:in `<module:SafeYAML>' from /var/lib/gems/2.3.0/gems/safe_yaml-1.0.4/lib/safe_yaml/parse/date.rb:1:in `<top (required)>' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /var/lib/gems/2.3.0/gems/safe_yaml-1.0.4/lib/safe_yaml/load.rb:14:in `<top (required)>' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:127:in `require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:127:in `rescue in require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:40:in `require' from /var/lib/gems/2.3.0/gems/test-kitchen-1.19.2/lib/kitchen/loader/yaml.rb:21:in `<top (required)>' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /var/lib/gems/2.3.0/gems/test-kitchen-1.19.2/lib/kitchen.rb:42:in `<top (required)>' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /var/lib/gems/2.3.0/gems/test-kitchen-1.19.2/lib/kitchen/cli.rb:21:in `<top (required)>' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /usr/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require' from /var/lib/gems/2.3.0/gems/test-kitchen-1.19.2/bin/kitchen:10:in `<top (required)>' from /usr/local/bin/kitchen:22:in `load' from /usr/local/bin/kitchen:22:in `<main>'
For more information and a fix please refer to safe_yaml issue 80

No comments:

Post a Comment