From 8bb5ec1730284c394553c6c854527b83c3e10ab4 Mon Sep 17 00:00:00 2001 From: jaguarondi Date: Sun, 15 May 2016 21:26:57 +0200 Subject: [PATCH] Update production --- source/production/admin_with_static.png | Bin 0 -> 6515 bytes source/production/environment.rst | 33 +++++++++- source/production/gunicorn.rst | 82 ++++++++++++++++++++++++ source/production/monitoring.rst | 55 +++++++++++++--- source/production/nginx.rst | 47 ++++++++++++-- 5 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 source/production/admin_with_static.png diff --git a/source/production/admin_with_static.png b/source/production/admin_with_static.png new file mode 100644 index 0000000000000000000000000000000000000000..66af9167e49c46d5f85cfe5c30dc75cfa5cd9e93 GIT binary patch literal 6515 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 literal 0 HcmV?d00001 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 =======================