Ansible/Nginx Playbook
From charlesreid1
This article covers an example Ansible playbook to set up a Nginx server to serve pages over HTTPS.
The page is organized as follows:
- Before You Begin: Vagrant setup
- Part 1: Setting up nginx HTTP server
- Part 2: Setting up nginx HTTPS server
Contents
Before you begin: Vagrant Setup
See Ansible/Nginx Playbook/Vagrant Setup for initial setup of Vagrant machines for testing this playbook.
Ansible Playbook Example 1: Nginx Server Playbook
Creating a simple playbook
The following simple playbook will set up an nginx web server on our fresh Ubuntu machine.
Here are the pieces in our playbook:
- The playbook itself (YAML file)
- nginx configuration file
- nginx HTML templates
- Create Ansible group webservers
The final directory structure for example 1 will look like this:
playbooks/ Vagrantfile web-notls.yml hosts
The Playbook: YAML file
Here is a simple playbook for our secure nginx server:
web-notls.yml
:
- name: Configure webserver with nginx hosts: webservers become: True tasks: - name: install nginx apt: name=nginx update_cache=yes - name: copy nginx config file copy: src=files/nginx.conf dest=/etc/nginx/sites-available/default - name: enable configuration file: > dest=/etc/nginx/sites-enabled/default src=/etc/nginx/sites-available/default state=link - name: copy index.html template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644 - name: restart nginx service: name=nginx state=restarted
Required files: /etc/nginx/sites-available/default
, /usr/share/nginx/html/index.html
YAML truth-y values: true, True, TRUE, yes, Yes, YES, on, On, ON, y, Y
YAML false-y values: false, False, FALSE, no, No, NO, off, Off, OFF, n, N
nginx http config file
Here is the corresponding nginx configuration file, which serves HTTP requests only. We put in files/nginx.conf
:
files/nginx.conf:
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; server_name localhost; location / { try_files $uri $uri/ =404; } }
nginx index html page
Likewise, we want to create an index page for nginx to serve up, and we want to put template files into the playbook directory, in the templates subdirectory.
(NOTE: .j2 extension means it is a Jinja 2 template)
playbooks/templates/index.html.j2
:
<html> <head> <title>Welcome to ansible</title> </head> <body> <h1>nginx, configured by Ansible</h1> <p>If you can see this, Ansible successfully installed nginx.</p> <p>Running on {{ inventory_hostname }}</p> </body> </html>
Ansible hosts file
We will create a webservers
Ansible group in the inventory file and refer to this group in the Ansible playbook.
In the playbooks/hosts file the "myvagrantbox" line is put under the heading [webservers]
:
playbooks/hosts
[webservers] myvagrantbox ansible_host=127.0.0.1 ansible_port=2222
Now test it out: ping the webservers group with a single command:
$ ansible webservers -m ping
Output:
testserver | success >> { "changed": false, "ping": "pong" }
Running a simple playbook
The ansible-playbook
command is used to execute playbooks:
ansible-playbook web-notls.yml
Alternatively, to run a playbook directly, use the shebang line:
#!/usr/bin/env ansible-playbook
Then execute it directly:
./web-notls.yml
Output
Here's the output from the playbook command:
$ ansible-playbook web-notls.yml _______________________________________ < PLAY [Configure webserver with nginx] > --------------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ________________________ < TASK [Gathering Facts] > ------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ok: [myvagrantbox] ______________________ < TASK [install nginx] > ---------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || changed: [myvagrantbox] _______________________________ < TASK [copy nginx config file] > ------------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || changed: [myvagrantbox] _____________________________ < TASK [enable configuration] > ----------------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || ok: [myvagrantbox] ________________________ < TASK [copy index.html] > ------------------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || changed: [myvagrantbox] ______________________ < TASK [restart nginx] > ---------------------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || changed: [myvagrantbox] ____________ < PLAY RECAP > ------------ \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || || myvagrantbox : ok=6 changed=4 unreachable=0 failed=0
Anatomy of example playbook
Let's examine the example playbook in detail.
Plays
A playbook is a list of plays.
Every play has:
- a set of hosts to configure
- a set of tasks to run on those hosts
- the play is the thing that connects hosts to tasks
Optional play settings:
- name - a comment that describes what the play is about
- become - if true, Ansible will run each task by becoming the root user (useful for Ubuntu, where ssh as root is disabled by default)
- vars - list of variables and values
In our example, the play is this entire section:
- name: Configure webserver with nginx hosts: webservers become: True tasks: - name: install nginx apt: name=nginx update_cache=yes - name: copy nginx config file copy: src=files/nginx.conf dest=/etc/nginx/sites-available/default - name: enable configuration file: > dest=/etc/nginx/sites-enabled/default src=/etc/nginx/sites-available/default state=link - name: copy index.html template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644 - name: restart nginx service: name=nginx state=restarted
Tasks
The tasks are the actions that are performed when the play is run.
The first task is to install nginx:
- name: install nginx apt: name=nginx update_cache=yes
This can also be written without the optional name parameter,
- apt: name=nginx update_cache=yes
Can also fold over multiple lines using >
:
- name: install nginx apt: > name=nginx update_cache=yes
Actions in tasks are composed of modules.
Modules
There are lots of useful modules that come with Ansible that can be used from playbooks.
- apt - installs/removes packages using aptitude package manager
- copy - copies files from local machine to host
- file - sets attributes of files/symlinks/directories
- service - starts/stops/restarts a service
- template - generates a file from a template and copies it to the hosts
Ansible Playbook Example 2: Secure Nginx Server Playbook
The second example introduces two new concepts:
- variables
- handlers
In example 2 we will generate a certificate on the machine that is running Ansible and distribute the certificate to the machines we're spinning up. (In this example we can use a self-signed certificate.)
The final directory structure for example 2 will look like this:
playbooks/ ansible.cfg hosts Vagrantfile web-notls.yml web-tls.yml files/nginx.key files/nginx.crt files/nginx.conf templates/index.html.j2 templates/nginx.conf.j2
The Playbook
Here is the YAML playbook file:
web-tls.yml
- name: Configure webserver with nginx and tls hosts: webservers become: True vars: key_file: /etc/nginx/ssl/nginx.key cert_file: /etc/nginx/ssl/nginx.crt conf_file: /etc/nginx/sites-available/default server_name: localhost tasks: - name: Install nginx apt: name=nginx update_cache=yes cache_valid_time=3600 - name: create directories for ssl certificates file: path=/etc/nginx/ssl state=directory - name: copy TLS key copy: src=files/nginx.key dest={{ key_file }} owner=root mode=0600 notify: restart nginx - name: copy TLS certificate copy: src=files/nginx.crt dest={{ cert_file }} notify: restart nginx - name: copy nginx config file template: src=templates/nginx.conf.j2 dest={{ conf_file }} notify: restart nginx - name: enable configuration file: dest=/etc/nginx/sites-enabled/default src={{ conf_file }} state=link notify: restart nginx - name: copy index.html template: src=templates/index.html.j2 dest=/usr/share/nginx/html/index.html mode=0644 handlers: - name: restart nginx service: name=nginx state=restarted
Creating the TLS (SSL) certificate
The next step is to create the certificate that Ansible will distribute to our nodes. This step should be run once (and is optional, if you already have a certificate).
From the playbooks directory, put the certificate into a "files" folder that will hold files we want to copy to the nodes:
mkdir files
Now generate the SSL cert into that folder:
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -subj /CN=localhost \ -keyout files/nginx.key -out files/nginx.crt
This results in nginx.key
and nginx.crt
. 3650 = days til expiry = 10 years
Playbook Anatomy
Variables
The playbook defines a variables section that contains four variables:
vars: key_file: /etc/nginx/ssl/nginx.key cert_file: /etc/nginx/ssl/nginx.crt conf_file: /etc/nginx/sites-available/default server_name: localhost
Any valid YAML can be used as the value of a variable (lists and dictionaries, strings and booleans).
Variables are referenced both in tasks and in template files. The {{ braces }}
notation is used to make a variable substitution.
Example: using variable in a task:
- name: copy TLS key copy: src=files/nginx.key dest={{ key_file }} owner=root mode=0600
Quoting
If you reference a variable right after specifying the module, the YAML parser will misinterpret the variable reference as the beginning of an inline dictionary.
WRONG:
- name: perform some task command: {{ myapp }} -a foo
You must quote the arguments instead.
RIGHT:
- name: perform some task command: "{{ myapp }} -a foo"
More Quoting
Arguments with colons cause similar problems, since the YAML parser will interpret the colon to indicate a yaml dictionary key-value split.
WRONG:
- name: show a debug message debug: msg="The debug module will print a message: neat, eh?"
If you quote the whole thing, that won't work either -
WRONG:
- name: show a debug message debug: "msg=The debug module will print a message: neat, eh?"
because the debug module's msg arg expected a quoted string, but didn't get one.
To do it right, use quotes around both:
RIGHT:
- name: show a debug message debug: "msg='The debug module will print a message: neat, eh?'"
Handlers and Notifiers
The playbook for the secure nginx node also added a handlers section:
handlers: - name: restart nginx service: name=nginx state=restarted
A handler is a conditional task. It is a task that is run if it is notified (triggered) by another task.
To notify the handler, we create a notifier key for the appropriate task. Note that handlers have pitfalls - they make it hard to debug playbooks, multiple handler notifications will only run the handler once per play, and the order of handlers occurs as in the file, not in the order triggered.
Best practice: only use handlers for restarting services and for reboots.
Here is an example task that has a notify key added to it to trigger the restart nginx task:
- name: copy TLS key copy: src=files/nginx.key dest={{ key_file }} owner=root mode=0600 notify: restart nginx
An nginx server would need to restart if:
- The key changes
- The cert changes
- The config file changes
- The sites-enabled directory contents change
Each of these tasks has a notify statement on it to ensure that Ansible will restart the nginx server in any of the above scenarios.
nginx configuration template
The nginx configuration file uses double-bracket template variables to make it easier to script deployment.
To do this, add another template to the templates folder:
templates/nginx.conf.j2
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; listen 443 ssl; root /usr/share/nginx/html; index index.html index.htm; server_name {{ server_name }}; ssl_certificate {{ cert_file }}; ssl_certificate_key {{ key_file }}; location / { try_files $uri $uri/ =404; } }
The three variables defined above are defined in the variables section of the playbook.
Note that you can also use template variables in the playbooks themselves.
Ansible hosts file
The Ansible hosts file will look the same as before:
[webservers] myvagrantbox ansible_host=127.0.0.1 ansible_port=2222
Running the playbook
Run the playbook with the ansible-playbook command:
ansible-playbook web-tls.yml
Flags