Classifying hosts vs. targeting them

4,224 views
Skip to first unread message

martin f krafft

unread,
Oct 15, 2012, 1:51:00 AM10/15/12
to salt users list
Hey folks,

can someone please help me understand the design motivation behind
the way that salt states target hosts?

Background of my question is that I come from 15 years of cfengine
and puppet, and my brain is thus almost hardwired to the paradigm of
defining hosts and giving them a number of roles or classes. If
a new host is born, I am used to defining it on the master and
assigning roles and setting parameters *on the master* to configure
it.

With salt, however, it seems like this is exactly the other way
around. Here, if a new web server is born, I either have to modify
some conditional in a state file to make sure that the webserver
state gets applied to the new hostname, or I have to somehow make
sure that salt can identify the new host as a web server (e.g. using
a custom grain).

This is completely backwards to me. I don't want to insinuate that
one approach is better than the other, though. On the contrary, I am
here because I am interested in salt and trying to wrap my head
around it while evaluating it for the various use cases where I am
looking to finally replace cfengine and puppet.

Can you try to help me understand the paradigm used by salt? Surely
you are not going to advocate modifying the webserver state file
whenever a new web server appears, "hardcoding" hostnames in there.
But given that you only have a single state hierarchy, I would
appreciate any help in understanding how to best design it.


Let me give you an example of where I come from, in the hope that
this might allow you to "pick me up". Please consider the following
two hosts:

** hostA ** ** hostB **
roles: roles:
- webserver - muninserver
- ntpclient - ntpclient
- debian-squeeze - debian-wheezy
- hosted-in-munich - hosted-in-zurich

When hostA comes to the master, its configuration is assembled from
the specified roles: it gets everything a webserver gets, it's
configured to run Debian squeeze, and Munich Debian and NTP servers
are configured.

hostB, on the other hand, is set up as munin server, running Debian
wheezy in Zurich.

Essentially, a role defines states and parameters. While "webserver"
will cause apache2 to be installed, hosted-in-zurich overrides
the default parameters in ntpclient and debian-* with more
appropriate values.


I was told yesterday on IRC that the above host definitions could be
trivially turned into custom grains by dropping the appropriate
files into e.g. /etc/salt/minion.d/roles. However, this seems like
a catch-22 to me because I do not really want to control the
configuration of a minion using a file on the minion.

Is there maybe a way to do what I want with salt already? I'd rather
not write all kinds of parsers and external scripts to meld my
configuration into shape though…

Thanks for your time,

--
martin | http://madduck.net/ | http://two.sentenc.es/

"health? what good is your health when you're otherwise an idiot?"
-- theodor w. adorno

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Joseph Hall

unread,
Oct 15, 2012, 8:38:30 AM10/15/12
to salt-...@googlegroups.com
Hi Martin,

Grains are a very powerful feature, but by no means the only method
for targeting a minion. If your servers have a naming scheme that
focuses on their functionality (web1, web2, db1, db2, etc) instead of
an unrelated theme (fozzy, kermit, rizzo, drteeth), then you can
target based on id. By default the minion's id is its hostname, but
you can also set it manually in /etc/salt/minion. Then in top.sls on
the master, you can refer to it by hostname:


base:
'*':
- core
- vim
- ntp
web:
'web*':
- apache
- flask
db:
'db*':
- mysql


Do you have some other means of identifying a server that is not obvious?

--
"In order to create, you have to have the willingness, the desire to
be challenged, to be learning." -- Ferran Adria (speaking at Harvard,
2011)

martin f krafft

unread,
Oct 15, 2012, 9:09:31 AM10/15/12
to Joseph Hall, salt-...@googlegroups.com
also sprach Joseph Hall <perl...@gmail.com> [2012.10.15.1438 +0200]:
> Do you have some other means of identifying a server that is not
> obvious?

No. All servers are traditionally named according to a host naming
theme (volcanoes, birds, Pink Floyd songs, alcoholic beverages,
etc.) and their function cannot be deduced from their names.
I cannot even think of a grain, installed or custom, that could be
used. In short: I don't see a way to target them than by explicitly
writing their hostnames into the state config. And that I would like
to avoid, for obvious reasons.
i don't want to get myself into a hot babe situation.
-- jonathan mcdowll, #debian-uk, 6 jul 2009

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Joseph Hall

unread,
Oct 15, 2012, 9:19:25 AM10/15/12
to Joseph Hall, salt-...@googlegroups.com
I don't have cfengine experience, but I was once a Puppet user like
yourself. Do you have a method in Puppet for doing this that you like?
When I started making the transition from Puppet to Salt last year
(before a lot of Salt's current functionality existed), I found
similarities between the two that helped considerably. Performing the
translation (or perhaps just transliteration) from Puppet to Salt
might be a good exercise, that can also help your understanding in
your research.

David Boucha

unread,
Oct 15, 2012, 11:03:50 AM10/15/12
to salt-...@googlegroups.com
Martin,

There are a lot of ways to target minions. Have you tried looking at node groups? 

You can add a new minion's hostname to the nodegroup and then run a state.hightstate.
Add the following to /etc/salt/master:

nodegroups:
  group1: 'kona, africangray, darksideofthemoon, kirsch'

You can also compose the nodegroups from grains, as well.
Then you can refer to the nodegroups in your state files.

Also, Salt has support for external node classifiers, similar to puppet :

One last thing to consider. If you are using a cloud provider such as Amazon EC2 or Rackspace, or others, you can use salt-cloud to create those servers. salt-cloud can pre-populate grains in the minion config when the server is created. So you could pre-populate the following:

grains:
  roles:
    - webserver
    - dbserver

Does this help?

Dave Boucha

martin f krafft

unread,
Oct 15, 2012, 3:20:18 PM10/15/12
to Joseph Hall, salt-...@googlegroups.com
also sprach Joseph Hall <perl...@gmail.com> [2012.10.15.1519 +0200]:
> I don't have cfengine experience, but I was once a Puppet user
> like yourself. Do you have a method in Puppet for doing this that
> you like?

http://git.madduck.net/v/puppet/reclass.git/

It's an "external node classifier" that you configure like this:

master/puppet.conf:
[main]

external_nodes=$confdir/reclass/node_classifier --storage-type=yaml_fs --node-uri=$confdir/nodes --role-uri=$confdir/roles
"if I can't dance, i don't want to be part of your revolution."
- emma goldman

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

martin f krafft

unread,
Oct 15, 2012, 3:25:42 PM10/15/12
to David Boucha, salt-...@googlegroups.com
also sprach David Boucha <da...@saltstack.com> [2012.10.15.1703 +0200]:
> There are a lot of ways to target minions. Have you tried looking
> at node groups?
> https://salt.readthedocs.org/en/latest/topics/targeting/nodegroups.html?highlight=node

No, I have missed this part of the docs somehow, thanks. This looks
better than what I thought was possible, but still requires you to
keep information about a node decentrally.

How do I query the config (as a human) for roles a node has? grep -C10?

> nodegroups:
> group1: 'kona, africangray, darksideofthemoon, kirsch'

WHAT IS YOUR NAMING SCHEME?

> You can also compose the nodegroups from grains, as well.
> Then you can refer to the nodegroups in your state files.

Yeah, but at the moment there are no grains that describe the hosts'
roles, unless I add custom grains, but that's configuring hosts
locally; I am doing configuration management because I don't want
that. ;)

