Nginx/Unit installation¶
- Nginx is a proxy that can also be used as load balancer, mail proxy, and HTTP cache.
- Unit is a dynamic web and application server, designed to run applications in multiple languages and dynamically configured via API.
Installation¶
The installation of Nginx and Unit is done via the package manager of the operating system.
For our case, we are using Docker and they have a Docker image with Unit installed.
Dockerfile¶
The Unit image is based on the python image, so the only thing that needs to be changed is the image name.
After changing the image, the next step is to install the Nginx proxy. The proxy will listen to the requests and forward them to the Unit server and in the Nginx configuration we will enable the HTTP2 protocol.
RUN apt-get update && apt-get install -y nginx && apt-get -y autoremove && apt-get -y autoclean && rm -rf /var/lib/apt/lists/*
Probably this line is already there, so we just need to add nginx to it.
Unit¶
Configuration¶
With Unit image in the dockerfile, the next step is to configure the server. The configuration is done via Unit API, the API is a RESTful API that can be accessed via HTTP requests. So we need to create a json file with the configuration and use curl to send the configuration to the server. The place where the configuration file is located is not important, but it is a good practice to put it in the same directory as the Dockerfile. Example: /var/app/docker/unit.json
{
"listeners": {
"127.0.0.1:<UNIT_PORT_NUMBER>": {
"pass": "applications/starlette"
}
},
"applications": {
"starlette": {
"type": "python 3.12",
"path": "/var/app/",
"protocol": "asgi",
"module": "<FILE_NAME>",
"callable": "<APP_OBJECT_NAME>",
"processes": {
"max": 20,
"spare": 1,
"idle_timeout": 60
},
"threads": 8
}
}
}
NOTES
- the
pathis the path to the application directory in our case is/var/app. - the
unit port numberis the port that the Unit server will listen to, not the port that the application will listen to. The application port will be defined in the Nginx configuration. - the
moduleis the python file name that contains the application, normally the file name ismainorapi. - the
callableis the object name that will be called by the Unit server, normally the object name isapp.
Start the Unit server¶
To start the Unit server and send the logs to stdout, we need to use the following command:
unitd --control unix:/var/run/control.unit.sock --user root --group root --pid /var/run/unit.pid --no-daemon &> /dev/stdout &
After starting the server, we need to send the configuration to the Unit server so we use curl.
curl -X PUT --retry-all-errors --retry 5 --data-binary @/var/app/docker/unit.json --unix-socket /var/run/control.unit.sock http://localhost/config/
Nginx¶
Configuration¶
The Nginx configuration is done via a configuration file. The place where the configuration file is located is not important, but it is a good practice to put it in the same directory as the Dockerfile. Example: /var/app/docker/nginx.conf
# Basic Nginx configuration
user www-data;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
# To run in foreground
daemon off;
# Check the number of CPU cores
worker_processes auto;
events {
# Set the maximum number of simultaneous connections that can be opened by a worker process
worker_connections 1024;
# Use epoll for Linux because it's inside docker
use epoll;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
# We don't need this log on Google Cloud
access_log off;
##
# Gzip Settings
##
# Enable Gzip compression only when the Starlette was not doing it
gzip off;
##
# Server Settings
##
# https://betterstack.com/community/questions/nginx-reverse-proxy-causing-504/
upstream http_backend {
# Here is the Unit server address and port
server 127.0.0.1:<UNIT_PORT_NUMBER>;
# Enable keepalive connections
# This will allow the client to reuse the connection
# https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive
keepalive 1;
}
server {
# Load the PORT that this server will listen
#
listen <APPLICATION_PORT> http2;
location / {
proxy_pass http://http_backend;
# To enable keepalive connections we need to set the following
proxy_http_version 1.1;
proxy_set_header Connection "";
# Set the Host header to the original host
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Set the maximum time for the request to be processed
# These times are between the Nginx and the Unit server
# and if the Unit server takes more than 300s to process the request
# the Nginx will return a 504 error so maybe we need to increase this time
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
send_timeout 300s;
}
}
}
NOTES
- the APPLICATION_PORT is the port that the application will be accessed.
- the upstream server is the Unit server that was configured earlier.
- the http2 directive only works on Google, so for the development environment, we need to remove it.
Start the Nginx proxy¶
To start the Nginx proxy, we use the following command:
The first command is to test the configuration file and the second command is to start the Nginx proxy. It is important to test the configuration file before starting the server because if there is an error in the configuration file the Nginx proxy will not start.run.sh version¶
For our case, this is the run.sh version:
api-unit)
PID_FILE="/var/run/unit.pid"
UNIT_CONFIG="${PROJECT_ROOT}/docker/unit.json "
NGINX_CONFIG="${PROJECT_ROOT}/docker/nginx.conf"
NGINX_PORT="${PROJECT_ROOT}/docker/nginx_port.conf"
# shellcheck disable=SC2317
function ctrl_c() {
## This function is called when the Nginx is interrupted/terminated so we need to stop the Unit too
echo "Shutting down..."
if [ -f /var/run/unit.pid ]; then
kill "$(cat ${PID_FILE})"
fi
}
# Runs the ctrl_c function when the Nginx is interrupted
trap ctrl_c INT
# Run the Unit Webserver and send all logs to stdout
unitd --control unix:/var/run/control.unit.sock --user root --group root --pid $PID_FILE --no-daemon &> /dev/stdout &
# Set the configuration
curl -X PUT --retry-all-errors --retry 5 --data-binary @$UNIT_CONFIG --unix-socket /var/run/control.unit.sock http://localhost/config/
# Check if the configuration was set otherwise we need to stop the process
if [ $? -ne 0 ]; then
ctrl_c
exit 1
fi
# Start the Nginx proxy
if ! nginx -t -c "${NGINX_CONFIG}" || ! nginx -c "${NGINX_CONFIG}"; then
ctrl_c
exit 1
fi
;;