Using Sinatra in Kubernetes

A short tale about host interfaces

Posted by Harry Lascelles on November 8, 2023

Here at Silvercat we make pragmatic use of microservices, and recently we were deploying a very simple Sinatra application to Kubernetes. The app was little more than this, launched with Rack:

require "sinatra"

class App < Sinatra::Base
  set :public_folder, "./public"

  get "/" do
    redirect "/index.html"
  end
end

run App

The app performed perfectly on a developer machine. We then deployed it to Kubernetes, and correctly assigned a Service to the app, and also an Ingress.

However, when we loaded it in the browser, we were faced with:

Bad Gateway



Since we were pretty sure the Service and Ingress were right, we gained shell access to the pod, and ran netstat to see what addresses the app was listening on:

# netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:80            0.0.0.0:*               LISTEN      7/rackup -p 80

There was our problem. The application was only listening on 127.0.0.0, the address of the loopback interface, and not on the externally accessible 0.0.0.0 address.

The solution was to tell Sinatra (which uses the Rack webserver) to bind to all interfaces, by adding the following ENV variable to the deployment:

    env:
    - name: RACK_ENV
      value: production

We could now see the app was listening on the correct interface:

# netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      7/rackup -p 80

By default, when running a Sinatra app, it runs with a RACK_ENV of development. This is useful as it provides detailed error messages and other debugging information. However it also means the app binds to the localhost interface, which is not available outside of the pod in a Kubernetes environment. Changing the RACK_ENV to production tells Rack under the hood to bind to all interfaces, and everything works as expected.

This is somewhat equivalent to running:

rackup --host 0.0.0.0

Note, we found that changing the Sinatra APP_ENV to production was not sufficient.

Now all is resolved, onwards and upwards with Sinatra!