Thursday, May 4, 2017

Puppet ServerSpec with Test Kitchen

I initially discovered test kitchen through Chef, but having worked in many different DevOps environments have found that although popular in Chef has been adapted to work with Puppet, albeit with some minor configuration.

The handy thing about test kitchen is the ability to switch environments, e.g. from Vagrant to AWS EC2, etc for being able to start up and perform server testing on virtual nodes.  It also makes the use of Rake easier since you don't have to worry about the configuration of the Rakefile, or VM files, e.g. Vagrantfile.  All configuration is made in the .kitchen.yml file, and inside a fixtures directory to inform Kitchen where the dependent modules and the module to test are.

The project directory;

The above diagram shows how the .kitchen.yml file maps to the directory structure of our project.  Since we are serverspec testing we are less interested in the code, but need access to the configuration management environment so that Kitchen can provision the nodes we require.

This example shows only 1 test suite, called default, which has a corresponding directory in the test/integration directory.  Each test suite is named and associated to particular nodes.  As part of the suites section you can target specific tests to specific hosts by adding a driver section and vm_hostname as per the below example;

suites:
  - name: web
    mainfests: site.pp
    driver:
      vm_hostname: web1.tps-services.ltd.uk
  - name: database
    manifests: site.pp
    driver:
      vm_hostname: db1.tps-services.ltd.uk


Each test in this example makes use of the site.pp, which contains node definitions such as;

node /^web/ {
}

node /^db/ {
}


These will then be picked up and the Puppet configuration applied.

The web and database suite names will then have corresponding directories in the test/integration directory.

Each test suite then has a serverspec directory containing a spec_helper.rb and hostname, IP addres or localhost directories which will contain the tests relevant to each host.  For Kitchen tests only localhost would be required.

Preparing for serverspec testing

To create the test/integration layout you should make use of the kitchen init command.  To do this you will need to run the following commands;
  • bundle init
    • This will create a Gemfile for you
  • echo -e "test-kitchen\nkitchen-puppet" >>Gemfile
    • Add the test kitchen tools to the project Gemfile
  • bundle install
    • Will install kitchen into your environment
Once kitchen is installed you can then create the directory structure using;
  • kitchen init
    • Creates the relevant directory and files for kitchen test in your project
      • This would include .kitchen.yml and all kitchen files
    • This should be done at the root of your Puppet environment, e.g. where the manifests and modules directory would show in a listing.
  • bundle install
    • This is done again to ensure we have all the necessary modules

The .kitchen.yml file

The core file for defining the configuration of Kitchen to build, provision and test VMs is done in the file called .kitchen.yml

The driver section defines the VM platform you will be using, so possible to have automated testing systems build and test our provisioning for us. The provisioner part tells the system how we will be provisioning, e.g. Puppet, Ansible, Chef, etc. The platforms defines the type of VM image that will be required and its configuration to the driver software, and the suites are where the starting point of the provisioner are and also any testing.

Example file using Vagrant:
---
driver:
  name: vagrant

provisioner:
  name: puppet_apply
  manifests_path: manifests
  modules_path: modules
  hierdata_path: hierdata
  require_puppet_collections: true
  puppet_yum_collections_repo: https://yum.puppetlabs.com/puppetlabs-release-pc1-el-7.noarch.rpm


# See - https://github.com/neillturner/kitchen-puppet/blob/master/provisioner_options.md  for more options

platforms:
  - name: centos-7.2
    driver_plugin: vagrant
    driver_config:
      box: bento/centos-7.2

verifier:
  ruby_bindir: '/usr/bin/'

suites:
  - name: default
    manifests: site.pp
    driver:
      vm_hostname: web1.tps-services.ltd.uk

The .kitchen.yml file should be at the root of your environment directory that you wish to perform tests in.

Example file using AWS ec2

---
driver:
  name: ec2
  aws_ssh_key_id: steveshilling
  region: eu-west-2
  availability_zone: b
  instance_type: t2.micro
  iam_profile_name: ee_kitchen
  security_group_ids: ["sg-6d30b304"]

provisioner:
  name: puppet_apply
  manifests_path: manifests
  modules_path: modules
  hierdata_path: hierdata

platforms:
  - name: centos-7.2
    driver:
      image_id: ami-9c363cf8
    transport:
      username: ec2-user
      ssh_key: ~/.ssh/id_rsa

verifier:
  ruby_bindir: '/usr/bin/'

suites:
  - name: default
    manifests: site.pp
    driver:
      vm_hostname: web1.tps-services.ltd.uk


Adding the serverspec test directory

To be able to write your serverspec tests, you could create the directory structure and files yourself, or you could create the initial structure as follows, and then add the rest of your files.
  • /opt/puppetlabs/puppet/bin/gem install serverspec
Note that we are using the Puppet gem command not the system one, since we want this to work with Puppet.
  • serverspec-init
The command that will create the initial test directory structure and the spec_helper.rb file for a host.  When you run this command you will be asked whether you are testing local or SSH.  For kitchen you can chose local.

