Running OpenStack Compute APIs in nginx and uWSGI
In this article I will explain how run the OpenStack Compute (nova) APis behind nginx, instead of using the Eventlet builtin WSGI server. This is a setup we have been using at IFCA since long after using plain Eventlet and Apache + mod_wsgi.
Unlike other HTTP servers like Apache, nginx does not have direct WSGI support. However, it can act as an application gateway, as it offers a number of built-in interfaces to pass the incoming requests to other HTTP servers, lightweight application servers and web frameworks. One of those is uWSGI, and that is what we are going to use to actually run the OpenStack APIs. uWSGI is one of the most common application servers implementing the WSGI protocol (but not only) and, as you probably already know, the OpenStack Compute APIs are WSGI applications
This basic setup will run uWSGI and nginx in the same server, but you can easily decouple them so that you can deploy several API nodes and use all the advanced HTTP features of nginx (such as load balancing or SSL termination), thus horizontally scaling your controller quite easily.
Update (2016-11-16)
I've received some comments from Carlos Gimeno (thanks!) about two missing bits in the steps below:
- It is required to install the
uwsgi-plugin-python
alongside all the other uWSGI packages. - It is needed to set the proper ownership to the vassals files, in our case
the user and group are
nova:nova
Preparation
I assume that you have the OpenStack Compute APIs already configured and running on a server, so lets focus on installing the required extra packages:
apt-get install nginx
apt-get install uwsgi-core uwsgi-emperor uwsgi-plugin-python
Since we are going to run the API using uWSGI, we must stop (and disable) the
nova-api
service:
service nova-api stop
echo manual > /etc/init/SERVICE.override
Configure uWSGI
As we said, the nova APIs are WSGI applications, but we need some helper
scripts that can be handled by uWSGI. Let's create the
/usr/lib/cgi-bin/nova-uwsgi/nova.wsgi
file with the following contents:
import os
import eventlet
from paste import deploy
from oslo_config import cfg
from oslo_log import log as logging
from nova import config
from nova import objects
from nova import service
from nova import utils
#eventlet.monkey_patch()
CONF = cfg.CONF
config.parse_args([])
logging.setup(CONF, "nova")
utils.monkey_patch()
objects.register_all()
name = os.path.basename(__file__).rsplit(".", 1)[0]
paste_conf = "/etc/nova/api-paste.ini"
options = deploy.appconfig('config:%s' % paste_conf, name=name)
application = deploy.loadapp('config:%s' % paste_conf, name=name)
Now create a links for the API, so that uWSGI is able to find it:
ln -sf /usr/lib/cgi-bin/nova-uwsgi/nova.wsgi /usr/lib/cgi-bin/nova-uwsgi/osapi_compute.py
The above script can be executed by a standalone uWSGI instance as follows:
uwsgi --http-socket :8080 --plugin python --chdir /usr/lib/cgi-bin/nova-uwsgi --module osapi_compute --master --processes 1
If you point to the http://<server>:8080
URL you should see the nova endpoint
advertising its API versions. Don't forget to stop this process whenever you're
done testing it.
However, the above is not appropriate for a production environment. uWSGI implements a more convenient module, called "Emperor", where a special uWSGI instance will control several application instances (called vassals) according to some specific events (like sudden application termination). Therefore, we will rely on the uWSGI emperor mode for spawning all our configured applications (that is, the different OpenStack Compute APIs) instead of running them manually.
Lets configure the Emperor. First of all, configure the Emperor itself via its
/etc/wsgi/emperor.ini
[uwsgi]
# try to autoload appropriate plugin if "unknown" option has been specified
autoload = true
# enable master process manager
master = true
# spawn 2 uWSGI emperor worker processes
workers = 2
# automatically kill workers on master's death
no-orphans = true
# place timestamps into log
log-date = true
# user identifier of uWSGI processes
uid = www-data
# group identifier of uWSGI processes
gid = www-data
# vassals directory
emperor = /etc/uwsgi-emperor/vassals
# let the emperor change uids and gids
emperor-tyrant = true
cap = setgid,setuid
And now configure it as a vassals in /etc/uwsgi-emperor/vassals/nova.ini
:
:::ini
[uwsgi]
plugin = python
chdir = /usr/lib/cgi-bin/nova-uwsgi
module = osapi_compute
master = true
processes = 25
socket = /var/run/nova/nova.uwsgi.sock
stats = /var/run/nova/nova.uwsgi.stats.sock
vacuum = true
You must change the ownership to nova:nova
, then you can restart it and check
that it is working.
Configure nginx
Now it is time to configure nginx to distribute the requests to the relevant
uWSGI processes. This is the usual nginx configuration for load balancing, so
if you are familiar with nginx configuration you should be familiar with this
configuration. Create a file called /etc/nginx/sites-enabled/nova.conf
with
the following contents. This setup uses SSL and you should use it too!
upstream nova {
server unix:///var/run/nova/nova.uwsgi.sock;
}
server {
listen 8774;
server_name controller.example.org;
root html;
index index.html index.htm;
ssl on;
ssl_certificate /etc/ssl/certs/hostcert.pem;
ssl_certificate_key /etc/ssl/private/hostkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";
ssl_prefer_server_ciphers on;
location / {
uwsgi_pass nova;
uwsgi_param SCRIPT_NAME "";
include /etc/nginx/uwsgi_params;
}
}
Restart your services, and you should be done.
Extra: OpenStack OCCI Interface
If by change you are running an OCCI interface (or you want to run it) you can also deploy it using this setup. Assuming that you are using ooi the setup will be quite straightforward.
First, create the required link as follows:
ln -sf /usr/lib/cgi-bin/nova-uwsgi/nova.wsgi /usr/lib/cgi-bin/nova-uwsgi/ooi_api.py
Now we can create another uWSGI vassal for ooi. Put the following contents in a
file called /etc/uwsgi-emperor/vassals/ooi.ini
(remember to change the
ownership to nova:nova
:
[uwsgi]
plugin = python
chdir = /usr/lib/cgi-bin/nova-uwsgi
module = ooi_api
master = true
processes = 4
socket = /var/run/nova/ooi.uwsgi.sock
stats = /var/run/nova/ooi.uwsgi.stats.sock
vacuum = true
Then, add the following nginx configuration:
upstream ooi {
server unix:///var/run/nova/ooi.uwsgi.sock;
}
server {
listen 8787;
server_name cloud.ifca.es;
root html;
index index.html index.htm;
ssl on;
ssl_certificate /etc/ssl/certs/hostcert.pem;
ssl_certificate_key /etc/ssl/private/hostkey.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";
ssl_prefer_server_ciphers on;
location / {
uwsgi_pass ooi;
uwsgi_param SCRIPT_NAME "";
include /etc/nginx/uwsgi_params;
}
}