Deploying nginx with Puppet

Deploying nginx with Puppet

By David Mytton,
CEO & Founder of Server Density.

Published on the 9th February, 2016.

Back in 2013 we wrote about our experience deploying Nginx with Puppet and how we transitioned from Pound.

As you would expect, Puppet has evolved since then. In fact, deployments are becoming bigger and more intricate by the day. To cater for that complexity, it’s necessary to abstract as much of our automation code as possible, grouping it around modules and using inheritance on classes and nodes.

That is a good first step, but it may not suffice in scenarios where we need to reuse 3rd party modules or make our own modules reusable in other environments. This is what led to the creation of the role and profiles design pattern.

Nodes are assigned a role class, which is equivalent to a high level task, such as a web or mail server. Within these classes we have profile classes that correspond to: technology-specific configurations (for things like Nginx or Apache), monitoring, backups, et cetera. This helps keep things organized and therefore easier to manage.

Craig Dunn wrote first about roles and profiles and Adrien Thebo compared them to Legos.

In addition to new design patterns, new tools have also been introduced to further abstract configuration management. Hiera, for example, stores configuration data outside Puppet manifests, the code that holds configuration logic.

Puppet nginx role/Profiles

When people embark on their DevOps journey, having good end-to-end examples to refer to, is invaluable. What we wanted was a template of sorts. An example of how to deploy Nginx with Puppet, using roles and profiles, together with Hiera. We couldn’t find any that met those requirements, so we decided to write one.

We endeavoured to follow best practices where possible. For simplicity sake, we’ve skipped things like integrating a version control system, proper 3rd party modules lifecycle maintenance or using Puppet environments.

Requirements

We assume you have a working Puppet setup. Either a single standalone server where you can run puppet apply manifests/site.pp, or a full-blown Puppet master server  together with a server where you will deploy Nginx.

Using Puppet with Hiera to deploy Nginx

The following is an example deployment of Nginx (and PHP-FPM) on Ubuntu 14.04 or later, using Puppet3, Hiera, along with the role and profiles pattern we discussed earlier on. We will be using the popular jfryman/puppet-nginx and mayflower/puppet-php modules for that.

First step is to configure Hiera by configuring /etc/puppet/hiera.yaml:


File: hiera.yaml
----------------

---
:backends:
  - yaml
 
:hierarchy:
  - "nodes/%{::fqdn}"
  - "roles/%{::role}"
  - common
 
:yaml:
  :datadir: /etc/puppet/hiera/

:logging:
  - console

Hiera does a lookup of your YAML configuration under the datadir. It first searches for a file with the hostname (fqdn), then one with the assigned role, and then a common file for all your servers. Keep in mind that every time you edit this file (shouldn’t be too often) you have to restart your Puppet server (if running inside Passenger, just restart Apache: sudo service apache2 restart).

The hostname YAML file (in my case /etc/puppet/hiera/nodes/nodefqdn.yaml) is where the specifics of this host are stored:


File: nodefqdn.yaml
-------------------

---
roles:
   - roles::www

vdomain: example.com

nginx::config::vhost_purge: true
nginx::config::confd_purge: true

nginx::nginx_vhosts:
  "%{hiera('vdomain')}":
    ensure: present
    rewrite_www_to_non_www: true
    www_root: "/srv/www/%{hiera('vdomain')}/"
    try_files:
      - '$uri'
      - '$uri/'
      - '/index.php$is_args$args'

nginx::nginx_locations:
  'php':
    ensure: present
    vhost: example.com
    location: '~ .php$'
    www_root: "/srv/www/%{hiera('vdomain')}/"
    try_files:
      - '$uri'
      - '/index.php =404'
    location_cfg_append:
      fastcgi_split_path_info: '^(.+\.php)(.*)$'
      fastcgi_pass: 'php'
      fastcgi_index: 'index.php'
      fastcgi_param SCRIPT_FILENAME: "/srv/www/%{hiera('vdomain')}$fastcgi_script_name"
      include: 'fastcgi_params'
      fastcgi_param QUERY_STRING: '$query_string'
      fastcgi_param REQUEST_METHOD: '$request_method'
      fastcgi_param CONTENT_TYPE: '$content_type'
      fastcgi_param CONTENT_LENGTH: '$content_length'
      fastcgi_intercept_errors: 'on'
      fastcgi_ignore_client_abort: 'off'
      fastcgi_connect_timeout: '60'
      fastcgi_send_timeout: '180'
      fastcgi_read_timeout: '180'
      fastcgi_buffer_size: '128k'
      fastcgi_buffers: '4 256k'
      fastcgi_busy_buffers_size: '256k'
      fastcgi_temp_file_write_size: '256k'
    
  'server-status':
    ensure: present
    vhost: "%{hiera('vdomain')}"
    location: /server-status
    stub_status: true
    location_cfg_append:
      access_log: off
      allow: 127.0.0.1
      deny: all

serverdensity_agent::plugin::nginx::nginx_status_url: "http://%{hiera('vdomain')}/server-status"