> Also, Salt has support for external node classifiers, similar to puppet :
> https://salt.readthedocs.org/en/latest/ref/configuration/master.html?highlight=enc#external-nodes

Does anyone have an example of this?

I would prefer not to have to "customise" core parts of the tool
before even using it, but if this is a robust way forward, it might
just well do!

> One last thing to consider. If you are using a cloud provider such as
> Amazon EC2 or Rackspace, or others, you can use salt-cloud to create those
> servers. salt-cloud can pre-populate grains in the minion config when the
> server is created. So you could pre-populate the following:
>
> grains:
> roles:
> - webserver
> - dbserver

I do not use third-party providers. We don't trust anyone but
ourselves. ;)
"there are two major products that come out of berkeley: lsd and unix.
we don't believe this to be a coincidence."
-- jeremy s. anderson

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

David Boucha

unread,
Oct 15, 2012, 3:45:12 PM10/15/12
to David Boucha, salt-...@googlegroups.com
On Mon, Oct 15, 2012 at 1:25 PM, martin f krafft <mad...@madduck.net> wrote:
also sprach David Boucha <da...@saltstack.com> [2012.10.15.1703 +0200]:
> There are a lot of ways to target minions. Have you tried looking
> at node groups?
> https://salt.readthedocs.org/en/latest/topics/targeting/nodegroups.html?highlight=node

No, I have missed this part of the docs somehow, thanks. This looks
better than what I thought was possible, but still requires you to
keep information about a node decentrally.


You create these nodegroups in your master config. Isn't that keeping the information
centrally enough?  I may not be understanding your use case completely.
 
How do I query the config (as a human) for roles a node has? grep -C10?

salt -N group1 test.ping

The above line will give you a list of minions in group1. You can also look in the nodegroup
definition in your master config.
 

> nodegroups:
>   group1: 'kona, africangray, darksideofthemoon, kirsch'

WHAT IS YOUR NAMING SCHEME?

Ha, I was trying to be funny.  :)
 

> You can also compose the nodegroups from grains, as well.
> Then you can refer to the nodegroups in your state files.

Yeah, but at the moment there are no grains that describe the hosts'
roles, unless I add custom grains, but that's configuring hosts
locally; I am doing configuration management because I don't want
that. ;) 

> Also, Salt has support for external node classifiers, similar to puppet :
> https://salt.readthedocs.org/en/latest/ref/configuration/master.html?highlight=enc#external-nodes

Does anyone have an example of this?

I would prefer not to have to "customise" core parts of the tool
before even using it, but if this is a robust way forward, it might
just well do!

> One last thing to consider. If you are using a cloud provider such as
> Amazon EC2 or Rackspace, or others, you can use salt-cloud to create those
> servers. salt-cloud can pre-populate grains in the minion config when the
> server is created. So you could pre-populate the following:
>
> grains:
>   roles:
>     - webserver
>     - dbserver

I do not use third-party providers. We don't trust anyone but
ourselves. ;)

Gotcha!

martin f krafft

unread,
Oct 16, 2012, 1:34:56 AM10/16/12
to David Boucha, salt-...@googlegroups.com
also sprach David Boucha <da...@saltstack.com> [2012.10.15.2145 +0200]:
> > How do I query the config (as a human) for roles a node has? grep -C10?
>
> salt -N group1 test.ping
>
> The above line will give you a list of minions in group1. You can
> also look in the nodegroup definition in your master config.

Say I want to find out what roles (group memberships) host
pepper.example.org has — I cannot look at the host definition, but
I have to search for all occurrences of the hostname in nodegroup
definitions.

On the other hand, nodegroups allow me to quickly get a feel of my
webservers etc.

The question is really just whether it makes more sense to have
a node-centric definition (which is what I prefer for now), or
a role-centric one.

I am correct in assuming that a node can be a member of multiple
groups, right?
military intelligence is a contradiction in terms.
-- groucho marx

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Les Mikesell

unread,
Oct 16, 2012, 10:51:40 AM10/16/12
to salt-...@googlegroups.com, David Boucha
On Tue, Oct 16, 2012 at 12:34 AM, martin f krafft <mad...@madduck.net> wrote:
> >
> The question is really just whether it makes more sense to have
> a node-centric definition (which is what I prefer for now), or
> a role-centric one.

If you don't have groups of similar machines, is it really worth the
trouble to automate the configuration?

--
Les Mikesell
lesmi...@gmail.com

David Boucha

unread,
Oct 16, 2012, 11:21:29 AM10/16/12
to salt-...@googlegroups.com, David Boucha
On Mon, Oct 15, 2012 at 11:34 PM, martin f krafft <mad...@madduck.net> wrote:
also sprach David Boucha <da...@saltstack.com> [2012.10.15.2145 +0200]:
> > How do I query the config (as a human) for roles a node has? grep -C10?
>
> salt -N group1 test.ping
>
> The above line will give you a list of minions in group1. You can
> also look in the nodegroup definition in your master config.

