📜 ⬆️ ⬇️

Deploy Django apps using Ansible for Dummies

Good day!

Most recently, my colleague introduced me to the wonderful manual work automation tool called Ansbile . After that, the idea was instantly born to write something of our own, which simplifies the very manual work. What do you usually have to do with your hands? Correctly, deploy.

In this article I will talk about how using ansible to roll out a django-project on a clean remote server ubuntu 14.04, while creating for the project an individual user.

What is ansible? A set of commands written in python that allow you to automatically perform specified actions on remote machines. Wonderful! Let's build an action plan, how would we do it with pens?

')
Without further ado, get down to business.

What does ansible begin with - with the hosts file. In it we indicate all the machines available to us, over which actions will be taken. In our case, the machine is one (I will not talk about how to perform operations on several machines, the purpose of the article is not in this) and the file looks like this:

[project-hosts] root ansible_ssh_host=192.168.0.102 ansible_ssh_user=freylis ansible_ssh_pass=z ansible_sudo_pass=z user ansible_ssh_host=192.168.0.102 ansible_ssh_user=example2 ansible_ssh_pass=zz [user-hosts] user [root-hosts] root 

Let's break it down.
In the [project-hosts] section, all the machines at our disposal are listed, with the details of access to them. In our case, the machine is one, but indicated twice: the first one contains the root credentials, on behalf of which the system will be configured, the second - the credentials of the user who has not yet been created, the owner of our django application. The first parameter is the alias of this machine.

The [user-hosts] and [root-hosts] sections unite the machines into groups according to their general purpose (I hope this is understandable. Route in one group, non-root ones in another).

So, with the hosts figured out. Now we need to somehow tell the ansible what to do.

But first, talk about variables. It is absolutely clear that, for example, the project name is used many times: in the nginx config, supervisor, project paths, etc. Ansible has many ways to specify variables, but I prefer the following: in the grpou_vars directory, create a file with the section name from the hosts file. For example, I did not bother and created a file called project-hosts. Now the variables declared in this file will be accessible to all machines included in the [project-hosts] section, i.e. globally for our project.

Here is all I needed for this project (ansible uses the jinja2 syntax):

 # # system options # # linux username username: # about password crypt # http://docs.ansible.com/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module # or run `mkpasswd --method=SHA-512` # here crypted user_crypt_password: # really password user_password: z user_homedir: "/home/{{ username }}" mysql_root_user: root # root mysql user mysql_root_password: "" # # project options # # project slug ( if u have `example/manage.py` and example/example/settings.py` - `example` is project_slug) project_slug: # url or list urls for nginx project_url: project_dir: "{{ user_homedir }}/projects/{{ project_slug }}" project_homedir: "{{ user_homedir }}/projects/{{ project_slug }}/{{ project_slug }}" # virtualenv name env: "{{ project_dir }}/env" # port for uwsgi, must be unique for each project uwsgi_port: 9000 # mysql database for current project mysql_database: "{{ project_slug }}" # mysql user for current project mysql_user: # mysql user password for current project mysql_user_password: # # django settings # debug: True local_settings: 'local_settings.py' # set empty string if not used requirements: 'requirements.txt' 

Let's first deal with the configuration of the system. Create a root-playbook.yml file with the following content:
 --- - hosts: root-hosts sudo: true roles: - system 

Here I will explain two things:

1. hosts: root-hosts - a directive telling us about which group of machines to perform the following actions;
2. roles: system - a list of directories with further action scenarios.

Let's take a look at the scripts directory. It has the following form:

 roles/ system/ handlers/ main.yml tasks/ main.yml templates/ nginx.j2 supervisor.j2 

Here:
handlers - stores a description of the handlers. For example, it contains a description of how to restart nginx;
tasks - all over the head. List of tasks for Ansible;
templates - templates of files we need for some daemons;

We go in order.

handlers:
 --- - name: restart site supervisorctl: name={{ project_url }} state=restarted - name: restart mysql service: name=mysql state=restarted enabled=yes - name: restart nginx service: name=nginx state=restarted enabled=yes 

