Como instalar Python, Django, Nginx y Memcached en un servidor Ubuntu desde cero

Como instalar Python, Django, Nginx y Memcached en un servidor Ubuntu desde cero
Photo by Kevin Ache / Unsplash

Después de varios años utilizando el mismo servidor para hospedar namespace.mx, recibí una oferta para contratar una nueva VPS con mejores características y decidí aprovechar la oportunidad para migrar todo el sitio.

El cambio resultó completamente transparente para los usuarios. Preparé el nuevo servidor, instalé todas las dependencias, copié el proyecto, realicé las pruebas necesarias y, únicamente cuando todo funcionó correctamente, cambié el DNS para que el tráfico comenzara a llegar al nuevo servidor.

Ésta no pretende ser la única forma de preparar un servidor para Django. Es simplemente el procedimiento que seguí durante esa migración y que decidí documentar mientras iba resolviendo los pequeños problemas que fueron apareciendo.

El objetivo de esta serie es partir de un servidor recién instalado y dejarlo listo para ejecutar aplicaciones desarrolladas con Python y Django utilizando PostgreSQL, Nginx y Memcached.

Preparación del servidor

En aquel momento elegí una instalación mínima de Ubuntu Server.

Más allá de la distribución concreta, mi recomendación sigue siendo la misma: siempre que sea posible utiliza una versión con soporte de largo plazo (Long Term Support o LTS). La estabilidad suele ser mucho más importante que disponer inmediatamente de la versión más reciente de cada paquete.

Lo primero que conviene hacer después de recibir el acceso al servidor es comprobar qué versión del sistema operativo estamos utilizando.

cat /etc/issue

También es una buena práctica actualizar inmediatamente la información de los paquetes disponibles.

sudo apt-get update

Este comando no instala nada; simplemente descarga la información más reciente de los repositorios configurados.

Una vez actualizado el índice de paquetes podemos instalar las actualizaciones disponibles.

sudo apt-get upgrade

En algunas ocasiones el sistema indicará que determinados paquetes no pueden actualizarse porque requieren instalar o eliminar dependencias adicionales.

Cuando eso ocurre puede ser necesario ejecutar:

sudo apt-get dist-upgrade

Este comando permite resolver cambios mayores en las dependencias del sistema.

No es algo que deba ejecutarse diariamente sin revisar su salida, pero sí resulta útil cuando estamos preparando un servidor nuevo y queremos comenzar con un sistema completamente actualizado.

Finalmente, antes de continuar, cambia la contraseña inicial que proporciona el proveedor del servicio.

passwd

Es un paso muy sencillo y, sin embargo, suele olvidarse con demasiada frecuencia.

Crear un usuario de trabajo

Una de las primeras medidas de seguridad que conviene adoptar consiste en evitar trabajar directamente como root.

El usuario administrador tiene permisos para modificar cualquier archivo del sistema y un simple error al escribir un comando puede provocar daños importantes.

Lo recomendable es crear un usuario normal y concederle permisos administrativos mediante sudo.

Creamos primero el usuario.

adduser javier

El sistema solicitará una contraseña y algunos datos adicionales que pueden dejarse en blanco si no son necesarios.

A partir de este momento ya disponemos de un usuario con el que podremos trabajar diariamente.

Instalar y configurar sudo

Dependiendo de la distribución utilizada, es posible que el paquete sudo no venga instalado por defecto.

En ese caso basta con instalarlo.

apt-get install sudo

Una vez instalado debemos editar el archivo /etc/sudoers.

La forma correcta de hacerlo es utilizando visudo.

visudo

Esta utilidad verifica la sintaxis antes de guardar los cambios y evita dejar el sistema en un estado inconsistente.

Busca la sección correspondiente a los privilegios de usuario.

# User privilege specification
root    ALL=(ALL) ALL

y agrega el usuario recién creado.

root    ALL=(ALL) ALL
javier  ALL=(ALL) ALL