Say I want to find out what roles (group memberships) host
pepper.example.org has — I cannot look at the host definition, but
I have to search for all occurrences of the hostname in nodegroup
definitions.

There isn't a good way to determine which or how many nodegroups a
minion is in yet.
 

On the other hand, nodegroups allow me to quickly get a feel of my
webservers etc.

The question is really just whether it makes more sense to have
a node-centric definition (which is what I prefer for now), or
a role-centric one.

Salt is very flexible. I'm sure you'll be able to find a way to make it work as you'd like.
Since you're creating  each of these servers yourself, is it too much to
add a custom grain to the minion config?
 

I am correct in assuming that a node can be a member of multiple
groups, right?

Yes, this is correct.

Jason Godden

unread,
Oct 16, 2012, 12:35:33 PM10/16/12
to mad...@madduck.net, salt-...@googlegroups.com
On 16/10/12 16:34, martin f krafft wrote:
> also sprach David Boucha <da...@saltstack.com> [2012.10.15.2145 +0200]:
>>> How do I query the config (as a human) for roles a node has? grep -C10?
>> salt -N group1 test.ping
>>
>> The above line will give you a list of minions in group1. You can
>> also look in the nodegroup definition in your master config.
> Say I want to find out what roles (group memberships) host
> pepper.example.org has — I cannot look at the host definition, but
> I have to search for all occurrences of the hostname in nodegroup
> definitions.
>
> On the other hand, nodegroups allow me to quickly get a feel of my
> webservers etc.
>
> The question is really just whether it makes more sense to have
> a node-centric definition (which is what I prefer for now), or
> a role-centric one.
>
> I am correct in assuming that a node can be a member of multiple
> groups, right?
>

Jinja templating in pillar perhaps?

root@master:/srv/salt/pillar# cat top.sls
base:
'*':
- roles
root@master:/srv/salt/pillar# cat roles/roles.sls
{% set roles = {
'zabbix': [ 'pgsql.localmonkey', 'master.localmonkey' ],
'pgsql_client': [ 'pgsql.localmonkey' ]
}
%}

root@master:/srv/salt/pillar# cat roles/init.sls
{% from "roles.sls" import roles %}

role:
{% for role, items in roles.items() %}
{% if grains['id'] in items %}
- {{ role }}
{% endif %}
{% endfor %}

root@pgsql:~$ salt-call pillar.data
[INFO ] Loaded configuration file: /etc/salt/minion
{'local': {'role': ['pgsql_client', 'zabbix']}}

root@master:/srv/salt/pillar# cat ../base/top.sls
base:
'I:roles.pgsql_client':
- postgresql.client.9-2
'I:roles.zabbix':
- zabbix

root@master:/srv/salt/pillar# salt -I 'role:pgsql_client' test.ping
pgsql.localmonkey: True
root@master:/srv/salt/pillar# salt -I 'role:zabbix' test.ping
pgsql.localmonkey: True
master.localmonkey: True

martin f krafft

unread,
Oct 16, 2012, 12:35:57 PM10/16/12
to Les Mikesell, salt-...@googlegroups.com, David Boucha
also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.16.1651 +0200]:
> > The question is really just whether it makes more sense to have
> > a node-centric definition (which is what I prefer for now), or
> > a role-centric one.
>
> If you don't have groups of similar machines, is it really worth the
> trouble to automate the configuration?

Yes. I have plenty of groups of similar machines. Let's make
a simple example:

Zurich site:
- blue.example.org: web server
- white.example.org: mail server
Munich site:
- black.example.org: web server
- yellow.example.org: mail server

So now I have four groups:

[webserver]
blue & black

[mailserver]
white & yellow

[site-munich]
black & yellow

[site-zurich]
blue & white

In order to find out which groups (roles, classes, whatever) e.g.
white has, I need to search the whole file and inspect context of
occurrences.

Compare this to my current approach:

blue:
- webserver
- site-zurich

black:
- webserver
- site-munich

yellow:
- mailserver
- site-munich

white:
- mailserver
- site-zurich
"i like .net for the same reason i like gentoo. it keeps all the
people with no clue from writing c code, which is much harder for me
to identify and eliminate from my systems. in the same way that
gentoo gives those people a place to be that isn't in debian"
-- andrew suffield

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

martin f krafft

unread,
Oct 16, 2012, 12:38:53 PM10/16/12
to David Boucha, salt-...@googlegroups.com
also sprach David Boucha <da...@saltstack.com> [2012.10.16.1721 +0200]:
> Salt is very flexible. I'm sure you'll be able to find a way to
> make it work as you'd like.

The question that immediately comes to my mind is: how likely is it,
that varying paradigms will be accepted upstream? I hate to have to
maintain downstream changes…

> Since you're creating each of these servers yourself, is it too
> much to add a custom grain to the minion config?

It's a bootstrapping problem. Sure, I can manually create the grain
on the machine, but I'd prefer not to have to do that. Ideally, I'd
provision a base-line image that has salt-minion configured and then
let it run, so that all configuration of the machine is done on the
master.
"next the statesmen will invent cheap lies, putting the blame upon the
nation that is attacked, and every man will be glad of those
conscience-soothing falsities, and will diligently study them, and
refuse to examine any refutations of them; and thus he will by and by
convince himself that the war is just, and will thank god for the
better sleep he enjoys after this process of grotesque
self-deception."
-- mark twain

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

martin f. krafft

unread,
Oct 16, 2012, 12:41:01 PM10/16/12
to Jason Godden, salt-...@googlegroups.com
also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.1835 +0200]:
> Jinja templating in pillar perhaps?

I am sure there is a smart way to get it done, maybe the
external_nodes interfaces is all I need.

The example you provide does seem to replicate node groups, though:

