En esta serie de dos posts voy a explicar desde cero como he desplegado este proyecto en AWS. La arquitectura no tiene demasiado misterio, me he decidido por una arquitectura IaaS con una única instancia EC2 que contenga tanto la capa de aplicación como la base de datos. La razón es sencilla. Es lo más eficiente en coste para un proyecto de este tipo
1. Conexión a EC2 e instalación de aplicaciones necesarias
Primero vamos a conectar a la instancia EC2 utilizando para ello la clave privada generada al levantar la instancia:
ssh -i key_pair.pem admin@ec2-54-172-3-252.compute-1.amazonaws.com admin@ip-172-31-88-32:~$
Antes de nada, lanzamos un apt-get update para actualizar los repositorios:
admin@ip-172-31-88-32:~$ sudo apt-get update Get:1 http://security.debian.org/debian-security bullseye-security InRelease [48.4 kB] Get:2 http://cdn-aws.deb.debian.org/debian bullseye InRelease [116 kB] Get:3 http://cdn-aws.deb.debian.org/debian bullseye-updates InRelease [44.1 kB] Get:4 http://cdn-aws.deb.debian.org/debian bullseye-backports InRelease [49.0 kB] Get:5 http://security.debian.org/debian-security bullseye-security/main Sources [173 kB] Get:6 http://security.debian.org/debian-security bullseye-security/main amd64 Packages [209 kB] Get:7 http://security.debian.org/debian-security bullseye-security/main Translation-en [135 kB] Get:8 http://cdn-aws.deb.debian.org/debian bullseye/main Sources [8633 kB] Get:9 http://cdn-aws.deb.debian.org/debian bullseye/main amd64 Packages [8184 kB] Get:10 http://cdn-aws.deb.debian.org/debian bullseye/main Translation-en [6239 kB] Get:11 http://cdn-aws.deb.debian.org/debian bullseye-updates/main Sources [4812 B] Get:12 http://cdn-aws.deb.debian.org/debian bullseye-updates/main amd64 Packages [14.6 kB] Get:13 http://cdn-aws.deb.debian.org/debian bullseye-updates/main Translation-en [7929 B] Get:14 http://cdn-aws.deb.debian.org/debian bullseye-backports/main Sources [365 kB] Get:15 http://cdn-aws.deb.debian.org/debian bullseye-backports/main amd64 Packages [367 kB] Get:16 http://cdn-aws.deb.debian.org/debian bullseye-backports/main Translation-en [301 kB] Fetched 24.9 MB in 4s (6167 kB/s) Reading package lists... Done
Ahora sí, instalamos el software necesario para poder levantar nuestro site. Se trata de un proyecto Django con BBDD Postgre, por lo que instalaremos lo estrictamente necesario para empezar:
- Python3
- Pip
- Postgresql
- Virtualenv
Y adicionalmente vamos a instalar Git para ayudarnos a clonar el repositorio desde Github que será el que levantemos en la instancia.
De momento nos vamos a centrar únicamente en instalar lo básico para poder levantar el proyecto Django. Luego ya profundizaremos para montar nginx, etc.
admin@ip-172-31-88-32:~$ sudo apt-get install git virtualenv postgresql python3-pip
Tras las instalaciones chequeamos las versiones instaladas de Python, pip, virtualenv y postgresql:
admin@ip-172-31-88-32:~$ python3 --version Python 3.9.2 admin@ip-172-31-88-32:~$ pip --version pip 20.3.4 from /usr/lib/python3/dist-packages/pip (python 3.9) admin@ip-172-31-88-32:~$ virtualenv --version virtualenv 20.4.0+ds from /usr/lib/python3/dist-packages/virtualenv/__init__.py
Tras tartar de conectar postgre se recibirán un error de localización no definida. Similar a esto:
admin@ip-172-31-88-32:~$ sudo -u postgres psql perl: warning: Setting locale failed. perl: warning: Please check that your locale settings: LANGUAGE = (unset), LC_ALL = (unset), LC_ADDRESS = "es_ES.UTF-8", LC_NAME = "es_ES.UTF-8", LC_MONETARY = "es_ES.UTF-8", LC_PAPER = "es_ES.UTF-8", LC_IDENTIFICATION = "es_ES.UTF-8", LC_TELEPHONE = "es_ES.UTF-8", LC_MEASUREMENT = "es_ES.UTF-8", LC_NUMERIC = "es_ES.UTF-8", LANG = "C.UTF-8" are supported and installed on your system. perl: warning: Falling back to a fallback locale ("C.UTF-8"). psql (13.8 (Debian 13.8-0+deb11u1)) Type "help" for help.
Para solucionarlo vamos a generar los locales que requiera nuestra aplicación. En mi caso es-ES.UTF-8:
admin@ip-172-31-88-32:~$ sudo dpkg-reconfigure locales perl: warning: Setting locale failed. perl: warning: Please check that your locale settings: LANGUAGE = (unset), LC_ALL = "es_ES.UTF-8", LC_ADDRESS = "es_ES.UTF-8", LC_NAME = "es_ES.UTF-8", LC_MONETARY = "es_ES.UTF-8", LC_PAPER = "es_ES.UTF-8", LC_IDENTIFICATION = "es_ES.UTF-8", LC_TELEPHONE = "es_ES.UTF-8", LC_MEASUREMENT = "es_ES.UTF-8", LC_CTYPE = "es_ES.UTF-8", LC_NUMERIC = "es_ES.UTF-8", LANG = "C.UTF-8" are supported and installed on your system. perl: warning: Falling back to a fallback locale ("C.UTF-8"). Generating locales (this might take a while)... en_US.UTF-8... done es_ES.UTF-8... done Generation complete.
Ahora ya si deberíamos poder conectar a postgre, y vemos que tenemos instalada la versión 13.8:
admin@ip-172-31-88-32:~$ sudo -u postgres psql psql (13.8 (Debian 13.8-0+deb11u1)) Digite «help» para obtener ayuda. postgres=#
Estamos listos para empezar la configuración del entorno
2. Creando del entorno virtual con virtualenv
De cara a tener un entorno aislado y con las dependencias requeridas para levantar nuestro site Django, vamos a utilizar virtualenv. Es decir, NO vamos a instalar las dependencias cross-instancia, porque eso haría que si a futuro queremos utilizar esta instancia para levantar otro tipo de proyectos en paralelo, pudiésemos tener problemas de dependencias.
Para ello debemos indicar a virtualenv que versión de Python queremos utilizar. Anteriormente vimos que estamos utilizando la versión 3.9.2. Pero veamos cual es el interprete real y donde se ubica en nuestra máquina:
admin@ip-172-31-88-32:~$ which python3 /usr/bin/python3 admin@ip-172-31-88-32:~$ ls -l /usr/bin/pyth* lrwxrwxrwx 1 root root 9 abr 5 2021 /usr/bin/python3 -> python3.9 -rwxr-xr-x 1 root root 5479736 feb 28 2021 /usr/bin/python3.9 lrwxrwxrwx 1 root root 33 feb 28 2021 /usr/bin/python3.9-config -> x86_64-linux-gnu-python3.9-config lrwxrwxrwx 1 root root 16 abr 5 2021 /usr/bin/python3-config -> python3.9-config
Ahora si lo tenemos todo. Python3 es un enlace simbólico a /usr/bin/python3.9. Por tanto vamos a utilizar explícitamente este python3.9 para crear el entorno virtual:
admin@ip-172-31-88-32:~$ virtualenv -p /usr/bin/python3.9 webenv created virtual environment CPython3.9.2.final.0-64 in 253ms creator CPython3Posix(dest=/home/admin/webenv, clear=False, no_vcs_ignore=False, global=False) seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/admin/.local/share/virtualenv) added seed packages: pip==20.3.4, pkg_resources==0.0.0, setuptools==44.1.1, wheel==0.34.2 activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
Ya lo deberíamos tener creado con el nombre de webenv en el directorio home del usuario:
admin@ip-172-31-88-32:~$ ls webenv
Por último, vamos a activar el entorno:
admin@ip-172-31-88-32:~$ source webenv/bin/activate (webenv) admin@ip-172-31-88-32:~$
Perfecto, el (webenv) antes del prompt nos indica que el entorno ha quedado activo y funcionando. Estamos listaos para instalar las dependencias, Django y todo lo que necesite nuestro proyecto.
3. Clonar el repositorio
Antes de continuar vamos a clonarnos el repositorio del blog. Primero hacemos un “deactivate” para desactivar el entorno virtual (aunque este paso creo que es irrelevante, ya que el clonado del repositorio en cualquier caso no quedará asociado al entorno virtual):
(webenv) admin@ip-172-31-88-32:~$ deactivate admin@ip-172-31-88-32:~$ admin@ip-172-31-88-32:~$ git clone https://github.com/IsmaelB83/laestanciaazul.git Clonando en 'laestanciaazul'... remote: Enumerating objects: 9733, done. remote: Counting objects: 100% (244/244), done. remote: Compressing objects: 100% (165/165), done. remote: Total 9733 (delta 106), reused 171 (delta 78), pack-reused 9489 Recibiendo objetos: 100% (9733/9733), 20.44 MiB | 26.87 MiB/s, listo. Resolviendo deltas: 100% (3394/3394), listo.
Una vez tenemos el repositorio clonado, y nos hemos copiado nuestro passwords.json (ver instrucciones del repo para configurarlo en detalle), vamos a pasar a instalar todas las dependencias del fichero requirements.txt. Para eso es necesario volver a activar el entorno virtual previamente. Esto es MUY IMPORTANTE para no ensuciar de dependencias nuestra instancia. Ahora mismo nuestro entorno virtual debería tener unas dependencias mínimas:
(webenv) admin@ip-172-31-88-32:~$ pip list Package Version ------------- ------- pip 20.3.4 pkg-resources 0.0.0 setuptools 44.1.1 wheel 0.34.2 (webenv) admin@ip-172-31-88-32:~$
Accedemos a la ruta del proyecto donde está el fichero requirements.txt e instalamos las dependencias:
(webenv) admin@ip-172-31-88-32:~$ cd laestanciaazul/ (webenv) admin@ip-172-31-88-32:~/laestanciaazul$ pip install -r requirements.txt Collecting Django==4.1.4 Downloading Django-4.1.4-py3-none-any.whl (8.1 MB) |████████████████████████████████| 8.1 MB 25.5 MB/s Collecting django-ckeditor==5.4.0 Downloading django-ckeditor-5.4.0.tar.gz (1.6 MB) |████████████████████████████████| 1.6 MB 35.7 MB/s Collecting django-widget-tweaks==1.4.12 Downloading django_widget_tweaks-1.4.12-py3-none-any.whl (8.9 kB) Collecting django-wysiwyg==0.8.0 Downloading django_wysiwyg-0.8.0-py3-none-any.whl (23 kB) Collecting Pillow==9.3.0 Downloading Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl (3.3 MB) |████████████████████████████████| 3.3 MB 23.1 MB/s Collecting psycopg2-binary==2.9.5 Downloading psycopg2_binary-2.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.0 MB) |████████████████████████████████| 3.0 MB 23.5 MB/s Collecting urllib3==1.26.5 Downloading urllib3-1.26.5-py2.py3-none-any.whl (138 kB) |████████████████████████████████| 138 kB 42.3 MB/s Collecting sqlparse>=0.2.2 Downloading sqlparse-0.4.3-py3-none-any.whl (42 kB) |████████████████████████████████| 42 kB 2.5 MB/s Collecting asgiref<4,>=3.5.2 Downloading asgiref-3.5.2-py3-none-any.whl (22 kB) Collecting django-js-asset Downloading django_js_asset-2.0.0-py3-none-any.whl (4.9 kB) Building wheels for collected packages: django-ckeditor Building wheel for django-ckeditor (setup.py) ... done Created wheel for django-ckeditor: filename=django_ckeditor-5.4.0-py2.py3-none-any.whl size=2256438 sha256=6e7966d319b045764adbdb03b2ee16d5d5af285dc0a1af6c549b7c8a59bb4be9 Stored in directory: /home/admin/.cache/pip/wheels/f5/1f/6c/5c892fd93990877b657249cc2e63bfc2a27667ceb7bf5871b5 Successfully built django-ckeditor Installing collected packages: sqlparse, asgiref, Django, django-js-asset, urllib3, psycopg2-binary, Pillow, django-wysiwyg, django-widget-tweaks, django-ckeditor Successfully installed Django-4.1.4 Pillow-9.3.0 asgiref-3.5.2 django-ckeditor-5.4.0 django-js-asset-2.0.0 django-widget-tweaks-1.4.12 django-wysiwyg-0.8.0 psycopg2-binary-2.9.5 sqlparse-0.4.3 urllib3-1.26.5 (webenv) admin@ip-172-31-88-32:~/laestanciaazul$
Todo listo, ya tenemos nuestro entorno “virtual” preparado.
4. Preparar instancia para aceptar conexiones remotas con public_key y crear usuario NO admin
En este punto me doy cuenta de que he realizado toda la instalación con el usuario “admin” el cual posee permisos de root de toda la instancia. Lo cual es una muy mala práctica de seguridad. Por tanto, voy a crear primero un usuario con permisos NO root, y volcaré a su directorio home toda la configuración realizada hasta ahora. Como se ha realizado la configuración mediante virtualenv. Es muy sencillo copiar el entorno.
admin@ip-172-31-88-32:~/laestanciaazul$ sudo adduser trama Añadiendo el usuario `trama' ... Añadiendo el nuevo grupo `trama' (1001) ... Añadiendo el nuevo usuario `trama' (1001) con grupo `trama' ... Creando el directorio personal `/home/trama' ... Copiando los ficheros desde `/etc/skel' ... Nueva contraseña: Vuelva a escribir la nueva contraseña: passwd: contraseña actualizada correctamente Cambiando la información de usuario para trama Introduzca el nuevo valor, o pulse INTRO para usar el valor predeterminado Nombre completo []: Ismael Número de habitación []: Teléfono del trabajo []: Teléfono de casa []: Otro []: ¿Es correcta la información? [S/n] S admin@ip-172-31-88-32:~/laestanciaazul$ sudo usermod -aG sudo trama admin@ip-172-31-88-32:~/laestanciaazul$ cd .. admin@ip-172-31-88-32:~$ ls laestanciaazul webenv admin@ip-172-31-88-32:~$ sudo mv laestanciaazul/ /home/trama/ admin@ip-172-31-88-32:~$ sudo mv webenv/ /home/trama/ admin@ip-172-31-88-32:~$ ls -a . .. .bash_history .bash_logout .bashrc .cache .local .profile .python_history .ssh admin@ip-172-31-88-32:~$
NOTA: tras esto será necesario volver a hacer el pip install -r requirements.txt una vez logados con el nuevo usuario y teniendo la webenv activa.
5. Preparar instancia para aceptar conexiones remotas con public_key
En este punto también nos interesa habilitar la posibilidad de que el usuario trama se pueda conectar a la instancia por ssh sin necesidad de utilizar el fichero .pem generado. Que debería utilizarse únicamente para acceder con el usuario admin (en muy contadas ocasiones).
Para ello vamos a habilitar en el .ssh/authorized_keys la clave publica de nuestro equipo (no voy a entrar en explicar en este manual como generar una clave publica/privada. Hay varios manuales en internet para ver el proceso).
admin@ip-172-31-88-32:~$ cd .ssh/ admin@ip-172-31-88-32:~/.ssh$ ls authorized_keys admin@ip-172-31-88-32:~/.ssh$ nano authorized_keys
Básicamente accedemos al fichero “authorized_keys” y añadimos tras la clave .pem generada por AWS, la clave publica de nuestro equipo para que pueda conectar sin necesidad de utilización del fichero .pem. Esto lo hacemos tanto para el usuario admin como para el nuevo usuario trama.
Ahora ya podemos conectar sin necesidad del fichero .pem:
ssh trama@ec2-54-172-3-252.compute-1.amazonaws.com trama@ip-172-31-88-32:~$
6. Preparar la base de datos
En este punto voy a crear la base de datos (vacía) y el usuario que tendrá acceso a la misma.
trama@ip-172-31-88-32:~$ sudo -u postgres psql postgres=# CREATE DATABASE laestanciaazul; CREATE DATABASE postgres=# CREATE USER trama WITH PASSWORD 'xxxxxxxxx'; CREATE ROLE postgres=# ALTER ROLE trama SET client_encoding TO 'utf8'; ALTER ROLE postgres=# ALTER ROLE trama SET default_transaction_isolation TO 'read committed'; ALTER ROLE postgres=# ALTER ROLE trama SET timezone TO 'UTC'; ALTER ROLE postgres=# GRANT ALL PRIVILEGES ON DATABASE laestanciaazul TO trama; GRANT postgres=# \q (webenv) trama@ip-172-31-88-32:~$
En este punto lo normal sería hacer un migrate y makemigrations con ./manage.py de Django, pero yo estoy haciendo montando el site desde otro equipo, por lo que voy a restaurar la base de datos que tengo de backup mediante pg_restore.
Para ello lo primero es utilizar sshfs para montar la estructura de ficheros remota en mi equipo (punto de montaje /media/awsdjango):
sshfs trama@ec2-54-172-3-252.compute-1.amazonaws.com:/home/trama /media/awsdjango
A partir de aquí puedo acceder a la estructura de ficheros de la instancia EC2 desde mi local. Pudiendo copiar los ficheros que necesite para poder luego trabajarlos desde la instancia. Esto es una alternativa sencilla y rapida al uso de un sftp o similar cuando tenemos montado SSH en el servidor remoto.
trama@ip-172-31-88-32:~$ pg_restore -d laestanciaazul laestanciaazul09122022.sql
7. Levantar el sitio en modo debug
En este punto estamos listos para hacer un “collectstatics” y levantar el sitio en modo debug con runserver.
(webenv) trama@ip-172-31-88-32:~/laestanciaazul$ python3 ./manage.py collectstatic 1266 static files copied to '/home/trama/cdn_static'. (webenv) trama@ip-172-31-88-32:~/laestanciaazul$ python3 ./manage.py runserver 0.0.0.0:8080 Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). December 10, 2022 - 10:42:00 Django version 4.1.4, using settings 'web.settings' Starting development server at http://0.0.0.0:8080/ Quit the server with CONTROL-C.