11

I'm trying to build a puppet managed infrastructure (non-enterprise) in AWS with EC2 instances. Using puppetlabs-aws module I'm able to create the machines by convenient means. Next up is to make local settings on each node, most importantly setting a unique hostname. How can I do this?

One way I know of is to provide a script via the user_data parameter. That would be great, but to be usable I need to be able to parameterize that script in order to avoid duplicating the script once for each agent.

Does it make sense? I'd really appreciate a convenient way of achieving this, as I want to launch new instances programmatically. Any suggestion will be considered.

Update

To give an example of my problem, consider this snippet of my provisioning puppet manifest:

ec2_instance { 'backend':
  ensure => present,
  name => 'backend',
  region => 'us-west-2',
  image_id => 'ami-f0091d91',
  instance_type => 't2.micro',
  key_name => 'mykey',
  security_groups => ['provision-sg'],
  user_data => template('configure.erb'),
}

ec2_instance { 'webfront':
  ensure => present,
  name => 'webfront',
  region => 'us-west-2',
  image_id => 'ami-f0091d91',
  instance_type => 't2.micro',
  key_name => 'mykey',
  security_groups => ['provision-sg'],
  user_data => template('configure.erb'),
}

This will ensure the two instances are up and running. Please notice the user_data => template('configure.erb') referring to a template script which is executed on the instance once it is created. Here I would be able to set the hostname (or whatever I wanted to) if I only knew what data to base the decision on. I can add tags to the instance descriptions, but that is not readable from the configure.erb script as far at I know.

Anyway, setting the hostname is just my idea of solving the root problem. There might be other more convenient methods. What I want is simply a way of having these two instances representing different node types to the puppet master.

Vorsprung
  • 32,923
  • 5
  • 39
  • 63
Johan
  • 5,003
  • 3
  • 36
  • 50
  • put the parameters in a hash, make a "define" class and call it with create_resources(mymodule::usercreate, $uhash) I'm sure there are other / better ways to do this but this works for me – Vorsprung Dec 14 '15 at 14:40
  • See http://docs.puppetlabs.com/puppet/4.3/reference/lang_defined_types.html for more details on defined types. See https://docs.puppetlabs.com/puppet/latest/reference/lang_resources_advanced.html#implementing-the-createresources-function for create_resources – Vorsprung Dec 14 '15 at 14:51
  • I've read up on defines and resources. Yet I don't really see how these can solve my problem. Indeed I can use them for not repeating the ec2_instance declarations, but that's not what I'm looking for. What I want is to have these two instances configured as two different puppet classes so the puppetmaster server can give them different configurations. – Johan Dec 16 '15 at 14:56
  • ok, maybe I've misunderstood you *again* but I will edit the answer and see if I can address this particular thing – Vorsprung Dec 16 '15 at 15:58
  • This question is obviously too hard to solve. Thanks @Vorsprung for spending time, but unfortunately the key issue remains. The bounty will be auto-assigned to you, yet but in order for me to accept the answer the initial chicken-egg problem needs to be resolved. – Johan Dec 21 '15 at 07:28
  • I am not really bothered about the bounty. I've offered solutions to several different aspects to the question you are asking and you have said repeatedly that the solutions do not solve your problem! I've added more detail to the answer on what I *believe* is the problem and how I see the answer to this problem – Vorsprung Dec 21 '15 at 13:02
  • Could you comment on how the question I am answering differs from what you want to do? Hopefully this will help with any downstream questions you might need to make. thx – Vorsprung Dec 21 '15 at 13:15
  • I am sorry if I sound rude. You have been to great help and I have already implemented my solution based on your answers. The initial issue remains unsolved though (or maybe you have explained but I misunderstood). I have my configure.erb template which give as user_data for every instance. What I need is to send a parameter with for instance a role name or host name from the _instance declaration_ to the _template_. – Johan Dec 21 '15 at 13:20
  • you don't sound rude at all! sending parameters from the instance declaration (the ``ec2_instance`` block) to the template can be done with the defined type, as all the parameters to the defined type would be available to the template within the defined type. Sounds like you have got something that works going though and that what counts! – Vorsprung Dec 21 '15 at 13:53

1 Answers1

3

The problem is how to set up a new instance with so that it will load it's config from a particular class

Let me try and explain the problem I think you are trying to address

What I am trying to answer here

You have an existing script that sets up EC2 virtual hosts on AWS using the aws-puppet module. This module calls AWS API to actually make EC2 virtual hosts. But they only contain configuration that is "built in" to the AMI file that is used in the API call. A typical AMI file might be a Centos base image. Further configuration is possible at this phase via a "user data script". But let's assume this a shell script, difficult to test and maintain and so not containing complex setup

So further configuration, install of packages and setup is needed. In order to make this setup happen, there is a second phase of activity from puppet, using entirely different manifests (that are not detailed in the question)

This second phase is controlled by the new EC2 virtual hosts attaching to the puppet master in their own right. So what I am assuming you are doing is:

  • phase 1, making EC2 hosts
  • phase 2, when they are up config themselves from puppet

Basic Answer using roles

Here some ideas of how to make this scenario with two phase configuration of the EC2 hosts work

At create time make a custom fact "role". Make a file in /etc/facter/facts.d/role.yaml like this

role: webserver

This can be setup as the instance is made by adding a command like this to a User Data script

echo 'role: webserver' > /etc/facter/facts.d/role.yaml

As long as this "role" is setup before puppet starts up it will work fine.