> {% set roles = {
> 'zabbix': [ 'pgsql.localmonkey', 'master.localmonkey' ],
> 'pgsql_client': [ 'pgsql.localmonkey' ]
> }

"in just seven days, i can make you a man!"
-- the rocky horror picture show

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Jason Godden

unread,
Oct 16, 2012, 12:57:23 PM10/16/12
to salt-...@googlegroups.com, mad...@madduck.net
On 17/10/12 03:41, martin f. krafft wrote:
> also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.1835 +0200]:
>> Jinja templating in pillar perhaps?
> I am sure there is a smart way to get it done, maybe the
> external_nodes interfaces is all I need.
>
> The example you provide does seem to replicate node groups, though:
>
>> {% set roles = {
>> 'zabbix': [ 'pgsql.localmonkey', 'master.localmonkey' ],
>> 'pgsql_client': [ 'pgsql.localmonkey' ]
>> }
Indeed but it does allow you to lookup up what roles a host has (which
was the other issue you raised). Nodegroups don't allow this.

Either from the host:

root@pgsql:~# salt-call pillar.data
[INFO ] Loaded configuration file: /etc/salt/minion
{'local': {'role': ['pgsql_client', 'zabbix']}}

from a state top file:

'I:roles.pgsql_client':
- postgresql.client.9-2
'I:roles.zabbix':
- zabbix

or from the salt master:

root@master:/srv/salt/pillar# salt 'pgsql.localmonkey' pillar.data
{'pgsql.localmonkey': {'role': ['pgsql_client', 'zabbix']}}

The end result is the flexibility of grains and its centrally managed
without having to write your own ENC. With a few more lines you could
add globbing or regular expressions too..

David Boucha

unread,
Oct 16, 2012, 1:05:13 PM10/16/12
to David Boucha, salt-...@googlegroups.com
On Tue, Oct 16, 2012 at 10:38 AM, martin f krafft <mad...@madduck.net> wrote:
also sprach David Boucha <da...@saltstack.com> [2012.10.16.1721 +0200]:
> Salt is very flexible. I'm sure you'll be able to find a way to
> make it work as you'd like.

The question that immediately comes to my mind is: how likely is it,
that varying paradigms will be accepted upstream? I hate to have to
maintain downstream changes…

Salt is VERY open to varying paradigms. We love code contributions from 
the community. 

martin f. krafft

unread,
Oct 16, 2012, 1:32:07 PM10/16/12
to Jason Godden, salt-...@googlegroups.com
Dear Jason,

your gratuitous use of jinja in top.sls generation, along with the
question "how do I specify free-form parameters for a host" got me
thinking.

Conceivably, top.sls could just be Python à la

{% from hostinfo import HostInfo
hosts = dict()
hosts['white'] = HostInfo(
roles = [ 'webserver'
, 'site-zurich'
],
param = { 'rackloc': '29 D H23'
, 'motd' : 'downtime sunday 6-8'
}
)

%}

and then

base:
{% for h, i in hosts.iteritems() %}
{{ h }}:
{% for role in i.roles %}
- {{ role }}
{% endfor %}
{% endfor %}

Surely, I can also just import the hosts dictionary from a .py file
and maintain my nodes database in a Python script (or well,
external_nodes).

The question now is: how do I make sure that states evaluated for
a node have access to the node's parameters? Trivial example:
per-node message-of-the-day entries (e.g. host white: "downtime
sunday 6–8"). includes cannot be parametrised, apparently. How do
I ensure that my motd.sls file uses the 'downtime sunday 6-8'
string when it targets white (see Python above).

Conveniently, Mike Chesnut just asked about external_nodes and
Thomas Hatch said he would write something, so I am standing on my
toes. Because — let's face it — the above is a gross hack! ;)

Thanks,
"if english was good enough for jesus christ,
it's good enough for us."
-- miriam ferguson, governor of texas

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Jason Godden

unread,
Oct 16, 2012, 2:00:27 PM10/16/12
to salt-...@googlegroups.com, mad...@madduck.net
Nice.. although I can't help but think that a lot of what you're wanting
to do is really the realm of grains and pillar ("how do I specify
free-form parameters for a host" - sounds like pillar to me).

What if there was a simple state for the minion include directory

/etc/salt/minion.d
file.recurse:
- source:
- salt://hosts/{{ grains['id'] }}/etc/salt/minion.d
- salt://defaults/etc/salt/minion.d (this would be empty)
- clean
- user: root
- group: root
- dir_mode: 0700
- file_mode: 0600

and then /srv/salt/base/hosts/myhost/etc/salt/minion.d/ contains files
as required:

grains.conf:

grains:
roles:
- webserver
- site-zurich
rackloc: 29 D H23
motd: downtime sunday 6-8

There may be issues with refreshing this data during a run (have to call
salt states twice maybe - haven't tested) but perhaps a salt '*'
state.sls miniongrains base cronned on a regular basis is all thats
required? I would move motd into pillar though as that would potentially
be dynamic data vs static. You could also write your own grains in python.

If you go down this path you could expose the data in your python file
DB via pillar and that is visible to your states. You could even write
your states in python if you want and skip the yaml based configs all
together.

I'd be careful about getting too complex (although the truth is you can
get as custom as you want).

Les Mikesell

unread,
Oct 16, 2012, 4:09:58 PM10/16/12
to salt-...@googlegroups.com, Jason Godden
On Tue, Oct 16, 2012 at 12:32 PM, martin f. krafft <mad...@madduck.net> wrote:
>
> Conveniently, Mike Chesnut just asked about external_nodes and
> Thomas Hatch said he would write something, so I am standing on my
> toes. Because — let's face it — the above is a gross hack! ;)

Maybe it would make more sense if you started with who knows the
connection between a new arbitrary hostname and its roles and
parameters, and how they would like to describe them. Personally, I
think some sql tables would make much more sense than the other ways
you might abstract this except that you probably want at least a small
hierarchy of groupings down to host-specific settings and it is hard
to express that in a single sql operation.

--
Les Mikesell
lesmi...@gmail.com

martin f. krafft

unread,
Oct 16, 2012, 4:35:03 PM10/16/12
to Jason Godden, salt-...@googlegroups.com
also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.2000 +0200]:
> - source:
> - salt://hosts/{{ grains['id'] }}/etc/salt/minion.d
> - salt://defaults/etc/salt/minion.d (this would be empty)

Funny how we think in parallel — see my last message, which
I composed before seeing this one…

> and then /srv/salt/base/hosts/myhost/etc/salt/minion.d/ contains
> files as required:
>
> grains.conf:
>
> grains:
> roles:
> - webserver
> - site-zurich
> rackloc: 29 D H23
> motd: downtime sunday 6-8

I can imagine this working, but with two salt invocations, as you
point out. It still feels like a hack. Why do I need to move the
information to the host just to be able to use it on the master?
I shouldn't need to go that indirection.

Quite possibly, external_nodes will do everything I want. I am
eagerly awaiting Thomas' answer…

As pertaining to pillars, I have read
http://docs.saltstack.org/en/latest/topics/pillar/index.html and can
only conclude that this doesn't look too complex. Soon to be in my
20th year of Unix, I know well that "doesn't look too complex" is
usually a synonym for powerful. Where can I find commented uses of
pillars to do crazy albeit useful stuff?
"i doubt larry wall ever uses strict."
-- frederick heckel

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

martin f krafft

unread,
Oct 16, 2012, 4:52:18 PM10/16/12
to Les Mikesell, salt-...@googlegroups.com, Jason Godden
also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.16.2209 +0200]:
> Maybe it would make more sense if you started with who knows the
> connection between a new arbitrary hostname and its roles and
> parameters, and how they would like to describe them.

This is an excellent remark. Let's just assume that I am the person
who "knows" the connection. I could well set up a relational
database, e.g.

node ∞:1 location
node ∞:1 webserver
node ∞:1 debian release
etc.

but in reality, it's hierarchical data. And actually, it's more than
that. It's tag data. It's really just a list of nodes associated
with lists of tags, and a tags taxonomy.

white: webserver site-zurich debian-squeeze munin-client postfix-satellite@blue
black: mailserver site-munich debian-wheezy munin-client
blue: mailserver site-zurich debian-squeeze munin-client
red: munin-server site-zurich debian-sid postfix-satellite@blue
yellow: webserver site-munich debian-squeeze munin-client postfix-satellite@black

You will see how this turns into YAML quite easily. Hence, this is
how I define hosts currently, using "reclass" as external_nodes
classifier in puppet, e.g.:

roles:
- debiannode@squeeze
- hosted@ifi
- puppetclient
- backuppcclient
- muninnode
- sudo
- ferm
- postfix@satellite
- ip6t...@HE.zrh1
- apache@virtual
parameters:
apache:
enable_ssl: false
use_gnutls: true
debian::motd:
headeritems:
- This machine is hosted at the University of Zurich.
- IPv6 traffic is tunneled.
services:
- stuff.madduck.net
- tunes.madduck.net
- flics.madduck.net
- debian.madduck.net
ferm:
openports:
- ftp/tcp
- http/tcp
- https/tcp
- 7789/tcp
postfix:
[…]

If you were to ask me to enumerate my web- and mail- servers,
I would take quite a bit longer. Speaking in RDBMS terms, my indices
are clearly host-centric. ;)
"common sense is the collection of prejudices
acquired by age eighteen."
-- albert einstein

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Les Mikesell

