shazino

Run your SaaS Product in Enterprise Mode

Tuesday, 6 August 2013 in dev journal by Damien Mathieu

Hivebench is a platform for biotechnology researchers. We have been working on building, maintaining and improving it for the past two years.

While the public face of it is a SaaS product, the less visible part is an Enterprise installation, i.e. in a virtual machine, inside your own network. Zach Holman has a talk about this.

In this article, we are going to walk you through some of the technical decisions we made while building this product.

What are we talking about?

When you deploy your SaaS product, you just need to push the new code to your server and restart the services. You can easily deploy new fixes several times a day if necessary. And you can troubleshoot your server manually if something you don’t understand happens.

When shipping a product (the same or not) inside your customer’s network, deploying takes a whole other dimension. Releasing a new version doesn’t mean you’re deploying. No customer is forced to upgrade (you can enforce that by contract though). And you just cannot connect to your customer’s virtual machine in SSH, like you’d be tempted to do on a production server.

Therefore, we will need to setup a few rules. Similar ones than if we were deploying a desktop or mobile application actually.

DevOps

I talked a bit about this at the last LyonRB meetup. There are plenty of DevOps tools (Chef, Puppet) around. We are using Chef, but you should pick the one you prefer.

For Hivebench, we settled on the following process:

And for the updates:

Just like that. That’s the whole install and update process.

Of course, it will get a bit more complicated. We don’t have any way to troubleshoot anything right now. Actually, we haven’t even described how we can package the application.

Packaging our application

Since our update packages contain only cookbooks, we need to set our application in one of those. We decided to build a Debian package containing our application.

This solves a lot of potential problems. We don’t have to manually manage unpacking our application and moving the files to the right path. Versions are managed by the package.

We’re using fpm to achieve this. There are other solution around. This is the most flexible one though.

We also need to run a few bunch of steps when packaging

Finally, we can create our debian package with fpm. Something like this:

1
2
3
4
5
6
7
8
9
10
11
bundle exec fpm \
  -s dir \
  -t deb \
  -n "my_project" \
  -v 0.0.1 \
  -p cookbooks/my_project/files/default/package.deb\
  -m "contact@example.com" \
  --description "Describe your application" \
  --prefix "/home/my_project/current" \
  -C "tmp/my_project" \
  ./

This will take all the files in tmp/my_project and set them in a file cookbooks/my_project/files/default/package.deb.
When installed, the files will end up in /home/my_project/current.

About your package version

This is not an Open Source project. Using semver isn’t really the best idea here. You need to think of your own versionning model.

With Hivebench, we decided to do it simple. A version could be 1.0.0.2013.07.2. The 1.0.0 part is actually part of semver. It is unlikely to be upgraded anytime soon though.

After that comes the year of the release, then the month. And finally, the release number for this month. Here, this is the second release.

We won’t release everything publicly. When debugging before an Enterprise release, we will probably need to upgrade this last number. Therefore, there might be an upgrade of more than 1 before two public releases.

Protecting your source code

Since your code is hosted in an environment which you cannot control, you need to protect your source code. It needs to remain executable. Someone viewing the file shouldn’t be able to get it though.

We’re using Ruby Encoder for that.
Your source code will be compiled into bytecode format, and then encrypted.

Troubleshooting your application

This is actually the tricky part.

After deploying on your customer’s server, you cannot connect yourself to his own server to troubleshoot the problems he might have.

Indeed, one of the main ideas of running an instance of an application inside their own server is to avoid having it being accessible from outside their network.

Therefore, most of the time, you just won’t have any access to the machine.

Logging

Since you cannot just ask your customer to site behind his ssh console and wait for the problem to occur, you need to log everything.

The easy ones are the server and application logs. But don’t forget to keep a log of all your updates, of all outgoing emails, all the SQL queries, …

There’s no exhaustive list for this, since it might depend on your application. But anything happens, log it.

And, most of all, don’t forget to rotate the logs. And, of course, to log the space consumption in the machine.

Test, and debug

You should have unit tests in your application of course. That’s not enough though. There might always be regressions, or just bugs which haven’t been thought of.

So before doing any release, plan a few days/weeks where you won’t ship anything new, and will only do bugfixes. It is very hard to release. You don’t want to fail it.

If your application also has a SaaS version, that’s awesome, since your SaaS users are already debugging for you.

Automate all the things

You already started automating with DevOps. Keep doing it. Any manual command is prone to errors. In the examples above, there is an example of the fpm command to create the package for your application. You should never execute it manually.

With Hivebench, we have a few Thor tasks, which do this all. We can run, for example:

thor release --version 0.0.2 --branch enterprise

Which will create everything. From the OVA file to the update package, and will checkout the branch “enterprise” from our application.

The less you do manually, the better it is.

Running in SaaS and Enterprise at the same time

Finally, if you’re running your application both in SaaS and Enterprise mode at the same time, you want to have the same code base for both of them. Even if you have a master and an enterprise branch with the latest deploys for both applications, they need to be the same at some points in time.

We manage that with environment variables.
In our enterprise mode, we define an environment variable LAUNCH_TYPE.
It can have either the value enterprise or cloud (and will default to enterprise).

There might be other differences of course, and implementing this is an architectural problem which we won’t cover in this article.

There is one thing you don’t want though, it is to have conditions for the cloud or the enterprise everywhere in your code. You will want to abstract to the maximum the things you do differently in those two environments and get one or an other behavior applied automatically.

You could imagine something like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module MyApp
  class Enterprise
    attr_reader :config

    def initialize(config={})
      @config = Rails.application.config.env.merge(config)
    end

    def launch_type
      config['launch_type'] || 'enterprise'
    end

    def limits
      "MyApp::Enterprises::Limits::#{launch_type.camelize}".constantize.new(config)
    rescue NameError
      MyApp::Enterprises::Limits::Enterprise.new(config)
    end
  end
end

Then, for the cloud launch type:

1
2
3
4
5
6
7
8
9
10
11
module MyApp
  module Enterprises
    module Limits
      class Cloud
        def create_teams(user)
          true
        end
      end
    end
  end
end

And for the enterprise launch type:

1
2
3
4
5
6
7
8
9
10
11
module MyApp
  module Enterprises
    module Limits
      class Enterprise
        def create_teams(user)
          user.admin?
        end
      end
    end
  end
end

In cloud mode, all users can create teams.
In enterprise mode, only the admins of the platform can create new team.

When checking if the user can create a team or not, you just have to do

MyApp::Enterprise.new.limits.create_teams?(current_user)

Conclusion

We’ve covered most of what we have considered being important for running your SaaS application on your customer’s server.

You shouldn’t take this for granted though. While we have been having this in production for a few months now, we are still pretty young in this and might have forgotten a few things.

Please tweet us if you want to suggest adding something to this process.