Building a Puppetmaster with Docker

This is based on the README I wrote for the macadmins/puppetmaster image.

Puppet is an industrial-strength cross-platform configuration management engine. Though you’ll find lots of existing Puppetmaster images on the Docker registry, this one will serve as the baseline for other expanded uses of Puppet – such as using it with Munki and SSL client certificates.

This is a walkthrough for building a Puppetmaster Docker container.

Building Puppetmaster into a Docker container:

Let’s start with our Dockerfile:

FROM centos:centos6

MAINTAINER nmcspadden@gmail.com

ENV PUPPET_VERSION 3.7.3

RUN rpm --import https://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs && rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
RUN yum install -y yum-utils && yum-config-manager --enable centosplus >& /dev/null
RUN yum install -y puppet-$PUPPET_VERSION
RUN yum install -y puppet-server-$PUPPET_VERSION
RUN yum clean all
ADD puppet.conf /etc/puppet/puppet.conf
VOLUME ["/opt/puppet"]
RUN cp -rf /etc/puppet/* /opt/puppet/
VOLUME ["/opt/varpuppet/lib/puppet"]
RUN cp -rf /var/lib/puppet/* /opt/varpuppet/lib/puppet/
EXPOSE 8140
ENTRYPOINT [ "/usr/bin/puppet", "master", "--no-daemonize", "--verbose" ]

FROM centos:centos6
This is based on a CentOS 6 container, for no other reason than that I’m already familiar with CentOS 6. I could just as easily have used Ubuntu or Debian or any of the other base image variants. I chose CentOS 6 over 7 due to major changes in 7 (such as the inclusion of systemd) that I’m not quite familiar with.

ENV PUPPET_VERSION 3.7.3
This makes it easy for me to enforce specific versioning of Puppet in my Dockerfile. As of writing time, Open-source Puppet is on 3.7.3, so that’s what we’ll install. It also means that future builds of this Dockerfile won’t suddenly change or update Puppet without specific admin intervention. It also means that we can easily update the Puppet version by only making one change in the Dockerfile.

RUN rpm --import https://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs && rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm

As per Puppet’s installation instructions, we need to add the GPG key for Puppetlab’s yum repo, and then install the yum repo to get the updated Puppet from.

RUN yum install -y yum-utils && yum-config-manager --enable centosplus >& /dev/null
RUN yum install -y puppet-$PUPPET_VERSION
RUN yum install -y puppet-server-$PUPPET_VERSION
RUN yum clean all

Install the yum utils, install puppet, install the puppet server, clean up after ourselves.

ADD puppet.conf /etc/puppet/puppet.conf
Add in the Puppet configuration file. That will be explored later.

VOLUME ["/opt/puppet"]
RUN cp -rf /etc/puppet/* /opt/puppet/
VOLUME ["/opt/varpuppet/lib/puppet"]
RUN cp -rf /var/lib/puppet/* /opt/varpuppet/lib/puppet/

Here, we expose two volumes for sharing – /opt/puppet/ and /opt/varpuppet/lib/puppet/. These are important, as we’ll configure Puppet to use these for configuration and dynamic data in puppet.conf. That way, we can share this out to data-only containers so we don’t lose anything if we ever remove the Puppetmaster container.

In addition to creating those two directories, we’re also copying the contents from the default directories for Puppet (/etc/puppet/ and /var/lib/puppet/) into our new alternate directories, so they’ll be pre-populated.

EXPOSE 8140
Puppet runs on port 8140, so we need that port available to the outside world.

ENTRYPOINT [ "/usr/bin/puppet", "master", "--no-daemonize", "--verbose" ]
The entrypoint starts Puppet in master mode, with verbose logging, as the primary process in the container. This is what allows us to run the container in detached mode, and easily check the logs.

The Puppet Configuration file:

[agent]
    certname        = puppetmaster
    pluginsync      = true

[master]
    certname        = puppet
    confdir         = /opt/puppet
    vardir	    = /opt/varpuppet/lib/puppet
    basemodulepath  = $confdir/site-modules:$confdir/modules:/usr/share/puppet/modules
    factpath        = $confdir/facts:/var/lib/puppet/lib/facter:/var/lib/puppet/facts
    autosign        = true
    hiera_config    = $confdir/hiera.yaml
    rest_authconfig = $confdir/auth.conf
    ssldir          = $vardir/ssl
    csr_attributes  = $confdir/csr_attributes.yaml

The important things about the Puppet.conf to notice:

  1. confdir and vardir are set to custom directories located in /opt/. We shared these volumes earlier with the VOLUME directives in the Dockerfile, so that means this data will exist in a shareable form that can be linked to other Docker containers.

  2. autosign is true. That means all client certificates will be automatically signed on request. This makes a fine default for testing, but for production use, we’ll want to change this.

  3. csr_attributes is set to a file called “csr_attributes.yaml” which exists in in /opt/puppet/. This isn’t necessary for this particular demo, but it’ll play a part in the next iteration of our Puppet docker container.

You can build this container yourself (if you git clone the project) using:
docker build -t name/puppetmaster .
or you can pull the automated build from the Docker registry:
docker pull macadmins/puppetmaster

To use this container:

As always, we want a data-only container to keep all of Puppet’s configuration and dynamic data in. This is especially important as we need to preserve the Puppet certificates so that they’re not lost if the Puppetmaster container is removed:

docker run -d --name puppet-data --entrypoint /bin/echo macadmins/puppetmaster Data-only container for puppetmaster

Now run the container:
docker run -d --name puppetmaster -h puppet -p 8140:8140 --volumes-from puppet-data macadmins/puppetmaster
Here, we’ve set the hostname to “puppet.” The puppet agent will always try to reach the puppet master via DNS at “puppet”, so we’ll need to make that happen in DNS. As explained earlier, we’re mapping port 8140 to the localhost at 8140.

There’s a critical step that needs to happen next:
docker exec puppetmaster cp -Rf /etc/puppet /opt/
We need to populate the Puppet configuration directory with all the of the content in /etc/puppet/. Because of the order in which puppet.conf is read, /etc/puppet is populated with the default Puppet setup before it discovers that there’s a new confdir directive. Thus, /opt/puppet, while being used as the confdir for all Puppet configurations, does not start out with content in it. We need to fix that manually.

Puppet has started, let’s see a list of certs:
docker exec puppetmaster puppet cert list -all
Only one certificate exists so far – the puppetmaster itself.

Puppetizing a client:

I did all this on an OS X 10.10.1 VM, but this will work on any client.

  1. Install Puppet, Hiera, and Facter onto a client.

  2. Add the IP of your Docker host to /etc/hosts (or configure DNS so that your Docker host is reachable at “puppet”). For example, if your Docker host IP is 10.0.0.1:
    10.0.0.1 puppet

  3. Test puppet on client running as root:
    # puppet agent --test
    You should see the certificate request being generated and autosigned.

  4. Verify cert signing on puppetmaster docker container:
    docker exec puppetmaster puppet cert list -all
    You should see the certificate for the client’s hostname in the list.

  5. On the client, run:
    # puppet agent --test again to verify that cert exists and was confirmed.

Conclusion

We’ve got a working Puppet master in a Docker container! This will make a baseline Docker image we can expand upon.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s