14 min read

This article is written by Michael Peacock, the author of Creating Development Environments with Vagrant (Second Edition). Now that we have a good knowledge of using Vagrant to manage software development projects and how to use the Puppet provisioning tool, let’s take a look at how to use these tools to build a Linux, Nginx, MySQL, and PHP (LEMP) development environment with Vagrant.

In this article, you will learn the following topics:

  • How to update the package manager
  • How to create a LEMP-based development environment in Vagrant, including the following:
    • How to install the Nginx web server
    • How to customize the Nginx configuration file
    • How to install PHP
    • How to install and configure MySQL
    • How to install e-mail sending services

With the exception of MySQL, we will create simple Puppet modules to install and manage the software required. For MySQL, we will use the official Puppet module from Puppet Labs; this module makes it very easy for us to install and configure all aspects of MySQL.

(For more resources related to this topic, see here.)

Creating the Vagrant project

First, we want to create a new project, so let’s create a new folder called lemp-stack and initialize a new ubuntu/trusty64 Vagrant project within it by executing the following commands:

mkdir lemp-stack
cd lemp-stack
vagrant init ubuntu/trusty64 ub

The easiest way for us to pull in the MySQL Puppet module is to simply add it as a git submodule to our project. In order to add a git submodule, our project needs to be a git repository, so let’s initialize it as a git repository now to save time later:

git init

To make the virtual machine reflective of a real-world production server, instead of forwarding the web server port on the virtual machine to another port on our host machine, we will instead network the virtual machine. This means that we would be able to access the web server via port 80 (which is typical on a production web server) by connecting directly to the virtual machine.

In order to ensure a fixed IP address to which we can allocate a hostname on our network, we need to uncomment the following line from our Vagrantfile by removing the # from the start of the line:

# config.vm.network "private_network", ip: "192.168.33.10"

The IP address can be changed depending on the needs of our project.

As this is a sample LEMP stack designed for web-based projects, let’s configure our projects directory to a relevant web folder on the virtual machine:

config.vm.synced_folder ".", "/var/www/project", type: "nfs"

We will still need to configure our web server to point to this folder; however, it is more appropriate than the default mapping location of /vagrant.

Before we run our Puppet provisioner to install our LEMP stack, we should instruct Vagrant to run the apt-get update command on the virtual machine. Without this, it isn’t always possible to install new packages. So, let’s add the following line to our Vagrant file within the |config| block:

config.vm.provision "shell", inline: "apt-get update"

As we will put our Puppet modules and manifests in a provision folder, we need to configure Vagrant to use the correct folders for our Puppet manifests and modules as well as the default manifest file. Adding the following code to our Vagrantfile will do this for us:

config.vm.provision :puppet do |puppet|
   puppet.manifests_path = "provision/manifests"
   puppet.module_path = "provision/modules"
   puppet.manifest_file = "vagrant.pp"
end

Creating the Puppet manifests

Let's start by creating some folders for our Puppet modules and manifests by executing the following commands:
mkdir provision
cd provision
mkdir modules
mkdir manifests

For each of the modules we want to create, we need to create a folder within the provision/modules folder for the module. Within this folder, we need to create a manifests folder, and within this, our Puppet manifest file, init.pp. Structurally, this looks something like the following:

|-- provision
|   |-- manifests
|   |   `-- vagrant.pp
|   `-- modules
|       |-- our module
|           |-- manifests
|               `-- init.pp
`-- Vagrantfile

Installing Nginx

Let’s take a look at what is involved to install Nginx through a module and manifest file provision/modules/nginx/manifests/init.pp. First, we define our class, passing in a variable so that we can change the configuration file we use for Nginx (useful for using the same module for different projects or different environments such as staging and production environments), then we need to ensure that the nginx package is installed:

class nginx ($file = 'default') {
 
package {"nginx":
   ensure => present
}

Note that we have not closed the curly bracket for the nginx class. That is because this is just the first snippet of the file; we will close it at the end.

Because we want to change our default Nginx configuration file, we should update the contents of the Nginx configuration file with one of our own (this will need to be placed in the provision/modules/nginx/files folder; unless the file parameter is passed to the class, the file default will be used):

file { '/etc/nginx/sites-available/default':
     source => "puppet:///modules/nginx/${file}",
     owner => 'root',
     group => 'root',
     notify => Service['nginx'],
     require => Package['nginx']
}

Finally, we need to ensure that the nginx service is actually running once it has been installed:

service { "nginx":
   ensure => running,
   require => Package["nginx"]
}
}