A partir de ese momento ya no será necesario iniciar sesión como root.

Las tareas administrativas podrán ejecutarse anteponiendo sudo al comando correspondiente.

sudo apt-get update
sudo service ssh restart
sudo vi /etc/hosts

Además de reducir el riesgo de errores, esta práctica permite mantener un registro de las operaciones administrativas realizadas por cada usuario.

Una vez comprobado que el nuevo usuario puede utilizar sudo, ya podemos cerrar la sesión de root y continuar el resto de la configuración trabajando únicamente con nuestra cuenta habitual.

En la siguiente parte configuraremos el acceso remoto mediante SSH, ajustaremos la zona horaria del servidor y prepararemos el entorno para comenzar a instalar Python y el resto de las herramientas necesarias para ejecutar nuestros proyectos con Django.

Configurar SSH

Una vez que ya tenemos un usuario normal con permisos administrativos, el siguiente paso consiste en asegurar el acceso remoto al servidor.

En mi caso existía además una razón práctica.

En la red desde la que trabajaba el puerto estándar de SSH (22) se encontraba bloqueado, por lo que necesitaba utilizar un puerto diferente. Aproveché ese cambio para endurecer un poco la configuración del servidor.

Cambiar el puerto no convierte automáticamente a un servidor en un sistema más seguro. Quien realmente quiera encontrar un servicio SSH terminará haciéndolo. Sin embargo, sí ayuda a reducir una enorme cantidad de intentos automáticos de acceso que recorren continuamente Internet buscando servidores mal configurados.

Mucho más importante que cambiar el puerto es impedir que el usuario root pueda iniciar sesión directamente mediante SSH.

Abrimos entonces el archivo de configuración del servidor.

sudo vi /etc/ssh/sshd_config

Al ejecutar el comando mediante sudo probablemente veremos un mensaje parecido al siguiente.

We trust you have received the usual lecture from the local System
Administrator...

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

Ese mensaje aparece únicamente la primera vez y sirve para recordar que los permisos administrativos deben utilizarse con cuidado.

Dentro del archivo realizé los siguientes cambios.

Port 2222
PermitRootLogin no
UseDNS no
AllowUsers javier

Cada una de estas opciones cumple una función distinta.

  • Port cambia el puerto donde escucha el servidor SSH.
  • PermitRootLogin no impide iniciar sesión directamente como root.
  • UseDNS no evita que SSH realice búsquedas DNS inversas al aceptar una conexión, reduciendo ligeramente el tiempo de espera durante el inicio de sesión.
  • AllowUsers limita el acceso únicamente a los usuarios indicados.

Una vez guardados los cambios sólo queda recargar la configuración.

sudo service ssh reload

o, dependiendo de la distribución,

sudo /etc/init.d/ssh reload

Antes de cerrar la sesión actual conviene abrir una segunda terminal e intentar conectarse utilizando el nuevo puerto.

ssh -p 2222 javier@mi-servidor

Si la conexión funciona correctamente, entonces ya podemos cerrar la sesión anterior con tranquilidad.

Es un detalle importante.

Si cerramos la única sesión abierta antes de comprobar la nueva configuración y cometimos algún error, podríamos quedarnos sin acceso remoto al servidor.

Configurar la zona horaria

Las fechas parecen un detalle menor... hasta que dejan de coincidir.

Aplicaciones como Django y PostgreSQL manejan las fechas con bastante precisión y una configuración incorrecta del huso horario puede producir resultados difíciles de detectar: publicaciones con una hora distinta, tareas programadas ejecutándose antes o después de tiempo o registros almacenados con un desfase de varias horas.

Por eso prefiero dejar correctamente configurada la zona horaria antes de comenzar a instalar el resto del software.

Podemos comprobar la configuración actual mediante:

date

En muchos servidores recién instalados veremos una salida semejante a ésta.

Mon Dec  9 01:34:25 UTC