This will generate a spec_helper.rb file under test/integration/default/localhost which will contain;


require 'serverspec' 

set :backend, :exec 

After running this command you will have the following structure;
test
└── integration
    └── default
        └── serverspec
            ├── localhost
            |   └── sample_spec.rb
            └── spec_helper.rb

You can now write your serverspec tests under the localhost directory, and create further directories based on the name of your tests.  The sample_spec.rb is an example set of tests, so you would define files based on the types of test you wish to carry out, to which you may have multiple files in the localhost folder.

Example serverspec test file

require 'spec_helper'

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

describe service('nginx') do
  it { should be_enabled }
  it { should be_running }
end

describe port(80) do
  it { should be_listening }
end

Running your tests

Once you have your tests ready you can perform a test run by typing;
  • kitchen test
This will build the VMs, run the configuration management and then the serverspec tests for each suite.  If you have dependencies on other VMs being built then you should order the suites section correctly, since it is sequential.  If your web nodes rely on a database node, then the database node should be in the list first and tested.

If all tests complete successfully then the VMs will all be destroyed.  If there is a failure then those VMs that have completed their testing will still be running, including the one that failed.  To remove the VMs simply run;

  • kitchen destroy
If you only want to build and configure VMs but not test then you can run;
  • kitchen converge

Tuesday, May 2, 2017

Insecure Docker Registry on RHEL

There are many documents out there on the inter web that tell you to do the following to make your Docker Registry run in insecure mode;
  • Edit one of the configuration files;
    • /etc/default/docker
      • DOCKER_OPTS="--insecure-registry yourPublicIP:dockerPort"
    • /etc/sysconfig/docker
      • OPTIONS="--insecure-registry yourPublicIP:dockerPort"
These are OK as long as they exist or are recognised, and in most cases would apply to the good old sysvinit method.

The way to identify is that you will normally receive the following error message when using your hosts public IP address instead of localhost;

Get https://192.168.0.20:5000/v1/_ping: http: server gave HTTP response to HTTPS client

If you find that neither of the above work after restarting the Docker daemon then try the following solution for those systems using systemd.

  1. Edit the file /usr/lib/systemd/system/docker.service
  2. In the file look for the line ExecStart and add to the end of that line the --insecure-registry, e.g. if your host IP is 192.168.0.100 and you map to port 5000
      ExecStart=/usr/bin/dockerd --insecure-registry 192.168.0.20:5000
  3. Save the file
  4. Tell systemd that you have changed the configuration
      sudo systemctl daemon-reload
  5. Restart your docker daemon
      sudo systemctl restart docker
  6. Start your docker containers
You should now be able to push to the public IP address of your server which will forward on to your Docker container running the registry.

This article is based on using https://hub.docker.com/_/registry/ image to run your Private Docker Registry, you will find that unless you create certificates for the service and map the certificate directory you will only be able to use localhost:port/tagName to push your images to the repository.  This is due to your Docker daemon running on an SSL port, and the client wanting to make secure connections.

Friday, April 21, 2017

Puppet RSpec set up and testing with Puppet Forge module dependencies

Puppet & RSpec

As at the 21st April 2017, Puppet and RSpec testing is still an issue, with .fixtures.yml not full functioning as it should, that is loading dependencies into the spec/fixtures directory.

Even when you place your .fixtures.yml in the root of your project directory, which is to say the module where the spec directory lives along with the modules and manifest, and you run the puppet-spec-init or rspec or rake commands your fixtures file is ignored, and the only module linked is the current module.

It is essential that after creating your module with the puppet module generate command you will get the necessary layout for your puppet module.  Once you start to build your module you’ll eventually want to add some code spec tests to ensure that someone doesn’t break the existing code.

ServerSpec tests can be run independently of your code, so the key difference is that RSpec is there to perform tests on your code and not the final server build.

To fix this means you still need to deal with all your module dependencies manually, so here are the steps.

Example project space;

The Rakefile

require 'rspec-puppet/rake_task'
begin
  if Gem::Specification::find_by_name('puppet-lint')
    require 'puppet-lint/tasks/puppet-lint'
    PuppetLint.configuration.send('disable_autoloader_layout')
    PuppetLint.configuration.ignore_paths = ["spec/**/*.pp""vendor/**/*.pp"]
    task :default => [:rspec, :lint]
  end
rescue Gem::LoadError
  task :default => :rspec
end

This file controls what will happen when the tests run, such as in this example a linter will run against your code first, and certain files will be ignores by the linter as they are not puppet code.

The spec_helper.rb file

This file defines other files required to help perform the testing, and where the files to test can be located.

require 'rspec-puppet/spec_helper' 

fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) 

RSpec.configure do |c| 
  c.module_path = File.join(fixture_path, 'modules') 
  c.manifest_dir = File.join(fixture_path, 'manifests') 
  c.environmentpath = File.join(Dir.pwd, 'spec') 
end


Preparing the module dependencies

First let’s take a look at a sample directory layout of what is required;

