Skip to main content

NGINX Salt State

TST, Hong Kong

Configuration management with Salt State for an NGINX web proxy.

Nginx State File

To start off our Nginx configuration, we’ll create a directory in this location specific to the software we are configuring. Then create an init.sls file within this directory to get started::

mkdir /srv/salt/nginx
nano /srv/salt/nginx/init.sls

Nginx Package and Service States

First we want to make sure that NGINX is installed on our Minion and the service is running:

nginx/init.sls

nginx:
  pkg:
    - installed
  service.running:
    - watch:
        - pkg: nginx
        - file: /etc/nginx/nginx.conf
        - file: /etc/nginx/sites-available/default

Nginx should automatically reload when the package is updated, when the main configuration file has been changed, or when the default server block file is modified. We can tell Salt to restart the Nginx service when these conditions occur by using watch.

Nginx Configuration File States

We now want to define the contents of the /etc/nginx/nginx.conf file on the master server and upload it to each minion who needs it. We will later create this default configuration file in /srv/salt/nginx/files/etc/nginx:

nginx/init.sls

nginx:
  pkg:
    - installed
  service.running:
    - watch:
        - pkg: nginx
        - file: /etc/nginx/nginx.conf
        - file: /etc/nginx/sites-available/default

/etc/nginx/nginx.conf:
  file.managed:
    - source: salt://nginx/files/etc/nginx/nginx.conf
    - user: root
    - group: root
    - mode: 640

Additionally we need to define the server block that controls how our content will be served with the /etc/nginx/sites-available/default file. The difference here is that this file has to be a Jinja template. This means that we can pull information from each host and construct an appropriate, customized version of the file for each of our web servers:

nginx/init.sls

nginx:
  pkg:
    - installed
  service.running:
    - watch:
        - pkg: nginx
        - file: /etc/nginx/nginx.conf
        - file: /etc/nginx/sites-available/default

/etc/nginx/nginx.conf:
  file.managed:
    - source: salt://nginx/files/etc/nginx/nginx.conf
    - user: root
    - group: root
    - mode: 640

/etc/nginx/sites-available/default:
  file.managed:
    - source: salt://nginx/files/etc/nginx/sites-available/default.jinja
    - template: jinja
    - user: root
    - group: root
    - mode: 640

Now, we just need to create a state for our index.html file which will be the frontpage for our site:

nginx/init.sls

nginx:
  pkg:
    - installed
  service.running:
    - watch:
        - pkg: nginx
        - file: /etc/nginx/nginx.conf
        - file: /etc/nginx/sites-available/default

/etc/nginx/nginx.conf:
  file.managed:
    - source: salt://nginx/files/etc/nginx/nginx.conf
    - user: root
    - group: root
    - mode: 640

/etc/nginx/sites-available/default:
  file.managed:
    - source: salt://nginx/files/etc/nginx/sites-available/default.jinja
    - template: jinja
    - user: root
    - group: root
    - mode: 640

/usr/share/nginx/html/index.html:
  file.managed:
    - source: salt://nginx/files/usr/share/nginx/html/index.html.jinja
    - template: jinja
    - user: root
    - group: root
    - mode: 644

Creating the Master Files

To get the default NGINX configuration files we can install it manually on our Minion using Salt:

salt ubuntuMaster pkg.install nginx

We can now configure our master to allow file transfers from Minions nano /etc/salt/master.d/local.conf:

# Allow minions to push files to the master. This is disabled by default, for
# security purposes.
file_recv: True

Save and systemctl restart salt-master your master. We can now grab the default versions of the files we’ll be managing:

salt ubuntuMaster cp.push /etc/nginx/nginx.conf
salt ubuntuMaster cp.push /etc/nginx/sites-available/default
salt ubuntuMaster cp.push /usr/share/nginx/html/index.html

These files should now be available on the master. The path to these files is recreated within the /var/cache/salt/master/minions/minion_id/files directory. We can copy the directories beneath this location, which represents the file paths on the minion, to our Salt state directory:

cp -r /var/cache/salt/master/minions/ubuntuMaster/files /srv/salt/nginx

