37

UPDATE: See the answer I've provided below for the solution I eventually got set up on AWS.

I'm currently experimenting with methods to implement a global load-balancing layer for my app servers on Digital Ocean and there's a few pieces I've yet to put together.

The Goal

Offer highly-available service to my users by routing all connections to the closest 'cluster' of servers in SFO, NYC, LON, and eventually Singapore.

Additionally, I would eventually like to automate the maintenance of this by writing a daemon that can monitor, scale, and heal any of the servers on the system. Or I'll combine various services to achieve the same automation goals. First I need to figure out how to do it manually.

The Stack

  1. Ubuntu 14.04
  2. Nginx 1.4.6
  3. node.js
  4. MongoDB from Compose.io (formerly MongoHQ)

Global Domain Breakdown

Once I rig everything up, my domain would look something like this:

**GLOBAL**
global-balancing-1.myapp.com
global-balancing-2.myapp.com
global-balancing-3.myapp.com

**NYC**
nyc-load-balancing-1.myapp.com
nyc-load-balancing-2.myapp.com
nyc-load-balancing-3.myapp.com

nyc-app-1.myapp.com
nyc-app-2.myapp.com
nyc-app-3.myapp.com

nyc-api-1.myapp.com
nyc-api-2.myapp.com
nyc-api-3.myapp.com

**SFO**
sfo-load-balancing-1.myapp.com
sfo-load-balancing-2.myapp.com
sfo-load-balancing-3.myapp.com

sfo-app-1.myapp.com
sfo-app-2.myapp.com
sfo-app-3.myapp.com

sfo-api-1.myapp.com
sfo-api-2.myapp.com
sfo-api-3.myapp.com

**LON**
lon-load-balancing-1.myapp.com
lon-load-balancing-2.myapp.com
lon-load-balancing-3.myapp.com

lon-app-1.myapp.com
lon-app-2.myapp.com
lon-app-3.myapp.com

lon-api-1.myapp.com
lon-api-2.myapp.com
lon-api-3.myapp.com

And then if there's any strain on any given layer, in any given region, I can just spin up a new droplet to help out: nyc-app-4.myapp.com, lon-load-balancing-5.myapp.com, etc…

Current Working Methodology

  • A (minimum) trio of global-balancing servers receive all traffic. These servers are "DNS Round-Robin" balanced as illustrated in this (frankly confusing) article: How To Configure DNS Round-Robin Load Balancing.

  • Using the Nginx GeoIP Module and MaxMind GeoIP Data the origin of any given request is determined down to the $geoip_city_continent_code.

  • The global-balancing layer then routes the request to the least connected server on the load-balancing layer of the appropriate cluster: nyc-load-balancing-1, sfo-load-balancing-3, lon-load-balancing-2, etc.. This layer is also a (minimum) trio of droplets.

  • The regional load-balancing layer then routes the request to the least connected server in the app or api layer: nyc-app-2, sfo-api-1, lon-api-3, etc…

The details of the Nginx kung fu can be found in this tutorial: Villiage Idiot: Setting up Nginx with GSLB/Reverse Proxy on AWS. More general info about Nginx load-balancing is available here and here.

Questions

Where do I put the global-balancing servers?

It strikes me as odd that I would put them either all in one place, or spread that layer out around the globe either. Say, for instance, I put them all in NYC. Then someone from France hits my domain. The request would go from France, to NYC, and then be routed back to LON. Or if I put one of each in SFO, NYC, and LON then isn't it still possible that a user from Toronto (Parkdale, represent) could send a request that ends up going to LON only to be routed back to NYC?

Do subsequent requests get routed to the same IP?

As in, if a user from Toronto sends a request that the global-balancing layer determines should be going to NYC, does the next request from that origin go directly to NYC, or is it still luck of the draw that it will hit the nearest global-balancing server (NYC in this case).

What about sessions?

I've configured Nginx to use the ip_hash; directive so it will direct the user to the same app or api endpoint (a node process, in my case) but how will global balancing affect this, if at all?

Any DNS Examples?

I'm not exactly a DNS expert (I'm currently trying to figure out why my CNAME records aren't resolving) but I'm a quick study when provided with a solid example. Has anyone gone through this process before and can provide a sample of what the DNS records look like for a successful setup?

