Disclosure: I’m the developer of Filantis, a commercial product, which is why this is in the Commercial category.
Filantis is a multi-tenant business phone system (cloud PBX or on-premise) built on Asterisk, with hotel, restaurant and office features built into the PBX: wake-up calls, room status from the room phone, an outside line that opens at check-in, a restaurant order line with kitchen tickets, and ring groups per department.
How it is built on Asterisk
PJSIP with realtime configuration, one set of tables per tenant.
Dialplan generated per tenant, with hand-written contexts for the shared parts.
An ARI application for the call-attendant (IVR) menus.
A WebRTC softphone in the browser over WSS, plus Android and Windows SIP softphones.
ConfBridge for group video calls.
Three problems that may save someone else a day
Same extension number in every tenant. Every tenant’s first extension is 100, and on one shared Asterisk the realtime rows collided, so a new tenant could take over another tenant’s extension. Endpoint, AOR and auth objects are now named 100@tenant-domain, while the auth username stays a bare 100. Phones still register as user 100 against their own domain, and Asterisk matches the endpoint from user@domain in the From header without extra configuration.
Calls answer, silence both ways, drop at exactly 32 seconds.external_media_address and external_signaling_address were set to the server’s host name, and /etc/hosts mapped that name to 127.0.1.1. Asterisk therefore sent c=IN IP4 127.0.1.1 in the SDP. pjsip show channelstats showing zero RTP in both directions on both legs was the clue. Writing the public IP as a literal fixed it.
Browser softphone would not register over WSS on Asterisk 20. With chan_sip loaded, it claimed the “sip” WebSocket subprotocol and res_pjsip_transport_websocket declined to load. noload => chan_sip.so under [modules] and a clean restart fixed it.
A question for people running shared multi-tenant Asterisk: do you name endpoints ext@domain like this, or do you put Kamailio in front and keep Asterisk single-tenant behind it? I’d like to hear where the ext@domain approach stops scaling.
There is a free hosted plan (10 extensions) if you want to look at it: https://filantis.com
basit.engg, that is close to what we do. The tenant’s domain acts as the tenant id, so 100@acme.example.com is unique across the box.
@ldo we considered it. Separate instances give cleaner isolation: a bad dialplan or a reload in one tenant cannot touch another. But every instance then needs its own SIP port or a proxy in front that routes by domain, and at that point you are running Kamailio anyway. Most of our tenants have fewer than 20 extensions, so one Asterisk with realtime is far fewer moving parts: one RTP range, one set of trunks, one thing to upgrade. The cost is that isolation lives in dialplan contexts and object naming, so it has to be tested on every change.
@benphone good to hear the 100-alice form holds up at scale. One reason I went with @domain is that the default username identifier already tries user@domain from the From header before the bare user, so I needed no identify sections. With 100-alice, do the phones register with the username 100-alice, or do you map a bare 100 to it somewhere?
You could use Kamailio or Asterisk as a gateway between all the containers and the rest of the Internet (including upstream VoIP providers). The gateway instance would be under your complete control, of course, and could implement whatever routing policy you specify. Tenant instances would have no way out (or in) without going through that.
Not VMs, containers. Much lighter weight. They all share the same kernel, just different userlands isolated via the Linux namespace system.
You can package up a container template and instantiate it multiple times, once for each tenant. The separation would be much more robust and further-reaching than relying on Asterisk itself to keep users separate. For example, you can allocate resource usage on a per-container (i.e. per-tenant) basis.