{"id":7142,"date":"2023-06-22T12:47:41","date_gmt":"2023-06-22T10:47:41","guid":{"rendered":"http:\/\/blog.bart.sk\/en\/?p=7142"},"modified":"2024-01-25T14:03:06","modified_gmt":"2024-01-25T13:03:06","slug":"zero-downtime-deployments-using-supervisor-and-nginx-load-balancer-how-to-do-it","status":"publish","type":"post","link":"https:\/\/blog.bart.sk\/en\/zero-downtime-deployments-using-supervisor-and-nginx-load-balancer-how-to-do-it\/","title":{"rendered":"Zero-downtime Deployments Using Supervisor and Nginx Load Balancer: How to Do It?"},"content":{"rendered":"<p><b>When you run a web application in a production environment, you should strive for its 100% availability. If you often deploy new versions and cause outages while doing so, it&#8217;s perceived as a big mistake. A few minutes &#8220;off&#8221; can result in a significant loss for the client.<\/b><\/p>\n<p><span style=\"font-weight: 400;\">We use the <\/span><span style=\"font-weight: 400;\">Supervisor<\/span><span style=\"font-weight: 400;\"> tool to run and monitor processes on Unix and Unix-like systems. It has proven to be flexible, extensible and, moreover, it enables the automation and simplification of process management. Usually, it maintains Node.js and REST API services for us<\/span><b>. Under normal circumstances, when restarting such a service, a few seconds of downtime occur. <\/b><span style=\"font-weight: 400;\">New source codes are uploaded to the server and the service must be reloaded.<\/span><\/p>\n<p><b>We&#8217;re able to fix this problem by setting up Nginx and Supervisor.<\/b><span style=\"font-weight: 400;\"> It should be noted that although it&#8217;s a quick, simple and relatively efficient solution, it isn&#8217;t a system that &#8220;thinks&#8221; of all the problems associated with deploying applications. That&#8217;s why it&#8217;s necessary to keep your eyes open and to fine-tune the deployment on the fly.<\/span><\/p>\n<h2><b>Initial configuration with outages<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Suppose we use the following command to run the REST API:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">$<\/span><span style=\"font-weight: 400;\"> npm run production\u00a0<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">The simplified related Supervisor configuration is as follows: <\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">[program:mojweb-api]<\/span>\r\n <span style=\"font-weight: 400;\">command=npm<\/span> <span style=\"font-weight: 400;\">run<\/span> <span style=\"font-weight: 400;\">production<\/span> \r\n <span style=\"font-weight: 400;\">directory=\/var\/www\/mojweb.sk\/www\/api<\/span>\r\n <span style=\"font-weight: 400;\">user=mojweb<\/span>\r\n <span style=\"font-weight: 400;\">stopasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">killasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">autostart=true<\/span>\r\n <span style=\"font-weight: 400;\">autorestart=true<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><b>This configuration results in the service being turned on as soon as the virtual machine is started. <\/b><span style=\"font-weight: 400;\">At the same time, the Supervisor shall ensure that the process is started whenever a failure occurs.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The Node.js itself &#8220;listens&#8221; to the process on the port or socket, which in our case was defined in the configuration file. So let&#8217;s assume that the API will listen on port 10000 when it starts.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">The simplified NGINX configuration is as follows: <\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">location<\/span> <span style=\"font-weight: 400;\">\/api-eshop<\/span> <span style=\"font-weight: 400;\">{<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">rewrite<\/span> <span style=\"font-weight: 400;\">^\/api-eshop\/(.*)<\/span> <span style=\"font-weight: 400;\">\/$1<\/span><span style=\"font-weight: 400;\">\u00a0 <\/span><span style=\"font-weight: 400;\">break;<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">proxy_pass<\/span><span style=\"font-weight: 400;\"> http:\/\/localhost:10000;<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">}<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">Requests that arrive at the line \/api-eshop will be routed to port 10000.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400;\">To restart the service, we use the Supervisor:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">$<\/span><span style=\"font-weight: 400;\"> supervisorctl restart mojweb-api\u00a0<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2><b>New configuration<\/b><\/h2>\n<p><b>The principle of starting changes in the new configuration will work as follows.<\/b><\/p>\n<ol>\n<li style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">An API instance is running on the server\u00a0<\/span><\/li>\n<li style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">Changes to the source code will be uploaded to the web\u00a0<\/span><\/li>\n<li style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">The second instance of the API will be turned on on the server with the new source code\u00a0<\/span><\/li>\n<li style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">The old API instance will shut down\u00a0<\/span><\/li>\n<li style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">There\u2019s only one updated application running on the server.<\/span><\/li>\n<\/ol>\n<p><b>In order to achieve deployment of changes to the site without outages, we need to make a few minor adjustments.\u00a0<\/b><\/p>\n<p><span style=\"font-weight: 400;\">On the application side, we need to achieve the ability to configure the port on which we are listening directly when running from the command line.\u00a0<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We&#8217;ll replace the original command with:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">$<\/span><span style=\"font-weight: 400;\"> npm run production -p 10000<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">And we&#8217;ll modify the Supervisor configuration as follows:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">[program:mojweb-api1]<\/span>\r\n <span style=\"font-weight: 400;\">command=npm<\/span> <span style=\"font-weight: 400;\">run<\/span> <span style=\"font-weight: 400;\">production<\/span><span style=\"font-weight: 400;\"> -p 10000<\/span>\r\n <span style=\"font-weight: 400;\">directory=\/var\/www\/mojweb.sk\/www\/api<\/span>\r\n <span style=\"font-weight: 400;\">user=mojweb<\/span>\r\n <span style=\"font-weight: 400;\">stopasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">killasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">autostart=true<\/span>\r\n <span style=\"font-weight: 400;\">autorestart=true<\/span>\r\n\r\n <span style=\"font-weight: 400;\">[program:mojweb-api2]<\/span>\r\n <span style=\"font-weight: 400;\">command=npm<\/span> <span style=\"font-weight: 400;\">run<\/span> <span style=\"font-weight: 400;\">production<\/span><span style=\"font-weight: 400;\"> -p 10001<\/span>\r\n <span style=\"font-weight: 400;\">directory=\/var\/www\/mojweb.sk\/www\/api<\/span>\r\n <span style=\"font-weight: 400;\">user=mojweb<\/span>\r\n <span style=\"font-weight: 400;\">stopasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">killasgroup=true<\/span>\r\n <span style=\"font-weight: 400;\">autostart=true<\/span>\r\n <span style=\"font-weight: 400;\">autorestart=true<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">In this way, we&#8217;re able to run 2 API instances on different ports.<\/span><\/p>\n<h2><b>Nginx load balancer<\/b><\/h2>\n<p><b>If one instance is restarted, requests must be directed to the other. This step can be provided by NGINX load balancer.\u00a0<\/b><\/p>\n<p><span style=\"font-weight: 400;\">Modified configuration for NGINX:<\/span><\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\"> \u00a0 <\/span><span style=\"font-weight: 400;\">upstream<\/span> <span style=\"font-weight: 400;\">magazin-ssr<\/span> <span style=\"font-weight: 400;\">{<\/span>\r\n  <span style=\"font-weight: 400;\">server<\/span><span style=\"font-weight: 400;\"> localhost:10000;<\/span>\r\n  <span style=\"font-weight: 400;\">server<\/span><span style=\"font-weight: 400;\"> localhost:10001;<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">}<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">location<\/span> <span style=\"font-weight: 400;\">\/api-eshop<\/span> <span style=\"font-weight: 400;\">{<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">rewrite<\/span> <span style=\"font-weight: 400;\">^\/api-eshop\/(.*)<\/span> <span style=\"font-weight: 400;\">\/$1<\/span><span style=\"font-weight: 400;\">\u00a0 <\/span><span style=\"font-weight: 400;\">break;<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">proxy_pass<\/span><span style=\"font-weight: 400;\"> http:\/\/magazin-ssr;<\/span>\r\n <span style=\"font-weight: 400;\">\u00a0 \u00a0 \u00a0 \u00a0 <\/span><span style=\"font-weight: 400;\">}<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">Nginx therefore decides which API instance to use. If one is off, traffic is directed to the other, and vice versa.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">We then use a simple SHELL script to restart the service<\/span>.<\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre><span style=\"font-weight: 400;\">supervisorctl start ssr-edsi1<\/span>\r\n <span style=\"font-weight: 400;\">sleep 10s<\/span>\r\n <span style=\"font-weight: 400;\">supervisorctl restart ssr-edsi<\/span>\r\n <span style=\"font-weight: 400;\">sleep 10s<\/span>\r\n <span style=\"font-weight: 400;\">supervisorctl stop ssr-edsi1<\/span><\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">All done!<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"When you run a web application in a production environment, you should strive for its 100% availability. If&hellip;","protected":false},"author":13,"featured_media":7130,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","csco_display_header_overlay":false,"csco_singular_sidebar":"","csco_page_header_type":""},"categories":[199],"tags":[460,458,459,457,456],"_links":{"self":[{"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/posts\/7142"}],"collection":[{"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/comments?post=7142"}],"version-history":[{"count":1,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/posts\/7142\/revisions"}],"predecessor-version":[{"id":7143,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/posts\/7142\/revisions\/7143"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/media\/7130"}],"wp:attachment":[{"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/media?parent=7142"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/categories?post=7142"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.bart.sk\/en\/wp-json\/wp\/v2\/tags?post=7142"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}