How to use Ansible to deploy your Website

Deploy your Webapps Using Ansible

Ansible is mostly known for configuration management. It’s used when provisioning new servers. We can also use it to deploy our web applications.

In this tutorial, we are going to show you how to deploy an AngularJS application using Ansible. Our solution will utilize Githib and Git tagging.

Authentication and Credentials

The beauty of Ansible is that it works through SSH. We can provide a simple deployment solution without having to poke additional holes into our server, such as you would with Puppet or other solutions.

The challenge is where to store your credentials and how to do so safely, since the information needs to be available to Ansible in order for the connection to work. Additionally, some tasks require privileged execution using sudo, which requires another password.

The easiest method is to add the following to your inventory file.

[all:vars]
ansible_user=myuser
ansible_pass=mypassword
ansible_sudo_pass=mypassword

While convienent, I think we can all understand why this is dangerous. An alternative is to place your credentials in a .ssh/config file on your local machine. This prevents it from being captured in version control and leaked to the public. However, this solution only protects your ssh login credentials. Your sudo password would have to still be placed in the inventory file.

Vaults provide a different solution. Ansible vaults are highly encrypted using SHA256 and it’s a decent place to store your credentials if they need to be shared. It’s still risky since the vault can be brute-forced. It’s clearly better than just plopping in clear text credentials.

Ansible Playbook

The simplest form of Ansible is a single playbook. Playbooks allow you to execute tasks, which can be from builtin libraries or simple shell executions. An example of a module is Apt, used to update and install packages on Debian and Ubuntu.

Deploying Files to a Remote Host

In this example, we are going to do a straight copy of local files to a remote host. This is the simplest method of deploying files using Ansible.

Ansible uses an inventory file to manage and organize your servers. Before we can do anything we’ll need to create an inventory file. We’ll call our file inventory.

[appserver]
192.168.57.102

Appserver, the name in brackets, is what we reference whenever we want to run a playbook against a server or group of servers. Underneath it will be a list of IP addresses or hostnames for any server that should be grouped under the name.

Let’s execute a command to copy our static application files to the /var/www/html directory of an Apache web server.

ansible -v webapp -m copy -a "src=dist/ dest=/var/www/html mode=0755 owner=www-data group=www-data" -i inventory -b

That’s a lot to remember and it soon becomes unweldly as we add additional tasks to our deployment. Instead, let’s create an Ansible playbook file that performs everything for us.

Create a new file called simple-deploy.yaml. Add the following lines to it.

---
- host: appserver
  task:
    - name: Copy files to remote host
      copy:
        src: dist/
        dest: /var/www/html
        owner: www-data
        group: www-data
        mode: 0755

The beauty of Ansible, or any other configuration management tool, is we can codify every step, including ensure directories exist with the proper privileges.

Let’s add an additional step to our playbook that will create our application’s install directory.

---
- host: appservers
  task:
    - name: Create install directory
      file:
        state: directory
        path: /var/www/html
        owner: www-data
        group: www-data
        mode: 0755
    - name: Copy files to remote host
      copy:
        src: files/my-application
        dest: /opt/my-application
        owner: appuser
        group: appuser
        mode: 0755

To deploy our application using our playbook we execute the following Ansible command.

ansible-playbook -i inventory simple-deploy.yml

Deploying from a Git Repository to a Remote Host

A more traditional method is to deploy code from a central repository. This could be a version control system, such as Github or Bitbucket, as we will use in our example. It could also be from an artifact repository server, where we have pre-compiled binaries or packaged tars.

This example will pull down the latest commit from the master branch of our remote Github repository.

---
- host: appservers
  task:
    - name: Pull file from source
      git:
        repo: 'https://foosball.example.org/path/to/repo.git'
        dest: /var/www/html

 

Deploying a Specific release to a Remote Host

Tagging allows us to quickly identify release versions from our commit history. In this example, we are going to deploy a specific release from our remote Git repository.

Git Tagging

As you should already be aware, Git provides a mechanism to tag specific commits. These are human-readable descriptions of a particular commit. We typical tag the release version of our application, and that’s what we will do in our example.

If you’re inexperienced with Git, tagging isn’t much different than commits. It is an additional step that is done after you commit your changes.

To tag the latest commit with the name “1.0.0” and the description “Release 1.0.0”, you would execute the following command.