What about SSL/TLS?

Would I need a certificate for every server, or just for the three global-balancing servers since that's the only public-facing gateway?

If you read this whole thing then reward yourself with a cupcake. Thanks in advance for any help.

Boann
  • 48,794
  • 16
  • 117
  • 146
AJB
  • 7,389
  • 14
  • 57
  • 88
  • 2
    For what it's worth, I have an almost identical setup, and have gone down a couple roads with solving the problem. I'm curious what sort of headaches you have had as you expand your global build-out on Digital Ocean. Feel free to e-mail me at brad@musatcha.com if you would like. – Brad Sep 05 '14 at 03:32
  • @Brad I sent you an email just now. – adrianTNT Feb 08 '20 at 02:36

4 Answers4

27

The Goal: Offer highly-available service to my users by routing all connections to the closest 'cluster' of servers in SFO, NYC, LON, and eventually Singapore.

The global-balancing layer then routes the request to theleast connected server...

If I'm reading your configuration correctly, you're actually proxying from your global balancers to the balancers at each region. This does not meet your goal of routing users to the nearest region.

There are three ways that I know of to get what you're looking for:

  1. 30x Redirect
    Your global balancers receive the HTTP request and then redirect it to a server group in or near the region it thinks the request is coming from, based on IP address. This sounds like what you were trying to set up. This method has side effects for some applications, and also increases the time it takes for a user to get data since you're adding a ton of overhead. This only makes sense if the resources you're redirecting to are very large, and the local regional cluster will be able to serve much more efficiently.

  2. Anycast (taking advantage of BGP routing)
    This is what the big players like Akamai use for their CDN. Basically, there are multiple servers out on the internet with the exact same routable IP address. Suppose I have servers in several regions, and they have the IP address of 192.0.2.1. If I'm in the US and try to connect to 192.0.2.1, and someone is in Europe that tries to connect to 192.0.2.1, it's likely that we'll be routed to the nearest server. This uses the internet's own routing to find the best path (based on network conditions) for the traffic. Unfortunately, you can't just use this method. You need your own AS number, and physical hardware. If you find a VPS provider that lets you have a chunk of their Anycast block, let me know!

  3. Geo-DNS
    There are some DNS providers that provide a service often marketed as "Geo-DNS". They have a bunch of DNS servers hosted on anycast addresses which can route traffic to your nearest servers. If a client queries a European DNS server, it should return the address for your European region servers, vs. some in other regions. There are many variations on the Geo DNS services. Others simply maintain a geo-IP database and return the server for the region they think is closer, just like the redirect method but for DNS before the HTTP request is ever made. This is usually the good option, for price and ease of use.

Do subsequent requests get routed to the same IP?

Many load balancers have a "stickiness" option that says requests from the same network address should be routed to the same end server (provided that end server is still up and running).

What about sessions?

This is exactly why you would want that stickiness. When it comes to session data, you are going to have to find a way to keep all your servers up-to-date. Realistically, this isn't always guaranteed. How you handle it depends on your application. Can you keep a Redis instance or whatever out there for all your servers to reliably hit from around the world? Do you really need that session data in every region? Or can you have your main application servers dealing with session data in one location?

Any DNS Examples?

Post separate questions for these. Everyone's "successful setup" looks differently.

What about SSL/TLS?

If you're proxying data, only your global balancers need to handle HTTPS. If you're redirecting, then all the servers need to handle it.

Community
  • 1
  • 1