unread,
Oct 16, 2012, 5:24:03 PM10/16/12
to Les Mikesell, salt-...@googlegroups.com, Jason Godden
On Tue, Oct 16, 2012 at 3:52 PM, martin f krafft <mad...@madduck.net> wrote:
> also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.16.2209 +0200]:
>> Maybe it would make more sense if you started with who knows the
>> connection between a new arbitrary hostname and its roles and
>> parameters, and how they would like to describe them.
>
> This is an excellent remark. Let's just assume that I am the person
> who "knows" the connection.

I was hoping that wasn't the case... It makes the problem more
interesting. Even more so if different people make the decisions
about different steps.

> I could well set up a relational
> database, e.g.
>
> node ∞:1 location
> node ∞:1 webserver
> node ∞:1 debian release
> etc.
>
> but in reality, it's hierarchical data. And actually, it's more than
> that. It's tag data. It's really just a list of nodes associated
> with lists of tags, and a tags taxonomy.

But there are a lot of human-understandable tools to deal with tabular
data. Not so many with hierarchies and while tag lists can be
managed, what do you do if there are duplicates or conflicts and you
haven't established the hierarchy?

--
Les Mikesell
lesmi...@gmail.com

Jason Godden

unread,
Oct 16, 2012, 5:38:18 PM10/16/12
to Les Mikesell, salt-...@googlegroups.com, mad...@madduck.net
The example yaml block you've provided above *is exactly* how you would
define this in pillar for a host. If you want to programmatically
generate this you can use a hierarchical database (PostgreSQL with the
lastest JSON support, then write that out as YAML, or use WITH RECURSIVE
queries, MongoDB etc...).

Your top file then references this:

base:
'roles:ferm':
match:pillar
states.muninnode

You can then reference the pillar data in configs (ignore the lack of
context here but you get the drift):

templates/ferm.conf:

{% for openport in pillar['ferm']['openports'] %}
{% set service, port = openport.split('/') %}
myfunkyfwrule_for_{{ service }} {
allow tcp {{ port }}
}
{% endfor %}

You can use this in the ferm state:

ferm:
pkg:
- installed
file.managed:
- name: /etc/ferm.conf
- source: salt://templates/ferm.conf
- template: jinja
- require:
- pkg.installed: ferm
service.running:
- enable: True
- require:
- file.managed: /etc/ferm.conf
- watch:
- file.managed: /etc/ferm.conf

You could add other dependencies to get it to run a ferm test etc..
before doing a restart, backing up files so you can re-instate the
working firewall config.

MLister2006

unread,
Oct 16, 2012, 6:45:41 PM10/16/12
to salt-...@googlegroups.com, mad...@madduck.net

here is how I am trying to use grains (pushed from master per host, so no need to maintain a manual copy on minion itself)

