Saturday, December 6, 2014

Masterless SaltStack Provisioning to VirtualBox with Vagrant

So we've gone over a good number of steps in our series of sysadmin articles, many of them manual. This article begins to cover a more automated approach to provisioning servers, tasking sysadmins to focus more on configuration files and scripts. You may already be familiar with Chef, Puppet, and Ansible. For this article, we'll be implementing our solution using SaltStack. Make sure to check back, in the future, for a comparison of all four solutions.

Saltstack

SaltStack describes their solution as a "fast, scalable and flexible systems management software for data center automation, cloud orchestration, server provisioning, configuration management and more." It has rapidly been gaining traction over other solutions, especially in the world of cloud deployments for its speed and scalability. SaltStack or Salt also makes it fairly easy to set up, which we'll demonstrate below. This article will focus on setting up a masterless Salt minion using a VagrantFile and Salt configuration files. As you'll see from the Vagrant Docs, Vagrant supports a variety of different provisioning solutions, including Salt, so integration between the two will be very simple. The reason we're going masterless is because we're provisioning a server using Salt for testing, and don't really need a master to coordinate a cluster of minions, but will only be provisioning one minion. In the future, I'll provide an example with a master-minion configuration.

Set Up

The VagrantFile

Prerequisites: You'll want to be familiar with setting up VirtualBox with Vagrant. It also helps to understand how folders are shared between a Mac OS X host and a VirtualBox Ubuntu guest, but it's not absolutely necessary since Vagrant will automate a lot of these steps for us. You should also stop by the excellent SaltStack documentation. At the writing of this article, we're at v2014.7.0. Lastly, this guide was written for Mac OS X users.

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

Alright, so now that you're a bit more familiar with how to set up Linux instances with Vagrant, let's go over the example VagrantFile we'll be using for this project. Don't worry if you don't understand every line the first time through. I'll explain everything after the jump.

# -*- 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|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "ubuntu/trusty64"

  # If true, then any SSH connections made will enable agent forwarding.
  # Default value: false
  config.ssh.forward_agent = true

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder "../../vbnfs", "/vbnfs"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  config.vm.provider "virtualbox" do |vb|
    # Don't boot with headless mode
    # vb.gui = true

    # Use VBoxManage to customize the VM. For example to change memory:
    vb.customize ["modifyvm", :id, "--memory", "1024"]
  end

  # Masterless Salt configuration
  config.vm.synced_folder "salt/roots", "/srv/salt"

  # Use all the defaults:
  config.vm.provision :salt do |salt|
    salt.minion_config = "salt/minion.conf"
    salt.run_highstate = true
  end
  #
  # View the documentation for the provider you're using for more
  # information on available options.
end

Since you've already been exposed to some of these configurations in the introductory tutorial about VirtualBox and Vagrant, we'll skip right to the new items.

config.ssh.forward_agent = true

This line allows you to leverage your Mac OS X host's SSH key as the VM guest in order to, for example, clone github repositories your hosts's public key has been uploaded to.

config.vm.synced_folder "../../vbnfs", "/vbnfs"

This maps a shared folder located relative to this VagrantFile on the host machine to a folder located at the root /vbnfs location. In case you're curious, "vbnfs" stands for Virtual Box Network File System, but you're free to name this folder anything you like.

Note: Make sure you have a vbnfs folder two levels up from the root, where your VagrantFile is located. Otherwise, place it anywhere you like and update the path in your VagrantFile.

  config.vm.provider "virtualbox" do |vb|
    # Don't boot with headless mode
    # vb.gui = true

    # Use VBoxManage to customize the VM. For example to change memory:
    vb.customize ["modifyvm", :id, "--memory", "1024"]
  end

Instructs Virtualbox to allocate 1024 MB (1GB) of memory to your instance.

  # Masterless salt configuration
  config.vm.synced_folder "salt/roots", "/srv/salt"

  # Use all the defaults:
  config.vm.provision :salt do |salt|
    salt.minion_config = "salt/minion.conf"
    salt.run_highstate = true
  end

