Puppet Without Root: A Real-Life Example

I work at UTi Worldwide, a freight forwarding company. I do not perform the traditional role of system administrator, but handle application-level deployment. Unlike at my previous gigs, our team does not have root access on the systems we are responsible for managing.

Still, we want to use configuration management to improve our workflow. We have been using open source Puppet without root privileges for about a year. I presented some of our techniques at Puppet Conf 2013.

With the release Puppet Enterprise 3.2, PE customers can run the puppet agent as a non-root user. The only requirement is that the administrator must install the software, which installs to /opt. This makes it much easier to get started with using Puppet without root. What we run at UTi is a hand-crafted source installation of the Puppet open source software. Puppet Open Source can also be installed from packages and then run as a non-root user.

What I want to demonstrate today is what Puppet code can look like in a rootless environment.

Let’s take a look at the typical file-package-service Puppet pattern from a rooted environment:

class archiva {    

  # package
  package { 'archiva':
	ensure => latest,
  } ->    

  # file
  file { '/etc/archiva/wrapper.conf':
	ensure  => file,
	content => template('archiva/wrapper.conf'),
  } ->    

  # service
  service { 'archiva':
	ensure   => running,
	enabled  => true,
  }    

}

What does this look like in a rootless environment? How do we express the three components of the package-file-service pattern?

We use an exec resource to unpack a tarball in lieu of the package type. We use the file type, but will use extra parameters. And we use the service type, but we will manually specify the start, stop, and status commands.

First we will accept some parameters to this class. We need to have a fully qualified path under which to install the software, $install_root below. Since we don't have a package manager, we'll need a directory full of tarballs to act as a package repo. In our environment, we accomplish this through a shared NFS directory containing lots of tarballs. We pass the path of this directory ($src_root below) into the class. We also set defaults.

# class to install and configure archiva
# http://archiva.apache.org/index.cgi
class archiva (
  $install_root = "/opt/app/${::id}/opt",
  $src_root = "/software/install/installers",
){
...

Note that we use the id fact to get the name of the user running the puppet agent. There is not a native fact for the group of the Puppet user. The following Fact definition adds $::puppet_user $::puppet_group and $::puppet_user_home variables as facts.

require 'etc'
Facter.add("puppet_user") do
  confine :kernel => "Linux"
  setcode do
	Etc.getpwuid(Process.uid).name
  end
end

Facter.add("puppet_group") do
  confine :kernel => "Linux"
  setcode do
	Etc.getgrgid(Process.gid).name
  end
end    

Facter.add("puppet_user_home") do
  confine :kernel => "Linux"
  setcode do
	Etc.getpwuid(Process.uid).dir
  end
end    

Add this code where you would normally add facts — for instance, lib/facter/rootless.rb under a module root directory.

Second we use an exec to untar the tarball instead of the package type. Tarballs, git repositories, and rpm2cpio execs can all be used in place of the package type.

# package
exec { 'untar-archiva':
    command => "tar -xvzf ${src_root}/archiva.tar.gz",
    creates => "${install_root}/archiva",
    cwd     => $install_root,
}  ->

This command will be run as the current puppet user. The untar will make sure all permissions are set properly.

Third we create the configuration file:

  ...
# file
file { "${install_root}/archiva/conf/wrapper.conf":
  ensure  => present,
  owner   => $::puppet_user, # could use ::id here
  group   => $::puppet_group,# could (probably) use ::id here
  content => template('archiva/wrapper.conf'),
} ~>
...    

Puppet does not default to setting owner and group to the user running Puppet. This means we have to explicitly set the user and group of the file. Aside from that, the file type behaves quite well in a rootless environment.

The file type works by placing a version of the expected file as .puppettmp next to the file being managed, then copying the .puppettmp version into place. This requires write access to the directory containing the file being managed. One workaround is to use an exec resource to use the shell and > operator to overwrite the managed file.

Fourth, we create the service definition. Services can be managed neatly with Puppet using the verbose service resource shown below:

...
# service
service { 'archiva':
  ensure     => running,
  provider   => base,
  enable     => true,
  hasstatus  => true,
  hasrestart => true,
  start      => "${install_root}/archiva/bin/archiva start",
  stop       => "${install_root}/archiva/bin/archiva stop",
  restart    => "${install_root}/archiva/bin/archiva restart",
  name       => 'archiva',
}    

The ability to directly specify the commands to run to start-stop-status a service makes the service resource integrate well into rootless environments. If we were restarting a service through sudo, we could put the full sudo command into the start, stop, and status parameters and it would work fine.

With this we have developed a class that can install, configure, and manage an application in a rootless environment. This can be extended to handle version numbers, arbitrary configuration options, and more. I often use the file_line type from puppetlabs-stdlib to add environment variables to my .bashrc (such as JAVA_HOME), which are then picked up in my service definitions.

Using Puppet without root isn't as clean as regular Puppet use, and it can be troublesome to get working. However, if you're deploying software without root already, chances are good you already have some gross hacks in place right now. Moving to this system gives you the three major strengths of using Puppet:

  • speed of deployment of configuration
  • consistency across systems
  • a system of record for configuration information

Armed with these three things, your team can move faster, do more, and respond to the needs of the business better.

I encourage anyone working with this kind of setup to reach out to me directly. I am on freenode #puppet as nibalizer and my email address is krum.spencer at gmail.com.

One last word about certificates: You'll want to specifically set the certificate name. I use user-fqdn, such as appserv-machine12.go2uti.com. The PE docs are recommending user.fqdn like appserv.machine12.go2uti.com. So long as you don't just use fqdn, you'll probably be fine, but I like using hyphens because they are unambiguous with subdomains.

Spencer Krum is a Linux and application administrator with UTi Worldwide and a co-author of Pro Puppet. You can hear Spencer and co-author William Van Hevelingen talking about their book in this podcast.

##Learn More

Puppet sites use proprietary and third-party cookies. By using our sites, you agree to our cookie policy.