Deployment
Configuration
In production, you should create a dedicated user for your Daiquiri application. All steps for the installation, which do not need root access, should be done using this user. As before, we assume this user is called daiquiri
and it’s home is /srv/daiquiri
and therefore your app
is located in /srv/daiquiri/app
.
In addition, a few more settings need to be configured in your .env
file. The most important change is to set DEBUG=False
.
DEBUG=False
ALLOWED_HOSTS=<hostname>
# ADMINS will get E-Mails in case of an error
ADMINS=Anna Admin <admin@example.com>, Manni Manager <manager@example.com>
LOG_DIR=/var/log/django/daiquiri
Web server
Daiquiri can be run in different configurations, both with Apache2 and NGINX as web server. Daiquiri itself is using the wsgi protocol for the communication between the HTTP and the Python layer. The recommended way of deploying Daiquiri is using Apache2 as a reverse proxy and Gunicorn as wsgi server.
For this setup, you need to add:
PROXY=True
to your .env
file.
First install Gunicorn inside your virtual environment:
pip install gunicorn
Then, test gunicorn
using:
gunicorn --bind 0.0.0.0:8000 config.wsgi:application
This should serve the application like runserver
, but without the static assets, like CSS files and images. After the test kill the gunicorn
process again.
Systemd will launch the gunicorn
process on startup and keep running. In order to start/restart/stop the web application as well as the asyncronous workers with one command, first create a pseudo-service for your Daiquiri application by creating the file /etc/systemd/system/daiquiri.service
(you will need root/sudo permissions for that):
[Unit]
Description=pseudo-service for all Daiquiri services
[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes
[Install]
WantedBy=network.target
Then create the systemd service file for the actual web application in /etc/systemd/system/daiquiri-app.service
:
[Unit]
Description=Daiquiri gunicorn daemon
PartOf=daiquiri.service
After=daiquiri.service
[Service]
User=daiquiri
Group=daiquiri
WorkingDirectory=/srv/daiquiri/app
EnvironmentFile=/srv/daiquiri/app/.env
Environment=GUNICORN_BIN=/srv/daiquiri/app/env/bin/gunicorn
Environment=GUNICORN_WORKER=5
Environment=GUNICORN_PORT=9000
Environment=GUNICORN_TIMEOUT=120
Environment=GUNICORN_PID_FILE=/var/run/gunicorn/daiquiri/pid
Environment=GUNICORN_ACCESS_LOG_FILE=/var/log/gunicorn/daiquiri/access.log
Environment=GUNICORN_ERROR_LOG_FILE=/var/log/gunicorn/daiquiri/error.log
ExecStart=/bin/sh -c '${GUNICORN_BIN} \
--workers ${GUNICORN_WORKER} \
--pid ${GUNICORN_PID_FILE} \
--bind localhost:${GUNICORN_PORT} \
--timeout ${GUNICORN_TIMEOUT} \
--access-logfile ${GUNICORN_ACCESS_LOG_FILE} \
--error-logfile ${GUNICORN_ERROR_LOG_FILE} \
config.wsgi:application'
ExecReload=/bin/sh -c '/usr/bin/pkill -HUP -F ${GUNICORN_PID_FILE}'
ExecStop=/bin/sh -c '/usr/bin/pkill -TERM -F ${GUNICORN_PID_FILE}'
[Install]
WantedBy=daiquiri.target
The setup needs to have several directories for logfiles set up with the correct permissions. This can be done using tmpfiles.d. First, create a file /etc/tmpfiles.d/daiquiri.conf
:
d /run/celery/daiquiri 750 daiquiri daiquiri
d /run/gunicorn/daiquiri 750 daiquiri daiquiri
d /var/log/django/daiquiri 750 daiquiri daiquiri
d /var/log/celery/daiquiri 750 daiquiri daiquiri
d /var/log/gunicorn/daiquiri 755 daiquiri daiquiri
Then run:
systemd-tmpfiles --create
The daiquiri
systemd service needs to be started and enabled like any other service:
sudo systemctl daemon-reload
sudo systemctl start daiquiri
sudo systemctl enable daiquiri
Next, install the web server. On Debian/Ubuntu use:
sudo apt install apache2 libapache2-mod-xsendfile
sudo a2enmod proxy
sudo a2enmod remoteip
sudo a2enmod headers
and on CentOS 7/8 use
sudo yum install httpd mod_xsendfile
Then, edit the virtual host configuration to create a reverse proxy to the Gunicorn server:
# in /etc/apache2/sites-available/000-default.conf on Debian/Ubuntu
# in /etc/httpd/conf.d/vhost.conf on RHEL/CentOS
<VirtualHost *:80>
...
DocumentRoot "/var/www/html"
XSendFile on
XSendFilePath <FILES_BASE_PATH from the Daiquiri settings>
XSendFilePath <QUERY_DOWNLOAD_PATH from the Daiquiri settings>
RequestHeader set X-Forwarded-Proto 'https' env=HTTPS
ProxyPass /static !
ProxyPass /cms !
ProxyPass / http://localhost:9000/
ProxyPassReverse /dev http://localhost:9000/
Alias /static/ /srv/daiquiri/app/static_root/
<Directory /srv/daiquiri/app/static_root/>
Require all granted
</Directory>
# if you intent to use the WordPress integration
Alias /cms/ /opt/wordpress/
<Directory /opt/wordpress/>
AllowOverride all
Require all granted
</Directory>
<Location /cms/wp-json/>
Deny from all
</Location>
</VirtualHost>
On CentOS selinux
needs to be set to persive (or configured properly):
setenforce permissive
Start the Apache server:
sudo systemctl restart apache2 # on Debian/Ubuntu
sudo systemctl restart httpd # on RHEL/CentOS
Your Daiquiri app should now be available on the configured virtual host, but again, without the static assets, like CSS files and images.
Static assets
As you can see from the virtual host configurations, the static assets such as CSS and JavaScript files are served independently from the WSGI-python script. In order to do so, they need to be gathered in the static_root
directory. This can be achieved by running:
python manage.py collectstatic
in your virtual environment. The Apache user needs to have read permissions to /srv/daiquiri/app/static_root/
.
In order to apply changes to the code, the daiquiri-app
job needs to be reloaded.
Asyncronous workers
In production, and especially if you intent to run several Daiquiri applications on the same RabbitMQ instance, it is recomended to use virtual hosts and users with RabbitMQ. In order to create a virtual host and a user use the following commands on your RabbitMQ host:
# first enable the managemetn interface and crate an admin user
rabbitmq-plugins enable rabbitmq_management
rabbitmqctl add_user admin <a secret password>
rabbitmqctl set_user_tags admin administrator
# then create a vhost and a user for your daiquiri app
rabbitmqctl add_vhost <vhost>
rabbitmqctl add_user <user> <a secret password>
rabbitmqctl set_permissions -p <user> <user> ".*" ".*" ".*"
rabbitmqctl set_permissions -p <user> admin ".*" ".*" ".*"
Then add CELERY_BROKER_URL
to the .env
file of your Daiquiri application:
CELERY_BROKER_URL=amqp://<user>:<password>@<host>:<port>/<vhost>
As with the Gunicorn process, the asyncronous workers are also using systemd to launch and keep running. Create the following service files for the three workers:
# in /etc/systemd/system/daiquiri-default-worker.service
[Unit]
Description=celery worker for the default queue
PartOf=daiquiri.service
After=daiquiri.service
[Service]
Type=forking
User=daiquiri
Group=daiquiri
WorkingDirectory=/srv/daiquiri/app
EnvironmentFile=/srv/daiquiri/app/.env
Environment=CELERY_BIN=/srv/daiquiri/app/env/bin/celery
Environment=CELERYD_NODE=daiquiri_default
Environment=CELERYD_QUEUE=default
Environment=CELERYD_CONCURRENCY=1
Environment=CELERYD_PID_FILE=/var/run/celery/daiquiri/default.pid
Environment=CELERYD_LOG_FILE=/var/log/celery/daiquiri/default.log
Environment=CELERYD_LOG_LEVEL=INFO
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${CELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODE} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${ELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
[Install]
WantedBy=daiquiri.service
# in /etc/systemd/system/daiquiri-query-worker.service
[Unit]
Description=celery worker for the query queue
PartOf=daiquiri.service
After=daiquiri.service
[Service]
Type=forking
User=daiquiri
Group=daiquiri
WorkingDirectory=/srv/daiquiri/app
EnvironmentFile=/srv/daiquiri/app/.env
Environment=CELERY_BIN=/srv/daiquiri/app/env/bin/celery
Environment=CELERYD_NODE=daiquiri_query
Environment=CELERYD_QUEUE=query
Environment=CELERYD_CONCURRENCY=1
Environment=CELERYD_PID_FILE=/var/run/celery/daiquiri/query.pid
Environment=CELERYD_LOG_FILE=/var/log/celery/daiquiri/query.log
Environment=CELERYD_LOG_LEVEL=INFO
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${CELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODE} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${ELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
[Install]
WantedBy=daiquiri.service
# in /etc/systemd/system/daiquiri-download-worker.service
[Unit]
Description=celery worker for the download queue
PartOf=daiquiri.service
After=daiquiri.service
[Service]
Type=forking
User=daiquiri
Group=daiquiri
WorkingDirectory=/srv/daiquiri/app
EnvironmentFile=/srv/daiquiri/app/.env
Environment=CELERY_BIN=/srv/daiquiri/app/env/bin/celery
Environment=CELERYD_NODE=daiquiri_download
Environment=CELERYD_QUEUE=download
Environment=CELERYD_CONCURRENCY=1
Environment=CELERYD_PID_FILE=/var/run/celery/daiquiri/download.pid
Environment=CELERYD_LOG_FILE=/var/log/celery/daiquiri/download.log
Environment=CELERYD_LOG_LEVEL=INFO
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${CELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODE} \
--pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODE} \
-A config \
-Q ${CELERYD_QUEUE} \
-c ${CELERYD_CONCURRENCY} \
--pidfile=${CELERYD_PID_FILE} \
--logfile=${CELERYD_LOG_FILE} \
--loglevel=${CELERYD_LOG_LEVEL}'
[Install]
WantedBy=daiquiri.service
Then, the worker can be started and enabled as before:
sudo systemctl daemon-reload
sudo systemctl start daiquiri-default-worker
sudo systemctl start daiquiri-query-worker
sudo systemctl start daiquiri-download-worker
sudo systemctl enable daiquiri-default-worker
sudo systemctl enable daiquiri-query-worker
sudo systemctl enable daiquiri-download-worker
Caching
To use memcached as cache, first install it from the distribution:
apt install memcached # Debian/Ubuntu
yum install memcached # CentOS
On CentOS memcached needs to be restricted to listen only to localhost in /etc/sysconfig/memcached
:
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS="-l 127.0.0.1,::1"
Then memcached can be enabled and started:
systemctl start memcached
systemctl enable memcached