I am assuming that you have a set of modules with manifests and maybe files subdirectories in the module path with the same name as the role

Next, alter your site.pp to say something like

include "$role"

And the init.pp from the module will kick in and do the right thing, install packages, configure files etc!

This idea is explained in more detail here https://puppetlabs.com/presentations/designing-puppet-rolesprofiles-pattern


Another Approach

The above is a really crude way of doing it which I haven't tested! Our setup has roles but loads them via hiera configuration. The heira configuration looks somewhat like this

---
:backends:
  - yaml
:hierarchy:
    - role/%{::role}
    - global
:yaml:
  :datadir: /etc/puppet/environments/production/hiera

Then I might have a /etc/puppet/environments/production/hiera/role/webserver.yaml file which says

classes:
  - webserver
  - yum_repos
  - logstash
  - java8

And the end of the site.pp says

hiera_include('classes')

Which loads all the relevant "classes" definitions from the modules_include files

This has the advantage that multiple classes can be loaded by each role with much less duplication of code

The "global" part of the yaml configuration is intended for classes that are loaded by everything in your environment, for example admin user ssh keys


defined type example

Here is an example of how you might use a defined type as a wrapper around ec2_instance to pass the "myrole" into the template. I have not tested this, I don't have the aws puppet stuff installed

define my_instance( 
  $ensure = present,
  $region = 'us-west-2',
  $image_id = 'ami-f0091d91',
  $instance_type = 't2.micro',
  $key_name= 'mykey',
  $security_groups = ['provision-sg'],
  $myrole = 'webserver'
  )
{
ec2_instance { $title :
  ensure => $ensure,
  name => $title,
  region => $region,
  image_id => $image_id,
  instance_type => $instance_type,
  key_name => $key,
  security_groups => $security_groups,
  user_data => template('configure.erb'),
}
}

$instance_data={
  'backend' =>
  {
  ensure => present,
  name => 'backend',
  region => 'us-west-2',
  image_id => 'ami-f0091d91',
  instance_type => 't2.micro',
  key_name => 'mykey',
  security_groups => ['provision-sg'],
  myrole => 'voodooswamp'
},
  'webfront'=>
  {
  ensure => present,
  region => 'us-west-2',
  image_id => 'ami-f0091d91',
  instance_type => 't2.micro',
  key_name => 'mykey',
  security_groups => ['provision-sg'],
  myrole => 'humanfly'
  }
}


create_resources(my_instance, $instance_data)
Vorsprung
  • 32,923
  • 5
  • 39
  • 63
  • Thank you for replying! I like the idea of having the puppet master to look at facter. Yet, I cannot figure out how to solve the chicken-egg problem. How do I get the custom fact into each instance? How can I tell the provisioning system to create the /etc/facter/facts.d/realhostname.yaml on a fresh auto-launched instance? – Johan Dec 14 '15 at 12:37
  • @Johan I've added a little more detail, hope there is enough to go on – Vorsprung Dec 14 '15 at 13:05
  • Thank you! However, I still don't understand how I can differentiate between my nodes. I don't want to create a unique userdata script for each instance. I updated my original post with an example (under Updated) in order to put a context to my issue. – Johan Dec 14 '15 at 14:08
  • I have COMPLETELY ALTERED this answer to address the emergent requirements of the question – Vorsprung Dec 16 '15 at 15:59
  • Now this is very valuable information. Thank you for that! I will look into both ways and try them out. Though, still one of the key issues remain. How can I tell the two different machines which role to assign without having to provide different user-data scripts for both configurations? My script (in my example above called configure.erb) is a pretty big script with all common settings between all nodes. I would not like to make hundreds of copies of this just to change to role parameter. – Johan Dec 17 '15 at 07:26
  • By the way, I don't think the requirements have emerged---only been clarified. :) The initial problem has been the same all the way (and still is, although it CAN be solved by massively redundant copies of the user-data script). – Johan Dec 17 '15 at 07:28
  • Your user data is in a template so add a parameterized field to it something like ``echo 'role: <%= @myrole %>' > /etc/facter/facts.d/role.yaml`` – Vorsprung Dec 17 '15 at 08:45
  • Right on! Now I think we are very close to the goal. Only, where can I provide the input to @myrole? – Johan Dec 17 '15 at 09:38
  • make a defined type, put the "myrole" names in a hash and then call the defined type with create_resources. The defined type is just a thin wrapper to ec2_instance – Vorsprung Dec 17 '15 at 11:28
  • If I understand correctly, the defined type would only help me with writing shorter ec2_instance declarations. What I need is to take the value from my ec2_instance declaration and use it in my user-data file. Something like `ec2_instance { 'backend': myrole => 'backend', user_data => template('configure.erb') }` and in configure.erb I need to take the data from the ec2_instance myrole somehow. It's all about this very *somehow*. :) – Johan Dec 17 '15 at 11:44
  • I still have not solved this. Starting to believe that my approach is out of the supported range. – Johan Dec 18 '15 at 13:26
  • Actually I want to set the role name of the instance in what you call _phase 1_. That is, within the ec2_instance puppet declaration or otherwise in such an early stage so when it comes to phase 2, the puppetmaster have enough knowledge about the instance so it can decide what manifest to provide. Edit: Ok, now I see your defined type example. That makes sense. I'll try it out now. – Johan Dec 21 '15 at 13:26
  • Ah, it is finally solved! Thank you so much! With your defined type example, things got straight. :D – Johan Dec 21 '15 at 14:03