This completes the manifest. We do still, however, need to create a default configuration file for Nginx, which is saved as provision/modules/nginx/files/default. This will be used unless we pass a file parameter to the nginx class when using the module. The sample file here is a basic configuration file, pointing to the public folder within our synced folder. The server name of lemp-stack.local means that Nginx will listen for requests on that hostname and will serve content from our projects folder:

server {
   listen   80;
 
   root /var/www/project/public;
   index index.php index.html index.htm;
 
   server_name lemp-stack.local;
 
   location / {
       try_files $uri $uri/ /index.php?$query_string;
   }
 
   location ~ .php$ {
       try_files $uri =404;
       fastcgi_split_path_info ^(.+.php)(/.+)$;
       #fastcgi_pass 127.0.0.1:9000;
       fastcgi_param SERVER_NAME $host;
       fastcgi_pass unix:/var/run/php5-fpm.sock;
       fastcgi_index index.php;
       fastcgi_intercept_errors on;
       include fastcgi_params;
   }
 
   location ~ /.ht {
       deny all;
   }
 
   location ~* .(jpg|jpeg|gif|css|png|js|ico|html)$ {
       access_log off;
       expires max;
   }
 
   location ~* .svgz {
       add_header Content-Encoding "gzip";
   }
}

Because this configuration file listens for requests on lemp-stack.local, we need to add a record to the hosts file on our host machine, which will redirect traffic from lemp-stack.local to the IP address of our virtual machine.

Installing PHP

To install PHP, we need to install a range of related packages, including the Nginx PHP module. This would be in the file provision/modules/php/manifests/init.pp.

On more recent (within the past few years) Linux and PHP installations, PHP uses a handler called php-fpm as a bridge between PHP and the web server being used. This means that when new PHP modules are installed or PHP configurations are changed, we need to restart the php-fpm service for these changes to take effect, whereas in the past, it was often the web servers that needed to be restarted or reloaded.

To make our simple PHP Puppet module flexible, we need to install the php5-fpm package and restart it when other modules are installed, but only when we use Nginx on our server. To achieve this, we can use a class parameter, which defaults to true. This lets us use the same module in servers that don’t have a web server, and where we don’t want to have the overhead of the FPM service, such as a server that runs background jobs or processing:

class php ($nginx = true) {

If the nginx parameter is true, then we need to install php5-fpm. Since this package is only installed when the flag is set to true, we cannot have PHP and its modules requiring or notifying the php-fpm package, as it may not be installed; so instead we need to have the php5-fpm package subscribe to these packages:

   if ($nginx) {
       package { "php5-fpm":
         ensure => present,
         subscribe => [Package['php5-dev'], Package['php5-curl'], Package['php5-gd'], 
Package['php5-imagick'], Package['php5-mcrypt'], Package['php5-mhash'],
Package['php5-pspell'], Package['php5-json'], Package['php5-xmlrpc'],
Package['php5-xsl'], Package['php5-mysql']]        }    }

The rest of the manifest can then simply be the installation of the various PHP modules that are required for a typical LEMP setup:

   package { "php5-dev":
       ensure => present
   }
 
   package { "php5-curl":
       ensure => present
   }
 
   package { "php5-gd":
       ensure => present
   }
 
   package { "php5-imagick":
       ensure => present
   }
 
   package { "php5-mcrypt":
       ensure => present
   }
 
   package { "php5-mhash":
       ensure => present
   }
 
   package { "php5-pspell":
       ensure => present
   }
 
   package { "php5-xmlrpc":
       ensure => present
   }
 
   package { "php5-xsl":
       ensure => present
   }
 
   package { "php5-cli":
       ensure => present
   }
 
   package { "php5-json":
       ensure => present
   }
}

Installing the MySQL module

Because we are going to use the Puppet module for MySQL provided by Puppet Labs, installing the module is very straightforward; we simply add it as a git submodule to our project with the following command:

git submodule add https://github.com/puppetlabs/puppetlabs-mysql.git provision/modules/mysql

You might want to use a specific release for this module, as the code changes on a semi-regular basis. A stable release is available at https://github.com/puppetlabs/puppetlabs-mysql/releases/tag/3.1.0.

Default manifest

Finally, we need to pull these modules together, and install them when our machine is provisioned. To do this, we simply add the following modules to our vagrant.pp manifest file in the provision/manifests folder.

Installing Nginx and PHP

