Monday, December 8, 2014

Master-Minion SaltStack Provisioning to VirtualBox Using Vagrant

This is a follow up to my article about masterless SaltStack provisioning with vagrant. This extends beyond that, showing you how to set up a 1-master, 2-minion cluster, and outside of pre-seeding keys and grains, we won't be going over too much else. Just a straightforward guide to getting your virtual machines set up.

Prerequisites: You'll want to be familiar with a masterless setup prior to attempting any of the steps in this guide. These steps were written for Mac OS X users.

Source Files: Make sure to clone the source files on my Github page.

Creating a VirtualBox Host-Only Network

We need to make sure we create a host-only network so our Virtual Machines can communicate, internally. Run the following command in your terminal:

VBoxManage hostonlyif create ipconfig vboxnet0 --ip 192.168.56.1 --netmask 255.255.255.0
# 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
# Interface 'vboxnet0' was successfully created

VagrantFile

After cloning the source files, you'll notice that the VagrantFile is a bit more involved this time. We have designations for our master, and 2 minions. This repository provides keys for all of the instances, but you'll want to learn how to set up your own keys. We'll go over those steps, later in the article.

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.ssh.forward_agent = true
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "1024"]
  end

  # Master salt configuration
  config.vm.define "master" do |master|
    master.vm.box = "ubuntu/trusty64"
    master.vm.host_name = "master"
    master.vm.synced_folder "../../vbnfs", "/vbnfs"
    master.vm.synced_folder "salt/roots", "/srv/salt"
    master.vm.network :private_network, ip: "available.ip.address.0"    
    master.vm.network "public_network", :bridge => 'en0: Wi-Fi (AirPort)'
    master.vm.provision :salt do |salt|
      salt.install_master = true

      salt.master_config = 'salt/configs/master.conf'
      salt.seed_master = {
        master: 'salt/keys/master.pub',
        minion1: 'salt/keys/minion1.pub',
        minion2: 'salt/keys/minion2.pub'
      }

      salt.install_type = :stable
    end
  end

  # Minion1 salt configuration
  config.vm.define "minion1" do |minion|    
    minion.vm.box = "ubuntu/trusty64"
    minion.vm.host_name = "minion1"
    minion.vm.synced_folder "../../vbnfs", "/vbnfs"
    minion.vm.synced_folder "salt/roots", "/srv/salt"
    minion.vm.network :private_network, ip: "available.ip.address.1"
    minion.vm.network "public_network", :bridge => 'en0: Wi-Fi (AirPort)'
    minion.vm.provision :salt do |salt|
      salt.run_highstate = true
      salt.minion_config = "salt/configs/minion1.conf"
      salt.minion_key = "salt/keys/minion1.pem"
      salt.minion_pub = "salt/keys/minion1.pub"
    end 
  end  

  # Minion2 salt configuration
  config.vm.define "minion2" do |minion|    
    minion.vm.box = "ubuntu/trusty64"
    minion.vm.host_name = "minion2"
    minion.vm.synced_folder "../../vbnfs", "/vbnfs"
    minion.vm.synced_folder "salt/roots", "/srv/salt"
    minion.vm.network :private_network, ip: "available.ip.address.2"
    minion.vm.network "public_network", :bridge => 'en0: Wi-Fi (AirPort)'
    minion.vm.provision :salt do |salt|
      salt.run_highstate = true
      salt.minion_config = "salt/configs/minion2.conf"
      salt.minion_key = "salt/keys/minion2.pem"
      salt.minion_pub = "salt/keys/minion2.pub"
    end 
  end  
end

You'll also notice the IP allocations for all of your VMs.

Note: Make sure you point to the master's IP address in your salt/configs/minion1.conf and salt/configs/minion2.conf files.

# At the top of the salt/configs/minion1.conf file

master: available.ip.address.0
id: "minion1"
file_client: remote
# Top of the salt/configs/minion2.conf file

master: available.ip.address.0
id: "minion2"
file_client: remote

Vagrant Up

You're free to run vagrant up, which will create all three VMs or one at a time:

# spin up the master
vagrant up master

# spin up minion1
vagrant up minion1

# spin up minion2
vagrant up minion2

# or spin up all three
vagrant up

Now, accessing minion2 in your terminal is easy as running ssh vagrant@available.ip.address.2.

Pre-seeding Your Minion Keys

If you didn't already have keys, the easiest way to generate your ssh keys would be to set up any VM with the salt-key module and generate the keys, yourself, one by one. Then, you'd copy the keys over. To make things easy on ourselves, just follow these steps:

# from a terminal window on your Mac OS X Host
# ssh into the master
ssh vagrant@available.ip.address.1 # password is "vagrant"

# navigate to the /vbnfs directory
cd /vbnfs

# generate the master key
sudo salt-key --gen-keys=master

# generate the minion1 key
sudo salt-key --gen-keys=minion1

# generate the minion2 key
sudo salt-key --gen-keys=minion2

Now just navigate to your host machine's shared vbnfs folder and drag the generated keys into master-minion-salt-vagrant/salt/keys.

Note: we'd take a different approach if we were actually provisioning production servers, but this will do just fine for now.

Grains

If you take a look at salt/configs/minion1.conf and salt/configs/minion2.conf, now you'll notice some new lines that pertain to the grains specification.

# salt/configs/minion1.conf
grains:
  roles:
    - webserver

# salt/configs/minion1.conf
grains:
  roles:
    - repo

All we're doing in each minion's configuration file is specifying the grain, or category for that minion. By doing so, we're able to provision each minion with a different set of states and target them through the command line interface.

Now if you look at our top file located at salt/roots/states/top.sls, you'll see that we've designated states for each grain.

base:
  '*':
    - base.sanity
    - wheel
    - users
  'role:webserver':
    - match: grain
    - nginx
  'role:repo':
    - match: grain
    - git

For more information about grains, see the SaltStack documentation

That concludes this article. Hopefully, you have a better understanding of SaltStack for having gone through it. In the future, we'll expand upon this topic by diving into setting up SaltStack in a production environment. Until next time.

No comments:

Post a Comment