No tiene nada de malo trabajar en UTC; de hecho, muchos administradores prefieren mantener todos sus servidores en esa zona horaria.

Sin embargo, durante el desarrollo suelo preferir que la hora del servidor coincida con mi zona local para facilitar la revisión de registros y archivos de bitácora.

Para modificarla utilizamos:

sudo dpkg-reconfigure tzdata

El asistente solicitará primero el continente y después la ciudad correspondiente.

Al finalizar veremos un resumen similar al siguiente.

Current default time zone: 'America/Mexico_City'

Local time is now:      Sun Dec  8 19:42:51 CST
Universal Time is now:  Mon Dec  9 01:42:51 UTC

A partir de ese momento todas las aplicaciones que utilicen la configuración del sistema compartirán el mismo huso horario.

Configurar los locales

Otro detalle que suele pasarse por alto son los locales del sistema.

Los locales determinan aspectos como:

  • el idioma de los mensajes del sistema;
  • el formato de fechas;
  • la codificación de caracteres;
  • las reglas de ordenamiento alfabético.

Una configuración incompleta suele manifestarse mediante advertencias similares a las siguientes.

perl: warning: Setting locale failed.

No suele impedir que el sistema funcione, pero es preferible corregirlo desde el principio.

Dependiendo de la distribución utilizada, basta con instalar el soporte correspondiente y configurar el idioma deseado.

Por ejemplo:

sudo apt-get install localization-config

y posteriormente:

sudo update-locale-config es_MX

En sistemas más recientes el procedimiento puede variar ligeramente, pero la idea es exactamente la misma: generar e instalar los locales que utilizará el sistema.

Podemos comprobar el resultado ejecutando nuevamente:

date

Si el idioma quedó correctamente configurado veremos que los nombres de meses y días aparecen ya traducidos.

Con esto terminamos la preparación básica del servidor.

Ya tenemos un sistema actualizado, un usuario para el trabajo diario, acceso remoto configurado, la zona horaria correctamente establecida y un entorno listo para comenzar la instalación de Python y el resto de las herramientas que utilizaremos durante el desarrollo.

Compilación de Python

En el momento en que preparé este servidor, la versión de Python incluida por la distribución era anterior a la que necesitaba para ejecutar mi proyecto.

En lugar de sustituir el intérprete del sistema preferí compilar una copia independiente de Python. De esta forma el sistema operativo continúa utilizando su propia versión, mientras que nuestros proyectos trabajan con otra completamente separada.

Ésta es una práctica que sigo considerando recomendable cuando necesitamos una versión diferente del intérprete sin alterar el funcionamiento del sistema.

Instalar las herramientas de compilación

El primer paso consiste en instalar el compilador y las herramientas básicas de desarrollo.

sudo apt-get install build-essential

A continuación instalamos algunas bibliotecas de desarrollo que Python utiliza para compilar varios módulos de la biblioteca estándar.

sudo apt-get install \
    libsqlite3-dev \
    libreadline-dev \
    libncurses5-dev \
    libgdbm-dev \
    libbz2-dev \
    zlib1g-dev

No todas son estrictamente obligatorias, pero es preferible instalarlas desde el principio para evitar que algunos módulos importantes queden fuera de la compilación.

Descargar y compilar Python

Con el entorno preparado descargamos el código fuente directamente desde el sitio oficial de Python.

Una vez descargado el archivo basta con descomprimirlo.

tar xfz Python-x.y.z.tgz

Entramos al directorio recién creado.

cd Python-x.y.z

y comenzamos la compilación.

El procedimiento consta de tres pasos.

Primero configuramos el código fuente.

./configure

Después compilamos.

make

Y finalmente instalamos el resultado.

sudo make install

No tiene realmente mayor dificultad.

La mayor parte del tiempo simplemente estaremos esperando a que termine la compilación.

Módulos que no pudieron compilarse

Al finalizar es posible que aparezca un mensaje similar al siguiente.

Python build finished, but the necessary bits to build these modules were not found:

_bsddb
_tkinter
imageop
...

La primera vez que vi este mensaje pensé que la compilación había fallado.

En realidad no es así.

Python informa qué módulos no pudieron construirse porque faltan las bibliotecas de desarrollo correspondientes.

En la mayoría de los casos basta con identificar el paquete -dev asociado, instalarlo y volver a ejecutar la compilación.

No todos esos módulos son necesarios.

En mi caso, por ejemplo, no tenía ninguna necesidad de utilizar tkinter en un servidor, por lo que simplemente ignoré ese aviso.

Lo importante es revisar la lista y decidir conscientemente cuáles módulos necesitamos realmente.

Preparando el entorno para Django

Una vez instalado Python llega el momento de preparar el entorno donde vivirán nuestros proyectos.

Mi objetivo era muy simple.

Quería poder instalar diferentes versiones de Django, diferentes bibliotecas y distintos proyectos sin que unos interfirieran con otros.

La herramienta que hace posible esto es virtualenv.

Antes de instalarla necesitamos disponer de pip, el administrador de paquetes de Python.

En aquella época el procedimiento habitual consistía en instalar primero setuptools, que proporciona la utilidad easy_install.

python setup.py install

Una vez instalado setuptools ya podemos instalar pip.

easy_install pip

A partir de este momento todas las bibliotecas de Python pueden instalarse utilizando un único comando.

Instalar virtualenv

Ahora actualizamos virtualenv.

sudo pip install --upgrade virtualenv

y posteriormente instalamos virtualenvwrapper, un conjunto de herramientas que facilita enormemente el manejo de múltiples entornos virtuales.

sudo pip install virtualenvwrapper

Precaución

Procura instalar o actualizar virtualenv y virtualenvwrapper fuera de cualquier entorno virtual activo. De esa forma las herramientas quedarán disponibles para todos tus proyectos.

Configurar virtualenvwrapper

Para mantener organizados mis proyectos acostumbro crear un directorio que contendrá todos los entornos virtuales.

mkdir ~/entornos

Después agrego unas cuantas variables al archivo ~/.bashrc.

# Variables de entorno para virtualenvwrapper

export WORKON_HOME=~/entornos
source /usr/local/bin/virtualenvwrapper.sh

export PIP_VIRTUALENV_BASE=$WORKON_HOME
export PIP_RESPECT_VIRTUALENV=true

La variable más importante es WORKON_HOME, que indica dónde se almacenarán todos los entornos virtuales.

A partir de ese momento basta con abrir una nueva sesión para que virtualenvwrapper quede disponible.

Si trabajas sobre macOS es posible que la ubicación de virtualenvwrapper.sh sea distinta.

En ese caso únicamente deberás ajustar la ruta correspondiente.

Con esto queda preparado todo el entorno de Python.

En la siguiente parte instalaremos PostgreSQL, crearemos nuestro primer entorno virtual y dejaremos listo el servidor para instalar Django y las dependencias del proyecto.

Instalar PostgreSQL

Con Python y el entorno virtual preparados, el siguiente paso es instalar el servidor de base de datos.

Desde hace varios años PostgreSQL se ha convertido en mi primera opción para desarrollar con Django. Su integración con el framework es excelente y ofrece muchas características avanzadas sin necesidad de recurrir a componentes adicionales.

La instalación es muy sencilla.

sudo apt-get install postgresql

También necesitaremos los archivos de desarrollo para poder compilar el adaptador de PostgreSQL para Python.

sudo apt-get install libpq-dev

Una vez terminada la instalación podemos comprobar que el servicio se encuentre funcionando.

sudo service postgresql status

Si todo salió correctamente ya podemos comenzar a crear nuestros usuarios y bases de datos.

Crear un usuario para Django

Una buena práctica consiste en crear un usuario específico para cada aplicación, en lugar de utilizar el administrador de PostgreSQL para todo.

Entramos primero al intérprete de PostgreSQL.