We need to include our nginx class and optionally provide a filename for the configuration file; if we don’t provide one, the default will be used:

class {
   'nginx':
       file => 'default'
}

Similarly for PHP, we need to include the class and in this case, pass an nginx parameter to ensure that it installs PHP5-FPM too:

class {
   'php':
       nginx => true
}

Hostname configuration

We should tell our Vagrant virtual machine what its hostname is by adding a host resource to our manifest:

host { 'lemp-stack.local':
   ip => '127.0.0.1',
   host_aliases => 'localhost',
}

E-mail sending services

Because some of our projects might involve sending e-mails, we should install e-mail sending services on our virtual machine. As these are simply two packages, it makes more sense to include them in our Vagrant manifest, as opposed to their own modules:

package { "postfix":
   ensure => present
}
 
package { "mailutils":
   ensure => present
}

MySQL configuration

Because the MySQL module is very flexible and manages all aspects of MySQL, there is quite a bit for us to configure. We need to perform the following steps:

  1. Create a database.
  2. Create a user.
  3. Give the user permission to use the database (grants).
  4. Configure the MySQL root password.
  5. Install the MySQL client.
  6. Install the MySQL client bindings for PHP.

The MySQL server class has a range of parameters that can be passed to configure it, including databases, users, and grants. So, first, we need to define what the databases, users, and grants are that we want to be configured:

$databases = {
'lemp' => {
   ensure => 'present',
   charset => 'utf8'
},
}
 
$users = {
'lemp@localhost' => {
   ensure                   => 'present',
   max_connections_per_hour => '0',
   max_queries_per_hour     => '0',
   max_updates_per_hour     => '0',
   max_user_connections     => '0',
   password_hash           => 'MySQL-Password-Hash',
},
}

The password_hash parameter here is for a hash generated by MySQL. You can generate a password hash by connecting to an existing MySQL instance and running a query such as SELECT PASSWORD(‘password’).

The grant maps our user and database and specifies what permissions the user can perform on that database when connecting from a particular host (in this case, localhost—so from the virtual machine itself):

$grants = {
'lemp@localhost/lemp.*' => {
   ensure     => 'present',
   options   => ['GRANT'],
   privileges => ['ALL'],
   table      => 'lemp.*',
   user       => 'lemp@localhost',
},
}

We then pass these values to the MySQL server class. We also provide a root password for MySQL (unlike earlier, this is provided in plain text), and we can override the options from the MySQL configuration file. This is unlike our own Nginx module that provides a full file—in this instance, the MySQL module provides a template configuration file and the changes are replaced in that template to create a configuration file:

class { '::mysql::server':
root_password   => 'lemp-root-password',
override_options => { 'mysqld' => { 'max_connections' => '1024' } },
databases => $databases,
users => $users,
grants => $grants,
restart => true
}

As we will have a web server running on this machine, which needs to connect to this database server, we also need the client library and the client bindings for PHP, so that we can include them too:

include '::mysql::client'
 
class { '::mysql::bindings':
php_enable => true
}

Launching the virtual machine

In order to launch our new virtual machine, we simply need to run the following command:

Vagrant up

We should now see our VM boot and the various Puppet phases execute. If all goes well, we should see no errors in this process.

Summary

In this article, we learned about the steps involved in creating a brand new Vagrant project, configuring it to integrate with our host machine, and setting up a standard LEMP stack using the Puppet provisioning tool. Now you should have a basic understanding of Vagrant and how to use it to ensure that your software projects are managed more effectively!

Resources for Article:


Further resources on this subject:


Packt

Share
Published by
Packt

Recent Posts

Harnessing Tech for Good to Drive Environmental Impact

At Packt, we are always on the lookout for innovative startups that are not only…

2 months ago

Top life hacks for prepping for your IT certification exam

I remember deciding to pursue my first IT certification, the CompTIA A+. I had signed…

3 years ago

Learn Transformers for Natural Language Processing with Denis Rothman

Key takeaways The transformer architecture has proved to be revolutionary in outperforming the classical RNN…

3 years ago

Learning Essential Linux Commands for Navigating the Shell Effectively

Once we learn how to deploy an Ubuntu server, how to manage users, and how…

3 years ago

Clean Coding in Python with Mariano Anaya

Key-takeaways:   Clean code isn’t just a nice thing to have or a luxury in software projects; it's a necessity. If we…

3 years ago

Exploring Forms in Angular – types, benefits and differences   

While developing a web application, or setting dynamic pages and meta tags we need to deal with…

3 years ago