Deploying Python applications¶
Overview¶
This document is a hands-on guide to deploying a simple Python application in tsuru. The example application will be a very simple Django project using a SQLite database. It’s applicable to any WSGI application.
Creating the app¶
To create an app, you use the command app-create:
$ tsuru app-create <app-name> <app-platform>
For Python, the app platform is, guess what, python
! Let’s be over creative
and develop a never-developed tutorial-app: a blog, and its name will also be
very creative, let’s call it “blog”:
$ tsuru app-create blog python
To list all available platforms, use the command platform-list.
You can see all your applications using the command app-list:
$ tsuru app-list
+-------------+-------+--------------------------+
| Application | Units | Address |
+-------------+-------+--------------------------+
| blog | | blog.192.168.50.4.nip.io |
+-------------+-------+--------------------------+
You can then send the code of your application.
Application code¶
This document will not focus on how to write a Django blog, you can clone the entire source direct from GitHub: https://github.com/tsuru/tsuru-django-sample. Here is what we did for the project:
- Create the project (
django-admin startproject blog
) - Create a “posts” app (
django-admin startapp posts
) - Add a “Post” model to the app
- Register the model in django-admin
Git deployment¶
When you create a new app, tsuru will display the Git remote that you should use. You can always get it using the command app-info:
$ tsuru app-info --app blog
Application: blog
Repository: git@192.168.50.4.nip.io:blog.git
Tags:
Platform: python
Teams: admin
Address: blog.192.168.50.4.nip.io
Owner: admin@example.com
Team owner: admin
Deploys: 0
Pool: theonepool
App Plan:
+---------------+--------+------+-----------+---------+
| Name | Memory | Swap | Cpu Share | Default |
+---------------+--------+------+-----------+---------+
| autogenerated | 0 MB | 0 MB | 100 | false |
+---------------+--------+------+-----------+---------+
The Git remote will be used to deploy your application using Git. You can just push to tsuru remote and your project will be deployed:
$ git push git@192.168.50.4.nip.io:blog.git master
remote: HEAD is now at 260ae00...
remote: -- Using python version: 2.7.13 (default) --
remote: /home/application/current /
remote:
remote: ---- Building image ----
remote: ---> Sending image to repository (0.01MB)
remote: ---> Cleaning up
#####################################
# OMIT #
#####################################
To git@192.168.50.4.nip.io:blog.git
* [new branch] master -> master
If you get a “Permission denied (publickey).”, make sure you’re member of a team and have a public key added to tsuru. To add a key, use the command key-add:
$ tsuru key-add mykey ~/.ssh/id_rsa.pub
You can use git remote add
to avoid typing the entire remote url every time
you want to push:
$ git remote add tsuru git@192.168.50.4.nip.io:blog.git
Then you can run:
$ git push tsuru master
Everything up-to-date
And you will be also able to omit the --app
flag from now on:
$ tsuru app-info
Application: blog
Repository: git@192.168.50.4.nip.io:blog.git
Platform: python
Teams: admin
Address: blog.192.168.50.4.nip.io
Owner: admin@example.com
Team owner: admin
Deploys: 0
Pool: theonepool
Units: 1
+------------+---------+
| Unit | Status |
+------------+---------+
| eab5151eff | started |
+------------+---------+
App Plan:
+---------------+--------+------+-----------+---------+
| Name | Memory | Swap | Cpu Share | Default |
+---------------+--------+------+-----------+---------+
| autogenerated | 0 MB | 0 MB | 100 | false |
+---------------+--------+------+-----------+---------+
Listing dependencies¶
In the last section we omitted the dependencies step of deploy. In tsuru, an application can have two kinds of dependencies:
- Operating system dependencies, represented by packages in the package manager
of the underlying operating system (e.g.:
yum
andapt-get
); - Platform dependencies, represented by packages in the package manager of the
platform/language (in Python,
pip
).
All apt-get
dependencies must be specified in a requirements.apt
file,
located in the root of your application, and pip dependencies must be located
in a file called requirements.txt
, also in the root of the application.
Since we will use Django, we need to install django
package using pip
.
As this project doesn’t have any external dependencies, we don’t need a
requirements.apt
file. Here is the requirements.txt
file contents:
Django<=1.11
You can see the complete output of installing these dependencies below:
% git push tsuru master
remote: HEAD is now at 260ae00...
remote: -- Using python version: 2.7.13 (default) --
remote: /home/application/current /
remote: requirements.txt detected, using 'pip install -r ./requirements.txt' to install dependencies
remote: Requirement already satisfied: Django<=1.11 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 1))
remote: Requirement already satisfied: pytz in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from Django<=1.11->-r ./requirements.txt (line 1))
remote: /
remote:
remote: ---- Building image ----
remote: ---> Sending image to repository (0.01MB)
remote: ---> Cleaning up
#####################################
# OMIT #
#####################################
To git@192.168.50.4.nip.io:blog.git
* [new branch] master -> master
Running the application¶
As you can see, in the deploy output there is a step described as “Restarting your app”. In this step, tsuru will restart your app if it’s running, or start it if it’s not. But how does tsuru start an application? That’s very simple, it uses a Procfile (a concept stolen from Foreman). In this Procfile, you describe how your application should be started. We can use gunicorn, for example, to start our Django application. Here is how the Procfile should look like:
web: gunicorn -b 0.0.0.0:$PORT blog.wsgi
Now we commit the file and push the changes to tsuru git server, running another deploy:
$ git add Procfile
$ git commit -m "Procfile: added file"
$ git push tsuru master
remote: HEAD is now at 260ae00...
remote: -- Using python version: 2.7.13 (default) --
remote: /home/application/current /
remote: requirements.txt detected, using 'pip install -r ./requirements.txt' to install dependencies
remote: Requirement already satisfied: Django<=1.11 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 1))
remote: Requirement already satisfied: pytz in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from Django<=1.11->-r ./requirements.txt (line 1))
remote: /
remote:
remote: ---- Building image ----
remote: ---> Sending image to repository (0.01MB)
remote: ---> Cleaning up
#####################################
# OMIT #
#####################################
remote: ---> Restarting your app
remote: /var/lib/tsuru/hooks/start: line 13: gunicorn: command not found
remote:
remote: ---> Deploy done!
remote:
To git@192.168.50.4.nip.io:blog.git
81e884e..530c528 master -> master
Now we get an error: gunicorn: command not found
. It means that we need to
add gunicorn to requirements.txt
file:
$ cat >> requirements.txt
gunicorn==19.6
^D
Now we commit the changes and run another deploy:
$ git add requirements.txt
$ git commit -m "requirements.txt: added gunicorn"
$ git push tsuru master
remote: -- Using python version: 2.7.13 (default) --
remote: /home/application/current /
remote: requirements.txt detected, using 'pip install -r ./requirements.txt' to install dependencies
remote: Requirement already satisfied: Django<=1.11 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 1))
remote: Requirement already satisfied: gunicorn==19.6 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 2))
remote: Requirement already satisfied: pytz in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from Django<=1.11->-r ./requirements.txt (line 1))
remote: /
remote:
remote: ---- Building image ----
remote: ---> Sending image to repository (0.01MB)
remote: ---> Cleaning up
#####################################
# OMIT #
#####################################
remote: ---> Restarting your app
remote:
remote: ---> Deploy done!
remote:
To git@192.168.50.4.nip.io:blog.git
81e884e..530c528 master -> master
Now that the app is deployed, you can access it from your browser, getting the
IP or host listed in app-list
and opening it. For example,
in the list below:
$ tsuru app-list
+-------------+-----------+---------------------+
| Application | Units | Address |
+-------------+-----------+---------------------+
| blog | 1 started | blog.cloud.tsuru.io |
+-------------+-----------+---------------------+
We can access the admin of the app in the URL http://blog.cloud.tsuru.io/admin/.
Deployment hooks¶
It would be boring to manually run syncdb
and/or migrate
after every
deployment. So we can configure an automatic hook to always run before or after
the app restarts.
tsuru parses a file called tsuru.yml
and runs restart hooks. As the
extension suggests, this is a YAML file, that contains a list of commands that
should run before and after the restart. Here is our example of tsuru.yml:
hooks:
build:
- python manage.py collectstatic -c --noinput
- python manage.py migrate
For more details, check the hooks documentation.
tsuru will look for the file in the root of the project. Let’s commit and deploy it:
$ git add tsuru.yml
$ git commit -m "tsuru.yml: added file"
$ git push tsuru master
remote: -- Using python version: 2.7.13 (default) --
remote: /home/application/current /
remote: requirements.txt detected, using 'pip install -r ./requirements.txt' to install dependencies
remote: Requirement already satisfied: Django<=1.11 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 1))
remote: Requirement already satisfied: gunicorn==19.6 in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from -r ./requirements.txt (line 2))
remote: Requirement already satisfied: pytz in /var/lib/pyenv/versions/2.7.13/envs/app_env_2.7.13/lib/python2.7/site-packages (from Django<=1.11->-r ./requirements.txt (line 1))
remote: /
remote:
remote: ---- Building image ----
remote: ---> Sending image to repository (0.01MB)
remote: ---> Cleaning up
remote: ---- Running build hooks ----
remote: ---> Running "python manage.py collectstatic -c --noinput"
#####################################
# OMIT #
#####################################
remote: ---> Restarting your app
remote:
remote: ---> Deploy done!
remote:
To git@192.168.50.4.nip.io:blog.git
81e884e..530c528 master -> master
It’s done! Now we have a Django project deployed on tsuru.
Going further¶
For more information, you can dig into tsuru docs, or read complete instructions of use for the tsuru client.