The Ansible Yml syntax is clear: the name of a directive, the directive itself, an action with parameters. These handlers will be used in our tasks, if the task is completed - kindly launch the necessary handler (notify section)
tasks:

 --- # apt-get update - name: updating the system apt: update_cache=yes cache_valid_time=86400 notify: - restart server #  apt-key   mariadb - name: Add mariadb apt repository key apt_key: id=0xcbcb082a1bb943db keyserver=hkp://keyserver.ubuntu.com:80 state=present #     mariadb - name: Add mariadb apt repository apt_repository: repo='deb http://mirror.timeweb.ru/mariadb/repo/10.1/debian wheezy main' state=present #    - name: install packages apt: pkg={{ item.name }} state=present with_items: - name: python-mysqldb - name: python-virtualenv - name: python-pip - name: supervisor - name: mariadb-server - name: nginx - name: uwsgi - name: uwsgi-plugin-python #   supervisor.conf.j2   templates      (   ) - name: copy supervisor config template: src=supervisor.conf.j2 dest=/etc/supervisor/conf.d/{{ project_url }}.conf notify: - restart site #     - name: create linux user user: name={{ username }} shell=/bin/bash home={{ user_homedir }} password={{ user_crypt_password }} #   mysql    - name: Create MySQL user mysql_user: > name={{ mysql_user }} host=% password={{ mysql_user_password }} priv={{ mysql_database }}.*:ALL login_user={{ mysql_root_user }} login_password={{ mysql_root_password }} state=present notify: - restart mysql # create database - name: Create MySQL database mysql_db: > name={{ mysql_database }} collation=utf8_general_ci encoding=utf8 login_user={{ mysql_root_user }} login_password={{ mysql_root_password }} state=present notify: - restart mysql #  nginx.j2   templates      - name: copy nginx config template: src=nginx.j2 dest=/etc/nginx/sites-available/{{ project_url }} notify: - restart nginx - name: create symlink nginx config file: src=/etc/nginx/sites-available/{{ project_url }} dest=/etc/nginx/sites-enabled/{{ project_url }} state=link 

Disassemble one section line by line:

- name: updating the system - the name displayed in the deployment process
- apt: update_cache = yes cache_valid_time = 86400: apt - the name of the directive is ansible (I call them directives). update_cache, cache_valid_time - directive parameters;
- notify: - restart server - an action from the handlers that must be done to complete the task.

Actually, the syntax is extremely simple. If some parameters are not clear - you can read in the documentation for Ansible. But I would like to draw attention to the template directive. It takes two parameters: src - the name of the source file stored in the templates directory of the current role and dest - where this file should be put, having previously been rendered using all available variables.

For example, my nginx.j2 template file looks like this:

 server { root {{ project_dir }}/{{ project_slug }}; access_log {{ project_dir }}/logs/nginx-access.log; error_log {{ project_dir }}/logs/nginx-errors.log; server_name {{ project_url }}; gzip on; gzip_min_length 1000; gzip_proxied expired no-cache no-store private auth; gzip_types text/plain application/xml; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:{{ uwsgi_port }}; } location /static { root {{ project_dir }}; } location /media { root {{ project_dir }}; } location /robots.txt { root {{ project_dir }}; } } 

The attentive reader noticed that with the user directive we created a new user of our system. Let's deploy our project on his behalf.

Create another playbook with the name user-playbook.yml and the following content:

 --- - hosts: user-hosts sudo: false roles: - django - hosts: root-hosts sudo: true tasks: - name: restart site in supervisor supervisorctl: name={{ project_url }} state=restarted - name: restart mysql service: name=mysql state=restarted enabled=yes - name: restart nginx service: name=nginx state=restarted enabled=yes 

And inside, we see that a certain django role is first performed, and then again using the superuser's rights, the restarting daemons task is performed. Let's look at what we need to deploy the django project:

 --- - name: create project directory file: path={{ project_dir }} state=directory - name: create logs directory file: path={{ project_dir }}/logs state=directory - name: create project home directory file: path={{ project_homedir }} state=directory #  ,      - name: unarchive project archive unarchive: src=/tmp/django_deploy.tar dest={{ project_homedir }} - name: create virtualenv pip: virtualenv={{ env }} virtualenv_site_packages=yes {% if requirements %}requirements={{ project_homedir }}/{{ requirements }}{% endif %} #  uwsgi.j2   ,    .     - name: copy uwsg file template: src=uwsgi.j2 dest={{ project_homedir }}/uwsgi.{{ project_slug }}.ini #  - name: copy local_settings.py template: src=local_settings.py dest={{ project_homedir }}/{{ project_slug }}/{{ local_settings }} - name: syncdb (for django<1.7) django_manage: command=syncdb virtualenv={{ env }} app_path={{ project_homedir }} - name: migrate database django_manage: command=migrate virtualenv={{ env }} app_path={{ project_homedir }} - name: collectstatic django_manage: command=collectstatic virtualenv={{ env }} app_path={{ project_homedir }} - name: create media directory file: path={{ project_dir }}/media state=directory #      django-tinymce - name: create `uploads` directory file: path={{ project_dir }}/media/uploads state=directory 


That's all. We installed the necessary software on a clean system, created a new user, deployed a django project on its behalf and restarted all the servers.

All this happiness starts like this:

 #     (   )       ansible-playbook -i hosts root-playbook.yml #       tar -cf /tmp/django-deploy.tar * #       ansible-playbook -i hosts user-playbook.yml 


A working project for deploying on ubuntu server 14.04 is in the repository .

Thank you for your time, I hope it was useful.

Source: https://habr.com/ru/post/241579/


All Articles