Thursday, May 4, 2017

Puppet Rake serverspec testing

Using Rake to ServerSpec test

If you don't like kitchen, or your team is using Rake you may choose to use the rake spec command for serverspec testing.  The directory layout is simpler than Kitchen, but requires more configuration as you need to create the following files;
  • Rakefile
  • spec_helper.rb
  • Vagrantfile
  • Provisioning script for the Vagrant VMs
These files need further configuration to make serverspec work using Rake.  The Rakefile and the spec_helper.rb files will live within the test directory, but spec_helper.rb will live in a spec subdirectory.
You'll notice the key difference compared to RSpec is the localhost directory. Each host will need it's spec test included. For example;
 Vagrantfile
 test
 ├── coretests
 │   ├── nginx_spec.rb
 │   └── dns_spec.rb
 ├── Rakefile
 └── spec
     └── web
     │   ├── nginx_spec.rb -> ../../coretests/nginx_spec.rb
     └── webserver1.al.local
     │   └── nginx_spec.rb -> ../../coretests/nginx_spec.rb 
     └── spec_helper.rb
      
Example diagramatic view of a project and where the directory structure fits;

Above we now have an SSH host for web and weberver1.al.local. The difference between the localhost and the SSH version is in the spec_helper.rb file, so be sure to check your configurations.
In the above we symlink each host with the required tests in the coretests folder.

Rakefile

Here is an example Rake file to perform build, config and run;
require 'rake'
require 'rspec/core/rake_task'
 
hosts = %w(
  web
)
 
task :spec => 'spec:all'
 
namespace :spec do
  task :all => hosts.map {|h| 'spec:' + h.split('.')[0] }
  hosts.each do |host|
    short_name = host.split('.')[0]
    role       = short_name.match(/.+/)[0]
 
    desc "Run serverspec to #{host}"
    RSpec::Core::RakeTask.new(short_name) do |t|
      ENV['TARGET_HOST'] = host
      t.pattern = "spec/{base,#{role}}/*_spec.rb"
      t.verbose = true
    end
  end
end
The important elements about the above file are;
  • hosts = %w (
    • Each line in this list will be a host in your Vagrantfile
    • The name specified here will be the same as the name of the define :web for example, which is the beginning of the definition of the VM
  • task :spec => 'spec:all'
    • This line defines what tasks to run within the file.  This file will run the all task.
  • task :all
    • This is the main task that tells rake to use the hosts variable to loop through
    • The hosts.each starts the real work
    • If you have more than one host you will need to add them to the hosts array
  • t.pattern
    • This line defines the name of the directory that will be used to target the tests to run for the current host in the loop

spec/spec_helper.rb

This file defines how the VM will be created and the connection to it, as well as other environmental information to be used within the VM.
Example;
require 'serverspec'
require 'net/ssh'
require 'tempfile'
 
set :backend, :ssh
 
if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end
 
host = ENV['TARGET_HOST']
`vagrant up #{host}`
 
config = Tempfile.new('', Dir.tmpdir)
config.write(`vagrant ssh-config #{host}`)
config.close
 
options = Net::SSH::Config.for(host, [config.path])
 
options[:user] ||= Etc.getlogin
 
set :host,        options[:host_name] || host
set :ssh_options, options
 
# Disable sudo
# set :disable_sudo, true
 
# Set environment variables
# set :env, :LANG => 'C', :LC_MESSAGES => 'C'
 
# Set PATH
# set :path, '/sbin:/usr/local/sbin:$PATH'
#

Vagrantfile

This will live above the test directory.  For our example so far we would need a VM with the name of web;
Vagrant.configure("2"do |config|
  config.vm.define :web do | web |
    web.vm.provider "virtualbox" do | vb |
        vb.memory = 1024
        vb.cpus = 1
        vb.name = "WebNode"
    end
    web.vm.hostname = "web.al.local"
    web.vm.network "private_network", ip: "192.168.100.23"
    web.vm.provision "shell", path: "bin/web.sh"
    web.vm.box = "bento/centos-7.2"
  end
end
The define :web matches the web in the Rakefile.  The bin/web.sh will call the necessary commands to provision the VM, prior to serverspec running.

Running the test

To run a serverspec test using rake simply run the following;
rake spec
Unlike kitchen, rake does not show the build and configuration of the VM, so you will be waiting some time until it gets to the test phase, which means the output is cleaner, since it is only the tests.

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.