find /srv/salt/nginx -printf "%P\n"

  init.sls
  files
  files/etc
  files/etc/nginx
  files/etc/nginx/sites-available
  files/etc/nginx/sites-available/default
  files/etc/nginx/nginx.conf
  files/usr
  files/usr/share
  files/usr/share/nginx
  files/usr/share/nginx/html
  files/usr/share/nginx/html/index.html

NGINX Config

Start by creating a backup of the original configuration and adjust the conf file according to your needs:

cd /srv/salt/nginx/files/etc/nginx
cp nginx.conf nginx.conf.ori
diff nginx.conf nginx.conf.ori

NGINX Default Server

Next, let’s take a look at our default server block template. Start with making a copy of the original file and rename the file to .jinja:

cd /srv/salt/nginx/files/etc/nginx/sites-available
cp default default.ori
mv default default.jinja

Now, we can open the template file to make some changes nano default.jinja. We’ll then use the grains.get execution module function to grab the address associated with the selected interface and use that as the value for the addr variable. We will add this to the very top of the file:

# Set interface to load balancer in production - change interface before using
{%- set interface = 'enp3s0' if salt['grains.get']('env') == 'dev' else 'enp3s0' -%}
{%- set addr = salt['network.interface_ip'](interface) -%}
##
# You should look at the following URL's in order to grasp a solid understanding

Next, we can edit the server block further down in the file. We can use the addr variable we set at the top in the listen and server_name directives. We’ve removed the IPv6 and default server portions to restrict what this block serves:

server {
    listen {{ addr }}:80;

    root /usr/share/nginx/html;
    index index.html index.htm;

    server_name {{ addr }};

    location / {
        try_files $uri $uri/ =404;
    }
}

Save and close the file when you are finished.

NGINX Default Frontpage Template

cd /srv/salt/nginx/files/usr/share/nginx/html

cp index.html index.html.ori
mv index.html index.html.jinja

nano index.html.jinja

At the top, we’ll set another variable using Jinja. We’ll use the grains.get execution module function to grab the minion’s hostname. We’ll store this in the host variable:

{% set host = salt['grains.get']('os-family') -%}
{% set os = salt['grains.get']('os') -%}

<!DOCTYPE html>
<html>
  <head>
  <title>Welcome from {{ host }}</title>
    <body>
    <h1>This is NGINX!</h1>
    <p>{{ os }} Rocks!</p>
    </body>
</html>

Testing the Nginx State File

First, we can use the state.show_sls execution module function to view how Salt will interpret our Nginx state file:

salt ubuntuMaster state.show_sls nginx

ubuntuMaster:
    ----------
    /etc/nginx/nginx.conf:
        ----------
        __env__:
            base
        __sls__:
            nginx
        file:
            |_
              ----------
              source:
                  salt://nginx/files/etc/nginx/nginx.conf
            |_
              ----------
              user:
                  root
            |_
              ----------
              group:
                  root
            |_
              ----------
              mode:
                  640
            - managed
            |_
              ----------
              order:
                  10002
    /etc/nginx/sites-available/default:
        ----------
        __env__:
            base
        __sls__:
            nginx
        file:
            |_
              ----------
              source:
                  salt://nginx/files/etc/nginx/sites-available/default.jinja
            |_
              ----------
              template:
                  jinja
            |_
              ----------
              user:
                  root
            |_
              ----------
              group:
                  root
            |_
              ----------
              mode:
                  640
            - managed
            |_
              ----------
              order:
                  10003
    /usr/share/nginx/html/index.html:
        ----------
        __env__:
            base
        __sls__:
            nginx
        file:
            |_
              ----------
              source:
                  salt://nginx/files/usr/share/nginx/html/index.html.jinja
            |_
              ----------
              template:
                  jinja
            |_
              ----------
              user:
                  root
            |_
              ----------
              group:
                  root
            |_
              ----------
              mode:
                  644
            - managed
            |_
              ----------
              order:
                  10004
    nginx:
        ----------
        __env__:
            base
        __sls__:
            nginx
        pkg:
            - installed
            |_
              ----------
              order:
                  10000
        service:
            |_
              ----------
              watch:
                  |_
                    ----------
                    pkg:
                        nginx
                  |_
                    ----------
                    file:
                        /etc/nginx/nginx.conf
                  |_
                    ----------
                    file:
                        /etc/nginx/sites-available/default
            - running
            |_
              ----------
              order:
                  10001