These lines tell VirtualBox to sync some more folders up for Salt. These folders are relative to the VagrantFile and allow you to quickly update your configuration on your host machine prior to running any Salt calls on the host VM. Also points Salt to your minion configuration file and instructs it to run highstate, immediately. Now let's examine our Salt source files:

Salt Source Files

`~masterless-salt-vagrant/
  |~salt/
  | |~configs/
  | | `-minion.conf
  | `~roots/
  | | |~pillars/
  | | | |-top.sls
  | | | `-users.sls
  | | `~states/
  | |   |~base/
  | |   | `-sanity.sls
  | |   |~git/
  | |   | `-init.sls
  | |   |~nginx/
  | |   | `-init.sls
  | |   |~pkg/
  | |   | `-git.sls
  | |   |~users/
  | |   | `-init.sls
  | |   |~wheel/
  | |   | `-init.sls
  | |   `-top.sls
  | `-minion.conf
  `-Vagrantfile

You'll notice the VagrantFile at the top most level, whereas all of the Salt files are contained in the salt directory. The roots folder is what we instructed VirtualBox to map to /srv/salt on our host machine. Within that, you have a top.sls file, a pillars and a states folder, which we'll go over in detail, later in the article.

The Minion Configuration

master: localhost
file_client: local

file_roots:
  base:
    - /srv/salt/states

pillar_roots:
  base:
    - /srv/salt/pillars

In our minion configuration file, we first indicate the hostname of our server, since there is no master. We then instruct Salt to look on the minion as the file_client. Next, we give Salt a path to the minions states and pillar files. Head over to this guide to get a better understanding of everything you can do with the minion configuration file.

Top.sls

The top file tells Salt which modules to load into which minion through the state systems. Read more about the top file here.

base:
  '*':
    - base.sanity
    - nginx
    - git
    - wheel
    - users

Our top file indicates that we want to load base.sanity and then a series of other states. The base.sanity file is a set of programs I prefer to have installed into every Ubuntu instance, regardless of what type of minion I configure. The remaining states indicated here, as what I would like configured specificaly for this machine. In this case, we are setting up an nginx server with git and a user who will fall under the wheel group.

States

States are the representation of the state in which a system should be in. You can also think of a state as configuration management data. Read more about states here