On master I maintain one file per minion.
e.g: /srv/salt/minions/minion.d/[HOSTNAME]/grains.conf  (you can pick your own directory structure) . There you can define roles or any other additional minion parameters. I use grains.conf to define roles, site location etc. (This is just my preference to separate roles per host, certainly there are several ways in salt to achieve this)
 
e.g: for hostname1 I have /srv/salt/minions/minion.d/hostname1/grains.conf    
<code>
grains:
  type: vps
  location: muc
  facility: Datacenter1
  roles:
    - WEBSERVER
    - APPSERVER1
</code>
 
Now defined minions/init.sls and adding a stanza like to push host specific files to minions
 
<code>
#-- Pushing additional files (e.g: role files) to minions.
/etc/salt/minion.d:
  file.recurse:
    - makedirs: True
    - clean: True
    - source: salt://minions/minion.d/{{ grains['id'] }}
</code>
 
setup salt-minion service to watch for /etc/salt/minion.d and restart automatically everytime there is a change.
<code>
salt-minion:
  pkg:
    - installed
  service:
    - running
    - enable: True
    - watch:
      - file: /etc/salt/minion
      - file: /etc/salt/minion.d
</code>
 
from top.sls apply this to all hosts
 
<code>
base:
  '*':
    - minions
</code>
 
There is a little bootstrap condition I agree. and one can run a command to prime minions with latest config and grains file before doing any futher. (or run state.highstate two time :)
 
salt '*' state.sls minions
  This will push latest file per minion and restart minions if needed.
 
I noticed w/ 0.10.3 there is a new options now  (I have yet to play w/ this)
  startup_states: highstate
but looks at at every restart of minion, you can tell which state minion should run at startup, may helpwhen you bring new minion or if minion was down for long time and missed the last update.
 
not sure of this solves your condition.
I use similar paradigm (like you mentioned in cfengine and puppet), and in the begining for a few days it is hard to map salt and oter CMS and come out of the old thinking. But as I am diving more into salt, the potential is phenomenal and there are always a way to do  all things available in other CMS + lot more.
salt team is doing great job in adding features and modules as users raising their hands.
 
good luck !
 
 

 

Ask Bjørn Hansen

unread,
Oct 16, 2012, 8:26:29 PM10/16/12
to salt-...@googlegroups.com, Les Mikesell, Jason Godden, mad...@madduck.net
I'm in roughly the same situation as Martin, funny enough.

I've just been listing each host in my top.sls and including the states it should have, but so far I am only managing a couple of (not-global) states on each box.

Today I needed to add a host specific parameter for a state and this discussion is along the same lines.

I've tried variations on this in my pillar/top.sls file, but it's unhappy with that because every item should be another SLS file (so no parameters?) and secondarily because the pillar data gets compiled on the master so my default IP gets set to the IP of the master. The latter problem I can probably fix by having the template try the data from the pillar and fallback to the data from salt[] (assuming jinja supports that).

base:
  '*':
    roles:
      - geodns
    parameters:
      geodns_ip: "{{ salt['network.ipaddr']('eth0') }}"

  'somehost':
    roles:
      - geodns
    parameters:
      geodns_ip: "10.10.0.42,192.168.0.3"

Any hints for me?  Writing/distributing custom grains seems really backwards to me.


Ask

martin f. krafft

unread,
Oct 17, 2012, 2:15:14 AM10/17/12
to Jason Godden, Les Mikesell, salt-...@googlegroups.com
also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.2338 +0200]:
> The example yaml block you've provided above *is exactly* how you
> would define this in pillar for a host.

Correct me if I am wrong, but my understanding was that pillars are
sort of like reusable definitions to build upon. I thought the whole
point was that they were generic, not host-specific.

> Your top file then references this:
>
> base:
> 'roles:ferm':
> match:pillar
> states.muninnode

I understand how to use pillar data, but I don't quite understand
why you are making the muninnode state a child of a pillar match for
ferm. Fundamentally, while every munin node will have ferm
installed, the two have nothing to do with each other.
in seattle, washington, it is illegal to carry a concealed weapon that
is over six feet in length.

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

martin f krafft

unread,
Oct 17, 2012, 2:21:18 AM10/17/12
to Les Mikesell, salt-...@googlegroups.com, Jason Godden
also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.16.2324 +0200]:
> But there are a lot of human-understandable tools to deal with
> tabular data. Not so many with hierarchies and while tag lists
> can be managed, what do you do if there are duplicates or
> conflicts and you haven't established the hierarchy?

Tables and hierarchies cannot express all types of information, at
least not natively. Sure, I can have a table of webservers
referencing nodes and a table of host in zurich referencing nodes
and consider a node with an entry in those tables to be "tagged",
but that's hardly a "human-understandable" way of going about
things — and apart, it's the same as nodegroups.

With plain (non-cyclical) hierarchies, there are plenty of tools to
deal with them, cd, /bin/ls, /usr/bin/find are just examples. Cycles
are currently not handled well, not even by hierarchical databases
like ZoDB.

Pertaining to duplicates, those are not nice, but hardly a problem.
It's mainly a question of data storage. In MongoDB, I might choose
to maintain a tags dictionary instead of a list, e.g.

{ 'mynode' : { 'webserver' : 1, 'site-zurich' : 1 } }

and make use of the implicit hash table to prevent storing
duplicates.

Conflicts are, of course, not nice, but they are a semantic problem
with tags, e.g.

{ 'mynode' : { 'site-munich' : 1, 'site-zurich' : 1 } }

The result is implementation-specific. Most likely, one role will
overwrite the other, but fundamentally there is no keeping you back
from writing a checker at the semantic level to prevent this sort of
things. The integrity of the data itself is not harmed, though.
sex an und für sich ist reine selbstbefriedigung.

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Jason Godden

unread,
Oct 17, 2012, 4:52:09 AM10/17/12
to salt-...@googlegroups.com, mad...@madduck.net
On 17/10/12 17:15, martin f. krafft wrote:
> also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.2338 +0200]:
>> The example yaml block you've provided above *is exactly* how you
>> would define this in pillar for a host.
> Correct me if I am wrong, but my understanding was that pillars are
> sort of like reusable definitions to build upon. I thought the whole
> point was that they were generic, not host-specific.
Pillar is quite generic. It is simply a generic data provider. The
documentation at
http://salt.readthedocs.org/en/latest/topics/pillar/index.html provides
notes on using pillar for global resources as well as targeting
configuration to individual minions.

