Find and use ip addresses of hostnames from an array

Hey guys,

I have a custom redis recipe where I need to use the IP addresses of host names from an array.
The array looks like this
default['redis']['masters'] = ["hostname1", "hostname2"]
default['redis']['slaves'] = ["hostname3", "hostname4", "hostname5"]

The recipe looks like this:

package "redis" do
    version "#{node['redis']['version']}"
    flush_cache [ :before ]
    allow_downgrade false
    action :install
end

template "redis.conf" do
 path "/etc/redis.conf"
 source "redis.conf.erb"
 owner node['redis']['user']
 group node['redis']['group']
 mode "0644"
 variables(
  :master => node['redis']['master'],
  :slave => node['redis']['slave'],
  :hostname => node.name.split('.')[0]
    )
end

template "redis-sentinel.conf" do
 path "/etc/redis-sentinel.conf"
 source "redis-sentinel.conf.erb"
 owner node['redis']['user']
 group node['redis']['group']
 mode "0644"
 variables(
  :master => node['lc-redis']['master'],
    :masterip => node['lc-redis']['master']['ipaddress'],
  :masterport => node['lc-redis']['masterport'],
  :masterauth => node['lc-redis']['masterauth']
    )
end
service "redis" do
 supports :restart => true
 action [:enable, :start]
end
service "redis-sentinel" do
 supports :restart => true
 action [:enable, :start]
end

In the end, what I need is to be able to deploy the recipe to all 5 nodes (the 2 masters and the 3 slaves)
The master hostname1 should have as slaves hostname3 and hostname4 and master hostname2 should have as slave hostname5

The template file for redis.conf looks like this:

daemonize yes
pidfile /var/run/redis.pid
dbfilename dump.rdb
dir /var/lib/redis/
<% if @hostname != @master -%>
slaveof <%= node['redis']['master'] %> <%= node['redis']['defaultport'] %>
<% end -%>
<% if @hostname != @master -%>
masterauth <%= node['redis']['masterpass'] %>
<% else %>
requirepass <%= node['redis']['masterpass'] %>
<% end -%>
loglevel notice
logfile /var/log/redis/redis.log

And the template file for redis-sentinel looks like this:

pidfile "/var/run/redis/sentinel.pid"
daemonize yes
logfile "/var/log/redis/sentinel.log"
port 26379
dir "/tmp"

sentinel monitor <%= node['lredis']['master']%> <%= node['redis']['master']['ipaddress']%> <%= node['redis']['masterport']%> 2
sentinel down-after-milliseconds <%= node['redis']['master']%> 10000
sentinel failover-timeout <%= node['redis']['master']%> 30000
sentinel auth-pass <%= node['redis']['master']%> <%= node['redis']['masterauth']%>
sentinel parallel-syncs <%= node['redis']['master']%> 1

Obviously, is wrong because when I have two masters i need to have the sentinel commands for each of them. I should use each method for all the masters and have all sentinel commands for for every master, but i cannot get the ip address of all the masters.

What I’ve tried is something like this:

masters = node['redis']['master']
masters.each do |node|
  Chef::Log.info("#{node["name"]} has IP address #{node["ipaddress"]}")
end

How do I store the IP address of each master in a variable?
How can I improve this recipe and make it work?

I know it’s a long shot, but I dare asking.

Thank you,
Gabriel

Hi Gabriel,

You should try using data bags for maintaining the hostname - ipaddress
mapping as well as redis master - slave mapping.

Data items can be something similar to this:
{
‘id’: ‘hostname1’,
‘Ipaddress’: ‘x.x.x.x’,
‘slaves’: ‘hostname3, hostname4’
}
Swati

1 Like

Gabriel.

Sorry due to my lack of redis sentinel knowledge I am going to ask probably a stupid questions:
Is there any reason that each master should not have a list of all slaves?
In the event of a master failure does a slave get promoted to a master?

There are many ways to store something between chef runs and are more dynamic than a data bag such as a “normal” attribute. I would also recommend looking into using a chef search to build a more dynamic list.