Brad
  • 159,648
  • 54
  • 349
  • 530
  • Amazing answer, @Brad. This sheds a lot of light on the problem for me. Based on your answer I think I see that I've got an extra balancing layer and that the regional `load-balancing` layer could be baked into the `global-balancing` layer. Then I would use 30x to redirect traffic, which I believe would make the end-point IP 'sticky' for the originating IP. So without employing Geo-DNS or anything else, the first request for any given session may end up with an unlucky long-hop from the `DNS Round Robin`, but once redirected it would then 'automatically' go to the nearest server. – AJB Sep 05 '14 at 03:46
  • I'll have to do some homework on the details of any given 30x redirect and how DNS servers talk to each other in general. – AJB Sep 05 '14 at 03:47
  • Curently my APP and API servers run redis locally. I actually got on this global balancing problem when I started thinking about centralizing the session data into a redis layer of its own. Thanks for the info on SSL/TLS. That's the kick in the pants about the 30x method, very expensive for certificates. Anyhow, I'll take this all in and then hopefully get to a point of an experiment in the next couple days. I'll keep you posted on what I find out. I have an idea to perhaps write a DO tutorial on this and I can always use the feedback of someone that's done it before. Cheers. – AJB Sep 05 '14 at 03:48
  • Hmmm, I don't think much of what I said in my answer stuck. Redirections happen on the HTTP layer. DNS records can't redirect anything, but a DNS server can choose to return a specific IP address for a lookup. Now at that point, there is no guarantee that the client will always connect to that same IP. The DNS cache could be flushed and another lookup performed, with a different result. What makes them sticky is a proxying load balancer that checks remote address and/or cookie... but the traffic would have to get to the load balancer first. – Brad Sep 05 '14 at 03:56
  • For that redirection, remember that you're effectively sending a client to a new URL. This may or may not matter for you. For my purposes, I'm building internet radio servers. The clients usually don't know what that end URL is so I can redirect all day long without negative effect. – Brad Sep 05 '14 at 03:57
  • Hey Brad, just wanted to update you with where I'm at with this. I've moved away from DO DNS to Route53 to take advantage of the multiple 'routing policies' they offer: Latency, GeoIP, Failover. There's just no sense in re-inventing the wheel (even if it is fun and educational) and Route53's pricing is reasonable for the service. Now I'm trying to figure out how to get rid of my load-balancing layer altogether. I'm moving my sessions to a centralized Mongo Store (or redis) and then I'm going to forward port 80/443 to my node port to try and use Route53 for SSL termination. I'll update again. – AJB Sep 06 '14 at 17:17
  • @AJB Good to know. Last I looked into Route 53, it didn't seem to support any of those policies except with AWS environments. Is that no longer the case? – Brad Sep 07 '14 at 00:41
  • Same here, they do now: Simple, Weighted, Latency, Failover, and Geolocation for any AWS endpoint or external IP. http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html. They also offer Health Checks now as well. And 33 POP's. So, a pretty good deal it seems, but I'll have to keep researching. I do plan on updating this answer with all my findings over the past 72 hours but first I have to keep going with testing on Heroku and Appfog. Yup, I think I've ended up with PaaS after all this. Pricier, but with all the ops busywork baked-in it's looking like the best idea. – AJB Sep 07 '14 at 17:38
  • And now I'm at OpenShift and it's looking like this is where I'll land to get everything I need (at a very reasonable price). A bit of a rocky start, but now that I've got my sea-legs here it's looking good for OpenShift. More testing ... – AJB Sep 10 '14 at 03:21
  • @AJB Interesting. I haven't looked into OpenShift's offerings much, but for my purposes (dealing with a lot of multimedia) I'm pretty tied to having access to a full system that's Debian-based. I hope it works well for you! – Brad Sep 11 '14 at 00:44
  • And apparently OpenShift is a sinking ship, so I detoured to make sure I'm taking the scenic route, burned through Pivatol and I'm now seriously considering going steady with AppFog. As the only PaaS that offers GA (at all), coupled with an aggressive pricing model, and a 'NoOps' philosophy that almost seems genuine enough to drink the Kool-Aid it might be the right solution to my problem. Currently waiting to hear back about how they'll handle Global High Availability SSL Termination other than their current solution: https://blog.appfog.com/how-to-load-balance-your-app-around-the-world/ – AJB Sep 12 '14 at 09:57
  • Through AppFog, and Elastic Beanstalk, now getting what I need using AWS OpsWorks. – AJB Sep 18 '14 at 19:44
  • @AJB Around the world in 30 cloud providers? You should seriously document your experiences somewhere. :-) – Brad Sep 18 '14 at 19:55
  • 2
    Ha. That's a great title! I'm planning on it. Got lots of notes. The working titles I had were "Down the PaaS-Hole We Go!" or "Global High-Availability on Beer Budget". I like yours though. In any event, OpsWorks seems to strike the right balance between automation and customization so hopefully this is the end of my whirl-wind trip and I'll be able to write up my findings soon. I'll send it through to you once I've got a draft ready to read. – AJB Sep 18 '14 at 20:07
  • This is an awesome topic and I'd like to read more about your technical setups and experiences on it. Please include a link here too. :) – Tan Hong Tat Sep 23 '14 at 06:18
  • Hi @TanHongTat, added an answer below that outlines the result of all my experimentation. I left off the review of the various PaaS providers that I took a pass on over the course of my experimentation, but a pretty comprehensive outline of the solution that finally worked in my case. – AJB Dec 15 '14 at 03:10
  • @Brad, love to hear about how this compares to the setup that you've got going. Cheers. – AJB Dec 15 '14 at 03:11
  • Cloud Provider offering free Anycast Service: buyvm.net. -> You have to buy 3 VMs (in all of there regions), then you are able to use anycast. – Berndinox Sep 24 '17 at 17:36
  • Thank you @Brad for the amazing answer. I do a have a query you might be able to help me. I have 3 VMs on DO and I'm using GCP cloud DNS to get anycast but I need to add session stickiness as there is some firebase socket code in the backend and it's getting triggered 3 times, which is bad. How can I achieve session stickiness, so it rigger only one time? – kapoorji Mar 09 '19 at 04:44
  • @kapoorji I think you can do this with Digital Ocean's built-in load balancer these days. – Brad Mar 09 '19 at 05:12
  • @Brad The built-in load balancer in Digital Ocean only allow in one region like 1 load balancer in all instances in SF but I'm running in multiple regions – kapoorji Mar 09 '19 at 06:04
