How to use Ansible to update your Django web app


Now, as you have overcome or evaded the reefs, shoals and swirls of initial development and deployment and your appetite grows, you ask “How do I automate the update and restart of my web app when I change the code?” There is already one simple and elegant method on our blog, that uses one of the possible push to publish methods, but this time we will dip our toes into vast waters of Ansible automation.

You may think that using Ansible for a simple task like this is overkill, and it’s a valid thought, but our example is a good practice case that introduces multiple elements that can be used later for much bigger projects. That’s why, instead of writing a simple bash or python script, we will build a full Ansible playbook with accompanying configuration.

Ansible uses ssh connections for remote execution so you will need a paid account that allows that.

We will use an example Django project that we normally use to test our helper scripts. That will add some Django specific stuff into the Ansible playbook (migrations), but with small modifications it can be used with other web frameworks.

We assume that:

  • you work on your local machine and want to remotely control your web app already deployed to PythonAnywhere;
  • your web app code is stored in some remote git repository (Github, Gitlab, Bitbucket etc);
  • your code is running in the virtual environment and has its requirements defined in a requirements.txt file in the root of the repository.

First you will need to install Ansible on your machine. You will find a nice installation guide in the official Ansible documentation. Note that Windows is not supported for the control node so you will need Cygwin or Windows subsystem for Linux to execute Ansible playbooks on a Windows machine.

Let’s create an ansible subdirectory where we will store our ansible-related files.

$ mkdir ansible
$ cd ansible

We will start our Ansible configuration with creation of an inventory of machines that we want to make Ansible run things on. By default Ansible looks into /etc/ansible/hosts, but we will create a local file that explicitly informs Ansible about the desired list of machines. Not surprisingly, there will be only one machine defined in it.

Our inventory file will look like that:

[pythonanywhere]
ssh.pythonanywhere.com

[pythonanywhere:vars]
ansible_ssh_user=newpythonanywhereuser

Note that here we use an example user called newpythonanywhereuser. You should replace it with your own username.

If your account is on our European site eu.pythonanywhere.com, you need to replace ssh.pythonanywhere.com with ssh.eu.pythonanywhere.com

Then we will create a simple ansible.cfg file to inform Ansible that we want to use the inventory file that we have just created.

[defaults]
inventory=inventory

As we will be using PythonAnwyhere API, you will need your API token. To get it, log in to PythonAnywhere, and go to the “Account” page using the link at the top right. Click on the “API token” tab; if you don’t already have a token, it will look like this:

Click the “Create a new API token” button to get your token, and you’ll see this:

That string of letters and numbers (d870f0cac74964b27db563aeda9e418565a0d60d in the screenshot) is an API token, and anyone who has it can access your PythonAnywhere account and do stuff – so keep it secret. If someone does somehow get hold of it, you can revoke it on this page by clicking the red button – that stops it from working in the future, and creates a new one for you to use.

Fortunately Ansible provides the Vault, a mechanism to keep secrets in encrypted files. Let’s create one named vars.yml.

$ ansible-vault create vars.yml

That will ask you for the new password and drop you into your editor of choice (evergreen vi by default). You need to add only two lines to define the api_token variable in the beloved yaml format. Maybe you don’t like yaml? Well, it could have been XML.

---
api_token: d870f0cac74964b27db563aeda9e418565a0d60d

Again, remember to replace the example value with your real token.

Now, we can write our playbook called update.yml.

First we will tell Ansible that we want to run it on pythonanywhere that we defined in our inventory

---
- hosts: pythonanywhere

Then we will set some variables to keep our playbook DRY.

  vars:
    home: /home/newpythonanywhereuser/
    app_path: "{{ home }}newpythonanywhereuser.pythonanywhere.com/"
    virtualenv: "{{ home }}.virtualenvs/newpythonanywhereuser.pythonanywhere.com"

We will include our secret api_token from encrypted vars.yml file

  vars_files:
    vars.yml

We can start performing tasks. The first one is to pull our git repo if needed.

  tasks:
  - name: update code
    git:
      repo: 'https://github.com/filiplajszczak/example-django-project.git'
      dest: "{{ app_path }}"
      update: yes

The second is to update dependencies in our virtual environment

  - name: update requirements
    pip:
      requirements: "{{ app_path }}requirements.txt"
      virtualenv: "{{ virtualenv }}"

The third is to apply migrations if we changed our models. You have to remember to generate and commit them.

  - name: apply django migrations
    django_manage:
      command: migrate
      app_path: "{{ app_path }}"
      settings: myproject.settings
      virtualenv: "{{ virtualenv }}"

And the last step is to reload your web app to apply your new code. As we do not have a PythonAnywhere Ansible module yet, we will use one of our helper scripts so remember to include the pythonanywhere package in your requirements.txt file. This is where the api_token is used.

  - name: reload web app
    shell: "{{ virtualenv }}/bin/pa_reload_webapp.py newpythonanywhereuser.pythonanywhere.com"
    environment:
      API_TOKEN: "{{ api_token }}"

Your ansible subdirectory should look like this now:

ansible
├── ansible.cfg
├── inventory
├── update.yml
└── vars.yml

One extra step that might be required to execute your playbook is to create and copy your public ssh key to PythonAnywhere.

If you do not have one, run:

$ ssh-keygen

and follow the instructions.

If you created a key pair or you already have one, you can upload your public key to PythonAnywhere.

$ ssh-copy-id newpythonanywhereuser@ssh.pythonanywhere.com

Again replace the example username with the real one and use ssh.eu.pythonanywhere.com if you are on our european site.

You can now execute your playbook:

$ ansible-playbook update.yml --ask-vault-pass

You will be asked for the vault password and it should all work.

All the example files we talked about in this post are available here.

So, that’s one more way to update your code on PythonAnywhere. There are others where you can combine push to your remote repository with execution of some code. It could be done using Github actions or Gitlab CI/CD pipeline. You can run your own script or execute the Ansible playbook. That would require some changes to how you manage secrets and it could be a good topic for a blog post in future.

Let us know if you have any comments or questions – otherwise, happy coding!

comments powered by Disqus