nginx::nginx_upstreams:
  'php':
    ensure: present
    members:
      - unix:/var/run/php5-fpm.sock

php::fpm: true

php::fpm::settings:
  PHP/short_open_tag: 'On'

php::extensions:
    json: {}
    curl: {}
    mcrypt: {}

php::fpm::pools:
  'www':
    listen: unix:/var/run/php5-fpm.sock
    pm_status_path: /php-status

And to finish the Hiera part, we can add some configuration common to all servers, like the Server Density monitoring credentials, in the /etc/puppet/hiera/common.yaml file:


File: common.yaml
-----------------

serverdensity_agent::sd_account: bencer
serverdensity_agent::api_token: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

With the Hiera side of things complete, we can now configure the main Puppet manifest. This is how /etc/puppet/manifests/site.pp should look like:


File: site.pp
-------------

node default {
  hiera_include('roles')
}

This instructs Puppet to include the roles classes defined in the role section we added in the YAML file. We therefore don’t need all the node definitions in our .pp files code anymore (so things stay clean and awesome).

To make all this possible, we need to create the necessary classes for roles and profiles. The roles are created as an additional module under /etc/puppet/modules/roles. Within that folder we need to create another manifest folder with the role definition. In our case, it would be /etc/puppet/modules/roles/manifests/www.pp:


File: www.pp
------------

class roles::www {
  include profiles::nginx
  include profiles::php
  include profiles::serverdensity
  class { 'serverdensity_agent::plugin::nginx': }
}

This class includes the different profiles. Here it’s the Nginx profile, the PHP profile and the Server Density agent profile. In addition we call the class that installs the Server Density Nginx plugin.

Nginx monitoring

This is Server Density monitoring Nginx in a LEMP stack setup. By the way, if you are looking for a hosted monitoring tool that integrates with Puppet, you should sign up for a 2-week trial of Server Density.

Then the profiles module needs to define these 3 previous profiles. What these profile classes do is call the 3rd party modules we are using, getting the configuration specifics from Hiera.

The Nginx profile class is defined on /etc/puppet/modules/profiles/manifests/nginx.pp:


File: nginx.pp
--------------

class profiles::nginx {
  class{ '::nginx': }
}

The PHP profile class is defined on /etc/puppet/modules/profiles/manifests/php.pp:


File: php.pp
------------

class profiles::php {
  class{ '::php': }
}

And finally the Server Density agent profile class is defined on /etc/puppet/modules/profiles/manifests/serverdensity.pp:


File: serverdensity.pp
----------------------

class profiles::serverdensity {
  class{ '::serverdensity_agent': }
}

To finish, we need to install the modules we are using from the Puppet forge:

sudo puppet module install jfryman-nginx --modulepath /etc/puppet/modules
sudo puppet module install mayflower-php --modulepath /etc/puppet/modules
sudo puppet module install serverdensity-serverdensity_agent --modulepath /etc/puppet/modules

And we are done. Wait for the next Puppet run to apply changes or do it now in your target server with:

sudo puppet agent --test --debug

Further Steps

Keep your modules up to date

We installed 3rd party modules with the Puppet module subcommand. This was easy and simple. It was also suboptimal. This approach cannot handle conflicts between module dependencies. Modules might be outdated in the forge, and different versions may be fetched when updates are pushed. This can lead to version mismatch and other incompatibilities.

librarian-puppet is one way to deal with those challenges. It handles module maintenance (install, update, remove) with strict dependency checking. Sometimes, strict dependency checking can be too hard. That’s why librarian-puppet-simple was created. This is a cut down version of librarian-puppet (it includes no dependency checking).

One further step is r10k. It handles Puppet repositories at scale, using git branches and Puppet environments. Puppet infrastructure with r10k and How to build a Puppet repo using r10k with roles and profiles are great guides here.

Puppet Environments

Environments allow different configurations over the same Puppet code base. They are typically used when we work with more than one scenario, for example: development, QA, staging and production. Puppet environments are useful in any scenarios where the number of nodes might vary or configurations change (example: using a standalone database server for development and a replicated cluster for production).

Summary

Deploying Nginx (and PHP-FPM) with Puppet shouldn’t be that difficult. Existing modules make our life easier because we don’t have to reinvent the wheel. Wrapping them around role/profiles patterns allows customisation with no Puppet coding whatsoever (making use of Hiera).

What about you? Have you started using role/profiles or any other Puppet design patterns? How do you make your code reusable across different environments and projects?

Let us know in the comments or with pull requests against this GitHub repository.

Everything we know about NGINX: the eBook

We’ve worked with NGINX for more than 7 years.
So we thought it was time we wrote a book about it. It’s packed with insights, tricks, and pretty much everything we've learned from using NGINX.

Fill in the form below to get your free copy!

Help us speak your language. What is your primary tech stack?

What infrastructure do you currently work with?

Articles you care about. Delivered.

Help us speak your language. What is your primary tech stack?

Maybe another time