To answer how you could store the ips of all masters given the hostname you are almost there you can simply create an array outside of your loop and append the ips that you get in the loop to the array and then you can do whatever you want with that array elsewhere in the recipe.

Additionally I am not sure if you checked out the redis sentinal lwrp: https://github.com/brianbianco/redisio#lwrp-examples which seems to just want a list of nodes which in this case doing a search seems appropriate. Again apologies for the ignorance in answering the question.

Thanks,

1 Like

Thank you, swatir88. I’ll get a try with data_bags as it sounds like a reasonable solution.

Why not using tags or roles for masters and slave pairs ? and then using search and ohai data from search result ?

1 Like

Hello majormoses,

I have tried to use “normal” attributes, but (due to my lack of deeper chef knowledge) I fail to understand how to.
before I get to use data bags, until now I’ve used a per environment redis attributes like:

"redis": {
    "masters": ["test01", "test02"],
    "slave": ["test03", "test04", "test05"]
}
},

But the problem is that if I try to set some slaves for a master and the other slaves for the other master, I feel lost.

One thing I thought of was something like this:

"redis": {
    "master1": "test01",
    "master2": "test02",
    "slaves1": ["test03", "test04"],
    "slaves2": "test05"
}
},

Thank you Tensibai. That’s a good idea also.
I’ll try with databags first.

the main drawback I see with databags is that you’re making a theoretical based configuration instead of real world based one.

I do prefer using search and node data gathered by ohai for those case and not have to manually maintain clusters relationship already done by runlist and attributes (roles aren a easy tag, using any attribute works well too)

What I would do in term of attributes could be:

"redis": {
  "master1": {
    "name": "test01",
    "slaves": ["test03", "test04"]
  },
  "master2": {
    "name": "test02",
    "slaves": ["test05"]
  }

But this won’t be really usable, so maybe something like:

"redis": {
  "test01": {
    "role": "master"
  },
  "test02": {
    "role": "master"
  },
  "test03": {
    "role": "slave",
    "master": "test01"
  },
  "test04": {
    "role": "slave",
    "master": "test01"
  },
  "test05": {
    "role": "slave",
    "master": "test02"
  }
}

And in recipe:

For mas{ter recipe (assuming “testXX” are the hostnames of the nodes:

slaves = node['redis'].select { |n,p| p["master"] == node['hostname"] }

To get an array of the slaves names only use slaves.keys in the template variable parameter

And in slave recipe:

master = node['redis'].select { |n| n == n['redis'][ node['hostname'] ]['master'] }

Same as above us master.keys[0] to get only the master name without other attributes from the hash.

To get a specific attribute from the entries, you’ll have to work with the hash, something like this should do:

roles=[]
slaves.each { |s,p| roles << p['role'] }

and roles will be an array with all slaves role (not very useful here, but you get the idea I think)

Main advantage of this approach is that you can replace the attributes by a search later.

Documentation about he methods used here are on the ruby Hash page: http://docs.ruby-lang.org/en/2.1.0/Hash.html

1 Like

In your recipe you can do this node.normal['foo']['bar'] = 'foobar' when you want to set the persistent attribute.

1 Like

Here is a simple search example:

host_search = search(:node, "chef_environment:#{env} AND roles:#{role}")
if host_search.empty? || !host_search.any?
  server_ips << node['ipaddress']
else
  host_search.each do |nodeobj|
  server_ips << nodeobj.node.ipaddress
end
1 Like

No please, don’t use node.normal for things done on each run. node.default if usually enough.

node.normal should be used only to definitely persists values on the node object and clearly knowing the caveats coming with it.

Of course it should not be used when you do not need persistence and should only be used when you understand it. It sounds like he want to keep a dynamic listing of master and slaves instances without a role or something to query. This could be used by a monitoring solution to update state changes that may not be available in the context of chef but you can easily update them via your monitoring. In this case this makes perfect sense. I have seen the similar be used appropriately in mysql master slave setups or when setting a random secret / password when being spun up the first time.