sudo -u postgres psql

Creamos el usuario.

CREATE USER django WITH PASSWORD 'tu_password';

Y posteriormente la base de datos.

CREATE DATABASE mi_proyecto
    OWNER django
    ENCODING 'UTF8';

Finalmente otorgamos los permisos necesarios.

GRANT ALL PRIVILEGES
ON DATABASE mi_proyecto
TO django;

Podemos salir del intérprete escribiendo:

\q

Con esto PostgreSQL queda listo para ser utilizado desde Django.

Crear el entorno virtual

Ahora sí podemos crear el entorno donde vivirá nuestra aplicación.

mkvirtualenv mi_proyecto

Al terminar veremos que el nombre del entorno aparece al inicio del prompt.

(mi_proyecto) javier@servidor:~$

Eso indica que cualquier biblioteca que instalemos a partir de este momento quedará aislada dentro de este proyecto.

Si más adelante necesitamos trabajar en otro desarrollo simplemente crearemos un nuevo entorno virtual.

Para regresar posteriormente a este entorno utilizaremos:

workon mi_proyecto

Y para salir de él:

deactivate

Después de trabajar durante algún tiempo con virtualenvwrapper resulta difícil imaginar administrar varios proyectos sin esta herramienta.

Instalar Django

Con el entorno virtual activo ya podemos instalar Django.

pip install Django

A diferencia de una instalación global, únicamente este entorno tendrá acceso a la versión instalada.

Eso significa que otro proyecto puede utilizar una versión completamente distinta sin producir conflictos.

Ésta es una de las principales ventajas de trabajar con entornos virtuales.

Instalar el adaptador de PostgreSQL

Django necesita un controlador para comunicarse con PostgreSQL.

En aquella época el paquete más utilizado era psycopg2.

La instalación es tan sencilla como cualquier otra biblioteca de Python.

pip install psycopg2

Una vez instalado ya podremos utilizar PostgreSQL como motor de base de datos modificando únicamente la configuración de Django.

Instalar Memcached

Aunque un sitio pequeño puede funcionar perfectamente sin un sistema de caché, instalarlo desde el principio simplifica bastante las cosas cuando el proyecto comienza a crecer.

En mi caso decidí utilizar Memcached.

La instalación se reduce prácticamente a un comando.

sudo apt-get install memcached

Y posteriormente instalamos el cliente para Python.

pip install python-memcached

Más adelante bastará con agregar unas cuantas líneas a la configuración de Django para comenzar a utilizar el sistema de caché.

Crear el proyecto

Con todas las piezas instaladas ya podemos comprobar que el entorno funciona correctamente creando un proyecto de prueba.

django-admin.py startproject ejemplo

Entramos al directorio recién creado.

cd ejemplo

Y ejecutamos el servidor de desarrollo.

python manage.py runserver

Si todo salió correctamente veremos un mensaje parecido al siguiente.

Starting development server at http://127.0.0.1:8000/

Todavía no estamos listos para poner el sitio en producción, pero ya sabemos que Python, Django, PostgreSQL y el entorno virtual funcionan correctamente y pueden trabajar juntos.

En la última parte terminaremos de preparar el servidor, instalaremos las dependencias del proyecto, configuraremos Nginx y dejaremos listo el entorno para publicar una aplicación Django en producción.

Instalar las dependencias del proyecto

Con el servidor listo ya podemos comenzar a preparar nuestra aplicación.

Una de las ventajas de trabajar con pip es que todas las bibliotecas utilizadas por un proyecto pueden documentarse en un solo archivo. De esta manera es posible reconstruir el entorno completo con un único comando.

Durante el desarrollo acostumbro mantener un archivo requirements.txt dentro del proyecto.

Su contenido depende completamente de las necesidades de cada aplicación, por lo que no tiene sentido copiar el de otro proyecto. Lo importante es comprender la idea: el archivo contiene la lista de dependencias necesarias para ejecutar la aplicación.

