How to start with agentless infrastructure automation using Bolt Apply and Plans

I work on the team that runs Puppet Enterprise (PE) at Puppet (Inc.). We use PE to enforce the configuration of our infrastructure, and maintain the standards we’ve decided on as a team. But sometimes we need to configure servers that don’t exist within the confines of our production continuous integration (CI) infrastructure, and that’s when we turn to bolt to ensure that those servers are configured in a repeatable fashion.

Here’s a scalable way to automate this workflow using Bolt, Apply, and Plans.

Avoiding excess manual work with an agentless approach

We use PE to manage the physical servers that run Openstack, but the VMs (virtual machines) themselves aren’t centrally managed, since this is a sandboxed environment. In this sandboxed environment, there was a problem I wanted to solve. Our users would report issues with their VMs in the Openstack environment, and I didn’t have access to ssh into the VMs that my internal customers were setting up. Even if I did have access to ssh into their vms, I didn’t have historical data about the experience of being on a VM in the environment.

I solved this problem by building two servers in the environment that communicated with each other, using telegraf to send performance data to influxdb. There was shared code between these two servers, and I didn’t want to configure them manually, particularly in an environment that doesn’t have the SLA of production.

To take this solution even further, I used Bolt to create Plans to help me manage these servers quickly.

How to use Bolt Apply

With the introduction of Bolt Apply, Bolt allows you to manage your agentless infrastructure using the same code and modules that you use to manage your infrastructure managed by PE. The first thing to note is that Bolt is opinionated about how you set up your environment, so that you can take advantage of code you write, as well as modules on the Puppet Forge.

These examples are based on users using a unix-based system. After installing Bolt, change to your ~/.puppetlabs/bolt directory, and create a Puppetfile.

forge "http://forge.puppetlabs.com"

mod 'profiles', local: true
mod 'puppetlabs-concat', '5.2.0'
mod 'puppetlabs-stdlib', '5.1.0'
mod 'puppet-telegraf', '2.1.0'

A few things to note:

  • Add any modules that you will be using, in this case, I knew I wanted to use the telegraf module, and the telegraf module was dependent on the concat and stdlib modules.
  • Ensure you add the module you are about to create here with the flag local: true. Bolt will manage your modules from here on out, and if you do not list it as local_true, bolt will delete the code that you wrote, attempting to overwrite it with a module from the forge.

Next, use the command bolt puppetfile install to install all of the modules you listed in your puppetfile, then create a modules directory if one doesn’t exist, and create the directory for your module inside that directory. Bolt supports simpler tasks, which can run in any language you desire, and plans which are more extensible and use the Puppet language, in this case I wanted to use the bolt apply feature which requires puppet plans. I created the puppet plan, telegraf.pp in the directory ~/.puppetlabs/bolt/modules/profiles/plans/telegraf.pp.

plan profiles::telegraf(
  TargetSpec $nodes,
  String[1]  $influxdb_hostname,
  String[1]  $influxdb_password,
) {

  # Install the puppet-agent package if Puppet is not detected.
  # Copy over custom facts from the Bolt modulepath.
  # Run the `facter` command line tool to gather node information.
  $nodes.apply_prep

  # Compile the manifest block into a catalog
  apply($nodes) {

    class { 'telegraf':
      hostname => $facts['networking']['fqdn'],
      logfile  => '/var/log/telegraf/telegraf.log',
      outputs  => {
      'influxdb' => {
          'urls'     => [ "http://${influxdb_hostname}:8086" ],
          'database' => 'telegraf',
          'username' => 'telegraf',
          'password' => $influxdb_password,
          },
      },
      inputs   => {
        'cpu'       => {
          'percpu'   => true,
          'totalcpu' => true,
        },
        'disk'      => {},
        'diskio'    => {},
        'mem'       => {},
        'net'       => {},
        'processes' => {},
        'syslog'    => {
          'server' => 'tcp://:6514',
        },
        'system'    => {},
      },
    }
  }
}

This is the plan which is used to install telegraf on the nodes. In this plan, I am passing in three variables:

$nodes
$influxdb_hostname,
$influxdb_password,

The node variable is populated using the bolt flag --nodes.

Here is an example of how you would apply this bolt plan:

bolt plan run profiles::telegraf --nodes centos@10.234.0.115 --run-as root --tty  influxdb_hostname=hostname.puppet.com influxdb_password=ReallySecureP@ssword

$nodes.apply_prep is what gets the node ready to run puppet on them, though this is an agentless experience, we need to copy over the code which runs puppet apply onto the hosts running this code.

apply($nodes) { is the line that tells bolt you are about to pass Puppet language code to bolt to apply onto the server. You are running it on the nodes you passed into $nodes. This means you can generate this variable in other ways, if you want to develop a node list programmatically.

Note: If this were a more complex infrastructure, I could have used hiera to configure some of the variables.

Try this agentless approach for yourself or learn more

Bolt is a great way to automate the infrastructure in your organization that is detached from your primary Puppet environment. This allows us to get the benefit of code reuse between teams managing infrastructure, while also keeping our code base smaller, and more purpose built.

Mikker is a site reliability engineer at Puppet.

Here are some handy links

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