In this blog post, I will share my take of solving a specific issue faced while setting up monitoring for a Uvicorn server using Monit.
At Qxf2, we have a web application built with ReactJS, Neo4j and FastAPI, implemented for an internal use-case. It runs on an EC2 instance. The application, silently going down has been troublesome. Hence, we looked for a monitoring and self-healing solution. Monit deemed fit. I started off by setting up the Monit daemon, on the respective EC2 instance and enabling Monit’s web interface. Monitoring and maintaining applications using Monit, is pretty straight-forward for any daemon implementations like nginx. However, I hit a tricky spot while setting up monitoring using Monit.
Issue faced
The web application uses FastAPI as the backend framework which is run using an ASGI server program called Uvicorn. The FastAPI application’s run command `nohup uvicorn main:app –host hostIP –port portNumber &` could however not be called out as it is in the Monit’s start program command. One of the reasons being difference in shells between Monit and the terminal program. To run the application, Monit has to activate the Python virtual environment and move into the respective application folder.
My solution
I resolved this issue by implementing a shell script and calling it in `monitrc` (Monit’s config file, by default, located at /etc/monit/monitrc with the monit install). Sharing sample snippets of my Monit implementation.
1. Write a shell script
#!/bin/sh start_uvicorn () { . /home/ubuntu/<application_dir_name>/venv/bin/activate cd /home/ubuntu/<application_dir_name>/backend/app uvicorn main:app --reload --host 0.0.0.0 --port <port_no> } if [ "$1" = "start" ]; then echo "Starting application!" start_uvicorn elif [ "$1" = "stop" ]; then echo "Terminating application!" kill $(ps aux | grep uvicorn| grep -v grep| pgrep uvicorn) else echo "Command not found!" fi |
Note: Replace placeholders marked with <> appropriately.
Let me quickly go through the script. The shell script compares the first command line argument passed and acts accordingly. If it is `start`, then start_uvicorn(), a user-defined function is called. This function starts the web application by first activating the Python virtual environment and moving into the respective application folder. (.) dot command is source command’s equivalent, to activate the virtual environment. If the first command line argument to the shell script is `stop`, then the application’s process ID is extracted and passed on to kill command to terminate the application execution.
2. Update Monit configuration file
check process uvicorn matching "uvicorn" start program = "/bin/bash -c '/bin/sh /home/ubuntu/monitor_app.sh start'" with timeout 100 seconds stop program = "/bin/bash -c '/bin/sh /home/ubuntu/monitor_app.sh stop'" with timeout 60 seconds if cpu usage > 80% for 5 cycles then alert if failed host 0.0.0.0 port <port_no> then restart if 5 restarts within 5 cycles then timeout |
Note: Replace placeholders marked with <> appropriately.
Call the shell script written in step 1 with the appropriate parameter, in Monit’s start and stop program commands.
3. Restart and Verify
Finally, post every Monit config file update, remember to restart Monit service. Run `service monit restart` command to restart Monit with root privileges. Attempt stopping, starting or restarting application using Monit’s web interface. Post performing any Monit action, verify application status by running `monit status` or `monit summary`. Alternatively, monit logs located at `/var/log/monit.log` or in the web interface, can also be looked at for more detailed information.
This approach helped me solve the issue. Also, we were able to do a health check of our web-application using Monit’s web interface with ease. I believe, you find the solution that I had discussed in this blog useful. I would be glad to hear from you, on your solutions, to similar issues with Monit.
I have done my Masters in Software Engineering. I am very passionate about testing and I’ve had the opportunity to work in different phases of the software life cycle. Have built and tested various applications. My curiosity drives me to explore, learn and keep myself updated. Qxf2 keeps me constantly challenged with interesting work and encourages me into blogging about it. I love music, reading and staying fit!