mysqlmodule
├── Gemfile
├── Gemfile.lock
├── manifests
│   └── init.pp
├── Rakefile
├── spec
│   ├── classes
│   │   └── mysqltest_spec.rb
│   ├── defines
│   ├── fixtures
│   │   ├── manifests
│   │   │   └── site.pp
│   │   └── modules
│   │       ├── mysql
│   │       │   ├── lib -> ../../../../../mysql/lib
│   │       │   ├── manifests -> ../../../../../mysql/manifests
│   │       │   └── templates -> ../../../../../mysql/templates
│   │       ├── mysqlmodule
│   │       │   ├── manifests -> ../../../../manifests
│   │       │   └── templates -> ../../../../templates
│   │       ├── staging
│   │       │   ├── files -> ../../../../../staging/files
│   │       │   ├── libs -> ../../../../../staging/libs
│   │       │   └── manifests -> ../../../../../staging/manifests
│   │       └── stdlib
│   │           ├── lib -> ../../../../../stdlib/lib
│   │           ├── manifests -> ../../../../../stdlib/manifests
│   │           └── types -> ../../../../../stdlib/types
│   ├── functions
│   ├── hosts
│   └── spec_helper.rb
└── templates

You’ll notice that in this module the user has made use of PuppetForge modules for MySQL, which in itself has other dependencies on Staging and StdLib. For RSpec to work these fixtures need to be available during the test to enable checking of your own modules code.  For example;

it { is_expected.to contain_class_mysql__db(‘mydb’) }

This would look for  mysql::db{‘mydb’:} in your code.
Since the .fixture.yml is not being read at the moment, you should manually add the dependencies to your 
spec/fixtures/modules directory.  Let’s take the MySQL module.

In our modules directory, that is where we would see mysqlmodule if listed, we would ensure that we have done a puppet module install of those modules require, and making sure we note the dependencies that have been downloaded too.

Here is the layout of the MySQL puppetlabs/mysql module;

mysql
├── CHANGELOG.md
├── checksums.json
├── CONTRIBUTING.md
├── examples/
├── Gemfile
├── lib/
├── LICENSE
├── manifests/
├── metadata.json
├── NOTICE
├── Rakefile
├── README.md
├── spec/
├── templates/
└── TODO

In our fixtures directory under modules we will create a directory called mysql and within that directory you will create symlinks back to the essential directories and files that make up the module.  In this case we will symlink to;
- lib
- manifests
- templates

We would then do the same for the other 2 dependent modules stdlib and staging to ensure that our tests will work with all elements.

Overcoming variable issues

When running rspec after configuring your module dependencies, you may then find that your test still fails.  Right near the top of the output you might see something like;

F

Failures:

  1) mysqlmodule should contain File_line[/etc/my.cnf] with bind-address => "0.0.0.0"
     Failure/Error: it {is_expected.to contain_file_line('/etc/my.cnf').with('bind-address' => '0.0.0.0')}
     
     Puppet::PreformattedError:
       Evaluation Error: Unknown variable: '::osfamily'. at /home/steve/web/puppet/modules/mysqlmodule/spec/fixtures/modules/mysql/manifests/params.pp:36:8 on node tpslaptop

From this output we are interested in the line that comes after Puppet::PreformatedError which states that the variable ::osfamily is missing.  This is actually a Puppet Facter variable, and would normally be collected by the Puppet Master during the compilation of the Puppet catalog.  We therefore need to define all of the values for the Facts in our classes spec test.

Our spec test file is called mysqltest_spec.rb in spec/classes, and originally looked like;

require 'spec_helper'
describe 'mysqlmodule' do
  it {is_expected.to contain_file_line('/etc/my.cnf').with('bind-address' => '0.0.0.0')}
end
  
The test is using the file_line type from the mysql module, and we need to tell our test to set the osfamily facter variable before this is run.  The change to the code that will allow the variable to be set is;

let(:facts) do
  { :osfamily => RedHat}
end

This piece of code is added within the describe block before the tests, and we have provided a value.

We would need to add each variable into the block as they appear during our tests.  For example, the next one to pop up would be ::operatingsystem, so we would have the following code;

require 'spec_helper'
describe 'mysqlmodule' do
  let(:facts) do
{
  :osfamily => 'RedHat' ,
      :operatingsystem => 'RedHat' 
    }
  end
  it {is_expected.to contain_file_line('/etc/my.cnf').with('bind-address' => '0.0.0.0')}
end

After running through the test we eventually find that to test the file_line type for the MySQL module we would need the following;

describe 'mysqlmodule' do
  let(:facts) do
    { :osfamily => 'RedHat',
    :operatingsystemmajrelease => '7.2',
    :root_home => '',
    :puppetversion => '4.10.0',
    :operatingsystem => 'RedHat' }
  end

  it {is_expected.to contain_file_line('Edit my.cnf')}
end

This will finally result in your module being checked, or at least the file_line type;

.

Finished in 2.24 seconds (files took 0.93215 seconds to load)
1 example, 0 failures



Now you repeat for the other modules.