SystemD FastCGI multiple processes
Of late, many mainstream distributions have been switching to SystemD as their init system. This includes Debian (since Debian 8) and Ubuntu (since Ubuntu 15.04). In the traditional SysV init system we used to have stuff like spawn-fcgi or custom scripts for starting a FastCGI process and having the web server connect to it over Unix or TCP sockets. Such kind of usage decreased when PHP FPM was introduced since it’s safe enough to assume that 90% (probably more) of the FastCGI deployments are just launching PHP interpreters using whatever mechanism is there (spawn-fcgi or custom scripts). PHP FPM does this for you now and it’s pretty good at it.
FastCGI is just a protocol, it can be used by any application. For custom applications which do not support starting their own FastCGI processes and listening on a socket we have to use external mechanisms. SystemD has a couple of good features which can help reduce the amount of custom work needed in terms of process monitoring, socket paths, file ownership, etc.
My use case:
So I installed the support ticketing system called Request Tracker from Best Practical on a server, and initially using spawn-fcgi to start the FastCGI process and nginx to serve the site. I found problems quite fast: spawn-fcgi would create N number of processes which are listening on the given socket, but there’s no re-launching mechanism if the processes die. Applications can crash any time, so we need something to relaunch. The traditional options would have been to use something like a cron job to monitor the PID file or use monit, there are many options. Then I came across this article by the spawn-fcgi people about how to use SystemD to start a FastCGI process.
Once again, a custom script is involved. Digging through SystemD documentation and some more Googling, I was able to get the FastCGI process spawning working using SystemD without any external dependencies (no extra scripts, etc). Here’s how:
[Unit]
Description = Request Tracker FastCGI backend
After = postgresql.service
Wants = postgresql.service
[Service]
User = rt
Group = rt
ExecStart = /usr/local/rt/sbin/rt-server.fcgi
StandardOutput = null
StandardInput = socket
StandardError = null
Restart = always
[Install]
WantedBy = multi-user.target
[Unit]
Description = RT FastCGI Socket
[Socket]
SocketUser = www-data
SocketGroup = www-data
SocketMode = 0660
ListenStream = /run/rt.sock
[Install]
WantedBy = sockets.target
If you do systemctl enable rt-fastcgi.socket && systemctl start rt-fastcgi.socket then you should have the RT process started up by SystemD when the first request arrives from the web server at /run/rt.sock , and the process keeps running listening for further requests. It gets restarted automatically in case if it crashes or is issued a SIGTERM manually (which is needed if you make changes to the configuration file).
The problem with this setup:
If you see the difference between this method and the spawn-fcgi method, you will observe that while it is possible to spawn multiple request handlers (i.e. multiple RT processes which can serve the web server) using a single command but the same is not possible with this SystemD method. The daemon being spawned by SystemD must do this extra handling. A single process cannot serve many concurrent requests which is definitely a problem. So let’s use SystemD’s automatic unit files based on per instance feature to make it multiple processes:
[Unit]
Description = Request Tracker FastCGI backend (instance %i)
After = postgresql.service
Wants = postgresql.service
[Service]
User = rt
Group = rt
ExecStart = /usr/local/rt/sbin/rt-server.fcgi
StandardOutput = null
StandardInput = socket
StandardError = null
Restart = always
[Install]
WantedBy = multi-user.target
[Unit]
Description = RT FastCGI Socket (instance %i)
[Socket]
SocketUser = www-data
SocketGroup = www-data
SocketMode = 0660
ListenStream = /run/rt%i.sock
[Install]
WantedBy = sockets.target
What the above setup would do is, listen for requests on multiple sockets at /run/rt%i.sock and spawn the appropriate number of instances. How to use this feature from nginx? Here’s how:
upstream rt_backend {
server unix:/run/rt1.sock;
server unix:/run/rt2.sock;
server unix:/run/rt3.sock;
server unix:/run/rt4.sock;
server unix:/run/rt5.sock;
}
server {
# other stuff whatever needed
location / {
include fastcgi_params;
fastcgi_param SCRIPT_NAME "";
fastcgi_param PATH_INFO $uri;
#fastcgi_pass unix:/run/rt.sock;
fastcgi_pass rt_backend;
}
}
Then just do systemctl enable rt-fastcgi@{1..N}.socket && systemctl start rt-fastcgi@{1..N}.socket , start firing requests from nginx and you should see the number of processes growing because nginx sends requests to the upstream in a Round Robin fashion (which can be changed, of course. Refer to nginx documentation for that).
I thought you hated SystemD, what changed? Growing adoption rate by distro makers?
LikeLike
I don’t hate SystemD. It definitely has some good features that otherwise have to be scripted in the traditional system. It’s just a matter of learning new ways to do the thing. I don’t think I’ve to explain the first law of physics to a physicist!
LikeLike
I didn’t get what you meant by the last sentence.
LikeLike
The public outrage against it is mostly driven by inertia I believe.
LikeLike
Really nice solution. I just installed RT5 and decided to change från Apache to Nginx since i like Nginx more. Works like a charm. Thanks for the post. (5 years later ;))
LikeLike