`~masterless/
  |~salt/
  | |~configs/
  | | `-minion.conf
  | `~roots/
  | | |+pillars/
  | | `~states/
  | |   |~base/
  | |   | `-sanity.sls
  | |   |~git/
  | |   | `-init.sls
  | |   |~nginx/
  | |   | `-init.sls
  | |   |~pkg/
  | |   | `-git.sls
  | |   |~users/
  | |   | `-init.sls
  | |   |~wheel/
  | |   | `-init.sls
  | |   `-top.sls
  | `-minion
  `-Vagrantfile

We'll be reviewing two different state files, today. The first is our base.sanity state file and is fairly straightforward. You just indicate a top level key, and use the following conventions to tell salt which packages to install.

foundation:
  pkg.installed:
    - pkgs:
      - vim
      - tmux
      - htop
      - bash-completion

Next, we'll look at our users state file, which uses jinja syntax to iterate through the users pillar and generate a file using the standard format. Here, we also add a conditional to tell salt which users to create and which to destroy, associating ssh keys accordingly.

{% for name, user in pillar.get('users', {}).items() %}
{{name}}:
  user.{{user.state}}:
  {% if user.state == 'present' %}
    - fullname: {{user.fullname}}
    - shell: {{user.shell}}
    - home: {{user.home}}
    - uid: {{user.uid}}
    - groups: {{user.groups}}
ssh_key_{{name}}:
  ssh_auth:
    - present
    - user: {{name}}
    - names: 
      - {{user.pubkey}}
    - require:
      - user: {{ name }}
  {% elif user.state == 'absent' %}
    - purge: {{user.purge}}
  {% endif %}
{% endfor %}

Pillars

Think of pillars as tree-like structures that allow you to abstract sensitive data that are relevant to the minion.

`~masterless-salt-vagrant/
  |~salt/
  | |+configs/
  | `~roots/
  | | |~pillars/
  | | | |-top.sls
  | | | `-users.sls
  | | `+states/
  | `-minion
  `-Vagrantfile

Here is our users pillar file, where we indicate that we want to create a "rob" user and delete the ubuntu user that comes with our VirtualBox instance. Feel free to change the username to whatever you like. We've also allocated this user with a uid, indicated where his home directory should be located and which group he should belong to. You'll need to paste the pub key contents of your ~/.ssh/id_rsa.pub file as the pubkey value if you want to be able to ssh into your instance as this user. If you aren't familiar with generating SSH keys, make sure to follow this guide.

users:
  rob:
    state: present
    fullname: Rob Layton
    shell: /bin/bash
    home: /home/rob
    uid: 4000 
    groups:
      - wheel
    pubkey: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs6BbzN9mFcxxwXXeVP9ea/o8mwb6B+vPisemj/U20NNXncJ4IMYz7FB/IeCjwVHAI5ZwFUpsRELasi7vXY8z5fOgaH/GwnOFwklC8oYrlhSejoMLMDYZt67yb9wky1QO4JM9rqoKUJtY9p3C0QxHJn7HgI6yp8ZrF0W1B/Zfqgl6oX/XcsaGCio+X6CAn2Ae2nAexUgEPKcV2tcCVjpOlK/9NANvJey32zLC7ysgx6VNijWkbcvTq1Ptdv28YQxpoyptjo/aABilyTxnQBYXWu+hFCaF6CInLtkbKIhMVcZ2Qk4Kx8f0zByLDev8OE3eAccgNEYHSGBpiEroAWzax roblayton@Roberts-MacBook-Pro.local
  ubuntu:
    state: absent
    purge: True

Running Vagrant

Now, all you have to do is navigate to the root folder, where your VagrantFile sits, and run vagrant up. If everything went according to plan, you should receive the following output:

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
==> default: Setting the name of the VM: masterless_default_1417806377059_81932
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 80 => 8080 (adapter 1)
    default: 22 => 2223 (adapter 1)
    default: 22 => 2222 (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vbnfs => /Users/roblayton/vbnfs
    default: /vagrant => /Users/roblayton/vagrants/masterless
    default: /srv/salt => /Users/roblayton/vagrants/masterless/salt/roots
==> default: Running provisioner: salt...
Copying salt minion config to vm.
Checking if salt-minion is installed
salt-minion was not found.
Checking if salt-call is installed
salt-call was not found.
Bootstrapping Salt... (this may take a while)
Salt successfully configured and installed!
run_overstate set to false. Not running state.overstate.
Calling state.highstate... (this may take a while)

Now you can ssh into the machine with the terminal command, ssh user@localhost -p2222.

Note: Make sure you remembered to paste in your own id_rsa.pub contents into the salt/roots/pillars/users.sls file! Otherwise, you'll have to log in with ssh vagrant@localhost -p2222 and have to enter the password, "vagrant".

Any changes you make to your pillars or states will be reflected immediately in your host instance because the folders are shared. After making your changes, remember to run salt-call --local state.highstate on the host VM.

That's all we'll cover for now. In future articles, we'll go over more in depth salt topics like masters, grains, and the reactor system, so check back soon!

3 comments:

  1. Thanks a lot it's work ! Now i go straightforward on your master-minion with vagrant article, the next step for me will be to replace machines by containers with docker.

    ReplyDelete
  2. Glad that worked out. Thank you for the feedback, Sébastien. Docker's amazing. I've been meaning to write some guides on it. It helps to have a good understanding of the manual steps, first, and then put them all into a dockerfile. Would love to hear about your experience with it.

    Also, here's a Dockerfile I've made for elasticsearch: https://github.com/roblayton/docker-elasticsearch.

    ReplyDelete
    Replies
    1. Docker is completly new for me, i'll experiment first with the help of your tutorial and then i will try with container, it is'nt seems a hard gap cause Vagrant have a tool for that. I'll come back soon with the result (maybe a fork of your master-minion git code).

      Delete