git tag -a "1.0.0" -m "Release 1.0.0"

Your tag will only be in your local repository. In order to push your tags to a remote repository, you need to do a push. To perform a tag push, you would run the following command.

git push --tags
---
- host: appservers
  task:
    - name: Pull file from source
      git:
        repo: 'https://foosball.example.org/path/to/repo.git'
        dest: /var/www/html
        version: 1.0.0

Ansible Roles

The examples above are treated like simple deployment scripts. When provision a new instance of your application server, turning that deployment script into an Ansible role will you to automate the entire process of provisioning your application server.

The following is your typical Ansible role directory structure. At a minimum, you need only the tasks directory.

\role_name
  \tasks
    main.yml
    other.yml
  \handlers
    main.yml
  \templates
    apache2.conf.j2
  \files
    foo.txt
    bar.crt
  \vars
    main.yml
  \defaults
    main.yml
  \meta
    main.yml

In our example, we are going to create the following structure.

\myaplication
  \tasks
    main.yml
  \vars
    main.yml

Role Vars

Ansible allows us to create variables that can be used throughout our role. This will be a good place to store our Git repository URL and the release version of our application.

In the \vars\main.yml file, add the following lines

---
gitrepo=github.com/demouser/myapplication.git
release=1.0.0
deploy_dir=/var/www/html

Storing these values as a variable has two benefits: first, it is a central location to store dynamic information. Secondly, we can override these values from a playbook or the command-line, allowing for custom and quick changes.

Role Tasks

Our tasks will be nearly identical to how they were created earlier in this tutorial. The difference is that will be referencing our variables to set information.

---
- tasks:
  - name: Create app directory
    file:
      state: directory
      path: '{{deploy_dir}}'
      owner: www-data
      group: www-data
      mode: 0755
  - name: Deploy app
    git:
      repo: '{{gitrepo}}'
      dest: '{{deploy_dir}}
      version: '{{release}}'

Using Ansible Roles

With our deployment script now an Ansible role we can add it to a server provisioning playbook.

The playbook for provisioning our server will be called app-server-01.yml. The provisioning process will include three roles: common, apache2, and myapp. The latter being for our application deployment. Our Ansible directory structure will look like the following.

/devops
  /roles
    /common
    /apache2
    /myapp
  /inventory
    production
  app-servers.yml

 

Our inventory file will be where we add and group our servers. In this example, we have three app servers that our application will be deployed to. Our inventory file — /inventory/production — will look some similar to the following.

[app-servers]
192.168.1.34 app-server-01
192.168.1.35 app-server-02
192.168.1.36 app-server-03

And our playbook for the app servers will look like the following example. You’ll notice in includes the hasts name of app-server, matching our inventory. It also includes the three roles in place of the tasks section.

---
- hosts: app-servers
  roles:
    - common
    - apache2
    - { role: myapp, version: 1.0.0 }

We aren’t modifying and variables for the first two roles. However, as an example, we are showing up to change the version variable of our role without having to modify the role itself. If you have no intention of changing the variable, you can just add the role name, as was done for common and apache2.

To provision our server and

Ansible Tags

There is a good chance that you only want to deploy your code and not re-provision the servers. Using Ansible tags we can specify which roles or tasks to perform. A task can have multiple tags to allow for highly fine grained control of your playbook tasks.

Let’s add the tag “deploy” to both our myapp tasks. We will also add the tag “predeploy” to the task that creates our deployment directory, and the tag “gitpull” to our Git task. This will allow us to either perform both tasks, just the directory creation/modification or just a git pull.

---
- tasks:
  - name: Create app directory
    file:
      state: directory
      path: '{{deploy_dir}}'
      owner: www-data
      group: www-data
      mode: 0755
    tags:
      - predeploy
      - deploy
  - name: Deploy app
    git:
      repo: '{{gitrepo}}'
      dest: '{{deploy_dir}}'
      version: '{{release}}'
    tags:
      - gitpull
      - deploy

To perform just the deployment tasks for our application, we would execute the following Ansible command.

ansible-playbook -i inventory/production --tags 'deploy' app-server.yml

Alternatively, if the directory permissions or ownership didn’t change, we can perform just the git pull using the following command.

ansible-playbook -i inventory/production --tags 'gitpull' app-server.yml