At Container Solutions we're big fans of Arnold Schwarzenegger. His 6 rules of success are highly motivational to us and we keep them in mind in our daily work. Of course we like to joke about them a bit ("Sleep faster!") but there are times when they really do drive our motivation. Like the time when I was asked to provide Puppet scripts for setting up Artifactory. To me this seemed like an excellent opportunity to try a proper Puppet master-slave setup in Docker containers, something I have been wanting to do for a while since I came across James Turnbull's presentation from last year's PuppetConf. I asked my colleagues how to go about it and they frowned and said I don’t like it. It’s not nice. You shouldn’t do that. It can’t be done. Which only made me more determined, because it reminded me of rule #4: Don't listen to the naysayers!
There's a few ways to go about this. Of course you can do the whole thing in one container with a simple puppet apply
but that's not really any fun and it doesn't reflect the desired setup with a master and slave. The other option is to create a container with a master and another with a slave. The Puppet master part is relatively easy if you follow the directions:
FROM centos:7
MAINTAINER thijs.schnitger@container-solutions.com
RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm
RUN yum -y install puppet-server hostname tar
ADD etc/puppet/autosign.conf /etc/puppet/autosign.conf
CMD puppet master --verbose --no-daemonize
EXPOSE 8140
I added the autosign because I don't want to manually sign every cert for each client that connects. The file just contains a single '*'. The image from this Dockerfile is also available on the Docker hub. For our purpose, installing Artifactory, we need a few modules so I decided to create a new image from the basic puppetmaster:
FROM containersol/puppetmaster
MAINTAINER thijs.schnitger@container-solutions.com
RUN puppet module install puppetlabs-java
&& puppet module install puppetlabs-postgresql
For the agent we have 2 choices: you can either run puppet agent -t
at runtime which goes against the idea of immutable containers, or run it at buildtime like James Turnbull suggests. The problem with CentOS7 however is that it uses systemd which needs to run in a privileged container. Running privileged containers at buildtime is still an issue, so I decided to go for the runtime Puppet build.
This means we first need a generic Puppet slave image (also available on the Docker Hub):
FROM centos:7
MAINTAINER thijs.schnitger@container-solutions.com
RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm
RUN yum -y install puppet hostname
CMD ["/sbin/init"]
You need to put init
as the command to run, because at some point Puppet will try to start services using systemd. I guess you could either add puppet agent -t
or start the agent in daemon mode and wait for it to fire, but I decided to execute the puppet agent -t
manually.
Then I used docker-compose to spin up the containers with the proper settings. I add the manifests directory and my module directory as a volume at runtime, so I can edit the nodes and scripts without restarting the containers. The slaves need privileged mode. The documentation for the CentOS image seems to be a bit outdated, which is confusing, because the steps listed there don't seem to be necessary anymore. My slave ran just fine without either CAP_SYS_ADMIN or the cgroups mounted.
puppet:
build: puppetmaster/
hostname: puppet
volumes:
- /path/to/manifests:/etc/puppet/manifests
- /path/to/modules/artifactory:/etc/puppet/modules/artifactory
postgresql:
image: containersol/puppetslave
hostname: postgresql
privileged: true
links:
- puppet:puppet
artifactory:
build: puppetslave/
hostname: artifactory
privileged: true
links:
- puppet:puppet
- postgresql:postgresql
After docker-compose up
, exec into the postgresql container: docker exec -it artifactory_postgresql_1 /bin/bash
and do puppet agent -t
. You'll see the Puppet master complain it can't resolve the address of the slave to a hostname, but it returns the config for our postgresql node anyway. I guess after it finishes, you could probably commit the image and ship it, but I didn't go that far (yet).
The Puppet scripts are like this:
class artifactory::postgresql {
class { 'postgresql::globals':
encoding => 'UTF8',
locale => 'en_US.UTF-8'
}
class { 'postgresql::server':
postgres_password => 'postgres',
locale => 'en_US.UTF-8',
ip_mask_allow_all_users => '0.0.0.0/0',
listen_addresses => '*',
ipv4acls => ['local all all md5'],
service_provider => 'systemd'
}
postgresql::server::db { 'artifactory':
user => 'artifactory',
password => postgresql_password('artifactory', 'youllneverguess'),
}
}
class artifactory {
package { 'rsync':
ensure => installed
}
package { 'java-1.8.0-openjdk':
ensure => installed
}
package { 'Artifactory':
ensure => installed,
source => '/tmp/artifactory.rpm',
provider => 'rpm',
install_options => '-Uvh',
require => [ File['/tmp/artifactory.rpm'], Package['rsync','java-1.8.0-openjdk'] ]
}
file { '/tmp/artifactory.rpm':
source => 'puppet:///modules/artifactory/artifactory-powerpack-rpm-3.8.0.rpm',
}
}
And then we define the nodes like this in the manifests directory:
node artifactory {
require 'artifactory::artifactory'
}
node postgresql {
require 'artifactory::postgresql'
}
As you can see, the scripts still require some work, like starting Artifactory and applying the license and configuration. But the basic idea is clear and it is working, a Puppet master-slave setup in containers!