Chapter 4 - Auto-provisioning With Cloud Provisioner
Provisioning a new server can be time-consuming. Auto-provisioning is the ability to build a standard, fully configured system with very little input. Puppet Enterprise provides tools which can help with this, but the process still requires some manual intervention. The aim of this exercise is to produce a single script which handles all the fiddly bits, resulting in a fully functional system.
We’re running Cloud Provisioner from the Puppet Console host. Puppet terminology for this host is the “CP control node”.
Auto-provisioning - Fundamental Problems
The subject of auto-provisioning raises some questions which must be answered before we can proceed. There are also a few different ways we can answer these questions. We’ll try and summarise the main issues in the following table.
Q: Do we clone an existing VM image, or install the OS from scratch?
To automatically install from scratch requires the use of DHCP and PXE, with new nodes downloading a microkernel, then running a custom unattended installation procedure using a downloaded OS installation ISO image. It’s a rather involved process, but it’s the only one which works for new physical machines.
A: We will use VMware ‘templates’, which are essentially virtual machine images, ready-installed with a basic version of our chosen OS.
Cloud Provisioner command: puppet node_vmware create
Q: How do we determine the IP address of the new node?
If using DHCP, this can be obtained by parsing the output of ‘puppet node_vmware find’.
Alternatively, all newly-provisioned nodes could be initiated with a known static IP address. We will take this approach, but only because it’s the easiest thing to do in our environment.
Q: How do we ensure the Puppet Agent is configured with the correct certificate name?
Once the IP address of the new node is known, we can install the agent across the network using ‘puppet node install’, providing the agent certificate name with the –certname option.
SSH must be configured appropriately on the new node to allow access from the Cloud Provisioner host, so this must be a property of the VMware template we’re using (more on this later).
Cloud Provisioner command: puppet node install
Q: How do we automatically configure the network?
Puppet can manage the IP address of a host, but ideally the interface must be configured before any other network-dependent services are installed. There may be a way to specify this dependency in Puppet, but I haven’t found this yet.
The easiest solution we’ve found so far is to manipulate the network configuration files directly using sed, then rebooting the new VM before the puppet agent starts to configure the machine. It’s not ideal, and it’s not elegant, but it will work - and it solves the dependency problem.
This will require a custom script (more on this script later).
Pre-requisites
VMware Template Requirements
We’ll be using a basic CentOS 6 VMware template, which needs a bit of preparation. Namely:
- The SSH public key of the Cloud Provisioner host’s root account listed in /root/.ssh/authorized_keys
- VMware-tools installed
- A known static IP address configured *
- No entries in /etc/udev/rules.d/70-persistent-net.rules
- Appropriate entries in /etc/resolv.conf
- NetworkManager disabled
* Of course it’s also possible to use DHCP rather than a pre-determined IP address, as mentioned previously.
Credentials for the VMware Cluster
These are required to let the Cloud Provisioner talk to VMware to create, start, stop VMs and to query the running machine list. We’ve set up a new vCenter user called ‘auto_provisioner’. The credentals are added to the root user’s ~/.fog file, which looks like this:
Note: We have to specify the Windows domain name in the username field. This was not mentioned in any of the official documentation.
The vsphere_expected_pubkey_hash is returned by vCenter and allows us to confirm that we’re talking to the correct host.
Method
Before creating a new VM, we’ll need a corresponding Address record added to DNS. Once that’s done, we can run the shell script which will be responsible for provisioning a new host. This complete method for provisioning a new machine will only require two simple steps:
- Create new DNS entries for the hosts to be provisioned.
- Run the provisioning script once per node.
The script we’re going to write will do a lot of things for us. Here’s what it has to do…
-
Startup checks:
- Check we’re running as the correct user.
- Validate command line parameters.
- Check the new host’s DNS entry is in place (not strictly necessary, but it ensures this step is completed).
- Check that nothing responds to the temporary provisioning IP address.
- Check that nothing responds to the target IP address.
- Check whether an agent certificate for the new host already exists (it shouldn’t).
- Clone the specified template and wait for the new VM to boot.
- Install puppet agent, which will automatically send a certificate signing request to the master.
- Sign the certificate for the new node.
- Update the ‘environment’ setting in the agent’s puppet.conf.
- Set the ‘role’ fact.
- Reconfigure the network (scripted manipulation of config files)
- Reboot
One the machine has rebooted, and assuming that the network reconfiguration was successful, the puppet agent should run and configure everything the way we want it.
But first, we’ll run through all the steps manually to test the concept…
Manual Implementation
Creating the new VM from a Template
Installing the Puppet Agent
This is the command we want to run to install the Puppet agent:
Here’s the output:
If we then check the Puppet Console, we see there’s a new node certificate waiting to be signed! Alternatively, we can check for pending certificate signing requests from the command line on the Puppet Master:
Signing the Certificate
Since we’re running everything from the Puppet Console machine, we’ll want to sign the new agent’s certificate from this machine too. It’s possible to do remote certificate signing, but we have to configure a few things first…
Remote Certificate Signing
Before we can remotely sign certificate requests, we need to:
- Generate a new certificate on the Puppet Console host.
- Sign the new certificate on the Puppet Master.
- Configure ACLs on the master to allow certificate signing from this host.
On the CP control node:
On the master, we can list and then sign the new certificate:
Again on the master, add an ‘allow’ line to the relevant ACL in Puppet’s $confdir/auth.conf file:
So now, on the CP control node, we can request a list of certificate signing requests by specifying the name of the newly-signed certificate:
Brilliant! So to sign the certificate, we can do this:
To remove this certificate between test runs, we need to revoke it on the master:
More info on these commands can be found here:
Configuring the Network
So the quick and dirty way of configuring the network is to use sed to edit the network files directly.
As with most things there’s more than one way to do it, and I expect there’s a cleverer way than this, but it works fine for now.
Rebooting
Once the network is all configured, it makes sense to reboot before going any further. To do this, we call ‘puppet node_vmware stop’ and then ‘puppet node_vmware start’.
When the node comes back up and the Puppet agent is running, it will immediately contact the puppet master, collect a catalog and start making the requested changes to the system.
Automatic Implementation
I’ve written a bash shell script which ties all this together in a single command. The script is called ‘hatch’, and lives on the Puppet Console host. It’s inspired by a really useful script by Ger Apeldoorn.
Another script called ‘mkbrood’ is capable of using the hatch script to provision multiple hosts according to instructions in a configuration file (a bit like Amazon’s Cloud Formation facility).
Both scripts can be found on GitHub!
Now, here’s how to use them…
Provisioning Multiple Nodes
The hatch script has a wrapper script called ‘mkbrood’, written in Perl. A YAML configuration file describes the hosts to be built. The script reads the file and runs ‘hatch’ for each node described.
Note that a valid environment with the same name must exist in the puppet configuration before running this script.
A typical platform configuration file might look like this: