Legacy Saltstack

This is a COMPLETE public release of my personal salt states.

The states presented here were used for running this network.

This repository includes a lot of neat tricks I’ve learned and shows off many salt best practices (bad practices are bugs). It also includes some components that will be eventually be described on my personal blog [1], such as IDI (Inventory Defined Infrastructure) [2] and automated highstates [3].

At this point, the infrastructure described within has been retired and rebuilt from scratch from an absolute minimalist approach. The approach shared within works exceptionally well for small, medium, and multi-national corporations. It also worked well for my home lab/network and gave me the chance to explore some amazing concepts. Ultimately, as I hosted more and more open source projects (enabled by automation), my $hobby and $home[‘life’] devolved further and further into $work; I stopped having fun. … hence, the entire public export of the FINAL and COMPLETE incantation of my home network. It is complete, and it is retired. So long, and thanks for all the fish.

Cloud Nodes

Visualization of the pillar data generated by my salt setup.

salt-cloud:

  defaults:
    digitalocean:
      image: "8.7 x64"
      location: "New York 3"
      size: 512MB
    proxint:
      image: slowdisk:vztmpl/debian-8.0_Lustfield.tar.gz
      disk: "slow;8"
      type: lxc 
      host: prox1
      onboot: 1
      swap: 0
      cpu: 2
      mem: 256 

  digitalocean-nodes:

    oob-c1.domain.tld:
      id: oob-c1

    pbin.ngx.cc:
      id: pbin

    irc.domain.tld:
      id: irc 

    pubweb00.domain.tld:
      id: pubweb00

    pubweb02.domain.tld:
      id: pubweb02
      location: "Singapore 1"

  proxint-nodes:

    forge.domain.tld:
      id: "210;forge"
      type: qemu
      mem: 256-8192
      cpu: 16
      disk: "slow;64"
      iface:
        net0:
          name: eth0
          vlan: 2

    ## 
    # PROD
    ##

    boot.domain.tld:
      id: "101;boot"
      mem: 512 
      cpu: 1
      disk: "fast;8"
      iface:
        net0:
          name: eth0
          vlan: 57
          ipv4_addr: 10.41.57.10/24
          ipv4_gw: 10.41.57.1

    salt.domain.tld:
      id: "111;salt"
      mem: 2048
      cpu: 8
      disk: "crypt;16"
      iface:
        net0:
          name: eth0
          vlan: 50
          ipv4_addr: 10.41.50.11/24
          ipv4_gw: 10.41.50.1
          ipv6_addr: 2001:dead:b33f:50::11/64
          ipv6_gw: 2001:dead:b33f:50::1

    git.domain.tld:
      id: "112;git"
      mem: 2048
      cpu: 4
      disk: "crypt;32"
      iface:
        net0:
          name: eth0
          vlan: 50
          ipv4_addr: 10.41.50.12/24
          ipv4_gw: 10.41.50.1
          ipv6_addr: 2001:dead:b33f:50::12/64
          ipv6_gw: 2001:dead:b33f:50::1

    log.domain.tld:
      id: "113;log"
      mem: 512
      disk: "crypt;64"
      iface:
        net0:
          name: eth0
          vlan: 50
          ipv4_addr: 10.41.50.13/24
          ipv4_gw: 10.41.50.1
          ipv6_addr: 2001:dead:b33f:50::13/64
          ipv6_gw: 2001:dead:b33f:50::1

    apt.domain.tld:
      id: "114;apt"
      mem: 1024
      disk: "fast;16"
      iface:
        net0:
          name: eth0
          vlan: 50
          ipv4_addr: 10.41.50.14/24
          ipv4_gw: 10.41.50.1
          ipv6_addr: 2001:dead:b33f:50::14/64
          ipv6_gw: 2001:dead:b33f:50::1

    #[...]

Global Highstate

Server-side git hook that sends notifications to salt server:

root@prd-git-01:/var/lib/gitolite3/repositories/salt/states.git# cat hooks/post-receive.h00-salt-global-highstate 
#!/bin/sh
sudo /usr/local/sbin/saltevent 'global' 'highstate'

Expose a cache database to salt master:

localsdb:
  driver: sqlite3
  database: /var/cache/salt/local.db
  table: sdb_local
  create_table: True

Use salt reactor and sdb to orchestrate the delayed execution of a highstate when a git trigger is received:

{% if 'action' in data['data'] %}
{% set action  = data['data']['action'] %}{% endif %}

{% if 'host' in data['data'] %}
{% set host    = data['data']['host'] %}{% endif %}

{% if 'env' in data['data'] %}
{% set env     = data['data']['env'] %}{% endif %}

{% set tick    = salt['sdb.get']('sdb://localsdb/hst_tick') %}

{% set src     = data['id'] %}


##
# git.lustfield.net
##
{% if src == 'git.lustfield.net' and action == 'highstate' %}
update_fileserver:
  runner.fileserver.update:
    - tgt: salt.lustfield.net

  {% if 'env' in data['data'] %}
highstate_run:
  local.state.highstate:
    - tgt: "G@id:{{ host }}* and G@environment:{{ env }}"
    - expr_form: compound
    - require:
      - runner: update_fileserver

  {% elif host == 'global' %}
    {% if not tick or (tick > 10 or tick == 0) %}
{##
  # This is my magic highstate scheduling!
  # When a salt event comes in from the git server that requests
  # a highstate be pushed across the environment, check the
  # current tick value. If a global highstate was not recently
  # executed, then set the tick to 15.
  #
  # A separate process on the salt master is used to decrease
  # this tick value by one every minute and triggers a global
  # highstate runner when the value reaches zero.
  #
  # This results in a global highstate orchestration run being
  # scheduled with a five minute delay when certain git repositories
  # are pushed to. It also creates a delay before the next global
  # higshtate can be triggered, meaning time for this process to
  # complete.
  #
  # Message me if this deserves a separate blog post!
  ##}
highstate_countdown:
  local.sdb.set:
    - tgt: salt.lustfield.net
    - arg:
      - sdb://local/hst_tick
      - '15'
    {% endif %}

  {% else %}
highstate_run:
  local.state.highstate:
    - tgt: "{{ host }}*"
    - require:
      - runner: update_fileserver

{% endif %}

Script that handles tick and highstate execution logic:

#!/usr/bin/env python
'''
Author:      Michael Lustfield (MTecknology)
License:     CC BY-NC-ND 4.0
             https://creativecommons.org/licenses/by-nc-nd/4.0/
Description: This script is meant to be triggered via the salt scheduler. It
             reads a counter stored in sdb and drops the value by one. If the
             counter reaches a specified value, a global highstate is triggered
             via the orchestrate runner. Setting the value and the conditions
             in which it's set are handled by separate logic.
'''
import salt.config
import subprocess
import time
from salt.utils.sdb import sdb_get, sdb_set

opts = salt.config.minion_config('/etc/salt/minion')
tick = int(sdb_get('sdb://localsdb/hst_tick', opts))

if tick:
    if tick > 0:
        sdb_set('sdb://localsdb/hst_tick', tick - 1, opts)
    if tick == 10:
        p = subprocess.Popen(
                ['/usr/bin/salt-run', 'state.orch', '_orchestrate.global_highstate'],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        if stdout or stderr:
            with open('/var/log/salt/global_highstate.log', 'a') as fh:
                fh.write('{}\n'.format(time.strftime("%c")))
                if stdout:
                    fh.write('{}\n'.format(stdout))
                if stderr:
                    fh.write('{}\n'.format(stderr))
                fh.close()