As pillar is a generic interface you can supply data to various salt
constructs using a deterministic call to a data supplier. This example
uses mongodb as an ext_pillar data provider
(https://salt.readthedocs.org/en/latest/ref/pillar/all/salt.pillar.mongo.html?highlight=ext_pillar)
but you could use any tool you wish or integrate with any CMDB or
monitoring application. Now the mongodb pillar data provider in
particular may not satisfy all of your requirements - that is not the
point of my example - it would be simple to provide a hierarchical and
conditional interface to your node or service specific data. The
implementation is up to you.

You also have hiera providers and the more generic cmd_yaml provider.

The configuration chunk you provided is perfectly matched to the pillar
yaml format. Drilling down - there is no difference between a generic
data blob of tags and hierarchies and a node specific blob of data - its
all data. The trick is how you use it.

Take your example - Puppet ENC -
http://docs.puppetlabs.com/guides/external_nodes.html. This must return
classes, parameters and environment keys however with pillars used in
this fashion we're not limited to these three keys - rather we can
define any flexible tagging system we desire. You could emulate this of
course using a combination of pillar matched roles (the role key),
environment as the specific target environment in the top definition and
parameters as data passed to jinja macros. Now master_tops let salt
interface to your node data which can be a simple
rendering/transformation of your total pillar data.

All we've done now is duplicate Puppets ENC system - *easy* - the
exercise of extending this is left to the reader ;) I have no doubt that
helper applications/scripts will be built that provide this out of the
box but there are no supporting commands just yet. It's not hard though
and would probably well be deployment specific in any case.

>> Your top file then references this:
>>
>> base:
>> 'roles:ferm':
>> match:pillar
>> states.muninnode
> I understand how to use pillar data, but I don't quite understand
> why you are making the muninnode state a child of a pillar match for
> ferm. Fundamentally, while every munin node will have ferm
> installed, the two have nothing to do with each other.
>
This was a typo - it should be states.ferm. The example was to
demonstrate that you can target your states based on pillar matches
providing you with the various target filter and reporting requirements
you had. This would be provided by the master_tops transformation of
your pillar data.

Ask Bjørn Hansen

unread,
Oct 17, 2012, 5:00:50 AM10/17/12
to salt-...@googlegroups.com, mad...@madduck.net, ja...@godden.id.au


On Wednesday, October 17, 2012 1:52:19 AM UTC-7, Jason Godden wrote:

You also have hiera providers and the more generic cmd_yaml provider.

How do you do "per host" data with the cmd_yaml provider?


Ask 

Les Mikesell

unread,
Oct 17, 2012, 9:05:11 AM10/17/12
to Les Mikesell, salt-...@googlegroups.com, Jason Godden
On Wed, Oct 17, 2012 at 1:21 AM, martin f krafft <mad...@madduck.net> wrote:
> also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.16.2324 +0200]:
>> But there are a lot of human-understandable tools to deal with
>> tabular data. Not so many with hierarchies and while tag lists
>> can be managed, what do you do if there are duplicates or
>> conflicts and you haven't established the hierarchy?
>
> Tables and hierarchies cannot express all types of information, at
> least not natively. Sure, I can have a table of webservers
> referencing nodes and a table of host in zurich referencing nodes
> and consider a node with an entry in those tables to be "tagged",
> but that's hardly a "human-understandable" way of going about
> things — and apart, it's the same as nodegroups.

The problem I have with nested/tagged arrangement is not the initial
layout pulling some default from a higher level, but where is it that
you need to change a parameter to maintain it later. If a person
just manages one aspect of the configuration - like an application
version or parameters - how would he know where to find the setting to
change across a set of servers? Or more typically in our case, where
would be the right place to change to change half of a group one day
and the other half the next?

--
Les Mikesell
lesmi...@gmail.com

martin f krafft

unread,
Oct 27, 2012, 5:20:09 PM10/27/12
to Les Mikesell, salt-...@googlegroups.com, Jason Godden
also sprach Les Mikesell <lesmi...@gmail.com> [2012.10.17.1505 +0200]:
> The problem I have with nested/tagged arrangement is not the initial
> layout pulling some default from a higher level, but where is it that
> you need to change a parameter to maintain it later. If a person
> just manages one aspect of the configuration - like an application
> version or parameters - how would he know where to find the setting to
> change across a set of servers? Or more typically in our case, where
> would be the right place to change to change half of a group one day
> and the other half the next?

It's been a while, but: I agree with what you are saying. YAML-style
configuration à la anything goes exascerbates the problem too IMHO.
What I would love to see is parameter namespacing, e.g. the
salt.minion module gets its own exclusive namespace for parameters
(e.g. params['salt.minion']) and can then enforce a vocabulary, e.g.
fail if unknown parameters are encountered.

Apart from that, it is a matter of docs and testing…
"once ... in the wilds of afghanistan, i lost my corkscrew, and we
were forced to live on nothing but food and water for days."
-- w. c. fields, "my little chickadee"

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Tom Vaughan

unread,
Oct 27, 2012, 5:54:28 PM10/27/12
to salt-...@googlegroups.com, mad...@madduck.net, ja...@godden.id.au


On Wednesday, October 17, 2012 5:52:19 AM UTC-3, Jason Godden wrote:
On 17/10/12 17:15, martin f. krafft wrote:
> also sprach Jason Godden <ja...@godden.id.au> [2012.10.16.2338 +0200]:
[snip] 
>> Your top file then references this:
>>
>> base:
>>    'roles:ferm':
>>      match:pillar
>>      states.muninnode
> I understand how to use pillar data, but I don't quite understand
> why you are making the muninnode state a child of a pillar match for
> ferm. Fundamentally, while every munin node will have ferm
> installed, the two have nothing to do with each other.
>
This was a typo - it should be states.ferm. The example was to
demonstrate that you can target your states based on pillar matches
providing you with the various target filter and reporting requirements
you had. This would be provided by the master_tops transformation of
your pillar data.

