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