diff --git a/source/production/admin_with_static.png b/source/production/admin_with_static.png new file mode 100644 index 0000000..66af916 Binary files /dev/null and b/source/production/admin_with_static.png differ diff --git a/source/production/environment.rst b/source/production/environment.rst index 8fb9ad3..6c87f92 100644 --- a/source/production/environment.rst +++ b/source/production/environment.rst @@ -74,7 +74,7 @@ Il ne nous reste plus qu'à mettre à jour la DB. On commance par créer le fich (gwift)gwift@gwift:~$ touch gwift/gwift/settings/local.py -Et le contenu de local.py, avec la clé secrète et les paramètres pour se connecter à la DB: +Et le contenu de local.py, avec la clé secrète, les paramètres pour se connecter à la DB et l'endroit où mettre les fichiers statics (voir point suivant): .. code-block:: python @@ -88,6 +88,12 @@ Et le contenu de local.py, avec la clé secrète et les paramètres pour se conn # Allowed host needed to be defined in production ALLOWED_HOSTS = ["sever_name.com", "www.sever_name.com"] + + # Be sure to force https for csrf cookie + CSRF_COOKIE_SECURE = True + + # Same for session cookie + SESSION_COOKIE_SECURE = True # DB DATABASES = { @@ -100,6 +106,13 @@ Et le contenu de local.py, avec la clé secrète et les paramètres pour se conn 'PORT': '', # Set to empty string for default. } } + + # Add static root + STATIC_ROOT = "/webapps/gwift/gwift/static" + + STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), + ] Finalement, on peut mettre à jour la DB et créer un super utilisateur: @@ -108,6 +121,24 @@ Finalement, on peut mettre à jour la DB et créer un super utilisateur: (gwift)gwift@gwift:~$ python manage.py migrate (gwift)gwift@gwift:~$ python manage.py createsuperuser +Fichiers statics +================ + +Django n'est pas fait pour servir les fichiers statics. Tous les fichiers statics doivent donc être déplacés dans un répertoire pour que Nginx puisse les servir facilement. + +On commence par créer le répertoire où mettre les fichiers statics comme configuré dans le fichier local.py: + +.. code-block:: shell + + (gwift)gwift@gwift:~$ mkdir /webapps/gwift/gwift/static + +Et on utilise django pour copier tous les fichiers statics au bon endroit: + +.. code-block:: shell + + (gwift)gwift@gwift:~$ python manage.py collectstatic + + Test ==== diff --git a/source/production/gunicorn.rst b/source/production/gunicorn.rst index e69de29..beef77a 100644 --- a/source/production/gunicorn.rst +++ b/source/production/gunicorn.rst @@ -0,0 +1,82 @@ +************* +Gunicorn +************* + +Nous allons utiliser ``gunicorn`` comme serveur d'applications, le serveur fourni par django n'étant pas fait pour être utilisé en production. + +Gunicorn a déjà été installé lors de la préparation de l'environnement. De même que ``setproctitle``, qui est nécessaire pour donner le nom de l'application aux processus python lancés par gunicorn. + +Nous pouvons donc directement tester s'il fonctionne: + +.. code-block:: shell + + (gwift)gwift@gwift:~$ gunicorn gwift.wsgi:application --bind esever_name.com:8000 + +Et en se rendant sur server_name.com:8000/admin, on obtient la même chose qu'avec le serveur de django: + +.. image:: production/admin_without_static.png + :align: center + +Nous allons maintenant créer un ficheir qui se chargera de lancer gunicorn correctement, que l'on sauve dans ``/webapps/gwift/gwift/bin/gunicorn_start``: + +.. code-block:: shell + + #!/bin/bash + + # Define settings for gunicorn + NAME="gwift" # Name of the application + DJANGODIR=/webapps/gwift/gwift/gwift # Django project directory + SOCKFILE=/webapps/gwift/gwift/run/gunicorn.sock # we will communicte using this unix socket + USER=gwift # the user to run as + GROUP=webapps # the group to run as + NUM_WORKERS=3 # how many worker processes should Gunicorn spawn + DJANGO_SETTINGS_MODULE=gwift.settings # which settings file should Django use + DJANGO_WSGI_MODULE=gwift.wsgi # WSGI module name + + echo "Starting $NAME as `whoami`" + + # Activate the virtual environment + source /webapps/gwift/.virtualenvs/gwift/bin/activate + cd $DJANGODIR + export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE + export PYTHONPATH=$DJANGODIR:$PYTHONPATH + + # Create the run directory if it doesn't exist + RUNDIR=$(dirname $SOCKFILE) + test -d $RUNDIR || mkdir -p $RUNDIR + + # Start your Django Unicorn + # Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon) + exec gunicorn ${DJANGO_WSGI_MODULE}:application \ + --name $NAME \ + --workers $NUM_WORKERS \ + --user=$USER --group=$GROUP \ + --bind=unix:$SOCKFILE \ + --log-level=debug \ + --log-file=- + +Explications: + + * NUM_WORKERS : gunicorn lance autant de worker que ce nombre. Un worker représente l'équivallant d'une instance de django et ne peut traiter qu'une requête à la fois. Traditionnellement, on créé autant de worker que le double du nombre de processeurs plus un. + * SOCKFILE : on configure gunicorn pour communiquer via un socket unix, ce qui est plus efficace de le faire par tcp/ip + +Le script se charge donc de définir les options de configuration de gunicorn, de lancer l'environnement virtuel et ensuite gunicorn. + +On peut le tester avec la commande suivante (hors environnement virtuel): + +.. code-block:: shell + + gwift@gwift:~$ source /webapps/gwift/gwift/bin/gunicorn_start + +Et avec un petit ``ps`` dans un autre shell: + +.. code-block:: shell + + gwift@gwift:~$ ps -u gwift -F + UID PID PPID C SZ RSS PSR STIME TTY TIME CMD + gwift 31983 15685 0 18319 15084 1 Apr29 ? 00:00:01 gunicorn: master [gwift] + gwift 31992 31983 0 35636 29312 1 Apr29 ? 00:00:00 gunicorn: worker [gwift] + gwift 31993 31983 0 35634 29280 2 Apr29 ? 00:00:00 gunicorn: worker [gwift] + gwift 31994 31983 0 35618 29228 0 Apr29 ? 00:00:00 gunicorn: worker [gwift] + +On voit donc bien qu'il y a un maître et trois workers. \ No newline at end of file diff --git a/source/production/monitoring.rst b/source/production/monitoring.rst index 360e5ba..eefd651 100644 --- a/source/production/monitoring.rst +++ b/source/production/monitoring.rst @@ -2,15 +2,50 @@ Monitoring ********** +Pour lancer et surveiller gunicorn, nous allons utiliser supervisord, qui est un démon sous lunix permettant de lancer d'autre programmes. + +On commence par l'installer: + .. code-block:: shell - [program:simple] - command={{ virtualenv_path }}/bin/gunicorn simple.app:app -w {{ workers }} --log-file={{ appdir }}/logs/gunicorn.log --bind 127.0.0.1:{{ proxy_port }} - directory={{ appdir }}/ - environment=PATH="{{ virtualenv_path }}/bin" - user=nobody - autostart=true - autorestart=true - killasgroup = true - redirect_stdout=true - redirect_stderr=true \ No newline at end of file + $$$ aptitude install supervisor + +On crée ensuite une fichier de configuration, ``/etc/supervisor/conf.d/gwift.conf``, qui sera lu au démarage de supervisord. + +.. code-block:: shell + + [program:gwift] + command = /webapps/gwift/gwift/bin/gunicorn_start ; Command to start + user = gwift ; User to run as + stdout_logfile = /webapps/gwift/gwift/logs/gunicorn_supervisor.log ; Where to write log messages + environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8 ; Set UTF-8 as default encoding + autostart=true ; Start the program when supervisord is starting + autorestart=unexpected ; Restart program if it exited anormaly + redirect_stdout=true ; Redirect program output to the log file + redirect_stderr=true ; Redirect program error to the log file + +On peut alors démarer supervisor: + + +.. code-block:: shell + + $$$ service supervisor start + +On peut vérifier que notre site est bien en train de tourner, à l'aide de la commande ``supervisorctl``: + +.. code-block:: shell + + $$$ supervisorctl status gwift + gwift RUNNING pid 31983, uptime 0:01:00 + +Pour gérer le démarage ou l'arrêt de notre application, nous pouvons effectuer les commandes suivantes: + +.. code-block:: shell + + $$$ supervisorctl stop gwift + gwift: stopped + root@ks3353535:/etc/supervisor/conf.d# supervisorctl start gwift + gwift: started + root@ks3353535:/etc/supervisor/conf.d# supervisorctl restart gwift + gwift: stopped + gwift: started diff --git a/source/production/nginx.rst b/source/production/nginx.rst index 5d317b5..f8cf293 100644 --- a/source/production/nginx.rst +++ b/source/production/nginx.rst @@ -7,6 +7,12 @@ FrontEnd Nginx est là pour agir en tant que front-end Web. A moins d'avoir configuré un mécanisme de cache type `Varnish `_, c'est lui qui va recevoir la requête envoyée par l'utilisateur, gérer les fichiers et les informations statiques, et transmettre toute la partie dynamique vers Gunicorn. +Pour l'installer, on effectue la commande suivante: + +.. code-block:: shell + + $$$ aptitude install nginx + L'exemple ci-dessous se compose de plusieurs grandes parties: commune (par défaut), static, uploads, racine. Partie commune @@ -18,7 +24,7 @@ Partie commune * keepalive ?? * La compression Gzip doit-elle être activée ? * Avec quels paramètres ? [gzip_comp_level 7, gzip_proxied any] - * Quels types de fichiers GZip doit-il prendre en compte ? [ + * Quels types de fichiers GZip doit-il prendre en compte ? * Où les fichiers de logs doivent-ils être stockés ? [/logs/access.log & /logs/error.log] Fichiers statiques @@ -36,13 +42,33 @@ Si vous souhaitez implémenter un mécanisme d'accès géré, supprimez cette pa Racine ------ -La partie racine de votre domaine ou sous-domaine fera simplement le *pass_through* vers l'instance Gunicorn. En gros, et comme déjà expliqué, Gunicorn tourne en local sur un port (eg. 8001); la requête qui arrive sur le port 80 ou 443 est prise en compte par NGinx, puis transmise à Gunicorn sur le port 8001. Ceci est complétement transparent pour l'utilisateur de notre application. +La partie racine de votre domaine ou sous-domaine fera simplement le *pass_through* vers l'instance Gunicorn via un socket unix. En gros, et comme déjà expliqué, Gunicorn tourne en local sur un port (eg. 8001); la requête qui arrive sur le port 80 ou 443 est prise en compte par NGinx, puis transmise à Gunicorn sur le port 8001. Ceci est complétement transparent pour l'utilisateur de notre application. + +On délare un upstream pour préciser à nginx comment envoyer les requêtes à gunicorn: + +.. code-block:: shell + + upstream gwift_server { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response (in case the Unicorn master nukes a + # single worker for timing out). + + server unix:/directory/to/gunicorn.sock fail_timeout=0; + } Au final -------- .. code-block:: shell + upstream gwift_server { + # fail_timeout=0 means we always retry an upstream even if it failed + # to return a good HTTP response (in case the Unicorn master nukes a + # single worker for timing out). + + server unix:/directory/to/gunicorn.sock fail_timeout=0; + } + server { listen 80; client_max_body_size 4G; @@ -58,7 +84,7 @@ Au final error_log {{ cwd }}/logs/error.log; location /static/ { - alias {{ static_root }}/; + alias /webapps/gwift/gwift/static; gzip on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js; gzip_comp_level 9; @@ -80,10 +106,23 @@ Au final proxy_set_header Host $http_host; proxy_redirect off; - proxy_pass http://127.0.0.1:{{ proxy_port }}; + proxy_pass http://gwift_server; } } +Dans notre cas, et à adbater suivant les besoins, nous avans créé le fichier ``/etc/nginx/sites-available/gwift`` et créé un lien symbolique dans ``/etc/nginx/sites-enabled/gwift`` pour l'activer. Ensuite, nous pouvons redémarer nginx: + +.. code-block:: shell + + $$$ service nginx restart + +Et maintenant, si on se connecte à notre server sur www.sever_name.com/admin, nous obtenons le site suivant: + +.. image:: production/admin_with_static.png + :align: center + +Où l'on peut voir que la mise en forme est correcte, ce qui signifie que les fichiers statics sont bien servis par nginx. + Modules complémentaires =======================