This is one such example:


The hostnames are still "hardcoded" on the server-side to select the pillar role. So this doesn't solve the original question that started this thread. But this does demonstrate one way to pass parameters to the salt states without a lot if if/else clauses or custom grains (see "countrycode" in the example above).

-Tom


martin f. krafft

unread,
Oct 28, 2012, 6:56:43 AM10/28/12
to Ask Bjørn Hansen, salt-...@googlegroups.com, ja...@godden.id.au
also sprach Ask Bjørn Hansen <a...@develooper.com> [2012.10.17.1100 +0200]:
> How do you do "per host" data with the cmd_yaml provider?

This question in the thread at [0] remains open.

0. https://groups.google.com/forum/#!searchin/salt-users/ext_pillars/salt-users/R_jgNdYDPk0/V32IjwKGANsJ

How does one pass the hostname to the script invoked by cmd_yaml?

I don't think it's possible without the following patch, which would
ensure that the node id is passed to the command as first argument.

--- /tmp/cmd_yaml.py 2012-10-28 11:55:49.000000000 +0100
+++ /usr/share/pyshared/salt/pillar/cmd_yaml.py 2012-10-28 11:55:44.964252324 +0100
@@ -19,7 +19,7 @@
'''
try:
import sys
- return yaml.safe_load(__salt__['cmd.run'](command))
+ return yaml.safe_load(__salt__['cmd.run']('{0} {1}'.format(command, __opts__['id'])))
except Exception:
log.critical(
'YAML data from {0} failed to parse'.format(command)
"the only difference between the saint and the sinner
is that every saint has a past and every sinner has a future."
-- oscar wilde

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Ask Bjørn Hansen

unread,
Oct 28, 2012, 11:34:46 AM10/28/12
to martin f. krafft, salt-...@googlegroups.com, ja...@godden.id.au

On Oct 28, 2012, at 3:56, "martin f. krafft" <mad...@madduck.net> wrote:

>> How do you do "per host" data with the cmd_yaml provider?
>
> This question in the thread at [0] remains open.
>
> 0. https://groups.google.com/forum/#!searchin/salt-users/ext_pillars/salt-users/R_jgNdYDPk0/V32IjwKGANsJ
>
> How does one pass the hostname to the script invoked by cmd_yaml?
>
> I don't think it's possible without the following patch, which would
> ensure that the node id is passed to the command as first argument.

There's a ticket in the issue tracker about this as well: https://github.com/saltstack/salt/issues/2276

Thomas indicated there that just parsing the host might not be all we want/need.


Ask

Jason Godden

unread,
Oct 28, 2012, 8:58:29 PM10/28/12
to salt-...@googlegroups.com, Ask Bjørn Hansen, martin f. krafft
In my previous post I mentioned you might need some of your own
interfaces to make this work. Updating the cmd_yaml ext_pillar to
include id may be correct but you might also want to filter on other
items apart from id (like the way the hiera ext_pillar does). I think an
additional command should be provided that will work with this and
cmd_yaml should be left as is. It may be that the master configuration
should include the list of grains that get passed to the ext_pillar.

This is an example of how you can have a centralized configuration file
with ext_nodes from master tops and then per host pillar configuration
using a custom pillar. This an *example* only and not intended to
facilitate everything you need. I'm sure there are issues with the code :)

http://pastebin.com/b5CT0DJQ

I will probably update/harden this and provide it as an add-on module if
there is interest.

martin f. krafft

unread,
Oct 29, 2012, 2:53:12 AM10/29/12
to Jason Godden, salt-...@googlegroups.com, Ask Bjørn Hansen
also sprach Jason Godden <ja...@godden.id.au> [2012.10.29.0158 +0100]:
> This is an example of how you can have a centralized configuration
> file with ext_nodes from master tops and then per host pillar
> configuration using a custom pillar. This an *example* only and not
> intended to facilitate everything you need. I'm sure there are issues
> with the code :)
>
> http://pastebin.com/b5CT0DJQ

This is very much like what I have converted my Puppet reclass tool
('_r_ecursive _e_xternal _class_ifier') into yesterday. The main
difference is that I go indirectly via roles. Clients and roles
specify roles they extend, as well as environment, states,
parameters and variables. My script does a depth-first-search in the
roles tree and merges the other data as it goes back up the tree.
This allows me to have e.g. a unixnode default role (which installs
ssh-server and sets PermitRootLogin to no) and also a backup-client
role (which extends the ssh-server role and overrides
PermitRootLogin).

Once I have successfully melded this to salt-land, I will write
about it here.

But yes, it requires a modified ext_pillar handler. I am not opposed
to changing cmd_yaml [0] mostly because it can be extended without
affecting existing behaviour (see my patch) and because there is no
point really in distributing proof-of-concepts when more flexible
approaches also exist.
"it isn't pollution that's harming the environment.
it's the impurities in our air and water that are doing it."
- dan quayle

spamtraps: madduc...@madduck.net
digital_signature_gpg.asc

Jason Godden

unread,
Oct 29, 2012, 6:01:13 AM10/29/12
to salt-...@googlegroups.com, Ask Bjørn Hansen, martin f. krafft
On 29/10/12 17:53, martin f. krafft wrote:
> also sprach Jason Godden <ja...@godden.id.au> [2012.10.29.0158 +0100]:
> This is very much like what I have converted my Puppet reclass tool
> ('_r_ecursive _e_xternal _class_ifier') into yesterday. The main
> difference is that I go indirectly via roles. Clients and roles
> specify roles they extend, as well as environment, states,
> parameters and variables. My script does a depth-first-search in the
> roles tree and merges the other data as it goes back up the tree.
> This allows me to have e.g. a unixnode default role (which installs
> ssh-server and sets PermitRootLogin to no) and also a backup-client
> role (which extends the ssh-server role and overrides
> PermitRootLogin).
>
> Once I have successfully melded this to salt-land, I will write
> about it here.
Great - the more tools and examples the better ;)
Reply all
Reply to author
Forward
0 new messages