Una vez copiado el proyecto al servidor y activado el entorno virtual, basta con ejecutar:

pip install -r requirements.txt

pip descargará e instalará automáticamente todas las bibliotecas indicadas, respetando las versiones especificadas en el archivo.

Éste es uno de los mayores beneficios de utilizar entornos virtuales. Todo el software necesario para ejecutar una aplicación queda documentado y puede reconstruirse fácilmente en otro servidor.

Configurar la base de datos

Antes de iniciar el proyecto debemos revisar la configuración de la base de datos.

En settings.py únicamente es necesario indicar los datos correspondientes al servidor PostgreSQL.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'mi_proyecto',
        'USER': 'django',
        'PASSWORD': '********',
        'HOST': 'localhost',
        'PORT': '',
    }
}

Con la configuración lista podemos crear las tablas correspondientes.

python manage.py syncdb

En aquel momento syncdb era el encargado de crear las tablas necesarias para el proyecto y solicitar la creación del usuario administrador.

Una vez finalizado este paso ya es posible acceder al administrador de Django.

Comprobando la instalación

Antes de configurar el servidor web conviene asegurarse de que la aplicación funciona correctamente utilizando el servidor de desarrollo.

python manage.py runserver

Si el proyecto inicia sin errores podemos continuar con tranquilidad.

Siempre prefiero resolver los problemas de configuración en esta etapa. Cuando Nginx, Memcached o cualquier otro componente entra en escena resulta más difícil identificar el origen de un fallo.

Instalar Nginx

El último componente importante del servidor es Nginx.

Desde hace tiempo se ha convertido en uno de mis servidores web favoritos por su bajo consumo de memoria y su excelente desempeño al servir contenido estático.

La instalación es directa.

sudo apt-get install nginx

Una vez instalado podemos comprobar que el servicio se encuentre activo.

sudo service nginx status

Si abrimos la dirección IP del servidor desde un navegador deberíamos ver la página de bienvenida de Nginx.

Ese pequeño detalle confirma que el servidor web está funcionando correctamente antes de integrarlo con Django.

Servir archivos estáticos

Una de las responsabilidades de Nginx consiste en entregar los archivos estáticos de la aplicación.

Imágenes, hojas de estilo, archivos JavaScript y documentos no necesitan pasar por Django para llegar al navegador.

Mientras menos trabajo realice la aplicación, mejor será el desempeño general del sitio.

Por esa razón conviene configurar correctamente el directorio donde Django recopilará todos sus archivos estáticos y permitir que Nginx los entregue directamente.

Una nota sobre el despliegue

Hasta este punto ya tenemos prácticamente todo lo necesario para publicar una aplicación Django.

Disponemos de:

  • un servidor actualizado;
  • un usuario para el trabajo diario;
  • acceso remoto mediante SSH;
  • Python compilado independientemente del sistema;
  • entornos virtuales;
  • PostgreSQL;
  • Memcached;
  • Nginx;
  • Django y todas las dependencias del proyecto.

A partir de aquí comienza una etapa distinta.

Cada proyecto tiene necesidades diferentes. Algunos utilizan uWSGI, otros Gunicorn; algunos requieren balanceadores de carga, varios servidores de aplicaciones o configuraciones específicas de caché.

Por esa razón prefiero detener aquí esta guía.

Mi intención nunca fue construir un procedimiento universal para instalar Django, sino documentar el proceso que seguí para preparar un servidor completamente funcional.

Con el tiempo seguramente cambiarán las versiones de Python, Ubuntu, Django o PostgreSQL. Incluso aparecerán herramientas nuevas que sustituyan a las actuales.

Sin embargo, la idea general seguirá siendo la misma: construir el servidor paso a paso, comprobar que cada componente funciona correctamente antes de continuar y mantener cada responsabilidad claramente separada.

Cuando algo falla, esa disciplina suele ahorrar muchas horas de trabajo.