30 de Abril de 2020 · 6 min de lectura
Se recomienda antes haber realizado la configuración inicial para un servidor antes de abordar esta guía.
Se pretende preparar un servidor para el despliegue de aplicaciones django. Para ello:
Cuando veas un bloque de comandos debes saber que utilizaré →
para referirme a una salida, la respuesta que nos dará el terminal.
Para facilitar la ejecución de las instrucciones, se han declarado a modo de constantes una serie de palabras reservadas para que el lector pueda realizar una búsqueda masiva y pueda reemplazar todas las cadenas encontradas, adaptando de esta manera los comandos al caso de uso del lector.
<server_ip>
: Dirección IP del servidor que alojará nuestra web.<user>
: Usuario que tendrá acceso mediante SSH al servidor el cual pertenecerá al grupo sudo
.<git_url_project>
: Url del repositorio Django a desplegar.<project_name>
: Nombre de la aplicación Django a desplegar descargado del repositorio git.<db_schema>
: Nombre de la base de datos que será utilizada por nuestra aplicación Django.<db_user>
: Usuario de la base de datos.<db_password>
: Password para el usuario de nuestra base de datos.<betatester>
: Nombre para el usuario con acceso a pre (Este paso es opcional, depende de si deseas realizar: Sistema de acceso mediante usuario+password).Como primer paso, actualizaremos la paquetería e instalaremos las dependencias:
sudo apt update
sudo apt upgrade
sudo apt install gcc python3-pip python3.7-dev libpq-dev postgresql postgresql-contrib gettext nginx git
sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv
Usaremos virtualenv y pip para aislar las aplicación Django.
Asumimos que el proyecto a desplegar está publicado en un repositorio online.
cd ~
git clone <git_url_project>
cd <project_name>
virtualenv --python=/usr/bin/python3.7 <project_name>env
source <project_name>env/bin/activate
pip install django gunicorn psycopg2
pip install -r requirements.txt
Se espera que exista el fichero
requirements.txt
con todas las dependencias del proyecto.
Entramos en la consola de postgres:
sudo -u postgres psql
Creamos el esquema, el usuario y configuramos ciertos parámetros:
# consola de postgres
CREATE DATABASE <db_schema>;
CREATE USER <db_user> WITH PASSWORD '<db_password>';
ALTER ROLE <db_user> SET client_encoding TO 'utf8';
ALTER ROLE <db_user> SET default_transaction_isolation TO 'read committed';
ALTER ROLE <db_user> SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE <db_schema> TO <db_user>;
\q
Debemos asegurarnos que en nuestro fichero settings.py
:
ALLOWED_HOSTS
.DATABASES
.STATIC_ROOT
y tiene definida la ruta para los estáticos.Aplicamos las migraciones:
# ~/<project_name>
python manage.py migrate
Generamos los estáticos:
# ~/<project_name>
python manage.py collectstatic
Generamos los .mo
de los idiomas:
# ~/<project_name>
django-admin compilemessages --locale=es
Creamos el fichero /etc/systemd/system/gunicorn.service
y le añadimos el siguiente contenido:
# nano
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=<user>
Group=www-data
WorkingDirectory=/home/<user>/<project_name>
ExecStart=/home/<user>/<project_name>/<project_name>env/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/<user>/<project_name>/<project_name>.sock main.wsgi:application
[Install]
WantedBy=multi-user.target
Iniciamos el servicio:
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
Para ver el estado de gnunicorn
sudo systemctl status gunicorn
Tip: Con sockets puedes ganar sobre el 5% de velocidad en las respuestas pero a cambio dificulta mucho la traza de los errores, así que si lo prefieres puedes ver de definir el servicio a través de http sin sockets.
ExecStart=/home/<user>/<project_name>/<project_name>env/bin/gunicorn --access-logfile - --workers 3 --bind 0.0.0.0:8000 main.wsgi:application
Creamos el fichero /etc/nginx/sites-available/gunicorn
con el siguiente contenido:
# nano
server {
listen 80;
server_name <server_ip>;
location = /static/favicon.svg {
root /home/<user>/<project_name>;
}
location /static/ {
root /home/<user>/<project_name>;
}
location / {
proxy_pass http://unix:/home/<user>/<project_name>/<project_name>.sock;
include proxy_params;
}
}
Tip: Si el servicio de Gunicorn lo declaramos sin el uso de sockets, será necesario cambiar el proxy pass por:
proxy_pass http://127.0.0.1:8000
Crearemos un soft link en sites-enabled:
sudo ln -s /etc/nginx/sites-available/gunicorn /etc/nginx/sites-enabled
Y finalmente verificamos que la sintaxis es correcta y reiniciamos nginx:
sudo nginx -t
→ nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
→ nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl restart nginx
Si realizamos la guía configuración inicial para un servidor, tendremos un firewall que solo da acceso a ssh, por lo tanto, deberemos habilitar que Nginx sea visible.
Añadimos la excepción al firewall:
sudo ufw allow 'Nginx Full'
Log de errores de Nginx:
sudo tail -F /var/log/nginx/error.log
Log de accesos de Nginx:
sudo tail -F /var/log/nginx/access.log
Logs de Gunicorn:
sudo journalctl -u gunicorn
Reiniciar servicio:
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
Requerido al modificar el fichero del servicio
Imaginemos que queremos tener dos entornos, preproducción y producción, siendo uno de ellos el entorno beta para realizar pruebas por un grupo de testers. Una de las soluciones que se nos presenta es limitar el acceso a pre mediante usuario-contraseña, para lo cual necesitaremos hacer una pequeña modificación a nuestro fichero de configuración en Nginx.
Creamos un fichero de usuario+password, para ello ejecutaremos:
sudo sh -c "echo -n '<betatester>:' >> /etc/nginx/.htpasswd"
sudo sh -c "openssl passwd -apr1 >> /etc/nginx/.htpasswd"
→ Password:
cat /etc/nginx/.htpasswd
<betatester>:$apr1$szR.y8ij$llavOdo1KgebphhA1vVkj0
Editamos el fichero de configuración de Nginx y añadimos a la sección location /
la autentificación, quedando de la siguiente forma el contenido del fichero:
# nano
server {
listen 80;
server_name <server_ip>;
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;
location = /static/favicon.svg {
root /home/<user>/<project_name>;
}
location /static/ {
root /home/<user>/<project_name>;
}
location / {
proxy_pass http://unix:/home/<user>/<project_name>/<project_name>.sock;
include proxy_params;
}
}
Verificamos que la sintaxis es correcta y reiniciamos Nginx:
sudo nginx -t
→ nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
→ nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl restart nginx
Personalmente me gusta replicar al máximo posible el entorno de producción en local, por lo tanto, si has realizado la guía configuración inicial para un servidor dispondrás de un usuario en producción creado, ese usuario yo lo crearía en local sin el grupo sudo
. En caso de que no hayas realizado dicha guía, puedes saltarte el paso de crear un usuario.
Creamos el usuario para Gunicorn y deshabilitamos el password para que no nos salga en el login del sistema operativo el usuario:
adduser <user>
sudo passwd -d <user>
A continuación, seguiríamos todos los pasos anteriores descritos en esta guía, salvo En caso de tener restricciones de firewall, abrimos acceso y [EXTRA] Sistema de acceso mediante usuario+password.
Y finalmente, para aislar el servicio publicado por nuestro nginx, realizaremos una pequeña modificación al site, para ello editaremos /etc/nginx/sites-available/gunicorn
añadiendo allow
y deny all
al bloque a server
y cambiaremos el server_name a localhost, quedando de la siguiente manera el fichero de configuración:
# nano
server {
listen 80;
server_name 127.0.0.1;
allow 127.0.0.1;
allow 192.168.1.0/16;
deny all;
location = /static/favicon.svg {
root /home/<user>/<project_name>;
}
location /static/ {
root /home/<user>/<project_name>;
}
location / {
proxy_pass http://unix:/home/<user>/<project_name>/<project_name>.sock;
include proxy_params;
}
}
Al haber definido las excepciones para localhost
y para la 192.168.1.0/16
conseguimos que no se pueda acceder al servidor desde fuera de nuestra LAN, ya que si no se trabaja una de estas IPs se aplicará el deny all
por defecto a cualquier otra máquina que intente acceder.