At work, we’ve recently changed how we build our bootstrap package to having the main code that connects a Mac to our Puppet infrastructure pulled down from GitHub when the client boots up for the first time.
This might sound like madness to you. Why would anyone want to do this? We had two main issues to solve:
I got sick of rebuilding our images every time our bootstrap script changed.
Our engineers got sick of downloading the latest version of our package every time they thin / no-imaged a Mac.
Why would our script change so much? In our case, it is to install the latest versions of Puppet and Facter. This isn’t strictly necessary, as we update Puppet and Facter with Munki, but occasionally there will be something in our Puppet config that requires a specific version - for example, when we started configuring usernames on 10.8 Macs with Puppet, the salt parameter was introduced. This required Puppet 3.0.2-ish or higher - which meant that any NetRestore image or old package that contained a version of Puppet lower than this would fail, and the engineer on site was in for a world of pain.
Ok, I’m convinced.
I’ve put an example up on GitHub. This is a sanitised version of the bootstrap script we use at pebble.it. All we do in the script that gets deployed to the client is set the address of our Puppet server and then pull the rest of the script from GitHub - if you don’t need to set any variables, you could do all of the work in the remote script.
So this can be used with all of the deployment methods we use at pebble.it (imaging, createOSXinstallPkg and no imaging), we do the actual work in a script that is triggered by a launch daemon, so we can be sure we’re a) performing the work in a full OS X environment (we need Python to be available for our script) and that we’re running it on the boot volume of the client Mac (we need the serial number, and this could be installed via Target Disk Mode when no-imaging.
So when the bootstrap script needs to be updated, rather than rebuilding the package and distributing it to engineers and baking it into images, the workflow becomes:
install_puppet.py downloads and installs the correct versions of Puppet and Facter and configures Puppet.
Puppet downloads, installs and configures Munki along with all of the other configuration.
This clearly isn’t required or suitable for all types of script - but if you have a package that is frequently updated and you have staff installing it by hand, this is a relatively simple way to make sure they’ve got the latest version at all times.
Facter is what gives Puppet it’s brains. It collects information about the computer it is run on and then passes it to the Puppet Master for use in manifests and can optionally be stored. I know, it doesn’t sound like the most earth shattering revelation of all time, but stop for a moment. Every time your current scripts need to taget a specific OS version or a machine with a certain bit of hardware, you need to code it directly into the script. If the method of extracting that information changes, you need to modify every single script that uses that method. With Facter, you’re editing one file, which is always up to date on the client. Anyway, that’s enough waffle from me. Let’s get started.
We’ve made quite a bit of progress with our Puppet install. We’ve already made Puppet do something useful with setting up an admin user, but let’s get back to being lazy - let’s get someone else to write the code.
Before reading this post, you really need to read part 1, part 2 and part 3 of the series.
Modules are little pre-built bits of Puppet code. They’re a good example of Puppet’s philosophy of convention over configuration - Puppet will assume your modules follow a set pattern. We’ll be using two of the available folders in modules today: files and manifests. Files are static files that Puppet will copy over to our client machine, and manifests will contain the Puppet code we’ve previously been putting into /etc/puppet/manifests/site.pp - whilst it’s been easy to put code into this file, it can become unwieldy when you have a few nodes to manage.
There are also loads of pre-built modules on the Puppet Forge - it’s one of these modules we’ll be using today.
In this post, we’ll do something pretty much all Mac admins will need to do - set up their admin user. Bust first, a little housekeeping.
Before reading this post, you really need to read part 1, part 2 and most importantly my post on building a Puppet Master with Vagrant. The Puppet Labs provided VM won’t cut it here, we need the latest version of Puppet on our Master. If you are using the same Mac / OS X VM that was previously hooked up to the Puppet Master VM, you will need to run the following command on the client - don’t worry, it will get new certificates from your very own Puppet Master:
sudo rm -rf /var/lib/puppet/ssl
Make sure your test Mac is pointing to the right server - unless you’ve changed your Vagrantfile, your Puppet Master’s IP address will be 192.168.33.10 - you will need to change your test Mac’s /etc/hosts file to reflect this change.
Updates from the previous post: Since the last post was published, Puppet version 3.1 has been released - the main bonus to Mac users is that the Puppet user and group are now created, so the manual Puppet run command is a little shorter. What was this:
sudo puppet agent --test --group 0
Can now be shortened to:
sudo puppet agent --test
As the Puppet user and group now exist, you no longer need to run Puppet as root. This creates another issue (the Puppet user is visible at the login screen despite it not being able to log in), but we’ll get around that in this article. Regardless, you want to install Puppet 3.1. Back to the main event.
First thing’s first, create your admin user. I’ve called mine “Local Administrator”, with a short name of “ladmin” and the very imaginative passord of “password”. Next open up a Terminal window on your puppetclient Mac and issue the following command:
That looks suspiciously like Puppet code. Let’s try it.
With the Vagrant based Puppet Master, the manifests file that previously lived at /etc/puppet/manifests is now located on your Mac at puppet/manifests (as is the modules folder, Vagrant takes care of linking it to the right place on the VM). Open up puppet/manifests/sites.pp in your favourite text editor (for the love of all that’s holy, please don’t use TextEdit. Try TextWrangler, or my current favourite Chocolat).
Save it, and then back on your client perform a Puppet run:
sudo puppet agent --test
Of course nothing has changed - that’s because your client’s configuration is how you have described it in site.pp. Try changing ladmin’s password in system preferences, then perform another Puppet run. You’ll see Puppet change the password right back.
Now we’ve got our Local Admin user working, it might be nice to hide it away from inquisitive users. The first step is to change our Local Admin’s UID to something lower than 500 - I like 404 (nerd joke), and then for good measure, we’ll move the home folder out of /Users and into /var.
When you perform a Puppet run, you’ll notice that your command is run every time, regardless of whether it needs to. We only really need to run that when we change our Local Admin user. To do that, we’ll change two lines. When the Admin User changes, we’ll send a signal to the exec, and we’ll change the exec to only run when it is told to.
Puppet is awesome. Until you deploy some code that worked locally, but for some reason didn’t when you put it onto your Puppet Master. Whoops.
So, you need a testing setup. But Puppet can take a while to keep configuring. Which is where Vagrant comes in. It it a tool which allows you to build virtual machines automatically (currently only with VirtualBox, but VMWare Fusion support is coming very soon). And the best part (for me, anyway) is that it uses Puppet to configure the VM (Puppet to configure your Puppet Master? All too meta for this time of the morning).
Anyway, that’s enough waffle - the Vagrant configuration is up on my GitHub.
If you are following along with my series on getting started with Puppet on OS X, you can replace the Puppet Labs provided VM with this setup (which would be a good idea, as the Enterprise version is a few versions behind the Open Source version, missing some features when managing Macs).
This testing setup includes:
A Puppet Master running using the built in web server (fine for testing, not enough poke for a production server)
Puppet Dashboard (we all love a GUI, right?)
PuppetDB (this will store data about your nodes, and then hooks into the Dashboard to display it)
Once those are installed, cd into the directory where you keep your code (mine lives in ~/Documents/Code), clone the repo and then tell Vagrant to bring the VM up.
git clone https://github.com/grahamgilbert/vagrant-puppetmaster.git
If you don’t have the base box Ubuntu box downloaded, Vagrant will pull it down for you and cache the clean VM for you. It will then make a copy, run the script that installs the latest version of Puppet, then run through the Puppet code that will configure the VM to be a Puppet Master for you. Once the VM is running, you can place your modules and manifests in puppet/modules and puppet/manifests, respectively. The dashboard is accessible at http://192.168.33.10:3000.
This VM is not suitable for production. I’ve made several tweaks to the configuration that makes it easier to test your code, but would be a security risk if used on a production server. Only use this configuration for testing. We’re also installing everything onto one VM - you probably want to separate this out into at least two boxes in production, maybe even three if you have a large deployment. Like I said, the idea here is to quickly set up a testing environment that behaves like our production environment.