16

A Working Solution

I've had a wild ride over the past few months figuring out the whole Global-HA setup. Tonnes of fun and I've finally settled with a rig that works very well, and is nothing like the one outlined in the above question.

I still plan on writing this up in tutorial form, but time is scarce as I head into the final sprint to get my app launched early next year, so here's a quick outline of the working rig I ended up with.


Overview

I ended up moving my entire deployment to AWS. I love Digital Ocean, but the frank reality is that AWS is light years ahead of them (and everyone, really) when it comes to the services offered under one roof. My monthly expenses went up slightly, but once I was done tweaking and streamlining I ended up with a solution that costs about $75/month per region for the most basic deployment (2 instances behind an ELB). And a new region can be spun up and deployed within about 30 minutes.


Global Balancing

I quickly found out (thanks to @Brad's answer above) that trying to spin up my own global balancing DNS layer is insane. It was a hell of a lot of fun figuring out how a layer like this works, but short of getting on a plane and scraping my knuckles installing millions of dollars worth of equipment around the world, it was not going to be possible to roll my own.

When I finally figured out what I was looking for, I found my new best friend: AWS Route 53. It offers a robust DNS network with about 50-odd nodes globally and the ability to do some really cool routing tricks like location-based routing, latency-based routing (which is kinda awesome), and AWS Alias records that 'automagically' route traffic to other AWS Services you'll be using (Like ELB for load balancing).

I ended up using latency-based routing that directs the global traffic to the closest regional Elastic Load Balancer, which has an Auto-Scaling Group attached to it in any given region.

I'll leave it up to you to do your homework on the other providers: www.f5.com, www.dyn.com, www.akamai.com, www.dnsmadeeasy.com. Depending on your needs, there may be a better solution for you, but this works very well for me.


Content Delivery Network

Route 53 integrates with AWS Cloudfront very nicely. I setup an S3 bucket that I'm using to store all the static media files that my users will upload, and I've configured a Cloudfront distribution to source from my media.myapp.com S3 bucket. There are other CDN providers, so do your shopping. But Cloudfront gets pretty good reviews and it's a snap to setup.


Load Balancing & SSL Termination

I'm currently using AWS Elastic Load Balancer to balance the load across my application instances, which live in an Auto-Scaling Group. The request is first received by ELB, at which point SSL is terminated and the request is passed through to an instance in the Auto-Scaling Group.

NOTE: One giant caveat for ELB is that, somewhat ironically, it doesn't handle massive spikes very well. It can take up to 15 minutes for an ELB to trigger a scale-up event for itself, creating 500/timeouts in the meantime. A steady, constant increase in traffic is supposedly handled quite well, but if you get hit with a spike it can fail you. If you know you're going to get hit, you can 'call ahead' and AWS will warm up your ELB for you, which is pretty ridiculous and anti-pattern to the essence of AWS, but I imaging they're either working on it, or ignoring it because it's not really that big of a problem. You can always spin up your own HAProxy or Nginx load-balancing layer if ELB doesn't work for you.


Auto-Scaling Group

Each region has an ASG which is programmed to scale when the load passes a certain metric:

IF CPU > 90% FOR 5 MINUTES: SCALEUP
IF CPU < 70% FOR 5 MINUTES: SCALEDN

I haven't yet put the ELB/ASG combo through its paces. That's a little way down my To-Do list, but I do know that there are many others using this setup and it doesn't seem to have any major performance issues.

The config for an Auto-Scaling Group is a little convoluted in my opinion. It's actually a three-step process:

  1. Create an AMI configured to your liking.
  2. Create a Launch Configuration that uses the AMI you've created.
  3. Create an Auto-Scaling Group that uses the Launch Configuration you've created to determine what AMI and instance type to launch for any given SCALEUP event.

To handle config and app deployment when any instance launches, you use the "User Data" field to input a script that will run once any given instance launches. This is possibly the worst nomenclature in the history of time. How "User Data" describes a startup script only the author knows. Anyhow, that's where you stick the script that handles all your apt-gets, mkdirs, git clones, etc.


Instances & Internal Balancing

I've also added an additional 'internal balancing layer' using Nginx that allows me to 'flat-pack' all my Node.js apps (app.myapp.com, api.myapp.com, mobile.myapp.com, www.myapp.com, etc.myapp.com) on every instance. When an instance receives a request passed to it from ELB, Nginx handles routing the request to the correct Node.js port for any given application. Sort of like a poor-mans containerization. This has the added benefit that any time one of my apps needs to talk to the other (like when app. needs to send a request to api.) it's done via localhost:XXXX rather than having to go out across the AWS network, or the internet itself.

This setup also maximizes usage of my resources by eliminating any idle infrastructure if the app layer it hosts happens to be receiving light traffic. It also obviates the need to have and ELB/ASG combo for every app, saving more cash.

There's no gotchas or caveats that I've run into using this sort of setup, but there is one work-around that needs to be in place with regard to health-checking (see below).

There's also a nice benefit in that all instances have an IAM role which means that your AWS creds are 'baked in' to each instance upon birth and accessible via your ENV vars. And AWS 'automagically' rotates your creds for you. Very secure, very cool.


Health Checks

If you go the route of the above setup, flat-packing all your apps on one box and running an internal load-balancer, then you need to create a little utility to handle the ELB Health Checks. What I did was create an additional app called ping.myapp.com. And then I configured my ELB Health Checks to send any health checks to the port that my ping app is running on, like so:

Ping Protocol: HTTP
Ping Port:     XXXX
Ping Path:     /ping

This sends all health checks to my little ping helper, which in turn hits localhost:XXXX/ping on all the apps residing on the instance. If they all return a 200 response, my ping app then returns a 200 response to the ELB health check and the instances gets to live for another 30 seconds.

NOTE: Do not use Auto-Scaling Health Checks if you're using an ELB. Use the ELB health checks. It's kinda confusing, I thought they were the same thing, they're not. You have the option to enable one or the other. Go with ELB.


The Data Layer

One thing that is glaringly absent from my setup is the data layer. I use Compose.io as my managed data-layer provider and I deploy on AWS so I get very low latency between my app layers and my data layer. I've done some prelim investigation on how I would roll my data layer out globally and found that it's very complex — and very expensive — so I've kicked it down my list as a problem that doesn't yet need to be solved. Worst case is that I'll be running my data layer in US-East only and beefing up the hardware. This isn't the worst thing in the world since my API is strictly JSON data on the wire so the average response is relatively tiny. But I can see this becoming a bottleneck at very large, global scale — if I ever get there. If anyone has any input on this layer I'd love to hear what you have to say.


Ta-Da!

Global High Availability On A Beer Budget. Only took me 6 months to figure it out.

Love to hear any input or ideas from anyone that happens to read this.

AJB
  • 7,389
  • 14
  • 57
  • 88
  • Very cool, glad to see you got everything working! In the last 6 months, I too have ran to AWS for one application, but have ran far away from it for my bread and butter. On AWS for my Node.js application, I ended up using Elastic Beanstalk. The app there has very simple requirements. It uses S3 for data, and outputs to clients over HTTP. Even that proved to be a real hassle though due to the limitations of the way you can set up Beanstalk. If I were to do it all over again, Docker would be my go-to for any Beanstalk app, but that doesn't really apply to your situation. – Brad Dec 15 '14 at 03:19
  • 1
    Regarding your health check, take a look at this: https://github.com/STRML/node-toobusy/ I strongly recommend embedding that health check into your application (if it makes sense to do so in your case). That way, if things get a bit too bogged down (from the Node.js perspective), you can let the load balancer know. I've had situations where Node.js is simply IO-bound, leaving the normal health check stuff free to say that everything is okay, when in fact it isn't. The event loop test may or may not help you, but it's worth looking into. – Brad Dec 15 '14 at 03:21
  • 1
    For my application where I'm basically re-inventing load balancing... I have picked up a new business condition requiring this. I have some clients with excessive bandwidth to spare, so I may be working out scenarios where they host a box for me in exchange for reduced cost or free services, just like Skype. In this situation, I need a lot of flexibility and need to bake in quality checks into the application layer. Speed is also important. At the moment, if one of my servers goes down, traffic stops routing to it within ~100ms. That works for me, but not needed for all. – Brad Dec 15 '14 at 03:24
  • Yup. Docker is the next step for me too, but my girlfriend won't let me until after I launch. And it's likely I'll be using ECS, which hasn't even launched yet, so that'll probably be 12-18 months in the future. For now, this works and it's stable so I'm gonna roll with it. I found Elastic Beanstalk Restrictive too. Probably better using the Dockerized version, but I've never managed to just 'simply launch any application' on it at all. – AJB Dec 15 '14 at 03:24
  • Literally 95% of the dev time for my project was spent mucking around with `.ebextensions`, and then waiting for Amazon to get around to loading it all, just to tell me it failed with no explanation as to why. Once things went "green", I thought I was out of the woods... and then I just realized that my problems were hidden from view. :-) – Brad Dec 15 '14 at 03:26
  • Yup, hidden from view with almost no way to do any diag or debugging. Very frustrating tool to work with. That ~100ms time to flag a dead node is wicked-fast. I'm currently at 30s, which I know is going to be 'somewhat problematic'. Just haven't gotten to the point on the list where I solve that one. – AJB Dec 15 '14 at 03:31
  • To be fair, it does depend on how the disconnection happens. TCP connections can literally hang for days if you let them. If the process gets killed, the connection is killed straight away, and the management server knows this immediately. If someone trips over a cable somewhere, it will be 10 seconds or so before the management server knows that the connection has been severed. In my case, there is regular data being sent over that management connection, which helps with quickly detecting disconnects. If there wasn't, there is always TCP keepalive which is now configurable in Node.js. – Brad Dec 15 '14 at 03:33