Next, we can do a dry-run of applying our state file. We can do this with the state.apply function with the test=True option:

salt ubuntuMaster state.apply nginx test=True
ubuntuMaster:
----------
          ID: nginx
    Function: pkg.installed
      Result: True
     Comment: All specified packages are already installed
     Started: 11:14:33.183423
    Duration: 21.944 ms
     Changes:
----------
          ID: /etc/nginx/nginx.conf
    Function: file.managed
      Result: None
     Comment: The file /etc/nginx/nginx.conf is set to be changed
              Note: No changes made, actual changes may
              be different due to other states.
     Started: 11:14:33.209030
    Duration: 10.764 ms
     Changes:
              ----------
              mode:
                  0640
----------
          ID: /etc/nginx/sites-available/default
    Function: file.managed
      Result: None
     Comment: The file /etc/nginx/sites-available/default is set to be changed
              Note: No changes made, actual changes may
              be different due to other states.
     Started: 11:14:33.219892
    Duration: 70.677 ms
     Changes:
              ----------
              diff:
                  ---
                  +++
                  @@ -1,4 +1,4 @@
                  -##
                  +# Set interface to load balancer in production - change interface before using##
                   # You should look at the following URL's in order to grasp a solid understanding
                   # of Nginx configuration files in order to fully unleash the power of Nginx.
                   # https://www.nginx.com/resources/wiki/start/
                  @@ -19,8 +19,7 @@
                   # Default server configuration
                   #
                   server {
                  -     listen 80 default_server;
                  -     listen [::]:80 default_server;
                  +     listen 192.168.2.110 80 default_server;

                        # SSL configuration
                        #
                  @@ -38,12 +37,12 @@
                        #
                        # include snippets/snakeoil.conf;

                  -     root /var/www/html;
                  +     root /usr/share/nginx/html/;

                        # Add index.php to the list if you are using PHP
                        index index.html index.htm index.nginx-debian.html;

                  -     server_name _;
                  +     server_name 192.168.2.110;

                        location / {
                                # First attempt to serve request as file, then
              mode:
                  0640
----------
          ID: nginx
    Function: service.running
      Result: None
     Comment: Service is set to be started
     Started: 11:14:33.330309
    Duration: 12.555 ms
     Changes:
----------
          ID: /usr/share/nginx/html/index.html
    Function: file.managed
      Result: None
     Comment: The file /usr/share/nginx/html/index.html is set to be changed
              Note: No changes made, actual changes may
              be different due to other states.
     Started: 11:14:33.343135
    Duration: 18.835 ms
     Changes:
              ----------
              diff:
                  ---
                  +++
                  @@ -1,25 +1,9 @@
                   <!DOCTYPE html>
                   <html>
                  -<head>
                  -<title>Welcome to nginx!</title>
                  -<style>
                  -    body {
                  -        width: 35em;
                  -        margin: 0 auto;
                  -        font-family: Tahoma, Verdana, Arial, sans-serif;
                  -    }
                  -</style>
                  -</head>
                  -<body>
                  -<h1>Welcome to nginx!</h1>
                  -<p>If you see this page, the nginx web server is successfully installed and
                  -working. Further configuration is required.</p>
                  -
                  -<p>For online documentation and support please refer to
                  -<a href="http://nginx.org/">nginx.org</a>.<br/>
                  -Commercial support is available at
                  -<a href="http://nginx.com/">nginx.com</a>.</p>
                  -
                  -<p><em>Thank you for using nginx.</em></p>
                  -</body>
                  +  <head>
                  +  <title>Welcome from </title>
                  +    <body>
                  +    <h1>This is NGINX!</h1>
                  +    <p>Ubuntu Rocks!</p>
                  +    </body>
                   </html>

Summary for ubuntuMaster
------------
Succeeded: 5 (unchanged=4, changed=3)
Failed:    0
------------
Total states run:     5
Total run time: 134.775 ms

If the dry-run did not reveal any problems, you can try to apply the state to all of your available web servers by typing:

salt ubuntuMaster state.apply nginx

You can check that the Page is actually running on your Minions IP address:

curl 192.168.2.110

<!DOCTYPE html>
<html>
  <head>
  <title>Welcome from </title>
    <body>
    <h1>This is NGINX!</h1>
    <p>Ubuntu Rocks!</p>
    </body>
</html>