3

You can use Anycast for your webservice for free if using Cloudflare free plan.

0

Digital Ocean now supports Load Balancing of servers itself. It is extremely easy to set up and works great! Saves you having to add in unnecessary components such as nginx (if you only want to use for load balancing).

We were having issues using SSL file uploads with nginx on a digital ocean server, however since the Digital Ocean update, we have removed nginx and now use Digital Ocean's load balancing feature and it works just as we need it to!

Sean _space
  • 112
  • 7
  • I'm not sure why anyone would have down voted this. Upvote to balance it out. And, yes, I saw that too. DO is making great headway, and will eventually catch up. Hopefully sooner than later, I'd much rather give them my money. – AJB Aug 09 '18 at 08:58
  • 1
    Maybe they wanted more detail as to how to sign into digital ocean, and then going to Networking -> Load Balancers -> Create Load Balancer! Yeah, we have most of our infrastructure switched over to DO now, the more money the community pump in, the quicker and more advanced these features will be! – Sean _space Aug 10 '18 at 09:06
  • 1
    DO Load balancing is still single-region only, that's the downside. – Kenny Grant Nov 05 '18 at 11:51
  • 1
    with CloudFare and DigitalOcean one can achieve same or better result with more affordable prices. no free lunch AWS and DO have their own pro cons. another benefit of CloudFare is you can go different vendors like OVH. – Gurjinder Singh Mar 18 '21 at 04:41